rig-parts 1.83.0 → 1.83.1
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/docs/AUTHORING.md +14 -3
- package/package.json +1 -1
- package/src/assemble.ts +17 -5
- package/src/takelayers.ts +19 -2
package/docs/AUTHORING.md
CHANGED
|
@@ -668,8 +668,19 @@ Taken layers replace the target's tag, keeping the donor's tag and
|
|
|
668
668
|
`depth_median`, so existing hair/blink classification continues to read `from`.
|
|
669
669
|
Larger depth draws behind smaller depth. Numeric ties keep manifest order only
|
|
670
670
|
inside one original run. Equal or null depths **across** runs are ambiguous
|
|
671
|
-
and refused, even if the plan gives an order.
|
|
672
|
-
|
|
671
|
+
and refused, even if the plan gives an order. Plan-order checks compare only
|
|
672
|
+
different original runs' layers whose original supports intersect (both alpha
|
|
673
|
+
> 8, in the target-run canvas, without dilation). Disjoint supports have no
|
|
674
|
+
visible order; intersecting bounding boxes alone do not establish overlap.
|
|
675
|
+
Within one role band, larger depth must draw behind smaller depth. In a two-run
|
|
676
|
+
plan, full-run rows follow the proposer's head-fallback roles before scalar
|
|
677
|
+
depth: back hair, body, headwear/earwear, face, head details, front hair. Thus
|
|
678
|
+
fallback headwear is after the body and before the face, and fallback front
|
|
679
|
+
hair stays over the face even when its donor depth is larger. Roles come from
|
|
680
|
+
source tags, never part names. Head-run rows and single-run plans keep scalar
|
|
681
|
+
depth order. A supplied plan reversing an overlapping mixed-run pair under
|
|
682
|
+
these rules is refused; `--propose-plan` drafts the same order. Null/tied depth
|
|
683
|
+
ambiguity remains a refusal even for disjoint supports.
|
|
673
684
|
Replacing a layer with hidden companion art is refused because independent
|
|
674
685
|
hidden-art identity has not been declared. Active scalar depth sheets (`depth`
|
|
675
686
|
opt-in or already bound sheets) are also refused: selecting donor art does not
|
|
@@ -4451,7 +4462,7 @@ those settings. Changing a config seed alone cannot change a no-config run.
|
|
|
4451
4462
|
| <a id="ASSEMBLE_TAKE_TAG_IN_RUN"></a>`ASSEMBLE_TAKE_TAG_IN_RUN` | selected donor does not hold the tag | choose a tag present in that donor |
|
|
4452
4463
|
| <a id="ASSEMBLE_TAKE_SUPPORT"></a>`ASSEMBLE_TAKE_SUPPORT` | selected layer has zero alpha > 8 pixels | choose a nonempty donor layer |
|
|
4453
4464
|
| <a id="ASSEMBLE_TAKE_DEPTH_AMBIGUOUS"></a>`ASSEMBLE_TAKE_DEPTH_AMBIGUOUS` | different original runs have equal or null depths | use a run with distinct numeric depths; do not invent an inter-run tie order |
|
|
4454
|
-
| <a id="ASSEMBLE_TAKE_PLAN_ORDER"></a>`ASSEMBLE_TAKE_PLAN_ORDER` | supplied plan reverses mixed-run depth
|
|
4465
|
+
| <a id="ASSEMBLE_TAKE_PLAN_ORDER"></a>`ASSEMBLE_TAKE_PLAN_ORDER` | supplied plan reverses an overlapping mixed-run pair: two-run full-layer head roles first, scalar depth within a role band (head-run and single-run rows use scalar depth); names the parts and support intersection | use the order drafted by `assemble --propose-plan`; fallback headwear follows the body and precedes the face, front hair follows the face; disjoint supports impose no plan-order constraint |
|
|
4455
4466
|
| <a id="ASSEMBLE_TAKE_GEOMETRY"></a>`ASSEMBLE_TAKE_GEOMETRY` | missing target head, unequal run canvases or unequal original input dimensions | supply matching geometry; the check does not resize inputs |
|
|
4456
4467
|
| <a id="ASSEMBLE_TAKE_META"></a>`ASSEMBLE_TAKE_META` | donor `meta.json` lacks a readable prompt ID or seed | retain the original run metadata |
|
|
4457
4468
|
| <a id="ASSEMBLE_TAKE_INPUT"></a>`ASSEMBLE_TAKE_INPUT` | declared donor input PNG cannot be read | name its original PNG relative to the config |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rig-parts",
|
|
3
|
-
"version": "1.83.
|
|
3
|
+
"version": "1.83.1",
|
|
4
4
|
"description": "AI-authored 2D character parts from one anime painting and its See-through layer decomposition: measured parts, weighted-mesh rig specs, verified through rig-c before anything is written. A CLI for agents that cannot see the image.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/assemble.ts
CHANGED
|
@@ -6048,6 +6048,18 @@ export const PROPOSE_HEAD_TAGS: readonly string[] = [
|
|
|
6048
6048
|
'eyebrow-l',
|
|
6049
6049
|
];
|
|
6050
6050
|
|
|
6051
|
+
/** #869: two-run full rows are body rows or head fallbacks, regardless of part spelling.
|
|
6052
|
+
* Within one band the run's depth order survives; the bands encode the proposer's roles.
|
|
6053
|
+
*/
|
|
6054
|
+
export function fullPlanBand(tag: string): number {
|
|
6055
|
+
if (tag === 'back hair') return 0;
|
|
6056
|
+
if (!PROPOSE_HEAD_TAGS.includes(tag)) return 1;
|
|
6057
|
+
if (tag === 'headwear' || tag === 'earwear') return 2;
|
|
6058
|
+
if (tag === 'face') return 3;
|
|
6059
|
+
if (tag === 'front hair') return 5;
|
|
6060
|
+
return 4;
|
|
6061
|
+
}
|
|
6062
|
+
|
|
6051
6063
|
/** #863: prefer suitable head layers; only unusable head candidates may fall back for these tags. */
|
|
6052
6064
|
const HEAD_FALLBACK_TAGS: readonly string[] = ['back hair', 'face', 'front hair'];
|
|
6053
6065
|
|
|
@@ -6106,7 +6118,7 @@ function fmt0(v: number): string {
|
|
|
6106
6118
|
* A suitable head candidate is retained. Every fallback names the two rig
|
|
6107
6119
|
* areas and the reason. A `headwear`/`earwear` the head run drops or
|
|
6108
6120
|
* shrinks (its rig-pixel area below 0.8 of the full run's) is taken from the
|
|
6109
|
-
* full run instead, drawn
|
|
6121
|
+
* full run instead, drawn after the body and before the face — noted. `front hair` and
|
|
6110
6122
|
* `back hair` taken from the head run are extended below the crop from the
|
|
6111
6123
|
* full run when the full run's layer reaches more than 8 rig pixels below the
|
|
6112
6124
|
* crop line. A lone `handwear` layer is named `sleeves`.
|
|
@@ -6232,7 +6244,7 @@ export function proposePlan(full0: LayerSet, head0: LayerSet, g: Geometry, minPx
|
|
|
6232
6244
|
return tag !== 'nose' && !(run === 'head' && tag === 'face' && headFaceRefusal !== null) &&
|
|
6233
6245
|
l !== undefined && l.opaqueRunPx >= floor && !implausible(run, tag, 'not proposed');
|
|
6234
6246
|
};
|
|
6235
|
-
const body = runs.full.filter((l) =>
|
|
6247
|
+
const body = runs.full.filter((l) => fullPlanBand(l.tag) === 1 && ok('full', l.tag)).map((l) => l.tag);
|
|
6236
6248
|
// Issue #256: an author-made layer is proposed from the run that holds it, at its place in that run's draw order,
|
|
6237
6249
|
// like any layer; one both runs hold is proposed once, from the full run when the full run's is proposed, and the note says so.
|
|
6238
6250
|
const authorBoth = runs.head.filter((l) => readAuthorTag(l.tag) !== null && body.includes(l.tag)).map((l) => l.tag);
|
|
@@ -6279,15 +6291,15 @@ export function proposePlan(full0: LayerSet, head0: LayerSet, g: Geometry, minPx
|
|
|
6279
6291
|
const draw = t === 'back hair' ? 'behind the body and face' : t === 'face' ? 'before head details and front hair' : 'in front of the face';
|
|
6280
6292
|
notes.push(`${t}: head run ${fmt0(hp)} rig px (${reason}); full run ${fmt0(fp)} rig px -> full-run layer, drawn ${draw}`);
|
|
6281
6293
|
}
|
|
6282
|
-
|
|
6294
|
+
// Keep the full run's order inside the accessory band, including mixed donors.
|
|
6295
|
+
for (const { tag: t } of runs.full.filter(l => fullPlanBand(l.tag) === 2)) {
|
|
6283
6296
|
const hl = find('head', t);
|
|
6284
6297
|
const fl = find('full', t);
|
|
6285
6298
|
const hp = hl !== undefined ? hl.opaqueRunPx * headArea : 0;
|
|
6286
6299
|
const fp = fl !== undefined ? fl.opaqueRunPx * fullArea : 0;
|
|
6287
6300
|
if (ok('full', t) && hp < 0.8 * fp) {
|
|
6288
6301
|
drop(t);
|
|
6289
|
-
|
|
6290
|
-
plan.splice(at, 0, [PROPOSE_ALIAS[t], 'full', t]);
|
|
6302
|
+
plan.push([PROPOSE_ALIAS[t], 'full', t]);
|
|
6291
6303
|
notes.push(`${t}: head run ${fmt0(hp)} rig px < 0.8 x full run ${fmt0(fp)} -> full-run layer, drawn behind the face`);
|
|
6292
6304
|
}
|
|
6293
6305
|
}
|
package/src/takelayers.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { readFileSync } from 'node:fs';
|
|
3
3
|
import { dirname, resolve } from 'node:path';
|
|
4
4
|
import type { EarlyConfig, PlanEntry, Run } from './config.ts';
|
|
5
|
-
import { CORE_KERNEL } from './assemble.ts';
|
|
5
|
+
import { CORE_KERNEL, fullPlanBand } from './assemble.ts';
|
|
6
6
|
import { makeInputs } from './inputs.ts';
|
|
7
7
|
import { OPAQUE_ALPHA_ABOVE, readLayers, type Layer, type LayerSet } from './layers.ts';
|
|
8
8
|
import { PartsError, refuseIfAny, type Problem } from './errors.ts';
|
|
@@ -51,6 +51,16 @@ function identity(layer: Layer, canvas: LayerSet['canvas'], donor: Raster, targe
|
|
|
51
51
|
export function takeLayerNote(source: TakenLayerSource): string {
|
|
52
52
|
return `take ${source.run}:${source.tag} from ${source.run_dir}; prompt_id ${source.prompt_id}; seed ${source.seed}; st_input ${source.st_input}; support ${source.support_px} run px; checked ${source.checked_px} input px; differing ${source.differing_px}; alpha > ${OPAQUE_ALPHA_ABOVE}, ${CORE_KERNEL}x${CORE_KERNEL} margin`;
|
|
53
53
|
}
|
|
54
|
+
/** Intersection of original alpha supports in the common target-run canvas; no bbox or dilation proxy. */
|
|
55
|
+
function supportOverlap(a: Layer, b: Layer): number {
|
|
56
|
+
let px = 0;
|
|
57
|
+
for (let y = Math.max(a.top, b.top); y < Math.min(a.bottom, b.bottom); y++)
|
|
58
|
+
for (let x = Math.max(a.left, b.left); x < Math.min(a.right, b.right); x++) {
|
|
59
|
+
if (a.pixels.data[((y - a.top) * a.pixels.width + x - a.left) * 4 + 3] > OPAQUE_ALPHA_ABOVE &&
|
|
60
|
+
b.pixels.data[((y - b.top) * b.pixels.width + x - b.left) * 4 + 3] > OPAQUE_ALPHA_ABOVE) px++;
|
|
61
|
+
}
|
|
62
|
+
return px;
|
|
63
|
+
}
|
|
54
64
|
/** Apply every declaration together, so replacing all tied layers from one run can retain its known order. */
|
|
55
65
|
export function applyTakenLayers(runs: { full: LayerSet; head: LayerSet; single: boolean }, painting: Raster, cfg: Pick<EarlyConfig, 'seethrough' | 'depth'> & { assemble: { take?: TakeLayers[]; plan?: PlanEntry[] } }, configPath: string): { full: LayerSet; head: LayerSet; single: boolean; notes: string[] } {
|
|
56
66
|
const declarations = cfg.assemble.take ?? [];
|
|
@@ -99,7 +109,14 @@ export function applyTakenLayers(runs: { full: LayerSet; head: LayerSet; single:
|
|
|
99
109
|
const selected = (cfg.assemble.plan ?? []).filter(p => p[1] === run).map(p => ({ part: p[0], layer: list.find(l => l.name === p[2]) }));
|
|
100
110
|
for (let i = 0; i < selected.length; i++) for (let j = i + 1; j < selected.length; j++) {
|
|
101
111
|
const a = selected[i], b = selected[j];
|
|
102
|
-
if (a.layer
|
|
112
|
+
if (a.layer === undefined || b.layer === undefined || origin(a.layer) === origin(b.layer)) continue;
|
|
113
|
+
const overlap = supportOverlap(a.layer, b.layer);
|
|
114
|
+
if (overlap === 0) continue;
|
|
115
|
+
// Only full rows in a two-run plan have the proposer's fallback roles.
|
|
116
|
+
// Head rows and one-run plans retain the scalar order, as do peers in a band.
|
|
117
|
+
const band = !runs.single && run === 'full' ? fullPlanBand(a.layer.name) - fullPlanBand(b.layer.name) : 0;
|
|
118
|
+
const reversed = band === 0 ? list.indexOf(a.layer) > list.indexOf(b.layer) : band > 0;
|
|
119
|
+
if (reversed) problems.push({ code: 'ASSEMBLE_TAKE_PLAN_ORDER', object: `${run} plan parts "${a.part}" / "${b.part}"`, detail: `plan order reverses ${band === 0 ? `mixed-run depths ${a.layer.depth} / ${b.layer.depth}; put the larger depth behind` : `two-run full-layer roles "${a.layer.name}" / "${b.layer.name}"; order back hair, body, headwear/earwear, face, head details, front hair`}; supports overlap at ${overlap} run px (alpha > ${OPAQUE_ALPHA_ABOVE}); use assemble --propose-plan` });
|
|
103
120
|
}
|
|
104
121
|
refuseIfAny(problems);
|
|
105
122
|
const replaced = new Set(declarations.filter(d => d.run === run).flatMap(d => d.tags));
|