1224 lines
37 KiB
Python
1224 lines
37 KiB
Python
#!/usr/bin/env python
|
||
# -*- coding: utf-8 -*-
|
||
|
||
import maya.cmds as cmds
|
||
import maya.mel as mel
|
||
import os
|
||
import json
|
||
from scripts.utils.config import DNA_CONFIG
|
||
|
||
|
||
|
||
def GetMeshes(m=None, i=None, lod=None, cm=None):
|
||
"""
|
||
获取网格信息 (通用化函数,支持DNA网格)
|
||
|
||
参数:
|
||
m (int, 可选): 通过索引获取网格名称
|
||
i (int, 可选): 通过索引获取网格基础名称
|
||
lod (int, 可选): 获取指定LOD级别的网格索引列表
|
||
cm (int, 可选): 创建指定索引的网格
|
||
|
||
返回:
|
||
str/list: 根据参数返回相应的网格信息
|
||
"""
|
||
# 检查是否有DNA数据
|
||
dna_path = DNA_CONFIG['DNA_PATH']
|
||
if dna_path and os.path.exists(dna_path):
|
||
reader = LoadDNA(dna_path)
|
||
if reader:
|
||
mesh_info = GetDNAMeshInfo(reader)
|
||
if m is not None:
|
||
# 通过索引获取网格名称
|
||
for name, info in mesh_info.items():
|
||
if info['index'] == m:
|
||
return name
|
||
elif i is not None:
|
||
# 获取网格基础名称
|
||
return f"mesh_{i}"
|
||
elif lod is not None:
|
||
# 获取指定LOD级别的网格索引列表
|
||
return [info['index'] for name, info in mesh_info.items()
|
||
if info['lod'] == lod]
|
||
|
||
# 如果没有DNA数据,使用默认逻辑
|
||
if m is not None:
|
||
# 通过索引获取网格名称
|
||
meshes = cmds.ls(type="mesh")
|
||
if 0 <= m < len(meshes):
|
||
return meshes[m]
|
||
elif i is not None:
|
||
# 获取网格基础名称
|
||
meshes = cmds.ls(type="mesh")
|
||
if 0 <= i < len(meshes):
|
||
return meshes[i].split("|")[-1].split(":")[-1]
|
||
elif lod is not None:
|
||
# 获取指定LOD级别的网格索引列表
|
||
if 0 <= lod < 8: # 支持LOD0-7
|
||
if lod < 7: # 头部LOD0-6
|
||
start_index = lod * 9 # 每个LOD有9个网格
|
||
return list(range(start_index, start_index + 9))
|
||
else: # 身体LOD
|
||
return list(range(50, 54)) # 身体LOD索引从50开始
|
||
|
||
# 获取所有网格
|
||
return cmds.ls(type="mesh")
|
||
|
||
def GetBlendShape(mesh):
|
||
"""
|
||
获取网格的混合变形节点
|
||
|
||
参数:
|
||
mesh (str): 网格名称
|
||
|
||
返回:
|
||
str: 混合变形节点名称
|
||
"""
|
||
if not mesh or not cmds.objExists(mesh):
|
||
return ""
|
||
|
||
# 获取历史记录
|
||
history = cmds.listHistory(mesh, pdo=True) or []
|
||
|
||
# 查找混合变形节点
|
||
for node in history:
|
||
if cmds.nodeType(node) == "blendShape":
|
||
return node
|
||
|
||
return ""
|
||
|
||
def SetBlendShapes(r=None, value=None):
|
||
"""
|
||
设置混合变形目标
|
||
|
||
参数:
|
||
r (float, 可选): 范围值
|
||
value (str, 可选): 目标名称
|
||
"""
|
||
if r is not None and value:
|
||
# 设置范围值和目标
|
||
blend_shape = GetBlendShape(value)
|
||
if blend_shape:
|
||
cmds.setAttr(f"{blend_shape}.envelope", r)
|
||
|
||
def ProgressBar(sp=False, max_value=None, t=None, apr=None, ep=False):
|
||
"""
|
||
控制进度条显示
|
||
|
||
参数:
|
||
sp (bool, 可选): 开始进度条
|
||
max_value (int, 可选): 设置最大值
|
||
t (str, 可选): 设置文本
|
||
apr (int, 可选): 前进进度
|
||
ep (bool, 可选): 结束进度条
|
||
"""
|
||
if sp:
|
||
# 开始进度条
|
||
cmds.progressWindow(
|
||
title='Progress',
|
||
progress=0,
|
||
status='Starting...',
|
||
isInterruptable=True
|
||
)
|
||
elif max_value is not None:
|
||
# 设置最大值
|
||
cmds.progressWindow(edit=True, maxValue=max_value)
|
||
elif t is not None:
|
||
# 设置文本
|
||
cmds.progressWindow(edit=True, status=t)
|
||
elif apr is not None:
|
||
# 前进进度
|
||
cmds.progressWindow(edit=True, step=apr)
|
||
elif ep:
|
||
# 结束进度条
|
||
cmds.progressWindow(endProgress=True)
|
||
|
||
def GetBlendShapeTargets(tc=None, bsn=None, index=None):
|
||
"""
|
||
获取混合变形目标相关信息 (重命名为 GetBlendShapeTargets)
|
||
|
||
可以获取目标数量或混合变形目标名称。
|
||
|
||
参数:
|
||
tc (int, 可选): 目标数量,用于获取混合变形目标的总数.
|
||
bsn (int, 可选): 目标数量,用于配合 index 参数获取特定索引的混合变形目标名称.
|
||
index (int, 可选): 目标索引,需要与 bsn 参数一起使用,获取特定索引的混合变形目标名称.
|
||
|
||
返回:
|
||
int 或 str: 根据提供的参数返回不同的混合变形信息。
|
||
如果 tc 参数被指定,返回混合变形目标的总数。
|
||
如果 bsn 和 index 参数被指定,返回对应索引的混合变形目标名称。
|
||
如果没有任何参数,则返回 None。
|
||
"""
|
||
if tc is not None:
|
||
# 这里是 GetBlendShapes(tc=target_count) 的逻辑,假设返回目标总数
|
||
# 实际实现需要根据您的 Maya 场景和命名约定来确定
|
||
print(f"GetBlendShapes with tc: {tc}") # 占位符,需要根据实际情况实现
|
||
return tc + 5 # 示例实现,假设目标总数是 tc + 5
|
||
elif bsn is not None and index is not None:
|
||
# 这里是 GetBlendShapes(bsn=target_count, index=index) 的逻辑,假设返回特定索引的 blend shape name
|
||
# 实际实现需要根据您的 Maya 场景和命名约定来确定
|
||
print(f"GetBlendShapes with bsn: {bsn}, index: {index}") # 占位符,需要根据实际情况实现
|
||
return f"blendShape_target_{index}" # 示例实现
|
||
else:
|
||
return None
|
||
|
||
def SetBlendShapes(ct=None, index=None, target=None):
|
||
"""
|
||
设置混合变形目标
|
||
|
||
为指定的混合变形节点设置目标网格体。
|
||
|
||
参数:
|
||
ct (int, 可选): 网格体索引,用于指定目标网格体.
|
||
index (int, 可选): 目标索引,指定要设置的目标索引.
|
||
target (str, 可选): 目标网格体名称.
|
||
"""
|
||
if ct is not None and index is not None and target is not None:
|
||
# 这里是 SetBlendShapes(ct=mesh_index, index=index, target=bs_name) 的逻辑
|
||
# 实际实现需要根据您的 Maya 场景和命名约定来确定
|
||
print(f"SetBlendShapes ct: {ct}, index: {index}, target: {target}") # 占位符,需要根据实际情况实现
|
||
print(f"Setting blend shape target {target} for index {index} of mesh index {ct}") # 示例实现
|
||
else:
|
||
print("SetBlendShapes: Missing parameters")
|
||
|
||
def get_orig_name(geo):
|
||
"""
|
||
获取模型的Orig节点名称列表 (通用化函数,移除 "sg_" 前缀)
|
||
|
||
参数:
|
||
geo (str): 模型名称
|
||
返回:
|
||
list: Orig节点名称列表
|
||
"""
|
||
orig = []
|
||
if not geo or not cmds.objExists(geo):
|
||
return orig
|
||
|
||
shape_mesh = cmds.listRelatives(geo, shapes=True) or []
|
||
for shape in shape_mesh:
|
||
if "Orig" in shape:
|
||
orig.append(shape)
|
||
return orig
|
||
|
||
def get_orig_shape_name(geo):
|
||
"""
|
||
获取模型的带有groupParts连接的Orig节点名称列表 (通用化函数,移除 "sg_" 前缀)
|
||
|
||
参数:
|
||
geo (str): 模型名称
|
||
返回:
|
||
list: 符合条件的Orig节点名称列表
|
||
"""
|
||
orig = []
|
||
if not geo or not cmds.objExists(geo):
|
||
return orig
|
||
|
||
shape_mesh = cmds.listRelatives(geo, shapes=True) or []
|
||
for shape in shape_mesh:
|
||
if "Orig" in shape:
|
||
connections = cmds.listConnections(shape) or []
|
||
for conn in connections:
|
||
if "groupParts" in conn:
|
||
orig.append(shape)
|
||
break
|
||
return orig
|
||
|
||
def GetBlendShapeNodes(geo):
|
||
"""
|
||
获取指定模型的混合变形节点列表 (通用化函数,替换 sparkey_get_blend_shape)
|
||
|
||
参数:
|
||
geo (str): 模型名称
|
||
返回:
|
||
list: 混合变形节点列表
|
||
"""
|
||
if not geo or not cmds.objExists(geo):
|
||
return []
|
||
|
||
# 获取历史记录
|
||
history = cmds.listHistory(geo, pruneDagObjects=True, interestLevel=2) or []
|
||
|
||
# 过滤出blendShape节点
|
||
return [node for node in history if cmds.nodeType(node) == "blendShape"]
|
||
|
||
def GetWrapNodes(geo):
|
||
"""
|
||
获取指定模型的wrap变形节点列表 (通用化函数,替换 sparkey_get_wrap)
|
||
|
||
参数:
|
||
geo (str): 模型名称
|
||
返回:
|
||
list: wrap变形节点列表
|
||
"""
|
||
if not geo or not cmds.objExists(geo):
|
||
return []
|
||
|
||
# 获取历史记录
|
||
history = cmds.listHistory(geo, pruneDagObjects=True, interestLevel=2) or []
|
||
|
||
# 过滤出wrap节点
|
||
return [node for node in history if cmds.nodeType(node) == "wrap"]
|
||
|
||
def FilterSingleBlendShapeDeformerTargets(items):
|
||
"""
|
||
从单个混合变形变形器中返回选定的目标(组) (通用化函数,替换 filter_single_bsd)
|
||
|
||
参数:
|
||
items (list): 要过滤的项目列表
|
||
返回:
|
||
list: 过滤后的项目列表,如果来自多个BSD则返回空列表
|
||
"""
|
||
result = []
|
||
one_bsd = ""
|
||
|
||
for item in items:
|
||
sub_strings = item.split(".")
|
||
if not one_bsd:
|
||
one_bsd = sub_strings[0]
|
||
if one_bsd != sub_strings[0]:
|
||
return []
|
||
result.append(item)
|
||
|
||
return result
|
||
|
||
def SetColor(obj, r, g, b, ro, ao, ov):
|
||
"""
|
||
设置物体的颜色覆盖
|
||
|
||
参数:
|
||
obj (str): 物体名称
|
||
r (float): 红色分量 (0-1)
|
||
g (float): 绿色分量 (0-1)
|
||
b (float): 蓝色分量 (0-1)
|
||
ro (bool): 启用颜色覆盖
|
||
ao (bool): 启用alpha覆盖
|
||
ov (bool): 启用覆盖显示
|
||
"""
|
||
cmds.setAttr(f"{obj}.overrideEnabled", 1)
|
||
cmds.setAttr(f"{obj}.overrideRGBColors", 1)
|
||
cmds.setAttr(f"{obj}.overrideColorRGB", r, g, b)
|
||
cmds.setAttr(f"{obj}.overrideAlpha", 1)
|
||
|
||
def SkinCluster(mesh=None, import_file=None, export_file=None):
|
||
"""
|
||
处理蒙皮权重的导入导出
|
||
|
||
参数:
|
||
mesh (str): 要处理的网格名称
|
||
import_file (str, 可选): 导入文件路径
|
||
export_file (str, 可选): 导出文件路径
|
||
|
||
返回:
|
||
bool: 操作是否成功
|
||
"""
|
||
try:
|
||
if not mesh:
|
||
mesh = cmds.ls(sl=1)[0]
|
||
if not cmds.objExists(mesh):
|
||
cmds.error("Object does not exist.")
|
||
return False
|
||
|
||
if export_file:
|
||
# 查找蒙皮变形器
|
||
skin_cluster = FindSkinCluster(mesh)
|
||
if not skin_cluster:
|
||
cmds.warning(f'Object "{mesh}" has no skinCluster.')
|
||
return False
|
||
|
||
# 获取顶点数量和影响对象
|
||
vtx_count = cmds.polyEvaluate(mesh, vertex=True)
|
||
influence_objects = cmds.skinCluster(skin_cluster, query=True, influence=True)
|
||
|
||
# 收集权重数据
|
||
weights = []
|
||
for i in range(vtx_count):
|
||
vtx_weights = cmds.skinPercent(
|
||
skin_cluster,
|
||
f'{mesh}.vtx[{i}]',
|
||
query=True,
|
||
value=True,
|
||
transform=influence_objects
|
||
)
|
||
weights.append(dict(zip(influence_objects, vtx_weights)))
|
||
|
||
# 保存数据
|
||
data = {
|
||
'object': mesh,
|
||
'influences': influence_objects,
|
||
'weights': weights
|
||
}
|
||
|
||
with open(export_file, 'w') as f:
|
||
json.dump(data, f, indent=4)
|
||
return True
|
||
|
||
elif import_file:
|
||
# 读取权重数据
|
||
with open(import_file, 'r') as f:
|
||
data = json.load(f)
|
||
|
||
# 检查影响对象是否存在
|
||
for influence in data['influences']:
|
||
if not cmds.objExists(influence):
|
||
cmds.warning(f'Influence object "{influence}" not found.')
|
||
return False
|
||
|
||
# 创建蒙皮变形器
|
||
skin_cluster = cmds.skinCluster(
|
||
data['influences'],
|
||
mesh,
|
||
toSelectedBones=True,
|
||
bindMethod=0,
|
||
normalizeWeights=1,
|
||
weightDistribution=1
|
||
)[0]
|
||
|
||
# 应用权重
|
||
for i, weights in enumerate(data['weights']):
|
||
for influence, weight in weights.items():
|
||
if weight > 0:
|
||
cmds.skinPercent(
|
||
skin_cluster,
|
||
f'{mesh}.vtx[{i}]',
|
||
transformValue=[(influence, weight)]
|
||
)
|
||
return True
|
||
|
||
except Exception as e:
|
||
cmds.warning(f"Error in SkinCluster: {str(e)}")
|
||
return False
|
||
|
||
def ReadJson(f=None, d=None, k=None, t=None):
|
||
"""
|
||
读取JSON数据 (通用化函数,替换 ReadJson)
|
||
|
||
参数:
|
||
f (str, 可选): JSON文件路径
|
||
d (dict, 可选): JSON数据字典
|
||
k (str, 可选): 要读取的键
|
||
t (str, 可选): 返回值类型 ("object", "string", "double" 等)
|
||
|
||
返回:
|
||
根据类型返回相应的数据
|
||
"""
|
||
import json
|
||
|
||
try:
|
||
if f:
|
||
with open(f, 'r') as json_file:
|
||
data = json.load(json_file)
|
||
elif d:
|
||
data = d
|
||
|
||
if k:
|
||
if isinstance(data, dict):
|
||
value = data.get(k)
|
||
if t == "object":
|
||
return value
|
||
elif t == "string":
|
||
return [str(v) for v in value] if isinstance(value, list) else [str(value)]
|
||
elif t == "double":
|
||
return [float(v) for v in value] if isinstance(value, list) else [float(value)]
|
||
return None
|
||
return data
|
||
except Exception as e:
|
||
print(f"Error reading JSON: {str(e)}")
|
||
return None
|
||
|
||
def SkinAmendAxis():
|
||
"""
|
||
修正蒙皮轴向
|
||
|
||
修正蒙皮变形器的轴向,使其与骨骼轴向一致
|
||
|
||
返回:
|
||
bool: 操作是否成功
|
||
"""
|
||
try:
|
||
# 获取所有蒙皮变形器
|
||
skin_clusters = cmds.ls(type='skinCluster')
|
||
if not skin_clusters:
|
||
return False
|
||
|
||
# 修正每个蒙皮变形器
|
||
for skin_cluster in skin_clusters:
|
||
# 获取影响对象
|
||
influences = cmds.skinCluster(skin_cluster, q=True, inf=True)
|
||
|
||
# 更新蒙皮变形器的轴向
|
||
for influence in influences:
|
||
# 获取关节的当前轴向
|
||
joint_matrix = cmds.xform(influence, q=True, m=True, ws=True)
|
||
|
||
# 更新蒙皮变形器中的轴向
|
||
cmds.skinCluster(
|
||
skin_cluster,
|
||
e=True,
|
||
ug=False,
|
||
dr=4,
|
||
mi=3,
|
||
bindMethod=0
|
||
)
|
||
|
||
return True
|
||
|
||
except Exception as e:
|
||
cmds.warning(f"Error in SkinAmendAxis: {str(e)}")
|
||
return False
|
||
|
||
def BindPoseReset(meshes):
|
||
"""
|
||
重置绑定姿势 (通用化函数,替换 BindPoseReset)
|
||
|
||
参数:
|
||
meshes (list): 需要重置绑定姿势的网格列表
|
||
"""
|
||
for mesh in meshes:
|
||
if cmds.objExists(mesh):
|
||
# 获取蒙皮变形器
|
||
skin_cluster = mel.eval(f'findRelatedSkinCluster("{mesh}")')
|
||
if skin_cluster:
|
||
# 重置绑定姿势
|
||
cmds.dagPose(mesh, reset=True, bindPose=True)
|
||
|
||
def WriteJson(of=None, sf=None, d=None, k=None, t=None, value=None):
|
||
"""
|
||
写入JSON数据 (通用化函数,替换 WriteJson)
|
||
|
||
参数:
|
||
of (str, 可选): 输出文件路径
|
||
sf (str, 可选): 源文件路径
|
||
d (dict, 可选): JSON数据字典
|
||
k (str, 可选): 要写入的键
|
||
t (str, 可选): 值的类型 ("object", "string", "double", "int", "bool" 等)
|
||
value: 要写入的值
|
||
|
||
返回:
|
||
dict: 更新后的数据
|
||
"""
|
||
try:
|
||
# 读取源数据
|
||
if sf:
|
||
with open(sf, 'r') as f:
|
||
data = json.load(f)
|
||
elif d:
|
||
data = d
|
||
else:
|
||
data = {}
|
||
|
||
# 更新数据
|
||
if k:
|
||
data[k] = value
|
||
|
||
# 写入文件
|
||
if of:
|
||
with open(of, 'w') as f:
|
||
json.dump(data, f, indent=4)
|
||
|
||
return data
|
||
except Exception as e:
|
||
print(f"Error writing JSON: {str(e)}")
|
||
return None
|
||
|
||
def Descriptor(ti=None, l=None, p=None, n=None, ed=None):
|
||
"""
|
||
获取项目描述信息
|
||
|
||
参数:
|
||
ti (str, 可选): 类型标识符,用于获取特定类型的信息文件路径
|
||
l (bool, 可选): 获取语言设置
|
||
p (bool, 可选): 获取项目路径
|
||
n (bool, 可选): 获取项目名称
|
||
ed (bool, 可选): 获取编辑器版本
|
||
|
||
返回:
|
||
str/int: 根据参数返回相应的描述信息
|
||
"""
|
||
if ti:
|
||
# 获取特定类型的信息文件路径
|
||
base_path = os.getenv('SG_PATH')
|
||
if ti == "vertexsInfo":
|
||
return os.path.join(base_path, "files/data/vertex_info.json")
|
||
elif ti == "jointsInfo":
|
||
return os.path.join(base_path, "files/data/joint_info.json")
|
||
elif l:
|
||
# 获取语言设置
|
||
return os.getenv('SG_LANGUAGE', 'EN')
|
||
elif p:
|
||
# 获取项目路径
|
||
return os.getenv('SG_PATH', '')
|
||
elif n:
|
||
# 获取项目名称
|
||
return os.path.basename(os.getenv('SG_PATH', ''))
|
||
elif ed:
|
||
# 获取编辑器版本
|
||
return int(os.getenv('SG_EDITOR_VERSION', '1'))
|
||
return None
|
||
|
||
def SetMeshes(m=None, value=None, tvo=None):
|
||
"""
|
||
设置网格相关属性
|
||
|
||
参数:
|
||
m (int, 可选): 网格索引,用于设置网格
|
||
value (str, 可选): 要设置的值
|
||
tvo (str, 可选): 源网格,用于传递UV顶点顺序
|
||
|
||
返回:
|
||
bool: 操作是否成功
|
||
"""
|
||
try:
|
||
if m is not None and value is not None:
|
||
# 设置网格
|
||
# 实现设置网格的逻辑
|
||
pass
|
||
|
||
elif tvo and value:
|
||
# 传递UV顶点顺序
|
||
source_shape = cmds.listRelatives(tvo, shapes=True)[0]
|
||
target_shape = cmds.listRelatives(value, shapes=True)[0]
|
||
|
||
# 创建临时的transferAttributes节点
|
||
transfer_node = cmds.createNode('transferAttributes')
|
||
|
||
# 连接源和目标
|
||
cmds.connectAttr(f"{source_shape}.worldMesh[0]", f"{transfer_node}.sourceGeometry")
|
||
cmds.connectAttr(f"{target_shape}.worldMesh[0]", f"{transfer_node}.targetGeometry")
|
||
|
||
# 设置传递属性
|
||
cmds.setAttr(f"{transfer_node}.uvSets", 1)
|
||
cmds.setAttr(f"{transfer_node}.vertexOrder", 1)
|
||
|
||
# 执行传递
|
||
cmds.transferAttributes(tvo, value,
|
||
transferUVs=2,
|
||
transferVertexOrder=1,
|
||
sampleSpace=0)
|
||
|
||
# 删除临时节点
|
||
cmds.delete(transfer_node)
|
||
|
||
return True
|
||
|
||
except Exception as e:
|
||
cmds.warning(f"Error in SetMeshes: {str(e)}")
|
||
return False
|
||
|
||
def LoadDNA(dna_file):
|
||
"""
|
||
加载DNA文件
|
||
|
||
参数:
|
||
dna_file (str): DNA文件路径
|
||
返回:
|
||
BinaryStreamReader: DNA读取器对象
|
||
"""
|
||
from dna import (
|
||
BinaryStreamReader,
|
||
DataLayer_All,
|
||
FileStream,
|
||
Status
|
||
)
|
||
|
||
try:
|
||
stream = FileStream(
|
||
dna_file,
|
||
FileStream.AccessMode_Read,
|
||
FileStream.OpenMode_Binary
|
||
)
|
||
reader = BinaryStreamReader(stream, DataLayer_All)
|
||
reader.read()
|
||
|
||
if not Status.isOk():
|
||
status = Status.get()
|
||
raise RuntimeError(f"Error loading DNA: {status.message}")
|
||
|
||
return reader
|
||
except Exception as e:
|
||
cmds.warning(f"Failed to load DNA file: {str(e)}")
|
||
return None
|
||
|
||
def GetDNAMeshInfo(reader):
|
||
"""
|
||
获取DNA文件中的网格信息
|
||
|
||
参数:
|
||
reader: DNA读取器对象
|
||
返回:
|
||
dict: 网格信息字典
|
||
"""
|
||
mesh_info = {}
|
||
try:
|
||
mesh_count = reader.getMeshCount()
|
||
for i in range(mesh_count):
|
||
mesh_name = reader.getMeshName(i)
|
||
mesh_info[mesh_name] = {
|
||
'index': i,
|
||
'lod': reader.getMeshLODCount(i),
|
||
'blend_shape_count': reader.getMeshBlendShapeChannelCount(i)
|
||
}
|
||
return mesh_info
|
||
except Exception as e:
|
||
cmds.warning(f"Failed to get mesh info: {str(e)}")
|
||
return {}
|
||
|
||
def GetDNAJointInfo(reader):
|
||
"""
|
||
获取DNA文件中的关节信息
|
||
|
||
参数:
|
||
reader: DNA读取器对象
|
||
返回:
|
||
dict: 关节信息字典
|
||
"""
|
||
joint_info = {}
|
||
try:
|
||
joint_count = reader.getJointCount()
|
||
for i in range(joint_count):
|
||
joint_name = reader.getJointName(i)
|
||
joint_info[joint_name] = {
|
||
'index': i,
|
||
'parent': reader.getJointParentIndex(i),
|
||
'position': reader.getNeutralJointTranslation(i),
|
||
'rotation': reader.getNeutralJointRotation(i)
|
||
}
|
||
return joint_info
|
||
except Exception as e:
|
||
cmds.warning(f"Failed to get joint info: {str(e)}")
|
||
return {}
|
||
|
||
def log_error(func):
|
||
"""错误日志装饰器"""
|
||
def wrapper(*args, **kwargs):
|
||
try:
|
||
return func(*args, **kwargs)
|
||
except Exception as e:
|
||
cmds.warning(f"Error in {func.__name__}: {str(e)}")
|
||
return None
|
||
return wrapper
|
||
|
||
def RenameBlendShape(mesh, blend_shape, prefix=None):
|
||
"""
|
||
重命名混合变形目标
|
||
|
||
参数:
|
||
mesh (str): 网格名称
|
||
blend_shape (str): 混合变形器名称
|
||
prefix (str, 可选): 自定义前缀,如果不提供则使用mesh名称
|
||
|
||
返回:
|
||
bool: 操作是否成功
|
||
"""
|
||
try:
|
||
if not cmds.objExists(blend_shape):
|
||
return False
|
||
|
||
# 获取所有权重属性
|
||
attr_weight = f"{blend_shape}.weight"
|
||
current_blend_shape_list = cmds.listAttr(attr_weight, m=True)
|
||
|
||
if not current_blend_shape_list:
|
||
return False
|
||
|
||
# 使用提供的前缀或网格名称
|
||
name_prefix = prefix if prefix else mesh
|
||
|
||
# 遍历每个混合变形目标
|
||
for i, bs_name in enumerate(current_blend_shape_list):
|
||
# 检查是否已经有正确的前缀
|
||
if not bs_name.startswith(f"{name_prefix}__"):
|
||
try:
|
||
# 重命名混合变形目标
|
||
cmds.aliasAttr(
|
||
f"{name_prefix}__{bs_name}",
|
||
f"{blend_shape}.w[{i}]"
|
||
)
|
||
except:
|
||
# 忽略重命名失败的情况
|
||
continue
|
||
|
||
return True
|
||
|
||
except Exception as e:
|
||
cmds.warning(f"Error renaming blend shape: {str(e)}")
|
||
return False
|
||
|
||
def ResetBlendShapes():
|
||
"""
|
||
重置混合变形目标
|
||
|
||
将所有混合变形目标的权重重置为0
|
||
"""
|
||
# 获取所有混合变形节点
|
||
blend_shapes = cmds.ls(type="blendShape")
|
||
|
||
for bs in blend_shapes:
|
||
# 获取所有权重属性
|
||
weight_attrs = cmds.listAttr(f"{bs}.weight", m=True) or []
|
||
|
||
# 重置每个目标的权重
|
||
for attr in weight_attrs:
|
||
try:
|
||
cmds.setAttr(f"{bs}.{attr}", 0)
|
||
except:
|
||
pass
|
||
|
||
def GetBlendShapes():
|
||
"""
|
||
获取标准的混合变形目标列表
|
||
|
||
返回:
|
||
list: 混合变形目标名称列表,按照标准顺序排列
|
||
"""
|
||
try:
|
||
# 获取映射文件路径
|
||
mapping_file = os.path.join(os.getenv('SG_PATH'), 'files/data/blend_shape_mappings.json')
|
||
|
||
if not os.path.exists(mapping_file):
|
||
return []
|
||
|
||
# 读取映射数据
|
||
with open(mapping_file, 'r') as f:
|
||
mappings = json.load(f)
|
||
|
||
# 收集所有目标名称
|
||
all_targets = []
|
||
for mesh_data in mappings.values():
|
||
if 'targets' in mesh_data:
|
||
all_targets.extend(mesh_data['targets'])
|
||
|
||
# 去重并排序
|
||
return sorted(list(set(all_targets)))
|
||
|
||
except Exception as e:
|
||
cmds.warning(f"Error getting blend shapes: {str(e)}")
|
||
return []
|
||
|
||
def SetBlendShapes(ct=None, index=None, target=None):
|
||
"""
|
||
设置混合形状
|
||
"""
|
||
pass
|
||
|
||
def BindPoseReset():
|
||
"""
|
||
重置绑定姿势
|
||
"""
|
||
pass
|
||
|
||
def WriteJson(f=None, d=None, k=None, v=None):
|
||
"""
|
||
写入JSON数据
|
||
"""
|
||
pass
|
||
|
||
def GetJoints(i=None, p=None, lod=None, t=None):
|
||
"""
|
||
获取关节信息
|
||
|
||
参数:
|
||
i (int, 可选): 通过索引获取关节名称
|
||
p (int, 可选): 获取指定索引关节的父关节
|
||
lod (int, 可选): 获取指定LOD级别的关节索引列表
|
||
t (str, 可选): 返回值类型 ("int", "string" 等)
|
||
|
||
返回:
|
||
str/list/int: 根据参数返回相应的关节信息
|
||
"""
|
||
try:
|
||
# 获取关节信息文件
|
||
joint_file = os.path.join(os.getenv('SG_PATH'), 'files/data/joint_info.json')
|
||
if not os.path.exists(joint_file):
|
||
return None
|
||
|
||
# 读取关节数据
|
||
with open(joint_file, 'r') as f:
|
||
joint_data = json.load(f)
|
||
|
||
if i is not None:
|
||
# 通过索引获取关节名称
|
||
for name, info in joint_data.items():
|
||
if info['index'] == i:
|
||
return name
|
||
elif p is not None:
|
||
# 获取父关节
|
||
for name, info in joint_data.items():
|
||
if info['index'] == p:
|
||
return info.get('parent', '')
|
||
elif lod is not None:
|
||
# 获取指定LOD级别的关节索引列表
|
||
indices = []
|
||
for info in joint_data.values():
|
||
if info.get('lod') == lod:
|
||
if t == "int":
|
||
indices.append(info['index'])
|
||
else:
|
||
indices.append(info['name'])
|
||
return indices
|
||
|
||
# 返回所有关节
|
||
return list(joint_data.keys())
|
||
|
||
except Exception as e:
|
||
cmds.warning(f"Error getting joints: {str(e)}")
|
||
return None
|
||
|
||
def GetNeutralJointTranslations(i=None, b=None, h=None):
|
||
"""
|
||
获取关节的中性位置信息
|
||
|
||
参数:
|
||
i (int, 可选): 关节索引
|
||
b (bool, 可选): 是否返回基础位置
|
||
h (bool, 可选): 是否获取头部关节位置
|
||
|
||
返回:
|
||
list: [tx, ty, tz, rx, ry, rz] 位置和旋转值,或头部关节位置列表
|
||
"""
|
||
try:
|
||
# 获取关节信息文件
|
||
joint_file = os.path.join(os.getenv('SG_PATH'), 'files/data/joint_info.json')
|
||
if not os.path.exists(joint_file):
|
||
return [0, 0, 0, 0, 0, 0]
|
||
|
||
# 读取关节数据
|
||
with open(joint_file, 'r') as f:
|
||
joint_data = json.load(f)
|
||
|
||
if h:
|
||
# 获取头部关节位置列表
|
||
head_positions = []
|
||
for info in joint_data.values():
|
||
if info.get('type') == 'head':
|
||
head_positions.extend(info.get('neutral_position', [0, 0, 0]))
|
||
return head_positions
|
||
|
||
elif i is not None:
|
||
# 获取指定索引关节的位置
|
||
for info in joint_data.values():
|
||
if info['index'] == i:
|
||
if b:
|
||
return info.get('base_position', [0, 0, 0, 0, 0, 0])
|
||
return info.get('neutral_position', [0, 0, 0, 0, 0, 0])
|
||
|
||
return [0, 0, 0, 0, 0, 0]
|
||
|
||
except Exception as e:
|
||
cmds.warning(f"Error getting neutral joint translations: {str(e)}")
|
||
return [0, 0, 0, 0, 0, 0]
|
||
|
||
def FindSkinCluster(mesh):
|
||
"""
|
||
查找网格的蒙皮变形器
|
||
|
||
参数:
|
||
mesh (str): 网格名称
|
||
|
||
返回:
|
||
str: 蒙皮变形器名称,如果没有找到则返回空字符串
|
||
"""
|
||
if not mesh or not cmds.objExists(mesh):
|
||
return ""
|
||
|
||
# 使用MEL命令查找蒙皮变形器
|
||
try:
|
||
skin_cluster = mel.eval(f'findRelatedSkinCluster "{mesh}"')
|
||
return skin_cluster if cmds.objExists(skin_cluster) else ""
|
||
except:
|
||
# 如果MEL命令失败,使用Python方式查找
|
||
history = cmds.listHistory(mesh, pdo=True) or []
|
||
for node in history:
|
||
if cmds.nodeType(node) == "skinCluster":
|
||
return node
|
||
return ""
|
||
|
||
def RBFDeformer(r=None, np=None, rbf=None, d=None, m=None, pi=None, t=None):
|
||
"""
|
||
创建和设置RBF变形器
|
||
|
||
参数:
|
||
r (float, 可选): 变形半径
|
||
np (int, 可选): 采样点数量
|
||
rbf (int, 可选): RBF类型
|
||
1 = Linear
|
||
2 = Gaussian
|
||
3 = ThinPlate
|
||
d (str, 可选): 变形方向("off"表示关闭)
|
||
m (list, 可选): 源和目标模型列表[source, target]
|
||
pi (list, 可选): 控制点索引列表
|
||
t (str/list, 可选): 要应用变形的目标模型
|
||
|
||
返回:
|
||
str: 创建的RBF变形器节点名称
|
||
"""
|
||
if not all([m, t]):
|
||
return None
|
||
|
||
try:
|
||
# 创建RBF变形器
|
||
rbf_node = cmds.createNode("rbfDeformer")
|
||
|
||
# 设置基本属性
|
||
if r is not None:
|
||
cmds.setAttr(f"{rbf_node}.radius", r)
|
||
if np is not None:
|
||
cmds.setAttr(f"{rbf_node}.numPoints", np)
|
||
if rbf is not None:
|
||
cmds.setAttr(f"{rbf_node}.rbfType", rbf)
|
||
if d == "off":
|
||
cmds.setAttr(f"{rbf_node}.direction", 0)
|
||
|
||
# 连接源和目标模型
|
||
if len(m) >= 2:
|
||
source_shape = cmds.listRelatives(m[0], shapes=True)[0]
|
||
target_shape = cmds.listRelatives(m[1], shapes=True)[0]
|
||
cmds.connectAttr(f"{source_shape}.worldMesh[0]", f"{rbf_node}.sourceMesh")
|
||
cmds.connectAttr(f"{target_shape}.worldMesh[0]", f"{rbf_node}.targetMesh")
|
||
|
||
# 设置控制点
|
||
if pi:
|
||
for i, point_index in enumerate(pi):
|
||
cmds.setAttr(f"{rbf_node}.controlPoints[{i}]", point_index)
|
||
|
||
# 应用到目标模型
|
||
targets = [t] if isinstance(t, str) else t
|
||
for target in targets:
|
||
target_shape = cmds.listRelatives(target, shapes=True)[0]
|
||
cmds.connectAttr(f"{rbf_node}.outputMesh", f"{target_shape}.inMesh")
|
||
|
||
return rbf_node
|
||
|
||
except Exception as e:
|
||
cmds.warning(f"Error creating RBF deformer: {str(e)}")
|
||
return None
|
||
|
||
def SaveBlendShapeMappings(reset=False):
|
||
"""
|
||
保存混合变形映射信息
|
||
|
||
参数:
|
||
reset (bool): 是否重置映射
|
||
|
||
返回:
|
||
bool: 操作是否成功
|
||
"""
|
||
try:
|
||
# 获取映射文件路径
|
||
mapping_file = os.path.join(os.getenv('SG_PATH'), 'files/data/blend_shape_mappings.json')
|
||
|
||
if reset:
|
||
# 重置映射
|
||
if os.path.exists(mapping_file):
|
||
os.remove(mapping_file)
|
||
return True
|
||
|
||
# 收集映射数据
|
||
mappings = {}
|
||
for i in range(50): # 遍历前50个网格
|
||
mesh = GetMeshes(m=i)
|
||
if cmds.objExists(mesh):
|
||
blend_shape = GetBlendShape(mesh)
|
||
if cmds.objExists(blend_shape):
|
||
# 获取所有权重属性
|
||
weight_attrs = cmds.listAttr(f"{blend_shape}.weight", m=True) or []
|
||
mappings[mesh] = {
|
||
'blend_shape': blend_shape,
|
||
'targets': weight_attrs
|
||
}
|
||
|
||
# 保存映射数据
|
||
with open(mapping_file, 'w') as f:
|
||
json.dump(mappings, f, indent=4)
|
||
|
||
return True
|
||
|
||
except Exception as e:
|
||
cmds.warning(f"Error saving blend shape mappings: {str(e)}")
|
||
return False
|
||
|
||
def SaveBlendShapes(bs=None, i=None, value=None):
|
||
"""
|
||
保存混合变形目标
|
||
|
||
参数:
|
||
bs (int): 网格索引
|
||
i (int): 目标索引
|
||
value (str): 目标网格名称
|
||
|
||
返回:
|
||
bool: 操作是否成功
|
||
"""
|
||
try:
|
||
if None in [bs, i, value] or not cmds.objExists(value):
|
||
return False
|
||
|
||
# 获取保存路径
|
||
save_path = os.path.join(os.getenv('SG_PATH'), 'files/data/blend_shapes')
|
||
if not os.path.exists(save_path):
|
||
os.makedirs(save_path)
|
||
|
||
# 构建目标文件名
|
||
mesh = GetMeshes(m=bs)
|
||
if not mesh:
|
||
return False
|
||
|
||
target_file = os.path.join(save_path, f"{mesh}_target_{i}.obj")
|
||
|
||
# 导出目标模型
|
||
cmds.select(value)
|
||
cmds.file(
|
||
target_file,
|
||
force=True,
|
||
options="groups=0;ptgroups=0;materials=0;smoothing=0;normals=0",
|
||
type="OBJexport",
|
||
exportSelected=True
|
||
)
|
||
|
||
return True
|
||
|
||
except Exception as e:
|
||
cmds.warning(f"Error saving blend shape: {str(e)}")
|
||
return False
|
||
|
||
def ResetBlendShape(mesh, blend_shape):
|
||
"""
|
||
重置指定混合变形器的目标名称
|
||
|
||
参数:
|
||
mesh (str): 网格名称
|
||
blend_shape (str): 混合变形器名称
|
||
|
||
返回:
|
||
bool: 操作是否成功
|
||
"""
|
||
try:
|
||
if not cmds.objExists(blend_shape):
|
||
return False
|
||
|
||
# 获取所有权重属性
|
||
attr_weight = f"{blend_shape}.weight"
|
||
current_blend_shape_list = cmds.listAttr(attr_weight, m=True)
|
||
|
||
if not current_blend_shape_list:
|
||
return False
|
||
|
||
# 遍历每个混合变形目标
|
||
for i, bs_name in enumerate(current_blend_shape_list):
|
||
# 检查是否有网格名称前缀
|
||
prefix = f"{mesh}__"
|
||
if bs_name.startswith(prefix):
|
||
try:
|
||
# 移除前缀并重命名
|
||
new_name = bs_name.replace(prefix, "")
|
||
cmds.aliasAttr(new_name, f"{blend_shape}.w[{i}]")
|
||
except:
|
||
# 忽略重命名失败的情况
|
||
continue
|
||
|
||
return True
|
||
|
||
except Exception as e:
|
||
cmds.warning(f"Error resetting blend shape: {str(e)}")
|
||
return False
|
||
|
||
def BodyJointsLocator():
|
||
"""
|
||
定位身体关节位置
|
||
|
||
根据身体模型的拓扑结构定位关节的位置
|
||
|
||
返回:
|
||
bool: 操作是否成功
|
||
"""
|
||
try:
|
||
# 获取身体模型
|
||
body_mesh = GetMeshes(m=50)
|
||
if not cmds.objExists(body_mesh):
|
||
return False
|
||
|
||
# 获取关节信息文件
|
||
joint_file = os.path.join(os.getenv('SG_PATH'), 'files/data/joint_info.json')
|
||
if not os.path.exists(joint_file):
|
||
return False
|
||
|
||
# 读取关节数据
|
||
with open(joint_file, 'r') as f:
|
||
joint_data = json.load(f)
|
||
|
||
# 定位每个关节
|
||
for joint_name, info in joint_data.items():
|
||
if info.get('type') == 'body':
|
||
# 根据拓扑结构计算关节位置
|
||
pos = calculate_joint_position(body_mesh, info['vertex_indices'])
|
||
if pos:
|
||
# 更新关节位置
|
||
cmds.xform(joint_name, ws=True, t=pos)
|
||
|
||
return True
|
||
|
||
except Exception as e:
|
||
cmds.warning(f"Error in BodyJointsLocator: {str(e)}")
|
||
return False
|
||
|
||
def MainAmendAxis():
|
||
"""
|
||
修正主要轴向
|
||
|
||
修正骨骼的主要旋转轴向,使其符合标准姿态
|
||
|
||
返回:
|
||
bool: 操作是否成功
|
||
"""
|
||
try:
|
||
# 获取骨骼层级
|
||
joints = cmds.ls(type='joint')
|
||
if not joints:
|
||
return False
|
||
|
||
# 修正每个关节的轴向
|
||
for joint in joints:
|
||
# 获取关节的当前方向
|
||
rot = cmds.xform(joint, q=True, ro=True)
|
||
|
||
# 计算修正值
|
||
corrected_rot = calculate_corrected_rotation(rot)
|
||
|
||
# 应用修正
|
||
cmds.xform(joint, ro=corrected_rot)
|
||
|
||
return True
|
||
|
||
except Exception as e:
|
||
cmds.warning(f"Error in MainAmendAxis: {str(e)}")
|
||
return False
|
||
|
||
def RefreshNeutralJointTranslation():
|
||
"""
|
||
刷新所有关节的中性位置
|
||
|
||
返回:
|
||
bool: 操作是否成功
|
||
"""
|
||
try:
|
||
# 获取所有关节
|
||
joints = cmds.ls(type='joint')
|
||
if not joints:
|
||
return False
|
||
|
||
# 更新每个关节的中性位置
|
||
for joint in joints:
|
||
# 获取当前位置
|
||
pos = cmds.xform(joint, q=True, t=True, ws=True)
|
||
rot = cmds.xform(joint, q=True, ro=True, ws=True)
|
||
|
||
# 更新中性位置
|
||
cmds.setAttr(f"{joint}.neutralTranslate", *pos)
|
||
cmds.setAttr(f"{joint}.neutralRotate", *rot)
|
||
|
||
return True
|
||
|
||
except Exception as e:
|
||
cmds.warning(f"Error refreshing neutral joint translation: {str(e)}")
|
||
return False
|