MetaFusion/scripts/utils/Core.py

1224 lines
37 KiB
Python
Raw Permalink Normal View History

2025-02-07 05:10:30 +08:00
#!/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