rig-parts 1.87.0 → 1.88.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/cli.ts +16 -12
- package/docs/AUTHORING.md +80 -2
- package/docs/STABILITY.md +35 -5
- package/package.json +1 -1
- package/src/assemble.ts +2 -10
- package/src/build.ts +12 -8
- package/src/check.ts +5 -4
- package/src/config.ts +14 -3
- package/src/rig.ts +17 -0
- package/src/seamrule.ts +11 -0
- package/src/setuppainting.ts +6 -1
package/cli.ts
CHANGED
|
@@ -555,7 +555,8 @@ usage:
|
|
|
555
555
|
[--page-edges pot|free] [--pack-shape rect|polygon]
|
|
556
556
|
[--requirements <file.json>] [--seam-pairs] [--keep-stills]
|
|
557
557
|
[--set <name> | --all-sets] [--environment <schedule.json>]
|
|
558
|
-
[--wind-band <spec.json>]
|
|
558
|
+
[--wind-band <spec.json>] [--json]
|
|
559
|
+
--json prints the completed check.json object only, with unchanged exit codes.
|
|
559
560
|
Build, gate, render and measure a rig through rig-c's CLI (the rigc at
|
|
560
561
|
node_modules/.bin/rigc, or on PATH). --rig holds rig.json and motion.json:
|
|
561
562
|
a rig spec, which rigc build compiles, not a compiled skeleton.json.
|
|
@@ -777,7 +778,7 @@ usage:
|
|
|
777
778
|
extends below a crop (ASSEMBLE_SINGLE_RUN_PLAN), and the config sets no
|
|
778
779
|
seethrough.head_box (ASSEMBLE_SINGLE_RUN_HEAD_BOX).
|
|
779
780
|
Writes nothing unless every check passed.
|
|
780
|
-
--seam
|
|
781
|
+
--seam overrides config.assemble.seam, otherwise ${DEFAULT_SEAM_RULE}. --project says where a layer takes
|
|
781
782
|
the painting's pixel: core (the reference's) erodes every layer's top-most
|
|
782
783
|
opaque area by 5x5 first, so a part a few pixels wide takes none; visible
|
|
783
784
|
erodes only along a rim where a later layer is in front. It defaults to
|
|
@@ -861,7 +862,7 @@ usage:
|
|
|
861
862
|
green build ends with the pack line beside the spineboy yardstick and the
|
|
862
863
|
three artifact paths — skeleton .json, .atlas and the packed page: the
|
|
863
864
|
packed atlas is the result, the loose parts are the intermediate it was
|
|
864
|
-
made from. --seam
|
|
865
|
+
made from. --seam overrides config.assemble.seam, otherwise ${DEFAULT_SEAM_RULE}; --project to
|
|
865
866
|
${DEFAULT_PROJECT_RULE} (both as for assemble), --page-edges to ${DEFAULT_PAGE_EDGES} and
|
|
866
867
|
--pack-shape to ${DEFAULT_PACK_SHAPE} (as for check; both packed builds, rig's gate
|
|
867
868
|
and check's artifact, take them). --requirements is forwarded to check
|
|
@@ -1095,7 +1096,7 @@ const TAKES = {
|
|
|
1095
1096
|
rig: { valued: ['--config', '--parts', '--out', '--idle-keys', '--page-edges', '--pack-shape'], switches: [] },
|
|
1096
1097
|
propose: { valued: ['--parts', '--source', '--out', '--from-config', '--compare', '--full', '--head', '--canvas', '--keypoints', '--person', '--wrist-snap', '--grid-rule', '--cloth', '--write-config', '--from'], switches: ['--head-box', '--no-back-hair', '--only-missing'] },
|
|
1097
1098
|
compare: { valued: ['--left', '--right', '--map'], switches: [] },
|
|
1098
|
-
check: { valued: ['--rig', '--out', '--parts', '--source', '--page-edges', '--pack-shape', '--requirements', '--set', '--environment', '--wind-band'], switches: ['--seam-pairs', '--keep-stills', '--all-sets', '--no-extremes'] },
|
|
1099
|
+
check: { valued: ['--rig', '--out', '--parts', '--source', '--page-edges', '--pack-shape', '--requirements', '--set', '--environment', '--wind-band'], switches: ['--seam-pairs', '--keep-stills', '--all-sets', '--no-extremes', '--json'] },
|
|
1099
1100
|
loop: { valued: ['--frames', '--out'], switches: ['--palette'] },
|
|
1100
1101
|
assemble: { valued: ['--source', '--full', '--head', '--config', '--out', '--seam', '--project', '--alt', '--write-config', '--requirements'], switches: ['--propose-plan'] },
|
|
1101
1102
|
'comfy seethrough': { valued: ['--image', '--out', '--resolution', '--steps', '--seed', '--prefix', '--host', '--wait', '--timeout', '--poll', '--config', '--depth-run'], switches: ['--offload', '--no-offload', '--lama', '--nf4'] },
|
|
@@ -1338,6 +1339,8 @@ function flags(args: string[], command: Command, required: readonly string[]): M
|
|
|
1338
1339
|
|
|
1339
1340
|
function cmdCheck(args: string[]): number {
|
|
1340
1341
|
// The switches check takes (issues #202, #209, #454): they carry no value, so they are taken out before the valued flags are read.
|
|
1342
|
+
const json = args.filter((a) => a === '--json').length;
|
|
1343
|
+
if (json > 1) return usage('--json is given twice');
|
|
1341
1344
|
const seamPairs = args.filter((a) => a === '--seam-pairs').length;
|
|
1342
1345
|
if (seamPairs > 1) return usage('--seam-pairs is given twice');
|
|
1343
1346
|
const keepStills = args.filter((a) => a === '--keep-stills').length;
|
|
@@ -1347,7 +1350,7 @@ function cmdCheck(args: string[]): number {
|
|
|
1347
1350
|
const allSets = args.filter((a) => a === '--all-sets').length;
|
|
1348
1351
|
if (allSets > 1) return usage('--all-sets is given twice');
|
|
1349
1352
|
const f = flags(
|
|
1350
|
-
args.filter((a) => a !== '--seam-pairs' && a !== '--keep-stills' && a !== '--all-sets' && a !== '--no-extremes'),
|
|
1353
|
+
args.filter((a) => a !== '--seam-pairs' && a !== '--keep-stills' && a !== '--all-sets' && a !== '--no-extremes' && a !== '--json'),
|
|
1351
1354
|
'check',
|
|
1352
1355
|
['--rig', '--out'],
|
|
1353
1356
|
);
|
|
@@ -1365,8 +1368,9 @@ function cmdCheck(args: string[]): number {
|
|
|
1365
1368
|
{ rig, parts: f.get('--parts'), source: f.get('--source'), out, pageEdges: edges, packShape: shape, requirements: f.get('--requirements'), seamPairs: seamPairs === 1, keepStills: keepStills === 1, noExtremes: noExtremes === 1, ...(f.has('--set') ? { set: f.get('--set') as string } : {}), allSets: allSets === 1, ...(f.has('--environment') ? { environment: f.get('--environment') as string } : {}), ...(f.has('--wind-band') ? { windBand: f.get('--wind-band') as string } : {}) },
|
|
1366
1369
|
rigcRunner(bin),
|
|
1367
1370
|
bin,
|
|
1368
|
-
console.log,
|
|
1371
|
+
json === 1 ? () => {} : console.log,
|
|
1369
1372
|
);
|
|
1373
|
+
if (json === 1) process.stdout.write(`${JSON.stringify(r.figures, null, 1)}\n`);
|
|
1370
1374
|
return r.figures.PASS ? EXIT_OK : EXIT_REFUSED;
|
|
1371
1375
|
} catch (err) {
|
|
1372
1376
|
return printRefusal(err);
|
|
@@ -1726,8 +1730,8 @@ function cmdAssemble(args: string[]): number {
|
|
|
1726
1730
|
if (propose && (flags.has('--out') || flags.has('--seam') || flags.has('--project') || alts.length > 0)) return usage('--propose-plan prints to the console; it takes none of --out, --seam, --project, --alt');
|
|
1727
1731
|
if (!propose && flags.has('--write-config')) return usage('--write-config belongs to assemble --propose-plan');
|
|
1728
1732
|
if (!propose && !flags.has('--out')) return usage('assemble needs --out <dir>');
|
|
1729
|
-
const seam = flags.get('--seam')
|
|
1730
|
-
if (!(SEAM_RULES as readonly string[]).includes(seam)) return usage(`--seam ${seam}; one of ${SEAM_RULES.join(', ')} is required`);
|
|
1733
|
+
const seam = flags.get('--seam');
|
|
1734
|
+
if (seam !== undefined && !(SEAM_RULES as readonly string[]).includes(seam)) return usage(`--seam ${seam}; one of ${SEAM_RULES.join(', ')} is required`);
|
|
1731
1735
|
const project = flags.get('--project') ?? DEFAULT_PROJECT_RULE;
|
|
1732
1736
|
if (!(PROJECT_RULES as readonly string[]).includes(project)) return usage(`--project ${project}; one of ${PROJECT_RULES.join(', ')} is required`);
|
|
1733
1737
|
const [source, full, config] = ['--source', '--full', '--config'].map((f) => flags.get(f) as string);
|
|
@@ -1755,7 +1759,7 @@ function cmdAssemble(args: string[]): number {
|
|
|
1755
1759
|
}
|
|
1756
1760
|
const out = flags.get('--out') as string;
|
|
1757
1761
|
assembleStage(
|
|
1758
|
-
{ source, full, head, config, ...(flags.has('--requirements') ? { requirements: flags.get('--requirements') as string } : {}), seam: seam as SeamRule, project: project as ProjectRule, ...(alts.length === 0 ? {} : { alts }) },
|
|
1762
|
+
{ source, full, head, config, ...(flags.has('--requirements') ? { requirements: flags.get('--requirements') as string } : {}), ...(seam === undefined ? {} : { seam: seam as SeamRule }), project: project as ProjectRule, ...(alts.length === 0 ? {} : { alts }) },
|
|
1759
1763
|
{ 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) },
|
|
1760
1764
|
console.log,
|
|
1761
1765
|
);
|
|
@@ -1946,8 +1950,8 @@ function cmdBuild(args: string[]): number {
|
|
|
1946
1950
|
// --head is optional (issue #359), as for assemble.
|
|
1947
1951
|
for (const f of ['--config', '--source', '--full', '--out']) if (!flags.has(f)) return usage(`build needs ${f}`);
|
|
1948
1952
|
if (on.has('--all-sets') && flags.has('--set')) return usage('--set and --all-sets are two modes; give one');
|
|
1949
|
-
const seam = flags.get('--seam')
|
|
1950
|
-
if (!(SEAM_RULES as readonly string[]).includes(seam)) return usage(`--seam ${seam}; one of ${SEAM_RULES.join(', ')} is required`);
|
|
1953
|
+
const seam = flags.get('--seam');
|
|
1954
|
+
if (seam !== undefined && !(SEAM_RULES as readonly string[]).includes(seam)) return usage(`--seam ${seam}; one of ${SEAM_RULES.join(', ')} is required`);
|
|
1951
1955
|
const project = flags.get('--project') ?? DEFAULT_PROJECT_RULE;
|
|
1952
1956
|
if (!(PROJECT_RULES as readonly string[]).includes(project)) return usage(`--project ${project}; one of ${PROJECT_RULES.join(', ')} is required`);
|
|
1953
1957
|
const edges = pageEdgesOf(flags.get('--page-edges'));
|
|
@@ -1971,7 +1975,7 @@ function cmdBuild(args: string[]): number {
|
|
|
1971
1975
|
const scratch = mkdtempSync(join(tmpdir(), 'rig-parts-build-'));
|
|
1972
1976
|
try {
|
|
1973
1977
|
const r = build(
|
|
1974
|
-
{ config, source, full, head, out, seam: seam as SeamRule, project: project as ProjectRule, loop, pageEdges: edges, packShape: shape, ...(flags.has('--requirements') ? { requirements: flags.get('--requirements') as string } : {}), idleKeys: keys, ...(leadText === undefined ? {} : { leadIn: Number(leadText) }), seamPairs: on.has('--seam-pairs'), keepStills: on.has('--keep-stills'), noExtremes: on.has('--no-extremes'), ...(alts.length === 0 ? {} : { alts }), ...(flags.has('--set') ? { set: flags.get('--set') as string } : {}), allSets: on.has('--all-sets'), ...(flags.has('--environment') ? { environment: flags.get('--environment') as string } : {}), ...(flags.has('--wind-band') ? { windBand: flags.get('--wind-band') as string } : {}) },
|
|
1978
|
+
{ config, source, full, head, out, ...(seam === undefined ? {} : { seam: seam as SeamRule }), project: project as ProjectRule, loop, pageEdges: edges, packShape: shape, ...(flags.has('--requirements') ? { requirements: flags.get('--requirements') as string } : {}), idleKeys: keys, ...(leadText === undefined ? {} : { leadIn: Number(leadText) }), seamPairs: on.has('--seam-pairs'), keepStills: on.has('--keep-stills'), noExtremes: on.has('--no-extremes'), ...(alts.length === 0 ? {} : { alts }), ...(flags.has('--set') ? { set: flags.get('--set') as string } : {}), allSets: on.has('--all-sets'), ...(flags.has('--environment') ? { environment: flags.get('--environment') as string } : {}), ...(flags.has('--wind-band') ? { windBand: flags.get('--wind-band') as string } : {}) },
|
|
1975
1979
|
{ rig: rigcRunner(bin), check: rigcRunner(bin), checkBin: bin, scratch: join(scratch, 'rig-gate'), stages: {
|
|
1976
1980
|
assemble: (input, outputs) => runBuildProcess({ stage: 'assemble', bin, input, outputs }, join(scratch, 'workers')),
|
|
1977
1981
|
rig: (input, _rigc, gateScratch) => runBuildProcess({ stage: 'rig', bin, input, scratch: gateScratch }, join(scratch, 'workers')),
|
package/docs/AUTHORING.md
CHANGED
|
@@ -147,6 +147,7 @@ read by no CPU stage).
|
|
|
147
147
|
| `seethrough.steps`, `.seed`, `.offload` | `comfy seethrough --config` | run inputs when `--config <config.json>` is given; explicit CLI flags override them, including `--no-offload` for false |
|
|
148
148
|
| `seethrough.head_box` | inputs (cuts the head image there; optional at the first call); assemble — where the head run sits on the painting | **proposed** by `propose --head-box` from the full run, square, source pixels, shifted inside the painting when needed; the head run must then be fed exactly that crop. With one run (no `--head`, issue #359: an artist's own file, §4 *For artists*) it is left out — there is no crop to place — and a config that sets it is refused (`ASSEMBLE_SINGLE_RUN_HEAD_BOX`) |
|
|
149
149
|
| `seethrough.arm_split` | assemble, `assemble --propose-plan` — separates a single full-run `handwear-r` or `handwear-l` layer that holds both overlapping arms into both arm tags | **authored**, optional (issue #439): `{x?: number, line?: [[x, y], …], order: ["handwear-r", "handwear-l"] \| ["handwear-l", "handwear-r"]}` in `seethrough.resolution` full-run pixels. Exactly one of `x` (image column, `0 <= x <= resolution`) or `line` (a polyline of two or more `[x, y]` points in `[0, resolution]`, interpolated in x along y and clamped above/below its endpoints) is required (`CONFIG_FIELD_PRESENT`, `CONFIG_FIELD_TYPE`), and `order` is `[back_tag, front_tag]`, the draw order of the two separated arm layers (`CONFIG_FIELD_PRESENT`, `CONFIG_FIELD_TYPE`). Pixels at `< splitX(y)` go to `handwear-r` (image left, the subject's right arm) and pixels at `>= splitX(y)` go to `handwear-l` (image right, the subject's left arm). A split line that cuts a horizontal opaque run of the arm layer into more than two segments is refused (`ASSEMBLE_ARM_SPLIT_CUTS_ARM`). Without the declaration, when one arm layer's 8-connected component reaches both wrists under `propose --keypoints`, `propose` emits an `overlapping-arms` `LINT` naming the part, both wrists and `seethrough.arm_split`; absent, no byte of any output moves |
|
|
150
|
+
| `assemble.seam` | assemble, build | **authored**, optional `"near-white"` or `"silhouette"` (issue #905). An explicit `--seam` overrides the config; otherwise this field overrides the existing `near-white` default. An invalid value is `CONFIG_FIELD_TYPE` at `config.assemble.seam`, including when a CLI override is given. Absent, existing build bytes are unchanged. |
|
|
150
151
|
| `assemble.rig_scale` | assemble — the rig is `trunc(w × rig_scale)` x `trunc(h × rig_scale)`, and every rig-px value after it (`bones`, `meshes`, `motion`, `patches`, `cuts`) is in that rig's pixels; `check --source` (`CHECK_SOURCE_SIZE`) and `compose` (`SCENE_RIG_SCALE_AGREES`) read it back off `parts.json` | authored; rig pixels per source pixel. 0.5 in both examples (a 1664x2432 painting makes an 832x1216 rig). **Keep the figure about 1,100 to 1,200 rig px tall** — the `H` of `propose`'s `mesh spacing:` note, the height of the parts' boxes together — so for another painting, about 1,150 over the figure's height in painting px: that is the size the proposer's spacing divisor was calibrated at (H 1106–1185, the `mesh spacing` note says so) and the size of the two examples `check`'s bars are derived from, and the proposer and `check` also hold fixed rig-px values that do not scale with it (the fringe's 10 px step, the trunk segments' 18, 20, 30 and 100 px, the 15 px wrist tolerance, the 10-row pendant floor, the seam bar's 50 rig px over 40). What else it trades, measured on `demo` with the README loop from a fresh config at 0.25, 0.5 and 1 (issue #345, rig-c 2.47.1): the same plan and the same ten chains; every bar but the one the uncorrected `demo` always fails (`bottomwear` `TIP_OVER_ROOT`, the README loop's `RL01`) passing at all three; the atlas page 418x606, 832x1205 and 1825x2047; the figure's mesh spacing 16, 32 and 64, so 830, 868 and 932 triangles; the breath 2.5 % of a torso that scales with it; and the proposed bones, scaled to 0.5, off the 0.5 proposal's by up to 38 px at 0.25 and 22.5 px at 1 (the hairpin's origin; the skirt roots 20 and 10.5) — the fixed-px values above. `propose` records it on the proposal as `proposed_rig_scale` (issue #354), and changing `assemble.rig_scale` after the rig sections are written is refused (`CONFIG_PROPOSED_RIG_SCALE`): without that refusal `demo`'s tracked config built at 0.25 gated green and `check` failed only `TIP_OVER_ROOT` on both sleeves (`propose --from-config` named 54 `LINT` lines, links off the art) — re-run `assemble` and `propose` at the new scale. Absent: `CONFIG_FIELD_PRESENT`; 0, negative or not a number: `CONFIG_FIELD_TYPE`; a rig under 1x1: `ASSEMBLE_RIG_SIZE` |
|
|
151
152
|
| `assemble.plan` | assemble; also the list every `meshes`/`regions` key must come from | **proposed** by `assemble --propose-plan`. `[part name, "full" \| "head", See-through tag]`, in draw order back to front. Names are free; roles come from the tag. A layer the author made and added to a run takes `author:<label>` in the tag's place, and has no role (§5, *A layer the author made*). When `generation.control.skeleton` is present and a full-run `handwear-r` or `handwear-l` layer merges sleeve, bare hand and overgarment drape into one layer — a skin-coloured hand cluster (`>= 40` run px, `GHOST_MIN_AREA`, measured `516..4,787` run px across the 9-standee corpus) lies within `0.35` forearm lengths of `<s>_wrist` and non-skin pixels reach more than `1.0x` the forearm length (`<s>_elbow -> <s>_wrist`) below `<s>_wrist` (issue #490; measured `-0.15x..0.45x` on the 6 clean standees vs `1.25x..2.02x` on the 3 merged standees, placing `1.0x` inside the `0.45x..1.25x` gap) — the layer is split into `drape-<s>`, `handwear-<s>` and `hand-<s>` (back to front) when the Euclidean RGB distance between the sleeve median colour above `<s>_wrist` and the drape median colour below `<s>_wrist` is at least `80` (`ARM_DRAPE_COLOR_DISTANCE`, measured `103.6..236.0` on the two distinct-colour merged standees vs `20.8..28.2` on the same-colour merged standee, placing `80` between `28.2` and `103.6`), or refused with `SEETHROUGH_ARM_RUN_MERGED` when the distance is under `80` |
|
|
152
153
|
| `assemble.extend_below_crop` | assemble | **proposed** with the plan: `{part, run, tag}` — a head-run part that reaches the bottom of the head crop is continued from a full-run layer, whole connected components |
|
|
@@ -5233,6 +5234,7 @@ and stay absent).
|
|
|
5233
5234
|
| <a id="RIG_TURN_SURFACE_MESH_BINDINGS"></a>`RIG_TURN_SURFACE_MESH_BINDINGS` | an authored surface mesh has unresolved/nonclosing weights or shares a frame moved by a unit/rear turn carrier; declared stop draw aliases resolve to their links for setup sampling | provide weighted bindings closing at 1 in the authored frame; `<link>_d` resolves only for links declared in `config.stops`; use independent chains for parts with different roles |
|
|
5234
5235
|
| <a id="RIG_TURN_SURFACE_MESH_ATTACHMENT"></a>`RIG_TURN_SURFACE_MESH_ATTACHMENT` | a surface mesh has an alternate drawing without its own registered depth sampling frame | use the base attachment; explicit islands retain their own slot, attachment and bindings |
|
|
5235
5236
|
| <a id="RIG_TURN_BODY_UNCOVERED"></a>`RIG_TURN_BODY_UNCOVERED` | **Warning:** a shown part without a body role overlaps a moved `turn.body` surface at setup alpha >= 128 and has different emitted endpoint displacement | declare the intended body role and inspect both endpoints; no role or depth is inferred |
|
|
5237
|
+
| <a id="RIG_TURN_HEAD_ROLL"></a>`RIG_TURN_HEAD_ROLL` | **Warning:** a declared head turn has an absolute authored-eye-origin roll above the empirical limit displayed as 18.485385 degrees; names the face/eye parts, both eye bones, signed roll and limit | inspect the neck at both extremes; this proxy does not detect the painting independently, correct tilt or change emitted bytes; see [Head roll before a declared turn](#head-roll-before-a-declared-turn) |
|
|
5236
5238
|
| <a id="RIG_TURN_SURFACE_DEPTH_REQUIRED"></a>`RIG_TURN_SURFACE_DEPTH_REQUIRED` | a part with role `"surface"` in `config.turn.roles` or `config.turn.body.roles` carries no depth sheet in `parts.json` | run `assemble` with `config.depth` or provide `<part>_depth.png` in `parts.json` |
|
|
5237
5239
|
| <a id="RIG_TURN_GRID_BUDGET"></a>`RIG_TURN_GRID_BUDGET` | a `"surface"` grid exceeds 2,400 triangles on one slot or 4,000 triangles across all `"surface"` slots | lower `config.turn.grid` / `config.turn.body.grid` `[cols, rows]` within the per-slot and total budgets |
|
|
5238
5240
|
| <a id="RIG_TURN_HEAD_BONE"></a>`RIG_TURN_HEAD_BONE` | `buildRig` found no `"head"` (or body turn root) bone when emitting 2.5D turn carriers | declare `"head"` (or the body turn root bone) in `config.bones` |
|
|
@@ -5663,7 +5665,7 @@ Under `--source <painting.png>`, a second reported line, also never a FAIL
|
|
|
5663
5665
|
| line | reports | bar | `reference-demo` | `reference-sample` | SKIP when |
|
|
5664
5666
|
| --- | --- | --- | --- | --- | --- |
|
|
5665
5667
|
| <a id="SETUP_POSE_VS_SOURCE"></a>`SETUP_POSE_VS_SOURCE` | the setup pose against the painting, by `assemble`'s own `measureRecomposite` over the stage's pixels — `mean_abs` and `within_share` (mean channel, within 8), `error_px` (max channel over 40), `uncovered_error_px` (of those, where the pose has alpha 128 or less), `hole_count` and the largest hole's box — with `painting_px`, `stage_px`, `render_px` and `render_scale` beside them. The painting is resampled onto the stage as assemble resamples it (lanczos3, alpha dropped). rigc renders onto an opaque grey, so the pose over white and its alpha are read off a second still of the same pose with every slot tinted black; both are carried onto the stage with the seam's map turned round (bilinear) | none — status `REPORTED`; assemble applies no bar to the figures it measures, so none is derived here | 3.146 / 93.43 % / 12,399 / 1,732, 518 holes, the largest 61 px at 351,157 6x20 (assemble's recomposite: 2.391 / 95.81 % / 11,050 / 1,564, 453) | 1.095 / 97.30 % / 5,348 / 1,239, 313 holes, the largest 88 px at 479,416 6x29 (assemble's: 0.835 / 97.95 % / 4,512 / 1,185, 259) | not written without `--source` |
|
|
5666
|
-
| <a id="SETUP_MATCHES_PAINTING"></a>`SETUP_MATCHES_PAINTING` | declared region count, native box, limit and actual top contributor rows | PASS/FAIL against authored limits; no limit is SKIP; undeclared regions are not judged |
|
|
5668
|
+
| <a id="SETUP_MATCHES_PAINTING"></a>`SETUP_MATCHES_PAINTING` | declared region count, native box, limit and actual top contributor rows | PASS/FAIL against authored limits; no limit is SKIP; with `--source`, no declaration is SKIP with its reason and declaration remedy; undeclared regions are not judged |
|
|
5667
5669
|
|
|
5668
5670
|
The figures are assemble's by construction, not a second definition beside them;
|
|
5669
5671
|
what differs is what they are taken of — the pose rigc draws, not the flat stack —
|
|
@@ -6229,7 +6231,22 @@ maximum absolute RGB-channel difference is **strictly greater** than that
|
|
|
6229
6231
|
value. The region passes when its total count is at most its authored
|
|
6230
6232
|
nonnegative integer `max_pixels`. Without that limit the region reports
|
|
6231
6233
|
**SKIP**, including a zero-count region. An undeclared region has no verdict.
|
|
6232
|
-
|
|
6234
|
+
With `--source` but no `setup_painting` declaration, `SETUP_MATCHES_PAINTING`
|
|
6235
|
+
is explicitly **SKIP** in the console, `check.json`, the report's skipped-bar
|
|
6236
|
+
list and `check --json`. Its reason says the comparison was not measured and
|
|
6237
|
+
asks for `--requirements <file>` declaring `setup_painting.rgb_tolerance` and
|
|
6238
|
+
named `regions` with `box` and authored `max_pixels` limits. The SKIP changes
|
|
6239
|
+
no exit code and invents no metric, region or limit. This applies both without
|
|
6240
|
+
a requirements file and with one that omits the key. Without `--source` and
|
|
6241
|
+
without the declaration, the line stays absent. Declared observations retain
|
|
6242
|
+
their output bytes and renderer calls.
|
|
6243
|
+
|
|
6244
|
+
`check --json` prints only the completed `check.json` object to stdout, with
|
|
6245
|
+
the same bytes as the file and the same exit code as ordinary `check` (0 or 1).
|
|
6246
|
+
Early input refusals and usage errors retain their named text and exits (1 or 2);
|
|
6247
|
+
they have no completed check object. `build --json` is refused by name: run
|
|
6248
|
+
`check --rig <out>/rig --parts <out> --out <dir> --json` after the build to read
|
|
6249
|
+
the completed check object.
|
|
6233
6250
|
|
|
6234
6251
|
The observation is the actual renderer's setup pose, without animation or
|
|
6235
6252
|
physics advancement, from the already gated setup candidate. Its viewport is
|
|
@@ -6566,6 +6583,47 @@ art. An authored `parts.json` depth reference remains an active standalone
|
|
|
6566
6583
|
guards still apply. With binding inactive there is no added reference, file or
|
|
6567
6584
|
legacy-byte change. #773 adds no image generation, geometry or motion model.
|
|
6568
6585
|
|
|
6586
|
+
### Head roll before a declared turn
|
|
6587
|
+
|
|
6588
|
+
`rig` and `build` report `WARN RIG_TURN_HEAD_ROLL` when a declared head
|
|
6589
|
+
turn (`turn.depths` or `turn.roles`, including the depth-sampled producer)
|
|
6590
|
+
has an absolute setup eye-line roll above 18.485385 degrees (six-place display).
|
|
6591
|
+
The line names the face part (or the eye parts without a face), the signed
|
|
6592
|
+
roll, its absolute value, the limit, and both eye bones and parts. It reads
|
|
6593
|
+
the existing `eyeLineDegrees` on the authored `eye_r` and `eye_l` origins,
|
|
6594
|
+
image-left to image-right in Spine degrees, counter-clockwise; it does not
|
|
6595
|
+
detect the painting's tilt independently of those authored origins, estimate
|
|
6596
|
+
tilt from a bounding box, or invent a reading without both eye parts
|
|
6597
|
+
and bones or when their origins share a column.
|
|
6598
|
+
|
|
6599
|
+
The limit is the midpoint between the maximum absolute roll of the measured
|
|
6600
|
+
public controls, 4.864514 degrees, and the tilted `three-quarter`
|
|
6601
|
+
input from issue #900, 32.106256 degrees. The comparison uses the unrounded
|
|
6602
|
+
midpoint. The controls are the five
|
|
6603
|
+
parts examples plus the published dancer config and head-turn demo's compiled
|
|
6604
|
+
setup eye origins. The named quiet controls read 3.239700 degrees
|
|
6605
|
+
(`reference-demo`), -1.941486 degrees (`dancer`), and
|
|
6606
|
+
0.520856 degrees (head-turn demo). This is an empirical separation
|
|
6607
|
+
of these inputs, not an anatomical limit or a visual-quality verdict.
|
|
6608
|
+
|
|
6609
|
+
The parts configs were read at
|
|
6610
|
+
[`38e5f574`](https://github.com/rig-c/parts/tree/38e5f574a88b2e6753a982b0ee0952dbf273a12e/examples),
|
|
6611
|
+
the [dancer config](https://github.com/rig-c/examples/blob/564285414fccf7b47df32501c0b3c2968db77691/dancer/config.json)
|
|
6612
|
+
at `56428541`, and the
|
|
6613
|
+
[head-turn model](https://github.com/rig-c/rig-c.github.io/blob/989cebbd6733a58142923cec96fe2333da08194a/demos/head-turn/bundle/skeleton.model.json)
|
|
6614
|
+
at `989cebbd`. The [issue #900](https://github.com/rig-c/parts/issues/900)
|
|
6615
|
+
bootstrap used public `three-quarter` inputs at examples commit
|
|
6616
|
+
`ba99e403d4058fc1cfe40699bdcde8aaf00a90c0`; its authored crop-pixel eye origins
|
|
6617
|
+
are `[236, 136]` and `[287, 104]`. The compiled demo is a setup metric control,
|
|
6618
|
+
not a rebuild of its unavailable parts config.
|
|
6619
|
+
|
|
6620
|
+
An already rolled head is turned about upright yaw/pitch axes; inspect its
|
|
6621
|
+
neck at both extremes before accepting that motion. This warning does not
|
|
6622
|
+
refuse the build, correct the tilt, add a roll axis, or change any pose or
|
|
6623
|
+
emitted file. With no head turn, including a body-only turn, it adds no warning.
|
|
6624
|
+
With an unreadable eye line the existing face-axis report retains its reason;
|
|
6625
|
+
silence is not a measured zero. The warning lives only in the stage report.
|
|
6626
|
+
|
|
6569
6627
|
### Missing body-turn coverage
|
|
6570
6628
|
|
|
6571
6629
|
`RIG_TURN_BODY_UNCOVERED` warns when a shown part without a
|
|
@@ -6682,6 +6740,26 @@ The trial's role relief/width ratios (face 0.1264803, front hair 0.1018772,
|
|
|
6682
6740
|
back hair 0.0857722, ornament 0.0375868, garment 0.0990473) came from one
|
|
6683
6741
|
head-turn demo's p5..p95 depth and part widths. They are measured starting
|
|
6684
6742
|
points for declared proportions, not anatomical laws or default calibration.
|
|
6743
|
+
For a head-referenced calibration, declare
|
|
6744
|
+
`relief = referenceRoleRelief * targetFaceWidth / referenceFaceWidth`, using
|
|
6745
|
+
the assembled target face width and the same declared reference head measure
|
|
6746
|
+
for every role. This keeps head relief on one scale when a hair silhouette
|
|
6747
|
+
is much wider than the face. In contrast, `roleRatio * targetPartWidth`
|
|
6748
|
+
increases relief with that silhouette while the group's `zScale` stays fixed.
|
|
6749
|
+
An ornament narrower than its reference can gain relief under the head rule;
|
|
6750
|
+
inspect it as well as the widest part. The command derives neither rule:
|
|
6751
|
+
write the resulting rig-pixel `relief` explicitly in each recipe entry.
|
|
6752
|
+
|
|
6753
|
+
Compare calibrations on identical prepared inputs and numeric maps, with the
|
|
6754
|
+
same medians, group pivot and `zScale`; only the declared relief changes.
|
|
6755
|
+
Read each named report row's `relief`, `zScale` and `clipFraction` before
|
|
6756
|
+
judging its render. The clipping denominator is every original part pixel
|
|
6757
|
+
with alpha >=128, including boundary support, never the padded input area.
|
|
6758
|
+
A low clipped fraction does not establish shape quality. Inspect setup,
|
|
6759
|
+
both yaw endpoints and their difference at native scale, then enlarge the
|
|
6760
|
+
same pixels with nearest-neighbour sampling. Neither clipping nor agreement
|
|
6761
|
+
with a reference map substitutes for that inspection.
|
|
6762
|
+
|
|
6685
6763
|
The owner's preference for per-part volume is subjective provenance, not an
|
|
6686
6764
|
automated quality conclusion. Model licence posture remains the owner's choice.
|
|
6687
6765
|
|
package/docs/STABILITY.md
CHANGED
|
@@ -62,7 +62,7 @@ defaults are the help's too, and none is guessed from the art.
|
|
|
62
62
|
| `propose` | `--parts`, `--source`, `--out`, `--full`, `--head`, `--write-config` together: the [neck draft](AUTHORING.md#a-neck-that-moves-on-its-own) for an existing config with `turn.body` and one neck plan row; reassembles under `<out>/rig`, preserves the sampled turn and drafts a rigid neck unit (issue #826) |
|
|
63
63
|
| `compare` | `--left`, `--right`; optional `--map` |
|
|
64
64
|
| `rig` | `--config`, `--parts`, `--out`; optional `--idle-keys` (ctl, direct), `--page-edges` (pot, free), `--pack-shape` (rect, polygon) |
|
|
65
|
-
| `check` | `--rig`, `--out`; optional `--parts`, `--source`, `--page-edges`, `--pack-shape`, `--requirements`, `--seam-pairs`, `--keep-stills`, `--no-extremes` (issue #829), `--set` or `--all-sets` (issue #454), `--environment` (issue #538, additive), `--wind-band` (issue #590, additive) |
|
|
65
|
+
| `check` | `--rig`, `--out`; optional `--parts`, `--source`, `--page-edges`, `--pack-shape`, `--requirements`, `--seam-pairs`, `--keep-stills`, `--no-extremes` (issue #829), `--set` or `--all-sets` (issue #454), `--environment` (issue #538, additive), `--wind-band` (issue #590, additive), `--json` (issue #904) |
|
|
66
66
|
| `loop` | `--frames`, `--out`; optional `--palette` |
|
|
67
67
|
| `build` | `--config`, `--source`, `--full`, `--out`; optional `--head` (as for `assemble`), `--seam`, `--project`, `--page-edges`, `--pack-shape`, `--loop`, `--lead-in`, `--requirements`, `--idle-keys`, `--seam-pairs`, `--keep-stills`, `--no-extremes` (issue #829), `--alt` (as for `assemble`, issue #454), `--set` or `--all-sets` (as for `check`, issue #454), `--environment` (as for `check`, issue #538, additive), `--wind-band` (as for `check`, issue #590, additive) |
|
|
68
68
|
| `compose` | `--scene`, `--out`; optional `--requirements`, `--accept` |
|
|
@@ -116,6 +116,18 @@ rules that make the contract:
|
|
|
116
116
|
- **`turn`** (issue #251) adds a control bone `turn` under `root` that nothing
|
|
117
117
|
keys, and carries each part in `depths` by `(pivot − depth) / (pivot − nearest
|
|
118
118
|
depth)` of its x. Without the field no byte of any output moves.
|
|
119
|
+
A declared head turn (`depths` or `roles`) reports `RIG_TURN_HEAD_ROLL`
|
|
120
|
+
when the absolute setup eye-line roll exceeds the limit displayed as
|
|
121
|
+
18.485385 degrees
|
|
122
|
+
(issue #903). This empirical limit is the midpoint of the measured public
|
|
123
|
+
control maximum, 4.864514 degrees, and `three-quarter`,
|
|
124
|
+
32.106256 degrees (the comparison uses the unrounded midpoint). The warning
|
|
125
|
+
names the face/eye parts, the eye
|
|
126
|
+
bones, signed roll and limit. It is report-only: no refusal, field, pose or
|
|
127
|
+
emitted-byte change. No head turn, a body-only turn, or an unreadable eye
|
|
128
|
+
line adds no warning; unreadable retains the existing face-axis reason.
|
|
129
|
+
See [Head roll before a declared turn](AUTHORING.md#head-roll-before-a-declared-turn)
|
|
130
|
+
for the measurement scope and its limits.
|
|
119
131
|
- **`scene`** (issue #242) declares the painting a scene — the figure and what
|
|
120
132
|
is round it — and takes no field: `{}`. With it `assemble` reads the
|
|
121
133
|
recomposite inside the figure (the union of the full run's layers at alpha
|
|
@@ -135,7 +147,7 @@ part name, `[i]` an entry of a list):
|
|
|
135
147
|
- **generation.control.skeleton**, the pose file it names when it is no built-in — required: `width`, `height`, `points`
|
|
136
148
|
- **seethrough** — required: `resolution`, `steps`, `seed`, `offload`; optional: `head_box`, `arm_split`
|
|
137
149
|
- **seethrough.arm_split** — required: `order`, and one of `x` or `line` (issue #439)
|
|
138
|
-
- **assemble** — required: `rig_scale`, `plan` (from plain `assemble` on); optional: `extend_below_crop`, `patches`, `cuts`, `alternates`, `head_face_underfill`, `yield_refused`, `keep_under_occluders`, `extend_painting`, `take`
|
|
150
|
+
- **assemble** — required: `rig_scale`, `plan` (from plain `assemble` on); optional: `seam`, `extend_below_crop`, `patches`, `cuts`, `alternates`, `head_face_underfill`, `yield_refused`, `keep_under_occluders`, `extend_painting`, `take`
|
|
139
151
|
- **assemble.alternates[i]** — required: `name`; optional: `source`, `full`, `head`, `parts`, `hides`, `head_box`, `cuts`, `skeleton`, `anchor`, `base_anchor`, `drawn`, `tips`, `front`, `layers`, `focus` (issues #454, #465, #543, #652, #664, #666, #719, #780). `focus` is an authored finite `[x, y]` in painting pixels, within its canvas (edges included), refused as CONFIG_SET_FOCUS before assemble output when invalid; only when present, `parts.json` records it with focus_source_size and `sets.json` emits it in setup-world coordinates through the actual rig resize and `src/coords.ts`. Absent focus emits no field or inferred value; changing/removing it requires reassembly
|
|
140
152
|
- **assemble.extend_below_crop[i]** — required: `part`, `run`, `tag`
|
|
141
153
|
- **assemble.extend_painting[i]** — required: `part`, `band`, `max_distance`; optional: `min_held_alpha`, `order` (issues #848, #854, #855; explicit final layer-derived part, half-open rig band and positive four-neighbour path bound; base painting only). The optional integer floor 1–255 admits exact original alpha-over coverage strictly below it; absent or 1 blocks every nonzero holder. Only selected target samples below the floor may take painting RGBA; target samples at/above the floor and every non-target holder remain exact. By default overlapping reachable claims refuse. With `order` on every entry (unique nonnegative safe integers), lower values claim first and their gains block later candidates and paths; array order never breaks ties or supplies missing precedence
|
|
@@ -206,6 +218,11 @@ part name, `[i]` an entry of a list):
|
|
|
206
218
|
- **waist** — required: `top`, `bottom`, `upper`, `lower`, `bone`, `constraint` (#650; absent keeps the ordinary bindings and graph)
|
|
207
219
|
<!-- /stable:config -->
|
|
208
220
|
|
|
221
|
+
`assemble.seam` accepts `near-white` or `silhouette` (issue #905). CLI
|
|
222
|
+
`--seam` overrides config, then the existing `near-white` default applies.
|
|
223
|
+
Invalid values refuse as `CONFIG_FIELD_TYPE` at `config.assemble.seam`;
|
|
224
|
+
an absent field preserves existing output bytes.
|
|
225
|
+
|
|
209
226
|
`TY14` holds the set of field names in backticks above to the set of names the
|
|
210
227
|
loader's object checks spell (every `c.object(…)` in `src/config.ts`, with the lists
|
|
211
228
|
they name), in both directions, and refuses an object check whose key list it cannot
|
|
@@ -507,10 +524,17 @@ The separate ordering scalar is unchanged. No-image outputs retain legacy bytes.
|
|
|
507
524
|
### Declared setup-painting regions
|
|
508
525
|
|
|
509
526
|
The optional `setup_painting` key in `spine-parts-requirements/1` adds a
|
|
510
|
-
`SETUP_MATCHES_PAINTING`
|
|
527
|
+
`SETUP_MATCHES_PAINTING` measurement when declared. It requires authored
|
|
511
528
|
`rgb_tolerance` and named native-stage boxes. Each optional `max_pixels` is
|
|
512
|
-
that whole region's limit; omission is SKIP, never PASS.
|
|
513
|
-
|
|
529
|
+
that whole region's limit; omission is SKIP, never PASS. With `--source` but
|
|
530
|
+
no declaration, the console, `check.json` and the report's skipped-bar list now
|
|
531
|
+
say SKIP with the reason and the `--requirements` declaration remedy; exit
|
|
532
|
+
codes stay unchanged. No metric, region or limit is invented. Without source
|
|
533
|
+
and without the key, the line stays absent. Declared output bytes and renderer
|
|
534
|
+
calls remain unchanged. `check --json` prints the completed `check.json` bytes
|
|
535
|
+
to stdout and keeps the ordinary check's exit code; early input refusals and
|
|
536
|
+
usage errors keep their existing named text and exits. `build --json` is refused
|
|
537
|
+
with a `check --json` remedy; build keeps its multi-stage text report. Other requirement fields
|
|
514
538
|
remain independent. The [authoring contract](AUTHORING.md#setup-against-the-painting)
|
|
515
539
|
defines white compositing, the figure domain, strict RGB comparison, top
|
|
516
540
|
contributor attribution and named measurement refusals. The check observes the
|
|
@@ -540,3 +564,9 @@ encoding, convexity sign and refusal remedies are in
|
|
|
540
564
|
calls these stages; no-config builds retain their existing behavior and bytes.
|
|
541
565
|
Group numbers and unit depths are explicit; no provider or absolute scale is
|
|
542
566
|
inferred. A new depth stage never replaces Core's rig gate.
|
|
567
|
+
`relief` is an explicit rig-pixel value, not an implicit width multiplier.
|
|
568
|
+
Part-width and head-referenced calibrations are authored recipe choices;
|
|
569
|
+
changing a calibration requires preparing its recipe again. Compare the same
|
|
570
|
+
provider maps and preserve their provenance. Each report row names the part,
|
|
571
|
+
declared relief, group `zScale` and clipping over original alpha>=128 support;
|
|
572
|
+
it remains a measurement rather than a shape-quality gate.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rig-parts",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.88.0",
|
|
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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type SeamRule } from './seamrule.ts';
|
|
1
2
|
import { edgeLength } from './length.ts';
|
|
2
3
|
import { materialSource, type MaterialSource, materialRows, polygonMaterialEdges, type MaterialRow, type MaterialEdge, type PlacedArt } from './cutmaterial.ts';
|
|
3
4
|
import { unionTakenSources } from './takesource.ts';
|
|
@@ -188,16 +189,7 @@ export const SILHOUETTE_BORDER = 8;
|
|
|
188
189
|
/** `figureSilhouette`: a pixel is figure where it differs from that median by more than this, max channel. */
|
|
189
190
|
export const SILHOUETTE_LIMIT = 40;
|
|
190
191
|
|
|
191
|
-
export type SeamRule
|
|
192
|
-
export const SEAM_RULES: readonly SeamRule[] = ['near-white', 'silhouette'];
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* The seam rule a run uses when none is named. Set by measurement, not taste:
|
|
196
|
-
* the silhouette rule ships as the default only if it lowers the error pixel
|
|
197
|
-
* count on every oracle character without raising mean |d| on any
|
|
198
|
-
* (ORACLE_assemble.md, *faithful vs silhouette*).
|
|
199
|
-
*/
|
|
200
|
-
export const DEFAULT_SEAM_RULE: SeamRule = 'near-white';
|
|
192
|
+
export { DEFAULT_SEAM_RULE, SEAM_RULES, type SeamRule } from './seamrule.ts';
|
|
201
193
|
|
|
202
194
|
/**
|
|
203
195
|
* Where a layer may take the painting's pixel (`projectSource`). `core` is the
|
package/src/build.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DEFAULT_SEAM_RULE } from './seamrule.ts';
|
|
1
2
|
import { SETUP_PAINTING_LINE, setupPaintingLines } from './setuppainting.ts';
|
|
2
3
|
import { writeMaterialSources } from './cutmaterialio.ts';
|
|
3
4
|
import { CUT_MATERIAL_LINE, CUT_MATERIAL_SOURCES, materialLines } from './cutmaterial.ts';
|
|
@@ -164,7 +165,7 @@ export interface AssembleStageInput {
|
|
|
164
165
|
/** The head run, or null for one run that holds the face (issue #359). */
|
|
165
166
|
head: string | null;
|
|
166
167
|
config: string;
|
|
167
|
-
seam
|
|
168
|
+
seam?: SeamRule;
|
|
168
169
|
project: ProjectRule;
|
|
169
170
|
/** `--alt` entries, merged with `config.assemble.alternates` by set name (issue #454). */
|
|
170
171
|
alts?: readonly AlternateCliInput[];
|
|
@@ -327,13 +328,14 @@ export function assembleStage(input: AssembleStageInput, outs: AssembleOutputs,
|
|
|
327
328
|
// The early door: assemble runs before propose has drafted bones, meshes,
|
|
328
329
|
// regions and motion, so it requires only what it reads (issue #20).
|
|
329
330
|
const cfg = loadEarlyConfig(input.config, 'assemble');
|
|
331
|
+
const seam = input.seam ?? cfg.assemble.seam ?? DEFAULT_SEAM_RULE;
|
|
330
332
|
const src = readSource(input.source);
|
|
331
333
|
const runs = applyTakenLayers(readRuns(input.full, input.head), src, cfg, input.config);
|
|
332
334
|
for (const note of runs.notes) log(`note: ${note}`);
|
|
333
335
|
const fields = stageFields(cfg, runs.single ? { w: src.width, h: src.height } : null);
|
|
334
336
|
const under = readUnders(input.config, fields.cuts);
|
|
335
337
|
const caps = fields.limbs === undefined ? undefined : readCaps(input.config, fields.limbs);
|
|
336
|
-
const result = assemble({ source: src, full: runs.full, head: runs.head, depth: cfg.depth, depthBase: dirname(resolve(input.config)), ...fields, under, ...(caps === undefined ? {} : { caps }), seamRule:
|
|
338
|
+
const result = assemble({ source: src, full: runs.full, head: runs.head, depth: cfg.depth, depthBase: dirname(resolve(input.config)), ...fields, under, ...(caps === undefined ? {} : { caps }), seamRule: seam, projectRule: input.project });
|
|
337
339
|
|
|
338
340
|
// Issue #454: alternate painting sets, assembled before any file is written.
|
|
339
341
|
const configDir = dirname(resolve(input.config));
|
|
@@ -673,7 +675,7 @@ export function assembleStage(input: AssembleStageInput, outs: AssembleOutputs,
|
|
|
673
675
|
...(fields.yieldRefused === undefined ? {} : { yieldRefused: fields.yieldRefused }),
|
|
674
676
|
...(fields.turnMovingPair === undefined ? {} : { turnMovingPair: fields.turnMovingPair }),
|
|
675
677
|
...(fields.turn === undefined ? {} : { turn: fields.turn, regions: fields.regions, meshes: fields.meshes, motion: fields.motion }),
|
|
676
|
-
seamRule:
|
|
678
|
+
seamRule: seam,
|
|
677
679
|
projectRule: input.project,
|
|
678
680
|
...(altExclude.length === 0 ? {} : { exclude: altExclude }),
|
|
679
681
|
...(altIslands.length === 0 ? {} : { limbIslands: altIslands }),
|
|
@@ -2218,7 +2220,7 @@ export interface BuildInput {
|
|
|
2218
2220
|
/** The head run, or null for one run that holds the face (issue #359). */
|
|
2219
2221
|
head: string | null;
|
|
2220
2222
|
out: string;
|
|
2221
|
-
seam
|
|
2223
|
+
seam?: SeamRule;
|
|
2222
2224
|
project: ProjectRule;
|
|
2223
2225
|
loop: boolean;
|
|
2224
2226
|
/** `--page-edges` for both packed builds, the rig stage's gate and the check's artifact. */
|
|
@@ -2253,7 +2255,7 @@ export interface BuildInput {
|
|
|
2253
2255
|
windBand?: string;
|
|
2254
2256
|
}
|
|
2255
2257
|
|
|
2256
|
-
/** What `build` does with one flag `check` takes: hands it to its check stage, takes it as its own, or refuses it because build supplies the value itself. */
|
|
2258
|
+
/** What `build` does with one flag `check` takes: hands it to its check stage, takes it as its own, or refuses it because build supplies the value or report itself. */
|
|
2257
2259
|
export type CheckFlagInBuild =
|
|
2258
2260
|
| { as: 'forwarded' }
|
|
2259
2261
|
| { as: 'own'; why: string }
|
|
@@ -2280,6 +2282,7 @@ export const CHECK_FLAGS_IN_BUILD: Readonly<Record<string, CheckFlagInBuild>> =
|
|
|
2280
2282
|
'--no-extremes': { as: 'forwarded' },
|
|
2281
2283
|
'--environment': { as: 'forwarded' },
|
|
2282
2284
|
'--wind-band': { as: 'forwarded' },
|
|
2285
|
+
'--json': { as: 'supplied', why: 'build supplies its multi-stage text report; run check --rig <out>/rig --parts <out> --out <dir> --json for the completed check object' },
|
|
2283
2286
|
};
|
|
2284
2287
|
|
|
2285
2288
|
/** The requirements file build's check reads (issue #556): `read` is where it is opened, `recorded` what `check.json` names it by. */
|
|
@@ -2315,7 +2318,7 @@ export function buildRequirements(flag: string | undefined, declared: string | u
|
|
|
2315
2318
|
]);
|
|
2316
2319
|
}
|
|
2317
2320
|
|
|
2318
|
-
/** The `build` refusal of a `check` flag build supplies itself ({@link CHECK_FLAGS_IN_BUILD}), or null for any other flag. */
|
|
2321
|
+
/** The `build` refusal of a `check` flag whose value or report build supplies itself ({@link CHECK_FLAGS_IN_BUILD}), or null for any other flag. */
|
|
2319
2322
|
export function suppliedCheckFlag(flag: string): string | null {
|
|
2320
2323
|
const row = Object.hasOwn(CHECK_FLAGS_IN_BUILD, flag) ? CHECK_FLAGS_IN_BUILD[flag] : undefined;
|
|
2321
2324
|
return row?.as === 'supplied' ? `build does not take "${flag}"; ${row.why}` : null;
|
|
@@ -2408,7 +2411,6 @@ export function build(input: BuildInput, run: BuildRunners, log: Log): BuildResu
|
|
|
2408
2411
|
rmSync(join(out, FIGURE_MASK_FILE), { force: true });
|
|
2409
2412
|
// The idle-keys value is named only when it is not the default, so a build without the flag, or with the default, prints the line it printed before issue #95.
|
|
2410
2413
|
const idleKeys = input.idleKeys ?? DEFAULT_IDLE_KEYS;
|
|
2411
|
-
log(`rig-parts build: ${input.config} -> ${out}, seam rule ${input.seam}, projection rule ${input.project}, page edges ${input.pageEdges}, pack shape ${input.packShape ?? DEFAULT_PACK_SHAPE}${idleKeys === DEFAULT_IDLE_KEYS ? '' : `, idle keys ${idleKeys}`}${input.loop ? `, with the idle loop${input.leadIn === undefined ? '' : ` after a lead-in of ${input.leadIn} period(s)`}` : ''}`);
|
|
2412
2414
|
|
|
2413
2415
|
let req: BuildRequirements | null = null;
|
|
2414
2416
|
let configRecord: InputFileRecord | null = null;
|
|
@@ -2420,6 +2422,8 @@ export function build(input: BuildInput, run: BuildRunners, log: Log): BuildResu
|
|
|
2420
2422
|
// Issue #556: the flag, or the config's declaration when there is none; both naming other bytes is refused here.
|
|
2421
2423
|
// Issue #607: the bytes this read parsed are the ones check.json's config record hashes, under the path as typed.
|
|
2422
2424
|
const loaded = loadConfigWithBytes(input.config);
|
|
2425
|
+
const seam = input.seam ?? loaded.config.assemble.seam ?? DEFAULT_SEAM_RULE;
|
|
2426
|
+
log(`rig-parts build: ${input.config} -> ${out}, seam rule ${seam}, projection rule ${input.project}, page edges ${input.pageEdges}, pack shape ${input.packShape ?? DEFAULT_PACK_SHAPE}${idleKeys === DEFAULT_IDLE_KEYS ? '' : `, idle keys ${idleKeys}`}${input.loop ? `, with the idle loop${input.leadIn === undefined ? '' : ` after a lead-in of ${input.leadIn} period(s)`}` : ''}`);
|
|
2423
2427
|
configRecord = inputFileRecord(loaded.bytes, input.config);
|
|
2424
2428
|
req = buildRequirements(input.requirements, loaded.config.requirements, input.config);
|
|
2425
2429
|
// The same for the requirements file's own shape; its names resolve against the rig, in the check, before it builds.
|
|
@@ -2429,7 +2433,7 @@ export function build(input: BuildInput, run: BuildRunners, log: Log): BuildResu
|
|
|
2429
2433
|
// And the steady-wind band spec (issue #590), as check reads it.
|
|
2430
2434
|
if (input.windBand !== undefined) readWindBand(input.windBand);
|
|
2431
2435
|
stages.assemble(
|
|
2432
|
-
{ source: input.source, full: input.full, head: input.head, config: input.config, seam
|
|
2436
|
+
{ source: input.source, full: input.full, head: input.head, config: input.config, seam, project: input.project, ...(req === null ? {} : { requirements: req.read }), ...(input.alts === undefined ? {} : { alts: input.alts }) },
|
|
2433
2437
|
{ partsJson: join(out, 'parts.json'), partsDir: join(out, 'parts'), recomposite: join(out, 'recomposite_rig.png'), errorMap: join(out, ERROR_MAP_FILE) },
|
|
2434
2438
|
prefixed('assemble'),
|
|
2435
2439
|
);
|
package/src/check.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SETUP_PAINTING_LINE, setupPaintingBounds, setupPaintingLine, type SetupPainting, type SetupPaintingLine } from './setuppainting.ts';
|
|
1
|
+
import { SETUP_PAINTING_LINE, UNDECLARED_SETUP_PAINTING_REASON, setupPaintingBounds, setupPaintingLine, type SetupPainting, type SetupPaintingLine, type SetupPaintingObservation } from './setuppainting.ts';
|
|
2
2
|
import { readMaterialSources } from './cutmaterialio.ts';
|
|
3
3
|
import { CUT_MATERIAL_LINE, checkJointMaterial, type MaterialLine } from './cutmaterial.ts';
|
|
4
4
|
import { ART_JOINT_LINE, artJointLine, type ArtJointLine } from './artjoints.ts';
|
|
@@ -2730,8 +2730,8 @@ export interface CheckFigures {
|
|
|
2730
2730
|
requirements?: RequirementsBlock;
|
|
2731
2731
|
/** Issue #880: only when requirements declares fixed_skeleton. */
|
|
2732
2732
|
[ART_JOINT_LINE]?: ArtJointLine;
|
|
2733
|
-
/**
|
|
2734
|
-
[SETUP_PAINTING_LINE]?:
|
|
2733
|
+
/** Under --source: the declared observation, or an explicit undeclared SKIP. */
|
|
2734
|
+
[SETUP_PAINTING_LINE]?: SetupPaintingObservation;
|
|
2735
2735
|
[CUT_MATERIAL_LINE]?: MaterialLine;
|
|
2736
2736
|
PASS: boolean;
|
|
2737
2737
|
}
|
|
@@ -6476,7 +6476,8 @@ export function runCheck(rigDir: string, outDir: string, rigc: RigcRunner, parts
|
|
|
6476
6476
|
const setHints = new Map<string, string>();
|
|
6477
6477
|
const fixedSkeleton = reqFile?.fixed_skeleton;
|
|
6478
6478
|
let artJoints: ArtJointLine | null = null;
|
|
6479
|
-
let setupPainting:
|
|
6479
|
+
let setupPainting: SetupPaintingObservation | null = inp.source !== null && reqFile?.setup_painting === undefined
|
|
6480
|
+
? { status: 'SKIP', reason: UNDECLARED_SETUP_PAINTING_REASON } : null;
|
|
6480
6481
|
if (inp.parts !== null || inp.source !== null || fixedSkeleton !== undefined) {
|
|
6481
6482
|
try {
|
|
6482
6483
|
mkdirSync(stillDir, { recursive: true });
|
package/src/config.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SEAM_RULES, type SeamRule } from './seamrule.ts';
|
|
1
2
|
import type { TakeLayers } from './takelayers.ts';
|
|
2
3
|
import { paintingExtensionProblems, type PaintingExtension } from './paintingextension.ts';
|
|
3
4
|
import { baseAnchorProblems, type BaseAnchor } from './baseanchor.ts';
|
|
@@ -300,6 +301,8 @@ export interface AlternateSetConfig {
|
|
|
300
301
|
}
|
|
301
302
|
|
|
302
303
|
export interface Assemble {
|
|
304
|
+
/** Optional per-character seam rule; an explicit CLI flag wins. */
|
|
305
|
+
seam?: SeamRule;
|
|
303
306
|
/** Verified named replacements from another See-through run (#866). */
|
|
304
307
|
take?: TakeLayers[];
|
|
305
308
|
/** Rig pixels per source pixel. */
|
|
@@ -1622,7 +1625,7 @@ export interface EarlyConfig {
|
|
|
1622
1625
|
depth?: DepthConfig;
|
|
1623
1626
|
key: string;
|
|
1624
1627
|
seethrough: SeeThrough;
|
|
1625
|
-
assemble: { rig_scale: number; take?: TakeLayers[]; head_face_underfill?: boolean; yield_refused?: boolean; keep_under_occluders?: string[]; extend_painting?: PaintingExtension[] };
|
|
1628
|
+
assemble: { rig_scale: number; seam?: SeamRule; take?: TakeLayers[]; head_face_underfill?: boolean; yield_refused?: boolean; keep_under_occluders?: string[]; extend_painting?: PaintingExtension[] };
|
|
1626
1629
|
}
|
|
1627
1630
|
|
|
1628
1631
|
/** What plain `assemble` reads: the early config plus the plan `--propose-plan` printed. */
|
|
@@ -1691,8 +1694,9 @@ function parseEarlyConfigFrom(raw: Json, door: EarlyDoor, base: string | null):
|
|
|
1691
1694
|
if (door === 'layers') {
|
|
1692
1695
|
if ('seethrough' in t) checkSeeThrough(c, t.seethrough);
|
|
1693
1696
|
if ('assemble' in t) {
|
|
1694
|
-
const a = c.object('config.assemble', t.assemble, sectionRequired(door, 'assemble'), ['plan', 'extend_below_crop', 'patches', 'cuts', 'alternates', 'head_face_underfill', 'yield_refused', 'keep_under_occluders', 'extend_painting', 'take']);
|
|
1697
|
+
const a = c.object('config.assemble', t.assemble, sectionRequired(door, 'assemble'), ['plan', 'extend_below_crop', 'patches', 'cuts', 'alternates', 'head_face_underfill', 'yield_refused', 'keep_under_occluders', 'extend_painting', 'take', 'seam']);
|
|
1695
1698
|
if (a !== null) {
|
|
1699
|
+
checkSeam(c, a);
|
|
1696
1700
|
if ('rig_scale' in a) c.number('config.assemble.rig_scale', a.rig_scale, 'positive');
|
|
1697
1701
|
if ('head_face_underfill' in a && typeof a.head_face_underfill !== 'boolean') {
|
|
1698
1702
|
c.fail('CONFIG_FIELD_TYPE', 'config.assemble.head_face_underfill', `is ${show(a.head_face_underfill)}; true or false is required`);
|
|
@@ -2457,10 +2461,17 @@ function checkTakeLayers(c: Check, value: Json): void {
|
|
|
2457
2461
|
});
|
|
2458
2462
|
}
|
|
2459
2463
|
|
|
2464
|
+
function checkSeam(c: Check, a: Record<string, Json>): void {
|
|
2465
|
+
if ('seam' in a && !(SEAM_RULES as readonly unknown[]).includes(a.seam)) {
|
|
2466
|
+
c.fail('CONFIG_FIELD_TYPE', 'config.assemble.seam', `is ${show(a.seam)}; one of ${SEAM_RULES.join(', ')} is required`);
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
|
|
2460
2470
|
function checkAssemble(c: Check, v: Json, door: 'assemble' | 'full'): { parts: string[]; patches: string[]; cuts: string[] } {
|
|
2461
2471
|
const required = sectionRequired(door, 'assemble');
|
|
2462
|
-
const a = c.object('config.assemble', v, required.filter((k) => k !== 'plan'), ['plan', 'extend_below_crop', 'patches', 'cuts', 'alternates', 'head_face_underfill', 'yield_refused', 'keep_under_occluders', 'extend_painting', 'take']);
|
|
2472
|
+
const a = c.object('config.assemble', v, required.filter((k) => k !== 'plan'), ['plan', 'extend_below_crop', 'patches', 'cuts', 'alternates', 'head_face_underfill', 'yield_refused', 'keep_under_occluders', 'extend_painting', 'take', 'seam']);
|
|
2463
2473
|
if (a === null) return { parts: [], patches: [], cuts: [] };
|
|
2474
|
+
checkSeam(c, a);
|
|
2464
2475
|
if (required.includes('plan') && !('plan' in a)) {
|
|
2465
2476
|
c.fail('CONFIG_FIELD_PRESENT', 'config.assemble.plan', 'is absent and required; `assemble --propose-plan` prints one from the two runs — paste its plan and extend_below_crop into config.assemble');
|
|
2466
2477
|
}
|
package/src/rig.ts
CHANGED
|
@@ -309,6 +309,14 @@ function lineGap(a: number, b: number): number {
|
|
|
309
309
|
/** The bones the face's line runs through: rigc's A51 draws its line through their origins, and the face bones are turned to that same line. */
|
|
310
310
|
export const EYE_BONES: readonly [string, string] = ['eye_r', 'eye_l'];
|
|
311
311
|
|
|
312
|
+
/**
|
|
313
|
+
* Issue #903: midpoint of absolute setup eye-line rolls measured on the public
|
|
314
|
+
* controls (maximum 4.864514437760526 degrees across the five parts examples,
|
|
315
|
+
* dancer and the head-turn demo) and three-quarter (32.106255955117845 degrees).
|
|
316
|
+
* This separates those inputs; it is not an anatomical or visual-quality bar.
|
|
317
|
+
*/
|
|
318
|
+
const HEAD_TURN_ROLL_LIMIT = (4.864514437760526 + 32.106255955117845) / 2;
|
|
319
|
+
|
|
312
320
|
/**
|
|
313
321
|
* The eye line (issue #239). It exists when parts.json holds an eye on each
|
|
314
322
|
* side — the first part tagged `eyewhite-r` and the first tagged `eyewhite-l`
|
|
@@ -3656,6 +3664,15 @@ export function buildWaistPairSource(
|
|
|
3656
3664
|
boneLandmarks.set(bName, [bEntry.x, bEntry.y]);
|
|
3657
3665
|
}
|
|
3658
3666
|
const turnWarnings: Problem[] = [];
|
|
3667
|
+
if ((cfg.turn?.depths !== undefined || cfg.turn?.roles !== undefined) && line.degrees !== null && Math.abs(line.degrees) > HEAD_TURN_ROLL_LIMIT) {
|
|
3668
|
+
const faces = parts.parts.filter(p => tagOf(p.from) === 'face' && p.cut_from === undefined).map(p => p.name);
|
|
3669
|
+
const names = faces.length === 0 ? line.parts : faces;
|
|
3670
|
+
turnWarnings.push({
|
|
3671
|
+
code: 'RIG_TURN_HEAD_ROLL',
|
|
3672
|
+
object: `${names.length === 1 ? 'part' : 'parts'} ${names.map(n => `"${n}"`).join(', ')}`,
|
|
3673
|
+
detail: `setup eye-line roll ${pyRound(line.degrees, 6)} degrees (Spine, counter-clockwise), absolute ${pyRound(Math.abs(line.degrees), 6)} exceeds limit ${pyRound(HEAD_TURN_ROLL_LIMIT, 6)} degrees; read through authored ${EYE_BONES.join(' and ')} origins on ${line.parts.map(n => `"${n}"`).join(' and ')} (an eye-origin proxy, not painting detection); the declared head turn uses upright yaw/pitch axes on an already rolled head; inspect the neck at both turn extremes; warning only, no pose or output changed`,
|
|
3674
|
+
});
|
|
3675
|
+
}
|
|
3659
3676
|
if (cfg.turn?.roles !== undefined || cfg.turn?.body !== undefined) {
|
|
3660
3677
|
buildDepth25DTurn(rig, motion, {
|
|
3661
3678
|
warnings: turnWarnings,
|
package/src/seamrule.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Shared seam choices for the config reader and assembly. */
|
|
2
|
+
export type SeamRule = 'near-white' | 'silhouette';
|
|
3
|
+
export const SEAM_RULES: readonly SeamRule[] = ['near-white', 'silhouette'];
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The seam rule a run uses when none is named. Set by measurement, not taste:
|
|
7
|
+
* the silhouette rule ships as the default only if it lowers the error pixel
|
|
8
|
+
* count on every oracle character without raising mean |d| on any
|
|
9
|
+
* (ORACLE_assemble.md, *faithful vs silhouette*).
|
|
10
|
+
*/
|
|
11
|
+
export const DEFAULT_SEAM_RULE: SeamRule = 'near-white';
|
package/src/setuppainting.ts
CHANGED
|
@@ -74,6 +74,10 @@ export interface SetupPaintingLine {
|
|
|
74
74
|
reason?: string;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
/** An undeclared observation has no measured pixels, inferred regions or limit. */
|
|
78
|
+
export type SetupPaintingObservation = SetupPaintingLine | { status: 'SKIP'; reason: string };
|
|
79
|
+
export const UNDECLARED_SETUP_PAINTING_REASON = 'setup_painting is undeclared; setup-to-painting was not measured. To measure it, give --requirements <file> declaring setup_painting.rgb_tolerance and setup_painting.regions with name, box and authored max_pixels limits';
|
|
80
|
+
|
|
77
81
|
/** Pixel metric only. Caller supplies the actual renderer's textured setup and
|
|
78
82
|
* its per-pixel drawn-piece owners on precisely the source grid. RGB under
|
|
79
83
|
* zero alpha is ignored: both observations are source-over white first.
|
|
@@ -119,7 +123,8 @@ export function setupPaintingLine(
|
|
|
119
123
|
...(noLimit ? { reason: 'a region has no declared max_pixels; it is measured as SKIP, never PASS' } : {}) };
|
|
120
124
|
}
|
|
121
125
|
|
|
122
|
-
export function setupPaintingLines(line:
|
|
126
|
+
export function setupPaintingLines(line: SetupPaintingObservation): string[] {
|
|
127
|
+
if (!('regions' in line)) return [`${SETUP_PAINTING_LINE}: SKIP — ${line.reason}`];
|
|
123
128
|
return [`${SETUP_PAINTING_LINE}: ${line.status}${line.reason === undefined ? '' : ` — ${line.reason}`}; ${line.registration}; ${line.compositing}`,
|
|
124
129
|
...line.regions.flatMap(r => [`${SETUP_PAINTING_LINE}: region "${r.name}" ${r.status} — box [${r.box.join(', ')}], pixelcount ${r.pixel_count}, limit ${r.limit ?? 'undeclared (SKIP)'}, RGB tolerance ${line.rgb_tolerance}`,
|
|
125
130
|
...r.slots.map(s => `${SETUP_PAINTING_LINE}: slot "${s.slot}" region "${r.name}" — box [${s.box.join(', ')}], pixelcount ${s.pixel_count}; region total ${r.pixel_count}, limit ${r.limit ?? 'undeclared (SKIP)'}`)])];
|