Skip to content
Back to skills

Blender Animation

ASecurity

Animates 3D objects and characters in Blender with Python (bpy): keyframes, armatures and IK rigs, shape keys, F-Curves, the NLA editor and drivers. Use when the user wants to keyframe properties, create armatures and rigs, set up IK/FK chains, animate shape keys for facial animation, edit F-Curves, blend actions with NLA strips, add drivers, or script any animation workflow in Blender from the terminal.

  • 142 stars
  • 0 votes
  • 3 copies
  • 11 views
  • Added May 27, 2026
developmentpythongobashexpressapi

Works with

  • terminal
  • api

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned October 4, 2026

npx -y skills add TerminalSkills/skills --skill blender-animation --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Blender Animation?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Blender Animation
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/terminalskills-blender-animation/badge)](https://www.skillsdirectory.com/skills/terminalskills-blender-animation)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: blender-animation
description: >-
  Animates 3D objects and characters in Blender with Python (bpy): keyframes, armatures and IK rigs, shape keys, F-Curves, the NLA editor and drivers. Use when the user wants to keyframe properties, create armatures and rigs, set up IK/FK chains, animate shape keys for facial animation, edit F-Curves, blend actions with NLA strips, add drivers, or script any animation workflow in Blender from the terminal.
license: Apache-2.0
compatibility: >-
  Blender 4.4 or newer (verified on 5.2.2 LTS); run scripts with blender --background --python script.py. Blender 5.0 removed the old action.fcurves API used by older tutorials.
metadata:
  author: terminal-skills
  version: "1.1.0"
  category: design
  tags: ["blender", "animation", "rigging", "keyframes", "armature"]
  repository: https://projects.blender.org/blender/blender
---

# Blender Animation

## Overview

Create and control animations in Blender using Python: keyframe object and bone transforms, build armatures with IK rigs, animate shape keys, edit F-Curves, layer actions in the NLA editor and wire up drivers, all from a script.

Since Blender 4.4 an **Action** is layered and has **slots**: a slot says which data-block an Action animates, and the F-Curves live in a *channelbag* on a keyframe strip inside a layer. Blender 5.0 removed the old shortcuts `action.fcurves`, `action.groups` and `action.id_root`, so code from older tutorials raises `AttributeError: 'Action' object has no attribute 'fcurves'`. `obj.keyframe_insert(...)` still works unchanged and creates the action, slot and channelbag for you. Run scripts in the background:

```bash
blender --background --factory-startup --python animate.py
```

A factory-startup scene contains `Cube`, `Camera` and `Light`.

## Instructions

### 1. Keyframe object properties

```python
import bpy
obj = bpy.data.objects["Cube"]
for loc, frame in [((0, 0, 0), 1), ((5, 0, 3), 30), ((5, 4, 0), 60)]:
    obj.location = loc
    obj.keyframe_insert(data_path="location", frame=frame)
obj.rotation_euler = (0, 0, 3.14159)
obj.keyframe_insert(data_path="rotation_euler", frame=60)
obj.scale = (2, 2, 2)
obj.keyframe_insert(data_path="scale", frame=60)
obj["intensity"] = 1.0                                        # custom property
obj.keyframe_insert(data_path='["intensity"]', frame=30)
obj.keyframe_insert(data_path="location", index=2, frame=45)  # one axis only (Z)
bpy.context.scene.frame_start, bpy.context.scene.frame_end = 1, 60
```

The created action is `obj.animation_data.action` and its slot is `obj.animation_data.action_slot`.

### 2. Control F-Curve interpolation and modifiers

F-Curves are reached through the channelbag of the slot:

```python
import bpy
from bpy_extras import anim_utils
obj = bpy.data.objects["Cube"]
anim = obj.animation_data
channelbag = anim_utils.action_get_channelbag_for_slot(anim.action, anim.action_slot)
for fcurve in channelbag.fcurves:
    for kp in fcurve.keyframe_points:
        kp.interpolation = 'BEZIER'   # CONSTANT, LINEAR, BEZIER, SINE, EXPO, BOUNCE, ELASTIC
        kp.easing = 'EASE_IN_OUT'     # AUTO, EASE_IN, EASE_OUT, EASE_IN_OUT
    mod = fcurve.modifiers.new(type='CYCLES')   # loop the animation
    mod.mode_before = mod.mode_after = 'REPEAT'  # NONE, REPEAT, REPEAT_OFFSET, MIRROR
z_curve = channelbag.fcurves.find("location", index=2)
if z_curve:
    noise = z_curve.modifiers.new(type='NOISE')
    noise.strength, noise.scale = 0.3, 5.0
# Get-or-create a curve in a named channel group
fc = channelbag.fcurves.ensure("location", index=0, group_name="Object Transforms")
```

`anim_utils.action_ensure_channelbag_for_slot(action, slot)` creates the layer, strip and channelbag when none exist.

### 3. Create armatures and bones

```python
import bpy
arm_data = bpy.data.armatures.new("Rig")
arm_obj = bpy.data.objects.new("Rig", arm_data)
bpy.context.collection.objects.link(arm_obj)
bpy.context.view_layer.objects.active = arm_obj
bpy.ops.object.mode_set(mode='EDIT')
# (name, head, tail, parent_name, connected)
bone_defs = [
    ("Spine",      (0, 0, 1.0),   (0, 0, 1.4),    None,         False),
    ("Chest",      (0, 0, 1.4),   (0, 0, 1.8),    "Spine",      False),
    ("UpperArm.L", (0.2, 0, 1.7), (0.5, 0, 1.4),  "Chest",      False),
    ("Forearm.L",  (0.5, 0, 1.4), (0.8, 0, 1.1),  "UpperArm.L", True),
    ("Thigh.L",    (0.1, 0, 1.0), (0.1, 0, 0.5),  "Spine",      False),
    ("Shin.L",     (0.1, 0, 0.5), (0.1, 0, 0.05), "Thigh.L",    True),
]
for name, head, tail, parent, connected in bone_defs:
    b = arm_data.edit_bones.new(name)
    b.head, b.tail = head, tail
    if parent:
        b.parent = arm_data.edit_bones[parent]
        b.use_connect = connected
bpy.ops.object.mode_set(mode='OBJECT')
```

### 4. Constraints on bones

```python
import bpy
arm_obj = bpy.data.objects["Rig"]
for name, loc in (("IK_Target", (0.8, 0, 1.1)), ("IK_Pole", (0.5, -1, 1.4)), ("ChestCtrl", (0, 0, 2))):
    empty = bpy.data.objects.new(name, None)          # an Empty: object data is None
    empty.location = loc; bpy.context.collection.objects.link(empty)
bpy.context.view_layer.objects.active = arm_obj
bpy.ops.object.mode_set(mode='POSE')
ik = arm_obj.pose.bones["Forearm.L"].constraints.new('IK')
ik.target = bpy.data.objects["IK_Target"]
ik.pole_target = bpy.data.objects["IK_Pole"]
ik.chain_count = 2                                    # 0 = whole chain up to the root
cr = arm_obj.pose.bones["Chest"].constraints.new('COPY_ROTATION')
cr.target = bpy.data.objects["ChestCtrl"]
```

### 5. Keyframe bone poses

Pose bones default to quaternion rotation, so set `rotation_mode = 'XYZ'` before keying `rotation_euler`. Keying creates the action; rename it afterwards:

```python
import bpy, math
arm_obj = bpy.data.objects["Rig"]
bpy.context.view_layer.objects.active = arm_obj
bpy.ops.object.mode_set(mode='POSE')
thigh = arm_obj.pose.bones["Thigh.L"]
thigh.rotation_mode = 'XYZ'
for angle, frame in [(-30, 1), (0, 13), (30, 25), (-30, 37)]:   # back, neutral, forward, back
    thigh.rotation_euler = (math.radians(angle), 0, 0)
    thigh.keyframe_insert(data_path="rotation_euler", frame=frame)
arm_obj.animation_data.action.name = "WalkCycle"
bpy.ops.object.mode_set(mode='OBJECT')
```

### 6. Create and animate shape keys

```python
import bpy
bpy.ops.mesh.primitive_uv_sphere_add(radius=1.0)
head = bpy.context.active_object
head.name = "Head"
if not head.data.shape_keys:
    head.shape_key_add(name="Basis", from_mix=False)      # Basis must exist first
smile = head.shape_key_add(name="Smile", from_mix=False)
for vert in smile.data:
    if vert.co.y < -0.6 and abs(vert.co.z) < 0.3:         # a patch on the front of the sphere
        vert.co.z += 0.1
sk = head.data.shape_keys.key_blocks["Smile"]
for val, frame in [(0.0, 1), (1.0, 15), (0.0, 30)]:
    sk.value = val
    sk.keyframe_insert(data_path="value", frame=frame)
```

### 7. Layer actions with the NLA editor

```python
import bpy, math
arm_obj = bpy.data.objects["Rig"]
anim = arm_obj.animation_data
anim.action = None                       # detach WalkCycle; it stays in bpy.data.actions
upper = arm_obj.pose.bones["UpperArm.L"]
upper.rotation_mode = 'XYZ'
for angle, frame in [(0, 1), (60, 10), (0, 20)]:
    upper.rotation_euler = (0, 0, math.radians(angle))
    upper.keyframe_insert(data_path="rotation_euler", frame=frame)
anim.action.name = "WaveHand"
anim.action = None                       # an active action overrides NLA, so clear it
track1 = anim.nla_tracks.new(); track1.name = "Walk"
strip1 = track1.strips.new("Walk", start=1, action=bpy.data.actions["WalkCycle"])
strip1.repeat = 4
strip1.blend_type = 'REPLACE'            # REPLACE, COMBINE, ADD, SUBTRACT, MULTIPLY
track2 = anim.nla_tracks.new(); track2.name = "Wave"
strip2 = track2.strips.new("Wave", start=25, action=bpy.data.actions["WaveHand"])
strip2.blend_type = 'COMBINE'            # on top of the walk
strip2.influence = 0.8
```

Strips pick a compatible slot automatically (`strip.action_slot`). Assigning `anim.action = some_action` can leave no slot selected; if the object stays static, also set `anim.action_slot = some_action.slots[0]`.

### 8. Drivers

```python
import bpy
cube = bpy.data.objects["Cube"]
driver = cube.driver_add("location", 2).driver      # drive Z location
driver.type = 'SCRIPTED'
var = driver.variables.new()
var.name = "ctrl"
var.type = 'TRANSFORMS'
var.targets[0].id = bpy.data.objects["ChestCtrl"]
var.targets[0].transform_type = 'LOC_X'
var.targets[0].transform_space = 'WORLD_SPACE'
driver.expression = "ctrl * 3 + sin(frame * 0.1)"   # shape keys work too: sk.driver_add("value")
print(driver.is_valid)                               # scripted expressions need auto-run scripts allowed
```


### 9. Bake before export

Other software cannot read constraints, NLA or drivers. Bake to plain keyframes (a new action is written and the constraints are removed):

```python
arm_obj = bpy.data.objects["Rig"]
arm_obj.select_set(True); bpy.context.view_layer.objects.active = arm_obj
bpy.ops.nla.bake(frame_start=1, frame_end=60, only_selected=False,
                 visual_keying=True, clear_constraints=True, bake_types={'POSE'})
```

## Examples

### Example 1: Bouncing ball with squash and stretch

User request: "Animate a ball bouncing with squash and stretch."

```python
import bpy, os
from bpy_extras import anim_utils
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete()
bpy.ops.mesh.primitive_uv_sphere_add(radius=0.5, location=(0, 0, 3))
ball = bpy.context.active_object
ball.name = "BouncingBall"
keyframes = [  # (frame, z_pos, scale_x, scale_y, scale_z)
    (1, 3.0, 1, 1, 1), (12, 0.5, 1, 1, 1),                  # fall
    (15, 0.3, 1.3, 1.3, 0.6), (18, 0.5, 0.85, 0.85, 1.2),   # squash, then stretch
    (30, 2.2, 1, 1, 1), (42, 0.5, 1, 1, 1),                 # apex, second fall
    (45, 0.3, 1.2, 1.2, 0.7), (55, 1.5, 1, 1, 1), (62, 0.5, 1, 1, 1),
]
for frame, z, sx, sy, sz in keyframes:
    ball.location = (0, 0, z)
    ball.keyframe_insert(data_path="location", frame=frame)
    ball.scale = (sx, sy, sz)
    ball.keyframe_insert(data_path="scale", frame=frame)
anim = ball.animation_data
for fc in anim_utils.action_get_channelbag_for_slot(anim.action, anim.action_slot).fcurves:
    for kp in fc.keyframe_points:
        kp.interpolation = 'BEZIER'
bpy.context.scene.frame_end = 62; bpy.ops.wm.save_as_mainfile(filepath=os.path.abspath("bouncing_ball.blend"))
```

Result: `bouncing_ball.blend` in the working directory with 6 F-Curves (location and scale, XYZ) over frames 1 to 62; the ball flattens on impact and stretches on the way up.

### Example 2: Arm rig with IK reaching for a cube

User request: "Create a simple arm rig with IK and animate it reaching for a cube."

```python
import bpy, os
from bpy_extras import anim_utils
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete()
arm_data = bpy.data.armatures.new("ArmRig")
arm_obj = bpy.data.objects.new("ArmRig", arm_data)
bpy.context.collection.objects.link(arm_obj)
bpy.context.view_layer.objects.active = arm_obj
bpy.ops.object.mode_set(mode='EDIT')
prev = None
for name, x0, x1 in [("UpperArm", 0, 0.6), ("Forearm", 0.6, 1.2), ("Hand", 1.2, 1.4)]:
    b = arm_data.edit_bones.new(name)
    b.head, b.tail = (x0, 0, 1.5), (x1, 0, 1.5)
    if prev:
        b.parent, b.use_connect = prev, True
    prev = b
bpy.ops.object.mode_set(mode='OBJECT')
ik_target = bpy.data.objects.new("IK_Hand", None)
ik_target.location = (1.4, 0, 1.5); bpy.context.collection.objects.link(ik_target)
bpy.context.view_layer.objects.active = arm_obj
bpy.ops.object.mode_set(mode='POSE')
ik = arm_obj.pose.bones["Hand"].constraints.new('IK')
ik.target, ik.chain_count = ik_target, 3
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.mesh.primitive_cube_add(size=0.3, location=(1.0, 0.5, 1.0))   # the cube to reach
for loc, frame in [((1.4, 0, 1.5), 1), ((1.0, 0.5, 1.0), 30), ((1.0, 0.5, 1.3), 50)]:
    ik_target.location = loc
    ik_target.keyframe_insert(data_path="location", frame=frame)
anim = ik_target.animation_data
for fc in anim_utils.action_get_channelbag_for_slot(anim.action, anim.action_slot).fcurves:
    for kp in fc.keyframe_points:
        kp.interpolation, kp.easing = 'BEZIER', 'EASE_IN_OUT'
bpy.context.scene.frame_end = 50; bpy.ops.wm.save_as_mainfile(filepath=os.path.abspath("arm_grab.blend"))
```

Result: at frame 30 the hand bone's tip sits at the cube (1.0, 0.5, 1.0) in world space, then lifts to z=1.3 by frame 50.

## Guidelines

- `data_path` must match the RNA path exactly: `"location"`, `"rotation_euler"`, `"scale"`, `'["custom_prop"]'`, `'pose.bones["Thigh.L"].rotation_euler'`.
- Shape keys need a "Basis" key first; animate `key_blocks["Name"].value` from 0.0 to 1.0.
- Do not write `action.fcurves`, `action.groups` or `action.id_root`; they are gone in 5.0. Use the channelbag, and `slot.target_id_type` instead of `id_root`.
- An active action overrides NLA: set `animation_data.action = None` so strips drive the object.
- Armature workflow: Edit mode for `edit_bones`, Pose mode for constraints and keyframes (`pose.bones`); return to Object mode before saving.
- Run scripts in a separate `--background` process, never inside a session the user is working in.
- `bpy.ops` calls depend on context (active object, mode): set the active object explicitly and prefer data API calls such as `bpy.data.objects.new`.

Files in this skill

  • SKILL.md10.4 KB
  • _scores.json1.7 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…