476 lines
14 KiB
Python
476 lines
14 KiB
Python
|
#!/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
|