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,270 @@
|
||||
# Clothing Pipeline — garment mesh → game-ready outfit part
|
||||
|
||||
**What this is:** a headless-Blender pipeline that takes a clothing mesh from any
|
||||
source (Marvelous Designer export, marketplace FBX/OBJ/GLB, hand-modeled) and turns
|
||||
it into outfit-part GLBs that ariki-game characters actually wear — fitted to the
|
||||
real body, reduced to game budget, skinned to the shared 65-bone Quaternius
|
||||
skeleton, and registered in the game's `OutfitCatalog`.
|
||||
|
||||
Built 2026-07-30 after the CC5/Reallusion clothing lane was investigated and
|
||||
rejected (CC5 provably refuses clothing tools on AccuRig "Humanoid" characters —
|
||||
verified via its Python API *and* GUI; see `ariki-game` session notes). Everything
|
||||
here is Blender-only, deterministic, and agent-drivable end to end.
|
||||
|
||||
## Why each stage exists
|
||||
|
||||
A downloaded garment is useless to the game for four reasons; the pipeline is the
|
||||
four fixes in order:
|
||||
|
||||
| Problem | Fix | Stage |
|
||||
|---|---|---|
|
||||
| Not shaped like our character | Shrinkwrap-fit to the real body GLB (inflated "inner shell" target guarantees clearance) | `fit` |
|
||||
| Millions of tris vs ~1.5k/part budget | Split by material into parts, rebuild/decimate, bake lost detail to normal+AO maps | `reduce`, `bake` |
|
||||
| No idea how to move | Copy skin weights from the character's own body (special cone-proxy recipe for skirts) | `skin` |
|
||||
| Game has never heard of it | Export per-slot GLBs on the shared skeleton + register in `OutfitCatalog` | `export` + manual catalog entry |
|
||||
|
||||
## Design decisions (and where they came from)
|
||||
|
||||
Reviewed by GLM-5.2 and glm-4.6 before implementation
|
||||
(`../garments/glm-advice-glm52.md`, `../garments/glm-advice.md`) plus web research
|
||||
on Marvelous Designer game workflows. Key adopted corrections:
|
||||
|
||||
1. **Fit BEFORE decimate, bake AFTER** — shrinkwrapping the high-poly garment
|
||||
molds real pleat geometry; fitting a faceted low-poly loses the silhouette.
|
||||
2. **Pleated skirts are not decimated** — a clean low-poly cylinder proxy is
|
||||
generated and shrinkwrapped to the high-poly skirt; pleats live in the baked
|
||||
normal map (at 1.5k tris geometric pleats read as noise).
|
||||
3. **Skirt weights come from a "cone proxy"** — a leg-bridging tapered cylinder
|
||||
weighted with a Z-gradient (body weights at waist → pelvis-only at hem), then
|
||||
transferred to the skirt. Kills the hard left/right-leg seam that tears skirts
|
||||
in walk cycles. Forward-compatible with skirt bones later.
|
||||
4. **Clearance via inner shell** — garments wrap to a ~4mm inflated copy of the
|
||||
body, not the body itself: mathematical clearance, not hope.
|
||||
5. **Layer stacking** — bow wraps to bodice, bodice wraps to body shell
|
||||
(cumulative offsets), so layered cloth keeps separation.
|
||||
6. **Weight transfer uses Data Transfer 'nearest face interpolated'**, not raw
|
||||
nearest-vertex KDTree (fails on overhangs like collars/bow tails).
|
||||
7. Per-part tri budgets vary (bow ~400, bodice ~1200, skirt ~2200);
|
||||
verify in the game test bed EARLY (after skin), not at the end.
|
||||
|
||||
Sources also agree: if a garment came from Marvelous Designer, re-exporting from
|
||||
MD with lower particle density / MD's own quad remesh skips the worst reduction
|
||||
pain at the source. Prefer that when the .zprj is available.
|
||||
|
||||
## Files
|
||||
|
||||
| File | What |
|
||||
|---|---|
|
||||
| `garment_pipeline.py` | The whole pipeline. One Blender headless script, staged; each stage checkpoints a .blend into `work/` so stages can be re-run/iterated independently. |
|
||||
| `configs/<garment>.json` | Per-garment recipe: source file, island→part mapping, slots, budgets, alignment, fit, cuts. The pipeline is data-driven; new garment = new config, not new code. |
|
||||
| `work/` | Intermediates (checkpoint .blends, census data, QA renders). Disposable, gitignored. |
|
||||
| `README.md` | This file. |
|
||||
|
||||
## Stages as-built (2026-07-30, after the dress pilot)
|
||||
|
||||
| Stage | What it does | Hard lessons baked in |
|
||||
|---|---|---|
|
||||
| `census` | Import garment, drop configured junk materials, weld, split into loose **islands**, render each in a distinct color + write `work/<name>/census.json` | MD material names lie (all fabric on one material; 4M faces were topstitch threads → `drop_materials`). Parts are configured as island index lists, not material names. |
|
||||
| `prepare` | Import body+rig, build configured parts from islands, **bodyshell** parts cut from the body itself, sleeve pose-warp (garment-local, pre-align), anisotropic align (`scale_xy`/`scale_z` — heroic bodies are wide, not tall), radial `torso_boost`, post-align `cut_above_z`/`cut_outboard_x`, `prune_scraps_x` (drop disconnected leftovers by centroid), fabric `texture` on MD's own UVs | Fitted tops on a bust bigger than the garment shred under any shrinkwrap — use a **bodyshell** part (body region copy + normal offset, Islander technique): perfect fit and weights by construction. Cuts must run post-align in body space. MD's exported UVs already sample the fabric PNG correctly — leave `texture_offset` at zero, and judge any change in the game, since Workbench QA renders ignore shader Mapping nodes. |
|
||||
| `fit` | Inflated `BODY_SHELL` (+4mm) target; shrinkwrap mode **OUTSIDE** (push out penetrating verts ONLY — never snap the whole cloth, that shreds layered garments); optional `hem_mm` adds a second OUTSIDE pass masked 0 at hip → 1 at hem for graded leg clearance on skirts | `OUTSIDE_SURFACE` destroyed multi-layer bodices; `OUTSIDE` unmasked is safe everywhere since it can't pull cloth inward. A uniform 4mm fit clips in walk cycles — hems need 15–20mm where legs travel (`hem_mm`), while the waist stays snug. |
|
||||
| `reduce` | Keep `HI_<part>` copy for baking; `weld_band` fuses stacked layers (waistband/belt sandwiches) before decimation; planar dissolve → collapse to per-part budget; optional cylinder proxy for standalone skirts | Decimating coincident stacked layers = swiss cheese; protecting zones from the decimator just moves the damage elsewhere — **weld the sandwich first**. |
|
||||
| `bake` | (optional, Cycles) normals+AO from HI onto a second UV map | |
|
||||
| `skin` | Data Transfer (nearest-face-interpolated) from body; `weights: dress` adds pelvis-gradient below hip so the hem doesn't tear between legs; bodyshell parts keep inherited weights; smooth+normalize; bind to RIG | |
|
||||
| `export` | One GLB per outfit slot (multi-part slots joined by selection), full 65-bone armature included, vertex hash printed for determinism | |
|
||||
|
||||
Related repo tooling this builds on (in `ariki-game/tools/`):
|
||||
`cc_clothing_to_quaternius.py` (earlier one-shot converter — superseded by this
|
||||
for production but its skeleton-frame alignment math is the reference),
|
||||
`make_islander_outfits.py` (KDTree weight-copy precedent + tri budgets),
|
||||
`make_fitted_body.py` / `race-body-fitting` skill (measure-then-warp doctrine).
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
BLENDER="/c/Program Files/Blender Foundation/Blender 5.1/blender.exe"
|
||||
cd /c/Users/Jeremy/tinqs/animation/clothing
|
||||
|
||||
# run stages in order (each saves work/<NN>_<stage>.blend + QA renders)
|
||||
"$BLENDER" --background --python garment_pipeline.py -- --config configs/dress.json --stage prepare
|
||||
"$BLENDER" --background --python garment_pipeline.py -- --config configs/dress.json --stage fit
|
||||
"$BLENDER" --background --python garment_pipeline.py -- --config configs/dress.json --stage reduce
|
||||
"$BLENDER" --background --python garment_pipeline.py -- --config configs/dress.json --stage bake # optional, slow (Cycles)
|
||||
"$BLENDER" --background --python garment_pipeline.py -- --config configs/dress.json --stage skin
|
||||
"$BLENDER" --background --python garment_pipeline.py -- --config configs/dress.json --stage export
|
||||
```
|
||||
|
||||
Outputs land in the config's `export.out_dir` (default
|
||||
`ariki-game/assets/quaternius/outfits/<set>/`), one GLB per outfit slot, ready for
|
||||
an `OutfitCatalog.Register` entry (see `ariki-game/src/Character/OutfitCatalog.cs`
|
||||
`Initialize()` — path pattern `{Gender}_{Set}_{Slot}`).
|
||||
|
||||
## Shipped garments
|
||||
|
||||
| Config | Source | In game as | Notes |
|
||||
|---|---|---|---|
|
||||
| `pari.json` | `../tools/tailor/lena_pari_v3_garment.fbx` (MD, ours) | `Kapahaka` / Body | Tāniko bodice. First **MD-authored** garment through the pipeline (2026-07-31) — the upstream/downstream seam. |
|
||||
| `piupiu.json` | `../tools/tailor/lena_piupiu_v2_garment.fbx` (MD, ours) | `Kapahaka` / Legs | Flax skirt, `hem_mm: 14`. |
|
||||
| `dress.json` | `../garments/thin-unweld.fbx` (downloaded) | `MDDress` / Body | The pilot: 2.49M verts, 12 materials, bodice + belt/bow + pleated skirt. |
|
||||
|
||||
MD-authored garments are the easy case and confirm the `clothing-lane.md`
|
||||
prediction: both kapa haka pieces arrived at game budget already (their 4k/8k tri
|
||||
ceilings never bit) and needed no alignment beyond a `z_nudge`, because they were
|
||||
draped on `Ariki_Female_QuatSkin.glb` itself. `reduce` is only the hard stage for
|
||||
downloaded meshes.
|
||||
|
||||
Deferred on the dress: the sleeved jacket (`cape*` materials) — sleeves need a
|
||||
pose-warp to T-pose (GLM advice §4 has the recipe). Unknown `Material*` slots are
|
||||
excluded pending the per-material stats `prepare` prints; reassign them in the
|
||||
config if they turn out to be buttons/trim.
|
||||
|
||||
## QA renders only prove the pose that cannot fail
|
||||
|
||||
Every `qa_*` render this pipeline writes is the **rest pose** — the pose the
|
||||
garment was fitted in. A garment that clears the body perfectly there can still
|
||||
open at the neckline or let a limb through the moment the skeleton moves, because
|
||||
the weights (not the fit) decide that. Both kapa haka pieces did exactly this: the
|
||||
Blender QA showed a closed scoop neck and a hem covering both thighs; in the game
|
||||
bed, the neckline opens over the sternum and a thigh comes through the skirt.
|
||||
|
||||
So `skin` is not the last checkpoint — **the game test bed is**, and it has to be
|
||||
driven through more than one clip (`Idle` / `Walk` / `Dance` buttons in
|
||||
`ClothingTestBed`; the AnimationTree used to override them, fixed 2026-07-31).
|
||||
Treat a rest-pose-only sign-off as unverified.
|
||||
|
||||
## Unified orchestrator + QC gates (2026-07-31)
|
||||
|
||||
The stage-at-a-time invocations above still work and still own the logic. What
|
||||
changed is that you no longer have to type them, and that each seam now has a
|
||||
check that fails **before** the defect ships.
|
||||
|
||||
```bash
|
||||
cd /c/Users/Jeremy/tinqs/animation
|
||||
python clothing/garment.py configs/piupiu.json --from census --to export
|
||||
```
|
||||
|
||||
`garment.py` is a driver, not a rewrite: every stage shells out to the tool that
|
||||
already owns it (`garment_pipeline.py` for Blender, `md_bridge.py` for MD, agent
|
||||
E's scripts for the game). It resolves the config's `extends` chain, absolutizes
|
||||
its paths and writes `work/<name>/resolved.json` — every stage and gate is then
|
||||
handed that one file, never the raw config.
|
||||
|
||||
| | Stage | Runs where |
|
||||
|---|---|---|
|
||||
| upstream | `draft` `drape` `publish` | Marvelous Designer, over the MD bridge |
|
||||
| core | `census` `prepare` `fit` `reduce` `bake` `skin` `export` | Blender headless |
|
||||
| downstream | `register` `import` `verify` | ariki-game |
|
||||
|
||||
Default range is `census..export` — the Blender half. MD and game stages run only
|
||||
when you ask for them by name (`--from`/`--to`/`--only`). Other flags worth
|
||||
knowing: `--list` (what would run, and which gates are armed), `--dry-run` (print
|
||||
the exact commands), `--no-gate-stop` / `--gate-stop` (override the QC mode's
|
||||
stop-on-failure behaviour), `--selftest`.
|
||||
|
||||
### QC modes — `--qc light` (default) and `--qc deep`
|
||||
|
||||
Two ways to use the pipeline, two costs.
|
||||
|
||||
```bash
|
||||
python clothing/garment.py configs/x.json --from census --to export # light
|
||||
python clothing/garment.py configs/x.json --qc deep --from census --to export
|
||||
```
|
||||
|
||||
**light** is the iterate-on-a-garment loop: one clip (`Walk`), two frames, one
|
||||
anim pack, two QA renders, and a failing gate **reports instead of stopping** so
|
||||
one run tells you everything that is wrong with a work-in-progress garment.
|
||||
**deep** is the sign-off sweep: `Idle Walk Dance`, eight frames each, both packs,
|
||||
the synthetic extremes appended on top, twelve renders, and a failing gate stops
|
||||
the run. Measured end to end on `configs/tests/piupiu_sb_test.json`,
|
||||
`--from census --to export` (Blender 5.1, this box):
|
||||
|
||||
| | census | g2 | prepare | fit | **G3** | reduce | skin | **G5** | export | **total** |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| light | 3.3s | 0.1s | 4.2s | 3.3s | 2.9s | 3.2s | 3.6s | **9.9s** | 2.8s | **33.3s** |
|
||||
| deep | 3.1s | 1.1s | 4.0s | 3.1s | 2.9s | 3.1s | 3.4s | **19.1s** | 2.8s | **42.6s** |
|
||||
|
||||
All of the difference is G5: 13 s of gate time in light (1 rest + 2 Walk frames,
|
||||
UAL2 only) against 23 s in deep (1 rest + 24 clip frames + 4 synthetic extremes,
|
||||
both packs). ~7 s of light's G5 is importing the one anim pack — loading UAL1 as
|
||||
well costs another ~7 s and buys nothing when only `Walk` is sampled, and `Walk`
|
||||
is aliased onto **UAL2's** `Walk_Fwd_Loop`. Light still catches the shipped
|
||||
thigh-poke on both of its Walk frames (168 and 90 verts inside the body, ~80 mm
|
||||
deep) — the bug fires on every frame, which is exactly why two are enough to
|
||||
iterate against. Deep is what tells you the whole shape of it: worst frame 231
|
||||
verts (Idle), the Dance clip light never samples, and a synthetic `step_thigh_60`
|
||||
extreme at 187 verts / 95 mm.
|
||||
|
||||
A mode only ever changes **how hard the pipeline looks** — the sampling and cost
|
||||
knobs. It can never touch a threshold (`depth_mm`, `max_verts`, gape budgets):
|
||||
those are the config's, so a garment that passes light and fails deep failed on
|
||||
frames light did not sample, never on a moved goalpost. Per-garment overrides go
|
||||
in the config's `qc` block; full precedence and the key whitelist are in
|
||||
[`PIPELINE-CONTRACT.md`](PIPELINE-CONTRACT.md#qc-modes-as-built).
|
||||
|
||||
Omitting `--qc` gives you light plus a one-line reminder at the end of the run
|
||||
(`QC mode: light -- run --qc deep before sign-off`). Run deep before you bless a
|
||||
baseline or register a set.
|
||||
|
||||
### The gates
|
||||
|
||||
A gate runs after its stage, and only when the config carries the `expect` block
|
||||
that gives it thresholds — no thresholds, no opinion. All of them write
|
||||
`work/<name>/qc/<gate>.json` and speak one exit code: **0 pass · 2 fail · 3 could
|
||||
not evaluate**.
|
||||
|
||||
| Gate | After | What it catches |
|
||||
|---|---|---|
|
||||
| G1 | `drape` | Band placement in the MD snapshot — hem/top edge off its target in metres. Found the shipped pari hem sitting **+3.9 cm** and the piupiu bands **+8/+14 cm** above their own written targets. Needs `expect.bands` (+ a `drape.mask` palette; the default heuristic counts shaded skin as fabric). |
|
||||
| G2 | `census` | An MD re-export renumbering islands, so `parts[*].islands` now points at the wrong geometry and every later stage builds the wrong garment. Needs `expect.islands` — in **census units** (pre-`align.scale_*`), read straight off `census.json`. |
|
||||
| G3 | `fit` | Rest-pose clearance. Same script as G5 (`g5_posed_sweep.py --rest`) — there is no `g3_*.py` — and it writes **`qc/g3.json`**. Needs `expect.penetration`. |
|
||||
| G5 | `skin` | **The one that matters.** Poses the skinned checkpoint with the real game clips and counts garment verts inside the body, per frame. This is the gate that turns "QA renders only prove the pose that cannot fail" (above) into a number. Needs `expect.penetration`. |
|
||||
| G8 | `register` | Catalog↔asset lint: a missing `.gltf` (warn-only at runtime — the character just goes naked), a set with no `BaseDirFor` arm (silently resolves to the *Fantasy* pack folder), duplicate ids, unregistered exports. Always runs. |
|
||||
| G7 | `import` | `targeted_reimport.sh` — deletes just this asset's `.godot/imported/` entries, re-imports, and verifies they came back fresh. The blunt fix for the `.bin`-hash problem in "Known limitations" below, without a 17k-entry full reimport. |
|
||||
| G6 | `verify` | `clothing_motion_qa.sh` — spawns `ClothingTestBed` on the set, drives Idle/Walk/Dance, captures frames, and fails on any outfit load error. **Spawns with `SPAWN_BUILD=0`** — after editing game C# (catalog/bed entries), run `bash tools/game.sh build` first or G6 silently QAs the stale binary (its fallback messages will quote constants your edit already changed — that's the tell). |
|
||||
|
||||
G5 is the only gate that runs under Blender; the rest are plain `python`.
|
||||
|
||||
### First sign-off and blessing (G6)
|
||||
|
||||
G6's hard signal is deterministic (an outfit that failed to load says so). Its
|
||||
soft signal is a pixel diff against a blessed baseline, and that has a measured
|
||||
floor: re-running the same outfit moves ~0.002 of the frame, swapping the outfit
|
||||
**entirely** moves 0.014. So the diff is a regression tripwire for "the garment
|
||||
vanished or grossly changed" — it will not see a gaping neckline. G5 owns that
|
||||
class of bug.
|
||||
|
||||
Which means the first sign-off on a new garment is a human or VLM looking at the
|
||||
frames, and the flow is deliberately two-step:
|
||||
|
||||
```bash
|
||||
# 1. no baseline yet -> captures evidence, no diff, and tells you to go look
|
||||
python clothing/garment.py configs/piupiu.json --only verify
|
||||
|
||||
# 2. once the frames are actually good, freeze them
|
||||
GARMENT_BLESS=1 python clothing/garment.py configs/piupiu.json --only verify
|
||||
# (or --bless-baseline)
|
||||
|
||||
# 3. every later run diffs against that baseline automatically
|
||||
python clothing/garment.py configs/piupiu.json --only verify
|
||||
```
|
||||
|
||||
Baselines live in `clothing/baselines/<set-lowercased>/`. Blessing before looking
|
||||
freezes a defect as the thing every future run is compared to — that is why step 1
|
||||
refuses to bless for you.
|
||||
|
||||
Interfaces, schemas and the full `expect` reference:
|
||||
[`PIPELINE-CONTRACT.md`](PIPELINE-CONTRACT.md).
|
||||
|
||||
## Known limitations (v1)
|
||||
|
||||
- Skirt will still favor standing/dance poses; deep leg swings compress the hem
|
||||
(no skirt bones yet — the Z-gradient weights are ready for them later).
|
||||
- Albedo is the MD fabric PNG re-applied over MD's own exported UVs (part
|
||||
`texture` in the config); the bake stage still only produces normal+AO. Parts
|
||||
without a `texture` fall back to the flat `color`.
|
||||
- Body stays under garments (no hide-mask system in game yet) — clearance is the
|
||||
only poke-through defense; report tunnel-through in the test bed per animation.
|
||||
- Re-export alone does not reach the game. `GLTF_SEPARATE` keeps geometry and UVs
|
||||
in the sidecar `.bin`, but Godot's reimport check hashes only the `.gltf`, so a
|
||||
vertex- or UV-only change is silently skipped. Delete that asset's entries in
|
||||
`.godot/imported/` (those two files only — never the folder) and run
|
||||
`godot --headless --import --path .` to force it.
|
||||
Reference in New Issue
Block a user