Give machine props world-space 3D form that rotates with the camera

Scene shapes, cable anchors, bar angles, pad perpendiculars, and roller
offsets all resolve in the authored view exactly as before, then rotate
about the world-vertical axis through the root anchor - the same
resolve-then-rotate pattern as the figure's pins and the mat - so at the
authored yaw every exercise renders bit-identically to today, and under
an orbiting camera the equipment turns with the figure while staying
welded to its hands and feet. Scene lines gain an optional depth plane
(z) and slab extrusion (depth) so seats, backrests, and platforms keep
form edge-on; the rect shape is retired (re-authored as slab lines).
All 14 machines' props re-authored with depths and verified at eight
orbit angles. The fixture snapshots move into the pipeline as
render.py --fixtures and now cover orbit-presentation samples with
resolved prop primitives for a spread of prop flavors; the in-app
renderer resolves props in MotionSolver (lockstep with resolve_props)
and the view just draws primitives.

Claude-Session: https://claude.ai/code/session_01HJDQQDA9QdP8zByg43H5v3
This commit is contained in:
2026-07-06 22:15:45 -04:00
parent ed906535b6
commit 81186c51b1
92 changed files with 723 additions and 403 deletions
@@ -1,11 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Abdominal</title>
<path d="M 105.8 160.8 L 213.8 160.8 L 213.8 150.3 L 105.8 150.3 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
<path d="M 120.0 128.0 L 114.0 62.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 106.0 130.0 L 150.0 130.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 120.0 128.0 L 114.0 62.0 L 114.0 62.0 L 120.0 128.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 106.0 130.0 L 150.0 130.0 L 150.0 130.0 L 106.0 130.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 122.0 132.0 L 122.0 150.0" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 102.0 150.0 L 150.0 150.0" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 156.0 149.0 L 190.0 149.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 102.0 150.0 L 150.0 150.0 L 150.0 150.0 L 102.0 150.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 156.0 149.0 L 190.0 149.0 L 190.0 149.0 L 156.0 149.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 133.9 126.8 L 176.4 109.3 L 172.0 148.4 L 182.9 149.6" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 130.9 42.5 L 159.5 33.6 L 161.0 63.1" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="girdle" d="M 130.9 42.5 L 125.0 42.4 L 125.0 44.3" stroke="#0d9488" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

@@ -1,11 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Abdominal</title>
<path d="M 105.8 160.8 L 213.8 160.8 L 213.8 150.3 L 105.8 150.3 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
<path d="M 120.0 128.0 L 114.0 62.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 106.0 130.0 L 150.0 130.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 120.0 128.0 L 114.0 62.0 L 114.0 62.0 L 120.0 128.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 106.0 130.0 L 150.0 130.0 L 150.0 130.0 L 106.0 130.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 122.0 132.0 L 122.0 150.0" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 102.0 150.0 L 150.0 150.0" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 156.0 149.0 L 190.0 149.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 102.0 150.0 L 150.0 150.0 L 150.0 150.0 L 102.0 150.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 156.0 149.0 L 190.0 149.0 L 190.0 149.0 L 156.0 149.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 133.9 126.8 L 176.4 109.3 L 172.0 148.4 L 182.9 149.6" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 173.5 57.8 L 201.8 47.9 L 196.0 76.8" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="girdle" d="M 173.5 57.8 L 167.6 57.7 L 167.6 59.6" stroke="#0d9488" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

+4 -4
View File
@@ -4,11 +4,11 @@
"working": ["spine"],
"props": [
{"type": "scene", "shapes": [
{"kind": "line", "pts": [[120, 128], [114, 62]], "w": 8},
{"kind": "line", "pts": [[106, 130], [150, 130]], "w": 8},
{"kind": "line", "pts": [[120, 128], [114, 62]], "w": 8, "depth": 7},
{"kind": "line", "pts": [[106, 130], [150, 130]], "w": 8, "depth": 8},
{"kind": "line", "pts": [[122, 132], [122, 150]], "w": 4},
{"kind": "line", "pts": [[102, 150], [150, 150]], "w": 4},
{"kind": "line", "pts": [[156, 149], [190, 149]], "w": 5}
{"kind": "line", "pts": [[102, 150], [150, 150]], "w": 4, "depth": 5},
{"kind": "line", "pts": [[156, 149], [190, 149]], "w": 5, "depth": 6}
]},
{"type": "pad", "at": ["hand_r", "hand_l"], "angle": 90, "halfLen": 14, "w": 6}
],
Binary file not shown.

Before

Width:  |  Height:  |  Size: 188 KiB

After

Width:  |  Height:  |  Size: 177 KiB

+4 -4
View File
@@ -1,11 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Abdominal</title>
<path d="M 105.8 160.8 L 213.8 160.8 L 213.8 150.3 L 105.8 150.3 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
<path d="M 120.0 128.0 L 114.0 62.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 106.0 130.0 L 150.0 130.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 120.0 128.0 L 114.0 62.0 L 114.0 62.0 L 120.0 128.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 106.0 130.0 L 150.0 130.0 L 150.0 130.0 L 106.0 130.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 122.0 132.0 L 122.0 150.0" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 102.0 150.0 L 150.0 150.0" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 156.0 149.0 L 190.0 149.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 102.0 150.0 L 150.0 150.0 L 150.0 150.0 L 102.0 150.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 156.0 149.0 L 190.0 149.0 L 190.0 149.0 L 156.0 149.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 133.9 126.8 L 176.4 109.3 L 172.0 148.4 L 182.9 149.6" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 173.5 57.8 L 201.8 47.9 L 196.0 76.8" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="girdle" d="M 173.5 57.8 L 167.6 57.7 L 167.6 59.6" stroke="#0d9488" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

+2 -2
View File
@@ -1,8 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Abductor</title>
<path d="M 130.0 145.6 L 130.0 165.2 L 190.0 165.2 L 190.0 145.6 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
<rect x="152" y="56" width="16" height="72" rx="5" fill="#c5cad4"/>
<rect x="130" y="126" width="60" height="10" rx="3" fill="#c5cad4"/>
<path d="M 160.0 64.0 L 160.0 120.0 L 160.0 120.0 L 160.0 64.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="16" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 135.0 131.0 L 185.0 131.0 L 185.0 131.0 L 135.0 131.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 160.0 136.0 L 160.0 150.0" stroke="#c5cad4" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="girdle" d="M 171.0 32.3 L 160.0 32.3 L 149.0 32.3" stroke="#3a3f4b" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="pelvisBar" d="M 167.0 116.0 L 160.0 116.0 L 153.0 116.0" stroke="#3a3f4b" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

+2 -2
View File
@@ -1,8 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Abductor</title>
<path d="M 130.0 145.6 L 130.0 165.2 L 190.0 165.2 L 190.0 145.6 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
<rect x="152" y="56" width="16" height="72" rx="5" fill="#c5cad4"/>
<rect x="130" y="126" width="60" height="10" rx="3" fill="#c5cad4"/>
<path d="M 160.0 64.0 L 160.0 120.0 L 160.0 120.0 L 160.0 64.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="16" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 135.0 131.0 L 185.0 131.0 L 185.0 131.0 L 135.0 131.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 160.0 136.0 L 160.0 150.0" stroke="#c5cad4" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="girdle" d="M 171.0 32.3 L 160.0 32.3 L 149.0 32.3" stroke="#3a3f4b" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="pelvisBar" d="M 167.0 116.0 L 160.0 116.0 L 153.0 116.0" stroke="#3a3f4b" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

