blender-animation
Animate 3D objects and characters in Blender with Python. 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, use the NLA editor to blend actions, add drivers for expression-based animation, or script any animation workflow in Blender.
Usage
Getting Started
- Install the skill using the command above
- Open your AI coding agent (Claude Code, Codex, Gemini CLI, or Cursor)
- Reference the skill in your prompt
- The AI will use the skill's capabilities automatically
Example Prompts
- "Create a responsive landing page layout following our brand guidelines"
- "Review this component for accessibility issues and suggest fixes"
Documentation
Overview
Create and control animations in Blender using Python. Keyframe object and bone transforms, build armatures with IK/FK rigs, animate shape keys for facial motion, edit F-Curves for timing control, layer actions in the NLA editor, and wire up drivers for expression-based automation — all scriptable from the terminal.
Instructions
1. Keyframe object properties
import bpy
obj = bpy.data.objects["Cube"]
# Keyframe location at multiple frames
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)
# Also works for rotation_euler, scale, single-axis (index=2 for Z), custom props
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)
bpy.context.scene.frame_start, bpy.context.scene.frame_end = 1, 60
2. Control F-Curve interpolation and modifiers
import bpy
action = bpy.data.objects["Cube"].animation_data.action
for fcurve in action.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
# Cycles modifier to loop the animation
mod = fcurve.modifiers.new(type='CYCLES')
mod.mode_before = 'REPEAT' # NONE, REPEAT, REPEAT_OFFSET, MIRROR
mod.mode_after = 'REPEAT'
# Add noise to a specific channel
z_curve = action.fcurves.find("location", index=2) # Z location
if z_curve:
noise = z_curve.modifiers.new(type='NOISE')
noise.strength, noise.scale = 0.3, 5.0
3. Create armatures and bones
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. Set up constraints on bones
import bpy
arm_obj = bpy.data.objects["Rig"]
bpy.context.view_layer.objects.active = arm_obj
bpy.ops.object.mode_set(mode='POSE')
# IK — use an empty as target, chain_count limits affected bones
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
# Copy Rotation — mirrors another object's rotation
cr = arm_obj.pose.bones["Chest"].constraints.new('COPY_ROTATION')
cr.target = bpy.data.objects["ChestCtrl"]
# Other types: LIMIT_ROTATION, DAMPED_TRACK, STRETCH_TO, COPY_LOCATION, TRACK_TO
bpy.ops.object.mode_set(mode='OBJECT')
5. Keyframe bone poses
import bpy, math
arm_obj = bpy.data.objects["Rig"]
bpy.context.view_layer.objects.active = arm_obj
bpy.ops.object.mode_set(mode='POSE')
if not arm_obj.animation_data:
arm_obj.animation_data_create()
action = bpy.data.actions.new("WalkCycle")
arm_obj.animation_data.action = action
# Keyframe thigh swing: back → neutral → forward
thigh = arm_obj.pose.bones["Thigh.L"]
for angle, frame in [(-30, 1), (0, 13), (30, 25)]:
thigh.rotation_euler = (math.radians(angle), 0, 0)
thigh.keyframe_insert(data_path="rotation_euler", frame=frame)
bpy.ops.object.mode_set(mode='OBJECT')
6. Create and animate shape keys
import bpy
obj = bpy.data.objects["Head"]
mesh = obj.data
if not mesh.shape_keys: # Basis key required first
obj.shape_key_add(name="Basis", from_mix=False)
smile = obj.shape_key_add(name="Smile", from_mix=False)
for i, vert in enumerate(smile.data):
if i in [42, 43, 56, 57]: # mouth corner vertices
vert.co.z += 0.02
# Animate shape key value (0.0 → 1.0 → 0.0)
sk = mesh.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
import bpy
arm_obj = bpy.data.objects["Rig"]
if not arm_obj.animation_data:
arm_obj.animation_data_create()
# Push actions as NLA strips on separate tracks
track1 = arm_obj.animation_data.nla_tracks.new()
track1.name = "Walk"
strip1 = track1.strips.new("Walk", start=1, action=bpy.data.actions["WalkCycle"])
strip1.repeat = 4 # loop 4 times
strip1.blend_type = 'REPLACE' # REPLACE, COMBINE, ADD, SUBTRACT, MULTIPLY
track2 = arm_obj.animation_data.nla_tracks.new()
track2.name = "Wave"
strip2 = track2.strips.new("Wave", start=25, action=bpy.data.actions["WaveHand"])
strip2.blend_type = 'COMBINE' # blend on top of walk
strip2.influence = 0.8 # partial blend
arm_obj.animation_data.action = None # clear active action so NLA takes over
8. Add drivers for expression-based animation
import bpy
driver = bpy.data.objects["Cube"].driver_add("location", 2).driver # drive Z location
driver.type = 'SCRIPTED'
driver.expression = "sin(frame * 0.1) * 2"
# Add a variable that reads another object's transform
var = driver.variables.new()
var.name = "ctrl"
var.type = 'TRANSFORMS'
var.targets[0].id = bpy.data.objects["Controller"]
var.targets[0].transform_type = 'LOC_X'
var.targets[0].transform_space = 'WORLD_SPACE'
driver.expression = "ctrl * 3" # also works on shape keys via sk.driver_add("value")
Examples
Example 1: Bouncing ball with squash and stretch
User request: "Animate a ball bouncing with squash and stretch"
import bpy
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.0, 1.0, 1.0), (12, 0.5, 1.0, 1.0, 1.0), # fall
(15, 0.3, 1.3, 1.3, 0.6), (18, 0.5, 0.85, 0.85, 1.2), # squash + stretch
(30, 2.2, 1.0, 1.0, 1.0), (42, 0.5, 1.0, 1.0, 1.0), # apex + second fall
(45, 0.3, 1.2, 1.2, 0.7), (55, 1.5, 1.0, 1.0, 1.0), # smaller bounce
(62, 0.5, 1.0, 1.0, 1.0), # settle
]
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)
for fc in ball.animation_data.action.fcurves:
for kp in fc.keyframe_points:
kp.interpolation = 'BEZIER'
bpy.context.scene.frame_end = 62
bpy.ops.wm.save_as_mainfile(filepath="/tmp/bouncing_ball.blend")
Example 2: Arm rig with IK reaching for an object
User request: "Create a simple arm rig with IK and animate it reaching for a cube"
import bpy
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, head, tail in [("UpperArm",(0,0,1.5),(0.6,0,1.5)),
("Forearm",(0.6,0,1.5),(1.2,0,1.5)),
("Hand",(1.2,0,1.5),(1.4,0,1.5))]:
b = arm_data.edit_bones.new(name)
b.head, b.tail = head, tail
if prev: b.parent, b.use_connect = prev, True
prev = b
bpy.ops.object.mode_set(mode='OBJECT')
# IK target + constraint
bpy.ops.object.empty_add(type='PLAIN_AXES', location=(1.2, 0, 1.5))
ik_target = bpy.context.active_object
ik_target.name = "IK_Hand"
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')
# Animate IK target reaching toward a cube
bpy.ops.mesh.primitive_cube_add(size=0.3, location=(1.5, 0.5, 1.0))
for loc, frame in [((1.2,0,1.5),1), ((1.5,0.5,1.0),30), ((1.5,0.5,1.3),50)]:
ik_target.location = loc
ik_target.keyframe_insert(data_path="location", frame=frame)
for fc in ik_target.animation_data.action.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="/tmp/arm_grab.blend")
Guidelines
data_pathmust match the RNA path exactly:"location","rotation_euler","scale",'["custom_prop"]'.- Armature workflow: Edit mode for bones (
edit_bones), Pose mode for constraints and keyframes (pose.bones). - For IK, use Empty objects as targets.
chain_count=0solves the entire chain to root. - Shape keys need a "Basis" key first. Animate via
key_blocks["Name"].value(0.0 to 1.0). - Interpolation:
CONSTANTfor hold/step,LINEARfor mechanical,BEZIERwith easing for organic motion. - Set
animation_data.action = Noneto let NLA strips drive — an active action overrides NLA. - For walk cycles, keyframe one stride and use a Cycles F-Curve modifier or NLA repeat to loop.
- Bake constraints/drivers with
bpy.ops.nla.bake()before export — other software cannot read them.
Information
- Version
- 1.0.0
- Author
- terminal-skills
- Category
- Design
- License
- Apache-2.0