@rig-c/parts 1.38.0 → 1.39.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/docs/AUTHORING.md +65 -2
- package/docs/MIGRATION.md +21 -0
- package/docs/STABILITY.md +1 -1
- package/package.json +1 -1
- package/src/build.ts +25 -2
- package/src/check.ts +187 -0
package/docs/AUTHORING.md
CHANGED
|
@@ -1766,7 +1766,7 @@ translate mix at 0.2 / 0.5 / 1): as drafted, no response on the translate, 1.382
|
|
|
1766
1766
|
|
|
1767
1767
|
**What moves them.** Physics answers motion of the bones above it and the environment.
|
|
1768
1768
|
The drafted idle keys `chest` (the breath) and the head chain, never `hip`, and the
|
|
1769
|
-
recipe adds no key: under the idle alone a skirt's trees move only by the environment.
|
|
1769
|
+
recipe adds no key: under the idle alone a skirt's trees move only by the environment. Under rigc's default environment that motion is the physics reset at frame 0 dying out, and `check` says so by name: `CLOTH_SETTLES` (§6, issue #501) warns on each chain whose tip comes to rest before the idle's loop length — on `wind`, the skirt's eight trees and its swing, between 1.5 and 2.75 s of a 3.333 s idle.
|
|
1770
1770
|
`rigc render` and `check` pose under spine-core's default, wind (1, 0) and gravity
|
|
1771
1771
|
(0, 1), so on the fixture `check` reads `check: PASS`, `TEXTURE_STRETCH` 1.382,
|
|
1772
1772
|
`TIP_OVER_ROOT` ratio 15.06 (root 0.095, tip 1.438 rig px), `CHAIN_LAG` PASS,
|
|
@@ -1780,6 +1780,68 @@ by up to 10.396 and 10.188, `skirt` 3.390 and 3.382, and `hair_front` 2.131 and
|
|
|
1780
1780
|
px, either way alike; held three cycles each from a fresh track, 10.299 / 10.277,
|
|
1781
1781
|
3.390 / 3.382 and 2.119 / 2.115.
|
|
1782
1782
|
|
|
1783
|
+
**A standing cloth is the consumer's to drive** (issue #502). The drafted idle keys no
|
|
1784
|
+
`hip`, and that is a measured decision, not an omission. A hip sway was tried on every
|
|
1785
|
+
public example: a `motion.tracks` entry `{ "bone": "hip", "prop": "rotate", "amp": a,
|
|
1786
|
+
"period": <the idle's>, "phase": 0 }` at the breathing period, a = 0.1°, 0.2° and 0.3°
|
|
1787
|
+
(this port's build of each, rig-c 2.60.0, `check`'s idle at 12 fps). Every sway keeps the
|
|
1788
|
+
chains it reaches above `CLOTH_SETTLES`'s floor, and every one turns a passing `check` red:
|
|
1789
|
+
|
|
1790
|
+
| example | chains no idle key drives | 0.1° | 0.2° and 0.3° | idle frames changed by 0.1° |
|
|
1791
|
+
| --- | --- | --- | --- | --- |
|
|
1792
|
+
| `demo` | 0 | `CHAIN_LAG` | `CHAIN_LAG`, `TIP_OVER_ROOT` | 11.91 % of pixels, up to 148 levels |
|
|
1793
|
+
| `scarf` | 0 | `CHAIN_LAG`, `TIP_OVER_ROOT` | the same | the framing box changes, 201 to 202 px wide |
|
|
1794
|
+
| `wind` | 9, the skirt | `CHAIN_LAG`, `BREATH_VISIBLE`, `STILL_REGIONS_DARK` | those and `TIP_OVER_ROOT` | 17.42 %, up to 203 |
|
|
1795
|
+
| `reference-sample` | 1, `hip_jiggle` | `CHAIN_LAG` | the same | 29.42 %, up to 121 |
|
|
1796
|
+
| `reference-demo` | 0 (no physics) | `CHAIN_LAG` | the same | 47.63 %, up to 222 |
|
|
1797
|
+
|
|
1798
|
+
Two of those are not the amplitude's to fix. `CHAIN_LAG` reads a lag modulo a quarter
|
|
1799
|
+
cycle, and the keyed chains under `hip` start at phases from the neck's 0.08 to the third
|
|
1800
|
+
skirt chain's 0.38: a span of 0.30 cycle on `demo`, `scarf`, `reference-sample` and
|
|
1801
|
+
`reference-demo`, so no phase of a hip turn at the idle's period has all of them lagging
|
|
1802
|
+
it. `wind`'s span, 0.08 to 0.29, admits phase 0.06, and there 0.1° still fails
|
|
1803
|
+
`BREATH_VISIBLE`, `STILL_REGIONS_DARK` and `TIP_OVER_ROOT`. And the legs hang from `hip`:
|
|
1804
|
+
a hip turn moves the feet (0.857 rig px at 0.1° on `wind`), which a standing character
|
|
1805
|
+
holds still. So a cloth that should move while the character stands is moved by the
|
|
1806
|
+
world, which the consumer sets when it plays the rig (rig-c's live track
|
|
1807
|
+
`setEnvironment`, rig-play's `environment()`). `rigc render --environment` reads the
|
|
1808
|
+
same world from a file, to look at the response before a player exists (rig-c's
|
|
1809
|
+
AUTHORING §8.4: each step holds from its `t`, x right and y down). A worked schedule for
|
|
1810
|
+
`wind`, tracked as `examples/wind/breeze.json`, is a breeze that changes every half
|
|
1811
|
+
second:
|
|
1812
|
+
|
|
1813
|
+
```json
|
|
1814
|
+
{
|
|
1815
|
+
"spec": "rigc-environment/1",
|
|
1816
|
+
"steps": [
|
|
1817
|
+
{ "t": 0, "wind": [0.4, 0] },
|
|
1818
|
+
{ "t": 0.5, "wind": [1, 0] },
|
|
1819
|
+
{ "t": 1, "wind": [0.2, 0] },
|
|
1820
|
+
{ "t": 1.5, "wind": [0.8, 0] },
|
|
1821
|
+
{ "t": 2, "wind": [0.3, 0] },
|
|
1822
|
+
{ "t": 2.5, "wind": [1, 0] },
|
|
1823
|
+
{ "t": 3, "wind": [0.4, 0] }
|
|
1824
|
+
]
|
|
1825
|
+
}
|
|
1826
|
+
```
|
|
1827
|
+
|
|
1828
|
+
```bash
|
|
1829
|
+
rigc render --candidate out/check/build --animation idle --fps 12 --max 640 --geometry \
|
|
1830
|
+
--environment examples/wind/breeze.json --out out/breeze
|
|
1831
|
+
```
|
|
1832
|
+
|
|
1833
|
+
Read by `CLOTH_SETTLES`'s own definition on the `geometry.json` that writes, none of the
|
|
1834
|
+
16 chains settles. The nine that settle under the default world keep moving to the last
|
|
1835
|
+
frame: the skirt's eight trees end at 7.66 to 23.79 rig px/s, and the swing at 2.778
|
|
1836
|
+
(selftest `CH21` holds this). `check` itself always reads the default world, so `wind`'s
|
|
1837
|
+
`check.json` keeps its WARN. The WARN states what the rig does with nothing driving it;
|
|
1838
|
+
it is not a fault (`CH20` holds that the idle keys no hip and that the chains which
|
|
1839
|
+
settle are exactly the ones no idle key drives). A soft region with no wind response,
|
|
1840
|
+
such as `reference-sample`'s `hip_jiggle`, is not reached by an environment at all:
|
|
1841
|
+
`rigc render --environment` refuses the schedule by name, since no physics constraint
|
|
1842
|
+
answers wind. It answers the body, so what moves it is a consumer's animation that
|
|
1843
|
+
moves `hip`, such as a walk or a turn, not the idle.
|
|
1844
|
+
|
|
1783
1845
|
**Not in this recipe.**
|
|
1784
1846
|
|
|
1785
1847
|
- **Weights across neighbouring chains** (rig-c/core#1415's per-vertex weights, a vertex
|
|
@@ -2175,7 +2237,7 @@ move off it (`AS245`).
|
|
|
2175
2237
|
| `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 hairback_… on mesh '<part>' (side <r\|l>): <N> tree(s) for <M> visible hair mass(es) (mass 0 columns x=<minX>..<maxX>, y=<minY>..<maxY>; …)` — back-hair cloth trees whose count does not match the side's visible hair masses (`max(1, floor(w / (4 * grid)) + 1)` per mass) or whose link or tip leaves its mass's column range (issues #445, #461): omit `chains` on `cloth."back hair"` or draft trees per visible hair mass inside its columns; `LINT <a_limb> and <b_limb> on <part> (<from>) cross at [x, y] with no per-limb split: …` — two limbs sharing one layer cross in an X without a per-limb split (issue #446): declare `config.limbs.<covering>.split` (`{ "line": [[x, y], …] }` or `{ "edge": "painting" }`, §3 *Which limb*); `LINT stop on <chain> (<firstLink> at [x0, y0]) against <against> (at [ax, ay]) places <chain>_touch at [tx, ty], above <firstLink>'s root (rest R deg, \|rest\| + outward B deg): …` — a stop's contact point `<chain>_touch` lies above `links[0]`'s root in rig px or pushes `\|rest\| + outward` past `180°` (issue #447, §2 `stops`): stop against the descending limb bone below `firstLink`, set `links` to start at a link below `<chain>_touch`, or omit the stop on a rising limb; `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 |
|
|
2176
2238
|
| `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 |
|
|
2177
2239
|
| `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` |
|
|
2178
|
-
| `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), `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)
|
|
2240
|
+
| `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) |
|
|
2179
2241
|
| `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` |
|
|
2180
2242
|
| `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 |
|
|
2181
2243
|
| `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) |
|
|
@@ -2894,6 +2956,7 @@ documents do not ship). Each is an input this package built; none is an author's
|
|
|
2894
2956
|
| <a id="CHECK_LIMB_ONE_BONE"></a>`CHECK_LIMB_ONE_BONE` | a part a `config.limbs` segment made is bound in `rig.json` to a bone besides its segment's — a mesh weighted to a second bone, or a slot on another bone — or to none (issue #349); the object names the part, its limb and its bone, the detail every other bone | `regions.<part>` the segment's bone, or a mesh whose `segments` name that bone alone: a bend is a rotation at the joint |
|
|
2895
2957
|
| <a id="CHECK_SEAM_OVER_RANGE"></a>`CHECK_SEAM_OVER_RANGE` | a limb joint with a declared `range` (issue #360), bent to one of its range's ends or its middle, fails the rest-pose seam's bar against the same pose with the joint's own art drawn under the limb — the bend uncovers the joint; the object names the joint, its limb and segment, the part, the bone and the angle. Under `cap_shape: "disc"` (issue #360, unit B), also any pixel of the disc over 40 at a pose, under the seam's bar or not. Also a joint the line cannot read (its bone has no parent in `rig.json`, no slot shows its limb, or its cap's disc holds no opaque art), and, refused, two renders of the bent copy that rigc did not put on one grid | a cap that covers the turn: the layer's art within the cap's radius on the upper side of the joint line, or a `cap` image painting what the layer lacks (`limb cap:` names the missing pixels); or a narrower `range` |
|
|
2896
2958
|
| <a id="SEAM_OVER_MOTION"></a>`SEAM_OVER_MOTION` | **a warning, never a refusal** (issue #493; the owner's ruling, 2026-10-18: a seam that opens in motion may be intended). Every declared neighbour pair — each limb's two segments at a joint, the upper and the one below (`parts.json`'s limb records, `config.limbs`), and under `--requirements` each `seam`'s `part` and `neighbour`, one pair however many declare it — read in every animation `motion.json` holds, the idle included, by rig-c's seam reading: one `rigc render --seam <a>:<b>,… --fps 12` over the packed build as played (physics on), whose `seam.json` (`rigc-seam/1`, rig-c's AUTHORING §8.8) names per pair and frame a **gap** (background inside the joint) or a **ghost** (one piece's overlap band swung clear of its partner), each frame's cells (`px`, one texel of the coarser drawing — a rig px on a build at scale 1), its cell size and its world box. Printed per pair and animation with a frame over the floor: `WARN seam "<a>\|<b>" opens on N frame(s) [<frames>] max <px> px in "<animation>" — over the one-cell floor`, then how many more frames rigc named that were under it. **The floor**: a frame's gap or ghost is reported when it holds more cells than its box is long (its longer side, in cells) — thicker than one cell. A cell is drawn when the renderer paints its centre at half opacity, so an outline that moves by less than a cell flips the cells along it: a line one cell deep, joined or scattered, holds at most as many cells as its box is long, and a hole of any area more. The rule types no number; measured on `examples/wind`'s idle, rigc names 43 openings over its six limb pairs (the two elbows, 1 to 5 cells, boxes up to 17 cells long), every one one cell thick, and on rig-c's `gallery/walk` 13 joint cracks of 1 to 3 cells, all one cell thick. `check.json`'s `SEAM_OVER_MOTION` holds `status` (`WARN`, `CLEAR`), `fps`, `floor`, `pairs`, `animations`, `warnings` and one row per pair and animation (`opens` and `px` over the floor, `max_px`, `under_floor`, rigc's `status`, `drawn` and `banded`); it moves no exit status, no `check: PASS` and no bar count — the summary ends `; N warning(s) (SEAM_OVER_MOTION, not a bar)`. **SKIP**, named: no pair declared (no limb segment, no `seam`; then `check.json` has no key), no `parts.json`, no animation, every pair naming a slot `rig.json` lacks (each printed `not read`), or rigc reading no band on any frame of any pair (rigc's own `skip`: the two drawings share no cell at rest) — nothing measured, never a pass | if the opening is not intended: a cap that covers the turn (`config.limbs`, *Limbs: cut at their joints*; `limb cap:` names the pixels the layer lacks), or a motion that bends that joint less; if it is intended, nothing |
|
|
2959
|
+
| <a id="CLOTH_SETTLES"></a>`CLOTH_SETTLES` | **a warning, never a refusal** (issue #501; the owner's ruling: cloth at rest while the character stands may be intended). Every physics chain, read by its tip on the idle `check` renders — `rigc render --animation idle --fps 12 --geometry`, physics on, rigc's default environment. A chain is a **physics leaf** — a bone a physics constraint names (`rig.json`'s `constraints` or `motion.json`'s `physics`) with no such bone under it — and the run of physics bones above it; its tip is the leaf's end (origin plus `length` along its world x axis), or its origin when the leaf has no length; its speed between frames is the tip's distance over their time, from the idle's `geometry.json`. A chain **settles** at the first frame from which every speed to the idle's last frame is under the floor, and each one that does — before the loop's length is out — prints `WARN chain "<root>" .. "<tip>" (N physics link(s)) settles at <t> s of the <d> s idle — its tip's end under 0.01694 rig px/s from there to the last frame (fastest on the idle <v> rig px/s)`. **The floor**, 0.01694 rig px/s, is derived, not chosen: half the slowest tip an idle key drives on the public examples (the repository's floor rule, half the weaker figure) — measured with this port's build of each (rig-c 2.60.0), over the 31 chains a keyed bone at or above the leaf drives (`demo` 12, `reference-sample` 1, `scarf` 11, `wind` 7) the slowest frame-to-frame tip speed is `scarf`'s `skirt_l2`, 0.03388 rig px/s; the ten no key drives — `wind`'s skirt, eight trees and the swing, hung from a `hip` its idle never keys, and `reference-sample`'s `hip_jiggle` — fall under it from 1.5 to 2.75 s on a 3.333 s idle (`hip_jiggle` at 0 s: it never moves) and end at 0.0025 rig px/s or less. `check.json`'s `CLOTH_SETTLES` holds `status` (`WARN`, `CLEAR`), `fps`, `frames`, `duration`, `floor_rig_px_per_s`, `floor`, `levers`, `warnings` and one row per chain (`tip`, `root`, `links`, `point` — `end` or `origin` — `settles_at`, null when it does not, `max_rig_px_per_s`, `min_rig_px_per_s`); it moves no exit status, no `check: PASS` and no bar count — the summary's warning clause names it, `; N warning(s) (CLOTH_SETTLES, not a bar)` (with `SEAM_OVER_MOTION` before it, and N their sum, where that read a pair). It reads the idle alone, one loop from rigc's physics reset at frame 0: a chain that settles later than the idle is long is not seen. **SKIP**, named: no physics constraint (then `check.json` has no key), or no idle — nothing measured, never a pass | if the chain should move while the character stands, one of the consumer's two levers: an environment at render (`rigc render --environment`, wind and gravity in time — rig-c's AUTHORING §8.4), or a consumer's animation that moves the bone the chain hangs from. The drafted idle keys no hip, by measurement: a hip turn at the idle's period fails a bar on all five public examples (issue #502, §3 *What moves them*). A `cloth` recipe's skirt hangs from `hip`, which the drafted idle never keys (§3, *Recipe: cloth and hair that answer the world*, *What moves them*, which has a worked schedule for `wind`). If it is intended, nothing |
|
|
2897
2960
|
| <a id="CHECK_FOLDS_READ"></a>`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` |
|
|
2898
2961
|
| <a id="CHECK_SEAM_FRAME_SIZE"></a><a id="FRAMES_SIDECAR"></a><a id="CHECK_GEOMETRY_FILE"></a>`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 |
|
|
2899
2962
|
| <a id="LOOP_ENCODE"></a>`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 |
|
package/docs/MIGRATION.md
CHANGED
|
@@ -341,3 +341,24 @@ moves (AUTHORING §6, `SEAM_OVER_MOTION`).
|
|
|
341
341
|
What to do: nothing. A warning names the pair, the animation, the frames and the cells;
|
|
342
342
|
an opening that is not intended wants a cap that covers the turn, or a motion that bends
|
|
343
343
|
less.
|
|
344
|
+
|
|
345
|
+
## After 1.38.0: cloth that comes to rest on the idle
|
|
346
|
+
|
|
347
|
+
Issue #501 reads every physics chain by its tip on the idle `check` renders (physics on,
|
|
348
|
+
rigc's default environment): a chain settles at the first frame from which its tip moves
|
|
349
|
+
slower than 0.01694 rig px/s to the idle's last frame, and the line warns when one
|
|
350
|
+
settles before the loop's length is out. It is a warning, never a bar: cloth at rest
|
|
351
|
+
while the character stands may be intended, so no exit status, no `check: PASS` and no
|
|
352
|
+
bar count moves (AUTHORING §6, `CLOTH_SETTLES`).
|
|
353
|
+
|
|
354
|
+
| change | before | after |
|
|
355
|
+
| --- | --- | --- |
|
|
356
|
+
| `check` on a rig with a physics constraint and an idle | not read | `CLOTH_SETTLES` in `check.json` after `SEAM_OVER_MOTION` (where that is written; else where it would be), its console lines after the `SEAM_OVER_MOTION` lines, one `WARN chain` line per chain that settles, and the summary's warning clause names it: `; N warning(s) (CLOTH_SETTLES, not a bar)`, or `(SEAM_OVER_MOTION, CLOTH_SETTLES, not a bar)` with N their sum where both read |
|
|
357
|
+
| `check` on any other rig | — | one console line, `CLOTH_SETTLES: SKIP — …`, naming why; no key in `check.json` |
|
|
358
|
+
| the public examples' `expected/check.json` | — | `demo`, `reference-sample`, `scarf` and `wind` gain the block: `wind`'s skirt (eight trees and the swing) and `reference-sample`'s `hip_jiggle` settle, every chain an idle key drives does not |
|
|
359
|
+
|
|
360
|
+
What to do: nothing. A warning names the chain and the second it comes to rest; a chain
|
|
361
|
+
that should keep moving wants an environment at render (`rigc render --environment`;
|
|
362
|
+
AUTHORING §3, *What moves them*, has a worked schedule for `wind`) or a consumer's
|
|
363
|
+
animation that moves the bone it hangs from. The drafted idle keys no hip: issue #502
|
|
364
|
+
measured a hip sway at the idle's period on every public example, and each failed a bar.
|
package/docs/STABILITY.md
CHANGED
|
@@ -190,7 +190,7 @@ every number) are AUTHORING §3's tables, and the loader refuses any other by na
|
|
|
190
190
|
| `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) |
|
|
191
191
|
| `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) |
|
|
192
192
|
| `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) |
|
|
193
|
-
| `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 (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`), `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 |
|
|
193
|
+
| `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 (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 |
|
|
194
194
|
| `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 |
|
|
195
195
|
| the template `.psd` | `layers --template` | one empty layer per name `templateLayerNames` (`src/layers.ts`) holds, bottom first; the same canvas writes the same bytes |
|
|
196
196
|
| `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-c/parts",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.39.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, frameRuns, MOTION_SEAM_LINE, type MotionSeamLine, type MotionSeamPlant, motionSeamWarnText, FOLD_LINE, type FoldsLine, LEVEL_LINE, type LevelLine, LIMB_LINE, type LimbLine, STRESS_LINE, SWEEP_LINE, type SweepLine, buildGateLines, causeLines, IDLE_FPS, IDLE_MAX_PX, maxRgbDiff, REQUIREMENTS_DIR, type CheckReport, type PackLine, DEFAULT_PACK_SHAPE, DEFAULT_PAGE_EDGES, GATE_PROFILE, 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';
|
|
73
|
+
import { besideIdleLine, CLOTH_LINE, type ClothLine, clothWarnText, frameRuns, MOTION_SEAM_LINE, type MotionSeamLine, type MotionSeamPlant, motionSeamWarnText, FOLD_LINE, type FoldsLine, LEVEL_LINE, type LevelLine, LIMB_LINE, type LimbLine, STRESS_LINE, SWEEP_LINE, type SweepLine, buildGateLines, causeLines, IDLE_FPS, IDLE_MAX_PX, maxRgbDiff, REQUIREMENTS_DIR, type CheckReport, type PackLine, DEFAULT_PACK_SHAPE, DEFAULT_PAGE_EDGES, GATE_PROFILE, 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, type Extend, type Limb, loadConfig, loadConfigAndAnimations, loadEarlyConfig, type Patch, type PlanEntry } from './config.ts';
|
|
75
75
|
import { failLine, PartsError, type Problem, refuseIfAny } from './errors.ts';
|
|
76
76
|
import type { MeshQualityReport, MotionSchedule } from 'rig-c/mesh';
|
|
@@ -1506,6 +1506,28 @@ export function motionSeamLines(line: MotionSeamLine, plant: MotionSeamPlant = {
|
|
|
1506
1506
|
];
|
|
1507
1507
|
}
|
|
1508
1508
|
|
|
1509
|
+
/**
|
|
1510
|
+
* The cloth reading (issue #501) as the console prints it: the status, what was read, the floor, the levers and that it
|
|
1511
|
+
* is not a bar, then one WARN line per chain that settles. A SKIP prints its reason alone. `silent` is the mutant that
|
|
1512
|
+
* removes the reading's lines.
|
|
1513
|
+
*/
|
|
1514
|
+
export function clothLines(line: ClothLine, plant: { silent?: boolean } = {}): string[] {
|
|
1515
|
+
if (plant.silent === true) return [];
|
|
1516
|
+
if (line.status === 'SKIP') return [`${CLOTH_LINE}: SKIP — ${String(line.reason)}`];
|
|
1517
|
+
const read = `${line.rows.length} physics chain(s) read by their tip on the idle as rendered (${line.frames} frame(s) at ${line.fps} fps over ${line.duration} s, physics on)`;
|
|
1518
|
+
return [
|
|
1519
|
+
`${CLOTH_LINE}: ${line.status} — ${read}: ${line.warnings} of them settle before the loop's length — ${line.warnings} warning(s), never a bar; ${line.floor}; ${line.levers}`,
|
|
1520
|
+
...line.rows.filter((r) => r.settles_at !== null).map((r) => clothWarnText(r, line.duration)),
|
|
1521
|
+
];
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
/** The summary's warning clause: each warning line that read something, named, with the warnings they printed together; empty when none read. */
|
|
1525
|
+
export function warningsClause(r: Pick<CheckReport, 'motionSeam' | 'cloth'>): string {
|
|
1526
|
+
const read = [...(r.motionSeam.status === 'SKIP' ? [] : [{ name: MOTION_SEAM_LINE, n: r.motionSeam.warnings }]), ...(r.cloth.status === 'SKIP' ? [] : [{ name: CLOTH_LINE, n: r.cloth.warnings }])];
|
|
1527
|
+
if (read.length === 0) return '';
|
|
1528
|
+
return `; ${read.reduce((a, x) => a + x.n, 0)} warning(s) (${read.map((x) => x.name).join(', ')}, not a bar)`;
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1509
1531
|
/** A requirement's line as the console prints it: its name and status, the reason first when NOT MEASURABLE, then its figures as check.json holds them. */
|
|
1510
1532
|
export function requirementText(name: string, line: RequirementLine): string {
|
|
1511
1533
|
const rest = Object.entries(line).filter(([k]) => k !== 'status' && k !== 'reason');
|
|
@@ -1565,6 +1587,7 @@ export function checkStage(input: CheckStageInput, rigc: RigcRunner, bin: string
|
|
|
1565
1587
|
const sweep = fig[SWEEP_LINE];
|
|
1566
1588
|
if (sweep !== undefined) for (const [i, l] of sweepCheckLines(sweep).entries()) log(` ${i === 0 ? '' : ' '}${l}`);
|
|
1567
1589
|
for (const [i, l] of motionSeamLines(r.motionSeam).entries()) log(` ${i === 0 ? '' : ' '}${l}`);
|
|
1590
|
+
for (const [i, l] of clothLines(r.cloth).entries()) log(` ${i === 0 ? '' : ' '}${l}`);
|
|
1568
1591
|
for (const name of REPORTED_LINES) log(` ${judgementLine(name, fig[name])}`);
|
|
1569
1592
|
if (fig.sets !== undefined) {
|
|
1570
1593
|
for (const [setName, setFig] of Object.entries(fig.sets)) {
|
|
@@ -1582,7 +1605,7 @@ export function checkStage(input: CheckStageInput, rigc: RigcRunner, bin: string
|
|
|
1582
1605
|
for (const p of r.problems) log(failLine(p));
|
|
1583
1606
|
const reqNotPass = r.requirements === null ? 0 : r.requirements.summary.declared - r.requirements.summary.pass;
|
|
1584
1607
|
const notMet = r.requirements === null ? `${r.problems.length} bar(s) not met` : `${r.problems.length - reqNotPass} bar(s) not met, ${reqNotPass} declared requirement(s) not PASS`;
|
|
1585
|
-
log(`${fig.PASS ? 'check: PASS' : `check: FAIL — ${notMet}`}; ${barsSummary(r)}${r
|
|
1608
|
+
log(`${fig.PASS ? 'check: PASS' : `check: FAIL — ${notMet}`}; ${barsSummary(r)}${warningsClause(r)}`);
|
|
1586
1609
|
return r;
|
|
1587
1610
|
}
|
|
1588
1611
|
|
package/src/check.ts
CHANGED
|
@@ -1864,6 +1864,8 @@ export interface CheckFigures {
|
|
|
1864
1864
|
/** Written only where parts.json holds a limb record with a `range` (issue #360): {@link SweepLine}. */
|
|
1865
1865
|
[SWEEP_LINE]?: SweepLine;
|
|
1866
1866
|
[MOTION_SEAM_LINE]?: MotionSeamLine;
|
|
1867
|
+
/** Written only when the rig declares a physics constraint and has an idle (issue #501): {@link ClothLine}, a warning, never a bar. */
|
|
1868
|
+
[CLOTH_LINE]?: ClothLine;
|
|
1867
1869
|
RECOMPOSITE_HOLES: ReportedLine;
|
|
1868
1870
|
/** Written only under `--source` (issue #77): the setup pose against the painting, REPORTED. */
|
|
1869
1871
|
SETUP_POSE_VS_SOURCE?: ReportedLine;
|
|
@@ -2128,6 +2130,8 @@ export interface CheckReport {
|
|
|
2128
2130
|
besideIdle: string[];
|
|
2129
2131
|
/** `SEAM_OVER_MOTION` (issue #493): a warning, never a bar — in `check.json` only when a pair is declared; its SKIP is printed either way. */
|
|
2130
2132
|
motionSeam: MotionSeamLine;
|
|
2133
|
+
/** `CLOTH_SETTLES` (issue #501): a warning, never a bar — in `check.json` only when the rig declares a physics constraint and has an idle; its SKIP is printed either way. */
|
|
2134
|
+
cloth: ClothLine;
|
|
2131
2135
|
}
|
|
2132
2136
|
|
|
2133
2137
|
/**
|
|
@@ -3400,6 +3404,165 @@ export function motionSeamWarnText(r: MotionSeamRow): string {
|
|
|
3400
3404
|
return `WARN seam "${r.pair}" opens on ${r.opens.length} frame(s) ${frameRuns(r.opens)} max ${r.max_px} px in "${r.animation}" — over the one-cell floor${r.under_floor.length === 0 ? '' : `, ${r.under_floor.length} more frame(s) one cell thick not reported`}`;
|
|
3401
3405
|
}
|
|
3402
3406
|
|
|
3407
|
+
// ---------------------------------------------------------------------------
|
|
3408
|
+
// cloth that comes to rest on the idle (issue #501; the line is {@link CLOTH_LINE}'s)
|
|
3409
|
+
// ---------------------------------------------------------------------------
|
|
3410
|
+
|
|
3411
|
+
/**
|
|
3412
|
+
* `CLOTH_SETTLES` (issue #501): every physics chain read by its tip on the
|
|
3413
|
+
* idle as `check` renders it (physics on, rigc's default environment) — a
|
|
3414
|
+
* **warning, never a bar**. The owner's ruling: cloth at rest while the
|
|
3415
|
+
* character stands can be intended, so the line names the chain, the time its
|
|
3416
|
+
* tip comes to rest and the floor, and moves neither the exit status nor
|
|
3417
|
+
* `check: PASS`; the summary counts it apart from the bars.
|
|
3418
|
+
*
|
|
3419
|
+
* Why it exists: `CHAIN_LAG` reads the idle's keyed chains off `motion.json`
|
|
3420
|
+
* and `TIP_OVER_ROOT` the idle's posed parts, and neither reads what a
|
|
3421
|
+
* physics constraint does with a chain nothing above it moves. A skirt hung
|
|
3422
|
+
* from `hip` under an idle that keys only the breath is pushed by rigc's
|
|
3423
|
+
* reset at frame 0 and then comes to rest, and every bar passes.
|
|
3424
|
+
*
|
|
3425
|
+
* A chain is a physics leaf — a bone carrying a physics constraint (rig.json's
|
|
3426
|
+
* `constraints` or motion.json's `physics`) with no such bone under it — and
|
|
3427
|
+
* the physics bones above it, as far as the run of them goes. Its tip is the
|
|
3428
|
+
* leaf's end (origin plus `length` along its world x axis), or its origin when
|
|
3429
|
+
* the leaf has no length. Its speed between two frames is the tip's distance
|
|
3430
|
+
* over their time; it **settles** at the first frame from which every speed
|
|
3431
|
+
* to the idle's last frame is under {@link CLOTH_SETTLE_FLOOR}, and the line
|
|
3432
|
+
* warns when one does — that is, before the loop's length is out.
|
|
3433
|
+
*/
|
|
3434
|
+
export const CLOTH_LINE = 'CLOTH_SETTLES';
|
|
3435
|
+
|
|
3436
|
+
/**
|
|
3437
|
+
* The floor, rig px per second — half the slowest tip a keyed bone drives on
|
|
3438
|
+
* the public examples, the repository's floor rule (half the weaker figure).
|
|
3439
|
+
* Measured for issue #501 with this port's build of each example (rig-c
|
|
3440
|
+
* 2.60.0, `check`'s idle at 12 fps, physics on): over the 31 physics chains an
|
|
3441
|
+
* idle key drives — a keyed bone at or above the leaf (`demo` 12,
|
|
3442
|
+
* `reference-sample` 1, `scarf` 11, `wind` 7) — the slowest frame-to-frame tip
|
|
3443
|
+
* speed is `scarf`'s `skirt_l2`, 0.03388; the ten no key drives (`wind`'s
|
|
3444
|
+
* skirt, its eight trees and its swing, and `reference-sample`'s `hip_jiggle`)
|
|
3445
|
+
* fall under half of it, from 0 s (`hip_jiggle`, which never moves) to 2.75 s
|
|
3446
|
+
* on a 3.333 s idle, and end at 0.0025 rig px/s or less. `reference-demo`
|
|
3447
|
+
* declares no physics constraint.
|
|
3448
|
+
*/
|
|
3449
|
+
export const CLOTH_SETTLE_FLOOR = 0.01694;
|
|
3450
|
+
|
|
3451
|
+
/** The floor as the console and `check.json` state it. */
|
|
3452
|
+
export const CLOTH_FLOOR_TEXT = `a chain settles at the first frame from which its tip moves slower than ${CLOTH_SETTLE_FLOOR} rig px/s to the idle's last frame — half the slowest tip an idle key drives on the public examples (scarf's skirt_l2, 0.03388 rig px/s)`;
|
|
3453
|
+
|
|
3454
|
+
/** The consumer's two levers, as the console and `check.json` state them. */
|
|
3455
|
+
export const CLOTH_LEVERS_TEXT = "a chain at rest moves again when the world or the body does: an environment at render (rigc render --environment, wind and gravity in time; AUTHORING §3, What moves them, has a worked schedule) or a consumer's animation that moves the bone it hangs from; the drafted idle keys no hip (rig-c/parts#502)";
|
|
3456
|
+
|
|
3457
|
+
/** One physics chain as the line reads it. */
|
|
3458
|
+
export interface ClothRow {
|
|
3459
|
+
/** The leaf: the physics bone with no physics bone under it. */
|
|
3460
|
+
tip: string;
|
|
3461
|
+
/** The topmost physics bone of the run the leaf ends. */
|
|
3462
|
+
root: string;
|
|
3463
|
+
/** Physics bones from root to tip. */
|
|
3464
|
+
links: number;
|
|
3465
|
+
/** What was tracked: the leaf's end, or its origin when it has no `length`. */
|
|
3466
|
+
point: 'end' | 'origin';
|
|
3467
|
+
/** The time of the frame from which the tip stays under the floor; null when it never does. */
|
|
3468
|
+
settles_at: number | null;
|
|
3469
|
+
/** The fastest and slowest frame-to-frame speeds of the point, rig px/s, four significant figures. */
|
|
3470
|
+
max_rig_px_per_s: number;
|
|
3471
|
+
min_rig_px_per_s: number;
|
|
3472
|
+
}
|
|
3473
|
+
|
|
3474
|
+
/** The line: `WARN` with a chain that settles, `CLEAR` with none, `SKIP` with no chain or no idle. */
|
|
3475
|
+
export interface ClothLine {
|
|
3476
|
+
status: 'WARN' | 'CLEAR' | 'SKIP';
|
|
3477
|
+
reason?: string;
|
|
3478
|
+
fps: number;
|
|
3479
|
+
frames: number;
|
|
3480
|
+
duration: number;
|
|
3481
|
+
floor_rig_px_per_s: number;
|
|
3482
|
+
floor: string;
|
|
3483
|
+
levers: string;
|
|
3484
|
+
/** Rows that settle: the WARN lines. */
|
|
3485
|
+
warnings: number;
|
|
3486
|
+
rows: ClothRow[];
|
|
3487
|
+
}
|
|
3488
|
+
|
|
3489
|
+
/**
|
|
3490
|
+
* The physics chains of a rig, leaf by leaf in rig.json's bone order: each
|
|
3491
|
+
* bone a physics constraint names with no such bone under it, and how far up
|
|
3492
|
+
* the run of physics bones above it goes.
|
|
3493
|
+
*/
|
|
3494
|
+
export function clothChains(rig: Record<string, unknown>, physics: readonly LoopPhysics[]): Array<{ tip: string; root: string; links: number; length: number }> {
|
|
3495
|
+
const bones = (Array.isArray(rig.bones) ? rig.bones : []).filter(isRecord);
|
|
3496
|
+
const parent = new Map(bones.map((b) => [String(b.name), typeof b.parent === 'string' ? b.parent : null]));
|
|
3497
|
+
const length = new Map(bones.map((b) => [String(b.name), typeof b.length === 'number' && Number.isFinite(b.length) ? b.length : 0]));
|
|
3498
|
+
const held = new Set(physics.map((p) => p.bone));
|
|
3499
|
+
const above = (b: string, a: string): boolean => {
|
|
3500
|
+
const seen = new Set<string>();
|
|
3501
|
+
for (let p = parent.get(b) ?? null; p !== null && !seen.has(p); p = parent.get(p) ?? null) {
|
|
3502
|
+
if (p === a) return true;
|
|
3503
|
+
seen.add(p);
|
|
3504
|
+
}
|
|
3505
|
+
return false;
|
|
3506
|
+
};
|
|
3507
|
+
const out: Array<{ tip: string; root: string; links: number; length: number }> = [];
|
|
3508
|
+
for (const b of bones.map((x) => String(x.name))) {
|
|
3509
|
+
if (!held.has(b) || [...held].some((o) => o !== b && above(o, b))) continue;
|
|
3510
|
+
let root = b;
|
|
3511
|
+
let links = 1;
|
|
3512
|
+
for (let p = parent.get(b) ?? null; p !== null && held.has(p); p = parent.get(p) ?? null) {
|
|
3513
|
+
root = p;
|
|
3514
|
+
links++;
|
|
3515
|
+
}
|
|
3516
|
+
out.push({ tip: b, root, links, length: length.get(b) ?? 0 });
|
|
3517
|
+
}
|
|
3518
|
+
return out;
|
|
3519
|
+
}
|
|
3520
|
+
|
|
3521
|
+
/** One chain's row off its leaf's bone track: the point, the speeds, and when it settles under the floor. */
|
|
3522
|
+
export function clothRow(chain: { tip: string; root: string; links: number; length: number }, track: BoneTrack, floor: number = CLOTH_SETTLE_FLOOR): ClothRow {
|
|
3523
|
+
const L = chain.length;
|
|
3524
|
+
const pts = track.frames.map((w) => [w.worldX + L * w.a, w.worldY + L * w.c]);
|
|
3525
|
+
const speeds: number[] = [];
|
|
3526
|
+
for (let i = 1; i < pts.length; i++) {
|
|
3527
|
+
const dt = track.times[i] - track.times[i - 1];
|
|
3528
|
+
speeds.push(dt > 0 ? edgeLength(pts[i][0] - pts[i - 1][0], pts[i][1] - pts[i - 1][1]) / dt : Infinity);
|
|
3529
|
+
}
|
|
3530
|
+
let s = speeds.length;
|
|
3531
|
+
while (s > 0 && speeds[s - 1] < floor) s--;
|
|
3532
|
+
return {
|
|
3533
|
+
tip: chain.tip,
|
|
3534
|
+
root: chain.root,
|
|
3535
|
+
links: chain.links,
|
|
3536
|
+
point: L > 0 ? 'end' : 'origin',
|
|
3537
|
+
settles_at: speeds.length > 0 && s < speeds.length ? round3(track.times[s]) : null,
|
|
3538
|
+
max_rig_px_per_s: speeds.length === 0 ? 0 : sig4(Math.max(...speeds)),
|
|
3539
|
+
min_rig_px_per_s: speeds.length === 0 ? 0 : sig4(Math.min(...speeds)),
|
|
3540
|
+
};
|
|
3541
|
+
}
|
|
3542
|
+
|
|
3543
|
+
/** The line from its rows: WARN when a chain settles, CLEAR when none does, SKIP with the reason when nothing was read. */
|
|
3544
|
+
export function clothLine(rows: ClothRow[], fps: number, frames: number, duration: number, reason?: string): ClothLine {
|
|
3545
|
+
const warnings = rows.filter((r) => r.settles_at !== null).length;
|
|
3546
|
+
return {
|
|
3547
|
+
status: reason !== undefined ? 'SKIP' : warnings > 0 ? 'WARN' : 'CLEAR',
|
|
3548
|
+
...(reason === undefined ? {} : { reason }),
|
|
3549
|
+
fps,
|
|
3550
|
+
frames,
|
|
3551
|
+
duration: round3(duration),
|
|
3552
|
+
floor_rig_px_per_s: CLOTH_SETTLE_FLOOR,
|
|
3553
|
+
floor: CLOTH_FLOOR_TEXT,
|
|
3554
|
+
levers: CLOTH_LEVERS_TEXT,
|
|
3555
|
+
warnings,
|
|
3556
|
+
rows,
|
|
3557
|
+
};
|
|
3558
|
+
}
|
|
3559
|
+
|
|
3560
|
+
/** One warning as the console prints it. */
|
|
3561
|
+
export function clothWarnText(r: ClothRow, duration: number): string {
|
|
3562
|
+
const chain = r.links === 1 ? `"${r.tip}"` : `"${r.root}" .. "${r.tip}"`;
|
|
3563
|
+
return `WARN chain ${chain} (${r.links} physics link(s)) settles at ${r.settles_at ?? ''} s of the ${round3(duration)} s idle — its tip's ${r.point} under ${CLOTH_SETTLE_FLOOR} rig px/s from there to the last frame (fastest on the idle ${r.max_rig_px_per_s} rig px/s)`;
|
|
3564
|
+
}
|
|
3565
|
+
|
|
3403
3566
|
/**
|
|
3404
3567
|
* One `CHECK_RIGC_GREEN` per line that says why `rigc <what>` was not green:
|
|
3405
3568
|
* its FAIL and compile-error lines, or, when there are none, {@link causeLines}.
|
|
@@ -3711,11 +3874,13 @@ export function runCheck(rigDir: string, outDir: string, rigc: RigcRunner, parts
|
|
|
3711
3874
|
let chain: JudgementLine = skip(inp.noIdle ?? '');
|
|
3712
3875
|
let stretch: JudgementLine = skip(inp.noIdle ?? '');
|
|
3713
3876
|
let still: JudgementLine = skip(lacking);
|
|
3877
|
+
let idleGeometry: IdleGeometry | null = null;
|
|
3714
3878
|
if (idle !== null) {
|
|
3715
3879
|
chain = chainLine(inp, problems, reqFile === null ? NO_CHAIN_RESTS : { path: reqFile.path, rests: reqFile.rests });
|
|
3716
3880
|
// One reader for the one geometry file: the stretch reads its meshes, the still regions every slot and the head bone.
|
|
3717
3881
|
const geometryPath = join(idle.dir, GEOMETRY_FILE);
|
|
3718
3882
|
const geometry = readGeometry(geometryPath, idle.written);
|
|
3883
|
+
idleGeometry = geometry;
|
|
3719
3884
|
stretch = stretchLine(geometry, geometryPath, problems, inp);
|
|
3720
3885
|
if (full !== null) still = stillLine(full, idle, geometry, geometryPath, problems, notPlanted);
|
|
3721
3886
|
}
|
|
@@ -3847,6 +4012,26 @@ export function runCheck(rigDir: string, outDir: string, rigc: RigcRunner, parts
|
|
|
3847
4012
|
}
|
|
3848
4013
|
}
|
|
3849
4014
|
|
|
4015
|
+
// 3f. every physics chain read by its tip on the idle as rendered above, physics on (issue #501): a warning, never a
|
|
4016
|
+
// bar; SKIP, named, with no idle or no physics constraint
|
|
4017
|
+
let cloth: ClothLine;
|
|
4018
|
+
if (idle === null) cloth = clothLine([], IDLE_FPS, 0, 0, inp.noIdle ?? 'no idle');
|
|
4019
|
+
else if (stripped === null) cloth = clothLine([], idle.fps, idle.frames.length, idle.duration, "the rig declares no physics constraint (rig.json's constraints, motion.json's physics), so there is no chain to read");
|
|
4020
|
+
else {
|
|
4021
|
+
const geometryPath = join(idle.dir, GEOMETRY_FILE);
|
|
4022
|
+
const rows: ClothRow[] = [];
|
|
4023
|
+
let unread: string | undefined;
|
|
4024
|
+
for (const c of clothChains(inp.rig, stripped.physics)) {
|
|
4025
|
+
const track = boneTrackOf(idleGeometry, geometryPath, idle, c.tip);
|
|
4026
|
+
if (typeof track === 'string') {
|
|
4027
|
+
unread = track;
|
|
4028
|
+
break;
|
|
4029
|
+
}
|
|
4030
|
+
rows.push(clothRow(c, track));
|
|
4031
|
+
}
|
|
4032
|
+
cloth = unread === undefined ? clothLine(rows, idle.fps, idle.frames.length, idle.duration) : clothLine([], idle.fps, idle.frames.length, idle.duration, unread);
|
|
4033
|
+
}
|
|
4034
|
+
|
|
3850
4035
|
// 4. the setup pose as a one-key throwaway animation — the seam reads it against the parts, the source line against
|
|
3851
4036
|
// the painting — and, beside it, the same pose with the eyes shut
|
|
3852
4037
|
let seam: SeamFigures | null = null;
|
|
@@ -4027,6 +4212,7 @@ export function runCheck(rigDir: string, outDir: string, rigc: RigcRunner, parts
|
|
|
4027
4212
|
...(limbs === null ? {} : { [LIMB_LINE]: limbs }),
|
|
4028
4213
|
...(sweep === null ? {} : { [SWEEP_LINE]: sweep }),
|
|
4029
4214
|
...(motionSeam.pairs.length === 0 ? {} : { [MOTION_SEAM_LINE]: motionSeam }),
|
|
4215
|
+
...(stripped === null || idle === null ? {} : { [CLOTH_LINE]: cloth }),
|
|
4030
4216
|
RECOMPOSITE_HOLES: inp.parts === null ? { status: 'SKIP', reason: inp.noParts ?? '' } : holesLine(inp.parts),
|
|
4031
4217
|
...(sourceRead === null ? {} : { [SOURCE_LINE]: sourceRead }),
|
|
4032
4218
|
...(Object.keys(skipped).length === 0 ? {} : { skipped }),
|
|
@@ -4111,6 +4297,7 @@ export function runCheck(rigDir: string, outDir: string, rigc: RigcRunner, parts
|
|
|
4111
4297
|
requirements: reqBlock,
|
|
4112
4298
|
besideIdle: named,
|
|
4113
4299
|
motionSeam,
|
|
4300
|
+
cloth,
|
|
4114
4301
|
};
|
|
4115
4302
|
}
|
|
4116
4303
|
|