spine-parts 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 firejune
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/NOTICE.md ADDED
@@ -0,0 +1,121 @@
1
+ # Third-party notices
2
+
3
+ spine-parts' own code is MIT (see `LICENSE`). It depends on, reads the output
4
+ of, or reimplements the behaviour of the third-party work below, each under its
5
+ own terms. Naming a project here identifies where something comes from; it does
6
+ not imply that its authors endorse spine-parts.
7
+
8
+ Every statement below is either quoted from the named source or marked
9
+ **[observed]** with the date and the place it was read. Upstream terms can
10
+ change; check the source when it matters.
11
+
12
+ ## 1. See-through
13
+
14
+ spine-parts takes See-through's output as its input. **spine-parts does not
15
+ vendor, embed or redistribute See-through code or weights**: the user runs
16
+ See-through by one of the routes in the README (*See-through routes*) and points
17
+ spine-parts at what it wrote.
18
+
19
+ - Project: [shitagaki-lab/see-through](https://github.com/shitagaki-lab/see-through)
20
+ - Paper: Jian Lin, Chengze Li, Haoyun Qin, Kwun Wang Chan, Yanghua Jin, Hanyuan
21
+ Liu, Stephen Chun Wang Choy, Xueting Liu, *See-through: Single-image Layer
22
+ Decomposition for Anime Characters*, SIGGRAPH 2026,
23
+ [arXiv:2602.03749](https://arxiv.org/abs/2602.03749)
24
+ - Licence: Apache License 2.0 — **[observed 2026-09-27]** the repository's
25
+ licence as GitHub reports it.
26
+
27
+ ### The ComfyUI wrapper
28
+
29
+ - Project: [jtydhr88/ComfyUI-See-through](https://github.com/jtydhr88/ComfyUI-See-through)
30
+ - Licence: **[observed 2026-09-27]** there is no `LICENSE` file in the
31
+ repository (`LICENSE` on the default branch `master` returns 404, and GitHub
32
+ reports no licence); `pyproject.toml` declares `license = "MIT"`.
33
+
34
+ spine-parts reads the wrapper's `layers.json` manifest format; it contains no
35
+ wrapper code.
36
+
37
+ ### Weights
38
+
39
+ | Weights | Licence as stated on the model card | Observed |
40
+ | --- | --- | --- |
41
+ | [`layerdifforg/seethroughv0.0.2_layerdiff3d`](https://huggingface.co/layerdifforg/seethroughv0.0.2_layerdiff3d) | `license:apache-2.0` tag | 2026-09-27, Hugging Face model API |
42
+ | [`layerdifforg/seethroughv0.0.1_marigold`](https://huggingface.co/layerdifforg/seethroughv0.0.1_marigold) | no licence tag on the card | 2026-09-27, Hugging Face model API |
43
+
44
+ Maintainer statement, on the Hugging Face discussion
45
+ [`layerdifforg/seethroughv0.0.2_layerdiff3d/discussions/1`](https://huggingface.co/layerdifforg/seethroughv0.0.2_layerdiff3d/discussions/1)
46
+ (user `24yearsold`, 2026-04-07): *"Yes all our models are aligned with our main
47
+ repo to use Apache 2.0."*
48
+
49
+ ### Training data
50
+
51
+ The paper describes its own supervision this way: *"we introduce a scalable
52
+ engine that bootstraps high-quality supervision from commercial Live2D models,
53
+ capturing pixel-perfect semantics and hidden geometry."* That is the paper's
54
+ description of how See-through was trained. spine-parts does not use,
55
+ include or redistribute any of that data, any Live2D model, or anything derived
56
+ from them beyond the layers See-through itself writes for the user's own image.
57
+
58
+ ## 2. Spine Runtimes, through spine-rigc
59
+
60
+ spine-parts depends on [`spine-rigc`](https://www.npmjs.com/package/spine-rigc)
61
+ (MIT, same author), which compiles and validates Spine skeleton data and links
62
+ `@esotericsoftware/spine-core`, part of the
63
+ [Spine Runtimes](https://github.com/EsotericSoftware/spine-runtimes), under the
64
+ [Spine Runtimes License Agreement](https://esotericsoftware.com/spine-runtimes-license).
65
+ spine-rigc's [NOTICE.md](https://github.com/firejune/rigc/blob/main/NOTICE.md)
66
+ sets out the chain in full; briefly, as a restatement of Esoteric Software's
67
+ terms and not a term of this project:
68
+
69
+ 1. What spine-parts produces is input to spine-rigc, whose output **is Spine
70
+ skeleton data**.
71
+ 2. Playing Spine skeleton data in a product requires a Spine Runtime, and the
72
+ Spine Runtimes License requires **each user of such a product to own a
73
+ Spine Editor license**.
74
+ 3. spine-rigc links `spine-core`, and spine-parts depends on spine-rigc, so the
75
+ same obligation applies to running the stages of spine-parts that call it.
76
+
77
+ spine-parts also imports spine-rigc's own modules directly, by deep path: its PNG codec
78
+ (`tools/plate.ts`, `src/png.ts`), its 5x7 label font (`tools/font5x7.ts`) and
79
+ its coordinate conversion (`src/transform.ts`). Those are spine-rigc's MIT code.
80
+
81
+ ## 3. Checkpoints
82
+
83
+ Generating the painting is an **optional** adapter (`spine-parts comfy paint`).
84
+ The checkpoint, any LoRA and any ControlNet it runs are the user's: spine-parts
85
+ ships none and downloads none, and the terms that govern an image they produce
86
+ are the terms of those models. A painting from any other source works the same
87
+ way.
88
+
89
+ The images this repository ships — `assets/demo-*` and `assets/sample-parts.png`
90
+ — and the public examples' paintings were generated with
91
+ `ponyDiffusionV6XL_v6StartWithThisOne.safetensors`,
92
+ [Pony Diffusion V6 XL](https://civitai.com/models/257749), with no LoRA. The
93
+ checkpoint itself is not redistributed. **[observed 2026-09-27]** CivitAI's model
94
+ API reported for it `allowCommercialUse: ["Image", "RentCivit"]`,
95
+ `allowNoCredit: false`, `allowDerivatives: true` and
96
+ `allowDifferentLicense: false`: generated images may be used commercially and
97
+ the model must be credited, which this line and the
98
+ [spine-parts-examples](https://github.com/firejune/spine-parts-examples) README do.
99
+
100
+ ## 4. Behaviour reimplemented, not code copied
101
+
102
+ The raster operations in `src/raster/` reproduce the documented behaviour of
103
+ calls the reference implementation made, so that the port measures the same
104
+ numbers — [OpenCV](https://github.com/opencv/opencv) (Apache-2.0),
105
+ [SciPy](https://github.com/scipy/scipy) (BSD-3-Clause) and
106
+ [Pillow](https://github.com/python-pillow/Pillow) (MIT-CMU). They are written in
107
+ TypeScript for this package and contain none of those projects' code; each
108
+ function's comment says which call it stands in for and what was measured.
109
+
110
+ ## 5. npm dependencies
111
+
112
+ | Package | Licence | Why |
113
+ | --- | --- | --- |
114
+ | [`spine-rigc`](https://www.npmjs.com/package/spine-rigc) | MIT | compile, gate, render and check; its PNG codec, font and coordinate conversion |
115
+ | ↳ [`@esotericsoftware/spine-core`](https://www.npmjs.com/package/@esotericsoftware/spine-core) | Spine Runtimes License | spine-rigc's round trip (section 2) |
116
+ | [`ag-psd`](https://www.npmjs.com/package/ag-psd) | MIT | reading an upstream See-through `.psd` |
117
+ | ↳ [`pako`](https://www.npmjs.com/package/pako) | MIT AND Zlib | ag-psd's deflate |
118
+ | ↳ [`base64-js`](https://www.npmjs.com/package/base64-js) | MIT | ag-psd's base64 |
119
+
120
+ Development only, not installed with the package: `typescript` (Apache-2.0),
121
+ `eslint` (MIT), `typescript-eslint` (MIT), `@types/bun` (MIT).
package/README.md ADDED
@@ -0,0 +1,280 @@
1
+ # spine-parts
2
+
3
+ **AI-authored Spine 2D character rigs from one anime painting, verified before they
4
+ are written.** spine-parts takes a single character painting and the layers
5
+ [See-through](https://github.com/shitagaki-lab/see-through) decomposed it into,
6
+ merges them into measured rig-space parts, authors weighted meshes, bone chains
7
+ and a looping idle over them in [spine-rigc](https://github.com/firejune/rigc)'s
8
+ spec, and hands that spec to spine-rigc to compile, gate, pack, render and check.
9
+ It is built for agents that cannot see the image: every stage prints named,
10
+ numeric findings, a refusal names the object, the value found and the value
11
+ required, nothing is written after a red, and no value is invented where the
12
+ input is silent.
13
+
14
+ ## What you get
15
+
16
+ <p align="center">
17
+ <img src="https://raw.githubusercontent.com/firejune/spine-parts/main/assets/demo-source.png" alt="The demo painting: a generated full-body character in a white and pink frilled dress with long pink twin tails, standing with her hands clasped" height="420" />
18
+ <img src="https://raw.githubusercontent.com/firejune/spine-parts/main/assets/demo-idle.gif" alt="The same character as a Spine rig, breathing, blinking once and swaying her hair, sleeves and skirt in a four-second loop" height="420" />
19
+ </p>
20
+
21
+ <p align="center">
22
+ <img src="https://raw.githubusercontent.com/firejune/spine-parts/main/assets/demo-parts.png" alt="Contact sheet of the painting and the 22 assembled parts it was split into: hair, dress, sleeves, shoes, face, eyes, brows, mouth and ornaments" width="100%" />
23
+ </p>
24
+
25
+ <p align="center"><em>
26
+ The painting was generated for this repository with a public checkpoint (Pony
27
+ Diffusion V6 XL) and no LoRA; its generation record, See-through layers and licence
28
+ are in <a href="https://github.com/firejune/spine-parts-examples">spine-parts-examples</a>.
29
+ See-through ran twice, once on the whole figure and once on a square head crop.
30
+ Then one command: <code>spine-parts build --seam silhouette --loop</code>. The
31
+ proposer's bones, meshes, regions and motion were used <b>unedited</b>. Two input-stage
32
+ edits were made by hand for the reference run this example reproduces: the head box
33
+ was shifted down into the canvas (its proposal ran 118 px above the top edge;
34
+ <code>propose --head-box</code> now makes that shift itself), and <code>hair_back</code>
35
+ is taken from the full run, because the twin tails leave the head crop sideways
36
+ (a plan edit). <code>check</code> printed, verbatim:<br/>
37
+ <code>49 assertions: 23 measured (23 passed, 0 failed), 26 skipped, 0 not in profile "spine-html"</code><br/>
38
+ <code>49 assertions: 14 measured (14 passed, 0 failed), 20 skipped, 15 not in profile "spine"</code><br/>
39
+ <code>loop: idle 49 frame(s) at 12 fps, f0000 vs f0048 (t = 4s): max |d| 0 (0 required)</code><br/>
40
+ <code>seam: setup pose at 714x1216, scale 0.9422: mean |d| 0.324 (&lt;= 1.0), 2 px over 40 (&lt;= 50), 0 px over 80 (reported)</code><br/>
41
+ <code>pack: skeleton.png 1024x2048, 22 region(s), 56.3% covered, padding 2</code><br/>
42
+ <code>--seam silhouette</code> repaired the navy blobs the default rule leaves on the white
43
+ blouse (recomposite error pixels 11,050 → 9,540); the default stays
44
+ <code>near-white</code>, the reference implementation's rule, so the examples stay
45
+ comparable with it. The loop is <code>spine-parts loop</code>'s GIF, 48 frames at
46
+ 12 fps, 1,812,041 bytes; the painting is shown at half size, resampled and written
47
+ by this package's PNG codec (nothing here encodes JPEG). <code>spine-parts sheet</code>
48
+ made the contact sheet.
49
+ </em></p>
50
+
51
+ <p align="center">
52
+ <img src="https://raw.githubusercontent.com/firejune/spine-parts/main/assets/sample-parts.png" alt="Contact sheet of the sample character's painting and its 20 parts" width="560" />
53
+ </p>
54
+
55
+ <p align="center"><em>
56
+ The plain fixture, <code>examples/sample</code>: same checkpoint, no LoRA, and a rig
57
+ built from its own proposal with no hand edit at all —
58
+ <code>seam: … mean |d| 0.207 (&lt;= 1.0), 0 px over 40 (&lt;= 50)</code>, loop max |d| 0.
59
+ </em></p>
60
+
61
+ ## What it takes in
62
+
63
+ - **One painting** — a PNG of one character, front-facing, full body, taller than
64
+ wide.
65
+ - **Its See-through layers, twice**: one run on the whole figure (the painting padded
66
+ white to a square) and one on a square crop around the head, because the eyes of a
67
+ full-figure run are too small to separate. Either form See-through reaches you in
68
+ is read:
69
+ - the **ComfyUI wrapper form** — a directory holding `layers.json` and one RGBA PNG
70
+ per layer (flat beside the manifest, or under `parts/<tag>.png`);
71
+ - an **upstream `.psd`** — one pixel layer per tag, the layer name being the tag,
72
+ the stacking order being the draw order.
73
+ - **A character config** (`config.json`) — the part plan, the head box, the bones,
74
+ which bones may pull which layer, and the idle's sines. `src/config.ts` is its
75
+ schema and documents every field; the loader refuses an unknown or a missing field
76
+ by name. [docs/AUTHORING.md](docs/AUTHORING.md) says where each value comes from.
77
+
78
+ ## What it gives out
79
+
80
+ `spine-parts build` ends the way a Spine editor export does — with **one packed atlas
81
+ page** (issue #2):
82
+
83
+ | path under `--out` | what |
84
+ | --- | --- |
85
+ | `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 |
86
+ | `parts/*.png`, `parts.json`, `recomposite_rig.png` | the loose parts, each cropped to its alpha box; the record of where every part came from and how many of its pixels were re-taken from the painting; the flat stack of parts |
87
+ | `rig/` | `rig.json` and `motion.json` in spine-rigc's spec, `mesh_report.json`, the padded `images/` |
88
+ | `check/` | both gate files verbatim, the idle's frames, `contact.png`, `motion_heat.png`, `check.json` |
89
+ | `idle.gif`, `idle.png` | with `--loop`: the idle as a GIF and a lossless APNG |
90
+
91
+ The last lines of a green build are the pack line, printed beside the Spine example
92
+ export's `spineboy.png` as a yardstick (a reference, not a bar), and the three
93
+ artifact paths — here for `examples/sample`:
94
+
95
+ ```
96
+ build: PASS — the packed atlas is the artifact; parts/ and rig/ are the intermediates it was made from
97
+ pack: skeleton.png 512x2048, 20 region(s), 49.7% covered, padding 2; page opaque 28.7% (alpha > 0) — spineboy yardstick 1024x256, 40 region(s), 45.8% opaque (alpha > 0), a reference and not a bar
98
+ out/check/build/skeleton.json
99
+ out/check/build/skeleton.atlas
100
+ out/check/build/skeleton.png
101
+ ```
102
+
103
+ The packer is spine-rigc's, and no other: a packed region is a lossless copy, and an
104
+ atlas written by anything else would have no oracle behind it.
105
+
106
+ ## Painting → parts → rig → browser
107
+
108
+ | stage | tool | what it owns |
109
+ | --- | --- | --- |
110
+ | painting + See-through layers → parts and specs | **spine-parts** | the merge of two runs, the measurements, the rig spec and motion spec |
111
+ | specs → Spine skeleton data | **[spine-rigc](https://github.com/firejune/rigc)** | compile, the round trip through `spine-core`, the named assertions, the packer, the renderer |
112
+ | 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` |
113
+
114
+ spine-parts never writes Spine data itself. Everything on disk under `check/build/`
115
+ was written by spine-rigc after its own gate passed.
116
+
117
+ ## Getting See-through layers
118
+
119
+ See-through is required; how you run it is not. spine-parts does not vendor, embed or
120
+ redistribute See-through code or weights — run it by any of these and point
121
+ spine-parts at what it wrote:
122
+
123
+ | Route | Where | Output spine-parts reads |
124
+ | --- | --- | --- |
125
+ | Hugging Face Space | [24yearsold/see-through-demo](https://huggingface.co/spaces/24yearsold/see-through-demo) (ZeroGPU; upstream states 1-2 extractions a day for a registered user) | the `.psd` it produces |
126
+ | ModelScope demo | [ljsabc/See-Through](https://modelscope.cn/studios/ljsabc/See-Through), linked from the upstream README | the `.psd` it produces |
127
+ | Upstream CLI | `python inference/scripts/inference_psd.py --srcp <image> --save_to_psd` in a checkout of [shitagaki-lab/see-through](https://github.com/shitagaki-lab/see-through) | the `.psd` in `workspace/layerdiff_output/` |
128
+ | ComfyUI wrapper | [jtydhr88/ComfyUI-See-through](https://github.com/jtydhr88/ComfyUI-See-through) on your own ComfyUI box; the optional `comfy` adapter (`spine-parts comfy seethrough`) drives it | the `layers.json` + PNGs it writes |
129
+
130
+ **See-through is required; ComfyUI is not.** The wrapper is one route among
131
+ four, and `spine-parts comfy` is only a convenience for that route: every
132
+ stage after See-through reads files, whichever route wrote them. On the two
133
+ public examples each run took 171–199 s through the wrapper (each run's
134
+ `meta.json`, in [spine-parts-examples](https://github.com/firejune/spine-parts-examples)).
135
+
136
+ ### The two images See-through is fed: `inputs`
137
+
138
+ ```sh
139
+ spine-parts inputs --source painting.png --config config.json --out <dir>
140
+ ```
141
+
142
+ cuts the two images See-through is fed: the painting centred on a white square
143
+ (`st_input_full.png`) and, when the config sets `seethrough.head_box`, that
144
+ box at its exact size (`st_input_head.png`). Pure raster, no GPU.
145
+
146
+ ### The optional ComfyUI adapter
147
+
148
+ For a user who has a ComfyUI box, two
149
+ commands drive it; nothing else in the pipeline needs one:
150
+
151
+ ```sh
152
+ spine-parts comfy paint --config config.json --out <dir> --host http://<box>:8188
153
+ spine-parts comfy seethrough --image st_input_full.png --out layers/full --host http://<box>:8188 --offload
154
+ ```
155
+
156
+ `comfy paint` generates `painting_<seed>.png` from the config's inline
157
+ `generation` block — checkpoint, LoRAs, sampler, and an optional OpenPose
158
+ control skeleton it draws itself — and records the prompts verbatim beside it.
159
+ `comfy seethrough` runs the [ComfyUI-See-through](https://github.com/jtydhr88/ComfyUI-See-through)
160
+ wrapper on one image and writes the wrapper form `layers` reads. The host comes
161
+ from `--host` or `COMFY_HOST` and has no default; before anything is uploaded
162
+ the graph is checked against the box's `/object_info`, so a missing node class
163
+ or model is refused by name, and the adapter waits for an empty queue rather
164
+ than queueing behind someone else's job.
165
+
166
+ ## The loop, for an agent
167
+
168
+ ```sh
169
+ spine-parts inputs --source painting.png --config config.json --out inputs # st_input_full.png; the config needs only key, seethrough, assemble.rig_scale
170
+ # See-through on st_input_full.png (external, or `spine-parts comfy seethrough`) -> layers/full
171
+ spine-parts layers layers/full # every layer: box, opaque px, depth
172
+ spine-parts propose --head-box --full layers/full --canvas 1664x2432
173
+ # -> seethrough.head_box into config.json
174
+ spine-parts inputs --source painting.png --config config.json --out inputs # now st_input_head.png too
175
+ # See-through on st_input_head.png (external, or `spine-parts comfy seethrough`) -> layers/head
176
+ spine-parts sheet --source painting.png --layers layers/full --layers layers/head --out sheets/layers.png
177
+ spine-parts assemble --propose-plan --source painting.png --full layers/full --head layers/head --config config.json
178
+ # -> assemble.plan and extend_below_crop
179
+ spine-parts assemble --source painting.png --full layers/full --head layers/head --config config.json --out work
180
+ spine-parts propose --parts work/rig --source painting.png --out work
181
+ # -> proposal.json and render/landmarks.png; correct it, copy bones/meshes/regions/motion into config.json
182
+ spine-parts propose --parts work/rig --source painting.png --out work --from-config config.json
183
+ # -> LINT lines; exit 1 while any remain
184
+ spine-parts build --config config.json --source painting.png --full layers/full --head layers/head --out out --loop
185
+ # -> read out/check/check.json; every FAIL line names what has to change
186
+ ```
187
+
188
+ `propose` is deliberately not a step of `build`: the proposal is a draft to correct
189
+ against its overlay, and a config with bones is `build`'s input. `rig`, `check` and
190
+ `loop` are the same stages one at a time. [docs/AUTHORING.md](docs/AUTHORING.md) is
191
+ the guide an agent authors from — every config field, what to look at after each
192
+ stage, what each refusal means and which field it points at — and
193
+ [`skills/spine-parts/SKILL.md`](skills/spine-parts/SKILL.md) is the same loop as an
194
+ agent skill.
195
+
196
+ ## Commands
197
+
198
+ | command | does |
199
+ | --- | --- |
200
+ | `inputs --source <png> --config <json> --out <dir>` | the two images See-through is fed: the painting on a white square, and the head box's crop once the config has one |
201
+ | `comfy paint --config --out [--host]` | optional: generate the painting on a ComfyUI box from the config's `generation` block |
202
+ | `comfy seethrough --image --out [--host]` | optional: run the ComfyUI See-through wrapper on one image and write the form `layers` reads |
203
+ | `layers <dir \| layers.json \| file.psd>` | print every layer of a decomposition: draw order, name, tag group, box, size, opaque pixels, depth |
204
+ | `sheet --source <png> --layers <path>… --out <png>` | a labelled contact sheet of the painting and every layer or part |
205
+ | `assemble --propose-plan …` | propose `assemble.plan` and `extend_below_crop` from the two runs |
206
+ | `assemble --source --full --head --config --out [--seam]` | merge the two runs into rig-space parts, `parts.json` and the recomposite |
207
+ | `propose --head-box --full <run> --canvas WxH` | propose `seethrough.head_box` from the full run, held inside the painting |
208
+ | `propose --parts --source --out [--compare <config>]` | propose bones, meshes, regions and an idle; draw the overlay |
209
+ | `propose … --from-config <config>` | draw and LINT the config's current bones |
210
+ | `rig --config --parts --out` | author `rig.json` + `motion.json`, written only after spine-rigc's round trip is green |
211
+ | `check --rig --out [--parts]` | build packed, gate under both profiles, render the idle, measure seam and loop |
212
+ | `loop --frames <dir> --out <file.gif \| file.png>` | encode a rendered idle as a looping GIF or APNG |
213
+ | `build --config --source --full --head --out [--seam] [--loop]` | assemble, rig and check in one process, stopping at the first refusal |
214
+
215
+ `spine-parts --help` has every flag. Exit codes: 0 done, 1 input refused (every
216
+ reason is a FAIL line), 2 a usage error or a command this version does not implement.
217
+
218
+ ## What it does not do
219
+
220
+ These are limits of the approach, stated so nobody reads more into a green run:
221
+
222
+ - **One depth value per layer.** See-through gives each layer a single depth, so a
223
+ layer that is in front of another in one place and behind it in another is drawn
224
+ right in the still (the painting's own pixels are projected onto it) and wrong once
225
+ it moves.
226
+ - **No expression or lip-sync.** The mouth is one layer; there is no mouth-shape set
227
+ and no expression axis.
228
+ - **Occluded pixels are See-through's synthesis**, not the artist's. How much of a rig
229
+ that is, is below.
230
+ - **No success rate is claimed.** Ten characters have been measured stage by stage
231
+ against the reference implementation this package ports — the two public examples
232
+ here and eight private ones, one See-through seed each — and all ten check green.
233
+ That is an existence proof, not a rate.
234
+ - **`loop` writes GIF and APNG, not WebP**: an animated WebP needs a VP8/VP8L
235
+ encoder, which this package does not carry.
236
+ - **The proposer reads tags, not pictures.** A swinging element painted inside another
237
+ layer (a sash tail in the skirt), hair that is none of the shapes it knows, and
238
+ whether an accessory swings are the corrector's to add.
239
+
240
+ ### How much of a rig the model painted
241
+
242
+ `parts.json` records, per part, how many opaque pixels were re-taken from the painting
243
+ (`source_px_taken`) against the part's opaque pixels (`opaque_px`); the rest is
244
+ See-through's synthesis. Across the ten reference rigs, **41.8 %** of all rig pixels
245
+ are not source pixels (issue #9); on the two public examples it is 38.6 % (`sample`)
246
+ and 36.4 % (`demo`), from their `expected/parts.json`. Most of it is art that really
247
+ is hidden — a back-hair layer, a neck — but the figure **over-counts**: a thin part
248
+ that is fully visible (a brow, a lash, an iris) has no eroded opaque core, so none of
249
+ its pixels qualify for projection and they count as synthesized (issue #9).
250
+
251
+ ## Requirements
252
+
253
+ [Bun](https://bun.sh) 1.2 or later. `npm install -g spine-parts` installs the
254
+ `spine-parts` command; it hands off to Bun and says so in one sentence if Bun is not
255
+ on `PATH`. spine-rigc comes with it as a dependency.
256
+
257
+ ## Licence posture
258
+
259
+ spine-parts is MIT, and it depends on spine-rigc, which links Esoteric Software's
260
+ `spine-core`: using what it produces in a product requires a Spine Editor licence, as
261
+ any Spine data does. [NOTICE.md](NOTICE.md) records that chain and every other
262
+ third-party term this package touches, See-through's included.
263
+
264
+ ## Development
265
+
266
+ ```sh
267
+ bun install
268
+ bun run typecheck # tsc --noEmit, strict
269
+ bun run lint # one rule: no explicit any
270
+ bun run fetch-examples # the public examples' paintings and layers, into examples/*/inputs (needs a network)
271
+ bun run selftest # every gate's controls; with the examples fetched, build on each of them against its expected/
272
+ bun run smoke # pack, install into an empty directory, run from the install (needs a network)
273
+ ```
274
+
275
+ [CLAUDE.md](CLAUDE.md) is the doctrine, [CONTRIBUTING.md](CONTRIBUTING.md) the
276
+ practice, [RELEASING.md](RELEASING.md) the cut.
277
+
278
+ ## Licence
279
+
280
+ MIT — see [LICENSE](LICENSE). Third-party terms: [NOTICE.md](NOTICE.md).
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * spine-parts' `bin` entry has one job: hand off to Bun.
6
+ *
7
+ * npm's `bin` field has to be something any installed Node can run, but
8
+ * spine-parts is a Bun program (it runs its TypeScript sources directly, and so
9
+ * does spine-rigc underneath it). Without this file, a machine with no Bun
10
+ * would fail as a bare `env: bun: No such file or directory`, with no hint
11
+ * why. So: if `bun` is on PATH, run the real CLI (cli.ts, next to this file)
12
+ * under it and disappear — argv, stdio, the exit code and signals all pass
13
+ * straight through. If it isn't, say so once and stop. No downloads, no
14
+ * network, no writes — just the hand-off or the message.
15
+ */
16
+
17
+ const path = require('node:path');
18
+ const { spawnSync } = require('node:child_process');
19
+
20
+ const cli = path.join(__dirname, '..', 'cli.ts');
21
+ const result = spawnSync('bun', [cli, ...process.argv.slice(2)], { stdio: 'inherit' });
22
+
23
+ if (result.error) {
24
+ if (result.error.code === 'ENOENT') {
25
+ process.stderr.write('spine-parts runs on Bun, which was not found on PATH — install it from https://bun.sh\n');
26
+ } else {
27
+ process.stderr.write(`spine-parts: could not launch bun: ${result.error.message}\n`);
28
+ }
29
+ process.exit(1);
30
+ }
31
+
32
+ if (result.signal) {
33
+ // A signal (e.g. Ctrl-C) killed the child — die the same way instead of
34
+ // inventing an exit code, so the caller sees what it would have seen running
35
+ // bun directly.
36
+ process.kill(process.pid, result.signal);
37
+ } else {
38
+ process.exit(typeof result.status === 'number' ? result.status : 1);
39
+ }