Создайте закрытый путь с подвижными узлами в QGraphicScene

Я пытаюсь расширить отличную реализацию из этого ответа, поэтому путь будет создан из кликов пользователя внутри графического сцена и закройте путь, используя двойной щелчок.

Это работает хорошо, но единственная проблема, которую я пока не смог решить, — это изменение первого узла в пути. Очевидно, я хотел бы, чтобы он обновил последний прикрепленный к нему подпуть с помощью метода itemChanged, но не смог этого сделать.

Любые предложения о том, как подойти к этому?

Я попробовал следующий вариант, но это не повлияло на последний подпуть:

def itemChange(self, change, value):
    if change == QGraphicsItem.ItemPositionChange:
        self.path.updateElement(self.index, value.toPoint())
        if self.index == 0:
            last_element_idx = self.path.path.elementCount()
            self.path.updateElement(last_element_idx, value.toPoint())
    return QGraphicsEllipseItem.itemChange(self, change, value)

Полный код моего эксперимента:

from PyQt5 import QtCore, QtGui
from PyQt5.QtCore import QPointF, Qt
from PyQt5.QtGui import QPen, QPainterPath, QPainter, QPolygonF
from PyQt5.QtWidgets import QGraphicsEllipseItem, QGraphicsItem, QGraphicsPathItem, QApplication, QGraphicsScene, \
    QGraphicsView, QFrame, QMainWindow

rad = 3

class Node(QGraphicsEllipseItem):
    def __init__(self, path, index):
        super(Node, self).__init__(-rad, -rad, 2*rad, 2*rad)
        self.rad = rad
        self.path = path
        self.index = index
        self.setZValue(1)
        self.setFlag(QGraphicsItem.ItemIsMovable)
        self.setFlag(QGraphicsItem.ItemSendsGeometryChanges)
        self.setBrush(Qt.green)

    def itemChange(self, change, value):
        if change == QGraphicsItem.ItemPositionChange:
            self.path.updateElement(self.index, value.toPoint())
            if self.index == 0:
                last_element_idx = self.path.path.elementCount()
                self.path.updateElement(last_element_idx, value.toPoint())
        return QGraphicsEllipseItem.itemChange(self, change, value)

class Path(QGraphicsPathItem):
    def __init__(self, pos, scene):
        self.path = QPainterPath()
        self.path.moveTo(*pos)
        super(Path, self).__init__(self.path)
        self.scene = scene
        self.pos = pos
        self.setPen(QPen(Qt.red, 1.75))
        self.scene.addItem(self)

    def addElement(self, pos):
        self.path.lineTo(QPointF(*pos))
        self.setPath(self.path)
        self.scene.update()

    def closePath(self):
        self.path.closeSubpath()
        n = self.path.elementCount()
        for i in range(n-1):
            node = Node(self, i)
            elem = self.path.elementAt(i)
            node.setPos(elem.x, elem.y)
            self.scene.addItem(node)
        self.setPath(self.path)
        self.scene.update()

    def updateElement(self, index, pos):
        self.path.setElementPositionAt(index, pos.x(), pos.y())
        self.setPath(self.path)
        self.scene.update()

class GraphicViewer(QGraphicsView):
    def __init__(self, parent, img=None, masks=None):
        super(GraphicViewer, self).__init__(parent)
        self.img = img
        self._scene = QGraphicsScene(self)
        self._scene.setSceneRect(0, 0, 1920, 900)
        self.setScene(self._scene)
        self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
        self.setResizeAnchor(QGraphicsView.AnchorUnderMouse)
        self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.setBackgroundBrush(QtGui.QBrush(QtGui.QColor(0, 0, 0)))
        self.setFrameShape(QFrame.NoFrame)
        self.setRenderHint(QPainter.Antialiasing)
        self.resize(1920, 900)
        self.editable_path = None

    def mousePressEvent(self, event: QtGui.QMouseEvent):
        modifiers = QApplication.keyboardModifiers()
        if modifiers == QtCore.Qt.AltModifier:
            self.pos = (event.x(), event.y())
            if self.editable_path is None:
                self.editable_path = Path(self.pos, self._scene)
            else:
                self.editable_path.addElement(self.pos)
        else:
            super(GraphicViewer, self).mousePressEvent(event)

    def mouseDoubleClickEvent(self, event: QtGui.QMouseEvent):
        if self.editable_path is not None:
            self.editable_path.closePath()
            self.editable_path = None

if __name__ == "__main__":

    app = QApplication([])
    MainWindow = QMainWindow()
    view = GraphicViewer(MainWindow)
    MainWindow.resize(1920, 900)
    MainWindow.show()
    app.exec_()

person avielbl    schedule 24.10.2017    source источник


Ответы (1)


У вас почти получилось, но в вашем методе itemChange есть ошибка "один за другим".

Я бы переписал ваш пример, чтобы он выглядел так:

class Node(QGraphicsEllipseItem):
    ...
    def itemChange(self, change, value):
        if change == QGraphicsItem.ItemPositionChange:
            self.path.updateElement(self.index, value.toPoint())
        return QGraphicsEllipseItem.itemChange(self, change, value)

class Path(QGraphicsPathItem):
    ...    
    def updateElement(self, index, pos):
        self.path.setElementPositionAt(index, pos.x(), pos.y())
        if index == 0:
            self.path.setElementPositionAt(
                self.path.elementCount() - 1, pos.x(), pos.y())
        self.setPath(self.path)
        self.scene.update()
person ekhumoro    schedule 25.10.2017