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 os
import sys import sys
import webbrowser import webbrowser
# Maya imports
import maya.mel as mel import maya.mel as mel
import maya.cmds as cmds import maya.cmds as cmds
import maya.OpenMayaUI as omui 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: try:
ROOT_PATH = data.ROOT_PATH from PySide2 import QtCore, QtGui, QtWidgets
except NameError: from shiboken2 import wrapInstance
ROOT_PATH = os.path.abspath(os.path.dirname(__file__)).replace("\\", "/") print("从PySide2加载Qt和shiboken2")
TOOL_NAME = data.TOOL_NAME except ImportError:
TOOL_VERSION = data.TOOL_VERSION try:
TOOL_AUTHOR = data.TOOL_AUTHOR from PySide6 import QtCore, QtGui, QtWidgets
TOOL_LANG = data.TOOL_LANG from shiboken6 import wrapInstance
TOOL_WSCL_NAME = data.TOOL_WSCL_NAME print("从PySide6加载Qt和shiboken6")
TOOL_HELP_URL = data.TOOL_HELP_URL except ImportError:
SCRIPTS_PATH = data.SCRIPTS_PATH try:
ICONS_PATH = data.ICONS_PATH 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 ===================================== #===================================== 3. Utility Functions =====================================
def maya_main_window(): def maya_main_window():
"""Get Maya main window as QWidget""" """获取Maya主窗口"""
main_window_ptr = omui.MQtUtil.mainWindow() main_window_ptr = omui.MQtUtil.mainWindow()
return wrapInstance(int(main_window_ptr), QtWidgets.QWidget) if main_window_ptr:
return wrapInstance(int(main_window_ptr), QtWidgets.QWidget)
return None
def ensure_directory(directory_path): def ensure_directory(directory_path):
"""Ensure directory exists, create if not""" """确保目录存在"""
if not os.path.exists(directory_path): if directory_path and isinstance(directory_path, str):
os.makedirs(directory_path) if not os.path.exists(directory_path):
print(f"Created directory: {directory_path}") os.makedirs(directory_path)
print(f"Created directory: {directory_path}")
return directory_path return directory_path
def get_maya_modules_dir(): def get_maya_modules_dir():
"""Get Maya modules directory path""" """获取Maya模块目录"""
maya_app_dir = cmds.internalVar(userAppDir=True) maya_app_dir = cmds.internalVar(userAppDir=True)
return ensure_directory(os.path.join(maya_app_dir, "modules")) 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 ===================================== #===================================== 4. UI Component Classes =====================================
class SetButton(QtWidgets.QPushButton): class SetButton(QtWidgets.QPushButton):
@ -71,16 +98,10 @@ class InstallDialog(QtWidgets.QDialog):
self.setup_ui() self.setup_ui()
def load_stylesheet(self): def load_stylesheet(self):
"""加载 QSS 样式文件""" with open(TOOL_STYLE_FILE, 'r', encoding='utf-8') as f:
try: style = f.read()
style_file = data.TOOL_STYLE_FILE self.setStyleSheet(style)
if os.path.exists(style_file): print(f"已加载样式文件: {TOOL_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}")
def setup_ui(self): def setup_ui(self):
"""Initialize and setup UI components""" """Initialize and setup UI components"""
@ -161,7 +182,7 @@ class InstallDialog(QtWidgets.QDialog):
webbrowser.open(TOOL_HELP_URL) webbrowser.open(TOOL_HELP_URL)
QtWidgets.QApplication.restoreOverrideCursor() QtWidgets.QApplication.restoreOverrideCursor()
def get_script_path(self): def get_script_path():
maya_script = mel.eval('getenv("MAYA_SCRIPT_NAME")') maya_script = mel.eval('getenv("MAYA_SCRIPT_NAME")')
if maya_script and os.path.exists(maya_script): if maya_script and os.path.exists(maya_script):
return os.path.dirname(maya_script) return os.path.dirname(maya_script)
@ -322,7 +343,7 @@ except ImportError as e:
print("sys.path:", sys.path) print("sys.path:", sys.path)
print("Contents of Scripts folder:", os.listdir(SCRIPTS_PATH)) print("Contents of Scripts folder:", os.listdir(SCRIPTS_PATH))
""" """
def uninstall_tool(self): def uninstall_tool(self):
"""Uninstall the tool from Maya""" """Uninstall the tool from Maya"""
window_name = f"{TOOL_NAME}Window" window_name = f"{TOOL_NAME}Window"

