Hi Martin
Well …
At this point I can definitely say I’m not much of a graphics type guy !
I’ve been playing around with the your code and have run into a different set of problems. I want to be able to drag a curved line not a straight one. I modified your code to extend the line as the circle is dragged and this works. When I complete dragging I want to add a triangle (image for now) to the end of line. The problems occur when attempting to figure the direction and angle of the line and set the triangle accordingly. There is some logic I’ve copied from online which I’ve added which returns the direction (Up, Down, Left, Right) and this is generally fine but setting the angle for the image just comes up wrong. I did find that you need to set the Transform Origin Point prior to calling the setRotation but it still doesn’t set the angle properly for me.
Code:
import math
import sys
from PyQt6.QtCore import QLineF, QRectF, Qt
from PyQt6.QtGui import QBrush, QPainter, QPen, QPixmap
from PyQt6.QtWidgets import (
QApplication,
QGraphicsEllipseItem,
QGraphicsLineItem,
QGraphicsScene,
QGraphicsView, QGraphicsPixmapItem,
)
def determine_direction(start_pos, last_pos):
direction_arr = [[]]
# Vector Diffs
dx = last_pos.x() - start_pos.x()
dy = last_pos.y() - start_pos.y()
if abs(dx) > abs(dy):
if dx > 0:
direction = "Right"
else:
direction = "Left"
else:
if dy > 0:
direction = "Down"
else:
direction = "Up"
direction_arr[0].append(direction)
angle_rad = math.atan2(-dy, dx)
angle_deg = math.degrees(angle_rad)
if angle_deg < 0:
angle_deg += 360
direction_arr[0].append(angle_deg)
print(direction_arr[0][0] + ": " + str(direction_arr[0][1]))
return direction_arr
class DraggableCircle(QGraphicsEllipseItem):
def __init__(self, radius=20, *args, **kwargs):
super().__init__(*args, **kwargs)
self.triangle = QGraphicsPixmapItem(QPixmap("./images/route_end.png"))
# Draw the circle
self.setRect(QRectF(-radius, -radius, radius * 2, radius * 2))
# Make the item movable and selectable
self.setFlag(QGraphicsEllipseItem.GraphicsItemFlag.ItemIsMovable)
self.setFlag(QGraphicsEllipseItem.GraphicsItemFlag.ItemIsSelectable)
# Simple styling
self.setBrush(QBrush(Qt.GlobalColor.yellow))
self.setPen(QPen(Qt.GlobalColor.black, 3))
# For tracking the drag line. We'll create the line when
# we start the drag, and store the origin.
self._line_item = None
self._line_origin = None
self._last_point = None
self._points_arr = []
def mousePressEvent(self, event):
if event.button() == Qt.MouseButton.LeftButton:
# Store the origin of the drag in scene coordinates
# We'll use the center of the circle at the moment the drag begins
self._line_origin = self.sceneBoundingRect().center()
# Create a new line starting and ending at the origin.
if self.scene() is not None:
line = QLineF(self._line_origin, self._line_origin)
self._points_arr.append(self._line_origin)
self._line_item = QGraphicsLineItem(line)
# Put the line behind the circle
self._line_item.setZValue(self.zValue() - 1)
self.scene().addItem(self._line_item)
self._last_point = self._line_origin
# Let the base class handle the default behavior (moving the item)
super().mousePressEvent(event)
def mouseReleaseEvent(self, event):
if event.button() == Qt.MouseButton.LeftButton:
width = self.triangle.boundingRect().width()
height = self.triangle.boundingRect().height()
self.triangle.setPos(self._last_point.x() - width / 2,
self._last_point.y() - height / 2)
self.triangle.setTransformOriginPoint(self.triangle.boundingRect().center())
if len(self._points_arr) > 100:
start_point = self._points_arr[len(self._points_arr) - 40]
elif len(self._points_arr) > 50:
start_point = self._points_arr[len(self._points_arr) - 20]
else:
start_point = self._line_origin
print("Line Length: " + str(len(self._points_arr)) + "Portion: " + str(start_point))
self._points_arr.clear()
rtn_arr = determine_direction(start_point, self._last_point)
if len(rtn_arr) > 0:
if len(rtn_arr[0]) > 1:
deg = rtn_arr[0][1]
self.triangle.setRotation(deg)
if self.triangle.scene() is None:
self.scene().addItem(self.triangle)
self.setPos(self._line_origin)
def mouseMoveEvent(self, event):
# First let the base class move the item
super().mouseMoveEvent(event)
# Then update the line endpoint based on the new position
if self._line_item is not None:
current_center = self.sceneBoundingRect().center()
# Extend Line from last point
line = QLineF(self._last_point, current_center)
self._points_arr.append(current_center)
self._line_item = QGraphicsLineItem(line)
pen = QPen(Qt.GlobalColor.blue, 3)
self._line_item.setPen(pen)
# Put the line behind the circle
self._line_item.setZValue(self.zValue() - 1)
self.scene().addItem(self._line_item)
self._last_point = current_center
class GraphicsViewDemo(QGraphicsView):
def __init__(self, parent=None):
super().__init__(parent)
# Create the scene.
scene = QGraphicsScene(0, 0, 876, 536)
# Create the movable circle and add it to the scene centered
circle = DraggableCircle()
circle.setPos(scene.width() / 2 - circle.boundingRect().width() / 2,
scene.height() / 2 - circle.boundingRect().height() / 2)
scene.addItem(circle)
self.setScene(scene)
# Enable antialiasing for smoother edges on the shapes/lines.
self.setRenderHint(QPainter.RenderHint.Antialiasing)
app = QApplication(sys.argv)
view = GraphicsViewDemo()
view.show()
app.exec()
Screen: