【问题标题】:Seems impossible to rotate custom QGraphicsItem using QPainter (or any other method.)似乎不可能使用 QPainter (或任何其他方法)旋转自定义 QGraphicsItem 。
【发布时间】:2017-05-18 02:37:24
【问题描述】:

这里是代码。它运行。展示这个错误。右键单击椭圆,通过在椭圆上单击拖动来缩放它。右键单击它>“完成编辑”。然后用“旋转”做同样的事情。

我已经尝试了超过 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_())

【问题讨论】:

  • 能够旋转其虚线选择 QPainterPath,所以我将尝试将椭圆转换为路径。一定是bug!我什至尝试使用 setTransformations() 为缩放和旋转设置两个独立的转换。规模仍然照常工作。

标签: qt rotation transform qgraphicsitem qpainter


【解决方案1】:

让它工作。这是操作旋转/缩放的顺序。永远不要用圆圈来测试你的旋转代码,哈哈!

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多