基于boundingRect中心镜像选定的QGraphicsItems

问题描述 投票:0回答:1

对于我来说,我似乎无法弄清楚如何根据所选的中心来镜像所选的 QGraphicsItems?我尝试的每一次尝试都会使物品最终从屏幕上射出。我在 3d 应用程序中重新创建了相同的设置并使其正常工作,但我遗漏了一些东西并且无法弄清楚。任何帮助将非常感激。最终目标是能够单击“镜像”按钮,然后将它们来回翻转,如下图所示......

import sys
from PySide2 import QtWidgets, QtCore, QtGui
from PySide2.QtGui import QPixmap, QPainter, QColor
import os
import random


class ImageWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self.resize(1500,1080)

        # controls
        self.scene = QtWidgets.QGraphicsScene(self)
        self.scene.setBackgroundBrush(QColor(40,40,40))
        
        self.graphicsView = QtWidgets.QGraphicsView(self)
        self.graphicsView.setSceneRect(-4000, -4000, 8000, 8000)
        self.graphicsView.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform)
        self.graphicsView.setScene(self.scene)

        # align actions
        self.mirrorItemsHorizontalAct = QtWidgets.QAction('Mirror Horizontal', self)
        self.mirrorItemsHorizontalAct.triggered.connect(self.mirror_items_horizontal)

        transformToolbar = QtWidgets.QToolBar('Transform', self)
        transformToolbar.addAction(self.mirrorItemsHorizontalAct)
        self.addToolBar(transformToolbar)

        self.setCentralWidget(self.graphicsView)

        # Load images from subfolder
        self.create_shapes()


    def create_shapes(self):
        gradient = QtGui.QLinearGradient(0, 0, 100, 0)
        gradient.setColorAt(0, QtGui.QColor(0, 0, 255))
        gradient.setColorAt(1, QtGui.QColor(255, 0, 0))

        rectA = QtWidgets.QGraphicsRectItem(0,0,150,100)
        rectA.setBrush(QtGui.QBrush(gradient))
        rectA.setPen(QtGui.QPen(QtCore.Qt.NoPen))
        rectA.setPen(QtGui.QPen(QtCore.Qt.green, 10, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
        rectA.setFlags(QtWidgets.QGraphicsItem.ItemIsSelectable | QtWidgets.QGraphicsItem.ItemIsMovable)
        rectA.setSelected(True)
        rectA.setPos(-120,50)
        self.scene.addItem(rectA)

        rectB = QtWidgets.QGraphicsRectItem(0,0,70,35)
        rectB.setBrush(QtGui.QBrush(gradient))
        rectB.setPen(QtGui.QPen(QtCore.Qt.NoPen))
        rectB.setFlags(QtWidgets.QGraphicsItem.ItemIsSelectable | QtWidgets.QGraphicsItem.ItemIsMovable)
        rectB.setSelected(True)
        rectB.setPos(150,-150)
        self.scene.addItem(rectB)

        rectC = QtWidgets.QGraphicsRectItem(0,0,120,75)
        rectC.setBrush(QtGui.QBrush(gradient))
        rectC.setPen(QtGui.QPen(QtCore.Qt.NoPen))
        rectC.setFlags(QtWidgets.QGraphicsItem.ItemIsSelectable | QtWidgets.QGraphicsItem.ItemIsMovable)
        rectC.setSelected(True)
        rectC.setPos(400,70)
        self.scene.addItem(rectC)


    # Align
    def mirror_items_horizontal(self):
        items = self.scene.selectedItems()
        if not items:
            items = self.scene.items()   
        
        # Calculate the collective bounding box
        collectivePath = QtGui.QPainterPath()
        for item in items:
            collectivePath.addRect(item.sceneBoundingRect())
        collectiveRect = collectivePath.boundingRect()

        # Calculate the horizontal center of the collective bounding box
        centerX = collectiveRect.center().x()

        # print('centerX:', centerX)
        # print('collectiveRect:', collectiveRect)

        # Debug
        cItem = QtWidgets.QGraphicsRectItem(collectiveRect)
        cItem.setBrush(QtGui.QBrush(QtGui.QColor(255,0,0,128)))
        cItem.setZValue(-1000)
        self.scene.addItem(cItem)

        cItem = QtWidgets.QGraphicsEllipseItem(0, 0, 10, 10)
        cItem.setPos(collectiveRect.center() - QtCore.QPointF(cItem.boundingRect().width()*0.5, cItem.boundingRect().height()*0.5) )
        cItem.setBrush(QtGui.QBrush(QtGui.QColor(255,255,0,128)))
        cItem.setZValue(-1000)
        self.scene.addItem(cItem)

        for item in items:
            local_offset = item.boundingRect().center().x() * 2.0
            global_offset = collectiveRect.width() + item.boundingRect().x() * 2.0
            print('scenePos', item.scenePos())
            print('boundingRect', item.boundingRect())
            print('boundingRect center', item.boundingRect().center())
            print('local_offset', local_offset)
            print('global_offset', global_offset)

            scaleTm = QtGui.QTransform()
            scaleTm.translate(local_offset, 0)
            # scaleTm.translate(global_offset, 0)
            # scaleTm.translate(645, 0)
            scaleTm.scale(-1, 1)

            tm = item.transform() * scaleTm
            item.setTransform(tm)


if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    mainWindow = ImageWindow()
    mainWindow.show()
    sys.exit(app.exec_())
python pyside qgraphicsview
1个回答
0
投票

您没有考虑到变换始终应用于其原点(

0, 0
),而您想要基于参考点(即“选择”的中心)来镜像项目。

应用此类转换的程序是:

  1. 翻译到参考点;
  2. 设置变换;
  3. 将翻译重置为原点;

然后,你的尝试中还有另一个问题,这最初也让我感到困惑。您要将新的转换与现有的转换相结合,但这必须非常小心,如有关

*
运算符:

的文档中所述

请注意,矩阵乘法不可交换,即

a*b != b*a

与常见的乘法不同(我们知道乘法的顺序并不重要),由于变换矩阵的复杂关系,情况并非如此。由于镜像基于 current 变换(也可以镜像和平移),因此必须在 before 之前应用它,因此顺序实际上是相反的:

scaleTm * item.transform()

如果您考虑一下最终根据每个变换步骤映射一个点的顺序(映射 from 当前的点,然后映射 to 新的点),它就更有意义了(参见 this 相关帖子)。

最后,请注意,为了保持一致性,即使您只应用水平变换,您也应该始终考虑映射点的完整坐标(而不仅仅是 x)。另外,由于

transform()
考虑项目 scale()
rotation()
,因此您必须避免它们,并且应用于该项目的任何进一步缩放或旋转都必须在当前项目 
transform()
 内完成。 

在下面的示例中,我添加了一个自定义类来将“选择”显示为单独的项目,这样就不会出现累积绘图。

class CollectiveRect(QGraphicsObject): def __init__(self): super().__init__() self.setZValue(-1000) self.rect = QRectF() self.brush = QBrush(QColor(255, 0, 0, 128)) self.center = QGraphicsEllipseItem(-5, -5, 10, 10, self) self.center.setBrush(QBrush(QColor(255, 255, 0, 128))) self.hideAni = QPropertyAnimation(self, b'opacity') self.hideAni.setDuration(1000) self.hideAni.setStartValue(1.) self.hideAni.setEndValue(0.) self.hideTimer = QTimer(self, singleShot=True, interval=2000, timeout=self.hideAni.start) self.hide() def boundingRect(self): return self.rect | self.childrenBoundingRect() def setRect(self, *args): self.rect.setRect(*QRectF(*args).getRect()) self.center.setPos(self.rect.center()) self.prepareGeometryChange() if self.hideAni.state(): self.hideAni.stop() self.setOpacity(1) self.show() self.hideTimer.start() def paint(self, qp, opt, widget=None): qp.setBrush(self.brush) qp.drawRect(self.rect) class ImageWindow(QMainWindow): ... def create_shapes(self): ... self.collectiveRect = CollectiveRect() self.scene.addItem(self.collectiveRect) def mirror_items_horizontal(self): items = self.scene.selectedItems() if not items: for item in self.scene.items(): if item.flags() & QGraphicsItem.ItemIsSelectable: item.setSelected(True) items.append(item) if not items: return # Calculate the collective bounding box collectiveRect = QRectF() for item in items: collectiveRect |= item.sceneBoundingRect() self.collectiveRect.setRect(collectiveRect) center = collectiveRect.center() for item in items: reference = item.mapFromScene(center) scaleTm = QTransform() scaleTm.translate(reference.x(), reference.y()) scaleTm.scale(-1, 1) scaleTm.translate(-reference.x(), -reference.y()) item.setTransform(scaleTm * item.transform())
    
© www.soinside.com 2019 - 2024. All rights reserved.