I need to render a cyclic animation of an object-armature from the command line. At the moment I am stack on how to repeat the keyframes, ie how to copy the keyframes of the first time points and paste them at the end of the animation the same object. I was able to do that with 2 different functions. One uses ‘NLA_EDITOR’:
def extend_cyclic_animation_nla(obj, n_repeatitions):
for obj_i in bpy.data.objects:
obj_i.select = False
bpy.context.area.type = 'NLA_EDITOR'
bpy.ops.nla.action_pushdown(channel_index=1)
obj.animation_data.nla_tracks["NlaTrack"].strips["Armature|mixamo.com|Layer0"].repeat = n_repeatitions
bpy.context.area.type = 'TEXT_EDITOR'
The second function uses ‘DOPESHEET_EDITOR’:
def extend_cyclic_animation_dopesheet(obj, n_repeatitions, frame_end):
for obj_i in bpy.data.objects:
obj_i.select = False
bpy.context.area.type = 'DOPESHEET_EDITOR'
bpy.context.space_data.dopesheet.show_only_selected = True
obj.select = True
bpy.ops.action.copy()
bpy.context.scene.frame_current = frame_end
for i in range(n_repeatitions):
bpy.ops.action.paste()
bpy.context.scene.frame_current += frame_end
bpy.context.space_data.dopesheet.show_only_selected = False
bpy.context.scene.frame_current = tmp
bpy.context.area.type = 'TEXT_EDITOR'
My problem is that they only work when I run them from the blender window. When I run them from the command line I get an error. the reason is that when I run the code from the command line in the background bpy.context.area seems to be set to None and None does not have an attribute ‘type’. The error is:
bpy.context.area.type = 'DOPESHEET_EDITOR'
AttributeError: 'NoneType' object has no attribute 'type'
So, My question is given an object with a cyclic animation data, how can I copy (or repeat) the keyframes of that object and paste them at the end of the same animation from the command line? I am using blender 2.79.
The problem with your script is that you are accessing all of the data through context. You could do something like bpy.data.objects.animation_data.action The bpy.data object offers a generic way of accessing all the data in the .blend file. It’s great!
I am not entirely sure from reading your code what you want. At first I thought you wanted to cycle the f-curves within an action, but upon a second look it seems you also want to create a repitition in the NLA editor. It looks like you can access the NLA information from object.animation_data.nla_tracksNlaTracks(bpy_struct) — Blender 2.79.0 855d2955c49 - API documentation, and then there are NLA Strips inside, something like:
tracks = ob.animation_data.nla_tracks
strips = tracks.active.strips
strips = [strip for strip in strips if strip.active == True]
# This is a "list comprehension"
#This is equivalent to:
strips = []
for strip in strips:
if (strip.active == True):
strips.append(strip)
# but this takes so many lines of code!! It's probably slower, too.
prev_strip = strips[0]
for i in range(extend):
length = (prev_strip.frame_end - prev_strip.frame_start)
frame_start = prev_strip.frame_start + length * i
frame_end = frame_start + length
strip = strips.new(prev_strip.name, frame_start, prev_strip.action)
strip.frame_end = frame_end
Please note that I haven’t tested the above code and it likely will require some changes before it will run. I don’t have any scenes on-hand with NLA tracks.
Anyhow, for keyframes, they are fairly simple to handle. They’re stored in action.fcurves.keyframe_points and they can be added with action.fcurves.keyframe_points.insert()
My advice is, add the keyframes with insert first, and collect them in a list. Add the same number of keyframes as in the original animation (and you may need to add a final keyframe at the end to duplicate the first keyframe) and collect them as you do (insert() returns the keyframe object) and then go into each one and copy the relevant values using something like this method (How to copy all properties of an object to another object, in Python? - Stack Overflow).
That you very much for your help. Your code is very helpful and gives me a deeper understanding of how to access the animation data.
Honestly, I was able to sove my specific issue by appling a cyclic modifier to each fcurve of the object. I did that with the following function:
def extend_cyclic_animation_command_line(obj, n_repetitions_after=0, n_repetitions_before=0):
if obj.animation_data is not None and obj.animation_data.action is not None:
for fcurves_f in obj.animation_data.action.fcurves:
new_modifier = fcurves_f.modifiers.new(type='CYCLES')
# default n_repetitions_after is 0 which means
# infinite repetitions after the end of the fcurves_f
new_modifier.cycles_after = n_repetitions_after
# default n_repetitions_before is 0 which means
# infinite repetitions before the start of the fcurves_f
new_modifier.cycles_before = n_repetitions_before
extend_cyclic_animation_command_line(bpy.data.objects['Body_a'])
However, I really find your commend very helpful and now I may update my function based on your advice and code. Thanks a lot.
I know it’s quite different from what you suggested, but your example made me better understand how to access the nla_tracks and the strips. Thanks again.