Fix #104510: X3D & BVH import error with Python 3.11
[blender-addons.git] / animation_add_corrective_shape_key.py
blob821b4eab35304cba645f7c2623fab0b36b43554e
1 # SPDX-License-Identifier: GPL-2.0-or-later
3 bl_info = {
4 "name": "Corrective Shape Keys",
5 "author": "Ivo Grigull (loolarge), Tal Trachtman", "Tokikake"
6 "version": (1, 1, 1),
7 "blender": (2, 80, 0),
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",
14 """
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 )
41 """
44 import bpy
45 from mathutils import Vector, Matrix
47 iterations = 20
48 threshold = 1e-16
50 def update_mesh(ob):
51 depth = bpy.context.evaluated_depsgraph_get()
52 depth.update()
53 ob.update_tag()
54 bpy.context.view_layer.update()
55 ob.data.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
72 verts = mesh.vertices
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))]
75 mesh.user_clear()
76 bpy.data.meshes.remove(mesh)
77 update_mesh(ob)
78 return arr
82 def apply_vert_coords(ob, mesh, x):
83 for i, v in enumerate(mesh):
84 v.co = x[i]
85 update_mesh(ob)
88 def func_add_corrective_pose_shape(source, target, flag):
90 ob_1 = target
91 mesh_1 = target.data
92 ob_2 = source
93 mesh_2 = source.data
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()
100 basis.name = "Basis"
101 update_mesh(ob_1)
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
105 print(ob_1)
106 print(ob_1.active_shape_key)
107 active_key_name = ob_1.active_shape_key.name
109 if (flag == True):
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"
115 update_mesh(ob_1)
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)
121 if key_index == 0:
122 new_shapekey = ob_1.shape_key_add()
123 new_shapekey.name = "Shape_" + ob_2.name
124 update_mesh(ob_1)
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:
152 epsilon = 0.0
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])
175 x[i] += G @ Delmorph
177 apply_vert_coords(ob_1, mesh_1_key_verts, x)
179 ob_1.show_only_shape_key = True
181 if (flag == 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
189 update_mesh(ob_1)
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)
194 ob_1.data.update()
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
201 update_mesh(ob_1)
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"
213 @classmethod
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")
221 return {'CANCELLED'}
223 target = context.active_object
224 if context.active_object == selection[0]:
225 source = selection[1]
226 else:
227 source = selection[0]
229 delta_flag = False
231 func_add_corrective_pose_shape(source, target, delta_flag)
233 return {'FINISHED'}
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"
243 @classmethod
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")
251 return {'CANCELLED'}
253 target = context.active_object
254 if context.active_object == selection[0]:
255 source = selection[1]
256 else:
257 source = selection[0]
259 delta_flag = True
261 func_add_corrective_pose_shape(source, target, delta_flag)
263 return {'FINISHED'}
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)
274 return 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"
283 @classmethod
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)
292 # setup the context
293 bpy.ops.object.select_all(action='DESELECT')
295 context.view_layer.objects.active = new_object
296 new_object.select_set(True)
298 return {'FINISHED'}
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
308 meshData =obj.data
309 mesh = meshData
311 armData = armatureOb.data
313 pose = armatureOb.pose
314 pbones = pose.bones
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:
324 try:
325 name = obj.vertex_groups[n.group].name
326 weight = n.weight
327 is_bone = False
328 for i in armData.bones:
329 if i.name == name:
330 is_bone = True
331 break
332 # ignore non-bone vertex groups
333 if is_bone:
334 listOfBoneNameWeightPairs.append([name, weight])
335 except:
336 print('error')
337 pass
339 weightedAverageDictionary = {}
340 totalWeight = 0
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
347 else:
348 weightedAverageDictionary[pair[0]] = 0
350 # Matrix filled with zeros
351 sigma = Matrix()
352 sigma.zero()
354 list = []
355 for n in pbones:
356 list.append(n)
357 list.reverse()
359 for pbone in list:
360 if pbone.name in weightedAverageDictionary:
361 #~ print("found key %s", pbone.name)
362 vertexWeight = weightedAverageDictionary[pbone.name]
363 m = pbone.matrix_channel.copy()
364 #m.transpose()
365 sigma += (m - I) * vertexWeight
367 else:
368 pass
369 #~ print("no key for bone " + pbone.name)
371 sigma = I + sigma
372 sigma.invert()
373 psdMeshVert.co = sigma @ psdMeshVert.co
374 obj.update_tag()
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()
385 basis.name = "Basis"
386 target.data.update()
388 key_index = target.active_shape_key_index
390 if key_index == 0:
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
405 try:
406 vgroup = target.active_shape_key.vertex_group
407 target.active_shape_key.vertex_group = ''
408 except:
409 pass
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
415 target.update_tag()
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:
420 #~ print("got one")
421 n.use_bone_envelopes = False
422 n.use_deform_preserve_volume = False
423 n.use_vertex_groups = True
424 armature = n.object
425 unposeMesh(shape_key_verts, target, armature)
426 break
428 # set the new shape key value to 1.0, so we see the result instantly
429 target.active_shape_key.value = 1.0
431 try:
432 target.active_shape_key.vertex_group = vgroup
433 except:
434 pass
436 target.show_only_shape_key = False
437 target.update_tag()
438 bpy.context.view_layer.update()
440 target.data.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"
452 @classmethod
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")
460 return {'CANCELLED'}
462 target = context.active_object
463 if context.active_object == selection[0]:
464 source = selection[1]
465 else:
466 source = selection[0]
468 func_add_corrective_pose_shape_fast(source, target)
470 return {'FINISHED'}
474 # -----------------------------------------------------------------------------
475 # GUI
477 def vgroups_draw(self, context):
478 layout = self.layout
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):
491 pass
493 classes = (add_corrective_pose_shape, add_corrective_pose_shape_delta, object_duplicate_flatten_modifiers, add_corrective_pose_shape_fast)
494 def register():
495 from bpy.utils import register_class
496 for cls in classes:
497 register_class(cls)
498 bpy.types.MESH_MT_shape_key_context_menu.append(vgroups_draw)
499 bpy.types.DATA_PT_modifiers.append(modifiers_draw)
502 def unregister():
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__":
510 register()