如何将顶点插入QGraphicsPolygonItem?

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

这个多边形是由QGraphicsPolygonItem和QGraphicsPathItem绘制的。现在我想通过用鼠标点击多边形边来添加顶点,但是我添加了添加顶点的代码,导致多边形出现变形。 请帮我。 代码如下。

 class GripItem(QGraphicsPathItem):
    editing = False
    circle = QPainterPath()
    circle.addEllipse(QRectF(-6, -6, 12, 12))
    square = QPainterPath()
    square.addRect(QRectF(-10, -10, 20, 20))
    def __init__(self, annotation_item, index):
        super(GripItem, self).__init__()
        self.m_annotation_item = annotation_item
        self.m_index = index
        self.mySignal = SignalManager()
        #self.editing = True
        self.setPath(GripItem.circle)
        self.setBrush(QColor("green"))
        self.setPen(QPen(QColor("green"), 2))
        self.setFlag(QGraphicsItem.ItemIsSelectable, True)
        self.setFlag(QGraphicsItem.ItemIsMovable, True)
        self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True)
        self.setAcceptHoverEvents(True)
        self.setZValue(11)
        self.setCursor(QCursor(Qt.PointingHandCursor))

    def hoverEnterEvent(self, event):
        self.setPath(GripItem.square)
        self.setBrush(QColor("red"))
        super(GripItem, self).hoverEnterEvent(event)

    def hoverLeaveEvent(self, event):
        self.setPath(GripItem.circle)
        self.setBrush(QColor("green"))
        super(GripItem, self).hoverLeaveEvent(event)

    def mouseReleaseEvent(self, event):
        print("release")
        self.setSelected(False)
        super(GripItem, self).mouseReleaseEvent(event)

    def itemChange(self, change, value):
        if change == QGraphicsItem.ItemPositionChange and self.isEnabled():
            self.m_annotation_item.movePoint(self.m_index, value)
        return super(GripItem, self).itemChange(change, value)

    def mousePressEvent(self, event):
        if event.button() == Qt.RightButton:
            if self.editing:
                if self.m_annotation_item is not None:
                    print("parent")
                    #self.m_annotation_item.scene().removeItem(self)
                    it = self.m_annotation_item.m_items[self.m_index]
                    self.scene().removeItem(it)
                    del it
                    self.remove_point()
                    #del self.m_annotation_item.m_points[self.m_index]
                    self.m_annotation_item.setPolygon(QPolygonF(self.m_annotation_item.m_points))
        super(GripItem, self).mousePressEvent(event)

    def remove_point(self):
        print(self.m_annotation_item.dict_points[self.m_index])
        self.m_annotation_item.m_points.clear()
        self.m_annotation_item.dict_points[self.m_index] = 0
        for key, value in self.m_annotation_item.dict_points.items():
            if  not isinstance(value, int):
                self.m_annotation_item.m_points.append(value)


