The proxy rig that made Mixamo optional

blender/cj/proxy_rig.py generates a humanoid stand-in: 22 core mixamorig: bones at human proportions, skinned capsule parts, five VRM-named face shape keys. No real mesh. No real face. The correct skeleton shape and naming convention, nothing else.

That file landed on P4 of the bootstrap sequence, two days into the EVA Music visualiser build. The purpose: prove everything below the mesh before committing real geometry. P0 had specified image-to-3D → Blender → Mixamo → Godot as the rig route, and the proxy was the test harness for it.

Twenty-four hours later, the proxy was still in the repo. Mixamo wasn’t in the chain at all.

What P0 assumed

Day one’s design session laid out the full stack: a 24/7 radio-style stream, Godot 4 as the real-time engine, a 2.5D toon avatar driven by the music, one persistent World3D scene. The avatar route was image-to-3D mesh from Meshy, rigged via Mixamo’s auto-rigger, retargeted in Blender, exported as a .glb, imported into Godot.

Mixamo is the obvious choice from a game-dev starting point. Upload a mesh, click “Auto-Rig”, download a .fbx with mixamorig: bones already placed. Free, predictable, natively supported in Godot’s retargeting. The mixamorig: naming scheme has been the indie default for years because Mixamo was the dominant free auto-rigger through most of the era. Godot’s Skeleton3D retargeting targets it by name. Assuming it wasn’t lazy. It was just what the problem looked like from the outside.

The constraint is the word “upload.” Mixamo is a web service with no API. Every mesh goes through a browser form: drag the file in, wait for analysis, adjust the character scale, download the result. For a one-off character, that’s an afternoon’s work. For a scripted pipeline where the mesh might change, or where you want to regenerate on demand without touching a browser, it’s a permanent human-in-the-loop requirement.

That constraint wasn’t visible on day one. It became visible on day two, when the pipeline design got concrete enough to write code against.

The proxy strategy

P4’s approach: decouple the mesh from the pipeline test. Build a proxy with the right skeleton and skin, verify every step downstream (Blender armature, shape key mapping, Godot import, animation retargeting), then drop the real mesh in at the end and touch Mixamo once.

proxy_rig.py outputs a .glb with capsule geometry bound to the 22 bones, face keys named to VRM convention, and mixamorig: joints exactly where a Mixamo export would put them. Deterministic: no manual step to regenerate it, no binary stored in the repo. Run the script, get the stand-in.

The proxy did what it was built to do. The Blender pipeline accepted it. Shape keys mapped. The .glb imported into Godot. The animation retargeter found the bones. P4’s entry: “everything below the mesh is built and proven.”

One gate remained: swap the proxy for a real mesh. The assumption was that this required Mixamo.

The Meshy API

Meshy’s image-to-3D feature was already in the P0 design as the mesh generation step. The assumed sequence: run Meshy in the browser to get a mesh, download it, feed it to Mixamo for rigging. Two manual steps in sequence, two browser windows open.

What wasn’t known at the time of the P0 session: Meshy has a full REST API. Image upload, job polling, mesh download, all scriptable.

Day three: created a Meshy API key, stored it in Keychain under meshy-api-key, verified the account with a balance call (3,105 credits). Wrote tools/meshy.py. Stdlib-only, no external dependencies. TDD from the start with seven tests covering the API surface. The module wraps image-to-3D job submission, polls until the task completes, and downloads the output mesh. One file, no browser.

The auto-rigging replacement needed one more look. Mixamo’s mixamorig: skeleton isn’t magic. It’s a naming convention Godot’s retargeter recognises. proxy_rig.py already generated that convention from scratch. The question was whether the Meshy mesh, with its arbitrary vertex topology, could be bound to the same skeleton programmatically.

Blender’s weight painting is scriptable. The proxy had been a simplification of exactly that binding problem: capsule geometry, 22 bone volumes, a workable approximation of how weight should distribute across a humanoid. The Meshy mesh has more vertices and more complex geometry but the same skeleton shape. Bind the skeleton, run automated weight painting, export as .glb. No Mixamo.

What the pipeline looks like now

image → meshy.py → mesh.glb → blender rig script → godot

Every step is scripted. meshy.py submits the job, polls, downloads. The Blender rig script binds the mixamorig: skeleton and runs weight painting. The .glb goes into Godot’s asset pipeline. The full chain runs unattended from a single Python entry point.

Mixamo sat between the Meshy download and the Blender export. Its function: apply a mixamorig: skeleton to arbitrary geometry and set up initial skin weights. The proxy work had already established that both steps were replaceable in script. Once Meshy had an API, the replacement ran on day three, before any real mesh had ever touched Mixamo at all.

The proxy still exists in the repo. Faster for testing animation curves and shape key mappings than running a full Meshy job. Out of scope: removing it. It earns its place as a test fixture.

The credit cost

Meshy charges per generation job. At 3,105 credits on day three, that’s a real constraint Mixamo’s free tier didn’t impose. For the 24/7 stream use case, the mesh doesn’t change often. The avatar is stable while the set, lighting, and track reactivity iterate around it. The marginal cost per run is low enough not to be the deciding factor.

If mesh iteration rate increases, proxy_rig.py exists to run the full downstream pipeline at zero cost while the mesh work happens separately. The two paths (scripted Meshy run and manual proxy swap-in) share the same Blender and Godot stages.

The trade-off: scriptability over zero marginal cost. For this use case, that’s correct.

What the proxy got right

The proxy strategy was correct independently of how it resolved. Separating “does the pipeline work?” from “is the real mesh in it?” meant day two’s P4 work remained valid when the Meshy API arrived on day three. Blender rig scripts, Godot import settings, shape key mappings, all verified before committing real geometry. Swapping the proxy for the Meshy output was a file path change.

Building proxy_rig.py also forced a precise specification of what “the right skeleton” meant. That made Mixamo’s actual contribution legible: naming bones and setting initial weights. Once that was legible, the API question followed immediately. The proxy didn’t just test the pipeline. It decomposed the problem precisely enough to expose an unnecessary dependency.

The broader point: when a pipeline step is manual and service-gated, the first question is whether the service has an API. That’s a search, not an assumption, and it takes seconds. Building the proxy required understanding exactly what Mixamo did at the structural level. That understanding produced the question. The answer was already there.

The step was assumed rather than checked. Two days of proxy work later, the assumption was examined enough to be dropped.

All writing