From bd163ae8a5bd80794fcbcf43028b70ddefe17bd4 Mon Sep 17 00:00:00 2001 From: jeffreytsai1004 Date: Sun, 23 Nov 2025 21:09:11 +0800 Subject: [PATCH] update --- 2024/README.md | 47 -- 2024/RELOAD_SHELF.py | 121 --- 2024/icons/README.md | 24 - 2024/icons/batchextrusion.png | Bin 13822 -> 0 bytes 2024/plug-ins/nexus_plugin.py | 58 -- 2024/scripts/animation_tools/__init__.py | 9 - 2024/scripts/modeling_tools/__init__.py | 13 - .../__pycache__/__init__.cpython-39.pyc | Bin 312 -> 0 bytes .../__pycache__/batchextrusion.cpython-39.pyc | Bin 20111 -> 0 bytes 2024/scripts/modeling_tools/batchextrusion.py | 761 ------------------ 2024/scripts/nexus_test.py | 31 - 2024/scripts/rigging_tools/__init__.py | 9 - 2024/scripts/userSetup.py | 200 ----- 2024/shelves/shelf_Nexus_Animation.mel | 43 - 2024/shelves/shelf_Nexus_Modeling.mel | 43 - 2024/shelves/shelf_Nexus_Rigging.mel | 43 - 2025/README.md | 47 -- 2025/RELOAD_SHELF.py | 121 --- 2025/icons/README.md | 24 - 2025/icons/batchextrusion.png | Bin 13822 -> 0 bytes 2025/plug-ins/nexus_plugin.py | 58 -- 2025/scripts/animation_tools/__init__.py | 9 - 2025/scripts/modeling_tools/__init__.py | 13 - .../__pycache__/__init__.cpython-39.pyc | Bin 312 -> 0 bytes .../__pycache__/batchextrusion.cpython-39.pyc | Bin 20111 -> 0 bytes 2025/scripts/modeling_tools/batchextrusion.py | 761 ------------------ 2025/scripts/nexus_test.py | 31 - 2025/scripts/rigging_tools/__init__.py | 9 - 2025/scripts/userSetup.py | 200 ----- 2025/shelves/shelf_Nexus_Animation.mel | 43 - 2025/shelves/shelf_Nexus_Modeling.mel | 43 - 2025/shelves/shelf_Nexus_Rigging.mel | 43 - 32 files changed, 2804 deletions(-) delete mode 100644 2024/README.md delete mode 100644 2024/RELOAD_SHELF.py delete mode 100644 2024/icons/README.md delete mode 100644 2024/icons/batchextrusion.png delete mode 100644 2024/plug-ins/nexus_plugin.py delete mode 100644 2024/scripts/animation_tools/__init__.py delete mode 100644 2024/scripts/modeling_tools/__init__.py delete mode 100644 2024/scripts/modeling_tools/__pycache__/__init__.cpython-39.pyc delete mode 100644 2024/scripts/modeling_tools/__pycache__/batchextrusion.cpython-39.pyc delete mode 100644 2024/scripts/modeling_tools/batchextrusion.py delete mode 100644 2024/scripts/nexus_test.py delete mode 100644 2024/scripts/rigging_tools/__init__.py delete mode 100644 2024/scripts/userSetup.py delete mode 100644 2024/shelves/shelf_Nexus_Animation.mel delete mode 100644 2024/shelves/shelf_Nexus_Modeling.mel delete mode 100644 2024/shelves/shelf_Nexus_Rigging.mel delete mode 100644 2025/README.md delete mode 100644 2025/RELOAD_SHELF.py delete mode 100644 2025/icons/README.md delete mode 100644 2025/icons/batchextrusion.png delete mode 100644 2025/plug-ins/nexus_plugin.py delete mode 100644 2025/scripts/animation_tools/__init__.py delete mode 100644 2025/scripts/modeling_tools/__init__.py delete mode 100644 2025/scripts/modeling_tools/__pycache__/__init__.cpython-39.pyc delete mode 100644 2025/scripts/modeling_tools/__pycache__/batchextrusion.cpython-39.pyc delete mode 100644 2025/scripts/modeling_tools/batchextrusion.py delete mode 100644 2025/scripts/nexus_test.py delete mode 100644 2025/scripts/rigging_tools/__init__.py delete mode 100644 2025/scripts/userSetup.py delete mode 100644 2025/shelves/shelf_Nexus_Animation.mel delete mode 100644 2025/shelves/shelf_Nexus_Modeling.mel delete mode 100644 2025/shelves/shelf_Nexus_Rigging.mel diff --git a/2024/README.md b/2024/README.md deleted file mode 100644 index 06c65ed..0000000 --- a/2024/README.md +++ /dev/null @@ -1,47 +0,0 @@ -# Nexus Maya 2023 Plugin - -## 目录结构 - -- **shelves/** - 工具架文件 (.mel 格式) - - shelf_Nexus_Modeling.mel - 建模工具架 - - shelf_Nexus_Rigging.mel - 绑定工具架 - - shelf_Nexus_Animation.mel - 动画工具架 - -- **scripts/** - Python/MEL 脚本 - - userSetup.py - Maya 启动时自动执行 - - nexus_test.py - 测试脚本 - - modeling_tools/ - 建模工具包 - - rigging_tools/ - 绑定工具包 - - animation_tools/ - 动画工具包 - -- **plug-ins/** - Maya 插件文件 (.py 或 .mll) - - nexus_plugin.py - Nexus 插件 - -- **icons/** - 工具图标 - - 工具架按钮使用的图标文件 - -## 使用方法 - -1. 在 NexusLauncher 的 config.json 中配置: - ```json - "maya_plugin_path": "E:/Zoroot/Dev/NexusLauncher/template/plugins/Nexus" - ``` - -2. 启动 Maya 2023,系统将自动: - - 加载 Nexus 工具架(Nexus_Modeling, Nexus_Rigging, Nexus_Animation) - - 执行 userSetup.py - - 设置环境变量 - -3. 测试: - - 检查是否出现 Nexus 工具架 - - 点击测试按钮 - - 应出现确认对话框 - -## 环境变量 - -启动时自动设置: -- MAYA_SHELF_PATH - 指向 shelves 目录 -- MAYA_SCRIPT_PATH - 指向 scripts 目录 -- PYTHONPATH - 指向 scripts 目录 -- MAYA_PLUG_IN_PATH - 指向 plug-ins 目录 -- XBMLANGPATH - 指向 icons 目录 diff --git a/2024/RELOAD_SHELF.py b/2024/RELOAD_SHELF.py deleted file mode 100644 index 81677ec..0000000 --- a/2024/RELOAD_SHELF.py +++ /dev/null @@ -1,121 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -""" -Nexus Shelf Reload Script -Use this script to reload Nexus shelves without restarting Maya -""" - -import maya.cmds as cmds -import maya.mel as mel -import os - -SHELF_NAMES = ["Nexus_Modeling", "Nexus_Rigging", "Nexus_Animation"] - -def reload_nexus_shelves(): - """Reload all Nexus shelves""" - print("=" * 60) - print("[Nexus] Reloading Nexus shelves...") - print("=" * 60) - - # Get shelf path - shelf_paths = os.environ.get('MAYA_SHELF_PATH', '') - - if not shelf_paths: - print("[Nexus] MAYA_SHELF_PATH not set") - try: - script_dir = os.path.dirname(os.path.abspath(__file__)) - shelf_paths = os.path.join(script_dir, "shelves") - print(f"[Nexus] Using script directory: {shelf_paths}") - except: - print("[Nexus] Could not determine shelf path") - return - - path_separator = ';' if os.name == 'nt' else ':' - shelf_path_list = shelf_paths.split(path_separator) - - for shelf_name in SHELF_NAMES: - # Find shelf file - shelf_file_found = None - for shelf_path in shelf_path_list: - shelf_path = shelf_path.strip() - if not shelf_path: - continue - - shelf_file = os.path.join(shelf_path, f"shelf_{shelf_name}.mel") - shelf_file = shelf_file.replace("\\", "/") - - if os.path.exists(shelf_file): - shelf_file_found = shelf_file - break - - if not shelf_file_found: - print(f"[Nexus] Could not find shelf_{shelf_name}.mel") - continue - - # Delete old shelf if exists - if cmds.shelfLayout(shelf_name, exists=True): - print(f"[Nexus] Deleting old shelf: {shelf_name}") - try: - cmds.deleteUI(shelf_name, layout=True) - except Exception as e: - print(f"[Nexus] Warning: Could not delete old shelf: {e}") - - # Load shelf using proper MEL method - print(f"[Nexus] Loading shelf: {shelf_name}") - try: - # Disable auto-save - mel.eval('optionVar -intValue "saveLastLoadedShelf" 0;') - - # Create shelf layout - mel.eval(f''' - global string $gShelfTopLevel; - if (`shelfLayout -exists {shelf_name}`) {{ - deleteUI -layout {shelf_name}; - }} - setParent $gShelfTopLevel; - shelfLayout -cellWidth 35 -cellHeight 34 {shelf_name}; - ''') - print(f"[Nexus] ✓ Created shelf layout: {shelf_name}") - - # Set parent and execute shelf script - mel.eval(f'setParent {shelf_name};') - mel.eval(f'source "{shelf_file_found}";') - mel.eval(f'shelf_{shelf_name}();') - print(f"[Nexus] ✓ Executed shelf script: shelf_{shelf_name}()") - - # Verify shelf - if cmds.shelfLayout(shelf_name, exists=True): - buttons = cmds.shelfLayout(shelf_name, query=True, childArray=True) or [] - if buttons: - print(f"[Nexus] ✓ Shelf loaded with {len(buttons)} button(s): {shelf_name}") - else: - print(f"[Nexus] ⚠ Shelf created but no buttons: {shelf_name}") - - # Remove auto-saved config - try: - maya_version = cmds.about(version=True).split()[0] - maya_app_dir = os.environ.get('MAYA_APP_DIR', '') - if maya_app_dir: - shelf_config = os.path.join(maya_app_dir, maya_version, "prefs", "shelves", f"shelf_{shelf_name}.mel") - if os.path.exists(shelf_config): - os.remove(shelf_config) - print(f"[Nexus] ✓ Removed auto-saved config: {shelf_name}") - except Exception as e: - print(f"[Nexus] Warning: {e}") - else: - print(f"[Nexus] ✗ Shelf layout not created: {shelf_name}") - - except Exception as e: - print(f"[Nexus] Error loading shelf {shelf_name}: {e}") - import traceback - traceback.print_exc() - continue - - print("=" * 60) - print("[Nexus] Shelf reload complete!") - print("=" * 60) - - -if __name__ == "__main__": - reload_nexus_shelves() diff --git a/2024/icons/README.md b/2024/icons/README.md deleted file mode 100644 index 438368a..0000000 --- a/2024/icons/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# Nexus Icons Directory - -存放工具架按钮使用的图标文件。 - -## 图标格式 - -- 支持格式:PNG, XPM, BMP -- 推荐格式:PNG(支持透明) -- 推荐尺寸:32x32 或 64x64 像素 - -## 命名规范 - -- 使用小写字母和下划线 -- 例如:`modeling_tool.png`, `rigging_helper.png` - -## 使用方法 - -在 MEL 工具架文件中引用: -```mel --image "your_icon.png" --image1 "your_icon.png" -``` - -Maya 会自动在 XBMLANGPATH 环境变量指定的目录中查找图标文件。 diff --git a/2024/icons/batchextrusion.png b/2024/icons/batchextrusion.png deleted file mode 100644 index e74527dd0778430ea6f5c325182ea8e758827f46..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13822 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 diff --git a/2024/plug-ins/nexus_plugin.py b/2024/plug-ins/nexus_plugin.py deleted file mode 100644 index d85d6e6..0000000 --- a/2024/plug-ins/nexus_plugin.py +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -""" -Nexus Plugin -Maya Python API 2.0 plugin -""" - -import sys -import maya.api.OpenMaya as om - -def maya_useNewAPI(): - """Tell Maya to use Python API 2.0""" - pass - - -class NexusCmd(om.MPxCommand): - """Nexus command""" - - kPluginCmdName = "nexusCmd" - - def __init__(self): - om.MPxCommand.__init__(self) - - def doIt(self, args): - """Execute the command""" - print("[Nexus] Nexus Plugin command executed!") - om.MGlobal.displayInfo("Nexus Plugin is working!") - - -def cmdCreator(): - """Create command instance""" - return NexusCmd() - - -def initializePlugin(mobject): - """Initialize plugin""" - mplugin = om.MFnPlugin(mobject, "Nexus", "1.0", "Any") - try: - mplugin.registerCommand( - NexusCmd.kPluginCmdName, - cmdCreator - ) - print("[Nexus] Plugin initialized: nexus_plugin.py") - except: - sys.stderr.write(f"Failed to register command: {NexusCmd.kPluginCmdName}\n") - raise - - -def uninitializePlugin(mobject): - """Uninitialize plugin""" - mplugin = om.MFnPlugin(mobject) - try: - mplugin.deregisterCommand(NexusCmd.kPluginCmdName) - print("[Nexus] Plugin uninitialized: nexus_plugin.py") - except: - sys.stderr.write(f"Failed to deregister command: {NexusCmd.kPluginCmdName}\n") - raise diff --git a/2024/scripts/animation_tools/__init__.py b/2024/scripts/animation_tools/__init__.py deleted file mode 100644 index b553fc0..0000000 --- a/2024/scripts/animation_tools/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -""" -Nexus Animation Tools Package -Animation utilities and helpers -""" - -__all__ = [] diff --git a/2024/scripts/modeling_tools/__init__.py b/2024/scripts/modeling_tools/__init__.py deleted file mode 100644 index cb40630..0000000 --- a/2024/scripts/modeling_tools/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -""" -Nexus Modeling Tools Package -General modeling utilities -""" - -from .batchextrusion import show_batch_extrusion_ui - -__all__ = [ - 'show_batch_extrusion_ui' -] diff --git a/2024/scripts/modeling_tools/__pycache__/__init__.cpython-39.pyc b/2024/scripts/modeling_tools/__pycache__/__init__.cpython-39.pyc deleted file mode 100644 index dd05dafca0410033fe4cf149f2303eac2b2c1e65..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 312 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 U&F$t6b4THeiRhngr7Z1#1EFxUjQ{`u diff --git a/2024/scripts/modeling_tools/batchextrusion.py b/2024/scripts/modeling_tools/batchextrusion.py deleted file mode 100644 index 350c0c7..0000000 --- a/2024/scripts/modeling_tools/batchextrusion.py +++ /dev/null @@ -1,761 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -# @Site : CGNICO Games -# @Author : Jeffrey Tsai - -""" -Maya Batch Extrusion Shell Mesh Tool - -Features: -1. Copy selected model to a specified number of layers -2. Extrude each layer, automatically deleting original layers to prevent overlap -3. Set vertex colors increasing from the inside out -4. Support model merging -""" - -import maya.cmds as cmds -import maya.mel as mel - -# UI Style configurations - -MESSAGE_BUTTON_STYLE = """ - QPushButton { - background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #D97706, stop:1 #B45309); - color: white; - border: 1px solid #92400E; - border-radius: 8px; - padding: 10px 16px; - font-weight: 600; - font-size: 12px; - min-width: 100px; - } - QPushButton:hover { - background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #F59E0B, stop:1 #D97706); - border-color: #D97706; - } - QPushButton:pressed { - background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #B45309, stop:1 #92400E); - border-color: #78350F; - } -""" - -SUCCESS_BUTTON_STYLE = """ - QPushButton { - background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #3B82F6, stop:1 #2563EB); - color: white; - border: none; - border-radius: 10px; - padding: 12px 20px; - font-weight: 600; - font-size: 13px; - min-width: 110px; - } - QPushButton:hover { - background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #60A5FA, stop:1 #3B82F6); - transform: translateY(-1px); - box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3); - } - QPushButton:pressed { - background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #2563EB, stop:1 #1D4ED8); - transform: translateY(0px); - } -""" - -WARNING_BUTTON_STYLE = """ - QPushButton { - background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #F59E0B, stop:1 #D97706); - color: white; - border: none; - border-radius: 10px; - padding: 12px 20px; - font-weight: 600; - font-size: 13px; - min-width: 110px; - } - QPushButton:hover { - background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #FBBF24, stop:1 #F59E0B); - transform: translateY(-1px); - box-shadow: 0 4px 12px rgba(245, 158, 11, 0.3); - } - QPushButton:pressed { - background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #D97706, stop:1 #B45309); - transform: translateY(0px); - } -""" - -class BatchExtrusion: - def __init__(self): - self.window_name = "BatchExtrusionWindow" - self.layers = 7 # Default number of layers - self.thickness = 0.1 # Default thickness - self.min_color = [0.0, 0.0, 0.0] # Inner layer color (black) - self.max_color = [1.0, 1.0, 1.0] # Outer layer color (white) - self.merge_layers = False # Whether to merge all layers - self.original_objects = [] # Original object list - self.generated_objects = [] # Generated object list - self.is_updating = False # Prevent recursive updates - self.button_info = [] # Store button information for delayed style application - self.qt_available = self.check_qt_availability() # Check Qt availability - - def check_qt_availability(self): - """Check Qt and related module availability""" - try: - maya_version = int(cmds.about(version=True)) - print(f"Maya version: {maya_version}") - - # Try PySide6 first (Maya 2022+) - try: - from PySide6 import QtWidgets, QtCore - import shiboken6 as shiboken - print("Using PySide6 (Maya 2022+)") - return "PySide6" - except ImportError: - try: - from PySide2 import QtWidgets, QtCore - import shiboken2 as shiboken - print("Using PySide2 (Maya 2020-2021)") - return "PySide2" - except ImportError: - print("PySide module is not available, using Maya native style") - return None - - except Exception as e: - print(f"Qt availability check failed: {str(e)}") - return None - - def create_ui(self): - """Create user interface""" - # If the window already exists, delete it - if cmds.window(self.window_name, exists=True): - cmds.deleteUI(self.window_name) - # Reset window preferences to avoid obscuring buttons due to old window dimensions - if cmds.windowPref(self.window_name, exists=True): - cmds.windowPref(self.window_name, remove=True) - - # Create window - cmds.window(self.window_name, title="Batch Extrusion Shell Mesh", widthHeight=(420, 600), sizeable=True) - - # Create main layout (using scroll layout to prevent small window obscuring bottom buttons) - scroll = cmds.scrollLayout(horizontalScrollBarThickness=0, verticalScrollBarThickness=12) - main_layout = cmds.columnLayout(adjustableColumn=True, rowSpacing=10, columnOffset=('both', 10)) - - # Title - cmds.text(label="Batch Extrusion Shell Mesh", font="boldLabelFont", height=30) - cmds.separator(height=10) - - # Number of layers control - cmds.text(label="Layers:", align="left", font="boldLabelFont") - self.layers_slider = cmds.intSliderGrp( - label="Layers: ", - field=True, - minValue=1, - maxValue=20, - value=self.layers, - step=1, - changeCommand=self.on_layers_changed - ) - - # Thickness control - cmds.text(label="Thickness:", align="left", font="boldLabelFont") - self.thickness_slider = cmds.floatSliderGrp( - label="Thickness: ", - field=True, - minValue=0.000001, - maxValue=10.0, - value=self.thickness, - precision=4, - step=0.01, - changeCommand=self.on_thickness_changed - ) - - # Vertex color control - cmds.text(label="Vertex Color:", align="left", font="boldLabelFont") - self.min_color_field = cmds.colorSliderGrp( - label="Inner layer: ", - rgb=self.min_color, - changeCommand=self.on_color_changed - ) - self.max_color_field = cmds.colorSliderGrp( - label="Outer layer: ", - rgb=self.max_color, - changeCommand=self.on_color_changed - ) - - # Merge options - cmds.text(label="Merge:", align="left", font="boldLabelFont") - self.merge_checkbox = cmds.checkBox( - label="Merge All Extruded Layers", - value=self.merge_layers, - changeCommand=self.on_merge_changed - ) - - # Preview area - cmds.text(label="Vertex Color Setting Preview:", align="left", font="boldLabelFont") - self.preview_text = cmds.scrollField( - editable=False, - wordWrap=True, - height=100, - backgroundColor=[0.2, 0.2, 0.2] - ) - - cmds.separator(height=10) - - # Operation buttons - self.create_styled_button("Extrude Shell Mesh", self.select_base_objects, SUCCESS_BUTTON_STYLE) - - self.create_styled_button("Clear All Extruded Layers", self.clear_all_layers, WARNING_BUTTON_STYLE) - - # Delay style application to ensure Qt controls are fully created - cmds.evalDeferred(self.apply_delayed_styles) - - # Show window - cmds.showWindow(self.window_name) - - # Initial preview - self.preview_colors() - - def create_styled_button(self, label, command, style): - """Create styled button""" - # Set base color based on style type - base_color = [0.4, 0.4, 0.4] # Default gray - if "SUCCESS" in style: - base_color = [0.2, 0.5, 0.8] # Blue - elif "WARNING" in style: - base_color = [0.8, 0.6, 0.2] # Orange - elif "MESSAGE" in style: - base_color = [0.7, 0.4, 0.1] # Deep orange - - # Create Maya button, set base color first - button_name = cmds.button( - label=label, - command=command, - height=35, - backgroundColor=base_color - ) - - # Store button info for delayed style application - self.button_info.append({ - 'name': button_name, - 'label': label, - 'style': style - }) - - # Immediately try to apply Qt style - self.apply_style_to_button(button_name, label, style) - - return button_name - - def apply_style_to_button(self, button_name, label, style): - """Apply style to a single button""" - # If Qt is not available, return False - if not self.qt_available: - print(f"Qt is not available, skipping style application: {label}") - return False - - try: - # Import corresponding modules based on detected Qt version - if self.qt_available == "PySide6": - from PySide6 import QtWidgets, QtCore - import shiboken6 as shiboken - elif self.qt_available == "PySide2": - from PySide2 import QtWidgets, QtCore - import shiboken2 as shiboken - else: - return False - - import maya.OpenMayaUI as omui - - # Get the Qt object of the button - button_ptr = omui.MQtUtil.findControl(button_name) - if button_ptr: - qt_button = shiboken.wrapInstance(int(button_ptr), QtWidgets.QPushButton) - if qt_button: - qt_button.setStyleSheet(style) - print(f"✓ Successfully applied Qt style: {label}") - return True - else: - print(f"✗ Failed to get Qt button object: {label}") - else: - print(f"✗ Failed to find button control: {label}") - - except Exception as e: - print(f"✗ Qt style application failed ({label}): {str(e)}") - - return False - - def apply_delayed_styles(self): - """Delay style application to all buttons""" - print("=== Starting delayed style application ===") - success_count = 0 - for button_info in self.button_info: - if cmds.control(button_info['name'], exists=True): - if self.apply_style_to_button( - button_info['name'], - button_info['label'], - button_info['style'] - ): - success_count += 1 - else: - print(f"✗ Button control does not exist: {button_info['label']}") - - print(f"=== Style application completed: {success_count}/{len(self.button_info)} successful ===") - - def show_dialog(self, title, message): - """Show styled confirmation dialog""" - dialog_name = "StyledConfirmDialog" - - # If the dialog already exists, delete it - if cmds.window(dialog_name, exists=True): - cmds.deleteUI(dialog_name) - - # Create dialog window - cmds.window(dialog_name, title=title, widthHeight=(300, 120), sizeable=False) - - # Main layout - main_layout = cmds.columnLayout(adjustableColumn=True, rowSpacing=10, columnOffset=('both', 15)) - - # Message text - cmds.text(label=message, align="center", wordWrap=True, height=40) - - cmds.separator(height=5) - - # Confirm button - self.create_styled_button("Yes", lambda *args: self.close_dialog(dialog_name), MESSAGE_BUTTON_STYLE) - - # Show dialog - cmds.showWindow(dialog_name) - - def close_dialog(self, dialog_name): - """Close dialog""" - if cmds.window(dialog_name, exists=True): - cmds.deleteUI(dialog_name) - - def calculate_vertex_colors(self, total_layers): - """Calculate vertex colors for each layer""" - if total_layers <= 1: - return [[1.0, 1.0, 1.0]] - - colors = [] - for i in range(total_layers): - # Calculate interpolation factor (0.0 to 1.0) - factor = i / (total_layers - 1) if total_layers > 1 else 0.0 - - # Interpolate between minimum and maximum colors - r = self.min_color[0] + (self.max_color[0] - self.min_color[0]) * factor - g = self.min_color[1] + (self.max_color[1] - self.min_color[1]) * factor - b = self.min_color[2] + (self.max_color[2] - self.min_color[2]) * factor - - colors.append([r, g, b]) - - return colors - - def on_layers_changed(self, *args): - """Callback function when the number of layers changes - Real-time update""" - if self.is_updating: - return - - self.layers = cmds.intSliderGrp(self.layers_slider, query=True, value=True) - self.preview_colors() - - # Update logic - if self.original_objects: # If there are base models - if self.merge_layers: - print("The models have been merged, please manually clean up and re-extrude!") - else: - print(f"Number of layers updated to {self.layers}, regenerating shell layers...") - # Clean up existing layers - self.clear_generated_objects() - # Regenerate layers - self.generate_layers() - - def on_thickness_changed(self, *args): - """Callback function when thickness changes - Real-time update""" - if self.is_updating: - return - - self.thickness = cmds.floatSliderGrp(self.thickness_slider, query=True, value=True) - self.preview_colors() - - # Update logic - if self.original_objects and self.generated_objects: # If there are base models and generated layers - if self.merge_layers: - print("The models have been merged, please manually clean up and re-extrude!") - else: - print(f"Thickness updated to {self.thickness}, updating thickness for all layers...") - self.update_thickness() - - def on_merge_changed(self, *args): - """Callback function when merge option changes""" - if self.is_updating: - return - self.merge_layers = cmds.checkBox(self.merge_checkbox, query=True, value=True) - self.preview_colors() - - def on_color_changed(self, *args): - """Callback function when colors change - Real-time update""" - if self.is_updating: - return - - self.min_color = cmds.colorSliderGrp(self.min_color_field, query=True, rgb=True) - self.max_color = cmds.colorSliderGrp(self.max_color_field, query=True, rgb=True) - self.preview_colors() - - # Update logic - if self.original_objects and self.generated_objects: # If there are base models and generated layers - if self.merge_layers: - print("The models have been merged, please manually clean up and re-extrude!") - else: - print("Colors updated, updating vertex colors for all layers...") - self.update_colors_for_all_layers() - - def preview_colors(self, *args): - """Preview color distribution""" - layers = self.layers - total_layers = layers + 1 # Include original layer - colors = self.calculate_vertex_colors(total_layers) - - preview_text = f"Colors preview ({total_layers} layers):\n\n" - - for i, color in enumerate(colors): - r, g, b = color - if i == 0: - layer_name = "Original layer" - else: - layer_name = f"Layer {i}" - - preview_text += f"{layer_name}: RGB({r:.2f}, {g:.2f}, {b:.2f})\n" - - cmds.scrollField(self.preview_text, edit=True, text=preview_text) - - def select_base_objects(self, *args): - """Select base objects and start extrusion""" - selected = cmds.ls(selection=True, type='transform') - if not selected: - cmds.warning("Please select the models to process first") - return - - # Clean up existing layers - self.clear_generated_objects() - - # Save original objects - self.original_objects = selected[:] - - # Generate layers immediately - self.generate_layers() - - self.show_dialog("Completed", f"Processed {len(selected)} objects, generated {self.layers} shell layers") - - def clear_all_layers(self, *args): - """Clear all generated layers""" - # Check if merge option is checked - if self.merge_layers: - print("The models have been merged, please manually clean up and re-extrude!") - return - self.clear_generated_objects() - self.show_dialog("Completed", "All generated layers have been cleared") - - def clear_generated_objects(self): - """Clear generated objects""" - for obj in self.generated_objects: - if cmds.objExists(obj): - cmds.delete(obj) - self.generated_objects = [] - - def generate_layers(self): - """Generate shell layers""" - if not self.original_objects: - print("Error: No original objects selected") - return - - try: - print(f"=== Starting generation of {self.layers} shell layers ===") - - created_objects = [] - - for obj_index, original_obj in enumerate(self.original_objects): - print(f"Processing object {obj_index + 1}: {original_obj}") - - # Collect all layer objects (including original object) - all_layers = [original_obj] - - # Create layers one by one (always from the original object) - for layer_num in range(1, self.layers + 1): - print(f" Creating layer {layer_num}...") - - # Key: Always copy from the original object, not from the previous layer - new_layer = self.extrude_layer_correct(original_obj, layer_num, self.thickness) - if new_layer: - all_layers.append(new_layer) - created_objects.append(new_layer) - print(f" ✓ Layer {layer_num} completed") - else: - print(f" ✗ Layer {layer_num} failed, stopping") - break - - # Set vertex colors - print(f" Setting vertex colors for {len(all_layers)} layers...") - self.apply_colors(all_layers) - - # Save results - self.generated_objects = created_objects - print(f"=== Completed! Created {len(created_objects)} layers ===") - - # Check if merge option is checked - if self.merge_layers and len(created_objects) > 0: - print("=== Starting simple merge ===") - self.simple_merge_objects() - else: - print("=== Skipping merge (not checked or no objects) ===") - - except Exception as e: - print(f"Generation failed: {str(e)}") - # Cleanup - for obj in created_objects: - if cmds.objExists(obj): - try: - cmds.delete(obj) - except: - pass - - def simple_merge_objects(self): - """Simple merge method to avoid complex operations""" - try: - all_objects = self.original_objects + self.generated_objects - print(f"Simple merge {len(all_objects)} objects") - - if len(all_objects) > 1: - # Only do basic merge - existing_objects = [obj for obj in all_objects if cmds.objExists(obj)] - - if len(existing_objects) > 1: - cmds.select(existing_objects, replace=True) - merged = cmds.polyUnite(existing_objects, name="BatchExtruded_Simple")[0] - print(f"Simple merge completed: {merged}") - - # Update object list - self.original_objects = [merged] - self.generated_objects = [] - else: - print("Not enough objects to merge") - else: - print("Not enough objects to merge") - - except Exception as e: - print(f"Simple merge failed: {str(e)}") - - def extrude_layer_correct(self, obj, layer_index, thickness): - """Follow the correct Maya workflow: duplicate -> extrude -> delete by inverse selection""" - duplicated = None - try: - print(f" === Correct workflow for layer {layer_index} ===") - - # 1. Verify object - if not cmds.objExists(obj): - print(f" Error: Object {obj} does not exist") - return None - - # 2. Copy model - duplicated = cmds.duplicate(obj, name=f"Layer_{layer_index}")[0] - print(f" Copy completed: {duplicated}") - - # 3. Switch to face mode and select all faces - print(f" Switching to face mode and selecting all faces...") - - # Switch to component mode - cmds.selectMode(component=True) - cmds.selectType(facet=True) - - # Select all faces directly (more reliable method) - try: - cmds.select(f"{duplicated}.f[*]", replace=True) - selected_faces = cmds.ls(selection=True, flatten=True) - print(f" Selected faces count: {len(selected_faces)}") - - if len(selected_faces) == 0: - # Backup method 1: Select object first then convert - print(f" Direct selection failed, trying conversion method...") - cmds.selectMode(object=True) - cmds.select(duplicated, replace=True) - cmds.selectMode(component=True) - cmds.selectType(facet=True) - - # Use mel command to convert - try: - mel.eval("ConvertSelectionToFaces;") - selected_faces = cmds.ls(selection=True, flatten=True) - print(f" Selected faces count: {len(selected_faces)}") - except Exception as mel_error: - print(f" MEL conversion failed: {str(mel_error)}") - - # Backup method 2: Use polyEvaluate to get face count, then select - try: - face_count = cmds.polyEvaluate(duplicated, face=True) - if face_count > 0: - face_range = f"{duplicated}.f[0:{face_count-1}]" - cmds.select(face_range, replace=True) - selected_faces = cmds.ls(selection=True, flatten=True) - print(f" Selected faces count: {len(selected_faces)}") - except Exception as backup_error: - print(f" Backup method also failed: {str(backup_error)}") - - except Exception as select_error: - print(f" Face selection failed: {str(select_error)}") - raise select_error - - # Verify face selection success - if len(selected_faces) == 0: - print(f" Error: No faces selected") - return None - - print(f" Selected {len(selected_faces)} faces, starting extrusion...") - - # Execute extrusion (thickness=0) - extrude_result = cmds.polyExtrudeFacet( - constructionHistory=True, - keepFacesTogether=True, - divisions=1, - twist=0, - taper=1, - off=0, - thickness=0, - smoothingAngle=30 - ) - print(f" Extrusion command completed: {extrude_result}") - - # Set cumulative thickness (each layer's thickness is cumulative) - if extrude_result: - extrude_node = extrude_result[0] - # Cumulative thickness = base thickness * layer number - cumulative_thickness = thickness * layer_index - cmds.setAttr(f"{extrude_node}.thickness", cumulative_thickness) - print(f" Set cumulative thickness: {cumulative_thickness} (Layer {layer_index})") - - # 4. Invert selection and delete internal faces - print(f" Inverting selection and deleting internal faces...") - - # After extrusion, the original faces are still selected - # We need to keep these original faces and delete the newly generated faces - original_selected_faces = cmds.ls(selection=True, flatten=True) - print(f" Original selected faces count: {len(original_selected_faces)}") - - # Get all faces after extrusion - all_faces_after_extrude = cmds.ls(f"{duplicated}.f[*]", flatten=True) - print(f" Total faces after extrusion: {len(all_faces_after_extrude)}") - - # Calculate newly generated faces (faces to delete) - if len(all_faces_after_extrude) > len(original_selected_faces): - # New faces = All faces - Original faces - new_faces = [face for face in all_faces_after_extrude if face not in original_selected_faces] - - if new_faces: - print(f" Deleting {len(new_faces)} newly generated faces") - cmds.select(new_faces, replace=True) - cmds.delete() - print(f" Deleted successfully, keeping original faces as shell") - else: - print(f" No newly generated faces found") - else: - print(f" No faces added, skipping deletion") - - # 5. Return to object mode - cmds.selectMode(object=True) - cmds.select(clear=True) - - print(f" === Layer {layer_index} completed ===") - return duplicated - - except Exception as e: - print(f" Layer {layer_index} failed: {str(e)}") - if duplicated and cmds.objExists(duplicated): - try: - cmds.delete(duplicated) - except: - pass - return None - - def apply_colors(self, layer_objects): - """Simple vertex color application""" - try: - total_layers = len(layer_objects) - colors = self.calculate_vertex_colors(total_layers) - - print(f" Setting colors for {total_layers} layers...") - - for i, obj in enumerate(layer_objects): - if cmds.objExists(obj) and i < len(colors): - color = colors[i] - print(f" Layer {i}: {obj} -> RGB{color}") - - # Set vertex colors - try: - # Select all vertices - cmds.select(f"{obj}.vtx[*]", replace=True) - - # Apply color - cmds.polyColorPerVertex(rgb=color, colorDisplayOption=True) - - # Enable vertex color display - cmds.setAttr(f"{obj}.displayColors", 1) - - except Exception as color_error: - print(f" Setting vertex colors failed: {str(color_error)}") - - # Clear selection - cmds.select(clear=True) - print(f" Vertex colors set successfully") - except Exception as e: - print(f" Vertex color application failed: {str(e)}") - - def update_thickness(self): - """Update thickness for all layers""" - try: - print(f"=== Updating thickness to {self.thickness} ===") - - for layer_index, obj in enumerate(self.generated_objects, 1): - if cmds.objExists(obj): - print(f" Updating layer {layer_index}: {obj}") - - # Find extrude node - history = cmds.listHistory(obj, pruneDagObjects=True) - extrude_nodes = [node for node in history if cmds.nodeType(node) == 'polyExtrudeFace'] - - if extrude_nodes: - extrude_node = extrude_nodes[0] # Take the latest extrude node - # Set cumulative thickness - cumulative_thickness = self.thickness * layer_index - cmds.setAttr(f"{extrude_node}.thickness", cumulative_thickness) - print(f" ✓ Updated thickness: {cumulative_thickness}") - else: - print(f" ✗ No extrude node found") - - print(f"=== Thickness updated successfully ===") - - except Exception as e: - print(f"Thickness update failed: {str(e)}") - - def update_colors_for_all_layers(self): - """Update colors for all layers""" - try: - print(f"=== Updating colors for all layers ===") - - # Collect all layer objects (including original objects) - all_layers = self.original_objects + self.generated_objects - - # Reapply colors - self.apply_colors(all_layers) - - print(f"=== Colors updated successfully ===") - - except Exception as e: - print(f"Colors update failed: {str(e)}") - -# Create and display UI function -def show_batch_extrusion_ui(): - """Show batch extrusion UI""" - batch_extrusion = BatchExtrusion() - batch_extrusion.create_ui() - -# If run this script directly -if __name__ == "__main__": - show_batch_extrusion_ui() diff --git a/2024/scripts/nexus_test.py b/2024/scripts/nexus_test.py deleted file mode 100644 index 8a8eb10..0000000 --- a/2024/scripts/nexus_test.py +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -""" -Nexus Test Script -Simple test to verify the Nexus plugin system is working -""" - -import maya.cmds as cmds - - -def run_test(): - """Run a simple test""" - print("[Nexus Test] Running test...") - - # Show confirmation dialog - result = cmds.confirmDialog( - title='Nexus Test', - message='Nexus Plugin System is working correctly!', - button=['OK'], - defaultButton='OK', - cancelButton='OK', - dismissString='OK' - ) - - print(f"[Nexus Test] Test completed: {result}") - return result - - -if __name__ == "__main__": - run_test() diff --git a/2024/scripts/rigging_tools/__init__.py b/2024/scripts/rigging_tools/__init__.py deleted file mode 100644 index a987049..0000000 --- a/2024/scripts/rigging_tools/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -""" -Nexus Rigging Tools Package -Character rigging utilities -""" - -__all__ = [] diff --git a/2024/scripts/userSetup.py b/2024/scripts/userSetup.py deleted file mode 100644 index 5d41610..0000000 --- a/2024/scripts/userSetup.py +++ /dev/null @@ -1,200 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -""" -Nexus Maya 2023 - User Setup Script -Automatically executed when Maya starts -""" - -import maya.cmds as cmds -import maya.mel as mel -import maya.utils -import os -import sys - -SHELF_NAMES = ["Nexus_Modeling", "Nexus_Rigging", "Nexus_Animation"] - -def load_nexus_shelves(): - """Load all Nexus shelves (force refresh)""" - try: - shelf_paths = os.environ.get('MAYA_SHELF_PATH', '') - - if not shelf_paths: - print("[Nexus] MAYA_SHELF_PATH not set, trying alternative method...") - try: - script_dir = os.path.dirname(os.path.abspath(__file__)) - shelf_paths = os.path.join(os.path.dirname(script_dir), "shelves") - except: - print("[Nexus] Could not determine shelf path, skipping shelf load") - return - - path_separator = ';' if os.name == 'nt' else ':' - shelf_path_list = shelf_paths.split(path_separator) - - for shelf_name in SHELF_NAMES: - shelf_file_found = None - for shelf_path in shelf_path_list: - shelf_path = shelf_path.strip() - if not shelf_path: - continue - - shelf_file = os.path.join(shelf_path, f"shelf_{shelf_name}.mel") - shelf_file = shelf_file.replace("\\", "/") - - if os.path.exists(shelf_file): - shelf_file_found = shelf_file - print(f"[Nexus] Found shelf file: {shelf_file}") - break - - if not shelf_file_found: - print(f"[Nexus] Could not find shelf_{shelf_name}.mel") - continue - - # Delete old shelf if exists - if cmds.shelfLayout(shelf_name, exists=True): - print(f"[Nexus] Deleting old shelf: {shelf_name}") - try: - cmds.deleteUI(shelf_name, layout=True) - except Exception as e: - print(f"[Nexus] Warning: Could not delete old shelf: {e}") - - # Load shelf using proper MEL method - print(f"[Nexus] Loading shelf: {shelf_name}") - try: - # Disable auto-save - mel.eval('optionVar -intValue "saveLastLoadedShelf" 0;') - - # Create shelf layout - mel.eval(f''' - global string $gShelfTopLevel; - if (`shelfLayout -exists {shelf_name}`) {{ - deleteUI -layout {shelf_name}; - }} - setParent $gShelfTopLevel; - shelfLayout -cellWidth 35 -cellHeight 34 {shelf_name}; - ''') - print(f"[Nexus] ✓ Created shelf layout: {shelf_name}") - - # Set parent and execute shelf script - mel.eval(f'setParent {shelf_name};') - mel.eval(f'source "{shelf_file_found}";') - mel.eval(f'shelf_{shelf_name}();') - print(f"[Nexus] ✓ Executed shelf script: shelf_{shelf_name}()") - - # Verify shelf - if cmds.shelfLayout(shelf_name, exists=True): - buttons = cmds.shelfLayout(shelf_name, query=True, childArray=True) or [] - if buttons: - print(f"[Nexus] ✓ Shelf loaded with {len(buttons)} button(s): {shelf_name}") - else: - print(f"[Nexus] ⚠ Shelf created but no buttons: {shelf_name}") - - # Remove auto-saved config - try: - maya_version = cmds.about(version=True).split()[0] - maya_app_dir = os.environ.get('MAYA_APP_DIR', '') - if maya_app_dir: - shelf_config = os.path.join(maya_app_dir, maya_version, "prefs", "shelves", f"shelf_{shelf_name}.mel") - if os.path.exists(shelf_config): - os.remove(shelf_config) - print(f"[Nexus] ✓ Removed auto-saved config: {shelf_name}") - except Exception as e: - print(f"[Nexus] Warning: {e}") - else: - print(f"[Nexus] ✗ Shelf layout not created: {shelf_name}") - - except Exception as e: - print(f"[Nexus] Error loading shelf {shelf_name}: {e}") - import traceback - traceback.print_exc() - continue - - except Exception as e: - print(f"[Nexus] Error in load_nexus_shelves: {e}") - import traceback - traceback.print_exc() - - -def load_nexus_plugins(): - """Load Nexus plugins""" - try: - plugin_paths = os.environ.get('MAYA_PLUG_IN_PATH', '') - - if not plugin_paths: - print("[Nexus] MAYA_PLUG_IN_PATH not set") - return - - path_separator = ';' if os.name == 'nt' else ':' - plugin_path_list = plugin_paths.split(path_separator) - - plugins_to_load = ["nexus_plugin.py"] - - for plugin_name in plugins_to_load: - plugin_found = False - for plugin_path in plugin_path_list: - plugin_path = plugin_path.strip() - if not plugin_path: - continue - - plugin_file = os.path.join(plugin_path, plugin_name) - if os.path.exists(plugin_file): - plugin_found = True - break - - if not plugin_found: - print(f"[Nexus] Plugin not found: {plugin_name}") - continue - - # Load plugin - try: - if not cmds.pluginInfo(plugin_name, query=True, loaded=True): - cmds.loadPlugin(plugin_name) - print(f"[Nexus] ✓ Plugin loaded: {plugin_name}") - else: - print(f"[Nexus] Plugin already loaded: {plugin_name}") - except Exception as e: - print(f"[Nexus] Error loading plugin {plugin_name}: {e}") - - except Exception as e: - print(f"[Nexus] Error in load_nexus_plugins: {e}") - import traceback - traceback.print_exc() - - -# Deferred execution -def initialize_nexus(): - """Initialize Nexus plugin system""" - print("=" * 80) - print("[Nexus] Initializing Nexus Maya 2023 Plugin System...") - print("=" * 80) - - load_nexus_shelves() - load_nexus_plugins() - - print("=" * 80) - print("[Nexus] Nexus Plugin System Initialized!") - print("=" * 80) - - -# Execute after Maya is fully loaded -maya.utils.executeDeferred(initialize_nexus) - - -# Cleanup on exit -def cleanup_on_exit(): - """Cleanup when Maya exits""" - print("[Nexus] Cleaning up...") - - # Unload plugins - plugins_to_unload = ["nexus_plugin.py"] - for plugin_name in plugins_to_unload: - if cmds.pluginInfo(plugin_name, query=True, loaded=True): - try: - cmds.unloadPlugin(plugin_name) - print(f"[Nexus] Plugin unloaded: {plugin_name}") - except: - pass - - -import atexit -atexit.register(cleanup_on_exit) diff --git a/2024/shelves/shelf_Nexus_Animation.mel b/2024/shelves/shelf_Nexus_Animation.mel deleted file mode 100644 index a7ff9a1..0000000 --- a/2024/shelves/shelf_Nexus_Animation.mel +++ /dev/null @@ -1,43 +0,0 @@ -global proc shelf_Nexus_Animation () { - global string $gBuffStr; - global string $gBuffStr0; - global string $gBuffStr1; - - - shelfButton - -enableCommandRepeat 1 - -flexibleWidthType 3 - -flexibleWidthValue 32 - -enable 1 - -width 35 - -height 34 - -manage 1 - -visible 1 - -preventOverride 0 - -annotation "Nexus Test - Verify plugin system" - -enableBackground 0 - -backgroundColor 0 0 0 - -highlightColor 0.321569 0.521569 0.65098 - -align "center" - -label "Test" - -labelOffset 0 - -rotation 0 - -flipX 0 - -flipY 0 - -useAlpha 1 - -font "plainLabelFont" - -imageOverlayLabel "Test" - -overlayLabelColor 0.8 0.8 0.8 - -overlayLabelBackColor 0 0 0 0.5 - -image "commandButton.png" - -image1 "commandButton.png" - -style "iconOnly" - -marginWidth 0 - -marginHeight 1 - -command "import nexus_test\nnexus_test.run_test()" - -sourceType "python" - -commandRepeatable 1 - -flat 1 - ; - -} diff --git a/2024/shelves/shelf_Nexus_Modeling.mel b/2024/shelves/shelf_Nexus_Modeling.mel deleted file mode 100644 index 590ff7f..0000000 --- a/2024/shelves/shelf_Nexus_Modeling.mel +++ /dev/null @@ -1,43 +0,0 @@ -global proc shelf_Nexus_Modeling () { - global string $gBuffStr; - global string $gBuffStr0; - global string $gBuffStr1; - - - shelfButton - -enableCommandRepeat 1 - -flexibleWidthType 3 - -flexibleWidthValue 32 - -enable 1 - -width 35 - -height 34 - -manage 1 - -visible 1 - -preventOverride 0 - -annotation "Batch Extrusion - Shell mesh tool with vertex colors" - -enableBackground 0 - -backgroundColor 0 0 0 - -highlightColor 0.321569 0.521569 0.65098 - -align "center" - -label "BatchExt" - -labelOffset 0 - -rotation 0 - -flipX 0 - -flipY 0 - -useAlpha 1 - -font "plainLabelFont" - -imageOverlayLabel "Extrude" - -overlayLabelColor 0.8 0.8 0.8 - -overlayLabelBackColor 0 0 0 0.5 - -image "batchextrusion.png" - -image1 "batchextrusion.png" - -style "iconOnly" - -marginWidth 0 - -marginHeight 1 - -command "import modeling_tools\nmodeling_tools.show_batch_extrusion_ui()" - -sourceType "python" - -commandRepeatable 1 - -flat 1 - ; - -} diff --git a/2024/shelves/shelf_Nexus_Rigging.mel b/2024/shelves/shelf_Nexus_Rigging.mel deleted file mode 100644 index 778a59f..0000000 --- a/2024/shelves/shelf_Nexus_Rigging.mel +++ /dev/null @@ -1,43 +0,0 @@ -global proc shelf_Nexus_Rigging () { - global string $gBuffStr; - global string $gBuffStr0; - global string $gBuffStr1; - - - shelfButton - -enableCommandRepeat 1 - -flexibleWidthType 3 - -flexibleWidthValue 32 - -enable 1 - -width 35 - -height 34 - -manage 1 - -visible 1 - -preventOverride 0 - -annotation "Nexus Test - Verify plugin system" - -enableBackground 0 - -backgroundColor 0 0 0 - -highlightColor 0.321569 0.521569 0.65098 - -align "center" - -label "Test" - -labelOffset 0 - -rotation 0 - -flipX 0 - -flipY 0 - -useAlpha 1 - -font "plainLabelFont" - -imageOverlayLabel "Test" - -overlayLabelColor 0.8 0.8 0.8 - -overlayLabelBackColor 0 0 0 0.5 - -image "commandButton.png" - -image1 "commandButton.png" - -style "iconOnly" - -marginWidth 0 - -marginHeight 1 - -command "import nexus_test\nnexus_test.run_test()" - -sourceType "python" - -commandRepeatable 1 - -flat 1 - ; - -} diff --git a/2025/README.md b/2025/README.md deleted file mode 100644 index 06c65ed..0000000 --- a/2025/README.md +++ /dev/null @@ -1,47 +0,0 @@ -# Nexus Maya 2023 Plugin - -## 目录结构 - -- **shelves/** - 工具架文件 (.mel 格式) - - shelf_Nexus_Modeling.mel - 建模工具架 - - shelf_Nexus_Rigging.mel - 绑定工具架 - - shelf_Nexus_Animation.mel - 动画工具架 - -- **scripts/** - Python/MEL 脚本 - - userSetup.py - Maya 启动时自动执行 - - nexus_test.py - 测试脚本 - - modeling_tools/ - 建模工具包 - - rigging_tools/ - 绑定工具包 - - animation_tools/ - 动画工具包 - -- **plug-ins/** - Maya 插件文件 (.py 或 .mll) - - nexus_plugin.py - Nexus 插件 - -- **icons/** - 工具图标 - - 工具架按钮使用的图标文件 - -## 使用方法 - -1. 在 NexusLauncher 的 config.json 中配置: - ```json - "maya_plugin_path": "E:/Zoroot/Dev/NexusLauncher/template/plugins/Nexus" - ``` - -2. 启动 Maya 2023,系统将自动: - - 加载 Nexus 工具架(Nexus_Modeling, Nexus_Rigging, Nexus_Animation) - - 执行 userSetup.py - - 设置环境变量 - -3. 测试: - - 检查是否出现 Nexus 工具架 - - 点击测试按钮 - - 应出现确认对话框 - -## 环境变量 - -启动时自动设置: -- MAYA_SHELF_PATH - 指向 shelves 目录 -- MAYA_SCRIPT_PATH - 指向 scripts 目录 -- PYTHONPATH - 指向 scripts 目录 -- MAYA_PLUG_IN_PATH - 指向 plug-ins 目录 -- XBMLANGPATH - 指向 icons 目录 diff --git a/2025/RELOAD_SHELF.py b/2025/RELOAD_SHELF.py deleted file mode 100644 index 81677ec..0000000 --- a/2025/RELOAD_SHELF.py +++ /dev/null @@ -1,121 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -""" -Nexus Shelf Reload Script -Use this script to reload Nexus shelves without restarting Maya -""" - -import maya.cmds as cmds -import maya.mel as mel -import os - -SHELF_NAMES = ["Nexus_Modeling", "Nexus_Rigging", "Nexus_Animation"] - -def reload_nexus_shelves(): - """Reload all Nexus shelves""" - print("=" * 60) - print("[Nexus] Reloading Nexus shelves...") - print("=" * 60) - - # Get shelf path - shelf_paths = os.environ.get('MAYA_SHELF_PATH', '') - - if not shelf_paths: - print("[Nexus] MAYA_SHELF_PATH not set") - try: - script_dir = os.path.dirname(os.path.abspath(__file__)) - shelf_paths = os.path.join(script_dir, "shelves") - print(f"[Nexus] Using script directory: {shelf_paths}") - except: - print("[Nexus] Could not determine shelf path") - return - - path_separator = ';' if os.name == 'nt' else ':' - shelf_path_list = shelf_paths.split(path_separator) - - for shelf_name in SHELF_NAMES: - # Find shelf file - shelf_file_found = None - for shelf_path in shelf_path_list: - shelf_path = shelf_path.strip() - if not shelf_path: - continue - - shelf_file = os.path.join(shelf_path, f"shelf_{shelf_name}.mel") - shelf_file = shelf_file.replace("\\", "/") - - if os.path.exists(shelf_file): - shelf_file_found = shelf_file - break - - if not shelf_file_found: - print(f"[Nexus] Could not find shelf_{shelf_name}.mel") - continue - - # Delete old shelf if exists - if cmds.shelfLayout(shelf_name, exists=True): - print(f"[Nexus] Deleting old shelf: {shelf_name}") - try: - cmds.deleteUI(shelf_name, layout=True) - except Exception as e: - print(f"[Nexus] Warning: Could not delete old shelf: {e}") - - # Load shelf using proper MEL method - print(f"[Nexus] Loading shelf: {shelf_name}") - try: - # Disable auto-save - mel.eval('optionVar -intValue "saveLastLoadedShelf" 0;') - - # Create shelf layout - mel.eval(f''' - global string $gShelfTopLevel; - if (`shelfLayout -exists {shelf_name}`) {{ - deleteUI -layout {shelf_name}; - }} - setParent $gShelfTopLevel; - shelfLayout -cellWidth 35 -cellHeight 34 {shelf_name}; - ''') - print(f"[Nexus] ✓ Created shelf layout: {shelf_name}") - - # Set parent and execute shelf script - mel.eval(f'setParent {shelf_name};') - mel.eval(f'source "{shelf_file_found}";') - mel.eval(f'shelf_{shelf_name}();') - print(f"[Nexus] ✓ Executed shelf script: shelf_{shelf_name}()") - - # Verify shelf - if cmds.shelfLayout(shelf_name, exists=True): - buttons = cmds.shelfLayout(shelf_name, query=True, childArray=True) or [] - if buttons: - print(f"[Nexus] ✓ Shelf loaded with {len(buttons)} button(s): {shelf_name}") - else: - print(f"[Nexus] ⚠ Shelf created but no buttons: {shelf_name}") - - # Remove auto-saved config - try: - maya_version = cmds.about(version=True).split()[0] - maya_app_dir = os.environ.get('MAYA_APP_DIR', '') - if maya_app_dir: - shelf_config = os.path.join(maya_app_dir, maya_version, "prefs", "shelves", f"shelf_{shelf_name}.mel") - if os.path.exists(shelf_config): - os.remove(shelf_config) - print(f"[Nexus] ✓ Removed auto-saved config: {shelf_name}") - except Exception as e: - print(f"[Nexus] Warning: {e}") - else: - print(f"[Nexus] ✗ Shelf layout not created: {shelf_name}") - - except Exception as e: - print(f"[Nexus] Error loading shelf {shelf_name}: {e}") - import traceback - traceback.print_exc() - continue - - print("=" * 60) - print("[Nexus] Shelf reload complete!") - print("=" * 60) - - -if __name__ == "__main__": - reload_nexus_shelves() diff --git a/2025/icons/README.md b/2025/icons/README.md deleted file mode 100644 index 438368a..0000000 --- a/2025/icons/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# Nexus Icons Directory - -存放工具架按钮使用的图标文件。 - -## 图标格式 - -- 支持格式:PNG, XPM, BMP -- 推荐格式:PNG(支持透明) -- 推荐尺寸:32x32 或 64x64 像素 - -## 命名规范 - -- 使用小写字母和下划线 -- 例如:`modeling_tool.png`, `rigging_helper.png` - -## 使用方法 - -在 MEL 工具架文件中引用: -```mel --image "your_icon.png" --image1 "your_icon.png" -``` - -Maya 会自动在 XBMLANGPATH 环境变量指定的目录中查找图标文件。 diff --git a/2025/icons/batchextrusion.png b/2025/icons/batchextrusion.png deleted file mode 100644 index e74527dd0778430ea6f5c325182ea8e758827f46..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13822 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 diff --git a/2025/plug-ins/nexus_plugin.py b/2025/plug-ins/nexus_plugin.py deleted file mode 100644 index d85d6e6..0000000 --- a/2025/plug-ins/nexus_plugin.py +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -""" -Nexus Plugin -Maya Python API 2.0 plugin -""" - -import sys -import maya.api.OpenMaya as om - -def maya_useNewAPI(): - """Tell Maya to use Python API 2.0""" - pass - - -class NexusCmd(om.MPxCommand): - """Nexus command""" - - kPluginCmdName = "nexusCmd" - - def __init__(self): - om.MPxCommand.__init__(self) - - def doIt(self, args): - """Execute the command""" - print("[Nexus] Nexus Plugin command executed!") - om.MGlobal.displayInfo("Nexus Plugin is working!") - - -def cmdCreator(): - """Create command instance""" - return NexusCmd() - - -def initializePlugin(mobject): - """Initialize plugin""" - mplugin = om.MFnPlugin(mobject, "Nexus", "1.0", "Any") - try: - mplugin.registerCommand( - NexusCmd.kPluginCmdName, - cmdCreator - ) - print("[Nexus] Plugin initialized: nexus_plugin.py") - except: - sys.stderr.write(f"Failed to register command: {NexusCmd.kPluginCmdName}\n") - raise - - -def uninitializePlugin(mobject): - """Uninitialize plugin""" - mplugin = om.MFnPlugin(mobject) - try: - mplugin.deregisterCommand(NexusCmd.kPluginCmdName) - print("[Nexus] Plugin uninitialized: nexus_plugin.py") - except: - sys.stderr.write(f"Failed to deregister command: {NexusCmd.kPluginCmdName}\n") - raise diff --git a/2025/scripts/animation_tools/__init__.py b/2025/scripts/animation_tools/__init__.py deleted file mode 100644 index b553fc0..0000000 --- a/2025/scripts/animation_tools/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -""" -Nexus Animation Tools Package -Animation utilities and helpers -""" - -__all__ = [] diff --git a/2025/scripts/modeling_tools/__init__.py b/2025/scripts/modeling_tools/__init__.py deleted file mode 100644 index cb40630..0000000 --- a/2025/scripts/modeling_tools/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -""" -Nexus Modeling Tools Package -General modeling utilities -""" - -from .batchextrusion import show_batch_extrusion_ui - -__all__ = [ - 'show_batch_extrusion_ui' -] diff --git a/2025/scripts/modeling_tools/__pycache__/__init__.cpython-39.pyc b/2025/scripts/modeling_tools/__pycache__/__init__.cpython-39.pyc deleted file mode 100644 index dd05dafca0410033fe4cf149f2303eac2b2c1e65..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 312 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 U&F$t6b4THeiRhngr7Z1#1EFxUjQ{`u diff --git a/2025/scripts/modeling_tools/batchextrusion.py b/2025/scripts/modeling_tools/batchextrusion.py deleted file mode 100644 index 350c0c7..0000000 --- a/2025/scripts/modeling_tools/batchextrusion.py +++ /dev/null @@ -1,761 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -# @Site : CGNICO Games -# @Author : Jeffrey Tsai - -""" -Maya Batch Extrusion Shell Mesh Tool - -Features: -1. Copy selected model to a specified number of layers -2. Extrude each layer, automatically deleting original layers to prevent overlap -3. Set vertex colors increasing from the inside out -4. Support model merging -""" - -import maya.cmds as cmds -import maya.mel as mel - -# UI Style configurations - -MESSAGE_BUTTON_STYLE = """ - QPushButton { - background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #D97706, stop:1 #B45309); - color: white; - border: 1px solid #92400E; - border-radius: 8px; - padding: 10px 16px; - font-weight: 600; - font-size: 12px; - min-width: 100px; - } - QPushButton:hover { - background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #F59E0B, stop:1 #D97706); - border-color: #D97706; - } - QPushButton:pressed { - background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #B45309, stop:1 #92400E); - border-color: #78350F; - } -""" - -SUCCESS_BUTTON_STYLE = """ - QPushButton { - background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #3B82F6, stop:1 #2563EB); - color: white; - border: none; - border-radius: 10px; - padding: 12px 20px; - font-weight: 600; - font-size: 13px; - min-width: 110px; - } - QPushButton:hover { - background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #60A5FA, stop:1 #3B82F6); - transform: translateY(-1px); - box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3); - } - QPushButton:pressed { - background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #2563EB, stop:1 #1D4ED8); - transform: translateY(0px); - } -""" - -WARNING_BUTTON_STYLE = """ - QPushButton { - background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #F59E0B, stop:1 #D97706); - color: white; - border: none; - border-radius: 10px; - padding: 12px 20px; - font-weight: 600; - font-size: 13px; - min-width: 110px; - } - QPushButton:hover { - background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #FBBF24, stop:1 #F59E0B); - transform: translateY(-1px); - box-shadow: 0 4px 12px rgba(245, 158, 11, 0.3); - } - QPushButton:pressed { - background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #D97706, stop:1 #B45309); - transform: translateY(0px); - } -""" - -class BatchExtrusion: - def __init__(self): - self.window_name = "BatchExtrusionWindow" - self.layers = 7 # Default number of layers - self.thickness = 0.1 # Default thickness - self.min_color = [0.0, 0.0, 0.0] # Inner layer color (black) - self.max_color = [1.0, 1.0, 1.0] # Outer layer color (white) - self.merge_layers = False # Whether to merge all layers - self.original_objects = [] # Original object list - self.generated_objects = [] # Generated object list - self.is_updating = False # Prevent recursive updates - self.button_info = [] # Store button information for delayed style application - self.qt_available = self.check_qt_availability() # Check Qt availability - - def check_qt_availability(self): - """Check Qt and related module availability""" - try: - maya_version = int(cmds.about(version=True)) - print(f"Maya version: {maya_version}") - - # Try PySide6 first (Maya 2022+) - try: - from PySide6 import QtWidgets, QtCore - import shiboken6 as shiboken - print("Using PySide6 (Maya 2022+)") - return "PySide6" - except ImportError: - try: - from PySide2 import QtWidgets, QtCore - import shiboken2 as shiboken - print("Using PySide2 (Maya 2020-2021)") - return "PySide2" - except ImportError: - print("PySide module is not available, using Maya native style") - return None - - except Exception as e: - print(f"Qt availability check failed: {str(e)}") - return None - - def create_ui(self): - """Create user interface""" - # If the window already exists, delete it - if cmds.window(self.window_name, exists=True): - cmds.deleteUI(self.window_name) - # Reset window preferences to avoid obscuring buttons due to old window dimensions - if cmds.windowPref(self.window_name, exists=True): - cmds.windowPref(self.window_name, remove=True) - - # Create window - cmds.window(self.window_name, title="Batch Extrusion Shell Mesh", widthHeight=(420, 600), sizeable=True) - - # Create main layout (using scroll layout to prevent small window obscuring bottom buttons) - scroll = cmds.scrollLayout(horizontalScrollBarThickness=0, verticalScrollBarThickness=12) - main_layout = cmds.columnLayout(adjustableColumn=True, rowSpacing=10, columnOffset=('both', 10)) - - # Title - cmds.text(label="Batch Extrusion Shell Mesh", font="boldLabelFont", height=30) - cmds.separator(height=10) - - # Number of layers control - cmds.text(label="Layers:", align="left", font="boldLabelFont") - self.layers_slider = cmds.intSliderGrp( - label="Layers: ", - field=True, - minValue=1, - maxValue=20, - value=self.layers, - step=1, - changeCommand=self.on_layers_changed - ) - - # Thickness control - cmds.text(label="Thickness:", align="left", font="boldLabelFont") - self.thickness_slider = cmds.floatSliderGrp( - label="Thickness: ", - field=True, - minValue=0.000001, - maxValue=10.0, - value=self.thickness, - precision=4, - step=0.01, - changeCommand=self.on_thickness_changed - ) - - # Vertex color control - cmds.text(label="Vertex Color:", align="left", font="boldLabelFont") - self.min_color_field = cmds.colorSliderGrp( - label="Inner layer: ", - rgb=self.min_color, - changeCommand=self.on_color_changed - ) - self.max_color_field = cmds.colorSliderGrp( - label="Outer layer: ", - rgb=self.max_color, - changeCommand=self.on_color_changed - ) - - # Merge options - cmds.text(label="Merge:", align="left", font="boldLabelFont") - self.merge_checkbox = cmds.checkBox( - label="Merge All Extruded Layers", - value=self.merge_layers, - changeCommand=self.on_merge_changed - ) - - # Preview area - cmds.text(label="Vertex Color Setting Preview:", align="left", font="boldLabelFont") - self.preview_text = cmds.scrollField( - editable=False, - wordWrap=True, - height=100, - backgroundColor=[0.2, 0.2, 0.2] - ) - - cmds.separator(height=10) - - # Operation buttons - self.create_styled_button("Extrude Shell Mesh", self.select_base_objects, SUCCESS_BUTTON_STYLE) - - self.create_styled_button("Clear All Extruded Layers", self.clear_all_layers, WARNING_BUTTON_STYLE) - - # Delay style application to ensure Qt controls are fully created - cmds.evalDeferred(self.apply_delayed_styles) - - # Show window - cmds.showWindow(self.window_name) - - # Initial preview - self.preview_colors() - - def create_styled_button(self, label, command, style): - """Create styled button""" - # Set base color based on style type - base_color = [0.4, 0.4, 0.4] # Default gray - if "SUCCESS" in style: - base_color = [0.2, 0.5, 0.8] # Blue - elif "WARNING" in style: - base_color = [0.8, 0.6, 0.2] # Orange - elif "MESSAGE" in style: - base_color = [0.7, 0.4, 0.1] # Deep orange - - # Create Maya button, set base color first - button_name = cmds.button( - label=label, - command=command, - height=35, - backgroundColor=base_color - ) - - # Store button info for delayed style application - self.button_info.append({ - 'name': button_name, - 'label': label, - 'style': style - }) - - # Immediately try to apply Qt style - self.apply_style_to_button(button_name, label, style) - - return button_name - - def apply_style_to_button(self, button_name, label, style): - """Apply style to a single button""" - # If Qt is not available, return False - if not self.qt_available: - print(f"Qt is not available, skipping style application: {label}") - return False - - try: - # Import corresponding modules based on detected Qt version - if self.qt_available == "PySide6": - from PySide6 import QtWidgets, QtCore - import shiboken6 as shiboken - elif self.qt_available == "PySide2": - from PySide2 import QtWidgets, QtCore - import shiboken2 as shiboken - else: - return False - - import maya.OpenMayaUI as omui - - # Get the Qt object of the button - button_ptr = omui.MQtUtil.findControl(button_name) - if button_ptr: - qt_button = shiboken.wrapInstance(int(button_ptr), QtWidgets.QPushButton) - if qt_button: - qt_button.setStyleSheet(style) - print(f"✓ Successfully applied Qt style: {label}") - return True - else: - print(f"✗ Failed to get Qt button object: {label}") - else: - print(f"✗ Failed to find button control: {label}") - - except Exception as e: - print(f"✗ Qt style application failed ({label}): {str(e)}") - - return False - - def apply_delayed_styles(self): - """Delay style application to all buttons""" - print("=== Starting delayed style application ===") - success_count = 0 - for button_info in self.button_info: - if cmds.control(button_info['name'], exists=True): - if self.apply_style_to_button( - button_info['name'], - button_info['label'], - button_info['style'] - ): - success_count += 1 - else: - print(f"✗ Button control does not exist: {button_info['label']}") - - print(f"=== Style application completed: {success_count}/{len(self.button_info)} successful ===") - - def show_dialog(self, title, message): - """Show styled confirmation dialog""" - dialog_name = "StyledConfirmDialog" - - # If the dialog already exists, delete it - if cmds.window(dialog_name, exists=True): - cmds.deleteUI(dialog_name) - - # Create dialog window - cmds.window(dialog_name, title=title, widthHeight=(300, 120), sizeable=False) - - # Main layout - main_layout = cmds.columnLayout(adjustableColumn=True, rowSpacing=10, columnOffset=('both', 15)) - - # Message text - cmds.text(label=message, align="center", wordWrap=True, height=40) - - cmds.separator(height=5) - - # Confirm button - self.create_styled_button("Yes", lambda *args: self.close_dialog(dialog_name), MESSAGE_BUTTON_STYLE) - - # Show dialog - cmds.showWindow(dialog_name) - - def close_dialog(self, dialog_name): - """Close dialog""" - if cmds.window(dialog_name, exists=True): - cmds.deleteUI(dialog_name) - - def calculate_vertex_colors(self, total_layers): - """Calculate vertex colors for each layer""" - if total_layers <= 1: - return [[1.0, 1.0, 1.0]] - - colors = [] - for i in range(total_layers): - # Calculate interpolation factor (0.0 to 1.0) - factor = i / (total_layers - 1) if total_layers > 1 else 0.0 - - # Interpolate between minimum and maximum colors - r = self.min_color[0] + (self.max_color[0] - self.min_color[0]) * factor - g = self.min_color[1] + (self.max_color[1] - self.min_color[1]) * factor - b = self.min_color[2] + (self.max_color[2] - self.min_color[2]) * factor - - colors.append([r, g, b]) - - return colors - - def on_layers_changed(self, *args): - """Callback function when the number of layers changes - Real-time update""" - if self.is_updating: - return - - self.layers = cmds.intSliderGrp(self.layers_slider, query=True, value=True) - self.preview_colors() - - # Update logic - if self.original_objects: # If there are base models - if self.merge_layers: - print("The models have been merged, please manually clean up and re-extrude!") - else: - print(f"Number of layers updated to {self.layers}, regenerating shell layers...") - # Clean up existing layers - self.clear_generated_objects() - # Regenerate layers - self.generate_layers() - - def on_thickness_changed(self, *args): - """Callback function when thickness changes - Real-time update""" - if self.is_updating: - return - - self.thickness = cmds.floatSliderGrp(self.thickness_slider, query=True, value=True) - self.preview_colors() - - # Update logic - if self.original_objects and self.generated_objects: # If there are base models and generated layers - if self.merge_layers: - print("The models have been merged, please manually clean up and re-extrude!") - else: - print(f"Thickness updated to {self.thickness}, updating thickness for all layers...") - self.update_thickness() - - def on_merge_changed(self, *args): - """Callback function when merge option changes""" - if self.is_updating: - return - self.merge_layers = cmds.checkBox(self.merge_checkbox, query=True, value=True) - self.preview_colors() - - def on_color_changed(self, *args): - """Callback function when colors change - Real-time update""" - if self.is_updating: - return - - self.min_color = cmds.colorSliderGrp(self.min_color_field, query=True, rgb=True) - self.max_color = cmds.colorSliderGrp(self.max_color_field, query=True, rgb=True) - self.preview_colors() - - # Update logic - if self.original_objects and self.generated_objects: # If there are base models and generated layers - if self.merge_layers: - print("The models have been merged, please manually clean up and re-extrude!") - else: - print("Colors updated, updating vertex colors for all layers...") - self.update_colors_for_all_layers() - - def preview_colors(self, *args): - """Preview color distribution""" - layers = self.layers - total_layers = layers + 1 # Include original layer - colors = self.calculate_vertex_colors(total_layers) - - preview_text = f"Colors preview ({total_layers} layers):\n\n" - - for i, color in enumerate(colors): - r, g, b = color - if i == 0: - layer_name = "Original layer" - else: - layer_name = f"Layer {i}" - - preview_text += f"{layer_name}: RGB({r:.2f}, {g:.2f}, {b:.2f})\n" - - cmds.scrollField(self.preview_text, edit=True, text=preview_text) - - def select_base_objects(self, *args): - """Select base objects and start extrusion""" - selected = cmds.ls(selection=True, type='transform') - if not selected: - cmds.warning("Please select the models to process first") - return - - # Clean up existing layers - self.clear_generated_objects() - - # Save original objects - self.original_objects = selected[:] - - # Generate layers immediately - self.generate_layers() - - self.show_dialog("Completed", f"Processed {len(selected)} objects, generated {self.layers} shell layers") - - def clear_all_layers(self, *args): - """Clear all generated layers""" - # Check if merge option is checked - if self.merge_layers: - print("The models have been merged, please manually clean up and re-extrude!") - return - self.clear_generated_objects() - self.show_dialog("Completed", "All generated layers have been cleared") - - def clear_generated_objects(self): - """Clear generated objects""" - for obj in self.generated_objects: - if cmds.objExists(obj): - cmds.delete(obj) - self.generated_objects = [] - - def generate_layers(self): - """Generate shell layers""" - if not self.original_objects: - print("Error: No original objects selected") - return - - try: - print(f"=== Starting generation of {self.layers} shell layers ===") - - created_objects = [] - - for obj_index, original_obj in enumerate(self.original_objects): - print(f"Processing object {obj_index + 1}: {original_obj}") - - # Collect all layer objects (including original object) - all_layers = [original_obj] - - # Create layers one by one (always from the original object) - for layer_num in range(1, self.layers + 1): - print(f" Creating layer {layer_num}...") - - # Key: Always copy from the original object, not from the previous layer - new_layer = self.extrude_layer_correct(original_obj, layer_num, self.thickness) - if new_layer: - all_layers.append(new_layer) - created_objects.append(new_layer) - print(f" ✓ Layer {layer_num} completed") - else: - print(f" ✗ Layer {layer_num} failed, stopping") - break - - # Set vertex colors - print(f" Setting vertex colors for {len(all_layers)} layers...") - self.apply_colors(all_layers) - - # Save results - self.generated_objects = created_objects - print(f"=== Completed! Created {len(created_objects)} layers ===") - - # Check if merge option is checked - if self.merge_layers and len(created_objects) > 0: - print("=== Starting simple merge ===") - self.simple_merge_objects() - else: - print("=== Skipping merge (not checked or no objects) ===") - - except Exception as e: - print(f"Generation failed: {str(e)}") - # Cleanup - for obj in created_objects: - if cmds.objExists(obj): - try: - cmds.delete(obj) - except: - pass - - def simple_merge_objects(self): - """Simple merge method to avoid complex operations""" - try: - all_objects = self.original_objects + self.generated_objects - print(f"Simple merge {len(all_objects)} objects") - - if len(all_objects) > 1: - # Only do basic merge - existing_objects = [obj for obj in all_objects if cmds.objExists(obj)] - - if len(existing_objects) > 1: - cmds.select(existing_objects, replace=True) - merged = cmds.polyUnite(existing_objects, name="BatchExtruded_Simple")[0] - print(f"Simple merge completed: {merged}") - - # Update object list - self.original_objects = [merged] - self.generated_objects = [] - else: - print("Not enough objects to merge") - else: - print("Not enough objects to merge") - - except Exception as e: - print(f"Simple merge failed: {str(e)}") - - def extrude_layer_correct(self, obj, layer_index, thickness): - """Follow the correct Maya workflow: duplicate -> extrude -> delete by inverse selection""" - duplicated = None - try: - print(f" === Correct workflow for layer {layer_index} ===") - - # 1. Verify object - if not cmds.objExists(obj): - print(f" Error: Object {obj} does not exist") - return None - - # 2. Copy model - duplicated = cmds.duplicate(obj, name=f"Layer_{layer_index}")[0] - print(f" Copy completed: {duplicated}") - - # 3. Switch to face mode and select all faces - print(f" Switching to face mode and selecting all faces...") - - # Switch to component mode - cmds.selectMode(component=True) - cmds.selectType(facet=True) - - # Select all faces directly (more reliable method) - try: - cmds.select(f"{duplicated}.f[*]", replace=True) - selected_faces = cmds.ls(selection=True, flatten=True) - print(f" Selected faces count: {len(selected_faces)}") - - if len(selected_faces) == 0: - # Backup method 1: Select object first then convert - print(f" Direct selection failed, trying conversion method...") - cmds.selectMode(object=True) - cmds.select(duplicated, replace=True) - cmds.selectMode(component=True) - cmds.selectType(facet=True) - - # Use mel command to convert - try: - mel.eval("ConvertSelectionToFaces;") - selected_faces = cmds.ls(selection=True, flatten=True) - print(f" Selected faces count: {len(selected_faces)}") - except Exception as mel_error: - print(f" MEL conversion failed: {str(mel_error)}") - - # Backup method 2: Use polyEvaluate to get face count, then select - try: - face_count = cmds.polyEvaluate(duplicated, face=True) - if face_count > 0: - face_range = f"{duplicated}.f[0:{face_count-1}]" - cmds.select(face_range, replace=True) - selected_faces = cmds.ls(selection=True, flatten=True) - print(f" Selected faces count: {len(selected_faces)}") - except Exception as backup_error: - print(f" Backup method also failed: {str(backup_error)}") - - except Exception as select_error: - print(f" Face selection failed: {str(select_error)}") - raise select_error - - # Verify face selection success - if len(selected_faces) == 0: - print(f" Error: No faces selected") - return None - - print(f" Selected {len(selected_faces)} faces, starting extrusion...") - - # Execute extrusion (thickness=0) - extrude_result = cmds.polyExtrudeFacet( - constructionHistory=True, - keepFacesTogether=True, - divisions=1, - twist=0, - taper=1, - off=0, - thickness=0, - smoothingAngle=30 - ) - print(f" Extrusion command completed: {extrude_result}") - - # Set cumulative thickness (each layer's thickness is cumulative) - if extrude_result: - extrude_node = extrude_result[0] - # Cumulative thickness = base thickness * layer number - cumulative_thickness = thickness * layer_index - cmds.setAttr(f"{extrude_node}.thickness", cumulative_thickness) - print(f" Set cumulative thickness: {cumulative_thickness} (Layer {layer_index})") - - # 4. Invert selection and delete internal faces - print(f" Inverting selection and deleting internal faces...") - - # After extrusion, the original faces are still selected - # We need to keep these original faces and delete the newly generated faces - original_selected_faces = cmds.ls(selection=True, flatten=True) - print(f" Original selected faces count: {len(original_selected_faces)}") - - # Get all faces after extrusion - all_faces_after_extrude = cmds.ls(f"{duplicated}.f[*]", flatten=True) - print(f" Total faces after extrusion: {len(all_faces_after_extrude)}") - - # Calculate newly generated faces (faces to delete) - if len(all_faces_after_extrude) > len(original_selected_faces): - # New faces = All faces - Original faces - new_faces = [face for face in all_faces_after_extrude if face not in original_selected_faces] - - if new_faces: - print(f" Deleting {len(new_faces)} newly generated faces") - cmds.select(new_faces, replace=True) - cmds.delete() - print(f" Deleted successfully, keeping original faces as shell") - else: - print(f" No newly generated faces found") - else: - print(f" No faces added, skipping deletion") - - # 5. Return to object mode - cmds.selectMode(object=True) - cmds.select(clear=True) - - print(f" === Layer {layer_index} completed ===") - return duplicated - - except Exception as e: - print(f" Layer {layer_index} failed: {str(e)}") - if duplicated and cmds.objExists(duplicated): - try: - cmds.delete(duplicated) - except: - pass - return None - - def apply_colors(self, layer_objects): - """Simple vertex color application""" - try: - total_layers = len(layer_objects) - colors = self.calculate_vertex_colors(total_layers) - - print(f" Setting colors for {total_layers} layers...") - - for i, obj in enumerate(layer_objects): - if cmds.objExists(obj) and i < len(colors): - color = colors[i] - print(f" Layer {i}: {obj} -> RGB{color}") - - # Set vertex colors - try: - # Select all vertices - cmds.select(f"{obj}.vtx[*]", replace=True) - - # Apply color - cmds.polyColorPerVertex(rgb=color, colorDisplayOption=True) - - # Enable vertex color display - cmds.setAttr(f"{obj}.displayColors", 1) - - except Exception as color_error: - print(f" Setting vertex colors failed: {str(color_error)}") - - # Clear selection - cmds.select(clear=True) - print(f" Vertex colors set successfully") - except Exception as e: - print(f" Vertex color application failed: {str(e)}") - - def update_thickness(self): - """Update thickness for all layers""" - try: - print(f"=== Updating thickness to {self.thickness} ===") - - for layer_index, obj in enumerate(self.generated_objects, 1): - if cmds.objExists(obj): - print(f" Updating layer {layer_index}: {obj}") - - # Find extrude node - history = cmds.listHistory(obj, pruneDagObjects=True) - extrude_nodes = [node for node in history if cmds.nodeType(node) == 'polyExtrudeFace'] - - if extrude_nodes: - extrude_node = extrude_nodes[0] # Take the latest extrude node - # Set cumulative thickness - cumulative_thickness = self.thickness * layer_index - cmds.setAttr(f"{extrude_node}.thickness", cumulative_thickness) - print(f" ✓ Updated thickness: {cumulative_thickness}") - else: - print(f" ✗ No extrude node found") - - print(f"=== Thickness updated successfully ===") - - except Exception as e: - print(f"Thickness update failed: {str(e)}") - - def update_colors_for_all_layers(self): - """Update colors for all layers""" - try: - print(f"=== Updating colors for all layers ===") - - # Collect all layer objects (including original objects) - all_layers = self.original_objects + self.generated_objects - - # Reapply colors - self.apply_colors(all_layers) - - print(f"=== Colors updated successfully ===") - - except Exception as e: - print(f"Colors update failed: {str(e)}") - -# Create and display UI function -def show_batch_extrusion_ui(): - """Show batch extrusion UI""" - batch_extrusion = BatchExtrusion() - batch_extrusion.create_ui() - -# If run this script directly -if __name__ == "__main__": - show_batch_extrusion_ui() diff --git a/2025/scripts/nexus_test.py b/2025/scripts/nexus_test.py deleted file mode 100644 index 8a8eb10..0000000 --- a/2025/scripts/nexus_test.py +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -""" -Nexus Test Script -Simple test to verify the Nexus plugin system is working -""" - -import maya.cmds as cmds - - -def run_test(): - """Run a simple test""" - print("[Nexus Test] Running test...") - - # Show confirmation dialog - result = cmds.confirmDialog( - title='Nexus Test', - message='Nexus Plugin System is working correctly!', - button=['OK'], - defaultButton='OK', - cancelButton='OK', - dismissString='OK' - ) - - print(f"[Nexus Test] Test completed: {result}") - return result - - -if __name__ == "__main__": - run_test() diff --git a/2025/scripts/rigging_tools/__init__.py b/2025/scripts/rigging_tools/__init__.py deleted file mode 100644 index a987049..0000000 --- a/2025/scripts/rigging_tools/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -""" -Nexus Rigging Tools Package -Character rigging utilities -""" - -__all__ = [] diff --git a/2025/scripts/userSetup.py b/2025/scripts/userSetup.py deleted file mode 100644 index 5d41610..0000000 --- a/2025/scripts/userSetup.py +++ /dev/null @@ -1,200 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -""" -Nexus Maya 2023 - User Setup Script -Automatically executed when Maya starts -""" - -import maya.cmds as cmds -import maya.mel as mel -import maya.utils -import os -import sys - -SHELF_NAMES = ["Nexus_Modeling", "Nexus_Rigging", "Nexus_Animation"] - -def load_nexus_shelves(): - """Load all Nexus shelves (force refresh)""" - try: - shelf_paths = os.environ.get('MAYA_SHELF_PATH', '') - - if not shelf_paths: - print("[Nexus] MAYA_SHELF_PATH not set, trying alternative method...") - try: - script_dir = os.path.dirname(os.path.abspath(__file__)) - shelf_paths = os.path.join(os.path.dirname(script_dir), "shelves") - except: - print("[Nexus] Could not determine shelf path, skipping shelf load") - return - - path_separator = ';' if os.name == 'nt' else ':' - shelf_path_list = shelf_paths.split(path_separator) - - for shelf_name in SHELF_NAMES: - shelf_file_found = None - for shelf_path in shelf_path_list: - shelf_path = shelf_path.strip() - if not shelf_path: - continue - - shelf_file = os.path.join(shelf_path, f"shelf_{shelf_name}.mel") - shelf_file = shelf_file.replace("\\", "/") - - if os.path.exists(shelf_file): - shelf_file_found = shelf_file - print(f"[Nexus] Found shelf file: {shelf_file}") - break - - if not shelf_file_found: - print(f"[Nexus] Could not find shelf_{shelf_name}.mel") - continue - - # Delete old shelf if exists - if cmds.shelfLayout(shelf_name, exists=True): - print(f"[Nexus] Deleting old shelf: {shelf_name}") - try: - cmds.deleteUI(shelf_name, layout=True) - except Exception as e: - print(f"[Nexus] Warning: Could not delete old shelf: {e}") - - # Load shelf using proper MEL method - print(f"[Nexus] Loading shelf: {shelf_name}") - try: - # Disable auto-save - mel.eval('optionVar -intValue "saveLastLoadedShelf" 0;') - - # Create shelf layout - mel.eval(f''' - global string $gShelfTopLevel; - if (`shelfLayout -exists {shelf_name}`) {{ - deleteUI -layout {shelf_name}; - }} - setParent $gShelfTopLevel; - shelfLayout -cellWidth 35 -cellHeight 34 {shelf_name}; - ''') - print(f"[Nexus] ✓ Created shelf layout: {shelf_name}") - - # Set parent and execute shelf script - mel.eval(f'setParent {shelf_name};') - mel.eval(f'source "{shelf_file_found}";') - mel.eval(f'shelf_{shelf_name}();') - print(f"[Nexus] ✓ Executed shelf script: shelf_{shelf_name}()") - - # Verify shelf - if cmds.shelfLayout(shelf_name, exists=True): - buttons = cmds.shelfLayout(shelf_name, query=True, childArray=True) or [] - if buttons: - print(f"[Nexus] ✓ Shelf loaded with {len(buttons)} button(s): {shelf_name}") - else: - print(f"[Nexus] ⚠ Shelf created but no buttons: {shelf_name}") - - # Remove auto-saved config - try: - maya_version = cmds.about(version=True).split()[0] - maya_app_dir = os.environ.get('MAYA_APP_DIR', '') - if maya_app_dir: - shelf_config = os.path.join(maya_app_dir, maya_version, "prefs", "shelves", f"shelf_{shelf_name}.mel") - if os.path.exists(shelf_config): - os.remove(shelf_config) - print(f"[Nexus] ✓ Removed auto-saved config: {shelf_name}") - except Exception as e: - print(f"[Nexus] Warning: {e}") - else: - print(f"[Nexus] ✗ Shelf layout not created: {shelf_name}") - - except Exception as e: - print(f"[Nexus] Error loading shelf {shelf_name}: {e}") - import traceback - traceback.print_exc() - continue - - except Exception as e: - print(f"[Nexus] Error in load_nexus_shelves: {e}") - import traceback - traceback.print_exc() - - -def load_nexus_plugins(): - """Load Nexus plugins""" - try: - plugin_paths = os.environ.get('MAYA_PLUG_IN_PATH', '') - - if not plugin_paths: - print("[Nexus] MAYA_PLUG_IN_PATH not set") - return - - path_separator = ';' if os.name == 'nt' else ':' - plugin_path_list = plugin_paths.split(path_separator) - - plugins_to_load = ["nexus_plugin.py"] - - for plugin_name in plugins_to_load: - plugin_found = False - for plugin_path in plugin_path_list: - plugin_path = plugin_path.strip() - if not plugin_path: - continue - - plugin_file = os.path.join(plugin_path, plugin_name) - if os.path.exists(plugin_file): - plugin_found = True - break - - if not plugin_found: - print(f"[Nexus] Plugin not found: {plugin_name}") - continue - - # Load plugin - try: - if not cmds.pluginInfo(plugin_name, query=True, loaded=True): - cmds.loadPlugin(plugin_name) - print(f"[Nexus] ✓ Plugin loaded: {plugin_name}") - else: - print(f"[Nexus] Plugin already loaded: {plugin_name}") - except Exception as e: - print(f"[Nexus] Error loading plugin {plugin_name}: {e}") - - except Exception as e: - print(f"[Nexus] Error in load_nexus_plugins: {e}") - import traceback - traceback.print_exc() - - -# Deferred execution -def initialize_nexus(): - """Initialize Nexus plugin system""" - print("=" * 80) - print("[Nexus] Initializing Nexus Maya 2023 Plugin System...") - print("=" * 80) - - load_nexus_shelves() - load_nexus_plugins() - - print("=" * 80) - print("[Nexus] Nexus Plugin System Initialized!") - print("=" * 80) - - -# Execute after Maya is fully loaded -maya.utils.executeDeferred(initialize_nexus) - - -# Cleanup on exit -def cleanup_on_exit(): - """Cleanup when Maya exits""" - print("[Nexus] Cleaning up...") - - # Unload plugins - plugins_to_unload = ["nexus_plugin.py"] - for plugin_name in plugins_to_unload: - if cmds.pluginInfo(plugin_name, query=True, loaded=True): - try: - cmds.unloadPlugin(plugin_name) - print(f"[Nexus] Plugin unloaded: {plugin_name}") - except: - pass - - -import atexit -atexit.register(cleanup_on_exit) diff --git a/2025/shelves/shelf_Nexus_Animation.mel b/2025/shelves/shelf_Nexus_Animation.mel deleted file mode 100644 index a7ff9a1..0000000 --- a/2025/shelves/shelf_Nexus_Animation.mel +++ /dev/null @@ -1,43 +0,0 @@ -global proc shelf_Nexus_Animation () { - global string $gBuffStr; - global string $gBuffStr0; - global string $gBuffStr1; - - - shelfButton - -enableCommandRepeat 1 - -flexibleWidthType 3 - -flexibleWidthValue 32 - -enable 1 - -width 35 - -height 34 - -manage 1 - -visible 1 - -preventOverride 0 - -annotation "Nexus Test - Verify plugin system" - -enableBackground 0 - -backgroundColor 0 0 0 - -highlightColor 0.321569 0.521569 0.65098 - -align "center" - -label "Test" - -labelOffset 0 - -rotation 0 - -flipX 0 - -flipY 0 - -useAlpha 1 - -font "plainLabelFont" - -imageOverlayLabel "Test" - -overlayLabelColor 0.8 0.8 0.8 - -overlayLabelBackColor 0 0 0 0.5 - -image "commandButton.png" - -image1 "commandButton.png" - -style "iconOnly" - -marginWidth 0 - -marginHeight 1 - -command "import nexus_test\nnexus_test.run_test()" - -sourceType "python" - -commandRepeatable 1 - -flat 1 - ; - -} diff --git a/2025/shelves/shelf_Nexus_Modeling.mel b/2025/shelves/shelf_Nexus_Modeling.mel deleted file mode 100644 index 590ff7f..0000000 --- a/2025/shelves/shelf_Nexus_Modeling.mel +++ /dev/null @@ -1,43 +0,0 @@ -global proc shelf_Nexus_Modeling () { - global string $gBuffStr; - global string $gBuffStr0; - global string $gBuffStr1; - - - shelfButton - -enableCommandRepeat 1 - -flexibleWidthType 3 - -flexibleWidthValue 32 - -enable 1 - -width 35 - -height 34 - -manage 1 - -visible 1 - -preventOverride 0 - -annotation "Batch Extrusion - Shell mesh tool with vertex colors" - -enableBackground 0 - -backgroundColor 0 0 0 - -highlightColor 0.321569 0.521569 0.65098 - -align "center" - -label "BatchExt" - -labelOffset 0 - -rotation 0 - -flipX 0 - -flipY 0 - -useAlpha 1 - -font "plainLabelFont" - -imageOverlayLabel "Extrude" - -overlayLabelColor 0.8 0.8 0.8 - -overlayLabelBackColor 0 0 0 0.5 - -image "batchextrusion.png" - -image1 "batchextrusion.png" - -style "iconOnly" - -marginWidth 0 - -marginHeight 1 - -command "import modeling_tools\nmodeling_tools.show_batch_extrusion_ui()" - -sourceType "python" - -commandRepeatable 1 - -flat 1 - ; - -} diff --git a/2025/shelves/shelf_Nexus_Rigging.mel b/2025/shelves/shelf_Nexus_Rigging.mel deleted file mode 100644 index 778a59f..0000000 --- a/2025/shelves/shelf_Nexus_Rigging.mel +++ /dev/null @@ -1,43 +0,0 @@ -global proc shelf_Nexus_Rigging () { - global string $gBuffStr; - global string $gBuffStr0; - global string $gBuffStr1; - - - shelfButton - -enableCommandRepeat 1 - -flexibleWidthType 3 - -flexibleWidthValue 32 - -enable 1 - -width 35 - -height 34 - -manage 1 - -visible 1 - -preventOverride 0 - -annotation "Nexus Test - Verify plugin system" - -enableBackground 0 - -backgroundColor 0 0 0 - -highlightColor 0.321569 0.521569 0.65098 - -align "center" - -label "Test" - -labelOffset 0 - -rotation 0 - -flipX 0 - -flipY 0 - -useAlpha 1 - -font "plainLabelFont" - -imageOverlayLabel "Test" - -overlayLabelColor 0.8 0.8 0.8 - -overlayLabelBackColor 0 0 0 0.5 - -image "commandButton.png" - -image1 "commandButton.png" - -style "iconOnly" - -marginWidth 0 - -marginHeight 1 - -command "import nexus_test\nnexus_test.run_test()" - -sourceType "python" - -commandRepeatable 1 - -flat 1 - ; - -}