class PolygonAnnotation(QGraphicsPolygonItem): 
    editing = False 

    def __init__(self, scene):
        super(PolygonAnnotation, self).__init__()
        self.parent_scene = scene
        self.m_points = []
        self.center_x = 0
        self.center_y = 0
        self.setZValue(10)
        self.setPen(QPen(QColor("green"), 2))
        self.setAcceptHoverEvents(True)

        self.setFlag(QGraphicsItem.ItemIsSelectable, True)
        self.setFlag(QGraphicsItem.ItemIsMovable, True)
        self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True)

        self.setCursor(QCursor(Qt.PointingHandCursor))
        self.setAcceptHoverEvents(True)
        self.m_items = []
        self.dict_points = dict()

    def number_of_points(self):
        return len(self.m_items)

    def calculate_center(self, p):
        self.center_x += p.x()
        self.center_y += p.y()

    def addPoint(self, p):
        self.m_points.append(p)
        #print(len(self.dict_points))
        self.dict_points[len(self.dict_points)] = p        
        self.setPolygon(QPolygonF(self.m_points))
        item = GripItem(self, len(self.m_points)-1)
        self.scene().addItem(item)
        self.m_items.append(item)
        item.setPos(p)

    def removeLastPoint(self):
        if self.m_points:
            self.m_points.pop()
            self.setPolygon(QPolygonF(self.m_points))
            it = self.m_items.pop()
            if it is not None and self.scene() is not None:
                self.scene().removeItem(it)
                del it
        if self.editing and self.dict_points:
            last_key = list(self.dict_points.keys())[-1]
            self.dict_points.pop(last_key)

    def movePoint(self, i, p):
        if 0 <= i < len(self.m_points):
            self.m_points[i] = self.mapFromScene(p)
            self.setPolygon(QPolygonF(self.m_points))

    def move_item(self, index, pos):
        if 0 <= index < len(self.m_items):
            item = self.m_items[index]
            item.setEnabled(False)
            item.setPos(pos)
            item.setEnabled(True)

    def itemChange(self, change, value):
        if change == QGraphicsItem.ItemPositionHasChanged:
            for i, point in enumerate(self.m_points):
                    self.move_item(i, self.mapToScene(point))
        return super(PolygonAnnotation, self).itemChange(change, value)


    def delete_polygon(self):
        for i in range(len(self.m_items)):
                it = self.m_items.pop()
                self.scene().removeItem(it)
        self.scene().removeItem(self)
        self.m_points.clear()
        del self

    def hoverEnterEvent(self, event):
        self.setBrush(QColor(255, 0, 0, 100))
        super(PolygonAnnotation, self).hoverEnterEvent(event)

    def hoverLeaveEvent(self, event):
        self.setBrush(QBrush(Qt.NoBrush))
        super(PolygonAnnotation, self).hoverLeaveEvent(event)

    def getQPointFromDict(self, dict):
        self.m_points.clear()
        for key, value in dict.items():
            if type(value) is not int:
                self.m_points.append(value)

    # insert a vertex to the polygon.
    def mousePressEvent(self, event: QGraphicsSceneMouseEvent) -> None:
        if self.parent_scene.current_instruction != Instructions.No_Instruction:
            self.parent_scene.current_instruction = Instructions.No_Instruction
            self.parent_scene.start_point = None

        if event.button() == Qt.LeftButton:
            mouse_position = event.pos()
            index = self.isEdgeClick(mouse_position)
            if index != -1:
                # Add a vertex at the mouse click position
                self.insert_point(index, self.mapToScene(mouse_position))
        return super().mousePressEvent(event)
    
    def insert_point(self, index, p):
        self.m_points.insert(index, p)
        #print(len(self.dict_points))
        self.dict_points[len(self.dict_points)] = p        
        self.setPolygon(QPolygonF(self.m_points))
        item = GripItem(self, len(self.m_points)-1)
        self.scene().addItem(item)
        self.m_items.insert(index, item)
        item.setPos(p)

    def isEdgeClick(self, mouse_position):
        # Check if the mouse click is on the edge of the polygon
        if len(self.m_points) < 2:
            return False  # Need at least 2 points to have an edge

        polygon = QPolygonF(self.m_points)

        # Iterate through the edges of the polygon and check if the click is close to any edge
        for i in range(len(self.m_points)):
            p1 = polygon.at(i)
            p2 = polygon.at((i + 1) % len(self.m_points))

            # Calculate the distance from the point to the edge
            dist = distance_point_to_line(p1, p2, mouse_position)
                    
            # You can adjust this threshold as needed
            if dist < 1.0:
                return i+1

        return -1

这是一个失败的结果。

请提供正确的代码,谢谢!

pyqt pyqt5 qgraphicsview qgraphicsscene qgraphicsitem
1个回答
0
投票

假设您的

distance_point_to_line
计算(顺便说一句,您从未提供过)是有效的,那么您有两个问题:

  • 您添加的新夹点项目的索引错误:多边形顶点的大小 (
    GripItem(self, len(self.m_points)-1)
    ),而不是它们的真实索引;
  • 您没有更新剩余顶点的索引(任何位于新插入顶点之后的现有顶点),因此在移动它们时它们最终将返回无效的顺序;每当您删除顶点时这也是一个问题,因为您没有更新剩余的顶点;
