diff --git a/resources/img/Preview.png b/resources/img/Default.png similarity index 100% rename from resources/img/Preview.png rename to resources/img/Default.png diff --git a/scripts/MetaFusion.py b/scripts/MetaFusion.py index 9b1f1ad..a87532d 100644 --- a/scripts/MetaFusion.py +++ b/scripts/MetaFusion.py @@ -15,33 +15,39 @@ from scripts.config import data try: from PySide2 import QtCore, QtGui, QtWidgets from shiboken2 import wrapInstance - print("从PySide2加载Qt和shiboken2") except ImportError: try: from PySide6 import QtCore, QtGui, QtWidgets from shiboken6 import wrapInstance - print("从PySide6加载Qt和shiboken6") except ImportError: try: from PySide import QtCore, QtGui, QtWidgets from shiboken import wrapInstance - print("从PySide加载Qt和shiboken") except ImportError as e: - print(f"Qt加载失败: {str(e)}") QtCore = QtGui = QtWidgets = None wrapInstance = None # 导入UI模块 from scripts.ui.menu import MenuManager -from scripts.ui.models import ModelTab -from scripts.ui.rigging import RigTab +from scripts.ui.mesh import MeshTab +from scripts.ui.rigging import RiggingTab from scripts.ui.adjust import AdjustTab from scripts.ui.define import DefineTab +from scripts.ui.widgets import ModernTabWidget #===================================== 2. Main Window Class ===================================== class MainWindow(QtWidgets.QMainWindow): def __init__(self, parent=None): super(MainWindow, self).__init__(parent) + # 设置基础字体大小 + font = self.font() + font.setPointSize(9) # 设置基础字号 + self.setFont(font) + + # 设置窗口大小 + self.resize(700, 600) # 调整为更合适的尺寸 + self.setMinimumSize(700, 500) + self.setup_core_components() def setup_core_components(self): @@ -61,19 +67,41 @@ class MainWindow(QtWidgets.QMainWindow): def init_ui(self): """初始化UI框架""" self.setWindowTitle(f"{data.TOOL_NAME} {data.TOOL_VERSION}") - self.setMinimumSize(1200, 800) # 主窗口布局 main_widget = QtWidgets.QWidget() self.setCentralWidget(main_widget) main_layout = QtWidgets.QVBoxLayout(main_widget) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) # 创建菜单和工具栏 self.menu_manager = MenuManager(self) - self.create_tab_widget() - # 加载样式 - self.load_styles() + # 创建标签页 + self.tab_widget = ModernTabWidget() + main_layout.addWidget(self.tab_widget) + + # 初始化各标签页 + self.model_tab = MeshTab() + self.rig_tab = RiggingTab() + self.adjust_tab = AdjustTab() + self.define_tab = DefineTab() + + # 添加标签页 + self.tab_widget.addTab(self.model_tab, "模型") + self.tab_widget.addTab(self.rig_tab, "绑定") + self.tab_widget.addTab(self.adjust_tab, "调整") + self.tab_widget.addTab(self.define_tab, "定义") + + # 添加状态栏 + self.statusBar().showMessage("就绪") + self.statusBar().setStyleSheet(""" + QStatusBar { + background: #252525; + color: #e0e0e0; + } + """) def load_styles(self): """加载样式表""" @@ -81,23 +109,6 @@ class MainWindow(QtWidgets.QMainWindow): with open(data.TOOL_STYLE_FILE, "r", encoding="utf-8") as f: self.setStyleSheet(f.read()) - def create_tab_widget(self): - """创建标签页""" - self.tab_widget = QtWidgets.QTabWidget() - self.centralWidget().layout().addWidget(self.tab_widget) - - # 初始化各标签页 - self.model_tab = ModelTab() - self.rig_tab = RigTab() - self.adjust_tab = AdjustTab() - self.define_tab = DefineTab() - - # 添加标签页 - self.tab_widget.addTab(self.model_tab, "模型") - self.tab_widget.addTab(self.rig_tab, "绑定") - self.tab_widget.addTab(self.adjust_tab, "调整") - self.tab_widget.addTab(self.define_tab, "定义") - # ===================================== 显示主窗口 ===================================== def get_maya_window(): """获取Maya主窗口""" diff --git a/scripts/config/data.py b/scripts/config/data.py index 6c85c05..c22c7be 100644 --- a/scripts/config/data.py +++ b/scripts/config/data.py @@ -4,6 +4,7 @@ import os import sys import maya.cmds as cmds +import datetime # Base Information TOOL_NAME = str("MetaFusion") @@ -12,6 +13,7 @@ TOOL_AUTHOR = str("CGNICO") TOOL_LANG = str('en_US') TOOL_WSCL_NAME = str(f"{TOOL_NAME}WorkSpaceControl") TOOL_HELP_URL = str(f"https://gitea.cgnico.com/CGNICO/{TOOL_NAME}/wiki") +TOOL_YEAR = str(datetime.datetime.now().year) # BASE_PATH TOOL_PATH = str(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))).replace("\\", "/")) diff --git a/scripts/ui/__init__.py b/scripts/ui/__init__.py index 44b3172..dcc69bf 100644 --- a/scripts/ui/__init__.py +++ b/scripts/ui/__init__.py @@ -1,4 +1,26 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from . import * +import maya.cmds as cmds +import maya.mel as mel +import sys +import os + +from scripts.ui.widgets import * +from scripts.utils import * + + +try: + from PySide2 import QtCore, QtGui, QtWidgets + from shiboken2 import wrapInstance +except ImportError: + try: + from PySide6 import QtCore, QtGui, QtWidgets + from shiboken6 import wrapInstance + except ImportError: + try: + from PySide import QtCore, QtGui, QtWidgets + from shiboken import wrapInstance + except ImportError as e: + QtCore = QtGui = QtWidgets = None + wrapInstance = None diff --git a/scripts/ui/adjust.py b/scripts/ui/adjust.py index e28b81e..bd15d39 100644 --- a/scripts/ui/adjust.py +++ b/scripts/ui/adjust.py @@ -6,200 +6,203 @@ import maya.cmds as cmds import maya.mel as mel import sys import os - -from scripts.ui.widgets import (BaseWidget, BlendShapeList, BlendShapeControls, BlendShapeTools, IconButton, SliderWithValue) +from scripts.config import data +from scripts.ui import adjust_utils as adjust_utils +from scripts.ui import widgets as widgets try: from PySide2 import QtCore, QtGui, QtWidgets from shiboken2 import wrapInstance - print("从PySide2加载Qt和shiboken2") except ImportError: try: from PySide6 import QtCore, QtGui, QtWidgets from shiboken6 import wrapInstance - print("从PySide6加载Qt和shiboken6") except ImportError: try: from PySide import QtCore, QtGui, QtWidgets from shiboken import wrapInstance - print("从PySide加载Qt和shiboken") except ImportError as e: - print(f"Qt加载失败: {str(e)}") QtCore = QtGui = QtWidgets = None wrapInstance = None #===================================== 2. Adjust Tab Class ===================================== -class AdjustTab(BaseWidget): +class AdjustTab(widgets.BaseWidget): """调整标签页""" def __init__(self, parent=None): + self.raw_list = None + self.blend_list = None + self.raw_slider = None + self.blend_slider = None + self.raw_value_label = None + self.blend_value_label = None super(AdjustTab, self).__init__(parent) def setup_ui(self): + """初始化UI""" layout = QtWidgets.QVBoxLayout(self) + layout.setSpacing(self.SPACING) + layout.setContentsMargins(self.MARGINS, self.MARGINS, + self.MARGINS, self.MARGINS) - # 创建分割器 - splitter = QtWidgets.QSplitter(QtCore.Qt.Vertical) + # 创建左右分栏布局 + splitter = QtWidgets.QSplitter(QtCore.Qt.Horizontal) layout.addWidget(splitter) - # 上部分 - 主要BlendShape列表 - top_widget = QtWidgets.QWidget() - top_layout = QtWidgets.QVBoxLayout(top_widget) + # 左侧 Raw Control 列表 + left_widget = QtWidgets.QWidget() + left_layout = QtWidgets.QVBoxLayout(left_widget) - # RowControl BlendShape列表 - self.main_bs_list = BlendShapeList("RowControl") - top_layout.addWidget(self.main_bs_list) + # 标题和计数 + left_layout.addWidget(QtWidgets.QLabel("Raw Control [814/814]")) - # BlendShape控制 - self.bs_controls = BlendShapeControls() - top_layout.addWidget(self.bs_controls) + # 搜索框 + search_layout = QtWidgets.QHBoxLayout() + search_input = widgets.ModernLineEdit() + search_input.setPlaceholderText("搜索...") + search_layout.addWidget(search_input) + left_layout.addLayout(search_layout) - splitter.addWidget(top_widget) + # Raw Control列表 + self.raw_list = QtWidgets.QListWidget() + self.raw_list.setStyleSheet("QListWidget { background-color: #333333; }") + left_layout.addWidget(self.raw_list) - # 下部分 - 相关BlendShape和工具 - bottom_widget = QtWidgets.QWidget() - bottom_layout = QtWidgets.QVBoxLayout(bottom_widget) - - # Related BlendShape列表 - self.related_bs_list = BlendShapeList("Related Blend Shapes") - bottom_layout.addWidget(self.related_bs_list) - - # BlendShape工具 - self.bs_tools = BlendShapeTools() - bottom_layout.addWidget(self.bs_tools) - - # 表情控制工具栏 - expression_tools = self.create_expression_tools() - bottom_layout.addWidget(expression_tools) - - splitter.addWidget(bottom_widget) - - # 设置分割器比例 - splitter.setStretchFactor(0, 2) - splitter.setStretchFactor(1, 1) - - # 连接信号 - self.connect_signals() - - def create_expression_tools(self): - """创建表情控制工具栏""" - group = QtWidgets.QGroupBox("表情控制") - layout = QtWidgets.QVBoxLayout(group) - - # 功能开关 - toggle_layout = QtWidgets.QHBoxLayout() - - toggles = [ - ("PSD", "psd.png"), - ("BSE", "blendShape.png"), - ("KEY", "centerCurrentTime.png"), - ("MIR", "mirrorR.png"), - ("ARK", "ARKit52.png"), - ("CTR", "ctrl_hide.png") - ] - - for text, icon in toggles: - btn = IconButton(icon, text) - btn.setCheckable(True) - toggle_layout.addWidget(btn) - - layout.addLayout(toggle_layout) + # 添加示例项 + for i, name in enumerate(['browDownL', 'browDownR', 'browLateralL', 'browLateralR', + 'browRaiseInL', 'browRaiseInR', 'browRaiseOuterL', 'browRaiseOuterR', + 'earUpL', 'earUpR', 'eyeBlinkL']): + item = QtWidgets.QListWidgetItem() + item.setText(f"{name}") + item.setData(QtCore.Qt.UserRole, f"{i:03d}") # 存储编号 + self.raw_list.addItem(item) # 数值控制 value_layout = QtWidgets.QHBoxLayout() + self.raw_value_label = QtWidgets.QLabel("0.010") + value_layout.addWidget(self.raw_value_label) + self.raw_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal) + self.raw_slider.setRange(0, 100) + self.raw_slider.valueChanged.connect(self._on_raw_value_changed) + value_layout.addWidget(self.raw_slider) + value_layout.addWidget(QtWidgets.QLabel("全部")) + left_layout.addLayout(value_layout) - self.expr_slider = SliderWithValue(min_val=0.0, max_val=1.0, default=0.0) - value_layout.addWidget(self.expr_slider) + # 页码控制 + page_layout = QtWidgets.QHBoxLayout() + page_layout.addWidget(QtWidgets.QLabel("全部")) + for i in range(2, 7): + btn = QtWidgets.QPushButton(str(i)) + page_layout.addWidget(btn) + left_layout.addLayout(page_layout) - self.expr_all_check = QtWidgets.QCheckBox("全部") - value_layout.addWidget(self.expr_all_check) + # 源目标选择 + source_layout = QtWidgets.QHBoxLayout() + source_layout.addWidget(QtWidgets.QLabel("源目标")) + source_layout.addWidget(QtWidgets.QComboBox()) + left_layout.addLayout(source_layout) - layout.addLayout(value_layout) + splitter.addWidget(left_widget) - # 表情控制按钮 - expr_layout = QtWidgets.QHBoxLayout() + # 右侧 Related Blend Shapes 列表 + right_widget = QtWidgets.QWidget() + right_layout = QtWidgets.QVBoxLayout(right_widget) - expr_btns = [ - ("还原默认表情", "reset.png", self.reset_expression), - ("选择选择表情", "expressions_current.png", self.select_expression), - ("写入当前表情", "expression.png", self.write_expression), - ("控制面板查找", "controller.png", self.find_controller), - ("选择关联关节", "kinJoint.png", self.select_joints), - ("写入镜像表情", "ctrl_hide.png", self.write_mirror_expression) + # 标题和列表 + right_layout.addWidget(QtWidgets.QLabel("Related Blend Shapes [858/858]")) + self.blend_list = QtWidgets.QListWidget() + self.blend_list.setStyleSheet("QListWidget { background-color: #333333; }") + + # 添加示例项 + for i in range(10): + item = QtWidgets.QListWidgetItem() + item.setText(f"head_lod0_mesh | brow_down_L {i:03d}") + self.blend_list.addItem(item) + + right_layout.addWidget(self.blend_list) + + # 数值控制 + blend_value_layout = QtWidgets.QHBoxLayout() + self.blend_value_label = QtWidgets.QLabel("0.000") + blend_value_layout.addWidget(self.blend_value_label) + self.blend_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal) + self.blend_slider.setRange(0, 100) + self.blend_slider.valueChanged.connect(self._on_blend_value_changed) + blend_value_layout.addWidget(self.blend_slider) + blend_value_layout.addWidget(QtWidgets.QLabel("全部")) + right_layout.addLayout(blend_value_layout) + + # 工具按钮组 + tools_layout = QtWidgets.QGridLayout() + tools = [ + ("整理目标", adjust_utils.organize_targets), + ("高级混合变形", adjust_utils.advanced_blend), + ("重建混合目标", adjust_utils.rebuild_targets), + ("添加混合变形", adjust_utils.add_blend), + ("删除混合变形", adjust_utils.delete_blend), + ("批量混合变形", adjust_utils.batch_blend) ] - for text, icon, callback in expr_btns: - btn = IconButton(icon, text) - btn.clicked.connect(callback) - expr_layout.addWidget(btn) - - layout.addLayout(expr_layout) + for i, (text, callback) in enumerate(tools): + btn = QtWidgets.QPushButton(text) + btn.clicked.connect(lambda checked=False, cb=callback: cb()) + row = i // 2 + col = i % 2 + tools_layout.addWidget(btn, row, col) + right_layout.addLayout(tools_layout) - return group + # 底部工具栏 + bottom_layout = QtWidgets.QVBoxLayout() - def connect_signals(self): - """连接信号""" - # BlendShape列表选择变化 - self.main_bs_list.list_widget.itemSelectionChanged.connect( - self.on_main_selection_changed) - self.related_bs_list.list_widget.itemSelectionChanged.connect( - self.on_related_selection_changed) - - # 数值变化 - self.bs_controls.value_slider.valueChanged.connect( - self.on_bs_value_changed) - self.expr_slider.valueChanged.connect( - self.on_expr_value_changed) - - # 回调函数 - def on_main_selection_changed(self): - """主BlendShape列表选择变化""" - from scripts.utils import adjust_utils - items = self.main_bs_list.list_widget.selectedItems() - adjust_utils.on_main_bs_selected([item.text() for item in items]) + # 功能开关和数值 + toggle_layout = QtWidgets.QHBoxLayout() - def on_related_selection_changed(self): - """相关BlendShape列表选择变化""" - from scripts.utils import adjust_utils - items = self.related_bs_list.list_widget.selectedItems() - adjust_utils.on_related_bs_selected([item.text() for item in items]) + # 左侧开关 + left_toggles = QtWidgets.QHBoxLayout() + for text in ["KEY", "MIR", "ARK", "CTR"]: + btn = QtWidgets.QPushButton(text) + btn.setCheckable(True) + left_toggles.addWidget(btn) + toggle_layout.addLayout(left_toggles) - def on_bs_value_changed(self, value): - """BlendShape权重变化""" - from scripts.utils import adjust_utils - adjust_utils.set_bs_value(value) + # 右侧数值 + toggle_layout.addStretch() + value_spin = QtWidgets.QDoubleSpinBox() + value_spin.setValue(0.000) + toggle_layout.addWidget(value_spin) + toggle_layout.addWidget(QtWidgets.QPushButton("全部")) - def on_expr_value_changed(self, value): - """表情权重变化""" - from scripts.utils import adjust_utils - adjust_utils.set_expr_value(value) + bottom_layout.addLayout(toggle_layout) - # 表情控制回调 - def reset_expression(self): - """还原默认表情""" - from scripts.utils import adjust_utils - adjust_utils.reset_expression() + # 操作按钮 + action_layout = QtWidgets.QGridLayout() + actions = [ + ("在线帮助", adjust_utils.show_help), + ("控制面板编辑", adjust_utils.edit_control_panel), + ("控制面板预览", adjust_utils.preview_control_panel), + ("导入编辑器", adjust_utils.import_editor), + ("导入编辑器预览", adjust_utils.import_editor_preview), + ("导入编辑器预览", adjust_utils.import_editor_preview) + ] - def select_expression(self): - """选择表情""" - from scripts.utils import adjust_utils - adjust_utils.select_expression() + for i, (text, callback) in enumerate(actions): + btn = QtWidgets.QPushButton(text) + btn.clicked.connect(lambda checked=False, cb=callback: cb()) + row = i // 2 + col = i % 2 + action_layout.addWidget(btn, row, col) + bottom_layout.addLayout(action_layout) - def write_expression(self): - """写入当前表情""" - from scripts.utils import adjust_utils - adjust_utils.write_expression() + right_layout.addLayout(bottom_layout) + splitter.addWidget(right_widget) - def find_controller(self): - """查找控制器""" - from scripts.utils import adjust_utils - adjust_utils.find_controller() + # 设置分割器比例 + splitter.setSizes([400, 400]) - def select_joints(self): - """选择关联关节""" - from scripts.utils import adjust_utils - adjust_utils.select_joints() + def _on_raw_value_changed(self, value): + """Raw Control数值改变""" + self.raw_value_label.setText(f"{value/1000:.3f}") - def write_mirror_expression(self): - """写入镜像表情""" - from scripts.utils import adjust_utils - adjust_utils.write_mirror_expression() \ No newline at end of file + def _on_blend_value_changed(self, value): + """Blend Shape数值改变""" + self.blend_value_label.setText(f"{value/1000:.3f}") + \ No newline at end of file diff --git a/scripts/ui/adjust_utils.py b/scripts/ui/adjust_utils.py new file mode 100644 index 0000000..5a2dfa6 --- /dev/null +++ b/scripts/ui/adjust_utils.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +#===================================== 1. Module Imports ===================================== +import maya.cmds as cmds +import maya.mel as mel +import os +from scripts.config import data + +try: + from PySide2 import QtCore, QtGui, QtWidgets + from shiboken2 import wrapInstance +except ImportError: + try: + from PySide6 import QtCore, QtGui, QtWidgets + from shiboken6 import wrapInstance + except ImportError: + try: + from PySide import QtCore, QtGui, QtWidgets + from shiboken import wrapInstance + except ImportError as e: + QtCore = QtGui = QtWidgets = None + wrapInstance = None + +#===================================== 2. BlendShape Manager Class ===================================== +# 菜单 +@staticmethod +def create_rl4_node(): pass + +@staticmethod +def delete_rl4_node(): pass + +@staticmethod +def mirror_left_to_right(): pass + +@staticmethod +def mirror_right_to_left(): pass + +@staticmethod +def pose_a_to_t(): pass + +@staticmethod +def pose_t_to_a(): pass + +@staticmethod +def transfer_lod_texture(): pass + +@staticmethod +def set_joint_color(): pass + +@staticmethod +def unmark_all(): pass + +@staticmethod +def rebuild_all_targets(): pass + +@staticmethod +def bake_all_animations(): pass + +@staticmethod +def bake_all_keyframes(): pass + +@staticmethod +def restore_expression(): pass + +@staticmethod +def blend_filter(): pass + +@staticmethod +def range_increase(): pass + +@staticmethod +def range_reduction(): pass + +@staticmethod +def flip_target(): pass + +@staticmethod +def mirror_target(): pass + +@staticmethod +def find_flip_target(): pass + +@staticmethod +def add_blend_shape(): pass + +@staticmethod +def batch_blend_shape(): pass + +@staticmethod +def rebuild_selected_target(): pass + +@staticmethod +def blend_selected_target(): pass + +@staticmethod +def psd(): pass + +@staticmethod +def bse(): pass + +@staticmethod +def key(): pass + +@staticmethod +def mir(): pass + +@staticmethod +def ark(): pass + +@staticmethod +def ctr(): pass + +@staticmethod +def restore_default_expression(): pass + +@staticmethod +def find_selected_expression(): pass + +@staticmethod +def write_current_expression(): pass + +@staticmethod +def control_panel_search(): pass + +@staticmethod +def select_associated_joints(): pass + +@staticmethod +def write_mirror_expression(): pass + +@staticmethod +def organize_targets(): pass + +@staticmethod +def advanced_blend(): pass + +@staticmethod +def rebuild_targets(): pass + +@staticmethod +def add_blend(): pass + +@staticmethod +def delete_blend(): pass + +@staticmethod +def batch_blend(): pass + +@staticmethod +def show_help(): pass + +@staticmethod +def edit_control_panel(): pass + +@staticmethod +def preview_control_panel(): pass + +@staticmethod +def import_editor(): pass + +@staticmethod +def import_editor_preview(): pass + diff --git a/scripts/ui/define.py b/scripts/ui/define.py index 8e66c78..8549ffe 100644 --- a/scripts/ui/define.py +++ b/scripts/ui/define.py @@ -1,195 +1,222 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- - #===================================== 1. Module Imports ===================================== import maya.cmds as cmds import maya.mel as mel import sys import os - -from scripts.ui.widgets import ( BaseWidget, IconButton, SearchLineEdit) +from scripts.config import data +from scripts.ui import define_utils as define_utils +from scripts.ui import widgets as widgets try: from PySide2 import QtCore, QtGui, QtWidgets from shiboken2 import wrapInstance - print("从PySide2加载Qt和shiboken2") except ImportError: try: from PySide6 import QtCore, QtGui, QtWidgets from shiboken6 import wrapInstance - print("从PySide6加载Qt和shiboken6") except ImportError: try: from PySide import QtCore, QtGui, QtWidgets from shiboken import wrapInstance - print("从PySide加载Qt和shiboken") except ImportError as e: - print(f"Qt加载失败: {str(e)}") QtCore = QtGui = QtWidgets = None wrapInstance = None - #===================================== 2. Define Tab Class ===================================== -class DefineTab(BaseWidget): +class DefineTab(widgets.BaseWidget): """定义标签页""" def __init__(self, parent=None): + self.lod_tree = None + self.mesh_tree = None + self.joint_tree = None + self.blend_tree = None + self.anim_tree = None super(DefineTab, self).__init__(parent) def setup_ui(self): + """初始化UI""" layout = QtWidgets.QVBoxLayout(self) + layout.setSpacing(self.SPACING) + layout.setContentsMargins(self.MARGINS, self.MARGINS, + self.MARGINS, self.MARGINS) - # 创建滚动区域 - scroll = QtWidgets.QScrollArea() - scroll.setWidgetResizable(True) - scroll.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - layout.addWidget(scroll) + # 创建左右分栏布局 + splitter = QtWidgets.QSplitter(QtCore.Qt.Horizontal) + layout.addWidget(splitter) - # 创建内容控件 - content = QtWidgets.QWidget() - content_layout = QtWidgets.QVBoxLayout(content) - scroll.setWidget(content) + # 左侧树形结构 + left_widget = QtWidgets.QWidget() + left_layout = QtWidgets.QVBoxLayout(left_widget) - # DNA定义部分 - dna_group = self.create_dna_definition() - content_layout.addWidget(dna_group) + # LODs树 + lod_group = QtWidgets.QGroupBox("LODs") + lod_layout = QtWidgets.QVBoxLayout() + self.lod_tree = QtWidgets.QTreeWidget() + self.lod_tree.setHeaderHidden(True) - # 骨骼定义部分 - joint_group = self.create_joint_definition() - content_layout.addWidget(joint_group) + # 添加LOD项 + for i in range(8): + lod_item = QtWidgets.QTreeWidgetItem([f"LOD {i}"]) + self.lod_tree.addTopLevelItem(lod_item) + + lod_layout.addWidget(self.lod_tree) + lod_group.setLayout(lod_layout) + left_layout.addWidget(lod_group) - # BlendShape定义部分 - bs_group = self.create_blendshape_definition() - content_layout.addWidget(bs_group) + # Meshes树 + mesh_group = QtWidgets.QGroupBox("Meshes [010/54]") + mesh_layout = QtWidgets.QVBoxLayout() + self.mesh_tree = QtWidgets.QTreeWidget() + self.mesh_tree.setHeaderHidden(True) - content_layout.addStretch() + # 添加示例网格 + 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") + ] - def create_dna_definition(self): - """创建DNA定义组""" - group = QtWidgets.QGroupBox("DNA定义") - layout = QtWidgets.QVBoxLayout(group) + for name, index in meshes: + mesh_item = QtWidgets.QTreeWidgetItem([name]) + mesh_item.setData(0, QtCore.Qt.UserRole, index) + self.mesh_tree.addTopLevelItem(mesh_item) + + mesh_layout.addWidget(self.mesh_tree) + mesh_group.setLayout(mesh_layout) + left_layout.addWidget(mesh_group) - # DNA文件选择 - file_layout = QtWidgets.QHBoxLayout() - file_layout.addWidget(QtWidgets.QLabel("DNA文件:")) - self.dna_file_input = QtWidgets.QLineEdit() - file_layout.addWidget(self.dna_file_input) + splitter.addWidget(left_widget) - browse_btn = IconButton("target.png", "浏览DNA文件") - browse_btn.clicked.connect(self.browse_dna_file) - file_layout.addWidget(browse_btn) + # 右侧内容 + right_widget = QtWidgets.QWidget() + right_layout = QtWidgets.QVBoxLayout(right_widget) - layout.addLayout(file_layout) + # Joints树 + joint_group = QtWidgets.QGroupBox("Joints [840/870]") + joint_layout = QtWidgets.QVBoxLayout() + self.joint_tree = QtWidgets.QTreeWidget() + self.joint_tree.setHeaderHidden(True) - # DNA预览 - preview_group = QtWidgets.QGroupBox("DNA预览") - preview_layout = QtWidgets.QVBoxLayout(preview_group) - self.dna_preview = QtWidgets.QTextEdit() - self.dna_preview.setReadOnly(True) - preview_layout.addWidget(self.dna_preview) + # 添加示例关节 + joints = [ + ("FACIAL_C_NeckB", "001"), + ("FACIAL_L_NeckB1", "002"), + ("FACIAL_R_NeckB1", "003"), + ("FACIAL_L_NeckB2", "002") + ] - layout.addWidget(preview_group) + for name, group in joints: + joint_item = QtWidgets.QTreeWidgetItem([f"{name} Group: {group}"]) + self.joint_tree.addTopLevelItem(joint_item) + + joint_layout.addWidget(self.joint_tree) + joint_group.setLayout(joint_layout) + right_layout.addWidget(joint_group) - return group + # Blend Shapes树 + blend_group = QtWidgets.QGroupBox("Blend Shapes [782/782]") + blend_layout = QtWidgets.QVBoxLayout() + self.blend_tree = QtWidgets.QTreeWidget() + self.blend_tree.setHeaderHidden(True) - def create_joint_definition(self): - """创建骨骼定义组""" - group = QtWidgets.QGroupBox("骨骼定义") - layout = QtWidgets.QVBoxLayout(group) + # 添加示例混合形状 + blends = [ + "brow_down_L", + "brow_down_R", + "brow_lateral_L", + "brow_lateral_R" + ] - # 骨骼列表 - self.joint_list = QtWidgets.QTreeWidget() - self.joint_list.setHeaderLabels(["骨骼名称", "位置", "旋转", "缩放"]) - layout.addWidget(self.joint_list) + for name in blends: + blend_item = QtWidgets.QTreeWidgetItem([name]) + self.blend_tree.addTopLevelItem(blend_item) + + blend_layout.addWidget(self.blend_tree) + blend_group.setLayout(blend_layout) + right_layout.addWidget(blend_group) - # 骨骼工具栏 - tools_layout = QtWidgets.QHBoxLayout() + # AnimatedMap树 + anim_group = QtWidgets.QGroupBox("AnimatedMap [082/82]") + anim_layout = QtWidgets.QVBoxLayout() + self.anim_tree = QtWidgets.QTreeWidget() + self.anim_tree.setHeaderHidden(True) - # 添加骨骼 - add_btn = IconButton("joint.png", "添加骨骼") - add_btn.clicked.connect(self.add_joint) - tools_layout.addWidget(add_btn) + # 添加示例动画贴图 + anims = [ + "head_cm2_color.head_wm2_browsDown_L", + "head_cm2_color.head_wm2_browsDown_R", + "head_cm2_color.head_wm2_browsLateral_L", + "head_cm2_color.head_wm2_browsLateral_R" + ] - # 删除骨骼 - del_btn = IconButton("delete.png", "删除骨骼") - del_btn.clicked.connect(self.delete_joint) - tools_layout.addWidget(del_btn) + for name in anims: + anim_item = QtWidgets.QTreeWidgetItem([name]) + self.anim_tree.addTopLevelItem(anim_item) + + anim_layout.addWidget(self.anim_tree) + anim_group.setLayout(anim_layout) + right_layout.addWidget(anim_group) - # 修改骨骼 - mod_btn = IconButton("modify.png", "修改骨骼") - mod_btn.clicked.connect(self.modify_joint) - tools_layout.addWidget(mod_btn) + splitter.addWidget(right_widget) - tools_layout.addStretch() - layout.addLayout(tools_layout) + # 底部按钮组 + bottom_layout = QtWidgets.QHBoxLayout() - return group + # 导入组 + write_group = QtWidgets.QGroupBox("导入") + write_layout = QtWidgets.QVBoxLayout() + write_btns = [ + ("导入关节和权重", define_utils.write_joints_weights), + ("导入几何体", define_utils.write_geometry), + ("导入蒙皮权重", define_utils.write_skin_weights), + ("导入混合变形目标", define_utils.write_blend_targets) + ] - def create_blendshape_definition(self): - """创建BlendShape定义组""" - group = QtWidgets.QGroupBox("BlendShape定义") - layout = QtWidgets.QVBoxLayout(group) + for text, callback in write_btns: + btn = QtWidgets.QPushButton(text) + btn.clicked.connect(lambda checked=False, cb=callback: cb()) + write_layout.addWidget(btn) + + write_group.setLayout(write_layout) + bottom_layout.addWidget(write_group) - # BlendShape列表 - self.bs_list = QtWidgets.QTreeWidget() - self.bs_list.setHeaderLabels(["名称", "目标", "权重"]) - layout.addWidget(self.bs_list) + # 创建组 + create_group = QtWidgets.QGroupBox("创建") + create_layout = QtWidgets.QVBoxLayout() + create_btns = [ + ("创建新的LOD", define_utils.create_new_lod), + ("绑定蒙皮", define_utils.bind_skin), + ("取消蒙皮", define_utils.unbind_skin) + ] - # BlendShape工具栏 - tools_layout = QtWidgets.QHBoxLayout() + for text, callback in create_btns: + btn = QtWidgets.QPushButton(text) + btn.clicked.connect(lambda checked=False, cb=callback: cb()) + create_layout.addWidget(btn) + + create_group.setLayout(create_layout) + bottom_layout.addWidget(create_group) - # 添加BlendShape - add_btn = IconButton("blendShape.png", "添加BlendShape") - add_btn.clicked.connect(self.add_blendshape) - tools_layout.addWidget(add_btn) + # 工具组 + tools_group = QtWidgets.QGroupBox("工具") + tools_layout = QtWidgets.QVBoxLayout() + tools_btns = [ + ("更新运行时LOD", define_utils.update_runtime_lod), + ("快速创建绑定", define_utils.quick_create_binding), + ("创建蒙皮", define_utils.create_skin) + ] - # 删除BlendShape - del_btn = IconButton("delete.png", "删除BlendShape") - del_btn.clicked.connect(self.delete_blendshape) - tools_layout.addWidget(del_btn) + for text, callback in tools_btns: + btn = QtWidgets.QPushButton(text) + btn.clicked.connect(lambda checked=False, cb=callback: cb()) + tools_layout.addWidget(btn) + + tools_group.setLayout(tools_layout) + bottom_layout.addWidget(tools_group) - # 修改BlendShape - mod_btn = IconButton("modify.png", "修改BlendShape") - mod_btn.clicked.connect(self.modify_blendshape) - tools_layout.addWidget(mod_btn) - - tools_layout.addStretch() - layout.addLayout(tools_layout) - - return group - - # DNA定义回调 - def browse_dna_file(self): - """浏览DNA文件""" - from scripts.utils import define_utils - define_utils.browse_dna_file() - - # 骨骼定义回调 - def add_joint(self): - """添加骨骼""" - from scripts.utils import define_utils - define_utils.add_joint() - - def delete_joint(self): - """删除骨骼""" - from scripts.utils import define_utils - define_utils.delete_joint() - - def modify_joint(self): - """修改骨骼""" - from scripts.utils import define_utils - define_utils.modify_joint() - - # BlendShape定义回调 - def add_blendshape(self): - """添加BlendShape""" - from scripts.utils import define_utils - define_utils.add_blendshape() - - def delete_blendshape(self): - """删除BlendShape""" - from scripts.utils import define_utils - define_utils.delete_blendshape() - - def modify_blendshape(self): - """修改BlendShape""" - from scripts.utils import define_utils - define_utils.modify_blendshape() \ No newline at end of file + layout.addLayout(bottom_layout) + \ No newline at end of file diff --git a/scripts/ui/define_utils.py b/scripts/ui/define_utils.py new file mode 100644 index 0000000..3bf112d --- /dev/null +++ b/scripts/ui/define_utils.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +#===================================== 1. Module Imports ===================================== +import maya.cmds as cmds +import maya.mel as mel +import os +from scripts.config import data + +try: + from PySide2 import QtCore, QtGui, QtWidgets + from shiboken2 import wrapInstance +except ImportError: + try: + from PySide6 import QtCore, QtGui, QtWidgets + from shiboken6 import wrapInstance + except ImportError: + try: + from PySide import QtCore, QtGui, QtWidgets + from shiboken import wrapInstance + except ImportError as e: + QtCore = QtGui = QtWidgets = None + wrapInstance = None +#===================================== 2. DNA Definition Class ===================================== + +@staticmethod +def write_joints_weights(): pass + +@staticmethod +def write_geometry(): pass + +@staticmethod +def write_skin_weights(): pass + +@staticmethod +def write_blend_targets(): pass + +@staticmethod +def create_new_lod(): pass + +@staticmethod +def bind_skin(): pass + +@staticmethod +def unbind_skin(): pass + +@staticmethod +def update_runtime_lod(): pass + +@staticmethod +def quick_create_binding(): pass + +@staticmethod +def create_skin(): pass diff --git a/scripts/ui/menu.py b/scripts/ui/menu.py index 2f458fe..cef3dbe 100644 --- a/scripts/ui/menu.py +++ b/scripts/ui/menu.py @@ -2,130 +2,162 @@ # -*- coding: utf-8 -*- #===================================== 1. Module Imports ===================================== +import maya.cmds as cmds +import maya.mel as mel +import sys import os from scripts.config import data -from scripts.utils import menu_utils +from scripts.ui import menu_utils as menu_utils +from scripts.ui import mesh_utils as mesh_utils +from scripts.ui import rigging_utils as rigging_utils +from scripts.ui import define_utils as define_utils +from scripts.ui import adjust_utils as adjust_utils +from scripts.ui import widgets as widgets try: from PySide2 import QtCore, QtGui, QtWidgets from shiboken2 import wrapInstance - print("从PySide2加载Qt和shiboken2") except ImportError: try: from PySide6 import QtCore, QtGui, QtWidgets from shiboken6 import wrapInstance - print("从PySide6加载Qt和shiboken6") except ImportError: try: from PySide import QtCore, QtGui, QtWidgets from shiboken import wrapInstance - print("从PySide加载Qt和shiboken") except ImportError as e: - print(f"Qt加载失败: {str(e)}") QtCore = QtGui = QtWidgets = None wrapInstance = None #===================================== 2. Menu Manager Class ===================================== + class MenuManager: """菜单管理器""" def __init__(self, parent): self.parent = parent self.menu_bar = parent.menuBar() - self.create_menus() - self.create_toolbar() + self._create_menus() + self._create_toolbar() - def create_menus(self): + def _create_menus(self): """创建菜单""" - # 文件菜单 - file_menu = self.menu_bar.addMenu("文件") - self.add_menu_item(file_menu, "打开DNA", "open.png", menu_utils.load_dna) - self.add_menu_item(file_menu, "保存DNA", "save.png", menu_utils.save_dna) - self.add_menu_item(file_menu, "加载当前项目的DNA", "open.png", menu_utils.load_project_dna) - file_menu.addSeparator() - self.add_menu_item(file_menu, "修改混合目标名称", "rename.png", menu_utils.rename_blend_target) - self.add_menu_item(file_menu, "重置混合目标名称", "resetname.png", menu_utils.reset_blend_target) - file_menu.addSeparator() - self.add_menu_item(file_menu, "导出FBX", "export.png", menu_utils.export_fbx) - file_menu.addSeparator() - self.add_menu_item(file_menu, "退出", "exit.png", menu_utils.safe_shutdown) + # 定义所有菜单项 + self.menu_definitions = { + "文件": [ + ("打开DNA", "open.png", menu_utils.load_dna), + ("保存DNA", "save.png", menu_utils.save_dna), + ("加载当前项目的DNA", "open.png", menu_utils.load_project_dna), + None, + ("修改混合目标名称", "rename.png", menu_utils.rename_blendshape_target), + ("重置混合目标名称", "resetname.png", menu_utils.reset_blendshape_target), + None, + ("导出FBX", "export.png", menu_utils.export_fbx), + None, + ("退出", "exit.png", menu_utils.shutdown) + ], + "编辑": [ + ("创建RL4节点", "connect.png", adjust_utils.create_rl4_node), + ("删除RL4节点", "disconnect.png", adjust_utils.delete_rl4_node), + None, + ("镜像左至右", "mirrorL.png", adjust_utils.mirror_left_to_right), + ("镜像右至左", "mirrorR.png", adjust_utils.mirror_right_to_left), + None, + ("姿势由A型转T型", "pose_A_To_T.png", adjust_utils.pose_a_to_t), + ("姿势由T型转A型", "pose_T_To_A.png", adjust_utils.pose_t_to_a), + None, + ("传输LOD贴图", "locator.png", adjust_utils.transfer_lod_texture), + ("设置关节颜色", "color.png", adjust_utils.set_joint_color), + None, + ("取消全部标记", "unmark_all.png", adjust_utils.unmark_all), + ("重建所有目标", "rebuildTargets.png", adjust_utils.rebuild_all_targets), + None, + ("为所有表情设置关键帧", "bakeAnimation.png", adjust_utils.bake_all_animations), + ("烘焙所有表情的关键帧", "centerCurrentTime.png", adjust_utils.bake_all_keyframes) + ], + "工具": [ + ("导出蒙皮", "export_skin.png", rigging_utils.export_skin), + ("导入蒙皮", "import_skin.png", rigging_utils.import_skin), + ("拷贝蒙皮", "copy_skin.png", rigging_utils.copy_skin), + None, + ("RBF变形器", "blendShape.png", rigging_utils.create_rbf_deformer), + ("快速绑定服装", "clothing_weight.png", rigging_utils.quick_bind_clothing), + ("克隆混合变形", "blendShape.png", rigging_utils.clone_blendshape), + None, + ("UV传递点序", "repair_vertex_order.png", rigging_utils.transfer_uv_order), + ("面部生成控制器", "controller.png", rigging_utils.create_face_controller), + ("提取52BS", "ARKit52.png", rigging_utils.extract_52bs), + None, + ("关节轴向修复", "joint.png", rigging_utils.fix_joint_orientation), + ("生成身体控制器", "create_body_ctrl.png", rigging_utils.create_body_controller), + None, + ("导入面部动画", "import_face_anim.png", rigging_utils.import_face_animation), + ("导入身体动画", "import_body_anim.png", rigging_utils.import_body_animation) + ], + "语言": [ + ("中文", "chinese.png", menu_utils.set_chinese), + ("English", "english.png", menu_utils.set_english) + ], + "帮助": [ + ("帮助文档", "help.png", menu_utils.show_help), + ("关于", "warning.png", menu_utils.show_about) + ] + } - # 编辑菜单 - edit_menu = self.menu_bar.addMenu("编辑") - self.add_menu_item(edit_menu, "创建RL4节点", "connect.png", menu_utils.create_rl4_node) - self.add_menu_item(edit_menu, "删除RL4节点", "disconnect.png", menu_utils.delete_rl4_node) - edit_menu.addSeparator() - self.add_menu_item(edit_menu, "镜像左至右", "mirrorL.png", menu_utils.mirror_left_to_right) - self.add_menu_item(edit_menu, "镜像右至左", "mirrorR.png", menu_utils.mirror_right_to_left) - edit_menu.addSeparator() - self.add_menu_item(edit_menu, "姿势由A型转T型", "pose_A_To_T.png", menu_utils.pose_a_to_t) - self.add_menu_item(edit_menu, "姿势由T型转A型", "pose_T_To_A.png", menu_utils.pose_t_to_a) - edit_menu.addSeparator() - self.add_menu_item(edit_menu, "传输LOD贴图", "locator.png", menu_utils.transfer_lod_texture) - self.add_menu_item(edit_menu, "设置关节颜色", "color.png", menu_utils.set_joint_color) - edit_menu.addSeparator() - self.add_menu_item(edit_menu, "取消全部标记", "unmark_all.png", menu_utils.unmark_all) - self.add_menu_item(edit_menu, "重建所有目标", "rebuildTargets.png", menu_utils.rebuild_all_targets) - edit_menu.addSeparator() - self.add_menu_item(edit_menu, "为所有表情设置关键帧", "bakeAnimation.png", menu_utils.bake_all_animations) - self.add_menu_item(edit_menu, "烘焙所有表情的关键帧", "centerCurrentTime.png", menu_utils.bake_all_keyframes) - - # 工具菜单 - tools_menu = self.menu_bar.addMenu("工具") - self.add_menu_item(tools_menu, "导出蒙皮", "export_skin.png", menu_utils.export_skin) - self.add_menu_item(tools_menu, "导入蒙皮", "import_skin.png", menu_utils.import_skin) - self.add_menu_item(tools_menu, "拷贝蒙皮", "copy_skin.png", menu_utils.copy_skin) - tools_menu.addSeparator() - self.add_menu_item(tools_menu, "RBF变形器", "blendShape.png", menu_utils.create_rbf_deformer) - self.add_menu_item(tools_menu, "快速绑定服装", "clothing_weight.png", menu_utils.quick_bind_clothing) - self.add_menu_item(tools_menu, "克隆混合变形", "blendShape.png", menu_utils.clone_blendshape) - tools_menu.addSeparator() - self.add_menu_item(tools_menu, "UV传递点序", "repair_vertex_order.png", menu_utils.transfer_uv_order) - self.add_menu_item(tools_menu, "面部生成控制器", "controller.png", menu_utils.create_face_controller) - self.add_menu_item(tools_menu, "提取52BS", "ARKit52.png", menu_utils.extract_52bs) - tools_menu.addSeparator() - self.add_menu_item(tools_menu, "关节轴向修复", "joint.png", menu_utils.fix_joint_orientation) - self.add_menu_item(tools_menu, "生成身体控制器", "create_body_ctrl.png", menu_utils.create_body_controller) - tools_menu.addSeparator() - self.add_menu_item(tools_menu, "导入面部动画", "import_face_anim.png", menu_utils.import_face_animation) - self.add_menu_item(tools_menu, "导入身体动画", "import_body_anim.png", menu_utils.import_body_animation) - - # 语言菜单 - lang_menu = self.menu_bar.addMenu("语言") - self.add_menu_item(lang_menu, "中文", "chinese.png", menu_utils.set_chinese) - self.add_menu_item(lang_menu, "English", "english.png", menu_utils.set_english) - - # 帮助菜单 - help_menu = self.menu_bar.addMenu("帮助") - self.add_menu_item(help_menu, "帮助文档", "help.png", menu_utils.show_help) - self.add_menu_item(help_menu, "关于", "warning.png", menu_utils.show_about) - - def create_toolbar(self): + # 创建所有菜单 + for menu_name, items in self.menu_definitions.items(): + menu = self.menu_bar.addMenu(menu_name) + self._add_menu_items(menu, items) + + def _create_toolbar(self): """创建工具栏""" toolbar = self.parent.addToolBar("主工具栏") - toolbar.setMovable(False) + toolbar.setMovable(True) # 允许移动 + toolbar.setFloatable(True) # 允许浮动 - # 添加工具栏按钮 - self.add_toolbar_item(toolbar, "保存DNA", "save.png", menu_utils.save_dna) - self.add_toolbar_item(toolbar, "加载当前项目DNA", "open.png", menu_utils.load_project_dna) + # 定义工具栏按钮 + toolbar_items = [ + ("保存DNA", "save.png", menu_utils.save_dna), + ("加载当前项目DNA", "open.png", menu_utils.load_project_dna), + None, + ("创建RL4节点", "connect.png", adjust_utils.create_rl4_node), + ("删除RL4节点", "disconnect.png", adjust_utils.delete_rl4_node), + None, + ("导出FBX蒙皮", "export_skin.png", rigging_utils.export_skin), + None, + ("退出", "exit.png", menu_utils.shutdown), + None, + ("帮助文档", "help.png", menu_utils.show_help), + ("关于", "warning.png", menu_utils.show_about) + ] - def add_menu_item(self, menu, text, icon, callback): + self._add_toolbar_items(toolbar, toolbar_items) + + def _add_menu_items(self, menu, items): """添加菜单项""" - action = QtWidgets.QAction(text, self.parent) - if icon: - icon_path = os.path.join(data.ICONS_PATH, icon) - if os.path.exists(icon_path): - action.setIcon(QtGui.QIcon(icon_path)) - action.triggered.connect(callback) - menu.addAction(action) - return action - - def add_toolbar_item(self, toolbar, text, icon, callback): + for item in items: + if item is None: + menu.addSeparator() + else: + text, icon, callback = item + action = QtWidgets.QAction(text, self.parent) + if icon: + icon_path = os.path.join(data.ICONS_PATH, icon) + if os.path.exists(icon_path): + action.setIcon(QtGui.QIcon(icon_path)) + action.triggered.connect(lambda checked=False, cb=callback: cb()) + menu.addAction(action) + + def _add_toolbar_items(self, toolbar, items): """添加工具栏项""" - action = QtWidgets.QAction(text, self.parent) - if icon: - icon_path = os.path.join(data.ICONS_PATH, icon) - if os.path.exists(icon_path): - action.setIcon(QtGui.QIcon(icon_path)) - action.triggered.connect(callback) - toolbar.addAction(action) - return action \ No newline at end of file + for item in items: + if item is None: + toolbar.addSeparator() + else: + text, icon, callback = item + action = QtWidgets.QAction(text, self.parent) + if icon: + icon_path = os.path.join(data.ICONS_PATH, icon) + if os.path.exists(icon_path): + action.setIcon(QtGui.QIcon(icon_path)) + action.triggered.connect(lambda checked=False, cb=callback: cb()) + toolbar.addAction(action) \ No newline at end of file diff --git a/scripts/ui/menu_utils.py b/scripts/ui/menu_utils.py new file mode 100644 index 0000000..ac198e7 --- /dev/null +++ b/scripts/ui/menu_utils.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +#===================================== 1. Module Imports ===================================== +import maya.cmds as cmds +import maya.mel as mel +import os +from scripts.config import data + +try: + from PySide2 import QtCore, QtGui, QtWidgets + from shiboken2 import wrapInstance +except ImportError: + try: + from PySide6 import QtCore, QtGui, QtWidgets + from shiboken6 import wrapInstance + except ImportError: + try: + from PySide import QtCore, QtGui, QtWidgets + from shiboken import wrapInstance + except ImportError as e: + QtCore = QtGui = QtWidgets = None + wrapInstance = None + +#===================================== 2. Menu Utils ===================================== +# File +@staticmethod +def load_dna(): pass + +@staticmethod +def save_dna(): pass + +@staticmethod +def load_project_dna(): pass + +@staticmethod +def rename_blendshape_target(): pass + +@staticmethod +def reset_blendshape_target(): pass + +@staticmethod +def export_fbx(): pass + +@staticmethod +def shutdown(): pass + +@staticmethod +def set_chinese(): pass + +@staticmethod +def set_english(): pass + +@staticmethod +def show_help(): + """显示帮助文档""" + try: + help_url = data.TOOL_HELP_URL + webbrowser.open(help_url) + except Exception as e: + cmds.warning(f"打开帮助文档失败: {str(e)}") + +@staticmethod +def show_about(): + """显示关于信息""" + try: + about_text = ( + f"{data.TOOL_NAME} {data.TOOL_VERSION}\n\n" + f"作者: {data.TOOL_AUTHOR}\n" + f"版权所有 © {data.TOOL_YEAR}" + ) + cmds.confirmDialog( + title="关于", + message=about_text, + button=["确定"], + defaultButton="确定" + ) + except Exception as e: + cmds.warning(f"显示关于信息失败: {str(e)}") \ No newline at end of file diff --git a/scripts/ui/mesh.py b/scripts/ui/mesh.py new file mode 100644 index 0000000..dc24d8d --- /dev/null +++ b/scripts/ui/mesh.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +#===================================== 1. Module Imports ===================================== +import maya.cmds as cmds +import maya.mel as mel +import sys +import os +from scripts.config import data +from scripts.ui import mesh_utils as mesh_utils +from scripts.ui import widgets as widgets + +try: + from PySide2 import QtCore, QtGui, QtWidgets + from shiboken2 import wrapInstance +except ImportError: + try: + from PySide6 import QtCore, QtGui, QtWidgets + from shiboken6 import wrapInstance + except ImportError: + try: + from PySide import QtCore, QtGui, QtWidgets + from shiboken import wrapInstance + except ImportError as e: + QtCore = QtGui = QtWidgets = None + wrapInstance = None + +#===================================== 2. Model Tab Class ===================================== +class MeshTab(widgets.BaseWidget): + """模型标签页""" + def __init__(self, parent=None): + # 在调用父类的__init__之前初始化数据 + self.lod_tabs = None + self.lod_buttons = [] + self.source_combo = None + self.target_combo = None + + # LOD模型类型定义 + self.lod_models = { + 0: ["*头部", "*牙齿", "*牙龈", "*左眼", "*右眼", "*虹膜", "*睫毛", "*眼睑", "*软骨", "*身体"], + 1: ["头部", "牙齿", "牙龈", "左眼", "右眼", "虹膜", "睫毛", "眼睑", "软骨", "身体"], + 2: ["头部", "牙齿", "牙龈", "左眼", "右眼", "虹膜", "睫毛", "眼睑", "身体"], + 3: ["头部", "牙齿", "左眼", "右眼", "虹膜", "睫毛", "眼睑", "身体"], + 4: ["头部", "牙齿", "左眼", "右眼", "虹膜"], + 5: ["头部", "牙齿", "左眼", "右眼"], + 6: ["头部", "牙齿", "左眼", "右眼"], + 7: ["头部", "牙齿", "左眼", "右眼"] + } + + # 调用父类的__init__,这会触发setup_ui + super(MeshTab, self).__init__(parent) + + def setup_ui(self): + """初始化UI""" + layout = QtWidgets.QVBoxLayout(self) + layout.setSpacing(self.SPACING) + layout.setContentsMargins(self.MARGINS, self.MARGINS, + self.MARGINS, self.MARGINS) + + # 创建LOD标签页 + self.lod_tabs = QtWidgets.QTabWidget() + layout.addWidget(self.lod_tabs) + + # 添加LOD0-7标签页 + for i in range(8): + tab = self._create_lod_tab(i) + self.lod_tabs.addTab(tab, f"LOD{i}") + + # 添加底部工具区 + bottom_layout = QtWidgets.QVBoxLayout() + + # 添加三个主要按钮 + button_layout = QtWidgets.QHBoxLayout() + btn = widgets.IconButton("auto_load.png", "自动加载模型") + btn.clicked.connect(lambda checked=False, cb=mesh_utils.auto_load_models: cb()) + button_layout.addWidget(btn) + button_layout.addWidget(widgets.IconButton("standardize.png", "标准化命名", mesh_utils.standardize_naming)) + button_layout.addWidget(widgets.IconButton("auto_group.png", "自动分组", mesh_utils.auto_group)) + bottom_layout.addLayout(button_layout) + + # 添加模型工具区 + tools_group = QtWidgets.QGroupBox("模型工具") + tools_layout = QtWidgets.QGridLayout() + + # 左侧选择区 + model_layout = QtWidgets.QHBoxLayout() + model_layout.addWidget(QtWidgets.QLabel("预设模型:")) + self.source_combo = QtWidgets.QComboBox() + self.source_combo.addItem("Meta-Human") + model_layout.addWidget(self.source_combo) + + # 右侧LOD选择 + lod_layout = QtWidgets.QHBoxLayout() + lod_layout.addWidget(QtWidgets.QLabel("运行LOD:")) + self.target_combo = QtWidgets.QComboBox() + self.target_combo.addItem("全部") + lod_layout.addWidget(self.target_combo) + lod_layout.addWidget(widgets.IconButton("create.png", "创建LOD", mesh_utils.create_lod)) + + tools_layout.addLayout(model_layout, 0, 0) + tools_layout.addLayout(lod_layout, 0, 1) + + # 添加修复工具按钮 + repair_tools = [ + ("修复分离", mesh_utils.fix_split), + ("生成面部配件", mesh_utils.generate_facial_accessories), + ("修复法线", mesh_utils.fix_normals), + ("修复点序", mesh_utils.fix_vertex_order), + ("修复接缝", mesh_utils.fix_seams) + ] + + for i, (text, callback) in enumerate(repair_tools): + btn = QtWidgets.QPushButton(text) + btn.clicked.connect(lambda checked=False, cb=callback: cb()) + row = (i + 2) // 2 + col = (i + 2) % 2 + tools_layout.addWidget(btn, row, col) + + tools_group.setLayout(tools_layout) + bottom_layout.addWidget(tools_group) + + layout.addLayout(bottom_layout) + + def _create_lod_tab(self, lod_index): + """创建LOD标签页""" + widget = QtWidgets.QWidget() + layout = QtWidgets.QVBoxLayout(widget) + layout.setSpacing(5) + + # 获取当前LOD级别的模型列表 + models = self.lod_models.get(lod_index, []) + + for model_name in models: + model_layout = QtWidgets.QHBoxLayout() + + # 标签 + label = QtWidgets.QLabel(f"{model_name}:") + model_layout.addWidget(label) + + # 路径输入框 + path_input = widgets.ModernLineEdit(read_only=True) + model_layout.addWidget(path_input, 1) + + # 加载按钮 + load_btn = widgets.IconButton("target.png", "加载...") + load_btn.clicked.connect(lambda x, m=model_name: self._load_model(lod_index, m)) + model_layout.addWidget(load_btn) + + layout.addLayout(model_layout) + + layout.addStretch() + return widget + \ No newline at end of file diff --git a/scripts/ui/mesh_utils.py b/scripts/ui/mesh_utils.py new file mode 100644 index 0000000..4cc4f8e --- /dev/null +++ b/scripts/ui/mesh_utils.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +#===================================== 1. Module Imports ===================================== +import maya.cmds as cmds +import maya.mel as mel +import os +from scripts.config import data + +try: + from PySide2 import QtCore, QtGui, QtWidgets + from shiboken2 import wrapInstance +except ImportError: + try: + from PySide6 import QtCore, QtGui, QtWidgets + from shiboken6 import wrapInstance + except ImportError: + try: + from PySide import QtCore, QtGui, QtWidgets + from shiboken import wrapInstance + except ImportError as e: + QtCore = QtGui = QtWidgets = None + wrapInstance = None + +#===================================== 2. Model Manager Class ===================================== +@staticmethod +def load_custom_models(): pass + +@staticmethod +def create_lod(): pass + +@staticmethod +def auto_load_models(): pass + +@staticmethod +def standardize_naming(): pass + +@staticmethod +def auto_group(): pass + +@staticmethod +def split_mesh(): pass + +@staticmethod +def generate_facial_accessories(): pass + +@staticmethod +def fix_split(): pass + +@staticmethod +def fix_normals(): pass + +@staticmethod +def fix_vertex_order(): pass + +@staticmethod +def fix_seams(): pass + +@staticmethod +def clean_options(): pass + +@staticmethod +def import_skeleton(): pass + +@staticmethod +def create_skin(): pass + +@staticmethod +def export_skin(): pass + +@staticmethod +def import_skin(): pass + +@staticmethod +def copy_skin(): pass + +@staticmethod +def create_rbf_deformer(): pass + +@staticmethod +def quick_bind_clothing(): pass + +@staticmethod +def clone_blendshape(): pass + +@staticmethod +def transfer_uv_order(): pass + +@staticmethod +def create_face_controller(): pass + +@staticmethod +def extract_52bs(): pass + +@staticmethod +def fix_joint_orientation(): pass + +@staticmethod +def create_body_controller(): pass + +@staticmethod +def import_face_animation(): pass + +@staticmethod +def import_body_animation(): pass + + + + + + + + + + diff --git a/scripts/ui/models.py b/scripts/ui/models.py deleted file mode 100644 index 7c816ee..0000000 --- a/scripts/ui/models.py +++ /dev/null @@ -1,340 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -#===================================== 1. Module Imports ===================================== -import maya.cmds as cmds -import maya.mel as mel -import sys -import os - -from scripts.ui.widgets import (BaseWidget, LODGroup, IconButton, SearchLineEdit) - -try: - from PySide2 import QtCore, QtGui, QtWidgets - from shiboken2 import wrapInstance - print("从PySide2加载Qt和shiboken2") -except ImportError: - try: - from PySide6 import QtCore, QtGui, QtWidgets - from shiboken6 import wrapInstance - print("从PySide6加载Qt和shiboken6") - except ImportError: - try: - from PySide import QtCore, QtGui, QtWidgets - from shiboken import wrapInstance - print("从PySide加载Qt和shiboken") - except ImportError as e: - print(f"Qt加载失败: {str(e)}") - QtCore = QtGui = QtWidgets = None - wrapInstance = None - -#===================================== 2. Model Tab Class ===================================== -class ModelTab(BaseWidget): - """模型标签页""" - def __init__(self, parent=None): - # 在super()之前初始化类属性 - self.lod_tabs = None - self.lod_buttons = [] - self.source_combo = None - self.target_combo = None - # 调用父类初始化 - super(ModelTab, self).__init__(parent) - - def setup_ui(self): - """初始化UI""" - layout = QtWidgets.QVBoxLayout(self) - layout.setSpacing(10) - layout.setContentsMargins(10, 10, 10, 10) - - # 创建标签页控件 - self.lod_tabs = QtWidgets.QTabWidget() - layout.addWidget(self.lod_tabs) - - # 每个LOD级别的模型类型定义 - lod_models = { - 0: ["*头部", "*牙齿", "*牙龈", "*左眼", "*右眼", "*虹膜", "*睫毛", "*眼睑", "*软骨", "*身体"], - 1: ["头部", "牙齿", "牙龈", "左眼", "右眼", "虹膜", "睫毛", "眼睑", "软骨", "身体"], - 2: ["头部", "牙齿", "牙龈", "左眼", "右眼", "虹膜", "睫毛", "眼睑", "身体"], - 3: ["头部", "牙齿", "左眼", "右眼", "虹膜", "睫毛", "眼睑", "身体"], - 4: ["头部", "牙齿", "左眼", "右眼", "虹膜"], - 5: ["头部", "牙齿", "左眼", "右眼"], - 6: ["头部", "牙齿", "左眼", "右眼"], - 7: ["头部", "牙齿", "左眼", "右眼"] - } - - # 创建LOD0-7标签页 - for i in range(8): - tab = QtWidgets.QWidget() - tab_layout = QtWidgets.QVBoxLayout(tab) - tab_layout.setSpacing(10) - - # 创建网格布局 - grid = QtWidgets.QGridLayout() - grid.setSpacing(10) - - # 获取当前LOD级别的模型类型 - models = lod_models[i] - - # 为每个模型类型创建控件 - for idx, model in enumerate(models): - # 添加标签 - label_widget = QtWidgets.QLabel(f"{model}:") - label_widget.setMinimumWidth(25) - grid.addWidget(label_widget, idx, 0) - - # 添加输入框 - line_edit = QtWidgets.QLineEdit() - line_edit.setReadOnly(True) - line_edit.setMinimumWidth(200) - grid.addWidget(line_edit, idx, 1) - - # 添加加载按钮 - btn = IconButton("target.png", "加载...") - btn.setMinimumWidth(80) - if i > 0: # LOD0以外的按钮默认禁用 - btn.setEnabled(False) - grid.addWidget(btn, idx, 2) - self.lod_buttons.append(btn) - - # 连接按钮信号 - callback_name = f"load_{model.lower()}" - if hasattr(self, callback_name): - btn.clicked.connect(getattr(self, callback_name)) - - # 添加删除按钮 - delete_btn = IconButton("delete.png", "") - delete_btn.clicked.connect(lambda x=i: self.delete_lod(x)) - tab_layout.addWidget(delete_btn, alignment=QtCore.Qt.AlignRight) - - tab_layout.addLayout(grid) - tab_layout.addStretch() - self.lod_tabs.addTab(tab, f"LOD{i}") - - # LOD功能组 - lod_tools = self.create_lod_tools() - layout.addWidget(lod_tools) - - # 模型工具组 - model_tools = self.create_model_tools() - layout.addWidget(model_tools) - - def create_lod_tools(self): - """创建LOD功能组""" - group = QtWidgets.QGroupBox("LOD功能") - layout = QtWidgets.QHBoxLayout(group) - layout.setSpacing(5) - - # 创建三个按钮并设置为等宽 - buttons = [ - ("自定义加载模型", "custom_load.png", self.load_custom_models), - ("标准化命名", "standardize.png", self.standardize_naming), - ("自动分组", "auto_group.png", self.auto_group) - ] - - for text, icon, callback in buttons: - btn = IconButton(icon, text) - btn.clicked.connect(callback) - btn.setSizePolicy(QtWidgets.QSizePolicy.Expanding, - QtWidgets.QSizePolicy.Fixed) - layout.addWidget(btn) - - return group - - def create_model_tools(self): - """创建模型工具组""" - group = QtWidgets.QGroupBox("模型工具") - layout = QtWidgets.QVBoxLayout(group) - layout.setSpacing(10) - - # 第一行:选择栏和创建LOD按钮 - top_layout = QtWidgets.QHBoxLayout() - top_layout.setSpacing(10) - - # 源模型选择 - source_layout = QtWidgets.QHBoxLayout() - source_label = QtWidgets.QLabel("源模型:") - source_label.setMinimumWidth(30) - self.source_combo = QtWidgets.QComboBox() - self.source_combo.addItems(["选择源模型..."]) - self.source_combo.setMinimumWidth(120) - source_layout.addWidget(source_label) - source_layout.addWidget(self.source_combo) - top_layout.addLayout(source_layout) - - # 目标LOD选择 - target_layout = QtWidgets.QHBoxLayout() - target_label = QtWidgets.QLabel("目标LOD:") - target_label.setMinimumWidth(30) - self.target_combo = QtWidgets.QComboBox() - self.target_combo.addItems([f"LOD{i}" for i in range(8)]) - self.target_combo.setMinimumWidth(120) - target_layout.addWidget(target_label) - target_layout.addWidget(self.target_combo) - top_layout.addLayout(target_layout) - - # 创建LOD按钮 - create_lod_btn = IconButton("create_lod.png", "创建LOD") - create_lod_btn.clicked.connect(self.create_lod) - create_lod_btn.setMinimumWidth(50) - top_layout.addWidget(create_lod_btn) - - layout.addLayout(top_layout) - - # 其他工具按钮,每行两个 - tool_buttons = [ - ("模型分离", "polySplitVertex.png", self.split_model), - ("生成面部配件", "supplement_meshes.png", self.generate_facial_accessories), - ("修复法线", "repair_normals.png", self.fix_normals), - ("修复点序", "repair_vertex_order.png", self.fix_vertex_order), - ("修复接缝", "polyChipOff.png", self.fix_seams) - ] - - # 创建网格布局 - grid = QtWidgets.QGridLayout() - grid.setSpacing(10) - for idx, (text, icon, callback) in enumerate(tool_buttons): - btn = IconButton(icon, text) - btn.clicked.connect(callback) - btn.setSizePolicy(QtWidgets.QSizePolicy.Expanding, - QtWidgets.QSizePolicy.Fixed) - grid.addWidget(btn, idx//2, idx%2) - - layout.addLayout(grid) - return group - - def create_lod(self): - """创建LOD""" - # 获取选中的源模型和目标LOD - source_model = self.source_combo.currentText() - target_lod = int(self.target_combo.currentText().replace("LOD", "")) - - # 启用目标LOD的所有按钮 - for btn in self.lod_buttons: - if btn.parent().parent() == self.lod_tabs.widget(target_lod): - btn.setEnabled(True) - - # LOD功能回调 - def load_custom_models(self): - """自定加载模型""" - from scripts.utils import model_utils - model_utils.load_custom_models() - - def standardize_naming(self): - """标准化命名""" - from scripts.utils import model_utils - model_utils.standardize_naming() - - def auto_group(self): - """自动分组""" - from scripts.utils import model_utils - model_utils.auto_group() - - # 模型工具回调 - def split_model(self): - """分离模型""" - from scripts.utils import model_utils - model_utils.split_model() - - def generate_facial_accessories(self): - """生成面部配件""" - from scripts.utils import model_utils - model_utils.generate_facial_accessories() - - def fix_normals(self): - """修复法线""" - from scripts.utils import model_utils - model_utils.fix_normals() - - def fix_vertex_order(self): - """修复点序""" - from scripts.utils import model_utils - model_utils.fix_vertex_order() - - def fix_seams(self): - """修复接缝""" - from scripts.utils import model_utils - model_utils.fix_seams() - - def load_head(self, lod_index): - """加载头部模型""" - from scripts.utils import model_utils - file_path = self._get_file_path("头部模型") - if file_path: - model_utils.load_model(lod_index, "head", file_path) - self._update_input_text(lod_index, "头部", file_path) - - def load_teeth(self, lod_index): - """加载牙齿模型""" - from scripts.utils import model_utils - file_path = self._get_file_path("牙齿模型") - if file_path: - model_utils.load_model(lod_index, "teeth", file_path) - self._update_input_text(lod_index, "牙齿", file_path) - - def load_gums(self, lod_index): - """加载牙龈模型""" - from scripts.utils import model_utils - file_path = self._get_file_path("牙龈模型") - if file_path: - model_utils.load_model(lod_index, "gums", file_path) - self._update_input_text(lod_index, "牙龈", file_path) - - def delete_lod(self, lod_index): - """删除指定LOD""" - from scripts.utils import model_utils - reply = QtWidgets.QMessageBox.question( - self, - "删除LOD", - f"确定要删除LOD{lod_index}吗?", - QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No - ) - - if reply == QtWidgets.QMessageBox.Yes: - model_utils.delete_lod(lod_index) - # 清空输入框 - self._clear_lod_inputs(lod_index) - # 禁用按钮 - if lod_index > 0: - self._disable_lod_buttons(lod_index) - - def _get_file_path(self, model_type): - """获取文件路径""" - file_path, _ = QtWidgets.QFileDialog.getOpenFileName( - self, - f"选择{model_type}", - "", - "模型文件 (*.obj *.fbx *.ma *.mb);;所有文件 (*.*)" - ) - return file_path - - def _update_input_text(self, lod_index, model_type, file_path): - """更新输入框文本""" - tab = self.lod_tabs.widget(lod_index) - if tab: - grid = tab.layout().itemAt(1).layout() # 获取网格布局 - for i in range(grid.rowCount()): - label = grid.itemAtPosition(i, 0).widget() - if label.text().startswith(model_type): - input_field = grid.itemAtPosition(i, 1).widget() - input_field.setText(os.path.basename(file_path)) - break - - def _clear_lod_inputs(self, lod_index): - """清空LOD的所有输入框""" - tab = self.lod_tabs.widget(lod_index) - if tab: - grid = tab.layout().itemAt(1).layout() - for i in range(grid.rowCount()): - input_field = grid.itemAtPosition(i, 1).widget() - if input_field: - input_field.clear() - - def _disable_lod_buttons(self, lod_index): - """禁用LOD的所有按钮""" - tab = self.lod_tabs.widget(lod_index) - if tab: - grid = tab.layout().itemAt(1).layout() - for i in range(grid.rowCount()): - btn = grid.itemAtPosition(i, 2).widget() - if btn: - btn.setEnabled(False) \ No newline at end of file diff --git a/scripts/ui/rigging.py b/scripts/ui/rigging.py index 0f5e4cd..c722dd2 100644 --- a/scripts/ui/rigging.py +++ b/scripts/ui/rigging.py @@ -6,193 +6,228 @@ import maya.cmds as cmds import maya.mel as mel import sys import os - -from scripts.ui.widgets import (BaseWidget, DNABrowser, DescriptionWidget, IconButton, SearchLineEdit) +from scripts.config import data +from scripts.ui import rigging_utils as rigging_utils +from scripts.ui import widgets as widgets try: from PySide2 import QtCore, QtGui, QtWidgets from shiboken2 import wrapInstance - print("从PySide2加载Qt和shiboken2") except ImportError: try: from PySide6 import QtCore, QtGui, QtWidgets from shiboken6 import wrapInstance - print("从PySide6加载Qt和shiboken6") except ImportError: try: from PySide import QtCore, QtGui, QtWidgets from shiboken import wrapInstance - print("从PySide加载Qt和shiboken") except ImportError as e: - print(f"Qt加载失败: {str(e)}") QtCore = QtGui = QtWidgets = None wrapInstance = None #===================================== 2. Rigging Tab Class ===================================== -class RigTab(BaseWidget): +class RiggingTab(widgets.BaseWidget): """绑定标签页""" def __init__(self, parent=None): - super(RigTab, self).__init__(parent) + self.dna_browser = None + self.dna_flow = None # DNA按钮流布局 + self.scale_slider = None + self.dna_buttons = [] # 存储DNA按钮 + self.current_scale = 90 # 当前缩放值 + super(RiggingTab, self).__init__(parent) def setup_ui(self): + """初始化UI""" layout = QtWidgets.QVBoxLayout(self) + layout.setSpacing(self.SPACING) + layout.setContentsMargins(self.MARGINS, self.MARGINS, + self.MARGINS, self.MARGINS) + + # DNA浏览器区域 + browser_group = QtWidgets.QGroupBox("预览") + browser_layout = QtWidgets.QVBoxLayout() + + # DNA按钮流布局 + self.dna_flow = QtWidgets.QGridLayout() + self.dna_flow.setSpacing(5) # 创建滚动区域 - scroll = QtWidgets.QScrollArea() - scroll.setWidgetResizable(True) - scroll.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - layout.addWidget(scroll) + scroll_widget = QtWidgets.QWidget() + scroll_widget.setLayout(self.dna_flow) - # 创建内容控件 - content = QtWidgets.QWidget() - content_layout = QtWidgets.QVBoxLayout(content) - scroll.setWidget(content) + scroll_area = QtWidgets.QScrollArea() + scroll_area.setWidget(scroll_widget) + scroll_area.setWidgetResizable(True) + scroll_area.setMinimumHeight(300) + scroll_area.setStyleSheet("QScrollArea { background-color: #333333; }") + browser_layout.addWidget(scroll_area) - # DNA部分 - dna_group = self.create_dna_group() - content_layout.addWidget(dna_group) + # 缩放滑块 + slider_layout = QtWidgets.QHBoxLayout() + self.scale_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal) + self.scale_slider.setRange(50, 150) + self.scale_slider.setValue(self.current_scale) + self.scale_slider.valueChanged.connect(self._on_scale_changed) - # 资产部分 - asset_group = self.create_asset_group() - content_layout.addWidget(asset_group) - - # 描述部分 - self.description_widget = DescriptionWidget() - content_layout.addWidget(self.description_widget) - - # 骨架工具 - skeleton_tools = self.create_skeleton_tools() - content_layout.addWidget(skeleton_tools) - - content_layout.addStretch() - - def create_dna_group(self): - """创建DNA组""" - group = QtWidgets.QGroupBox("DNA") - layout = QtWidgets.QVBoxLayout(group) - - # DNA浏览器 - self.dna_browser = DNABrowser() - self.dna_browser.dnaSelected.connect(self.on_dna_selected) - layout.addWidget(self.dna_browser) + # 添加数值显示 + self.scale_label = QtWidgets.QLabel(f"{self.current_scale}") + slider_layout.addWidget(self.scale_label) + slider_layout.addWidget(self.scale_slider) # 导入导出按钮 - btn_layout = QtWidgets.QHBoxLayout() + button_layout = QtWidgets.QHBoxLayout() + button_layout.addWidget(widgets.IconButton("export.png", "导出预览")) + button_layout.addWidget(widgets.IconButton("import.png", "导入预览")) + slider_layout.addLayout(button_layout) - export_btn = IconButton("export.png", "导出设置") - export_btn.clicked.connect(self.export_settings) - btn_layout.addWidget(export_btn) + browser_layout.addLayout(slider_layout) + browser_group.setLayout(browser_layout) + layout.addWidget(browser_group) - import_btn = IconButton("import.png", "导入设置") - import_btn.clicked.connect(self.import_settings) - btn_layout.addWidget(import_btn) - - btn_layout.addStretch() - layout.addLayout(btn_layout) - - return group - - def create_asset_group(self): - """创建资产组""" - group = QtWidgets.QGroupBox("资产") - layout = QtWidgets.QFormLayout(group) + # 资产设置区域 + settings_layout = QtWidgets.QVBoxLayout() # 项目路径 project_layout = QtWidgets.QHBoxLayout() - self.project_edit = QtWidgets.QLineEdit() - project_btn = IconButton("target.png", "选择项目路径") - project_btn.clicked.connect(self.browse_project) - project_layout.addWidget(self.project_edit) - project_layout.addWidget(project_btn) - layout.addRow("项目路径:", project_layout) + project_layout.addWidget(QtWidgets.QLabel("项目路径:")) + project_input = widgets.ModernLineEdit(read_only=True) + project_input.setText("D:/Personal/Document/maya/SuperRiggingEditor/files/data/MetaHuman") + project_layout.addWidget(project_input) + project_layout.addWidget(widgets.IconButton("folder.png", "加载...")) + settings_layout.addLayout(project_layout) # 预设文件 preset_layout = QtWidgets.QHBoxLayout() - self.preset_edit = QtWidgets.QLineEdit() - preset_btn = IconButton("target.png", "选择预设文件") - preset_btn.clicked.connect(self.browse_preset) - preset_layout.addWidget(self.preset_edit) - preset_layout.addWidget(preset_btn) - layout.addRow("预设文件:", preset_layout) + preset_layout.addWidget(QtWidgets.QLabel("预设文件:")) + preset_input = widgets.ModernLineEdit(read_only=True) + preset_layout.addWidget(preset_input) + preset_layout.addWidget(widgets.IconButton("folder.png", "加载...")) + settings_layout.addLayout(preset_layout) - # 数据分层 - layer_layout = QtWidgets.QHBoxLayout() - self.layer_combo = QtWidgets.QComboBox() - self.layer_combo.addItems(["行为"]) - self.override_check = QtWidgets.QCheckBox("覆盖表情") - layer_layout.addWidget(self.layer_combo) - layer_layout.addWidget(self.override_check) - layout.addRow("数据分层:", layer_layout) + # 数据层设置 + data_layout = QtWidgets.QHBoxLayout() + data_layout.addWidget(QtWidgets.QLabel("数据层:")) + data_combo = QtWidgets.QComboBox() + data_combo.addItem("无效(未定义)") + data_layout.addWidget(data_combo) + data_layout.addWidget(QtWidgets.QCheckBox("检查清理")) + settings_layout.addLayout(data_layout) - return group + # 描述信息 + desc_group = QtWidgets.QGroupBox("描述") + desc_layout = QtWidgets.QGridLayout() - def create_skeleton_tools(self): - """创建骨架工具""" - group = QtWidgets.QGroupBox("骨架工具") - layout = QtWidgets.QHBoxLayout(group) + desc_fields = [ + ("名称:", ""), + ("原型:", "亚洲人"), + ("性别:", "女性"), + ("年龄:", "18"), + ("变换单位:", "厘米"), + ("旋转单位:", "角度"), + ("向上轴:", "Z轴向上"), + ("LOD升级:", "0") + ] - # 清空选项 - clear_btn = IconButton("delete.png", "清空选项") - clear_btn.clicked.connect(self.clear_options) - layout.addWidget(clear_btn) + for i, (label, default) in enumerate(desc_fields): + desc_layout.addWidget(QtWidgets.QLabel(label), i, 0) + if i in [1, 2, 4, 5, 6]: # 这些字段使用下拉框 + combo = QtWidgets.QComboBox() + combo.addItem(default) + desc_layout.addWidget(combo, i, 1) + else: # 其他字段使用输入框 + input_widget = widgets.ModernLineEdit() + input_widget.setText(default) + desc_layout.addWidget(input_widget, i, 1) - # 导入骨架 - import_btn = IconButton("HIKCharacterToolSkeleton.png", "导入骨架") - import_btn.clicked.connect(self.import_skeleton) - layout.addWidget(import_btn) + desc_group.setLayout(desc_layout) + settings_layout.addWidget(desc_group) - # 创建骨架 - create_btn = IconButton("HIKcreateControlRig.png", "创建骨架") - create_btn.clicked.connect(self.create_skeleton) - layout.addWidget(create_btn) + # 底部按钮 + bottom_layout = QtWidgets.QHBoxLayout() + btn = widgets.IconButton("clear.png", "清空预览") + btn.clicked.connect(lambda checked=False, cb=rigging_utils.clear_preview: cb()) + bottom_layout.addWidget(btn) + bottom_layout.addWidget(widgets.IconButton("import.png", "导入预览")) + bottom_layout.addWidget(widgets.IconButton("create.png", "创建预览")) + settings_layout.addLayout(bottom_layout) - layout.addStretch() - return group + layout.addLayout(settings_layout) - # DNA功能回调 - def on_dna_selected(self, dna_path): - """DNA文件选中""" - from scripts.utils import rigging_utils - rigging_utils.load_dna(dna_path) + def _on_scale_changed(self, value): + """缩放值改变时更新按钮大小""" + self.current_scale = value + self.scale_label.setText(f"{value}") + self._update_button_size() - def export_settings(self): - """导出设置""" - from scripts.utils import rigging_utils - rigging_utils.export_settings() - - def import_settings(self): - """导入设置""" - from scripts.utils import rigging_utils - rigging_utils.import_settings() - - # 资产功能回调 - def browse_project(self): - """浏览项目路径""" - path = QtWidgets.QFileDialog.getExistingDirectory( - self, "选择项目路径", os.path.expanduser("~")) - if path: - self.project_edit.setText(path) + def _update_button_size(self): + """更新所有DNA按钮的大小""" + size = int(self.current_scale * 1.5) # 将滑块值转换为合适的按钮大小 + for btn in self.dna_buttons: + btn.setFixedSize(size, size) - def browse_preset(self): - """浏览预设文件""" - file_path, _ = QtWidgets.QFileDialog.getOpenFileName( - self, "选择预设文件", os.path.expanduser("~"), - "预设文件 (*.json *.preset)") - if file_path: - self.preset_edit.setText(file_path) + def _create_dna_button(self, dna_file, img_file): + """创建DNA按钮""" + btn = QtWidgets.QPushButton() + btn.setFixedSize(int(self.current_scale * 1.5), int(self.current_scale * 1.5)) + + # 设置图标 + if os.path.exists(img_file): + icon = QtGui.QIcon(img_file) + else: + # 使用默认图标 + default_img = os.path.join(data.DNA_IMG_PATH, "Default.png") + if os.path.exists(default_img): + icon = QtGui.QIcon(default_img) + else: + # 如果默认图标也不存在,使用系统默认图标 + icon = QtGui.QIcon.fromTheme("image-missing") + + btn.setIcon(icon) + btn.setIconSize(QtCore.QSize(int(self.current_scale * 1.4), int(self.current_scale * 1.4))) + + # 设置工具提示为DNA文件名 + btn.setToolTip(os.path.basename(dna_file)) + + # 点击事件 + btn.clicked.connect(lambda: self._on_dna_clicked(dna_file)) + + return btn + + def refresh_dna_browser(self): + """刷新DNA浏览器""" + # 清除现有按钮 + for btn in self.dna_buttons: + self.dna_flow.removeWidget(btn) + btn.deleteLater() + self.dna_buttons.clear() + + # 获取DNA和图片文件 + dna_path = data.DNA_FILE_PATH + img_path = data.DNA_IMG_PATH + + if not os.path.exists(dna_path) or not os.path.exists(img_path): + return - # 骨架工具回调 - def clear_options(self): - """清空选项""" - from scripts.utils import rigging_utils - rigging_utils.clear_options() + # 加载DNA文件和对应的预览图 + dna_files = [f for f in os.listdir(dna_path) if f.endswith('.dna')] - def import_skeleton(self): - """导入骨架""" - from scripts.utils import rigging_utils - rigging_utils.import_skeleton() - - def create_skeleton(self): - """创建骨架""" - from scripts.utils import rigging_utils - rigging_utils.create_skeleton() \ No newline at end of file + for i, dna_file in enumerate(dna_files): + base_name = os.path.splitext(dna_file)[0] + img_file = os.path.join(img_path, f"{base_name}.png") + dna_file_path = os.path.join(dna_path, dna_file) + + if os.path.exists(img_file): + btn = self._create_dna_button(dna_file_path, img_file) + self.dna_buttons.append(btn) + + # 添加到网格布局 + row = i // 4 # 每行4个按钮 + col = i % 4 + self.dna_flow.addWidget(btn, row, col) + + def _on_dna_clicked(self, dna_file): + """DNA按钮点击事件""" + try: + rigging_utils.load_dna(dna_file) + except Exception as e: + cmds.warning(f"加载DNA失败: {str(e)}") \ No newline at end of file diff --git a/scripts/ui/rigging_utils.py b/scripts/ui/rigging_utils.py new file mode 100644 index 0000000..fa9ba59 --- /dev/null +++ b/scripts/ui/rigging_utils.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +#===================================== 1. Module Imports ===================================== +import maya.cmds as cmds +import maya.mel as mel +import os +from scripts.config import data + +try: + from PySide2 import QtCore, QtGui, QtWidgets + from shiboken2 import wrapInstance +except ImportError: + try: + from PySide6 import QtCore, QtGui, QtWidgets + from shiboken6 import wrapInstance + except ImportError: + try: + from PySide import QtCore, QtGui, QtWidgets + from shiboken import wrapInstance + except ImportError as e: + QtCore = QtGui = QtWidgets = None + wrapInstance = None + +#===================================== 2. Rigging Manager Class ===================================== +# 菜单 +@staticmethod +def export_skin(): pass + +@staticmethod +def import_skin(): pass + +@staticmethod +def copy_skin(): pass + +@staticmethod +def create_rbf_deformer(): pass + +@staticmethod +def quick_bind_clothing(): pass + +@staticmethod +def clone_blendshape(): pass + +@staticmethod +def transfer_uv_order(): pass + +@staticmethod +def create_face_controller(): pass + +@staticmethod +def extract_52bs(): pass + +@staticmethod +def fix_joint_orientation(): pass + +@staticmethod +def create_body_controller(): pass + +@staticmethod +def import_face_animation(): pass + +@staticmethod +def import_body_animation(): pass + +# 预设 +@staticmethod +def update_dna_button(): pass + +@staticmethod +def export_preset(): pass + +@staticmethod +def import_preset(): pass + +@staticmethod +def load_project_dna(): pass + +@staticmethod +def load_preset(): pass + +@staticmethod +def data_layer(): pass + +@staticmethod +def overwirte_description(): pass + +# 描述 +@staticmethod +def create_description(): pass + +@staticmethod +def clear_preview(): pass + +@staticmethod +def import_skeleton(): pass + +@staticmethod +def create_binding(): pass + +@staticmethod +def create_skin(): pass + +@staticmethod +def import_preview(): pass + +@staticmethod +def create_preview(): pass + +@staticmethod +def load_dna(): pass + diff --git a/scripts/ui/style.qss b/scripts/ui/style.qss index a69d37c..74f33e8 100644 --- a/scripts/ui/style.qss +++ b/scripts/ui/style.qss @@ -1,28 +1,33 @@ -/* 全局 QPushButton 样式 */ +/* 现代深色主题 */ +* { + font-family: "Segoe UI", "Microsoft YaHei"; + font-size: 9pt; +} + +/* 全局 QPushButton 样式 */ QPushButton { - background-color: #2A2A2A; - color: #CCCCCC; + background-color: #2d2d2d; + color: #e0e0e0; + border: 1px solid #3d3d3d; border-radius: 3px; - padding: 5px; - font-weight: bold; - min-width: 80px; - border: 1px solid #444444; + padding: 4px 12px; + min-height: 22px; } QPushButton:hover { - background-color: #3A3A3A; - border-color: #555555; + background-color: #3d3d3d; + border-color: #4d4d4d; } QPushButton:pressed { - background-color: #1A1A1A; - border-color: #333333; + background-color: #1d1d1d; + border-color: #0078d4; } QPushButton:disabled { - background-color: #1A1A1A; - border-color: #333333; + background-color: #252525; color: #666666; + border-color: #2d2d2d; } /* 单独的消息按钮样式(可选) */ @@ -50,15 +55,14 @@ QPushButton.message-button:pressed { /* 主窗口样式 */ QMainWindow { - background-color: #333333; - color: #CCCCCC; + background-color: #1e1e1e; + color: #e0e0e0; } /* 菜单栏样式 */ QMenuBar { - background-color: #2A2A2A; - color: #CCCCCC; - border-bottom: 1px solid #222222; + background-color: #1e1e1e; + color: #e0e0e0; } QMenuBar::item { @@ -67,7 +71,7 @@ QMenuBar::item { } QMenuBar::item:selected { - background-color: #444444; + background-color: #2d2d2d; } QMenuBar::item:pressed { @@ -108,25 +112,30 @@ QToolButton:disabled { /* 标签页样式 */ QTabWidget::pane { - border: 1px solid #222222; - background-color: #333333; + border: 1px solid #2d2d2d; + background: #1e1e1e; + border-radius: 3px; } QTabBar::tab { - background-color: #333333; - border: 1px solid #444444; - color: #CCCCCC; - padding: 5px 10px; + background: #252525; + border: 1px solid #2d2d2d; + border-bottom: none; + border-top-left-radius: 4px; + border-top-right-radius: 4px; min-width: 80px; + padding: 4px 12px; + margin-right: 2px; + color: #e0e0e0; } QTabBar::tab:selected { - background-color: #444444; - border-bottom-color: #555555; + background: #2d2d2d; + border-bottom: 2px solid #0078d4; } QTabBar::tab:hover { - background-color: #444444; + background: #303030; } QTabBar::tab:pressed { @@ -135,36 +144,54 @@ QTabBar::tab:pressed { /* 列表和树形控件样式 */ QTreeView, QListView { - background-color: #2A2A2A; - border: 1px solid #222222; - color: #CCCCCC; + background-color: #252525; + border: 1px solid #2d2d2d; + border-radius: 3px; } -QTreeView::item:hover, QListView::item:hover { - background-color: #3A3A3A; +QTreeView::item { + padding: 4px; } -QTreeView::item:selected, QListView::item:selected { - background-color: #444444; +QTreeView::item:selected { + background-color: #0078d4; } /* 输入框样式 */ QLineEdit { - background-color: #2A2A2A; - border: 1px solid #222222; - border-radius: 2px; - color: #CCCCCC; - padding: 3px; + background-color: #252525; + color: #e0e0e0; + border: 1px solid #3d3d3d; + border-radius: 3px; + padding: 2px 6px; + min-height: 20px; +} + +QLineEdit:focus { + border-color: #0078d4; +} + +QLineEdit:read-only { + background-color: #1e1e1e; + border-color: #2d2d2d; } /* 下拉框样式 */ QComboBox { - background-color: #2A2A2A; - border: 1px solid #222222; - border-radius: 2px; - color: #CCCCCC; - padding: 3px; - min-width: 100px; + background-color: #252525; + color: #e0e0e0; + border: 1px solid #3d3d3d; + border-radius: 3px; + padding: 2px 6px; + min-height: 20px; +} + +QComboBox:hover { + border-color: #4d4d4d; +} + +QComboBox:focus { + border-color: #0078d4; } QComboBox::drop-down { @@ -173,28 +200,27 @@ QComboBox::drop-down { } QComboBox::down-arrow { - border-image: url(:/resources/icons/down_arrow.png); + border-image: url(resources/icons/down_arrow.png); width: 12px; height: 12px; } /* 滚动条样式 */ QScrollBar:vertical { - background: #373737; - width: 16px; + background: #1e1e1e; + width: 10px; margin: 0; - border-radius: 5px; } QScrollBar::handle:vertical { - background: #4b5cc4; + background: #3d3d3d; min-height: 16px; border-radius: 5px; margin: 2px; } QScrollBar::handle:vertical:hover { - background: #5b6cd4; + background: #4d4d4d; } QScrollBar::handle:vertical:pressed { @@ -206,21 +232,20 @@ QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { } QScrollBar:horizontal { - background: #373737; - height: 16px; + background: #1e1e1e; + height: 10px; margin: 0; - border-radius: 5px; } QScrollBar::handle:horizontal { - background: #4b5cc4; + background: #3d3d3d; min-width: 16px; border-radius: 5px; margin: 2px; } QScrollBar::handle:horizontal:hover { - background: #5b6cd4; + background: #4d4d4d; } QScrollBar::handle:horizontal:pressed { @@ -233,30 +258,34 @@ QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal { /* 分组框样式 */ QGroupBox { - border: 1px solid #222222; - border-radius: 3px; - margin-top: 6px; - padding-top: 6px; - color: #CCCCCC; + background-color: #252525; + border: 1px solid #2d2d2d; + border-radius: 4px; + margin-top: 8px; + padding-top: 8px; + font-weight: bold; } QGroupBox::title { - left: 7px; - padding: 0px 3px; + margin-top: 8px; + margin-left: 8px; + padding: 0 4px; + color: #0078d4; } /* 状态栏样式 */ QStatusBar { - background-color: #333333; - color: #CCCCCC; + background-color: #1e1e1e; + color: #e0e0e0; } /* 工具提示样式 */ QToolTip { - background-color: #2A2A2A; - border: 1px solid #222222; - color: #CCCCCC; - padding: 3px; + background-color: #252525; + color: #e0e0e0; + border: 1px solid #3d3d3d; + border-radius: 3px; + padding: 4px; } /* DNA 浏览器样式 */ @@ -283,18 +312,16 @@ QListWidget::item:selected { /* 菜单样式 */ QMenu { - background-color: #2A2A2A; - border: 1px solid #444444; - color: #CCCCCC; + background-color: #252525; + border: 1px solid #3d3d3d; } QMenu::item { - background-color: transparent; - padding: 5px 20px; + padding: 4px 20px; } QMenu::item:selected { - background-color: #444444; + background-color: #3d3d3d; } QMenu::item:pressed { @@ -308,7 +335,8 @@ QDialog { } QLabel { - color: #E0E0E0; + color: #CCCCCC; + padding: 0px 2px; } /* 基础按钮样式 - 用于安装界面 */ @@ -337,3 +365,47 @@ QLabel { background-color: #404040; color: #E0E0E0; } + +/* 功能按钮样式 */ +QPushButton, QToolButton { + background-color: #404040; + color: #E0E0E0; + border: 1px solid #505050; + border-radius: 5px; + padding: 5px; + min-width: 80px; + font-weight: bold; +} + +QPushButton:hover, QToolButton:hover { + background-color: #505050; + border-color: #606060; +} + +QPushButton:pressed, QToolButton:pressed { + background-color: #303030; + border-color: #0078d4; +} + +QPushButton:disabled, QToolButton:disabled { + background-color: #353535; + color: #808080; + border-color: #404040; +} + +/* 工具栏按钮样式 */ +QToolBar QToolButton { + background-color: transparent; + border: none; + border-radius: 2px; + padding: 4px; + min-width: 24px; +} + +QToolBar QToolButton:hover { + background-color: #505050; +} + +QToolBar QToolButton:pressed { + background-color: #303030; +} diff --git a/scripts/ui/widgets.py b/scripts/ui/widgets.py index e99f787..5ab8d9f 100644 --- a/scripts/ui/widgets.py +++ b/scripts/ui/widgets.py @@ -6,185 +6,127 @@ import maya.cmds as cmds import maya.mel as mel import sys import os - from scripts.config import data try: from PySide2 import QtCore, QtGui, QtWidgets from shiboken2 import wrapInstance - print("从PySide2加载Qt和shiboken2") except ImportError: try: from PySide6 import QtCore, QtGui, QtWidgets from shiboken6 import wrapInstance - print("从PySide6加载Qt和shiboken6") except ImportError: try: from PySide import QtCore, QtGui, QtWidgets from shiboken import wrapInstance - print("从PySide加载Qt和shiboken") except ImportError as e: - print(f"Qt加载失败: {str(e)}") QtCore = QtGui = QtWidgets = None wrapInstance = None - #===================================== 2. Base Widget Class ===================================== class BaseWidget(QtWidgets.QWidget): """基础控件类""" def __init__(self, parent=None): super(BaseWidget, self).__init__(parent) + self.SPACING = 4 + self.MARGINS = 6 self.setup_ui() def setup_ui(self): - """设置UI""" pass +class IconButton(QtWidgets.QPushButton): + """图标按钮""" + def __init__(self, icon_name, text, callback=None, parent=None): + super(IconButton, self).__init__(text, parent) + + # 设置图标 + if icon_name: + icon_path = os.path.join(data.ICONS_PATH, icon_name) + if os.path.exists(icon_path): + self.setIcon(QtGui.QIcon(icon_path)) + + # 连接回调 + if callback: + self.clicked.connect(lambda checked=False, cb=callback: cb()) + +class LODGroup(QtWidgets.QGroupBox): + """LOD分组控件""" + def __init__(self, lod_index, parent=None): + super(LODGroup, self).__init__(parent) + self.lod_index = lod_index + self.setTitle(f"LOD{lod_index}") + self.setup_ui() + + def setup_ui(self): + layout = QtWidgets.QVBoxLayout(self) + layout.setSpacing(4) + layout.setContentsMargins(6, 6, 6, 6) + class SearchLineEdit(QtWidgets.QLineEdit): """搜索输入框""" def __init__(self, parent=None): super(SearchLineEdit, self).__init__(parent) - self.setup_ui() - - def setup_ui(self): self.setPlaceholderText("搜索...") self.setClearButtonEnabled(True) - self.setMinimumWidth(200) -class LoadButton(QtWidgets.QPushButton): - """加载按钮""" - def __init__(self, text="", icon_name="target.png", parent=None): - super(LoadButton, self).__init__(parent) - self.setup_ui(text, icon_name) - - def setup_ui(self, text, icon_name): - if text: - self.setText(text) - icon_path = os.path.join(data.ICONS_PATH, icon_name) - if os.path.exists(icon_path): - self.setIcon(QtGui.QIcon(icon_path)) - self.setFixedSize(25, 25) - -class ModelInput(QtWidgets.QWidget): - """模型输入组件""" - def __init__(self, label="", parent=None): - super(ModelInput, self).__init__(parent) - self.label = label +class BlendShapeList(QtWidgets.QGroupBox): + """BlendShape列表控件""" + def __init__(self, title, parent=None): + super(BlendShapeList, self).__init__(title, parent) self.setup_ui() def setup_ui(self): - layout = QtWidgets.QHBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(2) - - # 标签 - if self.label: - label = QtWidgets.QLabel(self.label) - layout.addWidget(label) - - # 输入框 - self.line_edit = QtWidgets.QLineEdit() - layout.addWidget(self.line_edit) - - # 加载按钮 - self.load_btn = LoadButton() - layout.addWidget(self.load_btn) + layout = QtWidgets.QVBoxLayout(self) + self.list_widget = QtWidgets.QListWidget() + layout.addWidget(self.list_widget) -class LODGroup(QtWidgets.QGroupBox): - """LOD分组""" - def __init__(self, lod_index, parent=None): - super(LODGroup, self).__init__(parent) - self.lod_index = lod_index +class BlendShapeControls(QtWidgets.QGroupBox): + """BlendShape控制控件""" + def __init__(self, parent=None): + super(BlendShapeControls, self).__init__("控制", parent) self.setup_ui() def setup_ui(self): - self.setTitle(f"LOD{self.lod_index}") - - # 主布局 - main_layout = QtWidgets.QVBoxLayout(self) - - # 顶部工具栏 - toolbar = QtWidgets.QHBoxLayout() - toolbar.addStretch() - - # 删除按钮 - delete_btn = LoadButton(icon_name="delete.png") - delete_btn.clicked.connect(lambda: self.delete_lod()) - toolbar.addWidget(delete_btn) - - main_layout.addLayout(toolbar) - - # 模型输入列表 - self.model_list = QtWidgets.QVBoxLayout() - self.add_model_inputs() - main_layout.addLayout(self.model_list) - - def add_model_inputs(self): - """添加模型输入组件""" - # 根据LOD级别添加不同的输入组件 - model_types = self.get_model_types() - for model_type, label in model_types: - input_widget = ModelInput(label) - input_widget.load_btn.clicked.connect( - lambda checked, t=model_type: self.load_model(t)) - self.model_list.addWidget(input_widget) - - def get_model_types(self): - """获取当前LOD级别需要的模型类型""" - # 基础类型 - types = [ - ("head", "头部"), - ("teeth", "牙齿") - ] - - # 根据LOD级别添加额外类型 - if self.lod_index <= 3: - types.extend([ - ("eye_l", "左眼"), - ("eye_r", "右眼"), - ("iris", "虹膜"), - ("eyelash", "睫毛"), - ("eyelid", "眼睑") - ]) - - if self.lod_index <= 2: - types.append(("gums", "牙龈")) - - if self.lod_index <= 1: - types.append(("cartilage", "软骨")) - - if self.lod_index <= 2: - types.append(("body", "身体")) - - return types - - def load_model(self, model_type): - """加载模型""" - from scripts.utils import model_utils - model_utils.load_model(self.lod_index, model_type) - - def delete_lod(self): - """删除当前LOD""" - from scripts.utils import model_utils - model_utils.delete_lod(self.lod_index) + layout = QtWidgets.QVBoxLayout(self) + self.value_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal) + layout.addWidget(self.value_slider) -class IconButton(QtWidgets.QPushButton): - """图标按钮""" - def __init__(self, icon_name, text=""): - super(IconButton, self).__init__() - self.setText(text) - if icon_name: - icon_path = os.path.join(data.ICONS_PATH, icon_name) - if os.path.exists(icon_path): - self.setIcon(QtGui.QIcon(icon_path)) - self.setIconSize(QtCore.QSize(24, 24)) # 设置图标大小 - self.setMinimumHeight(32) # 设置最小高度 - # 设置文字在图标右侧 - self.setStyleSheet(""" - QPushButton { - text-align: left; - padding-left: 5px; - } - """) +class DNABrowser(QtWidgets.QGroupBox): + """DNA浏览器控件""" + dnaSelected = QtCore.Signal(str) + + def __init__(self, parent=None): + super(DNABrowser, self).__init__("DNA浏览器", parent) + self.setup_ui() + + def setup_ui(self): + layout = QtWidgets.QVBoxLayout(self) + self.list_widget = QtWidgets.QListWidget() + layout.addWidget(self.list_widget) + + def selectedItems(self): + """获取选中项""" + return self.list_widget.selectedItems() + + def itemSelectionChanged(self): + """选择变化信号""" + return self.list_widget.itemSelectionChanged() + + def connect(self, callback): + """连接选择变化信号""" + self.list_widget.itemSelectionChanged.connect(callback) + +class DescriptionWidget(QtWidgets.QGroupBox): + """描述控件""" + def __init__(self, parent=None): + super(DescriptionWidget, self).__init__("描述", parent) + self.setup_ui() + + def setup_ui(self): + layout = QtWidgets.QVBoxLayout(self) + self.text_edit = QtWidgets.QTextEdit() + self.text_edit.setReadOnly(True) + layout.addWidget(self.text_edit) class SliderWithValue(QtWidgets.QWidget): """带数值显示的滑块""" @@ -192,1385 +134,98 @@ class SliderWithValue(QtWidgets.QWidget): def __init__(self, min_val=0.0, max_val=1.0, default=0.0, parent=None): super(SliderWithValue, self).__init__(parent) - self.min_val = min_val - self.max_val = max_val - self.default = default - self.setup_ui() + self.setup_ui(min_val, max_val, default) - def setup_ui(self): + def setup_ui(self, min_val, max_val, default): layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) - # 滑块 self.slider = QtWidgets.QSlider(QtCore.Qt.Horizontal) - self.slider.setRange(0, 100) - self.slider.setValue(self.to_slider_value(self.default)) - self.slider.valueChanged.connect(self.on_slider_changed) + self.slider.setRange(int(min_val * 100), int(max_val * 100)) + self.slider.setValue(int(default * 100)) + + self.value_label = QtWidgets.QLabel(f"{default:.2f}") + self.value_label.setMinimumWidth(40) + layout.addWidget(self.slider) + layout.addWidget(self.value_label) - # 数值显示 - self.value_spin = QtWidgets.QDoubleSpinBox() - self.value_spin.setRange(self.min_val, self.max_val) - self.value_spin.setValue(self.default) - self.value_spin.setSingleStep(0.01) - self.value_spin.valueChanged.connect(self.on_spin_changed) - layout.addWidget(self.value_spin) + self.slider.valueChanged.connect(self._on_slider_changed) - def to_slider_value(self, value): - """转换为滑块值""" - return int((value - self.min_val) * 100 / (self.max_val - self.min_val)) - - def to_real_value(self, slider_value): - """转换为实际值""" - return self.min_val + slider_value * (self.max_val - self.min_val) / 100 - - def on_slider_changed(self, value): - real_value = self.to_real_value(value) - self.value_spin.setValue(real_value) - self.valueChanged.emit(real_value) - - def on_spin_changed(self, value): - self.slider.setValue(self.to_slider_value(value)) - self.valueChanged.emit(value) + def _on_slider_changed(self, value): + float_value = value / 100.0 + self.value_label.setText(f"{float_value:.2f}") + self.valueChanged.emit(float_value) -class DNABrowser(QtWidgets.QWidget): - """DNA浏览器""" - dnaSelected = QtCore.Signal(str) # 发送选中的DNA文件路径 - +class ModernTabWidget(QtWidgets.QTabWidget): + """现代风格标签页控件""" def __init__(self, parent=None): - super(DNABrowser, self).__init__(parent) - self.setup_ui() - self.load_dna_files() + super(ModernTabWidget, self).__init__(parent) + self.setTabBar(TabBar()) + self.setDocumentMode(True) + self.setMovable(True) - def setup_ui(self): - layout = QtWidgets.QVBoxLayout(self) - - # 工具栏 - toolbar = QtWidgets.QHBoxLayout() - - # 缩放滑块 - self.zoom_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal) - self.zoom_slider.setRange(50, 200) - self.zoom_slider.setValue(100) - self.zoom_slider.valueChanged.connect(self.on_zoom_changed) - toolbar.addWidget(QtWidgets.QLabel("缩放:")) - toolbar.addWidget(self.zoom_slider) - - layout.addLayout(toolbar) - - # DNA网格视图 - self.grid_view = QtWidgets.QListView() - self.grid_view.setViewMode(QtWidgets.QListView.IconMode) - self.grid_view.setIconSize(QtCore.QSize(100, 100)) - self.grid_view.setSpacing(10) - self.grid_view.setResizeMode(QtWidgets.QListView.Adjust) - self.grid_view.clicked.connect(self.on_dna_selected) - - self.model = QtGui.QStandardItemModel() - self.grid_view.setModel(self.model) - - layout.addWidget(self.grid_view) - - def load_dna_files(self): - """加载DNA文件""" - self.model.clear() - - # 从DNA目录加载文件 - dna_path = data.DNA_FILE_PATH - img_path = data.DNA_IMG_PATH - - if os.path.exists(dna_path): - for file in os.listdir(dna_path): - if file.endswith(".dna"): - # 创建项 - item = QtGui.QStandardItem() - item.setText(os.path.splitext(file)[0]) - item.setData(os.path.join(dna_path, file), QtCore.Qt.UserRole) - - # 设置图标 - img_file = os.path.join(img_path, f"{os.path.splitext(file)[0]}.png") - if os.path.exists(img_file): - item.setIcon(QtGui.QIcon(img_file)) - - self.model.appendRow(item) - - def on_zoom_changed(self, value): - """缩放改变""" - size = value - self.grid_view.setIconSize(QtCore.QSize(size, size)) - - def on_dna_selected(self, index): - """DNA选中""" - item = self.model.itemFromIndex(index) - dna_path = item.data(QtCore.Qt.UserRole) - self.dnaSelected.emit(dna_path) - -class DescriptionWidget(QtWidgets.QGroupBox): - """描述信息组件""" +class TabBar(QtWidgets.QTabBar): + """自定义标签栏""" def __init__(self, parent=None): - super(DescriptionWidget, self).__init__("描述", parent) - self.setup_ui() - - def setup_ui(self): - layout = QtWidgets.QFormLayout(self) - - # 名称 - self.name_edit = QtWidgets.QLineEdit() - layout.addRow("名称:", self.name_edit) - - # 原型 - self.prototype_combo = QtWidgets.QComboBox() - self.prototype_combo.addItems(["亚洲人", "黑人", "高加素人", "拉美裔", "外星人", "其他"]) - layout.addRow("原型:", self.prototype_combo) - - # 性别 - self.gender_combo = QtWidgets.QComboBox() - self.gender_combo.addItems(["男性", "女性", "其他"]) - layout.addRow("性别:", self.gender_combo) - - # 年龄 - self.age_spin = QtWidgets.QSpinBox() - self.age_spin.setRange(0, 100) - self.age_spin.setValue(24) - layout.addRow("年龄:", self.age_spin) - - # 变换单位 - self.unit_combo = QtWidgets.QComboBox() - self.unit_combo.addItems(["厘米", "米"]) - layout.addRow("变换单位:", self.unit_combo) - - # 旋转单位 - self.rotation_combo = QtWidgets.QComboBox() - self.rotation_combo.addItems(["角度", "弧度"]) - layout.addRow("旋转单位:", self.rotation_combo) - - # 坐标向上 - self.up_combo = QtWidgets.QComboBox() - self.up_combo.addItems(["Y轴向上", "Z轴向上"]) - layout.addRow("坐标向上:", self.up_combo) - - # LOD计数 - self.lod_spin = QtWidgets.QSpinBox() - self.lod_spin.setRange(1, 8) - self.lod_spin.setValue(8) - layout.addRow("LOD计数:", self.lod_spin) + super(TabBar, self).__init__(parent) + self.setDrawBase(False) + self.setExpanding(False) -class BlendShapeList(QtWidgets.QWidget): - """BlendShape列表组件""" - def __init__(self, title="BlendShapes", parent=None): - super(BlendShapeList, self).__init__(parent) - self.title = title - self.setup_ui() - - def setup_ui(self): - layout = QtWidgets.QVBoxLayout(self) - - # 标题栏 - title_layout = QtWidgets.QHBoxLayout() - title_label = QtWidgets.QLabel(self.title) - self.count_label = QtWidgets.QLabel("[0/0]") - title_layout.addWidget(title_label) - title_layout.addWidget(self.count_label) - title_layout.addStretch() - layout.addLayout(title_layout) - - # 搜索栏 - self.search_edit = SearchLineEdit() - self.search_edit.textChanged.connect(self.filter_items) - layout.addWidget(self.search_edit) - - # BS列表 - self.list_widget = QtWidgets.QListWidget() - self.list_widget.setSelectionMode(QtWidgets.QListWidget.ExtendedSelection) - layout.addWidget(self.list_widget) - - def filter_items(self, text): - """过滤列表项""" - for i in range(self.list_widget.count()): - item = self.list_widget.item(i) - item.setHidden(text.lower() not in item.text().lower()) - - def update_count(self): - """更新计数""" - visible = sum(1 for i in range(self.list_widget.count()) - if not self.list_widget.item(i).isHidden()) - total = self.list_widget.count() - self.count_label.setText(f"[{visible}/{total}]") - -class BlendShapeControls(QtWidgets.QWidget): - """BlendShape控制组件""" +class ModernToolBar(QtWidgets.QToolBar): + """现代风格工具栏""" def __init__(self, parent=None): - super(BlendShapeControls, self).__init__(parent) - self.setup_ui() + super(ModernToolBar, self).__init__(parent) + self.setMovable(False) + self.setFloatable(False) + self.setIconSize(QtCore.QSize(16, 16)) - def setup_ui(self): - layout = QtWidgets.QVBoxLayout(self) + def addAction(self, icon, text, callback): + """添加工具栏按钮""" + action = QtWidgets.QAction(text, self) + if icon: + icon_path = os.path.join(data.ICONS_PATH, icon) + if os.path.exists(icon_path): + action.setIcon(QtGui.QIcon(icon_path)) + action.triggered.connect(callback) + super(ModernToolBar, self).addAction(action) + return action - # 工具栏 - toolbar = QtWidgets.QHBoxLayout() - - # 恢复表情按钮 - reset_btn = IconButton("reset.png", "恢复表情") - reset_btn.clicked.connect(self.reset_blendshapes) - toolbar.addWidget(reset_btn) - - # 混合筛选按钮 - filter_btn = IconButton("blendRaw.png", "混合筛选") - filter_btn.clicked.connect(self.filter_blendshapes) - toolbar.addWidget(filter_btn) - - # 分类按钮组 - for i in range(1, 7): - btn = QtWidgets.QPushButton(str(i) if i > 1 else "全部") - btn.setCheckable(True) - btn.setFixedWidth(40) - toolbar.addWidget(btn) - - layout.addLayout(toolbar) - - # 数值控制 - value_layout = QtWidgets.QHBoxLayout() - - self.value_slider = SliderWithValue(min_val=0.0, max_val=1.0, default=0.01) - value_layout.addWidget(self.value_slider) - - self.all_check = QtWidgets.QCheckBox("全部") - value_layout.addWidget(self.all_check) - - layout.addLayout(value_layout) - - # 范围控制 - range_layout = QtWidgets.QHBoxLayout() - - range_plus = QtWidgets.QPushButton("范围 +") - range_minus = QtWidgets.QPushButton("范围 -") - range_layout.addWidget(range_plus) - range_layout.addWidget(range_minus) - - layout.addLayout(range_layout) - - def reset_blendshapes(self): - """重置所有BlendShape""" - from scripts.utils import adjust_utils - adjust_utils.reset_blendshapes() - - def filter_blendshapes(self): - """过滤BlendShape""" - from scripts.utils import adjust_utils - adjust_utils.filter_blendshapes() + def addStretch(self): + """添加弹性空间""" + spacer = QtWidgets.QWidget() + spacer.setSizePolicy(QtWidgets.QSizePolicy.Expanding, + QtWidgets.QSizePolicy.Preferred) + self.addWidget(spacer) -class BlendShapeTools(QtWidgets.QWidget): - """BlendShape工具组件""" +class GradientGroupBox(QtWidgets.QGroupBox): + """渐变背景分组框""" + def __init__(self, title, parent=None): + super(GradientGroupBox, self).__init__(title, parent) + +class ModernComboBox(QtWidgets.QComboBox): + """现代风格下拉框""" def __init__(self, parent=None): - super(BlendShapeTools, self).__init__(parent) - self.setup_ui() - - def setup_ui(self): - layout = QtWidgets.QVBoxLayout(self) - - # 工具按钮 - tools_layout = QtWidgets.QHBoxLayout() - - # 翻转 - flip_btn = IconButton("mirrorL.png", "翻转") - tools_layout.addWidget(flip_btn) - - # 镜像目标 - mirror_btn = IconButton("symmetry.png", "镜像目标") - tools_layout.addWidget(mirror_btn) - - # 查找翻转目标 - find_flip_btn = IconButton("mirrorR.png", "查找翻转目标") - tools_layout.addWidget(find_flip_btn) - - # 添加混合目标 - add_bs_btn = IconButton("blendShape.png", "添加混合目标") - tools_layout.addWidget(add_bs_btn) - - # 删除混合目标 - del_bs_btn = IconButton("blendShape.png", "删除混合目标") - tools_layout.addWidget(del_bs_btn) - - # 批量混合目标 - batch_bs_btn = IconButton("blendShape.png", "批量混合目标") - tools_layout.addWidget(batch_bs_btn) - - layout.addLayout(tools_layout) - - # 功能开关 - toggle_layout = QtWidgets.QHBoxLayout() - - toggles = [ - ("PSD", "psd.png"), - ("BSE", "blendShape.png"), - ("KEY", "centerCurrentTime.png"), - ("MIR", "mirrorR.png"), - ("ARK", "ARKit52.png"), - ("CTR", "ctrl_hide.png") - ] - - for text, icon in toggles: - btn = IconButton(icon, text) - btn.setCheckable(True) - toggle_layout.addWidget(btn) - - layout.addLayout(toggle_layout) - - # 表情控制 - expr_layout = QtWidgets.QHBoxLayout() - - expr_btns = [ - ("还原默认表情", "reset.png"), - ("选择选择表情", "expressions_current.png"), - ("写入当前表情", "expression.png"), - ("控制面板查找", "controller.png"), - ("选择关联关节", "kinJoint.png"), - ("写入镜像表情", "ctrl_hide.png") - ] - - for text, icon in expr_btns: - btn = IconButton(icon, text) - expr_layout.addWidget(btn) - - layout.addLayout(expr_layout) + super(ModernComboBox, self).__init__(parent) -class WeightEditorWidget(QtWidgets.QWidget): - """权重编辑器界面""" - - weightChanged = QtCore.Signal(str, float) # 权重变化信号(目标名称, 权重值) - - def __init__(self, parent=None): - super(WeightEditorWidget, self).__init__(parent) - self.bs_node = None - self.setup_ui() - - def setup_ui(self): - """创建界面""" - layout = QtWidgets.QVBoxLayout(self) - - # 顶部工具栏 - toolbar = QtWidgets.QHBoxLayout() - - # BlendShape节点选择 - self.bs_combo = QtWidgets.QComboBox() - self.bs_combo.currentTextChanged.connect(self.on_bs_changed) - toolbar.addWidget(self.bs_combo) - - # 刷新按钮 - refresh_btn = IconButton("refresh.png", "刷新") - refresh_btn.clicked.connect(self.refresh_weights) - toolbar.addWidget(refresh_btn) - - # 优化按钮 - optimize_btn = IconButton("optimize.png", "优化权重") - optimize_btn.clicked.connect(self.optimize_weights) - toolbar.addWidget(optimize_btn) - - # 分析按钮 - analyze_btn = IconButton("analyze.png", "分析权重") - analyze_btn.clicked.connect(self.analyze_weights) - toolbar.addWidget(analyze_btn) - - layout.addLayout(toolbar) - - # 搜索栏 - self.search_edit = SearchLineEdit() - self.search_edit.textChanged.connect(self.filter_targets) - layout.addWidget(self.search_edit) - - # 权重列表 - self.weight_list = QtWidgets.QTreeWidget() - self.weight_list.setHeaderLabels(["目标", "权重"]) - self.weight_list.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) - self.weight_list.itemChanged.connect(self.on_item_changed) - layout.addWidget(self.weight_list) - - # 底部工具栏 - bottom_toolbar = QtWidgets.QHBoxLayout() - - # 批量设置 - bottom_toolbar.addWidget(QtWidgets.QLabel("批量设置:")) - self.batch_spin = QtWidgets.QDoubleSpinBox() - self.batch_spin.setRange(0, 1) - self.batch_spin.setSingleStep(0.1) - bottom_toolbar.addWidget(self.batch_spin) - - apply_btn = IconButton("apply.png", "应用") - apply_btn.clicked.connect(self.apply_batch_weight) - bottom_toolbar.addWidget(apply_btn) - - bottom_toolbar.addStretch() - - # 快捷按钮 - for value in [0, 0.5, 1.0]: - btn = QtWidgets.QPushButton(str(value)) - btn.clicked.connect(lambda x, v=value: self.quick_set_weight(v)) - bottom_toolbar.addWidget(btn) - - layout.addLayout(bottom_toolbar) - - def set_blendshape(self, bs_node): - """设置BlendShape节点""" - self.bs_node = bs_node - self.refresh_weights() - - def refresh_weights(self): - """刷新权重列表""" - from scripts.utils import adjust_utils - - self.weight_list.clear() - - if not self.bs_node: - return - - # 获取所有目标 - targets = adjust_utils.bs_manager.get_all_targets(self.bs_node) - - # 添加目标项 - for target in targets: - weight = adjust_utils.bs_manager.get_weight(self.bs_node, target) - item = QtWidgets.QTreeWidgetItem([target, str(weight)]) - item.setFlags(item.flags() | QtCore.Qt.ItemIsEditable) - self.weight_list.addTopLevelItem(item) - - def filter_targets(self, text): - """过滤目标""" - for i in range(self.weight_list.topLevelItemCount()): - item = self.weight_list.topLevelItem(i) - item.setHidden(text.lower() not in item.text(0).lower()) - - def on_item_changed(self, item, column): - """项目变化""" - if column == 1: # 权重列 - try: - target = item.text(0) - weight = float(item.text(1)) - self.weightChanged.emit(target, weight) - except ValueError: - pass - - def on_bs_changed(self, bs_node): - """BlendShape节点变化""" - self.set_blendshape(bs_node) - - def optimize_weights(self): - """优化权重""" - from scripts.utils import adjust_utils - - if self.bs_node: - adjust_utils.optimize_blendshape_weights(self.bs_node) - self.refresh_weights() - - def analyze_weights(self): - """分析权重""" - from scripts.utils import adjust_utils - - if self.bs_node: - results = adjust_utils.analyze_blendshape_weights(self.bs_node) - if results: - # 显示分析结果 - msg = ( - f"总目标数: {results['total_targets']}\n" - f"活动目标: {results['active_targets']}\n" - f"未使用目标: {results['unused_targets']}\n" - f"全权重目标: {results['full_weight_targets']}\n" - f"部分权重目标: {results['partial_weight_targets']}\n\n" - "权重分布:\n" - ) - - for weight, count in sorted(results["weight_distribution"].items()): - msg += f" {weight:.1f}: {count}\n" - - QtWidgets.QMessageBox.information(self, "权重分析", msg) - - def apply_batch_weight(self): - """应用批量权重""" - from scripts.utils import adjust_utils - - weight = self.batch_spin.value() - - # 获取选中项 - items = self.weight_list.selectedItems() - if not items: - return - - # 设置权重 - for item in items: - target = item.text(0) - adjust_utils.bs_manager.set_weight(self.bs_node, target, weight) - item.setText(1, str(weight)) - - def quick_set_weight(self, weight): - """快速设置权重""" - self.batch_spin.setValue(weight) - self.apply_batch_weight() +class ModernLabel(QtWidgets.QLabel): + """现代风格标签""" + def __init__(self, text, parent=None): + super(ModernLabel, self).__init__(text, parent) -class ExpressionPreviewWidget(QtWidgets.QWidget): - """表情预览组件""" - def __init__(self, parent=None): - super(ExpressionPreviewWidget, self).__init__(parent) - self.expr_set = None - self.setup_ui() - - def setup_ui(self): - """创建界面""" - layout = QtWidgets.QVBoxLayout(self) - - # 顶部工具栏 - toolbar = QtWidgets.QHBoxLayout() - - # 表情集选择 - self.expr_combo = QtWidgets.QComboBox() - self.expr_combo.currentTextChanged.connect(self.on_expr_changed) - toolbar.addWidget(self.expr_combo) - - # 刷新按钮 - refresh_btn = IconButton("refresh.png", "刷新") - refresh_btn.clicked.connect(self.refresh_expressions) - toolbar.addWidget(refresh_btn) - - # 播放控制 - self.play_btn = IconButton("play.png", "播放") - self.play_btn.setCheckable(True) - self.play_btn.clicked.connect(self.toggle_play) - toolbar.addWidget(self.play_btn) - - # 循环播放 - self.loop_btn = IconButton("loop.png", "循环") - self.loop_btn.setCheckable(True) - toolbar.addWidget(self.loop_btn) - - layout.addLayout(toolbar) - - # 预览区域 - preview_group = QtWidgets.QGroupBox("预览") - preview_layout = QtWidgets.QVBoxLayout(preview_group) - - # 时间控制 - time_layout = QtWidgets.QHBoxLayout() - - self.time_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal) - self.time_slider.setRange(0, 100) - self.time_slider.valueChanged.connect(self.on_time_changed) - time_layout.addWidget(self.time_slider) - - self.time_spin = QtWidgets.QSpinBox() - self.time_spin.setRange(0, 100) - self.time_spin.valueChanged.connect(self.time_slider.setValue) - time_layout.addWidget(self.time_spin) - - preview_layout.addLayout(time_layout) - - # 权重控制 - weight_layout = QtWidgets.QHBoxLayout() - - self.weight_slider = SliderWithValue(min_val=0.0, max_val=1.0, default=1.0) - self.weight_slider.valueChanged.connect(self.on_weight_changed) - weight_layout.addWidget(self.weight_slider) - - preview_layout.addLayout(weight_layout) - - layout.addWidget(preview_group) - - # 表情列表 - list_group = QtWidgets.QGroupBox("表情列表") - list_layout = QtWidgets.QVBoxLayout(list_group) - - # 搜索栏 - self.search_edit = SearchLineEdit() - self.search_edit.textChanged.connect(self.filter_expressions) - list_layout.addWidget(self.search_edit) - - # 表情树 - self.expr_tree = QtWidgets.QTreeWidget() - self.expr_tree.setHeaderLabels(["表情", "权重"]) - self.expr_tree.itemChanged.connect(self.on_item_changed) - list_layout.addWidget(self.expr_tree) - - layout.addWidget(list_group) - - # 创建定时器 - self.play_timer = QtCore.QTimer() - self.play_timer.timeout.connect(self.update_time) - - def set_expression(self, expr_set): - """设置表情集""" - self.expr_set = expr_set - self.refresh_expressions() - - def refresh_expressions(self): - """刷新表情列表""" - from scripts.utils import adjust_utils - - self.expr_tree.clear() - - if not self.expr_set: - return - - # 获取表情数据 - target_data = cmds.getAttr(f"{self.expr_set}.targets") - for item in target_split(";"): - name, value = item.split(":") - tree_item = QtWidgets.QTreeWidgetItem([name, value]) - tree_item.setFlags(tree_item.flags() | QtCore.Qt.ItemIsEditable) - self.expr_tree.addTopLevelItem(tree_item) - - def filter_expressions(self, text): - """过滤表情""" - for i in range(self.expr_tree.topLevelItemCount()): - item = self.expr_tree.topLevelItem(i) - item.setHidden(text.lower() not in item.text(0).lower()) - - def on_expr_changed(self, expr_set): - """表情集变化""" - self.set_expression(expr_set) - - def on_item_changed(self, item, column): - """项目变化""" - if column == 1: # 权重列 - try: - target = item.text(0) - weight = float(item.text(1)) - self.apply_expression_weight(target, weight) - except ValueError: - pass - - def on_time_changed(self, time): - """时间变化""" - self.time_spin.setValue(time) - self.update_preview() - - def on_weight_changed(self, weight): - """权重变化""" - self.update_preview() - - def toggle_play(self, checked): - """切换播放状态""" - if checked: - self.play_timer.start(33) # ~30fps - self.play_btn.setIcon(QtGui.QIcon(os.path.join(data.ICONS_PATH, "pause.png"))) - else: - self.play_timer.stop() - self.play_btn.setIcon(QtGui.QIcon(os.path.join(data.ICONS_PATH, "play.png"))) - - def update_time(self): - """更新时间""" - current = self.time_slider.value() - if current >= self.time_slider.maximum(): - if self.loop_btn.isChecked(): - self.time_slider.setValue(0) - else: - self.play_btn.setChecked(False) - else: - self.time_slider.setValue(current + 1) - - def update_preview(self): - """更新预览""" - from scripts.utils import adjust_utils - - if not self.expr_set: - return - - # 获取当前权重 - weight = self.weight_slider.value_spin.value() - - # 应用表情 - adjust_utils.apply_expression_set(self.expr_set, weight) - - def apply_expression_weight(self, target, weight): - """应用表情权重""" - from scripts.utils import adjust_utils - - if not self.expr_set: - return - - # 更新表情集数据 - target_data = cmds.getAttr(f"{self.expr_set}.targets") - targets = [] - for item in target_split(";"): - name, value = item.split(":") - if name == target: - targets.append(f"{name}:{weight}") - else: - targets.append(item) - - cmds.setAttr(f"{self.expr_set}.targets", ";".join(targets), type="string") - - # 更新预览 - self.update_preview() +class ModernLineEdit(QtWidgets.QLineEdit): + """现代风格输入框""" + def __init__(self, read_only=False, parent=None): + super(ModernLineEdit, self).__init__(parent) + self.setReadOnly(read_only) -class ExpressionCombinationPreview(QtWidgets.QWidget): - """表情组合预览组件""" - def __init__(self, parent=None): - super(ExpressionCombinationPreview, self).__init__(parent) - self.combo = None - self.setup_ui() - - def setup_ui(self): - """创建界面""" - layout = QtWidgets.QVBoxLayout(self) - - # 顶部工具栏 - toolbar = QtWidgets.QHBoxLayout() - - # 组合选择 - self.combo_combo = QtWidgets.QComboBox() - self.combo_combo.currentTextChanged.connect(self.on_combo_changed) - toolbar.addWidget(self.combo_combo) - - # 刷新按钮 - refresh_btn = IconButton("refresh.png", "刷新") - refresh_btn.clicked.connect(self.refresh_combinations) - toolbar.addWidget(refresh_btn) - - # 创建组合 - create_btn = IconButton("create.png", "创建组合") - create_btn.clicked.connect(self.create_combination) - toolbar.addWidget(create_btn) - - # 删除组合 - delete_btn = IconButton("delete.png", "删除组合") - delete_btn.clicked.connect(self.delete_combination) - toolbar.addWidget(delete_btn) - - layout.addLayout(toolbar) - - # 预览区域 - preview_group = QtWidgets.QGroupBox("预览") - preview_layout = QtWidgets.QVBoxLayout(preview_group) - - # 总权重控制 - weight_layout = QtWidgets.QHBoxLayout() - weight_layout.addWidget(QtWidgets.QLabel("总权重:")) - - self.weight_slider = SliderWithValue(min_val=0.0, max_val=1.0, default=1.0) - self.weight_slider.valueChanged.connect(self.on_weight_changed) - weight_layout.addWidget(self.weight_slider) - - preview_layout.addLayout(weight_layout) - - layout.addWidget(preview_group) - - # 表情列表 - list_group = QtWidgets.QGroupBox("组合表情") - list_layout = QtWidgets.QVBoxLayout(list_group) - - # 搜索栏 - self.search_edit = SearchLineEdit() - self.search_edit.textChanged.connect(self.filter_expressions) - list_layout.addWidget(self.search_edit) - - # 表情树 - self.expr_tree = QtWidgets.QTreeWidget() - self.expr_tree.setHeaderLabels(["表情", "权重"]) - self.expr_tree.itemChanged.connect(self.on_item_changed) - list_layout.addWidget(self.expr_tree) - - # 表情工具栏 - expr_toolbar = QtWidgets.QHBoxLayout() - - # 添加表情 - add_btn = IconButton("add.png", "添加表情") - add_btn.clicked.connect(self.add_expression) - expr_toolbar.addWidget(add_btn) - - # 移除表情 - remove_btn = IconButton("remove.png", "移除表情") - remove_btn.clicked.connect(self.remove_expression) - expr_toolbar.addWidget(remove_btn) - - # 上移 - up_btn = IconButton("up.png", "上移") - up_btn.clicked.connect(self.move_expression_up) - expr_toolbar.addWidget(up_btn) - - # 下移 - down_btn = IconButton("down.png", "下移") - down_btn.clicked.connect(self.move_expression_down) - expr_toolbar.addWidget(down_btn) - - list_layout.addLayout(expr_toolbar) - - layout.addWidget(list_group) - - def set_combination(self, combo): - """设置表情组合""" - self.combo = combo - self.refresh_combinations() - - def refresh_combinations(self): - """刷新组合列表""" - from scripts.utils import adjust_utils - - self.expr_tree.clear() - - if not self.combo: - return - - # 获取组合数据 - expr_data = cmds.getAttr(f"{self.combo}.expressions") - for item in expr_split(";"): - expr_set, weight = item.split(":") - tree_item = QtWidgets.QTreeWidgetItem([expr_set, weight]) - tree_item.setFlags(tree_item.flags() | QtCore.Qt.ItemIsEditable) - self.expr_tree.addTopLevelItem(tree_item) - - def filter_expressions(self, text): - """过滤表情""" - for i in range(self.expr_tree.topLevelItemCount()): - item = self.expr_tree.topLevelItem(i) - item.setHidden(text.lower() not in item.text(0).lower()) - - def on_combo_changed(self, combo): - """组合变化""" - self.set_combination(combo) - - def on_item_changed(self, item, column): - """项目变化""" - if column == 1: # 权重列 - try: - expr_set = item.text(0) - weight = float(item.text(1)) - self.update_expression_weight(expr_set, weight) - except ValueError: - pass - - def on_weight_changed(self, weight): - """总权重变化""" - from scripts.utils import adjust_utils - - if self.combo: - adjust_utils.apply_expression_combination(self.combo, weight) - - def create_combination(self): - """创建组合""" - name, ok = QtWidgets.QInputDialog.getText( - self, "创建组合", "请输入组合名称:") - - if ok and name: - from scripts.utils import adjust_utils - combo = adjust_utils.create_expression_combination(name, []) - if combo: - self.combo_combo.addItem(combo) - self.combo_combo.setCurrentText(combo) - - def delete_combination(self): - """删除组合""" - if self.combo: - reply = QtWidgets.QMessageBox.question( - self, "删除组合", - f"确定要删除组合 {self.combo} 吗?", - QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No - ) - - if reply == QtWidgets.QMessageBox.Yes: - cmds.delete(self.combo) - self.combo_combo.removeItem( - self.combo_combo.findText(self.combo)) - self.combo = None - self.refresh_combinations() - - def add_expression(self): - """添加表情""" - from scripts.utils import adjust_utils - - if not self.combo: - return - - # 获取场景中的表情集 - expr_sets = cmds.ls(type="objectSet", long=True) - expr_sets = [s for s in expr_sets if "_exprSet" in s] - - if not expr_sets: - QtWidgets.QMessageBox.warning( - self, "添加表情", "场景中没有可用的表情集") - return - - # 选择表情集 - expr_set, ok = QtWidgets.QInputDialog.getItem( - self, "添加表情", "选择表情集:", expr_sets, 0, False) - - if ok and expr_set: - # 添加到组合 - expr_data = cmds.getAttr(f"{self.combo}.expressions") - if expr_data: - expr_data += f";{expr_set}:1.0" - else: - expr_data = f"{expr_set}:1.0" - cmds.setAttr(f"{self.combo}.expressions", expr_data, type="string") - - # 刷新列表 - self.refresh_combinations() - - def remove_expression(self): - """移除表情""" - items = self.expr_tree.selectedItems() - if not items: - return - - # 更新组合数据 - expr_data = cmds.getAttr(f"{self.combo}.expressions") - exprs = [] - for item in expr_split(";"): - expr_set, weight = item.split(":") - if expr_set not in [i.text(0) for i in items]: - exprs.append(item) - - cmds.setAttr(f"{self.combo}.expressions", - ";".join(exprs), type="string") - - # 刷新列表 - self.refresh_combinations() - - def move_expression_up(self): - """上移表情""" - item = self.expr_tree.currentItem() - if not item: - return - - index = self.expr_tree.indexOfTopLevelItem(item) - if index <= 0: - return - - # 移动项目 - self.expr_tree.takeTopLevelItem(index) - self.expr_tree.insertTopLevelItem(index - 1, item) - self.expr_tree.setCurrentItem(item) - - # 更新组合数据 - self.update_expression_order() - - def move_expression_down(self): - """下移表情""" - item = self.expr_tree.currentItem() - if not item: - return - - index = self.expr_tree.indexOfTopLevelItem(item) - if index >= self.expr_tree.topLevelItemCount() - 1: - return - - # 移动项目 - self.expr_tree.takeTopLevelItem(index) - self.expr_tree.insertTopLevelItem(index + 1, item) - self.expr_tree.setCurrentItem(item) - - # 更新组合数据 - self.update_expression_order() - - def update_expression_order(self): - """更新表情顺序""" - exprs = [] - for i in range(self.expr_tree.topLevelItemCount()): - item = self.expr_tree.topLevelItem(i) - exprs.append(f"{item.text(0)}:{item.text(1)}") - - cmds.setAttr(f"{self.combo}.expressions", - ";".join(exprs), type="string") - - def update_expression_weight(self, expr_set, weight): - """更新表情权重""" - # 更新组合数据 - expr_data = cmds.getAttr(f"{self.combo}.expressions") - exprs = [] - for item in expr_split(";"): - name, value = item.split(":") - if name == expr_set: - exprs.append(f"{name}:{weight}") - else: - exprs.append(item) - - cmds.setAttr(f"{self.combo}.expressions", - ";".join(exprs), type="string") - - # 更新预览 - self.on_weight_changed(self.weight_slider.value_spin.value()) - -class WeightCurveEditor(QtWidgets.QWidget): - """权重曲线编辑器""" - def __init__(self, parent=None): - super(WeightCurveEditor, self).__init__(parent) - self.curve = None - self.setup_ui() - - def setup_ui(self): - """创建界面""" - layout = QtWidgets.QVBoxLayout(self) - - # 顶部工具栏 - toolbar = QtWidgets.QHBoxLayout() - - # 曲线选择 - self.curve_combo = QtWidgets.QComboBox() - self.curve_combo.currentTextChanged.connect(self.on_curve_changed) - toolbar.addWidget(self.curve_combo) - - # 创建曲线 - create_btn = IconButton("create.png", "创建曲线") - create_btn.clicked.connect(self.create_curve) - toolbar.addWidget(create_btn) - - # 删除曲线 - delete_btn = IconButton("delete.png", "删除曲线") - delete_btn.clicked.connect(self.delete_curve) - toolbar.addWidget(delete_btn) - - # 重置曲线 - reset_btn = IconButton("reset.png", "重置曲线") - reset_btn.clicked.connect(self.reset_curve) - toolbar.addWidget(reset_btn) - - layout.addLayout(toolbar) - - # 曲线编辑区域 - curve_group = QtWidgets.QGroupBox("曲线编辑") - curve_layout = QtWidgets.QVBoxLayout(curve_group) - - # 添加曲线视图 - curve_view = self.setup_curve_view() - curve_layout.addWidget(curve_view) - - # 添加关键帧控制 - key_controls = self.setup_key_controls() - curve_layout.addWidget(key_controls) - - layout.addWidget(curve_group) - - def set_curve(self, curve): - """设置曲线""" - self.curve = curve - self.refresh_view() - - def refresh_view(self): - """刷新视图""" - self.curve_scene.clear() - - if not self.curve: - return - - # 绘制网格 - self.draw_grid() - - # 绘制曲线 - self.draw_curve() - - # 绘制关键帧 - self.draw_keyframes() - - def draw_grid(self): - """绘制网格""" - # 获取视图大小 - width = self.curve_view.width() - height = self.curve_view.height() - - # 绘制水平线 - for i in range(11): - y = height * i / 10 - self.curve_scene.addLine(0, y, width, y, - QtGui.QPen(QtGui.QColor(100, 100, 100))) - - # 绘制垂直线 - for i in range(11): - x = width * i / 10 - self.curve_scene.addLine(x, 0, x, height, - QtGui.QPen(QtGui.QColor(100, 100, 100))) - - def draw_curve(self): - """绘制曲线""" - if not self.curve: - return - - # 获取曲线数据 - times = cmds.keyframe(self.curve, q=True, timeChange=True) - values = cmds.keyframe(self.curve, q=True, valueChange=True) - - if not times or not values: - return - - # 创建路径 - path = QtGui.QPainterPath() - path.moveTo(times[0], values[0]) - - for i in range(1, len(times)): - path.lineTo(times[i], values[i]) - - # 添加到场景 - self.curve_scene.addPath(path, - QtGui.QPen(QtGui.QColor(0, 255, 0), 2)) - - def draw_keyframes(self): - """绘制关键帧""" - if not self.curve: - return - - # 获取关键帧 - times = cmds.keyframe(self.curve, q=True, timeChange=True) - values = cmds.keyframe(self.curve, q=True, valueChange=True) - - if not times or not values: - return - - # 绘制关键帧点 - for t, v in zip(times, values): - self.curve_scene.addEllipse(t-3, v-3, 6, 6, - QtGui.QPen(QtGui.QColor(255, 255, 0)), - QtGui.QBrush(QtGui.QColor(255, 255, 0))) - - def create_curve(self): - """创建曲线""" - from scripts.utils import adjust_utils - - name, ok = QtWidgets.QInputDialog.getText( - self, "创建曲线", "请输入曲线名称:") - - if ok and name: - curve = adjust_utils.create_weight_curve(name) - if curve: - self.curve_combo.addItem(curve) - self.curve_combo.setCurrentText(curve) - - def delete_curve(self): - """删除曲线""" - if self.curve: - reply = QtWidgets.QMessageBox.question( - self, "删除曲线", - f"确定要删除曲线 {self.curve} 吗?", - QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No - ) - - if reply == QtWidgets.QMessageBox.Yes: - cmds.delete(self.curve) - self.curve_combo.removeItem( - self.curve_combo.findText(self.curve)) - self.curve = None - self.refresh_view() - - def reset_curve(self): - """重置曲线""" - if not self.curve: - return - - # 删除所有关键帧 - cmds.cutKey(self.curve) - - # 添加默认关键帧 - start = self.start_spin.value() - end = self.end_spin.value() - - cmds.setKeyframe(self.curve, time=start, value=0) - cmds.setKeyframe(self.curve, time=end, value=0) - - self.refresh_view() - - def on_curve_changed(self, curve): - """曲线变化""" - self.set_curve(curve) - - def setup_curve_view(self): - """设置曲线视图""" - # 创建视图容器 - view_container = QtWidgets.QWidget() - view_layout = QtWidgets.QVBoxLayout(view_container) - view_layout.setContentsMargins(0, 0, 0, 0) - - # 添加标尺 - self.h_ruler = TimeRuler(QtCore.Qt.Horizontal) - self.v_ruler = ValueRuler(QtCore.Qt.Vertical) - - # 创建视图区域 - view_area = QtWidgets.QWidget() - grid_layout = QtWidgets.QGridLayout(view_area) - grid_layout.setSpacing(0) - grid_layout.setContentsMargins(0, 0, 0, 0) - - # 添加标尺和视图 - grid_layout.addWidget(self.v_ruler, 1, 0) - grid_layout.addWidget(self.curve_view, 1, 1) - grid_layout.addWidget(self.h_ruler, 0, 1) - - view_layout.addWidget(view_area) - - # 添加缩放控制 - zoom_layout = QtWidgets.QHBoxLayout() - - # 水平缩放 - zoom_layout.addWidget(QtWidgets.QLabel("水平缩放:")) - self.h_zoom = QtWidgets.QSlider(QtCore.Qt.Horizontal) - self.h_zoom.setRange(10, 200) - self.h_zoom.setValue(100) - self.h_zoom.valueChanged.connect(self.update_view_scale) - zoom_layout.addWidget(self.h_zoom) - - # 垂直缩放 - zoom_layout.addWidget(QtWidgets.QLabel("垂直缩放:")) - self.v_zoom = QtWidgets.QSlider(QtCore.Qt.Horizontal) - self.v_zoom.setRange(10, 200) - self.v_zoom.setValue(100) - self.v_zoom.valueChanged.connect(self.update_view_scale) - zoom_layout.addWidget(self.v_zoom) - - view_layout.addLayout(zoom_layout) - - return view_container - - def update_view_scale(self): - """更新视图缩放""" - # 获取缩放值 - h_scale = self.h_zoom.value() / 100.0 - v_scale = self.v_zoom.value() / 100.0 - - # 更新变换 - self.curve_view.setTransform( - QtGui.QTransform().scale(h_scale, v_scale)) - - # 更新标尺 - self.h_ruler.setScale(h_scale) - self.v_ruler.setScale(v_scale) - - # 刷新视图 - self.refresh_view() - - def setup_key_controls(self): - """设置关键帧控制""" - controls = QtWidgets.QWidget() - layout = QtWidgets.QHBoxLayout(controls) - layout.setContentsMargins(0, 0, 0, 0) - - # 关键帧导航 - nav_group = QtWidgets.QGroupBox("关键帧导航") - nav_layout = QtWidgets.QHBoxLayout(nav_group) - - prev_key = IconButton("prevKey.png", "上一帧") - prev_key.clicked.connect(self.goto_prev_key) - nav_layout.addWidget(prev_key) - - next_key = IconButton("nextKey.png", "下一帧") - next_key.clicked.connect(self.goto_next_key) - nav_layout.addWidget(next_key) - - layout.addWidget(nav_group) - - # 关键帧编辑 - edit_group = QtWidgets.QGroupBox("关键帧编辑") - edit_layout = QtWidgets.QHBoxLayout(edit_group) - - # 时间输入 - edit_layout.addWidget(QtWidgets.QLabel("时间:")) - self.time_edit = QtWidgets.QSpinBox() - self.time_edit.setRange(-9999, 9999) - self.time_edit.valueChanged.connect(self.on_time_changed) - edit_layout.addWidget(self.time_edit) - - # 值输入 - edit_layout.addWidget(QtWidgets.QLabel("值:")) - self.value_edit = QtWidgets.QDoubleSpinBox() - self.value_edit.setRange(-9999.0, 9999.0) - self.value_edit.setDecimals(3) - self.value_edit.valueChanged.connect(self.on_value_changed) - edit_layout.addWidget(self.value_edit) - - layout.addWidget(edit_group) - - return controls - - def goto_prev_key(self): - """跳转到上一个关键帧""" - if not self.curve: - return - - current = cmds.currentTime(q=True) - times = cmds.keyframe(self.curve, q=True, timeChange=True) - - if not times: - return - - # 查找上一帧 - prev_time = max([t for t in times if t < current], default=None) - if prev_time is not None: - cmds.currentTime(prev_time) - self.time_edit.setValue(prev_time) - - def goto_next_key(self): - """跳转到下一个关键帧""" - if not self.curve: - return - - current = cmds.currentTime(q=True) - times = cmds.keyframe(self.curve, q=True, timeChange=True) - - if not times: - return - - # 查找下一帧 - next_time = min([t for t in times if t > current], default=None) - if next_time is not None: - cmds.currentTime(next_time) - self.time_edit.setValue(next_time) - - def on_time_changed(self, time): - """时间变化""" - if not self.curve: - return - - # 更新当前时间 - cmds.currentTime(time) - - # 更新值显示 - value = cmds.getValue(self.curve, time=time) - self.value_edit.setValue(value) - - # 刷新视图 - self.refresh_view() - - def on_value_changed(self, value): - """值变化""" - if not self.curve: - return - - # 获取当前时间 - time = self.time_edit.value() - - # 设置关键帧 - cmds.setKeyframe(self.curve, time=time, value=value) - - # 刷新视图 - self.refresh_view() - - def apply_curve_to_target(self): - """应用曲线到目标""" - if not self.curve: - return - - # 选择目标 - sel = cmds.ls(sl=True) - if not sel: - QtWidgets.QMessageBox.warning( - self, "应用曲线", "请先选择目标对象") - return - - # 获取目标属性 - attrs = [] - for obj in sel: - # 获取可设置关键帧的属性 - keyable_attrs = cmds.listAttr(obj, keyable=True) or [] - for attr in keyable_attrs: - attrs.append(f"{obj}.{attr}") - - if not attrs: - QtWidgets.QMessageBox.warning( - self, "应用曲线", "选中的对象没有可设置关键帧的属性") - return - - # 选择属性 - attr, ok = QtWidgets.QInputDialog.getItem( - self, "应用曲线", "选择目标属性:", attrs, 0, False) - - if ok and attr: - # 连接曲线到属性 - try: - cmds.connectAttr(f"{self.curve}.output", attr) - QtWidgets.QMessageBox.information( - self, "应用曲线", f"已将曲线应用到 {attr}") - except Exception as e: - QtWidgets.QMessageBox.warning( - self, "应用曲线", f"应用曲线失败: {str(e)}") \ No newline at end of file +class ModernToolButton(QtWidgets.QToolButton): + """现代风格工具按钮""" + def __init__(self, icon, text, parent=None): + super(ModernToolButton, self).__init__(parent) + self.setText(text) + if icon: + icon_path = os.path.join(data.ICONS_PATH, icon) + if os.path.exists(icon_path): + self.setIcon(QtGui.QIcon(icon_path)) + self.setIconSize(QtCore.QSize(16, 16)) \ No newline at end of file diff --git a/scripts/utils/adjust_utils.py b/scripts/utils/adjust_utils.py deleted file mode 100644 index a00e4f0..0000000 --- a/scripts/utils/adjust_utils.py +++ /dev/null @@ -1,1194 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -#===================================== 1. Module Imports ===================================== -import maya.cmds as cmds -import maya.mel as mel -import sys -import os - -#===================================== 2. BlendShape Manager Class ===================================== -class BlendShapeManager: - """BlendShape管理器""" - def __init__(self): - self.main_bs = None # 主要BlendShape节点 - self.related_bs = [] # 相关BlendShape节点 - self.current_weights = {} # 当前权重缓存 - - def set_main_blendshape(self, bs_node): - """设置主要BlendShape节点""" - if not cmds.objExists(bs_node): - raise ValueError(f"BlendShape节点不存在: {bs_node}") - self.main_bs = bs_node - - def add_related_blendshape(self, bs_node): - """添加相关BlendShape节点""" - if not cmds.objExists(bs_node): - raise ValueError(f"BlendShape节点不存在: {bs_node}") - if bs_node not in self.related_bs: - self.related_bs.append(bs_node) - - def clear_related_blendshapes(self): - """清空相关BlendShape节点""" - self.related_bs = [] - - def get_all_targets(self, bs_node): - """获取BlendShape的所有目标""" - if not cmds.objExists(bs_node): - return [] - - # 获取目标数量 - target_count = cmds.blendShape(bs_node, q=True, weightCount=True) - targets = [] - - # 获取每个目标的名称 - for i in range(target_count): - alias = cmds.aliasAttr(f"{bs_node}.weight[{i}]", q=True) - if alias: - targets.append(alias) - - return targets - - def set_weight(self, bs_node, target, weight): - """设置BlendShape权重""" - if not cmds.objExists(bs_node): - return False - - try: - # 获取权重索引 - weight_index = -1 - target_count = cmds.blendShape(bs_node, q=True, weightCount=True) - - for i in range(target_count): - alias = cmds.aliasAttr(f"{bs_node}.weight[{i}]", q=True) - if alias == target: - weight_index = i - break - - if weight_index >= 0: - cmds.setAttr(f"{bs_node}.weight[{weight_index}]", weight) - return True - - except Exception as e: - cmds.warning(f"设置权重失败: {str(e)}") - - return False - - def get_weight(self, bs_node, target): - """获取BlendShape权重""" - if not cmds.objExists(bs_node): - return 0.0 - - try: - # 获取权重索引 - weight_index = -1 - target_count = cmds.blendShape(bs_node, q=True, weightCount=True) - - for i in range(target_count): - alias = cmds.aliasAttr(f"{bs_node}.weight[{i}]", q=True) - if alias == target: - weight_index = i - break - - if weight_index >= 0: - return cmds.getAttr(f"{bs_node}.weight[{weight_index}]") - - except Exception as e: - cmds.warning(f"获取权重失败: {str(e)}") - - return 0.0 - - def store_weights(self): - """存储当前权重""" - self.current_weights = {} - - # 存储主要BlendShape权重 - if self.main_bs: - self.current_weights[self.main_bs] = {} - for target in self.get_all_targets(self.main_bs): - self.current_weights[self.main_bs][target] = self.get_weight(self.main_bs, target) - - # 存储相关BlendShape权重 - for bs in self.related_bs: - self.current_weights[bs] = {} - for target in self.get_all_targets(bs): - self.current_weights[bs][target] = self.get_weight(bs, target) - - def restore_weights(self): - """还原存储的权重""" - for bs, weights in self.current_weights.items(): - for target, weight in weights.items(): - self.set_weight(bs, target, weight) - -# 全局BlendShape管理器实例 -bs_manager = BlendShapeManager() - -def reset_blendshapes(): - """重置所有BlendShape""" - try: - # 重置主要BlendShape - if bs_manager.main_bs: - for target in bs_manager.get_all_targets(bs_manager.main_bs): - bs_manager.set_weight(bs_manager.main_bs, target, 0) - - # 重置相关BlendShape - for bs in bs_manager.related_bs: - for target in bs_manager.get_all_targets(bs): - bs_manager.set_weight(bs, target, 0) - - print("BlendShape重置完成") - - except Exception as e: - cmds.warning(f"重置BlendShape失败: {str(e)}") - -def filter_blendshapes(): - """过滤BlendShape""" - # TODO: 实现BlendShape过滤功能 - print("BlendShape过滤功能待实现") - -def on_main_bs_selected(targets): - """主要BlendShape选择变化""" - try: - # 存储当前权重 - bs_manager.store_weights() - - # 设置新的权重 - if bs_manager.main_bs: - for target in targets: - bs_manager.set_weight(bs_manager.main_bs, target, 1.0) - - except Exception as e: - cmds.warning(f"设置BlendShape权重失败: {str(e)}") - -def on_related_bs_selected(targets): - """相关BlendShape选择变化""" - try: - # 存储当前权重 - bs_manager.store_weights() - - # 设置新的权重 - for bs in bs_manager.related_bs: - for target in targets: - bs_manager.set_weight(bs, target, 1.0) - - except Exception as e: - cmds.warning(f"设置BlendShape权重失败: {str(e)}") - -def set_bs_value(value): - """设置BlendShape权重值""" - try: - # 设置主要BlendShape权重 - if bs_manager.main_bs: - for target in bs_manager.get_all_targets(bs_manager.main_bs): - bs_manager.set_weight(bs_manager.main_bs, target, value) - - # 设置相关BlendShape权重 - for bs in bs_manager.related_bs: - for target in bs_manager.get_all_targets(bs): - bs_manager.set_weight(bs, target, value) - - except Exception as e: - cmds.warning(f"设置BlendShape权重值失败: {str(e)}") - -def set_expr_value(value): - """设置表情权重值""" - try: - # 获取当前选中的表情控制器 - sel = cmds.ls(sl=True) - if not sel: - return - - # 设置权重 - for ctrl in sel: - if cmds.objExists(f"{ctrl}.weight"): - cmds.setAttr(f"{ctrl}.weight", value) - - except Exception as e: - cmds.warning(f"设置表情权重值失败: {str(e)}") - -# 表情控制功能 -def reset_expression(): - """还原默认表情""" - try: - # 还原存储的权重 - bs_manager.restore_weights() - print("表情已还原") - - except Exception as e: - cmds.warning(f"还原表情失败: {str(e)}") - -def select_expression(): - """选择表情""" - # TODO: 实现表情选择功能 - print("表情选择功能待实现") - -def write_expression(): - """写入当前表情""" - # TODO: 实现表情写入功能 - print("表情写入功能待实现") - -def find_controller(): - """查找控制器""" - # TODO: 实现控制器查找功能 - print("控制器查找功能待实现") - -def select_joints(): - """选择关联关节""" - # TODO: 实现关联关节选择功能 - print("关联关节选择功能待实现") - -def write_mirror_expression(): - """写入镜像表情""" - # TODO: 实现镜像表情写入功能 - print("镜像表情写入功能待实现") - -def reset_expression(): - """恢复表情""" - print("恢复表情功能待实现") - -def blend_filter(): - """混合筛选""" - print("混合筛选功能待实现") - -def flip(): - """翻转""" - print("翻转功能待实现") - -def mirror_target(): - """镜像目标""" - print("镜像目标功能待实现") - -def find_flip_target(): - """查找翻转目标""" - print("查找翻转目标功能待实现") - -def add_blend_target(): - """添加混合目标""" - print("添加混合目标功能待实现") - -def delete_blend_target(): - """删除混合目标""" - print("删除混合目标功能待实现") - -def batch_blend_target(): - """批量混合目标""" - print("批量混合目标功能待实现") - -def rebuild_selected_target(): - """重建选择目标""" - print("重建选择目标功能待实现") - -def blend_selected_target(): - """混合选择目标""" - print("混合选择目标功能待实现") - -# 功能开关 -def toggle_psd(checked): - """PSD开关""" - print(f"PSD功能{'开启' if checked else '关闭'}") - -def toggle_bse(checked): - """BSE开关""" - print(f"BSE功能{'开启' if checked else '关闭'}") - -def toggle_key(checked): - """KEY开关""" - print(f"KEY功能{'开启' if checked else '关闭'}") - -def toggle_mir(checked): - """MIR开关""" - print(f"MIR功能{'开启' if checked else '关闭'}") - -def toggle_ark(checked): - """ARK开关""" - print(f"ARK功能{'开启' if checked else '关闭'}") - -def toggle_ctr(checked): - """CTR开关""" - print(f"CTR功能{'开启' if checked else '关闭'}") - -# 底部功能按钮 -def reset_default_expression(): - """还原默认表情""" - print("还原默认表情功能待实现") - -def find_control_panel(): - """查找控制面板""" - print("查找控制面板功能待实现") - -def select_related_joints(): - """选择关联关节""" - print("选择关联关节功能待实现") - -def write_mirror_expression(): - """写入镜像表情""" - print("写入镜像表情功能待实现") - -def mirror_blendshape(source_bs, target_bs, left_prefix="L_", right_prefix="R_"): - """镜像BlendShape - Args: - source_bs: 源BlendShape节点 - target_bs: 目标BlendShape节点 - left_prefix: 左侧前缀 - right_prefix: 右侧前缀 - """ - try: - # 获取源BlendShape的所有目标 - source_targets = bs_manager.get_all_targets(source_bs) - - for source_target in source_targets: - # 获取镜像目标名称 - if source_target.startswith(left_prefix): - mirror_target = source_target.replace(left_prefix, right_prefix) - elif source_target.startswith(right_prefix): - mirror_target = source_target.replace(right_prefix, left_prefix) - else: - continue - - # 获取源权重 - weight = bs_manager.get_weight(source_bs, source_target) - - # 设置镜像权重 - bs_manager.set_weight(target_bs, mirror_target, weight) - - print(f"BlendShape镜像完成: {source_bs} -> {target_bs}") - - except Exception as e: - cmds.warning(f"BlendShape镜像失败: {str(e)}") - -def find_mirror_target(target_name, left_prefix="L_", right_prefix="R_"): - """查找镜像目标 - Args: - target_name: 目标名称 - left_prefix: 左侧前缀 - right_prefix: 右侧前缀 - Returns: - str: 镜像目标名称 - """ - if target_name.startswith(left_prefix): - return target_name.replace(left_prefix, right_prefix) - elif target_name.startswith(right_prefix): - return target_name.replace(right_prefix, left_prefix) - return "" - -def create_blend_target(base_mesh, target_mesh, target_name): - """创建混合目标 - Args: - base_mesh: 基础模型 - target_mesh: 目标模型 - target_name: 目标名称 - Returns: - str: 创建的BlendShape节点名称 - """ - try: - # 检查模型是否存在 - if not all(cmds.objExists(obj) for obj in [base_mesh, target_mesh]): - raise ValueError("模型不存在") - - # 创建BlendShape节点 - bs_node = cmds.blendShape( - target_mesh, - base_mesh, - name=f"{base_mesh}_blendShape", - frontOfChain=True - )[0] - - # 重命名目标 - target_index = 0 - cmds.aliasAttr(target_name, f"{bs_node}.weight[{target_index}]") - - return bs_node - - except Exception as e: - cmds.warning(f"创建混合目标失败: {str(e)}") - return None - -def batch_create_blend_targets(base_mesh, target_meshes, name_prefix=""): - """批量创建混合目标 - Args: - base_mesh: 基础模型 - target_meshes: 目标模型列表 - name_prefix: 名称前缀 - """ - try: - # 创建BlendShape节点 - bs_node = cmds.blendShape( - base_mesh, - name=f"{base_mesh}_blendShape", - frontOfChain=True - )[0] - - # 添加目标 - for i, target in enumerate(target_meshes): - target_name = f"{name_prefix}target_{i+1}" - cmds.blendShape(bs_node, edit=True, target=(base_mesh, i, target, 1.0)) - cmds.aliasAttr(target_name, f"{bs_node}.weight[{i}]") - - print(f"批量创建混合目标完成: {bs_node}") - return bs_node - - except Exception as e: - cmds.warning(f"批量创建混合目标失败: {str(e)}") - return None - -def rebuild_blend_target(bs_node, target_name): - """重建混合目标 - Args: - bs_node: BlendShape节点 - target_name: 目标名称 - """ - try: - # 获取基础模型 - base_mesh = cmds.blendShape(bs_node, q=True, geometry=True)[0] - - # 获取目标索引 - target_index = -1 - target_count = cmds.blendShape(bs_node, q=True, weightCount=True) - - for i in range(target_count): - alias = cmds.aliasAttr(f"{bs_node}.weight[{i}]", q=True) - if alias == target_name: - target_index = i - break - - if target_index < 0: - raise ValueError(f"找不到目标: {target_name}") - - # 提取目标形状 - target_mesh = cmds.duplicate(base_mesh, name=f"{target_name}_rebuild")[0] - cmds.setAttr(f"{bs_node}.weight[{target_index}]", 1) - cmds.delete(target_mesh, constructionHistory=True) - - # 重建目标 - cmds.blendShape(bs_node, edit=True, remove=True, target=(base_mesh, target_index, target_name, 1.0)) - cmds.blendShape(bs_node, edit=True, target=(base_mesh, target_index, target_mesh, 1.0)) - - # 清理 - cmds.delete(target_mesh) - print(f"重建混合目标完成: {target_name}") - - except Exception as e: - cmds.warning(f"重建混合目标失败: {str(e)}") - -def blend_selected_targets(bs_node, targets, weight=1.0): - """混合选中的目标 - Args: - bs_node: BlendShape节点 - targets: 目标名称列表 - weight: 混合权重 - """ - try: - # 重置所有权重 - all_targets = bs_manager.get_all_targets(bs_node) - for target in all_targets: - bs_manager.set_weight(bs_node, target, 0) - - # 设置选中目标的权重 - for target in targets: - bs_manager.set_weight(bs_node, target, weight) - - print(f"混合目标完成: {targets}") - - except Exception as e: - cmds.warning(f"混合目标失败: {str(e)}") - -def create_corrective_blend(base_mesh, pose_mesh, corrective_mesh, pose_attrs): - """创建修正混合变形 - Args: - base_mesh: 基础模型 - pose_mesh: 姿势模型 - corrective_mesh: 修正模型 - pose_attrs: 姿势属性列表 [(attr, value), ...] - """ - try: - # 创建BlendShape节点 - bs_node = cmds.blendShape( - [pose_mesh, corrective_mesh], - base_mesh, - name=f"{base_mesh}_corrective", - frontOfChain=True - )[0] - - # 设置驱动关系 - for attr, value in pose_attrs: - # 创建条件节点 - cond = cmds.createNode("condition", name=f"{bs_node}_cond") - cmds.setAttr(f"{cond}.operation", 2) # Greater Than - cmds.setAttr(f"{cond}.secondTerm", value) - - # 连接属性 - cmds.connectAttr(attr, f"{cond}.firstTerm") - cmds.connectAttr(f"{cond}.outColorR", f"{bs_node}.weight[1]") - - print(f"创建修正混合变形完成: {bs_node}") - return bs_node - - except Exception as e: - cmds.warning(f"创建修正混合变形失败: {str(e)}") - return None - -def create_expression_set(name, targets, base_mesh=None): - """创建表情集 - Args: - name: 表情集名称 - targets: 目标列表 [(target_name, weight), ...] - base_mesh: 基础模型(可选) - Returns: - str: 创建的表情集节点名称 - """ - try: - # 创建表情集节点 - expr_set = cmds.createNode("objectSet", name=f"{name}_exprSet") - - # 添加自定义属性 - cmds.addAttr(expr_set, ln="weight", at="double", min=0, max=1, dv=0) - cmds.setAttr(f"{expr_set}.weight", e=True, keyable=True) - - # 添加目标信息 - cmds.addAttr(expr_set, ln="targets", dt="string") - target_data = ";".join([f"{t}:{w}" for t, w in targets]) - cmds.setAttr(f"{expr_set}.targets", target_data, type="string") - - # 添加基础模型 - if base_mesh: - cmds.addAttr(expr_set, ln="baseMesh", dt="string") - cmds.setAttr(f"{expr_set}.baseMesh", base_mesh, type="string") - - return expr_set - - except Exception as e: - cmds.warning(f"创建表情集失败: {str(e)}") - return None - -def apply_expression_set(expr_set, weight=None): - """应用表情集 - Args: - expr_set: 表情集节点 - weight: 权重值(可选) - """ - try: - # 获取目标信息 - target_data = cmds.getAttr(f"{expr_set}.targets") - targets = [] - for item in target_data.split(";"): - name, value = item.split(":") - targets.append((name, float(value))) - - # 获取权重 - if weight is None: - weight = cmds.getAttr(f"{expr_set}.weight") - - # 应用权重 - for target_name, target_weight in targets: - bs_node = target_name.split(".")[0] - bs_manager.set_weight(bs_node, target_name, target_weight * weight) - - except Exception as e: - cmds.warning(f"应用表情集失败: {str(e)}") - -def create_mirror_expression(expr_set, left_prefix="L_", right_prefix="R_"): - """创建镜像表情 - Args: - expr_set: 源表情集节点 - left_prefix: 左侧前缀 - right_prefix: 右侧前缀 - Returns: - str: 创建的镜像表情集节点名称 - """ - try: - # 获取源表情信息 - target_data = cmds.getAttr(f"{expr_set}.targets") - source_targets = [] - for item in target_data.split(";"): - name, value = item.split(":") - source_targets.append((name, float(value))) - - # 创建镜像目标 - mirror_targets = [] - for target_name, weight in source_targets: - mirror_name = find_mirror_target(target_name, left_prefix, right_prefix) - if mirror_name: - mirror_targets.append((mirror_name, weight)) - - # 创建镜像表情集 - name = cmds.getAttr(f"{expr_set}.name") - mirror_name = f"{name}_mirror" - if cmds.objExists(f"{expr_set}.baseMesh"): - base_mesh = cmds.getAttr(f"{expr_set}.baseMesh") - else: - base_mesh = None - - return create_expression_set(mirror_name, mirror_targets, base_mesh) - - except Exception as e: - cmds.warning(f"创建镜像表情失败: {str(e)}") - return None - -def optimize_weights(bs_node, threshold=0.001): - """优化权重 - Args: - bs_node: BlendShape节点 - threshold: 权重阈值 - """ - try: - # 获取所有目标 - targets = bs_manager.get_all_targets(bs_node) - - # 检查每个目标的权重 - for target in targets: - weight = bs_manager.get_weight(bs_node, target) - - # 移除小权重 - if abs(weight) < threshold: - bs_manager.set_weight(bs_node, target, 0) - - # 规范化权重 - elif abs(weight - 1.0) < threshold: - bs_manager.set_weight(bs_node, target, 1.0) - - print(f"权重优化完成: {bs_node}") - - except Exception as e: - cmds.warning(f"权重优化失败: {str(e)}") - -def create_weight_driver(bs_node, target, driver_attr): - """创建权重驱动器 - Args: - bs_node: BlendShape节点 - target: 目标名称 - driver_attr: 驱动属性 - """ - try: - # 创建条件节点 - cond = cmds.createNode("condition", name=f"{target}_cond") - cmds.setAttr(f"{cond}.operation", 2) # Greater Than - cmds.setAttr(f"{cond}.secondTerm", 0) - - # 连接驱动属性 - cmds.connectAttr(driver_attr, f"{cond}.firstTerm") - - # 获取目标索引 - target_index = -1 - target_count = cmds.blendShape(bs_node, q=True, weightCount=True) - for i in range(target_count): - alias = cmds.aliasAttr(f"{bs_node}.weight[{i}]", q=True) - if alias == target: - target_index = i - break - - if target_index >= 0: - # 连接权重 - cmds.connectAttr(f"{cond}.outColorR", f"{bs_node}.weight[{target_index}]") - print(f"创建权重驱动器完成: {target}") - return cond - - except Exception as e: - cmds.warning(f"创建权重驱动器失败: {str(e)}") - return None - -def create_expression_group(name, expressions): - """创建表情组 - Args: - name: 组名称 - expressions: 表情集列表 [(expr_name, weight), ...] - Returns: - str: 创建的表情组节点名称 - """ - try: - # 创建组节点 - group = cmds.createNode("objectSet", name=f"{name}_exprGroup") - - # 添加表情集信息 - cmds.addAttr(group, ln="expressions", dt="string") - expr_data = ";".join([f"{e}:{w}" for e, w in expressions]) - cmds.setAttr(f"{group}.expressions", expr_data, type="string") - - return group - - except Exception as e: - cmds.warning(f"创建表情组失败: {str(e)}") - return None - -def create_expression_controller(name, attributes): - """创建表情控制器 - Args: - name: 控制器名称 - attributes: 属性列表 [(attr_name, min_val, max_val, default), ...] - Returns: - str: 创建的控制器名称 - """ - try: - # 创建控制器 - ctrl = cmds.circle(name=f"{name}_ctrl", normal=(0, 1, 0))[0] - - # 添加属性 - for attr_name, min_val, max_val, default in attributes: - cmds.addAttr(ctrl, ln=attr_name, at="double", - min=min_val, max=max_val, dv=default) - cmds.setAttr(f"{ctrl}.{attr_name}", e=True, keyable=True) - - return ctrl - - except Exception as e: - cmds.warning(f"创建表情控制器失败: {str(e)}") - return None - -def create_expression_driver(source_attr, target_attrs, remap=False): - """创建表情驱动器 - Args: - source_attr: 源属性 - target_attrs: 目标属性列表 [(attr, value), ...] - remap: 是否重映射值 - """ - try: - for target_attr, target_value in target_attrs: - if remap: - # 创建重映射节点 - remap_node = cmds.createNode("remapValue") - cmds.connectAttr(source_attr, f"{remap_node}.inputValue") - cmds.connectAttr(f"{remap_node}.outValue", target_attr) - - # 设置映射值 - cmds.setAttr(f"{remap_node}.value[0].value_Position", 0) - cmds.setAttr(f"{remap_node}.value[0].value_FloatValue", 0) - cmds.setAttr(f"{remap_node}.value[1].value_Position", 1) - cmds.setAttr(f"{remap_node}.value[1].value_FloatValue", target_value) - else: - # 直接连接 - cmds.connectAttr(source_attr, target_attr) - - print(f"创建表情驱动器完成: {source_attr}") - - except Exception as e: - cmds.warning(f"创建表情驱动器失败: {str(e)}") - -def create_expression_pose(name, targets, mirror=False): - """创建表情姿势 - Args: - name: 姿势名称 - targets: 目标列表 [(node, attr, value), ...] - mirror: 是否创建镜像姿势 - Returns: - str: 创建的姿势节点名称 - """ - try: - # 创建姿势节点 - pose = cmds.createNode("objectSet", name=f"{name}_pose") - - # 添加目标信息 - cmds.addAttr(pose, ln="targets", dt="string") - target_data = ";".join([f"{n}.{a}:{v}" for n, a, v in targets]) - cmds.setAttr(f"{pose}.targets", target_data, type="string") - - if mirror: - # 创建镜像姿势 - mirror_targets = [] - for node, attr, value in targets: - if "_L_" in node: - mirror_node = node.replace("_L_", "_R_") - elif "_R_" in node: - mirror_node = node.replace("_R_", "_L_") - else: - continue - mirror_targets.append((mirror_node, attr, value)) - - if mirror_targets: - create_expression_pose(f"{name}_mirror", mirror_targets) - - return pose - - except Exception as e: - cmds.warning(f"创建表情姿势失败: {str(e)}") - return None - -def apply_expression_pose(pose, weight=1.0): - """应用表情姿势 - Args: - pose: 姿势节点 - weight: 应用权重 - """ - try: - # 获取目标信息 - target_data = cmds.getAttr(f"{pose}.targets") - for item in target_data.split(";"): - attr_path, value = item.split(":") - node, attr = attr_path.split(".") - - if cmds.objExists(attr_path): - current = cmds.getAttr(attr_path) - target = float(value) - blend = current + (target - current) * weight - cmds.setAttr(attr_path, blend) - - except Exception as e: - cmds.warning(f"应用表情姿势失败: {str(e)}") - -def create_expression_sequence(name, poses, times): - """创建表情序列 - Args: - name: 序列名称 - poses: 姿势列表 - times: 时间列表 - Returns: - str: 创建的序列节点名称 - """ - try: - # 创建序列节点 - sequence = cmds.createNode("objectSet", name=f"{name}_sequence") - - # 添加序列信息 - cmds.addAttr(sequence, ln="poses", dt="string") - pose_data = ";".join([f"{p}:{t}" for p, t in zip(poses, times)]) - cmds.setAttr(f"{sequence}.poses", pose_data, type="string") - - return sequence - - except Exception as e: - cmds.warning(f"创建表情序列失败: {str(e)}") - return None - -def play_expression_sequence(sequence): - """播放表情序列 - Args: - sequence: 序列节点 - """ - try: - # 获取序列信息 - pose_data = cmds.getAttr(f"{sequence}.poses") - for item in pose_data.split(";"): - pose, time = item.split(":") - # 设置时间 - cmds.currentTime(float(time)) - # 应用姿势 - apply_expression_pose(pose) - - except Exception as e: - cmds.warning(f"播放表情序列失败: {str(e)}") - -def create_weight_editor(bs_node): - """创建权重编辑器 - Args: - bs_node: BlendShape节点 - Returns: - str: 创建的编辑器节点名称 - """ - try: - # 创建编辑器节点 - editor = cmds.createNode("objectSet", name=f"{bs_node}_weightEditor") - - # 添加编辑器信息 - cmds.addAttr(editor, ln="blendShape", dt="string") - cmds.setAttr(f"{editor}.blendShape", bs_node, type="string") - - # 获取所有目标 - targets = bs_manager.get_all_targets(bs_node) - - # 添加目标权重属性 - for target in targets: - cmds.addAttr(editor, ln=target, at="double", min=0, max=1, dv=0) - cmds.setAttr(f"{editor}.{target}", e=True, keyable=True) - - # 连接权重 - weight = bs_manager.get_weight(bs_node, target) - cmds.setAttr(f"{editor}.{target}", weight) - - return editor - - except Exception as e: - cmds.warning(f"创建权重编辑器失败: {str(e)}") - return None - -def create_expression_preview(expr_set): - """创建表情预览 - Args: - expr_set: 表情集节点 - Returns: - str: 创建的预览节点名称 - """ - try: - # 创建预览节点 - preview = cmds.createNode("objectSet", name=f"{expr_set}_preview") - - # 添加预览信息 - cmds.addAttr(preview, ln="expression", dt="string") - cmds.setAttr(f"{preview}.expression", expr_set, type="string") - - # 添加预览控制 - cmds.addAttr(preview, ln="weight", at="double", min=0, max=1, dv=0) - cmds.setAttr(f"{preview}.weight", e=True, keyable=True) - - # 添加时间控制 - cmds.addAttr(preview, ln="time", at="time") - cmds.setAttr(f"{preview}.time", e=True, keyable=True) - - # 添加播放控制 - cmds.addAttr(preview, ln="play", at="bool") - cmds.setAttr(f"{preview}.play", e=True, keyable=True) - - return preview - - except Exception as e: - cmds.warning(f"创建表情预览失败: {str(e)}") - return None - -def update_weight_editor(editor): - """更新权重编辑器 - Args: - editor: 编辑器节点 - """ - try: - # 获取BlendShape节点 - bs_node = cmds.getAttr(f"{editor}.blendShape") - - # 获取所有目标 - targets = bs_manager.get_all_targets(bs_node) - - # 更新权重 - for target in targets: - if cmds.objExists(f"{editor}.{target}"): - weight = bs_manager.get_weight(bs_node, target) - cmds.setAttr(f"{editor}.{target}", weight) - - except Exception as e: - cmds.warning(f"更新权重编辑器失败: {str(e)}") - -def update_expression_preview(preview): - """更新表情预览 - Args: - preview: 预览节点 - """ - try: - # 获取表情集 - expr_set = cmds.getAttr(f"{preview}.expression") - - # 获取预览权重 - weight = cmds.getAttr(f"{preview}.weight") - - # 应用表情 - apply_expression_set(expr_set, weight) - - # 检查播放状态 - if cmds.getAttr(f"{preview}.play"): - # 更新时间 - current_time = cmds.getAttr(f"{preview}.time") - cmds.setAttr(f"{preview}.time", current_time + 1) - - # 循环播放 - if current_time >= cmds.playbackOptions(q=True, maxTime=True): - cmds.setAttr(f"{preview}.time", cmds.playbackOptions(q=True, minTime=True)) - - except Exception as e: - cmds.warning(f"更新表情预览失败: {str(e)}") - -def create_weight_curve(bs_node, target): - """创建权重曲线 - Args: - bs_node: BlendShape节点 - target: 目标名称 - Returns: - str: 创建的动画曲线节点名称 - """ - try: - # 创建动画曲线 - curve = cmds.createNode("animCurve", name=f"{target}_weightCurve") - - # 设置曲线类型 - cmds.setAttr(f"{curve}.preInfinity", 1) # Cycle - cmds.setAttr(f"{curve}.postInfinity", 1) # Cycle - - # 添加关键帧 - cmds.setKeyframe(curve, time=0, value=0) - cmds.setKeyframe(curve, time=12, value=1) - cmds.setKeyframe(curve, time=24, value=0) - - # 设置切线类型 - cmds.keyTangent(curve, edit=True, time=(0,24), outTangentType="linear") - cmds.keyTangent(curve, edit=True, time=(0,24), inTangentType="linear") - - return curve - - except Exception as e: - cmds.warning(f"创建权重曲线失败: {str(e)}") - return None - -def apply_weight_curve(curve, bs_node, target): - """应用权重曲线 - Args: - curve: 动画曲线节点 - bs_node: BlendShape节点 - target: 目标名称 - """ - try: - # 获取当前时间 - current_time = cmds.currentTime(q=True) - - # 获取曲线值 - weight = cmds.getValue(curve, time=current_time) - - # 设置权重 - bs_manager.set_weight(bs_node, target, weight) - - except Exception as e: - cmds.warning(f"应用权重曲线失败: {str(e)}") - -def create_expression_combination(name, expressions, weights=None): - """创建表情组合 - Args: - name: 组合名称 - expressions: 表情集列表 - weights: 权重列表(可选) - Returns: - str: 创建的组合节点名称 - """ - try: - # 创建组合节点 - combo = cmds.createNode("objectSet", name=f"{name}_exprCombo") - - # 添加组合信息 - cmds.addAttr(combo, ln="expressions", dt="string") - if weights: - expr_data = ";".join([f"{e}:{w}" for e, w in zip(expressions, weights)]) - else: - expr_data = ";".join([f"{e}:1.0" for e in expressions]) - cmds.setAttr(f"{combo}.expressions", expr_data, type="string") - - # 添加总权重控制 - cmds.addAttr(combo, ln="weight", at="double", min=0, max=1, dv=1) - cmds.setAttr(f"{combo}.weight", e=True, keyable=True) - - return combo - - except Exception as e: - cmds.warning(f"创建表情组合失败: {str(e)}") - return None - -def apply_expression_combination(combo, weight=None): - """应用表情组合 - Args: - combo: 组合节点 - weight: 总权重(可选) - """ - try: - # 获取组合信息 - expr_data = cmds.getAttr(f"{combo}.expressions") - - # 获取总权重 - if weight is None: - weight = cmds.getAttr(f"{combo}.weight") - - # 应用每个表情 - for item in expr_data.split(";"): - expr_set, expr_weight = item.split(":") - apply_expression_set(expr_set, float(expr_weight) * weight) - - except Exception as e: - cmds.warning(f"应用表情组合失败: {str(e)}") - -def optimize_blendshape_weights(bs_node, options=None): - """优化BlendShape权重 - Args: - bs_node: BlendShape节点 - options: 优化选项字典 - """ - try: - if options is None: - options = { - "threshold": 0.001, # 权重阈值 - "normalize": True, # 是否规范化 - "remove_unused": True, # 是否移除未使用的目标 - "clean_history": True # 是否清理历史 - } - - # 获取所有目标 - targets = bs_manager.get_all_targets(bs_node) - - # 移除未使用的目标 - if options["remove_unused"]: - for target in targets: - weight = bs_manager.get_weight(bs_node, target) - if abs(weight) < options["threshold"]: - # 获取目标索引 - target_index = -1 - target_count = cmds.blendShape(bs_node, q=True, weightCount=True) - for i in range(target_count): - alias = cmds.aliasAttr(f"{bs_node}.weight[{i}]", q=True) - if alias == target: - target_index = i - break - - if target_index >= 0: - # 移除目标 - base_mesh = cmds.blendShape(bs_node, q=True, geometry=True)[0] - cmds.blendShape(bs_node, edit=True, remove=True, - target=(base_mesh, target_index, target, 1.0)) - - # 规范化权重 - if options["normalize"]: - for target in targets: - weight = bs_manager.get_weight(bs_node, target) - if abs(weight) > options["threshold"]: - if abs(weight - 1.0) < options["threshold"]: - bs_manager.set_weight(bs_node, target, 1.0) - elif abs(weight) < options["threshold"]: - bs_manager.set_weight(bs_node, target, 0.0) - else: - normalized = round(weight, 3) - bs_manager.set_weight(bs_node, target, normalized) - - # 清理历史 - if options["clean_history"]: - base_mesh = cmds.blendShape(bs_node, q=True, geometry=True)[0] - cmds.delete(base_mesh, constructionHistory=True) - - print(f"权重优化完成: {bs_node}") - - except Exception as e: - cmds.warning(f"权重优化失败: {str(e)}") - -def analyze_blendshape_weights(bs_node): - """分析BlendShape权重 - Args: - bs_node: BlendShape节点 - Returns: - dict: 分析结果 - """ - try: - results = { - "total_targets": 0, - "active_targets": 0, - "unused_targets": 0, - "full_weight_targets": 0, - "partial_weight_targets": 0, - "weight_distribution": {} - } - - # 获取所有目标 - targets = bs_manager.get_all_targets(bs_node) - results["total_targets"] = len(targets) - - # 分析每个目标 - for target in targets: - weight = bs_manager.get_weight(bs_node, target) - - # 统计权重分布 - weight_range = round(weight * 10) / 10 # 取一位小数 - if weight_range in results["weight_distribution"]: - results["weight_distribution"][weight_range] += 1 - else: - results["weight_distribution"][weight_range] = 1 - - # 统计目标类型 - if abs(weight) < 0.001: - results["unused_targets"] += 1 - elif abs(weight - 1.0) < 0.001: - results["full_weight_targets"] += 1 - results["active_targets"] += 1 - elif abs(weight) > 0.001: - results["partial_weight_targets"] += 1 - results["active_targets"] += 1 - - return results - - except Exception as e: - cmds.warning(f"分析权重失败: {str(e)}") - return None \ No newline at end of file diff --git a/scripts/utils/define_utils.py b/scripts/utils/define_utils.py deleted file mode 100644 index 3d19c2f..0000000 --- a/scripts/utils/define_utils.py +++ /dev/null @@ -1,522 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -#===================================== 1. Module Imports ===================================== -import maya.cmds as cmds -import maya.mel as mel -import sys -import os -import json -from scripts.config import data - -try: - from PySide2 import QtCore, QtGui, QtWidgets - from shiboken2 import wrapInstance - print("从PySide2加载Qt和shiboken2") -except ImportError: - try: - from PySide6 import QtCore, QtGui, QtWidgets - from shiboken6 import wrapInstance - print("从PySide6加载Qt和shiboken6") - except ImportError: - try: - from PySide import QtCore, QtGui, QtWidgets - from shiboken import wrapInstance - print("从PySide加载Qt和shiboken") - except ImportError as e: - print(f"Qt加载失败: {str(e)}") - QtCore = QtGui = QtWidgets = None - wrapInstance = None - -#===================================== 2. DNA Definition Class ===================================== -class DNADefinition: - """DNA定义类""" - def __init__(self): - self.file_path = "" - self.content = {} - - def load(self, file_path): - """加载DNA文件""" - if not os.path.exists(file_path): - raise FileNotFoundError(f"DNA文件不存在: {file_path}") - - try: - with open(file_path, "r") as f: - self.content = json.load(f) - self.file_path = file_path - return True - except Exception as e: - cmds.warning(f"加载DNA文件失败: {str(e)}") - return False - - def save(self, file_path=None): - """保存DNA文件""" - save_path = file_path or self.file_path - if not save_path: - cmds.warning("未指定保存路径") - return False - - try: - with open(save_path, "w") as f: - json.dump(self.content, f, indent=4) - return True - except Exception as e: - cmds.warning(f"保存DNA文件失败: {str(e)}") - return False - - def validate(self): - """验证DNA内容""" - required_keys = ["joints", "blendshapes", "description"] - for key in required_keys: - if key not in self.content: - return False - return True - -# 全局DNA定义实例 -dna_definition = DNADefinition() - -def browse_dna_file(): - """浏览DNA文件""" - file_path, _ = cmds.fileDialog2( - fileFilter="DNA Files (*.dna)", - dialogStyle=2, - fileMode=1 - ) - - if not file_path: - return - - # 加载DNA文件 - if dna_definition.load(file_path[0]): - # 更新UI预览 - update_dna_preview() - # 更新骨骼列表 - update_joint_list() - # 更新BlendShape列表 - update_blendshape_list() - -def update_dna_preview(): - """更新DNA预览""" - # 获取UI实例 - from scripts.MetaFusion import app - if not app: - return - - define_tab = app.window.findChild(QtWidgets.QWidget, "DefineTab") - if not define_tab: - return - - # 更新预览内容 - preview_text = json.dumps(dna_definition.content, indent=4) - define_tab.dna_preview.setText(preview_text) - -def update_joint_list(): - """更新骨骼列表""" - from scripts.MetaFusion import app - if not app: - return - - define_tab = app.window.findChild(QtWidgets.QWidget, "DefineTab") - if not define_tab: - return - - # 清空列表 - define_tab.joint_list.clear() - - # 添加骨骼项 - joints = dna_definition.content.get("joints", []) - for joint in joints: - item = QtWidgets.QTreeWidgetItem([ - joint["name"], - str(joint["position"]), - str(joint["rotation"]), - str(joint["scale"]) - ]) - define_tab.joint_list.addTopLevelItem(item) - -def update_blendshape_list(): - """更新BlendShape列表""" - from scripts.MetaFusion import app - if not app: - return - - define_tab = app.window.findChild(QtWidgets.QWidget, "DefineTab") - if not define_tab: - return - - # 清空列表 - define_tab.bs_list.clear() - - # 添加BlendShape项 - blendshapes = dna_definition.content.get("blendshapes", []) - for bs in blendshapes: - item = QtWidgets.QTreeWidgetItem([ - bs["name"], - bs["target"], - str(bs["weight"]) - ]) - define_tab.bs_list.addTopLevelItem(item) - -# 骨骼功能 -def add_joint(): - """添加骨骼""" - # 获取选中的骨骼 - sel = cmds.ls(sl=True, type="joint") - if not sel: - cmds.warning("请先选择要添加的骨骼") - return - - # 获取骨骼信息 - joint = sel[0] - pos = cmds.xform(joint, q=True, ws=True, t=True) - rot = cmds.xform(joint, q=True, ws=True, ro=True) - scl = cmds.xform(joint, q=True, ws=True, s=True) - - # 添加到DNA定义 - joint_data = { - "name": joint, - "position": pos, - "rotation": rot, - "scale": scl - } - - if "joints" not in dna_definition.content: - dna_definition.content["joints"] = [] - dna_definition.content["joints"].append(joint_data) - - # 更新UI - update_joint_list() - -def delete_joint(): - """删除骨骼""" - from scripts.MetaFusion import app - if not app: - return - - define_tab = app.window.findChild(QtWidgets.QWidget, "DefineTab") - if not define_tab: - return - - # 获取选中项 - items = define_tab.joint_list.selectedItems() - if not items: - cmds.warning("请先选择要删除的骨骼") - return - - # 从DNA定义中删除 - for item in items: - joint_name = item.text(0) - joints = dna_definition.content.get("joints", []) - dna_definition.content["joints"] = [ - j for j in joints if j["name"] != joint_name - ] - - # 更新UI - update_joint_list() - -def modify_joint(): - """修改骨骼""" - from scripts.MetaFusion import app - if not app: - return - - define_tab = app.window.findChild(QtWidgets.QWidget, "DefineTab") - if not define_tab: - return - - # 获取选中项 - items = define_tab.joint_list.selectedItems() - if not items: - cmds.warning("请先选择要修改的骨骼") - return - - # 获取场景中选中的骨骼 - sel = cmds.ls(sl=True, type="joint") - if not sel: - cmds.warning("请先选择要用于更新的骨骼") - return - - # 更新骨骼信息 - joint = sel[0] - pos = cmds.xform(joint, q=True, ws=True, t=True) - rot = cmds.xform(joint, q=True, ws=True, ro=True) - scl = cmds.xform(joint, q=True, ws=True, s=True) - - # 更新DNA定义 - joint_name = items[0].text(0) - joints = dna_definition.content.get("joints", []) - for j in joints: - if j["name"] == joint_name: - j["position"] = pos - j["rotation"] = rot - j["scale"] = scl - break - - # 更新UI - update_joint_list() - -# BlendShape功能 -def add_blendshape(): - """添加BlendShape""" - # 获取选中的BlendShape - sel = cmds.ls(sl=True, type="blendShape") - if not sel: - cmds.warning("请先选择要添加的BlendShape") - return - - # 获取BlendShape信息 - bs = sel[0] - target = cmds.blendShape(bs, q=True, g=True)[0] - weight = cmds.getAttr(f"{bs}.weight[0]") - - # 添加到DNA定义 - bs_data = { - "name": bs, - "target": target, - "weight": weight - } - - if "blendshapes" not in dna_definition.content: - dna_definition.content["blendshapes"] = [] - dna_definition.content["blendshapes"].append(bs_data) - - # 更新UI - update_blendshape_list() - -def delete_blendshape(): - """删除BlendShape""" - from scripts.MetaFusion import app - if not app: - return - - define_tab = app.window.findChild(QtWidgets.QWidget, "DefineTab") - if not define_tab: - return - - # 获取选中项 - items = define_tab.bs_list.selectedItems() - if not items: - cmds.warning("请先选择要删除的BlendShape") - return - - # 从DNA定义中删除 - for item in items: - bs_name = item.text(0) - blendshapes = dna_definition.content.get("blendshapes", []) - dna_definition.content["blendshapes"] = [ - bs for bs in blendshapes if bs["name"] != bs_name - ] - - # 更新UI - update_blendshape_list() - -def modify_blendshape(): - """修改BlendShape""" - from scripts.MetaFusion import app - if not app: - return - - define_tab = app.window.findChild(QtWidgets.QWidget, "DefineTab") - if not define_tab: - return - - # 获取选中项 - items = define_tab.bs_list.selectedItems() - if not items: - cmds.warning("请先选择要修改的BlendShape") - return - - # 获取场景中选中的BlendShape - sel = cmds.ls(sl=True, type="blendShape") - if not sel: - cmds.warning("请先选择要用于更新的BlendShape") - return - - # 更新BlendShape信息 - bs = sel[0] - target = cmds.blendShape(bs, q=True, g=True)[0] - weight = cmds.getAttr(f"{bs}.weight[0]") - - # 更新DNA定义 - bs_name = items[0].text(0) - blendshapes = dna_definition.content.get("blendshapes", []) - for b in blendshapes: - if b["name"] == bs_name: - b["target"] = target - b["weight"] = weight - break - - # 更新UI - update_blendshape_list() - -# DNA相关功能 -def load_dna_preview(dna_file): - """加载DNA预览 - Args: - dna_file: DNA文件路径 - Returns: - bool: 是否加载成功 - """ - try: - # 加载DNA文件 - if not dna_definition.load(dna_file): - return False - - # 更新UI预览 - update_dna_preview() - return True - - except Exception as e: - cmds.warning(f"加载DNA预览失败: {str(e)}") - return False - -def validate_dna(dna_file): - """验证DNA文件 - Args: - dna_file: DNA文件路径 - Returns: - bool: 是否验证通过 - """ - try: - # 加载DNA文件 - if not dna_definition.load(dna_file): - return False - - # 验证必要字段 - if not dna_definition.validate(): - cmds.warning("DNA文件缺少必要字段") - return False - - # 验证骨骼数据 - joints = dna_definition.content.get("joints", []) - for joint in joints: - required = ["name", "position", "rotation", "scale"] - if not all(key in joint for key in required): - cmds.warning(f"骨骼数据不完整: {joint.get('name', 'unknown')}") - return False - - # 验证BlendShape数据 - blendshapes = dna_definition.content.get("blendshapes", []) - for bs in blendshapes: - required = ["name", "target", "weight"] - if not all(key in bs for key in required): - cmds.warning(f"BlendShape数据不完整: {bs.get('name', 'unknown')}") - return False - - return True - - except Exception as e: - cmds.warning(f"验证DNA文件失败: {str(e)}") - return False - -def export_dna_definition(dna_file): - """导出DNA定义 - Args: - dna_file: 导出文件路径 - Returns: - bool: 是否导出成功 - """ - try: - # 收集场景中的骨骼数据 - joints = [] - for joint in cmds.ls(type="joint"): - pos = cmds.xform(joint, q=True, ws=True, t=True) - rot = cmds.xform(joint, q=True, ws=True, ro=True) - scl = cmds.xform(joint, q=True, ws=True, s=True) - - joints.append({ - "name": joint, - "position": pos, - "rotation": rot, - "scale": scl - }) - - # 收集场景中的BlendShape数据 - blendshapes = [] - for bs in cmds.ls(type="blendShape"): - target = cmds.blendShape(bs, q=True, g=True)[0] - weight = cmds.getAttr(f"{bs}.weight[0]") - - blendshapes.append({ - "name": bs, - "target": target, - "weight": weight - }) - - # 更新DNA内容 - dna_definition.content.update({ - "joints": joints, - "blendshapes": blendshapes, - "description": { - "name": cmds.file(q=True, sn=True, shn=True), - "version": data.TOOL_VERSION, - "author": data.TOOL_AUTHOR, - "date": cmds.date(format="YYYY-MM-DD HH:mm:ss") - } - }) - - # 保存文件 - return dna_definition.save(dna_file) - - except Exception as e: - cmds.warning(f"导出DNA定义失败: {str(e)}") - return False - -def import_dna_definition(dna_file): - """导入DNA定义 - Args: - dna_file: DNA文件路径 - Returns: - bool: 是否导入成功 - """ - try: - # 验证DNA文件 - if not validate_dna(dna_file): - return False - - # 导入骨骼 - joints = dna_definition.content.get("joints", []) - for joint_data in joints: - # 检查骨骼是否存在 - joint_name = joint_data["name"] - if not cmds.objExists(joint_name): - # 创建骨骼 - joint = cmds.joint(name=joint_name) - else: - joint = joint_name - - # 设置变换 - cmds.xform(joint, - ws=True, - t=joint_data["position"], - ro=joint_data["rotation"], - s=joint_data["scale"] - ) - - # 导入BlendShape - blendshapes = dna_definition.content.get("blendshapes", []) - for bs_data in blendshapes: - # 检查目标是否存在 - if not cmds.objExists(bs_data["target"]): - cmds.warning(f"BlendShape目标不存在: {bs_data['target']}") - continue - - # 创建或获取BlendShape - bs_name = bs_data["name"] - if not cmds.objExists(bs_name): - bs = cmds.blendShape( - bs_data["target"], - name=bs_name, - frontOfChain=True - )[0] - else: - bs = bs_name - - # 设置权重 - cmds.setAttr(f"{bs}.weight[0]", bs_data["weight"]) - - return True - - except Exception as e: - cmds.warning(f"导入DNA定义失败: {str(e)}") - return False \ No newline at end of file diff --git a/scripts/utils/menu_utils.py b/scripts/utils/menu_utils.py deleted file mode 100644 index 9b4f127..0000000 --- a/scripts/utils/menu_utils.py +++ /dev/null @@ -1,193 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -#===================================== 1. Module Imports ===================================== -import maya.cmds as cmds -import maya.mel as mel -import sys -import os -from scripts.config import data -from scripts.utils import model_utils, rigging_utils, adjust_utils, define_utils - -#===================================== 2. Menu Utils ===================================== -def load_dna(): - """打开DNA文件""" - file_path = cmds.fileDialog2( - fileFilter="DNA Files (*.dna)", - dialogStyle=2, - fileMode=1 - ) - if file_path: - print(f"加载DNA文件: {file_path[0]}") - # TODO: 实现DNA加载逻辑 - -def save_dna(): - """保存DNA文件""" - file_path = cmds.fileDialog2( - fileFilter="DNA Files (*.dna)", - dialogStyle=2, - fileMode=0 - ) - if file_path: - print(f"保存DNA文件: {file_path[0]}") - # TODO: 实现DNA保存逻辑 - -def load_project_dna(): - """加载当前项目的DNA""" - print("加载当前项目DNA功能待实现") - -def rename_blend_target(): - """修改混合目标名称""" - print("修改混合目标名称功能待实现") - -def reset_blend_target(): - """重置混合目标名称""" - print("重置混合目标名称功能待实现") - -def export_fbx(): - """导出FBX""" - file_path = cmds.fileDialog2( - fileFilter="FBX Files (*.fbx)", - dialogStyle=2, - fileMode=0 - ) - if file_path: - print(f"导出FBX文件: {file_path[0]}") - # TODO: 实现FBX导出逻辑 - -def safe_shutdown(): - """安全退出""" - # 保存当前状态 - save_dna() - # 关闭窗口 - cmds.deleteUI(data.TOOL_WSCL_NAME) - -# 编辑菜单功能 -def create_rl4_node(): - """创建RL4节点""" - print("创建RL4节点功能待实现") - -def delete_rl4_node(): - """删除RL4节点""" - print("删除RL4节点功能待实现") - -def mirror_left_to_right(): - """镜像左至右""" - print("镜像左至右功能待实现") - -def mirror_right_to_left(): - """镜像右至左""" - print("镜像右至左功能待实现") - -def pose_a_to_t(): - """姿势由A型转T型""" - print("姿势由A型转T型功能待实现") - -def pose_t_to_a(): - """姿势由T型转A型""" - print("姿势由T型转A型功能待实现") - -def transfer_lod_texture(): - """传输LOD贴图""" - print("传输LOD贴图功能待实现") - -def set_joint_color(): - """设置关节颜色""" - print("设置关节颜色功能待实现") - -def unmark_all(): - """取消全部标记""" - print("取消全部标记功能待实现") - -def rebuild_all_targets(): - """重建所有目标""" - print("重建所有目标功能待实现") - -def bake_all_animations(): - """为所有表情设置关键帧""" - print("为所有表情设置关键帧功能待实现") - -def bake_all_keyframes(): - """烘焙所有表情的关键帧""" - print("烘焙所有表情的关键帧功能待实现") - -# 工具菜单功能 -def export_skin(): - """导出蒙皮""" - print("导出蒙皮功能待实现") - -def import_skin(): - """导入蒙皮""" - print("导入蒙皮功能待实现") - -def copy_skin(): - """拷贝蒙皮""" - print("拷贝蒙皮功能待实现") - -def create_rbf_deformer(): - """创建RBF变形器""" - print("创建RBF变形器功能待实现") - -def quick_bind_clothing(): - """快速绑定服装""" - print("快速绑定服装功能待实现") - -def clone_blendshape(): - """克隆混合变形""" - print("克隆混合变形功能待实现") - -def transfer_uv_order(): - """UV传递点序""" - print("UV传递点序功能待实现") - -def create_face_controller(): - """面部生成控制器""" - print("面部生成控制器功能待实现") - -def extract_52bs(): - """提取52BS""" - print("提取52BS功能待实现") - -def fix_joint_orientation(): - """关节轴向修复""" - print("关节轴向修复功能待实现") - -def create_body_controller(): - """生成身体控制器""" - print("生成身体控制器功能待实现") - -def import_face_animation(): - """导入面部动画""" - print("导入面部动画功能待实现") - -def import_body_animation(): - """导入身体动画""" - print("导入身体动画功能待实现") - -# 语言菜单功能 -def set_chinese(): - """设置中文界面""" - print("设置中文界面功能待实现") - -def set_english(): - """设置英文界面""" - print("设置英文界面功能待实现") - -# 帮助菜单功能 -def show_help(): - """显示帮助文档""" - if os.path.exists(data.TOOL_HELP_URL): - os.startfile(data.TOOL_HELP_URL) - else: - cmds.warning("帮助文档不存在") - -def show_about(): - """显示关于对话框""" - cmds.confirmDialog( - title="关于", - message=f"{data.TOOL_NAME} {data.TOOL_VERSION}\n" - f"作者: {data.TOOL_AUTHOR}\n" - f"帮助: {data.TOOL_HELP_URL}", - button=["确定"], - defaultButton="确定" - ) \ No newline at end of file diff --git a/scripts/utils/model_utils.py b/scripts/utils/model_utils.py deleted file mode 100644 index c87a484..0000000 --- a/scripts/utils/model_utils.py +++ /dev/null @@ -1,599 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -#===================================== 1. Module Imports ===================================== -import maya.cmds as cmds -import maya.mel as mel -import sys -import os - -#===================================== 2. Model Utils ===================================== -def load_model(lod_index, model_type, file_path): - """加载模型 - Args: - lod_index: LOD级别(0-7) - model_type: 模型类型(head/teeth/gums/eye_l/eye_r/iris/eyelash/eyelid/cartilage/body) - file_path: 模型文件路径 - """ - try: - # 检查文件是否存在 - if not os.path.exists(file_path): - raise FileNotFoundError(f"模型文件不存在: {file_path}") - - # 导入模型 - imported_nodes = cmds.file( - file_path, - i=True, - returnNewNodes=True, - namespace=f"LOD{lod_index}_{model_type}" - ) - - # 设置模型属性 - for node in imported_nodes: - if cmds.objectType(node) == "transform": - # 添加自定义属性 - cmds.addAttr(node, ln="LOD_INDEX", at="long", dv=lod_index) - cmds.addAttr(node, ln="MODEL_TYPE", dt="string") - cmds.setAttr(f"{node}.MODEL_TYPE", model_type, type="string") - - print(f"成功加载LOD{lod_index}_{model_type}模型") - return imported_nodes - - except Exception as e: - cmds.warning(f"加载模型失败: {str(e)}") - return None - -def delete_lod(lod_index): - """删除指定LOD级别的所有模型""" - try: - # 查找指定LOD的所有模型 - lod_nodes = cmds.ls(f"LOD{lod_index}_*", long=True) - if lod_nodes: - cmds.delete(lod_nodes) - print(f"成功删除LOD{lod_index}的所有模型") - else: - print(f"未找到LOD{lod_index}的模型") - except Exception as e: - cmds.warning(f"删除LOD{lod_index}失败: {str(e)}") - -# 模型工具功能 -def split_model(): - """分离选中的模型""" - try: - selection = cmds.ls(sl=True, type="transform") - if not selection: - raise ValueError("请先选择要分离的模型") - - for obj in selection: - # 获取模型的面数 - mesh = cmds.listRelatives(obj, shapes=True, type="mesh") - if not mesh: - continue - - # 分离每个面为独立的模型 - cmds.polySeparate(obj, ch=False) - - print("模型分离完成") - - except Exception as e: - cmds.warning(f"模型分离失败: {str(e)}") - -def generate_facial_accessories(): - """生成面部配件""" - print("生成面部配件功能待实现") - -def repair_normals(): - """修复法线""" - try: - selection = cmds.ls(sl=True, type="transform") - if not selection: - raise ValueError("请先选择要修复法线的模型") - - for obj in selection: - mesh = cmds.listRelatives(obj, shapes=True, type="mesh") - if not mesh: - continue - - # 统一法线方向 - cmds.polyNormal(obj, normalMode=2, userNormalMode=0) - # 解锁法线 - cmds.polyNormalPerVertex(obj, ufn=True) - - print("法线修复完成") - - except Exception as e: - cmds.warning(f"修复法线失败: {str(e)}") - -def repair_vertex_order(): - """修复点序""" - try: - selection = cmds.ls(sl=True, type="transform") - if not selection: - raise ValueError("请先选择要修复点序的模型") - - for obj in selection: - # 重新排序顶点 - cmds.polyReorder(obj) - - print("点序修复完成") - - except Exception as e: - cmds.warning(f"修复点序失败: {str(e)}") - -def repair_seams(): - """修复接缝""" - try: - selection = cmds.ls(sl=True, type="transform") - if not selection: - raise ValueError("请先选择要修复接缝的模型") - - for obj in selection: - # 合并重叠顶点 - cmds.polyMergeVertex(obj, d=0.001) - # 删除重复面 - cmds.polyRemoveFace(obj, removeDuplicateFaces=True) - - print("接缝修复完成") - - except Exception as e: - cmds.warning(f"修复接缝失败: {str(e)}") - -# LOD功能 -def load_custom_models(): - """自定义加载模型""" - print("自定义加载模型功能待实现") - -def standardize_naming(): - """标准化命名""" - try: - selection = cmds.ls(sl=True, type="transform") - if not selection: - raise ValueError("请先选择要标准化命名的模型") - - for obj in selection: - # 获取LOD信息 - if cmds.attributeQuery("LOD_INDEX", node=obj, exists=True): - lod_index = cmds.getAttr(f"{obj}.LOD_INDEX") - model_type = cmds.getAttr(f"{obj}.MODEL_TYPE") - # 重命名为标准格式 - new_name = f"LOD{lod_index}_{model_type}" - cmds.rename(obj, new_name) - - print("命名标准化完成") - - except Exception as e: - cmds.warning(f"标准化命名失败: {str(e)}") - -def auto_group(): - """自动分组""" - try: - # 创建LOD组 - for i in range(8): - group_name = f"LOD{i}_GROUP" - if not cmds.objExists(group_name): - cmds.group(empty=True, name=group_name) - - # 将模型移动到对应组 - all_models = cmds.ls("LOD*_*", type="transform") - for model in all_models: - if cmds.attributeQuery("LOD_INDEX", node=model, exists=True): - lod_index = cmds.getAttr(f"{model}.LOD_INDEX") - group_name = f"LOD{lod_index}_GROUP" - cmds.parent(model, group_name) - - print("自动分组完成") - - except Exception as e: - cmds.warning(f"自动分组失败: {str(e)}") - -# MetaHuman模型特定功能 -def validate_metahuman_topology(): - """验证模型是否符合MetaHuman拓扑要求""" - try: - selection = cmds.ls(sl=True, type="transform") - if not selection: - raise ValueError("请先选择要验证的模型") - - results = [] - for obj in selection: - mesh = cmds.listRelatives(obj, shapes=True, type="mesh") - if not mesh: - continue - - # 获取模型信息 - vertex_count = cmds.polyEvaluate(obj, vertex=True) - face_count = cmds.polyEvaluate(obj, face=True) - - # 检查是否符合MetaHuman标准 - is_valid = True - messages = [] - - # 获取LOD级别 - lod_index = -1 - if cmds.attributeQuery("LOD_INDEX", node=obj, exists=True): - lod_index = cmds.getAttr(f"{obj}.LOD_INDEX") - - # 根据LOD级别验证顶点数和面数 - if lod_index == 0: - if vertex_count != 7127: - messages.append(f"顶点数不符合LOD0标准(当前:{vertex_count}, 应为:7127)") - is_valid = False - elif lod_index == 1: - if vertex_count != 5127: - messages.append(f"顶点数不符合LOD1标准(当前:{vertex_count}, 应为:5127)") - is_valid = False - # ... 其他LOD级别的验证 - - results.append({ - "object": obj, - "is_valid": is_valid, - "messages": messages - }) - - # 显示验证结果 - for result in results: - status = "✓" if result["is_valid"] else "✗" - print(f"{status} {result['object']}:") - if not result["is_valid"]: - for msg in result["messages"]: - print(f" - {msg}") - - return results - - except Exception as e: - cmds.warning(f"拓扑验证失败: {str(e)}") - return None - -def transfer_uvs(): - """传递UV到其他LOD级别""" - try: - # 获取源模型和目标模型 - selection = cmds.ls(sl=True, type="transform") - if len(selection) != 2: - raise ValueError("请选择一个源模型和一个目标模型") - - source = selection[0] - target = selection[1] - - # 执行UV传递 - cmds.transferAttributes( - source, - target, - transferUVs=2, - transferColors=0, - sampleSpace=4 # World space - ) - - # 删除构建历史 - cmds.delete(target, ch=True) - - print(f"UV传递完成: {source} -> {target}") - - except Exception as e: - cmds.warning(f"UV传递失败: {str(e)}") - -def generate_lod_chain(): - """生成完整的LOD链""" - try: - # 获取选中的LOD0模型 - selection = cmds.ls(sl=True, type="transform") - if not selection: - raise ValueError("请先选择LOD0模型") - - source = selection[0] - if not cmds.attributeQuery("LOD_INDEX", node=source, exists=True): - raise ValueError("请选择带有LOD属性的模型") - - lod_index = cmds.getAttr(f"{source}.LOD_INDEX") - if lod_index != 0: - raise ValueError("请选择LOD0模型") - - # 为每个LOD级别生成简化模型 - for i in range(1, 8): - target_name = source.replace("LOD0", f"LOD{i}") - - # 复制模型 - duplicate = cmds.duplicate(source, name=target_name)[0] - - # 设置LOD属性 - cmds.setAttr(f"{duplicate}.LOD_INDEX", i) - - # 简化模型 - reduction_ratio = 1.0 - (i * 0.1) # 每级减少10% - cmds.polyReduce( - duplicate, - percentage=reduction_ratio * 100, - triangulate=False, - preserveTopology=True, - keepQuadsWeight=1.0, - keepBorder=True - ) - - print(f"生成LOD{i}完成: {target_name}") - - print("LOD链生成完成") - - except Exception as e: - cmds.warning(f"LOD链生成失败: {str(e)}") - -def check_model_symmetry(): - """检查模型对称性""" - try: - selection = cmds.ls(sl=True, type="transform") - if not selection: - raise ValueError("请先选择要检查的模型") - - for obj in selection: - # 获取顶点位置 - vertices = cmds.ls(f"{obj}.vtx[*]", flatten=True) - vertex_positions = {} - - for vtx in vertices: - pos = cmds.pointPosition(vtx, world=True) - # 使用x坐标的绝对值作为键,保存顶点信息 - x_abs = abs(pos[0]) - if x_abs not in vertex_positions: - vertex_positions[x_abs] = [] - vertex_positions[x_abs].append((vtx, pos)) - - # 检查对称性 - asymmetric_vertices = [] - for x_abs, points in vertex_positions.items(): - if len(points) == 2: - vtx1, pos1 = points[0] - vtx2, pos2 = points[1] - # 检查y和z坐标是否对称 - if abs(pos1[1] - pos2[1]) > 0.001 or abs(pos1[2] - pos2[2]) > 0.001: - asymmetric_vertices.extend([vtx1, vtx2]) - elif len(points) == 1 and x_abs > 0.001: # 忽略中心线上的点 - asymmetric_vertices.append(points[0][0]) - - # 显示结果 - if asymmetric_vertices: - cmds.select(asymmetric_vertices) - print(f"发现{len(asymmetric_vertices)}个不对称顶点") - else: - print(f"{obj}模型对称性检查通过") - - except Exception as e: - cmds.warning(f"对称性检查失败: {str(e)}") - -def mirror_geometry(): - """镜像几何体""" - try: - selection = cmds.ls(sl=True, type="transform") - if not selection: - raise ValueError("请先选择要镜像的模型") - - for obj in selection: - # 创建镜像几何体 - mirrored = cmds.duplicate(obj, name=f"{obj}_mirrored")[0] - - # 执行镜像 - cmds.scale(-1, 1, 1, mirrored) - - # 反转法线 - cmds.polyNormal(mirrored, normalMode=0) - - print(f"模型镜像完成: {mirrored}") - - except Exception as e: - cmds.warning(f"模型镜像失败: {str(e)}") - -# 骨骼权重相关功能 -def transfer_skin_weights(): - """传递蒙皮权重""" - try: - selection = cmds.ls(sl=True, type="transform") - if len(selection) < 2: - raise ValueError("请选择源模型和目标模型") - - source = selection[0] - targets = selection[1:] - - # 获取源模型的蒙皮变形器 - skin_cluster = mel.eval(f'findRelatedSkinCluster("{source}")') - if not skin_cluster: - raise ValueError(f"源模型{source}没有蒙皮变形器") - - # 获取骨骼列表 - joints = cmds.skinCluster(skin_cluster, q=True, inf=True) - - # 为每个目标模型创建蒙皮并传递权重 - for target in targets: - # 创建新的蒙皮变形器 - new_skin = cmds.skinCluster( - joints, - target, - toSelectedBones=True, - bindMethod=0, - skinMethod=0, - normalizeWeights=1 - )[0] - - # 复制权重 - cmds.copySkinWeights( - ss=skin_cluster, - ds=new_skin, - noMirror=True, - surfaceAssociation="closestPoint", - influenceAssociation="oneToOne" - ) - - print(f"权重传递完成: {source} -> {target}") - - except Exception as e: - cmds.warning(f"权重传递失败: {str(e)}") - -def mirror_skin_weights(): - """镜像蒙皮权重""" - try: - selection = cmds.ls(sl=True, type="transform") - if not selection: - raise ValueError("请选择要镜像权重的模型") - - for obj in selection: - # 获取蒙皮变形器 - skin_cluster = mel.eval(f'findRelatedSkinCluster("{obj}")') - if not skin_cluster: - continue - - # 执行镜像 - cmds.copySkinWeights( - ss=skin_cluster, - ds=skin_cluster, - mirrorMode="YZ", - surfaceAssociation="closestPoint", - influenceAssociation="closestJoint" - ) - - print(f"权重镜像完成: {obj}") - - except Exception as e: - cmds.warning(f"权重镜像失败: {str(e)}") - -def clean_skin_weights(): - """清理蒙皮权重""" - try: - selection = cmds.ls(sl=True, type="transform") - if not selection: - raise ValueError("请选择要清理权重的模型") - - for obj in selection: - # 获取蒙皮变形器 - skin_cluster = mel.eval(f'findRelatedSkinCluster("{obj}")') - if not skin_cluster: - continue - - # 移除小权重 - cmds.skinPercent( - skin_cluster, - obj, - pruneWeights=0.01 # 移除小于1%的权重 - ) - - # 规范化权重 - cmds.skinPercent( - skin_cluster, - obj, - normalize=True - ) - - print(f"权重清理完成: {obj}") - - except Exception as e: - cmds.warning(f"权重清理失败: {str(e)}") - -# 变形器处理功能 -def transfer_blendshapes(): - """传递变形器""" - try: - selection = cmds.ls(sl=True, type="transform") - if len(selection) < 2: - raise ValueError("请选择源模型和目标模型") - - source = selection[0] - targets = selection[1:] - - # 获取源模型的变形器 - blendshapes = cmds.listConnections(source, type="blendShape") - if not blendshapes: - raise ValueError(f"源模型{source}没有变形器") - - for bs in blendshapes: - # 获取所有目标形状 - targets_count = cmds.blendShape(bs, q=True, target=True) - target_weights = [] - target_names = [] - - # 保存当前权重 - for i in range(targets_count): - weight = cmds.getAttr(f"{bs}.weight[{i}]") - name = cmds.aliasAttr(f"{bs}.weight[{i}]", q=True) - target_weights.append(weight) - target_names.append(name) - - # 为每个目标模型创建变形器 - for target in targets: - new_bs = cmds.blendShape( - target, - frontOfChain=True, - name=f"{target}_blendShape" - )[0] - - # 设置权重 - for i, (weight, name) in enumerate(zip(target_weights, target_names)): - cmds.setAttr(f"{new_bs}.{name}", weight) - - print(f"变形器传递完成: {source} -> {targets}") - - except Exception as e: - cmds.warning(f"变形器传递失败: {str(e)}") - -def optimize_blendshapes(): - """优化变形器""" - try: - selection = cmds.ls(sl=True, type="transform") - if not selection: - raise ValueError("请选择要优化变形器的模型") - - for obj in selection: - # 获取变形器 - blendshapes = cmds.listConnections(obj, type="blendShape") - if not blendshapes: - continue - - for bs in blendshapes: - # 获取所有目标形状 - target_count = cmds.blendShape(bs, q=True, target=True) - - # 删除未使用的目标形状 - for i in range(target_count): - weight = cmds.getAttr(f"{bs}.weight[{i}]") - if abs(weight) < 0.001: # 权重接近0的目标形状 - cmds.removeMultiInstance(f"{bs}.weight[{i}]", b=True) - - # 重新排序索引 - cmds.blendShape(bs, edit=True, resetTargetDelta=True) - - print(f"变形器优化完成: {obj}") - - except Exception as e: - cmds.warning(f"变形器优化失败: {str(e)}") - -def create_corrective_blendshape(): - """创建修正变形器""" - try: - selection = cmds.ls(sl=True, type="transform") - if len(selection) != 2: - raise ValueError("请选择基础模型和目标形状") - - base = selection[0] - target = selection[1] - - # 创建修正变形器 - corrective_bs = cmds.blendShape( - target, - base, - frontOfChain=True, - name=f"{base}_corrective" - )[0] - - # 设置权重驱动 - cmds.setDrivenKeyframe( - f"{corrective_bs}.{target}", - currentDriver="time", - driverValue=0, - value=0 - ) - cmds.setDrivenKeyframe( - f"{corrective_bs}.{target}", - currentDriver="time", - driverValue=1, - value=1 - ) - - print(f"修正变形器创建完成: {corrective_bs}") - - except Exception as e: - cmds.warning(f"创建修正变形器失败: {str(e)}") \ No newline at end of file diff --git a/scripts/utils/rigging_utils.py b/scripts/utils/rigging_utils.py deleted file mode 100644 index 25fd11e..0000000 --- a/scripts/utils/rigging_utils.py +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -#===================================== 1. Module Imports ===================================== -import maya.cmds as cmds -import maya.mel as mel -import sys -import os - -#===================================== 2. Rigging Utils ===================================== -def export_settings(): - """导出设置""" - print("导出设置功能待实现") - -def import_settings(): - """导入设置""" - print("导入设置功能待实现") - -def clear_options(): - """清空选项""" - print("清空选项功能待实现") - -def import_skeleton(): - """导入骨架""" - print("导入骨架功能待实现") - -def create_skeleton(): - """创建骨架""" - print("创建骨架功能待实现") - -# 其他功能函数... \ No newline at end of file