@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.
- package/LICENSE +21 -0
- package/README.md +153 -0
- package/dist/anatomy/muscles.d.ts +55 -0
- package/dist/anatomy/muscles.d.ts.map +1 -0
- package/dist/anatomy/muscles.js +79 -0
- package/dist/anatomy/muscles.js.map +1 -0
- package/dist/anatomy/validate.d.ts +11 -0
- package/dist/anatomy/validate.d.ts.map +1 -0
- package/dist/anatomy/validate.js +94 -0
- package/dist/anatomy/validate.js.map +1 -0
- package/dist/cli/commands/export.d.ts +3 -0
- package/dist/cli/commands/export.d.ts.map +1 -0
- package/dist/cli/commands/export.js +45 -0
- package/dist/cli/commands/export.js.map +1 -0
- package/dist/cli/commands/info.d.ts +3 -0
- package/dist/cli/commands/info.d.ts.map +1 -0
- package/dist/cli/commands/info.js +101 -0
- package/dist/cli/commands/info.js.map +1 -0
- package/dist/cli/commands/render.d.ts +3 -0
- package/dist/cli/commands/render.d.ts.map +1 -0
- package/dist/cli/commands/render.js +103 -0
- package/dist/cli/commands/render.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +31 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/resolve.d.ts +12 -0
- package/dist/cli/resolve.d.ts.map +1 -0
- package/dist/cli/resolve.js +46 -0
- package/dist/cli/resolve.js.map +1 -0
- package/dist/core/angles.d.ts +12 -0
- package/dist/core/angles.d.ts.map +1 -0
- package/dist/core/angles.js +27 -0
- package/dist/core/angles.js.map +1 -0
- package/dist/core/rig.d.ts +8 -0
- package/dist/core/rig.d.ts.map +1 -0
- package/dist/core/rig.js +97 -0
- package/dist/core/rig.js.map +1 -0
- package/dist/core/skeleton.d.ts +17 -0
- package/dist/core/skeleton.d.ts.map +1 -0
- package/dist/core/skeleton.js +144 -0
- package/dist/core/skeleton.js.map +1 -0
- package/dist/core/types.d.ts +105 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +47 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/vec2.d.ts +19 -0
- package/dist/core/vec2.d.ts.map +1 -0
- package/dist/core/vec2.js +41 -0
- package/dist/core/vec2.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/library/index.d.ts +27 -0
- package/dist/library/index.d.ts.map +1 -0
- package/dist/library/index.js +75 -0
- package/dist/library/index.js.map +1 -0
- package/dist/model/index.d.ts +4 -0
- package/dist/model/index.d.ts.map +1 -0
- package/dist/model/index.js +4 -0
- package/dist/model/index.js.map +1 -0
- package/dist/model/parse.d.ts +12 -0
- package/dist/model/parse.d.ts.map +1 -0
- package/dist/model/parse.js +43 -0
- package/dist/model/parse.js.map +1 -0
- package/dist/model/pose.d.ts +11 -0
- package/dist/model/pose.d.ts.map +1 -0
- package/dist/model/pose.js +26 -0
- package/dist/model/pose.js.map +1 -0
- package/dist/model/schema.d.ts +1122 -0
- package/dist/model/schema.d.ts.map +1 -0
- package/dist/model/schema.js +178 -0
- package/dist/model/schema.js.map +1 -0
- package/dist/render/anatomy.d.ts +10 -0
- package/dist/render/anatomy.d.ts.map +1 -0
- package/dist/render/anatomy.js +77 -0
- package/dist/render/anatomy.js.map +1 -0
- package/dist/render/annotations.d.ts +9 -0
- package/dist/render/annotations.d.ts.map +1 -0
- package/dist/render/annotations.js +185 -0
- package/dist/render/annotations.js.map +1 -0
- package/dist/render/context.d.ts +16 -0
- package/dist/render/context.d.ts.map +1 -0
- package/dist/render/context.js +2 -0
- package/dist/render/context.js.map +1 -0
- package/dist/render/figure.d.ts +8 -0
- package/dist/render/figure.d.ts.map +1 -0
- package/dist/render/figure.js +138 -0
- package/dist/render/figure.js.map +1 -0
- package/dist/render/index.d.ts +7 -0
- package/dist/render/index.d.ts.map +1 -0
- package/dist/render/index.js +7 -0
- package/dist/render/index.js.map +1 -0
- package/dist/render/project.d.ts +18 -0
- package/dist/render/project.d.ts.map +1 -0
- package/dist/render/project.js +35 -0
- package/dist/render/project.js.map +1 -0
- package/dist/render/props.d.ts +9 -0
- package/dist/render/props.d.ts.map +1 -0
- package/dist/render/props.js +178 -0
- package/dist/render/props.js.map +1 -0
- package/dist/render/raster.d.ts +19 -0
- package/dist/render/raster.d.ts.map +1 -0
- package/dist/render/raster.js +39 -0
- package/dist/render/raster.js.map +1 -0
- package/dist/render/scene.d.ts +26 -0
- package/dist/render/scene.d.ts.map +1 -0
- package/dist/render/scene.js +144 -0
- package/dist/render/scene.js.map +1 -0
- package/dist/render/sheet.d.ts +20 -0
- package/dist/render/sheet.d.ts.map +1 -0
- package/dist/render/sheet.js +74 -0
- package/dist/render/sheet.js.map +1 -0
- package/dist/render/styles.d.ts +77 -0
- package/dist/render/styles.d.ts.map +1 -0
- package/dist/render/styles.js +122 -0
- package/dist/render/styles.js.map +1 -0
- package/dist/render/svg.d.ts +19 -0
- package/dist/render/svg.d.ts.map +1 -0
- package/dist/render/svg.js +40 -0
- package/dist/render/svg.js.map +1 -0
- package/dist/standards/keypoints.d.ts +31 -0
- package/dist/standards/keypoints.d.ts.map +1 -0
- package/dist/standards/keypoints.js +118 -0
- package/dist/standards/keypoints.js.map +1 -0
- package/docs/AUTHORING.md +144 -0
- package/package.json +94 -0
- package/poses/surf/bottom-turn.pose.yaml +47 -0
- package/poses/surf/cutback.pose.yaml +47 -0
- package/poses/surf/duck-dive.pose.yaml +47 -0
- package/poses/surf/noseride.pose.yaml +48 -0
- package/poses/surf/paddling.pose.yaml +48 -0
- package/poses/surf/pop-up.pose.yaml +47 -0
- package/poses/surf/takeoff.pose.yaml +47 -0
- package/poses/surf/tube-stance.pose.yaml +47 -0
- package/poses/yoga/ashtanga/adho-mukha-svanasana.pose.yaml +42 -0
- package/poses/yoga/ashtanga/ardha-uttanasana.pose.yaml +42 -0
- package/poses/yoga/ashtanga/baddha-konasana.pose.yaml +42 -0
- package/poses/yoga/ashtanga/balasana.pose.yaml +43 -0
- package/poses/yoga/ashtanga/chaturanga-dandasana.pose.yaml +41 -0
- package/poses/yoga/ashtanga/dandasana.pose.yaml +42 -0
- package/poses/yoga/ashtanga/halasana.pose.yaml +43 -0
- package/poses/yoga/ashtanga/janu-sirsasana-a.pose.yaml +42 -0
- package/poses/yoga/ashtanga/karnapidasana.pose.yaml +43 -0
- package/poses/yoga/ashtanga/marichyasana-a.pose.yaml +42 -0
- package/poses/yoga/ashtanga/marichyasana-c.pose.yaml +42 -0
- package/poses/yoga/ashtanga/matsyasana.pose.yaml +43 -0
- package/poses/yoga/ashtanga/navasana.pose.yaml +42 -0
- package/poses/yoga/ashtanga/padahastasana.pose.yaml +42 -0
- package/poses/yoga/ashtanga/padangusthasana.pose.yaml +42 -0
- package/poses/yoga/ashtanga/padmasana.pose.yaml +43 -0
- package/poses/yoga/ashtanga/parsvottanasana.pose.yaml +42 -0
- package/poses/yoga/ashtanga/paschimottanasana.pose.yaml +42 -0
- package/poses/yoga/ashtanga/prasarita-padottanasana-a.pose.yaml +46 -0
- package/poses/yoga/ashtanga/prasarita-padottanasana-c.pose.yaml +46 -0
- package/poses/yoga/ashtanga/primary.seq.yaml +74 -0
- package/poses/yoga/ashtanga/purvottanasana.pose.yaml +42 -0
- package/poses/yoga/ashtanga/salamba-sarvangasana.pose.yaml +43 -0
- package/poses/yoga/ashtanga/samasthiti.pose.yaml +29 -0
- package/poses/yoga/ashtanga/savasana.pose.yaml +43 -0
- package/poses/yoga/ashtanga/setu-bandhasana.pose.yaml +42 -0
- package/poses/yoga/ashtanga/sirsasana.pose.yaml +43 -0
- package/poses/yoga/ashtanga/supta-padangusthasana.pose.yaml +42 -0
- package/poses/yoga/ashtanga/upavistha-konasana.pose.yaml +42 -0
- package/poses/yoga/ashtanga/urdhva-dhanurasana.pose.yaml +43 -0
- package/poses/yoga/ashtanga/urdhva-hastasana.pose.yaml +42 -0
- package/poses/yoga/ashtanga/urdhva-mukha-svanasana.pose.yaml +42 -0
- package/poses/yoga/ashtanga/utkatasana.pose.yaml +42 -0
- package/poses/yoga/ashtanga/uttanasana.pose.yaml +42 -0
- package/poses/yoga/ashtanga/utthita-parsvakonasana.pose.yaml +44 -0
- package/poses/yoga/ashtanga/utthita-trikonasana.pose.yaml +44 -0
- package/poses/yoga/ashtanga/virabhadrasana-a.pose.yaml +42 -0
- 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
|
+

