feat: clothing lane, character sources, and DCC bridges
Bulk import of the working lanes that were living untracked on the PC. Content: - characters/ Lena/male body lanes, bakes, texture work, run logs - clothing/ garment pipeline, configs, gates, contract docs - garments/ MD-authored garment sources (.zprj/.zpac) - UAL-Lib/ Universal Animation Library 2 source (.blend/.fbx/.glb) - tools/ blender_bridge, iclone_bridge, md_bridge, tailor, glm_agent - docs/, plans/, dev/, .agents/plans/ Repo hygiene: - .gitattributes: LFS now covers .blend, .zprj, .zpac, .obj, .npy and the Reallusion .iAvatar/.ccAvatar/.ccRestore containers. Without this the ~3.8 GB in this commit would land as raw blobs. .png/.jpg are left out on purpose — ~250 are already tracked raw and converting them would rewrite every one without shrinking history. - .gitignore: exclude /accurig/ (~1 GB AccuRig program files, redistributable from Reallusion, nothing authored here) and /dev/null/ (git-lfs hook copies dropped by a `>/dev/null` redirect on Windows). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
# FBX→GLB Converter Research — what to learn from external tools & skills
|
||||
|
||||
Date: 2026-07-30
|
||||
Scope: survey of established FBX→GLB converters, retargeting tools, and published Claude
|
||||
skills, cross-referenced against our pipeline (`tools/cc_retarget.py`, `mixamo_retarget.py`,
|
||||
`kevin_retarget.py` + `loop_qc.py`/`loop_fix.py`). Complements — does not replace —
|
||||
`plans/fbx-pipeline-plan-2026-07-21.md`, which this research largely validates.
|
||||
|
||||
---
|
||||
|
||||
## TL;DR — ranked recommendations
|
||||
|
||||
| # | Change | Fixes | Effort | Source of technique |
|
||||
|---|--------|-------|--------|---------------------|
|
||||
| 1 | Enforce quaternion neighborhood in the baker (negate q when dot(qᵢ,qᵢ₊₁)<0) | latent rotation-flip pops at runtime | ~10 lines | Khronos glTF #1395/#2073, Magnum |
|
||||
| 2 | Post-export `gltf-transform resample` pass (slerp-aware, tol 1e-4, lossless) | dense 1800-frame bakes, GLB size | 1 script hook (Node stack already in repo) | glTF-Transform / keyframe-resample-wasm |
|
||||
| 3 | `gltf-validator` JSON output as a machine gate after export | silent malformed output | small | Khronos glTF-Validator (no published skill does this — we'd be first) |
|
||||
| 4 | Unmapped-bone diagnostics + mapping-quality score before retarget | silently degraded clips | small | blender-toolkit skill (Excellent/Good/Fair/Poor gate) |
|
||||
| 5 | Explicit rest-pose alignment step (A-pose→T-pose correction) | the open shoulders-~45°-off QA item | medium | avatar-asset-pipeline pose configs, ARP "Redefine Rest Pose", Godot Rest Fixer |
|
||||
| 6 | Bake twist-bone rotation into parent instead of dropping it | candy-wrapper forearms/thighs | medium | ufbx helper-node insight, standard practice |
|
||||
| 7 | Unified retargeter + JSON rig maps (already planned as P2/FR-3) | 3 divergent forks | planned | validated by avatar-asset-pipeline's declarative component design |
|
||||
| 8 | Motion QC thresholds beyond the loop seam (ground penetration, scale drift, heading jumps) | "green-verify-is-not-correct" | medium | blender-motion-state-inspection skill |
|
||||
| 9 | Evaluate soupday cc_blender_tools importer for CC FBX | stock-importer risk we already documented | evaluation spike | soupday docs: "do not use the standard Blender FBX importer" |
|
||||
| 10 | If GLB size ever matters: meshopt (not Draco) + gltfpack-style quantization | — | later | gltfpack (12-bit rot / 16-bit trans / 30 Hz defaults) |
|
||||
|
||||
Items 1–4 are cheap, independent, and zero-risk to output quality. Item 5 closes our
|
||||
single biggest open correctness question.
|
||||
|
||||
---
|
||||
|
||||
## 1. What the established converters teach
|
||||
|
||||
### FBX2glTF (Facebook, archived; Godot fork)
|
||||
- Pattern worth keeping (we already follow it): **correctness by evaluation, not
|
||||
translation** — never convert FBX curves directly; sample the evaluated transform per
|
||||
frame. FBX2glTF bakes everything at a fixed rate exactly like our depsgraph-evaluate loop.
|
||||
- Its failure modes (GeometricTransformation pivots mishandled — [fork #56](https://github.com/godotengine/FBX2glTF/issues/56);
|
||||
skeleton defects that made Godot abandon it) confirm Blender-headless was the right lane.
|
||||
- Blend-shape lesson: morph normals/tangents in FBX are "rarely correctly present" — if we
|
||||
ever carry facial morphs, don't export morph normals unless verified.
|
||||
|
||||
### glTF-Blender-IO (what our export actually runs through)
|
||||
- Armature export is always sampled; rest pose is the baseline for the relative TRS it
|
||||
writes. **If our armature rest ≠ the game skeleton rest, the export silently differs** —
|
||||
reinforces the plan's QC-1 `verify_target.py` (pinned rig vs live game rig, tol 1e-5).
|
||||
- Known issues we should pin settings against: sampling keys all bones (#1432/#2657),
|
||||
stepped keys become linear (#842). Action: set `export_force_sampling`,
|
||||
`export_frame_step`, `export_optimize_animation_size` and the `keep_anim_*` flags
|
||||
explicitly per Blender version instead of trusting defaults (we already set the first
|
||||
and last — add the rest).
|
||||
|
||||
### ufbx — the best-documented catalog of FBX semantics
|
||||
- **Rest pose ≠ bind pose is legal FBX.** Correct converters trust skin-cluster matrices
|
||||
(`geometry_to_bone`), never the node hierarchy at time 0. (assimp gets this wrong —
|
||||
[assimp #4015](https://github.com/assimp/assimp/issues/4015) — which is why it's
|
||||
disqualified for rigged characters.) Relevant to our A-pose question: iClone's
|
||||
"T-pose export" only prepends a T-pose *frame*; the skin bind stays A-pose.
|
||||
- Scale-inheritance and geometric-transform mismatches with glTF are solved by inserting
|
||||
**helper nodes** — the explanation to reach for if iClone props/twist bones ever explode.
|
||||
- Baking niceties: don't re-resample tracks that are already dense
|
||||
(`minimum_sample_rate` guard); reduce keys *after* baking, not by blind resampling.
|
||||
|
||||
### glTF-Transform (directly usable — Node + gltf-transform already in repo via `bake_run_punch.mjs`)
|
||||
- **`resample()` is lossless keyframe dedup**: drop interior keys exactly reproduced by
|
||||
interpolating neighbors, using the track's real mode — **slerp-aware for rotations**,
|
||||
default tolerance 1e-4 (algorithm: [keyframe-resample-wasm](https://github.com/donmccurdy/keyframe-resample-wasm)).
|
||||
Perfect complement to our bake-every-frame approach; free size win, zero quality risk.
|
||||
- Compression fact: **Draco compresses mesh primitives only. meshopt
|
||||
(EXT_meshopt_compression) also compresses animation samplers** — meshopt is the only
|
||||
correct choice for our animation-only GLBs, if size ever matters (needs gzip/brotli
|
||||
outer layer to pay off; Godot must support the extension — verify before adopting).
|
||||
- Community warning (blender-kiln skill): never run `gltf-transform optimize` as one
|
||||
blob — its simplify pass can destroy assets; apply individual steps (`resample`,
|
||||
`prune`, `dedup`) sequentially.
|
||||
|
||||
### Keyframe reduction done right — ozz-animation
|
||||
- ozz's `AnimationOptimizer` measures error as **world-space distance at the end of the
|
||||
joint's child hierarchy** (default tolerance 1 mm, probe distance 0.1 m, per-joint
|
||||
overrides). Far better than per-channel epsilons: tighten fingers/feet, loosen spine.
|
||||
The metric to copy if we ever add lossy reduction on top of `resample()`.
|
||||
- gltfpack's shipping defaults — 30 Hz resample, 12-bit rotation / 16-bit translation
|
||||
quantization — calibrate "what players don't notice."
|
||||
|
||||
### Quaternion continuity — a latent bug we likely have
|
||||
- glTF runtimes disagree on >180° rotation steps (lerp+normalize vs slerp, shortest-path
|
||||
ambiguity — Khronos [#1395](https://github.com/KhronosGroup/glTF/issues/1395),
|
||||
[#2073](https://github.com/KhronosGroup/glTF/issues/2073)). Blender's matrix
|
||||
decomposition does **not** guarantee quaternion sign continuity frame-to-frame.
|
||||
- Fix at bake time in `retarget_one()`: keep the previous frame's quaternion per bone;
|
||||
if `dot(prev, cur) < 0`, negate `cur` before `keyframe_insert`. ~10 lines, applies to
|
||||
all three retargeters (or the future unified one).
|
||||
- Also: never use CUBICSPLINE samplers for rotations (overshoot — Khronos #2008). We
|
||||
export sampled/linear, so we're fine there.
|
||||
|
||||
---
|
||||
|
||||
## 2. Retargeting techniques
|
||||
|
||||
### Godot's fixed-skeleton retargeting (closest published design to our Quaternius lane)
|
||||
- `SkeletonProfileHumanoid` + BoneMap + **Rest Fixer** with:
|
||||
- *Overwrite Axis* — rewrite all bone rests to canonical profile rests (their version of
|
||||
our "rig onto the clip rest" move).
|
||||
- *Fix Silhouette* — A-pose sources corrected toward the T-pose profile (documented
|
||||
limitation: can't fully fix bone roll).
|
||||
- *Remove Unimportant Positions* — strip position tracks from everything except
|
||||
root/hips. **We already comply** (rotation-only except pelvis/root) — validated.
|
||||
- *Normalize Position Tracks* — scale hip translation by source/target hips-height
|
||||
ratio (`motion_scale`). **Our `tscale` is the same idea** — validated, keep it.
|
||||
- Docs: https://docs.godotengine.org/en/stable/tutorials/assets_pipeline/retargeting_3d_skeletons.html
|
||||
|
||||
### Rest-pose alignment (our open weakness #1)
|
||||
Every serious retargeter has an explicit rest-alignment step; we have none:
|
||||
- **Auto-Rig Pro Remap**: map → *Redefine Rest Pose* (snapshot a corrected source pose as
|
||||
the retarget basis) → retarget → per-bone offsets → bake. The proven order of operations.
|
||||
- **avatar-asset-pipeline** ([infosia](https://github.com/infosia/avatar-asset-pipeline)):
|
||||
declarative JSON pipelines with an A-pose↔T-pose component driven by a **pose config
|
||||
file** (quaternion per bone). Closest architectural analog to our planned
|
||||
`pipeline/retarget/rigs/*.json` — extend the rig-map JSON schema with an optional
|
||||
`rest_correction` block of per-bone quaternions applied to the source rest before
|
||||
computing `C`, and the A-pose problem becomes data, not code.
|
||||
- **soupday cc_blender_tools** maps skin bones **by iClone ID rather than name** and
|
||||
explicitly warns against Blender's stock FBX importer ("Automatic Bone Orientation"
|
||||
alters joint rotations and breaks round-tripping). It retargets armature *and*
|
||||
shape-key animation. Worth a spike: import one known-problem CC FBX both ways and diff
|
||||
the baked curves.
|
||||
|
||||
### Twist bones (our weakness #2)
|
||||
We currently drop `*Twist01/02` rotation entirely → candy-wrapper forearms/thighs.
|
||||
Standard fix: compose the twist bone's captured rotation into its mapped parent
|
||||
(upperarm/lowerarm/thigh) before writing the target key — the twist joints exist to
|
||||
*distribute* roll, so discarding them loses real roll. (ufbx's helper-node machinery is
|
||||
the deep version; composing into the parent is the cheap correct-enough one for a rig
|
||||
with no twist joints on the target.)
|
||||
|
||||
### Further afield (noted, not recommended now)
|
||||
- Unity Mecanim: retarget in normalized "muscle space," then IK-correct hands/feet — the
|
||||
principled fix for foot slide, heavyweight.
|
||||
- ossos (Ubisoft IK-Rig style): motion as normalized IK targets re-solved on the target —
|
||||
the modern answer when proportions differ wildly. Prototype-quality.
|
||||
- Root Motionist add-on: hip→root channel migration pattern, if we ever need root-motion
|
||||
variants (currently banned in-game).
|
||||
|
||||
---
|
||||
|
||||
## 3. Published Claude skills — what exists, what to borrow
|
||||
|
||||
Nothing published covers our exact lane (FBX skeletal retarget → game GLB with numeric
|
||||
animation QC); our `loop-qc` agent is already ahead of public art. Reusable pieces:
|
||||
|
||||
- **blender-motion-state-inspection** ([SKILL.md](https://github.com/ksmithRenweb/everything-claude-code/blob/main/skills/blender-motion-state-inspection/SKILL.md)) —
|
||||
"measure, don't eyeball" QC playbook with thresholds worth adopting into a broader
|
||||
motion-QC gate alongside loop-qc: ground penetration >1–2 cm visible; scale drift >5% =
|
||||
rig problem; root heading jump >30°/frame suspicious. Sample frames *likely to expose
|
||||
errors* (contact, airborne, extremes), check source integrity before blaming the
|
||||
retarget, report facts (frame numbers, coordinates) before verdicts.
|
||||
- **blender-toolkit** ([kevinbadi/blender-skills](https://github.com/kevinbadi/blender-skills)) —
|
||||
only published skill with a real retargeting workflow. Borrow: named bone-map presets as
|
||||
skill assets, and a **mapping-quality score** (Excellent/Good/Fair/Poor by critical-bone
|
||||
coverage) that gates whether the pipeline may proceed autonomously — direct fit for the
|
||||
plan's FR-2 (reject with unmapped-bone list).
|
||||
- **blender-kiln** ([elithril](https://github.com/elithril/blender-kiln)) — best-structured
|
||||
3D skill: thin SKILL.md dispatcher + deep `references/` files, phase-gated pipeline with
|
||||
mandatory state-read before each phase, scale sanity via reference dimensions
|
||||
(character ≈1.75 m — same trick as our 1.700 m AccuRig rule), batch manifest pattern.
|
||||
- **VibeCAD render-glb** ([rawwerks](https://github.com/rawwerks/VibeCAD)) — GLB→PNG so the
|
||||
agent can *see* its output. Rendering 3–4 keyframes of a retargeted clip to PNG would
|
||||
partially automate the mandatory human-eyeball QC-3 step (catches upside-down/T-posed/
|
||||
exploded output; still not a substitute for motion judgment).
|
||||
- **anthropics/skills docx pattern** — the canonical file-conversion skill structure our
|
||||
`animation` skill could adopt more fully: decision table routing by source type
|
||||
(Mixamo / CC / Kevin → script + bone map), explicit Gotchas section (AccuRig 4 cm
|
||||
offset, Dummy export, External Motion skating), pinned script invocations, and an
|
||||
explicit SHIP / NO-SHIP verdict at the end.
|
||||
- **gltf-validator gap**: no published skill or agent workflow wires
|
||||
[Khronos glTF-Validator](https://github.com/KhronosGroup/glTF-Validator) in as a
|
||||
machine-checkable gate — everyone stops at `gltf-transform inspect` + eyeballing.
|
||||
Adding `gltf-validator -o json` after export is cheap and genuinely novel.
|
||||
|
||||
---
|
||||
|
||||
## 4. What this validates in the existing plan
|
||||
|
||||
`plans/fbx-pipeline-plan-2026-07-21.md` is independently confirmed by this research:
|
||||
- Unified retargeter + JSON rig maps (P2/FR-3) = avatar-asset-pipeline's architecture.
|
||||
- Reject-with-unmapped-list (FR-2) = blender-toolkit's mapping-quality gate.
|
||||
- `verify_target.py` pinned-rig check (QC-1) = the glTF-Blender-IO rest-pose-baseline risk.
|
||||
- Intake T-pose WARN (IC-3) = every retargeter's rest-alignment prerequisite.
|
||||
- Fail-loud + atomic writes + provenance (NFR-1/2) = table stakes in every surveyed tool.
|
||||
|
||||
Two additions this research argues for beyond the plan:
|
||||
1. A **`rest_correction` per-bone quaternion block** in the rig-map JSON schema (turns the
|
||||
A-pose fix into data).
|
||||
2. A **post-export normalize step** in FR-1's single command: quaternion-neighborhood fix
|
||||
(in-baker) → `gltf-transform resample` → `gltf-validator` gate → optional render-to-PNG
|
||||
contact sheet.
|
||||
Reference in New Issue
Block a user