Update
This commit is contained in:
parent
2c777c7418
commit
270f5702d9
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@ -15,33 +15,39 @@ from scripts.config import data
|
||||
try:
|
||||
from PySide2 import QtCore, QtGui, QtWidgets
|
||||
from shiboken2 import wrapInstance
|
||||
print("从PySide2加载Qt和shiboken2")
|
||||
except ImportError:
|
||||
try:
|
||||
from PySide6 import QtCore, QtGui, QtWidgets
|
||||
from shiboken6 import wrapInstance
|
||||
print("从PySide6加载Qt和shiboken6")
|
||||
except ImportError:
|
||||
try:
|
||||
from PySide import QtCore, QtGui, QtWidgets
|
||||
from shiboken import wrapInstance
|
||||
print("从PySide加载Qt和shiboken")
|
||||
except ImportError as e:
|
||||
print(f"Qt加载失败: {str(e)}")
|
||||
QtCore = QtGui = QtWidgets = None
|
||||
wrapInstance = None
|
||||
|
||||
# 导入UI模块
|
||||
from scripts.ui.menu import MenuManager
|
||||
from scripts.ui.models import ModelTab
|
||||
from scripts.ui.rigging import RigTab
|
||||
from scripts.ui.mesh import MeshTab
|
||||
from scripts.ui.rigging import RiggingTab
|
||||
from scripts.ui.adjust import AdjustTab
|
||||
from scripts.ui.define import DefineTab
|
||||
from scripts.ui.widgets import ModernTabWidget
|
||||
|
||||
#===================================== 2. Main Window Class =====================================
|
||||
class MainWindow(QtWidgets.QMainWindow):
|
||||
def __init__(self, parent=None):
|
||||
super(MainWindow, self).__init__(parent)
|
||||
# 设置基础字体大小
|
||||
font = self.font()
|
||||
font.setPointSize(9) # 设置基础字号
|
||||
self.setFont(font)
|
||||
|
||||
# 设置窗口大小
|
||||
self.resize(700, 600) # 调整为更合适的尺寸
|
||||
self.setMinimumSize(700, 500)
|
||||
|
||||
self.setup_core_components()
|
||||
|
||||
def setup_core_components(self):
|
||||
@ -61,19 +67,41 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
def init_ui(self):
|
||||
"""初始化UI框架"""
|
||||
self.setWindowTitle(f"{data.TOOL_NAME} {data.TOOL_VERSION}")
|
||||
self.setMinimumSize(1200, 800)
|
||||
|
||||
# 主窗口布局
|
||||
main_widget = QtWidgets.QWidget()
|
||||
self.setCentralWidget(main_widget)
|
||||
main_layout = QtWidgets.QVBoxLayout(main_widget)
|
||||
main_layout.setContentsMargins(0, 0, 0, 0)
|
||||
main_layout.setSpacing(0)
|
||||
|
||||
# 创建菜单和工具栏
|
||||
self.menu_manager = MenuManager(self)
|
||||
self.create_tab_widget()
|
||||
|
||||
# 加载样式
|
||||
self.load_styles()
|
||||
# 创建标签页
|
||||
self.tab_widget = ModernTabWidget()
|
||||
main_layout.addWidget(self.tab_widget)
|
||||
|
||||
# 初始化各标签页
|
||||
self.model_tab = MeshTab()
|
||||
self.rig_tab = RiggingTab()
|
||||
self.adjust_tab = AdjustTab()
|
||||
self.define_tab = DefineTab()
|
||||
|
||||
# 添加标签页
|
||||
self.tab_widget.addTab(self.model_tab, "模型")
|
||||
self.tab_widget.addTab(self.rig_tab, "绑定")
|
||||
self.tab_widget.addTab(self.adjust_tab, "调整")
|
||||
self.tab_widget.addTab(self.define_tab, "定义")
|
||||
|
||||
# 添加状态栏
|
||||
self.statusBar().showMessage("就绪")
|
||||
self.statusBar().setStyleSheet("""
|
||||
QStatusBar {
|
||||
background: #252525;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
""")
|
||||
|
||||
def load_styles(self):
|
||||
"""加载样式表"""
|
||||
@ -81,23 +109,6 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
with open(data.TOOL_STYLE_FILE, "r", encoding="utf-8") as f:
|
||||
self.setStyleSheet(f.read())
|
||||
|
||||
def create_tab_widget(self):
|
||||
"""创建标签页"""
|
||||
self.tab_widget = QtWidgets.QTabWidget()
|
||||
self.centralWidget().layout().addWidget(self.tab_widget)
|
||||
|
||||
# 初始化各标签页
|
||||
self.model_tab = ModelTab()
|
||||
self.rig_tab = RigTab()
|
||||
self.adjust_tab = AdjustTab()
|
||||
self.define_tab = DefineTab()
|
||||
|
||||
# 添加标签页
|
||||
self.tab_widget.addTab(self.model_tab, "模型")
|
||||
self.tab_widget.addTab(self.rig_tab, "绑定")
|
||||
self.tab_widget.addTab(self.adjust_tab, "调整")
|
||||
self.tab_widget.addTab(self.define_tab, "定义")
|
||||
|
||||
# ===================================== 显示主窗口 =====================================
|
||||
def get_maya_window():
|
||||
"""获取Maya主窗口"""
|
||||
|
@ -4,6 +4,7 @@
|
||||
import os
|
||||
import sys
|
||||
import maya.cmds as cmds
|
||||
import datetime
|
||||
|
||||
# Base Information
|
||||
TOOL_NAME = str("MetaFusion")
|
||||
@ -12,6 +13,7 @@ TOOL_AUTHOR = str("CGNICO")
|
||||
TOOL_LANG = str('en_US')
|
||||
TOOL_WSCL_NAME = str(f"{TOOL_NAME}WorkSpaceControl")
|
||||
TOOL_HELP_URL = str(f"https://gitea.cgnico.com/CGNICO/{TOOL_NAME}/wiki")
|
||||
TOOL_YEAR = str(datetime.datetime.now().year)
|
||||
|
||||
# BASE_PATH
|
||||
TOOL_PATH = str(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))).replace("\\", "/"))
|
||||
|
@ -1,4 +1,26 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import *
|
||||
import maya.cmds as cmds
|
||||
import maya.mel as mel
|
||||
import sys
|
||||
import os
|
||||
|
||||
from scripts.ui.widgets import *
|
||||
from scripts.utils import *
|
||||
|
||||
|
||||
try:
|
||||
from PySide2 import QtCore, QtGui, QtWidgets
|
||||
from shiboken2 import wrapInstance
|
||||
except ImportError:
|
||||
try:
|
||||
from PySide6 import QtCore, QtGui, QtWidgets
|
||||
from shiboken6 import wrapInstance
|
||||
except ImportError:
|
||||
try:
|
||||
from PySide import QtCore, QtGui, QtWidgets
|
||||
from shiboken import wrapInstance
|
||||
except ImportError as e:
|
||||
QtCore = QtGui = QtWidgets = None
|
||||
wrapInstance = None
|
||||
|
@ -6,200 +6,203 @@ import maya.cmds as cmds
|
||||
import maya.mel as mel
|
||||
import sys
|
||||
import os
|
||||
|
||||
from scripts.ui.widgets import (BaseWidget, BlendShapeList, BlendShapeControls, BlendShapeTools, IconButton, SliderWithValue)
|
||||
from scripts.config import data
|
||||
from scripts.ui import adjust_utils as adjust_utils
|
||||
from scripts.ui import widgets as widgets
|
||||
|
||||
try:
|
||||
from PySide2 import QtCore, QtGui, QtWidgets
|
||||
from shiboken2 import wrapInstance
|
||||
print("从PySide2加载Qt和shiboken2")
|
||||
except ImportError:
|
||||
try:
|
||||
from PySide6 import QtCore, QtGui, QtWidgets
|
||||
from shiboken6 import wrapInstance
|
||||
print("从PySide6加载Qt和shiboken6")
|
||||
except ImportError:
|
||||
try:
|
||||
from PySide import QtCore, QtGui, QtWidgets
|
||||
from shiboken import wrapInstance
|
||||
print("从PySide加载Qt和shiboken")
|
||||
except ImportError as e:
|
||||
print(f"Qt加载失败: {str(e)}")
|
||||
QtCore = QtGui = QtWidgets = None
|
||||
wrapInstance = None
|
||||
|
||||
#===================================== 2. Adjust Tab Class =====================================
|
||||
class AdjustTab(BaseWidget):
|
||||
class AdjustTab(widgets.BaseWidget):
|
||||
"""调整标签页"""
|
||||
def __init__(self, parent=None):
|
||||
self.raw_list = None
|
||||
self.blend_list = None
|
||||
self.raw_slider = None
|
||||
self.blend_slider = None
|
||||
self.raw_value_label = None
|
||||
self.blend_value_label = None
|
||||
super(AdjustTab, self).__init__(parent)
|
||||
|
||||
def setup_ui(self):
|
||||
"""初始化UI"""
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.setSpacing(self.SPACING)
|
||||
layout.setContentsMargins(self.MARGINS, self.MARGINS,
|
||||
self.MARGINS, self.MARGINS)
|
||||
|
||||
# 创建分割器
|
||||
splitter = QtWidgets.QSplitter(QtCore.Qt.Vertical)
|
||||
# 创建左右分栏布局
|
||||
splitter = QtWidgets.QSplitter(QtCore.Qt.Horizontal)
|
||||
layout.addWidget(splitter)
|
||||
|
||||
# 上部分 - 主要BlendShape列表
|
||||
top_widget = QtWidgets.QWidget()
|
||||
top_layout = QtWidgets.QVBoxLayout(top_widget)
|
||||
# 左侧 Raw Control 列表
|
||||
left_widget = QtWidgets.QWidget()
|
||||
left_layout = QtWidgets.QVBoxLayout(left_widget)
|
||||
|
||||
# RowControl BlendShape列表
|
||||
self.main_bs_list = BlendShapeList("RowControl")
|
||||
top_layout.addWidget(self.main_bs_list)
|
||||
# 标题和计数
|
||||
left_layout.addWidget(QtWidgets.QLabel("Raw Control [814/814]"))
|
||||
|
||||
# BlendShape控制
|
||||
self.bs_controls = BlendShapeControls()
|
||||
top_layout.addWidget(self.bs_controls)
|
||||
# 搜索框
|
||||
search_layout = QtWidgets.QHBoxLayout()
|
||||
search_input = widgets.ModernLineEdit()
|
||||
search_input.setPlaceholderText("搜索...")
|
||||
search_layout.addWidget(search_input)
|
||||
left_layout.addLayout(search_layout)
|
||||
|
||||
splitter.addWidget(top_widget)
|
||||
# Raw Control列表
|
||||
self.raw_list = QtWidgets.QListWidget()
|
||||
self.raw_list.setStyleSheet("QListWidget { background-color: #333333; }")
|
||||
left_layout.addWidget(self.raw_list)
|
||||
|
||||
# 下部分 - 相关BlendShape和工具
|
||||
bottom_widget = QtWidgets.QWidget()
|
||||
bottom_layout = QtWidgets.QVBoxLayout(bottom_widget)
|
||||
|
||||
# Related BlendShape列表
|
||||
self.related_bs_list = BlendShapeList("Related Blend Shapes")
|
||||
bottom_layout.addWidget(self.related_bs_list)
|
||||
|
||||
# BlendShape工具
|
||||
self.bs_tools = BlendShapeTools()
|
||||
bottom_layout.addWidget(self.bs_tools)
|
||||
|
||||
# 表情控制工具栏
|
||||
expression_tools = self.create_expression_tools()
|
||||
bottom_layout.addWidget(expression_tools)
|
||||
|
||||
splitter.addWidget(bottom_widget)
|
||||
|
||||
# 设置分割器比例
|
||||
splitter.setStretchFactor(0, 2)
|
||||
splitter.setStretchFactor(1, 1)
|
||||
|
||||
# 连接信号
|
||||
self.connect_signals()
|
||||
|
||||
def create_expression_tools(self):
|
||||
"""创建表情控制工具栏"""
|
||||
group = QtWidgets.QGroupBox("表情控制")
|
||||
layout = QtWidgets.QVBoxLayout(group)
|
||||
|
||||
# 功能开关
|
||||
toggle_layout = QtWidgets.QHBoxLayout()
|
||||
|
||||
toggles = [
|
||||
("PSD", "psd.png"),
|
||||
("BSE", "blendShape.png"),
|
||||
("KEY", "centerCurrentTime.png"),
|
||||
("MIR", "mirrorR.png"),
|
||||
("ARK", "ARKit52.png"),
|
||||
("CTR", "ctrl_hide.png")
|
||||
]
|
||||
|
||||
for text, icon in toggles:
|
||||
btn = IconButton(icon, text)
|
||||
btn.setCheckable(True)
|
||||
toggle_layout.addWidget(btn)
|
||||
|
||||
layout.addLayout(toggle_layout)
|
||||
# 添加示例项
|
||||
for i, name in enumerate(['browDownL', 'browDownR', 'browLateralL', 'browLateralR',
|
||||
'browRaiseInL', 'browRaiseInR', 'browRaiseOuterL', 'browRaiseOuterR',
|
||||
'earUpL', 'earUpR', 'eyeBlinkL']):
|
||||
item = QtWidgets.QListWidgetItem()
|
||||
item.setText(f"{name}")
|
||||
item.setData(QtCore.Qt.UserRole, f"{i:03d}") # 存储编号
|
||||
self.raw_list.addItem(item)
|
||||
|
||||
# 数值控制
|
||||
value_layout = QtWidgets.QHBoxLayout()
|
||||
self.raw_value_label = QtWidgets.QLabel("0.010")
|
||||
value_layout.addWidget(self.raw_value_label)
|
||||
self.raw_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
|
||||
self.raw_slider.setRange(0, 100)
|
||||
self.raw_slider.valueChanged.connect(self._on_raw_value_changed)
|
||||
value_layout.addWidget(self.raw_slider)
|
||||
value_layout.addWidget(QtWidgets.QLabel("全部"))
|
||||
left_layout.addLayout(value_layout)
|
||||
|
||||
self.expr_slider = SliderWithValue(min_val=0.0, max_val=1.0, default=0.0)
|
||||
value_layout.addWidget(self.expr_slider)
|
||||
# 页码控制
|
||||
page_layout = QtWidgets.QHBoxLayout()
|
||||
page_layout.addWidget(QtWidgets.QLabel("全部"))
|
||||
for i in range(2, 7):
|
||||
btn = QtWidgets.QPushButton(str(i))
|
||||
page_layout.addWidget(btn)
|
||||
left_layout.addLayout(page_layout)
|
||||
|
||||
self.expr_all_check = QtWidgets.QCheckBox("全部")
|
||||
value_layout.addWidget(self.expr_all_check)
|
||||
# 源目标选择
|
||||
source_layout = QtWidgets.QHBoxLayout()
|
||||
source_layout.addWidget(QtWidgets.QLabel("源目标"))
|
||||
source_layout.addWidget(QtWidgets.QComboBox())
|
||||
left_layout.addLayout(source_layout)
|
||||
|
||||
layout.addLayout(value_layout)
|
||||
splitter.addWidget(left_widget)
|
||||
|
||||
# 表情控制按钮
|
||||
expr_layout = QtWidgets.QHBoxLayout()
|
||||
# 右侧 Related Blend Shapes 列表
|
||||
right_widget = QtWidgets.QWidget()
|
||||
right_layout = QtWidgets.QVBoxLayout(right_widget)
|
||||
|
||||
expr_btns = [
|
||||
("还原默认表情", "reset.png", self.reset_expression),
|
||||
("选择选择表情", "expressions_current.png", self.select_expression),
|
||||
("写入当前表情", "expression.png", self.write_expression),
|
||||
("控制面板查找", "controller.png", self.find_controller),
|
||||
("选择关联关节", "kinJoint.png", self.select_joints),
|
||||
("写入镜像表情", "ctrl_hide.png", self.write_mirror_expression)
|
||||
# 标题和列表
|
||||
right_layout.addWidget(QtWidgets.QLabel("Related Blend Shapes [858/858]"))
|
||||
self.blend_list = QtWidgets.QListWidget()
|
||||
self.blend_list.setStyleSheet("QListWidget { background-color: #333333; }")
|
||||
|
||||
# 添加示例项
|
||||
for i in range(10):
|
||||
item = QtWidgets.QListWidgetItem()
|
||||
item.setText(f"head_lod0_mesh | brow_down_L {i:03d}")
|
||||
self.blend_list.addItem(item)
|
||||
|
||||
right_layout.addWidget(self.blend_list)
|
||||
|
||||
# 数值控制
|
||||
blend_value_layout = QtWidgets.QHBoxLayout()
|
||||
self.blend_value_label = QtWidgets.QLabel("0.000")
|
||||
blend_value_layout.addWidget(self.blend_value_label)
|
||||
self.blend_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
|
||||
self.blend_slider.setRange(0, 100)
|
||||
self.blend_slider.valueChanged.connect(self._on_blend_value_changed)
|
||||
blend_value_layout.addWidget(self.blend_slider)
|
||||
blend_value_layout.addWidget(QtWidgets.QLabel("全部"))
|
||||
right_layout.addLayout(blend_value_layout)
|
||||
|
||||
# 工具按钮组
|
||||
tools_layout = QtWidgets.QGridLayout()
|
||||
tools = [
|
||||
("整理目标", adjust_utils.organize_targets),
|
||||
("高级混合变形", adjust_utils.advanced_blend),
|
||||
("重建混合目标", adjust_utils.rebuild_targets),
|
||||
("添加混合变形", adjust_utils.add_blend),
|
||||
("删除混合变形", adjust_utils.delete_blend),
|
||||
("批量混合变形", adjust_utils.batch_blend)
|
||||
]
|
||||
|
||||
for text, icon, callback in expr_btns:
|
||||
btn = IconButton(icon, text)
|
||||
btn.clicked.connect(callback)
|
||||
expr_layout.addWidget(btn)
|
||||
|
||||
layout.addLayout(expr_layout)
|
||||
for i, (text, callback) in enumerate(tools):
|
||||
btn = QtWidgets.QPushButton(text)
|
||||
btn.clicked.connect(lambda checked=False, cb=callback: cb())
|
||||
row = i // 2
|
||||
col = i % 2
|
||||
tools_layout.addWidget(btn, row, col)
|
||||
right_layout.addLayout(tools_layout)
|
||||
|
||||
return group
|
||||
# 底部工具栏
|
||||
bottom_layout = QtWidgets.QVBoxLayout()
|
||||
|
||||
def connect_signals(self):
|
||||
"""连接信号"""
|
||||
# BlendShape列表选择变化
|
||||
self.main_bs_list.list_widget.itemSelectionChanged.connect(
|
||||
self.on_main_selection_changed)
|
||||
self.related_bs_list.list_widget.itemSelectionChanged.connect(
|
||||
self.on_related_selection_changed)
|
||||
|
||||
# 数值变化
|
||||
self.bs_controls.value_slider.valueChanged.connect(
|
||||
self.on_bs_value_changed)
|
||||
self.expr_slider.valueChanged.connect(
|
||||
self.on_expr_value_changed)
|
||||
|
||||
# 回调函数
|
||||
def on_main_selection_changed(self):
|
||||
"""主BlendShape列表选择变化"""
|
||||
from scripts.utils import adjust_utils
|
||||
items = self.main_bs_list.list_widget.selectedItems()
|
||||
adjust_utils.on_main_bs_selected([item.text() for item in items])
|
||||
# 功能开关和数值
|
||||
toggle_layout = QtWidgets.QHBoxLayout()
|
||||
|
||||
def on_related_selection_changed(self):
|
||||
"""相关BlendShape列表选择变化"""
|
||||
from scripts.utils import adjust_utils
|
||||
items = self.related_bs_list.list_widget.selectedItems()
|
||||
adjust_utils.on_related_bs_selected([item.text() for item in items])
|
||||
# 左侧开关
|
||||
left_toggles = QtWidgets.QHBoxLayout()
|
||||
for text in ["KEY", "MIR", "ARK", "CTR"]:
|
||||
btn = QtWidgets.QPushButton(text)
|
||||
btn.setCheckable(True)
|
||||
left_toggles.addWidget(btn)
|
||||
toggle_layout.addLayout(left_toggles)
|
||||
|
||||
def on_bs_value_changed(self, value):
|
||||
"""BlendShape权重变化"""
|
||||
from scripts.utils import adjust_utils
|
||||
adjust_utils.set_bs_value(value)
|
||||
# 右侧数值
|
||||
toggle_layout.addStretch()
|
||||
value_spin = QtWidgets.QDoubleSpinBox()
|
||||
value_spin.setValue(0.000)
|
||||
toggle_layout.addWidget(value_spin)
|
||||
toggle_layout.addWidget(QtWidgets.QPushButton("全部"))
|
||||
|
||||
def on_expr_value_changed(self, value):
|
||||
"""表情权重变化"""
|
||||
from scripts.utils import adjust_utils
|
||||
adjust_utils.set_expr_value(value)
|
||||
bottom_layout.addLayout(toggle_layout)
|
||||
|
||||
# 表情控制回调
|
||||
def reset_expression(self):
|
||||
"""还原默认表情"""
|
||||
from scripts.utils import adjust_utils
|
||||
adjust_utils.reset_expression()
|
||||
# 操作按钮
|
||||
action_layout = QtWidgets.QGridLayout()
|
||||
actions = [
|
||||
("在线帮助", adjust_utils.show_help),
|
||||
("控制面板编辑", adjust_utils.edit_control_panel),
|
||||
("控制面板预览", adjust_utils.preview_control_panel),
|
||||
("导入编辑器", adjust_utils.import_editor),
|
||||
("导入编辑器预览", adjust_utils.import_editor_preview),
|
||||
("导入编辑器预览", adjust_utils.import_editor_preview)
|
||||
]
|
||||
|
||||
def select_expression(self):
|
||||
"""选择表情"""
|
||||
from scripts.utils import adjust_utils
|
||||
adjust_utils.select_expression()
|
||||
for i, (text, callback) in enumerate(actions):
|
||||
btn = QtWidgets.QPushButton(text)
|
||||
btn.clicked.connect(lambda checked=False, cb=callback: cb())
|
||||
row = i // 2
|
||||
col = i % 2
|
||||
action_layout.addWidget(btn, row, col)
|
||||
bottom_layout.addLayout(action_layout)
|
||||
|
||||
def write_expression(self):
|
||||
"""写入当前表情"""
|
||||
from scripts.utils import adjust_utils
|
||||
adjust_utils.write_expression()
|
||||
right_layout.addLayout(bottom_layout)
|
||||
splitter.addWidget(right_widget)
|
||||
|
||||
def find_controller(self):
|
||||
"""查找控制器"""
|
||||
from scripts.utils import adjust_utils
|
||||
adjust_utils.find_controller()
|
||||
# 设置分割器比例
|
||||
splitter.setSizes([400, 400])
|
||||
|
||||
def select_joints(self):
|
||||
"""选择关联关节"""
|
||||
from scripts.utils import adjust_utils
|
||||
adjust_utils.select_joints()
|
||||
def _on_raw_value_changed(self, value):
|
||||
"""Raw Control数值改变"""
|
||||
self.raw_value_label.setText(f"{value/1000:.3f}")
|
||||
|
||||
def write_mirror_expression(self):
|
||||
"""写入镜像表情"""
|
||||
from scripts.utils import adjust_utils
|
||||
adjust_utils.write_mirror_expression()
|
||||
def _on_blend_value_changed(self, value):
|
||||
"""Blend Shape数值改变"""
|
||||
self.blend_value_label.setText(f"{value/1000:.3f}")
|
||||
|
164
scripts/ui/adjust_utils.py
Normal file
164
scripts/ui/adjust_utils.py
Normal 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
|
||||
|
@ -1,195 +1,222 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#===================================== 1. Module Imports =====================================
|
||||
import maya.cmds as cmds
|
||||
import maya.mel as mel
|
||||
import sys
|
||||
import os
|
||||
|
||||
from scripts.ui.widgets import ( BaseWidget, IconButton, SearchLineEdit)
|
||||
from scripts.config import data
|
||||
from scripts.ui import define_utils as define_utils
|
||||
from scripts.ui import widgets as widgets
|
||||
|
||||
try:
|
||||
from PySide2 import QtCore, QtGui, QtWidgets
|
||||
from shiboken2 import wrapInstance
|
||||
print("从PySide2加载Qt和shiboken2")
|
||||
except ImportError:
|
||||
try:
|
||||
from PySide6 import QtCore, QtGui, QtWidgets
|
||||
from shiboken6 import wrapInstance
|
||||
print("从PySide6加载Qt和shiboken6")
|
||||
except ImportError:
|
||||
try:
|
||||
from PySide import QtCore, QtGui, QtWidgets
|
||||
from shiboken import wrapInstance
|
||||
print("从PySide加载Qt和shiboken")
|
||||
except ImportError as e:
|
||||
print(f"Qt加载失败: {str(e)}")
|
||||
QtCore = QtGui = QtWidgets = None
|
||||
wrapInstance = None
|
||||
|
||||
#===================================== 2. Define Tab Class =====================================
|
||||
class DefineTab(BaseWidget):
|
||||
class DefineTab(widgets.BaseWidget):
|
||||
"""定义标签页"""
|
||||
def __init__(self, parent=None):
|
||||
self.lod_tree = None
|
||||
self.mesh_tree = None
|
||||
self.joint_tree = None
|
||||
self.blend_tree = None
|
||||
self.anim_tree = None
|
||||
super(DefineTab, self).__init__(parent)
|
||||
|
||||
def setup_ui(self):
|
||||
"""初始化UI"""
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.setSpacing(self.SPACING)
|
||||
layout.setContentsMargins(self.MARGINS, self.MARGINS,
|
||||
self.MARGINS, self.MARGINS)
|
||||
|
||||
# 创建滚动区域
|
||||
scroll = QtWidgets.QScrollArea()
|
||||
scroll.setWidgetResizable(True)
|
||||
scroll.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
||||
layout.addWidget(scroll)
|
||||
# 创建左右分栏布局
|
||||
splitter = QtWidgets.QSplitter(QtCore.Qt.Horizontal)
|
||||
layout.addWidget(splitter)
|
||||
|
||||
# 创建内容控件
|
||||
content = QtWidgets.QWidget()
|
||||
content_layout = QtWidgets.QVBoxLayout(content)
|
||||
scroll.setWidget(content)
|
||||
# 左侧树形结构
|
||||
left_widget = QtWidgets.QWidget()
|
||||
left_layout = QtWidgets.QVBoxLayout(left_widget)
|
||||
|
||||
# DNA定义部分
|
||||
dna_group = self.create_dna_definition()
|
||||
content_layout.addWidget(dna_group)
|
||||
# LODs树
|
||||
lod_group = QtWidgets.QGroupBox("LODs")
|
||||
lod_layout = QtWidgets.QVBoxLayout()
|
||||
self.lod_tree = QtWidgets.QTreeWidget()
|
||||
self.lod_tree.setHeaderHidden(True)
|
||||
|
||||
# 骨骼定义部分
|
||||
joint_group = self.create_joint_definition()
|
||||
content_layout.addWidget(joint_group)
|
||||
# 添加LOD项
|
||||
for i in range(8):
|
||||
lod_item = QtWidgets.QTreeWidgetItem([f"LOD {i}"])
|
||||
self.lod_tree.addTopLevelItem(lod_item)
|
||||
|
||||
lod_layout.addWidget(self.lod_tree)
|
||||
lod_group.setLayout(lod_layout)
|
||||
left_layout.addWidget(lod_group)
|
||||
|
||||
# BlendShape定义部分
|
||||
bs_group = self.create_blendshape_definition()
|
||||
content_layout.addWidget(bs_group)
|
||||
# Meshes树
|
||||
mesh_group = QtWidgets.QGroupBox("Meshes [010/54]")
|
||||
mesh_layout = QtWidgets.QVBoxLayout()
|
||||
self.mesh_tree = QtWidgets.QTreeWidget()
|
||||
self.mesh_tree.setHeaderHidden(True)
|
||||
|
||||
content_layout.addStretch()
|
||||
# 添加示例网格
|
||||
meshes = [
|
||||
("head_lod0_mesh", "000"),
|
||||
("teeth_lod0_mesh", "001"),
|
||||
("saliva_lod0_mesh", "002"),
|
||||
("eyeLeft_lod0_mesh", "003"),
|
||||
("eyeRight_lod0_mesh", "004"),
|
||||
("eyeshell_lod0_mesh", "005")
|
||||
]
|
||||
|
||||
def create_dna_definition(self):
|
||||
"""创建DNA定义组"""
|
||||
group = QtWidgets.QGroupBox("DNA定义")
|
||||
layout = QtWidgets.QVBoxLayout(group)
|
||||
for name, index in meshes:
|
||||
mesh_item = QtWidgets.QTreeWidgetItem([name])
|
||||
mesh_item.setData(0, QtCore.Qt.UserRole, index)
|
||||
self.mesh_tree.addTopLevelItem(mesh_item)
|
||||
|
||||
mesh_layout.addWidget(self.mesh_tree)
|
||||
mesh_group.setLayout(mesh_layout)
|
||||
left_layout.addWidget(mesh_group)
|
||||
|
||||
# DNA文件选择
|
||||
file_layout = QtWidgets.QHBoxLayout()
|
||||
file_layout.addWidget(QtWidgets.QLabel("DNA文件:"))
|
||||
self.dna_file_input = QtWidgets.QLineEdit()
|
||||
file_layout.addWidget(self.dna_file_input)
|
||||
splitter.addWidget(left_widget)
|
||||
|
||||
browse_btn = IconButton("target.png", "浏览DNA文件")
|
||||
browse_btn.clicked.connect(self.browse_dna_file)
|
||||
file_layout.addWidget(browse_btn)
|
||||
# 右侧内容
|
||||
right_widget = QtWidgets.QWidget()
|
||||
right_layout = QtWidgets.QVBoxLayout(right_widget)
|
||||
|
||||
layout.addLayout(file_layout)
|
||||
# Joints树
|
||||
joint_group = QtWidgets.QGroupBox("Joints [840/870]")
|
||||
joint_layout = QtWidgets.QVBoxLayout()
|
||||
self.joint_tree = QtWidgets.QTreeWidget()
|
||||
self.joint_tree.setHeaderHidden(True)
|
||||
|
||||
# DNA预览
|
||||
preview_group = QtWidgets.QGroupBox("DNA预览")
|
||||
preview_layout = QtWidgets.QVBoxLayout(preview_group)
|
||||
self.dna_preview = QtWidgets.QTextEdit()
|
||||
self.dna_preview.setReadOnly(True)
|
||||
preview_layout.addWidget(self.dna_preview)
|
||||
# 添加示例关节
|
||||
joints = [
|
||||
("FACIAL_C_NeckB", "001"),
|
||||
("FACIAL_L_NeckB1", "002"),
|
||||
("FACIAL_R_NeckB1", "003"),
|
||||
("FACIAL_L_NeckB2", "002")
|
||||
]
|
||||
|
||||
layout.addWidget(preview_group)
|
||||
for name, group in joints:
|
||||
joint_item = QtWidgets.QTreeWidgetItem([f"{name} Group: {group}"])
|
||||
self.joint_tree.addTopLevelItem(joint_item)
|
||||
|
||||
joint_layout.addWidget(self.joint_tree)
|
||||
joint_group.setLayout(joint_layout)
|
||||
right_layout.addWidget(joint_group)
|
||||
|
||||
return group
|
||||
# Blend Shapes树
|
||||
blend_group = QtWidgets.QGroupBox("Blend Shapes [782/782]")
|
||||
blend_layout = QtWidgets.QVBoxLayout()
|
||||
self.blend_tree = QtWidgets.QTreeWidget()
|
||||
self.blend_tree.setHeaderHidden(True)
|
||||
|
||||
def create_joint_definition(self):
|
||||
"""创建骨骼定义组"""
|
||||
group = QtWidgets.QGroupBox("骨骼定义")
|
||||
layout = QtWidgets.QVBoxLayout(group)
|
||||
# 添加示例混合形状
|
||||
blends = [
|
||||
"brow_down_L",
|
||||
"brow_down_R",
|
||||
"brow_lateral_L",
|
||||
"brow_lateral_R"
|
||||
]
|
||||
|
||||
# 骨骼列表
|
||||
self.joint_list = QtWidgets.QTreeWidget()
|
||||
self.joint_list.setHeaderLabels(["骨骼名称", "位置", "旋转", "缩放"])
|
||||
layout.addWidget(self.joint_list)
|
||||
for name in blends:
|
||||
blend_item = QtWidgets.QTreeWidgetItem([name])
|
||||
self.blend_tree.addTopLevelItem(blend_item)
|
||||
|
||||
blend_layout.addWidget(self.blend_tree)
|
||||
blend_group.setLayout(blend_layout)
|
||||
right_layout.addWidget(blend_group)
|
||||
|
||||
# 骨骼工具栏
|
||||
tools_layout = QtWidgets.QHBoxLayout()
|
||||
# AnimatedMap树
|
||||
anim_group = QtWidgets.QGroupBox("AnimatedMap [082/82]")
|
||||
anim_layout = QtWidgets.QVBoxLayout()
|
||||
self.anim_tree = QtWidgets.QTreeWidget()
|
||||
self.anim_tree.setHeaderHidden(True)
|
||||
|
||||
# 添加骨骼
|
||||
add_btn = IconButton("joint.png", "添加骨骼")
|
||||
add_btn.clicked.connect(self.add_joint)
|
||||
tools_layout.addWidget(add_btn)
|
||||
# 添加示例动画贴图
|
||||
anims = [
|
||||
"head_cm2_color.head_wm2_browsDown_L",
|
||||
"head_cm2_color.head_wm2_browsDown_R",
|
||||
"head_cm2_color.head_wm2_browsLateral_L",
|
||||
"head_cm2_color.head_wm2_browsLateral_R"
|
||||
]
|
||||
|
||||
# 删除骨骼
|
||||
del_btn = IconButton("delete.png", "删除骨骼")
|
||||
del_btn.clicked.connect(self.delete_joint)
|
||||
tools_layout.addWidget(del_btn)
|
||||
for name in anims:
|
||||
anim_item = QtWidgets.QTreeWidgetItem([name])
|
||||
self.anim_tree.addTopLevelItem(anim_item)
|
||||
|
||||
anim_layout.addWidget(self.anim_tree)
|
||||
anim_group.setLayout(anim_layout)
|
||||
right_layout.addWidget(anim_group)
|
||||
|
||||
# 修改骨骼
|
||||
mod_btn = IconButton("modify.png", "修改骨骼")
|
||||
mod_btn.clicked.connect(self.modify_joint)
|
||||
tools_layout.addWidget(mod_btn)
|
||||
splitter.addWidget(right_widget)
|
||||
|
||||
tools_layout.addStretch()
|
||||
layout.addLayout(tools_layout)
|
||||
# 底部按钮组
|
||||
bottom_layout = QtWidgets.QHBoxLayout()
|
||||
|
||||
return group
|
||||
# 导入组
|
||||
write_group = QtWidgets.QGroupBox("导入")
|
||||
write_layout = QtWidgets.QVBoxLayout()
|
||||
write_btns = [
|
||||
("导入关节和权重", define_utils.write_joints_weights),
|
||||
("导入几何体", define_utils.write_geometry),
|
||||
("导入蒙皮权重", define_utils.write_skin_weights),
|
||||
("导入混合变形目标", define_utils.write_blend_targets)
|
||||
]
|
||||
|
||||
def create_blendshape_definition(self):
|
||||
"""创建BlendShape定义组"""
|
||||
group = QtWidgets.QGroupBox("BlendShape定义")
|
||||
layout = QtWidgets.QVBoxLayout(group)
|
||||
for text, callback in write_btns:
|
||||
btn = QtWidgets.QPushButton(text)
|
||||
btn.clicked.connect(lambda checked=False, cb=callback: cb())
|
||||
write_layout.addWidget(btn)
|
||||
|
||||
write_group.setLayout(write_layout)
|
||||
bottom_layout.addWidget(write_group)
|
||||
|
||||
# BlendShape列表
|
||||
self.bs_list = QtWidgets.QTreeWidget()
|
||||
self.bs_list.setHeaderLabels(["名称", "目标", "权重"])
|
||||
layout.addWidget(self.bs_list)
|
||||
# 创建组
|
||||
create_group = QtWidgets.QGroupBox("创建")
|
||||
create_layout = QtWidgets.QVBoxLayout()
|
||||
create_btns = [
|
||||
("创建新的LOD", define_utils.create_new_lod),
|
||||
("绑定蒙皮", define_utils.bind_skin),
|
||||
("取消蒙皮", define_utils.unbind_skin)
|
||||
]
|
||||
|
||||
# BlendShape工具栏
|
||||
tools_layout = QtWidgets.QHBoxLayout()
|
||||
for text, callback in create_btns:
|
||||
btn = QtWidgets.QPushButton(text)
|
||||
btn.clicked.connect(lambda checked=False, cb=callback: cb())
|
||||
create_layout.addWidget(btn)
|
||||
|
||||
create_group.setLayout(create_layout)
|
||||
bottom_layout.addWidget(create_group)
|
||||
|
||||
# 添加BlendShape
|
||||
add_btn = IconButton("blendShape.png", "添加BlendShape")
|
||||
add_btn.clicked.connect(self.add_blendshape)
|
||||
tools_layout.addWidget(add_btn)
|
||||
# 工具组
|
||||
tools_group = QtWidgets.QGroupBox("工具")
|
||||
tools_layout = QtWidgets.QVBoxLayout()
|
||||
tools_btns = [
|
||||
("更新运行时LOD", define_utils.update_runtime_lod),
|
||||
("快速创建绑定", define_utils.quick_create_binding),
|
||||
("创建蒙皮", define_utils.create_skin)
|
||||
]
|
||||
|
||||
# 删除BlendShape
|
||||
del_btn = IconButton("delete.png", "删除BlendShape")
|
||||
del_btn.clicked.connect(self.delete_blendshape)
|
||||
tools_layout.addWidget(del_btn)
|
||||
for text, callback in tools_btns:
|
||||
btn = QtWidgets.QPushButton(text)
|
||||
btn.clicked.connect(lambda checked=False, cb=callback: cb())
|
||||
tools_layout.addWidget(btn)
|
||||
|
||||
tools_group.setLayout(tools_layout)
|
||||
bottom_layout.addWidget(tools_group)
|
||||
|
||||
# 修改BlendShape
|
||||
mod_btn = IconButton("modify.png", "修改BlendShape")
|
||||
mod_btn.clicked.connect(self.modify_blendshape)
|
||||
tools_layout.addWidget(mod_btn)
|
||||
|
||||
tools_layout.addStretch()
|
||||
layout.addLayout(tools_layout)
|
||||
|
||||
return group
|
||||
|
||||
# DNA定义回调
|
||||
def browse_dna_file(self):
|
||||
"""浏览DNA文件"""
|
||||
from scripts.utils import define_utils
|
||||
define_utils.browse_dna_file()
|
||||
|
||||
# 骨骼定义回调
|
||||
def add_joint(self):
|
||||
"""添加骨骼"""
|
||||
from scripts.utils import define_utils
|
||||
define_utils.add_joint()
|
||||
|
||||
def delete_joint(self):
|
||||
"""删除骨骼"""
|
||||
from scripts.utils import define_utils
|
||||
define_utils.delete_joint()
|
||||
|
||||
def modify_joint(self):
|
||||
"""修改骨骼"""
|
||||
from scripts.utils import define_utils
|
||||
define_utils.modify_joint()
|
||||
|
||||
# BlendShape定义回调
|
||||
def add_blendshape(self):
|
||||
"""添加BlendShape"""
|
||||
from scripts.utils import define_utils
|
||||
define_utils.add_blendshape()
|
||||
|
||||
def delete_blendshape(self):
|
||||
"""删除BlendShape"""
|
||||
from scripts.utils import define_utils
|
||||
define_utils.delete_blendshape()
|
||||
|
||||
def modify_blendshape(self):
|
||||
"""修改BlendShape"""
|
||||
from scripts.utils import define_utils
|
||||
define_utils.modify_blendshape()
|
||||
layout.addLayout(bottom_layout)
|
||||
|
54
scripts/ui/define_utils.py
Normal file
54
scripts/ui/define_utils.py
Normal 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
|
@ -2,130 +2,162 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#===================================== 1. Module Imports =====================================
|
||||
import maya.cmds as cmds
|
||||
import maya.mel as mel
|
||||
import sys
|
||||
import os
|
||||
from scripts.config import data
|
||||
from scripts.utils import menu_utils
|
||||
from scripts.ui import menu_utils as menu_utils
|
||||
from scripts.ui import mesh_utils as mesh_utils
|
||||
from scripts.ui import rigging_utils as rigging_utils
|
||||
from scripts.ui import define_utils as define_utils
|
||||
from scripts.ui import adjust_utils as adjust_utils
|
||||
from scripts.ui import widgets as widgets
|
||||
|
||||
try:
|
||||
from PySide2 import QtCore, QtGui, QtWidgets
|
||||
from shiboken2 import wrapInstance
|
||||
print("从PySide2加载Qt和shiboken2")
|
||||
except ImportError:
|
||||
try:
|
||||
from PySide6 import QtCore, QtGui, QtWidgets
|
||||
from shiboken6 import wrapInstance
|
||||
print("从PySide6加载Qt和shiboken6")
|
||||
except ImportError:
|
||||
try:
|
||||
from PySide import QtCore, QtGui, QtWidgets
|
||||
from shiboken import wrapInstance
|
||||
print("从PySide加载Qt和shiboken")
|
||||
except ImportError as e:
|
||||
print(f"Qt加载失败: {str(e)}")
|
||||
QtCore = QtGui = QtWidgets = None
|
||||
wrapInstance = None
|
||||
|
||||
#===================================== 2. Menu Manager Class =====================================
|
||||
|
||||
class MenuManager:
|
||||
"""菜单管理器"""
|
||||
def __init__(self, parent):
|
||||
self.parent = parent
|
||||
self.menu_bar = parent.menuBar()
|
||||
self.create_menus()
|
||||
self.create_toolbar()
|
||||
self._create_menus()
|
||||
self._create_toolbar()
|
||||
|
||||
def create_menus(self):
|
||||
def _create_menus(self):
|
||||
"""创建菜单"""
|
||||
# 文件菜单
|
||||
file_menu = self.menu_bar.addMenu("文件")
|
||||
self.add_menu_item(file_menu, "打开DNA", "open.png", menu_utils.load_dna)
|
||||
self.add_menu_item(file_menu, "保存DNA", "save.png", menu_utils.save_dna)
|
||||
self.add_menu_item(file_menu, "加载当前项目的DNA", "open.png", menu_utils.load_project_dna)
|
||||
file_menu.addSeparator()
|
||||
self.add_menu_item(file_menu, "修改混合目标名称", "rename.png", menu_utils.rename_blend_target)
|
||||
self.add_menu_item(file_menu, "重置混合目标名称", "resetname.png", menu_utils.reset_blend_target)
|
||||
file_menu.addSeparator()
|
||||
self.add_menu_item(file_menu, "导出FBX", "export.png", menu_utils.export_fbx)
|
||||
file_menu.addSeparator()
|
||||
self.add_menu_item(file_menu, "退出", "exit.png", menu_utils.safe_shutdown)
|
||||
# 定义所有菜单项
|
||||
self.menu_definitions = {
|
||||
"文件": [
|
||||
("打开DNA", "open.png", menu_utils.load_dna),
|
||||
("保存DNA", "save.png", menu_utils.save_dna),
|
||||
("加载当前项目的DNA", "open.png", menu_utils.load_project_dna),
|
||||
None,
|
||||
("修改混合目标名称", "rename.png", menu_utils.rename_blendshape_target),
|
||||
("重置混合目标名称", "resetname.png", menu_utils.reset_blendshape_target),
|
||||
None,
|
||||
("导出FBX", "export.png", menu_utils.export_fbx),
|
||||
None,
|
||||
("退出", "exit.png", menu_utils.shutdown)
|
||||
],
|
||||
"编辑": [
|
||||
("创建RL4节点", "connect.png", adjust_utils.create_rl4_node),
|
||||
("删除RL4节点", "disconnect.png", adjust_utils.delete_rl4_node),
|
||||
None,
|
||||
("镜像左至右", "mirrorL.png", adjust_utils.mirror_left_to_right),
|
||||
("镜像右至左", "mirrorR.png", adjust_utils.mirror_right_to_left),
|
||||
None,
|
||||
("姿势由A型转T型", "pose_A_To_T.png", adjust_utils.pose_a_to_t),
|
||||
("姿势由T型转A型", "pose_T_To_A.png", adjust_utils.pose_t_to_a),
|
||||
None,
|
||||
("传输LOD贴图", "locator.png", adjust_utils.transfer_lod_texture),
|
||||
("设置关节颜色", "color.png", adjust_utils.set_joint_color),
|
||||
None,
|
||||
("取消全部标记", "unmark_all.png", adjust_utils.unmark_all),
|
||||
("重建所有目标", "rebuildTargets.png", adjust_utils.rebuild_all_targets),
|
||||
None,
|
||||
("为所有表情设置关键帧", "bakeAnimation.png", adjust_utils.bake_all_animations),
|
||||
("烘焙所有表情的关键帧", "centerCurrentTime.png", adjust_utils.bake_all_keyframes)
|
||||
],
|
||||
"工具": [
|
||||
("导出蒙皮", "export_skin.png", rigging_utils.export_skin),
|
||||
("导入蒙皮", "import_skin.png", rigging_utils.import_skin),
|
||||
("拷贝蒙皮", "copy_skin.png", rigging_utils.copy_skin),
|
||||
None,
|
||||
("RBF变形器", "blendShape.png", rigging_utils.create_rbf_deformer),
|
||||
("快速绑定服装", "clothing_weight.png", rigging_utils.quick_bind_clothing),
|
||||
("克隆混合变形", "blendShape.png", rigging_utils.clone_blendshape),
|
||||
None,
|
||||
("UV传递点序", "repair_vertex_order.png", rigging_utils.transfer_uv_order),
|
||||
("面部生成控制器", "controller.png", rigging_utils.create_face_controller),
|
||||
("提取52BS", "ARKit52.png", rigging_utils.extract_52bs),
|
||||
None,
|
||||
("关节轴向修复", "joint.png", rigging_utils.fix_joint_orientation),
|
||||
("生成身体控制器", "create_body_ctrl.png", rigging_utils.create_body_controller),
|
||||
None,
|
||||
("导入面部动画", "import_face_anim.png", rigging_utils.import_face_animation),
|
||||
("导入身体动画", "import_body_anim.png", rigging_utils.import_body_animation)
|
||||
],
|
||||
"语言": [
|
||||
("中文", "chinese.png", menu_utils.set_chinese),
|
||||
("English", "english.png", menu_utils.set_english)
|
||||
],
|
||||
"帮助": [
|
||||
("帮助文档", "help.png", menu_utils.show_help),
|
||||
("关于", "warning.png", menu_utils.show_about)
|
||||
]
|
||||
}
|
||||
|
||||
# 编辑菜单
|
||||
edit_menu = self.menu_bar.addMenu("编辑")
|
||||
self.add_menu_item(edit_menu, "创建RL4节点", "connect.png", menu_utils.create_rl4_node)
|
||||
self.add_menu_item(edit_menu, "删除RL4节点", "disconnect.png", menu_utils.delete_rl4_node)
|
||||
edit_menu.addSeparator()
|
||||
self.add_menu_item(edit_menu, "镜像左至右", "mirrorL.png", menu_utils.mirror_left_to_right)
|
||||
self.add_menu_item(edit_menu, "镜像右至左", "mirrorR.png", menu_utils.mirror_right_to_left)
|
||||
edit_menu.addSeparator()
|
||||
self.add_menu_item(edit_menu, "姿势由A型转T型", "pose_A_To_T.png", menu_utils.pose_a_to_t)
|
||||
self.add_menu_item(edit_menu, "姿势由T型转A型", "pose_T_To_A.png", menu_utils.pose_t_to_a)
|
||||
edit_menu.addSeparator()
|
||||
self.add_menu_item(edit_menu, "传输LOD贴图", "locator.png", menu_utils.transfer_lod_texture)
|
||||
self.add_menu_item(edit_menu, "设置关节颜色", "color.png", menu_utils.set_joint_color)
|
||||
edit_menu.addSeparator()
|
||||
self.add_menu_item(edit_menu, "取消全部标记", "unmark_all.png", menu_utils.unmark_all)
|
||||
self.add_menu_item(edit_menu, "重建所有目标", "rebuildTargets.png", menu_utils.rebuild_all_targets)
|
||||
edit_menu.addSeparator()
|
||||
self.add_menu_item(edit_menu, "为所有表情设置关键帧", "bakeAnimation.png", menu_utils.bake_all_animations)
|
||||
self.add_menu_item(edit_menu, "烘焙所有表情的关键帧", "centerCurrentTime.png", menu_utils.bake_all_keyframes)
|
||||
|
||||
# 工具菜单
|
||||
tools_menu = self.menu_bar.addMenu("工具")
|
||||
self.add_menu_item(tools_menu, "导出蒙皮", "export_skin.png", menu_utils.export_skin)
|
||||
self.add_menu_item(tools_menu, "导入蒙皮", "import_skin.png", menu_utils.import_skin)
|
||||
self.add_menu_item(tools_menu, "拷贝蒙皮", "copy_skin.png", menu_utils.copy_skin)
|
||||
tools_menu.addSeparator()
|
||||
self.add_menu_item(tools_menu, "RBF变形器", "blendShape.png", menu_utils.create_rbf_deformer)
|
||||
self.add_menu_item(tools_menu, "快速绑定服装", "clothing_weight.png", menu_utils.quick_bind_clothing)
|
||||
self.add_menu_item(tools_menu, "克隆混合变形", "blendShape.png", menu_utils.clone_blendshape)
|
||||
tools_menu.addSeparator()
|
||||
self.add_menu_item(tools_menu, "UV传递点序", "repair_vertex_order.png", menu_utils.transfer_uv_order)
|
||||
self.add_menu_item(tools_menu, "面部生成控制器", "controller.png", menu_utils.create_face_controller)
|
||||
self.add_menu_item(tools_menu, "提取52BS", "ARKit52.png", menu_utils.extract_52bs)
|
||||
tools_menu.addSeparator()
|
||||
self.add_menu_item(tools_menu, "关节轴向修复", "joint.png", menu_utils.fix_joint_orientation)
|
||||
self.add_menu_item(tools_menu, "生成身体控制器", "create_body_ctrl.png", menu_utils.create_body_controller)
|
||||
tools_menu.addSeparator()
|
||||
self.add_menu_item(tools_menu, "导入面部动画", "import_face_anim.png", menu_utils.import_face_animation)
|
||||
self.add_menu_item(tools_menu, "导入身体动画", "import_body_anim.png", menu_utils.import_body_animation)
|
||||
|
||||
# 语言菜单
|
||||
lang_menu = self.menu_bar.addMenu("语言")
|
||||
self.add_menu_item(lang_menu, "中文", "chinese.png", menu_utils.set_chinese)
|
||||
self.add_menu_item(lang_menu, "English", "english.png", menu_utils.set_english)
|
||||
|
||||
# 帮助菜单
|
||||
help_menu = self.menu_bar.addMenu("帮助")
|
||||
self.add_menu_item(help_menu, "帮助文档", "help.png", menu_utils.show_help)
|
||||
self.add_menu_item(help_menu, "关于", "warning.png", menu_utils.show_about)
|
||||
|
||||
def create_toolbar(self):
|
||||
# 创建所有菜单
|
||||
for menu_name, items in self.menu_definitions.items():
|
||||
menu = self.menu_bar.addMenu(menu_name)
|
||||
self._add_menu_items(menu, items)
|
||||
|
||||
def _create_toolbar(self):
|
||||
"""创建工具栏"""
|
||||
toolbar = self.parent.addToolBar("主工具栏")
|
||||
toolbar.setMovable(False)
|
||||
toolbar.setMovable(True) # 允许移动
|
||||
toolbar.setFloatable(True) # 允许浮动
|
||||
|
||||
# 添加工具栏按钮
|
||||
self.add_toolbar_item(toolbar, "保存DNA", "save.png", menu_utils.save_dna)
|
||||
self.add_toolbar_item(toolbar, "加载当前项目DNA", "open.png", menu_utils.load_project_dna)
|
||||
# 定义工具栏按钮
|
||||
toolbar_items = [
|
||||
("保存DNA", "save.png", menu_utils.save_dna),
|
||||
("加载当前项目DNA", "open.png", menu_utils.load_project_dna),
|
||||
None,
|
||||
("创建RL4节点", "connect.png", adjust_utils.create_rl4_node),
|
||||
("删除RL4节点", "disconnect.png", adjust_utils.delete_rl4_node),
|
||||
None,
|
||||
("导出FBX蒙皮", "export_skin.png", rigging_utils.export_skin),
|
||||
None,
|
||||
("退出", "exit.png", menu_utils.shutdown),
|
||||
None,
|
||||
("帮助文档", "help.png", menu_utils.show_help),
|
||||
("关于", "warning.png", menu_utils.show_about)
|
||||
]
|
||||
|
||||
def add_menu_item(self, menu, text, icon, callback):
|
||||
self._add_toolbar_items(toolbar, toolbar_items)
|
||||
|
||||
def _add_menu_items(self, menu, items):
|
||||
"""添加菜单项"""
|
||||
action = QtWidgets.QAction(text, self.parent)
|
||||
if icon:
|
||||
icon_path = os.path.join(data.ICONS_PATH, icon)
|
||||
if os.path.exists(icon_path):
|
||||
action.setIcon(QtGui.QIcon(icon_path))
|
||||
action.triggered.connect(callback)
|
||||
menu.addAction(action)
|
||||
return action
|
||||
|
||||
def add_toolbar_item(self, toolbar, text, icon, callback):
|
||||
for item in items:
|
||||
if item is None:
|
||||
menu.addSeparator()
|
||||
else:
|
||||
text, icon, callback = item
|
||||
action = QtWidgets.QAction(text, self.parent)
|
||||
if icon:
|
||||
icon_path = os.path.join(data.ICONS_PATH, icon)
|
||||
if os.path.exists(icon_path):
|
||||
action.setIcon(QtGui.QIcon(icon_path))
|
||||
action.triggered.connect(lambda checked=False, cb=callback: cb())
|
||||
menu.addAction(action)
|
||||
|
||||
def _add_toolbar_items(self, toolbar, items):
|
||||
"""添加工具栏项"""
|
||||
action = QtWidgets.QAction(text, self.parent)
|
||||
if icon:
|
||||
icon_path = os.path.join(data.ICONS_PATH, icon)
|
||||
if os.path.exists(icon_path):
|
||||
action.setIcon(QtGui.QIcon(icon_path))
|
||||
action.triggered.connect(callback)
|
||||
toolbar.addAction(action)
|
||||
return action
|
||||
for item in items:
|
||||
if item is None:
|
||||
toolbar.addSeparator()
|
||||
else:
|
||||
text, icon, callback = item
|
||||
action = QtWidgets.QAction(text, self.parent)
|
||||
if icon:
|
||||
icon_path = os.path.join(data.ICONS_PATH, icon)
|
||||
if os.path.exists(icon_path):
|
||||
action.setIcon(QtGui.QIcon(icon_path))
|
||||
action.triggered.connect(lambda checked=False, cb=callback: cb())
|
||||
toolbar.addAction(action)
|
79
scripts/ui/menu_utils.py
Normal file
79
scripts/ui/menu_utils.py
Normal 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
153
scripts/ui/mesh.py
Normal 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
115
scripts/ui/mesh_utils.py
Normal 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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -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)
|
@ -6,193 +6,228 @@ import maya.cmds as cmds
|
||||
import maya.mel as mel
|
||||
import sys
|
||||
import os
|
||||
|
||||
from scripts.ui.widgets import (BaseWidget, DNABrowser, DescriptionWidget, IconButton, SearchLineEdit)
|
||||
from scripts.config import data
|
||||
from scripts.ui import rigging_utils as rigging_utils
|
||||
from scripts.ui import widgets as widgets
|
||||
|
||||
try:
|
||||
from PySide2 import QtCore, QtGui, QtWidgets
|
||||
from shiboken2 import wrapInstance
|
||||
print("从PySide2加载Qt和shiboken2")
|
||||
except ImportError:
|
||||
try:
|
||||
from PySide6 import QtCore, QtGui, QtWidgets
|
||||
from shiboken6 import wrapInstance
|
||||
print("从PySide6加载Qt和shiboken6")
|
||||
except ImportError:
|
||||
try:
|
||||
from PySide import QtCore, QtGui, QtWidgets
|
||||
from shiboken import wrapInstance
|
||||
print("从PySide加载Qt和shiboken")
|
||||
except ImportError as e:
|
||||
print(f"Qt加载失败: {str(e)}")
|
||||
QtCore = QtGui = QtWidgets = None
|
||||
wrapInstance = None
|
||||
|
||||
#===================================== 2. Rigging Tab Class =====================================
|
||||
class RigTab(BaseWidget):
|
||||
class RiggingTab(widgets.BaseWidget):
|
||||
"""绑定标签页"""
|
||||
def __init__(self, parent=None):
|
||||
super(RigTab, self).__init__(parent)
|
||||
self.dna_browser = None
|
||||
self.dna_flow = None # DNA按钮流布局
|
||||
self.scale_slider = None
|
||||
self.dna_buttons = [] # 存储DNA按钮
|
||||
self.current_scale = 90 # 当前缩放值
|
||||
super(RiggingTab, self).__init__(parent)
|
||||
|
||||
def setup_ui(self):
|
||||
"""初始化UI"""
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.setSpacing(self.SPACING)
|
||||
layout.setContentsMargins(self.MARGINS, self.MARGINS,
|
||||
self.MARGINS, self.MARGINS)
|
||||
|
||||
# DNA浏览器区域
|
||||
browser_group = QtWidgets.QGroupBox("预览")
|
||||
browser_layout = QtWidgets.QVBoxLayout()
|
||||
|
||||
# DNA按钮流布局
|
||||
self.dna_flow = QtWidgets.QGridLayout()
|
||||
self.dna_flow.setSpacing(5)
|
||||
|
||||
# 创建滚动区域
|
||||
scroll = QtWidgets.QScrollArea()
|
||||
scroll.setWidgetResizable(True)
|
||||
scroll.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
||||
layout.addWidget(scroll)
|
||||
scroll_widget = QtWidgets.QWidget()
|
||||
scroll_widget.setLayout(self.dna_flow)
|
||||
|
||||
# 创建内容控件
|
||||
content = QtWidgets.QWidget()
|
||||
content_layout = QtWidgets.QVBoxLayout(content)
|
||||
scroll.setWidget(content)
|
||||
scroll_area = QtWidgets.QScrollArea()
|
||||
scroll_area.setWidget(scroll_widget)
|
||||
scroll_area.setWidgetResizable(True)
|
||||
scroll_area.setMinimumHeight(300)
|
||||
scroll_area.setStyleSheet("QScrollArea { background-color: #333333; }")
|
||||
browser_layout.addWidget(scroll_area)
|
||||
|
||||
# DNA部分
|
||||
dna_group = self.create_dna_group()
|
||||
content_layout.addWidget(dna_group)
|
||||
# 缩放滑块
|
||||
slider_layout = QtWidgets.QHBoxLayout()
|
||||
self.scale_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
|
||||
self.scale_slider.setRange(50, 150)
|
||||
self.scale_slider.setValue(self.current_scale)
|
||||
self.scale_slider.valueChanged.connect(self._on_scale_changed)
|
||||
|
||||
# 资产部分
|
||||
asset_group = self.create_asset_group()
|
||||
content_layout.addWidget(asset_group)
|
||||
|
||||
# 描述部分
|
||||
self.description_widget = DescriptionWidget()
|
||||
content_layout.addWidget(self.description_widget)
|
||||
|
||||
# 骨架工具
|
||||
skeleton_tools = self.create_skeleton_tools()
|
||||
content_layout.addWidget(skeleton_tools)
|
||||
|
||||
content_layout.addStretch()
|
||||
|
||||
def create_dna_group(self):
|
||||
"""创建DNA组"""
|
||||
group = QtWidgets.QGroupBox("DNA")
|
||||
layout = QtWidgets.QVBoxLayout(group)
|
||||
|
||||
# DNA浏览器
|
||||
self.dna_browser = DNABrowser()
|
||||
self.dna_browser.dnaSelected.connect(self.on_dna_selected)
|
||||
layout.addWidget(self.dna_browser)
|
||||
# 添加数值显示
|
||||
self.scale_label = QtWidgets.QLabel(f"{self.current_scale}")
|
||||
slider_layout.addWidget(self.scale_label)
|
||||
slider_layout.addWidget(self.scale_slider)
|
||||
|
||||
# 导入导出按钮
|
||||
btn_layout = QtWidgets.QHBoxLayout()
|
||||
button_layout = QtWidgets.QHBoxLayout()
|
||||
button_layout.addWidget(widgets.IconButton("export.png", "导出预览"))
|
||||
button_layout.addWidget(widgets.IconButton("import.png", "导入预览"))
|
||||
slider_layout.addLayout(button_layout)
|
||||
|
||||
export_btn = IconButton("export.png", "导出设置")
|
||||
export_btn.clicked.connect(self.export_settings)
|
||||
btn_layout.addWidget(export_btn)
|
||||
browser_layout.addLayout(slider_layout)
|
||||
browser_group.setLayout(browser_layout)
|
||||
layout.addWidget(browser_group)
|
||||
|
||||
import_btn = IconButton("import.png", "导入设置")
|
||||
import_btn.clicked.connect(self.import_settings)
|
||||
btn_layout.addWidget(import_btn)
|
||||
|
||||
btn_layout.addStretch()
|
||||
layout.addLayout(btn_layout)
|
||||
|
||||
return group
|
||||
|
||||
def create_asset_group(self):
|
||||
"""创建资产组"""
|
||||
group = QtWidgets.QGroupBox("资产")
|
||||
layout = QtWidgets.QFormLayout(group)
|
||||
# 资产设置区域
|
||||
settings_layout = QtWidgets.QVBoxLayout()
|
||||
|
||||
# 项目路径
|
||||
project_layout = QtWidgets.QHBoxLayout()
|
||||
self.project_edit = QtWidgets.QLineEdit()
|
||||
project_btn = IconButton("target.png", "选择项目路径")
|
||||
project_btn.clicked.connect(self.browse_project)
|
||||
project_layout.addWidget(self.project_edit)
|
||||
project_layout.addWidget(project_btn)
|
||||
layout.addRow("项目路径:", project_layout)
|
||||
project_layout.addWidget(QtWidgets.QLabel("项目路径:"))
|
||||
project_input = widgets.ModernLineEdit(read_only=True)
|
||||
project_input.setText("D:/Personal/Document/maya/SuperRiggingEditor/files/data/MetaHuman")
|
||||
project_layout.addWidget(project_input)
|
||||
project_layout.addWidget(widgets.IconButton("folder.png", "加载..."))
|
||||
settings_layout.addLayout(project_layout)
|
||||
|
||||
# 预设文件
|
||||
preset_layout = QtWidgets.QHBoxLayout()
|
||||
self.preset_edit = QtWidgets.QLineEdit()
|
||||
preset_btn = IconButton("target.png", "选择预设文件")
|
||||
preset_btn.clicked.connect(self.browse_preset)
|
||||
preset_layout.addWidget(self.preset_edit)
|
||||
preset_layout.addWidget(preset_btn)
|
||||
layout.addRow("预设文件:", preset_layout)
|
||||
preset_layout.addWidget(QtWidgets.QLabel("预设文件:"))
|
||||
preset_input = widgets.ModernLineEdit(read_only=True)
|
||||
preset_layout.addWidget(preset_input)
|
||||
preset_layout.addWidget(widgets.IconButton("folder.png", "加载..."))
|
||||
settings_layout.addLayout(preset_layout)
|
||||
|
||||
# 数据分层
|
||||
layer_layout = QtWidgets.QHBoxLayout()
|
||||
self.layer_combo = QtWidgets.QComboBox()
|
||||
self.layer_combo.addItems(["行为"])
|
||||
self.override_check = QtWidgets.QCheckBox("覆盖表情")
|
||||
layer_layout.addWidget(self.layer_combo)
|
||||
layer_layout.addWidget(self.override_check)
|
||||
layout.addRow("数据分层:", layer_layout)
|
||||
# 数据层设置
|
||||
data_layout = QtWidgets.QHBoxLayout()
|
||||
data_layout.addWidget(QtWidgets.QLabel("数据层:"))
|
||||
data_combo = QtWidgets.QComboBox()
|
||||
data_combo.addItem("无效(未定义)")
|
||||
data_layout.addWidget(data_combo)
|
||||
data_layout.addWidget(QtWidgets.QCheckBox("检查清理"))
|
||||
settings_layout.addLayout(data_layout)
|
||||
|
||||
return group
|
||||
# 描述信息
|
||||
desc_group = QtWidgets.QGroupBox("描述")
|
||||
desc_layout = QtWidgets.QGridLayout()
|
||||
|
||||
def create_skeleton_tools(self):
|
||||
"""创建骨架工具"""
|
||||
group = QtWidgets.QGroupBox("骨架工具")
|
||||
layout = QtWidgets.QHBoxLayout(group)
|
||||
desc_fields = [
|
||||
("名称:", ""),
|
||||
("原型:", "亚洲人"),
|
||||
("性别:", "女性"),
|
||||
("年龄:", "18"),
|
||||
("变换单位:", "厘米"),
|
||||
("旋转单位:", "角度"),
|
||||
("向上轴:", "Z轴向上"),
|
||||
("LOD升级:", "0")
|
||||
]
|
||||
|
||||
# 清空选项
|
||||
clear_btn = IconButton("delete.png", "清空选项")
|
||||
clear_btn.clicked.connect(self.clear_options)
|
||||
layout.addWidget(clear_btn)
|
||||
for i, (label, default) in enumerate(desc_fields):
|
||||
desc_layout.addWidget(QtWidgets.QLabel(label), i, 0)
|
||||
if i in [1, 2, 4, 5, 6]: # 这些字段使用下拉框
|
||||
combo = QtWidgets.QComboBox()
|
||||
combo.addItem(default)
|
||||
desc_layout.addWidget(combo, i, 1)
|
||||
else: # 其他字段使用输入框
|
||||
input_widget = widgets.ModernLineEdit()
|
||||
input_widget.setText(default)
|
||||
desc_layout.addWidget(input_widget, i, 1)
|
||||
|
||||
# 导入骨架
|
||||
import_btn = IconButton("HIKCharacterToolSkeleton.png", "导入骨架")
|
||||
import_btn.clicked.connect(self.import_skeleton)
|
||||
layout.addWidget(import_btn)
|
||||
desc_group.setLayout(desc_layout)
|
||||
settings_layout.addWidget(desc_group)
|
||||
|
||||
# 创建骨架
|
||||
create_btn = IconButton("HIKcreateControlRig.png", "创建骨架")
|
||||
create_btn.clicked.connect(self.create_skeleton)
|
||||
layout.addWidget(create_btn)
|
||||
# 底部按钮
|
||||
bottom_layout = QtWidgets.QHBoxLayout()
|
||||
btn = widgets.IconButton("clear.png", "清空预览")
|
||||
btn.clicked.connect(lambda checked=False, cb=rigging_utils.clear_preview: cb())
|
||||
bottom_layout.addWidget(btn)
|
||||
bottom_layout.addWidget(widgets.IconButton("import.png", "导入预览"))
|
||||
bottom_layout.addWidget(widgets.IconButton("create.png", "创建预览"))
|
||||
settings_layout.addLayout(bottom_layout)
|
||||
|
||||
layout.addStretch()
|
||||
return group
|
||||
layout.addLayout(settings_layout)
|
||||
|
||||
# DNA功能回调
|
||||
def on_dna_selected(self, dna_path):
|
||||
"""DNA文件选中"""
|
||||
from scripts.utils import rigging_utils
|
||||
rigging_utils.load_dna(dna_path)
|
||||
def _on_scale_changed(self, value):
|
||||
"""缩放值改变时更新按钮大小"""
|
||||
self.current_scale = value
|
||||
self.scale_label.setText(f"{value}")
|
||||
self._update_button_size()
|
||||
|
||||
def export_settings(self):
|
||||
"""导出设置"""
|
||||
from scripts.utils import rigging_utils
|
||||
rigging_utils.export_settings()
|
||||
|
||||
def import_settings(self):
|
||||
"""导入设置"""
|
||||
from scripts.utils import rigging_utils
|
||||
rigging_utils.import_settings()
|
||||
|
||||
# 资产功能回调
|
||||
def browse_project(self):
|
||||
"""浏览项目路径"""
|
||||
path = QtWidgets.QFileDialog.getExistingDirectory(
|
||||
self, "选择项目路径", os.path.expanduser("~"))
|
||||
if path:
|
||||
self.project_edit.setText(path)
|
||||
def _update_button_size(self):
|
||||
"""更新所有DNA按钮的大小"""
|
||||
size = int(self.current_scale * 1.5) # 将滑块值转换为合适的按钮大小
|
||||
for btn in self.dna_buttons:
|
||||
btn.setFixedSize(size, size)
|
||||
|
||||
def browse_preset(self):
|
||||
"""浏览预设文件"""
|
||||
file_path, _ = QtWidgets.QFileDialog.getOpenFileName(
|
||||
self, "选择预设文件", os.path.expanduser("~"),
|
||||
"预设文件 (*.json *.preset)")
|
||||
if file_path:
|
||||
self.preset_edit.setText(file_path)
|
||||
def _create_dna_button(self, dna_file, img_file):
|
||||
"""创建DNA按钮"""
|
||||
btn = QtWidgets.QPushButton()
|
||||
btn.setFixedSize(int(self.current_scale * 1.5), int(self.current_scale * 1.5))
|
||||
|
||||
# 设置图标
|
||||
if os.path.exists(img_file):
|
||||
icon = QtGui.QIcon(img_file)
|
||||
else:
|
||||
# 使用默认图标
|
||||
default_img = os.path.join(data.DNA_IMG_PATH, "Default.png")
|
||||
if os.path.exists(default_img):
|
||||
icon = QtGui.QIcon(default_img)
|
||||
else:
|
||||
# 如果默认图标也不存在,使用系统默认图标
|
||||
icon = QtGui.QIcon.fromTheme("image-missing")
|
||||
|
||||
btn.setIcon(icon)
|
||||
btn.setIconSize(QtCore.QSize(int(self.current_scale * 1.4), int(self.current_scale * 1.4)))
|
||||
|
||||
# 设置工具提示为DNA文件名
|
||||
btn.setToolTip(os.path.basename(dna_file))
|
||||
|
||||
# 点击事件
|
||||
btn.clicked.connect(lambda: self._on_dna_clicked(dna_file))
|
||||
|
||||
return btn
|
||||
|
||||
def refresh_dna_browser(self):
|
||||
"""刷新DNA浏览器"""
|
||||
# 清除现有按钮
|
||||
for btn in self.dna_buttons:
|
||||
self.dna_flow.removeWidget(btn)
|
||||
btn.deleteLater()
|
||||
self.dna_buttons.clear()
|
||||
|
||||
# 获取DNA和图片文件
|
||||
dna_path = data.DNA_FILE_PATH
|
||||
img_path = data.DNA_IMG_PATH
|
||||
|
||||
if not os.path.exists(dna_path) or not os.path.exists(img_path):
|
||||
return
|
||||
|
||||
# 骨架工具回调
|
||||
def clear_options(self):
|
||||
"""清空选项"""
|
||||
from scripts.utils import rigging_utils
|
||||
rigging_utils.clear_options()
|
||||
# 加载DNA文件和对应的预览图
|
||||
dna_files = [f for f in os.listdir(dna_path) if f.endswith('.dna')]
|
||||
|
||||
def import_skeleton(self):
|
||||
"""导入骨架"""
|
||||
from scripts.utils import rigging_utils
|
||||
rigging_utils.import_skeleton()
|
||||
|
||||
def create_skeleton(self):
|
||||
"""创建骨架"""
|
||||
from scripts.utils import rigging_utils
|
||||
rigging_utils.create_skeleton()
|
||||
for i, dna_file in enumerate(dna_files):
|
||||
base_name = os.path.splitext(dna_file)[0]
|
||||
img_file = os.path.join(img_path, f"{base_name}.png")
|
||||
dna_file_path = os.path.join(dna_path, dna_file)
|
||||
|
||||
if os.path.exists(img_file):
|
||||
btn = self._create_dna_button(dna_file_path, img_file)
|
||||
self.dna_buttons.append(btn)
|
||||
|
||||
# 添加到网格布局
|
||||
row = i // 4 # 每行4个按钮
|
||||
col = i % 4
|
||||
self.dna_flow.addWidget(btn, row, col)
|
||||
|
||||
def _on_dna_clicked(self, dna_file):
|
||||
"""DNA按钮点击事件"""
|
||||
try:
|
||||
rigging_utils.load_dna(dna_file)
|
||||
except Exception as e:
|
||||
cmds.warning(f"加载DNA失败: {str(e)}")
|
112
scripts/ui/rigging_utils.py
Normal file
112
scripts/ui/rigging_utils.py
Normal 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
|
||||
|
@ -1,28 +1,33 @@
|
||||
/* 全局 QPushButton 样式 */
|
||||
/* 现代深色主题 */
|
||||
* {
|
||||
font-family: "Segoe UI", "Microsoft YaHei";
|
||||
font-size: 9pt;
|
||||
}
|
||||
|
||||
/* 全局 QPushButton 样式 */
|
||||
QPushButton {
|
||||
background-color: #2A2A2A;
|
||||
color: #CCCCCC;
|
||||
background-color: #2d2d2d;
|
||||
color: #e0e0e0;
|
||||
border: 1px solid #3d3d3d;
|
||||
border-radius: 3px;
|
||||
padding: 5px;
|
||||
font-weight: bold;
|
||||
min-width: 80px;
|
||||
border: 1px solid #444444;
|
||||
padding: 4px 12px;
|
||||
min-height: 22px;
|
||||
}
|
||||
|
||||
QPushButton:hover {
|
||||
background-color: #3A3A3A;
|
||||
border-color: #555555;
|
||||
background-color: #3d3d3d;
|
||||
border-color: #4d4d4d;
|
||||
}
|
||||
|
||||
QPushButton:pressed {
|
||||
background-color: #1A1A1A;
|
||||
border-color: #333333;
|
||||
background-color: #1d1d1d;
|
||||
border-color: #0078d4;
|
||||
}
|
||||
|
||||
QPushButton:disabled {
|
||||
background-color: #1A1A1A;
|
||||
border-color: #333333;
|
||||
background-color: #252525;
|
||||
color: #666666;
|
||||
border-color: #2d2d2d;
|
||||
}
|
||||
|
||||
/* 单独的消息按钮样式(可选) */
|
||||
@ -50,15 +55,14 @@ QPushButton.message-button:pressed {
|
||||
|
||||
/* 主窗口样式 */
|
||||
QMainWindow {
|
||||
background-color: #333333;
|
||||
color: #CCCCCC;
|
||||
background-color: #1e1e1e;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
/* 菜单栏样式 */
|
||||
QMenuBar {
|
||||
background-color: #2A2A2A;
|
||||
color: #CCCCCC;
|
||||
border-bottom: 1px solid #222222;
|
||||
background-color: #1e1e1e;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
QMenuBar::item {
|
||||
@ -67,7 +71,7 @@ QMenuBar::item {
|
||||
}
|
||||
|
||||
QMenuBar::item:selected {
|
||||
background-color: #444444;
|
||||
background-color: #2d2d2d;
|
||||
}
|
||||
|
||||
QMenuBar::item:pressed {
|
||||
@ -108,25 +112,30 @@ QToolButton:disabled {
|
||||
|
||||
/* 标签页样式 */
|
||||
QTabWidget::pane {
|
||||
border: 1px solid #222222;
|
||||
background-color: #333333;
|
||||
border: 1px solid #2d2d2d;
|
||||
background: #1e1e1e;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
QTabBar::tab {
|
||||
background-color: #333333;
|
||||
border: 1px solid #444444;
|
||||
color: #CCCCCC;
|
||||
padding: 5px 10px;
|
||||
background: #252525;
|
||||
border: 1px solid #2d2d2d;
|
||||
border-bottom: none;
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
min-width: 80px;
|
||||
padding: 4px 12px;
|
||||
margin-right: 2px;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
QTabBar::tab:selected {
|
||||
background-color: #444444;
|
||||
border-bottom-color: #555555;
|
||||
background: #2d2d2d;
|
||||
border-bottom: 2px solid #0078d4;
|
||||
}
|
||||
|
||||
QTabBar::tab:hover {
|
||||
background-color: #444444;
|
||||
background: #303030;
|
||||
}
|
||||
|
||||
QTabBar::tab:pressed {
|
||||
@ -135,36 +144,54 @@ QTabBar::tab:pressed {
|
||||
|
||||
/* 列表和树形控件样式 */
|
||||
QTreeView, QListView {
|
||||
background-color: #2A2A2A;
|
||||
border: 1px solid #222222;
|
||||
color: #CCCCCC;
|
||||
background-color: #252525;
|
||||
border: 1px solid #2d2d2d;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
QTreeView::item:hover, QListView::item:hover {
|
||||
background-color: #3A3A3A;
|
||||
QTreeView::item {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
QTreeView::item:selected, QListView::item:selected {
|
||||
background-color: #444444;
|
||||
QTreeView::item:selected {
|
||||
background-color: #0078d4;
|
||||
}
|
||||
|
||||
/* 输入框样式 */
|
||||
QLineEdit {
|
||||
background-color: #2A2A2A;
|
||||
border: 1px solid #222222;
|
||||
border-radius: 2px;
|
||||
color: #CCCCCC;
|
||||
padding: 3px;
|
||||
background-color: #252525;
|
||||
color: #e0e0e0;
|
||||
border: 1px solid #3d3d3d;
|
||||
border-radius: 3px;
|
||||
padding: 2px 6px;
|
||||
min-height: 20px;
|
||||
}
|
||||
|
||||
QLineEdit:focus {
|
||||
border-color: #0078d4;
|
||||
}
|
||||
|
||||
QLineEdit:read-only {
|
||||
background-color: #1e1e1e;
|
||||
border-color: #2d2d2d;
|
||||
}
|
||||
|
||||
/* 下拉框样式 */
|
||||
QComboBox {
|
||||
background-color: #2A2A2A;
|
||||
border: 1px solid #222222;
|
||||
border-radius: 2px;
|
||||
color: #CCCCCC;
|
||||
padding: 3px;
|
||||
min-width: 100px;
|
||||
background-color: #252525;
|
||||
color: #e0e0e0;
|
||||
border: 1px solid #3d3d3d;
|
||||
border-radius: 3px;
|
||||
padding: 2px 6px;
|
||||
min-height: 20px;
|
||||
}
|
||||
|
||||
QComboBox:hover {
|
||||
border-color: #4d4d4d;
|
||||
}
|
||||
|
||||
QComboBox:focus {
|
||||
border-color: #0078d4;
|
||||
}
|
||||
|
||||
QComboBox::drop-down {
|
||||
@ -173,28 +200,27 @@ QComboBox::drop-down {
|
||||
}
|
||||
|
||||
QComboBox::down-arrow {
|
||||
border-image: url(:/resources/icons/down_arrow.png);
|
||||
border-image: url(resources/icons/down_arrow.png);
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
QScrollBar:vertical {
|
||||
background: #373737;
|
||||
width: 16px;
|
||||
background: #1e1e1e;
|
||||
width: 10px;
|
||||
margin: 0;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
QScrollBar::handle:vertical {
|
||||
background: #4b5cc4;
|
||||
background: #3d3d3d;
|
||||
min-height: 16px;
|
||||
border-radius: 5px;
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
QScrollBar::handle:vertical:hover {
|
||||
background: #5b6cd4;
|
||||
background: #4d4d4d;
|
||||
}
|
||||
|
||||
QScrollBar::handle:vertical:pressed {
|
||||
@ -206,21 +232,20 @@ QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {
|
||||
}
|
||||
|
||||
QScrollBar:horizontal {
|
||||
background: #373737;
|
||||
height: 16px;
|
||||
background: #1e1e1e;
|
||||
height: 10px;
|
||||
margin: 0;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
QScrollBar::handle:horizontal {
|
||||
background: #4b5cc4;
|
||||
background: #3d3d3d;
|
||||
min-width: 16px;
|
||||
border-radius: 5px;
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
QScrollBar::handle:horizontal:hover {
|
||||
background: #5b6cd4;
|
||||
background: #4d4d4d;
|
||||
}
|
||||
|
||||
QScrollBar::handle:horizontal:pressed {
|
||||
@ -233,30 +258,34 @@ QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal {
|
||||
|
||||
/* 分组框样式 */
|
||||
QGroupBox {
|
||||
border: 1px solid #222222;
|
||||
border-radius: 3px;
|
||||
margin-top: 6px;
|
||||
padding-top: 6px;
|
||||
color: #CCCCCC;
|
||||
background-color: #252525;
|
||||
border: 1px solid #2d2d2d;
|
||||
border-radius: 4px;
|
||||
margin-top: 8px;
|
||||
padding-top: 8px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
QGroupBox::title {
|
||||
left: 7px;
|
||||
padding: 0px 3px;
|
||||
margin-top: 8px;
|
||||
margin-left: 8px;
|
||||
padding: 0 4px;
|
||||
color: #0078d4;
|
||||
}
|
||||
|
||||
/* 状态栏样式 */
|
||||
QStatusBar {
|
||||
background-color: #333333;
|
||||
color: #CCCCCC;
|
||||
background-color: #1e1e1e;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
/* 工具提示样式 */
|
||||
QToolTip {
|
||||
background-color: #2A2A2A;
|
||||
border: 1px solid #222222;
|
||||
color: #CCCCCC;
|
||||
padding: 3px;
|
||||
background-color: #252525;
|
||||
color: #e0e0e0;
|
||||
border: 1px solid #3d3d3d;
|
||||
border-radius: 3px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
/* DNA 浏览器样式 */
|
||||
@ -283,18 +312,16 @@ QListWidget::item:selected {
|
||||
|
||||
/* 菜单样式 */
|
||||
QMenu {
|
||||
background-color: #2A2A2A;
|
||||
border: 1px solid #444444;
|
||||
color: #CCCCCC;
|
||||
background-color: #252525;
|
||||
border: 1px solid #3d3d3d;
|
||||
}
|
||||
|
||||
QMenu::item {
|
||||
background-color: transparent;
|
||||
padding: 5px 20px;
|
||||
padding: 4px 20px;
|
||||
}
|
||||
|
||||
QMenu::item:selected {
|
||||
background-color: #444444;
|
||||
background-color: #3d3d3d;
|
||||
}
|
||||
|
||||
QMenu::item:pressed {
|
||||
@ -308,7 +335,8 @@ QDialog {
|
||||
}
|
||||
|
||||
QLabel {
|
||||
color: #E0E0E0;
|
||||
color: #CCCCCC;
|
||||
padding: 0px 2px;
|
||||
}
|
||||
|
||||
/* 基础按钮样式 - 用于安装界面 */
|
||||
@ -337,3 +365,47 @@ QLabel {
|
||||
background-color: #404040;
|
||||
color: #E0E0E0;
|
||||
}
|
||||
|
||||
/* 功能按钮样式 */
|
||||
QPushButton, QToolButton {
|
||||
background-color: #404040;
|
||||
color: #E0E0E0;
|
||||
border: 1px solid #505050;
|
||||
border-radius: 5px;
|
||||
padding: 5px;
|
||||
min-width: 80px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
QPushButton:hover, QToolButton:hover {
|
||||
background-color: #505050;
|
||||
border-color: #606060;
|
||||
}
|
||||
|
||||
QPushButton:pressed, QToolButton:pressed {
|
||||
background-color: #303030;
|
||||
border-color: #0078d4;
|
||||
}
|
||||
|
||||
QPushButton:disabled, QToolButton:disabled {
|
||||
background-color: #353535;
|
||||
color: #808080;
|
||||
border-color: #404040;
|
||||
}
|
||||
|
||||
/* 工具栏按钮样式 */
|
||||
QToolBar QToolButton {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
border-radius: 2px;
|
||||
padding: 4px;
|
||||
min-width: 24px;
|
||||
}
|
||||
|
||||
QToolBar QToolButton:hover {
|
||||
background-color: #505050;
|
||||
}
|
||||
|
||||
QToolBar QToolButton:pressed {
|
||||
background-color: #303030;
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -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
|
@ -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="确定"
|
||||
)
|
@ -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)}")
|
@ -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("创建骨架功能待实现")
|
||||
|
||||
# 其他功能函数...
|
Loading…
Reference in New Issue
Block a user