Quick Graph Sampling Operator

I made a script to aid me getting into a more classic animation workflow and thought I might as well share it here. Inspired by some article on when to spline your animation curves, I tried alternating between bezier and constant interpolation to get the most fluid results while keeping the animation stepped for maximum overview. Needless to say that is very tedious. The article mentioned a tool in Maya that basically does the back and forth for you. So here is my attempt at making that function in Blender.

import bpy
from bpy.props import (
    IntProperty,
    EnumProperty
)

def main(context,type, step, insert,keyframe):
    
    cache_keyframe = bpy.context.scene.tool_settings.keyframe_type
    
    start = 0
    end = 1
    
    bpy.context.scene.tool_settings.keyframe_type = keyframe
    
    if context.scene.use_preview_range:
        start = context.scene.frame_preview_start
        end = context.scene.frame_preview_end
        
    else:
        start = context.scene.frame_start
        end = context.scene.frame_end
        
    if(insert == 'ALL'):
        bpy.ops.graph.select_all(action='SELECT')
    
    if(type != 'NONE'):
        bpy.ops.graph.interpolation_type(type=type)
        bpy.ops.graph.handle_type(type='AUTO_CLAMPED')
        
    for i in range(start,end,step):
        context.scene.frame_set(i)
        bpy.ops.graph.keyframe_insert(type=insert)
        
    bpy.ops.graph.interpolation_type(type='CONSTANT')
    
    bpy.context.scene.tool_settings.keyframe_type = cache_keyframe


class SampleCurvesStepped(bpy.types.Operator):
    """Sample Curves only at the desired step for all selected Objects or Bones (based on auto clamped splines)"""
    bl_idname = "curve.sample_curve_stepped"
    bl_label = "Sample Curve Stepped"
    bl_options = {'REGISTER', 'UNDO'}
    
    step: IntProperty(
        name = "Step",
        description = "Keyframe Step",
        min = 1, 
        default = 8,)
        
    type_items = (
        ('NONE',"Current","Use current interpolation Type (do not change)"),
        ('LINEAR',"Linear","Straight-line interpolation between A and B (i.e. no ease in/out)"),
        ('BEZIER',"Bezier","Smooth interpolation between A and B, with some control over curve shape"),
        ('SINE',"Sine","Sinusoidal easing (weakest, almost linear but with a slight curvature)"),
        ('QUAD',"Quad","Quadratic easing"),
        ('CUBIC',"Cubic","Cubic easing"),
        ('QUART',"Quart","Quartic easing"),
        ('QUINT',"Quint","Quintic easing"),
        ('EXPO',"Exponential","Exponential easing (dramatic)"),
        ('CIRC',"Circular","Circular easing (strongest and most dynamic)"),
        ('BACK',"Back","Cubic easing with overshoot and settle"),
        ('BOUNCE',"Bounce","Exponentially decaying parabolic bounce, like when objects collide"),
        ('ELASTIC',"Elastic","Exponentially decaying sine wave, like an elastic band")
    )
    
    type: EnumProperty(
        name="Type",
        items=type_items,
        default='BEZIER',
    )
    
    insert_items = (
        ('ALL',"All Channels","Insert a keyframe on all visible and editable F-Curves using each curve’s current value"),
        ('SEL',"Only Selected Channels","Insert a keyframe on selected F-Curves using each curve’s current value"),
    )
    insert: EnumProperty(
        name="Insert",
        items=insert_items,
        default='ALL',
    )
    
    keyframe_items=(
        ('KEYFRAME',"Keyframe","Normal keyframe - e.g. for key poses"),
        ('BREAKDOWN',"Breakdown","A breakdown pose - e.g. for transitions between key poses"),
        ('JITTER',"Jitter","A filler or baked keyframe for keying on ones, or some other purpose as needed"),
    )
    keyframe: EnumProperty(
        name= "Keyframe Type",
        items = keyframe_items,
        default = 'BREAKDOWN',
    )
    @classmethod
    def poll(cls, context):
        return True

    def execute(self, context):
        main(context,self.type,self.step,self.insert,self.keyframe)
        return {'FINISHED'}


def register():
    bpy.utils.register_class(SampleCurvesStepped)


def unregister():
    bpy.utils.unregister_class(SampleCurvesStepped)


if __name__ == "__main__":
    register()

The operator will work in the graph editor. It exposes the step (how many frames between new keyframes), sample interpolation (any interpolation type, or keep whatever interpolation the curves have right now) and keyframe type (Keyframe, Breakdown and Jitter cause the others dont make alot of sense for workflows).
Works on all visible graphs or only selected ones.
The Result will be a stepped graph with neatly interpolated breakdowns.

Example


Before


After

1 Like