Move the shoulder bag clear of coat and dress skirts

Narrow the bag to 0.06 m and move it outboard to centre x=0.292. The body inner
face is now 0.262 m, clearing the coat skirt (0.225) and dress (0.260); the
strap lower points start on the same line. The fit report measures the bag body
separately from the torso strap and uses a signed inner-face clearance. Run
Blender build/bake and Godot import. Python tooling 87/87.
This commit is contained in:
zwitschi committed 2026-11-01 17:58:47 +01:00
1 parent bbf4693475
commit 702f54ae53
6 files changed
+44 -10

No files matched your search

+36 -4
View File
@@ -113,6 +113,9 @@ FIT_HEAD_MATERIALS = ("skin",)
FIT_CLOTHING_MATERIAL = "cloth"
FIT_CLOTHING_BAND = (0.30, 0.52) # hip/skirt band; captures coat/dress flare
FIT_HIP_BAND = (0.55, 0.95) # fallback body radius for trousers/vests
# Side bags include a strap that crosses the torso, so the full gear bbox cannot
# tell where the bag body starts. Measure the body-only y band for this fit.
FIT_BODY_BANDS = {"shoulder_bag": (-0.34, -0.16)}
GEAR = {
"cap": "head", "beanie": "head", "hood": "head", "sunglasses": "head",
"firefighter_helmet": "head",
@@ -1005,11 +1008,13 @@ def build_gear(name):
(0.11 * s, -0.13, 0.18), (0.12 * s, -0.12, -0.08)],
0.012, M("plastic_black"), seg=6))
elif name == "shoulder_bag":
p.append(box("Bag", (0.08, 0.26, 0.20), (0.21, 0.0, -0.22), M("leather"),
# Keep the bag's inner face outside the widest skirt flare (dress = 0.26):
# 0.292 - 0.06/2 = 0.262. The strap starts/ends on that same outboard line.
p.append(box("Bag", (0.06, 0.26, 0.20), (0.292, 0.0, -0.22), M("leather"),
bevel=0.025, segs=2))
p.append(tube("Strap", [(0.20, 0.0, -0.12), (0.15, -0.12, 0.10), (0.0, -0.13, 0.26),
p.append(tube("Strap", [(0.262, 0.0, -0.12), (0.15, -0.12, 0.10), (0.0, -0.13, 0.26),
(-0.15, -0.05, 0.34), (-0.16, 0.10, 0.28), (0.0, 0.14, 0.05),
(0.20, 0.03, -0.12)], 0.009, M("leather"), seg=6))
(0.262, 0.03, -0.12)], 0.009, M("leather"), seg=6))
elif name == "scba_tank":
p.append(cyl("Tank", 0.085, 0.085, 0.56, (0, 0.21, 0.02), M("steel"), seg=18))
p.append(sphere("TopCap", 0.085, (0, 0.21, 0.30), M("steel"), seg=18, rings=8,
@@ -1741,6 +1746,23 @@ def character_fit_reference(name):
}
def _gear_local_positions(name):
path = asset_path("npc_gear", name)
if not path.is_file():
return []
try:
_magic, _version, _length, _size, gltf, binary = _read_glb(path)
except (struct.error, ValueError, KeyError):
return []
out = []
for mesh in gltf.get("meshes", []):
for prim in mesh.get("primitives", []):
attrs = prim.get("attributes", {})
if "POSITION" in attrs:
out.extend(_accessor(gltf, binary, attrs["POSITION"]))
return out
def _gear_fit(name, reference):
attach = GEAR.get(name)
if attach not in ("head", "back", "chest"):
@@ -1768,12 +1790,21 @@ def _gear_fit(name, reference):
else:
# Compare the widest point (|x|) and the corner radial extent with the
# skirt flare. The radial value catches bags that sit diagonally out.
inner_x = min(abs(lo[0]), abs(hi[0]))
if name in FIT_BODY_BANDS:
band = FIT_BODY_BANDS[name]
body_x = [abs(p[0]) for p in _gear_local_positions(name)
if band[0] <= p[1] <= band[1]]
if body_x:
inner_x = min(body_x)
x_extent = max(abs(lo[0]), abs(hi[0]))
radial = max(math.hypot(x, z) for x in (lo[0], hi[0]) for z in (lo[2], hi[2]))
result.update({
"inner_x": round(inner_x, 3),
"x_extent": round(x_extent, 3),
"radial": round(radial, 3),
"x_clearance": round(reference["clothing_radius"] - x_extent, 3),
# Positive clearance means the bag's inner face is outside the body.
"x_clearance": round(inner_x - reference["clothing_radius"], 3),
"radial_clearance": round(reference["clothing_radius"] - radial, 3),
})
return result
@@ -1917,6 +1948,7 @@ def main(argv=None):
else:
clip = "CLIP" if row["x_clearance"] < 0 else "clear"
print(f" {row['name']:20s} {row['attach']:5s} {box} "
f"inner_x={row['inner_x']:.3f} "
f"clearance x={row['x_clearance']:+.3f} "
f"r={row['radial_clearance']:+.3f} {clip}")
return 0