增加缺陷填写框,右侧图片列表能够接收图片放入并同步树模型和、json文件
This commit is contained in:
parent
81d6711fac
commit
cc460cfbcd
|
@ -0,0 +1,374 @@
|
||||||
|
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)
|
|
@ -6,7 +6,8 @@ from PySide6.QtWidgets import (QGroupBox, QVBoxLayout, QHBoxLayout, QCheckBox,
|
||||||
QStackedWidget, QStackedLayout, QStyledItemDelegate,
|
QStackedWidget, QStackedLayout, QStyledItemDelegate,
|
||||||
QTreeView, QSplitter, QFileSystemModel, QScrollArea,
|
QTreeView, QSplitter, QFileSystemModel, QScrollArea,
|
||||||
QToolTip, QGridLayout, QSizePolicy, QProgressDialog,
|
QToolTip, QGridLayout, QSizePolicy, QProgressDialog,
|
||||||
QButtonGroup, QInputDialog, QMenu)
|
QButtonGroup, QInputDialog, QMenu, QComboBox, QDialogButtonBox,
|
||||||
|
QTextEdit, QStyle)
|
||||||
from PySide6.QtCore import (QDateTime,QTimer,QDateTime,Signal,QSettings, QPoint, QEvent,
|
from PySide6.QtCore import (QDateTime,QTimer,QDateTime,Signal,QSettings, QPoint, QEvent,
|
||||||
QSortFilterProxyModel, QSize, QDir, QMimeData, QRunnable, QObject,
|
QSortFilterProxyModel, QSize, QDir, QMimeData, QRunnable, QObject,
|
||||||
QThreadPool, QPoint, QRect, QUrl)
|
QThreadPool, QPoint, QRect, QUrl)
|
||||||
|
@ -14,8 +15,9 @@ from PySide6.QtGui import (QPixmap, QDragEnterEvent, QDropEvent, Qt, QIcon,
|
||||||
QFontMetrics, QStandardItem, QStandardItemModel,
|
QFontMetrics, QStandardItem, QStandardItemModel,
|
||||||
QAction, QColor, QImageReader, QDrag, QCursor,
|
QAction, QColor, QImageReader, QDrag, QCursor,
|
||||||
QPainter)
|
QPainter)
|
||||||
import json, sys, os
|
import json, sys, os, re, time
|
||||||
|
|
||||||
|
from info_core.Img_edit import DefectMarkEditor
|
||||||
from info_core.defines import *
|
from info_core.defines import *
|
||||||
|
|
||||||
class ConfigComboBoxGroup(QGroupBox):
|
class ConfigComboBoxGroup(QGroupBox):
|
||||||
|
@ -1514,33 +1516,68 @@ class FolderBrowser(QWidget):
|
||||||
self.right_model.dataChanged.connect(self.on_right_data_changed)
|
self.right_model.dataChanged.connect(self.on_right_data_changed)
|
||||||
self.left_tree.expanded.connect(self.sync_right_tree_expand)
|
self.left_tree.expanded.connect(self.sync_right_tree_expand)
|
||||||
self.left_tree.collapsed.connect(self.sync_right_tree_collapse)
|
self.left_tree.collapsed.connect(self.sync_right_tree_collapse)
|
||||||
|
self.left_tree.clicked.connect(self.on_left_tree_clicked)
|
||||||
|
self.folder_selected.connect(self.image_browser.show_images)
|
||||||
|
|
||||||
self.right_tree.expanded.connect(self.sync_left_tree_expand)
|
self.right_tree.expanded.connect(self.sync_left_tree_expand)
|
||||||
self.right_tree.collapsed.connect(self.sync_left_tree_collapse)
|
self.right_tree.collapsed.connect(self.sync_left_tree_collapse)
|
||||||
self.right_tree.clicked.connect(self.on_right_tree_clicked)
|
self.right_tree.clicked.connect(self.on_right_tree_clicked)
|
||||||
self.left_tree.clicked.connect(self.on_left_tree_clicked)
|
|
||||||
self.folder_selected.connect(self.image_browser.show_images)
|
self.dictionary_browser.dictionary_updated.connect(self.handle_dictionary_update)
|
||||||
self.folder_selected.connect(self.update_blade_name)
|
|
||||||
|
def handle_dictionary_update(self, folder_name, part_name, dict_type_en, dict_data):
|
||||||
|
"""处理字典更新"""
|
||||||
|
print(f"🔧 字典更新 {self.json_data}:\n[{folder_name}][{part_name}][{dict_type_en}] -> \n{dict_data}")
|
||||||
|
# 1. 更新JSON数据
|
||||||
|
if folder_name in self.json_data and part_name in self.json_data[folder_name]:
|
||||||
|
self.json_data[folder_name][part_name][dict_type_en] = dict_data
|
||||||
|
|
||||||
|
# 2. 更新树模型
|
||||||
|
self.update_right_tree(folder_name)
|
||||||
|
|
||||||
|
# 3. 保存到文件
|
||||||
|
self.save_to_json(folder_name)
|
||||||
|
|
||||||
|
def update_right_tree(self, folder_name):
|
||||||
|
"""更新右侧树视图"""
|
||||||
|
# 找到对应的文件夹项
|
||||||
|
for row in range(self.right_model.rowCount()):
|
||||||
|
folder_item = self.right_model.item(row, 0)
|
||||||
|
if folder_item.text() == folder_name:
|
||||||
|
# 更新整个子树
|
||||||
|
TreeModelManager.update_model_from_json(
|
||||||
|
self.right_model,
|
||||||
|
folder_name,
|
||||||
|
self.json_data
|
||||||
|
)
|
||||||
|
# 展开该节点
|
||||||
|
self.right_tree.expand(folder_item.index())
|
||||||
|
break
|
||||||
|
|
||||||
def on_right_tree_clicked(self, index):
|
def on_right_tree_clicked(self, index):
|
||||||
"""处理右侧树视图点击事件"""
|
"""处理右侧树视图点击事件"""
|
||||||
if not index.isValid():
|
if not index.isValid():
|
||||||
return
|
return
|
||||||
|
|
||||||
item = self.right_model.itemFromIndex(index)
|
item = self.right_model.itemFromIndex(index)
|
||||||
path = self.get_item_path(item)
|
text = item.text() # 获取显示文本(中文)
|
||||||
print(f"点击了右侧树节点: {item.text()}")
|
print(f"点击了右侧树节点: {text}")
|
||||||
|
# 检查是否点击了字典节点(中文)
|
||||||
|
if text in ["缺陷图字典", "典型图字典"]:
|
||||||
|
# 获取part名称和原文件夹名
|
||||||
|
part_item = item.parent()
|
||||||
|
folder_item = part_item.parent() if part_item else None
|
||||||
|
|
||||||
# 检查是否点击了字典节点
|
if part_item and folder_item:
|
||||||
if "dictionaries" in path:
|
folder_name = folder_item.text()
|
||||||
parts = path.split("/dictionaries/")
|
part_name = part_item.text()
|
||||||
if len(parts) == 2:
|
# 转换为英文键名获取数据
|
||||||
blade_name = parts[0]
|
dict_type = DictionaryBrowser.DICT_TYPE_MAPPING.get(text, "")
|
||||||
dict_type = parts[1]
|
if dict_type:
|
||||||
|
dict_data = self.json_data.get(folder_name, {}).get(DictionaryBrowser.DICT_TYPE_MAPPING.get(part_name, "")).get(dict_type, {})
|
||||||
|
|
||||||
# 获取字典数据
|
# 传递中文显示名称
|
||||||
dict_data = self.json_data.get(blade_name, {}).get("dictionaries", {}).get(dict_type, {})
|
self.dictionary_browser.show_dictionary(folder_name, part_name, text, dict_data)
|
||||||
|
|
||||||
# 更新字典浏览器
|
|
||||||
self.dictionary_browser.show_dictionary(blade_name, dict_type, dict_data)
|
|
||||||
|
|
||||||
def get_item_path(self, item):
|
def get_item_path(self, item):
|
||||||
"""获取树节点路径"""
|
"""获取树节点路径"""
|
||||||
|
@ -1556,22 +1593,6 @@ class FolderBrowser(QWidget):
|
||||||
blade_name = os.path.basename(folder_path.rstrip('/\\'))
|
blade_name = os.path.basename(folder_path.rstrip('/\\'))
|
||||||
self.dictionary_browser.set_current_blade(blade_name)
|
self.dictionary_browser.set_current_blade(blade_name)
|
||||||
|
|
||||||
def save_dictionary_data(self, dict_type, blade_name, data):
|
|
||||||
"""保存字典数据到JSON"""
|
|
||||||
# 找到对应的文件夹项
|
|
||||||
for folder_name in self.json_data:
|
|
||||||
if blade_name in folder_name: # 简单匹配逻辑,可根据实际情况调整
|
|
||||||
# 在JSON数据中创建或更新字典数据
|
|
||||||
if "dictionaries" not in self.json_data[folder_name]:
|
|
||||||
self.json_data[folder_name]["dictionaries"] = {}
|
|
||||||
|
|
||||||
if dict_type not in self.json_data[folder_name]["dictionaries"]:
|
|
||||||
self.json_data[folder_name]["dictionaries"][dict_type] = {}
|
|
||||||
|
|
||||||
self.json_data[folder_name]["dictionaries"][dict_type] = data
|
|
||||||
self.save_to_json(folder_name)
|
|
||||||
break
|
|
||||||
|
|
||||||
def on_left_tree_clicked(self, index):
|
def on_left_tree_clicked(self, index):
|
||||||
"""处理左侧树视图选择变化事件"""
|
"""处理左侧树视图选择变化事件"""
|
||||||
if not index.isValid():
|
if not index.isValid():
|
||||||
|
@ -1744,6 +1765,11 @@ class FolderBrowser(QWidget):
|
||||||
self.data_changed.emit(self.json_data)
|
self.data_changed.emit(self.json_data)
|
||||||
|
|
||||||
def save_to_json(self, folder_name):
|
def save_to_json(self, folder_name):
|
||||||
|
"""将类中现在的self.json_data保存到JSON文件
|
||||||
|
|
||||||
|
Args:
|
||||||
|
folder_name (str): 文件夹名称(要保存的json文件名,文件名为机组号)
|
||||||
|
"""
|
||||||
json_path = self.get_json_file_path(folder_name)
|
json_path = self.get_json_file_path(folder_name)
|
||||||
print(f"🛠️ 准备保存到: {json_path}")
|
print(f"🛠️ 准备保存到: {json_path}")
|
||||||
|
|
||||||
|
@ -2278,20 +2304,59 @@ class ImageBrowser(QWidget):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"拖动失败: {str(e)}")
|
print(f"拖动失败: {str(e)}")
|
||||||
|
|
||||||
|
class OverlayLabel(QLabel):
|
||||||
|
"""支持叠加图标的标签控件"""
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self._overlay_icon = None
|
||||||
|
|
||||||
|
def set_overlay_icon(self, standard_pixmap):
|
||||||
|
"""设置叠加图标"""
|
||||||
|
self._overlay_icon = standard_pixmap
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def paintEvent(self, event):
|
||||||
|
"""重绘事件,绘制图标叠加"""
|
||||||
|
super().paintEvent(event)
|
||||||
|
|
||||||
|
if self._overlay_icon and self.pixmap():
|
||||||
|
# 获取系统标准图标
|
||||||
|
icon = self.style().standardIcon(self._overlay_icon)
|
||||||
|
pixmap = icon.pixmap(24, 24) # 图标大小
|
||||||
|
|
||||||
|
# 在右下角绘制图标
|
||||||
|
painter = QPainter(self)
|
||||||
|
margin = 5
|
||||||
|
x = self.width() - pixmap.width() - margin
|
||||||
|
y = self.height() - pixmap.height() - margin
|
||||||
|
painter.drawPixmap(x, y, pixmap)
|
||||||
|
painter.end()
|
||||||
|
|
||||||
class DictionaryBrowser(QWidget):
|
class DictionaryBrowser(QWidget):
|
||||||
"""右侧叶片典型/缺陷字典浏览窗口"""
|
"""字典浏览编辑窗口"""
|
||||||
dictionary_updated = Signal(str, str, dict) # 字典类型, 叶片名称, 字典数据
|
dictionary_updated = Signal(str, str, str, dict) # 原文件夹名, part名称, 字典类型(英文), 字典数据
|
||||||
|
|
||||||
|
# 中英文映射字典
|
||||||
|
DICT_TYPE_MAPPING = {
|
||||||
|
"缺陷图字典": "defect_picture_dict",
|
||||||
|
"典型图字典": "typical_picture_dict",
|
||||||
|
"叶片1" : "part1",
|
||||||
|
"叶片2" : "part2",
|
||||||
|
"叶片3" : "part3",
|
||||||
|
}
|
||||||
|
|
||||||
|
REVERSE_DICT_MAPPING = {v: k for k, v in DICT_TYPE_MAPPING.items()}
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.current_blade = "" # 当前叶片名称
|
self.original_folder_name = "" # 原文件夹名
|
||||||
self.current_dict_type = "" # 当前字典类型(典型/缺陷)
|
self.current_part = "" # 当前part名称(part1/part2/part3)
|
||||||
self.dictionaries = {} # 存储所有字典数据
|
self.current_dict_type = "" # 当前字典类型(英文)
|
||||||
|
self.current_dict_data = {} # 当前字典数据
|
||||||
self.init_ui()
|
self.init_ui()
|
||||||
|
|
||||||
def init_ui(self):
|
def init_ui(self):
|
||||||
"""初始化用户界面"""
|
"""初始化界面"""
|
||||||
self.setMinimumWidth(300)
|
self.setMinimumWidth(300)
|
||||||
layout = QVBoxLayout(self)
|
layout = QVBoxLayout(self)
|
||||||
layout.setContentsMargins(5, 5, 5, 5)
|
layout.setContentsMargins(5, 5, 5, 5)
|
||||||
|
@ -2305,106 +2370,112 @@ class DictionaryBrowser(QWidget):
|
||||||
# 图片浏览区域
|
# 图片浏览区域
|
||||||
self.scroll_area = QScrollArea()
|
self.scroll_area = QScrollArea()
|
||||||
self.scroll_area.setWidgetResizable(True)
|
self.scroll_area.setWidgetResizable(True)
|
||||||
|
|
||||||
# 使用网格布局
|
|
||||||
self.image_container = QWidget()
|
|
||||||
self.image_layout = QGridLayout(self.image_container)
|
|
||||||
self.image_layout.setContentsMargins(5, 5, 5, 5)
|
|
||||||
self.image_layout.setSpacing(10)
|
|
||||||
self.image_layout.setAlignment(Qt.AlignTop)
|
|
||||||
|
|
||||||
self.scroll_area.setWidget(self.image_container)
|
|
||||||
layout.addWidget(self.scroll_area)
|
layout.addWidget(self.scroll_area)
|
||||||
|
|
||||||
# 初始状态提示
|
# 初始状态提示
|
||||||
self.placeholder_label = QLabel("请点击右侧JSON树中的字典节点")
|
self.set_placeholder("请点击右侧JSON树中的字典节点")
|
||||||
self.placeholder_label.setAlignment(Qt.AlignCenter)
|
|
||||||
self.placeholder_label.setStyleSheet("color: gray; font-size: 14px;")
|
|
||||||
self.scroll_area.setWidget(self.placeholder_label)
|
|
||||||
|
|
||||||
# 设置拖放接收
|
# 设置拖放接收
|
||||||
self.setAcceptDrops(True)
|
self.setAcceptDrops(True)
|
||||||
|
|
||||||
def show_dictionary(self, blade_name, dict_type, dict_data):
|
def set_placeholder(self, text):
|
||||||
"""显示指定字典内容"""
|
"""设置占位文本"""
|
||||||
self.current_blade = blade_name
|
label = QLabel(text)
|
||||||
self.current_dict_type = dict_type
|
label.setAlignment(Qt.AlignCenter)
|
||||||
self.dictionaries = dict_data
|
label.setStyleSheet("color: gray; font-size: 14px;")
|
||||||
|
self.scroll_area.setWidget(label)
|
||||||
|
|
||||||
|
def show_dictionary(self, folder_name, part_name, dict_type_display, dict_data):
|
||||||
|
"""显示指定字典内容
|
||||||
|
:param dict_type_display: 显示用的字典类型名称(中文)
|
||||||
|
"""
|
||||||
|
self.original_folder_name = folder_name
|
||||||
|
self.current_part = self.DICT_TYPE_MAPPING.get(part_name, "")
|
||||||
|
# 转换为英文键名存储
|
||||||
|
self.current_dict_type = self.DICT_TYPE_MAPPING.get(dict_type_display, "")
|
||||||
|
self.current_dict_data = dict_data or {}
|
||||||
|
|
||||||
self.update_title()
|
self.update_title()
|
||||||
self.refresh_dictionary_view()
|
self.refresh_view()
|
||||||
|
|
||||||
def setup_connections(self):
|
|
||||||
"""设置信号和槽的连接"""
|
|
||||||
self.dict_type_group.buttonClicked.connect(self.on_dict_type_changed)
|
|
||||||
|
|
||||||
def on_dict_type_changed(self, button):
|
|
||||||
"""处理字典类型选择变化"""
|
|
||||||
# 直接比较按钮对象来确定类型
|
|
||||||
if button == self.typical_btn:
|
|
||||||
self.current_dict_type = "典型图"
|
|
||||||
else:
|
|
||||||
self.current_dict_type = "缺陷图"
|
|
||||||
self.update_title()
|
|
||||||
self.refresh_dictionary_view()
|
|
||||||
|
|
||||||
def set_current_blade(self, blade_name):
|
|
||||||
"""设置当前叶片名称"""
|
|
||||||
self.current_blade = blade_name
|
|
||||||
self.update_title()
|
|
||||||
self.refresh_dictionary_view()
|
|
||||||
|
|
||||||
def update_title(self):
|
def update_title(self):
|
||||||
"""更新标题显示"""
|
"""更新标题显示(使用中文)"""
|
||||||
if not self.current_blade or not self.current_dict_type:
|
if not all([self.original_folder_name, self.current_part, self.current_dict_type]):
|
||||||
self.title_label.setText("请选择字典节点")
|
self.title_label.setText("请选择字典节点")
|
||||||
else:
|
else:
|
||||||
self.title_label.setText(f"{self.current_blade} - {self.current_dict_type}")
|
# 转换为中文显示
|
||||||
|
dict_name = self.REVERSE_DICT_MAPPING.get(self.current_dict_type, self.current_dict_type)
|
||||||
|
self.title_label.setText(f"{self.original_folder_name} - {self.current_part} - {dict_name}")
|
||||||
|
|
||||||
def refresh_dictionary_view(self):
|
def refresh_view(self):
|
||||||
"""刷新字典视图"""
|
"""刷新视图"""
|
||||||
if not self.current_dict_type or not self.current_blade:
|
# 安全清除旧内容
|
||||||
self.scroll_area.setWidget(self.placeholder_label)
|
old_widget = self.scroll_area.takeWidget()
|
||||||
|
if old_widget:
|
||||||
|
old_widget.deleteLater()
|
||||||
|
|
||||||
|
if not all([self.original_folder_name, self.current_part, self.current_dict_type]):
|
||||||
|
self.set_placeholder("请选择有效的字典节点")
|
||||||
return
|
return
|
||||||
|
|
||||||
# 获取当前字典数据
|
if not self.current_dict_data:
|
||||||
dict_data = self.dictionaries.get(self.current_dict_type, {})
|
self.set_placeholder("该字典没有数据")
|
||||||
|
|
||||||
if not dict_data:
|
|
||||||
no_data_label = QLabel(f"该字典没有数据")
|
|
||||||
no_data_label.setAlignment(Qt.AlignCenter)
|
|
||||||
no_data_label.setStyleSheet("color: gray; font-size: 14px;")
|
|
||||||
self.scroll_area.setWidget(no_data_label)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# 创建图片容器
|
# 创建新的容器
|
||||||
self.image_container = QWidget()
|
container = QWidget()
|
||||||
self.image_layout = QGridLayout(self.image_container)
|
layout = QVBoxLayout(container) # 改为垂直布局
|
||||||
self.image_layout.setContentsMargins(5, 5, 5, 5)
|
layout.setContentsMargins(5, 5, 5, 5)
|
||||||
self.image_layout.setSpacing(5)
|
layout.setSpacing(5)
|
||||||
self.image_layout.setAlignment(Qt.AlignTop)
|
|
||||||
|
|
||||||
self.scroll_area.setWidget(self.image_container)
|
# 创建水平流式布局的容器
|
||||||
|
flow_widget = QWidget()
|
||||||
|
flow_layout = QHBoxLayout(flow_widget)
|
||||||
|
flow_layout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
flow_layout.setSpacing(5)
|
||||||
|
flow_layout.setAlignment(Qt.AlignLeft | Qt.AlignTop) # 左对齐且顶部对齐
|
||||||
|
|
||||||
# 添加图片项
|
# 添加图片项
|
||||||
for i, (desc, img_path) in enumerate(dict_data.items()):
|
for key, img_path in self.current_dict_data.items():
|
||||||
self.add_image_item(img_path, desc, i)
|
self._add_image_item(flow_layout, img_path, key)
|
||||||
|
|
||||||
def add_image_item(self, img_path, description, index):
|
# 添加流式布局到主布局
|
||||||
"""添加单个图片项到布局"""
|
layout.addWidget(flow_widget)
|
||||||
thumbnail_size = 150 # 缩略图大小
|
layout.addStretch(1) # 添加拉伸因子使内容保持在顶部
|
||||||
|
|
||||||
|
self.scroll_area.setWidget(container)
|
||||||
|
|
||||||
|
def _check_description_format(self, key):
|
||||||
|
"""检查描述是否符合标准格式"""
|
||||||
|
if not key or "_" not in key:
|
||||||
|
return False
|
||||||
|
|
||||||
|
parts = key.split("_")
|
||||||
|
# 检查是否有足够的字段(序号+6个标准字段+建议)
|
||||||
|
return len(parts) >= 8
|
||||||
|
|
||||||
|
def _add_image_item(self, parent_layout, img_path, key):
|
||||||
|
"""添加单个图片项 - 只显示图片,符合格式的显示对勾"""
|
||||||
|
thumbnail_size = 150
|
||||||
|
|
||||||
# 创建容器widget
|
# 创建容器widget
|
||||||
item_widget = QWidget()
|
item_widget = QWidget()
|
||||||
|
|
||||||
|
# 设置悬停提示
|
||||||
|
hover_text = f"图片路径: {img_path}\n\n描述详情:\n{self.get_description_display(key)}"
|
||||||
|
item_widget.setToolTip(hover_text)
|
||||||
|
|
||||||
item_layout = QVBoxLayout(item_widget)
|
item_layout = QVBoxLayout(item_widget)
|
||||||
item_layout.setContentsMargins(5, 5, 5, 5)
|
item_layout.setContentsMargins(5, 5, 5, 5)
|
||||||
item_layout.setSpacing(3)
|
item_layout.setSpacing(0)
|
||||||
|
|
||||||
# 图片标签
|
# 图片标签 - 使用自定义的OverlayLabel类
|
||||||
img_label = QLabel()
|
img_label = OverlayLabel()
|
||||||
img_label.setFixedSize(thumbnail_size, thumbnail_size)
|
img_label.setFixedSize(thumbnail_size, thumbnail_size)
|
||||||
img_label.setAlignment(Qt.AlignCenter)
|
img_label.setAlignment(Qt.AlignCenter)
|
||||||
img_label.setStyleSheet("border: 1px solid #ddd; background: white;")
|
|
||||||
|
# 检查描述是否符合格式
|
||||||
|
is_valid_format = self._check_description_format(key)
|
||||||
|
|
||||||
# 加载图片
|
# 加载图片
|
||||||
if os.path.exists(img_path):
|
if os.path.exists(img_path):
|
||||||
|
@ -2414,140 +2485,398 @@ class DictionaryBrowser(QWidget):
|
||||||
thumbnail_size, thumbnail_size,
|
thumbnail_size, thumbnail_size,
|
||||||
Qt.KeepAspectRatio, Qt.SmoothTransformation
|
Qt.KeepAspectRatio, Qt.SmoothTransformation
|
||||||
))
|
))
|
||||||
|
# 如果格式正确,显示对勾
|
||||||
|
if is_valid_format:
|
||||||
|
img_label.set_overlay_icon(QStyle.SP_DialogApplyButton)
|
||||||
else:
|
else:
|
||||||
img_label.setText("无效图片")
|
img_label.setText("无效图片")
|
||||||
|
img_label.setStyleSheet("color: red;")
|
||||||
else:
|
else:
|
||||||
img_label.setText("图片不存在")
|
img_label.setText("图片不存在")
|
||||||
|
img_label.setStyleSheet("color: red;")
|
||||||
|
|
||||||
# 描述标签
|
|
||||||
desc_label = QLabel(description)
|
|
||||||
desc_label.setAlignment(Qt.AlignCenter)
|
|
||||||
desc_label.setStyleSheet("font-size: 11px;")
|
|
||||||
desc_label.setWordWrap(True)
|
|
||||||
|
|
||||||
# 添加到布局
|
|
||||||
item_layout.addWidget(img_label)
|
item_layout.addWidget(img_label)
|
||||||
item_layout.addWidget(desc_label)
|
|
||||||
|
|
||||||
# 添加上下文菜单
|
# 添加上下文菜单
|
||||||
item_widget.setContextMenuPolicy(Qt.CustomContextMenu)
|
item_widget.setContextMenuPolicy(Qt.CustomContextMenu)
|
||||||
item_widget.customContextMenuRequested.connect(
|
item_widget.customContextMenuRequested.connect(
|
||||||
lambda pos, path=img_path, desc=description: self.show_context_menu(pos, path, desc)
|
lambda pos, path=img_path, k=key: self.show_context_menu(pos, path, k))
|
||||||
|
|
||||||
|
parent_layout.addWidget(item_widget)
|
||||||
|
|
||||||
|
def get_description_display(self, key):
|
||||||
|
"""获取描述信息的显示文本 - 用于悬停提示"""
|
||||||
|
if not key or "_" not in key:
|
||||||
|
return "⚠ 无描述信息"
|
||||||
|
|
||||||
|
parts = key.split("_")
|
||||||
|
if len(parts) < 8:
|
||||||
|
return f"⚠ 描述格式不完整\n当前描述: {key}"
|
||||||
|
|
||||||
|
# 格式化显示
|
||||||
|
description = (
|
||||||
|
f"✔ 已编辑描述\n"
|
||||||
|
f"缺陷类型: {parts[1]}\n"
|
||||||
|
f"位置: {parts[2]}\n"
|
||||||
|
f"尺寸: {parts[3]}\n"
|
||||||
|
f"可见程度: {parts[4]}\n"
|
||||||
|
f"紧急程度: {parts[5]}\n"
|
||||||
|
f"危重等级: {parts[6]}\n"
|
||||||
|
f"维修建议: {'_'.join(parts[7:])}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# 计算位置
|
return description
|
||||||
cols_per_row = max(1, self.width() // (thumbnail_size + 20))
|
|
||||||
row = index // cols_per_row
|
|
||||||
col = index % cols_per_row
|
|
||||||
|
|
||||||
self.image_layout.addWidget(item_widget, row, col)
|
|
||||||
|
|
||||||
def show_context_menu(self, pos, img_path, description):
|
def dragEnterEvent(self, event):
|
||||||
|
"""拖拽进入事件"""
|
||||||
|
if event.mimeData().hasUrls():
|
||||||
|
event.acceptProposedAction()
|
||||||
|
|
||||||
|
def dropEvent(self, event):
|
||||||
|
"""处理拖放事件"""
|
||||||
|
if not all([self.original_folder_name, self.current_part, self.current_dict_type]):
|
||||||
|
QMessageBox.warning(self, "警告", "请先选择有效的字典节点")
|
||||||
|
return
|
||||||
|
|
||||||
|
urls = event.mimeData().urls()
|
||||||
|
if urls:
|
||||||
|
img_path = urls[0].toLocalFile()
|
||||||
|
if os.path.isfile(img_path):
|
||||||
|
# 生成新键(使用当前最大数字+1)
|
||||||
|
keys = [int(k) for k in self.current_dict_data.keys() if k.isdigit()]
|
||||||
|
max_key = max(keys) if keys else -1
|
||||||
|
new_key = str(max_key + 1)
|
||||||
|
|
||||||
|
# 更新字典数据
|
||||||
|
self.current_dict_data[new_key] = img_path
|
||||||
|
|
||||||
|
# 刷新显示
|
||||||
|
self.refresh_view()
|
||||||
|
|
||||||
|
# 发射信号通知更新 (包含英文字典类型)
|
||||||
|
self.dictionary_updated.emit(
|
||||||
|
self.original_folder_name,
|
||||||
|
self.current_part,
|
||||||
|
self.current_dict_type, # 英文键名
|
||||||
|
self.current_dict_data
|
||||||
|
)
|
||||||
|
|
||||||
|
def show_context_menu(self, pos, img_path, key):
|
||||||
"""显示上下文菜单"""
|
"""显示上下文菜单"""
|
||||||
menu = QMenu(self)
|
menu = QMenu(self)
|
||||||
|
|
||||||
# 编辑描述动作
|
|
||||||
edit_action = QAction("编辑描述", self)
|
|
||||||
edit_action.triggered.connect(lambda: self.edit_description(img_path, description))
|
|
||||||
|
|
||||||
# 删除图片动作
|
# 删除图片动作
|
||||||
delete_action = QAction("删除图片", self)
|
delete_action = QAction("删除图片", self)
|
||||||
delete_action.triggered.connect(lambda: self.delete_image(img_path))
|
delete_action.triggered.connect(lambda: self.delete_image(key))
|
||||||
|
|
||||||
|
# 添加编辑描述动作 (仅当是缺陷图字典时显示)
|
||||||
|
edit_desc_action = None
|
||||||
|
if self.current_dict_type == "defect_picture_dict":
|
||||||
|
edit_desc_action = QAction("编辑缺陷描述", self)
|
||||||
|
edit_desc_action.triggered.connect(lambda: self.edit_defect_description(key, img_path))
|
||||||
|
menu.addAction(edit_desc_action)
|
||||||
|
|
||||||
menu.addAction(edit_action)
|
|
||||||
menu.addAction(delete_action)
|
menu.addAction(delete_action)
|
||||||
menu.exec_(QCursor.pos())
|
menu.exec_(QCursor.pos())
|
||||||
|
|
||||||
def edit_description(self, img_path, old_desc):
|
def edit_defect_description(self, key, img_path):
|
||||||
"""编辑图片描述"""
|
"""编辑缺陷图描述"""
|
||||||
new_desc, ok = QInputDialog.getText(
|
# 获取当前描述 (如果没有则使用默认格式)
|
||||||
self, "编辑描述", "请输入新的描述:",
|
current_desc = self.current_dict_data.get(key, "") if key in self.current_dict_data else ""
|
||||||
QLineEdit.Normal, old_desc
|
|
||||||
)
|
|
||||||
|
|
||||||
if ok and new_desc and new_desc != old_desc:
|
# 创建并显示编辑对话框
|
||||||
# 更新字典数据
|
editor = DefectDescriptionEditor(self, current_description=current_desc)
|
||||||
dict_data = self.dictionaries[self.current_dict_type][self.current_blade]
|
if editor.exec_() == QDialog.Accepted:
|
||||||
if img_path in dict_data.values():
|
# 获取新描述
|
||||||
# 找到对应的条目并更新
|
new_desc = editor.get_description()
|
||||||
for desc, path in dict_data.items():
|
|
||||||
if path == img_path:
|
|
||||||
dict_data.pop(desc)
|
|
||||||
dict_data[new_desc] = img_path
|
|
||||||
break
|
|
||||||
|
|
||||||
# 刷新视图并发射信号
|
# 更新字典数据 - 删除旧键并添加新键
|
||||||
self.refresh_dictionary_view()
|
if key in self.current_dict_data:
|
||||||
|
# 保留图片路径,只更新键名
|
||||||
|
img_path = self.current_dict_data.pop(key)
|
||||||
|
self.current_dict_data[new_desc] = img_path
|
||||||
|
|
||||||
|
# 刷新显示
|
||||||
|
self.refresh_view()
|
||||||
|
|
||||||
|
# 发射信号通知更新
|
||||||
self.dictionary_updated.emit(
|
self.dictionary_updated.emit(
|
||||||
|
self.original_folder_name,
|
||||||
|
self.current_part,
|
||||||
self.current_dict_type,
|
self.current_dict_type,
|
||||||
self.current_blade,
|
self.current_dict_data
|
||||||
dict_data
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def delete_image(self, img_path):
|
|
||||||
"""删除图片"""
|
def delete_image(self, key):
|
||||||
|
"""删除指定图片"""
|
||||||
reply = QMessageBox.question(
|
reply = QMessageBox.question(
|
||||||
self, "确认删除",
|
self, "确认删除",
|
||||||
"确定要从字典中删除这张图片吗?",
|
f"确定要从字典中删除Key为 {key} 的图片吗?",
|
||||||
QMessageBox.Yes | QMessageBox.No
|
QMessageBox.Yes | QMessageBox.No
|
||||||
)
|
)
|
||||||
|
|
||||||
if reply == QMessageBox.Yes:
|
if reply == QMessageBox.Yes:
|
||||||
# 从字典中删除
|
# 从字典中删除
|
||||||
dict_data = self.dictionaries[self.current_dict_type][self.current_blade]
|
self.current_dict_data.pop(key, None)
|
||||||
for desc, path in list(dict_data.items()):
|
|
||||||
if path == img_path:
|
|
||||||
dict_data.pop(desc)
|
|
||||||
break
|
|
||||||
|
|
||||||
# 刷新视图并发射信号
|
# 刷新显示
|
||||||
self.refresh_dictionary_view()
|
self.refresh_view()
|
||||||
|
|
||||||
|
# 发射信号通知更新
|
||||||
self.dictionary_updated.emit(
|
self.dictionary_updated.emit(
|
||||||
|
self.original_folder_name,
|
||||||
|
self.current_part,
|
||||||
self.current_dict_type,
|
self.current_dict_type,
|
||||||
self.current_blade,
|
self.current_dict_data
|
||||||
dict_data
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def add_image_to_dictionary(self, img_path):
|
class DefectDescriptionEditor(QDialog):
|
||||||
"""添加图片到当前字典"""
|
def __init__(self, parent=None, current_description="", img_path=""):
|
||||||
if not self.current_dict_type or not self.current_blade:
|
super().__init__(parent)
|
||||||
QMessageBox.warning(self, "警告", "请先选择叶片和字典类型")
|
self.setWindowTitle("编辑缺陷图描述")
|
||||||
return
|
self.setMinimumSize(1000, 700) # 增大窗口尺寸以适应两个编辑器
|
||||||
|
self.setStyleSheet(DEFECT_EDIT_WINDOW_STYLE)
|
||||||
|
|
||||||
# 获取或创建当前字典
|
self.current_description = current_description
|
||||||
if self.current_blade not in self.dictionaries[self.current_dict_type]:
|
self.img_path = img_path
|
||||||
self.dictionaries[self.current_dict_type][self.current_blade] = {}
|
self.init_ui()
|
||||||
|
|
||||||
dict_data = self.dictionaries[self.current_dict_type][self.current_blade]
|
def init_ui(self):
|
||||||
|
main_layout = QHBoxLayout(self)
|
||||||
|
main_layout.setContentsMargins(5, 5, 5, 5)
|
||||||
|
|
||||||
# 生成默认描述(序号)
|
# 左侧 - 图片编辑器 (使用DefectMarkEditor)
|
||||||
default_desc = f"图片_{len(dict_data) + 1}"
|
self.image_editor = DefectMarkEditor(self, self.img_path)
|
||||||
|
self.image_editor.setMinimumSize(600, 600)
|
||||||
|
main_layout.addWidget(self.image_editor, stretch=2)
|
||||||
|
|
||||||
# 添加到字典
|
# 右侧 - 描述编辑器
|
||||||
dict_data[default_desc] = img_path
|
self.init_description_editor()
|
||||||
|
main_layout.addWidget(self.desc_editor_widget, stretch=1)
|
||||||
|
|
||||||
# 刷新视图并发射信号
|
def init_description_editor(self):
|
||||||
self.refresh_dictionary_view()
|
self.desc_editor_widget = QWidget()
|
||||||
self.dictionary_updated.emit(
|
layout = QVBoxLayout(self.desc_editor_widget)
|
||||||
self.current_dict_type,
|
layout.setContentsMargins(5, 0, 0, 0)
|
||||||
self.current_blade,
|
|
||||||
dict_data
|
# 创建一个简单的 QTextEdit 作为消息浏览器
|
||||||
|
self.message_browser = QTextEdit()
|
||||||
|
self.message_browser.setReadOnly(True)
|
||||||
|
layout.addWidget(self.message_browser)
|
||||||
|
|
||||||
|
# 解析当前描述
|
||||||
|
parts = self.parse_current_description()
|
||||||
|
|
||||||
|
# 1. 缺陷类型
|
||||||
|
type_group = QGroupBox("缺陷类型")
|
||||||
|
type_group.setStyleSheet(GROUP_BOX_STYLE)
|
||||||
|
type_layout = QHBoxLayout(type_group)
|
||||||
|
|
||||||
|
# 使用 SmartDropdown 作为缺陷类型选择器
|
||||||
|
self.defect_type_dropdown = SmartDropdown(
|
||||||
|
dropdown_type="缺陷类型",
|
||||||
|
messagebrowser=self.message_browser,
|
||||||
|
parent=self
|
||||||
)
|
)
|
||||||
|
if "type" in parts:
|
||||||
|
self.defect_type_dropdown.combo_box.setCurrentText(parts["type"])
|
||||||
|
type_layout.addWidget(self.defect_type_dropdown)
|
||||||
|
|
||||||
def set_dictionary_data(self, dict_type, blade_name, data):
|
layout.addWidget(type_group)
|
||||||
"""设置字典数据"""
|
|
||||||
if dict_type in self.dictionaries:
|
|
||||||
if data:
|
|
||||||
self.dictionaries[dict_type][blade_name] = data
|
|
||||||
elif blade_name in self.dictionaries[dict_type]:
|
|
||||||
del self.dictionaries[dict_type][blade_name]
|
|
||||||
|
|
||||||
# 如果当前显示的是这个字典,则刷新视图
|
# 2. 缺陷位置
|
||||||
if self.current_dict_type == dict_type and self.current_blade == blade_name:
|
location_group = QGroupBox("缺陷位置")
|
||||||
self.refresh_dictionary_view()
|
location_group.setStyleSheet(GROUP_BOX_STYLE)
|
||||||
|
location_layout = QVBoxLayout(location_group)
|
||||||
|
|
||||||
def resizeEvent(self, event):
|
self.location_edit = QLineEdit()
|
||||||
"""窗口大小改变时重新计算布局"""
|
self.location_edit.setStyleSheet(LINE_EDIT_STYLE)
|
||||||
super().resizeEvent(event)
|
self.location_edit.setText(parts.get("location", ""))
|
||||||
if hasattr(self, 'current_dict_type') and hasattr(self, 'current_blade'):
|
location_layout.addWidget(self.location_edit)
|
||||||
QTimer.singleShot(100, self.refresh_dictionary_view)
|
|
||||||
|
layout.addWidget(location_group)
|
||||||
|
|
||||||
|
# 3. 缺陷尺寸
|
||||||
|
size_group = QGroupBox("缺陷尺寸")
|
||||||
|
size_group.setStyleSheet(GROUP_BOX_STYLE)
|
||||||
|
size_layout = QVBoxLayout(size_group)
|
||||||
|
|
||||||
|
# 轴向尺寸
|
||||||
|
axial_container = QWidget()
|
||||||
|
axial_container.setStyleSheet(DIMENSION_CONTAINER_STYLE)
|
||||||
|
axial_layout = QHBoxLayout(axial_container)
|
||||||
|
axial_layout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
|
||||||
|
axial_label = QLabel("轴向尺寸:")
|
||||||
|
axial_label.setStyleSheet(DIMENSION_LABEL_STYLE)
|
||||||
|
axial_layout.addWidget(axial_label)
|
||||||
|
|
||||||
|
self.axial_edit = QLineEdit()
|
||||||
|
self.axial_edit.setStyleSheet(DIMENSION_EDIT_STYLE)
|
||||||
|
self.axial_edit.setText(parts.get("axial_size", ""))
|
||||||
|
axial_layout.addWidget(self.axial_edit)
|
||||||
|
|
||||||
|
# 添加单位标签
|
||||||
|
axial_unit = QLabel("mm")
|
||||||
|
axial_unit.setStyleSheet(LABEL_STYLE)
|
||||||
|
axial_layout.addWidget(axial_unit)
|
||||||
|
|
||||||
|
axial_layout.addStretch()
|
||||||
|
size_layout.addWidget(axial_container)
|
||||||
|
|
||||||
|
# 弦向尺寸
|
||||||
|
chord_container = QWidget()
|
||||||
|
chord_container.setStyleSheet(DIMENSION_CONTAINER_STYLE)
|
||||||
|
chord_layout = QHBoxLayout(chord_container)
|
||||||
|
chord_layout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
|
||||||
|
chord_label = QLabel("弦向尺寸:")
|
||||||
|
chord_label.setStyleSheet(DIMENSION_LABEL_STYLE)
|
||||||
|
chord_layout.addWidget(chord_label)
|
||||||
|
|
||||||
|
self.chord_edit = QLineEdit()
|
||||||
|
self.chord_edit.setStyleSheet(DIMENSION_EDIT_STYLE)
|
||||||
|
self.chord_edit.setText(parts.get("chord_size", ""))
|
||||||
|
chord_layout.addWidget(self.chord_edit)
|
||||||
|
|
||||||
|
# 添加单位标签
|
||||||
|
chord_unit = QLabel("mm")
|
||||||
|
chord_unit.setStyleSheet(LABEL_STYLE)
|
||||||
|
chord_layout.addWidget(chord_unit)
|
||||||
|
|
||||||
|
chord_layout.addStretch()
|
||||||
|
size_layout.addWidget(chord_container)
|
||||||
|
|
||||||
|
layout.addWidget(size_group)
|
||||||
|
|
||||||
|
# 4. 其他属性
|
||||||
|
attr_group = QGroupBox("其他属性")
|
||||||
|
attr_group.setStyleSheet(GROUP_BOX_STYLE)
|
||||||
|
attr_layout = QGridLayout(attr_group)
|
||||||
|
|
||||||
|
# 可见程度
|
||||||
|
visibility_label = QLabel("可见程度:")
|
||||||
|
visibility_label.setStyleSheet(LABEL_STYLE)
|
||||||
|
self.visibility_combo = QComboBox()
|
||||||
|
self.visibility_combo.setStyleSheet(COMBO_BOX_STYLE)
|
||||||
|
self.visibility_combo.addItems(["轻微", "一般", "重要", "严重"])
|
||||||
|
if "visibility" in parts:
|
||||||
|
self.visibility_combo.setCurrentText(parts["visibility"])
|
||||||
|
attr_layout.addWidget(visibility_label, 0, 0)
|
||||||
|
attr_layout.addWidget(self.visibility_combo, 0, 1)
|
||||||
|
|
||||||
|
# 紧急程度
|
||||||
|
urgency_label = QLabel("紧急程度:")
|
||||||
|
urgency_label.setStyleSheet(LABEL_STYLE)
|
||||||
|
self.urgency_combo = QComboBox()
|
||||||
|
self.urgency_combo.setStyleSheet(COMBO_BOX_STYLE)
|
||||||
|
self.urgency_combo.addItems(["不紧急", "一般", "紧急", "非常紧急"])
|
||||||
|
if "urgency" in parts:
|
||||||
|
self.urgency_combo.setCurrentText(parts["urgency"])
|
||||||
|
attr_layout.addWidget(urgency_label, 1, 0)
|
||||||
|
attr_layout.addWidget(self.urgency_combo, 1, 1)
|
||||||
|
|
||||||
|
# 危重等级
|
||||||
|
severity_label = QLabel("危重等级:")
|
||||||
|
severity_label.setStyleSheet(LABEL_STYLE)
|
||||||
|
self.severity_combo = QComboBox()
|
||||||
|
self.severity_combo.setStyleSheet(COMBO_BOX_STYLE)
|
||||||
|
self.severity_combo.addItems(["轻微", "一般", "重要", "严重"])
|
||||||
|
if "severity" in parts:
|
||||||
|
self.severity_combo.setCurrentText(parts["severity"])
|
||||||
|
attr_layout.addWidget(severity_label, 2, 0)
|
||||||
|
attr_layout.addWidget(self.severity_combo, 2, 1)
|
||||||
|
|
||||||
|
layout.addWidget(attr_group)
|
||||||
|
|
||||||
|
# 5. 维修建议
|
||||||
|
suggestion_group = QGroupBox("维修建议")
|
||||||
|
suggestion_group.setStyleSheet(GROUP_BOX_STYLE)
|
||||||
|
suggestion_layout = QVBoxLayout(suggestion_group)
|
||||||
|
|
||||||
|
self.suggestion_edit = QLineEdit()
|
||||||
|
self.suggestion_edit.setStyleSheet(LINE_EDIT_STYLE)
|
||||||
|
self.suggestion_edit.setText(parts.get("suggestion", ""))
|
||||||
|
suggestion_layout.addWidget(self.suggestion_edit)
|
||||||
|
|
||||||
|
layout.addWidget(suggestion_group)
|
||||||
|
|
||||||
|
# 按钮
|
||||||
|
button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||||
|
button_box.setStyleSheet(PRIMARY_BUTTON_STYLE)
|
||||||
|
button_box.accepted.connect(self.accept)
|
||||||
|
button_box.rejected.connect(self.reject)
|
||||||
|
layout.addWidget(button_box)
|
||||||
|
|
||||||
|
def parse_current_description(self):
|
||||||
|
"""解析当前描述字符串"""
|
||||||
|
parts = {
|
||||||
|
"type": "",
|
||||||
|
"location": "",
|
||||||
|
"axial_size": "",
|
||||||
|
"chord_size": "",
|
||||||
|
"visibility": "一般",
|
||||||
|
"urgency": "一般",
|
||||||
|
"severity": "一般",
|
||||||
|
"suggestion": ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if not self.current_description:
|
||||||
|
return parts
|
||||||
|
|
||||||
|
# 尝试解析格式: 1_缺陷类型_缺陷位置_缺陷尺寸_可见程度_紧急程度_危重等级_维修建议
|
||||||
|
desc_parts = self.current_description.split("_")
|
||||||
|
if len(desc_parts) >= 8:
|
||||||
|
parts["type"] = desc_parts[1]
|
||||||
|
parts["location"] = desc_parts[2]
|
||||||
|
|
||||||
|
# 解析尺寸 (格式: 轴向XXmm弦向XXmm)
|
||||||
|
size_str = desc_parts[3]
|
||||||
|
axial_match = re.search(r"轴向(\d+)mm", size_str)
|
||||||
|
chord_match = re.search(r"弦向(\d+)mm", size_str)
|
||||||
|
if axial_match:
|
||||||
|
parts["axial_size"] = axial_match.group(1)
|
||||||
|
if chord_match:
|
||||||
|
parts["chord_size"] = chord_match.group(1)
|
||||||
|
|
||||||
|
if len(desc_parts) > 4:
|
||||||
|
parts["visibility"] = desc_parts[4]
|
||||||
|
if len(desc_parts) > 5:
|
||||||
|
parts["urgency"] = desc_parts[5]
|
||||||
|
if len(desc_parts) > 6:
|
||||||
|
parts["severity"] = desc_parts[6]
|
||||||
|
if len(desc_parts) > 7:
|
||||||
|
parts["suggestion"] = "_".join(desc_parts[7:])
|
||||||
|
|
||||||
|
return parts
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
"""生成描述字符串"""
|
||||||
|
# 生成序号 (使用当前时间戳的最后4位确保唯一性)
|
||||||
|
seq = str(int(time.time()) % 10000)
|
||||||
|
|
||||||
|
# 获取各字段值
|
||||||
|
defect_type = self.defect_type_dropdown.combo_box.currentText()
|
||||||
|
location = self.location_edit.text().strip()
|
||||||
|
axial_size = self.axial_edit.text().strip()
|
||||||
|
chord_size = self.chord_edit.text().strip()
|
||||||
|
visibility = self.visibility_combo.currentText()
|
||||||
|
urgency = self.urgency_combo.currentText()
|
||||||
|
severity = self.severity_combo.currentText()
|
||||||
|
suggestion = self.suggestion_edit.text().strip()
|
||||||
|
|
||||||
|
# 构建尺寸字符串
|
||||||
|
size_str = ""
|
||||||
|
if axial_size or chord_size:
|
||||||
|
size_parts = []
|
||||||
|
if axial_size:
|
||||||
|
size_parts.append(f"轴向{axial_size}mm")
|
||||||
|
if chord_size:
|
||||||
|
size_parts.append(f"弦向{chord_size}mm")
|
||||||
|
size_str = "".join(size_parts)
|
||||||
|
|
||||||
|
# 构建完整描述
|
||||||
|
description = f"{seq}_{defect_type}_{location}_{size_str}_{visibility}_{urgency}_{severity}_{suggestion}"
|
||||||
|
return description
|
|
@ -288,3 +288,46 @@ MESSAGE_BOX_BUTTON_STYLE = f"""
|
||||||
color: {LIGHT_TEXT_COLOR};
|
color: {LIGHT_TEXT_COLOR};
|
||||||
}}
|
}}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# ====================== 缺陷描述编辑相关样式 ======================
|
||||||
|
# 缺陷描述编辑窗口样式
|
||||||
|
DEFECT_EDIT_WINDOW_STYLE = f"""
|
||||||
|
QWidget {{
|
||||||
|
background-color: {LIGHT_COLOR};
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 输入框样式
|
||||||
|
LINE_EDIT_STYLE = f"""
|
||||||
|
QLineEdit {{
|
||||||
|
font-family: "{FONT_FAMILY}";
|
||||||
|
font-size: {CONTENT_FONT_SIZE}pt;
|
||||||
|
min-height: {COMBO_BOX_HEIGHT + 5}px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
border: 1px solid #bdc3c7;
|
||||||
|
border-radius: 4px;
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 尺寸输入框样式
|
||||||
|
DIMENSION_EDIT_STYLE = f"""
|
||||||
|
QLineEdit {{
|
||||||
|
{LINE_EDIT_STYLE}
|
||||||
|
min-width: 60px;
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 尺寸标签样式
|
||||||
|
DIMENSION_LABEL_STYLE = f"""
|
||||||
|
QLabel {{
|
||||||
|
{LABEL_STYLE}
|
||||||
|
min-width: 60px;
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 尺寸控件容器样式
|
||||||
|
DIMENSION_CONTAINER_STYLE = f"""
|
||||||
|
QWidget {{
|
||||||
|
background-color: transparent;
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
filelock==3.18.0
|
||||||
|
fsspec==2025.7.0
|
||||||
|
Jinja2==3.1.6
|
||||||
|
lxml==6.0.0
|
||||||
|
MarkupSafe==3.0.2
|
||||||
|
mpmath==1.3.0
|
||||||
|
networkx==3.4.2
|
||||||
|
numpy==2.2.6
|
||||||
|
nvidia-cublas-cu12==12.6.4.1
|
||||||
|
nvidia-cuda-cupti-cu12==12.6.80
|
||||||
|
nvidia-cuda-nvrtc-cu12==12.6.77
|
||||||
|
nvidia-cuda-runtime-cu12==12.6.77
|
||||||
|
nvidia-cudnn-cu12==9.5.1.17
|
||||||
|
nvidia-cufft-cu12==11.3.0.4
|
||||||
|
nvidia-cufile-cu12==1.11.1.6
|
||||||
|
nvidia-curand-cu12==10.3.7.77
|
||||||
|
nvidia-cusolver-cu12==11.7.1.2
|
||||||
|
nvidia-cusparse-cu12==12.5.4.2
|
||||||
|
nvidia-cusparselt-cu12==0.6.3
|
||||||
|
nvidia-nccl-cu12==2.26.2
|
||||||
|
nvidia-nvjitlink-cu12==12.6.85
|
||||||
|
nvidia-nvtx-cu12==12.6.77
|
||||||
|
opencv-python==4.12.0.88
|
||||||
|
piexif==1.1.3
|
||||||
|
pillow==11.3.0
|
||||||
|
PySide6==6.9.1
|
||||||
|
PySide6_Addons==6.9.1
|
||||||
|
PySide6_Essentials==6.9.1
|
||||||
|
python-docx==1.2.0
|
||||||
|
shiboken6==6.9.1
|
||||||
|
sympy==1.14.0
|
||||||
|
torch==2.7.1
|
||||||
|
torchaudio==2.7.1
|
||||||
|
torchvision==0.22.1
|
||||||
|
triton==3.3.1
|
||||||
|
typing_extensions==4.14.1
|
Loading…
Reference in New Issue