rig-parts 1.79.0 → 1.79.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 +8 -1
- package/package.json +1 -1
- package/src/assemble.ts +5 -0
- package/src/build.ts +4 -1
- package/src/depthsheets.ts +9 -3
- package/src/neckdraft.ts +1 -0
package/docs/AUTHORING.md
CHANGED
|
@@ -5772,7 +5772,14 @@ Assembly uses the actual run registration, source-copy/composite coordinates,
|
|
|
5772
5772
|
merge ownership and final crop. Scalar bilinear sampling at pixel centres is
|
|
5773
5773
|
quantized half up at each intermediate copy/composite grid, then again at
|
|
5774
5774
|
final part registration. PAD4 repeats the resulting u8 edge values. There is no
|
|
5775
|
-
universal half-level total-error bound across those operators.
|
|
5775
|
+
universal half-level total-error bound across those operators. The drawing warp rounds its inverse coordinates to
|
|
5776
|
+
1/32 px and uses bilinear or bicubic support; scalar sampling keeps its existing
|
|
5777
|
+
unrounded inverse map plus the half-pixel centre. Visible final-grid samples
|
|
5778
|
+
less than one source pixel outside a registered crop retain the edge texel.
|
|
5779
|
+
At one pixel or more outside on either axis, `DEPTH_PART_SUPPORT` still refuses.
|
|
5780
|
+
Assembly logs and neck-draft notes report the clamped visible sample count per
|
|
5781
|
+
part, counting a corner once. Transparent cells are excluded from that count.
|
|
5782
|
+
No source sheet is padded or inferred. No art-color
|
|
5776
5783
|
premultiplication or local min/max rescaling is applied. The rig sheet receives the drawing's PAD4 with
|
|
5777
5784
|
explicit rectangular edge clamping. Original source hashes, rectangles, medians
|
|
5778
5785
|
and declared units accompany the optional `parts.json` part `depth` reference.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rig-parts",
|
|
3
|
-
"version": "1.79.
|
|
3
|
+
"version": "1.79.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
|
@@ -4445,6 +4445,8 @@ export interface AssembleResult {
|
|
|
4445
4445
|
seamRule: SeamRule;
|
|
4446
4446
|
projectRule: ProjectRule;
|
|
4447
4447
|
depthReadings?: Array<{ from: string; reading: import('./depthsheets.ts').DepthReading }>;
|
|
4448
|
+
/** Visible registered samples clamped within <1 source pixel of a crop. */
|
|
4449
|
+
depthClamps?: Array<{ part: string; samples: number }>;
|
|
4448
4450
|
/** Issue #492: head-canvas pixels filled by `config.assemble.head_face_underfill: true` when > 0. */
|
|
4449
4451
|
headFaceUnderfillPx?: number;
|
|
4450
4452
|
/** Issue #587: what each `exclude` entry took, in entry order; empty without one. */
|
|
@@ -5397,6 +5399,7 @@ export function assemble(input: AssembleInput): AssembleResult {
|
|
|
5397
5399
|
});
|
|
5398
5400
|
|
|
5399
5401
|
const planned: Array<PlacedPart | null> = plan.map(() => null);
|
|
5402
|
+
const depthClamps: NonNullable<AssembleResult['depthClamps']> = [];
|
|
5400
5403
|
const cutPlaced: Array<PlacedPart | null> = cuts.map(() => null);
|
|
5401
5404
|
// A cut's piece records the plan part it was cut from (issue #230): parts.json is in draw order, so a piece drawn
|
|
5402
5405
|
// "back" comes before its base, and nothing else in the file says which of the two is the piece.
|
|
@@ -5472,6 +5475,7 @@ export function assemble(input: AssembleInput): AssembleResult {
|
|
|
5472
5475
|
...((limb?.cap?.image_px ?? 0) > 0 || (limb?.cap?.lower_px ?? 0) > 0 ? { unsupported: `cap copies ${(limb?.cap?.image_px ?? 0) + (limb?.cap?.lower_px ?? 0)} pixels from a separate cap image whose scalar grid is undeclared` } : {}),
|
|
5473
5476
|
});
|
|
5474
5477
|
if (scalar !== undefined) record.depth = scalar.reference;
|
|
5478
|
+
if (scalar !== undefined && scalar.clampedSamples > 0) depthClamps.push({ part: name, samples: scalar.clampedSamples });
|
|
5475
5479
|
set({ record, image, ...(scalar === undefined ? {} : { depthImage: scalar.image }) });
|
|
5476
5480
|
});
|
|
5477
5481
|
refuseIfAny(empty);
|
|
@@ -5528,6 +5532,7 @@ export function assemble(input: AssembleInput): AssembleResult {
|
|
|
5528
5532
|
},
|
|
5529
5533
|
images: placed,
|
|
5530
5534
|
...(registeredDepth.size === 0 ? {} : { depthReadings: ([['full', depthFullInput], ...(single ? [] : [['head', depthHeadInput]])] as Array<['full' | 'head', LayerSet]>).flatMap(([run, set]) => set.layers.flatMap(layer => layer.depthSheet === undefined ? [] : [{ from: `${run}:${layer.name}`, reading: layer.depthSheet.reading }])) }),
|
|
5535
|
+
...(depthClamps.length === 0 ? {} : { depthClamps }),
|
|
5531
5536
|
recomposite: can,
|
|
5532
5537
|
errorMap: recompositeErrorMap(can, srcr, placed, inside),
|
|
5533
5538
|
figure: inside === null ? null : figureImage(inside, W, H),
|
package/src/build.ts
CHANGED
|
@@ -384,7 +384,7 @@ export function assembleStage(input: AssembleStageInput, outs: AssembleOutputs,
|
|
|
384
384
|
}
|
|
385
385
|
|
|
386
386
|
const altProblems: Problem[] = [];
|
|
387
|
-
const altBuilt: Array<{ record: AlternatePartsRecord; images: Array<{ name: string; image: Raster }>; excluded: ExcludedCount[]; islands: IslandCount[]; splits: SplitCount[]; capSplits: SplitCount[] }> = [];
|
|
387
|
+
const altBuilt: Array<{ record: AlternatePartsRecord; images: Array<{ name: string; image: Raster }>; excluded: ExcludedCount[]; islands: IslandCount[]; splits: SplitCount[]; capSplits: SplitCount[]; depthClamps: AssembleResult['depthClamps'] }> = [];
|
|
388
388
|
const baseSlots = result.parts.parts.map((p) => p.name);
|
|
389
389
|
// Issue #664: each limb segment part with a declared front slot -> the part its front slot follows.
|
|
390
390
|
const frontAfter = new Map<string, string>();
|
|
@@ -724,6 +724,7 @@ export function assembleStage(input: AssembleStageInput, outs: AssembleOutputs,
|
|
|
724
724
|
islands: altRes.islands,
|
|
725
725
|
splits: altRes.splits,
|
|
726
726
|
capSplits: altRes.capSplits,
|
|
727
|
+
depthClamps: altRes.depthClamps,
|
|
727
728
|
});
|
|
728
729
|
}
|
|
729
730
|
const emittedAssets = [
|
|
@@ -762,6 +763,8 @@ export function assembleStage(input: AssembleStageInput, outs: AssembleOutputs,
|
|
|
762
763
|
if (depthImage !== undefined && record.depth !== undefined) writePng(join(outs.partsDir, record.depth.image), depthImage);
|
|
763
764
|
}
|
|
764
765
|
for (const row of result.depthReadings ?? []) log(` depth reading ${row.from}: ${row.reading.relation}; ordering median ${row.reading.orderingMedian}, source-sheet median ${row.reading.sheetMedian} (half-level quantization; reading only, polarity declared)`);
|
|
766
|
+
for (const row of result.depthClamps ?? []) log(` depth edge clamp ${row.part}: ${row.samples} visible samples less than 1 source pixel outside the crop; edge texels retained`);
|
|
767
|
+
for (const b of altBuilt) for (const row of b.depthClamps ?? []) log(` set "${b.record.name}" depth edge clamp ${row.part}: ${row.samples} visible samples less than 1 source pixel outside the crop; edge texels retained`);
|
|
765
768
|
for (const b of altBuilt) {
|
|
766
769
|
for (const { name, image } of b.images) writePng(join(outs.partsDir, `${name}.png`), image);
|
|
767
770
|
}
|
package/src/depthsheets.ts
CHANGED
|
@@ -207,6 +207,10 @@ export interface DepthTransport {
|
|
|
207
207
|
}
|
|
208
208
|
/** Resample on the FINAL unpadded part grid. Visible synthesized/unsupported
|
|
209
209
|
* material refuses; transparent cells and later PAD4 use explicit edge clamp.
|
|
210
|
+
* Registered art filtering can reach less than one source pixel beyond its
|
|
211
|
+
* crop. Keep that edge texel; a visible sample >=1px outside still refuses.
|
|
212
|
+
* clampedSamples counts visible samples outside the half-open crop, once per
|
|
213
|
+
* output pixel even at a corner. It is a reading, not emitted provenance.
|
|
210
214
|
* No geometry, art pixels, source field or calibration are changed here.
|
|
211
215
|
*/
|
|
212
216
|
export function transportPartDepth(
|
|
@@ -214,13 +218,14 @@ export function transportPartDepth(
|
|
|
214
218
|
art: Raster,
|
|
215
219
|
registered: ReadonlyMap<string, RegisteredDepth>,
|
|
216
220
|
transport: DepthTransport,
|
|
217
|
-
): { reference: PartDepthReference; image: Raster } | undefined {
|
|
221
|
+
): { reference: PartDepthReference; image: Raster; clampedSamples: number } | undefined {
|
|
218
222
|
const keys = [part.from, ...transport.extend];
|
|
219
223
|
const initial = keys.map(k => registered.get(k)).find((s): s is RegisteredDepth => s !== undefined);
|
|
220
224
|
if (initial === undefined) return undefined;
|
|
221
225
|
const fail = (detail: string): never => { throw new PartsError([{ code: 'DEPTH_PART_SUPPORT', object: `part "${part.name}"`, detail }]); };
|
|
222
226
|
if (transport.unsupported !== undefined) fail(`${transport.unsupported}; an exact source-bound scalar transport is required`);
|
|
223
227
|
const image = newRaster(part.w, part.h), used = new Map<string, RegisteredDepth>();
|
|
228
|
+
let clampedSamples = 0;
|
|
224
229
|
for (let y = 0; y < part.h; y++) for (let x = 0; x < part.w; x++) {
|
|
225
230
|
const rx = part.x + x, ry = part.y + y, pos = ry * transport.width + rx;
|
|
226
231
|
const visible = art.data[(y * part.w + x) * 4 + 3] > 0;
|
|
@@ -233,12 +238,13 @@ export function transportPartDepth(
|
|
|
233
238
|
const sourceKey = source === undefined ? keys.find(k => registered.get(k) === initial) as string : key;
|
|
234
239
|
const run = sourceKey.startsWith('head:') ? 'head' : 'full';
|
|
235
240
|
const m = transport.map(run), px = (rx - m.tx) / m.sx - input.layer.left + 0.5, py = (ry - m.ty) / m.sx - input.layer.top + 0.5;
|
|
236
|
-
if (visible && (px
|
|
241
|
+
if (visible && (px <= -1 || py <= -1 || px >= input.sheet.pixels.width + 1 || py >= input.sheet.pixels.height + 1)) fail(`rig pixel (${rx},${ry}) maps to source (${px},${py}) outside ${input.sheet.pixels.width}x${input.sheet.pixels.height}; source art filtering/generated material has no declared depth there`);
|
|
242
|
+
if (visible && (px < 0 || py < 0 || px >= input.sheet.pixels.width || py >= input.sheet.pixels.height)) clampedSamples++;
|
|
237
243
|
const level = sampleDepthLevel(input.sheet.pixels, px, py);
|
|
238
244
|
image.data.set([level, level, level, 255], (y * part.w + x) * 4);
|
|
239
245
|
if (visible && source !== undefined) used.set(key, source);
|
|
240
246
|
}
|
|
241
|
-
return { image, reference: {
|
|
247
|
+
return { image, clampedSamples, reference: {
|
|
242
248
|
image: `${part.name}_depth.png`, near: initial.near, zScale: initial.zScale,
|
|
243
249
|
sources: [...new Map([...used].flatMap(([from, s]) => s.sheet.sources ?? [{ from, image: s.sheet.file, sha256: s.sheet.sha256, crop: [s.layer.left, s.layer.top, s.layer.right, s.layer.bottom] as [number, number, number, number], reading: s.sheet.reading }]).map(s => [`${s.from}:${s.sha256}`, s])).values()],
|
|
244
250
|
} };
|
package/src/neckdraft.ts
CHANGED
|
@@ -51,6 +51,7 @@ export function draftIndependentNeck(input: { config: CharacterConfig; source: R
|
|
|
51
51
|
body.roles[part] = 'unit';
|
|
52
52
|
// Stage fields now see the new role before any color/fringe ownership operation.
|
|
53
53
|
const assembled = assemble({ source: input.source, full: input.full, head: input.head, ...stageFields(cfg), under: input.under, caps: input.caps, seamRule: 'near-white', projectRule: 'core', depth: cfg.depth, depthBase: input.base });
|
|
54
|
+
for (const row of assembled.depthClamps ?? []) notes.push(`depth edge clamp ${row.part}: ${row.samples} visible samples less than 1 source pixel outside the crop; edge texels retained`);
|
|
54
55
|
const rec = assembled.parts.parts.find(p => p.name === part)!;
|
|
55
56
|
const image = assembled.images.find(p => p.record.name === part)!.image;
|
|
56
57
|
const own = assembled.images.find(p => p.record.name === part)!;
|