在QGraphicScene中创建具有可移动节点的闭合路径

时间:2017-10-24 10:38:01

标签: python pyqt pyqt5 qgraphicsscene qpainterpath

我正在尝试从this answer扩展优秀的实现,因此将根据用户在图形场景中的点击创建路径,并使用双击关闭路径。

这很有效,但我唯一无法解决的问题是更改路径中的第一个节点。显然我希望它会通过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_()

1 个答案:

答案 0 :(得分:2)

你几乎拥有它,但你的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()