rig-parts 1.49.2 → 1.50.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 +5 -0
- package/docs/AUTHORING.md +18 -1
- package/docs/STABILITY.md +2 -2
- package/package.json +1 -1
- package/src/build.ts +43 -5
- package/src/check.ts +28 -4
- package/src/config.ts +21 -1
package/cli.ts
CHANGED
|
@@ -714,6 +714,11 @@ usage:
|
|
|
714
714
|
and check's artifact, take them). --requirements is forwarded to check
|
|
715
715
|
(as for check); the file's own shape is read before assemble runs, and
|
|
716
716
|
its names resolve against the rig in the check, before it builds.
|
|
717
|
+
Without the flag, config.requirements (a path beside the config) is
|
|
718
|
+
read in its place; a flag naming other bytes than the declaration is
|
|
719
|
+
BUILD_REQUIREMENTS_CONFLICT, before assemble. check.json's
|
|
720
|
+
requirements.file records the path as typed or declared, its sha256
|
|
721
|
+
and the file's keys.
|
|
717
722
|
--environment is forwarded to check (as for check); the schedule is read
|
|
718
723
|
by rig-c's reader before assemble runs, so a malformed one is refused,
|
|
719
724
|
CHECK_ENVIRONMENT, under [assemble] before anything is assembled.
|
package/docs/AUTHORING.md
CHANGED
|
@@ -130,6 +130,7 @@ read by no CPU stage).
|
|
|
130
130
|
| `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 |
|
|
131
131
|
| `cloth` | `propose --cloth` only; no other stage reads it, so it moves no byte of any build | **authored**, optional (issue #364): one recipe per tag, `{"bottomwear": {…}, "back hair": {…}, "front hair": {…}}`, each `{chains, links, wind, gravity, split?, length_ratio?, strength?, damping?, alternate_damping?, translate?, swing?, part?, influence?}`. `influence` (issue #518) — `{across, max, shape, radius?}`: the parts this recipe meshes are drafted in the influence mode (`meshes.<part>.influence` above) at their lattice's cell, with `across`, `max` and `shape` as declared (each required — the reading behind the radius did not vary them) and `radius` as declared or, absent, drafted as the farthest vertex's distance plus 3 cells (the `influence.radius` row); without it the parts keep their lattice. `chains` — trees across the piece (for `back hair`, per side of the eye axis), a whole number, 1 or more; `links` — links from root to tip on every path, 1 or more; `split` — `{at, into}`: the first `at` links are the trunk, which forks into `into` (2 or more) branches; `at` below `links` (`CONFIG_CLOTH_SPLIT_BEFORE_TIP`); `length_ratio` — a path's last link over its first, above 0, default 0.5; `strength` — `{trunk, branch, edge}`, each above 0 and below the spring's stability bound (`CONFIG_CLOTH_SPRING_STABLE`), default 100 / 100 / 50; `damping` — in [0, 1], absent: rig-c's default; `alternate_damping` — the odd trees' damping, in [0, 1], beside `damping` and different from it (`CONFIG_CLOTH_ALTERNATE_DIFFERS`); `wind`, `gravity` — `[root, tip]`, each 0 or more, tip at or above root (`CONFIG_CLOTH_RESPONSE_GROWS_TO_TIP`); `translate` — the x and y mix of a dangling end's flutter, above 0 and at most 1; `swing` — `bottomwear` only, `{scales}`, one positive scale per tree (`CONFIG_CLOTH_SWING_SCALE_PER_TREE`); `part` — `bottomwear` only (`CONFIG_CLOTH_PART_ON_BOTTOMWEAR`), a non-empty part or cut-piece name: the `topwear` part that holds a skirt See-through returned inside it (a dress), or its skirt piece cut by `assemble.cuts`, which takes the skirt's trees (issue #402, *A skirt inside topwear* below; the part is checked by `propose`, `PROPOSE_CLOTH_PART`). The two defaults are the recipe's own, measured on its synthetic fixture (the recipe below). The recipe below says what each field drafts. The config written: `propose --cloth <config.json> --write-config <out.json>` (issue #537) writes this config's own text back with the sections the recipe drafts spliced in — `bones` and `constraints` whole, `meshes.<part>`, `regions.<part>` and `motion.tracks` entry by entry — and every other byte as read (*The config written*, under the recipe below) |
|
|
132
132
|
| `proposed_rig_scale` | assemble, rig, build, `propose --from-config`/`--compare` | **proposed** by `propose` (issue #354): `parts.json`'s `scale_rig_per_source`, the `assemble.rig_scale` the rig sections (`bones`, `meshes`, `limbs`, `motion`) were drafted at. Held equal to `assemble.rig_scale` when present (`CONFIG_PROPOSED_RIG_SCALE`; 0, negative or not a number: `CONFIG_FIELD_TYPE`). Absent on a hand-authored config, where no byte of any output moves |
|
|
133
|
+
| `requirements` | build (read in place of `--requirements` when the flag is not given), and so its check | **authored**, optional, never proposed (issue #556): a path, relative to the config file's directory, to the `spine-parts-requirements/1` file the build's check reads (§7, *Declared requirements*) — so a rig that needs one (a `not_swinging` part, a `may_fold`) rebuilds from its config alone. `build` reads it before assemble, as it reads the flag's file, and `check.json`'s `requirements.file` records it with `path` as declared. Given beside `--requirements`, the two must name the same file once resolved, or a file of the same bytes, and the declaration is read and recorded (a build given both writes what the config alone writes); otherwise `BUILD_REQUIREMENTS_CONFLICT`, naming both. Not a non-empty string: `CONFIG_FIELD_TYPE`; no such file, where the config is read from a path: `CONFIG_FILE_PRESENT`. `check`, `compose`, `mesh-draft`, `remesh` and `retune` do not read it: their `--requirements` is the only door. Absent, nothing is read and no byte of any output moves |
|
|
133
134
|
|
|
134
135
|
**One drawing, one band; beyond it, another drawing (issue #472).** A single front-facing
|
|
135
136
|
`face` plate has one painted outline, one nose ridge, and two eyes in front-view topology.
|
|
@@ -2284,7 +2285,7 @@ move off it (`AS245`).
|
|
|
2284
2285
|
| `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 |
|
|
2285
2286
|
| `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` |
|
|
2286
2287
|
| `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), then — where a `footwear` part rides a bone that hangs from another — `FEET_STAY_LEVEL` and one `foot` line per such part (issue #348), then — when `parts.json` names a `handwear` part and there is an idle — `STRESS_OFF_CHAIN` (issue #347), then — where `parts.json` holds a limb segment's part — `LIMB_ONE_BONE` and one `limb` line per such part (issue #349), then — where a limb record carries a `range` — `SEAM_OVER_RANGE` and one `joint` line per joint and angle (issue #360), then `SEAM_OVER_MOTION` — a SKIP naming why on a rig that declares no neighbour pair, else the pairs read in every animation and one `WARN seam` line per pair and animation over the one-cell floor (issue #493, a warning and never a bar), then `CLOTH_SETTLES` — a SKIP naming why on a rig with no physics constraint, else the chains read and one `WARN chain` line per chain whose tip comes to rest before the idle's loop length (issue #501, a warning and never a bar), `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, one more each with the feet-level bar, the stress bar, the limb bar and the range bar — and names the ones that said SKIP, then — where `SEAM_OVER_MOTION` read a pair — how many warnings it printed, `; N warning(s) (SEAM_OVER_MOTION, not a bar)` — with `CLOTH_SETTLES` named beside it, and N their sum, where the rig declares a physics constraint — which no bar counts | `check: PASS; 11 of 11 bar(s) measured, 0 skipped` on `demo` and `scarf`, whose sleeves each ride one chain; `check: PASS; 10 of 11 bar(s) measured, 1 skipped (STRESS_OFF_CHAIN)` on `reference-demo` and `reference-sample`, whose one `sleeves` mesh rides both sleeve chains (issue #347) — no example writes `FEET_STAY_LEVEL`, every shoe riding `root`; 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); `CHECK_STRESS_OFF_CHAIN` — a sleeve weighted off its chain (§6); `CHECK_LIMB_ONE_BONE` — a limb part on a second bone (§6); `CHECK_SEAM_OVER_RANGE` — a joint that opens when bent (§6) |
|
|
2287
|
-
| `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` |
|
|
2288
|
+
| `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, first its `file` record (issue #556); the renders under `requirements/` (§7, *Declared requirements*) — `build` reads `config.requirements` in place of the flag when it is not given | 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` |
|
|
2288
2289
|
| `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 |
|
|
2289
2290
|
| `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) |
|
|
2290
2291
|
|
|
@@ -3134,6 +3135,7 @@ documents do not ship). Each is an input this package built; none is an author's
|
|
|
3134
3135
|
| <a id="CHECK_PACK_PAGE_EDGES"></a>`CHECK_PACK_PAGE_EDGES` | the pack line disagrees with the `--page-edges` the build was run with — its `, page edges free` clause is there under `pot` or missing under `free`, or a `pot` page is not a power of two on both edges | a rigc problem; report it, with `rigc --version` |
|
|
3135
3136
|
| <a id="CHECK_PACK_SHAPE"></a>`CHECK_PACK_SHAPE` | the pack line disagrees with the `--pack-shape` the build was run with — it ends `, shape rect` under `polygon` or `, shape polygon` under `rect`, or it has no shape clause (the 1.5–2.0 form, read as `rect`) under `polygon` | a rigc older than 2.1.0 on `PATH`: rigc 2.0.3 takes `--pack-shape` without a word, packs by rectangles and prints the form with no shape clause, so this refusal is what catches it — `bun install` (this package depends on rig-c ^2.20.4); otherwise a rigc problem, report it with `rigc --version` |
|
|
3136
3137
|
| <a id="BUILD_ARTIFACT_PRESENT"></a>`BUILD_ARTIFACT_PRESENT` | the packed build lacks its `.json`, `.atlas` or page | a rigc problem; report it |
|
|
3138
|
+
| <a id="BUILD_REQUIREMENTS_CONFLICT"></a>`BUILD_REQUIREMENTS_CONFLICT` | `build --requirements <file>` names a file with other bytes than the one `config.requirements` declares (issue #556): the detail names both, each resolved (the flag against the working directory, the declaration against the config's) with its sha256 — or `no such file`. Refused before assemble writes anything. The same file by both, or a copy of its bytes, is not refused: the declaration is read and recorded | drop the flag to build from the config's file, or point `config.requirements` at the flag's |
|
|
3137
3139
|
| <a id="REQUIREMENTS_FILE"></a>`REQUIREMENTS_FILE` | `--requirements` names no file, or one that does not parse as a JSON object. Refused before anything is built (by `build`, before assemble) | the path of a `spine-parts-requirements/1` file |
|
|
3138
3140
|
| <a id="REQUIREMENTS_FIELD"></a>`REQUIREMENTS_FIELD` | a field of the file is missing, of the wrong type, or not one the format reads: `spec`, `fps` (no default), `requirements`, each requirement's `name` (letters, digits, underscores; once each), `kind`, `animation` and its kind's fields — every bar among them (`within_px`, `fraction`, `tolerance`, `least_drive` above 0, `within_degrees` from 0 to 180, `lo_degrees` ≤ `hi_degrees`, `within_ratio` ≥ 1) — and each target's `bone`, `animation` and exactly one of `at` and `keys` (times strictly increasing), and `not_planted`, `not_swinging`, `rises` and `rests` when given (each a non-empty list of part names, each once; no part both in `not_swinging` and in `rises` or `rests`), and `may_fold` when given (a non-empty list of `{ "slot", "animation", "why" }`, every one a non-blank string, each pair once; issue #320). `note`, `*_note` (strings) and `x-…` (any value) are read by nothing, as in the config | write the field; the bar is yours |
|
|
3139
3141
|
| <a id="REQUIREMENTS_RESOLVES"></a>`REQUIREMENTS_RESOLVES` | a bone, an `ik` or `transform` constraint (by name and `constraint_type`, as rig-c resolves it), a slot, a mesh attachment or an animation the file names is not in `rig.json` or `motion.json`, or a seam's part or a `not_planted`, `not_swinging`, `rises` or `rests` name is not in `parts.json` — the detail lists the ones that are; or an animation whose name cannot be a directory. Refused before anything is built | the name as the rig spells it |
|
|
@@ -3834,6 +3836,21 @@ unit (the stage box maps to the world by a translation and the y flip,
|
|
|
3834
3836
|
}
|
|
3835
3837
|
```
|
|
3836
3838
|
|
|
3839
|
+
**Where a rebuild finds the file** (issue #556). A rig that passes only under its
|
|
3840
|
+
requirements — a rigid shorts piece in `not_swinging`, a fold the art means in
|
|
3841
|
+
`may_fold` — rebuilds green only if the next caller knows the file. `check.json`'s
|
|
3842
|
+
`requirements` block opens with `file`: `path` as the caller wrote it (`--requirements`
|
|
3843
|
+
as typed, or `config.requirements` as declared — never resolved, so a build moved to
|
|
3844
|
+
another directory writes the same bytes), the `sha256` of the bytes read, and `keys`,
|
|
3845
|
+
the file's top-level keys in its order, so a reader sees what it asked without opening
|
|
3846
|
+
it. `config.requirements` (§3) declares the file beside the config, and `build` reads
|
|
3847
|
+
it when the flag is not given: a build with `--requirements reqs.json`, typed from the
|
|
3848
|
+
config's directory, and a rebuild from a config declaring `"requirements": "reqs.json"`
|
|
3849
|
+
write the same bytes, every file (selftest `BU178`). A flag naming other bytes than the
|
|
3850
|
+
declaration is `BUILD_REQUIREMENTS_CONFLICT` (`BU179`); the same file by both, or a copy
|
|
3851
|
+
of it, builds what the config alone builds (`BU180`). Without either, no `requirements`
|
|
3852
|
+
block is written and no byte moves.
|
|
3853
|
+
|
|
3837
3854
|
**Feet that are not planted** (issue #231). The feet halves of `BREATH_VISIBLE` and
|
|
3838
3855
|
`STILL_REGIONS_DARK` assume a figure that stands: every `footwear` part holds still.
|
|
3839
3856
|
Whether the figure stands is the scene's knowledge, so a scene where it sits and its
|
package/docs/STABILITY.md
CHANGED
|
@@ -114,7 +114,7 @@ The fields, object by object (a bold path is where the object sits; `<part>` is
|
|
|
114
114
|
part name, `[i]` an entry of a list):
|
|
115
115
|
|
|
116
116
|
<!-- stable:config -->
|
|
117
|
-
- **config** — required by the full loader, in front of rig and build: `key`, `assemble`, `bones`, `meshes`, `regions`, `motion`; optional: `generation`, `seethrough`, `limbs`, `constraints`, `stops`, `turn`, `scene`, `cloth`, `proposed_rig_scale`
|
|
117
|
+
- **config** — required by the full loader, in front of rig and build: `key`, `assemble`, `bones`, `meshes`, `regions`, `motion`; optional: `generation`, `seethrough`, `limbs`, `constraints`, `stops`, `turn`, `scene`, `cloth`, `proposed_rig_scale`, `requirements` (issue #556, additive: a path beside the config that build reads in place of its requirements flag, whose behaviour is unchanged)
|
|
118
118
|
- **generation** — required: `checkpoint`, `loras`, `trigger`, `identity`, `sampler`, `costume`, `negative_extra`, `style`, `negative_pose`, `latent`, `seed`; optional: `pose`, `control`
|
|
119
119
|
- **generation.loras[i]** — required: `name`, `strength`; optional: `strength_clip`
|
|
120
120
|
- **generation.sampler** — required: `steps`, `cfg`, `sampler`, `scheduler`
|
|
@@ -193,7 +193,7 @@ every number) are AUTHORING §3's tables, and the loader refuses any other by na
|
|
|
193
193
|
| `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), `limb` on a limb segment's part only (issue #349: `name`, `segment`, `of`, `bone`, and below a joint `joint`, `range` when the config segment declares one (issue #360), and `cap` — `at`, `width`, `radius`, `box`, `layer_px`, `image_px` with a cap image, `image_layer` after it when that image was a hidden-art layer — issue #359, `<run>:<layer>:hidden` — `missing_px`, then `shape` and `upper_px` under the segment's `cap_shape`, and `edge`, `feather_radius` and `feathered_px` under its `edge`, and `cut_band`, `cut_offset`, `cut_at`, `cut_crossed`, `joint_crossed`, `band_px` and `stroke_luma_max` (when the band has a split) under its `cut_band`, issue #360), 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); `single_run_pad` (`file_size`, `side`, `left`, `top`) last, only for one run whose file is not the `seethrough.resolution` square (issue #366) |
|
|
194
194
|
| `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; a bone carries `inherit` only where config.bones declares one, rig-c's mode name as written (issue #348) |
|
|
195
195
|
| `rig/mesh_report.json` | `rig`, `build` | one row per mesh: `part`, `vertices`, `triangles`, `hull`, `bones` (the sorted names the attachment's weights bind in the written `rig.json`, in every mode — since 1.49.1 (issue #536) a rig with `turn` or `stops` names the twins and `<link>_d` bones there, where the row named the segment bones they replace: a fix of a wrong name, not a format change; a rig with neither writes the bytes it wrote), `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` "influence" with `influence` (as declared), `reach` (`farthest`, `at`, `nearest`, `capped`, `widest`) and `lattice` (rig-c's `SegmentsLatticeReport`: `cols`, `rows`, `artCells`, `keptCells`, `passes`, `islands`) (issue #378); 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) |
|
|
196
|
-
| `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`, `environment` after it under `--environment` only (issue #538, additive: `path` as given, `sha256`, `steps`), `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 (issues #320, #363: `status`, `animations`, `fps`, `physics_left_out`, `folding` — each `animation`, `slot`, `attachment`, `frames`, `worst_frame`, `worst_triangles`, `triangles`, `physics_on`, `physics_off`, `physics_constraints`, `declared`, `line` — `folding_undeclared`, `declared_not_folding`, `bar`), `FEET_STAY_LEVEL` after it when a `footwear` part rides a bone that hangs from another and `motion.json` holds an animation (issue #348: `status`, `reason` on a SKIP only, `animations`, `fps`, `physics_left_out`, `bar_degrees`, `bar`, `feet` — each `part`, `bone`, `hangs_from`, `status`, `frames_posed`, `worst`), `LIMB_ONE_BONE` after it (and after `STRESS_OFF_CHAIN` where that is written) when `parts.json` holds a limb segment's part (issue #349: `status`, `parts` — each `part`, `limb`, `bone`, `status`, `others`), `SEAM_OVER_RANGE` after it when a limb record carries a `range` (issue #360: `status`, `frame_scale`, `physics_left_out`, `bar` — `mean`, `rig_px_over_40`, `level` — and `joints`, each `part`, `limb`, `segment`, `joint`, `bone`, `range`, `at`, `radius`, `under_px`, `status`, `poses` — each `degrees`, `status`, `mean`, `px_over_40`, `px_over_80`, `rig_px_over_40` — and `unread` on a joint not read), `SEAM_OVER_MOTION` after it when `parts.json` holds a limb segment or `--requirements` a `seam` (issue #493, a warning and not a bar: `status` — `WARN`, `CLEAR` or `SKIP` — `reason` on a SKIP only, `fps`, `floor`, `pairs` — each `pair`, `a`, `b`, `declared`, and `unread` on a pair not read — `animations`, `warnings`, `rows` — each `pair`, `animation`, `status`, `drawn`, `banded`, `opens`, `px`, `max_px`, `under_floor`), `CLOTH_SETTLES` after it when the rig declares a physics constraint and has an idle (issue #501, a warning and not a bar: `status` — `WARN` or `CLEAR` — `fps`, `frames`, `duration`, `floor_rig_px_per_s`, `floor`, `levers`, `warnings`, `rows` — each `tip`, `root`, `links`, `point`, `settles_at`, `max_rig_px_per_s`, `min_rig_px_per_s`), `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 |
|
|
196
|
+
| `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`, `environment` after it under `--environment` only (issue #538, additive: `path` as given, `sha256`, `steps`), `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 (issues #320, #363: `status`, `animations`, `fps`, `physics_left_out`, `folding` — each `animation`, `slot`, `attachment`, `frames`, `worst_frame`, `worst_triangles`, `triangles`, `physics_on`, `physics_off`, `physics_constraints`, `declared`, `line` — `folding_undeclared`, `declared_not_folding`, `bar`), `FEET_STAY_LEVEL` after it when a `footwear` part rides a bone that hangs from another and `motion.json` holds an animation (issue #348: `status`, `reason` on a SKIP only, `animations`, `fps`, `physics_left_out`, `bar_degrees`, `bar`, `feet` — each `part`, `bone`, `hangs_from`, `status`, `frames_posed`, `worst`), `LIMB_ONE_BONE` after it (and after `STRESS_OFF_CHAIN` where that is written) when `parts.json` holds a limb segment's part (issue #349: `status`, `parts` — each `part`, `limb`, `bone`, `status`, `others`), `SEAM_OVER_RANGE` after it when a limb record carries a `range` (issue #360: `status`, `frame_scale`, `physics_left_out`, `bar` — `mean`, `rig_px_over_40`, `level` — and `joints`, each `part`, `limb`, `segment`, `joint`, `bone`, `range`, `at`, `radius`, `under_px`, `status`, `poses` — each `degrees`, `status`, `mean`, `px_over_40`, `px_over_80`, `rig_px_over_40` — and `unread` on a joint not read), `SEAM_OVER_MOTION` after it when `parts.json` holds a limb segment or `--requirements` a `seam` (issue #493, a warning and not a bar: `status` — `WARN`, `CLEAR` or `SKIP` — `reason` on a SKIP only, `fps`, `floor`, `pairs` — each `pair`, `a`, `b`, `declared`, and `unread` on a pair not read — `animations`, `warnings`, `rows` — each `pair`, `animation`, `status`, `drawn`, `banded`, `opens`, `px`, `max_px`, `under_floor`), `CLOTH_SETTLES` after it when the rig declares a physics constraint and has an idle (issue #501, a warning and not a bar: `status` — `WARN` or `CLEAR` — `fps`, `frames`, `duration`, `floor_rig_px_per_s`, `floor`, `levers`, `warnings`, `rows` — each `tip`, `root`, `links`, `point`, `settles_at`, `max_rig_px_per_s`, `min_rig_px_per_s`), `RECOMPOSITE_HOLES` (its first figure `declared_scene` when `parts.json` declares a scene), `SETUP_POSE_VS_SOURCE`, `skipped`, a `requirements` block under `--requirements` (or `build`'s `config.requirements`) — `file` first from issue #556, additive: `path` as typed or declared, `sha256`, `keys` — and `PASS`; `check/build/skeleton.json`, `.atlas` and `.png` are the packed artifact rig-c writes |
|
|
197
197
|
| `layers.json`, `<index>_<name>.png` | `layers --export` | the wrapper form `layers` reads (issue #359): `width`, `height`, then `layers` — each `name`, `filename`, `left`, `top`, `right`, `bottom`, `depth_median` (the input's own; `null` for a PSD, the manifest's order then being the draw order) — drawn layers in draw order, then the hidden-art layers; a PNG's name is its index and its layer's name, and the manifest, not the file name, says which layer it is |
|
|
198
198
|
| the template `.psd` | `layers --template` | one empty layer per name `templateLayerNames` (`src/layers.ts`) holds, bottom first; the same canvas writes the same bytes |
|
|
199
199
|
| `proposal.json`, `render/landmarks*.png` | `propose` | the config's four rig sections, `constraints` with `--cloth` (issue #364: a physics constraint per tree link named `<bone>_physics`, a dangling end's `<bone>_translate`, and a skirt swing's transforms `<swing>_<tree>`), `turn` with `--full`/`--head`, `proposed_rig_scale` (issue #354), and `notes` |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rig-parts",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.50.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
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
* no child process. rig-c runs as a process, but the process is injected
|
|
41
41
|
* as a {@link RigcRunner}, exactly as `src/check.ts` takes it.
|
|
42
42
|
*/
|
|
43
|
+
import { createHash } from 'node:crypto';
|
|
43
44
|
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
44
45
|
import { dirname, join, resolve } from 'node:path';
|
|
45
46
|
import { type AnimFrame, EncodeError, encodeApng, encodeIndexedApng, INDEXED_DEFAULTS } from './apng.ts';
|
|
@@ -1347,6 +1348,8 @@ export interface CheckStageInput {
|
|
|
1347
1348
|
packShape?: PackShape;
|
|
1348
1349
|
/** The scene's declared requirements (`--requirements`, issue #93); absent, nothing is read, written or printed for them. */
|
|
1349
1350
|
requirements?: string;
|
|
1351
|
+
/** The path `check.json`'s `requirements.file` records (issue #556): `config.requirements` as declared when build read it from the config; absent, `requirements` as given. */
|
|
1352
|
+
requirementsAs?: string;
|
|
1350
1353
|
/**
|
|
1351
1354
|
* `check --seam-pairs` (issue #202): split the seam's counted pixels by pair
|
|
1352
1355
|
* and draw each pair's picture even when the bar passes. A failing seam is
|
|
@@ -1567,7 +1570,7 @@ export function checkStage(input: CheckStageInput, rigc: RigcRunner, bin: string
|
|
|
1567
1570
|
const entryLine = versionLines.find((l) => l.startsWith('entry:'));
|
|
1568
1571
|
log(` ${rigcIdentity(bin, v)}${entryLine === undefined ? '' : `; ${entryLine}`}`);
|
|
1569
1572
|
const mode: PackMode = { pageEdges: input.pageEdges ?? DEFAULT_PAGE_EDGES, packShape: input.packShape ?? DEFAULT_PACK_SHAPE };
|
|
1570
|
-
const r = runCheck(input.rig, input.out, rigc, input.parts, mode, input.source, input.requirements, input.seamPairs === true, input.keepStills === true, input.set, input.allSets === true, input.environment);
|
|
1573
|
+
const r = runCheck(input.rig, input.out, rigc, input.parts, mode, input.source, input.requirements, input.seamPairs === true, input.keepStills === true, input.set, input.allSets === true, input.environment, input.requirementsAs);
|
|
1571
1574
|
if (r.figures.environment !== undefined) log(` ${environmentLine(r.figures.environment)}`);
|
|
1572
1575
|
log(` gate rig-play (rigc ${packedBuildLabel(mode)}), verbatim:`);
|
|
1573
1576
|
for (const l of r.gateHtml) log(l);
|
|
@@ -1885,7 +1888,7 @@ export interface BuildInput {
|
|
|
1885
1888
|
pageEdges: PageEdges;
|
|
1886
1889
|
/** `--pack-shape` for both packed builds, as `pageEdges`; {@link DEFAULT_PACK_SHAPE} when absent. */
|
|
1887
1890
|
packShape?: PackShape;
|
|
1888
|
-
/** `--requirements`, forwarded to the check (issue #93): read before assemble runs, so a file check would refuse is refused first; absent, nothing moves. */
|
|
1891
|
+
/** `--requirements`, forwarded to the check (issue #93): read before assemble runs, so a file check would refuse is refused first; absent, `config.requirements` is read in its place (issue #556, {@link buildRequirements}), and with neither nothing moves. */
|
|
1889
1892
|
requirements?: string;
|
|
1890
1893
|
/** `--idle-keys`, forwarded to the rig stage as `rig` takes it (issue #95); {@link DEFAULT_IDLE_KEYS} when absent. */
|
|
1891
1894
|
idleKeys?: IdleKeys;
|
|
@@ -1936,6 +1939,39 @@ export const CHECK_FLAGS_IN_BUILD: Readonly<Record<string, CheckFlagInBuild>> =
|
|
|
1936
1939
|
'--environment': { as: 'forwarded' },
|
|
1937
1940
|
};
|
|
1938
1941
|
|
|
1942
|
+
/** The requirements file build's check reads (issue #556): `read` is where it is opened, `recorded` what `check.json` names it by. */
|
|
1943
|
+
export interface BuildRequirements {
|
|
1944
|
+
read: string;
|
|
1945
|
+
recorded: string;
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
/**
|
|
1949
|
+
* Which requirements file a build reads (issue #556): the `--requirements`
|
|
1950
|
+
* flag as typed, else `config.requirements` resolved against the config's
|
|
1951
|
+
* directory and recorded as declared, else none. Both given, they must name
|
|
1952
|
+
* one file — the same path once resolved, or a file of the same bytes — and the
|
|
1953
|
+
* declaration is the one read and recorded, so the build writes what a rebuild
|
|
1954
|
+
* from the config alone writes; two files of different bytes are
|
|
1955
|
+
* `BUILD_REQUIREMENTS_CONFLICT`, naming both.
|
|
1956
|
+
*/
|
|
1957
|
+
export function buildRequirements(flag: string | undefined, declared: string | undefined, configPath: string): BuildRequirements | null {
|
|
1958
|
+
if (declared === undefined) return flag === undefined ? null : { read: flag, recorded: flag };
|
|
1959
|
+
const fromConfig = resolve(dirname(resolve(configPath)), declared);
|
|
1960
|
+
if (flag === undefined || resolve(flag) === fromConfig) return { read: fromConfig, recorded: declared };
|
|
1961
|
+
const digestOf = (path: string): string | null => (existsSync(path) && statSync(path).isFile() ? createHash('sha256').update(readFileSync(path)).digest('hex') : null);
|
|
1962
|
+
const flagDigest = digestOf(resolve(flag));
|
|
1963
|
+
const configDigest = digestOf(fromConfig);
|
|
1964
|
+
if (flagDigest !== null && flagDigest === configDigest) return { read: fromConfig, recorded: declared };
|
|
1965
|
+
const shown = (d: string | null): string => (d === null ? 'no such file' : `sha256 ${d.slice(0, 16)}`);
|
|
1966
|
+
throw new PartsError([
|
|
1967
|
+
{
|
|
1968
|
+
code: 'BUILD_REQUIREMENTS_CONFLICT',
|
|
1969
|
+
object: `--requirements ${flag} and config.requirements "${declared}"`,
|
|
1970
|
+
detail: `the flag names ${resolve(flag)} (${shown(flagDigest)}) and the config declares ${fromConfig} (${shown(configDigest)}); a build reads one requirements file and records it, so the two must name the same file or the same bytes — drop the flag to build from the config's file, or point config.requirements at the flag's`,
|
|
1971
|
+
},
|
|
1972
|
+
]);
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1939
1975
|
/** The `build` refusal of a `check` flag build supplies itself ({@link CHECK_FLAGS_IN_BUILD}), or null for any other flag. */
|
|
1940
1976
|
export function suppliedCheckFlag(flag: string): string | null {
|
|
1941
1977
|
const row = Object.hasOwn(CHECK_FLAGS_IN_BUILD, flag) ? CHECK_FLAGS_IN_BUILD[flag] : undefined;
|
|
@@ -2019,14 +2055,16 @@ export function build(input: BuildInput, run: BuildRunners, log: Log): BuildResu
|
|
|
2019
2055
|
const idleKeys = input.idleKeys ?? DEFAULT_IDLE_KEYS;
|
|
2020
2056
|
log(`rig-parts build: ${input.config} -> ${out}, seam rule ${input.seam}, projection rule ${input.project}, page edges ${input.pageEdges}, pack shape ${input.packShape ?? DEFAULT_PACK_SHAPE}${idleKeys === DEFAULT_IDLE_KEYS ? '' : `, idle keys ${idleKeys}`}${input.loop ? `, with the idle loop${input.leadIn === undefined ? '' : ` after a lead-in of ${input.leadIn} period(s)`}` : ''}`);
|
|
2021
2057
|
|
|
2058
|
+
let req: BuildRequirements | null = null;
|
|
2022
2059
|
try {
|
|
2023
2060
|
// build runs rig next, which needs the whole config, so the full loader is
|
|
2024
2061
|
// asked first: a config rig would refuse is refused before assemble writes
|
|
2025
2062
|
// anything, and under [assemble], as it was before the stage had its own
|
|
2026
2063
|
// narrower door.
|
|
2027
|
-
|
|
2064
|
+
// Issue #556: the flag, or the config's declaration when there is none; both naming other bytes is refused here.
|
|
2065
|
+
req = buildRequirements(input.requirements, loadConfig(input.config).requirements, input.config);
|
|
2028
2066
|
// The same for the requirements file's own shape; its names resolve against the rig, in the check, before it builds.
|
|
2029
|
-
if (
|
|
2067
|
+
if (req !== null) readRequirements(req.read);
|
|
2030
2068
|
// And the schedule (issue #538), by rigc's own reader, as check reads it.
|
|
2031
2069
|
if (input.environment !== undefined) readCheckEnvironment(input.environment);
|
|
2032
2070
|
assembleStage(
|
|
@@ -2046,7 +2084,7 @@ export function build(input: BuildInput, run: BuildRunners, log: Log): BuildResu
|
|
|
2046
2084
|
|
|
2047
2085
|
let report: CheckReport;
|
|
2048
2086
|
try {
|
|
2049
|
-
report = checkStage({ rig: join(out, 'rig'), parts: out, out: join(out, 'check'), pageEdges: input.pageEdges, packShape: input.packShape, ...(
|
|
2087
|
+
report = checkStage({ rig: join(out, 'rig'), parts: out, out: join(out, 'check'), pageEdges: input.pageEdges, packShape: input.packShape, ...(req === null ? {} : { requirements: req.read, requirementsAs: req.recorded }), seamPairs: input.seamPairs === true, keepStills: input.keepStills === true, ...(input.set === undefined ? {} : { set: input.set }), allSets: input.allSets === true, ...(input.environment === undefined ? {} : { environment: input.environment }) }, run.check, run.checkBin, prefixed('check'));
|
|
2050
2088
|
} catch (err) {
|
|
2051
2089
|
return refused('check', err);
|
|
2052
2090
|
}
|
package/src/check.ts
CHANGED
|
@@ -1905,8 +1905,9 @@ export interface SetCheckFigures {
|
|
|
1905
1905
|
PASS: boolean;
|
|
1906
1906
|
}
|
|
1907
1907
|
|
|
1908
|
-
/** `check.json`'s `requirements` block: the fps every named animation was sampled at, the summary, and each line by its name, in the file's order. */
|
|
1908
|
+
/** `check.json`'s `requirements` block: which file was read, the fps every named animation was sampled at, the summary, and each line by its name, in the file's order. */
|
|
1909
1909
|
export interface RequirementsBlock {
|
|
1910
|
+
file: RequirementsFileRecord;
|
|
1910
1911
|
fps: number;
|
|
1911
1912
|
summary: RequirementsSummary;
|
|
1912
1913
|
lines: Record<string, RequirementLine>;
|
|
@@ -3722,9 +3723,10 @@ export function environmentLine(env: CheckEnvironment): string {
|
|
|
3722
3723
|
* MEASURABLE is a problem. Without it nothing here reads, writes or prints
|
|
3723
3724
|
* anything it did not before.
|
|
3724
3725
|
*/
|
|
3725
|
-
export function runCheck(rigDir: string, outDir: string, rigc: RigcRunner, partsHome?: string, mode: PackMode = DEFAULT_PACK_MODE, source?: string, requirements?: string, seamPairsAsked = false, keepStills = false, set?: string, allSets = false, environment?: string): CheckReport {
|
|
3726
|
+
export function runCheck(rigDir: string, outDir: string, rigc: RigcRunner, partsHome?: string, mode: PackMode = DEFAULT_PACK_MODE, source?: string, requirements?: string, seamPairsAsked = false, keepStills = false, set?: string, allSets = false, environment?: string, requirementsAs: string | undefined = requirements): CheckReport {
|
|
3726
3727
|
let inp: CheckInputs;
|
|
3727
3728
|
let reqFile: RequirementsFile | null = null;
|
|
3729
|
+
let reqRecord: RequirementsFileRecord | null = null;
|
|
3728
3730
|
let seams: Map<string, SeamPair[]> | null = null;
|
|
3729
3731
|
// Every reader collects every problem; one refusal names every set.
|
|
3730
3732
|
const refused: Problem[] = [];
|
|
@@ -3747,6 +3749,7 @@ export function runCheck(rigDir: string, outDir: string, rigc: RigcRunner, parts
|
|
|
3747
3749
|
const got = take(() => readCheckInputs(rigDir, partsHome, source, set, allSets));
|
|
3748
3750
|
reqFile = take(() => readRequirements(requirements));
|
|
3749
3751
|
refuseIfAny(refused);
|
|
3752
|
+
reqRecord = requirementsFileRecord(requirements, requirementsAs ?? requirements);
|
|
3750
3753
|
inp = got as CheckInputs;
|
|
3751
3754
|
const file = reqFile as RequirementsFile;
|
|
3752
3755
|
const ready = inp;
|
|
@@ -4331,7 +4334,7 @@ export function runCheck(rigDir: string, outDir: string, rigc: RigcRunner, parts
|
|
|
4331
4334
|
|
|
4332
4335
|
// 5. the scene's declared requirements (issue #93), only under --requirements
|
|
4333
4336
|
const reqProblems: Problem[] = [];
|
|
4334
|
-
const reqBlock = reqFile === null ? null : measureRequirements(inp, reqFile, buildDir, out, rigc, reqProblems, seams ?? new Map(), measured);
|
|
4337
|
+
const reqBlock = reqFile === null || reqRecord === null ? null : { file: reqRecord, ...measureRequirements(inp, reqFile, buildDir, out, rigc, reqProblems, seams ?? new Map(), measured) };
|
|
4335
4338
|
|
|
4336
4339
|
const skipped: { loop?: string; seam?: string } = {};
|
|
4337
4340
|
if (inp.noIdle !== null) skipped.loop = inp.noIdle;
|
|
@@ -4457,6 +4460,27 @@ export function runCheck(rigDir: string, outDir: string, rigc: RigcRunner, parts
|
|
|
4457
4460
|
/** Where `check` writes the requirement renders, under `--out`: `as-declared/<animation>/`, `released/<name>/`, `full/<name>/`. */
|
|
4458
4461
|
export const REQUIREMENTS_DIR = 'requirements';
|
|
4459
4462
|
|
|
4463
|
+
/**
|
|
4464
|
+
* Issue #556: the requirements file a check read, first in `check.json`'s
|
|
4465
|
+
* `requirements` block — the path as the caller wrote it (the `--requirements`
|
|
4466
|
+
* flag as typed, or `config.requirements` as declared; never resolved, so the
|
|
4467
|
+
* bytes do not move with the directory the build ran in), the sha256 of the
|
|
4468
|
+
* bytes read, and the file's top-level keys in its order, so a reader sees
|
|
4469
|
+
* what the file asked (a `not_swinging`, a `may_fold`) without opening it.
|
|
4470
|
+
*/
|
|
4471
|
+
export interface RequirementsFileRecord {
|
|
4472
|
+
path: string;
|
|
4473
|
+
sha256: string;
|
|
4474
|
+
keys: string[];
|
|
4475
|
+
}
|
|
4476
|
+
|
|
4477
|
+
/** The {@link RequirementsFileRecord} of a file {@link readRequirements} has passed, read at `path` and recorded as `recorded`. */
|
|
4478
|
+
export function requirementsFileRecord(path: string, recorded: string): RequirementsFileRecord {
|
|
4479
|
+
const bytes = readFileSync(resolve(path));
|
|
4480
|
+
const raw = JSON.parse(bytes.toString('utf8')) as Record<string, unknown>;
|
|
4481
|
+
return { path: recorded, sha256: createHash('sha256').update(bytes).digest('hex'), keys: Object.keys(raw) };
|
|
4482
|
+
}
|
|
4483
|
+
|
|
4460
4484
|
/** The throwaway copies' specs and builds, under `--out`; removed before the stage returns. */
|
|
4461
4485
|
const REQUIREMENTS_SCRATCH = '_requirements';
|
|
4462
4486
|
|
|
@@ -4478,7 +4502,7 @@ const REQUIREMENTS_SCRATCH = '_requirements';
|
|
|
4478
4502
|
* `CHECK_RIGC_GREEN`, quoting rigc. Lines go into the returned block; each
|
|
4479
4503
|
* FAIL and NOT MEASURABLE into `problems`.
|
|
4480
4504
|
*/
|
|
4481
|
-
export function measureRequirements(inp: CheckInputs, file: RequirementsFile, buildDir: string, out: string, rigc: RigcRunner, problems: Problem[], seams: ReadonlyMap<string, readonly SeamPair[]> = new Map(), render: RigcRunner = rigc): RequirementsBlock {
|
|
4505
|
+
export function measureRequirements(inp: CheckInputs, file: RequirementsFile, buildDir: string, out: string, rigc: RigcRunner, problems: Problem[], seams: ReadonlyMap<string, readonly SeamPair[]> = new Map(), render: RigcRunner = rigc): Omit<RequirementsBlock, 'file'> {
|
|
4482
4506
|
const reqDir = join(out, REQUIREMENTS_DIR);
|
|
4483
4507
|
const scratch = join(out, REQUIREMENTS_SCRATCH);
|
|
4484
4508
|
for (const d of [reqDir, scratch]) rmSync(d, { recursive: true, force: true });
|
package/src/config.ts
CHANGED
|
@@ -88,6 +88,7 @@ import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
|
88
88
|
import { dirname, resolve } from 'node:path';
|
|
89
89
|
import { RIG_BONE_INHERIT, RIG_SKIN_CONSTRAINT_KEYS, type RigBoneInherit, type RigConstraint, type RigSkinConstraintKey, type RigStop } from 'rig-c/src/rig.ts';
|
|
90
90
|
import { pyRound } from './round.ts';
|
|
91
|
+
import { REQUIREMENTS_SPEC } from './requirements.ts';
|
|
91
92
|
import { CLOTH_TARGETS, type ClothRecipes, RECIPE_STRENGTH, RIGC_DEFAULT_DAMPING, springStable } from './cloth.ts';
|
|
92
93
|
import { GRID, MAX_SIDE } from './contour.ts';
|
|
93
94
|
import { type Problem, refuseIfAny } from './errors.ts';
|
|
@@ -951,6 +952,13 @@ export interface CharacterConfig {
|
|
|
951
952
|
proposed_rig_scale?: number;
|
|
952
953
|
/** Alternate painting sets (issue #454): absent, no alternate sets are assembled or emitted. */
|
|
953
954
|
alternates?: AlternateSetConfig[];
|
|
955
|
+
/**
|
|
956
|
+
* The requirements file the build's check reads (issue #556), a path relative
|
|
957
|
+
* to the config's directory: `build` reads it when no `--requirements` is
|
|
958
|
+
* given, and refuses a flag naming a file with other bytes. Absent, nothing
|
|
959
|
+
* is read and no byte of any output moves.
|
|
960
|
+
*/
|
|
961
|
+
requirements?: string;
|
|
954
962
|
}
|
|
955
963
|
|
|
956
964
|
/** `config.scene` (issue #242): the declaration that the painting is a scene. It holds no field today. */
|
|
@@ -1259,7 +1267,7 @@ export interface AssembleConfig {
|
|
|
1259
1267
|
proposed_rig_scale?: number;
|
|
1260
1268
|
}
|
|
1261
1269
|
|
|
1262
|
-
const TOP_KEYS = ['key', 'generation', 'seethrough', 'assemble', 'limbs', 'bones', 'meshes', 'regions', 'motion', 'constraints', 'stops', 'turn', 'scene', 'cloth', 'proposed_rig_scale'] as const;
|
|
1270
|
+
const TOP_KEYS = ['key', 'generation', 'seethrough', 'assemble', 'limbs', 'bones', 'meshes', 'regions', 'motion', 'constraints', 'stops', 'turn', 'scene', 'cloth', 'proposed_rig_scale', 'requirements'] as const;
|
|
1263
1271
|
|
|
1264
1272
|
/**
|
|
1265
1273
|
* The one partial entry point. It validates exactly what its door requires —
|
|
@@ -1527,6 +1535,7 @@ function parseConfigFrom(raw: Json, base: string | null): { config: CharacterCon
|
|
|
1527
1535
|
if ('turn' in t) checkTurn(c, t.turn, attached, names.bones, hasAltFace);
|
|
1528
1536
|
if ('scene' in t) checkScene(c, t.scene);
|
|
1529
1537
|
if ('cloth' in t) checkCloth(c, t.cloth);
|
|
1538
|
+
if ('requirements' in t && c.string('config.requirements', t.requirements) && base !== null) checkRequirementsFile(c, resolve(base, t.requirements));
|
|
1530
1539
|
const from = isPlainObject(t.motion) ? t.motion.animations_from : undefined;
|
|
1531
1540
|
const animations = base !== null && typeof from === 'string' && from !== '' ? readAnimationsFrom(c, resolve(base, from)) : null;
|
|
1532
1541
|
if (animations !== null) {
|
|
@@ -1568,6 +1577,17 @@ function isPlainObject(v: Json): v is Record<string, Json> {
|
|
|
1568
1577
|
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
1569
1578
|
}
|
|
1570
1579
|
|
|
1580
|
+
/**
|
|
1581
|
+
* Issue #556: the file `config.requirements` names is on disk —
|
|
1582
|
+
* `CONFIG_FILE_PRESENT` otherwise, as `motion.animations_from`'s. Its shape is
|
|
1583
|
+
* the requirements reader's (`src/requirements.ts`), which `build` runs on it
|
|
1584
|
+
* before assemble, as on the flag's file.
|
|
1585
|
+
*/
|
|
1586
|
+
function checkRequirementsFile(c: Check, file: string): void {
|
|
1587
|
+
if (existsSync(file) && statSync(file).isFile()) return;
|
|
1588
|
+
c.fail('CONFIG_FILE_PRESENT', 'config.requirements', `names ${file}, which is no such file; a ${REQUIREMENTS_SPEC} file, its path relative to the config's directory, is required`);
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1571
1591
|
/**
|
|
1572
1592
|
* Read the file `motion.animations_from` names (issue #183) and vouch for what
|
|
1573
1593
|
* merging its animations beside the idle needs, every problem collected in `c`:
|