2025-02-06 04:46:41 +08:00
|
|
|
#!/usr/bin/env python
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
#===================================== 1. Module Imports =====================================
|
2025-02-06 04:00:17 +08:00
|
|
|
import maya.cmds as cmds
|
2025-02-06 04:46:41 +08:00
|
|
|
import maya.mel as mel
|
|
|
|
import sys
|
|
|
|
import os
|
2025-02-06 04:00:17 +08:00
|
|
|
|
2025-02-06 04:46:41 +08:00
|
|
|
#===================================== 2. BlendShape Manager Class =====================================
|
2025-02-06 04:00:17 +08:00
|
|
|
class BlendShapeManager:
|
|
|
|
"""BlendShape管理器"""
|
|
|
|
def __init__(self):
|
|
|
|
self.main_bs = None # 主要BlendShape节点
|
|
|
|
self.related_bs = [] # 相关BlendShape节点
|
|
|
|
self.current_weights = {} # 当前权重缓存
|
|
|
|
|
|
|
|
def set_main_blendshape(self, bs_node):
|
|
|
|
"""设置主要BlendShape节点"""
|
|
|
|
if not cmds.objExists(bs_node):
|
|
|
|
raise ValueError(f"BlendShape节点不存在: {bs_node}")
|
|
|
|
self.main_bs = bs_node
|
|
|
|
|
|
|
|
def add_related_blendshape(self, bs_node):
|
|
|
|
"""添加相关BlendShape节点"""
|
|
|
|
if not cmds.objExists(bs_node):
|
|
|
|
raise ValueError(f"BlendShape节点不存在: {bs_node}")
|
|
|
|
if bs_node not in self.related_bs:
|
|
|
|
self.related_bs.append(bs_node)
|
|
|
|
|
|
|
|
def clear_related_blendshapes(self):
|
|
|
|
"""清空相关BlendShape节点"""
|
|
|
|
self.related_bs = []
|
|
|
|
|
|
|
|
def get_all_targets(self, bs_node):
|
|
|
|
"""获取BlendShape的所有目标"""
|
|
|
|
if not cmds.objExists(bs_node):
|
|
|
|
return []
|
|
|
|
|
|
|
|
# 获取目标数量
|
|
|
|
target_count = cmds.blendShape(bs_node, q=True, weightCount=True)
|
|
|
|
targets = []
|
|
|
|
|
|
|
|
# 获取每个目标的名称
|
|
|
|
for i in range(target_count):
|
|
|
|
alias = cmds.aliasAttr(f"{bs_node}.weight[{i}]", q=True)
|
|
|
|
if alias:
|
|
|
|
targets.append(alias)
|
|
|
|
|
|
|
|
return targets
|
|
|
|
|
|
|
|
def set_weight(self, bs_node, target, weight):
|
|
|
|
"""设置BlendShape权重"""
|
|
|
|
if not cmds.objExists(bs_node):
|
|
|
|
return False
|
|
|
|
|
|
|
|
try:
|
|
|
|
# 获取权重索引
|
|
|
|
weight_index = -1
|
|
|
|
target_count = cmds.blendShape(bs_node, q=True, weightCount=True)
|
|
|
|
|
|
|
|
for i in range(target_count):
|
|
|
|
alias = cmds.aliasAttr(f"{bs_node}.weight[{i}]", q=True)
|
|
|
|
if alias == target:
|
|
|
|
weight_index = i
|
|
|
|
break
|
|
|
|
|
|
|
|
if weight_index >= 0:
|
|
|
|
cmds.setAttr(f"{bs_node}.weight[{weight_index}]", weight)
|
|
|
|
return True
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
cmds.warning(f"设置权重失败: {str(e)}")
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
def get_weight(self, bs_node, target):
|
|
|
|
"""获取BlendShape权重"""
|
|
|
|
if not cmds.objExists(bs_node):
|
|
|
|
return 0.0
|
|
|
|
|
|
|
|
try:
|
|
|
|
# 获取权重索引
|
|
|
|
weight_index = -1
|
|
|
|
target_count = cmds.blendShape(bs_node, q=True, weightCount=True)
|
|
|
|
|
|
|
|
for i in range(target_count):
|
|
|
|
alias = cmds.aliasAttr(f"{bs_node}.weight[{i}]", q=True)
|
|
|
|
if alias == target:
|
|
|
|
weight_index = i
|
|
|
|
break
|
|
|
|
|
|
|
|
if weight_index >= 0:
|
|
|
|
return cmds.getAttr(f"{bs_node}.weight[{weight_index}]")
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
cmds.warning(f"获取权重失败: {str(e)}")
|
|
|
|
|
|
|
|
return 0.0
|
|
|
|
|
|
|
|
def store_weights(self):
|
|
|
|
"""存储当前权重"""
|
|
|
|
self.current_weights = {}
|
|
|
|
|
|
|
|
# 存储主要BlendShape权重
|
|
|
|
if self.main_bs:
|
|
|
|
self.current_weights[self.main_bs] = {}
|
|
|
|
for target in self.get_all_targets(self.main_bs):
|
|
|
|
self.current_weights[self.main_bs][target] = self.get_weight(self.main_bs, target)
|
|
|
|
|
|
|
|
# 存储相关BlendShape权重
|
|
|
|
for bs in self.related_bs:
|
|
|
|
self.current_weights[bs] = {}
|
|
|
|
for target in self.get_all_targets(bs):
|
|
|
|
self.current_weights[bs][target] = self.get_weight(bs, target)
|
|
|
|
|
|
|
|
def restore_weights(self):
|
|
|
|
"""还原存储的权重"""
|
|
|
|
for bs, weights in self.current_weights.items():
|
|
|
|
for target, weight in weights.items():
|
|
|
|
self.set_weight(bs, target, weight)
|
|
|
|
|
|
|
|
# 全局BlendShape管理器实例
|
|
|
|
bs_manager = BlendShapeManager()
|
|
|
|
|
|
|
|
def reset_blendshapes():
|
|
|
|
"""重置所有BlendShape"""
|
|
|
|
try:
|
|
|
|
# 重置主要BlendShape
|
|
|
|
if bs_manager.main_bs:
|
|
|
|
for target in bs_manager.get_all_targets(bs_manager.main_bs):
|
|
|
|
bs_manager.set_weight(bs_manager.main_bs, target, 0)
|
|
|
|
|
|
|
|
# 重置相关BlendShape
|
|
|
|
for bs in bs_manager.related_bs:
|
|
|
|
for target in bs_manager.get_all_targets(bs):
|
|
|
|
bs_manager.set_weight(bs, target, 0)
|
|
|
|
|
|
|
|
print("BlendShape重置完成")
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
cmds.warning(f"重置BlendShape失败: {str(e)}")
|
|
|
|
|
|
|
|
def filter_blendshapes():
|
|
|
|
"""过滤BlendShape"""
|
|
|
|
# TODO: 实现BlendShape过滤功能
|
|
|
|
print("BlendShape过滤功能待实现")
|
|
|
|
|
|
|
|
def on_main_bs_selected(targets):
|
|
|
|
"""主要BlendShape选择变化"""
|
|
|
|
try:
|
|
|
|
# 存储当前权重
|
|
|
|
bs_manager.store_weights()
|
|
|
|
|
|
|
|
# 设置新的权重
|
|
|
|
if bs_manager.main_bs:
|
|
|
|
for target in targets:
|
|
|
|
bs_manager.set_weight(bs_manager.main_bs, target, 1.0)
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
cmds.warning(f"设置BlendShape权重失败: {str(e)}")
|
|
|
|
|
|
|
|
def on_related_bs_selected(targets):
|
|
|
|
"""相关BlendShape选择变化"""
|
|
|
|
try:
|
|
|
|
# 存储当前权重
|
|
|
|
bs_manager.store_weights()
|
|
|
|
|
|
|
|
# 设置新的权重
|
|
|
|
for bs in bs_manager.related_bs:
|
|
|
|
for target in targets:
|
|
|
|
bs_manager.set_weight(bs, target, 1.0)
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
cmds.warning(f"设置BlendShape权重失败: {str(e)}")
|
|
|
|
|
|
|
|
def set_bs_value(value):
|
|
|
|
"""设置BlendShape权重值"""
|
|
|
|
try:
|
|
|
|
# 设置主要BlendShape权重
|
|
|
|
if bs_manager.main_bs:
|
|
|
|
for target in bs_manager.get_all_targets(bs_manager.main_bs):
|
|
|
|
bs_manager.set_weight(bs_manager.main_bs, target, value)
|
|
|
|
|
|
|
|
# 设置相关BlendShape权重
|
|
|
|
for bs in bs_manager.related_bs:
|
|
|
|
for target in bs_manager.get_all_targets(bs):
|
|
|
|
bs_manager.set_weight(bs, target, value)
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
cmds.warning(f"设置BlendShape权重值失败: {str(e)}")
|
|
|
|
|
|
|
|
def set_expr_value(value):
|
|
|
|
"""设置表情权重值"""
|
|
|
|
try:
|
|
|
|
# 获取当前选中的表情控制器
|
|
|
|
sel = cmds.ls(sl=True)
|
|
|
|
if not sel:
|
|
|
|
return
|
|
|
|
|
|
|
|
# 设置权重
|
|
|
|
for ctrl in sel:
|
|
|
|
if cmds.objExists(f"{ctrl}.weight"):
|
|
|
|
cmds.setAttr(f"{ctrl}.weight", value)
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
cmds.warning(f"设置表情权重值失败: {str(e)}")
|
|
|
|
|
|
|
|
# 表情控制功能
|
|
|
|
def reset_expression():
|
|
|
|
"""还原默认表情"""
|
|
|
|
try:
|
|
|
|
# 还原存储的权重
|
|
|
|
bs_manager.restore_weights()
|
|
|
|
print("表情已还原")
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
cmds.warning(f"还原表情失败: {str(e)}")
|
|
|
|
|
|
|
|
def select_expression():
|
|
|
|
"""选择表情"""
|
|
|
|
# TODO: 实现表情选择功能
|
|
|
|
print("表情选择功能待实现")
|
|
|
|
|
|
|
|
def write_expression():
|
|
|
|
"""写入当前表情"""
|
|
|
|
# TODO: 实现表情写入功能
|
|
|
|
print("表情写入功能待实现")
|
|
|
|
|
|
|
|
def find_controller():
|
|
|
|
"""查找控制器"""
|
|
|
|
# TODO: 实现控制器查找功能
|
|
|
|
print("控制器查找功能待实现")
|
|
|
|
|
|
|
|
def select_joints():
|
|
|
|
"""选择关联关节"""
|
|
|
|
# TODO: 实现关联关节选择功能
|
|
|
|
print("关联关节选择功能待实现")
|
|
|
|
|
|
|
|
def write_mirror_expression():
|
|
|
|
"""写入镜像表情"""
|
|
|
|
# TODO: 实现镜像表情写入功能
|
|
|
|
print("镜像表情写入功能待实现")
|
|
|
|
|
|
|
|
def reset_expression():
|
|
|
|
"""恢复表情"""
|
|
|
|
print("恢复表情功能待实现")
|
|
|
|
|
|
|
|
def blend_filter():
|
|
|
|
"""混合筛选"""
|
|
|
|
print("混合筛选功能待实现")
|
|
|
|
|
|
|
|
def flip():
|
|
|
|
"""翻转"""
|
|
|
|
print("翻转功能待实现")
|
|
|
|
|
|
|
|
def mirror_target():
|
|
|
|
"""镜像目标"""
|
|
|
|
print("镜像目标功能待实现")
|
|
|
|
|
|
|
|
def find_flip_target():
|
|
|
|
"""查找翻转目标"""
|
|
|
|
print("查找翻转目标功能待实现")
|
|
|
|
|
|
|
|
def add_blend_target():
|
|
|
|
"""添加混合目标"""
|
|
|
|
print("添加混合目标功能待实现")
|
|
|
|
|
|
|
|
def delete_blend_target():
|
|
|
|
"""删除混合目标"""
|
|
|
|
print("删除混合目标功能待实现")
|
|
|
|
|
|
|
|
def batch_blend_target():
|
|
|
|
"""批量混合目标"""
|
|
|
|
print("批量混合目标功能待实现")
|
|
|
|
|
|
|
|
def rebuild_selected_target():
|
|
|
|
"""重建选择目标"""
|
|
|
|
print("重建选择目标功能待实现")
|
|
|
|
|
|
|
|
def blend_selected_target():
|
|
|
|
"""混合选择目标"""
|
|
|
|
print("混合选择目标功能待实现")
|
|
|
|
|
|
|
|
# 功能开关
|
|
|
|
def toggle_psd(checked):
|
|
|
|
"""PSD开关"""
|
|
|
|
print(f"PSD功能{'开启' if checked else '关闭'}")
|
|
|
|
|
|
|
|
def toggle_bse(checked):
|
|
|
|
"""BSE开关"""
|
|
|
|
print(f"BSE功能{'开启' if checked else '关闭'}")
|
|
|
|
|
|
|
|
def toggle_key(checked):
|
|
|
|
"""KEY开关"""
|
|
|
|
print(f"KEY功能{'开启' if checked else '关闭'}")
|
|
|
|
|
|
|
|
def toggle_mir(checked):
|
|
|
|
"""MIR开关"""
|
|
|
|
print(f"MIR功能{'开启' if checked else '关闭'}")
|
|
|
|
|
|
|
|
def toggle_ark(checked):
|
|
|
|
"""ARK开关"""
|
|
|
|
print(f"ARK功能{'开启' if checked else '关闭'}")
|
|
|
|
|
|
|
|
def toggle_ctr(checked):
|
|
|
|
"""CTR开关"""
|
|
|
|
print(f"CTR功能{'开启' if checked else '关闭'}")
|
|
|
|
|
|
|
|
# 底部功能按钮
|
|
|
|
def reset_default_expression():
|
|
|
|
"""还原默认表情"""
|
|
|
|
print("还原默认表情功能待实现")
|
|
|
|
|
|
|
|
def find_control_panel():
|
|
|
|
"""查找控制面板"""
|
|
|
|
print("查找控制面板功能待实现")
|
|
|
|
|
|
|
|
def select_related_joints():
|
|
|
|
"""选择关联关节"""
|
|
|
|
print("选择关联关节功能待实现")
|
|
|
|
|
|
|
|
def write_mirror_expression():
|
|
|
|
"""写入镜像表情"""
|
|
|
|
print("写入镜像表情功能待实现")
|
|
|
|
|
|
|
|
def mirror_blendshape(source_bs, target_bs, left_prefix="L_", right_prefix="R_"):
|
|
|
|
"""镜像BlendShape
|
|
|
|
Args:
|
|
|
|
source_bs: 源BlendShape节点
|
|
|
|
target_bs: 目标BlendShape节点
|
|
|
|
left_prefix: 左侧前缀
|
|
|
|
right_prefix: 右侧前缀
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
# 获取源BlendShape的所有目标
|
|
|
|
source_targets = bs_manager.get_all_targets(source_bs)
|
|
|
|
|
|
|
|
for source_target in source_targets:
|
|
|
|
# 获取镜像目标名称
|
|
|
|
if source_target.startswith(left_prefix):
|
|
|
|
mirror_target = source_target.replace(left_prefix, right_prefix)
|
|
|
|
elif source_target.startswith(right_prefix):
|
|
|
|
mirror_target = source_target.replace(right_prefix, left_prefix)
|
|
|
|
else:
|
|
|
|
continue
|
|
|
|
|
|
|
|
# 获取源权重
|
|
|
|
weight = bs_manager.get_weight(source_bs, source_target)
|
|
|
|
|
|
|
|
# 设置镜像权重
|
|
|
|
bs_manager.set_weight(target_bs, mirror_target, weight)
|
|
|
|
|
|
|
|
print(f"BlendShape镜像完成: {source_bs} -> {target_bs}")
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
cmds.warning(f"BlendShape镜像失败: {str(e)}")
|
|
|
|
|
|
|
|
def find_mirror_target(target_name, left_prefix="L_", right_prefix="R_"):
|
|
|
|
"""查找镜像目标
|
|
|
|
Args:
|
|
|
|
target_name: 目标名称
|
|
|
|
left_prefix: 左侧前缀
|
|
|
|
right_prefix: 右侧前缀
|
|
|
|
Returns:
|
|
|
|
str: 镜像目标名称
|
|
|
|
"""
|
|
|
|
if target_name.startswith(left_prefix):
|
|
|
|
return target_name.replace(left_prefix, right_prefix)
|
|
|
|
elif target_name.startswith(right_prefix):
|
|
|
|
return target_name.replace(right_prefix, left_prefix)
|
|
|
|
return ""
|
|
|
|
|
|
|
|
def create_blend_target(base_mesh, target_mesh, target_name):
|
|
|
|
"""创建混合目标
|
|
|
|
Args:
|
|
|
|
base_mesh: 基础模型
|
|
|
|
target_mesh: 目标模型
|
|
|
|
target_name: 目标名称
|
|
|
|
Returns:
|
|
|
|
str: 创建的BlendShape节点名称
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
# 检查模型是否存在
|
|
|
|
if not all(cmds.objExists(obj) for obj in [base_mesh, target_mesh]):
|
|
|
|
raise ValueError("模型不存在")
|
|
|
|
|
|
|
|
# 创建BlendShape节点
|
|
|
|
bs_node = cmds.blendShape(
|
|
|
|
target_mesh,
|
|
|
|
base_mesh,
|
|
|
|
name=f"{base_mesh}_blendShape",
|
|
|
|
frontOfChain=True
|
|
|
|
)[0]
|
|
|
|
|
|
|
|
# 重命名目标
|
|
|
|
target_index = 0
|
|
|
|
cmds.aliasAttr(target_name, f"{bs_node}.weight[{target_index}]")
|
|
|
|
|
|
|
|
return bs_node
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
cmds.warning(f"创建混合目标失败: {str(e)}")
|
|
|
|
return None
|
|
|
|
|
|
|
|
def batch_create_blend_targets(base_mesh, target_meshes, name_prefix=""):
|
|
|
|
"""批量创建混合目标
|
|
|
|
Args:
|
|
|
|
base_mesh: 基础模型
|
|
|
|
target_meshes: 目标模型列表
|
|
|
|
name_prefix: 名称前缀
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
# 创建BlendShape节点
|
|
|
|
bs_node = cmds.blendShape(
|
|
|
|
base_mesh,
|
|
|
|
name=f"{base_mesh}_blendShape",
|
|
|
|
frontOfChain=True
|
|
|
|
)[0]
|
|
|
|
|
|
|
|
# 添加目标
|
|
|
|
for i, target in enumerate(target_meshes):
|
|
|
|
target_name = f"{name_prefix}target_{i+1}"
|
|
|
|
cmds.blendShape(bs_node, edit=True, target=(base_mesh, i, target, 1.0))
|
|
|
|
cmds.aliasAttr(target_name, f"{bs_node}.weight[{i}]")
|
|
|
|
|
|
|
|
print(f"批量创建混合目标完成: {bs_node}")
|
|
|
|
return bs_node
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
cmds.warning(f"批量创建混合目标失败: {str(e)}")
|
|
|
|
return None
|
|
|
|
|
|
|
|
def rebuild_blend_target(bs_node, target_name):
|
|
|
|
"""重建混合目标
|
|
|
|
Args:
|
|
|
|
bs_node: BlendShape节点
|
|
|
|
target_name: 目标名称
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
# 获取基础模型
|
|
|
|
base_mesh = cmds.blendShape(bs_node, q=True, geometry=True)[0]
|
|
|
|
|
|
|
|
# 获取目标索引
|
|
|
|
target_index = -1
|
|
|
|
target_count = cmds.blendShape(bs_node, q=True, weightCount=True)
|
|
|
|
|
|
|
|
for i in range(target_count):
|
|
|
|
alias = cmds.aliasAttr(f"{bs_node}.weight[{i}]", q=True)
|
|
|
|
if alias == target_name:
|
|
|
|
target_index = i
|
|
|
|
break
|
|
|
|
|
|
|
|
if target_index < 0:
|
|
|
|
raise ValueError(f"找不到目标: {target_name}")
|
|
|
|
|
|
|
|
# 提取目标形状
|
|
|
|
target_mesh = cmds.duplicate(base_mesh, name=f"{target_name}_rebuild")[0]
|
|
|
|
cmds.setAttr(f"{bs_node}.weight[{target_index}]", 1)
|
|
|
|
cmds.delete(target_mesh, constructionHistory=True)
|
|
|
|
|
|
|
|
# 重建目标
|
|
|
|
cmds.blendShape(bs_node, edit=True, remove=True, target=(base_mesh, target_index, target_name, 1.0))
|
|
|
|
cmds.blendShape(bs_node, edit=True, target=(base_mesh, target_index, target_mesh, 1.0))
|
|
|
|
|
|
|
|
# 清理
|
|
|
|
cmds.delete(target_mesh)
|
|
|
|
print(f"重建混合目标完成: {target_name}")
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
cmds.warning(f"重建混合目标失败: {str(e)}")
|
|
|
|
|
|
|
|
def blend_selected_targets(bs_node, targets, weight=1.0):
|
|
|
|
"""混合选中的目标
|
|
|
|
Args:
|
|
|
|
bs_node: BlendShape节点
|
|
|
|
targets: 目标名称列表
|
|
|
|
weight: 混合权重
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
# 重置所有权重
|
|
|
|
all_targets = bs_manager.get_all_targets(bs_node)
|
|
|
|
for target in all_targets:
|
|
|
|
bs_manager.set_weight(bs_node, target, 0)
|
|
|
|
|
|
|
|
# 设置选中目标的权重
|
|
|
|
for target in targets:
|
|
|
|
bs_manager.set_weight(bs_node, target, weight)
|
|
|
|
|
|
|
|
print(f"混合目标完成: {targets}")
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
cmds.warning(f"混合目标失败: {str(e)}")
|
|
|
|
|
|
|
|
def create_corrective_blend(base_mesh, pose_mesh, corrective_mesh, pose_attrs):
|
|
|
|
"""创建修正混合变形
|
|
|
|
Args:
|
|
|
|
base_mesh: 基础模型
|
|
|
|
pose_mesh: 姿势模型
|
|
|
|
corrective_mesh: 修正模型
|
|
|
|
pose_attrs: 姿势属性列表 [(attr, value), ...]
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
# 创建BlendShape节点
|
|
|
|
bs_node = cmds.blendShape(
|
|
|
|
[pose_mesh, corrective_mesh],
|
|
|
|
base_mesh,
|
|
|
|
name=f"{base_mesh}_corrective",
|
|
|
|
frontOfChain=True
|
|
|
|
)[0]
|
|
|
|
|
|
|
|
# 设置驱动关系
|
|
|
|
for attr, value in pose_attrs:
|
|
|
|
# 创建条件节点
|
|
|
|
cond = cmds.createNode("condition", name=f"{bs_node}_cond")
|
|
|
|
cmds.setAttr(f"{cond}.operation", 2) # Greater Than
|
|
|
|
cmds.setAttr(f"{cond}.secondTerm", value)
|
|
|
|
|
|
|
|
# 连接属性
|
|
|
|
cmds.connectAttr(attr, f"{cond}.firstTerm")
|
|
|
|
cmds.connectAttr(f"{cond}.outColorR", f"{bs_node}.weight[1]")
|
|
|
|
|
|
|
|
print(f"创建修正混合变形完成: {bs_node}")
|
|
|
|
return bs_node
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
cmds.warning(f"创建修正混合变形失败: {str(e)}")
|
|
|
|
return None
|
|
|
|
|
|
|
|
def create_expression_set(name, targets, base_mesh=None):
|
|
|
|
"""创建表情集
|
|
|
|
Args:
|
|
|
|
name: 表情集名称
|
|
|
|
targets: 目标列表 [(target_name, weight), ...]
|
|
|
|
base_mesh: 基础模型(可选)
|
|
|
|
Returns:
|
|
|
|
str: 创建的表情集节点名称
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
# 创建表情集节点
|
|
|
|
expr_set = cmds.createNode("objectSet", name=f"{name}_exprSet")
|
|
|
|
|
|
|
|
# 添加自定义属性
|
|
|
|
cmds.addAttr(expr_set, ln="weight", at="double", min=0, max=1, dv=0)
|
|
|
|
cmds.setAttr(f"{expr_set}.weight", e=True, keyable=True)
|
|
|
|
|
|
|
|
# 添加目标信息
|
|
|
|
cmds.addAttr(expr_set, ln="targets", dt="string")
|
|
|
|
target_data = ";".join([f"{t}:{w}" for t, w in targets])
|
|
|
|
cmds.setAttr(f"{expr_set}.targets", target_data, type="string")
|
|
|
|
|
|
|
|
# 添加基础模型
|
|
|
|
if base_mesh:
|
|
|
|
cmds.addAttr(expr_set, ln="baseMesh", dt="string")
|
|
|
|
cmds.setAttr(f"{expr_set}.baseMesh", base_mesh, type="string")
|
|
|
|
|
|
|
|
return expr_set
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
cmds.warning(f"创建表情集失败: {str(e)}")
|
|
|
|
return None
|
|
|
|
|
|
|
|
def apply_expression_set(expr_set, weight=None):
|
|
|
|
"""应用表情集
|
|
|
|
Args:
|
|
|
|
expr_set: 表情集节点
|
|
|
|
weight: 权重值(可选)
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
# 获取目标信息
|
|
|
|
target_data = cmds.getAttr(f"{expr_set}.targets")
|
|
|
|
targets = []
|
|
|
|
for item in target_data.split(";"):
|
|
|
|
name, value = item.split(":")
|
|
|
|
targets.append((name, float(value)))
|
|
|
|
|
|
|
|
# 获取权重
|
|
|
|
if weight is None:
|
|
|
|
weight = cmds.getAttr(f"{expr_set}.weight")
|
|
|
|
|
|
|
|
# 应用权重
|
|
|
|
for target_name, target_weight in targets:
|
|
|
|
bs_node = target_name.split(".")[0]
|
|
|
|
bs_manager.set_weight(bs_node, target_name, target_weight * weight)
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
cmds.warning(f"应用表情集失败: {str(e)}")
|
|
|
|
|
|
|
|
def create_mirror_expression(expr_set, left_prefix="L_", right_prefix="R_"):
|
|
|
|
"""创建镜像表情
|
|
|
|
Args:
|
|
|
|
expr_set: 源表情集节点
|
|
|
|
left_prefix: 左侧前缀
|
|
|
|
right_prefix: 右侧前缀
|
|
|
|
Returns:
|
|
|
|
str: 创建的镜像表情集节点名称
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
# 获取源表情信息
|
|
|
|
target_data = cmds.getAttr(f"{expr_set}.targets")
|
|
|
|
source_targets = []
|
|
|
|
for item in target_data.split(";"):
|
|
|
|
name, value = item.split(":")
|
|
|
|
source_targets.append((name, float(value)))
|
|
|
|
|
|
|
|
# 创建镜像目标
|
|
|
|
mirror_targets = []
|
|
|
|
for target_name, weight in source_targets:
|
|
|
|
mirror_name = find_mirror_target(target_name, left_prefix, right_prefix)
|
|
|
|
if mirror_name:
|
|
|
|
mirror_targets.append((mirror_name, weight))
|
|
|
|
|
|
|
|
# 创建镜像表情集
|
|
|
|
name = cmds.getAttr(f"{expr_set}.name")
|
|
|
|
mirror_name = f"{name}_mirror"
|
|
|
|
if cmds.objExists(f"{expr_set}.baseMesh"):
|
|
|
|
base_mesh = cmds.getAttr(f"{expr_set}.baseMesh")
|
|
|
|
else:
|
|
|
|
base_mesh = None
|
|
|
|
|
|
|
|
return create_expression_set(mirror_name, mirror_targets, base_mesh)
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
cmds.warning(f"创建镜像表情失败: {str(e)}")
|
|
|
|
return None
|
|
|
|
|
|
|
|
def optimize_weights(bs_node, threshold=0.001):
|
|
|
|
"""优化权重
|
|
|
|
Args:
|
|
|
|
bs_node: BlendShape节点
|
|
|
|
threshold: 权重阈值
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
# 获取所有目标
|
|
|
|
targets = bs_manager.get_all_targets(bs_node)
|
|
|
|
|
|
|
|
# 检查每个目标的权重
|
|
|
|
for target in targets:
|
|
|
|
weight = bs_manager.get_weight(bs_node, target)
|
|
|
|
|
|
|
|
# 移除小权重
|
|
|
|
if abs(weight) < threshold:
|
|
|
|
bs_manager.set_weight(bs_node, target, 0)
|
|
|
|
|
|
|
|
# 规范化权重
|
|
|
|
elif abs(weight - 1.0) < threshold:
|
|
|
|
bs_manager.set_weight(bs_node, target, 1.0)
|
|
|
|
|
|
|
|
print(f"权重优化完成: {bs_node}")
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
cmds.warning(f"权重优化失败: {str(e)}")
|
|
|
|
|
|
|
|
def create_weight_driver(bs_node, target, driver_attr):
|
|
|
|
"""创建权重驱动器
|
|
|
|
Args:
|
|
|
|
bs_node: BlendShape节点
|
|
|
|
target: 目标名称
|
|
|
|
driver_attr: 驱动属性
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
# 创建条件节点
|
|
|
|
cond = cmds.createNode("condition", name=f"{target}_cond")
|
|
|
|
cmds.setAttr(f"{cond}.operation", 2) # Greater Than
|
|
|
|
cmds.setAttr(f"{cond}.secondTerm", 0)
|
|
|
|
|
|
|
|
# 连接驱动属性
|
|
|
|
cmds.connectAttr(driver_attr, f"{cond}.firstTerm")
|
|
|
|
|
|
|
|
# 获取目标索引
|
|
|
|
target_index = -1
|
|
|
|
target_count = cmds.blendShape(bs_node, q=True, weightCount=True)
|
|
|
|
for i in range(target_count):
|
|
|
|
alias = cmds.aliasAttr(f"{bs_node}.weight[{i}]", q=True)
|
|
|
|
if alias == target:
|
|
|
|
target_index = i
|
|
|
|
break
|
|
|
|
|
|
|
|
if target_index >= 0:
|
|
|
|
# 连接权重
|
|
|
|
cmds.connectAttr(f"{cond}.outColorR", f"{bs_node}.weight[{target_index}]")
|
|
|
|
print(f"创建权重驱动器完成: {target}")
|
|
|
|
return cond
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
cmds.warning(f"创建权重驱动器失败: {str(e)}")
|
|
|
|
return None
|
|
|
|
|
|
|
|
def create_expression_group(name, expressions):
|
|
|
|
"""创建表情组
|
|
|
|
Args:
|
|
|
|
name: 组名称
|
|
|
|
expressions: 表情集列表 [(expr_name, weight), ...]
|
|
|
|
Returns:
|
|
|
|
str: 创建的表情组节点名称
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
# 创建组节点
|
|
|
|
group = cmds.createNode("objectSet", name=f"{name}_exprGroup")
|
|
|
|
|
|
|
|
# 添加表情集信息
|
|
|
|
cmds.addAttr(group, ln="expressions", dt="string")
|
|
|
|
expr_data = ";".join([f"{e}:{w}" for e, w in expressions])
|
|
|
|
cmds.setAttr(f"{group}.expressions", expr_data, type="string")
|
|
|
|
|
|
|
|
return group
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
cmds.warning(f"创建表情组失败: {str(e)}")
|
|
|
|
return None
|
|
|
|
|
|
|
|
def create_expression_controller(name, attributes):
|
|
|
|
"""创建表情控制器
|
|
|
|
Args:
|
|
|
|
name: 控制器名称
|
|
|
|
attributes: 属性列表 [(attr_name, min_val, max_val, default), ...]
|
|
|
|
Returns:
|
|
|
|
str: 创建的控制器名称
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
# 创建控制器
|
|
|
|
ctrl = cmds.circle(name=f"{name}_ctrl", normal=(0, 1, 0))[0]
|
|
|
|
|
|
|
|
# 添加属性
|
|
|
|
for attr_name, min_val, max_val, default in attributes:
|
|
|
|
cmds.addAttr(ctrl, ln=attr_name, at="double",
|
|
|
|
min=min_val, max=max_val, dv=default)
|
|
|
|
cmds.setAttr(f"{ctrl}.{attr_name}", e=True, keyable=True)
|
|
|
|
|
|
|
|
return ctrl
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
cmds.warning(f"创建表情控制器失败: {str(e)}")
|
|
|
|
return None
|
|
|
|
|
|
|
|
def create_expression_driver(source_attr, target_attrs, remap=False):
|
|
|
|
"""创建表情驱动器
|
|
|
|
Args:
|
|
|
|
source_attr: 源属性
|
|
|
|
target_attrs: 目标属性列表 [(attr, value), ...]
|
|
|
|
remap: 是否重映射值
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
for target_attr, target_value in target_attrs:
|
|
|
|
if remap:
|
|
|
|
# 创建重映射节点
|
|
|
|
remap_node = cmds.createNode("remapValue")
|
|
|
|
cmds.connectAttr(source_attr, f"{remap_node}.inputValue")
|
|
|
|
cmds.connectAttr(f"{remap_node}.outValue", target_attr)
|
|
|
|
|
|
|
|
# 设置映射值
|
|
|
|
cmds.setAttr(f"{remap_node}.value[0].value_Position", 0)
|
|
|
|
cmds.setAttr(f"{remap_node}.value[0].value_FloatValue", 0)
|
|
|
|
cmds.setAttr(f"{remap_node}.value[1].value_Position", 1)
|
|
|
|
cmds.setAttr(f"{remap_node}.value[1].value_FloatValue", target_value)
|
|
|
|
else:
|
|
|
|
# 直接连接
|
|
|
|
cmds.connectAttr(source_attr, target_attr)
|
|
|
|
|
|
|
|
print(f"创建表情驱动器完成: {source_attr}")
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
cmds.warning(f"创建表情驱动器失败: {str(e)}")
|
|
|
|
|
|
|
|
def create_expression_pose(name, targets, mirror=False):
|
|
|
|
"""创建表情姿势
|
|
|
|
Args:
|
|
|
|
name: 姿势名称
|
|
|
|
targets: 目标列表 [(node, attr, value), ...]
|
|
|
|
mirror: 是否创建镜像姿势
|
|
|
|
Returns:
|
|
|
|
str: 创建的姿势节点名称
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
# 创建姿势节点
|
|
|
|
pose = cmds.createNode("objectSet", name=f"{name}_pose")
|
|
|
|
|
|
|
|
# 添加目标信息
|
|
|
|
cmds.addAttr(pose, ln="targets", dt="string")
|
|
|
|
target_data = ";".join([f"{n}.{a}:{v}" for n, a, v in targets])
|
|
|
|
cmds.setAttr(f"{pose}.targets", target_data, type="string")
|
|
|
|
|
|
|
|
if mirror:
|
|
|
|
# 创建镜像姿势
|
|
|
|
mirror_targets = []
|
|
|
|
for node, attr, value in targets:
|
|
|
|
if "_L_" in node:
|
|
|
|
mirror_node = node.replace("_L_", "_R_")
|
|
|
|
elif "_R_" in node:
|
|
|
|
mirror_node = node.replace("_R_", "_L_")
|
|
|
|
else:
|
|
|
|
continue
|
|
|
|
mirror_targets.append((mirror_node, attr, value))
|
|
|
|
|
|
|
|
if mirror_targets:
|
|
|
|
create_expression_pose(f"{name}_mirror", mirror_targets)
|
|
|
|
|
|
|
|
return pose
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
cmds.warning(f"创建表情姿势失败: {str(e)}")
|
|
|
|
return None
|
|
|
|
|
|
|
|
def apply_expression_pose(pose, weight=1.0):
|
|
|
|
"""应用表情姿势
|
|
|
|
Args:
|
|
|
|
pose: 姿势节点
|
|
|
|
weight: 应用权重
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
# 获取目标信息
|
|
|
|
target_data = cmds.getAttr(f"{pose}.targets")
|
|
|
|
for item in target_data.split(";"):
|
|
|
|
attr_path, value = item.split(":")
|
|
|
|
node, attr = attr_path.split(".")
|
|
|
|
|
|
|
|
if cmds.objExists(attr_path):
|
|
|
|
current = cmds.getAttr(attr_path)
|
|
|
|
target = float(value)
|
|
|
|
blend = current + (target - current) * weight
|
|
|
|
cmds.setAttr(attr_path, blend)
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
cmds.warning(f"应用表情姿势失败: {str(e)}")
|
|
|
|
|
|
|
|
def create_expression_sequence(name, poses, times):
|
|
|
|
"""创建表情序列
|
|
|
|
Args:
|
|
|
|
name: 序列名称
|
|
|
|
poses: 姿势列表
|
|
|
|
times: 时间列表
|
|
|
|
Returns:
|
|
|
|
str: 创建的序列节点名称
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
# 创建序列节点
|
|
|
|
sequence = cmds.createNode("objectSet", name=f"{name}_sequence")
|
|
|
|
|
|
|
|
# 添加序列信息
|
|
|
|
cmds.addAttr(sequence, ln="poses", dt="string")
|
|
|
|
pose_data = ";".join([f"{p}:{t}" for p, t in zip(poses, times)])
|
|
|
|
cmds.setAttr(f"{sequence}.poses", pose_data, type="string")
|
|
|
|
|
|
|
|
return sequence
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
cmds.warning(f"创建表情序列失败: {str(e)}")
|
|
|
|
return None
|
|
|
|
|
|
|
|
def play_expression_sequence(sequence):
|
|
|
|
"""播放表情序列
|
|
|
|
Args:
|
|
|
|
sequence: 序列节点
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
# 获取序列信息
|
|
|
|
pose_data = cmds.getAttr(f"{sequence}.poses")
|
|
|
|
for item in pose_data.split(";"):
|
|
|
|
pose, time = item.split(":")
|
|
|
|
# 设置时间
|
|
|
|
cmds.currentTime(float(time))
|
|
|
|
# 应用姿势
|
|
|
|
apply_expression_pose(pose)
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
cmds.warning(f"播放表情序列失败: {str(e)}")
|
|
|
|
|
|
|
|
def create_weight_editor(bs_node):
|
|
|
|
"""创建权重编辑器
|
|
|
|
Args:
|
|
|
|
bs_node: BlendShape节点
|
|
|
|
Returns:
|
|
|
|
str: 创建的编辑器节点名称
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
# 创建编辑器节点
|
|
|
|
editor = cmds.createNode("objectSet", name=f"{bs_node}_weightEditor")
|
|
|
|
|
|
|
|
# 添加编辑器信息
|
|
|
|
cmds.addAttr(editor, ln="blendShape", dt="string")
|
|
|
|
cmds.setAttr(f"{editor}.blendShape", bs_node, type="string")
|
|
|
|
|
|
|
|
# 获取所有目标
|
|
|
|
targets = bs_manager.get_all_targets(bs_node)
|
|
|
|
|
|
|
|
# 添加目标权重属性
|
|
|
|
for target in targets:
|
|
|
|
cmds.addAttr(editor, ln=target, at="double", min=0, max=1, dv=0)
|
|
|
|
cmds.setAttr(f"{editor}.{target}", e=True, keyable=True)
|
|
|
|
|
|
|
|
# 连接权重
|
|
|
|
weight = bs_manager.get_weight(bs_node, target)
|
|
|
|
cmds.setAttr(f"{editor}.{target}", weight)
|
|
|
|
|
|
|
|
return editor
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
cmds.warning(f"创建权重编辑器失败: {str(e)}")
|
|
|
|
return None
|
|
|
|
|
|
|
|
def create_expression_preview(expr_set):
|
|
|
|
"""创建表情预览
|
|
|
|
Args:
|
|
|
|
expr_set: 表情集节点
|
|
|
|
Returns:
|
|
|
|
str: 创建的预览节点名称
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
# 创建预览节点
|
|
|
|
preview = cmds.createNode("objectSet", name=f"{expr_set}_preview")
|
|
|
|
|
|
|
|
# 添加预览信息
|
|
|
|
cmds.addAttr(preview, ln="expression", dt="string")
|
|
|
|
cmds.setAttr(f"{preview}.expression", expr_set, type="string")
|
|
|
|
|
|
|
|
# 添加预览控制
|
|
|
|
cmds.addAttr(preview, ln="weight", at="double", min=0, max=1, dv=0)
|
|
|
|
cmds.setAttr(f"{preview}.weight", e=True, keyable=True)
|
|
|
|
|
|
|
|
# 添加时间控制
|
|
|
|
cmds.addAttr(preview, ln="time", at="time")
|
|
|
|
cmds.setAttr(f"{preview}.time", e=True, keyable=True)
|
|
|
|
|
|
|
|
# 添加播放控制
|
|
|
|
cmds.addAttr(preview, ln="play", at="bool")
|
|
|
|
cmds.setAttr(f"{preview}.play", e=True, keyable=True)
|
|
|
|
|
|
|
|
return preview
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
cmds.warning(f"创建表情预览失败: {str(e)}")
|
|
|
|
return None
|
|
|
|
|
|
|
|
def update_weight_editor(editor):
|
|
|
|
"""更新权重编辑器
|
|
|
|
Args:
|
|
|
|
editor: 编辑器节点
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
# 获取BlendShape节点
|
|
|
|
bs_node = cmds.getAttr(f"{editor}.blendShape")
|
|
|
|
|
|
|
|
# 获取所有目标
|
|
|
|
targets = bs_manager.get_all_targets(bs_node)
|
|
|
|
|
|
|
|
# 更新权重
|
|
|
|
for target in targets:
|
|
|
|
if cmds.objExists(f"{editor}.{target}"):
|
|
|
|
weight = bs_manager.get_weight(bs_node, target)
|
|
|
|
cmds.setAttr(f"{editor}.{target}", weight)
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
cmds.warning(f"更新权重编辑器失败: {str(e)}")
|
|
|
|
|
|
|
|
def update_expression_preview(preview):
|
|
|
|
"""更新表情预览
|
|
|
|
Args:
|
|
|
|
preview: 预览节点
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
# 获取表情集
|
|
|
|
expr_set = cmds.getAttr(f"{preview}.expression")
|
|
|
|
|
|
|
|
# 获取预览权重
|
|
|
|
weight = cmds.getAttr(f"{preview}.weight")
|
|
|
|
|
|
|
|
# 应用表情
|
|
|
|
apply_expression_set(expr_set, weight)
|
|
|
|
|
|
|
|
# 检查播放状态
|
|
|
|
if cmds.getAttr(f"{preview}.play"):
|
|
|
|
# 更新时间
|
|
|
|
current_time = cmds.getAttr(f"{preview}.time")
|
|
|
|
cmds.setAttr(f"{preview}.time", current_time + 1)
|
|
|
|
|
|
|
|
# 循环播放
|
|
|
|
if current_time >= cmds.playbackOptions(q=True, maxTime=True):
|
|
|
|
cmds.setAttr(f"{preview}.time", cmds.playbackOptions(q=True, minTime=True))
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
cmds.warning(f"更新表情预览失败: {str(e)}")
|
|
|
|
|
|
|
|
def create_weight_curve(bs_node, target):
|
|
|
|
"""创建权重曲线
|
|
|
|
Args:
|
|
|
|
bs_node: BlendShape节点
|
|
|
|
target: 目标名称
|
|
|
|
Returns:
|
|
|
|
str: 创建的动画曲线节点名称
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
# 创建动画曲线
|
|
|
|
curve = cmds.createNode("animCurve", name=f"{target}_weightCurve")
|
|
|
|
|
|
|
|
# 设置曲线类型
|
|
|
|
cmds.setAttr(f"{curve}.preInfinity", 1) # Cycle
|
|
|
|
cmds.setAttr(f"{curve}.postInfinity", 1) # Cycle
|
|
|
|
|
|
|
|
# 添加关键帧
|
|
|
|
cmds.setKeyframe(curve, time=0, value=0)
|
|
|
|
cmds.setKeyframe(curve, time=12, value=1)
|
|
|
|
cmds.setKeyframe(curve, time=24, value=0)
|
|
|
|
|
|
|
|
# 设置切线类型
|
|
|
|
cmds.keyTangent(curve, edit=True, time=(0,24), outTangentType="linear")
|
|
|
|
cmds.keyTangent(curve, edit=True, time=(0,24), inTangentType="linear")
|
|
|
|
|
|
|
|
return curve
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
cmds.warning(f"创建权重曲线失败: {str(e)}")
|
|
|
|
return None
|
|
|
|
|
|
|
|
def apply_weight_curve(curve, bs_node, target):
|
|
|
|
"""应用权重曲线
|
|
|
|
Args:
|
|
|
|
curve: 动画曲线节点
|
|
|
|
bs_node: BlendShape节点
|
|
|
|
target: 目标名称
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
# 获取当前时间
|
|
|
|
current_time = cmds.currentTime(q=True)
|
|
|
|
|
|
|
|
# 获取曲线值
|
|
|
|
weight = cmds.getValue(curve, time=current_time)
|
|
|
|
|
|
|
|
# 设置权重
|
|
|
|
bs_manager.set_weight(bs_node, target, weight)
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
cmds.warning(f"应用权重曲线失败: {str(e)}")
|
|
|
|
|
|
|
|
def create_expression_combination(name, expressions, weights=None):
|
|
|
|
"""创建表情组合
|
|
|
|
Args:
|
|
|
|
name: 组合名称
|
|
|
|
expressions: 表情集列表
|
|
|
|
weights: 权重列表(可选)
|
|
|
|
Returns:
|
|
|
|
str: 创建的组合节点名称
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
# 创建组合节点
|
|
|
|
combo = cmds.createNode("objectSet", name=f"{name}_exprCombo")
|
|
|
|
|
|
|
|
# 添加组合信息
|
|
|
|
cmds.addAttr(combo, ln="expressions", dt="string")
|
|
|
|
if weights:
|
|
|
|
expr_data = ";".join([f"{e}:{w}" for e, w in zip(expressions, weights)])
|
|
|
|
else:
|
|
|
|
expr_data = ";".join([f"{e}:1.0" for e in expressions])
|
|
|
|
cmds.setAttr(f"{combo}.expressions", expr_data, type="string")
|
|
|
|
|
|
|
|
# 添加总权重控制
|
|
|
|
cmds.addAttr(combo, ln="weight", at="double", min=0, max=1, dv=1)
|
|
|
|
cmds.setAttr(f"{combo}.weight", e=True, keyable=True)
|
|
|
|
|
|
|
|
return combo
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
cmds.warning(f"创建表情组合失败: {str(e)}")
|
|
|
|
return None
|
|
|
|
|
|
|
|
def apply_expression_combination(combo, weight=None):
|
|
|
|
"""应用表情组合
|
|
|
|
Args:
|
|
|
|
combo: 组合节点
|
|
|
|
weight: 总权重(可选)
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
# 获取组合信息
|
|
|
|
expr_data = cmds.getAttr(f"{combo}.expressions")
|
|
|
|
|
|
|
|
# 获取总权重
|
|
|
|
if weight is None:
|
|
|
|
weight = cmds.getAttr(f"{combo}.weight")
|
|
|
|
|
|
|
|
# 应用每个表情
|
|
|
|
for item in expr_data.split(";"):
|
|
|
|
expr_set, expr_weight = item.split(":")
|
|
|
|
apply_expression_set(expr_set, float(expr_weight) * weight)
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
cmds.warning(f"应用表情组合失败: {str(e)}")
|
|
|
|
|
|
|
|
def optimize_blendshape_weights(bs_node, options=None):
|
|
|
|
"""优化BlendShape权重
|
|
|
|
Args:
|
|
|
|
bs_node: BlendShape节点
|
|
|
|
options: 优化选项字典
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
if options is None:
|
|
|
|
options = {
|
|
|
|
"threshold": 0.001, # 权重阈值
|
|
|
|
"normalize": True, # 是否规范化
|
|
|
|
"remove_unused": True, # 是否移除未使用的目标
|
|
|
|
"clean_history": True # 是否清理历史
|
|
|
|
}
|
|
|
|
|
|
|
|
# 获取所有目标
|
|
|
|
targets = bs_manager.get_all_targets(bs_node)
|
|
|
|
|
|
|
|
# 移除未使用的目标
|
|
|
|
if options["remove_unused"]:
|
|
|
|
for target in targets:
|
|
|
|
weight = bs_manager.get_weight(bs_node, target)
|
|
|
|
if abs(weight) < options["threshold"]:
|
|
|
|
# 获取目标索引
|
|
|
|
target_index = -1
|
|
|
|
target_count = cmds.blendShape(bs_node, q=True, weightCount=True)
|
|
|
|
for i in range(target_count):
|
|
|
|
alias = cmds.aliasAttr(f"{bs_node}.weight[{i}]", q=True)
|
|
|
|
if alias == target:
|
|
|
|
target_index = i
|
|
|
|
break
|
|
|
|
|
|
|
|
if target_index >= 0:
|
|
|
|
# 移除目标
|
|
|
|
base_mesh = cmds.blendShape(bs_node, q=True, geometry=True)[0]
|
|
|
|
cmds.blendShape(bs_node, edit=True, remove=True,
|
|
|
|
target=(base_mesh, target_index, target, 1.0))
|
|
|
|
|
|
|
|
# 规范化权重
|
|
|
|
if options["normalize"]:
|
|
|
|
for target in targets:
|
|
|
|
weight = bs_manager.get_weight(bs_node, target)
|
|
|
|
if abs(weight) > options["threshold"]:
|
|
|
|
if abs(weight - 1.0) < options["threshold"]:
|
|
|
|
bs_manager.set_weight(bs_node, target, 1.0)
|
|
|
|
elif abs(weight) < options["threshold"]:
|
|
|
|
bs_manager.set_weight(bs_node, target, 0.0)
|
|
|
|
else:
|
|
|
|
normalized = round(weight, 3)
|
|
|
|
bs_manager.set_weight(bs_node, target, normalized)
|
|
|
|
|
|
|
|
# 清理历史
|
|
|
|
if options["clean_history"]:
|
|
|
|
base_mesh = cmds.blendShape(bs_node, q=True, geometry=True)[0]
|
|
|
|
cmds.delete(base_mesh, constructionHistory=True)
|
|
|
|
|
|
|
|
print(f"权重优化完成: {bs_node}")
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
cmds.warning(f"权重优化失败: {str(e)}")
|
|
|
|
|
|
|
|
def analyze_blendshape_weights(bs_node):
|
|
|
|
"""分析BlendShape权重
|
|
|
|
Args:
|
|
|
|
bs_node: BlendShape节点
|
|
|
|
Returns:
|
|
|
|
dict: 分析结果
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
results = {
|
|
|
|
"total_targets": 0,
|
|
|
|
"active_targets": 0,
|
|
|
|
"unused_targets": 0,
|
|
|
|
"full_weight_targets": 0,
|
|
|
|
"partial_weight_targets": 0,
|
|
|
|
"weight_distribution": {}
|
|
|
|
}
|
|
|
|
|
|
|
|
# 获取所有目标
|
|
|
|
targets = bs_manager.get_all_targets(bs_node)
|
|
|
|
results["total_targets"] = len(targets)
|
|
|
|
|
|
|
|
# 分析每个目标
|
|
|
|
for target in targets:
|
|
|
|
weight = bs_manager.get_weight(bs_node, target)
|
|
|
|
|
|
|
|
# 统计权重分布
|
|
|
|
weight_range = round(weight * 10) / 10 # 取一位小数
|
|
|
|
if weight_range in results["weight_distribution"]:
|
|
|
|
results["weight_distribution"][weight_range] += 1
|
|
|
|
else:
|
|
|
|
results["weight_distribution"][weight_range] = 1
|
|
|
|
|
|
|
|
# 统计目标类型
|
|
|
|
if abs(weight) < 0.001:
|
|
|
|
results["unused_targets"] += 1
|
|
|
|
elif abs(weight - 1.0) < 0.001:
|
|
|
|
results["full_weight_targets"] += 1
|
|
|
|
results["active_targets"] += 1
|
|
|
|
elif abs(weight) > 0.001:
|
|
|
|
results["partial_weight_targets"] += 1
|
|
|
|
results["active_targets"] += 1
|
|
|
|
|
|
|
|
return results
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
cmds.warning(f"分析权重失败: {str(e)}")
|
|
|
|
return None
|