@rig-c/parts 1.14.0 → 1.15.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 +9 -8
- package/docs/AUTHORING.md +39 -25
- package/docs/MIGRATION.md +62 -0
- package/docs/STABILITY.md +3 -3
- package/package.json +1 -1
- package/src/build.ts +11 -9
- package/src/check.ts +100 -24
- package/src/contour.ts +41 -5
- package/src/meshdraft.ts +28 -6
- package/src/requirements.ts +6 -5
- package/src/rig.ts +1 -1
package/cli.ts
CHANGED
|
@@ -17,7 +17,7 @@ import { tmpdir } from 'node:os';
|
|
|
17
17
|
import { basename, dirname, extname, join } from 'node:path';
|
|
18
18
|
import { DEFAULT_PROJECT_RULE, DEFAULT_SEAM_RULE, HOLES_LISTED, PROJECT_RULES, type ProjectRule, proposeFields, proposePlan, SEAM_RULES, type SeamRule } from './src/assemble.ts';
|
|
19
19
|
import { assembleStage, build, checkStage, ERROR_MAP_FILE, loopStage, readRuns, readSource, rigStage, suppliedCheckFlag } from './src/build.ts';
|
|
20
|
-
import { ACCEPTABLE_BARS, DEFAULT_PACK_SHAPE, DEFAULT_PAGE_EDGES, findRigc, PACK_SHAPES, type PackShape, PAGE_EDGES, type PageEdges, PARTS_HOME_SENTENCE, RIGC_GEOMETRY_VERSION, type RigcRunner, NO_PARTS_SENTENCE, SEAM_MEAN_BAR, SOURCE_SENTENCE, SEAM_PX_BAR, SEAM_PX_LEVEL, SPINEBOY_YARDSTICK, TEXTURE_STRETCH_CEILING } from './src/check.ts';
|
|
20
|
+
import { ACCEPTABLE_BARS, DEFAULT_PACK_SHAPE, DEFAULT_PAGE_EDGES, findRigc, PACK_SHAPES, rigcRunnerAt, type PackShape, PAGE_EDGES, type PageEdges, PARTS_HOME_SENTENCE, RIGC_GEOMETRY_VERSION, type RigcRunner, NO_PARTS_SENTENCE, SEAM_MEAN_BAR, SOURCE_SENTENCE, SEAM_PX_BAR, SEAM_PX_LEVEL, SPINEBOY_YARDSTICK, TEXTURE_STRETCH_CEILING } from './src/check.ts';
|
|
21
21
|
import { ComfyClient, resolveHost, runPainting, runSeeThrough } from './src/comfy/index.ts';
|
|
22
22
|
import { type CharacterConfig, loadConfig, loadEarlyConfig, loadPaintConfig } from './src/config.ts';
|
|
23
23
|
import { PartsError, problemLine } from './src/errors.ts';
|
|
@@ -347,8 +347,8 @@ usage:
|
|
|
347
347
|
BREATH_VISIBLE, BLINK_NO_HOLE, TIP_OVER_ROOT, STILL_REGIONS_DARK and
|
|
348
348
|
RECOMPOSITE_HOLES; without an idle the loop and every line that reads idle
|
|
349
349
|
frames, and no idle is rendered. The gate always runs. When motion.json
|
|
350
|
-
holds an animation
|
|
351
|
-
rigc render --folds, physics constraints left out: a mesh triangle turned
|
|
350
|
+
holds an animation, ANIMATIONS_DO_NOT_FOLD reads each, the idle included,
|
|
351
|
+
with rigc render --folds, physics constraints left out: a mesh triangle turned
|
|
352
352
|
over against the setup pose fails, unless the --requirements file's
|
|
353
353
|
may_fold declares that slot and animation with a why. The last line says
|
|
354
354
|
how many of the bars measured — nine, ten with the fold bar — and names
|
|
@@ -825,14 +825,15 @@ function cmdRig(args: string[]): number {
|
|
|
825
825
|
* `cli_core.ts` and rigc's own validator where it does not. No stage runs a
|
|
826
826
|
* rigc source file by path: under rig-c 2.0, `cli.ts` by path is the
|
|
827
827
|
* spine-core entry whether or not spine-core is there. `src/` is pure and takes
|
|
828
|
-
* the spawn from here.
|
|
828
|
+
* the spawn from here. A call that fails carries which rigc ran it, path and
|
|
829
|
+
* version ({@link rigcRunnerAt}, issue #337), and every refusal quoting it says so.
|
|
829
830
|
*/
|
|
830
831
|
function rigcRunner(bin: string): RigcRunner {
|
|
831
|
-
return (args) => {
|
|
832
|
-
const r = spawnSync(
|
|
833
|
-
if (r.error !== undefined) return { status: 127, out: `could not start ${
|
|
832
|
+
return rigcRunnerAt(bin, (b, args) => {
|
|
833
|
+
const r = spawnSync(b, [...args], { encoding: 'utf8', maxBuffer: 1 << 28 });
|
|
834
|
+
if (r.error !== undefined) return { status: 127, out: `could not start ${b}: ${r.error.message}` };
|
|
834
835
|
return { status: r.status ?? 1, out: `${r.stdout ?? ''}${r.stderr ?? ''}` };
|
|
835
|
-
};
|
|
836
|
+
});
|
|
836
837
|
}
|
|
837
838
|
|
|
838
839
|
/** The command's valued flags as `--flag value` pairs, each one its {@link TAKES} row holds; `required` must all be given. Its bare switches are taken out by the caller before this reads the rest. A string is a usage error. */
|
package/docs/AUTHORING.md
CHANGED
|
@@ -98,7 +98,7 @@ read by no CPU stage).
|
|
|
98
98
|
| `assemble.cuts` | assemble; `meshes`/`regions` keys may name the new parts too, and must (each is a part like any other) | **authored**, optional (issue #170): `[{from, into, polygon, draw, overlap, under}]`, `under` optional — a plan part's layer cut by a polygon the author draws: the pixels of plan part `from` whose **centre** lies inside `polygon` (even-odd; a centre exactly on an edge is outside) leave `from` and make the new part `into`, colour and alpha, so every pixel of the layer ends in exactly one of the two. `polygon` is `[[x, y], …]`, at least three points, non-negative integer **rig** pixels — the space of `parts.json` and of the patches' boxes. The file that shows the layer in those coordinates is `assemble`'s own `rig/parts/<from>.png`, placed at its `parts.json` `x`, `y` (the PNG's pixel `(i, j)` is rig pixel `(x + i, y + j)`), or `render/recomposite_rig.png`, the whole rig 1:1; the contact sheets (`sheet`) are scaled and carry no coordinates. `draw` is `"back"`, `"front"` or `{"before": "<plan part>"}`, as for a patch (to keep the setup pose, draw the piece immediately in front of `from` — `{"before": "<the plan part after from>"}`, or `"front"` when `from` is the last plan part; `{"before": "<from>"}` draws it behind `from`, which `under` refuses as `ASSEMBLE_UNDER_DRAW` because the fill would hide it). `from` must be a plan part (`CONFIG_NAME_RESOLVES`; a patch or another cut's piece is refused), `into` a new name (`CONFIG_PART_UNIQUE`), the polygon three or more integer points (`CONFIG_FIELD_TYPE`). The piece's `parts.json` record carries `from`'s `<run>:<tag>` and its own counts, each split from the layer's by where the pixel lies; it is a mesh or a region under its own name, and the coverage rule holds it like a plan part. What the config cannot know the stage refuses: a polygon point past the rig (`ASSEMBLE_CUT_INSIDE`), two cuts of one part sharing a pixel (`ASSEMBLE_CUT_OVERLAP`), a cut that takes none of the layer's opaque pixels or leaves `from` none (`ASSEMBLE_CUT_PIXELS`). `overlap` is required, a non-negative integer of rig pixels with no default (`CONFIG_FIELD_TYPE`): the **band** — the polygon's pixels within `overlap` px of its edge by Chebyshev distance (the polygon's mask less its erosion by a `2 * overlap + 1` square) — stays in `from` as well as going to the piece, so the piece's resampled edge has art under it; the counts stay a strict partition and the band is the base's extra copy, printed on the `cut:` line. At `0` the two abut and `check`'s seam bar judges the edge between them (`CHECK_SEAM_WITHIN_BAR`, §5). A band only covers the edge at rest: a piece that swings well clear of where it was painted shows the hole it leaves, and a thin piece's band shows a ghost of the piece. `under` (issue #193), optional, is the author's image of what lies under the piece: a PNG path relative to the config's directory (`CONFIG_FIELD_TYPE` unless a non-empty string), in rig pixels over the polygon's **bounding box** — the least and greatest polygon x and y, so the image is `max x − min x` by `max y − min y` and its pixel `(i, j)` is rig pixel `(min x + i, min y + j)`. Where the cut removed pixels from `from` — the piece's pixels (alpha above 0) less the band — `from` takes the image's pixel, colour and alpha wherever the image's alpha is at least the alpha the cut removed there (**filled**); elsewhere `from` stays empty there (**left empty**): a transparent pixel is the author's statement that nothing lies under it (the background, or a part drawn behind), and a less opaque one is not half-filled. The image is read nowhere else, so its pixels over the band, over the box outside the polygon or where the layer had no art never change `from`. The `cut:` line then ends `; under <n> px filled, <m> px left empty` (`n + m` is every removed pixel), so an unintended hole is a number in the log, not a guess; the filled pixels count in `from`'s `opaque_px` and `occluded_px` (the piece is drawn over them), never as visible or projected. The stage refuses: a file that is not there (`ASSEMBLE_UNDER_PRESENT`) or not a PNG (`ASSEMBLE_UNDER_DECODES`), an image not exactly the box's size (`ASSEMBLE_UNDER_SIZE`, both sizes given), an image that fills none of the removed pixels — all transparent there, or another file (`ASSEMBLE_UNDER_EMPTY`; to leave the whole hole, drop `under`), and a piece drawn behind `from` (`ASSEMBLE_UNDER_DRAW`: the fill would hide it; draw it `"front"` or `{"before": "<the plan part after from>"}`). Producing the image is the author's, by whatever means; absent, `from` keeps only its band |
|
|
99
99
|
| `bones` | rig; `propose --from-config` | **proposed** by `propose`, then corrected. A single bone `{name, parent, at, tip?}` or a chain `{chain, parent, points, tip}` whose links are named `<chain>0 … <chain>n`. Parents come before children. In `rig.json` each link is **turned along its chain** — `rotation` is the direction from its origin to the next link's (the last link's to `tip`) and `length` that distance (issue #73), so a physics constraint added downstream finds a lever; under `--idle-keys ctl` the link's `<link>_ctl` carries the turn and the length and the link sits at local rotation 0 beneath it. A single bone with a `tip` is turned the same way, towards its tip, with that distance as its length (issue #239); a tip on the bone's own origin is refused (`RIG_TIP_DISTINCT`). The five face bones `eye_r`, `eye_l`, `brow_r`, `brow_l` and `mouth` (by name, when one has no tip of its own) are turned to the eye line — the line through the origins of `eye_r` and `eye_l` as `bones` writes them, read image-left to image-right, drawn when parts.json holds both an `eyewhite-r` and an `eyewhite-l` part — so the blink's `scaley` squashes across the eye line on a tilted head; move an eye bone and the line moves with it. With one eyewhite or none, or an eye bone missing, the rig stage's `face axis:` line says so and they keep the page's axes. A bone under a face bone keeps the face's axis. With the line, `rig.json` also declares `invariants.axes` — one entry, `"face"`, through `eye_r` and `eye_l`, holding the turned face bones (and their controls) — so rig-c's gate holds the build to the line; the line is the one rigc draws through the same two bones, so the tolerance is only the float floor — the largest gap the emitted (float32) bones have to it, rounded up to 6 places plus one 6-place unit — and the `face axis:` line prints it. With the line the rig stage also adds `head_up` (issue #250): a slotless bone under `head`, at head's origin, turned to the eye line + 90 degrees and as long as the eye midpoint lies from head's origin along that axis — the head's frame, which a rule that needs it reads; `head` keeps its own frame, so no key on it or under it changes meaning, and a config bone of that name is refused (`RIG_HEAD_UP_NAME_FREE`). A face bone with its own `tip` is held too (issue #258), in a second entry, `"face_tips"`, when its tip lies along the line within whole-pixel rounding — a tip written in whole pixels sits at most sqrt(2)/2 px off the point of the line it stands for, so its direction may sit up to asin((sqrt(2)/2) / length) degrees off — with its tolerance measured the same way; a tip further off is the bone's own axis, and the `face axis:` line names it with its gap and that bound. A failure is rig-c's `A51_BONES_LIE_ALONG_THEIR_DECLARED_AXIS` line, which names the bone and the fix (rig-c's `docs/AUTHORING.md` §3.7 and §5.2); with no line no entry is written and A51 reports SKIP. Every region on a turned bone is turned back, so the setup pose does not move. Any other single bone keeps world rotation 0; one whose parent is a link is turned back upright |
|
|
100
100
|
| `meshes.<part>` | rig | **proposed**, then corrected. Exactly one of `grid` (the lattice: cell size, px — what `propose` writes: by default one spacing for the figure, round(H / 37) rig px with H the height of every part's box together, a part whose tenth of its short side is already coarser keeping that, and under `propose --grid-rule part` the reference's tenth of the part's short side clamped to 5..36, issue #265), `contour` (the outline mode, authored; the next row and "A contour mesh" below) or `auto` (the automatic mode, authored; "An automatic mesh" below) — two, or none, is `CONFIG_MESH_MODE`. In every mode: `r` (added to every distance before weighting: `w = 1/(d + r)²`), `segments` (a chain name, a bone name with a `tip`, or `[bone, [x0,y0], [x1,y1]]`), and, **authored**, optional, `exponent` (issue #161): a number above 0 that replaces the 2 in that rule, `w = 1/(d + r)^exponent` — a larger one hands each vertex more fully to its nearest bone; absent is 2 and changes no byte, a value that is not a number above 0 is `CONFIG_FIELD_TYPE` at `meshes.<part>.exponent`, and a declared one is echoed as `exponent` in the mesh's `mesh_report.json` row; the exponent is the distance rule's, so declared beside `rule: "heat"` it is refused (`CONFIG_FIELD_TYPE` at `meshes.<part>.exponent`). The segment list is the one authored decision about a layer: which bones may pull it. The slot's bone is the first segment's. Optional `rule` (issue #161): `"distance"` — the rule above, and what an absent `rule` means (every byte is the same) — or `"heat"`, bone heat over the part's own silhouette, in every mode: the lattice's art (alpha above 8, less any island a declared `stray` left out) with holes filled, or for `contour` and `auto` the silhouette their outline is traced from (that mode's art — alpha above 8 for `contour`, alpha 1 and above for `auto` — less any island a declared `stray` left out, holes filled, grown by its `margin`); each bound bone's sources are the silhouette pixels whose centre lies within 0.75 px of its segment (a pixel two bones reach goes to the nearer, a tie to the first listed), held at 1 for that bone and 0 for the others; the field is solved by row-major Gauss–Seidel over-relaxation to a sweep change under 1e-7 within 20000 sweeps; a vertex reads the nearest silhouette pixel and takes the same cap and floor (and `regions`) as the distance rule; `r` is not read. The mesh's `mesh_report.json` row gains `heat` (the constants, each bone's sources and sweeps, the residual). Any other value is `CONFIG_FIELD_TYPE`; a bound bone with no source pixel is `RIG_HEAT_BONE_SOURCE`, an island no bone has a source in `RIG_HEAT_ISLAND_SOURCE`, a solve past its sweep bound `RIG_HEAT_CONVERGED` — none is weighted 0. The rule is chosen per mesh, so `RIG_HEAT_BONE_SOURCE` names three remedies: move the segment onto the art, take the bone out of `segments`, or keep this part on the distance rule — the one to take when the bone anchors the part from outside its art (a torso bone under a neck), since the first moves a bone another part placed and the second drops the anchor (issue #303). On a lattice, `RIG_HEAT_ISLAND_SOURCE` on an island that is dust — at most 4 px and at most 0.1 % of the part's art px, `mesh-draft`'s line — names `meshes.<part>.stray` and the figure that leaves it out, with the value declared; an island above that line keeps the segment remedy, a real piece being a real authoring question |
|
|
101
|
-
| `meshes.<part>.contour` | rig | **authored**, optional (issue #84): `{tolerance, margin, spacing, maxEdge?, budget?, stray?, bridges?, regions?}`. `tolerance` — rig-c's Douglas–Peucker tolerance on the traced outline, px, 0 or more; `margin` — how far the silhouette grows before it is traced, px: 0, or 1 or more (a pixel joins when its centre lies within `margin` of an art pixel's centre; a value above 0 and below 1 adds nothing and is refused, `CONFIG_FIELD_TYPE`); `spacing` — the background interior spacing, px, above 0; `maxEdge` (issue #265) — the longest outline edge, px, above 0: every simplified edge longer than it is cut into `ceil(length / maxEdge)` equal pieces, each cut point on the 1/256 px grid, before any region or rib touches the outline (absent: nothing is cut, and no byte moves; under 2/256 px the rig stage refuses it, `CONTOUR_PARAMETER`, because two cut points could snap together); the row echoes it in `params` and counts the points it added in `contour.outlineCut`, both only when declared; `budget` — the most vertices the mesh may have (refused above it, nothing thinned); `stray` — the largest island, in art pixels, that may be left out of the mesh (absent: none is); `bridges` (issue #326) — `true`, its one value:
|
|
101
|
+
| `meshes.<part>.contour` | rig | **authored**, optional (issue #84): `{tolerance, margin, spacing, maxEdge?, budget?, stray?, bridges?, regions?}`. `tolerance` — rig-c's Douglas–Peucker tolerance on the traced outline, px, 0 or more; `margin` — how far the silhouette grows before it is traced, px: 0, or 1 or more (a pixel joins when its centre lies within `margin` of an art pixel's centre; a value above 0 and below 1 adds nothing and is refused, `CONFIG_FIELD_TYPE`); `spacing` — the background interior spacing, px, above 0; `maxEdge` (issue #265) — the longest outline edge, px, above 0: every simplified edge longer than it is cut into `ceil(length / maxEdge)` equal pieces, each cut point on the 1/256 px grid, before any region or rib touches the outline (absent: nothing is cut, and no byte moves; under 2/256 px the rig stage refuses it, `CONTOUR_PARAMETER`, because two cut points could snap together); the row echoes it in `params` and counts the points it added in `contour.outlineCut`, both only when declared; `budget` — the most vertices the mesh may have (refused above it, nothing thinned); `stray` — the largest island, in art pixels, that may be left out of the mesh (absent: none is); `bridges` (issue #326) — `true`, its one value: islands are read in this order — **dust first** (those `stray` leaves out), **then faint islands** (every pixel's alpha under 64, a quarter of full opacity: left out the same way, their pixels kept in the image but not meshed, so not drawn, and named in the row's `contour.bridges.faint` with their px, brightest alpha and box), **then every visible island is meshed and bridged, whatever the bridge costs** — overdraw is reported, never a reason to drop art; the largest island, or a tie for it, is never left out, faint or not: dropping it would draft the part without its art, and a part whose main body is faint is a question about the layer, which the plan and the author own, not one for the mesh — and the pieces are joined into the one outline rig-c takes by bridges — the kept islands, holes filled, grow together by `margin`, so islands closer than about twice the margin are one grown silhouette, traced as one; each silhouette is traced, simplified, capped, refined and triangulated as a one-island mesh is; then, starting from the silhouette with the most art, the nearest pair of a vertex on the joined outline and one on a silhouette still apart whose quad holds (wound as the mesh, crossing no outline, holding no other vertex, one closed outline after) is joined by that quad, two triangles over no art, until none is apart. Coverage and overshoot read the silhouettes' triangles; a bridge's area is in `meshArea` and in the row's `contour.bridges` (`islands`, `units`, and per join `unit`, `span` — its two crossing edges, px — `area`, `vertices`, `pair`; and `faint`, only when an island was left out as faint), and the row's `params` echoes `bridges`, both only when declared; `art_coverage` counts a faint island's pixels as art not covered. Absent, a second island is refused as below and no byte moves; on one island it writes the mesh the undeclared entry writes. Any other value is `CONFIG_FIELD_TYPE`; beside `ribs` it is `CONTOUR_PARAMETER` (a rib runs across one outline); a join no pair makes is `CONTOUR_BRIDGE`. A bridge stretches with whatever pulls its two ends apart, so it belongs on a part whose pieces ride one bone — the only case `mesh-draft` drafts; `regions` — local deformation regions, each `{name, shape, bone, spacing, band}` with `shape` `"circle"` (`cx, cy, r`) or `"polygon"` (`points`, 3 or more): `bone` is the region's control bone (any bone `bones` declares), `spacing` the interior spacing inside the region and its band, `band` the width the bone's weight falls across. Every length and position is **rig px**, like every other in the config; the part image is the rig less `(x − 4, y − 4)` (its box less the pad), a translation, so a length is the same number in both. A region's `cx`, `cy`, `r`, `band` and `points` are multiples of 1/256 px (`CONFIG_FIELD_TYPE` otherwise); region names are unique per mesh (`CONFIG_REGION_NAME_UNIQUE`). The alpha threshold is not a field: art is alpha above 8, the lattice's and `check`'s |
|
|
102
102
|
| `meshes.<part>.ribs` | rig | **authored**, optional (issue #188), in the `contour` and `auto` modes: `{<chain>: {stations}, …}`, each key a chain this mesh's `segments` name. Each link of the chain gets a row of vertices across the part at its origin and at `stations` points evenly between its origin and its end (`stations`, a whole number, 0 or more, required; a chain of L links carries L × (stations + 1) ribs), each rib a row of vertices across the part, kept as a row of locked edges; the rule is "Ribs along a chain" under "A contour mesh". Absent places none and changes no byte. Refused: a lattice mesh with ribs (`CONFIG_RIBS_MODE` — its vertices are its grid's corners), a key that is not one of the mesh's chains (`CONFIG_NAME_RESOLVES`), an object naming no chain or a `stations` that is not a whole number from 0 (`CONFIG_FIELD_TYPE`); at the rig stage `CONTOUR_RIB_ART`, `CONTOUR_RIB_LINK`, `CONTOUR_RIB_CROSSING`, `CONTOUR_RIB_EDGE` (the contour refusals). The mesh's `mesh_report.json` row gains `ribs` — the declaration, the ribs placed and the vertices they hold — and its contour report (the source's, in `auto`) lists each rib's vertices |
|
|
103
103
|
| `meshes.<part>.influences` | rig | **authored**, optional (issue #284), in the `grid` and `contour` modes: `{maxInfluences?, floor?}`, at least one. `maxInfluences` — the most bones that pull one vertex, a whole number 1 or more, or `null`, no cap (every bone the mesh's `segments` name; `null` is the config's spelling of a bound declared absent, as in `maxOvershoot` and `weightJump`); absent is 4. `floor` — `"cut"` (the capped shares normalised, any under 0.03 dropped, the rest normalised again; what an absent `floor` means) or `"continuous"` (0.03 subtracted from every capped share, those at or below 0 dropped, the rest normalised again, so a bone fades out of a vertex instead of leaving it in one step). Absent, the mesh is weighted at 4 and the cut and every byte is the one written before the field existed; declared, it applies under either `rule`, and is echoed as `influences` in the mesh's `mesh_report.json` row. `propose` never writes it, and `mesh-draft` carries a lattice's declaration to the contour candidate it drafts. A cap that is not a whole number 1 or more or `null` (the string `"none"` included), another `floor`, or an object naming neither is `CONFIG_FIELD_TYPE`; a vertex the declared cut leaves with no bone is `RIG_INFLUENCES_KEPT`; beside `auto` it is `CONFIG_INFLUENCES_MODE` (that mode's cap and floor are `auto.influences`). What each setting trades is "The weight cut" below |
|
|
104
104
|
| `meshes.<part>.stray` | rig | **authored**, optional (issue #303), in the `grid` mode: the largest island, in art pixels, that may be left out of the lattice — the contour mode's `contour.stray`, with the same meaning, over the same islands (the 4-connected islands of the art, alpha above 8): every island of `stray` art px or fewer is cleared from the art the lattice and, under `rule: "heat"`, its silhouette are built over, except an island as large as the largest, which is never left out (two equal largest pieces both stay). Unlike the contour mode an island above the figure is not refused — the lattice bridges every island it keeps, as it always has. An island that sits in a cell the lattice keeps for other art leaves the lattice unchanged: there `stray` changes only the silhouette `rule: "heat"` reads, and gives back no triangle (demo `bottomwear`'s 2 px island, in the cell kept for the hem's tip, is that case). Absent, nothing is left out and every byte is the one written before the field existed; a figure that leaves nothing out changes no byte of `rig.json` or `motion.json`. The part's image is not touched: a pixel left out stays in it, and is drawn only where a cell kept for other art happens to cover it; `art_coverage` is over all the art, the islands left out included. Declared, the mesh's `mesh_report.json` row echoes `stray` and adds `stray_cleared` (`islands`, `pixels`, `art_pixels`, after `influences`), and the rig stage's mesh line ends `stray <n> left out <i> island(s), <p> of <a> art px`. A value that is not a whole number from 0 is `CONFIG_FIELD_TYPE`; beside `contour` or `auto` it is `CONFIG_STRAY_MODE` (those modes state it in `contour.stray` and `auto.source.stray`). `propose` and `mesh-draft` never write it |
|
|
@@ -107,7 +107,7 @@ read by no CPU stage).
|
|
|
107
107
|
| `motion.tracks` | rig | **proposed**, then tuned. Single `{bone, prop, amp, period, phase, base?}` or chain `{chain, amps, period, phase, lag}` — one amplitude per link, link `i` at phase `phase + lag·i`. Every `period` must divide `duration` (`CONFIG_PERIOD_DIVIDES_DURATION`). A bone property is keyed by one track (`CONFIG_BONE_PROPERTY_KEYED_ONCE`): a chain keys `rotate` on every link, the blink's `eyes` group `scaley` and its `brows` group `translatey` on every member, so a single track on any of those is a second track on it — rigc would refuse it one stage later, `animation "idle" has two tracks on eye.scaley`. The proposed breath (issues #252, #270) is `chest` `translatey` travelling 2.5 % of the torso's length peak to peak — the distance from the `neck` bone to the `hip` bone as the proposal writes them, the run the chest sits half way along, whichever rule placed them — (amplitude half of it to two decimals, `base` the amplitude, so the chest rises from the setup pose and returns); the `breath:` note prints the fraction, the length, the two bones and the pixels. Until #270 it was 2.5 % of the `topwear` part's height, and a sweater to the thighs (`scarf`, 690 px) breathed 2.9 times as deep as `demo`; on neck → hip `scarf` reads 431 px (10.78 px) and `demo` 192 px (4.80 px). Neck and hip on one point: no length to scale by, so the breath keeps the reference's amplitude 1.3 (2.6 px peak to peak) and the note says so — scale it to the figure. `chest` `scalex` and the `head` and `neck` rolls are a ratio and angles and are proposed the same on every figure. A chain link is turned along its chain (the `bones` row), so a `translatex`/`translatey` key on a bone whose parent is a turned link, or a `scale`/`shear` key on a turned link, would move or stretch along the link and not along the picture's axis: the rig stage refuses it (`RIG_KEY_FRAME_UNTURNED`); `rotate` is keyed on any bone |
|
|
108
108
|
| `motion.blink` | rig | **proposed**: `{t, eyes, brows, squash, brow_drop}`; the whole blink, `t` to `t + 0.364` s (`t + 0.314` s without brows: the brows' keys are the ones that end last), must fit inside the idle (`RIG_BLINK_INSIDE_IDLE`). The `eyes` group's `scaley` squashes every part on the eye bones about the bone's origin, the eyewhite's centre. Optional: a figure with no `eyewhite-r`/`-l` part has no eye bone, so `propose` writes no `blink` and notes `no blink: no eyewhite part (looked for: eyewhite-r, eyewhite-l), …`; with eyes and no `eyebrow-r`/`-l` part it writes the blink without `brows` and `brow_drop` and notes `blink without brows: …`. `brows` and `brow_drop` are stated together or not at all (`CONFIG_BLINK_BROWS_PAIRED`), and a group that names no bone is refused (`CONFIG_BLINK_GROUP_MEMBERS`) — rigc would refuse it one stage later, `group "eyes" declares no members` — as is a group that names one bone twice (`CONFIG_BLINK_GROUP_UNIQUE`; rigc: `group "eyes" names member "eye" twice`). An `irides-*` or `eyelash-*` part on a side with no eyewhite has no eye bone to ride: `propose` places it on `head` as a region, which the blink does not move, and notes `no eyewhite part for eye_r (looked for: eyewhite-r): iris (head:irides-r) is placed on head as a region, …`. The timing is fixed, not a field (table below) |
|
|
109
109
|
| `motion.blink.still` | rig | **proposed** only for a lash `propose` notes (below), then checked: `{<part>: {row, bone}}` — the rows of that region part above `row` (rig px, y down) are drawn by a second slot `<part>_still` on `bone` and do not blink; rows from `row` down keep the part's slot and blink. The part must be a region on a bone `eyes` names, `bone` one it does not (`CONFIG_STILL_OFF_THE_BLINK`), and `row` a row of the part with no art across its whole width (`RIG_STILL_ROW_CLEAR`) |
|
|
110
|
-
| `motion.animations_from` | rig; check (named; read for folds) | optional, never proposed (issue #183): a path, relative to the config file's directory, to a file in rig-c's motion-spec shape, `{"spec": "rigc-motion/1", "animations": {"<name>": {"duration": …, "tracks": [...]}, …}}` — an expression, a gesture, anything beside the idle. On every `rig` and `build` each of its animations is written into `rig/motion.json` after `idle`, as read: nothing added, re-keyed or judged here, and each is rigc's to accept or refuse at the rig stage's gate, in rigc's words. It may name what `motion.json` already holds — the bones `bones` declares, the easings `shut` and `open`, the groups `eyes` and `brows` when the blink writes them — and nothing else from the file reaches it. The loader refuses, every problem at once: no such file or one that cannot be read (`CONFIG_FILE_PRESENT`), text that is not JSON (`CONFIG_IS_JSON`), a file that is not an object, a `spec` other than `"rigc-motion/1"`, an `animations` that is not a table of objects (`CONFIG_FIELD_TYPE`), any other key — rig-c's `easings`, `groups`, `setup`, `physics`, `mix`, `archetype` and `cut` included, which would be read and not written (`CONFIG_KEY_KNOWN`), an animation named `idle` (`CONFIG_ANIMATION_NAME_FREE`), and a key written twice in one object of the file, which JSON would fold into its last value without a word (`CONFIG_KEY_UNIQUE`). `check` names the others on one line (`beside the idle: …`) and reads each for folds — `ANIMATIONS_DO_NOT_FOLD` (§7,
|
|
110
|
+
| `motion.animations_from` | rig; check (named; read for folds) | optional, never proposed (issue #183): a path, relative to the config file's directory, to a file in rig-c's motion-spec shape, `{"spec": "rigc-motion/1", "animations": {"<name>": {"duration": …, "tracks": [...]}, …}}` — an expression, a gesture, anything beside the idle. On every `rig` and `build` each of its animations is written into `rig/motion.json` after `idle`, as read: nothing added, re-keyed or judged here, and each is rigc's to accept or refuse at the rig stage's gate, in rigc's words. It may name what `motion.json` already holds — the bones `bones` declares, the easings `shut` and `open`, the groups `eyes` and `brows` when the blink writes them — and nothing else from the file reaches it. The loader refuses, every problem at once: no such file or one that cannot be read (`CONFIG_FILE_PRESENT`), text that is not JSON (`CONFIG_IS_JSON`), a file that is not an object, a `spec` other than `"rigc-motion/1"`, an `animations` that is not a table of objects (`CONFIG_FIELD_TYPE`), any other key — rig-c's `easings`, `groups`, `setup`, `physics`, `mix`, `archetype` and `cut` included, which would be read and not written (`CONFIG_KEY_KNOWN`), an animation named `idle` (`CONFIG_ANIMATION_NAME_FREE`), and a key written twice in one object of the file, which JSON would fold into its last value without a word (`CONFIG_KEY_UNIQUE`). `check` names the others on one line (`beside the idle: …`) and reads each for folds, with the idle — `ANIMATIONS_DO_NOT_FOLD` (§7, issues #320, #333); every other bar reads the idle. Absent, `motion.json` holds the idle alone, byte for byte as before, and `check` reads that idle for folds |
|
|
111
111
|
| `constraints` | rig (handed to rig-c as `rig.json`'s `constraints`); `compare` and the coverage lines (roles) | **authored**, optional; `propose` writes none. A list of constraints in **rig-c's own rig-spec shape** (`RigConstraint`: `ik`, `transform`, `path`, `physics`, `slider` — rig-c's `src/rig.ts` documents every field), in the order rigc applies them. The loader checks only what this package owns: each entry is an object with a `type` rigc names (`CONFIG_CONSTRAINT_TYPE_KNOWN`) and a non-empty `name`, unique within its kind as rigc finds constraints (`CONFIG_CONSTRAINT_NAME_UNIQUE`; an ik and a transform may share one); every bone it names — `bones`, an ik's `target`, a transform's `source`, a physics or slider constraint's `bone` — is a bone `bones` declares, a chain link as `<chain><i>` (`CONFIG_NAME_RESOLVES`); the bone a constraint follows does not sit under a bone it drives (`CONFIG_CONSTRAINT_TARGET_DETACHED`). Every other field (`mix`, `softness`, `properties`, a path's `slot`, a slider's `animation`) and every value is rigc's to accept or refuse at the rig stage's gate, in rigc's words (`RIG_RIGC_GREEN`) — an ik's `bones` as a shape too: from rig-c 2.15.0 rigc refuses an ik over more than two bones, or over a pair whose second bone is not the first's child (issue #103; this loader refused both itself until then). A note and an `x-` record may ride on an entry; the rig stage leaves them out of what it hands rigc. Under the default `--idle-keys ctl` a two-bone ik whose child link the idle keys is split by that link's control, and rigc refuses the pair: run `rig --idle-keys direct` or `build --idle-keys direct` (the rig stage adds that sentence, naming the flag on the command that ran, to rigc's line; `build` forwards it to its rig stage, issue #95). Each bone a constraint follows is declared to rigc as `invariants.detached` from every bone it drives, which rigc's gate rule `A25` checks |
|
|
112
112
|
| `turn` | rig (adds the turn control) | **proposed** only with `propose --full`/`--head` (issue #251), then corrected: `{pivot, depths}`. `depths` holds, per part, the See-through layer's `depth_median` the part came from (larger = further back), read from the run the part was assembled from; a `painting:` patch has none and is not carried. A rider (issue #271) — a part from an `eyewhite`, `irides`, `eyelash`, `eyebrow`, `nose` or `mouth` layer — is drafted at the `face` part's depth, 0.001 nearer when parts.json draws it after the face and 0.001 further when before: See-through makes no depth of its own for the eye group (it cuts them from one depth map of the combined eyes) and clamps nose and mouth to the face's median − 0.001, and the two runs' own medians for these layers disagreed by up to 0.47 and swapped their order against the face. Every other layer — ears, headwear and earwear included — keeps its own median. With no `face` part, or more than one, the riders keep their own and a note says so. One note lists each part's depth and where it came from; a carrier the other run places more than 10 % of `pivot − nearest` away gets a note naming both values and the run read — the two runs are not reconciled. `pivot` is the depth that stays put; `propose` drafts the mean of the parts' depths weighted by `opaque_px`, the one depth at which the painted pixels' centroid does not slide under the dial. The rig stage adds a bone `turn` under `root` at its origin that nothing keys — its local x is the dial, in rig px — and carries each part by `(pivot − depth) / (pivot − nearest depth)` of it: the nearest part all of it, a part behind the pivot the other way, a part at the pivot not at all. Each part rides leaf twins (`turn_<part>_<bone>`, an identity child of every bone its slot and weights bind) moved by one `transform` constraint (`turn_<part>`, after the config's own); the idle's keys, the `<bone>_ctl` controls and a physics constraint stay on the bones, and the dial moves none of them. At rest: with `turn` declared, no pixel differs by more than 1 level at dial 0 (not byte-identical; the cause is float noise in the identity child bones, and the constraints add nothing); without `turn`, byte-identical. Neither field has a default (`CONFIG_FIELD_PRESENT`); the pivot must lie behind the nearest part (`CONFIG_TURN_PIVOT_BEHIND_NEAREST`). Absent, no control is added and no byte of any output moves |
|
|
113
113
|
| `scene` | assemble (the recomposite, its holes and the figure's mask); check, through `parts.json`'s `scene` block (`RECOMPOSITE_HOLES`, the seam) | **authored**, optional; `propose` writes none (issue #242). `{}` declares the painting a scene: the figure and its surroundings in one image, See-through's full run holding only the figure. With it, `assemble` reads the recomposite figures, the hole list and the `uncovered:` lines inside the figure — the rig pixels where any full-run layer's alpha is above 8 (`OPAQUE_ALPHA_ABOVE`, the cut every layer's painted pixels are read by) — each line starting `inside the figure (config.scene: …, <n> of <m> rig px)`; writes that figure as `figure_rig.png` (white on black) beside the recomposite, and a `scene` block in `parts.json`; flags error pixels on the error map inside it only (outside, the painting grey); and `check` leaves out of the seam every frame pixel whose composite sample reaches the painting's outermost row or column, saying how many (`seam_border` in `check.json`, and on the seam line). It takes no field: the figure and its threshold are derived. A key in it other than a record or an annotation is `CONFIG_KEY_KNOWN`. Absent, no byte of any output moves |
|
|
@@ -1172,7 +1172,7 @@ issue #320). [`examples/reference-sample`](https://github.com/rig-c/parts/tree/m
|
|
|
1172
1172
|
```
|
|
1173
1173
|
[rig] animations beside the idle, from …/animations.json as written: hop
|
|
1174
1174
|
[check] beside the idle: 1 animation(s), hop — built and gated by rigc with the rig; ANIMATIONS_DO_NOT_FOLD reads each for folds, every other bar here reads the idle
|
|
1175
|
-
[check] ANIMATIONS_DO_NOT_FOLD: PASS —
|
|
1175
|
+
[check] ANIMATIONS_DO_NOT_FOLD: PASS — 2 animation(s) read by rigc render --folds at 12 fps, its 2 physics constraint(s) left out (idle, hop): 0 folding mesh(es), 0 not declared in may_fold (<= 0)
|
|
1176
1176
|
[check] check: PASS; 10 of 10 bar(s) measured, 0 skipped
|
|
1177
1177
|
```
|
|
1178
1178
|
|
|
@@ -1226,8 +1226,8 @@ way there, are in their READMEs, in the repository:
|
|
|
1226
1226
|
**Limits.** Every slot holds one attachment, the part's own image, so an expression moves,
|
|
1227
1227
|
turns and scales the face's parts and never swaps one for another: there is no second mouth
|
|
1228
1228
|
or eye shape to key, and no lip-sync. `check` reads an animation beside the idle for folds
|
|
1229
|
-
alone (`ANIMATIONS_DO_NOT_FOLD`, §7): beyond rigc's gate, no seam,
|
|
1230
|
-
it. On the
|
|
1229
|
+
alone (`ANIMATIONS_DO_NOT_FOLD`, §7, which reads the idle too): beyond rigc's gate, no seam,
|
|
1230
|
+
stretch or hole bar has read it. On the
|
|
1231
1231
|
examples each clip was read by `check`'s own `TEXTURE_STRETCH` reading applied to the clip's
|
|
1232
1232
|
`rigc render --geometry` (their READMEs give the figures); that is a measurement, not a
|
|
1233
1233
|
gate, and it is where an arm raised overhead was found to tear `demo`'s elbow (5.327
|
|
@@ -1519,7 +1519,16 @@ full loader before its assemble stage writes anything.
|
|
|
1519
1519
|
is drafted. A bridge covers no art: the part's `candidate.bridges` in
|
|
1520
1520
|
`mesh_draft.json` gives each join's span and area and the share of the
|
|
1521
1521
|
candidate's overdraw they draw (`overdraw`), and the five readings below
|
|
1522
|
-
read the bridged mesh as they read any candidate.
|
|
1522
|
+
read the bridged mesh as they read any candidate. Islands are read in a
|
|
1523
|
+
fixed order: dust first (`stray`, above); then **faint** islands — every
|
|
1524
|
+
pixel's alpha under 64 — are left out of the draft and the line names
|
|
1525
|
+
each (`LEFT OUT as faint`, its px, its brightest alpha, its box;
|
|
1526
|
+
`candidate.bridges.faint`): not meshed, so not drawn, and if those pixels
|
|
1527
|
+
matter to you, keep the lattice for that part; then every visible island
|
|
1528
|
+
is bridged, whatever its bridge costs — overdraw is reported, never a
|
|
1529
|
+
reason to drop art. On the public examples the faint islands a draft
|
|
1530
|
+
reaches peak at alpha 11–17 (demo `earring`'s 8, 2 and 1 px specks) and
|
|
1531
|
+
every visible one at 255. Each candidate is
|
|
1523
1532
|
kept only where it holds against that part's
|
|
1524
1533
|
lattice: every `check` bar that reads the part passes; within 1 rig px of
|
|
1525
1534
|
the lattice over the idle; at the bend set (every chain link the part's
|
|
@@ -1566,7 +1575,7 @@ proposal in as the steps above say (`RL01`); every step must exit 0.
|
|
|
1566
1575
|
| `propose` | `note:` lines, `LINT` lines, the `coverage` lines, `landmarks.png`, `basis.json` | no LINT line: every chain link lies on its mesh's art, and the hip is below the chest and the figure's top quarter; the `coverage:` summary says how many bones that verdict covers — a `not checked` bone was read by no check (its line says why), and a `ratio` or `fallback` bone in `basis.json` is a guess | a link off the art (a bone on the background) — move it onto the layer; `LINT <link> at … runs to … N degrees off the head's down axis …` — a fringe link that does not lean with the head (§3, the fringe in the head's frame): move it along the lock it drives; `LINT hip at [x, y] is not below chest at [x, y]: …` or `LINT hip at [x, y] is above 0.25 of the figure height (figure y T..B, so hip y must be at least L): a hip at the shoulders` — move `hip` down to the waist (and `chest` between it and the neck); a `hanging strand … -- no chain proposed` note — that strand hangs stiff until you add a chain down the x and rows it names (§3); a `no blink: …` or `blink without brows: …` note — the idle will not blink (or its brows will not drop), because no part came from an `eyewhite` (or `eyebrow`) layer; the note names the tags looked for; a `no eyewhite part for eye_<s> …` note — the iris and lash parts it names ride `head` and do not blink, because no `eyewhite-<s>` part made that side's eye bone: take the eyewhite from the other run, or keep them on `head`; a `no face part: face box derived from …` note (always the first) — `head`, `neck` and every face-height scale come from a box guessed off the hair and neck (§3): check `head` and `neck` on `landmarks_head.png` and move them; when it adds `the blink shuts the eyes over no face part`, read `BLINK_NO_HOLE` after `check` — an eye with no face under it can open a hole (the `reference-sample` with only its face removed: 587 px). Under `--keypoints`: the `lint rule set:` line first (joints, or screen and why), then the joint notes — `missing: the rule "…" placed bone X instead` is a bone still guessed; `not used — …` names why an arm's joints did not place its sleeve (for a wrist on no part, the nearest part and the `--wrist-snap` that would bind it); a `wrist snap: …` note is a wrist bound past 15 px by the flag — check that chain's tip on `landmarks.png`; a `one handwear part holds both wrists` note is two chains on one mesh — cut the part with `assemble.cuts` to separate them; `LINT chest at … is not between the neck joint …`, `LINT hip at … is not past chest …`, `LINT hip at … is nearer the neck joint …` and `LINT <chain><i> at … does not advance from <s>_shoulder toward <s>_wrist` are the joint-frame lines: move that bone between, past or along the joints they name |
|
|
1567
1576
|
| `compare` | one line per pair (origin, parent, tip, length, direction — or SKIP and why), then the per-row summary, the unmapped bones of each side, `required:`, each side's roles and the `controls:` line | proposal against the config you corrected: the rows you changed and no others. Config against the `rig.json` `build` writes for it: measured on both examples, every origin within 6.19e-7 px (the rig's offsets are written to 6 places) and every chain link's tip and length within 4.99e-4 px (`length` is written to 3), printed to three places as `0.000` (a signed figure as `+0.000` or `-0.000`); `parent … is the right's ancestor at depth 2, <bone>_ctl between` for every keyed mesh bone, no `DIFFERENT` and no `NOT MAPPED` | `parent DIFFERENT: left a, right b` — a bone hangs from another parent than the other side's; `frames not related` — a rig with no stage (`skeleton.width`/`height`), two configs at different `rig_scale`: the distance rows SKIP, so declare the frame in a `--map` file if the author knows it; `FAIL STRUCTURE_REQUIRED_PRESENT` — a bone the map requires is missing through its pairs |
|
|
1568
1577
|
| `rig` (inside `build`) | one line per mesh: vertices, triangles, bones, influences, `cover`; the `bones` line; the `idle keys` line; then rigc's gate lines (with A15's declared SKIP under `--idle-keys direct`); in `rig.json`, each chain link's (or its `_ctl`'s) `length` and `rotation` | `cover 1.00000` on every mesh, both gate summaries `0 failed` (the compile and the packed pages); every link's `length` the distance to the next link and `rotation` its direction (Spine degrees, counter-clockwise, y up, local to the parent), every offset under it — a child bone's `x, y`, a weight's bind `x, y`, a region's `x, y` — in that turned frame, and a region on a link carrying `rotation` that turns it back upright. Nothing moved: `flattenRig` (`src/rig.ts`) turns every offset back and gives the unturned numbers | `RIG_LATTICE_ONE_LOOP`: change that mesh's `grid` |
|
|
1569
|
-
| `check` (inside `build`) | the gate lines verbatim, the pack line, `loop:` (and `loop physics:` when the rig declares a physics constraint, §7), `seam:`, the six judgement lines, then — when `motion.json` holds an animation
|
|
1578
|
+
| `check` (inside `build`) | the gate lines verbatim, the pack line, `loop:` (and `loop physics:` when the rig declares a physics constraint, §7), `seam:`, the six judgement lines, then — when `motion.json` holds an animation, the idle included — `ANIMATIONS_DO_NOT_FOLD` and one `folds` line per folding mesh, rigc's own (issues #320, #333), `RECOMPOSITE_HOLES` and, under `--source`, `SETUP_POSE_VS_SOURCE` (§7), `check.json`; the last line says how many of the bars measured — nine, ten with the fold bar — and names the ones that said SKIP | `check: PASS; 10 of 10 bar(s) measured, 0 skipped` on all four examples — `demo`, `scarf` and `reference-sample`, which carry animations beside the idle, and `reference-demo`, whose idle alone the fold bar reads (issue #333); a judgement line SKIP only where the character lacks what it reads; on a rig spec with no `parts.json` or no `idle` (a merged rig, §7 *Measuring a rig rig-parts did not assemble*), PASS with the skipped bars named | `CHECK_SEAM_WITHIN_BAR` or `CHECK_LOOP_CLOSES` (§6); `CHECK_ANIMATIONS_DO_NOT_FOLD` — a clip turns a mesh over (§6) |
|
|
1570
1579
|
| `check --requirements` (and `build --requirements`) | after the lines above, one line per declared requirement, `NAME: PASS`, `FAIL` or `NOT MEASURABLE — …` with its figures, the bar as declared and the worst frame; then `requirements: N declared — M measured (P PASS, F FAIL), K NOT MEASURABLE; not declared: <kinds>`; `check.json`'s `requirements` block; the renders under `requirements/` (§7, *Declared requirements*) | every line PASS and `0 NOT MEASURABLE`; a follow's `released_copy` says whether rigc's consumer-driven door was taken on the throwaway copy; a `stretch` line's `held_by` (issue #321), right after `worst`, names the bones the worst edge's two vertices hang on and their idle chains, in `CHECK_TEXTURE_STRETCH`'s words (§6) and by the same function, on a PASS as on a FAIL | `CHECK_REQUIREMENT_MET` (a figure past its bar; the line names the frame) or `CHECK_REQUIREMENT_MEASURABLE` (the quantity is undefined in the frames: a tip or an axis on a bone of length 0, an aim with no line, a follow no frame of which reaches its least drive); the last line then reads `check: FAIL — B bar(s) not met, R declared requirement(s) not PASS` |
|
|
1571
1580
|
| `compose` | the `[compose]` lines: one per character (its offset, its scale, its placed bounds, the shift and how many first-level bones, regions on `root` and weights bound to `root` it moved, its bone, slot, track and constraint counts, its `rig_scale` and idle), the declared order and the slot count it expands to, rigc's gate lines; then the `[check]` lines as for `check`, and `scene.json` | both gate summaries `0 failed`, `check: PASS` with the bars that measured and the ones that said SKIP named — on a composed rig `4 of 9`: the gate, the loop, `CHAIN_LAG` and `TEXTURE_STRETCH` (there is no `parts.json`; each character's own `build` measured all nine). `scene.json`'s `order.slots` is the order the compiled skeleton draws | a `SCENE_*` refusal (§6, *compose*) — the scene file, a build, or the order; `COMPOSE_RIGC_GREEN` — rigc's own line, quoted |
|
|
1572
1581
|
| `loop` (inside `build --loop`, or `loop --frames … --out …`) | the dropped-duplicate line, or, when the end frame is not frame 0, the jump line: its max \|d\| and px over 40, then why — under `build`, `the rig holds N physics constraint(s), and rigc's render starts every one at rest at frame 0` with the remedy `build --loop --lead-in <n>`, or `the rig holds no physics constraint, so the idle itself does not close` (`CHECK_LOOP_CLOSES`); under `loop --frames`, which reads frames alone, both causes (issue #325); under `build --lead-in`, the lead-in line in its place (the frames kept and the jump at the wrap); each file's line, then `loop: idle.png N B (lossless); idle-indexed.png N B (max …, mean …); idle.gif N B (max …, mean …)` | `f0048.png equals f0000.png byte for byte, so it is dropped` | `LOOP_ENCODE` (§6) |
|
|
@@ -2156,7 +2165,7 @@ exits 0, and `mesh_draft.json` and the console name the reading and the amount.
|
|
|
2156
2165
|
| `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 |
|
|
2157
2166
|
| `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 |
|
|
2158
2167
|
| `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*) |
|
|
2159
|
-
| `CHECK_ANIMATIONS_DO_NOT_FOLD` | an animation
|
|
2168
|
+
| `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 constraints left out (issues #320, #333); 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. Or a `may_fold` entry of the `--requirements` file names a slot and animation that does not fold: an exception that excepts nothing | the clip's keys on the bones that mesh is weighted to (a bend past what the weights carry, 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 |
|
|
2160
2169
|
| `CHECK_FOLDS_READ` | the fold render's `frames.json` names no set for an animation, a `folds.json` is missing, not `rigc-folds/1`, another animation's or another rate's, or a folding mesh has no line rigc printed for it | a rigc problem; report it, with `rigc --version` |
|
|
2161
2170
|
| `CHECK_SEAM_FRAME_SIZE`, `FRAMES_SIDECAR`, `CHECK_GEOMETRY_FILE` | rigc's render is not what its `frames.json` says, or its `geometry.json` is not a whole `rigc-geometry/1` export of the frames beside it (a frame count, a mesh whose vertex count differs from its rest entry; for the face half, another viewport than `frames.json`'s, other frame indices, the head bone or a feature's bone missing from a frame, a non-finite transform or time) | a rigc problem; report it |
|
|
2162
2171
|
| `LOOP_ENCODE` | the loop encoder refused a frame (translucent pixel in a GIF, a size change) | the frames; for a translucent frame write the lossless or the indexed APNG, which keep alpha |
|
|
@@ -2174,7 +2183,7 @@ exits 0, and `mesh_draft.json` and the console name the reading and the amount.
|
|
|
2174
2183
|
| `CHECK_REQUIREMENTS_FRAMES` | a requirement render did not write every frame rigc sampled, or the as-declared, released and full renders of a follow sampled different frames | a rigc problem; report it |
|
|
2175
2184
|
| `CHECK_REQUIREMENT_MET` | a declared requirement measured outside its bar; the figure, its frame and the bar are quoted | the rig or its motion (§7, *Declared requirements*) — or the bar, if the scene asks less |
|
|
2176
2185
|
| `CHECK_REQUIREMENT_MEASURABLE` | a declared requirement is NOT MEASURABLE: what it asks is undefined in the frames, and the reason is quoted. Not a pass | the declaration (a bone with a length, a target off the bone's origin, a least drive the constraint reaches) or the rig |
|
|
2177
|
-
| `REQUIREMENTS_MAY_FOLD` | a `may_fold` entry names
|
|
2186
|
+
| `REQUIREMENTS_MAY_FOLD` | a `may_fold` entry names a slot with no mesh attachment — a region is one quad and cannot fold (issue #320). Refused before anything is built. Before issue #333 an entry naming the idle was refused here too; the bar reads the idle now, and `idle` is an animation name like any other | a mesh slot |
|
|
2178
2187
|
| `REQUIREMENTS_NOT_PLANTED` | a `not_planted` name is a part `check` does not read as a planted foot — it does not come from a See-through `footwear` layer — or there is no `parts.json` to choose the feet by (issue #231). Refused before anything is built | a `footwear` part's name |
|
|
2179
2188
|
| `REQUIREMENTS_TIP_EXCEPTION` | a `not_swinging`, `rises` or `rests` name is a part `TIP_OVER_ROOT` does not read — not from a See-through `handwear` or `bottomwear` layer — or there is no `parts.json` (refused before anything is built); or a `rises` part is not carried by a chain that rises: the chain's **last link**, at the setup pose, runs other than up — from its origin along its `length` (the extent the rig stage turns a link to), or, a last link with no length, from the link before's origin to its own — the detail giving the link read and the direction measured (refused after the idle is rendered, before `check.json` is written) (issues #231, #322; until #322 it read the chain's first link's origin to its last's, which refused a folded arm — a link that hangs, then a forearm raised) | a swinging part's name; for `rises`, a part whose chain does rise |
|
|
2180
2189
|
| `REQUIREMENTS_SEAM` | a `seam` cannot be read against the rig: there is no `parts.json` (a merged rig, §7 *Measuring a rig rig-parts did not assemble*), or the two parts' art does not meet at the setup pose (no 4-adjacent pair). Refused before anything is built | two parts whose art touches, on a rig with its `parts.json` |
|
|
@@ -2446,11 +2455,13 @@ The selftest's planted cases (by hand):
|
|
|
2446
2455
|
| a foot whose root moves `fround(0.05)` rig px | PASS, heat 0.779–0.785 | FAIL, 0.05 rig px |
|
|
2447
2456
|
| a face mesh weighted to a bone sliding `fround(0.05)` under a rolling head | PASS, 0.851–0.855 against 0.936–0.948 | FAIL, 0.05 rig px |
|
|
2448
2457
|
|
|
2449
|
-
**`ANIMATIONS_DO_NOT_FOLD`:
|
|
2450
|
-
reads an animation other than the idle,
|
|
2451
|
-
`TEXTURE_STRETCH`, and a console line after the judgement lines —
|
|
2452
|
-
|
|
2453
|
-
|
|
2458
|
+
**`ANIMATIONS_DO_NOT_FOLD`: every animation, the idle included (issues #320, #333).** The one
|
|
2459
|
+
bar that reads an animation other than the idle, and it reads the idle with them, written — as
|
|
2460
|
+
a key of `check.json` after `TEXTURE_STRETCH`, and a console line after the judgement lines —
|
|
2461
|
+
whenever `motion.json` holds an animation: a rig of the idle alone writes it too, and the
|
|
2462
|
+
summary counts ten bars. `TEXTURE_STRETCH` reads how far the idle's triangles stretch; a
|
|
2463
|
+
triangle that turns inside out is this bar's defect, on the idle as on any clip (issue #333;
|
|
2464
|
+
until then the idle was read for folds by hand, beside the build). It is rig-c's reading, not this package's: one
|
|
2454
2465
|
`rigc render --folds` over every animation of the build, at 12 fps (the idle's rate, under
|
|
2455
2466
|
`--requirements` too: the file's `fps` is its requirements', and a file moves only what it
|
|
2456
2467
|
declares), with frames drawn at rigc's smallest
|
|
@@ -2465,7 +2476,7 @@ exemption that exempts nothing.
|
|
|
2465
2476
|
| field | what |
|
|
2466
2477
|
| --- | --- |
|
|
2467
2478
|
| `status` | `PASS` or `FAIL` |
|
|
2468
|
-
| `animations` | every animation
|
|
2479
|
+
| `animations` | every animation, the idle included, in `motion.json`'s order: each one read |
|
|
2469
2480
|
| `fps` | the rate the reading sampled at |
|
|
2470
2481
|
| `physics_left_out` | how many physics constraints the read build left out (0 without any) |
|
|
2471
2482
|
| `folding` | one row per mesh that folds in an animation: `animation`, `slot`, `attachment`, `frames` (how many show a folded triangle), `worst_frame`, `worst_triangles`, `triangles` (the mesh's), `declared` (the `may_fold` why, or null) and `line`, rigc's printed line verbatim |
|
|
@@ -2483,8 +2494,10 @@ A bar on the reading with physics would have refused a public example for a fram
|
|
|
2483
2494
|
physics at is the scene's. To read a clip with its physics at a rate you choose:
|
|
2484
2495
|
`rigc render --candidate <out>/check/build --animation <name> --fps <n> --folds`.
|
|
2485
2496
|
|
|
2486
|
-
On the examples: `demo` (7 animations), `scarf` (
|
|
2487
|
-
|
|
2497
|
+
On the examples: `demo` (the idle and 7 animations), `scarf` (the idle and 7),
|
|
2498
|
+
`reference-sample` (the idle and 1) and `reference-demo` (the idle alone) read no folding mesh,
|
|
2499
|
+
and neither does the trailer's idle (`rig-c/trailer`, its parts built by this tree, and its film
|
|
2500
|
+
rig's `idle` and `show`) [measured for issue #333, rig-c 2.47.1, physics left out, 12 fps]. What the
|
|
2488
2501
|
reading adds to `check`, measured with `/usr/bin/time` on `demo`'s keyed build (median of
|
|
2489
2502
|
three, user + sys, a machine under load, so the wall figures are not quoted): one run over the
|
|
2490
2503
|
idle and its 7 animations 4.1 s, the idle alone 2.9 s, and the same rig with its 7 animations
|
|
@@ -2625,16 +2638,16 @@ it reads is there, and says SKIP with the reason when it is not:
|
|
|
2625
2638
|
| seam | `parts.json` and `parts/` | no `parts.json` (`seam_*` null, `skipped.seam` the reason) |
|
|
2626
2639
|
| `BREATH_VISIBLE`, `BLINK_NO_HOLE`, `TIP_OVER_ROOT`, `STILL_REGIONS_DARK` | `parts.json`'s tags and the idle | either missing, or as in the table above |
|
|
2627
2640
|
| `CHAIN_LAG`, `TEXTURE_STRETCH` | the idle (and `rig.json`) | no `idle`, or as in the table above |
|
|
2628
|
-
| `ANIMATIONS_DO_NOT_FOLD` |
|
|
2641
|
+
| `ANIMATIONS_DO_NOT_FOLD` | every animation, the idle included, through `rigc render --folds` | never SKIP: written when `motion.json` holds an animation, the idle alone included (issue #333), absent when it holds none (with or without a `parts.json`) |
|
|
2629
2642
|
| `RECOMPOSITE_HOLES` | `parts.json`'s `recomposite` block | no `parts.json`, or no block |
|
|
2630
2643
|
| `SETUP_POSE_VS_SOURCE` | the rig spec and the painting | no `--source` |
|
|
2631
2644
|
|
|
2632
2645
|
`PASS` reads every bar that measured, and the last console line says how many did:
|
|
2633
|
-
`check: PASS; 1 of 9 bar(s) measured, 8 skipped (loop, seam, …)` is a rig
|
|
2634
|
-
green and nothing else was looked at. Without an idle no idle is rendered
|
|
2646
|
+
`check: PASS; 1 of 9 bar(s) measured, 8 skipped (loop, seam, …)` is a rig with no animation at
|
|
2647
|
+
all whose gate is green and nothing else was looked at. Without an idle no idle is rendered
|
|
2635
2648
|
(`idle_frames/`, `contact.png` and `motion_heat.png` are not written). Reference-demo's own
|
|
2636
|
-
rig directory, copied away from its `parts.json`, reads `
|
|
2637
|
-
`CHAIN_LAG` and `
|
|
2649
|
+
rig directory, copied away from its `parts.json`, reads `5 of 10`: the gate, the loop,
|
|
2650
|
+
`CHAIN_LAG`, `TEXTURE_STRETCH` and, since issue #333, `ANIMATIONS_DO_NOT_FOLD` over its idle.
|
|
2638
2651
|
|
|
2639
2652
|
### Composing several characters (`compose`, issue #74)
|
|
2640
2653
|
|
|
@@ -2831,8 +2844,8 @@ declaration says how far the tip moves and not when. That line's first figures a
|
|
|
2831
2844
|
`declared_rests` and `resting_chains`. Each name must be a swinging part in
|
|
2832
2845
|
`parts.json` and not one `not_swinging` names; a part may both rise and rest.
|
|
2833
2846
|
|
|
2834
|
-
**Folds the art means** (issue #320). `ANIMATIONS_DO_NOT_FOLD` holds every animation
|
|
2835
|
-
|
|
2847
|
+
**Folds the art means** (issue #320). `ANIMATIONS_DO_NOT_FOLD` holds every animation, the
|
|
2848
|
+
idle included (issue #333), to no mesh triangle turned over against the setup pose (§7). When a clip folds a
|
|
2836
2849
|
mesh on purpose — a sleeve that tucks behind the body as the arm crosses it — the scene says
|
|
2837
2850
|
so, by slot and animation, with its reason:
|
|
2838
2851
|
`"may_fold": [{ "slot": "handwear_l", "animation": "dance", "why": "the cuff tucks behind the chest on the cross step" }]`.
|
|
@@ -2840,7 +2853,8 @@ The fold's row in the line carries `declared`, the why, the console's `folds` li
|
|
|
2840
2853
|
`— declared in may_fold: <why>`, and it is not held to the bar. Each entry is three names, no
|
|
2841
2854
|
default — a `why` left blank is refused, as is an empty list, a field the entry does not read
|
|
2842
2855
|
and a pair declared twice (`REQUIREMENTS_FIELD`); the slot must hold a mesh and the
|
|
2843
|
-
animation must be one
|
|
2856
|
+
animation must be one `motion.json` declares — `idle` is one like any other
|
|
2857
|
+
(`REQUIREMENTS_RESOLVES`, `REQUIREMENTS_MAY_FOLD`);
|
|
2844
2858
|
and an entry whose slot does not fold in its animation fails the line
|
|
2845
2859
|
(`CHECK_ANIMATIONS_DO_NOT_FOLD`, `declared_not_folding`) — an exception that excepts nothing
|
|
2846
2860
|
is taken out of the file, as rig-c refuses one for `invariants.deformMayFold`.
|
package/docs/MIGRATION.md
CHANGED
|
@@ -148,3 +148,65 @@ nothing else.
|
|
|
148
148
|
What to do: install with `npm i -D @rig-c/parts` (or `npm install -g @rig-c/parts`);
|
|
149
149
|
command lines do not change. A dependant that names `rig-parts` keeps receiving
|
|
150
150
|
every release under it (#222).
|
|
151
|
+
|
|
152
|
+
## After 1.14.0: the idle is read for folds
|
|
153
|
+
|
|
154
|
+
| | 1.14.0 | after 1.14.0 |
|
|
155
|
+
| --- | --- | --- |
|
|
156
|
+
| `check.json`'s `ANIMATIONS_DO_NOT_FOLD` | written only when `motion.json` holds an animation beside the idle; its `animations` lists those | written whenever `motion.json` holds an animation, a rig of the idle alone included; its `animations` lists every one read, the idle first (#333) |
|
|
157
|
+
| the console's fold line | `N animation(s) beside the idle read by rigc render --folds …` | `N animation(s) read by rigc render --folds …`, the idle counted and named |
|
|
158
|
+
| the summary of a rig with an idle and no other animation | `… of 9 bar(s) measured …` | `… of 10 bar(s) measured …` |
|
|
159
|
+
| `may_fold` naming `"animation": "idle"` | refused, `REQUIREMENTS_MAY_FOLD` (the bar did not read the idle) | accepted like any animation name; a fold it names is listed with its why and not held, and one that does not fold fails the bar |
|
|
160
|
+
|
|
161
|
+
The reading is the one #320 made for the other animations — one `rigc render --folds` at
|
|
162
|
+
12 fps over the build with its physics constraints left out — and now takes the idle in
|
|
163
|
+
the same run. A rig whose idle turns a mesh triangle over against the setup pose, which
|
|
164
|
+
`check` passed before, fails it, naming the idle and quoting rigc's line. On the four
|
|
165
|
+
public examples and the trailer no idle folds [measured for #333, rig-c 2.47.1], so no
|
|
166
|
+
verdict moved; every example's `expected/check.json` moved by the key and nothing else.
|
|
167
|
+
`reference-demo`, an idle alone, gains the whole key after `TEXTURE_STRETCH`:
|
|
168
|
+
|
|
169
|
+
```diff
|
|
170
|
+
@@ -259,6 +259,18 @@
|
|
171
|
+
"severity": 1.074
|
|
172
|
+
}
|
|
173
|
+
]
|
|
174
|
+
+ },
|
|
175
|
+
+ "ANIMATIONS_DO_NOT_FOLD": {
|
|
176
|
+
+ "status": "PASS",
|
|
177
|
+
+ "animations": [
|
|
178
|
+
+ "idle"
|
|
179
|
+
+ ],
|
|
180
|
+
+ "fps": 12,
|
|
181
|
+
+ "physics_left_out": 0,
|
|
182
|
+
+ "folding": [],
|
|
183
|
+
+ "folding_undeclared": 0,
|
|
184
|
+
+ "declared_not_folding": [],
|
|
185
|
+
+ "bar": 0
|
|
186
|
+
},
|
|
187
|
+
"RECOMPOSITE_HOLES": {
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
and `demo`, `scarf` and `reference-sample` gain `"idle"` as the first entry of its
|
|
191
|
+
`animations`.
|
|
192
|
+
|
|
193
|
+
What to do: nothing for a rig whose idle does not fold. A consumer that reads
|
|
194
|
+
`check.json` finds the key on every rig with an animation. An idle that folds on purpose
|
|
195
|
+
is declared, `"may_fold": [{ "slot": …, "animation": "idle", "why": … }]`, in the
|
|
196
|
+
`--requirements` file.
|
|
197
|
+
|
|
198
|
+
## After 1.14.0: faint islands under `contour.bridges`
|
|
199
|
+
|
|
200
|
+
`contour.bridges` shipped in 1.14.0 meshing every island past `stray` and bridging it.
|
|
201
|
+
The patch after it reads islands in a fixed order (issue #326, HQ's ruling): dust first
|
|
202
|
+
(`stray`), then **faint** islands — every pixel's alpha under 64 — left out and named,
|
|
203
|
+
then every visible island bridged whatever its bridge costs. No field was added and
|
|
204
|
+
no declared form changed.
|
|
205
|
+
|
|
206
|
+
| change | what moved | measured |
|
|
207
|
+
| --- | --- | --- |
|
|
208
|
+
| #326, faint islands left out | only a part that declares `contour.bridges` and holds a faint island (not the largest): its mesh, its `mesh_report.json` row (`contour.bridges.faint`, and `art_coverage`, which counts those pixels as art not covered) and, from `mesh-draft`, its written entry's note and `mesh_draft.json`'s `candidate.bridges.faint`. A part without a faint island writes the bytes 1.14.0 wrote | demo `earring` (the only such part a `mesh-draft` of the public examples drafts): its 8, 2 and 1 px specks, brightest alpha 17, 17 and 11, are left out — 33 → 26 triangles, overdraw 4.82 → 1.94; the idle rendered at scale 1.01 differs from the build of #335 (the change 1.14.0 carries) by at most 9 of 255 levels on at most 38 px where the specks were (4 frames; elsewhere 158 px differ by 1 level, as they do with the earring slot hidden), and `check`'s seam bar reads 0 px over 40 on both |
|
|
209
|
+
|
|
210
|
+
What to do: nothing for a config without `contour.bridges`. A part that declares it and
|
|
211
|
+
holds a faint island no longer draws that island; if those pixels matter, keep the
|
|
212
|
+
lattice for that part.
|
package/docs/STABILITY.md
CHANGED
|
@@ -171,11 +171,11 @@ every number) are AUTHORING §3's tables, and the loader refuses any other by na
|
|
|
171
171
|
| --- | --- | --- |
|
|
172
172
|
| `parts.json`, `parts/<name>.png` | `assemble`, `build` | one record per part: `from` (`<run>:<tag>` with a See-through v3 tag, `<run>:author:<label>` for a layer the author made — issue #256, read by no role rule — or `painting:<name>` for a patch), `cut_from` on a cut's piece only (the plan part it was cut from, issue #230), the box `x`, `y`, `w`, `h` (rig px), the pixel counts `src/parts.ts` documents (`opaque_px`, `visible_px`, `occluded_px`, `projected_core_px`, `source_px_taken`, `visible_not_projected_px`, `refused_drift_px`, `merged_px`, `seam_override_px`, and `fringe_pushed_back` when not empty); `rig_size`, `scale_rig_per_source`, `ghost_px`, `recomposite`, and `scene` (`figure_alpha_above`, `figure_px`, `figure_mask`) under `config.scene` only, with the figure's mask `figure_rig.png` beside the recomposite (issue #242) |
|
|
173
173
|
| `rig/rig.json`, `rig/motion.json`, `rig/images/` | `rig`, `build`, `compose` | rig-c's rig spec and motion spec, `spec` `rigc-rig/1` and `rigc-motion/1`: the format is rig-c's, and this package writes only what rig-c's parser accepts |
|
|
174
|
-
| `rig/mesh_report.json` | `rig`, `build` | one row per mesh: `part`, `vertices`, `triangles`, `hull`, `bones`, `max_influences`, `mean_influences`, `art_coverage`, then `grid` (a lattice); `mode` "contour" with `params`, `contour`, `regions` (`params.maxEdge` and `contour.outlineCut` only when the mesh declares `contour.maxEdge`; `params.bridges` and `contour.bridges` — `islands`, `units`, `joins` — only when it declares `contour.bridges`); or `mode` "auto" with `settings`, `source`, `result`, `residuals`, `worst_residual`, `worst_region`, `motion_amplitude`, `termination`, `weights`, `regions`, `deformation`, `quality_report` (rig-c's `mesh-quality-report/1` document whole), `motion_report`, and, only when they apply, `stray_cleared` (after `source`, when `source.stray` is declared), `skinning_residual` and `replay` (`AutoMeshReport` in `src/rig.ts`); in every mode, `exponent` when the mesh declares one (after `grid`, or after `mode`); `influences`, as declared, when a lattice or contour mesh declares it (after `exponent`, or where `exponent` would be); `stray` and `stray_cleared` when a lattice mesh declares `stray` (after `influences`, or where `influences` would be); `ribs` when a contour or auto mesh declares them (after `exponent`, or after `mode`), and then `contour.ribs` (`source.contour.ribs` in the automatic mode) |
|
|
175
|
-
| `check/check.json` and the rest of `check/` | `check`, `build`, `compose` | the reference's fields in its order (`gate_spine_html_green`, `rigc_entry`, `pack_mode`, `loop_max_diff`, the seam figures — `seam_mean`, `seam_px_over_40`, `seam_px_over_80` and, from issue #216, `seam_frame_scale`, the frame scale the bar divides the count by), `loop_physics` after `loop_max_diff` when the rig declares a physics constraint, `seam_border` after the seam figures when `parts.json` declares a scene (issue #242), `seam_pairs` after the seam figures when the seam bar fails or `--seam-pairs` asks (each row's picture under `check/seam/`; `--keep-stills` leaves the setup-pose still and its `frames.json` under `check/setup_still/`), then the judgement lines (`BREATH_VISIBLE`, `BLINK_NO_HOLE`, `CHAIN_LAG`, `TIP_OVER_ROOT`, `STILL_REGIONS_DARK`, `TEXTURE_STRETCH`), `ANIMATIONS_DO_NOT_FOLD` after them when `motion.json` holds an animation
|
|
174
|
+
| `rig/mesh_report.json` | `rig`, `build` | one row per mesh: `part`, `vertices`, `triangles`, `hull`, `bones`, `max_influences`, `mean_influences`, `art_coverage`, then `grid` (a lattice); `mode` "contour" with `params`, `contour`, `regions` (`params.maxEdge` and `contour.outlineCut` only when the mesh declares `contour.maxEdge`; `params.bridges` and `contour.bridges` — `islands`, `units`, `joins`, and `faint` only when an island was left out as faint — only when it declares `contour.bridges`); or `mode` "auto" with `settings`, `source`, `result`, `residuals`, `worst_residual`, `worst_region`, `motion_amplitude`, `termination`, `weights`, `regions`, `deformation`, `quality_report` (rig-c's `mesh-quality-report/1` document whole), `motion_report`, and, only when they apply, `stray_cleared` (after `source`, when `source.stray` is declared), `skinning_residual` and `replay` (`AutoMeshReport` in `src/rig.ts`); in every mode, `exponent` when the mesh declares one (after `grid`, or after `mode`); `influences`, as declared, when a lattice or contour mesh declares it (after `exponent`, or where `exponent` would be); `stray` and `stray_cleared` when a lattice mesh declares `stray` (after `influences`, or where `influences` would be); `ribs` when a contour or auto mesh declares them (after `exponent`, or after `mode`), and then `contour.ribs` (`source.contour.ribs` in the automatic mode) |
|
|
175
|
+
| `check/check.json` and the rest of `check/` | `check`, `build`, `compose` | the reference's fields in its order (`gate_spine_html_green`, `rigc_entry`, `pack_mode`, `loop_max_diff`, the seam figures — `seam_mean`, `seam_px_over_40`, `seam_px_over_80` and, from issue #216, `seam_frame_scale`, the frame scale the bar divides the count by), `loop_physics` after `loop_max_diff` when the rig declares a physics constraint, `seam_border` after the seam figures when `parts.json` declares a scene (issue #242), `seam_pairs` after the seam figures when the seam bar fails or `--seam-pairs` asks (each row's picture under `check/seam/`; `--keep-stills` leaves the setup-pose still and its `frames.json` under `check/setup_still/`), then the judgement lines (`BREATH_VISIBLE`, `BLINK_NO_HOLE`, `CHAIN_LAG`, `TIP_OVER_ROOT`, `STILL_REGIONS_DARK`, `TEXTURE_STRETCH`), `ANIMATIONS_DO_NOT_FOLD` after them when `motion.json` holds an animation, the idle included — on every rig with an idle since issue #333 (issue #320: `status`, `animations`, `fps`, `physics_left_out`, `folding`, `folding_undeclared`, `declared_not_folding`, `bar`), `RECOMPOSITE_HOLES` (its first figure `declared_scene` when `parts.json` declares a scene), `SETUP_POSE_VS_SOURCE`, `skipped`, a `requirements` block under `--requirements`, and `PASS`; `check/build/skeleton.json`, `.atlas` and `.png` are the packed artifact rig-c writes |
|
|
176
176
|
| `proposal.json`, `render/landmarks*.png` | `propose` | the config's four rig sections, `turn` with `--full`/`--head`, and `notes` |
|
|
177
177
|
| `basis.json` | `propose` | spec `spine-parts-basis/1` |
|
|
178
|
-
| `config.json`, `mesh_draft.json` | `mesh-draft` | `config.json` is the config read, byte for byte, but for each drafted part's `meshes` entry; `mesh_draft.json` is spec `rig-parts-mesh-draft/1`: `rules`, `lattice_check`, `draft_check`, `figure_overdraw`, and per lattice part `part`, `mode`, `lattice`, `candidate` (with `bridges` — `units`, `joins` of `span` and `area_px2`, `area_px2`, `overdraw` — only when the candidate is a bridged one, issue #326), `delta` (under `--more-triangles` only), `readings`, `gate` (each line's `reading`, `pass`, `text`, `margin`), `note`; and `not_compared` (issue #307), only when the config holds an `auto` or `contour` entry: per such entry, in config order, `part`, `mode` and `note` — the part was neither drafted nor compared, and a config of lattices alone writes no such key |
|
|
178
|
+
| `config.json`, `mesh_draft.json` | `mesh-draft` | `config.json` is the config read, byte for byte, but for each drafted part's `meshes` entry; `mesh_draft.json` is spec `rig-parts-mesh-draft/1`: `rules`, `lattice_check`, `draft_check`, `figure_overdraw`, and per lattice part `part`, `mode`, `lattice`, `candidate` (with `bridges` — `units`, `joins` of `span` and `area_px2`, `area_px2`, `overdraw`, and `faint` only when an island was left out as faint — only when the candidate is a bridged one, issue #326), `delta` (under `--more-triangles` only), `readings`, `gate` (each line's `reading`, `pass`, `text`, `margin`), `note`; and `not_compared` (issue #307), only when the config holds an `auto` or `contour` entry: per such entry, in config order, `part`, `mode` and `note` — the part was neither drafted nor compared, and a config of lattices alone writes no such key |
|
|
179
179
|
| the scene report | `compose` | spec `spine-parts-scene-report/1` |
|
|
180
180
|
| `idle.png`, `idle-indexed.png`, `idle.gif` | `loop`, `build --loop` | APNG and GIF encodings of the rendered idle |
|
|
181
181
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rig-c/parts",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.15.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/build.ts
CHANGED
|
@@ -70,7 +70,7 @@ import {
|
|
|
70
70
|
splitSchedule,
|
|
71
71
|
untestedIntervals,
|
|
72
72
|
} from './autoreplay.ts';
|
|
73
|
-
import { besideIdleLine, FOLD_LINE, type FoldsLine, buildGateLines, causeLines, IDLE_FPS, IDLE_MAX_PX, maxRgbDiff, REQUIREMENTS_DIR, type CheckReport, type PackLine, DEFAULT_PACK_SHAPE, DEFAULT_PAGE_EDGES, JUDGEMENT_LINES, type JudgementLine, packedBuildArgs, packedBuildLabel, type PackMode, type PackShape, type PageEdges, readFrameSet, rigcFailed, REPORTED_LINES, type ReportedLine, type RigcRunner, runCheck, SEAM_DIR, SEAM_MEAN_BAR, seamPairText, seamRigPxText, SOURCE_LINE, SEAM_PX_BAR, SEAM_PX_LEVEL, SEAM_PX_LEVEL_HIGH, SPINEBOY_YARDSTICK, STILL_DIR } from './check.ts';
|
|
73
|
+
import { besideIdleLine, FOLD_LINE, type FoldsLine, buildGateLines, causeLines, IDLE_FPS, IDLE_MAX_PX, maxRgbDiff, REQUIREMENTS_DIR, type CheckReport, type PackLine, DEFAULT_PACK_SHAPE, DEFAULT_PAGE_EDGES, JUDGEMENT_LINES, type JudgementLine, packedBuildArgs, packedBuildLabel, type PackMode, type PackShape, type PageEdges, readFrameSet, rigcFailed, rigcIdentity, rigcRanClause, REPORTED_LINES, type ReportedLine, type RigcRunner, runCheck, SEAM_DIR, SEAM_MEAN_BAR, seamPairText, seamRigPxText, SOURCE_LINE, SEAM_PX_BAR, SEAM_PX_LEVEL, SEAM_PX_LEVEL_HIGH, SPINEBOY_YARDSTICK, STILL_DIR } from './check.ts';
|
|
74
74
|
import { type Cut, loadConfig, loadConfigAndAnimations, loadEarlyConfig } from './config.ts';
|
|
75
75
|
import { PartsError, type Problem, problemLine, refuseIfAny } from './errors.ts';
|
|
76
76
|
import type { MeshQualityReport, MotionSchedule } from 'rig-c/mesh';
|
|
@@ -277,6 +277,8 @@ export interface GateRun {
|
|
|
277
277
|
* {@link causeLines} says name why.
|
|
278
278
|
*/
|
|
279
279
|
lines: string[];
|
|
280
|
+
/** Which rigc ran it, when the runner said ({@link RigcCall.ran}; issue #337). */
|
|
281
|
+
ran?: string;
|
|
280
282
|
}
|
|
281
283
|
|
|
282
284
|
/**
|
|
@@ -295,7 +297,7 @@ function gateRun(label: string, rigc: RigcRunner, args: string[]): GateRun {
|
|
|
295
297
|
const r = rigc(args);
|
|
296
298
|
const lines = r.out.split('\n').filter((l) => /^ {2}FAIL {2}/.test(l) || /assertions: \d+ measured/.test(l) || /^rigc compile error/.test(l) || DECLARED_SKIP.test(l) || HERE_LINE.test(l));
|
|
297
299
|
const failed = lines.some((l) => /^ {2}FAIL {2}/.test(l) || /^rigc compile error/.test(l));
|
|
298
|
-
return { label, status: r.status, lines: r.status !== 0 && !failed ? [...lines, ...causeLines(r.out)] : lines };
|
|
300
|
+
return { label, status: r.status, lines: r.status !== 0 && !failed ? [...lines, ...causeLines(r.out)] : lines, ...(r.ran === undefined ? {} : { ran: r.ran }) };
|
|
299
301
|
}
|
|
300
302
|
|
|
301
303
|
/**
|
|
@@ -490,7 +492,7 @@ export function motionGates(
|
|
|
490
492
|
{
|
|
491
493
|
code: 'AUTO_MESH_MOTION_INPUT',
|
|
492
494
|
object: c.object,
|
|
493
|
-
detail: `the candidate with the declared stimulus (the rig as written, its motion carrying the stimulus animations) ${candidate.status !== 0 ? `is refused by rigc's gate, exit ${candidate.status}${candidate.lines.length > 0 ? `: ${candidate.lines.map((l) => l.trim()).join(' | ')}` : ''}` : `built green and wrote no ${RIGC_MODEL_DOCUMENT}`}; a candidate that is not gated is not evidence, and nothing is built in the part's place`,
|
|
495
|
+
detail: `the candidate with the declared stimulus (the rig as written, its motion carrying the stimulus animations) ${candidate.status !== 0 ? `is refused by rigc's gate, exit ${candidate.status}${candidate.lines.length > 0 ? `: ${candidate.lines.map((l) => l.trim()).join(' | ')}` : ''}${rigcRanClause(candidate)}` : `built green and wrote no ${RIGC_MODEL_DOCUMENT}`}; a candidate that is not gated is not evidence, and nothing is built in the part's place`,
|
|
494
496
|
},
|
|
495
497
|
],
|
|
496
498
|
notCompared: 'the stimulus candidate build is red',
|
|
@@ -520,7 +522,7 @@ export function motionGates(
|
|
|
520
522
|
{
|
|
521
523
|
code: 'AUTO_MESH_MOTION_INPUT',
|
|
522
524
|
object: c.object,
|
|
523
|
-
detail: `the reference (the rig with "${c.part}" as its unreduced source mesh) ${gate.status !== 0 ? `is refused by rigc's gate, exit ${gate.status}${gate.lines.length > 0 ? `: ${gate.lines.map((l) => l.trim()).join(' | ')}` : ''}` : `built green and wrote no ${RIGC_MODEL_DOCUMENT}`}; a reference that is not gated is not evidence, and nothing is built in the part's place`,
|
|
525
|
+
detail: `the reference (the rig with "${c.part}" as its unreduced source mesh) ${gate.status !== 0 ? `is refused by rigc's gate, exit ${gate.status}${gate.lines.length > 0 ? `: ${gate.lines.map((l) => l.trim()).join(' | ')}` : ''}${rigcRanClause(gate)}` : `built green and wrote no ${RIGC_MODEL_DOCUMENT}`}; a reference that is not gated is not evidence, and nothing is built in the part's place`,
|
|
524
526
|
},
|
|
525
527
|
],
|
|
526
528
|
notCompared: 'the reference build is red',
|
|
@@ -642,7 +644,7 @@ export function replaySearch(
|
|
|
642
644
|
fatal = {
|
|
643
645
|
code: 'AUTO_MESH_MOTION_INPUT',
|
|
644
646
|
object: c.object,
|
|
645
|
-
detail: `the replay to accepted step ${step} (the rig with "${c.part}" as that step's mesh) ${gate.status !== 0 ? `is refused by rigc's gate, exit ${gate.status}${gate.lines.length > 0 ? `: ${gate.lines.map((l) => l.trim()).join(' | ')}` : ''}` : `built green and wrote no ${RIGC_MODEL_DOCUMENT}`}; a candidate that is not gated is not evidence, and nothing is built in the part's place`,
|
|
647
|
+
detail: `the replay to accepted step ${step} (the rig with "${c.part}" as that step's mesh) ${gate.status !== 0 ? `is refused by rigc's gate, exit ${gate.status}${gate.lines.length > 0 ? `: ${gate.lines.map((l) => l.trim()).join(' | ')}` : ''}${rigcRanClause(gate)}` : `built green and wrote no ${RIGC_MODEL_DOCUMENT}`}; a candidate that is not gated is not evidence, and nothing is built in the part's place`,
|
|
646
648
|
};
|
|
647
649
|
return refused(`rigc exit ${gate.status}`);
|
|
648
650
|
}
|
|
@@ -897,7 +899,7 @@ export function rigStage(input: RigStageInput, rigc: RigcRunner, scratch: string
|
|
|
897
899
|
const problems: Problem[] = red.map((g) => ({
|
|
898
900
|
code: 'RIG_RIGC_GREEN',
|
|
899
901
|
object: `rigc ${g.label}`,
|
|
900
|
-
detail: `exited ${g.status}${g.lines.length > 0 ? `: ${g.lines.map((l) => l.trim()).join(' | ')}` : ', and it printed nothing'}; ${ctlRemedies(g.lines, rig.controls, input.command ?? 'rig').map((s) => `${s}; `).join('')}exit 0 is required before anything is written, and nothing was`,
|
|
902
|
+
detail: `exited ${g.status}${g.lines.length > 0 ? `: ${g.lines.map((l) => l.trim()).join(' | ')}` : ', and it printed nothing'}${rigcRanClause(g)}; ${ctlRemedies(g.lines, rig.controls, input.command ?? 'rig').map((s) => `${s}; `).join('')}exit 0 is required before anything is written, and nothing was`,
|
|
901
903
|
}));
|
|
902
904
|
throw new PartsError(problems);
|
|
903
905
|
}
|
|
@@ -1031,14 +1033,14 @@ export function judgementLine(name: string, line: JudgementLine | ReportedLine):
|
|
|
1031
1033
|
}
|
|
1032
1034
|
|
|
1033
1035
|
/**
|
|
1034
|
-
* The fold line (
|
|
1036
|
+
* The fold line (issues #320, #333) as the console prints it: the status, what was
|
|
1035
1037
|
* read and at what rate, and then each folding mesh on a line of its own,
|
|
1036
1038
|
* rigc's own words, with the declaration's why where `may_fold` takes it.
|
|
1037
1039
|
*/
|
|
1038
1040
|
export function foldsLines(line: FoldsLine): string[] {
|
|
1039
1041
|
const physics = line.physics_left_out === 0 ? '' : `, its ${line.physics_left_out} physics constraint(s) left out`;
|
|
1040
1042
|
const head =
|
|
1041
|
-
`${FOLD_LINE}: ${line.status} — ${line.animations.length} animation(s)
|
|
1043
|
+
`${FOLD_LINE}: ${line.status} — ${line.animations.length} animation(s) read by rigc render --folds at ${line.fps} fps${physics} (${line.animations.join(', ')}): ` +
|
|
1042
1044
|
`${line.folding.length} folding mesh(es), ${line.folding_undeclared} not declared in may_fold (<= ${line.bar})` +
|
|
1043
1045
|
(line.declared_not_folding.length === 0 ? '' : `; may_fold declares ${line.declared_not_folding.map((d) => `slot "${d.slot}" in "${d.animation}"`).join(', ')}, which do(es) not fold`);
|
|
1044
1046
|
return [head, ...line.folding.map((r) => ` folds ${r.line}${r.declared === null ? '' : ` — declared in may_fold: ${r.declared}`}`)];
|
|
@@ -1060,7 +1062,7 @@ export function checkStage(input: CheckStageInput, rigc: RigcRunner, bin: string
|
|
|
1060
1062
|
log(`rig-parts check: ${input.rig} -> ${input.out}`);
|
|
1061
1063
|
const versionLines = v.out.trim().split('\n');
|
|
1062
1064
|
const entryLine = versionLines.find((l) => l.startsWith('entry:'));
|
|
1063
|
-
log(`
|
|
1065
|
+
log(` ${rigcIdentity(bin, v)}${entryLine === undefined ? '' : `; ${entryLine}`}`);
|
|
1064
1066
|
const mode: PackMode = { pageEdges: input.pageEdges ?? DEFAULT_PAGE_EDGES, packShape: input.packShape ?? DEFAULT_PACK_SHAPE };
|
|
1065
1067
|
const r = runCheck(input.rig, input.out, rigc, input.parts, mode, input.source, input.requirements, input.seamPairs === true, input.keepStills === true);
|
|
1066
1068
|
log(` gate spine-html (rigc ${packedBuildLabel(mode)}), verbatim:`);
|
package/src/check.ts
CHANGED
|
@@ -56,9 +56,9 @@
|
|
|
56
56
|
* uncovered holes `assemble` recorded in `parts.json`'s `recomposite` block,
|
|
57
57
|
* copied into `check.json` because the seam cannot see them — it compares the
|
|
58
58
|
* setup pose with the flat stack of parts, and a pixel no part holds is
|
|
59
|
-
* missing from both. When `motion.json` holds an animation
|
|
60
|
-
*
|
|
61
|
-
*
|
|
59
|
+
* missing from both. When `motion.json` holds an animation, one more bar,
|
|
60
|
+
* `ANIMATIONS_DO_NOT_FOLD` (issues #320, #333): rigc's own `render --folds`
|
|
61
|
+
* over every animation, the idle included, physics constraints left out
|
|
62
62
|
* ({@link FOLD_LINE}). The part-alone renders, the shut-eye still, the keyed
|
|
63
63
|
* build and the fold render are scratch (`_isolated/`, `_still/`, `_loop/`,
|
|
64
64
|
* `_folds/`), removed before the stage returns.
|
|
@@ -317,10 +317,58 @@ export interface RigcCall {
|
|
|
317
317
|
status: number;
|
|
318
318
|
/** stdout then stderr. */
|
|
319
319
|
out: string;
|
|
320
|
+
/**
|
|
321
|
+
* Which rigc ran the call, as {@link rigcIdentity} words it (`rigc <version> at <path>`), from a runner that knows
|
|
322
|
+
* its binary ({@link rigcRunnerAt}): set on every call that exited non-zero and on every `--version` call, so a
|
|
323
|
+
* refusal quoting the call names the binary and its version (issue #337). Absent from a runner that does not know.
|
|
324
|
+
*/
|
|
325
|
+
ran?: string;
|
|
320
326
|
}
|
|
321
327
|
|
|
322
328
|
export type RigcRunner = (args: readonly string[]) => RigcCall;
|
|
323
329
|
|
|
330
|
+
/** The process primitive {@link rigcRunnerAt} is given: `src/` spawns nothing, so the caller's spawn is injected. */
|
|
331
|
+
export type RigcSpawn = (bin: string, args: readonly string[]) => RigcCall;
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* A rigc binary as what it says it is: `rigc <version> at <path>`, the version being the first line `--version`
|
|
335
|
+
* printed — the line `check` prints before it builds (`checkStage`). A `--version` run that exited non-zero or
|
|
336
|
+
* printed nothing is said so, with the path, rather than read as a version.
|
|
337
|
+
*/
|
|
338
|
+
export function rigcIdentity(bin: string, version: RigcCall): string {
|
|
339
|
+
const first = version.out.trim().split('\n')[0] ?? '';
|
|
340
|
+
if (version.status === 0 && first !== '') return `rigc ${first} at ${bin}`;
|
|
341
|
+
return `rigc (\`--version\` exited ${version.status}${first === '' ? ' and printed nothing' : `, printing ${JSON.stringify(first)}`}) at ${bin}`;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/** The clause a refusal quoting a rigc call ends with: which rigc ran it ({@link RigcCall.ran}), or nothing when the runner did not say. */
|
|
345
|
+
export function rigcRanClause(call: { ran?: string }): string {
|
|
346
|
+
return call.ran === undefined ? '' : `; ${call.ran} ran it`;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* The runner every stage is given (issue #337): `bin` spawned through `spawn`, each call that exited non-zero, and
|
|
351
|
+
* each `--version` call, carrying {@link RigcCall.ran}. The identity is read once per runner, off its first
|
|
352
|
+
* `--version` call or, when a call fails before any, off one `--version` run then — so a green run spawns nothing
|
|
353
|
+
* extra, and a failure names the binary and version that produced it rather than leaving the reader to guess
|
|
354
|
+
* which rigc a path resolved to.
|
|
355
|
+
*/
|
|
356
|
+
export function rigcRunnerAt(bin: string, spawn: RigcSpawn): RigcRunner {
|
|
357
|
+
let identity: string | null = null;
|
|
358
|
+
return (args) => {
|
|
359
|
+
const call = spawn(bin, args);
|
|
360
|
+
const version = args.length === 1 && args[0] === '--version';
|
|
361
|
+
if (version) {
|
|
362
|
+
const own = rigcIdentity(bin, call);
|
|
363
|
+
identity ??= own;
|
|
364
|
+
return { ...call, ran: own };
|
|
365
|
+
}
|
|
366
|
+
if (call.status === 0) return call;
|
|
367
|
+
identity ??= rigcIdentity(bin, spawn(bin, ['--version']));
|
|
368
|
+
return { ...call, ran: identity };
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
324
372
|
/** A line in which a rigc run that stopped says why: Bun's own `error: …`, rigc's `rigc <command>: …` refusals, a `usage: …` line. */
|
|
325
373
|
const CAUSE = /^(error: |rigc[\w -]*: |usage: )/;
|
|
326
374
|
|
|
@@ -396,6 +444,27 @@ export function readRigcEntry(versionOut: string): RigcEntryRecord {
|
|
|
396
444
|
throw new Error('unreachable');
|
|
397
445
|
}
|
|
398
446
|
|
|
447
|
+
/**
|
|
448
|
+
* This package's own rigc: `node_modules/.bin/rigc` under `root` and nowhere else — no directory above it and no
|
|
449
|
+
* PATH, which {@link findRigc} walks (issue #337). The selftest's suites run rigc through it from the package root,
|
|
450
|
+
* so a fixture in a temp directory, or a tree nested inside another checkout (a harness worktree under
|
|
451
|
+
* `.claude/worktrees/`) whose own install is missing, cannot reach another tree's rigc. A miss is refused naming
|
|
452
|
+
* the one path looked at.
|
|
453
|
+
*/
|
|
454
|
+
export function packageRigc(root: string): string {
|
|
455
|
+
const bin = join(resolve(root), 'node_modules', '.bin', 'rigc');
|
|
456
|
+
if (!existsSync(bin)) {
|
|
457
|
+
refuseIfAny([
|
|
458
|
+
{
|
|
459
|
+
code: 'CHECK_RIGC_PACKAGE',
|
|
460
|
+
object: bin,
|
|
461
|
+
detail: `does not exist; this package's own rigc is required here and no other is looked for — not in a directory above ${resolve(root)}, not on PATH (\`bun install\` puts it there)`,
|
|
462
|
+
},
|
|
463
|
+
]);
|
|
464
|
+
}
|
|
465
|
+
return bin;
|
|
466
|
+
}
|
|
467
|
+
|
|
399
468
|
/**
|
|
400
469
|
* Where the `rigc` binary is: `node_modules/.bin/rigc` in `from` or any
|
|
401
470
|
* directory above it (a clone has it beside the package; an install has it in
|
|
@@ -1646,7 +1715,7 @@ export interface CheckFigures {
|
|
|
1646
1715
|
TIP_OVER_ROOT: JudgementLine;
|
|
1647
1716
|
STILL_REGIONS_DARK: JudgementLine;
|
|
1648
1717
|
TEXTURE_STRETCH: JudgementLine;
|
|
1649
|
-
/** Written
|
|
1718
|
+
/** Written whenever motion.json holds an animation, the idle included (issues #320, #333): {@link FoldsLine}. */
|
|
1650
1719
|
[FOLD_LINE]?: FoldsLine;
|
|
1651
1720
|
RECOMPOSITE_HOLES: ReportedLine;
|
|
1652
1721
|
/** Written only under `--source` (issue #77): the setup pose against the painting, REPORTED. */
|
|
@@ -1672,8 +1741,9 @@ export interface RequirementsBlock {
|
|
|
1672
1741
|
}
|
|
1673
1742
|
|
|
1674
1743
|
/**
|
|
1675
|
-
* The bar over
|
|
1676
|
-
* turned over against the setup pose on any frame, on rig-c's own
|
|
1744
|
+
* The bar over every animation, the idle included (issues #320, #333): no mesh
|
|
1745
|
+
* triangle turned over against the setup pose on any frame, on rig-c's own
|
|
1746
|
+
* reading —
|
|
1677
1747
|
* `rigc render --folds`, one run over every animation the build holds — and
|
|
1678
1748
|
* not a reading of this package's. A fold the art means is declared in the
|
|
1679
1749
|
* requirements file's `may_fold`, by slot and animation with its why, and is
|
|
@@ -1689,8 +1759,12 @@ export interface RequirementsBlock {
|
|
|
1689
1759
|
* not tracked]. The keys and weights fold at every rate or at none that the
|
|
1690
1760
|
* sampling resolves; the rate a consumer steps physics at is the scene's.
|
|
1691
1761
|
*
|
|
1692
|
-
* The idle is
|
|
1693
|
-
*
|
|
1762
|
+
* The idle is read like any other animation (issue #333): `TEXTURE_STRETCH`
|
|
1763
|
+
* reads how far the idle's triangles stretch, and a triangle that turns inside
|
|
1764
|
+
* out is this bar's. Written whenever the motion holds an animation, so a rig
|
|
1765
|
+
* of the idle alone writes it too; on the public examples and the trailer no
|
|
1766
|
+
* idle folds, keys only [measured for issue #333, rigc 2.47.1, a scratch run,
|
|
1767
|
+
* not tracked].
|
|
1694
1768
|
*/
|
|
1695
1769
|
export const FOLD_LINE = 'ANIMATIONS_DO_NOT_FOLD';
|
|
1696
1770
|
|
|
@@ -1777,7 +1851,7 @@ export function besideIdleLine(names: readonly string[]): string | null {
|
|
|
1777
1851
|
}
|
|
1778
1852
|
|
|
1779
1853
|
// ---------------------------------------------------------------------------
|
|
1780
|
-
//
|
|
1854
|
+
// every animation, the idle included, read for folds (issues #320, #333)
|
|
1781
1855
|
// ---------------------------------------------------------------------------
|
|
1782
1856
|
|
|
1783
1857
|
/**
|
|
@@ -1792,7 +1866,7 @@ export const FOLD_RENDER_MAX = 16;
|
|
|
1792
1866
|
/** The fold render, under `--out`; removed before the stage returns. */
|
|
1793
1867
|
const FOLD_SCRATCH = '_folds';
|
|
1794
1868
|
|
|
1795
|
-
/** One mesh that folds in one animation
|
|
1869
|
+
/** One mesh that folds in one animation: rigc's figures, the declaration that takes it if any, and rigc's own line. */
|
|
1796
1870
|
export interface FoldRow {
|
|
1797
1871
|
animation: string;
|
|
1798
1872
|
slot: string;
|
|
@@ -1811,10 +1885,10 @@ export interface FoldRow {
|
|
|
1811
1885
|
line: string;
|
|
1812
1886
|
}
|
|
1813
1887
|
|
|
1814
|
-
/** `check.json`'s `ANIMATIONS_DO_NOT_FOLD`: written
|
|
1888
|
+
/** `check.json`'s `ANIMATIONS_DO_NOT_FOLD`: written whenever the motion holds an animation, the idle included. */
|
|
1815
1889
|
export interface FoldsLine {
|
|
1816
1890
|
status: 'PASS' | 'FAIL';
|
|
1817
|
-
/** Every animation
|
|
1891
|
+
/** Every animation, the idle included, in motion.json's order: each one read. */
|
|
1818
1892
|
animations: string[];
|
|
1819
1893
|
fps: number;
|
|
1820
1894
|
/** How many physics constraints the read build left out ({@link withoutPhysics}); 0 when the rig has none. */
|
|
@@ -1957,8 +2031,9 @@ export function rigcFailed(what: string, call: RigcCall, lines: readonly string[
|
|
|
1957
2031
|
const named = lines.filter((l) => l.includes('FAIL') || l.includes('compile error'));
|
|
1958
2032
|
const quoted = named.length > 0 ? named.map((l) => l.trim()) : causeLines(call.out);
|
|
1959
2033
|
const object = `\`rigc ${what}\``;
|
|
1960
|
-
|
|
1961
|
-
|
|
2034
|
+
const ran = rigcRanClause(call);
|
|
2035
|
+
if (quoted.length === 0) return [{ code: 'CHECK_RIGC_GREEN', object, detail: `exit ${call.status}, and it printed nothing${ran}` }];
|
|
2036
|
+
return quoted.map((l) => ({ code: 'CHECK_RIGC_GREEN', object, detail: `exit ${call.status}: ${l}${ran}` }));
|
|
1962
2037
|
}
|
|
1963
2038
|
|
|
1964
2039
|
/**
|
|
@@ -2065,7 +2140,7 @@ export function runCheck(rigDir: string, outDir: string, rigc: RigcRunner, parts
|
|
|
2065
2140
|
let loopSet: FrameSet | null = null;
|
|
2066
2141
|
let loopPhysics: LoopPhysics[] | null = null;
|
|
2067
2142
|
// The keyed build (issues #183, #320): with a physics constraint, the same rig and motion with every one left out,
|
|
2068
|
-
// built once the same way, which the loop and the folds of
|
|
2143
|
+
// built once the same way, which the loop and the folds of every animation are both read from.
|
|
2069
2144
|
const stripped = withoutPhysics(inp.rig, inp.motion);
|
|
2070
2145
|
let keyed: string | null = null;
|
|
2071
2146
|
const keyedBuild = (what: string): string => {
|
|
@@ -2080,7 +2155,8 @@ export function runCheck(rigDir: string, outDir: string, rigc: RigcRunner, parts
|
|
|
2080
2155
|
keyed = join(loopDir, 'build');
|
|
2081
2156
|
return keyed;
|
|
2082
2157
|
};
|
|
2083
|
-
const
|
|
2158
|
+
const animationNames = isRecord(inp.motion.animations) ? Object.keys(inp.motion.animations) : [];
|
|
2159
|
+
const named = animationNames.filter((n) => n !== 'idle');
|
|
2084
2160
|
let folds: FoldsLine | null = null;
|
|
2085
2161
|
const foldProblems: Problem[] = [];
|
|
2086
2162
|
try {
|
|
@@ -2133,15 +2209,15 @@ export function runCheck(rigDir: string, outDir: string, rigc: RigcRunner, parts
|
|
|
2133
2209
|
written.push('motion_heat.png');
|
|
2134
2210
|
}
|
|
2135
2211
|
|
|
2136
|
-
// 3a.
|
|
2137
|
-
// rate whatever --requirements says, so a requirements file moves only what it declares (CK78), from the keyed build
|
|
2138
|
-
if (
|
|
2212
|
+
// 3a. every animation, the idle included, read for folds by rigc (issues #320, #333) — only when there is one; at
|
|
2213
|
+
// the idle's rate whatever --requirements says, so a requirements file moves only what it declares (CK78), from the keyed build
|
|
2214
|
+
if (animationNames.length > 0) {
|
|
2139
2215
|
const fps = IDLE_FPS;
|
|
2140
2216
|
try {
|
|
2141
|
-
const fr = rigc(['render', '--candidate', keyedBuild('the animations
|
|
2217
|
+
const fr = rigc(['render', '--candidate', keyedBuild('the animations with their physics constraint(s) left out'), '--folds', '--fps', String(fps), '--max', String(FOLD_RENDER_MAX), '--out', foldDir]);
|
|
2142
2218
|
if (fr.status !== 0) refuseIfAny(rigcFailed(`render --folds --fps ${fps}${stripped === null ? '' : ' (physics constraint(s) left out)'}`, fr, fr.out.split('\n').filter((l) => l.includes('FAIL') || l.includes('rigc:'))));
|
|
2143
|
-
const readings = readFoldSets(foldDir,
|
|
2144
|
-
folds = foldsLine(
|
|
2219
|
+
const readings = readFoldSets(foldDir, animationNames, fps, fr.out);
|
|
2220
|
+
folds = foldsLine(animationNames, fps, stripped === null ? 0 : stripped.physics.length, readings, reqFile === null ? [] : reqFile.may_fold, reqFile === null ? null : reqFile.path, foldProblems);
|
|
2145
2221
|
} finally {
|
|
2146
2222
|
rmSync(foldDir, { recursive: true, force: true });
|
|
2147
2223
|
}
|
|
@@ -2334,7 +2410,7 @@ export function runCheck(rigDir: string, outDir: string, rigc: RigcRunner, parts
|
|
|
2334
2410
|
const l = figures[n];
|
|
2335
2411
|
tally(n, l.status === 'SKIP' ? l.reason : null);
|
|
2336
2412
|
}
|
|
2337
|
-
//
|
|
2413
|
+
// Issues #320, #333: the fold bar is counted where it is written — on every rig whose motion holds an animation.
|
|
2338
2414
|
if (folds !== null) tally(FOLD_LINE, null);
|
|
2339
2415
|
return {
|
|
2340
2416
|
figures,
|
|
@@ -3511,7 +3587,7 @@ export function requireRigcVersion(rigc: RigcRunner): string {
|
|
|
3511
3587
|
{
|
|
3512
3588
|
code: 'CHECK_RIGC_VERSION',
|
|
3513
3589
|
object: '`rigc --version`',
|
|
3514
|
-
detail: `${got === null ? `exit ${v.status}, printed ${JSON.stringify(v.out.trim().split('\n')[0] ?? '')} — no version` : `is ${got.join('.')}`}; rig-c ${RIGC_GEOMETRY_VERSION} or later is required — its \`render --geometry\` writes the skinned mesh vertices TEXTURE_STRETCH is measured from (\`bun install\` puts this package's own rig-c at node_modules/.bin/rigc)`,
|
|
3590
|
+
detail: `${got === null ? `exit ${v.status}, printed ${JSON.stringify(v.out.trim().split('\n')[0] ?? '')} — no version` : `is ${got.join('.')}`}${rigcRanClause(v)}; rig-c ${RIGC_GEOMETRY_VERSION} or later is required — its \`render --geometry\` writes the skinned mesh vertices TEXTURE_STRETCH is measured from (\`bun install\` puts this package's own rig-c at node_modules/.bin/rigc)`,
|
|
3515
3591
|
},
|
|
3516
3592
|
]);
|
|
3517
3593
|
}
|
package/src/contour.ts
CHANGED
|
@@ -380,8 +380,31 @@ export interface ContourBridges {
|
|
|
380
380
|
units: number[][];
|
|
381
381
|
/** One per unit but unit 0, in the order they were joined. */
|
|
382
382
|
joins: ContourBridge[];
|
|
383
|
+
/**
|
|
384
|
+
* Only when one was left out (HQ's ruling on #326): each faint island — every pixel's alpha under
|
|
385
|
+
* {@link FAINT_ALPHA} — largest first. Its pixels stay in the image and are not meshed, so not drawn, as `stray`
|
|
386
|
+
* leaves dust; `artPixels` does not count them.
|
|
387
|
+
*/
|
|
388
|
+
faint?: ContourFaintIsland[];
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/** A faint island a bridged mesh left out (issue #326): its art px, its brightest pixel's alpha, and its box in part-image px `[x, y, w, h]`. */
|
|
392
|
+
export interface ContourFaintIsland {
|
|
393
|
+
art: number;
|
|
394
|
+
maxAlpha: number;
|
|
395
|
+
box: [number, number, number, number];
|
|
383
396
|
}
|
|
384
397
|
|
|
398
|
+
/**
|
|
399
|
+
* Issue #326, HQ's ruling: under `bridges`, an island whose every pixel has alpha below this is faint, left out and
|
|
400
|
+
* named; any island with one pixel at or above it is visible and bridged, whatever the bridge costs. 64/255 is a
|
|
401
|
+
* quarter of full opacity — a faint pixel moves the composite by at most 63/255 of the colour beneath it — and it
|
|
402
|
+
* sits below the 128 that assemble and `check` read as covered (`FRONT_ALPHA`, `COVERED_ALPHA`, check's coverage),
|
|
403
|
+
* so nothing those readings count as drawn is left out. Read over what a bridged draft reaches on the public figures
|
|
404
|
+
* (the four examples and rig-c/trailer 6e2de45), the faint islands peak at 11–17 and the visible ones at 255.
|
|
405
|
+
*/
|
|
406
|
+
export const FAINT_ALPHA = 64;
|
|
407
|
+
|
|
385
408
|
export interface ContourMesh {
|
|
386
409
|
/** Part-image px, y down, every value a multiple of 1/{@link GRID}; the first `hull` are the outline, in walk order. */
|
|
387
410
|
vertices: Array<[number, number]>;
|
|
@@ -2010,8 +2033,11 @@ interface BridgeUnit {
|
|
|
2010
2033
|
* The bridged mesh (issue #326, Stage B; `bridges: true`). Stage A (`docs/evidence/island-draft.md`) measured the
|
|
2011
2034
|
* join this builds by hand: rig-c reads one closed outline per mesh, and a bridge is what a join costs.
|
|
2012
2035
|
*
|
|
2013
|
-
* 1. **Islands
|
|
2014
|
-
*
|
|
2036
|
+
* 1. **Islands**, in this order. Dust first: an island that is not the largest (nor tied with it) and holds `stray`
|
|
2037
|
+
* art px or fewer is left out as the one-island rule leaves it out. Then faint islands: one that is not the
|
|
2038
|
+
* largest and whose every pixel has alpha under {@link FAINT_ALPHA} is left out the same way and named in
|
|
2039
|
+
* `bridges.faint`. Every other island is kept, and step 3 bridges it whatever its bridge costs — overdraw is
|
|
2040
|
+
* reported (`joins[].area`), never a reason to drop art.
|
|
2015
2041
|
* 2. **Silhouettes, in the trace.** The kept islands with their holes filled grow together by the margin
|
|
2016
2042
|
* ({@link growSilhouette}, its pinch fill included), and each 4-connected piece of the grown set is one unit,
|
|
2017
2043
|
* traced on its own. Islands closer than about twice the margin grow into one unit and are traced as one
|
|
@@ -2044,17 +2070,26 @@ function bridgedMesh(part: string, mask: AlphaMask, params: ContourParams, comp:
|
|
|
2044
2070
|
for (let l = 1; l <= n; l++) largest = Math.max(largest, comp.stats[l].area);
|
|
2045
2071
|
const leave: number[] = [];
|
|
2046
2072
|
for (let l = 1; l <= n; l++) if (comp.stats[l].area !== largest && params.stray !== undefined && comp.stats[l].area <= params.stray) leave.push(l);
|
|
2073
|
+
// Issue #326 (HQ's ruling): after dust, a faint island — every pixel's alpha under FAINT_ALPHA — is left out and
|
|
2074
|
+
// named; every other island is bridged whatever its bridge costs. The largest island (or a tie for it) is never left out.
|
|
2075
|
+
const peak = new Array<number>(n + 1).fill(0);
|
|
2076
|
+
for (let i = 0; i < comp.labels.length; i++) if (comp.labels[i] > 0) peak[comp.labels[i]] = Math.max(peak[comp.labels[i]], mask.alpha[i]);
|
|
2077
|
+
const faint: number[] = [];
|
|
2078
|
+
for (let l = 1; l <= n; l++) if (comp.stats[l].area !== largest && !leave.includes(l) && peak[l] < FAINT_ALPHA) faint.push(l);
|
|
2047
2079
|
let meshed = mask;
|
|
2048
2080
|
let strayPixels = 0;
|
|
2049
|
-
if (leave.length > 0) {
|
|
2050
|
-
const out = new Set(leave);
|
|
2081
|
+
if (leave.length > 0 || faint.length > 0) {
|
|
2082
|
+
const out = new Set([...leave, ...faint]);
|
|
2051
2083
|
const alpha = new Uint8Array(mask.alpha);
|
|
2052
2084
|
for (let i = 0; i < alpha.length; i++) if (out.has(comp.labels[i])) alpha[i] = 0;
|
|
2053
2085
|
meshed = { width: w, height: h, alpha };
|
|
2054
2086
|
for (const l of leave) strayPixels += comp.stats[l].area;
|
|
2055
2087
|
}
|
|
2056
2088
|
const keptIslands = new Set<number>();
|
|
2057
|
-
for (let l = 1; l <= n; l++) if (!leave.includes(l)) keptIslands.add(l);
|
|
2089
|
+
for (let l = 1; l <= n; l++) if (!leave.includes(l) && !faint.includes(l)) keptIslands.add(l);
|
|
2090
|
+
const left: ContourFaintIsland[] = faint
|
|
2091
|
+
.map((l) => ({ art: comp.stats[l].area, maxAlpha: peak[l], box: [comp.stats[l].left, comp.stats[l].top, comp.stats[l].width, comp.stats[l].height] as [number, number, number, number] }))
|
|
2092
|
+
.sort((a, b) => b.art - a.art || a.box[1] - b.box[1] || a.box[0] - b.box[0]);
|
|
2058
2093
|
|
|
2059
2094
|
const island = artMask(meshed, threshold);
|
|
2060
2095
|
const filled = fillHoles(island);
|
|
@@ -2269,6 +2304,7 @@ function bridgedMesh(part: string, mask: AlphaMask, params: ContourParams, comp:
|
|
|
2269
2304
|
islands: [...keptIslands].map((l) => comp.stats[l].area).sort((a, b) => b - a),
|
|
2270
2305
|
units: units.map((u) => u.islands),
|
|
2271
2306
|
joins: joins.map((j, i) => ({ ...j, vertices: quads[i].map(at) as [number, number, number, number] })),
|
|
2307
|
+
...(left.length === 0 ? {} : { faint: left }),
|
|
2272
2308
|
},
|
|
2273
2309
|
},
|
|
2274
2310
|
};
|
package/src/meshdraft.ts
CHANGED
|
@@ -70,7 +70,7 @@ import { BARS, type CheckReport, type PackShape, type PageEdges, type RigcRunner
|
|
|
70
70
|
import { type CharacterConfig, type ContourMeshSpec, type LatticeMeshSpec, loadConfigAndAnimations, type Segment } from './config.ts';
|
|
71
71
|
import { PartsError, type Problem, problemLine } from './errors.ts';
|
|
72
72
|
import { IDLE_FPS } from './motion.ts';
|
|
73
|
-
import type
|
|
73
|
+
import { type ContourBridges, FAINT_ALPHA } from './contour.ts';
|
|
74
74
|
import { ART_ALPHA, DUST_MAX_PX, DUST_MAX_SHARE, isDust } from './mesh.ts';
|
|
75
75
|
import { type PartsFile, readParts } from './parts.ts';
|
|
76
76
|
import { alphaAbove, connectedComponents } from './raster/index.ts';
|
|
@@ -607,6 +607,12 @@ export interface BridgeFigure {
|
|
|
607
607
|
area_px2: number;
|
|
608
608
|
/** That area over the part's art px: the share of the candidate's overdraw the bridges draw (4 decimals). */
|
|
609
609
|
overdraw: number;
|
|
610
|
+
/**
|
|
611
|
+
* Only when one was left out (HQ's ruling on #326): each faint island — every pixel's alpha under `FAINT_ALPHA`
|
|
612
|
+
* (`src/contour.ts`) — with its art px, its brightest pixel's alpha and its box in part-image px. Not meshed, so not
|
|
613
|
+
* drawn.
|
|
614
|
+
*/
|
|
615
|
+
faint?: Array<{ art_px: number; max_alpha: number; box: [number, number, number, number] }>;
|
|
610
616
|
}
|
|
611
617
|
|
|
612
618
|
export interface PartRow {
|
|
@@ -637,7 +643,13 @@ export function bridgesOf(rigDir: string, part: string, art: number): BridgeFigu
|
|
|
637
643
|
const b = rows.find((r) => r.part === part)?.contour?.bridges;
|
|
638
644
|
if (b === undefined) return null;
|
|
639
645
|
const area = b.joins.reduce((t, j) => t + j.area, 0);
|
|
640
|
-
return {
|
|
646
|
+
return {
|
|
647
|
+
units: b.units,
|
|
648
|
+
joins: b.joins.map((j) => ({ span: j.span, area_px2: j.area })),
|
|
649
|
+
area_px2: area,
|
|
650
|
+
overdraw: art === 0 ? 0 : pyRound(area / art, 4),
|
|
651
|
+
...(b.faint === undefined ? {} : { faint: b.faint.map((f) => ({ art_px: f.art, max_alpha: f.maxAlpha, box: f.box })) }),
|
|
652
|
+
};
|
|
641
653
|
}
|
|
642
654
|
|
|
643
655
|
/** The mesh's area over the art's: the triangles' area in image px over the image's art px. */
|
|
@@ -772,11 +784,20 @@ export function meshDraftStage(input: MeshDraftInput, rigc: RigcRunner, checkBin
|
|
|
772
784
|
return d === null || 'keep' in d || d.dust.length === 0 ? 'no dust' : `stray ${d.stray as number} leaves out ${d.dust.length} dust island(s) of ${d.dust.join(', ')} px`;
|
|
773
785
|
};
|
|
774
786
|
const row = (part: string): PartRow => rows.get(part) as PartRow;
|
|
787
|
+
const faintText = (faint: NonNullable<BridgeFigure['faint']>): string =>
|
|
788
|
+
`LEFT OUT as faint, every pixel under alpha ${FAINT_ALPHA}: ${faint.map((f) => `${f.art_px} px (brightest alpha ${f.max_alpha}, at part-image px ${f.box[0]},${f.box[1]} ${f.box[2]}x${f.box[3]})`).join('; ')} — not meshed, so not drawn; if those pixels matter to you, keep the lattice for this part`;
|
|
775
789
|
const bridgeText = (r: PartRow): string => {
|
|
776
790
|
const j = joined.get(r.part) as { real: number[]; bone: string };
|
|
777
791
|
const b = r.candidate?.bridges;
|
|
778
|
-
const joins =
|
|
779
|
-
|
|
792
|
+
const joins =
|
|
793
|
+
b === undefined
|
|
794
|
+
? 'its bridges were not read'
|
|
795
|
+
: b.joins.length === 0
|
|
796
|
+
? 'no bridge'
|
|
797
|
+
: `${b.joins.length} bridge(s) of ${b.joins.map((x) => `${x.span[0]} and ${x.span[1]} px`).join('; ')} span, ${b.area_px2} px² over no art (overdraw ${b.overdraw} of the candidate's ${r.candidate?.overdraw}; overdraw is reported, never a reason to leave art out)`;
|
|
798
|
+
const faint = b?.faint ?? [];
|
|
799
|
+
const visible = j.real.length - faint.length;
|
|
800
|
+
return `its ${j.real.length} real islands (${j.real.join(', ')} px)${faint.length === 0 ? '' : `: ${faintText(faint)}; the ${visible} visible`} traced as ${b === undefined ? 'its' : b.units.length} grown silhouette(s) and joined into one outline by ${joins}, every vertex bound to the one bone "${j.bone}" (issue #326)`;
|
|
780
801
|
};
|
|
781
802
|
|
|
782
803
|
// 3. The drafted rig must build: a refusal laid on a part keeps that part's lattice.
|
|
@@ -933,7 +954,8 @@ export function meshDraftStage(input: MeshDraftInput, rigc: RigcRunner, checkBin
|
|
|
933
954
|
drafted.set(row.part, { ...cand, note: row.note });
|
|
934
955
|
} else if (row.note === '') {
|
|
935
956
|
const failed = row.gate.filter((x) => !x.pass);
|
|
936
|
-
|
|
957
|
+
const faint = row.candidate?.bridges?.faint ?? [];
|
|
958
|
+
row.note = `keeps its lattice at grid ${row.lattice.grid}: ${failed.length === 0 ? 'no candidate was read' : failed.map((x) => `${x.reading} — ${x.text}`).join('; ')}${faint.length === 0 ? '' : `; on the bridged candidate those readings read: ${faintText(faint)}`}`;
|
|
937
959
|
}
|
|
938
960
|
}
|
|
939
961
|
const configText = splicedConfig(text, drafted);
|
|
@@ -972,7 +994,7 @@ export function meshDraftStage(input: MeshDraftInput, rigc: RigcRunner, checkBin
|
|
|
972
994
|
writeFileSync(written[0], configText);
|
|
973
995
|
writeFileSync(written[1], `${JSON.stringify(report, null, 2)}\n`);
|
|
974
996
|
for (const r of rowsOut) {
|
|
975
|
-
const cand = r.candidate === null ? '' : `; candidate ${r.candidate.triangles} triangle(s), overdraw ${r.candidate.overdraw}${r.candidate.bridges === undefined ? '' : ` (${r.candidate.bridges.joins.length} bridge(s), ${r.candidate.bridges.area_px2} px² over no art: overdraw ${r.candidate.bridges.overdraw} of it)`}`;
|
|
997
|
+
const cand = r.candidate === null ? '' : `; candidate ${r.candidate.triangles} triangle(s), overdraw ${r.candidate.overdraw}${r.candidate.bridges === undefined ? '' : ` (${r.candidate.bridges.joins.length} bridge(s), ${r.candidate.bridges.area_px2} px² over no art: overdraw ${r.candidate.bridges.overdraw} of it${(r.candidate.bridges.faint ?? []).map((f) => `; LEFT OUT faint ${f.art_px} px, brightest alpha ${f.max_alpha} < ${FAINT_ALPHA}`).join('')})`}`;
|
|
976
998
|
log(` ${r.mode === 'contour' ? 'DRAFTED' : 'LATTICE'} ${r.part}: lattice grid ${r.lattice.grid} ${r.lattice.triangles} triangle(s), overdraw ${r.lattice.overdraw}${cand}`);
|
|
977
999
|
for (const g of r.gate) log(` ${g.pass ? 'pass' : 'FAIL'} ${g.reading}: ${g.text}`);
|
|
978
1000
|
if (r.mode === 'grid' && r.gate.length === 0) log(` ${r.note}`);
|
package/src/requirements.ts
CHANGED
|
@@ -181,8 +181,9 @@ export interface RequirementsFile {
|
|
|
181
181
|
rests: string[];
|
|
182
182
|
/**
|
|
183
183
|
* Issue #320: the folds the scene means — a slot whose mesh turns a triangle
|
|
184
|
-
* over in
|
|
185
|
-
* the arm crosses it), each by slot and animation
|
|
184
|
+
* over in an animation, the idle included (issue #333), on purpose (a sleeve
|
|
185
|
+
* hidden behind the body as the arm crosses it), each by slot and animation
|
|
186
|
+
* with the author's why.
|
|
186
187
|
* `ANIMATIONS_DO_NOT_FOLD` names each such fold with its why and does not
|
|
187
188
|
* hold it to the bar, and fails an entry whose slot does not fold in that
|
|
188
189
|
* animation, as rig-c refuses an exemption that exempts nothing. Empty when
|
|
@@ -581,11 +582,11 @@ export function resolveRequirements(file: RequirementsFile, rig: Record<string,
|
|
|
581
582
|
}
|
|
582
583
|
}
|
|
583
584
|
|
|
584
|
-
//
|
|
585
|
+
// Issues #320, #333: each declared fold names a slot that holds a mesh and an animation motion.json declares — the idle
|
|
586
|
+
// included, since ANIMATIONS_DO_NOT_FOLD reads every animation.
|
|
585
587
|
file.may_fold.forEach((m, i) => {
|
|
586
588
|
const object = `may_fold[${i}] (slot "${m.slot}", animation "${m.animation}")`;
|
|
587
|
-
if (m.animation
|
|
588
|
-
else if (!(m.animation in anims)) unresolved(object, `names animation "${m.animation}", which motion.json does not declare; it declares ${listed(animNames)}`);
|
|
589
|
+
if (!(m.animation in anims)) unresolved(object, `names animation "${m.animation}", which motion.json does not declare; it declares ${listed(animNames)}`);
|
|
589
590
|
const slots = (Array.isArray(rig.slots) ? rig.slots : []).filter(isRecord).map((s) => s.name);
|
|
590
591
|
if (!slots.includes(m.slot)) {
|
|
591
592
|
unresolved(object, `names slot "${m.slot}", which rig.json does not declare`);
|
package/src/rig.ts
CHANGED
|
@@ -1455,7 +1455,7 @@ export function buildRig(
|
|
|
1455
1455
|
bones: [...bones].sort(),
|
|
1456
1456
|
max_influences: maxInfl,
|
|
1457
1457
|
mean_influences: pyRound(infl / cm.vertices.length, 2),
|
|
1458
|
-
art_coverage: pyRound(rep.coveredArtPixels / (rep.artPixels + rep.strayPixels), 5),
|
|
1458
|
+
art_coverage: pyRound(rep.coveredArtPixels / (rep.artPixels + rep.strayPixels + (rep.bridges?.faint ?? []).reduce((t, f) => t + f.art, 0)), 5),
|
|
1459
1459
|
mode: 'contour',
|
|
1460
1460
|
...(exponent === undefined ? {} : { exponent }),
|
|
1461
1461
|
...(declared === undefined ? {} : { influences: declared }),
|