Script to clear useless keyframes from objects

Hi! I made this script to clear the useless keyframes from objects of a physics simulation. It removes the keyframes on frames that have same position data. It works, but it is incredible slow! It is slower as the complexitiy of the scene increases, and in my scene I have 1700 simulated objects.

So I wanto to ask if there is a way to make it work faster.
Here’s the code. It works on selected objects. For every object it goes back and forth in the frames and checks objects position, and removes the keyframes when needed. Any idea to improve it?

import bpy, copy

start_frame = 2
end_frame = 2500

objects = bpy.context.selected_objects

bpy.ops.object.select_all(action = 'DESELECT')


for obj in objects:
    obj.select = True
    bpy.context.scene.objects.active = obj
    
    for f in range(start_frame, end_frame):
        bpy.context.scene.frame_set(f)
        cur_loc = copy.copy(obj.location)
        bpy.context.scene.frame_set(f+1)
        next_loc = copy.copy(obj.location)
        bpy.context.scene.frame_set(f-1)
        prev_loc = copy.copy(obj.location)
        if cur_loc == prev_loc and cur_loc == next_loc:
            bpy.context.scene.frame_set(f)        
            bpy.ops.anim.keyframe_delete_v3d()
    
    obj.select = False  

In my simulation only the 10% of the keyframes are useful, so I suppose that removing those useless will make the file lighter and saves memory during the rendering process of the animation.

jumping back and forward in the frames makes it a bit slow…

I used a distance difference jus for more control… keeping the ‘if (cur_loc==start_loc):’ will still work and it will still be faster! :wink:

import bpy

start_frame = 1
end_frame = 250
Error=0.0

objects = bpy.context.selected_objects
bpy.ops.object.select_all(action = 'DESELECT')

for obj in objects:
    obj.select = True
    bpy.context.scene.objects.active = obj
    
    frame=start_frame
    bpy.context.scene.frame_set(frame)
    start_loc = obj.location.copy()
    frame+=1
    
    while frame<=end_frame:
        bpy.context.scene.frame_set(frame)
        cur_loc=obj.location.copy()

        #''''''''''''''''''''''''''''''''''''
        # if cur_loc==start_loc:
        #''''''''''''''''''''''''''''''''''''
        diff=cur_loc-start_loc
        if diff.length<=Error:
 
            bpy.ops.anim.keyframe_delete_v3d()
        else:
            start_loc=cur_loc
        frame+=1    
    obj.select = False

Isn’t it impossible to compare the float error variable with a difference between two vector3 variables?
Also, does this check also the difference between the current and the previous frame? I don’t want to delete a frame just becouse the next is equal. It must be equal also to the previous.

Anyway, I supposed that the frame changing is what slows down the script.

Don’t know if there’s such a function, that’s why I used the distance between the location in the previous keyframe, and the current one.

the script only deletes a keyframe, if is equal to the previous keyframe… this poses some problems when objects stop/resume movement.
it wouldn’t be difficult to improve it, and even remove keyframes during the movement’s if they are just interpolations of the previous and next keyframes.

There’s an excellent script Aaron Koressel wrote for Maya many years ago that does this exact function. I use it every day. It’s written in MEL, but it should be simple enough to translate into Python, since it’s mostly just Python functionality, not Maya commands. You can find it in two places: my github repo for my maya prefs and Aaron’s website.

Found this pretty high up on Google, but I must say that ChatGPT did a better job solving my problem, so I’ll post its solution here:

import bpy

def remove_redundant_keyframes(action, threshold=1e-6):
    """
    Removes redundant keyframes from all F-Curves in the active action.
    A keyframe is considered redundant if it has the same value as its neighbors within a given threshold.
    """
    for fcurve in action.fcurves:
        if not fcurve.keyframe_points:
            continue
        
        keyframes = fcurve.keyframe_points
        i = 1  # Start from second keyframe
        
        while i < len(keyframes) - 1:
            prev_kf = keyframes[i - 1]
            curr_kf = keyframes[i]
            next_kf = keyframes[i + 1]
            
            # Check if current keyframe is redundant (same value as before and after within threshold)
            if (
                abs(prev_kf.co.y - curr_kf.co.y) < threshold and
                abs(next_kf.co.y - curr_kf.co.y) < threshold
            ):
                keyframes.remove(curr_kf)
            else:
                i += 1  # Only increment if we didn't delete to avoid skipping elements

# Run the function
for i, action in enumerate(bpy.data.actions):
	print(f"Cleaning {i}/{len(bpy.data.actions)}")
	remove_redundant_keyframes(action)

Instead of comparing distances in 3D space across various frames, it just reads the fcurve keyframe point data and compares it against the previous frame, which is exactly what anyone finding this thread would want to do. This then also does rotation, scale, and any other properties, since fcurves are just fcurves.

2 Likes