Update
This commit is contained in:
parent
eea26bcc87
commit
7aeee81643
113
Install.py
113
Install.py
@ -6,56 +6,83 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import webbrowser
|
import webbrowser
|
||||||
|
|
||||||
# Maya imports
|
|
||||||
import maya.mel as mel
|
import maya.mel as mel
|
||||||
import maya.cmds as cmds
|
import maya.cmds as cmds
|
||||||
import maya.OpenMayaUI as omui
|
import maya.OpenMayaUI as omui
|
||||||
|
|
||||||
# Qt imports
|
|
||||||
from PySide2 import QtWidgets, QtGui, QtCore
|
|
||||||
from shiboken2 import wrapInstance
|
|
||||||
|
|
||||||
# Custom imports
|
|
||||||
from scripts.config import data
|
|
||||||
QtCore, QtGui, QtWidgets = data.Qt()
|
|
||||||
|
|
||||||
#===================================== 2. Global Variables =====================================
|
|
||||||
try:
|
try:
|
||||||
ROOT_PATH = data.ROOT_PATH
|
from PySide2 import QtCore, QtGui, QtWidgets
|
||||||
except NameError:
|
from shiboken2 import wrapInstance
|
||||||
ROOT_PATH = os.path.abspath(os.path.dirname(__file__)).replace("\\", "/")
|
print("从PySide2加载Qt和shiboken2")
|
||||||
TOOL_NAME = data.TOOL_NAME
|
except ImportError:
|
||||||
TOOL_VERSION = data.TOOL_VERSION
|
try:
|
||||||
TOOL_AUTHOR = data.TOOL_AUTHOR
|
from PySide6 import QtCore, QtGui, QtWidgets
|
||||||
TOOL_LANG = data.TOOL_LANG
|
from shiboken6 import wrapInstance
|
||||||
TOOL_WSCL_NAME = data.TOOL_WSCL_NAME
|
print("从PySide6加载Qt和shiboken6")
|
||||||
TOOL_HELP_URL = data.TOOL_HELP_URL
|
except ImportError:
|
||||||
SCRIPTS_PATH = data.SCRIPTS_PATH
|
try:
|
||||||
ICONS_PATH = data.ICONS_PATH
|
from PySide import QtCore, QtGui, QtWidgets
|
||||||
|
from shiboken import wrapInstance
|
||||||
|
print("从PySide加载Qt和shiboken")
|
||||||
|
except ImportError as e:
|
||||||
|
print(f"Qt加载失败: {str(e)}")
|
||||||
|
QtCore = QtGui = QtWidgets = None
|
||||||
|
wrapInstance = None
|
||||||
|
|
||||||
|
from scripts.config.data import (
|
||||||
|
TOOL_NAME, TOOL_VERSION, TOOL_AUTHOR, TOOL_LANG,
|
||||||
|
TOOL_WSCL_NAME, TOOL_HELP_URL, ROOT_PATH, SCRIPTS_PATH,
|
||||||
|
ICONS_PATH, STYLES_PATH, DNA_FILE_PATH, DNA_IMG_PATH,
|
||||||
|
PLUGIN_PATH, PYDNA_PATH, DNACALIB_PATH, BUILDER_PATH,
|
||||||
|
DNALIB_PATH, UI_PATH, UTILS_PATH, TOOL_MAIN_SCRIPT,
|
||||||
|
TOOL_STYLE_FILE, TOOL_ICON, TOOL_COMMAND_ICON,
|
||||||
|
TOOL_MOD_FILENAME
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_script_path():
|
||||||
|
try:
|
||||||
|
maya_script = mel.eval('getenv("MAYA_SCRIPT_PATH")')
|
||||||
|
if maya_script:
|
||||||
|
paths = maya_script.split(os.pathsep)
|
||||||
|
for path in paths:
|
||||||
|
install_path = os.path.join(path, "Install.py")
|
||||||
|
if os.path.exists(install_path):
|
||||||
|
return os.path.dirname(install_path)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
return os.path.dirname(os.path.abspath(__file__))
|
||||||
|
except:
|
||||||
|
return os.getcwd()
|
||||||
|
|
||||||
|
ROOT_PATH = get_script_path()
|
||||||
|
if ROOT_PATH not in sys.path:
|
||||||
|
sys.path.insert(0, ROOT_PATH)
|
||||||
|
|
||||||
TOOL_MAIN_SCRIPT = data.TOOL_MAIN_SCRIPT
|
|
||||||
TOOL_MOD_FILENAME = data.TOOL_MOD_FILENAME
|
|
||||||
TOOL_ICON = data.TOOL_ICON
|
|
||||||
TOOL_COMMAND_ICON = data.TOOL_COMMAND_ICON
|
|
||||||
|
|
||||||
#===================================== 3. Utility Functions =====================================
|
#===================================== 3. Utility Functions =====================================
|
||||||
def maya_main_window():
|
def maya_main_window():
|
||||||
"""Get Maya main window as QWidget"""
|
"""获取Maya主窗口"""
|
||||||
main_window_ptr = omui.MQtUtil.mainWindow()
|
main_window_ptr = omui.MQtUtil.mainWindow()
|
||||||
return wrapInstance(int(main_window_ptr), QtWidgets.QWidget)
|
if main_window_ptr:
|
||||||
|
return wrapInstance(int(main_window_ptr), QtWidgets.QWidget)
|
||||||
|
return None
|
||||||
|
|
||||||
def ensure_directory(directory_path):
|
def ensure_directory(directory_path):
|
||||||
"""Ensure directory exists, create if not"""
|
"""确保目录存在"""
|
||||||
if not os.path.exists(directory_path):
|
if directory_path and isinstance(directory_path, str):
|
||||||
os.makedirs(directory_path)
|
if not os.path.exists(directory_path):
|
||||||
print(f"Created directory: {directory_path}")
|
os.makedirs(directory_path)
|
||||||
|
print(f"Created directory: {directory_path}")
|
||||||
return directory_path
|
return directory_path
|
||||||
|
|
||||||
def get_maya_modules_dir():
|
def get_maya_modules_dir():
|
||||||
"""Get Maya modules directory path"""
|
"""获取Maya模块目录"""
|
||||||
maya_app_dir = cmds.internalVar(userAppDir=True)
|
maya_app_dir = cmds.internalVar(userAppDir=True)
|
||||||
return ensure_directory(os.path.join(maya_app_dir, "modules"))
|
if maya_app_dir and isinstance(maya_app_dir, str):
|
||||||
|
return ensure_directory(os.path.join(maya_app_dir, "modules"))
|
||||||
|
return None
|
||||||
|
|
||||||
#===================================== 4. UI Component Classes =====================================
|
#===================================== 4. UI Component Classes =====================================
|
||||||
class SetButton(QtWidgets.QPushButton):
|
class SetButton(QtWidgets.QPushButton):
|
||||||
@ -71,16 +98,10 @@ class InstallDialog(QtWidgets.QDialog):
|
|||||||
self.setup_ui()
|
self.setup_ui()
|
||||||
|
|
||||||
def load_stylesheet(self):
|
def load_stylesheet(self):
|
||||||
"""加载 QSS 样式文件"""
|
with open(TOOL_STYLE_FILE, 'r', encoding='utf-8') as f:
|
||||||
try:
|
style = f.read()
|
||||||
style_file = data.TOOL_STYLE_FILE
|
self.setStyleSheet(style)
|
||||||
if os.path.exists(style_file):
|
print(f"已加载样式文件: {TOOL_STYLE_FILE}")
|
||||||
with open(style_file, 'r') as f:
|
|
||||||
self.setStyleSheet(f.read())
|
|
||||||
else:
|
|
||||||
print(f"Warning: Style file not found: {style_file}")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error loading stylesheet: {e}")
|
|
||||||
|
|
||||||
def setup_ui(self):
|
def setup_ui(self):
|
||||||
"""Initialize and setup UI components"""
|
"""Initialize and setup UI components"""
|
||||||
@ -161,7 +182,7 @@ class InstallDialog(QtWidgets.QDialog):
|
|||||||
webbrowser.open(TOOL_HELP_URL)
|
webbrowser.open(TOOL_HELP_URL)
|
||||||
QtWidgets.QApplication.restoreOverrideCursor()
|
QtWidgets.QApplication.restoreOverrideCursor()
|
||||||
|
|
||||||
def get_script_path(self):
|
def get_script_path():
|
||||||
maya_script = mel.eval('getenv("MAYA_SCRIPT_NAME")')
|
maya_script = mel.eval('getenv("MAYA_SCRIPT_NAME")')
|
||||||
if maya_script and os.path.exists(maya_script):
|
if maya_script and os.path.exists(maya_script):
|
||||||
return os.path.dirname(maya_script)
|
return os.path.dirname(maya_script)
|
||||||
@ -322,7 +343,7 @@ except ImportError as e:
|
|||||||
print("sys.path:", sys.path)
|
print("sys.path:", sys.path)
|
||||||
print("Contents of Scripts folder:", os.listdir(SCRIPTS_PATH))
|
print("Contents of Scripts folder:", os.listdir(SCRIPTS_PATH))
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def uninstall_tool(self):
|
def uninstall_tool(self):
|
||||||
"""Uninstall the tool from Maya"""
|
"""Uninstall the tool from Maya"""
|
||||||
window_name = f"{TOOL_NAME}Window"
|
window_name = f"{TOOL_NAME}Window"
|
||||||
|
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 sys
|
||||||
import maya.cmds as cmds
|
import maya.cmds as cmds
|
||||||
import maya.OpenMayaUI as omui
|
import maya.OpenMayaUI as omui
|
||||||
from shiboken2 import wrapInstance
|
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
|
import os
|
||||||
from scripts.config import data
|
import sys
|
||||||
QtCore, QtGui, QtWidgets = data.Qt()
|
import webbrowser
|
||||||
|
import maya.mel as mel
|
||||||
#===================================== 2. Global Variables =====================================
|
import maya.cmds as cmds
|
||||||
|
import maya.OpenMayaUI as omui
|
||||||
try:
|
try:
|
||||||
ROOT_PATH = data.ROOT_PATH
|
from PySide2 import QtCore, QtGui, QtWidgets
|
||||||
except NameError:
|
from shiboken2 import wrapInstance
|
||||||
ROOT_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))).replace("\\", "/")
|
print("从PySide2加载Qt和shiboken2")
|
||||||
TOOL_NAME = data.TOOL_NAME
|
except ImportError:
|
||||||
TOOL_VERSION = data.TOOL_VERSION
|
try:
|
||||||
TOOL_AUTHOR = data.TOOL_AUTHOR
|
from PySide6 import QtCore, QtGui, QtWidgets
|
||||||
TOOL_LANG = data.TOOL_LANG
|
from shiboken6 import wrapInstance
|
||||||
TOOL_WSCL_NAME = data.TOOL_WSCL_NAME
|
print("从PySide6加载Qt和shiboken6")
|
||||||
TOOL_HELP_URL = data.TOOL_HELP_URL
|
except ImportError:
|
||||||
SCRIPTS_PATH = data.SCRIPTS_PATH
|
try:
|
||||||
ICONS_PATH = data.ICONS_PATH
|
from PySide import QtCore, QtGui, QtWidgets
|
||||||
TOOL_MAIN_SCRIPT = data.TOOL_MAIN_SCRIPT
|
from shiboken import wrapInstance
|
||||||
TOOL_MOD_FILENAME = data.TOOL_MOD_FILENAME
|
print("从PySide加载Qt和shiboken")
|
||||||
TOOL_ICON = data.TOOL_ICON
|
except ImportError as e:
|
||||||
TOOL_COMMAND_ICON = data.TOOL_COMMAND_ICON
|
print(f"Qt加载失败: {str(e)}")
|
||||||
DNA_PATH = data.DNA_PATH
|
QtCore = QtGui = QtWidgets = None
|
||||||
DNA_IMG_PATH = data.DNA_IMG_PATH
|
wrapInstance = None
|
||||||
DNALIB_PATH = data.DNALIB_PATH
|
|
||||||
BUILDER_PATH = data.BUILDER_PATH
|
|
||||||
UI_PATH = data.UI_PATH
|
|
||||||
UTILS_PATH = data.UTILS_PATH
|
|
||||||
|
|
||||||
main_window = None
|
from scripts.config.data import (
|
||||||
|
TOOL_NAME, TOOL_VERSION, TOOL_AUTHOR, TOOL_LANG,
|
||||||
|
TOOL_WSCL_NAME, TOOL_HELP_URL, ROOT_PATH, SCRIPTS_PATH,
|
||||||
|
ICONS_PATH, STYLES_PATH, DNA_FILE_PATH, DNA_IMG_PATH,
|
||||||
|
PLUGIN_PATH, PYDNA_PATH, DNACALIB_PATH, BUILDER_PATH,
|
||||||
|
DNALIB_PATH, UI_PATH, UTILS_PATH, TOOL_MAIN_SCRIPT,
|
||||||
|
TOOL_STYLE_FILE, TOOL_ICON, TOOL_COMMAND_ICON,
|
||||||
|
TOOL_MOD_FILENAME
|
||||||
|
)
|
||||||
|
|
||||||
class ModelTab(QtWidgets.QWidget):
|
# 导入UI模块
|
||||||
def __init__(self):
|
from scripts.ui.menu import MenuManager
|
||||||
super().__init__()
|
from scripts.ui.models import ModelTab
|
||||||
self.init_ui()
|
from scripts.ui.rigging import RigTab
|
||||||
|
from scripts.ui.adjust import AdjustTab
|
||||||
def init_ui(self):
|
from scripts.ui.define import DefineTab
|
||||||
layout = QtWidgets.QVBoxLayout(self)
|
|
||||||
|
|
||||||
# LOD导航栏
|
|
||||||
self.lod_tabs = QtWidgets.QTabWidget()
|
|
||||||
for i in range(1, 7):
|
|
||||||
tab = QtWidgets.QWidget()
|
|
||||||
self.lod_tabs.addTab(tab, f"LOD{i-1}")
|
|
||||||
layout.addWidget(self.lod_tabs)
|
|
||||||
|
|
||||||
# 模型操作工具栏
|
|
||||||
tool_bar = QtWidgets.QHBoxLayout()
|
|
||||||
self.add_button(tool_bar, "导入模型", "import.png", self.import_model)
|
|
||||||
self.add_button(tool_bar, "导出模型", "export.png", self.export_model)
|
|
||||||
layout.addLayout(tool_bar)
|
|
||||||
|
|
||||||
def add_button(self, layout, text, icon, callback):
|
|
||||||
btn = QtWidgets.QPushButton(QtGui.QIcon(os.path.join(data.ICONS_PATH, icon)), text)
|
|
||||||
btn.clicked.connect(callback)
|
|
||||||
layout.addWidget(btn)
|
|
||||||
|
|
||||||
def import_model(self): pass
|
|
||||||
def export_model(self): pass
|
|
||||||
|
|
||||||
class RigTab(QtWidgets.QWidget):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
self.init_ui()
|
|
||||||
|
|
||||||
def init_ui(self):
|
|
||||||
splitter = QtWidgets.QSplitter(QtCore.Qt.Horizontal)
|
|
||||||
|
|
||||||
# 左侧控制器列表
|
|
||||||
left_panel = QtWidgets.QWidget()
|
|
||||||
left_layout = QtWidgets.QVBoxLayout(left_panel)
|
|
||||||
self.controller_list = QtWidgets.QListWidget()
|
|
||||||
left_layout.addWidget(self.controller_list)
|
|
||||||
|
|
||||||
# 右侧视口区域
|
|
||||||
right_panel = QtWidgets.QWidget()
|
|
||||||
right_layout = QtWidgets.QVBoxLayout(right_panel)
|
|
||||||
self.viewport_label = QtWidgets.QLabel("3D视口区域")
|
|
||||||
right_layout.addWidget(self.viewport_label)
|
|
||||||
|
|
||||||
splitter.addWidget(left_panel)
|
|
||||||
splitter.addWidget(right_panel)
|
|
||||||
|
|
||||||
main_layout = QtWidgets.QHBoxLayout(self)
|
|
||||||
main_layout.addWidget(splitter)
|
|
||||||
|
|
||||||
class AdjustTab(QtWidgets.QWidget):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
self.init_ui()
|
|
||||||
|
|
||||||
def init_ui(self):
|
|
||||||
layout = QtWidgets.QVBoxLayout(self)
|
|
||||||
layout.addWidget(QtWidgets.QLabel("调整功能开发中..."))
|
|
||||||
|
|
||||||
class DefineTab(QtWidgets.QWidget):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
self.init_ui()
|
|
||||||
|
|
||||||
def init_ui(self):
|
|
||||||
layout = QtWidgets.QVBoxLayout(self)
|
|
||||||
layout.addWidget(QtWidgets.QLabel("定义功能开发中..."))
|
|
||||||
|
|
||||||
class MainWindow(QtWidgets.QMainWindow):
|
class MainWindow(QtWidgets.QMainWindow):
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
@ -120,21 +58,19 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
"""初始化核心组件"""
|
"""初始化核心组件"""
|
||||||
self.setup_paths()
|
self.setup_paths()
|
||||||
self.init_ui()
|
self.init_ui()
|
||||||
self.setup_connections()
|
|
||||||
|
|
||||||
def setup_paths(self):
|
def setup_paths(self):
|
||||||
"""配置系统路径"""
|
"""配置系统路径"""
|
||||||
# 添加插件路径
|
if PLUGIN_PATH not in sys.path:
|
||||||
if data.PLUGIN_PATH not in sys.path:
|
sys.path.insert(0, PLUGIN_PATH)
|
||||||
sys.path.insert(0, data.PLUGIN_PATH)
|
|
||||||
|
|
||||||
# 添加PyDNA二进制路径到环境变量
|
# 添加PyDNA路径
|
||||||
pydna_bin = os.path.join(data.PYDNA_PATH, "bin")
|
pydna_bin = os.path.join(PYDNA_PATH, "bin")
|
||||||
os.environ["PATH"] = f"{pydna_bin}{os.pathsep}{os.environ['PATH']}"
|
os.environ["PATH"] = f"{pydna_bin}{os.pathsep}{os.environ['PATH']}"
|
||||||
|
|
||||||
def init_ui(self):
|
def init_ui(self):
|
||||||
"""初始化UI框架"""
|
"""初始化UI框架"""
|
||||||
self.setWindowTitle(f"{data.TOOL_NAME} {data.TOOL_VERSION}")
|
self.setWindowTitle(f"{TOOL_NAME} {TOOL_VERSION}")
|
||||||
self.setMinimumSize(1200, 800)
|
self.setMinimumSize(1200, 800)
|
||||||
|
|
||||||
# 主窗口布局
|
# 主窗口布局
|
||||||
@ -142,9 +78,8 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
self.setCentralWidget(main_widget)
|
self.setCentralWidget(main_widget)
|
||||||
main_layout = QtWidgets.QVBoxLayout(main_widget)
|
main_layout = QtWidgets.QVBoxLayout(main_widget)
|
||||||
|
|
||||||
# 创建核心组件
|
# 创建菜单和工具栏
|
||||||
self.create_menu_bar()
|
self.menu_manager = MenuManager(self)
|
||||||
self.create_toolbar()
|
|
||||||
self.create_tab_widget()
|
self.create_tab_widget()
|
||||||
|
|
||||||
# 加载样式
|
# 加载样式
|
||||||
@ -152,108 +87,12 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
|
|
||||||
def load_styles(self):
|
def load_styles(self):
|
||||||
"""加载样式表"""
|
"""加载样式表"""
|
||||||
style_path = os.path.join(data.STYLES_PATH, "style.qss")
|
if os.path.exists(TOOL_STYLE_FILE):
|
||||||
if os.path.exists(style_path):
|
with open(TOOL_STYLE_FILE, "r", encoding="utf-8") as f:
|
||||||
with open(style_path, "r", encoding="utf-8") as f:
|
|
||||||
self.setStyleSheet(f.read())
|
self.setStyleSheet(f.read())
|
||||||
|
|
||||||
def create_menu_bar(self):
|
|
||||||
"""创建菜单栏"""
|
|
||||||
menubar = self.menuBar()
|
|
||||||
|
|
||||||
# 文件菜单
|
|
||||||
file_menu = menubar.addMenu("文件")
|
|
||||||
self.add_menu_action(file_menu, "打开DNA", "open.png", lambda: self.load_dna())
|
|
||||||
self.add_menu_action(file_menu, "保存DNA", "save.png", lambda: self.save_dna())
|
|
||||||
self.add_menu_action(file_menu, "加载当前项目的DNA", "open.png", self.on_load_project_dna)
|
|
||||||
self.add_menu_action(file_menu, "修改混合目标名称", "rename.png", self.on_rename_blend_target)
|
|
||||||
self.add_menu_action(file_menu, "重置混合目标名称", "resetname.png", self.on_reset_blend_target)
|
|
||||||
self.add_menu_action(file_menu, "导出FBX", "export.png", self.on_export_fbx)
|
|
||||||
file_menu.addSeparator()
|
|
||||||
self.add_menu_action(file_menu, "退出", "exit.png", self.close)
|
|
||||||
|
|
||||||
# 编辑菜单
|
|
||||||
edit_menu = menubar.addMenu("编辑")
|
|
||||||
self.add_menu_action(edit_menu, "创建RL4节点", "connect.png", self.create_rl4_node)
|
|
||||||
self.add_menu_action(edit_menu, "删除RL4节点", "disconnect.png", self.delete_rl4_node)
|
|
||||||
self.add_menu_action(edit_menu, "镜像左至右", "mirrorL.png", self.mirror_left_to_right)
|
|
||||||
self.add_menu_action(edit_menu, "镜像右至左", "mirrorR.png", self.mirror_right_to_left)
|
|
||||||
self.add_menu_action(edit_menu, "姿势由A型转T型", "pose_A_To_T.png", self.pose_A_to_T)
|
|
||||||
self.add_menu_action(edit_menu, "姿势由T型转A型", "pose_T_To_A.png", self.pose_T_to_A)
|
|
||||||
self.add_menu_action(edit_menu, "传输LOD贴图", "locator.png", self.transfer_lod_texture)
|
|
||||||
self.add_menu_action(edit_menu, "设置关节颜色", "color.png", self.set_joint_color)
|
|
||||||
self.add_menu_action(edit_menu, "取消全部标记", "unmark_all.png", self.unmark_all)
|
|
||||||
self.add_menu_action(edit_menu, "重建所有目标", "rebuildTargets.png", self.rebuild_targets)
|
|
||||||
self.add_menu_action(edit_menu, "为所有表情设置关键帧", "bakeAnimation.png", self.bake_all_animations)
|
|
||||||
self.add_menu_action(edit_menu, "烘焙所有表情的关键帧", "centerCurrentTime.png", self.bake_all_keyframes)
|
|
||||||
|
|
||||||
# 工具菜单
|
|
||||||
tool_menu = menubar.addMenu("工具")
|
|
||||||
self.add_menu_action(tool_menu, "导出蒙皮", "export_skin.png", self.export_skin)
|
|
||||||
self.add_menu_action(tool_menu, "导入蒙皮", "import_skin.png", self.import_skin)
|
|
||||||
self.add_menu_action(tool_menu, "拷贝装皮", "copy_skin.png", self.copy_skin)
|
|
||||||
self.add_menu_action(tool_menu, "RBF变形器", "blendShape.png", self.create_blend_shape)
|
|
||||||
self.add_menu_action(tool_menu, "快速绑定服装", "clothing_weight.png", self.quick_bind_clothing)
|
|
||||||
self.add_menu_action(tool_menu, "克隆混合变形", "blendShape.png", self.clone_blend_shape)
|
|
||||||
self.add_menu_action(tool_menu, "UV传递点序", "repair_vertex_order.png", self.repair_vertex_order)
|
|
||||||
self.add_menu_action(tool_menu, "面部生成控制器", "controller.png", self.create_face_controller)
|
|
||||||
self.add_menu_action(tool_menu, "提取52BS", "ARKit52.png", self.extract_52BS)
|
|
||||||
self.add_menu_action(tool_menu, "关节轴向修复", "joint.png", self.repair_joint_axis)
|
|
||||||
self.add_menu_action(tool_menu, "生成身体控制器", "create_body_ctrl.png", self.create_body_controller)
|
|
||||||
self.add_menu_action(tool_menu, "导入面部动画", "import_face_anim.png", self.import_face_animation)
|
|
||||||
self.add_menu_action(tool_menu, "导入身体动画", "import_body_anim.png", self.import_body_animation)
|
|
||||||
|
|
||||||
# 语言菜单
|
|
||||||
lang_menu = menubar.addMenu("语言")
|
|
||||||
self.add_menu_action(lang_menu, "中文", "chinese.png", self.set_chinese)
|
|
||||||
self.add_menu_action(lang_menu, "English", "english.png", self.set_english)
|
|
||||||
|
|
||||||
# 帮助菜单
|
|
||||||
help_menu = menubar.addMenu("帮助")
|
|
||||||
self.add_menu_action(help_menu, "帮助文档", "help.png", self.show_help_document)
|
|
||||||
self.add_menu_action(help_menu, "关于", "warning.png", self.show_about_dialog)
|
|
||||||
|
|
||||||
def add_menu_action(self, menu, text, icon, callback):
|
|
||||||
"""通用菜单项添加方法"""
|
|
||||||
action = QtWidgets.QAction(text, self)
|
|
||||||
if icon:
|
|
||||||
# 优先使用Maya内置图标
|
|
||||||
maya_icon = self.get_maya_icon(icon)
|
|
||||||
if maya_icon:
|
|
||||||
action.setIcon(maya_icon)
|
|
||||||
else:
|
|
||||||
# 使用自定义图标
|
|
||||||
icon_path = os.path.join(data.ICONS_PATH, icon)
|
|
||||||
if os.path.exists(icon_path):
|
|
||||||
action.setIcon(QtGui.QIcon(icon_path))
|
|
||||||
else:
|
|
||||||
cmds.warning(f"图标文件不存在: {icon_path}")
|
|
||||||
action.triggered.connect(callback)
|
|
||||||
menu.addAction(action)
|
|
||||||
|
|
||||||
def get_maya_icon(self, icon_name):
|
|
||||||
"""获取Maya内置图标"""
|
|
||||||
maya_icons = {
|
|
||||||
"bakeAnimation.png": "BakeSimulation.png",
|
|
||||||
"centerCurrentTime.png": "timeCurrentFrame.png",
|
|
||||||
"export_skin.png": "kinReroot.png",
|
|
||||||
# 添加更多图标映射...
|
|
||||||
}
|
|
||||||
if icon_name in maya_icons:
|
|
||||||
return QtGui.QIcon(f":/{maya_icons[icon_name]}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
def create_toolbar(self):
|
|
||||||
"""创建工具栏"""
|
|
||||||
toolbar = self.addToolBar("主工具栏")
|
|
||||||
toolbar.setMovable(False)
|
|
||||||
|
|
||||||
# 添加工具栏按钮
|
|
||||||
toolbar.addAction(QtGui.QIcon(os.path.join(ICONS_PATH, "save.png")), "保存DNA")
|
|
||||||
toolbar.addAction(QtGui.QIcon(os.path.join(ICONS_PATH, "open.png")), "加载当前项目DNA")
|
|
||||||
|
|
||||||
def create_tab_widget(self):
|
def create_tab_widget(self):
|
||||||
"""创建主标签页"""
|
"""创建标签页"""
|
||||||
self.tab_widget = QtWidgets.QTabWidget()
|
self.tab_widget = QtWidgets.QTabWidget()
|
||||||
self.centralWidget().layout().addWidget(self.tab_widget)
|
self.centralWidget().layout().addWidget(self.tab_widget)
|
||||||
|
|
||||||
@ -268,305 +107,68 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
self.tab_widget.addTab(self.rig_tab, "绑定")
|
self.tab_widget.addTab(self.rig_tab, "绑定")
|
||||||
self.tab_widget.addTab(self.adjust_tab, "调整")
|
self.tab_widget.addTab(self.adjust_tab, "调整")
|
||||||
self.tab_widget.addTab(self.define_tab, "定义")
|
self.tab_widget.addTab(self.define_tab, "定义")
|
||||||
|
|
||||||
def setup_connections(self):
|
|
||||||
"""建立信号连接"""
|
|
||||||
# 示例:连接标签页切换信号
|
|
||||||
self.tab_widget.currentChanged.connect(self.on_tab_changed)
|
|
||||||
|
|
||||||
def on_tab_changed(self, index):
|
|
||||||
"""标签页切换回调"""
|
|
||||||
current_tab = self.tab_widget.widget(index)
|
|
||||||
print(f"切换到标签页: {current_tab.objectName()}")
|
|
||||||
|
|
||||||
def setup_model_tab(self):
|
|
||||||
"""设置模型标签页内容"""
|
|
||||||
layout = QtWidgets.QVBoxLayout(self.model_tab)
|
|
||||||
|
|
||||||
# 创建 LOD 分栏
|
|
||||||
self.lod_tabs = QtWidgets.QTabWidget()
|
|
||||||
layout.addWidget(self.lod_tabs)
|
|
||||||
|
|
||||||
# 添加 LOD 分页
|
|
||||||
for i in range(8):
|
|
||||||
lod_tab = QtWidgets.QWidget()
|
|
||||||
lod_layout = QtWidgets.QVBoxLayout(lod_tab)
|
|
||||||
|
|
||||||
# 添加删除按钮
|
|
||||||
delete_btn = QtWidgets.QPushButton(QtGui.QIcon(os.path.join(ICONS_PATH, "delete.png")), "删除")
|
|
||||||
lod_layout.addWidget(delete_btn)
|
|
||||||
|
|
||||||
# 添加模型输入框和加载按钮
|
|
||||||
for part in ["头部", "牙齿", "牙龈", "左眼", "右眼", "虹膜", "睫毛", "眼睑", "软骨", "身体"]:
|
|
||||||
part_layout = QtWidgets.QHBoxLayout()
|
|
||||||
part_input = QtWidgets.QLineEdit()
|
|
||||||
load_btn = QtWidgets.QPushButton(QtGui.QIcon(os.path.join(ICONS_PATH, "target.png")), "加载")
|
|
||||||
part_layout.addWidget(part_input)
|
|
||||||
part_layout.addWidget(load_btn)
|
|
||||||
lod_layout.addLayout(part_layout)
|
|
||||||
|
|
||||||
self.lod_tabs.addTab(lod_tab, f"LOD{i}")
|
|
||||||
|
|
||||||
# 添加 LOD 功能按钮
|
|
||||||
lod_func_layout = QtWidgets.QHBoxLayout()
|
|
||||||
lod_func_layout.addWidget(QtWidgets.QPushButton(QtGui.QIcon(os.path.join(ICONS_PATH, "load_meshes.png")), "自定加载模型"))
|
|
||||||
lod_func_layout.addWidget(QtWidgets.QPushButton(QtGui.QIcon(os.path.join(ICONS_PATH, "standardized_naming.png")), "标准化命名"))
|
|
||||||
lod_func_layout.addWidget(QtWidgets.QPushButton(QtGui.QIcon(os.path.join(ICONS_PATH, "automatic_groupingg.png")), "自动分组"))
|
|
||||||
layout.addLayout(lod_func_layout)
|
|
||||||
|
|
||||||
# 添加模型工具
|
|
||||||
model_tool_layout = QtWidgets.QHBoxLayout()
|
|
||||||
model_tool_layout.addWidget(QtWidgets.QComboBox()) # 拓扑结构下拉菜单
|
|
||||||
model_tool_layout.addWidget(QtWidgets.QComboBox()) # 选择LOD下拉菜单
|
|
||||||
model_tool_layout.addWidget(QtWidgets.QPushButton(QtGui.QIcon(os.path.join(ICONS_PATH, "polySplitVertex.png")), "模型分离"))
|
|
||||||
model_tool_layout.addWidget(QtWidgets.QPushButton(QtGui.QIcon(os.path.join(ICONS_PATH, "supplement_meshes.png")), "生成面部配件"))
|
|
||||||
model_tool_layout.addWidget(QtWidgets.QPushButton(QtGui.QIcon(os.path.join(ICONS_PATH, "repair_normals.png")), "修复法线"))
|
|
||||||
model_tool_layout.addWidget(QtWidgets.QPushButton(QtGui.QIcon(os.path.join(ICONS_PATH, "repair_vertex_order.png")), "修复点序"))
|
|
||||||
model_tool_layout.addWidget(QtWidgets.QPushButton(QtGui.QIcon(os.path.join(ICONS_PATH, "polyChipOff.png")), "修复接缝"))
|
|
||||||
layout.addLayout(model_tool_layout)
|
|
||||||
|
|
||||||
def setup_rig_tab(self):
|
|
||||||
"""设置绑定标签页内容"""
|
|
||||||
layout = QtWidgets.QVBoxLayout(self.rig_tab)
|
|
||||||
|
|
||||||
# 添加 DNA 浏览器
|
|
||||||
self.dna_browser = QtWidgets.QListWidget()
|
|
||||||
self.dna_browser.setIconSize(QtCore.QSize(64, 64))
|
|
||||||
layout.addWidget(self.dna_browser)
|
|
||||||
|
|
||||||
# 添加 DNA 图标缩放滑块
|
|
||||||
self.dna_scale_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
|
|
||||||
self.dna_scale_slider.setMinimum(50)
|
|
||||||
self.dna_scale_slider.setMaximum(200)
|
|
||||||
self.dna_scale_slider.setValue(100)
|
|
||||||
self.dna_scale_slider.valueChanged.connect(self.on_dna_scale_changed)
|
|
||||||
layout.addWidget(self.dna_scale_slider)
|
|
||||||
|
|
||||||
# 添加导入/导出按钮
|
|
||||||
button_layout = QtWidgets.QHBoxLayout()
|
|
||||||
import_btn = QtWidgets.QPushButton("导入设置")
|
|
||||||
export_btn = QtWidgets.QPushButton("导出设置")
|
|
||||||
import_btn.clicked.connect(self.on_import_settings)
|
|
||||||
export_btn.clicked.connect(self.on_export_settings)
|
|
||||||
button_layout.addWidget(import_btn)
|
|
||||||
button_layout.addWidget(export_btn)
|
|
||||||
layout.addLayout(button_layout)
|
|
||||||
|
|
||||||
# 初始化 DNA 浏览器
|
|
||||||
self.init_dna_browser()
|
|
||||||
|
|
||||||
def init_dna_browser(self):
|
|
||||||
"""初始化 DNA 浏览器"""
|
|
||||||
# 清空现有内容
|
|
||||||
self.dna_browser.clear()
|
|
||||||
|
|
||||||
# 获取 DNA 文件列表
|
|
||||||
dna_files = os.listdir(DNA_PATH)
|
|
||||||
img_files = os.listdir(DNA_IMG_PATH)
|
|
||||||
|
|
||||||
# 添加 DNA 项目
|
|
||||||
for dna_file in dna_files:
|
|
||||||
item = QtWidgets.QListWidgetItem(dna_file)
|
|
||||||
# 查找对应的图片
|
|
||||||
img_name = os.path.splitext(dna_file)[0] + ".png"
|
|
||||||
if img_name in img_files:
|
|
||||||
item.setIcon(QtGui.QIcon(os.path.join(data.DNA_IMG_PATH, img_name)))
|
|
||||||
self.dna_browser.addItem(item)
|
|
||||||
|
|
||||||
def on_dna_scale_changed(self, value):
|
|
||||||
"""处理 DNA 图标缩放"""
|
|
||||||
size = int(64 * (value / 100))
|
|
||||||
self.dna_browser.setIconSize(QtCore.QSize(size, size))
|
|
||||||
|
|
||||||
def on_import_settings(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def on_export_settings(self):
|
|
||||||
"""导出设置"""
|
|
||||||
# TODO: 实现导出设置功能
|
|
||||||
pass
|
|
||||||
|
|
||||||
def setup_adjust_tab(self):
|
|
||||||
"""设置调整标签页内容"""
|
|
||||||
layout = QtWidgets.QVBoxLayout(self.adjust_tab)
|
|
||||||
|
|
||||||
# 添加BlendShape列表
|
|
||||||
blend_list = QtWidgets.QListWidget()
|
|
||||||
layout.addWidget(blend_list)
|
|
||||||
|
|
||||||
# 添加滑块组
|
|
||||||
slider_layout = QtWidgets.QVBoxLayout()
|
|
||||||
for i in range(5):
|
|
||||||
slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
|
|
||||||
slider_layout.addWidget(slider)
|
|
||||||
layout.addLayout(slider_layout)
|
|
||||||
|
|
||||||
# 添加按钮组
|
|
||||||
button_layout = QtWidgets.QHBoxLayout()
|
|
||||||
edit_btn = QtWidgets.QPushButton("编辑BlendShape")
|
|
||||||
save_btn = QtWidgets.QPushButton("保存设置")
|
|
||||||
button_layout.addWidget(edit_btn)
|
|
||||||
button_layout.addWidget(save_btn)
|
|
||||||
layout.addLayout(button_layout)
|
|
||||||
|
|
||||||
def setup_define_tab(self):
|
|
||||||
"""设置定义标签页内容"""
|
|
||||||
layout = QtWidgets.QVBoxLayout(self.define_tab)
|
|
||||||
|
|
||||||
# 添加DNA编辑区域
|
|
||||||
self.dna_edit = QtWidgets.QTextEdit()
|
|
||||||
layout.addWidget(self.dna_edit)
|
|
||||||
|
|
||||||
# 添加按钮组
|
|
||||||
button_layout = QtWidgets.QHBoxLayout()
|
|
||||||
self.load_btn = QtWidgets.QPushButton("载入DNA")
|
|
||||||
self.save_btn = QtWidgets.QPushButton("保存DNA")
|
|
||||||
self.export_btn = QtWidgets.QPushButton("导出FBX")
|
|
||||||
|
|
||||||
# 连接信号槽
|
|
||||||
self.load_btn.clicked.connect(self.on_load_dna)
|
|
||||||
self.save_btn.clicked.connect(self.on_save_dna)
|
|
||||||
self.export_btn.clicked.connect(self.on_export_fbx)
|
|
||||||
|
|
||||||
button_layout.addWidget(self.load_btn)
|
|
||||||
button_layout.addWidget(self.save_btn)
|
|
||||||
button_layout.addWidget(self.export_btn)
|
|
||||||
layout.addLayout(button_layout)
|
|
||||||
|
|
||||||
# 创建DNA管理器
|
|
||||||
self.dna_manager = DNAManager()
|
|
||||||
|
|
||||||
def on_load_dna(self): pass
|
|
||||||
def on_save_dna(self): pass
|
|
||||||
def on_export_fbx(self): pass
|
|
||||||
def on_open_dna(self): pass
|
|
||||||
def on_load_project_dna(self): pass
|
|
||||||
def on_rename_blend_target(self): pass
|
|
||||||
def on_reset_blend_target(self): pass
|
|
||||||
def create_rl4_node(self): pass
|
|
||||||
def delete_rl4_node(self): pass
|
|
||||||
def mirror_left_to_right(self): pass
|
|
||||||
def mirror_right_to_left(self): pass
|
|
||||||
def pose_A_to_T(self): pass
|
|
||||||
def pose_T_to_A(self): pass
|
|
||||||
def transfer_lod_texture(self): pass
|
|
||||||
def set_joint_color(self): pass
|
|
||||||
def unmark_all(self): pass
|
|
||||||
def rebuild_targets(self): pass
|
|
||||||
def bake_all_animations(self): pass
|
|
||||||
def bake_all_keyframes(self): pass
|
|
||||||
def safe_shutdown(self): pass
|
|
||||||
def export_skin(self): pass
|
|
||||||
def import_skin(self): pass
|
|
||||||
def copy_skin(self): pass
|
|
||||||
def create_blend_shape(self): pass
|
|
||||||
def quick_bind_clothing(self): pass
|
|
||||||
def clone_blend_shape(self): pass
|
|
||||||
def repair_vertex_order(self): pass
|
|
||||||
def create_face_controller(self): pass
|
|
||||||
def extract_52BS(self): pass
|
|
||||||
def repair_joint_axis(self): pass
|
|
||||||
def create_body_controller(self): pass
|
|
||||||
def import_face_animation(self): pass
|
|
||||||
def import_body_animation(self): pass
|
|
||||||
|
|
||||||
# 添加语言支持方法
|
|
||||||
def set_chinese(self):
|
|
||||||
"""设置中文界面"""
|
|
||||||
print("切换中文界面(待实现)")
|
|
||||||
# TODO: 实现国际化切换
|
|
||||||
|
|
||||||
def set_english(self):
|
|
||||||
"""设置英文界面"""
|
|
||||||
print("切换英文界面(待实现)")
|
|
||||||
# TODO: 实现国际化切换
|
|
||||||
|
|
||||||
# 添加帮助相关方法
|
|
||||||
def show_help_document(self):
|
|
||||||
"""显示帮助文档"""
|
|
||||||
print("打开帮助文档(待实现)")
|
|
||||||
# TODO: 实现帮助文档打开逻辑
|
|
||||||
|
|
||||||
def show_about_dialog(self):
|
|
||||||
"""显示关于对话框"""
|
|
||||||
print("显示关于信息(待实现)")
|
|
||||||
# TODO: 实现关于对话框
|
|
||||||
|
|
||||||
# ===================================== 显示主窗口 =====================================
|
# ===================================== 显示主窗口 =====================================
|
||||||
def get_maya_window():
|
def get_maya_window():
|
||||||
"""获取 Maya 主窗口"""
|
"""获取Maya主窗口"""
|
||||||
import maya.OpenMayaUI as omui
|
|
||||||
from shiboken2 import wrapInstance
|
|
||||||
|
|
||||||
maya_main_window_ptr = omui.MQtUtil.mainWindow()
|
maya_main_window_ptr = omui.MQtUtil.mainWindow()
|
||||||
return wrapInstance(int(maya_main_window_ptr), QtWidgets.QWidget)
|
if maya_main_window_ptr is not None:
|
||||||
|
# 确保指针转换为整数
|
||||||
|
ptr = int(maya_main_window_ptr)
|
||||||
|
return wrapInstance(ptr, QtWidgets.QWidget)
|
||||||
|
return None
|
||||||
|
|
||||||
def dock_to_maya():
|
def dock_to_maya():
|
||||||
"""将窗口嵌入到 Maya 的 Dock 面板"""
|
"""嵌入Maya Dock面板"""
|
||||||
|
global main_window
|
||||||
try:
|
try:
|
||||||
# 先清理可能存在的旧控件
|
|
||||||
if cmds.workspaceControl(TOOL_WSCL_NAME, exists=True):
|
if cmds.workspaceControl(TOOL_WSCL_NAME, exists=True):
|
||||||
cmds.deleteUI(TOOL_WSCL_NAME)
|
cmds.deleteUI(TOOL_WSCL_NAME)
|
||||||
|
|
||||||
# 创建新的Dock控件
|
|
||||||
dock_control = cmds.workspaceControl(
|
dock_control = cmds.workspaceControl(
|
||||||
TOOL_WSCL_NAME,
|
TOOL_WSCL_NAME,
|
||||||
label=TOOL_NAME,
|
label=TOOL_NAME,
|
||||||
tabToControl=["AttributeEditor", -1],
|
tabToControl=["AttributeEditor", -1],
|
||||||
initialWidth=1400,
|
initialWidth=1400,
|
||||||
minimumWidth=1000,
|
minimumWidth=1000,
|
||||||
minimumHeight=800,
|
minimumHeight=800
|
||||||
widthProperty="free",
|
|
||||||
heightProperty="free"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# 获取Dock控件指针
|
|
||||||
maya_dock_ptr = omui.MQtUtil.findControl(dock_control)
|
maya_dock_ptr = omui.MQtUtil.findControl(dock_control)
|
||||||
if not maya_dock_ptr:
|
if not maya_dock_ptr:
|
||||||
raise RuntimeError("无法获取Dock控件指针")
|
raise RuntimeError("无法获取Dock控件指针")
|
||||||
|
|
||||||
# 获取Maya主窗口并创建实例
|
|
||||||
maya_main_window = get_maya_window()
|
maya_main_window = get_maya_window()
|
||||||
main_window = MainWindow(parent=maya_main_window)
|
main_window = MainWindow(parent=maya_main_window)
|
||||||
|
|
||||||
# 嵌入到Dock
|
# 确保指针转换为整数
|
||||||
maya_dock_widget = wrapInstance(int(maya_dock_ptr), QtWidgets.QWidget)
|
maya_dock_widget = wrapInstance(int(maya_dock_ptr), QtWidgets.QWidget)
|
||||||
maya_dock_widget.layout().addWidget(main_window)
|
maya_dock_widget.layout().addWidget(main_window)
|
||||||
|
|
||||||
return main_window
|
return main_window
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_msg = f"Dock嵌入失败: {str(e)}\n{''.join(traceback.format_exc())}"
|
cmds.warning(f"Dock嵌入失败: {str(e)}")
|
||||||
cmds.warning(error_msg)
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def show():
|
def show():
|
||||||
"""显示主窗口"""
|
"""显示主窗口"""
|
||||||
global main_window
|
global main_window
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 清理旧实例
|
|
||||||
if main_window:
|
if main_window:
|
||||||
main_window.close()
|
main_window.close()
|
||||||
cmds.deleteUI(TOOL_WSCL_NAME)
|
cmds.deleteUI(TOOL_WSCL_NAME)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# 尝试嵌入 Dock
|
|
||||||
main_window = dock_to_maya()
|
main_window = dock_to_maya()
|
||||||
|
|
||||||
# 备用方案:独立窗口模式
|
|
||||||
if not main_window:
|
if not main_window:
|
||||||
cmds.warning("Dock 模式失败,使用独立窗口模式")
|
cmds.warning("使用独立窗口模式")
|
||||||
main_window = MainWindow()
|
main_window = MainWindow()
|
||||||
main_window.show()
|
main_window.show()
|
||||||
|
|
||||||
return main_window
|
return main_window
|
||||||
|
|
||||||
# ===================================== 主函数 =====================================
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app = QtWidgets.QApplication([])
|
show()
|
||||||
window = show()
|
|
||||||
app.exec_()
|
|
||||||
|
|
||||||
|
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
|
import maya.cmds as cmds
|
||||||
|
|
||||||
# Base Information
|
# Base Information
|
||||||
TOOL_NAME = "MetaFusion"
|
TOOL_NAME = str("MetaFusion")
|
||||||
TOOL_VERSION = "Beta v1.0.0"
|
TOOL_VERSION = str("Beta v1.0.0")
|
||||||
TOOL_AUTHOR = "CGNICO"
|
TOOL_AUTHOR = str("CGNICO")
|
||||||
TOOL_LANG = 'en_US'
|
TOOL_LANG = str('en_US')
|
||||||
TOOL_WSCL_NAME = f"{TOOL_NAME}WorkSpaceControl"
|
TOOL_WSCL_NAME = str(f"{TOOL_NAME}WorkSpaceControl")
|
||||||
TOOL_HELP_URL = f"https://gitea.cgnico.com/CGNICO/{TOOL_NAME}/wiki"
|
TOOL_HELP_URL = str(f"https://gitea.cgnico.com/CGNICO/{TOOL_NAME}/wiki")
|
||||||
|
|
||||||
|
|
||||||
# BASE_PATH
|
# BASE_PATH
|
||||||
ROOT_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))).replace("\\", "/")
|
ROOT_PATH = str(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))).replace("\\", "/"))
|
||||||
SCRIPTS_PATH = os.path.join(ROOT_PATH, "scripts").replace("\\", "/")
|
SCRIPTS_PATH = str(os.path.join(ROOT_PATH, "scripts").replace("\\", "/"))
|
||||||
ICONS_PATH = os.path.join(ROOT_PATH, "resources", "icons").replace("\\", "/")
|
|
||||||
STYLES_PATH = os.path.join(ROOT_PATH, "resources", "styles").replace("\\", "/")
|
|
||||||
DNA_FILE_PATH = os.path.join(ROOT_PATH, "resources", "dna").replace("\\", "/")
|
|
||||||
DNA_IMG_PATH = os.path.join(ROOT_PATH, "resources", "img").replace("\\", "/")
|
|
||||||
|
|
||||||
# PYDNA_PATH & PLUGIN_PATH
|
# 资源路径
|
||||||
SYSTEM_OS = "Windows" if cmds.about(os=True).lower().startswith("win") else "Linux"
|
RESOURCES_PATH = str(os.path.join(ROOT_PATH, "resources").replace("\\", "/"))
|
||||||
MAYA_VERSION = int(cmds.about(version=True).split('.')[0])
|
ICONS_PATH = str(os.path.join(RESOURCES_PATH, "icons").replace("\\", "/"))
|
||||||
PYTHON_VERSION = sys.version.replace(".", "")
|
STYLES_PATH = str(os.path.join(RESOURCES_PATH, "styles").replace("\\", "/"))
|
||||||
PYTHON_VERSION_DIR_MAPPING = {"3108": "python3108", "311": "python311", "397": "python397"}
|
DNA_FILE_PATH = str(os.path.join(RESOURCES_PATH, "dna").replace("\\", "/"))
|
||||||
PYTHON_VERSION_DIR = PYTHON_VERSION_DIR_MAPPING.get(PYTHON_VERSION, "python3")
|
DNA_IMG_PATH = str(os.path.join(RESOURCES_PATH, "img").replace("\\", "/"))
|
||||||
PLUGIN_PATH = os.path.join(ROOT_PATH, "plugins", SYSTEM_OS, MAYA_VERSION).replace("\\", "/")
|
|
||||||
PYDNA_PATH = os.path.join(ROOT_PATH, "plugins", SYSTEM_OS, "pydna", PYTHON_VERSION_DIR).replace("\\", "/")
|
# SYSTEM_INFO
|
||||||
|
SYSTEM_OS = str("Windows" if cmds.about(os=True).lower().startswith("win") else "Linux")
|
||||||
|
MAYA_VERSION = str(int(cmds.about(version=True).split('.')[0]))
|
||||||
|
|
||||||
|
# PYTHON_VERSION
|
||||||
|
PYTHON_VERSION = str(sys.version.replace(".", ""))
|
||||||
|
major_version = int(PYTHON_VERSION[0])
|
||||||
|
minor_version = int(PYTHON_VERSION[1:3]) if len(PYTHON_VERSION) > 1 else None
|
||||||
|
version_tuple = (major_version,) if minor_version is None else (major_version, minor_version)
|
||||||
|
|
||||||
|
# 版本映射表
|
||||||
|
PYTHON_VERSION_MAP = {
|
||||||
|
(3,): "python3", # 所有Python3主版本
|
||||||
|
(3, 9): "python397", # 3.9.x → python397
|
||||||
|
(3, 10): "python3108", # 3.10.x → python3108
|
||||||
|
(3, 11): "python311" # 3.11.x → python311
|
||||||
|
}
|
||||||
|
|
||||||
|
# 获取Python版本目录
|
||||||
|
PYTHON_VERSION_DIR = str(PYTHON_VERSION_MAP.get(version_tuple, "python3"))
|
||||||
|
|
||||||
|
# PATHS
|
||||||
|
PLUGIN_PATH = str(os.path.join(ROOT_PATH, "plugins", SYSTEM_OS, MAYA_VERSION).replace("\\", "/"))
|
||||||
|
PYDNA_PATH = str(os.path.join(ROOT_PATH, "plugins", SYSTEM_OS, "pydna", PYTHON_VERSION_DIR).replace("\\", "/"))
|
||||||
|
|
||||||
# TOOLS_PATH
|
# TOOLS_PATH
|
||||||
DNACALIB_PATH = os.path.join(ROOT_PATH, "dnacalib").replace("\\", "/")
|
DNACALIB_PATH = str(os.path.join(ROOT_PATH, "dnacalib").replace("\\", "/"))
|
||||||
BUILDER_PATH = os.path.join(SCRIPTS_PATH, "builder").replace("\\", "/")
|
BUILDER_PATH = str(os.path.join(SCRIPTS_PATH, "builder").replace("\\", "/"))
|
||||||
DNALIB_PATH = os.path.join(SCRIPTS_PATH, "dnalib").replace("\\", "/")
|
DNALIB_PATH = str(os.path.join(SCRIPTS_PATH, "dnalib").replace("\\", "/"))
|
||||||
UI_PATH = os.path.join(SCRIPTS_PATH, "ui").replace("\\", "/")
|
UI_PATH = str(os.path.join(SCRIPTS_PATH, "ui").replace("\\", "/"))
|
||||||
UTILS_PATH = os.path.join(SCRIPTS_PATH, "utils").replace("\\", "/")
|
UTILS_PATH = str(os.path.join(SCRIPTS_PATH, "utils").replace("\\", "/"))
|
||||||
|
|
||||||
#FILES
|
# FILES
|
||||||
TOOL_MAIN_SCRIPT = os.path.join(SCRIPTS_PATH, f"{TOOL_NAME}.py").replace("\\", "/")
|
TOOL_MAIN_SCRIPT = str(os.path.join(SCRIPTS_PATH, f"{TOOL_NAME}.py").replace("\\", "/"))
|
||||||
TOOL_STYLE_FILE = os.path.join(STYLES_PATH, "style.qss").replace("\\", "/")
|
TOOL_STYLE_FILE = str(os.path.join(UI_PATH, "style.qss").replace("\\", "/"))
|
||||||
TOOL_ICON = os.path.join(ICONS_PATH, f"{TOOL_NAME}Logo.png").replace("\\", "/")
|
TOOL_ICON = str(os.path.join(ICONS_PATH, f"{TOOL_NAME}Logo.png").replace("\\", "/"))
|
||||||
TOOL_COMMAND_ICON = os.path.join(ICONS_PATH, "CommandButton.png").replace("\\", "/")
|
TOOL_COMMAND_ICON = str(os.path.join(ICONS_PATH, "CommandButton.png").replace("\\", "/"))
|
||||||
TOOL_MOD_FILENAME = f"{TOOL_NAME}.mod"
|
TOOL_MOD_FILENAME = str(f"{TOOL_NAME}.mod")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
validate_paths = {
|
||||||
|
ROOT_PATH,
|
||||||
|
SCRIPTS_PATH,
|
||||||
|
ICONS_PATH,
|
||||||
|
STYLES_PATH,
|
||||||
|
DNA_FILE_PATH,
|
||||||
|
DNA_IMG_PATH,
|
||||||
|
PLUGIN_PATH,
|
||||||
|
PYDNA_PATH,
|
||||||
|
DNACALIB_PATH,
|
||||||
|
BUILDER_PATH,
|
||||||
|
DNALIB_PATH,
|
||||||
|
UI_PATH,
|
||||||
|
UTILS_PATH
|
||||||
|
}
|
||||||
|
for i in validate_paths:
|
||||||
|
if not i in sys.path:
|
||||||
|
sys.path.append(i)
|
||||||
|
|
||||||
|
print("============================================")
|
||||||
|
print(f"TOOL_NAME: {TOOL_NAME}")
|
||||||
|
print(f"TOOL_VERSION: {TOOL_VERSION}")
|
||||||
|
print(f"TOOL_AUTHOR: {TOOL_AUTHOR}")
|
||||||
|
print(f"TOOL_LANG: {TOOL_LANG}")
|
||||||
|
print(f"TOOL_WSCL_NAME: {TOOL_WSCL_NAME}")
|
||||||
|
print(f"TOOL_HELP_URL: {TOOL_HELP_URL}")
|
||||||
|
|
||||||
print("TOOL_NAME",TOOL_NAME)
|
print(f"ROOT_PATH: {ROOT_PATH}")
|
||||||
print("TOOL_VERSION",TOOL_VERSION)
|
print(f"SCRIPTS_PATH: {SCRIPTS_PATH}")
|
||||||
print("TOOL_AUTHOR",TOOL_AUTHOR)
|
print(f"ICONS_PATH: {ICONS_PATH}")
|
||||||
print("TOOL_LANG",TOOL_LANG)
|
print(f"STYLES_PATH: {STYLES_PATH}")
|
||||||
print("TOOL_WSCL_NAME",TOOL_WSCL_NAME)
|
print(f"DNA_FILE_PATH: {DNA_FILE_PATH}")
|
||||||
print("TOOL_HELP_URL",TOOL_HELP_URL)
|
print(f"DNA_IMG_PATH: {DNA_IMG_PATH}")
|
||||||
|
|
||||||
print("ROOT_PATH",ROOT_PATH)
|
print(f"PLUGIN_PATH: {PLUGIN_PATH}")
|
||||||
print("SCRIPTS_PATH",SCRIPTS_PATH)
|
print(f"PYDNA_PATH: {PYDNA_PATH}")
|
||||||
print("ICONS_PATH",ICONS_PATH)
|
|
||||||
print("STYLES_PATH",STYLES_PATH)
|
|
||||||
print("DNA_FILE_PATH",DNA_FILE_PATH)
|
|
||||||
print("DNA_IMG_PATH",DNA_IMG_PATH)
|
|
||||||
|
|
||||||
print("PLUGIN_PATH",PLUGIN_PATH)
|
print(f"DNACALIB_PATH: {DNACALIB_PATH}")
|
||||||
print("PYDNA_PATH",PYDNA_PATH)
|
print(f"BUILDER_PATH: {BUILDER_PATH}")
|
||||||
|
print(f"DNALIB_PATH: {DNALIB_PATH}")
|
||||||
|
print(f"UI_PATH: {UI_PATH}")
|
||||||
|
print(f"UTILS_PATH: {UTILS_PATH}")
|
||||||
|
|
||||||
print("DNACALIB_PATH",DNACALIB_PATH)
|
print(f"TOOL_MAIN_SCRIPT: {TOOL_MAIN_SCRIPT}")
|
||||||
print("BUILDER_PATH",BUILDER_PATH)
|
print(f"TOOL_STYLE_FILE: {TOOL_STYLE_FILE}")
|
||||||
print("DNALIB_PATH",DNALIB_PATH)
|
print(f"TOOL_ICON: {TOOL_ICON}")
|
||||||
print("UI_PATH",UI_PATH)
|
print(f"TOOL_COMMAND_ICON: {TOOL_COMMAND_ICON}")
|
||||||
print("UTILS_PATH",UTILS_PATH)
|
print(f"TOOL_MOD_FILENAME: {TOOL_MOD_FILENAME}")
|
||||||
|
|
||||||
print("TOOL_MAIN_SCRIPT",TOOL_MAIN_SCRIPT)
|
|
||||||
print("TOOL_STYLE_FILE",TOOL_STYLE_FILE)
|
|
||||||
print("TOOL_ICON",TOOL_ICON)
|
|
||||||
print("TOOL_COMMAND_ICON",TOOL_COMMAND_ICON)
|
|
||||||
print("TOOL_MOD_FILENAME",TOOL_MOD_FILENAME)
|
|
||||||
|
|
||||||
|
@ -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 样式 */
|
||||||
QPushButton {
|
QPushButton {
|
||||||
background-color: #2A2A2A;
|
background-color: #D0D0D0;
|
||||||
color: #CCCCCC;
|
color: #303030;
|
||||||
border-radius: 3px;
|
border-radius: 10px;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
min-width: 80px;
|
min-width: 80px;
|
||||||
border: 1px solid #444444;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QPushButton:hover {
|
QPushButton:hover {
|
||||||
background-color: #3A3A3A;
|
background-color: #E0E0E0;
|
||||||
border-color: #555555;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QPushButton:pressed {
|
QPushButton:pressed {
|
||||||
background-color: #1A1A1A;
|
background-color: #C0C0C0;
|
||||||
border-color: #333333;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QPushButton:disabled {
|
QPushButton:disabled {
|
||||||
@ -26,7 +23,7 @@ QPushButton:disabled {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* 单独的消息按钮样式(可选) */
|
/* 单独的消息按钮样式(可选) */
|
||||||
.messageButton {
|
QPushButton.message-button {
|
||||||
background-color: #B0B0B0;
|
background-color: #B0B0B0;
|
||||||
color: #303030;
|
color: #303030;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
@ -35,11 +32,11 @@ QPushButton:disabled {
|
|||||||
min-width: 80px;
|
min-width: 80px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.messageButton:hover {
|
QPushButton.message-button:hover {
|
||||||
background-color: #C0C0C0;
|
background-color: #C0C0C0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.messageButton:pressed {
|
QPushButton.message-button:pressed {
|
||||||
background-color: #A0A0A0;
|
background-color: #A0A0A0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -277,3 +274,13 @@ QMenu::item:selected {
|
|||||||
QMenu::item:pressed {
|
QMenu::item:pressed {
|
||||||
background-color: #333333;
|
background-color: #333333;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 其他控件样式 */
|
||||||
|
QDialog {
|
||||||
|
background-color: #404040;
|
||||||
|
color: #E0E0E0;
|
||||||
|
}
|
||||||
|
|
||||||
|
QLabel {
|
||||||
|
color: #E0E0E0;
|
||||||
|
}
|
||||||
|
1635
scripts/ui/widgets.py
Normal file
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