Update
This commit is contained in:
47
2023/README.md
Normal file
47
2023/README.md
Normal 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
121
2023/RELOAD_SHELF.py
Normal 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
24
2023/icons/README.md
Normal 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 环境变量指定的目录中查找图标文件。
|
||||
BIN
2023/icons/batchextrusion.png
Normal file
BIN
2023/icons/batchextrusion.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
58
2023/plug-ins/nexus_plugin.py
Normal file
58
2023/plug-ins/nexus_plugin.py
Normal 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
|
||||
9
2023/scripts/animation_tools/__init__.py
Normal file
9
2023/scripts/animation_tools/__init__.py
Normal file
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Nexus Animation Tools Package
|
||||
Animation utilities and helpers
|
||||
"""
|
||||
|
||||
__all__ = []
|
||||
13
2023/scripts/modeling_tools/__init__.py
Normal file
13
2023/scripts/modeling_tools/__init__.py
Normal 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'
|
||||
]
|
||||
BIN
2023/scripts/modeling_tools/__pycache__/__init__.cpython-39.pyc
Normal file
BIN
2023/scripts/modeling_tools/__pycache__/__init__.cpython-39.pyc
Normal file
Binary file not shown.
Binary file not shown.
761
2023/scripts/modeling_tools/batchextrusion.py
Normal file
761
2023/scripts/modeling_tools/batchextrusion.py
Normal 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()
|
||||
31
2023/scripts/nexus_test.py
Normal file
31
2023/scripts/nexus_test.py
Normal 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()
|
||||
9
2023/scripts/rigging_tools/__init__.py
Normal file
9
2023/scripts/rigging_tools/__init__.py
Normal 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
200
2023/scripts/userSetup.py
Normal 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)
|
||||
43
2023/shelves/shelf_Nexus_Animation.mel
Normal file
43
2023/shelves/shelf_Nexus_Animation.mel
Normal 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
|
||||
;
|
||||
|
||||
}
|
||||
43
2023/shelves/shelf_Nexus_Modeling.mel
Normal file
43
2023/shelves/shelf_Nexus_Modeling.mel
Normal 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
|
||||
;
|
||||
|
||||
}
|
||||
43
2023/shelves/shelf_Nexus_Rigging.mel
Normal file
43
2023/shelves/shelf_Nexus_Rigging.mel
Normal 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
|
||||
;
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user