12 KiB
12 KiB
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 2
reload(module)
# Python 3
import importlib
importlib.reload(module)
影响文件: 50+ 个模块
修复方法: 添加 import importlib 并替换所有 reload() 调用
print 语句 → print() 函数
# Python 2
print "message"
print "format %s" % value
# Python 3
print("message")
print(f"format {value}") # 使用 f-string
影响文件: 40+ 个模块
修复方法: 统一使用 f-string 格式化
xrange() → range()
# Python 2
for i in xrange(100):
pass
# Python 3
for i in range(100):
pass
影响文件: riggingUtils.py 及相关模块
修复方法: 全局替换 xrange 为 range
except 语句
# Python 2
except:
pass
# Python 3
except Exception:
pass
影响文件: 100+ 处
修复方法: 明确捕获 Exception 类型
🏗️ 核心系统架构
1. ART_RigModule 基类
文件: System/ART_RigModule.py
职责:
- 所有骨骼模块的基类
- 提供通用的骨骼创建方法
- 管理模块网络节点
- 处理模块间的父子关系
关键方法:
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:
@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 - 异常变量作用域问题:
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`
#### 构建阶段
-
预检查 (preflightCheck) ├─ 检查角色存在 ├─ 检查模块存在 ├─ 检查骨骼状态 └─ 检查场景状态
-
设置绑定姿态 (setRigPose) ├─ 导出皮肤权重 └─ 删除旧骨骼
-
重建骨骼 (rebuildSkeleton) ├─ 安全删除根骨骼 └─ 重新构建骨骼层级
-
构建绑定 (buildRigs) ├─ 创建驱动骨架 ├─ 逐个构建模块绑定 └─ 设置绑定层级
-
导入权重 (importWeights) ├─ 检查网格存在 ├─ 导入皮肤权重 └─ 生成导入摘要
-
后处理 (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
错误恢复机制
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
导出权重
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
导入权重(带错误处理)
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
# 自动检测并导入正确的 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
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. 节点锁定处理
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. 安全删除
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. 循环优化
# 优化前
for node in networkNodes:
if condition:
networkNode = node
# 继续循环所有节点
# 优化后
for node in networkNodes:
if condition:
networkNode = node
break # 找到后立即退出
2. 批量操作
# 优化前 - 逐个设置属性
for obj in objects:
cmds.setAttr(f"{obj}.attr", value)
# 优化后 - 批量设置
cmds.setAttr([f"{obj}.attr" for obj in objects], value)
🧪 测试策略
单元测试示例
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. 启用详细日志
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 脚本编辑器输出
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