Files
animation/.claude/skills/animation-creation/references/blender-mcp-recipes.md
T
jeremy 31ba2911df init: animation pipeline hub — Mac↔PC bridge for converting and implementing animations
Skills (.claude/skills/): animation-creation, iclone-video-mocap,
pose-estimation (MediaPipe models via LFS), retarget-animations (deprecated).
Tools: cc/mixamo/kevin/mocap retargeters + composite baker.
Plans: animation-gen-pipeline + skills-adoption.
exchange/: incoming-fbx, converted-glb, reference-video (LFS for fbx/glb/mp4).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 10:29:48 -07:00

6.6 KiB

blender-mcp recipes

Practical bpy snippets the agent pastes through blender-mcp's execute-code tool to author animations on the shared Quaternius skeleton and export them as game-ready GLB packs. These mirror the proven conventions in tools/mixamo_retarget.py — keep them in lockstep.

Setup assumptions

  • Blender 5.1.2 at /Applications/Blender.app (call headless as /Applications/Blender.app/Contents/MacOS/Blender).

  • The BlenderMCP addon is installed and connected (3D View → sidebar → BlenderMCP → Connect). If the blender MCP tool isn't available, the addon isn't connected.

  • One game GLB imported so the scene holds the real Quaternius armature. The game ships these as animations-only GLBs; import one to reuse its exact rig:

    bpy.ops.import_scene.gltf(filepath="/Users/jeremykashkett/Tinqs/local.repo/ariki-game/assets/quaternius/kevin/kevin_male_social.glb")
    arm = next(o for o in bpy.data.objects if o.type == "ARMATURE")
    arm.name = "Armature"   # match the convention mixamo_retarget.py enforces
    bpy.context.scene.render.fps = 30
    

Skeleton orientation

65 bones, UE-style names: pelvis, spine_01, spine_02, spine_03, neck_01, Head (capital H), clavicle_l, upperarm_l, lowerarm_l, hand_l, thigh_l, calf_l, foot_l, ball_l, and the _r twins, plus 3-segment fingers (index_0{l,r}pinky_03, thumb_0{l,r}thumb_03). The armature object is named Armature; work in pose mode via bpy.context.object.pose.bones["<name>"].

Keyframing recipe

Build an action from an explicit list of poses (name, frame, per-bone quaternions). Quaternius bones are authored in quaternion rotation mode, so set every pose bone's rotation_mode = "QUATERNION" first (this is what mixamo_retarget.py does):

import bpy
from mathutils import Quaternion

arm = bpy.data.objects["Armature"]
for pb in arm.pose.bones:
    pb.rotation_mode = "QUATERNION"

act = bpy.data.actions.new("Wave01")
arm.animation_data_create()
arm.animation_data.action = act
bpy.context.scene.frame_start, bpy.context.scene.frame_end = 1, 30

# poses: (frame, {bone: (w,x,y,z)})
poses = [
    (1,  {"hand_l": (1,0,0,0), "lowerarm_l": (1,0,0,0)}),
    (15, {"hand_l": (0.7,0,0,0.7), "lowerarm_l": (0.9,0.3,0,0)}),  # raise hand
    (30, {"hand_l": (1,0,0,0), "lowerarm_l": (1,0,0,0)}),
]
for frame, bones in poses:
    bpy.context.scene.frame_set(frame)
    for name, q in bones.items():
        pb = arm.pose.bones[name]
        pb.rotation_quaternion = Quaternion(q)
        pb.keyframe_insert(data_path="rotation_quaternion", frame=frame)

IK setup recipe

Drive calf_l (or lowerarm_l) toward a target empty with a 2-bone chain, then bake the solved motion to real keyframes and drop the constraint — the game never needs IK:

import bpy

arm = bpy.data.objects["Armature"]
target = bpy.data.objects.new("IK_target_l", None)          # empty
bpy.context.collection.objects.link(target)
target.location = arm.matrix_world @ arm.pose.bones["foot_l"].head + (0.0, 0.0, 0.2)

pb = arm.pose.bones["calf_l"]
ik = pb.constraints.new("IK")
ik.target = target
ik.chain_count = 2                                          # thigh_l + calf_l
ik.use_tail = True

# Bake the visual (constraint-solved) result to plain keyframes, then remove the constraint.
bpy.ops.object.select_all(action="DESELECT")
arm.select_set(True)
bpy.context.view_layer.objects.active = arm
bpy.ops.nla.bake(frame_start=1, frame_end=30, visual_keying=True,
                 bake_types={"POSE"}, clean_curves=True)
for c in list(pb.constraints):
    pb.constraints.remove(c)

NLA layering recipe

The glTF exporter emits each NLA track as a separate animation clip. To ship multiple clips in one GLB (or layer retargeted clips), push each action onto its own NLA track — this is the exact pattern tools/mixamo_retarget.py uses to export a pack:

import bpy
arm = bpy.data.objects["Armature"]
if arm.animation_data is None:
    arm.animation_data_create()
for act in bpy.data.actions:                      # every action you want to ship
    tr = arm.animation_data.nla_tracks.new()
    tr.name = act.name                            # clip name = track name
    tr.strips.new(act.name, 1, act)
arm.animation_data.action = None                  # avoid a stray duplicate clip

To blend two clips on the stack (e.g. a base loop under a gesture), set the upper strip's blend_type ("ADD", "REPLACE") and blend_in/blend_out frames, then bake the combined result down to a single action (visual keying) and re-export that.

Export recipe

The canonical export block lives in tools/mixamo_retarget.py and must be mirrored exactly — the game relies on these flags (NLA_TRACKS mode = one clip per track; export_force_sampling for clean per-bone channels; size optimization off to preserve retargeted curves). Select only the armature and drop meshes first so the GLB is animations-only (the game binds clips to its own body):

import bpy
out = "/Users/jeremykashkett/Tinqs/local.repo/ariki-game/assets/quaternius/mixamo/wave_pack.glb"

# Drop meshes — animations-only GLB (mirrors mixamo_retarget.py).
for o in [o for o in bpy.data.objects if o.type == "MESH"]:
    bpy.data.objects.remove(o, do_unlink=True)

bpy.ops.object.select_all(action="DESELECT")
arm = bpy.data.objects["Armature"]
arm.select_set(True)
bpy.context.view_layer.objects.active = arm

bpy.ops.export_scene.gltf(
    filepath=out, use_selection=True,
    export_animations=True, export_animation_mode="NLA_TRACKS",
    export_force_sampling=True, export_optimize_animation_size=False)
print("[blender-mcp] EXPORTED →", out)

Output GLBs in assets/quaternius/mixamo/ are auto-discovered by ClipCatalog / AnimationShowcase — referenceable immediately as <pack_name>/<ClipName> where <ClipName> is the NLA track (Blender action) name.

Gotchas

  • Never export *RM root-motion variants for dances/composites — they translate the rig and break compositing. Author clips in place.
  • Skip the RESET/rest action — don't push it to an NLA track or it ships as a clip.
  • Clip name = Blender action/NLA-track name. The game refers to clips as <pack>/<ClipName> (e.g. wave_pack/Wave01).
  • One armature per exported GLB. If you imported a GLB that brought its own body meshes, delete them before export (see the export block).
  • Match fps=30 so frame counts line up with shipped packs and the dance beat clock.
  • Quaternion mode for all pose bones before keyframing (matches the retargeter).
  • .uid files auto-generate in Godot — never hand-author them.