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