Draw both arms on the supine motions and retire the hide list

Hollow Body Hold, Leg Raises, and Reverse Crunch hid the far arm
because it was never authored - a leftover from the planar rig. Both
arms are now posed, so the far arm reads as the standard light member
behind the near one, the same visual language every other exercise
uses, and the figures stay truthful from any viewpoint. With no
remaining users, the hide mechanism is deleted from both renderers,
the motion schema, and the docs.

Claude-Session: https://claude.ai/code/session_01HJDQQDA9QdP8zByg43H5v3
This commit is contained in:
2026-07-06 22:19:23 -04:00
parent 81186c51b1
commit b8c3b326c1
25 changed files with 35 additions and 45 deletions
@@ -1,6 +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>Hollow Body Hold</title> <title>Hollow Body Hold</title>
<path d="M 28.0 161.1 L 288.9 161.1 L 288.9 150.7 L 28.0 150.7 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/> <path d="M 28.0 161.1 L 288.9 161.1 L 288.9 150.7 L 28.0 150.7 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
<path id="arm_l" d="M 105.9 148.1 L 75.9 148.1 L 45.9 148.1" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 190.9 148.8 L 236.9 148.8 L 276.9 148.8 L 276.9 137.9" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/> <path id="leg_l" d="M 190.9 148.8 L 236.9 148.8 L 276.9 148.8 L 276.9 137.9" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="girdle" d="M 105.9 148.1 L 100.0 148.0 L 100.0 149.9" stroke="#0d9488" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/> <path id="girdle" d="M 105.9 148.1 L 100.0 148.0 L 100.0 149.9" stroke="#0d9488" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="pelvisBar" d="M 190.9 148.8 L 185.0 148.0 L 185.0 149.2" stroke="#0d9488" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/> <path id="pelvisBar" d="M 190.9 148.8 L 185.0 148.0 L 185.0 149.2" stroke="#0d9488" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

@@ -1,6 +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>Hollow Body Hold</title> <title>Hollow Body Hold</title>
<path d="M 28.0 161.1 L 288.9 161.1 L 288.9 150.7 L 28.0 150.7 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/> <path d="M 28.0 161.1 L 288.9 161.1 L 288.9 150.7 L 28.0 150.7 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
<path id="arm_l" d="M 108.7 129.5 L 81.5 117.0 L 54.3 104.5" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 190.9 148.8 L 235.9 139.3 L 275.0 131.1 L 272.7 120.5" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/> <path id="leg_l" d="M 190.9 148.8 L 235.9 139.3 L 275.0 131.1 L 272.7 120.5" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="girdle" d="M 108.7 129.5 L 102.8 129.4 L 102.8 131.3" stroke="#0d9488" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/> <path id="girdle" d="M 108.7 129.5 L 102.8 129.4 L 102.8 131.3" stroke="#0d9488" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="pelvisBar" d="M 190.9 148.8 L 185.0 148.0 L 185.0 149.2" stroke="#0d9488" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/> <path id="pelvisBar" d="M 190.9 148.8 L 185.0 148.0 L 185.0 149.2" stroke="#0d9488" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

@@ -2,7 +2,6 @@
"name": "Hollow Body Hold", "name": "Hollow Body Hold",
"primary": 2, "primary": 2,
"working": ["spine"], "working": ["spine"],
"hide": ["arm_l"],
"frames": [ "frames": [
{ {
"hold": 0.4, "tween": 0.8, "hold": 0.4, "tween": 0.8,
@@ -10,6 +9,7 @@
"spine": [0, 0], "spine": [0, 0],
"neck": 12, "head": -12, "neck": 12, "head": -12,
"shoulder_r": 180, "elbow_r": 0, "shoulder_r": 180, "elbow_r": 0,
"shoulder_l": 180, "elbow_l": 0,
"hip_r": 0, "knee_r": 0, "hip_r": 0, "knee_r": 0,
"hip_l": 0, "knee_l": 0 "hip_l": 0, "knee_l": 0
}, },
@@ -19,6 +19,7 @@
"spine": [0, 14], "spine": [0, 14],
"neck": 20, "neck": 20,
"shoulder_r": 175, "elbow_r": 0, "shoulder_r": 175, "elbow_r": 0,
"shoulder_l": 175, "elbow_l": 0,
"hip_r": 18, "knee_r": 0, "hip_r": 18, "knee_r": 0,
"hip_l": 18, "knee_l": 0 "hip_l": 18, "knee_l": 0
} }
Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

