Add Warm-Up and Stretching activity types
Adds WorkoutActivityType.warmUp (HealthKit .preparationAndRecovery) and .stretching (.flexibility), and retags the six starter splits that were all mislabeled as Functional Strength: - Warm-Up: Upper Body Warm-Up, Lower Body Warm-Up, Morning Wake-Up - Stretching: Morning Mobility, Full Body Stretch, Evening Stretch The split editor's activity picker surfaces them automatically (CaseIterable). Older app versions decode the new raw values as the default type — additive and not schema-gated, so no quarantine.
This commit is contained in:
@@ -118,6 +118,18 @@ def _clamp(x, lo=-1.0, hi=1.0):
|
||||
return max(lo, min(hi, x))
|
||||
|
||||
|
||||
# Axial rotation of a two-bone limb is recoverable only from the lower bone's
|
||||
# lateral tip; below this magnitude the limb is effectively in-plane and its
|
||||
# rotation is left at 0 (see invert_limb).
|
||||
ROT_MIN_LATERAL = 0.08
|
||||
|
||||
# When a leg's authored knee sits within this fraction of the thigh length off the
|
||||
# hip->ankle line (~sin 8.6 deg), the leg is treated as straight and its IK knee is
|
||||
# bent anatomically forward rather than trusting the (unreliable) authored side
|
||||
# (see solve_limb).
|
||||
KNEE_STRAIGHT_FRAC = 0.15
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- the frame
|
||||
|
||||
# DoF names per joint type; the first is the shorthand a bare number sets.
|
||||
@@ -296,13 +308,27 @@ def solve_limb(kind, attach, target, guess_mid, lengths, parent, sigma):
|
||||
|
||||
along = (a * a + d * d - b * b) / (2 * d)
|
||||
h = math.sqrt(max(a * a - along * along, 0.0))
|
||||
best = None
|
||||
for sign in (1.0, -1.0):
|
||||
if kind == "leg":
|
||||
# A knee bends one way only. Near full extension the two knee solutions
|
||||
# straddle the hip->ankle line and the authored guess (also near that line)
|
||||
# cannot reliably pick a side - the bend plane's normal is a cross of two
|
||||
# near-parallel vectors, so its sign is noise and the knee can flip behind
|
||||
# the leg, swinging the whole thigh backward. When the authored knee sits
|
||||
# within KNEE_STRAIGHT_FRAC of the line, treat the leg as straight and bend
|
||||
# the knee anatomically forward (anterior); otherwise honor the authored side.
|
||||
gm = vsub(guess_mid, attach)
|
||||
gm_perp = vsub(gm, vscale(dir_t, vdot(gm, dir_t)))
|
||||
ref = mvec(parent, (1, 0, 0)) if vlen(gm_perp) < KNEE_STRAIGHT_FRAC * a else gm_perp
|
||||
sign = 1.0 if vdot(perp, ref) >= 0 else -1.0
|
||||
mid = vadd(attach, vadd(vscale(dir_t, along), vscale(perp, sign * h)))
|
||||
dist = vlen(vsub(mid, guess_mid))
|
||||
if best is None or dist < best[0]:
|
||||
best = (dist, mid)
|
||||
mid = best[1]
|
||||
else:
|
||||
best = None
|
||||
for sign in (1.0, -1.0):
|
||||
mid = vadd(attach, vadd(vscale(dir_t, along), vscale(perp, sign * h)))
|
||||
dist = vlen(vsub(mid, guess_mid))
|
||||
if best is None or dist < best[0]:
|
||||
best = (dist, mid)
|
||||
mid = best[1]
|
||||
end = vadd(mid, vscale(vnorm(vsub(target, mid)), b))
|
||||
return invert_limb(kind, attach, mid, end, lengths, parent, sigma)
|
||||
|
||||
@@ -318,9 +344,17 @@ def invert_limb(kind, attach, mid, end, lengths, parent, sigma):
|
||||
peel = mtrans(chain(rot_z(flex), rot_x(-sigma * abd)))
|
||||
w = vnorm(mvec(peel, mvec(pt, vsub(end, mid))))
|
||||
bend = math.degrees(math.acos(_clamp(-w[1])))
|
||||
rot = sigma * math.degrees(math.atan2(w[2], w[0])) if bend > 0.5 else 0.0
|
||||
# Axial rotation is observable only through the lower bone's lateral tip
|
||||
# (w[2]); a near-sagittal limb (w[2] ~ 0) carries no recoverable rotation.
|
||||
# Gating on `bend` alone is too weak: a limb can straighten through this
|
||||
# degeneracy while still visibly bent, and atan2 then snaps to +/-180 on the
|
||||
# sign of a near-zero anterior component - twisting the limb a half-turn and
|
||||
# flipping a pinned hand/foot backward.
|
||||
twist = bend > 0.5 and abs(w[2]) > ROT_MIN_LATERAL
|
||||
if kind == "leg": # knees hinge backward; the sign convention flips
|
||||
rot = sigma * math.degrees(math.atan2(-w[2], -w[0])) if bend > 0.5 else 0.0
|
||||
rot = sigma * math.degrees(math.atan2(-w[2], -w[0])) if twist else 0.0
|
||||
else:
|
||||
rot = sigma * math.degrees(math.atan2(w[2], w[0])) if twist else 0.0
|
||||
upper = {"flexion": flex, "abduction": abd, "rotation": rot}
|
||||
return upper, {"flexion": bend}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user