object_collection_manager: Remove import globbing
[blender-addons.git] / animation_add_corrective_shape_key.py
blobe0961e8dcbfdef3203046c8e41d9bdf67566a9af
1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
19 # <pep8-80 compliant>
21 bl_info = {
22 "name": "Corrective Shape Keys",
23 "author": "Ivo Grigull (loolarge), Tal Trachtman", "Tokikake"
24 "version": (1, 1, 1),
25 "blender": (2, 80, 0),
26 "location": "Object Data > Shape Keys Specials or Search",
27 "description": "Creates a corrective shape key for the current pose",
28 "wiki_url": "https://docs.blender.org/manual/en/dev/addons/"
29 "animation/corrective_shape_keys.html",
30 "category": "Animation",
33 """
34 This script transfer the shape from an object (base mesh without
35 modifiers) to another object with modifiers (i.e. posed Armature).
36 Only two objects must be selected.
37 The first selected object will be added to the second selected
38 object as a new shape key.
40 - Original 2.4x script by Brecht
41 - Unpose-function reused from a script by Tal Trachtman in 2007
42 http://www.apexbow.com/randd.html
43 - Converted to Blender 2.5 by Ivo Grigull
44 - Converted to Blender 2.8 by Tokikake
45 ("fast" option was removed, add new "delta" option
46 which count currently used shape key values of armature mesh when transfer)
48 Limitations and new delta option for 2.8
49 - Target mesh may not have any transformation at object level,
50 it will be set to zero.
52 - new "delta" option usage, when you hope to make new shape-key with keep currently visible other shape keys value.
53 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.
55 - If overwrite shape key,<select active shape key of target as non "base shape">
56 current shape key value is ignored and turn as 1.00.
58 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.
59 if active-shape key have no driver, you can overwrite it (but as 1.00 value )
60 """
63 import bpy
64 from mathutils import Vector, Matrix
66 iterations = 20
67 threshold = 1e-16
69 def update_mesh(ob):
70 depth = bpy.context.evaluated_depsgraph_get()
71 depth.update()
72 ob.update_tag()
73 bpy.context.view_layer.update()
74 ob.data.update()
77 def reset_transform(ob):
78 ob.matrix_local.identity()
80 # this version is for shape_key data
81 def extract_vert_coords(verts):
82 return [v.co.copy() for v in verts]
84 def extract_mapped_coords(ob, shape_verts):
85 depth = bpy.context.evaluated_depsgraph_get()
86 eobj = ob.evaluated_get(depth)
87 mesh = bpy.data.meshes.new_from_object(eobj)
89 # cheating, the original mapped verts happen
90 # to be at the end of the vertex array
91 verts = mesh.vertices
92 #arr = [verts[i].co.copy() for i in range(len(verts) - totvert, len(verts))]
93 arr = [verts[i].co.copy() for i in range(0, len(verts))]
94 mesh.user_clear()
95 bpy.data.meshes.remove(mesh)
96 update_mesh(ob)
97 return arr
101 def apply_vert_coords(ob, mesh, x):
102 for i, v in enumerate(mesh):
103 v.co = x[i]
104 update_mesh(ob)
107 def func_add_corrective_pose_shape(source, target, flag):
109 ob_1 = target
110 mesh_1 = target.data
111 ob_2 = source
112 mesh_2 = source.data
114 reset_transform(target)
116 # If target object doesn't have Base shape key, create it.
117 if not mesh_1.shape_keys:
118 basis = ob_1.shape_key_add()
119 basis.name = "Basis"
120 update_mesh(ob_1)
121 ob_1.active_shape_key_index = 0
122 ob_1.show_only_shape_key = False
123 key_index = ob_1.active_shape_key_index
124 print(ob_1)
125 print(ob_1.active_shape_key)
126 active_key_name = ob_1.active_shape_key.name
128 if (flag == True):
129 # Make mix shape key from currently used shape keys
130 if not key_index == 0:
131 ob_1.active_shape_key.value = 0
132 mix_shape = ob_1.shape_key_add(from_mix = True)
133 mix_shape.name = "Mix_shape"
134 update_mesh(ob_1)
135 keys = ob_1.data.shape_keys.key_blocks.keys()
136 ob_1.active_shape_key_index = keys.index(active_key_name)
138 print("active_key_name: ", active_key_name)
140 if key_index == 0:
141 new_shapekey = ob_1.shape_key_add()
142 new_shapekey.name = "Shape_" + ob_2.name
143 update_mesh(ob_1)
144 keys = ob_1.data.shape_keys.key_blocks.keys()
145 ob_1.active_shape_key_index = keys.index(new_shapekey.name)
147 # else, the active shape will be used (updated)
149 ob_1.show_only_shape_key = True
151 vgroup = ob_1.active_shape_key.vertex_group
152 ob_1.active_shape_key.vertex_group = ""
154 #mesh_1_key_verts = mesh_1.shape_keys.key_blocks[key_index].data
155 mesh_1_key_verts = ob_1.active_shape_key.data
157 x = extract_vert_coords(mesh_1_key_verts)
159 targetx = extract_vert_coords(mesh_2.vertices)
161 for iteration in range(0, iterations):
162 dx = [[], [], [], [], [], []]
164 mapx = extract_mapped_coords(ob_1, mesh_1_key_verts)
166 # finite differencing in X/Y/Z to get approximate gradient
167 for i in range(0, len(mesh_1.vertices)):
168 epsilon = (targetx[i] - mapx[i]).length
170 if epsilon < threshold:
171 epsilon = 0.0
173 dx[0] += [x[i] + 0.5 * epsilon * Vector((1, 0, 0))]
174 dx[1] += [x[i] + 0.5 * epsilon * Vector((-1, 0, 0))]
175 dx[2] += [x[i] + 0.5 * epsilon * Vector((0, 1, 0))]
176 dx[3] += [x[i] + 0.5 * epsilon * Vector((0, -1, 0))]
177 dx[4] += [x[i] + 0.5 * epsilon * Vector((0, 0, 1))]
178 dx[5] += [x[i] + 0.5 * epsilon * Vector((0, 0, -1))]
180 for j in range(0, 6):
181 apply_vert_coords(ob_1, mesh_1_key_verts, dx[j])
182 dx[j] = extract_mapped_coords(ob_1, mesh_1_key_verts)
184 # take a step in the direction of the gradient
185 for i in range(0, len(mesh_1.vertices)):
186 epsilon = (targetx[i] - mapx[i]).length
188 if epsilon >= threshold:
189 Gx = list((dx[0][i] - dx[1][i]) / epsilon)
190 Gy = list((dx[2][i] - dx[3][i]) / epsilon)
191 Gz = list((dx[4][i] - dx[5][i]) / epsilon)
192 G = Matrix((Gx, Gy, Gz))
193 Delmorph = (targetx[i] - mapx[i])
194 x[i] += G @ Delmorph
196 apply_vert_coords(ob_1, mesh_1_key_verts, x)
198 ob_1.show_only_shape_key = True
200 if (flag == True):
201 # remove delta of mix-shape key values from new shape key
202 key_index = ob_1.active_shape_key_index
203 active_key_name = ob_1.active_shape_key.name
204 shape_data = ob_1.active_shape_key.data
205 mix_data = mix_shape.data
206 for i in range(0, len(mesh_1.vertices)):
207 shape_data[i].co = mesh_1.vertices[i].co + shape_data[i].co - mix_data[i].co
208 update_mesh(ob_1)
210 ob_1.active_shape_key_index = ob_1.data.shape_keys.key_blocks.keys().index("Mix_shape")
211 bpy.ops.object.shape_key_remove()
212 ob_1.active_shape_key_index = ob_1.data.shape_keys.key_blocks.keys().index(active_key_name)
213 ob_1.data.update()
214 ob_1.show_only_shape_key = False
216 ob_1.active_shape_key.vertex_group = vgroup
218 # set the new shape key value to 1.0, so we see the result instantly
219 ob_1.active_shape_key.value = 1.0
220 update_mesh(ob_1)
224 class add_corrective_pose_shape(bpy.types.Operator):
225 """Adds first object as shape to second object for the current pose """ \
226 """while maintaining modifiers """ \
227 """(i.e. anisculpt, avoiding crazy space) Beware of slowness!"""
229 bl_idname = "object.add_corrective_pose_shape"
230 bl_label = "Add object as corrective pose shape"
232 @classmethod
233 def poll(cls, context):
234 return context.active_object is not None
236 def execute(self, context):
237 selection = context.selected_objects
238 if len(selection) != 2:
239 self.report({'ERROR'}, "Select source and target objects")
240 return {'CANCELLED'}
242 target = context.active_object
243 if context.active_object == selection[0]:
244 source = selection[1]
245 else:
246 source = selection[0]
248 delta_flag = False
250 func_add_corrective_pose_shape(source, target, delta_flag)
252 return {'FINISHED'}
254 class add_corrective_pose_shape_delta (bpy.types.Operator):
255 """Adds first object as shape to second object for the current pose """ \
256 """while maintaining modifiers and currently used other shape keys""" \
257 """with keep other shape key value, generate new shape key which deform to source shape """
259 bl_idname = "object.add_corrective_pose_shape_delta"
260 bl_label = "Add object as corrective pose shape delta"
262 @classmethod
263 def poll(cls, context):
264 return context.active_object is not None
266 def execute(self, context):
267 selection = context.selected_objects
268 if len(selection) != 2:
269 self.report({'ERROR'}, "Select source and target objects")
270 return {'CANCELLED'}
272 target = context.active_object
273 if context.active_object == selection[0]:
274 source = selection[1]
275 else:
276 source = selection[0]
278 delta_flag = True
280 func_add_corrective_pose_shape(source, target, delta_flag)
282 return {'FINISHED'}
285 def func_object_duplicate_flatten_modifiers(context, ob):
286 depth = bpy.context.evaluated_depsgraph_get()
287 eobj = ob.evaluated_get(depth)
288 mesh = bpy.data.meshes.new_from_object(eobj)
289 name = ob.name + "_clean"
290 new_object = bpy.data.objects.new(name, mesh)
291 new_object.data = mesh
292 bpy.context.collection.objects.link(new_object)
293 return new_object
296 class object_duplicate_flatten_modifiers(bpy.types.Operator):
297 #Duplicates the selected object with modifiers applied
299 bl_idname = "object.object_duplicate_flatten_modifiers"
300 bl_label = "Duplicate and apply all"
302 @classmethod
303 def poll(cls, context):
304 return context.active_object is not None
306 def execute(self, context):
307 obj_act = context.active_object
309 new_object = func_object_duplicate_flatten_modifiers(context, obj_act)
311 # setup the context
312 bpy.ops.object.select_all(action='DESELECT')
314 context.view_layer.objects.active = new_object
315 new_object.select_set(True)
317 return {'FINISHED'}
319 #these old functions and class not work correctly just keep code for others try to edit
321 def unposeMesh(meshObToUnpose, obj, armatureOb):
322 psdMeshData = meshObToUnpose
324 psdMesh = psdMeshData
325 I = Matrix() # identity matrix
327 meshData =obj.data
328 mesh = meshData
330 armData = armatureOb.data
332 pose = armatureOb.pose
333 pbones = pose.bones
335 for index, v in enumerate(mesh.vertices):
336 # above is python shortcut for:index goes up from 0 to tot num of
337 # verts in mesh, with index incrementing by 1 each iteration
339 psdMeshVert = psdMesh[index]
341 listOfBoneNameWeightPairs = []
342 for n in mesh.vertices[index].groups:
343 try:
344 name = obj.vertex_groups[n.group].name
345 weight = n.weight
346 is_bone = False
347 for i in armData.bones:
348 if i.name == name:
349 is_bone = True
350 break
351 # ignore non-bone vertex groups
352 if is_bone:
353 listOfBoneNameWeightPairs.append([name, weight])
354 except:
355 print('error')
356 pass
358 weightedAverageDictionary = {}
359 totalWeight = 0
360 for pair in listOfBoneNameWeightPairs:
361 totalWeight += pair[1]
363 for pair in listOfBoneNameWeightPairs:
364 if totalWeight > 0: # avoid divide by zero!
365 weightedAverageDictionary[pair[0]] = pair[1] / totalWeight
366 else:
367 weightedAverageDictionary[pair[0]] = 0
369 # Matrix filled with zeros
370 sigma = Matrix()
371 sigma.zero()
373 list = []
374 for n in pbones:
375 list.append(n)
376 list.reverse()
378 for pbone in list:
379 if pbone.name in weightedAverageDictionary:
380 #~ print("found key %s", pbone.name)
381 vertexWeight = weightedAverageDictionary[pbone.name]
382 m = pbone.matrix_channel.copy()
383 #m.transpose()
384 sigma += (m - I) * vertexWeight
386 else:
387 pass
388 #~ print("no key for bone " + pbone.name)
390 sigma = I + sigma
391 sigma.invert()
392 psdMeshVert.co = sigma @ psdMeshVert.co
393 obj.update_tag()
394 bpy.context.view_layer.update()
398 def func_add_corrective_pose_shape_fast(source, target):
399 reset_transform(target)
401 # If target object doesn't have Basis shape key, create it.
402 if not target.data.shape_keys:
403 basis = target.shape_key_add()
404 basis.name = "Basis"
405 target.data.update()
407 key_index = target.active_shape_key_index
409 if key_index == 0:
411 # Insert new shape key
412 new_shapekey = target.shape_key_add()
413 new_shapekey.name = "Shape_" + source.name
415 key_index = len(target.data.shape_keys.key_blocks) - 1
416 target.active_shape_key_index = key_index
418 # else, the active shape will be used (updated)
420 target.show_only_shape_key = True
422 shape_key_verts = target.data.shape_keys.key_blocks[key_index].data
424 try:
425 vgroup = target.active_shape_key.vertex_group
426 target.active_shape_key.vertex_group = ''
427 except:
428 pass
430 # copy the local vertex positions to the new shape
431 verts = source.data.vertices
432 for n in range(len(verts)):
433 shape_key_verts[n].co = verts[n].co
434 target.update_tag()
435 bpy.context.view_layer.update()
436 # go to all armature modifies and unpose the shape
437 for n in target.modifiers:
438 if n.type == 'ARMATURE' and n.show_viewport:
439 #~ print("got one")
440 n.use_bone_envelopes = False
441 n.use_deform_preserve_volume = False
442 n.use_vertex_groups = True
443 armature = n.object
444 unposeMesh(shape_key_verts, target, armature)
445 break
447 # set the new shape key value to 1.0, so we see the result instantly
448 target.active_shape_key.value = 1.0
450 try:
451 target.active_shape_key.vertex_group = vgroup
452 except:
453 pass
455 target.show_only_shape_key = False
456 target.update_tag()
457 bpy.context.view_layer.update()
459 target.data.update()
465 class add_corrective_pose_shape_fast(bpy.types.Operator):
466 #Adds 1st object as shape to 2nd object as pose shape (only 1 armature)
468 bl_idname = "object.add_corrective_pose_shape_fast"
469 bl_label = "Add object as corrective shape faster"
471 @classmethod
472 def poll(cls, context):
473 return context.active_object is not None
475 def execute(self, context):
476 selection = context.selected_objects
477 if len(selection) != 2:
478 self.report({'ERROR'}, "Select source and target objects")
479 return {'CANCELLED'}
481 target = context.active_object
482 if context.active_object == selection[0]:
483 source = selection[1]
484 else:
485 source = selection[0]
487 func_add_corrective_pose_shape_fast(source, target)
489 return {'FINISHED'}
493 # -----------------------------------------------------------------------------
494 # GUI
496 def vgroups_draw(self, context):
497 layout = self.layout
499 layout.operator("object.object_duplicate_flatten_modifiers",
500 text='Create duplicate for editing')
501 layout.operator("object.add_corrective_pose_shape",
502 text='Add as corrective pose-shape (slow, all modifiers)',
503 icon='COPY_ID') # icon is not ideal
504 layout.operator("object.add_corrective_pose_shape_delta",
505 text='Add as corrective pose-shape delta" (slow, all modifiers + other shape key values)',
506 icon='COPY_ID') # icon is not ideal
509 def modifiers_draw(self, context):
510 pass
512 classes = (add_corrective_pose_shape, add_corrective_pose_shape_delta, object_duplicate_flatten_modifiers, add_corrective_pose_shape_fast)
513 def register():
514 from bpy.utils import register_class
515 for cls in classes:
516 register_class(cls)
517 bpy.types.MESH_MT_shape_key_context_menu.append(vgroups_draw)
518 bpy.types.DATA_PT_modifiers.append(modifiers_draw)
521 def unregister():
522 from bpy.utils import unregister_class
523 for cls in reversed(classes):
524 unregister_class(cls)
525 bpy.types.MESH_MT_shape_key_context_menu.remove(vgroups_draw)
526 bpy.types.DATA_PT_modifiers.remove(modifiers_draw)
528 if __name__ == "__main__":
529 register()