@rig-c/pose 0.7.0 → 0.8.1

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/README.md CHANGED
@@ -38,6 +38,7 @@ Nothing is written until every check passes:
38
38
 
39
39
  and what the run cannot decide for you it lists:
40
40
 
41
+ - `RIGC` — the rig-c version and entry that gated and posed the run (also `rigc` in the report): a later rig-c can change the bytes.
41
42
  - `HOLD` — a joint missing, or scored under `--min-score`, holds its last good value; one line per span, with the frames and the reason.
42
43
  - `SHORT` — a segment shorter in the picture than `--short` of its median length (with `--short <fraction>`; off by default) has its angle there interpolated, not read; one line per span, with the chain, the segment, the frames and the lowest fraction. Below.
43
44
  - `END-ON` — a `SHORT` run lasting at least 0.15 s: the limb points at the camera, a pose the rig cannot draw.
@@ -109,6 +110,8 @@ Exit codes: `0` written, `1` refused (every reason printed, nothing written), `2
109
110
 
110
111
  **From COCO-17** — the layout most pose models and datasets use — `rig-pose convert --coco in.json --out pose.json`, or `retarget --coco`. The input is `{ "format": "coco17-sequence/1", "width", "height", "source", "frames": [{ "t", "keypoints" }] }`, keypoints in COCO's order as 17 `[x, y, score]` or 51 numbers, or `null` for a frame with nobody. The mapping: COCO's `left_*`/`right_*` → body-18's `l_*`/`r_*` (both are the subject's sides), a score of 0 → `missing`, and body-18's `neck`, which COCO lacks, is the shoulders' midpoint scored the lower of the two (OpenPose's definition).
111
112
 
113
+
114
+ **The older name.** `rig-c-pose-frames/0`, written by the extraction jobs this package's demos were made from, is the same file under another spec string. It has `space.units` "painting-px" and an `fps` key; each frame's `t` is the time read. It is read as it is.
112
115
  ## The map: `rig-pose-map/1`
113
116
 
114
117
  Which bones carry which joints. You write it for your rig:
@@ -178,9 +181,87 @@ Either way the `SWITCH` line ends with `moved on …, off …`: each boundary's
178
181
  **TURN.** The cutoff was measured on the three lines across the body before it was fixed ([#6](https://github.com/rig-c/pose/issues/6)): on the 13 s front-facing dance their picture length never drops under 0.81 of its median and none reverses; on a second sequence the hip line falls to 0.17 at frames 465–491 (side-on) and the eye line to 0.22 at 26–58 (head in profile), and 3 frames reverse. 0.5 lies between. A line is reversed when it points more than 90° from the subject's right joint to the left (front-facing, it points at 0°). The lines are read after holds and smoothing, held and smoothed as the map's points are even where the map does not use them; a line whose points are good on no frame is named as not measured.
179
182
 
180
183
 
181
- **A stopgap window: `frames`.** A set may state `"frames": [[first, last], …]`: it shows only inside those windows, and on them it is chosen over any set within tolerance that has no window. It is for two sets whose key skeletons the angles cannot tell apart. Measured on HQ's dancer, the hands clasped behind the back and the hands low in front differ by only 2–21°, and the wrist and hand-point scores do not separate them either; the cue that might is #33's depth state. It is recorded in the map as a stopgap, not a selection rule.
184
+ **A stopgap window: `frames`.** A set may state `"frames": [[first, last], …]`: it shows only inside those windows, and on them it is chosen over any set within tolerance that has no window. It is for two sets whose key skeletons the angles cannot tell apart. Measured on HQ's dancer, the hands clasped behind the back and the hands low in front differ by only 2–21°, and the wrist and hand-point scores do not separate them either; the cue that might is #33's depth state. It is recorded in the map as a stopgap, not a selection rule. A window **restricts** a set; it never forces one. Where the set does not show inside its window, the run says why in a `FRAMES` line: the stretch, and whether the pose there was past the tolerance (with the largest distance), or within it but the run was dropped.
182
185
 
183
186
  **How far a set's art may turn: `turn`.** A drawing reads right only near the angle it was painted at. A set may state, per bone with a `drawn` angle, how far its art may turn from it, `"turn": { "sleeve_r0": [-20, 20] }`. While the set shows, the bone is held within it: its target becomes drawn + clamp(wrap(target − drawn), min, max), each bone on its own world angle, so a forearm under a held upper arm keeps its own target. The landing check measures against the held angle. The overflow the pose asked for is listed as `ART-RANGE`. A `turn` on a bone the set has no `drawn` angle for is refused (`MAP_ALT_TURN`).
187
+ ## Compare: the rig's render against the video
188
+
189
+ ```sh
190
+ rig-pose compare --reference ref.pose.json --render render.pose.json --map map.json \
191
+ [--retarget-report report.json] [--reference-hands ref.wholebody.json --render-hands render.wholebody.json] \
192
+ [--cloth-reference ref.cloth.json --cloth-render render.cloth.json --cloth-windows 3.9-7.4,7.6-11.1] \
193
+ [--report compare.json] [--sheet compare.png]
194
+ ```
195
+
196
+ Polishing a retarget used to mean rendering it, having a person look, and hearing what was wrong. Most of what a person names can be measured with the same pose detector on both sides ([#38](https://github.com/rig-c/pose/issues/38)). `compare` reads two `rig-pose-sequence/1` files on one time base: the **reference** (the source video's keypoints) and the **render** (the same detector run on `rigc render` frames of the retargeted motion). It lists the differences per body unit, worst first, each with the fix it suggests. The person is then needed only for taste. The detector stays outside rig-pose, as it does for `retarget`.
197
+
198
+ The figures and bars below are fixed. They were set in [#38](https://github.com/rig-c/pose/issues/38) before any render was measured, and they are grading lines for [rig-c/exam](https://github.com/rig-c/exam). A change to one is a new comment on that issue first.
199
+
200
+ **One time base.** Both sequences have the same number of frames, and each frame's times differ by at most **1 ms**. Render the motion at the reference's rate, frame for frame. Anything else is refused (`COMPARE_FRAMES`, `COMPARE_TIMES`, with the first frames that differ). The hands files and cloth tracks are held to the same rule against the reference (`COMPARE_HANDS_FRAMES`/`_TIMES`, `COMPARE_CLOTH_FRAMES`/`_TIMES`).
201
+
202
+ **Positions are normalised per side.** A position is divided by that side's own **hip width**: the median over its frames of |r_hip − l_hip|, in picture px. Directions are compared as Spine angles of the picture segment, |wrap(render − reference)|. Every joint with a position counts, whatever its score; only hand visibility reads scores. A frame where either side lacks a point the measure needs is not measured, and it is never over the bar.
203
+
204
+ **A finding** is a run of frames over a measure's bar lasting **≥ 0.15 s**. A run is timed from the first frame's time to the end of the last frame, as `END-ON` is: 4 frames at 24 fps (0.167 s) are a finding, 3 (0.125 s) are not. Runs **one frame apart** merge, and the merged gap is not counted as over the bar. A finding's worst frame is the frame of its largest figure. Within a measure, findings are listed worst first.
205
+
206
+ | unit | measure | figure, per frame | bar | fix class |
207
+ | --- | --- | --- | --- | --- |
208
+ | head | eye-line tilt | \|direction difference\| of r_eye → l_eye | **> 15°** | not derivable |
209
+ | arms | direction, per mapped arm segment | \|direction difference\| | **> 20°** | **paint** inside a set's span, **rig** outside |
210
+ | arms | hand visibility, per hand | mean score of the hand's 21 points (or the wrist's score) on each side | reference **≥ 0.6** while render **< 0.4** | **depth** (#33) |
211
+ | body | torso length | \|mid_shoulder − mid_hip\| ÷ that side's median | the render's range **< 0.5 ×** the reference's, over the whole run | **motion** |
212
+ | body | hip vertical travel | mid_hip's y in hip widths | the same rule as torso length | **motion** |
213
+ | legs | direction, per mapped leg segment | \|direction difference\| | **> 20°** | **paint** inside a set's span, **rig** outside |
214
+ | legs | foot plant, per ankle | the ankle's speed in hip widths per frame (from the frame before) | render **> 0.05** while reference **< 0.02** | **motion** |
215
+ | cloth | lag, swing ratio, overshoot, per window | #34's three figures, on each side | outside #34's bands around the reference's figure | **rig** (physics) |
216
+
217
+ Why these bars ([#38](https://github.com/rig-c/pose/issues/38)):
218
+
219
+ - **20° for a segment's direction.** HQ's rule for a set's art is `drawn` ± 20°: the art may turn up to 20° from the angle it was painted at and still read. A bigger difference is an arm that looks different.
220
+ - **0.6 and 0.4 for a hand.** On the 13 s dance, the hands in front (S4) score 0.53–0.88 and the hands hidden behind the back (S1) score 0.41–0.57. 0.6 and 0.4 sit outside that overlap, one on each side.
221
+ - **15°** for the head's tilt, **0.05 / 0.02** hip widths per frame for a sliding foot, and **0.5 ×** of the reference's range for a missing bounce were fixed with the others before the dancer was measured.
222
+
223
+ **Units and segments.** The map says which segments are compared. An **arm** segment joins two of one side's shoulder, elbow and wrist, and a **leg** segment two of one side's hip, knee and ankle. A segment's name is `chain/bone`. Other chains, such as the trunk, the pelvis or the head, are not compared as segments. The head is measured on its eye line whatever the map says, and the body on its torso and hip. A mapped arm or leg segment whose point has a position on no frame of one side is refused (`COMPARE_JOINT_MISSING`, naming the chain, the bone, the point and the side).
224
+
225
+ **The fix class** is given only when it can be derived:
226
+
227
+ - **An arm or leg direction.** The finding's worst frame is checked against the spans in `--retarget-report` (its `switches`: each set's frames and the chains it depicts). Inside a span of a set that depicts the segment's chain, the class is **paint**: the drawing shown there is not this pose. Outside every such span it is **rig**: the art does not show the bone's direction. Without a report, a chain no set of the map depicts is **rig**. A chain some set depicts is not derivable, and the line names those sets.
228
+ - **Hand visibility** is **depth** ([#33](https://github.com/rig-c/pose/issues/33)): the hand is drawn behind something.
229
+ - **A foot slide or no bounce** is **motion**.
230
+ - **Cloth** is **rig**: the cloth is the rig's physics.
231
+ - **A head tilt** has no class in #38, so the line says it is not derivable.
232
+
233
+ **Hand visibility.** `rig-pose-sequence/1` carries body-18 only, so the hand points come from COCO-WholeBody files, one per side: `--reference-hands` and `--render-hands`. The shape is `{ "spec"?: "coco-wholebody-133", "fps"?, "width"?, "height"?, "source"?, "frames": [{ "t", "points": [[x, y, score] × 133] | null }] }`. The subject's left hand is points 91–111 and the right hand 112–132. A frame's figure is the mean score of the hand's 21 points. A `null` frame (nobody found) is not measured. Without the files, the wrist's score is read instead: an absent or `missing` wrist reads 0, as COCO's v = 0 does. The summary line then says `from wrist scores`. One of the two files without the other is refused with exit 2, because the two sides must be measured the same way.
234
+
235
+ **Body.** Each figure is read over the whole run, so the unit gives at most one finding per measure, `no bounce`, spanning every frame. Its figure is the ratio of the ranges, render ÷ reference. Its worst frame, the one the sheet draws, is the reference's frame farthest from its median. The summary line always gives both ranges.
236
+
237
+ **Cloth.** Cloth is measured only when both cloth tracks are given, with `--cloth-windows`. Without them the unit reads `not measured`. A cloth track is HQ's `rig-c-cloth-track/0`: `{ "spec": "rig-c-cloth-track/0", "subject"?, "method"?, "source"?, "fps"?, "frames_count"?, "units"?, "hipWidth": { "median", "unit": "picture px" | "rig world units" }, "origin"?, "axes"?, "skirtSwing"?, "frames": [{ "i", "t", "hip_x", "hip_y", "hip_w", "tilt_deg", "hem_mid", "hem_l", "hem_r", "hem_l_y", "hem_r_y", "hem_band_cx", "skirt_cx", "skirt_top", "skirt_bottom" }] }`, the frame keys all numbers. Every position is already divided by the subject's own median hip width, with x to the right. `hip_x` is the hips' midpoint and `hem_mid` the midpoint of the hem's leftmost and rightmost points in the lower half of the skirt ([#34](https://github.com/rig-c/pose/issues/34)'s hem); only those two and `t` are read. Any other spec, or a key outside that list, is refused (`COMPARE_CLOTH`). The three figures are #34's, as HQ's skirt criteria define them, ported from HQ's measuring script. On HQ's per-frame record of the dancer they reproduce its numbers exactly (reference A 0.958 / 2.634 / 0.061, B 0.696 / 1.409 / 0.000). Read through `rig-c-cloth-track/0`, whose values are rounded to 5 decimals, the reference's lag in A reads 0.959.
238
+
239
+ - Both signals are smoothed with a Gaussian, σ = 1 frame over ± 4 frames, ends padded, before differentiating (numpy's `gradient`).
240
+ - **lag**, in frames, hem after hip positive: the peak of the normalised cross-correlation of hip-x velocity with hem-x velocity over the window, searched over −6…+12 frames and refined by a parabola.
241
+ - **swing ratio**: per hip stroke, the hem's excursion over the hip's. A stroke runs from one hip reversal (a zero crossing of hip velocity) to the next and moves at least the stroke threshold. A wiggle under the threshold is dropped as a pair. The hem is read `round(lag)` frames after each reversal. The figure is the median over the window's strokes.
242
+ - **overshoot**: per stroke, how far the hem keeps travelling in the stroke's direction after the hips turn, within half of the next stroke (or 12 frames after the last), as a fraction of the hip stroke. The figure is the median.
243
+ - **The stroke threshold** is 0.2 *reference* hip widths: one physical size on both sides (the criteria's Revision 1), expressed in the render's own hip widths. A render track in rig world units is carried by the retarget's scale, `scale.factor` in `--retarget-report` (rig units per reference px): 0.2 × reference hip width × factor ÷ render hip width. On the dancer that is 0.2 × 128.1 × 0.54152 ÷ 92.202 = 0.1505, the criteria's own figure. Without the report it is refused (`COMPARE_CLOTH_SCALE`). A render track in picture px is carried by the torso, as the retarget scales the rig: 0.2 × (reference hip width ÷ reference torso) ÷ (render hip width ÷ render torso), each torso the sequence's median mid_hip → mid_shoulder. A reference track not in picture px is refused (`COMPARE_CLOTH_UNITS`). The `CLOTH` lines print both thresholds and how the render's was carried.
244
+ - **The bands**, per window, around the reference's own figure: lag ± 1.0 frame; swing ratio ± 25 %; overshoot ± 0.10, absolute, floored at 0. A render figure outside its band is a finding over the window's frames. A window with no stroke on one side is listed as not measured.
245
+ - A window is `t0-t1` in seconds, and windows are named A, B, … in the order given. A window's frames are `round((t − t₀) × fps)` up to the end, exclusive. A window of 12 frames or fewer is refused, since the lag search reaches 12.
246
+
247
+ **Output.** The first line gives the frames, the duration, both hip widths, and the report the spans came from. Then come one summary line per unit (`HEAD`, `ARMS`, `BODY`, `LEGS`, `CLOTH`) and one `FIND` line per finding. A summary line gives the unit's findings and, per measure, its findings, its worst figure with the subject and frame, the frames over the bar (in runs of any length), and the bar. A `FIND` line gives the unit, the measure, the subject and its points, the worst figure with both sides' readings and its frame, the frames and seconds, the frames over the bar, the bar, and the fix class with the reason. With cloth, a `CLOTH` line per window gives both sides' figures. The run ends with the number of findings.
248
+
249
+ `--report <file>` writes the same as JSON (`rig-pose-compare/1`):
250
+
251
+ - `inputs`, `frames`, `duration`, `hipWidth: { reference, render }`, `torso: { reference, render }` (medians, px);
252
+ - `rule: { findingSeconds, findingGap, timeTolerance }` and `bars` (every bar above, by name);
253
+ - `units: [{ unit, findings, notMeasured, measures: [{ measure, subjects, source, findings, overBar, notMeasured, bar, ranges? }] }]`;
254
+ - `findings: [{ unit, measure, subject, points, chain, frames: [first, last], seconds, over, worst: { frame, t, figure, reference, render }, figureUnit, bar, fix, fixWhy }]`;
255
+ - `cloth`: `null`, or `{ windows: [{ name, t, frames, reference, render, band }], stroke: { reference, render, carried } }`.
256
+
257
+ Numbers are rounded to 4 decimals, once, at the writer.
258
+
259
+ `--sheet <png>` draws the reference skeleton beside the render skeleton at each finding's worst frame, one row per finding, with the finding's own points marked in yellow. It never draws a video frame. A cloth finding is a figure over a window, with no single frame, so it has no row. When no finding has a frame, no sheet is written and the run says so.
260
+
261
+ **The control** is the reference compared with itself. It must report no finding in any unit, and every summary line says `0 findings`. On HQ's dancer (313 frames, RTMPose, with its whole-body hand points and the retarget's report) it does.
262
+
263
+ Exit codes: `0` compared, whether it found anything or not, since a finding is not a failure; `1` refused, with every reason printed; `2` bad invocation.
264
+
184
265
  ## Requirements
185
266
 
186
267
  [Bun](https://bun.sh) ≥ 1.2 on `PATH` — rig-pose, like rig-c, runs its TypeScript sources directly. `rig-c` comes as a dependency. With `@esotericsoftware/spine-core` installed beside it, rig-c's gate runs the spine-core round trip; without it, rig-c's own gate, and the gate's report says which.
package/cli.ts CHANGED
@@ -9,7 +9,9 @@
9
9
  import { readFileSync, writeFileSync } from 'node:fs';
10
10
  import pkg from './package.json' with { type: 'json' };
11
11
  import { convertCoco17, writePoseSequence } from './src/pose.ts';
12
+ import { parseWindows } from './src/compare.ts';
12
13
  import { lineOf, Refusal } from './src/problems.ts';
14
+ import { runCompare, type CompareArgs } from './src/runcompare.ts';
13
15
  import { runRetarget, type RetargetArgs } from './src/run.ts';
14
16
 
15
17
  const HELP = `rig-pose ${pkg.version} — motion from a pose sequence onto a rig-c rig
