This commit is contained in:
Jeffreytsai1004 2025-02-06 19:49:53 +08:00
parent 2c777c7418
commit 270f5702d9
22 changed files with 1660 additions and 5003 deletions

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -15,33 +15,39 @@ from scripts.config import data
try: try:
from PySide2 import QtCore, QtGui, QtWidgets from PySide2 import QtCore, QtGui, QtWidgets
from shiboken2 import wrapInstance from shiboken2 import wrapInstance
print("从PySide2加载Qt和shiboken2")
except ImportError: except ImportError:
try: try:
from PySide6 import QtCore, QtGui, QtWidgets from PySide6 import QtCore, QtGui, QtWidgets
from shiboken6 import wrapInstance from shiboken6 import wrapInstance
print("从PySide6加载Qt和shiboken6")
except ImportError: except ImportError:
try: try:
from PySide import QtCore, QtGui, QtWidgets from PySide import QtCore, QtGui, QtWidgets
from shiboken import wrapInstance from shiboken import wrapInstance
print("从PySide加载Qt和shiboken")
except ImportError as e: except ImportError as e:
print(f"Qt加载失败: {str(e)}")
QtCore = QtGui = QtWidgets = None QtCore = QtGui = QtWidgets = None
wrapInstance = None wrapInstance = None
# 导入UI模块 # 导入UI模块
from scripts.ui.menu import MenuManager from scripts.ui.menu import MenuManager
from scripts.ui.models import ModelTab from scripts.ui.mesh import MeshTab
from scripts.ui.rigging import RigTab from scripts.ui.rigging import RiggingTab
from scripts.ui.adjust import AdjustTab from scripts.ui.adjust import AdjustTab
from scripts.ui.define import DefineTab from scripts.ui.define import DefineTab
from scripts.ui.widgets import ModernTabWidget
#===================================== 2. Main Window Class ===================================== #===================================== 2. Main Window Class =====================================
class MainWindow(QtWidgets.QMainWindow): class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None): def __init__(self, parent=None):
super(MainWindow, self).__init__(parent) 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() self.setup_core_components()
def setup_core_components(self): def setup_core_components(self):
@ -61,19 +67,41 @@ class MainWindow(QtWidgets.QMainWindow):
def init_ui(self): def init_ui(self):
"""初始化UI框架""" """初始化UI框架"""
self.setWindowTitle(f"{data.TOOL_NAME} {data.TOOL_VERSION}") self.setWindowTitle(f"{data.TOOL_NAME} {data.TOOL_VERSION}")
self.setMinimumSize(1200, 800)
# 主窗口布局 # 主窗口布局
main_widget = QtWidgets.QWidget() main_widget = QtWidgets.QWidget()
self.setCentralWidget(main_widget) self.setCentralWidget(main_widget)
main_layout = QtWidgets.QVBoxLayout(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.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): def load_styles(self):
"""加载样式表""" """加载样式表"""
@ -81,23 +109,6 @@ class MainWindow(QtWidgets.QMainWindow):
with open(data.TOOL_STYLE_FILE, "r", encoding="utf-8") as f: with open(data.TOOL_STYLE_FILE, "r", encoding="utf-8") as f:
self.setStyleSheet(f.read()) 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(): def get_maya_window():
"""获取Maya主窗口""" """获取Maya主窗口"""

View File

@ -4,6 +4,7 @@
import os import os
import sys import sys
import maya.cmds as cmds import maya.cmds as cmds
import datetime
# Base Information # Base Information
TOOL_NAME = str("MetaFusion") TOOL_NAME = str("MetaFusion")
@ -12,6 +13,7 @@ TOOL_AUTHOR = str("CGNICO")
TOOL_LANG = str('en_US') TOOL_LANG = str('en_US')
TOOL_WSCL_NAME = str(f"{TOOL_NAME}WorkSpaceControl") TOOL_WSCL_NAME = str(f"{TOOL_NAME}WorkSpaceControl")
TOOL_HELP_URL = str(f"https://gitea.cgnico.com/CGNICO/{TOOL_NAME}/wiki") TOOL_HELP_URL = str(f"https://gitea.cgnico.com/CGNICO/{TOOL_NAME}/wiki")
TOOL_YEAR = str(datetime.datetime.now().year)
# BASE_PATH # BASE_PATH
TOOL_PATH = str(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))).replace("\\", "/")) TOOL_PATH = str(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))).replace("\\", "/"))

View File

@ -1,4 +1,26 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- 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

View File

