MetaFusion/scripts/utils/Core.py
2025-02-07 05:10:30 +08:00

1224 lines
37 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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