spine-parts 0.5.0 → 0.6.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/README.md +16 -5
- package/cli.ts +46 -14
- package/docs/AUTHORING.md +38 -3
- package/package.json +1 -1
- package/src/build.ts +18 -13
- package/src/check.ts +95 -9
package/README.md
CHANGED
|
@@ -38,14 +38,14 @@ is taken from the full run, because the twin tails leave the head crop sideways
|
|
|
38
38
|
<code>49 assertions: 14 measured (14 passed, 0 failed), 20 skipped, 15 not in profile "spine"</code><br/>
|
|
39
39
|
<code>loop: idle 49 frame(s) at 12 fps, f0000 vs f0048 (t = 4s): max |d| 0 (0 required)</code><br/>
|
|
40
40
|
<code>seam: setup pose at 714x1216, scale 0.9422: mean |d| 0.324 (<= 1.0), 2 px over 40 (<= 50), 0 px over 80 (reported)</code><br/>
|
|
41
|
-
<code>pack: skeleton.png
|
|
41
|
+
<code>pack: skeleton.png 967x1338, 22 region(s), 91.2% covered, padding 2, page edges free</code><br/>
|
|
42
42
|
<code>--seam silhouette</code> repaired the navy blobs the default rule leaves on the white
|
|
43
43
|
blouse (recomposite error pixels 11,050 → 9,540); the default stays
|
|
44
44
|
<code>near-white</code>, the reference implementation's rule, so the examples stay
|
|
45
45
|
comparable with it. The loop is <code>spine-parts loop --palette</code>'s indexed APNG, 48 frames at
|
|
46
|
-
12 fps, 1,706,
|
|
46
|
+
12 fps, 1,706,520 bytes, one 256-entry palette at a measured error of max 57, mean 1.601
|
|
47
47
|
per channel over every frame (the lossless APNG beside it, the exactness record, is
|
|
48
|
-
13,645,
|
|
48
|
+
13,645,350 bytes; the GIF, at the same error, 1,812,238). Frame 29 (2.417 s) is the
|
|
49
49
|
closed eye: the blink holds for 0.084 s, one 12 fps frame rounded up, where the
|
|
50
50
|
reference implementation held 0.04 s and no frame of its loop showed the eyes shut
|
|
51
51
|
(issue #32); the painting is shown at half size, resampled and written
|
|
@@ -87,7 +87,7 @@ page** (issue #2):
|
|
|
87
87
|
|
|
88
88
|
| path under `--out` | what |
|
|
89
89
|
| --- | --- |
|
|
90
|
-
| `check/build/skeleton.json`, `skeleton.atlas`, `skeleton.png` | **the artifact** — Spine 4.3 skeleton data and one packed page, written by `rigc build --pack` and gated under both profiles |
|
|
90
|
+
| `check/build/skeleton.json`, `skeleton.atlas`, `skeleton.png` | **the artifact** — Spine 4.3 skeleton data and one packed page, written by `rigc build --pack --page-edges free` and gated under both profiles |
|
|
91
91
|
| `parts/*.png`, `parts.json`, `recomposite_rig.png`, `recomposite_error_rig.png` | the loose parts, each cropped to its alpha box; the record of where every part came from, how many of its pixels were re-taken from the painting, and the recomposite's uncovered holes with their boxes; the flat stack of parts; its error map — red where no part covers a pixel the painting has, blue where a part covers it in the wrong colour |
|
|
92
92
|
| `rig/` | `rig.json` and `motion.json` in spine-rigc's spec, `mesh_report.json`, the padded `images/` |
|
|
93
93
|
| `check/` | both gate files verbatim, the idle's frames and their `geometry.json` (skinned vertices per frame), `contact.png`, `motion_heat.png`, `check.json` |
|
|
@@ -99,12 +99,23 @@ artifact paths — here for `examples/sample`:
|
|
|
99
99
|
|
|
100
100
|
```
|
|
101
101
|
build: PASS — the packed atlas is the artifact; parts/ and rig/ are the intermediates it was made from
|
|
102
|
-
pack: skeleton.png
|
|
102
|
+
pack: skeleton.png 479x1166, 20 region(s), 93.4% covered, padding 2, page edges free; page opaque 53.9% (alpha > 0) — spineboy yardstick 1024x256, 40 region(s), 45.8% opaque (alpha > 0), a reference and not a bar
|
|
103
103
|
out/check/build/skeleton.json
|
|
104
104
|
out/check/build/skeleton.atlas
|
|
105
105
|
out/check/build/skeleton.png
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
+
The page's edges are free by default: rigc picks the least-area page the parts need,
|
|
109
|
+
not the next power of two. On the two examples that is 967x1338 and 479x1166, where a
|
|
110
|
+
power-of-two page is 1024x2048 and 512x2048, and covered goes from 56.3 % to 91.2 % and
|
|
111
|
+
from 49.7 % to 93.4 %. The cost is the one rigc's help states: *"region attachments
|
|
112
|
+
sampling within 1 LSB of the loose build rather than exactly"*. Measured on both
|
|
113
|
+
examples, every idle frame stays within 1 level of the power-of-two build's. The atlas says
|
|
114
|
+
`filter: Linear, Linear` and has no `repeat` line, so `--page-edges pot` is for a
|
|
115
|
+
consumer that mipmaps or repeats the page; it writes exactly the page builds wrote
|
|
116
|
+
before the flag existed. The measurements are in
|
|
117
|
+
[AUTHORING §5, *Page edges*](docs/AUTHORING.md#5-after-each-stage-what-to-read).
|
|
118
|
+
|
|
108
119
|
The packer is spine-rigc's, and no other: a packed region is a lossless copy, and an
|
|
109
120
|
atlas written by anything else would have no oracle behind it.
|
|
110
121
|
|
package/cli.ts
CHANGED
|
@@ -18,7 +18,7 @@ import { basename, dirname, extname, join } from 'node:path';
|
|
|
18
18
|
import { fileURLToPath } from 'node:url';
|
|
19
19
|
import { DEFAULT_PROJECT_RULE, DEFAULT_SEAM_RULE, HOLES_LISTED, PROJECT_RULES, type ProjectRule, proposeFields, proposePlan, SEAM_RULES, type SeamRule } from './src/assemble.ts';
|
|
20
20
|
import { assembleStage, build, checkStage, ERROR_MAP_FILE, loopStage, readRuns, readSource, rigStage } from './src/build.ts';
|
|
21
|
-
import { findRigc, PARTS_HOME_SENTENCE, RIGC_GEOMETRY_VERSION, type RigcRunner, SEAM_MEAN_BAR, SEAM_PX_BAR, SEAM_PX_LEVEL, SPINEBOY_YARDSTICK, TEXTURE_STRETCH_CEILING } from './src/check.ts';
|
|
21
|
+
import { DEFAULT_PAGE_EDGES, findRigc, PAGE_EDGES, type PageEdges, PARTS_HOME_SENTENCE, RIGC_GEOMETRY_VERSION, type RigcRunner, SEAM_MEAN_BAR, SEAM_PX_BAR, SEAM_PX_LEVEL, SPINEBOY_YARDSTICK, TEXTURE_STRETCH_CEILING } from './src/check.ts';
|
|
22
22
|
import { ComfyClient, resolveHost, runPainting, runSeeThrough } from './src/comfy/index.ts';
|
|
23
23
|
import { type CharacterConfig, loadConfig, loadEarlyConfig } from './src/config.ts';
|
|
24
24
|
import { PartsError, problemLine } from './src/errors.ts';
|
|
@@ -98,6 +98,7 @@ usage:
|
|
|
98
98
|
printed when one was needed.
|
|
99
99
|
|
|
100
100
|
spine-parts rig --config <config.json> --parts <dir> --out <dir> [--idle-keys ctl|direct]
|
|
101
|
+
[--page-edges pot|free]
|
|
101
102
|
Author the rig: unrotated bones at the config's landmarks (a chain makes
|
|
102
103
|
<chain>0..n), a square lattice mesh over every part in config.meshes
|
|
103
104
|
weighted by distance to its candidate bone segments, a region for every
|
|
@@ -110,8 +111,8 @@ usage:
|
|
|
110
111
|
bone twice, is refused by the loader, CONFIG_BLINK_GROUP_MEMBERS or
|
|
111
112
|
CONFIG_BLINK_GROUP_UNIQUE, before rigc starts. --parts is the
|
|
112
113
|
directory holding parts.json and parts/<name>.png. The result is built
|
|
113
|
-
through spine-rigc (profile spine-html, packed
|
|
114
|
-
profile spine) in a scratch directory first, and --out receives
|
|
114
|
+
through spine-rigc (profile spine-html, packed with --page-edges as for
|
|
115
|
+
check, then validated under profile spine) in a scratch directory first, and --out receives
|
|
115
116
|
images/*.png, rig.json, motion.json and mesh_report.json only when both
|
|
116
117
|
are green. Prints one line per mesh and the rigc gate lines.
|
|
117
118
|
--idle-keys says where the idle's keys on a bone a mesh is weighted to go:
|
|
@@ -121,14 +122,14 @@ usage:
|
|
|
121
122
|
spine-rigc 1.3.0 or later and makes A15 a SKIP that prints its cost (the
|
|
122
123
|
stage prints that SKIP line). The pose is the same to one level of float
|
|
123
124
|
rounding, and so is the per-frame mesh work (AUTHORING §5).
|
|
124
|
-
spine-parts check --rig <dir> --out <dir> [--parts <dir>]
|
|
125
|
+
spine-parts check --rig <dir> --out <dir> [--parts <dir>] [--page-edges pot|free]
|
|
125
126
|
Build, gate, render and measure a rig through spine-rigc's CLI (the rigc at
|
|
126
127
|
node_modules/.bin/rigc, or on PATH). --rig holds rig.json and motion.json
|
|
127
128
|
(with an "idle").
|
|
128
129
|
${PARTS_HOME_SENTENCE}.
|
|
129
130
|
Both are only read. Into --out:
|
|
130
|
-
build/ (rigc build --profile spine-html --pack
|
|
131
|
-
artifact), gate_spine-html.txt and gate_spine.txt (the gate lines
|
|
131
|
+
build/ (rigc build --profile spine-html --pack --page-edges <value>: the
|
|
132
|
+
packed atlas is the artifact), gate_spine-html.txt and gate_spine.txt (the gate lines
|
|
132
133
|
verbatim), idle_frames/ (rigc render --animation idle --fps 12 --max 640
|
|
133
134
|
--geometry: the frames and geometry.json, the skinned vertices and
|
|
134
135
|
every bone's world transform per frame),
|
|
@@ -163,6 +164,16 @@ usage:
|
|
|
163
164
|
beside the spineboy yardstick (${SPINEBOY_YARDSTICK}), a reference and not
|
|
164
165
|
a bar. Exit 0 on PASS, 1 on FAIL — every FAIL line names the bar, the value
|
|
165
166
|
and the value required.
|
|
167
|
+
--page-edges is handed to rigc verbatim and defaults to ${DEFAULT_PAGE_EDGES}: the
|
|
168
|
+
least-area page the parts need (on the two public examples 967x1338 and
|
|
169
|
+
479x1166, where pot writes 1024x2048 and 512x2048), at the cost rigc
|
|
170
|
+
states — region attachments sample within 1 LSB of the loose build
|
|
171
|
+
rather than exactly (measured on both examples: every idle frame within
|
|
172
|
+
1 level of the pot build's; AUTHORING §5, Page edges). pot is a power of two on both edges, for a consumer
|
|
173
|
+
that mipmaps or repeats the page (the atlas declares filter Linear,
|
|
174
|
+
Linear and no repeat). rigc's pack line ends ", page edges free" under
|
|
175
|
+
free; a line that disagrees with the value passed, or a pot page that is
|
|
176
|
+
not a power of two, is refused, CHECK_PACK_PAGE_EDGES.
|
|
166
177
|
|
|
167
178
|
spine-parts loop --frames <dir> --out <file.png | file.gif> [--palette]
|
|
168
179
|
Encode a frame set rigc render wrote (its --out directory, or the set
|
|
@@ -247,7 +258,7 @@ usage:
|
|
|
247
258
|
|
|
248
259
|
spine-parts build --config <config.json> --source <painting.png> --full <dir|psd>
|
|
249
260
|
--head <dir|psd> --out <dir> [--seam near-white|silhouette]
|
|
250
|
-
[--project core|visible] [--loop]
|
|
261
|
+
[--project core|visible] [--page-edges pot|free] [--loop]
|
|
251
262
|
assemble, then rig, then check, in one process, each stage's own lines
|
|
252
263
|
printed under [assemble], [rig] and [check]; the first stage that refuses
|
|
253
264
|
stops the build with its own FAIL lines. The config must already carry
|
|
@@ -263,7 +274,8 @@ usage:
|
|
|
263
274
|
three artifact paths — skeleton .json, .atlas and the packed page: the
|
|
264
275
|
packed atlas is the result, the loose parts are the intermediate it was
|
|
265
276
|
made from. --seam defaults to ${DEFAULT_SEAM_RULE}, --project to
|
|
266
|
-
${DEFAULT_PROJECT_RULE} (both as for assemble)
|
|
277
|
+
${DEFAULT_PROJECT_RULE} (both as for assemble), --page-edges to ${DEFAULT_PAGE_EDGES}
|
|
278
|
+
(as for check; both packed builds, rig's gate and check's artifact, take it).
|
|
267
279
|
|
|
268
280
|
spine-parts --version
|
|
269
281
|
spine-parts --help
|
|
@@ -398,15 +410,26 @@ function rigGateRunner(): RigcRunner {
|
|
|
398
410
|
};
|
|
399
411
|
}
|
|
400
412
|
|
|
413
|
+
/** `--page-edges`, or its default; a value spine-rigc does not take is a usage error naming the two it does. */
|
|
414
|
+
function pageEdgesOf(value: string | undefined): PageEdges | string {
|
|
415
|
+
const edges = value ?? DEFAULT_PAGE_EDGES;
|
|
416
|
+
return (PAGE_EDGES as readonly string[]).includes(edges) ? (edges as PageEdges) : `--page-edges ${edges}; one of ${PAGE_EDGES.join(', ')} is required`;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function isPageEdges(v: PageEdges | string): v is PageEdges {
|
|
420
|
+
return (PAGE_EDGES as readonly string[]).includes(v);
|
|
421
|
+
}
|
|
422
|
+
|
|
401
423
|
function cmdRig(args: string[]): number {
|
|
402
424
|
let config: string | null = null;
|
|
403
425
|
let partsDir: string | null = null;
|
|
404
426
|
let out: string | null = null;
|
|
405
427
|
let idleKeys: string | null = null;
|
|
428
|
+
let pageEdges: string | null = null;
|
|
406
429
|
for (let i = 0; i < args.length; i++) {
|
|
407
430
|
const flag = args[i];
|
|
408
431
|
const value = args[i + 1];
|
|
409
|
-
if (!['--config', '--parts', '--out', '--idle-keys'].includes(flag)) return usage(`rig does not take "${flag}"`);
|
|
432
|
+
if (!['--config', '--parts', '--out', '--idle-keys', '--page-edges'].includes(flag)) return usage(`rig does not take "${flag}"`);
|
|
410
433
|
if (value === undefined) return usage(`${flag} needs a value`);
|
|
411
434
|
i++;
|
|
412
435
|
if (flag === '--config') config = value;
|
|
@@ -414,6 +437,9 @@ function cmdRig(args: string[]): number {
|
|
|
414
437
|
else if (flag === '--idle-keys') {
|
|
415
438
|
if (idleKeys !== null) return usage('--idle-keys is given twice');
|
|
416
439
|
idleKeys = value;
|
|
440
|
+
} else if (flag === '--page-edges') {
|
|
441
|
+
if (pageEdges !== null) return usage('--page-edges is given twice');
|
|
442
|
+
pageEdges = value;
|
|
417
443
|
} else out = value;
|
|
418
444
|
}
|
|
419
445
|
if (config === null) return usage('rig needs --config <config.json>');
|
|
@@ -421,9 +447,11 @@ function cmdRig(args: string[]): number {
|
|
|
421
447
|
if (out === null) return usage('rig needs --out <dir>');
|
|
422
448
|
const keys = idleKeys ?? DEFAULT_IDLE_KEYS;
|
|
423
449
|
if (!(IDLE_KEYS as readonly string[]).includes(keys)) return usage(`--idle-keys ${keys}; one of ${IDLE_KEYS.join(', ')} is required`);
|
|
450
|
+
const edges = pageEdgesOf(pageEdges ?? undefined);
|
|
451
|
+
if (!isPageEdges(edges)) return usage(edges);
|
|
424
452
|
const scratch = mkdtempSync(join(tmpdir(), 'spine-parts-rig-'));
|
|
425
453
|
try {
|
|
426
|
-
rigStage({ config, parts: partsDir, out, idleKeys: keys as IdleKeys }, rigGateRunner(), scratch, console.log);
|
|
454
|
+
rigStage({ config, parts: partsDir, out, idleKeys: keys as IdleKeys, pageEdges: edges }, rigGateRunner(), scratch, console.log);
|
|
427
455
|
return EXIT_OK;
|
|
428
456
|
} catch (err) {
|
|
429
457
|
return printRefusal(err);
|
|
@@ -456,13 +484,15 @@ function flags(args: string[], known: readonly string[], command: string, option
|
|
|
456
484
|
}
|
|
457
485
|
|
|
458
486
|
function cmdCheck(args: string[]): number {
|
|
459
|
-
const f = flags(args, ['--rig', '--out'], 'check', ['--parts']);
|
|
487
|
+
const f = flags(args, ['--rig', '--out'], 'check', ['--parts', '--page-edges']);
|
|
460
488
|
if (typeof f === 'string') return usage(f);
|
|
461
489
|
const rig = f.get('--rig') as string;
|
|
462
490
|
const out = f.get('--out') as string;
|
|
491
|
+
const edges = pageEdgesOf(f.get('--page-edges'));
|
|
492
|
+
if (!isPageEdges(edges)) return usage(edges);
|
|
463
493
|
try {
|
|
464
494
|
const bin = findRigc(import.meta.dir, process.env.PATH ?? '');
|
|
465
|
-
const r = checkStage({ rig, parts: f.get('--parts') ?? rig, out }, rigcRunner(bin), bin, console.log);
|
|
495
|
+
const r = checkStage({ rig, parts: f.get('--parts') ?? rig, out, pageEdges: edges }, rigcRunner(bin), bin, console.log);
|
|
466
496
|
return r.figures.PASS ? EXIT_OK : EXIT_REFUSED;
|
|
467
497
|
} catch (err) {
|
|
468
498
|
return printRefusal(err);
|
|
@@ -767,7 +797,7 @@ function cmdInputs(args: string[]): number {
|
|
|
767
797
|
function cmdBuild(args: string[]): number {
|
|
768
798
|
const flags = new Map<string, string>();
|
|
769
799
|
let loop = false;
|
|
770
|
-
const valued = ['--config', '--source', '--full', '--head', '--out', '--seam', '--project'];
|
|
800
|
+
const valued = ['--config', '--source', '--full', '--head', '--out', '--seam', '--project', '--page-edges'];
|
|
771
801
|
for (let i = 0; i < args.length; i++) {
|
|
772
802
|
const flag = args[i];
|
|
773
803
|
if (flag === '--loop') {
|
|
@@ -787,6 +817,8 @@ function cmdBuild(args: string[]): number {
|
|
|
787
817
|
if (!(SEAM_RULES as readonly string[]).includes(seam)) return usage(`--seam ${seam}; one of ${SEAM_RULES.join(', ')} is required`);
|
|
788
818
|
const project = flags.get('--project') ?? DEFAULT_PROJECT_RULE;
|
|
789
819
|
if (!(PROJECT_RULES as readonly string[]).includes(project)) return usage(`--project ${project}; one of ${PROJECT_RULES.join(', ')} is required`);
|
|
820
|
+
const edges = pageEdgesOf(flags.get('--page-edges'));
|
|
821
|
+
if (!isPageEdges(edges)) return usage(edges);
|
|
790
822
|
const [config, source, full, head, out] = ['--config', '--source', '--full', '--head', '--out'].map((f) => flags.get(f) as string);
|
|
791
823
|
let bin: string;
|
|
792
824
|
try {
|
|
@@ -797,7 +829,7 @@ function cmdBuild(args: string[]): number {
|
|
|
797
829
|
const scratch = mkdtempSync(join(tmpdir(), 'spine-parts-build-'));
|
|
798
830
|
try {
|
|
799
831
|
const r = build(
|
|
800
|
-
{ config, source, full, head, out, seam: seam as SeamRule, project: project as ProjectRule, loop },
|
|
832
|
+
{ config, source, full, head, out, seam: seam as SeamRule, project: project as ProjectRule, loop, pageEdges: edges },
|
|
801
833
|
{ rig: rigGateRunner(), check: rigcRunner(bin), checkBin: bin, scratch: join(scratch, 'rig-gate') },
|
|
802
834
|
console.log,
|
|
803
835
|
);
|
package/docs/AUTHORING.md
CHANGED
|
@@ -279,9 +279,11 @@ entry, no dithering, filter None) — and is the small file to show. `idle.gif`
|
|
|
279
279
|
(`loop --out x.gif`) is the same median cut in a GIF. The indexed APNG and the GIF
|
|
280
280
|
print their palette error, per channel over R, G and B of every frame (and alpha's
|
|
281
281
|
max for the APNG); a figure is a measurement of the file, not a bar. On the demo:
|
|
282
|
-
13,645,
|
|
282
|
+
13,645,350 B lossless, 1,706,520 B indexed and 1,812,238 B GIF, both palette files at
|
|
283
283
|
max 57, mean 1.601 — the two share one quantiser, so their error is the same by
|
|
284
|
-
construction and the size is the difference.
|
|
284
|
+
construction and the size is the difference. That is `--seam silhouette` (the README's
|
|
285
|
+
animation); with `--page-edges pot` the same build writes 13,645,519, 1,706,785 and
|
|
286
|
+
1,812,625 B at the same error. The loop shows the blink closed: the
|
|
285
287
|
eyes' hold is at least one 12 fps frame (below), so whatever `motion.blink.t` is, one
|
|
286
288
|
idle frame lands inside it, and `check.json`'s `BLINK_NO_HOLE.idle_frames_closed`
|
|
287
289
|
names it — `[29]` on both examples.
|
|
@@ -294,6 +296,36 @@ outside the figure. On the demo it lowered recomposite error pixels from 11,050
|
|
|
294
296
|
9,540 and changed no check bar; on the sample it changed nothing. The default stays
|
|
295
297
|
the reference's so the examples stay comparable with it.
|
|
296
298
|
|
|
299
|
+
**Page edges.** `rig`, `check` and `build` pack through `rigc build --pack
|
|
300
|
+
--page-edges <v>`, and hand the value to rigc verbatim. The default is `free`,
|
|
301
|
+
the least-area page the parts need. `--page-edges pot` is the opt-out: a power of
|
|
302
|
+
two on both edges, which is the page every build wrote before this flag. The pack
|
|
303
|
+
line says which one ran, because rigc ends it with `, page edges free` under `free`.
|
|
304
|
+
Measured with rigc 1.5.1 on the two examples, by `tools/atlas_population.ts`:
|
|
305
|
+
|
|
306
|
+
| build | page | covered | page opaque | page / figure |
|
|
307
|
+
| --- | --- | --- | --- | --- |
|
|
308
|
+
| demo, `pot` | 1024x2048 | 56.3 % | 36.8 % | 2.651 |
|
|
309
|
+
| demo, `free` | 967x1338 | 91.2 % | 59.7 % | 1.636 |
|
|
310
|
+
| sample, `pot` | 512x2048 | 49.7 % | 28.7 % | 3.294 |
|
|
311
|
+
| sample, `free` | 479x1166 | 93.4 % | 53.9 % | 1.755 |
|
|
312
|
+
|
|
313
|
+
rigc's help states the cost: *"a smaller page, at the cost of region attachments
|
|
314
|
+
sampling within 1 LSB of the loose build rather than exactly"*. On these builds
|
|
315
|
+
every one of the 49 idle frames differs from the `pot` build, by at most 1 level
|
|
316
|
+
in any channel:
|
|
317
|
+
|
|
318
|
+
| example | idle frames that differ | pixels that differ (all 49 frames) | max \|d\| |
|
|
319
|
+
| --- | --- | --- | --- |
|
|
320
|
+
| demo | 49 of 49 | 2,373 | 1 |
|
|
321
|
+
| sample | 49 of 49 | 651 | 1 |
|
|
322
|
+
|
|
323
|
+
Only one check figure moved. It has no bar: sample's reported screen-space face
|
|
324
|
+
mean in `STILL_REGIONS_DARK`, 11.475 under `pot` and 11.472 under `free`. Every
|
|
325
|
+
barred figure and every status is the same under both. The atlas rigc writes says
|
|
326
|
+
`filter: Linear, Linear` and has no `repeat` line, so choose `pot` for a consumer
|
|
327
|
+
that mipmaps or repeats the page.
|
|
328
|
+
|
|
297
329
|
`--project core` (the default) is the reference's projection rule: a layer takes the
|
|
298
330
|
painting's pixel only inside its top-most `alpha >= 250` area eroded by a 5x5 square,
|
|
299
331
|
so a part a few pixels wide (a lash, a brow, an iris) takes none. `--project visible`
|
|
@@ -569,6 +601,8 @@ See-through with another seed.
|
|
|
569
601
|
| `CHECK_TEXTURE_STRETCH` | a mesh triangle's edge, in some idle frame, is more than 1.926544 times its rest length or less than 1/1.926544 of it (the mesh, triangle, its three vertices, the edge and the frame are named); or a rest edge has length 0 | the chain tracks' `amps` on the bones that mesh is weighted to (the stretch grows with them), or the mesh's `segments` — a vertex blending two bones that swing against each other |
|
|
570
602
|
| `CHECK_SEAM_FRAME_SIZE`, `FRAMES_SIDECAR`, `CHECK_GEOMETRY_FILE` | rigc's render is not what its `frames.json` says, or its `geometry.json` is not a whole `rigc-geometry/1` export of the frames beside it (a frame count, a mesh whose vertex count differs from its rest entry; for the face half, another viewport than `frames.json`'s, other frame indices, the head bone or a feature's bone missing from a frame, a non-finite transform or time) | a rigc problem; report it |
|
|
571
603
|
| `LOOP_ENCODE` | the loop encoder refused a frame (translucent pixel in a GIF, a size change) | the frames; for a translucent frame write the lossless or the indexed APNG, which keep alpha |
|
|
604
|
+
| `CHECK_PACK_LINE_READS` | rigc printed a `pack:` line that is not `pack: <page> <W>x<H>, <N> region(s), <P>% covered, padding <D>` with `, page edges free` after it or nothing; the line is quoted | a rigc this package does not know the output of; report it, with `rigc --version` |
|
|
605
|
+
| `CHECK_PACK_PAGE_EDGES` | the pack line disagrees with the `--page-edges` the build was run with — its `, page edges free` suffix is there under `pot` or missing under `free`, or a `pot` page is not a power of two on both edges | a rigc problem; report it, with `rigc --version` |
|
|
572
606
|
| `BUILD_ARTIFACT_PRESENT` | the packed build lacks its `.json`, `.atlas` or page | a rigc problem; report it |
|
|
573
607
|
|
|
574
608
|
## 7. The bars `check` enforces, and what only an eye answers today
|
|
@@ -676,7 +710,8 @@ What each figure is, and is not:
|
|
|
676
710
|
- **The face is measured in the head's frame, and its bar is the model's (issue #33).**
|
|
677
711
|
Until #33 the face half read the screen-space heat map, where the head's intended
|
|
678
712
|
roll lights the outline on both examples (16.988 and 11.475, the figures now
|
|
679
|
-
reported as `screen_heat_mean
|
|
713
|
+
reported as `screen_heat_mean`; sample's reads 11.472 since the page edges
|
|
714
|
+
default to `free`, *Page edges* in §5), so its ceiling was twice the worse example,
|
|
680
715
|
33.976 — a ceiling a smooth fixture face sliding two rig pixels stayed under (24.8;
|
|
681
716
|
eight pixels measured 85.5). The judgement means *nothing moves on the face that
|
|
682
717
|
the head does not move*, so the heat is now taken after each frame's face region is
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spine-parts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "AI-authored Spine 2D character parts from one anime painting and its See-through layer decomposition: measured parts, weighted-mesh rig specs, verified through spine-rigc before anything is written. A CLI for agents that cannot see the image.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/build.ts
CHANGED
|
@@ -44,7 +44,7 @@ import { existsSync, mkdirSync, readdirSync, rmSync, writeFileSync } from 'node:
|
|
|
44
44
|
import { dirname, join } from 'node:path';
|
|
45
45
|
import { type AnimFrame, EncodeError, encodeApng, encodeIndexedApng, INDEXED_DEFAULTS } from './apng.ts';
|
|
46
46
|
import { assemble, type AssembleResult, figuresLine, holeLines, type ProjectRule, type SeamRule, stageFields } from './assemble.ts';
|
|
47
|
-
import { type CheckReport, JUDGEMENT_LINES, type JudgementLine, readFrameSet, REPORTED_LINES, type ReportedLine, type RigcRunner, runCheck, SEAM_MEAN_BAR, SEAM_PX_BAR, SEAM_PX_LEVEL, SEAM_PX_LEVEL_HIGH, SPINEBOY_YARDSTICK } from './check.ts';
|
|
47
|
+
import { type CheckReport, DEFAULT_PAGE_EDGES, JUDGEMENT_LINES, type JudgementLine, packedBuildArgs, packedBuildLabel, type PageEdges, readFrameSet, REPORTED_LINES, type ReportedLine, type RigcRunner, runCheck, SEAM_MEAN_BAR, SEAM_PX_BAR, SEAM_PX_LEVEL, SEAM_PX_LEVEL_HIGH, SPINEBOY_YARDSTICK } from './check.ts';
|
|
48
48
|
import { loadConfig, loadEarlyConfig } from './config.ts';
|
|
49
49
|
import { PartsError, type Problem, problemLine } from './errors.ts';
|
|
50
50
|
import { encodeGif } from './gif.ts';
|
|
@@ -183,21 +183,19 @@ function gateRun(label: string, rigc: RigcRunner, args: string[]): GateRun {
|
|
|
183
183
|
|
|
184
184
|
/**
|
|
185
185
|
* spine-rigc's round trip over the rig in a scratch directory: `build` under
|
|
186
|
-
* the spine-html profile with `--pack
|
|
187
|
-
* the spine profile. The files are staged exactly as `--out` will receive
|
|
186
|
+
* the spine-html profile with `--pack --page-edges <edges>`, then `validate`
|
|
187
|
+
* of that build under the spine profile. The files are staged exactly as `--out` will receive
|
|
188
188
|
* them, so what passed is what is written. The scratch directory is the
|
|
189
189
|
* caller's, and is emptied here before and after.
|
|
190
190
|
*/
|
|
191
|
-
function gateThroughRigc(out: RigOutput, texts: Array<[string, string]>, rigc: RigcRunner, scratch: string): GateRun[] {
|
|
191
|
+
function gateThroughRigc(out: RigOutput, texts: Array<[string, string]>, rigc: RigcRunner, scratch: string, edges: PageEdges): GateRun[] {
|
|
192
192
|
rmSync(scratch, { recursive: true, force: true });
|
|
193
193
|
try {
|
|
194
194
|
mkdirSync(join(scratch, 'images'), { recursive: true });
|
|
195
195
|
for (const [file, img] of out.images) writeFileSync(join(scratch, 'images', file), encodePngBytes(img));
|
|
196
196
|
for (const [file, text] of texts) writeFileSync(join(scratch, file), text);
|
|
197
197
|
const build = join(scratch, 'build');
|
|
198
|
-
const html = gateRun('build --
|
|
199
|
-
'build', '--rig', join(scratch, 'rig.json'), '--motion', join(scratch, 'motion.json'), '--out', build, '--profile', 'spine-html', '--pack',
|
|
200
|
-
]);
|
|
198
|
+
const html = gateRun(packedBuildLabel(edges), rigc, ['build', '--rig', join(scratch, 'rig.json'), '--motion', join(scratch, 'motion.json'), '--out', build, ...packedBuildArgs(edges)]);
|
|
201
199
|
if (html.status !== 0) return [html];
|
|
202
200
|
return [html, gateRun('validate --profile spine', rigc, ['validate', build, '--profile', 'spine'])];
|
|
203
201
|
} finally {
|
|
@@ -212,6 +210,8 @@ export interface RigStageInput {
|
|
|
212
210
|
out: string;
|
|
213
211
|
/** Where the idle's keys on mesh-driving bones go; `ctl` when absent. See `IDLE_KEYS` in `src/rig.ts`. */
|
|
214
212
|
idleKeys?: IdleKeys;
|
|
213
|
+
/** The gate build's `--page-edges`; {@link DEFAULT_PAGE_EDGES} when absent. The pack is scratch here; the gate is what it is for. */
|
|
214
|
+
pageEdges?: PageEdges;
|
|
215
215
|
}
|
|
216
216
|
|
|
217
217
|
/** Author the rig, gate it through rigc in `scratch`, and write `out` only when both gates are green. */
|
|
@@ -249,7 +249,7 @@ export function rigStage(input: RigStageInput, rigc: RigcRunner, scratch: string
|
|
|
249
249
|
? ` idle keys ctl: ${rig.meshKeyed.length} mesh-driving bone(s) keyed by the idle, each keyed through a same-origin <bone>_ctl parent`
|
|
250
250
|
: ` idle keys direct: ${rig.meshKeyed.length} mesh-driving bone(s) keyed in place, ${rig.rig.invariants === undefined ? 'so no invariants.idleDrivesMeshes is declared (it would switch nothing off)' : 'invariants.idleDrivesMeshes declared'}`,
|
|
251
251
|
);
|
|
252
|
-
const gate = gateThroughRigc(rig, texts, rigc, scratch);
|
|
252
|
+
const gate = gateThroughRigc(rig, texts, rigc, scratch, input.pageEdges ?? DEFAULT_PAGE_EDGES);
|
|
253
253
|
for (const g of gate) {
|
|
254
254
|
log(` rigc ${g.label}: exit ${g.status}`);
|
|
255
255
|
for (const l of g.lines) log(` ${l.trim()}`);
|
|
@@ -280,6 +280,8 @@ export interface CheckStageInput {
|
|
|
280
280
|
/** The directory holding parts.json and parts/; the rig directory when they sit there. */
|
|
281
281
|
parts: string;
|
|
282
282
|
out: string;
|
|
283
|
+
/** The packed build's `--page-edges`; {@link DEFAULT_PAGE_EDGES} when absent. */
|
|
284
|
+
pageEdges?: PageEdges;
|
|
283
285
|
}
|
|
284
286
|
|
|
285
287
|
/** The pack line as it is printed: rigc's own, then the page's opaque share beside the spineboy yardstick. */
|
|
@@ -315,8 +317,9 @@ export function checkStage(input: CheckStageInput, rigc: RigcRunner, bin: string
|
|
|
315
317
|
const v = rigc(['--version']);
|
|
316
318
|
log(`spine-parts check: ${input.rig} -> ${input.out}`);
|
|
317
319
|
log(` rigc ${v.out.trim().split('\n')[0]} at ${bin}`);
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
+
const edges = input.pageEdges ?? DEFAULT_PAGE_EDGES;
|
|
321
|
+
const r = runCheck(input.rig, input.out, rigc, input.parts, edges);
|
|
322
|
+
log(` gate spine-html (rigc ${packedBuildLabel(edges)}), verbatim:`);
|
|
320
323
|
for (const l of r.gateHtml) log(l);
|
|
321
324
|
log(' gate spine (rigc validate build --profile spine), verbatim:');
|
|
322
325
|
for (const l of r.gateSpine) log(l);
|
|
@@ -427,6 +430,8 @@ export interface BuildInput {
|
|
|
427
430
|
seam: SeamRule;
|
|
428
431
|
project: ProjectRule;
|
|
429
432
|
loop: boolean;
|
|
433
|
+
/** `--page-edges` for both packed builds, the rig stage's gate and the check's artifact. */
|
|
434
|
+
pageEdges: PageEdges;
|
|
430
435
|
}
|
|
431
436
|
|
|
432
437
|
/** The two rigc processes, as each command runs its own: `rig` gates through one, `check` measures through the other. */
|
|
@@ -487,7 +492,7 @@ export function build(input: BuildInput, run: BuildRunners, log: Log): BuildResu
|
|
|
487
492
|
const out = input.out;
|
|
488
493
|
mkdirSync(out, { recursive: true });
|
|
489
494
|
for (const p of BUILD_OWNS) rmSync(join(out, p), { recursive: true, force: true });
|
|
490
|
-
log(`spine-parts build: ${input.config} -> ${out}, seam rule ${input.seam}, projection rule ${input.project}${input.loop ? ', with the idle loop' : ''}`);
|
|
495
|
+
log(`spine-parts build: ${input.config} -> ${out}, seam rule ${input.seam}, projection rule ${input.project}, page edges ${input.pageEdges}${input.loop ? ', with the idle loop' : ''}`);
|
|
491
496
|
|
|
492
497
|
try {
|
|
493
498
|
// build runs rig next, which needs the whole config, so the full loader is
|
|
@@ -505,14 +510,14 @@ export function build(input: BuildInput, run: BuildRunners, log: Log): BuildResu
|
|
|
505
510
|
}
|
|
506
511
|
|
|
507
512
|
try {
|
|
508
|
-
rigStage({ config: input.config, parts: out, out: join(out, 'rig') }, run.rig, run.scratch, prefixed('rig'));
|
|
513
|
+
rigStage({ config: input.config, parts: out, out: join(out, 'rig'), pageEdges: input.pageEdges }, run.rig, run.scratch, prefixed('rig'));
|
|
509
514
|
} catch (err) {
|
|
510
515
|
return refused('rig', err);
|
|
511
516
|
}
|
|
512
517
|
|
|
513
518
|
let report: CheckReport;
|
|
514
519
|
try {
|
|
515
|
-
report = checkStage({ rig: join(out, 'rig'), parts: out, out: join(out, 'check') }, run.check, run.checkBin, prefixed('check'));
|
|
520
|
+
report = checkStage({ rig: join(out, 'rig'), parts: out, out: join(out, 'check'), pageEdges: input.pageEdges }, run.check, run.checkBin, prefixed('check'));
|
|
516
521
|
} catch (err) {
|
|
517
522
|
return refused('check', err);
|
|
518
523
|
}
|
package/src/check.ts
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
*
|
|
24
24
|
* | output | what |
|
|
25
25
|
* | --- | --- |
|
|
26
|
-
* | `build/` | `rigc build --profile spine-html --pack` — the packed atlas is the final artifact |
|
|
26
|
+
* | `build/` | `rigc build --profile spine-html --pack --page-edges free` (or `pot`, when the caller names it; {@link PAGE_EDGES}) — the packed atlas is the final artifact |
|
|
27
27
|
* | `gate_spine-html.txt` | that build's gate lines, verbatim |
|
|
28
28
|
* | `gate_spine.txt` | `rigc validate build/ --profile spine`'s gate lines, verbatim |
|
|
29
29
|
* | `idle_frames/` | `rigc render --animation idle --fps 12 --max 640 --geometry`: `frames.json` + `idle/f*.png` + `idle/geometry.json` (each mesh's skinned vertices, which `TEXTURE_STRETCH` reads, and every bone's world transform, which the face half of `STILL_REGIONS_DARK` reads) |
|
|
@@ -184,6 +184,40 @@ export const GEOMETRY_SPEC = 'rigc-geometry/1';
|
|
|
184
184
|
/** Issue #2's yardstick, measured on the Spine example export `spineboy.png` (alpha > 0). */
|
|
185
185
|
export const SPINEBOY_YARDSTICK = '1024x256, 40 region(s), 45.8% opaque (alpha > 0)';
|
|
186
186
|
|
|
187
|
+
/**
|
|
188
|
+
* What a packed page's edges may be: spine-rigc's `build --pack --page-edges`,
|
|
189
|
+
* handed to it verbatim (the flag is in spine-rigc's CLI since 1.4.0, the
|
|
190
|
+
* floor `check` already requires; 1.5.0 made `free` search every width).
|
|
191
|
+
* `pot` is a power of two on both edges; `free` is the least-area page the
|
|
192
|
+
* placement needs, at the cost rigc's help states: region attachments sample
|
|
193
|
+
* within 1 LSB of the loose build rather than exactly.
|
|
194
|
+
*
|
|
195
|
+
* `free` is the default here although rigc's own default is `pot`: the packed
|
|
196
|
+
* page is this package's final artifact, and on the two public examples `free`
|
|
197
|
+
* takes the page from 1024x2048 to 967x1338 (covered 56.3 % -> 91.2 %) and
|
|
198
|
+
* from 512x2048 to 479x1166 (49.7 % -> 93.4 %), rigc 1.5.1. The cost was
|
|
199
|
+
* measured on the same two builds. Each of the 49 idle frames differs from the
|
|
200
|
+
* `pot` build's, by at most 1 level in any channel (demo 2,373 px, sample 651
|
|
201
|
+
* px over all 49). On sample that moves STILL_REGIONS_DARK's reported
|
|
202
|
+
* screen-space face mean from 11.475 to 11.472; no bar or status moves. The
|
|
203
|
+
* atlas rigc writes says `filter: Linear, Linear` and has no `repeat` line, so
|
|
204
|
+
* a consumer that mipmaps or repeats the page itself is the one that wants
|
|
205
|
+
* `pot`. AUTHORING §5, *Page edges*, holds the tables.
|
|
206
|
+
*/
|
|
207
|
+
export const PAGE_EDGES = ['pot', 'free'] as const;
|
|
208
|
+
export type PageEdges = (typeof PAGE_EDGES)[number];
|
|
209
|
+
export const DEFAULT_PAGE_EDGES: PageEdges = 'free';
|
|
210
|
+
|
|
211
|
+
/** The packed build's rigc arguments after `--out <dir>`, as every stage that packs passes them. */
|
|
212
|
+
export function packedBuildArgs(edges: PageEdges): string[] {
|
|
213
|
+
return ['--profile', 'spine-html', '--pack', '--page-edges', edges];
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** The packed build as a label: the rigc command line that was run, after `rigc`. */
|
|
217
|
+
export function packedBuildLabel(edges: PageEdges): string {
|
|
218
|
+
return `build ${packedBuildArgs(edges).join(' ')}`;
|
|
219
|
+
}
|
|
220
|
+
|
|
187
221
|
// ---------------------------------------------------------------------------
|
|
188
222
|
// the rigc process, injected
|
|
189
223
|
// ---------------------------------------------------------------------------
|
|
@@ -423,19 +457,68 @@ export interface PackLine {
|
|
|
423
457
|
regions: number;
|
|
424
458
|
coveredPct: number;
|
|
425
459
|
padding: number;
|
|
460
|
+
/** `free` when the line ends `, page edges free`, `pot` when it carries no suffix — spine-rigc prints the suffix exactly when it packed under `--page-edges free` (its `cli.ts`, the pack line). */
|
|
461
|
+
pageEdges: PageEdges;
|
|
426
462
|
}
|
|
427
463
|
|
|
428
|
-
|
|
464
|
+
/** rigc's pack line, whole: the line is this and nothing else, or it is refused. */
|
|
465
|
+
const PACK = /^pack: (\S+) (\d+)x(\d+), (\d+) region\(s\), (\d+(?:\.\d+)?)% covered, padding (\d+)(, page edges free)?$/;
|
|
429
466
|
|
|
467
|
+
/**
|
|
468
|
+
* Every pack line among the gate lines — a line that reads `pack:` once its
|
|
469
|
+
* `..` gutter is taken off. A pack line spine-rigc 1.5.1 would not print (an
|
|
470
|
+
* unknown suffix, a missing field) is refused, `CHECK_PACK_LINE_READS`, naming
|
|
471
|
+
* the line and the form required: a line half-read would lose the one field
|
|
472
|
+
* that changed.
|
|
473
|
+
*/
|
|
430
474
|
export function parsePackLines(lines: readonly string[]): PackLine[] {
|
|
431
475
|
const out: PackLine[] = [];
|
|
476
|
+
const problems: Problem[] = [];
|
|
432
477
|
for (const l of lines) {
|
|
433
|
-
const
|
|
434
|
-
if (
|
|
478
|
+
const text = l.trim().replace(/^\.\.\s+/, '');
|
|
479
|
+
if (!text.startsWith('pack:')) continue;
|
|
480
|
+
const m = PACK.exec(text);
|
|
481
|
+
if (m === null) {
|
|
482
|
+
problems.push({
|
|
483
|
+
code: 'CHECK_PACK_LINE_READS',
|
|
484
|
+
object: `rigc's pack line ${JSON.stringify(text)}`,
|
|
485
|
+
detail: 'does not read as "pack: <page> <W>x<H>, <N> region(s), <P>% covered, padding <D>", with ", page edges free" after it or nothing; that form is required',
|
|
486
|
+
});
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
out.push({ line: text, page: m[1], width: Number(m[2]), height: Number(m[3]), regions: Number(m[4]), coveredPct: Number(m[5]), padding: Number(m[6]), pageEdges: m[7] === undefined ? 'pot' : 'free' });
|
|
435
490
|
}
|
|
491
|
+
refuseIfAny(problems);
|
|
436
492
|
return out;
|
|
437
493
|
}
|
|
438
494
|
|
|
495
|
+
function powerOfTwo(n: number): boolean {
|
|
496
|
+
return Number.isInteger(n) && n > 0 && (n & (n - 1)) === 0;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Where a pack line disagrees with the `--page-edges` the build was given:
|
|
501
|
+
* the line's own suffix must say the same, and a `pot` page must be a power of
|
|
502
|
+
* two on both edges, as rigc's help defines it. One problem per disagreement,
|
|
503
|
+
* `CHECK_PACK_PAGE_EDGES`; empty is agreement.
|
|
504
|
+
*/
|
|
505
|
+
export function packEdgeProblems(pack: readonly PackLine[], edges: PageEdges): Problem[] {
|
|
506
|
+
const problems: Problem[] = [];
|
|
507
|
+
for (const p of pack) {
|
|
508
|
+
const object = `packed page ${p.page}`;
|
|
509
|
+
if (p.pageEdges !== edges) {
|
|
510
|
+
problems.push({
|
|
511
|
+
code: 'CHECK_PACK_PAGE_EDGES',
|
|
512
|
+
object,
|
|
513
|
+
detail: `rigc's pack line says page edges ${p.pageEdges} (${p.pageEdges === 'free' ? 'it ends ", page edges free"' : 'no ", page edges free" suffix'}); the build was run with --page-edges ${edges}, so page edges ${edges} is required`,
|
|
514
|
+
});
|
|
515
|
+
} else if (edges === 'pot' && !(powerOfTwo(p.width) && powerOfTwo(p.height))) {
|
|
516
|
+
problems.push({ code: 'CHECK_PACK_PAGE_EDGES', object, detail: `is ${p.width}x${p.height}; the build was run with --page-edges pot, so a power of two on both edges is required` });
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
return problems;
|
|
520
|
+
}
|
|
521
|
+
|
|
439
522
|
/** Share of a page's pixels with alpha above 0 — the measure issue #2's spineboy figure is in. rigc's "covered" is region rectangles, not pixels. */
|
|
440
523
|
export function opaqueShare(r: Raster): number {
|
|
441
524
|
let n = 0;
|
|
@@ -745,9 +828,11 @@ function rigcFailed(what: string, call: RigcCall, lines: readonly string[]): Pro
|
|
|
745
828
|
* gate file is written first, so the refusal's evidence is on disk. Otherwise
|
|
746
829
|
* it measures everything, writes every output including `check.json`, and
|
|
747
830
|
* returns the report; `report.problems` names each bar that was not met, and
|
|
748
|
-
* `figures.PASS` is true exactly when it is empty.
|
|
831
|
+
* `figures.PASS` is true exactly when it is empty. `pageEdges` reaches rigc's
|
|
832
|
+
* `--page-edges` verbatim, and a pack line that disagrees with it is refused
|
|
833
|
+
* (`CHECK_PACK_PAGE_EDGES`).
|
|
749
834
|
*/
|
|
750
|
-
export function runCheck(rigDir: string, outDir: string, rigc: RigcRunner, partsHome: string = rigDir): CheckReport {
|
|
835
|
+
export function runCheck(rigDir: string, outDir: string, rigc: RigcRunner, partsHome: string = rigDir, pageEdges: PageEdges = DEFAULT_PAGE_EDGES): CheckReport {
|
|
751
836
|
const inp = readCheckInputs(rigDir, partsHome);
|
|
752
837
|
requireRigcVersion(rigc);
|
|
753
838
|
const out = resolve(outDir);
|
|
@@ -764,11 +849,12 @@ export function runCheck(rigDir: string, outDir: string, rigc: RigcRunner, parts
|
|
|
764
849
|
};
|
|
765
850
|
|
|
766
851
|
// 1. build, packed, under the policy profile
|
|
767
|
-
const build = rigc(['build', '--rig', inp.rigPath, '--motion', inp.motionPath, '--out', buildDir,
|
|
852
|
+
const build = rigc(['build', '--rig', inp.rigPath, '--motion', inp.motionPath, '--out', buildDir, ...packedBuildArgs(pageEdges)]);
|
|
768
853
|
const gateHtml = buildGateLines(build.out);
|
|
769
854
|
write('gate_spine-html.txt', `${gateHtml.join('\n')}\n`);
|
|
770
|
-
if (build.status !== 0) refuseIfAny(rigcFailed(
|
|
855
|
+
if (build.status !== 0) refuseIfAny(rigcFailed(packedBuildLabel(pageEdges), build, gateHtml));
|
|
771
856
|
const pack = parsePackLines(gateHtml);
|
|
857
|
+
refuseIfAny(packEdgeProblems(pack, pageEdges));
|
|
772
858
|
const packOpaque = pack.filter((p) => existsSync(join(buildDir, p.page))).map((p) => ({ page: p.page, share: opaqueShare(readPng(join(buildDir, p.page))) }));
|
|
773
859
|
|
|
774
860
|
// 2. validate under the validity profile
|
|
@@ -907,7 +993,7 @@ export function runCheck(rigDir: string, outDir: string, rigc: RigcRunner, parts
|
|
|
907
993
|
PASS: false,
|
|
908
994
|
};
|
|
909
995
|
const barProblems: Problem[] = [];
|
|
910
|
-
if (!htmlGreen) barProblems.push(...rigcFailed(
|
|
996
|
+
if (!htmlGreen) barProblems.push(...rigcFailed(packedBuildLabel(pageEdges), build, gateHtml));
|
|
911
997
|
if (!spineGreen) barProblems.push(...rigcFailed('validate --profile spine', validate, gateSpine));
|
|
912
998
|
if (figures.loop_max_diff !== LOOP_MAX_BAR) {
|
|
913
999
|
barProblems.push({
|