728 lines
30 KiB
Python
728 lines
30 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
Nexus User Setup Script
|
|
Automatically sets up the Nexus plugin system in Maya upon startup.
|
|
|
|
Features:
|
|
- Auto-loads scripts, plugins, and shelves
|
|
- Sets up icon paths for UI elements
|
|
- Manages command port for external connections
|
|
- Clean exit with proper resource cleanup
|
|
- Maya 2018+ compatible
|
|
"""
|
|
|
|
import maya.cmds as cmds
|
|
import maya.mel as mel
|
|
import maya.utils
|
|
import atexit
|
|
import os
|
|
import sys
|
|
import re
|
|
|
|
# Set MAYA_MODULE_PATH environment variable for external tools
|
|
try:
|
|
_current_file = os.path.abspath(__file__)
|
|
_script_dir = os.path.dirname(_current_file)
|
|
_project_root = os.path.dirname(os.path.dirname(_script_dir))
|
|
_modules_dir = os.path.join(_project_root, 'modules')
|
|
|
|
if os.path.exists(_modules_dir):
|
|
_modules_dir_normalized = os.path.normpath(_modules_dir)
|
|
_current_module_path = os.environ.get('MAYA_MODULE_PATH', '')
|
|
|
|
# Check if already in path
|
|
_paths = [p.strip() for p in _current_module_path.split(os.pathsep) if p.strip()]
|
|
_normalized_paths = [os.path.normpath(p) for p in _paths]
|
|
|
|
if _modules_dir_normalized not in _normalized_paths:
|
|
if _current_module_path:
|
|
os.environ['MAYA_MODULE_PATH'] = f"{_modules_dir_normalized}{os.pathsep}{_current_module_path}"
|
|
else:
|
|
os.environ['MAYA_MODULE_PATH'] = _modules_dir_normalized
|
|
print(f"[Tool] MAYA_MODULE_PATH set to: {_modules_dir_normalized}")
|
|
except Exception as e:
|
|
print(f"[Tool] Warning: Could not set MAYA_MODULE_PATH: {e}")
|
|
|
|
# Silently try to open default commandPort to avoid startup error if it's already in use
|
|
try:
|
|
mel.eval('catchQuiet("commandPort -securityWarning -name \\"commandportDefault\\";");')
|
|
except Exception:
|
|
pass
|
|
|
|
# =============================================================================
|
|
# Configuration
|
|
# =============================================================================
|
|
|
|
# Shelves to load
|
|
SHELF_NAMES = ["Nexus_Manage", "Nexus_Modeling", "Nexus_Rigging", "Nexus_Animation", "Nexus_DevTools"]
|
|
|
|
# Tool packages configuration
|
|
TOOL_CONFIG = {
|
|
'scripts': [
|
|
{'name': 'animation_tools', 'path': 'animation_tools'},
|
|
{'name': 'modeling_tools', 'path': 'modeling_tools'},
|
|
{'name': 'rigging_tools', 'path': 'rigging_tools'},
|
|
{'name': 'manage_tools', 'path': 'manage_tools'},
|
|
],
|
|
'plugins': [
|
|
{'name': 'cv_manip.mll', 'path': 'modeling_tools/gs_curvetools/plugins/2025'},
|
|
{'name': 'ngskintools2.mll', 'path': 'rigging_tools/ngskintools2/plug-ins/2025'},
|
|
{'name': 'MGPicker_2025x64.mll', 'path': 'animation_tools/mgpicker/MGPicker_Program/Plug-ins'},
|
|
],
|
|
'icons': [
|
|
{'name': 'gs_curvetools', 'path': 'modeling_tools/gs_curvetools/icons'},
|
|
{'name': 'gs_toolbox', 'path': 'modeling_tools/gs_toolbox/icons'},
|
|
{'name': 'modit', 'path': 'modeling_tools/ModIt/Icons/Theme_Classic'},
|
|
{'name': 'creaseplus', 'path': 'modeling_tools/creaseplus/icons'},
|
|
{'name': 'ngskintools2', 'path': 'rigging_tools/ngskintools2/ui/images'},
|
|
{'name': 'mgpicker', 'path': 'animation_tools/mgpicker/MGPicker_Program/Icons'},
|
|
{'name': 'atools', 'path': 'animation_tools/atools/img'},
|
|
{'name': 'dwpicker', 'path': 'animation_tools/dwpicker/icons'},
|
|
{'name': 'studiolibrary', 'path': 'animation_tools/studiolibrary/studiolibrary/resource/icons'},
|
|
{'name': 'studiolibrary_maya', 'path': 'animation_tools/studiolibrary/studiolibrarymaya/icons'},
|
|
{'name': 'springmagic', 'path': 'animation_tools/springmagic/icons'},
|
|
],
|
|
}
|
|
|
|
# Maya version compatibility check
|
|
MAYA_MIN_VERSION = 2018
|
|
|
|
# Debug control: set environment variable TOOL_DEBUG=1 for verbose logs
|
|
TOOL_DEBUG = os.environ.get('TOOL_DEBUG', '0') == '1'
|
|
|
|
# Global state - capture __file__ at module level before executeDeferred
|
|
try:
|
|
_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
except NameError:
|
|
_SCRIPT_DIR = None
|
|
|
|
_ICONS_PATHS = {}
|
|
_LOADED_PLUGINS = {}
|
|
_COMMAND_PORT_OPENED = False
|
|
_ADDED_SCRIPT_PATHS = []
|
|
|
|
# =============================================================================
|
|
# Utility Functions
|
|
# =============================================================================
|
|
|
|
def _tool_log(msg):
|
|
"""Print debug message if TOOL_DEBUG is enabled"""
|
|
if TOOL_DEBUG:
|
|
print(msg)
|
|
|
|
def _tool_print(msg):
|
|
"""Print info message"""
|
|
print(msg)
|
|
|
|
def _get_script_dir():
|
|
"""Get the directory containing this script (with fallback)"""
|
|
global _SCRIPT_DIR
|
|
if _SCRIPT_DIR:
|
|
return _SCRIPT_DIR
|
|
|
|
# Try multiple methods to get script directory
|
|
try:
|
|
_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
except NameError:
|
|
try:
|
|
import inspect
|
|
_SCRIPT_DIR = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
|
|
except Exception:
|
|
# Last resort: use current working directory's scripts folder
|
|
_SCRIPT_DIR = os.getcwd()
|
|
|
|
return _SCRIPT_DIR
|
|
|
|
def _norm_path(path):
|
|
"""Normalize path with forward slashes"""
|
|
return os.path.normpath(path).replace('\\', '/')
|
|
|
|
def _resolve_relative_path(path, base_dir):
|
|
"""Resolve relative path against base directory"""
|
|
if os.path.isabs(path):
|
|
return path
|
|
return os.path.normpath(os.path.join(base_dir, path))
|
|
|
|
def _safe_mel_eval(cmd):
|
|
"""Execute MEL command safely, return None on error"""
|
|
try:
|
|
return mel.eval(cmd)
|
|
except Exception:
|
|
return None
|
|
|
|
def _check_maya_version():
|
|
"""Check if Maya version meets minimum requirements"""
|
|
try:
|
|
maya_version = int(cmds.about(version=True).split()[0])
|
|
if maya_version < MAYA_MIN_VERSION:
|
|
_tool_print(f"[Tool] Warning: Maya {maya_version} detected. Minimum supported version is {MAYA_MIN_VERSION}")
|
|
return False
|
|
_tool_log(f"[Tool] Maya version: {maya_version}")
|
|
return True
|
|
except Exception as e:
|
|
_tool_print(f"[Tool] Warning: Could not determine Maya version: {e}")
|
|
return True # Continue anyway
|
|
|
|
def _find_file_in_paths(filename, search_paths):
|
|
"""Find file in a list of search paths"""
|
|
for p in search_paths:
|
|
if not p:
|
|
continue
|
|
candidate = os.path.join(p, filename)
|
|
if os.path.exists(candidate):
|
|
return candidate
|
|
return None
|
|
|
|
def _verify_shelf_icon_references(search_paths):
|
|
"""Verify that shelf icon references can be resolved"""
|
|
if not TOOL_DEBUG:
|
|
return # Only verify in debug mode
|
|
|
|
try:
|
|
for shelf_name in SHELF_NAMES:
|
|
shelf_file = os.path.join(search_paths[-1] if search_paths else '', f'shelf_{shelf_name}.mel').replace('\\', '/')
|
|
if not os.path.exists(shelf_file):
|
|
shelf_file = os.path.join(os.path.dirname(_get_script_dir()), 'shelves', f'shelf_{shelf_name}.mel').replace('\\', '/')
|
|
if not os.path.exists(shelf_file):
|
|
continue
|
|
|
|
try:
|
|
with open(shelf_file, 'r', encoding='utf-8') as fh:
|
|
data = fh.read()
|
|
except:
|
|
continue
|
|
|
|
imgs = re.findall(r'-image\s+"([^"]+)"', data)
|
|
for img in imgs:
|
|
found = any(os.path.exists(os.path.join(p, img).replace('\\', '/')) for p in search_paths)
|
|
if not found:
|
|
_tool_log(f"[Tool] Warning: Icon '{img}' not found for shelf '{shelf_name}'")
|
|
except Exception as e:
|
|
_tool_log(f"[Tool] Icon verification error: {e}")
|
|
|
|
def _remove_shelf_config(shelf_name):
|
|
"""Remove shelf config from Maya prefs to force reload"""
|
|
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)
|
|
_tool_log(f"[Tool] Removed cached shelf config: {shelf_name}")
|
|
except Exception as e:
|
|
_tool_log(f"[Tool] Could not remove shelf config: {e}")
|
|
|
|
# =============================================================================
|
|
# Setup Functions
|
|
# =============================================================================
|
|
|
|
def setup_icon_paths():
|
|
"""Configure icon paths in XBMLANGPATH environment variable"""
|
|
try:
|
|
script_dir = _get_script_dir()
|
|
icon_configs = TOOL_CONFIG.get('icons', [])
|
|
if not icon_configs:
|
|
_tool_log("[Tool] No icons configured")
|
|
return
|
|
|
|
# Build icon paths dictionary
|
|
icon_paths = {}
|
|
for cfg in icon_configs:
|
|
name = cfg.get('name', '')
|
|
path = cfg.get('path', '')
|
|
if not (name and path):
|
|
continue
|
|
full_path = _norm_path(_resolve_relative_path(path, script_dir))
|
|
if os.path.exists(full_path):
|
|
icon_paths[name] = full_path
|
|
else:
|
|
_tool_print(f"[Tool] Warning: Icon path not found: {full_path}")
|
|
|
|
# Check if already configured
|
|
global _ICONS_PATHS
|
|
if _ICONS_PATHS and _ICONS_PATHS == icon_paths:
|
|
_tool_log("[Tool] Icon paths already configured")
|
|
return
|
|
_ICONS_PATHS = icon_paths
|
|
|
|
# Update XBMLANGPATH
|
|
xbmlangpath = os.environ.get('XBMLANGPATH', '')
|
|
paths = [_norm_path(p.strip()) for p in xbmlangpath.split(os.pathsep) if p.strip()]
|
|
|
|
# Add icon paths to front
|
|
for icon_path in icon_paths.values():
|
|
if icon_path not in paths:
|
|
paths.insert(0, icon_path)
|
|
|
|
# Set environment variable
|
|
new_xbmlangpath = os.pathsep.join(paths)
|
|
os.environ['XBMLANGPATH'] = new_xbmlangpath
|
|
|
|
# Update MEL environment with proper escaping
|
|
try:
|
|
# Escape backslashes and quotes for MEL
|
|
safe_path = new_xbmlangpath.replace('\\', '/').replace('"', '\\"')
|
|
mel.eval(f'putenv "XBMLANGPATH" "{safe_path}";')
|
|
except Exception:
|
|
pass
|
|
|
|
_tool_log(f"[Tool] ✓ Icon paths configured: {len(icon_paths)} package(s)")
|
|
_verify_shelf_icon_references(paths)
|
|
|
|
except Exception as e:
|
|
_tool_print(f"[Tool] Error setting up icon paths: {e}")
|
|
|
|
def load_tool_shelves():
|
|
"""Load Nexus shelves into Maya"""
|
|
try:
|
|
# Determine shelf paths
|
|
shelf_paths = os.environ.get('MAYA_SHELF_PATH', '')
|
|
if not shelf_paths:
|
|
script_dir = _get_script_dir()
|
|
shelf_paths = os.path.join(os.path.dirname(script_dir), "shelves")
|
|
if not os.path.exists(shelf_paths):
|
|
_tool_print("[Tool] Shelf directory not found, skipping")
|
|
return
|
|
|
|
shelf_path_list = [p.strip() for p in shelf_paths.split(os.pathsep) if p.strip()]
|
|
|
|
# Load each shelf
|
|
loaded_count = 0
|
|
for shelf_name in SHELF_NAMES:
|
|
# Find shelf file
|
|
shelf_file_found = None
|
|
for shelf_path in shelf_path_list:
|
|
shelf_file = _norm_path(os.path.join(shelf_path, f"shelf_{shelf_name}.mel"))
|
|
if os.path.exists(shelf_file):
|
|
shelf_file_found = shelf_file
|
|
_tool_log(f"[Tool] Found shelf: {shelf_file}")
|
|
break
|
|
|
|
if not shelf_file_found:
|
|
_tool_print(f"[Tool] Shelf not found: shelf_{shelf_name}.mel")
|
|
continue
|
|
|
|
# Remove existing shelf
|
|
if cmds.shelfLayout(shelf_name, exists=True):
|
|
try:
|
|
cmds.deleteUI(shelf_name, layout=True)
|
|
_tool_log(f"[Tool] Removed existing shelf: {shelf_name}")
|
|
except Exception as e:
|
|
_tool_log(f"[Tool] Could not remove shelf: {e}")
|
|
|
|
# Remove cached config
|
|
_remove_shelf_config(shelf_name)
|
|
|
|
try:
|
|
# Disable shelf saving
|
|
_safe_mel_eval('optionVar -intValue "saveLastLoadedShelf" 0;')
|
|
|
|
# Create shelf layout using Python API instead of MEL
|
|
try:
|
|
# Get shelf parent using safe MEL evaluation
|
|
shelf_parent = mel.eval('global string $gShelfTopLevel; $temp = $gShelfTopLevel;')
|
|
cmds.setParent(shelf_parent)
|
|
cmds.shelfLayout(shelf_name, cellWidth=35, cellHeight=34)
|
|
except Exception as e:
|
|
_tool_log(f"[Tool] Error creating shelf layout: {e}")
|
|
raise
|
|
|
|
if not cmds.shelfLayout(shelf_name, exists=True):
|
|
raise RuntimeError(f"Failed to create shelf layout: {shelf_name}")
|
|
|
|
# Load shelf content with safe path handling
|
|
_safe_mel_eval(f'setParent {shelf_name};')
|
|
|
|
# Use forward slashes for cross-platform compatibility
|
|
safe_shelf_path = shelf_file_found.replace('\\', '/')
|
|
mel.eval(f'source "{safe_shelf_path}";')
|
|
mel.eval(f'shelf_{shelf_name}();')
|
|
|
|
# Verify loaded
|
|
buttons = cmds.shelfLayout(shelf_name, query=True, childArray=True) or []
|
|
_tool_print(f"[Tool] ✓ Shelf loaded: {shelf_name} ({len(buttons)} buttons)")
|
|
loaded_count += 1
|
|
|
|
# Remove config again to prevent saving
|
|
_remove_shelf_config(shelf_name)
|
|
|
|
except Exception as e:
|
|
_tool_print(f"[Tool] Error loading shelf {shelf_name}: {e}")
|
|
|
|
_tool_log(f"[Tool] ✓ Shelves: {loaded_count}/{len(SHELF_NAMES)} loaded")
|
|
|
|
except Exception as e:
|
|
_tool_print(f"[Tool] Error loading shelves: {e}")
|
|
|
|
def load_tool_plugins():
|
|
"""Load Nexus plugins"""
|
|
try:
|
|
script_dir = _get_script_dir()
|
|
plugin_configs = TOOL_CONFIG.get('plugins', [])
|
|
if not plugin_configs:
|
|
_tool_log("[Tool] No plugins configured")
|
|
return
|
|
|
|
loaded_count = 0
|
|
for cfg in plugin_configs:
|
|
plugin_name = cfg.get('name', '')
|
|
plugin_rel_path = cfg.get('path', '')
|
|
env_var = cfg.get('env_var', 'MAYA_PLUG_IN_PATH')
|
|
if not plugin_name:
|
|
continue
|
|
|
|
_tool_log(f"[Tool] Processing plugin: {plugin_name}")
|
|
|
|
# First check the configured path parameter
|
|
plugin_path = None
|
|
if plugin_rel_path:
|
|
# Build full path: script_dir/path/plugin_name
|
|
full_path = _norm_path(os.path.join(script_dir, plugin_rel_path, plugin_name))
|
|
if os.path.exists(full_path):
|
|
plugin_path = full_path
|
|
_tool_log(f"[Tool] Found plugin at configured path: {plugin_path}")
|
|
|
|
# If the configuration path is not found, search the environment variable path.
|
|
if not plugin_path:
|
|
plugin_path = _find_file_in_paths(plugin_name, os.environ.get(env_var, '').split(os.pathsep))
|
|
|
|
# Finally, try the script directory and the parent plug-ins folder.
|
|
if not plugin_path:
|
|
search_dirs = [
|
|
script_dir,
|
|
os.path.join(os.path.dirname(script_dir), 'plug-ins')
|
|
]
|
|
plugin_path = _find_file_in_paths(plugin_name, search_dirs)
|
|
|
|
if not plugin_path:
|
|
_tool_print(f"[Tool] Plugin not found: {plugin_name}")
|
|
continue
|
|
|
|
# Add plugin directory to environment
|
|
plugin_dir = _norm_path(os.path.dirname(plugin_path))
|
|
current_paths = [p for p in os.environ.get(env_var, '').split(os.pathsep) if p]
|
|
if plugin_dir not in current_paths:
|
|
os.environ[env_var] = os.pathsep.join([plugin_dir] + current_paths)
|
|
|
|
# Get plugin basename
|
|
plugin_basename = os.path.splitext(os.path.basename(plugin_path))[0]
|
|
|
|
# Check if already loaded
|
|
try:
|
|
already_loaded = bool(cmds.pluginInfo(plugin_basename, query=True, loaded=True))
|
|
except Exception:
|
|
already_loaded = False
|
|
|
|
if already_loaded:
|
|
_tool_log(f"[Tool] Plugin already loaded: {plugin_basename}")
|
|
_LOADED_PLUGINS[plugin_basename] = plugin_path
|
|
continue
|
|
|
|
# Load plugin
|
|
try:
|
|
cmds.loadPlugin(plugin_path, quiet=True)
|
|
_tool_print(f"[Tool] ✓ Plugin loaded: {plugin_basename}")
|
|
loaded_count += 1
|
|
_LOADED_PLUGINS[plugin_basename] = plugin_path
|
|
except Exception:
|
|
try:
|
|
# Fallback: load by basename
|
|
cmds.loadPlugin(plugin_basename, quiet=True)
|
|
_tool_print(f"[Tool] ✓ Plugin loaded: {plugin_basename}")
|
|
loaded_count += 1
|
|
_LOADED_PLUGINS[plugin_basename] = plugin_path
|
|
except Exception as e:
|
|
_tool_print(f"[Tool] Error loading plugin {plugin_basename}: {e}")
|
|
|
|
_tool_log(f"[Tool] ✓ Plugins: {loaded_count}/{len(plugin_configs)} loaded")
|
|
|
|
except Exception as e:
|
|
_tool_print(f"[Tool] Error loading plugins: {e}")
|
|
|
|
def load_tool_scripts():
|
|
"""Add Nexus script paths to sys.path"""
|
|
try:
|
|
script_dir = _get_script_dir()
|
|
script_configs = TOOL_CONFIG.get('scripts', [])
|
|
if not script_configs:
|
|
_tool_log("[Tool] No scripts configured")
|
|
return
|
|
|
|
loaded = 0
|
|
for cfg in script_configs:
|
|
name = cfg.get('name', '')
|
|
path = cfg.get('path', '')
|
|
if not (name and path):
|
|
continue
|
|
|
|
full_path = os.path.normpath(os.path.join(script_dir, path))
|
|
if not os.path.exists(full_path):
|
|
_tool_print(f"[Tool] Script path not found: {full_path}")
|
|
continue
|
|
|
|
if full_path not in sys.path:
|
|
sys.path.insert(0, full_path)
|
|
_ADDED_SCRIPT_PATHS.append(full_path)
|
|
_tool_log(f"[Tool] Added script path: {name} -> {full_path}")
|
|
loaded += 1
|
|
|
|
_tool_log(f"[Tool] ✓ Script paths: {loaded}/{len(script_configs)} added")
|
|
|
|
except Exception as e:
|
|
_tool_print(f"[Tool] Error loading scripts: {e}")
|
|
|
|
def load_project_modules():
|
|
"""Load plugins defined in .mod files from the modules directory."""
|
|
try:
|
|
# Get current Maya version
|
|
maya_version = int(cmds.about(version=True).split()[0])
|
|
|
|
# Get project root directory
|
|
script_dir = _get_script_dir()
|
|
project_root = os.path.dirname(os.path.dirname(script_dir))
|
|
modules_dir = _norm_path(os.path.join(project_root, "modules"))
|
|
|
|
if not os.path.exists(modules_dir):
|
|
_tool_log(f"[Tool] Modules directory not found: {modules_dir}")
|
|
return
|
|
|
|
# Find all .mod files
|
|
mod_files = [f for f in os.listdir(modules_dir) if f.endswith('.mod')]
|
|
|
|
if not mod_files:
|
|
_tool_log(f"[Tool] No .mod files found in: {modules_dir}")
|
|
return
|
|
|
|
_tool_print(f"[Tool] Found {len(mod_files)} module(s): {', '.join(mod_files)}")
|
|
|
|
# Parse .mod files and load plugins
|
|
plugins_loaded = 0
|
|
for mod_file in mod_files:
|
|
mod_path = os.path.join(modules_dir, mod_file)
|
|
_tool_log(f"[Tool] Processing: {mod_file}")
|
|
|
|
try:
|
|
current_module_root = None
|
|
in_correct_version = False
|
|
|
|
with open(mod_path, 'r', encoding='utf-8') as f:
|
|
for line in f:
|
|
line = line.strip()
|
|
|
|
# Skip comments and empty lines
|
|
if not line or line.startswith('#') or line.startswith('//'):
|
|
continue
|
|
|
|
# Parse module definition lines (starting with +)
|
|
if line.startswith('+'):
|
|
parts = line.split()
|
|
in_correct_version = False
|
|
current_module_root = None
|
|
|
|
if len(parts) >= 4:
|
|
# Check for version restrictions
|
|
has_version_requirement = False
|
|
for part in parts:
|
|
if part.startswith('MAYAVERSION:'):
|
|
has_version_requirement = True
|
|
required_version = int(part.split(':')[1])
|
|
if required_version == maya_version:
|
|
in_correct_version = True
|
|
# Get the module root path (last parameter)
|
|
current_module_root = parts[-1]
|
|
if current_module_root.startswith('..'):
|
|
current_module_root = _norm_path(os.path.join(modules_dir, current_module_root))
|
|
_tool_log(f"[Tool] Version matched for Maya {maya_version}: {current_module_root}")
|
|
break
|
|
|
|
# If there are no version restrictions, it works for all versions.
|
|
if not has_version_requirement:
|
|
in_correct_version = True
|
|
current_module_root = parts[-1]
|
|
if current_module_root.startswith('..'):
|
|
current_module_root = _norm_path(os.path.join(modules_dir, current_module_root))
|
|
_tool_log(f"[Tool] Module without version restriction loaded: {current_module_root}")
|
|
|
|
# Only process MAYA_PLUG_IN_PATH that matches the version.
|
|
elif line.startswith('MAYA_PLUG_IN_PATH') and in_correct_version and current_module_root:
|
|
if '+:=' in line or '+=' in line:
|
|
plugin_path_relative = line.split('=')[-1].strip()
|
|
plugins_dir = _norm_path(os.path.join(current_module_root, plugin_path_relative))
|
|
|
|
if os.path.exists(plugins_dir):
|
|
_tool_log(f"[Tool] Checking plugin dir: {plugins_dir}")
|
|
|
|
for plugin_file in os.listdir(plugins_dir):
|
|
if plugin_file.endswith(('.mll', '.so', '.bundle', '.py')):
|
|
plugin_name = os.path.splitext(plugin_file)[0]
|
|
plugin_full_path = os.path.join(plugins_dir, plugin_file)
|
|
|
|
# Check if already loaded
|
|
if plugin_name in _LOADED_PLUGINS:
|
|
continue
|
|
|
|
try:
|
|
is_loaded = cmds.pluginInfo(plugin_name, query=True, loaded=True)
|
|
except:
|
|
is_loaded = False
|
|
|
|
if not is_loaded:
|
|
try:
|
|
cmds.loadPlugin(plugin_full_path, quiet=True)
|
|
_tool_print(f"[Tool] ✓ Plugin loaded: {plugin_name} (from {mod_file})")
|
|
plugins_loaded += 1
|
|
_LOADED_PLUGINS[plugin_name] = plugin_full_path
|
|
except Exception as e:
|
|
_tool_print(f"[Tool] Failed to load {plugin_name}: {e}")
|
|
else:
|
|
_tool_log(f"[Tool] Plugin dir not found: {plugins_dir}")
|
|
|
|
except Exception as e:
|
|
_tool_print(f"[Tool] Error processing {mod_file}: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
|
|
if plugins_loaded > 0:
|
|
_tool_print(f"[Tool] ✓ Total {plugins_loaded} module plugin(s) loaded")
|
|
else:
|
|
_tool_print(f"[Tool] No module plugins loaded")
|
|
|
|
except Exception as e:
|
|
_tool_print(f"[Tool] Error loading modules: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
|
|
def setup_command_port():
|
|
"""Setup command port for external connections"""
|
|
global _COMMAND_PORT_OPENED
|
|
try:
|
|
# Check if already open
|
|
try:
|
|
port_exists = bool(mel.eval('commandPort -q -name "commandportDefault"'))
|
|
except Exception:
|
|
port_exists = False
|
|
|
|
if port_exists:
|
|
_tool_log("[Tool] Command port already open")
|
|
_COMMAND_PORT_OPENED = True
|
|
return
|
|
|
|
# Open command port
|
|
mel.eval('commandPort -securityWarning -name "commandportDefault";')
|
|
_tool_log("[Tool] ✓ Command port opened")
|
|
_COMMAND_PORT_OPENED = True
|
|
|
|
except Exception as e:
|
|
_tool_log(f"[Tool] Could not open command port: {e}")
|
|
|
|
# =============================================================================
|
|
# Main Initialization
|
|
# =============================================================================
|
|
|
|
def initialize_tool():
|
|
"""Main initialization function called on Maya startup"""
|
|
try:
|
|
print("=" * 80)
|
|
print("[Tool] Nexus Plugin System - Initializing...")
|
|
print("=" * 80)
|
|
|
|
# Check Maya version compatibility
|
|
if not _check_maya_version():
|
|
print("[Tool] Warning: Running on unsupported Maya version")
|
|
|
|
# First, set the icon path so that the shelf can find the icon when it loads.
|
|
setup_icon_paths()
|
|
|
|
# Load project modules
|
|
load_project_modules() # Use Maya standard module systems for automatic loading.
|
|
|
|
# Setup command port
|
|
setup_command_port()
|
|
|
|
# Load components in order
|
|
load_tool_scripts()
|
|
load_tool_plugins()
|
|
load_tool_shelves()
|
|
|
|
print("=" * 80)
|
|
print("[Tool] Nexus Plugin System - Ready!")
|
|
print("=" * 80)
|
|
|
|
except Exception as e:
|
|
print(f"[Tool] Initialization error: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
|
|
# Defer initialization until Maya is fully loaded
|
|
maya.utils.executeDeferred(initialize_tool)
|
|
|
|
# =============================================================================
|
|
# Cleanup on Exit
|
|
# =============================================================================
|
|
|
|
def cleanup_on_exit():
|
|
"""Cleanup resources when Maya exits"""
|
|
try:
|
|
_tool_log("[Tool] Cleanup initiated...")
|
|
|
|
# Close command port
|
|
global _COMMAND_PORT_OPENED
|
|
if _COMMAND_PORT_OPENED:
|
|
try:
|
|
if mel.eval('commandPort -q -name "commandportDefault"'):
|
|
mel.eval('commandPort -cl "commandportDefault"')
|
|
_tool_log("[Tool] ✓ Command port closed")
|
|
except Exception as e:
|
|
_tool_log(f"[Tool] Could not close command port: {e}")
|
|
|
|
# Unload plugins
|
|
if _LOADED_PLUGINS:
|
|
unloaded = 0
|
|
for plugin_basename, plugin_path in list(_LOADED_PLUGINS.items()):
|
|
try:
|
|
# Check if still loaded
|
|
try:
|
|
is_loaded = bool(cmds.pluginInfo(plugin_basename, query=True, loaded=True))
|
|
except Exception:
|
|
is_loaded = False
|
|
|
|
if is_loaded:
|
|
try:
|
|
cmds.unloadPlugin(plugin_basename, force=True)
|
|
_tool_log(f"[Tool] ✓ Plugin unloaded: {plugin_basename}")
|
|
unloaded += 1
|
|
except Exception as e:
|
|
_tool_log(f"[Tool] Could not unload plugin {plugin_basename}: {e}")
|
|
except Exception:
|
|
continue
|
|
|
|
if unloaded > 0:
|
|
_tool_log(f"[Tool] ✓ Plugins unloaded: {unloaded}/{len(_LOADED_PLUGINS)}")
|
|
|
|
# Clean up script paths from sys.path
|
|
global _ADDED_SCRIPT_PATHS
|
|
if _ADDED_SCRIPT_PATHS:
|
|
for path in _ADDED_SCRIPT_PATHS:
|
|
try:
|
|
if path in sys.path:
|
|
sys.path.remove(path)
|
|
except Exception:
|
|
pass
|
|
_tool_log(f"[Tool] ✓ Script paths cleaned: {len(_ADDED_SCRIPT_PATHS)}")
|
|
|
|
# Clear global state
|
|
_LOADED_PLUGINS.clear()
|
|
_ICONS_PATHS.clear()
|
|
_ADDED_SCRIPT_PATHS.clear()
|
|
|
|
_tool_log("[Tool] ✓ Cleanup complete")
|
|
|
|
except Exception as e:
|
|
_tool_log(f"[Tool] Cleanup error: {e}")
|
|
|
|
# Register cleanup function
|
|
atexit.register(cleanup_on_exit)
|