Give machine props world-space 3D form that rotates with the camera
Scene shapes, cable anchors, bar angles, pad perpendiculars, and roller offsets all resolve in the authored view exactly as before, then rotate about the world-vertical axis through the root anchor - the same resolve-then-rotate pattern as the figure's pins and the mat - so at the authored yaw every exercise renders bit-identically to today, and under an orbiting camera the equipment turns with the figure while staying welded to its hands and feet. Scene lines gain an optional depth plane (z) and slab extrusion (depth) so seats, backrests, and platforms keep form edge-on; the rect shape is retired (re-authored as slab lines). All 14 machines' props re-authored with depths and verified at eight orbit angles. The fixture snapshots move into the pipeline as render.py --fixtures and now cover orbit-presentation samples with resolved prop primitives for a spread of prop flavors; the in-app renderer resolves props in MotionSolver (lockstep with resolve_props) and the view just draws primitives. Claude-Session: https://claude.ai/code/session_01HJDQQDA9QdP8zByg43H5v3
This commit is contained in:
+186
-50
@@ -13,9 +13,11 @@ Outputs per exercise: frames/frame-N.svg, preview.gif (tweened, looping),
|
||||
visual.svg (the primary frame). `--sheet` writes contact-sheet.png of every
|
||||
key frame; `--demo` writes demo-sheet.png showing rig customizations (body
|
||||
profiles, flipped camera, theme); `--orbit` writes orbit.gif per named
|
||||
exercise (the camera sweeps 360 degrees while the motion loops). `--export`
|
||||
instead bakes each motion down to the legacy planar schema consumed by the
|
||||
in-app SwiftUI renderer. SVGs need no dependencies; GIFs/sheets need Pillow.
|
||||
exercise (the camera sweeps 360 degrees while the motion loops — props
|
||||
rotate with the figure). `--export` copies the app's bundled resources
|
||||
verbatim; `--fixtures` regenerates the projected-geometry snapshots pinning
|
||||
the in-app Swift solver to this pipeline. SVGs need no dependencies;
|
||||
GIFs/sheets need Pillow.
|
||||
"""
|
||||
|
||||
import copy
|
||||
@@ -227,24 +229,35 @@ def timeline(norms, fps=20):
|
||||
# figure in the recessive equipment gray; joint-attached items (`bar`,
|
||||
# `dumbbell`, `pad`) draw over the limbs in the darker prop gray, following
|
||||
# the resolved hand/foot positions frame by frame.
|
||||
#
|
||||
# Props have world-space 3D form: everything is authored (and resolved) in the
|
||||
# authored view — scene points and cable anchors carry an optional depth `z`
|
||||
# (+ toward the camera), a scene line an optional `depth` extrusion half-width
|
||||
# that makes it a slab — then rotated about the world-vertical axis through
|
||||
# the root anchor for the orbiting presentation, exactly like the figure and
|
||||
# the mat. Positions that follow the figure (joint anchors) come from the
|
||||
# *rotated* geometry; fixed points, directions, and offsets (scene shapes,
|
||||
# cable anchors, bar angles, pad perpendiculars, roller offsets) resolve in
|
||||
# the *authored* view and rotate — never recompute them in the rotated view.
|
||||
|
||||
def flip_props(props, width):
|
||||
"""Mirror the props horizontally, matching the flipped camera. Joint-
|
||||
attached props follow the mirrored limbs automatically; only fixed
|
||||
coordinates and world angles need mirroring."""
|
||||
"""Mirror the props horizontally, matching the flipped camera (which views
|
||||
from the opposite side, so authored depths flip sign too). Joint-attached
|
||||
props follow the mirrored limbs automatically; only fixed coordinates,
|
||||
depths, and world angles need mirroring."""
|
||||
def fx(p):
|
||||
return [width - p[0], p[1]]
|
||||
return [width - p[0], p[1], -p[2]] if len(p) > 2 else [width - p[0], p[1]]
|
||||
out = []
|
||||
for prop in props:
|
||||
p = json.loads(json.dumps(prop))
|
||||
if p["type"] == "scene":
|
||||
for s in p["shapes"]:
|
||||
if "z" in s:
|
||||
s["z"] = -s["z"]
|
||||
if s["kind"] == "line":
|
||||
s["pts"] = [fx(pt) for pt in s["pts"]]
|
||||
elif s["kind"] == "circle":
|
||||
s["c"] = fx(s["c"])
|
||||
elif s["kind"] == "rect":
|
||||
s["x"] = width - s["x"] - s["w"]
|
||||
elif p["type"] == "cable":
|
||||
p["from"] = fx(p["from"])
|
||||
elif p["type"] in ("bar", "pad") and "angle" in p:
|
||||
@@ -255,6 +268,16 @@ def flip_props(props, width):
|
||||
return out
|
||||
|
||||
|
||||
def prop_rotation(pitch, yaw_offset):
|
||||
"""View-space rotation carrying authored-view prop geometry to the camera
|
||||
`yaw_offset` degrees past the authored yaw: a rotation about the world-
|
||||
vertical axis through the root anchor, conjugated by the camera elevation.
|
||||
The identity at offset 0, so the authored view stays bit-exact."""
|
||||
if yaw_offset == 0:
|
||||
return K.IDENTITY
|
||||
return K.chain(K.rot_x(pitch), K.rot_y(-yaw_offset), K.rot_x(-pitch))
|
||||
|
||||
|
||||
def joint_points(geo, ref):
|
||||
"""Resolve a joint ref — `"hand_r"`, `"knee_l"`, or a midpoint list like
|
||||
`["knee_r", "foot_r"]` — to (point, unit direction of the bone ending at
|
||||
@@ -275,45 +298,90 @@ def joint_points(geo, ref):
|
||||
sum(p[1] for p in pts) / len(pts)), direction)
|
||||
|
||||
|
||||
def resolve_props(props, geo):
|
||||
"""Props -> drawable primitives for one frame: (background, foreground)."""
|
||||
def resolve_props(props, geo, anchor, rot=K.IDENTITY, auth_geo=None):
|
||||
"""Props -> drawable primitives for one frame: (background, foreground).
|
||||
|
||||
`geo` is the frame's drawn (possibly orbit-rotated) geometry, `auth_geo`
|
||||
the same frame at the authored camera (defaults to `geo`), `anchor` the
|
||||
frame's root canvas anchor, and `rot` the `prop_rotation` between them.
|
||||
Joint positions come from `geo`; everything authored — scene points,
|
||||
cable anchors, bar angles, pad perpendiculars, roller offsets — resolves
|
||||
against `auth_geo` and rotates through `rot`.
|
||||
"""
|
||||
auth = auth_geo if auth_geo is not None else geo
|
||||
|
||||
def place(pt, z=0.0):
|
||||
"""Authored canvas point (+ depth toward the camera) -> drawn canvas."""
|
||||
v = K.mvec(rot, (pt[0] - anchor[0], anchor[1] - pt[1], z))
|
||||
return (anchor[0] + v[0], anchor[1] - v[1])
|
||||
|
||||
def swing(vec, z=0.0):
|
||||
"""Authored canvas-space direction/offset -> drawn canvas (y-down)."""
|
||||
v = K.mvec(rot, (vec[0], -vec[1], z))
|
||||
return (v[0], -v[1])
|
||||
|
||||
bg, fg = [], []
|
||||
for p in props or []:
|
||||
t = p["type"]
|
||||
if t == "scene":
|
||||
for s in p["shapes"]:
|
||||
bg.append(dict(s, color=s.get("color", "equipment")))
|
||||
color = s.get("color", "equipment")
|
||||
z = s.get("z", 0.0)
|
||||
if s["kind"] == "line":
|
||||
depth = s.get("depth", 0.0)
|
||||
pts = [(pt[0], pt[1], z + (pt[2] if len(pt) > 2 else 0.0))
|
||||
for pt in s["pts"]]
|
||||
if depth:
|
||||
# An extruded slab: the polyline swept through +/-depth,
|
||||
# filled and outlined so it degenerates to the plain
|
||||
# line whenever the sweep collapses edge-on.
|
||||
quad = ([place(pt, pt[2] + depth) for pt in pts]
|
||||
+ [place(pt, pt[2] - depth) for pt in reversed(pts)])
|
||||
bg.append({"kind": "poly", "pts": [list(q) for q in quad],
|
||||
"w": s.get("w", 4), "color": color})
|
||||
else:
|
||||
bg.append({"kind": "line",
|
||||
"pts": [list(place(pt, pt[2])) for pt in pts],
|
||||
"w": s.get("w", 4), "color": color})
|
||||
elif s["kind"] == "circle":
|
||||
bg.append({"kind": "circle", "c": list(place(s["c"], z)),
|
||||
"r": s["r"], "fill": s.get("fill", False),
|
||||
"w": s.get("w", 3), "color": color})
|
||||
elif t == "cable":
|
||||
end, _ = joint_points(geo, p["to"])
|
||||
if end:
|
||||
bg.append({"kind": "line", "pts": [list(p["from"]), list(end)],
|
||||
start = place(p["from"], p["from"][2] if len(p["from"]) > 2 else 0.0)
|
||||
bg.append({"kind": "line", "pts": [list(start), 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:
|
||||
# The offset resolves along the authored-view bone, then rotates.
|
||||
c, _ = joint_points(geo, p["at"])
|
||||
_, d = joint_points(auth, p["at"])
|
||||
if not c or not d:
|
||||
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,
|
||||
off = swing((-d[0] * back + px * (r + 3), -d[1] * back + py * (r + 3)))
|
||||
fg.append({"kind": "circle", "c": [c[0] + off[0], c[1] + off[1]], "r": r,
|
||||
"fill": True, "color": "prop"})
|
||||
elif t in ("bar", "dumbbell", "pad"):
|
||||
c, d = joint_points(geo, p["at"])
|
||||
if not c:
|
||||
c, _ = joint_points(geo, p["at"])
|
||||
_, d = joint_points(auth, p["at"])
|
||||
if not c or not d:
|
||||
continue
|
||||
if t == "bar" or "angle" in p:
|
||||
ux, uy = dirv(p.get("angle", 0)) # fixed world angle
|
||||
axis = dirv(p.get("angle", 0)) # fixed authored-view angle
|
||||
else:
|
||||
ux, uy = -d[1], d[0] # perpendicular to the lower bone
|
||||
axis = (-d[1], d[0]) # perpendicular to the lower bone
|
||||
ux, uy = swing(axis) # foreshortens under orbit
|
||||
h = p.get("halfLen", {"bar": 24, "dumbbell": 7, "pad": 8}[t])
|
||||
a = (c[0] - ux * h, c[1] - uy * h)
|
||||
b = (c[0] + ux * h, c[1] + uy * h)
|
||||
fg.append({"kind": "line", "pts": [a, b],
|
||||
fg.append({"kind": "line", "pts": [list(a), list(b)],
|
||||
"w": p.get("w", {"bar": 4, "dumbbell": 3, "pad": 7}[t]),
|
||||
"color": "prop"})
|
||||
plate = p.get("plateR", 4.5 if t == "dumbbell" else 0)
|
||||
@@ -332,6 +400,11 @@ def svg_prims(prims, colors):
|
||||
d = "M " + " L ".join(f"{x:.1f} {y:.1f}" for x, y in p["pts"])
|
||||
lines.append(f' <path d="{d}" stroke="{color}" stroke-width="{p.get("w", 4)}"'
|
||||
f' stroke-linecap="round" stroke-linejoin="round"/>')
|
||||
elif p["kind"] == "poly":
|
||||
d = "M " + " L ".join(f"{x:.1f} {y:.1f}" for x, y in p["pts"]) + " Z"
|
||||
lines.append(f' <path d="{d}" fill="{color}" stroke="{color}"'
|
||||
f' stroke-width="{p.get("w", 4)}"'
|
||||
f' stroke-linecap="round" stroke-linejoin="round"/>')
|
||||
elif p["kind"] == "circle":
|
||||
cx, cy = p["c"]
|
||||
if p.get("fill"):
|
||||
@@ -339,17 +412,17 @@ def svg_prims(prims, colors):
|
||||
else:
|
||||
lines.append(f' <circle cx="{cx:.1f}" cy="{cy:.1f}" r="{p["r"]}"'
|
||||
f' stroke="{color}" stroke-width="{p.get("w", 3)}"/>')
|
||||
elif p["kind"] == "rect":
|
||||
lines.append(f' <rect x="{p["x"]}" y="{p["y"]}" width="{p["w"]}" height="{p["h"]}"'
|
||||
f' rx="{p.get("r", 2)}" fill="{colors[p.get("color", "equipment")]}"/>')
|
||||
return lines
|
||||
|
||||
|
||||
def draw_prims(d, prims, colors, scale):
|
||||
for p in prims:
|
||||
color = colors[p["color"]]
|
||||
if p["kind"] == "line":
|
||||
if p["kind"] in ("line", "poly"):
|
||||
pts = [(x * scale, y * scale) for x, y in p["pts"]]
|
||||
if p["kind"] == "poly":
|
||||
d.polygon(pts, fill=color)
|
||||
pts = pts + pts[:1]
|
||||
w = p.get("w", 4) * scale
|
||||
d.line(pts, fill=color, width=w, joint="curve")
|
||||
for x, y in (pts[0], pts[-1]):
|
||||
@@ -362,10 +435,6 @@ def draw_prims(d, prims, colors, scale):
|
||||
else:
|
||||
d.ellipse([cx - r, cy - r, cx + r, cy + r], outline=color,
|
||||
width=p.get("w", 3) * scale)
|
||||
elif p["kind"] == "rect":
|
||||
x, y = p["x"] * scale, p["y"] * scale
|
||||
d.rounded_rectangle([x, y, x + p["w"] * scale, y + p["h"] * scale],
|
||||
radius=p.get("r", 2) * scale, fill=color)
|
||||
|
||||
|
||||
# ------------------------------------------------------------------- drawing
|
||||
@@ -400,8 +469,8 @@ def quad_points(p0, ctrl, p2, n=24):
|
||||
return pts
|
||||
|
||||
|
||||
def svg_for_frame(name, geo, order, shade, working, colors, props=None):
|
||||
bg, fg = resolve_props(props, geo)
|
||||
def svg_for_frame(name, geo, order, shade, working, colors, prims=((), ())):
|
||||
bg, fg = prims
|
||||
w, h = CANVAS
|
||||
parts = [f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {w} {h}" fill="none">',
|
||||
f' <title>{name}</title>',
|
||||
@@ -443,10 +512,10 @@ def svg_for_frame(name, geo, order, shade, working, colors, props=None):
|
||||
return "\n".join(parts) + "\n"
|
||||
|
||||
|
||||
def draw_geo(geo, order, shade, working, colors, scale=2, font=None, props=None):
|
||||
def draw_geo(geo, order, shade, working, colors, scale=2, font=None, prims=((), ())):
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
bg, fg = resolve_props(props, geo)
|
||||
bg, fg = prims
|
||||
w, h = CANVAS[0] * scale, CANVAS[1] * scale
|
||||
img = Image.new("RGB", (w, h), "white")
|
||||
d = ImageDraw.Draw(img)
|
||||
@@ -545,21 +614,21 @@ def render_exercise(folder, figure="neutral", flip=False, strict=False):
|
||||
return geo, order, shade
|
||||
|
||||
resolved = []
|
||||
key_geos = []
|
||||
key_cells = []
|
||||
for nf in norms:
|
||||
out, geo, order, shade = frame_geometry(nf, prof, cam, flip, pitch, mat)
|
||||
for limb in hide:
|
||||
geo.pop(limb, None)
|
||||
resolved.append(out)
|
||||
key_geos.append((geo, order, shade))
|
||||
key_cells.append((geo, order, shade, resolve_props(props, geo, nf["root"]["pos"])))
|
||||
|
||||
frames_dir = folder / "frames"
|
||||
frames_dir.mkdir(exist_ok=True)
|
||||
for old in frames_dir.glob("frame-*.svg"):
|
||||
old.unlink()
|
||||
colors = PALETTES["default"]
|
||||
svgs = [svg_for_frame(motion["name"], geo, order, shade, working, colors, props)
|
||||
for geo, order, shade in key_geos]
|
||||
svgs = [svg_for_frame(motion["name"], geo, order, shade, working, colors, prims)
|
||||
for geo, order, shade, prims in key_cells]
|
||||
for i, svg in enumerate(svgs, start=1):
|
||||
(frames_dir / f"frame-{i}.svg").write_text(svg)
|
||||
(folder / "visual.svg").write_text(svgs[motion.get("primary", 1) - 1])
|
||||
@@ -569,7 +638,8 @@ def render_exercise(folder, figure="neutral", flip=False, strict=False):
|
||||
imgs = []
|
||||
for nf in timeline(resolved):
|
||||
geo, order, shade = geometry(nf)
|
||||
imgs.append(draw_geo(geo, order, shade, working, colors, font=font, props=props))
|
||||
prims = resolve_props(props, geo, nf["root"]["pos"])
|
||||
imgs.append(draw_geo(geo, order, shade, working, colors, font=font, prims=prims))
|
||||
imgs[0].save(folder / "preview.gif", save_all=True, append_images=imgs[1:],
|
||||
duration=50, loop=0)
|
||||
print(f" {motion['name']}: {len(svgs)} frames, preview.gif")
|
||||
@@ -578,29 +648,34 @@ def render_exercise(folder, figure="neutral", flip=False, strict=False):
|
||||
|
||||
|
||||
def render_orbit(folder, figure="neutral"):
|
||||
"""A full-turn demo: the camera sweeps 360 degrees while the motion loops.
|
||||
Scene props are view-locked billboards, so orbit shines on prop-free
|
||||
motions (bodyweight exercises)."""
|
||||
"""An orbiting demo of what the app shows: the camera sweeps 360 degrees
|
||||
while the motion loops (or swings a `camera.sweep` pendulum when the
|
||||
motion authors one). Props rotate with the figure about the root anchor."""
|
||||
motion = load_motion(folder)
|
||||
working = set(motion.get("working", []))
|
||||
hide = set(motion.get("hide", []))
|
||||
norms, prof, cam, pitch, props = prepare(motion, figure)
|
||||
resolved = [frame_geometry(nf, prof, cam, pitch=pitch)[0] for nf in norms]
|
||||
mat = mat_bounds(norms, prof, cam, pitch)
|
||||
sweep = motion.get("camera", {}).get("sweep")
|
||||
font = legend_font()
|
||||
colors = PALETTES["default"]
|
||||
ticks = timeline(resolved)
|
||||
imgs = []
|
||||
for i, nf in enumerate(ticks):
|
||||
yaw = cam + 360.0 * i / len(ticks)
|
||||
off = (sweep * math.sin(2 * math.pi * i / len(ticks)) if sweep
|
||||
else 360.0 * i / len(ticks))
|
||||
# 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, pitch=pitch)
|
||||
# then rotate the posed body - never re-pin in the rotated view. The
|
||||
# authored-view geometry also feeds the props' directions and offsets.
|
||||
posed, auth_geo, _, _ = frame_geometry(nf, prof, cam, pitch=pitch)
|
||||
posed["pins"] = {}
|
||||
_, geo, order, shade = frame_geometry(posed, prof, yaw, pitch=pitch, mat=mat)
|
||||
_, geo, order, shade = frame_geometry(posed, prof, cam + off, pitch=pitch, mat=mat)
|
||||
for limb in hide:
|
||||
geo.pop(limb, None)
|
||||
imgs.append(draw_geo(geo, order, shade, working, colors, font=font, props=props))
|
||||
prims = resolve_props(props, geo, nf["root"]["pos"],
|
||||
prop_rotation(pitch, off), auth_geo)
|
||||
imgs.append(draw_geo(geo, order, shade, working, colors, font=font, prims=prims))
|
||||
imgs[0].save(folder / "orbit.gif", save_all=True, append_images=imgs[1:],
|
||||
duration=50, loop=0)
|
||||
print(f" {motion['name']}: orbit.gif ({len(imgs)} frames)")
|
||||
@@ -618,9 +693,10 @@ def contact_sheet(folders, figure="neutral", out=None):
|
||||
_, geo, order, shade = frame_geometry(nf, prof, cam, pitch=pitch, mat=mat)
|
||||
for limb in hide:
|
||||
geo.pop(limb, None)
|
||||
prims = resolve_props(props, geo, nf["root"]["pos"])
|
||||
cells.append((f"{motion['name']} {i}/{len(norms)}",
|
||||
draw_geo(geo, order, shade, working, PALETTES["default"],
|
||||
font=font, props=props)))
|
||||
font=font, prims=prims)))
|
||||
save_sheet(cells, Path(out) if out else LIB / "contact-sheet.png")
|
||||
|
||||
|
||||
@@ -644,9 +720,10 @@ def demo_sheet(folder):
|
||||
_, geo, order, shade = frame_geometry(norms[idx], prof, cam, flip, pitch, mat)
|
||||
for limb in hide:
|
||||
geo.pop(limb, None)
|
||||
prims = resolve_props(props, geo, norms[idx]["root"]["pos"])
|
||||
cells.append((f"{motion['name']} — {label}",
|
||||
draw_geo(geo, order, shade, working, PALETTES[palette],
|
||||
font=font, props=props)))
|
||||
font=font, prims=prims)))
|
||||
save_sheet(cells, LIB / "demo-sheet.png", cols=3)
|
||||
|
||||
|
||||
@@ -689,6 +766,62 @@ def export_app_resources(folders):
|
||||
print(f" exported skeleton.json -> {out}")
|
||||
|
||||
|
||||
def write_fixtures(folders):
|
||||
"""Regenerate `WorkoutsTests/Fixtures/figure-fixtures.json` — the projected
|
||||
geometry snapshots that pin the Swift solver to this pipeline: per exercise
|
||||
and key frame the draw order, shading, spine, head, nose, and limb chains,
|
||||
plus a mid-tween sample and (for a spread of prop flavors) orbit samples
|
||||
taken through the presentation path — resolve at the authored camera, clear
|
||||
the pins, rotate the posed body — with the resolved prop primitives."""
|
||||
ORBIT_SAMPLED = {"Bird Dog", "Lat Pull Down", "Leg Extension", "Abductor"}
|
||||
OFFSETS = (37.0, 180.0)
|
||||
T = 0.5
|
||||
|
||||
def sample(geo, order, shade, prims=None):
|
||||
out = {"order": order, "shade": shade,
|
||||
"spine": [list(p) for p in geo["spine"]],
|
||||
"head": list(geo["head"])}
|
||||
if "nose" in geo:
|
||||
out["nose"] = [list(geo["nose"][0]), list(geo["nose"][1])]
|
||||
for limb in K.LIMBS:
|
||||
out[limb] = [list(p) for p in geo[limb]]
|
||||
if prims is not None:
|
||||
out["props"] = {"bg": prims[0], "fg": prims[1]}
|
||||
return out
|
||||
|
||||
exercises = []
|
||||
for folder in folders:
|
||||
motion = load_motion(folder)
|
||||
norms, prof, cam, pitch, props = prepare(motion, "neutral")
|
||||
resolved, frames = [], []
|
||||
for nf in norms:
|
||||
out, geo, order, shade = frame_geometry(nf, prof, cam)
|
||||
resolved.append(out)
|
||||
frames.append(sample(geo, order, shade))
|
||||
mid = K.lerp_frames(resolved[0], resolved[1], ease(T))
|
||||
_, geo, order, shade = frame_geometry(mid, prof, cam)
|
||||
entry = {"name": motion["name"], "camera": cam, "frames": frames,
|
||||
"tween": {"t": T, "sample": sample(geo, order, shade)}}
|
||||
if motion["name"] in ORBIT_SAMPLED:
|
||||
anchor = norms[0]["root"]["pos"]
|
||||
posed, auth_geo, _, _ = frame_geometry(norms[0], prof, cam)
|
||||
posed["pins"] = {}
|
||||
orbit = []
|
||||
for off in OFFSETS:
|
||||
_, geo, order, shade = frame_geometry(posed, prof, cam + off)
|
||||
prims = resolve_props(props, geo, anchor,
|
||||
prop_rotation(CAMERA_PITCH, off), auth_geo)
|
||||
orbit.append({"yaw": cam + off,
|
||||
"sample": sample(geo, order, shade, prims)})
|
||||
entry["orbit"] = orbit
|
||||
exercises.append(entry)
|
||||
|
||||
out_path = LIB.parent / "WorkoutsTests" / "Fixtures" / "figure-fixtures.json"
|
||||
out_path.write_text(json.dumps(
|
||||
{"profile": "neutral", "pitch": CAMERA_PITCH, "exercises": exercises}))
|
||||
print(f" {out_path.name} ({len(exercises)} exercises)")
|
||||
|
||||
|
||||
def main():
|
||||
flags = [a for a in sys.argv[1:] if a.startswith("--")]
|
||||
names = [a for a in sys.argv[1:] if not a.startswith("--")]
|
||||
@@ -704,6 +837,9 @@ def main():
|
||||
if "--export" in flags:
|
||||
export_app_resources(folders)
|
||||
return
|
||||
if "--fixtures" in flags:
|
||||
write_fixtures(folders)
|
||||
return
|
||||
if "--orbit" in flags:
|
||||
for folder in folders:
|
||||
render_orbit(folder, figure=figure)
|
||||
|
||||
Reference in New Issue
Block a user