增加缺陷填写框,右侧图片列表能够接收图片放入并同步树模型和、json文件

This commit is contained in:
Voge1imkafig 2025-08-11 17:58:06 +08:00
parent 81d6711fac
commit cc460cfbcd
4 changed files with 997 additions and 215 deletions

374
info_core/Img_edit.py Normal file
View File

@ -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)

View File

@ -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

View File

@ -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;
}}
"""

36
requirements.txt Normal file
View File

@ -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