Skip to content

Bone Collections

Bone Collections are a way to organise the bones of an Armature. Each Armature has its own set of bone collections.

Storage and Hierarchy

An armature's bone collections are stored in bArmature in a flat heap-allocated array of BoneCollection pointers. Each element of the array points to a single heap-allocated BoneCollection.

The indirection via a pointer (instead of storing BoneCollection structs directly) is to ensure that their memory locations remain stable when reordering or reallocating the array.

Hierarchy

Despite being stored in a flat array, the BoneCollections of an armature are conceptually in a tree structure, with parent and child collections. This tree is defined by two properties on each BoneCollection and some rules about how the BoneCollections are ordered in the array.

Firstly, siblings (collections with the same parent) are always stored as a contiguous block next to each other in the array. And second, the root collections (collections with no parent) are always stored contiguously at the start of the array.

Secondly, the structure of the tree is defined by two fields in each BoneCollection: the array index of its first child (child_index) and the number of children (child_count), which together effectively point to a block of children.

More exhaustively, here are the storage and hierarchy layout invariants:

  • Roots are always stored contiguously at the start of the array.
  • Siblings are always stored contiguously next to each other.
  • The collection array is allowed to be empty.
  • If non-empty, there must be at least one root collection.
  • All non-root collections must be descendants (either directly or indirectly) of a root collection. Equivalently, there should be no collections in the array that cannot be accessed by traversing the tree starting from a root.
  • The structure is always a strict forest. There are multiple roots, each of which are a strict tree. Cycles (i.e. a collection being its own ancestor/descendant) and shared children (a child having more than one parent) are not permitted.

Note on the Array Order

Aside from roots always being at the start of the array and siblings always being stored contiguously, there are no guarantees about the order of the collections in the array. For example, children may be stored either earlier or later in the array than their parent.

Naming

Bone Collections are identified by name, which has to be unique within the Armature. This uniqueness constraint has a downside, in that it is only possible to have one collection named Left, and one Right. This means that it is not possible to have a structure like:

  graph TB;
    Arm-->Left_Arm[Left];
    Arm-->Right_Arm[Right];

    Leg-->Left_Leg[Left];
    Leg-->Right_Leg[Right];

Instead, the naming will have to be:

  graph TB;
    Arm-->Left_Arm[Left Arm];
    Arm-->Right_Arm[Right Arm];

    Leg-->Left_Leg[Left Leg];
    Leg-->Right_Leg[Right Leg];

The reasons this uniqueness constraint was still maintained are:

  • The identifier of a bone collection is independent of the hierarchy. This makes it possible to reorganise the collections while retaining things like overrides, Python access, or even animation.
  • If it turns out to be overly restrictive, this uniqueness constraint can be dropped without breaking backward compatibility.

Python API

Root bone collections can be accessed via armature.collections, which also gives access to the main API for creating and removing bone collections. All collections are available via armature.collections_all.

Both .collections and .collections_all can be indexed by name or by index, i.e. both armature.collections_all["Root"] and `armature.collections_all[0] are valid.

import bpy

# Create bone collections
armature = bpy.context.object.data
bcoll_root = armature.collections.new("A Root Collection")
bcoll_child = armature.collections.new("Child Collection", parent=bcoll_root)

# Moving the bone collection after it has been created.
bcoll = armature.collections.new("collection")
bcoll.parent = bcoll_root  # Assign a new parent collection.
bcoll.child_number = 0     # Move to be the first of its siblings.

# Access to the top level (aka 'root') collections:
for bcoll in armature.collections:
    print(f'Root collection: {bcoll.name}')

# Access to all collections:
for bcoll in armature.collections_all:
    print(f'Collection: {bcoll.name}')

# Assigned bones can be retrieved hierarchically:
bcoll_child.assign(armature.bones['thigh.L'])
for bone in bcoll_root.bones_recursive:
    print(bone.name)

History & Versioning

Bone Collections were first introduced in Blender 4.0, and replaced Armature Layers and Bone Groups.

For a guide on how to upgrade Python code to transition from the Armature Layers API to the Bone Collections API, see Bone Collections & Colors: Upgrading to 4.0.