Merge branch 'main' of https://tinqs.com/tinqs/animation
# Conflicts: # characters/REGISTRY.md
@@ -66,14 +66,50 @@ Census facts that shape the plan:
|
||||
3. **AccuRig** (manual, Jeremy/Ozlem): rig the bait FBX. Export rigged FBX.
|
||||
4. **Graft** (headless Blender): import rigged bait FBX + pristine GLB; snap rest
|
||||
poses; transfer skeleton + skin weights decimated→full-res by nearest-surface
|
||||
with tight max-distance; **hands get special handling** (per-finger masked
|
||||
transfer or reduced distance + limit-totals + normalize) — this is where the
|
||||
old hand mangling gets fixed. Export GLB: original mesh + original packed
|
||||
with tight max-distance. Export GLB: original mesh + original packed
|
||||
textures + new rig. FBX artifacts are discarded.
|
||||
**Hands are no longer part of this step — see "Hands" below.**
|
||||
5. **QC**: pose sweep incl. finger curls; then (if/when destined for game) the
|
||||
existing AccuRig→Quaternius conversion (`ariki-game/tools/make_male_ib_quatskin_accurig.py`
|
||||
lineage) picks it up.
|
||||
|
||||
## Hands: transplanted, not solved (RESOLVED 2026-08-07)
|
||||
|
||||
The hands come off the bait entirely and are transplanted from a body that already
|
||||
ships working ones — `tools/graft_hands.py`, wrist-aligned. Three measurements
|
||||
decided this:
|
||||
|
||||
- **There is nothing to solve.** The nude lane never touched the hands: hand-region
|
||||
displacement from the Tripo original is mean **0.010 mm**, p50 exactly 0.000, only
|
||||
0.06% beyond 1 mm (the 12.6% vertex churn there is micron jitter from the v03
|
||||
despeckle pass). A donor's hand weights fit this mesh as-is.
|
||||
- **A mitten rig would cost real animation.** Fingers move *relative to each other*
|
||||
by up to **89°** over a clip (fertility 89.4°, taming 77.6°, nd_01 46.7°,
|
||||
canoedismount 26.4°); only `war_dance_01` and `hakadance1_static` hold them still.
|
||||
So four of six dances would visibly flatten. Rejected.
|
||||
- **The bone list is not negotiable anyway.** 40 of the 65 joints are hands, every
|
||||
clip carries tracks for all 40, and `tools/verify_body_variant.py` gates on "65
|
||||
joints in identical order". A mitten could only ever have been a *skinning* choice.
|
||||
|
||||
`graft_hands.py` edits the glTF node graph directly rather than round-tripping the
|
||||
armature through Blender, because Blender re-derives bone rest orientation from
|
||||
edit-bone head/tail — the exact failure `ariki-game/tools/rig_pose_gate.py` exists to
|
||||
catch. Validated: two self-tests (identity, and a 0.55× + 7° similarity) place every
|
||||
hand joint to **5e-16 u** and recover 100% of donor weights; output passes
|
||||
`verify_body_variant.py`; and `rig_pose_gate.py` reports **max error 0.000° over 260
|
||||
(bone, clip, sample) comparisons** under the two most finger-heavy dances.
|
||||
|
||||
Two self-inflicted bugs the similarity test caught, worth not re-introducing: the
|
||||
wrist scale gets applied **twice** if you compose the target's wrist matrix without
|
||||
stripping its own scale first (the forearm ratio was measured in that same scaled
|
||||
space), and the weight transfer must convert donor-local → world → target-local, not
|
||||
compare raw POSITION arrays across two files.
|
||||
|
||||
**Still open on hands:** if AccuRig returns no wrist joint at all once the hands are
|
||||
cut from the bait, `graft_hands.py` aborts — the `--wrist-from-forearm` fallback is
|
||||
specified but not implemented, because what AccuRig actually returns is unknown until
|
||||
step 3 runs.
|
||||
|
||||
## Naming + step 2 status (RESOLVED 2026-08-04, later same day)
|
||||
|
||||
Jeremy raised the hands budget and released the bait step. Built with
|
||||
|
||||
@@ -50,7 +50,7 @@ head of the live chain. Unpin the head's parent once the head is accepted.
|
||||
## Pruning
|
||||
|
||||
```
|
||||
python tools/prune_lane.py characters/female/lena_nude --recursive # dry run
|
||||
python tools/prune_lane.py characters/work/lena --recursive # dry run
|
||||
python tools/prune_lane.py <lane> --apply # delete scratch
|
||||
```
|
||||
|
||||
|
||||
@@ -14,6 +14,17 @@ __pycache__/
|
||||
**/dbg_*/
|
||||
**/probe_*/
|
||||
|
||||
# Per-stage lane output dirs — the SCRATCH tier of working-files.md, in bulk.
|
||||
# characters/work/lena/v02/ alone is 1,019 MB: 15 .blend snapshots of the same
|
||||
# ~883k-vertex mesh (48_headsafe, seamed, reatlased, 52_transplant, 55_fix1/2/3,
|
||||
# rigged, rigged_fix1/2/3, rigged_fullres, ...) plus their .glb exports. Exactly the
|
||||
# growth the rule was written to stop — the lane hit 3.0 GB once before, 2.2 GB of it
|
||||
# .blend files. The 48-55 recipes ARE the history and rebuild any of these from
|
||||
# masters/55_fullres_v02.blend, which is pinned in .lanekeep and tracked.
|
||||
# If a v02 artifact is ever registered in characters/REGISTRY.md, `git add -f` it —
|
||||
# nothing here is registered yet (the v03 body is HELD with known defects).
|
||||
characters/work/*/v0[0-9]/
|
||||
|
||||
# Vendor application install — AccuRig (~1 GB of Reallusion program files).
|
||||
# Re-downloadable from Reallusion; nothing here is authored by us.
|
||||
/accurig/
|
||||
|
||||
@@ -43,10 +43,11 @@ AGENTS.md ← you are here
|
||||
tools/ ← Blender-headless retargeters, loop tools, prop export,
|
||||
md_bridge* (drive Marvelous Designer), tailor/ (garment
|
||||
authoring: recipes, measurements, QC, screenshots)
|
||||
characters/ ← character body models: originals/ (read-only Tripo sources) +
|
||||
per-character working folders (male/mako, female/lena).
|
||||
Naming law in characters/REGISTRY.md — applies to all new
|
||||
derived models
|
||||
characters/ ← character body models: originals/ (read-only Tripo sources),
|
||||
work/<character>/ (live lanes: recipes + masters), and one
|
||||
frozen folder per SHIPPED body (female/lena_base_v01).
|
||||
A folder is born at ship time, not at experiment time —
|
||||
naming law in characters/REGISTRY.md
|
||||
clothing/ ← garment mesh → game-ready outfit GLB (staged Blender pipeline)
|
||||
garments/ ← raw incoming garment sources + design advice notes
|
||||
exchange/ ← files in flight between Mac and PC (see exchange/GUIDE.md)
|
||||
|
||||
@@ -1,87 +1,111 @@
|
||||
# Character Model Registry
|
||||
|
||||
Naming law for `characters/`. **Scope: this system applies only to newly created
|
||||
variants derived from mako and lena** — every such artifact from here on, *wherever
|
||||
it is produced*. (This clause used to read "anything born in this folder", which let
|
||||
the AccuRig rig baits get built into `ariki-game/assets/models/characters/rig-work/`
|
||||
and slip the net entirely; they were moved here 2026-08-06. A character artifact
|
||||
generated in another repo is still governed by this file and still belongs under
|
||||
`characters/`.)
|
||||
Naming law for `characters/`. **Scope: newly created bodies derived from mako and
|
||||
lena** — every such artifact from here on, *wherever it is produced*. A character
|
||||
artifact generated in another repo is still governed by this file and still belongs
|
||||
under `characters/`.
|
||||
|
||||
Legacy models elsewhere — game-side `derived-bodies/`, old intermediates, historical
|
||||
race-sources — keep their names and are NOT registered here; they will eventually be
|
||||
replaced by files that follow this system.
|
||||
race-sources — keep their names and are NOT registered here.
|
||||
|
||||
Modeled on the dance registry (`.agents/wiki/dances/REGISTRY.md`): numbers are never
|
||||
reused, superseded files are archived, never deleted.
|
||||
---
|
||||
|
||||
## Filename grammar
|
||||
## The central rule: a folder is born at ship time
|
||||
|
||||
**A character folder exists only for a body that has shipped to ariki-game.** Not for
|
||||
a variant, not for an experiment, not for a promising candidate. Until it ships, it is
|
||||
lane material and it lives in `characters/work/`.
|
||||
|
||||
```
|
||||
<character>[_<variant>...]_<stage>_<format>_v<NN>.<ext>
|
||||
characters/
|
||||
originals/ immutable delivered sources — never modified, never renamed
|
||||
female/
|
||||
lena_base_v01/ SHIPPED 2026-08-10 <- frozen
|
||||
lena_base_v02/ the next one to ship
|
||||
male/
|
||||
mako_base_v01/ SHIPPED 2026-08-12 <- frozen
|
||||
work/
|
||||
lena/ the live lane: recipes + masters. Grammar-exempt.
|
||||
mako/
|
||||
rig-work/ disposable AccuRig carriers. Grammar-exempt.
|
||||
archive/ superseded SHIPPED folders only
|
||||
```
|
||||
|
||||
- **character** — registered name from the Characters table below (`lena`, `mako`).
|
||||
New characters get a row here before their first file exists.
|
||||
- **variant** — optional sculpt-level descriptors (`elder`, `muscular`). A new
|
||||
variant is a new folder and a new Characters-table row.
|
||||
- **stage** — exactly one token from the closed vocabulary below.
|
||||
- **format** — file format tag matching the extension (`fbx`, `glb`).
|
||||
- **v\<NN\>** — mandatory two-digit version, per-artifact.
|
||||
### Folder grammar
|
||||
|
||||
```
|
||||
<character>_base_v<NN>
|
||||
```
|
||||
|
||||
`NN` is the **ship ordinal** for that character — the 1st shipped lena is
|
||||
`lena_base_v01`, the 2nd is `lena_base_v02`. It counts ships, not attempts. Numbers
|
||||
are monotonic and never reused, even if a ship is later rolled back.
|
||||
|
||||
This replaces the old variant-folder scheme (`lena`, `lena_nude`, `lena_elder`). A
|
||||
nude body, an elder body and a re-sculpt are no longer three folders — they are
|
||||
whichever of them actually ships, numbered in the order they ship. What distinguishes
|
||||
them is recorded in the ship folder's README, not in its name.
|
||||
|
||||
### File grammar inside a ship folder
|
||||
|
||||
```
|
||||
<character>_base_<stage>_<format>_v<NN>.<ext>
|
||||
```
|
||||
|
||||
`NN` is **the folder's ship number**, repeated — not a per-artifact counter. Every
|
||||
file in `lena_base_v01/` ends `_v01`. This is the one real break from the old law
|
||||
(old rule 4: "version is per-artifact"), and it is deliberate: per-artifact counters
|
||||
are what produced ten `decimated_glb_vNN` files sitting next to four
|
||||
`sculpt_glb_vNN` files with no way to tell which went together. Now the version
|
||||
identifies the ship, and everything carrying it belongs to that ship.
|
||||
|
||||
All lowercase snake_case, `[a-z0-9_]` only. No dates, no status words, no hyphens.
|
||||
|
||||
### Stage vocabulary (closed set — extending it is an edit to this table)
|
||||
#### Stage vocabulary (closed set — extending it is an edit to this table)
|
||||
|
||||
| token | meaning |
|
||||
|---|---|
|
||||
| `sculpt` | unrigged full-res mesh (sculpt-level alteration) |
|
||||
| `sculpt` | unrigged full-res mesh |
|
||||
| `decimated` | post-decimation, unrigged |
|
||||
| `tpose` | pose-prep export for a rigging tool |
|
||||
| `accurig` | AccuRig output, CC_Base_* skeleton |
|
||||
| `mixamo` | Mixamo-rigged (legacy lane) |
|
||||
| `accurig` | AccuRig output / AccuRig-skeleton body |
|
||||
| `quatskin` | 65-bone Quaternius UAL game skeleton |
|
||||
| `lod<NN>` | LOD at NN% of tris (appended after `quatskin`) |
|
||||
| `toned` | skin-tone pass (appended after `quatskin`) |
|
||||
|
||||
## The rules
|
||||
### The rules
|
||||
|
||||
1. **Originals are read-only; delivered names immutable** — even misspelled or
|
||||
inconsistent (`female_lena_tripo` keeps its missing `_v1`). Identity is recorded
|
||||
in this registry, never fixed on disk. Renaming would fork identity vs the
|
||||
name-synced mirror in `ariki-game/assets/models/characters/race-sources/` and
|
||||
risk breaking FBX-internal texture paths.
|
||||
2. Every new derived file matches the grammar above and starts with a registered
|
||||
character name. Canonical male spelling is **`mako`** — the AccuRig-era `moka`
|
||||
typo and `moko` are never used in new files.
|
||||
3. Exactly one stage token per filename.
|
||||
4. Version is per-artifact; **ancestry lives in this registry, never in the name**
|
||||
(a `quatskin_v01` built from `decimated_v03` does not encode the parent's version).
|
||||
5. **Status lives in this registry, never in a filename** — no `_candidate`,
|
||||
1. **Originals are read-only; delivered names immutable** — even misspelled
|
||||
(`female_lena_tripo` keeps its missing `_v1`). Identity is recorded here, never
|
||||
fixed on disk: renaming would fork identity against the name-synced mirror in
|
||||
`ariki-game/assets/models/characters/race-sources/` and can break FBX-internal
|
||||
texture paths.
|
||||
2. **A ship folder is frozen on ship.** Nothing inside it is ever edited, re-exported
|
||||
or re-graded. A change of any kind means the next `_vNN` folder. This is what makes
|
||||
the folder a truthful record of what the game actually has.
|
||||
3. **A ship folder holds only what the ship needs**: the shipped body, the inputs that
|
||||
cannot be regenerated by script, a `preview/` render set, and a README. Working
|
||||
`.blend` files, QA renders, probes and superseded stages do not belong here — they
|
||||
are lane material.
|
||||
4. Exactly one stage token per filename.
|
||||
5. **Ancestry lives in this registry and the folder README, never in a filename.**
|
||||
6. **Status lives in this registry, never in a filename** — no `_candidate`,
|
||||
`_final`, `_locked`.
|
||||
6. New geometry identity = new character-variant folder (e.g. `female/lena_elder/`);
|
||||
re-run of the same recipe = version bump. Test: "would the game treat it as a
|
||||
different character/body?" → new variant; "a better attempt at the same thing?"
|
||||
→ bump. Numbers never reused.
|
||||
7. FBX or loose-texture models get a folder named exactly the model basename
|
||||
(e.g. `mako_accurig_fbx_v01/`); loose textures inside are `<character>_<map>.<ext>`
|
||||
(`mako_basecolor.jpg`) — no version chains in texture names. `.fbm/` folders and
|
||||
Tripo UUID internals are never renamed.
|
||||
8. **Boundary law:** snake_case = animation-repo working names; PascalCase `Ariki_*`
|
||||
= game-repo ship names. The rename happens exactly once, at import into
|
||||
`ariki-game/assets/quaternius/derived-bodies/`, and is recorded in the ship-name
|
||||
column below. Never create `Ariki_*` files in this repo.
|
||||
9. Superseded files move to `characters/archive/` — never deleted, never renamed.
|
||||
10. Every new file under `characters/` (outside `archive/`) gets a row in the
|
||||
Derived artifacts table before its first commit.
|
||||
11. **`characters/rig-work/` is exempt from the grammar and from rule 10** — it
|
||||
holds disposable AccuRig rig carriers ("baits"): decimated FBX copies whose
|
||||
only job is to carry a skeleton out of AccuRig and back. They are never
|
||||
shipped and never contribute geometry to a registered artifact (the graft
|
||||
takes geometry from the GLB parent and only the skeleton from the bait), so
|
||||
they get no row of their own — instead the grafted body's **notes** column
|
||||
names the bait it got its skeleton from. Keeping their tool-era names is
|
||||
deliberate: they must stay recognisable as throwaway. See that folder's
|
||||
`README.md`.
|
||||
7. FBX or loose-texture models get a folder named exactly the model basename;
|
||||
loose textures inside are `<character>_<map>.<ext>` — no version chains in texture
|
||||
names. `.fbm/` folders and Tripo UUID internals are never renamed.
|
||||
8. **Boundary law:** snake_case = animation-repo working names; PascalCase `Ariki_*` =
|
||||
game-repo ship names. The rename happens exactly once, at import into
|
||||
`ariki-game/assets/quaternius/derived-bodies/`, and is recorded in the ship table
|
||||
below. Never create `Ariki_*` files in this repo.
|
||||
9. **A superseded ship folder moves to `characters/archive/` — never deleted.** This
|
||||
applies to ship folders only. Lane material is governed by
|
||||
`.agents/rules/working-files.md` and is deleted freely.
|
||||
10. Every ship folder gets a row in the Ships table before its first commit.
|
||||
11. **`characters/work/` and `characters/rig-work/` are exempt from the grammar and
|
||||
from rule 10.** They hold recipes, masters and disposable rig carriers. See
|
||||
`.agents/rules/working-files.md` and `rig-work/README.md`.
|
||||
|
||||
---
|
||||
|
||||
## Originals
|
||||
|
||||
@@ -90,18 +114,17 @@ Never modified, never renamed, never resaved in place. Verify with
|
||||
|
||||
| file | sha256 | delivered by | tool | date | identity note |
|
||||
|---|---|---|---|---|---|
|
||||
| `originals/male/male_base_bald_tripo_v1.fbx` | `f7c884bb8b8a71351b1448fa355658459a61a2537fdd48f9c72e52709693608c` | Özlem | Tripo | 2026-08-04 (copied from ariki-game race-sources) | mako generation 1, unrigged A-pose ~1.9M tris |
|
||||
| `originals/male/male_base_bald_tripo_v1.glb` | `59df0de4b097428324f6b9048e0eeb7e8e034062aa8c8dad7e1eba1937be8aca` | Özlem | Tripo | 2026-08-04 (copied from ariki-game race-sources) | mako generation 1 |
|
||||
| `originals/female/female_lena_tripo.fbx` | `c3a7cb1bf4958b27bd81b6bf73fd132c88f7a582d045c8936992d5d22bfd4e8e` | Özlem | Tripo | 2026-08-04 (copied from ariki-game race-sources) | lena generation 1 — delivered name has no `_v1`; treat as v1 |
|
||||
| `originals/female/female_lena_tripo.glb` | `de17a1e7d56cefc3e07596bfb08efc65b41d04fac709c27b76500729e4fbb4b8` | Özlem | Tripo | 2026-08-04 (copied from ariki-game race-sources) | lena generation 1 |
|
||||
| `originals/female/female_lena_leafbikini_tripo.glb` | `977104ba4338ca76e04696d130dd21f0924a5614baa5f1ab8339d5c07666cf29` | Özlem | Tripo | 2026-08-13 (copied from ariki-game race-sources) | lena_leafbikini generation 1 — lena wearing a sculpted-in leaf bikini |
|
||||
| `originals/female/female_lena_leafbikini_tripo_fbx/tripo_convert_9bb20cb8-9dff-4b8a-99c1-f087540d4aa4.fbx` | `d11693f331d144c64520ffe89b7e755ccd44e7aa0398a8808fc97664975aaf3a` | Özlem | Tripo | 2026-08-13 (copied from ariki-game race-sources) | lena_leafbikini generation 1 — FBX delivery; lives in a `_fbx/` folder with its `.fbm/` textures, UUID name kept per rule 7 |
|
||||
| `originals/male/male_base_bald_tripo_v1.fbx` | `f7c884bb8b8a71351b1448fa355658459a61a2537fdd48f9c72e52709693608c` | Özlem | Tripo | 2026-08-04 | mako generation 1, unrigged A-pose ~1.9M tris |
|
||||
| `originals/male/male_base_bald_tripo_v1.glb` | `59df0de4b097428324f6b9048e0eeb7e8e034062aa8c8dad7e1eba1937be8aca` | Özlem | Tripo | 2026-08-04 | mako generation 1 |
|
||||
| `originals/female/female_lena_tripo.fbx` | `c3a7cb1bf4958b27bd81b6bf73fd132c88f7a582d045c8936992d5d22bfd4e8e` | Özlem | Tripo | 2026-08-04 | lena generation 1 — delivered name has no `_v1`; treat as v1 |
|
||||
| `originals/female/female_lena_tripo.glb` | `de17a1e7d56cefc3e07596bfb08efc65b41d04fac709c27b76500729e4fbb4b8` | Özlem | Tripo | 2026-08-04 | lena generation 1 |
|
||||
| `originals/female/female_lena_leafbikini_tripo.glb` | `977104ba4338ca76e04696d130dd21f0924a5614baa5f1ab8339d5c07666cf29` | Özlem | Tripo | 2026-08-13 | lena_leafbikini generation 1 — lena wearing a sculpted-in leaf bikini |
|
||||
| `originals/female/female_lena_leafbikini_tripo_fbx/tripo_convert_9bb20cb8-9dff-4b8a-99c1-f087540d4aa4.fbx` | `d11693f331d144c64520ffe89b7e755ccd44e7aa0398a8808fc97664975aaf3a` | Özlem | Tripo | 2026-08-13 | lena_leafbikini generation 1 — FBX delivery; lives in a `_fbx/` folder with its `.fbm/` textures, UUID name kept per rule 7 |
|
||||
|
||||
### Canonical-name aliases
|
||||
|
||||
Byte-identical copies of the originals under grammar-conforming names, so tools can
|
||||
reference canonical names without touching the immutable delivered files. Same
|
||||
read-only guard, same checksums as their sources above.
|
||||
Byte-identical copies under grammar-conforming names, so tools can reference canonical
|
||||
names without touching the immutable delivered files.
|
||||
|
||||
| file | byte-identical to | date |
|
||||
|---|---|---|
|
||||
@@ -112,24 +135,68 @@ read-only guard, same checksums as their sources above.
|
||||
|
||||
## Characters
|
||||
|
||||
| character | gender | parent original | description | status |
|
||||
|---|---|---|---|---|
|
||||
| `mako` | male | `male_base_bald_tripo_v1` | bald base male, Tripo sculpt. Spelling history: game wiki "Mako", AccuRig-era typo "moka" — canonical filename token is `mako` | active |
|
||||
| `lena` | female | `female_lena_tripo` | base female, Tripo sculpt | active |
|
||||
| `lena_nude` | female | `female_lena_tripo` | lena with the sculpted-in underwear removed: no bra, no briefs, breasts with volume and no nipples, featureless crotch. Separate variant rather than a version of `lena` because the game selects it as a different body (rule 6) | active |
|
||||
| `lena_leafbikini` | female | `female_lena_leafbikini_tripo` | lena wearing a sculpted-in leaf bikini garment, Tripo sculpt. Separate variant rather than a version of `lena` because the game selects it as a distinct clothed body (rule 6) | active |
|
||||
| character | gender | parent original | ships so far |
|
||||
|---|---|---|---|
|
||||
| `lena` | female | `female_lena_tripo` | `lena_base_v01` |
|
||||
| `mako` | male | `male_base_bald_tripo_v1` | `mako_base_v01` |
|
||||
| `lena_leafbikini` | female | `female_lena_leafbikini_tripo` | — (none yet; ancestor registered 2026-08-13, derivatives pending) |
|
||||
|
||||
## Derived artifacts
|
||||
Spelling history for the male: game wiki "Mako", AccuRig-era typo "moka", also "moko".
|
||||
Canonical filename token is **`mako`**; the others are never used in new files.
|
||||
|
||||
Status vocabulary: `wip / candidate / approved / shipped / archived / rejected`.
|
||||
---
|
||||
|
||||
| name | character | parent file | stage | tool/script | date | status | ship name | notes |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| `female/lena_nude/lena_nude_quatskin_glb_v01.glb` | `lena_nude` | game-side `Ariki_Female_QuatSkin.glb` (+ `Ariki_Female_QuatSkin_ChestV1.glb` as breast-shape donor) | `quatskin` | `tools/make_lena_nude_body.py` (`--size 1.0`) | 2026-08-04 | candidate | `Ariki_Female_QuatSkin_Nude.glb` (not yet released) | sha256 `931c0948a3b0201dbfbdd4d2671c3ae41688afc592a29e332bc2bab234aa1ad3`. Texture embedded, internal image name `lena_nude_basecolor` (no ship name baked inside a WIP asset). 31,670 v / 65 joints / 1.777 m; passes `tools/verify_body_variant.py` against the canonical body. Vertex count is below stock by design — the base mesh's open slits are welded shut, since the painted underwear had been hiding them. `--size` is breast volume; a different size is a version bump, not a new variant |
|
||||
| `female/lena_nude/lena_nude_basecolor.png` | `lena_nude` | game-side `Ariki_Female_QuatSkin_Lena_Body_Toned.png` | — (texture) | `tools/_bake_nude_body_texture.py` | 2026-08-04 | candidate | `Ariki_Female_QuatSkin_Nude_Lena_Body_Toned_nude.png` (not yet released) | sha256 `576fcf614cfcb19befd5bd4fb57210b526db2754f7d35468b21252f31528c9f2`. 4096² albedo with bra and briefs inpainted out to uniform skin, no tan line. Loose copy for iteration; the shipped GLB embeds it. Unversioned per rule 7 |
|
||||
| `female/lena_nude/lena_nude_sculpt_glb_v01.glb` | `lena_nude` | `originals/female/female_lena_tripo.glb` (hires, pre-decimation) + game-side `Ariki_Male_QuatBody_D_RigCandidate.glb` (crotch donor) | `sculpt` | `female/lena_nude/hires_claude/` pipeline (00 weld → 03 sculpt → 06 crotch+membrane → 05/05b texture+grade → 08 export); reference `female/lena_nude/reference_breasts.jpg` | 2026-08-05 | candidate | — (game asset derives via decimation later) | sha256 `cb781cf376dc046b108d1c00e2368e0ed7e74fde5a783bedc6fc435818e4b2c7`. 953,966 v / 1,907,931 tris / 0.9792 units tall, unrigged. Full-density nude: garment membrane-removed, C-cup round breasts (apex projection 0.040 units, cleavage gap 7 mm), no nipples; crotch = male-GLB donor + bi-harmonic gusset heal (featureless). Basecolor + normal + rm embedded; fabric repainted by 3D-nearest skin fill, rosy chest V graded out to uniform skin. Masters: `hires_claude/06_final.blend` (geometry), `07_polished.blend` (textured) |
|
||||
| `female/lena_nude/lena_nude_sculpt_glb_v02.glb` | `lena_nude` | v01 geometry + `originals/female/female_lena_tripo.glb` textures | `sculpt` | `hires_claude/05_texture.py` in `patch` mode (garment texels only, mirror-averaged fill from her own skin) + `06f` fold melt; master `hires_claude/10_patch.blend` | 2026-08-05 | candidate | — | sha256 `4a8c811305352a5fc60a58f6146c6797de03131da56dbd4073f90580d2e59e9b`. Same nude geometry as v01 but ORIGINAL skin everywhere (rosy chest V, arm/leg tones kept); only the bra/briefs/strap/crotch texels are refilled. Jeremy's preference after v01's global tone grade changed too much of her look |
|
||||
| `female/lena_nude/lena_nude_sculpt_glb_v04.glb` | `lena_nude` | `lena_nude_sculpt_glb_v03.glb` (master `hires_claude/29b_final.blend`) | `sculpt` | `hires_claude/33_bust_variants.py` (size study) → `34_apply_bust.py --k 0.5`; master `hires_claude/34_v04.blend` | 2026-08-06 | candidate | — | sha256 `88931098f27915f5a498a1bccc014e800af7e8fff3e1d43faf8b97616c307910`. 860,389 v / 1,721,805 tris, geometry identical to v03 except the bust. **Guided by the approved concept turnaround** (`exchange/INBOX/lena-base-turnaround`, GPT Image 2, approved by Can 2026-08-06): `32_ref_measure.py` compared reference silhouette against mesh and found proportions already agree — 6.43 vs 6.08 heads, waist/hip 0.504 vs 0.505, widths within 5% — with the bust the one real gap. Breast forms rescaled to 50% of their projection off a bi-harmonic chest wall, taking bust projection +0.0210 → **+0.0155 H against the reference's +0.0156**. 68,781 verts moved (max 27.8 mm, median 5.5 mm in-region), zero movement outside the chest region, height unchanged. Texture/UVs untouched from v03. Caveats: the projection metric saturates below k≈0.5 (k=0.35 also reads +0.0154) and the reference reading may be inflated by the T-pose arm crossing the chest in the side view, so 0.5 is a floor rather than a midpoint; front renders cannot distinguish sizes (frontal key light flattens the chest) — judge from the side |
|
||||
| `female/lena_nude/lena_nude_decimated_glb_v01.glb` | `lena_nude` | `lena_nude_sculpt_glb_v03.glb` (master `hires_claude/29b_final.blend`) | `decimated` | `hires_claude/30_decimate.py` (COLLAPSE ratio **0.037728**, bisected to hit the vertex target) + `30b_fix_winding.py` | 2026-08-06 | candidate | — | sha256 `b11940bb46732ae0a60e1624e88e76fa48dcc3e3af49a6363348521bb7c52cc8`. 31,964 v / 64,957 tris in Blender (46,810 / 64,957 as exported, UV seams split verts) — 3.72% of the sculpt, matched to `lena_nude_quatskin_glb_v01.glb`'s 31,670 v at Jeremy's request. Unrigged, no skins, transforms applied; basecolor + normal + rm embedded. Height preserved to +0.12 mm, bbox unchanged. **Built as input for the re-atlas lane** (`24_seams.py`): collapse destroys Tripo's 5,870-chart UV layout, so the carried TEXCOORD_0 smears badly and is expected to be replaced, not reused. Residual: 25 backfacing tris of 64,957 (down from 61; the remainder sit in non-manifold knots where consistent-orientation oscillates), plus the sculpt's inherited 835 boundary / 1649 non-manifold edges |
|
||||
| `female/lena_nude/lena_nude_sculpt_glb_v03.glb` | `lena_nude` | v02 chain head `hires_claude/10_welded.blend` | `sculpt` | `hires_claude/` 17 line heal → 26 median despeckle + cleavage fillet → 25 flipped-face repair → 29 on-body tone levelling → 08 export; masters `27_clean.blend` (geometry), `29b_final.blend` (textured) | 2026-08-06 | candidate | — | sha256 `7a8fe4c45d3af6acb7b39c2fac5fcc2aadf910f8d61a4a7d046911125a6e8baa`. 860,389 v / 1,721,805 tris. Jeremy's three v02 notes: (1) cut lines — reduced, not gone: shading kinks >12° 9704 → ~6900, ribcage/thigh lines gone frontally, waistband + underbust still legible under raking light; (2) discolouration — the pale bra/briefs panels are gone, on-body tone gap patch-vs-skin 0.0102 → 0.0068 luma; (3) cleavage — sternum fillet radius 9.5 mm → no sharp sample, notch depth 44 → 31 mm. Also 1511 → 10 flipped faces (the black dashes). Still open: 1649 non-manifold + 830 boundary edges (every fill refused them). See the lane README for the four dead ends this version ruled out |
|
||||
| `female/lena_nude/lena_nude_decimated_glb_v02.glb` | `lena_nude` | `lena_nude_decimated_glb_v01.glb` (geometry byte-for-byte unchanged) | `decimated` | `hires_claude/24_seams.py` (anatomical seams + minimum-stretch unwrap) then `hires_claude/31_reatlas.py` (map resample) | 2026-08-06 | candidate | — | sha256 `cd3d53550470ae5a1958dc473ce11793f746b13f78ac911e6d426b6f7b9f5825`. **Re-atlas only — same 31,964 v / 64,957 tris, same shape, same material, unrigged.** Replaces Tripo's 5,870-chart atlas with 14 anatomical charts (torso, head, 2 arms, 2 legs, 2 hands ×2, 2 feet ×2): coverage 62.0→68.6%, texel density spread 1.8x→1.00x, per-triangle area-scale p95 2.55→1.77 / p99 4.25→2.27. Exported vertex count drops 46,810→42,980 because the new layout has far fewer UV seams. Maps resampled into the new layout at 4096², normal map rotated per face into its new tangent frame (p50 81°). Renders identical to v01: mean pixel \|Δ\| 0.0012, p99 0.0118 over five views (`hires_claude/33_ab.py`). Version bump, not a new variant, per rule 6 — same body, better atlas |
|
||||
## Ships
|
||||
|
||||
Status vocabulary: `shipped / superseded / rolled-back`.
|
||||
|
||||
| folder | character | date | status | ship name in ariki-game | sha256 (repo-side body) |
|
||||
|---|---|---|---|---|---|
|
||||
| `female/lena_base_v01` | `lena` | 2026-08-10 | shipped | `Ariki_Female_QuatSkin_Nude.glb`, and swapped into `Ariki_Female_QuatSkin.glb` | `0d602accef0992a6a0dfffa5b73b1f9b702400df5258ff7517f786f11050f4d0` |
|
||||
| `male/mako_base_v01` | `mako` | 2026-08-12 | shipped | `Ariki_Male_Mako.glb` (male Ariki default, lineage `MALE-MAKO-FULLHEAD-V1`) | `220714a2fb6283188be3834214877204533fde78de9a6ffa03ed85e1167735d3` |
|
||||
|
||||
### `lena_base_v01` — what it is
|
||||
|
||||
Lena with the sculpted-in underwear removed (no bra, no briefs, breasts with volume
|
||||
and no nipples, featureless crotch), athletic definition in the normal map, on the
|
||||
65-bone Quaternius contract. She is now the game's female base body, not a side
|
||||
variant — `Ariki_Female_QuatSkin.glb` was swapped to her on 2026-08-10, with the
|
||||
previous body kept as `Ariki_Female_QuatSkin_PRESWAP_2026-08-10.glb.bak`.
|
||||
|
||||
Ancestry, condensed from the 20 intermediate stages that produced her (all pruned
|
||||
2026-08-11; the recipes in `work/lena/` regenerate any of them from
|
||||
`originals/female/female_lena_tripo.glb`):
|
||||
|
||||
| step | what happened |
|
||||
|---|---|
|
||||
| sculpt | garment membrane-removed, crotch healed with a male-GLB donor + bi-harmonic gusset, bust rescaled to match the concept turnaround approved by Can 2026-08-06 → 860,389 v |
|
||||
| decimate | COLLAPSE 0.037728 → 31,964 v (3.72% of the sculpt) |
|
||||
| re-atlas | Tripo's 5,870-chart UV soup replaced with 14 anatomical charts; maps resampled at 4096², normal map rotated per face into its new tangent frame |
|
||||
| refill | breast/underwear texels re-authored by a harmonic solve with Dirichlet boundaries, so the fill equals her real skin at the mask edge — no ghost outline |
|
||||
| despeckle | two defects, not one: 892 folded edges collapsed/dissolved (geometry) + 44k local-contrast texels inpainted (Tripo scan noise) → 31,111 v |
|
||||
| musculature | anatomy into the NORMAL map only, geometry untouched; 3D-space gradient projected into per-texel tangent frames so the back midline seam does not tear |
|
||||
| rig | AccuRig 1.10.1822.1, manual. 101 CC_Base_* bones. Vertex order preserved, so weights grafted **index-exact**, not nearest-surface — this is what avoided the mangled fingers of the July lane |
|
||||
| quatskin | `ariki-game/tools/make_lena_nude_quatskin_accurig.py` → 65 joints, 1.777 m, rigid-mitt fingers |
|
||||
|
||||
**Departure from the approved concept:** the turnaround Can approved is a smooth
|
||||
mannequin with no muscle definition. The shipped body has athletic definition
|
||||
(`42_musculature.py` strength 1.0, `SPINE=0.34`). Flagged here so it is a known
|
||||
decision rather than a drift.
|
||||
|
||||
### `mako_base_v01` — what it is
|
||||
|
||||
The Tripo male sculpt with the body decimated and the **head untouched** (158,575 tris,
|
||||
full scan resolution, cut at the neck at 86.5% of height): body 24k / hands 40k. First
|
||||
mako-geometry body ever in the game — it replaced the July graft body (Superhero_Male +
|
||||
Lena's head, `MALE-MAKO-D-V1`, kept on disk as comparison) as the male Ariki default.
|
||||
|
||||
| step | what happened |
|
||||
|---|---|
|
||||
| decimate | body/hands only (`work/mako/01_decimate_fullhead.py`); head gated bit-identical → 222,575 tris |
|
||||
| rig | AccuRig 1.10, manual, on a 1.700 m materials-free carrier → 101 CC_Base_* bones |
|
||||
| graft | index-exact (122,062 verts both sides), mesh moved 0 mm (`work/mako/02_graft.py`) |
|
||||
| quatskin | `ariki-game/tools/make_mako_fullhead_quatskin_accurig.py` `HEAD_SAFE=1` → 65 joints, 1.818 m, rigid mitts, body-only weld + body-only split-normals clear |
|
||||
|
||||
**Known decisions:** height 1.818 m is provisional (IB-lane taller-than-Lena convention,
|
||||
not eye-locked); ship name avoids the `QuatSkin`/`QuatBody` tokens because
|
||||
`PlayerController` string-gates on them; no male LOD1 yet. Details in the folder README.
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
# lena_base_v01 — SHIPPED 2026-08-10
|
||||
|
||||
**Frozen.** Nothing in this folder is ever edited or re-exported (REGISTRY rule 2).
|
||||
Any change at all becomes `lena_base_v02`.
|
||||
|
||||
The game's female base body. Nude sculpt (no bra, no briefs, no nipples, featureless
|
||||
crotch), athletic definition in the normal map, 65-bone Quaternius skeleton.
|
||||
|
||||
## Contents
|
||||
|
||||
| file | what |
|
||||
|---|---|
|
||||
| `lena_base_accurig_glb_v01.glb` | the body as it left this repo — mesh + atlas + AccuRig skeleton, 31,111 v / 101 joints. `doubleSided=true` |
|
||||
| `rig/lena_base_accurig_fbx_v01/` | **irreplaceable.** Output of a manual AccuRig 1.10.1822.1 session — no script regenerates this. Settings JSON kept alongside as provenance |
|
||||
| `rig/lena_base_tpose_fbx_v01.fbx` | the carrier fed *into* that AccuRig session. Regenerable (`45_accurig_export.py`) but kept so the rig is reproducible end to end |
|
||||
| `preview/` | renders of the shipped body |
|
||||
|
||||
## Where it went
|
||||
|
||||
Converted to the 65-bone contract by `ariki-game/tools/make_lena_nude_quatskin_accurig.py`
|
||||
and imported as:
|
||||
|
||||
- `Ariki_Female_QuatSkin_Nude.glb` — sha256 `75a51668fe6f084b60995fd257afd2ee8e4e6317d1676ff793675ca018f237b0`
|
||||
- `Ariki_Female_QuatSkin.glb` — same body, swapped in on 2026-08-10. The body it
|
||||
replaced is kept as `Ariki_Female_QuatSkin_PRESWAP_2026-08-10.glb.bak`.
|
||||
|
||||
Textures ship loose as `Ariki_Female_QuatSkin_Nude_athletic_v04_{base,normal,rm}.jpg`.
|
||||
|
||||
## Two facts that bite
|
||||
|
||||
- **`doubleSided=true` is load-bearing.** Patches of the mesh are wound inward; a
|
||||
single-sided material culls them into grey holes across her face and underbust.
|
||||
- **Two corrections live in the graft**, both solved by least-squares fit rather than
|
||||
hardcoded: the T-pose carrier was exported at 1.700 m (scale 0.576058) and AccuRig
|
||||
shifted the body ~11.77 mm. Same family as the July lane's 4 cm offset trap.
|
||||
|
||||
## Rebuilding / continuing
|
||||
|
||||
Recipes and masters are in `characters/work/lena/`. Start from
|
||||
`masters/athletic_v04.blend` (the game-res head, 31,111 v) or `masters/34_v04.blend`
|
||||
(the full-res sculpt head, 860,389 v). Full stage-by-stage history including the dead
|
||||
ends is in `work/lena/README_lane_history.md`.
|
||||
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
@@ -0,0 +1,429 @@
|
||||
{
|
||||
"rename this": {
|
||||
"Version": "1.10.1822.1",
|
||||
"Scene": {
|
||||
"Name": true,
|
||||
"SupportShaderSelect": true
|
||||
},
|
||||
"Export Directory": "C:/Users/Jeremy/tinqs/animation/characters/female/lena_nude/rig",
|
||||
"Object": {
|
||||
"rename this": {
|
||||
"Generation": "AccuRig",
|
||||
"Physics": {
|
||||
"Collision Shapes": {
|
||||
"RL_BoneRoot": {},
|
||||
"CC_Base_Hip": {},
|
||||
"CC_Base_Pelvis": {},
|
||||
"CC_Base_L_Thigh": {},
|
||||
"CC_Base_L_Calf": {},
|
||||
"CC_Base_L_Foot": {},
|
||||
"CC_Base_L_ToeBaseShareBone": {},
|
||||
"CC_Base_L_ToeBase": {},
|
||||
"CC_Base_L_PinkyToe1": {},
|
||||
"CC_Base_L_RingToe1": {},
|
||||
"CC_Base_L_MidToe1": {},
|
||||
"CC_Base_L_IndexToe1": {},
|
||||
"CC_Base_L_BigToe1": {},
|
||||
"CC_Base_L_CalfTwist01": {},
|
||||
"CC_Base_L_CalfTwist02": {},
|
||||
"CC_Base_L_KneeShareBone": {},
|
||||
"CC_Base_L_ThighTwist01": {},
|
||||
"CC_Base_L_ThighTwist02": {},
|
||||
"CC_Base_R_Thigh": {},
|
||||
"CC_Base_R_ThighTwist01": {},
|
||||
"CC_Base_R_ThighTwist02": {},
|
||||
"CC_Base_R_Calf": {},
|
||||
"CC_Base_R_Foot": {},
|
||||
"CC_Base_R_ToeBase": {},
|
||||
"CC_Base_R_PinkyToe1": {},
|
||||
"CC_Base_R_BigToe1": {},
|
||||
"CC_Base_R_IndexToe1": {},
|
||||
"CC_Base_R_MidToe1": {},
|
||||
"CC_Base_R_RingToe1": {},
|
||||
"CC_Base_R_ToeBaseShareBone": {},
|
||||
"CC_Base_R_KneeShareBone": {},
|
||||
"CC_Base_R_CalfTwist01": {},
|
||||
"CC_Base_R_CalfTwist02": {},
|
||||
"CC_Base_Waist": {},
|
||||
"CC_Base_Spine01": {},
|
||||
"CC_Base_Spine02": {},
|
||||
"CC_Base_NeckTwist01": {},
|
||||
"CC_Base_NeckTwist02": {},
|
||||
"CC_Base_Head": {},
|
||||
"CC_Base_FacialBone": {},
|
||||
"CC_Base_JawRoot": {},
|
||||
"CC_Base_Tongue01": {},
|
||||
"CC_Base_Tongue02": {},
|
||||
"CC_Base_Tongue03": {},
|
||||
"CC_Base_Teeth02": {},
|
||||
"CC_Base_R_Eye": {},
|
||||
"CC_Base_L_Eye": {},
|
||||
"CC_Base_UpperJaw": {},
|
||||
"CC_Base_Teeth01": {},
|
||||
"CC_Base_L_Clavicle": {},
|
||||
"CC_Base_L_Upperarm": {},
|
||||
"CC_Base_L_Forearm": {},
|
||||
"CC_Base_L_ForearmTwist01": {},
|
||||
"CC_Base_L_ForearmTwist02": {},
|
||||
"CC_Base_L_ElbowShareBone": {},
|
||||
"CC_Base_L_Hand": {},
|
||||
"CC_Base_L_Mid1": {},
|
||||
"CC_Base_L_Mid2": {},
|
||||
"CC_Base_L_Mid3": {},
|
||||
"CC_Base_L_Index1": {},
|
||||
"CC_Base_L_Index2": {},
|
||||
"CC_Base_L_Index3": {},
|
||||
"CC_Base_L_Ring1": {},
|
||||
"CC_Base_L_Ring2": {},
|
||||
"CC_Base_L_Ring3": {},
|
||||
"CC_Base_L_Pinky1": {},
|
||||
"CC_Base_L_Pinky2": {},
|
||||
"CC_Base_L_Pinky3": {},
|
||||
"CC_Base_L_Thumb1": {},
|
||||
"CC_Base_L_Thumb2": {},
|
||||
"CC_Base_L_Thumb3": {},
|
||||
"CC_Base_L_UpperarmTwist01": {},
|
||||
"CC_Base_L_UpperarmTwist02": {},
|
||||
"CC_Base_R_Clavicle": {},
|
||||
"CC_Base_R_Upperarm": {},
|
||||
"CC_Base_R_UpperarmTwist01": {},
|
||||
"CC_Base_R_UpperarmTwist02": {},
|
||||
"CC_Base_R_Forearm": {},
|
||||
"CC_Base_R_ForearmTwist01": {},
|
||||
"CC_Base_R_ForearmTwist02": {},
|
||||
"CC_Base_R_ElbowShareBone": {},
|
||||
"CC_Base_R_Hand": {},
|
||||
"CC_Base_R_Mid1": {},
|
||||
"CC_Base_R_Mid2": {},
|
||||
"CC_Base_R_Mid3": {},
|
||||
"CC_Base_R_Ring1": {},
|
||||
"CC_Base_R_Ring2": {},
|
||||
"CC_Base_R_Ring3": {},
|
||||
"CC_Base_R_Thumb1": {},
|
||||
"CC_Base_R_Thumb2": {},
|
||||
"CC_Base_R_Thumb3": {},
|
||||
"CC_Base_R_Index1": {},
|
||||
"CC_Base_R_Index2": {},
|
||||
"CC_Base_R_Index3": {},
|
||||
"CC_Base_R_Pinky1": {},
|
||||
"CC_Base_R_Pinky2": {},
|
||||
"CC_Base_R_Pinky3": {},
|
||||
"CC_Base_R_RibsTwist": {},
|
||||
"CC_Base_R_Breast": {},
|
||||
"CC_Base_L_RibsTwist": {},
|
||||
"CC_Base_L_Breast": {},
|
||||
"meshes_0_": {}
|
||||
}
|
||||
},
|
||||
"ExpressionSet": "4",
|
||||
"Expression": {
|
||||
"V_None": {
|
||||
"Bones": {}
|
||||
},
|
||||
"V_Open": {
|
||||
"Bones": {}
|
||||
},
|
||||
"V_Explosive": {
|
||||
"Bones": {}
|
||||
},
|
||||
"V_Dental_Lip": {
|
||||
"Bones": {}
|
||||
},
|
||||
"V_Tight_O": {
|
||||
"Bones": {}
|
||||
},
|
||||
"V_Tight": {
|
||||
"Bones": {}
|
||||
},
|
||||
"V_Wide": {
|
||||
"Bones": {}
|
||||
},
|
||||
"V_Affricate": {
|
||||
"Bones": {}
|
||||
},
|
||||
"V_Lip_Open": {
|
||||
"Bones": {}
|
||||
},
|
||||
"V_Tongue_up": {
|
||||
"Bones": {}
|
||||
},
|
||||
"V_Tongue_Raise": {
|
||||
"Bones": {}
|
||||
},
|
||||
"V_Tongue_Out": {
|
||||
"Bones": {}
|
||||
},
|
||||
"V_Tongue_Narrow": {
|
||||
"Bones": {}
|
||||
},
|
||||
"V_Tongue_Lower": {
|
||||
"Bones": {}
|
||||
},
|
||||
"V_Tongue_Curl_U": {
|
||||
"Bones": {}
|
||||
},
|
||||
"V_Tongue_Curl_D": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Brow_Raise_Inner_L": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Brow_Raise_Inner_R": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Brow_Raise_Outer_L": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Brow_Raise_Outer_R": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Brow_Drop_L": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Brow_Drop_R": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Nose_Sneer_L": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Nose_Sneer_R": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Cheek_Raise_L": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Cheek_Raise_R": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Cheek_Puff_L": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Cheek_Puff_R": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Head_Turn_Up": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Head_Turn_Down": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Head_Turn_L": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Head_Turn_R": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Head_Tilt_L": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Head_Tilt_R": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Head_L": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Head_R": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Head_Forward": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Head_Backward": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Jaw_Open": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Jaw_Forward": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Jaw_L": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Jaw_R": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Tongue_Out": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Tongue_Up": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Tongue_Down": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Tongue_Tip_Up": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Tongue_Tip_Down": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Tongue_Narrow": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Tongue_Wide": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Tongue_Roll": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Tongue_L": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Tongue_R": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Tongue_Bulge_L": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Tongue_Bulge_R": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Eye_Blink_L": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Eye_Blink_R": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Eye_Squint_L": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Eye_Squint_R": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Eye_Wide_L": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Eye_Wide_R": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Eye_L_Look_L": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Eye_R_Look_L": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Eye_L_Look_R": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Eye_R_Look_R": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Eye_L_Look_Up": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Eye_R_Look_Up": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Eye_L_Look_Down": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Eye_R_Look_Down": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Mouth_Smile_L": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Mouth_Smile_R": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Mouth_Frown_L": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Mouth_Frown_R": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Mouth_Stretch_L": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Mouth_Stretch_R": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Mouth_Dimple_L": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Mouth_Dimple_R": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Mouth_Press_L": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Mouth_Press_R": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Mouth_Pucker": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Mouth_Funnel": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Mouth_Roll_In_Upper": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Mouth_Roll_In_Lower": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Mouth_L": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Mouth_R": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Mouth_Shrug_Upper": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Mouth_Shrug_Lower": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Mouth_Up_Upper_L": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Mouth_Up_Upper_R": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Mouth_Down_Lower_L": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Mouth_Down_Lower_R": {
|
||||
"Bones": {}
|
||||
},
|
||||
"Mouth_Close": {
|
||||
"Bones": {}
|
||||
}
|
||||
},
|
||||
"Constraint": {},
|
||||
"Meshes": {
|
||||
"meshes_0_": {
|
||||
"SubD Level": 0,
|
||||
"Materials": {
|
||||
"Default": {
|
||||
"Material Type": "Pbr",
|
||||
"MultiUV Index": 0,
|
||||
"Two Side": true,
|
||||
"Diffuse Color": [
|
||||
255.0,
|
||||
255.0,
|
||||
255.0
|
||||
],
|
||||
"Ambient Color": [
|
||||
255.0,
|
||||
255.0,
|
||||
255.0
|
||||
],
|
||||
"Specular Color": [
|
||||
255.0,
|
||||
255.0,
|
||||
255.0
|
||||
],
|
||||
"Opacity": 1.0,
|
||||
"Self Illumination": 0.0,
|
||||
"Textures": {},
|
||||
"Resource Textures": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
# Handoff: decimated nude Lena → retexture / re-atlas lane
|
||||
|
||||
**2026-08-06.** Jeremy asked for the healed nude Lena decimated to the game-body budget and passed
|
||||
to the retexture / re-atlas work, "to see how they do".
|
||||
|
||||
## The file
|
||||
|
||||
`characters/female/lena_nude/lena_nude_decimated_glb_v01.glb`
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| sha256 | `b11940bb46732ae0a60e1624e88e76fa48dcc3e3af49a6363348521bb7c52cc8` |
|
||||
| parent | `lena_nude_sculpt_glb_v03.glb` (master `hires_claude/29b_final.blend`) |
|
||||
| recipe | `hires_claude/30_decimate.py` then `30b_fix_winding.py` |
|
||||
| ratio | Decimate **COLLAPSE 0.037728** — bisected, not guessed |
|
||||
| density | 31,964 v / 64,957 tris in Blender (46,810 v as exported; UV seams split verts) |
|
||||
| why that density | matched to the shipped `lena_nude_quatskin_glb_v01.glb` (31,670 v), Jeremy's call |
|
||||
| rig | none — unrigged, no skins, transforms applied |
|
||||
| maps | basecolor + normal + rm embedded (3 images, wired baseColor / metallicRoughness / normal) |
|
||||
|
||||
Ready to load directly: `24_seams.py` accepts a `.glb` as its first argument and applies transforms
|
||||
itself, so this drops in with no preparation.
|
||||
|
||||
## Read this before you spend a pass on the UVs
|
||||
|
||||
**The carried `TEXCOORD_0` is not worth preserving.** Collapse decimation shreds Tripo's
|
||||
5,870-chart atlas — in the textured review render (`hires_claude/review_30/chest_tex_0.png`) the
|
||||
transplanted skin grain smears into streaks and the throat picks up a red blotch. That is expected
|
||||
and is exactly the problem the ~14-chart anatomical atlas solves. Judge this mesh from
|
||||
`review_30/full_clay_0.png` (clay, no material) and treat the UVs as scrap.
|
||||
|
||||
**Decimation improved one thing.** The waistband/underbust lines that survive on the hires sculpt
|
||||
are nearly invisible at 32k — the collapse averaged them out. So the residual line problem is a
|
||||
hires-only concern; don't go hunting for it here.
|
||||
|
||||
## Known defects being handed over
|
||||
|
||||
- **25 backfacing triangles** of 64,957. Down from 61 via `30b_fix_winding.py`, but the remainder
|
||||
sit in non-manifold knots where `normals_make_consistent` oscillates (61 → 260 → 39 → 25 → 25).
|
||||
They render black. If your pass rebuilds topology, they will likely vanish for free.
|
||||
- **835 boundary edges / 1649 non-manifold edges**, inherited from the sculpt. Every fill attempt
|
||||
refused them (`bmesh.ops.holes_fill`, edit-mode `mesh.fill_holes`, and a per-loop pass) — they
|
||||
are dangling flaps and slivers, not perforations. Documented in the lane README.
|
||||
- **The head is untouched original Tripo output.** Every geometry pass in this lane stopped below
|
||||
the chin (z < 0.90) on purpose, so lips/nostrils/eyes still have the source smearing. If the
|
||||
retexture covers the face, that is new ground, not a regression.
|
||||
- **She reads glossy** — the `rm` map was neutralised to a median over the old garment texels, so
|
||||
the whole body is shinier than skin should be. Untouched deliberately: an earlier attempt at
|
||||
correcting roughness made her plasticky (see the lane README's fourth dead end).
|
||||
|
||||
## Provenance the three fixes came from
|
||||
|
||||
v03 addressed Jeremy's three notes on v02 — cut lines, discoloured bra/crotch patches, and the
|
||||
cleavage. Measured outcomes and the four dead ends that were ruled out on the way are in
|
||||
`README.md` under "Hi-res sculpt v03". Worth skimming before re-treading the geometry.
|
||||
|
||||
## Numbering note
|
||||
|
||||
This lane had two agents working in it simultaneously on 2026-08-06. `20`/`21` exist twice under
|
||||
different filenames (`20_atlas_probe.py` + `20_cleavage.py`, `21_seam_probe.py` + `21_tone.py`).
|
||||
Nothing was overwritten, but pick a fresh number rather than assuming the next one is free.
|
||||
@@ -1,87 +0,0 @@
|
||||
# Handoff: re-atlased nude Lena — `lena_nude_decimated_glb_v02.glb`
|
||||
|
||||
**2026-08-06.** Jeremy asked for the decimated nude body re-atlased and versioned. This is a
|
||||
**UV + texture change only**: the geometry is byte-for-byte the v01 mesh.
|
||||
|
||||
## The file
|
||||
|
||||
`characters/female/lena_nude/lena_nude_decimated_glb_v02.glb`
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| sha256 | `cd3d53550470ae5a1958dc473ce11793f746b13f78ac911e6d426b6f7b9f5825` |
|
||||
| parent | `lena_nude_decimated_glb_v01.glb` |
|
||||
| recipe | `hires_claude/24_seams.py` → `hires_claude/31_reatlas.py` |
|
||||
| mesh | 31,964 v / 64,957 tris in Blender — **identical to v01** |
|
||||
| exported verts | 42,980 (v01: 46,810) — the new layout needs fewer seam splits |
|
||||
| maps | basecolor + normal + rm embedded, 4096², JPEG q95, correct colorspaces |
|
||||
| rig | none — unrigged, transforms applied |
|
||||
|
||||
Rebuild:
|
||||
|
||||
```
|
||||
blender --background --python 24_seams.py -- ../lena_nude_decimated_glb_v01.glb atlas_v02
|
||||
blender --background --python 31_reatlas.py -- atlas_v02/seamed.blend atlas_v02 \
|
||||
../lena_nude_decimated_glb_v02.glb
|
||||
```
|
||||
|
||||
## What changed
|
||||
|
||||
| | Tripo atlas (v01) | new atlas (v02) |
|
||||
|---|---|---|
|
||||
| UV islands | 5,870 | 968 |
|
||||
| charts holding 99% of the area | 84 | **14** |
|
||||
| atlas coverage | 62.0% | **68.6%** |
|
||||
| texel-density spread | 1.8× | **1.00×** |
|
||||
| per-triangle area-scale p95 / p99 | — | 1.77 / 2.27 |
|
||||
|
||||
The 14 charts are torso, head, two arms, two legs, two hands (back + palm each) and two feet
|
||||
(upper + sole each). Seams run up the back midline, the back of each arm and the inner leg, with
|
||||
rings at neck, shoulder, wrist, hip and ankle — every landmark measured off the mesh's own
|
||||
cross-section radius profiles, so the rules port to any density.
|
||||
|
||||
**It looks the same, on purpose.** Five matched views diff at mean \|Δ\| 0.0012, p99 0.0118, with
|
||||
0.19% of body pixels over 0.05 (`33_ab.py`). Her original tones are untouched — this pass bought a
|
||||
paintable, uniform-density layout, not a new look.
|
||||
|
||||
## Three traps this pass hit, so you don't
|
||||
|
||||
1. **Colorspace is not cosmetic.** `image.pixels` returns *linear* values for an sRGB image and
|
||||
*raw* values for a Non-Color one, and re-encodes the same way on save. The normal and rm maps
|
||||
are Non-Color; writing them into default (sRGB) images gamma-encoded them, so 0.5 came back as
|
||||
0.21 — every stored normal became a large false perturbation and she rendered dark and
|
||||
wet-plastic. New maps must inherit `colorspace_settings.name` from their source.
|
||||
2. **Use the minimum-stretch solver, not angle-based.** ABF is conformal: it preserves angles and
|
||||
is free to crush area. At her folds it squashed 4,502 triangles (3.7% of her surface) below one
|
||||
texel, and they came out untextured. `method='MINIMUM_STRETCH'` took per-triangle area-scale p05
|
||||
from 0.03 to 0.33 and the untextured count from 4,502 to 569 (0.56%, all genuine sub-texel
|
||||
slivers absorbed by the 16 px padding pass).
|
||||
3. **Watch the LOW tail of the distortion stat.** p95/p99 measure stretching; it was p05 = 0.03 —
|
||||
crushing — that was losing whole patches. I read past it twice.
|
||||
|
||||
Also tried and rejected: splitting the 1,649 non-manifold edges before unwrapping. It cost +3,512
|
||||
verts and made the collapsed-face count *worse* (4,502 → 5,482), because the collapse was a solver
|
||||
problem, not a topology one.
|
||||
|
||||
## Still broken, and not from this pass
|
||||
|
||||
**The ragged grey tears on the face, across the underbust, and as flecks down the arms and shins
|
||||
are in v01 already** — I rendered v01 through the same script to confirm, and they are identical in
|
||||
position and shape (`hires_claude/beauty_input_v01/` vs `beauty_atlas_v02/`). They are the 835
|
||||
boundary edges / dangling flaps the v01 handoff lists as a known defect. A re-atlas cannot fix
|
||||
geometry. They need mesh surgery before this body ships.
|
||||
|
||||
The other v01 defects carry over untouched: 25 backfacing triangles, the head still being original
|
||||
Tripo output, and the glossy `rm` map.
|
||||
|
||||
## What this does NOT do
|
||||
|
||||
Jeremy's stated goal is to **keep her original texture and replace only the breast and underwear
|
||||
region**. This pass deliberately does not touch colour — it only rehouses it. The re-authoring step
|
||||
(a gradient-domain fill pinned to her surrounding skin at the boundary, so the old bra/briefs
|
||||
outline cannot ghost) is the next stage, and it is much easier now that the charts are anatomical
|
||||
and uniform-density.
|
||||
|
||||
Note that keeping her original texture keeps its known incoherence: her feet read 0.11 luma darker
|
||||
and 50% redder than her belly, and the rosy chest V remains. Measured in
|
||||
`hires_claude/21_seam_probe.py`; see the `lena-tripo-atlas-chart-soup` note.
|
||||
@@ -1,195 +0,0 @@
|
||||
# lena_nude — nude body variant (WIP, not released)
|
||||
|
||||
Lena's body with the underwear removed: no bra, no briefs, breasts with volume and **no
|
||||
nipples**, smooth featureless crotch. Staged here until it is approved for the game.
|
||||
|
||||
| file | role |
|
||||
|---|---|
|
||||
| `lena_nude_quatskin_glb_v01.glb` | the body — 65-bone Quaternius game skeleton, texture **embedded** (self-contained) |
|
||||
| `lena_nude_basecolor.png` | the baked albedo, loose, for further iteration |
|
||||
|
||||
Ship name at release is `Ariki_Female_QuatSkin_Nude.glb` in
|
||||
`ariki-game/assets/quaternius/derived-bodies/`. Per `../../REGISTRY.md` rule 8 that rename happens
|
||||
exactly once, on import — never create `Ariki_*` files in this repo.
|
||||
|
||||
## How it was made
|
||||
|
||||
Generators live in **this** repo (moved out of ariki-game 2026-08-06 — they author a character,
|
||||
so `../../REGISTRY.md` governs them; see `.agents/wiki/ARCHITECTURE.md` "Tool provenance"). They
|
||||
still *read* the canonical rigged bodies from the game repo (`Ariki_Female_QuatSkin.glb` and the
|
||||
`_ChestV1` breast-sculpt donor), resolved via `$ARIKI_GAME` or a sibling `ariki-game/` checkout —
|
||||
if neither resolves they abort with the path they tried, rather than reading the wrong file.
|
||||
Run from `C:\Users\Jeremy\tinqs\animation`:
|
||||
|
||||
```
|
||||
blender --background --python tools/_bake_nude_body_texture.py
|
||||
# defaults to writing lena_nude_basecolor.png into THIS folder
|
||||
|
||||
blender --background --python tools/make_lena_nude_body.py -- \
|
||||
--out characters/female/lena_nude/lena_nude_quatskin_glb_v01.glb \
|
||||
--size 1.0 \
|
||||
--texture characters/female/lena_nude/lena_nude_basecolor.png
|
||||
|
||||
python tools/verify_body_variant.py \
|
||||
characters/female/lena_nude/lena_nude_quatskin_glb_v01.glb
|
||||
```
|
||||
|
||||
`--size` is breast volume as a multiple of the `_ChestV1` sculpt (0.75 / 1.0 / 1.25 were
|
||||
rendered for review; **v01 ships 1.0**). A new size is a **version bump**, not a new variant —
|
||||
same recipe, different parameter. (This README and `../../REGISTRY.md` both called the flag
|
||||
`--scale` until 2026-08-06; the script has always parsed `--size`.)
|
||||
|
||||
## Facts worth knowing before editing this
|
||||
|
||||
- **The underwear is the body surface.** A ray-crossing census finds exactly two surface crossings
|
||||
at every height — thigh, belly, bra cup, briefs alike. There is no skin underneath, so the
|
||||
garment can only be flattened into the skin, never deleted. Deleting it opens a hole, which is
|
||||
precisely the defect in the game repo's `Ariki_Female_QuatSkin_Bare.glb` (black cavity across
|
||||
the chest, visible even untextured). Do not use `_Bare` geometry for anything.
|
||||
- **The base mesh is not watertight** (3,809 boundary edges). The painted underwear was hiding
|
||||
open slits; with it gone they read as a dotted line of black triangles, so the builder welds
|
||||
them shut in the torso band. That is where this file's lower vertex count comes from.
|
||||
- **Use clay renders to judge geometry.** `tools/_render_body_closeup.py --clay` drops the
|
||||
material; it is the only reliable way to tell a mesh defect from a painted one. Several dead
|
||||
ends here came from mistaking painted bra shadow for a dent, and vice versa.
|
||||
|
||||
## Working files
|
||||
|
||||
This lane follows `.agents/rules/working-files.md`: **a version is a milestone,
|
||||
not a step.** Fixing the bra produces one saved file at the end, not one per cut.
|
||||
|
||||
`hires_claude/.lanekeep` pins the only `.blend` files that survive here — the
|
||||
masters named in the Derived-artifacts table of `../../REGISTRY.md`, plus the
|
||||
live chain head. Everything else a step script wrote is scratch: the `NN_*.py`
|
||||
recipe plus `masks.npz` regenerates it from the master above it.
|
||||
|
||||
```
|
||||
python tools/prune_lane.py characters/female/lena_nude --recursive # dry run
|
||||
```
|
||||
|
||||
Two things this lane learned the hard way:
|
||||
|
||||
- **Check for a running Blender before pruning.** A heal step takes ~7 minutes
|
||||
and writes only at the end; the lane gained a new head (`17_healed.blend`)
|
||||
during the audit that produced this section.
|
||||
- `hires_work/` is the abandoned GLM-agent attempt (see its
|
||||
`glm_run_attempt*.log`), superseded by `hires_claude/`. It has no `.lanekeep`,
|
||||
so every `.blend` in it classifies as scratch.
|
||||
|
||||
The root-level `lena_sculpt_glb_v01-nude-Zephyr.blend` is hand-authored — no
|
||||
step script rebuilds it — so the pruner reports it as UNKNOWN and never touches
|
||||
it. Decide its fate by hand.
|
||||
|
||||
## Status
|
||||
|
||||
Verified against the canonical body: 1 primitive, identical attribute set (no tangents), material
|
||||
`MI_Body_Lena`, 65 joints in identical order, height 1.777 m, inverse-bind drift 4.6e-6, weights
|
||||
normalised, texture embedded. Posed sweep under a UAL clip is clean (no spikes or tearing).
|
||||
|
||||
Known cosmetic limits: a faint tonal seam where the fill meets untouched skin at extreme zoom, and
|
||||
no skin-pore detail inside the filled area — that smoothness is also what guarantees no nipple or
|
||||
crotch detail can appear.
|
||||
|
||||
Full history and the reasoning behind each choice: `~/.claude/plans/i-have-a-lena-velvet-ripple.md`.
|
||||
|
||||
## v04 — bust matched to the approved concept turnaround (2026-08-06)
|
||||
|
||||
`lena_nude_sculpt_glb_v04.glb`, master `hires_claude/34_v04.blend`. Recipes
|
||||
`32_ref_measure.py` (measure) → `33_bust_variants.py` (size study) → `34_apply_bust.py` (k=0.5).
|
||||
|
||||
Guiding art: `exchange/INBOX/lena-base-turnaround/` — front/side/back T-pose renders, GPT Image 2,
|
||||
approved by Can 2026-08-06.
|
||||
|
||||
**The reference is the same character, not a new one.** Measured against the front/side silhouettes:
|
||||
|
||||
| landmark | reference | v03 mesh |
|
||||
|---|---|---|
|
||||
| heads tall | 6.43 | 6.08 |
|
||||
| waist / hip | 0.504 | 0.505 |
|
||||
| waist width | 0.1260 H | 0.1318 H |
|
||||
| hip width | 0.2500 H | 0.2608 H |
|
||||
| **bust projection** | **+0.0156 H** | **+0.0210 H** |
|
||||
|
||||
Proportions already agree and the widths sit inside silhouette-threshold noise for generated art, so
|
||||
only the bust was changed: the breast forms are rescaled to 50% of their projection off a
|
||||
bi-harmonic chest wall, landing at +0.0155 H. Nothing outside the chest region moves (verified 0.0000 mm)
|
||||
and height is unchanged, so every v03 heal survives.
|
||||
|
||||
Two traps for whoever tunes this next:
|
||||
|
||||
- **The projection metric saturates below k≈0.5** — k=0.35 measures +0.0154, indistinguishable from
|
||||
k=0.5. Below that the "deepest slice" row migrates and stops tracking breast volume, so the number
|
||||
cannot be pushed lower meaningfully. Treat 0.5 as a floor, not a midpoint. The reference reading may
|
||||
also be inflated by the T-pose arm crossing the chest in a side view, which would mean the real
|
||||
reference bust is smaller still.
|
||||
- **Front renders cannot tell the sizes apart.** k=0.5, 0.65 and 0.8 look identical head-on because
|
||||
the frontal key light flattens the chest. Judge from the side view only. Variants are in
|
||||
`hires_claude/review_33/k{100,080,065,050,035}/`.
|
||||
|
||||
Re-running at another k takes seconds: `_bust_wall_cache.npy` holds the 8-minute chest-wall solve.
|
||||
|
||||
Not addressed, and the largest remaining gap to the concept art: the reference is a **smooth
|
||||
mannequin** with no scan noise, while this mesh still carries speckles and dents on the hips and
|
||||
thighs, plus the untouched original head.
|
||||
|
||||
## Decimated for the retexture / re-atlas lane (2026-08-06)
|
||||
|
||||
`lena_nude_decimated_glb_v01.glb` — v03 collapsed to the game-body budget (31,964 v / 64,957 tris,
|
||||
ratio 0.037728) as input for `hires_claude/24_seams.py`. Recipe `30_decimate.py` + `30b_fix_winding.py`.
|
||||
**Read `HANDOFF_decimated_v01.md` before working on it** — in particular, its carried UVs are scrap
|
||||
(collapse shreds Tripo's 5,870-chart atlas) and it ships with 25 known backfacing triangles.
|
||||
|
||||
## Re-atlased (2026-08-06)
|
||||
|
||||
`lena_nude_decimated_glb_v02.glb` — **v01's geometry with a new UV atlas and resampled maps.**
|
||||
Same 31,964 v / 64,957 tris; Tripo's 5,870 charts replaced by 14 anatomical ones (coverage
|
||||
62.0 → 68.6%, texel-density spread 1.8× → 1.00×). Recipe `hires_claude/24_seams.py` →
|
||||
`hires_claude/31_reatlas.py`. It deliberately **looks identical** to v01 (mean pixel |Δ| 0.0012
|
||||
across five views) — the win is a paintable, uniform-density layout, not a new look.
|
||||
**Read `HANDOFF_reatlas_v02.md`**, especially the colorspace and unwrap-solver traps, and note that
|
||||
the grey tears on the face/underbust are v01's dangling-flap defect and are still there.
|
||||
|
||||
## Hi-res sculpt v03 (2026-08-06) — Jeremy's three notes on v02
|
||||
|
||||
`lena_nude_sculpt_glb_v03.glb`, masters `hires_claude/27_clean.blend` (geometry) and
|
||||
`29b_final.blend` (textured). Recipes: `17_line_heal.py` → `26_finish.py` →
|
||||
`25_speckle.py --apply` → `29_tone3d.py` → `08_export.py`.
|
||||
|
||||
| note | measured outcome |
|
||||
|---|---|
|
||||
| cut lines | shading kinks >12° 9704 → ~6900; flipped faces (the black dashes) 1511 → 10. Ribcage and thigh lines gone frontally; waistband + underbust still legible under raking light |
|
||||
| discoloured bra/crotch | on-body tone gap, patch interior vs surrounding skin: 0.0102 → 0.0068 luma. The pale panels in `beauty_v02_healed/` are gone in `beauty_v03b/` |
|
||||
| cleavage | sternum fillet radius 9.5 mm → no sharp sample at any height; notch depth 44 → 31 mm |
|
||||
|
||||
### Four dead ends this version ruled out — do not repeat them
|
||||
|
||||
1. **The lines are not cracks; do not weld.** Stage 15 assumed the panel seams were disconnected
|
||||
vertex runs. They are not: the mesh has only 830 boundary edges in 689 loops of 3–5 edges, and
|
||||
for kink verts the nearest vertex outside the 3-ring sits at 1.58× the local edge length
|
||||
(median) against 1.73× for control skin. Welding at eps 1.2 mm — about the 1.85 mm mean edge
|
||||
length — merged ordinary neighbours and tore the mesh, 830 → 24k boundary edges.
|
||||
2. **They are not in the custom normals either.** Corner-vs-vertex deviation is 0.008° mean, 15
|
||||
loops above 5°; clearing custom split normals changes nothing.
|
||||
3. **A membrane is the wrong operator, and widening it is worse.** The relief is ONE VERTEX WIDE
|
||||
(0.23 mm median at the centre, back to the 0.074 mm background by ring 2), so a collar-fixed
|
||||
membrane pins itself to the defect's own shoulders. GROW 4 and 8 were no better than GROW 2
|
||||
while moving material up to 38 mm. A **ring median** is correct: it deletes a 1-vertex outlier
|
||||
and returns anything broader unchanged, so the underbust fold, clavicles and navel survive by
|
||||
construction instead of by a tuned threshold.
|
||||
4. **Never level tone from atlas-adjacent texels.** A Poisson solve whose boundary condition was
|
||||
the mismatch against UV neighbours turned the patches into pale grey panels — UV adjacency is
|
||||
not body adjacency, so the border mismatch came from other body parts and gutter. This is the
|
||||
warning already in `05_texture.py`'s header. Sample on the body (KD over skin verts in 3D),
|
||||
smooth the correction over the mesh graph, then rasterise.
|
||||
|
||||
Two process traps worth keeping: **restore the real material before saving** — `18/20/25`
|
||||
originally rendered clay last and then saved, so the file on disk kept the clay material and lost
|
||||
its texture wiring, which is why the first tone attempt aborted with "could not find the wired
|
||||
basecolor". And **the pristine textures are not inside working blends**: Blender purges the orphan
|
||||
`.002/.003` copies on save, so read them from `00_welded.blend`.
|
||||
|
||||
Still open: 1649 non-manifold and 830 boundary edges — `bmesh.ops.holes_fill`, the edit-mode
|
||||
`mesh.fill_holes` operator, and a per-loop pass all refused every one, which is why
|
||||
`normals_make_consistent` could only reach 1198 of 1511 flipped faces before explicit per-face
|
||||
reversal finished the job. None of this is propagated to the game body
|
||||
(`lena_nude_quatskin_glb_v01.glb`), which is built independently on the 65-bone Quaternius rig.
|
||||
@@ -1,36 +0,0 @@
|
||||
# .lanekeep — .blend files pinned as MASTER in this lane.
|
||||
# Everything else here that a step script wrote is scratch: regenerable by
|
||||
# re-running the NN_*.py recipe from the pinned master above it.
|
||||
# Read by tools/prune_lane.py. One filename per line, # for comments.
|
||||
#
|
||||
# Rule: a .blend earns a line here only when a registered artifact in
|
||||
# characters/REGISTRY.md was built from it, or it is the head of the live chain.
|
||||
# Intermediate attempts (the 06b-06h melt series, the 10_healed/rimheal/seamheal
|
||||
# series) never get pinned — only the state at the end of the fix.
|
||||
|
||||
# --- phase roots ---
|
||||
00_welded.blend # raw original, slits welded shut. Root of every branch;
|
||||
# keeps a re-sculpt from needing a fresh weld+isolate pass.
|
||||
|
||||
# --- masters of registered artifacts (see characters/REGISTRY.md) ---
|
||||
06_final.blend # lena_nude_sculpt_glb_v01.glb — geometry master
|
||||
07_polished.blend # lena_nude_sculpt_glb_v01.glb — textured master
|
||||
10_patch.blend # lena_nude_sculpt_glb_v02.glb — master (original skin retained)
|
||||
|
||||
27_clean.blend # lena_nude_sculpt_glb_v03.glb — geometry master (phase boundary:
|
||||
# line heal + cleavage fillet + flipped-face repair, pre-texture)
|
||||
29b_final.blend # lena_nude_sculpt_glb_v03.glb — textured master, HEAD 2026-08-06.
|
||||
# 27_clean + on-body tone levelling (29_tone3d.py str 1.0, 25 passes)
|
||||
34_v04.blend # lena_nude_sculpt_glb_v04.glb — master, HEAD 2026-08-06. 29b_final with
|
||||
# the bust rescaled to k=0.5 per the approved concept turnaround.
|
||||
10_welded.blend # parent of the v03 chain. Kept as the fallback until v03/v04 are
|
||||
# accepted; 11-15 regenerate it from 10_patch, so unpin then.
|
||||
|
||||
# _bust_wall_cache.npy is the bi-harmonic chest wall for 29b_final (an 8-minute CG solve).
|
||||
# Keep it while bust size is still in play: 33/34 reuse it and a different k then costs
|
||||
# seconds instead of minutes. It is regenerable, and invalid for any other mesh — both
|
||||
# scripts assert its shape against the vertex count before trusting it.
|
||||
|
||||
# 17_healed / 18_smoothnrm / 22_lines / 23_cleavage / 26_geo / 29_final and the
|
||||
# rejected atlas-tone attempt were per-step snapshots and have been pruned; the
|
||||
# 17/25/26/29 recipes rebuild them from 10_welded.
|
||||
|
Before Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 1.5 MiB |
@@ -1,60 +0,0 @@
|
||||
12:26:35 | INFO: Data are loaded, start creating Blender stuff
|
||||
12:26:35 | INFO: Blender create Mesh node meshes[0]
|
||||
12:26:35 | INFO: glTF import finished in 0.16s
|
||||
[seam 0.4s] body 'tripo_node_9701d6b7-20ea-449d-8637-2fc9d4bc199c' 46810v 64957f
|
||||
Info: Removed 14846 vertices
|
||||
Info: Removed: 0 vertices, 0 edges, 0 faces
|
||||
Info: Removed: 0 vertices, 0 edges, 0 faces
|
||||
[seam 0.6s] clean[after decimate]: 31964v 64957f nonmanifold_edges=1649 degenerate_faces=0
|
||||
axes up=z lr=x fb=y height 0.9793 units
|
||||
single-leg radius profile (u -> radius), the ankle is the narrow point:
|
||||
0.01:45 0.01:51 0.03:50 0.04:42 0.04:35 0.06:29 0.07:27 0.08:25 0.08:25 0.10:25 0.11:23 0.11:26 0.12:26 0.14:27 0.15:31 0.15:33 0.17:34 0.17:37 0.18:38 0.20:39 0.21:41 0.21:41 0.23:41 0.23:40 0.24:40 0.26:39 0.27:38 0.28:38 0.29:40 0.29:39 0.30:40 0.32:40 0.33:42 0.34:45 0.34:48
|
||||
arm radius profile (|s| -> radius), the wrist is the narrow point before the hand:
|
||||
0.36:28 0.37:28 0.39:27 0.41:27 0.42:26 0.44:25 0.46:25 0.47:26 0.49:26 0.50:28 0.52:28 0.54:28 0.55:28 0.57:28 0.59:27 0.60:26 0.62:24 0.63:23 0.65:22 0.67:21 0.68:20 0.70:20 0.72:18 0.73:19 0.75:19 0.76:25 0.78:28 0.80:30 0.81:29 0.83:29 0.85:31 0.86:23 0.88:22 0.89:21 0.91:22 0.93:18 0.94:19 0.96:17 0.98:15 0.99:8
|
||||
LANDMARKS neck_u 0.825 hip_u 0.415 ankle_u 0.085 arm_in 0.350 wrist 0.748 (fractions of height / half-span)
|
||||
foot split at u 0.018 (upper|sole) hand split at d +0.0040 (back|palm)
|
||||
arm centre line u at |s|=[0.35, 0.45, 0.55, 0.65, 0.75]: [0.727, 0.722, 0.721, 0.705, 0.689]
|
||||
leg(+) centre line |s| at u=[0.08, 0.17, 0.25, 0.33, 0.41]: [0.23, 0.199, 0.197, 0.145, 0.081]
|
||||
armpit u 0.713 -> relief cut across the back at that height
|
||||
seam 'ankle rings': 203 edges
|
||||
seam 'arm back line': 371 edges
|
||||
seam 'armpit relief': 116 edges
|
||||
seam 'back midline': 314 edges
|
||||
seam 'foot sole line': 493 edges
|
||||
seam 'hand palm line': 267 edges
|
||||
seam 'hip ring': 370 edges
|
||||
seam 'leg inner line': 135 edges
|
||||
seam 'neck ring': 113 edges
|
||||
seam 'shoulder rings': 263 edges
|
||||
seam 'wrist rings': 181 edges
|
||||
[seam 2.0s] marked 2826 seam edges
|
||||
[seam 2.0s] uv layers: ['UVMap_tripo', 'UVMap_atlas'] active=UVMap_atlas
|
||||
[seam 8.2s] unwrapped + packed
|
||||
|
||||
=== NEW ATLAS (Tripo baseline in brackets) ===
|
||||
islands 968 [5870] 99%-of-area islands 14 [84]
|
||||
coverage 68.6% [62.0] confetti <20 faces 953 [5763]
|
||||
texel density 2.29..2.29 px/mm -> 1.00x spread [1.8x]
|
||||
per-triangle area-scale vs median, real charts only: p05 0.33 p95 1.77 p99 2.27 (1.0 = undistorted)
|
||||
|
||||
the charts: # faces uv_area% 3D cm2 stretch_p95 where
|
||||
815 21024 26.011 8290.6 2.00 torso mid
|
||||
478 5544 8.739 2785.4 1.69 leg R
|
||||
811 5600 8.733 2783.4 1.38 leg L
|
||||
653 9887 7.861 2505.5 2.53 head mid
|
||||
887 2741 3.089 984.7 1.81 arm L
|
||||
154 2596 3.074 979.7 1.83 arm R
|
||||
833 2139 1.921 612.2 1.21 foot L
|
||||
310 2151 1.915 610.5 1.63 foot R
|
||||
192 2146 1.284 409.2 1.18 foot R
|
||||
834 2084 1.269 404.5 1.21 foot L
|
||||
967 2025 1.115 355.4 1.40 hand L
|
||||
71 1997 1.109 353.3 2.28 hand R
|
||||
77 1954 1.103 351.7 1.32 hand R
|
||||
931 1961 1.090 347.4 1.39 hand L
|
||||
Info: Saved as "seamed.blend"
|
||||
[seam 13.9s] wrote C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\atlas_v02\charts.png + seamed.blend
|
||||
SEAMS_DONE
|
||||
Blender 5.1.2 (hash ec6e62d40fa9 built 2026-05-19 01:37:34)
|
||||
|
||||
Blender quit
|
||||
|
Before Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 910 KiB |
|
Before Width: | Height: | Size: 880 KiB |
@@ -1,17 +0,0 @@
|
||||
00:01.922 blend | Read blend: "C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\28_final.blend"
|
||||
C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\09_beauty.py:19: DeprecationWarning: 'World.use_nodes' is expected to be removed in Blender 6.0
|
||||
wd.use_nodes = True
|
||||
00:05.078 render | Saved: 'C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\beauty_v03\full_front.png'
|
||||
rendered full_front
|
||||
00:06.062 render | Saved: 'C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\beauty_v03\full_40.png'
|
||||
rendered full_40
|
||||
00:07.000 render | Saved: 'C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\beauty_v03\full_back.png'
|
||||
rendered full_back
|
||||
00:07.984 render | Saved: 'C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\beauty_v03\chest.png'
|
||||
rendered chest
|
||||
00:09.031 render | Saved: 'C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\beauty_v03\hip.png'
|
||||
rendered hip
|
||||
BEAUTY_DONE
|
||||
Blender 5.1.2 (hash ec6e62d40fa9 built 2026-05-19 01:37:34)
|
||||
|
||||
Blender quit
|
||||
@@ -1,17 +0,0 @@
|
||||
00:01.890 blend | Read blend: "C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\29b_final.blend"
|
||||
C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\09_beauty.py:19: DeprecationWarning: 'World.use_nodes' is expected to be removed in Blender 6.0
|
||||
wd.use_nodes = True
|
||||
00:04.640 render | Saved: 'C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\beauty_v03b\full_front.png'
|
||||
rendered full_front
|
||||
00:05.594 render | Saved: 'C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\beauty_v03b\full_40.png'
|
||||
rendered full_40
|
||||
00:06.484 render | Saved: 'C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\beauty_v03b\full_back.png'
|
||||
rendered full_back
|
||||
00:07.453 render | Saved: 'C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\beauty_v03b\chest.png'
|
||||
rendered chest
|
||||
00:08.422 render | Saved: 'C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\beauty_v03b\hip.png'
|
||||
rendered hip
|
||||
BEAUTY_DONE
|
||||
Blender 5.1.2 (hash ec6e62d40fa9 built 2026-05-19 01:37:34)
|
||||
|
||||
Blender quit
|
||||
|
Before Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 1.5 MiB |
@@ -1,17 +0,0 @@
|
||||
00:01.797 blend | Read blend: "C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\atlas_v02\reatlased.blend"
|
||||
C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\09_beauty.py:19: DeprecationWarning: 'World.use_nodes' is expected to be removed in Blender 6.0
|
||||
wd.use_nodes = True
|
||||
00:03.812 render | Saved: 'C:\beauty_atlas_v02\full_front.png'
|
||||
rendered full_front
|
||||
00:04.172 render | Saved: 'C:\beauty_atlas_v02\full_40.png'
|
||||
rendered full_40
|
||||
00:04.500 render | Saved: 'C:\beauty_atlas_v02\full_back.png'
|
||||
rendered full_back
|
||||
00:04.875 render | Saved: 'C:\beauty_atlas_v02\chest.png'
|
||||
rendered chest
|
||||
00:05.265 render | Saved: 'C:\beauty_atlas_v02\hip.png'
|
||||
rendered hip
|
||||
BEAUTY_DONE
|
||||
Blender 5.1.2 (hash ec6e62d40fa9 built 2026-05-19 01:37:34)
|
||||
|
||||
Blender quit
|
||||
|
Before Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 867 KiB |
|
Before Width: | Height: | Size: 888 KiB |
@@ -1,38 +0,0 @@
|
||||
00:01.812 blend | Read blend: "C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\26_geo.blend"
|
||||
[spk 0.3s] in: 860389v 1721805f custom_normals=True
|
||||
C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\25_speckle.py:217: DeprecationWarning: 'Material.use_nodes' is expected to be removed in Blender 6.0
|
||||
clay.use_nodes = True
|
||||
00:15.812 render | Saved: 'C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\review_25diag\chest_clay_0.png'
|
||||
FLIPPED FACES: 1511 of 1721805 (0.0878%) [dot<0 vs smoothed field]
|
||||
nearly-perpendicular (|dot|<0.2): 442
|
||||
flipped-face z histogram:
|
||||
z 0.002-0.083: 212
|
||||
z 0.083-0.164: 63
|
||||
z 0.164-0.246: 25
|
||||
z 0.246-0.327: 33
|
||||
z 0.327-0.409: 95
|
||||
z 0.409-0.490: 37
|
||||
z 0.490-0.572: 100
|
||||
z 0.572-0.653: 212
|
||||
z 0.653-0.735: 292
|
||||
z 0.735-0.816: 115
|
||||
z 0.816-0.898: 145
|
||||
z 0.898-0.979: 182
|
||||
FACE COMPONENTS: 1 (largest [1721805])
|
||||
shards <= 200 faces: 0 components, 0 faces total
|
||||
SLIVERS: 0 faces with area < (0.01 mm-unit)^2; min area 4.542e-10, p01 4.805e-08
|
||||
[spk 14.0s] render chest_clay_0
|
||||
00:16.281 render | Saved: 'C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\review_25diag\chest_clay_40.png'
|
||||
[spk 14.5s] render chest_clay_40
|
||||
00:17.500 render | Saved: 'C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\review_25diag\chest_tex_0.png'
|
||||
[spk 15.7s] render chest_tex_0
|
||||
00:17.953 render | Saved: 'C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\review_25diag\hip_clay_0.png'
|
||||
[spk 16.1s] render hip_clay_0
|
||||
00:18.343 render | Saved: 'C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\review_25diag\full_clay_0.png'
|
||||
[spk 16.5s] render full_clay_0
|
||||
00:19.187 render | Saved: 'C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\review_25diag\full_tex_0.png'
|
||||
[spk 17.4s] render full_tex_0
|
||||
SPK_DONE
|
||||
Blender 5.1.2 (hash ec6e62d40fa9 built 2026-05-19 01:37:34)
|
||||
|
||||
Blender quit
|
||||
|
Before Width: | Height: | Size: 1.0 MiB |
@@ -1,27 +0,0 @@
|
||||
import bpy, sys
|
||||
import numpy as np
|
||||
W = r"C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude"
|
||||
bpy.ops.wm.open_mainfile(filepath=W + r"\00_welded.blend")
|
||||
ob = max([o for o in bpy.data.objects if o.type=='MESH'], key=lambda o: len(o.data.vertices))
|
||||
n = len(ob.data.vertices)
|
||||
co0 = np.empty(n*3); ob.data.vertices.foreach_get("co", co0); co0 = co0.reshape(-1,3)
|
||||
bpy.ops.wm.open_mainfile(filepath=W + r"\03_sculpted.blend")
|
||||
ob = max([o for o in bpy.data.objects if o.type=='MESH'], key=lambda o: len(o.data.vertices))
|
||||
co1 = np.empty(n*3); ob.data.vertices.foreach_get("co", co1); co1 = co1.reshape(-1,3)
|
||||
disp = np.linalg.norm(co1-co0, axis=1)
|
||||
M = np.load(W + r"\masks.npz")
|
||||
A = np.load(W + r"\masks_active.npz")
|
||||
act = A["active"]; garment = M["garment"]; cups = M["cups"]; shield = M["shield"]
|
||||
# regions to interrogate (welded coords)
|
||||
zones = {
|
||||
"bow": (np.abs(co0[:,0])<0.03) & (co0[:,2]>0.715) & (co0[:,2]<0.760) & (co0[:,1]<-0.05),
|
||||
"strap_tuft":(np.abs(co0[:,0])>0.035)&(np.abs(co0[:,0])<0.09)&(co0[:,2]>0.80)&(co0[:,2]<0.86),
|
||||
"armpit": (np.abs(co0[:,0])>0.10)&(np.abs(co0[:,0])<0.15)&(co0[:,2]>0.66)&(co0[:,2]<0.75)&(co0[:,1]>-0.02),
|
||||
"good_cup": (np.abs(co0[:,0])>0.03)&(np.abs(co0[:,0])<0.06)&(co0[:,2]>0.66)&(co0[:,2]<0.70)&(co0[:,1]<-0.06),
|
||||
}
|
||||
for nm, m in zones.items():
|
||||
if not m.sum():
|
||||
print(nm, "EMPTY"); continue
|
||||
print(f"{nm}: n={m.sum():6d} garment={100*garment[m].mean():5.1f}% act={100*act[m].mean():5.1f}% "
|
||||
f"disp mm p50={np.median(disp[m])*1000:.2f} p90={np.percentile(disp[m],90)*1000:.2f}")
|
||||
print("DBG_BOW_DONE")
|
||||
@@ -1,92 +0,0 @@
|
||||
# Diagnose the inter-thigh residue below the crotch snap window.
|
||||
# blender --background --python dbg_gusset.py -- <blend> <outdir>
|
||||
import bpy, sys, math, os
|
||||
import numpy as np
|
||||
from mathutils import Vector, Euler
|
||||
|
||||
argv = sys.argv[sys.argv.index("--") + 1:]
|
||||
BLEND, OUT = argv[0], argv[1]
|
||||
os.makedirs(OUT, exist_ok=True)
|
||||
|
||||
bpy.ops.wm.open_mainfile(filepath=BLEND)
|
||||
ob = max([o for o in bpy.data.objects if o.type == 'MESH'],
|
||||
key=lambda o: len(o.data.vertices))
|
||||
me = ob.data
|
||||
n_v = len(me.vertices)
|
||||
co = np.empty(n_v * 3)
|
||||
me.vertices.foreach_get("co", co)
|
||||
co = co.reshape(-1, 3)
|
||||
|
||||
# numeric: midline strip profile — for each z slice near the crotch, front-most and
|
||||
# back-most y, and the x extent of geometry in the inter-thigh gap
|
||||
print("== midline (|x|<0.01) y-range per z slice ==")
|
||||
for z0 in np.arange(0.34, 0.50, 0.01):
|
||||
m = (np.abs(co[:, 0]) < 0.01) & (co[:, 2] >= z0) & (co[:, 2] < z0 + 0.01)
|
||||
if m.sum() == 0:
|
||||
print(f" z {z0:.2f}: EMPTY")
|
||||
continue
|
||||
ys = co[m, 1]
|
||||
print(f" z {z0:.2f}: n={m.sum():5d} y [{ys.min():+.4f} .. {ys.max():+.4f}]")
|
||||
|
||||
print("== inter-thigh occupancy: verts with |x|<0.06 per z slice ==")
|
||||
for z0 in np.arange(0.30, 0.46, 0.01):
|
||||
m = (np.abs(co[:, 0]) < 0.06) & (co[:, 2] >= z0) & (co[:, 2] < z0 + 0.01)
|
||||
print(f" z {z0:.2f}: n={m.sum():5d}", end="")
|
||||
if m.sum():
|
||||
print(f" x [{co[m,0].min():+.4f}..{co[m,0].max():+.4f}]"
|
||||
f" y [{co[m,1].min():+.4f}..{co[m,1].max():+.4f}]")
|
||||
else:
|
||||
print()
|
||||
|
||||
# renders: clay, shadowless, tight on the crotch — front, and from below looking up
|
||||
for o in list(bpy.data.objects):
|
||||
if o.type in ('LIGHT', 'CAMERA'):
|
||||
bpy.data.objects.remove(o, do_unlink=True)
|
||||
|
||||
sc = bpy.context.scene
|
||||
sc.render.engine = 'BLENDER_EEVEE'
|
||||
sc.render.resolution_x = sc.render.resolution_y = 1000
|
||||
sc.render.film_transparent = False
|
||||
wd = bpy.data.worlds.new("w")
|
||||
wd.use_nodes = True
|
||||
wd.node_tree.nodes["Background"].inputs[0].default_value = (0.18, 0.18, 0.18, 1)
|
||||
sc.world = wd
|
||||
|
||||
mat = bpy.data.materials.new("clay")
|
||||
mat.use_nodes = True
|
||||
b = mat.node_tree.nodes["Principled BSDF"]
|
||||
b.inputs["Base Color"].default_value = (0.8, 0.8, 0.8, 1)
|
||||
b.inputs["Roughness"].default_value = 0.6
|
||||
ob.data.materials.clear()
|
||||
ob.data.materials.append(mat)
|
||||
|
||||
def sun(rot, e):
|
||||
ld = bpy.data.lights.new("s", 'SUN')
|
||||
ld.energy = e
|
||||
ld.use_shadow = False
|
||||
lo = bpy.data.objects.new("s", ld)
|
||||
lo.rotation_euler = rot
|
||||
bpy.context.collection.objects.link(lo)
|
||||
|
||||
sun(Euler((math.radians(60), 0, math.radians(-30))), 3.0)
|
||||
sun(Euler((math.radians(120), 0, math.radians(150))), 1.2)
|
||||
|
||||
cam = bpy.data.cameras.new("c")
|
||||
cam.lens = 85
|
||||
cob = bpy.data.objects.new("c", cam)
|
||||
bpy.context.collection.objects.link(cob)
|
||||
sc.camera = cob
|
||||
|
||||
views = [
|
||||
# (name, location, rotation) — model front = -Y
|
||||
("front_tight", Vector((0.0, -0.55, 0.41)), Euler((math.radians(90), 0, 0))),
|
||||
("below_up", Vector((0.0, -0.30, 0.05)), Euler((math.radians(50), 0, 0))),
|
||||
("side_gap", Vector((-0.45, -0.35, 0.40)), Euler((math.radians(88), 0, math.radians(-52)))),
|
||||
]
|
||||
for name, loc, rot in views:
|
||||
cob.location = loc
|
||||
cob.rotation_euler = rot
|
||||
sc.render.filepath = os.path.join(OUT, f"{name}.png")
|
||||
bpy.ops.render.render(write_still=True)
|
||||
print(f"rendered {name}")
|
||||
print("DBG_DONE")
|
||||
@@ -1,37 +0,0 @@
|
||||
# Probe: (1) duplicate coincident verts along the stomach/waist lines, (2) where the heal
|
||||
# strip actually was vs where the lines are.
|
||||
# blender --background --python dbg_lines.py -- <blend> <masks.npz>
|
||||
import bpy, sys
|
||||
import numpy as np
|
||||
|
||||
argv = sys.argv[sys.argv.index("--") + 1:]
|
||||
BLEND, MASKS = argv[0], argv[1]
|
||||
bpy.ops.wm.open_mainfile(filepath=BLEND)
|
||||
ob = max([o for o in bpy.data.objects if o.type == 'MESH'],
|
||||
key=lambda o: len(o.data.vertices))
|
||||
me = ob.data
|
||||
n_v = len(me.vertices)
|
||||
co = np.empty(n_v * 3)
|
||||
me.vertices.foreach_get("co", co)
|
||||
co = co.reshape(-1, 3)
|
||||
print(f"verts: {n_v}")
|
||||
|
||||
# coincident duplicates: exact-position hash
|
||||
key = np.round(co * 1e5).astype(np.int64)
|
||||
kview = key[:, 0] * 73856093 ^ key[:, 1] * 19349663 ^ key[:, 2] * 83492791
|
||||
uniq, counts = np.unique(kview, return_counts=True)
|
||||
dup_groups = (counts > 1).sum()
|
||||
print(f"coincident position groups: {dup_groups} (verts in dups: {counts[counts>1].sum()})")
|
||||
|
||||
# where are the dups? histogram by z in the torso front
|
||||
from collections import Counter
|
||||
dupset = set(uniq[counts > 1].tolist())
|
||||
isdup = np.array([k in dupset for k in kview])
|
||||
front = (np.abs(co[:, 0]) < 0.06) & (co[:, 1] < 0)
|
||||
print("z-slice | dup verts (front) | hemband verts (front)")
|
||||
M = np.load(MASKS)
|
||||
hemband = M["hemband"]
|
||||
for z0 in np.arange(0.44, 0.68, 0.02):
|
||||
zi = (co[:, 2] >= z0) & (co[:, 2] < z0 + 0.02) & front
|
||||
print(f" {z0:.2f}-{z0+0.02:.2f}: dup {int((zi & isdup).sum()):6d} hem {int((zi & hemband).sum()):6d}")
|
||||
print("PROBE_DONE")
|
||||
@@ -1,44 +0,0 @@
|
||||
import bpy, sys
|
||||
import numpy as np
|
||||
argv = sys.argv[sys.argv.index("--") + 1:]
|
||||
bpy.ops.wm.open_mainfile(filepath=argv[0])
|
||||
ob = max([o for o in bpy.data.objects if o.type=='MESH'], key=lambda o: len(o.data.vertices))
|
||||
me = ob.data
|
||||
act = np.load(argv[1])["active"]
|
||||
lay = me.color_attributes.new(name="dbg", type='FLOAT_COLOR', domain='POINT')
|
||||
n = len(me.vertices)
|
||||
cols = np.zeros((n, 4), dtype=np.float32)
|
||||
cols[:, 3] = 1.0
|
||||
cols[act, 0] = 1.0 # red = replaced
|
||||
cols[~act, 1] = 0.6 # green = untouched
|
||||
lay.data.foreach_set("color", cols.reshape(-1))
|
||||
mat = bpy.data.materials.new("dbg")
|
||||
mat.use_nodes = True
|
||||
nt = mat.node_tree
|
||||
vc = nt.nodes.new("ShaderNodeVertexColor")
|
||||
vc.layer_name = "dbg"
|
||||
bsdf = next(x for x in nt.nodes if x.type == 'BSDF_PRINCIPLED')
|
||||
nt.links.new(vc.outputs["Color"], bsdf.inputs["Base Color"])
|
||||
me.materials.clear()
|
||||
me.materials.append(mat)
|
||||
import math
|
||||
from mathutils import Vector
|
||||
scn = bpy.context.scene
|
||||
w = bpy.data.worlds.new("W"); w.color=(0.2,0.2,0.2); scn.world = w
|
||||
sun = bpy.data.objects.new("S", bpy.data.lights.new("S",'SUN'))
|
||||
sun.data.energy = 3; sun.data.use_shadow = False
|
||||
sun.rotation_euler = (math.radians(60),0,math.radians(75))
|
||||
bpy.context.collection.objects.link(sun)
|
||||
cam = bpy.data.objects.new("C", bpy.data.cameras.new("C"))
|
||||
cam.data.lens = 85
|
||||
bpy.context.collection.objects.link(cam)
|
||||
scn.camera = cam
|
||||
scn.render.engine = 'BLENDER_EEVEE'
|
||||
scn.render.resolution_x = scn.render.resolution_y = 1000
|
||||
ctr = Vector((0,0,0.675))
|
||||
yaw = math.radians(40)
|
||||
cam.location = ctr + Vector((math.sin(yaw)*0.66, -math.cos(yaw)*0.66, 0.02))
|
||||
cam.rotation_euler = (ctr - cam.location).to_track_quat('-Z','Y').to_euler()
|
||||
scn.render.filepath = argv[2]
|
||||
bpy.ops.render.render(write_still=True)
|
||||
print("DBG_OK")
|
||||
|
Before Width: | Height: | Size: 752 KiB |
@@ -1,140 +0,0 @@
|
||||
# Visual: paint masks as vertex colors and render the hip view — where are the lines
|
||||
# relative to the garment mask (red), rim band (green), slit seams (blue)?
|
||||
# blender --background --python dbg_maskviz.py -- <blend> <masks.npz> <raw.glb> <out.png>
|
||||
import bpy, sys, math
|
||||
import numpy as np
|
||||
from mathutils import Vector, Euler
|
||||
from mathutils.kdtree import KDTree
|
||||
|
||||
argv = sys.argv[sys.argv.index("--") + 1:]
|
||||
BLEND, MASKS, RAW, OUT = argv[0], argv[1], argv[2], argv[3]
|
||||
|
||||
bpy.ops.wm.open_mainfile(filepath=BLEND)
|
||||
ob = max([o for o in bpy.data.objects if o.type == 'MESH'],
|
||||
key=lambda o: len(o.data.vertices))
|
||||
me = ob.data
|
||||
n_v = len(me.vertices)
|
||||
co = np.empty(n_v * 3)
|
||||
me.vertices.foreach_get("co", co)
|
||||
co = co.reshape(-1, 3)
|
||||
M = np.load(MASKS)
|
||||
garment = M["garment"]
|
||||
|
||||
n_e = len(me.edges)
|
||||
ev = np.empty(n_e * 2, dtype=np.int32)
|
||||
me.edges.foreach_get("vertices", ev)
|
||||
ev = ev.reshape(-1, 2)
|
||||
|
||||
before = set(bpy.data.objects)
|
||||
bpy.ops.import_scene.gltf(filepath=RAW)
|
||||
new = [o for o in bpy.data.objects if o not in before]
|
||||
raw = max([o for o in new if o.type == 'MESH'], key=lambda o: len(o.data.vertices))
|
||||
rme = raw.data
|
||||
rn = len(rme.vertices)
|
||||
rco = np.empty(rn * 3)
|
||||
rme.vertices.foreach_get("co", rco)
|
||||
rco = rco.reshape(-1, 3)
|
||||
l_tot = np.empty(len(rme.polygons), dtype=np.int32)
|
||||
rme.polygons.foreach_get("loop_total", l_tot)
|
||||
l_start = np.empty(len(rme.polygons), dtype=np.int32)
|
||||
rme.polygons.foreach_get("loop_start", l_start)
|
||||
l_v = np.empty(len(rme.loops), dtype=np.int32)
|
||||
rme.loops.foreach_get("vertex_index", l_v)
|
||||
ecount = {}
|
||||
for fs, ft in zip(l_start, l_tot):
|
||||
idxs = l_v[fs:fs + ft]
|
||||
for k in range(ft):
|
||||
a, b = idxs[k], idxs[(k + 1) % ft]
|
||||
kk = (a, b) if a < b else (b, a)
|
||||
ecount[kk] = ecount.get(kk, 0) + 1
|
||||
rbnd = np.zeros(rn, dtype=bool)
|
||||
for (a, b), c in ecount.items():
|
||||
if c == 1:
|
||||
rbnd[a] = rbnd[b] = True
|
||||
for o in new:
|
||||
bpy.data.objects.remove(o, do_unlink=True)
|
||||
|
||||
kd = KDTree(n_v)
|
||||
for i in range(n_v):
|
||||
kd.insert(Vector(co[i]), i)
|
||||
kd.balance()
|
||||
seam = np.zeros(n_v, dtype=bool)
|
||||
for p in rco[rbnd]:
|
||||
hit = kd.find(Vector(p))
|
||||
if hit[0] is not None and hit[2] < 0.002:
|
||||
seam[hit[1]] = True
|
||||
|
||||
|
||||
def grow(mask, rings):
|
||||
m = mask.copy()
|
||||
for _ in range(rings):
|
||||
h = m[ev[:, 0]] | m[ev[:, 1]]
|
||||
m2 = m.copy()
|
||||
m2[ev[:, 0]] |= h
|
||||
m2[ev[:, 1]] |= h
|
||||
m = m2
|
||||
return m
|
||||
|
||||
|
||||
rim = grow(garment, 10) & ~(~grow(~garment, 10))
|
||||
seam_g = grow(seam, 1)
|
||||
|
||||
col = np.ones((n_v, 4))
|
||||
col[:, :3] = 0.75
|
||||
col[garment, 0] = 0.9
|
||||
col[garment, 1] = 0.45
|
||||
col[garment, 2] = 0.45
|
||||
col[rim, 0] = 0.3
|
||||
col[rim, 1] = 0.8
|
||||
col[rim, 2] = 0.3
|
||||
col[seam_g, 0] = 0.15
|
||||
col[seam_g, 1] = 0.25
|
||||
col[seam_g, 2] = 0.95
|
||||
|
||||
vc = me.color_attributes.new("viz", 'FLOAT_COLOR', 'POINT')
|
||||
vc.data.foreach_set("color", col.reshape(-1))
|
||||
|
||||
mat = bpy.data.materials.new("viz")
|
||||
mat.use_nodes = True
|
||||
nt = mat.node_tree
|
||||
b = nt.nodes["Principled BSDF"]
|
||||
attr = nt.nodes.new("ShaderNodeVertexColor")
|
||||
attr.layer_name = "viz"
|
||||
nt.links.new(attr.outputs["Color"], b.inputs["Base Color"])
|
||||
b.inputs["Roughness"].default_value = 0.9
|
||||
me.materials.clear()
|
||||
me.materials.append(mat)
|
||||
|
||||
for o in list(bpy.data.objects):
|
||||
if o.type in ('LIGHT', 'CAMERA'):
|
||||
bpy.data.objects.remove(o, do_unlink=True)
|
||||
sc = bpy.context.scene
|
||||
sc.render.engine = 'BLENDER_EEVEE'
|
||||
sc.render.resolution_x = sc.render.resolution_y = 1000
|
||||
wd = bpy.data.worlds.new("w")
|
||||
wd.use_nodes = True
|
||||
wd.node_tree.nodes["Background"].inputs[0].default_value = (0.18, 0.18, 0.18, 1)
|
||||
sc.world = wd
|
||||
ld = bpy.data.lights.new("s", 'SUN')
|
||||
ld.energy = 3.0
|
||||
ld.use_shadow = False
|
||||
lo = bpy.data.objects.new("s", ld)
|
||||
lo.rotation_euler = Euler((math.radians(60), 0, math.radians(-30)))
|
||||
bpy.context.collection.objects.link(lo)
|
||||
ld2 = bpy.data.lights.new("s2", 'SUN')
|
||||
ld2.energy = 1.2
|
||||
ld2.use_shadow = False
|
||||
lo2 = bpy.data.objects.new("s2", ld2)
|
||||
lo2.rotation_euler = Euler((math.radians(120), 0, math.radians(150)))
|
||||
bpy.context.collection.objects.link(lo2)
|
||||
|
||||
cam = bpy.data.cameras.new("c")
|
||||
cam.lens = 60
|
||||
cob = bpy.data.objects.new("c", cam)
|
||||
bpy.context.collection.objects.link(cob)
|
||||
sc.camera = cob
|
||||
cob.location = Vector((0.0, -0.95, 0.53))
|
||||
cob.rotation_euler = Euler((math.radians(90), 0, 0))
|
||||
sc.render.filepath = OUT
|
||||
bpy.ops.render.render(write_still=True)
|
||||
print("VIZ_DONE")
|
||||
@@ -1,52 +0,0 @@
|
||||
# Probe: was the rosy V repainted? Sample per-vertex basecolor in the V region vs belly,
|
||||
# and re-run the rosy classifier to see who it caught.
|
||||
# blender --background --python dbg_rosy.py -- <07_textured.blend>
|
||||
import bpy, sys
|
||||
import numpy as np
|
||||
|
||||
BLEND = sys.argv[sys.argv.index("--") + 1]
|
||||
bpy.ops.wm.open_mainfile(filepath=BLEND)
|
||||
ob = max([o for o in bpy.data.objects if o.type == 'MESH'],
|
||||
key=lambda o: len(o.data.vertices))
|
||||
me = ob.data
|
||||
n_v = len(me.vertices)
|
||||
co = np.empty(n_v * 3)
|
||||
me.vertices.foreach_get("co", co)
|
||||
co = co.reshape(-1, 3)
|
||||
|
||||
base = next(i for i in bpy.data.images if "basecolor" in i.name.lower())
|
||||
w, h = base.size
|
||||
buf = np.empty(w * h * 4, dtype=np.float32)
|
||||
base.pixels.foreach_get(buf)
|
||||
rgb = buf.reshape(h, w, 4)
|
||||
|
||||
loops_v = np.empty(len(me.loops), dtype=np.int32)
|
||||
me.loops.foreach_get("vertex_index", loops_v)
|
||||
uv = np.empty(len(me.loops) * 2)
|
||||
me.uv_layers.active.data.foreach_get("uv", uv)
|
||||
uv = uv.reshape(-1, 2)
|
||||
lx = np.clip(uv[:, 0], 0, 1) * (w - 1)
|
||||
ly = np.clip(uv[:, 1], 0, 1) * (h - 1)
|
||||
first_loop = np.full(n_v, len(loops_v), dtype=np.int64)
|
||||
np.minimum.at(first_loop, loops_v, np.arange(len(loops_v), dtype=np.int64))
|
||||
first_loop = np.minimum(first_loop, len(loops_v) - 1)
|
||||
vcol = rgb[ly[first_loop].astype(int), lx[first_loop].astype(int), :3]
|
||||
|
||||
regions = {
|
||||
"V_chest (z .78-.85, y<-.02)": (np.abs(co[:, 0]) < 0.06) & (co[:, 1] < -0.02) & (co[:, 2] > 0.78) & (co[:, 2] < 0.85),
|
||||
"throat (z .85-.90, y<-.02)": (np.abs(co[:, 0]) < 0.04) & (co[:, 1] < -0.02) & (co[:, 2] > 0.85) & (co[:, 2] < 0.90),
|
||||
"cup_R (z .68-.74, x<-.03)": (co[:, 0] < -0.03) & (co[:, 0] > -0.09) & (co[:, 1] < -0.02) & (co[:, 2] > 0.68) & (co[:, 2] < 0.74),
|
||||
"belly (z .55-.60)": (np.abs(co[:, 0]) < 0.06) & (co[:, 1] < 0) & (co[:, 2] > 0.55) & (co[:, 2] < 0.60),
|
||||
"shoulder (z .84-.86, |x|>.10)": (np.abs(co[:, 0]) > 0.10) & (np.abs(co[:, 0]) < 0.16) & (co[:, 2] > 0.84) & (co[:, 2] < 0.86),
|
||||
}
|
||||
band = (co[:, 2] > 0.30) & (co[:, 2] < 0.905)
|
||||
rg = vcol[:, 0] - vcol[:, 1]
|
||||
print(f"whole-band med rg: {np.median(rg[band]):.3f}")
|
||||
for name, m in regions.items():
|
||||
if not m.any():
|
||||
print(f"{name}: EMPTY")
|
||||
continue
|
||||
c = vcol[m]
|
||||
print(f"{name}: n={m.sum():6d} mean RGB ({c[:,0].mean():.3f},{c[:,1].mean():.3f},{c[:,2].mean():.3f}) "
|
||||
f"rg med {np.median(rg[m]):.3f}")
|
||||
print("PROBE_DONE")
|
||||
@@ -1,68 +0,0 @@
|
||||
00:05.516 blend | Read blend: "C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\10_welded.blend"
|
||||
[diag 0.3s] mesh 'tripo_node_9701d6b7-20ea-449d-8637-2fc9d4bc199c': 860389 verts, 1721808 faces, custom_normals=True
|
||||
BBOX z 0.000..0.979 x -0.463..0.463 y -0.093..0.089
|
||||
TOPO boundary_edges=830 nonmanifold_edges=1649 degenerate_faces=3 loose_verts=0
|
||||
BOUNDARY-EDGE z histogram (holes live here):
|
||||
z 0.002-0.082: 176
|
||||
z 0.082-0.163: 117
|
||||
z 0.163-0.244: 25
|
||||
z 0.244-0.325: 43
|
||||
z 0.325-0.406: 179
|
||||
z 0.406-0.487: 31
|
||||
z 0.487-0.567: 103
|
||||
z 0.567-0.648: 211
|
||||
z 0.648-0.729: 329
|
||||
z 0.729-0.810: 120
|
||||
z 0.810-0.891: 169
|
||||
z 0.891-0.972: 157
|
||||
KINK > 8.0deg : 15535 verts (groove depth p05=-0.685 mm, median=+0.007 mm, p95=+0.865 mm)
|
||||
KINK >12.0deg : 9704 verts (groove depth p05=-0.771 mm, median=+0.006 mm, p95=+0.866 mm)
|
||||
KINK >20.0deg : 4767 verts (groove depth p05=-0.808 mm, median=+0.011 mm, p95=+0.912 mm)
|
||||
KINK z histogram (the lines):
|
||||
z 0.329-0.365: 28
|
||||
z 0.365-0.400: 280
|
||||
z 0.400-0.436: 138
|
||||
z 0.436-0.472: 307
|
||||
z 0.472-0.507: 430
|
||||
z 0.507-0.543: 515
|
||||
z 0.543-0.579: 399
|
||||
z 0.614-0.650: 845
|
||||
z 0.650-0.686: 423
|
||||
z 0.686-0.722: 907
|
||||
z 0.722-0.757: 844
|
||||
z 0.757-0.793: 794
|
||||
z 0.793-0.829: 30
|
||||
z 0.829-0.864: 559
|
||||
z 0.864-0.900: 3192
|
||||
SPLIT-PANEL test on 4852 kink verts: 2850 (58.7%) have an unmerged twin within 0.8 mm
|
||||
|
||||
=== CLEAVAGE: medial corridor cross-sections y(x) ===
|
||||
z=0.620 sternum y=-0.0679 max concave curvature 6.9 1/m -> fillet radius 145.5 mm
|
||||
z=0.635 sternum y=-0.0695 max concave curvature 13.7 1/m -> fillet radius 73.2 mm
|
||||
z=0.650 sternum y=-0.0720 max concave curvature 14.8 1/m -> fillet radius 67.6 mm
|
||||
z=0.665 sternum y=-0.0749 max concave curvature 1.8 1/m -> fillet radius 552.2 mm
|
||||
z=0.680 sternum y=-0.0789 max concave curvature 16.8 1/m -> fillet radius 59.4 mm
|
||||
z=0.695 sternum y=-0.0833 max concave curvature 7.0 1/m -> fillet radius 142.0 mm
|
||||
z=0.710 sternum y=-0.0772 max concave curvature 121.9 1/m -> fillet radius 8.2 mm <-- SHARP
|
||||
z=0.725 sternum y=-0.0508 max concave curvature 400.7 1/m -> fillet radius 2.5 mm <-- SHARP
|
||||
z=0.740 sternum y=-0.0436 max concave curvature 22.2 1/m -> fillet radius 45.0 mm
|
||||
|
||||
=== CLEAVAGE: depth of the notch (breast apex y vs sternum y) ===
|
||||
z=0.620 sternum -0.0679 apex -0.0633 notch -4.5 mm
|
||||
z=0.635 sternum -0.0695 apex -0.0657 notch -3.9 mm
|
||||
z=0.650 sternum -0.0720 apex -0.0690 notch -2.9 mm
|
||||
z=0.665 sternum -0.0749 apex -0.0749 notch +0.0 mm
|
||||
z=0.680 sternum -0.0789 apex -0.0825 notch +3.6 mm
|
||||
z=0.695 sternum -0.0835 apex -0.0891 notch +5.6 mm
|
||||
z=0.710 sternum -0.0772 apex -0.0887 notch +11.6 mm
|
||||
z=0.725 sternum -0.0509 apex -0.0752 notch +24.3 mm
|
||||
z=0.740 sternum -0.0436 apex -0.0470 notch +3.3 mm
|
||||
[diag 15.6s] current basecolor 4096x4096 'lena+glb_basecolor.jpg.003'
|
||||
00:21.547 blend | Read blend: "C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\00_welded.blend"
|
||||
[diag 17.0s] original basecolor 4096x4096 'lena+glb_basecolor.jpg'
|
||||
|
||||
DISCOLOUR: repainted texels = 0 (0.00% of atlas)
|
||||
DIAG_DONE
|
||||
Blender 5.1.2 (hash ec6e62d40fa9 built 2026-05-19 01:37:34)
|
||||
|
||||
Blender quit
|
||||
@@ -1,14 +0,0 @@
|
||||
00:01.875 blend | Read blend: "C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\29b_final.blend"
|
||||
[export] tripo_node_9701d6b7-20ea-449d-8637-2fc9d4bc199c: 860389v 1721805f height 0.9792 units, images []
|
||||
INFO Draco mesh compression is available, use library at C:\Program Files\Blender Foundation\Blender 5.1\5.1\scripts\addons_core\io_scene_gltf2\extern_draco.dll
|
||||
08:55:39 | INFO: Starting glTF 2.0 export
|
||||
08:55:40 | INFO: Extracting primitive: meshes[0]
|
||||
08:55:42 | WARNING: More than one shader node tex image used for a texture. The resulting glTF sampler will behave like the first shader node tex image.
|
||||
08:55:42 | INFO: Primitives created: 1
|
||||
08:55:43 | INFO: Finished glTF 2.0 export in 3.3285176753997803 s
|
||||
|
||||
[export] WROTE ../lena_nude_sculpt_glb_v03.glb (5.0s)
|
||||
EXPORT_DONE
|
||||
Blender 5.1.2 (hash ec6e62d40fa9 built 2026-05-19 01:37:34)
|
||||
|
||||
Blender quit
|
||||
@@ -1,40 +0,0 @@
|
||||
00:01.891 blend | Read blend: "C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\10_welded.blend"
|
||||
00:09.969 blend | Read blend: "C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\00_welded.blend"
|
||||
|
||||
===== 10_welded.blend =====
|
||||
body 'tripo_node_9701d6b7-20ea-449d-8637-2fc9d4bc199c' 860389v materials=['tripo_material_9701d6b7-20ea-449d-8637-2fc9d4bc199c']
|
||||
IMG 'lena+glb_basecolor.jpg' 4096x4096 packed=True file='' mean [0.6656 0.4327 0.2466] std [0.062 0.1002 0.0658]
|
||||
IMG 'lena+glb_basecolor.jpg.002' 4096x4096 packed=True file='' mean [0.6601 0.4317 0.2562] std [0.0664 0.1041 0.0752]
|
||||
IMG 'lena+glb_basecolor.jpg.003' 4096x4096 packed=True file='' mean [0.6601 0.4317 0.2562] std [0.0664 0.1041 0.0752]
|
||||
IMG 'lena+glb_normal.jpg' 4096x4096 packed=True file='' mean [0.5001 0.4997 0.9995] std [0.0201 0.0204 0.0171]
|
||||
IMG 'lena+glb_normal.jpg.002' 4096x4096 packed=True file='' mean [0.4999 0.4997 0.9991] std [0.0268 0.0269 0.0214]
|
||||
IMG 'lena+glb_normal.jpg.003' 4096x4096 packed=True file='' mean [0.4999 0.4997 0.9991] std [0.0268 0.0269 0.0214]
|
||||
IMG 'lena+glb_rm.jpg' 4096x4096 packed=True file='' mean [1. 0.4054 0.0322] std [0.0035 0.0987 0.0198]
|
||||
IMG 'lena+glb_rm.jpg.002' 4096x4096 packed=True file='' mean [1. 0.4585 0.0304] std [0.004 0.1722 0.0204]
|
||||
IMG 'lena+glb_rm.jpg.003' 4096x4096 packed=True file='' mean [1. 0.4585 0.0304] std [0.004 0.1722 0.0204]
|
||||
MAT 'tripo_material_9701d6b7-20ea-449d-8637-2fc9d4bc199c':
|
||||
TEX_IMAGE node 'Image Texture' image='lena+glb_basecolor.jpg' -> ['Principled BSDF.Base Color']
|
||||
TEX_IMAGE node 'Image Texture.001' image='lena+glb_rm.jpg' -> ['Separate Color.Color']
|
||||
TEX_IMAGE node 'Image Texture.002' image='lena+glb_normal.jpg' -> ['Normal Map.Color']
|
||||
BSDF.Base Color <- Image Texture image='lena+glb_basecolor.jpg'
|
||||
BSDF.Normal <- Normal Map
|
||||
BSDF.Roughness <- Separate Color
|
||||
BSDF.Metallic <- Separate Color
|
||||
|
||||
===== 00_welded.blend =====
|
||||
body 'tripo_node_9701d6b7-20ea-449d-8637-2fc9d4bc199c' 953966v materials=['tripo_material_9701d6b7-20ea-449d-8637-2fc9d4bc199c']
|
||||
IMG 'lena+glb_basecolor.jpg' 4096x4096 packed=True file='' mean [0.6601 0.4317 0.2562] std [0.0664 0.1041 0.0752]
|
||||
IMG 'lena+glb_normal.jpg' 4096x4096 packed=True file='' mean [0.4999 0.4997 0.9991] std [0.0268 0.0269 0.0214]
|
||||
IMG 'lena+glb_rm.jpg' 4096x4096 packed=True file='' mean [1. 0.4585 0.0304] std [0.004 0.1722 0.0204]
|
||||
MAT 'tripo_material_9701d6b7-20ea-449d-8637-2fc9d4bc199c':
|
||||
TEX_IMAGE node 'Image Texture' image='lena+glb_basecolor.jpg' -> ['Principled BSDF.Base Color']
|
||||
TEX_IMAGE node 'Image Texture.001' image='lena+glb_rm.jpg' -> ['Separate Color.Color']
|
||||
TEX_IMAGE node 'Image Texture.002' image='lena+glb_normal.jpg' -> ['Normal Map.Color']
|
||||
BSDF.Base Color <- Image Texture image='lena+glb_basecolor.jpg'
|
||||
BSDF.Normal <- Normal Map
|
||||
BSDF.Roughness <- Separate Color
|
||||
BSDF.Metallic <- Separate Color
|
||||
IMAGES_DONE
|
||||
Blender 5.1.2 (hash ec6e62d40fa9 built 2026-05-19 01:37:34)
|
||||
|
||||
Blender quit
|
||||
@@ -1,20 +0,0 @@
|
||||
00:01.954 blend | Read blend: "C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\10_welded.blend"
|
||||
[line 0.3s] in: 860389v 1721808f
|
||||
[line 2.1s] boundary loops: 689 (edges: 830)
|
||||
[line 407.3s] pinholes filled: 689 loops, 0 skipped (> 8 edges or error)
|
||||
[line 407.9s] deleted 3 degenerate faces
|
||||
[line 409.7s] after fill: 860389v 1721805f boundary_edges=839 nonmanifold=1649
|
||||
[line 410.5s] zone 699714 verts, protected 3442
|
||||
[line 424.4s] round 1: seed 5111 -> band 12595 (S=19744) CG it=862 rel=9.9e-15 moved max 16.922 mm, median(band) 0.888 mm
|
||||
[line 428.6s] after: seed mask 2790, kink>12deg 7449, kink>20deg 3649
|
||||
[line 435.6s] round 2: seed 2790 -> band 7862 (S=13441) CG it=551 rel=9.1e-15 moved max 15.878 mm, median(band) 0.546 mm
|
||||
[line 439.6s] after: seed mask 2154, kink>12deg 7186, kink>20deg 3680
|
||||
[line 446.5s] round 3: seed 2154 -> band 6157 (S=10984) CG it=705 rel=7.6e-15 moved max 15.741 mm, median(band) 0.434 mm
|
||||
[line 450.6s] after: seed mask 1860, kink>12deg 6994, kink>20deg 3607
|
||||
[line 453.2s] TOTAL displacement: max 17.261 mm, 16589 verts moved >0.05 mm
|
||||
Info: Saved as "17_healed.blend"
|
||||
[line 453.3s] WROTE 17_healed.blend
|
||||
LINE_DONE
|
||||
Blender 5.1.2 (hash ec6e62d40fa9 built 2026-05-19 01:37:34)
|
||||
|
||||
Blender quit
|
||||
@@ -1,36 +0,0 @@
|
||||
import bpy, sys
|
||||
import numpy as np
|
||||
W = r"C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude"
|
||||
bpy.ops.wm.open_mainfile(filepath=W + r"\03_sculpted.blend")
|
||||
ob = max([o for o in bpy.data.objects if o.type=='MESH'], key=lambda o: len(o.data.vertices))
|
||||
me = ob.data
|
||||
n_v = len(me.vertices)
|
||||
co = np.empty(n_v*3); me.vertices.foreach_get("co", co); co = co.reshape(-1,3)
|
||||
# face normals -> vertex normal variance (crease detector)
|
||||
n_f = len(me.polygons)
|
||||
fn = np.empty(n_f*3); me.polygons.foreach_get("normal", fn); fn = fn.reshape(-1,3)
|
||||
lt = np.empty(n_f, dtype=np.int32); me.polygons.foreach_get("loop_total", lt)
|
||||
ls = np.empty(n_f, dtype=np.int32); me.polygons.foreach_get("loop_start", ls)
|
||||
lv = np.empty(len(me.loops), dtype=np.int32); me.loops.foreach_get("vertex_index", lv)
|
||||
acc = np.zeros((n_v,3)); acc2 = np.zeros(n_v); cnt = np.zeros(n_v)
|
||||
for fi in range(n_f):
|
||||
for k in range(ls[fi], ls[fi]+lt[fi]):
|
||||
vi = lv[k]
|
||||
acc[vi] += fn[fi]; cnt[vi] += 1
|
||||
mean = acc / np.maximum(cnt,1)[:,None]
|
||||
ml = np.linalg.norm(mean, axis=1) # 1 = all normals aligned; low = crease/facet
|
||||
window = (co[:,1] < 0) & (co[:,2] > 0.58) & (co[:,2] < 0.82) & (np.abs(co[:,0]) < 0.12)
|
||||
bad = window & (ml < 0.90) & (cnt > 0)
|
||||
print(f"creased verts in front-chest window: {bad.sum()}")
|
||||
if bad.sum():
|
||||
b = co[bad]
|
||||
print(f"bbox: x [{b[:,0].min():+.3f},{b[:,0].max():+.3f}] "
|
||||
f"y [{b[:,1].min():+.3f},{b[:,1].max():+.3f}] "
|
||||
f"z [{b[:,2].min():+.3f},{b[:,2].max():+.3f}]")
|
||||
# cluster by z-decile to see structure
|
||||
for zlo in np.arange(0.58, 0.82, 0.02):
|
||||
m = bad & (co[:,2] >= zlo) & (co[:,2] < zlo+0.02)
|
||||
if m.sum() > 30:
|
||||
print(f" z {zlo:.2f}-{zlo+0.02:.2f}: n={m.sum():5d} "
|
||||
f"x[{co[m,0].min():+.3f},{co[m,0].max():+.3f}]")
|
||||
print("LOCATE_DONE")
|
||||
@@ -1,26 +0,0 @@
|
||||
00:01.797 blend | Read blend: "C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\17_healed.blend"
|
||||
[nrm 0.3s] tripo_node_9701d6b7-20ea-449d-8637-2fc9d4bc199c: 860389v 1721805f 5165415 loops has_custom_normals=True
|
||||
[nrm 0.5s] flat-shaded polygons: 208 of 1721805
|
||||
CORNER-vs-VERTEX normal deviation: mean 0.008deg p50 0.002 p95 0.025 p99.9 0.034 max 90.000
|
||||
loops deviating >5deg: 15 (0.000%)
|
||||
loops deviating >15deg: 12 (0.000%)
|
||||
torso verts whose corner normals deviate >10deg from smooth: 4
|
||||
[nrm 2.8s] custom normals: had=True now has_custom_normals=False cleared=True
|
||||
C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\18_normal_probe.py:120: DeprecationWarning: 'Material.use_nodes' is expected to be removed in Blender 6.0
|
||||
clay.use_nodes = True
|
||||
00:06.094 render | Saved: 'C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\review_18\chest_clay_0.png'
|
||||
[nrm 4.3s] render chest_clay_0
|
||||
00:06.578 render | Saved: 'C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\review_18\chest_clay_40.png'
|
||||
[nrm 4.8s] render chest_clay_40
|
||||
00:08.047 render | Saved: 'C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\review_18\chest_tex_0.png'
|
||||
[nrm 6.3s] render chest_tex_0
|
||||
00:08.500 render | Saved: 'C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\review_18\hip_clay_0.png'
|
||||
[nrm 6.7s] render hip_clay_0
|
||||
00:08.875 render | Saved: 'C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\review_18\full_clay_0.png'
|
||||
[nrm 7.1s] render full_clay_0
|
||||
Info: Saved as "18_smoothnrm.blend"
|
||||
[nrm 7.2s] WROTE 18_smoothnrm.blend
|
||||
NRM_DONE
|
||||
Blender 5.1.2 (hash ec6e62d40fa9 built 2026-05-19 01:37:34)
|
||||
|
||||
Blender quit
|
||||