roble3/cc-blender-skill

blender-animation

Animate objects, cameras, lights, and properties in Blender — keyframes, F-curves, easing (Bezier, Linear, Sine, Bounce, Elastic), shape keys (morph targets / blendshapes / visemes), drivers (Python expressions on properties), NLA actions for reuse and laye…

View source
Original skill document

Rendered from the source repository. Headings, examples, code, tables, links, and referenced images are preserved.

Blender Animation

Animate properties over time. Most animation is just keyframes — the trick is choosing the right interpolation and easing for the motion's character.

Decision tree

What kind of motion?
├── Object movement (translate / rotate / scale)
│   → Keyframe `location` / `rotation_euler` / `scale`
│   → Recipe 1, 2
│
├── Mechanical / constant speed (gears, conveyor belts, scrolling)
│   → Linear interpolation
│   → Recipe 3
│
├── Natural / organic (most things)
│   → Bezier interpolation with auto handles
│   → Recipe 1
│
├── Cartoon / stylized (overshoot, bounce, anticipation)
│   → Bounce / Elastic / Back easing
│   → Recipe 4
│
├── Facial / morph / blendshape
│   → Shape Keys, animate `value` property
│   → Recipe 5
│
├── Mechanical relations (one property = function of another)
│   → Drivers (Python expression)
│   → Recipe 6
│
└── Reusable / layered animations
    → NLA actions
    → Recipe 7

Recipes

Recipe 1 — Animate object position (Bezier, natural)

python
import bpy

obj = bpy.data.objects['GEO-target']

scene = bpy.context.scene
scene.frame_start = 1
scene.frame_end = 60
scene.render.fps = 24

# Keyframe 1: at frame 1, at origin
scene.frame_set(1)
obj.location = (0, 0, 0)
obj.keyframe_insert('location', frame=1)

# Keyframe 2: at frame 60, moved to (5, 0, 0)
scene.frame_set(60)
obj.location = (5, 0, 0)
obj.keyframe_insert('location', frame=60)

print(f"animated:{obj.name} 1->60")

Default interpolation = Bezier (smooth in/out). To make it linear, see Recipe 3.

Recipe 2 — Animate rotation (a 360° spin)

python
import bpy, math

obj = bpy.data.objects['GEO-target']
scene = bpy.context.scene

# Use rotation_euler with a single axis.
# WARNING: animating past 180° on Euler can flip; use multiple keyframes or quaternions for full rotations.

scene.frame_set(1)
obj.rotation_euler = (0, 0, 0)
obj.keyframe_insert('rotation_euler', frame=1)

scene.frame_set(120)
obj.rotation_euler = (0, 0, math.radians(180))   # half-turn
obj.keyframe_insert('rotation_euler', frame=120)

scene.frame_set(240)
obj.rotation_euler = (0, 0, math.radians(360))   # full turn
obj.keyframe_insert('rotation_euler', frame=240)

print(f"rotated:{obj.name} 360 over 240 frames")

For perfectly constant spin, set keyframes to Linear interpolation (Recipe 3).

Recipe 3 — Set keyframes to Linear interpolation

⚠ Blender 5.x changed the Action API. Legacy action.fcurves was removed in favour of layered Actions: action.layers[].strips[].channelbags[].fcurves. Use this compat helper.

python
import bpy

def get_fcurves_compat(action):
    """Return all fcurves on an Action — works on both legacy (≤4.x) and layered (5.x+) actions."""
    if hasattr(action, 'fcurves'):
        return list(action.fcurves)
    fcurves = []
    for layer in action.layers:
        for strip in layer.strips:
            if hasattr(strip, 'channelbags'):
                for cb in strip.channelbags:
                    fcurves.extend(cb.fcurves)
    return fcurves

obj = bpy.data.objects['GEO-target']
if obj.animation_data and obj.animation_data.action:
    for fc in get_fcurves_compat(obj.animation_data.action):
        for kp in fc.keyframe_points:
            kp.interpolation = 'LINEAR'
print(f"interp:linear {obj.name}")

