Author the machine exercise circuit with props and corrected hip machine motions

Fourteen Planet Fitness machine exercises join the rig library, each with
authored motion, info page, and schematic equipment via the new props layer
(scene shapes, cables, bars, pads) rendered in lockstep by render.py and the
in-app figure renderer. Abductor and Adductor are authored face-on with the
real seated machine motion — knees-bent legs swinging apart/together against
knee pads — replacing the earlier middle-split depiction. The watch target
now bundles the figure renderer and motion rigs too.

Claude-Session: https://claude.ai/code/session_01LEoff8bXGBS83tK1c55Mf7
This commit is contained in:
2026-07-06 16:29:31 -04:00
parent 888852cc2e
commit fce8fa4c17
130 changed files with 4116 additions and 82 deletions
+75 -2
View File
@@ -39,9 +39,81 @@ struct ExerciseMotion: Codable {
let working: [String]?
/// Limbs fully occluded in this view never drawn.
let hide: [String]?
/// Equipment layer: scene shapes and cables behind the figure, joint-attached
/// items (bar/dumbbell/pad) over the limbs. See SYSTEM.md "The props layer".
let props: [MotionProp]?
let frames: [MotionKeyFrame]
}
/// A prop's joint reference: one extremity (`"hand_r"`) or the midpoint of two
/// (`["foot_r", "foot_l"]`). Extremity keys match the pin keys.
enum PropJointRef: Codable {
case single(String)
case midpoint([String])
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let name = try? container.decode(String.self) {
self = .single(name)
} else {
self = .midpoint(try container.decode([String].self))
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .single(let name): try container.encode(name)
case .midpoint(let names): try container.encode(names)
}
}
var names: [String] {
switch self {
case .single(let name): [name]
case .midpoint(let names): names
}
}
}
/// One static shape of a `scene` prop, in canvas coordinates.
struct PropSceneShape: Codable {
let kind: String // "line" | "circle" | "rect"
/// line: polyline points; `w` is the stroke width.
let pts: [[Double]]?
let w: Double?
/// circle: center + radius; `fill` false draws an outline.
let c: [Double]?
let r: Double?
let fill: Bool?
/// rect: origin + size (`w`/`h`), corner radius `r`; always filled.
let x: Double?
let y: Double?
let h: Double?
/// Optional palette override: `"prop"` for the darker attached-item gray.
let color: String?
}
/// One equipment prop. `type` selects the flavor; the other fields apply per type
/// (mirroring the reference renderer's `resolve_props`).
struct MotionProp: Codable {
let type: String // "scene" | "cable" | "bar" | "dumbbell" | "pad"
/// scene: the static shapes.
let shapes: [PropSceneShape]?
/// cable: fixed anchor `[x, y]` moving joint `to`.
let from: [Double]?
let to: PropJointRef?
/// bar/dumbbell/pad: the joint(s) the item is centered on.
let at: PropJointRef?
/// Fixed world angle (degrees, y-up). Default: bars are horizontal;
/// dumbbells/pads sit perpendicular to the lower bone.
let angle: Double?
let halfLen: Double?
let w: Double?
/// End-disc radius (dumbbell plates default 4.5; bars none).
let plateR: Double?
}
/// A key frame of absolute joint angles. Limbs listed in the motion's `hide` may be
/// absent entirely; a two-element array is `[upper, lower]` bone angles.
struct MotionKeyFrame: Codable {
@@ -56,8 +128,9 @@ struct MotionKeyFrame: Codable {
let spine: [Double]
/// Head direction from the neck joint.
let neck: Double
/// Nose-tick direction (the belly is on that side).
let gaze: Double
/// Nose-tick direction (the belly is on that side). Absent when the figure
/// faces the viewer no nose is drawn.
let gaze: Double?
let armR: [Double]?
let armL: [Double]?
let legR: [Double]?