After

Width:  |  Height:  |  Size: 124 KiB

@@ -1,6 +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>Hollow Body Hold</title> <title>Hollow Body Hold</title>
<path d="M 28.0 161.1 L 288.9 161.1 L 288.9 150.7 L 28.0 150.7 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/> <path d="M 28.0 161.1 L 288.9 161.1 L 288.9 150.7 L 28.0 150.7 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
<path id="arm_l" d="M 108.7 129.5 L 81.5 117.0 L 54.3 104.5" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 190.9 148.8 L 235.9 139.3 L 275.0 131.1 L 272.7 120.5" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/> <path id="leg_l" d="M 190.9 148.8 L 235.9 139.3 L 275.0 131.1 L 272.7 120.5" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="girdle" d="M 108.7 129.5 L 102.8 129.4 L 102.8 131.3" stroke="#0d9488" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/> <path id="girdle" d="M 108.7 129.5 L 102.8 129.4 L 102.8 131.3" stroke="#0d9488" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="pelvisBar" d="M 190.9 148.8 L 185.0 148.0 L 185.0 149.2" stroke="#0d9488" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/> <path id="pelvisBar" d="M 190.9 148.8 L 185.0 148.0 L 185.0 149.2" stroke="#0d9488" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

@@ -6,6 +6,7 @@
<path id="pelvisBar" d="M 190.9 148.8 L 185.0 148.0 L 185.0 149.2" stroke="#3a3f4b" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/> <path id="pelvisBar" d="M 190.9 148.8 L 185.0 148.0 L 185.0 149.2" stroke="#3a3f4b" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 185.0 148.0 Q 141.5 148.0 100.0 148.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/> <path id="spine" d="M 185.0 148.0 Q 141.5 148.0 100.0 148.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 190.9 148.8 L 213.9 109.5 L 233.9 75.4 L 224.4 70.0" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/> <path id="leg_l" d="M 190.9 148.8 L 213.9 109.5 L 233.9 75.4 L 224.4 70.0" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 100.0 149.9 L 130.0 150.9 L 160.0 152.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 185.0 149.2 L 208.0 110.0 L 228.0 75.9 L 218.5 70.5" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/> <path id="leg_r" d="M 185.0 149.2 L 208.0 110.0 L 228.0 75.9 L 218.5 70.5" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="80.4" cy="143.9" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/> <circle id="head" cx="80.4" cy="143.9" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<line id="nose" x1="80.4" y1="133.9" x2="80.4" y2="127.0" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/> <line id="nose" x1="80.4" y1="133.9" x2="80.4" y2="127.0" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

@@ -7,6 +7,7 @@
<path id="pelvisBar" d="M 190.9 148.8 L 185.0 148.0 L 185.0 149.2" stroke="#3a3f4b" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/> <path id="pelvisBar" d="M 190.9 148.8 L 185.0 148.0 L 185.0 149.2" stroke="#3a3f4b" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 185.0 148.0 Q 141.5 148.0 100.0 148.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/> <path id="spine" d="M 185.0 148.0 Q 141.5 148.0 100.0 148.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 185.0 149.2 L 230.6 142.9 L 270.2 137.4 L 268.6 126.7" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/> <path id="leg_r" d="M 185.0 149.2 L 230.6 142.9 L 270.2 137.4 L 268.6 126.7" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 100.0 149.9 L 130.0 150.9 L 160.0 152.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="80.4" cy="143.9" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/> <circle id="head" cx="80.4" cy="143.9" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<line id="nose" x1="80.4" y1="133.9" x2="80.4" y2="127.0" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/> <line id="nose" x1="80.4" y1="133.9" x2="80.4" y2="127.0" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
<g font-family="-apple-system, Helvetica, sans-serif" font-size="11" fill="#6b7180"> <g font-family="-apple-system, Helvetica, sans-serif" font-size="11" fill="#6b7180">

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

