este addon precisa de opção de fazer os sticks esquerda e direita responderen a uma nota e reproduzir para que a animação ocorra na timilene do blender: bl_info = {
    "name": "Drumsticks Complex Rig Procedural Animation with Controls",
    "author": "ChatGPT",
    "version": (1, 4),
    "blender": (2, 80, 0),
    "location": "3D View > Sidebar > Drumsticks",
    "description": "Baquetas com rig complexo, animação procedural com controles, gravação e limpeza",
    "category": "Animation",
}

import bpy
from math import radians

# -------------- Propriedades customizadas --------------

def register_props():
    scn = bpy.types.Scene
    scn.drumsticks_duration = bpy.props.IntProperty(
        name="Duração da Animação",
        description="Duração do toque em frames",
        default=20,
        min=1,
        max=120
    )
    scn.drumsticks_angle_handle = bpy.props.FloatProperty(
        name="Ângulo Handle (°)",
        default=10,
        min=0,
        max=90,
        description="Ângulo máximo de rotação do osso Handle"
    )
    scn.drumsticks_angle_main = bpy.props.FloatProperty(
        name="Ângulo Main (°)",
        default=40,
        min=0,
        max=90,
        description="Ângulo máximo de rotação do osso Main"
    )
    scn.drumsticks_angle_mid = bpy.props.FloatProperty(
        name="Ângulo Mid (°)",
        default=10,
        min=0,
        max=90,
        description="Ângulo máximo de rotação do osso Mid"
    )
    scn.drumsticks_angle_tip = bpy.props.FloatProperty(
        name="Ângulo Tip (°)",
        default=5,
        min=0,
        max=90,
        description="Ângulo máximo de rotação do osso Tip"
    )
    scn.drumsticks_animate_handle = bpy.props.BoolProperty(
        name="Animar Handle",
        default=True,
        description="Ativa rotação do osso Handle na animação do toque"
    )

def unregister_props():
    scn = bpy.types.Scene
    for prop in [
            "drumsticks_duration",
            "drumsticks_angle_handle",
            "drumsticks_angle_main",
            "drumsticks_angle_mid",
            "drumsticks_angle_tip",
            "drumsticks_animate_handle"
        ]:
        if hasattr(scn, prop):
            delattr(scn, prop)

# -------------- Estado da animação (globais) -------------

state = {
    "left_hit": False,
    "left_frame": 0,
    "right_hit": False,
    "right_frame": 0,
}

# -------------- Função de animação procedural (handler) -------------

def animate_baquetas(scene):
    arm_left = bpy.data.objects.get("Armature_Left")
    arm_right = bpy.data.objects.get("Armature_Right")
    if not arm_left or not arm_right:
        return

    duration = scene.drumsticks_duration
    angle_handle_deg = scene.drumsticks_angle_handle
    angle_main_deg = scene.drumsticks_angle_main
    angle_mid_deg = scene.drumsticks_angle_mid
    angle_tip_deg = scene.drumsticks_angle_tip
    animate_handle = scene.drumsticks_animate_handle

    def curve(x):
        return 1 - (x - 0.5)**2 * 4

    def animate_single(arm, hit, frame):
        if not hit:
            return

        progress = frame / duration

        pb_handle = arm.pose.bones.get("Bone_Handle")
        pb_main = arm.pose.bones.get("Bone_Main")
        pb_mid = arm.pose.bones.get("Bone_Mid")
        pb_tip = arm.pose.bones.get("Bone_Tip")

        if pb_handle and animate_handle:
            pb_handle.rotation_mode = 'XYZ'
            pb_handle.rotation_euler.x = radians(angle_handle_deg) * curve(progress)

        if pb_main:
            pb_main.rotation_mode = 'XYZ'
            pb_main.rotation_euler.x = radians(angle_main_deg) * curve(progress)

        if pb_mid:
            pb_mid.rotation_mode = 'XYZ'
            pb_mid.rotation_euler.x = radians(angle_mid_deg) * curve(progress)

        if pb_tip:
            pb_tip.rotation_mode = 'XYZ'
            pb_tip.rotation_euler.x = radians(angle_tip_deg) * curve(progress)

    # Esquerda
    if state["left_hit"]:
        animate_single(arm_left, True, state["left_frame"])
        state["left_frame"] += 1
        if state["left_frame"] > duration:
            state["left_hit"] = False
            state["left_frame"] = 0
            for bname in ["Bone_Handle","Bone_Main", "Bone_Mid", "Bone_Tip"]:
                pb = arm_left.pose.bones.get(bname)
                if pb:
                    pb.rotation_euler.x = 0

    # Direita
    if state["right_hit"]:
        animate_single(arm_right, True, state["right_frame"])
        state["right_frame"] += 1
        if state["right_frame"] > duration:
            state["right_hit"] = False
            state["right_frame"] = 0
            for bname in ["Bone_Handle","Bone_Main", "Bone_Mid", "Bone_Tip"]:
                pb = arm_right.pose.bones.get(bname)
                if pb:
                    pb.rotation_euler.x = 0

