0
$\begingroup$

I am building an animation of a machine where various parts move along paths in turn. So far I have done this without any armatures. I was hoping to string several cycles of operation together, and modify object parameters (using python script) for each machine cycle.

From previous questions, it seems that the NLA editor can't work with groups of actions associated with multiple objects - so maybe the best I can achieve is to write a script to step and repeat the keyframes for all objects? Alternatively, is it possible to render-time transpose my keyframes on the fly at the end of the cycle, or programatically render frames 1..300 twice (with some tweaks) to achieve 1..600?

There are two reasons for wishing to automate the process. Firstly, it would allow me to generate any number of different cycles based on a small number of parameters (my first trial has 5 cycles of 300 frames). Second, as I have already discovered, defects in my base model only become obvious after I made and modified 5 copies of the basic cycle.

(for what its worth, I'm not an artist - just trying to find a good way to demonstrate how the machine works)

$\endgroup$
1
$\begingroup$

If you need help figuring out how to rig NLAs with python, perhaps this sample code from my virtual kinetic art helps:

import bpy
from math import *

def set_kp(kp, co, easing):
    kp.co = co
    kp.handle_left = co
    kp.handle_right = co
    kp.easing = easing
    kp.interpolation = 'SINE'


def wipe_fcurves(action):
    while len(action.fcurves) > 0:
        action.fcurves.remove(action.fcurves[-1])


def keyframe_action1(action):
    wipe_fcurves(action)

    fc = action.fcurves.new("location", 2)
    fc.keyframe_points.add(5)
    set_kp(fc.keyframe_points[0], [0,0], 'EASE_OUT')
    set_kp(fc.keyframe_points[1], [20,1], 'EASE_IN')
    set_kp(fc.keyframe_points[2], [40,0], 'EASE_OUT')
    set_kp(fc.keyframe_points[3], [60,-1], 'EASE_IN')
    set_kp(fc.keyframe_points[4], [80,0], 'EASE_OUT')

def keyframe_action2(action, scale=1):

    wipe_fcurves(action)
    timescale = 400
    fc = action.fcurves.new("location", 2)
    fc.keyframe_points.add(5)
    set_kp(fc.keyframe_points[0], [0,0], 'EASE_IN')
    set_kp(fc.keyframe_points[1], [1*timescale,1*scale], 'EASE_OUT')
    set_kp(fc.keyframe_points[2], [2*timescale,2*scale], 'EASE_IN')
    set_kp(fc.keyframe_points[3], [3*timescale,1*scale], 'EASE_OUT')
    set_kp(fc.keyframe_points[4], [4*timescale,0], 'EASE_IN')

def action_for(u,v):

    name = "high frequency sine"
    action = bpy.data.actions.get(name)
    if action is None:
        action = bpy.data.actions.new(name)
        keyframe_action1(action)

    return action

def action2a():
    name = "low frequency cosine up"
    action = bpy.data.actions.get(name)
    if action is None:
        action = bpy.data.actions.new(name)
        keyframe_action2(action, 2)
    return action

def action2b():
    name = "low frequency cosine down"
    action = bpy.data.actions.get(name)
    if action is None:
        action = bpy.data.actions.new(name)
        keyframe_action2(action, -2)
    return action


def rig_track_one_strip(ad, track_no, strip_start_frame, action, source_start, source_end, repeat_count, strip_scale=1,
                        track_name="high frequency", blend_type='REPLACE'):
    if track_no < len(ad.nla_tracks):
        tr1 = ad.nla_tracks[track_no]
    else:
        tr1 = ad.nla_tracks.new()
    tr1.name = track_name
    if len(tr1.strips) < 1:
        strip1 = tr1.strips.new("bacon", strip_start_frame, action)
    else:
        strip1 = tr1.strips[0]
        strip1.action = action
    strip1.frame_start = strip_start_frame
    strip1.action_frame_start = source_start
    strip1.action_frame_end = source_end
    strip1.repeat = repeat_count
    strip1.scale = strip_scale
    strip1.blend_type = blend_type

    return (tr1, strip1)

def rig_nlas(obj, u,v):
    theta = u/3+v/10 # + frame*pi/40

    if obj.animation_data is None:
        obj.animation_data_create()

    ad = obj.animation_data

    if 0 == u % 2:
        repeat = 40
        scale = 1
    else:
        repeat = 38
        scale = 40 / 38
    rig_track_one_strip(ad, 0, -theta / (pi / 40), action_for(u, v), 0,80, repeat, scale)

    if 0==u%2:
        rig_track_one_strip(ad, 1, 1, action2a(), 0, 1600, 2, 1, "low frequency", 'ADD')
    else:
        rig_track_one_strip(ad, 1, 1, action2b(), 0, 1600, 2, 1, "low frequency", 'ADD')


def fab_object_for(scn, u,v):
    name = "ball2 at %d,%d" % (u, v)
    obj = bpy.data.objects.get(name)
    if obj is None:
        obj = bpy.data.objects.new(name, bpy.data.meshes.get("Icosphere"))
    try:
        scn.objects.link(obj)
    except:
        pass
    obj.location = (u,v,0)
    rig_nlas(obj, u, v)


scn = bpy.context.scene

for v in range(20):
    for u in range(20):
        fab_object_for(scn ,u,v)

keyframe_action1(bpy.data.actions['high frequency sine'])
keyframe_action2(bpy.data.actions['low frequency cosine down'], -2)
keyframe_action2(bpy.data.actions['low frequency cosine up'], 2)

It assumes you already have a mesh named 'Icosphere' (you can add one using blender's interactive menus).

$\endgroup$
1
$\begingroup$

Does it need to be programmatic? If not, select one of the objects, select the keyframes (in the graph editor) in frames 1-300:

  1. Hit Shift+D to duplicate the keyframes
  2. Then hit G and 300
  3. Then Enter to confirm the operation

This will duplicate the exact keyframe in 1-300 and move the duplicates to 301-600.

If it is more complicated than that, perhaps you might update your question with more details on exactly what you are attempting to accomplish.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.