rig-parts 2.4.1 → 2.6.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 +11 -9
- package/docs/AUTHORING.md +87 -6
- package/docs/STABILITY.md +8 -1
- package/package.json +2 -2
- package/src/assemble.ts +70 -19
- package/src/build.ts +17 -6
- package/src/check.ts +176 -20
- package/src/config.ts +12 -6
- package/src/paintingedges.ts +543 -0
- package/src/projectrule.ts +22 -0
- package/src/provenance.ts +1 -1
package/cli.ts
CHANGED
|
@@ -783,7 +783,9 @@ usage:
|
|
|
783
783
|
--seam overrides config.assemble.seam, otherwise ${DEFAULT_SEAM_RULE}. --project says where a layer takes
|
|
784
784
|
the painting's pixel: core (the reference's) erodes every layer's top-most
|
|
785
785
|
opaque area by 5x5 first, so a part a few pixels wide takes none; visible
|
|
786
|
-
erodes only along a rim where a later layer is in front
|
|
786
|
+
erodes only along a rim where a later layer is in front, and resolves the
|
|
787
|
+
parts' edges from the painting (the line and coverage where two parts
|
|
788
|
+
meet, issue #953). --project overrides config.assemble.project, otherwise
|
|
787
789
|
${DEFAULT_PROJECT_RULE}.
|
|
788
790
|
|
|
789
791
|
rig-parts assemble --propose-plan --source <painting.png> --full <dir|psd>
|
|
@@ -864,8 +866,8 @@ usage:
|
|
|
864
866
|
green build ends with the pack line beside the spineboy yardstick and the
|
|
865
867
|
three artifact paths — skeleton .json, .atlas and the packed page: the
|
|
866
868
|
packed atlas is the result, the loose parts are the intermediate it was
|
|
867
|
-
made from. --seam overrides config.assemble.seam, otherwise ${DEFAULT_SEAM_RULE}; --project
|
|
868
|
-
${DEFAULT_PROJECT_RULE} (both as for assemble), --page-edges to ${DEFAULT_PAGE_EDGES} and
|
|
869
|
+
made from. --seam overrides config.assemble.seam, otherwise ${DEFAULT_SEAM_RULE}; --project overrides
|
|
870
|
+
config.assemble.project, otherwise ${DEFAULT_PROJECT_RULE} (both as for assemble), --page-edges to ${DEFAULT_PAGE_EDGES} and
|
|
869
871
|
--pack-shape to ${DEFAULT_PACK_SHAPE} (as for check; both packed builds, rig's gate
|
|
870
872
|
and check's artifact, take them). --requirements is forwarded to check
|
|
871
873
|
(as for check); the file's own shape is read before assemble runs, and
|
|
@@ -1745,8 +1747,8 @@ function cmdAssemble(args: string[]): number {
|
|
|
1745
1747
|
if (!propose && !flags.has('--out')) return usage('assemble needs --out <dir>');
|
|
1746
1748
|
const seam = flags.get('--seam');
|
|
1747
1749
|
if (seam !== undefined && !(SEAM_RULES as readonly string[]).includes(seam)) return usage(`--seam ${seam}; one of ${SEAM_RULES.join(', ')} is required`);
|
|
1748
|
-
const project = flags.get('--project')
|
|
1749
|
-
if (!(PROJECT_RULES as readonly string[]).includes(project)) return usage(`--project ${project}; one of ${PROJECT_RULES.join(', ')} is required`);
|
|
1750
|
+
const project = flags.get('--project');
|
|
1751
|
+
if (project !== undefined && !(PROJECT_RULES as readonly string[]).includes(project)) return usage(`--project ${project}; one of ${PROJECT_RULES.join(', ')} is required`);
|
|
1750
1752
|
const [source, full, config] = ['--source', '--full', '--config'].map((f) => flags.get(f) as string);
|
|
1751
1753
|
const head = flags.get('--head') ?? null;
|
|
1752
1754
|
const writePath = flags.get('--write-config');
|
|
@@ -1772,7 +1774,7 @@ function cmdAssemble(args: string[]): number {
|
|
|
1772
1774
|
}
|
|
1773
1775
|
const out = flags.get('--out') as string;
|
|
1774
1776
|
assembleStage(
|
|
1775
|
-
{ 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 }) },
|
|
1777
|
+
{ source, full, head, config, ...(flags.has('--requirements') ? { requirements: flags.get('--requirements') as string } : {}), ...(seam === undefined ? {} : { seam: seam as SeamRule }), ...(project === undefined ? {} : { project: project as ProjectRule }), ...(alts.length === 0 ? {} : { alts }) },
|
|
1776
1778
|
{ 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) },
|
|
1777
1779
|
console.log,
|
|
1778
1780
|
);
|
|
@@ -1965,8 +1967,8 @@ function cmdBuild(args: string[]): number {
|
|
|
1965
1967
|
if (on.has('--all-sets') && flags.has('--set')) return usage('--set and --all-sets are two modes; give one');
|
|
1966
1968
|
const seam = flags.get('--seam');
|
|
1967
1969
|
if (seam !== undefined && !(SEAM_RULES as readonly string[]).includes(seam)) return usage(`--seam ${seam}; one of ${SEAM_RULES.join(', ')} is required`);
|
|
1968
|
-
const project = flags.get('--project')
|
|
1969
|
-
if (!(PROJECT_RULES as readonly string[]).includes(project)) return usage(`--project ${project}; one of ${PROJECT_RULES.join(', ')} is required`);
|
|
1970
|
+
const project = flags.get('--project');
|
|
1971
|
+
if (project !== undefined && !(PROJECT_RULES as readonly string[]).includes(project)) return usage(`--project ${project}; one of ${PROJECT_RULES.join(', ')} is required`);
|
|
1970
1972
|
const edges = pageEdgesOf(flags.get('--page-edges'));
|
|
1971
1973
|
if (!isPageEdges(edges)) return usage(edges);
|
|
1972
1974
|
const shape = packShapeOf(flags.get('--pack-shape'));
|
|
@@ -1988,7 +1990,7 @@ function cmdBuild(args: string[]): number {
|
|
|
1988
1990
|
const scratch = mkdtempSync(join(tmpdir(), 'rig-parts-build-'));
|
|
1989
1991
|
try {
|
|
1990
1992
|
const r = build(
|
|
1991
|
-
{ 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 } : {}) },
|
|
1993
|
+
{ config, source, full, head, out, ...(seam === undefined ? {} : { seam: seam as SeamRule }), ...(project === undefined ? {} : { 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 } : {}) },
|
|
1992
1994
|
{ rig: rigcRunner(bin), check: rigcRunner(bin), checkBin: bin, scratch: join(scratch, 'rig-gate'), stages: {
|
|
1993
1995
|
assemble: (input, outputs) => runBuildProcess({ stage: 'assemble', bin, input, outputs }, join(scratch, 'workers')),
|
|
1994
1996
|
rig: (input, _rigc, gateScratch) => runBuildProcess({ stage: 'rig', bin, input, scratch: gateScratch }, join(scratch, 'workers')),
|
package/docs/AUTHORING.md
CHANGED
|
@@ -148,6 +148,7 @@ read by no CPU stage).
|
|
|
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
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. |
|
|
151
|
+
| `assemble.project` | assemble, build | **authored**, optional `"core"` or `"visible"` (issue #953): the projection rule for this character. An explicit `--project` overrides the config; otherwise this field overrides the existing `core` default. An invalid value is `CONFIG_FIELD_TYPE` at `config.assemble.project`, including when a CLI override is given. Absent, existing build bytes are unchanged. |
|
|
151
152
|
| `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` |
|
|
152
153
|
| `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` |
|
|
153
154
|
| `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 |
|
|
@@ -3273,7 +3274,7 @@ step-up method below.
|
|
|
3273
3274
|
| `motion.duration` and `track.period` | `check`'s 12 fps grid (`1/12` s per frame) | Choose `N` frames (e.g. `N = 40`, `duration = 40 / 12 = 3.3333333333333335` s, or `N = 48`, `duration = 4.0` s) and set every body track's `period = duration`. `CHECK_LOOP_LAST_FRAME_AT_DURATION` requires `duration` to land on a `1/12` s tick, and `CONFIG_PERIOD_DIVIDES_DURATION` requires `duration / period` to be an integer. |
|
|
3274
3275
|
| Torso breath (`chest` `translatey`, `scalex`) | Figure height `H` (`parts.json` union height, kept near `1,100..1,200` rig px via `assemble.rig_scale`), torso span `L_torso = hypot(hip.x - neck.x, hip.y - neck.y)` from `bones`, and `BREATH_VISIBLE`'s `torso_heat_mean` vs `torso_floor` (pictured in `motion_heat.png`) | Floor: peak-to-peak vertical travel at `1.25 %` of `L_torso` (`amp = round2(0.00625 * L_torso)`, `base = amp`, `phase = 0.0`, i.e. `amp = 1.2` rig px on `demo`), paired with `chest` `scalex` `amp = 0.008`, `base = 1.008`, `phase = 0.0`. `check` scales the reference floor (`2.13` at `render_scale = 0.4211`) by `render_scale / 0.4211` and isolates the breath by posing only `chest`/`hip` `translate` and `scale` tracks. |
|
|
3275
3276
|
| Upper-body sway (`chest` → `neck` → `head` `rotate`) | Shoulder lever arm `R_shoulder = \|sleeve_s0.x - chest.x\|` in `bones`, and `CHAIN_LAG` / `TIP_OVER_ROOT` on `handwear` | Floor: `amp(chest) = 0.7°`, `amp(neck) = 0.85°`, `amp(head) = 1.0°` with lag `0.05` (`phase: 0.25, 0.30, 0.35`). **Set `chest` `rotate` `phase` to `0.25` (quadrature, 90° from `chest` `translatey`'s `phase: 0.0`), never near `0.0`:** at `phase ≈ 0`, vertical breath `y(t)` and shoulder lift `Δx · θ(t)` reinforce on one shoulder and cancel on the other, inflating `root_rig_px` on the reinforced sleeve and dropping `tip_rig_px / root_rig_px` below `1.4725`. At `phase = 0.25`, vertical breath and rotational sway are orthogonal on both shoulders alike. |
|
|
3276
|
-
| Sleeve physics (`sleeve_<s>0..2` under `chest`) | `CHECK_TIP_OVER_ROOT` (`ratio >= 1.4725` on each `handwear` part) and `CHECK_ANIMATIONS_DO_NOT_FOLD` | Set `"rotate": 1, "mix": 1, "inertia": 1.0, "damping": 0.75, "mass": 1` and taper `strength` down the chain: `[45, 30, 20]` for a 3-link sleeve (`1 : 0.67 : 0.44`). Stiff follow-through springs (`strength: 200`, `inertia: 0.2`) barely bend under a `3.333` s idle and move the sleeve as a rigid unit (`ratio ≈ 1.19..1.22`, failing `TIP_OVER_ROOT`); tapering `45 → 30 → 20` lets cumulative link rotation amplify cuff travel past `2.0x` the shoulder travel while keeping `0` folded triangles in `wave` and `hop`. |
|
|
3277
|
+
| Sleeve physics (`sleeve_<s>0..2` under `chest`) | `CHECK_TIP_OVER_ROOT` (`ratio >= 1.4725` on each `handwear` part) and `CHECK_ANIMATIONS_DO_NOT_FOLD` | Set `"rotate": 1, "mix": 1, "inertia": 1.0, "damping": 0.75, "mass": 1` and taper `strength` down the chain: `[45, 30, 20]` for a 3-link sleeve (`1 : 0.67 : 0.44`). Stiff follow-through springs (`strength: 200`, `inertia: 0.2`) barely bend under a `3.333` s idle and move the sleeve as a rigid unit (`ratio ≈ 1.19..1.22`, failing `TIP_OVER_ROOT`); tapering `45 → 30 → 20` lets cumulative link rotation amplify cuff travel past `2.0x` the shoulder travel while keeping `0` folded triangles in `wave` and `hop`. Those ratios are the world reading `TIP_OVER_ROOT` took until issue #987; it now reads the halves against `chest`, the bone the sleeves hang from, and this recipe on `demo` (the working build reproduced below) reads `9.383` and `9.952` there (`handwear_l`, `handwear_r`; `3.626` and `2.905` in the world, which the row keeps as `world_ratio`) — it passes the reading the line holds (rig-parts at `c17292c` with #987's change, rigc 3.2.1). |
|
|
3277
3278
|
| Hair physics (`hairback_*`, `bang_*` under `head`) | `CLOTH_SETTLES` (`>= 0.01694` rig px/s to the last frame), `CHECK_TEXTURE_STRETCH` (`<= 1.926544`), and `CHECK_ANIMATIONS_DO_NOT_FOLD` | Taper `strength` down each `n`-link chain by `strength_0 * (1 - 0.35 * i / (n - 1))`: on long back-hair chains, `strength_0 = 90` (`[90, 74.25, 58.5]` for 3 links), `inertia = 0.8`, `damping = 0.85`; on shorter fringe chains (`bang_*`), `strength = [130, 105]`, `inertia = 0.2`, `damping = 0.9`. |
|
|
3278
3279
|
| Skirt / `bottomwear` physics (`skirt_*` under `hip`) | `CHECK_TIP_OVER_ROOT` on `bottomwear` (`ratio >= 1.4725`), `CHECK_ANIMATIONS_DO_NOT_FOLD` on `hop`, and `CLOTH_SETTLES` | Keep high restoration `strength` (`[200, 165, 130]` for 3 links, `inertia = 0.5`, `damping = 0.82`) so `bottomwear` does not fold during `hop`. With `hip` unkeyed, floor `"wind": [0.4, 0.6, 0.8]` passes `TIP_OVER_ROOT` (`ratio 1.83`); with `hip` keyed in `rotate` (`amp <= 0.4°`, `phase: 0.20`), set `"wind": 0` on the upper-half link `skirt_*0` and put `"wind"` on the lower links (`[0, 15, 40]`) so `tip / root` stays above `1.4725` and `CLOTH_SETTLES` clears on the default world. |
|
|
3279
3280
|
|
|
@@ -3330,13 +3331,15 @@ at `../examples/demo/animations.json` and `generation.control.skeleton` at
|
|
|
3330
3331
|
loop: idle 41 frame(s) at 12 fps, f0000 vs f0040 (t = 3.333333333333333s) with the rig's 29 physics constraint(s) left out (named on the `loop physics:` line; rendered with them, the ends need not meet — `build --loop --lead-in <n>`): max |d| 0 (0 required)
|
|
3331
3332
|
BREATH_VISIBLE: PASS — torso_parts topwear; torso_heat_mean 48.993; torso_floor 3.809; feet_parts shoes; feet_heat_max 0; feet_ceiling 0
|
|
3332
3333
|
CHAIN_LAG: PASS — chains hip_ctl > chest_ctl > neck > head_ctl: period 3.333s, phase 0.200 0.250 0.300 0.350, amp 0.400 2.500 3.000 3.600; lags hip_ctl > chest_ctl +0.050 | chest_ctl > neck +0.050 | neck > head_ctl +0.050; min_step 0.05; step_floor 0.001; other_period none; unread none; first_violation null
|
|
3333
|
-
TIP_OVER_ROOT: PASS — instrument geometry; parts part handwear_l, root_rig_px
|
|
3334
|
+
TIP_OVER_ROOT: PASS — instrument geometry, carrier frame; parts part handwear_l, hang_from chest, root_rig_px 1.3, tip_rig_px 12.202, ratio 9.383, world_root_rig_px 5.786, world_tip_rig_px 20.983, world_ratio 3.626 | part handwear_r, hang_from chest, root_rig_px 1.053, tip_rig_px 10.48, ratio 9.952, world_root_rig_px 6.213, world_tip_rig_px 18.047, world_ratio 2.905 | part bottomwear, hang_from hip, root_rig_px 0.231, tip_rig_px 3.813, ratio 16.512, world_root_rig_px 2.742, world_tip_rig_px 4.239, world_ratio 1.546; ratio_floor 1.4725
|
|
3334
3335
|
TEXTURE_STRETCH: PASS — meshes 9; triangles 1816; frames 41; severity 1.259; severity_ceiling 1.926544
|
|
3335
3336
|
ANIMATIONS_DO_NOT_FOLD: PASS — 8 animation(s) read by rigc render --folds at 12 fps, with its 29 physics constraint(s) on and off (idle, tilt, look_l, look_r, smile, startled, wave, hop): 0 folding mesh(es), 0 not declared in may_fold (<= 0)
|
|
3336
3337
|
CLOTH_SETTLES: CLEAR — 12 physics chain(s) read by their tip on the idle as rendered (41 frame(s) at 12 fps over 3.333 s, physics on): 0 of them settle before the loop's length — 0 warning(s), never a bar
|
|
3337
3338
|
check: PASS; 11 of 11 bar(s) measured, 0 skipped; 0 warning(s) (CLOTH_SETTLES, not a bar)
|
|
3338
3339
|
```
|
|
3339
3340
|
|
|
3341
|
+
The `TIP_OVER_ROOT` line above was read again for issue #987, from this build at rig-parts `c17292c` with #987's change and rigc 3.2.1; the other lines are as first printed. Its world figures for the sleeves (`3.626`, `2.905`) differ from the first print's (`3.399`, `2.491`); `bottomwear`'s are the same.
|
|
3342
|
+
|
|
3340
3343
|
## 4. The command order
|
|
3341
3344
|
|
|
3342
3345
|
Two steps are external: the See-through runs (by any route; the optional `comfy seethrough` adapter is only a client for a ComfyUI box). Everything else is this tool.
|
|
@@ -4191,7 +4194,33 @@ takes every other top-most `alpha >= 250` pixel; neither rule takes a fringe pix
|
|
|
4191
4194
|
projected from 36,227 to 22,476 (`reference-sample`) and 76,801 to 49,672 (`reference-demo`), recomposite
|
|
4192
4195
|
error pixels from 4,512 to 4,116 and 11,050 to 9,820, and the check seam from 0.207 to
|
|
4193
4196
|
0.206 and 0.326 to 0.325, with no other check figure changed. The default stays the
|
|
4194
|
-
reference's so the examples stay comparable with it. `build` takes both flags
|
|
4197
|
+
reference's so the examples stay comparable with it. `build` takes both flags, and
|
|
4198
|
+
`config.assemble.project` sets the rule per character (an explicit `--project` wins).
|
|
4199
|
+
|
|
4200
|
+
Under `visible` the parts' edges are also resolved from the painting (issue #953,
|
|
4201
|
+
`src/paintingedges.ts`). A See-through run is capped at its resolution, so every run
|
|
4202
|
+
layer is enlarged into the rig and its alpha edge is a soft ramp the painting does not
|
|
4203
|
+
have; where two parts meet along a painted line each layer repaints the line on its own
|
|
4204
|
+
side of its ramp — a doubled line, a stair-step, or the background through a gap where
|
|
4205
|
+
neither is opaque. Within 3 rig px of where two parts' half-coverage lines meet (or a
|
|
4206
|
+
part's and the background's), the part drawn in front takes the painting's coverage
|
|
4207
|
+
along the axis from the colour behind to its own, its colour unmixed from the colour
|
|
4208
|
+
behind, and the part behind is made opaque under it in that colour, so the composite
|
|
4209
|
+
is the painting and the rim stays the occluding part's. Where a painted line is darker or
|
|
4210
|
+
lighter than both colours, the front part's alpha rises until the composite is within 20
|
|
4211
|
+
of the painting. A pixel is left as it was where
|
|
4212
|
+
a third part with alpha above 8 covers it, where the painting draws no edge between the
|
|
4213
|
+
two (contrast below 40, the recomposite's error limit), where either part is a patch,
|
|
4214
|
+
keeps the pixel, feathers it, or is a turn's moving pair. A part's art keeps its
|
|
4215
|
+
4-connected pieces (`CONTOUR_ONE_ISLAND` reads them so): a write that would split a piece
|
|
4216
|
+
or start a new one is left out. The assemble line says how
|
|
4217
|
+
many pixels it resolved and why it left the rest; the pixels it writes are
|
|
4218
|
+
`provenance.automatic[].stage: "paintingEdge"`. On the five public examples it lowered
|
|
4219
|
+
the recomposite's error pixels under `visible` from 11,780 to 765 (`demo`), 7,427 to
|
|
4220
|
+
703 (`reference-demo`), 3,965 to 243 (`reference-sample`), 6,749 to 2,190 (`scarf`)
|
|
4221
|
+
and 13,872 to 1,110 (`wind`), and the holes from 1,020 to 62, 453 to 40, 259 to 20,
|
|
4222
|
+
291 to 34 and 1,118 to 62; `check` passes the same bars as before on all five. A
|
|
4223
|
+
painting patch is read placed on the rig canvas, and is never written. `core` is unchanged.
|
|
4195
4224
|
|
|
4196
4225
|
**`rig --idle-keys ctl|direct`** (and `build --idle-keys`, which forwards the
|
|
4197
4226
|
value to its rig stage; without the flag, or with `ctl`, a build writes and prints
|
|
@@ -4492,8 +4521,9 @@ still ends in the unchanged class by the first comparison.
|
|
|
4492
4521
|
|
|
4493
4522
|
Automatic value changes are named `sourceProjection`, `extend`, `growRim`,
|
|
4494
4523
|
`limbCap`, `limbFeather`, `fringeRestore`, `seamOverride`, `armSplit`,
|
|
4495
|
-
`headFaceUnderfill`, `ghostCleanup` or `
|
|
4524
|
+
`headFaceUnderfill`, `ghostCleanup`, `paintingExtension` or `paintingEdge`. `limbSplit` and `limbHybrid` remain
|
|
4496
4525
|
recognized stage names, but their pure copies do not claim a value change.
|
|
4526
|
+
`paintingEdge` (under `visible` only) is a part's edge resolved from the painting;
|
|
4497
4527
|
`paintingExtension` copies unheld painting samples within an explicit band;
|
|
4498
4528
|
`painting_extension_px` counts that ownership assignment independently, even
|
|
4499
4529
|
when restoring raw-layer values makes its final value class unchanged.
|
|
@@ -5316,7 +5346,7 @@ documents do not ship). Each is an input this package built; none is an author's
|
|
|
5316
5346
|
| <a id="CHECK_BREATH_VISIBLE"></a>`CHECK_BREATH_VISIBLE` | the breath alone barely moves the torso (`topwear`, rendered alone, the idle with and without its breath tracks — issue #411; the whole idle's figure is printed beside it, and motion that is not the breath's is not counted), or the idle has no breath track at all — 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) |
|
|
5317
5347
|
| <a id="CHECK_BLINK_NO_HOLE"></a>`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 |
|
|
5318
5348
|
| <a id="CHECK_CHAIN_LAG"></a>`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 — or, in a chain that carries a part the scene file declares `rests`, more than the link above (issue #245; the object names the part and the file). A lag refusal (issue #580) prints the step, then the two phases it read — as the sine reader returns them, a negative amplitude read as amplitude > 0 at phase + 0.5 — then their difference, folded into (−¼, ¼] cycle because a sine's sign is half a cycle (so flipping an amplitude's sign does not change the step), then the child phases that pass for this parent: `phase step -0.230 cycle(s); >= 0.001 is required — … Phases read: "chest" 0.000, "sleeve_l0" 0.270 …; the step is their difference, 0.270, folded into (-1/4, 1/4] cycle … here a "sleeve_l0" phase in [0.001, 0.250] or [0.501, 0.750]` | for a lag, that chain track's `phase`/`lag`: a child phase from the parent's + 0.001 to + 0.25, modulo 0.5 — the windows the line prints. A larger phase helps only inside them: a child 0.25 to 0.5 above its parent still reads as a lead, and a negated `amps` moves nothing. For a swing, its `amps` (non-decreasing toward the tip; non-increasing on a chain declared resting) |
|
|
5319
|
-
| <a id="CHECK_TIP_OVER_ROOT"></a>`CHECK_TIP_OVER_ROOT` | a `handwear`/`bottomwear` part's art in the lower half of its box travels less than 1.4725 times as far as in the upper half, measured from the idle's posed geometry (rig px quoted) | the chain track's `amps` (grow toward the tip), or the mesh's `segments` (the chain must be among them), or its mode: on `scarf`'s short skirt the proposal's lattice read 1.207 and the same part in `contour` mode 1.738 with the same segments and idle (issue #118) — both carried the `chest` segment #262 removed, after which the lattice reads 3.487. When the detail says `the root travels with the breath (x px of y px): it is carried by \`<bone>\`` (issue #262) the chain is not the cause: a bone an idle `translate` track moves (the breath) moves the root more than the tip, and with that translate held still (the part posed again without it, in proportion to each vertex's weight on the bone and those under it) the ratio passes — the row adds `carried_by`, `breath_rig_px`, `root_without_breath_rig_px` and `tip_without_breath_rig_px`; take that bone's segment off the mesh, or bind the root to the bone the part hangs from. A part the scene file declares `rests` (issue #245) is held the other way, its tip half's travel at most its root half's (ratio ≤ 1); the detail then reads `the scene file's rests declares its tip at rest, so a ratio <= 1 is required`, and for it the fix is the chain's `amps` (shrinking toward the tip) or the mesh's tip weighted to a bone the idle does not swing |
|
|
5349
|
+
| <a id="CHECK_TIP_OVER_ROOT"></a>`CHECK_TIP_OVER_ROOT` | a `handwear`/`bottomwear` part's art in the lower half of its box travels less than 1.4725 times as far as in the upper half, measured from the idle's posed geometry against the bone the part hangs from (issue #987; rig px quoted, the world reading beside it) | the chain track's `amps` (grow toward the tip), or the mesh's `segments` (the chain must be among them), or its mode: on `scarf`'s short skirt the proposal's lattice read 1.207 and the same part in `contour` mode 1.738 with the same segments and idle (issue #118) — both carried the `chest` segment #262 removed, after which the lattice reads 3.487. When the detail says `the root travels with the breath (x px of y px): it is carried by \`<bone>\`` (issue #262) the chain is not the cause: a bone an idle `translate` track moves (the breath) moves the root more than the tip, and with that translate held still (the part posed again without it, in proportion to each vertex's weight on the bone and those under it) the ratio passes — the row adds `carried_by`, `breath_rig_px`, `root_without_breath_rig_px` and `tip_without_breath_rig_px`; take that bone's segment off the mesh, or bind the root to the bone the part hangs from, which the detail names. Since issue #987 only a breath keyed under that bone is read: one at or above it is in the frame the halves are read in and carries nothing. When the bone named is a link of the chain the part hangs on, its segment stays, and the detail says to key the translate on the bone the part hangs from or above it. `nothing in it moves against \`<bone>\`` is a part that rides the bone it hangs from whole — a rigid sleeve, whose world ratio is 1. Since issue #995 a lone swing counts as a link too — a rotate-keyed bone with no child bone, or whose only child is its control twin (a one-link chain's control) — so a skirt bound to the one bone that swings it is read against the bone above, where its swing shows. A rotate-keyed body segment other bones hang under (a pelvis carrying the legs) is not one: a part bound whole to it reads `nothing in it moves against` that bone and fails, its world ratio passing only through the lever arm. Hang swinging cloth on its own bone under the body segment; a fitted garment the rig holds rigid on purpose (shorts on the pelvis) is declared in the scene file's `not_swinging` (§7), and the line then reads it SKIP with the reason. A part the scene file declares `rests` (issue #245) is held the other way, its tip half's travel at most its root half's (ratio ≤ 1); the detail then reads `the scene file's rests declares its tip at rest, so a ratio <= 1 is required`, and for it the fix is the chain's `amps` (shrinking toward the tip) or the mesh's tip weighted to a bone the idle does not swing |
|
|
5320
5350
|
| <a id="CHECK_STILL_REGIONS_DARK"></a>`CHECK_STILL_REGIONS_DARK` | a slot of the face's still set (the `face` slots and every slot drawn over the face that the head bone alone carries) moves relative to the bone the face's slot rides, or a `footwear` slot moves on the screen, by more than the arithmetic's own rounding — measured from the idle's posed geometry (the slot, its largest displacement in rig px, the idle frame and the tolerance quoted) | on the face: a face mesh with a weight on a bone other than the head (`segments`), or one deformed by another bone; on the feet: a mesh weighted to a swinging bone, or a region on the wrong bone. A bang swinging over the face is not this line's failure: its depth is reported (`crossing`), with no bar |
|
|
5321
5351
|
| <a id="CHECK_TEXTURE_STRETCH"></a>`CHECK_TEXTURE_STRETCH` | a mesh triangle's edge, in some idle frame, is more than 1.926544 times its rest length or less than 1/1.926544 of it (the mesh, triangle, its three vertices, the edge and the frame are named, and — issue #314 — the bones the edge's two vertices hang on: each vertex's two largest weights from `rig.json`, `vertex <i> is weighted to "<bone>" <w>, …`, then `on the idle's chains, the chain from "<first link>" carries "<bone>", …` — each chain `CHAIN_LAG` reads named once, by its first link — with a bone on no chain said, or `none of these bones hangs on a chain of the idle`); or a rest edge has length 0 | the chain tracks' `amps` on the bones that mesh is weighted to (the stretch grows with them), or the mesh's `segments` — a vertex blending two bones that swing against each other. Two chains named, with opposed amplitudes: give both one sign, or drop one — `propose --from-config` measures the pair before a build (§3, *Two chains on one lattice*). An edge whose two ends disagree on whether a bone's small share survives the 0.03 cut (one vertex keeps the bone at a few hundredths, the next drops it — read the two vertices' weights the line quotes): declare `influences: {floor: "continuous"}` on that mesh first, `rule: "heat"` second; the weight `exponent` moves the figure without removing the cut (issue #115, `reference-demo` `hair_back` as contour at tolerance 1, rig-c 2.47.1: 2.535 as drafted; continuous floor 1.422, heat 1.816, both PASS; exponent 1.5 / 3 / 4 / 6 read 2.564 / 1.902 / 2.119 / 4.816, the one pass on the same cut at another edge) |
|
|
5322
5352
|
| <a id="CHECK_ANIMATIONS_DO_NOT_FOLD"></a>`CHECK_ANIMATIONS_DO_NOT_FOLD` | an animation, the idle included, turns a mesh triangle over against the setup pose on some frame, read by `rigc render --folds` over the build with its physics on — and with its physics constraints left out beside it when the rig declares any, so each row reports `physics_on` and `physics_off` side by side (issues #320, #333, #363); the object names the animation and the mesh, the detail quotes rigc's line — the frames, the worst frame, which triangles, their world box and setup-pose area — and when the fold appears only with physics on, names the physics constraint(s) on the mesh's bones and the worst frame. After rigc's line it names what the worst frame's folded triangles hang on (issue #567), in `TEXTURE_STRETCH`'s words: each vertex's dominant bone is its largest weight in `rig.json`, the triangles whose vertices share one set of dominant bones are one group (the set in `rig.json`'s bone order), and each group lists its first 6 triangles (`triangles 63, 78, 79 dominated by "knee_r", "knee_l"`, past 6 `… and N more`) and spells out its first triangle's vertices — the largest two weights of each, and the idle chains those bones hang on; 3 groups are written and the rest counted (`; … and N more group(s), M triangle(s)`). An attachment the skin does not hold, or a triangle its table does not reach, is said, not guessed. Or a `may_fold` entry of the `--requirements` file names a slot and animation that does not fold: an exception that excepts nothing | the physics constraint(s) the line names (lower `inertia`, raise `strength`, or lower `damping`), the clip's keys on the bones the line names (a bend past what the weights carry, two bones turned toward each other until the rows between them pass, a translate that carries one row of vertices past the next), or the mesh's `segments`; to look, `rigc inspect --candidate <out>/check/build --show art,folds,mesh --animation <name> --at <the worst frame's time> --zoom folds`. A fold the art means (a sleeve behind the body) is declared, `"may_fold": [{ "slot": …, "animation": …, "why": … }]` (§7, *Declared requirements*); a stale entry is taken out |
|
|
@@ -5727,7 +5757,7 @@ the two examples [observed]:
|
|
|
5727
5757
|
| <a id="BLINK_NO_HOLE"></a>`BLINK_NO_HOLE` | the setup pose with every `scaley` track on the eyewhite slots' bones held at its closed value, against the setup pose, at full size: pixels in the eyewhite box that show the page where the open eye had art | 0 px | 0 | 0 | no `eyewhite` part, or no `scaley` track on its bones goes below its first key |
|
|
5728
5758
|
| ″ (reported) | the same box: each closed-eye pixel's max-channel distance to the nearest colour the open eye's box holds — max, and pixels over 40 | none | 15; 0 | 11; 0 | as above |
|
|
5729
5759
|
| <a id="CHAIN_LAG"></a>`CHAIN_LAG` | `motion.json`'s rotate tracks read as sines (DFT of the keys: period, amplitude, phase) and arranged by the bone tree — a keyed bone's parent is its nearest keyed ancestor | every lag ≥ 0.001 cycle; amplitude non-decreasing down each unbranched chain — non-increasing down a chain that carries a part declared `rests` (issue #245) | lags 0.040 (neck to head) to 0.120; 12 chains | lags 0.040 to 0.100; 9 chains | no rotate track under another of the same period |
|
|
5730
|
-
| <a id="TIP_OVER_ROOT"></a>`TIP_OVER_ROOT` | each `handwear`/`bottomwear` part: how far the centroid of its art travels in the lower half of its box against the upper half — the art's AREA centroid inside each fixed half (cut at h/2), every art pixel (alpha above 8) carried through its attachment's posed triangles from the idle's `geometry.json` and clipped to the half exactly, rig px; `instrument: "geometry"` in the line (issue #118) | ratio ≥ 1.4725; a part declared `rests`, ratio ≤ 1 (issue #245) | `bottomwear` 3.222, `sleeves` 4.
|
|
5760
|
+
| <a id="TIP_OVER_ROOT"></a>`TIP_OVER_ROOT` | each `handwear`/`bottomwear` part: how far the centroid of its art travels in the lower half of its box against the upper half — the art's AREA centroid inside each fixed half (cut at h/2), every art pixel (alpha above 8) carried through its attachment's posed triangles from the idle's `geometry.json` and clipped to the half exactly, rig px; `instrument: "geometry"` in the line (issue #118). Since issue #987 every posed vertex is first carried into the setup place of the bone the part hangs from (`hang_from`, below), so the halves travel against it; the row keeps the world reading as `world_root_rig_px`, `world_tip_rig_px`, `world_ratio`, and the line says `instrument: "geometry, carrier frame"`. A half that travels 0 at the line's precision is 0 | ratio ≥ 1.4725; a part declared `rests`, ratio ≤ 1 (issue #245) | `bottomwear` 3.222, `sleeves` 9.891 against `chest` (world 4.762) | `bottomwear` 5.393, `sleeves` 27.482 against `chest` (world 11.830) | no such part |
|
|
5731
5761
|
| <a id="STILL_REGIONS_DARK"></a>`STILL_REGIONS_DARK` | from the idle's posed geometry (`geometry.json`), `instrument: "geometry"` in each half (issue #123). **Face:** the still set — the `face` slots, and every slot drawn after the first of them whose art overlaps a face part's at the setup pose, that the head bone alone carries (a region on it, or a mesh whose every weight is it) and that the idle does not key by slot (`deform`, `sequence`, a slot track: `driven_slots`). The head bone is the bone the `face` parts' slot rides in `rig.json`. Each still slot's art (alpha above 8), carried through its posed attachment and back through the head bone's setup-to-frame map on every idle frame, against its setup position: `max_rig_px` (and where, `at`, when it is a motion), `rms_rig_px`, per slot in `still_slots`. **Feet:** the `footwear` slots the same way in screen space — they ride the root, which the idle does not key, so the screen is their own frame. **Reported, no bar:** `crossing` — per slot drawn after the still set's first slot and not in it, how deep its art reaches into the region (`face` on top at the setup pose, clear of where `eyewhite`, `irides`, `eyelash`, `eyebrow` and `mouth` go in the head's frame; for the feet, `footwear` on top), `depth_rig_px`, and the most of it covered on one frame, `area_rig_px2` | each half's `max_rig_px` ≤ its `tolerance_rig_px`: nothing moves, up to the arithmetic's rounding, √2 × 2 × 35 × M² × ulp(C) (below) | face 4.202e-13 ≤ 2.286e-11; `hair_front` crossing 2.816 rig px deep, 89.395 rig px² (frame 47); feet 0 ≤ 2.814e-12 | face 4.418e-13 ≤ 2.286e-11 (`face`, `ear_l`, `ear_r`); `hair_front` 2.194 deep, 50.605 rig px² (frame 23); feet 0 ≤ 2.814e-12 | no `face` and no `footwear` part (one of the two absent leaves that half unmeasured; so do face parts on two slot bones, or idle frames with no `geometry.json`) |
|
|
5732
5762
|
| <a id="TEXTURE_STRETCH"></a>`TEXTURE_STRETCH` | every mesh triangle's three edges in every idle frame, read from the `geometry.json` the idle render writes (`rigc render --geometry`, rig-c 1.4.0 or later): skinned length over rest length, the rest being the setup pose's bones with no deform. The figure is the rig's worst max(ratio, 1/ratio); each mesh's largest and smallest ratio, with triangle, vertices, edge and frame, is the detail | max(ratio, 1/ratio) ≤ 1.926544 | 1.388: `hair_back` triangle 449, edge 42-43 at 0.720 in frame 26 (largest stretch 1.291, frame 2) | 1.227: `sleeves` triangle 169, edge 126-51 at 0.815 in frame 28 (largest stretch 1.196, frame 5) | the rig draws no mesh, or the render wrote no `geometry.json` |
|
|
5733
5763
|
| <a id="ANIMATIONS_DO_NOT_FOLD"></a>`ANIMATIONS_DO_NOT_FOLD` | every animation, the idle included, through `rigc render --folds` at 12 fps over the build with its physics stepping and, when the rig declares physics constraints, over the keyed build with them left out beside it (issue #363): the meshes with a triangle turned over against the setup pose on some frame, less those the requirements file's `may_fold` declares (below) | 0 undeclared folding meshes, and no `may_fold` entry that matches no fold | 0, over `idle` | 0, over `idle` and `hop`, 2 physics constraints on and off | never SKIP: written whenever `motion.json` holds an animation |
|
|
@@ -5767,6 +5797,57 @@ on the geometric figure (`scarf`'s lattice `bottomwear` 1.337, 1.316, 1.285, 1.2
|
|
|
5767
5797
|
1200, 2400, 4800, 8000), with one gap not explained: `reference-sample`'s `bottomwear` tip reads
|
|
5768
5798
|
2.342 rig px there against 2.596 from geometry, its root agreeing to 0.4 %.
|
|
5769
5799
|
|
|
5800
|
+
**`TIP_OVER_ROOT` reads the halves against the bone the part hangs from (issue #987).**
|
|
5801
|
+
Read in the world, a sleeve whose chain trails its body's sway scores the trail as the
|
|
5802
|
+
tip moving less: the body carries the root, the chain's lag carries the tip back, and the
|
|
5803
|
+
two cancel at the tip. A heavier physics sleeve read lower there, and a pendulum that
|
|
5804
|
+
turned about its middle read higher, so tuning toward the floor made the sleeve swing
|
|
5805
|
+
more or turn the wrong way. Each idle frame's posed vertices are now mapped by
|
|
5806
|
+
setup(B) · frame(B)⁻¹, B the bone the part hangs from: from the deepest common
|
|
5807
|
+
ancestor-or-self of the bones its attachments are bound to, walk up while the bone is a
|
|
5808
|
+
link — a bone of a chain `CHAIN_LAG` reads, or a bone a physics constraint drives — and
|
|
5809
|
+
B is the first that is not (the rig stage keys `<link>_ctl` and binds the mesh to its
|
|
5810
|
+
child the physics drives, so the walk passes both; no bone is read by its name). When
|
|
5811
|
+
that common bone is a link's control twin — at the link's origin, unturned, unscaled and
|
|
5812
|
+
keyed by no idle track — the walk starts from the link, so a keyed chain whose bound
|
|
5813
|
+
child no physics drives is lifted too; the twin step is taken only there, so the walk
|
|
5814
|
+
stops at `chest`, which is `chest_ctl`'s twin. The row names the bone as `hang_from`.
|
|
5815
|
+
The floor stays 1.4725, the bar set from the pixel instrument above, not chased. Every
|
|
5816
|
+
public example passes both
|
|
5817
|
+
readings; the bottomwear parts hang from `hip`, which no example keys, so they read the
|
|
5818
|
+
same:
|
|
5819
|
+
|
|
5820
|
+
| part | world (until #987) | against `hang_from` |
|
|
5821
|
+
| --- | --- | --- |
|
|
5822
|
+
| `demo` `handwear_l`, `handwear_r` | 4.761, 4.835 | 10.249, 11.911 (`chest`) |
|
|
5823
|
+
| `demo` `bottomwear` | 2.426 | 2.426 (`hip`) |
|
|
5824
|
+
| `reference-demo` `bottomwear`, `sleeves` | 3.222, 4.762 | 3.222 (`hip`), 9.891 (`chest`) |
|
|
5825
|
+
| `reference-sample` `bottomwear`, `sleeves` | 5.393, 11.830 | 5.393 (`hip`), 27.482 (`chest`) |
|
|
5826
|
+
| `scarf` `handwear_r`, `handwear_l` | 2.632, 2.460 | 9.235, 7.732 (`chest`) |
|
|
5827
|
+
| `scarf` `bottomwear` | 3.436 | 3.436 (`hip`) |
|
|
5828
|
+
| `wind` `bottomwear` | 1.816 | 1.816 (`hip`) |
|
|
5829
|
+
|
|
5830
|
+
The selftest plants the four cases the reading is for on one two-link sleeve under a
|
|
5831
|
+
body the idle carries 4 rig px: ridden rigid it reads 0 / 0 against the bone it rides
|
|
5832
|
+
and fails (world 1); turned about its middle it reads about 1 and fails (world above
|
|
5833
|
+
the floor); trailing the carry, on keyed links with physics children or on a
|
|
5834
|
+
physics-only chain, it passes (world below 1). The skirt whose waistband rides `chest`
|
|
5835
|
+
(issue #262) hangs from a bone above `chest`, so the breath still carries its root and
|
|
5836
|
+
it still fails, naming `chest`.
|
|
5837
|
+
|
|
5838
|
+
A lone swing is a link too (issue #995): a rotate-keyed bone with no child bone, or whose
|
|
5839
|
+
only child is its control twin, is the swing itself — a skirt bound to the one bone that
|
|
5840
|
+
swings it from the top of its box, or a one-link chain's control — so the walk passes it
|
|
5841
|
+
and the part is read against the bone above (in the build fixtures that is `root`, which
|
|
5842
|
+
the idle does not move, so the carrier reading equals the world one: 2.336 for both). A
|
|
5843
|
+
rotate-keyed body segment other bones hang under is not one: a garment bound whole to a
|
|
5844
|
+
pelvis that carries the legs reads 0 / 0 against it and fails (world 3.501, through the
|
|
5845
|
+
lever arm alone). `TIP_OVER_ROOT` reads every `handwear` and `bottomwear` part, declared or
|
|
5846
|
+
not, so a fitted garment held rigid on purpose is declared `not_swinging` and reads SKIP
|
|
5847
|
+
with the reason; the leg-set build fixtures declare their shorts so. A track that keys a
|
|
5848
|
+
`motion.groups` group keys each member for the walk too, as `CHAIN_LAG` reads it, so a
|
|
5849
|
+
group-keyed link is not taken for its control's unkeyed twin.
|
|
5850
|
+
|
|
5770
5851
|
**`STILL_REGIONS_DARK` is measured from geometry too, and its scope changed (issue #123).**
|
|
5771
5852
|
|
|
5772
5853
|
**What it read until #123.** The face half compared two pixel figures on the idle's
|
package/docs/STABILITY.md
CHANGED
|
@@ -147,7 +147,7 @@ part name, `[i]` an entry of a list):
|
|
|
147
147
|
- **generation.control.skeleton**, the pose file it names when it is no built-in — required: `width`, `height`, `points`
|
|
148
148
|
- **seethrough** — required: `resolution`, `steps`, `seed`, `offload`; optional: `head_box`, `arm_split`
|
|
149
149
|
- **seethrough.arm_split** — required: `order`, and one of `x` or `line` (issue #439)
|
|
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`, `editor_receipts`
|
|
150
|
+
- **assemble** — required: `rig_scale`, `plan` (from plain `assemble` on); optional: `seam`, `project`, `extend_below_crop`, `patches`, `cuts`, `alternates`, `head_face_underfill`, `yield_refused`, `keep_under_occluders`, `extend_painting`, `take`, `editor_receipts`
|
|
151
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
|
|
152
152
|
- **assemble.extend_below_crop[i]** — required: `part`, `run`, `tag`
|
|
153
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
|
|
@@ -225,6 +225,13 @@ part name, `[i]` an entry of a list):
|
|
|
225
225
|
Invalid values refuse as `CONFIG_FIELD_TYPE` at `config.assemble.seam`;
|
|
226
226
|
an absent field preserves existing output bytes.
|
|
227
227
|
|
|
228
|
+
`assemble.project` accepts `core` or `visible` (issue #953). CLI
|
|
229
|
+
`--project` overrides config, then the existing `core` default applies.
|
|
230
|
+
Invalid values refuse as `CONFIG_FIELD_TYPE` at `config.assemble.project`;
|
|
231
|
+
an absent field preserves existing output bytes. The bytes `visible` writes
|
|
232
|
+
are not promised across versions (issue #953 resolves its edges from the
|
|
233
|
+
painting).
|
|
234
|
+
|
|
228
235
|
`TY14` holds the set of field names in backticks above to the set of names the
|
|
229
236
|
loader's object checks spell (every `c.object(…)` in `src/config.ts`, with the lists
|
|
230
237
|
they name), in both directions, and refuses an object check whose key list it cannot
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rig-parts",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.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": {
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
},
|
|
69
69
|
"dependencies": {
|
|
70
70
|
"ag-psd": "^31.0.2",
|
|
71
|
-
"@rig-c/core": "3.2.
|
|
71
|
+
"@rig-c/core": "3.2.1"
|
|
72
72
|
},
|
|
73
73
|
"devDependencies": {
|
|
74
74
|
"@esotericsoftware/spine-core": "4.3.13",
|
package/src/assemble.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { type SeamRule } from './seamrule.ts';
|
|
2
|
+
import { DEFAULT_PROJECT_RULE, type ProjectRule } from './projectrule.ts';
|
|
3
|
+
import { paintingEdges, type PaintingEdgeSkips } from './paintingedges.ts';
|
|
2
4
|
import { edgeLength } from './length.ts';
|
|
3
5
|
import { materialSource, type MaterialSource, materialRows, polygonMaterialEdges, type MaterialRow, type MaterialEdge, type PlacedArt } from './cutmaterial.ts';
|
|
4
6
|
import { unionTakenSources } from './takesource.ts';
|
|
@@ -191,23 +193,7 @@ export const SILHOUETTE_LIMIT = 40;
|
|
|
191
193
|
|
|
192
194
|
export { DEFAULT_SEAM_RULE, SEAM_RULES, type SeamRule } from './seamrule.ts';
|
|
193
195
|
|
|
194
|
-
|
|
195
|
-
* Where a layer may take the painting's pixel (`projectSource`). `core` is the
|
|
196
|
-
* reference's rule: the layer's top-most `alpha >= 250` pixels eroded by a 5x5
|
|
197
|
-
* square, so a part a few pixels wide has no core and none of its visible
|
|
198
|
-
* pixels is projected. `visible` keeps the erosion only where it guards
|
|
199
|
-
* something — along a rim where a later layer of the run is in front — and
|
|
200
|
-
* projects every other top-most `alpha >= 250` pixel of the layer.
|
|
201
|
-
*/
|
|
202
|
-
export type ProjectRule = 'core' | 'visible';
|
|
203
|
-
export const PROJECT_RULES: readonly ProjectRule[] = ['core', 'visible'];
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* The projection rule a run uses when none is named: the reference's, so the
|
|
207
|
-
* examples stay comparable with it. `visible` was measured on both public
|
|
208
|
-
* examples against it (the change that added the flag); it is opt-in.
|
|
209
|
-
*/
|
|
210
|
-
export const DEFAULT_PROJECT_RULE: ProjectRule = 'core';
|
|
196
|
+
export { DEFAULT_PROJECT_RULE, PROJECT_RULES, type ProjectRule } from './projectrule.ts';
|
|
211
197
|
|
|
212
198
|
// ---------------------------------------------------------------------------
|
|
213
199
|
// inputs
|
|
@@ -4598,6 +4584,8 @@ export interface AssembleResult {
|
|
|
4598
4584
|
hidden: HiddenArtUse[];
|
|
4599
4585
|
seamRule: SeamRule;
|
|
4600
4586
|
projectRule: ProjectRule;
|
|
4587
|
+
/** Issue #953: what `paintingEdges` resolved, under `projectRule: 'visible'` only. */
|
|
4588
|
+
paintingEdges?: PaintingEdgeReading;
|
|
4601
4589
|
depthReadings?: Array<{ from: string; reading: import('./depthsheets.ts').DepthReading }>;
|
|
4602
4590
|
/** Visible registered samples clamped within <1 source pixel of a crop. */
|
|
4603
4591
|
depthClamps?: Array<{ part: string; samples: number }>;
|
|
@@ -4613,6 +4601,14 @@ export interface AssembleResult {
|
|
|
4613
4601
|
capSplits: SplitCount[];
|
|
4614
4602
|
}
|
|
4615
4603
|
|
|
4604
|
+
/** Issue #953: the edge pixels `paintingEdges` resolved, the part pixels it changed, and the edge pixels it left, by reason. */
|
|
4605
|
+
export interface PaintingEdgeReading {
|
|
4606
|
+
resolved: number;
|
|
4607
|
+
unseparated: number;
|
|
4608
|
+
changed: number;
|
|
4609
|
+
skipped: PaintingEdgeSkips;
|
|
4610
|
+
}
|
|
4611
|
+
|
|
4616
4612
|
/** A hidden-art layer of a run (issue #359) and what read it: `cuts[i].under`, or a limb segment's cap; none is said. */
|
|
4617
4613
|
export interface HiddenArtUse {
|
|
4618
4614
|
/** `<run>:<layer>:hidden`. */
|
|
@@ -5588,6 +5584,48 @@ export function assemble(input: AssembleInput): AssembleResult {
|
|
|
5588
5584
|
}
|
|
5589
5585
|
}
|
|
5590
5586
|
});
|
|
5587
|
+
// Issue #953: under `visible`, the parts' visible edges are the painting's (`paintingEdges`); See-through keeps the hidden surface.
|
|
5588
|
+
const edged = new Map<string, Mask>();
|
|
5589
|
+
let edgeReading: PaintingEdgeReading | null = null;
|
|
5590
|
+
if (projectRule === 'visible') {
|
|
5591
|
+
// paintingEdges reads every image as the whole rig canvas. A plan part's image already is one; a patch is its cut,
|
|
5592
|
+
// placed at record.x / record.y, so it is placed on a canvas here. The step never writes a patch, so a copy is enough.
|
|
5593
|
+
const onRig = (q: PlacedPart): Raster => {
|
|
5594
|
+
const { image, record } = q;
|
|
5595
|
+
if (image.width === W && image.height === H && record.x === 0 && record.y === 0) return image;
|
|
5596
|
+
const whole = newRaster(W, H);
|
|
5597
|
+
for (let y = 0; y < image.height; y++) {
|
|
5598
|
+
const ry = record.y + y;
|
|
5599
|
+
if (ry < 0 || ry >= H) continue;
|
|
5600
|
+
for (let x = 0; x < image.width; x++) {
|
|
5601
|
+
const rx = record.x + x;
|
|
5602
|
+
if (rx < 0 || rx >= W) continue;
|
|
5603
|
+
const s = (y * image.width + x) * 4;
|
|
5604
|
+
whole.data.set(image.data.subarray(s, s + 4), (ry * W + rx) * 4);
|
|
5605
|
+
}
|
|
5606
|
+
}
|
|
5607
|
+
return whole;
|
|
5608
|
+
};
|
|
5609
|
+
const e = paintingEdges(stack.map(onRig), srcr, {
|
|
5610
|
+
painted: paintedFigure(srcr),
|
|
5611
|
+
editable: order.map((d) => !('patch' in d)),
|
|
5612
|
+
keep: keepInOrder,
|
|
5613
|
+
feathered: featheredInOrder,
|
|
5614
|
+
movingPair: input.turnMovingPair === undefined || input.turnMovingPair === null ? null : (a, b) => (input.turnMovingPair as (x: string, y: string) => boolean)(stack[a].record.name, stack[b].record.name),
|
|
5615
|
+
written: (i, p) => {
|
|
5616
|
+
writersInOrder[i][p] = AW.paintingEdge;
|
|
5617
|
+
const d = order[i];
|
|
5618
|
+
if (!('patch' in d)) {
|
|
5619
|
+
const region = pieceAt(d).b.region;
|
|
5620
|
+
if (region !== null && stack[i].image.data[p * 4 + 3] > 0) region.data[p] = 1;
|
|
5621
|
+
}
|
|
5622
|
+
},
|
|
5623
|
+
});
|
|
5624
|
+
order.forEach((d, i) => {
|
|
5625
|
+
if (!('patch' in d) && e.changed[i].data.some((v) => v === 1)) edged.set(pieceAt(d).name, e.changed[i]);
|
|
5626
|
+
});
|
|
5627
|
+
edgeReading = { resolved: e.resolved, unseparated: e.unseparated, changed: e.changed.reduce((a, m) => a + m.data.reduce((x, v) => x + v, 0), 0), skipped: e.skipped };
|
|
5628
|
+
}
|
|
5591
5629
|
const pushedOf: FringePushed[][] = plan.map(() => []);
|
|
5592
5630
|
const pushedOfCut: FringePushed[][] = cuts.map(() => []);
|
|
5593
5631
|
const pushedOfLimb: FringePushed[][] = lim.parts.map(() => []);
|
|
@@ -5610,6 +5648,18 @@ export function assemble(input: AssembleInput): AssembleResult {
|
|
|
5610
5648
|
...cuts.map((q, i) => ({ name: q.into, b: pieces[i] as Built, pushed: pushedOfCut[i], at: `config.assemble.cuts[${i}]`, cutFrom: q.from, set: (pp: PlacedPart) => (cutPlaced[i] = pp) })),
|
|
5611
5649
|
...lim.parts.map((q, i) => ({ name: q.part, b: limbPieces[i] as Built, pushed: pushedOfLimb[i], at: `config.limbs.${q.limb}.segments[${q.segment}]`, cutFrom: null, limb: q.record, set: (pp: PlacedPart) => (limbPlaced[i] = pp) })),
|
|
5612
5650
|
].filter((q) => !taken.has(q.name));
|
|
5651
|
+
/** Issue #953: the part's projected pixels `paintingEdges` rewrote, less those an earlier exclusion already took out of `source_px_taken`. */
|
|
5652
|
+
const edgedTaken = (name: string, st: ProjectionStats, region: Mask | null): number => {
|
|
5653
|
+
const m = edged.get(name);
|
|
5654
|
+
if (m === undefined) return 0;
|
|
5655
|
+
let k = 0;
|
|
5656
|
+
for (let p = 0; p < W * H; p++) {
|
|
5657
|
+
if (m.data[p] !== 1 || st.takenMask.data[p] !== 1 || (region !== null && region.data[p] !== 1)) continue;
|
|
5658
|
+
if (paintingGains.get(name)?.data[p] === 1 || retakeByName.get(name)?.data[p] === 1 || yields.get(name)?.mask.data[p] === 1) continue;
|
|
5659
|
+
k++;
|
|
5660
|
+
}
|
|
5661
|
+
return k;
|
|
5662
|
+
};
|
|
5613
5663
|
pieceList.forEach(({ name, b, pushed: pushedHere, at, cutFrom, limb, set }) => {
|
|
5614
5664
|
const { key, r, st, extendKeys, trace, merged, region } = b;
|
|
5615
5665
|
// A pixel the merge wrote is judged in the extend layer's run, every other
|
|
@@ -5625,7 +5675,7 @@ export function assemble(input: AssembleInput): AssembleResult {
|
|
|
5625
5675
|
// A pixel outside the piece's region is a base pixel filled from a cut's under image (issue #193): occluded, never visible.
|
|
5626
5676
|
if (r.data[p * 4 + 3] > OPAQUE_ALPHA_ABOVE && own.clear.data[p] === 1 && (region === null || region.data[p] === 1)) visible.data[p] = 1;
|
|
5627
5677
|
if (paintingGains.get(name)?.data[p] === 1) visible.data[p] = 1;
|
|
5628
|
-
if (paintingGains.get(name)?.data[p] !== 1 && retakeByName.get(name)?.data[p] !== 1 && yields.get(name)?.mask.data[p] !== 1 && trace.ring.data[p] === 0 && own.takenMask.data[p] === 1 && (region === null || region.data[p] === 1)) projected.data[p] = 1;
|
|
5678
|
+
if (edged.get(name)?.data[p] !== 1 && paintingGains.get(name)?.data[p] !== 1 && retakeByName.get(name)?.data[p] !== 1 && yields.get(name)?.mask.data[p] !== 1 && trace.ring.data[p] === 0 && own.takenMask.data[p] === 1 && (region === null || region.data[p] === 1)) projected.data[p] = 1;
|
|
5629
5679
|
}
|
|
5630
5680
|
const { x0, y0, x1, y1, opaque } = measureBox(r);
|
|
5631
5681
|
if (opaque === 0) {
|
|
@@ -5650,7 +5700,7 @@ export function assemble(input: AssembleInput): AssembleResult {
|
|
|
5650
5700
|
visible_px: vc.visible_px,
|
|
5651
5701
|
occluded_px: vc.occluded_px,
|
|
5652
5702
|
projected_core_px: region === null ? st.core : count(st.coreMask, region),
|
|
5653
|
-
source_px_taken: (region === null ? st.taken : count(st.takenMask, region)) - (yields.get(name)?.reading.yieldedTaken ?? 0) - (retakeByName.get(name) == null ? 0 : count(st.takenMask, retakeByName.get(name)!)) - (paintingGains.has(name) ? count(st.takenMask, paintingGains.get(name)!) : 0),
|
|
5703
|
+
source_px_taken: (region === null ? st.taken : count(st.takenMask, region)) - (yields.get(name)?.reading.yieldedTaken ?? 0) - (retakeByName.get(name) == null ? 0 : count(st.takenMask, retakeByName.get(name)!)) - (paintingGains.has(name) ? count(st.takenMask, paintingGains.get(name)!) : 0) - edgedTaken(name, st, region),
|
|
5654
5704
|
visible_not_projected_px: vc.visible_not_projected_px,
|
|
5655
5705
|
refused_drift_px: region === null ? st.refused : count(st.refusedMask, region),
|
|
5656
5706
|
merged_px: merged,
|
|
@@ -5771,6 +5821,7 @@ export function assemble(input: AssembleInput): AssembleResult {
|
|
|
5771
5821
|
hidden: [...hiddenUses].map(([layer, readBy]) => ({ layer, readBy })),
|
|
5772
5822
|
seamRule,
|
|
5773
5823
|
projectRule,
|
|
5824
|
+
...(edgeReading === null ? {} : { paintingEdges: edgeReading }),
|
|
5774
5825
|
...(headFacePrep.underfillPx > 0 ? { headFaceUnderfillPx: headFacePrep.underfillPx } : {}),
|
|
5775
5826
|
excluded,
|
|
5776
5827
|
islands: islandKept.counts,
|
package/src/build.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { DEFAULT_SEAM_RULE } from './seamrule.ts';
|
|
2
|
+
import { DEFAULT_PROJECT_RULE } from './projectrule.ts';
|
|
3
|
+
import { EDGE_CONTRAST } from './paintingedges.ts';
|
|
2
4
|
import { SETUP_PAINTING_LINE, setupPaintingLines } from './setuppainting.ts';
|
|
3
5
|
import { boundaryColourLines } from './boundarycolour.ts';
|
|
4
6
|
import { writeMaterialSources } from './cutmaterialio.ts';
|
|
@@ -169,7 +171,8 @@ export interface AssembleStageInput {
|
|
|
169
171
|
head: string | null;
|
|
170
172
|
config: string;
|
|
171
173
|
seam?: SeamRule;
|
|
172
|
-
project
|
|
174
|
+
/** `--project`; absent, `config.assemble.project`, then {@link DEFAULT_PROJECT_RULE} (issue #953). */
|
|
175
|
+
project?: ProjectRule;
|
|
173
176
|
/** `--alt` entries, merged with `config.assemble.alternates` by set name (issue #454). */
|
|
174
177
|
alts?: readonly AlternateCliInput[];
|
|
175
178
|
/**
|
|
@@ -335,6 +338,7 @@ export function assembleStage(input: AssembleStageInput, outs: AssembleOutputs,
|
|
|
335
338
|
// regions and motion, so it requires only what it reads (issue #20).
|
|
336
339
|
const cfg = loadEarlyConfig(input.config, 'assemble');
|
|
337
340
|
const seam = input.seam ?? cfg.assemble.seam ?? DEFAULT_SEAM_RULE;
|
|
341
|
+
const project = input.project ?? cfg.assemble.project ?? DEFAULT_PROJECT_RULE;
|
|
338
342
|
const src = readSource(input.source);
|
|
339
343
|
const read = readRuns(input.full, input.head);
|
|
340
344
|
// Issue #946: every declared receipt is held to its file, and under `required` every edited image needs one, before anything is assembled.
|
|
@@ -346,7 +350,7 @@ export function assembleStage(input: AssembleStageInput, outs: AssembleOutputs,
|
|
|
346
350
|
const fields = stageFields(cfg, runs.single ? { w: src.width, h: src.height } : null);
|
|
347
351
|
const under = readUnders(input.config, fields.cuts);
|
|
348
352
|
const caps = fields.limbs === undefined ? undefined : readCaps(input.config, fields.limbs);
|
|
349
|
-
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:
|
|
353
|
+
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: project });
|
|
350
354
|
|
|
351
355
|
// Issue #454: alternate painting sets, assembled before any file is written.
|
|
352
356
|
const configDir = dirname(resolve(input.config));
|
|
@@ -688,7 +692,7 @@ export function assembleStage(input: AssembleStageInput, outs: AssembleOutputs,
|
|
|
688
692
|
...(fields.turnMovingPair === undefined ? {} : { turnMovingPair: fields.turnMovingPair }),
|
|
689
693
|
...(fields.turn === undefined ? {} : { turn: fields.turn, regions: fields.regions, meshes: fields.meshes, motion: fields.motion }),
|
|
690
694
|
seamRule: seam,
|
|
691
|
-
projectRule:
|
|
695
|
+
projectRule: project,
|
|
692
696
|
...(altExclude.length === 0 ? {} : { exclude: altExclude }),
|
|
693
697
|
...(altIslands.length === 0 ? {} : { limbIslands: altIslands }),
|
|
694
698
|
...(altOthers === undefined ? {} : { otherLimbs: alternateLayerOthers(altOthers, selectedLayers.groups) }),
|
|
@@ -805,6 +809,11 @@ export function assembleStage(input: AssembleStageInput, outs: AssembleOutputs,
|
|
|
805
809
|
if (figurePath !== null) writePng(figurePath, result.figure as Raster);
|
|
806
810
|
const [W, H] = result.parts.rig_size;
|
|
807
811
|
log(`rig-parts assemble: ${result.images.length} part(s) on a ${W}x${H} rig (${result.parts.scale_rig_per_source} rig px per source px), seam rule ${result.seamRule}, projection rule ${result.projectRule}`);
|
|
812
|
+
// Issue #953: what the painting's edges resolved, under the visible rule only.
|
|
813
|
+
if (result.paintingEdges !== undefined) {
|
|
814
|
+
const e = result.paintingEdges;
|
|
815
|
+
log(`painting edges: ${e.resolved} edge px resolved from the painting (${e.unseparated} against the two reference colours where the window's far sides overlap), ${e.changed} part px changed; left as they were: ${e.skipped.cover} under a part the two-layer model does not carry, ${e.skipped.contrast} where the painting draws no edge (contrast below ${EDGE_CONTRAST}), ${e.skipped.locked} kept, feathered, a patch or a turn's moving pair, ${e.skipped.noBehind} with no behind pixel in the window; ${e.skipped.pieces} part px not written so a part's art keeps its pieces`);
|
|
816
|
+
}
|
|
808
817
|
// Issue #359: one run, said, so a reader of the lines knows no head run was placed.
|
|
809
818
|
if (runs.single) log(` one run: ${input.full} holds the face (${SINGLE_RUN_FACE_TAGS.join(', ')}), so no head run is read; every part comes from the full run`);
|
|
810
819
|
// Issue #366: a file that is not the resolution square was padded to it; said, with where parts.json records it. None for a square file.
|
|
@@ -2241,7 +2250,8 @@ export interface BuildInput {
|
|
|
2241
2250
|
head: string | null;
|
|
2242
2251
|
out: string;
|
|
2243
2252
|
seam?: SeamRule;
|
|
2244
|
-
project
|
|
2253
|
+
/** `--project`; absent, `config.assemble.project`, then {@link DEFAULT_PROJECT_RULE} (issue #953). */
|
|
2254
|
+
project?: ProjectRule;
|
|
2245
2255
|
loop: boolean;
|
|
2246
2256
|
/** `--page-edges` for both packed builds, the rig stage's gate and the check's artifact. */
|
|
2247
2257
|
pageEdges: PageEdges;
|
|
@@ -2446,7 +2456,8 @@ export function build(input: BuildInput, run: BuildRunners, log: Log): BuildResu
|
|
|
2446
2456
|
// Issue #607: the bytes this read parsed are the ones check.json's config record hashes, under the path as typed.
|
|
2447
2457
|
const loaded = loadConfigWithBytes(input.config);
|
|
2448
2458
|
const seam = input.seam ?? loaded.config.assemble.seam ?? DEFAULT_SEAM_RULE;
|
|
2449
|
-
|
|
2459
|
+
const project = input.project ?? loaded.config.assemble.project ?? DEFAULT_PROJECT_RULE;
|
|
2460
|
+
log(`rig-parts build: ${input.config} -> ${out}, seam rule ${seam}, projection rule ${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)`}` : ''}`);
|
|
2450
2461
|
configRecord = inputFileRecord(loaded.bytes, input.config);
|
|
2451
2462
|
req = buildRequirements(input.requirements, loaded.config.requirements, input.config);
|
|
2452
2463
|
// The same for the requirements file's own shape; its names resolve against the rig, in the check, before it builds.
|
|
@@ -2456,7 +2467,7 @@ export function build(input: BuildInput, run: BuildRunners, log: Log): BuildResu
|
|
|
2456
2467
|
// And the steady-wind band spec (issue #590), as check reads it.
|
|
2457
2468
|
if (input.windBand !== undefined) readWindBand(input.windBand);
|
|
2458
2469
|
stages.assemble(
|
|
2459
|
-
{ source: input.source, full: input.full, head: input.head, config: input.config, seam, project
|
|
2470
|
+
{ source: input.source, full: input.full, head: input.head, config: input.config, seam, project, ...(req === null ? {} : { requirements: req.read }), ...(input.alts === undefined ? {} : { alts: input.alts }) },
|
|
2460
2471
|
{ partsJson: join(out, 'parts.json'), partsDir: join(out, 'parts'), recomposite: join(out, 'recomposite_rig.png'), errorMap: join(out, ERROR_MAP_FILE) },
|
|
2461
2472
|
prefixed('assemble'),
|
|
2462
2473
|
);
|