@ -6,200 +6,203 @@ import maya.cmds as cmds
import maya.mel as mel import maya.mel as mel
import sys import sys
import os import os
from scripts.config import data
from scripts.ui.widgets import (BaseWidget, BlendShapeList, BlendShapeControls, BlendShapeTools, IconButton, SliderWithValue) from scripts.ui import adjust_utils as adjust_utils
from scripts.ui import widgets as widgets
try: try:
from PySide2 import QtCore, QtGui, QtWidgets from PySide2 import QtCore, QtGui, QtWidgets
from shiboken2 import wrapInstance from shiboken2 import wrapInstance
print("从PySide2加载Qt和shiboken2")
except ImportError: except ImportError:
try: try:
from PySide6 import QtCore, QtGui, QtWidgets from PySide6 import QtCore, QtGui, QtWidgets
from shiboken6 import wrapInstance from shiboken6 import wrapInstance
print("从PySide6加载Qt和shiboken6")
except ImportError: except ImportError:
try: try:
from PySide import QtCore, QtGui, QtWidgets from PySide import QtCore, QtGui, QtWidgets
from shiboken import wrapInstance from shiboken import wrapInstance
print("从PySide加载Qt和shiboken")
except ImportError as e: except ImportError as e:
print(f"Qt加载失败: {str(e)}")
QtCore = QtGui = QtWidgets = None QtCore = QtGui = QtWidgets = None
wrapInstance = None wrapInstance = None
#===================================== 2. Adjust Tab Class ===================================== #===================================== 2. Adjust Tab Class =====================================
class AdjustTab(BaseWidget): class AdjustTab(widgets.BaseWidget):
"""调整标签页""" """调整标签页"""
def __init__(self, parent=None): 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) super(AdjustTab, self).__init__(parent)
def setup_ui(self): def setup_ui(self):
"""初始化UI"""
layout = QtWidgets.QVBoxLayout(self) 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) layout.addWidget(splitter)
# 上部分 - 主要BlendShape列表 # 左侧 Raw Control 列表
top_widget = QtWidgets.QWidget() left_widget = QtWidgets.QWidget()
top_layout = QtWidgets.QVBoxLayout(top_widget) left_layout = QtWidgets.QVBoxLayout(left_widget)
# RowControl BlendShape列表 # 标题和计数
self.main_bs_list = BlendShapeList("RowControl") left_layout.addWidget(QtWidgets.QLabel("Raw Control [814/814]"))
top_layout.addWidget(self.main_bs_list)
# BlendShape控制 # 搜索框
self.bs_controls = BlendShapeControls() search_layout = QtWidgets.QHBoxLayout()
top_layout.addWidget(self.bs_controls) 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() for i, name in enumerate(['browDownL', 'browDownR', 'browLateralL', 'browLateralR',
bottom_layout = QtWidgets.QVBoxLayout(bottom_widget) 'browRaiseInL', 'browRaiseInR', 'browRaiseOuterL', 'browRaiseOuterR',
'earUpL', 'earUpR', 'eyeBlinkL']):
# Related BlendShape列表 item = QtWidgets.QListWidgetItem()
self.related_bs_list = BlendShapeList("Related Blend Shapes") item.setText(f"{name}")
bottom_layout.addWidget(self.related_bs_list) item.setData(QtCore.Qt.UserRole, f"{i:03d}") # 存储编号
self.raw_list.addItem(item)
# 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)
# 数值控制 # 数值控制
value_layout = QtWidgets.QHBoxLayout() 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)
# 表情控制按钮 # 右侧 Related Blend Shapes 列表
expr_layout = QtWidgets.QHBoxLayout() right_widget = QtWidgets.QWidget()
right_layout = QtWidgets.QVBoxLayout(right_widget)
expr_btns = [ # 标题和列表
("还原默认表情", "reset.png", self.reset_expression), right_layout.addWidget(QtWidgets.QLabel("Related Blend Shapes [858/858]"))
("选择选择表情", "expressions_current.png", self.select_expression), self.blend_list = QtWidgets.QListWidget()
("写入当前表情", "expression.png", self.write_expression), self.blend_list.setStyleSheet("QListWidget { background-color: #333333; }")
("控制面板查找", "controller.png", self.find_controller),
("选择关联关节", "kinJoint.png", self.select_joints), # 添加示例项
("写入镜像表情", "ctrl_hide.png", self.write_mirror_expression) 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: for i, (text, callback) in enumerate(tools):
btn = IconButton(icon, text) btn = QtWidgets.QPushButton(text)
btn.clicked.connect(callback) btn.clicked.connect(lambda checked=False, cb=callback: cb())
expr_layout.addWidget(btn) row = i // 2
col = i % 2
layout.addLayout(expr_layout) tools_layout.addWidget(btn, row, col)
right_layout.addLayout(tools_layout)
return group # 底部工具栏
bottom_layout = QtWidgets.QVBoxLayout()
def connect_signals(self): # 功能开关和数值
"""连接信号""" toggle_layout = QtWidgets.QHBoxLayout()
# 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])
def on_related_selection_changed(self): # 左侧开关
"""相关BlendShape列表选择变化""" left_toggles = QtWidgets.QHBoxLayout()
from scripts.utils import adjust_utils for text in ["KEY", "MIR", "ARK", "CTR"]:
items = self.related_bs_list.list_widget.selectedItems() btn = QtWidgets.QPushButton(text)
adjust_utils.on_related_bs_selected([item.text() for item in items]) btn.setCheckable(True)
left_toggles.addWidget(btn)
toggle_layout.addLayout(left_toggles)
def on_bs_value_changed(self, value): # 右侧数值
"""BlendShape权重变化""" toggle_layout.addStretch()
from scripts.utils import adjust_utils value_spin = QtWidgets.QDoubleSpinBox()
adjust_utils.set_bs_value(value) value_spin.setValue(0.000)
toggle_layout.addWidget(value_spin)
toggle_layout.addWidget(QtWidgets.QPushButton("全部"))
def on_expr_value_changed(self, value): bottom_layout.addLayout(toggle_layout)
"""表情权重变化"""
from scripts.utils import adjust_utils
adjust_utils.set_expr_value(value)
# 表情控制回调 # 操作按钮
def reset_expression(self): action_layout = QtWidgets.QGridLayout()
"""还原默认表情""" actions = [
from scripts.utils import adjust_utils ("在线帮助", adjust_utils.show_help),
adjust_utils.reset_expression() ("控制面板编辑", 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): for i, (text, callback) in enumerate(actions):
"""选择表情""" btn = QtWidgets.QPushButton(text)
from scripts.utils import adjust_utils btn.clicked.connect(lambda checked=False, cb=callback: cb())
adjust_utils.select_expression() row = i // 2
col = i % 2
action_layout.addWidget(btn, row, col)
bottom_layout.addLayout(action_layout)
def write_expression(self): right_layout.addLayout(bottom_layout)
"""写入当前表情""" splitter.addWidget(right_widget)
from scripts.utils import adjust_utils
adjust_utils.write_expression()
def find_controller(self): # 设置分割器比例
"""查找控制器""" splitter.setSizes([400, 400])
from scripts.utils import adjust_utils
adjust_utils.find_controller()
def select_joints(self): def _on_raw_value_changed(self, value):
"""选择关联关节""" """Raw Control数值改变"""
from scripts.utils import adjust_utils self.raw_value_label.setText(f"{value/1000:.3f}")
adjust_utils.select_joints()
def write_mirror_expression(self): def _on_blend_value_changed(self, value):
"""写入镜像表情""" """Blend Shape数值改变"""
from scripts.utils import adjust_utils self.blend_value_label.setText(f"{value/1000:.3f}")
adjust_utils.write_mirror_expression()

164
scripts/ui/adjust_utils.py Normal file
View File

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

View File

@ -1,195 +1,222 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
#===================================== 1. Module Imports ===================================== #===================================== 1. Module Imports =====================================
import maya.cmds as cmds import maya.cmds as cmds
import maya.mel as mel import maya.mel as mel
import sys import sys
import os import os
from scripts.config import data
from scripts.ui.widgets import ( BaseWidget, IconButton, SearchLineEdit) from scripts.ui import define_utils as define_utils
from scripts.ui import widgets as widgets
try: try:
from PySide2 import QtCore, QtGui, QtWidgets from PySide2 import QtCore, QtGui, QtWidgets
from shiboken2 import wrapInstance from shiboken2 import wrapInstance
print("从PySide2加载Qt和shiboken2")
except ImportError: except ImportError:
try: try:
from PySide6 import QtCore, QtGui, QtWidgets from PySide6 import QtCore, QtGui, QtWidgets
from shiboken6 import wrapInstance from shiboken6 import wrapInstance
print("从PySide6加载Qt和shiboken6")
except ImportError: except ImportError:
try: try:
from PySide import QtCore, QtGui, QtWidgets from PySide import QtCore, QtGui, QtWidgets
from shiboken import wrapInstance from shiboken import wrapInstance
print("从PySide加载Qt和shiboken")
except ImportError as e: except ImportError as e:
print(f"Qt加载失败: {str(e)}")
QtCore = QtGui = QtWidgets = None QtCore = QtGui = QtWidgets = None
wrapInstance = None wrapInstance = None
#===================================== 2. Define Tab Class ===================================== #===================================== 2. Define Tab Class =====================================
class DefineTab(BaseWidget): class DefineTab(widgets.BaseWidget):
"""定义标签页""" """定义标签页"""
def __init__(self, parent=None): 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) super(DefineTab, self).__init__(parent)
def setup_ui(self): def setup_ui(self):
"""初始化UI"""
layout = QtWidgets.QVBoxLayout(self) layout = QtWidgets.QVBoxLayout(self)
layout.setSpacing(self.SPACING)
layout.setContentsMargins(self.MARGINS, self.MARGINS,
self.MARGINS, self.MARGINS)
# 创建滚动区域 # 创建左右分栏布局
scroll = QtWidgets.QScrollArea() splitter = QtWidgets.QSplitter(QtCore.Qt.Horizontal)
scroll.setWidgetResizable(True) layout.addWidget(splitter)
scroll.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
layout.addWidget(scroll)
# 创建内容控件 # 左侧树形结构
content = QtWidgets.QWidget() left_widget = QtWidgets.QWidget()
content_layout = QtWidgets.QVBoxLayout(content) left_layout = QtWidgets.QVBoxLayout(left_widget)
scroll.setWidget(content)
# DNA定义部分 # LODs树
dna_group = self.create_dna_definition() lod_group = QtWidgets.QGroupBox("LODs")
content_layout.addWidget(dna_group) lod_layout = QtWidgets.QVBoxLayout()
self.lod_tree = QtWidgets.QTreeWidget()
self.lod_tree.setHeaderHidden(True)
# 骨骼定义部分 # 添加LOD项
joint_group = self.create_joint_definition() for i in range(8):
content_layout.addWidget(joint_group) 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定义部分 # Meshes树
bs_group = self.create_blendshape_definition() mesh_group = QtWidgets.QGroupBox("Meshes [010/54]")
content_layout.addWidget(bs_group) 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): for name, index in meshes:
"""创建DNA定义组""" mesh_item = QtWidgets.QTreeWidgetItem([name])
group = QtWidgets.QGroupBox("DNA定义") mesh_item.setData(0, QtCore.Qt.UserRole, index)
layout = QtWidgets.QVBoxLayout(group) self.mesh_tree.addTopLevelItem(mesh_item)
mesh_layout.addWidget(self.mesh_tree)
mesh_group.setLayout(mesh_layout)
left_layout.addWidget(mesh_group)
# DNA文件选择 splitter.addWidget(left_widget)
file_layout = QtWidgets.QHBoxLayout()
file_layout.addWidget(QtWidgets.QLabel("DNA文件:"))
self.dna_file_input = QtWidgets.QLineEdit()
file_layout.addWidget(self.dna_file_input)
browse_btn = IconButton("target.png", "浏览DNA文件") # 右侧内容
browse_btn.clicked.connect(self.browse_dna_file) right_widget = QtWidgets.QWidget()
file_layout.addWidget(browse_btn) 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预览") joints = [
preview_layout = QtWidgets.QVBoxLayout(preview_group) ("FACIAL_C_NeckB", "001"),
self.dna_preview = QtWidgets.QTextEdit() ("FACIAL_L_NeckB1", "002"),
self.dna_preview.setReadOnly(True) ("FACIAL_R_NeckB1", "003"),
preview_layout.addWidget(self.dna_preview) ("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): # 添加示例混合形状
"""创建骨骼定义组""" blends = [
group = QtWidgets.QGroupBox("骨骼定义") "brow_down_L",
layout = QtWidgets.QVBoxLayout(group) "brow_down_R",
"brow_lateral_L",
"brow_lateral_R"
]
# 骨骼列表 for name in blends:
self.joint_list = QtWidgets.QTreeWidget() blend_item = QtWidgets.QTreeWidgetItem([name])
self.joint_list.setHeaderLabels(["骨骼名称", "位置", "旋转", "缩放"]) self.blend_tree.addTopLevelItem(blend_item)
layout.addWidget(self.joint_list)
blend_layout.addWidget(self.blend_tree)
blend_group.setLayout(blend_layout)
right_layout.addWidget(blend_group)
# 骨骼工具栏 # AnimatedMap树
tools_layout = QtWidgets.QHBoxLayout() 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", "添加骨骼") anims = [
add_btn.clicked.connect(self.add_joint) "head_cm2_color.head_wm2_browsDown_L",
tools_layout.addWidget(add_btn) "head_cm2_color.head_wm2_browsDown_R",
"head_cm2_color.head_wm2_browsLateral_L",
"head_cm2_color.head_wm2_browsLateral_R"
]
# 删除骨骼 for name in anims:
del_btn = IconButton("delete.png", "删除骨骼") anim_item = QtWidgets.QTreeWidgetItem([name])
del_btn.clicked.connect(self.delete_joint) self.anim_tree.addTopLevelItem(anim_item)
tools_layout.addWidget(del_btn)
anim_layout.addWidget(self.anim_tree)
anim_group.setLayout(anim_layout)
right_layout.addWidget(anim_group)
# 修改骨骼 splitter.addWidget(right_widget)
mod_btn = IconButton("modify.png", "修改骨骼")
mod_btn.clicked.connect(self.modify_joint)
tools_layout.addWidget(mod_btn)
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): for text, callback in write_btns:
"""创建BlendShape定义组""" btn = QtWidgets.QPushButton(text)
group = QtWidgets.QGroupBox("BlendShape定义") btn.clicked.connect(lambda checked=False, cb=callback: cb())
layout = QtWidgets.QVBoxLayout(group) write_layout.addWidget(btn)
write_group.setLayout(write_layout)
bottom_layout.addWidget(write_group)
# BlendShape列表 # 创建组
self.bs_list = QtWidgets.QTreeWidget() create_group = QtWidgets.QGroupBox("创建")
self.bs_list.setHeaderLabels(["名称", "目标", "权重"]) create_layout = QtWidgets.QVBoxLayout()
layout.addWidget(self.bs_list) create_btns = [
("创建新的LOD", define_utils.create_new_lod),
("绑定蒙皮", define_utils.bind_skin),
("取消蒙皮", define_utils.unbind_skin)
]
# BlendShape工具栏 for text, callback in create_btns:
tools_layout = QtWidgets.QHBoxLayout() 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") tools_group = QtWidgets.QGroupBox("工具")
add_btn.clicked.connect(self.add_blendshape) tools_layout = QtWidgets.QVBoxLayout()
tools_layout.addWidget(add_btn) tools_btns = [
("更新运行时LOD", define_utils.update_runtime_lod),
("快速创建绑定", define_utils.quick_create_binding),
("创建蒙皮", define_utils.create_skin)
]
# 删除BlendShape for text, callback in tools_btns:
del_btn = IconButton("delete.png", "删除BlendShape") btn = QtWidgets.QPushButton(text)
del_btn.clicked.connect(self.delete_blendshape) btn.clicked.connect(lambda checked=False, cb=callback: cb())
tools_layout.addWidget(del_btn) tools_layout.addWidget(btn)
tools_group.setLayout(tools_layout)
bottom_layout.addWidget(tools_group)
# 修改BlendShape layout.addLayout(bottom_layout)
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()

