@rig-c/parts 1.17.0 → 1.18.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 CHANGED
@@ -93,9 +93,9 @@ read by no CPU stage).
93
93
  | `key` | every stage's report | authored; the examples use the directory name |
94
94
  | `generation.*` | the optional `comfy` adapter only; no CPU stage | recorded: checkpoint, LoRAs, sampler, prompt parts, `latent`, `seed`, `control` |
95
95
  | `generation.control.skeleton` | `comfy paint` — the OpenPose control image, drawn at `latent` | **authored**: a built-in standing pose, `"stand_sides"` or `"stand_clasp"`, or (issue #198) any other string, read as a path relative to the config file's directory to a pose file `{"width": W, "height": H, "points": {"nose": [x, y], "neck": [x, y], …}}` — `width` x `height` is the canvas and must be `generation.latent` (the points are not scaled; `CONFIG_POSE_CANVAS`), and `points` holds the 18 body-18 keypoints of `src/skeleton.ts` by name (`nose`, `neck`, `r_shoulder`, `r_elbow`, `r_wrist`, `l_shoulder`, `l_elbow`, `l_wrist`, `r_hip`, `r_knee`, `r_ankle`, `l_hip`, `l_knee`, `l_ankle`, `r_eye`, `l_eye`, `r_ear`, `l_ear`; `r`/`l` the subject's sides), each `[x, y]` in canvas px, x right, y down, `0 <= x <= W`, `0 <= y <= H`. The image is drawn from the file exactly as from a built-in (same renderer, same size), written as `control_pose-<first 12 hex of its sha256>.png`. A pose file carries no words, so `generation.pose` is required beside it (`CONFIG_FIELD_PRESENT`). The file is read wherever the config is read from a path, every problem at once: no such file — a misspelt built-in lands here too — or one that cannot be read (`CONFIG_FILE_PRESENT`), not JSON (`CONFIG_IS_JSON`), not an object, a size that is not a whole positive px count, a point that is not two numbers (`CONFIG_FIELD_TYPE`), a key besides the three (`CONFIG_KEY_KNOWN`) or written twice (`CONFIG_KEY_UNIQUE`), `points` holding other than the 18 names (`CONFIG_POSE_KEYPOINTS`, with the count found and required), a point off the canvas (`CONFIG_POSE_IN_CANVAS`, naming the point and the canvas size) |
96
- | `seethrough.resolution` | assemble — each run's canvas must be `resolution` square (`ASSEMBLE_RUN_CANVAS`) | recorded from the See-through runs; 1024 in both examples |
96
+ | `seethrough.resolution` | assemble — each run's canvas must be `resolution` square (`ASSEMBLE_RUN_CANVAS`); with one run (no `--head`, issue #366) the file may be smaller on either side, and is padded to the `resolution` square, centred | recorded from the See-through runs; 1024 in both examples. With one run it is the square the file is read on: at least the file's longer side (`ASSEMBLE_RUN_CANVAS` otherwise), and nothing a part records depends on how much larger |
97
97
  | `seethrough.steps`, `.seed`, `.offload` | no CPU stage | recorded from the runs |
98
- | `seethrough.head_box` | inputs (cuts the head image there; optional at the first call); assemble — where the head run sits on the painting | **proposed** by `propose --head-box` from the full run, square, source pixels, shifted inside the painting when needed; the head run must then be fed exactly that crop |
98
+ | `seethrough.head_box` | inputs (cuts the head image there; optional at the first call); assemble — where the head run sits on the painting | **proposed** by `propose --head-box` from the full run, square, source pixels, shifted inside the painting when needed; the head run must then be fed exactly that crop. With one run (no `--head`, issue #359: an artist's own file, §4 *For artists*) it is left out — there is no crop to place — and a config that sets it is refused (`ASSEMBLE_SINGLE_RUN_HEAD_BOX`) |
99
99
  | `assemble.rig_scale` | assemble — the rig is `trunc(w × rig_scale)` x `trunc(h × rig_scale)`, and every rig-px value after it (`bones`, `meshes`, `motion`, `patches`, `cuts`) is in that rig's pixels; `check --source` (`CHECK_SOURCE_SIZE`) and `compose` (`SCENE_RIG_SCALE_AGREES`) read it back off `parts.json` | authored; rig pixels per source pixel. 0.5 in both examples (a 1664x2432 painting makes an 832x1216 rig). **Keep the figure about 1,100 to 1,200 rig px tall** — the `H` of `propose`'s `mesh spacing:` note, the height of the parts' boxes together — so for another painting, about 1,150 over the figure's height in painting px: that is the size the proposer's spacing divisor was calibrated at (H 1106–1185, the `mesh spacing` note says so) and the size of the two examples `check`'s bars are derived from, and the proposer and `check` also hold fixed rig-px values that do not scale with it (the fringe's 10 px step, the trunk segments' 18, 20, 30 and 100 px, the 15 px wrist tolerance, the 10-row pendant floor, the seam bar's 50 rig px over 40). What else it trades, measured on `demo` with the README loop from a fresh config at 0.25, 0.5 and 1 (issue #345, rig-c 2.47.1): the same plan and the same ten chains; every bar but the one the uncorrected `demo` always fails (`bottomwear` `TIP_OVER_ROOT`, the README loop's `RL01`) passing at all three; the atlas page 418x606, 832x1205 and 1825x2047; the figure's mesh spacing 16, 32 and 64, so 830, 868 and 932 triangles; the breath 2.5 % of a torso that scales with it; and the proposed bones, scaled to 0.5, off the 0.5 proposal's by up to 38 px at 0.25 and 22.5 px at 1 (the hairpin's origin; the skirt roots 20 and 10.5) — the fixed-px values above. Changing it after the rig sections are written is not refused: `demo`'s tracked config built at 0.25 gated green and `check` failed only `TIP_OVER_ROOT` on both sleeves; `propose --from-config` names it (54 `LINT` lines, links off the art) — propose again at the new scale. Absent: `CONFIG_FIELD_PRESENT`; 0, negative or not a number: `CONFIG_FIELD_TYPE`; a rig under 1x1: `ASSEMBLE_RIG_SIZE` |
100
100
  | `assemble.plan` | assemble; also the list every `meshes`/`regions` key must come from | **proposed** by `assemble --propose-plan`. `[part name, "full" \| "head", See-through tag]`, in draw order back to front. Names are free; roles come from the tag. A layer the author made and added to a run takes `author:<label>` in the tag's place, and has no role (§5, *A layer the author made*) |
101
101
  | `assemble.extend_below_crop` | assemble | **proposed** with the plan: `{part, run, tag}` — a head-run part that reaches the bottom of the head crop is continued from a full-run layer, whole connected components |
@@ -1724,6 +1724,136 @@ The selftest runs this order, with the README's flags, on each fetched example f
1724
1724
  a config holding only `key`, `seethrough` and `assemble.rig_scale`, pasting each
1725
1725
  proposal in as the steps above say (`RL01`); every step must exit 0.
1726
1726
 
1727
+ ### For artists: your own layered file (issue #359)
1728
+
1729
+ An artist's layered file is an input in its own right: you paint the figure in
1730
+ layers, the tool reads them, and an agent does the data work. Nothing below asks
1731
+ for See-through.
1732
+
1733
+ **What to paint.** One pixel layer per part, named by its tag. `rig-parts layers
1734
+ --template art.psd --canvas <side>x<side>` writes a PSD holding one empty layer per
1735
+ name the rig reads, bottom first: `back hair`, `neck`, `neckwear`, `topwear`,
1736
+ `handwear-r`, `handwear-l`, `bottomwear`, `legwear`, `footwear`, `tail`, `wings`,
1737
+ `objects`, `headwear`, `face`, `irides-r`, `irides-l`, `eyebrow-r`, `eyebrow-l`,
1738
+ `eyewhite-r`, `eyewhite-l`, `eyelash-r`, `eyelash-l`, `eyewear`, `ears-r`, `ears-l`,
1739
+ `earwear`, `nose`, `mouth`, `front hair` — 29 names: the 24 See-through tags
1740
+ (`src/tags.ts`) less `head`, which upstream never emits and no stage reads, with the
1741
+ six split tags as their halves, `-r` being the character's right (the smaller x).
1742
+ The order is a starting stack, not a rule. Paint the layers the figure has and
1743
+ delete the rest: an empty layer is refused (`PSD_LAYER_HAS_PIXELS`). A layer you
1744
+ need that no tag fits is `author:<label>` (§5, *A layer the author made*).
1745
+
1746
+ - **The face, separated.** `face`, `eyewhite-r` and `eyewhite-l`, each painted, are
1747
+ what lets one file stand for See-through's two runs: the face box comes from
1748
+ `face`, the eye bones and the blink from the eyewhites. Without them the run is
1749
+ refused (`ASSEMBLE_SINGLE_RUN_FACE`, one line per tag missing).
1750
+ - **The stacking order is the draw order**, bottom first. A PSD carries no depth,
1751
+ and none is invented: `layers` prints one `SKIP depth:` line naming the file,
1752
+ and the turn draft (`propose --full`) skips it (`turn: SKIP`).
1753
+ - **Flatten each layer.** Normal blending, 100 % opacity, no clipping layer, no
1754
+ layer mask, 8-bit RGB: each is refused by name (`PSD_LAYER_PLAIN` naming the
1755
+ layer and what it carries, `PSD_IS_RGB8`), because the composite it would make
1756
+ is not one the reader can see. Groups are read through (pass-through or normal
1757
+ blending only). A switched-off layer is refused too, except a hidden-art layer.
1758
+ - **Small pieces.** A layer's pieces under 40 px, and under 1 % of its largest
1759
+ piece, are removed as See-through's specks (`GHOST_MIN_AREA`, `assemble`'s
1760
+ `ghost px removed:` line counts them), and `--propose-plan` leaves out a layer
1761
+ under 150 px (a note names it); both are in run pixels, so paint at the size
1762
+ the rig needs.
1763
+ - **The canvas.** One run is one canvas, of any shape no side of which is above
1764
+ `seethrough.resolution` (`ASSEMBLE_RUN_CANVAS` otherwise: padding cannot shrink a
1765
+ file, so set `resolution` to at least the longer side). The painting — `--source`,
1766
+ the file flattened and saved as an opaque PNG — is the same size
1767
+ (`ASSEMBLE_SINGLE_RUN_SOURCE` otherwise). A file that is not the `resolution`
1768
+ square is padded to it before it is read, the way `inputs` pads a painting for
1769
+ See-through (issue #366): centred, each margin `floor((resolution - side) / 2)`,
1770
+ the odd pixel to the right or the bottom, the pad transparent. The rig holds no
1771
+ pad — it is the file times `rig_scale`, so a part's box over `rig_scale` is its
1772
+ box in the file — and `parts.json` records the pad in `single_run_pad` (§5); a
1773
+ `padded:` line under `one run:` prints the four margins. A 75 x 128 file at
1774
+ resolution 128 is padded left 26, right 27; a square file at the resolution is
1775
+ not padded, and its `parts.json` has no such block.
1776
+ - **Hidden art.** A layer named `<layer>:hidden` — `handwear-r:hidden`, the forearm
1777
+ under the sleeve; `topwear:hidden`, the shirt under a scarf's tail — is that
1778
+ layer's art where something covers it. It is never drawn and never a part, and
1779
+ it may be switched off in the file. `assemble` reads it as the `under` of a cut
1780
+ of that layer's part (§3, `assemble.cuts`) and as the cap of a limb segment that
1781
+ takes that part (§3, *Limbs*), wherever the config names no file for them; a
1782
+ config that names one as well is refused (`ASSEMBLE_HIDDEN_ONE_SOURCE`), since
1783
+ reading either would be a guess. A cut of a part with hidden art is a cut with an
1784
+ `under`, so its piece is drawn in front of the part (`ASSEMBLE_UNDER_DRAW`
1785
+ otherwise), and a cut the hidden art holds nothing under fills 0 and says so. One `hidden art:` line per such layer names
1786
+ what read it, or says nothing did. Its name is the tag plus a suffix, so it
1787
+ needs no config field to say whose it is; a `:hidden` layer whose layer the file
1788
+ does not hold is refused (`LAYERS_HIDDEN_OF_PRESENT`).
1789
+
1790
+ **The commands.** The config holds no `seethrough.head_box`. Leave `--head` out
1791
+ everywhere:
1792
+
1793
+ ```sh
1794
+ rig-parts layers art.psd
1795
+ rig-parts assemble --propose-plan --source painting.png --full art.psd --config config.json
1796
+ rig-parts assemble --source painting.png --full art.psd --config config.json --out .
1797
+ rig-parts propose --parts rig --source painting.png --out .
1798
+ rig-parts build --config config.json --source painting.png --full art.psd --out build
1799
+ ```
1800
+
1801
+ `--propose-plan` writes every row from the full run, and its first note says the
1802
+ file was read as both runs; the assemble stage refuses a row that takes `"head"`
1803
+ or an `extend_below_crop` entry (`ASSEMBLE_SINGLE_RUN_PLAN`), and prints `one run:`
1804
+ under its first line.
1805
+
1806
+ **What you get back.** `parts/<name>.png` and `parts.json` (where every part came
1807
+ from and what it measured), `rig.json` and `motion.json`, and from `build` the packed
1808
+ atlas — `check/build/skeleton.json`, `.atlas` and `.png` — that a Spine runtime
1809
+ loads, with `check/contact.png` and the idle frames to look at.
1810
+
1811
+ ### The correction loop (issue #359)
1812
+
1813
+ A parse can be wrong: a patch painted into the wrong layer, two layers that should
1814
+ be one, a split along a painted edge. An agent cannot edit a PSD, so the loop runs
1815
+ on the wrapper form instead, which any image library edits:
1816
+
1817
+ 1. **Export.** `rig-parts layers --export work art.psd` writes `work/layers.json`
1818
+ and one PNG per layer (`<index>_<name>.png`), drawn layers in draw order, then
1819
+ the hidden-art layers; `depth_median` is `null` throughout, which the reader
1820
+ takes as "the manifest's order is the draw order". The export is read back at
1821
+ once and must reproduce the file's reading — layers, boxes, pixels, order — or
1822
+ it is refused (`LAYERS_EXPORT_ROUND_TRIP`).
1823
+ 2. **Read.** `rig-parts layers work` (the table and its `WARN` lines),
1824
+ `assemble --full work …` (the part lines, `recomposite vs source`, the
1825
+ `uncovered hole` and `uncovered:` lines) and `render/recomposite_error_rig.png`
1826
+ (uncovered error red, covered error blue). A pixel in the wrong layer shows as
1827
+ error where that layer is drawn behind another part and the painting is
1828
+ near-white there (the seam override leaves near-white alone), or as `drift=`
1829
+ and `seam=` counts on the part drawn over it when it is not: projection and the
1830
+ seam override repaint that part to the painting's colour, so the error map
1831
+ stays clean while the part carries the wrong art.
1832
+ 3. **Fix** the PNGs and `layers.json`: move the pixels, and when a layer's art
1833
+ leaves its box, grow the PNG and the box together (`LAYERS_PNG_MATCHES_BBOX`
1834
+ otherwise).
1835
+ 4. **Re-run** from step 2 on `work`.
1836
+
1837
+ **A worked example** (the selftest's, `fixtures/artist.ts`): a 128-px file whose
1838
+ right eyewhite was painted half into `back hair` — the 8 x 10 patch at
1839
+ `[52, 22, 60, 32]`, near-white, behind the face. Assembled from the export:
1840
+
1841
+ ```
1842
+ recomposite vs source: mean |d|=0.40, within 8: 99.5%, error px > 40: 80, uncovered error px: 0
1843
+ ```
1844
+
1845
+ 80 covered error pixels, blue on the error map, in one 8 x 10 block; the face's
1846
+ part line says `drift=60`, the block's pixels inside the face's projection core
1847
+ whose colour is more than 90 off the painting's. The fix:
1848
+ in `work/00_back_hair.png` clear the block (it is `back hair`'s pixels
1849
+ `(20..27, 18..27)`, box `[32, 4, 96, 48]`); widen `work/03_eyewhite-r.png` from
1850
+ 8 x 10 to 16 x 10, painting the block into its right half; and set that layer's
1851
+ `right` from 52 to 60 in `layers.json`. Re-run: `error px > 40: 0`, the clean
1852
+ file's figure, and every part is the clean file's but `hair_back`, which keeps an
1853
+ 80-px hole behind the face where the hair under the eye was never painted — every
1854
+ one of them occluded, so nothing shows it at rest; paint it there if the face will
1855
+ move off it (`AS245`).
1856
+
1727
1857
  ## 5. After each stage: what to read
1728
1858
 
1729
1859
  | stage | read | green looks like | a common red |
@@ -2083,6 +2213,21 @@ note saying so — give it a mesh in `meshes` if it should bend. The one excepti
2083
2213
  sleeve rule, which names its chains by side and takes a cut sleeve as a blob of its own
2084
2214
  (the `one handwear part holds both wrists` note, §3).
2085
2215
 
2216
+ With one run whose file is not the `seethrough.resolution` square (issue #366, §4
2217
+ *For artists*), `parts.json` ends with a `single_run_pad` block: where the file sat on
2218
+ the square it was read on. Two runs and a file that is the square write none.
2219
+
2220
+ | field | holds |
2221
+ | --- | --- |
2222
+ | `file_size` | the file's `[w, h]` — the painting's too |
2223
+ | `side` | the square's side, `seethrough.resolution` |
2224
+ | `left`, `top` | the margins, `floor((side - w) / 2)` and `floor((side - h) / 2)`; the right and the bottom take the remainder |
2225
+
2226
+ Run pixel `(x, y)` is file pixel `(x - left, y - top)`, and a part's box over
2227
+ `scale_rig_per_source` is its box in the file. The reader refuses margins that are not
2228
+ that centring, a file larger than its square or equal to it, and a `rig_size` that is
2229
+ not `file_size` times the scale, truncated (`PARTS_SINGLE_RUN_PAD`).
2230
+
2086
2231
  `visible_px + occluded_px = opaque_px`, and every projected pixel is visible; the stage
2087
2232
  refuses (`ASSEMBLE_COUNTS_ADD_UP`) rather than write counts that break either, and the
2088
2233
  reader refuses a record that breaks them (`PARTS_COUNTS_ADD_UP`). The three visibility
@@ -2134,7 +2279,11 @@ row in this page's `rule` and `line` tables; the selftest holds that (`TY16`).
2134
2279
  | <a id="LAYERS_MANIFEST_PRESENT"></a><a id="LAYERS_MANIFEST_IS_JSON"></a><a id="LAYERS_FIELD_PRESENT"></a><a id="LAYERS_KEY_KNOWN"></a>`LAYERS_MANIFEST_PRESENT`, `LAYERS_MANIFEST_IS_JSON`, `LAYERS_FIELD_PRESENT`, `LAYERS_KEY_KNOWN` | the wrapper's `layers.json` is missing, unparsable, or not the wrapper's shape | the run's output directory; re-export it |
2135
2280
  | <a id="LAYERS_PNG_PRESENT"></a><a id="LAYERS_PNG_UNAMBIGUOUS"></a><a id="LAYERS_PNG_DECODES"></a><a id="LAYERS_PNG_MATCHES_BBOX"></a>`LAYERS_PNG_PRESENT`, `LAYERS_PNG_UNAMBIGUOUS`, `LAYERS_PNG_DECODES`, `LAYERS_PNG_MATCHES_BBOX` | a layer's PNG is missing, found in two places, not a PNG, or not its box's size | the run's files |
2136
2281
  | <a id="LAYERS_TAG_KNOWN"></a><a id="LAYERS_NAME_UNIQUE"></a><a id="LAYERS_BBOX_INSIDE_CANVAS"></a>`LAYERS_TAG_KNOWN`, `LAYERS_NAME_UNIQUE`, `LAYERS_BBOX_INSIDE_CANVAS` | a layer name that is neither a See-through v3 tag nor `author:<label>` (or is `author:` with a label that is empty, starts with `.` or whitespace, ends with whitespace, or holds a slash, backslash or colon — the detail says which), a repeated name, a box off the canvas | the run (these are See-through's own outputs); a layer you added by hand is named `author:<label>` (§5, *A layer the author made*) |
2137
- | <a id="PSD_FILE_PRESENT"></a><a id="PSD_PARSES"></a><a id="PSD_IS_RGB8"></a><a id="PSD_HAS_LAYERS"></a><a id="PSD_LAYER_PLAIN"></a><a id="PSD_LAYER_HAS_PIXELS"></a>`PSD_FILE_PRESENT`, `PSD_PARSES`, `PSD_IS_RGB8`, `PSD_HAS_LAYERS`, `PSD_LAYER_PLAIN`, `PSD_LAYER_HAS_PIXELS` | the `.psd` is missing, unreadable, not 8-bit RGB, has no layer, or has a layer that is hidden, not at full opacity, or without pixels | the `.psd` |
2282
+ | <a id="PSD_FILE_PRESENT"></a><a id="PSD_PARSES"></a><a id="PSD_IS_RGB8"></a><a id="PSD_HAS_LAYERS"></a><a id="PSD_LAYER_PLAIN"></a><a id="PSD_LAYER_HAS_PIXELS"></a>`PSD_FILE_PRESENT`, `PSD_PARSES`, `PSD_IS_RGB8`, `PSD_HAS_LAYERS`, `PSD_LAYER_PLAIN`, `PSD_LAYER_HAS_PIXELS` | the `.psd` is missing, unreadable, not 8-bit RGB, has no layer, or has a layer that is hidden (a hidden-art layer, `<layer>:hidden`, may be), not at full opacity, blended other than normal, clipped, masked, or without pixels | the `.psd`; an artist's file: §4, *For artists* |
2283
+ | <a id="LAYERS_DEPTH_ALL_OR_NONE"></a>`LAYERS_DEPTH_ALL_OR_NONE` | some drawn layers of a wrapper `layers.json` carry a `depth_median` and others carry `null` (issue #359): a mix has no one draw order | a number on every drawn layer (a See-through run), or `null` on every one (an export of a PSD: the manifest's order is then the draw order) |
2284
+ | <a id="LAYERS_HIDDEN_OF_PRESENT"></a><a id="LAYERS_HIDDEN_NO_DEPTH"></a>`LAYERS_HIDDEN_OF_PRESENT`, `LAYERS_HIDDEN_NO_DEPTH` | a hidden-art layer, `<layer>:hidden` (issue #359), belongs to a drawn layer the input does not hold; or, in a wrapper `layers.json`, carries a `depth_median` other than `null` (it is never drawn) | the layer's name, or the layer it names; `depth_median: null` |
2285
+ | <a id="LAYERS_EXPORT_DIR_EMPTY"></a>`LAYERS_EXPORT_DIR_EMPTY` | `layers --export <dir>` was given a file, or a directory that is not empty (issue #359): a file left there could be read in place of the one written | a new directory, or empty it |
2286
+ | <a id="LAYERS_EXPORT_ROUND_TRIP"></a>`LAYERS_EXPORT_ROUND_TRIP` | the export, read back, differs from the input's reading (the detail names the first difference) — an exporter bug, not an input problem | report it with the input named |
2138
2287
  | <a id="ASSEMBLE_SOURCE_PRESENT"></a><a id="ASSEMBLE_SOURCE_DECODES"></a>`ASSEMBLE_SOURCE_PRESENT`, `ASSEMBLE_SOURCE_DECODES` | the painting is missing or not a PNG | `--source` |
2139
2288
  | <a id="SHEET_SOURCE_PRESENT"></a><a id="SHEET_PNG_PRESENT"></a>`SHEET_SOURCE_PRESENT`, `SHEET_PNG_PRESENT` | `sheet`'s painting, or a part PNG a `parts.json` lists, is missing | `--source`, `--layers` |
2140
2289
 
@@ -2230,6 +2379,24 @@ See-through with another seed.
2230
2379
  | <a id="INPUTS_PAINTING_OPAQUE"></a>`INPUTS_PAINTING_OPAQUE` | the painting has a pixel below alpha 255 | the painting |
2231
2380
  | <a id="INPUTS_HEAD_BOX_INSIDE"></a>`INPUTS_HEAD_BOX_INSIDE` | `seethrough.head_box` leaves the painting (the crop is not padded) | `head_box` — take `propose --head-box`'s |
2232
2381
 
2382
+ ### painting
2383
+
2384
+ `rig-parts painting --keypoints <k.json> <painting.png>` (issue #350) reads the painting
2385
+ before See-through runs on it. *Background* is ground (alpha 8 or less, or every channel
2386
+ above 235) connected to the painting's edge; the *figure* is the rest; a *gap* is one
2387
+ background pixel on the straight line between two joints. [PAINTING.md](PAINTING.md)
2388
+ says why each requirement exists and what breaks without it.
2389
+
2390
+ | rule | means | change |
2391
+ | --- | --- | --- |
2392
+ | <a id="PAINTING_SOURCE_PRESENT"></a><a id="PAINTING_SOURCE_DECODES"></a>`PAINTING_SOURCE_PRESENT`, `PAINTING_SOURCE_DECODES` | the painting is missing or not a PNG | the path |
2393
+ | <a id="PAINTING_BACKGROUND_PLAIN"></a>`PAINTING_BACKGROUND_PLAIN` | a border pixel is neither near-white nor unpainted: scenery, a floor, a gradient or a vignette reaches the edge, or the figure is cut by the frame; the detail counts them and names the first | a painting on a plain light background with the whole figure inside the frame (PAINTING.md, *Background*) |
2394
+ | <a id="PAINTING_JOINTS_STATED"></a>`PAINTING_JOINTS_STATED` | a wrist, hip, knee or ankle is `occluded` or `missing` in the keypoint file: the gap rules read those eight joints, and a hand or foot the painting does not show is a limb nothing can rig | a painting that shows it; or the joint, if the estimator missed one the painting shows |
2395
+ | <a id="PAINTING_JOINT_ON_FIGURE"></a>`PAINTING_JOINT_ON_FIGURE` | an observed joint's pixel is background | the keypoint file (measured on this painting?), or the painting |
2396
+ | <a id="PAINTING_ARM_CLEAR"></a>`PAINTING_ARM_CLEAR` | the line from a wrist to the hip of its side crosses no background: the arm is painted against the body. Hands that meet close a loop round the torso, so this fires with `PAINTING_HANDS_APART` on both sides | a painting with the arm held away from the body (PAINTING.md, *Arms*) |
2397
+ | <a id="PAINTING_HANDS_APART"></a>`PAINTING_HANDS_APART` | the line between the two wrists crosses no background: hands clasped or touching | a painting with the hands apart and open (PAINTING.md, *Hands*) |
2398
+ | <a id="PAINTING_LEGS_APART"></a>`PAINTING_LEGS_APART` | the line between the knees, or between the ankles, crosses no background: the legs are one column there (a long skirt over the knees is refused here too) | a painting with the legs apart and a visible gap at the knees and the ankles (PAINTING.md, *Legs*) |
2399
+
2233
2400
  ### The optional ComfyUI adapter (`comfy paint`, `comfy seethrough`)
2234
2401
 
2235
2402
  | rule | means | change |
@@ -2249,7 +2416,11 @@ See-through with another seed.
2249
2416
  | <a id="ASSEMBLE_FIELD_PRESENT"></a>`ASSEMBLE_FIELD_PRESENT` | `seethrough.head_box` is missing | `propose --head-box` proposes it (§3) |
2250
2417
  | <a id="ASSEMBLE_RIG_SIZE"></a>`ASSEMBLE_RIG_SIZE` | `rig_scale` makes an empty rig | `assemble.rig_scale` |
2251
2418
  | <a id="ASSEMBLE_HEAD_BOX_INSIDE"></a>`ASSEMBLE_HEAD_BOX_INSIDE` | the head box is outside the painting | `head_box` — `propose --head-box` holds it inside |
2252
- | <a id="ASSEMBLE_RUN_CANVAS"></a>`ASSEMBLE_RUN_CANVAS` | a run's canvas is not `resolution` square | `seethrough.resolution`, or the run |
2419
+ | <a id="ASSEMBLE_RUN_CANVAS"></a>`ASSEMBLE_RUN_CANVAS` | a run's canvas is not `resolution` square; with one run (issue #366), a side of the file is above `resolution` — a smaller one is padded, a larger one would have to be shrunk | `seethrough.resolution`, or the run |
2420
+ | <a id="ASSEMBLE_SINGLE_RUN_SOURCE"></a>`ASSEMBLE_SINGLE_RUN_SOURCE` | with no `--head` (issue #366), the painting `--source` names is not the one run's file's size: the painting is the file flattened, and every part's box maps back to the file through it | the painting — the file flattened, at the file's size |
2421
+ | <a id="ASSEMBLE_SINGLE_RUN_FACE"></a>`ASSEMBLE_SINGLE_RUN_FACE` | with no `--head` (issue #359), the one run holds no painted layer for one of `face`, `eyewhite-r`, `eyewhite-l` — the parts the head run exists to separate (one problem per tag) | paint the layer, or give the head run with `--head` |
2422
+ | <a id="ASSEMBLE_SINGLE_RUN_PLAN"></a>`ASSEMBLE_SINGLE_RUN_PLAN` | with no `--head`, a plan row takes `"head"`, or an `extend_below_crop` entry continues a part below a crop there is not | `"full"` in the row; remove the entry |
2423
+ | <a id="ASSEMBLE_SINGLE_RUN_HEAD_BOX"></a>`ASSEMBLE_SINGLE_RUN_HEAD_BOX` | with no `--head`, the config sets `seethrough.head_box`, which places a head run there is not | remove `head_box`, or give the head run with `--head` |
2253
2424
  | <a id="ASSEMBLE_PLAN_TAG_IN_RUN"></a><a id="ASSEMBLE_EXTEND_TAG_IN_RUN"></a>`ASSEMBLE_PLAN_TAG_IN_RUN`, `ASSEMBLE_EXTEND_TAG_IN_RUN` | an entry takes a tag its run does not hold (the detail lists what it does hold) | that entry's run or tag |
2254
2425
  | <a id="ASSEMBLE_PART_OPAQUE"></a>`ASSEMBLE_PART_OPAQUE` | a part ended with no opaque pixel | drop the entry, or take the tag from the other run |
2255
2426
  | <a id="ASSEMBLE_PATCH_BOX_INSIDE"></a>`ASSEMBLE_PATCH_BOX_INSIDE` | a patch's box reaches past the rig (the painting times `rig_scale`; the detail gives the size) | that patch's `box` — it is in rig pixels, not painting pixels |
@@ -2264,6 +2435,7 @@ See-through with another seed.
2264
2435
  | <a id="ASSEMBLE_LIMB_TAKES"></a><a id="ASSEMBLE_LIMB_PIXELS"></a>`ASSEMBLE_LIMB_TAKES`, `ASSEMBLE_LIMB_PIXELS` | a part a limb takes has no opaque pixel, or a limb segment gets none of it — the joint lines through the bones' origins put none of the limb's pixels on it, or another limb's polyline is nearer every one (issue #349) | the bones: their origins on the limb's art, each joint where the limb bends |
2265
2436
  | <a id="ASSEMBLE_LIMB_JOINT"></a><a id="ASSEMBLE_LIMB_JOINT_ON_ART"></a>`ASSEMBLE_LIMB_JOINT`, `ASSEMBLE_LIMB_JOINT_ON_ART` | a joint has no line — the upper bone has no length, or the limb folds straight back on itself — or its bone's origin lies on none of the limb's art within 1 px along the line, so the limb has no width there to size the cap by (issue #349) | the joint's bone: on the limb's art, at the joint |
2266
2437
  | <a id="ASSEMBLE_LIMB_CAP_PRESENT"></a><a id="ASSEMBLE_LIMB_CAP_DECODES"></a><a id="ASSEMBLE_LIMB_CAP_SIZE"></a><a id="ASSEMBLE_LIMB_CAP_EMPTY"></a>`ASSEMBLE_LIMB_CAP_PRESENT`, `ASSEMBLE_LIMB_CAP_DECODES`, `ASSEMBLE_LIMB_CAP_SIZE`, `ASSEMBLE_LIMB_CAP_EMPTY` | a segment's `cap` names no file (relative to the config's directory) or one that is not a PNG; an image not the size of the cap's box (the detail gives both, and the box's corner); or one transparent over every cap pixel (issue #349) | the image: the box the `limb cap:` line prints, `w`x`h`, its pixel `(i, j)` rig pixel `(x + i, y + j)`; to keep the layer's cap, drop `cap` |
2438
+ | <a id="ASSEMBLE_HIDDEN_ONE_SOURCE"></a>`ASSEMBLE_HIDDEN_ONE_SOURCE` | a cut's `under`, or a limb segment's `cap`, names a file, and the part it reads also has a hidden-art layer (`<layer>:hidden`, issue #359): reading either would be a guess | remove the file from the config, or the hidden-art layer from the input |
2267
2439
  | <a id="ASSEMBLE_COUNTS_ADD_UP"></a>`ASSEMBLE_COUNTS_ADD_UP` | a part's visible and occluded counts do not add up to its opaque pixels, or a projected pixel is not visible, or a cut part's pieces do not add up to it — an assembler bug, not an input problem | report it with the part named; nothing was written |
2268
2440
 
2269
2441
  ### propose
@@ -2276,14 +2448,14 @@ See-through with another seed.
2276
2448
  | <a id="PROPOSE_CUT_FROM_RECORDED"></a>`PROPOSE_CUT_FROM_RECORDED` | two parts share one layer and none of them records `cut_from`: a `parts.json` an `assemble` before 1.6.0 wrote with `assemble.cuts`. The records are in draw order, so which is the plan part and which the piece cannot be read off the file; the message names the parts and the layer. Every other reader still takes the file | `--parts`: re-run assemble (1.6.0+) to record `cut_from` |
2277
2449
  | <a id="PROPOSE_LIMB_PARTS"></a>`PROPOSE_LIMB_PARTS` | `parts.json` holds parts `config.limbs` cut at their joints (issue #349); the limbs are drafted from the parts before the cut, which the rules read by layer | `--parts` a build assembled with the config's `limbs` left out; keep the block the config holds (its caps and ranges are the author's). `propose --from-config` reads the config's bones on either build |
2278
2450
  | <a id="PROPOSE_ACCESSORY_BODY"></a>`PROPOSE_ACCESSORY_BODY` | an accessory has nothing above its pendant rows to hang its bone on | that part's plan entry, or author its bones by hand |
2279
- | <a id="PROPOSE_TURN_RUN_GIVEN"></a><a id="PROPOSE_TURN_LAYER_PRESENT"></a><a id="PROPOSE_TURN_DEPTH_PRESENT"></a>`PROPOSE_TURN_RUN_GIVEN`, `PROPOSE_TURN_LAYER_PRESENT`, `PROPOSE_TURN_DEPTH_PRESENT` | with `--full`/`--head` (issue #251): a part came from a run whose layers were not given, the run given holds no layer of the part's tag, or its layer carries no `depth_median` (a PSD carries none) | give the run the parts were assembled from, as the wrapper's `layers.json` |
2451
+ | <a id="PROPOSE_TURN_RUN_GIVEN"></a><a id="PROPOSE_TURN_LAYER_PRESENT"></a><a id="PROPOSE_TURN_DEPTH_PRESENT"></a>`PROPOSE_TURN_RUN_GIVEN`, `PROPOSE_TURN_LAYER_PRESENT`, `PROPOSE_TURN_DEPTH_PRESENT` | with `--full`/`--head` (issue #251): a part came from a run whose layers were not given, the run given holds no layer of the part's tag, or its layer carries no `depth_median` while another part's layer carries one; a run with no depth on any part's layer (a PSD, or an export of one) is not refused — the turn is skipped, and the note says `turn: SKIP` and names the run (issue #359) | give the run the parts were assembled from, as the wrapper's `layers.json` |
2280
2452
  | <a id="BASIS_RECORDS_EVERY_BONE"></a><a id="BASIS_NAMES_RESOLVE"></a><a id="BASIS_KIND_KNOWN"></a><a id="BASIS_KIND_HOLDS"></a>`BASIS_RECORDS_EVERY_BONE`, `BASIS_NAMES_RESOLVE`, `BASIS_KIND_KNOWN`, `BASIS_KIND_HOLDS` | `propose --keypoints`'s record of how it placed each bone, `basis.json`, does not hold to the proposal it was written with: a proposed bone with no record, one recorded twice or out of the proposal's order; a part, bone or frame quantity it names that is not there; a placement kind outside the known ones; or a placement that does not hold what its kind says (a joint placement with no joint, a non-joint one with a joint, a derived one with no bone, a constant that is not finite). Nothing is written | a proposer bug, not an input problem: report it with the line |
2281
2453
  | <a id="KEYPOINTS_FILE_PRESENT"></a><a id="KEYPOINTS_IS_JSON"></a><a id="KEYPOINTS_KEY_KNOWN"></a><a id="KEYPOINTS_FIELD_PRESENT"></a><a id="KEYPOINTS_FIELD_TYPE"></a>`KEYPOINTS_FILE_PRESENT`, `KEYPOINTS_IS_JSON`, `KEYPOINTS_KEY_KNOWN`, `KEYPOINTS_FIELD_PRESENT`, `KEYPOINTS_FIELD_TYPE` | the `--keypoints` file is absent, not JSON, has a key this reader does not know, lacks one of `spec`, `space`, `width`, `height`, `source`, `people`, or holds a value of the wrong type (a size that is not a whole positive px count, an empty `source`, no people) | the keypoint file (§3, *A posed figure*) |
2282
2454
  | <a id="KEYPOINTS_SPEC_KNOWN"></a><a id="KEYPOINTS_SPACE_STATED"></a>`KEYPOINTS_SPEC_KNOWN`, `KEYPOINTS_SPACE_STATED` | the file is not `spine-parts-keypoints/1`, or states a space other than painting-px, origin top-left, y down | convert the file to that space; it is not read as if it were in it |
2283
2455
  | <a id="KEYPOINTS_JOINT_KNOWN"></a><a id="KEYPOINTS_JOINT_STATE"></a><a id="KEYPOINTS_POSITION_STATED"></a><a id="KEYPOINTS_POSITION_INSIDE"></a>`KEYPOINTS_JOINT_KNOWN`, `KEYPOINTS_JOINT_STATE`, `KEYPOINTS_POSITION_STATED`, `KEYPOINTS_POSITION_INSIDE` | a joint name outside body-18, a state outside observed / occluded / missing, an observed joint with no `at` or a missing one with one, or a position outside the `width`x`height` image | that joint |
2284
2456
  | <a id="KEYPOINTS_PERSON_UNIQUE"></a><a id="KEYPOINTS_PERSON_CHOSEN"></a>`KEYPOINTS_PERSON_UNIQUE`, `KEYPOINTS_PERSON_CHOSEN` | two people share an id; or the file holds more than one person and no `--person` names one, or `--person` names none of them (the message lists the ids) | `--person <id>` |
2285
2457
  | <a id="KEYPOINTS_IMAGE_SIZE"></a>`KEYPOINTS_IMAGE_SIZE` | the file's `width`x`height` is not the `--source` painting's | a file measured on this painting |
2286
- | <a id="PARTS_FILE_PRESENT"></a><a id="PARTS_IS_JSON"></a><a id="PARTS_KEY_KNOWN"></a><a id="PARTS_FIELD_PRESENT"></a><a id="PARTS_FIELD_TYPE"></a><a id="PARTS_NAME_UNIQUE"></a><a id="PARTS_FROM_KNOWN"></a><a id="PARTS_CUT_FROM_KNOWN"></a><a id="PARTS_BOX_INSIDE_RIG"></a><a id="PARTS_COUNTS_ADD_UP"></a><a id="PARTS_HOLES_LARGEST_FIRST"></a><a id="PARTS_HOLE_PART_KNOWN"></a>`PARTS_*` (`PARTS_FILE_PRESENT`, `PARTS_IS_JSON`, `PARTS_KEY_KNOWN`, `PARTS_FIELD_PRESENT`, `PARTS_FIELD_TYPE`, `PARTS_NAME_UNIQUE`, `PARTS_FROM_KNOWN`, `PARTS_CUT_FROM_KNOWN`, `PARTS_BOX_INSIDE_RIG`, `PARTS_COUNTS_ADD_UP`, `PARTS_HOLES_LARGEST_FIRST`, `PARTS_HOLE_PART_KNOWN`) | the `parts.json` read is not assemble's contract (`PARTS_COUNTS_ADD_UP`: `visible_px + occluded_px` is not `opaque_px`, `visible_not_projected_px` is above `visible_px`, or the `recomposite` block's figures disagree — more uncovered than error pixels, a hole larger than its box, the wrong number of holes listed, listed areas that do not sum to `uncovered_error_px`; the last two codes are the block's order and its border names, §5) | re-run assemble; do not edit `parts.json` |
2458
+ | <a id="PARTS_FILE_PRESENT"></a><a id="PARTS_IS_JSON"></a><a id="PARTS_KEY_KNOWN"></a><a id="PARTS_FIELD_PRESENT"></a><a id="PARTS_FIELD_TYPE"></a><a id="PARTS_NAME_UNIQUE"></a><a id="PARTS_FROM_KNOWN"></a><a id="PARTS_CUT_FROM_KNOWN"></a><a id="PARTS_BOX_INSIDE_RIG"></a><a id="PARTS_COUNTS_ADD_UP"></a><a id="PARTS_HOLES_LARGEST_FIRST"></a><a id="PARTS_HOLE_PART_KNOWN"></a><a id="PARTS_SINGLE_RUN_PAD"></a>`PARTS_*` (`PARTS_FILE_PRESENT`, `PARTS_IS_JSON`, `PARTS_KEY_KNOWN`, `PARTS_FIELD_PRESENT`, `PARTS_FIELD_TYPE`, `PARTS_NAME_UNIQUE`, `PARTS_FROM_KNOWN`, `PARTS_CUT_FROM_KNOWN`, `PARTS_BOX_INSIDE_RIG`, `PARTS_COUNTS_ADD_UP`, `PARTS_HOLES_LARGEST_FIRST`, `PARTS_HOLE_PART_KNOWN`, `PARTS_SINGLE_RUN_PAD`) | the `parts.json` read is not assemble's contract (`PARTS_COUNTS_ADD_UP`: `visible_px + occluded_px` is not `opaque_px`, `visible_not_projected_px` is above `visible_px`, or the `recomposite` block's figures disagree — more uncovered than error pixels, a hole larger than its box, the wrong number of holes listed, listed areas that do not sum to `uncovered_error_px`; the last two codes are the block's order and its border names; `PARTS_SINGLE_RUN_PAD`: the `single_run_pad` block's margins are not the centring, its file is not smaller than its square, or `rig_size` is not the file at the scale, issue #366 — §5) | re-run assemble; do not edit `parts.json` |
2287
2459
 
2288
2460
  ### compare
2289
2461
 
package/docs/MIGRATION.md CHANGED
@@ -210,3 +210,55 @@ no declared form changed.
210
210
  What to do: nothing for a config without `contour.bridges`. A part that declares it and
211
211
  holds a faint island no longer draws that island; if those pixels matter, keep the
212
212
  lattice for that part.
213
+
214
+ ## After 1.17.0: an artist's own file
215
+
216
+ Issue #359 makes an artist's layered PSD an input in its own right (AUTHORING §4,
217
+ *For artists* and *The correction loop*). Nothing a config already builds changes:
218
+ every new form was refused or a usage error before, so no byte any existing build
219
+ writes moves (the selftest's existing controls and the examples' `expected/` are
220
+ unchanged by it).
221
+
222
+ | change | before | after |
223
+ | --- | --- | --- |
224
+ | `assemble`, `--propose-plan` and `build` without `--head` | a usage error (exit 2) | one run: the full run must hold `face`, `eyewhite-r` and `eyewhite-l`, painted (`ASSEMBLE_SINGLE_RUN_FACE`); every row takes `"full"` (`ASSEMBLE_SINGLE_RUN_PLAN`); no `seethrough.head_box` (`ASSEMBLE_SINGLE_RUN_HEAD_BOX`) |
225
+ | `propose --full`/`--head` over runs with no depth on any part's layer (a PSD) | refused, `PROPOSE_TURN_DEPTH_PRESENT` on every part (exit 1) | no `turn`, one note `turn: SKIP — <runs> carry no depth_median …`, the proposal written (exit 0); a run where some parts' layers carry a depth and others do not is still refused part by part, and the detail now says so |
226
+ | a wrapper `layers.json` with `depth_median: null` | refused, `LAYERS_FIELD_PRESENT` | `null` on every drawn layer reads in the manifest's order; a mix is `LAYERS_DEPTH_ALL_OR_NONE` |
227
+ | a layer named `<layer>:hidden` | refused, `LAYERS_TAG_KNOWN` | a hidden-art layer, listed apart and never drawn; read as a cut's `under` and a limb segment's cap where the config names no file (`ASSEMBLE_HIDDEN_ONE_SOURCE` when it names one too) |
228
+ | `layers` on a PSD, or on a manifest with no depth | the table | the table and one `SKIP depth:` line naming the file |
229
+ | `parts.json`, a limb segment's `cap` | `image_px` with a cap file | `image_layer` after `image_px` when the image was a hidden-art layer |
230
+
231
+ What to do: nothing. A consumer that matched `PROPOSE_TURN_DEPTH_PRESENT` to detect a
232
+ PSD run reads the `turn: SKIP` note instead.
233
+
234
+ Issue #366 lets that one run be a file that is not square (AUTHORING §4, *For
235
+ artists*: the canvas). Two-run builds and a square file at `seethrough.resolution`
236
+ are read exactly as before — no `single_run_pad` block, no `padded:` line, the same
237
+ bytes (the selftest's existing single-run controls and the examples' `expected/` are
238
+ unchanged by it).
239
+
240
+ | change | before | after |
241
+ | --- | --- | --- |
242
+ | one run whose file is not the `resolution` square, no side above it | refused, `ASSEMBLE_RUN_CANVAS` | padded to the square, centred (the odd pixel right or bottom), and assembled in the file's pixels; `parts.json` ends with `single_run_pad` (`file_size`, `side`, `left`, `top`) and `assemble` prints a `padded:` line under `one run:` |
243
+ | one run whose file has a side above `resolution` | refused, `ASSEMBLE_RUN_CANVAS` | still refused by that code; the detail now says padding cannot shrink a file and names the resolution that would hold it |
244
+ | one run with a painting of another size than the file | read: the run mapped onto the painting at the painting's scale | refused, `ASSEMBLE_SINGLE_RUN_SOURCE` — §4 already required the same size |
245
+ | a `parts.json` with `single_run_pad` | refused, `PARTS_KEY_KNOWN` | read; a block that is not the centring, or disagrees with `rig_size`, is `PARTS_SINGLE_RUN_PAD` |
246
+
247
+ What to do: nothing, unless a single-run painting was not the file's own size — pass
248
+ the file flattened.
249
+
250
+ ### And a check before See-through (`painting`, issue #350)
251
+
252
+ A new command, `rig-parts painting --keypoints <k.json> [--person <id>]
253
+ <painting.png>`, reads a painting against its body-18 joints before See-through runs
254
+ and refuses what a rig cannot be cut from (arms against the body, hands together,
255
+ legs without a gap, a joint off the figure, a background that is not plain;
256
+ [PAINTING.md](PAINTING.md)). It reads the keypoint file `propose --keypoints` reads
257
+ and writes nothing; no other command changed, and no byte any build writes moves.
258
+
259
+ | change | before | after |
260
+ | --- | --- | --- |
261
+ | `rig-parts painting` | a usage error, `unknown command` (exit 2) | the check: exit 0 PASS, 1 with a `FAIL PAINTING_…` line per refusal |
262
+ | the README's input bullet for the painting | one sentence | the requirements, linked to `docs/PAINTING.md`, which ships in the package |
263
+
264
+ What to do: nothing. Run `painting` on a new painting before See-through.
@@ -0,0 +1,172 @@
1
+ # The painting: what makes one riggable
2
+
3
+ rig-parts starts from one painting of one character. Making that painting is
4
+ yours — on your own GPU, with any generator, or by hand — and this package runs
5
+ no model. But a painting the pipeline cannot rig fails late: a sleeve layer that
6
+ holds the torso, a leg chain that drags the other leg, a dance clip that tears.
7
+ This page lists what the painting must be, why the pipeline needs each thing, and
8
+ what breaks without it; gives one generation recipe as a starting point; says how
9
+ to paint alternate art for a limb; and describes `rig-parts painting`, the check
10
+ that reads the painting before See-through runs on it.
11
+
12
+ There are two ways in:
13
+
14
+ - **A generated painting**, decomposed by [See-through](https://github.com/shitagaki-lab/see-through)
15
+ into layers. Everything below is about this route.
16
+ - **An artist's own layered file** (a PSD, one layer per part): [AUTHORING §4,
17
+ *For artists*](AUTHORING.md#for-artists-your-own-layered-file-issue-359) and
18
+ *The correction loop*. The requirements below still hold for the figure — a
19
+ limb the artist draws against the body is as hard to move — but there is no
20
+ See-through step to fail, and an artist can paint what a joint hides as a
21
+ `<layer>:hidden` layer.
22
+
23
+ For the environment that makes a painting (an NVIDIA GPU, an Apple Silicon Mac, or
24
+ none), see [rig-c/getting-started](https://github.com/rig-c/getting-started).
25
+
26
+ ## The requirements
27
+
28
+ Each requirement says what the pipeline needs, what fails without it, and whether
29
+ `rig-parts painting` reads it (and under which rule) or only an eye can.
30
+
31
+ | requirement | why the pipeline needs it | what fails without it | read by |
32
+ | --- | --- | --- | --- |
33
+ | **Front view** | the proposer and the keypoint file read the subject's right on the image's left of a figure facing the viewer, and the eyes, the blink and the head turn are drafted from a face toward the viewer | a back view has no face part for `propose` to draft from (`PROPOSE_FACE_PRESENT`, or the face-less fallback its first note names) | an eye |
34
+ | **Full body, inside the frame** | the figure's height is the proposer's yardstick, and the feet carry the leg chain's last link | a cropped foot is a leg with no ankle; a figure touching the edge reads as background that is not plain | `PAINTING_BACKGROUND_PLAIN` |
35
+ | **Arms clear of the body** (an A-pose: arms down and out, a gap under each arm) | See-through separates what it can see; an arm with background round it becomes its own `handwear` and sleeve layers | an arm painted against the side is cut into a layer that holds part of the torso, and no rotation of its bone can swing it clear without dragging the torso's pixels | `PAINTING_ARM_CLEAR`: a gap on the line from each wrist to the hip of its side |
36
+ | **Hands apart and open** | each hand is a part on its own arm's chain; open fingers read as a hand at a thumbnail | clasped or touching hands are one `handwear` layer, split only by an `assemble.cuts` polygon drawn by hand; and the clasped hands the README showed until issue #227 did not read as hands above a thumbnail (`examples/demo/README.md`) | `PAINTING_HANDS_APART`: a gap on the line between the wrists. Hands that meet close a loop of arms round the torso, so both arms are refused with them |
37
+ | **Legs apart, with a visible gap** | the leg chain needs the legs painted apart (two legs that touch are one column See-through cannot split), and limbs are cut at their joints (#349, AUTHORING §3, *Limbs*), so each knee must be its own | legs that touch at the knee or the ankle come back as one `legwear` silhouette, a cut at the knee takes the other leg's pixels, and a step drags both | `PAINTING_LEGS_APART`: a gap on the line between the knees and on the line between the ankles |
38
+ | **Feet facing front** | a foot seen from the front stays inside its leg's column, below the ankle the leg chain ends at | not measured in this repository; read the leg parts on the `sheet` | an eye (body-18 has no foot points) |
39
+ | **A plain light background** | See-through and `assemble` read near-white as background (every channel above 235, the one test the assemble rim and the layer plausibility figures share) | scenery, a floor, a cast shadow, a gradient or a vignette is read as more figure — a layer of haze (`PLAN_LAYER_BACKGROUND`), or pixels that belong to no part | `PAINTING_BACKGROUND_PLAIN`: every border pixel near-white or unpainted |
40
+ | **Nothing held, no prop across a limb** | each part rides one bone; a prop belongs to no tag See-through knows | a held object joins the hands (`PAINTING_HANDS_APART`) or lands in a hand or garment layer and moves with the wrong bone | partly: a prop that joins the hands is `PAINTING_HANDS_APART`; anything else, an eye |
41
+ | **Clothing that leaves the joints unoccluded** | limbs are cut at the elbow and the knee (#349); each lower part carries a cap painted from what the joint hides | long sleeves and tights are fine; a cape over the elbows, a wide skirt over the knees or a forearm in front of the skirt's flare puts two parts' pixels on one joint. `assemble` names the missing cap pixels in its `limb cap:` lines | partly: a skirt over the knees is `PAINTING_LEGS_APART`, a forearm across a skirt is `PAINTING_ARM_CLEAR`; a cape over an elbow, an eye |
42
+ | **Resolution and aspect** | See-through runs on a square (`inputs` pads the painting white to a square of its longer side) at `seethrough.resolution` (1024 by default), and a second run on a crop round the head, because the eyes of a full-figure run are too small to separate. A standing figure is taller than wide; `comfy paint` writes twice its latent (832x1216 → 1664x2432) | a landscape painting is padded above and below and spends See-through's square on white; a small painting leaves the head crop too few pixels for the eyes | an eye; `inputs` refuses a translucent painting (`INPUTS_PAINTING_OPAQUE`) and a head box off it |
43
+
44
+ **One painting, one character.** The keypoint file may hold several people, and
45
+ `--person` names the one checked; the pipeline rigs one.
46
+
47
+ ## The check: `rig-parts painting`
48
+
49
+ ```
50
+ rig-parts painting --keypoints <keypoints.json> [--person <id>] <painting.png>
51
+ ```
52
+
53
+ It runs on the CPU, before See-through, on the painting and its body-18 joints:
54
+ the same keypoint file `propose --keypoints` reads (AUTHORING §3, *A posed
55
+ figure*), from any estimator or written by hand, measured on this painting (its
56
+ `width` and `height` are the painting's, `KEYPOINTS_IMAGE_SIZE`). It writes
57
+ nothing. It prints the figure, every joint and every gap with its count, then
58
+ `PASS` and exit 0, or a `FAIL` line per refusal and exit 1. Each `FAIL` line ends
59
+ with `(see rig-parts guide <RULE>)`, which prints the rule's row in
60
+ [AUTHORING §6](AUTHORING.md#6-refusals-the-rule-and-what-has-to-change).
61
+
62
+ What it reads, each a definition with no tuned number:
63
+
64
+ - **Ground** — a pixel the package already calls background: alpha 8 or less
65
+ (unpainted, the layer reader's cut), or every colour channel above 235
66
+ (near-white, the assemble stage's and the plausibility figures' test).
67
+ - **Background** — the ground that reaches the edge: ground 4-connected to ground
68
+ on the border. A white blouse inside its line art is ground but not background,
69
+ so it is figure.
70
+ - **Figure** — everything that is not background.
71
+ - **A gap** between two joints — at least one background pixel on the straight
72
+ line between them, sampled once per pixel the line crosses.
73
+
74
+ The rules: `PAINTING_BACKGROUND_PLAIN` (every border pixel is ground),
75
+ `PAINTING_JOINTS_STATED` (the wrists, hips, knees and ankles are `observed` with a
76
+ position), `PAINTING_JOINT_ON_FIGURE` (every observed joint's pixel is figure),
77
+ `PAINTING_ARM_CLEAR`, `PAINTING_HANDS_APART` and `PAINTING_LEGS_APART` (the gaps in
78
+ the table above).
79
+
80
+ What it does not read: a front view, the feet, a prop that does not join the
81
+ hands, a cape over an elbow, the hands' fingers, the face. A green `painting` says
82
+ the joints and the silhouette hold the requirements it reads, not that the
83
+ painting is good. A shadow or a floor enclosed by the background is figure to it;
84
+ the border rule sees it only where it reaches the edge.
85
+
86
+ ## A recipe that is one way, not the only way
87
+
88
+ Anyone with a different generator reads the requirements above, not this. This
89
+ is the `generation` block of the public `demo` example (`examples/demo/config.json`,
90
+ with its pose file `examples/demo/pose.json`), which `rig-parts comfy paint`
91
+ painted, with three changes: its character's words in `identity` and `costume`
92
+ are placeholders, and the words its idol costume needed in `negative_extra`
93
+ ("microphone, holding microphone, stage, stage lights") are left out; its full record — the candidates, the seed choice and a hand-detailer
94
+ pass `comfy paint` does not run — is in
95
+ [parts-examples](https://github.com/rig-c/parts-examples):
96
+
97
+ ```json
98
+ "generation": {
99
+ "checkpoint": "hassakuXLIllustrious_v13StyleA.safetensors",
100
+ "loras": [],
101
+ "trigger": "",
102
+ "identity": "1girl, solo, adult woman, <your character's hair, eyes, accessories>",
103
+ "sampler": { "steps": 30, "cfg": 6.0, "sampler": "euler_ancestral", "scheduler": "normal" },
104
+ "costume": "<the costume>, fully clothed, empty hands",
105
+ "negative_extra": "revealing clothes, bare shoulders, off shoulder, 2girls, multiple girls, holding, bag, weapon, clouds, cloud, sun, sky, sky background, grey background, blue background, gradient background, scenery, outdoors, from behind, back view, back of head, facing away, vignette, beige background, brown background, dark background, black background, spotlight, lighting gradient, flat color, cel shading, monochrome, lineart",
106
+ "pose": "full body, head to toe, entire body in frame, front view, facing viewer, looking at viewer, standing, arms away from body, hands apart away from skirt, open hands, relaxed fingers, relaxed pose, gentle smile, feet visible, shoes",
107
+ "style": "anime illustration, detailed, soft shading, (white background:1.3), simple background, plain background",
108
+ "negative_pose": "cropped, out of frame, cut off feet, close-up, portrait, upper body, from side, profile, sitting, kneeling, dynamic pose, arms raised, hand on face, contact shadow, cast shadow, floor, multiple views, reference sheet",
109
+ "latent": [832, 1216],
110
+ "seed": 71006,
111
+ "control": { "skeleton": "pose.json", "strength": 0.8, "end_percent": 0.7 }
112
+ }
113
+ ```
114
+
115
+ Put your character in the two placeholders, and negate what its costume invites.
116
+ What each part does:
117
+
118
+ - **The checkpoint** is public: [Hassaku XL (Illustrious)](https://civitai.com/models/140272)
119
+ v1.3 Style A, no LoRA. `scarf` and the two reference examples used
120
+ [Pony Diffusion V6 XL](https://civitai.com/models/257749) with the same sampler.
121
+ - **The control skeleton** is what holds the pose; a pose asked for in words
122
+ alone is one the sampler is free to ignore (`src/skeleton.ts`). `comfy paint`
123
+ draws it as OpenPose body-18 in the annotator's own style and applies it through
124
+ `controlnet-openpose-sdxl-1.0` at strength 0.8, ending at 0.7 of the steps. The
125
+ two built-ins are `stand_sides` (arms nearly straight down at the sides) and
126
+ `stand_clasp` (hands clasped at the waist), neither an A-pose; `pose.json` is a
127
+ pose file (issue #198) with the arms held out: the wrists at (206, 590) and
128
+ (626, 590) on the 832x1216 canvas, the shoulders at (340, 272) and (492, 272).
129
+ A pose file carries no words, so `generation.pose` is required beside it.
130
+ - **The pose words** repeat what the skeleton asks — arms away, hands apart and
131
+ open — and the **negatives** refuse what breaks a rig: a held object, a
132
+ background that is not plain, a back or side view, a crop, a cast shadow or a
133
+ floor.
134
+ - **The seed** is chosen by looking: the demo's was the best of sixteen candidates,
135
+ read at the hands; generate several and run `rig-parts painting` on each.
136
+
137
+ What this recipe did not hold, measured: on the demo painting itself, with joints
138
+ read by eye, `rig-parts painting` refuses both arms (the forearms cross the
139
+ skirt's flare) and the legs at the knees and the ankles (the pose file's knees
140
+ and ankles are the built-ins', close together, and the thighhighs touch). So
141
+ change the two things it did not ask for, and check the result: move the pose
142
+ file's knees and ankles apart and add leg words ("legs apart") to `pose`, and keep
143
+ a skirt short or narrow enough that the forearms hang clear of it. How far apart
144
+ holds a gap on a given checkpoint is not measured here; `painting` is the
145
+ instrument that says.
146
+
147
+ ## Alternate art for a limb
148
+
149
+ A rig plays its base art in every pose; where a limb turns somewhere the base art
150
+ cannot follow (behind the head, toward the camera), the mesh folds.
151
+ [rig-c/pose#6](https://github.com/rig-c/pose/issues/6) lets `rig-pose retarget`
152
+ switch to an alternate drawing of the limb for those spans, and its report's
153
+ `uncovered` list names every fold no alternate covers — that list is the answer
154
+ to *which* angles to paint.
155
+
156
+ To paint one without the character drifting, repaint only the limb:
157
+
158
+ 1. **Start from the base painting**, not from a new generation: the face, the
159
+ costume and the colours stay the base painting's pixels.
160
+ 2. **Inpaint with a mask along the old and the new limb**: the region the limb
161
+ leaves and the region it moves into, from the shoulder (or hip) down. Keep the
162
+ face out of the mask.
163
+ 3. **Use the key skeleton as the control**: the body-18 pose the alternate depicts,
164
+ in the painting's space — the same skeleton the pose map's `alternates` entry
165
+ records — drawn the way `comfy paint` draws a pose file.
166
+ 4. **Run See-through on the result and take the limb's layers** (the sleeve, the
167
+ hand); the rest of that run is discarded. The base rig's other parts stay the
168
+ base painting's.
169
+
170
+ `comfy paint` does not run an inpainting graph; the demo's hand-detailer pass is
171
+ the same idea at hand scale (a masked repaint on the same checkpoint, recorded
172
+ beside its painting in parts-examples).
package/docs/STABILITY.md CHANGED
@@ -46,21 +46,22 @@ defaults are the help's too, and none is guessed from the art.
46
46
  <!-- stable:commands -->
47
47
  | command | flags |
48
48
  | --- | --- |
49
- | `layers` | one positional path: a wrapper directory, a `layers.json` or a `.psd` |
49
+ | `layers` | one positional path: a wrapper directory, a `layers.json` or a `.psd`; with it, optional `--export` (a directory to write the wrapper form into, issue #359); or `--template` with `--canvas` and no path |
50
50
  | `sheet` | `--source`, `--layers` (repeatable), `--out`; optional `--cell`, `--cols` |
51
51
  | `inputs` | `--source`, `--config`, `--out` |
52
52
  | `comfy` | `paint`: `--config`, `--out`; optional `--seeds`, `--seed0`, `--host`, `--wait`, `--timeout`, `--poll` |
53
53
  | `comfy` | `seethrough`: `--image`, `--out`; optional `--resolution`, `--steps`, `--seed`, `--prefix`, `--offload`, `--lama`, `--nf4`, `--host`, `--wait`, `--timeout`, `--poll` |
54
- | `assemble` | `--propose-plan` with `--source`, `--full`, `--head`, `--config`; or `--source`, `--full`, `--head`, `--config`, `--out`, optional `--seam` (near-white, silhouette), `--project` (core, visible) |
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
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`) |
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` |
60
60
  | `loop` | `--frames`, `--out`; optional `--palette` |
61
- | `build` | `--config`, `--source`, `--full`, `--head`, `--out`; optional `--seam`, `--project`, `--page-edges`, `--pack-shape`, `--loop`, `--lead-in`, `--requirements`, `--idle-keys`, `--seam-pairs`, `--keep-stills` |
61
+ | `build` | `--config`, `--source`, `--full`, `--out`; optional `--head` (as for `assemble`), `--seam`, `--project`, `--page-edges`, `--pack-shape`, `--loop`, `--lead-in`, `--requirements`, `--idle-keys`, `--seam-pairs`, `--keep-stills` |
62
62
  | `compose` | `--scene`, `--out`; optional `--requirements`, `--accept` |
63
63
  | `mesh-draft` | `--config`, `--parts`, `--out`; optional `--requirements`, `--idle-keys` (ctl, direct), `--page-edges` (pot, free), `--pack-shape` (rect, polygon), `--more-triangles` |
64
+ | `painting` | one positional path, the painting; `--keypoints`; optional `--person` |
64
65
  | `guide` | one positional argument: a rule name, `§<n>`, `§<n>.<m>` or `§<n>.<m>.<k>` |
65
66
  | (none) | `--version`, `--help` |
66
67
  <!-- /stable:commands -->
@@ -173,10 +174,12 @@ every number) are AUTHORING §3's tables, and the loader refuses any other by na
173
174
 
174
175
  | file | written by | what is stable |
175
176
  | --- | --- | --- |
176
- | `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, `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) |
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) |
177
178
  | `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) |
178
179
  | `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) |
179
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 |
181
+ | `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
+ | the template `.psd` | `layers --template` | one empty layer per name `templateLayerNames` (`src/layers.ts`) holds, bottom first; the same canvas writes the same bytes |
180
183
  | `proposal.json`, `render/landmarks*.png` | `propose` | the config's four rig sections, `turn` with `--full`/`--head`, and `notes` |
181
184
  | `basis.json` | `propose` | spec `spine-parts-basis/1` |
182
185
  | `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 |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rig-c/parts",
3
- "version": "1.17.0",
3
+ "version": "1.18.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": {
@@ -43,6 +43,7 @@
43
43
  "workflows",
44
44
  "docs/AUTHORING.md",
45
45
  "docs/MIGRATION.md",
46
+ "docs/PAINTING.md",
46
47
  "docs/STABILITY.md",
47
48
  "skills",
48
49
  "NOTICE.md"