Layered Actions¶
Overview¶
Blender stores animation in Actions, and IDs subscribe to an Action to get animated by it.
Actions are designed to support layered and non-linear animation workflows.1 For this purpose, Actions have Layers, and those Layers have animation Strips. These Layers and Strips are analogous to tracks and strips in Blender's VSE, except that they are for animation data rather than video.
Additionally, Actions can store animation data for more than one ID at a time via the "Slot" system.1 Each Action has a set of Slots, and every piece of animation data within an Action is earmarked for one of those Slots. IDs then subscribe not only to an Action, but also to a specific Slot within that Action. The ID is then animated by the subset of animation data in the Action that is marked for that Slot.
!!! Note Currently there is a limit of one Layer and one Strip per Action, because the full layered animation and strip features are not yet implemented. These limitation will be lifted in the future as those features are implemented. In the mean time, Blender's old NLA system is still supported and can be used for a limited form of layered animation workflow, albeit external to Actions rather than internal to them.
Nevertheless, the data model described below is still accurate, and despite
the limitation just mentioned, animation data within an Action is still stored
as described.
Action Data Model¶
To introduce Actions more completely, we'll start with a simplified high-level explanation, and then flesh it out as we discuss the individual components further below.
Here is a simplified diagram of the data inside an Action:
In this simplified model, an Action has three top-level arrays:
- Layers
- StripData
- Slots
Each Layer in turn contains its own array of Strips.
Strips do not directly contain animation data, but rather reference items in the StripData array, which in turn contain animation data. More than one Strip can reference the same StripData item (as seen in the last two strips of Layer 2 in the above diagram), in which case those Strips are instances of the same animation data.
The top-level Slot array defines the available Slots in an Action. StripData items in turn contain animation data for one or more of those Slots.
Slots¶
Conceptually, Slots are just a way to distinguish different sets of animation data within StripData items. For example, you may have two sets of "Location" F-Curves in a StripData item, one of which is for Slot "Cat" and the other of which is for Slot "Box". Importantly, Slots do not contain animation data themselves. Rather, they act as labels that earmark animation data in the StripData items of an Action.
Each Slot is conceptually made up of the following three things:
- An intended ID type.
- A user-facing name.
- A stable numeric handle.
The intended ID type indicates which type of ID the animation data earmarked by the Slot is supposed to animate. For example, it could be for objects, for meshes, for materials, for worlds, etc.
The combined ID type + name of a Slot is guaranteed to be unique within the Slot's owning Action, and this combination is referred to in code as the Slot's identifier. Note that the Slot name alone does not need to be unique within an Action: you can have an OB + "Bob" Slot and a ME + "Bob" Slot in the same Action, because the combination of ID type + name is still unique. This avoids having to name one of the two slots "Bob.001", which could be confusing as it arbitrarily fails to match the ID's name.
Note
Slot names are in no way required to match the names of the IDs that use them (nor to match anything else, for that matter), but it is often convenient for them to do so, and that is the default when Slots are created during keyframing.
The numeric handle of a Slot is also guaranteed to be unique within the Slot's owning Action, and additionally has the following even stricter guarantees:
- It never changes: it is assigned upon creation of the Slot, and never changes after that.
- It is guaranteed to uniquely represent a Slot over the entire lifetime of an Action as well. In other words, even if the Slot is deleted, its numeric handle will never be reused by another Slot within the same Action.
These guarantees make the numeric handle a stable way to refer to a Slot within an Action. The numeric handle is also very lightweight, since it's a simple integer. This is in contrast to the Slot's name, which can be changed and which is a comparatively heavyweight string.
The numeric handle therefore acts as the source-of-truth identity of a Slot within an Action and is used for the following things:
- It is used to earmark animation data within an Action for a specific Slot.
- A combination of Action pointer + Slot handle are used to record which Action and Slot animate an ID.
The Slot identifier (ID type + name) is mostly used in either:
- Higher-level, more user-facing contexts.
- Where the numeric handle isn't meaningful. For example, the Slot identifier is used for tracking which Slot was last assigned to an ID, independent of Action. Numeric handles are meaningless without the Action they belong to, but the identifier can be used to indicate a Slot independent of a specific Action.
StripData¶
StripData items are what store the actual animation data in an Action. For example, it is StripData that stores the F-Curves that animate an ID's properties.
In the future there will be more than one type of StripData, and each type will be stored in a different top-level array on the Action. However, at the moment there is just one type stored in one top-level array: keyframe StripData. Keyframe StripData items contain F-Curves and other keyframe data.
The F-Curves in a keyframe StripData item are organized into Channelbags, each of which is earmarked for a different Slot via the Slots' numeric handles. In this way, a keyframe StripData item can hold different animation data for more than one ID.
Strips¶
Strips live on Layers, and map StripData onto a Layer, much like video strips map video onto tracks in the VSE. A Strip is made up the following things:
- A reference to the StripData item that it maps.
- A start and end frame, which defines its extent in time on the Layer.
- A frame offset, which defines how the StripData it references is mapped in time onto the Layer.
The reference to the StripData item is in turn made up the following things:
- A Strip type. This determines which StripData array its StripData item is in, as different types each have their own array. (This also, of course, serves to define the type of the Strip.)
- A StripData item index. This is the index of the StripData item within the relevant array.
Note
Currently there is only one Strip type (a keyframe Strip), and thus only one array of StripData items. This will change in the future, however, and code that deals with StripData should be structured to account for this.
Layers¶
Layers are used to arrange animation data in a stack for mixing and related purposes. They are analogous to tracks in a video editor or DAW, or layers in image editing applications like Gimp, Krita, and Photoshop.
Layers have:
- A name.
- A blend mode that determines how their animation is combined with the animation of lower layers.
Action Versioning¶
TODO
Planning and Work-In-Progress Features¶
The above documentation is for the underlying data model so far, as actually implemented in Blender. The remaining documentation below describes the over-all planning of the layered animation project.
Work In Progress
The Layered animation system is currently work in progress. The goal for Blender 4.4 is to have "Slotted Actions" (see below) but not yet a fully layered animation system.
Relevant links:
- 2024: Slotted Actions planning (projects.blender.org)
- 2024: Animation 2025: Progress & Planning (code.blender.org)
- 2023: Layered Animation workshop (code.blender.org)
- 2022: Character Animation workshop (code.blender.org)
- Layered Animation: Open Questions (on HackMD)
Main Goals¶
In short, the work on "layered animation" brings two new features to the Action
data-block:
- Slots: Multiple data-blocks can be animated by a single
Action
(and still have different animation). - Layers: Animation can be stored in layers on an
Action
(rather than having separate NLA data-structures per animated data-block).
Design¶
For the broad design, see the 2023 workshop blog post. Since then some things were changed / detailed, which are described here.
Phase 1: Multi-data-block Animation¶
The first phase of the work will focus on multi-data-block animation. This makes it possible to have related data-blocks share an Action (so a single box to put all the animation in). Examples are:
- Mesh transform (object) and shape key values (belongs to the object data).
- Camera transform (object) and lens parameters (object data).
- Metaball transforms, where all objects in a group of metaballs can share the same Action.
The initial version of the "slotted Actions" system will not have layers or strips, at least not from the user/UI perspective. Under the hood animation data is still stored on a layer with a single strip. These are all implicitly created, though. This makes the whole UI/UX design a lot simpler, and allows us to focus on the multi-data-block animation part first.
For an up to date plan, see Slotted Actions Planning.
Dividing Actions with Slots¶
An Action is a container that holds the animation data for one or more data-blocks. Data-blocks in Blender represent various types of data such as objects, armatures, or materials, which can all be animated.
To differentiate the animations for each data-block within an Action, the concept of Slots is used. Each Slot within an Action corresponds to a specific data-block, allowing for organized and precise animation control. This division ensures that animations for different data-blocks do not interfere with each other and can be managed separately.
Linking Data-blocks to Actions and Slots¶
Data-blocks are linked to Actions to define their animations. Each data-block points to the Action that contains its animation data and specifies which Slot within that Action it uses. This setup allows for a flexible animation system where multiple data-blocks can share the same Action but still have distinct animations through their respective Slots.
Action Independence¶
It is important to note that an Action itself does not decide what it will animate. Instead, it is the data-blocks that reference an Action and choose the appropriate Slot to define their animation.
Sharing Slots Across Data-blocks¶
In some cases, multiple data-blocks can share the same Slot. When this occurs, the data-blocks will be animated identically, as they are referencing the same set of animation data within the Action. However, sharing the same Slot is considered a corner case and is not expected to be widely used. It can be useful in specific scenarios where identical animation across multiple data-blocks is desired.
Phase 2: Layered Animation¶
Once Slotted Actions have landed in Blender, and animators have had time to get used to it, the next
phase of the work will bring layered animation to the Action
. There will likely be other work done
between the two phases, in order for Phase 1 to receive the necessary feedback and subsequent
adjustments.
Channel Groups & F-Curve Ordering¶
Legacy Action
s are separated into 'action groups'. These are arbitrarily named
groupings of F-Curves. This has certain downsides:
- Grouping: Some tooling assumes that these are not arbitrarily named, and makes the assumption that if they match a bone name, all the animation for that bone is inside that group. These assumptions can fail, as it's possible to arbitrarily rearrange F-Curves and even pop them out of a group, or group them manually.
- F-Curve Order: F-Curves can be manually reordered within the Action. This makes it rather complex for the Blender code to efficiently, say, get all rotation components, as this requires a full scan of all the F-Curves.
Proposed design for F-Curves in an Animation
data-block:
Grouping would (for now) be hard-coded and automatically managed by Blender. These groups would have to exist in DNA in order to store things like expanded/collapsed state, selection state, and color (for example for bones). It shall not be possible to manually add groups or to move F-Curves between groups.
Blender would create the following groupings: - Property groups: "Transforms", "Custom Properties", maybe some other classification of properties. - Sub-data groups: Bone name, maybe other sub-data as well.
F-Curve Order: F-Curves should be ordered by group, and within that group by RNA path and array index. This makes it predictable (for both animators and Blender itself) where they are and how they're ordered. For example, the current Euler Filter operator assumes that F-Curves for rotation components are sequentially stored. In practice this is true most of the time, but not always. Enforcing a fixed ordering will make these operations faster and more predictable. It is also unclear how much advantage manually reorderable F-Curves bring to animators.
For groupings of sub-data, the group order should follow the sub-data order. For bones this would simply be alphabetical, but for shapekeys it should follow the shapekey order. It is unclear whether this reordering would always happen when reordering the sub-data, or whether it would be some button to press to sync up the ordering. Probably the former is more desirable.
Ordering of Groups manually should likely still be possible. That way an animator can, for example, order bones in a sensible-for-the-rig way. A common example is to order the fingers in actual finger order, instead of alphabetically.