I have QGraphicsItem
objects in a scene that I move jointly using a QGraphicsItemGroup
, as per this answer.
While the group is alive, it keeps its own transforms. Now once it is destroyed, these transforms cease to exist, and must be dispatched. My question is how is it done — in particular for the translation.
I have made a small example where a rectangle is displayed, then scaled and translated after pauses of 5 seconds. The object's transformations are displayed after each step.
from PyQt5.QtCore import Qt, QThread
from PyQt5.QtGui import QTransform
from PyQt5.QtWidgets import QApplication, QGraphicsRectItem, QGraphicsScene, QGraphicsView, QMainWindow
import sys
import time
def print_transforms(obj):
t = obj.transform()
print(t.m11(), t.m12(), t.m13(), t.m21(), t.m22(), t.m23(), t.m31(), t.m32(), t.m33(), t.dx(), t.dy())
assert len(obj.transformations()) == 0
print(obj.rotation(), obj.scale())
class Thread(QThread):
def run(self):
print('initial transforms')
print_transforms(rect)
time.sleep(5)
group = scene.createItemGroup([rect])
group.setTransform(QTransform().scale(1.5, 1.5))
scene.destroyItemGroup(group)
print('after scaling')
print_transforms(rect)
time.sleep(5)
group = scene.createItemGroup([rect])
group.setTransform(QTransform().translate(100., 100.))
scene.destroyItemGroup(group)
print('after translation')
print_transforms(rect)
time.sleep(5)
app = QApplication([])
window = QMainWindow()
window.setGeometry(100, 100, 400, 400)
view = QGraphicsView()
scene = QGraphicsScene()
view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
rect = QGraphicsRectItem(0, 0, 150, 150)
scene.addItem(rect)
view.setScene(scene)
window.setCentralWidget(view)
window.show()
thread = Thread()
thread.finished.connect(app.exit)
thread.start()
sys.exit(app.exec_())
It prints the following:
initial transforms
1.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 1.0 0.0 0.0
0.0 1.0
after scaling
1.5 0.0 0.0 0.0 1.5 0.0 0.0 0.0 1.0 0.0 0.0
0.0 1.0
after translation
1.5 0.0 0.0 0.0 1.5 0.0 0.0 0.0 1.0 0.0 0.0
0.0 1.0
So we start from an identity transform matrix, and after the scaling it is changed to a scaling matrics to hold the scaling factor.
However, after the translation, the transformation matrix is not changed, despite the fact that the rectangle has indeed moved.
So my question is, where is the group's translation dispatched after it has been destroyed?
First of all do not use QThread + sleep to make a delay since you may have strange behaviors, Qt alerts you by pointing to the warning on the console:
initial transforms
1.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 1.0 0.0 0.0
0.0 1.0
QObject::startTimer: Timers cannot be started from another thread
QObject::startTimer: Timers cannot be started from another thread
after scaling
1.5 0.0 0.0 0.0 1.5 0.0 0.0 0.0 1.0 0.0 0.0
0.0 1.0
QObject::startTimer: Timers cannot be started from another thread
QObject::startTimer: Timers cannot be started from another thread
after translation
1.5 0.0 0.0 0.0 1.5 0.0 0.0 0.0 1.0 0.0 0.0
0.0 1.0
Instead use QTimer.
Going to the problem, let's analyze the position of the item as the changes are applied:
from PyQt5.QtCore import Qt, QObject, QTimer
from PyQt5.QtGui import QTransform
from PyQt5.QtWidgets import (
QApplication,
QGraphicsRectItem,
QGraphicsScene,
QGraphicsView,
QMainWindow,
)
import sys
def print_transforms(obj):
t = obj.transform()
print(
t.m11(),
t.m12(),
t.m13(),
t.m21(),
t.m22(),
t.m23(),
t.m31(),
t.m32(),
t.m33(),
t.dx(),
t.dy(),
)
assert len(obj.transformations()) == 0
print(obj.rotation(), obj.scale())
class Test(QObject):
def initial(self):
print(rect.pos())
print("initial transforms")
print_transforms(rect)
QTimer.singleShot(5000, self.scaling)
def scaling(self):
print(rect.pos())
group = scene.createItemGroup([rect])
group.setTransform(QTransform().scale(1.5, 1.5))
scene.destroyItemGroup(group)
print("after scaling")
print_transforms(rect)
QTimer.singleShot(5000, self.translation)
print(rect.pos())
def translation(self):
print(rect.pos())
group = scene.createItemGroup([rect])
group.setTransform(QTransform().translate(100.0, 100.0))
print(rect.pos())
scene.destroyItemGroup(group)
print("after translation")
print_transforms(rect)
print(rect.pos())
QTimer.singleShot(1000, QApplication.quit)
if __name__ == "__main__":
app = QApplication([])
window = QMainWindow()
window.setGeometry(100, 100, 400, 400)
view = QGraphicsView()
scene = QGraphicsScene()
view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
rect = QGraphicsRectItem(0, 0, 150, 150)
scene.addItem(rect)
view.setScene(scene)
window.setCentralWidget(view)
window.show()
test = Test()
test.initial()
sys.exit(app.exec_())
And you get the following:
PyQt5.QtCore.QPointF()
initial transforms
1.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 1.0 0.0 0.0
0.0 1.0
PyQt5.QtCore.QPointF()
after scaling
1.5 0.0 0.0 0.0 1.5 0.0 0.0 0.0 1.0 0.0 0.0
0.0 1.0
PyQt5.QtCore.QPointF()
PyQt5.QtCore.QPointF()
PyQt5.QtCore.QPointF()
after translation
1.5 0.0 0.0 0.0 1.5 0.0 0.0 0.0 1.0 0.0 0.0
0.0 1.0
PyQt5.QtCore.QPointF(100.0, 100.0)
Where it is seen that the translation is applied to the position of the item and not to QTransform
since when using destroyItemGroup()
that indicates:
void QGraphicsScene::destroyItemGroup(QGraphicsItemGroup *group)
Reparents all items in group to group's parent item, then removes group from the scene, and finally deletes it. The items' positions and transformations are mapped from the group to the group's parent.
(emphasis mine)
In conclusion: When the group is destroyed, all the transformations are passed to the items except the translation since the item is moved using setPos.