Повернутый QGraphicsRectItem перемещается случайным образом при перетаскивании с помощью мыши

У меня есть подвижный QGraphicsRectItem, который повернут на 90 градусов и установлен на сцену. Когда я перетаскиваю предмет, он перемещается случайным образом и в конечном итоге исчезает.

Однако, когда я устанавливаю вращение на 0, элемент перемещается безупречно.

Вот мой минимально воспроизводимый пример.

      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.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())

(Я включил ползунок в нижней части главного окна, чтобы удобно вращать элемент)

Почему это происходит?

1 ответ

Проблема вызвана различными аспектами:

  • установка QRect в заданных координатах, сохраняя элемент в той же позиции по умолчанию ();
  • изменение прямоугольника вследствие события перемещения мыши;
  • после этого изменить точку начала трансформации;
  • отображение координат мыши между целочисленной точкой (на экране) и плавающей (на сцене);
  • преобразование (вращение);
  • реализация перемещения элемента без учета вышеизложенного (в то время как QGraphicsItem уже предоставляет ему флаг);

Обратите внимание: хотя вращение может показаться простой операцией, оно достигается с помощью комбинации двух преобразований: сдвига и масштабирования; это означает, что преобразование применяет очень сложные вычисления, которые зависят от точности с плавающей запятой.
Это становится проблемой при преобразовании целых чисел в числа с плавающей запятой: одна и та же координата мыши (основанная на целых числах) может отображаться в очень разных точках в зависимости от преобразования, и чем «выше» применяется преобразование, тем больше может быть разница. В результате отображаемое положение мыши может сильно отличаться, прямоугольник переводится в «неправильную» точку, а исходная точка преобразования перемещает прямоугольник «в сторону» в увеличивающемся соотношении.

Решение состоит в том, чтобы полностью изменить способ позиционирования прямоугольника и фактически упростить ссылку: прямоугольник всегда центрируется в позиции элемента, так что мы можем сохранить исходную точку преобразования по умолчанию ( в координатах объекта).

Единственное неудобство такого подхода заключается в том, что предмет больше не будет в верхнем левом углу, но это не настоящая проблема: когда элемент поворачивается, его верхний левый угол все равно не будет в этом положении.

Если вам нужно знать фактическое положение элемента, вы можете затем перевести прямоугольник в зависимости от положения элемента в сцене.
Если вы хотите расположить прямоугольник на основе его верхнего левого угла, вам необходимо сопоставить положение со сцены и вычислить дельту опорной точки ( фактический верхний левый угол).

Я взял на себя смелость ответить на ваш предыдущий вопрос, касающийся изменения размера, и улучшить его, чтобы лучше показать, как работает решение.

      class Selection(QtWidgets.QGraphicsRectItem):
    Left, Top, Right, Bottom = 1, 2, 4, 8
    def __init__(self, *args):
        rect = QtCore.QRectF(*args)
        pos = rect.center()
        # move the center of the rectangle to 0, 0
        rect.translate(-rect.center())
        super().__init__(rect)
        self.setPos(pos)
        self.setPen(QtGui.QPen(QtCore.Qt.red, 5))
        self.setFlags(
            self.ItemIsMovable | 
            self.ItemIsSelectable | 
            self.ItemSendsGeometryChanges
        )

    def mapRect(self):
        return QtCore.QRectF(
            self.mapToScene(self.rect().topLeft()), 
            self.rect().size()
        )

    def setRectPosition(self, pos):
        localPos = self.mapFromScene(pos)
        delta = self.rect().topLeft() - localPos
        self.setPos(self.pos() + delta)

    def itemChange(self, change, value):
        if change in (self.ItemPositionHasChanged, self.ItemRotationHasChanged):
            print(self.mapRect())
        return super().itemChange(change, value)

    def mousePressEvent(self, event):
        super().mousePressEvent(event)
        pos = event.pos()
        rect = self.rect()
        margin = self.pen().width() / 2
        self.anchor = 0

        if pos.x() <= rect.x() + margin:
            self.anchor |= self.Left
        elif pos.x() >= rect.right() - margin:
            self.anchor |= self.Right

        if pos.y() <= rect.y() + margin:
            self.anchor |= self.Top
        elif pos.y() >= rect.bottom() - margin:
            self.anchor |= self.Bottom

        if self.anchor:
            self.clickAngle = QtCore.QLineF(QtCore.QPointF(), pos).angle()
        else:
            super().mousePressEvent(event)

    def mouseMoveEvent(self, event):
        if not self.anchor:
            super().mouseMoveEvent(event)
            return
        rect = self.rect()
        pos = event.pos()
        if self.anchor == self.Left:
            rect.setLeft(pos.x())
        elif self.anchor == self.Right:
            rect.setRight(pos.x())
        elif self.anchor == self.Top:
            rect.setTop(pos.y())
        elif self.anchor == self.Bottom:
            rect.setBottom(pos.y())
        else:
            # clicked on a corner, let's rotate
            angle = QtCore.QLineF(QtCore.QPointF(), pos).angle()
            rotation = max(0, min(90, self.rotation() + self.clickAngle - angle))
            self.setRotation(rotation)
            return
        pos = self.mapToScene(rect.center())
        self.setPos(pos)
        rect.moveCenter(QtCore.QPointF())
        self.setRect(rect)
Другие вопросы по тегам