From f5aec154d25dd644b550fe58a6610086b2a0413a Mon Sep 17 00:00:00 2001 From: Jeffreytsai1004 Date: Tue, 14 Jan 2025 01:16:46 +0800 Subject: [PATCH] first commit --- CleanPycache.bat | 5 + Install.mel | 17 ++ Install.py | 499 +++++++++++++++++++++++++++++++++++++++++++++++ README.md | 1 + 4 files changed, 522 insertions(+) create mode 100644 CleanPycache.bat create mode 100644 Install.mel create mode 100644 Install.py create mode 100644 README.md diff --git a/CleanPycache.bat b/CleanPycache.bat new file mode 100644 index 0000000..9c14806 --- /dev/null +++ b/CleanPycache.bat @@ -0,0 +1,5 @@ +@echo off +REM Delete all __pycache__ folders +for /d /r %%d in (__pycache__) do @if exist "%%d" rd /s /q "%%d" + +echo Cleaned up! \ No newline at end of file diff --git a/Install.mel b/Install.mel new file mode 100644 index 0000000..a5ea88b --- /dev/null +++ b/Install.mel @@ -0,0 +1,17 @@ +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 += "exec(open(INSTALL_PATH, encoding='utf-8').read())\n"; + + python($pythonCmd); +} + +install(); \ No newline at end of file diff --git a/Install.py b/Install.py new file mode 100644 index 0000000..b276b43 --- /dev/null +++ b/Install.py @@ -0,0 +1,499 @@ +#!/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 ===================================== +# Path configurations +try: + ROOT_PATH = os.path.dirname(INSTALL_PATH) +except NameError: + ROOT_PATH = os.path.dirname(os.path.abspath(__file__)) +# Tool information +TOOL_NAME = "MetaBox" +TOOL_VERSION = "Beta v1.0.0" +TOOL_AUTHOR = "Virtuos" +TOOL_LANG = 'en_US' +SCRIPTS_PATH = os.path.join(ROOT_PATH, "Scripts") +ICONS_PATH = os.path.join(ROOT_PATH, "icons") +TOOL_ICON = os.path.join(ICONS_PATH, "logo.png") +DEFAULT_ICON = "commandButton.png" +TOOL_HELP_URL = f"http://10.72.61.59:3000/ArtGroup/{TOOL_NAME}/wiki" +TOOL_WSCL_NAME = "ToolBoxWorkSpaceControl" +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} 1.0 {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() + + # 切换到新创建的工具架 + 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() + + # 移除工具架之前先获取当前工具架 + current_shelf = cmds.shelfTabLayout("ShelfLayout", query=True, selectTab=True) + + # 删除工具架和按钮 + 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() + + # 从Python路径中移除 + if SCRIPTS_PATH in sys.path: + sys.path.remove(SCRIPTS_PATH) + + # 删除工具架文件 + 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 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""" + app = QtWidgets.QApplication.instance() or QtWidgets.QApplication(sys.argv) + dialog = InstallDialog() + dialog.show() + return app.exec_() + +if __name__ == "__main__": + sys.exit(main()) + diff --git a/README.md b/README.md new file mode 100644 index 0000000..2be7010 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# MetaBox