diff --git a/scripts/MetaFusion.py b/scripts/MetaFusion.py index a60fae0..f910b11 100644 --- a/scripts/MetaFusion.py +++ b/scripts/MetaFusion.py @@ -1,81 +1,128 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -import os -import sys -from maya import cmds +from PySide2 import QtCore, QtGui, QtWidgets import maya.OpenMayaUI as omui -from PySide2 import QtWidgets, QtCore from shiboken2 import wrapInstance - -from . import config -from .ui import menu, toolshelf -from .utils import maya_utils +from scripts import config +from scripts.ui import menu, toolshelf, meshes, rigging, behaviour, definition class MetaFusion(QtWidgets.QMainWindow): def __init__(self, parent=None): super(MetaFusion, self).__init__(parent) + self._setup_ui() + self._create_connections() + self._load_style() - # 设置窗口标题和基本属性 - self.setWindowTitle("MetaFusion") - self.setMinimumSize(1200, 800) + def _setup_ui(self): + """设置UI布局""" + # === 设置窗口属性 === + self.setWindowTitle(f"{config.TOOL_NAME} {config.TOOL_VERSION}") + # 设置初始大小和最小大小 + self.resize(550, 700) # 初始大小 + self.setMinimumSize(550, 700) # 最小大小限制 + + # === 创建中心部件 === + self.central_widget = QtWidgets.QWidget() + self.setCentralWidget(self.central_widget) - # 加载样式表 - style_file = os.path.join(os.path.dirname(__file__), "ui", "style.qss") - with open(style_file, "r", encoding="utf-8") as f: - self.setStyleSheet(f.read()) - - # 创建主窗口部件 - self.main_widget = QtWidgets.QWidget() - self.setCentralWidget(self.main_widget) + # === Main Layout === + self.main_layout = QtWidgets.QVBoxLayout(self.central_widget) + self.main_layout.setContentsMargins(0, 0, 0, 0) + self.main_layout.setSpacing(0) - # 创建主布局 - self.main_layout = QtWidgets.QVBoxLayout(self.main_widget) + # === 创建菜单栏 === + self.menu_bar = menu.MenuBar(self) + self.setMenuBar(self.menu_bar) - # 初始化UI组件 - self._init_ui() - - def _init_ui(self): - """初始化UI组件""" - # 创建工具架 + # === 创建工具架 === self.tool_shelf = toolshelf.ToolShelf(self) self.main_layout.addWidget(self.tool_shelf) - # 创建标签页 + # === 创建标签页 === self.tab_widget = QtWidgets.QTabWidget() self.main_layout.addWidget(self.tab_widget) # 添加各个功能页面 - self._add_meshes_tab() - self._add_rigging_tab() - self._add_behaviour_tab() - self._add_definition_tab() + self._create_tabs() - def _add_meshes_tab(self): - """添加模型页面""" - from .ui.meshes import MeshesTab - meshes_widget = MeshesTab() - self.tab_widget.addTab(meshes_widget, "模型") + # === 创建状态栏 === + self.status_bar = self._create_status_bar() + self.setStatusBar(self.status_bar) - def _add_rigging_tab(self): - """添加绑定页面""" - from .ui.rigging import RiggingTab - rigging_widget = RiggingTab() - self.tab_widget.addTab(rigging_widget, "绑定") + # 设置窗口位置居中 + self._center_window() + def _center_window(self): + """将窗口居中显示""" + # 获取主屏幕几何信息 + screen = QtWidgets.QApplication.primaryScreen().geometry() + # 计算窗口居中位置 + x = (screen.width() - self.width()) // 2 + y = (screen.height() - self.height()) // 2 + # 移动窗口 + self.move(x, y) - def _add_behaviour_tab(self): - """添加行为页面""" - from .ui.behaviour import BehaviourTab - behaviour_widget = BehaviourTab() - self.tab_widget.addTab(behaviour_widget, "行为") + def _create_tabs(self): + """创建标签页""" + # Meshes标签页 + self.meshes_tab = meshes.MeshesTab() + self.tab_widget.addTab(self.meshes_tab, "模型") - - def _add_definition_tab(self): - """添加定义页面""" - from .ui.definition import DefinitionTab - definition_widget = DefinitionTab() - self.tab_widget.addTab(definition_widget, "定义") + # Rigging标签页 + self.rigging_tab = rigging.RiggingTab() + self.tab_widget.addTab(self.rigging_tab, "绑定") + + # Behaviour标签页 + self.behaviour_tab = behaviour.BehaviourTab() + self.tab_widget.addTab(self.behaviour_tab, "行为") + + # Definition标签页 + self.definition_tab = definition.DefinitionTab() + self.tab_widget.addTab(self.definition_tab, "定义") + + def _create_status_bar(self): + """创建状态栏""" + # === Widget === + status_bar = QtWidgets.QStatusBar() + + # 添加版本信息 + version_label = QtWidgets.QLabel(f"{config.TOOL_VERSION}") + status_bar.addPermanentWidget(version_label) + + # 添加Maya版本信息 + maya_version_label = QtWidgets.QLabel(f"Maya {config.MAYA_VERSION}") + status_bar.addPermanentWidget(maya_version_label) + + # 添加Python版本信息 + python_version_label = QtWidgets.QLabel(f"Python {config.PYTHON_VERSION}") + status_bar.addPermanentWidget(python_version_label) + + return status_bar + + def _load_style(self): + """加载样式表""" + try: + with open(config.TOOL_STYLE_FILE, "r", encoding="utf-8") as f: + style = f.read() + self.setStyleSheet(style) + except Exception as e: + print(f"加载样式表失败: {str(e)}") + + def _create_connections(self): + """创建信号连接""" + # 标签页切换 + self.tab_widget.currentChanged.connect(self._on_tab_changed) + + def _on_tab_changed(self, index): + """标签页切换事件""" + tab_name = self.tab_widget.tabText(index) + self.status_bar.showMessage(f"当前页面: {tab_name}") + + def closeEvent(self, event): + """关闭窗口事件""" + # TODO: 实现关闭前的保存提示等逻辑 + event.accept() def show(): @@ -87,3 +134,7 @@ def show(): global meta_fusion_window meta_fusion_window = MetaFusion(parent=maya_main_window) meta_fusion_window.show() + + +if __name__ == "__main__": + show() diff --git a/scripts/ui/behaviour.py b/scripts/ui/behaviour.py index ee8e04a..7877665 100644 --- a/scripts/ui/behaviour.py +++ b/scripts/ui/behaviour.py @@ -32,7 +32,170 @@ except ImportError: wrapInstance = None class BehaviourTab(QtWidgets.QWidget): - pass + def __init__(self, parent=None): + super(BehaviourTab, self).__init__(parent) + self._setup_ui() + self._create_connections() + + def _setup_ui(self): + """设置UI布局""" + # 创建主布局 + self.main_layout = QtWidgets.QHBoxLayout(self) + self.main_layout.setContentsMargins(2, 2, 2, 2) + self.main_layout.setSpacing(2) + + # 创建左右两个主面板 + self.raw_control_widget = self._create_raw_control() + self.blend_shapes_widget = self._create_blend_shapes() + + self.main_layout.addWidget(self.raw_control_widget) + self.main_layout.addWidget(self.blend_shapes_widget) + + def _create_raw_control(self): + """创建Raw Control部分""" + # === Widget === + widget = QtWidgets.QWidget() + + # === Layout === + layout = QtWidgets.QVBoxLayout(widget) + layout.setContentsMargins(5, 5, 5, 5) + layout.setSpacing(5) + + # 标题 + title_layout = QtWidgets.QHBoxLayout() + title_label = QtWidgets.QLabel("Raw Control [814/814]") + title_layout.addWidget(title_label) + layout.addLayout(title_layout) + + # 搜索框 + self.search_edit = QtWidgets.QLineEdit() + self.search_edit.setPlaceholderText("搜索...") + layout.addWidget(self.search_edit) + + # BlendShape列表 + self.blend_list = QtWidgets.QListWidget() + layout.addWidget(self.blend_list) + + # 数值调节 + value_layout = QtWidgets.QHBoxLayout() + self.value_spin = QtWidgets.QDoubleSpinBox() + self.value_spin.setValue(0.010) + self.value_spin.setDecimals(3) + value_layout.addWidget(self.value_spin) + layout.addLayout(value_layout) + + # 过滤按钮组 + filter_layout = QtWidgets.QHBoxLayout() + self.filter_buttons = [] + for text in ["全部", "2", "3", "4", "5", "6"]: + btn = QtWidgets.QPushButton(text) + btn.setCheckable(True) + filter_layout.addWidget(btn) + self.filter_buttons.append(btn) + layout.addLayout(filter_layout) + + return widget + + def _create_blend_shapes(self): + """创建Related Blend Shapes部分""" + # === Widget === + widget = QtWidgets.QWidget() + + # === Layout === + layout = QtWidgets.QVBoxLayout(widget) + layout.setContentsMargins(5, 5, 5, 5) + layout.setSpacing(5) + + # 标题 + title_layout = QtWidgets.QHBoxLayout() + title_label = QtWidgets.QLabel("Related Blend Shapes [858/858]") + title_layout.addWidget(title_label) + layout.addLayout(title_layout) + + # BlendShape列表 + self.related_list = QtWidgets.QListWidget() + layout.addWidget(self.related_list) + + # 数值调节 + value_layout = QtWidgets.QHBoxLayout() + self.related_value_spin = QtWidgets.QDoubleSpinBox() + self.related_value_spin.setValue(0.000) + self.related_value_spin.setDecimals(3) + value_layout.addWidget(self.related_value_spin) + layout.addLayout(value_layout) + + # 操作按钮组 - 第一行 + op_layout1 = QtWidgets.QHBoxLayout() + op_buttons1 = [ + ("翻转目标", "flip.png"), + ("镜像目标", "mirror.png"), + ("查找翻转目标", "find_flip.png") + ] + for text, icon in op_buttons1: + btn = QtWidgets.QPushButton(text) + if icon: + btn.setIcon(QtGui.QIcon(f"{config.ICONS_PATH}/{icon}")) + op_layout1.addWidget(btn) + layout.addLayout(op_layout1) + + # 操作按钮组 - 第二行 + op_layout2 = QtWidgets.QHBoxLayout() + op_buttons2 = [ + ("添加混合目标", "add.png"), + ("删除混合目标", "delete.png"), + ("批量混合目标", "batch.png") + ] + for text, icon in op_buttons2: + btn = QtWidgets.QPushButton(text) + if icon: + btn.setIcon(QtGui.QIcon(f"{config.ICONS_PATH}/{icon}")) + op_layout2.addWidget(btn) + layout.addLayout(op_layout2) + + # 功能按钮组 + func_layout = QtWidgets.QHBoxLayout() + func_buttons = ["PSD", "BSE", "KEY", "MIR", "ARK", "CTR"] + for text in func_buttons: + btn = QtWidgets.QPushButton(text) + btn.setFixedWidth(40) + func_layout.addWidget(btn) + layout.addLayout(func_layout) + + return widget + + def _create_connections(self): + """创建信号连接""" + # 搜索框 + self.search_edit.textChanged.connect(self._filter_blend_shapes) + + # 数值调节 + self.value_spin.valueChanged.connect(self._update_raw_value) + self.related_value_spin.valueChanged.connect(self._update_related_value) + + # 过滤按钮组 + for btn in self.filter_buttons: + btn.clicked.connect(self._apply_filter) + + # ================================ 事件函数 ================================ + def _filter_blend_shapes(self, text): + """过滤BlendShape列表""" + # TODO: 实现过滤逻辑 + pass + + def _update_raw_value(self, value): + """更新Raw Control值""" + # TODO: 实现更新逻辑 + pass + + def _update_related_value(self, value): + """更新Related Blend Shapes值""" + # TODO: 实现更新逻辑 + pass + + def _apply_filter(self): + """应用过滤器""" + # TODO: 实现过滤逻辑 + pass if __name__ == "__main__": pass diff --git a/scripts/ui/definition.py b/scripts/ui/definition.py index 574422f..544bd60 100644 --- a/scripts/ui/definition.py +++ b/scripts/ui/definition.py @@ -32,7 +32,261 @@ except ImportError: wrapInstance = None class DefinitionTab(QtWidgets.QWidget): - pass + def __init__(self, parent=None): + super(DefinitionTab, self).__init__(parent) + self._setup_ui() + self._create_connections() + + def _setup_ui(self): + """设置UI布局""" + # === Main Layout === + self.main_layout = QtWidgets.QHBoxLayout(self) + self.main_layout.setContentsMargins(2, 2, 2, 2) + self.main_layout.setSpacing(2) + + # 创建左侧和右侧面板 + self.left_panel = self._create_left_panel() + self.right_panel = self._create_right_panel() + + self.main_layout.addWidget(self.left_panel, 1) # 1:3比例 + self.main_layout.addWidget(self.right_panel, 3) + + def _create_left_panel(self): + """创建左侧面板""" + # === Widget === + widget = QtWidgets.QWidget() + + # === Layout === + layout = QtWidgets.QVBoxLayout(widget) + layout.setContentsMargins(5, 5, 5, 5) + layout.setSpacing(5) + + # LODs列表 + lod_group = QtWidgets.QGroupBox("LODs") + lod_layout = QtWidgets.QVBoxLayout() + self.lod_list = QtWidgets.QListWidget() + for i in range(8): + self.lod_list.addItem(f"LOD {i}") + lod_layout.addWidget(self.lod_list) + + # Define Joint按钮 + self.define_joint_btn = QtWidgets.QPushButton("Define Joint For LOD") + lod_layout.addWidget(self.define_joint_btn) + + lod_group.setLayout(lod_layout) + layout.addWidget(lod_group) + + return widget + + def _create_right_panel(self): + """创建右侧面板""" + # === Widget === + widget = QtWidgets.QWidget() + + # === Layout === + layout = QtWidgets.QVBoxLayout(widget) + layout.setContentsMargins(5, 5, 5, 5) + layout.setSpacing(5) + + # 创建各个部分 + self.meshes_widget = self._create_meshes_section() + self.joints_widget = self._create_joints_section() + self.blend_shapes_widget = self._create_blend_shapes_section() + self.animated_map_widget = self._create_animated_map_section() + + # 添加到布局 + layout.addWidget(self.meshes_widget) + layout.addWidget(self.joints_widget) + layout.addWidget(self.blend_shapes_widget) + layout.addWidget(self.animated_map_widget) + + # 添加操作按钮区域 + self.operation_widget = self._create_operation_section() + layout.addWidget(self.operation_widget) + + return widget + + def _create_meshes_section(self): + """创建Meshes部分""" + # === Widget === + group = QtWidgets.QGroupBox("Meshes [010/54]") + + # === Layout === + layout = QtWidgets.QVBoxLayout(group) + + # 列表 + self.meshes_list = QtWidgets.QListWidget() + meshes = [ + "head_lod0_mesh (000)", + "teeth_lod0_mesh (001)", + "saliva_lod0_mesh (002)", + "eyeLeft_lod0_mesh (003)", + "eyeRight_lod0_mesh (004)", + "eyeshell_lod0_mesh (005)", + "eyelashes_lod0_mesh (006)", + "eyeEdge_lod0_mesh (007)" + ] + for mesh in meshes: + self.meshes_list.addItem(mesh) + + layout.addWidget(self.meshes_list) + + # Create Geometry按钮 + self.create_geo_btn = QtWidgets.QPushButton("Create Geometry") + layout.addWidget(self.create_geo_btn) + + return group + + def _create_joints_section(self): + """创建Joints部分""" + # === Widget === + group = QtWidgets.QGroupBox("Joints [840/870]") + + # === Layout === + layout = QtWidgets.QVBoxLayout(group) + + # 列表 + self.joints_list = QtWidgets.QListWidget() + joints = [ + "FACIAL_C_NeckB Group: 001", + "FACIAL_L_NeckB1 Group: 002", + "FACIAL_R_NeckB1 Group: 003", + "FACIAL_L_NeckB2 Group: 002", + "FACIAL_R_NeckB2 Group: 003", + "FACIAL_C12PV_NeckB1 Group: 001" + ] + for joint in joints: + self.joints_list.addItem(joint) + + layout.addWidget(self.joints_list) + return group + + def _create_blend_shapes_section(self): + """创建Blend Shapes部分""" + # === Widget === + group = QtWidgets.QGroupBox("Blend Shapes [782/782]") + + # === Layout === + layout = QtWidgets.QVBoxLayout(group) + + # 列表 + self.blend_shapes_list = QtWidgets.QListWidget() + shapes = [ + "brow_down_L 000", + "brow_down_R 001", + "brow_lateral_L 002", + "brow_lateral_R 003", + "brow_raiseIn_L 004", + "brow_raiseIn_R 005" + ] + for shape in shapes: + self.blend_shapes_list.addItem(shape) + + layout.addWidget(self.blend_shapes_list) + return group + + def _create_animated_map_section(self): + """创建AnimatedMap部分""" + # === Widget === + group = QtWidgets.QGroupBox("AnimatedMap [082/82]") + + # === Layout === + layout = QtWidgets.QVBoxLayout(group) + + # 列表 + self.animated_map_list = QtWidgets.QListWidget() + maps = [ + "head_cm2_color.head_wm2.browsDown 000", + "head_cm2_color.head_wm2.browsDown 001", + "head_cm2_color.head_wm2.browsLate 002", + "head_cm2_color.head_wm2.browsLate 003", + "head_cm1_color.head_wm1.browsRais 004", + "head_cm1_color.head_wm1.browsRais 005" + ] + for map_item in maps: + self.animated_map_list.addItem(map_item) + + layout.addWidget(self.animated_map_list) + return group + + def _create_operation_section(self): + """创建操作区域""" + # === Widget === + widget = QtWidgets.QWidget() + + # === Layout === + layout = QtWidgets.QVBoxLayout(widget) + + # Write部分 + write_group = QtWidgets.QGroupBox("Write") + write_layout = QtWidgets.QVBoxLayout() + write_buttons = [ + "Write Neutral Pose Joint Position", + "Write Geometry", + "Write Skin Weights", + "Write Blend Shapes Targets" + ] + for text in write_buttons: + btn = QtWidgets.QPushButton(text) + write_layout.addWidget(btn) + write_group.setLayout(write_layout) + layout.addWidget(write_group) + + # Create部分 + create_group = QtWidgets.QGroupBox("Create") + create_layout = QtWidgets.QVBoxLayout() + create_buttons = [ + "Create Blend Shapes For Mesh", + "Bind Skin For Mesh", + "Unbind Skin For Mesh" + ] + for text in create_buttons: + btn = QtWidgets.QPushButton(text) + create_layout.addWidget(btn) + create_group.setLayout(create_layout) + layout.addWidget(create_group) + + # Tools部分 + tools_group = QtWidgets.QGroupBox("Tools") + tools_layout = QtWidgets.QVBoxLayout() + tools_buttons = [ + "New Neutral Joint Transform", + "Quickly Create Presets" + ] + for text in tools_buttons: + btn = QtWidgets.QPushButton(text) + tools_layout.addWidget(btn) + tools_group.setLayout(tools_layout) + layout.addWidget(tools_group) + + return widget + + def _create_connections(self): + """创建信号连接""" + # LOD列表选择 + self.lod_list.currentItemChanged.connect(self._on_lod_changed) + + # Define Joint按钮 + self.define_joint_btn.clicked.connect(self._on_define_joint) + + # Create Geometry按钮 + self.create_geo_btn.clicked.connect(self._on_create_geometry) + + # ================================ 事件函数 ================================ + def _on_lod_changed(self, current, previous): + """LOD选择改变""" + # TODO: 实现LOD切换逻辑 + pass + + def _on_define_joint(self): + """Define Joint按钮点击""" + # TODO: 实现Define Joint逻辑 + pass + + def _on_create_geometry(self): + """Create Geometry按钮点击""" + # TODO: 实现Create Geometry逻辑 + pass if __name__ == "__main__": pass \ No newline at end of file diff --git a/scripts/ui/menu.py b/scripts/ui/menu.py index e64cd53..8d41826 100644 --- a/scripts/ui/menu.py +++ b/scripts/ui/menu.py @@ -31,9 +31,233 @@ except ImportError: QtCore = QtGui = QtWidgets = None wrapInstance = None -class Menu(QtWidgets.QWidget): - pass +class MenuBar(QtWidgets.QMenuBar): + def __init__(self, parent=None): + super(MenuBar, self).__init__(parent) + self._setup_ui() + self._create_connections() + + def _setup_ui(self): + """设置UI布局""" + # 创建主菜单 + self.file_menu = self._create_file_menu() + self.edit_menu = self._create_edit_menu() + self.tools_menu = self._create_tools_menu() + self.language_menu = self._create_language_menu() + self.help_menu = self._create_help_menu() + + # 添加到菜单栏 + self.addMenu(self.file_menu) + self.addMenu(self.edit_menu) + self.addMenu(self.tools_menu) + self.addMenu(self.language_menu) + self.addMenu(self.help_menu) + + def _create_file_menu(self): + """创建文件菜单""" + # === Widget === + menu = QtWidgets.QMenu("文件") + + # === Actions === + self.file_actions = {} + actions = [ + ("open_dna", "打开 DNA", "open.png", "Ctrl+O"), + ("save_dna", "保存 DNA", "save.png", "Ctrl+S"), + ("load_current", "加载当前项目 DNA", "open.png", ""), + None, # 分隔符 + ("rename_blend", "修改混合目标名称", "rename.png", ""), + ("reset_blend", "重置混合目标名称", "resetname.png", ""), + None, + ("export_fbx", "导出 FBX", "export.png", ""), + None, + ("exit", "退出", "exit.png", "Alt+F4") + ] + + self._add_menu_actions(menu, actions, self.file_actions) + return menu + + def _create_edit_menu(self): + """创建编辑菜单""" + # === Widget === + menu = QtWidgets.QMenu("编辑") + + # === Actions === + self.edit_actions = {} + actions = [ + ("create_rl4", "创建 RL4 节点", "connect.png", ""), + ("delete_rl4", "删除 RL4 节点", "disconnect.png", ""), + None, + ("mirror_l2r", "镜像左至右", "mirrorL.png", ""), + ("mirror_r2l", "镜像右至左", "mirrorR.png", ""), + None, + ("pose_a2t", "姿势由 A 型转 T 型", "pose_A_To_T.png", ""), + ("pose_t2a", "姿势由 T 型转 A 型", "pose_T_To_A.png", ""), + None, + ("transfer_lod", "传输 LOD 贴图", "locator.png", ""), + ("set_joint_color", "设置关节颜色", "color.png", ""), + None, + ("unmark_all", "取消全部标记", "unmark_all.png", ""), + ("rebuild_targets", "重建所有目标", "rebuildTargets.png", ""), + None, + ("set_expression_keys", "为所有表情设置关键帧", "bakeAnimation.png", ""), + ("bake_expression_keys", "烘焙所有表情的关键帧", "centerCurrentTime.png", "") + ] + + self._add_menu_actions(menu, actions, self.edit_actions) + return menu + + def _create_tools_menu(self): + """创建工具菜单""" + # === Widget === + menu = QtWidgets.QMenu("工具") + + # === Actions === + self.tools_actions = {} + actions = [ + ("export_skin", "导出蒙皮", "export_skin.png", ""), + ("import_skin", "导入蒙皮", "import_skin.png", ""), + ("copy_skin", "拷贝装皮", "copy_skin.png", ""), + None, + ("rbf_deformer", "RBF变形器", "blendShape.png", ""), + ("quick_bind", "快速绑定服装", "clothing_weight.png", ""), + ("clone_blend", "克隆混合变形", "blendShape.png", ""), + None, + ("transfer_uv", "UV传递点序", "repair_vertex_order.png", ""), + ("create_face_ctrl", "面部生成控制器", "controller.png", ""), + ("extract_52bs", "提取52BS", "ARKit52.png", ""), + None, + ("fix_joint_axis", "关节轴向修复", "joint.png", ""), + ("create_body_ctrl", "生成身体控制器", "create_body_ctrl.png", ""), + None, + ("import_face_anim", "导入面部动画", "import_face_anim.png", ""), + ("import_body_anim", "导入身体动画", "import_body_anim.png", "") + ] + + self._add_menu_actions(menu, actions, self.tools_actions) + return menu + + def _create_language_menu(self): + """创建语言菜单""" + # === Widget === + menu = QtWidgets.QMenu("语言") + + # === Actions === + self.language_actions = {} + actions = [ + ("chinese", "中文", "chinese.png", ""), + ("english", "English", "english.png", "") + ] + + self._add_menu_actions(menu, actions, self.language_actions) + return menu + + def _create_help_menu(self): + """创建帮助菜单""" + # === Widget === + menu = QtWidgets.QMenu("帮助") + + # === Actions === + self.help_actions = {} + actions = [ + ("check_update", "检查更新", "update.png", ""), + ("help", "帮助文档", "help.png", "F1"), + ("about", "关于", "about.png", "") + ] + + self._add_menu_actions(menu, actions, self.help_actions) + return menu + + def _add_menu_actions(self, menu, actions, action_dict): + """添加菜单项""" + for action in actions: + if action is None: + menu.addSeparator() + else: + action_id, name, icon, shortcut = action + act = QtWidgets.QAction(name, self) + + if icon: + act.setIcon(QtGui.QIcon(f"{config.ICONS_PATH}/{icon}")) + + if shortcut: + act.setShortcut(shortcut) + + menu.addAction(act) + action_dict[action_id] = act + + def _create_connections(self): + """创建信号连接""" + # 文件菜单 + self.file_actions["open_dna"].triggered.connect(self._on_open_dna) + self.file_actions["save_dna"].triggered.connect(self._on_save_dna) + self.file_actions["exit"].triggered.connect(self._on_exit) + + # 编辑菜单 + self.edit_actions["create_rl4"].triggered.connect(self._on_create_rl4) + self.edit_actions["delete_rl4"].triggered.connect(self._on_delete_rl4) + + # 工具菜单 + self.tools_actions["export_skin"].triggered.connect(self._on_export_skin) + self.tools_actions["import_skin"].triggered.connect(self._on_import_skin) + + # 语言菜单 + self.language_actions["chinese"].triggered.connect(lambda: self._on_language_change("zh")) + self.language_actions["english"].triggered.connect(lambda: self._on_language_change("en")) + + # 帮助菜单 + self.help_actions["help"].triggered.connect(self._on_help) + self.help_actions["about"].triggered.connect(self._on_about) + # ================================ 事件函数 ================================ + def _on_open_dna(self): + """打开DNA""" + # TODO: 实现打开DNA逻辑 + pass + + def _on_save_dna(self): + """保存DNA""" + # TODO: 实现保存DNA逻辑 + pass + + def _on_exit(self): + """退出程序""" + # TODO: 实现退出逻辑 + pass + + def _on_create_rl4(self): + """创建RL4节点""" + # TODO: 实现创建RL4节点逻辑 + pass + + def _on_delete_rl4(self): + """删除RL4节点""" + # TODO: 实现删除RL4节点逻辑 + pass + + def _on_export_skin(self): + """导出蒙皮""" + # TODO: 实现导出蒙皮逻辑 + pass + + def _on_import_skin(self): + """导入蒙皮""" + # TODO: 实现导入蒙皮逻辑 + pass + + def _on_language_change(self, lang): + """切换语言""" + # TODO: 实现语言切换逻辑 + pass + + def _on_help(self): + """显示帮助文档""" + # TODO: 实现显示帮助逻辑 + pass + + def _on_about(self): + """显示关于对话框""" + # TODO: 实现显示关于对话框逻辑 + pass if __name__ == "__main__": pass \ No newline at end of file diff --git a/scripts/ui/meshes.py b/scripts/ui/meshes.py index 3b1cc41..c4fb06b 100644 --- a/scripts/ui/meshes.py +++ b/scripts/ui/meshes.py @@ -32,7 +32,221 @@ except ImportError: wrapInstance = None class MeshesTab(QtWidgets.QWidget): - pass + def __init__(self, parent=None): + super(MeshesTab, self).__init__(parent) + self._setup_ui() + self._create_connections() + + def _setup_ui(self): + """设置UI布局""" + # === Main Layout === + self.main_layout = QtWidgets.QVBoxLayout(self) + self.main_layout.setContentsMargins(2, 2, 2, 2) + self.main_layout.setSpacing(2) + + # 创建各个部件 + self.toolbar_widget = self._create_toolbar() + self.lod_tab_widget = self._create_lod_tabs() + self.bottom_toolbar_widget = self._create_bottom_toolbar() + + # 添加到主布局 + self.main_layout.addWidget(self.toolbar_widget) + self.main_layout.addWidget(self.lod_tab_widget) + self.main_layout.addWidget(self.bottom_toolbar_widget) + + def _create_toolbar(self): + """创建顶部工具栏""" + # === Widget === + widget = QtWidgets.QWidget() + + # === Layout === + layout = QtWidgets.QHBoxLayout(widget) + layout.setContentsMargins(2, 2, 2, 2) + layout.setSpacing(2) + + # 预设下拉框 + self.preset_combo = QtWidgets.QComboBox() + self.preset_combo.addItem("MetaHuman") + layout.addWidget(self.preset_combo) + + # 垃圾桶按钮 + self.trash_btn = QtWidgets.QPushButton() + self.trash_btn.setIcon(QtGui.QIcon(f"{config.ICONS_PATH}/trash.png")) + self.trash_btn.setFixedSize(24, 24) + layout.addWidget(self.trash_btn) + + layout.addStretch() + + return widget + + def _create_lod_tabs(self): + """创建LOD选项卡""" + # === Widget === + self.lod_tab = QtWidgets.QTabWidget() + + # 添加LOD页面 + for i in range(8): + lod_widget = self._create_lod_page(i) + self.lod_tab.addTab(lod_widget, f"LOD{i}") + + return self.lod_tab + + def _create_lod_page(self, lod_index): + """创建单个LOD页面""" + # === Widget === + widget = QtWidgets.QWidget() + + # === Layout === + layout = QtWidgets.QVBoxLayout(widget) + layout.setContentsMargins(5, 5, 5, 5) + layout.setSpacing(2) + + # 创建网格列表 + scroll_area = QtWidgets.QScrollArea() + scroll_area.setWidgetResizable(True) + scroll_widget = QtWidgets.QWidget() + scroll_layout = QtWidgets.QVBoxLayout(scroll_widget) + + # 获取当前LOD级别的网格列表 + meshes = config.LOD_MESHES[f"LOD{lod_index}"] + self.mesh_items = [] # 存储所有mesh item的引用 + + for mesh in meshes: + mesh_item = self._create_mesh_item(mesh) + scroll_layout.addWidget(mesh_item) + self.mesh_items.append(mesh_item) + + scroll_layout.addStretch() + scroll_area.setWidget(scroll_widget) + layout.addWidget(scroll_area) + + # 添加底部按钮 + button_widget = QtWidgets.QWidget() + button_layout = QtWidgets.QHBoxLayout(button_widget) + button_layout.setContentsMargins(0, 0, 0, 0) + + self.auto_select_btn = QtWidgets.QPushButton("自动选择模型") + self.standardize_btn = QtWidgets.QPushButton("标准化命名") + self.auto_group_btn = QtWidgets.QPushButton("自动分组") + + button_layout.addWidget(self.auto_select_btn) + button_layout.addWidget(self.standardize_btn) + button_layout.addWidget(self.auto_group_btn) + + layout.addWidget(button_widget) + + return widget + + def _create_mesh_item(self, mesh_name): + """创建单个网格项""" + # === Widget === + widget = QtWidgets.QWidget() + + # === Layout === + layout = QtWidgets.QHBoxLayout(widget) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(2) + + # 网格名称标签 + label = QtWidgets.QLabel(mesh_name) + label.setFixedWidth(80) + layout.addWidget(label) + + # 网格路径输入框 + path_edit = QtWidgets.QLineEdit() + layout.addWidget(path_edit) + + # 添加按钮 + add_btn = QtWidgets.QPushButton("添加...") + add_btn.setFixedWidth(60) + layout.addWidget(add_btn) + + return widget + + def _create_bottom_toolbar(self): + """创建底部工具栏""" + # === Widget === + widget = QtWidgets.QWidget() + + # === Layout === + layout = QtWidgets.QHBoxLayout(widget) + layout.setContentsMargins(5, 5, 5, 5) + layout.setSpacing(5) + + # 预设选择 + preset_label = QtWidgets.QLabel("预设:") + self.preset_combo_bottom = QtWidgets.QComboBox() + self.preset_combo_bottom.addItem("MetaHuman") + + # LOD选择 + lod_label = QtWidgets.QLabel("选择LOD:") + self.lod_combo = QtWidgets.QComboBox() + self.lod_combo.addItem("全部") + + # 功能按钮 + self.create_lod_btn = QtWidgets.QPushButton("生成当前LOD") + self.modify_btn = QtWidgets.QPushButton("修复线性") + self.save_btn = QtWidgets.QPushButton("修复存储") + + # 添加到布局 + layout.addWidget(preset_label) + layout.addWidget(self.preset_combo_bottom) + layout.addStretch() + layout.addWidget(lod_label) + layout.addWidget(self.lod_combo) + layout.addWidget(self.create_lod_btn) + layout.addWidget(self.modify_btn) + layout.addWidget(self.save_btn) + + return widget + + def _create_connections(self): + """创建信号连接""" + # 顶部工具栏 + self.trash_btn.clicked.connect(self._on_trash_clicked) + self.preset_combo.currentIndexChanged.connect(self._on_preset_changed) + + # LOD页面按钮 + for i in range(self.lod_tab.count()): + page = self.lod_tab.widget(i) + auto_select_btn = page.findChild(QtWidgets.QPushButton, "自动选择模型") + if auto_select_btn: + auto_select_btn.clicked.connect(self._on_auto_select) + + # 底部工具栏 + self.create_lod_btn.clicked.connect(self._on_create_lod) + self.modify_btn.clicked.connect(self._on_modify) + self.save_btn.clicked.connect(self._on_save) + + def _on_trash_clicked(self): + """垃圾桶按钮点击""" + # TODO: 实现清除功能 + pass + + def _on_preset_changed(self, index): + """预设改变""" + # TODO: 实现预设切换 + pass + + def _on_auto_select(self): + """自动选择模型""" + # TODO: 实现自动选择 + pass + + def _on_create_lod(self): + """生成当前LOD""" + # TODO: 实现LOD生成 + pass + + def _on_modify(self): + """修复线性""" + # TODO: 实现修复 + pass + + def _on_save(self): + """修复存储""" + # TODO: 实现存储 + pass if __name__ == "__main__": diff --git a/scripts/ui/rigging.py b/scripts/ui/rigging.py index 55251d5..eda9e16 100644 --- a/scripts/ui/rigging.py +++ b/scripts/ui/rigging.py @@ -32,8 +32,235 @@ except ImportError: wrapInstance = None class RiggingTab(QtWidgets.QWidget): - pass - + def __init__(self, parent=None): + super(RiggingTab, self).__init__(parent) + self._setup_ui() + self._create_connections() + + def _setup_ui(self): + """设置UI布局""" + # === Main Layout === + self.main_layout = QtWidgets.QHBoxLayout(self) + self.main_layout.setContentsMargins(2, 2, 2, 2) + self.main_layout.setSpacing(2) + + # 创建左右两个主面板 + self.dna_browser_widget = self._create_dna_browser() + self.asset_settings_widget = self._create_asset_settings() + + # 设置左右比例为1:1 + self.main_layout.addWidget(self.dna_browser_widget, 1) + self.main_layout.addWidget(self.asset_settings_widget, 1) + + def _create_dna_browser(self): + """创建DNA预设浏览器""" + # === Widget === + widget = QtWidgets.QWidget() + + # === Layout === + layout = QtWidgets.QVBoxLayout(widget) + layout.setContentsMargins(5, 5, 5, 5) + layout.setSpacing(5) + + # 预设网格视图 + self.preset_view = QtWidgets.QListWidget() + self.preset_view.setViewMode(QtWidgets.QListView.IconMode) + self.preset_view.setIconSize(QtCore.QSize(90, 90)) + self.preset_view.setSpacing(10) + self.preset_view.setResizeMode(QtWidgets.QListView.Adjust) + self.preset_view.setMovement(QtWidgets.QListView.Static) + self.preset_view.setWrapping(True) + layout.addWidget(self.preset_view) + + # 缩放控制 + zoom_widget = QtWidgets.QWidget() + zoom_layout = QtWidgets.QHBoxLayout(zoom_widget) + zoom_layout.setContentsMargins(0, 0, 0, 0) + + zoom_label = QtWidgets.QLabel("缩放:") + self.zoom_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal) + self.zoom_slider.setMinimum(60) + self.zoom_slider.setMaximum(200) + self.zoom_slider.setValue(90) + self.zoom_value_label = QtWidgets.QLabel("90") + + zoom_layout.addWidget(zoom_label) + zoom_layout.addWidget(self.zoom_slider) + zoom_layout.addWidget(self.zoom_value_label) + layout.addWidget(zoom_widget) + + # 导入/导出按钮 + button_widget = QtWidgets.QWidget() + button_layout = QtWidgets.QHBoxLayout(button_widget) + button_layout.setContentsMargins(0, 0, 0, 0) + + self.import_btn = QtWidgets.QPushButton("导入设置") + self.export_btn = QtWidgets.QPushButton("导出设置") + + button_layout.addWidget(self.import_btn) + button_layout.addWidget(self.export_btn) + layout.addWidget(button_widget) + + return widget + + def _create_asset_settings(self): + """创建资产设置面板""" + # === Widget === + widget = QtWidgets.QWidget() + + # === Layout === + layout = QtWidgets.QVBoxLayout(widget) + layout.setContentsMargins(5, 5, 5, 5) + layout.setSpacing(5) + + # 项目路径设置 + self.project_group = self._create_project_settings() + layout.addWidget(self.project_group) + + # 数据分层选项 + self.layer_group = self._create_layer_settings() + layout.addWidget(self.layer_group) + + # 描述设置 + self.desc_group = self._create_description_settings() + layout.addWidget(self.desc_group) + + layout.addStretch() + + return widget + + def _create_project_settings(self): + """创建项目设置组""" + # === Widget === + group = QtWidgets.QGroupBox("项目设置") + + # === Layout === + layout = QtWidgets.QFormLayout(group) + layout.setContentsMargins(5, 5, 5, 5) + layout.setSpacing(5) + + # 项目路径 + path_widget = QtWidgets.QWidget() + path_layout = QtWidgets.QHBoxLayout(path_widget) + path_layout.setContentsMargins(0, 0, 0, 0) + + self.project_path = QtWidgets.QLineEdit() + self.browse_project_btn = QtWidgets.QPushButton("浏览...") + self.browse_project_btn.setFixedWidth(60) + + path_layout.addWidget(self.project_path) + path_layout.addWidget(self.browse_project_btn) + layout.addRow("项目路径:", path_widget) + + # 预设文件 + preset_widget = QtWidgets.QWidget() + preset_layout = QtWidgets.QHBoxLayout(preset_widget) + preset_layout.setContentsMargins(0, 0, 0, 0) + + self.preset_path = QtWidgets.QLineEdit() + self.browse_preset_btn = QtWidgets.QPushButton("浏览...") + self.browse_preset_btn.setFixedWidth(60) + + preset_layout.addWidget(self.preset_path) + preset_layout.addWidget(self.browse_preset_btn) + layout.addRow("预设文件:", preset_widget) + + return group + + def _create_layer_settings(self): + """创建数据分层设置组""" + # === Widget === + group = QtWidgets.QGroupBox("数据分层") + + # === Layout === + layout = QtWidgets.QVBoxLayout(group) + layout.setContentsMargins(5, 5, 5, 5) + layout.setSpacing(5) + + self.behavior_check = QtWidgets.QCheckBox("行为(包括描述和定义)") + self.expression_check = QtWidgets.QCheckBox("覆盖表情选项") + + layout.addWidget(self.behavior_check) + layout.addWidget(self.expression_check) + + return group + + def _create_description_settings(self): + """创建描述设置组""" + # === Widget === + group = QtWidgets.QGroupBox("描述设置") + + # === Layout === + layout = QtWidgets.QFormLayout(group) + layout.setContentsMargins(5, 5, 5, 5) + layout.setSpacing(5) + + # 创建设置项 + self.name_edit = QtWidgets.QLineEdit("Archetype") + self.type_combo = QtWidgets.QComboBox() + self.type_combo.addItem("其他") + self.gender_combo = QtWidgets.QComboBox() + self.gender_combo.addItem("女性") + self.age_spin = QtWidgets.QSpinBox() + self.age_spin.setValue(24) + self.transform_combo = QtWidgets.QComboBox() + self.transform_combo.addItem("厘米") + self.rotation_combo = QtWidgets.QComboBox() + self.rotation_combo.addItem("角度") + self.up_axis_combo = QtWidgets.QComboBox() + self.up_axis_combo.addItem("Z轴向上") + self.lod_spin = QtWidgets.QSpinBox() + self.lod_spin.setValue(8) + + # 添加到布局 + layout.addRow("名称:", self.name_edit) + layout.addRow("原型:", self.type_combo) + layout.addRow("性别:", self.gender_combo) + layout.addRow("年龄:", self.age_spin) + layout.addRow("变换单位:", self.transform_combo) + layout.addRow("旋转单位:", self.rotation_combo) + layout.addRow("向上轴标:", self.up_axis_combo) + layout.addRow("LOD计数:", self.lod_spin) + + return group + + def _create_connections(self): + """创建信号连接""" + # 缩放滑块 + self.zoom_slider.valueChanged.connect(self._on_zoom_changed) + + # 导入导出按钮 + self.import_btn.clicked.connect(self._on_import) + self.export_btn.clicked.connect(self._on_export) + + # 浏览按钮 + self.browse_project_btn.clicked.connect(self._on_browse_project) + self.browse_preset_btn.clicked.connect(self._on_browse_preset) + + def _on_zoom_changed(self, value): + """缩放值改变""" + self.zoom_value_label.setText(str(value)) + self.preset_view.setIconSize(QtCore.QSize(value, value)) + + def _on_import(self): + """导入设置""" + # TODO: 实现导入逻辑 + pass + + def _on_export(self): + """导出设置""" + # TODO: 实现导出逻辑 + pass + + def _on_browse_project(self): + """浏览项目路径""" + # TODO: 实现浏览逻辑 + pass + + def _on_browse_preset(self): + """浏览预设文件""" + # TODO: 实现浏览逻辑 + pass if __name__ == "__main__": pass \ No newline at end of file diff --git a/scripts/ui/toolshelf.py b/scripts/ui/toolshelf.py new file mode 100644 index 0000000..f73e417 --- /dev/null +++ b/scripts/ui/toolshelf.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from PySide2 import QtCore, QtGui, QtWidgets +from scripts import config + +class ToolShelf(QtWidgets.QWidget): + def __init__(self, parent=None): + super(ToolShelf, self).__init__(parent) + self._setup_ui() + self._create_connections() + + def _setup_ui(self): + """设置UI布局""" + # === Main Layout === + self.main_layout = QtWidgets.QHBoxLayout(self) + self.main_layout.setContentsMargins(2, 2, 2, 2) + self.main_layout.setSpacing(4) + + # 创建工具按钮 + self.tool_buttons = {} + self._create_tool_buttons() + + def _create_tool_buttons(self): + """创建工具按钮""" + # === 工具按钮配置 === + tools = [ + ("save_dna", "保存 DNA", "save.png"), + ("load_dna", "加载当前项目 DNA", "open.png"), + None, # 分隔符 + ("create_rl4", "创建RL4节点", "connect.png"), + ("delete_rl4", "删除RL4节点", "disconnect.png"), + None, + ("export_skin", "导出蒙皮", "export_skin.png"), + ("import_skin", "导入蒙皮", "import_skin.png"), + ("copy_skin", "复制蒙皮", "copy_skin.png"), + None, + ("help", "帮助文档", "help.png"), + ("about", "关于", "about.png") + ] + + # === 创建按钮 === + for tool in tools: + if tool is None: + # 添加分隔符 + separator = QtWidgets.QFrame() + separator.setFrameShape(QtWidgets.QFrame.VLine) + separator.setFrameShadow(QtWidgets.QFrame.Sunken) + self.main_layout.addWidget(separator) + else: + tool_id, name, icon = tool + button = self._create_tool_button(name, icon) + self.tool_buttons[tool_id] = button + self.main_layout.addWidget(button) + + # 添加弹性空间 + self.main_layout.addStretch() + + def _create_tool_button(self, name, icon): + """创建单个工具按钮""" + # === Widget === + button = QtWidgets.QToolButton() + + # === 设置按钮属性 === + button.setIcon(QtGui.QIcon(f"{config.ICONS_PATH}/{icon}")) + button.setIconSize(QtCore.QSize(24, 24)) + button.setToolTip(name) + button.setStatusTip(name) + + # 设置按钮样式 + button.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly) + button.setAutoRaise(True) # 鼠标悬停时才显示边框 + + return button + + def _create_connections(self): + """创建信号连接""" + # 保存/加载DNA + self.tool_buttons["save_dna"].clicked.connect(self._on_save_dna) + self.tool_buttons["load_dna"].clicked.connect(self._on_load_dna) + + # RL4节点 + self.tool_buttons["create_rl4"].clicked.connect(self._on_create_rl4) + self.tool_buttons["delete_rl4"].clicked.connect(self._on_delete_rl4) + + # 蒙皮操作 + self.tool_buttons["export_skin"].clicked.connect(self._on_export_skin) + self.tool_buttons["import_skin"].clicked.connect(self._on_import_skin) + self.tool_buttons["copy_skin"].clicked.connect(self._on_copy_skin) + + # 帮助/关于 + self.tool_buttons["help"].clicked.connect(self._on_help) + self.tool_buttons["about"].clicked.connect(self._on_about) + + # ================================ 事件函数 ================================ + def _on_save_dna(self): + """保存DNA""" + # TODO: 实现保存DNA逻辑 + pass + + def _on_load_dna(self): + """加载DNA""" + # TODO: 实现加载DNA逻辑 + pass + + def _on_create_rl4(self): + """创建RL4节点""" + # TODO: 实现创建RL4节点逻辑 + pass + + def _on_delete_rl4(self): + """删除RL4节点""" + # TODO: 实现删除RL4节点逻辑 + pass + + def _on_export_skin(self): + """导出蒙皮""" + # TODO: 实现导出蒙皮逻辑 + pass + + def _on_import_skin(self): + """导入蒙皮""" + # TODO: 实现导入蒙皮逻辑 + pass + + def _on_copy_skin(self): + """复制蒙皮""" + # TODO: 实现复制蒙皮逻辑 + pass + + def _on_help(self): + """显示帮助文档""" + # TODO: 实现显示帮助逻辑 + pass + + def _on_about(self): + """显示关于对话框""" + # TODO: 实现显示关于对话框逻辑 + pass diff --git a/scripts/ui/widgets.py b/scripts/ui/widgets.py index f77961b..3eabb7a 100644 --- a/scripts/ui/widgets.py +++ b/scripts/ui/widgets.py @@ -9,3 +9,209 @@ import maya.mel as mel import webbrowser import sys import os +from PySide2 import QtCore, QtGui, QtWidgets + +class ToolShelf(QtWidgets.QToolBar): + def __init__(self, parent=None): + super(ToolShelf, self).__init__(parent) + self.setIconSize(QtCore.QSize(24, 24)) + self.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly) + + # 添加工具按钮 + self._add_tools() + + def _add_tools(self): + """添加工具按钮""" + tools = [ + ("保存 DNA", "save.png"), + ("加载当前项目 DNA", "open.png"), + None, # 分隔符 + ("创建RL4节点", "connect.png"), + ("删除RL4节点", "disconnect.png"), + None, + ("导出蒙皮", "export_skin.png"), + ("导入蒙皮", "import_skin.png"), + ("复制蒙皮", "copy_skin.png"), + None, + ("帮助文档", "help.png"), + ("关于", "about.png") + ] + + for tool in tools: + if tool is None: + self.addSeparator() + else: + name, icon = tool + action = QtWidgets.QAction(QtGui.QIcon(f"{config.ICONS_PATH}/{icon}"), name, self) + self.addAction(action) + +class IconButton(QtWidgets.QPushButton): + """带图标的按钮""" + def __init__(self, icon_name, tooltip="", size=24, parent=None): + super(IconButton, self).__init__(parent) + self._setup_ui(icon_name, tooltip, size) + + def _setup_ui(self, icon_name, tooltip, size): + # 设置图标 + icon = QtGui.QIcon(f"{config.ICONS_PATH}/{icon_name}") + self.setIcon(icon) + self.setIconSize(QtCore.QSize(size, size)) + + # 设置样式 + self.setFixedSize(size, size) + self.setToolTip(tooltip) + self.setStyleSheet(""" + QPushButton { + border: none; + background: transparent; + } + QPushButton:hover { + background: rgba(255, 255, 255, 0.1); + } + QPushButton:pressed { + background: rgba(255, 255, 255, 0.2); + } + """) + +class SearchLineEdit(QtWidgets.QLineEdit): + """带搜索图标的输入框""" + def __init__(self, parent=None): + super(SearchLineEdit, self).__init__(parent) + self._setup_ui() + + def _setup_ui(self): + # 设置样式 + self.setPlaceholderText("搜索...") + + # 添加搜索图标 + search_action = QtWidgets.QAction(self) + search_action.setIcon(QtGui.QIcon(f"{config.ICONS_PATH}/search.png")) + self.addAction(search_action, QtWidgets.QLineEdit.LeadingPosition) + + # 添加清除按钮 + self.setClearButtonEnabled(True) + +class CollapsibleWidget(QtWidgets.QWidget): + """可折叠的控件组""" + def __init__(self, title="", parent=None): + super(CollapsibleWidget, self).__init__(parent) + self._setup_ui(title) + self._create_connections() + + def _setup_ui(self, title): + # === Main Layout === + self.main_layout = QtWidgets.QVBoxLayout(self) + self.main_layout.setContentsMargins(0, 0, 0, 0) + self.main_layout.setSpacing(0) + + # === Header === + self.header_widget = QtWidgets.QWidget() + header_layout = QtWidgets.QHBoxLayout(self.header_widget) + header_layout.setContentsMargins(5, 5, 5, 5) + + # 展开/折叠按钮 + self.toggle_btn = IconButton("arrow_down.png", size=16) + header_layout.addWidget(self.toggle_btn) + + # 标题 + title_label = QtWidgets.QLabel(title) + header_layout.addWidget(title_label) + header_layout.addStretch() + + self.main_layout.addWidget(self.header_widget) + + # === Content === + self.content_widget = QtWidgets.QWidget() + self.content_layout = QtWidgets.QVBoxLayout(self.content_widget) + self.content_layout.setContentsMargins(20, 5, 5, 5) + + self.main_layout.addWidget(self.content_widget) + + def _create_connections(self): + self.toggle_btn.clicked.connect(self._toggle_content) + + def _toggle_content(self): + """切换内容显示/隐藏""" + if self.content_widget.isVisible(): + self.content_widget.hide() + self.toggle_btn.setIcon(QtGui.QIcon(f"{config.ICONS_PATH}/arrow_right.png")) + else: + self.content_widget.show() + self.toggle_btn.setIcon(QtGui.QIcon(f"{config.ICONS_PATH}/arrow_down.png")) + + def add_widget(self, widget): + """添加控件到内容区域""" + self.content_layout.addWidget(widget) + +class LabeledSpinBox(QtWidgets.QWidget): + """带标签的数值输入框""" + valueChanged = QtCore.Signal(float) # 值改变信号 + + def __init__(self, label="", min_value=0.0, max_value=1.0, decimals=3, parent=None): + super(LabeledSpinBox, self).__init__(parent) + self._setup_ui(label, min_value, max_value, decimals) + self._create_connections() + + def _setup_ui(self, label, min_value, max_value, decimals): + # === Layout === + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + + # 标签 + self.label = QtWidgets.QLabel(label) + layout.addWidget(self.label) + + # 数值输入框 + self.spin_box = QtWidgets.QDoubleSpinBox() + self.spin_box.setMinimum(min_value) + self.spin_box.setMaximum(max_value) + self.spin_box.setDecimals(decimals) + layout.addWidget(self.spin_box) + + def _create_connections(self): + self.spin_box.valueChanged.connect(self.valueChanged.emit) + + def value(self): + return self.spin_box.value() + + def setValue(self, value): + self.spin_box.setValue(value) + +class StatusWidget(QtWidgets.QWidget): + """状态显示控件""" + def __init__(self, parent=None): + super(StatusWidget, self).__init__(parent) + self._setup_ui() + + def _setup_ui(self): + # === Layout === + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(5, 2, 5, 2) + + # 状态图标 + self.icon_label = QtWidgets.QLabel() + self.icon_label.setFixedSize(16, 16) + layout.addWidget(self.icon_label) + + # 状态文本 + self.text_label = QtWidgets.QLabel() + layout.addWidget(self.text_label) + + layout.addStretch() + + def set_status(self, status, message): + """设置状态 + status: 'normal', 'warning', 'error' + """ + icon_map = { + 'normal': 'status_normal.png', + 'warning': 'status_warning.png', + 'error': 'status_error.png' + } + + if status in icon_map: + self.icon_label.setPixmap( + QtGui.QPixmap(f"{config.ICONS_PATH}/{icon_map[status]}") + ) + + self.text_label.setText(message)