From 7baece90bca4b2fba5f7ca6379e307033db57149 Mon Sep 17 00:00:00 2001 From: jeffreytsai1004 Date: Sun, 23 Nov 2025 20:50:30 +0800 Subject: [PATCH] Update --- 2023/README.md | 47 ++ 2023/RELOAD_SHELF.py | 121 +++ 2023/icons/README.md | 24 + 2023/icons/batchextrusion.png | Bin 0 -> 13822 bytes 2023/plug-ins/nexus_plugin.py | 58 ++ 2023/scripts/animation_tools/__init__.py | 9 + 2023/scripts/modeling_tools/__init__.py | 13 + .../__pycache__/__init__.cpython-39.pyc | Bin 0 -> 312 bytes .../__pycache__/batchextrusion.cpython-39.pyc | Bin 0 -> 20111 bytes 2023/scripts/modeling_tools/batchextrusion.py | 761 ++++++++++++++++++ 2023/scripts/nexus_test.py | 31 + 2023/scripts/rigging_tools/__init__.py | 9 + 2023/scripts/userSetup.py | 200 +++++ 2023/shelves/shelf_Nexus_Animation.mel | 43 + 2023/shelves/shelf_Nexus_Modeling.mel | 43 + 2023/shelves/shelf_Nexus_Rigging.mel | 43 + CleanCache.bat | 37 + 17 files changed, 1439 insertions(+) create mode 100644 2023/README.md create mode 100644 2023/RELOAD_SHELF.py create mode 100644 2023/icons/README.md create mode 100644 2023/icons/batchextrusion.png create mode 100644 2023/plug-ins/nexus_plugin.py create mode 100644 2023/scripts/animation_tools/__init__.py create mode 100644 2023/scripts/modeling_tools/__init__.py create mode 100644 2023/scripts/modeling_tools/__pycache__/__init__.cpython-39.pyc create mode 100644 2023/scripts/modeling_tools/__pycache__/batchextrusion.cpython-39.pyc create mode 100644 2023/scripts/modeling_tools/batchextrusion.py create mode 100644 2023/scripts/nexus_test.py create mode 100644 2023/scripts/rigging_tools/__init__.py create mode 100644 2023/scripts/userSetup.py create mode 100644 2023/shelves/shelf_Nexus_Animation.mel create mode 100644 2023/shelves/shelf_Nexus_Modeling.mel create mode 100644 2023/shelves/shelf_Nexus_Rigging.mel create mode 100644 CleanCache.bat diff --git a/2023/README.md b/2023/README.md new file mode 100644 index 0000000..06c65ed --- /dev/null +++ b/2023/README.md @@ -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 目录 diff --git a/2023/RELOAD_SHELF.py b/2023/RELOAD_SHELF.py new file mode 100644 index 0000000..81677ec --- /dev/null +++ b/2023/RELOAD_SHELF.py @@ -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() diff --git a/2023/icons/README.md b/2023/icons/README.md new file mode 100644 index 0000000..438368a --- /dev/null +++ b/2023/icons/README.md @@ -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 环境变量指定的目录中查找图标文件。 diff --git a/2023/icons/batchextrusion.png b/2023/icons/batchextrusion.png new file mode 100644 index 0000000000000000000000000000000000000000..e74527dd0778430ea6f5c325182ea8e758827f46 GIT binary patch literal 13822 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 literal 0 HcmV?d00001 diff --git a/2023/plug-ins/nexus_plugin.py b/2023/plug-ins/nexus_plugin.py new file mode 100644 index 0000000..d85d6e6 --- /dev/null +++ b/2023/plug-ins/nexus_plugin.py @@ -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 diff --git a/2023/scripts/animation_tools/__init__.py b/2023/scripts/animation_tools/__init__.py new file mode 100644 index 0000000..b553fc0 --- /dev/null +++ b/2023/scripts/animation_tools/__init__.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Nexus Animation Tools Package +Animation utilities and helpers +""" + +__all__ = [] diff --git a/2023/scripts/modeling_tools/__init__.py b/2023/scripts/modeling_tools/__init__.py new file mode 100644 index 0000000..cb40630 --- /dev/null +++ b/2023/scripts/modeling_tools/__init__.py @@ -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' +] diff --git a/2023/scripts/modeling_tools/__pycache__/__init__.cpython-39.pyc b/2023/scripts/modeling_tools/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dd05dafca0410033fe4cf149f2303eac2b2c1e65 GIT binary patch literal 312 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 U&F$t6b4THeiRhngr7Z1#1EFxUjQ{`u literal 0 HcmV?d00001 diff --git a/2023/scripts/modeling_tools/batchextrusion.py b/2023/scripts/modeling_tools/batchextrusion.py new file mode 100644 index 0000000..350c0c7 --- /dev/null +++ b/2023/scripts/modeling_tools/batchextrusion.py @@ -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() diff --git a/2023/scripts/nexus_test.py b/2023/scripts/nexus_test.py new file mode 100644 index 0000000..8a8eb10 --- /dev/null +++ b/2023/scripts/nexus_test.py @@ -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() diff --git a/2023/scripts/rigging_tools/__init__.py b/2023/scripts/rigging_tools/__init__.py new file mode 100644 index 0000000..a987049 --- /dev/null +++ b/2023/scripts/rigging_tools/__init__.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Nexus Rigging Tools Package +Character rigging utilities +""" + +__all__ = [] diff --git a/2023/scripts/userSetup.py b/2023/scripts/userSetup.py new file mode 100644 index 0000000..5d41610 --- /dev/null +++ b/2023/scripts/userSetup.py @@ -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) diff --git a/2023/shelves/shelf_Nexus_Animation.mel b/2023/shelves/shelf_Nexus_Animation.mel new file mode 100644 index 0000000..a7ff9a1 --- /dev/null +++ b/2023/shelves/shelf_Nexus_Animation.mel @@ -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 + ; + +} diff --git a/2023/shelves/shelf_Nexus_Modeling.mel b/2023/shelves/shelf_Nexus_Modeling.mel new file mode 100644 index 0000000..590ff7f --- /dev/null +++ b/2023/shelves/shelf_Nexus_Modeling.mel @@ -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 + ; + +} diff --git a/2023/shelves/shelf_Nexus_Rigging.mel b/2023/shelves/shelf_Nexus_Rigging.mel new file mode 100644 index 0000000..778a59f --- /dev/null +++ b/2023/shelves/shelf_Nexus_Rigging.mel @@ -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 + ; + +} diff --git a/CleanCache.bat b/CleanCache.bat new file mode 100644 index 0000000..c73e079 --- /dev/null +++ b/CleanCache.bat @@ -0,0 +1,37 @@ +@echo off +echo ======================================== +echo NexusLauncher Cache Cleaner +echo ======================================== +echo. + +@REM echo Close NexusLauncher if it is running... +@REM taskkill /f /im pythonw.exe + +echo [1/4] Cleaning all __pycache__ folders... +for /d /r %%d in (__pycache__) do @if exist "%%d" ( + echo Deleting: %%d + rd /s /q "%%d" +) +echo. + +echo [2/4] Cleaning root cache... +if exist "__pycache__" rd /s /q "__pycache__" +echo. + +echo [3/4] Cleaning module caches... +if exist "config\__pycache__" rd /s /q "config\__pycache__" +if exist "ui\__pycache__" rd /s /q "ui\__pycache__" +if exist "ui\task\__pycache__" rd /s /q "ui\task\__pycache__" +echo. + +echo [4/4] Cleaning .pyc files... +del /s /q *.pyc 2>nul +echo. + +@REM echo Clear old config file +@REM if exist config.json del /f config.json + +echo ======================================== +echo Cache cleaned successfully! +echo ======================================== +pause \ No newline at end of file