Fix the wrist/hand detachment: wrist tracks the hand bone, sleeve cuff follows the hand, hand raised to meet the wrist
Full build / Import + headless tests (push) Successful in 40s
Full build / Full desktop build + optional release (push) Skipped

This commit is contained in:
zwitschi committed 2026-11-04 12:11:37 +01:00
1 parent c1ccfd6e72
commit 36576a023a
26 files changed
+45 -37

No files matched your search

+24 -24
View File
@@ -466,8 +466,8 @@
"accessory", "accessory",
"hair" "hair"
], ],
"triangles": 5038, "triangles": 5034,
"vertices": 4756 "vertices": 4607
}, },
"npc_coat": { "npc_coat": {
"bounds_godot": { "bounds_godot": {
@@ -507,8 +507,8 @@
"accessory", "accessory",
"hair" "hair"
], ],
"triangles": 6030, "triangles": 6046,
"vertices": 5802 "vertices": 5645
}, },
"npc_crowd_a": { "npc_crowd_a": {
"bounds_godot": { "bounds_godot": {
@@ -547,8 +547,8 @@
"accessory", "accessory",
"hair" "hair"
], ],
"triangles": 5200, "triangles": 5284,
"vertices": 4734 "vertices": 4908
}, },
"npc_crowd_b": { "npc_crowd_b": {
"bounds_godot": { "bounds_godot": {
@@ -587,8 +587,8 @@
"accessory", "accessory",
"hair" "hair"
], ],
"triangles": 4734, "triangles": 4812,
"vertices": 4353 "vertices": 4550
}, },
"npc_crowd_c": { "npc_crowd_c": {
"bounds_godot": { "bounds_godot": {
@@ -627,8 +627,8 @@
"accessory", "accessory",
"hair" "hair"
], ],
"triangles": 5674, "triangles": 5690,
"vertices": 5440 "vertices": 5510
}, },
"npc_crowd_d": { "npc_crowd_d": {
"bounds_godot": { "bounds_godot": {
@@ -667,8 +667,8 @@
"accessory", "accessory",
"hair" "hair"
], ],
"triangles": 5814, "triangles": 5810,
"vertices": 5300 "vertices": 5444
}, },
"npc_dress": { "npc_dress": {
"bounds_godot": { "bounds_godot": {
@@ -706,8 +706,8 @@
"accessory", "accessory",
"hair" "hair"
], ],
"triangles": 5456, "triangles": 5540,
"vertices": 5128 "vertices": 5073
}, },
"npc_firefighter": { "npc_firefighter": {
"bounds_godot": { "bounds_godot": {
@@ -747,8 +747,8 @@
"accessory", "accessory",
"hair" "hair"
], ],
"triangles": 5900, "triangles": 5888,
"vertices": 5902 "vertices": 5785
}, },
"npc_guard": { "npc_guard": {
"bounds_godot": { "bounds_godot": {
@@ -791,8 +791,8 @@
"accessory", "accessory",
"hair" "hair"
], ],
"triangles": 5602, "triangles": 5670,
"vertices": 5684 "vertices": 5671
}, },
"npc_hoodie": { "npc_hoodie": {
"bounds_godot": { "bounds_godot": {
@@ -832,8 +832,8 @@
"accessory", "accessory",
"hair" "hair"
], ],
"triangles": 5860, "triangles": 5864,
"vertices": 5492 "vertices": 5325
}, },
"npc_jacket": { "npc_jacket": {
"bounds_godot": { "bounds_godot": {
@@ -873,8 +873,8 @@
"accessory", "accessory",
"hair" "hair"
], ],
"triangles": 5428, "triangles": 5432,
"vertices": 5071 "vertices": 5225
}, },
"npc_vest": { "npc_vest": {
"bounds_godot": { "bounds_godot": {
@@ -914,8 +914,8 @@
"accessory", "accessory",
"hair" "hair"
], ],
"triangles": 5536, "triangles": 5532,
"vertices": 5375 "vertices": 5195
}, },
"palm_tree": { "palm_tree": {
"bounds_godot": { "bounds_godot": {
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 60 KiB

+21 -13
View File
@@ -566,7 +566,7 @@ def _skin_body(spec):
((0.20 * s * w, 0, 1.37), 0.080 * t * sleeve, 0.074 * t * sleeve), # shoulder ((0.20 * s * w, 0, 1.37), 0.080 * t * sleeve, 0.074 * t * sleeve), # shoulder
((0.27 * s * w, 0, 1.23), 0.058 * t * sleeve, 0.058 * t * sleeve), # upper arm ((0.27 * s * w, 0, 1.23), 0.058 * t * sleeve, 0.058 * t * sleeve), # upper arm
((0.275 * s * w, 0.005, 1.06), 0.046 * t * sleeve, 0.048 * t * sleeve), # elbow ((0.275 * s * w, 0.005, 1.06), 0.046 * t * sleeve, 0.048 * t * sleeve), # elbow
((0.275 * s * w, -0.01, 0.80), 0.034 * t, 0.030 * t), # wrist ((0.275 * s * w, -0.01, 0.78), 0.034 * t, 0.030 * t), # wrist
] ]
edges += [(3, base), (base, base + 1), (base + 1, base + 2), (base + 2, base + 3)] edges += [(3, base), (base, base + 1), (base + 1, base + 2), (base + 2, base + 3)]
hb = len(nodes) hb = len(nodes)
@@ -666,9 +666,15 @@ def _auto_weights(obj, layout, rigid=None):
groups[bone].add([v.index], 1.0, "REPLACE") groups[bone].add([v.index], 1.0, "REPLACE")
continue continue
if abs(p.x) > 0.19 and p.z > 0.74 and p.z < 1.42: if abs(p.x) > 0.19 and p.z > 0.74 and p.z < 1.42:
allowed = ["Spine", f"Shoulder{side}", f"Elbow{side}", f"Hand{side}"] if p.z < 0.82:
if abs(p.x) > 0.23: # Wrist: track the Hand bone fully so the forearm-end moves with
allowed.remove("Spine") # the rigid hand (the old Hand+Elbow blend left a visible gap at
# the wrist when the hand rotated).
allowed = [f"Hand{side}"]
else:
allowed = ["Spine", f"Shoulder{side}", f"Elbow{side}", f"Hand{side}"]
if abs(p.x) > 0.23:
allowed.remove("Spine")
elif p.z < 0.80: elif p.z < 0.80:
allowed = ["Hips", f"Hip{side}", f"Knee{side}"] allowed = ["Hips", f"Hip{side}", f"Knee{side}"]
if p.z < 0.66: if p.z < 0.66:
@@ -848,26 +854,26 @@ def _hands_and_shoes(mats, spec):
for s in (-1.0, 1.0): for s in (-1.0, 1.0):
x = 0.275 * s * w x = 0.275 * s * w
# Wider and shallower than the old ball: a palm, not a wrist knob. # Wider and shallower than the old ball: a palm, not a wrist knob.
parts.append(sphere("Palm", 0.046, (x, -0.012, 0.742), skin, seg=10, rings=6, parts.append(sphere("Palm", 0.046, (x, -0.012, 0.762), skin, seg=10, rings=6,
scale=(0.80, 0.62, 0.88))) scale=(0.80, 0.62, 0.88)))
# Four bent, tapering fingers with rounded tips. The two segments keep # Four bent, tapering fingers with rounded tips. The two segments keep
# the hand readable at conversation distance without adding a full # the hand readable at conversation distance without adding a full
# finger skeleton; the outer fingers are slightly shorter. # finger skeleton; the outer fingers are slightly shorter.
for index, offset in enumerate((-0.024, -0.008, 0.008, 0.024)): for index, offset in enumerate((-0.024, -0.008, 0.008, 0.024)):
fx = x + offset * s fx = x + offset * s
tip_z = (0.692, 0.681, 0.684, 0.694)[index] tip_z = (0.712, 0.701, 0.704, 0.714)[index]
radius = 0.0115 if index in (1, 2) else 0.0105 radius = 0.0115 if index in (1, 2) else 0.0105
parts.append(tube("Finger", [(fx, -0.018, 0.758), parts.append(tube("Finger", [(fx, -0.018, 0.778),
(fx, -0.030, 0.726), (fx, -0.030, 0.746),
(fx, -0.042, tip_z)], (fx, -0.042, tip_z)],
radius, skin, seg=6, caps=False)) radius, skin, seg=6, caps=False))
parts.append(sphere("Fingertip", radius, (fx, -0.042, tip_z), skin, parts.append(sphere("Fingertip", radius, (fx, -0.042, tip_z), skin,
seg=6, rings=4)) seg=6, rings=4))
parts.append(tube("Thumb", [(x - 0.010 * s, -0.018, 0.758), parts.append(tube("Thumb", [(x - 0.010 * s, -0.018, 0.778),
(x - 0.028 * s, -0.040, 0.740), (x - 0.028 * s, -0.040, 0.760),
(x - 0.034 * s, -0.055, 0.718)], (x - 0.034 * s, -0.055, 0.738)],
0.012, skin, seg=6, caps=False)) 0.012, skin, seg=6, caps=False))
parts.append(sphere("ThumbTip", 0.012, (x - 0.034 * s, -0.055, 0.718), skin, parts.append(sphere("ThumbTip", 0.012, (x - 0.034 * s, -0.055, 0.738), skin,
seg=6, rings=4)) seg=6, rings=4))
parts.append(box("Shoe", (0.10, 0.25, 0.075), (0.12 * s, -0.045, 0.04), shoe, parts.append(box("Shoe", (0.10, 0.25, 0.075), (0.12 * s, -0.045, 0.04), shoe,
bevel=0.028, segs=1)) bevel=0.028, segs=1))
@@ -1012,7 +1018,9 @@ def build_character(name):
return "Head" if p.z > 1.55 else "Neck" return "Head" if p.z > 1.55 else "Neck"
side = "L" if p.x < 0 else "R" side = "L" if p.x < 0 else "R"
if abs(p.x) > 0.26 and p.z > 0.8: if abs(p.x) > 0.26 and p.z > 0.8:
return f"Elbow{side}" # Sleeve: the cuff follows the hand, the upper sleeve the elbow,
# so the sleeve does not separate from the hand at the wrist.
return f"Hand{side}" if p.z < 1.06 else f"Elbow{side}"
if p.z < 0.25: if p.z < 0.25:
return f"Knee{side}" return f"Knee{side}"
return None return None