View File

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

View File

@ -2,130 +2,162 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
#===================================== 1. Module Imports ===================================== #===================================== 1. Module Imports =====================================
import maya.cmds as cmds
import maya.mel as mel
import sys
import os import os
from scripts.config import data 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: try:
from PySide2 import QtCore, QtGui, QtWidgets from PySide2 import QtCore, QtGui, QtWidgets
from shiboken2 import wrapInstance from shiboken2 import wrapInstance
print("从PySide2加载Qt和shiboken2")
except ImportError: except ImportError:
try: try:
from PySide6 import QtCore, QtGui, QtWidgets from PySide6 import QtCore, QtGui, QtWidgets
from shiboken6 import wrapInstance from shiboken6 import wrapInstance
print("从PySide6加载Qt和shiboken6")
except ImportError: except ImportError:
try: try:
from PySide import QtCore, QtGui, QtWidgets from PySide import QtCore, QtGui, QtWidgets
from shiboken import wrapInstance from shiboken import wrapInstance
print("从PySide加载Qt和shiboken")
except ImportError as e: except ImportError as e:
print(f"Qt加载失败: {str(e)}")
QtCore = QtGui = QtWidgets = None QtCore = QtGui = QtWidgets = None
wrapInstance = None wrapInstance = None
#===================================== 2. Menu Manager Class ===================================== #===================================== 2. Menu Manager Class =====================================
class MenuManager: class MenuManager:
"""菜单管理器""" """菜单管理器"""
def __init__(self, parent): def __init__(self, parent):
self.parent = parent self.parent = parent
self.menu_bar = parent.menuBar() self.menu_bar = parent.menuBar()
self.create_menus() self._create_menus()
self.create_toolbar() self._create_toolbar()
def create_menus(self): def _create_menus(self):
"""创建菜单""" """创建菜单"""
# 文件菜单 # 定义所有菜单项
file_menu = self.menu_bar.addMenu("文件") self.menu_definitions = {
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) ("打开DNA", "open.png", menu_utils.load_dna),
self.add_menu_item(file_menu, "加载当前项目的DNA", "open.png", menu_utils.load_project_dna) ("保存DNA", "save.png", menu_utils.save_dna),
file_menu.addSeparator() ("加载当前项目的DNA", "open.png", menu_utils.load_project_dna),
self.add_menu_item(file_menu, "修改混合目标名称", "rename.png", menu_utils.rename_blend_target) None,
self.add_menu_item(file_menu, "重置混合目标名称", "resetname.png", menu_utils.reset_blend_target) ("修改混合目标名称", "rename.png", menu_utils.rename_blendshape_target),
file_menu.addSeparator() ("重置混合目标名称", "resetname.png", menu_utils.reset_blendshape_target),
self.add_menu_item(file_menu, "导出FBX", "export.png", menu_utils.export_fbx) None,
file_menu.addSeparator() ("导出FBX", "export.png", menu_utils.export_fbx),
self.add_menu_item(file_menu, "退出", "exit.png", menu_utils.safe_shutdown) 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("编辑") for menu_name, items in self.menu_definitions.items():
self.add_menu_item(edit_menu, "创建RL4节点", "connect.png", menu_utils.create_rl4_node) menu = self.menu_bar.addMenu(menu_name)
self.add_menu_item(edit_menu, "删除RL4节点", "disconnect.png", menu_utils.delete_rl4_node) self._add_menu_items(menu, items)
edit_menu.addSeparator()
self.add_menu_item(edit_menu, "镜像左至右", "mirrorL.png", menu_utils.mirror_left_to_right) def _create_toolbar(self):
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):
"""创建工具栏""" """创建工具栏"""
toolbar = self.parent.addToolBar("主工具栏") 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) toolbar_items = [
self.add_toolbar_item(toolbar, "加载当前项目DNA", "open.png", menu_utils.load_project_dna) ("保存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) for item in items:
if icon: if item is None:
icon_path = os.path.join(data.ICONS_PATH, icon) menu.addSeparator()
if os.path.exists(icon_path): else:
action.setIcon(QtGui.QIcon(icon_path)) text, icon, callback = item
action.triggered.connect(callback) action = QtWidgets.QAction(text, self.parent)
menu.addAction(action) if icon:
return action icon_path = os.path.join(data.ICONS_PATH, icon)
if os.path.exists(icon_path):
def add_toolbar_item(self, toolbar, text, icon, callback): 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) for item in items:
if icon: if item is None:
icon_path = os.path.join(data.ICONS_PATH, icon) toolbar.addSeparator()
if os.path.exists(icon_path): else:
action.setIcon(QtGui.QIcon(icon_path)) text, icon, callback = item
action.triggered.connect(callback) action = QtWidgets.QAction(text, self.parent)
toolbar.addAction(action) if icon:
return action 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)