+2 -2
View File
@@ -5,8 +5,8 @@
"working": ["leg_r", "leg_l"],
"props": [
{"type": "scene", "shapes": [
{"kind": "rect", "x": 152, "y": 56, "w": 16, "h": 72, "r": 5},
{"kind": "rect", "x": 130, "y": 126, "w": 60, "h": 10, "r": 3},
{"kind": "line", "pts": [[160, 64], [160, 120]], "w": 16, "z": -14, "depth": 6},
{"kind": "line", "pts": [[135, 131], [185, 131]], "w": 10, "depth": 12},
{"kind": "line", "pts": [[160, 136], [160, 150]], "w": 6}
]},
{"type": "pad", "at": ["knee_r"], "angle": 90, "halfLen": 13, "w": 6},
Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 KiB

After

Width:  |  Height:  |  Size: 176 KiB

+2 -2
View File
@@ -1,8 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Abductor</title>
<path d="M 130.0 145.6 L 130.0 165.2 L 190.0 165.2 L 190.0 145.6 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
<rect x="152" y="56" width="16" height="72" rx="5" fill="#c5cad4"/>
<rect x="130" y="126" width="60" height="10" rx="3" fill="#c5cad4"/>
<path d="M 160.0 64.0 L 160.0 120.0 L 160.0 120.0 L 160.0 64.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="16" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 135.0 131.0 L 185.0 131.0 L 185.0 131.0 L 135.0 131.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 160.0 136.0 L 160.0 150.0" stroke="#c5cad4" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="girdle" d="M 171.0 32.3 L 160.0 32.3 L 149.0 32.3" stroke="#3a3f4b" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="pelvisBar" d="M 167.0 116.0 L 160.0 116.0 L 153.0 116.0" stroke="#3a3f4b" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

+2 -2
View File
@@ -1,8 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Adductor</title>
<path d="M 130.0 145.6 L 130.0 165.2 L 190.0 165.2 L 190.0 145.6 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
<rect x="152" y="56" width="16" height="72" rx="5" fill="#c5cad4"/>
<rect x="130" y="126" width="60" height="10" rx="3" fill="#c5cad4"/>
<path d="M 160.0 64.0 L 160.0 120.0 L 160.0 120.0 L 160.0 64.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="16" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 135.0 131.0 L 185.0 131.0 L 185.0 131.0 L 135.0 131.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 160.0 136.0 L 160.0 150.0" stroke="#c5cad4" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="girdle" d="M 171.0 32.3 L 160.0 32.3 L 149.0 32.3" stroke="#3a3f4b" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="pelvisBar" d="M 167.0 116.0 L 160.0 116.0 L 153.0 116.0" stroke="#3a3f4b" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

+2 -2
View File
@@ -1,8 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Adductor</title>
<path d="M 130.0 145.6 L 130.0 165.2 L 190.0 165.2 L 190.0 145.6 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
<rect x="152" y="56" width="16" height="72" rx="5" fill="#c5cad4"/>
<rect x="130" y="126" width="60" height="10" rx="3" fill="#c5cad4"/>
<path d="M 160.0 64.0 L 160.0 120.0 L 160.0 120.0 L 160.0 64.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="16" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 135.0 131.0 L 185.0 131.0 L 185.0 131.0 L 135.0 131.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 160.0 136.0 L 160.0 150.0" stroke="#c5cad4" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="girdle" d="M 171.0 32.3 L 160.0 32.3 L 149.0 32.3" stroke="#3a3f4b" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="pelvisBar" d="M 167.0 116.0 L 160.0 116.0 L 153.0 116.0" stroke="#3a3f4b" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

+2 -2
View File
@@ -5,8 +5,8 @@
"working": ["leg_r", "leg_l"],
"props": [
{"type": "scene", "shapes": [
{"kind": "rect", "x": 152, "y": 56, "w": 16, "h": 72, "r": 5},
{"kind": "rect", "x": 130, "y": 126, "w": 60, "h": 10, "r": 3},
{"kind": "line", "pts": [[160, 64], [160, 120]], "w": 16, "z": -14, "depth": 6},
{"kind": "line", "pts": [[135, 131], [185, 131]], "w": 10, "depth": 12},
{"kind": "line", "pts": [[160, 136], [160, 150]], "w": 6}
]},
{"type": "pad", "at": ["knee_r"], "angle": 90, "halfLen": 13, "w": 6},
Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 KiB

After

Width:  |  Height:  |  Size: 176 KiB

+2 -2
View File
@@ -1,8 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Adductor</title>
<path d="M 130.0 145.6 L 130.0 165.2 L 190.0 165.2 L 190.0 145.6 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
<rect x="152" y="56" width="16" height="72" rx="5" fill="#c5cad4"/>
<rect x="130" y="126" width="60" height="10" rx="3" fill="#c5cad4"/>
<path d="M 160.0 64.0 L 160.0 120.0 L 160.0 120.0 L 160.0 64.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="16" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 135.0 131.0 L 185.0 131.0 L 185.0 131.0 L 135.0 131.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 160.0 136.0 L 160.0 150.0" stroke="#c5cad4" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="girdle" d="M 171.0 32.3 L 160.0 32.3 L 149.0 32.3" stroke="#3a3f4b" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="pelvisBar" d="M 167.0 116.0 L 160.0 116.0 L 153.0 116.0" stroke="#3a3f4b" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

+2 -2
View File
@@ -1,8 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Arm Curl</title>
<path d="M 115.2 160.7 L 203.0 160.7 L 203.0 150.3 L 115.2 150.3 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
<path d="M 116.0 134.0 L 150.0 134.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 136.0 52.0 L 164.0 70.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 116.0 134.0 L 150.0 134.0 L 150.0 134.0 L 116.0 134.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 136.0 52.0 L 164.0 70.0 L 164.0 70.0 L 136.0 52.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 164.0 70.0 L 164.0 128.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 135.9 124.8 L 179.4 110.0 L 180.0 149.4 L 191.0 149.2" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 140.3 40.5 L 164.9 57.4 L 180.8 82.5" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

+2 -2
View File
@@ -1,8 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Arm Curl</title>
<path d="M 115.2 160.7 L 203.0 160.7 L 203.0 150.3 L 115.2 150.3 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
<path d="M 116.0 134.0 L 150.0 134.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 136.0 52.0 L 164.0 70.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 116.0 134.0 L 150.0 134.0 L 150.0 134.0 L 116.0 134.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 136.0 52.0 L 164.0 70.0 L 164.0 70.0 L 136.0 52.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 164.0 70.0 L 164.0 128.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 135.9 124.8 L 179.4 110.0 L 180.0 149.4 L 191.0 149.2" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 140.3 40.5 L 164.9 57.4 L 184.2 34.8" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

+2 -2
View File
@@ -4,8 +4,8 @@
"working": ["arm_r", "arm_l"],
"props": [
{"type": "scene", "shapes": [
{"kind": "line", "pts": [[116, 134], [150, 134]], "w": 8},
{"kind": "line", "pts": [[136, 52], [164, 70]], "w": 8},
{"kind": "line", "pts": [[116, 134], [150, 134]], "w": 8, "depth": 8},
{"kind": "line", "pts": [[136, 52], [164, 70]], "w": 8, "depth": 8},
{"kind": "line", "pts": [[164, 70], [164, 128]], "w": 5}
]},
{"type": "bar", "at": ["hand_r", "hand_l"], "angle": 90, "halfLen": 6}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 135 KiB

After

Width:  |  Height:  |  Size: 145 KiB

+2 -2
View File
@@ -1,8 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Arm Curl</title>
<path d="M 115.2 160.7 L 203.0 160.7 L 203.0 150.3 L 115.2 150.3 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
<path d="M 116.0 134.0 L 150.0 134.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 136.0 52.0 L 164.0 70.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 116.0 134.0 L 150.0 134.0 L 150.0 134.0 L 116.0 134.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 136.0 52.0 L 164.0 70.0 L 164.0 70.0 L 136.0 52.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 164.0 70.0 L 164.0 128.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 135.9 124.8 L 179.4 110.0 L 180.0 149.4 L 191.0 149.2" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 140.3 40.5 L 164.9 57.4 L 184.2 34.8" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

+3 -3
View File
@@ -1,10 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Calfs</title>
<path d="M 109.6 160.6 L 238.3 160.6 L 238.3 150.2 L 109.6 150.2 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
<path d="M 124.0 126.0 L 158.0 124.0" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 124.0 126.0 L 158.0 124.0 L 158.0 124.0 L 124.0 126.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 138.0 128.0 L 138.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 120.0 150.0 L 156.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<rect x="198" y="147" width="34" height="6" rx="1" fill="#c5cad4"/>
<path d="M 120.0 150.0 L 156.0 150.0 L 156.0 150.0 L 120.0 150.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 201.0 150.0 L 229.0 150.0 L 229.0 150.0 L 201.0 150.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 183.0 62.0 L 183.0 122.0" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 164.0 76.0 L 183.0 76.0" stroke="#6b7180" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 145.9 118.8 L 191.9 116.9 L 216.0 148.3 L 223.2 140.1" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

+3 -3
View File
@@ -1,10 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Calfs</title>
<path d="M 109.6 160.6 L 238.3 160.6 L 238.3 150.2 L 109.6 150.2 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
<path d="M 124.0 126.0 L 158.0 124.0" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 124.0 126.0 L 158.0 124.0 L 158.0 124.0 L 124.0 126.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 138.0 128.0 L 138.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 120.0 150.0 L 156.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<rect x="198" y="147" width="34" height="6" rx="1" fill="#c5cad4"/>
<path d="M 120.0 150.0 L 156.0 150.0 L 156.0 150.0 L 120.0 150.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 201.0 150.0 L 229.0 150.0 L 229.0 150.0 L 201.0 150.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 183.0 62.0 L 183.0 122.0" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 164.0 76.0 L 183.0 76.0" stroke="#6b7180" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 145.9 118.8 L 190.8 108.9 L 216.0 139.5 L 226.3 135.6" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

+3 -3
View File
@@ -4,10 +4,10 @@
"working": ["leg_r", "leg_l"],
"props": [
{"type": "scene", "shapes": [
{"kind": "line", "pts": [[124, 126], [158, 124]], "w": 9},
{"kind": "line", "pts": [[124, 126], [158, 124]], "w": 9, "depth": 8},
{"kind": "line", "pts": [[138, 128], [138, 150]], "w": 5},
{"kind": "line", "pts": [[120, 150], [156, 150]], "w": 5},
{"kind": "rect", "x": 198, "y": 147, "w": 34, "h": 6, "r": 1},
{"kind": "line", "pts": [[120, 150], [156, 150]], "w": 5, "depth": 5},
{"kind": "line", "pts": [[201, 150], [229, 150]], "w": 6, "depth": 7},
{"kind": "line", "pts": [[183, 62], [183, 122]], "w": 4},
{"kind": "line", "pts": [[164, 76], [183, 76]], "w": 3, "color": "prop"}
]},
Binary file not shown.

Before

Width:  |  Height:  |  Size: 146 KiB

After

Width:  |  Height:  |  Size: 154 KiB

+3 -3
View File
@@ -1,10 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Calfs</title>
<path d="M 109.6 160.6 L 238.3 160.6 L 238.3 150.2 L 109.6 150.2 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
<path d="M 124.0 126.0 L 158.0 124.0" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 124.0 126.0 L 158.0 124.0 L 158.0 124.0 L 124.0 126.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 138.0 128.0 L 138.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 120.0 150.0 L 156.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<rect x="198" y="147" width="34" height="6" rx="1" fill="#c5cad4"/>
<path d="M 120.0 150.0 L 156.0 150.0 L 156.0 150.0 L 120.0 150.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 201.0 150.0 L 229.0 150.0 L 229.0 150.0 L 201.0 150.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 183.0 62.0 L 183.0 122.0" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 164.0 76.0 L 183.0 76.0" stroke="#6b7180" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 145.9 118.8 L 190.8 108.9 L 216.0 139.5 L 226.3 135.6" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

@@ -1,10 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Chest Press</title>
<path d="M 78.4 160.7 L 184.9 160.7 L 184.9 150.3 L 78.4 150.3 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
<path d="M 114.0 132.0 L 86.0 40.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 106.0 133.0 L 146.0 133.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 114.0 132.0 L 86.0 40.0 L 86.0 40.0 L 114.0 132.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 106.0 133.0 L 146.0 133.0 L 146.0 133.0 L 106.0 133.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 122.0 135.0 L 122.0 151.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 108.0 151.0 L 138.0 151.0" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 108.0 151.0 L 138.0 151.0 L 138.0 151.0 L 108.0 151.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 121.9 122.8 L 166.4 111.2 L 162.0 150.4 L 172.9 151.5" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 104.2 40.2 L 99.0 69.3 L 122.0 50.3" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="girdle" d="M 104.2 40.2 L 98.3 40.2 L 98.3 42.1" stroke="#3a3f4b" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

@@ -1,10 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Chest Press</title>
<path d="M 78.4 160.7 L 184.9 160.7 L 184.9 150.3 L 78.4 150.3 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
<path d="M 114.0 132.0 L 86.0 40.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 106.0 133.0 L 146.0 133.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 114.0 132.0 L 86.0 40.0 L 86.0 40.0 L 114.0 132.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 106.0 133.0 L 146.0 133.0 L 146.0 133.0 L 106.0 133.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 122.0 135.0 L 122.0 151.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 108.0 151.0 L 138.0 151.0" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 108.0 151.0 L 138.0 151.0 L 138.0 151.0 L 108.0 151.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 121.9 122.8 L 166.4 111.2 L 162.0 150.4 L 172.9 151.5" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 104.2 40.2 L 134.0 43.3 L 163.2 50.5" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="girdle" d="M 104.2 40.2 L 98.3 40.2 L 98.3 42.1" stroke="#3a3f4b" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

+3 -3
View File
@@ -4,10 +4,10 @@
"working": ["arm_r", "arm_l"],
"props": [
{"type": "scene", "shapes": [
{"kind": "line", "pts": [[114, 132], [86, 40]], "w": 8},
{"kind": "line", "pts": [[106, 133], [146, 133]], "w": 8},
{"kind": "line", "pts": [[114, 132], [86, 40]], "w": 8, "depth": 8},
{"kind": "line", "pts": [[106, 133], [146, 133]], "w": 8, "depth": 8},
{"kind": "line", "pts": [[122, 135], [122, 151]], "w": 5},
{"kind": "line", "pts": [[108, 151], [138, 151]], "w": 4}
{"kind": "line", "pts": [[108, 151], [138, 151]], "w": 4, "depth": 5}
]},
{"type": "bar", "at": ["hand_r", "hand_l"], "angle": 90, "halfLen": 8}
],
Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 KiB

After

Width:  |  Height:  |  Size: 138 KiB

+3 -3
View File
@@ -1,10 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Chest Press</title>
<path d="M 78.4 160.7 L 184.9 160.7 L 184.9 150.3 L 78.4 150.3 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
<path d="M 114.0 132.0 L 86.0 40.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 106.0 133.0 L 146.0 133.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 114.0 132.0 L 86.0 40.0 L 86.0 40.0 L 114.0 132.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 106.0 133.0 L 146.0 133.0 L 146.0 133.0 L 106.0 133.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 122.0 135.0 L 122.0 151.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 108.0 151.0 L 138.0 151.0" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 108.0 151.0 L 138.0 151.0 L 138.0 151.0 L 108.0 151.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 121.9 122.8 L 166.4 111.2 L 162.0 150.4 L 172.9 151.5" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 104.2 40.2 L 134.0 43.3 L 163.2 50.5" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="girdle" d="M 104.2 40.2 L 98.3 40.2 L 98.3 42.1" stroke="#3a3f4b" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

@@ -1,7 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Lat Pull Down</title>
<path d="M 127.5 160.7 L 220.0 160.7 L 220.0 150.3 L 127.5 150.3 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
<path d="M 126.0 130.0 L 162.0 130.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 126.0 130.0 L 162.0 130.0 L 162.0 130.0 L 126.0 130.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 144.0 132.0 L 144.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="272.0" cy="19.0" r="4" fill="#6b7180"/>
<path d="M 272.0 19.0 L 196.0 3.8" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

@@ -1,7 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Lat Pull Down</title>
<path d="M 127.5 160.7 L 220.0 160.7 L 220.0 150.3 L 127.5 150.3 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
<path d="M 126.0 130.0 L 162.0 130.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 126.0 130.0 L 162.0 130.0 L 162.0 130.0 L 126.0 130.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 144.0 132.0 L 144.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="272.0" cy="19.0" r="4" fill="#6b7180"/>
<path d="M 272.0 19.0 L 196.0 37.3" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

+1 -1
View File
@@ -4,7 +4,7 @@
"working": ["arm_r", "arm_l"],
"props": [
{"type": "scene", "shapes": [
{"kind": "line", "pts": [[126, 130], [162, 130]], "w": 8},
{"kind": "line", "pts": [[126, 130], [162, 130]], "w": 8, "depth": 8},
{"kind": "line", "pts": [[144, 132], [144, 150]], "w": 5},
{"kind": "circle", "c": [272, 19], "r": 4, "fill": true, "color": "prop"}
]},
Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 KiB

After

Width:  |  Height:  |  Size: 161 KiB

+1 -1
View File
@@ -1,7 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Lat Pull Down</title>
<path d="M 127.5 160.7 L 220.0 160.7 L 220.0 150.3 L 127.5 150.3 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
<path d="M 126.0 130.0 L 162.0 130.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 126.0 130.0 L 162.0 130.0 L 162.0 130.0 L 126.0 130.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 144.0 132.0 L 144.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="272.0" cy="19.0" r="4" fill="#6b7180"/>
<path d="M 272.0 19.0 L 196.0 37.3" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

+4 -4
View File
@@ -1,12 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Leg Curl</title>
<path d="M 75.2 160.6 L 221.1 160.6 L 221.1 150.2 L 75.2 150.2 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
<path d="M 110.0 122.0 L 86.0 48.0" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 104.0 124.0 L 166.0 120.0" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 130.0 105.0 L 158.0 106.0" stroke="#c5cad4" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 110.0 122.0 L 86.0 48.0 L 86.0 48.0 L 110.0 122.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 104.0 124.0 L 166.0 120.0 L 166.0 120.0 L 104.0 124.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 130.0 105.0 L 158.0 106.0 L 158.0 106.0 L 130.0 105.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 108.0 126.0 L 108.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 160.0 124.0 L 160.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 104.0 150.0 L 166.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 104.0 150.0 L 166.0 150.0 L 166.0 150.0 L 104.0 150.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="114.0" cy="90.0" r="3.5" fill="#6b7180"/>
<path id="leg_l" d="M 123.9 116.8 L 169.2 108.9 L 209.1 106.1 L 208.3 95.3" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 97.6 36.5 L 105.4 65.0 L 120.4 90.6" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

+4 -4
View File
@@ -1,12 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Leg Curl</title>
<path d="M 75.2 160.6 L 221.1 160.6 L 221.1 150.2 L 75.2 150.2 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
<path d="M 110.0 122.0 L 86.0 48.0" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 104.0 124.0 L 166.0 120.0" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 130.0 105.0 L 158.0 106.0" stroke="#c5cad4" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 110.0 122.0 L 86.0 48.0 L 86.0 48.0 L 110.0 122.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 104.0 124.0 L 166.0 120.0 L 166.0 120.0 L 104.0 124.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 130.0 105.0 L 158.0 106.0 L 158.0 106.0 L 130.0 105.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 108.0 126.0 L 108.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 160.0 124.0 L 160.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 104.0 150.0 L 166.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 104.0 150.0 L 166.0 150.0 L 166.0 150.0 L 104.0 150.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="114.0" cy="90.0" r="3.5" fill="#6b7180"/>
<path id="leg_l" d="M 123.9 116.8 L 169.2 108.9 L 165.7 148.1 L 176.7 149.1" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 97.6 36.5 L 105.4 65.0 L 120.4 90.6" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

+4 -4
View File
@@ -4,12 +4,12 @@
"working": ["leg_r", "leg_l"],
"props": [
{"type": "scene", "shapes": [
{"kind": "line", "pts": [[110, 122], [86, 48]], "w": 9},
{"kind": "line", "pts": [[104, 124], [166, 120]], "w": 9},
{"kind": "line", "pts": [[130, 105], [158, 106]], "w": 7},
{"kind": "line", "pts": [[110, 122], [86, 48]], "w": 9, "depth": 8},
{"kind": "line", "pts": [[104, 124], [166, 120]], "w": 9, "depth": 8},
{"kind": "line", "pts": [[130, 105], [158, 106]], "w": 7, "depth": 7},
{"kind": "line", "pts": [[108, 126], [108, 150]], "w": 5},
{"kind": "line", "pts": [[160, 124], [160, 150]], "w": 5},
{"kind": "line", "pts": [[104, 150], [166, 150]], "w": 5},
{"kind": "line", "pts": [[104, 150], [166, 150]], "w": 5, "depth": 5},
{"kind": "circle", "c": [114, 90], "r": 3.5, "fill": true, "color": "prop"}
]},
{"type": "roller", "at": ["foot_r", "foot_l"], "side": -1, "r": 5, "back": 3}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 172 KiB

After

Width:  |  Height:  |  Size: 176 KiB

+4 -4
View File
@@ -1,12 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Leg Curl</title>
<path d="M 75.2 160.6 L 221.1 160.6 L 221.1 150.2 L 75.2 150.2 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
<path d="M 110.0 122.0 L 86.0 48.0" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 104.0 124.0 L 166.0 120.0" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 130.0 105.0 L 158.0 106.0" stroke="#c5cad4" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 110.0 122.0 L 86.0 48.0 L 86.0 48.0 L 110.0 122.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 104.0 124.0 L 166.0 120.0 L 166.0 120.0 L 104.0 124.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 130.0 105.0 L 158.0 106.0 L 158.0 106.0 L 130.0 105.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 108.0 126.0 L 108.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 160.0 124.0 L 160.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 104.0 150.0 L 166.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 104.0 150.0 L 166.0 150.0 L 166.0 150.0 L 104.0 150.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="114.0" cy="90.0" r="3.5" fill="#6b7180"/>
<path id="leg_l" d="M 123.9 116.8 L 169.2 108.9 L 165.7 148.1 L 176.7 149.1" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 97.6 36.5 L 105.4 65.0 L 120.4 90.6" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

@@ -1,11 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Leg Extension</title>
<path d="M 75.2 160.6 L 221.1 160.6 L 221.1 150.2 L 75.2 150.2 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
<path d="M 110.0 122.0 L 86.0 48.0" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 104.0 124.0 L 166.0 120.0" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 110.0 122.0 L 86.0 48.0 L 86.0 48.0 L 110.0 122.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 104.0 124.0 L 166.0 120.0 L 166.0 120.0 L 104.0 124.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 108.0 126.0 L 108.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 160.0 124.0 L 160.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 104.0 150.0 L 166.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 104.0 150.0 L 166.0 150.0 L 166.0 150.0 L 104.0 150.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="114.0" cy="90.0" r="3.5" fill="#6b7180"/>
<path id="leg_l" d="M 123.9 116.8 L 169.2 108.9 L 174.1 148.0 L 185.0 146.7" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 97.6 36.5 L 105.4 65.0 L 120.4 90.6" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

@@ -1,11 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Leg Extension</title>
<path d="M 75.2 160.6 L 221.1 160.6 L 221.1 150.2 L 75.2 150.2 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
<path d="M 110.0 122.0 L 86.0 48.0" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 104.0 124.0 L 166.0 120.0" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 110.0 122.0 L 86.0 48.0 L 86.0 48.0 L 110.0 122.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 104.0 124.0 L 166.0 120.0 L 166.0 120.0 L 104.0 124.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 108.0 126.0 L 108.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 160.0 124.0 L 160.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 104.0 150.0 L 166.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 104.0 150.0 L 166.0 150.0 L 166.0 150.0 L 104.0 150.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="114.0" cy="90.0" r="3.5" fill="#6b7180"/>
<path id="leg_l" d="M 123.9 116.8 L 169.2 108.9 L 209.1 106.1 L 208.3 95.3" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 97.6 36.5 L 105.4 65.0 L 120.4 90.6" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

+3 -3
View File
@@ -4,11 +4,11 @@
"working": ["leg_r", "leg_l"],
"props": [
{"type": "scene", "shapes": [
{"kind": "line", "pts": [[110, 122], [86, 48]], "w": 9},
{"kind": "line", "pts": [[104, 124], [166, 120]], "w": 9},
{"kind": "line", "pts": [[110, 122], [86, 48]], "w": 9, "depth": 8},
{"kind": "line", "pts": [[104, 124], [166, 120]], "w": 9, "depth": 8},
{"kind": "line", "pts": [[108, 126], [108, 150]], "w": 5},
{"kind": "line", "pts": [[160, 124], [160, 150]], "w": 5},
{"kind": "line", "pts": [[104, 150], [166, 150]], "w": 5},
{"kind": "line", "pts": [[104, 150], [166, 150]], "w": 5, "depth": 5},
{"kind": "circle", "c": [114, 90], "r": 3.5, "fill": true, "color": "prop"}
]},
{"type": "roller", "at": ["foot_r", "foot_l"], "side": 1, "r": 5, "back": 7}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 KiB

After

Width:  |  Height:  |  Size: 142 KiB

+3 -3
View File
@@ -1,11 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Leg Extension</title>
<path d="M 75.2 160.6 L 221.1 160.6 L 221.1 150.2 L 75.2 150.2 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
<path d="M 110.0 122.0 L 86.0 48.0" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 104.0 124.0 L 166.0 120.0" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 110.0 122.0 L 86.0 48.0 L 86.0 48.0 L 110.0 122.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 104.0 124.0 L 166.0 120.0 L 166.0 120.0 L 104.0 124.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 108.0 126.0 L 108.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 160.0 124.0 L 160.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 104.0 150.0 L 166.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 104.0 150.0 L 166.0 150.0 L 166.0 150.0 L 104.0 150.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="114.0" cy="90.0" r="3.5" fill="#6b7180"/>
<path id="leg_l" d="M 123.9 116.8 L 169.2 108.9 L 209.1 106.1 L 208.3 95.3" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 97.6 36.5 L 105.4 65.0 L 120.4 90.6" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

@@ -1,10 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Leg Press</title>
<path d="M 88.6 160.6 L 238.5 160.6 L 238.5 150.2 L 88.6 150.2 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
<path d="M 134.0 123.0 L 96.0 36.0" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 126.0 127.0 L 156.0 122.0" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 134.0 123.0 L 96.0 36.0 L 96.0 36.0 L 134.0 123.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 126.0 127.0 L 156.0 122.0 L 156.0 122.0 L 126.0 127.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 138.0 130.0 L 138.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 118.0 150.0 L 158.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 118.0 150.0 L 158.0 150.0 L 158.0 150.0 L 118.0 150.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 142.0 90.0 L 142.0 78.0" stroke="#c5cad4" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="142.0" cy="77.0" r="3.5" fill="#6b7180"/>
<path id="leg_l" d="M 145.9 116.8 L 161.7 74.2 L 189.0 103.0 L 197.0 95.6" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

@@ -1,10 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Leg Press</title>
<path d="M 88.6 160.6 L 238.5 160.6 L 238.5 150.2 L 88.6 150.2 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
<path d="M 134.0 123.0 L 96.0 36.0" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 126.0 127.0 L 156.0 122.0" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 134.0 123.0 L 96.0 36.0 L 96.0 36.0 L 134.0 123.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 126.0 127.0 L 156.0 122.0 L 156.0 122.0 L 126.0 127.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 138.0 130.0 L 138.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 118.0 150.0 L 158.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 118.0 150.0 L 158.0 150.0 L 158.0 150.0 L 118.0 150.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 142.0 90.0 L 142.0 78.0" stroke="#c5cad4" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="142.0" cy="77.0" r="3.5" fill="#6b7180"/>
<path id="leg_l" d="M 145.9 116.8 L 185.1 93.0 L 224.0 102.0 L 226.5 91.5" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

+3 -3
View File
@@ -4,10 +4,10 @@
"working": ["leg_r", "leg_l"],
"props": [
{"type": "scene", "shapes": [
{"kind": "line", "pts": [[134, 123], [96, 36]], "w": 9},
{"kind": "line", "pts": [[126, 127], [156, 122]], "w": 9},
{"kind": "line", "pts": [[134, 123], [96, 36]], "w": 9, "depth": 8},
{"kind": "line", "pts": [[126, 127], [156, 122]], "w": 9, "depth": 8},
{"kind": "line", "pts": [[138, 130], [138, 150]], "w": 5},
{"kind": "line", "pts": [[118, 150], [158, 150]], "w": 5},
{"kind": "line", "pts": [[118, 150], [158, 150]], "w": 5, "depth": 5},
{"kind": "line", "pts": [[142, 90], [142, 78]], "w": 3},
{"kind": "circle", "c": [142, 77], "r": 3.5, "fill": true, "color": "prop"}
]},
Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

After

Width:  |  Height:  |  Size: 180 KiB

+3 -3
View File
@@ -1,10 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Leg Press</title>
<path d="M 88.6 160.6 L 238.5 160.6 L 238.5 150.2 L 88.6 150.2 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
<path d="M 134.0 123.0 L 96.0 36.0" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 126.0 127.0 L 156.0 122.0" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 134.0 123.0 L 96.0 36.0 L 96.0 36.0 L 134.0 123.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 126.0 127.0 L 156.0 122.0 L 156.0 122.0 L 126.0 127.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 138.0 130.0 L 138.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 118.0 150.0 L 158.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 118.0 150.0 L 158.0 150.0 L 158.0 150.0 L 118.0 150.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 142.0 90.0 L 142.0 78.0" stroke="#c5cad4" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="142.0" cy="77.0" r="3.5" fill="#6b7180"/>
<path id="leg_l" d="M 145.9 116.8 L 185.1 93.0 L 224.0 102.0 L 226.5 91.5" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

+2 -2
View File
@@ -1,9 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Rotary</title>
<path d="M 130.0 144.5 L 130.0 166.5 L 190.0 166.5 L 190.0 144.5 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
<path d="M 138.0 130.0 L 182.0 130.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 138.0 130.0 L 182.0 130.0 L 182.0 130.0 L 138.0 130.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 160.0 132.0 L 160.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 144.0 150.0 L 176.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 144.0 150.0 L 176.0 150.0 L 176.0 150.0 L 144.0 150.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="girdle" d="M 167.1 36.8 L 160.0 38.3 L 152.9 39.8" stroke="#0d9488" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="pelvisBar" d="M 167.0 122.0 L 160.0 122.0 L 153.0 122.0" stroke="#0d9488" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 160.0 122.0 Q 160.0 79.2 160.0 38.3" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

+2 -2
View File
@@ -1,9 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Rotary</title>
<path d="M 130.0 144.5 L 130.0 166.5 L 190.0 166.5 L 190.0 144.5 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
<path d="M 138.0 130.0 L 182.0 130.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 138.0 130.0 L 182.0 130.0 L 182.0 130.0 L 138.0 130.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 160.0 132.0 L 160.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 144.0 150.0 L 176.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 144.0 150.0 L 176.0 150.0 L 176.0 150.0 L 144.0 150.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="girdle" d="M 167.1 39.8 L 160.0 38.3 L 152.9 36.8" stroke="#0d9488" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="pelvisBar" d="M 167.0 122.0 L 160.0 122.0 L 153.0 122.0" stroke="#0d9488" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 160.0 122.0 Q 160.0 79.2 160.0 38.3" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

+2 -2
View File
@@ -5,9 +5,9 @@
"working": ["spine"],
"props": [
{"type": "scene", "shapes": [
{"kind": "line", "pts": [[138, 130], [182, 130]], "w": 8},
{"kind": "line", "pts": [[138, 130], [182, 130]], "w": 8, "depth": 10},
{"kind": "line", "pts": [[160, 132], [160, 150]], "w": 5},
{"kind": "line", "pts": [[144, 150], [176, 150]], "w": 5}
{"kind": "line", "pts": [[144, 150], [176, 150]], "w": 5, "depth": 5}
]},
{"type": "bar", "at": ["hand_r", "hand_l"], "angle": 90, "halfLen": 14}
],
Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

After

Width:  |  Height:  |  Size: 149 KiB

+2 -2
View File
@@ -1,9 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Rotary</title>
<path d="M 130.0 144.5 L 130.0 166.5 L 190.0 166.5 L 190.0 144.5 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
<path d="M 138.0 130.0 L 182.0 130.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 138.0 130.0 L 182.0 130.0 L 182.0 130.0 L 138.0 130.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 160.0 132.0 L 160.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 144.0 150.0 L 176.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 144.0 150.0 L 176.0 150.0 L 176.0 150.0 L 144.0 150.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="girdle" d="M 167.1 36.8 L 160.0 38.3 L 152.9 39.8" stroke="#0d9488" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="pelvisBar" d="M 167.0 122.0 L 160.0 122.0 L 153.0 122.0" stroke="#0d9488" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 160.0 122.0 Q 160.0 79.2 160.0 38.3" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

+34 -17
View File
@@ -132,8 +132,8 @@ 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": "line", "pts": [[134, 123], [96, 36]], "w": 9, "depth": 8},
{"kind": "line", "pts": [[160, 64], [160, 120]], "w": 16, "z": -14},
{"kind": "circle", "c": [142, 77], "r": 3.5, "fill": true, "color": "prop"}
]},
{"type": "cable", "from": [190, 8], "to": ["hand_r", "hand_l"]},
@@ -143,23 +143,37 @@ scale drawings of the machine.
]
```
- **`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). Scene shapes
are view-locked billboards — they don't rotate with `--orbit`.
- **`cable`** — a thin line from a fixed anchor `from` to a moving joint `to`;
the machine's pulley line.
Props are authored in the authored view but have **world-space 3D form**:
under an orbiting camera the whole equipment layer rotates about the
world-vertical axis through the root anchor, exactly like the figure and the
mat. Everything resolves in the *authored* view — scene points, cable
anchors, bar angles, pad perpendiculars, roller offsets — and the resolved
constructs are then rotated; joint-attached items keep following the rotated
figure's hands and feet, so equipment stays welded to the body. At the
authored yaw nothing moves, so the authored look is exact.
- **`scene`** — static shapes: `line` (polyline, stroke width `w`) and
`circle` (`fill: false` for an outline). Points are canvas `[x, y]` (or
`[x, y, z]` where needed); a shape-level `"z"` sets its depth plane
(positive toward the camera), and a line with `"depth"` is a **slab**
extruded that half-width through its plane, it stays a plain line edge-on
and opens into a swept quad as the camera orbits (seats, backrests,
platforms). 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` (`[x, y]` or
`[x, y, z]`) to a moving joint `to`; the machine's pulley line.
- **`bar` / `dumbbell` / `pad`** — a segment centered on the joint(s) in
`at` (a single joint, or the midpoint of a list). Joints are the extremities
(`hand_r`, `foot_l`, …) plus the mid joints (`elbow_r`, `knee_l`, …), so a
machine pad can ride a knee (`["knee_r", "knee_l"]`) or span a shin
(`["knee_r", "foot_r"]`).
`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.
`bar` lies at a fixed authored-view `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`. Under orbit the segment rotates with the scene
and foreshortens naturally. `plateR` puts filled discs on both ends
(dumbbells default to 4.5). A prop whose limb is hidden that frame simply
isn't drawn.
- **`roller`** — a machine roller pad seen end-on: a filled disc riding the
limb's lower bone near the joint in `at`, on the `side` (+1/1) of the bone
it presses — a leg extension's instep roller (`side: 1`), a leg curl's
@@ -179,6 +193,7 @@ python3 render.py --figure=female # render with another skeleton profile
python3 render.py --flip # view from the other side (camera + 180°)
python3 render.py --strict # fail on any ROM violation, listing each
python3 render.py --export # bake app resources into Workouts/Resources/ExerciseMotions
python3 render.py --fixtures # regenerate WorkoutsTests/Fixtures/figure-fixtures.json
```
`render.py` needs only Pillow (for GIFs/sheets; the SVGs have no dependency).
@@ -192,6 +207,8 @@ the `--export` copies ship: `skeleton.json` plus uniquely-named
and the exercise-library reference screen (`ExerciseInfo.swift` parses the
info pages). The in-app solver is a line-for-line port of `kinematics.py`,
held to it by `WorkoutsTests/Fixtures/figure-fixtures.json` — projected
geometry snapshots the Swift solver must reproduce; regenerate them alongside
any pipeline change. Re-run `python3 render.py --export` after editing any
motion or info page; the library stays the source of truth.
geometry snapshots (figure and prop primitives, including orbit-presentation
samples) the Swift solver must reproduce; regenerate with
`python3 render.py --fixtures` alongside any pipeline change. Re-run
`python3 render.py --export` after editing any motion or info page; the
library stays the source of truth.
@@ -1,9 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Seated Row</title>
<path d="M 118.3 160.7 L 216.0 160.7 L 216.0 150.3 L 118.3 150.3 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
<path d="M 122.0 132.0 L 156.0 132.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 159.0 46.0 L 157.0 96.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 176.0 150.0 L 206.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 122.0 132.0 L 156.0 132.0 L 156.0 132.0 L 122.0 132.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 159.0 46.0 L 157.0 96.0 L 157.0 96.0 L 159.0 46.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 176.0 150.0 L 206.0 150.0 L 206.0 150.0 L 176.0 150.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 244.0 120.0 L 202.0 63.9" stroke="#c5cad4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 145.9 122.8 L 189.4 108.1 L 192.0 147.4 L 203.0 146.7" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 155.5 38.9 L 174.0 62.2 L 204.0 63.0" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

@@ -1,9 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Seated Row</title>
<path d="M 118.3 160.7 L 216.0 160.7 L 216.0 150.3 L 118.3 150.3 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
<path d="M 122.0 132.0 L 156.0 132.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 159.0 46.0 L 157.0 96.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 176.0 150.0 L 206.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 122.0 132.0 L 156.0 132.0 L 156.0 132.0 L 122.0 132.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 159.0 46.0 L 157.0 96.0 L 157.0 96.0 L 159.0 46.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 176.0 150.0 L 206.0 150.0 L 206.0 150.0 L 176.0 150.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 244.0 120.0 L 160.0 73.8" stroke="#c5cad4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 145.9 122.8 L 189.4 108.1 L 192.0 147.4 L 203.0 146.7" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 155.5 38.9 L 134.8 60.3 L 162.0 72.8" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

+3 -3
View File
@@ -4,9 +4,9 @@
"working": ["arm_r", "arm_l"],
"props": [
{"type": "scene", "shapes": [
{"kind": "line", "pts": [[122, 132], [156, 132]], "w": 8},
{"kind": "line", "pts": [[159, 46], [157, 96]], "w": 8},
{"kind": "line", "pts": [[176, 150], [206, 150]], "w": 5}
{"kind": "line", "pts": [[122, 132], [156, 132]], "w": 8, "depth": 8},
{"kind": "line", "pts": [[159, 46], [157, 96]], "w": 8, "depth": 7},
{"kind": "line", "pts": [[176, 150], [206, 150]], "w": 5, "depth": 6}
]},
{"type": "cable", "from": [244, 120], "to": ["hand_r", "hand_l"]},
{"type": "bar", "at": ["hand_r", "hand_l"], "angle": 90, "halfLen": 7}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 185 KiB

After

Width:  |  Height:  |  Size: 181 KiB

+3 -3
View File
@@ -1,9 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Seated Row</title>
<path d="M 118.3 160.7 L 216.0 160.7 L 216.0 150.3 L 118.3 150.3 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
<path d="M 122.0 132.0 L 156.0 132.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 159.0 46.0 L 157.0 96.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 176.0 150.0 L 206.0 150.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 122.0 132.0 L 156.0 132.0 L 156.0 132.0 L 122.0 132.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 159.0 46.0 L 157.0 96.0 L 157.0 96.0 L 159.0 46.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 176.0 150.0 L 206.0 150.0 L 206.0 150.0 L 176.0 150.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 244.0 120.0 L 160.0 73.8" stroke="#c5cad4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 145.9 122.8 L 189.4 108.1 L 192.0 147.4 L 203.0 146.7" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 155.5 38.9 L 134.8 60.3 L 162.0 72.8" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

@@ -1,10 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Shoulder Press</title>
<path d="M 76.6 160.8 L 191.0 160.8 L 191.0 150.4 L 76.6 150.4 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
<path d="M 116.0 140.0 L 88.0 50.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 110.0 141.0 L 148.0 141.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 116.0 140.0 L 88.0 50.0 L 88.0 50.0 L 116.0 140.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 110.0 141.0 L 148.0 141.0 L 148.0 141.0 L 110.0 141.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 126.0 143.0 L 126.0 151.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 110.0 151.0 L 142.0 151.0" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 110.0 151.0 L 142.0 151.0 L 142.0 151.0 L 110.0 151.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 125.9 130.8 L 167.4 111.1 L 168.0 150.5 L 179.0 150.3" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 101.0 50.1 L 116.0 75.7 L 116.0 46.2" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="girdle" d="M 101.0 50.1 L 95.1 50.1 L 95.1 52.0" stroke="#3a3f4b" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

@@ -1,10 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Shoulder Press</title>
<path d="M 76.6 160.8 L 191.0 160.8 L 191.0 150.4 L 76.6 150.4 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
<path d="M 116.0 140.0 L 88.0 50.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 110.0 141.0 L 148.0 141.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 116.0 140.0 L 88.0 50.0 L 88.0 50.0 L 116.0 140.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 110.0 141.0 L 148.0 141.0 L 148.0 141.0 L 110.0 141.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 126.0 143.0 L 126.0 151.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 110.0 151.0 L 142.0 151.0" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 110.0 151.0 L 142.0 151.0 L 142.0 151.0 L 110.0 151.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 125.9 130.8 L 167.4 111.1 L 168.0 150.5 L 179.0 150.3" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 101.0 50.1 L 118.7 26.2 L 142.9 8.9" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="girdle" d="M 101.0 50.1 L 95.1 50.1 L 95.1 52.0" stroke="#3a3f4b" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

+3 -3
View File
@@ -4,10 +4,10 @@
"working": ["arm_r", "arm_l"],
"props": [
{"type": "scene", "shapes": [
{"kind": "line", "pts": [[116, 140], [88, 50]], "w": 8},
{"kind": "line", "pts": [[110, 141], [148, 141]], "w": 8},
{"kind": "line", "pts": [[116, 140], [88, 50]], "w": 8, "depth": 8},
{"kind": "line", "pts": [[110, 141], [148, 141]], "w": 8, "depth": 8},
{"kind": "line", "pts": [[126, 143], [126, 151]], "w": 5},
{"kind": "line", "pts": [[110, 151], [142, 151]], "w": 4}
{"kind": "line", "pts": [[110, 151], [142, 151]], "w": 4, "depth": 5}
]},
{"type": "bar", "at": ["hand_r", "hand_l"], "halfLen": 10}
],
Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 KiB

After

Width:  |  Height:  |  Size: 148 KiB

+3 -3
View File
@@ -1,10 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Shoulder Press</title>
<path d="M 76.6 160.8 L 191.0 160.8 L 191.0 150.4 L 76.6 150.4 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
<path d="M 116.0 140.0 L 88.0 50.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 110.0 141.0 L 148.0 141.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 116.0 140.0 L 88.0 50.0 L 88.0 50.0 L 116.0 140.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 110.0 141.0 L 148.0 141.0 L 148.0 141.0 L 110.0 141.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 126.0 143.0 L 126.0 151.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 110.0 151.0 L 142.0 151.0" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 110.0 151.0 L 142.0 151.0 L 142.0 151.0 L 110.0 151.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 125.9 130.8 L 167.4 111.1 L 168.0 150.5 L 179.0 150.3" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 101.0 50.1 L 118.7 26.2 L 142.9 8.9" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="girdle" d="M 101.0 50.1 L 95.1 50.1 L 95.1 52.0" stroke="#3a3f4b" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

@@ -1,10 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Tricep Press</title>
<path d="M 82.7 160.7 L 182.8 160.7 L 182.8 150.3 L 82.7 150.3 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
<path d="M 116.0 134.0 L 92.0 42.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 108.0 135.0 L 146.0 135.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 116.0 134.0 L 92.0 42.0 L 92.0 42.0 L 116.0 134.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 108.0 135.0 L 146.0 135.0 L 146.0 135.0 L 108.0 135.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 124.0 137.0 L 124.0 151.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 110.0 151.0 L 140.0 151.0" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 110.0 151.0 L 140.0 151.0 L 140.0 151.0 L 110.0 151.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 123.9 124.8 L 168.0 111.8 L 160.0 150.4 L 170.8 152.6" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 110.6 41.4 L 98.5 68.4 L 128.0 63.0" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="girdle" d="M 110.6 41.4 L 104.7 41.3 L 104.7 43.2" stroke="#3a3f4b" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

@@ -1,10 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Tricep Press</title>
<path d="M 82.7 160.7 L 182.8 160.7 L 182.8 150.3 L 82.7 150.3 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
<path d="M 116.0 134.0 L 92.0 42.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 108.0 135.0 L 146.0 135.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 116.0 134.0 L 92.0 42.0 L 92.0 42.0 L 116.0 134.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 108.0 135.0 L 146.0 135.0 L 146.0 135.0 L 108.0 135.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 124.0 137.0 L 124.0 151.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 110.0 151.0 L 140.0 151.0" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 110.0 151.0 L 140.0 151.0 L 140.0 151.0 L 110.0 151.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 123.9 124.8 L 168.0 111.8 L 160.0 150.4 L 170.8 152.6" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 110.6 41.4 L 113.2 70.8 L 128.0 96.5" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="girdle" d="M 110.6 41.4 L 104.7 41.3 L 104.7 43.2" stroke="#3a3f4b" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

+3 -3
View File
@@ -4,10 +4,10 @@
"working": ["arm_r", "arm_l"],
"props": [
{"type": "scene", "shapes": [
{"kind": "line", "pts": [[116, 134], [92, 42]], "w": 8},
{"kind": "line", "pts": [[108, 135], [146, 135]], "w": 8},
{"kind": "line", "pts": [[116, 134], [92, 42]], "w": 8, "depth": 8},
{"kind": "line", "pts": [[108, 135], [146, 135]], "w": 8, "depth": 8},
{"kind": "line", "pts": [[124, 137], [124, 151]], "w": 5},
{"kind": "line", "pts": [[110, 151], [140, 151]], "w": 4}
{"kind": "line", "pts": [[110, 151], [140, 151]], "w": 4, "depth": 5}
]},
{"type": "bar", "at": ["hand_r", "hand_l"], "angle": 90, "halfLen": 6}
],
Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

After

Width:  |  Height:  |  Size: 156 KiB

+3 -3
View File
@@ -1,10 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
<title>Tricep Press</title>
<path d="M 82.7 160.7 L 182.8 160.7 L 182.8 150.3 L 82.7 150.3 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
<path d="M 116.0 134.0 L 92.0 42.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 108.0 135.0 L 146.0 135.0" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 116.0 134.0 L 92.0 42.0 L 92.0 42.0 L 116.0 134.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 108.0 135.0 L 146.0 135.0 L 146.0 135.0 L 108.0 135.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 124.0 137.0 L 124.0 151.0" stroke="#c5cad4" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 110.0 151.0 L 140.0 151.0" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 110.0 151.0 L 140.0 151.0 L 140.0 151.0 L 110.0 151.0 Z" fill="#c5cad4" stroke="#c5cad4" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 123.9 124.8 L 168.0 111.8 L 160.0 150.4 L 170.8 152.6" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_l" d="M 110.6 41.4 L 113.2 70.8 L 128.0 96.5" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="girdle" d="M 110.6 41.4 L 104.7 41.3 L 104.7 43.2" stroke="#3a3f4b" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 299 KiB

After

Width:  |  Height:  |  Size: 302 KiB

+186 -50
View File
@@ -13,9 +13,11 @@ Outputs per exercise: frames/frame-N.svg, preview.gif (tweened, looping),
visual.svg (the primary frame). `--sheet` writes contact-sheet.png of every
key frame; `--demo` writes demo-sheet.png showing rig customizations (body
profiles, flipped camera, theme); `--orbit` writes orbit.gif per named
exercise (the camera sweeps 360 degrees while the motion loops). `--export`
instead bakes each motion down to the legacy planar schema consumed by the
in-app SwiftUI renderer. SVGs need no dependencies; GIFs/sheets need Pillow.
exercise (the camera sweeps 360 degrees while the motion loops — props
rotate with the figure). `--export` copies the app's bundled resources
verbatim; `--fixtures` regenerates the projected-geometry snapshots pinning
the in-app Swift solver to this pipeline. SVGs need no dependencies;
GIFs/sheets need Pillow.
"""
import copy
@@ -227,24 +229,35 @@ def timeline(norms, fps=20):
# figure in the recessive equipment gray; joint-attached items (`bar`,
# `dumbbell`, `pad`) draw over the limbs in the darker prop gray, following
# the resolved hand/foot positions frame by frame.
#
# Props have world-space 3D form: everything is authored (and resolved) in the
# authored view — scene points and cable anchors carry an optional depth `z`
# (+ toward the camera), a scene line an optional `depth` extrusion half-width
# that makes it a slab — then rotated about the world-vertical axis through
# the root anchor for the orbiting presentation, exactly like the figure and
# the mat. Positions that follow the figure (joint anchors) come from the
# *rotated* geometry; fixed points, directions, and offsets (scene shapes,
# cable anchors, bar angles, pad perpendiculars, roller offsets) resolve in
# the *authored* view and rotate — never recompute them in the rotated view.
def flip_props(props, width):
"""Mirror the props horizontally, matching the flipped camera. Joint-
attached props follow the mirrored limbs automatically; only fixed
coordinates and world angles need mirroring."""
"""Mirror the props horizontally, matching the flipped camera (which views
from the opposite side, so authored depths flip sign too). Joint-attached
props follow the mirrored limbs automatically; only fixed coordinates,
depths, and world angles need mirroring."""
def fx(p):
return [width - p[0], p[1]]
return [width - p[0], p[1], -p[2]] if len(p) > 2 else [width - p[0], p[1]]
out = []
for prop in props:
p = json.loads(json.dumps(prop))
if p["type"] == "scene":
for s in p["shapes"]:
if "z" in s:
s["z"] = -s["z"]
if s["kind"] == "line":
s["pts"] = [fx(pt) for pt in s["pts"]]
elif s["kind"] == "circle":
s["c"] = fx(s["c"])
elif s["kind"] == "rect":
s["x"] = width - s["x"] - s["w"]
elif p["type"] == "cable":
p["from"] = fx(p["from"])
elif p["type"] in ("bar", "pad") and "angle" in p:
@@ -255,6 +268,16 @@ def flip_props(props, width):
return out
def prop_rotation(pitch, yaw_offset):
"""View-space rotation carrying authored-view prop geometry to the camera
`yaw_offset` degrees past the authored yaw: a rotation about the world-
vertical axis through the root anchor, conjugated by the camera elevation.
The identity at offset 0, so the authored view stays bit-exact."""
if yaw_offset == 0:
return K.IDENTITY
return K.chain(K.rot_x(pitch), K.rot_y(-yaw_offset), K.rot_x(-pitch))
def joint_points(geo, ref):
"""Resolve a joint ref — `"hand_r"`, `"knee_l"`, or a midpoint list like
`["knee_r", "foot_r"]` — to (point, unit direction of the bone ending at
@@ -275,45 +298,90 @@ def joint_points(geo, ref):
sum(p[1] for p in pts) / len(pts)), direction)
def resolve_props(props, geo):
"""Props -> drawable primitives for one frame: (background, foreground)."""
def resolve_props(props, geo, anchor, rot=K.IDENTITY, auth_geo=None):
"""Props -> drawable primitives for one frame: (background, foreground).
`geo` is the frame's drawn (possibly orbit-rotated) geometry, `auth_geo`
the same frame at the authored camera (defaults to `geo`), `anchor` the
frame's root canvas anchor, and `rot` the `prop_rotation` between them.
Joint positions come from `geo`; everything authored — scene points,
cable anchors, bar angles, pad perpendiculars, roller offsets — resolves
against `auth_geo` and rotates through `rot`.
"""
auth = auth_geo if auth_geo is not None else geo
def place(pt, z=0.0):
"""Authored canvas point (+ depth toward the camera) -> drawn canvas."""
v = K.mvec(rot, (pt[0] - anchor[0], anchor[1] - pt[1], z))
return (anchor[0] + v[0], anchor[1] - v[1])
def swing(vec, z=0.0):
"""Authored canvas-space direction/offset -> drawn canvas (y-down)."""
v = K.mvec(rot, (vec[0], -vec[1], z))
return (v[0], -v[1])
bg, fg = [], []
for p in props or []:
t = p["type"]
if t == "scene":
for s in p["shapes"]:
bg.append(dict(s, color=s.get("color", "equipment")))
color = s.get("color", "equipment")
z = s.get("z", 0.0)
if s["kind"] == "line":
depth = s.get("depth", 0.0)
pts = [(pt[0], pt[1], z + (pt[2] if len(pt) > 2 else 0.0))
for pt in s["pts"]]
if depth:
# An extruded slab: the polyline swept through +/-depth,
# filled and outlined so it degenerates to the plain
# line whenever the sweep collapses edge-on.
quad = ([place(pt, pt[2] + depth) for pt in pts]
+ [place(pt, pt[2] - depth) for pt in reversed(pts)])
bg.append({"kind": "poly", "pts": [list(q) for q in quad],
"w": s.get("w", 4), "color": color})
else:
bg.append({"kind": "line",
"pts": [list(place(pt, pt[2])) for pt in pts],
"w": s.get("w", 4), "color": color})
elif s["kind"] == "circle":
bg.append({"kind": "circle", "c": list(place(s["c"], z)),
"r": s["r"], "fill": s.get("fill", False),
"w": s.get("w", 3), "color": color})
elif t == "cable":
end, _ = joint_points(geo, p["to"])
if end:
bg.append({"kind": "line", "pts": [list(p["from"]), list(end)],
start = place(p["from"], p["from"][2] if len(p["from"]) > 2 else 0.0)
bg.append({"kind": "line", "pts": [list(start), list(end)],
"w": p.get("w", 2), "color": "equipment"})
elif t == "roller":
# A machine roller pad seen end-on: a disc riding the limb's lower
# bone near the joint, on the `side` (+1/-1) of the bone it presses.
c, d = joint_points(geo, p["at"])
if not c:
# The offset resolves along the authored-view bone, then rotates.
c, _ = joint_points(geo, p["at"])
_, d = joint_points(auth, p["at"])
if not c or not d:
continue
r = p.get("r", 5)
back = p.get("back", 0)
side = p.get("side", 1)
px, py = d[1] * side, -d[0] * side
center = (c[0] - d[0] * back + px * (r + 3),
c[1] - d[1] * back + py * (r + 3))
fg.append({"kind": "circle", "c": list(center), "r": r,
off = swing((-d[0] * back + px * (r + 3), -d[1] * back + py * (r + 3)))
fg.append({"kind": "circle", "c": [c[0] + off[0], c[1] + off[1]], "r": r,
"fill": True, "color": "prop"})
elif t in ("bar", "dumbbell", "pad"):
c, d = joint_points(geo, p["at"])
if not c:
c, _ = joint_points(geo, p["at"])
_, d = joint_points(auth, p["at"])
if not c or not d:
continue
if t == "bar" or "angle" in p:
ux, uy = dirv(p.get("angle", 0)) # fixed world angle
axis = dirv(p.get("angle", 0)) # fixed authored-view angle
else:
ux, uy = -d[1], d[0] # perpendicular to the lower bone
axis = (-d[1], d[0]) # perpendicular to the lower bone
ux, uy = swing(axis) # foreshortens under orbit
h = p.get("halfLen", {"bar": 24, "dumbbell": 7, "pad": 8}[t])
a = (c[0] - ux * h, c[1] - uy * h)
b = (c[0] + ux * h, c[1] + uy * h)
fg.append({"kind": "line", "pts": [a, b],
fg.append({"kind": "line", "pts": [list(a), list(b)],
"w": p.get("w", {"bar": 4, "dumbbell": 3, "pad": 7}[t]),
"color": "prop"})
plate = p.get("plateR", 4.5 if t == "dumbbell" else 0)
@@ -332,6 +400,11 @@ def svg_prims(prims, colors):
d = "M " + " L ".join(f"{x:.1f} {y:.1f}" for x, y in p["pts"])
lines.append(f' <path d="{d}" stroke="{color}" stroke-width="{p.get("w", 4)}"'
f' stroke-linecap="round" stroke-linejoin="round"/>')
elif p["kind"] == "poly":
d = "M " + " L ".join(f"{x:.1f} {y:.1f}" for x, y in p["pts"]) + " Z"
lines.append(f' <path d="{d}" fill="{color}" stroke="{color}"'
f' stroke-width="{p.get("w", 4)}"'
f' stroke-linecap="round" stroke-linejoin="round"/>')
elif p["kind"] == "circle":
cx, cy = p["c"]
if p.get("fill"):
@@ -339,17 +412,17 @@ def svg_prims(prims, colors):
else:
lines.append(f' <circle cx="{cx:.1f}" cy="{cy:.1f}" r="{p["r"]}"'
f' stroke="{color}" stroke-width="{p.get("w", 3)}"/>')
elif p["kind"] == "rect":
lines.append(f' <rect x="{p["x"]}" y="{p["y"]}" width="{p["w"]}" height="{p["h"]}"'
f' rx="{p.get("r", 2)}" fill="{colors[p.get("color", "equipment")]}"/>')
return lines
def draw_prims(d, prims, colors, scale):
for p in prims:
color = colors[p["color"]]
if p["kind"] == "line":
if p["kind"] in ("line", "poly"):
pts = [(x * scale, y * scale) for x, y in p["pts"]]
if p["kind"] == "poly":
d.polygon(pts, fill=color)
pts = pts + pts[:1]
w = p.get("w", 4) * scale
d.line(pts, fill=color, width=w, joint="curve")
for x, y in (pts[0], pts[-1]):
@@ -362,10 +435,6 @@ def draw_prims(d, prims, colors, scale):
else:
d.ellipse([cx - r, cy - r, cx + r, cy + r], outline=color,
width=p.get("w", 3) * scale)
elif p["kind"] == "rect":
x, y = p["x"] * scale, p["y"] * scale
d.rounded_rectangle([x, y, x + p["w"] * scale, y + p["h"] * scale],
radius=p.get("r", 2) * scale, fill=color)
# ------------------------------------------------------------------- drawing
@@ -400,8 +469,8 @@ def quad_points(p0, ctrl, p2, n=24):
return pts
def svg_for_frame(name, geo, order, shade, working, colors, props=None):
bg, fg = resolve_props(props, geo)
def svg_for_frame(name, geo, order, shade, working, colors, prims=((), ())):
bg, fg = prims
w, h = CANVAS
parts = [f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {w} {h}" fill="none">',
f' <title>{name}</title>',
@@ -443,10 +512,10 @@ def svg_for_frame(name, geo, order, shade, working, colors, props=None):
return "\n".join(parts) + "\n"
def draw_geo(geo, order, shade, working, colors, scale=2, font=None, props=None):
def draw_geo(geo, order, shade, working, colors, scale=2, font=None, prims=((), ())):
from PIL import Image, ImageDraw
bg, fg = resolve_props(props, geo)
bg, fg = prims
w, h = CANVAS[0] * scale, CANVAS[1] * scale
img = Image.new("RGB", (w, h), "white")
d = ImageDraw.Draw(img)
@@ -545,21 +614,21 @@ def render_exercise(folder, figure="neutral", flip=False, strict=False):
return geo, order, shade
resolved = []
key_geos = []
key_cells = []
for nf in norms:
out, geo, order, shade = frame_geometry(nf, prof, cam, flip, pitch, mat)
for limb in hide:
geo.pop(limb, None)
resolved.append(out)
key_geos.append((geo, order, shade))
key_cells.append((geo, order, shade, resolve_props(props, geo, nf["root"]["pos"])))
frames_dir = folder / "frames"
frames_dir.mkdir(exist_ok=True)
for old in frames_dir.glob("frame-*.svg"):
old.unlink()
colors = PALETTES["default"]
svgs = [svg_for_frame(motion["name"], geo, order, shade, working, colors, props)
for geo, order, shade in key_geos]
svgs = [svg_for_frame(motion["name"], geo, order, shade, working, colors, prims)
for geo, order, shade, prims in key_cells]
for i, svg in enumerate(svgs, start=1):
(frames_dir / f"frame-{i}.svg").write_text(svg)
(folder / "visual.svg").write_text(svgs[motion.get("primary", 1) - 1])
@@ -569,7 +638,8 @@ def render_exercise(folder, figure="neutral", flip=False, strict=False):
imgs = []
for nf in timeline(resolved):
geo, order, shade = geometry(nf)
imgs.append(draw_geo(geo, order, shade, working, colors, font=font, props=props))
prims = resolve_props(props, geo, nf["root"]["pos"])
imgs.append(draw_geo(geo, order, shade, working, colors, font=font, prims=prims))
imgs[0].save(folder / "preview.gif", save_all=True, append_images=imgs[1:],
duration=50, loop=0)
print(f" {motion['name']}: {len(svgs)} frames, preview.gif")
@@ -578,29 +648,34 @@ def render_exercise(folder, figure="neutral", flip=False, strict=False):
def render_orbit(folder, figure="neutral"):
"""A full-turn demo: the camera sweeps 360 degrees while the motion loops.
Scene props are view-locked billboards, so orbit shines on prop-free
motions (bodyweight exercises)."""
"""An orbiting demo of what the app shows: the camera sweeps 360 degrees
while the motion loops (or swings a `camera.sweep` pendulum when the
motion authors one). Props rotate with the figure about the root anchor."""
motion = load_motion(folder)
working = set(motion.get("working", []))
hide = set(motion.get("hide", []))
norms, prof, cam, pitch, props = prepare(motion, figure)
resolved = [frame_geometry(nf, prof, cam, pitch=pitch)[0] for nf in norms]
mat = mat_bounds(norms, prof, cam, pitch)
sweep = motion.get("camera", {}).get("sweep")
font = legend_font()
colors = PALETTES["default"]
ticks = timeline(resolved)
imgs = []
for i, nf in enumerate(ticks):
yaw = cam + 360.0 * i / len(ticks)
off = (sweep * math.sin(2 * math.pi * i / len(ticks)) if sweep
else 360.0 * i / len(ticks))
# Pins are canvas targets in the AUTHORED view: resolve the pose there,
# then rotate the posed body - never re-pin in the rotated view.
posed, _, _, _ = frame_geometry(nf, prof, cam, pitch=pitch)
# then rotate the posed body - never re-pin in the rotated view. The
# authored-view geometry also feeds the props' directions and offsets.
posed, auth_geo, _, _ = frame_geometry(nf, prof, cam, pitch=pitch)
posed["pins"] = {}
_, geo, order, shade = frame_geometry(posed, prof, yaw, pitch=pitch, mat=mat)
_, geo, order, shade = frame_geometry(posed, prof, cam + off, pitch=pitch, mat=mat)
for limb in hide:
geo.pop(limb, None)
imgs.append(draw_geo(geo, order, shade, working, colors, font=font, props=props))
prims = resolve_props(props, geo, nf["root"]["pos"],
prop_rotation(pitch, off), auth_geo)
imgs.append(draw_geo(geo, order, shade, working, colors, font=font, prims=prims))
imgs[0].save(folder / "orbit.gif", save_all=True, append_images=imgs[1:],
duration=50, loop=0)
print(f" {motion['name']}: orbit.gif ({len(imgs)} frames)")
@@ -618,9 +693,10 @@ def contact_sheet(folders, figure="neutral", out=None):
_, geo, order, shade = frame_geometry(nf, prof, cam, pitch=pitch, mat=mat)
for limb in hide:
geo.pop(limb, None)
prims = resolve_props(props, geo, nf["root"]["pos"])
cells.append((f"{motion['name']} {i}/{len(norms)}",
draw_geo(geo, order, shade, working, PALETTES["default"],
font=font, props=props)))
font=font, prims=prims)))
save_sheet(cells, Path(out) if out else LIB / "contact-sheet.png")
@@ -644,9 +720,10 @@ def demo_sheet(folder):
_, geo, order, shade = frame_geometry(norms[idx], prof, cam, flip, pitch, mat)
for limb in hide:
geo.pop(limb, None)
prims = resolve_props(props, geo, norms[idx]["root"]["pos"])
cells.append((f"{motion['name']}{label}",
draw_geo(geo, order, shade, working, PALETTES[palette],
font=font, props=props)))
font=font, prims=prims)))
save_sheet(cells, LIB / "demo-sheet.png", cols=3)
@@ -689,6 +766,62 @@ def export_app_resources(folders):
print(f" exported skeleton.json -> {out}")
def write_fixtures(folders):
"""Regenerate `WorkoutsTests/Fixtures/figure-fixtures.json` — the projected
geometry snapshots that pin the Swift solver to this pipeline: per exercise
and key frame the draw order, shading, spine, head, nose, and limb chains,
plus a mid-tween sample and (for a spread of prop flavors) orbit samples
taken through the presentation path — resolve at the authored camera, clear
the pins, rotate the posed body — with the resolved prop primitives."""
ORBIT_SAMPLED = {"Bird Dog", "Lat Pull Down", "Leg Extension", "Abductor"}
OFFSETS = (37.0, 180.0)
T = 0.5
def sample(geo, order, shade, prims=None):
out = {"order": order, "shade": shade,
"spine": [list(p) for p in geo["spine"]],
"head": list(geo["head"])}
if "nose" in geo:
out["nose"] = [list(geo["nose"][0]), list(geo["nose"][1])]
for limb in K.LIMBS:
out[limb] = [list(p) for p in geo[limb]]
if prims is not None:
out["props"] = {"bg": prims[0], "fg": prims[1]}
return out
exercises = []
for folder in folders:
motion = load_motion(folder)
norms, prof, cam, pitch, props = prepare(motion, "neutral")
resolved, frames = [], []
for nf in norms:
out, geo, order, shade = frame_geometry(nf, prof, cam)
resolved.append(out)
frames.append(sample(geo, order, shade))
mid = K.lerp_frames(resolved[0], resolved[1], ease(T))
_, geo, order, shade = frame_geometry(mid, prof, cam)
entry = {"name": motion["name"], "camera": cam, "frames": frames,
"tween": {"t": T, "sample": sample(geo, order, shade)}}
if motion["name"] in ORBIT_SAMPLED:
anchor = norms[0]["root"]["pos"]
posed, auth_geo, _, _ = frame_geometry(norms[0], prof, cam)
posed["pins"] = {}
orbit = []
for off in OFFSETS:
_, geo, order, shade = frame_geometry(posed, prof, cam + off)
prims = resolve_props(props, geo, anchor,
prop_rotation(CAMERA_PITCH, off), auth_geo)
orbit.append({"yaw": cam + off,
"sample": sample(geo, order, shade, prims)})
entry["orbit"] = orbit
exercises.append(entry)
out_path = LIB.parent / "WorkoutsTests" / "Fixtures" / "figure-fixtures.json"
out_path.write_text(json.dumps(
{"profile": "neutral", "pitch": CAMERA_PITCH, "exercises": exercises}))
print(f" {out_path.name} ({len(exercises)} exercises)")
def main():
flags = [a for a in sys.argv[1:] if a.startswith("--")]
names = [a for a in sys.argv[1:] if not a.startswith("--")]
@@ -704,6 +837,9 @@ def main():
if "--export" in flags:
export_app_resources(folders)
return
if "--fixtures" in flags:
write_fixtures(folders)
return
if "--orbit" in flags:
for folder in folders:
render_orbit(folder, figure=figure)