Files
Nexus/plug-ins/ARTv2/doc/TECHNICAL_DETAILS.md
2025-12-07 23:00:40 +08:00

534 lines
12 KiB
Markdown

# ARTv2 技术细节文档
**最后更新**: 2024-12-07
---
## 📐 架构概述
### 模块结构
```
ARTv2/
├── Core/
│ ├── Scripts/
│ │ ├── System/ # 核心系统模块
│ │ ├── RigModules/ # 骨骼模块
│ │ ├── Interfaces/ # UI 界面
│ │ └── ThirdParty/ # 第三方库
│ ├── JointMover/ # 骨骼移动器
│ └── Pickers/ # 动画选择器
└── plug-ins/
└── ARTv2.py # Maya 插件入口
```
---
## 🔧 Python 3 迁移技术细节
### 1. 关键语法变更
#### reload() → importlib.reload()
```python
# Python 2
reload(module)
# Python 3
import importlib
importlib.reload(module)
```
**影响文件**: 50+ 个模块
**修复方法**: 添加 `import importlib` 并替换所有 `reload()` 调用
#### print 语句 → print() 函数
```python
# Python 2
print "message"
print "format %s" % value
# Python 3
print("message")
print(f"format {value}") # 使用 f-string
```
**影响文件**: 40+ 个模块
**修复方法**: 统一使用 f-string 格式化
#### xrange() → range()
```python
# Python 2
for i in xrange(100):
pass
# Python 3
for i in range(100):
pass
```
**影响文件**: riggingUtils.py 及相关模块
**修复方法**: 全局替换 `xrange``range`
#### except 语句
```python
# Python 2
except:
pass
# Python 3
except Exception:
pass
```
**影响文件**: 100+ 处
**修复方法**: 明确捕获 Exception 类型
---
## 🏗️ 核心系统架构
### 1. ART_RigModule 基类
**文件**: `System/ART_RigModule.py`
**职责**:
- 所有骨骼模块的基类
- 提供通用的骨骼创建方法
- 管理模块网络节点
- 处理模块间的父子关系
**关键方法**:
```python
class ART_RigModule:
def __init__(self, moduleType, moduleName, userSpecifiedName)
def addAttributes(self)
def skeletonSettings_UI(self, name)
def addJointMoverToOutliner(self)
def updateSettingsUI(self)
def applyModuleChanges(self, moduleInst)
def resetSettings(self)
def pinModule(self, state)
def skinProxyGeo(self)
def buildRig(self) # 已修复异常变量作用域问题
def aimMode_Setup(self, state)
def returnNetworkNode(self) # 已修复 UnboundLocalError
```
**重要修复 1 - UnboundLocalError**:
```python
@property
def returnNetworkNode(self):
networkNode = None # 初始化变量
networkNodes = cmds.ls(type="network")
for node in networkNodes:
attrs = cmds.listAttr(node)
if "moduleName" in attrs:
if cmds.getAttr(node + ".moduleName") == self.name:
networkNode = node
break # 找到后退出
return networkNode
```
**重要修复 2 - 异常变量作用域问题**:
```python
def buildRig(self, textEdit, uiInst):
"""构建模块骨骼"""
currentNodes = cmds.ls("*", long=True)
successfulBuild = True
errorMessage = ""
buildException = None # ✅ 初始化异常变量
try:
self.buildRigCustom(textEdit, uiInst)
except Exception as e:
successfulBuild = False
buildException = e # ✅ 保存异常
errorMessage = str(traceback.format_exc())
# ... 处理新节点 ...
if not successfulBuild:
print(f"Build Rig Failed: {str(buildException)}") # ✅ 使用保存的异常
print(errorMessage)
if buildException:
raise buildException # ✅ 重新抛出供上层捕获
```
**修复影响**:
- ✅ 修复了 Root 模块构建失败
- ✅ 修复了 Torso 模块构建失败
- ✅ 修复了"控制器消失"的问题
- ✅ 确保异常能被 ART_BuildProgressUI 正确捕获和处理
```
---
### 2. 骨骼构建流程
**文件**: `Interfaces/ART_BuildProgressUI.py`
#### 构建阶段
```
1. 预检查 (preflightCheck)
├─ 检查角色存在
├─ 检查模块存在
├─ 检查骨骼状态
└─ 检查场景状态
2. 设置绑定姿态 (setRigPose)
├─ 导出皮肤权重
└─ 删除旧骨骼
3. 重建骨骼 (rebuildSkeleton)
├─ 安全删除根骨骼
└─ 重新构建骨骼层级
4. 构建绑定 (buildRigs)
├─ 创建驱动骨架
├─ 逐个构建模块绑定
└─ 设置绑定层级
5. 导入权重 (importWeights)
├─ 检查网格存在
├─ 导入皮肤权重
└─ 生成导入摘要
6. 后处理 (postScript)
└─ 清理和优化
```
#### 预检查机制
```python
def preflightCheck(self):
"""构建前验证"""
errors = []
warnings = []
# 检查 1: 角色存在
if not cmds.objExists("ART_RIG_ROOT"):
errors.append("No character found")
# 检查 2: 模块存在
modules = utils.returnRigModules()
if not modules:
errors.append("No modules found")
# 检查 3: 骨骼存在
if not cmds.objExists("root"):
errors.append("Skeleton not built")
# 检查 4: 场景状态
state = cmds.getAttr("ART_RIG_ROOT.state")
if state == 0:
errors.append("Character in Skeleton Placement mode")
return errors, warnings
```
#### 错误恢复机制
```python
def buildRigs(self):
"""构建绑定,支持错误恢复"""
failed_modules = []
for inst in self.mainUI.moduleInstances:
try:
inst.buildRig()
self.infoText.append(f"✓ Built: {inst.name}")
except Exception as e:
failed_modules.append((inst.name, str(e)))
self.infoText.setTextColor(QtGui.QColor(255, 100, 100))
self.infoText.append(f"✗ Failed: {inst.name}")
self.infoText.append(f" Error: {e}")
self.errors += 1
# 显示摘要
if failed_modules:
self.infoText.append(f"\nBuild Summary:")
self.infoText.append(f" Success: {len(self.mainUI.moduleInstances) - len(failed_modules)}")
self.infoText.append(f" Failed: {len(failed_modules)}")
```
---
### 3. 权重管理系统
**文件**: `System/riggingUtils.py`
#### 导出权重
```python
def export_skin_weights(filePath, mesh):
"""导出皮肤权重到文件"""
skinCluster = mel.eval(f'findRelatedSkinCluster("{mesh}")')
if not skinCluster:
return False
# 获取权重数据
weights = cmds.getAttr(f"{skinCluster}.weightList[*].weights[*]")
# 保存到文件
with open(filePath, 'w') as f:
json.dump(weights, f)
return True
```
#### 导入权重(带错误处理)
```python
def import_skin_weights(filePath, mesh, worldSpace=True):
"""导入皮肤权重,带完整错误处理"""
# 检查网格存在
if not cmds.objExists(mesh):
raise RuntimeError(f"Mesh not found: {mesh}")
# 检查文件存在
if not os.path.exists(filePath):
raise RuntimeError(f"Weight file not found: {filePath}")
# 读取权重数据
try:
with open(filePath, 'r') as f:
weights = json.load(f)
except Exception as e:
raise RuntimeError(f"Failed to read weight file: {e}")
# 应用权重
try:
skinCluster = mel.eval(f'findRelatedSkinCluster("{mesh}")')
if not skinCluster:
raise RuntimeError("No skin cluster found")
cmds.setAttr(f"{skinCluster}.weightList[*].weights[*]", *weights)
except Exception as e:
raise RuntimeError(f"Failed to apply weights: {e}")
return True
```
---
## 🎨 UI 系统架构
### Qt 兼容层
**文件**: `ThirdParty/Qt/__init__.py`
```python
# 自动检测并导入正确的 Qt 版本
try:
from PySide2 import QtCore, QtGui, QtWidgets
from PySide2.QtCore import Signal, Slot
except ImportError:
from PySide import QtCore, QtGui
QtWidgets = QtGui
from PySide.QtCore import Signal, Slot
```
### 主界面结构
**文件**: `Interfaces/ART_RigCreatorUI.py`
```python
class ART_RigCreatorUI(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(ART_RigCreatorUI, self).__init__(parent)
# 设置窗口属性
self.setWindowTitle("ARTv2 - Rig Creator")
self.setObjectName("ART_RigCreatorUI")
# 构建 UI
self.buildUI()
# 连接信号
self.connectSignals()
def buildUI(self):
"""构建主界面"""
# 创建中央部件
centralWidget = QtWidgets.QWidget()
self.setCentralWidget(centralWidget)
# 创建布局
mainLayout = QtWidgets.QVBoxLayout(centralWidget)
# 添加工具栏
self.createToolbar()
# 添加模块列表
self.createModuleList()
# 添加属性编辑器
self.createAttributeEditor()
```
---
## 🔒 安全机制
### 1. 节点锁定处理
```python
def unlock_node_hierarchy(node):
"""递归解锁节点及其子节点"""
if cmds.objExists(node):
# 解锁节点
if cmds.lockNode(node, q=True, lock=True)[0]:
cmds.lockNode(node, lock=False)
# 递归解锁子节点
children = cmds.listRelatives(node, children=True, fullPath=True) or []
for child in children:
unlock_node_hierarchy(child)
```
### 2. 安全删除
```python
def safe_delete(node):
"""安全删除节点"""
try:
# 解锁节点层级
unlock_node_hierarchy(node)
# 删除节点
if cmds.objExists(node):
cmds.delete(node)
return True
except Exception as e:
cmds.warning(f"Failed to delete {node}: {e}")
return False
```
---
## 📊 性能优化
### 1. 循环优化
```python
# 优化前
for node in networkNodes:
if condition:
networkNode = node
# 继续循环所有节点
# 优化后
for node in networkNodes:
if condition:
networkNode = node
break # 找到后立即退出
```
### 2. 批量操作
```python
# 优化前 - 逐个设置属性
for obj in objects:
cmds.setAttr(f"{obj}.attr", value)
# 优化后 - 批量设置
cmds.setAttr([f"{obj}.attr" for obj in objects], value)
```
---
## 🧪 测试策略
### 单元测试示例
```python
def test_returnNetworkNode():
"""测试 returnNetworkNode 方法"""
# 创建测试模块
module = ART_RigModule("test", "test", "test")
# 测试:节点不存在
result = module.returnNetworkNode
assert result is None, "Should return None when node not found"
# 创建网络节点
node = cmds.createNode("network")
cmds.addAttr(node, ln="moduleName", dt="string")
cmds.setAttr(f"{node}.moduleName", "test", type="string")
# 测试:节点存在
result = module.returnNetworkNode
assert result == node, "Should return the network node"
# 清理
cmds.delete(node)
```
---
## 🔍 调试技巧
### 1. 启用详细日志
```python
import logging
# 配置日志
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger('ARTv2')
logger.debug("Debug message")
```
### 2. Maya 脚本编辑器输出
```python
def debug_print(msg):
"""在 Maya 脚本编辑器中打印调试信息"""
cmds.warning(f"[ARTv2 DEBUG] {msg}")
```
---
## 📚 API 参考
### 核心类
#### ART_RigModule
- `addAttributes()` - 添加模块属性
- `buildRig()` - 构建模块绑定
- `returnNetworkNode` - 获取网络节点
#### ART_BuildProgressUI
- `preflightCheck()` - 预检查
- `buildRigs()` - 构建绑定
- `importWeights()` - 导入权重
### 工具函数
#### utils.py
- `returnRigModules()` - 获取所有模块
- `returnFriendlyPath()` - 转换路径格式
#### riggingUtils.py
- `export_skin_weights()` - 导出权重
- `import_skin_weights()` - 导入权重
---
## 🔗 依赖关系
```
ARTv2.py (插件入口)
├─ System/utils.py
├─ System/interfaceUtils.py
├─ Interfaces/ART_RigCreatorUI.py
│ ├─ System/ART_RigModule.py
│ ├─ RigModules/ART_*.py
│ └─ Interfaces/ART_BuildProgressUI.py
└─ ThirdParty/Qt/
```
---
**维护者**: Cascade AI
**技术支持**: GitHub Issues