ship(mako): mako_base_v01 — fullhead body (24k body / full-res 158k head) as male Ariki default

Decimate body-only (01), AccuRig manual rig, index-exact graft (02, mesh moved
0mm), QuatSkin via ariki-game converter (HEAD_SAFE=1, body-only normals fix).
Ships as Ariki_Male_Mako.glb, lineage MALE-MAKO-FULLHEAD-V1. First mako ship.
Height 1.818m provisional. Ceremony per REGISTRY.md; irreplaceable AccuRig FBX
preserved in rig/.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-12 10:33:15 -07:00
parent a4e16315ae
commit 6dc60b0d32
17 changed files with 1247 additions and 2 deletions
+21 -2
View File
@@ -23,7 +23,7 @@ characters/
lena_base_v01/ SHIPPED 2026-08-10 <- frozen lena_base_v01/ SHIPPED 2026-08-10 <- frozen
lena_base_v02/ the next one to ship lena_base_v02/ the next one to ship
male/ male/
mako_base_v01/ (none yet — mako has never shipped) mako_base_v01/ SHIPPED 2026-08-12 <- frozen
work/ work/
lena/ the live lane: recipes + masters. Grammar-exempt. lena/ the live lane: recipes + masters. Grammar-exempt.
mako/ mako/
@@ -136,7 +136,7 @@ names without touching the immutable delivered files.
| character | gender | parent original | ships so far | | character | gender | parent original | ships so far |
|---|---|---|---| |---|---|---|---|
| `lena` | female | `female_lena_tripo` | `lena_base_v01` | | `lena` | female | `female_lena_tripo` | `lena_base_v01` |
| `mako` | male | `male_base_bald_tripo_v1` | none | | `mako` | male | `male_base_bald_tripo_v1` | `mako_base_v01` |
Spelling history for the male: game wiki "Mako", AccuRig-era typo "moka", also "moko". 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. Canonical filename token is **`mako`**; the others are never used in new files.
@@ -150,6 +150,7 @@ Status vocabulary: `shipped / superseded / rolled-back`.
| folder | character | date | status | ship name in ariki-game | sha256 (repo-side body) | | 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` | | `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_base_v01` — what it is
@@ -178,3 +179,21 @@ Ancestry, condensed from the 20 intermediate stages that produced her (all prune
mannequin with no muscle definition. The shipped body has athletic definition 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 (`42_musculature.py` strength 1.0, `SPINE=0.34`). Flagged here so it is a known
decision rather than a drift. 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.
+43
View File
@@ -0,0 +1,43 @@
# mako_base_v01 — first shipped mako
**Shipped 2026-08-12** as `Ariki_Male_Mako.glb` → wired as the male Ariki player default
in ariki-game (`CharacterBuildData.GetBodyPath` male branch, lineage
`MALE-MAKO-FULLHEAD-V1`). The first mako ship ever: until this folder, the in-game
"Mako" was the July graft (Quaternius Superhero_Male body + Lena's head, `MALE-MAKO-D-V1`),
kept on disk as the comparison body.
## What it is
Özlem's Tripo male sculpt (`originals/male/male_base_bald_tripo_v1`, ~1.9M tris) with the
**body decimated and the head untouched**: body 24k tris, hands 40k, head 158,575 tris at
full scan resolution — cut at the neck (86.5% of bbox height). Face features (brows, lash
lines, lips) are raised geometry on the scan, which is why the head rides along whole.
| stage | result |
|---|---|
| decimate (body/hands only) | 222,575 tris, head bit-identical (`work/mako/01_decimate_fullhead.py`) |
| carrier | 1.700 m, materials stripped (Lena stage-45 recipe) |
| AccuRig | 1.10, manual session by Jeremy 2026-08-12 — **`rig/mako_base_accurig_fbx_v01/` is irreplaceable** |
| graft | index-exact, 101 CC_Base_* bones; mesh moved 0 mm; rest drift 0.0003 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-mitt fingers, body-only weld + body-only split-normals clear (head shading preserved) |
## Files
- `mako_base_accurig_glb_v01.glb` — the repo-side body (101 CC bones), input to the game converter
- `rig/mako_base_accurig_fbx_v01/` — manual AccuRig output; no script regenerates it
- `rig/mako_base_tpose_fbx_v01.fbx` — the carrier fed into AccuRig; regenerable via the work-lane recipes
- `preview/` — converter T-pose renders, full-res head closeup, hand closeup, in-game loco bed shot
## Known decisions / departures
- **Height 1.818 m is provisional** — pelvis-anchored then stretched, following the IB-lane
"male reads taller than Lena (1.777)" convention. Not eye-locked by Ozan/Can.
- **Neck cut is hard** (86.5% line, head in every protect group). Lena's v02 candidate uses a
feathered ramp (`work/lena/48_decimate_headsafe.py`) — nicer transition; adopt if v02 ships.
- The ship name deliberately contains neither `QuatSkin` nor `QuatBody`:
`PlayerController` gates Lena's eyes/brows/hair offsets on `Contains("QuatSkin")` and the
Mako-D runtime warp on `Contains("QuatBody")`.
- AccuRig moved some carrier verts (similarity-fit residual p99 6.5 mm) — weights copied by
index regardless and every rest-pose gate passed; don't treat the carrier FBX as
geometrically pristine.
- No male LOD1 yet — `CharacterBodyLodCatalog` still returns null for males.
Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 410 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

@@ -0,0 +1,429 @@
{
"mako_fullhead_rigged": {
"Version": "1.10.1822.1",
"Scene": {
"Name": true,
"SupportShaderSelect": true
},
"Export Directory": "C:/Users/Jeremy/tinqs/animation/characters/rig-work",
"Object": {
"mako_fullhead_rigged": {
"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": {}
}
}
}
}
}
}
}
}
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,429 @@
{
"mako_fullhead_rigged": {
"Version": "1.10.1822.1",
"Scene": {
"Name": true,
"SupportShaderSelect": true
},
"Export Directory": "C:/Users/Jeremy/tinqs/animation/characters/rig-work",
"Object": {
"mako_fullhead_rigged": {
"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": {}
}
}
}
}
}
}
}
}
+2
View File
@@ -0,0 +1,2 @@
# Masters for the mako fullhead lane (ship: male/mako_base_v01)
mako_fullhead_rigged.blend # 02_graft output — the rigged master the ship GLB came from
@@ -0,0 +1,114 @@
# Mako work lane, stage 01: decimate the Tripo original with the HEAD FULLY PROTECTED.
# Body -> 24k tris, hands -> 40k tris, head above the neck line -> UNTOUCHED (full
# Tripo resolution). Jeremy's ask 2026-08-11: a decimated mako that cuts at the neck
# like the Lena head graft did, so the face keeps every tri for later head work.
#
# Differences from tools/rigbait_decimate.py (same region geometry, different intent):
# * NO P_HEAD pass — head verts sit in every protect group instead.
# * Output is a GLB master (packed textures carried), not an FBX rig bait.
# Same as the bait: NO scale / recenter (native ~0.98 m, feet at Z=0); region cuts are
# bbox-relative — neck at 86.5% of height, hands beyond 75.6% of half-span.
#
# blender --background --python 01_decimate_fullhead.py -- <src.glb> <out.glb> <review_dir> [body hand]
import bpy, os, sys, math
from mathutils import Vector
args = sys.argv[sys.argv.index("--") + 1:]
SRC, OUT_GLB, REVIEW = args[0], args[1], args[2]
T_BODY, T_HAND = (int(a) for a in args[3:5]) if len(args) >= 5 else (24000, 40000)
os.makedirs(os.path.dirname(os.path.abspath(OUT_GLB)), exist_ok=True)
os.makedirs(REVIEW, exist_ok=True)
bpy.context.preferences.filepaths.save_version = 0
bpy.ops.wm.read_factory_settings(use_empty=True)
bpy.ops.import_scene.gltf(filepath=SRC)
body = max((o for o in bpy.data.objects if o.type == "MESH"), key=lambda o: len(o.data.vertices))
me = body.data
bpy.ops.object.select_all(action="DESELECT")
body.select_set(True)
bpy.context.view_layer.objects.active = body
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
bb = [body.matrix_world @ Vector(c) for c in body.bound_box]
mnz, mxz = min(v.z for v in bb), max(v.z for v in bb)
half_span = max(abs(v.x) for v in bb)
NECK_Z = mnz + 0.865 * (mxz - mnz)
HAND_X = 0.756 * half_span
print(f"[fullhead] height={mxz-mnz:.3f} half_span={half_span:.3f} neck_z={NECK_Z:.3f} hand_x={HAND_X:.3f}")
# anti-facet shading fixes (shading only, geometry untouched)
try: bpy.ops.mesh.customdata_custom_splitnormals_clear()
except Exception: pass
if "sharp_edge" in me.attributes:
me.attributes.remove(me.attributes["sharp_edge"])
def is_head(v): return v.co.z > NECK_Z
def is_hand(v): return abs(v.co.x) > HAND_X
def group(name, pred):
g = body.vertex_groups.new(name=name)
g.add([v.index for v in me.vertices if pred(v)], 1.0, "REPLACE")
return g
head_tris0 = sum(1 for p in me.polygons if all(is_head(me.vertices[vi]) for vi in p.vertices))
hand_tris0 = sum(1 for p in me.polygons if all(is_hand(me.vertices[vi]) for vi in p.vertices))
print(f"[fullhead] regions: head_tris={head_tris0} hand_tris={hand_tris0} total={len(me.polygons)}")
if head_tris0 < 20000 or hand_tris0 < 20000:
raise SystemExit(f"[fullhead] region cut looks wrong (head={head_tris0}, hands={hand_tris0})")
# Two passes only. The head is inside every protect group, so no pass ever touches it.
group("P_BODY", lambda v: is_head(v) or is_hand(v)) # pass decimates the body
group("P_HAND", lambda v: not is_hand(v)) # pass decimates the hands (head is not a hand)
targets = {"P_BODY": T_BODY, "P_HAND": T_HAND}
region_now = {"P_BODY": len(me.polygons) - head_tris0 - hand_tris0, "P_HAND": hand_tris0}
for pg in ("P_BODY", "P_HAND"):
other_now = len(me.polygons) - region_now[pg]
dec = body.modifiers.new("dec", "DECIMATE")
dec.ratio = min(1.0, (targets[pg] + other_now) / max(1, len(me.polygons)))
dec.vertex_group = pg
dec.invert_vertex_group = True
dec.delimit = {"UV"}
bpy.ops.object.modifier_apply(modifier="dec")
me = body.data
region_now[pg] = targets[pg]
print(f"[fullhead] after {pg} pass: tris={len(me.polygons)}")
# gate: the head must have survived bit-for-tri
head_tris1 = sum(1 for p in me.polygons if all(is_head(me.vertices[vi]) for vi in p.vertices))
print(f"[fullhead] head tris {head_tris0} -> {head_tris1}")
if head_tris1 < head_tris0 * 0.999:
raise SystemExit(f"[fullhead] HEAD WAS DECIMATED ({head_tris0} -> {head_tris1}) — abort, do not use output")
bpy.ops.object.shade_smooth()
def render(name, loc, look_z):
cam = bpy.data.objects.get("cam")
if cam is None:
camd = bpy.data.cameras.new("cam"); cam = bpy.data.objects.new("cam", camd)
bpy.context.scene.collection.objects.link(cam)
bpy.context.scene.camera = cam
cam.location = loc
cam.rotation_euler = (math.radians(90), 0, 0)
cam.location.z = look_z
sc = bpy.context.scene
sc.render.engine = "BLENDER_WORKBENCH"
sc.display.shading.light = "STUDIO"
sc.display.shading.color_type = "TEXTURE"
sc.render.resolution_x = 800; sc.render.resolution_y = 800
sc.render.filepath = os.path.join(REVIEW, name)
bpy.ops.render.render(write_still=True)
h = mxz - mnz
render("qa_front.png", Vector((0, -2.0 * h, 0)), mnz + 0.5 * h)
render("qa_head.png", Vector((0, -0.45 * h, 0)), mnz + 0.93 * h)
render("qa_hand.png", Vector((HAND_X + 0.08 * h, -0.35 * h, 0)), mnz + 0.72 * h)
bpy.ops.object.select_all(action="DESELECT"); body.select_set(True)
for g in list(body.vertex_groups): body.vertex_groups.remove(g)
bpy.ops.export_scene.gltf(
filepath=OUT_GLB, export_format="GLB", export_yup=True, export_apply=False,
export_animations=False, export_skins=False, export_morph=False)
print(f"[fullhead] wrote {OUT_GLB} ({os.path.getsize(OUT_GLB)/1e6:.1f} MB), "
f"final tris={len(me.polygons)} (head {head_tris1} + budgets {T_BODY}+{T_HAND})")
print("[fullhead] DONE")
+194
View File
@@ -0,0 +1,194 @@
# Mako work lane, stage 02: graft AccuRig skeleton + weights onto the fullhead GLB.
# Direct adaptation of work/lena/46_graft.py — sole difference: the target is the GLB itself
# (the mako lane has no .blend master; the GLB is the source of truth).
#
# blender --background --python 02_graft.py -- <target.glb> <rigged.fbx> <out.blend> <out.glb>
#
# blender --background --python 46_graft.py -- <target.glb> <rigged.fbx> <out.blend> <out.glb>
#
# The lane's rule (`.agents/plans/rig-graft-lane-2026-08-04.md`): FBX is a disposable rig carrier,
# the GLB is the sole source of truth for mesh and materials, and it only ever GAINS bones and
# weights. So nothing here touches a vertex position, a UV, or a texture — the mesh that comes out
# is byte-for-byte v10's, and that is asserted at the end, not hoped for.
#
# INDEX-EXACT, not nearest-surface. The plan assumed a decimated bait would have to hand weights to
# a full-res mesh by proximity, which is where the July lane mangled the fingers (close-packed
# digits confuse a proximity search). AccuRig rigged the shipping mesh whole and returned the same
# 31,111 vertices in the same order, so weights copy index-to-index and the failure mode is gone.
#
# TWO CORRECTIONS, both SOLVED here rather than hardcoded. The carrier was exported at 1.700 m and
# AccuRig also shifted the body ~1.2 cm (the same family as the July lane's 4 cm offset trap). Both
# are recovered by least-squares fitting a uniform scale + translation between the two vertex sets,
# so this stays correct if either changes on a future re-rig.
import bpy, sys, os, time
import numpy as np
from mathutils import Vector, Matrix
argv = sys.argv[sys.argv.index("--") + 1:]
GLB, FBX, OUTB, OUTG = argv[0], argv[1], os.path.abspath(argv[2]), os.path.abspath(argv[3])
t0 = time.time()
def log(m):
print(f"[graft {time.time()-t0:6.1f}s] {m}", flush=True)
bpy.ops.wm.read_factory_settings(use_empty=True)
bpy.ops.import_scene.gltf(filepath=GLB)
for _o in bpy.data.objects:
if _o.type == "MESH":
bpy.context.view_layer.objects.active = _o; _o.select_set(True)
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
body = max([o for o in bpy.data.objects if o.type == 'MESH'], key=lambda o: len(o.data.vertices))
me = body.data
n = len(me.vertices)
V = np.empty(n * 3); me.vertices.foreach_get("co", V); V = V.reshape(-1, 3)
V_ref = V.copy()
log(f"target: '{body.name}' {n}v {len(me.polygons)}f uv={[l.name for l in me.uv_layers]}")
before = {o.name for o in bpy.data.objects}
bpy.ops.import_scene.fbx(filepath=FBX)
new = [o for o in bpy.data.objects if o.name not in before]
arm = next(o for o in new if o.type == 'ARMATURE')
src = max([o for o in new if o.type == 'MESH'], key=lambda o: len(o.data.vertices))
log(f"carrier: armature '{arm.name}' {len(arm.data.bones)} bones, mesh {len(src.data.vertices)}v")
# AccuRig ships a "T-Pose" take, and Blender's importer binds it as an ACTION. An action re-applies
# its pose on every evaluation and on every file load, so clearing pose values while it exists is
# futile — the in-session assert passes, the saved file comes back posed, and the body deforms
# 1.25 units into the air with every edge length unchanged. Drop the animation data first.
dropped = []
for o in (arm, src):
if o.animation_data:
dropped.append(o.name)
o.animation_data_clear()
dat = o.data
if dat and getattr(dat, "animation_data", None):
dat.animation_data_clear()
sk = getattr(dat, "shape_keys", None)
if sk and sk.animation_data:
sk.animation_data_clear()
for act in list(bpy.data.actions):
if act.users == 0:
bpy.data.actions.remove(act)
log(f"animation data cleared on {dropped}; actions left: {[a.name for a in bpy.data.actions]}")
assert len(src.data.vertices) == n, \
f"vertex count differs ({len(src.data.vertices)} vs {n}) — index-exact graft is off the table"
# ---- recover scale + translation between carrier and target (order is preserved) ----
S = np.empty(n * 3); src.data.vertices.foreach_get("co", S); S = S.reshape(-1, 3)
M = np.array(src.matrix_world)
Sw = S @ M[:3, :3].T + M[:3, 3]
ca, cb = V.mean(axis=0), Sw.mean(axis=0)
A, B = V - ca, Sw - cb
s = float((A * B).sum() / (B * B).sum())
t = ca - s * cb
res = np.linalg.norm(V - (s * Sw + t), axis=1)
UNITM = 1.750 / (V[:, 2].max() - V[:, 2].min())
log(f"fit: scale {s:.6f} (1/{1/s:.6f}) translation {np.round(t, 6)} in target units")
log(f" carrier offset removed: {np.round(-t / s * 1000, 3)} mm in carrier space")
log(f" index-exact residual: mean {res.mean()*UNITM*1000:.4f} mm p99 "
f"{np.percentile(res,99)*UNITM*1000:.4f} mm max {res.max()*UNITM*1000:.4f} mm")
# ---- move the skeleton into the target's space; the mesh never moves ----
arm.matrix_world = Matrix.Translation(Vector(t)) @ Matrix.Diagonal(
Vector((s, s, s))).to_4x4() @ arm.matrix_world
bpy.ops.object.select_all(action='DESELECT')
arm.select_set(True)
bpy.context.view_layer.objects.active = arm
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
log(f"armature placed; scale now {tuple(round(v,5) for v in arm.scale)}")
# Clear the carrier's POSE, and do it AFTER transform_apply. The mesh binds to the REST skeleton,
# so any pose riding along is pure displacement — and this FBX arrives with one. Clearing it by
# setting location/rotation/scale properties BEFORE the apply did not survive: transform_apply
# reintroduced non-identity rotations on the thigh and foot, the saved file deformed the body
# 1.25 units into the air, and every edge length stayed put — rigid motion, which reads as
# "weights are fine, model is gone". Use the pose operator, last, and assert it took.
bpy.ops.object.mode_set(mode='POSE')
bpy.ops.pose.select_all(action='SELECT')
bpy.ops.pose.transforms_clear()
bpy.ops.object.mode_set(mode='OBJECT')
bpy.context.view_layer.update()
worst_pb = max(np.abs(np.array(pb.matrix_basis) - np.eye(4)).max() for pb in arm.pose.bones)
log(f"pose cleared; largest deviation from identity across {len(arm.pose.bones)} bones: "
f"{worst_pb:.2e}")
assert worst_pb < 1e-6, "pose did not clear — the bind would be displaced"
# ---- copy weights index-to-index ----
for vg in list(body.vertex_groups):
body.vertex_groups.remove(vg)
name_of = {}
for g in src.vertex_groups:
name_of[g.index] = g.name
body.vertex_groups.new(name=g.name)
tgt = {g.name: g for g in body.vertex_groups}
copied = 0
for v in src.data.vertices:
for g in v.groups:
w = g.weight
if w > 0.0:
tgt[name_of[g.group]].add([v.index], w, 'REPLACE')
copied += 1
log(f"copied {copied} weights into {len(body.vertex_groups)} groups")
# ---- bind ----
for m_ in list(body.modifiers):
if m_.type == 'ARMATURE':
body.modifiers.remove(m_)
mod = body.modifiers.new("Armature", 'ARMATURE')
mod.object = arm
body.parent = arm
body.matrix_parent_inverse = arm.matrix_world.inverted()
# ---- the carrier is disposable: nothing visual survives from the FBX ----
carrier_mats = [m_ for m_ in src.data.materials if m_]
bpy.data.objects.remove(src, do_unlink=True)
for m_ in carrier_mats:
if m_.users == 0:
bpy.data.materials.remove(m_)
for im in list(bpy.data.images):
if im.users == 0 and im.name not in {"Render Result", "Viewer Node"}:
bpy.data.images.remove(im)
# ---- verify ----
V2 = np.empty(n * 3); me.vertices.foreach_get("co", V2); V2 = V2.reshape(-1, 3)
moved = np.linalg.norm(V2 - V_ref, axis=1).max()
tot = np.zeros(n)
for v in me.vertices:
tot[v.index] = sum(g.weight for g in v.groups)
print("\n=== VERIFY ===")
print(f" mesh vertices moved: {moved*UNITM*1000:.6f} mm (must be 0)")
print(f" height {V2[:,2].max()-V2[:,2].min():.5f} units, feet min-Z {V2[:,2].min():+.6f}")
print(f" weight sums: min {tot.min():.4f} mean {tot.mean():.4f} max {tot.max():.4f}")
print(f" unweighted vertices: {int((tot < 1e-6).sum())}")
print(f" bones {len(arm.data.bones)} vertex groups {len(body.vertex_groups)}")
print(f" uv layers {[l.name for l in me.uv_layers]} materials "
f"{[m_.name for m_ in me.materials if m_]}")
print(f" images {[i.name for i in bpy.data.images if i.size[0]]}")
assert moved < 1e-9, "the mesh moved — the graft must be additive only"
# The rest mesh being right proves nothing: the armature modifier is what the engine will run.
# Evaluate it and require the deformed body to sit exactly where the undeformed one does.
dg = bpy.context.evaluated_depsgraph_get()
evo = body.evaluated_get(dg)
tmp = evo.to_mesh()
Dv = np.empty(len(tmp.vertices) * 3); tmp.vertices.foreach_get("co", Dv)
Dv = Dv.reshape(-1, 3)
evo.to_mesh_clear()
drift = np.linalg.norm(Dv - V_ref, axis=1).max() * UNITM * 1000
print(f" DEFORMED at rest pose: z {Dv[:,2].min():.5f}..{Dv[:,2].max():.5f} "
f"max drift vs undeformed {drift:.6f} mm")
assert drift < 0.01, (f"the armature displaces the body by {drift:.3f} mm at rest — a leftover "
f"pose, not a weighting problem")
bpy.ops.wm.save_as_mainfile(filepath=OUTB)
bpy.ops.object.select_all(action='DESELECT')
arm.select_set(True); body.select_set(True)
bpy.context.view_layer.objects.active = arm
bpy.ops.export_scene.gltf(filepath=OUTG, export_format='GLB', use_selection=True,
export_image_format='AUTO', export_jpeg_quality=95,
export_yup=True, export_apply=False, export_skins=True)
log(f"EXPORTED {OUTG} ({os.path.getsize(OUTG)/1e6:.2f} MB)")
print("GRAFT_DONE")