Minggu, 02 Februari 2014

In this article, I am sharing my research and notes on Blender NLA (Non Linear Animation) and write on how we could also use Python to orchestrate the animation of MANY objects in context of NLA.

NOTE: It comes to my own surprise that I only found out about the POWER of Blender NLA just recently.

NLA INTRO

I would start by reading these:
http://wiki.blender.org/index.php/Doc:2.6/Manual/Animation/Editors/NLA
http://aligorith.blogspot.com.au/2010/10/clarifying-animation-workflow-in.html

In short, I am seeing NLA as a way to non-linearly edit normal Animation(s) keyframes into a Strip of Action(s) just like any non-linear editor. A single Action animation can be offset, sliced, blended in time.

NLA workflow is great for Game Animation where one may reuse animations (Idle, Walk, Run) or to specifically trigger an Action Animation based on Event.

On top of that, I believe NLA is also useful for LOOPING kind of animation, usually for musical score that has beats and loops. Not only that, NLA has tremendous potential to create a lot of interesting "wave motion" animation effects.

Keep in mind that NLA is NOT complex NOR super technical, in fact I think Blender NLA is pretty intuitive and very simple to use.

I have not looked at NLA thoroughly, but I believe there are few different NLA types:
  • NLA for normal object transformation
  • NLA for Armature
  • NLA for Materials
  • etc
I am not yet 100% understand how NLA data block works in relation to each other, but I will touch on the basic first. For example, I am wondering whether it is possible to "drive" Material animation using Actions of Transform, apparently cannot. I am guessing we need Blender Drivers for that.


NLA BASIC

Lets do a little experiment.

1. MANUAL ANIMATION
Create a simple animation of a block going from position A to position B. Maybe just an animation of a Cube going from original position and go up few units in Z axis.
  • Single keyframe Position of Cube in Z axis at frame 1.
  • Move the frame in timeline, lift up the Cube in Z axis, and keyframe at frame 20.
2. SWITCH TO NLA EDITOR
Next, we like to switch panel to NLA Editor. I usually do this inside Blender Animation layout. But feel free to modify your current layout so you can get to NLA Editor panel.


3. NLA EDITOR
Now, we are ready to turn the animation into Strip. You simple click on tiny Snowflake button to freeze the animation and work in NLA kind of way.


By doing the freeze, we now get Strip named "CubeAction". NLA Track is also automatically created.


4. EDIT THE ACTION STRIP
To Edit the action like normal, we can apparently do that quickly by hitting TAB while selecting the Action Strip:

We hit TAB again to go out from the Action Edit Mode.

5. APPLY ACTION STRIP TO OTHER OBJECT aka INCLUDE SELECTED OBJECTS
Next, let say we want to apply the same animation to other object. Firstly, I created a few other objects. 

What we need to do next is to create NLA track for the objects. But as you may notice we dont have any listing of new objects. We need to Include Selected Objects inside NLA first, before we are able to add NLA Tracks and Action Strips.


Handy Shorcuts:
  • Shift+A while hovering on the NLA left panel to add NLA Track 
  • Shift+A while hovering on the NLA right panel to add Action.
  • X to quickly delete NLA Track
  • Ctrl+LMB click on the name of object to quickly rename the object name inside NLA
5. EDITING ACTIONS STRIPS IN NLA CONTEXT
Next thing you probably want to explore yourself is how you can edit and modify the strip inside NLA Editor. There are a lot of interesting NLE features that I did not even think that they are existed before, such as: Trimming, Muting, Splitting, Holding, Retiming, Combo-ing and Layering animations. 


What I am even more interested is how we can use Python to actually do some automation inside NLE that we may want to know especially when we are doing Motion Graphics type of work.

STUDY: FLIP ANIMATION WITH NLA AND PYTHON

This found video at Eibriel blog is a good inspiration for what we are trying to do:
http://www.eibriel.com/2012/01/30/working-with-blender-python-nla/

