Update
This commit is contained in:
parent
eea26bcc87
commit
7aeee81643
113
Install.py
113
Install.py
@ -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()
|
||||
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):
|
||||
"""Ensure directory exists, create if not"""
|
||||
if not os.path.exists(directory_path):
|
||||
os.makedirs(directory_path)
|
||||
print(f"Created directory: {directory_path}")
|
||||
"""确保目录存在"""
|
||||
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)
|
||||
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 =====================================
|
||||
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)
|
||||
@ -322,7 +343,7 @@ except ImportError as e:
|
||||
print("sys.path:", sys.path)
|
||||
print("Contents of Scripts folder:", os.listdir(SCRIPTS_PATH))
|
||||
"""
|
||||
|
||||
|
||||
def uninstall_tool(self):
|
||||
"""Uninstall the tool from Maya"""
|
||||
window_name = f"{TOOL_NAME}Window"
|
||||
|
BIN
__pycache__/Install.cpython-39.pyc
Normal file
BIN
__pycache__/Install.cpython-39.pyc
Normal file
Binary file not shown.
@ -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)
|
||||
|
||||
@ -268,305 +107,68 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
self.tab_widget.addTab(self.rig_tab, "绑定")
|
||||
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()
|
||||
|
||||
|
BIN
scripts/__pycache__/__init__.cpython-39.pyc
Normal file
BIN
scripts/__pycache__/__init__.cpython-39.pyc
Normal file
Binary file not shown.
BIN
scripts/config/__pycache__/__init__.cpython-39.pyc
Normal file
BIN
scripts/config/__pycache__/__init__.cpython-39.pyc
Normal file
Binary file not shown.
BIN
scripts/config/__pycache__/data.cpython-39.pyc
Normal file
BIN
scripts/config/__pycache__/data.cpython-39.pyc
Normal file
Binary file not shown.
@ -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("============================================")
|
||||
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("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(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("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"PLUGIN_PATH: {PLUGIN_PATH}")
|
||||
print(f"PYDNA_PATH: {PYDNA_PATH}")
|
||||
|
||||
print("PLUGIN_PATH",PLUGIN_PATH)
|
||||
print("PYDNA_PATH",PYDNA_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("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("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}")
|
||||
|
||||
|
@ -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
182
scripts/ui/adjust.py
Normal 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
171
scripts/ui/define.py
Normal 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
108
scripts/ui/menu.py
Normal 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
151
scripts/ui/models.py
Normal 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
203
scripts/ui/rigging.py
Normal 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()
|
@ -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
1635
scripts/ui/widgets.py
Normal file
File diff suppressed because it is too large
Load Diff
1187
scripts/utils/adjust_utils.py
Normal file
1187
scripts/utils/adjust_utils.py
Normal file
File diff suppressed because it is too large
Load Diff
496
scripts/utils/define_utils.py
Normal file
496
scripts/utils/define_utils.py
Normal 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
|
57
scripts/utils/install_check.py
Normal file
57
scripts/utils/install_check.py
Normal 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
146
scripts/utils/menu_utils.py
Normal 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="确定"
|
||||
)
|
595
scripts/utils/model_utils.py
Normal file
595
scripts/utils/model_utils.py
Normal 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)}")
|
24
scripts/utils/rigging_utils.py
Normal file
24
scripts/utils/rigging_utils.py
Normal 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("创建骨架功能待实现")
|
||||
|
||||
# 其他功能函数...
|
Loading…
Reference in New Issue
Block a user