# 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: ```python 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[""]`. ## 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): ```python 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: ```python 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: ```python 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): ```python 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 `/` where `` 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 `/` (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.