MetaFusion/scripts/utils/CloneBlendShape.py

476 lines
14 KiB
Python
Raw Permalink Normal View History

2025-02-07 05:10:30 +08:00
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import maya.cmds as cmds
import maya.mel as mel
from Core import GetBlendShapeNodes, GetWrapNodes, FilterSingleBlendShapeDeformerTargets
def wrap_convert_blend():
"""
将选中的模型的混合变形转换为新的混合变形
"""
# 获取选中的对象
sel = cmds.ls(selection=True)
if len(sel) != 2:
cmds.warning("请选择两个模型对象")
return
# 获取混合变形节点
bs_nodes = GetBlendShapeNodes(sel[1])
if not bs_nodes:
cmds.warning("目标模型不包含混合变形,请确保选择正确的源和目标模型")
return
# 获取混合变形目标列表
bs_target_attrs = cmds.listAttr(f"{bs_nodes[0]}.w", multi=True) or []
if not bs_target_attrs:
cmds.warning("混合变形目标列表为空")
return
# 如果选择了wrap方法
if cmds.checkBox("wrapMethod", query=True, value=True):
cmds.select(sel[0], sel[1])
mel.eval('CreateWrap')
# 创建临时组
if cmds.objExists("WrapConvertTemp"):
cmds.delete("WrapConvertTemp")
temp_group = cmds.group(empty=True, name="WrapConvertTemp")
rb_mesh = []
# 复制每个混合变形目标
for bs_attr in bs_target_attrs:
bs_weight = cmds.getAttr(f"{bs_nodes[0]}.{bs_attr}")
is_connected = cmds.connectionInfo(f"{bs_nodes[0]}.{bs_attr}", isDestination=True)
if bs_weight != 1 and not is_connected:
# 设置权重为1并复制模型
cmds.setAttr(f"{bs_nodes[0]}.{bs_attr}", 1)
copy_mesh = cmds.duplicate(sel[0])[0]
cmds.parent(copy_mesh, temp_group)
copy_mesh = cmds.rename(copy_mesh, bs_attr)
rb_mesh.append(f":WrapConvertTemp|:{bs_attr}")
cmds.setAttr(f"{bs_nodes[0]}.{bs_attr}", 0)
# 处理wrap节点
if cmds.checkBox("wrapMethod", query=True, value=True):
wrap_nodes = GetWrapNodes(sel[0])
base_nodes = cmds.ls(f"{sel[1]}Bas*")
if wrap_nodes or base_nodes:
cmds.delete(wrap_nodes + base_nodes)
# 创建新的混合变形
if rb_mesh:
cmds.blendShape(rb_mesh, sel[0], frontOfChain=True)
# 处理DeltaMush
if cmds.objExists('deltaMush') and cmds.checkBox("deltaMushTarget", query=True, value=True):
delta_value = cmds.intSliderGrp("deltaIntensity", query=True, value=True)
_process_delta_mush(sel[0], delta_value)
# 清理
cmds.delete("WrapConvertTemp")
print("混合变形处理完成")
def _process_delta_mush(target, delta_value):
"""
处理DeltaMush相关的操作
参数:
target (str): 目标模型
delta_value (int): DeltaMush强度值
"""
# 创建DeltaMush变形器
dl_names = cmds.deltaMush(
target,
smoothingIterations=delta_value,
smoothingStep=1.0,
pinBorderVertices=1,
envelope=1
)
# 获取混合变形节点和目标
bs_nodes = GetBlendShapeNodes(target)
bs_target_attrs = cmds.listAttr(f"{bs_nodes[0]}.w", multi=True) or []
# 创建临时组
if cmds.objExists("WrapConvertTemp"):
cmds.delete("WrapConvertTemp")
temp_group = cmds.group(empty=True, name="WrapConvertTemp")
rb_mesh = []
# 处理每个混合变形目标
for bs_attr in bs_target_attrs:
bs_weight = cmds.getAttr(f"{bs_nodes[0]}.{bs_attr}")
if bs_weight != 1:
# 设置权重并创建关键帧
cmds.setAttr(f"{bs_nodes[0]}.{bs_attr}", 0)
cmds.currentTime(-10)
cmds.setAttr(f"{bs_nodes[0]}.{bs_attr}", 1)
cmds.currentTime(0)
# 复制模型并重命名
copy_mesh = cmds.duplicate(target)[0]
cmds.parent(copy_mesh, temp_group)
copy_mesh = cmds.rename(copy_mesh, bs_attr)
rb_mesh.append(f":WrapConvertTemp|:{bs_attr}")
# 重置权重
cmds.setAttr(f"{bs_nodes[0]}.{bs_attr}", 0)
cmds.currentTime(-10)
cmds.currentTime(0)
# 清理并创建新的混合变形
cmds.delete(dl_names + bs_nodes)
if rb_mesh:
cmds.blendShape(rb_mesh, target, frontOfChain=True)
cmds.delete("WrapConvertTemp")
print("DeltaMush优化完成")
def clone_blend_shape_ui():
"""
创建混合变形克隆工具的UI界面
"""
window_name = "wrapChangeBS"
if cmds.window(window_name, exists=True):
cmds.deleteUI(window_name)
# 创建主窗口
window = cmds.window(
window_name,
title="BlendShapeBake",
width=300,
height=300,
menuBar=True
)
# 创建主布局
main_layout = cmds.columnLayout(
adjustableColumn=1,
columnAttach=["both", 5],
rowSpacing=2,
columnWidth=150
)
cmds.separator(height=10, style="in")
# 创建Wrap方法选项
cmds.checkBox(
"wrapMethod",
label="Use Wrap Method",
value=1,
changeCommand="sparkly_wrap_convert_blend_check_return"
)
# 创建DeltaMush选项如果可用
if cmds.pluginInfo("deltaMush", query=True, loaded=True):
cmds.checkBox(
"deltaMushTarget",
label="DeltaMushTarget",
value=0,
changeCommand="sparkly_wrap_convert_blend_check_return"
)
cmds.intSliderGrp(
"deltaIntensity",
field=True,
minValue=0,
maxValue=100,
value=10,
width=50
)
else:
cmds.text(
label="低版本DeltaMush不可用",
font="boldLabelFont"
)
cmds.separator(height=10, style="in")
# 创建功能按钮
cmds.button(label="Cloning", command="wrap_convert_blend()")
cmds.button(label="Copy", command="sg_copy_blend()")
cmds.button(label="Mirror", command="mirror_blend()")
cmds.separator(height=10, style="in")
# 调整窗口大小
cmds.window(
window_name,
edit=True,
width=260,
height=85
)
cmds.showWindow()
def sparkly_wrap_convert_blend_check_return():
"""
检查框回调函数保持为空以保持兼容性
"""
pass
def sg_copy_blend():
"""
复制混合变形目标
"""
# 获取选中的混合变形目标
sel_bs_name_list = mel.eval('getShapeEditorTreeviewSelection(14)')
# 解析目标索引
tmp_a = sel_bs_name_list[0].split('.')
tmp_b = sel_bs_name_list[1].split('.')
target_a = int(tmp_a[1])
target_b = int(tmp_b[1])
# 重新生成目标并设置连接
mesh_a = cmds.sculptTarget(
tmp_a[0],
edit=True,
regenerate=True,
target=target_a
)
mesh_shape_a = cmds.listRelatives(mesh_a[0], shapes=True)
# 断开原有连接并建立新连接
cmds.disconnectAttr(
f"{mesh_shape_a[0]}.worldMesh[0]",
f"{tmp_a[0]}.inputTarget[0].inputTargetGroup[{target_a}].inputTargetItem[6000].inputGeomTarget"
)
cmds.connectAttr(
f"{mesh_shape_a[0]}.worldMesh[0]",
f"{tmp_a[0]}.inputTarget[0].inputTargetGroup[{target_b}].inputTargetItem[6000].inputGeomTarget",
force=True
)
# 删除临时网格
cmds.delete(mesh_a)
def mirror_blend():
"""
镜像混合变形目标
"""
# 获取当前对称设置
original_symmetry = cmds.symmetricModelling(query=True, symmetry=True)
symmetry_space = None
symmetry_axis = None
if original_symmetry:
symmetry_space = cmds.symmetricModelling(query=True, about=True)
if symmetry_space == "topo":
symmetry_axis = mel.eval('blendShapeGetTopoSymmetryEdge()')
else:
symmetry_axis = cmds.symmetricModelling(query=True, axis=True)
# 获取选中的混合变形目标
sel_bs_name_list = mel.eval('getShapeEditorTreeviewSelection(14)')
# 解析目标索引
tmp_a = sel_bs_name_list[0].split('.')
tmp_b = sel_bs_name_list[1].split('.')
target_a = int(tmp_a[1])
target_b = int(tmp_b[1])
# 重新生成目标并设置连接
mesh_a = cmds.sculptTarget(
tmp_a[0],
edit=True,
regenerate=True,
target=target_a
)
mesh_shape_a = cmds.listRelatives(mesh_a[0], shapes=True)
# 断开原有连接并建立新连接
cmds.disconnectAttr(
f"{mesh_shape_a[0]}.worldMesh[0]",
f"{tmp_a[0]}.inputTarget[0].inputTargetGroup[{target_a}].inputTargetItem[6000].inputGeomTarget"
)
cmds.connectAttr(
f"{mesh_shape_a[0]}.worldMesh[0]",
f"{tmp_a[0]}.inputTarget[0].inputTargetGroup[{target_b}].inputTargetItem[6000].inputGeomTarget",
force=True
)
# 删除临时网格
cmds.delete(mesh_a)
# 翻转目标
cmds.blendShape(
tmp_b[0],
edit=True,
flipTarget=True,
geometryIndex=0,
targetIndex=target_b,
symmetryAxis="x",
symmetrySpace=1
)
# 重置反射模式
mel.eval('reflectionSetMode none')
# 恢复对称设置
if not original_symmetry or not symmetry_axis:
mel.eval('symmetricModelling -s 0')
elif symmetry_space == "topo":
mel.eval(f'symmetricModelling -e -about {symmetry_space} -s 1 {symmetry_axis}')
else:
mel.eval(f'symmetricModelling -e -about {symmetry_space} -axis {symmetry_axis} -s 1')
def get_shape_editor_treeview_selection(scope):
"""
获取形状编辑器树视图中的选择
参数:
scope (int): 选择范围:
0-8: 简单范围
9: 单个BSD中的目标
10-16: 纯类型选择
20: 最后选择的项目
21,24: 纯类型及其组类型
30: 引用项目检查
31: 第一个选择项
返回:
list: 根据范围返回的字符串数组
"""
# 定义类型上限
type_roof = 6 # 注意:添加新树项类型时更新上限
# 检查选择变量是否存在
if not cmds.optionVar(exists="blendShapeEditorTreeViewSelection"):
return []
# 获取选择数据
ov = cmds.optionVar(query="blendShapeEditorTreeViewSelection")
# 处理不同范围的选择
if -1 < scope < type_roof + 3: # 范围 0-8
return ov[scope].split("/") if ov[scope] else []
if scope == 9: # 范围 9
return filter_single_bsd(ov[8].split("/") if ov[8] else [])
if 9 < scope < type_roof + 11: # 范围 10-16
selection = ov[scope-10].split("/") if ov[scope-10] else []
if not selection:
return []
# 跳过相关类型检查
type_array = list(range(type_roof))
new_type_array = [x for x in type_array if x not in [scope-10, 2, 5]]
if scope in [12, 15]:
new_type_array = [x for x in new_type_array if x != scope-12]
# 检查混合选择
for i in new_type_array:
if ov[i] and ov[i].split("/"):
return []
return selection
if scope == 20: # 范围 20
return ov[9].split("/") if ov[9] else []
if scope in [21, 24]: # 范围 21,24
selection_out = ov[scope-20].split("/") if ov[scope-20] else []
selection_in = ov[scope-19].split("/") if ov[scope-19] else []
selection = list(set(selection_out + selection_in))
if not selection:
return []
# 跳过相关类型检查
type_array = list(range(type_roof))
new_type_array = [x for x in type_array if x not in [scope-20, scope-19, scope-21]]
# 检查混合选择
for i in new_type_array:
if ov[i] and ov[i].split("/"):
return []
return selection
if scope == 30: # 范围 30
return ov[10].split("/") if ov[10] else []
if scope == 31: # 范围 31
return ov[11].split("/") if ov[11] else []
return [] # 无效范围
def filter_single_bsd(items):
"""
从单个混合变形变形器中返回选定的目标
参数:
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 is_shape_editor_reference_item_selected():
"""
检查是否在形状编辑器树视图中选择了引用项
返回:
bool: 如果选择了引用项则返回True否则返回False
"""
result = get_shape_editor_treeview_selection(30)
if len(result) == 1:
return result[0] == "1"
return False
def blend_shape_editor_selected_ancestor(bsd_name, tgt_index, selected_targets):
"""
获取选定的祖先如果存在
参数:
bsd_name (str): 混合变形变形器名称
tgt_index (int): 目标索引>=0为目标<0为目标目录
selected_targets (list): 选定的目标列表
返回:
int: 最高选定的父目录索引如果没有选定的祖先则返回0
"""
highest_parent_directory = 0
parent_dir = -1
if tgt_index >= 0:
# 这是一个目标,获取父目录
temp_attr = f"{bsd_name}.parentDirectory[{tgt_index}]"
parent_dir = cmds.getAttr(temp_attr)
if parent_dir <= -1:
return 0 # 默认值
if parent_dir == 0:
return 0 # 目标项直接在混合变形节点下
else:
# 这是一个目标目录,获取父目录
temp_attr = f"{bsd_name}.targetDirectory[{-tgt_index}].parentIndex"
parent_dir = cmds.getAttr(temp_attr)
while parent_dir > 0:
# 检查此目录是否被选中
dir_name_encoded = f"{bsd_name}.{-parent_dir}"
if dir_name_encoded in selected_targets:
highest_parent_directory = parent_dir
# 获取父目录的父目录
temp_attr = f"{bsd_name}.targetDirectory[{parent_dir}].parentIndex"
parent_dir = cmds.getAttr(temp_attr)
return highest_parent_directory