@franzenzenhofer/asanakit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +153 -0
  3. package/dist/anatomy/muscles.d.ts +55 -0
  4. package/dist/anatomy/muscles.d.ts.map +1 -0
  5. package/dist/anatomy/muscles.js +79 -0
  6. package/dist/anatomy/muscles.js.map +1 -0
  7. package/dist/anatomy/validate.d.ts +11 -0
  8. package/dist/anatomy/validate.d.ts.map +1 -0
  9. package/dist/anatomy/validate.js +94 -0
  10. package/dist/anatomy/validate.js.map +1 -0
  11. package/dist/cli/commands/export.d.ts +3 -0
  12. package/dist/cli/commands/export.d.ts.map +1 -0
  13. package/dist/cli/commands/export.js +45 -0
  14. package/dist/cli/commands/export.js.map +1 -0
  15. package/dist/cli/commands/info.d.ts +3 -0
  16. package/dist/cli/commands/info.d.ts.map +1 -0
  17. package/dist/cli/commands/info.js +101 -0
  18. package/dist/cli/commands/info.js.map +1 -0
  19. package/dist/cli/commands/render.d.ts +3 -0
  20. package/dist/cli/commands/render.d.ts.map +1 -0
  21. package/dist/cli/commands/render.js +103 -0
  22. package/dist/cli/commands/render.js.map +1 -0
  23. package/dist/cli/index.d.ts +3 -0
  24. package/dist/cli/index.d.ts.map +1 -0
  25. package/dist/cli/index.js +31 -0
  26. package/dist/cli/index.js.map +1 -0
  27. package/dist/cli/resolve.d.ts +12 -0
  28. package/dist/cli/resolve.d.ts.map +1 -0
  29. package/dist/cli/resolve.js +46 -0
  30. package/dist/cli/resolve.js.map +1 -0
  31. package/dist/core/angles.d.ts +12 -0
  32. package/dist/core/angles.d.ts.map +1 -0
  33. package/dist/core/angles.js +27 -0
  34. package/dist/core/angles.js.map +1 -0
  35. package/dist/core/rig.d.ts +8 -0
  36. package/dist/core/rig.d.ts.map +1 -0
  37. package/dist/core/rig.js +97 -0
  38. package/dist/core/rig.js.map +1 -0
  39. package/dist/core/skeleton.d.ts +17 -0
  40. package/dist/core/skeleton.d.ts.map +1 -0
  41. package/dist/core/skeleton.js +144 -0
  42. package/dist/core/skeleton.js.map +1 -0
  43. package/dist/core/types.d.ts +105 -0
  44. package/dist/core/types.d.ts.map +1 -0
  45. package/dist/core/types.js +47 -0
  46. package/dist/core/types.js.map +1 -0
  47. package/dist/core/vec2.d.ts +19 -0
  48. package/dist/core/vec2.d.ts.map +1 -0
  49. package/dist/core/vec2.js +41 -0
  50. package/dist/core/vec2.js.map +1 -0
  51. package/dist/index.d.ts +11 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +11 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/library/index.d.ts +27 -0
  56. package/dist/library/index.d.ts.map +1 -0
  57. package/dist/library/index.js +75 -0
  58. package/dist/library/index.js.map +1 -0
  59. package/dist/model/index.d.ts +4 -0
  60. package/dist/model/index.d.ts.map +1 -0
  61. package/dist/model/index.js +4 -0
  62. package/dist/model/index.js.map +1 -0
  63. package/dist/model/parse.d.ts +12 -0
  64. package/dist/model/parse.d.ts.map +1 -0
  65. package/dist/model/parse.js +43 -0
  66. package/dist/model/parse.js.map +1 -0
  67. package/dist/model/pose.d.ts +11 -0
  68. package/dist/model/pose.d.ts.map +1 -0
  69. package/dist/model/pose.js +26 -0
  70. package/dist/model/pose.js.map +1 -0
  71. package/dist/model/schema.d.ts +1122 -0
  72. package/dist/model/schema.d.ts.map +1 -0
  73. package/dist/model/schema.js +178 -0
  74. package/dist/model/schema.js.map +1 -0
  75. package/dist/render/anatomy.d.ts +10 -0
  76. package/dist/render/anatomy.d.ts.map +1 -0
  77. package/dist/render/anatomy.js +77 -0
  78. package/dist/render/anatomy.js.map +1 -0
  79. package/dist/render/annotations.d.ts +9 -0
  80. package/dist/render/annotations.d.ts.map +1 -0
  81. package/dist/render/annotations.js +185 -0
  82. package/dist/render/annotations.js.map +1 -0
  83. package/dist/render/context.d.ts +16 -0
  84. package/dist/render/context.d.ts.map +1 -0
  85. package/dist/render/context.js +2 -0
  86. package/dist/render/context.js.map +1 -0
  87. package/dist/render/figure.d.ts +8 -0
  88. package/dist/render/figure.d.ts.map +1 -0
  89. package/dist/render/figure.js +138 -0
  90. package/dist/render/figure.js.map +1 -0
  91. package/dist/render/index.d.ts +7 -0
  92. package/dist/render/index.d.ts.map +1 -0
  93. package/dist/render/index.js +7 -0
  94. package/dist/render/index.js.map +1 -0
  95. package/dist/render/project.d.ts +18 -0
  96. package/dist/render/project.d.ts.map +1 -0
  97. package/dist/render/project.js +35 -0
  98. package/dist/render/project.js.map +1 -0
  99. package/dist/render/props.d.ts +9 -0
  100. package/dist/render/props.d.ts.map +1 -0
  101. package/dist/render/props.js +178 -0
  102. package/dist/render/props.js.map +1 -0
  103. package/dist/render/raster.d.ts +19 -0
  104. package/dist/render/raster.d.ts.map +1 -0
  105. package/dist/render/raster.js +39 -0
  106. package/dist/render/raster.js.map +1 -0
  107. package/dist/render/scene.d.ts +26 -0
  108. package/dist/render/scene.d.ts.map +1 -0
  109. package/dist/render/scene.js +144 -0
  110. package/dist/render/scene.js.map +1 -0
  111. package/dist/render/sheet.d.ts +20 -0
  112. package/dist/render/sheet.d.ts.map +1 -0
  113. package/dist/render/sheet.js +74 -0
  114. package/dist/render/sheet.js.map +1 -0
  115. package/dist/render/styles.d.ts +77 -0
  116. package/dist/render/styles.d.ts.map +1 -0
  117. package/dist/render/styles.js +122 -0
  118. package/dist/render/styles.js.map +1 -0
  119. package/dist/render/svg.d.ts +19 -0
  120. package/dist/render/svg.d.ts.map +1 -0
  121. package/dist/render/svg.js +40 -0
  122. package/dist/render/svg.js.map +1 -0
  123. package/dist/standards/keypoints.d.ts +31 -0
  124. package/dist/standards/keypoints.d.ts.map +1 -0
  125. package/dist/standards/keypoints.js +118 -0
  126. package/dist/standards/keypoints.js.map +1 -0
  127. package/docs/AUTHORING.md +144 -0
  128. package/package.json +94 -0
  129. package/poses/surf/bottom-turn.pose.yaml +47 -0
  130. package/poses/surf/cutback.pose.yaml +47 -0
  131. package/poses/surf/duck-dive.pose.yaml +47 -0
  132. package/poses/surf/noseride.pose.yaml +48 -0
  133. package/poses/surf/paddling.pose.yaml +48 -0
  134. package/poses/surf/pop-up.pose.yaml +47 -0
  135. package/poses/surf/takeoff.pose.yaml +47 -0
  136. package/poses/surf/tube-stance.pose.yaml +47 -0
  137. package/poses/yoga/ashtanga/adho-mukha-svanasana.pose.yaml +42 -0
  138. package/poses/yoga/ashtanga/ardha-uttanasana.pose.yaml +42 -0
  139. package/poses/yoga/ashtanga/baddha-konasana.pose.yaml +42 -0
  140. package/poses/yoga/ashtanga/balasana.pose.yaml +43 -0
  141. package/poses/yoga/ashtanga/chaturanga-dandasana.pose.yaml +41 -0
  142. package/poses/yoga/ashtanga/dandasana.pose.yaml +42 -0
  143. package/poses/yoga/ashtanga/halasana.pose.yaml +43 -0
  144. package/poses/yoga/ashtanga/janu-sirsasana-a.pose.yaml +42 -0
  145. package/poses/yoga/ashtanga/karnapidasana.pose.yaml +43 -0
  146. package/poses/yoga/ashtanga/marichyasana-a.pose.yaml +42 -0
  147. package/poses/yoga/ashtanga/marichyasana-c.pose.yaml +42 -0
  148. package/poses/yoga/ashtanga/matsyasana.pose.yaml +43 -0
  149. package/poses/yoga/ashtanga/navasana.pose.yaml +42 -0
  150. package/poses/yoga/ashtanga/padahastasana.pose.yaml +42 -0
  151. package/poses/yoga/ashtanga/padangusthasana.pose.yaml +42 -0
  152. package/poses/yoga/ashtanga/padmasana.pose.yaml +43 -0
  153. package/poses/yoga/ashtanga/parsvottanasana.pose.yaml +42 -0
  154. package/poses/yoga/ashtanga/paschimottanasana.pose.yaml +42 -0
  155. package/poses/yoga/ashtanga/prasarita-padottanasana-a.pose.yaml +46 -0
  156. package/poses/yoga/ashtanga/prasarita-padottanasana-c.pose.yaml +46 -0
  157. package/poses/yoga/ashtanga/primary.seq.yaml +74 -0
  158. package/poses/yoga/ashtanga/purvottanasana.pose.yaml +42 -0
  159. package/poses/yoga/ashtanga/salamba-sarvangasana.pose.yaml +43 -0
  160. package/poses/yoga/ashtanga/samasthiti.pose.yaml +29 -0
  161. package/poses/yoga/ashtanga/savasana.pose.yaml +43 -0
  162. package/poses/yoga/ashtanga/setu-bandhasana.pose.yaml +42 -0
  163. package/poses/yoga/ashtanga/sirsasana.pose.yaml +43 -0
  164. package/poses/yoga/ashtanga/supta-padangusthasana.pose.yaml +42 -0
  165. package/poses/yoga/ashtanga/upavistha-konasana.pose.yaml +42 -0
  166. package/poses/yoga/ashtanga/urdhva-dhanurasana.pose.yaml +43 -0
  167. package/poses/yoga/ashtanga/urdhva-hastasana.pose.yaml +42 -0
  168. package/poses/yoga/ashtanga/urdhva-mukha-svanasana.pose.yaml +42 -0
  169. package/poses/yoga/ashtanga/utkatasana.pose.yaml +42 -0
  170. package/poses/yoga/ashtanga/uttanasana.pose.yaml +42 -0
  171. package/poses/yoga/ashtanga/utthita-parsvakonasana.pose.yaml +44 -0
  172. package/poses/yoga/ashtanga/utthita-trikonasana.pose.yaml +44 -0
  173. package/poses/yoga/ashtanga/virabhadrasana-a.pose.yaml +42 -0
  174. package/poses/yoga/ashtanga/virabhadrasana-b.pose.yaml +42 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Franz Enzenhofer
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,153 @@
1
+ # asanakit
2
+
3
+ **Programmatic stick-figure and anatomical infographics for yoga and surf postures.**
4
+
5
+ Describe a posture as data. Get a correct, deterministic, machine-readable illustration.
6
+
7
+ ```bash
8
+ npx @franzenzenhofer/asanakit render adho-mukha-svanasana -o downdog.svg --style anatomy --title
9
+ npx @franzenzenhofer/asanakit sheet --sequence ashtanga-primary -o primary-series.png --columns 6 --numbered
10
+ ```
11
+
12
+ asanakit is CLI-first, has no browser and no DOM, and renders the same bytes on every
13
+ machine. It ships with the **full Ashtanga Primary Series** - 37 asanas, 60 steps - authored
14
+ in its own pose format, plus 8 surf postures.
15
+
16
+ ![The Ashtanga Primary Series rendered by asanakit](examples/ashtanga-primary-series.png)
17
+
18
+ ---
19
+
20
+ ## Why it exists
21
+
22
+ Every other option is a dead end. Traced SVG artwork is unposeable. Pose-estimation
23
+ datasets give you keypoints scraped from photos, with no joint angles, no licence you can
24
+ use, and no way to say "now bend that knee ten degrees more". 3D mannequin libraries are
25
+ GPL, or need a GPU, or both.
26
+
27
+ asanakit takes the third path: a **20-bone kinematic rig** plus a **declarative pose format**.
28
+ The posture is the data. The picture is a pure function of it.
29
+
30
+ ## What you get
31
+
32
+ - **A pose format** (`.pose.yaml`) with a published JSON Schema. Joint angles, absolute bone
33
+ directions, props, annotations, muscle activation, teaching cues.
34
+ - **Forward kinematics** over a 20-bone humanoid rig with anatomical proportions.
35
+ - **An anatomical validator.** Knees and elbows that bend backwards are rejected. A pose
36
+ declares which parts of the body touch the floor, and asanakit checks that they do.
37
+ - **Seven styles**, all pure token data: `stick`, `anatomy`, `silhouette`, `blueprint`,
38
+ `ink`, `poster`, `minimal`.
39
+ - **SVG and PNG.** Deterministic, HTML-safe, and every element carries `data-bone`,
40
+ `data-muscle`, `data-annotation` hooks so the output stays machine-readable.
41
+ - **Keypoint export** to MediaPipe-33 and COCO-17 for interop with pose-estimation tooling.
42
+ - **Contact sheets and sequences** - render a whole practice in one command.
43
+
44
+ ## The same posture, seven ways
45
+
46
+ ![Every style](examples/styles.png)
47
+
48
+ ## Anatomy, not decoration
49
+
50
+ Muscles are displaced in two anatomical planes - lateral (left/right) and sagittal
51
+ (front/back) - so the chest is never drawn behind the back. Engaged muscles shade one way,
52
+ stretched muscles the other.
53
+
54
+ ![Downward dog with muscle activation](examples/anatomy.png)
55
+
56
+ ## Surf
57
+
58
+ ![Surf postures](examples/surf.png)
59
+
60
+ ## Install
61
+
62
+ ```bash
63
+ npm install -g @franzenzenhofer/asanakit # the CLI is called `asanakit`
64
+ npx @franzenzenhofer/asanakit --help # or run it without installing
65
+ ```
66
+
67
+ The npm registry refuses the bare name `asanakit` (too close to the unrelated
68
+ `asynckit`), so the package is scoped. The command it installs is `asanakit`.
69
+
70
+ ## The format
71
+
72
+ ```yaml
73
+ asanakit: 1
74
+ id: utthita-trikonasana
75
+ name: Extended Triangle
76
+ sanskrit: Utthita Trikoṇāsana
77
+ discipline: yoga
78
+ family: standing
79
+ breath: inhale
80
+ drishti: hastagrai
81
+ cues:
82
+ - Lengthen both sides of the waist
83
+ - Stack the top shoulder over the bottom one
84
+ contact: [toeL, toeR, handTipR]
85
+ figure:
86
+ view: front
87
+ world: # absolute bone directions, in degrees
88
+ thighL: -55 # 0 = right, 90 = up, -90 = down
89
+ thighR: -125
90
+ spine: 35
91
+ upperArmL: 35
92
+ upperArmR: -145
93
+ props:
94
+ - type: mat
95
+ annotations:
96
+ - type: angle
97
+ at: kneeL
98
+ from: hipJointL
99
+ to: ankleL
100
+ muscles:
101
+ engaged: [obliques, quadriceps]
102
+ stretched: [hamstrings, adductors]
103
+ ```
104
+
105
+ Full guide: **[docs/AUTHORING.md](docs/AUTHORING.md)**.
106
+
107
+ ## CLI
108
+
109
+ | Command | What it does |
110
+ |---|---|
111
+ | `asanakit render <pose> -o out.svg` | render one pose (`.svg` or `.png`) |
112
+ | `asanakit sheet --all -o sheet.png` | contact sheet of many poses |
113
+ | `asanakit sequence <id> -o dir/` | every pose of a sequence, in practice order |
114
+ | `asanakit lint [poses...]` | check poses against the limits of a real body |
115
+ | `asanakit list` | what is in the library |
116
+ | `asanakit vocab` | every joint, landmark, muscle and style name |
117
+ | `asanakit schema` | the JSON Schema for the pose format |
118
+ | `asanakit keypoints <pose>` | export as MediaPipe-33 / COCO-17 keypoints |
119
+ | `asanakit landmarks <pose>` | the solved skeleton, as JSON |
120
+
121
+ Options: `--style`, `--width`, `--height`, `--title`, `--caption`, `--muscles`,
122
+ `--background`, `--optimize`, `--scale`, `--lib <dir>`.
123
+
124
+ ## Library API
125
+
126
+ ```ts
127
+ import { parsePose, renderSvg, renderPng, validatePose, solveSkeleton } from '@franzenzenhofer/asanakit';
128
+
129
+ const pose = parsePose(yamlSource, 'triangle.pose.yaml');
130
+
131
+ const issues = validatePose(pose); // [] means anatomically sound
132
+ const svg = renderSvg(pose, { style: 'anatomy', title: true });
133
+ const png = renderPng(svg, { width: 1200 });
134
+ ```
135
+
136
+ ## Built for machines as well as people
137
+
138
+ - Errors name the field and the file, and report every problem at once.
139
+ - `asanakit schema` and `asanakit vocab` are the full contract - a model can discover the
140
+ entire vocabulary without reading the source.
141
+ - `asanakit lint` turns "does this look right" into a check that either passes or fails.
142
+ - Rendering is deterministic, so output diffs are meaningful in review.
143
+
144
+ ## Licence
145
+
146
+ MIT.
147
+
148
+ Dependencies are all permissive: `gl-matrix` (MIT), `d3-shape`/`d3-path` (ISC),
149
+ `xmlbuilder2` (MIT), `commander` (MIT), `zod` (MIT), `yaml` (ISC), `svgo` (MIT),
150
+ `@resvg/resvg-js` (MPL-2.0, used unmodified as a dependency).
151
+
152
+ The MediaPipe-33 and COCO-17 keypoint layouts restate the public layouts published by the
153
+ Apache-2.0 licensed MediaPipe and tfjs-models projects.
@@ -0,0 +1,55 @@
1
+ import type { BoneId, ViewId } from '../core/types.js';
2
+ /**
3
+ * Muscles are modelled as capsules laid over the bone they act on: a span
4
+ * `t0..t1` along the bone, pushed off it, and drawn `width` thick.
5
+ *
6
+ * A muscle is displaced in two independent anatomical planes, and which one you
7
+ * can see depends on where you are standing:
8
+ *
9
+ * - `lateral` - left/right of the midline. You see it face-on; in profile it
10
+ * points into the screen and collapses to nothing.
11
+ * - `sagittal` - front/back of the body. You see it in profile; face-on it
12
+ * points at you and collapses to nothing.
13
+ *
14
+ * Keeping them apart is what stops the chest muscles being drawn behind the back
15
+ * in a side view. Positive `sagittal` is always anterior - toward the front of
16
+ * the body - whatever direction the bone happens to point.
17
+ *
18
+ * This is deliberately illustrative, not a biomechanical model. It is enough to
19
+ * shade "which muscle is working here", which is what a posture infographic needs.
20
+ * All measurements are fractions of stature.
21
+ */
22
+ export interface MuscleDef {
23
+ readonly id: MuscleId;
24
+ readonly label: string;
25
+ readonly latin: string;
26
+ readonly region: 'upper' | 'core' | 'lower';
27
+ readonly bone: BoneBase;
28
+ readonly t0: number;
29
+ readonly t1: number;
30
+ /** Offset left/right of the midline. Non-zero means the muscle comes in a pair. */
31
+ readonly lateral: number;
32
+ /** Offset toward the front of the body (positive) or the back (negative). */
33
+ readonly sagittal: number;
34
+ readonly width: number;
35
+ }
36
+ export type BoneBase = 'pelvis' | 'spine' | 'neck' | 'head' | 'clavicle' | 'upperArm' | 'forearm' | 'hand' | 'hip' | 'thigh' | 'shin' | 'foot';
37
+ export declare const MUSCLE_IDS: readonly ["deltoid", "rotatorCuff", "biceps", "triceps", "forearmFlexors", "pectoralis", "latissimus", "trapezius", "serratus", "erectorSpinae", "rectusAbdominis", "obliques", "gluteus", "hipFlexors", "quadriceps", "hamstrings", "adductors", "gastrocnemius", "tibialisAnterior"];
38
+ export type MuscleId = (typeof MUSCLE_IDS)[number];
39
+ export declare const MUSCLES: Record<MuscleId, MuscleDef>;
40
+ export declare const isMuscleId: (value: string) => value is MuscleId;
41
+ export interface MuscleInstance {
42
+ readonly bone: BoneId;
43
+ /** Offset along the bone's left-normal, already projected for this view. */
44
+ readonly offset: number;
45
+ }
46
+ /**
47
+ * Resolve a muscle to the concrete bellies drawn for a given view.
48
+ *
49
+ * Face-on, a paired torso muscle is two bellies either side of the spine and its
50
+ * front/back displacement is invisible. In profile it is one belly, placed in
51
+ * front of or behind the spine. Limb muscles are always drawn on both limbs -
52
+ * in profile the two simply overlap, which is exactly what you see.
53
+ */
54
+ export declare const muscleInstances: (muscle: MuscleDef, view: ViewId) => MuscleInstance[];
55
+ //# sourceMappingURL=muscles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"muscles.d.ts","sourceRoot":"","sources":["../../src/anatomy/muscles.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAEvD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;IAC5C,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,mFAAmF;IACnF,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,6EAA6E;IAC7E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,MAAM,QAAQ,GAChB,QAAQ,GACR,OAAO,GACP,MAAM,GACN,MAAM,GACN,UAAU,GACV,UAAU,GACV,SAAS,GACT,MAAM,GACN,KAAK,GACL,OAAO,GACP,MAAM,GACN,MAAM,CAAC;AAOX,eAAO,MAAM,UAAU,wRAoBb,CAAC;AAEX,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AAEnD,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,QAAQ,EAAE,SAAS,CAsB/C,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,OAAO,MAAM,KAAG,KAAK,IAAI,QAA4B,CAAC;AAEjF,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,4EAA4E;IAC5E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAID;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,GAAI,QAAQ,SAAS,EAAE,MAAM,MAAM,KAAG,cAAc,EAqB/E,CAAC"}
@@ -0,0 +1,79 @@
1
+ import { VIEWS } from '../core/rig.js';
2
+ /** Torso bones run upwards, limb bones hang downwards, so their left-normals point opposite ways. */
3
+ const TORSO_BONES = ['pelvis', 'spine', 'neck', 'head', 'clavicle', 'hip'];
4
+ const anteriorSign = (bone) => (TORSO_BONES.includes(bone) ? -1 : 1);
5
+ export const MUSCLE_IDS = [
6
+ 'deltoid',
7
+ 'rotatorCuff',
8
+ 'biceps',
9
+ 'triceps',
10
+ 'forearmFlexors',
11
+ 'pectoralis',
12
+ 'latissimus',
13
+ 'trapezius',
14
+ 'serratus',
15
+ 'erectorSpinae',
16
+ 'rectusAbdominis',
17
+ 'obliques',
18
+ 'gluteus',
19
+ 'hipFlexors',
20
+ 'quadriceps',
21
+ 'hamstrings',
22
+ 'adductors',
23
+ 'gastrocnemius',
24
+ 'tibialisAnterior',
25
+ ];
26
+ export const MUSCLES = {
27
+ deltoid: { id: 'deltoid', label: 'Deltoid', latin: 'musculus deltoideus', region: 'upper', bone: 'upperArm', t0: -0.08, t1: 0.32, lateral: 0, sagittal: 0, width: 0.058 },
28
+ rotatorCuff: { id: 'rotatorCuff', label: 'Rotator cuff', latin: 'musculi rotatores', region: 'upper', bone: 'upperArm', t0: -0.05, t1: 0.12, lateral: 0, sagittal: -0.012, width: 0.04 },
29
+ biceps: { id: 'biceps', label: 'Biceps', latin: 'musculus biceps brachii', region: 'upper', bone: 'upperArm', t0: 0.18, t1: 0.92, lateral: 0, sagittal: 0.02, width: 0.04 },
30
+ triceps: { id: 'triceps', label: 'Triceps', latin: 'musculus triceps brachii', region: 'upper', bone: 'upperArm', t0: 0.15, t1: 0.95, lateral: 0, sagittal: -0.02, width: 0.04 },
31
+ forearmFlexors: { id: 'forearmFlexors', label: 'Forearm flexors', latin: 'flexores antebrachii', region: 'upper', bone: 'forearm', t0: 0.05, t1: 0.8, lateral: 0, sagittal: 0.012, width: 0.034 },
32
+ pectoralis: { id: 'pectoralis', label: 'Chest', latin: 'musculus pectoralis major', region: 'upper', bone: 'spine', t0: 0.55, t1: 0.92, lateral: 0.05, sagittal: 0.032, width: 0.062 },
33
+ latissimus: { id: 'latissimus', label: 'Lats', latin: 'musculus latissimus dorsi', region: 'upper', bone: 'spine', t0: 0.32, t1: 0.78, lateral: 0.062, sagittal: -0.04, width: 0.05 },
34
+ trapezius: { id: 'trapezius', label: 'Trapezius', latin: 'musculus trapezius', region: 'upper', bone: 'spine', t0: 0.8, t1: 1.04, lateral: 0.032, sagittal: -0.03, width: 0.05 },
35
+ serratus: { id: 'serratus', label: 'Serratus', latin: 'musculus serratus anterior', region: 'upper', bone: 'spine', t0: 0.45, t1: 0.7, lateral: 0.056, sagittal: 0.01, width: 0.03 },
36
+ erectorSpinae: { id: 'erectorSpinae', label: 'Spinal erectors', latin: 'musculi erectores spinae', region: 'core', bone: 'spine', t0: 0.02, t1: 0.88, lateral: 0, sagittal: -0.026, width: 0.032 },
37
+ rectusAbdominis: { id: 'rectusAbdominis', label: 'Abdominals', latin: 'musculus rectus abdominis', region: 'core', bone: 'spine', t0: 0.0, t1: 0.56, lateral: 0, sagittal: 0.03, width: 0.055 },
38
+ obliques: { id: 'obliques', label: 'Obliques', latin: 'musculi obliqui abdominis', region: 'core', bone: 'spine', t0: 0.05, t1: 0.5, lateral: 0.05, sagittal: 0.012, width: 0.04 },
39
+ gluteus: { id: 'gluteus', label: 'Glutes', latin: 'musculus gluteus maximus', region: 'lower', bone: 'thigh', t0: -0.06, t1: 0.24, lateral: 0, sagittal: -0.03, width: 0.066 },
40
+ hipFlexors: { id: 'hipFlexors', label: 'Hip flexors', latin: 'musculus iliopsoas', region: 'lower', bone: 'thigh', t0: -0.04, t1: 0.34, lateral: 0, sagittal: 0.03, width: 0.036 },
41
+ quadriceps: { id: 'quadriceps', label: 'Quadriceps', latin: 'musculus quadriceps femoris', region: 'lower', bone: 'thigh', t0: 0.14, t1: 0.96, lateral: 0, sagittal: 0.024, width: 0.052 },
42
+ hamstrings: { id: 'hamstrings', label: 'Hamstrings', latin: 'musculi ischiocrurales', region: 'lower', bone: 'thigh', t0: 0.12, t1: 0.94, lateral: 0, sagittal: -0.026, width: 0.048 },
43
+ adductors: { id: 'adductors', label: 'Adductors', latin: 'musculi adductores', region: 'lower', bone: 'thigh', t0: 0.08, t1: 0.78, lateral: 0, sagittal: 0.0, width: 0.03 },
44
+ gastrocnemius: { id: 'gastrocnemius', label: 'Calves', latin: 'musculus gastrocnemius', region: 'lower', bone: 'shin', t0: 0.04, t1: 0.62, lateral: 0, sagittal: -0.024, width: 0.046 },
45
+ tibialisAnterior: { id: 'tibialisAnterior', label: 'Shin', latin: 'musculus tibialis anterior', region: 'lower', bone: 'shin', t0: 0.06, t1: 0.72, lateral: 0, sagittal: 0.016, width: 0.03 },
46
+ };
47
+ export const isMuscleId = (value) => value in MUSCLES;
48
+ const isTorsoMuscle = (muscle) => TORSO_BONES.includes(muscle.bone);
49
+ /**
50
+ * Resolve a muscle to the concrete bellies drawn for a given view.
51
+ *
52
+ * Face-on, a paired torso muscle is two bellies either side of the spine and its
53
+ * front/back displacement is invisible. In profile it is one belly, placed in
54
+ * front of or behind the spine. Limb muscles are always drawn on both limbs -
55
+ * in profile the two simply overlap, which is exactly what you see.
56
+ */
57
+ export const muscleInstances = (muscle, view) => {
58
+ const profile = VIEWS[view].lateralScale < 1;
59
+ const sagittal = muscle.sagittal * anteriorSign(muscle.bone);
60
+ if (isTorsoMuscle(muscle)) {
61
+ const bone = muscle.bone;
62
+ if (profile)
63
+ return [{ bone, offset: sagittal }];
64
+ if (muscle.lateral === 0)
65
+ return [{ bone, offset: 0 }];
66
+ return [
67
+ { bone, offset: muscle.lateral },
68
+ { bone, offset: -muscle.lateral },
69
+ ];
70
+ }
71
+ // A limb muscle rides its own bone; only its front/back displacement is ever
72
+ // visible, and face-on that displacement points at the viewer and collapses.
73
+ const offset = profile ? sagittal : muscle.lateral;
74
+ return [
75
+ { bone: `${muscle.bone}L`, offset },
76
+ { bone: `${muscle.bone}R`, offset },
77
+ ];
78
+ };
79
+ //# sourceMappingURL=muscles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"muscles.js","sourceRoot":"","sources":["../../src/anatomy/muscles.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { Rig } from '../core/types.js';
2
+ import { type PoseSpec } from '../model/index.js';
3
+ export type IssueCode = 'knee-hyperextension' | 'knee-overflexion' | 'elbow-hyperextension' | 'elbow-overflexion' | 'below-ground' | 'no-ground-contact' | 'contact-off-ground';
4
+ export interface Issue {
5
+ readonly code: IssueCode;
6
+ readonly message: string;
7
+ readonly severity: 'error' | 'warning';
8
+ }
9
+ /** Check a pose against the limits of an actual body. Empty result means it is sound. */
10
+ export declare const validatePose: (pose: PoseSpec, rig?: Rig) => Issue[];
11
+ //# sourceMappingURL=validate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/anatomy/validate.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAsB,GAAG,EAAY,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EAAiB,KAAK,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAEjE,MAAM,MAAM,SAAS,GACjB,qBAAqB,GACrB,kBAAkB,GAClB,sBAAsB,GACtB,mBAAmB,GACnB,cAAc,GACd,mBAAmB,GACnB,oBAAoB,CAAC;AAEzB,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;CACxC;AA6GD,yFAAyF;AACzF,eAAO,MAAM,YAAY,GAAI,MAAM,QAAQ,EAAE,MAAK,GAAiB,KAAG,KAAK,EAG1E,CAAC"}
@@ -0,0 +1,94 @@
1
+ import { normalizeDeg } from '../core/angles.js';
2
+ import { DEFAULT_RIG } from '../core/rig.js';
3
+ import { sideSign, solveSkeleton } from '../core/skeleton.js';
4
+ import { resolveFigure } from '../model/index.js';
5
+ const KNEE = { maxFlexion: 160, hyper: 'knee-hyperextension', over: 'knee-overflexion' };
6
+ const ELBOW = { maxFlexion: 155, hyper: 'elbow-hyperextension', over: 'elbow-overflexion' };
7
+ const HINGES = [
8
+ { child: 'shinL', parent: 'thighL', name: 'left knee', ...KNEE },
9
+ { child: 'shinR', parent: 'thighR', name: 'right knee', ...KNEE },
10
+ { child: 'forearmL', parent: 'upperArmL', name: 'left elbow', ...ELBOW },
11
+ { child: 'forearmR', parent: 'upperArmR', name: 'right elbow', ...ELBOW },
12
+ ];
13
+ /** A little slack: a locked-out knee measures a degree or two either side of straight. */
14
+ const HYPEREXTENSION_SLACK = 8;
15
+ const GROUND_TOLERANCE = 0.02;
16
+ const CONTACT_TOLERANCE = 0.035;
17
+ /**
18
+ * Flexion of a bone away from its parent, in the joint's own frame. Right-side
19
+ * bones mirror, so their world delta runs the other way - undo that, and both
20
+ * elbows report flexion as a positive number.
21
+ */
22
+ const flexion = (skeleton, hinge, rig) => {
23
+ const child = skeleton.bones[hinge.child];
24
+ const parent = skeleton.bones[hinge.parent];
25
+ const def = rig.bones.find((b) => b.id === hinge.child);
26
+ if (def === undefined)
27
+ throw new Error(`Rig "${rig.name}" has no bone "${hinge.child}"`);
28
+ // Read the joint the same way the solver writes it, or a mirrored limb reports
29
+ // a perfectly good elbow as bending backwards.
30
+ return normalizeDeg(child.worldAngle - parent.worldAngle) * sideSign(def, skeleton.view) * (def.flexSign ?? 1);
31
+ };
32
+ const jointIssues = (skeleton, rig) => {
33
+ const issues = [];
34
+ for (const hinge of HINGES) {
35
+ const angle = flexion(skeleton, hinge, rig);
36
+ if (angle < -HYPEREXTENSION_SLACK) {
37
+ issues.push({
38
+ code: hinge.hyper,
39
+ severity: 'error',
40
+ message: `${hinge.name} ("${hinge.child}") bends backwards by ${Math.abs(Math.round(angle))}°. A ${hinge.name} only flexes forwards.`,
41
+ });
42
+ }
43
+ else if (angle > hinge.maxFlexion) {
44
+ issues.push({
45
+ code: hinge.over,
46
+ severity: 'error',
47
+ message: `${hinge.name} ("${hinge.child}") is flexed ${Math.round(angle)}°, past its ${hinge.maxFlexion}° limit.`,
48
+ });
49
+ }
50
+ }
51
+ return issues;
52
+ };
53
+ const groundIssues = (skeleton) => {
54
+ const issues = [];
55
+ const lowest = skeleton.bounds.minY;
56
+ if (lowest < -GROUND_TOLERANCE) {
57
+ issues.push({
58
+ code: 'below-ground',
59
+ severity: 'error',
60
+ message: `The figure sinks ${Math.abs(lowest).toFixed(2)} below the ground line. Raise it, or set "grounded: true".`,
61
+ });
62
+ }
63
+ else if (lowest > GROUND_TOLERANCE) {
64
+ issues.push({
65
+ code: 'no-ground-contact',
66
+ severity: 'error',
67
+ message: `The figure floats ${lowest.toFixed(2)} above the ground line and touches nothing.`,
68
+ });
69
+ }
70
+ return issues;
71
+ };
72
+ /**
73
+ * The declared contact points are the pose's own claim about which parts of the
74
+ * body are on the floor. Checking them is what catches a limb that was meant to
75
+ * reach the mat and does not - the single most common authoring mistake.
76
+ */
77
+ const contactIssues = (skeleton, contact) => contact.flatMap((id) => {
78
+ const gap = skeleton.landmarks[id][1] - skeleton.bounds.minY;
79
+ if (gap <= CONTACT_TOLERANCE)
80
+ return [];
81
+ return [
82
+ {
83
+ code: 'contact-off-ground',
84
+ severity: 'error',
85
+ message: `"${id}" is declared as a contact point but sits ${gap.toFixed(2)} above the ground.`,
86
+ },
87
+ ];
88
+ });
89
+ /** Check a pose against the limits of an actual body. Empty result means it is sound. */
90
+ export const validatePose = (pose, rig = DEFAULT_RIG) => {
91
+ const skeleton = solveSkeleton(resolveFigure(pose.figure), rig);
92
+ return [...jointIssues(skeleton, rig), ...groundIssues(skeleton), ...contactIssues(skeleton, pose.contact)];
93
+ };
94
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/anatomy/validate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { Command } from 'commander';
2
+ export declare const registerExportCommands: (program: Command) => void;
3
+ //# sourceMappingURL=export.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/export.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AASzC,eAAO,MAAM,sBAAsB,GAAI,SAAS,OAAO,KAAG,IAiDzD,CAAC"}
@@ -0,0 +1,45 @@
1
+ import { DEFAULT_RIG } from '../../core/rig.js';
2
+ import { solveSkeleton } from '../../core/skeleton.js';
3
+ import { resolveFigure } from '../../model/index.js';
4
+ import { toKeypoints } from '../../standards/keypoints.js';
5
+ import { resolvePose } from '../resolve.js';
6
+ const FORMATS = ['mediapipe33', 'coco17'];
7
+ export const registerExportCommands = (program) => {
8
+ program
9
+ .command('keypoints <pose>')
10
+ .description('Export a pose as pose-estimation keypoints (MediaPipe 33 or COCO 17)')
11
+ .option('-f, --format <name>', 'mediapipe33 | coco17', 'mediapipe33')
12
+ .option('--normalize', 'emit 0..1 image coordinates with y pointing down')
13
+ .option('--lib <dir>', 'load poses from this directory')
14
+ .action(async (ref, options) => {
15
+ if (!FORMATS.includes(options.format)) {
16
+ throw new Error(`Unknown keypoint format "${options.format}". Available: ${FORMATS.join(', ')}`);
17
+ }
18
+ const pose = await resolvePose(ref, options.lib);
19
+ const skeleton = solveSkeleton(resolveFigure(pose.figure), DEFAULT_RIG);
20
+ const set = toKeypoints(skeleton, options.format, {
21
+ normalize: options.normalize === true,
22
+ });
23
+ process.stdout.write(`${JSON.stringify({ pose: pose.id, name: pose.name, ...set }, null, 2)}\n`);
24
+ });
25
+ program
26
+ .command('landmarks <pose>')
27
+ .description("Print the solved skeleton: every bone's endpoints and world angle, plus the named landmarks")
28
+ .option('--lib <dir>', 'load poses from this directory')
29
+ .action(async (ref, options) => {
30
+ const pose = await resolvePose(ref, options.lib);
31
+ const skeleton = solveSkeleton(resolveFigure(pose.figure), DEFAULT_RIG);
32
+ process.stdout.write(`${JSON.stringify({
33
+ pose: pose.id,
34
+ view: skeleton.view,
35
+ height: skeleton.height,
36
+ bounds: skeleton.bounds,
37
+ landmarks: skeleton.landmarks,
38
+ bones: Object.fromEntries(Object.entries(skeleton.bones).map(([id, b]) => [
39
+ id,
40
+ { start: b.start, end: b.end, angle: b.worldAngle, length: b.length },
41
+ ])),
42
+ }, null, 2)}\n`);
43
+ });
44
+ };
45
+ //# sourceMappingURL=export.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export.js","sourceRoot":"","sources":["../../../src/cli/commands/export.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { Command } from 'commander';
2
+ export declare const registerInfoCommands: (program: Command) => void;
3
+ //# sourceMappingURL=info.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"info.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/info.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAYzC,eAAO,MAAM,oBAAoB,GAAI,SAAS,OAAO,KAAG,IAgGvD,CAAC"}
@@ -0,0 +1,101 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { MUSCLES, MUSCLE_IDS } from '../../anatomy/muscles.js';
3
+ import { validatePose } from '../../anatomy/validate.js';
4
+ import { BONE_IDS, LANDMARK_IDS } from '../../core/types.js';
5
+ import { parsePose, poseJsonSchema, sequenceJsonSchema } from '../../model/index.js';
6
+ import { STYLES, STYLE_IDS } from '../../render/index.js';
7
+ import { library, resolvePose } from '../resolve.js';
8
+ const json = (value) => {
9
+ process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
10
+ };
11
+ export const registerInfoCommands = (program) => {
12
+ program
13
+ .command('list')
14
+ .description('List the poses and sequences in the library')
15
+ .option('-d, --discipline <name>', 'filter by discipline (yoga, surf, other)')
16
+ .option('--json', 'machine-readable output')
17
+ .option('--lib <dir>', 'load poses from this directory')
18
+ .action(async (options) => {
19
+ const lib = await library(options.lib);
20
+ const poses = [...lib.poses.values()].filter((p) => options.discipline === undefined || p.discipline === options.discipline);
21
+ if (options.json === true) {
22
+ json({
23
+ poses: poses.map((p) => ({
24
+ id: p.id,
25
+ name: p.name,
26
+ sanskrit: p.sanskrit,
27
+ discipline: p.discipline,
28
+ family: p.family,
29
+ difficulty: p.difficulty,
30
+ tags: p.tags,
31
+ })),
32
+ sequences: [...lib.sequences.values()].map((s) => ({
33
+ id: s.id,
34
+ name: s.name,
35
+ steps: s.sections.reduce((n, sec) => n + sec.steps.length, 0),
36
+ })),
37
+ });
38
+ return;
39
+ }
40
+ for (const p of poses) {
41
+ const sanskrit = p.sanskrit === undefined ? '' : ` ${p.sanskrit}`;
42
+ process.stdout.write(`${p.id.padEnd(30)} ${p.name}${sanskrit}\n`);
43
+ }
44
+ for (const s of lib.sequences.values()) {
45
+ const steps = s.sections.reduce((n, sec) => n + sec.steps.length, 0);
46
+ process.stdout.write(`\nsequence: ${s.id} - ${s.name} (${steps} steps)\n`);
47
+ }
48
+ });
49
+ program
50
+ .command('validate <files...>')
51
+ .description('Validate pose files; exits non-zero on the first problem')
52
+ .action(async (files) => {
53
+ for (const file of files) {
54
+ const pose = parsePose(await readFile(file, 'utf8'), file);
55
+ process.stdout.write(`ok ${file} (${pose.id})\n`);
56
+ }
57
+ });
58
+ program
59
+ .command('lint [poses...]')
60
+ .description('Check poses against the limits of a real body; defaults to the whole library')
61
+ .option('--lib <dir>', 'load poses from this directory')
62
+ .action(async (refs, options) => {
63
+ const lib = await library(options.lib);
64
+ const targets = refs.length === 0 ? [...lib.poses.values()] : await Promise.all(refs.map((r) => resolvePose(r, options.lib)));
65
+ let failed = 0;
66
+ for (const pose of targets) {
67
+ const issues = validatePose(pose);
68
+ if (issues.length === 0) {
69
+ process.stdout.write(`ok ${pose.id}\n`);
70
+ continue;
71
+ }
72
+ failed += 1;
73
+ for (const issue of issues)
74
+ process.stdout.write(`FAIL ${pose.id}: ${issue.message}\n`);
75
+ }
76
+ process.stdout.write(`\n${targets.length - failed}/${targets.length} poses sound\n`);
77
+ if (failed > 0)
78
+ process.exitCode = 1;
79
+ });
80
+ program
81
+ .command('schema [what]')
82
+ .description('Print the JSON Schema for the pose or sequence format')
83
+ .action((what = 'pose') => {
84
+ if (what !== 'pose' && what !== 'sequence')
85
+ throw new Error('schema takes "pose" or "sequence"');
86
+ json(what === 'pose' ? poseJsonSchema() : sequenceJsonSchema());
87
+ });
88
+ program
89
+ .command('vocab')
90
+ .description('Print every joint, landmark, muscle and style name the format accepts')
91
+ .action(() => {
92
+ json({
93
+ joints: BONE_IDS,
94
+ landmarks: LANDMARK_IDS,
95
+ muscles: MUSCLE_IDS.map((id) => ({ id, label: MUSCLES[id].label, region: MUSCLES[id].region })),
96
+ styles: STYLE_IDS.map((id) => ({ id, label: STYLES[id].label })),
97
+ views: ['front', 'back', 'side', 'three-quarter'],
98
+ });
99
+ });
100
+ };
101
+ //# sourceMappingURL=info.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"info.js","sourceRoot":"","sources":["../../../src/cli/commands/info.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { Command } from 'commander';
2
+ export declare const registerRenderCommands: (program: Command) => void;
3
+ //# sourceMappingURL=render.d.ts.map