Issue
I want to use QPropertyAnimation on QGraphicsItem, hoping the rect item can move from point(100, 30) to point(100, 90). But why the rect is moving itself on the right side of the window? The x coordinate 100 should make the rect move at the middle according to the Scene's size.
Here is my code:
import sys
from PyQt5.QtCore import QPropertyAnimation, QPointF, QRectF
from PyQt5.QtWidgets import QApplication, QGraphicsEllipseItem, QGraphicsScene, QGraphicsView, \
QGraphicsObject
class CustomRect(QGraphicsObject):
def __init__(self):
super(CustomRect, self).__init__()
def boundingRect(self):
return QRectF(100, 30, 100, 30)
def paint(self, painter, styles, widget=None):
painter.drawRect(self.boundingRect())
class Demo(QGraphicsView):
def __init__(self):
super(Demo, self).__init__()
self.resize(300, 300)
self.scene = QGraphicsScene()
self.scene.setSceneRect(0, 0, 300, 300)
self.rect = CustomRect()
self.ellipse = QGraphicsEllipseItem()
self.ellipse.setRect(100, 180, 100, 50)
self.scene.addItem(self.rect)
self.scene.addItem(self.ellipse)
self.setScene(self.scene)
self.animation = QPropertyAnimation(self.rect, b'pos')
self.animation.setDuration(1000)
self.animation.setStartValue(QPointF(100, 30))
self.animation.setEndValue(QPointF(100, 90))
self.animation.setLoopCount(-1)
self.animation.start()
if __name__ == '__main__':
app = QApplication(sys.argv)
demo = Demo()
demo.show()
sys.exit(app.exec_())
Solution
It seems that they do not know the different coordinate systems of the Graphics View Framework.
In this system there are at least the following coordinate systems:
The coordinate system of the window(viewport()) where the (0, 0) will always be the top-left of the window.
The coordinate system of the scene, this is with respect to some pre-established point.
The coordinate coordinate system of each item, this coordinate system is used by the paint() method to do the painting, and the boundingRect() and shape() methods to obtain the edges of the item.
You also have to have another concept, the position of an item is with respect to the parent if he has it, if he does not have it, it is with respect to the scene.
Analogy
To explain the different coordinate systems I use the analogy of recording a scene using a camera.
- The QGraphicsView would be the screen of the camera.
- The QGraphicsScene is the scene that is recorded, so the point (0, 0) is some point that is convenient.
- The QGraphicsItem are the elements of the scene, their position can be relative to other items or to the scene, for example we can consider the position of the actor's shoes with respect to the actor, or the item can be the same actor.
Based on the above I will explain what happens and we will give several solutions.
The rect item has no parent and by default the posicon of an item is (0, 0) so at that moment the coordinate system of the item and the scene coincide so the boundingRect will visually define the position and as you have placed QRectF(100, 30, 100, 30) this will be drawn in that position that coincidentally will be the same in the scene. But when you apply the animation the first thing that will be done is to set the position of the item to (100, 30) so that since the coordinate systems of the scene and the item do not match, one is displaced from the other, so the boundingRect no longer matches the QRectF(100, 30, 100, 30) of the scene, but will move in the same factor (only because there is a displacement, there is no scaling or rotation) and the rectangle will be QRectF(200, 60, 100, 30) and with respect to the ellipse that was always in the QRect(100, 180, 100, 50) so rectangle is on the right since 200>100 and it is up since 60<180.
So if you want the rectangle to be on top of the ellipse there are at least 2 solutions:
- Modify the boundingRect so that it is in position 0,0 so that with the displacement caused by the animation it makes them match:
import sys
from PyQt5 import QtCore, QtWidgets
class CustomRect(QtWidgets.QGraphicsObject):
def boundingRect(self):
return QtCore.QRectF(0, 0, 100, 30) # <---
def paint(self, painter, styles, widget=None):
painter.drawRect(self.boundingRect())
class Demo(QtWidgets.QGraphicsView):
def __init__(self):
super(Demo, self).__init__()
self.resize(300, 300)
self.scene = QtWidgets.QGraphicsScene()
self.scene.setSceneRect(0, 0, 300, 300)
self.rect = CustomRect()
self.ellipse = QtWidgets.QGraphicsEllipseItem()
self.ellipse.setRect(100, 180, 100, 50)
self.scene.addItem(self.rect)
self.scene.addItem(self.ellipse)
self.setScene(self.scene)
self.animation = QtCore.QPropertyAnimation(
self.rect,
b"pos",
duration=1000,
startValue=QtCore.QPointF(100, 30),
endValue=QtCore.QPointF(100, 90),
loopCount=-1,
)
self.animation.start()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
demo = Demo()
demo.show()
sys.exit(app.exec_())
- Modify the animation so that it does not generate the displacement:
import sys
from PyQt5 import QtCore, QtWidgets
class CustomRect(QtWidgets.QGraphicsObject):
def boundingRect(self):
return QtCore.QRectF(100, 30, 100, 30)
def paint(self, painter, styles, widget=None):
painter.drawRect(self.boundingRect())
class Demo(QtWidgets.QGraphicsView):
def __init__(self):
super(Demo, self).__init__()
self.resize(300, 300)
self.scene = QtWidgets.QGraphicsScene()
self.scene.setSceneRect(0, 0, 300, 300)
self.rect = CustomRect()
self.ellipse = QtWidgets.QGraphicsEllipseItem()
self.ellipse.setRect(100, 180, 100, 50)
self.scene.addItem(self.rect)
self.scene.addItem(self.ellipse)
self.setScene(self.scene)
self.animation = QtCore.QPropertyAnimation(
self.rect,
b"pos",
duration=1000,
startValue=QtCore.QPointF(0, 0), # <---
endValue=QtCore.QPointF(0, 60), # <---
loopCount=-1,
)
self.animation.start()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
demo = Demo()
demo.show()
sys.exit(app.exec_())
Answered By - eyllanesc
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.