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 |