Fix invalid string comparisons
[blender-addons.git] / animation_add_corrective_shape_key.py
blobefb357f825979224fe54d232032fb94b4307b4b3
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",
24 "version": (1, 0),
25 "blender": (2, 57, 0),
26 "location": "Object Data > Shape Keys (Search: corrective) ",
27 "description": "Creates a corrective shape key for the current pose",
28 "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
29 "Scripts/Animation/Corrective_Shape_Key",
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
45 Limitations:
46 - Target mesh may not have any transformation at object level,
47 it will be set to zero.
48 - Fast/Armature method does not work with Bone envelopes or dual quaternions,
49 both settings will be disabled in the modifier
50 """
53 import bpy
54 from mathutils import Vector, Matrix
57 iterations = 20
58 threshold = 1e-16
61 def reset_transform(ob):
62 ob.matrix_local.identity()
65 # this version is for shape_key data
66 def extract_vert_coords(ob, verts):
67 return [v.co.copy() for v in verts]
70 def extract_mapped_coords(ob, shape_verts):
71 totvert = len(shape_verts)
73 mesh = ob.to_mesh(bpy.context.scene, True, 'PREVIEW')
75 # cheating, the original mapped verts happen
76 # to be at the end of the vertex array
77 verts = mesh.vertices
78 arr = [verts[i].co.copy() for i in range(len(verts) - totvert, len(verts))]
80 mesh.user_clear()
81 bpy.data.meshes.remove(mesh)
83 return arr
86 def apply_vert_coords(ob, mesh, x):
87 for i, v in enumerate(mesh):
88 v.co = x[i]
89 ob.data.update()
92 def func_add_corrective_pose_shape(source, target):
94 ob_1 = target
95 mesh_1 = target.data
96 ob_2 = source
97 mesh_2 = source.data
99 reset_transform(target)
101 # If target object doesn't have Basis shape key, create it.
102 if not mesh_1.shape_keys:
103 basis = ob_1.shape_key_add()
104 basis.name = "Basis"
105 ob_1.data.update()
107 key_index = ob_1.active_shape_key_index
108 # Insert new shape key
109 if key_index == 0:
110 new_shapekey = ob_1.shape_key_add()
111 new_shapekey.name = "Shape_" + ob_2.name
113 key_index = len(mesh_1.shape_keys.key_blocks) - 1
114 ob_1.active_shape_key_index = key_index
116 # else, the active shape will be used (updated)
118 ob_1.show_only_shape_key = True
120 vgroup = ob_1.active_shape_key.vertex_group
121 ob_1.active_shape_key.vertex_group = ""
123 mesh_1_key_verts = mesh_1.shape_keys.key_blocks[key_index].data
125 x = extract_vert_coords(ob_1, mesh_1_key_verts)
127 targetx = extract_vert_coords(ob_2, mesh_2.vertices)
129 for iteration in range(0, iterations):
130 dx = [[], [], [], [], [], []]
132 mapx = extract_mapped_coords(ob_1, mesh_1_key_verts)
134 # finite differencing in X/Y/Z to get approximate gradient
135 for i in range(0, len(mesh_1.vertices)):
136 epsilon = (targetx[i] - mapx[i]).length
138 if epsilon < threshold:
139 epsilon = 0.0
141 dx[0] += [x[i] + 0.5 * epsilon * Vector((1, 0, 0))]
142 dx[1] += [x[i] + 0.5 * epsilon * Vector((-1, 0, 0))]
143 dx[2] += [x[i] + 0.5 * epsilon * Vector((0, 1, 0))]
144 dx[3] += [x[i] + 0.5 * epsilon * Vector((0, -1, 0))]
145 dx[4] += [x[i] + 0.5 * epsilon * Vector((0, 0, 1))]
146 dx[5] += [x[i] + 0.5 * epsilon * Vector((0, 0, -1))]
148 for j in range(0, 6):
149 apply_vert_coords(ob_1, mesh_1_key_verts, dx[j])
150 dx[j] = extract_mapped_coords(ob_1, mesh_1_key_verts)
152 # take a step in the direction of the gradient
153 for i in range(0, len(mesh_1.vertices)):
154 epsilon = (targetx[i] - mapx[i]).length
156 if epsilon >= threshold:
157 Gx = list((dx[0][i] - dx[1][i]) / epsilon)
158 Gy = list((dx[2][i] - dx[3][i]) / epsilon)
159 Gz = list((dx[4][i] - dx[5][i]) / epsilon)
160 G = Matrix((Gx, Gy, Gz))
161 x[i] += G * (targetx[i] - mapx[i])
163 apply_vert_coords(ob_1, mesh_1_key_verts, x)
165 ob_1.active_shape_key.vertex_group = vgroup
167 # set the new shape key value to 1.0, so we see the result instantly
168 ob_1.active_shape_key.value = 1.0
170 #mesh_1.update()
171 ob_1.show_only_shape_key = False
174 class add_corrective_pose_shape(bpy.types.Operator):
175 """Adds first object as shape to second object for the current pose """ \
176 """while maintaining modifiers """ \
177 """(i.e. anisculpt, avoiding crazy space) Beware of slowness!"""
179 bl_idname = "object.add_corrective_pose_shape"
180 bl_label = "Add object as corrective pose shape"
182 @classmethod
183 def poll(cls, context):
184 return context.active_object is not None
186 def execute(self, context):
187 selection = context.selected_objects
188 if len(selection) != 2:
189 self.report({'ERROR'}, "Select source and target objects")
190 return {'CANCELLED'}
192 target = context.active_object
193 if context.active_object == selection[0]:
194 source = selection[1]
195 else:
196 source = selection[0]
198 func_add_corrective_pose_shape(source, target)
200 return {'FINISHED'}
203 def func_object_duplicate_flatten_modifiers(scene, obj):
204 mesh = obj.to_mesh(scene, True, 'PREVIEW')
205 name = obj.name + "_clean"
206 new_object = bpy.data.objects.new(name, mesh)
207 new_object.data = mesh
208 scene.objects.link(new_object)
209 return new_object
212 class object_duplicate_flatten_modifiers(bpy.types.Operator):
213 """Duplicates the selected object with modifiers applied"""
215 bl_idname = "object.object_duplicate_flatten_modifiers"
216 bl_label = "Duplicate and apply all"
218 @classmethod
219 def poll(cls, context):
220 return context.active_object is not None
222 def execute(self, context):
223 scene = context.scene
224 obj_act = context.active_object
226 new_object = func_object_duplicate_flatten_modifiers(scene, obj_act)
228 # setup the context
229 bpy.ops.object.select_all(action='DESELECT')
231 scene.objects.active = new_object
232 new_object.select = True
234 return {'FINISHED'}
237 def unposeMesh(meshObToUnpose, meshObToUnposeWeightSrc, armatureOb):
238 psdMeshData = meshObToUnpose
240 psdMesh = psdMeshData
241 I = Matrix() # identity matrix
243 meshData = meshObToUnposeWeightSrc.data
244 mesh = meshData
246 armData = armatureOb.data
248 pose = armatureOb.pose
249 pbones = pose.bones
251 for index, v in enumerate(mesh.vertices):
252 # above is python shortcut for:index goes up from 0 to tot num of
253 # verts in mesh, with index incrementing by 1 each iteration
255 psdMeshVert = psdMesh[index]
257 listOfBoneNameWeightPairs = []
258 for n in mesh.vertices[index].groups:
259 try:
260 name = meshObToUnposeWeightSrc.vertex_groups[n.group].name
261 weight = n.weight
262 is_bone = False
263 for i in armData.bones:
264 if i.name == name:
265 is_bone = True
266 break
267 # ignore non-bone vertex groups
268 if is_bone:
269 listOfBoneNameWeightPairs.append([name, weight])
270 except:
271 print('error')
272 pass
274 weightedAverageDictionary = {}
275 totalWeight = 0
276 for pair in listOfBoneNameWeightPairs:
277 totalWeight += pair[1]
279 for pair in listOfBoneNameWeightPairs:
280 if totalWeight > 0: # avoid divide by zero!
281 weightedAverageDictionary[pair[0]] = pair[1] / totalWeight
282 else:
283 weightedAverageDictionary[pair[0]] = 0
285 # Matrix filled with zeros
286 sigma = Matrix()
287 sigma.zero()
289 list = []
290 for n in pbones:
291 list.append(n)
292 list.reverse()
294 for pbone in list:
295 if pbone.name in weightedAverageDictionary:
296 #~ print("found key %s", pbone.name)
297 vertexWeight = weightedAverageDictionary[pbone.name]
298 m = pbone.matrix_channel.copy()
299 #m.transpose()
300 sigma += (m - I) * vertexWeight
302 else:
303 pass
304 #~ print("no key for bone " + pbone.name)
306 sigma = I + sigma
307 sigma.invert()
308 psdMeshVert.co = psdMeshVert.co * sigma
311 def func_add_corrective_pose_shape_fast(source, target):
313 reset_transform(target)
315 # If target object doesn't have Basis shape key, create it.
316 if not target.data.shape_keys:
317 basis = target.shape_key_add()
318 basis.name = "Basis"
319 target.data.update()
321 key_index = target.active_shape_key_index
323 if key_index == 0:
325 # Insert new shape key
326 new_shapekey = target.shape_key_add()
327 new_shapekey.name = "Shape_" + source.name
329 key_index = len(target.data.shape_keys.key_blocks) - 1
330 target.active_shape_key_index = key_index
332 # else, the active shape will be used (updated)
334 target.show_only_shape_key = True
336 shape_key_verts = target.data.shape_keys.key_blocks[key_index].data
338 try:
339 vgroup = target.active_shape_key.vertex_group
340 target.active_shape_key.vertex_group = ''
341 except:
342 pass
344 # copy the local vertex positions to the new shape
345 verts = source.data.vertices
346 for n in range(len(verts)):
347 shape_key_verts[n].co = verts[n].co
349 # go to all armature modifies and unpose the shape
350 for n in target.modifiers:
351 if n.type == 'ARMATURE' and n.show_viewport:
352 #~ print("got one")
353 n.use_bone_envelopes = False
354 n.use_deform_preserve_volume = False
355 n.use_vertex_groups = True
356 armature = n.object
357 unposeMesh(shape_key_verts, target, armature)
358 break
360 # set the new shape key value to 1.0, so we see the result instantly
361 target.active_shape_key.value = 1.0
363 try:
364 target.active_shape_key.vertex_group = vgroup
365 except:
366 pass
368 target.show_only_shape_key = False
369 target.data.update()
372 class add_corrective_pose_shape_fast(bpy.types.Operator):
373 """Adds 1st object as shape to 2nd object as pose shape
374 (only 1 armature)"""
376 bl_idname = "object.add_corrective_pose_shape_fast"
377 bl_label = "Add object as corrective shape faster"
379 @classmethod
380 def poll(cls, context):
381 return context.active_object is not None
383 def execute(self, context):
384 selection = context.selected_objects
385 if len(selection) != 2:
386 self.report({'ERROR'}, "Select source and target objects")
387 return {'CANCELLED'}
389 target = context.active_object
390 if context.active_object == selection[0]:
391 source = selection[1]
392 else:
393 source = selection[0]
395 func_add_corrective_pose_shape_fast(source, target)
397 return {'FINISHED'}
400 # -----------------------------------------------------------------------------
401 # GUI
403 def vgroups_draw(self, context):
404 layout = self.layout
406 layout.operator("object.object_duplicate_flatten_modifiers",
407 text='Create duplicate for editing')
408 layout.operator("object.add_corrective_pose_shape_fast",
409 text='Add as corrective pose-shape (fast, armatures only)',
410 icon='COPY_ID') # icon is not ideal
411 layout.operator("object.add_corrective_pose_shape",
412 text='Add as corrective pose-shape (slow, all modifiers)',
413 icon='COPY_ID') # icon is not ideal
416 def modifiers_draw(self, context):
417 pass
420 def register():
421 bpy.utils.register_module(__name__)
423 bpy.types.MESH_MT_shape_key_specials.append(vgroups_draw)
424 bpy.types.DATA_PT_modifiers.append(modifiers_draw)
427 def unregister():
428 bpy.utils.unregister_module(__name__)
431 if __name__ == "__main__":
432 register()