#!/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