在 QGraphicsView 上用鼠标编辑 QGraphicsPathItems

问题描述 投票:0回答:1

这个工具允许我用鼠标为我的 QGraphicsView“雕刻”QGraphicsPathItems。但是每当我将路径项目移动到不同的位置时,我只能从原始位置雕刻该项目。例如,如果我将路径 A 从点 C 移动到点 B,我只能雕刻从点 C 开始的路径(就像它是不可见的一样)。

from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import sys
import math

class PathEditorGraphicsView(QGraphicsView):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.sculpting_item = None
        self.sculpting_item_point_index = -1
        self.sculpting_item_offset = QPointF()
        self.undo_stack = QUndoStack(self)
        self.initial_path = None
        self.sculpt_radius = 10  # Radius for sculpting tool
        self.setRenderHint(QPainter.Antialiasing)

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.on_sculpt_start(event)
        super().mousePressEvent(event)

    def mouseMoveEvent(self, event):
        self.on_sculpt(event)
        super().mouseMoveEvent(event)

    def mouseReleaseEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.on_sculpt_end(event)
        super().mouseReleaseEvent(event)

    def on_sculpt_start(self, event):
        pos = self.mapToScene(event.pos())
        item = self.scene().itemAt(pos, self.transform())

        if isinstance(item, QGraphicsPathItem):
            self.sculpting_item = item
            self.sculpting_item_point_index, self.sculpting_item_offset = self.find_closest_point(pos, item)
            self.sculpting_initial_path = QPainterPath(item.path())

            print(f"Sculpt Start: Item ID {id(item)}, Point Index {self.sculpting_item_point_index}")

    def on_sculpt(self, event):
        if self.sculpting_item is not None and self.sculpting_item_point_index != -1:
            pos = self.mapToScene(event.pos()) - self.sculpting_item_offset
            self.update_path_point(self.sculpting_item, self.sculpting_item_point_index, pos)

    def on_sculpt_end(self, event):
        self.sculpting_item = None
        self.sculpting_item_point_index = -1
        self.initial_path = None

    def find_closest_point(self, pos, item):
        path = item.path()
        min_dist = float('inf')
        closest_point_index = -1
        closest_offset = QPointF()

        for i in range(path.elementCount()):
            point = path.elementAt(i)
            point_pos = QPointF(point.x, point.y)
            dist = QLineF(point_pos, pos).length()

            if dist < min_dist:
                min_dist = dist
                closest_point_index = i
                closest_offset = pos - point_pos

        return closest_point_index, closest_offset

    def update_path_point(self, item, index, new_pos):
        path = item.path()
        elements = [path.elementAt(i) for i in range(path.elementCount())]

        if index < 0 or index >= len(elements):
            return  # Ensure the index is within bounds

        old_pos = QPointF(elements[index].x, elements[index].y)
        delta_pos = new_pos - old_pos

        def calculate_influence(dist, radius):
            return math.exp(-(dist**2) / (2 * (radius / 2.0)**2))

        for i in range(len(elements)):
            point = elements[i]
            point_pos = QPointF(point.x, point.y)
            dist = QLineF(point_pos, old_pos).length()

            if dist <= self.sculpt_radius:
                influence = calculate_influence(dist, self.sculpt_radius)
                elements[i].x += delta_pos.x() * influence
                elements[i].y += delta_pos.y() * influence

        new_path = QPainterPath()
        new_path.moveTo(elements[0].x, elements[0].y)

        i = 1
        while i < len(elements):
            if i + 2 < len(elements):
                new_path.cubicTo(elements[i].x, elements[i].y,
                                 elements[i + 1].x, elements[i + 1].y,
                                 elements[i + 2].x, elements[i + 2].y)
                i += 3
            else:
                new_path.lineTo(elements[i].x, elements[i].y)
                i += 1

        item.setPath(new_path)

app = QApplication(sys.argv)
scene = QGraphicsScene()
view = PathEditorGraphicsView()
view.setScene(scene)

# Example path item
path = QPainterPath()
path.moveTo(50, 50)
path.cubicTo(70, 100, 130, 0, 150, 50)
path.cubicTo(70, 100, 130, 0, 150, 50)
path.cubicTo(170, 100, 230, 0, 250, 50)
path.cubicTo(270, 100, 330, 0, 350, 50)
path.cubicTo(370, 100, 430, 0, 450, 50)
path.cubicTo(470, 100, 530, 0, 550, 50)
path.cubicTo(570, 100, 630, 0, 650, 50)
path.cubicTo(670, 100, 730, 0, 750, 50)
path_item = QGraphicsPathItem(path)
scene.addItem(path_item)

# if we move the position of the path at all, the tool no longer works
path_item.setPos(100, 100)

view.show()
sys.exit(app.exec_())

我在该项目上尝试了

mapFromScene()
,以及每当项目移动时的功能。我发现这些语句甚至没有在之前移动的路径上触发。

