1
$\begingroup$

I'm trying to delete a specific keyframe at a specific frame in an animation action, I've tried both fcurve.keyframe_points.remove(keyframe) and fcurve.keyframe_delete(data_path, f= frame) with no luck.

Here's what I'm attempting to do, cropping an action keyframes between two given frame numbers:

def crop_animation(action, frame_start, frame_end):
    """ Crop the given action keyframes by removing all keyframes before 
    frame_start and after frame_end
    """
    fcurves = action.fcurves
    for fcu in fcurves:
        for i, key_frame in enumerate(fcu.keyframe_points):
            if i < frame_start or i > frame_end:
                fcu.keyframe_points.remove(key_frame)

and I get this error:

RuntimeError: Error: Keyframe not in F-Curve

and when I use fcurve.keyframe_delete(data_path, f= frame) instead:

def crop_animation(action, frame_start, frame_end):
    """ Crop the given action keyframes by removing all keyframes before 
    frame_start and after frame_end
    """
    fcurves = action.fcurves
    for fcu in fcurves:
        datapath = fcu.data_path
        for i, key_frame in enumerate(fcu.keyframe_points):
            if i < frame_start or i > frame_end:
                fcu.keyframe_delete(datapath, frame= i)

I get this error message:

TypeError: bpy_struct.keyframe_insert() property "pose.bones["Bone"].location" not found
$\endgroup$

2 Answers 2

2
$\begingroup$

The keyframes are not enumerated to frame.

Commented on another question of yours that there is no necessity that the keyframes fall exactly on integer values. Feel some of this answer could be there or vice versa

For the 2D keyframe point coordinate vector x is the frame, and y is the value y . Both are floats.

Eg a simple fcurve may have points (1, 30), (33, 23.0), (222.3, 0) having a frame range of 1 to 222.3 but an enumeration of 0, 1, 2.

To crop based on the frame of the keyframe point

import bpy

def crop_animation(action, frame_start, frame_end):
    """ Crop the given action keyframes by removing all keyframes before 
    frame_start and after frame_end
    """
    fcurves = action.fcurves
    for fcu in fcurves:
        for kf in reversed(fcu.keyframe_points.values()):
            if kf.co.x < frame_start or kf.co.x > frame_end:
                fcu.keyframe_points.remove(kf)

# test call                
crop_animation(bpy.data.actions[0], 10000, 0)

Not sure how you get the keyframe not in fcurve error using code in question. .

Keyframe delete.

keyframe_delete is the opposite to keyframe_insert. An fcurve holds our keyframes, it is not itself keyframed.

For example if a location keyframe is added to context object on current frame then

>>> C.object.keyframe_insert('location')
True

>>> C.object.keyframe_delete('location')
True

removes it. Or in the case of bones

C.object.pose.bones["Foo"].keyframe_delete("location", frame=frame)

Make an amended copy of the action.

Have added a related link below re copying and cropping fcurves. To make your fcurve such that the kf's fall on integer frames would make a copy, then over it's integer range. For example if fc is our original, then the points of our new curve could be

