Search code examples
pythonqtpyqtvector-graphicsqgraphicsview

QGraphicsView vector edit: update path when moving node – properly?


I am creating a vector editing window. I add elements to a QGraphicsScene and display it. I am yet writing code so that when moving a node, it moves the underlying path as well.
But how do I move the two joints of the path that lead to the point I try to move? I would first need to break the QPainterPath into subpaths (how? the only method that breaks into subpaths seems to be toSubpathPolygon but polygon is a set of point which looses curve information…) then find the point before the one I am trying to change. How would I go about doing that?

See the screenshots: https://i.sstatic.net/vSoMW.jpg
When moving the node, only one joint gets updated.

The square nodes are defined like this:

class OnCurvePointItem(QGraphicsRectItem):
    def __init__(self, x, y, width, height, pointX, pointY, pen=None, brush=None, parent=None):
        super(OnCurvePointItem, self).__init__(x, y, width, height, parent)
        self.setFlag(QGraphicsItem.ItemIsMovable)
        self.setFlag(QGraphicsItem.ItemIsSelectable)
        self.setFlag(QGraphicsItem.ItemSendsGeometryChanges)
        if pen is not None: self.setPen(pen)
        if brush is not None: self.setBrush(brush)

        # Absolute coordinates of the points in the path
        self._pointX = pointX
        self._pointY = pointY

    """ # disabled, trying to use itemChange instead.
    def mouseMoveEvent(self, event):
        pos = event.pos()
        print(pos)
        super(OnCurvePointItem, self).mouseMoveEvent(event)
        path = self.scene()._outlineItem.path()
        path.setElementPositionAt(0, pos.x(), pos.y())
        self.scene()._outlineItem.setPath(path)
        #self.scene().update()
    """

    def itemChange(self, change, value):
        #print(change)
        if change == QGraphicsItem.ItemPositionHasChanged:
            # this is the outline path to mutate, stashed in the scene
            path = self.scene()._outlineItem.path()

            #for i in range(path.elementCount()):
            #    elem = path.elementAt(i)
            #    if elem.isCurveTo(): kind = "curve"
            #    elif elem.isLineTo(): kind = "line"
            #    else: kind = "move"
            #    print("{}: {} {}".format(kind, elem.x, elem.y))
            #print()

            # self.pos() is relative to the original position in the scene
            # hardcoding element 0 till I store index in the item object
            path.setElementPositionAt(0, self._pointX+self.pos().x(), self._pointY+self.pos().y())
            self.scene()._outlineItem.setPath(path)
        return QGraphicsItem.itemChange(self, change, value)

Thank you, I'm rather stuck from here. Is there eventually any simple Qt example of vector edition you know of?
I found a piece of software called Carve but the codebase is rather intricated and I can't find anything relevant to what I am doing here…


Solution

  • In the end I chose to just use the points coordinates on each move to create a new updated path from scratch and calling QGraphicsPathItem.updatePath() when a move happens.

    This avoid the need for low-level manipulation, something QPainterPath isn’t exactly made for anyway.