@@ -17,6 +19,7 @@ const HELP = `rig-pose ${pkg.version} — motion from a pose sequence onto a rig
17
19
  Usage:
18
20
  rig-pose retarget --rig <rig> --map <map.json> --pose <pose.json> --out <motion.json> [--name dance] [flags]
19
21
  rig-pose convert --coco <coco17.json> --out <pose.json>
22
+ rig-pose compare --reference <pose.json> --render <pose.json> --map <map.json> [flags]
20
23
  rig-pose --version | --help
21
24
 
22
25
  retarget
@@ -30,6 +33,8 @@ retarget
30
33
  --coco read --pose as coco17-sequence/1 and convert it first
31
34
  --person <id> the person to follow when the sequence holds more than one
32
35
  --base-motion <path> the rig's motion spec, when it is not motion.json beside rig.json
36
+ --images <dir> passed to rigc build: where the rig spec's images are, when its own "images" path
37
+ does not reach them (a rig.json copied away from its images)
33
38
  --min-score <s> a joint scored below s is not good on that frame and holds its last good
34
39
  value (default: 0.3); a joint with no score is good
35
40
  --smooth <n> Savitzky–Golay over n frames of joint positions: a centred least-squares cubic,
@@ -73,7 +78,30 @@ convert
73
78
  keypoints in COCO's order as 17 [x, y, score] or 51 numbers, or null
74
79
  --out <file> the rig-pose-sequence/1 it becomes; neck = the shoulders' midpoint
75
80
 
76
- Exit codes: 0 written, 1 refused (every reason printed), 2 bad invocation.
81
+ compare
82
+ --reference <file> rig-pose-sequence/1: the source video's keypoints
83
+ --render <file> rig-pose-sequence/1: the same detector on rigc's render of the retargeted motion,
84
+ rendered at the reference's rate — same frame count, times within 1 ms
85
+ --map <map.json> rig-pose-map/1: its arm and leg segments are compared
86
+ --retarget-report <f> the retarget's --report: a limb finding inside a set's span suggests paint, outside rig
87
+ --reference-hands <f> --render-hands <f>
88
+ COCO-WholeBody keypoints ({ frames: [{ t, points: [[x, y, score] x 133] }] }) for
89
+ hand visibility from the 21 hand points; without them, from the wrist scores
90
+ --cloth-reference <f> --cloth-render <f> --cloth-windows <t0-t1,...>
91
+ cloth tracks (rig-c-cloth-track/0: hip_x and hem_mid per frame, in hip widths) and
92
+ the windows, in seconds, #34's lag, swing ratio and overshoot are read over; a render
93
+ track in rig world units needs --retarget-report (its scale); without the tracks,
94
+ cloth reads "not measured"
95
+ --report <file> write the comparison as JSON (rig-pose-compare/1)
96
+ --sheet <png> draw the reference and render skeletons side by side at each finding's worst frame
97
+
98
+ A finding is a run of frames over a bar lasting >= 0.15 s (gaps of one frame merged), worst first.
99
+ The bars (README, "Compare"): arm and leg segment direction > 20 deg; hand visibility reference >= 0.6
100
+ while render < 0.4; foot plant render > 0.05 while reference < 0.02 hip widths/frame; eye-line tilt
101
+ > 15 deg; torso length and hip height range under 0.5 x the reference's; cloth outside #34's bands.
102
+ Exit 0 whenever it compared, findings or not.
103
+
104
+ Exit codes: 0 written (compare: compared), 1 refused (every reason printed), 2 bad invocation.
77
105
  `;
