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
@@ -1,11 +1,11 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
||||||
<title>Abdominal</title>
|
<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 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 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" 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 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 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" stroke="#c5cad4" stroke-width="5" 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="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="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"/>
|
<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">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
||||||
<title>Abdominal</title>
|
<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 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 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" 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 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 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" stroke="#c5cad4" stroke-width="5" 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="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="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"/>
|
<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,11 +4,11 @@
|
|||||||
"working": ["spine"],
|
"working": ["spine"],
|
||||||
"props": [
|
"props": [
|
||||||
{"type": "scene", "shapes": [
|
{"type": "scene", "shapes": [
|
||||||
{"kind": "line", "pts": [[120, 128], [114, 62]], "w": 8},
|
{"kind": "line", "pts": [[120, 128], [114, 62]], "w": 8, "depth": 7},
|
||||||
{"kind": "line", "pts": [[106, 130], [150, 130]], "w": 8},
|
{"kind": "line", "pts": [[106, 130], [150, 130]], "w": 8, "depth": 8},
|
||||||
{"kind": "line", "pts": [[122, 132], [122, 150]], "w": 4},
|
{"kind": "line", "pts": [[122, 132], [122, 150]], "w": 4},
|
||||||
{"kind": "line", "pts": [[102, 150], [150, 150]], "w": 4},
|
{"kind": "line", "pts": [[102, 150], [150, 150]], "w": 4, "depth": 5},
|
||||||
{"kind": "line", "pts": [[156, 149], [190, 149]], "w": 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}
|
{"type": "pad", "at": ["hand_r", "hand_l"], "angle": 90, "halfLen": 14, "w": 6}
|
||||||
],
|
],
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 188 KiB After Width: | Height: | Size: 177 KiB |
@@ -1,11 +1,11 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
||||||
<title>Abdominal</title>
|
<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 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 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" 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 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 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" stroke="#c5cad4" stroke-width="5" 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="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="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"/>
|
<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 |
@@ -1,8 +1,8 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
||||||
<title>Abductor</title>
|
<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"/>
|
<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"/>
|
<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"/>
|
||||||
<rect x="130" y="126" width="60" height="10" rx="3" fill="#c5cad4"/>
|
<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 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="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"/>
|
<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 |
@@ -1,8 +1,8 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
||||||
<title>Abductor</title>
|
<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"/>
|
<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"/>
|
<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"/>
|
||||||
<rect x="130" y="126" width="60" height="10" rx="3" fill="#c5cad4"/>
|
<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 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="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"/>
|
<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 |
@@ -5,8 +5,8 @@
|
|||||||
"working": ["leg_r", "leg_l"],
|
"working": ["leg_r", "leg_l"],
|
||||||
"props": [
|
"props": [
|
||||||
{"type": "scene", "shapes": [
|
{"type": "scene", "shapes": [
|
||||||
{"kind": "rect", "x": 152, "y": 56, "w": 16, "h": 72, "r": 5},
|
{"kind": "line", "pts": [[160, 64], [160, 120]], "w": 16, "z": -14, "depth": 6},
|
||||||
{"kind": "rect", "x": 130, "y": 126, "w": 60, "h": 10, "r": 3},
|
{"kind": "line", "pts": [[135, 131], [185, 131]], "w": 10, "depth": 12},
|
||||||
{"kind": "line", "pts": [[160, 136], [160, 150]], "w": 6}
|
{"kind": "line", "pts": [[160, 136], [160, 150]], "w": 6}
|
||||||
]},
|
]},
|
||||||
{"type": "pad", "at": ["knee_r"], "angle": 90, "halfLen": 13, "w": 6},
|
{"type": "pad", "at": ["knee_r"], "angle": 90, "halfLen": 13, "w": 6},
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 192 KiB After Width: | Height: | Size: 176 KiB |
@@ -1,8 +1,8 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
||||||
<title>Abductor</title>
|
<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"/>
|
<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"/>
|
<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"/>
|
||||||
<rect x="130" y="126" width="60" height="10" rx="3" fill="#c5cad4"/>
|
<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 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="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"/>
|
<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 |
@@ -1,8 +1,8 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
||||||
<title>Adductor</title>
|
<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"/>
|
<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"/>
|
<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"/>
|
||||||
<rect x="130" y="126" width="60" height="10" rx="3" fill="#c5cad4"/>
|
<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 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="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"/>
|
<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 |
@@ -1,8 +1,8 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
||||||
<title>Adductor</title>
|
<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"/>
|
<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"/>
|
<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"/>
|
||||||
<rect x="130" y="126" width="60" height="10" rx="3" fill="#c5cad4"/>
|
<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 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="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"/>
|
<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 |
@@ -5,8 +5,8 @@
|
|||||||
"working": ["leg_r", "leg_l"],
|
"working": ["leg_r", "leg_l"],
|
||||||
"props": [
|
"props": [
|
||||||
{"type": "scene", "shapes": [
|
{"type": "scene", "shapes": [
|
||||||
{"kind": "rect", "x": 152, "y": 56, "w": 16, "h": 72, "r": 5},
|
{"kind": "line", "pts": [[160, 64], [160, 120]], "w": 16, "z": -14, "depth": 6},
|
||||||
{"kind": "rect", "x": 130, "y": 126, "w": 60, "h": 10, "r": 3},
|
{"kind": "line", "pts": [[135, 131], [185, 131]], "w": 10, "depth": 12},
|
||||||
{"kind": "line", "pts": [[160, 136], [160, 150]], "w": 6}
|
{"kind": "line", "pts": [[160, 136], [160, 150]], "w": 6}
|
||||||
]},
|
]},
|
||||||
{"type": "pad", "at": ["knee_r"], "angle": 90, "halfLen": 13, "w": 6},
|
{"type": "pad", "at": ["knee_r"], "angle": 90, "halfLen": 13, "w": 6},
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 192 KiB After Width: | Height: | Size: 176 KiB |
@@ -1,8 +1,8 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
||||||
<title>Adductor</title>
|
<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"/>
|
<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"/>
|
<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"/>
|
||||||
<rect x="130" y="126" width="60" height="10" rx="3" fill="#c5cad4"/>
|
<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 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="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"/>
|
<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 |
@@ -1,8 +1,8 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
||||||
<title>Arm Curl</title>
|
<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 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 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" 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 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="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"/>
|
<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 |
@@ -1,8 +1,8 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
||||||
<title>Arm Curl</title>
|
<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 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 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" 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 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="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"/>
|
<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 |
@@ -4,8 +4,8 @@
|
|||||||
"working": ["arm_r", "arm_l"],
|
"working": ["arm_r", "arm_l"],
|
||||||
"props": [
|
"props": [
|
||||||
{"type": "scene", "shapes": [
|
{"type": "scene", "shapes": [
|
||||||
{"kind": "line", "pts": [[116, 134], [150, 134]], "w": 8},
|
{"kind": "line", "pts": [[116, 134], [150, 134]], "w": 8, "depth": 8},
|
||||||
{"kind": "line", "pts": [[136, 52], [164, 70]], "w": 8},
|
{"kind": "line", "pts": [[136, 52], [164, 70]], "w": 8, "depth": 8},
|
||||||
{"kind": "line", "pts": [[164, 70], [164, 128]], "w": 5}
|
{"kind": "line", "pts": [[164, 70], [164, 128]], "w": 5}
|
||||||
]},
|
]},
|
||||||
{"type": "bar", "at": ["hand_r", "hand_l"], "angle": 90, "halfLen": 6}
|
{"type": "bar", "at": ["hand_r", "hand_l"], "angle": 90, "halfLen": 6}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 135 KiB After Width: | Height: | Size: 145 KiB |
@@ -1,8 +1,8 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
||||||
<title>Arm Curl</title>
|
<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 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 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" 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 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="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"/>
|
<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 |
@@ -1,10 +1,10 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
||||||
<title>Calfs</title>
|
<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 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 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"/>
|
<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"/>
|
||||||
<rect x="198" y="147" width="34" height="6" rx="1" fill="#c5cad4"/>
|
<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 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 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"/>
|
<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 |
@@ -1,10 +1,10 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
||||||
<title>Calfs</title>
|
<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 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 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"/>
|
<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"/>
|
||||||
<rect x="198" y="147" width="34" height="6" rx="1" fill="#c5cad4"/>
|
<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 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 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"/>
|
<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 |
@@ -4,10 +4,10 @@
|
|||||||
"working": ["leg_r", "leg_l"],
|
"working": ["leg_r", "leg_l"],
|
||||||
"props": [
|
"props": [
|
||||||
{"type": "scene", "shapes": [
|
{"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": [[138, 128], [138, 150]], "w": 5},
|
||||||
{"kind": "line", "pts": [[120, 150], [156, 150]], "w": 5},
|
{"kind": "line", "pts": [[120, 150], [156, 150]], "w": 5, "depth": 5},
|
||||||
{"kind": "rect", "x": 198, "y": 147, "w": 34, "h": 6, "r": 1},
|
{"kind": "line", "pts": [[201, 150], [229, 150]], "w": 6, "depth": 7},
|
||||||
{"kind": "line", "pts": [[183, 62], [183, 122]], "w": 4},
|
{"kind": "line", "pts": [[183, 62], [183, 122]], "w": 4},
|
||||||
{"kind": "line", "pts": [[164, 76], [183, 76]], "w": 3, "color": "prop"}
|
{"kind": "line", "pts": [[164, 76], [183, 76]], "w": 3, "color": "prop"}
|
||||||
]},
|
]},
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 146 KiB After Width: | Height: | Size: 154 KiB |
@@ -1,10 +1,10 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
||||||
<title>Calfs</title>
|
<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 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 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"/>
|
<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"/>
|
||||||
<rect x="198" y="147" width="34" height="6" rx="1" fill="#c5cad4"/>
|
<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 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 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"/>
|
<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">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
||||||
<title>Chest Press</title>
|
<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 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 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" 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 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="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="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"/>
|
<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">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
||||||
<title>Chest Press</title>
|
<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 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 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" 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 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="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="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"/>
|
<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 |
@@ -4,10 +4,10 @@
|
|||||||
"working": ["arm_r", "arm_l"],
|
"working": ["arm_r", "arm_l"],
|
||||||
"props": [
|
"props": [
|
||||||
{"type": "scene", "shapes": [
|
{"type": "scene", "shapes": [
|
||||||
{"kind": "line", "pts": [[114, 132], [86, 40]], "w": 8},
|
{"kind": "line", "pts": [[114, 132], [86, 40]], "w": 8, "depth": 8},
|
||||||
{"kind": "line", "pts": [[106, 133], [146, 133]], "w": 8},
|
{"kind": "line", "pts": [[106, 133], [146, 133]], "w": 8, "depth": 8},
|
||||||
{"kind": "line", "pts": [[122, 135], [122, 151]], "w": 5},
|
{"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}
|
{"type": "bar", "at": ["hand_r", "hand_l"], "angle": 90, "halfLen": 8}
|
||||||
],
|
],
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 138 KiB After Width: | Height: | Size: 138 KiB |
@@ -1,10 +1,10 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
||||||
<title>Chest Press</title>
|
<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 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 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" 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 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="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="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"/>
|
<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">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
||||||
<title>Lat Pull Down</title>
|
<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 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"/>
|
<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"/>
|
<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"/>
|
<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">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
||||||
<title>Lat Pull Down</title>
|
<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 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"/>
|
<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"/>
|
<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"/>
|
<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,7 +4,7 @@
|
|||||||
"working": ["arm_r", "arm_l"],
|
"working": ["arm_r", "arm_l"],
|
||||||
"props": [
|
"props": [
|
||||||
{"type": "scene", "shapes": [
|
{"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": "line", "pts": [[144, 132], [144, 150]], "w": 5},
|
||||||
{"kind": "circle", "c": [272, 19], "r": 4, "fill": true, "color": "prop"}
|
{"kind": "circle", "c": [272, 19], "r": 4, "fill": true, "color": "prop"}
|
||||||
]},
|
]},
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 163 KiB After Width: | Height: | Size: 161 KiB |
@@ -1,7 +1,7 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
||||||
<title>Lat Pull Down</title>
|
<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 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"/>
|
<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"/>
|
<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"/>
|
<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,12 +1,12 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
||||||
<title>Leg Curl</title>
|
<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 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 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" 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" stroke="#c5cad4" stroke-width="7" 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 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 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"/>
|
<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="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"/>
|
<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,12 +1,12 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
||||||
<title>Leg Curl</title>
|
<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 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 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" 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" stroke="#c5cad4" stroke-width="7" 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 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 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"/>
|
<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="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"/>
|
<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,12 +4,12 @@
|
|||||||
"working": ["leg_r", "leg_l"],
|
"working": ["leg_r", "leg_l"],
|
||||||
"props": [
|
"props": [
|
||||||
{"type": "scene", "shapes": [
|
{"type": "scene", "shapes": [
|
||||||
{"kind": "line", "pts": [[110, 122], [86, 48]], "w": 9},
|
{"kind": "line", "pts": [[110, 122], [86, 48]], "w": 9, "depth": 8},
|
||||||
{"kind": "line", "pts": [[104, 124], [166, 120]], "w": 9},
|
{"kind": "line", "pts": [[104, 124], [166, 120]], "w": 9, "depth": 8},
|
||||||
{"kind": "line", "pts": [[130, 105], [158, 106]], "w": 7},
|
{"kind": "line", "pts": [[130, 105], [158, 106]], "w": 7, "depth": 7},
|
||||||
{"kind": "line", "pts": [[108, 126], [108, 150]], "w": 5},
|
{"kind": "line", "pts": [[108, 126], [108, 150]], "w": 5},
|
||||||
{"kind": "line", "pts": [[160, 124], [160, 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"}
|
{"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}
|
{"type": "roller", "at": ["foot_r", "foot_l"], "side": -1, "r": 5, "back": 3}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 172 KiB After Width: | Height: | Size: 176 KiB |
@@ -1,12 +1,12 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
||||||
<title>Leg Curl</title>
|
<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 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 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" 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" stroke="#c5cad4" stroke-width="7" 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 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 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"/>
|
<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="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"/>
|
<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">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
||||||
<title>Leg Extension</title>
|
<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 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 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" 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 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 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"/>
|
<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="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"/>
|
<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">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
||||||
<title>Leg Extension</title>
|
<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 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 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" 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 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 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"/>
|
<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="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"/>
|
<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 |
@@ -4,11 +4,11 @@
|
|||||||
"working": ["leg_r", "leg_l"],
|
"working": ["leg_r", "leg_l"],
|
||||||
"props": [
|
"props": [
|
||||||
{"type": "scene", "shapes": [
|
{"type": "scene", "shapes": [
|
||||||
{"kind": "line", "pts": [[110, 122], [86, 48]], "w": 9},
|
{"kind": "line", "pts": [[110, 122], [86, 48]], "w": 9, "depth": 8},
|
||||||
{"kind": "line", "pts": [[104, 124], [166, 120]], "w": 9},
|
{"kind": "line", "pts": [[104, 124], [166, 120]], "w": 9, "depth": 8},
|
||||||
{"kind": "line", "pts": [[108, 126], [108, 150]], "w": 5},
|
{"kind": "line", "pts": [[108, 126], [108, 150]], "w": 5},
|
||||||
{"kind": "line", "pts": [[160, 124], [160, 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"}
|
{"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}
|
{"type": "roller", "at": ["foot_r", "foot_l"], "side": 1, "r": 5, "back": 7}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 165 KiB After Width: | Height: | Size: 142 KiB |
@@ -1,11 +1,11 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
||||||
<title>Leg Extension</title>
|
<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 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 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" 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 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 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"/>
|
<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="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"/>
|
<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">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
||||||
<title>Leg Press</title>
|
<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 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 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" 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 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"/>
|
<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"/>
|
<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"/>
|
<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">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
||||||
<title>Leg Press</title>
|
<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 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 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" 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 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"/>
|
<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"/>
|
<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"/>
|
<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 |
@@ -4,10 +4,10 @@
|
|||||||
"working": ["leg_r", "leg_l"],
|
"working": ["leg_r", "leg_l"],
|
||||||
"props": [
|
"props": [
|
||||||
{"type": "scene", "shapes": [
|
{"type": "scene", "shapes": [
|
||||||
{"kind": "line", "pts": [[134, 123], [96, 36]], "w": 9},
|
{"kind": "line", "pts": [[134, 123], [96, 36]], "w": 9, "depth": 8},
|
||||||
{"kind": "line", "pts": [[126, 127], [156, 122]], "w": 9},
|
{"kind": "line", "pts": [[126, 127], [156, 122]], "w": 9, "depth": 8},
|
||||||
{"kind": "line", "pts": [[138, 130], [138, 150]], "w": 5},
|
{"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": "line", "pts": [[142, 90], [142, 78]], "w": 3},
|
||||||
{"kind": "circle", "c": [142, 77], "r": 3.5, "fill": true, "color": "prop"}
|
{"kind": "circle", "c": [142, 77], "r": 3.5, "fill": true, "color": "prop"}
|
||||||
]},
|
]},
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 166 KiB After Width: | Height: | Size: 180 KiB |
@@ -1,10 +1,10 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
||||||
<title>Leg Press</title>
|
<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 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 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" 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 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"/>
|
<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"/>
|
<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"/>
|
<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 |
@@ -1,9 +1,9 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
||||||
<title>Rotary</title>
|
<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 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 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="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="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"/>
|
<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 |
@@ -1,9 +1,9 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
||||||
<title>Rotary</title>
|
<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 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 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="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="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"/>
|
<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 |
@@ -5,9 +5,9 @@
|
|||||||
"working": ["spine"],
|
"working": ["spine"],
|
||||||
"props": [
|
"props": [
|
||||||
{"type": "scene", "shapes": [
|
{"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": [[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}
|
{"type": "bar", "at": ["hand_r", "hand_l"], "angle": 90, "halfLen": 14}
|
||||||
],
|
],
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 149 KiB |
@@ -1,9 +1,9 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
||||||
<title>Rotary</title>
|
<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 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 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="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="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"/>
|
<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 |
@@ -132,8 +132,8 @@ scale drawings of the machine.
|
|||||||
```json
|
```json
|
||||||
"props": [
|
"props": [
|
||||||
{"type": "scene", "shapes": [
|
{"type": "scene", "shapes": [
|
||||||
{"kind": "line", "pts": [[134, 123], [96, 36]], "w": 9},
|
{"kind": "line", "pts": [[134, 123], [96, 36]], "w": 9, "depth": 8},
|
||||||
{"kind": "rect", "x": 54, "y": 104, "w": 40, "h": 8, "r": 3},
|
{"kind": "line", "pts": [[160, 64], [160, 120]], "w": 16, "z": -14},
|
||||||
{"kind": "circle", "c": [142, 77], "r": 3.5, "fill": true, "color": "prop"}
|
{"kind": "circle", "c": [142, 77], "r": 3.5, "fill": true, "color": "prop"}
|
||||||
]},
|
]},
|
||||||
{"type": "cable", "from": [190, 8], "to": ["hand_r", "hand_l"]},
|
{"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
|
Props are authored in the authored view but have **world-space 3D form**:
|
||||||
width `w`), `circle` (`fill: false` for an outline), `rect` (filled, corner
|
under an orbiting camera the whole equipment layer rotates about the
|
||||||
radius `r`). A shape may set `"color": "prop"` to use the darker
|
world-vertical axis through the root anchor, exactly like the figure and the
|
||||||
attached-item gray (e.g. a fixed handle the hands rest on). Scene shapes
|
mat. Everything resolves in the *authored* view — scene points, cable
|
||||||
are view-locked billboards — they don't rotate with `--orbit`.
|
anchors, bar angles, pad perpendiculars, roller offsets — and the resolved
|
||||||
- **`cable`** — a thin line from a fixed anchor `from` to a moving joint `to`;
|
constructs are then rotated; joint-attached items keep following the rotated
|
||||||
the machine's pulley line.
|
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
|
- **`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
|
`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
|
(`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
|
machine pad can ride a knee (`["knee_r", "knee_l"]`) or span a shin
|
||||||
(`["knee_r", "foot_r"]`).
|
(`["knee_r", "foot_r"]`).
|
||||||
`bar` lies at a fixed world `angle` (default 0 = horizontal — in side view a
|
`bar` lies at a fixed authored-view `angle` (default 0 = horizontal — in
|
||||||
two-handed bar is drawn horizontal by convention); `dumbbell` and `pad`
|
side view a two-handed bar is drawn horizontal by convention); `dumbbell`
|
||||||
default to perpendicular to the lower bone (forearm/shin), or take an
|
and `pad` default to perpendicular to the lower bone (forearm/shin), or
|
||||||
explicit `angle`. `plateR` puts filled discs on both ends (dumbbells default
|
take an explicit `angle`. Under orbit the segment rotates with the scene
|
||||||
to 4.5). A prop whose limb is hidden that frame simply isn't drawn.
|
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
|
- **`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
|
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
|
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 --flip # view from the other side (camera + 180°)
|
||||||
python3 render.py --strict # fail on any ROM violation, listing each
|
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 --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).
|
`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
|
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`,
|
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
|
held to it by `WorkoutsTests/Fixtures/figure-fixtures.json` — projected
|
||||||
geometry snapshots the Swift solver must reproduce; regenerate them alongside
|
geometry snapshots (figure and prop primitives, including orbit-presentation
|
||||||
any pipeline change. Re-run `python3 render.py --export` after editing any
|
samples) the Swift solver must reproduce; regenerate with
|
||||||
motion or info page; the library stays the source of truth.
|
`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">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
||||||
<title>Seated Row</title>
|
<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 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 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" 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" stroke="#c5cad4" stroke-width="5" 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 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="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"/>
|
<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">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
||||||
<title>Seated Row</title>
|
<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 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 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" 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" stroke="#c5cad4" stroke-width="5" 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 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="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"/>
|
<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 |
@@ -4,9 +4,9 @@
|
|||||||
"working": ["arm_r", "arm_l"],
|
"working": ["arm_r", "arm_l"],
|
||||||
"props": [
|
"props": [
|
||||||
{"type": "scene", "shapes": [
|
{"type": "scene", "shapes": [
|
||||||
{"kind": "line", "pts": [[122, 132], [156, 132]], "w": 8},
|
{"kind": "line", "pts": [[122, 132], [156, 132]], "w": 8, "depth": 8},
|
||||||
{"kind": "line", "pts": [[159, 46], [157, 96]], "w": 8},
|
{"kind": "line", "pts": [[159, 46], [157, 96]], "w": 8, "depth": 7},
|
||||||
{"kind": "line", "pts": [[176, 150], [206, 150]], "w": 5}
|
{"kind": "line", "pts": [[176, 150], [206, 150]], "w": 5, "depth": 6}
|
||||||
]},
|
]},
|
||||||
{"type": "cable", "from": [244, 120], "to": ["hand_r", "hand_l"]},
|
{"type": "cable", "from": [244, 120], "to": ["hand_r", "hand_l"]},
|
||||||
{"type": "bar", "at": ["hand_r", "hand_l"], "angle": 90, "halfLen": 7}
|
{"type": "bar", "at": ["hand_r", "hand_l"], "angle": 90, "halfLen": 7}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 185 KiB After Width: | Height: | Size: 181 KiB |
@@ -1,9 +1,9 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
||||||
<title>Seated Row</title>
|
<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 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 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" 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" stroke="#c5cad4" stroke-width="5" 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 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="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"/>
|
<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">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
||||||
<title>Shoulder Press</title>
|
<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 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 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" 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 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="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="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"/>
|
<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">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
||||||
<title>Shoulder Press</title>
|
<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 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 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" 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 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="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="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"/>
|
<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 |
@@ -4,10 +4,10 @@
|
|||||||
"working": ["arm_r", "arm_l"],
|
"working": ["arm_r", "arm_l"],
|
||||||
"props": [
|
"props": [
|
||||||
{"type": "scene", "shapes": [
|
{"type": "scene", "shapes": [
|
||||||
{"kind": "line", "pts": [[116, 140], [88, 50]], "w": 8},
|
{"kind": "line", "pts": [[116, 140], [88, 50]], "w": 8, "depth": 8},
|
||||||
{"kind": "line", "pts": [[110, 141], [148, 141]], "w": 8},
|
{"kind": "line", "pts": [[110, 141], [148, 141]], "w": 8, "depth": 8},
|
||||||
{"kind": "line", "pts": [[126, 143], [126, 151]], "w": 5},
|
{"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}
|
{"type": "bar", "at": ["hand_r", "hand_l"], "halfLen": 10}
|
||||||
],
|
],
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 153 KiB After Width: | Height: | Size: 148 KiB |
@@ -1,10 +1,10 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
||||||
<title>Shoulder Press</title>
|
<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 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 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" 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 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="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="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"/>
|
<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">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
||||||
<title>Tricep Press</title>
|
<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 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 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" 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 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="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="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"/>
|
<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">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
||||||
<title>Tricep Press</title>
|
<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 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 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" 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 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="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="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"/>
|
<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 |
@@ -4,10 +4,10 @@
|
|||||||
"working": ["arm_r", "arm_l"],
|
"working": ["arm_r", "arm_l"],
|
||||||
"props": [
|
"props": [
|
||||||
{"type": "scene", "shapes": [
|
{"type": "scene", "shapes": [
|
||||||
{"kind": "line", "pts": [[116, 134], [92, 42]], "w": 8},
|
{"kind": "line", "pts": [[116, 134], [92, 42]], "w": 8, "depth": 8},
|
||||||
{"kind": "line", "pts": [[108, 135], [146, 135]], "w": 8},
|
{"kind": "line", "pts": [[108, 135], [146, 135]], "w": 8, "depth": 8},
|
||||||
{"kind": "line", "pts": [[124, 137], [124, 151]], "w": 5},
|
{"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}
|
{"type": "bar", "at": ["hand_r", "hand_l"], "angle": 90, "halfLen": 6}
|
||||||
],
|
],
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 156 KiB |
@@ -1,10 +1,10 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
||||||
<title>Tricep Press</title>
|
<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 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 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" 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 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="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="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"/>
|
<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 |
|
Before Width: | Height: | Size: 299 KiB After Width: | Height: | Size: 302 KiB |
@@ -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
|
visual.svg (the primary frame). `--sheet` writes contact-sheet.png of every
|
||||||
key frame; `--demo` writes demo-sheet.png showing rig customizations (body
|
key frame; `--demo` writes demo-sheet.png showing rig customizations (body
|
||||||
profiles, flipped camera, theme); `--orbit` writes orbit.gif per named
|
profiles, flipped camera, theme); `--orbit` writes orbit.gif per named
|
||||||
exercise (the camera sweeps 360 degrees while the motion loops). `--export`
|
exercise (the camera sweeps 360 degrees while the motion loops — props
|
||||||
instead bakes each motion down to the legacy planar schema consumed by the
|
rotate with the figure). `--export` copies the app's bundled resources
|
||||||
in-app SwiftUI renderer. SVGs need no dependencies; GIFs/sheets need Pillow.
|
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
|
import copy
|
||||||
@@ -227,24 +229,35 @@ def timeline(norms, fps=20):
|
|||||||
# figure in the recessive equipment gray; joint-attached items (`bar`,
|
# figure in the recessive equipment gray; joint-attached items (`bar`,
|
||||||
# `dumbbell`, `pad`) draw over the limbs in the darker prop gray, following
|
# `dumbbell`, `pad`) draw over the limbs in the darker prop gray, following
|
||||||
# the resolved hand/foot positions frame by frame.
|
# 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):
|
def flip_props(props, width):
|
||||||
"""Mirror the props horizontally, matching the flipped camera. Joint-
|
"""Mirror the props horizontally, matching the flipped camera (which views
|
||||||
attached props follow the mirrored limbs automatically; only fixed
|
from the opposite side, so authored depths flip sign too). Joint-attached
|
||||||
coordinates and world angles need mirroring."""
|
props follow the mirrored limbs automatically; only fixed coordinates,
|
||||||
|
depths, and world angles need mirroring."""
|
||||||
def fx(p):
|
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 = []
|
out = []
|
||||||
for prop in props:
|
for prop in props:
|
||||||
p = json.loads(json.dumps(prop))
|
p = json.loads(json.dumps(prop))
|
||||||
if p["type"] == "scene":
|
if p["type"] == "scene":
|
||||||
for s in p["shapes"]:
|
for s in p["shapes"]:
|
||||||
|
if "z" in s:
|
||||||
|
s["z"] = -s["z"]
|
||||||
if s["kind"] == "line":
|
if s["kind"] == "line":
|
||||||
s["pts"] = [fx(pt) for pt in s["pts"]]
|
s["pts"] = [fx(pt) for pt in s["pts"]]
|
||||||
elif s["kind"] == "circle":
|
elif s["kind"] == "circle":
|
||||||
s["c"] = fx(s["c"])
|
s["c"] = fx(s["c"])
|
||||||
elif s["kind"] == "rect":
|
|
||||||
s["x"] = width - s["x"] - s["w"]
|
|
||||||
elif p["type"] == "cable":
|
elif p["type"] == "cable":
|
||||||
p["from"] = fx(p["from"])
|
p["from"] = fx(p["from"])
|
||||||
elif p["type"] in ("bar", "pad") and "angle" in p:
|
elif p["type"] in ("bar", "pad") and "angle" in p:
|
||||||
@@ -255,6 +268,16 @@ def flip_props(props, width):
|
|||||||
return out
|
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):
|
def joint_points(geo, ref):
|
||||||
"""Resolve a joint ref — `"hand_r"`, `"knee_l"`, or a midpoint list like
|
"""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
|
`["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)
|
sum(p[1] for p in pts) / len(pts)), direction)
|
||||||
|
|
||||||
|
|
||||||
def resolve_props(props, geo):
|
def resolve_props(props, geo, anchor, rot=K.IDENTITY, auth_geo=None):
|
||||||
"""Props -> drawable primitives for one frame: (background, foreground)."""
|
"""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 = [], []
|
bg, fg = [], []
|
||||||
for p in props or []:
|
for p in props or []:
|
||||||
t = p["type"]
|
t = p["type"]
|
||||||
if t == "scene":
|
if t == "scene":
|
||||||
for s in p["shapes"]:
|
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":
|
elif t == "cable":
|
||||||
end, _ = joint_points(geo, p["to"])
|
end, _ = joint_points(geo, p["to"])
|
||||||
if end:
|
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"})
|
"w": p.get("w", 2), "color": "equipment"})
|
||||||
elif t == "roller":
|
elif t == "roller":
|
||||||
# A machine roller pad seen end-on: a disc riding the limb's lower
|
# 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.
|
# bone near the joint, on the `side` (+1/-1) of the bone it presses.
|
||||||
c, d = joint_points(geo, p["at"])
|
# The offset resolves along the authored-view bone, then rotates.
|
||||||
if not c:
|
c, _ = joint_points(geo, p["at"])
|
||||||
|
_, d = joint_points(auth, p["at"])
|
||||||
|
if not c or not d:
|
||||||
continue
|
continue
|
||||||
r = p.get("r", 5)
|
r = p.get("r", 5)
|
||||||
back = p.get("back", 0)
|
back = p.get("back", 0)
|
||||||
side = p.get("side", 1)
|
side = p.get("side", 1)
|
||||||
px, py = d[1] * side, -d[0] * side
|
px, py = d[1] * side, -d[0] * side
|
||||||
center = (c[0] - d[0] * back + px * (r + 3),
|
off = swing((-d[0] * back + px * (r + 3), -d[1] * back + py * (r + 3)))
|
||||||
c[1] - d[1] * back + py * (r + 3))
|
fg.append({"kind": "circle", "c": [c[0] + off[0], c[1] + off[1]], "r": r,
|
||||||
fg.append({"kind": "circle", "c": list(center), "r": r,
|
|
||||||
"fill": True, "color": "prop"})
|
"fill": True, "color": "prop"})
|
||||||
elif t in ("bar", "dumbbell", "pad"):
|
elif t in ("bar", "dumbbell", "pad"):
|
||||||
c, d = joint_points(geo, p["at"])
|
c, _ = joint_points(geo, p["at"])
|
||||||
if not c:
|
_, d = joint_points(auth, p["at"])
|
||||||
|
if not c or not d:
|
||||||
continue
|
continue
|
||||||
if t == "bar" or "angle" in p:
|
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:
|
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])
|
h = p.get("halfLen", {"bar": 24, "dumbbell": 7, "pad": 8}[t])
|
||||||
a = (c[0] - ux * h, c[1] - uy * h)
|
a = (c[0] - ux * h, c[1] - uy * h)
|
||||||
b = (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]),
|
"w": p.get("w", {"bar": 4, "dumbbell": 3, "pad": 7}[t]),
|
||||||
"color": "prop"})
|
"color": "prop"})
|
||||||
plate = p.get("plateR", 4.5 if t == "dumbbell" else 0)
|
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"])
|
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)}"'
|
lines.append(f' <path d="{d}" stroke="{color}" stroke-width="{p.get("w", 4)}"'
|
||||||
f' stroke-linecap="round" stroke-linejoin="round"/>')
|
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":
|
elif p["kind"] == "circle":
|
||||||
cx, cy = p["c"]
|
cx, cy = p["c"]
|
||||||
if p.get("fill"):
|
if p.get("fill"):
|
||||||
@@ -339,17 +412,17 @@ def svg_prims(prims, colors):
|
|||||||
else:
|
else:
|
||||||
lines.append(f' <circle cx="{cx:.1f}" cy="{cy:.1f}" r="{p["r"]}"'
|
lines.append(f' <circle cx="{cx:.1f}" cy="{cy:.1f}" r="{p["r"]}"'
|
||||||
f' stroke="{color}" stroke-width="{p.get("w", 3)}"/>')
|
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
|
return lines
|
||||||
|
|
||||||
|
|
||||||
def draw_prims(d, prims, colors, scale):
|
def draw_prims(d, prims, colors, scale):
|
||||||
for p in prims:
|
for p in prims:
|
||||||
color = colors[p["color"]]
|
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"]]
|
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
|
w = p.get("w", 4) * scale
|
||||||
d.line(pts, fill=color, width=w, joint="curve")
|
d.line(pts, fill=color, width=w, joint="curve")
|
||||||
for x, y in (pts[0], pts[-1]):
|
for x, y in (pts[0], pts[-1]):
|
||||||
@@ -362,10 +435,6 @@ def draw_prims(d, prims, colors, scale):
|
|||||||
else:
|
else:
|
||||||
d.ellipse([cx - r, cy - r, cx + r, cy + r], outline=color,
|
d.ellipse([cx - r, cy - r, cx + r, cy + r], outline=color,
|
||||||
width=p.get("w", 3) * scale)
|
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
|
# ------------------------------------------------------------------- drawing
|
||||||
@@ -400,8 +469,8 @@ def quad_points(p0, ctrl, p2, n=24):
|
|||||||
return pts
|
return pts
|
||||||
|
|
||||||
|
|
||||||
def svg_for_frame(name, geo, order, shade, working, colors, props=None):
|
def svg_for_frame(name, geo, order, shade, working, colors, prims=((), ())):
|
||||||
bg, fg = resolve_props(props, geo)
|
bg, fg = prims
|
||||||
w, h = CANVAS
|
w, h = CANVAS
|
||||||
parts = [f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {w} {h}" fill="none">',
|
parts = [f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {w} {h}" fill="none">',
|
||||||
f' <title>{name}</title>',
|
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"
|
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
|
from PIL import Image, ImageDraw
|
||||||
|
|
||||||
bg, fg = resolve_props(props, geo)
|
bg, fg = prims
|
||||||
w, h = CANVAS[0] * scale, CANVAS[1] * scale
|
w, h = CANVAS[0] * scale, CANVAS[1] * scale
|
||||||
img = Image.new("RGB", (w, h), "white")
|
img = Image.new("RGB", (w, h), "white")
|
||||||
d = ImageDraw.Draw(img)
|
d = ImageDraw.Draw(img)
|
||||||
@@ -545,21 +614,21 @@ def render_exercise(folder, figure="neutral", flip=False, strict=False):
|
|||||||
return geo, order, shade
|
return geo, order, shade
|
||||||
|
|
||||||
resolved = []
|
resolved = []
|
||||||
key_geos = []
|
key_cells = []
|
||||||
for nf in norms:
|
for nf in norms:
|
||||||
out, geo, order, shade = frame_geometry(nf, prof, cam, flip, pitch, mat)
|
out, geo, order, shade = frame_geometry(nf, prof, cam, flip, pitch, mat)
|
||||||
for limb in hide:
|
for limb in hide:
|
||||||
geo.pop(limb, None)
|
geo.pop(limb, None)
|
||||||
resolved.append(out)
|
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 = folder / "frames"
|
||||||
frames_dir.mkdir(exist_ok=True)
|
frames_dir.mkdir(exist_ok=True)
|
||||||
for old in frames_dir.glob("frame-*.svg"):
|
for old in frames_dir.glob("frame-*.svg"):
|
||||||
old.unlink()
|
old.unlink()
|
||||||
colors = PALETTES["default"]
|
colors = PALETTES["default"]
|
||||||
svgs = [svg_for_frame(motion["name"], geo, order, shade, working, colors, props)
|
svgs = [svg_for_frame(motion["name"], geo, order, shade, working, colors, prims)
|
||||||
for geo, order, shade in key_geos]
|
for geo, order, shade, prims in key_cells]
|
||||||
for i, svg in enumerate(svgs, start=1):
|
for i, svg in enumerate(svgs, start=1):
|
||||||
(frames_dir / f"frame-{i}.svg").write_text(svg)
|
(frames_dir / f"frame-{i}.svg").write_text(svg)
|
||||||
(folder / "visual.svg").write_text(svgs[motion.get("primary", 1) - 1])
|
(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 = []
|
imgs = []
|
||||||
for nf in timeline(resolved):
|
for nf in timeline(resolved):
|
||||||
geo, order, shade = geometry(nf)
|
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:],
|
imgs[0].save(folder / "preview.gif", save_all=True, append_images=imgs[1:],
|
||||||
duration=50, loop=0)
|
duration=50, loop=0)
|
||||||
print(f" {motion['name']}: {len(svgs)} frames, preview.gif")
|
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"):
|
def render_orbit(folder, figure="neutral"):
|
||||||
"""A full-turn demo: the camera sweeps 360 degrees while the motion loops.
|
"""An orbiting demo of what the app shows: the camera sweeps 360 degrees
|
||||||
Scene props are view-locked billboards, so orbit shines on prop-free
|
while the motion loops (or swings a `camera.sweep` pendulum when the
|
||||||
motions (bodyweight exercises)."""
|
motion authors one). Props rotate with the figure about the root anchor."""
|
||||||
motion = load_motion(folder)
|
motion = load_motion(folder)
|
||||||
working = set(motion.get("working", []))
|
working = set(motion.get("working", []))
|
||||||
hide = set(motion.get("hide", []))
|
hide = set(motion.get("hide", []))
|
||||||
norms, prof, cam, pitch, props = prepare(motion, figure)
|
norms, prof, cam, pitch, props = prepare(motion, figure)
|
||||||
resolved = [frame_geometry(nf, prof, cam, pitch=pitch)[0] for nf in norms]
|
resolved = [frame_geometry(nf, prof, cam, pitch=pitch)[0] for nf in norms]
|
||||||
mat = mat_bounds(norms, prof, cam, pitch)
|
mat = mat_bounds(norms, prof, cam, pitch)
|
||||||
|
sweep = motion.get("camera", {}).get("sweep")
|
||||||
font = legend_font()
|
font = legend_font()
|
||||||
colors = PALETTES["default"]
|
colors = PALETTES["default"]
|
||||||
ticks = timeline(resolved)
|
ticks = timeline(resolved)
|
||||||
imgs = []
|
imgs = []
|
||||||
for i, nf in enumerate(ticks):
|
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,
|
# Pins are canvas targets in the AUTHORED view: resolve the pose there,
|
||||||
# then rotate the posed body - never re-pin in the rotated view.
|
# then rotate the posed body - never re-pin in the rotated view. The
|
||||||
posed, _, _, _ = frame_geometry(nf, prof, cam, pitch=pitch)
|
# authored-view geometry also feeds the props' directions and offsets.
|
||||||
|
posed, auth_geo, _, _ = frame_geometry(nf, prof, cam, pitch=pitch)
|
||||||
posed["pins"] = {}
|
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:
|
for limb in hide:
|
||||||
geo.pop(limb, None)
|
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:],
|
imgs[0].save(folder / "orbit.gif", save_all=True, append_images=imgs[1:],
|
||||||
duration=50, loop=0)
|
duration=50, loop=0)
|
||||||
print(f" {motion['name']}: orbit.gif ({len(imgs)} frames)")
|
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)
|
_, geo, order, shade = frame_geometry(nf, prof, cam, pitch=pitch, mat=mat)
|
||||||
for limb in hide:
|
for limb in hide:
|
||||||
geo.pop(limb, None)
|
geo.pop(limb, None)
|
||||||
|
prims = resolve_props(props, geo, nf["root"]["pos"])
|
||||||
cells.append((f"{motion['name']} {i}/{len(norms)}",
|
cells.append((f"{motion['name']} {i}/{len(norms)}",
|
||||||
draw_geo(geo, order, shade, working, PALETTES["default"],
|
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")
|
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)
|
_, geo, order, shade = frame_geometry(norms[idx], prof, cam, flip, pitch, mat)
|
||||||
for limb in hide:
|
for limb in hide:
|
||||||
geo.pop(limb, None)
|
geo.pop(limb, None)
|
||||||
|
prims = resolve_props(props, geo, norms[idx]["root"]["pos"])
|
||||||
cells.append((f"{motion['name']} — {label}",
|
cells.append((f"{motion['name']} — {label}",
|
||||||
draw_geo(geo, order, shade, working, PALETTES[palette],
|
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)
|
save_sheet(cells, LIB / "demo-sheet.png", cols=3)
|
||||||
|
|
||||||
|
|
||||||
@@ -689,6 +766,62 @@ def export_app_resources(folders):
|
|||||||
print(f" exported skeleton.json -> {out}")
|
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():
|
def main():
|
||||||
flags = [a for a in sys.argv[1:] if a.startswith("--")]
|
flags = [a for a in sys.argv[1:] if a.startswith("--")]
|
||||||
names = [a for a in sys.argv[1:] if not a.startswith("--")]
|
names = [a for a in sys.argv[1:] if not a.startswith("--")]
|
||||||
@@ -704,6 +837,9 @@ def main():
|
|||||||
if "--export" in flags:
|
if "--export" in flags:
|
||||||
export_app_resources(folders)
|
export_app_resources(folders)
|
||||||
return
|
return
|
||||||
|
if "--fixtures" in flags:
|
||||||
|
write_fixtures(folders)
|
||||||
|
return
|
||||||
if "--orbit" in flags:
|
if "--orbit" in flags:
|
||||||
for folder in folders:
|
for folder in folders:
|
||||||
render_orbit(folder, figure=figure)
|
render_orbit(folder, figure=figure)
|
||||||
|
|||||||
@@ -46,11 +46,12 @@ struct FigureAnimation {
|
|||||||
/// describes one authored viewpoint, so machines keep their fixed camera.
|
/// describes one authored viewpoint, so machines keep their fixed camera.
|
||||||
var orbits: Bool { props.isEmpty && hide.isEmpty }
|
var orbits: Bool { props.isEmpty && hide.isEmpty }
|
||||||
|
|
||||||
/// Drawable geometry at `time`, with limbs listed in `hide` dropped so neither they
|
/// Drawable geometry at `time` — the figure plus the resolved equipment layers —
|
||||||
/// nor any prop riding them are drawn.
|
/// with limbs listed in `hide` dropped so neither they nor any prop riding them
|
||||||
|
/// are drawn.
|
||||||
func geometry(at time: Double) -> FigureGeometry {
|
func geometry(at time: Double) -> FigureGeometry {
|
||||||
let yaw = orbits ? 360 * (time / Self.orbitPeriod) : 0
|
let yaw = orbits ? 360 * (time / Self.orbitPeriod) : 0
|
||||||
var geo = timeline.geometry(at: time, yawOffset: yaw)
|
var geo = timeline.geometry(at: time, yawOffset: yaw, props: props)
|
||||||
for name in hide {
|
for name in hide {
|
||||||
if let limb = FigureLimb(rawValue: name) { geo.limbs[limb] = nil }
|
if let limb = FigureLimb(rawValue: name) { geo.limbs[limb] = nil }
|
||||||
}
|
}
|
||||||
@@ -121,14 +122,14 @@ struct ExerciseFigureView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Equipment behind the figure: scene shapes and cables.
|
// Equipment behind the figure: scene shapes and cables.
|
||||||
drawBackgroundProps(&ctx, geo)
|
drawProps(&ctx, geo.propsBackground)
|
||||||
|
|
||||||
// Parts far-to-near. The head paints last (opaque), preceded by the joint-attached
|
// Parts far-to-near. The head paints last (opaque), preceded by the joint-attached
|
||||||
// equipment so bars/pads sit over the limbs but behind the face.
|
// equipment so bars/pads sit over the limbs but behind the face.
|
||||||
for part in geo.order {
|
for part in geo.order {
|
||||||
switch part {
|
switch part {
|
||||||
case "head":
|
case "head":
|
||||||
drawAttachedProps(&ctx, geo)
|
drawProps(&ctx, geo.propsForeground)
|
||||||
drawHead(&ctx, geo)
|
drawHead(&ctx, geo)
|
||||||
case "spine":
|
case "spine":
|
||||||
drawSpine(&ctx, geo)
|
drawSpine(&ctx, geo)
|
||||||
@@ -164,132 +165,39 @@ struct ExerciseFigureView: View {
|
|||||||
|
|
||||||
// MARK: Props
|
// MARK: Props
|
||||||
|
|
||||||
/// Prop joint refs → (limb, chain index): extremities are index 2, mid joints
|
/// Draws one resolved equipment layer. The solver hands back ready canvas
|
||||||
/// (elbows/knees) index 1, so equipment can ride either joint. Kept 1:1 with
|
/// primitives (`MotionSolver.resolveProps`, kept 1:1 with the reference
|
||||||
/// the reference renderer's `JOINT_LIMB` (legs now end in a foot bone, so a foot
|
/// renderer); the view only maps inks to the adaptive palette.
|
||||||
/// prop rides the ankle at index 2).
|
private func drawProps(_ ctx: inout GraphicsContext, _ prims: [PropPrimitive]) {
|
||||||
private static let propJoints: [String: (limb: FigureLimb, index: Int)] = [
|
for prim in prims {
|
||||||
"hand_r": (.armR, 2), "elbow_r": (.armR, 1),
|
switch prim {
|
||||||
"hand_l": (.armL, 2), "elbow_l": (.armL, 1),
|
case .line(let points, let width, let ink):
|
||||||
"foot_r": (.legR, 2), "knee_r": (.legR, 1),
|
stroke(&ctx, points, color: color(for: ink), width: width)
|
||||||
"foot_l": (.legL, 2), "knee_l": (.legL, 1),
|
case .poly(let points, let width, let ink):
|
||||||
]
|
// An extruded scene slab: filled and outlined, so it degenerates
|
||||||
|
// to the plain line whenever the sweep collapses edge-on.
|
||||||
/// The joint point a prop follows (plus the unit direction of the bone ending
|
guard let first = points.first else { continue }
|
||||||
/// at the first joint, for perpendicular items). Nil when any referenced limb
|
var path = Path()
|
||||||
/// isn't drawn this frame.
|
path.addLines(points)
|
||||||
private func jointAnchor(_ geo: FigureGeometry, _ ref: PropJointRef) -> (point: CGPoint, direction: CGVector)? {
|
path.addLine(to: first)
|
||||||
var points: [CGPoint] = []
|
path.closeSubpath()
|
||||||
var direction: CGVector?
|
ctx.fill(path, with: .color(color(for: ink)))
|
||||||
for name in ref.names {
|
ctx.stroke(path, with: .color(color(for: ink)),
|
||||||
guard let joint = Self.propJoints[name],
|
style: StrokeStyle(lineWidth: width, lineCap: .round, lineJoin: .round))
|
||||||
let chain = geo.limbs[joint.limb], chain.count > joint.index else { return nil }
|
case .circle(let center, let radius, let width, let fill, let ink):
|
||||||
let a = chain[joint.index - 1]
|
let rect = CGRect(x: center.x - radius, y: center.y - radius,
|
||||||
let b = chain[joint.index]
|
width: 2 * radius, height: 2 * radius)
|
||||||
points.append(b)
|
if fill {
|
||||||
if direction == nil {
|
ctx.fill(Path(ellipseIn: rect), with: .color(color(for: ink)))
|
||||||
let d = max(hypot(b.x - a.x, b.y - a.y), 1)
|
|
||||||
direction = CGVector(dx: (b.x - a.x) / d, dy: (b.y - a.y) / d)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
guard let direction, !points.isEmpty else { return nil }
|
|
||||||
let center = CGPoint(x: points.reduce(0) { $0 + $1.x } / CGFloat(points.count),
|
|
||||||
y: points.reduce(0) { $0 + $1.y } / CGFloat(points.count))
|
|
||||||
return (center, direction)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func drawBackgroundProps(_ ctx: inout GraphicsContext, _ geo: FigureGeometry) {
|
|
||||||
for prop in figure.props {
|
|
||||||
switch prop.type {
|
|
||||||
case "scene":
|
|
||||||
for shape in prop.shapes ?? [] { drawSceneShape(&ctx, shape) }
|
|
||||||
case "cable":
|
|
||||||
guard let from = prop.from, from.count == 2, let to = prop.to,
|
|
||||||
let anchor = jointAnchor(geo, to) else { continue }
|
|
||||||
stroke(&ctx, [CGPoint(x: from[0], y: from[1]), anchor.point],
|
|
||||||
color: .figureEquipment, width: prop.w ?? 2)
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Bars sit at a fixed world angle (default horizontal); dumbbells and pads
|
|
||||||
/// default to perpendicular to the lower bone. Kept 1:1 with the reference
|
|
||||||
/// renderer's `resolve_props` — change them in lockstep.
|
|
||||||
private func drawAttachedProps(_ ctx: inout GraphicsContext, _ geo: FigureGeometry) {
|
|
||||||
for prop in figure.props {
|
|
||||||
if prop.type == "roller" {
|
|
||||||
drawRoller(&ctx, geo, prop)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
let defaults: (halfLen: Double, width: Double, plateR: Double)
|
|
||||||
switch prop.type {
|
|
||||||
case "bar": defaults = (24, 4, 0)
|
|
||||||
case "dumbbell": defaults = (7, 3, 4.5)
|
|
||||||
case "pad": defaults = (8, 7, 0)
|
|
||||||
default: continue
|
|
||||||
}
|
|
||||||
guard let at = prop.at, let anchor = jointAnchor(geo, at) else { continue }
|
|
||||||
let axis: CGVector
|
|
||||||
if prop.type == "bar" || prop.angle != nil {
|
|
||||||
axis = MotionSolver.direction(prop.angle ?? 0)
|
|
||||||
} else {
|
} else {
|
||||||
axis = CGVector(dx: -anchor.direction.dy, dy: anchor.direction.dx)
|
ctx.stroke(Path(ellipseIn: rect), with: .color(color(for: ink)), lineWidth: width)
|
||||||
}
|
|
||||||
let h = prop.halfLen ?? defaults.halfLen
|
|
||||||
let a = CGPoint(x: anchor.point.x - axis.dx * h, y: anchor.point.y - axis.dy * h)
|
|
||||||
let b = CGPoint(x: anchor.point.x + axis.dx * h, y: anchor.point.y + axis.dy * h)
|
|
||||||
stroke(&ctx, [a, b], color: .figureProp, width: prop.w ?? defaults.width)
|
|
||||||
let plateR = prop.plateR ?? defaults.plateR
|
|
||||||
if plateR > 0 {
|
|
||||||
for end in [a, b] {
|
|
||||||
let rect = CGRect(x: end.x - plateR, y: end.y - plateR,
|
|
||||||
width: 2 * plateR, height: 2 * plateR)
|
|
||||||
ctx.fill(Path(ellipseIn: rect), with: .color(.figureProp))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A machine roller pad seen end-on: a disc riding the limb's lower bone near the
|
private func color(for ink: PropInk) -> Color {
|
||||||
/// joint, on the `side` (+1/−1) of the bone it presses. Kept 1:1 with the reference
|
ink == .prop ? .figureProp : .figureEquipment
|
||||||
/// renderer's `resolve_props`.
|
|
||||||
private func drawRoller(_ ctx: inout GraphicsContext, _ geo: FigureGeometry, _ prop: MotionProp) {
|
|
||||||
guard let at = prop.at, let anchor = jointAnchor(geo, at) else { return }
|
|
||||||
let r = prop.r ?? 5
|
|
||||||
let back = prop.back ?? 0
|
|
||||||
let side = prop.side ?? 1
|
|
||||||
let px = anchor.direction.dy * side, py = -anchor.direction.dx * side
|
|
||||||
let center = CGPoint(x: anchor.point.x - anchor.direction.dx * back + px * (r + 3),
|
|
||||||
y: anchor.point.y - anchor.direction.dy * back + py * (r + 3))
|
|
||||||
let rect = CGRect(x: center.x - r, y: center.y - r, width: 2 * r, height: 2 * r)
|
|
||||||
ctx.fill(Path(ellipseIn: rect), with: .color(.figureProp))
|
|
||||||
}
|
|
||||||
|
|
||||||
private func drawSceneShape(_ ctx: inout GraphicsContext, _ shape: PropSceneShape) {
|
|
||||||
let color: Color = shape.color == "prop" ? .figureProp : .figureEquipment
|
|
||||||
switch shape.kind {
|
|
||||||
case "line":
|
|
||||||
guard let pts = shape.pts else { return }
|
|
||||||
stroke(&ctx, pts.compactMap { $0.count == 2 ? CGPoint(x: $0[0], y: $0[1]) : nil },
|
|
||||||
color: color, width: shape.w ?? 4)
|
|
||||||
case "circle":
|
|
||||||
guard let c = shape.c, c.count == 2, let r = shape.r else { return }
|
|
||||||
let rect = CGRect(x: c[0] - r, y: c[1] - r, width: 2 * r, height: 2 * r)
|
|
||||||
if shape.fill ?? true {
|
|
||||||
ctx.fill(Path(ellipseIn: rect), with: .color(color))
|
|
||||||
} else {
|
|
||||||
ctx.stroke(Path(ellipseIn: rect), with: .color(color), lineWidth: shape.w ?? 3)
|
|
||||||
}
|
|
||||||
case "rect":
|
|
||||||
guard let x = shape.x, let y = shape.y, let w = shape.w, let h = shape.h else { return }
|
|
||||||
let path = Path(roundedRect: CGRect(x: x, y: y, width: w, height: h),
|
|
||||||
cornerRadius: shape.r ?? 2)
|
|
||||||
ctx.fill(path, with: .color(color))
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Near parts take the dark ink, far parts the light one; working parts swap for the
|
/// Near parts take the dark ink, far parts the light one; working parts swap for the
|
||||||
|
|||||||
@@ -140,20 +140,22 @@ enum PropJointRef: Codable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// One static shape of a `scene` prop, in canvas coordinates.
|
/// One static shape of a `scene` prop, authored in canvas coordinates in the
|
||||||
|
/// authored view, with world-space 3D form for the orbiting presentation.
|
||||||
struct PropSceneShape: Codable {
|
struct PropSceneShape: Codable {
|
||||||
let kind: String // "line" | "circle" | "rect"
|
let kind: String // "line" | "circle"
|
||||||
/// line: polyline points; `w` is the stroke width.
|
/// line: polyline points, `[x, y]` or `[x, y, z]`; `w` is the stroke width.
|
||||||
let pts: [[Double]]?
|
let pts: [[Double]]?
|
||||||
let w: Double?
|
let w: Double?
|
||||||
/// circle: center + radius; `fill` false draws an outline.
|
/// circle: center + radius; `fill` false draws an outline.
|
||||||
let c: [Double]?
|
let c: [Double]?
|
||||||
let r: Double?
|
let r: Double?
|
||||||
let fill: Bool?
|
let fill: Bool?
|
||||||
/// rect: origin + size (`w`/`h`), corner radius `r`; always filled.
|
/// The shape's depth plane (+ toward the camera in the authored view).
|
||||||
let x: Double?
|
let z: Double?
|
||||||
let y: Double?
|
/// Extrusion half-width: a line with `depth` is a slab (a seat, a platform)
|
||||||
let h: Double?
|
/// that opens into a swept quad as the camera orbits.
|
||||||
|
let depth: Double?
|
||||||
/// Optional palette override: `"prop"` for the darker attached-item gray.
|
/// Optional palette override: `"prop"` for the darker attached-item gray.
|
||||||
let color: String?
|
let color: String?
|
||||||
}
|
}
|
||||||
@@ -164,7 +166,7 @@ struct MotionProp: Codable {
|
|||||||
let type: String // "scene" | "cable" | "bar" | "dumbbell" | "pad" | "roller"
|
let type: String // "scene" | "cable" | "bar" | "dumbbell" | "pad" | "roller"
|
||||||
/// scene: the static shapes.
|
/// scene: the static shapes.
|
||||||
let shapes: [PropSceneShape]?
|
let shapes: [PropSceneShape]?
|
||||||
/// cable: fixed anchor `[x, y]` → moving joint `to`.
|
/// cable: fixed anchor `[x, y]` or `[x, y, z]` → moving joint `to`.
|
||||||
let from: [Double]?
|
let from: [Double]?
|
||||||
let to: PropJointRef?
|
let to: PropJointRef?
|
||||||
/// bar/dumbbell/pad: the joint(s) the item is centered on.
|
/// bar/dumbbell/pad: the joint(s) the item is centered on.
|
||||||
|
|||||||
@@ -150,6 +150,25 @@ struct NormalizedFrame {
|
|||||||
/// the far member draws light, behind, and nudged by the readability offset.
|
/// the far member draws light, behind, and nudged by the readability offset.
|
||||||
enum Shade { case near, far }
|
enum Shade { case near, far }
|
||||||
|
|
||||||
|
/// The two equipment inks: recessive `equipment` gray for scene shapes and cables
|
||||||
|
/// behind the figure, darker `prop` gray for joint-attached items over the limbs.
|
||||||
|
enum PropInk {
|
||||||
|
case equipment, prop
|
||||||
|
|
||||||
|
init(_ name: String?) { self = name == "prop" ? .prop : .equipment }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// One resolved equipment drawing primitive, in canvas coordinates — the shape the
|
||||||
|
/// prop layer reduces to for a single frame (mirroring the reference renderer's
|
||||||
|
/// `resolve_props` output; kept 1:1, change them in lockstep).
|
||||||
|
enum PropPrimitive {
|
||||||
|
case line(points: [CGPoint], width: Double, ink: PropInk)
|
||||||
|
/// A closed, filled, outlined polygon — an extruded scene slab. Degenerates to
|
||||||
|
/// the plain line whenever the sweep collapses edge-on (the authored view).
|
||||||
|
case poly(points: [CGPoint], width: Double, ink: PropInk)
|
||||||
|
case circle(center: CGPoint, radius: Double, width: Double, fill: Bool, ink: PropInk)
|
||||||
|
}
|
||||||
|
|
||||||
/// A pose resolved to drawable canvas points (plus the depth-sorted draw order and
|
/// A pose resolved to drawable canvas points (plus the depth-sorted draw order and
|
||||||
/// per-limb shading the view needs).
|
/// per-limb shading the view needs).
|
||||||
struct FigureGeometry {
|
struct FigureGeometry {
|
||||||
@@ -171,6 +190,10 @@ struct FigureGeometry {
|
|||||||
/// Parts far-to-near, `"head"` always last (`"spine"`, `"arm_r"`, … then `"head"`).
|
/// Parts far-to-near, `"head"` always last (`"spine"`, `"arm_r"`, … then `"head"`).
|
||||||
var order: [String]
|
var order: [String]
|
||||||
var shade: [FigureLimb: Shade]
|
var shade: [FigureLimb: Shade]
|
||||||
|
/// The equipment layer resolved for this frame: scene shapes and cables drawn
|
||||||
|
/// behind the figure, joint-attached items drawn over the limbs (before the head).
|
||||||
|
var propsBackground: [PropPrimitive] = []
|
||||||
|
var propsForeground: [PropPrimitive] = []
|
||||||
}
|
}
|
||||||
|
|
||||||
/// FK output in view space (x right, y up, z toward the camera; origin at the root).
|
/// FK output in view space (x right, y up, z toward the camera; origin at the root).
|
||||||
@@ -500,6 +523,160 @@ enum MotionSolver {
|
|||||||
floor: floor, limbs: limbs, order: order, shade: shade)
|
floor: floor, limbs: limbs, order: order, shade: shade)
|
||||||
return (resolved, geo)
|
return (resolved, geo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Props
|
||||||
|
|
||||||
|
/// Prop joint refs → (limb, chain index): extremities are index 2, mid joints
|
||||||
|
/// (elbows/knees) index 1, so equipment can ride either joint. Kept 1:1 with
|
||||||
|
/// the reference renderer's `JOINT_LIMB` (legs end in a foot bone, so a foot
|
||||||
|
/// prop rides the ankle at index 2).
|
||||||
|
private static let propJoints: [String: (limb: FigureLimb, index: Int)] = [
|
||||||
|
"hand_r": (.armR, 2), "elbow_r": (.armR, 1),
|
||||||
|
"hand_l": (.armL, 2), "elbow_l": (.armL, 1),
|
||||||
|
"foot_r": (.legR, 2), "knee_r": (.legR, 1),
|
||||||
|
"foot_l": (.legL, 2), "knee_l": (.legL, 1),
|
||||||
|
]
|
||||||
|
|
||||||
|
/// The joint point a prop follows (plus the unit direction of the bone ending
|
||||||
|
/// at the first joint, for perpendicular items). Nil when any referenced limb
|
||||||
|
/// isn't drawn this frame.
|
||||||
|
private static func jointAnchor(_ geo: FigureGeometry, _ ref: PropJointRef) -> (point: CGPoint, direction: CGVector)? {
|
||||||
|
var points: [CGPoint] = []
|
||||||
|
var direction: CGVector?
|
||||||
|
for name in ref.names {
|
||||||
|
guard let joint = propJoints[name],
|
||||||
|
let chain = geo.limbs[joint.limb], chain.count > joint.index else { return nil }
|
||||||
|
let a = chain[joint.index - 1]
|
||||||
|
let b = chain[joint.index]
|
||||||
|
points.append(b)
|
||||||
|
if direction == nil {
|
||||||
|
let d = max(hypot(b.x - a.x, b.y - a.y), 1)
|
||||||
|
direction = CGVector(dx: (b.x - a.x) / d, dy: (b.y - a.y) / d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
guard let direction, !points.isEmpty else { return nil }
|
||||||
|
let center = CGPoint(x: points.reduce(0) { $0 + $1.x } / CGFloat(points.count),
|
||||||
|
y: points.reduce(0) { $0 + $1.y } / CGFloat(points.count))
|
||||||
|
return (center, direction)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// View-space rotation carrying authored-view prop geometry to the camera
|
||||||
|
/// `yawOffset` degrees past the authored yaw: a rotation about the world-vertical
|
||||||
|
/// axis through the root anchor, conjugated by the camera elevation. Nil (the
|
||||||
|
/// identity) at offset 0, so the authored view stays bit-exact.
|
||||||
|
static func propRotation(pitch: Double, yawOffset: Double) -> Mat3? {
|
||||||
|
guard yawOffset != 0 else { return nil }
|
||||||
|
return Mat3.rotX(pitch).times(Mat3.rotY(-yawOffset)).times(Mat3.rotX(-pitch))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Props → drawable primitives for one frame: (background, foreground).
|
||||||
|
///
|
||||||
|
/// `geo` is the frame's drawn (possibly orbit-rotated) geometry, `authored` the
|
||||||
|
/// same frame at the authored camera (pass `geo` itself when not orbiting),
|
||||||
|
/// `anchor` the frame's root canvas anchor, and `rotation` the `propRotation`
|
||||||
|
/// between them. Joint positions come from `geo`; everything authored — scene
|
||||||
|
/// points, cable anchors, bar angles, pad perpendiculars, roller offsets —
|
||||||
|
/// resolves against `authored` and rotates. Kept 1:1 with the reference
|
||||||
|
/// renderer's `resolve_props` — change them in lockstep.
|
||||||
|
static func resolveProps(_ props: [MotionProp], geo: FigureGeometry, authored: FigureGeometry,
|
||||||
|
anchor: CGPoint, rotation: Mat3?) -> (bg: [PropPrimitive], fg: [PropPrimitive]) {
|
||||||
|
/// Authored canvas point (+ depth toward the camera) → drawn canvas.
|
||||||
|
func place(_ x: Double, _ y: Double, _ z: Double) -> CGPoint {
|
||||||
|
guard let rotation else { return CGPoint(x: x, y: y) }
|
||||||
|
let v = rotation.apply(Vec3(x - anchor.x, anchor.y - y, z))
|
||||||
|
return CGPoint(x: anchor.x + v.x, y: anchor.y - v.y)
|
||||||
|
}
|
||||||
|
/// Authored canvas-space direction/offset → drawn canvas (y-down).
|
||||||
|
func swing(_ vec: CGVector) -> CGVector {
|
||||||
|
guard let rotation else { return vec }
|
||||||
|
let v = rotation.apply(Vec3(vec.dx, -vec.dy, 0))
|
||||||
|
return CGVector(dx: v.x, dy: -v.y)
|
||||||
|
}
|
||||||
|
|
||||||
|
var bg: [PropPrimitive] = []
|
||||||
|
var fg: [PropPrimitive] = []
|
||||||
|
for prop in props {
|
||||||
|
switch prop.type {
|
||||||
|
case "scene":
|
||||||
|
for shape in prop.shapes ?? [] {
|
||||||
|
let ink = PropInk(shape.color)
|
||||||
|
let z = shape.z ?? 0
|
||||||
|
switch shape.kind {
|
||||||
|
case "line":
|
||||||
|
guard let pts = shape.pts else { continue }
|
||||||
|
let lifted = pts.compactMap { pt -> (x: Double, y: Double, z: Double)? in
|
||||||
|
pt.count >= 2 ? (pt[0], pt[1], z + (pt.count > 2 ? pt[2] : 0)) : nil
|
||||||
|
}
|
||||||
|
if let depth = shape.depth, depth != 0 {
|
||||||
|
// An extruded slab: the polyline swept through ±depth,
|
||||||
|
// filled and outlined so it degenerates to the plain
|
||||||
|
// line whenever the sweep collapses edge-on.
|
||||||
|
let quad = lifted.map { place($0.x, $0.y, $0.z + depth) }
|
||||||
|
+ lifted.reversed().map { place($0.x, $0.y, $0.z - depth) }
|
||||||
|
bg.append(.poly(points: quad, width: shape.w ?? 4, ink: ink))
|
||||||
|
} else {
|
||||||
|
bg.append(.line(points: lifted.map { place($0.x, $0.y, $0.z) },
|
||||||
|
width: shape.w ?? 4, ink: ink))
|
||||||
|
}
|
||||||
|
case "circle":
|
||||||
|
guard let c = shape.c, c.count == 2, let r = shape.r else { continue }
|
||||||
|
bg.append(.circle(center: place(c[0], c[1], z), radius: r,
|
||||||
|
width: shape.w ?? 3, fill: shape.fill ?? false, ink: ink))
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "cable":
|
||||||
|
guard let from = prop.from, from.count >= 2, let to = prop.to,
|
||||||
|
let end = jointAnchor(geo, to) else { continue }
|
||||||
|
let start = place(from[0], from[1], from.count > 2 ? from[2] : 0)
|
||||||
|
bg.append(.line(points: [start, end.point], width: prop.w ?? 2, ink: .equipment))
|
||||||
|
case "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.
|
||||||
|
// The offset resolves along the authored-view bone, then rotates.
|
||||||
|
guard let at = prop.at, let now = jointAnchor(geo, at),
|
||||||
|
let auth = jointAnchor(authored, at) else { continue }
|
||||||
|
let r = prop.r ?? 5
|
||||||
|
let back = prop.back ?? 0
|
||||||
|
let side = prop.side ?? 1
|
||||||
|
let d = auth.direction
|
||||||
|
let px = d.dy * side, py = -d.dx * side
|
||||||
|
let off = swing(CGVector(dx: -d.dx * back + px * (r + 3),
|
||||||
|
dy: -d.dy * back + py * (r + 3)))
|
||||||
|
fg.append(.circle(center: CGPoint(x: now.point.x + off.dx, y: now.point.y + off.dy),
|
||||||
|
radius: r, width: 3, fill: true, ink: .prop))
|
||||||
|
case "bar", "dumbbell", "pad":
|
||||||
|
let defaults: (halfLen: Double, width: Double, plateR: Double) = switch prop.type {
|
||||||
|
case "bar": (24, 4, 0)
|
||||||
|
case "dumbbell": (7, 3, 4.5)
|
||||||
|
default: (8, 7, 0)
|
||||||
|
}
|
||||||
|
guard let at = prop.at, let now = jointAnchor(geo, at),
|
||||||
|
let auth = jointAnchor(authored, at) else { continue }
|
||||||
|
let axis: CGVector
|
||||||
|
if prop.type == "bar" || prop.angle != nil {
|
||||||
|
axis = direction(prop.angle ?? 0) // fixed authored-view angle
|
||||||
|
} else {
|
||||||
|
axis = CGVector(dx: -auth.direction.dy, dy: auth.direction.dx)
|
||||||
|
}
|
||||||
|
let u = swing(axis) // foreshortens under orbit
|
||||||
|
let h = prop.halfLen ?? defaults.halfLen
|
||||||
|
let a = CGPoint(x: now.point.x - u.dx * h, y: now.point.y - u.dy * h)
|
||||||
|
let b = CGPoint(x: now.point.x + u.dx * h, y: now.point.y + u.dy * h)
|
||||||
|
fg.append(.line(points: [a, b], width: prop.w ?? defaults.width, ink: .prop))
|
||||||
|
let plateR = prop.plateR ?? defaults.plateR
|
||||||
|
if plateR > 0 {
|
||||||
|
for end in [a, b] {
|
||||||
|
fg.append(.circle(center: end, radius: plateR, width: 3, fill: true, ink: .prop))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (bg, fg)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The full looping animation for one motion: key frames resolved to anatomical angles,
|
/// The full looping animation for one motion: key frames resolved to anatomical angles,
|
||||||
@@ -566,13 +743,28 @@ struct MotionTimeline {
|
|||||||
/// targets in the *authored* view, so the pose resolves there first and the posed
|
/// targets in the *authored* view, so the pose resolves there first and the posed
|
||||||
/// body is then rotated; re-pinning in a rotated view would glue hands to screen
|
/// body is then rotated; re-pinning in a rotated view would glue hands to screen
|
||||||
/// points that no longer correspond to anything (arms visibly "stuck" mid-orbit).
|
/// points that no longer correspond to anything (arms visibly "stuck" mid-orbit).
|
||||||
func geometry(at time: Double, yawOffset: Double = 0) -> FigureGeometry {
|
/// `props` are resolved into the geometry's primitive layers the same way: joint
|
||||||
|
/// anchors follow the rotated figure, while authored constructs (scene points,
|
||||||
|
/// cable anchors, bar angles, pad perpendiculars) resolve in the authored view
|
||||||
|
/// and rotate about the root anchor's vertical axis.
|
||||||
|
func geometry(at time: Double, yawOffset: Double = 0, props: [MotionProp] = []) -> FigureGeometry {
|
||||||
let frame = frame(at: time)
|
let frame = frame(at: time)
|
||||||
guard yawOffset != 0 else {
|
var geo: FigureGeometry
|
||||||
return MotionSolver.frameGeometry(frame, prof: profile, cam: cam, pitch: pitch, mat: mat).1
|
let authored: FigureGeometry
|
||||||
}
|
if yawOffset == 0 {
|
||||||
var posed = MotionSolver.frameGeometry(frame, prof: profile, cam: cam, pitch: pitch).0
|
geo = MotionSolver.frameGeometry(frame, prof: profile, cam: cam, pitch: pitch, mat: mat).1
|
||||||
|
authored = geo
|
||||||
|
} else {
|
||||||
|
let (posedFrame, authoredGeo) = MotionSolver.frameGeometry(frame, prof: profile, cam: cam, pitch: pitch)
|
||||||
|
authored = authoredGeo
|
||||||
|
var posed = posedFrame
|
||||||
posed.pins = [:]
|
posed.pins = [:]
|
||||||
return MotionSolver.frameGeometry(posed, prof: profile, cam: cam + yawOffset, pitch: pitch, mat: mat).1
|
geo = MotionSolver.frameGeometry(posed, prof: profile, cam: cam + yawOffset, pitch: pitch, mat: mat).1
|
||||||
|
}
|
||||||
|
guard !props.isEmpty else { return geo }
|
||||||
|
let rotation = MotionSolver.propRotation(pitch: pitch, yawOffset: yawOffset)
|
||||||
|
(geo.propsBackground, geo.propsForeground) = MotionSolver.resolveProps(
|
||||||
|
props, geo: geo, authored: authored, anchor: frame.rootPos, rotation: rotation)
|
||||||
|
return geo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,11 @@
|
|||||||
"working": ["spine"],
|
"working": ["spine"],
|
||||||
"props": [
|
"props": [
|
||||||
{"type": "scene", "shapes": [
|
{"type": "scene", "shapes": [
|
||||||
{"kind": "line", "pts": [[120, 128], [114, 62]], "w": 8},
|
{"kind": "line", "pts": [[120, 128], [114, 62]], "w": 8, "depth": 7},
|
||||||
{"kind": "line", "pts": [[106, 130], [150, 130]], "w": 8},
|
{"kind": "line", "pts": [[106, 130], [150, 130]], "w": 8, "depth": 8},
|
||||||
{"kind": "line", "pts": [[122, 132], [122, 150]], "w": 4},
|
{"kind": "line", "pts": [[122, 132], [122, 150]], "w": 4},
|
||||||
{"kind": "line", "pts": [[102, 150], [150, 150]], "w": 4},
|
{"kind": "line", "pts": [[102, 150], [150, 150]], "w": 4, "depth": 5},
|
||||||
{"kind": "line", "pts": [[156, 149], [190, 149]], "w": 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}
|
{"type": "pad", "at": ["hand_r", "hand_l"], "angle": 90, "halfLen": 14, "w": 6}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
"working": ["leg_r", "leg_l"],
|
"working": ["leg_r", "leg_l"],
|
||||||
"props": [
|
"props": [
|
||||||
{"type": "scene", "shapes": [
|
{"type": "scene", "shapes": [
|
||||||
{"kind": "rect", "x": 152, "y": 56, "w": 16, "h": 72, "r": 5},
|
{"kind": "line", "pts": [[160, 64], [160, 120]], "w": 16, "z": -14, "depth": 6},
|
||||||
{"kind": "rect", "x": 130, "y": 126, "w": 60, "h": 10, "r": 3},
|
{"kind": "line", "pts": [[135, 131], [185, 131]], "w": 10, "depth": 12},
|
||||||
{"kind": "line", "pts": [[160, 136], [160, 150]], "w": 6}
|
{"kind": "line", "pts": [[160, 136], [160, 150]], "w": 6}
|
||||||
]},
|
]},
|
||||||
{"type": "pad", "at": ["knee_r"], "angle": 90, "halfLen": 13, "w": 6},
|
{"type": "pad", "at": ["knee_r"], "angle": 90, "halfLen": 13, "w": 6},
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
"working": ["leg_r", "leg_l"],
|
"working": ["leg_r", "leg_l"],
|
||||||
"props": [
|
"props": [
|
||||||
{"type": "scene", "shapes": [
|
{"type": "scene", "shapes": [
|
||||||
{"kind": "rect", "x": 152, "y": 56, "w": 16, "h": 72, "r": 5},
|
{"kind": "line", "pts": [[160, 64], [160, 120]], "w": 16, "z": -14, "depth": 6},
|
||||||
{"kind": "rect", "x": 130, "y": 126, "w": 60, "h": 10, "r": 3},
|
{"kind": "line", "pts": [[135, 131], [185, 131]], "w": 10, "depth": 12},
|
||||||
{"kind": "line", "pts": [[160, 136], [160, 150]], "w": 6}
|
{"kind": "line", "pts": [[160, 136], [160, 150]], "w": 6}
|
||||||
]},
|
]},
|
||||||
{"type": "pad", "at": ["knee_r"], "angle": 90, "halfLen": 13, "w": 6},
|
{"type": "pad", "at": ["knee_r"], "angle": 90, "halfLen": 13, "w": 6},
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
"working": ["arm_r", "arm_l"],
|
"working": ["arm_r", "arm_l"],
|
||||||
"props": [
|
"props": [
|
||||||
{"type": "scene", "shapes": [
|
{"type": "scene", "shapes": [
|
||||||
{"kind": "line", "pts": [[116, 134], [150, 134]], "w": 8},
|
{"kind": "line", "pts": [[116, 134], [150, 134]], "w": 8, "depth": 8},
|
||||||
{"kind": "line", "pts": [[136, 52], [164, 70]], "w": 8},
|
{"kind": "line", "pts": [[136, 52], [164, 70]], "w": 8, "depth": 8},
|
||||||
{"kind": "line", "pts": [[164, 70], [164, 128]], "w": 5}
|
{"kind": "line", "pts": [[164, 70], [164, 128]], "w": 5}
|
||||||
]},
|
]},
|
||||||
{"type": "bar", "at": ["hand_r", "hand_l"], "angle": 90, "halfLen": 6}
|
{"type": "bar", "at": ["hand_r", "hand_l"], "angle": 90, "halfLen": 6}
|
||||||
|
|||||||
@@ -4,10 +4,10 @@
|
|||||||
"working": ["leg_r", "leg_l"],
|
"working": ["leg_r", "leg_l"],
|
||||||
"props": [
|
"props": [
|
||||||
{"type": "scene", "shapes": [
|
{"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": [[138, 128], [138, 150]], "w": 5},
|
||||||
{"kind": "line", "pts": [[120, 150], [156, 150]], "w": 5},
|
{"kind": "line", "pts": [[120, 150], [156, 150]], "w": 5, "depth": 5},
|
||||||
{"kind": "rect", "x": 198, "y": 147, "w": 34, "h": 6, "r": 1},
|
{"kind": "line", "pts": [[201, 150], [229, 150]], "w": 6, "depth": 7},
|
||||||
{"kind": "line", "pts": [[183, 62], [183, 122]], "w": 4},
|
{"kind": "line", "pts": [[183, 62], [183, 122]], "w": 4},
|
||||||
{"kind": "line", "pts": [[164, 76], [183, 76]], "w": 3, "color": "prop"}
|
{"kind": "line", "pts": [[164, 76], [183, 76]], "w": 3, "color": "prop"}
|
||||||
]},
|
]},
|
||||||
|
|||||||
@@ -4,10 +4,10 @@
|
|||||||
"working": ["arm_r", "arm_l"],
|
"working": ["arm_r", "arm_l"],
|
||||||
"props": [
|
"props": [
|
||||||
{"type": "scene", "shapes": [
|
{"type": "scene", "shapes": [
|
||||||
{"kind": "line", "pts": [[114, 132], [86, 40]], "w": 8},
|
{"kind": "line", "pts": [[114, 132], [86, 40]], "w": 8, "depth": 8},
|
||||||
{"kind": "line", "pts": [[106, 133], [146, 133]], "w": 8},
|
{"kind": "line", "pts": [[106, 133], [146, 133]], "w": 8, "depth": 8},
|
||||||
{"kind": "line", "pts": [[122, 135], [122, 151]], "w": 5},
|
{"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}
|
{"type": "bar", "at": ["hand_r", "hand_l"], "angle": 90, "halfLen": 8}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"working": ["arm_r", "arm_l"],
|
"working": ["arm_r", "arm_l"],
|
||||||
"props": [
|
"props": [
|
||||||
{"type": "scene", "shapes": [
|
{"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": "line", "pts": [[144, 132], [144, 150]], "w": 5},
|
||||||
{"kind": "circle", "c": [272, 19], "r": 4, "fill": true, "color": "prop"}
|
{"kind": "circle", "c": [272, 19], "r": 4, "fill": true, "color": "prop"}
|
||||||
]},
|
]},
|
||||||
|
|||||||
@@ -4,12 +4,12 @@
|
|||||||
"working": ["leg_r", "leg_l"],
|
"working": ["leg_r", "leg_l"],
|
||||||
"props": [
|
"props": [
|
||||||
{"type": "scene", "shapes": [
|
{"type": "scene", "shapes": [
|
||||||
{"kind": "line", "pts": [[110, 122], [86, 48]], "w": 9},
|
{"kind": "line", "pts": [[110, 122], [86, 48]], "w": 9, "depth": 8},
|
||||||
{"kind": "line", "pts": [[104, 124], [166, 120]], "w": 9},
|
{"kind": "line", "pts": [[104, 124], [166, 120]], "w": 9, "depth": 8},
|
||||||
{"kind": "line", "pts": [[130, 105], [158, 106]], "w": 7},
|
{"kind": "line", "pts": [[130, 105], [158, 106]], "w": 7, "depth": 7},
|
||||||
{"kind": "line", "pts": [[108, 126], [108, 150]], "w": 5},
|
{"kind": "line", "pts": [[108, 126], [108, 150]], "w": 5},
|
||||||
{"kind": "line", "pts": [[160, 124], [160, 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"}
|
{"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}
|
{"type": "roller", "at": ["foot_r", "foot_l"], "side": -1, "r": 5, "back": 3}
|
||||||
|
|||||||
@@ -4,11 +4,11 @@
|
|||||||
"working": ["leg_r", "leg_l"],
|
"working": ["leg_r", "leg_l"],
|
||||||
"props": [
|
"props": [
|
||||||
{"type": "scene", "shapes": [
|
{"type": "scene", "shapes": [
|
||||||
{"kind": "line", "pts": [[110, 122], [86, 48]], "w": 9},
|
{"kind": "line", "pts": [[110, 122], [86, 48]], "w": 9, "depth": 8},
|
||||||
{"kind": "line", "pts": [[104, 124], [166, 120]], "w": 9},
|
{"kind": "line", "pts": [[104, 124], [166, 120]], "w": 9, "depth": 8},
|
||||||
{"kind": "line", "pts": [[108, 126], [108, 150]], "w": 5},
|
{"kind": "line", "pts": [[108, 126], [108, 150]], "w": 5},
|
||||||
{"kind": "line", "pts": [[160, 124], [160, 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"}
|
{"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}
|
{"type": "roller", "at": ["foot_r", "foot_l"], "side": 1, "r": 5, "back": 7}
|
||||||
|
|||||||
@@ -4,10 +4,10 @@
|
|||||||
"working": ["leg_r", "leg_l"],
|
"working": ["leg_r", "leg_l"],
|
||||||
"props": [
|
"props": [
|
||||||
{"type": "scene", "shapes": [
|
{"type": "scene", "shapes": [
|
||||||
{"kind": "line", "pts": [[134, 123], [96, 36]], "w": 9},
|
{"kind": "line", "pts": [[134, 123], [96, 36]], "w": 9, "depth": 8},
|
||||||
{"kind": "line", "pts": [[126, 127], [156, 122]], "w": 9},
|
{"kind": "line", "pts": [[126, 127], [156, 122]], "w": 9, "depth": 8},
|
||||||
{"kind": "line", "pts": [[138, 130], [138, 150]], "w": 5},
|
{"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": "line", "pts": [[142, 90], [142, 78]], "w": 3},
|
||||||
{"kind": "circle", "c": [142, 77], "r": 3.5, "fill": true, "color": "prop"}
|
{"kind": "circle", "c": [142, 77], "r": 3.5, "fill": true, "color": "prop"}
|
||||||
]},
|
]},
|
||||||
|
|||||||
@@ -5,9 +5,9 @@
|
|||||||
"working": ["spine"],
|
"working": ["spine"],
|
||||||
"props": [
|
"props": [
|
||||||
{"type": "scene", "shapes": [
|
{"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": [[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}
|
{"type": "bar", "at": ["hand_r", "hand_l"], "angle": 90, "halfLen": 14}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -4,9 +4,9 @@
|
|||||||
"working": ["arm_r", "arm_l"],
|
"working": ["arm_r", "arm_l"],
|
||||||
"props": [
|
"props": [
|
||||||
{"type": "scene", "shapes": [
|
{"type": "scene", "shapes": [
|
||||||
{"kind": "line", "pts": [[122, 132], [156, 132]], "w": 8},
|
{"kind": "line", "pts": [[122, 132], [156, 132]], "w": 8, "depth": 8},
|
||||||
{"kind": "line", "pts": [[159, 46], [157, 96]], "w": 8},
|
{"kind": "line", "pts": [[159, 46], [157, 96]], "w": 8, "depth": 7},
|
||||||
{"kind": "line", "pts": [[176, 150], [206, 150]], "w": 5}
|
{"kind": "line", "pts": [[176, 150], [206, 150]], "w": 5, "depth": 6}
|
||||||
]},
|
]},
|
||||||
{"type": "cable", "from": [244, 120], "to": ["hand_r", "hand_l"]},
|
{"type": "cable", "from": [244, 120], "to": ["hand_r", "hand_l"]},
|
||||||
{"type": "bar", "at": ["hand_r", "hand_l"], "angle": 90, "halfLen": 7}
|
{"type": "bar", "at": ["hand_r", "hand_l"], "angle": 90, "halfLen": 7}
|
||||||
|
|||||||
@@ -4,10 +4,10 @@
|
|||||||
"working": ["arm_r", "arm_l"],
|
"working": ["arm_r", "arm_l"],
|
||||||
"props": [
|
"props": [
|
||||||
{"type": "scene", "shapes": [
|
{"type": "scene", "shapes": [
|
||||||
{"kind": "line", "pts": [[116, 140], [88, 50]], "w": 8},
|
{"kind": "line", "pts": [[116, 140], [88, 50]], "w": 8, "depth": 8},
|
||||||
{"kind": "line", "pts": [[110, 141], [148, 141]], "w": 8},
|
{"kind": "line", "pts": [[110, 141], [148, 141]], "w": 8, "depth": 8},
|
||||||
{"kind": "line", "pts": [[126, 143], [126, 151]], "w": 5},
|
{"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}
|
{"type": "bar", "at": ["hand_r", "hand_l"], "halfLen": 10}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -4,10 +4,10 @@
|
|||||||
"working": ["arm_r", "arm_l"],
|
"working": ["arm_r", "arm_l"],
|
||||||
"props": [
|
"props": [
|
||||||
{"type": "scene", "shapes": [
|
{"type": "scene", "shapes": [
|
||||||
{"kind": "line", "pts": [[116, 134], [92, 42]], "w": 8},
|
{"kind": "line", "pts": [[116, 134], [92, 42]], "w": 8, "depth": 8},
|
||||||
{"kind": "line", "pts": [[108, 135], [146, 135]], "w": 8},
|
{"kind": "line", "pts": [[108, 135], [146, 135]], "w": 8, "depth": 8},
|
||||||
{"kind": "line", "pts": [[124, 137], [124, 151]], "w": 5},
|
{"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}
|
{"type": "bar", "at": ["hand_r", "hand_l"], "angle": 90, "halfLen": 6}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -114,7 +114,9 @@ struct ExerciseMotionTests {
|
|||||||
|
|
||||||
/// The projected-geometry ground truth: for every exercise and key frame, the draw
|
/// The projected-geometry ground truth: for every exercise and key frame, the draw
|
||||||
/// order and near/far shading must match exactly and every point land within 0.5 px
|
/// order and near/far shading must match exactly and every point land within 0.5 px
|
||||||
/// of the reference, plus the mid-tween sample and (Bird Dog) two orbit-camera views.
|
/// of the reference, plus the mid-tween sample and orbit-camera views taken through
|
||||||
|
/// the presentation path — resolve at the authored camera, clear the pins, rotate
|
||||||
|
/// the posed body — with the equipment layer resolved into rotated primitives.
|
||||||
@Test func figureFixturesMatchReference() throws {
|
@Test func figureFixturesMatchReference() throws {
|
||||||
let bundle = Bundle(for: FigureFixtureMarker.self)
|
let bundle = Bundle(for: FigureFixtureMarker.self)
|
||||||
let url = try #require(
|
let url = try #require(
|
||||||
@@ -143,9 +145,20 @@ struct ExerciseMotionTests {
|
|||||||
expectMatch(MotionSolver.frameGeometry(mid, prof: profile, cam: cam).1,
|
expectMatch(MotionSolver.frameGeometry(mid, prof: profile, cam: cam).1,
|
||||||
exercise.tween.sample, "\(exercise.name) tween")
|
exercise.tween.sample, "\(exercise.name) tween")
|
||||||
|
|
||||||
for orbit in exercise.orbit ?? [] {
|
guard let orbits = exercise.orbit, !orbits.isEmpty else { continue }
|
||||||
expectMatch(MotionSolver.frameGeometry(norms[0], prof: profile, cam: orbit.yaw).1,
|
let (posedFrame, authored) = MotionSolver.frameGeometry(norms[0], prof: profile, cam: cam)
|
||||||
orbit.sample, "\(exercise.name) orbit \(orbit.yaw)")
|
var posed = posedFrame
|
||||||
|
posed.pins = [:]
|
||||||
|
for orbit in orbits {
|
||||||
|
let geo = MotionSolver.frameGeometry(posed, prof: profile, cam: orbit.yaw).1
|
||||||
|
expectMatch(geo, orbit.sample, "\(exercise.name) orbit \(orbit.yaw)")
|
||||||
|
guard let propFixture = orbit.sample.props else { continue }
|
||||||
|
let rotation = MotionSolver.propRotation(pitch: fixtures.pitch, yawOffset: orbit.yaw - cam)
|
||||||
|
let (bg, fg) = MotionSolver.resolveProps(
|
||||||
|
resources.motion.props ?? [], geo: geo, authored: authored,
|
||||||
|
anchor: norms[0].rootPos, rotation: rotation)
|
||||||
|
expectPrims(bg, propFixture.bg, "\(exercise.name) orbit \(orbit.yaw) bg")
|
||||||
|
expectPrims(fg, propFixture.fg, "\(exercise.name) orbit \(orbit.yaw) fg")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -158,6 +171,7 @@ private final class FigureFixtureMarker {}
|
|||||||
|
|
||||||
private struct FigureFixtures: Decodable {
|
private struct FigureFixtures: Decodable {
|
||||||
let profile: String
|
let profile: String
|
||||||
|
let pitch: Double
|
||||||
let exercises: [FixtureExercise]
|
let exercises: [FixtureExercise]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,13 +196,30 @@ private struct FixtureSample: Decodable {
|
|||||||
let armL: [[Double]]
|
let armL: [[Double]]
|
||||||
let legR: [[Double]]
|
let legR: [[Double]]
|
||||||
let legL: [[Double]]
|
let legL: [[Double]]
|
||||||
|
let props: FixtureProps?
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey {
|
enum CodingKeys: String, CodingKey {
|
||||||
case order, shade, spine, head, nose
|
case order, shade, spine, head, nose, props
|
||||||
case armR = "arm_r", armL = "arm_l", legR = "leg_r", legL = "leg_l"
|
case armR = "arm_r", armL = "arm_l", legR = "leg_r", legL = "leg_l"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private struct FixtureProps: Decodable {
|
||||||
|
let bg: [FixturePrim]
|
||||||
|
let fg: [FixturePrim]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// One reference prop primitive: a line/poly (`pts`) or a circle (`c` + `r`).
|
||||||
|
private struct FixturePrim: Decodable {
|
||||||
|
let kind: String
|
||||||
|
let pts: [[Double]]?
|
||||||
|
let c: [Double]?
|
||||||
|
let r: Double?
|
||||||
|
let w: Double?
|
||||||
|
let fill: Bool?
|
||||||
|
let color: String
|
||||||
|
}
|
||||||
|
|
||||||
private func expectClose(_ point: CGPoint, _ expected: [Double], _ label: String) {
|
private func expectClose(_ point: CGPoint, _ expected: [Double], _ label: String) {
|
||||||
#expect(abs(Double(point.x) - expected[0]) < 0.5 && abs(Double(point.y) - expected[1]) < 0.5,
|
#expect(abs(Double(point.x) - expected[0]) < 0.5 && abs(Double(point.y) - expected[1]) < 0.5,
|
||||||
"\(label): got (\(point.x), \(point.y)) expected \(expected)")
|
"\(label): got (\(point.x), \(point.y)) expected \(expected)")
|
||||||
@@ -227,3 +258,37 @@ private func expectMatch(_ geo: FigureGeometry, _ fixture: FixtureSample, _ labe
|
|||||||
expectChain(geo.limbs[.legR], fixture.legR, "\(label).leg_r")
|
expectChain(geo.limbs[.legR], fixture.legR, "\(label).leg_r")
|
||||||
expectChain(geo.limbs[.legL], fixture.legL, "\(label).leg_l")
|
expectChain(geo.limbs[.legL], fixture.legL, "\(label).leg_l")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// One resolved equipment layer against its reference: same primitive kinds and inks
|
||||||
|
/// in the same order, every point within 0.5 px, radii and widths matching.
|
||||||
|
private func expectPrims(_ prims: [PropPrimitive], _ fixtures: [FixturePrim], _ label: String) {
|
||||||
|
guard prims.count == fixtures.count else {
|
||||||
|
Issue.record("\(label): \(prims.count) primitives, expected \(fixtures.count)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for (index, (prim, fixture)) in zip(prims, fixtures).enumerated() {
|
||||||
|
let tag = "\(label)[\(index)]"
|
||||||
|
switch prim {
|
||||||
|
case .line(let points, let width, let ink):
|
||||||
|
#expect(fixture.kind == "line", "\(tag) kind")
|
||||||
|
expectChain(points, fixture.pts ?? [], tag)
|
||||||
|
#expect(width == (fixture.w ?? 4), "\(tag) width")
|
||||||
|
expectInk(ink, fixture.color, tag)
|
||||||
|
case .poly(let points, let width, let ink):
|
||||||
|
#expect(fixture.kind == "poly", "\(tag) kind")
|
||||||
|
expectChain(points, fixture.pts ?? [], tag)
|
||||||
|
#expect(width == (fixture.w ?? 4), "\(tag) width")
|
||||||
|
expectInk(ink, fixture.color, tag)
|
||||||
|
case .circle(let center, let radius, _, let fill, let ink):
|
||||||
|
#expect(fixture.kind == "circle", "\(tag) kind")
|
||||||
|
expectClose(center, fixture.c ?? [], tag)
|
||||||
|
#expect(radius == fixture.r, "\(tag) radius")
|
||||||
|
#expect(fill == (fixture.fill ?? false), "\(tag) fill")
|
||||||
|
expectInk(ink, fixture.color, tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func expectInk(_ ink: PropInk, _ expected: String, _ label: String) {
|
||||||
|
#expect((ink == .prop ? "prop" : "equipment") == expected, "\(label) ink")
|
||||||
|
}
|
||||||
|
|||||||