Search code examples
pythonpyqtpyqt5qgraphicsitem

PyQt - Is there an implementation to constrain QGraphicsItem movement to a certain line path?


I'm trying to constrain this QGraphicsRectItem's movement by making the origin of the rectangle item follow this QGraphicsLineItem when moving the rectangle mouse.

Is there any method to do this?


Solution

  • I got it with a simple implementation, by creating a modified rectangle item and line items:

    class Rectangle(QGraphicsRectItem):
        def __init__(self, x=0, y=0, w=0, h=0, pathes=[]):
            super(Rectangle, self).__init__(x, y, w, h)  # x, y are internal object coordinates
            self.setTransformOriginPoint(int(w / 2), int(h / 2))  # setting the origin point to the middle
            self.origin = self.transformOriginPoint()  # returns QPointF
            self.setFlag(QGraphicsItem.ItemIsSelectable)
            self.setFlag(QGraphicsItem.ItemSendsScenePositionChanges)
    
            self.pathes = pathes
    
    
        def item_on_path(self, new_point):
            new_point = QPointF(new_point.x() + self.origin.x(), new_point.y() + self.origin.y())
            for path in self.pathes:
                if path.contains(new_point):
                    return True
            return False
    
    
    class Line(QGraphicsLineItem):
        def __init__(self, pt1, pt2):
            super(Line, self).__init__(pt1[0], pt1[1], pt2[0], pt2[1])
    

    Then creating the scene object with QShortcut for keyboard control and setting the scene:

    class graphicsViewObject(QGraphicsView):
        def __init__(self):
            super(graphicsViewObject, self).__init__()
    
            self.scene = QGraphicsScene()
    
            black_pen = QPen(Qt.black)
            black_pen.setWidth(5)
    
            self.setScene(self.scene)
    
            self.speed = 10  # keyboard movement change value
    
            pt1 = (200, 200)
            pt2 = (800, 200)
    
            self.line_1 = Line(pt1, pt2)
            self.line_1.setPen(black_pen)
            self.scene.addItem(self.line_1)
    
            self.rect = Rectangle(w=200, h=200, pathes=[self.line_1])
            self.rect.setPen(black_pen)
            self.rect.setBrush(QBrush(Qt.red))
            self.scene.addItem(self.rect)
    
            # Moving the rectangle origin to the first point of the line
            self.rect.setPos(QPointF(pt1[0] - self.rect.origin.x(), pt1[1] - self.rect.origin.y()))
    
            QShortcut(Qt.Key_Up, self, self.fooUp)
            QShortcut(Qt.Key_Down, self, self.fooDown)
            QShortcut(Qt.Key_Left, self, self.fooLeft)
            QShortcut(Qt.Key_Right, self, self.fooRight)
    
    
        def fooUp(self):
            for item in self.scene.selectedItems():
                pos = item.scenePos()
                pos.setY(pos.y() - self.speed)
                item_inside_scene = self.scene.sceneRect().contains(item.mapRectToScene(item.boundingRect()))
                if item.item_on_path(pos) and item_inside_scene:
                    item.setPos(pos)
    
    
        def fooDown(self):
            for item in self.scene.selectedItems():
                pos = item.scenePos()
                pos.setY(pos.y() + self.speed)
                item_inside_scene = self.scene.sceneRect().contains(item.mapRectToScene(item.boundingRect()))
                if item.item_on_path(pos) and item_inside_scene:
                    item.setPos(pos)
    
    
        def fooLeft(self):
            for item in self.scene.selectedItems():
                pos = item.scenePos()
                pos.setX(pos.x() - self.speed)
                item_inside_scene = self.scene.sceneRect().contains(item.mapRectToScene(item.boundingRect()))
                if item.item_on_path(pos) and item_inside_scene:
                    item.setPos(pos)
    
    
        def fooRight(self):
            for item in self.scene.selectedItems():
                pos = item.scenePos()
                pos.setX(pos.x() + self.speed)
                item_inside_scene = self.scene.sceneRect().contains(item.mapRectToScene(item.boundingRect()))
                if item.item_on_path(pos) and item_inside_scene:
                    item.setPos(pos)