1
$\begingroup$

I have an object that has a location driver, so the location of this object is constantly changing. I want to keyframe its motion throughout a range of frames, with each frame being a keyframe (essentially creating an fcurve). The method I have been using in python is going frame by frame and inserting a keyframe each frame.

import bpy
from bpy import *

bpy.context.scene.frame_current = 0
i = 0

for i in range(0,1900):
    bpy.ops.anim.keyframe_insert_menu(type='Location', confirm_success=False, always_prompt=False)
    bpy.context.scene.frame_current = bpy.context.scene.frame_current + 1
    i += 1

Is there an easier way to keyframe a group of frames via python instead of going frame by frame? The more objects added, and a bigger range of frames means blender getting slower over time to add keyframes one by one, so this method can take many hours depending on how many different objects I have involved.

$\endgroup$
1

2 Answers 2

3
$\begingroup$

zeffii's answer is better than using bpy.ops, but if you want to get closer to the metal, it gets a little clumsy. Blender's action API is missing several convenience functions.

It boils down to these steps

  • make sure the datablock has animation_data
  • get the animation_data.action or .action=bpy.data.actions.new(name)
  • find the action_data.fcurves[i] that matches the data_path and array_index, or create the fcurve
  • make sure there are enough fcurve.keyframe_points to load our data into
  • assign the fcurve.keyframe_points[j].co and possibly .interpolation, .handle_left, .handle_right, etc.

There are a couple of examples embedded in http://web.purplefrog.com/~thoth/blender/python-cookbook/movie-card-stack.html and http://web.purplefrog.com/~thoth/blender/python-cookbook/csv-to-fcurve.html which I will summarize here:

def make_or_get_fcurve(obj, data_path, index=-1):
    """
    :param obj: the data block from which we'll get the fcurve
    :param data_path:
    :param index:
    :return:
    """

    # freshly created objects don't have animation_data yet.
    if obj.animation_data is None:
        obj.animation_data_create()
    ad=obj.animation_data
    if ad.action is None:
        ad.action = bpy.data.actions.new(obj.name+"Action")

    for fc in ad.action.fcurves:
        if (fc.data_path != data_path):
            continue
        if index<0 or index==fc.array_index:
            return fc
    # the action didn't have the fcurve we needed, yet
    return ad.action.fcurves.new(data_path, index)

and from the other example

def find_or_create_fcurve(action, data_path, array_index= -1):
    for fc in action.fcurves:
        if fc.data_path == data_path and ( array_index<0 or fc.array_index==array_index):
            return fc
    fc = action.fcurves.new(data_path, array_index)
    return fc


def make_eyeball_look_at(armature, frame):

    eyeball = get_eyeball()

    cns = find_constraint_track_to(eyeball, armature, "card release")
    data_path = 'constraints["%s"].influence' % cns.name
    fc = find_or_create_fcurve(eyeball.animation_data.action, data_path)

    i0 = len(fc.keyframe_points)
    fc.keyframe_points.add(4)
    kp0 = fc.keyframe_points[i0]
    kp1 = fc.keyframe_points[i0 + 1]
    kp2 = fc.keyframe_points[i0 + 2]
    kp3 = fc.keyframe_points[i0 + 3]

    kp0.handle_left = [frame - 7, 0]
    kp0.co = [frame - 6, 0]
    kp0.handle_right = [frame - 5, 0]

    kp1.handle_left = [frame - 4, 1]
    kp1.co = [frame - 3, 1]
    kp1.handle_right = [frame - 2, 1]

    kp2.handle_left = [frame + 2, 1]
    kp2.co = [frame + 3, 1]
    kp2.handle_right = [frame + 4, 1]

    kp3.handle_left = [frame + 5, 0]
    kp3.co = [frame + 6, 0]
    kp3.handle_right = [frame + 7, 0]


def add_keyframe_points(fcurve, kps):
    idx0 = len(fcurve.keyframe_points)
    fcurve.keyframe_points.add(len(kps))
    rval = []
    for i in range(len(kps)):
        kp = fcurve.keyframe_points[idx0 + i]
        kp.co = kps[i]
        rval.append(kp)
    return rval
$\endgroup$
2
$\begingroup$

You should avoid operators if possible and do something like this:

import bpy
from bpy import context

scene = context.scene
ob = context.object  # object to keyframe should be selected/active
# ob = bpy.data.objects["name"]  # or referenced by name


for i in range(0, 1900):
    scene.frame_set(i)
    ob.keyframe_insert(data_path="location", index=-1)
$\endgroup$

You must log in to answer this question.

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