diff options
author | meta-androcto <meta.androcto1@gmail.com> | 2019-06-23 13:40:37 +0300 |
---|---|---|
committer | meta-androcto <meta.androcto1@gmail.com> | 2019-06-23 13:40:37 +0300 |
commit | 9ec9bb2354dc91268aac6094374aa504e076ad38 (patch) | |
tree | c6e488c3558505e38fef8681a83dad01416deb4f | |
parent | f3d58200d5e34aacf41162181e0435d65c464753 (diff) |
animation_add_corrective_shape_key: return to release: T65258 38475602b2ef
-rw-r--r-- | animation_add_corrective_shape_key.py | 530 |
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() |