1 # SPDX-License-Identifier: GPL-2.0-or-later
4 "name": "Corrective Shape Keys",
5 "author": "Ivo Grigull (loolarge), Tal Trachtman", "Tokikake"
8 "location": "Object Data > Shape Keys Specials or Search",
9 "description": "Creates a corrective shape key for the current pose",
10 "doc_url": "{BLENDER_MANUAL_URL}/addons/animation/corrective_shape_keys.html",
11 "category": "Animation",
15 This script transfer the shape from an object (base mesh without
16 modifiers) to another object with modifiers (i.e. posed Armature).
17 Only two objects must be selected.
18 The first selected object will be added to the second selected
19 object as a new shape key.
21 - Original 2.4x script by Brecht
22 - Unpose-function reused from a script by Tal Trachtman in 2007
23 http://www.apexbow.com/randd.html
24 - Converted to Blender 2.5 by Ivo Grigull
25 - Converted to Blender 2.8 by Tokikake
26 ("fast" option was removed, add new "delta" option
27 which count currently used shape key values of armature mesh when transfer)
29 Limitations and new delta option for 2.8
30 - Target mesh may not have any transformation at object level,
31 it will be set to zero.
33 - new "delta" option usage, when you hope to make new shape-key with keep currently visible other shape keys value.
34 it can generate new shape key, with value as 1.00. then deform target shape as source shape with keep other shape key values relative.
36 - If overwrite shape key,<select active shape key of target as non "base shape">
37 current shape key value is ignored and turn as 1.00.
39 then if active shape key was driven (bone rotation etc), you may get un-expected result. When transfer, I recommend, keep set active-shape key as base. so transferred shape key do not "overwrite". but generate new shape key.
40 if active-shape key have no driver, you can overwrite it (but as 1.00 value )
45 from mathutils
import Vector
, Matrix
51 depth
= bpy
.context
.evaluated_depsgraph_get()
54 bpy
.context
.view_layer
.update()
58 def reset_transform(ob
):
59 ob
.matrix_local
.identity()
61 # this version is for shape_key data
62 def extract_vert_coords(verts
):
63 return [v
.co
.copy() for v
in verts
]
65 def extract_mapped_coords(ob
, shape_verts
):
66 depth
= bpy
.context
.evaluated_depsgraph_get()
67 eobj
= ob
.evaluated_get(depth
)
68 mesh
= bpy
.data
.meshes
.new_from_object(eobj
)
70 # cheating, the original mapped verts happen
71 # to be at the end of the vertex array
73 #arr = [verts[i].co.copy() for i in range(len(verts) - totvert, len(verts))]
74 arr
= [verts
[i
].co
.copy() for i
in range(0, len(verts
))]
76 bpy
.data
.meshes
.remove(mesh
)
82 def apply_vert_coords(ob
, mesh
, x
):
83 for i
, v
in enumerate(mesh
):
88 def func_add_corrective_pose_shape(source
, target
, flag
):
95 reset_transform(target
)
97 # If target object doesn't have Base shape key, create it.
98 if not mesh_1
.shape_keys
:
99 basis
= ob_1
.shape_key_add()
102 ob_1
.active_shape_key_index
= 0
103 ob_1
.show_only_shape_key
= False
104 key_index
= ob_1
.active_shape_key_index
106 print(ob_1
.active_shape_key
)
107 active_key_name
= ob_1
.active_shape_key
.name
110 # Make mix shape key from currently used shape keys
111 if not key_index
== 0:
112 ob_1
.active_shape_key
.value
= 0
113 mix_shape
= ob_1
.shape_key_add(from_mix
= True)
114 mix_shape
.name
= "Mix_shape"
116 keys
= ob_1
.data
.shape_keys
.key_blocks
.keys()
117 ob_1
.active_shape_key_index
= keys
.index(active_key_name
)
119 print("active_key_name: ", active_key_name
)
122 new_shapekey
= ob_1
.shape_key_add()
123 new_shapekey
.name
= "Shape_" + ob_2
.name
125 keys
= ob_1
.data
.shape_keys
.key_blocks
.keys()
126 ob_1
.active_shape_key_index
= keys
.index(new_shapekey
.name
)
128 # else, the active shape will be used (updated)
130 ob_1
.show_only_shape_key
= True
132 vgroup
= ob_1
.active_shape_key
.vertex_group
133 ob_1
.active_shape_key
.vertex_group
= ""
135 #mesh_1_key_verts = mesh_1.shape_keys.key_blocks[key_index].data
136 mesh_1_key_verts
= ob_1
.active_shape_key
.data
138 x
= extract_vert_coords(mesh_1_key_verts
)
140 targetx
= extract_vert_coords(mesh_2
.vertices
)
142 for iteration
in range(0, iterations
):
143 dx
= [[], [], [], [], [], []]
145 mapx
= extract_mapped_coords(ob_1
, mesh_1_key_verts
)
147 # finite differencing in X/Y/Z to get approximate gradient
148 for i
in range(0, len(mesh_1
.vertices
)):
149 epsilon
= (targetx
[i
] - mapx
[i
]).length
151 if epsilon
< threshold
:
154 dx
[0] += [x
[i
] + 0.5 * epsilon
* Vector((1, 0, 0))]
155 dx
[1] += [x
[i
] + 0.5 * epsilon
* Vector((-1, 0, 0))]
156 dx
[2] += [x
[i
] + 0.5 * epsilon
* Vector((0, 1, 0))]
157 dx
[3] += [x
[i
] + 0.5 * epsilon
* Vector((0, -1, 0))]
158 dx
[4] += [x
[i
] + 0.5 * epsilon
* Vector((0, 0, 1))]
159 dx
[5] += [x
[i
] + 0.5 * epsilon
* Vector((0, 0, -1))]
161 for j
in range(0, 6):
162 apply_vert_coords(ob_1
, mesh_1_key_verts
, dx
[j
])
163 dx
[j
] = extract_mapped_coords(ob_1
, mesh_1_key_verts
)
165 # take a step in the direction of the gradient
166 for i
in range(0, len(mesh_1
.vertices
)):
167 epsilon
= (targetx
[i
] - mapx
[i
]).length
169 if epsilon
>= threshold
:
170 Gx
= list((dx
[0][i
] - dx
[1][i
]) / epsilon
)
171 Gy
= list((dx
[2][i
] - dx
[3][i
]) / epsilon
)
172 Gz
= list((dx
[4][i
] - dx
[5][i
]) / epsilon
)
173 G
= Matrix((Gx
, Gy
, Gz
))
174 Delmorph
= (targetx
[i
] - mapx
[i
])
177 apply_vert_coords(ob_1
, mesh_1_key_verts
, x
)
179 ob_1
.show_only_shape_key
= True
182 # remove delta of mix-shape key values from new shape key
183 key_index
= ob_1
.active_shape_key_index
184 active_key_name
= ob_1
.active_shape_key
.name
185 shape_data
= ob_1
.active_shape_key
.data
186 mix_data
= mix_shape
.data
187 for i
in range(0, len(mesh_1
.vertices
)):
188 shape_data
[i
].co
= mesh_1
.vertices
[i
].co
+ shape_data
[i
].co
- mix_data
[i
].co
191 ob_1
.active_shape_key_index
= ob_1
.data
.shape_keys
.key_blocks
.keys().index("Mix_shape")
192 bpy
.ops
.object.shape_key_remove()
193 ob_1
.active_shape_key_index
= ob_1
.data
.shape_keys
.key_blocks
.keys().index(active_key_name
)
195 ob_1
.show_only_shape_key
= False
197 ob_1
.active_shape_key
.vertex_group
= vgroup
199 # set the new shape key value to 1.0, so we see the result instantly
200 ob_1
.active_shape_key
.value
= 1.0
205 class add_corrective_pose_shape(bpy
.types
.Operator
):
206 """Adds first object as shape to second object for the current pose """ \
207 """while maintaining modifiers """ \
208 """(i.e. anisculpt, avoiding crazy space) Beware of slowness!"""
210 bl_idname
= "object.add_corrective_pose_shape"
211 bl_label
= "Add object as corrective pose shape"
214 def poll(cls
, context
):
215 return context
.active_object
is not None
217 def execute(self
, context
):
218 selection
= context
.selected_objects
219 if len(selection
) != 2:
220 self
.report({'ERROR'}, "Select source and target objects")
223 target
= context
.active_object
224 if context
.active_object
== selection
[0]:
225 source
= selection
[1]
227 source
= selection
[0]
231 func_add_corrective_pose_shape(source
, target
, delta_flag
)
235 class add_corrective_pose_shape_delta (bpy
.types
.Operator
):
236 """Adds first object as shape to second object for the current pose """ \
237 """while maintaining modifiers and currently used other shape keys""" \
238 """with keep other shape key value, generate new shape key which deform to source shape """
240 bl_idname
= "object.add_corrective_pose_shape_delta"
241 bl_label
= "Add object as corrective pose shape delta"
244 def poll(cls
, context
):
245 return context
.active_object
is not None
247 def execute(self
, context
):
248 selection
= context
.selected_objects
249 if len(selection
) != 2:
250 self
.report({'ERROR'}, "Select source and target objects")
253 target
= context
.active_object
254 if context
.active_object
== selection
[0]:
255 source
= selection
[1]
257 source
= selection
[0]
261 func_add_corrective_pose_shape(source
, target
, delta_flag
)
266 def func_object_duplicate_flatten_modifiers(context
, ob
):
267 depth
= bpy
.context
.evaluated_depsgraph_get()
268 eobj
= ob
.evaluated_get(depth
)
269 mesh
= bpy
.data
.meshes
.new_from_object(eobj
)
270 name
= ob
.name
+ "_clean"
271 new_object
= bpy
.data
.objects
.new(name
, mesh
)
272 new_object
.data
= mesh
273 bpy
.context
.collection
.objects
.link(new_object
)
277 class object_duplicate_flatten_modifiers(bpy
.types
.Operator
):
278 #Duplicates the selected object with modifiers applied
280 bl_idname
= "object.object_duplicate_flatten_modifiers"
281 bl_label
= "Duplicate and apply all"
284 def poll(cls
, context
):
285 return context
.active_object
is not None
287 def execute(self
, context
):
288 obj_act
= context
.active_object
290 new_object
= func_object_duplicate_flatten_modifiers(context
, obj_act
)
293 bpy
.ops
.object.select_all(action
='DESELECT')
295 context
.view_layer
.objects
.active
= new_object
296 new_object
.select_set(True)
300 #these old functions and class not work correctly just keep code for others try to edit
302 def unposeMesh(meshObToUnpose
, obj
, armatureOb
):
303 psdMeshData
= meshObToUnpose
305 psdMesh
= psdMeshData
306 I
= Matrix() # identity matrix
311 armData
= armatureOb
.data
313 pose
= armatureOb
.pose
316 for index
, v
in enumerate(mesh
.vertices
):
317 # above is python shortcut for:index goes up from 0 to tot num of
318 # verts in mesh, with index incrementing by 1 each iteration
320 psdMeshVert
= psdMesh
[index
]
322 listOfBoneNameWeightPairs
= []
323 for n
in mesh
.vertices
[index
].groups
:
325 name
= obj
.vertex_groups
[n
.group
].name
328 for i
in armData
.bones
:
332 # ignore non-bone vertex groups
334 listOfBoneNameWeightPairs
.append([name
, weight
])
339 weightedAverageDictionary
= {}
341 for pair
in listOfBoneNameWeightPairs
:
342 totalWeight
+= pair
[1]
344 for pair
in listOfBoneNameWeightPairs
:
345 if totalWeight
> 0: # avoid divide by zero!
346 weightedAverageDictionary
[pair
[0]] = pair
[1] / totalWeight
348 weightedAverageDictionary
[pair
[0]] = 0
350 # Matrix filled with zeros
360 if pbone
.name
in weightedAverageDictionary
:
361 #~ print("found key %s", pbone.name)
362 vertexWeight
= weightedAverageDictionary
[pbone
.name
]
363 m
= pbone
.matrix_channel
.copy()
365 sigma
+= (m
- I
) * vertexWeight
369 #~ print("no key for bone " + pbone.name)
373 psdMeshVert
.co
= sigma
@ psdMeshVert
.co
375 bpy
.context
.view_layer
.update()
379 def func_add_corrective_pose_shape_fast(source
, target
):
380 reset_transform(target
)
382 # If target object doesn't have Basis shape key, create it.
383 if not target
.data
.shape_keys
:
384 basis
= target
.shape_key_add()
388 key_index
= target
.active_shape_key_index
392 # Insert new shape key
393 new_shapekey
= target
.shape_key_add()
394 new_shapekey
.name
= "Shape_" + source
.name
396 key_index
= len(target
.data
.shape_keys
.key_blocks
) - 1
397 target
.active_shape_key_index
= key_index
399 # else, the active shape will be used (updated)
401 target
.show_only_shape_key
= True
403 shape_key_verts
= target
.data
.shape_keys
.key_blocks
[key_index
].data
406 vgroup
= target
.active_shape_key
.vertex_group
407 target
.active_shape_key
.vertex_group
= ''
411 # copy the local vertex positions to the new shape
412 verts
= source
.data
.vertices
413 for n
in range(len(verts
)):
414 shape_key_verts
[n
].co
= verts
[n
].co
416 bpy
.context
.view_layer
.update()
417 # go to all armature modifies and unpose the shape
418 for n
in target
.modifiers
:
419 if n
.type == 'ARMATURE' and n
.show_viewport
:
421 n
.use_bone_envelopes
= False
422 n
.use_deform_preserve_volume
= False
423 n
.use_vertex_groups
= True
425 unposeMesh(shape_key_verts
, target
, armature
)
428 # set the new shape key value to 1.0, so we see the result instantly
429 target
.active_shape_key
.value
= 1.0
432 target
.active_shape_key
.vertex_group
= vgroup
436 target
.show_only_shape_key
= False
438 bpy
.context
.view_layer
.update()
446 class add_corrective_pose_shape_fast(bpy
.types
.Operator
):
447 #Adds 1st object as shape to 2nd object as pose shape (only 1 armature)
449 bl_idname
= "object.add_corrective_pose_shape_fast"
450 bl_label
= "Add object as corrective shape faster"
453 def poll(cls
, context
):
454 return context
.active_object
is not None
456 def execute(self
, context
):
457 selection
= context
.selected_objects
458 if len(selection
) != 2:
459 self
.report({'ERROR'}, "Select source and target objects")
462 target
= context
.active_object
463 if context
.active_object
== selection
[0]:
464 source
= selection
[1]
466 source
= selection
[0]
468 func_add_corrective_pose_shape_fast(source
, target
)
474 # -----------------------------------------------------------------------------
477 def vgroups_draw(self
, context
):
480 layout
.operator("object.object_duplicate_flatten_modifiers",
481 text
='Create duplicate for editing')
482 layout
.operator("object.add_corrective_pose_shape",
483 text
='Add as corrective pose-shape (slow, all modifiers)',
484 icon
='COPY_ID') # icon is not ideal
485 layout
.operator("object.add_corrective_pose_shape_delta",
486 text
='Add as corrective pose-shape delta" (slow, all modifiers + other shape key values)',
487 icon
='COPY_ID') # icon is not ideal
490 def modifiers_draw(self
, context
):
493 classes
= (add_corrective_pose_shape
, add_corrective_pose_shape_delta
, object_duplicate_flatten_modifiers
, add_corrective_pose_shape_fast
)
495 from bpy
.utils
import register_class
498 bpy
.types
.MESH_MT_shape_key_context_menu
.append(vgroups_draw
)
499 bpy
.types
.DATA_PT_modifiers
.append(modifiers_draw
)
503 from bpy
.utils
import unregister_class
504 for cls
in reversed(classes
):
505 unregister_class(cls
)
506 bpy
.types
.MESH_MT_shape_key_context_menu
.remove(vgroups_draw
)
507 bpy
.types
.DATA_PT_modifiers
.remove(modifiers_draw
)
509 if __name__
== "__main__":