Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormeta-androcto <meta.androcto1@gmail.com>2019-06-23 13:40:37 +0300
committermeta-androcto <meta.androcto1@gmail.com>2019-06-23 13:40:37 +0300
commit9ec9bb2354dc91268aac6094374aa504e076ad38 (patch)
treec6e488c3558505e38fef8681a83dad01416deb4f /animation_add_corrective_shape_key.py
parentf3d58200d5e34aacf41162181e0435d65c464753 (diff)
animation_add_corrective_shape_key: return to release: T65258 38475602b2ef
Diffstat (limited to 'animation_add_corrective_shape_key.py')
-rw-r--r--animation_add_corrective_shape_key.py530
1 files changed, 530 insertions, 0 deletions
diff --git a/animation_add_corrective_shape_key.py b/animation_add_corrective_shape_key.py
new file mode 100644
index 00000000..c0824168
--- /dev/null
+++ b/animation_add_corrective_shape_key.py
@@ -0,0 +1,530 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# <pep8-80 compliant>
+
+bl_info = {
+ "name": "Corrective Shape Keys",
+ "author": "Ivo Grigull (loolarge), Tal Trachtman", "Tokikake"
+ "version": (1, 1, 0),
+ "blender": (2, 80, 0),
+ "location": "Object Data > Shape Keys (Search: corrective) ",
+ "description": "Creates a corrective shape key for the current pose",
+ "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
+ "Scripts/Animation/Corrective_Shape_Key",
+ "category": "Animation",
+}
+
+"""
+This script transfer the shape from an object (base mesh without
+modifiers) to another object with modifiers (i.e. posed Armature).
+Only two objects must be selected.
+The first selected object will be added to the second selected
+object as a new shape key.
+
+- Original 2.4x script by Brecht
+- Unpose-function reused from a script by Tal Trachtman in 2007
+ http://www.apexbow.com/randd.html
+- Converted to Blender 2.5 by Ivo Grigull
+- Converted to Blender 2.8 by Tokikake
+("fast" option was removed, add new "delta" option
+which count currently used shape key values of armature mesh when transfer)
+
+Limitations and new delta option for 2.8
+- Target mesh may not have any transformation at object level,
+ it will be set to zero.
+
+- new "delta" option usage, when you hope to make new shape-key with keep currently visible other shape keys value.
+ it can generate new shape key, with value as 1.00. then deform target shape as soruce shape with keep other shape key values relative.
+
+- If overwrite shape key,<select active shape key of target as non "base shape">
+ current shape key value is ignored and turn as 1.00.
+
+ 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 transfered shape key do not "overwrite". but generate new shape key.
+ if active-shape key have no driver, you can overwrite it (but as 1.00 value )
+"""
+
+
+import bpy
+from mathutils import Vector, Matrix
+
+iterations = 20
+threshold = 1e-16
+
+def update_mesh(ob):
+ depth = bpy.context.evaluated_depsgraph_get()
+ depth.update()
+ ob.update_tag()
+ bpy.context.view_layer.update()
+ ob.data.update()
+
+
+def reset_transform(ob):
+ ob.matrix_local.identity()
+
+# this version is for shape_key data
+def extract_vert_coords(verts):
+ return [v.co.copy() for v in verts]
+
+def extract_mapped_coords(ob, shape_verts):
+ depth = bpy.context.evaluated_depsgraph_get()
+ eobj = ob.evaluated_get(depth)
+ mesh = bpy.data.meshes.new_from_object(eobj)
+
+ # cheating, the original mapped verts happen
+ # to be at the end of the vertex array
+ verts = mesh.vertices
+ #arr = [verts[i].co.copy() for i in range(len(verts) - totvert, len(verts))]
+ arr = [verts[i].co.copy() for i in range(0, len(verts))]
+ mesh.user_clear()
+ bpy.data.meshes.remove(mesh)
+ update_mesh(ob)
+ return arr
+
+
+
+def apply_vert_coords(ob, mesh, x):
+ for i, v in enumerate(mesh):
+ v.co = x[i]
+ update_mesh(ob)
+
+
+def func_add_corrective_pose_shape(source, target, flag):
+
+ ob_1 = target
+ mesh_1 = target.data
+ ob_2 = source
+ mesh_2 = source.data
+
+ reset_transform(target)
+
+ # If target object doesn't have Base shape key, create it.
+ if not mesh_1.shape_keys:
+ basis = ob_1.shape_key_add()
+ basis.name = "Basis"
+ update_mesh(ob_1)
+ ob_1.active_shape_key_index = 0
+ ob_1.show_only_shape_key = False
+ key_index = ob_1.active_shape_key_index
+ print(ob_1)
+ print(ob_1.active_shape_key)
+ active_key_name = ob_1.active_shape_key.name
+
+ if (flag == True):
+ # Make mix shape key from currently used shape keys
+ if not key_index == 0:
+ ob_1.active_shape_key.value = 0
+ mix_shape = ob_1.shape_key_add(from_mix = True)
+ mix_shape.name = "Mix_shape"
+ update_mesh(ob_1)
+ keys = ob_1.data.shape_keys.key_blocks.keys()
+ ob_1.active_shape_key_index = keys.index(active_key_name)
+
+ print("active_key_name: ", active_key_name)
+
+ if key_index == 0:
+ new_shapekey = ob_1.shape_key_add()
+ new_shapekey.name = "Shape_" + ob_2.name
+ update_mesh(ob_1)
+ keys = ob_1.data.shape_keys.key_blocks.keys()
+ ob_1.active_shape_key_index = keys.index(new_shapekey.name)
+
+ # else, the active shape will be used (updated)
+
+ ob_1.show_only_shape_key = True
+
+ vgroup = ob_1.active_shape_key.vertex_group
+ ob_1.active_shape_key.vertex_group = ""
+
+ #mesh_1_key_verts = mesh_1.shape_keys.key_blocks[key_index].data
+ mesh_1_key_verts = ob_1.active_shape_key.data
+
+ x = extract_vert_coords(mesh_1_key_verts)
+
+ targetx = extract_vert_coords(mesh_2.vertices)
+
+ for iteration in range(0, iterations):
+ dx = [[], [], [], [], [], []]
+
+ mapx = extract_mapped_coords(ob_1, mesh_1_key_verts)
+
+ # finite differencing in X/Y/Z to get approximate gradient
+ for i in range(0, len(mesh_1.vertices)):
+ epsilon = (targetx[i] - mapx[i]).length
+
+ if epsilon < threshold:
+ epsilon = 0.0
+
+ dx[0] += [x[i] + 0.5 * epsilon * Vector((1, 0, 0))]
+ dx[1] += [x[i] + 0.5 * epsilon * Vector((-1, 0, 0))]
+ dx[2] += [x[i] + 0.5 * epsilon * Vector((0, 1, 0))]
+ dx[3] += [x[i] + 0.5 * epsilon * Vector((0, -1, 0))]
+ dx[4] += [x[i] + 0.5 * epsilon * Vector((0, 0, 1))]
+ dx[5] += [x[i] + 0.5 * epsilon * Vector((0, 0, -1))]
+
+ for j in range(0, 6):
+ apply_vert_coords(ob_1, mesh_1_key_verts, dx[j])
+ dx[j] = extract_mapped_coords(ob_1, mesh_1_key_verts)
+
+ # take a step in the direction of the gradient
+ for i in range(0, len(mesh_1.vertices)):
+ epsilon = (targetx[i] - mapx[i]).length
+
+ if epsilon >= threshold:
+ Gx = list((dx[0][i] - dx[1][i]) / epsilon)
+ Gy = list((dx[2][i] - dx[3][i]) / epsilon)
+ Gz = list((dx[4][i] - dx[5][i]) / epsilon)
+ G = Matrix((Gx, Gy, Gz))
+ Delmorph = (targetx[i] - mapx[i])
+ x[i] += G @ Delmorph
+
+ apply_vert_coords(ob_1, mesh_1_key_verts, x)
+
+ ob_1.show_only_shape_key = True
+
+ if (flag == True):
+ # remove delta of mix-shape key values from new shape key
+ key_index = ob_1.active_shape_key_index
+ active_key_name = ob_1.active_shape_key.name
+ shape_data = ob_1.active_shape_key.data
+ mix_data = mix_shape.data
+ for i in range(0, len(mesh_1.vertices)):
+ shape_data[i].co = mesh_1.vertices[i].co + shape_data[i].co - mix_data[i].co
+ update_mesh(ob_1)
+
+ ob_1.active_shape_key_index = ob_1.data.shape_keys.key_blocks.keys().index("Mix_shape")
+ bpy.ops.object.shape_key_remove()
+ ob_1.active_shape_key_index = ob_1.data.shape_keys.key_blocks.keys().index(active_key_name)
+ ob_1.data.update()
+ ob_1.show_only_shape_key = False
+
+ ob_1.active_shape_key.vertex_group = vgroup
+
+ # set the new shape key value to 1.0, so we see the result instantly
+ ob_1.active_shape_key.value = 1.0
+ update_mesh(ob_1)
+
+
+
+class add_corrective_pose_shape(bpy.types.Operator):
+ """Adds first object as shape to second object for the current pose """ \
+ """while maintaining modifiers """ \
+ """(i.e. anisculpt, avoiding crazy space) Beware of slowness!"""
+
+ bl_idname = "object.add_corrective_pose_shape"
+ bl_label = "Add object as corrective pose shape"
+
+ @classmethod
+ def poll(cls, context):
+ return context.active_object is not None
+
+ def execute(self, context):
+ selection = context.selected_objects
+ if len(selection) != 2:
+ self.report({'ERROR'}, "Select source and target objects")
+ return {'CANCELLED'}
+
+ target = context.active_object
+ if context.active_object == selection[0]:
+ source = selection[1]
+ else:
+ source = selection[0]
+
+ delta_flag = False
+
+ func_add_corrective_pose_shape(source, target, delta_flag)
+
+ return {'FINISHED'}
+
+class add_corrective_pose_shape_delta (bpy.types.Operator):
+ """Adds first object as shape to second object for the current pose """ \
+ """while maintaining modifiers and currently used other shape keys""" \
+ """with keep other shape key value, generate new shape key which deform to soruce shape """
+
+ bl_idname = "object.add_corrective_pose_shape_delta"
+ bl_label = "Add object as corrective pose shape delta"
+
+ @classmethod
+ def poll(cls, context):
+ return context.active_object is not None
+
+ def execute(self, context):
+ selection = context.selected_objects
+ if len(selection) != 2:
+ self.report({'ERROR'}, "Select source and target objects")
+ return {'CANCELLED'}
+
+ target = context.active_object
+ if context.active_object == selection[0]:
+ source = selection[1]
+ else:
+ source = selection[0]
+
+ delta_flag = True
+
+ func_add_corrective_pose_shape(source, target, delta_flag)
+
+ return {'FINISHED'}
+
+
+def func_object_duplicate_flatten_modifiers(context, ob):
+ depth = bpy.context.evaluated_depsgraph_get()
+ eobj = ob.evaluated_get(depth)
+ mesh = bpy.data.meshes.new_from_object(eobj)
+ name = ob.name + "_clean"
+ new_object = bpy.data.objects.new(name, mesh)
+ new_object.data = mesh
+ bpy.context.collection.objects.link(new_object)
+ return new_object
+
+
+class object_duplicate_flatten_modifiers(bpy.types.Operator):
+ #Duplicates the selected object with modifiers applied
+
+ bl_idname = "object.object_duplicate_flatten_modifiers"
+ bl_label = "Duplicate and apply all"
+
+ @classmethod
+ def poll(cls, context):
+ return context.active_object is not None
+
+ def execute(self, context):
+ obj_act = context.active_object
+
+ new_object = func_object_duplicate_flatten_modifiers(context, obj_act)
+
+ # setup the context
+ bpy.ops.object.select_all(action='DESELECT')
+
+ context.view_layer.objects.active = new_object
+ new_object.select_set(True)
+
+ return {'FINISHED'}
+
+""" these old functions and class not work correctly just keep code for others try to edit
+
+def unposeMesh(meshObToUnpose, obj, armatureOb):
+ psdMeshData = meshObToUnpose
+
+ psdMesh = psdMeshData
+ I = Matrix() # identity matrix
+
+ meshData =obj.data
+ mesh = meshData
+
+ armData = armatureOb.data
+
+ pose = armatureOb.pose
+ pbones = pose.bones
+
+ for index, v in enumerate(mesh.vertices):
+ # above is python shortcut for:index goes up from 0 to tot num of
+ # verts in mesh, with index incrementing by 1 each iteration
+
+ psdMeshVert = psdMesh[index]
+
+ listOfBoneNameWeightPairs = []
+ for n in mesh.vertices[index].groups:
+ try:
+ name = obj.vertex_groups[n.group].name
+ weight = n.weight
+ is_bone = False
+ for i in armData.bones:
+ if i.name == name:
+ is_bone = True
+ break
+ # ignore non-bone vertex groups
+ if is_bone:
+ listOfBoneNameWeightPairs.append([name, weight])
+ except:
+ print('error')
+ pass
+
+ weightedAverageDictionary = {}
+ totalWeight = 0
+ for pair in listOfBoneNameWeightPairs:
+ totalWeight += pair[1]
+
+ for pair in listOfBoneNameWeightPairs:
+ if totalWeight > 0: # avoid divide by zero!
+ weightedAverageDictionary[pair[0]] = pair[1] / totalWeight
+ else:
+ weightedAverageDictionary[pair[0]] = 0
+
+ # Matrix filled with zeros
+ sigma = Matrix()
+ sigma.zero()
+
+ list = []
+ for n in pbones:
+ list.append(n)
+ list.reverse()
+
+ for pbone in list:
+ if pbone.name in weightedAverageDictionary:
+ #~ print("found key %s", pbone.name)
+ vertexWeight = weightedAverageDictionary[pbone.name]
+ m = pbone.matrix_channel.copy()
+ #m.transpose()
+ sigma += (m - I) * vertexWeight
+
+ else:
+ pass
+ #~ print("no key for bone " + pbone.name)
+
+ sigma = I + sigma
+ sigma.invert()
+ psdMeshVert.co = psdMeshVert.co @ sigma
+ obj.update_tag()
+ bpy.context.view_layer.update()
+"""
+
+"""
+def func_add_corrective_pose_shape_fast(source, target):
+
+ reset_transform(target)
+
+ # If target object doesn't have Basis shape key, create it.
+ if not target.data.shape_keys:
+ basis = target.shape_key_add()
+ basis.name = "Basis"
+ target.data.update()
+
+ key_index = target.active_shape_key_index
+
+ if key_index == 0:
+
+ # Insert new shape key
+ new_shapekey = target.shape_key_add()
+ new_shapekey.name = "Shape_" + source.name
+
+ key_index = len(target.data.shape_keys.key_blocks) - 1
+ target.active_shape_key_index = key_index
+
+ # else, the active shape will be used (updated)
+
+ target.show_only_shape_key = True
+
+ shape_key_verts = target.data.shape_keys.key_blocks[key_index].data
+
+ try:
+ vgroup = target.active_shape_key.vertex_group
+ target.active_shape_key.vertex_group = ''
+ except:
+ pass
+
+ # copy the local vertex positions to the new shape
+ verts = source.data.vertices
+ for n in range(len(verts)):
+ shape_key_verts[n].co = verts[n].co
+ target.update_tag()
+ bpy.context.view_layer.update()
+ # go to all armature modifies and unpose the shape
+ for n in target.modifiers:
+ if n.type == 'ARMATURE' and n.show_viewport:
+ #~ print("got one")
+ n.use_bone_envelopes = False
+ n.use_deform_preserve_volume = False
+ n.use_vertex_groups = True
+ armature = n.object
+ unposeMesh(shape_key_verts, target, armature)
+ break
+
+ # set the new shape key value to 1.0, so we see the result instantly
+ target.active_shape_key.value = 1.0
+
+ try:
+ target.active_shape_key.vertex_group = vgroup
+ except:
+ pass
+
+ target.show_only_shape_key = False
+ target.update_tag()
+ bpy.context.view_layer.update()
+
+ target.data.update()
+
+
+"""
+
+"""
+class add_corrective_pose_shape_fast(bpy.types.Operator):
+ #Adds 1st object as shape to 2nd object as pose shape (only 1 armature)
+
+ bl_idname = "object.add_corrective_pose_shape_fast"
+ bl_label = "Add object as corrective shape faster"
+
+ @classmethod
+ def poll(cls, context):
+ return context.active_object is not None
+
+ def execute(self, context):
+ selection = context.selected_objects
+ if len(selection) != 2:
+ self.report({'ERROR'}, "Select source and target objects")
+ return {'CANCELLED'}
+
+ target = context.active_object
+ if context.active_object == selection[0]:
+ source = selection[1]
+ else:
+ source = selection[0]
+
+ func_add_corrective_pose_shape_fast(source, target)
+
+ return {'FINISHED'}
+"""
+
+
+# -----------------------------------------------------------------------------
+# GUI
+
+def vgroups_draw(self, context):
+ layout = self.layout
+
+ layout.operator("object.object_duplicate_flatten_modifiers",
+ text='Create duplicate for editing')
+ layout.operator("object.add_corrective_pose_shape",
+ text='Add as corrective pose-shape (slow, all modifiers)',
+ icon='COPY_ID') # icon is not ideal
+ layout.operator("object.add_corrective_pose_shape_delta",
+ text='Add as corrective pose-shape delta" (slow, all modifiers + other shape key values)',
+ icon='COPY_ID') # icon is not ideal
+
+
+def modifiers_draw(self, context):
+ pass
+
+classes = (add_corrective_pose_shape, add_corrective_pose_shape_delta, object_duplicate_flatten_modifiers, )
+def register():
+ from bpy.utils import register_class
+ for cls in classes:
+ register_class(cls)
+ bpy.types.MESH_MT_shape_key_context_menu.append(vgroups_draw)
+ bpy.types.DATA_PT_modifiers.append(modifiers_draw)
+
+
+def unregister():
+ from bpy.utils import unregister_class
+ for cls in reversed(classes):
+ unregister_class(cls)
+ bpy.types.MESH_MT_shape_key_context_menu.remove(vgroups_draw)
+ bpy.types.DATA_PT_modifiers.remove(modifiers_draw)
+
+if __name__ == "__main__":
+ register()