#!/usr/bin/env python # -*- coding: utf-8 -*- #===================================== 1. Module Imports ===================================== import maya.OpenMayaUI as omui import maya.cmds as cmds import maya.mel as mel import webbrowser import sys import os from scripts import config try: from PySide2 import QtCore, QtGui, QtWidgets from shiboken2 import wrapInstance print("从PySide2加载Qt和shiboken2") except ImportError: try: from PySide6 import QtCore, QtGui, QtWidgets from shiboken6 import wrapInstance print("从PySide6加载Qt和shiboken6") except ImportError: try: from PySide import QtCore, QtGui, QtWidgets from shiboken import wrapInstance print("从PySide加载Qt和shiboken") except ImportError as e: print(f"Qt加载失败: {str(e)}") QtCore = QtGui = QtWidgets = None wrapInstance = None TOOL_NAME = config.TOOL_NAME TOOL_ICON = config.TOOL_ICON TOOL_COMMAND_ICON = config.TOOL_COMMAND_ICON TOOL_VERSION = config.TOOL_VERSION TOOL_AUTHOR = config.TOOL_AUTHOR TOOL_LANG = config.TOOL_LANG TOOL_WSCL_NAME = config.TOOL_WSCL_NAME TOOL_YEAR = config.TOOL_YEAR TOOL_MOD_FILENAME = config.TOOL_MOD_FILENAME SYSTEM_OS = config.SYSTEM_OS TOOL_PATH = config.TOOL_PATH ROOT_DIR = config.TOOL_PATH ICONS_PATH = config.ICONS_PATH SCRIPTS_PATH = config.SCRIPTS_PATH TOOL_STYLE_FILE = config.TOOL_STYLE_FILE MAYA_VERSION = config.MAYA_VERSION PYTHON_VERSION = config.PYTHON_VERSION PYTHON_VERSION_DIR = config.PYTHON_VERSION_DIR TOOL_HELP_URL = config.TOOL_HELP_URL PLUGIN_PATH = config.PLUGIN_PATH PYDNA_PATH = config.PYDNA_PATH TOOL_MAIN_SCRIPT = config.TOOL_MAIN_SCRIPT print(f"TOOL_NAME: {TOOL_NAME}") print(f"TOOL_VERSION: {TOOL_VERSION}") print(f"TOOL_AUTHOR: {TOOL_AUTHOR}") print(f"TOOL_LANG: {TOOL_LANG}") print(f"TOOL_WSCL_NAME: {TOOL_WSCL_NAME}") print(f"TOOL_YEAR: {TOOL_YEAR}") print(f"TOOL_MOD_FILENAME: {TOOL_MOD_FILENAME}") print(f"TOOL_HELP_URL: {TOOL_HELP_URL}") print(f"SYSTEM_OS: {SYSTEM_OS}") print(f"MAYA_VERSION: {MAYA_VERSION}") print(f"PYTHON: {PYTHON_VERSION_DIR}") print(f"PYTHON VERSION: {PYTHON_VERSION}") print(f"TOOL_PATH: {TOOL_PATH}") print(f"ROOT_DIR: {ROOT_DIR}") print(f"SCRIPTS_PATH: {SCRIPTS_PATH}") print(f"TOOL_STYLE_FILE: {TOOL_STYLE_FILE}") print(f"PLUGIN_PATH: {PLUGIN_PATH}") print(f"PYDNA_PATH: {PYDNA_PATH}") #===================================== 3. Utility Functions ===================================== def maya_main_window(): """获取Maya主窗口""" main_window_ptr = omui.MQtUtil.mainWindow() if main_window_ptr: return wrapInstance(int(main_window_ptr), QtWidgets.QWidget) return None def ensure_directory(directory_path): """确保目录存在""" if directory_path and isinstance(directory_path, str): if not os.path.exists(directory_path): os.makedirs(directory_path) print(f"Created directory: {directory_path}") return directory_path def get_maya_modules_dir(): """获取Maya模块目录""" maya_app_dir = cmds.internalVar(userAppDir=True) if maya_app_dir and isinstance(maya_app_dir, str): return ensure_directory(os.path.join(maya_app_dir, "modules")) return None #===================================== 4. UI Component Classes ===================================== class SetButton(QtWidgets.QPushButton): """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): with open(TOOL_STYLE_FILE, 'r', encoding='utf-8') as f: style = f.read() self.setStyleSheet(style) 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(): 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): """创建或更新Maya的.mod文件""" modules_dir = get_maya_modules_dir() # 获取所有Maya版本和对应的Python版本 version_map = { "2022": "python3", "2023": "python397", "2024": "python3108", "2025": "python311" } # 系统映射 os_paths = { "win64": "Windows", "linux": "Linux" } # 创建mod文件内容 mod_content = f"""+ {TOOL_NAME} {TOOL_VERSION} {TOOL_PATH} """ # 添加每个Maya版本的配置 for maya_version, python_version in version_map.items(): # 添加每个操作系统的配置 for os_name, os_path in os_paths.items(): mod_content += f""" if MAYA_VERSION == {maya_version} && PLATFORM == {os_name} scripts: {SCRIPTS_PATH} plug-ins: {os.path.join(PLUGIN_PATH, os_path)} XBMLANGPATH+:={ICONS_PATH} PATH+:={os.path.join(PLUGIN_PATH, os_path)} PATH+:={os.path.join(PLUGIN_PATH, os_path, "pydna", python_version)} PYTHONPATH+:={SCRIPTS_PATH} PYTHONPATH+:={os.path.join(PLUGIN_PATH, os_path, "pydna", python_version)} endif """ # 写入mod文件 mod_file_path = os.path.join(modules_dir, TOOL_MOD_FILENAME) try: with open(mod_file_path, "w", encoding="utf-8") as f: f.write(mod_content) print(f"Created mod file: {mod_file_path}") except Exception as e: print(f"Error creating mod file {mod_file_path}: {e}") 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 TOOL_PATH = r'{TOOL_PATH}' if TOOL_PATH not in sys.path: sys.path.insert(0, TOOL_PATH) SCRIPTS_PATH = r'{SCRIPTS_PATH}' if SCRIPTS_PATH not in sys.path: sys.path.insert(0, SCRIPTS_PATH) os.chdir(SCRIPTS_PATH) try: import MetaFusion MetaFusion.show() except ImportError as e: print("Error importing MetaFusion:", str(e)) print(f"Scripts path: {SCRIPTS_PATH}") print("sys.path:", sys.path) print(f"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(self) 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(self) 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": TOOL_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()