| name | blender-syntax-animation |
| description | Use when creating animations programmatically in Blender -- keyframes, FCurves, Actions, NLA strips, drivers, or armature operations. Prevents the breaking change pitfall of using bone.layers (removed in 4.0) instead of BoneCollection. Covers keyframe_insert, FCurve access, Action data blocks, NLA system, driver expressions, and timeline control. Keywords: keyframe, FCurve, Action, NLA, BoneCollection, armature, driver, animation, bone layers, timeline, bpy.ops.anim, set keyframe from script, animate object, move object over time.
|
| license | MIT |
| compatibility | Designed for Claude Code. Requires Blender 3.x/4.x/5.x with Python. |
| metadata | {"author":"OpenAEC-Foundation","version":"1.0"} |
blender-syntax-animation
Quick Reference
Critical Warnings
ALWAYS set the property value BEFORE calling keyframe_insert() — keyframes record the current value at call time.
ALWAYS call fcurve.update() after manually adding or modifying keyframe points via keyframe_points.add() or direct .co assignment.
NEVER access edit_bones outside of Edit Mode — the collection is empty/inaccessible in Object or Pose Mode.
NEVER use bone.layers or pose.bone_groups in Blender 4.0+ — these are removed. Use BoneCollection instead.
NEVER use driver.expression referencing self without enabling driver.use_self = True.
ALWAYS create animation_data before accessing .action or .nla_tracks — call obj.animation_data_create() if obj.animation_data is None.
Decision Tree: Animation Approach
Need to animate a property?
├─ Simple keyframe on existing property?
│ └─ Use obj.keyframe_insert(data_path=..., frame=...)
├─ Need programmatic control over curve shape?
│ └─ Use FCurve API: action.fcurves.new() + keyframe_points.add()
├─ Need to combine/layer multiple animations?
│ └─ Use NLA system: nla_tracks.new() + track.strips.new()
├─ Need property driven by expression or other property?
│ └─ Use Drivers: obj.driver_add() + driver.expression
├─ Need to organize armature bones into groups (4.0+)?
│ └─ Use BoneCollection: armature.collections.new()
└─ Need parametric mesh deformation?
└─ Use Shape Keys: obj.shape_key_add() + keyframe on .value
Version Matrix: Animation Features
| Feature | Blender 3.x | Blender 4.0+ | Blender 5.x |
|---|
keyframe_insert() | Available | Available | Available |
FCurve / Action | Available | Available | Available |
NLA system | Available | Available | Available |
bone.layers[i] | Available | REMOVED | REMOVED |
pose.bone_groups | Available | REMOVED | REMOVED |
BoneCollection | Not available | NEW | Available |
BoneCollection.color_tag | Not available | NEW | Available |
| Drivers | Available | Available | Available |
| Shape Keys | Available | Available | Available |
Essential Patterns
Pattern 1: Keyframe Insertion
import bpy
obj = bpy.data.objects.get("Cube")
if obj is None:
raise RuntimeError("Object 'Cube' not found")
obj.location = (0.0, 0.0, 0.0)
obj.keyframe_insert(data_path="location", frame=1)
obj.location = (5.0, 0.0, 3.0)
obj.keyframe_insert(data_path="location", frame=60)
obj.keyframe_insert(data_path="location", frame=30, index=2)
obj.keyframe_insert(data_path="rotation_euler", frame=1, group="Transform")
Parameters for keyframe_insert():
| Parameter | Type | Description |
|---|
data_path | str | RNA path to property ("location", "rotation_euler", "scale") |
index | int | Array index for vector properties (-1 = all, 0 = X, 1 = Y, 2 = Z) |
frame | float | Frame number for the keyframe |
group | str | Action group name for this FCurve |
options | set | {'INSERTKEY_NEEDED'}, {'INSERTKEY_VISUAL'}, etc. |
Pattern 2: FCurve Access and Manipulation
import bpy
obj = bpy.data.objects.get("Cube")
anim_data = obj.animation_data
if anim_data is None:
anim_data = obj.animation_data_create()
action = anim_data.action
if action is None:
action = bpy.data.actions.new(name="MyAction")
anim_data.action = action
fcurve = action.fcurves.new(data_path="location", index=0)
fcurve.keyframe_points.add(count=3)
fcurve.keyframe_points[0].co = (1.0, 0.0)
fcurve.keyframe_points[1].co = (30.0, 5.0)
fcurve.keyframe_points[2].co = (60.0, 0.0)
for kp in fcurve.keyframe_points:
kp.interpolation = 'BEZIER'
kp.handle_left_type = 'AUTO_CLAMPED'
kp.handle_right_type = 'AUTO_CLAMPED'
fcurve.update()
Pattern 3: Drivers
import bpy
obj = bpy.data.objects.get("Cube")
driver_fcurve = obj.driver_add("location", 2)
driver = driver_fcurve.driver
driver.type = 'SCRIPTED'
var = driver.variables.new()
var.name = "ctrl_z"
var.type = 'TRANSFORMS'
target = var.targets[0]
target.id = bpy.data.objects["ControlEmpty"]
target.transform_type = 'LOC_Z'
target.transform_space = 'WORLD_SPACE'
driver.expression = "ctrl_z * 2.0"
Pattern 4: NLA System
import bpy
obj = bpy.data.objects.get("Cube")
anim_data = obj.animation_data
if anim_data is None:
anim_data = obj.animation_data_create()
track = anim_data.nla_tracks.new()
track.name = "MovementTrack"
action = bpy.data.actions.get("MoveAction")
if action is not None:
strip = track.strips.new(name="Move", start=1, action=action)
strip.blend_type = 'REPLACE'
strip.use_auto_blend = True
strip.repeat = 1.0
strip.scale = 1.0
strip.mute = False
Pattern 5: Bone Collections (4.0+)
import bpy
armature_data = bpy.data.armatures.new("MyArmature")
armature_obj = bpy.data.objects.new("MyArmature", armature_data)
bpy.context.collection.objects.link(armature_obj)
bpy.context.view_layer.objects.active = armature_obj
bpy.ops.object.mode_set(mode='EDIT')
root = armature_data.edit_bones.new("Root")
root.head = (0, 0, 0)
root.tail = (0, 0, 1)
child = armature_data.edit_bones.new("Child")
child.head = (0, 0, 1)
child.tail = (0, 0, 2)
child.parent = root
bpy.ops.object.mode_set(mode='OBJECT')
deform_col = armature_data.collections.new("Deform")
control_col = armature_data.collections.new("Controls")
deform_col.assign(armature_data.bones["Root"])
control_col.assign(armature_data.bones["Child"])
deform_col.is_visible = True
control_col.is_visible = True
control_col.color_tag = 'THEME01'
Pattern 6: Shape Keys
import bpy
obj = bpy.data.objects.get("Cube")
basis = obj.shape_key_add(name="Basis", from_mix=False)
deformed = obj.shape_key_add(name="Open", from_mix=False)
for i, vert in enumerate(deformed.data):
vert.co.z += 0.5
key_block = obj.data.shape_keys.key_blocks["Open"]
key_block.value = 0.0
key_block.keyframe_insert(data_path="value", frame=1)
key_block.value = 1.0
key_block.keyframe_insert(data_path="value", frame=30)
Common Operations
Timeline Control
import bpy
scene = bpy.context.scene
scene.frame_start = 1
scene.frame_end = 250
scene.frame_set(50)
scene.frame_current = 50
fps = scene.render.fps / scene.render.fps_base
Reading FCurve Data
import bpy
obj = bpy.data.objects.get("Cube")
if obj.animation_data and obj.animation_data.action:
action = obj.animation_data.action
for fcurve in action.fcurves:
print(f"Path: {fcurve.data_path}[{fcurve.array_index}]")
print(f" Range: {fcurve.range()}")
val = fcurve.evaluate(frame=25.0)
print(f" Value at frame 25: {val}")
FCurve Modifiers
import bpy
obj = bpy.data.objects.get("Cube")
action = obj.animation_data.action
fcurve = action.fcurves[0]
noise = fcurve.modifiers.new(type='NOISE')
noise.scale = 5.0
noise.strength = 0.3
noise.phase = 0.0
cycles = fcurve.modifiers.new(type='CYCLES')
cycles.mode_before = 'REPEAT'
cycles.mode_after = 'REPEAT'
Constraints
import bpy
obj = bpy.data.objects.get("Cube")
constraint = obj.constraints.new('COPY_LOCATION')
constraint.name = "Follow"
constraint.target = bpy.data.objects.get("Empty")
constraint.influence = 0.5
armature = bpy.data.objects.get("Armature")
if armature:
pose_bone = armature.pose.bones.get("Hand")
if pose_bone:
ik = pose_bone.constraints.new('IK')
ik.target = bpy.data.objects.get("IKTarget")
ik.chain_count = 3
Bone Access by Mode
| Mode | Collection | Access Type | Use Case |
|---|
| Edit Mode | armature.edit_bones | Read/Write | Create, delete, reparent bones |
| Object Mode | armature.bones | Read-only properties | Read rest pose, check collections |
| Pose Mode | armature.pose.bones | Transforms, constraints | Animate, add constraints |
Version Migration: Bone Layers to Bone Collections
Blender 3.x (Legacy: BROKEN in 4.0+)
Blender 4.0+ (Current)
armature = bpy.data.armatures["MyArmature"]
col = armature.collections.new("MyGroup")
col.assign(armature.bones["BoneName"])
col.unassign(armature.bones["BoneName"])
is_member = armature.bones["BoneName"] in col.bones
col.color_tag = 'THEME05'
col.is_visible = False
Version-Safe Pattern
import bpy
def assign_bone_to_group(armature_data, bone_name, group_name, color_tag='THEME01'):
"""Assign bone to a named group. Works in 4.0+ only."""
if bpy.app.version < (4, 0, 0):
raise RuntimeError("BoneCollection requires Blender 4.0+")
col = armature_data.collections.get(group_name)
if col is None:
col = armature_data.collections.new(group_name)
col.color_tag = color_tag
bone = armature_data.bones.get(bone_name)
if bone is not None:
col.assign(bone)
return col
Reference Links
Official Sources