79
scripts/ui/menu_utils.py Normal file
View File

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

153
scripts/ui/mesh.py Normal file
View File

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

115
scripts/ui/mesh_utils.py Normal file
View File

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

View File

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

View File

@ -6,193 +6,228 @@ import maya.cmds as cmds
import maya.mel as mel import maya.mel as mel
import sys import sys
import os import os
from scripts.config import data
from scripts.ui.widgets import (BaseWidget, DNABrowser, DescriptionWidget, IconButton, SearchLineEdit) from scripts.ui import rigging_utils as rigging_utils
from scripts.ui import widgets as widgets
try: try:
from PySide2 import QtCore, QtGui, QtWidgets from PySide2 import QtCore, QtGui, QtWidgets
from shiboken2 import wrapInstance from shiboken2 import wrapInstance
print("从PySide2加载Qt和shiboken2")
except ImportError: except ImportError:
try: try:
from PySide6 import QtCore, QtGui, QtWidgets from PySide6 import QtCore, QtGui, QtWidgets
from shiboken6 import wrapInstance from shiboken6 import wrapInstance
print("从PySide6加载Qt和shiboken6")
except ImportError: except ImportError:
try: try:
from PySide import QtCore, QtGui, QtWidgets from PySide import QtCore, QtGui, QtWidgets
from shiboken import wrapInstance from shiboken import wrapInstance
print("从PySide加载Qt和shiboken")
except ImportError as e: except ImportError as e:
print(f"Qt加载失败: {str(e)}")
QtCore = QtGui = QtWidgets = None QtCore = QtGui = QtWidgets = None
wrapInstance = None wrapInstance = None
#===================================== 2. Rigging Tab Class ===================================== #===================================== 2. Rigging Tab Class =====================================
class RigTab(BaseWidget): class RiggingTab(widgets.BaseWidget):
"""绑定标签页""" """绑定标签页"""
def __init__(self, parent=None): 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): def setup_ui(self):
"""初始化UI"""
layout = QtWidgets.QVBoxLayout(self) 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_widget = QtWidgets.QWidget()
scroll.setWidgetResizable(True) scroll_widget.setLayout(self.dna_flow)
scroll.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
layout.addWidget(scroll)
# 创建内容控件 scroll_area = QtWidgets.QScrollArea()
content = QtWidgets.QWidget() scroll_area.setWidget(scroll_widget)
content_layout = QtWidgets.QVBoxLayout(content) scroll_area.setWidgetResizable(True)
scroll.setWidget(content) scroll_area.setMinimumHeight(300)
scroll_area.setStyleSheet("QScrollArea { background-color: #333333; }")
browser_layout.addWidget(scroll_area)
# DNA部分 # 缩放滑块
dna_group = self.create_dna_group() slider_layout = QtWidgets.QHBoxLayout()
content_layout.addWidget(dna_group) 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() self.scale_label = QtWidgets.QLabel(f"{self.current_scale}")
content_layout.addWidget(asset_group) slider_layout.addWidget(self.scale_label)
slider_layout.addWidget(self.scale_slider)
# 描述部分
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)
# 导入导出按钮 # 导入导出按钮
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", "导出设置") browser_layout.addLayout(slider_layout)
export_btn.clicked.connect(self.export_settings) browser_group.setLayout(browser_layout)
btn_layout.addWidget(export_btn) layout.addWidget(browser_group)
import_btn = IconButton("import.png", "导入设置") # 资产设置区域
import_btn.clicked.connect(self.import_settings) settings_layout = QtWidgets.QVBoxLayout()
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)
# 项目路径 # 项目路径
project_layout = QtWidgets.QHBoxLayout() project_layout = QtWidgets.QHBoxLayout()
self.project_edit = QtWidgets.QLineEdit() project_layout.addWidget(QtWidgets.QLabel("项目路径:"))
project_btn = IconButton("target.png", "选择项目路径") project_input = widgets.ModernLineEdit(read_only=True)
project_btn.clicked.connect(self.browse_project) project_input.setText("D:/Personal/Document/maya/SuperRiggingEditor/files/data/MetaHuman")
project_layout.addWidget(self.project_edit) project_layout.addWidget(project_input)
project_layout.addWidget(project_btn) project_layout.addWidget(widgets.IconButton("folder.png", "加载..."))
layout.addRow("项目路径:", project_layout) settings_layout.addLayout(project_layout)
# 预设文件 # 预设文件
preset_layout = QtWidgets.QHBoxLayout() preset_layout = QtWidgets.QHBoxLayout()
self.preset_edit = QtWidgets.QLineEdit() preset_layout.addWidget(QtWidgets.QLabel("预设文件:"))
preset_btn = IconButton("target.png", "选择预设文件") preset_input = widgets.ModernLineEdit(read_only=True)
preset_btn.clicked.connect(self.browse_preset) preset_layout.addWidget(preset_input)
preset_layout.addWidget(self.preset_edit) preset_layout.addWidget(widgets.IconButton("folder.png", "加载..."))
preset_layout.addWidget(preset_btn) settings_layout.addLayout(preset_layout)
layout.addRow("预设文件:", preset_layout)
# 数据 # 数据设置
layer_layout = QtWidgets.QHBoxLayout() data_layout = QtWidgets.QHBoxLayout()
self.layer_combo = QtWidgets.QComboBox() data_layout.addWidget(QtWidgets.QLabel("数据层:"))
self.layer_combo.addItems(["行为"]) data_combo = QtWidgets.QComboBox()
self.override_check = QtWidgets.QCheckBox("覆盖表情") data_combo.addItem("无效(未定义)")
layer_layout.addWidget(self.layer_combo) data_layout.addWidget(data_combo)
layer_layout.addWidget(self.override_check) data_layout.addWidget(QtWidgets.QCheckBox("检查清理"))
layout.addRow("数据分层:", layer_layout) settings_layout.addLayout(data_layout)
return group # 描述信息
desc_group = QtWidgets.QGroupBox("描述")
desc_layout = QtWidgets.QGridLayout()
def create_skeleton_tools(self): desc_fields = [
"""创建骨架工具""" ("名称:", ""),
group = QtWidgets.QGroupBox("骨架工具") ("原型:", "亚洲人"),
layout = QtWidgets.QHBoxLayout(group) ("性别:", "女性"),
("年龄:", "18"),
("变换单位:", "厘米"),
("旋转单位:", "角度"),
("向上轴:", "Z轴向上"),
("LOD升级:", "0")
]
# 清空选项 for i, (label, default) in enumerate(desc_fields):
clear_btn = IconButton("delete.png", "清空选项") desc_layout.addWidget(QtWidgets.QLabel(label), i, 0)
clear_btn.clicked.connect(self.clear_options) if i in [1, 2, 4, 5, 6]: # 这些字段使用下拉框
layout.addWidget(clear_btn) 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)
# 导入骨架 desc_group.setLayout(desc_layout)
import_btn = IconButton("HIKCharacterToolSkeleton.png", "导入骨架") settings_layout.addWidget(desc_group)
import_btn.clicked.connect(self.import_skeleton)
layout.addWidget(import_btn)
# 创建骨架 # 底部按钮
create_btn = IconButton("HIKcreateControlRig.png", "创建骨架") bottom_layout = QtWidgets.QHBoxLayout()
create_btn.clicked.connect(self.create_skeleton) btn = widgets.IconButton("clear.png", "清空预览")
layout.addWidget(create_btn) 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() layout.addLayout(settings_layout)
return group
# DNA功能回调 def _on_scale_changed(self, value):
def on_dna_selected(self, dna_path): """缩放值改变时更新按钮大小"""
"""DNA文件选中""" self.current_scale = value
from scripts.utils import rigging_utils self.scale_label.setText(f"{value}")
rigging_utils.load_dna(dna_path) self._update_button_size()
def export_settings(self): def _update_button_size(self):
"""导出设置""" """更新所有DNA按钮的大小"""
from scripts.utils import rigging_utils size = int(self.current_scale * 1.5) # 将滑块值转换为合适的按钮大小
rigging_utils.export_settings() for btn in self.dna_buttons:
btn.setFixedSize(size, size)
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 browse_preset(self): def _create_dna_button(self, dna_file, img_file):
"""浏览预设文件""" """创建DNA按钮"""
file_path, _ = QtWidgets.QFileDialog.getOpenFileName( btn = QtWidgets.QPushButton()
self, "选择预设文件", os.path.expanduser("~"), btn.setFixedSize(int(self.current_scale * 1.5), int(self.current_scale * 1.5))
"预设文件 (*.json *.preset)")
if file_path: # 设置图标
self.preset_edit.setText(file_path) 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
# 骨架工具回调 # 加载DNA文件和对应的预览图
def clear_options(self): dna_files = [f for f in os.listdir(dna_path) if f.endswith('.dna')]
"""清空选项"""
from scripts.utils import rigging_utils
rigging_utils.clear_options()
def import_skeleton(self): for i, dna_file in enumerate(dna_files):
"""导入骨架""" base_name = os.path.splitext(dna_file)[0]
from scripts.utils import rigging_utils img_file = os.path.join(img_path, f"{base_name}.png")
rigging_utils.import_skeleton() dna_file_path = os.path.join(dna_path, dna_file)
def create_skeleton(self): if os.path.exists(img_file):
"""创建骨架""" btn = self._create_dna_button(dna_file_path, img_file)
from scripts.utils import rigging_utils self.dna_buttons.append(btn)
rigging_utils.create_skeleton()
# 添加到网格布局
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)}")

