diff --git a/DevGoals.md b/DevGoals.md deleted file mode 100644 index 479c4b1..0000000 --- a/DevGoals.md +++ /dev/null @@ -1,1430 +0,0 @@ -# 项目目标 - - 我想做一个Maya的Metahuman自定义的插件, - - 语言:基于Python - - Maya版本:2022, 2023, 2024, 2025 - -## 项目描述 - - 本项目是一个Maya插件,主要功能是提供与MetaHuman相同拓扑的模型或者自定义的3d模型以来完成自定义绑定,编辑DNA,校准骨骼位置,保存DNA,载入DNA,导出fbx,保存DNA文件, 编辑BlendShape,等功能。 - -## 这个插件主要功能: - - 提供与MetaHuman相同拓扑的模型或者自定义的3d模型以来完成自定义绑定,编辑DNA,校准骨骼位置,保存DNA,载入DNA,导出fbx,保存DNA文件, 编辑BlendShape,等功能。 - -## 注意Reference路径不参与参见功能实现,只作为参考。Reference只作为参考,可以从中拷贝必要的文件到当前项目中 - -## 代码实现: - - 根据Maya和Python版本来获取plugin的路径,并尽可能使用PySide编写UI,要保证PySide的通用性,使用单独的ccs文件来定义定义样式。 - - 根据Maya不同的版本来定义PySide的UI定义,版本通用性参考MSLiveLink。 - -## 参考代码: - - DNA\_Calibration中主要参考DNA编辑等功能更,SuperRigigng主要参考UI样式并获取对应的功能的实现逻辑,MSLiveLink主要参开DNA编辑和文件处理方式。 - -## 产品功能对标: - - DNA Calibration Document : https://epicgames.github.io/MetaHuman-DNA-Calibration/index.html - - MetaHuman-DNA-Calibration 代码:https://github.com/EpicGames/MetaHuman-DNA-Calibration - - SuperRigging:https://docs.pointart.net/ - - AnimCraft:https://geekdaxue.co/read/animcraft@cn/ - -## 代码初始结构: - -. - -├── config - -│ └── \_\_init\_\_.py - -│ └── data.py - -├── plugins - -│ └── Linux - -│ │ ├── 2022 - -│ │ │ ├── \_py3dnacalib.so - -│ │ │ ├── dnacalib.py - -│ │ │ ├── libdnacalib.so.6 - -│ │ │ ├── libembeddedRL4.so - -│ │ │ ├── libembeddedRL4.so.8 - -│ │ │ ├── libembeddedRL4.so.8.0.8 - -│ │ │ ├── MayaUE4RBFPlugin2022.mll - -│ │ │ ├── MayaUERBFPlugin.mll - -│ │ ├── 2023 - -│ │ │ ├── \_py3dnacalib.so - -│ │ │ ├── dnacalib.py - -│ │ │ ├── libdnacalib.so.6 - -│ │ │ ├── libembeddedRL4.so - -│ │ │ ├── libembeddedRL4.so.8 - -│ │ │ ├── libembeddedRL4.so.8.0.8 - -│ │ │ ├── MayaUE4RBFPlugin2023.mll - -│ │ │ ├── MayaUERBFPlugin.mll - -│ │ ├── 2024 - -│ │ │ ├── \_py3dnacalib.so - -│ │ │ ├── dnacalib.py - -│ │ │ ├── libdnacalib.so.6 - -│ │ │ ├── libembeddedRL4.so - -│ │ │ ├── MayaUERBFPlugin.mll - -│ │ ├── 2025 - -│ │ │ ├── \_py3dnacalib.so - -│ │ │ ├── dnacalib.py - -│ │ │ ├── embeddedRL4.so - -│ │ │ ├── libdnacalib.so.6 - -│ │ │ ├── MayaUERBFPlugin.mll - -│ │ ├── pydna - -│ │ │ ├── python3 - -│ │ │ │ ├── \_py3dna.so - -│ │ │ │ ├── dna.py - -│ │ │ │ ├── libdna.so.7.1.0 - -│ │ │ ├── python311 - -│ │ │ │ ├── \_py3dna.so - -│ │ │ │ ├── dna.py - -│ │ │ │ ├── libdna.so.7 - -│ │ │ ├── python397 - -│ │ │ │ ├── \_py3dna.so - -│ │ │ │ ├── dna.py - -│ │ │ │ ├── libdna.so.7.1.0 - -│ │ │ ├── python3108 - -│ │ │ │ ├── \_py3dna.so - -│ │ │ │ ├── dna.py - -│ │ │ │ ├── libdna.so.7.1.0 - -│ └── Windows - -│ │ ├── 2022 - -│ │ │ ├── \_py3dnacalib.pyd - -│ │ │ ├── dnacalib.dll - -│ │ │ ├── dnacalib.py - -│ │ │ ├── embeddedRL4.mll - -│ │ │ ├── MayaUE4RBFPlugin2022.mll - -│ │ │ ├── MayaUERBFPlugin.mll - -│ │ ├── 2023 - -│ │ │ ├── \_py3dnacalib.pyd - -│ │ │ ├── dnacalib.dll - -│ │ │ ├── dnacalib.py - -│ │ │ ├── embeddedRL4.mll - -│ │ │ ├── MayaUE4RBFPlugin2023.mll - -│ │ │ ├── MayaUERBFPlugin.mll - -│ │ ├── 2024 - -│ │ │ ├── \_py3dnacalib.pyd - -│ │ │ ├── dnacalib.dll - -│ │ │ ├── dnacalib.py - -│ │ │ ├── embeddedRL4.mll - -│ │ │ ├── MayaUERBFPlugin.mll - -│ │ ├── 2025 - -│ │ │ ├── \_py3dnacalib.pyd - -│ │ │ ├── dnacalib.dll - -│ │ │ ├── dnacalib.py - -│ │ │ ├── embeddedRL4.mll - -│ │ │ ├── MayaUERBFPlugin.mll - -│ │ ├── pydna - -│ │ │ ├── python3 - -│ │ │ │ ├── \_py3dna.pyd - -│ │ │ │ ├── dna.dll - -│ │ │ │ ├── dna.py - -│ │ │ ├── python311 - -│ │ │ │ ├── \_py3dna.pyd - -│ │ │ │ ├── \_py3dna9\_4\_3.pyd - -│ │ │ │ ├── dna.dll - -│ │ │ │ ├── dna.py - -│ │ │ │ ├── dna9\_4\_3.dll - -│ │ │ │ ├── polyalloc1\_3\_12.dll - -│ │ │ │ ├── statuscode1\_2\_6.dll - -│ │ │ │ ├── trio4\_0\_16.dll - -│ │ │ ├── python397 - -│ │ │ │ ├── \_py3dna.pyd - -│ │ │ │ ├── dna.dll - -│ │ │ │ ├── dna.py - -│ │ │ ├── python3108 - -│ │ │ │ ├── \_py3dna.pyd - -│ │ │ │ ├── dna.dll - -│ │ │ │ ├── dna.py - -│ │ │ ├── \_\_init\_\_.py - -├── resources - -│ ├── icons - -│ │ ├── ARKit52.png - -│ │ ├── automatic\_grouping.png - -│ │ ├── backward.png - -│ │ ├── behaviour.png - -│ │ ├── blendRaw.png - -│ │ ├── blendShape\_current.png - -│ │ ├── blendShape.png - -│ │ ├── change\_password.png - -│ │ ├── chinese.png - -│ │ ├── clone\_blendShape.png - -│ │ ├── clothing\_weight.png - -│ │ ├── color.png - -│ │ ├── CommandButton.png - -│ │ ├── configuration.png - -│ │ ├── connect.png - -│ │ ├── controller.png - -│ │ ├── copy\_skin.png - -│ │ ├── copy.png - -│ │ ├── create\_body\_ctrl.png - -│ │ ├── create\_lod.png - -│ │ ├── ctrl\_hide.png - -│ │ ├── definition.png - -│ │ ├── delete.png - -│ │ ├── detector.png - -│ │ ├── disconnect.png - -│ │ ├── english.png - -│ │ ├── exit.png - -│ │ ├── export\_skin.png - -│ │ ├── export.png - -│ │ ├── expression.png - -│ │ ├── expressions\_blend.png - -│ │ ├── expressions\_current.png - -│ │ ├── expressions.png - -│ │ ├── forward.png - -│ │ ├── help.png - -│ │ ├── import\_body\_anim.png - -│ │ ├── import\_face\_anim.png - -│ │ ├── import\_skin.png - -│ │ ├── import.png - -│ │ ├── joint.png - -│ │ ├── load\_meshes.png - -│ │ ├── loading.png - -│ │ ├── locator.png - -│ │ ├── lock.png - -│ │ ├── mark.png - -│ │ ├── meshes.png - -│ │ ├── message.png - -│ │ ├── MetaFusionLogo.png - -│ │ ├── mirror.png - -│ │ ├── mirrorL.png - -│ │ ├── mirrorR.png - -│ │ ├── motion\_apply.png - -│ │ ├── open\_camera.png - -│ │ ├── open.png - -│ │ ├── pause.png - -│ │ ├── play.png - -│ │ ├── plus.png - -│ │ ├── pose\_A\_To\_T.png - -│ │ ├── pose\_T\_To\_A.png - -│ │ ├── presets.png - -│ │ ├── psd.png - -│ │ ├── rebuildTargets.png - -│ │ ├── reduce.png - -│ │ ├── rename.png - -│ │ ├── repair\_normals.png - -│ │ ├── repair\_vertex\_order.png - -│ │ ├── reset.png - -│ │ ├── resetname.png - -│ │ ├── return.png - -│ │ ├── save\_new.png - -│ │ ├── save.png - -│ │ ├── search.png - -│ │ ├── set\_current.png - -│ │ ├── set\_no.png - -│ │ ├── set\_ok.png - -│ │ ├── set\_range.png - -│ │ ├── settings.png - -│ │ ├── standardized\_naming.png - -│ │ ├── stop.png - -│ │ ├── supplement\_meshes.png - -│ │ ├── symmetry.png - -│ │ ├── target.png - -│ │ ├── transfer\_maps.png - -│ │ ├── unmark\_all.png - -│ │ ├── unreal.png - -│ │ ├── update.png - -│ │ ├── user\_login.png - -│ │ ├── visible.png - -│ │ ├── warning.png - -│ ├── styles - -│ │ ├── style.qss - -├── scripts - -│ ├── ui - -│ ├── MetaFusion.py - -├── CleanPycache.bat - -├── Install.mel - -├── Install.py - -├── README.md - -## 重要代码内容: - -├── Install.mel - -```Python -global proc install() -{ - string $scriptPath = `whatIs install`; - string $dirPath = `substring $scriptPath 25 (size($scriptPath))`; - $dirPath = `dirname $dirPath`; - string $pythonPath = $dirPath + "/Install.py"; - $pythonPath = substituteAllString($pythonPath, "\\", "/"); - - string $pythonCmd = "import os, sys\n"; - $pythonCmd += "INSTALL_PATH = r'" + $pythonPath + "'\n"; - $pythonCmd += "sys.path.append(os.path.dirname(INSTALL_PATH))\n"; - $pythonCmd += "import Install\n"; - $pythonCmd += "Install.main()\n"; - - python($pythonCmd); -} - -install(); -``` - -├── Install.py - -```Python -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -#===================================== 1. Module Imports ===================================== -# Standard library imports -import os -import sys -import webbrowser - -# Maya imports -import maya.mel as mel -import maya.cmds as cmds -import maya.OpenMayaUI as omui - -# Qt imports -from PySide2 import QtWidgets, QtGui, QtCore -from shiboken2 import wrapInstance - -# Custom imports -from config import data -QtCore, QtGui, QtWidgets = data.Qt() - -#===================================== 2. Global Variables ===================================== -ROOT_PATH = data.ROOT_PATH -TOOL_NAME = data.TOOL_NAME -TOOL_VERSION = data.TOOL_VERSION -TOOL_AUTHOR = data.TOOL_AUTHOR -TOOL_LANG = data.TOOL_LANG -TOOL_WSCL_NAME = data.TOOL_WSCL_NAME -TOOL_HELP_URL = data.TOOL_HELP_URL -SCRIPTS_PATH = data.SCRIPTS_PATH -ICONS_PATH = data.ICONS_PATH - -TOOL_MAIN_SCRIPT = data.TOOL_MAIN_SCRIPT -TOOL_MOD_FILENAME = data.TOOL_MOD_FILENAME -TOOL_ICON = data.TOOL_ICON -TOOL_COMMAND_ICON = data.TOOL_COMMAND_ICON - -#===================================== 3. Utility Functions ===================================== -def maya_main_window(): - """Get Maya main window as QWidget""" - main_window_ptr = omui.MQtUtil.mainWindow() - return wrapInstance(int(main_window_ptr), QtWidgets.QWidget) - -def ensure_directory(directory_path): - """Ensure directory exists, create if not""" - if not os.path.exists(directory_path): - os.makedirs(directory_path) - print(f"Created directory: {directory_path}") - return directory_path - -def get_maya_modules_dir(): - """Get Maya modules directory path""" - maya_app_dir = cmds.internalVar(userAppDir=True) - return ensure_directory(os.path.join(maya_app_dir, "modules")) - -#===================================== 4. UI Component Classes ===================================== -class SetButton(QtWidgets.QPushButton): - """Custom styled button for installation interface""" - def __init__(self, text): - super(SetButton, self).__init__(text) - -#===================================== 5. Main Window Class ===================================== -class InstallDialog(QtWidgets.QDialog): - def __init__(self, parent=maya_main_window()): - super(InstallDialog, self).__init__(parent) - self.load_stylesheet() - self.setup_ui() - - def load_stylesheet(self): - """加载 QSS 样式文件""" - try: - style_file = data.TOOL_STYLE_FILE - if os.path.exists(style_file): - with open(style_file, 'r') as f: - self.setStyleSheet(f.read()) - else: - print(f"Warning: Style file not found: {style_file}") - except Exception as e: - print(f"Error loading stylesheet: {e}") - - def setup_ui(self): - """Initialize and setup UI components""" - self.setWindowTitle(f"{TOOL_NAME} Installation") - self.setFixedSize(220, 120) - self.setup_window_icon() - self.create_widgets() - self.create_layouts() - self.create_connections() - - def setup_window_icon(self): - """Setup window icon if available""" - if os.path.exists(TOOL_ICON): - self.setWindowIcon(QtGui.QIcon(TOOL_ICON)) - else: - print(f"Warning: Icon file not found: {TOOL_ICON}") - - #----------------- 5.1 UI Methods ----------------- - def create_widgets(self): - self.new_shelf_toggle = QtWidgets.QCheckBox(f"{TOOL_NAME} Installation") - self.install_button = SetButton("Install " + TOOL_NAME) - self.uninstall_button = SetButton("Uninstall " + TOOL_NAME) - - def create_layouts(self): - main_layout = QtWidgets.QVBoxLayout(self) - main_layout.setContentsMargins(10, 2, 10, 5) - main_layout.setSpacing(5) - - header_layout = QtWidgets.QHBoxLayout() - header_layout.setSpacing(5) - - welcome_label = QtWidgets.QLabel("Welcome to " + TOOL_NAME + "!") - welcome_label.setStyleSheet("font-size: 11px; padding: 0px; margin: 0px;") - header_layout.addWidget(welcome_label) - header_layout.addStretch() - - main_layout.addLayout(header_layout) - main_layout.addWidget(self.install_button) - main_layout.addWidget(self.uninstall_button) - - self.install_button.setFixedHeight(30) - self.uninstall_button.setFixedHeight(30) - - def create_connections(self): - self.install_button.clicked.connect(self.install) - self.uninstall_button.clicked.connect(self.uninstall) - - def create_styled_message_box(self, title, text): - msg_box = QtWidgets.QMessageBox(self) - msg_box.setWindowTitle(title) - msg_box.setText(text) - msg_box.setStandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) - msg_box.setStyleSheet(self.styleSheet()) - return msg_box - - #----------------- 5.2 Event Handler Methods ----------------- - def event(self, event): - if event.type() == QtCore.QEvent.EnterWhatsThisMode: - QtWidgets.QWhatsThis.leaveWhatsThisMode() - self.open_help_url() - return True - return QtWidgets.QDialog.event(self, event) - - def closeEvent(self, event): - """Handle window close event""" - try: - super(InstallDialog, self).closeEvent(event) - except Exception as e: - print(f"Error closing window: {e}") - event.accept() - - def helpEvent(self, event): - self.open_help_url() - event.accept() - - #----------------- 5.3 Utility Methods ----------------- - def open_help_url(self): - webbrowser.open(TOOL_HELP_URL) - QtWidgets.QApplication.restoreOverrideCursor() - - def get_script_path(self): - maya_script = mel.eval('getenv("MAYA_SCRIPT_NAME")') - if maya_script and os.path.exists(maya_script): - return os.path.dirname(maya_script) - - for sys_path in sys.path: - install_path = os.path.join(sys_path, "install.py") - if os.path.exists(install_path): - return os.path.dirname(install_path) - - return os.getcwd() - - #----------------- 5.4 Installation Methods ----------------- - def install(self): - """Handle install request with error handling""" - if not self._validate_paths(): - return - - msg_box = self.create_styled_message_box( - "Confirm Installation", - f"Are you sure you want to install {TOOL_NAME}?" - ) - if msg_box.exec_() == QtWidgets.QMessageBox.Yes: - try: - self.install_tool() - self.close() - except Exception as e: - error_msg = f"Error during installation: {e}" - print(error_msg) - QtWidgets.QMessageBox.critical(self, "Error", error_msg) - - def uninstall(self, *args): - """Handle uninstall request""" - msg_box = self.create_styled_message_box( - "Confirm Uninstallation", - f"Are you sure you want to uninstall {TOOL_NAME}?" - ) - - if msg_box.exec_() == QtWidgets.QMessageBox.Yes: - try: - self.uninstall_tool() - self.close() - except Exception as e: - error_msg = f"Error during uninstallation: {e}" - print(error_msg) - QtWidgets.QMessageBox.critical(self, "Error", error_msg) - else: - print("Uninstallation cancelled") - - def create_mod_file(self): - """Create or update the .mod file for Maya""" - modules_dir = get_maya_modules_dir() - mod_content = f"""+ {TOOL_NAME} {TOOL_VERSION} {ROOT_PATH} - scripts: {SCRIPTS_PATH} - """ - mod_file_path = os.path.join(modules_dir, TOOL_MOD_FILENAME) - self._write_mod_file(mod_file_path, mod_content) - - def _write_mod_file(self, file_path, content): - """Helper method to write .mod file""" - try: - with open(file_path, "w") as f: - f.write(content) - print(f"Successfully created/updated: {file_path}") - except Exception as e: - error_msg = f"Error writing .mod file: {e}" - print(error_msg) - QtWidgets.QMessageBox.critical(self, "Error", error_msg) - - def uninstall_mod_file(self): - modules_dir = get_maya_modules_dir() - mod_file_path = os.path.join(modules_dir, TOOL_MOD_FILENAME) - if os.path.exists(mod_file_path): - try: - os.remove(mod_file_path) - print(f"{TOOL_NAME}.mod file deleted") - except Exception as e: - print(f"Error deleting {TOOL_NAME}.mod file: {e}") - - def clean_existing_buttons(self): - if cmds.shelfLayout(TOOL_NAME, exists=True): - buttons = cmds.shelfLayout(TOOL_NAME, query=True, childArray=True) or [] - for btn in buttons: - if cmds.shelfButton(btn, query=True, exists=True): - label = cmds.shelfButton(btn, query=True, label=True) - if label == TOOL_NAME: - cmds.deleteUI(btn) - print(f"Deleted existing {TOOL_NAME} button: {btn}") - - def install_tool(self): - """Install the tool to Maya""" - if not os.path.exists(SCRIPTS_PATH): - print(f"Error: Scripts path does not exist: {SCRIPTS_PATH}") - return - - if not os.path.exists(TOOL_MAIN_SCRIPT): - print(f"Error: Main script file not found: {TOOL_MAIN_SCRIPT}") - return - - # Add scripts path to Python path - if SCRIPTS_PATH not in sys.path: - sys.path.insert(0, SCRIPTS_PATH) - - # Create shelf and button - self._create_shelf_button() - self.create_mod_file() - - # Switch to the newly created shelf - try: - cmds.shelfTabLayout("ShelfLayout", edit=True, selectTab=TOOL_NAME) - print(f"Switched to {TOOL_NAME} shelf") - except Exception as e: - print(f"Error switching to {TOOL_NAME} shelf: {e}") - - self._show_install_success_message() - - def _create_shelf_button(self): - """Create shelf button for the tool""" - shelf_layout = mel.eval('$tmpVar=$gShelfTopLevel') - - # Create shelf if not exists - if not cmds.shelfLayout(TOOL_NAME, exists=True): - cmds.shelfLayout(TOOL_NAME, parent=shelf_layout) - - # Clean existing buttons - self.clean_existing_buttons() - - # Create new button - icon_path = TOOL_ICON if os.path.exists(TOOL_ICON) else TOOL_COMMAND_ICON - - command = self._get_shelf_button_command() - - cmds.shelfButton( - parent=TOOL_NAME, - image1=icon_path, - label=TOOL_NAME, - command=command, - sourceType="python", - annotation=f"{TOOL_NAME} {TOOL_VERSION}", - noDefaultPopup=True, - style="iconOnly" - ) - - def _get_shelf_button_command(self): - """Get the command string for shelf button""" - return f""" -import sys -import os -SCRIPTS_PATH = r'{SCRIPTS_PATH}' -if SCRIPTS_PATH not in sys.path: - sys.path.insert(0, SCRIPTS_PATH) -os.chdir(SCRIPTS_PATH) -try: - import {TOOL_NAME} - {TOOL_NAME}.show() -except ImportError as e: - print("Error importing {TOOL_NAME}:", str(e)) - print("Scripts path:", SCRIPTS_PATH) - print("sys.path:", sys.path) - print("Contents of Scripts folder:", os.listdir(SCRIPTS_PATH)) -""" - - def uninstall_tool(self): - """Uninstall the tool from Maya""" - window_name = f"{TOOL_NAME}Window" - dock_name = f"{TOOL_NAME}WindowDock" - shelf_file = f"shelf_{TOOL_NAME}.mel" - - if cmds.window(window_name, exists=True): - try: - cmds.deleteUI(window_name) - except Exception as e: - print(f"Error closing {TOOL_NAME} window: {e}") - - if cmds.dockControl(dock_name, exists=True): - try: - cmds.deleteUI(dock_name) - except Exception as e: - print(f"Error closing docked {TOOL_NAME} window: {e}") - - self.uninstall_mod_file() - - # Get the current shelf before removing it - current_shelf = cmds.shelfTabLayout("ShelfLayout", query=True, selectTab=True) - - # Delete Shelves and Buttons - if cmds.shelfLayout(TOOL_NAME, exists=True): - try: - cmds.deleteUI(TOOL_NAME, layout=True) - except Exception as e: - print(f"Error deleting {TOOL_NAME} shelf: {e}") - - self._clean_all_shelf_buttons() - - # Remove from Python path - if SCRIPTS_PATH in sys.path: - sys.path.remove(SCRIPTS_PATH) - - # Deleting Shelf Files - shelf_path = os.path.join( - cmds.internalVar(userAppDir=True), - cmds.about(version=True), - "prefs", - "shelves", - f"shelf_{TOOL_NAME}.mel" - ) - - if os.path.exists(shelf_path): - try: - os.remove(shelf_path) - except Exception as e: - print(f"Error deleting shelf file: {e}") - - # If the current tool shelf is a deleted tool shelf, switch to another tool shelf - if current_shelf == TOOL_NAME: - shelves = cmds.shelfTabLayout("ShelfLayout", query=True, childArray=True) - if shelves and len(shelves) > 0: - cmds.shelfTabLayout("ShelfLayout", edit=True, selectTab=shelves[0]) - - self._show_uninstall_success_message() - - def _clean_all_shelf_buttons(self): - """Clean up all shelf buttons related to the tool""" - all_shelves = cmds.shelfTabLayout("ShelfLayout", query=True, childArray=True) or [] - for shelf in all_shelves: - shelf_buttons = cmds.shelfLayout(shelf, query=True, childArray=True) or [] - for btn in shelf_buttons: - if cmds.shelfButton(btn, query=True, exists=True): - if cmds.shelfButton(btn, query=True, label=True) == TOOL_NAME: - cmds.deleteUI(btn) - - def _show_uninstall_success_message(self): - """Show uninstallation success message""" - msg_box = QtWidgets.QMessageBox() - msg_box.setWindowTitle("Uninstallation Successful") - msg_box.setText(f"{TOOL_NAME} has been successfully uninstalled!") - msg_box.setStandardButtons(QtWidgets.QMessageBox.Ok) - msg_box.setWindowIcon(QtGui.QIcon(TOOL_ICON)) - msg_box.setStyleSheet(self.styleSheet()) - msg_box.exec_() - - def _show_install_success_message(self): - msg_box = QtWidgets.QMessageBox() - msg_box.setWindowTitle("Installation Successful") - msg_box.setText(f"{TOOL_NAME} has been successfully installed!") - msg_box.setStandardButtons(QtWidgets.QMessageBox.Ok) - msg_box.setWindowIcon(QtGui.QIcon(TOOL_ICON)) - msg_box.setStyleSheet(self.styleSheet()) - msg_box.exec_() - - def _validate_paths(self): - """Validate all required paths exist""" - paths = { - "Root": ROOT_PATH, - "Scripts": SCRIPTS_PATH, - "Icons": ICONS_PATH - } - - for name, path in paths.items(): - if not os.path.exists(path): - error_msg = f"Error: {name} path does not exist: {path}" - print(error_msg) - QtWidgets.QMessageBox.critical(self, "Error", error_msg) - return False - return True - - def _log(self, message, error=False): - """Log messages with timestamp""" - from datetime import datetime - timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - log_message = f"[{timestamp}] {message}" - print(log_message) - if error: - QtWidgets.QMessageBox.critical(self, "Error", message) - - def _load_mel_shelf(self): - """Load mel shelf file with error handling""" - try: - mel.eval(f'loadNewShelf "shelf_{TOOL_NAME}.mel"') - except Exception as e: - self._log(f"Error loading shelf file: {e}", error=True) - -#===================================== 6. Main Function ===================================== -def main(): - """Main entry point for the installer""" - try: - dialog = InstallDialog() - dialog.show() - except Exception as e: - print(f"Error launching installer: {e}") - return -1 - return dialog - -if __name__ == "__main__": - main() -``` - -│ ├── styles - -│ │ ├── style.qss - -```Python -/* 全局 QPushButton 样式 */ -QPushButton { - background-color: #D0D0D0; - color: #303030; - border-radius: 10px; - padding: 5px; - font-weight: bold; - min-width: 80px; -} - -QPushButton:hover { - background-color: #E0E0E0; -} - -QPushButton:pressed { - background-color: #C0C0C0; -} - -/* 单独的消息按钮样式(可选) */ -.messageButton { - background-color: #B0B0B0; - color: #303030; - border-radius: 10px; - padding: 5px; - font-weight: bold; - min-width: 80px; -} - -.messageButton:hover { - background-color: #C0C0C0; -} - -.messageButton:pressed { - background-color: #A0A0A0; -} - -/* MetaFusion 深色主题样式 */ - -/* 主窗口样式 */ -QMainWindow { - background-color: #333333; - color: #CCCCCC; -} - -/* 菜单栏样式 */ -QMenuBar { - background-color: #333333; - color: #CCCCCC; - border-bottom: 1px solid #222222; -} - -QMenuBar::item { - background-color: transparent; - padding: 4px 8px; -} - -QMenuBar::item:selected { - background-color: #444444; -} - -/* 工具栏样式 */ -QToolBar { - background-color: #333333; - border: none; - padding: 3px; -} - -QToolButton { - background-color: transparent; - border: 1px solid transparent; - border-radius: 2px; - padding: 4px; - margin: 1px; -} - -QToolButton:hover { - background-color: #444444; - border: 1px solid #555555; -} - -/* 标签页样式 */ -QTabWidget::pane { - border: 1px solid #222222; - background-color: #333333; -} - -QTabBar::tab { - background-color: #2A2A2A; - color: #CCCCCC; - padding: 5px 10px; - border: 1px solid #222222; - min-width: 80px; -} - -QTabBar::tab:selected { - background-color: #333333; - border-bottom: none; -} - -QTabBar::tab:hover:not(:selected) { - background-color: #3A3A3A; -} - -/* 列表和树形控件样式 */ -QTreeView, QListView { - background-color: #2A2A2A; - border: 1px solid #222222; - color: #CCCCCC; -} - -QTreeView::item:hover, QListView::item:hover { - background-color: #3A3A3A; -} - -QTreeView::item:selected, QListView::item:selected { - background-color: #444444; -} - -/* 输入框样式 */ -QLineEdit { - background-color: #2A2A2A; - border: 1px solid #222222; - border-radius: 2px; - color: #CCCCCC; - padding: 3px; -} - -/* 下拉框样式 */ -QComboBox { - background-color: #2A2A2A; - border: 1px solid #222222; - border-radius: 2px; - color: #CCCCCC; - padding: 3px; - min-width: 100px; -} - -QComboBox::drop-down { - border: none; - width: 20px; -} - -QComboBox::down-arrow { - border-image: url(:/resources/icons/down_arrow.png); - width: 12px; - height: 12px; -} - -/* 按钮样式 */ -QPushButton { - background-color: #2A2A2A; - border: 1px solid #222222; - border-radius: 2px; - color: #CCCCCC; - padding: 5px 15px; - min-width: 80px; -} - -QPushButton:hover { - background-color: #3A3A3A; - border: 1px solid #444444; -} - -QPushButton:pressed { - background-color: #222222; -} - -/* 滚动条样式 */ -QScrollBar:vertical { - background: #2A2A2A; - width: 10px; - margin: 0; -} - -QScrollBar::handle:vertical { - background: #444444; - min-height: 20px; - border-radius: 5px; -} - -QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { - height: 0px; -} - -QScrollBar:horizontal { - background: #2A2A2A; - height: 10px; - margin: 0; -} - -QScrollBar::handle:horizontal { - background: #444444; - min-width: 20px; - border-radius: 5px; -} - -QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal { - width: 0px; -} - -/* 分组框样式 */ -QGroupBox { - border: 1px solid #222222; - border-radius: 3px; - margin-top: 6px; - padding-top: 6px; - color: #CCCCCC; -} - -QGroupBox::title { - left: 7px; - padding: 0px 3px; -} - -/* 状态栏样式 */ -QStatusBar { - background-color: #333333; - color: #CCCCCC; -} - -/* 工具提示样式 */ -QToolTip { - background-color: #2A2A2A; - border: 1px solid #222222; - color: #CCCCCC; - padding: 3px; -} - -``` - -├── config - -│ └── \_\_init\_\_.py - -```Python -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -from . import * -``` - -│ └── data.py - -```Python -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import os -import sys -import maya.cmds as cmds - -#===================================== 2. Global Variables ===================================== -try: - ROOT_PATH = os.path.dirname(INSTALL_PATH).replace("\\", "/") -except NameError: - # __file__ 在 config 中,所以返回上一级目录即项目根目录 - ROOT_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))).replace("\\", "/") -TOOL_NAME = "MetaFusion" -TOOL_VERSION = "Beta v1.0.0" -TOOL_AUTHOR = "Virtuos" -TOOL_LANG = 'en_US' -TOOL_WSCL_NAME = f"{TOOL_NAME}WorkSpaceControl" -TOOL_HELP_URL = f"https://gitea.cgnico.com/CGNICO/{TOOL_NAME}/wiki" - -#===================================== 3. Paths ===================================== -# PATHS -SCRIPTS_PATH = os.path.join(ROOT_PATH, "scripts").replace("\\", "/") -ICONS_PATH = os.path.join(ROOT_PATH, "resources", "icons").replace("\\", "/") -STYLES_PATH = os.path.join(ROOT_PATH, "resources", "styles").replace("\\", "/") - -MAYA_VERSION = cmds.about(version=True) -SYSTEM_OS = cmds.about(os=True) -if MAYA_VERSION in ["2022", "2023", "2024", "2025"]: - PLUGIN_PATH = os.path.join(ROOT_PATH, "plugins", SYSTEM_OS, MAYA_VERSION).replace("\\", "/") -else: - print(f"MetaFusion is not supported on Maya {MAYA_VERSION}") - -PYTHON_VERSION = sys.version_info.major -if PYTHON_VERSION == 3: - PYDNA_PATH = os.path.join(ROOT_PATH, "plugins", SYSTEM_OS, "python3").replace("\\", "/") -elif PYTHON_VERSION == 3.11: - PYDNA_PATH = os.path.join(ROOT_PATH, "plugins", SYSTEM_OS, "python311").replace("\\", "/") -elif PYTHON_VERSION == 3.9: - PYDNA_PATH = os.path.join(ROOT_PATH, "plugins", SYSTEM_OS, "python397").replace("\\", "/") -elif PYTHON_VERSION == 3.10: - PYDNA_PATH = os.path.join(ROOT_PATH, "plugins", SYSTEM_OS, "python3108").replace("\\", "/") -else: - print(f"MetaFusion is not supported on Python {PYTHON_VERSION}") - - -#===================================== 3. Files ===================================== -# FILES -TOOL_MAIN_SCRIPT = os.path.join(SCRIPTS_PATH, f"{TOOL_NAME}.py").replace("\\", "/") -TOOL_STYLE_FILE = os.path.join(STYLES_PATH, "style.qss").replace("\\", "/") -TOOL_ICON = os.path.join(ICONS_PATH, f"{TOOL_NAME}Logo.png").replace("\\", "/") -TOOL_COMMAND_ICON = os.path.join(ICONS_PATH, "CommandButton.png").replace("\\", "/") -TOOL_MOD_FILENAME = f"{TOOL_NAME}.mod" - -#===================================== 4. Qt ===================================== -# Qt -def Qt(): - try: - from PySide import QtCore, QtGui, QtWidgets - return QtCore, QtGui, QtWidgets - except ImportError: - try: - from PySide2 import QtCore, QtGui, QtWidgets - return QtCore, QtGui, QtWidgets - except ImportError: - try: - from PySide3 import QtCore, QtGui, QtWidgets - return QtCore, QtGui, QtWidgets - except ImportError: - try: - from PySide4 import QtCore, QtGui, QtWidgets - return QtCore, QtGui, QtWidgets - except ImportError: - try: - from PySide5 import QtCore, QtGui, QtWidgets - return QtCore, QtGui, QtWidgets - except ImportError: - try: - from PySide6 import QtCore, QtGui, QtWidgets - return QtCore, QtGui, QtWidgets - except ImportError: - print("未找到 Qt 模块") - return None, None, None -``` - -├── scripts - -│ ├── MetaFusion.py - -```Python -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import os -import sys -from PySide2 import QtCore, QtGui, QtWidgets - -# 添加项目根目录到 Python 路径 -ROOT_DIR = os.path.dirname(os.path.dirname(__file__)) -if ROOT_DIR not in sys.path: - sys.path.insert(0, ROOT_DIR) -from config import data -QtCore, QtGui, QtWidgets = data.Qt() - -#===================================== 2. Global Variables ===================================== -TOOL_NAME = data.TOOL_NAME -TOOL_VERSION = data.TOOL_VERSION -TOOL_AUTHOR = data.TOOL_AUTHOR -TOOL_LANG = data.TOOL_LANG -TOOL_WSCL_NAME = data.TOOL_WSCL_NAME -TOOL_HELP_URL = data.TOOL_HELP_URL -SCRIPTS_PATH = data.SCRIPTS_PATH -ICONS_PATH = data.ICONS_PATH - -TOOL_MAIN_SCRIPT = data.TOOL_MAIN_SCRIPT -TOOL_MOD_FILENAME = data.TOOL_MOD_FILENAME -TOOL_ICON = data.TOOL_ICON -TOOL_COMMAND_ICON = data.TOOL_COMMAND_ICON - -main_window = None - -class MetaFusionWindow(QtWidgets.QMainWindow): - def __init__(self, parent=None): - super(MetaFusionWindow, self).__init__(parent) - self.setWindowTitle("MetaFusion") - self.resize(800, 600) - - # 加载样式表 - self.load_stylesheet() - - # 创建UI - self.setup_ui() - - def load_stylesheet(self): - """加载QSS样式表""" - style_file = os.path.join(os.path.dirname(os.path.dirname(__file__)), "resources", "styles", "style.qss") - if os.path.exists(style_file): - with open(style_file, 'r', encoding='utf-8') as f: - self.setStyleSheet(f.read()) - - def setup_ui(self): - """设置UI结构""" - # 创建中心部件 - central_widget = QtWidgets.QWidget() - self.setCentralWidget(central_widget) - - # 创建主布局 - main_layout = QtWidgets.QVBoxLayout(central_widget) - - # 创建菜单栏 - self.create_menu_bar() - - # 创建工具栏 - self.create_tool_bar() - - # 创建标签页 - self.create_tabs() - - def create_menu_bar(self): - """创建菜单栏""" - menubar = self.menuBar() - - # 文件菜单 - file_menu = menubar.addMenu("文件") - file_menu.addAction("新建") - file_menu.addAction("打开") - file_menu.addAction("保存") - file_menu.addSeparator() - file_menu.addAction("退出") - - # 编辑菜单 - edit_menu = menubar.addMenu("编辑") - edit_menu.addAction("撤销") - edit_menu.addAction("重做") - - # 帮助菜单 - help_menu = menubar.addMenu("帮助") - help_menu.addAction("关于") - - def create_tool_bar(self): - """创建工具栏""" - toolbar = self.addToolBar("主工具栏") - toolbar.setMovable(False) - - # 添加工具栏按钮 - toolbar.addAction(QtGui.QIcon(":/icons/new.png"), "新建") - toolbar.addAction(QtGui.QIcon(":/icons/open.png"), "打开") - toolbar.addAction(QtGui.QIcon(":/icons/save.png"), "保存") - - def create_tabs(self): - """创建标签页""" - self.tab_widget = QtWidgets.QTabWidget() - self.centralWidget().layout().addWidget(self.tab_widget) - - # 创建四个主要标签页 - self.model_tab = QtWidgets.QWidget() - self.rig_tab = QtWidgets.QWidget() - self.adjust_tab = QtWidgets.QWidget() - self.define_tab = QtWidgets.QWidget() - - # 添加标签页到标签页控件 - self.tab_widget.addTab(self.model_tab, "模型") - self.tab_widget.addTab(self.rig_tab, "绑定") - self.tab_widget.addTab(self.adjust_tab, "调整") - self.tab_widget.addTab(self.define_tab, "定义") - - # 设置各个标签页的内容 - self.setup_model_tab() - self.setup_rig_tab() - self.setup_adjust_tab() - self.setup_define_tab() - - def setup_model_tab(self): - """设置模型标签页内容""" - layout = QtWidgets.QVBoxLayout(self.model_tab) - # 在这里添加模型标签页的具体控件 - - def setup_rig_tab(self): - """设置绑定标签页内容""" - layout = QtWidgets.QVBoxLayout(self.rig_tab) - # 在这里添加绑定标签页的具体控件 - - def setup_adjust_tab(self): - """设置调整标签页内容""" - layout = QtWidgets.QVBoxLayout(self.adjust_tab) - # 在这里添加调整标签页的具体控件 - - def setup_define_tab(self): - """设置定义标签页内容""" - layout = QtWidgets.QVBoxLayout(self.define_tab) - # 在这里添加定义标签页的具体控件 - -def show(): - """显示主窗口""" - global main_window - - try: - main_window.close() - except: - pass - - main_window = MetaFusionWindow() - main_window.show() - return main_window - -if __name__ == "__main__": - app = QtWidgets.QApplication([]) - window = show() - app.exec_() -``` \ No newline at end of file diff --git a/Install.py b/Install.py index 322b7f7..99b957e 100644 --- a/Install.py +++ b/Install.py @@ -17,11 +17,11 @@ from PySide2 import QtWidgets, QtGui, QtCore from shiboken2 import wrapInstance # Custom imports -from config import data +from scripts.config import data QtCore, QtGui, QtWidgets = data.Qt() #===================================== 2. Global Variables ===================================== -ROOT_PATH = data.ROOT_PATH +ROOT_PATH = os.path.dirname(os.path.abspath(__file__)) TOOL_NAME = data.TOOL_NAME TOOL_VERSION = data.TOOL_VERSION TOOL_AUTHOR = data.TOOL_AUTHOR diff --git a/Reference/MSLiveLink/plug-ins/embeddedRL4.mll b/Reference/MSLiveLink/plug-ins/embeddedRL4.mll deleted file mode 100644 index 7ed5124..0000000 Binary files a/Reference/MSLiveLink/plug-ins/embeddedRL4.mll and /dev/null differ diff --git a/config/data.py b/config/data.py deleted file mode 100644 index 1490a03..0000000 --- a/config/data.py +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -#===================================== IMPORT ===================================== -import os -import sys -import maya.cmds as cmds - -def Qt(): - try: - from PySide2 import QtCore, QtGui, QtWidgets - print("加载PySide2") - return QtCore, QtGui, QtWidgets - except ImportError as e: - print(f"PySide2加载失败: {str(e)}") - try: - from PySide import QtCore, QtGui - QtWidgets = QtGui - print("加载PySide") - return QtCore, QtGui, QtWidgets - except ImportError as e: - cmds.warning(f"PySide加载失败: {str(e)}") - try: - from PySide6 import QtCore, QtGui, QtWidgets - print("加载PySide6") - return QtCore, QtGui, QtWidgets - except ImportError as e: - cmds.warning(f"PySide加载失败: {str(e)}") - return None, None, None - -QtCore, QtGui, QtWidgets = Qt() - -#===================================== BASE VARIBLES ===================================== -try: - ROOT_PATH = os.path.dirname(INSTALL_PATH).replace("\\", "/") -except NameError: - # __file__ 在 config 中,所以返回上一级目录即项目根目录 - ROOT_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))).replace("\\", "/") -TOOL_NAME = "MetaFusion" -TOOL_VERSION = "Beta v1.0.0" -TOOL_AUTHOR = "Virtuos" -TOOL_LANG = 'en_US' -TOOL_WSCL_NAME = f"{TOOL_NAME}WorkSpaceControl" -TOOL_HELP_URL = f"https://gitea.cgnico.com/CGNICO/{TOOL_NAME}/wiki" - -#===================================== PATHS ===================================== -# PATHS -SCRIPTS_PATH = os.path.join(ROOT_PATH, "scripts").replace("\\", "/") -ICONS_PATH = os.path.join(ROOT_PATH, "resources", "icons").replace("\\", "/") -STYLES_PATH = os.path.join(ROOT_PATH, "resources", "styles").replace("\\", "/") - -DNA_PATH = os.path.join(ROOT_PATH, "resources", "dna").replace("\\", "/") -DNA_IMG_PATH = os.path.join(ROOT_PATH, "resources", "img").replace("\\", "/") - -#===================================== 4. PYTON_VERSION_PATH ===================================== -SYSTEM_OS = "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 = sys.version -# 去掉小数点,比如3.10.8 → 3108 -PYTHON_VERSION = PYTHON_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_VERSION_DIR -PYTHON_VERSION_DIR = PYTHON_VERSION_MAP.get(version_tuple, "python3") # 如果找不到对应版本,默认使用 python3 - -#===================================== FILES ===================================== -# FILES -TOOL_MAIN_SCRIPT = os.path.join(SCRIPTS_PATH, f"{TOOL_NAME}.py").replace("\\", "/") -TOOL_STYLE_FILE = os.path.join(STYLES_PATH, "style.qss").replace("\\", "/") -TOOL_ICON = os.path.join(ICONS_PATH, f"{TOOL_NAME}Logo.png").replace("\\", "/") -TOOL_COMMAND_ICON = os.path.join(ICONS_PATH, "CommandButton.png").replace("\\", "/") -TOOL_MOD_FILENAME = f"{TOOL_NAME}.mod" - -# 生成最终路径 -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("\\", "/") - -#===================================== TOOLS ===================================== -# 新增工具路径 -BUILDER_PATH = os.path.join(SCRIPTS_PATH, "builder").replace("\\", "/") -DNALIB_PATH = os.path.join(SCRIPTS_PATH, "dnalib").replace("\\", "/") - - - - - - diff --git a/goals.md b/goals.md new file mode 100644 index 0000000..3c33bf0 --- /dev/null +++ b/goals.md @@ -0,0 +1,202 @@ +# 项目目标 + + 我想做一个Maya的Metahuman自定义的插件, + + 语言:基于Python + + Maya版本:2022, 2023, 2024, 2025 + +## 项目描述 + + 本项目是一个Maya插件,主要功能是提供与MetaHuman相同拓扑的模型或者自定义的3d模型以来完成自定义绑定,编辑DNA,校准骨骼位置,保存DNA,载入DNA,导出fbx,保存DNA文件, 编辑BlendShape,等功能。 + +## 这个插件主要功能: + + 提供与MetaHuman相同拓扑的模型或者自定义的3d模型以来完成自定义绑定,编辑DNA,校准骨骼位置,保存DNA,载入DNA,导出fbx,保存DNA文件, 编辑BlendShape,等功能。 + +## 注意Reference路径不参与参见功能实现,只作为参考。Reference只作为参考,可以从中拷贝必要的文件到当前项目中 + +## 代码实现: + + 根据Maya和Python版本来获取plugin的路径,并尽可能使用PySide编写UI,要保证PySide的通用性,使用单独的ccs文件来定义定义样式。 + + 根据Maya不同的版本来定义PySide的UI定义,版本通用性参考MSLiveLink。 + +## 参考代码: + + DNA_Calibration中主要参考DNA编辑等功能更,SuperRigigng主要参考UI样式并获取对应的功能的实现逻辑,MSLiveLink主要参开DNA编辑和文件处理方式。 + +## 产品功能对标: + + DNA Calibration Document : https://epicgames.github.io/MetaHuman-DNA-Calibration/index.html + + MetaHuman-DNA-Calibration 代码:https://github.com/EpicGames/MetaHuman-DNA-Calibration + + SuperRigging:https://docs.pointart.net/ + + AnimCraft:https://geekdaxue.co/read/animcraft@cn/ + +## 代码基本结构: + +. +├── dnacalib\ +│ ├── CMakeModulesExtra\ +│ ├── DNACalib\ +│ ├── PyDNA\ +│ ├── PyDNACalib\ +│ ├── SPyUS\ +│ ├── CMakeLists.txt +├── plugins\ +│ └── Linux\ +│ │ ├── 2022\ +│ │ │ ├── _py3dnacalib.so +│ │ │ ├── dnacalib.py +│ │ │ ├── libdnacalib.so.6 +│ │ │ ├── libembeddedRL4.so +│ │ │ ├── libembeddedRL4.so.8 +│ │ │ ├── libembeddedRL4.so.8.0.8 +│ │ │ ├── MayaUE4RBFPlugin2022.mll +│ │ │ ├── MayaUERBFPlugin.mll +│ │ ├── 2023\ +│ │ │ ├── _py3dnacalib.so +│ │ │ ├── dnacalib.py +│ │ │ ├── libdnacalib.so.6 +│ │ │ ├── libembeddedRL4.so +│ │ │ ├── libembeddedRL4.so.8 +│ │ │ ├── libembeddedRL4.so.8.0.8 +│ │ │ ├── MayaUE4RBFPlugin2023.mll +│ │ │ ├── MayaUERBFPlugin.mll +│ │ ├── 2024\ +│ │ │ ├── _py3dnacalib.so +│ │ │ ├── dnacalib.py +│ │ │ ├── libdnacalib.so.6 +│ │ │ ├── libembeddedRL4.so +│ │ │ ├── MayaUERBFPlugin.mll +│ │ ├── 2025\ +│ │ │ ├── _py3dnacalib.so +│ │ │ ├── dnacalib.py +│ │ │ ├── embeddedRL4.so +│ │ │ ├── libdnacalib.so.6 +│ │ │ ├── MayaUERBFPlugin.mll +│ │ ├── pydna\ +│ │ │ ├── python3 +│ │ │ │ ├── _py3dna.so +│ │ │ │ ├── dna.py +│ │ │ │ ├── libdna.so.7.1.0 +│ │ │ ├── python311 +│ │ │ │ ├── _py3dna.so +│ │ │ │ ├── dna.py +│ │ │ │ ├── libdna.so.7 +│ │ │ ├── python397 +│ │ │ │ ├── _py3dna.so +│ │ │ │ ├── dna.py +│ │ │ │ ├── libdna.so.7.1.0 +│ │ │ ├── python3108 +│ │ │ │ ├── _py3dna.so +│ │ │ │ ├── dna.py +│ │ │ │ ├── libdna.so.7.1.0 +│ └── Windows\ +│ │ ├── 2022\ +│ │ │ ├── _py3dnacalib.pyd +│ │ │ ├── dnacalib.dll +│ │ │ ├── dnacalib.py +│ │ │ ├── embeddedRL4.mll +│ │ │ ├── MayaUE4RBFPlugin2022.mll +│ │ │ ├── MayaUERBFPlugin.mll +│ │ ├── 2023\ +│ │ │ ├── _py3dnacalib.pyd +│ │ │ ├── dnacalib.dll +│ │ │ ├── dnacalib.py +│ │ │ ├── embeddedRL4.mll +│ │ │ ├── MayaUE4RBFPlugin2023.mll +│ │ │ ├── MayaUERBFPlugin.mll +│ │ ├── 2024\ +│ │ │ ├── _py3dnacalib.pyd +│ │ │ ├── dnacalib.dll +│ │ │ ├── dnacalib.py +│ │ │ ├── embeddedRL4.mll +│ │ │ ├── MayaUERBFPlugin.mll +│ │ ├── 2025\ +│ │ │ ├── _py3dnacalib.pyd +│ │ │ ├── dnacalib.dll +│ │ │ ├── dnacalib.py +│ │ │ ├── embeddedRL4.mll +│ │ │ ├── MayaUERBFPlugin.mll +│ │ ├── pydna\ +│ │ │ ├── python3\ +│ │ │ │ ├── _py3dna.pyd +│ │ │ │ ├── dna.dll +│ │ │ │ ├── dna.py +│ │ │ ├── python311\ +│ │ │ │ ├── _py3dna.pyd +│ │ │ │ ├── _py3dna9_4_3.pyd +│ │ │ │ ├── dna.dll +│ │ │ │ ├── dna.py +│ │ │ │ ├── dna9_4_3.dll +│ │ │ │ ├── polyalloc1_3_12.dll +│ │ │ │ ├── statuscode1_2_6.dll +│ │ │ │ ├── trio4_0_16.dll +│ │ │ ├── python397\ +│ │ │ │ ├── _py3dna.pyd +│ │ │ │ ├── dna.dll +│ │ │ │ ├── dna.py +│ │ │ ├── python3108\ +│ │ │ │ ├── _py3dna.pyd +│ │ │ │ ├── dna.dll +│ │ │ │ ├── dna.py +├── Reference\ 参考模块(不引用,仅供参考) +│ ├── DNA_Calibration 参考模块:DNA_Calibration +│ ├── MSLiveLink 参考模块:MSLiveLink +│ ├── SuperRiggingEditor 参考模块:SuperRiggingEditor +├── resources\ +│ ├── dna\ +│ │ ├── NDA文件... +│ ├── icons\ +│ │ ├── NDA图标... +├── scripts\ +│ ├── builder\ +│ │ ├── maya\ +│ │ │ ├── __init__.py +│ │ │ ├── mesh.py +│ │ │ ├── skin_weights.py +│ │ │ ├── util.py +│ │ ├── __init__.py +│ │ ├── builder.py +│ │ ├── config.py +│ │ ├── joint.py +│ │ ├── mesh.py +│ │ ├── rig_builder.py +│ ├── config\ +│ │ ├── __init__.py +│ │ ├── data.py 配置变量 +│ ├── dnalib\ +│ │ ├── __init__.py +│ │ ├── behavior.py +│ │ ├── definition.py +│ │ ├── descriptor.py +│ │ ├── dnalib.py +│ │ ├── geometry.py +│ │ ├── layer.py +│ ├── ui\ +│ │ ├── __init__.py +│ │ ├── Qt.py Qt模块:根据Maya不同版本来设置Qt相关的模块 +│ │ ├── style.qss 样式文件 +│ │ ├── menu.py 菜单UI模块 +│ │ ├── toolshelf.py 工具栏UI模块 +│ │ ├── models.py 模型UI模块 +│ │ ├── rigging.py 绑定UI模块 +│ │ ├── adjust.py 调整UI模块 +│ │ ├── define.py 定义UI模块 +│ ├── utils\ +│ │ ├── __init__.py +│ │ ├── menu.py 菜单功能模块(文件,语言,帮助菜单功能,编辑和工具菜单功能从其他功能模块引用) +│ │ ├── models.py 模型功能模块 +│ │ ├── rigging.py 绑定功能模块 +│ │ ├── adjust.py 调整功能模块 +│ │ ├── define.py 定义功能模块 +│ ├── MetaFusion.py 插件启动框架 +├── CleanPycache.bat +├── Install.mel 拖入maya安装文件 +├── Install.py 安装执行文件 +├── CleanPycache.bat + diff --git a/scripts/MetaFusion.py b/scripts/MetaFusion.py index c2bf2d5..29923cb 100644 --- a/scripts/MetaFusion.py +++ b/scripts/MetaFusion.py @@ -8,15 +8,15 @@ import maya.OpenMayaUI as omui from shiboken2 import wrapInstance import traceback -# 添加项目根目录到 Python 路径 -ROOT_DIR = os.path.dirname(os.path.dirname(__file__)) -if ROOT_DIR not in sys.path: - sys.path.insert(0, ROOT_DIR) -from config import data -from dna_utils import DNAManager + +from scripts.config import data QtCore, QtGui, QtWidgets = data.Qt() #===================================== 2. Global Variables ===================================== +try: + ROOT_PATH = data.ROOT_PATH +except NameError: + ROOT_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))).replace("\\", "/") TOOL_NAME = data.TOOL_NAME TOOL_VERSION = data.TOOL_VERSION TOOL_AUTHOR = data.TOOL_AUTHOR @@ -33,6 +33,10 @@ DNA_PATH = data.DNA_PATH DNA_IMG_PATH = data.DNA_IMG_PATH DNALIB_PATH = data.DNALIB_PATH BUILDER_PATH = data.BUILDER_PATH +UI_PATH = data.UI_PATH +UTILS_PATH = data.UTILS_PATH + + main_window = None diff --git a/scripts/blend_utils.py b/scripts/blend_utils.py deleted file mode 100644 index 6bde238..0000000 --- a/scripts/blend_utils.py +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import maya.cmds as cmds - -class BlendShapeManager: - def __init__(self): - self.current_blend = None - - def create_blend_shape(self, base_mesh, target_mesh): - """创建BlendShape""" - pass - - def edit_blend_shape(self, blend_name): - """编辑BlendShape""" - pass - - def save_blend_shape(self, file_path): - """保存BlendShape设置""" - pass \ No newline at end of file diff --git a/config/__init__.py b/scripts/config/__init__.py similarity index 100% rename from config/__init__.py rename to scripts/config/__init__.py diff --git a/scripts/config/data.py b/scripts/config/data.py new file mode 100644 index 0000000..344eb68 --- /dev/null +++ b/scripts/config/data.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import os +import sys +import maya.cmds as cmds + +# Base Information +TOOL_NAME = "MetaFusion" +TOOL_VERSION = "Beta v1.0.0" +TOOL_AUTHOR = "CGNICO" +TOOL_LANG = 'en_US' +TOOL_WSCL_NAME = f"{TOOL_NAME}WorkSpaceControl" +TOOL_HELP_URL = f"https://gitea.cgnico.com/CGNICO/{TOOL_NAME}/wiki" + + +# BASE_PATH +ROOT_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))).replace("\\", "/") +SCRIPTS_PATH = os.path.join(ROOT_PATH, "scripts").replace("\\", "/") +ICONS_PATH = os.path.join(ROOT_PATH, "resources", "icons").replace("\\", "/") +STYLES_PATH = os.path.join(ROOT_PATH, "resources", "styles").replace("\\", "/") +DNA_FILE_PATH = os.path.join(ROOT_PATH, "resources", "dna").replace("\\", "/") +DNA_IMG_PATH = os.path.join(ROOT_PATH, "resources", "img").replace("\\", "/") + +# PYDNA_PATH & PLUGIN_PATH +SYSTEM_OS = "Windows" if cmds.about(os=True).lower().startswith("win") else "Linux" +MAYA_VERSION = int(cmds.about(version=True).split('.')[0]) +PYTHON_VERSION = sys.version.replace(".", "") +PYTHON_VERSION_DIR_MAPPING = {"3108": "python3108", "311": "python311", "397": "python397"} +PYTHON_VERSION_DIR = PYTHON_VERSION_DIR_MAPPING.get(PYTHON_VERSION, "python3") +PLUGIN_PATH = os.path.join(ROOT_PATH, "plugins", SYSTEM_OS, MAYA_VERSION).replace("\\", "/") +PYDNA_PATH = os.path.join(ROOT_PATH, "plugins", SYSTEM_OS, "pydna", PYTHON_VERSION_DIR).replace("\\", "/") + +# TOOLS_PATH +DNACALIB_PATH = os.path.join(ROOT_PATH, "dnacalib").replace("\\", "/") +BUILDER_PATH = os.path.join(SCRIPTS_PATH, "builder").replace("\\", "/") +DNALIB_PATH = os.path.join(SCRIPTS_PATH, "dnalib").replace("\\", "/") +UI_PATH = os.path.join(SCRIPTS_PATH, "ui").replace("\\", "/") +UTILS_PATH = os.path.join(SCRIPTS_PATH, "utils").replace("\\", "/") + +#FILES +TOOL_MAIN_SCRIPT = os.path.join(SCRIPTS_PATH, f"{TOOL_NAME}.py").replace("\\", "/") +TOOL_STYLE_FILE = os.path.join(STYLES_PATH, "style.qss").replace("\\", "/") +TOOL_ICON = os.path.join(ICONS_PATH, f"{TOOL_NAME}Logo.png").replace("\\", "/") +TOOL_COMMAND_ICON = os.path.join(ICONS_PATH, "CommandButton.png").replace("\\", "/") +TOOL_MOD_FILENAME = f"{TOOL_NAME}.mod" + + +print("TOOL_NAME",TOOL_NAME) +print("TOOL_VERSION",TOOL_VERSION) +print("TOOL_AUTHOR",TOOL_AUTHOR) +print("TOOL_LANG",TOOL_LANG) +print("TOOL_WSCL_NAME",TOOL_WSCL_NAME) +print("TOOL_HELP_URL",TOOL_HELP_URL) + +print("ROOT_PATH",ROOT_PATH) +print("SCRIPTS_PATH",SCRIPTS_PATH) +print("ICONS_PATH",ICONS_PATH) +print("STYLES_PATH",STYLES_PATH) +print("DNA_FILE_PATH",DNA_FILE_PATH) +print("DNA_IMG_PATH",DNA_IMG_PATH) + +print("PLUGIN_PATH",PLUGIN_PATH) +print("PYDNA_PATH",PYDNA_PATH) + +print("DNACALIB_PATH",DNACALIB_PATH) +print("BUILDER_PATH",BUILDER_PATH) +print("DNALIB_PATH",DNALIB_PATH) +print("UI_PATH",UI_PATH) +print("UTILS_PATH",UTILS_PATH) + +print("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) + diff --git a/scripts/dna_utils.py b/scripts/dna_utils.py deleted file mode 100644 index 23bb7c1..0000000 --- a/scripts/dna_utils.py +++ /dev/null @@ -1,175 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import os -import sys -import maya.cmds as cmds -import maya.mel as mel -from config import data - -# 根据Maya和Python版本获取正确的DNA模块路径 -MAYA_VERSION = data.MAYA_VERSION -PYDNA_PATH = data.PYDNA_PATH -PLUGIN_PATH = data.PLUGIN_PATH - -def load_dna_plugin(): - """安全加载插件""" - plugins = [ - ("dnacalib.py", "dnacalib"), - ("embeddedRL4.mll", "embeddedRL4"), - ("MayaUERBFPlugin.mll", "MayaUERBFPlugin") - ] - - for file_name, plugin_name in plugins: - try: - plugin_path = os.path.join(data.PLUGIN_PATH, file_name) - if not os.path.exists(plugin_path): - cmds.warning(f"⚠️ 插件文件缺失: {file_name}") - continue - - if cmds.pluginInfo(plugin_name, q=True, loaded=True): - print(f"✅ 已加载: {plugin_name}") - continue - - cmds.loadPlugin(plugin_path) - print(f"✔️ 成功加载: {plugin_name}") - - except Exception as e: - cmds.warning(f"❌ 加载失败 {plugin_name}: {str(e)}") - - -class DNAManager: - def __init__(self): - self.current_dna = None - self.reader = None - self.writer = None - - def is_ready(self): - """检查DNA模块是否可用""" - return dna is not None and dnacalib is not None - - def load_dna(self, file_path): - """载入DNA文件""" - if not self.is_ready(): - cmds.warning("DNA模块未正确加载") - return False - - try: - # 创建DNA读取器 - self.reader = dna.DNAReader() - self.reader.read(file_path) - self.current_dna = self.reader.get_dna() - return True - except Exception as e: - cmds.warning(f"DNA文件加载失败: {str(e)}") - return False - - def save_dna(self, file_path): - """保存DNA文件""" - try: - if not self.current_dna: - raise ValueError("没有DNA数据可保存") - - # 创建DNA写入器 - self.writer = DNAWriter() - self.writer.set_dna(self.current_dna) - self.writer.write(file_path) - return True - except Exception as e: - cmds.warning(f"DNA文件保存失败: {str(e)}") - return False - - def calibrate_joints(self): - """校准骨骼位置""" - try: - if not self.current_dna: - raise ValueError("请先加载DNA文件") - - # 获取骨骼定义 - joints = self.current_dna.get_joints() - - # 校准每个骨骼 - for joint in joints: - # 获取骨骼名称和位置 - joint_name = joint.get_name() - neutral_position = joint.get_neutral_position() - - # 如果Maya场景中存在该骨骼 - if cmds.objExists(joint_name): - # 设置骨骼位置 - cmds.xform(joint_name, - worldSpace=True, - translation=neutral_position) - - return True - except Exception as e: - cmds.warning(f"骨骼校准失败: {str(e)}") - return False - - def export_fbx(self, file_path): - """导出FBX文件""" - try: - # 选择所有需要导出的对象 - all_objects = self.get_export_objects() - cmds.select(all_objects) - - # 设置FBX导出选项 - mel.eval('FBXExportInputConnections -v true') - mel.eval('FBXExportIncludeChildren -v true') - - # 导出FBX - cmds.file(file_path, - force=True, - options="v=0", - type="FBX export", - preserveReferences=True, - exportSelected=True) - return True - except Exception as e: - cmds.warning(f"FBX导出失败: {str(e)}") - return False - - def get_export_objects(self): - """获取需要导出的对象列表""" - # 获取模型、骨骼等对象 - objects = [] - if self.current_dna: - # 添加骨骼 - joints = self.current_dna.get_joints() - for joint in joints: - joint_name = joint.get_name() - if cmds.objExists(joint_name): - objects.append(joint_name) - - # 添加蒙皮模型 - meshes = self.current_dna.get_meshes() - for mesh in meshes: - mesh_name = mesh.get_name() - if cmds.objExists(mesh_name): - objects.append(mesh_name) - - return objects - - def import_settings(self, file_path): - """导入 DNA 设置""" - try: - if not self.is_ready(): - raise ImportError("DNA模块未正确加载") - - # TODO: 实现导入设置逻辑 - return True - except Exception as e: - cmds.warning(f"导入设置失败: {str(e)}") - return False - - def export_settings(self, file_path): - """导出 DNA 设置""" - try: - if not self.is_ready(): - raise ImportError("DNA模块未正确加载") - - # TODO: 实现导出设置逻辑 - return True - except Exception as e: - cmds.warning(f"导出设置失败: {str(e)}") - return False \ No newline at end of file diff --git a/scripts/ui/Qt.py b/scripts/ui/Qt.py new file mode 100644 index 0000000..5650466 --- /dev/null +++ b/scripts/ui/Qt.py @@ -0,0 +1,36 @@ +#!/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 + print("加载PySide") + return QtCore, QtGui, QtWidgets + except ImportError as e: + print(f"PySide加载失败: {str(e)}") + try: + from PySide2 import QtCore, QtGui, QtWidgets + QtWidgets = QtGui + print("加载PySide2") + return QtCore, QtGui, QtWidgets + except ImportError as e: + cmds.warning(f"PySide2加载失败: {str(e)}") + try: + from PySide6 import QtCore, QtGui, QtWidgets + print("加载PySide6") + return QtCore, QtGui, QtWidgets + except ImportError as e: + cmds.warning(f"PySide6加载失败: {str(e)}") + return None, None, None + +QtCore, QtGui, QtWidgets = Qt() + + diff --git a/scripts/ui/__init__.py b/scripts/ui/__init__.py new file mode 100644 index 0000000..44b3172 --- /dev/null +++ b/scripts/ui/__init__.py @@ -0,0 +1,4 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from . import * diff --git a/resources/styles/style.qss b/scripts/ui/style.qss similarity index 100% rename from resources/styles/style.qss rename to scripts/ui/style.qss diff --git a/scripts/utils/__init__.py b/scripts/utils/__init__.py new file mode 100644 index 0000000..44b3172 --- /dev/null +++ b/scripts/utils/__init__.py @@ -0,0 +1,4 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from . import * diff --git a/ui.md b/ui.md index aed7e66..45dd057 100644 --- a/ui.md +++ b/ui.md @@ -22,8 +22,8 @@ ### 设置关节颜色 resources\icons\color.png ### 取消全部标记 resources\icons\unmark_all.png ### 重建所有目标 resources\icons\rebuildTargets.png -### 为所有表情设置关键帧 bakeAnimation.png -### 烘焙所有表情的关键帧 centerCurrentTime.png +### 为所有表情设置关键帧 resources\icons\bakeAnimation.png +### 烘焙所有表情的关键帧 resources\icons\centerCurrentTime.png ## 工具 ### 导出蒙皮 resources\icons\export_skin.png @@ -134,7 +134,7 @@ ### 模型工具: #### 拓扑结构: MetaHuman(下拉菜单) #### 选择LOD: 全部(下拉菜单) -#### 按钮, 模型分离 polySplitVertex.png +#### 按钮, 模型分离 resources\icons\polySplitVertex.png #### 按钮, 生成面部配件 resources\icons\supplement_meshes.png #### 按钮, 修复法线 resources\icons\repair_normals.png #### 按钮, 修复点序 resources\icons\repair_vertex_order.png @@ -164,8 +164,8 @@ LOD计数:8 (数值框,可调整) ### 清空选项 resources\icons\delete.png -### 导入骨架 HIKCharacterToolSkeleton.png -### 创建骨架 HIKcreateControlRig.png +### 导入骨架 resources\icons\HIKCharacterToolSkeleton.png +### 创建骨架 resources\icons\HIKcreateControlRig.png ## 调整 @@ -205,5 +205,5 @@ LOD计数:8 (数值框,可调整) #### 选择选择表情 resources\icons\expressions_current.png #### 写入当前表情 resources\icons\expression.png #### 控制面板查找 resources\icons\controller.png -#### 选择关联关节 kinJoint.png +#### 选择关联关节 resources\icons\kinJoint.png #### 写入镜像表情 resources\icons\ctrl_hide.png \ No newline at end of file