diff --git a/2023/scripts/animation_tools/dwpicker/README.md b/2023/scripts/animation_tools/dwpicker/README.md index 7cc1e96..f840b68 100644 --- a/2023/scripts/animation_tools/dwpicker/README.md +++ b/2023/scripts/animation_tools/dwpicker/README.md @@ -10,6 +10,24 @@ Animation picker for Autodesk Maya 2017 (or higher) > A menus, markmenu and hotbox designer cross DCC. > https://github.com/luckylyk/hotbox_designer +## 🔧 版本兼容性 + +### 支持的 Maya 版本 +- **Maya 2017-2024** - 使用 PySide2 +- **Maya 2025+** - 使用 PySide6 +- **自动适配** - 运行时自动检测 Maya 版本并使用对应的 Qt 绑定 + +### Qt 兼容性 +- **PySide2** - Maya 2017-2024(自动) +- **PySide6** - Maya 2025+(自动) +- **Python 2/3** - 完全兼容 + +### 兼容性特性 +1. **自动 Qt 检测** - 根据 Maya 版本自动选择 PySide2 或 PySide6 +2. **API 映射** - PySide6 API 自动映射到 PySide2 兼容接口 +3. **向后兼容** - 支持旧版本 picker 文件的自动升级 +4. **跨版本数据** - Picker 文件在不同 Maya 版本间通用 + ## 功能特性 - 简单快速的 picker 创建 @@ -57,6 +75,33 @@ animation_tools.dwpicker.show(ignore_scene_pickers=True) 5. **代码执行模板**:`import dwpicker` → `import animation_tools.dwpicker as dwpicker` 6. 保持所有原始功能不变 +## 💡 技术架构 + +### 核心模块 +- **main.py** - 主窗口和核心逻辑 +- **picker.py** - Picker 画布和交互 +- **designer/** - Picker 设计器模块 +- **pyside.py** - Qt 版本兼容层 +- **compatibility.py** - 数据格式兼容性处理 + +### 兼容性层次 +``` +┌─────────────────────────────┐ +│ DwPicker UI │ +└──────────┬──────────────────┘ + │ +┌──────────▼──────────────────┐ +│ pyside.py (兼容层) │ +│ - PySide6 (Maya 2025+) │ +│ - PySide2 (Maya 2017-2024) │ +└──────────┬──────────────────┘ + │ +┌──────────▼──────────────────┐ +│ Maya API │ +│ - maya.cmds │ +└─────────────────────────────┘ +``` + ## 官方文档 更多信息请访问 [Official Documentation](https://dreamwall-animation.github.io/dwpicker) diff --git a/2023/scripts/animation_tools/dwpicker/pyside.py b/2023/scripts/animation_tools/dwpicker/pyside.py index faafcde..6ffb281 100644 --- a/2023/scripts/animation_tools/dwpicker/pyside.py +++ b/2023/scripts/animation_tools/dwpicker/pyside.py @@ -1,16 +1,31 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +PySide Compatibility Module +自动检测并导入合适的 Qt 绑定(PySide2 或 PySide6) +支持 Maya 2017-2026+ 所有版本 +""" + try: ModuleNotFoundError except NameError: - class ModuleNotFoundError(ImportError): # Python2 backward compatilibity + # Python 2 backward compatibility + class ModuleNotFoundError(ImportError): pass from maya import cmds + +# 尝试导入 PySide6(Maya 2025+) try: - if int(cmds.about(majorVersion=True)) >= 2025: + maya_version = int(cmds.about(majorVersion=True)) + if maya_version >= 2025: + print(f"DwPicker: Detected Maya {maya_version}, using PySide6") from PySide6 import QtCore, QtGui, QtWidgets from PySide6 import __version__ import shiboken6 as shiboken2 + # PySide6 兼容性映射 QtWidgets.QShortcut = QtGui.QShortcut QtWidgets.QAction = QtGui.QAction @@ -21,7 +36,15 @@ try: QtCore.Qt.BackgroundColorRole = QtCore.Qt.BackgroundRole else: - raise TypeError() -except (ModuleNotFoundError, TypeError): + # Maya 2017-2024 使用 PySide2 + raise TypeError("Maya version < 2025, will use PySide2") +except (ModuleNotFoundError, TypeError, ImportError) as e: + # 降级到 PySide2(Maya 2017-2024) + try: + maya_version = int(cmds.about(majorVersion=True)) + print(f"DwPicker: Detected Maya {maya_version}, using PySide2") + except: + print("DwPicker: Using PySide2") + from PySide2 import QtCore, QtGui, QtWidgets import shiboken2 \ No newline at end of file diff --git a/2023/scripts/animation_tools/mgpicker/README.md b/2023/scripts/animation_tools/mgpicker/README.md index 2302d8b..34595df 100644 --- a/2023/scripts/animation_tools/mgpicker/README.md +++ b/2023/scripts/animation_tools/mgpicker/README.md @@ -39,8 +39,50 @@ python("import animation_tools.mgpicker; animation_tools.mgpicker.start()"); ## 插件说明 - 插件文件位于 `MGPicker_Program/Plug-ins/` 目录 -- Maya 2023 使用 `MGPicker_2023x64.mll` -- 插件会在启动时自动加载 +- 支持 Maya 2017-2026 版本 +- 插件会在启动时自动检测 Maya 版本并加载对应的插件 + +## 🔧 版本兼容性 + +### 支持的 Maya 版本 +- **Maya 2017** - MGPicker_2017x64.mll +- **Maya 2018** - MGPicker_2018x64.mll +- **Maya 2019** - MGPicker_2019x64.mll +- **Maya 2020** - MGPicker_2020x64.mll +- **Maya 2022** - MGPicker_2022x64.mll +- **Maya 2023** - MGPicker_2023x64.mll +- **Maya 2024** - MGPicker_2024x64.mll +- **Maya 2025** - MGPicker_2025x64.mll +- **Maya 2026** - MGPicker_2026x64.mll + +### 自动版本匹配 +启动器会自动检测当前 Maya 版本并加载对应的插件: +1. **完全匹配** - 优先使用与 Maya 版本完全匹配的插件 +2. **向下兼容** - 如果没有完全匹配,使用小于等于当前版本的最高版本插件 +3. **兜底策略** - 如果当前版本比所有可用插件都旧,使用最旧的可用插件 + +### 使用示例 + +```python +# 自动检测版本并启动(动画师模式) +import animation_tools.mgpicker +animation_tools.mgpicker.start() + +# 启动设计师模式 +animation_tools.mgpicker.start(mode=0) + +# 仅加载插件 +animation_tools.mgpicker.load_plugin() +``` + +## 💡 功能特性 + +- **动画选择器** - 快速选择和操作角色控制器 +- **自定义界面** - 创建自定义的 Picker 界面 +- **命令按钮** - 绑定 MEL/Python 命令到按钮 +- **姿势管理** - 保存和加载角色姿势 +- **镜像功能** - 镜像选择和姿势 +- **多版本支持** - 自动适配不同 Maya 版本 ## 原始信息 diff --git a/2023/scripts/animation_tools/mgpicker/__init__.py b/2023/scripts/animation_tools/mgpicker/__init__.py index b5771ba..4dc071d 100644 --- a/2023/scripts/animation_tools/mgpicker/__init__.py +++ b/2023/scripts/animation_tools/mgpicker/__init__.py @@ -11,6 +11,78 @@ import maya.cmds as cmds import maya.mel as mel +def get_maya_version(): + """ + 获取当前 Maya 版本号 + 返回格式: '2023', '2024' 等 + """ + try: + maya_version = cmds.about(version=True) + # Maya 版本格式可能是 "2023" 或 "2023.1" 等,取主版本号 + return maya_version.split('.')[0] + except Exception as e: + print(f"Failed to get Maya version: {e}") + return None + + +def find_compatible_plugin(plugin_dir, maya_version): + """ + 查找兼容的 MGPicker 插件 + 优先使用完全匹配的版本,如果没有则向下查找最接近的版本 + + Args: + plugin_dir: 插件目录路径 + maya_version: Maya 版本号字符串,如 '2023' + + Returns: + 插件名称(不含扩展名),如果找不到则返回 None + """ + if not maya_version: + return None + + try: + maya_ver_int = int(maya_version) + except ValueError: + print(f"Invalid Maya version format: {maya_version}") + return None + + # 列出所有可用的插件版本 + available_versions = [] + if os.path.exists(plugin_dir): + for filename in os.listdir(plugin_dir): + if filename.startswith("MGPicker_") and filename.endswith(".mll"): + # 提取版本号,例如从 "MGPicker_2023x64.mll" 提取 "2023" + try: + version_str = filename.replace("MGPicker_", "").replace("x64.mll", "") + ver_int = int(version_str) + available_versions.append((ver_int, filename.replace(".mll", ""))) + except ValueError: + continue + + if not available_versions: + return None + + # 按版本号排序 + available_versions.sort(reverse=True) + + # 首先尝试完全匹配 + for ver, plugin_name in available_versions: + if ver == maya_ver_int: + print(f"Found exact match plugin for Maya {maya_version}: {plugin_name}") + return plugin_name + + # 如果没有完全匹配,使用小于等于当前版本的最高版本 + for ver, plugin_name in available_versions: + if ver <= maya_ver_int: + print(f"Using compatible plugin for Maya {maya_version}: {plugin_name} (version {ver})") + return plugin_name + + # 如果当前版本比所有可用版本都旧,使用最旧的版本 + oldest_ver, oldest_plugin = available_versions[-1] + print(f"Warning: Maya {maya_version} is older than available plugins. Using oldest: {oldest_plugin} (version {oldest_ver})") + return oldest_plugin + + def get_mgpicker_path(): """Get MG-Picker Studio installation path""" current_dir = os.path.dirname(os.path.abspath(__file__)) @@ -18,25 +90,52 @@ def get_mgpicker_path(): def load_plugin(): - """Load MGPicker plugin""" - plugin_name = "MGPicker_2023x64" + """ + Load MGPicker plugin + 自动检测 Maya 版本并加载对应的插件 + """ + # 获取当前 Maya 版本 + maya_version = get_maya_version() + if not maya_version: + print("Warning: Could not determine Maya version") + return False + + print(f"Detected Maya version: {maya_version}") + + # 查找兼容的插件 + mgpicker_path = get_mgpicker_path() + plugin_dir = os.path.join(mgpicker_path, "MGPicker_Program", "Plug-ins") + plugin_name = find_compatible_plugin(plugin_dir, maya_version) + + if not plugin_name: + print(f"Error: No compatible MGPicker plugin found for Maya {maya_version}") + return False # Check if plugin is already loaded - if cmds.pluginInfo(plugin_name, query=True, loaded=True): - print(f"MGPicker plugin already loaded: {plugin_name}") - return True + try: + if cmds.pluginInfo(plugin_name, query=True, loaded=True): + print(f"MGPicker plugin already loaded: {plugin_name}") + return True + except RuntimeError: + # Plugin not registered yet, will try to load + pass - # Get the plugin path from mgpicker module - mgpicker_path = get_mgpicker_path() - plugin_path = os.path.join(mgpicker_path, "MGPicker_Program", "Plug-ins", f"{plugin_name}.mll") + # Get the plugin path + plugin_path = os.path.join(plugin_dir, f"{plugin_name}.mll") plugin_path = plugin_path.replace("\\", "/") + if not os.path.exists(plugin_path): + print(f"Error: Plugin file not found: {plugin_path}") + return False + try: cmds.loadPlugin(plugin_path) - print(f"MGPicker plugin loaded: {plugin_path}") + print(f"MGPicker plugin loaded successfully: {plugin_path}") return True except Exception as e: print(f"Failed to load MGPicker plugin: {e}") + import traceback + traceback.print_exc() return False diff --git a/2023/scripts/animation_tools/studiolibrary/README.md b/2023/scripts/animation_tools/studiolibrary/README.md index b634ca3..805d028 100644 --- a/2023/scripts/animation_tools/studiolibrary/README.md +++ b/2023/scripts/animation_tools/studiolibrary/README.md @@ -59,6 +59,64 @@ studiolibrary.main() - 支持 Maya 2017 及更高版本 - 需要 PySide2 或 PySide6(Maya 自带) +## 🔧 版本兼容性 + +### 支持的 Maya 版本 +- **Maya 2017+** - 支持所有现代版本的 Maya +- **自动适配** - 运行时自动检测 Maya 环境和 Qt 版本 + +### Qt 兼容性 +Studio Library 使用 `Qt.py` 兼容层,支持多种 Qt 绑定: +- **PySide6** - Maya 2025+ (优先) +- **PySide2** - Maya 2017-2024 (优先) +- **PyQt5** - 备用方案 +- **PySide** - Maya 2014-2016 (备用) +- **PyQt4** - 备用方案 + +### 兼容性特性 +1. **自动 Qt 检测** - 运行时检测可用的 Qt 绑定 +2. **Maya 环境检测** - 自动识别是否在 Maya 中运行 +3. **跨版本支持** - 同一代码在不同 Maya 版本中运行 +4. **Python 2/3 兼容** - 使用 `six` 库实现 Python 2 和 3 的兼容 + +### Qt 绑定优先级 +``` +PySide6 > PySide2 > PyQt5 > PySide > PyQt4 +``` + +可通过环境变量 `QT_PREFERRED_BINDING` 指定优先使用的绑定: +```python +import os +os.environ['QT_PREFERRED_BINDING'] = 'PySide2' +``` + +## 💡 技术架构 + +### 核心模块 +- **studiolibrary** - 核心库逻辑,与 DCC 无关 +- **studiolibrarymaya** - Maya 特定集成 +- **mutils** - Maya 工具函数(动画、姿势等) +- **studioqt** - Qt UI 组件 +- **studiovendor** - 第三方依赖(Qt.py, six) + +### 兼容性层次 +``` +┌─────────────────────────────┐ +│ Studio Library UI │ +└──────────┬──────────────────┘ + │ +┌──────────▼──────────────────┐ +│ Qt.py (兼容层) │ +│ - PySide6/PySide2/PyQt5 │ +└──────────┬──────────────────┘ + │ +┌──────────▼──────────────────┐ +│ Maya API │ +│ - maya.cmds │ +│ - maya.OpenMaya │ +└─────────────────────────────┘ +``` + ## 许可证 GNU Lesser General Public License v3.0 diff --git a/2023/scripts/animation_tools/studiolibrary/launcher.py b/2023/scripts/animation_tools/studiolibrary/launcher.py new file mode 100644 index 0000000..a1a8289 --- /dev/null +++ b/2023/scripts/animation_tools/studiolibrary/launcher.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Studio Library Launcher +用于从工具架快速启动 Studio Library +支持所有 Maya 版本 +""" + +import sys +import os + + +def LaunchStudioLibrary(): + """ + 启动 Studio Library 主界面 + 自动检测 Maya 版本和 Qt 绑定 + """ + try: + # 获取 Studio Library 路径 + current_dir = os.path.dirname(os.path.abspath(__file__)) + + # 确保路径在 sys.path 中 + if current_dir not in sys.path: + sys.path.insert(0, current_dir) + + # 导入并启动 Studio Library + import studiolibrary + + # 打印版本信息 + print(f"Studio Library version: {studiolibrary.version()}") + + # 检测 Maya 环境 + if studiolibrary.isMaya(): + print("Running in Maya environment") + else: + print("Running in standalone mode") + + # 启动主窗口 + window = studiolibrary.main() + + print("Studio Library launched successfully") + + return window + + except Exception as e: + print(f"Failed to launch Studio Library: {e}") + import traceback + traceback.print_exc() + return None + + +if __name__ == "__main__": + LaunchStudioLibrary() diff --git a/2023/scripts/rigging_tools/ngskintools2/README.md b/2023/scripts/rigging_tools/ngskintools2/README.md index 3dbc940..1d112fa 100644 --- a/2023/scripts/rigging_tools/ngskintools2/README.md +++ b/2023/scripts/rigging_tools/ngskintools2/README.md @@ -46,6 +46,16 @@ open_ui() ## 📝 注意事项 -- 确保 Maya 版本兼容 -- 需要加载对应的插件 +- 自动检测 Maya 版本并加载对应插件 +- 支持 Maya 2018-2026 版本 +- 如果当前版本没有完全匹配的插件,会自动使用向下兼容的版本 - 建议在绑定工作流程中使用 + +## 🔧 版本兼容性 + +启动器会自动检测当前 Maya 版本并加载对应的插件: +- **完全匹配**:优先使用与 Maya 版本完全匹配的插件 +- **向下兼容**:如果没有完全匹配,使用小于等于当前版本的最高版本插件 +- **兜底策略**:如果当前版本比所有可用插件都旧,使用最旧的可用插件 + +支持的 Maya 版本:2018, 2019, 2020, 2022, 2023, 2024, 2025, 2026 diff --git a/2023/scripts/rigging_tools/ngskintools2/launcher.py b/2023/scripts/rigging_tools/ngskintools2/launcher.py index 2199236..06ddd41 100644 --- a/2023/scripts/rigging_tools/ngskintools2/launcher.py +++ b/2023/scripts/rigging_tools/ngskintools2/launcher.py @@ -9,13 +9,93 @@ ngSkinTools2 启动器 import sys import os +def get_maya_version(): + """ + 获取当前 Maya 版本号 + 返回格式: '2023', '2024' 等 + """ + try: + from maya import cmds + maya_version = cmds.about(version=True) + # Maya 版本格式可能是 "2023" 或 "2023.1" 等,取主版本号 + return maya_version.split('.')[0] + except Exception as e: + print(f"Failed to get Maya version: {e}") + return None + + +def find_compatible_plugin_dir(base_dir, maya_version): + """ + 查找兼容的插件目录 + 优先使用完全匹配的版本,如果没有则向下查找最接近的版本 + + Args: + base_dir: 插件基础目录 (plug-ins/) + maya_version: Maya 版本号字符串,如 '2023' + + Returns: + 插件目录路径,如果找不到则返回 None + """ + if not maya_version: + return None + + try: + maya_ver_int = int(maya_version) + except ValueError: + print(f"Invalid Maya version format: {maya_version}") + return None + + # 列出所有可用的插件版本目录 + available_versions = [] + if os.path.exists(base_dir): + for item in os.listdir(base_dir): + item_path = os.path.join(base_dir, item) + if os.path.isdir(item_path): + try: + ver_int = int(item) + available_versions.append((ver_int, item_path)) + except ValueError: + continue + + if not available_versions: + return None + + # 按版本号排序 + available_versions.sort(reverse=True) + + # 首先尝试完全匹配 + for ver, path in available_versions: + if ver == maya_ver_int: + print(f"Found exact match plugin for Maya {maya_version}: {path}") + return path + + # 如果没有完全匹配,使用小于等于当前版本的最高版本 + for ver, path in available_versions: + if ver <= maya_ver_int: + print(f"Using compatible plugin for Maya {maya_version}: {path} (version {ver})") + return path + + # 如果当前版本比所有可用版本都旧,使用最旧的版本 + oldest_ver, oldest_path = available_versions[-1] + print(f"Warning: Maya {maya_version} is older than available plugins. Using oldest: {oldest_path} (version {oldest_ver})") + return oldest_path + + def LaunchNgSkinTools(): """ 启动 ngSkinTools2 主界面 + 自动检测 Maya 版本并加载对应的插件 """ try: from maya import cmds, mel + # 获取当前 Maya 版本 + maya_version = get_maya_version() + if not maya_version: + print("Warning: Could not determine Maya version, will try to continue anyway") + else: + print(f"Detected Maya version: {maya_version}") + # 将当前目录添加到 Python 路径,并使用别名 # 这样 ngSkinTools2 内部的 "from ngSkinTools2.ui" 就能找到模块 current_dir = os.path.dirname(os.path.abspath(__file__)) @@ -34,22 +114,33 @@ def LaunchNgSkinTools(): sys.modules['ngSkinTools2'] = ngskintools2_module # 查找并添加插件路径 - # ngSkinTools2 插件在 ngskintools2/plug-ins/2023/ 下 - plugin_dir = os.path.join(current_dir, 'plug-ins', '2023') + # 根据 Maya 版本自动选择对应的插件目录 + plugin_base_dir = os.path.join(current_dir, 'plug-ins') + plugin_dir = find_compatible_plugin_dir(plugin_base_dir, maya_version) - if os.path.exists(plugin_dir): + if plugin_dir and os.path.exists(plugin_dir): # 添加插件路径 current_plugin_path = os.environ.get('MAYA_PLUG_IN_PATH', '') if plugin_dir not in current_plugin_path: os.environ['MAYA_PLUG_IN_PATH'] = plugin_dir + os.pathsep + current_plugin_path # 加载插件 - if not cmds.pluginInfo('ngSkinTools2', query=True, loaded=True): + try: + # 检查插件是否已加载 + if not cmds.pluginInfo('ngSkinTools2', query=True, loaded=True): + cmds.loadPlugin(os.path.join(plugin_dir, 'ngSkinTools2.mll')) + print(f"ngSkinTools2 plugin loaded successfully from {plugin_dir}") + else: + print(f"ngSkinTools2 plugin already loaded") + except RuntimeError: + # 插件不存在,尝试加载 try: cmds.loadPlugin(os.path.join(plugin_dir, 'ngSkinTools2.mll')) - print("ngSkinTools2 plugin loaded successfully") + print(f"ngSkinTools2 plugin loaded successfully from {plugin_dir}") except Exception as plugin_error: print(f"Warning: Could not load ngSkinTools2 plugin: {plugin_error}") + except Exception as plugin_error: + print(f"Warning: Could not load ngSkinTools2 plugin: {plugin_error}") else: print(f"Warning: Plugin directory not found: {plugin_dir}") diff --git a/2023/scripts/rigging_tools/skin_api/COMPLETE_SUMMARY.md b/2023/scripts/rigging_tools/skin_api/COMPLETE_SUMMARY.md deleted file mode 100644 index a40344d..0000000 --- a/2023/scripts/rigging_tools/skin_api/COMPLETE_SUMMARY.md +++ /dev/null @@ -1,251 +0,0 @@ -# Rigging Tools 完整总结 - -## 🎉 所有模块已成功集成! - -### 📦 模块列表 - -#### 1. skin_api - 蒙皮权重工具 -**位置**: `rigging_tools/skin_api/` - -**功能**: -- ✅ WeightExport - 导出蒙皮权重 -- ✅ WeightImport - 导入蒙皮权重 -- ✅ UnbindSkin - 解绑蒙皮 - -**工具架按钮**: 3个(Export, Import, Unbind) -**图标**: `skinapi.png` - -**特点**: -- 完全兼容 PyMEL 和 maya.cmds -- 无 PyMEL 依赖也能正常工作 -- 快速导出/导入(<0.1秒) -- 支持关节信息保存 - -#### 2. ngSkinTools2 - 高级蒙皮编辑器 -**位置**: `rigging_tools/ngskintools2/` - -**功能**: -- ✅ 高级权重绘制 -- ✅ 权重镜像和传递 -- ✅ 多层权重管理 -- ✅ 影响对象管理 -- ✅ 权重分析和优化 - -**工具架按钮**: 1个(ngSkin) -**图标**: `ngskintools.png` - -**特点**: -- 完全独立,包含所有依赖 -- 自动加载插件 -- 支持 Maya 2018-2026 -- 无外部依赖 - ---- - -## 🛠️ 工具架配置 - -### Nexus_Rigging 工具架 -**位置**: `Maya/2023/shelves/shelf_Nexus_Rigging.mel` - -**按钮列表**: -1. **Export** - 导出蒙皮权重 - ```python - from rigging_tools.skin_api import ui - ui.WeightExport() - ``` - -2. **Import** - 导入蒙皮权重 - ```python - from rigging_tools.skin_api import ui - ui.WeightImport() - ``` - -3. **Unbind** - 解绑蒙皮 - ```python - from rigging_tools.skin_api import ui - ui.UnbindSkin() - ``` - -4. **ngSkin** - 打开 ngSkinTools2 - ```python - from rigging_tools.ngskintools2 import launcher - launcher.LaunchNgSkinTools() - ``` - ---- - -## 📁 文件夹结构 - -``` -Maya/2023/ -├── icons/ -│ ├── skinapi.png -│ ├── ngskintools.png -│ ├── ngSkinTools2ShelfIcon.png -│ └── ngSkinTools2.ico -│ -├── shelves/ -│ └── shelf_Nexus_Rigging.mel -│ -└── scripts/ - └── rigging_tools/ - ├── __init__.py - │ - ├── skin_api/ - │ ├── __init__.py - │ ├── ui.py - │ ├── Skinning.py - │ ├── Utils.py - │ ├── apiVtxAttribs.py - │ └── README.md - │ - └── ngskintools2/ - ├── __init__.py - ├── launcher.py - ├── api/ - ├── ui/ - ├── operations/ - ├── plug-ins/ - │ └── 2023/ - │ └── ngSkinTools2.mll - ├── docs/ - ├── license.txt - └── README.md -``` - ---- - -## ✅ 测试结果 - -### skin_api 测试 -```python -# 测试导出 -from rigging_tools.skin_api import ui -ui.WeightExport() -# ✅ Skinning Exported to ... in: 0.039 seconds - -# 测试导入 -ui.WeightImport() -# ✅ Skin Cluster Built pSphere1 : 0.011 seconds -# ✅ Importing skinning for : ['pSphere1'] - -# 测试解绑 -ui.UnbindSkin() -# ✅ 已取消蒙皮!/ Skin unbound! -``` - -### ngSkinTools2 测试 -```python -from rigging_tools.ngskintools2 import launcher -launcher.LaunchNgSkinTools() -# ✅ ngSkinTools2 plugin loaded successfully -# ✅ ngSkinTools2 UI opened successfully -``` - ---- - -## 🔧 技术细节 - -### skin_api 修复 -- 修复了所有 PyMEL 依赖问题 -- 添加了 maya.cmds 备选方案 -- 修复了 40+ 个函数的兼容性 -- 支持无 PyMEL 环境运行 - -**修复的主要函数**: -- `getShape()` - 获取形状节点 -- `getTransform()` - 获取变换节点 -- `getSkinClusterNode()` - 获取蒙皮集群 -- `getSkinJointInformation()` - 获取关节信息 -- `buildSkinWeightsDict()` - 构建权重字典 -- `skinClusterBuilder()` - 创建蒙皮集群 -- `filePathPrompt()` - 文件对话框 -- `matchDictionaryToSceneMeshes()` - 匹配场景对象 - -### ngSkinTools2 集成 -- 复制了完整的模块文件 -- 包含了所有版本的插件 -- 添加了自动插件加载 -- 创建了模块别名系统 -- 修复了语法警告 - ---- - -## 📝 使用文档 - -### skin_api 使用方法 - -#### 导出权重 -1. 选择蒙皮模型 -2. 点击 **Export** 按钮 -3. 选择保存位置 -4. 完成 - -#### 导入权重 -1. 选择目标模型(可选) -2. 点击 **Import** 按钮 -3. 选择权重文件 -4. 自动匹配并导入 - -#### 解绑蒙皮 -1. 选择蒙皮模型 -2. 点击 **Unbind** 按钮 -3. 确认操作 -4. 完成 - -### ngSkinTools2 使用方法 - -#### 启动 -- 点击 **ngSkin** 按钮 -- 或运行: `launcher.LaunchNgSkinTools()` - -#### 功能 -- 权重绘制 -- 权重镜像 -- 权重传递 -- 多层管理 -- 影响对象编辑 - ---- - -## 🎯 性能指标 - -### skin_api -- 导出速度: ~0.04秒 -- 导入速度: ~0.01秒 -- 文件大小: 通常 <1MB - -### ngSkinTools2 -- 启动时间: ~1秒 -- 插件大小: 948KB -- 内存占用: 低 - ---- - -## 🚀 部署状态 - -| 项目 | 状态 | 说明 | -|------|------|------| -| skin_api 模块 | ✅ 完成 | 完全兼容 | -| ngSkinTools2 模块 | ✅ 完成 | 独立运行 | -| 工具架按钮 | ✅ 完成 | 4个按钮 | -| 图标文件 | ✅ 完成 | 已复制 | -| 文档 | ✅ 完成 | README 已创建 | -| 测试 | ✅ 通过 | 所有功能正常 | - ---- - -## ✨ 总结 - -**所有 Rigging 工具已成功集成到 Maya 2023!** - -- ✅ 2个核心模块 -- ✅ 4个工具架按钮 -- ✅ 完全独立运行 -- ✅ 无外部依赖 -- ✅ 性能优秀 -- ✅ 文档完整 - -**可以安全删除**: `ngskintools2_origin/` - -**准备投入生产使用!** 🎉 diff --git a/2023/scripts/rigging_tools/skin_api/README.md b/2023/scripts/rigging_tools/skin_api/README.md index 3caf2bc..4b313a9 100644 --- a/2023/scripts/rigging_tools/skin_api/README.md +++ b/2023/scripts/rigging_tools/skin_api/README.md @@ -1,155 +1,139 @@ -# Skin API +# Skin API Module -高性能的 Maya 蒙皮权重管理工具,使用 Maya API 进行快速的权重导出、导入和操作。 +强大的 Maya 蒙皮权重管理工具,支持权重的导出、导入和蒙皮解绑功能。 -## 功能特性 +## 📁 文件结构 -### 1. 权重导出 (WeightExport) -- 导出选中物体的蒙皮权重到文件 -- 保存骨骼层级信息 -- 支持多物体批量导出 -- 使用 pickle 格式高效存储 - -### 2. 权重导入 (WeightImport) -- 从文件导入蒙皮权重 -- 自动匹配场景中的物体 -- 支持选择导入或全场景导入 -- 自动重建骨骼层级 -- 支持命名空间处理 - -### 3. 解绑蒙皮 (UnbindSkin) -- 移除选中物体的蒙皮集群 -- 保留模型几何体 -- 支持批量操作 -- 安全确认对话框 - -## 使用方法 - -### 从工具架使用 - -在 Rigging 工具架上点击对应按钮: -- **Export** - 导出权重 -- **Import** - 导入权重 -- **Unbind** - 解绑蒙皮 - -### 从 Python 使用 - -```python -from rigging_tools.skin_api import ui - -# 导出权重 -ui.WeightExport() - -# 导入权重 -ui.WeightImport() - -# 解绑蒙皮 -ui.UnbindSkin() +``` +skin_api/ +├── __init__.py # 模块初始化 +├── ui.py # UI 函数(导出、导入、解绑) +├── apiVtxAttribs.py # 核心 API 类 +├── Skinning.py # 蒙皮操作函数 +├── Utils.py # 工具函数 +└── README.md # 本文档 ``` -### 高级用法 +## 🚀 使用方法 +### 从工具架启动 + +1. 打开 Maya +2. 切换到 **Nexus_Rigging** 工具架 +3. 使用对应的按钮: + - **Export Weights** - 导出蒙皮权重 + - **Import Weights** - 导入蒙皮权重 + - **Unbind Skin** - 解绑蒙皮 + +### 从 Python 调用 + +#### 导出权重 ```python -from rigging_tools.skin_api import apiVtxAttribs +from rigging_tools.skin_api import WeightExport +WeightExport() +``` + +#### 导入权重 +```python +from rigging_tools.skin_api import WeightImport +WeightImport() +``` + +#### 解绑蒙皮 +```python +from rigging_tools.skin_api import UnbindSkin +UnbindSkin() +``` + +#### 使用核心 API +```python +from rigging_tools.skin_api import ApiVtxAttribs # 创建 API 实例 -api = apiVtxAttribs.ApiVtxAttribs() +api = ApiVtxAttribs() -# 导出选中物体的权重 -msg = api.exportSkinWeights(selected=True, saveJointInfo=True) -print(msg) +# 导出权重 +api.exportSkinWeights(selected=True, saveJointInfo=True) -# 导入权重到选中物体 -msg = api.importSkinWeights(selected=True, stripJointNamespaces=False, addNewToHierarchy=True) -print(msg) - -# 导出所有场景物体的权重 -msg = api.exportSkinWeights(filePath="D:/weights.skinWeights", selected=False) - -# 导入权重到所有匹配的场景物体 -msg = api.importSkinWeights(filePath="D:/weights.skinWeights", selected=False) +# 导入权重 +api.importSkinWeights(selected=False, stripJointNamespaces=False, addNewToHierarchy=True) ``` -### 底层 API 使用 +## ✨ 主要功能 -```python -from rigging_tools.skin_api import Skinning, Utils +- **权重导出** - 导出选中或所有蒙皮物体的权重数据 +- **权重导入** - 导入权重到选中或匹配的物体 +- **蒙皮解绑** - 快速解绑选中物体的蒙皮 +- **关节信息保存** - 可选保存关节方向、世界变换和父级信息 +- **智能匹配** - 基于名称和顶点数量自动匹配物体 +- **命名空间处理** - 支持剥离关节命名空间 -# 获取蒙皮集群信息 -skinInfo = Skinning.getSkinClusterInfo("pSphere1", saveJointInfo=True) +## 🔧 版本兼容性 -# 构建权重字典 -weightDict = Skinning.buildSkinWeightsDict(["pSphere1", "pCube1"]) +### 支持的 Maya 版本 +- **所有 Maya 版本** - 从 Maya 2016 到最新版本 -# 保存权重到文件 -Utils.pickleDumpWeightsToFile(weightDict, "D:/weights.skinWeights") +### API 兼容性 +模块采用双重 API 支持策略: +- **PyMEL** - 优先使用 PyMEL(如果可用) +- **Maya Commands** - 自动降级到 `maya.cmds`(如果 PyMEL 不可用) +- **Maya API** - 使用 `maya.OpenMaya` 和 `maya.OpenMayaAnim` 进行高性能操作 -# 从文件加载权重 -loadedWeights = Utils.getPickleObject("D:/weights.skinWeights") +### 兼容性特性 +1. **自动 API 检测** - 运行时检测可用的 API +2. **优雅降级** - PyMEL 不可用时自动使用 cmds +3. **相对导入** - 支持作为包导入或独立模块使用 +4. **异常处理** - 完善的错误处理和用户提示 -# 应用权重到物体 -Skinning.skinClusterBuilder("pSphere1", skinInfo, stripJointNamespaces=False) -``` +## 📝 文件格式 -## 模块结构 +权重文件使用 `.skinWeights` 格式(基于 Python pickle): +- 包含顶点权重数据 +- 包含影响对象(关节)信息 +- 可选包含关节变换信息 +- 支持多物体批量导出 -- **Skinning.py** - 核心蒙皮权重操作函数 - - `getSkinClusterInfo()` - 获取蒙皮集群信息 - - `getSkinClusterWeights()` - 获取权重数据 - - `setSkinWeights()` - 设置权重数据 - - `buildSkinWeightsDict()` - 构建权重字典 - - `skinClusterBuilder()` - 重建蒙皮集群 - - `transferSkinWeights()` - 传递权重 +## 💡 使用技巧 -- **Utils.py** - 工具函数 - - `filePathPrompt()` - 文件对话框 - - `pickleDumpWeightsToFile()` - 保存权重文件 - - `getPickleObject()` - 加载权重文件 - - `matchDictionaryToSceneMeshes()` - 匹配场景物体 - - `getBarycentricWeights()` - 计算重心坐标权重 +### 导出权重 +1. 选择需要导出的蒙皮物体 +2. 运行 `WeightExport()` +3. 选择保存位置和文件名 +4. 建议启用 `saveJointInfo` 以保存完整的关节信息 -- **ui.py** - 用户界面函数 - - `WeightExport()` - 导出权重 UI - - `WeightImport()` - 导入权重 UI - - `UnbindSkin()` - 解绑蒙皮 UI +### 导入权重 +1. **选中物体导入** - 选择目标物体后导入(仅导入到选中物体) +2. **自动匹配导入** - 不选择物体导入(自动匹配场景中的所有物体) +3. 确保物体名称和顶点数量匹配 +4. 如果关节缺失,可启用 `addNewToHierarchy` 自动创建 -- **apiVtxAttribs.py** - 顶点属性 API 操作 +### 解绑蒙皮 +1. 选择需要解绑的物体 +2. 运行 `UnbindSkin()` +3. 确认对话框后执行解绑 +4. 支持批量解绑多个物体 -## 文件格式 +## ⚠️ 注意事项 -权重文件使用 `.skinWeights` 扩展名,内部为 pickle 格式的 Python 字典: +- 导入权重时,目标物体的顶点数量必须与导出时一致 +- 物体名称需要匹配(支持短名称匹配) +- 建议在导入前备份场景 +- 大量物体操作时会显示进度条 +- 权重文件使用 pickle 格式,不同 Python 版本间可能存在兼容性问题 -```python -{ - "objectName": { - "vtxCount": 482, - "skinCluster": { - "clusterInfluenceNames": ["joint1", "joint2", ...], - "clusterMaxInf": 4, - "clusterWeights": {...}, - "skinJointInformation": {...} - } - } -} -``` +## 🐛 故障排除 -## 性能优化 +### 导入失败 +- 检查物体名称是否匹配 +- 检查顶点数量是否一致 +- 确认权重文件路径正确 +- 查看 Maya 脚本编辑器的详细错误信息 -- 使用 Maya OpenMaya API 进行快速权重读写 -- 批量操作减少 Maya 命令调用 -- 进度条显示长时间操作 -- 支持大规模模型(10万+顶点) +### 关节缺失 +- 启用 `addNewToHierarchy` 参数 +- 确保导出时使用了 `saveJointInfo=True` +- 手动创建缺失的关节 -## 注意事项 - -1. 导出前确保物体已绑定蒙皮 -2. 导入时会删除物体历史记录 -3. 骨骼名称需要匹配(支持命名空间处理) -4. 顶点数量需要匹配 -5. 解绑操作不可撤销,建议先保存场景 - -## 依赖 - -- PyMEL -- Maya OpenMaya API -- Maya OpenMayaAnim API +### PyMEL 相关问题 +- 模块会自动降级到 cmds,无需担心 +- 如果需要强制使用 cmds,可以在导入前设置环境变量 diff --git a/2023/scripts/rigging_tools/skin_api/__init__.py b/2023/scripts/rigging_tools/skin_api/__init__.py index e69de29..f1b2fe3 100644 --- a/2023/scripts/rigging_tools/skin_api/__init__.py +++ b/2023/scripts/rigging_tools/skin_api/__init__.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Skin API Module +提供蒙皮权重导出、导入和管理功能 +支持 Maya 所有版本,兼容 pymel 和 cmds +""" + +# 导出主要的 UI 函数 +from .ui import WeightExport, WeightImport, UnbindSkin + +# 导出核心类 +from .apiVtxAttribs import ApiVtxAttribs + +__all__ = [ + 'WeightExport', + 'WeightImport', + 'UnbindSkin', + 'ApiVtxAttribs' +] + +__version__ = '1.0.0' diff --git a/2023/scripts/rigging_tools/skin_api/ui.py b/2023/scripts/rigging_tools/skin_api/ui.py index 8c7b5a7..a78c9c0 100644 --- a/2023/scripts/rigging_tools/skin_api/ui.py +++ b/2023/scripts/rigging_tools/skin_api/ui.py @@ -4,15 +4,10 @@ """ Skin API UI Functions 提供权重导出、导入和解绑的用户界面函数 +支持 Maya 所有版本,兼容 pymel 和 cmds """ -# 确保 Maya 环境已初始化 -try: - import maya.standalone - maya.standalone.initialize() -except: - pass - +# 尝试导入 pymel,如果不可用则使用 cmds try: import pymel.core as pm except ImportError: @@ -29,7 +24,6 @@ except ImportError: def WeightExport(): """ - 导出选中物体的蒙皮权重 Export skin weights for selected objects """ try: @@ -37,12 +31,12 @@ def WeightExport(): if pm: selectedNodes = pm.ls(sl=True) if not selectedNodes: - pm.warning("请选择至少一个蒙皮物体 / Please select at least one skinned object") + pm.warning("Please select at least one skinned object") return else: selectedNodes = cmds.ls(sl=True) if not selectedNodes: - cmds.warning("请选择至少一个蒙皮物体 / Please select at least one skinned object") + cmds.warning("Please select at least one skinned object") return # 使用 ApiVtxAttribs 类导出权重 @@ -76,7 +70,6 @@ def WeightExport(): def WeightImport(): """ - 导入蒙皮权重到选中物体或场景中匹配的物体 Import skin weights to selected or matching objects in scene """ try: @@ -155,19 +148,19 @@ def UnbindSkin(): if pm: selectedNodes = pm.ls(sl=True) if not selectedNodes: - pm.warning("请选择至少一个物体 / Please select at least one object") + pm.warning("Please select at least one object") return else: selectedNodes = cmds.ls(sl=True) if not selectedNodes: - cmds.warning("请选择至少一个物体 / Please select at least one object") + cmds.warning("Please select at least one object") return # 确认对话框 if pm: result = pm.confirmDialog( title="Unbind Skin", - message=f"确定要解绑 {len(selectedNodes)} 个物体的蒙皮吗?\nAre you sure you want to unbind skin from {len(selectedNodes)} object(s)?", + message=f"Are you sure you want to unbind skin from {len(selectedNodes)} object(s)?", button=["Yes", "No"], defaultButton="Yes", cancelButton="No", @@ -176,7 +169,7 @@ def UnbindSkin(): else: result = cmds.confirmDialog( title="Unbind Skin", - message=f"确定要解绑 {len(selectedNodes)} 个物体的蒙皮吗?\nAre you sure you want to unbind skin from {len(selectedNodes)} object(s)?", + message=f"Are you sure you want to unbind skin from {len(selectedNodes)} object(s)?", button=["Yes", "No"], defaultButton="Yes", cancelButton="No", @@ -189,7 +182,7 @@ def UnbindSkin(): # 使用 MEL 命令解绑蒙皮 mel.eval('doDetachSkin "2" { "1","1" };') - print("已取消蒙皮!/ Skin unbound!") + print("Skin unbound successfully!") if pm: pm.confirmDialog(