Some times ago, I created a complex setup using Dynamic Paint to trigger flipping animation:
http://blendersushi.blogspot.com.au/2012/02/vfx-multilayers-triggering-of-effects.html

Last time on my blog post, I did the same thing using Python to set it up.

Now we are revisiting the idea once more, using NLA. Setting up repeating tasks is a breeze with Python scripting. Although it does require some hours of R&D.

Lets try to achieve the flipping animation of many objects using Python and NLA.

We basically need to "propagate" a single animation into many objects, with certain OFFSET value. Because a single repeating animation that starts and ends at the same time is kind of boring. While many same animation with offset is usually slightly more interesting.

Our animation in this case is just a flipping object, very simple. And we like to do that for many objects. The process will be like this:

1. Create a flipping animation, freeze and turn it into NLA Action Strip. This animation can be applied using any object. We just care about the Action.
2. Create some bunch objects that we like to flip.
3. Use Python to "copy" those Actions.

NOTE:
Before running the script below, you want to make sure that you have "Include Selected Objects" inside NLA Editor. Read "INCLUDE SELECTED OBJECT" topic above.

BLENDER SUSHI SCRIPT: Apply Action To Many Objects With Frame Offset


import bpy

# Initial setup
ACTION = "CubeAction" # Specify name of action to be copied here
START = 1 # Action Strip will be propagated starting with this frame number
OFFSET = 10 # How much frame offset
NEWTRACK = "MyTrack01" # Name of new NLA track for every object

# http://blenderartists.org/forum/showthread.php?233608-activate-NLA-track

sce = bpy.context.scene # get the scene
rig = bpy.context.active_object

objs = bpy.context.selected_objects

# get name to make active
names = []
for obj in objs:
names.append(obj.name)

# reverse order of selected objects
names.reverse()

# For every ACTIVE OBJECT

for i, name in enumerate(names):
bpy.context.scene.objects.active = bpy.data.objects[name]
rig = bpy.context.active_object

#get or create a new track called MyTrack

if NEWTRACK in rig.animation_data.nla_tracks:
NLATrack = rig.animation_data.nla_tracks[NEWTRACK]
else:
NLATrack= rig.animation_data.nla_tracks.new()
NLATrack.name = NEWTRACK

action = bpy.data.actions[ACTION] # your action

#sce.frame_set(frame=1)

startframe = START + OFFSET * i
actionstartframe = 1

MyNLAStrip = NLATrack.strips.new("MyStripName",start=startframe,action=action)






VIDEO DEMO

Here is an example how you can use the script above:





That simple script should show you a simple implementation of Python inside NLA. This one works only for Transform (Translate, Rotate, Scale). If you like to do the same thing on other context like Materials, Shapekeys, etc, keep reading.

NLA ACTION SCRIPT IN OTHER OBJECT TYPE CONTEXT

Further on in my curiosity toward the data flow in NLA and how we can access the data using Python, I also did a quick look and study on Shape Keys and Materials in context of NLA.

I dig and test inside Python Console and started to see some kind of interesting data pattern.

MATERIAL ---> NLA 
bpy.data.materials[MATERIALNAME].animation_data.nla_tracks.new()
bpy.data.materials[Material.001].animation_data.nla_tracks[NlaTrack].select = True
bpy.data.materials[Material.001].animation_data.nla_tracks[NlaTrack].select = False

SHAPE KEYS  ---> NLA 
# Create new NLA Track
bpy.data.shape_keys[KEY].animation_data.nla_tracks.new()

# Change the Frame Start and End Example
bpy.data.shape_keys[KEY].animation_data.nla_tracks[NLATRACK].strips[KEYACTION].frame_start = 1
bpy.data.shape_keys[KEY].animation_data.nla_tracks[NLATRACK].strips[KEYACTION].frame_end = 40

So if it is for Armature or for other Object Type, we can access and modify the Action Data just like above.

