Skip to content
Snippets Groups Projects
animation_animall.py 28.2 KiB
Newer Older
  • Learn to ignore specific revisions
  • # SPDX-License-Identifier: GPL-2.0-or-later
    
        "author": "Daniel Salazar (ZanQdo), Damien Picard (pioverfour)",
    
        "version": (0, 9, 3),
    
        "blender": (3, 3, 0),
    
        "location": "3D View > Toolbox > Animation tab > AnimAll",
    
        "description": "Allows animation of mesh, lattice, curve and surface data",
        "warning": "",
    
        "doc_url": "{BLENDER_MANUAL_URL}/addons/animation/animall.html",
    
        "category": "Animation",
    }
    
    import bpy
    from bpy.types import (
            Operator,
            Panel,
            AddonPreferences,
            )
    from bpy.props import (
            BoolProperty,
            StringProperty,
            )
    
    from bpy.app.handlers import persistent
    
    
    
    # Property Definitions
    class AnimallProperties(bpy.types.PropertyGroup):
        key_selected: BoolProperty(
    
            name="Key Selected Only",
    
            description="Insert keyframes only on selected elements",
    
            description="Insert keyframes on active Shape Key layer",
            default=False
        )
        key_uvs: BoolProperty(
    
            description="Insert keyframes on active UV coordinates",
            default=False
        )
        key_vbevel: BoolProperty(
    
            name="Vertex Bevel",
    
            description="Insert keyframes on vertex bevel weight",
            default=False
        )
    
        key_ebevel: BoolProperty(
            name="Edge Bevel",
            description="Insert keyframes on edge bevel weight",
            default=False
        )
        # key_vcrease: BoolProperty(
        #     name="Vertex Crease",
        #     description="Insert keyframes on vertex crease weight",
        #     default=False
        # )
        key_ecrease: BoolProperty(
            name="Edge Crease",
            description="Insert keyframes on edge crease weight",
    
            default=False
        )
        key_vgroups: BoolProperty(
    
            description="Insert keyframes on active vertex group values",
    
        key_attribute: BoolProperty(
    
            description="Insert keyframes on active attribute values",
            default=False
        )
    
        key_points: BoolProperty(
            name="Points",
            description="Insert keyframes on point locations",
            default=False
        )
        key_radius: BoolProperty(
            name="Radius",
            description="Insert keyframes on point radius (Shrink/Fatten)",
            default=False
        )
        key_tilt: BoolProperty(
            name="Tilt",
            description="Insert keyframes on point tilt",
            default=False
        )
    
    
    # Utility functions
    
    def refresh_ui_keyframes():
        try:
            for area in bpy.context.screen.areas:
                if area.type in ('TIMELINE', 'GRAPH_EDITOR', 'DOPESHEET_EDITOR'):
                    area.tag_redraw()
        except:
            pass
    
    
    
    def insert_key(data, key, group=None):
    
            if group is not None:
                data.keyframe_insert(key, group=group)
            else:
                data.keyframe_insert(key)
    
        except:
            pass
    
    
    def delete_key(data, key):
        try:
            data.keyframe_delete(key)
        except:
            pass
    
    
    
    def is_selected_vert_loop(data, loop_i):
        """Get selection status of vertex corresponding to a loop"""
        vertex_index = data.loops[loop_i].vertex_index
        return data.vertices[vertex_index].select
    
    
    
    # GUI (Panel)
    
    class VIEW3D_PT_animall(Panel):
        bl_space_type = 'VIEW_3D'
        bl_region_type = 'UI'
    
        bl_category = "Animate"
    
        bl_label = 'AnimAll'
    
        @classmethod
        def poll(self, context):
            return context.active_object and context.active_object.type in {'MESH', 'LATTICE', 'CURVE', 'SURFACE'}
    
        def draw(self, context):
            obj = context.active_object
    
            animall_properties = obj.animall_properties
    
            layout.label (text = 'Key:')
    
            col = layout.column(align=True)
            row = col.row(align = True)
    
    
            if obj.type == 'LATTICE':
                row.prop(animall_properties, "key_points")
                row.prop(animall_properties, "key_shape")
    
            elif obj.type == 'MESH':
                row.prop(animall_properties, "key_points")
                row.prop(animall_properties, "key_shape")
    
                row = col.row(align = True)
    
                row.prop(animall_properties, "key_vbevel")
    
                row.prop(animall_properties, "key_ebevel")
    
                row = col.row(align = True)
    
                row.prop(animall_properties, "key_ecrease")
    
                row.prop(animall_properties, "key_uvs")
    
                row = col.row(align = True)
    
                row.prop(animall_properties, "key_attribute")
    
                row.prop(animall_properties, "key_vgroups")
    
            # Vertex group update operator
            if (context.active_object is not None
                    and context.active_object.type == 'MESH'
                    and context.active_object.data.animation_data is not None
                    and context.active_object.data.animation_data.action is not None):
                for fcurve in context.active_object.data.animation_data.action.fcurves:
                    if fcurve.data_path.startswith("vertex_colors"):
                        layout.separator()
                        row = layout.row()
                        row.label(text="Object includes old-style vertex colors. Consider updating them.", icon="ERROR")
                        row = layout.row()
                        row.operator("anim.update_vertex_color_animation_animall", icon="FILE_REFRESH")
                        break
    
    
            elif obj.type == 'CURVE':
                row.prop(animall_properties, "key_points")
                row.prop(animall_properties, "key_shape")
    
                row = col.row(align = True)
    
                row.prop(animall_properties, "key_radius")
                row.prop(animall_properties, "key_tilt")
    
            elif obj.type == 'SURFACE':
                row.prop(animall_properties, "key_points")
                row.prop(animall_properties, "key_shape")
    
                row = col.row(align = True)
    
                row.prop(animall_properties, "key_radius")
                row.prop(animall_properties, "key_tilt")
    
    
            col.separator()
            col = layout.column(align=True)
            row = col.row()
            row.prop(animall_properties, "key_selected")
    
    
            row = layout.row(align=True)
            row.operator("anim.insert_keyframe_animall", icon="KEY_HLT")
            row.operator("anim.delete_keyframe_animall", icon="KEY_DEHLT")
            row = layout.row()
    
            row.operator("anim.clear_animation_animall", icon="CANCEL")
    
    
            if animall_properties.key_shape:
                shape_key = obj.active_shape_key
                shape_key_index = obj.active_shape_key_index
    
                split = layout.split()
                row = split.row()
    
                if shape_key_index > 0:
                    row.label(text=shape_key.name, icon="SHAPEKEY_DATA")
                    row.prop(shape_key, "value", text="")
                    row.prop(obj, "show_only_shape_key", text="")
                    if shape_key.value < 1:
                        row = layout.row()
                        row.label(text='Maybe set "%s" to 1.0?' % shape_key.name, icon="INFO")
                elif shape_key:
                    row.label(text="Cannot key on Basis Shape", icon="ERROR")
                else:
                    row.label(text="No active Shape Key", icon="ERROR")
    
            if animall_properties.key_points and animall_properties.key_shape:
                row = layout.row()
                row.label(text='"Points" and "Shape" are redundant?', icon="INFO")
    
    
    class ANIM_OT_insert_keyframe_animall(Operator):
    
        bl_label = "Insert Key"
    
        bl_idname = "anim.insert_keyframe_animall"
        bl_description = "Insert a Keyframe"
        bl_options = {'REGISTER', 'UNDO'}
    
        def execute(op, context):
            animall_properties = context.window_manager.animall_properties
    
    
            if context.mode == 'OBJECT':
                objects = context.selected_objects
            else:
                objects = context.objects_in_mode_unique_data[:]
    
            mode = context.object.mode
    
            # Separate loop for lattices, curves and surfaces, since keyframe insertion
            # has to happen in Edit Mode, otherwise points move back upon mode switch...
            # (except for curve shape keys)
            for obj in [o for o in objects if o.type in {'CURVE', 'SURFACE', 'LATTICE'}]:
                data = obj.data
    
                if obj.type == 'LATTICE':
                    if animall_properties.key_shape:
                        if obj.active_shape_key_index > 0:
                            sk_name = obj.active_shape_key.name
                            for p_i, point in enumerate(obj.active_shape_key.data):
                                if not animall_properties.key_selected or data.points[p_i].select:
                                    insert_key(point, 'co', group="%s Point %s" % (sk_name, p_i))
    
                    if animall_properties.key_points:
                        for p_i, point in enumerate(data.points):
                            if not animall_properties.key_selected or point.select:
                                insert_key(point, 'co_deform', group="Point %s" % p_i)
    
                else:
                    for s_i, spline in enumerate(data.splines):
                        if spline.type == 'BEZIER':
                            for v_i, CV in enumerate(spline.bezier_points):
                                if (not animall_properties.key_selected
                                        or CV.select_control_point
                                        or CV.select_left_handle
                                        or CV.select_right_handle):
                                    if animall_properties.key_points:
                                        insert_key(CV, 'co', group="Spline %s CV %s" % (s_i, v_i))
                                        insert_key(CV, 'handle_left', group="Spline %s CV %s" % (s_i, v_i))
                                        insert_key(CV, 'handle_right', group="Spline %s CV %s" % (s_i, v_i))
    
                                    if animall_properties.key_radius:
                                        insert_key(CV, 'radius', group="Spline %s CV %s" % (s_i, v_i))
    
                                    if animall_properties.key_tilt:
                                        insert_key(CV, 'tilt', group="Spline %s CV %s" % (s_i, v_i))
    
                        elif spline.type in ('POLY', 'NURBS'):
                            for v_i, CV in enumerate(spline.points):
                                if not animall_properties.key_selected or CV.select:
                                    if animall_properties.key_points:
                                        insert_key(CV, 'co', group="Spline %s CV %s" % (s_i, v_i))
    
                                    if animall_properties.key_radius:
                                        insert_key(CV, 'radius', group="Spline %s CV %s" % (s_i, v_i))
    
                                    if animall_properties.key_tilt:
                                        insert_key(CV, 'tilt', group="Spline %s CV %s" % (s_i, v_i))
    
            bpy.ops.object.mode_set(mode='OBJECT')
    
            for obj in [o for o in objects if o.type in {'MESH', 'CURVE', 'SURFACE'}]:
    
                if obj.type == 'MESH':
                    if animall_properties.key_points:
                        for v_i, vert in enumerate(data.vertices):
                            if not animall_properties.key_selected or vert.select:
                                insert_key(vert, 'co', group="Vertex %s" % v_i)
    
                    if animall_properties.key_vbevel:
                        for v_i, vert in enumerate(data.vertices):
                            if not animall_properties.key_selected or vert.select:
                                insert_key(vert, 'bevel_weight', group="Vertex %s" % v_i)
    
    
                    if animall_properties.key_ebevel:
                        for e_i, edge in enumerate(data.edges):
                            if not animall_properties.key_selected or edge.select:
                                insert_key(edge, 'bevel_weight', group="Edge %s" % e_i)
    
    
                    if animall_properties.key_vgroups:
                        for v_i, vert in enumerate(data.vertices):
                            if not animall_properties.key_selected or vert.select:
                                for group in vert.groups:
                                    insert_key(group, 'weight', group="Vertex %s" % v_i)
    
    
                    # if animall_properties.key_vcrease:
                    #     for v_i, vert in enumerate(data.vertices):
                    #         if not animall_properties.key_selected or vert.select:
                    #             insert_key(vert, 'crease', group="Vertex %s" % v_i)
    
                        for e_i, edge in enumerate(data.edges):
                            if not animall_properties.key_selected or edge.select:
                                insert_key(edge, 'crease', group="Edge %s" % e_i)
    
                    if animall_properties.key_shape:
                        if obj.active_shape_key_index > 0:
                            sk_name = obj.active_shape_key.name
                            for v_i, vert in enumerate(obj.active_shape_key.data):
                                if not animall_properties.key_selected or data.vertices[v_i].select:
                                    insert_key(vert, 'co', group="%s Vertex %s" % (sk_name, v_i))
    
                    if animall_properties.key_uvs:
                        if data.uv_layers.active is not None:
                            for uv_i, uv in enumerate(data.uv_layers.active.data):
                                if not animall_properties.key_selected or uv.select:
                                    insert_key(uv, 'uv', group="UV layer %s" % uv_i)
    
    
                    if animall_properties.key_attribute:
                        if data.attributes.active is not None:
                            attribute = data.attributes.active
                            if attribute.data_type != 'STRING':
                                # Cannot animate string attributes?
                                if attribute.data_type in {'FLOAT', 'INT', 'BOOLEAN', 'INT8'}:
                                    attribute_key = "value"
                                elif attribute.data_type in {'FLOAT_COLOR', 'BYTE_COLOR'}:
                                    attribute_key = "color"
                                elif attribute.data_type in {'FLOAT_VECTOR', 'FLOAT2'}:
                                    attribute_key = "vector"
    
                                if attribute.domain == 'POINT':
                                    group = "Vertex %s"
                                elif attribute.domain == 'EDGE':
                                    group = "Edge %s"
                                elif attribute.domain == 'FACE':
                                    group = "Face %s"
                                elif attribute.domain == 'CORNER':
                                    group = "Loop %s"
    
                                for e_i, _attribute_data in enumerate(attribute.data):
                                    if (not animall_properties.key_selected
                                            or attribute.domain == 'POINT' and data.vertices[e_i].select
                                            or attribute.domain == 'EDGE' and data.edges[e_i].select
                                            or attribute.domain == 'FACE' and data.polygons[e_i].select
                                            or attribute.domain == 'CORNER' and is_selected_vert_loop(data, e_i)):
                                        insert_key(data, f'attributes["{attribute.name}"].data[{e_i}].{attribute_key}',
                                                group=group % e_i)
    
    
                elif obj.type in {'CURVE', 'SURFACE'}:
                    # Shape key keys have to be inserted in object mode for curves...
                    if animall_properties.key_shape:
                        sk_name = obj.active_shape_key.name
                        global_spline_index = 0  # numbering for shape keys, which have flattened indices
                        for s_i, spline in enumerate(data.splines):
                            if spline.type == 'BEZIER':
                                for v_i, CV in enumerate(spline.bezier_points):
                                    if (not animall_properties.key_selected
                                            or CV.select_control_point
                                            or CV.select_left_handle
                                            or CV.select_right_handle):
                                        if obj.active_shape_key_index > 0:
                                            CV = obj.active_shape_key.data[global_spline_index]
                                            insert_key(CV, 'co', group="%s Spline %s CV %s" % (sk_name, s_i, v_i))
                                            insert_key(CV, 'handle_left', group="%s Spline %s CV %s" % (sk_name, s_i, v_i))
                                            insert_key(CV, 'handle_right', group="%s Spline %s CV %s" % (sk_name, s_i, v_i))
                                            insert_key(CV, 'radius', group="%s Spline %s CV %s" % (sk_name, s_i, v_i))
                                            insert_key(CV, 'tilt', group="%s Spline %s CV %s" % (sk_name, s_i, v_i))
                                    global_spline_index += 1
    
                            elif spline.type in ('POLY', 'NURBS'):
                                for v_i, CV in enumerate(spline.points):
                                    if not animall_properties.key_selected or CV.select:
                                        if obj.active_shape_key_index > 0:
                                            CV = obj.active_shape_key.data[global_spline_index]
                                            insert_key(CV, 'co', group="%s Spline %s CV %s" % (sk_name, s_i, v_i))
                                            insert_key(CV, 'radius', group="%s Spline %s CV %s" % (sk_name, s_i, v_i))
                                            insert_key(CV, 'tilt', group="%s Spline %s CV %s" % (sk_name, s_i, v_i))
                                    global_spline_index += 1
    
            bpy.ops.object.mode_set(mode=mode)
    
            refresh_ui_keyframes()
    
            return {'FINISHED'}
    
    
    class ANIM_OT_delete_keyframe_animall(Operator):
    
        bl_label = "Delete Key"
    
        bl_idname = "anim.delete_keyframe_animall"
        bl_description = "Delete a Keyframe"
        bl_options = {'REGISTER', 'UNDO'}
    
    
        def execute(op, context):
            animall_properties = context.window_manager.animall_properties
    
    
            if context.mode == 'OBJECT':
                objects = context.selected_objects
            else:
                objects = context.objects_in_mode_unique_data[:]
    
            mode = context.object.mode
    
            for obj in objects:
                data = obj.data
                if obj.type == 'MESH':
                    if animall_properties.key_points:
                        for vert in data.vertices:
                            if not animall_properties.key_selected or vert.select:
                                delete_key(vert, 'co')
    
                    if animall_properties.key_vbevel:
                        for vert in data.vertices:
                            if not animall_properties.key_selected or vert.select:
                                delete_key(vert, 'bevel_weight')
    
    
                    if animall_properties.key_ebevel:
                        for edge in data.edges:
                            if not animall_properties.key_selected or edge.select:
                                delete_key(edge, 'bevel_weight')
    
    
                    if animall_properties.key_vgroups:
                        for vert in data.vertices:
                            if not animall_properties.key_selected or vert.select:
                                for group in vert.groups:
                                    delete_key(group, 'weight')
    
    
                    # if animall_properties.key_vcrease:
                    #     for vert in data.vertices:
                    #         if not animall_properties.key_selected or vert.select:
                    #             delete_key(vert, 'crease')
    
                        for edge in data.edges:
    
                            if not animall_properties.key_selected or edge.select:
    
                                delete_key(edge, 'crease')
    
                    if animall_properties.key_shape:
                        if obj.active_shape_key:
                            for v_i, vert in enumerate(obj.active_shape_key.data):
                                if not animall_properties.key_selected or data.vertices[v_i].select:
                                    delete_key(vert, 'co')
    
                    if animall_properties.key_uvs:
                        if data.uv_layers.active is not None:
                            for uv in data.uv_layers.active.data:
                                if not animall_properties.key_selected or uv.select:
                                    delete_key(uv, 'uv')
    
    
                    if animall_properties.key_attribute:
                        if data.attributes.active is not None:
                            attribute = data.attributes.active
                            if attribute.data_type != 'STRING':
                                # Cannot animate string attributes?
                                if attribute.data_type in {'FLOAT', 'INT', 'BOOLEAN', 'INT8'}:
                                    attribute_key = "value"
                                elif attribute.data_type in {'FLOAT_COLOR', 'BYTE_COLOR'}:
                                    attribute_key = "color"
                                elif attribute.data_type in {'FLOAT_VECTOR', 'FLOAT2'}:
                                    attribute_key = "vector"
    
                                for e_i, _attribute_data in enumerate(attribute.data):
                                    if (not animall_properties.key_selected
                                            or attribute.domain == 'POINT' and data.vertices[e_i].select
                                            or attribute.domain == 'EDGE' and data.edges[e_i].select
                                            or attribute.domain == 'FACE' and data.polygons[e_i].select
                                            or attribute.domain == 'CORNER' and is_selected_vert_loop(data, e_i)):
                                        delete_key(data, f'attributes["{attribute.name}"].data[{e_i}].{attribute_key}')
    
    
                elif obj.type == 'LATTICE':
                    if animall_properties.key_shape:
                        if obj.active_shape_key:
                            for point in obj.active_shape_key.data:
                                delete_key(point, 'co')
    
                    if animall_properties.key_points:
                        for point in data.points:
                            if not animall_properties.key_selected or point.select:
                                delete_key(point, 'co_deform')
    
                elif obj.type in {'CURVE', 'SURFACE'}:
                    # run this outside the splines loop (only once)
                    if animall_properties.key_shape:
                        if obj.active_shape_key_index > 0:
                            for CV in obj.active_shape_key.data:
    
                                delete_key(CV, 'co')
                                delete_key(CV, 'handle_left')
                                delete_key(CV, 'handle_right')
    
    
                    for spline in data.splines:
                        if spline.type == 'BEZIER':
                            for CV in spline.bezier_points:
                                if (not animall_properties.key_selected
                                        or CV.select_control_point
                                        or CV.select_left_handle
                                        or CV.select_right_handle):
                                    if animall_properties.key_points:
                                        delete_key(CV, 'co')
                                        delete_key(CV, 'handle_left')
                                        delete_key(CV, 'handle_right')
                                    if animall_properties.key_radius:
                                        delete_key(CV, 'radius')
                                    if animall_properties.key_tilt:
                                        delete_key(CV, 'tilt')
    
                        elif spline.type in ('POLY', 'NURBS'):
                            for CV in spline.points:
                                if not animall_properties.key_selected or CV.select:
                                    if animall_properties.key_points:
                                        delete_key(CV, 'co')
                                    if animall_properties.key_radius:
                                        delete_key(CV, 'radius')
                                    if animall_properties.key_tilt:
                                        delete_key(CV, 'tilt')
    
    
            refresh_ui_keyframes()
    
            return {'FINISHED'}
    
    
    class ANIM_OT_clear_animation_animall(Operator):
        bl_label = "Clear Animation"
        bl_idname = "anim.clear_animation_animall"
        bl_description = ("Delete all keyframes for this object\n"
                          "If in a specific case it doesn't work\n"
                          "try to delete the keys manually")
        bl_options = {'REGISTER', 'UNDO'}
    
        def invoke(self, context, event):
            wm = context.window_manager
            return wm.invoke_confirm(self, event)
    
        def execute(self, context):
    
            if context.mode == 'OBJECT':
                objects = context.selected_objects
            else:
                objects = context.objects_in_mode_unique_data
    
            for obj in objects:
                try:
                    data = obj.data
                    data.animation_data_clear()
                except:
                    self.report({'WARNING'}, "Clear Animation could not be performed")
                    return {'CANCELLED'}
    
    class ANIM_OT_update_vertex_color_animation_animall(Operator):
        bl_label = "Update Vertex Color Animation"
        bl_idname = "anim.update_vertex_color_animation_animall"
        bl_description = "Update old vertex color channel formats from pre-3.3 versions"
        bl_options = {'REGISTER', 'UNDO'}
    
        @classmethod
        def poll(self, context):
            if (context.active_object is None
                    or context.active_object.type != 'MESH'
                    or context.active_object.data.animation_data is None
                    or context.active_object.data.animation_data.action is None):
                return False
            for fcurve in context.active_object.data.animation_data.action.fcurves:
                if fcurve.data_path.startswith("vertex_colors"):
                    return True
    
        def execute(self, context):
            for fcurve in context.active_object.data.animation_data.action.fcurves:
                if fcurve.data_path.startswith("vertex_colors"):
                    fcurve.data_path = fcurve.data_path.replace("vertex_colors", "attributes")
            return {'FINISHED'}
    
    
    # Add-ons Preferences Update Panel
    
    # Define Panel classes for updating
    panels = [
            VIEW3D_PT_animall
            ]
    
    
    def update_panel(self, context):
        message = "AnimAll: Updating Panel locations has failed"
        try:
            for panel in panels:
                if "bl_rna" in panel.__dict__:
                    bpy.utils.unregister_class(panel)
    
            for panel in panels:
                panel.bl_category = context.preferences.addons[__name__].preferences.category
                bpy.utils.register_class(panel)
    
        except Exception as e:
            print("\n[{}]\n{}\n\nError:\n{}".format(__name__, message, e))
            pass
    
    
    class AnimallAddonPreferences(AddonPreferences):
        # this must match the addon name, use '__package__'
        # when defining this in a submodule of a python package.
        bl_idname = __name__
    
        category: StringProperty(
            name="Tab Category",
            description="Choose a name for the category of the panel",
    
            default="Animate",
    
            update=update_panel
        )
    
        def draw(self, context):
            layout = self.layout
            row = layout.row()
            col = row.column()
    
            col.label(text="Tab Category:")
            col.prop(self, "category", text="")
    
    
    def register():
        bpy.utils.register_class(AnimallProperties)
    
        bpy.types.Object.animall_properties = bpy.props.PointerProperty(type=AnimallProperties)
    
        bpy.utils.register_class(VIEW3D_PT_animall)
        bpy.utils.register_class(ANIM_OT_insert_keyframe_animall)
        bpy.utils.register_class(ANIM_OT_delete_keyframe_animall)
        bpy.utils.register_class(ANIM_OT_clear_animation_animall)
    
        bpy.utils.register_class(ANIM_OT_update_vertex_color_animation_animall)
    
        bpy.utils.register_class(AnimallAddonPreferences)
        update_panel(None, bpy.context)
    
    
    def unregister():
    
        del bpy.types.Object.animall_properties
    
        bpy.utils.unregister_class(AnimallProperties)
        bpy.utils.unregister_class(VIEW3D_PT_animall)
        bpy.utils.unregister_class(ANIM_OT_insert_keyframe_animall)
        bpy.utils.unregister_class(ANIM_OT_delete_keyframe_animall)
        bpy.utils.unregister_class(ANIM_OT_clear_animation_animall)
    
        bpy.utils.unregister_class(ANIM_OT_update_vertex_color_animation_animall)
    
        bpy.utils.unregister_class(AnimallAddonPreferences)
    
    if __name__ == "__main__":
        register()