# ------------ Funções pra iniciar animação -------------

def start_left_hit():
    state["left_hit"] = True
    state["left_frame"] = 0

def start_right_hit():
    state["right_hit"] = True
    state["right_frame"] = 0

# ------------ Operador modal -------------

class ModalDrumsticksOperator(bpy.types.Operator):
    """Controle modal das baquetas - Procedural"""
    bl_idname = "wm.modal_drumsticks_procedural"
    bl_label = "Controlo das Baquetas (Procedural)"

    def modal(self, context, event):
        if event.type == 'ESC' and event.value == 'PRESS':
            if animate_baquetas in bpy.app.handlers.frame_change_post:
                bpy.app.handlers.frame_change_post.remove(animate_baquetas)
            context.window_manager.modal_handler_remove(self)
            self.report({'INFO'}, "Modal parado e handler removido")
            return {'CANCELLED'}

        if event.type == 'LEFT_ARROW' and event.value == 'PRESS':
            start_left_hit()

        if event.type == 'RIGHT_ARROW' and event.value == 'PRESS':
            start_right_hit()

        return {'PASS_THROUGH'}

    def execute(self, context):
        if animate_baquetas not in bpy.app.handlers.frame_change_post:
            bpy.app.handlers.frame_change_post.append(animate_baquetas)
        context.window_manager.modal_handler_add(self)
        self.report({'INFO'}, "Modal iniciado (ESC para sair)")
        return {'RUNNING_MODAL'}

# ------------ Criar rig complexo -------------

def create_more_complex_rig(context, location, arm_name, stick_name):
    objs = bpy.data.objects
    for name in [arm_name, stick_name]:
        if name in objs:
            bpy.data.objects.remove(objs[name], do_unlink=True)

    bpy.ops.mesh.primitive_cylinder_add(vertices=16, radius=0.03, depth=1.5, location=location)
    stick = context.active_object
    stick.name = stick_name
    stick.rotation_euler[0] = 1.57

    bpy.ops.object.armature_add(enter_editmode=True, location=location)
    arm = context.active_object
    arm.name = arm_name
    arm.show_in_front = True

    arm.data.edit_bones.remove(arm.data.edit_bones[0])

    bone_handle = arm.data.edit_bones.new("Bone_Handle")
    bone_handle.head = (0, 0, 0)
    bone_handle.tail = (0, 0, 0.4)
    bone_handle.roll = 0

    bone_main = arm.data.edit_bones.new("Bone_Main")
    bone_main.head = bone_handle.tail
    bone_main.tail = (0, 0, 0.8)
    bone_main.parent = bone_handle
    bone_main.use_connect = True

    bone_mid = arm.data.edit_bones.new("Bone_Mid")
    bone_mid.head = bone_main.tail
    bone_mid.tail = (0, 0, 1.05)
    bone_mid.parent = bone_main
    bone_mid.use_connect = True

    bone_tip = arm.data.edit_bones.new("Bone_Tip")
    bone_tip.head = bone_mid.tail
    bone_tip.tail = (0, 0, 1.3)
    bone_tip.parent = bone_mid
    bone_tip.use_connect = True

    bpy.ops.object.mode_set(mode='OBJECT')

    context.view_layer.objects.active = stick
    stick.select_set(True)
    arm.select_set(True)
    bpy.ops.object.parent_set(type='ARMATURE_AUTO')
    stick.select_set(False)
    arm.select_set(False)

    arm.rotation_euler[0] = 1.57
    stick.rotation_euler[0] = 1.57

    return arm, stick

