Armature repair & the FBX writer
What avatar armature fix repairs, what it only flags, the headless-Blender script route that
applies the whole plan, and the mesh-side tooling (avatar fbx reslot / blendshapes)
built on the same native FBX writer. The repair is deliberately conservative: it applies the changes that
are safe as metadata edits and refuses the ones that would silently move geometry. Full text:
docs/reference/armature-repair.md.
Applied: canonical humanoid renames
avatar armature check model.fbx
avatar armature fix model.fbx # dry run: print the plan
avatar armature fix model.fbx -o fixed.fbx # write the repaired FBX
The only native repair that is applied is canonical humanoid renames —
e.g. mixamorig:LeftArm → LeftUpperArm. That is what Unity's humanoid
auto-mapper keys on, so renaming makes a raw export configure as Humanoid without touching geometry. It is
a dry run by default; pass -o to write the repaired FBX.
Flagged, not applied: topology, scale, orientation
| Finding | Native fix | Blender script |
|---|---|---|
| Non-canonical bone names (Mixamo, Biped, VRoid…) | applied | applied |
| Mis-wired parent topology | flagged | reparented |
| Wrong scale / units, wrong up axis | flagged | baked |
| Extra skeleton root, unskinned mesh, missing T-pose | flagged | reported |
Mis-wired parent topology and scale/orientation problems are reported but not applied:
they need a geometry transform (i.e. Blender), not a metadata relabel. Re-pointing a bone's
OO connection without recomposing its local transform would move its rest/bind pose. The
low-level reparent_object primitive exists but is intentionally not wired into
apply_plan.
The Blender-script route (--blender-script)
The flagged repairs aren't a dead end: avatar armature fix model.fbx --blender-script fix.py
emits a headless Blender Python script that applies the whole plan —
renames, geometry-aware reparents, and scale/orientation baking — in one pass, run with
blender --background --python fix.py. Blender can apply what the FBX writer only flags:
edit-bones store absolute rest transforms (so reparenting preserves the world rest pose by construction),
and the importer's meter/Z-up normalization is baked with transform_apply, exported with
Unity-friendly settings. The script is deterministic text, self-contained on the original FBX, and never
executed by the tool itself; --blender-output sets its export path.
The FBX writer
avatar-fbx's FbxDocument retains fbxcel's mutable tree and
serializes via Writer::write_tree / finalize. It edits objects by FBX
object id — skin/anim refs are by id, not name — so renames are safe. One
known characteristic: write_tree re-emits arrays uncompressed, so a written FBX is larger
than the original but re-loads identically here.
--uv-mask)
or a Blender re-export for mesh edits.
unity-acceptance.yml) closes the loop for the
armature path: it emits a broken Mixamo-named rig, repairs it with armature fix, imports the
result in a real Unity editor (GameCI Docker), and asserts it configures as a valid Humanoid with no
manual bone assignment. It self-skips until a Unity license secret is configured.
Beyond the armature: material-slot tooling
avatar fbx reslot is the one mesh edit that keeps coming up on converted avatars: a
region of polygons on the wrong material (an MMD-conversion hair strand glowing under the wrong emission
map). It selects triangles by combinable criteria — current slot, distance from a bone's bind
position, height band, skin-weight exclusions, brightness sampled from a texture ("the ones lit by this
emission map") — and rewrites their LayerElementMaterial entries to another slot.
Nothing else in the file is touched; preview with avatar render --material-texture.
--uv-mask MASK.png is the texture-side route when the FBX can't be rewritten
(see the open problem above): it rasterizes the selected triangles' UV footprint and reports every
unselected same-slot triangle overlapping it — exactly what a texture edit under the mask would also
hit. Paint the map under the mask instead of moving polygons.
avatar fbx blendshapes is the read-only companion for emote shading bugs: it
lists every blendshape channel with the material slots its target vertices render with, and
--uv-mask writes per-channel/slot PNGs of the touched UV footprint — a shape that slides
hidden overlay geometry into view (an MMD cheek-blush) renders with whatever material that slot carries,
and this names the material to fix.
avatar armature fix model.fbx --blender-script fix.py
avatar fbx reslot model.fbx --mesh Hair --to-slot 10 --brighter-than Hair_Emission.png:0.2 -o out.fbx
avatar fbx blendshapes model.fbx --filter Blush --uv-mask masks/