375 lines
15 KiB
Python
375 lines
15 KiB
Python
|
from PySide6.QtCore import Qt, QPointF, QRectF, Signal
|
||
|
from PySide6.QtGui import QPixmap, QPainter, QPen, QBrush, QCursor, QColor, QPainterPath
|
||
|
from PySide6.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QPushButton,
|
||
|
QGraphicsView, QGraphicsScene, QGraphicsItem,
|
||
|
QGraphicsRectItem, QGraphicsEllipseItem,
|
||
|
QGraphicsPixmapItem, QSizePolicy)
|
||
|
|
||
|
class ResizableGraphicsItem(QGraphicsRectItem):
|
||
|
def __init__(self, x, y, width, height, parent=None):
|
||
|
super().__init__(x, y, width, height, parent)
|
||
|
self.setFlag(QGraphicsItem.ItemIsSelectable, True)
|
||
|
self.setFlag(QGraphicsItem.ItemIsMovable, True)
|
||
|
self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True)
|
||
|
self.setAcceptHoverEvents(True)
|
||
|
|
||
|
self.handle_size = 8
|
||
|
self.handles = {}
|
||
|
self.selected_handle = None
|
||
|
self.mouse_press_pos = None
|
||
|
self.mouse_press_rect = None
|
||
|
|
||
|
# 创建调整大小的手柄
|
||
|
self.create_handles()
|
||
|
|
||
|
def create_handles(self):
|
||
|
"""创建调整大小的手柄"""
|
||
|
rect = self.rect()
|
||
|
self.handles = {
|
||
|
"top_left": QRectF(rect.left(), rect.top(), self.handle_size, self.handle_size),
|
||
|
"top_right": QRectF(rect.right() - self.handle_size, rect.top(), self.handle_size, self.handle_size),
|
||
|
"bottom_left": QRectF(rect.left(), rect.bottom() - self.handle_size, self.handle_size, self.handle_size),
|
||
|
"bottom_right": QRectF(rect.right() - self.handle_size, rect.bottom() - self.handle_size, self.handle_size, self.handle_size),
|
||
|
"top": QRectF(rect.center().x() - self.handle_size/2, rect.top(), self.handle_size, self.handle_size),
|
||
|
"bottom": QRectF(rect.center().x() - self.handle_size/2, rect.bottom() - self.handle_size, self.handle_size, self.handle_size),
|
||
|
"left": QRectF(rect.left(), rect.center().y() - self.handle_size/2, self.handle_size, self.handle_size),
|
||
|
"right": QRectF(rect.right() - self.handle_size, rect.center().y() - self.handle_size/2, self.handle_size, self.handle_size),
|
||
|
}
|
||
|
|
||
|
def update_handles(self):
|
||
|
"""更新手柄位置"""
|
||
|
rect = self.rect()
|
||
|
self.handles["top_left"].moveTopLeft(rect.topLeft())
|
||
|
self.handles["top_right"].moveTopRight(rect.topRight())
|
||
|
self.handles["bottom_left"].moveBottomLeft(rect.bottomLeft())
|
||
|
self.handles["bottom_right"].moveBottomRight(rect.bottomRight())
|
||
|
self.handles["top"].moveCenter(QPointF(rect.center().x(), rect.top() + self.handle_size/2))
|
||
|
self.handles["bottom"].moveCenter(QPointF(rect.center().x(), rect.bottom() - self.handle_size/2))
|
||
|
self.handles["left"].moveCenter(QPointF(rect.left() + self.handle_size/2, rect.center().y()))
|
||
|
self.handles["right"].moveCenter(QPointF(rect.right() - self.handle_size/2, rect.center().y()))
|
||
|
|
||
|
def paint(self, painter, option, widget=None):
|
||
|
"""绘制项和手柄"""
|
||
|
# 绘制主矩形
|
||
|
pen = QPen(Qt.blue, 2, Qt.SolidLine)
|
||
|
if self.isSelected():
|
||
|
pen.setStyle(Qt.DashLine)
|
||
|
painter.setPen(pen)
|
||
|
painter.setBrush(QBrush(Qt.transparent))
|
||
|
painter.drawRect(self.rect())
|
||
|
|
||
|
# 如果选中,绘制手柄
|
||
|
if self.isSelected():
|
||
|
painter.setBrush(QBrush(Qt.white))
|
||
|
painter.setPen(QPen(Qt.black, 1))
|
||
|
for handle in self.handles.values():
|
||
|
painter.drawRect(handle)
|
||
|
|
||
|
def hoverMoveEvent(self, event):
|
||
|
"""鼠标悬停时检查是否在手柄上"""
|
||
|
for handle_name, handle_rect in self.handles.items():
|
||
|
if handle_rect.contains(event.pos()):
|
||
|
# 根据手柄位置设置不同的光标
|
||
|
if handle_name in ["top_left", "bottom_right"]:
|
||
|
self.setCursor(Qt.SizeFDiagCursor)
|
||
|
elif handle_name in ["top_right", "bottom_left"]:
|
||
|
self.setCursor(Qt.SizeBDiagCursor)
|
||
|
elif handle_name in ["top", "bottom"]:
|
||
|
self.setCursor(Qt.SizeVerCursor)
|
||
|
elif handle_name in ["left", "right"]:
|
||
|
self.setCursor(Qt.SizeHorCursor)
|
||
|
self.selected_handle = handle_name
|
||
|
return
|
||
|
|
||
|
self.setCursor(Qt.SizeAllCursor)
|
||
|
self.selected_handle = None
|
||
|
super().hoverMoveEvent(event)
|
||
|
|
||
|
def hoverLeaveEvent(self, event):
|
||
|
"""鼠标离开时恢复默认光标"""
|
||
|
self.setCursor(Qt.ArrowCursor)
|
||
|
self.selected_handle = None
|
||
|
super().hoverLeaveEvent(event)
|
||
|
|
||
|
def mousePressEvent(self, event):
|
||
|
"""鼠标按下事件"""
|
||
|
if event.button() == Qt.LeftButton:
|
||
|
self.mouse_press_pos = event.pos()
|
||
|
self.mouse_press_rect = self.rect()
|
||
|
|
||
|
# 如果点击的是手柄,则开始调整大小
|
||
|
if self.selected_handle:
|
||
|
event.accept()
|
||
|
return
|
||
|
|
||
|
super().mousePressEvent(event)
|
||
|
|
||
|
def mouseMoveEvent(self, event):
|
||
|
"""鼠标移动事件"""
|
||
|
if event.buttons() & Qt.LeftButton and self.mouse_press_pos:
|
||
|
if self.selected_handle:
|
||
|
# 调整大小
|
||
|
self.resize_item(event.pos())
|
||
|
event.accept()
|
||
|
return
|
||
|
else:
|
||
|
# 移动项
|
||
|
super().mouseMoveEvent(event)
|
||
|
else:
|
||
|
super().mouseMoveEvent(event)
|
||
|
|
||
|
def resize_item(self, mouse_pos):
|
||
|
"""根据鼠标位置调整项大小"""
|
||
|
rect = self.mouse_press_rect
|
||
|
pos = mouse_pos
|
||
|
new_rect = QRectF(rect)
|
||
|
|
||
|
# 根据选中的手柄调整矩形
|
||
|
if self.selected_handle == "top_left":
|
||
|
new_rect.setTopLeft(pos)
|
||
|
elif self.selected_handle == "top_right":
|
||
|
new_rect.setTopRight(pos)
|
||
|
elif self.selected_handle == "bottom_left":
|
||
|
new_rect.setBottomLeft(pos)
|
||
|
elif self.selected_handle == "bottom_right":
|
||
|
new_rect.setBottomRight(pos)
|
||
|
elif self.selected_handle == "top":
|
||
|
new_rect.setTop(pos.y())
|
||
|
elif self.selected_handle == "bottom":
|
||
|
new_rect.setBottom(pos.y())
|
||
|
elif self.selected_handle == "left":
|
||
|
new_rect.setLeft(pos.x())
|
||
|
elif self.selected_handle == "right":
|
||
|
new_rect.setRight(pos.x())
|
||
|
|
||
|
# 确保矩形不会太小
|
||
|
if new_rect.width() < 10:
|
||
|
if self.selected_handle in ["left", "top_left", "bottom_left"]:
|
||
|
new_rect.setLeft(new_rect.right() - 10)
|
||
|
else:
|
||
|
new_rect.setRight(new_rect.left() + 10)
|
||
|
|
||
|
if new_rect.height() < 10:
|
||
|
if self.selected_handle in ["top", "top_left", "top_right"]:
|
||
|
new_rect.setTop(new_rect.bottom() - 10)
|
||
|
else:
|
||
|
new_rect.setBottom(new_rect.top() + 10)
|
||
|
|
||
|
self.setRect(new_rect)
|
||
|
self.update_handles()
|
||
|
|
||
|
def mouseReleaseEvent(self, event):
|
||
|
"""鼠标释放事件"""
|
||
|
self.mouse_press_pos = None
|
||
|
self.mouse_press_rect = None
|
||
|
self.selected_handle = None
|
||
|
super().mouseReleaseEvent(event)
|
||
|
|
||
|
def itemChange(self, change, value):
|
||
|
"""项变化时更新手柄位置"""
|
||
|
if change == QGraphicsItem.ItemSelectedChange:
|
||
|
self.update_handles()
|
||
|
elif change == QGraphicsItem.ItemPositionHasChanged or change == QGraphicsItem.ItemTransformHasChanged:
|
||
|
self.update_handles()
|
||
|
|
||
|
return super().itemChange(change, value)
|
||
|
|
||
|
class ResizableEllipseItem(ResizableGraphicsItem):
|
||
|
def paint(self, painter, option, widget=None):
|
||
|
"""绘制椭圆和手柄"""
|
||
|
# 绘制主椭圆
|
||
|
pen = QPen(Qt.red, 2, Qt.SolidLine)
|
||
|
if self.isSelected():
|
||
|
pen.setStyle(Qt.DashLine)
|
||
|
painter.setPen(pen)
|
||
|
painter.setBrush(QBrush(Qt.transparent))
|
||
|
painter.drawEllipse(self.rect())
|
||
|
|
||
|
# 如果选中,绘制手柄
|
||
|
if self.isSelected():
|
||
|
painter.setBrush(QBrush(Qt.white))
|
||
|
painter.setPen(QPen(Qt.black, 1))
|
||
|
for handle in self.handles.values():
|
||
|
painter.drawRect(handle)
|
||
|
|
||
|
class DefectMarkEditor(QDialog):
|
||
|
def __init__(self, parent=None, image_path="", current_description=""):
|
||
|
super().__init__(parent)
|
||
|
self.setWindowTitle("缺陷标记编辑器")
|
||
|
self.setMinimumSize(800, 600)
|
||
|
|
||
|
self.image_path = image_path
|
||
|
self.current_description = current_description
|
||
|
self.mouse_state = "select" # select | create_rect | create_ellipse
|
||
|
self.start_pos = None
|
||
|
self.current_item = None
|
||
|
self.scale_factor = 1.0
|
||
|
|
||
|
self.init_ui()
|
||
|
self.load_image()
|
||
|
|
||
|
def init_ui(self):
|
||
|
main_layout = QVBoxLayout(self)
|
||
|
|
||
|
# 工具栏
|
||
|
toolbar = QHBoxLayout()
|
||
|
|
||
|
self.select_btn = QPushButton("选择")
|
||
|
self.select_btn.setCheckable(True)
|
||
|
self.select_btn.setChecked(True)
|
||
|
self.select_btn.clicked.connect(self.set_select_mode)
|
||
|
|
||
|
self.rect_btn = QPushButton("矩形框")
|
||
|
self.rect_btn.setCheckable(True)
|
||
|
self.rect_btn.clicked.connect(self.set_create_rect_mode)
|
||
|
|
||
|
self.ellipse_btn = QPushButton("圆形框")
|
||
|
self.ellipse_btn.setCheckable(True)
|
||
|
self.ellipse_btn.clicked.connect(self.set_create_ellipse_mode)
|
||
|
|
||
|
self.clear_btn = QPushButton("清除所有标记")
|
||
|
self.clear_btn.clicked.connect(self.clear_all_items)
|
||
|
|
||
|
toolbar.addWidget(self.select_btn)
|
||
|
toolbar.addWidget(self.rect_btn)
|
||
|
toolbar.addWidget(self.ellipse_btn)
|
||
|
toolbar.addWidget(self.clear_btn)
|
||
|
|
||
|
# 场景和视图
|
||
|
self.scene = QGraphicsScene(self)
|
||
|
self.view = CustomGraphicsView(self.scene, self)
|
||
|
self.view.setRenderHint(QPainter.Antialiasing)
|
||
|
self.view.setDragMode(QGraphicsView.RubberBandDrag)
|
||
|
self.view.setMouseTracking(True)
|
||
|
|
||
|
main_layout.addLayout(toolbar)
|
||
|
main_layout.addWidget(self.view)
|
||
|
|
||
|
def load_image(self):
|
||
|
"""加载图片到场景"""
|
||
|
if not self.image_path:
|
||
|
return
|
||
|
|
||
|
self.scene.clear()
|
||
|
self.pixmap_item = QGraphicsPixmapItem(QPixmap(self.image_path))
|
||
|
self.pixmap_item.setFlag(QGraphicsItem.ItemIsMovable, False)
|
||
|
self.pixmap_item.setFlag(QGraphicsItem.ItemIsSelectable, False)
|
||
|
self.scene.addItem(self.pixmap_item)
|
||
|
|
||
|
# 设置场景大小为图片大小
|
||
|
self.scene.setSceneRect(self.pixmap_item.boundingRect())
|
||
|
|
||
|
def set_select_mode(self):
|
||
|
"""设置为选择模式"""
|
||
|
self.mouse_state = "select"
|
||
|
self.select_btn.setChecked(True)
|
||
|
self.rect_btn.setChecked(False)
|
||
|
self.ellipse_btn.setChecked(False)
|
||
|
self.view.setDragMode(QGraphicsView.RubberBandDrag)
|
||
|
|
||
|
def set_create_rect_mode(self):
|
||
|
"""设置为创建矩形模式"""
|
||
|
self.mouse_state = "create_rect"
|
||
|
self.select_btn.setChecked(False)
|
||
|
self.rect_btn.setChecked(True)
|
||
|
self.ellipse_btn.setChecked(False)
|
||
|
self.view.setDragMode(QGraphicsView.NoDrag)
|
||
|
|
||
|
def set_create_ellipse_mode(self):
|
||
|
"""设置为创建圆形模式"""
|
||
|
self.mouse_state = "create_ellipse"
|
||
|
self.select_btn.setChecked(False)
|
||
|
self.rect_btn.setChecked(False)
|
||
|
self.ellipse_btn.setChecked(True)
|
||
|
self.view.setDragMode(QGraphicsView.NoDrag)
|
||
|
|
||
|
def clear_all_items(self):
|
||
|
"""清除所有标记项"""
|
||
|
for item in self.scene.items():
|
||
|
if isinstance(item, (ResizableGraphicsItem, ResizableEllipseItem)):
|
||
|
self.scene.removeItem(item)
|
||
|
|
||
|
def mouse_press_on_scene(self, pos):
|
||
|
"""场景鼠标按下事件"""
|
||
|
if self.mouse_state == "select":
|
||
|
return
|
||
|
|
||
|
self.start_pos = pos
|
||
|
if self.mouse_state == "create_rect":
|
||
|
self.current_item = ResizableGraphicsItem(pos.x(), pos.y(), 1, 1)
|
||
|
elif self.mouse_state == "create_ellipse":
|
||
|
self.current_item = ResizableEllipseItem(pos.x(), pos.y(), 1, 1)
|
||
|
|
||
|
if self.current_item:
|
||
|
self.scene.addItem(self.current_item)
|
||
|
|
||
|
def mouse_move_on_scene(self, pos):
|
||
|
"""场景鼠标移动事件"""
|
||
|
if not self.start_pos or not self.current_item:
|
||
|
return
|
||
|
|
||
|
rect = QRectF(self.start_pos, pos).normalized()
|
||
|
self.current_item.setRect(rect)
|
||
|
|
||
|
def mouse_release_on_scene(self, pos):
|
||
|
"""场景鼠标释放事件"""
|
||
|
if self.current_item and self.mouse_state != "select":
|
||
|
# 如果创建的项太小,则删除
|
||
|
if self.current_item.rect().width() < 10 or self.current_item.rect().height() < 10:
|
||
|
self.scene.removeItem(self.current_item)
|
||
|
|
||
|
self.start_pos = None
|
||
|
self.current_item = None
|
||
|
|
||
|
# 创建完成后自动回到选择模式
|
||
|
self.set_select_mode()
|
||
|
|
||
|
class CustomGraphicsView(QGraphicsView):
|
||
|
def __init__(self, scene, editor, parent=None):
|
||
|
super().__init__(scene, parent)
|
||
|
self.editor = editor
|
||
|
self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
|
||
|
self.setResizeAnchor(QGraphicsView.AnchorUnderMouse)
|
||
|
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
|
||
|
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
|
||
|
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||
|
|
||
|
def wheelEvent(self, event):
|
||
|
"""鼠标滚轮缩放"""
|
||
|
factor = 1.2
|
||
|
if event.angleDelta().y() < 0:
|
||
|
factor = 1.0 / factor
|
||
|
|
||
|
self.scale(factor, factor)
|
||
|
self.editor.scale_factor *= factor
|
||
|
|
||
|
def mousePressEvent(self, event):
|
||
|
"""鼠标按下事件"""
|
||
|
if event.button() == Qt.LeftButton:
|
||
|
scene_pos = self.mapToScene(event.pos())
|
||
|
items = self.scene().items(scene_pos)
|
||
|
|
||
|
# 如果点击的是背景或图片,则开始创建
|
||
|
if not items or items[-1] == self.editor.pixmap_item:
|
||
|
self.editor.mouse_press_on_scene(scene_pos)
|
||
|
else:
|
||
|
super().mousePressEvent(event)
|
||
|
else:
|
||
|
super().mousePressEvent(event)
|
||
|
|
||
|
def mouseMoveEvent(self, event):
|
||
|
"""鼠标移动事件"""
|
||
|
if self.editor.start_pos and self.editor.current_item:
|
||
|
scene_pos = self.mapToScene(event.pos())
|
||
|
self.editor.mouse_move_on_scene(scene_pos)
|
||
|
else:
|
||
|
super().mouseMoveEvent(event)
|
||
|
|
||
|
def mouseReleaseEvent(self, event):
|
||
|
"""鼠标释放事件"""
|
||
|
if event.button() == Qt.LeftButton and self.editor.start_pos:
|
||
|
scene_pos = self.mapToScene(event.pos())
|
||
|
self.editor.mouse_release_on_scene(scene_pos)
|
||
|
else:
|
||
|
super().mouseReleaseEvent(event)
|