I have a movable QGraphicsRectItem which is rotated to 90 degrees and set to a scene. When I drag the item, it moves randomly and eventually disappear.
However, when I set the rotation to 0, the item moves flawlessly.
Here is my minimal reproducible example.
class main_window(QWidget):
def __init__(self):
super().__init__()
self.rect = Rectangle(100, 100, 100, 100)
self.rect.setRotation(90)
self.view = QGraphicsView(self)
self.scene = QGraphicsScene(self.view)
self.scene.addItem(self.rect)
self.view.setSceneRect(0, 0, 500,500)
self.view.setScene(self.scene)
self.slider = QSlider(QtCore.Qt.Horizontal)
self.slider.setMinimum(0)
self.slider.setMaximum(90)
vbox = QVBoxLayout(self)
vbox.addWidget(self.view)
vbox.addWidget(self.slider)
self.setLayout(vbox)
self.slider.valueChanged.connect(self.rotate)
def rotate(self, value):
self.angle = int(value)
self.rect.setRotation(self.angle)
class Rectangle(QGraphicsRectItem):
def __init__(self, *args):
super().__init__(*args)
self.setFlag(QGraphicsItem.ItemIsMovable, True)
self.setFlag(QGraphicsItem.ItemIsSelectable, True)
self.setFlag(QGraphicsItem.ItemSendsScenePositionChanges, True)
self.setPen(QPen(QBrush(QtGui.QColor('red')), 5))
self.selected_edge = None
self.first_pos = None
self.click_rect = None
def mousePressEvent(self, event):
self.first_pos = event.pos()
self.rect_shape = self.rect()
self.click_rect = self.rect_shape
super().mousePressEvent(event)
def mouseMoveEvent(self, event):
# Calculate how much the mouse has moved since the click.
self.pos = event.pos()
x_diff = self.pos.x() - self.first_pos.x()
y_diff = self.pos.y() - self.first_pos.y()
# Start with the rectangle as it was when clicked.
self.rect_shape = QtCore.QRectF(self.click_rect)
self.rect_shape.translate(x_diff, y_diff)
self.setRect(self.rect_shape)
self.setTransformOriginPoint(self.rect_shape.center())
(I included a slider at the bottom of the main window to conveniently rotate the item)
Why does this happen?
The issue is caused by various aspects:
0, 0
);ItemIsMovable
flag);Note that while rotation might seem a simple operation, it is achieved by using a combination of two transformations: shearing and scaling; this means that the transformation applies very complex computations that depend on the floating point precision.
This becomes an issue when dealing with integer to floating conversion: the same mouse (integer based) coordinate can be mapped at a very different point depending on the transformation, and the "higher" the transformation is applied, the bigger the difference can be. As a result, the mapped mouse position can be very different, the rectangle is translated to a "wrong" point, and the transformation origin point moves the rectangle "away" by an increasing ratio.
The solution is to completely change the way the rectangle is positioned and actually simplify the reference: the rectangle is always centered at the item position, so that we can keep the transformation origin point at the default (
0, 0
in item coordinates).The only inconvenience with this approach is that the item's
pos()
will not be on its top left corner anymore, but that is not a real issue: when the item is rotated, its top left corner would not be at that position anyway.If you need to know the actual position of the item, you can then translate the rectangle based on the item scene position.
If you want to position the rectangle based on its top left corner, you have to map the position from the scene and compute the delta of the reference point (the actual top left corner).
I took the liberty of taking your previous question, which implemented the resizing, and improving it also to better show how the solution works.