Files
workouts/Exercise Library/SYSTEM.md
T
rzen 7274f155e9 Add the exercise reference library, animated exercise figures, and exercise categories
Exercise Library/ holds per-exercise reference docs (setup, cues,
mistakes, progressions) with SVG visuals and a Python-rendered motion
pipeline; Workouts/ExerciseFigure renders the bundled *.motion.json
rigs as animated stick figures on the exercise screen. Exercises gain
a warm-up/main-circuit category, timed exercises display hold time via
planSummary, and a completed exercise reopens to a check screen instead
of its timers.
2026-07-06 01:15:52 -04:00

98 lines
4.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.
- **`<Exercise>/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).
- 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`. Every render embeds a
small `R —` / `L —` legend, so opposite-limb moves (bird dog, dead bug) are
visibly opposite: one dark-teal limb, one light-teal limb.
- **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 + <Name>.motion.json 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
`<Name>.motion.json` files in `Workouts/Resources/ExerciseMotions/`, consumed
by the in-app SwiftUI renderer (`Workouts/ExerciseFigure/`). Re-run
`python3 render.py --export` after editing any motion; the library stays the
source of truth.