Search code examples
pythonpyqt5qgraphicssceneqgraphicsitem

move QGraphicsItem continuously in QGraphicsScene and check Collision


about 3 weeks ago I asked for changing a GraphicsItem in a GraphicsScene. It was a line and the solution was: self.setLine(..). Now I try to understand, moving it continuously. So I have a graphicsview gvCanvas and within a scene and initialize rectangles, lines... It works

For moving a rectange I have the following code for Class myRect with an __init__ and a movemyrect

class myRect(QtWidgets.QGraphicsRectItem):
    def __init__(self, QRectF):  # *args):
        QtWidgets.QGraphicsRectItem.__init__(self)
        self.setRect(QRectF)
        self.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable, True)
        self.setPen(QtGui.QPen(QtCore.Qt.white, 3))

    def movemyRect(self, x, y, angl):
        myyrs = QtWidgets.QGraphicsRectItem.rect(self) #(self.myyr)   #line(items[0])
        ptrr = myyrs.bottomLeft()
        xpos = ptrr.x() + x #+x
        ypos = ptrr.y()  - y
        ptnew=QtCore.QPoint(xpos,ypos)
        myr = QtCore.QRectF(ptnew, QtCore.QSizeF(15,15))
        self.setRotation(angl)
        self.setRect(myr)
        self.update()

I then try to move around a rect

    xm =20
    ym = 20
    for i in range(1,5):
        time.sleep(0.8)
        print("nachsleep",i)
        self.rect2.movemyRect(xm, ym, 10)
        myyrs = QtWidgets.QGraphicsRectItem.rect(self.rect2)
        self.rect2.setRect(myyrs)
        self.scene.update()
        listtreffer = self.scene.collidingItems(self.lnk, mode=Qt.IntersectsItemShape)
        for treffer in listtreffer:
            print("treffer", treffer)

The loop works, but the moved and rotated rectangle appears changed only at the end of the loop on my scene, not after every step. I thought, with the statement "setRect..." it should work at every walk through the loop. Also the scene.update() doesn't help.

If I do it without loop, it works, too.

What is wrong, that the moved rectangle does not appear on every step in the loop?

The statement with checking collision works correct in every step.

Here is an additional question: if I only want the collision checked of this rectangle, would it be better to define a sort of collision-check within the class definition instead of within this loop?

(I also try do do the same with an animation and QPropertyAnimantion. There i fail to fire or get the advance-statement running. to check collision, even when moving aroud works. But i think, for this i should open an new question)


Solution

  • In a GUI you should never use time.sleep since it blocks the eventloop and consequently the window freezes preventing the GUI from being repainted. In Qt you have to do the actions using events, for example in the following code each time the timer is triggered, change the position. To make smooth movement use a QVariantAnimation, QPropertyAnimation can not be used since the QGraphicsItem are not QObjects and the position is not a qproperty.

    import random
    from functools import partial
    from PyQt5 import QtCore, QtGui, QtWidgets
    
    class RectItem(QtWidgets.QGraphicsRectItem):
        def __init__(self, rect=QtCore.QRectF()):
            super(RectItem, self).__init__(rect)
            self.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable, True)
            self.setFlag(QtWidgets.QGraphicsItem.ItemSendsGeometryChanges, True)
            self.setPen(QtGui.QPen(QtCore.Qt.red, 3))
            self._pos_animation = QtCore.QVariantAnimation()
            self._pos_animation.valueChanged.connect(self.setPos)
    
        def move_smooth(self, end, duration=1000):
            if self._pos_animation.state() == QtCore.QAbstractAnimation.Running:
                self._pos_animation.stop()
            self._pos_animation.setDuration(duration)
            self._pos_animation.setStartValue(self.pos())
            self._pos_animation.setEndValue(end)
            self._pos_animation.start()
    
        def itemChange(self, change, value):
            if change ==QtWidgets.QGraphicsItem.ItemPositionChange:
                color = QtGui.QColor(QtCore.Qt.red)
                if self.scene().collidingItems(self):
                    color = QtGui.QColor(QtCore.Qt.green)
                self.setPen(QtGui.QPen(color, 3))
            return super(RectItem, self).itemChange(change, value)
    
    def move_pos(scene):
        for it in scene.items():
            pos = QtCore.QPointF(*random.sample(range(-100, 200), 2))
            if hasattr(it, 'move_smooth'):
                it.move_smooth(pos, 1000)
    
    if __name__ == '__main__':
        import sys
        app = QtWidgets.QApplication(sys.argv)
        app.setStyle('fusion')
        scene = QtWidgets.QGraphicsScene(-100, -100, 200, 200)
        scene.setBackgroundBrush(QtCore.Qt.gray)
        view = QtWidgets.QGraphicsView(scene)
        view.resize(640, 480)
        view.show()
        l = []
        for _ in range(4):
            pos = QtCore.QPointF(*random.sample(range(-100, 200), 2))
            it = RectItem(QtCore.QRectF(-20, -20, 40, 40))
            scene.addItem(it)
            it.setPos(pos)
            l.append(it)
        wrapper = partial(move_pos, scene)
        timer = QtCore.QTimer(interval=3000, timeout=wrapper)
        timer.start()
        wrapper()
        sys.exit(app.exec_())