This commit is contained in:
2025-11-30 14:49:16 +08:00
parent 021c593241
commit de46c4b073
1406 changed files with 526774 additions and 1221 deletions

View File

@@ -1,147 +0,0 @@
# Skin API Module
强大的 Maya 蒙皮权重管理工具,支持权重的导出、导入和蒙皮解绑功能。
## 📁 文件结构
```
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 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()
# 导出权重
api.exportSkinWeights(selected=True, saveJointInfo=True)
# 导入权重
api.importSkinWeights(selected=False, stripJointNamespaces=False, addNewToHierarchy=True)
```
## ✨ 主要功能
- **权重导出** - 导出选中或所有蒙皮物体的权重数据
- **权重导入** - 导入权重到选中或匹配的物体
- **蒙皮解绑** - 快速解绑选中物体的蒙皮
- **关节信息保存** - 可选保存关节方向、世界变换和父级信息
- **智能匹配** - 基于名称和顶点数量自动匹配物体
- **命名空间处理** - 支持剥离关节命名空间
## 🔧 版本兼容性
### 支持的 Maya 版本
- **所有 Maya 版本** - 从 Maya 2016 到 Maya 2025+
- **Maya 2025** - 完全兼容,修复了 PyMEL 相关问题
### API 兼容性
模块采用双重 API 支持策略:
- **PyMEL** - 优先使用 PyMEL如果可用
- **Maya Commands** - 自动降级到 `maya.cmds`(如果 PyMEL 不可用)
- **Maya API** - 使用 `maya.OpenMaya``maya.OpenMayaAnim` 进行高性能操作
### 兼容性特性
1. **自动 API 检测** - 运行时检测可用的 API
2. **优雅降级** - PyMEL 不可用时自动使用 cmds
3. **相对导入** - 支持作为包导入或独立模块使用
4. **异常处理** - 完善的错误处理和用户提示
5. **空值安全** - 处理节点无父节点等边界情况
6. **Maya 2025 优化** - 修复 PyMEL 在新版本中的兼容性问题
## 📝 文件格式
权重文件使用 `.skinWeights` 格式(基于 Python pickle
- 包含顶点权重数据
- 包含影响对象(关节)信息
- 可选包含关节变换信息
- 支持多物体批量导出
## 💡 使用技巧
### 导出权重
1. 选择需要导出的蒙皮物体
2. 运行 `WeightExport()`
3. 选择保存位置和文件名
4. 建议启用 `saveJointInfo` 以保存完整的关节信息
### 导入权重
1. **选中物体导入** - 选择目标物体后导入(仅导入到选中物体)
2. **自动匹配导入** - 不选择物体导入(自动匹配场景中的所有物体)
3. 确保物体名称和顶点数量匹配
4. 如果关节缺失,可启用 `addNewToHierarchy` 自动创建
### 解绑蒙皮
1. 选择需要解绑的物体
2. 运行 `UnbindSkin()`
3. 确认对话框后执行解绑
4. 支持批量解绑多个物体
## ⚠️ 注意事项
- 导入权重时,目标物体的顶点数量必须与导出时一致
- 物体名称需要匹配(支持短名称匹配)
- 建议在导入前备份场景
- 大量物体操作时会显示进度条
- 权重文件使用 pickle 格式,不同 Python 版本间可能存在兼容性问题
### Maya 2025 特别说明
- 已修复 PyMEL 在处理无父节点骨骼时的 `'NoneType' object has no attribute 'name'` 错误
- 增强了所有 PyMEL 对象的空值检查
- 建议使用 `saveJointInfo=True` 导出完整的骨骼信息
## 🐛 故障排除
### 导入失败
- 检查物体名称是否匹配
- 检查顶点数量是否一致
- 确认权重文件路径正确
- 查看 Maya 脚本编辑器的详细错误信息
### 关节缺失
- 启用 `addNewToHierarchy` 参数
- 确保导出时使用了 `saveJointInfo=True`
- 手动创建缺失的关节
### PyMEL 相关问题
- 模块会自动降级到 cmds无需担心
- 如果需要强制使用 cmds可以在导入前设置环境变量

View File

@@ -1,3 +1,9 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Site : Virtuos Games
# @Author : ZHou Shuhua
try:
import pymel.core as pm
except ImportError:
@@ -112,8 +118,8 @@ def getSkinClusterInfo(objectName, saveJointInfo=False):
def getSkinJointInformation(influences):
"""
获取骨骼信息(父节点、矩阵、旋转、关节方向)
兼容 PyMEL cmds,处理无父节点的情况
Retrieve skeletal information (parent node, matrix, rotation, joint orientation)
Compatible with PyMEL and cmds, handling cases without parent nodes.
"""
jointInformation = {}
@@ -122,7 +128,7 @@ def getSkinJointInformation(influences):
try:
if pm:
infNode = pm.PyNode(inf)
# 安全获取父节点,避免 None.name() 错误
# Safely retrieve the parent node and avoid the None.name() error.
parent = infNode.getParent()
jointInfo["parent"] = str(parent.name()) if parent else ""
jointInfo["matrix"] = infNode.getMatrix(worldSpace=True)
@@ -130,7 +136,7 @@ def getSkinJointInformation(influences):
jointInfo["jointOrient"] = infNode.getAttr("jointOrient")
jointInformation[str(infNode)] = copy.deepcopy(jointInfo)
else:
# cmds 版本
# cmds verison
infName = str(inf)
parents = cmds.listRelatives(infName, parent=True)
jointInfo["parent"] = parents[0] if parents else ""
@@ -140,7 +146,7 @@ def getSkinJointInformation(influences):
jointInformation[infName] = copy.deepcopy(jointInfo)
except Exception as e:
print(f"Warning: Failed to get joint information for {inf}: {e}")
# 使用默认值
# Use default values
jointInfo["parent"] = ""
jointInfo["matrix"] = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]
jointInfo["rotation"] = [0, 0, 0]
@@ -368,11 +374,11 @@ def buildSkinWeightsDict(objectList, showLoadingBar=True, saveJointInfo=False):
for object in objectList:
try:
if pm:
# 安全转换为字符串,处理可能的 None 或无效对象
# Safely convert to string, handling possible None or invalid objects.
obj_node = pm.PyNode(object) if not isinstance(object, pm.PyNode) else object
objectAsString = str(obj_node.name()) if obj_node else str(object)
else:
# cmds 版本 - object 已经是字符串
# cmds version - object is already a string
objectAsString = str(object)
except Exception as e:
print(f"Warning: Failed to process object {object}: {e}")
@@ -416,7 +422,7 @@ def transferSkinWeights(transferNodes=None, showLoadingBar=True):
if len(transferNodes):
sourceObj = transferNodes[0]
# 安全获取名称
# Get name
try:
sourceName = str(sourceObj.name()) if hasattr(sourceObj, 'name') else str(sourceObj)
except:
@@ -436,7 +442,7 @@ def transferSkinWeights(transferNodes=None, showLoadingBar=True):
# deep copy because: Mutable datatypes
sourceWeightDictCopy = copy.deepcopy(sourceWeightDict)
# 安全获取名称
# Get name
try:
targetName = str(tgtObject.name()) if hasattr(tgtObject, 'name') else str(tgtObject)
except:
@@ -526,7 +532,7 @@ def skinClusterBuilder(objName, weightDict, deleteHist=True, stripJointNamespace
joint.setMatrix(jointInfo.get("matrix"), worldSpace=True)
pm.select(cl=True)
else:
# cmds 版本
# cmds version
cmds.select(clear=True)
joint = cmds.joint(position=(0, 0, 0), name=jointName)
# putting joint in the hierarchy and setting matrix
@@ -557,7 +563,7 @@ def skinClusterBuilder(objName, weightDict, deleteHist=True, stripJointNamespace
pm.setAttr('%s.normalizeWeights' % clusterNode, 1)
clusterNodeName = str(clusterNode)
else:
# cmds 版本
# cmds verison
clusterNode = cmds.skinCluster(clusterJoints, objName, tsb=True, mi=clusterMaxInf, omi=True)[0]
# turn of normalization to nuke weights to 0, this is to get a true 1->1 application of old weights
cmds.setAttr('%s.normalizeWeights' % clusterNode, 0)

View File

@@ -1,3 +1,9 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Site : Virtuos Games
# @Author : ZHou Shuhua Cai Jianbo
try:
import pymel.core as pm
except ImportError:

View File

@@ -1,16 +1,19 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Site : Virtuos Games
# @Author : ZHou Shuhua
"""
Skin API Module
提供蒙皮权重导出、导入和管理功能
支持 Maya 所有版本,兼容 pymel cmds
Provides skin weight export, import, and management functions
Supports all Maya versions with compatibility for both pymel and cmds
"""
# 导出主要的 UI 函数
# Export main UI functions
from .ui import WeightExport, WeightImport, UnbindSkin
# 导出核心类
# Export core classes
from .apiVtxAttribs import ApiVtxAttribs
__all__ = [

View File

@@ -1,13 +1,16 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Site : Virtuos Games
# @Author : ZHou Shuhua
"""
Skin API UI Functions
提供权重导出、导入和解绑的用户界面函数
支持 Maya 所有版本,兼容 pymel cmds
Provides UI functions for weight export, import, and skin unbind
Supports all Maya versions with pymel and cmds compatibility
"""
# 尝试导入 pymel如果不可用则使用 cmds
# Try to import pymel, fall back to cmds if unavailable
try:
import pymel.core as pm
except ImportError:
@@ -27,7 +30,7 @@ def WeightExport():
Export skin weights for selected objects
"""
try:
# 检查是否有选中物体
# Check if there are selected objects
if pm:
selectedNodes = pm.ls(sl=True)
if not selectedNodes:
@@ -73,7 +76,7 @@ def WeightImport():
Import skin weights to selected or matching objects in scene
"""
try:
# 检查是否有选中物体
# Check if there are selected objects
if pm:
selectedNodes = pm.ls(sl=True)
else:
@@ -82,7 +85,7 @@ def WeightImport():
print(f"Import mode: {'selected objects' if useSelection else 'all matching objects'}")
# 使用 ApiVtxAttribs 类导入权重
# Use ApiVtxAttribs class to import weights
api = apiVtxAttribs.ApiVtxAttribs()
msg = api.importSkinWeights(selected=useSelection, stripJointNamespaces=False, addNewToHierarchy=True)
@@ -112,7 +115,7 @@ def WeightImport():
else:
cmds.warning(msg)
else:
# 成功导入
# Import successful
if pm:
pm.confirmDialog(
title="Import Complete",
@@ -140,11 +143,10 @@ def WeightImport():
def UnbindSkin():
"""
解绑选中物体的蒙皮
Unbind skin from selected objects
"""
try:
# 获取选中的物体
# Check selected objects
if pm:
selectedNodes = pm.ls(sl=True)
if not selectedNodes:
@@ -156,7 +158,7 @@ def UnbindSkin():
cmds.warning("Please select at least one object")
return
# 确认对话框
# Confirmation dialog
if pm:
result = pm.confirmDialog(
title="Unbind Skin",
@@ -180,7 +182,7 @@ def UnbindSkin():
print("Unbind cancelled")
return
# 使用 MEL 命令解绑蒙皮
# Use MEL command to unbind skin
mel.eval('doDetachSkin "2" { "1","1" };')
print("Skin unbound successfully!")