即使您解决了上述问题,您的实现也存在概念缺陷。

首先,您从根本上将顶点数据存储在

四个不同的“位置”:

  • m_points
  • dict_points
  • 间接地,与
  • m_items
    (放置在相同的点);
  • 间接地,多边形点;
那么,由于夹点项显然与多边形密切相关,因此它们不应该是“外部”项,而是多边形的

children

通过在这些项目之间使用更合适的层次结构,可以更好地管理各个方面,避免不必要的复杂化(例如在移动多边形时尝试移动夹点项目)。

我决定完全重写整个实现(基于我之前的一些代码),因为修复上述几点会更加复杂。

这个想法是,每个抓地力项目始终是

PolygonAnnotation

 的子项,它完全管理它们的创建、删除和位置更改。

只有

一个顶点列表,这是夹点项目的列表,仅在移动它们或添加新顶点或删除任何顶点时使用。这使得在移动(或删除)夹点项目时查找其索引变得更加简单,并且您无需在每次顶点数变化时手动更新每个项目的索引。

注意,我通过使用键盘修饰符(

Shift用于添加,Ctrl用于删除)更改了插入/删除行为,因为您的代码没有解释“编辑模式”,也没有解释 current_instruction

 的含义(您顺便说一句,应该使用 
self.scene()
)。