78
106
 
79
107
  class Usage extends Error {}
@@ -128,7 +156,7 @@ function shortOf(f: Map<string, string>): number | 'off' {
128
156
  }
129
157
 
130
158
  function retargetArgs(argv: string[]): RetargetArgs {
131
- const f = parseFlags(argv, ['rig', 'map', 'pose', 'out', 'name', 'person', 'base-motion', 'min-score', 'smooth', 'flip', 'short', 'bends', 'bend-tolerance', 'switch-tolerance', 'switch-min', 'switch-at', 'profile', 'build', 'render', 'report'], ['coco']);
159
+ const f = parseFlags(argv, ['rig', 'map', 'pose', 'out', 'name', 'person', 'base-motion', 'images', 'min-score', 'smooth', 'flip', 'short', 'bends', 'bend-tolerance', 'switch-tolerance', 'switch-min', 'switch-at', 'profile', 'build', 'render', 'report'], ['coco']);
132
160
  const bends = f.get('bends') ?? 'refuse';
133
161
  if (bends !== 'refuse' && bends !== 'clamp') throw new Usage(`--bends is "${bends}"; it must be refuse or clamp`);
134
162
  const switchAt = f.get('switch-at') ?? 'first';
@@ -146,6 +174,7 @@ function retargetArgs(argv: string[]): RetargetArgs {
146
174
  coco: f.has('coco'),
147
175
  person: f.get('person'),
148
176
  baseMotion: f.get('base-motion'),
177
+ images: f.get('images'),
149
178
  minScore: num(f, 'min-score', 0.3, (n) => n >= 0, 'a number ≥ 0'),
150
179
  smooth: smoothOf(f),
151
180
  flip: flipOf(f),
@@ -162,6 +191,36 @@ function retargetArgs(argv: string[]): RetargetArgs {
162
191
  };
163
192
  }
164
193
 
194
+ function compareArgs(argv: string[]): CompareArgs {
195
+ const f = parseFlags(argv, ['reference', 'render', 'map', 'retarget-report', 'reference-hands', 'render-hands', 'cloth-reference', 'cloth-render', 'cloth-windows', 'report', 'sheet'], []);
196
+ const pair = (x: string, y: string) => {
197
+ if (f.has(x) !== f.has(y)) throw new Usage(`--${f.has(x) ? x : y} is given without --${f.has(x) ? y : x}; the two are read together, so both sides are measured the same way`);
198
+ };
199
+ pair('reference-hands', 'render-hands');
200
+ pair('cloth-reference', 'cloth-render');
201
+ let clothWindows: CompareArgs['clothWindows'];
202
+ if (f.has('cloth-reference') && !f.has('cloth-windows')) throw new Usage('--cloth-reference and --cloth-render need --cloth-windows <t0-t1,...>: the figures are read per window');
203
+ if (f.has('cloth-windows')) {
204
+ if (!f.has('cloth-reference')) throw new Usage('--cloth-windows is given without --cloth-reference and --cloth-render');
205
+ const w = parseWindows(f.get('cloth-windows')!);
206
+ if (typeof w === 'string') throw new Usage(`--cloth-windows is "${f.get('cloth-windows')}": ${w}; it must be t0-t1[,t0-t1...] in seconds`);
207
+ clothWindows = w;
208
+ }
209
+ return {
210
+ reference: need(f, 'reference'),
211
+ render: need(f, 'render'),
212
+ map: need(f, 'map'),
213
+ retargetReport: f.get('retarget-report'),
214
+ referenceHands: f.get('reference-hands'),
215
+ renderHands: f.get('render-hands'),
216
+ clothReference: f.get('cloth-reference'),
217
+ clothRender: f.get('cloth-render'),
218
+ clothWindows,
219
+ report: f.get('report'),
220
+ sheet: f.get('sheet'),
221
+ };
222
+ }
223
+
165
224
  export function main(argv: string[]): number {
166
225
  const [cmd, ...rest] = argv;
167
226
  if (cmd === undefined || cmd === '--help' || cmd === '-h' || cmd === 'help') {
@@ -181,6 +240,10 @@ export function main(argv: string[]): number {
181
240
  runRetarget(retargetArgs(rest), (l) => process.stdout.write(`${l}\n`));
182
241
  return 0;
183
242
  }
243
+ if (cmd === 'compare') {
244
+ runCompare(compareArgs(rest), (l) => process.stdout.write(`${l}\n`));
245
+ return 0;
246
+ }
184
247
  if (cmd === 'convert') {
185
248
  const f = parseFlags(rest, ['coco', 'out'], []);
186
249
  const input = need(f, 'coco');
@@ -190,7 +253,7 @@ export function main(argv: string[]): number {
190
253
  process.stdout.write(`rig-pose convert: ${seq.frames.length} frame(s) of COCO-17 → body-18; wrote ${out}\n`);
191
254
  return 0;
192
255
  }
193
- process.stderr.write(`rig-pose: unknown command "${cmd}" — the commands are: retarget, convert\n`);
256
+ process.stderr.write(`rig-pose: unknown command "${cmd}" — the commands are: retarget, convert, compare\n`);
194
257
  return 2;
195
258
  } catch (e) {
196
259
  if (e instanceof Usage) {
@@ -199,7 +262,7 @@ export function main(argv: string[]): number {
199
262
  }
200
263
  if (e instanceof Refusal) {
201
264
  for (const p of e.problems) process.stderr.write(`FAIL ${lineOf(p)}\n`);
202
- process.stderr.write(`rig-pose ${cmd}: ${e.problems.length} problem(s) — nothing written\n`);
265
+ process.stderr.write(`rig-pose ${cmd}: ${e.problems.length} problem(s) — ${cmd === 'compare' ? 'not compared' : 'nothing written'}\n`);
203
266
  return 1;
204
267
  }
205
268
  throw e;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rig-c/pose",
3
- "version": "0.7.0",
3
+ "version": "0.8.1",
4
4
  "description": "Puts motion from a body-18 pose sequence onto a rig built with rig-c: bone keys in rig-c's motion format, checked through rig-c's gate before anything is written. A CLI for agents that cannot see the rig.",
5
5
  "type": "module",
6
6
  "bin": {