# SPDX-License-Identifier: GPL-2.0-or-later bl_info = { "name": "AnimAll", "author": "Daniel Salazar (ZanQdo), Damien Picard (pioverfour)", "version": (0, 9, 6), "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 from bpy.app.translations import (pgettext_iface as iface_, pgettext_data as data_) from . import translations # Property Definitions class AnimallProperties(bpy.types.PropertyGroup): key_selected: BoolProperty( name="Key Selected Only", description="Insert keyframes only on selected elements", default=False) # Generic attributes key_point_location: BoolProperty( name="Location", description="Insert keyframes on point locations", default=False) key_shape_key: BoolProperty( name="Shape Key", description="Insert keyframes on active Shape Key layer", default=False) key_material_index: BoolProperty( name="Material Index", description="Insert keyframes on face material indices", default=False) # Mesh attributes key_vertex_bevel: BoolProperty( name="Vertex Bevel", description="Insert keyframes on vertex bevel weight", default=False) # key_vertex_crease: BoolProperty( # name="Vertex Crease", # description="Insert keyframes on vertex crease weight", # default=False) key_vertex_group: BoolProperty( name="Vertex Group", description="Insert keyframes on active vertex group values", default=False) key_edge_bevel: BoolProperty( name="Edge Bevel", description="Insert keyframes on edge bevel weight", default=False) key_edge_crease: BoolProperty( name="Edge Crease", description="Insert keyframes on edge creases", default=False) key_attribute: BoolProperty( name="Attribute", description="Insert keyframes on active attribute values", default=False) key_uvs: BoolProperty( name="UV Map", description="Insert keyframes on active UV coordinates", default=False) # Curve and surface attributes 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): try: 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 = '' @classmethod def poll(self, context): return context.active_object and context.active_object.type in {'MESH', 'LATTICE', 'CURVE', 'SURFACE'} def draw_header(self, context): layout = self.layout row = layout.row() row.label (text = 'AnimAll', icon = 'ARMATURE_DATA') def draw(self, context): obj = context.active_object animall_properties = context.scene.animall_properties layout = self.layout layout.label(text='Key:') layout.use_property_split = True layout.use_property_decorate = False if obj.type == 'LATTICE': col = layout.column(heading="Points", align=True) col.prop(animall_properties, "key_point_location") col = layout.column(heading="Others", align=True) col.prop(animall_properties, "key_shape_key") elif obj.type == 'MESH': col = layout.column(heading="Points", align=True) col.prop(animall_properties, "key_point_location") col.prop(animall_properties, "key_vertex_bevel", text="Bevel") col.prop(animall_properties, "key_vertex_group") col = layout.column(heading="Edges", align=True) col.prop(animall_properties, "key_edge_bevel", text="Bevel") col.prop(animall_properties, "key_edge_crease", text="Crease") col = layout.column(heading="Faces", align=True) col.prop(animall_properties, "key_material_index") col = layout.column(heading="Others", align=True) col.prop(animall_properties, "key_attribute") col.prop(animall_properties, "key_uvs") col.prop(animall_properties, "key_shape_key") # Vertex group update operator if (obj.data.animation_data is not None and obj.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"): col = layout.column(align=True) col.label(text="Object includes old-style vertex colors. Consider updating them.", icon="ERROR") col.operator("anim.update_vertex_color_animation_animall", icon="FILE_REFRESH") break elif obj.type in {'CURVE', 'SURFACE'}: col = layout.column(align=True) col = layout.column(heading="Points", align=True) col.prop(animall_properties, "key_point_location") col.prop(animall_properties, "key_radius") col.prop(animall_properties, "key_tilt") col = layout.column(heading="Splines", align=True) col.prop(animall_properties, "key_material_index") col = layout.column(heading="Others", align=True) col.prop(animall_properties, "key_shape_key") if animall_properties.key_shape_key: shape_key = obj.active_shape_key shape_key_index = obj.active_shape_key_index if shape_key_index > 0: col = layout.column(align=True) row = col.row(align=True) row.prop(shape_key, "value", text=shape_key.name, icon="SHAPEKEY_DATA") row.prop(obj, "show_only_shape_key", text="") if shape_key.value < 1: col.label(text=iface_('Maybe set "%s" to 1.0?') % shape_key.name, icon="INFO") elif shape_key is not None: col = layout.column(align=True) col.label(text="Cannot key on Basis Shape", icon="ERROR") else: col = layout.column(align=True) col.label(text="No active Shape Key", icon="ERROR") if animall_properties.key_point_location: col.label(text='"Location" and "Shape Key" are redundant?', icon="INFO") layout.use_property_split = False layout.separator() row = layout.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") 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(self, context): animall_properties = context.scene.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_key: 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=data_("%s Point %s") % (sk_name, p_i)) if animall_properties.key_point_location: for p_i, point in enumerate(data.points): if not animall_properties.key_selected or point.select: insert_key(point, 'co_deform', group=data_("Point %s") % p_i) else: if animall_properties.key_material_index: for s_i, spline in enumerate(data.splines): if (not animall_properties.key_selected or any(point.select for point in spline.points) or any(point.select_control_point for point in spline.bezier_points)): insert_key(spline, 'material_index', group=data_("Spline %s") % s_i) 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_point_location: insert_key(CV, 'co', group=data_("Spline %s CV %s") % (s_i, v_i)) insert_key(CV, 'handle_left', group=data_("Spline %s CV %s") % (s_i, v_i)) insert_key(CV, 'handle_right', group=data_("Spline %s CV %s") % (s_i, v_i)) if animall_properties.key_radius: insert_key(CV, 'radius', group=data_("Spline %s CV %s") % (s_i, v_i)) if animall_properties.key_tilt: insert_key(CV, 'tilt', group=data_("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_point_location: insert_key(CV, 'co', group=data_("Spline %s CV %s") % (s_i, v_i)) if animall_properties.key_radius: insert_key(CV, 'radius', group=data_("Spline %s CV %s") % (s_i, v_i)) if animall_properties.key_tilt: insert_key(CV, 'tilt', group=data_("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'}]: data = obj.data if obj.type == 'MESH': if animall_properties.key_point_location: for v_i, vert in enumerate(data.vertices): if not animall_properties.key_selected or vert.select: insert_key(vert, 'co', group=data_("Vertex %s") % v_i) if animall_properties.key_vertex_bevel: for v_i, vert in enumerate(data.vertices): if not animall_properties.key_selected or vert.select: insert_key(vert, 'bevel_weight', group=data_("Vertex %s") % v_i) # if animall_properties.key_vertex_crease: # for v_i, vert in enumerate(data.vertices): # if not animall_properties.key_selected or vert.select: # insert_key(vert, 'crease', group=data_("Vertex %s") % v_i) if animall_properties.key_vertex_group: 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=data_("Vertex %s") % v_i) if animall_properties.key_edge_bevel: for e_i, edge in enumerate(data.edges): if not animall_properties.key_selected or edge.select: insert_key(edge, 'bevel_weight', group=data_("Edge %s") % e_i) if animall_properties.key_edge_crease: for e_i, edge in enumerate(data.edges): if not animall_properties.key_selected or edge.select: insert_key(edge, 'crease', group=data_("Edge %s") % e_i) if animall_properties.key_material_index: for p_i, polygon in enumerate(data.polygons): if not animall_properties.key_selected or polygon.select: insert_key(polygon, 'material_index', group=data_("Face %s") % p_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 = data_("Vertex %s") elif attribute.domain == 'EDGE': group = data_("Edge %s") elif attribute.domain == 'FACE': group = data_("Face %s") elif attribute.domain == 'CORNER': group = data_("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) 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=data_("UV layer %s") % uv_i) if animall_properties.key_shape_key: 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=data_("%s Vertex %s") % (sk_name, v_i)) elif obj.type in {'CURVE', 'SURFACE'}: # Shape key keys have to be inserted in object mode for curves... if animall_properties.key_shape_key: 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=data_("%s Spline %s CV %s") % (sk_name, s_i, v_i)) insert_key(CV, 'handle_left', group=data_("%s Spline %s CV %s") % (sk_name, s_i, v_i)) insert_key(CV, 'handle_right', group=data_("%s Spline %s CV %s") % (sk_name, s_i, v_i)) insert_key(CV, 'radius', group=data_("%s Spline %s CV %s") % (sk_name, s_i, v_i)) insert_key(CV, 'tilt', group=data_("%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=data_("%s Spline %s CV %s") % (sk_name, s_i, v_i)) insert_key(CV, 'radius', group=data_("%s Spline %s CV %s") % (sk_name, s_i, v_i)) insert_key(CV, 'tilt', group=data_("%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(self, context): animall_properties = context.scene.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_point_location: for vert in data.vertices: if not animall_properties.key_selected or vert.select: delete_key(vert, 'co') if animall_properties.key_vertex_bevel: for vert in data.vertices: if not animall_properties.key_selected or vert.select: delete_key(vert, 'bevel_weight') if animall_properties.key_vertex_group: 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_vertex_crease: # for vert in data.vertices: # if not animall_properties.key_selected or vert.select: # delete_key(vert, 'crease') if animall_properties.key_edge_bevel: for edge in data.edges: if not animall_properties.key_selected or edge.select: delete_key(edge, 'bevel_weight') if animall_properties.key_edge_crease: for edge in data.edges: if not animall_properties.key_selected or vert.select: delete_key(edge, 'crease') if animall_properties.key_shape_key: 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_key: if obj.active_shape_key: for point in obj.active_shape_key.data: delete_key(point, 'co') if animall_properties.key_point_location: 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_key: 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_point_location: 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_point_location: 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'} refresh_ui_keyframes() return {'FINISHED'} 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="") register_classes, unregister_classes = bpy.utils.register_classes_factory( (AnimallProperties, VIEW3D_PT_animall, ANIM_OT_insert_keyframe_animall, ANIM_OT_delete_keyframe_animall, ANIM_OT_clear_animation_animall, ANIM_OT_update_vertex_color_animation_animall, AnimallAddonPreferences)) def register(): register_classes() bpy.types.Scene.animall_properties = bpy.props.PointerProperty(type=AnimallProperties) update_panel(None, bpy.context) bpy.app.translations.register(__name__, translations.translations_dict) def unregister(): bpy.app.translations.unregister(__name__) del bpy.types.Scene.animall_properties unregister_classes() if __name__ == "__main__": register()