Update
This commit is contained in:
@@ -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,可以在导入前设置环境变量
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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__ = [
|
||||
|
||||
@@ -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!")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user