This is actually a new revelation for me in term of my understanding of Python Object Oriented Programming (OOP) in Blender and how the data is really stored inside Blender.

With this knowledge, we could actually further improve the script above to work on existing Strip Animation Data. So, instead of just assigning Strip, we can simply Query the current Script and maybe:

  • Randomize
  • Offset
  • Add, Combine, and Mix Strip with other actions.
A slight modifiaction from above script if this was for Object Material:

BLENDER SUSHI SCRIPT: Propagate Speificed Action To Selected Object Materials


import bpy

# Initial setup
ACTION = "MaterialAction" # Specify name of action to be copied here
START = 1 # Action Strip will be propagated starting with this frame number
OFFSET = 10 # How much frame offset
NEWTRACK = "MyTrack01" # Name of new NLA track for every object

# Get Active Material for every selected objects

objs = bpy.context.selected_objects

# Get Materials name, sort them in a list, and activate Animation Data for each one of them
mat = []
mat_names = []

for obj in objs:
# Query Material of every object
mat.append(obj.data.materials)
mat_names.append(obj.data.materials[0].name)

# Sort Animation Name
mat_names.sort()
mat_names.reverse()

# Create Animation Data
obj.data.materials[0].animation_data_create()

# Get Object to make active
names = []
for obj in objs:
names.append(obj.name)

# reverse order of selected objects
names.reverse()

# For every ACTIVE OBJECT

for i, name in enumerate(names):
bpy.context.scene.objects.active = bpy.data.objects[name]
rig = bpy.context.active_object.data.materials[0]

#get or create a new track called MyTrack

if NEWTRACK in rig.animation_data.nla_tracks:
NLATrack = rig.animation_data.nla_tracks[NEWTRACK]
else:
NLATrack= rig.animation_data.nla_tracks.new()
NLATrack.name = NEWTRACK

action = bpy.data.actions[ACTION] # your action

#sce.frame_set(frame=1)

startframe = START + OFFSET * i
actionstartframe = 1

MyNLAStrip = NLATrack.strips.new("MyStripName",start=startframe,action=action)







NOTE:
Notice that in this second script, we improve the code slightly so that we dont have to do "Include Selected Objects" thingy. We just simply run the script and it is accessing the Material, add Animation Action from the back door.

What you need to take care is the name of specified Material Action that you would like to propagate to every Objects Material. So you need to make sure you know the name of Action. In the script, I hardcoded "MaterialAction" as the action to be "copied" to every Material.

RENAMING ACTION EXAMPLE
bpy.data.actions[Material.004Action].name = "MaterialAction"


MORE THAN JUST DOMINO EFFECT

At this moment, all we get with the script above is this DOMINO EFFECT where one action leads to another action and all the actions are the same. What would be nice is is we could create a condition where the action only happens when triggered by a certain condition, or another action.

I really like the idea of "triggering" of actions based on distance approximation, for example. Something that Dynamic Paint does it well for Vertex Weighting.

Maybe I can do something like this:
1. Query some kind of STATE or CONDITION that Blender can write out or save in memory.
2. Based from that query, we then apply second code that will BORN NLA animation at that certain point.

I am sure that this can be done easily in BGE (Blender Game Engine) environment. At some point, I have to find out the relations between BGE and the normal Blender animation environment and hook them together.


OTHER REFERENCES

http://blenderartists.org/forum/showthread.php?248442-NLA-Editor-Make-a-strip-active
http://blenderartists.org/forum/showthread.php?233608-activate-NLA-track
http://blenderartists.org/forum/showthread.php?225783-How-do-I-emulate-the-quot-Snowflake-button-quot-in-the-NLA-in-python-code
https://sites.google.com/site/makehumandocs/blender-export-and-mhx/mocap-tool/combining-actions-in-nla-editor

Related Posts by Categories

0 komentar:

Posting Komentar