Give the figure real girdles, fix orbit pinning, add roller pads

Shoulder and pelvis widths grow to human-like proportions per profile
(shoulders wider than hips for neutral/male, reversed for female) and
are now drawn — bars across the attach points that read near-full-width
face-on and as a shoulder/hip nub in profile, so limbs visibly hang
from a torso instead of a point. Orbiting no longer re-solves IK pins
in the rotated view (pins are canvas targets in the authored camera):
the pose resolves first and the posed body rotates, which fixes hands
sticking to stale screen points mid-orbit (Cat-Cow, Bird Dog, Plank).
Leg Extension and Leg Curl swap their ankle bars for a machine roller
disc — a new `roller` prop riding the shin's press side. Fixtures
regenerated; both renderers updated in lockstep.

Claude-Session: https://claude.ai/code/session_01LEoff8bXGBS83tK1c55Mf7
This commit is contained in:
2026-07-06 21:00:03 -04:00
parent fd3bcb513f
commit 5e4980f0d7
106 changed files with 543 additions and 156 deletions
+37 -1
View File
@@ -149,6 +149,15 @@ def frame_geometry(nf, prof, cam, flipped=False):
geo[limb] = [scr(v, limb_off) for v in pts[limb]]
depths[limb] = _chain_depth(pts[limb])
# Shoulder girdle and pelvis, drawn with the spine so the limbs visibly hang
# from real width. Endpoints use each side's drawn attach (far offset included)
# so the bars meet the limbs exactly.
def attach(key, limb):
return scr(pts[key], off if shade[limb] == "far" else (0, 0))
geo["girdle"] = [attach("shoulder_l", "arm_l"), neck_b, attach("shoulder_r", "arm_r")]
geo["pelvisBar"] = [attach("hip_l", "leg_l"), pelvis, attach("hip_r", "leg_r")]
# The nose tick rides the head's anterior axis; it foreshortens naturally
# and disappears when the face points at (or away from) the camera.
nd = p["nose_dir"]
@@ -206,6 +215,8 @@ def flip_props(props, width):
p["from"] = fx(p["from"])
elif p["type"] in ("bar", "pad") and "angle" in p:
p["angle"] = 180 - p["angle"]
elif p["type"] == "roller":
p["side"] = -p.get("side", 1) # mirroring flips the perpendicular's handedness
out.append(p)
return out
@@ -243,6 +254,20 @@ def resolve_props(props, geo):
if end:
bg.append({"kind": "line", "pts": [list(p["from"]), list(end)],
"w": p.get("w", 2), "color": "equipment"})
elif t == "roller":
# A machine roller pad seen end-on: a disc riding the limb's lower
# bone near the joint, on the `side` (+1/-1) of the bone it presses.
c, d = joint_points(geo, p["at"])
if not c:
continue
r = p.get("r", 5)
back = p.get("back", 0)
side = p.get("side", 1)
px, py = d[1] * side, -d[0] * side
center = (c[0] - d[0] * back + px * (r + 3),
c[1] - d[1] * back + py * (r + 3))
fg.append({"kind": "circle", "c": list(center), "r": r,
"fill": True, "color": "prop"})
elif t in ("bar", "dumbbell", "pad"):
c, d = joint_points(geo, p["at"])
if not c:
@@ -353,6 +378,10 @@ def svg_for_frame(name, geo, order, shade, working, colors, props=None):
continue
color, width = part_style(part, working, colors, shade)
if part == "spine":
for bar in ("girdle", "pelvisBar"):
bd = "M " + " L ".join(f"{x:.1f} {y:.1f}" for x, y in geo[bar])
parts.append(f' <path id="{bar}" d="{bd}" stroke="{color}" stroke-width="5"'
f' stroke-linecap="round" stroke-linejoin="round"/>')
(ax, ay), (cx, cy), (bx, by) = geo["spine"]
d = f"M {ax:.1f} {ay:.1f} Q {cx:.1f} {cy:.1f} {bx:.1f} {by:.1f}"
else:
@@ -400,6 +429,9 @@ def draw_geo(geo, order, shade, working, colors, scale=2, font=None, props=None)
if part not in geo:
continue
color, width = part_style(part, working, colors, shade)
if part == "spine":
line(geo["girdle"], color, 5)
line(geo["pelvisBar"], color, 5)
pts = quad_points(*geo["spine"]) if part == "spine" else geo[part]
line(pts, color, width)
@@ -512,7 +544,11 @@ def render_orbit(folder, figure="neutral"):
imgs = []
for i, nf in enumerate(ticks):
yaw = cam + 360.0 * i / len(ticks)
_, geo, order, shade = frame_geometry(nf, prof, yaw)
# Pins are canvas targets in the AUTHORED view: resolve the pose there,
# then rotate the posed body - never re-pin in the rotated view.
posed, _, _, _ = frame_geometry(nf, prof, cam)
posed["pins"] = {}
_, geo, order, shade = frame_geometry(posed, prof, yaw)
for limb in hide:
geo.pop(limb, None)
imgs.append(draw_geo(geo, order, shade, working, colors, font=font, props=props))