This commit is contained in:
Jeffreytsai1004 2025-02-06 04:00:17 +08:00
parent eea26bcc87
commit 7aeee81643
21 changed files with 5196 additions and 633 deletions

View File

@ -6,56 +6,83 @@
import os
import sys
import webbrowser
# Maya imports
import maya.mel as mel
import maya.cmds as cmds
import maya.OpenMayaUI as omui
# Qt imports
from PySide2 import QtWidgets, QtGui, QtCore
from shiboken2 import wrapInstance
# Custom imports
from scripts.config import data
QtCore, QtGui, QtWidgets = data.Qt()
#===================================== 2. Global Variables =====================================
try:
ROOT_PATH = data.ROOT_PATH
except NameError:
ROOT_PATH = os.path.abspath(os.path.dirname(__file__)).replace("\\", "/")
TOOL_NAME = data.TOOL_NAME
TOOL_VERSION = data.TOOL_VERSION
TOOL_AUTHOR = data.TOOL_AUTHOR
TOOL_LANG = data.TOOL_LANG
TOOL_WSCL_NAME = data.TOOL_WSCL_NAME
TOOL_HELP_URL = data.TOOL_HELP_URL
SCRIPTS_PATH = data.SCRIPTS_PATH
ICONS_PATH = data.ICONS_PATH
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
from scripts.config.data import (
TOOL_NAME, TOOL_VERSION, TOOL_AUTHOR, TOOL_LANG,
TOOL_WSCL_NAME, TOOL_HELP_URL, ROOT_PATH, SCRIPTS_PATH,
ICONS_PATH, STYLES_PATH, DNA_FILE_PATH, DNA_IMG_PATH,
PLUGIN_PATH, PYDNA_PATH, DNACALIB_PATH, BUILDER_PATH,
DNALIB_PATH, UI_PATH, UTILS_PATH, TOOL_MAIN_SCRIPT,
TOOL_STYLE_FILE, TOOL_ICON, TOOL_COMMAND_ICON,
TOOL_MOD_FILENAME
)
def get_script_path():
try:
maya_script = mel.eval('getenv("MAYA_SCRIPT_PATH")')
if maya_script:
paths = maya_script.split(os.pathsep)
for path in paths:
install_path = os.path.join(path, "Install.py")
if os.path.exists(install_path):
return os.path.dirname(install_path)
except:
pass
try:
return os.path.dirname(os.path.abspath(__file__))
except:
return os.getcwd()
ROOT_PATH = get_script_path()
if ROOT_PATH not in sys.path:
sys.path.insert(0, ROOT_PATH)
TOOL_MAIN_SCRIPT = data.TOOL_MAIN_SCRIPT
TOOL_MOD_FILENAME = data.TOOL_MOD_FILENAME
TOOL_ICON = data.TOOL_ICON
TOOL_COMMAND_ICON = data.TOOL_COMMAND_ICON
#===================================== 3. Utility Functions =====================================
def maya_main_window():
"""Get Maya main window as QWidget"""
"""获取Maya主窗口"""
main_window_ptr = omui.MQtUtil.mainWindow()
if main_window_ptr:
return wrapInstance(int(main_window_ptr), QtWidgets.QWidget)
return None
def ensure_directory(directory_path):
"""Ensure directory exists, create if not"""
"""确保目录存在"""
if directory_path and isinstance(directory_path, str):
if not os.path.exists(directory_path):
os.makedirs(directory_path)
print(f"Created directory: {directory_path}")
return directory_path
def get_maya_modules_dir():
"""Get Maya modules directory path"""
"""获取Maya模块目录"""
maya_app_dir = cmds.internalVar(userAppDir=True)
if maya_app_dir and isinstance(maya_app_dir, str):
return ensure_directory(os.path.join(maya_app_dir, "modules"))
return None
#===================================== 4. UI Component Classes =====================================
class SetButton(QtWidgets.QPushButton):
@ -71,16 +98,10 @@ class InstallDialog(QtWidgets.QDialog):
self.setup_ui()
def load_stylesheet(self):
"""加载 QSS 样式文件"""
try:
style_file = data.TOOL_STYLE_FILE
if os.path.exists(style_file):
with open(style_file, 'r') as f:
self.setStyleSheet(f.read())
else:
print(f"Warning: Style file not found: {style_file}")
except Exception as e:
print(f"Error loading stylesheet: {e}")
with open(TOOL_STYLE_FILE, 'r', encoding='utf-8') as f:
style = f.read()
self.setStyleSheet(style)
print(f"已加载样式文件: {TOOL_STYLE_FILE}")
def setup_ui(self):
"""Initialize and setup UI components"""
@ -161,7 +182,7 @@ class InstallDialog(QtWidgets.QDialog):
webbrowser.open(TOOL_HELP_URL)
QtWidgets.QApplication.restoreOverrideCursor()
def get_script_path(self):
def get_script_path():
maya_script = mel.eval('getenv("MAYA_SCRIPT_NAME")')
if maya_script and os.path.exists(maya_script):
return os.path.dirname(maya_script)

Binary file not shown.

View File

