Give every Quaternius head a visible mouth and queue the prop and gear fit pass
No files matched your search
@@ -11,6 +11,8 @@ Semantic Versioning.
|
|||||||
- Removed every hat and hood from the NPC roster: the firefighter helmet is the only head gear
|
- Removed every hat and hood from the NPC roster: the firefighter helmet is the only head gear
|
||||||
left, so no character hides its face behind a cap. The street performer no longer wears the
|
left, so no character hides its face behind a cap. The street performer no longer wears the
|
||||||
cone hat and carries the baton and board in the hands instead.
|
cone hat and carries the baton and board in the hands instead.
|
||||||
|
- Every NPC now has a visible mouth; the Quaternius heads shipped without one, so the faces read as
|
||||||
|
brow-and-eye masks.
|
||||||
- Fixed the modular Worker characters: the guard, delivery, firefighter and crowd roles had holes
|
- Fixed the modular Worker characters: the guard, delivery, firefighter and crowd roles had holes
|
||||||
in the torso and an open crown, a continuous unibrow and no visible eyes. They now have a closed
|
in the torso and an open crown, a continuous unibrow and no visible eyes. They now have a closed
|
||||||
torso, hair, two eyebrows and two eyes.
|
torso, hair, two eyebrows and two eyes.
|
||||||
|
|||||||
@@ -930,6 +930,37 @@ gear) and 5 (head gear on the skull).
|
|||||||
- [ ] Refresh `CHARACTERS`, the manifest roles/tint data, `CREDITS.md` and the review doc rows as
|
- [ ] Refresh `CHARACTERS`, the manifest roles/tint data, `CREDITS.md` and the review doc rows as
|
||||||
each family lands.
|
each family lands.
|
||||||
|
|
||||||
|
### Prop and gear fit pass (new NPC attach points)
|
||||||
|
|
||||||
|
Goal: every gear piece and role prop is measured against, and re-authored for, the Quaternius
|
||||||
|
skeleton. The 0.9.9 roster swap moved the hand from y 0.78 to the wrist at y 1.017, the head centre
|
||||||
|
from 1.62 to 1.69 and the shoulder width from 0.27 to 0.15, so anything authored for the procedural
|
||||||
|
body is now the wrong size or in the wrong place. Nothing in this workstream changes the runtime
|
||||||
|
contract.
|
||||||
|
|
||||||
|
- [ ] Extend `tools/blender_assets.py fit` to measure **hand** and **chest** gear (it currently
|
||||||
|
reports head, back and chest only, and skips `hand` outright). Report each item's grip
|
||||||
|
offset from the wrist, its overall size and whether it clears the thigh at rest.
|
||||||
|
- [ ] Record a measurement table for the nine hand items (`phone`, `cup`, `clipboard`, `umbrella`,
|
||||||
|
`camera`, `protest_sign`, `flashlight`, `wand`, `radio`) against the Quaternius wrist
|
||||||
|
(Godot y 1.017, palm 4 cm forward) and pick a grip scale per item.
|
||||||
|
- [ ] Re-author the oversized hand items: `clipboard` (0.23 x 0.31 m) and `umbrella` (0.70 m
|
||||||
|
shaft) are the two the captures flag; rebuild, re-bake AO and re-check in the capture gate.
|
||||||
|
- [ ] Re-fit the role props in `NpcAvatar._add_props()` and `_add_hand_item()` — delivery box,
|
||||||
|
confused-visitor tray, influencer photo prop, tourist camera — against the same wrist
|
||||||
|
anchors, and delete the now-unused primitive cap/hood fallbacks, if any remain.
|
||||||
|
- [ ] Retire the gear GLBs the roster no longer uses (`cap`, `hood`, `beanie`) or mark them
|
||||||
|
fallback-only, and update `CREDITS.md`, the manifest and `docs/blender-assets.md`.
|
||||||
|
- [ ] Re-measure `fit` for `backpack`, `shoulder_bag`, `scba_tank` and `lanyard` on the slimmer
|
||||||
|
Quaternius torso and re-fit the straps where they no longer touch the body.
|
||||||
|
- [ ] Rebuild `docs/blender_previews/sheet_npc_gear.jpg` and add a gear-on-character sheet to the
|
||||||
|
review page, then re-run the 24-NPC budget smoke.
|
||||||
|
- [ ] Update `docs/npc-visual-review.md` row 4 (hands and hand-held gear) once the pass lands.
|
||||||
|
|
||||||
|
**Exit:** every gear piece and role prop has a measured fit against the Quaternius attach points,
|
||||||
|
the hand-held items are within +/-10 % of their authored real-world size, and the review page row 4
|
||||||
|
is signed off.
|
||||||
|
|
||||||
### Gates and rollout
|
### Gates and rollout
|
||||||
|
|
||||||
- [ ] Re-bake AO per rebuilt GLB and refresh the stale `.godot` import cache (scratch-copy
|
- [ ] Re-bake AO per rebuilt GLB and refresh the stale `.godot` import cache (scratch-copy
|
||||||
|
|||||||
@@ -450,7 +450,8 @@
|
|||||||
"Eyebrows",
|
"Eyebrows",
|
||||||
"Eye",
|
"Eye",
|
||||||
"hair",
|
"hair",
|
||||||
"pants"
|
"pants",
|
||||||
|
"mouth"
|
||||||
],
|
],
|
||||||
"mesh_node": "npc_base",
|
"mesh_node": "npc_base",
|
||||||
"path": "assets/models/blender/npc/npc_casual.glb",
|
"path": "assets/models/blender/npc/npc_casual.glb",
|
||||||
@@ -467,8 +468,8 @@
|
|||||||
"accessory",
|
"accessory",
|
||||||
"hair"
|
"hair"
|
||||||
],
|
],
|
||||||
"triangles": 6206,
|
"triangles": 6218,
|
||||||
"vertices": 12988
|
"vertices": 13012
|
||||||
},
|
},
|
||||||
"npc_coat": {
|
"npc_coat": {
|
||||||
"base": "hoodie",
|
"base": "hoodie",
|
||||||
@@ -493,7 +494,8 @@
|
|||||||
"Eyebrows",
|
"Eyebrows",
|
||||||
"Eye",
|
"Eye",
|
||||||
"hair",
|
"hair",
|
||||||
"pants"
|
"pants",
|
||||||
|
"mouth"
|
||||||
],
|
],
|
||||||
"mesh_node": "npc_base",
|
"mesh_node": "npc_base",
|
||||||
"path": "assets/models/blender/npc/npc_coat.glb",
|
"path": "assets/models/blender/npc/npc_coat.glb",
|
||||||
@@ -509,8 +511,8 @@
|
|||||||
"accessory",
|
"accessory",
|
||||||
"hair"
|
"hair"
|
||||||
],
|
],
|
||||||
"triangles": 6206,
|
"triangles": 6218,
|
||||||
"vertices": 12988
|
"vertices": 13012
|
||||||
},
|
},
|
||||||
"npc_crowd_a": {
|
"npc_crowd_a": {
|
||||||
"base": "casual",
|
"base": "casual",
|
||||||
@@ -535,7 +537,8 @@
|
|||||||
"Hair_Brown",
|
"Hair_Brown",
|
||||||
"Brown",
|
"Brown",
|
||||||
"hair",
|
"hair",
|
||||||
"pants"
|
"pants",
|
||||||
|
"mouth"
|
||||||
],
|
],
|
||||||
"mesh_node": "npc_base",
|
"mesh_node": "npc_base",
|
||||||
"path": "assets/models/blender/npc/npc_crowd_a.glb",
|
"path": "assets/models/blender/npc/npc_crowd_a.glb",
|
||||||
@@ -550,8 +553,8 @@
|
|||||||
"accessory",
|
"accessory",
|
||||||
"hair"
|
"hair"
|
||||||
],
|
],
|
||||||
"triangles": 6424,
|
"triangles": 6436,
|
||||||
"vertices": 13439
|
"vertices": 13459
|
||||||
},
|
},
|
||||||
"npc_crowd_b": {
|
"npc_crowd_b": {
|
||||||
"base": "casual",
|
"base": "casual",
|
||||||
@@ -576,7 +579,8 @@
|
|||||||
"Hair_Brown",
|
"Hair_Brown",
|
||||||
"Brown",
|
"Brown",
|
||||||
"hair",
|
"hair",
|
||||||
"pants"
|
"pants",
|
||||||
|
"mouth"
|
||||||
],
|
],
|
||||||
"mesh_node": "npc_base",
|
"mesh_node": "npc_base",
|
||||||
"path": "assets/models/blender/npc/npc_crowd_b.glb",
|
"path": "assets/models/blender/npc/npc_crowd_b.glb",
|
||||||
@@ -591,8 +595,8 @@
|
|||||||
"accessory",
|
"accessory",
|
||||||
"hair"
|
"hair"
|
||||||
],
|
],
|
||||||
"triangles": 6424,
|
"triangles": 6436,
|
||||||
"vertices": 13439
|
"vertices": 13459
|
||||||
},
|
},
|
||||||
"npc_crowd_c": {
|
"npc_crowd_c": {
|
||||||
"base": "hoodie",
|
"base": "hoodie",
|
||||||
@@ -617,7 +621,8 @@
|
|||||||
"Eyebrows",
|
"Eyebrows",
|
||||||
"Eye",
|
"Eye",
|
||||||
"hair",
|
"hair",
|
||||||
"pants"
|
"pants",
|
||||||
|
"mouth"
|
||||||
],
|
],
|
||||||
"mesh_node": "npc_base",
|
"mesh_node": "npc_base",
|
||||||
"path": "assets/models/blender/npc/npc_crowd_c.glb",
|
"path": "assets/models/blender/npc/npc_crowd_c.glb",
|
||||||
@@ -632,8 +637,8 @@
|
|||||||
"accessory",
|
"accessory",
|
||||||
"hair"
|
"hair"
|
||||||
],
|
],
|
||||||
"triangles": 6206,
|
"triangles": 6218,
|
||||||
"vertices": 12988
|
"vertices": 13012
|
||||||
},
|
},
|
||||||
"npc_crowd_d": {
|
"npc_crowd_d": {
|
||||||
"base": "worker",
|
"base": "worker",
|
||||||
@@ -660,7 +665,8 @@
|
|||||||
"pants",
|
"pants",
|
||||||
"Brows",
|
"Brows",
|
||||||
"Eye.001",
|
"Eye.001",
|
||||||
"hair"
|
"hair",
|
||||||
|
"mouth"
|
||||||
],
|
],
|
||||||
"mesh_node": "npc_base",
|
"mesh_node": "npc_base",
|
||||||
"path": "assets/models/blender/npc/npc_crowd_d.glb",
|
"path": "assets/models/blender/npc/npc_crowd_d.glb",
|
||||||
@@ -675,8 +681,8 @@
|
|||||||
"accessory",
|
"accessory",
|
||||||
"hair"
|
"hair"
|
||||||
],
|
],
|
||||||
"triangles": 6254,
|
"triangles": 6266,
|
||||||
"vertices": 13198
|
"vertices": 13222
|
||||||
},
|
},
|
||||||
"npc_dress": {
|
"npc_dress": {
|
||||||
"base": "casual",
|
"base": "casual",
|
||||||
@@ -701,7 +707,8 @@
|
|||||||
"Hair_Brown",
|
"Hair_Brown",
|
||||||
"Brown",
|
"Brown",
|
||||||
"hair",
|
"hair",
|
||||||
"pants"
|
"pants",
|
||||||
|
"mouth"
|
||||||
],
|
],
|
||||||
"mesh_node": "npc_base",
|
"mesh_node": "npc_base",
|
||||||
"path": "assets/models/blender/npc/npc_dress.glb",
|
"path": "assets/models/blender/npc/npc_dress.glb",
|
||||||
@@ -715,8 +722,8 @@
|
|||||||
"accessory",
|
"accessory",
|
||||||
"hair"
|
"hair"
|
||||||
],
|
],
|
||||||
"triangles": 6424,
|
"triangles": 6436,
|
||||||
"vertices": 13439
|
"vertices": 13459
|
||||||
},
|
},
|
||||||
"npc_firefighter": {
|
"npc_firefighter": {
|
||||||
"base": "worker",
|
"base": "worker",
|
||||||
@@ -743,7 +750,8 @@
|
|||||||
"pants",
|
"pants",
|
||||||
"Brows",
|
"Brows",
|
||||||
"Eye.001",
|
"Eye.001",
|
||||||
"hair"
|
"hair",
|
||||||
|
"mouth"
|
||||||
],
|
],
|
||||||
"mesh_node": "npc_base",
|
"mesh_node": "npc_base",
|
||||||
"path": "assets/models/blender/npc/npc_firefighter.glb",
|
"path": "assets/models/blender/npc/npc_firefighter.glb",
|
||||||
@@ -757,8 +765,8 @@
|
|||||||
"accessory",
|
"accessory",
|
||||||
"hair"
|
"hair"
|
||||||
],
|
],
|
||||||
"triangles": 6254,
|
"triangles": 6266,
|
||||||
"vertices": 13198
|
"vertices": 13222
|
||||||
},
|
},
|
||||||
"npc_guard": {
|
"npc_guard": {
|
||||||
"base": "worker",
|
"base": "worker",
|
||||||
@@ -785,7 +793,8 @@
|
|||||||
"pants",
|
"pants",
|
||||||
"Brows",
|
"Brows",
|
||||||
"Eye.001",
|
"Eye.001",
|
||||||
"hair"
|
"hair",
|
||||||
|
"mouth"
|
||||||
],
|
],
|
||||||
"mesh_node": "npc_base",
|
"mesh_node": "npc_base",
|
||||||
"path": "assets/models/blender/npc/npc_guard.glb",
|
"path": "assets/models/blender/npc/npc_guard.glb",
|
||||||
@@ -800,8 +809,8 @@
|
|||||||
"accessory",
|
"accessory",
|
||||||
"hair"
|
"hair"
|
||||||
],
|
],
|
||||||
"triangles": 6254,
|
"triangles": 6266,
|
||||||
"vertices": 13198
|
"vertices": 13222
|
||||||
},
|
},
|
||||||
"npc_hoodie": {
|
"npc_hoodie": {
|
||||||
"base": "hoodie",
|
"base": "hoodie",
|
||||||
@@ -826,7 +835,8 @@
|
|||||||
"Eyebrows",
|
"Eyebrows",
|
||||||
"Eye",
|
"Eye",
|
||||||
"hair",
|
"hair",
|
||||||
"pants"
|
"pants",
|
||||||
|
"mouth"
|
||||||
],
|
],
|
||||||
"mesh_node": "npc_base",
|
"mesh_node": "npc_base",
|
||||||
"path": "assets/models/blender/npc/npc_hoodie.glb",
|
"path": "assets/models/blender/npc/npc_hoodie.glb",
|
||||||
@@ -842,8 +852,8 @@
|
|||||||
"accessory",
|
"accessory",
|
||||||
"hair"
|
"hair"
|
||||||
],
|
],
|
||||||
"triangles": 6206,
|
"triangles": 6218,
|
||||||
"vertices": 12988
|
"vertices": 13012
|
||||||
},
|
},
|
||||||
"npc_jacket": {
|
"npc_jacket": {
|
||||||
"base": "casual",
|
"base": "casual",
|
||||||
@@ -868,7 +878,8 @@
|
|||||||
"Hair_Brown",
|
"Hair_Brown",
|
||||||
"Brown",
|
"Brown",
|
||||||
"hair",
|
"hair",
|
||||||
"pants"
|
"pants",
|
||||||
|
"mouth"
|
||||||
],
|
],
|
||||||
"mesh_node": "npc_base",
|
"mesh_node": "npc_base",
|
||||||
"path": "assets/models/blender/npc/npc_jacket.glb",
|
"path": "assets/models/blender/npc/npc_jacket.glb",
|
||||||
@@ -884,8 +895,8 @@
|
|||||||
"accessory",
|
"accessory",
|
||||||
"hair"
|
"hair"
|
||||||
],
|
],
|
||||||
"triangles": 6424,
|
"triangles": 6436,
|
||||||
"vertices": 13439
|
"vertices": 13459
|
||||||
},
|
},
|
||||||
"npc_vest": {
|
"npc_vest": {
|
||||||
"base": "worker",
|
"base": "worker",
|
||||||
@@ -913,7 +924,8 @@
|
|||||||
"pants",
|
"pants",
|
||||||
"Brows",
|
"Brows",
|
||||||
"Eye.001",
|
"Eye.001",
|
||||||
"hair"
|
"hair",
|
||||||
|
"mouth"
|
||||||
],
|
],
|
||||||
"mesh_node": "npc_base",
|
"mesh_node": "npc_base",
|
||||||
"path": "assets/models/blender/npc/npc_vest.glb",
|
"path": "assets/models/blender/npc/npc_vest.glb",
|
||||||
@@ -927,8 +939,8 @@
|
|||||||
"accessory",
|
"accessory",
|
||||||
"hair"
|
"hair"
|
||||||
],
|
],
|
||||||
"triangles": 6254,
|
"triangles": 6266,
|
||||||
"vertices": 13198
|
"vertices": 13222
|
||||||
},
|
},
|
||||||
"palm_tree": {
|
"palm_tree": {
|
||||||
"bounds_godot": {
|
"bounds_godot": {
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 155 KiB After Width: | Height: | Size: 155 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 214 KiB After Width: | Height: | Size: 215 KiB |
@@ -22,6 +22,7 @@ all build defects, not art. This revision covers what was fixed and what is stil
|
|||||||
| arms intersecting the body on `npc_dress`, `npc_jacket`, `npc_crowd_a`, `npc_crowd_d` | the baked rest aimed the upper arm straight down, which fails on the narrow bases | reduced — the arm rests 7° out from vertical (mesh width 0.180 → 0.220 on `casual`, 0.228 → 0.262 on `hoodie`) |
|
| arms intersecting the body on `npc_dress`, `npc_jacket`, `npc_crowd_a`, `npc_crowd_d` | the baked rest aimed the upper arm straight down, which fails on the narrow bases | reduced — the arm rests 7° out from vertical (mesh width 0.180 → 0.220 on `casual`, 0.228 → 0.262 on `hoodie`) |
|
||||||
| head gear did not sit on the skull | gear is authored around the procedural head centre (1.62); the Quaternius head centre is 1.69 | **resolved by removal** — see below |
|
| head gear did not sit on the skull | gear is authored around the procedural head centre (1.62); the Quaternius head centre is 1.69 | **resolved by removal** — see below |
|
||||||
| performer cone swallowed the head | a 0.44 m cone authored for the old body | removed — the performer carries a baton and board in the hands |
|
| performer cone swallowed the head | a 0.44 m cone authored for the old body | removed — the performer carries a baton and board in the hands |
|
||||||
|
| faces had **no mouth at all** | none of the three upstream heads carries a mouth material | fixed — a small lip block is placed on the measured skin surface for every base (dark maroon, not tinted) |
|
||||||
|
|
||||||
## Hats and head items
|
## Hats and head items
|
||||||
|
|
||||||
@@ -91,6 +92,10 @@ Recorded as tasks in `TODO.md` under **Owner review findings**:
|
|||||||
|
|
||||||
1. **Eyes have no whites.** Every base uses single-colour quads, so eyes read as dark rectangles at
|
1. **Eyes have no whites.** Every base uses single-colour quads, so eyes read as dark rectangles at
|
||||||
portrait distance. A light sclera plus pupil on the `Eye` slot would finish the face.
|
portrait distance. A light sclera plus pupil on the `Eye` slot would finish the face.
|
||||||
|
2. **Hands and hand-held gear are still authored for the old body.** The wrist moved from y 0.78 to
|
||||||
|
1.017 and the arm got shorter, so the clipboard and the umbrella are oversized. A dedicated
|
||||||
|
prop-and-gear fit pass is queued in [`../TODO.md`](../TODO.md) under **Prop and gear fit pass
|
||||||
|
(new NPC attach points)** — it starts by teaching `fit` to measure hand gear at all.
|
||||||
2. **Arms still graze the torso on the `worker` base** — the 7° rest tilt cleared the `casual` base
|
2. **Arms still graze the torso on the `worker` base** — the 7° rest tilt cleared the `casual` base
|
||||||
but not this one.
|
but not this one.
|
||||||
3. **The `npc_vest` clipboard is oversized** on the shorter Quaternius arm.
|
3. **The `npc_vest` clipboard is oversized** on the shorter Quaternius arm.
|
||||||
|
|||||||
@@ -1587,6 +1587,65 @@ def _purge_unused_materials(obj):
|
|||||||
bpy.data.materials.remove(material)
|
bpy.data.materials.remove(material)
|
||||||
|
|
||||||
|
|
||||||
|
def _add_quaternius_mouth(obj):
|
||||||
|
"""Give every Quaternius head a readable mouth.
|
||||||
|
|
||||||
|
None of the three upstream heads carries a mouth material: the face is skin
|
||||||
|
only, so at gameplay distance the characters read as brow-and-eye masks. A
|
||||||
|
small flat lip block is placed on the measured skin surface, which is why it
|
||||||
|
works for all three bases without per-source tuning.
|
||||||
|
"""
|
||||||
|
# This runs before the slot map, so the material is still the upstream `Skin`.
|
||||||
|
skin = next((i for i, m in enumerate(obj.data.materials)
|
||||||
|
if m is not None and _base_name(m.name).lower() == "skin"), None)
|
||||||
|
if skin is None:
|
||||||
|
return
|
||||||
|
band = []
|
||||||
|
for poly in obj.data.polygons:
|
||||||
|
if poly.material_index != skin:
|
||||||
|
continue
|
||||||
|
for index in poly.vertices:
|
||||||
|
co = obj.data.vertices[index].co
|
||||||
|
if 1.600 <= co.z <= 1.632 and abs(co.x) <= 0.035:
|
||||||
|
band.append(co.y)
|
||||||
|
if not band:
|
||||||
|
return
|
||||||
|
front = min(band) # Blender -Y is the face; the lip sits on the surface
|
||||||
|
height = 1.616
|
||||||
|
material = bpy.data.materials.new("mouth")
|
||||||
|
material.use_nodes = True
|
||||||
|
bsdf = material.node_tree.nodes.get("Principled BSDF")
|
||||||
|
if bsdf is not None:
|
||||||
|
# Blender node colours are linear; 0.16 linear reads as mauve once
|
||||||
|
# Tonemapped in Godot, so the lip is set well below the skin value.
|
||||||
|
bsdf.inputs["Base Color"].default_value = (0.055, 0.018, 0.02, 1.0)
|
||||||
|
bsdf.inputs["Roughness"].default_value = 0.6
|
||||||
|
else:
|
||||||
|
print(" mouth: no Principled node, colour left at default")
|
||||||
|
material.diffuse_color = (0.055, 0.018, 0.02, 1.0)
|
||||||
|
bm = bmesh.new()
|
||||||
|
bmesh.ops.create_cube(bm, size=1.0)
|
||||||
|
for vert in bm.verts:
|
||||||
|
vert.co.x *= 0.058
|
||||||
|
vert.co.y *= 0.012
|
||||||
|
vert.co.z *= 0.011
|
||||||
|
vert.co += Vector((0.0, front + 0.003, height))
|
||||||
|
mouth = bpy.data.objects.new("Mouth", bpy.data.meshes.new("Mouth"))
|
||||||
|
bm.to_mesh(mouth.data)
|
||||||
|
bm.free()
|
||||||
|
mouth.data.materials.append(material)
|
||||||
|
bpy.context.scene.collection.objects.link(mouth)
|
||||||
|
for o in bpy.context.selected_objects:
|
||||||
|
o.select_set(False)
|
||||||
|
obj.select_set(True)
|
||||||
|
mouth.select_set(True)
|
||||||
|
bpy.context.view_layer.objects.active = obj
|
||||||
|
bpy.ops.object.join()
|
||||||
|
group = obj.vertex_groups.get("Head") or obj.vertex_groups.new(name="Head")
|
||||||
|
group.add([v.index for v in obj.data.vertices if not v.groups], 1.0, "REPLACE")
|
||||||
|
print(f" mouth placed at y {front + 0.003:+.3f} z {height:.3f}")
|
||||||
|
|
||||||
|
|
||||||
def _base_name(name):
|
def _base_name(name):
|
||||||
"""Strip Blender's `.001` duplicate suffix."""
|
"""Strip Blender's `.001` duplicate suffix."""
|
||||||
head, sep, tail = name.rpartition(".")
|
head, sep, tail = name.rpartition(".")
|
||||||
@@ -1998,6 +2057,7 @@ def _quaternius_tpose_glb(base, overrides):
|
|||||||
slots.update(overrides)
|
slots.update(overrides)
|
||||||
if source.get("donor_head"):
|
if source.get("donor_head"):
|
||||||
_transplant_worker_head(obj)
|
_transplant_worker_head(obj)
|
||||||
|
_add_quaternius_mouth(obj)
|
||||||
_quaternius_slot_map(obj, slots)
|
_quaternius_slot_map(obj, slots)
|
||||||
|
|
||||||
# Upstream UVs are degenerate (every loop at 0,0), so an AO bake would write
|
# Upstream UVs are degenerate (every loop at 0,0), so an AO bake would write
|
||||||
|
|||||||