I am trying to perform flipping on a QGraphicsItem
that has child and grandchild QGraphicsItem
.
The original item looks like this:
(The blue rectangle and text are child and the number inside it is grandchild
I apply the following transformation to the parent item:
parentItem.setTransformOriginPoint(parentItem.boundingRect().center())
parentItem.setTransform(QTransform.fromScale(-1, 1))
Result after flipping parent item:
Since I want to reflip the text and number to be readable, I attempt to re-flip them after the parent's transformation as followed:
# For the child text
child.setTransformOriginPoint(child.boundingRect().center())
child.setTransform(QTransform.fromScale(-1, 1), True)
...
# For the grandchild number
grandchild.setTransformOriginPoint(grandchild.boundingRect().center())
grandchild.setTransform(QTransform.fromScale(-1, 1), True)
Here is the result after re-flipped the child and grandchild item:
.
It seems that the translation is not correct. Can someone advice?
Thanks!
Minimal reproducible example below:
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
class ParentItem(QGraphicsRectItem):
def __init__(self, pos, name, parent=None):
w, h = 550, 220
super().__init__(-w/2, -h/2, w, h)
self.setPos(pos)
self.name = ChildText(self.boundingRect().topLeft() - QPointF(0, 100), f">NAME{name}", self)
self.value = ChildText(self.boundingRect().topLeft()- QPointF(0, 50), f">VALUE_{name}", self)
self.ChildPad1 = ChildPad(QPointF(-150, 0), "1", self)
self.ChildPad2 = ChildPad(QPointF(+150, 0), "2", self)
self.color = QColor(192, 192, 192)
self.setPen(QPen(self.color, 5))
self.setFlag(self.ItemIsMovable, True)
def flipParent(self):
self.setTransformOriginPoint(self.boundingRect().center())
self.setTransform(QTransform.fromScale(-1, 1))
def reflipChilds(self):
# Child Texts
self.name.setTransformOriginPoint(self.name.boundingRect().center())
self.name.setTransform(QTransform.fromScale(-1, 1))
self.value.setTransformOriginPoint(self.value.boundingRect().center())
self.value.setTransform(QTransform.fromScale(-1, 1))
# GrandChild Numbers
for child in self.childItems():
if isinstance(child, ChildPad):
child.Number.setTransformOriginPoint(child.Number.boundingRect().center())
child.Number.setTransform(QTransform.fromScale(-1, 1))
class ChildText(QGraphicsTextItem):
def __init__(self, pos, text=">Text", parent=None):
super().__init__(parent)
self.setPos(pos)
self.parent = parent
self.text = text
self.color = QColor(255, 0, 0)
self.setDefaultTextColor(self.color)
self.setFlag(self.ItemIsMovable, True)
f = QFont()
f.setPointSizeF(min(self.parent.boundingRect().width()/8, self.parent.boundingRect().height()/8))
self.setFont(f)
self.setHtml(f"<p><center>{self.text}</center></p>")
class ChildPad(QGraphicsRectItem):
def __init__(self, pos, pinNumber, parent=None):
w, h = 200, 100
super().__init__(-w/2, -h/2, w, h, parent)
self.setPos(pos)
self.parent = parent
self.color = QColor(255, 0, 0)
self.setPen(QPen(self.color, Qt.MiterJoin, 1))
self.setBrush(QBrush(self.color))
self.Number = GrandChildNumber(pinNumber, self)
class GrandChildNumber(QGraphicsTextItem):
def __init__(self, pinNumber, parent=None):
super().__init__(parent)
self.parent = parent
self.color = QColor(32, 32, 32)
self.setHtml(f"{pinNumber}")
self.moveToParentCenter()
def moveToParentCenter(self):
f = QFont()
f.setPointSizeF(min(self.parent.boundingRect().width()/4, self.parent.boundingRect().height()/4))
self.setFont(f)
rect = self.boundingRect()
rect.moveCenter(self.parent.boundingRect().center())
self.setPos(rect.topLeft())
self.adjustSize()
def main():
import sys
app = QtWidgets.QApplication(sys.argv)
scene = QGraphicsScene()
# No Transformation applied
originalItem = ParentItem(QPointF(300, 100), "ORIGINAL", scene)
scene.addItem(originalItem)
# Flipped the whole parent item
flipParentItem = ParentItem(QPointF(300, 500), "FLIPP_PARENT", scene)
flipParentItem.flipParent()
scene.addItem(flipParentItem)
# Flipped the whole parent item, then reflip the Text and Number
reflipChildItem = ParentItem(QPointF(300, 900), "REFLIP_CHILDS", scene)
flipParentItem.flipParent()
reflipChildItem.reflipChilds()
scene.addItem(reflipChildItem)
view = QtWidgets.QGraphicsView(scene)
view.setRenderHints(QtGui.QPainter.Antialiasing)
view.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
If you want to ignore transformations, you should use the ItemIgnoresTransformations
flag.
Then, be aware that setTransformation()
doesn't apply the transformation "on top" of the existing one, but completely sets a new transformation on the item (transforms inherited from the parent are not considered).
A proper flip()
function should toggle the transformation, so it must consider the current transform()
and flip it.
Now, the problem with text items is that they always use the origin point as the top left of their contents. While the basic repositioning might work for generic usage, it will not whenever any parent has a transformation.
The problem you're seeing is because you are just mapping the position based on the parent, but since the parent is "flipped", the top left corner of the resulting rect is on the opposite side (relative to the center) in parent coordinates.
To properly get the actual position relative to the parent, you must always use the cumulative transformations, which means map coordinates to the scene and map them back to the parent.
In order to make the code simpler to understand, I moved the repositioning function to the parent of the text item.
class ParentItem(QGraphicsRectItem):
# ...
def flip(self):
self.setTransformOriginPoint(self.boundingRect().center())
self.setTransform(self.transform().scale(-1, 1))
for child in self.childItems():
if isinstance(child, ChildPad):
child.updateNumber()
class ChildPad(QGraphicsRectItem):
def __init__(self, pos, pinNumber, parent=None):
w, h = 200, 100
super().__init__(-w/2, -h/2, w, h, parent)
self.setPos(pos)
self.parent = parent
self.color = QColor(255, 0, 0)
self.setPen(QPen(self.color, Qt.MiterJoin, 1))
self.setBrush(QBrush(self.color))
self.number = GrandChildNumber(pinNumber, self)
self.updateNumber()
def updateNumber(self):
br = self.boundingRect()
f = QFont()
f.setPointSizeF(min(br.width() / 4, br.height() / 4))
self.number.setFont(f)
# get the "visual" rect of the parent in scene coordinates
parentRect = self.mapToScene(br).boundingRect()
rect = self.number.boundingRect()
rect.moveCenter(parentRect.center())
# map the new rect position *from* the scene in local coordinates
topLeft = self.mapFromScene(rect.topLeft())
self.number.setPos(topLeft)
class GrandChildNumber(QGraphicsTextItem):
def __init__(self, pinNumber, parent=None):
super().__init__(parent)
self.parent = parent
self.setFlag(self.ItemIgnoresTransformations)
self.color = QColor(32, 32, 32)
self.setHtml(str(pinNumber))