似乎无法使用 QPainter(或任何其他方法)旋转自定义 QGraphicsItem。

Seems impossible to rotate custom QGraphicsItem using QPainter (or any other method.)

这是代码。它运行。展示这个错误。右键单击椭圆,通过在椭圆上单击并拖动来缩放它。右键单击它 > "Done editing"。然后用 "Rotate."

做同样的事情

我已经尝试了 10 多种不同的排列方式,使用 self.setRotationself.setTransformpainter.rotate 等等...它唯一一次旋转是在我使用 self.setTransform(self.transform().rotate(r)) 但结果是错误的:以错误的顺序缩放和旋转什么的。

from PyQt5.QtWidgets import QGraphicsItem, QMenu
from PyQt5.QtGui import QTransform, QPen, QPainter, QColor, QBrush
from PyQt5.QtCore import Qt, QPointF, QRectF, QEvent
from math import sqrt


def scaleRect(rect, scale_x, scale_y):
    T = QTransform.fromScale(scale_x, scale_y)
    return T.mapRect(rect)

def debugPrintTransformMatrix(T):
    print(str(T.m11()) + '  ' + str(T.m12()) + '  ' + str(T.m13()))
    print(str(T.m21()) + '  ' + str(T.m22()) + '  ' + str(T.m23()))
    print(str(T.m31()) + '  ' + str(T.m32()) + '  ' + str(T.m33()))

# Assumes no shearing or stretching.   
# Only Rotation, Translation, and Scaling. 
def extractTransformScale(T):
    # This is math matrix notation transposed (debug print self.sceneTransform() to see)
    Sx = sqrt(T.m11()**2 + T.m12()**2)      
    Sy = sqrt(T.m21()**2 + T.m22()**2)
    return Sx, Sy    

def extractTransformTranslate(T):
    return T.m31(), T.m32()


class Object(QGraphicsItem):
    def sceneEvent(self, event):
        if event.type() == QEvent.GraphicsSceneMouseMove: 
            # move, scale, or rotate
            if self._mode in ['scale', 'rotate']:
                mouse_pos = event.scenePos()
                last_pos = event.lastScenePos()
                if self._mode == 'scale':
                    s = self.mouseScalingFactors(mouse_pos, last_pos)
                    self.setTransform(self.transform().scale(*s))
                if self._mode == 'rotate':
                    r = self.mouseRotationAngle(mouse_pos, last_pos)
                    self.setRotation(self.rotation() + r)
                return True

        return super().sceneEvent(event)

    def __init__(self):
        super().__init__()
        self.setFlags(QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsFocusable | QGraphicsItem.ItemIsSelectable)
        self._selectionPen = QPen(Qt.black, 1.0, style=Qt.DotLine, cap=Qt.FlatCap)
        self._lastPos = QPointF(0, 0)
        self.setPos(self._lastPos)
        self._mode = 'neutral'

    def setRenderHints(self, painter):
        painter.setRenderHints(QPainter.SmoothPixmapTransform | QPainter.HighQualityAntialiasing | QPainter.TextAntialiasing)

    def boundingRectExtraScale(self):
        return (1.2 , 1.2) 

    def mouseScalingFactors(self, pos, last_pos):
        delta = pos - last_pos
        return (1.01 ** delta.x(), 1.01 ** delta.y())

    def mouseRotationAngle(self, pos, last_pos):
        return 1   #TODO

    def createDefaultContextMenu(self):
        menu = QMenu()
        if self._mode == 'neutral':
            menu.addAction('Scale').triggered.connect(lambda: self.setMode('scale'))
            menu.addAction('Rotate').triggered.connect(lambda: self.setMode('rotate'))
        else:
            menu.addAction('Done Editing').triggered.connect(lambda: self.setMode('neutral'))
        return menu

    def contextMenuEvent(self, event):
            menu = self.createDefaultContextMenu()
            menu.exec(event.screenPos())

    def setMode(self, mode):
        self._mode = mode

    def setSelected(self, selected):
        super().setSelected(selected)
        self.update()


class ShapedObject(Object):
    def __init__(self):
        super().__init__()
        self._shape = {
            'name' : 'ellipse',
            'radius': 35
        }
        self._brush = QBrush(Qt.darkGreen)
        self._pen = QPen(Qt.yellow, 3)        

    def shapeDef(self):
        return self._shape

    def boundingRect(self):
        rect = self.shapeRect()
        s = self.boundingRectExtraScale()
        return scaleRect(rect, *s)

    def shape(self):      #TODO QPainterPath shape for collision detection
        # Should call self.boundingRectExtraScale()
        return super().shape()

    def paint(self, painter, option, widget):
        self.setRenderHints(painter)
        #super().paint(painter, option, widget)

        shape = self.shapeDef()
        name = shape['name']

        painter.setBrush(self._brush)
        painter.setPen(self._pen)

        painter.save()

        # ********** HERE IS THE PROBLEM ************* 

        debugPrintTransformMatrix(painter.transform())
        painter.rotate(5)
        debugPrintTransformMatrix(painter.transform())

        rect = self.shapeRect()
        if name == 'ellipse':
            painter.drawEllipse(rect)


        painter.restore()  


    def shapeRect(self):
        shape = self.shapeDef()
        name = shape['name']
        if name == 'ellipse':
            r = shape['radius']
            rect = QRectF(-r,  -r, 2*r, 2*r)
        return rect


####

import sys
from PyQt5.QtWidgets import QMainWindow, QGraphicsScene, QGraphicsView, QApplication

if __name__ == '__main__':
    app = QApplication(sys.argv)

    window = QMainWindow()
    view = QGraphicsView()
    scene = QGraphicsScene()
    view.setScene(scene)
    window.setCentralWidget(view)

    ellipse = ShapedObject()
    scene.addItem(ellipse)

    window.show()

    sys.exit(app.exec_())