class GripItem(QGraphicsPathItem): _pen = QPen(QColor('green'), 2) circle = QPainterPath() circle.addEllipse(QRectF(-6, -6, 12, 12)) circleBrush = QBrush(QColor('green')) square = QPainterPath() square.addRect(QRectF(-10, -10, 20, 20)) squareBrush = QBrush(QColor('red')) # keep the bounding rect consistent _boundingRect = (circle|square).boundingRect() def __init__(self, pos, parent): super().__init__(parent) self.poly = parent self.setPos(pos) self.setFlags( QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemSendsGeometryChanges ) self.setAcceptHoverEvents(True) self.setCursor(QCursor(Qt.PointingHandCursor)) self.setPen(self._pen) self._setHover(False) def itemChange(self, change, value): if change == QGraphicsItem.ItemPositionHasChanged: self.poly.gripMoved(self) return super().itemChange(change, value) def _setHover(self, hover): if hover: self.setBrush(self.squareBrush) self.setPath(self.square) else: self.setBrush(self.circleBrush) self.setPath(self.circle) def boundingRect(self): return self._boundingRect def hoverEnterEvent(self, event): super().hoverEnterEvent(event) self._setHover(True) def hoverLeaveEvent(self, event): super().hoverLeaveEvent(event) self._setHover(False) def mousePressEvent(self, event): if ( event.button() == Qt.LeftButton and event.modifiers() == Qt.ControlModifier ): self.poly.removeGrip(self) else: super().mousePressEvent(event) class PolygonAnnotation(QGraphicsPolygonItem): _threshold = None _pen = QPen(QColor("green"), 2) normalBrush = QBrush(Qt.NoBrush) hoverBrush = QBrush(QColor(255, 0, 0, 100)) def __init__(self, *args): super().__init__() self.setFlags( QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemSendsGeometryChanges ) self.setAcceptHoverEvents(True) self.setCursor(QCursor(Qt.PointingHandCursor)) self.setPen(self._pen) self.gripItems = [] if len(args) == 1: arg = args[0] if isinstance(arg, QPolygonF): self.setPolygon(arg) if isinstance(arg, (tuple, list)): args = arg if all(isinstance(p, QPointF) for p in args): self.setPolygon(QPolygonF(args)) def threshold(self): if self._threshold is not None: return self._threshold return self.pen().width() or 1. def setThreshold(self, threshold): self._threshold = threshold def setPolygon(self, poly): if self.polygon() == poly: return if self.gripItems: scene = self.scene() while self.gripItems: grip = self.gripItems.pop() if scene: scene.removeItem(grip) super().setPolygon(poly) for i, p in enumerate(poly): self.gripItems.append(GripItem(p, self)) def addPoint(self, pos): self.insertPoint(len(self.gripItems), pos) def insertPoint(self, index, pos): poly = list(self.polygon()) poly.insert(index, pos) self.gripItems.insert(index, GripItem(pos, self)) # just call the base implementation, not the override, as all required # items are already in place super().setPolygon(QPolygonF(poly)) def removePoint(self, index): if len(self.gripItems) <= 3: # a real polygon always has at least three vertexes, # otherwise it would be a line or a point return poly = list(self.polygon()) poly.pop(index) grip = self.gripItems.pop(index) if self.scene(): self.scene().removeItem(grip) # call the base implementation, as in insertPoint() super().setPolygon(QPolygonF(poly)) def closestPointToPoly(self, pos): ''' Get the position along the polygon sides that is the closest to the given point. Returns: - distance from the edge - QPointF within the polygon edge - insertion index If no closest point is found, distance and index are -1 ''' poly = self.polygon() points = list(poly) # iterate through pair of points, if the polygon is not "closed", # add the start to the end p1 = points.pop(0) if points[-1] != p1: # identical to QPolygonF.isClosed() points.append(p1) intersections = [] for i, p2 in enumerate(points, 1): line = QLineF(p1, p2) inters = QPointF() # create a perpendicular line that starts at the given pos perp = QLineF.fromPolar( self.threshold(), line.angle() + 90).translated(pos) if line.intersects(perp, inters) != QLineF.BoundedIntersection: # no intersection, reverse the perpendicular line by 180° perp.setAngle(perp.angle() + 180) if line.intersects(perp, inters) != QLineF.BoundedIntersection: # the pos is not within the line extent, ignore it p1 = p2 continue # get the distance between the given pos and the found intersection # point, then add it, the intersection and the insertion index to # the intersection list intersections.append(( QLineF(pos, inters).length(), inters, i)) p1 = p2 if intersections: # return the result with the shortest distance return sorted(intersections)[0] return -1, QPointF(), -1 def gripMoved(self, grip): if grip in self.gripItems: poly = list(self.polygon()) poly[self.gripItems.index(grip)] = grip.pos() super().setPolygon(QPolygonF(poly)) def removeGrip(self, grip): if grip in self.gripItems: self.removePoint(self.gripItems.index(grip)) def hoverEnterEvent(self, event): super().hoverEnterEvent(event) self.setBrush(self.hoverBrush) def hoverLeaveEvent(self, event): super().hoverLeaveEvent(event) self.setBrush(self.normalBrush) def mousePressEvent(self, event): if ( event.button() == Qt.LeftButton and event.modifiers() == Qt.ShiftModifier ): dist, pos, index = self.closestPointToPoly(event.pos()) if index >= 0 and dist <= self.threshold(): self.insertPoint(index, pos) return super().mousePressEvent(event)
您可以使用以下示例代码来测试上述内容:

def randomPoly(count=8, size=None): if count < 3: count = 3 if isinstance(size, int): size = max(10, size) else: size = max(50, count * 20) maxDiff = size / 5 path = QPainterPath() path.addEllipse(QRectF(0, 0, size, size)) mid = 2 / count points = [] for i in range(count): point = path.pointAtPercent((i / count + mid) % 1.) randiff = QPointF( uniform(-maxDiff, maxDiff), uniform(-maxDiff, maxDiff)) points.append(point + randiff) return QPolygonF(points) app = QApplication([]) scene = QGraphicsScene() scene.addItem(PolygonAnnotation(randomPoly(10, 200))) view = QGraphicsView(scene) view.resize(view.sizeHint() + QSize(250, 250)) view.setRenderHint(QPainter.Antialiasing) view.show() app.exec()
    
© www.soinside.com 2019 - 2024. All rights reserved.