├── .gitattributes ├── README.md └── ShapeKeyRetargeting.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # blender_ShapeKeyRetargeting 2 | 通过表面形变修改器来在两个网格之间批量重定向形态键 3 | -------------------------------------------------------------------------------- /ShapeKeyRetargeting.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation; either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, but 7 | # WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 9 | # General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | 14 | bl_info = { 15 | "name" : "Shape Key Retarget Tool", 16 | "author" : "Kumopult", 17 | "description" : "使用表面形变(Surface Deform)修改器来批量重定向形态键", 18 | "blender" : (2, 93, 0), 19 | "version" : (0, 0, 1), 20 | "location" : "View 3D > Toolshelf", 21 | "warning" : "因为作者很懒所以没写英文教学!", 22 | "category" : "Generic" 23 | # VScode调试:Ctrl + Shift + P 24 | } 25 | 26 | import bpy 27 | from bpy import context 28 | 29 | class SKR_PT_Panel(bpy.types.Panel): 30 | bl_space_type = "VIEW_3D" 31 | bl_region_type = "UI" 32 | bl_category = "ShapeKeyRetarget" 33 | bl_label = "Shape Key Retarget Tool" 34 | 35 | def draw(self, context): 36 | layout = self.layout 37 | 38 | if context.object != None: 39 | s: SKR_State = bpy.context.scene.kumopult_skr 40 | 41 | split = layout.row().split(factor=0.25) 42 | split.column().label(text='重定向目标:') 43 | split.column().label(text=context.object.name, icon='OUTLINER_DATA_MESH') 44 | target_row = layout.row() 45 | target_row.prop(s, 'target', text='形态键来源', icon='OUTLINER_DATA_MESH') 46 | target_row.alert = s.target_valid() 47 | layout.template_list('SKR_UL_Keys', '', s, 'retargeted_keys', s, 'active_key') 48 | layout.operator(SKR_OT_Retarget.bl_idname, text='重定向形态键') 49 | else: 50 | layout.label(text='未选中对象', icon='ERROR') 51 | 52 | class SKR_UL_Keys(bpy.types.UIList): 53 | def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index, flt_flag): 54 | owner_keys = bpy.context.object.data.shape_keys 55 | if owner_keys and owner_keys.key_blocks.get(item.name): 56 | layout.prop(item, 'valid', text=item.name, icon="FILE_REFRESH", expand=True) 57 | else: 58 | layout.prop(item, 'valid', text=item.name, icon="SHAPEKEY_DATA", expand=True) 59 | 60 | class SKR_Key(bpy.types.PropertyGroup): 61 | name: bpy.props.StringProperty() 62 | valid: bpy.props.BoolProperty(name="传递此形态键") 63 | 64 | class SKR_State(bpy.types.PropertyGroup): 65 | owner = property(lambda self: bpy.context.object) 66 | target: bpy.props.PointerProperty( 67 | type=bpy.types.Object, 68 | poll=lambda self, obj: obj.type == 'MESH' and obj.data.shape_keys, 69 | update=lambda self, ctx: self.update_target() 70 | ) 71 | 72 | retargeted_keys: bpy.props.CollectionProperty(type=SKR_Key) 73 | active_key: bpy.props.IntProperty() 74 | 75 | def owner_valid(self): 76 | return self.target.type == 'MESH' 77 | 78 | def target_valid(self): 79 | return self.target.type == 'MESH' and self.target != self.owner and self.target.data.shape_keys != None 80 | 81 | def update_target(self): 82 | self.retargeted_keys.clear() 83 | blocks = self.target.data.shape_keys.key_blocks 84 | for i in range(1, len(blocks)): 85 | k = self.retargeted_keys.add() 86 | k.name = blocks[i].name 87 | k.valid = True 88 | 89 | class SKR_OT_Retarget(bpy.types.Operator): 90 | bl_idname = 'kumopult_skr.retarget' 91 | bl_label = '形态键重定向' 92 | bl_description = '' 93 | 94 | @classmethod 95 | def poll(cls, context): 96 | s: SKR_State = context.scene.kumopult_skr 97 | return s.target_valid() and s.owner_valid() 98 | 99 | def execute(self, context): 100 | s: SKR_State = context.scene.kumopult_skr 101 | 102 | # 若已存在表面形变修改器,则直接获取,否则新建 103 | sd: bpy.types.SurfaceDeformModifier = None 104 | for modifier in s.owner.modifiers: 105 | if modifier.type == "SURFACE_DEFORM": 106 | sd = modifier 107 | sd_flag = False 108 | if not sd: 109 | sd = s.owner.modifiers.new(type="SURFACE_DEFORM", name="SurfaceDeform") 110 | sd.target = s.target 111 | bpy.ops.object.surfacedeform_bind(modifier="SurfaceDeform") 112 | else: 113 | # 已有手动添加的表面形变修改器的情况下,插件并不希望在重定向完成后删除辛苦绑好的修改器 114 | # 因此需要记录一下使用的修改器是脚本添加的还是手动添加的,如果是后者,则会在完成后将修改器效果关闭作为代替 115 | sd.show_viewport = True 116 | sd_flag = True 117 | 118 | owner_blocks = None 119 | if s.owner.data.shape_keys: 120 | owner_blocks = s.owner.data.shape_keys.key_blocks 121 | target_blocks = s.target.data.shape_keys.key_blocks 122 | 123 | # 归零所有目标形态键 124 | for target_block in target_blocks: 125 | target_block.value = 0 126 | 127 | for k in s.retargeted_keys: 128 | if not k.valid: 129 | continue 130 | 131 | owner_index = -1 132 | if owner_blocks: 133 | owner_index = owner_blocks.find(k.name) 134 | if owner_index > 0: 135 | s.owner.active_shape_key_index = owner_index 136 | bpy.ops.object.shape_key_remove(all=False) 137 | 138 | target_block: bpy.types.ShapeKey = target_blocks.get(k.name) 139 | target_block.value = 1 140 | sd.name = k.name # 将修改器名称修改后,应用为修改器时生成的形态键就会是修改器的名字 141 | bpy.ops.object.modifier_apply_as_shapekey(keep_modifier=True, modifier=k.name) 142 | target_block.value = 0 143 | 144 | if owner_index > 0: 145 | s.owner.active_shape_key_index = len(owner_blocks) - 1 146 | while s.owner.active_shape_key_index != owner_index: 147 | bpy.ops.object.shape_key_move(type='UP') 148 | 149 | if sd_flag: 150 | sd.name = "SurfaceDeform" # 完事后把名字改回来 151 | sd.show_viewport = False 152 | else: 153 | s.owner.modifiers.remove(sd) 154 | 155 | return {'FINISHED'} 156 | 157 | classes = ( 158 | SKR_PT_Panel, 159 | SKR_UL_Keys, 160 | SKR_Key, 161 | SKR_State, 162 | SKR_OT_Retarget, 163 | ) 164 | 165 | def register(): 166 | for cls in classes: 167 | bpy.utils.register_class(cls) 168 | bpy.types.Scene.kumopult_skr = bpy.props.PointerProperty(type=SKR_State) 169 | print("hello kumopult!") 170 | 171 | def unregister(): 172 | for cls in classes: 173 | bpy.utils.unregister_class(cls) 174 | del bpy.types.Scene.kumopult_skr 175 | print("goodbye kumopult!") 176 | --------------------------------------------------------------------------------