spine-parts 0.2.0 → 0.3.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 +19 -8
- package/cli.ts +76 -26
- package/docs/AUTHORING.md +264 -32
- package/package.json +1 -1
- package/skills/spine-parts/SKILL.md +23 -4
- package/src/assemble.ts +369 -18
- package/src/build.ts +25 -11
- package/src/check.ts +59 -3
- package/src/config.ts +238 -40
- package/src/instruments.ts +8 -4
- package/src/layers.ts +169 -0
- package/src/motion.ts +4 -0
- package/src/parts.ts +178 -7
- package/src/propose.ts +485 -48
- package/src/rig.ts +80 -3
package/README.md
CHANGED
|
@@ -85,7 +85,7 @@ page** (issue #2):
|
|
|
85
85
|
| path under `--out` | what |
|
|
86
86
|
| --- | --- |
|
|
87
87
|
| `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 |
|
|
88
|
-
| `parts/*.png`, `parts.json`, `recomposite_rig.png` | the loose parts, each cropped to its alpha box; the record of where every part came from
|
|
88
|
+
| `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 |
|
|
89
89
|
| `rig/` | `rig.json` and `motion.json` in spine-rigc's spec, `mesh_report.json`, the padded `images/` |
|
|
90
90
|
| `check/` | both gate files verbatim, the idle's frames, `contact.png`, `motion_heat.png`, `check.json` |
|
|
91
91
|
| `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 |
|
|
@@ -168,9 +168,9 @@ than queueing behind someone else's job.
|
|
|
168
168
|
## The loop, for an agent
|
|
169
169
|
|
|
170
170
|
```sh
|
|
171
|
-
spine-parts inputs --source painting.png --config config.json --out inputs # st_input_full.png
|
|
171
|
+
spine-parts inputs --source painting.png --config config.json --out inputs # st_input_full.png
|
|
172
172
|
# See-through on st_input_full.png (external, or `spine-parts comfy seethrough`) -> layers/full
|
|
173
|
-
spine-parts layers layers/full # every layer: box, opaque px, depth
|
|
173
|
+
spine-parts layers layers/full # every layer: box, opaque px, depth, plausibility figures; WARN lines
|
|
174
174
|
spine-parts propose --head-box --full layers/full --canvas 1664x2432
|
|
175
175
|
# -> seethrough.head_box into config.json
|
|
176
176
|
spine-parts inputs --source painting.png --config config.json --out inputs # now st_input_head.png too
|
|
@@ -179,6 +179,10 @@ spine-parts sheet --source painting.png --layers layers/full --layers layers/hea
|
|
|
179
179
|
spine-parts assemble --propose-plan --source painting.png --full layers/full --head layers/head --config config.json
|
|
180
180
|
# -> assemble.plan and extend_below_crop
|
|
181
181
|
spine-parts assemble --source painting.png --full layers/full --head layers/head --config config.json --out work
|
|
182
|
+
# -> work/rig: parts.json and parts/; the config holds no bones, meshes, regions or motion yet
|
|
183
|
+
# -> read the `uncovered hole N:` lines and look at work/render/recomposite_error_rig.png:
|
|
184
|
+
# red is painting that no part holds, and no later gate can see it
|
|
185
|
+
# -> a large red hole neither run holds? add an assemble.patches entry (cut from the painting) and assemble again
|
|
182
186
|
spine-parts propose --parts work/rig --source painting.png --out work
|
|
183
187
|
# -> proposal.json and render/landmarks.png; correct it, copy bones/meshes/regions/motion into config.json
|
|
184
188
|
spine-parts propose --parts work/rig --source painting.png --out work --from-config config.json
|
|
@@ -187,6 +191,11 @@ spine-parts build --config config.json --source painting.png --full layers/full
|
|
|
187
191
|
# -> read out/check/check.json; every FAIL line names what has to change
|
|
188
192
|
```
|
|
189
193
|
|
|
194
|
+
At each step the config holds only what that step reads; the one table of what
|
|
195
|
+
that is, step by step, is [docs/AUTHORING.md §4](docs/AUTHORING.md#4-the-command-order).
|
|
196
|
+
The selftest runs this block in order, command by command, on each fetched example,
|
|
197
|
+
from a config holding only what the first step reads (`RL01`).
|
|
198
|
+
|
|
190
199
|
`propose` is deliberately not a step of `build`: the proposal is a draft to correct
|
|
191
200
|
against its overlay, and a config with bones is `build`'s input. `rig`, `check` and
|
|
192
201
|
`loop` are the same stages one at a time. [docs/AUTHORING.md](docs/AUTHORING.md) is
|
|
@@ -202,15 +211,15 @@ agent skill.
|
|
|
202
211
|
| `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 |
|
|
203
212
|
| `comfy paint --config --out [--host]` | optional: generate the painting on a ComfyUI box from the config's `generation` block |
|
|
204
213
|
| `comfy seethrough --image --out [--host]` | optional: run the ComfyUI See-through wrapper on one image and write the form `layers` reads |
|
|
205
|
-
| `layers <dir \| layers.json \| file.psd>` | print every layer of a decomposition: draw order, name, tag group, box, size, opaque pixels, depth |
|
|
214
|
+
| `layers <dir \| layers.json \| file.psd>` | print every layer of a decomposition: draw order, name, tag group, box, size, opaque pixels, depth, and its translucent, background and area figures; a `WARN` line for a layer `--propose-plan` will leave out |
|
|
206
215
|
| `sheet --source <png> --layers <path>… --out <png>` | a labelled contact sheet of the painting and every layer or part |
|
|
207
|
-
| `assemble --propose-plan …` | propose `assemble.plan` and `extend_below_crop` from the two runs |
|
|
208
|
-
| `assemble --source --full --head --config --out [--seam] [--project]` | merge the two runs into rig-space parts, `parts.json
|
|
216
|
+
| `assemble --propose-plan …` | propose `assemble.plan` and `extend_below_crop` from the two runs, leaving out an implausible layer with a note naming the rule |
|
|
217
|
+
| `assemble --source --full --head --config --out [--seam] [--project]` | merge the two runs into rig-space parts, `parts.json`, the recomposite and its error map, and list the uncovered holes |
|
|
209
218
|
| `propose --head-box --full <run> --canvas WxH` | propose `seethrough.head_box` from the full run, held inside the painting |
|
|
210
219
|
| `propose --parts --source --out [--compare <config>]` | propose bones, meshes, regions and an idle; draw the overlay |
|
|
211
220
|
| `propose … --from-config <config>` | draw and LINT the config's current bones |
|
|
212
221
|
| `rig --config --parts --out` | author `rig.json` + `motion.json`, written only after spine-rigc's round trip is green |
|
|
213
|
-
| `check --rig --out [--parts]` | build packed, gate under both profiles, render the idle, measure seam, loop and the five judgement lines |
|
|
222
|
+
| `check --rig --out [--parts]` | build packed, gate under both profiles, render the idle, measure seam, loop and the five judgement lines, and report the recomposite's holes from `parts.json` |
|
|
214
223
|
| `loop --frames <dir> --out <file.gif \| file.png> [--palette]` | encode a rendered idle as a looping GIF, lossless APNG, or indexed APNG (`--palette`) |
|
|
215
224
|
| `build --config --source --full --head --out [--seam] [--project] [--loop]` | assemble, rig and check in one process, stopping at the first refusal |
|
|
216
225
|
|
|
@@ -239,7 +248,9 @@ These are limits of the approach, stated so nobody reads more into a green run:
|
|
|
239
248
|
encoder, which this package does not carry.
|
|
240
249
|
- **The proposer reads tags, not pictures.** A swinging element painted inside another
|
|
241
250
|
layer (a sash tail in the skirt), hair that is none of the shapes it knows, and
|
|
242
|
-
whether an accessory swings are the corrector's to add.
|
|
251
|
+
whether an accessory swings are the corrector's to add. The one accessory shape it
|
|
252
|
+
measures is a hanging strand on a headwear or earwear layer: each gets a pendulum
|
|
253
|
+
chain, and a strand it cannot chain is named in a note rather than left stiff.
|
|
243
254
|
|
|
244
255
|
### How much of a rig the model painted
|
|
245
256
|
|
package/cli.ts
CHANGED
|
@@ -16,16 +16,16 @@ import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, statSync, wri
|
|
|
16
16
|
import { tmpdir } from 'node:os';
|
|
17
17
|
import { basename, dirname, extname, join } from 'node:path';
|
|
18
18
|
import { fileURLToPath } from 'node:url';
|
|
19
|
-
import { DEFAULT_PROJECT_RULE, DEFAULT_SEAM_RULE, PROJECT_RULES, type ProjectRule, proposeFields, proposePlan, SEAM_RULES, type SeamRule } from './src/assemble.ts';
|
|
20
|
-
import { assembleStage, build, checkStage, loopStage, readRuns, readSource, rigStage } from './src/build.ts';
|
|
21
|
-
import { findRigc, type RigcRunner, SEAM_MEAN_BAR, SEAM_PX_BAR, SEAM_PX_LEVEL, SPINEBOY_YARDSTICK } from './src/check.ts';
|
|
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
|
+
import { assembleStage, build, checkStage, ERROR_MAP_FILE, loopStage, readRuns, readSource, rigStage } from './src/build.ts';
|
|
21
|
+
import { findRigc, PARTS_HOME_SENTENCE, type RigcRunner, SEAM_MEAN_BAR, SEAM_PX_BAR, SEAM_PX_LEVEL, SPINEBOY_YARDSTICK } 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';
|
|
25
25
|
import { proposeHeadBox } from './src/headbox.ts';
|
|
26
26
|
import { makeInputs } from './src/inputs.ts';
|
|
27
|
-
import { type LayerSet, readLayers } from './src/layers.ts';
|
|
28
|
-
import { checkProposal, compare, compareLines, drawLandmarks, lint, lintLine, type PartSet, propose, readPartSet, serializeProposal } from './src/propose.ts';
|
|
27
|
+
import { figuresPhrase, implausibleRules, layerFigures, type LayerSet, pct, readLayers, ruleSummary, times } from './src/layers.ts';
|
|
28
|
+
import { checkProposal, compare, compareLines, drawLandmarks, HIP_MIN_FRACTION, lint, lintLine, type PartSet, propose, readPartSet, serializeProposal } from './src/propose.ts';
|
|
29
29
|
import { readPng, writePng } from './src/raster/png.ts';
|
|
30
30
|
import { buildSheet, defaultCaption, type Tile, tilesFrom } from './src/sheet.ts';
|
|
31
31
|
|
|
@@ -52,9 +52,14 @@ usage:
|
|
|
52
52
|
spine-parts layers <dir | layers.json | file.psd>
|
|
53
53
|
Read a See-through decomposition — the ComfyUI wrapper form (a directory
|
|
54
54
|
holding layers.json and one PNG per layer) or an upstream .psd — and print
|
|
55
|
-
every layer: draw order, name, tag group, box, size, opaque pixels, depth
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
every layer: draw order, name, tag group, box, size, opaque pixels, depth,
|
|
56
|
+
and three plausibility figures over its opaque pixels — translucent share
|
|
57
|
+
(alpha below 128), background share (min channel above 235) and area as a
|
|
58
|
+
multiple of the rest of the figure (the other layers' union). A layer that
|
|
59
|
+
crosses a plausibility rule gets a WARN line naming the rule and the bar;
|
|
60
|
+
--propose-plan leaves it out. Refuses, by name, a missing file, an
|
|
61
|
+
unknown tag, a PNG whose size is not its box, and anything else outside
|
|
62
|
+
the input contract — never a WARN.
|
|
58
63
|
|
|
59
64
|
spine-parts sheet --source <painting.png> --layers <path> [--layers <path> ...]
|
|
60
65
|
--out <sheet.png> [--cell <px>] [--cols <n>]
|
|
@@ -68,9 +73,15 @@ usage:
|
|
|
68
73
|
Propose bones, meshes, regions and an idle from the assembled parts (<dir>
|
|
69
74
|
holds parts.json and parts/). Roles come from each part's See-through tag,
|
|
70
75
|
never its name. Writes <out>/proposal.json (config-shaped: bones, meshes,
|
|
71
|
-
regions, motion with its blink
|
|
72
|
-
|
|
73
|
-
|
|
76
|
+
regions, motion with its blink — and a blink.still cut for a lash that
|
|
77
|
+
reaches far above its eyewhite, when a clear row allows one — and notes)
|
|
78
|
+
and the overlay to correct against, <out>/render/landmarks.png and
|
|
79
|
+
landmarks_head.png. Prints every
|
|
80
|
+
note and a LINT line for each chain link that lies off its mesh's art, for a
|
|
81
|
+
hip that is not below the chest, and for a hip above ${HIP_MIN_FRACTION} of the figure's
|
|
82
|
+
height (the shoulders); a headwear/earwear layer with hanging strands
|
|
83
|
+
gets one pendulum chain per strand and a note with each strand's x, rows
|
|
84
|
+
and width ("-- no chain proposed" is the one to act on; AUTHORING §3).
|
|
74
85
|
--compare prints each shared bone's distance, proposal to config, in px.
|
|
75
86
|
|
|
76
87
|
spine-parts propose --parts <dir> --source <painting.png> --out <dir> --from-config <config.json>
|
|
@@ -86,7 +97,9 @@ usage:
|
|
|
86
97
|
Author the rig: unrotated bones at the config's landmarks (a chain makes
|
|
87
98
|
<chain>0..n), a square lattice mesh over every part in config.meshes
|
|
88
99
|
weighted by distance to its candidate bone segments, a region for every
|
|
89
|
-
part in config.regions
|
|
100
|
+
part in config.regions (a motion.blink.still part as two: the rows above
|
|
101
|
+
its row on a second slot <part>_still, which the blink does not move),
|
|
102
|
+
and one idle of sines and a blink. --parts is the
|
|
90
103
|
directory holding parts.json and parts/<name>.png. The result is built
|
|
91
104
|
through spine-rigc (profile spine-html, packed, then validated under
|
|
92
105
|
profile spine) in a scratch directory first, and --out receives
|
|
@@ -95,8 +108,9 @@ usage:
|
|
|
95
108
|
spine-parts check --rig <dir> --out <dir> [--parts <dir>]
|
|
96
109
|
Build, gate, render and measure a rig through spine-rigc's CLI (the rigc at
|
|
97
110
|
node_modules/.bin/rigc, or on PATH). --rig holds rig.json and motion.json
|
|
98
|
-
(with an "idle")
|
|
99
|
-
|
|
111
|
+
(with an "idle").
|
|
112
|
+
${PARTS_HOME_SENTENCE}.
|
|
113
|
+
Both are only read. Into --out:
|
|
100
114
|
build/ (rigc build --profile spine-html --pack: the packed atlas is the
|
|
101
115
|
artifact), gate_spine-html.txt and gate_spine.txt (the gate lines
|
|
102
116
|
verbatim), idle_frames/ (rigc render --animation idle --fps 12 --max 640),
|
|
@@ -114,7 +128,11 @@ usage:
|
|
|
114
128
|
STILL_REGIONS_DARK (the heat map over the face outline and the feet).
|
|
115
129
|
Regions come from parts.json's See-through tags; a line with nothing to
|
|
116
130
|
read says SKIP and why — neither a pass nor a failure — and PASS needs
|
|
117
|
-
every line that measured to be PASS.
|
|
131
|
+
every line that measured to be PASS. Then RECOMPOSITE_HOLES: REPORTED,
|
|
132
|
+
read from parts.json's recomposite block (uncovered error px, hole count,
|
|
133
|
+
the largest hole's box and the parts bordering it) — a line with no bar,
|
|
134
|
+
never a FAIL, because a pixel no part holds is missing from both sides of
|
|
135
|
+
the seam; SKIP when parts.json has no such block. Prints the pack line
|
|
118
136
|
beside the spineboy yardstick (${SPINEBOY_YARDSTICK}), a reference and not
|
|
119
137
|
a bar. Exit 0 on PASS, 1 on FAIL — every FAIL line names the bar, the value
|
|
120
138
|
and the value required.
|
|
@@ -137,14 +155,25 @@ usage:
|
|
|
137
155
|
--config <config.json> --out <dir> [--seam near-white|silhouette]
|
|
138
156
|
[--project core|visible]
|
|
139
157
|
Merge the full-body and head-crop See-through runs into rig-space parts:
|
|
140
|
-
<out>/rig/parts/<name>.png (each cropped to its alpha box), <out>/rig/parts.json
|
|
141
|
-
|
|
142
|
-
|
|
158
|
+
<out>/rig/parts/<name>.png (each cropped to its alpha box), <out>/rig/parts.json,
|
|
159
|
+
<out>/render/recomposite_rig.png and <out>/render/${ERROR_MAP_FILE} (the error
|
|
160
|
+
map: uncovered error px red, covered error px blue, the rest the painting in
|
|
161
|
+
light grey). Reads config.seethrough.head_box and
|
|
162
|
+
.resolution and config.assemble.rig_scale, .plan, .extend_below_crop and
|
|
163
|
+
.patches — extra parts cut from the painting itself over a rig-pixel box
|
|
164
|
+
(alpha "silhouette": the painting's figure inside it; "box": all of it),
|
|
165
|
+
drawn "back", "front" or {"before": <plan part>}, recorded in parts.json
|
|
166
|
+
as from "painting:<name>" and counted 100 % source — and no rig section:
|
|
167
|
+
bones, meshes, regions and motion need not exist yet, because propose
|
|
168
|
+
drafts them from these parts (AUTHORING §4, the table).
|
|
143
169
|
Prints one line per part, the seam override counts, the \`pixels:\` totals
|
|
144
170
|
(opaque = visible + occluded; taken from the painting; visible but not
|
|
145
171
|
projected), and \`recomposite vs source\` (mean |d| and % within 8 over the
|
|
146
172
|
mean channel; error px: max channel > 40; uncovered: of those, where no
|
|
147
|
-
part has alpha above 128)
|
|
173
|
+
part has alpha above 128), then the uncovered holes (8-connected) and the
|
|
174
|
+
largest ${HOLES_LISTED}, each \`uncovered hole N: <px> px at x,y wxh (between
|
|
175
|
+
"<part>" <px> px, …)\` — the list parts.json holds under recomposite.
|
|
176
|
+
Writes nothing unless every check passed.
|
|
148
177
|
--seam defaults to ${DEFAULT_SEAM_RULE}. --project says where a layer takes
|
|
149
178
|
the painting's pixel: core (the reference's) erodes every layer's top-most
|
|
150
179
|
opaque area by 5x5 first, so a part a few pixels wide takes none; visible
|
|
@@ -154,8 +183,11 @@ usage:
|
|
|
154
183
|
spine-parts assemble --propose-plan --source <painting.png> --full <dir|psd>
|
|
155
184
|
--head <dir|psd> --config <config.json>
|
|
156
185
|
Print {plan, extend_below_crop, notes} for config.assemble, from the two
|
|
157
|
-
runs.
|
|
158
|
-
|
|
186
|
+
runs. A layer \`layers\` WARNs about (PLAN_LAYER_TRANSLUCENT,
|
|
187
|
+
PLAN_LAYER_BACKGROUND, PLAN_LAYER_OVERSIZED) is not proposed, and a note
|
|
188
|
+
names it, its figures and the rule. Reads only config.seethrough.head_box,
|
|
189
|
+
config.seethrough.resolution and config.assemble.rig_scale — the rest of
|
|
190
|
+
the config need not exist yet.
|
|
159
191
|
|
|
160
192
|
spine-parts inputs --source <painting.png> --config <config.json> --out <dir>
|
|
161
193
|
Cut the two images See-through is fed: <out>/st_input_full.png (the
|
|
@@ -194,7 +226,8 @@ usage:
|
|
|
194
226
|
stops the build with its own FAIL lines. The config must already carry
|
|
195
227
|
bones, meshes, regions and motion — propose is not a step of build: run it,
|
|
196
228
|
correct the proposal against its overlay, and write the result into the
|
|
197
|
-
config. Into --out: parts/ + parts.json + recomposite_rig.png
|
|
229
|
+
config. Into --out: parts/ + parts.json + recomposite_rig.png +
|
|
230
|
+
${ERROR_MAP_FILE} (assemble),
|
|
198
231
|
rig/ (rig), check/ (check, with the packed build in check/build/), and with
|
|
199
232
|
--loop idle.png (lossless APNG), idle-indexed.png (indexed APNG) and
|
|
200
233
|
idle.gif from check/idle_frames/, then one loop: line with the three sizes
|
|
@@ -233,7 +266,8 @@ function fixed(v: number | null): string {
|
|
|
233
266
|
function printLayerTable(set: LayerSet): void {
|
|
234
267
|
console.log(`spine-parts layers: ${set.form === 'wrapper' ? 'ComfyUI wrapper form' : 'PSD'}, ${set.source}`);
|
|
235
268
|
console.log(` canvas ${set.canvas.w}x${set.canvas.h}, ${set.layers.length} layer(s), back to front`);
|
|
236
|
-
const
|
|
269
|
+
const figures = layerFigures(set);
|
|
270
|
+
const rows = set.layers.map((l, i) => [
|
|
237
271
|
String(l.drawOrder),
|
|
238
272
|
l.name,
|
|
239
273
|
l.tag.group,
|
|
@@ -242,14 +276,26 @@ function printLayerTable(set: LayerSet): void {
|
|
|
242
276
|
`${l.pixels.width}x${l.pixels.height}`,
|
|
243
277
|
String(l.opaquePx),
|
|
244
278
|
fixed(l.depth),
|
|
279
|
+
pct(figures[i].translucent),
|
|
280
|
+
pct(figures[i].background),
|
|
281
|
+
times(figures[i].areaRatio),
|
|
245
282
|
]);
|
|
246
|
-
const head = ['order', 'name', 'group', 'left,top', 'right,bottom', 'size', 'opaque_px', 'depth'];
|
|
283
|
+
const head = ['order', 'name', 'group', 'left,top', 'right,bottom', 'size', 'opaque_px', 'depth', 'translucent', 'background', 'area'];
|
|
247
284
|
const widths = head.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
248
285
|
const line = (cells: string[]): string => ` ${cells.map((c, i) => c.padEnd(widths[i])).join(' ')}`.trimEnd();
|
|
249
286
|
console.log(line(head));
|
|
250
287
|
for (const r of rows) console.log(line(r));
|
|
251
288
|
const painted = set.layers.filter((l) => l.opaquePx > 0).length;
|
|
252
289
|
console.log(` ${set.layers.length} layer(s): ${painted} with opaque pixels, ${set.layers.length - painted} with none`);
|
|
290
|
+
// A reader refuses nothing on plausibility: it says what --propose-plan will leave out, and why.
|
|
291
|
+
let warned = 0;
|
|
292
|
+
for (const f of figures) {
|
|
293
|
+
for (const rule of implausibleRules(f)) {
|
|
294
|
+
console.log(` WARN ${rule}: layer "${f.name}" — ${figuresPhrase(f)}; ${ruleSummary(rule)} is required, so --propose-plan leaves it out`);
|
|
295
|
+
warned++;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
console.log(` ${warned} WARN line(s)`);
|
|
253
299
|
}
|
|
254
300
|
|
|
255
301
|
function cmdLayers(args: string[]): number {
|
|
@@ -298,6 +344,9 @@ function cmdSheet(args: string[]): number {
|
|
|
298
344
|
const tiles: Tile[] = [{ name: 'source', image: src, caption: basename(source) }];
|
|
299
345
|
for (const path of layers) tiles.push(...tilesFrom(path));
|
|
300
346
|
const sheet = buildSheet(tiles, cols, cell);
|
|
347
|
+
// The README's loop writes sheets/layers.png into a folder nothing made yet;
|
|
348
|
+
// every other command creates its --out, so this one does too.
|
|
349
|
+
mkdirSync(dirname(out), { recursive: true });
|
|
301
350
|
writePng(out, sheet);
|
|
302
351
|
console.log(`spine-parts sheet: ${out}`);
|
|
303
352
|
console.log(` ${tiles.length} tile(s) in ${cols} column(s) of ${cell} px, sheet ${sheet.width}x${sheet.height}`);
|
|
@@ -396,7 +445,8 @@ function printLint(P: PartSet, spec: { bones: CharacterConfig['bones']; meshes:
|
|
|
396
445
|
const res = lint(P, spec);
|
|
397
446
|
for (const f of res.findings) console.log(lintLine(f));
|
|
398
447
|
for (const m of res.unknownMeshes) console.log(`note: mesh ${JSON.stringify(m)} names no part in parts.json, so it was not linted`);
|
|
399
|
-
console.log(
|
|
448
|
+
for (const b of res.missingTorsoBones) console.log(`note: no single bone named ${JSON.stringify(b)}, so the hip was not linted against the chest${b === 'hip' ? ' or the figure height' : ''}`);
|
|
449
|
+
console.log(`${res.findings.length} LINT line(s) over ${Object.keys(spec.meshes).length - res.unknownMeshes.length} mesh(es) and the hip`);
|
|
400
450
|
return res.findings.length;
|
|
401
451
|
}
|
|
402
452
|
|
|
@@ -541,7 +591,7 @@ function cmdAssemble(args: string[]): number {
|
|
|
541
591
|
const out = flags.get('--out') as string;
|
|
542
592
|
assembleStage(
|
|
543
593
|
{ source, full, head, config, seam: seam as SeamRule, project: project as ProjectRule },
|
|
544
|
-
{ partsJson: join(out, 'rig', 'parts.json'), partsDir: join(out, 'rig', 'parts'), recomposite: join(out, 'render', 'recomposite_rig.png') },
|
|
594
|
+
{ partsJson: join(out, 'rig', 'parts.json'), partsDir: join(out, 'rig', 'parts'), recomposite: join(out, 'render', 'recomposite_rig.png'), errorMap: join(out, 'render', ERROR_MAP_FILE) },
|
|
545
595
|
console.log,
|
|
546
596
|
);
|
|
547
597
|
return EXIT_OK;
|