spine-parts 0.6.0 → 0.8.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/NOTICE.md +17 -6
- package/README.md +32 -17
- package/cli.ts +73 -38
- package/docs/AUTHORING.md +77 -16
- package/package.json +3 -2
- package/skills/spine-parts/SKILL.md +4 -2
- package/src/build.ts +62 -34
- package/src/check.ts +237 -54
package/NOTICE.md
CHANGED
|
@@ -60,10 +60,15 @@ from them beyond the layers See-through itself writes for the user's own image.
|
|
|
60
60
|
## 2. Spine Runtimes, through spine-rigc
|
|
61
61
|
|
|
62
62
|
spine-parts depends on [`spine-rigc`](https://www.npmjs.com/package/spine-rigc)
|
|
63
|
-
(MIT, same author), which compiles and validates Spine skeleton data
|
|
64
|
-
`@esotericsoftware/spine-core`, part of the
|
|
63
|
+
(MIT, same author), which compiles and validates Spine skeleton data. Its round
|
|
64
|
+
trip runs through `@esotericsoftware/spine-core`, part of the
|
|
65
65
|
[Spine Runtimes](https://github.com/EsotericSoftware/spine-runtimes), under the
|
|
66
66
|
[Spine Runtimes License Agreement](https://esotericsoftware.com/spine-runtimes-license).
|
|
67
|
+
Since spine-rigc 2.0 neither package installs the runtime: spine-rigc carries it
|
|
68
|
+
only as a development dependency, and an install of spine-rigc gates a build with
|
|
69
|
+
its own validator. spine-parts does not install it either; it is this repository's
|
|
70
|
+
development dependency, at the version spine-rigc develops against, for the round
|
|
71
|
+
trip in the selftest and CI and the selftest's posing oracle.
|
|
67
72
|
spine-rigc's [NOTICE.md](https://github.com/firejune/rigc/blob/main/NOTICE.md)
|
|
68
73
|
sets out the chain in full; briefly, as a restatement of Esoteric Software's
|
|
69
74
|
terms and not a term of this project:
|
|
@@ -73,8 +78,11 @@ terms and not a term of this project:
|
|
|
73
78
|
2. Playing Spine skeleton data in a product requires a Spine Runtime, and the
|
|
74
79
|
Spine Runtimes License requires **each user of such a product to own a
|
|
75
80
|
Spine Editor license**.
|
|
76
|
-
3. spine-rigc
|
|
77
|
-
|
|
81
|
+
3. spine-rigc no longer installs `spine-core`, and spine-parts does not either;
|
|
82
|
+
the obligation in 2 attaches to the Spine skeleton data spine-rigc writes,
|
|
83
|
+
which the stages of spine-parts that call it produce, and to `spine-core`
|
|
84
|
+
wherever it is installed beside them — this repository's development
|
|
85
|
+
dependency included.
|
|
78
86
|
|
|
79
87
|
spine-parts also imports spine-rigc's own modules directly, by deep path: its PNG codec
|
|
80
88
|
(`tools/plate.ts`, `src/png.ts`), its 5x7 label font (`tools/font5x7.ts`) and
|
|
@@ -114,10 +122,13 @@ function's comment says which call it stands in for and what was measured.
|
|
|
114
122
|
| Package | Licence | Why |
|
|
115
123
|
| --- | --- | --- |
|
|
116
124
|
| [`spine-rigc`](https://www.npmjs.com/package/spine-rigc) | MIT | compile, gate, render and check; its PNG codec, font and coordinate conversion |
|
|
117
|
-
| ↳ [`@esotericsoftware/spine-core`](https://www.npmjs.com/package/@esotericsoftware/spine-core) | Spine Runtimes License | spine-rigc's round trip (section 2) |
|
|
118
125
|
| [`ag-psd`](https://www.npmjs.com/package/ag-psd) | MIT | reading an upstream See-through `.psd` |
|
|
119
126
|
| ↳ [`pako`](https://www.npmjs.com/package/pako) | MIT AND Zlib | ag-psd's deflate |
|
|
120
127
|
| ↳ [`base64-js`](https://www.npmjs.com/package/base64-js) | MIT | ag-psd's base64 |
|
|
121
128
|
|
|
122
129
|
Development only, not installed with the package: `typescript` (Apache-2.0),
|
|
123
|
-
`eslint` (MIT), `typescript-eslint` (MIT), `@types/bun` (MIT)
|
|
130
|
+
`eslint` (MIT), `typescript-eslint` (MIT), `@types/bun` (MIT), and
|
|
131
|
+
[`@esotericsoftware/spine-core`](https://www.npmjs.com/package/@esotericsoftware/spine-core)
|
|
132
|
+
(Spine Runtimes License; section 2), pinned to the version spine-rigc develops
|
|
133
|
+
against (`TY10`): the round trip in the selftest and CI, the selftest's posing
|
|
134
|
+
oracle, and `tools/atlas_population.ts` and `tools/idle_cost.ts`.
|
package/README.md
CHANGED
|
@@ -34,18 +34,17 @@ was shifted down into the canvas (its proposal ran 118 px above the top edge;
|
|
|
34
34
|
<code>propose --head-box</code> now makes that shift itself), and <code>hair_back</code>
|
|
35
35
|
is taken from the full run, because the twin tails leave the head crop sideways
|
|
36
36
|
(a plan edit). <code>check</code> printed, verbatim:<br/>
|
|
37
|
-
<code>
|
|
38
|
-
<code>49 assertions: 14 measured (14 passed, 0 failed), 20 skipped, 15 not in profile "spine"</code><br/>
|
|
37
|
+
<code>50 assertions: 24 measured (24 passed, 0 failed), 26 skipped, 0 not in profile "spine-html"</code><br/>
|
|
39
38
|
<code>loop: idle 49 frame(s) at 12 fps, f0000 vs f0048 (t = 4s): max |d| 0 (0 required)</code><br/>
|
|
40
39
|
<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
|
|
40
|
+
<code>pack: skeleton.png 922x1348, 22 region(s), 95.0% covered, padding 2, page edges free, shape polygon</code><br/>
|
|
42
41
|
<code>--seam silhouette</code> repaired the navy blobs the default rule leaves on the white
|
|
43
42
|
blouse (recomposite error pixels 11,050 → 9,540); the default stays
|
|
44
43
|
<code>near-white</code>, the reference implementation's rule, so the examples stay
|
|
45
44
|
comparable with it. The loop is <code>spine-parts loop --palette</code>'s indexed APNG, 48 frames at
|
|
46
|
-
12 fps, 1,706,
|
|
45
|
+
12 fps, 1,706,719 bytes, one 256-entry palette at a measured error of max 57, mean 1.601
|
|
47
46
|
per channel over every frame (the lossless APNG beside it, the exactness record, is
|
|
48
|
-
13,645,
|
|
47
|
+
13,645,503 bytes; the GIF, at the same error, 1,812,288). Frame 29 (2.417 s) is the
|
|
49
48
|
closed eye: the blink holds for 0.084 s, one 12 fps frame rounded up, where the
|
|
50
49
|
reference implementation held 0.04 s and no frame of its loop showed the eyes shut
|
|
51
50
|
(issue #32); the painting is shown at half size, resampled and written
|
|
@@ -87,10 +86,10 @@ page** (issue #2):
|
|
|
87
86
|
|
|
88
87
|
| path under `--out` | what |
|
|
89
88
|
| --- | --- |
|
|
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
|
|
89
|
+
| `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 --pack-shape polygon` and gated under `spine-html`, which holds every rule `spine` measures (on the demo, rigc 2.1.3: the 14 rules `validate --profile spine` measures are among the build's 24; 16 are not in `spine`) |
|
|
91
90
|
| `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
91
|
| `rig/` | `rig.json` and `motion.json` in spine-rigc's spec, `mesh_report.json`, the padded `images/` |
|
|
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` |
|
|
92
|
+
| `check/` | both gate files verbatim, the idle's frames and their `geometry.json` (skinned vertices per frame), `contact.png`, `motion_heat.png`, `check.json` (with `pack_mode`, the `--page-edges` and `--pack-shape` the page was packed under) |
|
|
94
93
|
| `idle.png`, `idle-indexed.png`, `idle.gif` | with `--loop`: the idle as a lossless APNG (the exactness record), an indexed APNG with one shared palette (the small one) and a GIF; the last two print their palette error |
|
|
95
94
|
|
|
96
95
|
The last lines of a green build are the pack line, printed beside the Spine example
|
|
@@ -99,15 +98,15 @@ artifact paths — here for `examples/sample`:
|
|
|
99
98
|
|
|
100
99
|
```
|
|
101
100
|
build: PASS — the packed atlas is the artifact; parts/ and rig/ are the intermediates it was made from
|
|
102
|
-
pack: skeleton.png
|
|
101
|
+
pack: skeleton.png 477x1151, 20 region(s), 95.0% covered, padding 2, page edges free, shape polygon; page opaque 54.8% (alpha > 0) — spineboy yardstick 1024x256, 40 region(s), 45.8% opaque (alpha > 0), a reference and not a bar
|
|
103
102
|
out/check/build/skeleton.json
|
|
104
103
|
out/check/build/skeleton.atlas
|
|
105
104
|
out/check/build/skeleton.png
|
|
106
105
|
```
|
|
107
106
|
|
|
108
107
|
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
|
|
110
|
-
power-of-two page is 1024x2048 and 512x2048, and covered goes from 56.3 % to 91.2 % and
|
|
108
|
+
not the next power of two. On the two examples, packed by rectangles, that is 967x1338
|
|
109
|
+
and 479x1166, where a power-of-two page is 1024x2048 and 512x2048, and covered goes from 56.3 % to 91.2 % and
|
|
111
110
|
from 49.7 % to 93.4 %. The cost is the one rigc's help states: *"region attachments
|
|
112
111
|
sampling within 1 LSB of the loose build rather than exactly"*. Measured on both
|
|
113
112
|
examples, every idle frame stays within 1 level of the power-of-two build's. The atlas says
|
|
@@ -116,6 +115,16 @@ consumer that mipmaps or repeats the page; it writes exactly the page builds wro
|
|
|
116
115
|
before the flag existed. The measurements are in
|
|
117
116
|
[AUTHORING §5, *Page edges*](docs/AUTHORING.md#5-after-each-stage-what-to-read).
|
|
118
117
|
|
|
118
|
+
The pack shape is `polygon` by default (spine-rigc 2.1): a region only meshes draw is
|
|
119
|
+
packed by its emitted hull, so a neighbour may sit inside its rectangle where the hull
|
|
120
|
+
is not; a region attachment stays its rectangle. On the two examples that takes the
|
|
121
|
+
free page from 967x1338 to 922x1348 (covered 91.2 % to 95.0 %, 3.9 % less area) and
|
|
122
|
+
from 479x1166 to 477x1151 (93.4 % to 95.0 %, 1.7 % less), at the cost class `free`
|
|
123
|
+
already has: every idle frame within 1 level of the `rect` build's, and no figure in
|
|
124
|
+
`check.json` but `pack_mode` moves. `--pack-shape rect` writes the page and atlas
|
|
125
|
+
0.7.0 wrote. The measurements are in
|
|
126
|
+
[AUTHORING §5, *Pack shape*](docs/AUTHORING.md#5-after-each-stage-what-to-read).
|
|
127
|
+
|
|
119
128
|
The packer is spine-rigc's, and no other: a packed region is a lossless copy, and an
|
|
120
129
|
atlas written by anything else would have no oracle behind it.
|
|
121
130
|
|
|
@@ -124,7 +133,7 @@ atlas written by anything else would have no oracle behind it.
|
|
|
124
133
|
| stage | tool | what it owns |
|
|
125
134
|
| --- | --- | --- |
|
|
126
135
|
| painting + See-through layers → parts and specs | **spine-parts** | the merge of two runs, the measurements, the rig spec and motion spec |
|
|
127
|
-
| specs → Spine skeleton data | **[spine-rigc](https://github.com/firejune/rigc)** | compile, the round trip through `spine-core
|
|
136
|
+
| specs → Spine skeleton data | **[spine-rigc](https://github.com/firejune/rigc)** | compile, the gate (rigc's own validator in an install; the round trip through `spine-core` where the runtime is beside it, as in this repository's CI), the named assertions, the packer, the renderer |
|
|
128
137
|
| skeleton data → a page | **[spine-html](https://github.com/firejune/spine-html)**, a sibling project | a DOM renderer; rigc's `spine-html` profile is its policy, and every build here is gated under that profile as well as under `spine` |
|
|
129
138
|
|
|
130
139
|
spine-parts never writes Spine data itself. Everything on disk under `check/build/`
|
|
@@ -253,8 +262,8 @@ agent skill.
|
|
|
253
262
|
| `propose --head-box --full <run> --canvas WxH` | propose `seethrough.head_box` from the full run, held inside the painting |
|
|
254
263
|
| `propose --parts --source --out [--compare <config>]` | propose bones, meshes, regions and an idle; draw the overlay |
|
|
255
264
|
| `propose … --from-config <config>` | draw and LINT the config's current bones |
|
|
256
|
-
| `rig --config --parts --out [--idle-keys ctl\|direct]` | author `rig.json` + `motion.json`, written only after spine-rigc's
|
|
257
|
-
| `check --rig --out [--parts]` | build packed,
|
|
265
|
+
| `rig --config --parts --out [--idle-keys ctl\|direct]` | author `rig.json` + `motion.json`, written only after spine-rigc's gate is green; `--idle-keys` says whether the idle's keys on mesh-driving bones go through `<bone>_ctl` parents (default) or stay on the bones with `invariants.idleDrivesMeshes` declared |
|
|
266
|
+
| `check --rig --out [--parts]` | build packed, gated under `spine-html`, render the idle, measure seam, loop and the six judgement lines (mesh texture stretch among them, from the idle's `render --geometry`), and report the recomposite's holes from `parts.json` |
|
|
258
267
|
| `loop --frames <dir> --out <file.gif \| file.png> [--palette]` | encode a rendered idle as a looping GIF, lossless APNG, or indexed APNG (`--palette`) |
|
|
259
268
|
| `build --config --source --full --head --out [--seam] [--project] [--loop]` | assemble, rig and check in one process, stopping at the first refusal |
|
|
260
269
|
|
|
@@ -319,13 +328,19 @@ the flag, puts 813 of `sample`'s 993 still-unprojected pixels and 879 of `demo`'
|
|
|
319
328
|
|
|
320
329
|
[Bun](https://bun.sh) 1.2 or later. `npm install -g spine-parts` installs the
|
|
321
330
|
`spine-parts` command; it hands off to Bun and says so in one sentence if Bun is not
|
|
322
|
-
on `PATH`. spine-rigc comes with it as a dependency.
|
|
331
|
+
on `PATH`. spine-rigc comes with it as a dependency. No Spine runtime does: since
|
|
332
|
+
spine-rigc 2.0 the installed `rigc` gates every build with its own validator over the
|
|
333
|
+
compiled model document, and `rigc --version` says `entry: cli_core.ts —
|
|
334
|
+
@esotericsoftware/spine-core absent`. With `@esotericsoftware/spine-core` installed
|
|
335
|
+
beside it, the same `rigc` runs the spine-core round trip instead, as this repository's
|
|
336
|
+
selftest and CI do; `check.json`'s `rigc_entry` records which one gated a build.
|
|
323
337
|
|
|
324
338
|
## Licence posture
|
|
325
339
|
|
|
326
|
-
spine-parts is MIT, and it depends on spine-rigc,
|
|
327
|
-
|
|
328
|
-
|
|
340
|
+
spine-parts is MIT, and it depends on spine-rigc, whose output is Spine skeleton data:
|
|
341
|
+
using what it produces in a product requires a Spine Editor licence, as any Spine data
|
|
342
|
+
does. Neither package installs Esoteric Software's `spine-core` any more; this
|
|
343
|
+
repository uses it in development, for the round trip in its selftest and CI. [NOTICE.md](NOTICE.md) records that chain and every other
|
|
329
344
|
third-party term this package touches, See-through's included.
|
|
330
345
|
|
|
331
346
|
## Development
|
package/cli.ts
CHANGED
|
@@ -15,10 +15,9 @@ import { spawnSync } from 'node:child_process';
|
|
|
15
15
|
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
16
16
|
import { tmpdir } from 'node:os';
|
|
17
17
|
import { basename, dirname, extname, join } from 'node:path';
|
|
18
|
-
import { fileURLToPath } from 'node:url';
|
|
19
18
|
import { DEFAULT_PROJECT_RULE, DEFAULT_SEAM_RULE, HOLES_LISTED, PROJECT_RULES, type ProjectRule, proposeFields, proposePlan, SEAM_RULES, type SeamRule } from './src/assemble.ts';
|
|
20
19
|
import { assembleStage, build, checkStage, ERROR_MAP_FILE, loopStage, readRuns, readSource, rigStage } from './src/build.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';
|
|
20
|
+
import { DEFAULT_PACK_SHAPE, DEFAULT_PAGE_EDGES, findRigc, PACK_SHAPES, type PackShape, 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
21
|
import { ComfyClient, resolveHost, runPainting, runSeeThrough } from './src/comfy/index.ts';
|
|
23
22
|
import { type CharacterConfig, loadConfig, loadEarlyConfig } from './src/config.ts';
|
|
24
23
|
import { PartsError, problemLine } from './src/errors.ts';
|
|
@@ -98,7 +97,7 @@ usage:
|
|
|
98
97
|
printed when one was needed.
|
|
99
98
|
|
|
100
99
|
spine-parts rig --config <config.json> --parts <dir> --out <dir> [--idle-keys ctl|direct]
|
|
101
|
-
[--page-edges pot|free]
|
|
100
|
+
[--page-edges pot|free] [--pack-shape rect|polygon]
|
|
102
101
|
Author the rig: unrotated bones at the config's landmarks (a chain makes
|
|
103
102
|
<chain>0..n), a square lattice mesh over every part in config.meshes
|
|
104
103
|
weighted by distance to its candidate bone segments, a region for every
|
|
@@ -111,10 +110,11 @@ usage:
|
|
|
111
110
|
bone twice, is refused by the loader, CONFIG_BLINK_GROUP_MEMBERS or
|
|
112
111
|
CONFIG_BLINK_GROUP_UNIQUE, before rigc starts. --parts is the
|
|
113
112
|
directory holding parts.json and parts/<name>.png. The result is built
|
|
114
|
-
through spine-rigc (profile spine-html, packed with --page-edges
|
|
115
|
-
check
|
|
116
|
-
images/*.png, rig.json,
|
|
117
|
-
|
|
113
|
+
through spine-rigc (profile spine-html, packed with --page-edges and
|
|
114
|
+
--pack-shape as for check; rigc's build gates the compile and the packed pages on disk) in a
|
|
115
|
+
scratch directory first, and --out receives images/*.png, rig.json,
|
|
116
|
+
motion.json and mesh_report.json only when it is green. Prints one line
|
|
117
|
+
per mesh and the rigc gate lines.
|
|
118
118
|
--idle-keys says where the idle's keys on a bone a mesh is weighted to go:
|
|
119
119
|
ctl (the default) keys a same-origin <bone>_ctl parent instead, which
|
|
120
120
|
passes A15_IDLE_NO_MESH_BONE_KEYS on any spine-rigc; direct keys the bone
|
|
@@ -123,17 +123,19 @@ usage:
|
|
|
123
123
|
stage prints that SKIP line). The pose is the same to one level of float
|
|
124
124
|
rounding, and so is the per-frame mesh work (AUTHORING §5).
|
|
125
125
|
spine-parts check --rig <dir> --out <dir> [--parts <dir>] [--page-edges pot|free]
|
|
126
|
+
[--pack-shape rect|polygon]
|
|
126
127
|
Build, gate, render and measure a rig through spine-rigc's CLI (the rigc at
|
|
127
128
|
node_modules/.bin/rigc, or on PATH). --rig holds rig.json and motion.json
|
|
128
129
|
(with an "idle").
|
|
129
130
|
${PARTS_HOME_SENTENCE}.
|
|
130
131
|
Both are only read. Into --out:
|
|
131
|
-
build/ (rigc build --profile spine-html --pack --page-edges <value
|
|
132
|
-
packed atlas is the artifact), gate_spine-html.txt
|
|
132
|
+
build/ (rigc build --profile spine-html --pack --page-edges <value>
|
|
133
|
+
--pack-shape <value>: the packed atlas is the artifact), gate_spine-html.txt (the gate lines
|
|
133
134
|
verbatim), idle_frames/ (rigc render --animation idle --fps 12 --max 640
|
|
134
135
|
--geometry: the frames and geometry.json, the skinned vertices and
|
|
135
136
|
every bone's world transform per frame),
|
|
136
|
-
contact.png, motion_heat.png and check.json
|
|
137
|
+
contact.png, motion_heat.png and check.json (with rigc_entry, the rigc
|
|
138
|
+
entry that gated the build, and pack_mode, the two pack flags). PASS needs every gate summary
|
|
137
139
|
"0 failed", the seam (setup pose vs the flat composite of parts/) at mean
|
|
138
140
|
|d| <= ${SEAM_MEAN_BAR.toFixed(1)} with <= ${SEAM_PX_BAR} px over ${SEAM_PX_LEVEL}, and the loop (idle frame 0 vs the
|
|
139
141
|
frame at t = duration) at max |d| 0. Then six judgement lines, each in
|
|
@@ -165,15 +167,25 @@ usage:
|
|
|
165
167
|
a bar. Exit 0 on PASS, 1 on FAIL — every FAIL line names the bar, the value
|
|
166
168
|
and the value required.
|
|
167
169
|
--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
|
|
169
|
-
479x1166, where pot writes 1024x2048 and 512x2048), at the cost rigc
|
|
170
|
+
least-area page the parts need (on the two public examples, rect, 967x1338
|
|
171
|
+
and 479x1166, where pot writes 1024x2048 and 512x2048), at the cost rigc
|
|
170
172
|
states — region attachments sample within 1 LSB of the loose build
|
|
171
173
|
rather than exactly (measured on both examples: every idle frame within
|
|
172
174
|
1 level of the pot build's; AUTHORING §5, Page edges). pot is a power of two on both edges, for a consumer
|
|
173
175
|
that mipmaps or repeats the page (the atlas declares filter Linear,
|
|
174
|
-
Linear and no repeat). rigc's pack line
|
|
176
|
+
Linear and no repeat). rigc's pack line carries ", page edges free" under
|
|
175
177
|
free; a line that disagrees with the value passed, or a pot page that is
|
|
176
178
|
not a power of two, is refused, CHECK_PACK_PAGE_EDGES.
|
|
179
|
+
--pack-shape is handed to rigc verbatim and defaults to ${DEFAULT_PACK_SHAPE}:
|
|
180
|
+
a region only meshes draw is packed by its emitted hull, so a neighbour
|
|
181
|
+
may sit inside its rectangle where the hull is not (on the two public
|
|
182
|
+
examples, under free, 922x1348 and 477x1151 where rect writes 967x1338
|
|
183
|
+
and 479x1166), at the cost of the same 1-level class as free (measured
|
|
184
|
+
on both examples: every idle frame within 1 level of the rect build's;
|
|
185
|
+
AUTHORING §5, Pack shape). rect keeps every region's cell apart and
|
|
186
|
+
writes the page earlier releases wrote. rigc's pack line ends
|
|
187
|
+
", shape rect" or ", shape polygon"; a line that disagrees with the
|
|
188
|
+
value passed is refused, CHECK_PACK_SHAPE.
|
|
177
189
|
|
|
178
190
|
spine-parts loop --frames <dir> --out <file.png | file.gif> [--palette]
|
|
179
191
|
Encode a frame set rigc render wrote (its --out directory, or the set
|
|
@@ -258,7 +270,8 @@ usage:
|
|
|
258
270
|
|
|
259
271
|
spine-parts build --config <config.json> --source <painting.png> --full <dir|psd>
|
|
260
272
|
--head <dir|psd> --out <dir> [--seam near-white|silhouette]
|
|
261
|
-
[--project core|visible] [--page-edges pot|free]
|
|
273
|
+
[--project core|visible] [--page-edges pot|free]
|
|
274
|
+
[--pack-shape rect|polygon] [--loop]
|
|
262
275
|
assemble, then rig, then check, in one process, each stage's own lines
|
|
263
276
|
printed under [assemble], [rig] and [check]; the first stage that refuses
|
|
264
277
|
stops the build with its own FAIL lines. The config must already carry
|
|
@@ -274,8 +287,9 @@ usage:
|
|
|
274
287
|
three artifact paths — skeleton .json, .atlas and the packed page: the
|
|
275
288
|
packed atlas is the result, the loose parts are the intermediate it was
|
|
276
289
|
made from. --seam defaults to ${DEFAULT_SEAM_RULE}, --project to
|
|
277
|
-
${DEFAULT_PROJECT_RULE} (both as for assemble), --page-edges to ${DEFAULT_PAGE_EDGES}
|
|
278
|
-
(as for check; both packed builds, rig's gate
|
|
290
|
+
${DEFAULT_PROJECT_RULE} (both as for assemble), --page-edges to ${DEFAULT_PAGE_EDGES} and
|
|
291
|
+
--pack-shape to ${DEFAULT_PACK_SHAPE} (as for check; both packed builds, rig's gate
|
|
292
|
+
and check's artifact, take them).
|
|
279
293
|
|
|
280
294
|
spine-parts --version
|
|
281
295
|
spine-parts --help
|
|
@@ -396,20 +410,6 @@ function cmdSheet(args: string[]): number {
|
|
|
396
410
|
}
|
|
397
411
|
}
|
|
398
412
|
|
|
399
|
-
/** spine-rigc's own CLI, resolved the way this package resolves every other rigc path. */
|
|
400
|
-
function rigcCli(): string {
|
|
401
|
-
return fileURLToPath(import.meta.resolve('spine-rigc/cli.ts'));
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
/** The rig stage's rigc: spine-rigc's cli.ts run by this Bun, as the stage has always gated. */
|
|
405
|
-
function rigGateRunner(): RigcRunner {
|
|
406
|
-
return (args) => {
|
|
407
|
-
const r = spawnSync(process.execPath, [rigcCli(), ...args], { encoding: 'utf8', maxBuffer: 1 << 28 });
|
|
408
|
-
if (r.error !== undefined) return { status: 127, out: `could not start ${rigcCli()}: ${r.error.message}` };
|
|
409
|
-
return { status: r.status ?? 1, out: `${r.stdout ?? ''}${r.stderr ?? ''}` };
|
|
410
|
-
};
|
|
411
|
-
}
|
|
412
|
-
|
|
413
413
|
/** `--page-edges`, or its default; a value spine-rigc does not take is a usage error naming the two it does. */
|
|
414
414
|
function pageEdgesOf(value: string | undefined): PageEdges | string {
|
|
415
415
|
const edges = value ?? DEFAULT_PAGE_EDGES;
|
|
@@ -420,16 +420,27 @@ function isPageEdges(v: PageEdges | string): v is PageEdges {
|
|
|
420
420
|
return (PAGE_EDGES as readonly string[]).includes(v);
|
|
421
421
|
}
|
|
422
422
|
|
|
423
|
+
/** `--pack-shape`, or its default; a value spine-rigc does not take is a usage error naming the two it does. */
|
|
424
|
+
function packShapeOf(value: string | undefined): PackShape | string {
|
|
425
|
+
const shape = value ?? DEFAULT_PACK_SHAPE;
|
|
426
|
+
return (PACK_SHAPES as readonly string[]).includes(shape) ? (shape as PackShape) : `--pack-shape ${shape}; one of ${PACK_SHAPES.join(', ')} is required`;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function isPackShape(v: PackShape | string): v is PackShape {
|
|
430
|
+
return (PACK_SHAPES as readonly string[]).includes(v);
|
|
431
|
+
}
|
|
432
|
+
|
|
423
433
|
function cmdRig(args: string[]): number {
|
|
424
434
|
let config: string | null = null;
|
|
425
435
|
let partsDir: string | null = null;
|
|
426
436
|
let out: string | null = null;
|
|
427
437
|
let idleKeys: string | null = null;
|
|
428
438
|
let pageEdges: string | null = null;
|
|
439
|
+
let packShape: string | null = null;
|
|
429
440
|
for (let i = 0; i < args.length; i++) {
|
|
430
441
|
const flag = args[i];
|
|
431
442
|
const value = args[i + 1];
|
|
432
|
-
if (!['--config', '--parts', '--out', '--idle-keys', '--page-edges'].includes(flag)) return usage(`rig does not take "${flag}"`);
|
|
443
|
+
if (!['--config', '--parts', '--out', '--idle-keys', '--page-edges', '--pack-shape'].includes(flag)) return usage(`rig does not take "${flag}"`);
|
|
433
444
|
if (value === undefined) return usage(`${flag} needs a value`);
|
|
434
445
|
i++;
|
|
435
446
|
if (flag === '--config') config = value;
|
|
@@ -440,6 +451,9 @@ function cmdRig(args: string[]): number {
|
|
|
440
451
|
} else if (flag === '--page-edges') {
|
|
441
452
|
if (pageEdges !== null) return usage('--page-edges is given twice');
|
|
442
453
|
pageEdges = value;
|
|
454
|
+
} else if (flag === '--pack-shape') {
|
|
455
|
+
if (packShape !== null) return usage('--pack-shape is given twice');
|
|
456
|
+
packShape = value;
|
|
443
457
|
} else out = value;
|
|
444
458
|
}
|
|
445
459
|
if (config === null) return usage('rig needs --config <config.json>');
|
|
@@ -449,9 +463,17 @@ function cmdRig(args: string[]): number {
|
|
|
449
463
|
if (!(IDLE_KEYS as readonly string[]).includes(keys)) return usage(`--idle-keys ${keys}; one of ${IDLE_KEYS.join(', ')} is required`);
|
|
450
464
|
const edges = pageEdgesOf(pageEdges ?? undefined);
|
|
451
465
|
if (!isPageEdges(edges)) return usage(edges);
|
|
466
|
+
const shape = packShapeOf(packShape ?? undefined);
|
|
467
|
+
if (!isPackShape(shape)) return usage(shape);
|
|
468
|
+
let bin: string;
|
|
469
|
+
try {
|
|
470
|
+
bin = findRigc(import.meta.dir, process.env.PATH ?? '');
|
|
471
|
+
} catch (err) {
|
|
472
|
+
return printRefusal(err);
|
|
473
|
+
}
|
|
452
474
|
const scratch = mkdtempSync(join(tmpdir(), 'spine-parts-rig-'));
|
|
453
475
|
try {
|
|
454
|
-
rigStage({ config, parts: partsDir, out, idleKeys: keys as IdleKeys, pageEdges: edges },
|
|
476
|
+
rigStage({ config, parts: partsDir, out, idleKeys: keys as IdleKeys, pageEdges: edges, packShape: shape }, rigcRunner(bin), scratch, console.log);
|
|
455
477
|
return EXIT_OK;
|
|
456
478
|
} catch (err) {
|
|
457
479
|
return printRefusal(err);
|
|
@@ -460,7 +482,16 @@ function cmdRig(args: string[]): number {
|
|
|
460
482
|
}
|
|
461
483
|
}
|
|
462
484
|
|
|
463
|
-
/**
|
|
485
|
+
/**
|
|
486
|
+
* The rigc process, for every stage that runs rigc — `rig`'s gate, `check`, and
|
|
487
|
+
* both inside `build`: the `rigc` binary {@link findRigc} located, spawned as
|
|
488
|
+
* it is, so spine-rigc's own launcher (`bin/rigc.cjs`) chooses the entry —
|
|
489
|
+
* `cli.ts` and the spine-core round trip where the runtime resolves beside it,
|
|
490
|
+
* `cli_core.ts` and rigc's own validator where it does not. No stage runs a
|
|
491
|
+
* rigc source file by path: under spine-rigc 2.0, `cli.ts` by path is the
|
|
492
|
+
* spine-core entry whether or not spine-core is there. `src/` is pure and takes
|
|
493
|
+
* the spawn from here.
|
|
494
|
+
*/
|
|
464
495
|
function rigcRunner(bin: string): RigcRunner {
|
|
465
496
|
return (args) => {
|
|
466
497
|
const r = spawnSync(bin, [...args], { encoding: 'utf8', maxBuffer: 1 << 28 });
|
|
@@ -484,15 +515,17 @@ function flags(args: string[], known: readonly string[], command: string, option
|
|
|
484
515
|
}
|
|
485
516
|
|
|
486
517
|
function cmdCheck(args: string[]): number {
|
|
487
|
-
const f = flags(args, ['--rig', '--out'], 'check', ['--parts', '--page-edges']);
|
|
518
|
+
const f = flags(args, ['--rig', '--out'], 'check', ['--parts', '--page-edges', '--pack-shape']);
|
|
488
519
|
if (typeof f === 'string') return usage(f);
|
|
489
520
|
const rig = f.get('--rig') as string;
|
|
490
521
|
const out = f.get('--out') as string;
|
|
491
522
|
const edges = pageEdgesOf(f.get('--page-edges'));
|
|
492
523
|
if (!isPageEdges(edges)) return usage(edges);
|
|
524
|
+
const shape = packShapeOf(f.get('--pack-shape'));
|
|
525
|
+
if (!isPackShape(shape)) return usage(shape);
|
|
493
526
|
try {
|
|
494
527
|
const bin = findRigc(import.meta.dir, process.env.PATH ?? '');
|
|
495
|
-
const r = checkStage({ rig, parts: f.get('--parts') ?? rig, out, pageEdges: edges }, rigcRunner(bin), bin, console.log);
|
|
528
|
+
const r = checkStage({ rig, parts: f.get('--parts') ?? rig, out, pageEdges: edges, packShape: shape }, rigcRunner(bin), bin, console.log);
|
|
496
529
|
return r.figures.PASS ? EXIT_OK : EXIT_REFUSED;
|
|
497
530
|
} catch (err) {
|
|
498
531
|
return printRefusal(err);
|
|
@@ -797,7 +830,7 @@ function cmdInputs(args: string[]): number {
|
|
|
797
830
|
function cmdBuild(args: string[]): number {
|
|
798
831
|
const flags = new Map<string, string>();
|
|
799
832
|
let loop = false;
|
|
800
|
-
const valued = ['--config', '--source', '--full', '--head', '--out', '--seam', '--project', '--page-edges'];
|
|
833
|
+
const valued = ['--config', '--source', '--full', '--head', '--out', '--seam', '--project', '--page-edges', '--pack-shape'];
|
|
801
834
|
for (let i = 0; i < args.length; i++) {
|
|
802
835
|
const flag = args[i];
|
|
803
836
|
if (flag === '--loop') {
|
|
@@ -819,6 +852,8 @@ function cmdBuild(args: string[]): number {
|
|
|
819
852
|
if (!(PROJECT_RULES as readonly string[]).includes(project)) return usage(`--project ${project}; one of ${PROJECT_RULES.join(', ')} is required`);
|
|
820
853
|
const edges = pageEdgesOf(flags.get('--page-edges'));
|
|
821
854
|
if (!isPageEdges(edges)) return usage(edges);
|
|
855
|
+
const shape = packShapeOf(flags.get('--pack-shape'));
|
|
856
|
+
if (!isPackShape(shape)) return usage(shape);
|
|
822
857
|
const [config, source, full, head, out] = ['--config', '--source', '--full', '--head', '--out'].map((f) => flags.get(f) as string);
|
|
823
858
|
let bin: string;
|
|
824
859
|
try {
|
|
@@ -829,8 +864,8 @@ function cmdBuild(args: string[]): number {
|
|
|
829
864
|
const scratch = mkdtempSync(join(tmpdir(), 'spine-parts-build-'));
|
|
830
865
|
try {
|
|
831
866
|
const r = build(
|
|
832
|
-
{ config, source, full, head, out, seam: seam as SeamRule, project: project as ProjectRule, loop, pageEdges: edges },
|
|
833
|
-
{ rig:
|
|
867
|
+
{ config, source, full, head, out, seam: seam as SeamRule, project: project as ProjectRule, loop, pageEdges: edges, packShape: shape },
|
|
868
|
+
{ rig: rigcRunner(bin), check: rigcRunner(bin), checkBin: bin, scratch: join(scratch, 'rig-gate') },
|
|
834
869
|
console.log,
|
|
835
870
|
);
|
|
836
871
|
return r.stoppedAt === null ? EXIT_OK : EXIT_REFUSED;
|
package/docs/AUTHORING.md
CHANGED
|
@@ -18,7 +18,7 @@ into `examples/sample/inputs/`.
|
|
|
18
18
|
| what | why | how |
|
|
19
19
|
| --- | --- | --- |
|
|
20
20
|
| [Bun](https://bun.sh) 1.2 or later | spine-parts and spine-rigc are Bun programs | `npm install -g spine-parts` installs the command; it says so if Bun is missing |
|
|
21
|
-
| [spine-rigc](https://www.npmjs.com/package/spine-rigc) | compiles, gates, packs and renders every rig; spine-parts writes no Spine data itself | installed with spine-parts as a dependency (`node_modules/.bin/rigc`) |
|
|
21
|
+
| [spine-rigc](https://www.npmjs.com/package/spine-rigc) | compiles, gates, packs and renders every rig; spine-parts writes no Spine data itself | installed with spine-parts as a dependency (`node_modules/.bin/rigc`); with no Spine runtime beside it, it gates with its own validator, and `rigc --version` says `entry: cli_core.ts` |
|
|
22
22
|
| [See-through](https://github.com/shitagaki-lab/see-through), somewhere | the layer decomposition is the input | any route in README *Getting See-through layers*; it runs twice per character, outside this tool |
|
|
23
23
|
| a GPU, wherever See-through runs | See-through is a diffusion model | nothing in spine-parts itself uses a GPU |
|
|
24
24
|
|
|
@@ -266,7 +266,7 @@ proposal in as the steps above say (`RL01`); every step must exit 0.
|
|
|
266
266
|
| `sheet` of both runs | the tile list (and the sheet, if you can see) | eyes, irises, lashes and brows as left/right pairs in the head run | a head box that cut off an ornament: move `head_box`, re-run the head crop |
|
|
267
267
|
| `assemble` | one line per part, the `pixels:` totals (opaque = visible + occluded; taken; visible but not projected), then `recomposite vs source: mean \|d\|, within 8, error px > 40, uncovered error px`, then `uncovered holes (8-connected): N` and the largest five as `uncovered hole K: <px> px at x,y wxh (between "<part>" <px> px, …)`; and look at `render/recomposite_error_rig.png` | on the examples: `sample` 0.84 / 98.0 % / 4,512 / 1,185, 259 holes, the largest 94 px; `demo` 2.39 / 95.8 % / 11,050 / 1,564, 453 holes, the largest 70 px (default rule) — many slivers along part edges, no hole a region could hide | one large hole: part of the figure is in no layer — a plan entry is missing, hair left the head crop sideways (the demo's `hair_back` is taken from the full run for that reason), or See-through split one garment into two and left the space between them in neither (a skirt as two trouser legs); the `between` parts say where. When neither run holds it at all, an `assemble.patches` entry cuts it from the painting (below) |
|
|
268
268
|
| `propose` | `note:` lines, `LINT` lines, `landmarks.png` | no LINT line: every chain link lies on its mesh's art, and the hip is below the chest and the figure's top quarter | a link off the art (a bone on the background) — move it onto the layer; `LINT hip at [x, y] is not below chest at [x, y]: …` or `LINT hip at [x, y] is above 0.25 of the figure height (figure y T..B, so hip y must be at least L): a hip at the shoulders` — move `hip` down to the waist (and `chest` between it and the neck); a `hanging strand … -- no chain proposed` note — that strand hangs stiff until you add a chain down the x and rows it names (§3); a `no blink: …` or `blink without brows: …` note — the idle will not blink (or its brows will not drop), because no part came from an `eyewhite` (or `eyebrow`) layer; the note names the tags looked for; a `no eyewhite part for eye_<s> …` note — the iris and lash parts it names ride `head` and do not blink, because no `eyewhite-<s>` part made that side's eye bone: take the eyewhite from the other run, or keep them on `head` |
|
|
269
|
-
| `rig` (inside `build`) | one line per mesh: vertices, triangles, bones, influences, `cover`; the `bones` line; the `idle keys` line; then rigc's gate lines (with A15's declared SKIP under `--idle-keys direct`) | `cover 1.00000` on every mesh, both
|
|
269
|
+
| `rig` (inside `build`) | one line per mesh: vertices, triangles, bones, influences, `cover`; the `bones` line; the `idle keys` line; then rigc's gate lines (with A15's declared SKIP under `--idle-keys direct`) | `cover 1.00000` on every mesh, both gate summaries `0 failed` (the compile and the packed pages) | `RIG_LATTICE_ONE_LOOP`: change that mesh's `grid` |
|
|
270
270
|
| `check` (inside `build`) | the gate lines verbatim, the pack line, `loop:`, `seam:`, the six judgement lines and `RECOMPOSITE_HOLES` (§7), `check.json` | `check: PASS`, and a judgement line SKIP only where the character lacks what it reads | `CHECK_SEAM_WITHIN_BAR` or `CHECK_LOOP_CLOSES` (§6) |
|
|
271
271
|
| `loop` (inside `build --loop`, or `loop --frames … --out …`) | the dropped-duplicate line, each file's line, then `loop: idle.png N B (lossless); idle-indexed.png N B (max …, mean …); idle.gif N B (max …, mean …)` | `f0048.png equals f0000.png byte for byte, so it is dropped` | `LOOP_ENCODE` (§6) |
|
|
272
272
|
|
|
@@ -279,11 +279,12 @@ 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,503 B lossless, 1,706,719 B indexed and 1,812,288 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
284
|
construction and the size is the difference. That is `--seam silhouette` (the README's
|
|
285
|
-
animation); with `--
|
|
286
|
-
1,812,
|
|
285
|
+
animation, spine-rigc 2.1.3); with `--pack-shape rect` the same build writes 13,645,350,
|
|
286
|
+
1,706,520 and 1,812,238 B, and with `--page-edges pot --pack-shape rect` 13,645,519,
|
|
287
|
+
1,706,785 and 1,812,625 B, all at the same error. The loop shows the blink closed: the
|
|
287
288
|
eyes' hold is at least one 12 fps frame (below), so whatever `motion.blink.t` is, one
|
|
288
289
|
idle frame lands inside it, and `check.json`'s `BLINK_NO_HOLE.idle_frames_closed`
|
|
289
290
|
names it — `[29]` on both examples.
|
|
@@ -300,8 +301,11 @@ the reference's so the examples stay comparable with it.
|
|
|
300
301
|
--page-edges <v>`, and hand the value to rigc verbatim. The default is `free`,
|
|
301
302
|
the least-area page the parts need. `--page-edges pot` is the opt-out: a power of
|
|
302
303
|
two on both edges, which is the page every build wrote before this flag. The pack
|
|
303
|
-
line says which one ran, because rigc
|
|
304
|
-
Measured
|
|
304
|
+
line says which one ran, because rigc writes `, page edges free` after the padding under
|
|
305
|
+
`free`. Measured on the two examples by `tools/atlas_population.ts` with rigc 1.5.1, and
|
|
306
|
+
again with rigc 2.0.3 and with rigc 2.1.3 under `--pack-shape rect` (the frames table
|
|
307
|
+
by the same idle render), which reproduced every figure in both tables and the one
|
|
308
|
+
check figure below:
|
|
305
309
|
|
|
306
310
|
| build | page | covered | page opaque | page / figure |
|
|
307
311
|
| --- | --- | --- | --- | --- |
|
|
@@ -326,6 +330,56 @@ barred figure and every status is the same under both. The atlas rigc writes say
|
|
|
326
330
|
`filter: Linear, Linear` and has no `repeat` line, so choose `pot` for a consumer
|
|
327
331
|
that mipmaps or repeats the page.
|
|
328
332
|
|
|
333
|
+
**Pack shape.** `rig`, `check` and `build` also hand rigc `--pack-shape <v>`
|
|
334
|
+
verbatim (spine-rigc 2.1.0 and later). `rect` keeps every region's cell apart, the
|
|
335
|
+
only packing before 2.1. `polygon`, the default here (rigc's own is `rect`), packs a
|
|
336
|
+
region that only meshes draw by its emitted hull, so a neighbour may sit inside its
|
|
337
|
+
rectangle where the hull is not, with the padding kept between footprints; a region
|
|
338
|
+
attachment stays its rectangle. rigc gates the result on the packed pages with
|
|
339
|
+
`A49_PACKED_FOOTPRINTS_DO_NOT_OVERLAP` (SKIP on the compile pass, where every part has
|
|
340
|
+
its own page; PASS on the packed pass, both shapes, both examples). The pack line ends
|
|
341
|
+
`, shape rect` or `, shape polygon` under either, and `check.json`'s `pack_mode` records
|
|
342
|
+
both flags. Measured on the two examples with spine-rigc 2.1.3 (the full entry,
|
|
343
|
+
spine-core 4.3.13 beside it), `spine-parts build` with `--page-edges free` and each
|
|
344
|
+
shape, the table columns by `tools/atlas_population.ts`:
|
|
345
|
+
|
|
346
|
+
| build | page | page area | covered | page opaque | page / figure |
|
|
347
|
+
| --- | --- | --- | --- | --- | --- |
|
|
348
|
+
| demo, `rect` | 967x1338 | 1,293,846 | 91.2 % | 59.7 % | 1.636 |
|
|
349
|
+
| demo, `polygon` | 922x1348 | 1,242,856 (−3.9 %) | 95.0 % | 62.1 % | 1.571 |
|
|
350
|
+
| sample, `rect` | 479x1166 | 558,514 | 93.4 % | 53.9 % | 1.755 |
|
|
351
|
+
| sample, `polygon` | 477x1151 | 549,027 (−1.7 %) | 95.0 % | 54.8 % | 1.725 |
|
|
352
|
+
|
|
353
|
+
Covered is Σ region rectangles over page area, as rigc prints it; under `polygon` the
|
|
354
|
+
rectangles may overlap where a hull leaves room, so it is not a union and can exceed
|
|
355
|
+
100 %. Page opaque is the measure of what the page holds. Under `--page-edges pot` the
|
|
356
|
+
shape changes nothing on these two examples: both shapes write 1024x2048 and 512x2048,
|
|
357
|
+
and the idle frames are identical.
|
|
358
|
+
|
|
359
|
+
The cost, from `rect` to `polygon` (the idle `check` renders, `rigc render --animation
|
|
360
|
+
idle --fps 12 --max 640`, compared frame by frame, max over R, G, B and A):
|
|
361
|
+
|
|
362
|
+
| example | idle frames that differ | pixels that differ (all 49 frames) | max \|d\| |
|
|
363
|
+
| --- | --- | --- | --- |
|
|
364
|
+
| demo | 49 of 49 | 2,594 | 1 |
|
|
365
|
+
| sample | 49 of 49 | 740 | 1 |
|
|
366
|
+
|
|
367
|
+
That is the class `free` already costs (the table above, measured by the same
|
|
368
|
+
comparison), a little larger. No figure in `check.json` moved but `pack_mode`: every
|
|
369
|
+
bar, every status and every reported figure is the same under both shapes.
|
|
370
|
+
`--pack-shape rect` gives back the page and atlas 0.7.0 wrote: on both examples
|
|
371
|
+
they are byte-identical to rigc 2.0.3's `--page-edges free` build of the same rig (the
|
|
372
|
+
skeleton JSON differs only in its relative `images` path), and selftest `CK53` holds
|
|
373
|
+
`rect` to rigc's own rectangle page.
|
|
374
|
+
|
|
375
|
+
The hull ceiling (spine-parts #58: hull area over rectangle area, 0.812 on demo and
|
|
376
|
+
0.814 on sample) is not what the packer realises here: the hollows inside the meshes'
|
|
377
|
+
rectangles are worth only what fits into them. On the polygon pages, read off the
|
|
378
|
+
atlas, demo's packed rectangles overlap in 21 pairs (17 a region attachment inside a
|
|
379
|
+
mesh's rectangle, 4 a mesh inside another's) and sample's in 12 (10 and 2); no two
|
|
380
|
+
region attachments overlap. More would take tighter hulls, meshes in place of
|
|
381
|
+
regions, or a stronger nesting search in rigc, each its own card.
|
|
382
|
+
|
|
329
383
|
`--project core` (the default) is the reference's projection rule: a layer takes the
|
|
330
384
|
painting's pixel only inside its top-most `alpha >= 250` area eroded by a 5x5 square,
|
|
331
385
|
so a part a few pixels wide (a lash, a brow, an iris) takes none. `--project visible`
|
|
@@ -357,8 +411,8 @@ differ by at most one level in a channel. The default stays `ctl`: the examples'
|
|
|
357
411
|
`expected/rig.json` and `motion.json` are the reference's output, and the
|
|
358
412
|
declaration is in the rig spec only, so `rigc validate <build> --profile spine-html`
|
|
359
413
|
run on a `direct` build (it has no rig spec to read) refuses A15 once per keyed
|
|
360
|
-
bone. `
|
|
361
|
-
|
|
414
|
+
bone. No stage runs `validate`: the gate is `rigc build`, which reads the rig spec
|
|
415
|
+
and its declaration, and is green on both (`RG19`).
|
|
362
416
|
|
|
363
417
|
**Patching a hole no layer holds.** When an `uncovered hole` stays large after the
|
|
364
418
|
plan is right — both See-through runs dropped a piece of the figure, red in
|
|
@@ -588,7 +642,8 @@ See-through with another seed.
|
|
|
588
642
|
| `CHECK_RIGC_PRESENT` | no `rigc` binary found (every place looked is listed) | `bun install` |
|
|
589
643
|
| `CHECK_INPUT_PRESENT` on `…/parts.json` | `--parts` named a directory without `parts.json` — most often `parts/` itself. The detail says it: "--parts names the directory holding parts.json and parts/, not parts/ itself (after build, that is build's --out, whose rig is <out>/rig); without --parts it is --rig", and names the parent when the parent holds `parts.json` | `--parts` — the directory above `parts/` |
|
|
590
644
|
| `CHECK_INPUT_PRESENT`, `CHECK_INPUT_IS_JSON`, `CHECK_PART_PNG_PRESENT`, `CHECK_PART_PNG_MATCHES_BOX`, `CHECK_PART_SLOT_PRESENT`, `CHECK_RIG_STAGE_PRESENT`, `CHECK_RIG_STAGE_IS_THE_CANVAS`, `CHECK_RIG_ROOT_BONE`, `CHECK_IDLE_PRESENT` | the rig directory is incomplete or disagrees with `parts.json` (`CHECK_PART_SLOT_PRESENT`: a part with no slot of its own name, which the judgement lines render it by) | re-run rig (`build` does both) |
|
|
591
|
-
| `CHECK_RIGC_VERSION` | `rigc --version` is below 1.4.0, or prints no version: its `render` has no `--geometry`, which `TEXTURE_STRETCH` reads. Refused before anything is built | `bun install` (this package depends on spine-rigc ^1.
|
|
645
|
+
| `CHECK_RIGC_VERSION` | `rigc --version` is below 1.4.0, or prints no version: its `render` has no `--geometry`, which `TEXTURE_STRETCH` reads. Refused before anything is built | `bun install` (this package depends on spine-rigc ^2.1.3), or put a newer `rigc` first on `PATH` |
|
|
646
|
+
| `CHECK_RIGC_ENTRY`, `CHECK_RIGC_ENTRY_READS` | `rigc --version` names no entry (a rigc below 2.0.0), or names one in neither launcher form (`entry: cli.ts — @esotericsoftware/spine-core <v> present`, `entry: cli_core.ts — @esotericsoftware/spine-core absent — …`); `check.json`'s `rigc_entry` records the one that gated the build | `bun install` |
|
|
592
647
|
| `CHECK_RIGC_GREEN` | a rigc step failed; its line is quoted | as `RIG_RIGC_GREEN` |
|
|
593
648
|
| `CHECK_LOOP_LAST_FRAME_AT_DURATION` | the idle's last frame does not sit at `duration` | `motion.duration` — a whole number of 1/12 s |
|
|
594
649
|
| `CHECK_LOOP_CLOSES` | frame 0 and the frame at `duration` differ (max and first pixel quoted) | a track whose last key is not its first |
|
|
@@ -601,8 +656,9 @@ See-through with another seed.
|
|
|
601
656
|
| `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 |
|
|
602
657
|
| `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 |
|
|
603
658
|
| `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
|
|
605
|
-
| `CHECK_PACK_PAGE_EDGES` | the pack line disagrees with the `--page-edges` the build was run with — its `, page edges free`
|
|
659
|
+
| `CHECK_PACK_LINE_READS` | rigc printed a `pack:` line that is not `pack: <page> <W>x<H>, <N> region(s), <P>% covered, padding <D>`, then `, page edges free` or nothing, then `, shape rect`, `, shape polygon` or nothing (spine-rigc 2.1 prints the shape; 1.5–2.0 printed none, read as `rect`); an unknown shape is this refusal; the line is quoted | a rigc this package does not know the output of; report it, with `rigc --version` |
|
|
660
|
+
| `CHECK_PACK_PAGE_EDGES` | the pack line disagrees with the `--page-edges` the build was run with — its `, page edges free` clause 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` |
|
|
661
|
+
| `CHECK_PACK_SHAPE` | the pack line disagrees with the `--pack-shape` the build was run with — it ends `, shape rect` under `polygon` or `, shape polygon` under `rect`, or it has no shape clause (the 1.5–2.0 form, read as `rect`) under `polygon` | a rigc older than 2.1.0 on `PATH`: rigc 2.0.3 takes `--pack-shape` without a word, packs by rectangles and prints the form with no shape clause, so this refusal is what catches it — `bun install` (this package depends on spine-rigc ^2.1.3); otherwise a rigc problem, report it with `rigc --version` |
|
|
606
662
|
| `BUILD_ARTIFACT_PRESENT` | the packed build lacks its `.json`, `.atlas` or page | a rigc problem; report it |
|
|
607
663
|
|
|
608
664
|
## 7. The bars `check` enforces, and what only an eye answers today
|
|
@@ -611,14 +667,19 @@ See-through with another seed.
|
|
|
611
667
|
implementation's bars):
|
|
612
668
|
|
|
613
669
|
- `gate_spine-html.txt`: every summary line `(N passed, 0 failed)` — the packed build
|
|
614
|
-
under the `spine-html` profile
|
|
615
|
-
|
|
670
|
+
under the `spine-html` profile, gated once over the compile and once over the packed
|
|
671
|
+
pages on disk. There is no `spine` gate of its own: `spine-html` holds every rule
|
|
672
|
+
`spine` measures (on the demo, rigc 1.5.1 and 2.0.3: `validate --profile spine`'s 14
|
|
673
|
+
measured rules are all among the build's 23, the 15 others not in `spine`; rigc
|
|
674
|
+
2.1.3: the same 14 among the build's 24, which adds
|
|
675
|
+
`A49_PACKED_FOOTPRINTS_DO_NOT_OVERLAP` on the packed pass, 16 not in `spine`; `CH09`
|
|
676
|
+
holds it on every fetched example);
|
|
616
677
|
- **seam**: the setup-pose render against the flat composite of `parts/`: mean
|
|
617
678
|
max-channel |d| ≤ 1.0 of 255, and at most 50 pixels over 40;
|
|
618
679
|
- **loop**: idle frame 0 against the frame at `t = duration`: max |d| exactly 0.
|
|
619
680
|
|
|
620
|
-
On the examples: `sample` 23/23
|
|
621
|
-
`demo` (default rule) 23/23
|
|
681
|
+
On the examples: `sample` 23/23, seam 0.207 with 0 pixels over 40, loop 0;
|
|
682
|
+
`demo` (default rule) 23/23, seam 0.326 with 2 pixels over 40, loop 0.
|
|
622
683
|
|
|
623
684
|
A green gate cannot see a wrong animation, so `check` also writes six **judgement
|
|
624
685
|
lines** (issue #11; `TEXTURE_STRETCH`, issue #31), each a key of `check.json` and a console line
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spine-parts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.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": {
|
|
@@ -57,9 +57,10 @@
|
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
59
|
"ag-psd": "^31.0.2",
|
|
60
|
-
"spine-rigc": "^1.
|
|
60
|
+
"spine-rigc": "^2.1.3"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
|
+
"@esotericsoftware/spine-core": "4.3.13",
|
|
63
64
|
"@types/bun": "^1.4.2",
|
|
64
65
|
"eslint": "^10.11.0",
|
|
65
66
|
"typescript": "^5.9.3",
|