@rig-c/parts 1.19.0 → 1.21.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/README.md +16 -3
- package/cli.ts +24 -6
- package/docs/AUTHORING.md +172 -12
- package/docs/MIGRATION.md +39 -0
- package/docs/PAINTING.md +4 -3
- package/docs/STABILITY.md +11 -7
- package/package.json +2 -2
- package/skills/rig-parts/SKILL.md +17 -3
- package/src/assemble.ts +109 -21
- package/src/build.ts +20 -2
- package/src/check.ts +397 -7
- package/src/cloth.ts +378 -0
- package/src/config.ts +199 -18
- package/src/limbs.ts +87 -5
- package/src/parts.ts +44 -6
- package/src/propose.ts +187 -15
- package/src/rig.ts +21 -0
- package/src/scene.ts +1 -1
- package/src/structure.ts +2 -1
package/docs/STABILITY.md
CHANGED
|
@@ -53,7 +53,7 @@ defaults are the help's too, and none is guessed from the art.
|
|
|
53
53
|
| `comfy` | `seethrough`: `--image`, `--out`; optional `--resolution`, `--steps`, `--seed`, `--prefix`, `--offload`, `--lama`, `--nf4`, `--host`, `--wait`, `--timeout`, `--poll` |
|
|
54
54
|
| `assemble` | `--propose-plan` with `--source`, `--full`, `--config`, optional `--head`; or `--source`, `--full`, `--config`, `--out`, optional `--head` (without it, one run: issue #359), `--seam` (near-white, silhouette), `--project` (core, visible) |
|
|
55
55
|
| `propose` | `--head-box` with `--full`, `--canvas` |
|
|
56
|
-
| `propose` | `--parts`, `--source`, `--out`; optional `--compare` or `--from-config` (one of them), `--keypoints`, `--person` and `--wrist-snap` (each with `--keypoints`), `--full`, `--head` (the turn control's depths; not with `--from-config`), `--grid-rule` (`figure` or `part`, the drafted lattice's spacing; not with `--from-config`) |
|
|
56
|
+
| `propose` | `--parts`, `--source`, `--out`; optional `--compare` or `--from-config` (one of them), `--keypoints`, `--person` and `--wrist-snap` (each with `--keypoints`), `--full`, `--head` (the turn control's depths; not with `--from-config`), `--grid-rule` (`figure` or `part`, the drafted lattice's spacing; not with `--from-config`), `--cloth` (a config whose `cloth` block is the recipe, issue #364; not with `--from-config`) |
|
|
57
57
|
| `compare` | `--left`, `--right`; optional `--map` |
|
|
58
58
|
| `rig` | `--config`, `--parts`, `--out`; optional `--idle-keys` (ctl, direct), `--page-edges` (pot, free), `--pack-shape` (rect, polygon) |
|
|
59
59
|
| `check` | `--rig`, `--out`; optional `--parts`, `--source`, `--page-edges`, `--pack-shape`, `--requirements`, `--seam-pairs`, `--keep-stills` |
|
|
@@ -112,7 +112,7 @@ The fields, object by object (a bold path is where the object sits; `<part>` is
|
|
|
112
112
|
part name, `[i]` an entry of a list):
|
|
113
113
|
|
|
114
114
|
<!-- stable:config -->
|
|
115
|
-
- **config** — required by the full loader, in front of rig and build: `key`, `assemble`, `bones`, `meshes`, `regions`, `motion`; optional: `generation`, `seethrough`, `limbs`, `constraints`, `turn`, `scene`
|
|
115
|
+
- **config** — required by the full loader, in front of rig and build: `key`, `assemble`, `bones`, `meshes`, `regions`, `motion`; optional: `generation`, `seethrough`, `limbs`, `constraints`, `turn`, `scene`, `cloth`
|
|
116
116
|
- **generation** — required: `checkpoint`, `loras`, `trigger`, `identity`, `sampler`, `costume`, `negative_extra`, `style`, `negative_pose`, `latent`, `seed`; optional: `pose`, `control`
|
|
117
117
|
- **generation.loras[i]** — required: `name`, `strength`; optional: `strength_clip`
|
|
118
118
|
- **generation.sampler** — required: `steps`, `cfg`, `sampler`, `scheduler`
|
|
@@ -123,10 +123,10 @@ part name, `[i]` an entry of a list):
|
|
|
123
123
|
- **assemble.extend_below_crop[i]** — required: `part`, `run`, `tag`
|
|
124
124
|
- **assemble.patches[i]** — required: `name`, `box`, `alpha`, `draw`
|
|
125
125
|
- **assemble.patches[i].draw**, when an object — required: `before`
|
|
126
|
-
- **assemble.cuts[i]** — required: `from`, `into`, `polygon`, `draw`, `overlap`; optional: `under`
|
|
126
|
+
- **assemble.cuts[i]** — required: `from`, `into`, `polygon`, `draw`, `overlap`; optional: `under`, `under_fill`
|
|
127
127
|
- **assemble.cuts[i].draw**, when an object — required: `before`
|
|
128
128
|
- **limbs.<limb>** — required: `segments` (issue #349)
|
|
129
|
-
- **limbs.<limb>.segments[i]** — required: `part`, `from`, `bone`; required below a joint (i > 0): `joint`; optional below a joint: `range`, `cap`
|
|
129
|
+
- **limbs.<limb>.segments[i]** — required: `part`, `from`, `bone`; required below a joint (i > 0): `joint`; optional below a joint: `range`, `cap`, `edge`, `cap_shape`
|
|
130
130
|
- **bones[i]**, a single bone — required: `name`, `parent`, `at`; optional: `tip`, `inherit`
|
|
131
131
|
- **bones[i]**, a chain — required: `chain`, `parent`, `points`, `tip`
|
|
132
132
|
- **meshes.<part>** — exactly one of `grid`, `contour`, `auto`; required in every mode: `r`, `segments`; optional in every mode: `exponent`, `rule`; optional with `contour` or `auto`: `ribs`; optional with `grid` or `contour`: `influences` (issue #284; beside `auto` it is refused, CONFIG_INFLUENCES_MODE); optional with `grid`: `stray` (issue #303; beside `contour` or `auto` it is refused, CONFIG_STRAY_MODE)
|
|
@@ -147,6 +147,10 @@ part name, `[i]` an entry of a list):
|
|
|
147
147
|
- **meshes.<part>.auto.regions[i]** — required: `name`, `shape`, `maxEdgeLength`, `transition`, `grade`, `minArtSamples`, and `cx`, `cy`, `r` for a circle or `points` for a polygon; optional: `bone`, `band` (both or neither; with neither the region is density only)
|
|
148
148
|
- **meshes.<part>.auto.boundaryRuns** — required: `maxVertices`
|
|
149
149
|
- **meshes.<part>.auto.stimulus**, or each entry of it when a list — required: `chain`, `degrees`
|
|
150
|
+
- **cloth.<tag>**, the tag bottomwear, back hair or front hair (issue #364; read by propose --cloth only) — required: `chains`, `links`, `wind`, `gravity`; optional: `split`, `length_ratio`, `strength`, `damping`, `alternate_damping`, `translate`, `swing` (on bottomwear only)
|
|
151
|
+
- **cloth.<tag>.split** — required: `at`, `into`
|
|
152
|
+
- **cloth.<tag>.strength** — required: `trunk`, `branch`, `edge`
|
|
153
|
+
- **cloth.<tag>.swing** — required: `scales`
|
|
150
154
|
- **regions.<part>** — a bone name
|
|
151
155
|
- **motion** — required: `duration`, `tracks`; optional: `blink`, `animations_from`
|
|
152
156
|
- **motion.tracks[i]**, a single track — required: `bone`, `prop`, `amp`, `period`, `phase`; optional: `base`
|
|
@@ -174,13 +178,13 @@ every number) are AUTHORING §3's tables, and the loader refuses any other by na
|
|
|
174
178
|
|
|
175
179
|
| file | written by | what is stable |
|
|
176
180
|
| --- | --- | --- |
|
|
177
|
-
| `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` 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`), 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) |
|
|
181
|
+
| `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`, 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) |
|
|
178
182
|
| `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) |
|
|
179
183
|
| `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) |
|
|
180
|
-
| `check/check.json` and the rest of `check/` | `check`, `build`, `compose` | the reference's fields in its order (`gate_spine_html_green`, `rigc_entry`, `pack_mode`, `loop_max_diff`, the seam figures — `seam_mean`, `seam_px_over_40`, `seam_px_over_80` and, from issue #216, `seam_frame_scale`, the frame scale the bar divides the count by), `loop_physics` after `loop_max_diff` when the rig declares a physics constraint, `seam_border` after the seam figures when `parts.json` declares a scene (issue #242), `seam_pairs` after the seam figures when the seam bar fails or `--seam-pairs` asks (each row's picture under `check/seam/`; `--keep-stills` leaves the setup-pose still and its `frames.json` under `check/setup_still/`), then the judgement lines (`BREATH_VISIBLE`, `BLINK_NO_HOLE`, `CHAIN_LAG`, `TIP_OVER_ROOT`, `STILL_REGIONS_DARK`, `TEXTURE_STRETCH`), `ANIMATIONS_DO_NOT_FOLD` after them when `motion.json` holds an animation, the idle included — on every rig with an idle since issue #333 (issue #320: `status`, `animations`, `fps`, `physics_left_out`, `folding`, `folding_undeclared`, `declared_not_folding`, `bar`), `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`), `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 |
|
|
184
|
+
| `check/check.json` and the rest of `check/` | `check`, `build`, `compose` | the reference's fields in its order (`gate_spine_html_green`, `rigc_entry`, `pack_mode`, `loop_max_diff`, the seam figures — `seam_mean`, `seam_px_over_40`, `seam_px_over_80` and, from issue #216, `seam_frame_scale`, the frame scale the bar divides the count by), `loop_physics` after `loop_max_diff` when the rig declares a physics constraint, `seam_border` after the seam figures when `parts.json` declares a scene (issue #242), `seam_pairs` after the seam figures when the seam bar fails or `--seam-pairs` asks (each row's picture under `check/seam/`; `--keep-stills` leaves the setup-pose still and its `frames.json` under `check/setup_still/`), then the judgement lines (`BREATH_VISIBLE`, `BLINK_NO_HOLE`, `CHAIN_LAG`, `TIP_OVER_ROOT`, `STILL_REGIONS_DARK`, `TEXTURE_STRETCH`), `ANIMATIONS_DO_NOT_FOLD` after them when `motion.json` holds an animation, the idle included — on every rig with an idle since issue #333 (issue #320: `status`, `animations`, `fps`, `physics_left_out`, `folding`, `folding_undeclared`, `declared_not_folding`, `bar`), `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), `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 |
|
|
181
185
|
| `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 |
|
|
182
186
|
| the template `.psd` | `layers --template` | one empty layer per name `templateLayerNames` (`src/layers.ts`) holds, bottom first; the same canvas writes the same bytes |
|
|
183
|
-
| `proposal.json`, `render/landmarks*.png` | `propose` | the config's four rig sections, `turn` with `--full`/`--head`, and `notes` |
|
|
187
|
+
| `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`, and `notes` |
|
|
184
188
|
| `basis.json` | `propose` | spec `spine-parts-basis/1` |
|
|
185
189
|
| `config.json`, `mesh_draft.json` | `mesh-draft` | `config.json` is the config read, byte for byte, but for each drafted part's `meshes` entry; `mesh_draft.json` is spec `rig-parts-mesh-draft/1`: `rules`, `lattice_check`, `draft_check`, `figure_overdraw`, and per lattice part `part`, `mode`, `lattice`, `candidate` (with `bridges` — `units`, `joins` of `span` and `area_px2`, `area_px2`, `overdraw`, and `faint` only when an island was left out as faint — only when the candidate is a bridged one, issue #326), `delta` (under `--more-triangles` only), `readings`, `gate` (each line's `reading`, `pass`, `text`, `margin`), `note`; and `not_compared` (issue #307), only when the config holds an `auto` or `contour` entry: per such entry, in config order, `part`, `mode` and `note` — the part was neither drafted nor compared, and a config of lattices alone writes no such key |
|
|
186
190
|
| the scene report | `compose` | spec `spine-parts-scene-report/1` |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rig-c/parts",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.21.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": {
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
63
|
"ag-psd": "^31.0.2",
|
|
64
|
-
"rig-c": "^2.
|
|
64
|
+
"rig-c": "^2.54.0"
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
67
|
"@esotericsoftware/spine-core": "4.3.13",
|
|
@@ -84,9 +84,11 @@ under that.
|
|
|
84
84
|
`work/render/recomposite_error_rig.png`; a large red hole between two parts is a
|
|
85
85
|
gap See-through left (a skirt split into two legs), fixed in the plan or the layers,
|
|
86
86
|
not in the rig. Then `rig-parts propose --parts work/rig
|
|
87
|
-
--source <painting> --out work` → copy `bones`, `meshes`, `regions`, `
|
|
88
|
-
from `proposal.json` into the config
|
|
89
|
-
|
|
87
|
+
--source <painting> --out work` → copy `bones`, `meshes`, `regions`, `limbs`,
|
|
88
|
+
`motion`, `constraints`, `turn` from `proposal.json` into the config, each it
|
|
89
|
+
wrote (`limbs` only with `--keypoints`, `constraints` only with `--cloth`, `turn`
|
|
90
|
+
only with `--full`/`--head`; a `limbs` block left out is refused at the next step). `assemble` needs the plan and none of
|
|
91
|
+
those sections; do not stub them to get past it (AUTHORING §4 says what each step
|
|
90
92
|
requires). If an `uncovered hole` stays large with the plan right — a piece
|
|
91
93
|
of the figure neither run holds, red in `recomposite_error_rig.png` — add an
|
|
92
94
|
`assemble.patches` entry (a rig-pixel box, `alpha: "silhouette"`, drawn
|
|
@@ -108,6 +110,18 @@ under that.
|
|
|
108
110
|
again, and read each `limb cap:` line for the pixels a cap is missing. A clean
|
|
109
111
|
LINT does not show a seated hip is right; the overlay does. Never write a joint you did not get from an
|
|
110
112
|
estimator or the painting: say `missing` and let the rule stand in.
|
|
113
|
+
A skirt or hair that must answer the scene (wind, the body moving) rather than
|
|
114
|
+
loop a keyed swing: add a `cloth` block to the config — one recipe per tag,
|
|
115
|
+
`bottomwear`, `back hair`, `front hair`: `chains`, `links`, `wind` and `gravity`
|
|
116
|
+
as `[root, tip]` (multipliers of the consumer's environment, tip >= root), and
|
|
117
|
+
optionally `split`, `strength`, `damping`/`alternate_damping`, `translate`,
|
|
118
|
+
and on the skirt `swing.scales` (AUTHORING §3, *Recipe: cloth and hair*) —
|
|
119
|
+
and propose with `--cloth config.json`; copy its `constraints` too. Every
|
|
120
|
+
number is yours but `length_ratio` and `strength`, whose defaults the note
|
|
121
|
+
names. Weights across neighbouring trees are not drafted (issue #378). No
|
|
122
|
+
stop at the legs is drafted: on the recipe's fixture the environment alone
|
|
123
|
+
pushed no hem through a thigh, but the consumer's hip motion can
|
|
124
|
+
(rig-c/core#1413).
|
|
111
125
|
6. Correct against `render/landmarks.png` and `note:` lines; a
|
|
112
126
|
`coverage … not checked` line is a bone no check read (the line says why),
|
|
113
127
|
and `basis.json` beside the proposal says what each bone rests on — check
|
package/src/assemble.ts
CHANGED
|
@@ -93,7 +93,9 @@
|
|
|
93
93
|
* - **What lies under a piece** (`cuts[].under`, `fillUnder`, issue #193) is
|
|
94
94
|
* this port's: an authored image the base takes where the cut removed its
|
|
95
95
|
* pixels, so a piece that swings away leaves no hole. The stage reads that
|
|
96
|
-
* image only there and never invents a pixel.
|
|
96
|
+
* image only there and never invents a pixel. With `under_fill: "colour"`
|
|
97
|
+
* (issue #360) the base takes the image's colour only and keeps the alpha
|
|
98
|
+
* the cut removed, so the part's silhouette is the uncut layer's.
|
|
97
99
|
*
|
|
98
100
|
* Pure: no clock, no randomness, no file access. The CLI reads and writes.
|
|
99
101
|
*/
|
|
@@ -102,7 +104,7 @@ import { type Problem, refuseIfAny } from './errors.ts';
|
|
|
102
104
|
import { figuresPhrase, implausibleRules, type Layer, type LayerFigures, layerFigures, type LayerSet, NEAR_WHITE_MIN, OPAQUE_ALPHA_ABOVE, ruleSummary } from './layers.ts';
|
|
103
105
|
import { padToSquare, squarePad } from './inputs.ts';
|
|
104
106
|
import { type CapRecord, type LimbRecord, PAINTING_RUN, type PartRecord, type PartsFile, type RecompositeRecord, type SceneRecord, type SingleRunPad } from './parts.ts';
|
|
105
|
-
import { type BonePlace, bonePlaces, capBox, inDisc, type JointLine,
|
|
107
|
+
import { type BonePlace, bonePlaces, capBox, coveredRadius, featherWeight, halfWidthAbout, inDisc, jointLine, type JointLine, jointOffsets, limbPolyline, limbRun, nearestLimb, pastJoint, segmentOf } from './limbs.ts';
|
|
106
108
|
import {
|
|
107
109
|
alphaComposite,
|
|
108
110
|
connectedComponents,
|
|
@@ -1726,22 +1728,28 @@ export interface CutCount {
|
|
|
1726
1728
|
under?: number;
|
|
1727
1729
|
/** With `under` only: the removed pixels the image left empty (its alpha below the alpha removed); `under + underEmpty` is the removed count. */
|
|
1728
1730
|
underEmpty?: number;
|
|
1731
|
+
/** With `under_fill: "colour"` only (issue #360): the `under` pixels took the image's colour at the alpha the cut removed; absent without it. */
|
|
1732
|
+
underFill?: 'colour';
|
|
1729
1733
|
}
|
|
1730
1734
|
|
|
1731
1735
|
/**
|
|
1732
1736
|
* The cut lines the assemble stage prints, one per plan part with cuts, in
|
|
1733
1737
|
* the `pixels:` line's shape: ` cut: "<from>" opaque <before> = "<from>"
|
|
1734
1738
|
* <kept> + "<into>" <taken> …`. Empty without cuts, so a config with none
|
|
1735
|
-
* prints what it printed before.
|
|
1739
|
+
* prints what it printed before. With a cut of `under_fill: "colour"`
|
|
1740
|
+
* (issue #360) the filled count says how many of its pixels took the colour
|
|
1741
|
+
* only; without one the line is issue #193's.
|
|
1736
1742
|
*/
|
|
1737
1743
|
export function cutLines(cuts: readonly CutCount[]): string[] {
|
|
1738
1744
|
const byFrom = new Map<string, CutCount[]>();
|
|
1739
1745
|
for (const c of cuts) byFrom.set(c.from, [...(byFrom.get(c.from) ?? []), c]);
|
|
1740
|
-
return [...byFrom.entries()].map(
|
|
1741
|
-
(
|
|
1746
|
+
return [...byFrom.entries()].map(([from, cs]) => {
|
|
1747
|
+
const colour = cs.some((c) => c.underFill === 'colour') ? ` (${cs.reduce((a, c) => a + (c.underFill === 'colour' ? (c.under ?? 0) : 0), 0)} colour only, at the alpha the cut removed)` : '';
|
|
1748
|
+
return (
|
|
1742
1749
|
` cut: "${from}" opaque ${cs[0].before} = "${from}" ${cs[0].kept} + ${cs.map((c) => `"${c.into}" ${c.taken}`).join(' + ')}; band ${cs.reduce((a, c) => a + c.band, 0)} px held by both` +
|
|
1743
|
-
(cs.some((c) => c.under !== undefined) ? `; under ${cs.reduce((a, c) => a + (c.under ?? 0), 0)} px filled, ${cs.reduce((a, c) => a + (c.underEmpty ?? 0), 0)} px left empty` : '')
|
|
1744
|
-
|
|
1750
|
+
(cs.some((c) => c.under !== undefined) ? `; under ${cs.reduce((a, c) => a + (c.under ?? 0), 0)} px filled${colour}, ${cs.reduce((a, c) => a + (c.underEmpty ?? 0), 0)} px left empty` : '')
|
|
1751
|
+
);
|
|
1752
|
+
});
|
|
1745
1753
|
}
|
|
1746
1754
|
|
|
1747
1755
|
/**
|
|
@@ -1771,8 +1779,14 @@ export function underBox(polygon: ReadonlyArray<readonly [number, number]>): { x
|
|
|
1771
1779
|
* is the only place the image is read: its pixels over the band, over pixels
|
|
1772
1780
|
* the layer never had, and over the box outside the polygon change nothing.
|
|
1773
1781
|
* `filled + empty` is the removed count.
|
|
1782
|
+
*
|
|
1783
|
+
* With `fill` `"colour"` (issue #360, `cuts[].under_fill`) the same pixels are
|
|
1784
|
+
* filled and left empty, and a filled pixel takes the image's colour at the
|
|
1785
|
+
* alpha the cut removed there instead of the image's alpha: the part's alpha
|
|
1786
|
+
* is the uncut layer's wherever the image fills, so its silhouette does not
|
|
1787
|
+
* grow over a soft edge an opaque image covers.
|
|
1774
1788
|
*/
|
|
1775
|
-
export function fillUnder(base: Raster, piece: Raster, band: Mask, under: Raster, box: { x0: number; y0: number }): { filled: number; empty: number } {
|
|
1789
|
+
export function fillUnder(base: Raster, piece: Raster, band: Mask, under: Raster, box: { x0: number; y0: number }, fill?: 'colour'): { filled: number; empty: number } {
|
|
1776
1790
|
const W = base.width;
|
|
1777
1791
|
let filled = 0;
|
|
1778
1792
|
let empty = 0;
|
|
@@ -1784,7 +1798,10 @@ export function fillUnder(base: Raster, piece: Raster, band: Mask, under: Raster
|
|
|
1784
1798
|
empty++;
|
|
1785
1799
|
continue;
|
|
1786
1800
|
}
|
|
1787
|
-
|
|
1801
|
+
if (fill === 'colour') {
|
|
1802
|
+
base.data.set(under.data.subarray(u, u + 3), p * 4);
|
|
1803
|
+
base.data[p * 4 + 3] = a;
|
|
1804
|
+
} else base.data.set(under.data.subarray(u, u + 4), p * 4);
|
|
1788
1805
|
filled++;
|
|
1789
1806
|
}
|
|
1790
1807
|
return { filled, empty };
|
|
@@ -1839,7 +1856,7 @@ export interface LimbCount {
|
|
|
1839
1856
|
before: number;
|
|
1840
1857
|
shares: Array<{ part: string; px: number }>;
|
|
1841
1858
|
/** The joints whose cap this part's segments carry, in `config.limbs` order. */
|
|
1842
|
-
joints: Array<{ limb: string; joint: string; part: string; cap: CapRecord }>;
|
|
1859
|
+
joints: Array<{ limb: string; joint: string; part: string; upper: string; cap: CapRecord }>;
|
|
1843
1860
|
}
|
|
1844
1861
|
|
|
1845
1862
|
/**
|
|
@@ -1853,13 +1870,22 @@ export function limbLines(counts: readonly LimbCount[]): string[] {
|
|
|
1853
1870
|
out.push(` limb cut: "${c.of}" opaque ${c.before} = ${c.shares.map((q) => `"${q.part}" ${q.px}`).join(' + ')}`);
|
|
1854
1871
|
for (const j of c.joints) {
|
|
1855
1872
|
const k = j.cap;
|
|
1856
|
-
const
|
|
1873
|
+
const base = k.image_px === undefined ? `${k.layer_px} px from the layer` : `${k.image_px} px from ${k.image_layer === undefined ? 'the cap image' : `the hidden-art layer ${k.image_layer}`}, ${k.layer_px} px from the layer`;
|
|
1874
|
+
// Issue #360: what cap_shape "disc" and edge "feather" did, said only when declared, so a limb without them prints what it did.
|
|
1875
|
+
const from = k.shape === undefined ? base : `${base}, ${k.upper_px ?? 0} px from "${j.upper}"`;
|
|
1876
|
+
const radius =
|
|
1877
|
+
k.shape === undefined
|
|
1878
|
+
? `a disc of radius ${k.radius} px (the limb ${k.width} px across the joint line)`
|
|
1879
|
+
: `a disc of radius ${k.radius} px (cap_shape "disc": the joint's distance to the nearer side of the limb, ${k.width} px across the joint line)`;
|
|
1880
|
+
const shaped = [
|
|
1881
|
+
...(k.edge === undefined ? [] : [`edge "feather": ${k.feathered_px ?? 0} px of "${j.upper}" faded across the ${k.feather_radius ?? 0} px about the joint that "${j.part}" covers at every turn`]),
|
|
1882
|
+
];
|
|
1857
1883
|
const miss =
|
|
1858
1884
|
k.missing_px === 0
|
|
1859
1885
|
? 'none missing'
|
|
1860
1886
|
: `${k.missing_px} px missing — the layer has no pixel there (hidden in the painting, or past the limb's outline), and the tool invents none: paint them into a ${k.box[2]}x${k.box[3]} image whose pixel (i, j) is rig pixel (${k.box[0]} + i, ${k.box[1]} + j), and name it in the segment's "cap"`;
|
|
1861
1887
|
out.push(
|
|
1862
|
-
` limb cap: "${j.limb}" joint "${j.joint}" at [${k.at[0]}, ${k.at[1]}], on "${j.part}":
|
|
1888
|
+
` limb cap: "${j.limb}" joint "${j.joint}" at [${k.at[0]}, ${k.at[1]}], on "${j.part}": ${radius}, box [${k.box.join(', ')}] — on the upper side of the line ${from}, ${miss}${shaped.map((q) => `; ${q}`).join('')}`,
|
|
1863
1889
|
);
|
|
1864
1890
|
}
|
|
1865
1891
|
}
|
|
@@ -1926,7 +1952,7 @@ function cutLimbs(
|
|
|
1926
1952
|
const parts: LimbPart[] = [];
|
|
1927
1953
|
for (const l of names) {
|
|
1928
1954
|
limbs[l].segments.forEach((sg, k) =>
|
|
1929
|
-
parts.push({ limb: l, segment: k, part: sg.part, of: sg.from, record: { name: l, segment: k, of: sg.from, bone: sg.bone, ...(k === 0 ? {} : { joint: sg.joint as string }) } }),
|
|
1955
|
+
parts.push({ limb: l, segment: k, part: sg.part, of: sg.from, record: { name: l, segment: k, of: sg.from, bone: sg.bone, ...(k === 0 ? {} : { joint: sg.joint as string, ...(sg.range === undefined ? {} : { range: [sg.range[0], sg.range[1]] as [number, number] }) }) } }),
|
|
1930
1956
|
);
|
|
1931
1957
|
}
|
|
1932
1958
|
const polylines = new Map(names.map((l) => [l, limbPolyline(limbs[l], places)]));
|
|
@@ -2028,8 +2054,8 @@ function cutLimbs(
|
|
|
2028
2054
|
return false;
|
|
2029
2055
|
};
|
|
2030
2056
|
const object = `config.limbs.${l}.segments[${k}] (joint "${String(sg.joint)}", part "${sg.part}")`;
|
|
2031
|
-
const
|
|
2032
|
-
if (
|
|
2057
|
+
const run = limbRun(line, art, [0, 0, W - 1, H - 1]);
|
|
2058
|
+
if (run === null) {
|
|
2033
2059
|
problems.push({
|
|
2034
2060
|
code: 'ASSEMBLE_LIMB_JOINT_ON_ART',
|
|
2035
2061
|
object,
|
|
@@ -2038,10 +2064,23 @@ function cutLimbs(
|
|
|
2038
2064
|
return;
|
|
2039
2065
|
}
|
|
2040
2066
|
// The width to 3 places (src/round.ts): a measured length, and a radius the line and the record print as read.
|
|
2041
|
-
const w3 = pyRound(
|
|
2042
|
-
|
|
2067
|
+
const w3 = pyRound(run[1] - run[0] + 1, 3);
|
|
2068
|
+
// Issue #360: under cap_shape "disc" the radius is the half-width about the joint (src/limbs.ts), to 3 places as well.
|
|
2069
|
+
const disc = sg.cap_shape === 'disc';
|
|
2070
|
+
const half = halfWidthAbout(run);
|
|
2071
|
+
if (disc && half <= 0) {
|
|
2072
|
+
problems.push({
|
|
2073
|
+
code: 'ASSEMBLE_LIMB_JOINT_ON_ART',
|
|
2074
|
+
object,
|
|
2075
|
+
detail: `the joint, "${sg.bone}"'s origin at [${line.at.join(', ')}], lies on the edge of the limb's art along its joint line — the run of art reaches ${pyRound(run[1] + 0.5, 3)} px from it one way and ${pyRound(0.5 - run[0], 3)} px the other — so cap_shape "disc" has no half-width about it; a bone origin inside the limb's art is required — move the bone onto the joint`,
|
|
2076
|
+
});
|
|
2077
|
+
return;
|
|
2078
|
+
}
|
|
2079
|
+
const radius = disc ? pyRound(half, 3) : w3 / 2;
|
|
2043
2080
|
const box = capBox(line.at, radius);
|
|
2044
2081
|
const at = parts.findIndex((q) => q.limb === l && q.segment === k);
|
|
2082
|
+
const upAt = parts.findIndex((q) => q.limb === l && q.segment === k - 1);
|
|
2083
|
+
const upper = rasters[upAt];
|
|
2045
2084
|
// Issue #359: the lower segment's part's hidden-art layer is its cap when the segment names no cap image; with one named, a refusal.
|
|
2046
2085
|
const hidden = hiddenOf(sg.from);
|
|
2047
2086
|
if (hidden !== null && sg.cap !== undefined) {
|
|
@@ -2066,6 +2105,7 @@ function cutLimbs(
|
|
|
2066
2105
|
const ownLower = owners.get(sg.from) as Int32Array;
|
|
2067
2106
|
let layerPx = 0;
|
|
2068
2107
|
let imagePx = 0;
|
|
2108
|
+
let upperPx = 0;
|
|
2069
2109
|
let missing = 0;
|
|
2070
2110
|
for (let y = Math.max(0, box[1]); y < Math.min(H, box[1] + box[3]); y++) {
|
|
2071
2111
|
for (let x = Math.max(0, box[0]); x < Math.min(W, box[0] + box[2]); x++) {
|
|
@@ -2078,6 +2118,10 @@ function cutLimbs(
|
|
|
2078
2118
|
} else if (ownLower[p] === li && lowerSrc.r.data[p * 4 + 3] > 0) {
|
|
2079
2119
|
lower.data.set(lowerSrc.r.data.subarray(p * 4, p * 4 + 4), p * 4);
|
|
2080
2120
|
layerPx++;
|
|
2121
|
+
} else if (disc && upper.data[p * 4 + 3] > 0) {
|
|
2122
|
+
// Issue #360: the disc is filled from the art — the upper segment's, where the lower's layer has none.
|
|
2123
|
+
lower.data.set(upper.data.subarray(p * 4, p * 4 + 4), p * 4);
|
|
2124
|
+
upperPx++;
|
|
2081
2125
|
} else missing++;
|
|
2082
2126
|
}
|
|
2083
2127
|
}
|
|
@@ -2085,11 +2129,43 @@ function cutLimbs(
|
|
|
2085
2129
|
problems.push({ code: 'ASSEMBLE_LIMB_CAP_EMPTY', object: `${object}.cap`, detail: `${String(sg.cap)} is transparent over every one of the cap's pixels; an image that gives at least one is required — to keep the layer's cap, drop cap` });
|
|
2086
2130
|
return;
|
|
2087
2131
|
}
|
|
2088
|
-
|
|
2132
|
+
// Issue #360: under edge "feather", the upper piece fades out across the cap, over the lower's opaque pixels only.
|
|
2133
|
+
let featheredPx = 0;
|
|
2134
|
+
let band = 0;
|
|
2135
|
+
if (sg.edge === 'feather') {
|
|
2136
|
+
band = pyRound(Math.max(0, coveredRadius(line.at, radius, (x, y) => x >= 0 && y >= 0 && x < W && y < H && lower.data[(y * W + x) * 4 + 3] === 255)), 3);
|
|
2137
|
+
const fb = capBox(line.at, band);
|
|
2138
|
+
for (let y = Math.max(0, fb[1]); y < Math.min(H, fb[1] + fb[3]); y++) {
|
|
2139
|
+
for (let x = Math.max(0, fb[0]); x < Math.min(W, fb[0] + fb[2]); x++) {
|
|
2140
|
+
if (band === 0 || !inDisc(line.at, band, x, y) || pastJoint(line, x, y)) continue;
|
|
2141
|
+
const p = y * W + x;
|
|
2142
|
+
const ua = upper.data[p * 4 + 3];
|
|
2143
|
+
if (ua === 0) continue;
|
|
2144
|
+
const o = jointOffsets(line, x, y);
|
|
2145
|
+
const na = Math.floor(ua * featherWeight(band, o.along, o.across) + 0.5);
|
|
2146
|
+
if (na === ua) continue;
|
|
2147
|
+
if (na === 0) upper.data.fill(0, p * 4, p * 4 + 4);
|
|
2148
|
+
else upper.data[p * 4 + 3] = na;
|
|
2149
|
+
featheredPx++;
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
const cap: CapRecord = {
|
|
2154
|
+
at: [line.at[0], line.at[1]],
|
|
2155
|
+
width: w3,
|
|
2156
|
+
radius,
|
|
2157
|
+
box,
|
|
2158
|
+
layer_px: layerPx,
|
|
2159
|
+
...(img === null ? {} : { image_px: imagePx }),
|
|
2160
|
+
...(hidden === null ? {} : { image_layer: hidden.layer }),
|
|
2161
|
+
missing_px: missing,
|
|
2162
|
+
...(disc ? { shape: 'disc' as const, upper_px: upperPx } : {}),
|
|
2163
|
+
...(sg.edge === 'feather' ? { edge: 'feather' as const, feather_radius: band, feathered_px: featheredPx } : {}),
|
|
2164
|
+
};
|
|
2089
2165
|
if (hidden !== null) readHidden(hidden.layer, `config.limbs.${l}.segments[${k}].cap (part "${sg.part}")`);
|
|
2090
2166
|
parts[at].record.cap = cap;
|
|
2091
2167
|
const c = counts.find((q) => q.of === sg.from);
|
|
2092
|
-
if (c !== undefined) c.joints.push({ limb: l, joint: String(sg.joint), part: sg.part, cap });
|
|
2168
|
+
if (c !== undefined) c.joints.push({ limb: l, joint: String(sg.joint), part: sg.part, upper: segs[k - 1].part, cap });
|
|
2093
2169
|
});
|
|
2094
2170
|
}
|
|
2095
2171
|
refuseIfAny(problems);
|
|
@@ -2377,7 +2453,17 @@ export function assemble(input: AssembleInput): AssembleResult {
|
|
|
2377
2453
|
cuts,
|
|
2378
2454
|
);
|
|
2379
2455
|
cuts.forEach((q, i) => {
|
|
2380
|
-
if (q.under === undefined)
|
|
2456
|
+
if (q.under === undefined) {
|
|
2457
|
+
// Issue #360: under_fill says how an under fills; a cut with no under (no file, no hidden-art layer) has nothing it could say it of.
|
|
2458
|
+
if (q.under_fill !== undefined) {
|
|
2459
|
+
patchProblems.push({
|
|
2460
|
+
code: 'ASSEMBLE_UNDER_FILL_SOURCE',
|
|
2461
|
+
object: `config.assemble.cuts[${i}].under_fill (part "${q.into}")`,
|
|
2462
|
+
detail: `is "${q.under_fill}", and the cut has no under: it names no under file and "${q.from}" has no hidden-art layer; under_fill says how an under fills the pixels the cut removed, so one is required — name under, or drop under_fill`,
|
|
2463
|
+
});
|
|
2464
|
+
}
|
|
2465
|
+
return;
|
|
2466
|
+
}
|
|
2381
2467
|
const object = `config.assemble.cuts[${i}].under (part "${q.into}")`;
|
|
2382
2468
|
const u = unders[i];
|
|
2383
2469
|
if (u === null) {
|
|
@@ -2563,7 +2649,8 @@ export function assemble(input: AssembleInput): AssembleResult {
|
|
|
2563
2649
|
const u = unders[i];
|
|
2564
2650
|
if (u === null) return undefined;
|
|
2565
2651
|
// Filled where the image is at least as opaque as the layer was; left empty elsewhere (`fillUnder`).
|
|
2566
|
-
|
|
2652
|
+
// With under_fill "colour" (issue #360) a filled pixel keeps the alpha the cut removed and takes the image's colour only.
|
|
2653
|
+
const f = fillUnder(b.r, (pieces[i] as Built).r, cutBand(cutMasks[i], cuts[i].overlap), u, underBox(cuts[i].polygon), cuts[i].under_fill);
|
|
2567
2654
|
// A hidden-art layer (issue #359) is the whole layer's covered art, read by every cut of its part: a cut it holds nothing
|
|
2568
2655
|
// under fills 0, which the cut line counts, and is not refused as a file made for this one cut would be.
|
|
2569
2656
|
if (f.filled === 0 && input.cuts[i].under !== undefined) {
|
|
@@ -2581,6 +2668,7 @@ export function assemble(input: AssembleInput): AssembleResult {
|
|
|
2581
2668
|
if (f !== undefined) {
|
|
2582
2669
|
c.under = f.filled;
|
|
2583
2670
|
c.underEmpty = f.empty;
|
|
2671
|
+
if (cuts[i].under_fill === 'colour') c.underFill = 'colour';
|
|
2584
2672
|
}
|
|
2585
2673
|
cutCounts.push(c);
|
|
2586
2674
|
});
|
package/src/build.ts
CHANGED
|
@@ -70,7 +70,7 @@ import {
|
|
|
70
70
|
splitSchedule,
|
|
71
71
|
untestedIntervals,
|
|
72
72
|
} from './autoreplay.ts';
|
|
73
|
-
import { besideIdleLine, FOLD_LINE, type FoldsLine, LEVEL_LINE, type LevelLine, LIMB_LINE, type LimbLine, STRESS_LINE, buildGateLines, causeLines, IDLE_FPS, IDLE_MAX_PX, maxRgbDiff, REQUIREMENTS_DIR, type CheckReport, type PackLine, DEFAULT_PACK_SHAPE, DEFAULT_PAGE_EDGES, JUDGEMENT_LINES, type JudgementLine, packedBuildArgs, packedBuildLabel, type PackMode, type PackShape, type PageEdges, readFrameSet, rigcFailed, rigcIdentity, rigcRanClause, REPORTED_LINES, type ReportedLine, type RigcRunner, runCheck, SEAM_DIR, SEAM_MEAN_BAR, seamPairText, seamRigPxText, SOURCE_LINE, SEAM_PX_BAR, SEAM_PX_LEVEL, SEAM_PX_LEVEL_HIGH, SPINEBOY_YARDSTICK, STILL_DIR } from './check.ts';
|
|
73
|
+
import { besideIdleLine, 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 Limb, loadConfig, loadConfigAndAnimations, loadEarlyConfig } from './config.ts';
|
|
75
75
|
import { failLine, PartsError, type Problem, refuseIfAny } from './errors.ts';
|
|
76
76
|
import type { MeshQualityReport, MotionSchedule } from 'rig-c/mesh';
|
|
@@ -365,7 +365,7 @@ function gateRun(label: string, rigc: RigcRunner, args: string[]): GateRun {
|
|
|
365
365
|
* written anywhere, so packing them was work nobody read. No render is asked
|
|
366
366
|
* of rigc by either build.
|
|
367
367
|
*/
|
|
368
|
-
export const REFERENCE_BUILD_ARGS: readonly string[] = ['--profile',
|
|
368
|
+
export const REFERENCE_BUILD_ARGS: readonly string[] = ['--profile', GATE_PROFILE];
|
|
369
369
|
|
|
370
370
|
/** The reference build's label, as the rig stage prints it. */
|
|
371
371
|
export const REFERENCE_BUILD_LABEL = `build ${REFERENCE_BUILD_ARGS.join(' ')}`;
|
|
@@ -1135,6 +1135,22 @@ export function limbCheckLines(line: LimbLine): string[] {
|
|
|
1135
1135
|
];
|
|
1136
1136
|
}
|
|
1137
1137
|
|
|
1138
|
+
/**
|
|
1139
|
+
* The seam over the range (issue #360) as the console prints it: the status, what was posed and the bar, then each
|
|
1140
|
+
* joint at each angle on a line of its own — its status and the seam figures there — or, not read, why.
|
|
1141
|
+
*/
|
|
1142
|
+
export function sweepCheckLines(line: SweepLine): string[] {
|
|
1143
|
+
const s = line.frame_scale;
|
|
1144
|
+
return [
|
|
1145
|
+
`${SWEEP_LINE}: ${line.status} — ${line.joints.length} joint(s) bent to their declared range's ends and middle${line.physics_left_out === 0 ? '' : `, ${line.physics_left_out} physics constraint(s) left out`}; at each, the pose against the same pose with the joint's own art drawn under the limb, on the rest-pose seam's bar (mean |d| <= ${line.bar.mean.toFixed(1)}, <= ${line.bar.rig_px_over_40} rig px over ${line.bar.level})${s === null ? '' : ` at frame scale ${s}`}`,
|
|
1146
|
+
...line.joints.flatMap((j) => {
|
|
1147
|
+
const head = `joint "${j.joint}" (${j.limb}, "${j.part}" on "${j.bone}", range [${j.range.join(', ')}]${j.shape === 'disc' ? `, cap_shape "disc": no pixel over ${line.bar.level} in the disc` : ''})`;
|
|
1148
|
+
if (j.unread !== undefined) return [`${head}: FAIL — not read: ${j.unread}`];
|
|
1149
|
+
return j.poses.map((p) => `${head} at ${p.degrees}°: ${p.status} — mean |d| ${p.mean}/255, ${p.px_over_40} frame px over ${line.bar.level} (${p.px_over_80} over ${SEAM_PX_LEVEL_HIGH}), ${p.rig_px_over_40.toFixed(2)} rig px`);
|
|
1150
|
+
}),
|
|
1151
|
+
];
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1138
1154
|
/** 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. */
|
|
1139
1155
|
export function requirementText(name: string, line: RequirementLine): string {
|
|
1140
1156
|
const rest = Object.entries(line).filter(([k]) => k !== 'status' && k !== 'reason');
|
|
@@ -1181,6 +1197,8 @@ export function checkStage(input: CheckStageInput, rigc: RigcRunner, bin: string
|
|
|
1181
1197
|
if (stress !== undefined) log(` ${judgementLine(STRESS_LINE, stress)}`);
|
|
1182
1198
|
const limbs = fig[LIMB_LINE];
|
|
1183
1199
|
if (limbs !== undefined) for (const [i, l] of limbCheckLines(limbs).entries()) log(` ${i === 0 ? '' : ' '}${l}`);
|
|
1200
|
+
const sweep = fig[SWEEP_LINE];
|
|
1201
|
+
if (sweep !== undefined) for (const [i, l] of sweepCheckLines(sweep).entries()) log(` ${i === 0 ? '' : ' '}${l}`);
|
|
1184
1202
|
for (const name of REPORTED_LINES) log(` ${judgementLine(name, fig[name])}`);
|
|
1185
1203
|
const vsSource = fig[SOURCE_LINE];
|
|
1186
1204
|
if (vsSource !== undefined) log(` ${judgementLine(SOURCE_LINE, vsSource)}`);
|