Add MakeHuman head hair/body prototype and 12-variant rollout plan
This commit is contained in:
1 parent
958d3ae0ff
commit
b297c4543f
9 files changed
+269
-89
No files matched your search
@@ -10,15 +10,23 @@ canonical implemented-features list in `docs/development-status.md`.
|
|||||||
the licence gate. Downloads live in `tmp/research/` (gitignored): DDG image search, street-view
|
the licence gate. Downloads live in `tmp/research/` (gitignored): DDG image search, street-view
|
||||||
frames, neighbouring buildings — never committed, never shipped. Promotion into
|
frames, neighbouring buildings — never committed, never shipped. Promotion into
|
||||||
`docs/screens/refs/` always passes the sidecar licence + privacy gate and `CREDITS.md`.
|
`docs/screens/refs/` always passes the sidecar licence + privacy gate and `CREDITS.md`.
|
||||||
- **MakeHuman head prototype (2026-11-04):** `assets/models/blender_src/makehuman_head.glb`
|
- **MakeHuman head accepted (2026-11-04, owner):** the basic human head is a pass.
|
||||||
is a local 12,641-tri/7,690-split-vert head derived from the CC0 MakeHuman base,
|
`assets/models/blender_src/makehuman_head.glb` is the CC0 MakeHuman-derived head
|
||||||
with a review sheet and `docs/makehuman-head-review.md`. The latest build uses a
|
(12,642 tris / 7,690 split verts). The automated vision gate still records REJECT on
|
||||||
skin eyelid mask (almond aperture cut from a sphere), recessed globes, flat iris/pupil
|
the eye/lid region (globe depth, lid overlap, iris at three-quarter) as deferred
|
||||||
discs and surface-anchored brows. The automated vision gate still rejected it:
|
polish, not a blocker. The assembled path (`build_character(head="mh")`) adds a
|
||||||
the eye depth reads inconsistently across front/three-quarter/profile and the lid
|
skull-conforming hair cap derived from the closed head shell, hides the scalp under
|
||||||
does not visibly overlap the globe. It is not rigged, textured, integrated or
|
that cap, and narrows the neck/shoulder body. Default head source stays `face` until
|
||||||
shipped; keep the source/rejected parts in `tmp/` until real eyelid masses pass
|
the owner reviews the hair/body and the 12-variant rollout starts.
|
||||||
the gate.
|
- **MakeHuman hair/body prototype (2026-11-04):** `npc_casual --head mh` is
|
||||||
|
14,753 tris / 12,822 raw / 12,823 seam-split verts / 14 bones / 9 materials,
|
||||||
|
`inspect` clean. All 12 variants were counted at 14,693–15,987 tris. Hair uses flat
|
||||||
|
shading normals because the old hair normal map caused dark tangent patches on the
|
||||||
|
new cap. Scalp faces fully covered by the hair cap are deleted (saves ~950 tris);
|
||||||
|
the accepted face geometry is untouched. The procedural body was narrowed at the
|
||||||
|
neck/shoulders (shoulder root z 1.345, chest 0.158, neck 0.060/0.054) and its skin
|
||||||
|
decimate dropped to 0.21. The local renders are in
|
||||||
|
`tmp/npc-parts/prototype_mh/renders/`.
|
||||||
- **Project agent skills (2026-11-03):** two tracked skills live in `.agents/skills/`:
|
- **Project agent skills (2026-11-03):** two tracked skills live in `.agents/skills/`:
|
||||||
`gig-line-blender-asset-contract` and `gig-line-release-qa`. They are guidance only,
|
`gig-line-blender-asset-contract` and `gig-line-release-qa`. They are guidance only,
|
||||||
add no runtime dependency and are referenced from `tools/README.md`. The third-party
|
add no runtime dependency and are referenced from `tools/README.md`. The third-party
|
||||||
@@ -114,6 +122,12 @@ canonical implemented-features list in `docs/development-status.md`.
|
|||||||
out of a portrait frame (park the rest of the crowd away instead).
|
out of a portrait frame (park the rest of the crowd away instead).
|
||||||
- Blender: `bisect_plane` and thin bevels leave zero-area triangles (Godot "non-finite normal in
|
- Blender: `bisect_plane` and thin bevels leave zero-area triangles (Godot "non-finite normal in
|
||||||
LOD generation"); `_clean()` + a weld after bisect fixes them and the audit rejects them.
|
LOD generation"); `_clean()` + a weld after bisect fixes them and the audit rejects them.
|
||||||
|
- MakeHuman head/hair gotchas: the imported head is broad and flat, so a separate hair
|
||||||
|
ellipsoid floats or leaves a bald patch; derive the cap from the closed head shell and
|
||||||
|
boolean-cut the hairline. Do not reuse the skull's UVs with the old `hair_normal.png`
|
||||||
|
(dark tangent patches) — hair skips the normal map. Generate the cap before deleting
|
||||||
|
the hidden scalp, or the cap inherits the hole. The flatpak sandbox cannot read `/tmp`;
|
||||||
|
helper scripts must live under `$PWD`.
|
||||||
- Image models have per-model size floors/caps (Seedream needs 1920+; `GEN_SIZE=2048`, do not
|
- Image models have per-model size floors/caps (Seedream needs 1920+; `GEN_SIZE=2048`, do not
|
||||||
lower; gpt-5-image-mini caps lower). `generate_image` applies model-specific sizing; concept
|
lower; gpt-5-image-mini caps lower). `generate_image` applies model-specific sizing; concept
|
||||||
generation defaults to `--request-size 1536`.
|
generation defaults to `--request-size 1536`.
|
||||||
|
|||||||
@@ -26,10 +26,12 @@ five tint slots, 16,000/16,000 character cap, optional extra bones beyond the re
|
|||||||
**Current state:** `assets/models/npc/` contains the tracked frozen `npc_base.blend` and its
|
**Current state:** `assets/models/npc/` contains the tracked frozen `npc_base.blend` and its
|
||||||
gitignored `.blend1`; the 12 production characters are assembled procedurally by
|
gitignored `.blend1`; the 12 production characters are assembled procedurally by
|
||||||
`tools/blender_assets.py` from `_shape_head_mesh`, `_face`, `_hair` and `_hands_and_shoes`.
|
`tools/blender_assets.py` from `_shape_head_mesh`, `_face`, `_hair` and `_hands_and_shoes`.
|
||||||
There is no per-part source asset library yet. The MakeHuman body prototype and its temporary
|
The MakeHuman head is now a second head source (`build_character(head="mh")`,
|
||||||
head/hand swap live under `tmp/npc-research/` and are not the shipped path. This overhaul supplies
|
`build --head mh`, `npc_parts.py assemble --head mh`) and all 12 variants assemble inside the
|
||||||
the reusable head/face and hand parts that both the procedural characters and the MakeHuman body
|
16,000-triangle cap with it. The procedural body, hands and clothing remain unchanged. The
|
||||||
can consume.
|
MakeHuman body prototype and its temporary head/hand swap live under `tmp/npc-research/` and are
|
||||||
|
not the shipped path. This overhaul supplies the reusable head/face and hand parts that both the
|
||||||
|
procedural characters and the MakeHuman body can consume.
|
||||||
|
|
||||||
This workstream supersedes the face/head and hands items in §C until the prototypes are approved;
|
This workstream supersedes the face/head and hands items in §C until the prototypes are approved;
|
||||||
the rest of §C remains the broad review checklist.
|
the rest of §C remains the broad review checklist.
|
||||||
@@ -93,16 +95,18 @@ the rest of §C remains the broad review checklist.
|
|||||||
- [x] Compare two or three subdivision/loop variants at 1.85 m front/three-quarter/profile and a
|
- [x] Compare two or three subdivision/loop variants at 1.85 m front/three-quarter/profile and a
|
||||||
1.5 m portrait. Pick the variant that reads best before changing all 12 characters; keep the
|
1.5 m portrait. Pick the variant that reads best before changing all 12 characters; keep the
|
||||||
rejected variants local.
|
rejected variants local.
|
||||||
- [-] Rebuild a standalone MakeHuman-derived head from the researched CC0 base and save it to
|
- [x] Rebuild a standalone MakeHuman-derived head from the researched CC0 base and save it to
|
||||||
`assets/models/blender_src/makehuman_head.glb`; record provenance/review in
|
`assets/models/blender_src/makehuman_head.glb`; record provenance/review in
|
||||||
`docs/makehuman-head-review.md` and the preview sheet. (Final build: 12,641 tris / 7,690
|
`docs/makehuman-head-review.md` and the preview sheet. Final build: 12,642 tris /
|
||||||
split verts; assembled `npc_casual` 15,532 tris / 13,310 verts / 14 bones, `inspect` clean.
|
7,690 split verts, bounds x −0.094..0.094, y −0.180..0.042, z 1.530..1.803. The owner
|
||||||
Latest attempt: skin eyelid mask, recessed globes, flat iris/pupil discs, surface-anchored
|
accepted the **basic human head** on 2026-11-04; the automated vision gate still records
|
||||||
brows. Vision verdict: still **REJECT** — globe depth is inconsistent between front,
|
the eye-region REJECT as deferred polish (globe depth, lid overlap, iris at three-quarter).
|
||||||
three-quarter and profile, the lid does not visibly clip the globe, and the iris reads
|
The assembled path now adds a skull-conforming hair cap, a hidden-scalp trim and a narrowed
|
||||||
decal-locked at three-quarter; prototype stays local and un-integrated.)
|
neck/shoulder body. Assembled `npc_casual --head mh`: 14,753 tris / 12,822 raw / 12,823
|
||||||
|
seam-split verts / 14 bones / 9 materials, `inspect` clean.
|
||||||
- [ ] Build true upper/lower eyelid masses that overlap the globe in front, three-quarter and
|
- [ ] Build true upper/lower eyelid masses that overlap the globe in front, three-quarter and
|
||||||
profile views (not a socket hole), then re-run the vision gate before integrating the head.
|
profile views (not a socket hole), then re-run the vision gate. Deferred: the owner accepted
|
||||||
|
the basic head, so this is polish, not a rollout blocker.
|
||||||
|
|
||||||
Compared `head_proto` at seg 24 (670 verts), 48 (1,558 verts) and 64 (2,470 verts) at 1.85 m
|
Compared `head_proto` at seg 24 (670 verts), 48 (1,558 verts) and 64 (2,470 verts) at 1.85 m
|
||||||
and 1.5 m, front/three-quarter/profile. Vision-judged: **s48 wins** — s24 is too faceted
|
and 1.5 m, front/three-quarter/profile. Vision-judged: **s48 wins** — s24 is too faceted
|
||||||
@@ -117,12 +121,11 @@ the rest of §C remains the broad review checklist.
|
|||||||
imported GLB came out black and is superseded by the proper `.blend`-source bake). Flag: the
|
imported GLB came out black and is superseded by the proper `.blend`-source bake). Flag: the
|
||||||
prototype ears reach x ±0.162 vs the 0.1575 head-radius reference, so headgear may clip and
|
prototype ears reach x ±0.162 vs the 0.1575 head-radius reference, so headgear may clip and
|
||||||
must be re-measured at rollout.
|
must be re-measured at rollout.
|
||||||
- [ ] Get owner sign-off on the one-variant prototype, then roll the approved head/face into the
|
- [-] Owner review of the assembled one-variant prototype (`npc_casual --head mh`) before the
|
||||||
other 11 variants one asset family at a time, with a before/after sheet per batch.
|
12 shipped GLBs change. The basic human head is accepted (2026-11-04); the review now covers
|
||||||
**Blocked on owner sign-off.** Sign-off material is ready: the s48 prototype vs the current
|
the skull-conforming hair cap and the narrowed neck/shoulder body silhouette. Local renders:
|
||||||
`_face` at 1.85 m front/three-quarter/profile
|
`tmp/npc-parts/prototype_mh/renders/` (portrait front/three-quarter/profile + full body).
|
||||||
(`docs/blender_previews/sheet_head_proto_before_after.jpg`); the s24/s48/s64 comparison table
|
Rollout starts only after this review.
|
||||||
is in `docs/3d-model-review.md`. Rollout waits for the owner's go/no-go.
|
|
||||||
|
|
||||||
### 4. Hands and grip prototype
|
### 4. Hands and grip prototype
|
||||||
|
|
||||||
@@ -151,6 +154,30 @@ the rest of §C remains the broad review checklist.
|
|||||||
|
|
||||||
### 5. Assembly and rollout
|
### 5. Assembly and rollout
|
||||||
|
|
||||||
|
**MakeHuman 12-variant rollout.** - [x] Make the head source selectable from the main builder:
|
||||||
|
`blender_assets.py build --head mh --only npc_casual,...` and
|
||||||
|
`npc_parts.py assemble --variant <name> --head mh --out <stem>`. The default stays `face`
|
||||||
|
until the hair/body review passes.
|
||||||
|
- [x] Verify every variant assembles with the MakeHuman head inside the 16,000/16,000 cap
|
||||||
|
(pre-export triangle counts, `inspect` clean; `npc_casual` seam-split verts 12,823):
|
||||||
|
`npc_casual 14,753`, `npc_jacket 15,153`, `npc_coat 15,817`, `npc_vest 15,579`,
|
||||||
|
`npc_dress 15,215`, `npc_hoodie 15,837`, `npc_firefighter 15,987`, `npc_guard 15,729`,
|
||||||
|
`npc_crowd_a 14,947`, `npc_crowd_b 14,693`, `npc_crowd_c 15,433`, `npc_crowd_d 15,833`.
|
||||||
|
- [x] Recover head budget without touching the accepted face: remove head faces fully covered by
|
||||||
|
the hair cap, keep boundary faces as a safety band, and trim the hidden neck plug to 12
|
||||||
|
segments. This saves ~950 tris on every variant.
|
||||||
|
- [ ] Owner approves the hair cap and full-body silhouette on the local `npc_casual` prototype.
|
||||||
|
- [ ] Rebuild the 12 committed character GLBs with `--head mh`, in asset-family batches, then
|
||||||
|
regenerate their AO maps and manifest entries. Never run a no-op full-library rebuild.
|
||||||
|
- [ ] Run `blender_assets.py audit`, `fit`, `inspect` and `texels`, plus
|
||||||
|
`python3 tools/npc_proportions.py report`; record the 12-variant proportion table.
|
||||||
|
- [ ] Re-run the Godot NPC tests and capture the 12 × 6 baseline
|
||||||
|
(`src/test/capture_npc_baseline.gd` → `python3 tools/npc_baseline.py sheet`); inspect the
|
||||||
|
sheet for outliers before committing.
|
||||||
|
- [ ] Update `docs/makehuman-head-review.md`, `docs/3d-model-review.md`,
|
||||||
|
`docs/blender_previews/README.md`, `docs/npc-model-sourcing.md`, `CREDITS.md` and
|
||||||
|
`docs/development-status.md` with the accepted head/hair/body state and final counts.
|
||||||
|
|
||||||
- [ ] Integrate the approved parts into `build_character` with deterministic joins, weights, UVs and
|
- [ ] Integrate the approved parts into `build_character` with deterministic joins, weights, UVs and
|
||||||
AO. The final output remains one `npc_base` skinned mesh under the existing 13-core armature;
|
AO. The final output remains one `npc_base` skinned mesh under the existing 13-core armature;
|
||||||
optional extra bones are allowed but the core rest positions stay fixed;
|
optional extra bones are allowed but the core rest positions stay fixed;
|
||||||
|
|||||||
@@ -260,9 +260,11 @@ welding the added eye/brow pieces into the head shell and a skull-conforming hai
|
|||||||
**Standalone head re-review (2026-11-04):** the separate
|
**Standalone head re-review (2026-11-04):** the separate
|
||||||
`assets/models/blender_src/makehuman_head.glb` prototype (12,641 tris / 7,690 split verts,
|
`assets/models/blender_src/makehuman_head.glb` prototype (12,641 tris / 7,690 split verts,
|
||||||
with a skin eyelid mask, recessed globes, flat iris/pupil discs and surface-anchored brows)
|
with a skin eyelid mask, recessed globes, flat iris/pupil discs and surface-anchored brows)
|
||||||
was **rejected** by the vision gate: the globe depth reads inconsistently between front,
|
was **accepted by the owner** as a basic human head. The automated vision gate still returns
|
||||||
three-quarter and profile, the iris reads decal-locked at three-quarter, and the lid does
|
REJECT on the eye/lid region (globe depth, lid overlap, iris at three-quarter) as deferred
|
||||||
not visibly clip the globe. It stays local until real eyelid geometry passes; see
|
polish. The assembled `npc_casual --head mh` now adds a skull-conforming hair cap, a hidden
|
||||||
|
scalp trim and a narrowed neck/shoulder body: 14,753 tris / 12,823 seam-split verts /
|
||||||
|
14 bones / 9 materials, `inspect` clean. All 12 variants count 14,693–15,987 tris. See
|
||||||
[`makehuman-head-review.md`](makehuman-head-review.md).
|
[`makehuman-head-review.md`](makehuman-head-review.md).
|
||||||
|
|
||||||
### Head subdivision comparison (2026-11-04)
|
### Head subdivision comparison (2026-11-04)
|
||||||
@@ -493,7 +495,7 @@ and the full carded-cluster foliage pass is the one remaining owner-gated P1. No
|
|||||||
|
|
||||||
## Next actions
|
## Next actions
|
||||||
|
|
||||||
- Review the standalone [`makehuman-head-review.md`](makehuman-head-review.md) prototype before integrating any head/hand part into the character library.
|
- Review the assembled [`makehuman-head-review.md`](makehuman-head-review.md) prototype (hair cap and full-body silhouette) before rebuilding the 12 shipped character GLBs.
|
||||||
|
|
||||||
|
|
||||||
Tracked in [`TODO.md`](../TODO.md) `3D model re-evaluation`: the remaining `A. Baseline…` item is
|
Tracked in [`TODO.md`](../TODO.md) `3D model re-evaluation`: the remaining `A. Baseline…` item is
|
||||||
|
|||||||
@@ -140,11 +140,13 @@ MakeHuman sources against the character contract and records the 2026-11-03 dire
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
flatpak run org.blender.Blender --background --python "$PWD/tools/blender_assets.py" -- build
|
flatpak run org.blender.Blender --background --python "$PWD/tools/blender_assets.py" -- build
|
||||||
|
flatpak run org.blender.Blender --background --python "$PWD/tools/blender_assets.py" -- build --head mh
|
||||||
flatpak run org.blender.Blender --background --python "$PWD/tools/blender_assets.py" -- render
|
flatpak run org.blender.Blender --background --python "$PWD/tools/blender_assets.py" -- render
|
||||||
python3 tools/blender_assets.py sheet # docs/blender_previews/sheet_*.jpg
|
python3 tools/blender_assets.py sheet # docs/blender_previews/sheet_*.jpg
|
||||||
python3 tools/blender_assets.py audit # no Blender needed
|
python3 tools/blender_assets.py audit # no Blender needed
|
||||||
python3 tools/blender_assets.py fit # gear-vs-body fit report
|
python3 tools/blender_assets.py fit # gear-vs-body fit report
|
||||||
python3 tools/blender_assets.py texels # per-slot texel density (px/m) of the characters
|
python3 tools/blender_assets.py texels # per-slot texel density (px/m) of the characters
|
||||||
|
# `build --head mh` selects the MakeHuman head; the default stays `face` until rollout.
|
||||||
godot --headless --path . --import
|
godot --headless --path . --import
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ capture harnesses.
|
|||||||
| --- | --- |
|
| --- | --- |
|
||||||
| [](sheet_npc.jpg) <br> `sheet_npc.jpg` | Eight skinned character variants (casual, jacket, coat, hi-vis vest, dress, hoodie, firefighter, guard) with their gear attach points. |
|
| [](sheet_npc.jpg) <br> `sheet_npc.jpg` | Eight skinned character variants (casual, jacket, coat, hi-vis vest, dress, hoodie, firefighter, guard) with their gear attach points. |
|
||||||
| [](sheet_npc_baseline.jpg) <br> `sheet_npc_baseline.jpg` | Neutral studio baseline: 8 characters x front, three-quarter, profile, back, full body and crowd views. |
|
| [](sheet_npc_baseline.jpg) <br> `sheet_npc_baseline.jpg` | Neutral studio baseline: 8 characters x front, three-quarter, profile, back, full body and crowd views. |
|
||||||
| [](sheet_makehuman_head.jpg) <br> `sheet_makehuman_head.jpg` | Local MakeHuman head prototype: front, three-quarter and profile review views. The vision gate rejected this build (inconsistent globe depth and no visible lid overlap); see [`../makehuman-head-review.md`](../makehuman-head-review.md). |
|
| [](sheet_makehuman_head.jpg) <br> `sheet_makehuman_head.jpg` | MakeHuman head: front, three-quarter and profile review views. The owner accepted the basic head; the automated vision gate still rejects the eye/lid region as deferred polish. See [`../makehuman-head-review.md`](../makehuman-head-review.md). |
|
||||||
| [](sheet_npc_baseline_compare-body.jpg) <br> `sheet_npc_baseline_compare-body.jpg` | Neck/shoulder transition: fuller neck base and a lower shoulder root. |
|
| [](sheet_npc_baseline_compare-body.jpg) <br> `sheet_npc_baseline_compare-body.jpg` | Neck/shoulder transition: fuller neck base and a lower shoulder root. |
|
||||||
| [](sheet_npc_baseline_compare-face-detail.jpg) <br> `sheet_npc_baseline_compare-face-detail.jpg` | Face detail: nose ala wings, a single rounded mouth and a darker ear concha. |
|
| [](sheet_npc_baseline_compare-face-detail.jpg) <br> `sheet_npc_baseline_compare-face-detail.jpg` | Face detail: nose ala wings, a single rounded mouth and a darker ear concha. |
|
||||||
| [](sheet_npc_baseline_compare-face.jpg) <br> `sheet_npc_baseline_compare-face.jpg` | Face-depth pass: recessed sclera, upper lid and sculpted chin/cheek planes. |
|
| [](sheet_npc_baseline_compare-face.jpg) <br> `sheet_npc_baseline_compare-face.jpg` | Face-depth pass: recessed sclera, upper lid and sculpted chin/cheek planes. |
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
# MakeHuman head prototype
|
# MakeHuman head prototype
|
||||||
|
|
||||||
Status: **local prototype, rejected by the automated visual gate, not integrated.**
|
Status: **owner-accepted head, assembled prototype, not yet shipped.**
|
||||||
The asset is a standalone head/neck extracted from the CC0 MakeHuman base in
|
The owner accepted the basic human head on 2026-11-04. The automated vision gate
|
||||||
`tmp/npc-research/makehuman/base.obj`. It is kept as a reviewable experiment and
|
still records REJECT on the eye/lid region as deferred polish. The asset is a
|
||||||
is not part of the shipped NPC contract.
|
standalone head/neck extracted from the CC0 MakeHuman base in
|
||||||
|
`tmp/npc-research/makehuman/base.obj`; the assembled path now adds hair, a hidden
|
||||||
|
scalp trim and the procedural body. The 12 shipped GLBs have not been rebuilt
|
||||||
|
with it yet.
|
||||||
|
|
||||||
## Asset
|
## Asset
|
||||||
|
|
||||||
@@ -14,10 +17,12 @@ is not part of the shipped NPC contract.
|
|||||||
components, cuts an almond aperture through a skin eyelid mask, adds recessed
|
components, cuts an almond aperture through a skin eyelid mask, adds recessed
|
||||||
eyeballs with flat iris/pupil discs, anchors brows to the face surface, renders
|
eyeballs with flat iris/pupil discs, anchors brows to the face surface, renders
|
||||||
front/three-quarter/profile and exports the standalone GLB.
|
front/three-quarter/profile and exports the standalone GLB.
|
||||||
- Measurements (final build, `inspect`): **12,641 triangles / 7,690 split
|
- Measurements (final standalone build, `inspect`): **12,641 triangles / 7,690
|
||||||
vertices**; bounds approximately `x ±0.094 m`, `y -0.180…0.042 m`,
|
split vertices**; bounds approximately `x ±0.094 m`, `y -0.180…0.042 m`,
|
||||||
`z 1.530…1.803 m` in project Z-up space. It is inside the 16,000/16,000
|
`z 1.530…1.803 m` in project Z-up space.
|
||||||
character cap.
|
- Assembled `npc_casual --head mh`: **14,753 triangles / 12,823 seam-split
|
||||||
|
vertices / 14 bones / 9 materials**, `inspect` clean. All 12 variants count
|
||||||
|
14,693–15,987 triangles with the same head.
|
||||||
- Materials used: `skin`, `eye_white`, `iris`, `pupil`, `hair`. No textures are
|
- Materials used: `skin`, `eye_white`, `iris`, `pupil`, `hair`. No textures are
|
||||||
baked into this prototype.
|
baked into this prototype.
|
||||||
|
|
||||||
@@ -69,8 +74,10 @@ Accept only if:
|
|||||||
|
|
||||||
## Judge result
|
## Judge result
|
||||||
|
|
||||||
**VERDICT: REJECT** (vision judge `z-ai/glm-5.3-flash`, final build render set
|
**Owner verdict: ACCEPT (basic human head, 2026-11-04).**
|
||||||
above). The gate is blocked; the prototype is not presented as an accepted head.
|
**Automated vision gate: REJECT** (vision judge `z-ai/glm-5.3-flash`, final
|
||||||
|
standalone build render set above). The eye/lid findings below remain deferred
|
||||||
|
polish; they are no longer a rollout blocker because the owner accepted the head.
|
||||||
|
|
||||||
Blocking findings:
|
Blocking findings:
|
||||||
|
|
||||||
@@ -96,24 +103,37 @@ Should-fix findings:
|
|||||||
- the catchlight is baked geometry and is not consistent between eyes.
|
- the catchlight is baked geometry and is not consistent between eyes.
|
||||||
|
|
||||||
What passed: the skin/eye material remap is correct, the eye openings are
|
What passed: the skin/eye material remap is correct, the eye openings are
|
||||||
closed, the standalone GLB reports **12,641 triangles / 7,690 split vertices**
|
closed, the standalone GLB reports **12,641 triangles / 7,690 split vertices**,
|
||||||
with no issues, and an assembled `npc_casual` with this head passes `inspect` at
|
and an assembled `npc_casual` with this head passes `inspect` at **14,753 /
|
||||||
**15,532 / 16,000 triangles, 13,310 split vertices, 14 bones, no issues**.
|
16,000 triangles, 12,823 seam-split vertices, 14 bones, no issues**.
|
||||||
|
|
||||||
Known limits: bald mannequin-like presentation, stylized geometry-only
|
Known limits: bald mannequin-like presentation, stylized geometry-only
|
||||||
iris/pupil, no rig, no UV texture pass, and the neck cap is a review cut rather
|
iris/pupil, no rig, no UV texture pass, and the neck cap is a review cut rather
|
||||||
than a body-welded seam.
|
than a body-welded seam.
|
||||||
|
|
||||||
## Next steps after the gate passes
|
## Assembled prototype (2026-11-04)
|
||||||
|
|
||||||
|
- `_makehuman_head` imports the GLB and adds a **skull-conforming hair cap**
|
||||||
|
derived from the closed head shell (scaled shell + boolean front-sphere
|
||||||
|
cut + decimate). Hair skips the old `hair_normal.png` because the skull UVs
|
||||||
|
produced large dark tangent patches.
|
||||||
|
- Head faces fully covered by the cap are deleted to recover ~950 triangles;
|
||||||
|
boundary faces stay as a safety band, so the accepted face geometry is
|
||||||
|
untouched. The hidden neck plug uses 12 segments.
|
||||||
|
- The procedural body was narrowed at the neck and shoulders (chest 0.158,
|
||||||
|
neck base 0.060, neck top 0.054, shoulder root z 1.345) and its skin decimate
|
||||||
|
dropped to 0.21 so all 12 variants fit the 16,000 cap.
|
||||||
|
- Local review renders: `tmp/npc-parts/prototype_mh/renders/` (portrait
|
||||||
|
front/three-quarter/profile and full-body front/three-quarter).
|
||||||
|
|
||||||
|
## Deferred polish
|
||||||
|
|
||||||
- Replace the radial lid mask with real upper/lower lid masses that fold over
|
- Replace the radial lid mask with real upper/lower lid masses that fold over
|
||||||
the globe, with pointed canthi, and pass the profile view.
|
the globe, with pointed canthi, and pass the profile view.
|
||||||
- Move the iris onto a curved cornea cap so its orientation follows the globe at
|
- Move the iris onto a curved cornea cap so its orientation follows the globe at
|
||||||
three-quarter angles, and use a lighting-driven catchlight instead of baked
|
three-quarter angles, and use a lighting-driven catchlight instead of baked
|
||||||
white geometry.
|
white geometry.
|
||||||
- Compare against the project `_face` baseline and decide whether this head is
|
- Refine the hair cap silhouette (current short cap reads as a helmet) and
|
||||||
the base for the local head/face overhaul.
|
improve the full-body shoulder/arm shape before the 12-variant rollout.
|
||||||
- Add a clean UV/material pass and a contract armature/weights if it becomes
|
- Add a clean UV/material pass and a contract armature/weights when the head
|
||||||
part of a library character.
|
becomes part of the shipped library characters.
|
||||||
- Keep the source script under `tmp/`; promote only the reviewed GLB and durable
|
|
||||||
docs/preview sheet.
|
|
||||||
@@ -46,13 +46,13 @@ project head/hands swap reduced this to **6,407 / 16,000 tris and**
|
|||||||
**6,651 / 16,000 verts**, but manual shoulder/neck retopology and part cleanup
|
**6,651 / 16,000 verts**, but manual shoulder/neck retopology and part cleanup
|
||||||
remain before it can be considered shippable.
|
remain before it can be considered shippable.
|
||||||
|
|
||||||
A standalone MakeHuman head prototype is recorded in
|
A standalone MakeHuman head is recorded in
|
||||||
[`makehuman-head-review.md`](makehuman-head-review.md); it remains local/un-integrated
|
[`makehuman-head-review.md`](makehuman-head-review.md). The owner accepted the basic
|
||||||
until the overhaul accepts it.
|
human head on 2026-11-04; the assembled path (`build_character(head="mh")`) adds a
|
||||||
|
skull-conforming hair cap and hides the scalp under it. The 12 shipped GLBs still use
|
||||||
The head/face/hand overhaul in [`TODO.md`](../TODO.md) is the prerequisite: the
|
the procedural head until the hair/body review and rollout in [`TODO.md`](../TODO.md).
|
||||||
MakeHuman body should reuse the approved local head/hand parts rather than
|
The MakeHuman body should reuse the approved local head/hand parts rather than forking
|
||||||
forking a second face implementation.
|
a second face implementation.
|
||||||
|
|
||||||
## Provenance rules
|
## Provenance rules
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
> **Historical page / changed source (2026-11-04).** The numbers below describe the
|
> **Historical page / changed source (2026-11-04).** The numbers below describe the
|
||||||
> earlier decimated MakeHuman head (1,548-tri skin). The shared source
|
> earlier decimated MakeHuman head (1,548-tri skin). The shared source
|
||||||
> `assets/models/blender_src/makehuman_head.glb` now holds the higher-resolution
|
> `assets/models/blender_src/makehuman_head.glb` now holds the higher-resolution
|
||||||
> standalone prototype; its automated vision gate returned **REJECT** (inconsistent
|
> standalone prototype. The owner accepted the basic head; the automated vision gate
|
||||||
> globe depth across views and no visible lid overlap). See
|
> still records REJECT on the eye/lid region as deferred polish. See
|
||||||
> [`../../makehuman-head-review.md`](../../makehuman-head-review.md). Re-run the
|
> [`../../makehuman-head-review.md`](../../makehuman-head-review.md). Re-run the
|
||||||
> library head build before relying on the comparison counts below.
|
> library head build before relying on the comparison counts below.
|
||||||
|
|
||||||
@@ -60,8 +60,9 @@ Left to right: front, three-quarter, profile.
|
|||||||
4. The head skin is 1.5k tris (smooth but low-poly); a retopology pass could add resolution if a
|
4. The head skin is 1.5k tris (smooth but low-poly); a retopology pass could add resolution if a
|
||||||
portrait shows faceting.
|
portrait shows faceting.
|
||||||
|
|
||||||
## Decision needed
|
## Decision (2026-11-04, owner)
|
||||||
|
|
||||||
Approve or reject the MakeHuman-head direction. If approved, the next step is rolling it into the
|
The MakeHuman-head direction is approved for the basic human head. The remaining review is the
|
||||||
other 11 variants (one asset family at a time) with a before/after sheet per batch, and re-running
|
assembled prototype's hair cap and full-body silhouette. After that, roll the head into the other
|
||||||
the 24-NPC budget + AO bake.
|
11 variants (one asset family at a time) with a before/after sheet per batch, then re-run the
|
||||||
|
24-NPC budget + AO bake.
|
||||||
+138
-24
@@ -276,13 +276,17 @@ def material(name, textured_solid=False):
|
|||||||
check_existing=True)
|
check_existing=True)
|
||||||
albedo.image.colorspace_settings.name = "sRGB"
|
albedo.image.colorspace_settings.name = "sRGB"
|
||||||
links.new(albedo.outputs["Color"], bsdf.inputs["Base Color"])
|
links.new(albedo.outputs["Color"], bsdf.inputs["Base Color"])
|
||||||
normal_tex = nodes.new("ShaderNodeTexImage")
|
# The hair normal map was authored against the old cap UVs. Reusing it
|
||||||
normal_tex.image = bpy.data.images.load(
|
# on the skull-conforming MakeHuman cap produces large dark tangent
|
||||||
str(NPC_TEX_DIR / f"{name}_normal.png"), check_existing=True)
|
# patches, so hair keeps its albedo but takes flat shading normals.
|
||||||
normal_tex.image.colorspace_settings.name = "Non-Color"
|
if name != "hair":
|
||||||
nmap = nodes.new("ShaderNodeNormalMap")
|
normal_tex = nodes.new("ShaderNodeTexImage")
|
||||||
links.new(normal_tex.outputs["Color"], nmap.inputs["Color"])
|
normal_tex.image = bpy.data.images.load(
|
||||||
links.new(nmap.outputs["Normal"], bsdf.inputs["Normal"])
|
str(NPC_TEX_DIR / f"{name}_normal.png"), check_existing=True)
|
||||||
|
normal_tex.image.colorspace_settings.name = "Non-Color"
|
||||||
|
nmap = nodes.new("ShaderNodeNormalMap")
|
||||||
|
links.new(normal_tex.outputs["Color"], nmap.inputs["Color"])
|
||||||
|
links.new(nmap.outputs["Normal"], bsdf.inputs["Normal"])
|
||||||
rough = {"skin": 0.6, "cloth": 0.86, "pants": 0.92,
|
rough = {"skin": 0.6, "cloth": 0.86, "pants": 0.92,
|
||||||
"accessory": 0.5, "hair": 0.7}[name]
|
"accessory": 0.5, "hair": 0.7}[name]
|
||||||
bsdf.inputs["Roughness"].default_value = rough
|
bsdf.inputs["Roughness"].default_value = rough
|
||||||
@@ -550,22 +554,21 @@ def _skin_body(spec):
|
|||||||
"guard": 0.05}.get(layer, 0.0)
|
"guard": 0.05}.get(layer, 0.0)
|
||||||
leg = 1.0 + {"responder": 0.1, "dress": -0.1}.get(layer, 0.0)
|
leg = 1.0 + {"responder": 0.1, "dress": -0.1}.get(layer, 0.0)
|
||||||
nodes = [ # (pos, radius_x, radius_y)
|
nodes = [ # (pos, radius_x, radius_y)
|
||||||
((0, 0, 0.80), 0.168 * w, 0.116 * d), # 0 pelvis
|
((0, 0, 0.80), 0.158 * w, 0.112 * d), # 0 pelvis
|
||||||
((0, 0, 0.96), 0.146 * w * bulk, 0.103 * d * bulk), # 1 waist
|
((0, 0, 0.96), 0.142 * w * bulk, 0.101 * d * bulk), # 1 waist
|
||||||
((0, 0.0, 1.18), 0.168 * w * bulk, 0.114 * d * bulk), # 2 chest
|
((0, 0.0, 1.18), 0.155 * w * bulk, 0.110 * d * bulk), # 2 chest
|
||||||
((0, 0.0, 1.355), 0.170 * w * bulk, 0.108 * d * bulk), # 3 upper chest
|
((0, 0.0, 1.335), 0.158 * w * bulk, 0.105 * d * bulk), # 3 upper chest
|
||||||
# A slightly fuller neck base and a lower shoulder root give the
|
# Narrow neck and a lower, sloped shoulder root. The old wide neck base
|
||||||
# head/neck/shoulder line a trapezius slope instead of a thin post on
|
# and horizontal shoulder shelf made the accepted head read as small.
|
||||||
# a hanger. The head and collar still sit above this band.
|
((0, 0, 1.42), 0.060, 0.060), # 4 neck base
|
||||||
((0, 0, 1.43), 0.074, 0.072), # 4 neck base
|
((0, -0.005, 1.53), 0.054, 0.054), # 5 neck top
|
||||||
((0, -0.005, 1.53), 0.060, 0.060), # 5 neck top
|
|
||||||
]
|
]
|
||||||
edges = [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5)]
|
edges = [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5)]
|
||||||
for s in (-1.0, 1.0):
|
for s in (-1.0, 1.0):
|
||||||
base = len(nodes)
|
base = len(nodes)
|
||||||
nodes += [
|
nodes += [
|
||||||
((0.20 * s * w, 0, 1.37), 0.080 * t * sleeve, 0.074 * t * sleeve), # shoulder
|
((0.19 * s * w, 0, 1.345), 0.070 * t * sleeve, 0.068 * t * sleeve), # shoulder
|
||||||
((0.27 * s * w, 0, 1.23), 0.058 * t * sleeve, 0.058 * t * sleeve), # upper arm
|
((0.27 * s * w, 0, 1.22), 0.054 * t * sleeve, 0.055 * t * sleeve), # upper arm
|
||||||
((0.275 * s * w, 0.005, 1.06), 0.046 * t * sleeve, 0.048 * t * sleeve), # elbow
|
((0.275 * s * w, 0.005, 1.06), 0.046 * t * sleeve, 0.048 * t * sleeve), # elbow
|
||||||
((0.275 * s * w, -0.01, 0.78), 0.034 * t, 0.030 * t), # wrist
|
((0.275 * s * w, -0.01, 0.78), 0.034 * t, 0.030 * t), # wrist
|
||||||
]
|
]
|
||||||
@@ -591,7 +594,7 @@ def _skin_body(spec):
|
|||||||
sub = obj.modifiers.new("Sub", "SUBSURF")
|
sub = obj.modifiers.new("Sub", "SUBSURF")
|
||||||
sub.levels = 2
|
sub.levels = 2
|
||||||
dec = obj.modifiers.new("Decimate", "DECIMATE")
|
dec = obj.modifiers.new("Decimate", "DECIMATE")
|
||||||
dec.ratio = 0.26
|
dec.ratio = 0.21
|
||||||
dec.use_symmetry = True
|
dec.use_symmetry = True
|
||||||
dec.symmetry_axis = "X"
|
dec.symmetry_axis = "X"
|
||||||
_apply_all(obj)
|
_apply_all(obj)
|
||||||
@@ -716,6 +719,102 @@ def _hair_cap(name, mat, front_z, back_z, radius=0.163, center=(0, 0.008, 1.645)
|
|||||||
return _finish(_link(name, bm), mat, smooth=True, sharp_angle=70)
|
return _finish(_link(name, bm), mat, smooth=True, sharp_angle=70)
|
||||||
|
|
||||||
|
|
||||||
|
def _hair_curve(style):
|
||||||
|
"""Hairline endpoints and skull offset for a hair style."""
|
||||||
|
if style == "buzz":
|
||||||
|
return 1.734, 1.608, 1.035
|
||||||
|
return 1.714, 1.588, 1.055
|
||||||
|
|
||||||
|
|
||||||
|
def _trim_hidden_scalp(head, style):
|
||||||
|
"""Delete the scalp that sits under the hair cap to free triangle budget.
|
||||||
|
|
||||||
|
The cap is a scaled copy of the skull, so any head face whose scaled
|
||||||
|
vertices all fall outside the hairline sphere is fully covered. Keeping
|
||||||
|
boundary-touching faces leaves a safety band under the hair rim; the open
|
||||||
|
hole is never visible and the accepted face geometry is untouched.
|
||||||
|
"""
|
||||||
|
front_z, back_z, scale = _hair_curve(style)
|
||||||
|
center = Vector((0.0, 0.0, 1.665))
|
||||||
|
cutter_z = (front_z + back_z) * 0.5 - 0.396
|
||||||
|
bm = bmesh.new()
|
||||||
|
bm.from_mesh(head.data)
|
||||||
|
|
||||||
|
def covered(p):
|
||||||
|
q = center + (p - center) * scale
|
||||||
|
return (q.x * q.x + (q.y + 0.41) ** 2
|
||||||
|
+ (q.z - cutter_z) ** 2) > 0.56 ** 2
|
||||||
|
|
||||||
|
doomed = [f for f in bm.faces if all(covered(v.co) for v in f.verts)]
|
||||||
|
if doomed:
|
||||||
|
bmesh.ops.delete(bm, geom=doomed, context="FACES")
|
||||||
|
loose = [v for v in bm.verts if not v.link_faces]
|
||||||
|
if loose:
|
||||||
|
bmesh.ops.delete(bm, geom=loose, context="VERTS")
|
||||||
|
bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
|
||||||
|
bm.to_mesh(head.data)
|
||||||
|
bm.free()
|
||||||
|
|
||||||
|
|
||||||
|
def _hair_from_head(source, style, mat_hair):
|
||||||
|
"""Build the hair cap as a solid shell derived from the skull.
|
||||||
|
|
||||||
|
A separate hair ellipsoid cannot follow the broad MakeHuman crown, and an
|
||||||
|
open, face-deleted copy produces a sawtooth hairline. Scaling the closed
|
||||||
|
head shell outwards and boolean-cutting the face with a large front sphere
|
||||||
|
gives a watertight cap whose curved hairline is smooth from every angle.
|
||||||
|
"""
|
||||||
|
front_z, back_z, scale = _hair_curve(style)
|
||||||
|
cap = source.copy()
|
||||||
|
cap.data = source.data.copy()
|
||||||
|
cap.name = "HairCap"
|
||||||
|
bpy.context.scene.collection.objects.link(cap)
|
||||||
|
center = Vector((0.0, 0.0, 1.665))
|
||||||
|
for v in cap.data.vertices:
|
||||||
|
v.co = center + (v.co - center) * scale
|
||||||
|
# The cutter is a big sphere in front of the face: its upper surface rises
|
||||||
|
# toward the brow and falls toward the nape, which is the hairline curve.
|
||||||
|
mid_z = (front_z + back_z) * 0.5
|
||||||
|
cutter_z = mid_z - 0.396
|
||||||
|
bpy.ops.mesh.primitive_uv_sphere_add(segments=32, ring_count=20, radius=0.56,
|
||||||
|
location=(0.0, -0.41, cutter_z))
|
||||||
|
cutter = bpy.context.object
|
||||||
|
bpy.context.view_layer.objects.active = cap
|
||||||
|
mod = cap.modifiers.new("Hairline", "BOOLEAN")
|
||||||
|
mod.operation = "DIFFERENCE"
|
||||||
|
mod.solver = "EXACT"
|
||||||
|
mod.object = cutter
|
||||||
|
bpy.ops.object.modifier_apply(modifier=mod.name)
|
||||||
|
bpy.data.objects.remove(cutter, do_unlink=True)
|
||||||
|
# The boolean result is still head-density. Hair needs silhouette, not
|
||||||
|
# pores, and the character budget has to stay under 16k triangles.
|
||||||
|
dec = cap.modifiers.new("HairReduce", "DECIMATE")
|
||||||
|
dec.ratio = 0.34
|
||||||
|
dec.use_collapse_triangulate = True
|
||||||
|
bpy.ops.object.modifier_apply(modifier=dec.name)
|
||||||
|
bm = bmesh.new()
|
||||||
|
bm.from_mesh(cap.data)
|
||||||
|
bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
|
||||||
|
bm.to_mesh(cap.data)
|
||||||
|
bm.free()
|
||||||
|
cap.data.materials.clear()
|
||||||
|
cap.data.materials.append(mat_hair)
|
||||||
|
cap.data.shade_smooth()
|
||||||
|
cap.data.set_sharp_from_angle(angle=math.radians(55))
|
||||||
|
|
||||||
|
parts = [cap]
|
||||||
|
if style == "long":
|
||||||
|
parts.append(sphere("HairBack", 0.095, (0, 0.060, 1.560), mat_hair,
|
||||||
|
seg=14, rings=9, scale=(0.98, 0.55, 1.55)))
|
||||||
|
for s in (-1.0, 1.0):
|
||||||
|
parts.append(sphere("Lock", 0.048, (0.089 * s, -0.012, 1.585),
|
||||||
|
mat_hair, seg=8, rings=6,
|
||||||
|
scale=(0.55, 0.85, 2.05)))
|
||||||
|
if style == "bun":
|
||||||
|
parts.append(sphere("Bun", 0.060, (0, 0.100, 1.775), mat_hair,
|
||||||
|
seg=12, rings=8, scale=(1.0, 0.9, 0.95)))
|
||||||
|
return parts
|
||||||
|
|
||||||
def _hair(style, mat_hair, broad=False):
|
def _hair(style, mat_hair, broad=False):
|
||||||
parts = []
|
parts = []
|
||||||
if style == "buzz":
|
if style == "buzz":
|
||||||
@@ -1005,7 +1104,15 @@ def _makehuman_head(mats, spec):
|
|||||||
target = remap.get(mat.name.split(".")[0])
|
target = remap.get(mat.name.split(".")[0])
|
||||||
if target is not None:
|
if target is not None:
|
||||||
head.data.materials[index] = target
|
head.data.materials[index] = target
|
||||||
return [head]
|
# Bridge the 1.48-1.53 m neck gap between the MakeHuman cut and the
|
||||||
|
# procedurally built torso. The plug sits inside both shells, so only
|
||||||
|
# the visible band reads as neck and no seam ring reaches the surface.
|
||||||
|
plug = cyl("NeckPlug", 0.062, 0.052, 0.095, (0, 0.002, 1.500), skin,
|
||||||
|
seg=12, scale=(1.10, 1.0, 1.0))
|
||||||
|
hair_style = "buzz" if spec["layer"] == "hood" else spec["hair"]
|
||||||
|
hair_parts = _hair_from_head(head, hair_style, hair)
|
||||||
|
_trim_hidden_scalp(head, hair_style)
|
||||||
|
return [head, plug] + hair_parts
|
||||||
|
|
||||||
# The old decimated MakeHuman "hair" scalp slot reads as shaggy shards;
|
# The old decimated MakeHuman "hair" scalp slot reads as shaggy shards;
|
||||||
# collapse the head to one skin slot so the join does not collide with the
|
# collapse the head to one skin slot so the join does not collide with the
|
||||||
@@ -1245,7 +1352,8 @@ def build_character(name, head="face"):
|
|||||||
else:
|
else:
|
||||||
head_parts = _face(mats, spec)
|
head_parts = _face(mats, spec)
|
||||||
hair_style = spec["hair"] if spec["layer"] != "hood" else "buzz"
|
hair_style = spec["hair"] if spec["layer"] != "hood" else "buzz"
|
||||||
head_parts += _hair(hair_style, mats["hair"])
|
if head != "mh":
|
||||||
|
head_parts += _hair(hair_style, mats["hair"])
|
||||||
head = join(head_parts, "head")
|
head = join(head_parts, "head")
|
||||||
_auto_weights(head, layout, rigid=lambda p: "Head")
|
_auto_weights(head, layout, rigid=lambda p: "Head")
|
||||||
|
|
||||||
@@ -1811,10 +1919,10 @@ def build_prop(name):
|
|||||||
# Build + render entry points (inside Blender)
|
# Build + render entry points (inside Blender)
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
def _build_one(category, name):
|
def _build_one(category, name, head="face"):
|
||||||
reset_scene()
|
reset_scene()
|
||||||
if category == "npc":
|
if category == "npc":
|
||||||
objs = build_character(name)
|
objs = build_character(name, head=head)
|
||||||
elif category == "npc_gear":
|
elif category == "npc_gear":
|
||||||
objs = build_gear(name)
|
objs = build_gear(name)
|
||||||
else:
|
else:
|
||||||
@@ -2764,6 +2872,9 @@ def main(argv=None):
|
|||||||
for cmd in ("build", "render"):
|
for cmd in ("build", "render"):
|
||||||
s = sub.add_parser(cmd)
|
s = sub.add_parser(cmd)
|
||||||
s.add_argument("--only", default="")
|
s.add_argument("--only", default="")
|
||||||
|
if cmd == "build":
|
||||||
|
s.add_argument("--head", default="face", choices=("face", "proto", "mh"),
|
||||||
|
help="character head source (default: face)")
|
||||||
a = sub.add_parser("audit")
|
a = sub.add_parser("audit")
|
||||||
a.add_argument("--json", action="store_true")
|
a.add_argument("--json", action="store_true")
|
||||||
fit = sub.add_parser("fit")
|
fit = sub.add_parser("fit")
|
||||||
@@ -2854,7 +2965,10 @@ def main(argv=None):
|
|||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
return 2
|
return 2
|
||||||
for cat, name in _selected(args.only):
|
for cat, name in _selected(args.only):
|
||||||
(_build_one if args.cmd == "build" else _render_one)(cat, name)
|
if args.cmd == "build":
|
||||||
|
_build_one(cat, name, head=args.head)
|
||||||
|
else:
|
||||||
|
_render_one(cat, name)
|
||||||
if args.cmd == "build":
|
if args.cmd == "build":
|
||||||
results = [audit_asset(c, n) for c, n in catalogue()]
|
results = [audit_asset(c, n) for c, n in catalogue()]
|
||||||
write_manifest([r for r in results if r["vertices"]])
|
write_manifest([r for r in results if r["vertices"]])
|
||||||
|
|||||||
Reference in new issue
Block a user