rig-parts 1.5.1 → 1.5.3
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 +4 -4
- package/cli.ts +63 -33
- package/docs/AUTHORING.md +23 -8
- package/docs/STABILITY.md +1 -1
- package/package.json +1 -1
- package/skills/rig-parts/SKILL.md +1 -1
- package/src/build.ts +2 -2
- package/src/check.ts +48 -9
package/README.md
CHANGED
|
@@ -37,10 +37,10 @@ is taken from the full run, because the twin tails leave the head crop sideways
|
|
|
37
37
|
(a plan edit). <code>check</code> printed, verbatim:<br/>
|
|
38
38
|
<code>50 assertions: 24 measured (24 passed, 0 failed), 26 skipped, 0 not in profile "spine-html"</code><br/>
|
|
39
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.
|
|
41
|
-
<code>pack: skeleton.png 922x1348, 22 region(s),
|
|
40
|
+
<code>seam: setup pose at 714x1216, scale 0.9422: mean |d| 0.234 (<= 1.0), 2 px over 40, 2.25 rig px (the count / s²; <= 50), 0 px over 80 (reported)</code><br/>
|
|
41
|
+
<code>pack: skeleton.png 922x1348, 22 region(s), 94.9% covered, padding 2, page edges free, shape polygon</code><br/>
|
|
42
42
|
<code>--seam silhouette</code> repaired the navy blobs the default rule leaves on the white
|
|
43
|
-
blouse (recomposite error pixels
|
|
43
|
+
blouse (recomposite error pixels 10,340 → 8,644); the default stays
|
|
44
44
|
<code>near-white</code>, the reference implementation's rule, so the examples stay
|
|
45
45
|
comparable with it. The loop is <code>rig-parts loop --palette</code>'s indexed APNG, 48 frames at
|
|
46
46
|
12 fps, 1,706,719 bytes, one 256-entry palette at a measured error of max 57, mean 1.601
|
|
@@ -60,7 +60,7 @@ made the contact sheet.
|
|
|
60
60
|
<p align="center"><em>
|
|
61
61
|
The plain fixture, <code>examples/sample</code>: same checkpoint, no LoRA, and a rig
|
|
62
62
|
built from its own proposal with no hand edit at all —
|
|
63
|
-
<code>seam:
|
|
63
|
+
<code>seam: setup pose at 379x1216, scale 1.0116: mean |d| 0.204 (<= 1.0), 2 px over 40, 1.95 rig px (the count / s²; <= 50), 0 px over 80 (reported)</code>, loop max |d| 0.
|
|
64
64
|
</em></p>
|
|
65
65
|
|
|
66
66
|
<p align="center"><em>
|
package/cli.ts
CHANGED
|
@@ -225,12 +225,17 @@ usage:
|
|
|
225
225
|
contact.png, motion_heat.png and check.json (with rigc_entry, the rigc
|
|
226
226
|
entry that gated the build, and pack_mode, the two pack flags). PASS needs every gate summary
|
|
227
227
|
"0 failed", the seam (setup pose vs the flat composite of parts/) at mean
|
|
228
|
-
|d| <= ${SEAM_MEAN_BAR.toFixed(1)} with <= ${SEAM_PX_BAR} px over ${SEAM_PX_LEVEL}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
228
|
+
|d| <= ${SEAM_MEAN_BAR.toFixed(1)} with <= ${SEAM_PX_BAR} rig px over ${SEAM_PX_LEVEL} (the frame pixels over ${SEAM_PX_LEVEL}
|
|
229
|
+
divided by s², s the frame scale the seam: line prints and check.json's
|
|
230
|
+
seam_frame_scale holds, so the bar means the same at every scale), and
|
|
231
|
+
the loop (idle frame 0 vs the frame at t = duration) at max |d| 0. A
|
|
232
|
+
failing seam, or any seam under --seam-pairs, is split by the pair of
|
|
233
|
+
parts each counted pixel lies between: one "seam pair" line each, worst
|
|
234
|
+
first, with its count in frame px, frame and rig boxes and a picture
|
|
235
|
+
under seam/, and check.json's seam_pairs (AUTHORING §7). A seam picture
|
|
236
|
+
is the setup-pose frame with that pair's counted pixels painted magenta
|
|
237
|
+
(255, 0, 255) — magenta = a counted pixel — and a cyan (0, 255, 255)
|
|
238
|
+
box one pixel outside them. --keep-stills leaves the setup-pose still the seam reads,
|
|
234
239
|
rigc's render directory with its frames.json (viewport and background),
|
|
235
240
|
under setup_still/. Then six judgement lines, each in
|
|
236
241
|
check.json and on the console as NAME: PASS|FAIL|SKIP with its figures and
|
|
@@ -545,6 +550,39 @@ function printLayerTable(set: LayerSet): void {
|
|
|
545
550
|
console.log(` ${warned} WARN line(s)`);
|
|
546
551
|
}
|
|
547
552
|
|
|
553
|
+
/**
|
|
554
|
+
* Every flag each command takes: `valued` flags carry a value, `switches` are bare. Each parser reads its row to
|
|
555
|
+
* decide what it accepts, and {@link notTaken} prints the same row in the unknown-flag refusal, so the refusal
|
|
556
|
+
* lists exactly the flags the parser accepts (CL26). `layers` takes one positional path and no flag, so it has no row.
|
|
557
|
+
*/
|
|
558
|
+
const TAKES = {
|
|
559
|
+
sheet: { valued: ['--source', '--layers', '--out', '--cell', '--cols'], switches: [] },
|
|
560
|
+
rig: { valued: ['--config', '--parts', '--out', '--idle-keys', '--page-edges', '--pack-shape'], switches: [] },
|
|
561
|
+
propose: { valued: ['--parts', '--source', '--out', '--from-config', '--compare', '--full', '--canvas', '--keypoints', '--person', '--wrist-snap'], switches: ['--head-box'] },
|
|
562
|
+
compare: { valued: ['--left', '--right', '--map'], switches: [] },
|
|
563
|
+
check: { valued: ['--rig', '--out', '--parts', '--source', '--page-edges', '--pack-shape', '--requirements'], switches: ['--seam-pairs', '--keep-stills'] },
|
|
564
|
+
loop: { valued: ['--frames', '--out'], switches: ['--palette'] },
|
|
565
|
+
assemble: { valued: ['--source', '--full', '--head', '--config', '--out', '--seam', '--project'], switches: ['--propose-plan'] },
|
|
566
|
+
'comfy seethrough': { valued: ['--image', '--out', '--resolution', '--steps', '--seed', '--prefix', '--host', '--wait', '--timeout', '--poll'], switches: ['--offload', '--lama', '--nf4'] },
|
|
567
|
+
'comfy paint': { valued: ['--config', '--out', '--seeds', '--seed0', '--host', '--wait', '--timeout', '--poll'], switches: [] },
|
|
568
|
+
inputs: { valued: ['--source', '--config', '--out'], switches: [] },
|
|
569
|
+
build: { valued: ['--config', '--source', '--full', '--head', '--out', '--seam', '--project', '--page-edges', '--pack-shape', '--requirements', '--idle-keys'], switches: ['--loop'] },
|
|
570
|
+
compose: { valued: ['--scene', '--out', '--requirements', '--accept'], switches: [] },
|
|
571
|
+
} as const satisfies Record<string, { valued: readonly string[]; switches: readonly string[] }>;
|
|
572
|
+
|
|
573
|
+
type Command = keyof typeof TAKES;
|
|
574
|
+
|
|
575
|
+
/** Whether `command` takes `flag` as a valued flag, read off its {@link TAKES} row. */
|
|
576
|
+
function takesValued(command: Command, flag: string): boolean {
|
|
577
|
+
return (TAKES[command].valued as readonly string[]).includes(flag);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/** The unknown-flag refusal: the flag, and every flag the command's {@link TAKES} row holds, valued then bare. */
|
|
581
|
+
function notTaken(command: Command, flag: string): string {
|
|
582
|
+
const row = TAKES[command];
|
|
583
|
+
return `${command} does not take "${flag}"; it takes ${[...row.valued, ...row.switches].join(', ')}`;
|
|
584
|
+
}
|
|
585
|
+
|
|
548
586
|
function cmdLayers(args: string[]): number {
|
|
549
587
|
if (args.length !== 1) return usage(`layers takes one path; got ${args.length}`);
|
|
550
588
|
try {
|
|
@@ -564,7 +602,7 @@ function cmdSheet(args: string[]): number {
|
|
|
564
602
|
for (let i = 0; i < args.length; i++) {
|
|
565
603
|
const flag = args[i];
|
|
566
604
|
const value = args[i + 1];
|
|
567
|
-
if (!
|
|
605
|
+
if (!takesValued('sheet', flag)) return usage(notTaken('sheet', flag));
|
|
568
606
|
if (value === undefined) return usage(`${flag} needs a value`);
|
|
569
607
|
i++;
|
|
570
608
|
if (flag === '--source') source = value;
|
|
@@ -634,7 +672,7 @@ function cmdRig(args: string[]): number {
|
|
|
634
672
|
for (let i = 0; i < args.length; i++) {
|
|
635
673
|
const flag = args[i];
|
|
636
674
|
const value = args[i + 1];
|
|
637
|
-
if (!
|
|
675
|
+
if (!takesValued('rig', flag)) return usage(notTaken('rig', flag));
|
|
638
676
|
if (value === undefined) return usage(`${flag} needs a value`);
|
|
639
677
|
i++;
|
|
640
678
|
if (flag === '--config') config = value;
|
|
@@ -694,17 +732,17 @@ function rigcRunner(bin: string): RigcRunner {
|
|
|
694
732
|
};
|
|
695
733
|
}
|
|
696
734
|
|
|
697
|
-
|
|
735
|
+
/** The command's valued flags as `--flag value` pairs, each one its {@link TAKES} row holds; `required` must all be given. Its bare switches are taken out by the caller before this reads the rest. A string is a usage error. */
|
|
736
|
+
function flags(args: string[], command: Command, required: readonly string[]): Map<string, string> | string {
|
|
698
737
|
const got = new Map<string, string>();
|
|
699
|
-
const all = [...known, ...optional];
|
|
700
738
|
for (let i = 0; i < args.length; i += 2) {
|
|
701
739
|
const flag = args[i];
|
|
702
|
-
if (!
|
|
740
|
+
if (!takesValued(command, flag)) return notTaken(command, flag);
|
|
703
741
|
if (args[i + 1] === undefined) return `${flag} needs a value`;
|
|
704
742
|
if (got.has(flag)) return `${flag} is given twice`;
|
|
705
743
|
got.set(flag, args[i + 1]);
|
|
706
744
|
}
|
|
707
|
-
for (const k of
|
|
745
|
+
for (const k of required) if (!got.has(k)) return `${command} needs ${k} <value>`;
|
|
708
746
|
return got;
|
|
709
747
|
}
|
|
710
748
|
|
|
@@ -716,9 +754,8 @@ function cmdCheck(args: string[]): number {
|
|
|
716
754
|
if (keepStills > 1) return usage('--keep-stills is given twice');
|
|
717
755
|
const f = flags(
|
|
718
756
|
args.filter((a) => a !== '--seam-pairs' && a !== '--keep-stills'),
|
|
719
|
-
['--rig', '--out'],
|
|
720
757
|
'check',
|
|
721
|
-
['--
|
|
758
|
+
['--rig', '--out'],
|
|
722
759
|
);
|
|
723
760
|
if (typeof f === 'string') return usage(f);
|
|
724
761
|
const rig = f.get('--rig') as string;
|
|
@@ -768,14 +805,13 @@ function printLint(P: PartSet, spec: { bones: CharacterConfig['bones']; meshes:
|
|
|
768
805
|
function cmdPropose(args: string[]): number {
|
|
769
806
|
const flags = new Map<string, string>();
|
|
770
807
|
let headBox = false;
|
|
771
|
-
const valued = ['--parts', '--source', '--out', '--from-config', '--compare', '--full', '--canvas', '--keypoints', '--person', '--wrist-snap'];
|
|
772
808
|
for (let i = 0; i < args.length; i++) {
|
|
773
809
|
const flag = args[i];
|
|
774
810
|
if (flag === '--head-box') {
|
|
775
811
|
headBox = true;
|
|
776
812
|
continue;
|
|
777
813
|
}
|
|
778
|
-
if (!
|
|
814
|
+
if (!takesValued('propose', flag)) return usage(notTaken('propose', flag));
|
|
779
815
|
const value = args[i + 1];
|
|
780
816
|
if (value === undefined) return usage(`${flag} needs a value`);
|
|
781
817
|
if (flags.has(flag)) return usage(`${flag} is given twice`);
|
|
@@ -873,10 +909,9 @@ function cmdPropose(args: string[]): number {
|
|
|
873
909
|
|
|
874
910
|
function cmdCompare(args: string[]): number {
|
|
875
911
|
const flags = new Map<string, string>();
|
|
876
|
-
const valued = ['--left', '--right', '--map'];
|
|
877
912
|
for (let i = 0; i < args.length; i++) {
|
|
878
913
|
const flag = args[i];
|
|
879
|
-
if (!
|
|
914
|
+
if (!takesValued('compare', flag)) return usage(notTaken('compare', flag));
|
|
880
915
|
const value = args[i + 1];
|
|
881
916
|
if (value === undefined) return usage(`${flag} needs a value`);
|
|
882
917
|
if (flags.has(flag)) return usage(`${flag} is given twice`);
|
|
@@ -905,8 +940,8 @@ function cmdLoop(args: string[]): number {
|
|
|
905
940
|
if (palettes > 1) return usage('--palette is given twice');
|
|
906
941
|
const f = flags(
|
|
907
942
|
args.filter((a) => a !== '--palette'),
|
|
908
|
-
['--frames', '--out'],
|
|
909
943
|
'loop',
|
|
944
|
+
['--frames', '--out'],
|
|
910
945
|
);
|
|
911
946
|
if (typeof f === 'string') return usage(f);
|
|
912
947
|
const out = f.get('--out') as string;
|
|
@@ -925,14 +960,13 @@ function cmdLoop(args: string[]): number {
|
|
|
925
960
|
function cmdAssemble(args: string[]): number {
|
|
926
961
|
const flags = new Map<string, string>();
|
|
927
962
|
let propose = false;
|
|
928
|
-
const valued = ['--source', '--full', '--head', '--config', '--out', '--seam', '--project'];
|
|
929
963
|
for (let i = 0; i < args.length; i++) {
|
|
930
964
|
const flag = args[i];
|
|
931
965
|
if (flag === '--propose-plan') {
|
|
932
966
|
propose = true;
|
|
933
967
|
continue;
|
|
934
968
|
}
|
|
935
|
-
if (!
|
|
969
|
+
if (!takesValued('assemble', flag)) return usage(notTaken('assemble', flag));
|
|
936
970
|
const value = args[i + 1];
|
|
937
971
|
if (value === undefined) return usage(`${flag} needs a value`);
|
|
938
972
|
if (flags.has(flag)) return usage(`${flag} is given twice`);
|
|
@@ -968,8 +1002,9 @@ function cmdAssemble(args: string[]): number {
|
|
|
968
1002
|
}
|
|
969
1003
|
}
|
|
970
1004
|
|
|
971
|
-
/** Parse `--flag value` pairs and bare `--switch`es; a string is a usage error. */
|
|
972
|
-
function comfyFlags(args: string[],
|
|
1005
|
+
/** Parse `--flag value` pairs and bare `--switch`es, as the command's {@link TAKES} row names them; a string is a usage error. */
|
|
1006
|
+
function comfyFlags(args: string[], command: 'comfy seethrough' | 'comfy paint'): { v: Map<string, string>; on: Set<string> } | string {
|
|
1007
|
+
const switches: readonly string[] = TAKES[command].switches;
|
|
973
1008
|
const v = new Map<string, string>();
|
|
974
1009
|
const on = new Set<string>();
|
|
975
1010
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -979,7 +1014,7 @@ function comfyFlags(args: string[], valued: readonly string[], switches: readonl
|
|
|
979
1014
|
on.add(flag);
|
|
980
1015
|
continue;
|
|
981
1016
|
}
|
|
982
|
-
if (!
|
|
1017
|
+
if (!takesValued(command, flag)) return notTaken(command, flag);
|
|
983
1018
|
const value = args[i + 1];
|
|
984
1019
|
if (value === undefined) return `${flag} needs a value`;
|
|
985
1020
|
if (v.has(flag)) return `${flag} is given twice`;
|
|
@@ -1005,14 +1040,10 @@ function secondsFlag(v: Map<string, string>, flag: string, fallback: number): nu
|
|
|
1005
1040
|
return Number.isFinite(n) && n > 0 ? n : `${flag} ${raw} is not a number of seconds above 0`;
|
|
1006
1041
|
}
|
|
1007
1042
|
|
|
1008
|
-
const COMFY_TIMING = ['--host', '--wait', '--timeout', '--poll'] as const;
|
|
1009
|
-
|
|
1010
1043
|
async function cmdComfy(args: string[]): Promise<number> {
|
|
1011
1044
|
const [sub, ...rest] = args;
|
|
1012
1045
|
if (sub !== 'seethrough' && sub !== 'paint') return usage(`comfy takes seethrough or paint; got ${sub === undefined ? 'nothing' : `"${sub}"`}`);
|
|
1013
|
-
const
|
|
1014
|
-
const switches = sub === 'seethrough' ? ['--offload', '--lama', '--nf4'] : [];
|
|
1015
|
-
const f = comfyFlags(rest, valued, switches, `comfy ${sub}`);
|
|
1046
|
+
const f = comfyFlags(rest, `comfy ${sub}`);
|
|
1016
1047
|
if (typeof f === 'string') return usage(f);
|
|
1017
1048
|
const { v, on } = f;
|
|
1018
1049
|
const wait = secondsFlag(v, '--wait', 1800);
|
|
@@ -1075,7 +1106,7 @@ async function cmdComfy(args: string[]): Promise<number> {
|
|
|
1075
1106
|
}
|
|
1076
1107
|
|
|
1077
1108
|
function cmdInputs(args: string[]): number {
|
|
1078
|
-
const f = flags(args, ['--source', '--config', '--out']
|
|
1109
|
+
const f = flags(args, 'inputs', ['--source', '--config', '--out']);
|
|
1079
1110
|
if (typeof f === 'string') return usage(f);
|
|
1080
1111
|
const source = f.get('--source') as string;
|
|
1081
1112
|
const out = f.get('--out') as string;
|
|
@@ -1107,7 +1138,6 @@ function cmdInputs(args: string[]): number {
|
|
|
1107
1138
|
function cmdBuild(args: string[]): number {
|
|
1108
1139
|
const flags = new Map<string, string>();
|
|
1109
1140
|
let loop = false;
|
|
1110
|
-
const valued = ['--config', '--source', '--full', '--head', '--out', '--seam', '--project', '--page-edges', '--pack-shape', '--requirements', '--idle-keys'];
|
|
1111
1141
|
for (let i = 0; i < args.length; i++) {
|
|
1112
1142
|
const flag = args[i];
|
|
1113
1143
|
if (flag === '--loop') {
|
|
@@ -1115,7 +1145,7 @@ function cmdBuild(args: string[]): number {
|
|
|
1115
1145
|
loop = true;
|
|
1116
1146
|
continue;
|
|
1117
1147
|
}
|
|
1118
|
-
if (!
|
|
1148
|
+
if (!takesValued('build', flag)) return usage(notTaken('build', flag));
|
|
1119
1149
|
const value = args[i + 1];
|
|
1120
1150
|
if (value === undefined) return usage(`${flag} needs a value`);
|
|
1121
1151
|
if (flags.has(flag)) return usage(`${flag} is given twice`);
|
|
@@ -1154,7 +1184,7 @@ function cmdBuild(args: string[]): number {
|
|
|
1154
1184
|
}
|
|
1155
1185
|
|
|
1156
1186
|
function cmdCompose(args: string[]): number {
|
|
1157
|
-
const f = flags(args,
|
|
1187
|
+
const f = flags(args, 'compose', ['--scene', '--out']);
|
|
1158
1188
|
if (typeof f === 'string') return usage(f);
|
|
1159
1189
|
let bin: string;
|
|
1160
1190
|
try {
|
package/docs/AUTHORING.md
CHANGED
|
@@ -1230,8 +1230,10 @@ names it — `[29]` on both examples.
|
|
|
1230
1230
|
flat stack of parts differs from the painting by more than 60, the top part takes the
|
|
1231
1231
|
painting's colour — except where the painting is near-white, which protects the page
|
|
1232
1232
|
and also every white garment. `--seam silhouette` protects near-white pixels only
|
|
1233
|
-
outside the figure. On the demo it
|
|
1234
|
-
|
|
1233
|
+
outside the figure. On the demo it lowers recomposite error pixels from 10,340 to
|
|
1234
|
+
8,644 (`assemble`'s `error px > 40`, at v1.5.2); on the sample it changes nothing
|
|
1235
|
+
(4,359 under both). At v0.14.0 the demo's were 11,050 and 9,540, and no check bar
|
|
1236
|
+
changed. The default stays
|
|
1235
1237
|
the reference's so the examples stay comparable with it.
|
|
1236
1238
|
|
|
1237
1239
|
**The fringe push-back** (issue #119; under either seam rule, no flag). Both places
|
|
@@ -1253,9 +1255,9 @@ and a cleared bridge to opaque art is put back, so no part gains an island
|
|
|
1253
1255
|
names them. On the examples (default flags, measured against the v0.14.0 build of
|
|
1254
1256
|
the same inputs): `demo` 1,193 px over 16 parts, recomposite error pixels 11,050 →
|
|
1255
1257
|
10,340; `sample` 276 px over 15 parts, 4,512 → 4,359; uncovered error pixels and
|
|
1256
|
-
holes unchanged on both, `check` 9 of 9 on both. The `--
|
|
1257
|
-
|
|
1258
|
-
|
|
1258
|
+
holes unchanged on both, `check` 9 of 9 on both. The `--project` comparison in
|
|
1259
|
+
this section and the `SETUP_POSE_VS_SOURCE` example figures in §7 were measured
|
|
1260
|
+
before it, at v0.14.0; the `--seam` figures above are v1.5.2's.
|
|
1259
1261
|
|
|
1260
1262
|
**Page edges.** `rig`, `check` and `build` pack through `rigc build --pack
|
|
1261
1263
|
--page-edges <v>`, and hand the value to rigc verbatim. The default is `free`,
|
|
@@ -1685,7 +1687,7 @@ See-through with another seed.
|
|
|
1685
1687
|
| `CHECK_RIGC_GREEN` | a rigc step failed; its line is quoted | as `RIG_RIGC_GREEN` |
|
|
1686
1688
|
| `CHECK_LOOP_LAST_FRAME_AT_DURATION` | the idle's last frame does not sit at `duration` | `motion.duration` — a whole number of 1/12 s |
|
|
1687
1689
|
| `CHECK_LOOP_CLOSES` | frame 0 and the frame at `duration` differ (max and first pixel quoted); with a physics constraint, the frames of the rig with every physics constraint left out, and the object says so (§7, *loop*) | a track whose last key is not its first |
|
|
1688
|
-
| `CHECK_SEAM_WITHIN_BAR` | the setup pose does not reproduce the flat stack of parts | read the `seam pair` lines and `check/seam/` pictures (§7): the pair, its boxes and the pixels counted. Usually a region or mesh placed off its part; compare with `recomposite_rig.png`. A pair of one layer cut in two with `differing_copies_px` is the cut line: move the polygon's edge, not `overlap` |
|
|
1690
|
+
| `CHECK_SEAM_WITHIN_BAR` | the setup pose does not reproduce the flat stack of parts | read the `seam pair` lines and `check/seam/` pictures (§7; magenta = a counted pixel, cyan = the box round them): the pair, its boxes and the pixels counted. Usually a region or mesh placed off its part; compare with `recomposite_rig.png`. A pair of one layer cut in two with `differing_copies_px` is the cut line: move the polygon's edge, not `overlap` |
|
|
1689
1691
|
| `CHECK_BREATH_VISIBLE` | the torso (`topwear`), rendered alone, barely moves over the idle — or the feet (`footwear`), rendered alone, move at all | the chest's breath tracks (`motion.tracks` on `chest`), or the torso mesh's `segments`; for the feet, the bone their region rides (`regions.<part>`, `root` in both examples) |
|
|
1690
1692
|
| `CHECK_BLINK_NO_HOLE` | with the blink held shut, the eyewhite box shows the page where the open eye had art | the layer under the eye: the `face` part has no art there. Take the face from the other run, or add a part under the eye; `motion.blink.squash` only hides the hole less |
|
|
1691
1693
|
| `CHECK_CHAIN_LAG` | a rotate track leads (or does not lag) the keyed bone above it, or a chain link swings less than the link above | that chain track's `phase`/`lag` (a positive `lag`, a child `phase` above its parent's) or its `amps` (non-decreasing toward the tip) |
|
|
@@ -1753,7 +1755,19 @@ assemble*, at the end of this section):
|
|
|
1753
1755
|
`A49_PACKED_FOOTPRINTS_DO_NOT_OVERLAP` on the packed pass, 16 not in `spine`; `CH09`
|
|
1754
1756
|
holds it on every fetched example);
|
|
1755
1757
|
- **seam**: the setup-pose render against the flat composite of `parts/`: mean
|
|
1756
|
-
max-channel |d| ≤ 1.0 of 255, and at most 50 pixels over 40
|
|
1758
|
+
max-channel |d| ≤ 1.0 of 255, and at most 50 **rig** pixels over 40 — the
|
|
1759
|
+
frame pixels over 40 divided by s², `s` the frame scale the `seam:` line
|
|
1760
|
+
prints and `check.json`'s `seam_frame_scale` holds (issue #216). The frame
|
|
1761
|
+
holds s² pixels per rig pixel, so a stack that parts from the render over a
|
|
1762
|
+
fixed area of the rig reads s² times as many frame pixels: on the public
|
|
1763
|
+
`sample` a part moved 2 rig px read 400, 891 and 1,560 frame px over 40 at
|
|
1764
|
+
s = 1.0, 1.5 and 2.0, and 400, 396 and 390 rig px; the sample as built read
|
|
1765
|
+
3, 3 and 7 frame px [measured, issue #216]. Divided by s² the bar reads the
|
|
1766
|
+
same rig at every scale, and at s = 1 it is the reference's count. The mean
|
|
1767
|
+
is a share of the frame and needs no such division (0.242, 0.188 and 0.226 on
|
|
1768
|
+
the sample as built; 0.353, 0.305 and 0.340 with the part moved).
|
|
1769
|
+
`seam_px_over_40` and `seam_px_over_80` stay frame pixels, and so does each
|
|
1770
|
+
pair's count below. The bar reads one
|
|
1757
1771
|
frame, the setup-pose still, never the idle. When it fails (or when `check
|
|
1758
1772
|
--seam-pairs` asks, pass or fail), the pixels over 40 are split by the pair of
|
|
1759
1773
|
parts that answers for each (issue #202): the two parts drawn last among those
|
|
@@ -1761,7 +1775,8 @@ assemble*, at the end of this section):
|
|
|
1761
1775
|
sample reads for that frame pixel, lower first, `(no art)` standing in where
|
|
1762
1776
|
fewer than two do. Each pair is a console line `seam pair <i> of <n>: "<a>" /
|
|
1763
1777
|
"<b>" at the setup pose: <count> px over 40 (<count> over 80), frame box x
|
|
1764
|
-
<x0>..<x1>, y <y0>..<y1>, rig box …, picture seam/<i>_<a>_<b>.png
|
|
1778
|
+
<x0>..<x1>, y <y0>..<y1>, rig box …, picture seam/<i>_<a>_<b>.png` (in the
|
|
1779
|
+
picture, **magenta = a counted pixel**, cyan = the box round them), worst first,
|
|
1765
1780
|
and a row of `check.json`'s `seam_pairs` (`parts`, `px_over_40`, `px_over_80`,
|
|
1766
1781
|
`frame_box` in frame pixels, `rig_box` in the rig pixels of `parts.json` and
|
|
1767
1782
|
`cuts[].polygon`, `picture`); `CHECK_SEAM_WITHIN_BAR`'s line quotes the worst.
|
package/docs/STABILITY.md
CHANGED
|
@@ -158,7 +158,7 @@ every number) are AUTHORING §3's tables, and the loader refuses any other by na
|
|
|
158
158
|
| `parts.json`, `parts/<name>.png` | `assemble`, `build` | one record per part: `from`, the box `x`, `y`, `w`, `h` (rig px), the pixel counts `src/parts.ts` documents (`opaque_px`, `visible_px`, `occluded_px`, `projected_core_px`, `source_px_taken`, `visible_not_projected_px`, `refused_drift_px`, `merged_px`, `seam_override_px`, and `fringe_pushed_back` when not empty); `rig_size`, `scale_rig_per_source`, `ghost_px`, `recomposite` |
|
|
159
159
|
| `rig/rig.json`, `rig/motion.json`, `rig/images/` | `rig`, `build`, `compose` | rig-c's rig spec and motion spec, `spec` `rigc-rig/1` and `rigc-motion/1`: the format is rig-c's, and this package writes only what rig-c's parser accepts |
|
|
160
160
|
| `rig/mesh_report.json` | `rig`, `build` | one row per mesh: `part`, `vertices`, `triangles`, `hull`, `bones`, `max_influences`, `mean_influences`, `art_coverage`, then `grid` (a lattice); `mode` "contour" with `params`, `contour`, `regions`; or `mode` "auto" with `settings`, `source`, `result`, `residuals`, `worst_residual`, `worst_region`, `motion_amplitude`, `termination`, `weights`, `regions`, `deformation`, `quality_report` (rig-c's `mesh-quality-report/1` document whole), `motion_report`, and, only when they apply, `stray_cleared` (after `source`, when `source.stray` is declared), `skinning_residual` and `replay` (`AutoMeshReport` in `src/rig.ts`); in every mode, `exponent` when the mesh declares one (after `grid`, or after `mode`); `ribs` when a contour or auto mesh declares them (after `exponent`, or after `mode`), and then `contour.ribs` (`source.contour.ribs` in the automatic mode) |
|
|
161
|
-
| `check/check.json` and the rest of `check/` | `check`, `build`, `compose` | the reference's fields in its order (`gate_spine_html_green`, `rigc_entry`, `pack_mode`, `loop_max_diff`, the seam figures), `loop_physics` after `loop_max_diff` when the rig declares a physics constraint, `seam_pairs` after the seam figures when the seam bar fails or `--seam-pairs` asks (each row's picture under `check/seam/`; `--keep-stills` leaves the setup-pose still and its `frames.json` under `check/setup_still/`), then the judgement lines (`BREATH_VISIBLE`, `BLINK_NO_HOLE`, `CHAIN_LAG`, `TIP_OVER_ROOT`, `STILL_REGIONS_DARK`, `TEXTURE_STRETCH`), `RECOMPOSITE_HOLES`, `SETUP_POSE_VS_SOURCE`, `skipped`, a `requirements` block under `--requirements`, and `PASS`; `check/build/skeleton.json`, `.atlas` and `.png` are the packed artifact rig-c writes |
|
|
161
|
+
| `check/check.json` and the rest of `check/` | `check`, `build`, `compose` | the reference's fields in its order (`gate_spine_html_green`, `rigc_entry`, `pack_mode`, `loop_max_diff`, the seam figures — `seam_mean`, `seam_px_over_40`, `seam_px_over_80` and, from issue #216, `seam_frame_scale`, the frame scale the bar divides the count by), `loop_physics` after `loop_max_diff` when the rig declares a physics constraint, `seam_pairs` after the seam figures when the seam bar fails or `--seam-pairs` asks (each row's picture under `check/seam/`; `--keep-stills` leaves the setup-pose still and its `frames.json` under `check/setup_still/`), then the judgement lines (`BREATH_VISIBLE`, `BLINK_NO_HOLE`, `CHAIN_LAG`, `TIP_OVER_ROOT`, `STILL_REGIONS_DARK`, `TEXTURE_STRETCH`), `RECOMPOSITE_HOLES`, `SETUP_POSE_VS_SOURCE`, `skipped`, a `requirements` block under `--requirements`, and `PASS`; `check/build/skeleton.json`, `.atlas` and `.png` are the packed artifact rig-c writes |
|
|
162
162
|
| `proposal.json`, `render/landmarks*.png` | `propose` | the config's four rig sections and `notes` |
|
|
163
163
|
| `basis.json` | `propose` | spec `spine-parts-basis/1` |
|
|
164
164
|
| the scene report | `compose` | spec `spine-parts-scene-report/1` |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rig-parts",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.3",
|
|
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": {
|
|
@@ -125,7 +125,7 @@ it and the order as `scene.json` records it (AUTHORING §7, *Composing several
|
|
|
125
125
|
characters*).
|
|
126
126
|
|
|
127
127
|
`--seam silhouette` repairs more of the seam on white garments than the default
|
|
128
|
-
`near-white` (the demo:
|
|
128
|
+
`near-white` (the demo: 10,340 → 8,644 recomposite error pixels); the default is
|
|
129
129
|
the reference implementation's rule. `--project visible` lets thin visible parts
|
|
130
130
|
(lashes, brows, irises) take the painting's pixels, which the default `core` rule
|
|
131
131
|
erodes away. Name both rules you used in your report, and quote the assemble
|
package/src/build.ts
CHANGED
|
@@ -70,7 +70,7 @@ import {
|
|
|
70
70
|
splitSchedule,
|
|
71
71
|
untestedIntervals,
|
|
72
72
|
} from './autoreplay.ts';
|
|
73
|
-
import { BARS, besideIdleLine, causeLines, REQUIREMENTS_DIR, type CheckReport, type PackLine, DEFAULT_PACK_SHAPE, DEFAULT_PAGE_EDGES, JUDGEMENT_LINES, type JudgementLine, packedBuildArgs, packedBuildLabel, type PackMode, type PackShape, type PageEdges, readFrameSet, REPORTED_LINES, type ReportedLine, type RigcRunner, runCheck, SEAM_DIR, SEAM_MEAN_BAR, seamPairText, SOURCE_LINE, SEAM_PX_BAR, SEAM_PX_LEVEL, SEAM_PX_LEVEL_HIGH, SPINEBOY_YARDSTICK, STILL_DIR } from './check.ts';
|
|
73
|
+
import { BARS, besideIdleLine, causeLines, REQUIREMENTS_DIR, type CheckReport, type PackLine, DEFAULT_PACK_SHAPE, DEFAULT_PAGE_EDGES, JUDGEMENT_LINES, type JudgementLine, packedBuildArgs, packedBuildLabel, type PackMode, type PackShape, type PageEdges, readFrameSet, REPORTED_LINES, type ReportedLine, type RigcRunner, runCheck, SEAM_DIR, SEAM_MEAN_BAR, seamPairText, seamRigPxText, SOURCE_LINE, SEAM_PX_BAR, SEAM_PX_LEVEL, SEAM_PX_LEVEL_HIGH, SPINEBOY_YARDSTICK, STILL_DIR } from './check.ts';
|
|
74
74
|
import { type Cut, loadConfig, loadConfigAndAnimations, loadEarlyConfig } from './config.ts';
|
|
75
75
|
import { PartsError, type Problem, problemLine, refuseIfAny } from './errors.ts';
|
|
76
76
|
import type { MeshQualityReport, MotionSchedule } from 'rig-c/mesh';
|
|
@@ -981,7 +981,7 @@ export function checkStage(input: CheckStageInput, rigc: RigcRunner, bin: string
|
|
|
981
981
|
else {
|
|
982
982
|
log(
|
|
983
983
|
` seam: setup pose at ${r.seamViewport.pixelWidth}x${r.seamViewport.pixelHeight}, scale ${r.seamViewport.scale.toFixed(4)}: mean |d| ${fig.seam_mean} (<= ${SEAM_MEAN_BAR.toFixed(1)}), ` +
|
|
984
|
-
`${fig.seam_px_over_40} px over ${SEAM_PX_LEVEL} (<= ${SEAM_PX_BAR}), ${fig.seam_px_over_80} px over ${SEAM_PX_LEVEL_HIGH} (reported)`,
|
|
984
|
+
`${fig.seam_px_over_40} px over ${SEAM_PX_LEVEL}, ${fig.seam_frame_scale === null || fig.seam_px_over_40 === null ? '' : seamRigPxText(fig.seam_px_over_40, fig.seam_frame_scale)} rig px (the count / s²; <= ${SEAM_PX_BAR}), ${fig.seam_px_over_80} px over ${SEAM_PX_LEVEL_HIGH} (reported)`,
|
|
985
985
|
);
|
|
986
986
|
const pairs = fig.seam_pairs ?? [];
|
|
987
987
|
pairs.forEach((row, i) => log(` seam pair ${i + 1} of ${pairs.length}: ${seamPairText(row)}`));
|
package/src/check.ts
CHANGED
|
@@ -153,7 +153,17 @@ import { type ScaleTranslate, warpAffine } from './raster/warp.ts';
|
|
|
153
153
|
|
|
154
154
|
/** Seam: mean max-channel |d| over the frame, in levels of 255, at most this. */
|
|
155
155
|
export const SEAM_MEAN_BAR = 1.0;
|
|
156
|
-
/**
|
|
156
|
+
/**
|
|
157
|
+
* Seam: pixels whose max-channel |d| exceeds {@link SEAM_PX_LEVEL}, at most
|
|
158
|
+
* this many **rig** pixels — the frame count divided by s², s the frame scale
|
|
159
|
+
* the setup-pose still renders at (issue #216). The frame holds s² pixels per
|
|
160
|
+
* rig pixel, so a stack that parts from the render over a fixed area of the
|
|
161
|
+
* rig reads s² times as many frame pixels: on the public sample a part moved
|
|
162
|
+
* 2 rig px read 400, 891 and 1,560 frame px over 40 at s = 1.0, 1.5 and 2.0,
|
|
163
|
+
* and 400, 396 and 390 divided by s²; the uncut sample 3, 3 and 7 frame px
|
|
164
|
+
* [measured for issue #216, a scratch run, not tracked]. At s = 1 the bar is
|
|
165
|
+
* the reference's frame count.
|
|
166
|
+
*/
|
|
157
167
|
export const SEAM_PX_BAR = 50;
|
|
158
168
|
export const SEAM_PX_LEVEL = 40;
|
|
159
169
|
/** Seam: the second, reported-only level. */
|
|
@@ -1372,9 +1382,29 @@ function round3(x: number): number {
|
|
|
1372
1382
|
return Number(x.toFixed(3));
|
|
1373
1383
|
}
|
|
1374
1384
|
|
|
1375
|
-
/**
|
|
1376
|
-
function
|
|
1377
|
-
return
|
|
1385
|
+
/** Python's `round(x, 6)` for the frame scale written: to six places, on the value's exact decimal expansion. */
|
|
1386
|
+
function round6(x: number): number {
|
|
1387
|
+
return Number(x.toFixed(6));
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
/** The seam's count in rig pixels (issue #216): the frame pixels over {@link SEAM_PX_LEVEL} divided by the square of the frame scale. */
|
|
1391
|
+
export function seamRigPx(over40: number, scale: number): number {
|
|
1392
|
+
return over40 / (scale * scale);
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
/** {@link seamRigPx} as the console and the refusal print it, to two places. */
|
|
1396
|
+
export function seamRigPxText(over40: number, scale: number): string {
|
|
1397
|
+
return seamRigPx(over40, scale).toFixed(2);
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
/**
|
|
1401
|
+
* The seam bar, on the figures as check.json writes them: the mean (rounded
|
|
1402
|
+
* to three places) over {@link SEAM_MEAN_BAR}, or more than
|
|
1403
|
+
* {@link SEAM_PX_BAR} rig pixels over {@link SEAM_PX_LEVEL} — the frame count
|
|
1404
|
+
* over the square of the written frame scale (issue #216).
|
|
1405
|
+
*/
|
|
1406
|
+
function seamFails(mean: number, over40: number, scale: number): boolean {
|
|
1407
|
+
return mean > SEAM_MEAN_BAR || seamRigPx(over40, scale) > SEAM_PX_BAR;
|
|
1378
1408
|
}
|
|
1379
1409
|
|
|
1380
1410
|
/** A seam row as the console prints it: the pair, the frame, the counts, both boxes and the picture. */
|
|
@@ -1536,10 +1566,16 @@ export interface CheckFigures {
|
|
|
1536
1566
|
* read from. Reported, not judged; a rig without one writes the keys it always wrote.
|
|
1537
1567
|
*/
|
|
1538
1568
|
loop_physics?: LoopPhysics[];
|
|
1539
|
-
/** The
|
|
1569
|
+
/** The seam figures: null when there is no parts.json (issue #77); `skipped.seam` then says why. The two counts are frame pixels. */
|
|
1540
1570
|
seam_mean: number | null;
|
|
1541
1571
|
seam_px_over_40: number | null;
|
|
1542
1572
|
seam_px_over_80: number | null;
|
|
1573
|
+
/**
|
|
1574
|
+
* The frame scale s the setup-pose still rendered at (rigc's viewport
|
|
1575
|
+
* scale, to six places), issue #216: the seam bar reads `seam_px_over_40`
|
|
1576
|
+
* divided by its square ({@link seamRigPx}). Null with the other three.
|
|
1577
|
+
*/
|
|
1578
|
+
seam_frame_scale: number | null;
|
|
1543
1579
|
/**
|
|
1544
1580
|
* Written only when the seam bar fails, or when it was asked for (`check
|
|
1545
1581
|
* --seam-pairs`), issue #202: the counted pixels split by the pair of parts
|
|
@@ -1600,7 +1636,9 @@ export function barsNotMet(fig: Readonly<Record<string, unknown>>): string[] {
|
|
|
1600
1636
|
const out: string[] = [];
|
|
1601
1637
|
if (fig.gate_spine_html_green !== true) out.push('gate');
|
|
1602
1638
|
if (typeof fig.loop_max_diff === 'number' && fig.loop_max_diff !== LOOP_MAX_BAR) out.push('loop');
|
|
1603
|
-
|
|
1639
|
+
// A check.json written before issue #216 has no seam_frame_scale; it is read by the frame count, the bar it was written under.
|
|
1640
|
+
const scale = typeof fig.seam_frame_scale === 'number' ? fig.seam_frame_scale : 1;
|
|
1641
|
+
if (typeof fig.seam_mean === 'number' && typeof fig.seam_px_over_40 === 'number' && seamFails(fig.seam_mean, fig.seam_px_over_40, scale)) out.push('seam');
|
|
1604
1642
|
for (const n of JUDGEMENT_LINES) {
|
|
1605
1643
|
const l = fig[n];
|
|
1606
1644
|
if (typeof l === 'object' && l !== null && (l as { status?: unknown }).status === 'FAIL') out.push(n);
|
|
@@ -1895,7 +1933,7 @@ export function runCheck(rigDir: string, outDir: string, rigc: RigcRunner, parts
|
|
|
1895
1933
|
if (inp.parts !== null) {
|
|
1896
1934
|
const composite = flatComposite(inp.parts, inp.partsDir, stillSet.background);
|
|
1897
1935
|
seam = seamFigures(composite, stillSet.frames[0].image, inp.stage, stillSet.viewport, stillSet.background);
|
|
1898
|
-
if (seamPairsAsked || seamFails(round3(seam.mean), seam.over40)) {
|
|
1936
|
+
if (seamPairsAsked || seamFails(round3(seam.mean), seam.over40, round6(stillSet.viewport.scale))) {
|
|
1899
1937
|
const placed = inp.parts.parts.map((p) => ({ name: p.name, from: p.from, x: p.x, y: p.y, image: readPng(join(inp.partsDir, `${p.name}.png`)) }));
|
|
1900
1938
|
const split = seamPairRows(seam, placed, inp.parts.rig_size, stillSet.viewport);
|
|
1901
1939
|
mkdirSync(seamDir, { recursive: true });
|
|
@@ -1955,6 +1993,7 @@ export function runCheck(rigDir: string, outDir: string, rigc: RigcRunner, parts
|
|
|
1955
1993
|
seam_mean: seam === null ? null : round3(seam.mean),
|
|
1956
1994
|
seam_px_over_40: seam === null ? null : seam.over40,
|
|
1957
1995
|
seam_px_over_80: seam === null ? null : seam.over80,
|
|
1996
|
+
seam_frame_scale: seam === null || seamViewport === null ? null : round6(seamViewport.scale),
|
|
1958
1997
|
...(seamPairs === null ? {} : { seam_pairs: seamPairs }),
|
|
1959
1998
|
BREATH_VISIBLE: breath,
|
|
1960
1999
|
BLINK_NO_HOLE: blink,
|
|
@@ -1978,13 +2017,13 @@ export function runCheck(rigDir: string, outDir: string, rigc: RigcRunner, parts
|
|
|
1978
2017
|
detail: `max |d| ${loop.max}/255, first at pixel ${loop.x},${loop.y} of ${loopSet.viewport.pixelWidth}x${loopSet.viewport.pixelHeight}; ${LOOP_MAX_BAR} is required — the idle's last key must equal its first`,
|
|
1979
2018
|
});
|
|
1980
2019
|
}
|
|
1981
|
-
if (figures.seam_mean !== null && figures.seam_px_over_40 !== null && seamFails(figures.seam_mean, figures.seam_px_over_40)) {
|
|
2020
|
+
if (figures.seam_mean !== null && figures.seam_px_over_40 !== null && figures.seam_frame_scale !== null && seamFails(figures.seam_mean, figures.seam_px_over_40, figures.seam_frame_scale)) {
|
|
1982
2021
|
const worst = seamPairs === null || seamPairs.length === 0 ? null : seamPairs[0];
|
|
1983
2022
|
barProblems.push({
|
|
1984
2023
|
code: 'CHECK_SEAM_WITHIN_BAR',
|
|
1985
2024
|
object: 'the setup-pose render vs the flat composite of parts/',
|
|
1986
2025
|
detail:
|
|
1987
|
-
`mean |d| ${figures.seam_mean}/255 and ${figures.seam_px_over_40} px over ${SEAM_PX_LEVEL} (${figures.seam_px_over_80} over ${SEAM_PX_LEVEL_HIGH}); mean <= ${SEAM_MEAN_BAR.toFixed(1)} and <= ${SEAM_PX_BAR} px over ${SEAM_PX_LEVEL} are required` +
|
|
2026
|
+
`mean |d| ${figures.seam_mean}/255 and ${figures.seam_px_over_40} frame px over ${SEAM_PX_LEVEL} (${figures.seam_px_over_80} over ${SEAM_PX_LEVEL_HIGH}) at frame scale ${figures.seam_frame_scale}, ${seamRigPxText(figures.seam_px_over_40, figures.seam_frame_scale)} rig px (the count / s²); mean <= ${SEAM_MEAN_BAR.toFixed(1)} and <= ${SEAM_PX_BAR} rig px over ${SEAM_PX_LEVEL} are required` +
|
|
1988
2027
|
(worst === null
|
|
1989
2028
|
? `; no pixel is over ${SEAM_PX_LEVEL}, so the mean alone fails and there is no pair to name`
|
|
1990
2029
|
: `; worst of ${(seamPairs as SeamPairRow[]).length} pair(s): ${seamPairText(worst)} — every pair is a \`seam pair\` line above and a row of check.json's seam_pairs`),
|