MetaFusion/scripts/utils/model_utils.py
2025-02-06 04:00:17 +08:00

595 lines
20 KiB
Python
Raw 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.

import os
import maya.cmds as cmds
from scripts.config import data
import maya.mel as mel
# LOD模型加载功能
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)}")