Other options: 'BEZIER' (default), 'CONSTANT' (step), 'SINE', 'QUAD', 'CUBIC', 'QUART', 'QUINT', 'BOUNCE', 'ELASTIC', 'BACK'.

Recipe 4 — Bouncy / cartoon easing on a specific keyframe

python
import bpy

# (Re-use get_fcurves_compat from Recipe 3.)
def get_fcurves_compat(action):
    if hasattr(action, 'fcurves'):
        return list(action.fcurves)
    fcurves = []
    for layer in action.layers:
        for strip in layer.strips:
            if hasattr(strip, 'channelbags'):
                for cb in strip.channelbags:
                    fcurves.extend(cb.fcurves)
    return fcurves

obj = bpy.data.objects['GEO-target']
target_fc = None
for fc in get_fcurves_compat(obj.animation_data.action):
    if fc.data_path == 'location' and fc.array_index == 2:   # Z axis
        target_fc = fc
        break

if target_fc and len(target_fc.keyframe_points) >= 2:
    last_kp = target_fc.keyframe_points[-1]
    last_kp.interpolation = 'BOUNCE'
    last_kp.easing = 'EASE_OUT'    # 'AUTO', 'EASE_IN', 'EASE_OUT', 'EASE_IN_OUT'
print('animated:bouncy_landing')

Recipe 5 — Shape keys (morph / blendshape / viseme)

python
import bpy

mesh_obj = bpy.data.objects['GEO-character_face']

# Add basis (the rest pose)
if mesh_obj.data.shape_keys is None:
    basis = mesh_obj.shape_key_add(name='Basis')

# Add a morph target
smile = mesh_obj.shape_key_add(name='Smile')
smile.value = 0.0
# ⚠ At this point, switch to Edit Mode interactively and modify the mesh while 'Smile' is selected.
#   Or set vertex coordinates programmatically (advanced).

# Animate
scene = bpy.context.scene
scene.frame_set(1)
smile.value = 0.0
smile.keyframe_insert('value', frame=1)

scene.frame_set(24)
smile.value = 1.0
smile.keyframe_insert('value', frame=24)

print('animated:shape_key_smile')

For lip sync: standard 15-viseme set (Oculus / ARKit) — name shape keys viseme_aa, viseme_E, viseme_O, etc. Animate each viseme's value across the audio timeline.

Recipe 6 — Driver (one property as expression of another)

python
import bpy

# Example: child object's X = parent's X × 2
target = bpy.data.objects['GEO-follower']
source = bpy.data.objects['GEO-leader']

fc = target.driver_add('location', 0)   # X axis
driver = fc.driver
driver.type = 'SCRIPTED'

# Add variable referencing source's X position
var = driver.variables.new()
var.name = 'src_x'
var.type = 'TRANSFORMS'
var.targets[0].id = source
var.targets[0].transform_type = 'LOC_X'
var.targets[0].transform_space = 'WORLD_SPACE'

driver.expression = 'src_x * 2'
print(f"driver:{target.name}.x = {source.name}.x * 2")

Recipe 7 — Push current animation to NLA strip (for reuse)

python
import bpy

obj = bpy.data.objects['GEO-character']

if obj.animation_data and obj.animation_data.action:
    track = obj.animation_data.nla_tracks.new()
    track.name = 'NLA-Walk'
    strip = track.strips.new('Walk', start=1, action=obj.animation_data.action)
    obj.animation_data.action = None    # clear timeline; NLA owns the animation
    print(f"nla:pushed_walk_strip")

After this, the animation is reusable — duplicate the strip, scale time, blend with other tracks.

Recipe 8 — Subtle idle animation (loopable rotation)

python
import bpy, math

obj = bpy.data.objects['GEO-target']

# Tiny sway around Y, 4-second loop
scene = bpy.context.scene
scene.frame_start = 1
scene.frame_end = 96    # 4s at 24fps

scene.frame_set(1)
obj.rotation_euler = (0, math.radians(-2), 0)
obj.keyframe_insert('rotation_euler', frame=1)