# -------- Operador criar rig ---------

class CreateDrumsticksRigOperator(bpy.types.Operator):
    bl_idname = "object.create_drumsticks_rig_proc"
    bl_label = "Criar Baquetas + Rig Complexo"
    bl_description = "Cria baquetas com rig complexo para animação procedural"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        objs = bpy.data.objects
        for name in ["Drumstick_Left", "Drumstick_Right", "Armature_Left", "Armature_Right"]:
            if name in objs:
                bpy.data.objects.remove(objs[name], do_unlink=True)

        create_more_complex_rig(context, (-0.3, 0, 1), "Armature_Left", "Drumstick_Left")
        create_more_complex_rig(context, (0.3, 0, 1), "Armature_Right", "Drumstick_Right")

        self.report({'INFO'}, "Baquetas e rig complexo criados")
        return {'FINISHED'}

# ---------- Operador grava animação -------------

class RecordHitAnimationOperator(bpy.types.Operator):
    bl_idname = "object.record_hit_animation"
    bl_label = "Gravar animação de toque na timeline"
    bl_description = "Grava keyframes da animação procedural da baqueta na timeline"

    side: bpy.props.EnumProperty(
        items=[
            ('LEFT', "Baqueta Esquerda", ""),
            ('RIGHT', "Baqueta Direita", "")
        ],
        name="Baqueta"
    )

    def execute(self, context):
        scene = context.scene
        duration = scene.drumsticks_duration

        arm_name = "Armature_Left" if self.side == 'LEFT' else "Armature_Right"
        arm = bpy.data.objects.get(arm_name)
        if not arm:
            self.report({'ERROR'}, f"{arm_name} não encontrado!")
            return {'CANCELLED'}

        pb_handle = arm.pose.bones.get("Bone_Handle")
        pb_main = arm.pose.bones.get("Bone_Main")
        pb_mid = arm.pose.bones.get("Bone_Mid")
        pb_tip = arm.pose.bones.get("Bone_Tip")
        if not pb_handle or not pb_main or not pb_mid or not pb_tip:
            self.report({'ERROR'}, f"Ossos necessários não encontrados em {arm_name}")
            return {'CANCELLED'}

        angle_handle_deg = scene.drumsticks_angle_handle
        angle_main_deg = scene.drumsticks_angle_main
        angle_mid_deg = scene.drumsticks_angle_mid
        angle_tip_deg = scene.drumsticks_angle_tip
        animate_handle = scene.drumsticks_animate_handle

        start_frame = scene.frame_current

        def curve(progress):
            return 1 - (progress - 0.5)**2 * 4

        for f in range(duration + 1):
            progress = f / duration
            if animate_handle:
                pb_handle.rotation_mode = 'XYZ'
                pb_handle.rotation_euler.x = radians(angle_handle_deg) * curve(progress)
            else:
                pb_handle.rotation_euler.x = 0

            pb_main.rotation_mode = 'XYZ'
            pb_mid.rotation_mode = 'XYZ'
            pb_tip.rotation_mode = 'XYZ'

            pb_main.rotation_euler.x = radians(angle_main_deg) * curve(progress)
            pb_mid.rotation_euler.x = radians(angle_mid_deg) * curve(progress)
            pb_tip.rotation_euler.x = radians(angle_tip_deg) * curve(progress)

            pb_handle.keyframe_insert(data_path="rotation_euler", frame=start_frame + f)
            pb_main.keyframe_insert(data_path="rotation_euler", frame=start_frame + f)
            pb_mid.keyframe_insert(data_path="rotation_euler", frame=start_frame + f)
            pb_tip.keyframe_insert(data_path="rotation_euler", frame=start_frame + f)

        # Reset rotações viewport
        for pb in (pb_handle, pb_main, pb_mid, pb_tip):
            pb.rotation_euler.x = 0

        self.report({'INFO'}, f"Animação da baqueta {self.side.lower()} gravada da frame {start_frame} a {start_frame+duration}")
        return {'FINISHED'}

