Surface machine settings in the exercise library and refine machine rigs
Library detail screens now lead with the user's recorded machine settings (per-split when they disagree, empty-state card for machine-based entries) and append the weight progression chart. Starter seeds mark machine exercises with an empty machineSettings list so the settings UI lights up before first use. The figure rig gains a frontal body profile for face-on machines, props that can ride mid joints (knees/elbows), and an alternating four-frame Bird Dog loop. Claude-Session: https://claude.ai/code/session_01LEoff8bXGBS83tK1c55Mf7
This commit is contained in:
@@ -140,16 +140,27 @@ struct ExerciseFigureView: View {
|
||||
|
||||
// MARK: Props
|
||||
|
||||
/// The extremity point a prop follows (plus the lower-bone unit direction for
|
||||
/// perpendicular items). Nil when any referenced limb isn't drawn this frame.
|
||||
/// Prop joint refs → (limb, chain index): extremities are index 2, mid joints
|
||||
/// (elbows/knees) index 1, so equipment can ride either joint. Kept 1:1 with
|
||||
/// the reference renderer's `JOINT_LIMB`.
|
||||
private static let propJoints: [String: (limb: FigureLimb, index: Int)] = [
|
||||
"hand_r": (.armR, 2), "elbow_r": (.armR, 1),
|
||||
"hand_l": (.armL, 2), "elbow_l": (.armL, 1),
|
||||
"foot_r": (.legR, 2), "knee_r": (.legR, 1),
|
||||
"foot_l": (.legL, 2), "knee_l": (.legL, 1),
|
||||
]
|
||||
|
||||
/// The joint point a prop follows (plus the unit direction of the bone ending
|
||||
/// at the first joint, for perpendicular items). Nil when any referenced limb
|
||||
/// isn't drawn this frame.
|
||||
private func jointAnchor(_ geo: FigureGeometry, _ ref: PropJointRef) -> (point: CGPoint, direction: CGVector)? {
|
||||
var points: [CGPoint] = []
|
||||
var direction: CGVector?
|
||||
for name in ref.names {
|
||||
guard let limb = FigureLimb.allCases.first(where: { $0.pinKey == name }),
|
||||
let chain = geo.limbs[limb], chain.count >= 2 else { return nil }
|
||||
let a = chain[chain.count - 2]
|
||||
let b = chain[chain.count - 1]
|
||||
guard let joint = Self.propJoints[name],
|
||||
let chain = geo.limbs[joint.limb], chain.count > joint.index else { return nil }
|
||||
let a = chain[joint.index - 1]
|
||||
let b = chain[joint.index]
|
||||
points.append(b)
|
||||
if direction == nil {
|
||||
let d = max(hypot(b.x - a.x, b.y - a.y), 1)
|
||||
|
||||
@@ -22,6 +22,13 @@ struct ExerciseInfo {
|
||||
var targets: [String]
|
||||
var sections: [Section]
|
||||
|
||||
/// Whether the authored `Type:` identifies this as a machine exercise — the
|
||||
/// library convention is that every machine entry's type starts "Machine-based".
|
||||
/// Drives the machine-settings sections' visibility before any settings exist.
|
||||
var isMachineBased: Bool {
|
||||
type?.localizedCaseInsensitiveContains("machine") == true
|
||||
}
|
||||
|
||||
struct Section {
|
||||
var title: String
|
||||
var items: [Item]
|
||||
|
||||
@@ -35,6 +35,9 @@ struct ExerciseMotion: Codable {
|
||||
let name: String
|
||||
/// 1-based frame used for the static visual (unused by the animated renderer).
|
||||
let primary: Int?
|
||||
/// Body-profile override (e.g. `"frontal"`: foreshortened legs for face-on
|
||||
/// seated machines). Nil uses the `neutral` profile.
|
||||
let figure: String?
|
||||
/// Parts (`arm_r`, `leg_l`, `spine`, …) drawn in the working accent color.
|
||||
let working: [String]?
|
||||
/// Limbs fully occluded in this view — never drawn.
|
||||
@@ -45,8 +48,9 @@ struct ExerciseMotion: Codable {
|
||||
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.
|
||||
/// A prop's joint reference: one joint (`"hand_r"`, `"knee_l"`, `"elbow_r"`, …)
|
||||
/// or the midpoint of two (`["foot_r", "foot_l"]`, `["knee_r", "foot_r"]`).
|
||||
/// Extremity keys match the pin keys; elbows/knees are the mid joints.
|
||||
enum PropJointRef: Codable {
|
||||
case single(String)
|
||||
case midpoint([String])
|
||||
@@ -180,7 +184,7 @@ enum ExerciseMotionLibrary {
|
||||
let bodyData = try? Data(contentsOf: bodyURL),
|
||||
let motion = try? JSONDecoder().decode(ExerciseMotion.self, from: motionData),
|
||||
let profiles = try? JSONDecoder().decode([String: ExerciseBodyProfile].self, from: bodyData),
|
||||
let body = profiles["neutral"]
|
||||
let body = profiles[motion.figure ?? "neutral"]
|
||||
else { return nil }
|
||||
return Resources(motion: motion, body: body)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user