这个多边形是由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
请提供正确的代码,谢谢!
假设您的
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()