# ----------- Operador limpar animação -------------

class ClearHitAnimationOperator(bpy.types.Operator):
    bl_idname = "object.clear_hit_animation"
    bl_label = "Limpar animação gravada"
    bl_description = "Remove todos os keyframes dos ossos da baqueta selecionada"

    side: bpy.props.EnumProperty(
        items=[
            ('LEFT', "Baqueta Esquerda", ""),
            ('RIGHT', "Baqueta Direita", "")
        ],
        name="Baqueta"
    )

    def execute(self, context):
        arm_name = "Armature_Left" if self.side == 'LEFT' else "Armature_Right"
        arm = bpy.data.objects.get(arm_name)
        if not arm:
            self.report({'ERROR'}, f"{arm_name} não encontrado!")
            return {'CANCELLED'}

        bones_to_clear = ["Bone_Handle", "Bone_Main", "Bone_Mid", "Bone_Tip"]
        anim_data = arm.animation_data
        if anim_data is None or anim_data.action is None:
            self.report({'INFO'}, "Nenhuma animação para limpar")
            return {'FINISHED'}

        action = anim_data.action
        cleared = False

        for bone_name in bones_to_clear:
            fcurves = [fc for fc in action.fcurves if fc.data_path.startswith(f'pose.bones["{bone_name}"].rotation_euler')]
            if not fcurves:
                continue
            cleared = True
            for fc in fcurves:
                fc.keyframe_points.clear()

        if cleared:
            self.report({'INFO'}, f"Keyframes da baqueta {self.side.lower()} removidos")
        else:
            self.report({'INFO'}, "Nenhuma curva de animação encontrada para limpar")
        return {'FINISHED'}

# ----------- Painel principal e painel de configuração -----------

class DrumsticksProceduralPanel(bpy.types.Panel):
    bl_label = "Drumsticks Procedural"
    bl_idname = "VIEW3D_PT_drumsticks_procedural"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = 'Drumsticks'

    def draw(self, context):
        layout = self.layout
        layout.operator("object.create_drumsticks_rig_proc", icon='ARMATURE_DATA')

        if animate_baquetas in bpy.app.handlers.frame_change_post:
            layout.operator("wm.modal_drumsticks_procedural", text="Parar Controle (ESC)", icon='PAUSE')
        else:
            layout.operator("wm.modal_drumsticks_procedural", text="Iniciar Controle", icon='PLAY')

        row = layout.row()
        row.operator("object.record_hit_animation", text="Gravar Toque Esquerdo").side = 'LEFT'
        row.operator("object.record_hit_animation", text="Gravar Toque Direito").side = 'RIGHT'

        layout.label(text="Limpar animação gravada:")
        row = layout.row()
        row.operator("object.clear_hit_animation", text="Limpar Esquerda").side = 'LEFT'
        row.operator("object.clear_hit_animation", text="Limpar Direita").side = 'RIGHT'

class DrumstickAnimSettingsPanel(bpy.types.Panel):
    bl_label = "Configurações da Animação das Baquetas"
    bl_idname = "VIEW3D_PT_drumstick_anim_settings"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = "Drumsticks"

    def draw(self, context):
        layout = self.layout
        scn = context.scene

        layout.prop(scn, "drumsticks_duration")
        layout.prop(scn, "drumsticks_animate_handle")

        layout.prop(scn, "drumsticks_angle_handle")
        layout.prop(scn, "drumsticks_angle_main")
        layout.prop(scn, "drumsticks_angle_mid")
        layout.prop(scn, "drumsticks_angle_tip")

# ------------ Registro ------------

classes = (
    ModalDrumsticksOperator,
    CreateDrumsticksRigOperator,
    RecordHitAnimationOperator,
    ClearHitAnimationOperator,
    DrumsticksProceduralPanel,
    DrumstickAnimSettingsPanel,
)

def register():
    register_props()
    for cls in classes:
        bpy.utils.register_class(cls)

def unregister():
    if animate_baquetas in bpy.app.handlers.frame_change_post: 
        bpy.app.handlers.frame_change_post.remove(animate_baquetas)
    for cls in reversed(classes):
        bpy.utils.unregister_class(cls)
    unregister_props()

if __name__ == "__main__":
    register()