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:
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user