# Exercise Visual System Exercise visuals are produced by an **articulated 2D rig**: one shared stick body posed per exercise by joint angles. Nothing is drawn by hand — a body profile plus a motion script resolve through forward kinematics into every frame, so figures are always in proportion, and the whole library can be re-proportioned (male/female), flipped, rotated in-plane, or re-themed by changing data, never artwork. ## The rig - **`body.json`** — proportion profiles. Each profile is a table of bone lengths: `headR`, `neck`, `spine1`/`spine2` (two chained segments so the spine can curve), `upperArm`/`foreArm`, `thigh`/`shin`, plus `leftOffset` (the small offset that separates left-limb attachments visually). `neutral` is the default; add profiles to add figures. - **`/motion.json`** — the exercise script: key frames of **absolute joint angles** (degrees, y-up: `0`=forward/right, `90`=up, `180`=back/left, `-90`=down), a `root` pelvis position, and timing. ```json { "name": "Bird Dog", "primary": 2, // 1-based frame used for visual.svg "working": ["arm_r", "leg_l"], // parts drawn in the accent color "hide": [], // limbs fully occluded in this view "frames": [ { "hold": 0.5, // seconds held at this key frame "tween": 0.8, // seconds animating to the NEXT frame "root": [190, 106], // pelvis, canvas coords "spine": [171, 171], // pelvis→mid, mid→neck angles "neck": 187, "gaze": 205, // head direction; nose tick direction "arm_r": [-90, -90], // upper-arm, forearm angles "arm_l": [-90, -90], "leg_r": [-83, 0], // thigh, shin angles "leg_l": [-83, 0], "pins": {"hand_r": [105, 152], "hand_l": [111, 154]} } ] } ``` - **Pins (IK)** — a planted hand/foot names a target point (`hand_r`/`hand_l`/`foot_r`/`foot_l`); the renderer solves the two-bone chain analytically so the extremity holds that point exactly, using the authored angles only to pick the elbow/knee bend direction. A pin active in two consecutive key frames stays planted *throughout the tween* (plank forearms, side-plank support arm); a pin present in only one frame releases naturally (bird-dog arm lifting off). - **Tweening** happens in angle space (shortest path), so limbs swing in natural arcs and bone lengths never distort. The last frame tweens back to the first (looping). Asymmetric timing carries technique: leg raises lower slowly (`tween` 1.4 s down, 0.6 s up). - **Face-on figures** — `gaze` is optional: a frame without it faces the viewer and draws no nose tick. Used by exercises whose motion is lateral (abductor/adductor), where a side view would hide the movement. ## The props layer Machines and free weights are data too: an optional top-level `"props"` array adds an equipment layer around the figure. `scene` shapes and `cable`s draw *behind* the figure in a recessive gray; joint-attached items (`bar`, `dumbbell`, `pad`) draw *over* the limbs in a darker gray and follow the resolved hand/foot positions every frame — a pinned foot pressing a `pad` carries the platform with it through the tween for free. The figure stays the hero: props are schematic silhouettes (a seat, a backrest, one handle), never scale drawings of the machine. ```json "props": [ {"type": "scene", "shapes": [ {"kind": "line", "pts": [[134, 123], [96, 36]], "w": 9}, {"kind": "rect", "x": 54, "y": 104, "w": 40, "h": 8, "r": 3}, {"kind": "circle", "c": [142, 77], "r": 3.5, "fill": true, "color": "prop"} ]}, {"type": "cable", "from": [190, 8], "to": ["hand_r", "hand_l"]}, {"type": "bar", "at": ["hand_r", "hand_l"], "halfLen": 26, "plateR": 0}, {"type": "dumbbell", "at": "hand_r"}, {"type": "pad", "at": ["foot_r", "foot_l"], "angle": 88, "halfLen": 20, "w": 6} ] ``` - **`scene`** — static shapes in canvas coordinates: `line` (polyline, stroke width `w`), `circle` (`fill: false` for an outline), `rect` (filled, corner radius `r`). A shape may set `"color": "prop"` to use the darker attached-item gray (e.g. a fixed handle the hands rest on). - **`cable`** — a thin line from a fixed anchor `from` to a moving joint `to`; the machine's pulley line. - **`bar` / `dumbbell` / `pad`** — a segment centered on the joint(s) in `at` (a single extremity, or the midpoint of `["hand_r", "hand_l"]`). `bar` lies at a fixed world `angle` (default 0 = horizontal — in side view a two-handed bar is drawn horizontal by convention); `dumbbell` and `pad` default to perpendicular to the lower bone (forearm/shin), or take an explicit `angle`. `plateR` puts filled discs on both ends (dumbbells default to 4.5). A prop whose limb is hidden that frame simply isn't drawn. - The same math is the plan for the app: a small SwiftUI renderer consumes `body.json` + `motion.json` and tweens angles on the lower half of the exercise screen (the paged timer flow occupies only the top half). ## The visual language - **Right vs left limb** — the one rule that never bends: the figure's **right**-side limbs are dark (`#3a3f4b`), its **left**-side limbs are light (`#a9afba`) and drawn *behind* the body. Working limbs keep the split: right = teal `#0d9488`, left = light teal `#86cfc5`. Opposite-limb moves (bird dog, dead bug) read as visibly opposite: one dark-teal limb, one light-teal limb. `render.py` reference renders embed a small `R —` / `L —` legend for the rig author; the in-app renderer deliberately omits it — the labels are only anatomically true for right-facing figures, and the contrast alone carries the meaning. - **Facing / front-of-torso** — the head carries a **nose tick** (`gaze` angle); the belly is on that side. Prone noses point at the floor, supine at the ceiling. The head is drawn last, filled opaque, so overhead arms pass behind the face. - **Spine** — rendered as a smooth curve through pelvis → mid → neck; teal when the trunk is the working part. - Canvas 320×180, ground line at y = 152. Limbs listed in `hide` are fully occluded in this view and not drawn. ## Rendering ```sh cd "Exercise Library" python3 render.py # all exercises: frames/*.svg, preview.gif, visual.svg python3 render.py "Bird Dog" # one exercise python3 render.py --sheet # + contact-sheet.png of every key frame python3 render.py --demo # + demo-sheet.png: profile / flip / theme variants python3 render.py --figure=female # render with another body profile python3 render.py --flip # mirror the figure (faces the other way) python3 render.py --export # copy body.json + .motion.json + .info.md app resources ``` `render.py` needs only Pillow (for GIFs/sheets; the SVGs have no dependency). The library lives at the repo root, outside every target's source folders — same-named files per entry (`info.md`, `visual.svg`) would collide in Xcode's flat resource copy, so the library itself never enters the app bundle. Only the `--export` copies ship: `body.json` plus uniquely-named `.motion.json` and `.info.md` files in `Workouts/Resources/ExerciseMotions/`, consumed by the in-app SwiftUI renderer (`Workouts/ExerciseFigure/`) and the exercise-library reference screen (`ExerciseInfo.swift` parses the info pages). Re-run `python3 render.py --export` after editing any motion or info page; the library stays the source of truth.