[(frame, fc.evaluate(frame) for frame in range(frame_start, frame_end)] 

For bvh animation as displayed in other question with constant spacing between keyframes Another option is to scale the animation such that between frame distance is 1.

Related.

How to change an FCurve value in Python

Copying specific frames from one action to another ( python )

Python. How to find a point on the timeline where Fcurve reaches a certain value without changing current subframe?

$\endgroup$
2
  • $\begingroup$ This question was a bit irrelevant to the other one I had posted earlier because I used a different simple action for testing it other than the motion capture animation. The keyframes were equally distributed along integer frame numbers in this file, the keyframe delete in both cases is frame agnostic anyway so it should still be deleted regardless, yet it didn't work. and your code still gives me the error "RuntimeError: Error: Keyframe not in F-Curve". $\endgroup$
    – Ahmed Ali
    Commented Jun 13, 2021 at 12:22
  • $\begingroup$ I preferred to remove keyframes rather than copy and evaluate the keyframes span I'm after because in my use case the keyframes I tend to remove are much less than the ones I need to keep, so I thought it might work faster, but now I think it might be a cleaner way to start fresh and evaluate since in your linked answer you are using numpy for doing the heavy lifting. So thank you ;) $\endgroup$
    – Ahmed Ali
    Commented Jun 13, 2021 at 12:23
0
$\begingroup$

I've got the same error RuntimeError: Error: Keyframe not in F-Curve trying to delete keyframes with keyframe_points.remove.

Here is my code that throws and error:

fc = bpy.context.object.animation_data.action.fcurves[200]
print([fc.co for fc in fc.keyframe_points])
print([id(fc) for fc in fc.keyframe_points])

for kf in fc.keyframe_points.values():
    print(kf.co, id(kf))
    if kf.co.x != 0 and kf.co.x != 28:
        fc.keyframe_points.remove(kf)
        print('removed')

print([fc.co for fc in fc.keyframe_points])
print([id(fc) for fc in fc.keyframe_points])

I have no idea why but it seems like it might something to do with how keyframe_points and it's elements stored in memory - so at the moment when I'm trying to remove keyframe from keyframe_points it's already moved somewhere and that's why I get the error.

It's so weird and it looks like Blender bug.

SOLUTION - workaround I've come up with:

fc = bpy.context.object.animation_data.action.fcurves[200]
print([fc.co for fc in fc.keyframe_points])
print([id(fc) for fc in fc.keyframe_points])

i = 0
while i < len(fc.keyframe_points):
    kf = fc.keyframe_points[i]
    print(i, kf.co, id(kf))
    if kf.co.x != 0 and kf.co.x != 28:
        print('removed')
        fc.keyframe_points.remove(kf)
    else:
        i = i + 1

print([fc.co for fc in fc.keyframe_points])
print([id(fc) for fc in fc.keyframe_points])

Full error traceback for anyone curious:

fc = bpy.context.object.animation_data.action.fcurves[200]
print([fc.co for fc in fc.keyframe_points])
> [Vector((9.0, 0.0)), Vector((10.0, -1.0803276495607861e-08)), Vector((11.0, 1.8595179840374954e-09)), Vector((24.0, 1.8595179840374954e-09)), Vector((25.0, 1.1508858310804726e-09)), Vector((26.0, 1.6575807393337527e-09)), Vector((27.0, 1.1508858310804726e-09)), Vector((28.0, 0.007000000216066837)), Vector((29.0, -0.007000000216066837)), Vector((30.0, 0.0))]

print([id(fc) for fc in fc.keyframe_points])
> [1923420162368, 1923420090112, 1923420162368, 1923420090112, 1923420162368, 1923419915648, 1923420162368, 1923419915648, 1923420162368, 1923420090112]


for kf in fc.keyframe_points.values():
    print(kf.co, id(kf))
    if kf.co.x != 0 and kf.co.x != 28:
        fc.keyframe_points.remove(kf)
        print('removed')

> <Vector (9.0000, 0.0000)> 1923419919232
> removed
> <Vector (11.0000, 0.0000)> 1923419921408
> removed
> <Vector (25.0000, 0.0000)> 1923419915648
> removed
> <Vector (27.0000, 0.0000)> 1923419925824
> removed
> <Vector (29.0000, -0.0070)> 1923419925312
> removed
> <Vector (30.0000, 0.0000)> 1923419928192
> Traceback (most recent call last):
>   File "<blender_console>", line 4, in <module>
> RuntimeError: Error: Keyframe not in F-Curve


print([fc.co for fc in fc.keyframe_points])

[Vector((10.0, -1.0803276495607861e-08)), Vector((24.0, 1.8595179840374954e-09)), Vector((26.0, 1.6575807393337527e-09)), Vector((28.0, 0.007000000216066837)), Vector((30.0, 0.0))]

print([id(fc) for fc in fc.keyframe_points])
[1923420349056, 1923420025856, 1923420349056, 1923420025856, 1923420349056]
```
$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .