NLA Additive Animation Layers: (Add/Subtract/Multiply)


Gumroad version updated.
The information and videos in this post are still mostly accurate, however there are multiple differences.
You can find a free beta version very similar to the gumroad version here but definitely not the same.

NLA Additive Animation Layers are built into Blender 2.80 (See this post for details) so this addon is essentially being deprecated. You can still use it with Blender 2.7x but while “some” features will get updated to 2.80, it’s not really the same addon.




(Old free version from videos): or if you don’t want to go to gumroad (29.1 KB)

This script adds functions that automatically fixes new keyframes, to properly work with NLA Blend Types.
(Add / Subtract / Multiply, ie Additive Layers)

Here’s a video example, showing an additive layer.


And this video shows what happens if you try to animate the same way for additive layers, without this addon

if you move the additive strip around, the offset will continue to apply on lower actions:

nla_add_layer_keys_1

Add
  • Note: If you have a lot of selected bones/objects with keyframes, performance may drop, so either deselect them or disable the Add button.

    nla_add_layer_keys_2

    This function will be ran continuously when enabled and any of the selected objects have an active action.
    (Because it’s only supposed to run on new keyframes)

    

  • The X button attempts to insert a blank keyframe of the selected items (and chain for any bone selected with IK constraint). It will only add keys for items already keyed in the active action.

    This is for clearing pose/animation, because Alt+G/R/S will calculate a different value, and not “reset” them (Maybe when official layers are built in).


Bake
  • If you click Bake, a dialog will open, similar to the built-in bake animation operator.
    However, if you set your Action Blending type to anything other than “Replace”, a custom bake operation will run, that will apply the animation with keyframes appropriate for the Blend type.

    nla_add_layer_keys_3

    The built-in baker only bakes for Replace, ignoring the Blend mode, making it useless for additive layers.

  • It also has an option to add bones in the IK chain to your bake, for baking FK chains without having to either bake to every bone in the rig, or select all the bones in the chain.


These buttons are added to the NLA header.
By default, Add is enabled, so you can just enable the script, then forget about it.

If you prefer to have it not run by default, you can change the animation_add_check property’s default value from True to False. or use this (29.1 KB)


What are NLA Animation Layers?
NLA = Non-Linear Animation.
  • What the NLA Editor does is allow you to re-arrange actions on your objects, in whatever order you want.
    You can for example, create several animations for your character (ie, Stand / Walk / Sit / Run / Jump) and have them loop separately

    But if you want to have them run in a sequence (ie, Stand >> Run >> Jump >> Sit), if you use the Dopesheet/Graph Editor, you will have to manage a lot of keyframes to get them in order, and you can’t use modifiers to for example, automatically loop anything.

    Using the NLA Editor makes it simple to put the animations together, and re-order their timing and locations, without needing to select the individual keyframes.
    The way it does this is it combines the keyframes into a single box (strip), that holds the action/animation.


Animation Layers
  • When you re-arrange the time that animations occur, you’re doing it on just one layer.
    You determine when one action starts/ends, and when the next start/end.

    However, if you want to blend between different animations at the same time (ie. Breathing <> Heavy Breathing <> Deep Breaths),
    you will have to either create a new animation based on the result you want,
    OR
    you can take those different animations and put them on different layers, and blend between them.
    This way, you can combine the animations to have a new animation that you can easily tweak.


Additive Animation Layers
  • The way blender gets the final animation you see on screen is it goes through all the Animation Layers and combine their values together.

    There are basically two types of layers:
    Replace/Override and Add/Additive.

  • Replace is the default mode Blender uses for animations.
    They way it works is it takes the current animation layer (above), then discards all the lower layers.

  • Additive layers take previous layers, and add the current layer’s result on top.


Add / Subtract / Multiply
  • These 3 modes basically work toward the same goal, for “additive”, except the math they use to get there.

You can use these modes interchangeably for different purposes/results.



Examples/Why Animation Layers:

Basic conversion of an action to the NLA Editor:

Basic manipulation of NLA strips:

Default result of animating on a higher layer:

Default result of animating on an additive layer (without this addon):

Animating WITH this addon:

Blender’s AutoIK and the addon’s X button (reset keyframes):

Add an IK onto an chain, then Bake it to an Additive Layer:

This is what happens if you bake to an Add layer without this addon:

This is what happens if you want to keep the constraint instead of baking, vs bake then removing it:

This is when you Bake to a Replace strip instead of Add

The result is visually the same., however the blending mode still applies, so you can move and layer the would-be result of the bake.

Usage tips:
  • You can add constraints to an item, then bake the animation on an additive layer, and remove the constraint.
    This will apply the offsets from the constraint, without needing to keep the constraint.

    For example, you can lock a bone’s Global Rotation for a period of time, then go back to animating normally, in the same Additive layer, without having to switch to new actions or keep the constraint (and animate it).


  • If you bake an additive layer, you can duplicate the strip, to double the effect of the offset.
    If you switch the duplicated strip to a different mode (ie Add >> Subtract), you can “reduce” the offset.
    You can also change the strip’s influence, to lower the amount of reduction/offset.

    Note: this is mostly theoretical, I haven’t done extensive testing to verify amount of usefulness for this method.


Feel free to experiment and share other uses / methods. (especially for multiply/subtract)

12 Likes

Wow that’s a cool workaround ! :slight_smile: Thanks for giving this away !

1 Like

Thanks.
I was working on a different thing to optimize tracks for useless keys (more keyed channels = less performance)
and found it will definitely have an error when custom properties are keyed. I’m also sure that keyframed constraints will also error, so I added a try/except to “skip” any error keyframes.

I’m too lazy to bother finding/setting a continuous way to find the difference between static_values (current)/constraints/custom properties, so I’ll just leave it as the one that’s the main goal.

I modified/updated this to account for constraints/custom properties, optimized it and set to run more often (for when the previous checks didn’t register).
Click the Check for Input button in the NLA Editor Header, then animate like normal.
Press Escape to stop the processing (if you start playback, with it running, animation performance will be decreased)

I also added a check for Multiply/Subtract strips.
They will essentially work/result the same as add but the end result “should” allow you to use the resulting strips differently, but i don’t know since I can’t think of a use over ‘Add’.

bl_info = {"name": "AutoCorrect NLA Blend Interpolation",
"description": "","author": "Pizza Face","version": (0, 1, 0),"blender": (2, 79, 1),"location": "NLA Editor (Header: Check for Input)",
"wiki_url": "https://blenderartists.org/forum/showthread.php?446220","category": "Animation" }
#"warning": "This addon is still in development.",
import bpy

class update_layer_keys_check(bpy.types.Operator):
    bl_idname = "my_operator.update_layer_keys_check"
    bl_label = "Check for Input"
    bl_description = "Begin key adjustment when key is pressed"
    #bl_options = {"REGISTER","UNDO"}
    
    @classmethod
    def poll(cls, context):
        return True

    def invoke(self, context, event):
        context.window_manager.modal_handler_add(self)
        self.report({'INFO'}, 'Waiting to Overwrite layered Keys')
        return {"RUNNING_MODAL"}

    def modal(self, context, event):
        def check():
            for obj in bpy.context.selected_objects:
                frame = bpy.context.scene.frame_current
                active_strip = None
                strips = []

                anim=obj.animation_data
                if anim:
                    for track in anim.nla_tracks:
                        for strip in track.strips:
                            if strip.influence != 0 and not strip.mute:
                                if any((strip.frame_start &lt;= frame &lt;= strip.frame_end, strip.extrapolation == 'HOLD', strip.extrapolation == 'HOLD_FORWARD' and frame &gt; strip.frame_end)):
                                    if strip.action:
                                        if strip.active: 
                                            if anim.action and not anim.use_tweak_mode: strips += [strip]
                                            else: active_strip = strip
                                        else: strips += [strip]

                    if not active_strip: active_strip = anim; active_blend = active_strip.action_blend_type
                    else: active_blend = active_strip.blend_type

                    if active_strip.action:
                        for active_curve in active_strip.action.fcurves:
                            for active_key in active_curve.keyframe_points:
                                if active_key.co[0] == frame:
                                    path = active_curve.data_path;
                                    
                                    if 'pose.bones' in path: 
                                        bone = obj.pose.bones[path.split('["',1)[1].split('"]',1)[0]];    
                                        path = path.split('"]',1)[1]
                                        path = path.split('.',1)[1] if path[0] == '.' else path;
                                    else: bone = None;

                                    if 'constraints' in path: value = getattr(obj.constraints[path.split('["',1)[1].split('"].',1)[0]], path.split('"].',1)[1])
                                    elif '["' in path: value = obj.get(path.split('"')[1])
                                    else:    value = getattr(bone, path) if bone else getattr(obj,path) 
                                    
                                    attr_type = str(type(value)).split("'")[1]
                                    new_value = value[active_curve.array_index] if attr_type in ('Vector','Euler','Quaternion') else value

                                    default = 1.0 if 'scale' in path else 0.0
                                    old_value = default
                                    for strip in strips:
                                        for curve in strip.action.fcurves:
                                            if curve.data_path == active_curve.data_path and curve.array_index == active_curve.array_index:
                                                active_value = curve.evaluate(strip.strip_time) * strip.influence
                                                if strip.blend_type == 'REPLACE':        old_value *= 1.0 - strip.influence;    old_value += active_value
                                                if strip.blend_type == 'ADD':            old_value += active_value
                                                if strip.blend_type == 'SUBTRACT':        old_value -= active_value
                                                if strip.blend_type == 'MULTIPLY':        old_value *= active_value
                                    
                                    dif = abs(old_value-new_value);
                                    dif *= -1 if new_value &lt; old_value else 1;
                                    
                                    if active_blend == 'ADD':    active_key.co[1] = dif# + default;
                                    if active_blend == 'SUBTRACT':    active_key.co[1] = dif * -1 
                                    if active_blend == 'MULTIPLY':    
                                        try: dif = abs(new_value/old_value);
                                        except: dif = default; #can't divide by 0
                                        #if 0 &lt; old_value &lt; 1: dif +=1
                                        #if -1&lt; old_value &lt; 0: dif -=1
                                        
                                        dif *= -1 if new_value &lt; old_value else 1;
                                        active_key.co[1] = dif
                                    
                                    active_curve.update()
                                    #self.report({'INFO'}, 'Add()');
        if event.type in {'ESC','Stop'}:
            self.report({'INFO'}, 'Stopped Overwrite Keys')
            return {'PASS_THROUGH','FINISHED'}
        if event.value == 'RELEASE':
            check();
        #if event.value == 'PRESS':
        #    check();
        
        return{"PASS_THROUGH"} #Don't block keys
        return {"RUNNING_MODAL"}


class NLA_ADD_Header(bpy.types.Header):
    bl_idname = "add_check_header"
    bl_space_type = "NLA_EDITOR"
    bl_region_type = "HEADER"

    def draw(self, context):    self.layout.row().operator('my_operator.update_layer_keys_check')

'''class NLA_Active_Action(bpy.types.Panel):
    bl_idname = "nla_active_action";    bl_label = "NLA Active Action";    bl_space_type = "NLA_EDITOR";    bl_region_type = "UI";    bl_category = "Animations";    bl_options = {"HIDE_HEADER"};

    def draw(self, context): self.layout.row().operator('my_operator.update_layer_keys_check')'''
def register():
    bpy.utils.register_module(__name__)

def unregister():
    bpy.utils.unregister_module(__name__)

if __name__ == "__main__":
    register()

Multiply will not alter a value of 0 (x*0 = 0), so I suppose that could be a reason to use that; it’s also possible that placing new strips under Multiply will “boost” those new layer values, whereas Add/Subtract should maintain the same offsets from when they were keyed.

If anyone knows when Multiply/Subtract would be of use, I’d love to hear how, as they just really seem useless to me.

This sounds brilliant. I hope someone makes a video demonstration for it soon. Thank you Edtion for your ingenuity and generosity. Cheers!

Thanks.
I guess seeing a video of someone using layers would be cool (I’ll assume you meant as situation/use-case examples, and not like a tutorial).

Example goals/usage could be seen in videos of “Maya Animation Layers”, except in the NLA Editor (and clicking the button).
Maybe someone will use this and record it but I doubt it and I’m not going to…well not a practical example anyway: I made this recording showing default Blend+Add Layers then using this to make 2 separate additive layers showing layered-animation. It’s just a cube being dragged and rotated around, not at all interesting.

https://gfycat.com/ShadyGreatKittiwake (second part won’t upload, but it’s just me making another layer and etc)

Thanks you so much and Congratulations.

PS: I found this error:

"category": " Animation"
                  *
&gt;&gt; "category": "Animation"

Byebye
Spirou4D

That’s more of a typo but thanks, I updated the bl_info.

Great work mate. This is very useful.
The record animation from your video is a nice feature Blender has. If I recall, there was an addon for it:

Problem is it doesn’t record pauses in movements

Yes, I have that but the thing I’m using is my own other script I made. I made it to simulate a recording function from another program. 1) disable playback-loop, 2) only start playback when I start moving (and mute channels so the animation doesn’t jitter at keyframes)

I prefer regular recording to RTA because the only viable way to continuously record with it is using the smooth mouse function or holding shift when you’re slowing down (or edit that addon). There’s an option labelled [Layered] during playback with record enabled, to auto-convert actions to NLA strips when the frames loop back (removing the jitter) but I completely forgot about this and prefer to have very few tracks.

If you want it, I could upload it. (the addon)
Like this NLA-correction thing, it’s a part of a multi-collection addon; I just split the relevant code and slap on a bl_info for others to use.

That would be awesome. Pls do. If you can, at your convenience if it’s not too much trouble, could you make a video showing how to use it or a manual we can read?

Thanks so much, mate :- )

It’s really simple to use.
Just replace the default playback key with the operator from this script (see installation) then play like usual. You’ll basically just have two toggleable buttons to enable/disable what it’s doing.

Installation:
Replace the default playback command: [screen.animation_play] with [screen.animation_play_wait] or create it under a new key.
The command is found under Frames in the User Prefs>>Input. You can also change the search type from Name to Key Binding then search for it that way (Alt+A).

Two buttons in the Timeline header. #1) Play = automatically start playback like normal. #1) Wait = don't start playback until the user presses [G, R, S, Spacebar, LeftMouse] (escape to cancel). Spacebar just begins playback. Depending on the key pressed (and the active pivot mode) channels from the selected objects/bones will be muted until you're done recording.

#2) Loop = the default behavior of jumping back to frame#1 and continuing playback.
#2) Once = when the animation reaches the last frame, playback will stop.

playback.zip (2.41 KB)

One thing to know about Wait, is if you record over an animation but don’t record anything (the moments where gaps are left in the curve), the previous keyframes will remain. At least afaik.

…heh, now that I think about it, I never really posted anything about this thing I did with keyframe types. I made an alternative to the jump to keyframes operator to make a faster “jump-between-keyframes” because Amaranth’s version is too slow. And then I added a variable that lets you “skip” keyframe types.
So for example, you can make some key poses, then add a bunch of in-between keys, then jump over them when you just want to see the key poses. Also when recording/sampling curves for a lot of keyframes.

1 Like

Thanks a ton, Edition. :slight_smile:

Thanks a lot

nla_add_layer_keys.py

I made some performance changes and updated the script to be able to always run.
This way, you don’t have to even think about whether or not your layers will be corrected.

I also made an additive Baker and keyframe resetter.

See first post for new version/info.

1 Like

Fixed a bug or two, added a slight performance increase (for more situations where the addon should or should not even run).
Also made video examples showing the addon with animation from Mixamo.

Also, here’s a video of a character + scene that I was going to use for the exampled, before opted to use Mixamo:

1 Like

I am interested. Please could you host on youtube? Thanks.
Great addon feature. Much needed.

Sorry, no can do.
I sort of “closed” my youtube account.
Streamable allows uploads without an arduous registration+agreements process, or even needing an account.

Can I ask for a feature?

Os it possible for you to add a bake process that sums keys on different layers and were the result is not a key-per-frame animation?

Cheers, great addon!

1 Like

That depends on what you’re asking.

Long Answer:

It sounds like you’re asking for a merge layers.
I’m just not sure if you’re also asking for additive, or regular (replace).

I've made attempts at merging layers.
  • I have a script/operator that can either do a
    frame-by-frame bake on only the bones available in the selected strips (using builtin baker)
    
    or insert a keyframe on every keyed frame (like I assume you’re asking).
    
    However one big problem with it, like the bake in this addon, is that it’s 127% the speed of the builtin baker (slower, and this addon additive-baker is likely even slower).
    
    It also doesn’t work with Additive layers (because I didn’t consider keeping the bakes as additive at the time).

If you want, I could quickly merge it with this addon to add the bake options.
MAYBE also slap the additive baker to it, so they stay/become additive, if getting that to work doesn’t take too much time

However, I wouldn’t expect that to be stable, and a better option would be to wait for me to get to merging after I try some “stabilization/optimization” tasks/tests/fixes

Short answer:
Yes/No. I can put it on my todo list

about my todo list:
  • Need to fix it to work in cases where you’re only using an additive layer for something’s keyframes. Currently if no lower strip has something keyframed, the value will default to an empty value (so that it doesn’t add/subtract/multiply anything, like the regular keyframed values would)
    
    There was also a problem with the baker, while using a Child Of object.
    Will have to look into these bugs, and any others, asap;

  • I learned something I could do with variables/properties, recently (long after the working core process was made for this). I could rewrite this to run/check less, and potentially run faster and more reliable.

  • The Pose In-Between operators (Push/Relax/Breakdowner) doesn’t work AT ALL with Additive layers, because they’re checking the keyframes from the current action, instead of the current and previous/next “VISUAL/Final” result.
    I think that would be a simple-ish task to create new add-operators of at least the relax and breakdowner.

  • For merge layers, and baking, currently I opt to cycling through frames in the scene, to visually update properties to find needed values. I’d hope using the math Blender uses internally for layers, would be a faster/cleaner and more reliable way of merging things.
    
    I gave up on my last attempt at this, and where I left it was practically as slow as cycling through the frames like currently, but potentially less accurate as well.
    Maybe another attempt would yield more favorable results ¯\_(ツ)_/¯

1 Like