Skills blender-animation
install
source · Clone the upstream repo
git clone https://github.com/TerminalSkills/skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/TerminalSkills/skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/blender-animation" ~/.claude/skills/terminalskills-skills-blender-animation && rm -rf "$T"
manifest:
skills/blender-animation/SKILL.mdsource content
Blender Animation
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
must match the RNA path exactly:data_path
,"location"
,"rotation_euler"
,"scale"
.'["custom_prop"]'- Armature workflow: Edit mode for bones (
), Pose mode for constraints and keyframes (edit_bones
).pose.bones - For IK, use Empty objects as targets.
solves the entire chain to root.chain_count=0 - Shape keys need a "Basis" key first. Animate via
(0.0 to 1.0).key_blocks["Name"].value - Interpolation:
for hold/step,CONSTANT
for mechanical,LINEAR
with easing for organic motion.BEZIER - Set
to let NLA strips drive — an active action overrides NLA.animation_data.action = None - For walk cycles, keyframe one stride and use a Cycles F-Curve modifier or NLA repeat to loop.
- Bake constraints/drivers with
before export — other software cannot read them.bpy.ops.nla.bake()