@ -5,111 +5,49 @@ import os
import sys
import maya.cmds as cmds
import maya.OpenMayaUI as omui
from shiboken2 import wrapInstance
import traceback
from scripts.config import data
QtCore, QtGui, QtWidgets = data.Qt()
#===================================== 2. Global Variables =====================================
import os
import sys
import webbrowser
import maya.mel as mel
import maya.cmds as cmds
import maya.OpenMayaUI as omui
try:
ROOT_PATH = data.ROOT_PATH
except NameError:
ROOT_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))).replace("\\", "/")
TOOL_NAME = data.TOOL_NAME
TOOL_VERSION = data.TOOL_VERSION
TOOL_AUTHOR = data.TOOL_AUTHOR
TOOL_LANG = data.TOOL_LANG
TOOL_WSCL_NAME = data.TOOL_WSCL_NAME
TOOL_HELP_URL = data.TOOL_HELP_URL
SCRIPTS_PATH = data.SCRIPTS_PATH
ICONS_PATH = data.ICONS_PATH
TOOL_MAIN_SCRIPT = data.TOOL_MAIN_SCRIPT
TOOL_MOD_FILENAME = data.TOOL_MOD_FILENAME
TOOL_ICON = data.TOOL_ICON
TOOL_COMMAND_ICON = data.TOOL_COMMAND_ICON
DNA_PATH = data.DNA_PATH
DNA_IMG_PATH = data.DNA_IMG_PATH
DNALIB_PATH = data.DNALIB_PATH
BUILDER_PATH = data.BUILDER_PATH
UI_PATH = data.UI_PATH
UTILS_PATH = data.UTILS_PATH
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
main_window = None
from scripts.config.data import (
TOOL_NAME, TOOL_VERSION, TOOL_AUTHOR, TOOL_LANG,
TOOL_WSCL_NAME, TOOL_HELP_URL, ROOT_PATH, SCRIPTS_PATH,
ICONS_PATH, STYLES_PATH, DNA_FILE_PATH, DNA_IMG_PATH,
PLUGIN_PATH, PYDNA_PATH, DNACALIB_PATH, BUILDER_PATH,
DNALIB_PATH, UI_PATH, UTILS_PATH, TOOL_MAIN_SCRIPT,
TOOL_STYLE_FILE, TOOL_ICON, TOOL_COMMAND_ICON,
TOOL_MOD_FILENAME
)
class ModelTab(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.init_ui()
def init_ui(self):
layout = QtWidgets.QVBoxLayout(self)
# LOD导航栏
self.lod_tabs = QtWidgets.QTabWidget()
for i in range(1, 7):
tab = QtWidgets.QWidget()
self.lod_tabs.addTab(tab, f"LOD{i-1}")
layout.addWidget(self.lod_tabs)
# 模型操作工具栏
tool_bar = QtWidgets.QHBoxLayout()
self.add_button(tool_bar, "导入模型", "import.png", self.import_model)
self.add_button(tool_bar, "导出模型", "export.png", self.export_model)
layout.addLayout(tool_bar)
def add_button(self, layout, text, icon, callback):
btn = QtWidgets.QPushButton(QtGui.QIcon(os.path.join(data.ICONS_PATH, icon)), text)
btn.clicked.connect(callback)
layout.addWidget(btn)
def import_model(self): pass
def export_model(self): pass
class RigTab(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.init_ui()
def init_ui(self):
splitter = QtWidgets.QSplitter(QtCore.Qt.Horizontal)
# 左侧控制器列表
left_panel = QtWidgets.QWidget()
left_layout = QtWidgets.QVBoxLayout(left_panel)
self.controller_list = QtWidgets.QListWidget()
left_layout.addWidget(self.controller_list)
# 右侧视口区域
right_panel = QtWidgets.QWidget()
right_layout = QtWidgets.QVBoxLayout(right_panel)
self.viewport_label = QtWidgets.QLabel("3D视口区域")
right_layout.addWidget(self.viewport_label)
splitter.addWidget(left_panel)
splitter.addWidget(right_panel)
main_layout = QtWidgets.QHBoxLayout(self)
main_layout.addWidget(splitter)
class AdjustTab(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.init_ui()
def init_ui(self):
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(QtWidgets.QLabel("调整功能开发中..."))
class DefineTab(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.init_ui()
def init_ui(self):
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(QtWidgets.QLabel("定义功能开发中..."))
# 导入UI模块
from scripts.ui.menu import MenuManager
from scripts.ui.models import ModelTab
from scripts.ui.rigging import RigTab
from scripts.ui.adjust import AdjustTab
from scripts.ui.define import DefineTab
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
@ -120,21 +58,19 @@ class MainWindow(QtWidgets.QMainWindow):
"""初始化核心组件"""
self.setup_paths()
self.init_ui()
self.setup_connections()
def setup_paths(self):
"""配置系统路径"""
# 添加插件路径
if data.PLUGIN_PATH not in sys.path:
sys.path.insert(0, data.PLUGIN_PATH)
if PLUGIN_PATH not in sys.path:
sys.path.insert(0, PLUGIN_PATH)
# 添加PyDNA二进制路径到环境变量
pydna_bin = os.path.join(data.PYDNA_PATH, "bin")
# 添加PyDNA路径
pydna_bin = os.path.join(PYDNA_PATH, "bin")
os.environ["PATH"] = f"{pydna_bin}{os.pathsep}{os.environ['PATH']}"
def init_ui(self):
"""初始化UI框架"""
self.setWindowTitle(f"{data.TOOL_NAME} {data.TOOL_VERSION}")
self.setWindowTitle(f"{TOOL_NAME} {TOOL_VERSION}")
self.setMinimumSize(1200, 800)
# 主窗口布局
@ -142,9 +78,8 @@ class MainWindow(QtWidgets.QMainWindow):
self.setCentralWidget(main_widget)
main_layout = QtWidgets.QVBoxLayout(main_widget)
# 创建核心组件
self.create_menu_bar()
self.create_toolbar()
# 创建菜单和工具栏
self.menu_manager = MenuManager(self)
self.create_tab_widget()
# 加载样式
@ -152,108 +87,12 @@ class MainWindow(QtWidgets.QMainWindow):
def load_styles(self):
"""加载样式表"""
style_path = os.path.join(data.STYLES_PATH, "style.qss")
if os.path.exists(style_path):
with open(style_path, "r", encoding="utf-8") as f:
if os.path.exists(TOOL_STYLE_FILE):
with open(TOOL_STYLE_FILE, "r", encoding="utf-8") as f:
self.setStyleSheet(f.read())
def create_menu_bar(self):
"""创建菜单栏"""
menubar = self.menuBar()
# 文件菜单
file_menu = menubar.addMenu("文件")
self.add_menu_action(file_menu, "打开DNA", "open.png", lambda: self.load_dna())
self.add_menu_action(file_menu, "保存DNA", "save.png", lambda: self.save_dna())
self.add_menu_action(file_menu, "加载当前项目的DNA", "open.png", self.on_load_project_dna)
self.add_menu_action(file_menu, "修改混合目标名称", "rename.png", self.on_rename_blend_target)
self.add_menu_action(file_menu, "重置混合目标名称", "resetname.png", self.on_reset_blend_target)
self.add_menu_action(file_menu, "导出FBX", "export.png", self.on_export_fbx)
file_menu.addSeparator()
self.add_menu_action(file_menu, "退出", "exit.png", self.close)
# 编辑菜单
edit_menu = menubar.addMenu("编辑")
self.add_menu_action(edit_menu, "创建RL4节点", "connect.png", self.create_rl4_node)
self.add_menu_action(edit_menu, "删除RL4节点", "disconnect.png", self.delete_rl4_node)
self.add_menu_action(edit_menu, "镜像左至右", "mirrorL.png", self.mirror_left_to_right)
self.add_menu_action(edit_menu, "镜像右至左", "mirrorR.png", self.mirror_right_to_left)
self.add_menu_action(edit_menu, "姿势由A型转T型", "pose_A_To_T.png", self.pose_A_to_T)
self.add_menu_action(edit_menu, "姿势由T型转A型", "pose_T_To_A.png", self.pose_T_to_A)
self.add_menu_action(edit_menu, "传输LOD贴图", "locator.png", self.transfer_lod_texture)
self.add_menu_action(edit_menu, "设置关节颜色", "color.png", self.set_joint_color)
self.add_menu_action(edit_menu, "取消全部标记", "unmark_all.png", self.unmark_all)
self.add_menu_action(edit_menu, "重建所有目标", "rebuildTargets.png", self.rebuild_targets)
self.add_menu_action(edit_menu, "为所有表情设置关键帧", "bakeAnimation.png", self.bake_all_animations)
self.add_menu_action(edit_menu, "烘焙所有表情的关键帧", "centerCurrentTime.png", self.bake_all_keyframes)
# 工具菜单
tool_menu = menubar.addMenu("工具")
self.add_menu_action(tool_menu, "导出蒙皮", "export_skin.png", self.export_skin)
self.add_menu_action(tool_menu, "导入蒙皮", "import_skin.png", self.import_skin)
self.add_menu_action(tool_menu, "拷贝装皮", "copy_skin.png", self.copy_skin)
self.add_menu_action(tool_menu, "RBF变形器", "blendShape.png", self.create_blend_shape)
self.add_menu_action(tool_menu, "快速绑定服装", "clothing_weight.png", self.quick_bind_clothing)
self.add_menu_action(tool_menu, "克隆混合变形", "blendShape.png", self.clone_blend_shape)
self.add_menu_action(tool_menu, "UV传递点序", "repair_vertex_order.png", self.repair_vertex_order)
self.add_menu_action(tool_menu, "面部生成控制器", "controller.png", self.create_face_controller)
self.add_menu_action(tool_menu, "提取52BS", "ARKit52.png", self.extract_52BS)
self.add_menu_action(tool_menu, "关节轴向修复", "joint.png", self.repair_joint_axis)
self.add_menu_action(tool_menu, "生成身体控制器", "create_body_ctrl.png", self.create_body_controller)
self.add_menu_action(tool_menu, "导入面部动画", "import_face_anim.png", self.import_face_animation)
self.add_menu_action(tool_menu, "导入身体动画", "import_body_anim.png", self.import_body_animation)
# 语言菜单
lang_menu = menubar.addMenu("语言")
self.add_menu_action(lang_menu, "中文", "chinese.png", self.set_chinese)
self.add_menu_action(lang_menu, "English", "english.png", self.set_english)
# 帮助菜单
help_menu = menubar.addMenu("帮助")
self.add_menu_action(help_menu, "帮助文档", "help.png", self.show_help_document)
self.add_menu_action(help_menu, "关于", "warning.png", self.show_about_dialog)
def add_menu_action(self, menu, text, icon, callback):
"""通用菜单项添加方法"""
action = QtWidgets.QAction(text, self)
if icon:
# 优先使用Maya内置图标
maya_icon = self.get_maya_icon(icon)
if maya_icon:
action.setIcon(maya_icon)
else:
# 使用自定义图标
icon_path = os.path.join(data.ICONS_PATH, icon)
if os.path.exists(icon_path):
action.setIcon(QtGui.QIcon(icon_path))
else:
cmds.warning(f"图标文件不存在: {icon_path}")
action.triggered.connect(callback)
menu.addAction(action)
def get_maya_icon(self, icon_name):
"""获取Maya内置图标"""
maya_icons = {
"bakeAnimation.png": "BakeSimulation.png",
"centerCurrentTime.png": "timeCurrentFrame.png",
"export_skin.png": "kinReroot.png",
# 添加更多图标映射...
}
if icon_name in maya_icons:
return QtGui.QIcon(f":/{maya_icons[icon_name]}")
return None
def create_toolbar(self):
"""创建工具栏"""
toolbar = self.addToolBar("主工具栏")
toolbar.setMovable(False)
# 添加工具栏按钮
toolbar.addAction(QtGui.QIcon(os.path.join(ICONS_PATH, "save.png")), "保存DNA")
toolbar.addAction(QtGui.QIcon(os.path.join(ICONS_PATH, "open.png")), "加载当前项目DNA")
def create_tab_widget(self):
"""创建标签页"""
"""创建标签页"""
self.tab_widget = QtWidgets.QTabWidget()
self.centralWidget().layout().addWidget(self.tab_widget)
@ -269,304 +108,67 @@ class MainWindow(QtWidgets.QMainWindow):
self.tab_widget.addTab(self.adjust_tab, "调整")
self.tab_widget.addTab(self.define_tab, "定义")
def setup_connections(self):
"""建立信号连接"""
# 示例:连接标签页切换信号
self.tab_widget.currentChanged.connect(self.on_tab_changed)
def on_tab_changed(self, index):
"""标签页切换回调"""
current_tab = self.tab_widget.widget(index)
print(f"切换到标签页: {current_tab.objectName()}")
def setup_model_tab(self):
"""设置模型标签页内容"""
layout = QtWidgets.QVBoxLayout(self.model_tab)
# 创建 LOD 分栏
self.lod_tabs = QtWidgets.QTabWidget()
layout.addWidget(self.lod_tabs)
# 添加 LOD 分页
for i in range(8):
lod_tab = QtWidgets.QWidget()
lod_layout = QtWidgets.QVBoxLayout(lod_tab)
# 添加删除按钮
delete_btn = QtWidgets.QPushButton(QtGui.QIcon(os.path.join(ICONS_PATH, "delete.png")), "删除")
lod_layout.addWidget(delete_btn)
# 添加模型输入框和加载按钮
for part in ["头部", "牙齿", "牙龈", "左眼", "右眼", "虹膜", "睫毛", "眼睑", "软骨", "身体"]:
part_layout = QtWidgets.QHBoxLayout()
part_input = QtWidgets.QLineEdit()
load_btn = QtWidgets.QPushButton(QtGui.QIcon(os.path.join(ICONS_PATH, "target.png")), "加载")
part_layout.addWidget(part_input)
part_layout.addWidget(load_btn)
lod_layout.addLayout(part_layout)
self.lod_tabs.addTab(lod_tab, f"LOD{i}")
# 添加 LOD 功能按钮
lod_func_layout = QtWidgets.QHBoxLayout()
lod_func_layout.addWidget(QtWidgets.QPushButton(QtGui.QIcon(os.path.join(ICONS_PATH, "load_meshes.png")), "自定加载模型"))
lod_func_layout.addWidget(QtWidgets.QPushButton(QtGui.QIcon(os.path.join(ICONS_PATH, "standardized_naming.png")), "标准化命名"))
lod_func_layout.addWidget(QtWidgets.QPushButton(QtGui.QIcon(os.path.join(ICONS_PATH, "automatic_groupingg.png")), "自动分组"))
layout.addLayout(lod_func_layout)
# 添加模型工具
model_tool_layout = QtWidgets.QHBoxLayout()
model_tool_layout.addWidget(QtWidgets.QComboBox()) # 拓扑结构下拉菜单
model_tool_layout.addWidget(QtWidgets.QComboBox()) # 选择LOD下拉菜单
model_tool_layout.addWidget(QtWidgets.QPushButton(QtGui.QIcon(os.path.join(ICONS_PATH, "polySplitVertex.png")), "模型分离"))
model_tool_layout.addWidget(QtWidgets.QPushButton(QtGui.QIcon(os.path.join(ICONS_PATH, "supplement_meshes.png")), "生成面部配件"))
model_tool_layout.addWidget(QtWidgets.QPushButton(QtGui.QIcon(os.path.join(ICONS_PATH, "repair_normals.png")), "修复法线"))
model_tool_layout.addWidget(QtWidgets.QPushButton(QtGui.QIcon(os.path.join(ICONS_PATH, "repair_vertex_order.png")), "修复点序"))
model_tool_layout.addWidget(QtWidgets.QPushButton(QtGui.QIcon(os.path.join(ICONS_PATH, "polyChipOff.png")), "修复接缝"))
layout.addLayout(model_tool_layout)
def setup_rig_tab(self):
"""设置绑定标签页内容"""
layout = QtWidgets.QVBoxLayout(self.rig_tab)
# 添加 DNA 浏览器
self.dna_browser = QtWidgets.QListWidget()
self.dna_browser.setIconSize(QtCore.QSize(64, 64))
layout.addWidget(self.dna_browser)
# 添加 DNA 图标缩放滑块
self.dna_scale_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
self.dna_scale_slider.setMinimum(50)
self.dna_scale_slider.setMaximum(200)
self.dna_scale_slider.setValue(100)
self.dna_scale_slider.valueChanged.connect(self.on_dna_scale_changed)
layout.addWidget(self.dna_scale_slider)
# 添加导入/导出按钮
button_layout = QtWidgets.QHBoxLayout()
import_btn = QtWidgets.QPushButton("导入设置")
export_btn = QtWidgets.QPushButton("导出设置")
import_btn.clicked.connect(self.on_import_settings)
export_btn.clicked.connect(self.on_export_settings)
button_layout.addWidget(import_btn)
button_layout.addWidget(export_btn)
layout.addLayout(button_layout)
# 初始化 DNA 浏览器
self.init_dna_browser()
def init_dna_browser(self):
"""初始化 DNA 浏览器"""
# 清空现有内容
self.dna_browser.clear()
# 获取 DNA 文件列表
dna_files = os.listdir(DNA_PATH)
img_files = os.listdir(DNA_IMG_PATH)
# 添加 DNA 项目
for dna_file in dna_files:
item = QtWidgets.QListWidgetItem(dna_file)
# 查找对应的图片
img_name = os.path.splitext(dna_file)[0] + ".png"
if img_name in img_files:
item.setIcon(QtGui.QIcon(os.path.join(data.DNA_IMG_PATH, img_name)))
self.dna_browser.addItem(item)
def on_dna_scale_changed(self, value):
"""处理 DNA 图标缩放"""
size = int(64 * (value / 100))
self.dna_browser.setIconSize(QtCore.QSize(size, size))
def on_import_settings(self):
pass
def on_export_settings(self):
"""导出设置"""
# TODO: 实现导出设置功能
pass
def setup_adjust_tab(self):
"""设置调整标签页内容"""
layout = QtWidgets.QVBoxLayout(self.adjust_tab)
# 添加BlendShape列表
blend_list = QtWidgets.QListWidget()
layout.addWidget(blend_list)
# 添加滑块组
slider_layout = QtWidgets.QVBoxLayout()
for i in range(5):
slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
slider_layout.addWidget(slider)
layout.addLayout(slider_layout)
# 添加按钮组
button_layout = QtWidgets.QHBoxLayout()
edit_btn = QtWidgets.QPushButton("编辑BlendShape")
save_btn = QtWidgets.QPushButton("保存设置")
button_layout.addWidget(edit_btn)
button_layout.addWidget(save_btn)
layout.addLayout(button_layout)
def setup_define_tab(self):
"""设置定义标签页内容"""
layout = QtWidgets.QVBoxLayout(self.define_tab)
# 添加DNA编辑区域
self.dna_edit = QtWidgets.QTextEdit()
layout.addWidget(self.dna_edit)
# 添加按钮组
button_layout = QtWidgets.QHBoxLayout()
self.load_btn = QtWidgets.QPushButton("载入DNA")
self.save_btn = QtWidgets.QPushButton("保存DNA")
self.export_btn = QtWidgets.QPushButton("导出FBX")
# 连接信号槽
self.load_btn.clicked.connect(self.on_load_dna)
self.save_btn.clicked.connect(self.on_save_dna)
self.export_btn.clicked.connect(self.on_export_fbx)
button_layout.addWidget(self.load_btn)
button_layout.addWidget(self.save_btn)
button_layout.addWidget(self.export_btn)
layout.addLayout(button_layout)
# 创建DNA管理器
self.dna_manager = DNAManager()
def on_load_dna(self): pass
def on_save_dna(self): pass
def on_export_fbx(self): pass
def on_open_dna(self): pass
def on_load_project_dna(self): pass
def on_rename_blend_target(self): pass
def on_reset_blend_target(self): pass
def create_rl4_node(self): pass
def delete_rl4_node(self): pass
def mirror_left_to_right(self): pass
def mirror_right_to_left(self): pass
def pose_A_to_T(self): pass
def pose_T_to_A(self): pass
def transfer_lod_texture(self): pass
def set_joint_color(self): pass
def unmark_all(self): pass
def rebuild_targets(self): pass
def bake_all_animations(self): pass
def bake_all_keyframes(self): pass
def safe_shutdown(self): pass
def export_skin(self): pass
def import_skin(self): pass
def copy_skin(self): pass
def create_blend_shape(self): pass
def quick_bind_clothing(self): pass
def clone_blend_shape(self): pass
def repair_vertex_order(self): pass
def create_face_controller(self): pass
def extract_52BS(self): pass
def repair_joint_axis(self): pass
def create_body_controller(self): pass
def import_face_animation(self): pass
def import_body_animation(self): pass
# 添加语言支持方法
def set_chinese(self):
"""设置中文界面"""
print("切换中文界面(待实现)")
# TODO: 实现国际化切换
def set_english(self):
"""设置英文界面"""
print("切换英文界面(待实现)")
# TODO: 实现国际化切换
# 添加帮助相关方法
def show_help_document(self):
"""显示帮助文档"""
print("打开帮助文档(待实现)")
# TODO: 实现帮助文档打开逻辑
def show_about_dialog(self):
"""显示关于对话框"""
print("显示关于信息(待实现)")
# TODO: 实现关于对话框
# ===================================== 显示主窗口 =====================================
def get_maya_window():
"""获取 Maya 主窗口"""
import maya.OpenMayaUI as omui
from shiboken2 import wrapInstance
"""获取Maya主窗口"""
maya_main_window_ptr = omui.MQtUtil.mainWindow()
return wrapInstance(int(maya_main_window_ptr), QtWidgets.QWidget)
if maya_main_window_ptr is not None:
# 确保指针转换为整数
ptr = int(maya_main_window_ptr)
return wrapInstance(ptr, QtWidgets.QWidget)
return None
def dock_to_maya():
"""将窗口嵌入到 Maya 的 Dock 面板"""
"""嵌入Maya Dock面板"""
global main_window
try:
# 先清理可能存在的旧控件
if cmds.workspaceControl(TOOL_WSCL_NAME, exists=True):
cmds.deleteUI(TOOL_WSCL_NAME)
# 创建新的Dock控件
dock_control = cmds.workspaceControl(
TOOL_WSCL_NAME,
label=TOOL_NAME,
tabToControl=["AttributeEditor", -1],
initialWidth=1400,
minimumWidth=1000,
minimumHeight=800,
widthProperty="free",
heightProperty="free"
minimumHeight=800
)
# 获取Dock控件指针
maya_dock_ptr = omui.MQtUtil.findControl(dock_control)
if not maya_dock_ptr:
raise RuntimeError("无法获取Dock控件指针")
# 获取Maya主窗口并创建实例
maya_main_window = get_maya_window()
main_window = MainWindow(parent=maya_main_window)
# 嵌入到Dock
# 确保指针转换为整数
maya_dock_widget = wrapInstance(int(maya_dock_ptr), QtWidgets.QWidget)
maya_dock_widget.layout().addWidget(main_window)
return main_window
except Exception as e:
error_msg = f"Dock嵌入失败: {str(e)}\n{''.join(traceback.format_exc())}"
cmds.warning(error_msg)
cmds.warning(f"Dock嵌入失败: {str(e)}")
return None
def show():
"""显示主窗口"""
global main_window
try:
# 清理旧实例
if main_window:
main_window.close()
cmds.deleteUI(TOOL_WSCL_NAME)
except:
pass
# 尝试嵌入 Dock
main_window = dock_to_maya()
# 备用方案:独立窗口模式
if not main_window:
cmds.warning("Dock 模式失败,使用独立窗口模式")
cmds.warning("使用独立窗口模式")
main_window = MainWindow()
main_window.show()
return main_window
# ===================================== 主函数 =====================================
if __name__ == "__main__":
app = QtWidgets.QApplication([])
window = show()
app.exec_()
show()

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -6,72 +6,110 @@ import sys
import maya.cmds as cmds
# Base Information
TOOL_NAME = "MetaFusion"
TOOL_VERSION = "Beta v1.0.0"
TOOL_AUTHOR = "CGNICO"
TOOL_LANG = 'en_US'
TOOL_WSCL_NAME = f"{TOOL_NAME}WorkSpaceControl"
TOOL_HELP_URL = f"https://gitea.cgnico.com/CGNICO/{TOOL_NAME}/wiki"
TOOL_NAME = str("MetaFusion")
TOOL_VERSION = str("Beta v1.0.0")
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")
# BASE_PATH
ROOT_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))).replace("\\", "/")
SCRIPTS_PATH = os.path.join(ROOT_PATH, "scripts").replace("\\", "/")
ICONS_PATH = os.path.join(ROOT_PATH, "resources", "icons").replace("\\", "/")
STYLES_PATH = os.path.join(ROOT_PATH, "resources", "styles").replace("\\", "/")
DNA_FILE_PATH = os.path.join(ROOT_PATH, "resources", "dna").replace("\\", "/")
DNA_IMG_PATH = os.path.join(ROOT_PATH, "resources", "img").replace("\\", "/")
ROOT_PATH = str(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))).replace("\\", "/"))
SCRIPTS_PATH = str(os.path.join(ROOT_PATH, "scripts").replace("\\", "/"))
# PYDNA_PATH & PLUGIN_PATH
SYSTEM_OS = "Windows" if cmds.about(os=True).lower().startswith("win") else "Linux"
MAYA_VERSION = int(cmds.about(version=True).split('.')[0])
PYTHON_VERSION = sys.version.replace(".", "")
PYTHON_VERSION_DIR_MAPPING = {"3108": "python3108", "311": "python311", "397": "python397"}
PYTHON_VERSION_DIR = PYTHON_VERSION_DIR_MAPPING.get(PYTHON_VERSION, "python3")
PLUGIN_PATH = os.path.join(ROOT_PATH, "plugins", SYSTEM_OS, MAYA_VERSION).replace("\\", "/")
PYDNA_PATH = os.path.join(ROOT_PATH, "plugins", SYSTEM_OS, "pydna", PYTHON_VERSION_DIR).replace("\\", "/")
# 资源路径
RESOURCES_PATH = str(os.path.join(ROOT_PATH, "resources").replace("\\", "/"))
ICONS_PATH = str(os.path.join(RESOURCES_PATH, "icons").replace("\\", "/"))
STYLES_PATH = str(os.path.join(RESOURCES_PATH, "styles").replace("\\", "/"))
DNA_FILE_PATH = str(os.path.join(RESOURCES_PATH, "dna").replace("\\", "/"))
DNA_IMG_PATH = str(os.path.join(RESOURCES_PATH, "img").replace("\\", "/"))
# SYSTEM_INFO
SYSTEM_OS = str("Windows" if cmds.about(os=True).lower().startswith("win") else "Linux")
MAYA_VERSION = str(int(cmds.about(version=True).split('.')[0]))
# PYTHON_VERSION
PYTHON_VERSION = str(sys.version.replace(".", ""))
major_version = int(PYTHON_VERSION[0])
minor_version = int(PYTHON_VERSION[1:3]) if len(PYTHON_VERSION) > 1 else None
version_tuple = (major_version,) if minor_version is None else (major_version, minor_version)
# 版本映射表
PYTHON_VERSION_MAP = {
(3,): "python3", # 所有Python3主版本
(3, 9): "python397", # 3.9.x → python397
(3, 10): "python3108", # 3.10.x → python3108
(3, 11): "python311" # 3.11.x → python311
}
# 获取Python版本目录
PYTHON_VERSION_DIR = str(PYTHON_VERSION_MAP.get(version_tuple, "python3"))
# PATHS
PLUGIN_PATH = str(os.path.join(ROOT_PATH, "plugins", SYSTEM_OS, MAYA_VERSION).replace("\\", "/"))
PYDNA_PATH = str(os.path.join(ROOT_PATH, "plugins", SYSTEM_OS, "pydna", PYTHON_VERSION_DIR).replace("\\", "/"))
# TOOLS_PATH
DNACALIB_PATH = os.path.join(ROOT_PATH, "dnacalib").replace("\\", "/")
BUILDER_PATH = os.path.join(SCRIPTS_PATH, "builder").replace("\\", "/")
DNALIB_PATH = os.path.join(SCRIPTS_PATH, "dnalib").replace("\\", "/")
UI_PATH = os.path.join(SCRIPTS_PATH, "ui").replace("\\", "/")
UTILS_PATH = os.path.join(SCRIPTS_PATH, "utils").replace("\\", "/")
DNACALIB_PATH = str(os.path.join(ROOT_PATH, "dnacalib").replace("\\", "/"))
BUILDER_PATH = str(os.path.join(SCRIPTS_PATH, "builder").replace("\\", "/"))
DNALIB_PATH = str(os.path.join(SCRIPTS_PATH, "dnalib").replace("\\", "/"))
UI_PATH = str(os.path.join(SCRIPTS_PATH, "ui").replace("\\", "/"))
UTILS_PATH = str(os.path.join(SCRIPTS_PATH, "utils").replace("\\", "/"))
#FILES
TOOL_MAIN_SCRIPT = os.path.join(SCRIPTS_PATH, f"{TOOL_NAME}.py").replace("\\", "/")
TOOL_STYLE_FILE = os.path.join(STYLES_PATH, "style.qss").replace("\\", "/")
TOOL_ICON = os.path.join(ICONS_PATH, f"{TOOL_NAME}Logo.png").replace("\\", "/")
TOOL_COMMAND_ICON = os.path.join(ICONS_PATH, "CommandButton.png").replace("\\", "/")
TOOL_MOD_FILENAME = f"{TOOL_NAME}.mod"
# FILES
TOOL_MAIN_SCRIPT = str(os.path.join(SCRIPTS_PATH, f"{TOOL_NAME}.py").replace("\\", "/"))
TOOL_STYLE_FILE = str(os.path.join(UI_PATH, "style.qss").replace("\\", "/"))
TOOL_ICON = str(os.path.join(ICONS_PATH, f"{TOOL_NAME}Logo.png").replace("\\", "/"))
TOOL_COMMAND_ICON = str(os.path.join(ICONS_PATH, "CommandButton.png").replace("\\", "/"))
TOOL_MOD_FILENAME = str(f"{TOOL_NAME}.mod")
if __name__ == "__main__":
validate_paths = {
ROOT_PATH,
SCRIPTS_PATH,
ICONS_PATH,
STYLES_PATH,
DNA_FILE_PATH,
DNA_IMG_PATH,
PLUGIN_PATH,
PYDNA_PATH,
DNACALIB_PATH,
BUILDER_PATH,
DNALIB_PATH,
UI_PATH,
UTILS_PATH
}
for i in validate_paths:
if not i in sys.path:
sys.path.append(i)
print("TOOL_NAME",TOOL_NAME)
print("TOOL_VERSION",TOOL_VERSION)
print("TOOL_AUTHOR",TOOL_AUTHOR)
print("TOOL_LANG",TOOL_LANG)
print("TOOL_WSCL_NAME",TOOL_WSCL_NAME)
print("TOOL_HELP_URL",TOOL_HELP_URL)
print("============================================")
print(f"TOOL_NAME: {TOOL_NAME}")
print(f"TOOL_VERSION: {TOOL_VERSION}")
print(f"TOOL_AUTHOR: {TOOL_AUTHOR}")
print(f"TOOL_LANG: {TOOL_LANG}")
print(f"TOOL_WSCL_NAME: {TOOL_WSCL_NAME}")
print(f"TOOL_HELP_URL: {TOOL_HELP_URL}")
print("ROOT_PATH",ROOT_PATH)
print("SCRIPTS_PATH",SCRIPTS_PATH)
print("ICONS_PATH",ICONS_PATH)
print("STYLES_PATH",STYLES_PATH)
print("DNA_FILE_PATH",DNA_FILE_PATH)
print("DNA_IMG_PATH",DNA_IMG_PATH)
print(f"ROOT_PATH: {ROOT_PATH}")
print(f"SCRIPTS_PATH: {SCRIPTS_PATH}")
print(f"ICONS_PATH: {ICONS_PATH}")
print(f"STYLES_PATH: {STYLES_PATH}")
print(f"DNA_FILE_PATH: {DNA_FILE_PATH}")
print(f"DNA_IMG_PATH: {DNA_IMG_PATH}")
print("PLUGIN_PATH",PLUGIN_PATH)
print("PYDNA_PATH",PYDNA_PATH)
print(f"PLUGIN_PATH: {PLUGIN_PATH}")
print(f"PYDNA_PATH: {PYDNA_PATH}")
print("DNACALIB_PATH",DNACALIB_PATH)
print("BUILDER_PATH",BUILDER_PATH)
print("DNALIB_PATH",DNALIB_PATH)
print("UI_PATH",UI_PATH)
print("UTILS_PATH",UTILS_PATH)
print(f"DNACALIB_PATH: {DNACALIB_PATH}")
print(f"BUILDER_PATH: {BUILDER_PATH}")
print(f"DNALIB_PATH: {DNALIB_PATH}")
print(f"UI_PATH: {UI_PATH}")
print(f"UTILS_PATH: {UTILS_PATH}")
print("TOOL_MAIN_SCRIPT",TOOL_MAIN_SCRIPT)
print("TOOL_STYLE_FILE",TOOL_STYLE_FILE)
print("TOOL_ICON",TOOL_ICON)
print("TOOL_COMMAND_ICON",TOOL_COMMAND_ICON)
print("TOOL_MOD_FILENAME",TOOL_MOD_FILENAME)
print(f"TOOL_MAIN_SCRIPT: {TOOL_MAIN_SCRIPT}")
print(f"TOOL_STYLE_FILE: {TOOL_STYLE_FILE}")
print(f"TOOL_ICON: {TOOL_ICON}")
print(f"TOOL_COMMAND_ICON: {TOOL_COMMAND_ICON}")
print(f"TOOL_MOD_FILENAME: {TOOL_MOD_FILENAME}")

