spine-parts 0.8.2 → 0.9.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 +3 -1
- package/docs/AUTHORING.md +13 -4
- package/package.json +1 -1
- package/src/comfy/painting.ts +6 -4
- package/src/config.ts +38 -12
- package/src/graphs.ts +11 -1
package/README.md
CHANGED
|
@@ -77,7 +77,9 @@ built from its own proposal with no hand edit at all —
|
|
|
77
77
|
- **A character config** (`config.json`) — the part plan, the head box, the bones,
|
|
78
78
|
which bones may pull which layer, and the idle's sines. `src/config.ts` is its
|
|
79
79
|
schema and documents every field; the loader refuses an unknown or a missing field
|
|
80
|
-
by name
|
|
80
|
+
by name, and reads nothing under `note`, a `<name>_note` (a string) or a key
|
|
81
|
+
beginning `x-` (any JSON — a project's own records, such as a past build's gate
|
|
82
|
+
results). [docs/AUTHORING.md](docs/AUTHORING.md) says where each value comes from.
|
|
81
83
|
|
|
82
84
|
## What it gives out
|
|
83
85
|
|
package/docs/AUTHORING.md
CHANGED
|
@@ -47,9 +47,18 @@ examples use, and the one this guide assumes:
|
|
|
47
47
|
|
|
48
48
|
`src/config.ts` is the schema and the loader. Every key is known or refused
|
|
49
49
|
(`CONFIG_KEY_KNOWN`); a required key that is missing is `CONFIG_FIELD_PRESENT`.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
reason there, beside the value it corrected
|
|
50
|
+
There are two open doors, and nothing reads what stands behind either. An
|
|
51
|
+
annotation: `note`, and any key ending in `_note`, may hold a string anywhere an
|
|
52
|
+
object is — write a hand correction's reason there, beside the value it corrected
|
|
53
|
+
(the examples do). A record: any key beginning `x-` with a name after it may hold
|
|
54
|
+
any JSON value in the same places — a project's own provenance, such as the gate
|
|
55
|
+
results of a past build (`"x-status": {…}`) or the seeds a search rejected, with
|
|
56
|
+
reasons (`generation.x-seeds_tried`). The record test comes first, so
|
|
57
|
+
`x-seed_note` is a record. `xstatus`, `x_status`, `X-status` and a bare `x-` are
|
|
58
|
+
not record names and are refused like any unknown key. The doors are not open in
|
|
59
|
+
`meshes`, `regions` or `motion.blink.still`: their keys are part names, and an
|
|
60
|
+
`x-` key there is a part like any other. Every refusal of an unknown key, of a
|
|
61
|
+
retired one and of an annotation that is not a string says where a record goes.
|
|
53
62
|
|
|
54
63
|
**Coordinates** in `bones`, `meshes` and `motion` are **rig pixels, y down, origin
|
|
55
64
|
top-left** — the parts' own space, which is the painting times `assemble.rig_scale`.
|
|
@@ -570,7 +579,7 @@ See-through with another seed.
|
|
|
570
579
|
| rule | means | change |
|
|
571
580
|
| --- | --- | --- |
|
|
572
581
|
| `CONFIG_FILE_PRESENT`, `CONFIG_IS_JSON` | no such file, or not JSON | `--config` |
|
|
573
|
-
| `CONFIG_KEY_KNOWN`, `CONFIG_KEY_RETIRED`, `CONFIG_FIELD_PRESENT`, `CONFIG_FIELD_TYPE` | an unknown key (a retired one says what replaces it), a missing one, a wrong type | the named field |
|
|
582
|
+
| `CONFIG_KEY_KNOWN`, `CONFIG_KEY_RETIRED`, `CONFIG_FIELD_PRESENT`, `CONFIG_FIELD_TYPE` | an unknown key (a retired one says what replaces it), a missing one, a wrong type; a project's own record under a plain key, or an annotation (`note`, `<name>_note`) holding something other than a string | the named field; a record goes under a key beginning `x-` (any JSON, read by nothing), a remark under `note` or `<name>_note` (a string) — §3 |
|
|
574
583
|
| `CONFIG_HEAD_BOX_SQUARE` | `seethrough.head_box` is not a non-empty square | `head_box` — take `propose --head-box`'s |
|
|
575
584
|
| `CONFIG_TAG_KNOWN`, `CONFIG_PART_NAME`, `CONFIG_PART_UNIQUE` | a plan entry names no v3 tag, a part name that is not a file name, or a part or layer twice — a patch named like a plan part or another patch included | `assemble.plan`, `assemble.patches` |
|
|
576
585
|
| `CONFIG_NAME_RESOLVES` | a parent, segment, region, track, blink member, blink still part or bone, extend part or patch `draw.before` names nothing declared above it | the named reference, or the declaration it needs |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spine-parts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "AI-authored Spine 2D character parts from one anime painting and its See-through layer decomposition: measured parts, weighted-mesh rig specs, verified through spine-rigc before anything is written. A CLI for agents that cannot see the image.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/comfy/painting.ts
CHANGED
|
@@ -19,14 +19,16 @@
|
|
|
19
19
|
*
|
|
20
20
|
* The meta file holds the positive and negative prompts verbatim, what was
|
|
21
21
|
* dropped as `DUP_WORDS`, where the pose words came from, the checkpoint, the
|
|
22
|
-
* LoRAs and
|
|
23
|
-
*
|
|
22
|
+
* LoRAs, control and sampler as the graph used them (defaults resolved, each
|
|
23
|
+
* by its named fields, so an annotation or an `x-` record its author wrote in
|
|
24
|
+
* the config never reaches the meta), the elapsed seconds and the prompt id —
|
|
25
|
+
* never the host.
|
|
24
26
|
*/
|
|
25
27
|
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
26
28
|
import { join } from 'node:path';
|
|
27
29
|
import type { PaintConfig } from '../config.ts';
|
|
28
30
|
import { type Problem, refuseIfAny } from '../errors.ts';
|
|
29
|
-
import { buildPrompts, checkGraph, paintingGraph, resolvedControl, resolvedLoras } from '../graphs.ts';
|
|
31
|
+
import { buildPrompts, checkGraph, paintingGraph, resolvedControl, resolvedLoras, resolvedSampler } from '../graphs.ts';
|
|
30
32
|
import { decodePngBytes, encodePngBytes } from '../raster/png.ts';
|
|
31
33
|
import { renderSkeleton } from '../skeleton.ts';
|
|
32
34
|
import { ComfyClient, historyImages, refuse } from './client.ts';
|
|
@@ -103,7 +105,7 @@ export async function runPainting(client: ComfyClient, run: PaintRun, say: (line
|
|
|
103
105
|
key: run.config.key,
|
|
104
106
|
checkpoint: g.checkpoint,
|
|
105
107
|
loras: resolvedLoras(g.loras),
|
|
106
|
-
sampler: g.sampler,
|
|
108
|
+
sampler: resolvedSampler(g.sampler),
|
|
107
109
|
control,
|
|
108
110
|
pose_from: prompts.poseFrom,
|
|
109
111
|
dropped: prompts.dropped,
|
package/src/config.ts
CHANGED
|
@@ -12,10 +12,20 @@
|
|
|
12
12
|
*
|
|
13
13
|
* 🔒 **Every key is known or refused.** An unknown key is refused by name and
|
|
14
14
|
* so is a missing one — a config that half-loads is a rig nobody can reason
|
|
15
|
-
* about.
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
15
|
+
* about. There are two open doors, and both lead nowhere — nothing reads what
|
|
16
|
+
* stands behind them. Annotation: `note`, and any key ending in `_note`, may
|
|
17
|
+
* hold a string anywhere an object is expected. Annotations are how a hand
|
|
18
|
+
* correction records its reason beside the value it corrected, and refusing
|
|
19
|
+
* them would push that reason out of the file. Record: any key beginning with
|
|
20
|
+
* `x-` and at least one character after it may hold any JSON value in the same
|
|
21
|
+
* places (issue #70) — a project's own provenance, such as the gate results of
|
|
22
|
+
* a past build or the seeds a search rejected, kept beside the conditions that
|
|
23
|
+
* made the rig. The record test comes first, so `x-seed_note` is a record. The
|
|
24
|
+
* part-name maps (`meshes`, `regions`, `motion.blink.still`) are not objects
|
|
25
|
+
* in this sense: their keys are part names, and an `x-` key there is a part
|
|
26
|
+
* name like any other. No stage writes a vouched object whole into an output;
|
|
27
|
+
* each writer names its fields (`resolvedSampler` and its kin in
|
|
28
|
+
* `src/graphs.ts`), which is what keeps both doors read by nothing.
|
|
19
29
|
*
|
|
20
30
|
* ⛔ **The generation block is inline.** Configs written for the private
|
|
21
31
|
* reference pointed at a character file and an art-job id elsewhere
|
|
@@ -224,11 +234,23 @@ const RETIRED: Record<string, Record<string, string>> = {
|
|
|
224
234
|
art_job: 'named a job record outside this tree; put the costume words themselves in generation.costume and where they came from in generation.costume_note',
|
|
225
235
|
identity_override: 'overrode the character file\'s identity; with the values inline, write the identity you want in generation.identity',
|
|
226
236
|
lora_strength: 'overrode every LoRA\'s strength; with the values inline, set generation.loras[i].strength on each',
|
|
227
|
-
seed0: 'recorded where a seed search started; the chosen seed is generation.seed, and the search belongs in generation.seed_note',
|
|
228
|
-
seeds_tried: 'recorded the seeds a search tried; the chosen seed is generation.seed, and the search belongs in generation.seed_note',
|
|
237
|
+
seed0: 'recorded where a seed search started; the chosen seed is generation.seed, and the search belongs in generation.seed_note, or as a structured record in generation.x-seeds_tried',
|
|
238
|
+
seeds_tried: 'recorded the seeds a search tried; the chosen seed is generation.seed, and the search belongs in generation.seed_note, or as a structured record in generation.x-seeds_tried',
|
|
229
239
|
},
|
|
230
240
|
};
|
|
231
241
|
|
|
242
|
+
/**
|
|
243
|
+
* A project's own record: a key beginning `x-` with at least one character
|
|
244
|
+
* after it, holding any JSON value, read by nothing. `X-`, `x_` and a bare
|
|
245
|
+
* `x-` are not record names, so a typo of one is still refused by name.
|
|
246
|
+
*/
|
|
247
|
+
function isRecordKey(key: string): boolean {
|
|
248
|
+
return key.length > 2 && key.startsWith('x-');
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** What every refusal of a key the schema does not know adds after its own sentence: the two doors. */
|
|
252
|
+
const DOORS = 'a project\'s own record goes under a key beginning "x-" (any JSON, read by nothing), a remark under note or <name>_note (a string)';
|
|
253
|
+
|
|
232
254
|
// ---------------------------------------------------------------------------
|
|
233
255
|
// the checker
|
|
234
256
|
// ---------------------------------------------------------------------------
|
|
@@ -242,7 +264,7 @@ class Check {
|
|
|
242
264
|
this.problems.push({ code, object, detail });
|
|
243
265
|
}
|
|
244
266
|
|
|
245
|
-
/** An object with exactly these keys (plus annotations); returns it, or null after refusing. */
|
|
267
|
+
/** An object with exactly these keys (plus records and annotations); returns it, or null after refusing. */
|
|
246
268
|
object(path: string, v: Json, required: readonly string[], optional: readonly string[]): Record<string, Json> | null {
|
|
247
269
|
if (typeof v !== 'object' || v === null || Array.isArray(v)) {
|
|
248
270
|
this.fail('CONFIG_FIELD_TYPE', path, `is ${show(v)}; an object is required`);
|
|
@@ -252,13 +274,16 @@ class Check {
|
|
|
252
274
|
const section = path.replace(/^config\.?/, '');
|
|
253
275
|
for (const key of Object.keys(o)) {
|
|
254
276
|
if (required.includes(key) || optional.includes(key)) continue;
|
|
277
|
+
if (isRecordKey(key)) continue;
|
|
255
278
|
if (key === 'note' || key.endsWith('_note')) {
|
|
256
|
-
if (typeof o[key] !== 'string')
|
|
279
|
+
if (typeof o[key] !== 'string') {
|
|
280
|
+
this.fail('CONFIG_FIELD_TYPE', `${path}.${key}`, `is ${show(o[key])}; an annotation is a string — a structured record goes under a key beginning "x-" (any JSON, read by nothing)`);
|
|
281
|
+
}
|
|
257
282
|
continue;
|
|
258
283
|
}
|
|
259
284
|
const retired = RETIRED[section]?.[key];
|
|
260
285
|
if (retired !== undefined) this.fail('CONFIG_KEY_RETIRED', `${path}.${key}`, retired);
|
|
261
|
-
else this.fail('CONFIG_KEY_KNOWN', `${path}.${key}`, `is not a field here; known: ${[...required, ...optional].join(', ')}`);
|
|
286
|
+
else this.fail('CONFIG_KEY_KNOWN', `${path}.${key}`, `is not a field here; known: ${[...required, ...optional].join(', ')} — ${DOORS}`);
|
|
262
287
|
}
|
|
263
288
|
for (const key of required) if (!(key in o)) this.fail('CONFIG_FIELD_PRESENT', `${path}.${key}`, 'is absent and required');
|
|
264
289
|
return o;
|
|
@@ -421,9 +446,10 @@ const TOP_KEYS = ['key', 'generation', 'seethrough', 'assemble', 'bones', 'meshe
|
|
|
421
446
|
* vouched for (for `paint` that is everything but `key` and `generation`; for
|
|
422
447
|
* `layers`, `assemble.plan`, `extend_below_crop`, `patches`, `bones`, `meshes`,
|
|
423
448
|
* `regions` and `motion`; for `assemble`, the last four); a caller that needs
|
|
424
|
-
* them uses {@link parseConfig}. Every other key is
|
|
425
|
-
*
|
|
426
|
-
*
|
|
449
|
+
* them uses {@link parseConfig}. Every other key that is not a record or an
|
|
450
|
+
* annotation is still refused by name, and a retired one
|
|
451
|
+
* (`generation.character_file` and its kin) still with what replaces it: a
|
|
452
|
+
* config is not allowed to be half-known at any stage.
|
|
427
453
|
*/
|
|
428
454
|
export function parseEarlyConfig(raw: Json, door: 'paint'): PaintConfig;
|
|
429
455
|
export function parseEarlyConfig(raw: Json, door: 'layers'): EarlyConfig;
|
package/src/graphs.ts
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
* The two heads are the reference's verbatim, including the safety terms of
|
|
29
29
|
* the negative head (no nudity, no minors): they are a guard, not a cast.
|
|
30
30
|
*/
|
|
31
|
-
import type { Control, Generation, Lora } from './config.ts';
|
|
31
|
+
import type { Control, Generation, Lora, Sampler } from './config.ts';
|
|
32
32
|
import type { Problem } from './errors.ts';
|
|
33
33
|
import { SKELETONS } from './skeleton.ts';
|
|
34
34
|
|
|
@@ -106,6 +106,16 @@ export function resolvedControl(c: Control): Required<Control> {
|
|
|
106
106
|
return { skeleton: c.skeleton, strength: c.strength, end_percent: c.end_percent, model: c.model ?? CONTROL_MODEL };
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
+
/**
|
|
110
|
+
* The sampler as the graph uses it: its four fields by name, in this order.
|
|
111
|
+
* The config's own object also carries whatever annotations and `x-` records
|
|
112
|
+
* its author wrote, and the loader vouches for the four fields only, so a
|
|
113
|
+
* writer that copied the object whole would carry those into an output.
|
|
114
|
+
*/
|
|
115
|
+
export function resolvedSampler(s: Sampler): Sampler {
|
|
116
|
+
return { steps: s.steps, cfg: s.cfg, sampler: s.sampler, scheduler: s.scheduler };
|
|
117
|
+
}
|
|
118
|
+
|
|
109
119
|
/**
|
|
110
120
|
* The painting graph: checkpoint -> LoRAs in order -> the two text encoders ->
|
|
111
121
|
* (ControlNetApplyAdvanced over the uploaded skeleton) -> KSampler -> VAE
|