import math from PySide6.QtCore import Qt, Signal, QPointF, QObject from PySide6.QtGui import QPen, QBrush, QColor, QPainterPath from PySide6.QtWidgets import QGraphicsEllipseItem, QGraphicsPathItem, \ QGraphicsTextItem, QGraphicsItem,QGraphicsLineItem class CustomLineItem(QGraphicsPathItem, QObject): pressMouse = Signal(object, int) # 定义鼠标按下信号 def __init__(self, sx, sy, ex, ey, edge_id, is_path=False, seq=2, parent=None, **kwargs): QGraphicsPathItem.__init__(self, parent) QObject.__init__(self, **kwargs) self.x = (ex + sx) // 2 self.y = (ey + sy) // 2 self.id = edge_id self.seq = seq if ex - sx != 0: m = (ey - sy) / (ex - sx) # 求得线段的斜率 self.angle_rad = math.atan(m) # 求弧度 self.angle = math.degrees(self.angle_rad) # 计算度数 else: self.angle_rad = math.pi / 2 self.angle = 90 self.off_x_center = math.cos(self.angle_rad + math.pi / 2) * 30 self.off_y_center = math.sin(self.angle_rad + math.pi / 2) * 30 self.pen_line = QPen(QColor(200, 200, 200, 100)) self.pen_line.setWidth(3) self.selected_pen_line = QPen(QColor(100, 255, 100, 255)) self.selected_pen_line.setWidth(3) if is_path: # 如果是路径 self.pen_line = QPen(QColor(0, 200, 200, 100)) self.pen_line.setWidth(5) # 设置画笔的宽度为5 # 创建画笔路径 path = QPainterPath() if self.seq == 0: path.moveTo(sx, sy) # 设置起点 path.lineTo(ex, ey) # 设置终点 elif self.seq == 1: path.moveTo(sx, sy) # 设置路径的起点 path.lineTo(self.x+self.off_x_center, self.y+self.off_y_center) # 设置路径的终点 path.lineTo(ex, ey) # 设置终点 elif self.seq == 2: path.moveTo(sx, sy) # 设置路径的起点 path.lineTo(self.x-self.off_x_center, self.y-self.off_y_center) # 设置路径的终点 path.lineTo(ex, ey) # 设置终点 # 将画笔路径设置给GraphicsPathItem self.setPath(path) # 设置路径 self.setPen(self.pen_line) # 设置画笔 self.setZValue(-1) # 设置z轴坐标 self.setFlag(QGraphicsItem.ItemIsSelectable, True) # 将连线设置为可选 # self.set_text("ID: " + str(self.id)) def set_text(self, text): # 清除之前的文本 if hasattr(self, 'text_item'): self.scene().removeItem(self.text_item) del self.text_item # 添加文本 self.text_item = QGraphicsTextItem(text, parent=self) # 修改字体属性 font = self.text_item.font() font.setPointSize(12) # 设置字体大小 self.text_item.setFont(font) # 计算理想的文本位置 tw = self.text_item.boundingRect().width()/2 th = self.text_item.boundingRect().height()/2 self.text_item.setRotation(self.angle) # 调整显示角度 # 调整路径信息显示方向和位置 ly = tw*math.sin(self.angle_rad) lx = tw*math.cos(self.angle_rad) my = th*math.sin(self.angle_rad+math.pi/2) mx = th*math.cos(self.angle_rad+math.pi/2) if self.seq == 0: self.text_item.setPos(self.x-lx-mx ,self.y-ly-my) elif self.seq == 1: self.text_item.setPos(self.x+self.off_x_center-lx-mx, self.y+self.off_y_center-ly-my) elif self.seq == 2: self.text_item.setPos(self.x-self.off_x_center-lx-mx, self.y-self.off_y_center-ly-my) def mousePressEvent(self, event): print("连接线") if event.button() == Qt.LeftButton: # 如果是鼠标左键按下 self.pressMouse.emit(event, self.id) # 鼠标左键按下时发送鼠标坐标信号和标签 for item in self.scene().items(): # 遍历场景中的所有图形项 if isinstance(item, (CustomLineItem, CustomEllipseItem)) and item.isSelected(): # 如果是线或者圆形并且被选中 item.setSelected(False) # 取消选中 self.setSelected(True) # 选中当前图形项 def itemChange(self, change, value): if change == QGraphicsItem.ItemSelectedChange: if value: self.setPen(self.selected_pen_line) else: self.setPen(self.pen_line) return super().itemChange(change, value) # 取消选中时恢复以前的状态 class CustomEllipseItem(QGraphicsEllipseItem, QObject): # 圆形对象 pressMouse = Signal(object, int) # 定义鼠标按下信号 releaseMouse = Signal(object) # 定义鼠标释放信号 collideMouse = Signal(int, int) # 定义碰撞信号发送自身节点的id和连线碰撞的节点id def __init__(self, x, y, id, text, is_must=False, radius=18, parent=None, **kwargs): ''' :param radius: 半径 :param color: 颜色带透明度 ''' super().__init__(x - radius, y - radius, radius * 2, radius * 2, parent) QObject.__init__(self, **kwargs) # 为了获得自定义信号和槽的能力 pen_circle = QPen(QColor(0, 0, 0, 255)) # 黑色 pen_circle.setWidth(0) # 设置画笔的宽度为0 self.brush_circle = QBrush(QColor(255, 255, 0, 255)) # 创建一个黄色色且透明度为255的刷子 if is_must: # 必经点设置为红色 self.brush_circle = QBrush(QColor(255, 100, 100, 255)) # 创建一个红色且透明度为100的刷子 self.setPen(pen_circle) self.setBrush(self.brush_circle) self.x = x self.y = y self.text = str(text) self.id = id # self.setFlag(QGraphicsEllipseItem.ItemIsMovable, True) self.setFlag(QGraphicsEllipseItem.ItemIsSelectable, True) # 设置为可选 # 设置鼠标悬停时的光标为抓手 self.setAcceptHoverEvents(True) # 设置接受鼠标悬停事件 self.set_text(self.text) # 设置文本 self.mouse_pressed = False # 鼠标按下标志 self.line_item = None # 初始化线 def set_text(self, text): # 清除之前的文本 if hasattr(self, 'text_item'): self.scene().removeItem(self.text_item) del self.text_item self.text_item = QGraphicsTextItem(text, parent=self) # 添加文本 font = self.text_item.font() # 修改字体属性 font.setPointSize(20) # 设置字体大小 self.text_item.setFont(font) # 设置格式 self.text_item.setPos(self.x - self.text_item.boundingRect().width() / 2, # 设置位置 self.y - self.text_item.boundingRect().height() / 2) def __str__(self): return f"圆形对象:x={self.x}, y={self.y}" def mousePressEvent(self, event): # 鼠标按下执行 if event.button() == Qt.LeftButton: self.pressMouse.emit(event, self.id) # 鼠标左键按下时发送鼠标坐标信号和标签 self.mouse_pressed = True for item in self.scene().items(): if isinstance(item, (CustomLineItem, CustomEllipseItem)) and item.isSelected(): item.setSelected(False) self.setSelected(True) self.setSelected(True) def mouseMoveEvent(self, event): if self.mouse_pressed: # 鼠标按住的情况下更新直线 mouse_x = event.scenePos().x() mouse_y = event.scenePos().y() if self.line_item is not None: # 如果存在 self.scene().removeItem(self.line_item) # 删除线 pen_circle = QPen(QColor(200, 200, 200, 255)) pen_circle.setWidth(2) self.line_item = QGraphicsLineItem(self.x, self.y, mouse_x, mouse_y) # print(mouse_x, mouse_y) # 鼠标移动时的坐标 self.line_item.setPen(pen_circle) self.line_item.setZValue(-1) self.scene().addItem(self.line_item) # 重新画线 # 获取线的端点坐标 # line_start = QPointF(self.x, self.y) line_end = QPointF(mouse_x, mouse_y) # 检查线的端点是否与其他图形项碰撞 for item in self.scene().items(): if isinstance(item, CustomEllipseItem) and item != self: if item.contains(line_end): # 如果线的端点与其他图形项碰撞 # print(f"Line endpoint collides with {item}") self.collideMouse.emit(self.id, item.id) def mouseReleaseEvent(self, event): # 鼠标释放执行 if event.button() == Qt.LeftButton: self.mouse_pressed = False # 鼠标松开时删除直线 if self.line_item is not None: self.scene().removeItem(self.line_item) self.line_item = None self.releaseMouse.emit(event) def hoverEnterEvent(self, event): # 鼠标悬停时设置光标为抓手 self.setCursor(Qt.PointingHandCursor) # 鼠标悬停时显示带背景的文字 info_text = QGraphicsTextItem(f"{self.id}-({self.x},{self.y})", self) info_text.setPos(self.x - 20, self.y + 20) self.info_text = info_text def hoverLeaveEvent(self, event): # 鼠标离开时恢复光标 self.unsetCursor() # 鼠标离开时移除信息 if hasattr(self, 'info_text'): self.scene().removeItem(self.info_text) del self.info_text def contextMenuEvent(self, event): # 在右键点击时选中图形项 self.setSelected(True) event.accept() def itemChange(self, change, value): if change == QGraphicsItem.ItemSelectedChange: if value: self.setBrush(QBrush(QColor(100, 255, 100, 255))) # 选中时设置为绿色 else: self.setBrush(self.brush_circle) # 取消选中时恢复原来的颜色 return super().itemChange(change, value)