112
scripts/ui/rigging_utils.py Normal file
View File

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

View File

@ -1,28 +1,33 @@
/* 全局 QPushButton 样式 */ /* 现代深色主题 */
* {
font-family: "Segoe UI", "Microsoft YaHei";
font-size: 9pt;
}
/* 全局 QPushButton 样式 */
QPushButton { QPushButton {
background-color: #2A2A2A; background-color: #2d2d2d;
color: #CCCCCC; color: #e0e0e0;
border: 1px solid #3d3d3d;
border-radius: 3px; border-radius: 3px;
padding: 5px; padding: 4px 12px;
font-weight: bold; min-height: 22px;
min-width: 80px;
border: 1px solid #444444;
} }
QPushButton:hover { QPushButton:hover {
background-color: #3A3A3A; background-color: #3d3d3d;
border-color: #555555; border-color: #4d4d4d;
} }
QPushButton:pressed { QPushButton:pressed {
background-color: #1A1A1A; background-color: #1d1d1d;
border-color: #333333; border-color: #0078d4;
} }
QPushButton:disabled { QPushButton:disabled {
background-color: #1A1A1A; background-color: #252525;
border-color: #333333;
color: #666666; color: #666666;
border-color: #2d2d2d;
} }
/* 单独的消息按钮样式(可选) */ /* 单独的消息按钮样式(可选) */
@ -50,15 +55,14 @@ QPushButton.message-button:pressed {
/* 主窗口样式 */ /* 主窗口样式 */
QMainWindow { QMainWindow {
background-color: #333333; background-color: #1e1e1e;
color: #CCCCCC; color: #e0e0e0;
} }
/* 菜单栏样式 */ /* 菜单栏样式 */
QMenuBar { QMenuBar {
background-color: #2A2A2A; background-color: #1e1e1e;
color: #CCCCCC; color: #e0e0e0;
border-bottom: 1px solid #222222;
} }
QMenuBar::item { QMenuBar::item {
@ -67,7 +71,7 @@ QMenuBar::item {
} }
QMenuBar::item:selected { QMenuBar::item:selected {
background-color: #444444; background-color: #2d2d2d;
} }
QMenuBar::item:pressed { QMenuBar::item:pressed {
@ -108,25 +112,30 @@ QToolButton:disabled {
/* 标签页样式 */ /* 标签页样式 */
QTabWidget::pane { QTabWidget::pane {
border: 1px solid #222222; border: 1px solid #2d2d2d;
background-color: #333333; background: #1e1e1e;
border-radius: 3px;
} }
QTabBar::tab { QTabBar::tab {
background-color: #333333; background: #252525;
border: 1px solid #444444; border: 1px solid #2d2d2d;
color: #CCCCCC; border-bottom: none;
padding: 5px 10px; border-top-left-radius: 4px;
border-top-right-radius: 4px;
min-width: 80px; min-width: 80px;
padding: 4px 12px;
margin-right: 2px;
color: #e0e0e0;
} }
QTabBar::tab:selected { QTabBar::tab:selected {
background-color: #444444; background: #2d2d2d;
border-bottom-color: #555555; border-bottom: 2px solid #0078d4;
} }
QTabBar::tab:hover { QTabBar::tab:hover {
background-color: #444444; background: #303030;
} }
QTabBar::tab:pressed { QTabBar::tab:pressed {
@ -135,36 +144,54 @@ QTabBar::tab:pressed {
/* 列表和树形控件样式 */ /* 列表和树形控件样式 */
QTreeView, QListView { QTreeView, QListView {
background-color: #2A2A2A; background-color: #252525;
border: 1px solid #222222; border: 1px solid #2d2d2d;
color: #CCCCCC; border-radius: 3px;
} }
QTreeView::item:hover, QListView::item:hover { QTreeView::item {
background-color: #3A3A3A; padding: 4px;
} }
QTreeView::item:selected, QListView::item:selected { QTreeView::item:selected {
background-color: #444444; background-color: #0078d4;
} }
/* 输入框样式 */ /* 输入框样式 */
QLineEdit { QLineEdit {
background-color: #2A2A2A; background-color: #252525;
border: 1px solid #222222; color: #e0e0e0;
border-radius: 2px; border: 1px solid #3d3d3d;
color: #CCCCCC; border-radius: 3px;
padding: 3px; padding: 2px 6px;
min-height: 20px;
}
QLineEdit:focus {
border-color: #0078d4;
}
QLineEdit:read-only {
background-color: #1e1e1e;
border-color: #2d2d2d;
} }
/* 下拉框样式 */ /* 下拉框样式 */
QComboBox { QComboBox {
background-color: #2A2A2A; background-color: #252525;
border: 1px solid #222222; color: #e0e0e0;
border-radius: 2px; border: 1px solid #3d3d3d;
color: #CCCCCC; border-radius: 3px;
padding: 3px; padding: 2px 6px;
min-width: 100px; min-height: 20px;
}
QComboBox:hover {
border-color: #4d4d4d;
}
QComboBox:focus {
border-color: #0078d4;
} }
QComboBox::drop-down { QComboBox::drop-down {
@ -173,28 +200,27 @@ QComboBox::drop-down {
} }
QComboBox::down-arrow { QComboBox::down-arrow {
border-image: url(:/resources/icons/down_arrow.png); border-image: url(resources/icons/down_arrow.png);
width: 12px; width: 12px;
height: 12px; height: 12px;
} }
/* 滚动条样式 */ /* 滚动条样式 */
QScrollBar:vertical { QScrollBar:vertical {
background: #373737; background: #1e1e1e;
width: 16px; width: 10px;
margin: 0; margin: 0;
border-radius: 5px;
} }
QScrollBar::handle:vertical { QScrollBar::handle:vertical {
background: #4b5cc4; background: #3d3d3d;
min-height: 16px; min-height: 16px;
border-radius: 5px; border-radius: 5px;
margin: 2px; margin: 2px;
} }
QScrollBar::handle:vertical:hover { QScrollBar::handle:vertical:hover {
background: #5b6cd4; background: #4d4d4d;
} }
QScrollBar::handle:vertical:pressed { QScrollBar::handle:vertical:pressed {
@ -206,21 +232,20 @@ QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {
} }
QScrollBar:horizontal { QScrollBar:horizontal {
background: #373737; background: #1e1e1e;
height: 16px; height: 10px;
margin: 0; margin: 0;
border-radius: 5px;
} }
QScrollBar::handle:horizontal { QScrollBar::handle:horizontal {
background: #4b5cc4; background: #3d3d3d;
min-width: 16px; min-width: 16px;
border-radius: 5px; border-radius: 5px;
margin: 2px; margin: 2px;
} }
QScrollBar::handle:horizontal:hover { QScrollBar::handle:horizontal:hover {
background: #5b6cd4; background: #4d4d4d;
} }
QScrollBar::handle:horizontal:pressed { QScrollBar::handle:horizontal:pressed {
@ -233,30 +258,34 @@ QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal {
/* 分组框样式 */ /* 分组框样式 */
QGroupBox { QGroupBox {
border: 1px solid #222222; background-color: #252525;
border-radius: 3px; border: 1px solid #2d2d2d;
margin-top: 6px; border-radius: 4px;
padding-top: 6px; margin-top: 8px;
color: #CCCCCC; padding-top: 8px;
font-weight: bold;
} }
QGroupBox::title { QGroupBox::title {
left: 7px; margin-top: 8px;
padding: 0px 3px; margin-left: 8px;
padding: 0 4px;
color: #0078d4;
} }
/* 状态栏样式 */ /* 状态栏样式 */
QStatusBar { QStatusBar {
background-color: #333333; background-color: #1e1e1e;
color: #CCCCCC; color: #e0e0e0;
} }
/* 工具提示样式 */ /* 工具提示样式 */
QToolTip { QToolTip {
background-color: #2A2A2A; background-color: #252525;
border: 1px solid #222222; color: #e0e0e0;
color: #CCCCCC; border: 1px solid #3d3d3d;
padding: 3px; border-radius: 3px;
padding: 4px;
} }
/* DNA 浏览器样式 */ /* DNA 浏览器样式 */
@ -283,18 +312,16 @@ QListWidget::item:selected {
/* 菜单样式 */ /* 菜单样式 */
QMenu { QMenu {
background-color: #2A2A2A; background-color: #252525;
border: 1px solid #444444; border: 1px solid #3d3d3d;
color: #CCCCCC;
} }
QMenu::item { QMenu::item {
background-color: transparent; padding: 4px 20px;
padding: 5px 20px;
} }
QMenu::item:selected { QMenu::item:selected {
background-color: #444444; background-color: #3d3d3d;
} }
QMenu::item:pressed { QMenu::item:pressed {
@ -308,7 +335,8 @@ QDialog {
} }
QLabel { QLabel {
color: #E0E0E0; color: #CCCCCC;
padding: 0px 2px;
} }
/* 基础按钮样式 - 用于安装界面 */ /* 基础按钮样式 - 用于安装界面 */
@ -337,3 +365,47 @@ QLabel {
background-color: #404040; background-color: #404040;
color: #E0E0E0; 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;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -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="确定"
)

View File

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

View File

@ -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("创建骨架功能待实现")
# 其他功能函数...