4
$\begingroup$

I noticed that every time I insert a new keyframe on an object using the Python API, the insertion takes more time. Here are some timings of a keyframe insertion:

  • 1st keyframe: 0.0176 ms
  • 10000th keyframe: 1.0012 ms

(both only measure the insertion time of one keyframe)

Now I don't need to insert so many keyframes but this is just to show what is happening. Although it's just ~1ms here, it makes a big difference for me because I need to keyframe many objects (~10000).

So here are my questions:

  • Why is this happening?
  • How can I insert ~1000 keyframes to an object in an efficient way?
$\endgroup$
2
  • $\begingroup$ Related If you have the data for frame and value. $\endgroup$
    – batFINGER
    Jun 27, 2018 at 5:50
  • $\begingroup$ @batFINGER Thanks, that solved it! Maybe post a normal answer so I can accept it? $\endgroup$
    – Ulwdo
    Jun 27, 2018 at 22:05

2 Answers 2

12
$\begingroup$

Use foreach_set to set all fcurve data from data

Similarly to this q&a can add our data directly to an fcurve.

fc.keyframe_points.foreach_set("co", 
        [x for co in zip(frames, samples) for x in co])

is setting the co, the (frame, value) pair of each keyframe point

i = 0
for f, s in zip(frames, samples): # zip => (f1, s1), (f2, s2) ...
    fc.keyframe_points[i].co = (f, s)
    i += 1

note foreach_set requires a "flat (or one dimensional) list" eg [f1, s1, f2, s2 ...]

flat_list = [x for co in zip(frames, samples) for x in co]

Script below creates a random noise per frame for frames 1 to 250. The action and fcurve is created for location.y and assigned to the context object.

enter image description here

import bpy
from random import random

frames = range(1, 250)
samples = (random() for f in frames)
# some action
a = bpy.data.actions.new("SomeAction")
fc = a.fcurves.new("location", index=1, "LocY")

fc.keyframe_points.add(count=len(frames))
# populate points

fc.keyframe_points.foreach_set("co", 
        [x for co in zip(frames, samples) for x in co])
# update 
fc.update()
# assign to context ob
ad = bpy.context.object.animation_data_create()
ad.action = a

To tweak it up a notch, if you are not going to edit the fcurve convert it to sampled points

fc.convert_to_samples(1, 250)

enter image description here

$\endgroup$
1
$\begingroup$

Every time you add a new keyframe, Blender has to add it to its internal list of keyframes and the larger the list becomes the more work is involved in maintaining it (allocating memory, sorting, inserting records, etc) - so it gets progressively slower.

1ms per keyframe doesn’t sound too bad to me - at least for any ‘reasonable’ number of keyframes (normally you would set a small number of keyframes and let the f-curve interpolation fill in the gaps - the whole point of keyframes really).

If you really must specify so many individual points for your animation then perhaps a Driver would suit better. A driver function could be written to take a list of distinct values - a dataset - and take some kind of ‘index’ (eg, the frame number) and return the relevant value for that object. In this way you don’t need to maintain lots of keyframes and can simply have a dataset (which could be held in a file, for example) that can be used to drive the animation.

$\endgroup$
1
  • $\begingroup$ Thanks for this idea. I tried it and it's very fast to create all the drivers but unfortunately it's quite slow when I play the animation (~25 FPS) while the keyframes were running at ~45 FPS. My drivers' expressions are just reading the values in memory. $\endgroup$
    – Ulwdo
    Jun 27, 2018 at 20:01

You must log in to answer this question.

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