You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

239 lines
9.1 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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)