开始使用了。这是旋转/缩放操作的顺序。永远不要用圆来测试你的旋转代码,哈哈!

from PyQt5.QtWidgets import QGraphicsItem, QMenu, QGraphicsRotation, QGraphicsScale
from PyQt5.QtGui import QTransform, QPen, QPainter, QColor, QBrush, QPainterPath
from PyQt5.QtCore import Qt, QPointF, QRectF, QEvent
from math import sqrt


def scaleRect(rect, scale_x, scale_y):
    T = QTransform.fromScale(scale_x, scale_y)
    return T.mapRect(rect)

def debugPrintTransformMatrix(T):
    print(str(T.m11()) + '  ' + str(T.m12()) + '  ' + str(T.m13()))
    print(str(T.m21()) + '  ' + str(T.m22()) + '  ' + str(T.m23()))
    print(str(T.m31()) + '  ' + str(T.m32()) + '  ' + str(T.m33()))

# Assumes no shearing or stretching.   
# Only Rotation, Translation, and Scaling. 
def extractTransformScale(T):
    # This is math matrix notation transposed (debug print self.sceneTransform() to see)
    Sx = sqrt(T.m11()**2 + T.m12()**2)      
    Sy = sqrt(T.m21()**2 + T.m22()**2)
    return Sx, Sy    

def extractTransformTranslate(T):
    return T.m31(), T.m32()


class Object(QGraphicsItem):
    def sceneEvent(self, event):
        if event.type() == QEvent.GraphicsSceneMouseMove: 
            # move, scale, or rotate
            if self._mode in ['scale', 'rotate']:
                mouse_pos = event.scenePos()
                last_pos = event.lastScenePos()
                if self._mode == 'scale':
                    s = self.mouseScalingFactors(mouse_pos, last_pos)
                    self.applyScaleTransform(*s)
                if self._mode == 'rotate':
                    r = self.mouseRotationAngle(mouse_pos, last_pos)
                    self.applyRotateTransform(r)
                return True

        return super().sceneEvent(event)

    def __init__(self):
        super().__init__()
        self.setFlags(QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsFocusable | QGraphicsItem.ItemIsSelectable)
        self._selectionPen = QPen(Qt.black, 1.0, style=Qt.DotLine, cap=Qt.FlatCap)
        self._lastPos = QPointF(0, 0)
        self.setPos(self._lastPos)
        self._mode = 'neutral'
        self._scale = QGraphicsScale()
        self._rotate = QGraphicsRotation()
        self.setTransformations([self._rotate, self._scale])

    def applyRotateTransform(self, angle):
        self._rotate.setAngle(self._rotate.angle() + 15)

    def applyScaleTransform(self, sx, sy):
        self._scale.setXScale(sx * self._scale.xScale())
        self._scale.setYScale(sy * self._scale.yScale())

    def setRenderHints(self, painter):
        painter.setRenderHints(QPainter.SmoothPixmapTransform | QPainter.HighQualityAntialiasing | QPainter.TextAntialiasing)

    def boundingRectExtraScale(self):
        return (1.2 , 1.2) 

    def mouseScalingFactors(self, pos, last_pos):
        delta = pos - last_pos
        return (1.01 ** delta.x(), 1.01 ** delta.y())

    def mouseRotationAngle(self, pos, last_pos):
        return 1   #TODO

    def createDefaultContextMenu(self):
        menu = QMenu()
        if self._mode == 'neutral':
            menu.addAction('Scale').triggered.connect(lambda: self.setMode('scale'))
            menu.addAction('Rotate').triggered.connect(lambda: self.setMode('rotate'))
        else:
            menu.addAction('Done Editing').triggered.connect(lambda: self.setMode('neutral'))
        return menu

    def contextMenuEvent(self, event):
            menu = self.createDefaultContextMenu()
            menu.exec(event.screenPos())

    def setMode(self, mode):
        self._mode = mode

    def setSelected(self, selected):
        super().setSelected(selected)
        self.update()


class ShapedObject(Object):
    def __init__(self):
        super().__init__()
        self._shape = {
            'name' : 'ellipse',
            'radius': 35
        }
        self._brush = QBrush(Qt.darkGreen)
        self._pen = QPen(Qt.yellow, 3)        

    def shapeDef(self):
        return self._shape

    def boundingRect(self):
        rect = self.shapeRect()
        s = self.boundingRectExtraScale()
        return scaleRect(rect, *s)

    def shape(self):      #TODO QPainterPath shape for collision detection
        # Should call self.boundingRectExtraScale()
        return super().shape()

    def paint(self, painter, option, widget):
        self.setRenderHints(painter)
        #super().paint(painter, option, widget)

        shape = self.shapeDef()
        name = shape['name']

        painter.setBrush(self._brush)
        painter.setPen(self._pen)

        painter.save()
        path = QPainterPath()

        if name == 'ellipse':
            r = shape['radius']
            path.addEllipse(QPointF(0, 0), r, r)

        painter.drawPath(path)
        painter.restore()  

    def shapeRect(self):
        shape = self.shapeDef()
        name = shape['name']
        if name == 'ellipse':
            r = shape['radius']
            rect = QRectF(-r,  -r, 2*r, 2*r)
        return rect


####

import sys
from PyQt5.QtWidgets import QMainWindow, QGraphicsScene, QGraphicsView, QApplication

if __name__ == '__main__':
    app = QApplication(sys.argv)

    window = QMainWindow()
    view = QGraphicsView()
    scene = QGraphicsScene()
    view.setScene(scene)
    window.setCentralWidget(view)

    ellipse = ShapedObject()
    scene.addItem(ellipse)

    window.show()

    sys.exit(app.exec_())