+2 -1
View File
@@ -2,13 +2,13 @@
"name": "Leg Raises", "name": "Leg Raises",
"primary": 1, "primary": 1,
"working": ["leg_r", "leg_l"], "working": ["leg_r", "leg_l"],
"hide": ["arm_r"],
"frames": [ "frames": [
{ {
"hold": 0.5, "tween": 1.4, "hold": 0.5, "tween": 1.4,
"root": {"pos": [185, 148], "pitch": -90}, "root": {"pos": [185, 148], "pitch": -90},
"spine": [0, 0], "spine": [0, 0],
"neck": 12, "head": -12, "neck": 12, "head": -12,
"shoulder_r": -2, "elbow_r": 0,
"shoulder_l": -2, "elbow_l": 0, "shoulder_l": -2, "elbow_l": 0,
"hip_r": 60, "knee_r": 0, "hip_r": 60, "knee_r": 0,
"hip_l": 60, "knee_l": 0 "hip_l": 60, "knee_l": 0
@@ -18,6 +18,7 @@
"root": {"pos": [185, 148], "pitch": -90}, "root": {"pos": [185, 148], "pitch": -90},
"spine": [0, 0], "spine": [0, 0],
"neck": 12, "head": -12, "neck": 12, "head": -12,
"shoulder_r": -2, "elbow_r": 0,
"shoulder_l": -2, "elbow_l": 0, "shoulder_l": -2, "elbow_l": 0,
"hip_r": 8, "knee_r": 0, "hip_r": 8, "knee_r": 0,
"hip_l": 8, "knee_l": 0 "hip_l": 8, "knee_l": 0
Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 111 KiB

+1
View File
@@ -6,6 +6,7 @@
<path id="pelvisBar" d="M 190.9 148.8 L 185.0 148.0 L 185.0 149.2" stroke="#3a3f4b" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/> <path id="pelvisBar" d="M 190.9 148.8 L 185.0 148.0 L 185.0 149.2" stroke="#3a3f4b" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 185.0 148.0 Q 141.5 148.0 100.0 148.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/> <path id="spine" d="M 185.0 148.0 Q 141.5 148.0 100.0 148.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_l" d="M 190.9 148.8 L 213.9 109.5 L 233.9 75.4 L 224.4 70.0" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/> <path id="leg_l" d="M 190.9 148.8 L 213.9 109.5 L 233.9 75.4 L 224.4 70.0" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 100.0 149.9 L 130.0 150.9 L 160.0 152.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 185.0 149.2 L 208.0 110.0 L 228.0 75.9 L 218.5 70.5" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/> <path id="leg_r" d="M 185.0 149.2 L 208.0 110.0 L 228.0 75.9 L 218.5 70.5" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="80.4" cy="143.9" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/> <circle id="head" cx="80.4" cy="143.9" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<line id="nose" x1="80.4" y1="133.9" x2="80.4" y2="127.0" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/> <line id="nose" x1="80.4" y1="133.9" x2="80.4" y2="127.0" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

@@ -6,6 +6,7 @@
<path id="girdle" d="M 92.9 148.1 L 87.0 148.0 L 87.0 149.9" stroke="#0d9488" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/> <path id="girdle" d="M 92.9 148.1 L 87.0 148.0 L 87.0 149.9" stroke="#0d9488" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="pelvisBar" d="M 177.9 148.8 L 172.0 148.0 L 172.0 149.2" stroke="#0d9488" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/> <path id="pelvisBar" d="M 177.9 148.8 L 172.0 148.0 L 172.0 149.2" stroke="#0d9488" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 172.0 148.0 Q 128.5 148.0 87.0 148.0" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/> <path id="spine" d="M 172.0 148.0 Q 128.5 148.0 87.0 148.0" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 87.0 149.9 L 117.0 150.9 L 147.0 152.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 172.0 149.2 L 164.0 104.6 L 201.6 118.1 L 205.4 107.9" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/> <path id="leg_r" d="M 172.0 149.2 L 164.0 104.6 L 201.6 118.1 L 205.4 107.9" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="67.4" cy="143.9" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/> <circle id="head" cx="67.4" cy="143.9" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<line id="nose" x1="67.4" y1="133.9" x2="67.4" y2="127.0" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/> <line id="nose" x1="67.4" y1="133.9" x2="67.4" y2="127.0" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

@@ -6,6 +6,7 @@
<path id="girdle" d="M 88.5 143.4 L 82.6 143.4 L 82.6 145.3" stroke="#0d9488" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/> <path id="girdle" d="M 88.5 143.4 L 82.6 143.4 L 82.6 145.3" stroke="#0d9488" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="pelvisBar" d="M 171.9 136.8 L 166.0 136.0 L 166.0 137.2" stroke="#0d9488" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/> <path id="pelvisBar" d="M 171.9 136.8 L 166.0 136.0 L 166.0 137.2" stroke="#0d9488" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 166.0 136.0 Q 124.6 154.2 82.6 143.4" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/> <path id="spine" d="M 166.0 136.0 Q 124.6 154.2 82.6 143.4" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 82.6 145.3 L 112.6 146.3 L 142.6 147.3" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 166.0 137.2 L 146.6 96.2 L 186.0 103.0 L 187.9 92.3" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/> <path id="leg_r" d="M 166.0 137.2 L 146.6 96.2 L 186.0 103.0 L 187.9 92.3" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="62.6" cy="144.0" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/> <circle id="head" cx="62.6" cy="144.0" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<line id="nose" x1="62.6" y1="134.0" x2="62.6" y2="127.1" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/> <line id="nose" x1="62.6" y1="134.0" x2="62.6" y2="127.1" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

+2 -1
View File
@@ -2,13 +2,13 @@
"name": "Reverse Crunch", "name": "Reverse Crunch",
"primary": 2, "primary": 2,
"working": ["spine"], "working": ["spine"],
"hide": ["arm_r"],
"frames": [ "frames": [
{ {
"hold": 0.5, "tween": 0.7, "hold": 0.5, "tween": 0.7,
"root": {"pos": [172, 148], "pitch": -90}, "root": {"pos": [172, 148], "pitch": -90},
"spine": [0, 0], "spine": [0, 0],
"neck": 12, "head": -12, "neck": 12, "head": -12,
"shoulder_r": -2, "elbow_r": 0,
"shoulder_l": -2, "elbow_l": 0, "shoulder_l": -2, "elbow_l": 0,
"hip_r": 100, "knee_r": 120, "hip_r": 100, "knee_r": 120,
"hip_l": 100, "knee_l": 120 "hip_l": 100, "knee_l": 120
@@ -18,6 +18,7 @@
"root": {"pos": [166, 136], "pitch": -105}, "root": {"pos": [166, 136], "pitch": -105},
"spine": [0, 20], "spine": [0, 20],
"neck": -7, "head": 2, "neck": -7, "head": 2,
"shoulder_r": 3, "elbow_r": 0,
"shoulder_l": 3, "elbow_l": 0, "shoulder_l": 3, "elbow_l": 0,
"hip_r": 100, "knee_r": 125, "hip_r": 100, "knee_r": 125,
"hip_l": 100, "knee_l": 125 "hip_l": 100, "knee_l": 125
Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

After

Width:  |  Height:  |  Size: 128 KiB

@@ -6,6 +6,7 @@
<path id="girdle" d="M 88.5 143.4 L 82.6 143.4 L 82.6 145.3" stroke="#0d9488" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/> <path id="girdle" d="M 88.5 143.4 L 82.6 143.4 L 82.6 145.3" stroke="#0d9488" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="pelvisBar" d="M 171.9 136.8 L 166.0 136.0 L 166.0 137.2" stroke="#0d9488" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/> <path id="pelvisBar" d="M 171.9 136.8 L 166.0 136.0 L 166.0 137.2" stroke="#0d9488" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path id="spine" d="M 166.0 136.0 Q 124.6 154.2 82.6 143.4" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/> <path id="spine" d="M 166.0 136.0 Q 124.6 154.2 82.6 143.4" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="arm_r" d="M 82.6 145.3 L 112.6 146.3 L 142.6 147.3" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path id="leg_r" d="M 166.0 137.2 L 146.6 96.2 L 186.0 103.0 L 187.9 92.3" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/> <path id="leg_r" d="M 166.0 137.2 L 146.6 96.2 L 186.0 103.0 L 187.9 92.3" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="head" cx="62.6" cy="144.0" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/> <circle id="head" cx="62.6" cy="144.0" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
<line id="nose" x1="62.6" y1="134.0" x2="62.6" y2="127.1" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/> <line id="nose" x1="62.6" y1="134.0" x2="62.6" y2="127.1" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

+4 -5
View File
@@ -45,7 +45,6 @@ never touches a motion script** — proportions are the skeleton's problem.
"primary": 2, // 1-based frame used for visual.svg "primary": 2, // 1-based frame used for visual.svg
"camera": {"yaw": 0}, // 0 = side view (default), 90 = face-on "camera": {"yaw": 0}, // 0 = side view (default), 90 = face-on
"working": ["arm_l", "leg_r"], // parts drawn in the accent color "working": ["arm_l", "leg_r"], // parts drawn in the accent color
"hide": [], // limbs occluded by convention, not drawn
"frames": [ "frames": [
{ {
"hold": 0.5, "tween": 0.8, // seconds held / animating to the next frame "hold": 0.5, "tween": 0.8, // seconds held / animating to the next frame
@@ -115,8 +114,9 @@ never touches a motion script** — proportions are the skeleton's problem.
shoulder girdle and pelvis drawn as bars across the attach points (near-full shoulder girdle and pelvis drawn as bars across the attach points (near-full
width face-on, a shoulder/hip nub in profile); teal when the trunk is the width face-on, a shoulder/hip nub in profile); teal when the trunk is the
working part. working part.
- Canvas 320×180, ground line at y = 152. Limbs listed in `hide` are - Canvas 320×180, ground line at y = 152. Every limb is always drawn — a
occluded by convention in this view and not drawn. supine figure's far arm reads as the light far member, never a hidden one,
so any viewpoint (and the orbit) stays truthful.
## The props layer ## The props layer
@@ -172,8 +172,7 @@ authored yaw nothing moves, so the authored look is exact.
and `pad` default to perpendicular to the lower bone (forearm/shin), or and `pad` default to perpendicular to the lower bone (forearm/shin), or
take an explicit `angle`. Under orbit the segment rotates with the scene take an explicit `angle`. Under orbit the segment rotates with the scene
and foreshortens naturally. `plateR` puts filled discs on both ends and foreshortens naturally. `plateR` puts filled discs on both ends
(dumbbells default to 4.5). A prop whose limb is hidden that frame simply (dumbbells default to 4.5).
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
Binary file not shown.

Before

Width:  |  Height:  |  Size: 302 KiB

After

Width:  |  Height:  |  Size: 303 KiB

+2 -14
View File
@@ -602,23 +602,18 @@ def prepare(motion, figure="neutral", flip=False, strict=False):
def render_exercise(folder, figure="neutral", flip=False, strict=False): def render_exercise(folder, figure="neutral", flip=False, strict=False):
motion = load_motion(folder) motion = load_motion(folder)
working = set(motion.get("working", [])) working = set(motion.get("working", []))
hide = set(motion.get("hide", []))
norms, prof, cam, pitch, props = prepare(motion, figure, flip, strict) norms, prof, cam, pitch, props = prepare(motion, figure, flip, strict)
mat = mat_bounds(norms, prof, cam, pitch) mat = mat_bounds(norms, prof, cam, pitch)
def geometry(nf): def geometry(nf):
_, geo, order, shade = frame_geometry(nf, prof, cam, flip, pitch, mat) _, geo, order, shade = frame_geometry(nf, prof, cam, flip, pitch, mat)
for limb in hide:
geo.pop(limb, None)
return geo, order, shade return geo, order, shade
resolved = [] resolved = []
key_cells = [] 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:
geo.pop(limb, None)
resolved.append(out) resolved.append(out)
key_cells.append((geo, order, shade, resolve_props(props, geo, nf["root"]["pos"]))) key_cells.append((geo, order, shade, resolve_props(props, geo, nf["root"]["pos"])))
@@ -653,7 +648,6 @@ def render_orbit(folder, figure="neutral"):
motion authors one). Props rotate with the figure about the root anchor.""" 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", []))
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)
@@ -671,8 +665,6 @@ def render_orbit(folder, figure="neutral"):
posed, auth_geo, _, _ = frame_geometry(nf, prof, cam, pitch=pitch) posed, auth_geo, _, _ = frame_geometry(nf, prof, cam, pitch=pitch)
posed["pins"] = {} posed["pins"] = {}
_, geo, order, shade = frame_geometry(posed, prof, cam + off, pitch=pitch, mat=mat) _, geo, order, shade = frame_geometry(posed, prof, cam + off, pitch=pitch, mat=mat)
for limb in hide:
geo.pop(limb, None)
prims = resolve_props(props, geo, nf["root"]["pos"], prims = resolve_props(props, geo, nf["root"]["pos"],
prop_rotation(pitch, off), auth_geo) prop_rotation(pitch, off), auth_geo)
imgs.append(draw_geo(geo, order, shade, working, colors, font=font, prims=prims)) imgs.append(draw_geo(geo, order, shade, working, colors, font=font, prims=prims))
@@ -686,13 +678,11 @@ def contact_sheet(folders, figure="neutral", out=None):
cells = [] cells = []
for folder in folders: for folder in folders:
motion = load_motion(folder) motion = load_motion(folder)
working, hide = set(motion.get("working", [])), set(motion.get("hide", [])) working = set(motion.get("working", []))
norms, prof, cam, pitch, props = prepare(motion, figure) norms, prof, cam, pitch, props = prepare(motion, figure)
mat = mat_bounds(norms, prof, cam, pitch) mat = mat_bounds(norms, prof, cam, pitch)
for i, nf in enumerate(norms, start=1): for i, nf in enumerate(norms, start=1):
_, 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:
geo.pop(limb, None)
prims = resolve_props(props, geo, nf["root"]["pos"]) 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"],
@@ -705,7 +695,7 @@ def demo_sheet(folder):
opens: neutral / female / male profiles (same motion script, different opens: neutral / female / male profiles (same motion script, different
proportions), the flipped camera, an alternate theme.""" proportions), the flipped camera, an alternate theme."""
motion = load_motion(folder) motion = load_motion(folder)
working, hide = set(motion.get("working", [])), set(motion.get("hide", [])) working = set(motion.get("working", []))
idx = motion.get("primary", 1) - 1 idx = motion.get("primary", 1) - 1
font = legend_font() font = legend_font()
variants = [("neutral", "neutral", False, "default"), variants = [("neutral", "neutral", False, "default"),
@@ -718,8 +708,6 @@ def demo_sheet(folder):
norms, prof, cam, pitch, props = prepare(motion, figure, flip) norms, prof, cam, pitch, props = prepare(motion, figure, flip)
mat = mat_bounds(norms, prof, cam, pitch) mat = mat_bounds(norms, prof, cam, pitch)
_, 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:
geo.pop(limb, None)
prims = resolve_props(props, geo, norms[idx]["root"]["pos"]) 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],
@@ -22,8 +22,6 @@ struct FigureAnimation {
let timeline: MotionTimeline let timeline: MotionTimeline
/// Parts drawn in the working accent color (`arm_r`, `leg_l`, `spine`, ). /// Parts drawn in the working accent color (`arm_r`, `leg_l`, `spine`, ).
let working: Set<String> let working: Set<String>
/// Limbs fully occluded in this view never drawn.
let hide: Set<String>
/// Equipment layer (see SYSTEM.md "The props layer"). /// Equipment layer (see SYSTEM.md "The props layer").
let props: [MotionProp] let props: [MotionProp]
@@ -34,28 +32,20 @@ struct FigureAnimation {
else { return nil } else { return nil }
self.timeline = timeline self.timeline = timeline
self.working = Set(resources.motion.working ?? []) self.working = Set(resources.motion.working ?? [])
self.hide = Set(resources.motion.hide ?? [])
self.props = resources.motion.props ?? [] self.props = resources.motion.props ?? []
} }
/// Seconds per full camera revolution for orbiting motions. /// Seconds per full camera revolution for orbiting motions.
private static let orbitPeriod: Double = 24 private static let orbitPeriod: Double = 24
/// Prop-free, hide-free motions slowly orbit the camera while animating the /// Prop-free motions slowly orbit the camera while animating the bodyweight
/// bodyweight moves. Scene equipment is a view-locked billboard and a `hide` list /// moves. Machines keep their fixed camera until their equipment rotates too.
/// describes one authored viewpoint, so machines keep their fixed camera. var orbits: Bool { props.isEmpty }
var orbits: Bool { props.isEmpty && hide.isEmpty }
/// Drawable geometry at `time` the figure plus the resolved equipment layers /// Drawable geometry at `time` the figure plus the resolved equipment layers.
/// 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, props: props) return timeline.geometry(at: time, yawOffset: yaw, props: props)
for name in hide {
if let limb = FigureLimb(rawValue: name) { geo.limbs[limb] = nil }
}
return geo
} }
} }
@@ -235,8 +235,6 @@ struct ExerciseMotion: Codable {
let camera: MotionCamera? let camera: MotionCamera?
/// Parts (`arm_r`, `leg_l`, `spine`, ) drawn in the working accent color. /// Parts (`arm_r`, `leg_l`, `spine`, ) drawn in the working accent color.
let working: [String]? let working: [String]?
/// Limbs fully occluded in this view never drawn.
let hide: [String]?
/// Equipment layer: scene shapes and cables behind the figure, joint-attached /// Equipment layer: scene shapes and cables behind the figure, joint-attached
/// items (bar/dumbbell/pad) over the limbs. See SYSTEM.md "The props layer". /// items (bar/dumbbell/pad) over the limbs. See SYSTEM.md "The props layer".
let props: [MotionProp]? let props: [MotionProp]?
@@ -2,7 +2,6 @@
"name": "Hollow Body Hold", "name": "Hollow Body Hold",
"primary": 2, "primary": 2,
"working": ["spine"], "working": ["spine"],
"hide": ["arm_l"],
"frames": [ "frames": [
{ {
"hold": 0.4, "tween": 0.8, "hold": 0.4, "tween": 0.8,
@@ -10,6 +9,7 @@
"spine": [0, 0], "spine": [0, 0],
"neck": 12, "head": -12, "neck": 12, "head": -12,
"shoulder_r": 180, "elbow_r": 0, "shoulder_r": 180, "elbow_r": 0,
"shoulder_l": 180, "elbow_l": 0,
"hip_r": 0, "knee_r": 0, "hip_r": 0, "knee_r": 0,
"hip_l": 0, "knee_l": 0 "hip_l": 0, "knee_l": 0
}, },
@@ -19,6 +19,7 @@
"spine": [0, 14], "spine": [0, 14],
"neck": 20, "neck": 20,
"shoulder_r": 175, "elbow_r": 0, "shoulder_r": 175, "elbow_r": 0,
"shoulder_l": 175, "elbow_l": 0,
"hip_r": 18, "knee_r": 0, "hip_r": 18, "knee_r": 0,
"hip_l": 18, "knee_l": 0 "hip_l": 18, "knee_l": 0
} }
@@ -2,13 +2,13 @@
"name": "Leg Raises", "name": "Leg Raises",
"primary": 1, "primary": 1,
"working": ["leg_r", "leg_l"], "working": ["leg_r", "leg_l"],
"hide": ["arm_r"],
"frames": [ "frames": [
{ {
"hold": 0.5, "tween": 1.4, "hold": 0.5, "tween": 1.4,
"root": {"pos": [185, 148], "pitch": -90}, "root": {"pos": [185, 148], "pitch": -90},
"spine": [0, 0], "spine": [0, 0],
"neck": 12, "head": -12, "neck": 12, "head": -12,
"shoulder_r": -2, "elbow_r": 0,
"shoulder_l": -2, "elbow_l": 0, "shoulder_l": -2, "elbow_l": 0,
"hip_r": 60, "knee_r": 0, "hip_r": 60, "knee_r": 0,
"hip_l": 60, "knee_l": 0 "hip_l": 60, "knee_l": 0
@@ -18,6 +18,7 @@
"root": {"pos": [185, 148], "pitch": -90}, "root": {"pos": [185, 148], "pitch": -90},
"spine": [0, 0], "spine": [0, 0],
"neck": 12, "head": -12, "neck": 12, "head": -12,
"shoulder_r": -2, "elbow_r": 0,
"shoulder_l": -2, "elbow_l": 0, "shoulder_l": -2, "elbow_l": 0,
"hip_r": 8, "knee_r": 0, "hip_r": 8, "knee_r": 0,
"hip_l": 8, "knee_l": 0 "hip_l": 8, "knee_l": 0
@@ -2,13 +2,13 @@
"name": "Reverse Crunch", "name": "Reverse Crunch",
"primary": 2, "primary": 2,
"working": ["spine"], "working": ["spine"],
"hide": ["arm_r"],
"frames": [ "frames": [
{ {
"hold": 0.5, "tween": 0.7, "hold": 0.5, "tween": 0.7,
"root": {"pos": [172, 148], "pitch": -90}, "root": {"pos": [172, 148], "pitch": -90},
"spine": [0, 0], "spine": [0, 0],
"neck": 12, "head": -12, "neck": 12, "head": -12,
"shoulder_r": -2, "elbow_r": 0,
"shoulder_l": -2, "elbow_l": 0, "shoulder_l": -2, "elbow_l": 0,
"hip_r": 100, "knee_r": 120, "hip_r": 100, "knee_r": 120,
"hip_l": 100, "knee_l": 120 "hip_l": 100, "knee_l": 120
@@ -18,6 +18,7 @@
"root": {"pos": [166, 136], "pitch": -105}, "root": {"pos": [166, 136], "pitch": -105},
"spine": [0, 20], "spine": [0, 20],
"neck": -7, "head": 2, "neck": -7, "head": 2,
"shoulder_r": 3, "elbow_r": 0,
"shoulder_l": 3, "elbow_l": 0, "shoulder_l": 3, "elbow_l": 0,
"hip_r": 100, "knee_r": 125, "hip_r": 100, "knee_r": 125,
"hip_l": 100, "knee_l": 125 "hip_l": 100, "knee_l": 125
+2 -2
View File
@@ -80,8 +80,8 @@ struct ExerciseMotionTests {
#expect(hypot(releasedHand.x - releasedHand0.x, releasedHand.y - releasedHand0.y) > 1) #expect(hypot(releasedHand.x - releasedHand0.x, releasedHand.y - releasedHand0.y) > 1)
} }
/// Prop-free bodyweight motions slowly orbit the camera while looping; motions with /// Prop-free bodyweight motions slowly orbit the camera while looping; motions
/// equipment (view-locked scene shapes) or a hide list keep their authored view. /// with equipment keep their authored view (until machines orbit too).
@Test func orbitAppliesOnlyToPropFreeMotions() throws { @Test func orbitAppliesOnlyToPropFreeMotions() throws {
let birdDog = try #require(FigureAnimation(exerciseName: "Bird Dog")) let birdDog = try #require(FigureAnimation(exerciseName: "Bird Dog"))
#expect(birdDog.orbits) #expect(birdDog.orbits)
File diff suppressed because one or more lines are too long