View File

@ -1,60 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
根据Maya版本加载对应的Qt库的对应模块
"""
import os
import sys
import maya.cmds as cmds
def Qt():
try:
from PySide import QtCore, QtGui, QtWidgets
return QtCore, QtGui, QtWidgets
except ImportError as e:
try:
from PySide2 import QtCore, QtGui, QtWidgets
QtWidgets = QtGui
return QtCore, QtGui, QtWidgets
except ImportError as e:
try:
from PySide6 import QtCore, QtGui, QtWidgets
return QtCore, QtGui, QtWidgets
except ImportError as e:
return None, None, None
QtCore, QtGui, QtWidgets = Qt()
if QtCore is None:
cmds.warning("QtCore加载失败")
if QtGui is None:
cmds.warning("QtGui加载失败")
if QtWidgets is None:
cmds.warning("QtWidgets加载失败")
def get_wrapInstance():
try:
from shiboken import wrapInstance
print("从shiboken加载wrapInstance")
return wrapInstance
except ImportError as e:
cmds.warning(f"shiboken加载失败: {str(e)}")
try:
from shiboken2 import wrapInstance
print("从shiboken2加载wrapInstance")
return wrapInstance
except ImportError as e:
cmds.warning(f"shiboken2加载失败: {str(e)}")
try:
from shiboken6 import wrapInstance
print("从shiboken6加载wrapInstance")
return wrapInstance
except ImportError as e:
cmds.warning(f"shiboken6加载失败: {str(e)}")
return None
wrapInstance = get_wrapInstance()
if wrapInstance is None:
cmds.warning("wrapInstance加载失败")

182
scripts/ui/adjust.py Normal file
View File

@ -0,0 +1,182 @@
import os
from scripts.config import data
from scripts.ui.widgets import (
BaseWidget, BlendShapeList, BlendShapeControls,
BlendShapeTools, IconButton, SliderWithValue
)
QtCore, QtGui, QtWidgets = data.Qt()
class AdjustTab(BaseWidget):
"""调整标签页"""
def __init__(self, parent=None):
super(AdjustTab, self).__init__(parent)
def setup_ui(self):
layout = QtWidgets.QVBoxLayout(self)
# 创建分割器
splitter = QtWidgets.QSplitter(QtCore.Qt.Vertical)
layout.addWidget(splitter)
# 上部分 - 主要BlendShape列表
top_widget = QtWidgets.QWidget()
top_layout = QtWidgets.QVBoxLayout(top_widget)
# RowControl BlendShape列表
self.main_bs_list = BlendShapeList("RowControl")
top_layout.addWidget(self.main_bs_list)
# BlendShape控制
self.bs_controls = BlendShapeControls()
top_layout.addWidget(self.bs_controls)
splitter.addWidget(top_widget)
# 下部分 - 相关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)
# 数值控制
value_layout = QtWidgets.QHBoxLayout()
self.expr_slider = SliderWithValue(min_val=0.0, max_val=1.0, default=0.0)
value_layout.addWidget(self.expr_slider)
self.expr_all_check = QtWidgets.QCheckBox("全部")
value_layout.addWidget(self.expr_all_check)
layout.addLayout(value_layout)
# 表情控制按钮
expr_layout = QtWidgets.QHBoxLayout()
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)
]
for text, icon, callback in expr_btns:
btn = IconButton(icon, text)
btn.clicked.connect(callback)
expr_layout.addWidget(btn)
layout.addLayout(expr_layout)
return group
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])
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])
def on_bs_value_changed(self, value):
"""BlendShape权重变化"""
from scripts.utils import adjust_utils
adjust_utils.set_bs_value(value)
def on_expr_value_changed(self, value):
"""表情权重变化"""
from scripts.utils import adjust_utils
adjust_utils.set_expr_value(value)
# 表情控制回调
def reset_expression(self):
"""还原默认表情"""
from scripts.utils import adjust_utils
adjust_utils.reset_expression()
def select_expression(self):
"""选择表情"""
from scripts.utils import adjust_utils
adjust_utils.select_expression()
def write_expression(self):
"""写入当前表情"""
from scripts.utils import adjust_utils
adjust_utils.write_expression()
def find_controller(self):
"""查找控制器"""
from scripts.utils import adjust_utils
adjust_utils.find_controller()
def select_joints(self):
"""选择关联关节"""
from scripts.utils import adjust_utils
adjust_utils.select_joints()
def write_mirror_expression(self):
"""写入镜像表情"""
from scripts.utils import adjust_utils
adjust_utils.write_mirror_expression()

171
scripts/ui/define.py Normal file
View File

@ -0,0 +1,171 @@
import os
from scripts.config import data
from scripts.ui.widgets import (
BaseWidget, IconButton, SearchLineEdit
)
QtCore, QtGui, QtWidgets = data.Qt()
class DefineTab(BaseWidget):
"""定义标签页"""
def __init__(self, parent=None):
super(DefineTab, self).__init__(parent)
def setup_ui(self):
layout = QtWidgets.QVBoxLayout(self)
# 创建滚动区域
scroll = QtWidgets.QScrollArea()
scroll.setWidgetResizable(True)
scroll.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
layout.addWidget(scroll)
# 创建内容控件
content = QtWidgets.QWidget()
content_layout = QtWidgets.QVBoxLayout(content)
scroll.setWidget(content)
# DNA定义部分
dna_group = self.create_dna_definition()
content_layout.addWidget(dna_group)
# 骨骼定义部分
joint_group = self.create_joint_definition()
content_layout.addWidget(joint_group)
# BlendShape定义部分
bs_group = self.create_blendshape_definition()
content_layout.addWidget(bs_group)
content_layout.addStretch()
def create_dna_definition(self):
"""创建DNA定义组"""
group = QtWidgets.QGroupBox("DNA定义")
layout = QtWidgets.QVBoxLayout(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)
browse_btn = IconButton("target.png", "浏览DNA文件")
browse_btn.clicked.connect(self.browse_dna_file)
file_layout.addWidget(browse_btn)
layout.addLayout(file_layout)
# 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)
layout.addWidget(preview_group)
return group
def create_joint_definition(self):
"""创建骨骼定义组"""
group = QtWidgets.QGroupBox("骨骼定义")
layout = QtWidgets.QVBoxLayout(group)
# 骨骼列表
self.joint_list = QtWidgets.QTreeWidget()
self.joint_list.setHeaderLabels(["骨骼名称", "位置", "旋转", "缩放"])
layout.addWidget(self.joint_list)
# 骨骼工具栏
tools_layout = QtWidgets.QHBoxLayout()
# 添加骨骼
add_btn = IconButton("joint.png", "添加骨骼")
add_btn.clicked.connect(self.add_joint)
tools_layout.addWidget(add_btn)
# 删除骨骼
del_btn = IconButton("delete.png", "删除骨骼")
del_btn.clicked.connect(self.delete_joint)
tools_layout.addWidget(del_btn)
# 修改骨骼
mod_btn = IconButton("modify.png", "修改骨骼")
mod_btn.clicked.connect(self.modify_joint)
tools_layout.addWidget(mod_btn)
tools_layout.addStretch()
layout.addLayout(tools_layout)
return group
def create_blendshape_definition(self):
"""创建BlendShape定义组"""
group = QtWidgets.QGroupBox("BlendShape定义")
layout = QtWidgets.QVBoxLayout(group)
# BlendShape列表
self.bs_list = QtWidgets.QTreeWidget()
self.bs_list.setHeaderLabels(["名称", "目标", "权重"])
layout.addWidget(self.bs_list)
# BlendShape工具栏
tools_layout = QtWidgets.QHBoxLayout()
# 添加BlendShape
add_btn = IconButton("blendShape.png", "添加BlendShape")
add_btn.clicked.connect(self.add_blendshape)
tools_layout.addWidget(add_btn)
# 删除BlendShape
del_btn = IconButton("delete.png", "删除BlendShape")
del_btn.clicked.connect(self.delete_blendshape)
tools_layout.addWidget(del_btn)
# 修改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()

108
scripts/ui/menu.py Normal file
View File

@ -0,0 +1,108 @@
import os
from scripts.config import data
from scripts.utils import menu_utils
QtCore, QtGui, QtWidgets = data.Qt()
class MenuManager:
"""菜单管理器"""
def __init__(self, parent):
self.parent = parent
self.menu_bar = parent.menuBar()
self.create_menus()
self.create_toolbar()
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)
# 编辑菜单
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):
"""创建工具栏"""
toolbar = self.parent.addToolBar("主工具栏")
toolbar.setMovable(False)
# 添加工具栏按钮
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)
def add_menu_item(self, menu, text, icon, callback):
"""添加菜单项"""
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):
"""添加工具栏项"""
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

151
scripts/ui/models.py Normal file
View File

@ -0,0 +1,151 @@
import os
from scripts.config import data
from scripts.ui.widgets import (
BaseWidget, LODGroup, IconButton, SearchLineEdit
)
QtCore, QtGui, QtWidgets = data.Qt()
class ModelTab(BaseWidget):
"""模型标签页"""
def __init__(self, parent=None):
super(ModelTab, self).__init__(parent)
def setup_ui(self):
layout = QtWidgets.QVBoxLayout(self)
# 创建滚动区域
scroll = QtWidgets.QScrollArea()
scroll.setWidgetResizable(True)
scroll.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
layout.addWidget(scroll)
# 创建内容控件
content = QtWidgets.QWidget()
content_layout = QtWidgets.QVBoxLayout(content)
scroll.setWidget(content)
# 添加LOD组
for i in range(8):
lod_group = LODGroup(i)
content_layout.addWidget(lod_group)
# 添加LOD功能组
lod_tools = self.create_lod_tools()
content_layout.addWidget(lod_tools)
# 添加模型工具组
model_tools = self.create_model_tools()
content_layout.addWidget(model_tools)
content_layout.addStretch()
def create_lod_tools(self):
"""创建LOD功能组"""
group = QtWidgets.QGroupBox("LOD功能")
layout = QtWidgets.QHBoxLayout(group)
# 自定加载模型
load_btn = IconButton("load_meshes.png", "自定加载模型")
load_btn.clicked.connect(self.load_custom_models)
layout.addWidget(load_btn)
# 标准化命名
name_btn = IconButton("standardized_naming.png", "标准化命名")
name_btn.clicked.connect(self.standardize_naming)
layout.addWidget(name_btn)
# 自动分组
group_btn = IconButton("automatic_grouping.png", "自动分组")
group_btn.clicked.connect(self.auto_group)
layout.addWidget(group_btn)
layout.addStretch()
return group
def create_model_tools(self):
"""创建模型工具组"""
group = QtWidgets.QGroupBox("模型工具")
layout = QtWidgets.QVBoxLayout(group)
# 拓扑和LOD选择
options = QtWidgets.QHBoxLayout()
# 拓扑结构选择
options.addWidget(QtWidgets.QLabel("拓扑结构:"))
topo_combo = QtWidgets.QComboBox()
topo_combo.addItem("MetaHuman")
options.addWidget(topo_combo)
# LOD选择
options.addWidget(QtWidgets.QLabel("选择LOD:"))
lod_combo = QtWidgets.QComboBox()
lod_combo.addItem("全部")
for i in range(8):
lod_combo.addItem(f"LOD{i}")
options.addWidget(lod_combo)
options.addStretch()
layout.addLayout(options)
# 工具按钮
tools = QtWidgets.QHBoxLayout()
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)
]
for text, icon, callback in tool_buttons:
btn = IconButton(icon, text)
btn.clicked.connect(callback)
tools.addWidget(btn)
tools.addStretch()
layout.addLayout(tools)
return group
# 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()

203
scripts/ui/rigging.py Normal file
View File

@ -0,0 +1,203 @@
import os
import sys
import maya.cmds as cmds
from scripts.ui.widgets import (
BaseWidget, DNABrowser, DescriptionWidget, IconButton, SearchLineEdit
)
try:
from PySide import QtCore, QtGui, QtWidgets
print(f"从PySide加载Qt")
except ImportError as e:
try:
from PySide2 import QtCore, QtGui, QtWidgets
print(f"从PySide2加载Qt")
except ImportError as e:
try:
from PySide6 import QtCore, QtGui, QtWidgets
print(f"从PySide6加载Qt")
except ImportError as e:
print(f"PySide6加载失败: {str(e)}")
try:
from shiboken import wrapInstance
print(f"从shiboken加载wrapInstance")
except ImportError as e:
cmds.warning(f"shiboken加载失败: {str(e)}")
try:
from shiboken2 import wrapInstance
print(f"从shiboken2加载wrapInstance")
except ImportError as e:
cmds.warning(f"shiboken2加载失败: {str(e)}")
try:
from shiboken6 import wrapInstance
print(f"从shiboken6加载wrapInstance")
except ImportError as e:
cmds.warning(f"shiboken6加载失败: {str(e)}")
class RigTab(BaseWidget):
"""绑定标签页"""
def __init__(self, parent=None):
super(RigTab, self).__init__(parent)
def setup_ui(self):
layout = QtWidgets.QVBoxLayout(self)
# 创建滚动区域
scroll = QtWidgets.QScrollArea()
scroll.setWidgetResizable(True)
scroll.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
layout.addWidget(scroll)
# 创建内容控件
content = QtWidgets.QWidget()
content_layout = QtWidgets.QVBoxLayout(content)
scroll.setWidget(content)
# DNA部分
dna_group = self.create_dna_group()
content_layout.addWidget(dna_group)
# 资产部分
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)
# 导入导出按钮
btn_layout = QtWidgets.QHBoxLayout()
export_btn = IconButton("export.png", "导出设置")
export_btn.clicked.connect(self.export_settings)
btn_layout.addWidget(export_btn)
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)
# 项目路径
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)
# 预设文件
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)
# 数据分层
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)
return group
def create_skeleton_tools(self):
"""创建骨架工具"""
group = QtWidgets.QGroupBox("骨架工具")
layout = QtWidgets.QHBoxLayout(group)
# 清空选项
clear_btn = IconButton("delete.png", "清空选项")
clear_btn.clicked.connect(self.clear_options)
layout.addWidget(clear_btn)
# 导入骨架
import_btn = IconButton("HIKCharacterToolSkeleton.png", "导入骨架")
import_btn.clicked.connect(self.import_skeleton)
layout.addWidget(import_btn)
# 创建骨架
create_btn = IconButton("HIKcreateControlRig.png", "创建骨架")
create_btn.clicked.connect(self.create_skeleton)
layout.addWidget(create_btn)
layout.addStretch()
return group
# DNA功能回调
def on_dna_selected(self, dna_path):
"""DNA文件选中"""
from scripts.utils import rigging_utils
rigging_utils.load_dna(dna_path)
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 browse_preset(self):
"""浏览预设文件"""
file_path, _ = QtWidgets.QFileDialog.getOpenFileName(
self, "选择预设文件", os.path.expanduser("~"),
"预设文件 (*.json *.preset)")
if file_path:
self.preset_edit.setText(file_path)
# 骨架工具回调
def clear_options(self):
"""清空选项"""
from scripts.utils import rigging_utils
rigging_utils.clear_options()
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()

View File

@ -1,22 +1,19 @@
/* 全局 QPushButton 样式 */
QPushButton {
background-color: #2A2A2A;
color: #CCCCCC;
border-radius: 3px;
background-color: #D0D0D0;
color: #303030;
border-radius: 10px;
padding: 5px;
font-weight: bold;
min-width: 80px;
border: 1px solid #444444;
}
QPushButton:hover {
background-color: #3A3A3A;
border-color: #555555;
background-color: #E0E0E0;
}
QPushButton:pressed {
background-color: #1A1A1A;
border-color: #333333;
background-color: #C0C0C0;
}
QPushButton:disabled {
@ -26,7 +23,7 @@ QPushButton:disabled {
}
/* 单独的消息按钮样式(可选) */
.messageButton {
QPushButton.message-button {
background-color: #B0B0B0;
color: #303030;
border-radius: 10px;
@ -35,11 +32,11 @@ QPushButton:disabled {
min-width: 80px;
}
.messageButton:hover {
QPushButton.message-button:hover {
background-color: #C0C0C0;
}
.messageButton:pressed {
QPushButton.message-button:pressed {
background-color: #A0A0A0;
}
@ -277,3 +274,13 @@ QMenu::item:selected {
QMenu::item:pressed {
background-color: #333333;
}
/* 其他控件样式 */
QDialog {
background-color: #404040;
color: #E0E0E0;
}
QLabel {
color: #E0E0E0;
}

1635
scripts/ui/widgets.py Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,496 @@
import os
import json
import maya.cmds as cmds
from scripts.config import data
class DNADefinition:
"""DNA定义类"""
def __init__(self):
self.file_path = ""
self.content = {}
def load(self, file_path):
"""加载DNA文件"""
if not os.path.exists(file_path):
raise FileNotFoundError(f"DNA文件不存在: {file_path}")
try:
with open(file_path, "r") as f:
self.content = json.load(f)
self.file_path = file_path
return True
except Exception as e:
cmds.warning(f"加载DNA文件失败: {str(e)}")
return False
def save(self, file_path=None):
"""保存DNA文件"""
save_path = file_path or self.file_path
if not save_path:
cmds.warning("未指定保存路径")
return False
try:
with open(save_path, "w") as f:
json.dump(self.content, f, indent=4)
return True
except Exception as e:
cmds.warning(f"保存DNA文件失败: {str(e)}")
return False
def validate(self):
"""验证DNA内容"""
required_keys = ["joints", "blendshapes", "description"]
for key in required_keys:
if key not in self.content:
return False
return True
# 全局DNA定义实例
dna_definition = DNADefinition()
def browse_dna_file():
"""浏览DNA文件"""
file_path, _ = cmds.fileDialog2(
fileFilter="DNA Files (*.dna)",
dialogStyle=2,
fileMode=1
)
if not file_path:
return
# 加载DNA文件
if dna_definition.load(file_path[0]):
# 更新UI预览
update_dna_preview()
# 更新骨骼列表
update_joint_list()
# 更新BlendShape列表
update_blendshape_list()
def update_dna_preview():
"""更新DNA预览"""
# 获取UI实例
from scripts.MetaFusion import app
if not app:
return
define_tab = app.window.findChild(QtWidgets.QWidget, "DefineTab")
if not define_tab:
return
# 更新预览内容
preview_text = json.dumps(dna_definition.content, indent=4)
define_tab.dna_preview.setText(preview_text)
def update_joint_list():
"""更新骨骼列表"""
from scripts.MetaFusion import app
if not app:
return
define_tab = app.window.findChild(QtWidgets.QWidget, "DefineTab")
if not define_tab:
return
# 清空列表
define_tab.joint_list.clear()
# 添加骨骼项
joints = dna_definition.content.get("joints", [])
for joint in joints:
item = QtWidgets.QTreeWidgetItem([
joint["name"],
str(joint["position"]),
str(joint["rotation"]),
str(joint["scale"])
])
define_tab.joint_list.addTopLevelItem(item)
def update_blendshape_list():
"""更新BlendShape列表"""
from scripts.MetaFusion import app
if not app:
return
define_tab = app.window.findChild(QtWidgets.QWidget, "DefineTab")
if not define_tab:
return
# 清空列表
define_tab.bs_list.clear()
# 添加BlendShape项
blendshapes = dna_definition.content.get("blendshapes", [])
for bs in blendshapes:
item = QtWidgets.QTreeWidgetItem([
bs["name"],
bs["target"],
str(bs["weight"])
])
define_tab.bs_list.addTopLevelItem(item)
# 骨骼功能
def add_joint():
"""添加骨骼"""
# 获取选中的骨骼
sel = cmds.ls(sl=True, type="joint")
if not sel:
cmds.warning("请先选择要添加的骨骼")
return
# 获取骨骼信息
joint = sel[0]
pos = cmds.xform(joint, q=True, ws=True, t=True)
rot = cmds.xform(joint, q=True, ws=True, ro=True)
scl = cmds.xform(joint, q=True, ws=True, s=True)
# 添加到DNA定义
joint_data = {
"name": joint,
"position": pos,
"rotation": rot,
"scale": scl
}
if "joints" not in dna_definition.content:
dna_definition.content["joints"] = []
dna_definition.content["joints"].append(joint_data)
# 更新UI
update_joint_list()
def delete_joint():
"""删除骨骼"""
from scripts.MetaFusion import app
if not app:
return
define_tab = app.window.findChild(QtWidgets.QWidget, "DefineTab")
if not define_tab:
return
# 获取选中项
items = define_tab.joint_list.selectedItems()
if not items:
cmds.warning("请先选择要删除的骨骼")
return
# 从DNA定义中删除
for item in items:
joint_name = item.text(0)
joints = dna_definition.content.get("joints", [])
dna_definition.content["joints"] = [
j for j in joints if j["name"] != joint_name
]
# 更新UI
update_joint_list()
def modify_joint():
"""修改骨骼"""
from scripts.MetaFusion import app
if not app:
return
define_tab = app.window.findChild(QtWidgets.QWidget, "DefineTab")
if not define_tab:
return
# 获取选中项
items = define_tab.joint_list.selectedItems()
if not items:
cmds.warning("请先选择要修改的骨骼")
return
# 获取场景中选中的骨骼
sel = cmds.ls(sl=True, type="joint")
if not sel:
cmds.warning("请先选择要用于更新的骨骼")
return
# 更新骨骼信息
joint = sel[0]
pos = cmds.xform(joint, q=True, ws=True, t=True)
rot = cmds.xform(joint, q=True, ws=True, ro=True)
scl = cmds.xform(joint, q=True, ws=True, s=True)
# 更新DNA定义
joint_name = items[0].text(0)
joints = dna_definition.content.get("joints", [])
for j in joints:
if j["name"] == joint_name:
j["position"] = pos
j["rotation"] = rot
j["scale"] = scl
break
# 更新UI
update_joint_list()
# BlendShape功能
def add_blendshape():
"""添加BlendShape"""
# 获取选中的BlendShape
sel = cmds.ls(sl=True, type="blendShape")
if not sel:
cmds.warning("请先选择要添加的BlendShape")
return
# 获取BlendShape信息
bs = sel[0]
target = cmds.blendShape(bs, q=True, g=True)[0]
weight = cmds.getAttr(f"{bs}.weight[0]")
# 添加到DNA定义
bs_data = {
"name": bs,
"target": target,
"weight": weight
}
if "blendshapes" not in dna_definition.content:
dna_definition.content["blendshapes"] = []
dna_definition.content["blendshapes"].append(bs_data)
# 更新UI
update_blendshape_list()
def delete_blendshape():
"""删除BlendShape"""
from scripts.MetaFusion import app
if not app:
return
define_tab = app.window.findChild(QtWidgets.QWidget, "DefineTab")
if not define_tab:
return
# 获取选中项
items = define_tab.bs_list.selectedItems()
if not items:
cmds.warning("请先选择要删除的BlendShape")
return
# 从DNA定义中删除
for item in items:
bs_name = item.text(0)
blendshapes = dna_definition.content.get("blendshapes", [])
dna_definition.content["blendshapes"] = [
bs for bs in blendshapes if bs["name"] != bs_name
]
# 更新UI
update_blendshape_list()
def modify_blendshape():
"""修改BlendShape"""
from scripts.MetaFusion import app
if not app:
return
define_tab = app.window.findChild(QtWidgets.QWidget, "DefineTab")
if not define_tab:
return
# 获取选中项
items = define_tab.bs_list.selectedItems()
if not items:
cmds.warning("请先选择要修改的BlendShape")
return
# 获取场景中选中的BlendShape
sel = cmds.ls(sl=True, type="blendShape")
if not sel:
cmds.warning("请先选择要用于更新的BlendShape")
return
# 更新BlendShape信息
bs = sel[0]
target = cmds.blendShape(bs, q=True, g=True)[0]
weight = cmds.getAttr(f"{bs}.weight[0]")
# 更新DNA定义
bs_name = items[0].text(0)
blendshapes = dna_definition.content.get("blendshapes", [])
for b in blendshapes:
if b["name"] == bs_name:
b["target"] = target
b["weight"] = weight
break
# 更新UI
update_blendshape_list()
# DNA相关功能
def load_dna_preview(dna_file):
"""加载DNA预览
Args:
dna_file: DNA文件路径
Returns:
bool: 是否加载成功
"""
try:
# 加载DNA文件
if not dna_definition.load(dna_file):
return False
# 更新UI预览
update_dna_preview()
return True
except Exception as e:
cmds.warning(f"加载DNA预览失败: {str(e)}")
return False
def validate_dna(dna_file):
"""验证DNA文件
Args:
dna_file: DNA文件路径
Returns:
bool: 是否验证通过
"""
try:
# 加载DNA文件
if not dna_definition.load(dna_file):
return False
# 验证必要字段
if not dna_definition.validate():
cmds.warning("DNA文件缺少必要字段")
return False
# 验证骨骼数据
joints = dna_definition.content.get("joints", [])
for joint in joints:
required = ["name", "position", "rotation", "scale"]
if not all(key in joint for key in required):
cmds.warning(f"骨骼数据不完整: {joint.get('name', 'unknown')}")
return False
# 验证BlendShape数据
blendshapes = dna_definition.content.get("blendshapes", [])
for bs in blendshapes:
required = ["name", "target", "weight"]
if not all(key in bs for key in required):
cmds.warning(f"BlendShape数据不完整: {bs.get('name', 'unknown')}")
return False
return True
except Exception as e:
cmds.warning(f"验证DNA文件失败: {str(e)}")
return False
def export_dna_definition(dna_file):
"""导出DNA定义
Args:
dna_file: 导出文件路径
Returns:
bool: 是否导出成功
"""
try:
# 收集场景中的骨骼数据
joints = []
for joint in cmds.ls(type="joint"):
pos = cmds.xform(joint, q=True, ws=True, t=True)
rot = cmds.xform(joint, q=True, ws=True, ro=True)
scl = cmds.xform(joint, q=True, ws=True, s=True)
joints.append({
"name": joint,
"position": pos,
"rotation": rot,
"scale": scl
})
# 收集场景中的BlendShape数据
blendshapes = []
for bs in cmds.ls(type="blendShape"):
target = cmds.blendShape(bs, q=True, g=True)[0]
weight = cmds.getAttr(f"{bs}.weight[0]")
blendshapes.append({
"name": bs,
"target": target,
"weight": weight
})
# 更新DNA内容
dna_definition.content.update({
"joints": joints,
"blendshapes": blendshapes,
"description": {
"name": cmds.file(q=True, sn=True, shn=True),
"version": data.TOOL_VERSION,
"author": data.TOOL_AUTHOR,
"date": cmds.date(format="YYYY-MM-DD HH:mm:ss")
}
})
# 保存文件
return dna_definition.save(dna_file)
except Exception as e:
cmds.warning(f"导出DNA定义失败: {str(e)}")
return False
def import_dna_definition(dna_file):
"""导入DNA定义
Args:
dna_file: DNA文件路径
Returns:
bool: 是否导入成功
"""
try:
# 验证DNA文件
if not validate_dna(dna_file):
return False
# 导入骨骼
joints = dna_definition.content.get("joints", [])
for joint_data in joints:
# 检查骨骼是否存在
joint_name = joint_data["name"]
if not cmds.objExists(joint_name):
# 创建骨骼
joint = cmds.joint(name=joint_name)
else:
joint = joint_name
# 设置变换
cmds.xform(joint,
ws=True,
t=joint_data["position"],
ro=joint_data["rotation"],
s=joint_data["scale"]
)
# 导入BlendShape
blendshapes = dna_definition.content.get("blendshapes", [])
for bs_data in blendshapes:
# 检查目标是否存在
if not cmds.objExists(bs_data["target"]):
cmds.warning(f"BlendShape目标不存在: {bs_data['target']}")
continue
# 创建或获取BlendShape
bs_name = bs_data["name"]
if not cmds.objExists(bs_name):
bs = cmds.blendShape(
bs_data["target"],
name=bs_name,
frontOfChain=True
)[0]
else:
bs = bs_name
# 设置权重
cmds.setAttr(f"{bs}.weight[0]", bs_data["weight"])
return True
except Exception as e:
cmds.warning(f"导入DNA定义失败: {str(e)}")
return False

View File

@ -0,0 +1,57 @@
import os
import maya.cmds as cmds
def check_installation():
"""检查插件安装状态"""
# 获取当前插件路径
plugin_path = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
issues = []
# 检查mod文件
maya_mod_path = os.path.join(os.getenv('MAYA_APP_DIR'), 'modules')
mod_file = os.path.join(maya_mod_path, 'MetaFusion.mod')
if not os.path.exists(mod_file):
issues.append("缺少mod文件")
else:
# 验证mod文件内容
with open(mod_file, 'r') as f:
content = f.read()
if plugin_path not in content:
issues.append("mod文件路径不正确")
# 检查必要目录
required_dirs = [
'ui',
'utils',
'config',
'resources/icons'
]
for dir_path in required_dirs:
if not os.path.exists(os.path.join(plugin_path, dir_path)):
issues.append(f"缺少必要目录: {dir_path}")
# 检查必要文件
required_files = [
'MetaFusion.py',
'ui/__init__.py',
'ui/menu.py',
'ui/models.py',
'ui/rigging.py',
'ui/define.py',
'utils/adjust_utils.py',
'utils/define_utils.py',
'config/data.py'
]
for file_path in required_files:
if not os.path.exists(os.path.join(plugin_path, file_path)):
issues.append(f"缺少必要文件: {file_path}")
# 检查插件加载
if not cmds.pluginInfo('MetaFusion', q=True, loaded=True):
try:
cmds.loadPlugin('MetaFusion')
except:
issues.append("插件无法加载")
return issues

146
scripts/utils/menu_utils.py Normal file
View File

@ -0,0 +1,146 @@
import os
import maya.cmds as cmds
from scripts.config import data
from scripts.utils import model_utils, rigging_utils, adjust_utils, define_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 set_chinese():
"""设置中文界面"""
print("设置中文界面功能待实现")
def set_english():
"""设置英文界面"""
print("设置英文界面功能待实现")
# 帮助菜单功能
def show_help():
"""显示帮助文档"""
if os.path.exists(data.TOOL_HELP_URL):
os.startfile(data.TOOL_HELP_URL)
else:
cmds.warning("帮助文档不存在")
def show_about():
"""显示关于对话框"""
cmds.confirmDialog(
title="关于",
message=f"{data.TOOL_NAME} {data.TOOL_VERSION}\n"
f"作者: {data.TOOL_AUTHOR}\n"
f"帮助: {data.TOOL_HELP_URL}",
button=["确定"],
defaultButton="确定"
)

View File

@ -0,0 +1,595 @@
import os
import maya.cmds as cmds
from scripts.config import data
import maya.mel as mel
# LOD模型加载功能
def load_model(lod_index, model_type, file_path):
"""加载模型
Args:
lod_index: LOD级别(0-7)
model_type: 模型类型(head/teeth/gums/eye_l/eye_r/iris/eyelash/eyelid/cartilage/body)
file_path: 模型文件路径
"""
try:
# 检查文件是否存在
if not os.path.exists(file_path):
raise FileNotFoundError(f"模型文件不存在: {file_path}")
# 导入模型
imported_nodes = cmds.file(
file_path,
i=True,
returnNewNodes=True,
namespace=f"LOD{lod_index}_{model_type}"
)
# 设置模型属性
for node in imported_nodes:
if cmds.objectType(node) == "transform":
# 添加自定义属性
cmds.addAttr(node, ln="LOD_INDEX", at="long", dv=lod_index)
cmds.addAttr(node, ln="MODEL_TYPE", dt="string")
cmds.setAttr(f"{node}.MODEL_TYPE", model_type, type="string")
print(f"成功加载LOD{lod_index}_{model_type}模型")
return imported_nodes
except Exception as e:
cmds.warning(f"加载模型失败: {str(e)}")
return None
def delete_lod(lod_index):
"""删除指定LOD级别的所有模型"""
try:
# 查找指定LOD的所有模型
lod_nodes = cmds.ls(f"LOD{lod_index}_*", long=True)
if lod_nodes:
cmds.delete(lod_nodes)
print(f"成功删除LOD{lod_index}的所有模型")
else:
print(f"未找到LOD{lod_index}的模型")
except Exception as e:
cmds.warning(f"删除LOD{lod_index}失败: {str(e)}")
# 模型工具功能
def split_model():
"""分离选中的模型"""
try:
selection = cmds.ls(sl=True, type="transform")
if not selection:
raise ValueError("请先选择要分离的模型")
for obj in selection:
# 获取模型的面数
mesh = cmds.listRelatives(obj, shapes=True, type="mesh")
if not mesh:
continue
# 分离每个面为独立的模型
cmds.polySeparate(obj, ch=False)
print("模型分离完成")
except Exception as e:
cmds.warning(f"模型分离失败: {str(e)}")
def generate_facial_accessories():
"""生成面部配件"""
print("生成面部配件功能待实现")
def repair_normals():
"""修复法线"""
try:
selection = cmds.ls(sl=True, type="transform")
if not selection:
raise ValueError("请先选择要修复法线的模型")
for obj in selection:
mesh = cmds.listRelatives(obj, shapes=True, type="mesh")
if not mesh:
continue
# 统一法线方向
cmds.polyNormal(obj, normalMode=2, userNormalMode=0)
# 解锁法线
cmds.polyNormalPerVertex(obj, ufn=True)
print("法线修复完成")
except Exception as e:
cmds.warning(f"修复法线失败: {str(e)}")
def repair_vertex_order():
"""修复点序"""
try:
selection = cmds.ls(sl=True, type="transform")
if not selection:
raise ValueError("请先选择要修复点序的模型")
for obj in selection:
# 重新排序顶点
cmds.polyReorder(obj)
print("点序修复完成")
except Exception as e:
cmds.warning(f"修复点序失败: {str(e)}")
def repair_seams():
"""修复接缝"""
try:
selection = cmds.ls(sl=True, type="transform")
if not selection:
raise ValueError("请先选择要修复接缝的模型")
for obj in selection:
# 合并重叠顶点
cmds.polyMergeVertex(obj, d=0.001)
# 删除重复面
cmds.polyRemoveFace(obj, removeDuplicateFaces=True)
print("接缝修复完成")
except Exception as e:
cmds.warning(f"修复接缝失败: {str(e)}")
# LOD功能
def load_custom_models():
"""自定义加载模型"""
print("自定义加载模型功能待实现")
def standardize_naming():
"""标准化命名"""
try:
selection = cmds.ls(sl=True, type="transform")
if not selection:
raise ValueError("请先选择要标准化命名的模型")
for obj in selection:
# 获取LOD信息
if cmds.attributeQuery("LOD_INDEX", node=obj, exists=True):
lod_index = cmds.getAttr(f"{obj}.LOD_INDEX")
model_type = cmds.getAttr(f"{obj}.MODEL_TYPE")
# 重命名为标准格式
new_name = f"LOD{lod_index}_{model_type}"
cmds.rename(obj, new_name)
print("命名标准化完成")
except Exception as e:
cmds.warning(f"标准化命名失败: {str(e)}")
def auto_group():
"""自动分组"""
try:
# 创建LOD组
for i in range(8):
group_name = f"LOD{i}_GROUP"
if not cmds.objExists(group_name):
cmds.group(empty=True, name=group_name)
# 将模型移动到对应组
all_models = cmds.ls("LOD*_*", type="transform")
for model in all_models:
if cmds.attributeQuery("LOD_INDEX", node=model, exists=True):
lod_index = cmds.getAttr(f"{model}.LOD_INDEX")
group_name = f"LOD{lod_index}_GROUP"
cmds.parent(model, group_name)
print("自动分组完成")
except Exception as e:
cmds.warning(f"自动分组失败: {str(e)}")
# MetaHuman模型特定功能
def validate_metahuman_topology():
"""验证模型是否符合MetaHuman拓扑要求"""
try:
selection = cmds.ls(sl=True, type="transform")
if not selection:
raise ValueError("请先选择要验证的模型")
results = []
for obj in selection:
mesh = cmds.listRelatives(obj, shapes=True, type="mesh")
if not mesh:
continue
# 获取模型信息
vertex_count = cmds.polyEvaluate(obj, vertex=True)
face_count = cmds.polyEvaluate(obj, face=True)
# 检查是否符合MetaHuman标准
is_valid = True
messages = []
# 获取LOD级别
lod_index = -1
if cmds.attributeQuery("LOD_INDEX", node=obj, exists=True):
lod_index = cmds.getAttr(f"{obj}.LOD_INDEX")
# 根据LOD级别验证顶点数和面数
if lod_index == 0:
if vertex_count != 7127:
messages.append(f"顶点数不符合LOD0标准(当前:{vertex_count}, 应为:7127)")
is_valid = False
elif lod_index == 1:
if vertex_count != 5127:
messages.append(f"顶点数不符合LOD1标准(当前:{vertex_count}, 应为:5127)")
is_valid = False
# ... 其他LOD级别的验证
results.append({
"object": obj,
"is_valid": is_valid,
"messages": messages
})
# 显示验证结果
for result in results:
status = "" if result["is_valid"] else ""
print(f"{status} {result['object']}:")
if not result["is_valid"]:
for msg in result["messages"]:
print(f" - {msg}")
return results
except Exception as e:
cmds.warning(f"拓扑验证失败: {str(e)}")
return None
def transfer_uvs():
"""传递UV到其他LOD级别"""
try:
# 获取源模型和目标模型
selection = cmds.ls(sl=True, type="transform")
if len(selection) != 2:
raise ValueError("请选择一个源模型和一个目标模型")
source = selection[0]
target = selection[1]
# 执行UV传递
cmds.transferAttributes(
source,
target,
transferUVs=2,
transferColors=0,
sampleSpace=4 # World space
)
# 删除构建历史
cmds.delete(target, ch=True)
print(f"UV传递完成: {source} -> {target}")
except Exception as e:
cmds.warning(f"UV传递失败: {str(e)}")
def generate_lod_chain():
"""生成完整的LOD链"""
try:
# 获取选中的LOD0模型
selection = cmds.ls(sl=True, type="transform")
if not selection:
raise ValueError("请先选择LOD0模型")
source = selection[0]
if not cmds.attributeQuery("LOD_INDEX", node=source, exists=True):
raise ValueError("请选择带有LOD属性的模型")
lod_index = cmds.getAttr(f"{source}.LOD_INDEX")
if lod_index != 0:
raise ValueError("请选择LOD0模型")
# 为每个LOD级别生成简化模型
for i in range(1, 8):
target_name = source.replace("LOD0", f"LOD{i}")
# 复制模型
duplicate = cmds.duplicate(source, name=target_name)[0]
# 设置LOD属性
cmds.setAttr(f"{duplicate}.LOD_INDEX", i)
# 简化模型
reduction_ratio = 1.0 - (i * 0.1) # 每级减少10%
cmds.polyReduce(
duplicate,
percentage=reduction_ratio * 100,
triangulate=False,
preserveTopology=True,
keepQuadsWeight=1.0,
keepBorder=True
)
print(f"生成LOD{i}完成: {target_name}")
print("LOD链生成完成")
except Exception as e:
cmds.warning(f"LOD链生成失败: {str(e)}")
def check_model_symmetry():
"""检查模型对称性"""
try:
selection = cmds.ls(sl=True, type="transform")
if not selection:
raise ValueError("请先选择要检查的模型")
for obj in selection:
# 获取顶点位置
vertices = cmds.ls(f"{obj}.vtx[*]", flatten=True)
vertex_positions = {}
for vtx in vertices:
pos = cmds.pointPosition(vtx, world=True)
# 使用x坐标的绝对值作为键保存顶点信息
x_abs = abs(pos[0])
if x_abs not in vertex_positions:
vertex_positions[x_abs] = []
vertex_positions[x_abs].append((vtx, pos))
# 检查对称性
asymmetric_vertices = []
for x_abs, points in vertex_positions.items():
if len(points) == 2:
vtx1, pos1 = points[0]
vtx2, pos2 = points[1]
# 检查y和z坐标是否对称
if abs(pos1[1] - pos2[1]) > 0.001 or abs(pos1[2] - pos2[2]) > 0.001:
asymmetric_vertices.extend([vtx1, vtx2])
elif len(points) == 1 and x_abs > 0.001: # 忽略中心线上的点
asymmetric_vertices.append(points[0][0])
# 显示结果
if asymmetric_vertices:
cmds.select(asymmetric_vertices)
print(f"发现{len(asymmetric_vertices)}个不对称顶点")
else:
print(f"{obj}模型对称性检查通过")
except Exception as e:
cmds.warning(f"对称性检查失败: {str(e)}")
def mirror_geometry():
"""镜像几何体"""
try:
selection = cmds.ls(sl=True, type="transform")
if not selection:
raise ValueError("请先选择要镜像的模型")
for obj in selection:
# 创建镜像几何体
mirrored = cmds.duplicate(obj, name=f"{obj}_mirrored")[0]
# 执行镜像
cmds.scale(-1, 1, 1, mirrored)
# 反转法线
cmds.polyNormal(mirrored, normalMode=0)
print(f"模型镜像完成: {mirrored}")
except Exception as e:
cmds.warning(f"模型镜像失败: {str(e)}")
# 骨骼权重相关功能
def transfer_skin_weights():
"""传递蒙皮权重"""
try:
selection = cmds.ls(sl=True, type="transform")
if len(selection) < 2:
raise ValueError("请选择源模型和目标模型")
source = selection[0]
targets = selection[1:]
# 获取源模型的蒙皮变形器
skin_cluster = mel.eval(f'findRelatedSkinCluster("{source}")')
if not skin_cluster:
raise ValueError(f"源模型{source}没有蒙皮变形器")
# 获取骨骼列表
joints = cmds.skinCluster(skin_cluster, q=True, inf=True)
# 为每个目标模型创建蒙皮并传递权重
for target in targets:
# 创建新的蒙皮变形器
new_skin = cmds.skinCluster(
joints,
target,
toSelectedBones=True,
bindMethod=0,
skinMethod=0,
normalizeWeights=1
)[0]
# 复制权重
cmds.copySkinWeights(
ss=skin_cluster,
ds=new_skin,
noMirror=True,
surfaceAssociation="closestPoint",
influenceAssociation="oneToOne"
)
print(f"权重传递完成: {source} -> {target}")
except Exception as e:
cmds.warning(f"权重传递失败: {str(e)}")
def mirror_skin_weights():
"""镜像蒙皮权重"""
try:
selection = cmds.ls(sl=True, type="transform")
if not selection:
raise ValueError("请选择要镜像权重的模型")
for obj in selection:
# 获取蒙皮变形器
skin_cluster = mel.eval(f'findRelatedSkinCluster("{obj}")')
if not skin_cluster:
continue
# 执行镜像
cmds.copySkinWeights(
ss=skin_cluster,
ds=skin_cluster,
mirrorMode="YZ",
surfaceAssociation="closestPoint",
influenceAssociation="closestJoint"
)
print(f"权重镜像完成: {obj}")
except Exception as e:
cmds.warning(f"权重镜像失败: {str(e)}")
def clean_skin_weights():
"""清理蒙皮权重"""
try:
selection = cmds.ls(sl=True, type="transform")
if not selection:
raise ValueError("请选择要清理权重的模型")
for obj in selection:
# 获取蒙皮变形器
skin_cluster = mel.eval(f'findRelatedSkinCluster("{obj}")')
if not skin_cluster:
continue
# 移除小权重
cmds.skinPercent(
skin_cluster,
obj,
pruneWeights=0.01 # 移除小于1%的权重
)
# 规范化权重
cmds.skinPercent(
skin_cluster,
obj,
normalize=True
)
print(f"权重清理完成: {obj}")
except Exception as e:
cmds.warning(f"权重清理失败: {str(e)}")
# 变形器处理功能
def transfer_blendshapes():
"""传递变形器"""
try:
selection = cmds.ls(sl=True, type="transform")
if len(selection) < 2:
raise ValueError("请选择源模型和目标模型")
source = selection[0]
targets = selection[1:]
# 获取源模型的变形器
blendshapes = cmds.listConnections(source, type="blendShape")
if not blendshapes:
raise ValueError(f"源模型{source}没有变形器")
for bs in blendshapes:
# 获取所有目标形状
targets_count = cmds.blendShape(bs, q=True, target=True)
target_weights = []
target_names = []
# 保存当前权重
for i in range(targets_count):
weight = cmds.getAttr(f"{bs}.weight[{i}]")
name = cmds.aliasAttr(f"{bs}.weight[{i}]", q=True)
target_weights.append(weight)
target_names.append(name)
# 为每个目标模型创建变形器
for target in targets:
new_bs = cmds.blendShape(
target,
frontOfChain=True,
name=f"{target}_blendShape"
)[0]
# 设置权重
for i, (weight, name) in enumerate(zip(target_weights, target_names)):
cmds.setAttr(f"{new_bs}.{name}", weight)
print(f"变形器传递完成: {source} -> {targets}")
except Exception as e:
cmds.warning(f"变形器传递失败: {str(e)}")
def optimize_blendshapes():
"""优化变形器"""
try:
selection = cmds.ls(sl=True, type="transform")
if not selection:
raise ValueError("请选择要优化变形器的模型")
for obj in selection:
# 获取变形器
blendshapes = cmds.listConnections(obj, type="blendShape")
if not blendshapes:
continue
for bs in blendshapes:
# 获取所有目标形状
target_count = cmds.blendShape(bs, q=True, target=True)
# 删除未使用的目标形状
for i in range(target_count):
weight = cmds.getAttr(f"{bs}.weight[{i}]")
if abs(weight) < 0.001: # 权重接近0的目标形状
cmds.removeMultiInstance(f"{bs}.weight[{i}]", b=True)
# 重新排序索引
cmds.blendShape(bs, edit=True, resetTargetDelta=True)
print(f"变形器优化完成: {obj}")
except Exception as e:
cmds.warning(f"变形器优化失败: {str(e)}")
def create_corrective_blendshape():
"""创建修正变形器"""
try:
selection = cmds.ls(sl=True, type="transform")
if len(selection) != 2:
raise ValueError("请选择基础模型和目标形状")
base = selection[0]
target = selection[1]
# 创建修正变形器
corrective_bs = cmds.blendShape(
target,
base,
frontOfChain=True,
name=f"{base}_corrective"
)[0]
# 设置权重驱动
cmds.setDrivenKeyframe(
f"{corrective_bs}.{target}",
currentDriver="time",
driverValue=0,
value=0
)
cmds.setDrivenKeyframe(
f"{corrective_bs}.{target}",
currentDriver="time",
driverValue=1,
value=1
)
print(f"修正变形器创建完成: {corrective_bs}")
except Exception as e:
cmds.warning(f"创建修正变形器失败: {str(e)}")

View File

@ -0,0 +1,24 @@
import maya.cmds as cmds
from scripts.config import data
def export_settings():
"""导出设置"""
print("导出设置功能待实现")
def import_settings():
"""导入设置"""
print("导入设置功能待实现")
def clear_options():
"""清空选项"""
print("清空选项功能待实现")
def import_skeleton():
"""导入骨架"""
print("导入骨架功能待实现")
def create_skeleton():
"""创建骨架"""
print("创建骨架功能待实现")
# 其他功能函数...