#!/usr/bin/env python # -*- coding: utf-8 -*- #===================================== 1. Module Imports ===================================== import maya.cmds as cmds import maya.mel as mel import sys import os #===================================== 2. Model Utils ===================================== def load_model(lod_index, model_type, file_path): """加载模型 Args: lod_index: LOD级别(0-7) model_type: 模型类型(head/teeth/gums/eye_l/eye_r/iris/eyelash/eyelid/cartilage/body) file_path: 模型文件路径 """ try: # 检查文件是否存在 if not os.path.exists(file_path): raise FileNotFoundError(f"模型文件不存在: {file_path}") # 导入模型 imported_nodes = cmds.file( file_path, i=True, returnNewNodes=True, namespace=f"LOD{lod_index}_{model_type}" ) # 设置模型属性 for node in imported_nodes: if cmds.objectType(node) == "transform": # 添加自定义属性 cmds.addAttr(node, ln="LOD_INDEX", at="long", dv=lod_index) cmds.addAttr(node, ln="MODEL_TYPE", dt="string") cmds.setAttr(f"{node}.MODEL_TYPE", model_type, type="string") print(f"成功加载LOD{lod_index}_{model_type}模型") return imported_nodes except Exception as e: cmds.warning(f"加载模型失败: {str(e)}") return None def delete_lod(lod_index): """删除指定LOD级别的所有模型""" try: # 查找指定LOD的所有模型 lod_nodes = cmds.ls(f"LOD{lod_index}_*", long=True) if lod_nodes: cmds.delete(lod_nodes) print(f"成功删除LOD{lod_index}的所有模型") else: print(f"未找到LOD{lod_index}的模型") except Exception as e: cmds.warning(f"删除LOD{lod_index}失败: {str(e)}") # 模型工具功能 def split_model(): """分离选中的模型""" try: selection = cmds.ls(sl=True, type="transform") if not selection: raise ValueError("请先选择要分离的模型") for obj in selection: # 获取模型的面数 mesh = cmds.listRelatives(obj, shapes=True, type="mesh") if not mesh: continue # 分离每个面为独立的模型 cmds.polySeparate(obj, ch=False) print("模型分离完成") except Exception as e: cmds.warning(f"模型分离失败: {str(e)}") def generate_facial_accessories(): """生成面部配件""" print("生成面部配件功能待实现") def repair_normals(): """修复法线""" try: selection = cmds.ls(sl=True, type="transform") if not selection: raise ValueError("请先选择要修复法线的模型") for obj in selection: mesh = cmds.listRelatives(obj, shapes=True, type="mesh") if not mesh: continue # 统一法线方向 cmds.polyNormal(obj, normalMode=2, userNormalMode=0) # 解锁法线 cmds.polyNormalPerVertex(obj, ufn=True) print("法线修复完成") except Exception as e: cmds.warning(f"修复法线失败: {str(e)}") def repair_vertex_order(): """修复点序""" try: selection = cmds.ls(sl=True, type="transform") if not selection: raise ValueError("请先选择要修复点序的模型") for obj in selection: # 重新排序顶点 cmds.polyReorder(obj) print("点序修复完成") except Exception as e: cmds.warning(f"修复点序失败: {str(e)}") def repair_seams(): """修复接缝""" try: selection = cmds.ls(sl=True, type="transform") if not selection: raise ValueError("请先选择要修复接缝的模型") for obj in selection: # 合并重叠顶点 cmds.polyMergeVertex(obj, d=0.001) # 删除重复面 cmds.polyRemoveFace(obj, removeDuplicateFaces=True) print("接缝修复完成") except Exception as e: cmds.warning(f"修复接缝失败: {str(e)}") # LOD功能 def load_custom_models(): """自定义加载模型""" print("自定义加载模型功能待实现") def standardize_naming(): """标准化命名""" try: selection = cmds.ls(sl=True, type="transform") if not selection: raise ValueError("请先选择要标准化命名的模型") for obj in selection: # 获取LOD信息 if cmds.attributeQuery("LOD_INDEX", node=obj, exists=True): lod_index = cmds.getAttr(f"{obj}.LOD_INDEX") model_type = cmds.getAttr(f"{obj}.MODEL_TYPE") # 重命名为标准格式 new_name = f"LOD{lod_index}_{model_type}" cmds.rename(obj, new_name) print("命名标准化完成") except Exception as e: cmds.warning(f"标准化命名失败: {str(e)}") def auto_group(): """自动分组""" try: # 创建LOD组 for i in range(8): group_name = f"LOD{i}_GROUP" if not cmds.objExists(group_name): cmds.group(empty=True, name=group_name) # 将模型移动到对应组 all_models = cmds.ls("LOD*_*", type="transform") for model in all_models: if cmds.attributeQuery("LOD_INDEX", node=model, exists=True): lod_index = cmds.getAttr(f"{model}.LOD_INDEX") group_name = f"LOD{lod_index}_GROUP" cmds.parent(model, group_name) print("自动分组完成") except Exception as e: cmds.warning(f"自动分组失败: {str(e)}") # MetaHuman模型特定功能 def validate_metahuman_topology(): """验证模型是否符合MetaHuman拓扑要求""" try: selection = cmds.ls(sl=True, type="transform") if not selection: raise ValueError("请先选择要验证的模型") results = [] for obj in selection: mesh = cmds.listRelatives(obj, shapes=True, type="mesh") if not mesh: continue # 获取模型信息 vertex_count = cmds.polyEvaluate(obj, vertex=True) face_count = cmds.polyEvaluate(obj, face=True) # 检查是否符合MetaHuman标准 is_valid = True messages = [] # 获取LOD级别 lod_index = -1 if cmds.attributeQuery("LOD_INDEX", node=obj, exists=True): lod_index = cmds.getAttr(f"{obj}.LOD_INDEX") # 根据LOD级别验证顶点数和面数 if lod_index == 0: if vertex_count != 7127: messages.append(f"顶点数不符合LOD0标准(当前:{vertex_count}, 应为:7127)") is_valid = False elif lod_index == 1: if vertex_count != 5127: messages.append(f"顶点数不符合LOD1标准(当前:{vertex_count}, 应为:5127)") is_valid = False # ... 其他LOD级别的验证 results.append({ "object": obj, "is_valid": is_valid, "messages": messages }) # 显示验证结果 for result in results: status = "✓" if result["is_valid"] else "✗" print(f"{status} {result['object']}:") if not result["is_valid"]: for msg in result["messages"]: print(f" - {msg}") return results except Exception as e: cmds.warning(f"拓扑验证失败: {str(e)}") return None def transfer_uvs(): """传递UV到其他LOD级别""" try: # 获取源模型和目标模型 selection = cmds.ls(sl=True, type="transform") if len(selection) != 2: raise ValueError("请选择一个源模型和一个目标模型") source = selection[0] target = selection[1] # 执行UV传递 cmds.transferAttributes( source, target, transferUVs=2, transferColors=0, sampleSpace=4 # World space ) # 删除构建历史 cmds.delete(target, ch=True) print(f"UV传递完成: {source} -> {target}") except Exception as e: cmds.warning(f"UV传递失败: {str(e)}") def generate_lod_chain(): """生成完整的LOD链""" try: # 获取选中的LOD0模型 selection = cmds.ls(sl=True, type="transform") if not selection: raise ValueError("请先选择LOD0模型") source = selection[0] if not cmds.attributeQuery("LOD_INDEX", node=source, exists=True): raise ValueError("请选择带有LOD属性的模型") lod_index = cmds.getAttr(f"{source}.LOD_INDEX") if lod_index != 0: raise ValueError("请选择LOD0模型") # 为每个LOD级别生成简化模型 for i in range(1, 8): target_name = source.replace("LOD0", f"LOD{i}") # 复制模型 duplicate = cmds.duplicate(source, name=target_name)[0] # 设置LOD属性 cmds.setAttr(f"{duplicate}.LOD_INDEX", i) # 简化模型 reduction_ratio = 1.0 - (i * 0.1) # 每级减少10% cmds.polyReduce( duplicate, percentage=reduction_ratio * 100, triangulate=False, preserveTopology=True, keepQuadsWeight=1.0, keepBorder=True ) print(f"生成LOD{i}完成: {target_name}") print("LOD链生成完成") except Exception as e: cmds.warning(f"LOD链生成失败: {str(e)}") def check_model_symmetry(): """检查模型对称性""" try: selection = cmds.ls(sl=True, type="transform") if not selection: raise ValueError("请先选择要检查的模型") for obj in selection: # 获取顶点位置 vertices = cmds.ls(f"{obj}.vtx[*]", flatten=True) vertex_positions = {} for vtx in vertices: pos = cmds.pointPosition(vtx, world=True) # 使用x坐标的绝对值作为键,保存顶点信息 x_abs = abs(pos[0]) if x_abs not in vertex_positions: vertex_positions[x_abs] = [] vertex_positions[x_abs].append((vtx, pos)) # 检查对称性 asymmetric_vertices = [] for x_abs, points in vertex_positions.items(): if len(points) == 2: vtx1, pos1 = points[0] vtx2, pos2 = points[1] # 检查y和z坐标是否对称 if abs(pos1[1] - pos2[1]) > 0.001 or abs(pos1[2] - pos2[2]) > 0.001: asymmetric_vertices.extend([vtx1, vtx2]) elif len(points) == 1 and x_abs > 0.001: # 忽略中心线上的点 asymmetric_vertices.append(points[0][0]) # 显示结果 if asymmetric_vertices: cmds.select(asymmetric_vertices) print(f"发现{len(asymmetric_vertices)}个不对称顶点") else: print(f"{obj}模型对称性检查通过") except Exception as e: cmds.warning(f"对称性检查失败: {str(e)}") def mirror_geometry(): """镜像几何体""" try: selection = cmds.ls(sl=True, type="transform") if not selection: raise ValueError("请先选择要镜像的模型") for obj in selection: # 创建镜像几何体 mirrored = cmds.duplicate(obj, name=f"{obj}_mirrored")[0] # 执行镜像 cmds.scale(-1, 1, 1, mirrored) # 反转法线 cmds.polyNormal(mirrored, normalMode=0) print(f"模型镜像完成: {mirrored}") except Exception as e: cmds.warning(f"模型镜像失败: {str(e)}") # 骨骼权重相关功能 def transfer_skin_weights(): """传递蒙皮权重""" try: selection = cmds.ls(sl=True, type="transform") if len(selection) < 2: raise ValueError("请选择源模型和目标模型") source = selection[0] targets = selection[1:] # 获取源模型的蒙皮变形器 skin_cluster = mel.eval(f'findRelatedSkinCluster("{source}")') if not skin_cluster: raise ValueError(f"源模型{source}没有蒙皮变形器") # 获取骨骼列表 joints = cmds.skinCluster(skin_cluster, q=True, inf=True) # 为每个目标模型创建蒙皮并传递权重 for target in targets: # 创建新的蒙皮变形器 new_skin = cmds.skinCluster( joints, target, toSelectedBones=True, bindMethod=0, skinMethod=0, normalizeWeights=1 )[0] # 复制权重 cmds.copySkinWeights( ss=skin_cluster, ds=new_skin, noMirror=True, surfaceAssociation="closestPoint", influenceAssociation="oneToOne" ) print(f"权重传递完成: {source} -> {target}") except Exception as e: cmds.warning(f"权重传递失败: {str(e)}") def mirror_skin_weights(): """镜像蒙皮权重""" try: selection = cmds.ls(sl=True, type="transform") if not selection: raise ValueError("请选择要镜像权重的模型") for obj in selection: # 获取蒙皮变形器 skin_cluster = mel.eval(f'findRelatedSkinCluster("{obj}")') if not skin_cluster: continue # 执行镜像 cmds.copySkinWeights( ss=skin_cluster, ds=skin_cluster, mirrorMode="YZ", surfaceAssociation="closestPoint", influenceAssociation="closestJoint" ) print(f"权重镜像完成: {obj}") except Exception as e: cmds.warning(f"权重镜像失败: {str(e)}") def clean_skin_weights(): """清理蒙皮权重""" try: selection = cmds.ls(sl=True, type="transform") if not selection: raise ValueError("请选择要清理权重的模型") for obj in selection: # 获取蒙皮变形器 skin_cluster = mel.eval(f'findRelatedSkinCluster("{obj}")') if not skin_cluster: continue # 移除小权重 cmds.skinPercent( skin_cluster, obj, pruneWeights=0.01 # 移除小于1%的权重 ) # 规范化权重 cmds.skinPercent( skin_cluster, obj, normalize=True ) print(f"权重清理完成: {obj}") except Exception as e: cmds.warning(f"权重清理失败: {str(e)}") # 变形器处理功能 def transfer_blendshapes(): """传递变形器""" try: selection = cmds.ls(sl=True, type="transform") if len(selection) < 2: raise ValueError("请选择源模型和目标模型") source = selection[0] targets = selection[1:] # 获取源模型的变形器 blendshapes = cmds.listConnections(source, type="blendShape") if not blendshapes: raise ValueError(f"源模型{source}没有变形器") for bs in blendshapes: # 获取所有目标形状 targets_count = cmds.blendShape(bs, q=True, target=True) target_weights = [] target_names = [] # 保存当前权重 for i in range(targets_count): weight = cmds.getAttr(f"{bs}.weight[{i}]") name = cmds.aliasAttr(f"{bs}.weight[{i}]", q=True) target_weights.append(weight) target_names.append(name) # 为每个目标模型创建变形器 for target in targets: new_bs = cmds.blendShape( target, frontOfChain=True, name=f"{target}_blendShape" )[0] # 设置权重 for i, (weight, name) in enumerate(zip(target_weights, target_names)): cmds.setAttr(f"{new_bs}.{name}", weight) print(f"变形器传递完成: {source} -> {targets}") except Exception as e: cmds.warning(f"变形器传递失败: {str(e)}") def optimize_blendshapes(): """优化变形器""" try: selection = cmds.ls(sl=True, type="transform") if not selection: raise ValueError("请选择要优化变形器的模型") for obj in selection: # 获取变形器 blendshapes = cmds.listConnections(obj, type="blendShape") if not blendshapes: continue for bs in blendshapes: # 获取所有目标形状 target_count = cmds.blendShape(bs, q=True, target=True) # 删除未使用的目标形状 for i in range(target_count): weight = cmds.getAttr(f"{bs}.weight[{i}]") if abs(weight) < 0.001: # 权重接近0的目标形状 cmds.removeMultiInstance(f"{bs}.weight[{i}]", b=True) # 重新排序索引 cmds.blendShape(bs, edit=True, resetTargetDelta=True) print(f"变形器优化完成: {obj}") except Exception as e: cmds.warning(f"变形器优化失败: {str(e)}") def create_corrective_blendshape(): """创建修正变形器""" try: selection = cmds.ls(sl=True, type="transform") if len(selection) != 2: raise ValueError("请选择基础模型和目标形状") base = selection[0] target = selection[1] # 创建修正变形器 corrective_bs = cmds.blendShape( target, base, frontOfChain=True, name=f"{base}_corrective" )[0] # 设置权重驱动 cmds.setDrivenKeyframe( f"{corrective_bs}.{target}", currentDriver="time", driverValue=0, value=0 ) cmds.setDrivenKeyframe( f"{corrective_bs}.{target}", currentDriver="time", driverValue=1, value=1 ) print(f"修正变形器创建完成: {corrective_bs}") except Exception as e: cmds.warning(f"创建修正变形器失败: {str(e)}")