599 lines
20 KiB
Python
599 lines
20 KiB
Python
#!/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)}") |