python qt pyqt qgraphicsview qgraphicsscene
1个回答
0
投票

通过使用地图方法解决了这个问题,这是最终的。我还添加了一个功能,因此当您双击时,路径会在该点变得平滑。

    def on_sculpt_start(self, event):
        pos = self.mapToScene(event.pos())
        item = self.scene().itemAt(pos, self.transform())

        if isinstance(item, CustomPathItem):
            pos_in_item_coords = item.mapFromScene(pos)
            self.sculpting_item = item
            self.sculpting_item_point_index, self.sculpting_item_offset = self.find_closest_point(pos_in_item_coords,
                                                                                                  item)
            self.sculpting_initial_path = QPainterPath(item.path())  # Make a deep copy of the path

            print(f"Sculpt Start: Item ID {id(item)}, Point Index {self.sculpting_item_point_index}")

        self.canvas.addItem(self.sculpt_shape)
        self.sculpt_shape.setPos(pos - self.sculpt_shape.boundingRect().center())

    def on_sculpt(self, event):
        if self.sculpting_item is not None and self.sculpting_item_point_index != -1:
            pos = self.mapToScene(event.pos())
            pos_in_item_coords = self.sculpting_item.mapFromScene(pos) - self.sculpting_item_offset
            self.update_path_point(self.sculpting_item, self.sculpting_item_point_index, pos_in_item_coords)
            print(f"Sculpt: Item ID {id(self.sculpting_item)}, Point Index {self.sculpting_item_point_index}")

        self.sculpt_shape.setPos(self.mapToScene(event.pos()) - self.sculpt_shape.boundingRect().center())

    def on_sculpt_end(self, event):
        if self.sculpting_item is not None:
            new_path = self.sculpting_item.path()
            if new_path != self.sculpting_initial_path:
                command = EditPathCommand(self.sculpting_item, self.sculpting_initial_path, new_path)
                self.canvas.addCommand(command)
                print(f"Sculpt End: Item ID {id(self.sculpting_item)}")

        self.reset_sculpting_state()

    def on_sculpt_double_click(self, event):
        pos = self.mapToScene(event.pos())
        item = self.scene().itemAt(pos, self.transform())

        if isinstance(item, CustomPathItem):
            pos_in_item_coords = item.mapFromScene(pos)
            point_index, offset = self.find_closest_point(pos_in_item_coords, item)
            if point_index != -1:
                self.smooth_path_point(item, point_index)

    def find_closest_point(self, pos, item):
        path = item.path()
        min_dist = float('inf')
        closest_point_index = -1
        closest_offset = QPointF()

        for i in range(path.elementCount()):
            point = path.elementAt(i)
            point_pos = QPointF(point.x, point.y)
            dist = QLineF(point_pos, pos).length()

            if dist < min_dist:
                min_dist = dist
                closest_point_index = i
                closest_offset = pos - point_pos

        return closest_point_index, closest_offset

    def update_path_point(self, item, index, new_pos):
        path = item.path()
        elements = [path.elementAt(i) for i in range(path.elementCount())]

        if index < 0 or index >= len(elements):
            return  # Ensure the index is within bounds

        old_pos = QPointF(elements[index].x, elements[index].y)
        delta_pos = new_pos - old_pos

        def calculate_influence(dist, radius):
            return math.exp(-(dist ** 2) / (2 * (radius / 2.0) ** 2))

        for i in range(len(elements)):
            point = elements[i]
            point_pos = QPointF(point.x, point.y)
            dist = QLineF(point_pos, old_pos).length()

            if dist <= self.sculpt_radius:
                influence = calculate_influence(dist, self.sculpt_radius)
                elements[i].x += delta_pos.x() * influence
                elements[i].y += delta_pos.y() * influence

        new_path = QPainterPath()
        new_path.setFillRule(Qt.WindingFill)
        new_path.moveTo(elements[0].x, elements[0].y)

        i = 1
        while i < len(elements):
            if i + 2 < len(elements):
                new_path.cubicTo(elements[i].x, elements[i].y,
                                 elements[i + 1].x, elements[i + 1].y,
                                 elements[i + 2].x, elements[i + 2].y)
                i += 3
            else:
                new_path.lineTo(elements[i].x, elements[i].y)
                i += 1

        item.setPath(new_path)
        item.smooth = False

    def smooth_path_point(self, item, point_index):
        path = item.path()
        elements = [path.elementAt(i) for i in range(path.elementCount())]

        if point_index > 0 and point_index + 1 < len(elements):
            smoothed_x = (elements[point_index - 1].x + elements[point_index + 1].x) / 2
            smoothed_y = (elements[point_index - 1].y + elements[point_index + 1].y) / 2

            path.setElementPositionAt(point_index, smoothed_x, smoothed_y)

            command = EditPathCommand(item, item.path(), path)
            self.canvas.addCommand(command)
            item.smooth = False

我还通过

EditPathCommand
添加了撤消功能。

© www.soinside.com 2019 - 2024. All rights reserved.