This commit is contained in:
2025-11-23 20:50:30 +08:00
commit 7baece90bc
17 changed files with 1439 additions and 0 deletions

47
2023/README.md Normal file
View File

@@ -0,0 +1,47 @@
# 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 目录

121
2023/RELOAD_SHELF.py Normal file
View File

@@ -0,0 +1,121 @@
#!/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()

24
2023/icons/README.md Normal file
View File

@@ -0,0 +1,24 @@
# 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 环境变量指定的目录中查找图标文件。

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,58 @@
#!/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

View File

@@ -0,0 +1,9 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Nexus Animation Tools Package
Animation utilities and helpers
"""
__all__ = []

View File

@@ -0,0 +1,13 @@
#!/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'
]

View File

@@ -0,0 +1,761 @@
#!/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()

View File

@@ -0,0 +1,31 @@
#!/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()

View File

@@ -0,0 +1,9 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Nexus Rigging Tools Package
Character rigging utilities
"""
__all__ = []

200
2023/scripts/userSetup.py Normal file
View File

@@ -0,0 +1,200 @@
#!/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)

View File

@@ -0,0 +1,43 @@
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
;
}

View File

@@ -0,0 +1,43 @@
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
;
}

View File

@@ -0,0 +1,43 @@
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
;
}