|
|
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
|
+

|
|
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
|
+

|
|
55
|
+
|
|
56
|
+
## Surf
|
|
57
|
+
|
|
58
|
+

|
|
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":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAoDvC,qGAAqG;AACrG,MAAM,WAAW,GAAwB,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,CAAC,CAAC;AAEhG,MAAM,YAAY,GAAG,CAAC,IAAc,EAAU,EAAE,CAAC,CAAC,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAEvF,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,SAAS;IACT,aAAa;IACb,QAAQ;IACR,SAAS;IACT,gBAAgB;IAChB,YAAY;IACZ,YAAY;IACZ,WAAW;IACX,UAAU;IACV,eAAe;IACf,iBAAiB;IACjB,UAAU;IACV,SAAS;IACT,YAAY;IACZ,YAAY;IACZ,YAAY;IACZ,WAAW;IACX,eAAe;IACf,kBAAkB;CACV,CAAC;AAIX,MAAM,CAAC,MAAM,OAAO,GAAgC;IAClD,OAAO,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,qBAAqB,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE;IACzK,WAAW,EAAE,EAAE,EAAE,EAAE,aAAa,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,mBAAmB,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE;IACxL,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,yBAAyB,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE;IAC3K,OAAO,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,0BAA0B,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE;IAChL,cAAc,EAAE,EAAE,EAAE,EAAE,gBAAgB,EAAE,KAAK,EAAE,iBAAiB,EAAE,KAAK,EAAE,sBAAsB,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE;IAEjM,UAAU,EAAE,EAAE,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,2BAA2B,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE;IACtL,UAAU,EAAE,EAAE,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,2BAA2B,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE;IACrL,SAAS,EAAE,EAAE,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,oBAAoB,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE;IAChL,QAAQ,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,4BAA4B,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE;IACpL,aAAa,EAAE,EAAE,EAAE,EAAE,eAAe,EAAE,KAAK,EAAE,iBAAiB,EAAE,KAAK,EAAE,0BAA0B,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE;IAClM,eAAe,EAAE,EAAE,EAAE,EAAE,iBAAiB,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,2BAA2B,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE;IAC/L,QAAQ,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,2BAA2B,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE;IAElL,OAAO,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,0BAA0B,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE;IAC9K,UAAU,EAAE,EAAE,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,oBAAoB,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE;IAClL,UAAU,EAAE,EAAE,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,6BAA6B,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE;IAC1L,UAAU,EAAE,EAAE,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,wBAAwB,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE;IACtL,SAAS,EAAE,EAAE,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,oBAAoB,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE;IAC3K,aAAa,EAAE,EAAE,EAAE,EAAE,eAAe,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,wBAAwB,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE;IACvL,gBAAgB,EAAE,EAAE,EAAE,EAAE,kBAAkB,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,4BAA4B,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE;CAC9L,CAAC;AAEF,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,KAAa,EAAqB,EAAE,CAAC,KAAK,IAAI,OAAO,CAAC;AAQjF,MAAM,aAAa,GAAG,CAAC,MAAiB,EAAW,EAAE,CAAC,WAAW,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AAExF;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,MAAiB,EAAE,IAAY,EAAoB,EAAE;IACnF,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,YAAY,GAAG,CAAC,CAAC;IAC7C,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,GAAG,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAE7D,IAAI,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,GAAG,MAAM,CAAC,IAAc,CAAC;QACnC,IAAI,OAAO;YAAE,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;QACjD,IAAI,MAAM,CAAC,OAAO,KAAK,CAAC;YAAE,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;QACvD,OAAO;YACL,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,OAAO,EAAE;YAChC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE;SAClC,CAAC;IACJ,CAAC;IAED,6EAA6E;IAC7E,6EAA6E;IAC7E,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;IACnD,OAAO;QACL,EAAE,IAAI,EAAE,GAAG,MAAM,CAAC,IAAI,GAAa,EAAE,MAAM,EAAE;QAC7C,EAAE,IAAI,EAAE,GAAG,MAAM,CAAC,IAAI,GAAa,EAAE,MAAM,EAAE;KAC9C,CAAC;AACJ,CAAC,CAAC"}
|
|
@@ -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 @@
|
|
|
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 @@
|
|
|
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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"}
|