Binary file not shown.

View File

@ -5,111 +5,49 @@ import os
import sys import sys
import maya.cmds as cmds import maya.cmds as cmds
import maya.OpenMayaUI as omui import maya.OpenMayaUI as omui
from shiboken2 import wrapInstance
import traceback import traceback
import os
from scripts.config import data import sys
QtCore, QtGui, QtWidgets = data.Qt() import webbrowser
import maya.mel as mel
#===================================== 2. Global Variables ===================================== import maya.cmds as cmds
import maya.OpenMayaUI as omui
try: try:
ROOT_PATH = data.ROOT_PATH from PySide2 import QtCore, QtGui, QtWidgets
except NameError: from shiboken2 import wrapInstance
ROOT_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))).replace("\\", "/") print("从PySide2加载Qt和shiboken2")
TOOL_NAME = data.TOOL_NAME except ImportError:
TOOL_VERSION = data.TOOL_VERSION try:
TOOL_AUTHOR = data.TOOL_AUTHOR from PySide6 import QtCore, QtGui, QtWidgets
TOOL_LANG = data.TOOL_LANG from shiboken6 import wrapInstance
TOOL_WSCL_NAME = data.TOOL_WSCL_NAME print("从PySide6加载Qt和shiboken6")
TOOL_HELP_URL = data.TOOL_HELP_URL except ImportError:
SCRIPTS_PATH = data.SCRIPTS_PATH try:
ICONS_PATH = data.ICONS_PATH from PySide import QtCore, QtGui, QtWidgets
TOOL_MAIN_SCRIPT = data.TOOL_MAIN_SCRIPT from shiboken import wrapInstance
TOOL_MOD_FILENAME = data.TOOL_MOD_FILENAME print("从PySide加载Qt和shiboken")
TOOL_ICON = data.TOOL_ICON except ImportError as e:
TOOL_COMMAND_ICON = data.TOOL_COMMAND_ICON print(f"Qt加载失败: {str(e)}")
DNA_PATH = data.DNA_PATH QtCore = QtGui = QtWidgets = None
DNA_IMG_PATH = data.DNA_IMG_PATH wrapInstance = None
DNALIB_PATH = data.DNALIB_PATH
BUILDER_PATH = data.BUILDER_PATH
UI_PATH = data.UI_PATH
UTILS_PATH = data.UTILS_PATH
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): # 导入UI模块
def __init__(self): from scripts.ui.menu import MenuManager
super().__init__() from scripts.ui.models import ModelTab
self.init_ui() from scripts.ui.rigging import RigTab
from scripts.ui.adjust import AdjustTab
def init_ui(self): from scripts.ui.define import DefineTab
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("定义功能开发中..."))
class MainWindow(QtWidgets.QMainWindow): class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None): def __init__(self, parent=None):
@ -120,21 +58,19 @@ class MainWindow(QtWidgets.QMainWindow):
"""初始化核心组件""" """初始化核心组件"""
self.setup_paths() self.setup_paths()
self.init_ui() self.init_ui()
self.setup_connections()
def setup_paths(self): def setup_paths(self):
"""配置系统路径""" """配置系统路径"""
# 添加插件路径 if PLUGIN_PATH not in sys.path:
if data.PLUGIN_PATH not in sys.path: sys.path.insert(0, PLUGIN_PATH)
sys.path.insert(0, data.PLUGIN_PATH)
# 添加PyDNA二进制路径到环境变量 # 添加PyDNA路径
pydna_bin = os.path.join(data.PYDNA_PATH, "bin") pydna_bin = os.path.join(PYDNA_PATH, "bin")
os.environ["PATH"] = f"{pydna_bin}{os.pathsep}{os.environ['PATH']}" os.environ["PATH"] = f"{pydna_bin}{os.pathsep}{os.environ['PATH']}"
def init_ui(self): def init_ui(self):
"""初始化UI框架""" """初始化UI框架"""
self.setWindowTitle(f"{data.TOOL_NAME} {data.TOOL_VERSION}") self.setWindowTitle(f"{TOOL_NAME} {TOOL_VERSION}")
self.setMinimumSize(1200, 800) self.setMinimumSize(1200, 800)
# 主窗口布局 # 主窗口布局
@ -142,9 +78,8 @@ class MainWindow(QtWidgets.QMainWindow):
self.setCentralWidget(main_widget) self.setCentralWidget(main_widget)
main_layout = QtWidgets.QVBoxLayout(main_widget) main_layout = QtWidgets.QVBoxLayout(main_widget)
# 创建核心组件 # 创建菜单和工具栏
self.create_menu_bar() self.menu_manager = MenuManager(self)
self.create_toolbar()
self.create_tab_widget() self.create_tab_widget()
# 加载样式 # 加载样式
@ -152,108 +87,12 @@ class MainWindow(QtWidgets.QMainWindow):
def load_styles(self): def load_styles(self):
"""加载样式表""" """加载样式表"""
style_path = os.path.join(data.STYLES_PATH, "style.qss") if os.path.exists(TOOL_STYLE_FILE):
if os.path.exists(style_path): with open(TOOL_STYLE_FILE, "r", encoding="utf-8") as f:
with open(style_path, "r", encoding="utf-8") as f:
self.setStyleSheet(f.read()) 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): def create_tab_widget(self):
"""创建标签页""" """创建标签页"""
self.tab_widget = QtWidgets.QTabWidget() self.tab_widget = QtWidgets.QTabWidget()
self.centralWidget().layout().addWidget(self.tab_widget) self.centralWidget().layout().addWidget(self.tab_widget)
@ -268,305 +107,68 @@ class MainWindow(QtWidgets.QMainWindow):
self.tab_widget.addTab(self.rig_tab, "绑定") self.tab_widget.addTab(self.rig_tab, "绑定")
self.tab_widget.addTab(self.adjust_tab, "调整") self.tab_widget.addTab(self.adjust_tab, "调整")
self.tab_widget.addTab(self.define_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(): def get_maya_window():
"""获取 Maya 主窗口""" """获取Maya主窗口"""
import maya.OpenMayaUI as omui
from shiboken2 import wrapInstance
maya_main_window_ptr = omui.MQtUtil.mainWindow() 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(): def dock_to_maya():
"""将窗口嵌入到 Maya 的 Dock 面板""" """嵌入Maya Dock面板"""
global main_window
try: try:
# 先清理可能存在的旧控件
if cmds.workspaceControl(TOOL_WSCL_NAME, exists=True): if cmds.workspaceControl(TOOL_WSCL_NAME, exists=True):
cmds.deleteUI(TOOL_WSCL_NAME) cmds.deleteUI(TOOL_WSCL_NAME)
# 创建新的Dock控件
dock_control = cmds.workspaceControl( dock_control = cmds.workspaceControl(
TOOL_WSCL_NAME, TOOL_WSCL_NAME,
label=TOOL_NAME, label=TOOL_NAME,
tabToControl=["AttributeEditor", -1], tabToControl=["AttributeEditor", -1],
initialWidth=1400, initialWidth=1400,
minimumWidth=1000, minimumWidth=1000,
minimumHeight=800, minimumHeight=800
widthProperty="free",
heightProperty="free"
) )
# 获取Dock控件指针
maya_dock_ptr = omui.MQtUtil.findControl(dock_control) maya_dock_ptr = omui.MQtUtil.findControl(dock_control)
if not maya_dock_ptr: if not maya_dock_ptr:
raise RuntimeError("无法获取Dock控件指针") raise RuntimeError("无法获取Dock控件指针")
# 获取Maya主窗口并创建实例
maya_main_window = get_maya_window() maya_main_window = get_maya_window()
main_window = MainWindow(parent=maya_main_window) main_window = MainWindow(parent=maya_main_window)
# 嵌入到Dock # 确保指针转换为整数
maya_dock_widget = wrapInstance(int(maya_dock_ptr), QtWidgets.QWidget) maya_dock_widget = wrapInstance(int(maya_dock_ptr), QtWidgets.QWidget)
maya_dock_widget.layout().addWidget(main_window) maya_dock_widget.layout().addWidget(main_window)
return main_window return main_window
except Exception as e: except Exception as e:
error_msg = f"Dock嵌入失败: {str(e)}\n{''.join(traceback.format_exc())}" cmds.warning(f"Dock嵌入失败: {str(e)}")
cmds.warning(error_msg)
return None return None
def show(): def show():
"""显示主窗口""" """显示主窗口"""
global main_window global main_window
try: try:
# 清理旧实例
if main_window: if main_window:
main_window.close() main_window.close()
cmds.deleteUI(TOOL_WSCL_NAME) cmds.deleteUI(TOOL_WSCL_NAME)
except: except:
pass pass
# 尝试嵌入 Dock
main_window = dock_to_maya() main_window = dock_to_maya()
# 备用方案:独立窗口模式
if not main_window: if not main_window:
cmds.warning("Dock 模式失败,使用独立窗口模式") cmds.warning("使用独立窗口模式")
main_window = MainWindow() main_window = MainWindow()
main_window.show() main_window.show()
return main_window return main_window
# ===================================== 主函数 =====================================
if __name__ == "__main__": if __name__ == "__main__":
app = QtWidgets.QApplication([]) show()
window = show()
app.exec_()

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 import maya.cmds as cmds
# Base Information # Base Information
TOOL_NAME = "MetaFusion" TOOL_NAME = str("MetaFusion")
TOOL_VERSION = "Beta v1.0.0" TOOL_VERSION = str("Beta v1.0.0")
TOOL_AUTHOR = "CGNICO" TOOL_AUTHOR = str("CGNICO")
TOOL_LANG = 'en_US' TOOL_LANG = str('en_US')
TOOL_WSCL_NAME = f"{TOOL_NAME}WorkSpaceControl" TOOL_WSCL_NAME = str(f"{TOOL_NAME}WorkSpaceControl")
TOOL_HELP_URL = f"https://gitea.cgnico.com/CGNICO/{TOOL_NAME}/wiki" TOOL_HELP_URL = str(f"https://gitea.cgnico.com/CGNICO/{TOOL_NAME}/wiki")
# BASE_PATH # BASE_PATH
ROOT_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))).replace("\\", "/") ROOT_PATH = str(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))).replace("\\", "/"))
SCRIPTS_PATH = os.path.join(ROOT_PATH, "scripts").replace("\\", "/") SCRIPTS_PATH = str(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("\\", "/")
# PYDNA_PATH & PLUGIN_PATH # 资源路径
SYSTEM_OS = "Windows" if cmds.about(os=True).lower().startswith("win") else "Linux" RESOURCES_PATH = str(os.path.join(ROOT_PATH, "resources").replace("\\", "/"))
MAYA_VERSION = int(cmds.about(version=True).split('.')[0]) ICONS_PATH = str(os.path.join(RESOURCES_PATH, "icons").replace("\\", "/"))
PYTHON_VERSION = sys.version.replace(".", "") STYLES_PATH = str(os.path.join(RESOURCES_PATH, "styles").replace("\\", "/"))
PYTHON_VERSION_DIR_MAPPING = {"3108": "python3108", "311": "python311", "397": "python397"} DNA_FILE_PATH = str(os.path.join(RESOURCES_PATH, "dna").replace("\\", "/"))
PYTHON_VERSION_DIR = PYTHON_VERSION_DIR_MAPPING.get(PYTHON_VERSION, "python3") DNA_IMG_PATH = str(os.path.join(RESOURCES_PATH, "img").replace("\\", "/"))
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("\\", "/") # 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 # TOOLS_PATH
DNACALIB_PATH = os.path.join(ROOT_PATH, "dnacalib").replace("\\", "/") DNACALIB_PATH = str(os.path.join(ROOT_PATH, "dnacalib").replace("\\", "/"))
BUILDER_PATH = os.path.join(SCRIPTS_PATH, "builder").replace("\\", "/") BUILDER_PATH = str(os.path.join(SCRIPTS_PATH, "builder").replace("\\", "/"))
DNALIB_PATH = os.path.join(SCRIPTS_PATH, "dnalib").replace("\\", "/") DNALIB_PATH = str(os.path.join(SCRIPTS_PATH, "dnalib").replace("\\", "/"))
UI_PATH = os.path.join(SCRIPTS_PATH, "ui").replace("\\", "/") UI_PATH = str(os.path.join(SCRIPTS_PATH, "ui").replace("\\", "/"))
UTILS_PATH = os.path.join(SCRIPTS_PATH, "utils").replace("\\", "/") UTILS_PATH = str(os.path.join(SCRIPTS_PATH, "utils").replace("\\", "/"))
#FILES # FILES
TOOL_MAIN_SCRIPT = os.path.join(SCRIPTS_PATH, f"{TOOL_NAME}.py").replace("\\", "/") TOOL_MAIN_SCRIPT = str(os.path.join(SCRIPTS_PATH, f"{TOOL_NAME}.py").replace("\\", "/"))
TOOL_STYLE_FILE = os.path.join(STYLES_PATH, "style.qss").replace("\\", "/") TOOL_STYLE_FILE = str(os.path.join(UI_PATH, "style.qss").replace("\\", "/"))
TOOL_ICON = os.path.join(ICONS_PATH, f"{TOOL_NAME}Logo.png").replace("\\", "/") TOOL_ICON = str(os.path.join(ICONS_PATH, f"{TOOL_NAME}Logo.png").replace("\\", "/"))
TOOL_COMMAND_ICON = os.path.join(ICONS_PATH, "CommandButton.png").replace("\\", "/") TOOL_COMMAND_ICON = str(os.path.join(ICONS_PATH, "CommandButton.png").replace("\\", "/"))
TOOL_MOD_FILENAME = f"{TOOL_NAME}.mod" 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("============================================")
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("TOOL_NAME",TOOL_NAME) print(f"ROOT_PATH: {ROOT_PATH}")
print("TOOL_VERSION",TOOL_VERSION) print(f"SCRIPTS_PATH: {SCRIPTS_PATH}")
print("TOOL_AUTHOR",TOOL_AUTHOR) print(f"ICONS_PATH: {ICONS_PATH}")
print("TOOL_LANG",TOOL_LANG) print(f"STYLES_PATH: {STYLES_PATH}")
print("TOOL_WSCL_NAME",TOOL_WSCL_NAME) print(f"DNA_FILE_PATH: {DNA_FILE_PATH}")
print("TOOL_HELP_URL",TOOL_HELP_URL) print(f"DNA_IMG_PATH: {DNA_IMG_PATH}")
print("ROOT_PATH",ROOT_PATH) print(f"PLUGIN_PATH: {PLUGIN_PATH}")
print("SCRIPTS_PATH",SCRIPTS_PATH) print(f"PYDNA_PATH: {PYDNA_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("PLUGIN_PATH",PLUGIN_PATH) print(f"DNACALIB_PATH: {DNACALIB_PATH}")
print("PYDNA_PATH",PYDNA_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("DNACALIB_PATH",DNACALIB_PATH) print(f"TOOL_MAIN_SCRIPT: {TOOL_MAIN_SCRIPT}")
print("BUILDER_PATH",BUILDER_PATH) print(f"TOOL_STYLE_FILE: {TOOL_STYLE_FILE}")
print("DNALIB_PATH",DNALIB_PATH) print(f"TOOL_ICON: {TOOL_ICON}")
print("UI_PATH",UI_PATH) print(f"TOOL_COMMAND_ICON: {TOOL_COMMAND_ICON}")
print("UTILS_PATH",UTILS_PATH) print(f"TOOL_MOD_FILENAME: {TOOL_MOD_FILENAME}")
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)

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 样式 */
QPushButton { QPushButton {
background-color: #2A2A2A; background-color: #D0D0D0;
color: #CCCCCC; color: #303030;
border-radius: 3px; border-radius: 10px;
padding: 5px; padding: 5px;
font-weight: bold; font-weight: bold;
min-width: 80px; min-width: 80px;
border: 1px solid #444444;
} }
QPushButton:hover { QPushButton:hover {
background-color: #3A3A3A; background-color: #E0E0E0;
border-color: #555555;
} }
QPushButton:pressed { QPushButton:pressed {
background-color: #1A1A1A; background-color: #C0C0C0;
border-color: #333333;
} }
QPushButton:disabled { QPushButton:disabled {
@ -26,7 +23,7 @@ QPushButton:disabled {
} }
/* 单独的消息按钮样式(可选) */ /* 单独的消息按钮样式(可选) */
.messageButton { QPushButton.message-button {
background-color: #B0B0B0; background-color: #B0B0B0;
color: #303030; color: #303030;
border-radius: 10px; border-radius: 10px;
@ -35,11 +32,11 @@ QPushButton:disabled {
min-width: 80px; min-width: 80px;
} }
.messageButton:hover { QPushButton.message-button:hover {
background-color: #C0C0C0; background-color: #C0C0C0;
} }
.messageButton:pressed { QPushButton.message-button:pressed {
background-color: #A0A0A0; background-color: #A0A0A0;
} }
@ -277,3 +274,13 @@ QMenu::item:selected {
QMenu::item:pressed { QMenu::item:pressed {
background-color: #333333; 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("创建骨架功能待实现")
# 其他功能函数...