diff --git a/Install.mel b/Install.mel new file mode 100644 index 0000000..cebbb41 --- /dev/null +++ b/Install.mel @@ -0,0 +1,18 @@ +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(); \ No newline at end of file diff --git a/Install.py b/Install.py new file mode 100644 index 0000000..7159fcd --- /dev/null +++ b/Install.py @@ -0,0 +1,500 @@ +#!/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 + +#===================================== 2. Global Variables ===================================== +try: + ROOT_PATH = os.path.dirname(INSTALL_PATH).replace("\\", "/") +except NameError: + ROOT_PATH = os.path.dirname(os.path.abspath(__file__)).replace("\\", "/") +TOOL_NAME = "MetaFusion" +TOOL_VERSION = "Beta v1.0.0" +TOOL_AUTHOR = "Virtuos" +TOOL_LANG = 'en_US' +SCRIPTS_PATH = os.path.join(ROOT_PATH, "scripts").replace("\\", "/") +ICONS_PATH = os.path.join(ROOT_PATH, "icons").replace("\\", "/") +TOOL_ICON = os.path.join(ICONS_PATH, "logo.png").replace("\\", "/") +DEFAULT_ICON = "commandButton.png" +TOOL_HELP_URL = f"http://10.72.61.59:3000/ArtGroup/{TOOL_NAME}/wiki" +TOOL_WSCL_NAME = "MetaFusionWorkSpaceControl" +MOD_FILE_NAME = f"{TOOL_NAME}.mod" +MAIN_SCRIPT_NAME = f"{TOOL_NAME}.py" + +# UI Style configurations +BUTTON_STYLE = """ + 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; + } +""" + +MESSAGE_BUTTON_STYLE = """ + QPushButton { + background-color: #B0B0B0; + color: #303030; + border-radius: 10px; + padding: 5px; + font-weight: bold; + min-width: 80px; + } + QPushButton:hover { + background-color: #C0C0C0; + } + QPushButton:pressed { + background-color: #A0A0A0; + } +""" + +#===================================== 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) + self.setStyleSheet(BUTTON_STYLE) + +#===================================== 5. Main Window Class ===================================== +class InstallDialog(QtWidgets.QDialog): + def __init__(self, parent=maya_main_window()): + super(InstallDialog, self).__init__(parent) + self.setup_ui() + + 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) + + button_style = """ + QPushButton { + background-color: #B0B0B0; + color: #303030; + border-radius: 10px; + padding: 5px; + font-weight: bold; + min-width: 80px; + } + QPushButton:hover { + background-color: #C0C0C0; + } + QPushButton:pressed { + background-color: #A0A0A0; + } + """ + + for button in msg_box.buttons(): + button.setStyleSheet(button_style) + + 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, MOD_FILE_NAME) + 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, MOD_FILE_NAME) + 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 + + main_script = os.path.join(SCRIPTS_PATH, MAIN_SCRIPT_NAME) + if not os.path.exists(main_script): + print(f"Error: Main script file not found: {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 DEFAULT_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) + + for button in msg_box.buttons(): + button.setStyleSheet(MESSAGE_BUTTON_STYLE) + + 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) + for button in msg_box.buttons(): + button.setStyleSheet(MESSAGE_BUTTON_STYLE) + 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() + diff --git a/MetaFusion.mod b/MetaFusion.mod new file mode 100644 index 0000000..1378605 --- /dev/null +++ b/MetaFusion.mod @@ -0,0 +1,48 @@ ++ MAYAVERSION:2018 PLATFORM:win64 MetaFusion any . +PYTHONPATH +:= +PYTHONPATH +:= data +PYTHONPATH +:= scripts +PYTHONPATH +:= plugins/2018 +MAYA_PLUG_IN_PATH +:= plugins/2018 + ++ MAYAVERSION:2019 PLATFORM:win64 MetaFusion any . +PYTHONPATH +:= +PYTHONPATH +:= data +PYTHONPATH +:= scripts +PYTHONPATH +:= plugins/2019 +MAYA_PLUG_IN_PATH +:= plugins/2019 + ++ MAYAVERSION:2020 PLATFORM:win64 MetaFusion any . +PYTHONPATH +:= +PYTHONPATH +:= data +PYTHONPATH +:= scripts +PYTHONPATH +:= plugins/2020 +MAYA_PLUG_IN_PATH +:= plugins/2020 + ++ MAYAVERSION:2021 PLATFORM:win64 MetaFusion any . +PYTHONPATH +:= +PYTHONPATH +:= data +PYTHONPATH +:= scripts +PYTHONPATH +:= plugins/2021 +MAYA_PLUG_IN_PATH +:= plugins/2021 + ++ MAYAVERSION:2022 PLATFORM:win64 MetaFusion any . +PYTHONPATH +:= +PYTHONPATH +:= data +PYTHONPATH +:= scripts +PYTHONPATH +:= plugins/2022 +MAYA_PLUG_IN_PATH +:= plugins/2022 + ++ MAYAVERSION:2023 PLATFORM:win64 MetaFusion any . +PYTHONPATH +:= +PYTHONPATH +:= data +PYTHONPATH +:= scripts +PYTHONPATH +:= plugins/2023 +MAYA_PLUG_IN_PATH +:= plugins/2023 + ++ MAYAVERSION:2024 PLATFORM:win64 MetaFusion any . +PYTHONPATH +:= +PYTHONPATH +:= data +PYTHONPATH +:= scripts +PYTHONPATH +:= plugins/2024 +MAYA_PLUG_IN_PATH +:= plugins/2024 diff --git a/README.md b/README.md index 0fbb8ad..65a8345 100644 --- a/README.md +++ b/README.md @@ -1 +1,15 @@ -# MetaHuman Custome Tool For Maya \ No newline at end of file +# Metahuman Customized Tool + +Metahuman Customized Toolbag for Maya + +# Tool Name + +MetaFusion + +## VERSION + +beta 1.0.0 + +## MAYA VERSION + +2023 diff --git a/data/img/Preview.png b/data/img/Preview.png new file mode 100644 index 0000000..0bd4679 Binary files /dev/null and b/data/img/Preview.png differ diff --git a/dna_calibration.mod b/dna_calibration.mod deleted file mode 100644 index 96b4da3..0000000 --- a/dna_calibration.mod +++ /dev/null @@ -1,35 +0,0 @@ -+ MAYAVERSION:2022 PLATFORM:win64 MetaHuman-DNA-Calibration any . -PYTHONPATH +:= -PYTHONPATH +:= data -PYTHONPATH +:= lib/Maya2022/windows -MAYA_PLUG_IN_PATH +:= lib/Maya2022/windows - -+ MAYAVERSION:2022 PLATFORM:linux MetaHuman-DNA-Calibration any . -PYTHONPATH +:= -PYTHONPATH +:= data -PYTHONPATH +:= lib/Maya2022/linux -MAYA_PLUG_IN_PATH +:= lib/Maya2022/linux - -+ MAYAVERSION:2023 PLATFORM:win64 MetaHuman-DNA-Calibration any . -PYTHONPATH +:= -PYTHONPATH +:= data -PYTHONPATH +:= lib/Maya2023/windows -MAYA_PLUG_IN_PATH +:= lib/Maya2023/windows - -+ MAYAVERSION:2023 PLATFORM:linux MetaHuman-DNA-Calibration any . -PYTHONPATH +:= -PYTHONPATH +:= data -PYTHONPATH +:= lib/Maya2023/linux -MAYA_PLUG_IN_PATH +:= lib/Maya2023/linux - -+ MAYAVERSION:2024 PLATFORM:win64 MetaHuman-DNA-Calibration any . -PYTHONPATH +:= -PYTHONPATH +:= data -PYTHONPATH +:= lib/Maya2024/windows -MAYA_PLUG_IN_PATH +:= lib/Maya2024/windows - -+ MAYAVERSION:2024 PLATFORM:linux MetaHuman-DNA-Calibration any . -PYTHONPATH +:= -PYTHONPATH +:= data -PYTHONPATH +:= lib/Maya2024/linux -MAYA_PLUG_IN_PATH +:= lib/Maya2024/linux diff --git a/metafusion.py b/metafusion.py deleted file mode 100644 index 366082b..0000000 --- a/metafusion.py +++ /dev/null @@ -1,304 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -from PySide2 import QtWidgets, QtCore, QtGui -from shiboken2 import wrapInstance -from maya import OpenMayaUI as omui -import maya.cmds as cmds -import maya.mel as mel -import maya.utils as utils -import importlib -import traceback -import subprocess -import webbrowser -import locale -import sys -import os - -# 全局变量声明 -TOOL_PATH = os.path.dirname(os.path.abspath(__file__)).replace('\\', '/') -TOOL_ICON = os.path.join(TOOL_PATH, "icons", "logo.png") -if not os.path.exists(TOOL_ICON): - print(f"Warning: Icon file not found at {TOOL_ICON}") - TOOL_ICON = "" # 设置为空字符串而不是 None -MAYA_VERSION = cmds.about(version=True) -TOOL_NAME = "MetaFusion" -TOOL_VERSION = "v1.0.0" -TOOL_AUTHOR = "CGNICO" -CURRENT_LANG = "en_US" -WKSP_CTRL = TOOL_NAME -DNA_PATH = "" -WKSP_PATH = "MetaFusion" -HelpURL = f"https://gitea.cgnico.com/CGNICO/{TOOL_NAME}/wiki" - -#==================================================== GLOBAL FUNCTIONS ==================================================== -class SetButton(QtWidgets.QPushButton): - """ - Custom rounded button class - - Features: - - Rounded design - - Custom color and hover effect - - Bold text - """ - def __init__(self, text="", icon=None, color="#D0D0D0", hover_color="#E0E0E0", pressed_color="#C0C0C0"): - super(SetButton, self).__init__(text) - if icon: - self.setIcon(icon) - self.setIconSize(QtCore.QSize(24, 24)) - self.setMinimumHeight(30) # Set minimum height - self.setStyleSheet( - f""" - QPushButton {{ - background-color: {color}; - color: #303030; - border-radius: 10px; - padding: 5px; - font-weight: bold; - text-align: center; - }} - QPushButton:hover {{ - background-color: {hover_color}; - }} - QPushButton:pressed {{ - background-color: {pressed_color}; - }} - """ - ) - -def get_system_encoding(): - encoding = sys.getdefaultencoding() - if encoding.lower() == 'ascii': - encoding = locale.getpreferredencoding() - return encoding - -def maya_main_window(): - main_window_ptr = omui.MQtUtil.mainWindow() - return wrapInstance(int(main_window_ptr), QtWidgets.QWidget) - -LANG = { - 'en_US': { - "Group 01": "Group 01", - "Group 02": "Group 02", - "Button 01": "Button 01", - "Button 02": "Button 02", - "DOCUMENT": "DOCUMENT", - "Help": "Help", - "Switch Language": "Switch Language" - }, - 'zh_CN': { - "Group 01": "组 01", - "Group 02": "组 02", - "Button 01": "按钮 01", - "Button 02": "按钮 02", - "DOCUMENT": "文档", - "Help": "帮助", - "Switch Language": "切换语言" - } -} - -#==================================================== MAIN WINDOW CLASS ==================================================== -class MainWindow(QtWidgets.QWidget): - def __init__(self, parent=None): - super(MainWindow, self).__init__(parent) - self.setWindowTitle(TOOL_NAME) - self.setObjectName(TOOL_NAME) - self.setWindowFlags(QtCore.Qt.Window) - self.setAttribute(QtCore.Qt.WA_DeleteOnClose) - self.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred) - self.setMinimumSize(300, 800) - self.create_widgets() - self.create_layouts() - self.create_connections() - - if os.path.exists(TOOL_ICON): - self.setWindowIcon(QtGui.QIcon(TOOL_ICON)) - else: - print(f"WARNING: Icon file not found: {TOOL_ICON}") - -#==================================================== UI COMPONENTS ==================================================== - def create_widgets(self): - - self.group_01 = QtWidgets.QGroupBox(LANG[CURRENT_LANG]["Group 01"]) - self.group_01_button = SetButton(LANG[CURRENT_LANG]["Button 01"], color="#A7C6ED", hover_color="#B2D3F0", pressed_color="#8BB8E0") - self.group_02 = QtWidgets.QGroupBox(LANG[CURRENT_LANG]["Group 02"]) - self.group_02_button = SetButton(LANG[CURRENT_LANG]["Button 02"], color="#FFCCBC", hover_color="#FFAB91", pressed_color="#FF8A65") - - self.help_btn = QtWidgets.QPushButton(LANG[CURRENT_LANG]["DOCUMENT"]) - self.help_btn.setToolTip(LANG[CURRENT_LANG]["Help"]) - self.help_btn.setFixedSize(100, 20) - self.help_btn.setStyleSheet(""" - QPushButton { - background-color: transparent; - border: none; - color: gray; - font-weight: bold; - } - QPushButton:hover { - color: black; - } - """) - self.lang_btn = QtWidgets.QPushButton("EN" if CURRENT_LANG == 'zh_CN' else "ZH") - self.lang_btn.setToolTip(LANG[CURRENT_LANG]["Switch Language"]) - self.lang_btn.setFixedSize(30, 20) - self.lang_btn.setStyleSheet(""" - QPushButton { - background-color: transparent; - border: none; - color: gray; - font-weight: bold; - } - QPushButton:hover { - color: black; - } - """) - - def create_layouts(self): - main_layout = QtWidgets.QVBoxLayout(self) - main_layout.setContentsMargins(2, 2, 2, 2) - main_layout.addStretch() - main_layout.addWidget(self.group_01) - main_layout.addWidget(self.group_01_button) - main_layout.addWidget(self.group_02) - main_layout.addWidget(self.group_02_button) - main_layout.addStretch() - - # Bottom layout - bottom_layout = QtWidgets.QHBoxLayout() - icon_label = QtWidgets.QLabel() - if TOOL_ICON and os.path.exists(TOOL_ICON): - icon = QtGui.QPixmap(TOOL_ICON).scaled(24, 24, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation) - icon_label.setPixmap(icon) - - bottom_layout.addWidget(icon_label) - version_label = QtWidgets.QLabel(f"{TOOL_VERSION}") - version_label.setStyleSheet("color: gray; font-size: 12px;") - bottom_layout.addWidget(version_label) - bottom_layout.addStretch() - bottom_layout.addWidget(self.help_btn) - bottom_layout.addWidget(self.lang_btn) - main_layout.addLayout(bottom_layout) - - def create_connections(self): - self.group_01_button.clicked.connect(self.run_group_01) - self.group_02_button.clicked.connect(self.run_group_02) - self.help_btn.clicked.connect(self.show_help) - self.lang_btn.clicked.connect(self.switch_language) - - def run_group_01(self): - print("Button 01") - - def run_group_02(self): - print("Button 02") - - def dock_to_maya(self): - if not WKSP_CTRL: - print("Error: WKSP_CTRL is not defined") - return - - if cmds.workspaceControl(WKSP_CTRL, exists=True): - cmds.deleteUI(WKSP_CTRL) - - def create_control(): - try: - if not self.objectName(): - self.setObjectName(TOOL_NAME) - - workspace_control = cmds.workspaceControl( - WKSP_CTRL, - label=TOOL_NAME, - floating=True, - retain=False, - resizeWidth=True, - initialWidth=300, - minimumWidth=300 - ) - - if not workspace_control: - raise RuntimeError("Failed to create workspace control") - - cmds.workspaceControl(WKSP_CTRL, e=True, resizeWidth=True) - - try: - cmds.control(self.objectName(), e=True, p=workspace_control) - except Exception as e: - print(f"Error parenting control: {e}") - return - - except Exception as e: - print(f"Error creating workspace control: {e}") - traceback.print_exc() - - cmds.evalDeferred(create_control) - - def show_help(self): - # Specify the URL of the website you want to open - webbrowser.open(HelpURL) - - def switch_language(self): - global CURRENT_LANG - CURRENT_LANG = 'en_US' if CURRENT_LANG == 'zh_CN' else 'zh_CN' - self.lang_btn.setText("EN" if CURRENT_LANG == 'zh_CN' else "CN") - self.retranslate_ui() - - QtWidgets.QToolTip.showText( - self.lang_btn.mapToGlobal(QtCore.QPoint(0, -30)), - "Language switched" if CURRENT_LANG == 'en_US' else "语言已切换", - self.lang_btn - ) - -def show(): - global main_window - - try: - current_width = 300 - if cmds.workspaceControl(WKSP_CTRL, exists=True): - try: - current_width = cmds.workspaceControl(WKSP_CTRL, q=True, width=True) - except: - pass - cmds.deleteUI(WKSP_CTRL, control=True) - - if 'main_window' in globals() and main_window: - try: - main_window.close() - main_window.deleteLater() - except: - pass - except Exception as e: - print(f"Error cleaning up previous window: {e}") - - def create_ui(retry_count=0, width=300): - global main_window - try: - if not WKSP_CTRL: - raise ValueError("WKSP_CTRL is not defined") - - main_window = MainWindow() - if not main_window: - raise RuntimeError("Failed to create main window") - - # 使用 try-except 包装每个 evalDeferred 调用 - def deferred_actions(): - try: - main_window.dock_to_maya() - cmds.workspaceControl(WKSP_CTRL, e=True, width=width) - cmds.workspaceControl(WKSP_CTRL, e=True, resizeWidth=True) - except Exception as e: - print(f"Error in deferred actions: {e}") - - cmds.evalDeferred(deferred_actions) - - except Exception as e: - if retry_count < 3: - print(f"{TOOL_NAME} creation failed, retrying... (Attempt {retry_count + 1})") - print(f"Error: {str(e)}") - utils.executeDeferred(lambda: create_ui(retry_count + 1, width)) - else: - print(f"Failed to create {TOOL_NAME} after 3 attempts:") - traceback.print_exc() - - utils.executeDeferred(lambda: create_ui(width=current_width)) - -if __name__ == "__main__": - show() diff --git a/scripts/BatchImport.py b/scripts/BatchImport.py new file mode 100644 index 0000000..c85bfe5 --- /dev/null +++ b/scripts/BatchImport.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import maya.cmds as cmds +import sys + +def onMayaDroppedPythonFile(*args): + run() + +#Let's you batch import: +def run(*args): + if CheckForBonusTools: + multipleFilters = 'All native importable files (*.3ds *.abc *.ass *.at *.catpart *.dae *.fbx *.igs *.iges *.jt *.ma *.mb *.obj *.prt *.sat *.step *.stp *.wire);; Maya binary (*.mb);; Maya Ascii (*.ma);; WIRE_ATF (*.wire);; Obj (*.obj);; FBX (*.fbx);; DAE_FBX (*.dae);; Alembic Cache (*.abc);; Atom (*.atom);; Step (*.stp *.step);; IGES_ATF (*.igs *.iges);; ASS (*.ass);; 3DS Max (*.3ds);; CATIAV5_ATF (*.catpart);; JT_ATF (*.jt);; SAT_ATF (*.sat);; NX_ATF (*.prt)' + else: + multipleFilters = 'All native importable files (*.abc *.ass *.at *.catpart *.dae *.fbx *.igs *.iges *.jt *.ma *.mb *.obj *.prt *.sat *.step *.stp *.wire);; Maya binary (*.mb);; Maya Ascii (*.ma);; WIRE_ATF (*.wire);; Obj (*.obj);; FBX (*.fbx);; DAE_FBX (*.dae);; Alembic Cache (*.abc);; Atom (*.atom);; Step (*.stp *.step);; IGES_ATF (*.igs *.iges);; ASS (*.ass);; 3DS Max (*.3ds);; CATIAV5_ATF (*.catpart);; JT_ATF (*.jt);; SAT_ATF (*.sat);; NX_ATF (*.prt)' + files = cmds.fileDialog2(caption = 'Choose files to import', ds = 2, fileMode = 4, okCaption = 'Import', fileFilter = multipleFilters, hideNameEdit = False) + + # Add checks to ensure files are valid + if not files or not isinstance(files, list) or len(files) == 0: + cmds.warning('No file was selected and the import operation was canceled.') + return # Return directly to avoid subsequent code execution + + for x in files: + if any(y in x for y in ['.ma', '.MA']): + fileType = 'mayaAscii' + options = '' + ImportFiles(fileType, x, options) + + if any(y in x for y in ['.mb', '.MB']): + fileType = 'mayaBinary' + options = '' + ImportFiles(fileType, x, options) + + if any(y in x for y in ['.wire', '.WIRE']): + fileType = 'WIRE_ATF' + options = '' + LoadPlugin('ATFPlugin') + ImportFiles(fileType, x, options) + + if any(y in x for y in ['.obj', '.OBJ']): + fileType = 'OBJ' + options = 'mo=0' + ImportFiles(fileType, x, options) + + if any(y in x for y in ['.fbx', '.FBX']): + fileType = 'FBX' + options = '' + LoadPlugin('fbxmaya') + ImportFiles(fileType, x, options) + + if any(y in x for y in ['.dae', '.DAE']): + fileType = 'DAE_FBX' + options = '' + ImportFiles(fileType, x, options) + + if any(y in x for y in ['.abc', '.ABC']): + fileType = 'Alembic' + options = '' + LoadPlugin('AbcImport') + ImportFiles(fileType, x, options) + + if any(y in x for y in ['.atom', '.ATOM', '.at', '.AT']): + fileType = 'atomImport' + options = '' + LoadPlugin('atomImportExport') + ImportFiles(fileType, x, options) + + if any(y in x for y in ['.step', '.STEP', '.stp', '.STP']): + fileType = 'STEP_ATF' + options = '' + LoadPlugin('ATFPlugin') + ImportFiles(fileType, x, options) + + if any(y in x for y in['igs.', '.IGS', '.iges', '.IGES']): + fileType = 'IGES_ATF' + options = '' + LoadPlugin('ATFPlugin') + ImportFiles(fileType, x, options) + + if any(y in x for y in['.ass', '.ASS']): + fileType = 'ASS' + options = '' + LoadPlugin('mtoa') + ImportFiles(fileType, x, options) + + if any(y in x for y in['.3ds', '.3DS']): + fileType = '3ds' + options = '' + if CheckForBonusTools: + LoadPlugin('3ds') + ImportFiles(fileType, x, options) + + if any(y in x for y in['.catpart', '.CATPART']): + fileType = 'CATIAV5_ATF' + options = '' + LoadPlugin('ATFPlugin') + ImportFiles(fileType, x, options) + + if any(y in x for y in['.jt', '.JT']): + fileType = 'JT_ATF' + options = '' + LoadPlugin('ATFPlugin') + ImportFiles(fileType, x, options) + + if any(y in x for y in['.sat', '.SAT']): + fileType = 'SAT_ATF' + options = '' + LoadPlugin('ATFPlugin') + ImportFiles(fileType, x, options) + + if any(y in x for y in['.prt', '.PRT']): + fileType = 'NX_ATF' + options = '' + LoadPlugin('ATFPlugin') + ImportFiles(fileType, x, options) + +def CheckForBonusTools(*args): + for x in sys.path: + if 'MayaBonusTools' in x: + return True + return False + +def LoadPlugin(plugin, *args): + if not cmds.pluginInfo(plugin, query = True, loaded = True): + try: + cmds.loadPlugin(plugin) + sys.stdout.write('Plugin "' + plugin + '" loaded.\n') + except(RuntimeError): + cmds.warning('Could not find "' + plugin + '" plugin or could not load it. Open the Plugin Manager and make sure Maya recognized the plugin and try again.\n') + +def ImportFiles(fileType, file, options, *args): + namespace = file.split('/') + namespace = namespace[-1].split('.') + namespace = namespace[0] + try: + cmds.file(str(file), i = True, type = fileType, ignoreVersion = True, mergeNamespacesOnClash = False, namespace = namespace, options = options) + sys.stdout.write('Imported "' + str(file) + '".\n') + except(UnicodeEncodeError): + sys.stdout.write('Either the directory path or the file name have some special characters.\n') + sys.stdout.write('The names will be changed.\n') + cmds.file(file, i = True, type = fileType, ignoreVersion = True, mergeNamespacesOnClash = False, namespace = namespace, options = options) + sys.stdout.write('Imported "' + file + '".\n') + except(ImportError): + cmds.warning('Could not import ' + file + '. Maybe you dont have the requiered permissions to the folder.\n') + +if __name__ == '__main__': + if not cmds.about(batch = True): + run() diff --git a/scripts/BodyPrep.py b/scripts/BodyPrep.py new file mode 100644 index 0000000..4d9c1c2 --- /dev/null +++ b/scripts/BodyPrep.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +用于准备身体文件,去除了body_grp的锁定,清理不需要的节点,并重命名。并存储为body_drv.mb文件用于后续加载。 +""" + +import maya.cmds as cmds + +def run(): + cmds.upAxis(axis='y') + cmds.delete("DHIhead:spine_04", "Lights", "export_geo_GRP", "head_grp") + cmds.rename("rig", "body_rig") + cmds.rename("geometry_grp", "body_geometry_grp") + body_grp_lock = cmds.listRelatives('body_grp', allDescendents=True, type='transform') + for obj in body_grp_lock: + cmds.setAttr(obj + '.translateX', lock=False) + cmds.setAttr(obj + '.translateY', lock=False) + cmds.setAttr(obj + '.translateZ', lock=False) + cmds.setAttr(obj + '.rotateX', lock=False) + cmds.setAttr(obj + '.rotateY', lock=False) + cmds.setAttr(obj + '.rotateZ', lock=False) + cmds.setAttr(obj + '.scaleX', lock=False) + cmds.setAttr(obj + '.scaleY', lock=False) + cmds.setAttr(obj + '.scaleZ', lock=False) + correctiveCube = cmds.polyCube()[0] + cmds.parent('root_drv', correctiveCube) + cmds.rotate(-90,0,0,correctiveCube,relative=True) + cmds.parent('root_drv',world=True) + cmds.delete(correctiveCube) + save_path = cmds.fileDialog2(fileMode=0, caption="Save Maya Scene", fileFilter="Maya Binary (*.mb)")[0] + cmds.file(rename=save_path) + cmds.file(save=True, type='mayaBinary') + +if __name__ == '__main__': + if not cmds.about(batch = True): + run() \ No newline at end of file diff --git a/scripts/DNA_Browser.py b/scripts/DNA_Browser.py new file mode 100644 index 0000000..43ab3ac --- /dev/null +++ b/scripts/DNA_Browser.py @@ -0,0 +1,237 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from PySide2 import QtWidgets, QtCore, QtGui +import maya.cmds as cmds +import os + +class DNABrowserWidget(QtWidgets.QWidget): + dna_selected = QtCore.Signal(str) # 信号:当DNA被选中时发出 + + def __init__(self, dna_path, img_path, parent=None): + super().__init__(parent) + self.dna_path = dna_path + self.img_path = img_path + self.setup_ui() + self.scan_dna_files() + self.update_grid() + + def setup_ui(self): + # 创建主布局 + self.main_layout = QtWidgets.QVBoxLayout(self) + self.main_layout.setContentsMargins(0, 0, 0, 0) + + # 创建流式布局容器 + self.flow_widget = QtWidgets.QWidget() + self.flow_layout = FlowLayout(self.flow_widget) + self.flow_layout.setSpacing(5) + + # 创建滚动区域 + self.scroll_area = QtWidgets.QScrollArea() + self.scroll_area.setWidgetResizable(True) + self.scroll_area.setWidget(self.flow_widget) + self.scroll_area.setStyleSheet(""" + QScrollArea { + border: none; + background-color: transparent; + } + QScrollBar:vertical { + border: none; + background: #F0F0F0; + width: 8px; + margin: 0px; + } + QScrollBar::handle:vertical { + background: #CCCCCC; + border-radius: 4px; + min-height: 20px; + } + QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { + height: 0px; + } + """) + + self.main_layout.addWidget(self.scroll_area) + + def scan_dna_files(self): + """扫描DNA文件夹并建立索引""" + self.dna_files = {} + if not os.path.exists(self.dna_path): + cmds.warning(f"DNA path not found: {self.dna_path}") + return + + if not os.path.exists(self.img_path): + cmds.warning(f"Image path not found: {self.img_path}") + return + + for file in os.listdir(self.dna_path): + if file.endswith('.dna'): + name = os.path.splitext(file)[0] + dna_file = os.path.join(self.dna_path, file).replace("\\", "/") + + # 直接在img目录下查找图片 + img_file = None + for ext in ['.jpg', '.png', '.jpeg']: + img_path = os.path.join(self.img_path, f"{name}{ext}").replace("\\", "/") + if os.path.exists(img_path): + img_file = img_path + break + + self.dna_files[name] = { + 'dna_path': dna_file, + 'img_path': img_file + } + + # 打印调试信息 + print(f"DNA file: {name}") + print(f" DNA path: {dna_file}") + print(f" Image path: {img_file}") + print(f" Image exists: {bool(img_file and os.path.exists(img_file))}") + + def update_grid(self): + """更新DNA网格""" + # 清除现有按钮 + for i in reversed(range(self.flow_layout.count())): + self.flow_layout.itemAt(i).widget().deleteLater() + + # 计算按钮大小 - 减小到原来的1/4左右 + container_width = self.flow_widget.width() or 300 + button_width = (container_width - 60) // 6 # 每行6个按钮 + button_height = int(button_width * 1.2) # 保持宽高比 + + # 创建DNA样本按钮 + for name, info in self.dna_files.items(): + dna_btn = self.create_dna_button(name, info, button_width, button_height) + self.flow_layout.addWidget(dna_btn) + + def create_dna_button(self, name, info, width, height): + """创建DNA按钮""" + btn = QtWidgets.QPushButton() + btn.setFixedSize(width, height) + + # 创建按钮布局 + layout = QtWidgets.QVBoxLayout(btn) + layout.setContentsMargins(2, 2, 2, 2) + layout.setSpacing(1) + + # 创建图标标签 + icon_label = QtWidgets.QLabel() + icon_label.setAlignment(QtCore.Qt.AlignCenter) + + if info['img_path']: + pixmap = QtGui.QPixmap(info['img_path']) + scaled_pixmap = pixmap.scaled( + width - 4, + height - 16, + QtCore.Qt.KeepAspectRatio, + QtCore.Qt.SmoothTransformation + ) + icon_label.setPixmap(scaled_pixmap) + else: + icon_label.setText("No Image") + icon_label.setStyleSheet("color: #FFFFFF; font-size: 8px;") # 改为白色 + + # 创建文本标签 + text_label = QtWidgets.QLabel(name) + text_label.setAlignment(QtCore.Qt.AlignCenter) + text_label.setStyleSheet("color: #FFFFFF; font-size: 8px;") # 改为白色 + + layout.addWidget(icon_label) + layout.addWidget(text_label) + + # 设置样式 - 保持黑色背景,文字改为白色 + btn.setStyleSheet(""" + QPushButton { + background-color: #303030; + border: 1px solid #202020; + border-radius: 5px; + padding: 2px; + color: #FFFFFF; /* 按钮文字颜色改为白色 */ + } + QPushButton:hover { + background-color: #404040; + border: 1px solid #303030; + } + QPushButton:pressed { + background-color: #202020; + } + """) + + btn.setProperty('dna_path', info['dna_path']) + btn.clicked.connect(lambda: self.on_dna_selected(info['dna_path'])) + + return btn + + def on_dna_selected(self, dna_path): + """当DNA被选中时发出信号""" + self.dna_selected.emit(dna_path) + +class FlowLayout(QtWidgets.QLayout): + def __init__(self, parent=None): + super().__init__(parent) + self.itemList = [] + self.spacing_x = 5 + self.spacing_y = 5 + + def addItem(self, item): + self.itemList.append(item) + + def count(self): + return len(self.itemList) + + def itemAt(self, index): + if 0 <= index < len(self.itemList): + return self.itemList[index] + return None + + def takeAt(self, index): + if 0 <= index < len(self.itemList): + return self.itemList.pop(index) + return None + + def expandingDirections(self): + return QtCore.Qt.Orientations(QtCore.Qt.Orientation(0)) + + def hasHeightForWidth(self): + return True + + def heightForWidth(self, width): + height = self.doLayout(QtCore.QRect(0, 0, width, 0), True) + return height + + def setGeometry(self, rect): + super().setGeometry(rect) + self.doLayout(rect, False) + + def sizeHint(self): + return self.minimumSize() + + def minimumSize(self): + size = QtCore.QSize() + for item in self.itemList: + size = size.expandedTo(item.minimumSize()) + return size + + def doLayout(self, rect, testOnly): + x = rect.x() + y = rect.y() + lineHeight = 0 + for item in self.itemList: + widget = item.widget() + spaceX = self.spacing_x + spaceY = self.spacing_y + nextX = x + item.sizeHint().width() + spaceX + if nextX - spaceX > rect.right() and lineHeight > 0: + x = rect.x() + y = y + lineHeight + spaceY + nextX = x + item.sizeHint().width() + spaceX + lineHeight = 0 + if not testOnly: + item.setGeometry(QtCore.QRect(QtCore.QPoint(x, y), item.sizeHint())) + x = nextX + lineHeight = max(lineHeight, item.sizeHint().height()) + return y + lineHeight - rect.y() + +def create_browser(dna_path, img_path, parent=None): + """创建并返回DNA浏览器实例""" + return DNABrowserWidget(dna_path, img_path, parent) diff --git a/scripts/MetaFusion.py b/scripts/MetaFusion.py new file mode 100644 index 0000000..78f7255 --- /dev/null +++ b/scripts/MetaFusion.py @@ -0,0 +1,474 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +#===================================== IMPORTS ===================================== +# Standard library imports +import os +import sys +import webbrowser +import locale + +# Qt imports +from PySide2 import QtWidgets, QtCore, QtGui +from shiboken2 import wrapInstance + +# Maya imports +from maya import OpenMayaUI as omui +import maya.cmds as cmds +import maya.mel as mel + +#===================================== IMPORT MODULES ===================================== +# Standard library imports +import BodyPrep +import BatchImport +import DNA_Viewer +import DNA_Browser +from DNA_Browser import FlowLayout + +#===================================== CONSTANTS ===================================== +# Tool info +TOOL_NAME = "MetaFusion" +TOOL_VERSION = "Beta v1.0.0" +TOOL_AUTHOR = "Virtuos" +# UI Constants +TOOL_WSCL_NAME = "MetaFusionWorkSpaceControl" +TOOL_HELP_URL = f"http://10.72.61.59:3000/ArtGroup/{TOOL_NAME}/wiki" +DEFAULT_WINDOW_SIZE = (500, 800) + +# Paths +TOOL_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))).replace("\\", "/") +SCRIPTS_PATH = os.path.join(TOOL_PATH, "scripts").replace("\\", "/") +ICONS_PATH = os.path.join(TOOL_PATH, "icons").replace("\\", "/") +TOOL_ICON = os.path.join(ICONS_PATH, "logo.png").replace("\\", "/") + +# Metahuman paths +DATA_PATH = os.path.join(TOOL_PATH, "data").replace("\\", "/") +DNA_PATH = os.path.join(DATA_PATH, "dna").replace("\\", "/") +BODY_PATH = os.path.join(DATA_PATH, "body").replace("\\", "/") +IMG_PATH = os.path.join(DATA_PATH, "img").replace("\\", "/") +MAP_PATH = os.path.join(DATA_PATH, "map").replace("\\", "/") +MASKS_PATH = os.path.join(DATA_PATH, "masks").replace("\\", "/") +SHADERS_PATH = os.path.join(DATA_PATH, "shaders").replace("\\", "/") +MH4_PATH = os.path.join(DATA_PATH, "mh4").replace("\\", "/") +MH4_DNA_PATH = os.path.join(MH4_PATH, "dna").replace("\\", "/") +OUT_PATH = os.path.join(DATA_PATH, "out").replace("\\", "/") +SAVE_PATH = os.path.join(DATA_PATH, "save").replace("\\", "/") +MAYA_VERSION = cmds.about(version=True) +PLUGIN_PATH = os.path.join(TOOL_PATH, "plugins", f"{MAYA_VERSION}").replace("\\", "/") +if not os.path.exists(PLUGIN_PATH): + cmds.warning(f"Plugin path not found: {PLUGIN_PATH}") + +# 打印上面的所有变量 +print(f"TOOL_PATH: {TOOL_PATH}") +print(f"SCRIPTS_PATH: {SCRIPTS_PATH}") +print(f"ICONS_PATH: {ICONS_PATH}") +print(f"TOOL_ICON: {TOOL_ICON}") +print(f"DATA_PATH: {DATA_PATH}") +print(f"DNA_PATH: {DNA_PATH}") +print(f"BODY_PATH: {BODY_PATH}") +print(f"IMG_PATH: {IMG_PATH}") +print(f"MAP_PATH: {MAP_PATH}") +print(f"MASKS_PATH: {MASKS_PATH}") +print(f"SHADERS_PATH: {SHADERS_PATH}") + +#===================================== LANGUAGE SETTINGS ===================================== +TOOL_LANG = 'en_US' +SUPPORTED_LANGUAGES = ['en_US', 'zh_CN'] + +LANG = { + "en_US": { + "MetaFusion": "MetaFusion", + "Load DNA": "Load DNA", + "Prepare Body": "Prepare Body", + "Help": "Help", + "Switch Language": "Switch Language", + "EN": "EN", + "ZH": "ZH", + "English": "English", + "Chinese": "Chinese", + "Language switched": "Language switched", + "DNA Samples": "DNA Samples", + "Prepare": "Prepare", + "Import": "Import", + "Body Prepare": "Body Prepare", + "Batch Import": "Batch Import", + "DNA Edit": "DNA Edit", + "Open DNA Viewer": "Open DNA Viewer", + "Load DNA": "Load DNA", + "DNA File:": "DNA File:" + }, + "zh_CN": { + "MetaFusion": "MetaFusion", + "DNA Samples": "DNA 样本", + "Load DNA": "加载 DNA", + "Prepare Body": "准备身体", + "Help": "帮助", + "Switch Language": "切换语言", + "EN": "英文", + "ZH": "中文", + "English": "英语", + "Chinese": "中文", + "Language switched": "语言已切换", + "DNA Samples": "DNA 样本", + "Prepare": "准备", + "Import": "导入", + "Body Prepare": "身体准备", + "Batch Import": "批量导入", + "DNA Edit": "DNA 编辑", + "Open DNA Viewer": "打开 DNA 查看器", + "Load DNA": "加载 DNA", + "DNA File:": "DNA 文件:" + } +} + +#===================================== UTILITY FUNCTIONS ===================================== +def get_system_encoding(): + encoding = sys.getdefaultencoding() + if encoding.lower() == 'ascii': + encoding = locale.getpreferredencoding() + return encoding + +def maya_main_window(): + main_window_ptr = omui.MQtUtil.mainWindow() + return wrapInstance(int(main_window_ptr), QtWidgets.QWidget) + +#===================================== UI COMPONENTS ===================================== +class MainButton(QtWidgets.QPushButton): + DEFAULT_COLORS = { + "normal": "#D0D0D0", + "hover": "#E0E0E0", + "pressed": "#C0C0C0" + } + + def __init__(self, text="", icon=None, color=None, hover_color=None, pressed_color=None): + super().__init__(text) + if icon: + self.setIcon(icon) + self.setIconSize(QtCore.QSize(24, 24)) + self.setMinimumHeight(30) + colors = { + "normal": color or self.DEFAULT_COLORS["normal"], + "hover": hover_color or self.DEFAULT_COLORS["hover"], + "pressed": pressed_color or self.DEFAULT_COLORS["pressed"] + } + self.setStyleSheet(self._generate_style_sheet(**colors)) + + @staticmethod + def _generate_style_sheet(normal, hover, pressed): + return f""" + QPushButton {{ + background-color: {normal}; + color: #303030; + border-radius: 10px; + padding: 5px; + font-weight: bold; + text-align: center; + }} + QPushButton:hover {{ + background-color: {hover}; + }} + QPushButton:pressed {{ + background-color: {pressed}; + }} + """ + +class BottomButton(QtWidgets.QPushButton): + def __init__(self, text="", icon=None): + super().__init__(text) + self.setMinimumHeight(20) + self.setStyleSheet(self._generate_style_sheet()) + self.setFont(QtGui.QFont("Microsoft YaHei", 10)) + + @staticmethod + def _generate_style_sheet(): + return """ + QPushButton { + background-color: transparent; + border: none; + color: gray; + font-weight: bold; + } + QPushButton:hover { + color: black; + } + """ + +#===================================== MAIN WINDOW ===================================== +class MainWindow(QtWidgets.QWidget): + + instance = None + + def __init__(self, parent=maya_main_window()): + super(MainWindow, self).__init__(parent) + self.setWindowTitle(f"{TOOL_NAME} - {TOOL_VERSION}") + self.setObjectName(TOOL_PATH) + self.setWindowFlags(QtCore.Qt.Window) + self.setAttribute(QtCore.Qt.WA_DeleteOnClose) + self.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred) + self.setMinimumSize(300, 800) + + self.create_widgets() + self.create_layouts() + self.create_connections() + + if os.path.exists(TOOL_ICON): + self.setWindowIcon(QtGui.QIcon(TOOL_ICON)) + else: + print(f"WARNING: Icon file not found: {TOOL_ICON}") + + @classmethod + def show_window(cls): + try: + if cmds.workspaceControl(TOOL_WSCL_NAME, exists=True): + cmds.deleteUI(TOOL_WSCL_NAME, control=True) + + if cls.instance is not None: + try: + cls.instance.close() + cls.instance.deleteLater() + except Exception: + pass + + cls.instance = cls() + cls.instance.dock_to_maya() + + return cls.instance + + except Exception as e: + print(f"Error showing {TOOL_NAME} window: {e}") + return None + + def dock_to_maya(self): + if cmds.workspaceControl(TOOL_WSCL_NAME, exists=True): + cmds.deleteUI(TOOL_WSCL_NAME) + try: + workspace_control = cmds.workspaceControl( + TOOL_WSCL_NAME, + label=TOOL_NAME, + floating=True, + retain=True, + resizeWidth=True, + initialWidth=500, + minimumWidth=500 + ) + cmds.workspaceControl(TOOL_WSCL_NAME, e=True, resizeWidth=True) + cmds.control(self.objectName(), e=True, p=workspace_control) + cmds.evalDeferred(lambda: cmds.workspaceControl(TOOL_WSCL_NAME, e=True, resizeWidth=True)) + except Exception as e: + print(f"Error creating workspace control: {e}") + +#===================================== UI COMPONENTS ===================================== + def create_widgets(self): + # DNA Samples group + self.dna_browser = DNA_Browser.create_browser(DNA_PATH, IMG_PATH) + self.dna_browser.dna_selected.connect(self.on_dna_selected) + + # DNA File input + self.dna_file_layout = QtWidgets.QHBoxLayout() + self.dna_file_label = QtWidgets.QLabel("DNA File:") + self.dna_file_input = QtWidgets.QLineEdit() + self.dna_file_input.setStyleSheet(""" + QLineEdit { + background-color: #303030; + color: #CCCCCC; + border: 1px solid #202020; + border-radius: 3px; + padding: 2px 5px; + font-size: 10px; + } + QLineEdit:hover { + border: 1px solid #404040; + } + QLineEdit:focus { + border: 1px solid #505050; + background-color: #353535; + } + """) + self.dna_file_input.textChanged.connect(self.on_dna_file_changed) + + self.dna_file_layout.addWidget(self.dna_file_label) + self.dna_file_layout.addWidget(self.dna_file_input) + + self.load_dna_btn = MainButton(LANG[TOOL_LANG]["Load DNA"], + color="#E6B3B3", hover_color="#F2BFBF", + pressed_color="#D99E9E") + + # Create function buttons + # Prepare group + self.prepare_btn = MainButton(LANG[TOOL_LANG]["Prepare"]) + self.body_prepare_btn = MainButton(LANG[TOOL_LANG]["Body Prepare"], color="#FFEBA1", hover_color="#FFF5B3", pressed_color="#FFE68A") + + # Import group + self.import_btn = MainButton(LANG[TOOL_LANG]["Import"]) + self.batch_import_btn = MainButton(LANG[TOOL_LANG]["Batch Import"], color="#A7C6ED", hover_color="#B2D3F0", pressed_color="#8BB8E0") + + # DNA Edit group + self.dna_edit_btn = MainButton(LANG[TOOL_LANG]["DNA Edit"]) + self.dna_viewer_btn = MainButton(LANG[TOOL_LANG]["Open DNA Viewer"], color="#B8E6B3", hover_color="#C4F2BF", pressed_color="#A3D99E") + + # Bottom buttons (existing code) + self.help_btn = BottomButton(LANG[TOOL_LANG]["Help"]) + self.help_btn.setToolTip(LANG[TOOL_LANG]["Help"]) + self.help_btn.setFixedSize(100, 20) + + self.lang_btn = BottomButton(LANG[TOOL_LANG]["ZH" if TOOL_LANG == 'en_US' else "EN"]) + self.lang_btn.setToolTip(LANG[TOOL_LANG]["Switch Language"]) + self.lang_btn.setFixedSize(30, 20) + + for button in [self.help_btn, self.lang_btn]: + button.setFont(QtGui.QFont("Microsoft YaHei", 10)) + + def create_layouts(self): + main_layout = QtWidgets.QVBoxLayout(self) + main_layout.setContentsMargins(2, 2, 2, 2) + + # Content layout + content_layout = QtWidgets.QVBoxLayout() + content_layout.setContentsMargins(5, 5, 5, 5) + + # DNA Samples group + dna_samples_group = QtWidgets.QGroupBox(LANG[TOOL_LANG]["DNA Samples"]) + dna_samples_layout = QtWidgets.QVBoxLayout(dna_samples_group) + dna_samples_layout.addWidget(self.dna_browser) + dna_samples_layout.addLayout(self.dna_file_layout) + dna_samples_layout.addWidget(self.load_dna_btn) + content_layout.addWidget(dna_samples_group) + + # Prepare group + prepare_group = QtWidgets.QGroupBox(LANG[TOOL_LANG]["Prepare"]) + prepare_layout = QtWidgets.QVBoxLayout(prepare_group) + prepare_layout.addWidget(self.body_prepare_btn) + content_layout.addWidget(prepare_group) + + # Import group + import_group = QtWidgets.QGroupBox(LANG[TOOL_LANG]["Import"]) + import_layout = QtWidgets.QVBoxLayout(import_group) + import_layout.addWidget(self.batch_import_btn) + content_layout.addWidget(import_group) + + # DNA Edit group + dna_edit_group = QtWidgets.QGroupBox(LANG[TOOL_LANG]["DNA Edit"]) + dna_edit_layout = QtWidgets.QVBoxLayout(dna_edit_group) + dna_edit_layout.addWidget(self.dna_viewer_btn) + content_layout.addWidget(dna_edit_group) + + main_layout.addLayout(content_layout) + main_layout.addStretch() + + # Bottom layout (existing code) + bottom_layout = QtWidgets.QHBoxLayout() + bottom_layout.setContentsMargins(5, 0, 5, 5) + + icon_label = QtWidgets.QLabel() + if os.path.exists(TOOL_ICON): + icon = QtGui.QPixmap(TOOL_ICON).scaled(24, 24, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation) + icon_label.setPixmap(icon) + + version_label = QtWidgets.QLabel(f"{TOOL_VERSION}") + version_label.setStyleSheet("color: gray; font-size: 12px;") + + bottom_layout.addWidget(icon_label) + bottom_layout.addWidget(version_label) + bottom_layout.addStretch() + bottom_layout.addWidget(self.help_btn) + bottom_layout.addWidget(self.lang_btn) + + main_layout.addLayout(bottom_layout) + + def create_connections(self): + # Connect function buttons + self.body_prepare_btn.clicked.connect(self.run_body_prepare) + self.batch_import_btn.clicked.connect(self.run_batch_import) + self.dna_viewer_btn.clicked.connect(self.run_dna_viewer) + + # Connect DNA Samples buttons + self.load_dna_btn.clicked.connect(self.run_load_dna) + + # Existing connections + self.help_btn.clicked.connect(self.help) + self.lang_btn.clicked.connect(self.switch_language) + +#===================================== FUNCTIONS ===================================== + #===================================== MAIN FUNCTIONS ===================================== + # Prepare group + def run_body_prepare(self): + import BodyPrep + BodyPrep.run() + + # Import group + def run_batch_import(self): + import BatchImport + BatchImport.run() + + # DNA Edit group + def run_dna_viewer(self): + import DNA_Viewer + DNA_Viewer.show() + + # DNA Samples group + def run_load_dna(self): + """加载选中的DNA文件""" + if hasattr(self, 'dna_list') and self.dna_list.currentItem(): + import DNA_Viewer + DNA_Viewer.load_dna(DNA_File) + else: + cmds.warning("Please select a DNA file first") + + #===================================== BOTTOM LAYOUT ===================================== + def help(self): + webbrowser.open(TOOL_HELP_URL) + + def switch_language(self): + global TOOL_LANG + TOOL_LANG = 'en_US' if TOOL_LANG == 'zh_CN' else 'zh_CN' + self.lang_btn.setText("ZH" if TOOL_LANG == 'en_US' else "EN") + self.retranslate_ui() + + QtWidgets.QToolTip.showText( + self.lang_btn.mapToGlobal(QtCore.QPoint(0, -30)), + "Language switched" if TOOL_LANG == 'en_US' else "语言已切换", + self.lang_btn + ) + + def retranslate_ui(self): + + # Update function button translations + self.load_dna_btn.setText(LANG[TOOL_LANG]["Load DNA"]) + self.body_prepare_btn.setText(LANG[TOOL_LANG]["Body Prepare"]) + self.batch_import_btn.setText(LANG[TOOL_LANG]["Batch Import"]) + self.dna_viewer_btn.setText(LANG[TOOL_LANG]["Open DNA Viewer"]) + + for button in [ + self.body_prepare_btn, + self.batch_import_btn, + self.dna_viewer_btn + ]: + button.setFont(QtGui.QFont("Microsoft YaHei", 10)) + + # Update bottom button translations + self.help_btn.setText(LANG[TOOL_LANG]["Help"]) + self.lang_btn.setText("ZH" if TOOL_LANG == 'en_US' else "EN") + for button in [ + self.help_btn, + self.lang_btn + ]: + button.setFont(QtGui.QFont("Microsoft YaHei", 10)) + + self.dna_file_label.setText(LANG[TOOL_LANG]["DNA File:"]) + + def on_dna_selected(self, dna_path): + """当DNA被选中时""" + global DNA_File + DNA_File = dna_path + self.dna_file_input.setText(DNA_File) + print(f"Selected DNA file: {DNA_File}") + + def on_dna_file_changed(self): + """当DNA文件输入框内容改变时""" + global DNA_File + DNA_File = self.dna_file_input.text() + print(f"DNA file path updated: {DNA_File}") + +#===================================== LAUNCH FUNCTIONS ===================================== +def show(): + return MainWindow.show_window() diff --git a/dna_viewer/__init__.py b/scripts/dna_viewer/__init__.py similarity index 100% rename from dna_viewer/__init__.py rename to scripts/dna_viewer/__init__.py diff --git a/dna_viewer/api.py b/scripts/dna_viewer/api.py similarity index 100% rename from dna_viewer/api.py rename to scripts/dna_viewer/api.py diff --git a/dna_viewer/builder/__init__.py b/scripts/dna_viewer/builder/__init__.py similarity index 100% rename from dna_viewer/builder/__init__.py rename to scripts/dna_viewer/builder/__init__.py diff --git a/dna_viewer/builder/builder.py b/scripts/dna_viewer/builder/builder.py similarity index 100% rename from dna_viewer/builder/builder.py rename to scripts/dna_viewer/builder/builder.py diff --git a/dna_viewer/builder/config.py b/scripts/dna_viewer/builder/config.py similarity index 100% rename from dna_viewer/builder/config.py rename to scripts/dna_viewer/builder/config.py diff --git a/dna_viewer/builder/joint.py b/scripts/dna_viewer/builder/joint.py similarity index 100% rename from dna_viewer/builder/joint.py rename to scripts/dna_viewer/builder/joint.py diff --git a/dna_viewer/builder/maya/__init__.py b/scripts/dna_viewer/builder/maya/__init__.py similarity index 100% rename from dna_viewer/builder/maya/__init__.py rename to scripts/dna_viewer/builder/maya/__init__.py diff --git a/dna_viewer/builder/maya/mesh.py b/scripts/dna_viewer/builder/maya/mesh.py similarity index 100% rename from dna_viewer/builder/maya/mesh.py rename to scripts/dna_viewer/builder/maya/mesh.py diff --git a/dna_viewer/builder/maya/skin_weights.py b/scripts/dna_viewer/builder/maya/skin_weights.py similarity index 100% rename from dna_viewer/builder/maya/skin_weights.py rename to scripts/dna_viewer/builder/maya/skin_weights.py diff --git a/dna_viewer/builder/maya/util.py b/scripts/dna_viewer/builder/maya/util.py similarity index 100% rename from dna_viewer/builder/maya/util.py rename to scripts/dna_viewer/builder/maya/util.py diff --git a/dna_viewer/builder/mesh.py b/scripts/dna_viewer/builder/mesh.py similarity index 100% rename from dna_viewer/builder/mesh.py rename to scripts/dna_viewer/builder/mesh.py diff --git a/dna_viewer/builder/rig_builder.py b/scripts/dna_viewer/builder/rig_builder.py similarity index 100% rename from dna_viewer/builder/rig_builder.py rename to scripts/dna_viewer/builder/rig_builder.py diff --git a/dna_viewer/common.py b/scripts/dna_viewer/common.py similarity index 100% rename from dna_viewer/common.py rename to scripts/dna_viewer/common.py diff --git a/dna_viewer/dnalib/__init__.py b/scripts/dna_viewer/dnalib/__init__.py similarity index 100% rename from dna_viewer/dnalib/__init__.py rename to scripts/dna_viewer/dnalib/__init__.py diff --git a/dna_viewer/dnalib/behavior.py b/scripts/dna_viewer/dnalib/behavior.py similarity index 100% rename from dna_viewer/dnalib/behavior.py rename to scripts/dna_viewer/dnalib/behavior.py diff --git a/dna_viewer/dnalib/definition.py b/scripts/dna_viewer/dnalib/definition.py similarity index 100% rename from dna_viewer/dnalib/definition.py rename to scripts/dna_viewer/dnalib/definition.py diff --git a/dna_viewer/dnalib/descriptor.py b/scripts/dna_viewer/dnalib/descriptor.py similarity index 100% rename from dna_viewer/dnalib/descriptor.py rename to scripts/dna_viewer/dnalib/descriptor.py diff --git a/dna_viewer/dnalib/dnalib.py b/scripts/dna_viewer/dnalib/dnalib.py similarity index 100% rename from dna_viewer/dnalib/dnalib.py rename to scripts/dna_viewer/dnalib/dnalib.py diff --git a/dna_viewer/dnalib/geometry.py b/scripts/dna_viewer/dnalib/geometry.py similarity index 100% rename from dna_viewer/dnalib/geometry.py rename to scripts/dna_viewer/dnalib/geometry.py diff --git a/dna_viewer/dnalib/layer.py b/scripts/dna_viewer/dnalib/layer.py similarity index 100% rename from dna_viewer/dnalib/layer.py rename to scripts/dna_viewer/dnalib/layer.py diff --git a/dna_viewer/model.py b/scripts/dna_viewer/model.py similarity index 100% rename from dna_viewer/model.py rename to scripts/dna_viewer/model.py diff --git a/dna_viewer/ui/__init__.py b/scripts/dna_viewer/ui/__init__.py similarity index 100% rename from dna_viewer/ui/__init__.py rename to scripts/dna_viewer/ui/__init__.py diff --git a/dna_viewer/ui/app.css b/scripts/dna_viewer/ui/app.css similarity index 100% rename from dna_viewer/ui/app.css rename to scripts/dna_viewer/ui/app.css diff --git a/dna_viewer/ui/app.py b/scripts/dna_viewer/ui/app.py similarity index 100% rename from dna_viewer/ui/app.py rename to scripts/dna_viewer/ui/app.py diff --git a/dna_viewer/ui/widgets.py b/scripts/dna_viewer/ui/widgets.py similarity index 100% rename from dna_viewer/ui/widgets.py rename to scripts/dna_viewer/ui/widgets.py diff --git a/dna_viewer/version.py b/scripts/dna_viewer/version.py similarity index 100% rename from dna_viewer/version.py rename to scripts/dna_viewer/version.py