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:LeftArmLeftUpperArm. 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

FindingNative fixBlender script
Non-canonical bone names (Mixamo, Biped, VRoid…)appliedapplied
Mis-wired parent topologyflaggedreparented
Wrong scale / units, wrong up axisflaggedbaked
Extra skeleton root, unskinned mesh, missing T-poseflaggedreported

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.

Open problem: rewritten skinned avatars import invisible in Unity 2022.3
A full-avatar FBX (3 skinned meshes, 148 bones, blendshapes) rewritten by this writer re-loads fine in these tools — same objects, geometry, skin, blendshapes — but imported invisible in Unity 2022.3. Not yet diagnosed. Until it is, treat any written FBX destined for Unity as unverified: keep the original, and prefer the texture-side route (--uv-mask) or a Blender re-export for mesh edits.
The optional Unity acceptance CI job (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/