scene.frame_set(48)
obj.rotation_euler = (0, math.radians(2), 0)
obj.keyframe_insert('rotation_euler', frame=48)

scene.frame_set(96)
obj.rotation_euler = (0, math.radians(-2), 0)
obj.keyframe_insert('rotation_euler', frame=96)

# Set extrapolation mode to cycle (loop)
def get_fcurves_compat(action):
    if hasattr(action, 'fcurves'):
        return list(action.fcurves)
    fcurves = []
    for layer in action.layers:
        for strip in layer.strips:
            if hasattr(strip, 'channelbags'):
                for cb in strip.channelbags:
                    fcurves.extend(cb.fcurves)
    return fcurves

if obj.animation_data and obj.animation_data.action:
    for fc in get_fcurves_compat(obj.animation_data.action):
        fc.modifiers.new('CYCLES')
print('animated:idle_loop')

Pro animation principles

  • Slow in, slow out — Bezier handles do this automatically; matches real-world physics
  • Anticipation — small reverse motion before the main action (jump prep)
  • Follow-through — secondary parts continue after main motion stops (cape, hair)
  • Squash & stretch — exaggeration with shape keys or scale animation
  • Arcs — natural motion follows curves, not straight lines

Common pitfalls

SymptomFix
Robotic motionDefault Bezier is correct; Linear is wrong for organic things
Rotation flips at 180°Use multiple keyframes (90, 180, 270, 360) or quaternions
Shape key changes lostMust exit Edit Mode to commit shape key state
Animation only on one axiskeyframe_insert('location', index=0) for X only; index=1 Y, =2 Z
Driver doesn't updateRefresh viewport; check Preferences → Editing → Allow Driver Python Expression
Render fps mismatchSet scene.render.fps BEFORE animating to avoid timing drift

When to load references/overview.md

Load when:

  • Animation curves need fine tuning (handle types, bezier shape control)
  • NLA layering / blending needed
  • Drivers with complex expressions (multi-variable, conditional)
  • ARKit 52-blendshape full face animation
  • Walk-cycle / run-cycle / attack patterns

The reference covers: full F-curve interpolation/easing matrix, NLA workflow, drivers cookbook, shape-key best practices, animation principles.

from this repository

More skills

All skills
roble3
Community

blender-modeling

Create and edit 3D meshes in Blender — primitives, hard-surface modeling, mesh operators, modifier stacks (Bevel, Subdivision, Boolean, Mirror, Array, Solidify), bmesh-level edits, retopology basics. Use whenever the user asks to "make/model/create/build a 3D object", "shape/sculpt this", "add a cube/sphere/cylinder/etc.", "extrude/inset/bevel this face", "add a modifier", or any geometry-creation request that isn't a wireframe trace. Make sure to use this skill even if the user does not say "mo

installs
3
GitHub stars
98
Updated
Oct 12
roble3
Community

reference-to-3d

Reconstruct Blender models from supplied reference sheets, branding templates, texture atlases, orthographic front/side/back/top views, or mascot/logo art where visual fidelity to the source is more important than a plausible generated object. Use when the user says the model must match a template, wireframe, texture pack, character sheet, mascot sheet, or brand asset exactly; also use after feedback like "does not look like the reference", "fit the texture 1:1", "wrong number of visible parts",

installs
2
GitHub stars
97
Updated
Oct 12
roble3
Community

animation-quality-gate

Validate Blender animation attempts before accepting them by rendering contact sheets, checking silhouette stability, flicker, framing, subject dominance, layer separation, export compatibility, and motion-design coherence. Use after animation renders or when the user says motion/morph/outside elements look bad.

installs
1
GitHub stars
88
Updated
May 1
roble3
Community

atlas-uv-fitting

Detect texture-atlas regions and map each source part to its own UV rectangle or projected UV surface. Use when a texture pack must fit a Blender model 1:1, when textures look off/stretched, when alpha decals or lightmaps are provided, or before exporting a textured GLB for a brand mascot/logo.

installs
1
GitHub stars
88
Updated
May 1