python - 在 QGraphicSscene 中创建带有可移动节点的闭合路径

标签 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_()

最佳答案

您几乎已经成功了,但您的 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()

关于python - 在 QGraphicSscene 中创建带有可移动节点的闭合路径,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46908327/

相关文章:

python - 向可变 tensorflow 添加随机噪声

python - 使用 QThread 动态创建 QImage 帧到 ffmpeg 标准输入

python - 在 QDialog 中显示子进程的实时输出

python - 我如何实际使用 WSGI?

python - 编码和 urllib 的问题

python - 如何正确缩放 QGraphicsScene

python - 使用橡皮筋选择 QGraphicsItem

python - 使用 PyQt 实现分割器(QtCreator 生成的文件)

python - 将网格子图添加到 WxPython GUI (Matplotlib)

python - QTreeWidget 中的拖放操作不复制拖放的项目