rig-parts 2.0.5 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli.ts +34 -19
- package/docs/AUTHORING.md +15 -10
- package/docs/PAINTING.md +62 -3
- package/docs/STABILITY.md +1 -1
- package/package.json +1 -1
- package/src/paintcheck.ts +126 -21
package/cli.ts
CHANGED
|
@@ -40,7 +40,7 @@ import { meshStatsStage } from './src/meshstats.ts';
|
|
|
40
40
|
import { chainsLeftBehind, clothConfigEdits, editPaths, plainCount, previousCount, writtenClothConfig } from './src/clothconfig.ts';
|
|
41
41
|
import { INPUT_CHECK_FILE, INPUT_CHECK_SPEC, PROVENANCE_FILE, REMESH_SPEC, remeshStage } from './src/remesh.ts';
|
|
42
42
|
import { RETUNE_FILE, RETUNE_SPEC, retuneStage } from './src/retune.ts';
|
|
43
|
-
import { checkPainting } from './src/paintcheck.ts';
|
|
43
|
+
import { checkPainting, DEFAULT_PAINTING_FORM, isPaintingForm, PAINTING_FORMS, paintingFormOf } from './src/paintcheck.ts';
|
|
44
44
|
import { readPng, writePng } from './src/raster/png.ts';
|
|
45
45
|
import { REQUIREMENTS_SENTENCE } from './src/requirements.ts';
|
|
46
46
|
import { composeStage, IMAGE_SEP, PLATE, PROVENANCES, SCENE_REPORT_FILE, SCENE_REPORT_SPEC, SCENE_SPEC } from './src/scene.ts';
|
|
@@ -947,20 +947,28 @@ usage:
|
|
|
947
947
|
line and ${SCENE_REPORT_FILE} name what was accepted, and the run exits 1.
|
|
948
948
|
Exit 0 when the check is PASS and nothing was accepted, 1 otherwise.
|
|
949
949
|
|
|
950
|
-
rig-parts painting --keypoints <keypoints.json> [--person <id>] <painting.png>
|
|
950
|
+
rig-parts painting --keypoints <keypoints.json> [--person <id>] [--form standee|illustration] <painting.png>
|
|
951
951
|
Before See-through: read the painting against its body-18 joints (the
|
|
952
952
|
file propose --keypoints reads, measured on this painting) and refuse,
|
|
953
|
-
by name, what a rig cannot be cut from.
|
|
954
|
-
(
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
occluded
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
953
|
+
by name, what a rig cannot be cut from. --form defaults to
|
|
954
|
+
${DEFAULT_PAINTING_FORM} (${PAINTING_FORMS.join(', ')}). Under standee,
|
|
955
|
+
background is near-white (every channel above 235) or unpainted (alpha 8
|
|
956
|
+
or less) ground connected to the edge; the figure is the rest; a gap is
|
|
957
|
+
one background pixel on the line between two joints.
|
|
958
|
+
PAINTING_BACKGROUND_PLAIN (a border pixel that is not ground),
|
|
959
|
+
PAINTING_JOINTS_STATED (a wrist, knee or ankle not observed, or a hip
|
|
960
|
+
neither observed nor occluded with a position), PAINTING_JOINT_ON_FIGURE
|
|
961
|
+
(an observed joint, or an occluded hip, on the background),
|
|
962
|
+
PAINTING_ARM_CLEAR (no gap from a wrist to its hip),
|
|
963
|
+
PAINTING_HANDS_APART (none between the wrists), PAINTING_LEGS_APART
|
|
964
|
+
(none between the knees, or the ankles). Under --form illustration (a
|
|
965
|
+
framed illustration with a painted room or a figure cut by the crop),
|
|
966
|
+
the source, keypoint canvas and bounds checks still run,
|
|
967
|
+
PAINTING_JOINTS_STATED requires at least one joint with a position and
|
|
968
|
+
PAINTING_JOINT_ON_FIGURE requires every stated joint's pixel to be
|
|
969
|
+
painted (alpha above 8); the standee border, 8-joint and gap rules do
|
|
970
|
+
not apply and print SKIP with the reason, never PASS. Prints the
|
|
971
|
+
figure, every joint and every gap or SKIP line; writes nothing.
|
|
964
972
|
docs/PAINTING.md says why each one matters. Exit 0 PASS, 1 refused.
|
|
965
973
|
|
|
966
974
|
rig-parts guide <RULE | §<n> | §<n>.<m> | §<n>.<m>.<k>>
|
|
@@ -1108,7 +1116,7 @@ const TAKES = {
|
|
|
1108
1116
|
'mesh-stats': { valued: ['--config', '--parts'], switches: [] },
|
|
1109
1117
|
remesh: { valued: ['--rig', '--config', '--parts', '--part', '--mesh', '--delta', '--out', '--requirements', '--idle-keys', '--page-edges', '--pack-shape', '--input-check'], switches: ['--record-input-check'] },
|
|
1110
1118
|
retune: { valued: ['--rig', '--config', '--parts', '--out', '--requirements', '--idle-keys', '--page-edges', '--pack-shape', '--input-check'], switches: ['--record-input-check'] },
|
|
1111
|
-
painting: { valued: ['--keypoints', '--person'], switches: [] },
|
|
1119
|
+
painting: { valued: ['--keypoints', '--person', '--form'], switches: [] },
|
|
1112
1120
|
} as const satisfies Record<string, { valued: readonly string[]; switches: readonly string[] }>;
|
|
1113
1121
|
|
|
1114
1122
|
type Command = keyof typeof TAKES;
|
|
@@ -2153,8 +2161,9 @@ function cmdRetune(args: string[]): number {
|
|
|
2153
2161
|
}
|
|
2154
2162
|
|
|
2155
2163
|
/**
|
|
2156
|
-
* `painting --keypoints <k.json> [--person <id>] <painting.png>` (
|
|
2157
|
-
* See-through runs on it, read off its pixels and the body-18 joints
|
|
2164
|
+
* `painting --keypoints <k.json> [--person <id>] [--form standee|illustration] <painting.png>` (issues #350, #931):
|
|
2165
|
+
* what the painting must be before See-through runs on it, read off its pixels and the body-18 joints
|
|
2166
|
+
* `propose --keypoints` reads. Writes nothing.
|
|
2158
2167
|
*/
|
|
2159
2168
|
function cmdPainting(args: string[]): number {
|
|
2160
2169
|
const flags = new Map<string, string>();
|
|
@@ -2174,6 +2183,8 @@ function cmdPainting(args: string[]): number {
|
|
|
2174
2183
|
}
|
|
2175
2184
|
const kpPath = flags.get('--keypoints');
|
|
2176
2185
|
if (kpPath === undefined) return usage('painting needs --keypoints <file>, the body-18 joints of the figure in painting px (the file propose --keypoints reads)');
|
|
2186
|
+
const form = paintingFormOf(flags.get('--form'));
|
|
2187
|
+
if (!isPaintingForm(form)) return usage(form);
|
|
2177
2188
|
if (paths.length !== 1) return usage(`painting takes one painting path; got ${paths.length}`);
|
|
2178
2189
|
const source = paths[0];
|
|
2179
2190
|
try {
|
|
@@ -2187,11 +2198,15 @@ function cmdPainting(args: string[]): number {
|
|
|
2187
2198
|
const kp = loadKeypoints(kpPath);
|
|
2188
2199
|
checkImageSize(kp, painting.width, painting.height, kpPath, source);
|
|
2189
2200
|
const person = choosePerson(kp, flags.get('--person'), kpPath);
|
|
2190
|
-
console.log(`rig-parts painting: ${source}, joints from ${kpPath} person "${person.id}" (source: ${kp.source})`);
|
|
2191
|
-
const r = checkPainting(painting, person, source, kpPath);
|
|
2201
|
+
console.log(`rig-parts painting${form === 'illustration' ? ' (--form illustration)' : ''}: ${source}, joints from ${kpPath} person "${person.id}" (source: ${kp.source})`);
|
|
2202
|
+
const r = checkPainting(painting, person, source, kpPath, form);
|
|
2192
2203
|
for (const l of r.lines) console.log(l);
|
|
2193
2204
|
if (r.problems.length > 0) throw new PartsError(r.problems);
|
|
2194
|
-
console.log(
|
|
2205
|
+
console.log(
|
|
2206
|
+
form === 'illustration'
|
|
2207
|
+
? ' PASS the painting holds the framed-illustration checks that apply (source, keypoint canvas and bounds, stated joints on painted pixels); standee background and gap rules reported SKIP above, not passed (docs/PAINTING.md)'
|
|
2208
|
+
: ' PASS the painting holds every requirement this check reads (docs/PAINTING.md); what only an eye reads is listed there',
|
|
2209
|
+
);
|
|
2195
2210
|
return EXIT_OK;
|
|
2196
2211
|
} catch (err) {
|
|
2197
2212
|
return printRefusal(err);
|
package/docs/AUTHORING.md
CHANGED
|
@@ -3657,7 +3657,7 @@ existing hardlinks are refused before artifact output; the ordinary mode names
|
|
|
3657
3657
|
| `compose --scene <scene.json> --out [--requirements] [--accept]` | bind several characters' green builds into one rig (or, under `--accept`, builds failing only the bars named — for looking, not shipping): every name prefixed `<id>:` under one shared root, each character's offset added at its first-level bones, an optional background plate (its provenance recorded, judged by nothing) drawn first, and the draw order between the characters' slots as the scene file declares it — nothing inferred, nothing scaled; gated through rig-c as `rig` is, then checked ([AUTHORING §7](AUTHORING.md), *Composing several characters*) |
|
|
3658
3658
|
| `mesh-draft --config <json> --parts <dir> --out <dir>` | draft contour meshes for configured lattices, keeping each only where it holds against its lattice through rig-c; write the revised config and measurement report |
|
|
3659
3659
|
| `mesh-stats --config <json> --parts <dir>` | print each configured mesh's vertex and triangle counts and their total through the rig stage's construction; write nothing |
|
|
3660
|
-
| `painting --keypoints <json> [--person <id>] <png>` | before See-through, read the painting against its declared joints
|
|
3660
|
+
| `painting --keypoints <json> [--person <id>] [--form standee\|illustration] <png>` | before See-through, read the painting against its declared joints (`standee` by default, or `illustration` for a framed illustration where standee border, 8-joint and gap rules report `SKIP`) and refuse named problems; write nothing |
|
|
3661
3661
|
| `remesh --rig <dir> --config <json> --parts <dir> --part <name> --out <dir>` | with exactly one of `--mesh <entry.json>` or `--delta <mesh-edit.json>`, re-fit one part of a gated rig into a fresh revision, preserving the input |
|
|
3662
3662
|
| `retune --rig <dir> --config <json> --parts <dir> --out <dir>` | splice the config's constraints and idle onto a gated rig in a fresh revision, carrying its attachments over as bytes |
|
|
3663
3663
|
| `guide <RULE \| §<n> \| §<n>.<m> \| §<n>.<m>.<k>>` | print the one row of [docs/AUTHORING.md](AUTHORING.md) that names a rule (a refusal and the bar it enforces, `CHECK_<LINE>` and `<LINE>`, print together), or one section, `###` subsection or `####` under it, read from the installed file when it runs |
|
|
@@ -4609,21 +4609,26 @@ See-through with another seed.
|
|
|
4609
4609
|
|
|
4610
4610
|
### painting
|
|
4611
4611
|
|
|
4612
|
-
`rig-parts painting --keypoints <k.json> <painting.png>` (
|
|
4613
|
-
before See-through runs on it. *
|
|
4612
|
+
`rig-parts painting --keypoints <k.json> [--person <id>] [--form standee|illustration] <painting.png>` (issues #350, #931) reads the painting
|
|
4613
|
+
before See-through runs on it. Under `--form standee` (the default), *background* is ground (alpha 8 or less, or every channel
|
|
4614
4614
|
above 235) connected to the painting's edge; the *figure* is the rest; a *gap* is one
|
|
4615
|
-
background pixel on the straight line between two joints.
|
|
4615
|
+
background pixel on the straight line between two joints. Under `--form illustration`
|
|
4616
|
+
(a framed illustration with a painted room or a figure cut by the crop), the source
|
|
4617
|
+
and keypoint checks still run, `PAINTING_JOINTS_STATED` requires at least one joint
|
|
4618
|
+
with a position, `PAINTING_JOINT_ON_FIGURE` checks that every stated joint's pixel is
|
|
4619
|
+
painted (alpha above 8), and the standee border, 8-joint and gap rules report `SKIP`
|
|
4620
|
+
with their reason, never `PASS`. [PAINTING.md](PAINTING.md)
|
|
4616
4621
|
says why each requirement exists and what breaks without it.
|
|
4617
4622
|
|
|
4618
4623
|
| rule | means | change |
|
|
4619
4624
|
| --- | --- | --- |
|
|
4620
4625
|
| <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 |
|
|
4621
|
-
| <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*) |
|
|
4622
|
-
| <a id="PAINTING_JOINTS_STATED"></a>`PAINTING_JOINTS_STATED` | a wrist, knee or ankle is `occluded` or `missing` in the keypoint file, or a hip has no position: the gap rules read those eight joints, and a hand or foot the painting does not show is a limb nothing can rig. A hip may be `occluded` (a skirt hides it on most clothed figures, issue #372) when its arm's gap is **well defined**: it carries a position — inside the image, which the keypoint reader holds (`KEYPOINTS_POSITION_INSIDE`), so the wrist-to-hip line's every sample is a pixel of the painting — and that pixel is figure (`PAINTING_JOINT_ON_FIGURE`). A hip `occluded` with no position, or `missing`, is refused: there is no line to read | a painting that shows it; or the joint, if the estimator missed one the painting shows; for a hip under a skirt, `occluded` with its estimated position (PAINTING.md, *An occluded hip*) |
|
|
4623
|
-
| <a id="PAINTING_JOINT_ON_FIGURE"></a>`PAINTING_JOINT_ON_FIGURE` | an observed joint's pixel, or an occluded hip's, is background
|
|
4624
|
-
| <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*) |
|
|
4625
|
-
| <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*) |
|
|
4626
|
-
| <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*) |
|
|
4626
|
+
| <a id="PAINTING_BACKGROUND_PLAIN"></a>`PAINTING_BACKGROUND_PLAIN` | under `--form standee`, 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 (`SKIP` under `--form illustration`) | a painting on a plain light background with the whole figure inside the frame, or `--form illustration` for a framed illustration (PAINTING.md, *Background* and *Framed illustrations*) |
|
|
4627
|
+
| <a id="PAINTING_JOINTS_STATED"></a>`PAINTING_JOINTS_STATED` | under `--form standee`, a wrist, knee or ankle is `occluded` or `missing` in the keypoint file, or a hip has no position: the gap rules read those eight joints, and a hand or foot the painting does not show is a limb nothing can rig. A hip may be `occluded` (a skirt hides it on most clothed figures, issue #372) when its arm's gap is **well defined**: it carries a position — inside the image, which the keypoint reader holds (`KEYPOINTS_POSITION_INSIDE`), so the wrist-to-hip line's every sample is a pixel of the painting — and that pixel is figure (`PAINTING_JOINT_ON_FIGURE`). A hip `occluded` with no position, or `missing`, is refused: there is no line to read. Under `--form illustration`, the eight standee gap joints report `SKIP`, and the rule refuses when the person states 0 joints with a position (`propose --keypoints` places bones only from joints with a position) | a painting that shows it; or the joint, if the estimator missed one the painting shows; for a hip under a skirt, `occluded` with its estimated position (PAINTING.md, *An occluded hip* and *Framed illustrations*) |
|
|
4628
|
+
| <a id="PAINTING_JOINT_ON_FIGURE"></a>`PAINTING_JOINT_ON_FIGURE` | under `--form standee`, an observed joint's pixel, or an occluded hip's, is background (for an occluded hip the detail adds that the arm's gap would be the estimate's own background, not one between the arm and the body). Under `--form illustration`, the near-white border flood reports `SKIP`, and any joint with a position whose pixel is unpainted (alpha 8 or less) is refused | the keypoint file (measured on this painting?), or the painting; an occluded hip's estimate, moved onto the figure it is hidden under |
|
|
4629
|
+
| <a id="PAINTING_ARM_CLEAR"></a>`PAINTING_ARM_CLEAR` | under `--form standee`, 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 (`SKIP` under `--form illustration`) | a painting with the arm held away from the body (PAINTING.md, *Arms*) |
|
|
4630
|
+
| <a id="PAINTING_HANDS_APART"></a>`PAINTING_HANDS_APART` | under `--form standee`, the line between the two wrists crosses no background: hands clasped or touching (`SKIP` under `--form illustration`) | a painting with the hands apart and open (PAINTING.md, *Hands*) |
|
|
4631
|
+
| <a id="PAINTING_LEGS_APART"></a>`PAINTING_LEGS_APART` | under `--form standee`, 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; `SKIP` under `--form illustration`) | a painting with the legs apart and a visible gap at the knees and the ankles (PAINTING.md, *Legs*) |
|
|
4627
4632
|
|
|
4628
4633
|
### The optional ComfyUI adapter (`comfy paint`, `comfy seethrough`)
|
|
4629
4634
|
|
package/docs/PAINTING.md
CHANGED
|
@@ -47,14 +47,16 @@ Each requirement says what the pipeline needs, what fails without it, and whethe
|
|
|
47
47
|
## The check: `rig-parts painting`
|
|
48
48
|
|
|
49
49
|
```
|
|
50
|
-
rig-parts painting --keypoints <keypoints.json> [--person <id>] <painting.png>
|
|
50
|
+
rig-parts painting --keypoints <keypoints.json> [--person <id>] [--form standee|illustration] <painting.png>
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
It runs on the CPU, before See-through, on the painting and its body-18 joints:
|
|
54
54
|
the same keypoint file `propose --keypoints` reads (AUTHORING §3, *A posed
|
|
55
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`).
|
|
57
|
-
|
|
56
|
+
`width` and `height` are the painting's, `KEYPOINTS_IMAGE_SIZE`). `--form`
|
|
57
|
+
defaults to `standee` (`standee` or `illustration`). It writes
|
|
58
|
+
nothing. It prints the figure, every joint and every gap (or under
|
|
59
|
+
`--form illustration`, the `SKIP` lines for rules that do not apply), then
|
|
58
60
|
`PASS` and exit 0, or a `FAIL` line per refusal and exit 1. Each `FAIL` line ends
|
|
59
61
|
with `(see rig-parts guide <RULE>)`, which prints the rule's row in
|
|
60
62
|
[AUTHORING §6](AUTHORING.md#6-refusals-the-rule-and-what-has-to-change).
|
|
@@ -132,6 +134,63 @@ the joints and the silhouette hold the requirements it reads, not that the
|
|
|
132
134
|
painting is good. A shadow or a floor enclosed by the background is figure to it;
|
|
133
135
|
the border rule sees it only where it reaches the edge.
|
|
134
136
|
|
|
137
|
+
### Framed illustrations (`--form illustration`, issue #931)
|
|
138
|
+
|
|
139
|
+
The default `--form standee` checks a full-body figure on a plain near-white or
|
|
140
|
+
unpainted ground, with gaps for articulated limb cuts (`config.limbs`). Measured
|
|
141
|
+
on the five 1664x2432 public examples (`demo`, `scarf`, `reference-demo`,
|
|
142
|
+
`reference-sample`, `wind` — all five of which build and pass `rig-parts check`),
|
|
143
|
+
only `wind` (the one example that declares `config.limbs`) passes the standee gap
|
|
144
|
+
rules; the four non-limb standees fail `PAINTING_ARM_CLEAR` and
|
|
145
|
+
`PAINTING_HANDS_APART` (on `demo` and `scarf` the wrists are 840 px and 392 px
|
|
146
|
+
apart on opposite sides of the figure, yet the straight wrist-to-wrist line
|
|
147
|
+
crosses the torso in between, so 0 of 841 and 0 of 393 sampled pixels are
|
|
148
|
+
background), and three (`demo`, `reference-demo`, `reference-sample`) fail
|
|
149
|
+
`PAINTING_LEGS_APART`:
|
|
150
|
+
|
|
151
|
+
| public example (1664x2432) | `borderNotGround` | `r_wrist..r_hip` | `l_wrist..l_hip` | `r_wrist..l_wrist` | `r_knee..l_knee` | `r_ankle..l_ankle` | `--form standee` | `--form illustration` |
|
|
152
|
+
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
|
153
|
+
| `demo` (`pose.json` x2) | 0 / 8188 | 0 / 345 (`FAIL`) | 0 / 345 (`FAIL`) | 0 / 841 (`FAIL`) | 0 / 129 (`FAIL`) | 0 / 113 (`FAIL`) | `FAIL` (5) | `PASS` (6 `SKIP`) |
|
|
154
|
+
| `scarf` (`stand_sides` x2) | 0 / 8188 | 0 / 121 (`FAIL`) | 0 / 121 (`FAIL`) | 0 / 393 (`FAIL`) | 34 / 129 (`PASS`) | 32 / 113 (`PASS`) | `FAIL` (3) | `PASS` (6 `SKIP`) |
|
|
155
|
+
| `reference-demo` (`stand_clasp` x2) | 0 / 8188 | 0 / 93 (`FAIL`) | 0 / 93 (`FAIL`) | 0 / 57 (`FAIL`) | 0 / 129 (`FAIL`) | 0 / 113 (`FAIL`) | `FAIL` (5) | `PASS` (6 `SKIP`) |
|
|
156
|
+
| `reference-sample` (`stand_clasp` x2) | 0 / 8188 | 0 / 93 (`FAIL`) | 0 / 93 (`FAIL`) | 0 / 57 (`FAIL`) | 0 / 129 (`FAIL`) | 0 / 113 (`FAIL`) | `FAIL` (5) | `PASS` (6 `SKIP`) |
|
|
157
|
+
| `wind` (`keypoints.json`) | 0 / 8188 | 60 / 429 (`PASS`) | 76 / 415 (`PASS`) | 157 / 1011 (`PASS`) | 137 / 261 (`PASS`) | 248 / 351 (`PASS`) | `PASS` | `PASS` (6 `SKIP`) |
|
|
158
|
+
|
|
159
|
+
On a framed illustration — a character in a painted room, or framed at the waist
|
|
160
|
+
or thighs so the figure touches the crop border and lower-body joints lie outside
|
|
161
|
+
the canvas — near-white border flood-fill (`backgroundMask`) fails in both
|
|
162
|
+
directions:
|
|
163
|
+
|
|
164
|
+
1. **A painted room is not near-white ground.** Non-white room walls at the
|
|
165
|
+
border (`R,G,B <= 235`) are not seeded by `backgroundMask`, so `backgroundPx`
|
|
166
|
+
is 0 across the painted room and `PAINTING_JOINT_ON_FIGURE` cannot tell a room
|
|
167
|
+
wall from the figure before See-through runs.
|
|
168
|
+
2. **White clothing touching the crop edge floods into the figure.** Measured on
|
|
169
|
+
`demo` cropped at `y = 1230` (1664x1230) and `wind` cropped at `y = 1250`
|
|
170
|
+
(1664x1250) over a painted room (`[80, 70, 65, 255]`): the character's white
|
|
171
|
+
dress/skirt touches the bottom border at `y = 1229` / `1249` with RGB channel
|
|
172
|
+
values above 235, so `backgroundMask` floods 31,806 px into `demo`'s dress
|
|
173
|
+
(covering `r_hip` at `[756, 1224]` RGB `[250, 241, 244]` and `l_hip` at
|
|
174
|
+
`[908, 1224]` RGB `[250, 239, 242]`) and 36,247 px into `wind`'s skirt
|
|
175
|
+
(covering `r_hip` at `[748, 1224]` RGB `[244, 245, 250]` and `l_hip` at
|
|
176
|
+
`[916, 1224]` RGB `[240, 241, 246]`), and `PAINTING_JOINT_ON_FIGURE` falsely
|
|
177
|
+
refuses both hips on the character.
|
|
178
|
+
|
|
179
|
+
Under `--form illustration`, every rule is either kept because it still predicts a
|
|
180
|
+
real downstream failure, or skipped with an explicit `SKIP` line naming the reason
|
|
181
|
+
(never a pass):
|
|
182
|
+
|
|
183
|
+
| rule | under `--form illustration` | why |
|
|
184
|
+
| --- | --- | --- |
|
|
185
|
+
| `PAINTING_SOURCE_PRESENT`, `PAINTING_SOURCE_DECODES` | **applies** | `inputs` and `assemble` read the same PNG; a missing or corrupt file fails every stage |
|
|
186
|
+
| `KEYPOINTS_*` (`KEYPOINTS_FILE_PRESENT`, `KEYPOINTS_IS_JSON`, `KEYPOINTS_SPEC_KNOWN`, `KEYPOINTS_SPACE_STATED`, `KEYPOINTS_IMAGE_SIZE`, `KEYPOINTS_PERSON_CHOSEN`, `KEYPOINTS_POSITION_INSIDE`, …) | **applies** | `propose --keypoints` runs the same reader: a keypoint file measured on an uncropped 1664x2432 canvas fails `KEYPOINTS_IMAGE_SIZE` on a 1664x1230 framed crop, and a joint whose coordinates lie past the crop edge fails `KEYPOINTS_POSITION_INSIDE` |
|
|
187
|
+
| `PAINTING_BACKGROUND_PLAIN` | **`SKIP`** | a painted room or a figure cut by the frame has non-ground border pixels (`5784 / 5784` on `demo` 1664x1230 and `5824 / 5824` on `wind` 1664x1250 over a painted room) |
|
|
188
|
+
| `PAINTING_JOINTS_STATED` | **partial (`SKIP` + check)** | the 8 standee gap-line joints (`r_wrist`, `l_wrist`, `r_hip`, `l_hip`, `r_knee`, `l_knee`, `r_ankle`, `l_ankle`) report `SKIP` because knees, ankles, hips or hands may lie outside the frame or under clothing; the rule still refuses a person that states **0 joints with a position**, because `propose --keypoints` places bones only from joints with a position |
|
|
189
|
+
| `PAINTING_JOINT_ON_FIGURE` | **partial (`SKIP` + check)** | the near-white border flood (`backgroundMask`) reports `SKIP` because a painted room is not near-white ground and white clothing at the crop edge floods into the figure (`r_hip` and `l_hip` on cropped `demo` and `wind`); the rule still checks every joint with a position against unpainted pixels (`alpha <= 8`, `OPAQUE_ALPHA_ABOVE`) and refuses a joint placed on an unpainted border or transparent hole (`inputs` also requires an opaque painting, `INPUTS_PAINTING_OPAQUE`) |
|
|
190
|
+
| `PAINTING_ARM_CLEAR` | **`SKIP`** | arm-to-hip lines cannot cross border-connected near-white background over a painted room, and an illustration without articulated arm cuts (`config.limbs`) does not require an A-pose gap (4 of 5 public examples build and pass `check` with 0 arm-gap pixels) |
|
|
191
|
+
| `PAINTING_HANDS_APART` | **`SKIP`** | the straight wrist-to-wrist line crosses the torso or painted room even when the hands are wide apart (`demo` 840 px apart, `scarf` 392 px apart: 0 background px) |
|
|
192
|
+
| `PAINTING_LEGS_APART` | **`SKIP`** | knees and ankles may lie outside the frame on a waist/thigh crop or under a skirt, and over a painted room there is no border-connected near-white background between the legs |
|
|
193
|
+
|
|
135
194
|
## A recipe that is one way, not the only way
|
|
136
195
|
|
|
137
196
|
Anyone with a different generator reads the requirements above, not this. This is
|
package/docs/STABILITY.md
CHANGED
|
@@ -70,7 +70,7 @@ defaults are the help's too, and none is guessed from the art.
|
|
|
70
70
|
| `mesh-stats` | `--config`, `--parts` (issue #566, additive: prints, writes nothing) |
|
|
71
71
|
| `remesh` | `--rig`, `--config`, `--parts`, `--part`, `--out`, and one of `--mesh` or `--delta` (issue #526); optional `--requirements`, `--idle-keys` (ctl, direct), `--page-edges` (pot, free), `--pack-shape` (rect, polygon), `--record-input-check` and `--input-check` (issue #617, additive: without them nothing `remesh` prints or writes changes) |
|
|
72
72
|
| `retune` | `--rig`, `--config`, `--parts`, `--out`; optional `--requirements`, `--idle-keys` (ctl, direct), `--page-edges` (pot, free), `--pack-shape` (rect, polygon), `--record-input-check` and `--input-check` (as for `remesh`, issue #617, additive) |
|
|
73
|
-
| `painting` | one positional path, the painting; `--keypoints`; optional `--person` |
|
|
73
|
+
| `painting` | one positional path, the painting; `--keypoints`; optional `--person`, `--form` (`standee` or `illustration`, issue #931) |
|
|
74
74
|
| `guide` | one positional argument: a rule name, `§<n>`, `§<n>.<m>` or `§<n>.<m>.<k>` |
|
|
75
75
|
| (none) | `--version`, `--help` |
|
|
76
76
|
<!-- /stable:commands -->
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rig-parts",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "AI-authored 2D character parts from one anime painting and its See-through layer decomposition: measured parts, weighted-mesh rig specs, verified through rig-c before anything is written. A CLI for agents that cannot see the image.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/paintcheck.ts
CHANGED
|
@@ -54,6 +54,29 @@
|
|
|
54
54
|
* - `PAINTING_LEGS_APART` — a gap between the two knees and a gap between the
|
|
55
55
|
* two ankles. None is legs painted as one column at that joint.
|
|
56
56
|
*
|
|
57
|
+
* **Framed illustrations (`--form illustration`, issue #931):**
|
|
58
|
+
*
|
|
59
|
+
* The default form (`--form standee`) checks a full-body standee on a plain
|
|
60
|
+
* near-white or unpainted ground. A framed illustration with a painted room or a
|
|
61
|
+
* figure cut by the crop edge fails the border and gap rules for reasons that do
|
|
62
|
+
* not touch whether it can be rigged, and near-white border flood-fill
|
|
63
|
+
* (`backgroundMask`) both fails to mark a painted room as background and floods
|
|
64
|
+
* from the crop border into white clothing on the character (`r_hip` / `l_hip`
|
|
65
|
+
* on `demo` cropped at y=1230 and `wind` cropped at y=1250). Under
|
|
66
|
+
* `--form illustration`:
|
|
67
|
+
*
|
|
68
|
+
* - `PAINTING_BACKGROUND_PLAIN`, `PAINTING_ARM_CLEAR`, `PAINTING_HANDS_APART`
|
|
69
|
+
* and `PAINTING_LEGS_APART` do not apply and report `SKIP` with the reason,
|
|
70
|
+
* never `PASS`.
|
|
71
|
+
* - `PAINTING_JOINTS_STATED` reports `SKIP` for the eight standee gap-line
|
|
72
|
+
* joints (wrists, hips, knees and ankles may lie outside the frame or under
|
|
73
|
+
* clothing), and still refuses a person that states zero joints with a
|
|
74
|
+
* position (`propose --keypoints` places bones only from joints with a
|
|
75
|
+
* position).
|
|
76
|
+
* - `PAINTING_JOINT_ON_FIGURE` reports `SKIP` for the near-white border flood
|
|
77
|
+
* and still refuses any joint with a position whose pixel is unpainted
|
|
78
|
+
* (`alpha <= OPAQUE_ALPHA_ABOVE`).
|
|
79
|
+
*
|
|
57
80
|
* Pure: the caller reads the files and hands over a raster and a person.
|
|
58
81
|
*/
|
|
59
82
|
import type { Problem } from './errors.ts';
|
|
@@ -62,18 +85,38 @@ import { NEAR_WHITE_MIN, OPAQUE_ALPHA_ABOVE } from './layers.ts';
|
|
|
62
85
|
import type { Raster } from './raster/types.ts';
|
|
63
86
|
import type { KeypointName } from './skeleton.ts';
|
|
64
87
|
|
|
88
|
+
/** The forms `painting --form` accepts: a full-body standee on a plain ground (the default) or a framed illustration. */
|
|
89
|
+
export const PAINTING_FORMS = ['standee', 'illustration'] as const;
|
|
90
|
+
export type PaintingForm = (typeof PAINTING_FORMS)[number];
|
|
91
|
+
export const DEFAULT_PAINTING_FORM: PaintingForm = 'standee';
|
|
92
|
+
|
|
93
|
+
/** Parse `--form`, returning a usage refusal detail when unknown. */
|
|
94
|
+
export function paintingFormOf(value: string | undefined): PaintingForm | string {
|
|
95
|
+
const f = value ?? DEFAULT_PAINTING_FORM;
|
|
96
|
+
return (PAINTING_FORMS as readonly string[]).includes(f) ? (f as PaintingForm) : `--form ${f}; one of ${PAINTING_FORMS.join(', ')} is required`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function isPaintingForm(v: PaintingForm | string): v is PaintingForm {
|
|
100
|
+
return (PAINTING_FORMS as readonly string[]).includes(v);
|
|
101
|
+
}
|
|
102
|
+
|
|
65
103
|
/** The joints the gap rules read, in the order a refusal lists them. */
|
|
66
104
|
export const PAINTING_JOINTS: readonly KeypointName[] = ['r_wrist', 'l_wrist', 'r_hip', 'l_hip', 'r_knee', 'l_knee', 'r_ankle', 'l_ankle'];
|
|
67
105
|
|
|
68
106
|
/** The joints that may be `occluded` with a position and still be read (issue #372): a skirt hides the hips on most clothed figures. */
|
|
69
107
|
export const PAINTING_OCCLUDABLE: readonly KeypointName[] = ['r_hip', 'l_hip'];
|
|
70
108
|
|
|
71
|
-
/** Whether the check reads joint `name` as given: observed with a position, or an occludable joint occluded with one. */
|
|
109
|
+
/** Whether the standee check reads joint `name` as given: observed with a position, or an occludable joint occluded with one. */
|
|
72
110
|
function readable(name: KeypointName, j: KeypointPerson['joints'][KeypointName]): j is KeypointPerson['joints'][KeypointName] & { at: [number, number] } {
|
|
73
111
|
if (j.at === null) return false;
|
|
74
112
|
return j.state === 'observed' || (j.state === 'occluded' && PAINTING_OCCLUDABLE.includes(name));
|
|
75
113
|
}
|
|
76
114
|
|
|
115
|
+
/** Whether the illustration check reads joint `j`: any observed or occluded joint that carries a position. */
|
|
116
|
+
function statedWithPosition(j: KeypointPerson['joints'][KeypointName]): j is KeypointPerson['joints'][KeypointName] & { state: 'observed' | 'occluded'; at: [number, number] } {
|
|
117
|
+
return j.at !== null && (j.state === 'observed' || j.state === 'occluded');
|
|
118
|
+
}
|
|
119
|
+
|
|
77
120
|
/** The four gap rules' pairs: the rule, the two joints, and what the pair is called in a line. */
|
|
78
121
|
export const PAINTING_GAPS: ReadonlyArray<readonly [string, KeypointName, KeypointName, string]> = [
|
|
79
122
|
['PAINTING_ARM_CLEAR', 'r_wrist', 'r_hip', 'right arm (r_wrist to r_hip)'],
|
|
@@ -112,8 +155,10 @@ export interface PaintingReading {
|
|
|
112
155
|
firstBorderNotGround: [number, number] | null;
|
|
113
156
|
backgroundPx: number;
|
|
114
157
|
figurePx: number;
|
|
115
|
-
/**
|
|
116
|
-
|
|
158
|
+
/** Pixels with alpha above {@link OPAQUE_ALPHA_ABOVE}. */
|
|
159
|
+
opaquePx: number;
|
|
160
|
+
/** In `standee`, every observed joint and occluded hip with a position (`onFigure` = not border-connected ground); in `illustration`, every joint with a position (`onFigure` = alpha above {@link OPAQUE_ALPHA_ABOVE}). */
|
|
161
|
+
joints: Array<{ name: KeypointName; state: 'observed' | 'occluded'; at: [number, number]; pixel: [number, number]; alpha: number; onFigure: boolean }>;
|
|
117
162
|
/** The gap readings whose two joints are both read as given (observed with a position, or an occluded hip with one). */
|
|
118
163
|
gaps: GapReading[];
|
|
119
164
|
}
|
|
@@ -172,17 +217,20 @@ export function linePixels(a: readonly [number, number], b: readonly [number, nu
|
|
|
172
217
|
}
|
|
173
218
|
|
|
174
219
|
/** Read the painting against the person's joints: every figure the rules decide on, before any rule decides. */
|
|
175
|
-
export function readPainting(r: Raster, person: KeypointPerson): PaintingReading {
|
|
220
|
+
export function readPainting(r: Raster, person: KeypointPerson, form: PaintingForm = DEFAULT_PAINTING_FORM): PaintingReading {
|
|
176
221
|
const { width: w, height: h } = r;
|
|
177
222
|
const bg = backgroundMask(r);
|
|
178
223
|
let border = 0;
|
|
179
224
|
let borderNotGround = 0;
|
|
180
225
|
let firstBorderNotGround: [number, number] | null = null;
|
|
226
|
+
let opaquePx = 0;
|
|
181
227
|
for (let y = 0; y < h; y++) {
|
|
182
228
|
for (let x = 0; x < w; x++) {
|
|
229
|
+
const idx = y * w + x;
|
|
230
|
+
if (r.data[idx * 4 + 3] > OPAQUE_ALPHA_ABOVE) opaquePx++;
|
|
183
231
|
if (y !== 0 && y !== h - 1 && x !== 0 && x !== w - 1) continue;
|
|
184
232
|
border++;
|
|
185
|
-
if (!isGround(r,
|
|
233
|
+
if (!isGround(r, idx)) {
|
|
186
234
|
borderNotGround++;
|
|
187
235
|
firstBorderNotGround ??= [x, y];
|
|
188
236
|
}
|
|
@@ -192,28 +240,62 @@ export function readPainting(r: Raster, person: KeypointPerson): PaintingReading
|
|
|
192
240
|
for (const v of bg) backgroundPx += v;
|
|
193
241
|
const joints: PaintingReading['joints'] = [];
|
|
194
242
|
for (const [name, j] of Object.entries(person.joints) as Array<[KeypointName, KeypointPerson['joints'][KeypointName]]>) {
|
|
243
|
+
if (form === 'illustration') {
|
|
244
|
+
if (!statedWithPosition(j)) continue;
|
|
245
|
+
const pixel = pixelOf(j.at, w, h);
|
|
246
|
+
const alpha = r.data[(pixel[1] * w + pixel[0]) * 4 + 3];
|
|
247
|
+
joints.push({ name, state: j.state, at: j.at, pixel, alpha, onFigure: alpha > OPAQUE_ALPHA_ABOVE });
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
195
250
|
if (!readable(name, j)) continue;
|
|
196
251
|
const pixel = pixelOf(j.at, w, h);
|
|
197
|
-
|
|
252
|
+
const alpha = r.data[(pixel[1] * w + pixel[0]) * 4 + 3];
|
|
253
|
+
joints.push({ name, state: j.state === 'observed' ? 'observed' : 'occluded', at: j.at, pixel, alpha, onFigure: bg[pixel[1] * w + pixel[0]] === 0 });
|
|
198
254
|
}
|
|
199
255
|
const gaps: GapReading[] = [];
|
|
200
|
-
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
256
|
+
if (form === 'standee') {
|
|
257
|
+
for (const [rule, a, b, what] of PAINTING_GAPS) {
|
|
258
|
+
const ja = person.joints[a];
|
|
259
|
+
const jb = person.joints[b];
|
|
260
|
+
if (!readable(a, ja) || !readable(b, jb)) continue;
|
|
261
|
+
const line = linePixels(ja.at, jb.at, w, h);
|
|
262
|
+
let background = 0;
|
|
263
|
+
for (const [x, y] of line) background += bg[y * w + x];
|
|
264
|
+
gaps.push({ rule, a, b, what, from: ja.at, to: jb.at, samples: line.length, background });
|
|
265
|
+
}
|
|
208
266
|
}
|
|
209
|
-
return { width: w, height: h, border, borderNotGround, firstBorderNotGround, backgroundPx, figurePx: w * h - backgroundPx, joints, gaps };
|
|
267
|
+
return { width: w, height: h, border, borderNotGround, firstBorderNotGround, backgroundPx, figurePx: w * h - backgroundPx, opaquePx, joints, gaps };
|
|
210
268
|
}
|
|
211
269
|
|
|
212
270
|
const pt = (p: readonly [number, number]): string => `[${p[0]}, ${p[1]}]`;
|
|
213
271
|
|
|
214
272
|
/** The refusals a reading earns, every one at once, in the order the module's comment lists the rules. */
|
|
215
|
-
export function paintingProblems(
|
|
273
|
+
export function paintingProblems(
|
|
274
|
+
reading: PaintingReading,
|
|
275
|
+
person: KeypointPerson,
|
|
276
|
+
painting: string,
|
|
277
|
+
keypoints: string,
|
|
278
|
+
form: PaintingForm = DEFAULT_PAINTING_FORM,
|
|
279
|
+
): Problem[] {
|
|
216
280
|
const problems: Problem[] = [];
|
|
281
|
+
if (form === 'illustration') {
|
|
282
|
+
if (reading.joints.length === 0) {
|
|
283
|
+
problems.push({
|
|
284
|
+
code: 'PAINTING_JOINTS_STATED',
|
|
285
|
+
object: `${keypoints} person "${person.id}"`,
|
|
286
|
+
detail: 'states 0 joints with a position; at least one observed or occluded joint with a position is required — propose --keypoints places bones only from joints that carry a position',
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
for (const j of reading.joints) {
|
|
290
|
+
if (j.onFigure) continue;
|
|
291
|
+
problems.push({
|
|
292
|
+
code: 'PAINTING_JOINT_ON_FIGURE',
|
|
293
|
+
object: `${keypoints} person "${person.id}" joint ${j.name}`,
|
|
294
|
+
detail: `is ${j.state === 'occluded' ? 'occluded ' : ''}at ${pt(j.at)}, and pixel ${pt(j.pixel)} is unpainted (alpha ${j.alpha} <= ${OPAQUE_ALPHA_ABOVE}); a joint on painted canvas (alpha above ${OPAQUE_ALPHA_ABOVE}) is required — a file measured on another painting, or a joint placed on an empty border`,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
return problems;
|
|
298
|
+
}
|
|
217
299
|
if (reading.borderNotGround > 0) {
|
|
218
300
|
const f = reading.firstBorderNotGround as [number, number];
|
|
219
301
|
problems.push({
|
|
@@ -253,8 +335,25 @@ export function paintingProblems(reading: PaintingReading, person: KeypointPerso
|
|
|
253
335
|
return problems;
|
|
254
336
|
}
|
|
255
337
|
|
|
256
|
-
/** The lines a reading prints, before any refusal: the figure, every joint and every gap, each with its numbers. */
|
|
257
|
-
export function paintingLines(reading: PaintingReading): string[] {
|
|
338
|
+
/** The lines a reading prints, before any refusal: the figure, every joint and every gap (or in `illustration`, the `SKIP` lines), each with its numbers. */
|
|
339
|
+
export function paintingLines(reading: PaintingReading, form: PaintingForm = DEFAULT_PAINTING_FORM): string[] {
|
|
340
|
+
if (form === 'illustration') {
|
|
341
|
+
const unpainted = reading.width * reading.height - reading.opaquePx;
|
|
342
|
+
const on = reading.joints.filter((j) => j.onFigure).length;
|
|
343
|
+
const observed = reading.joints.filter((j) => j.state === 'observed').length;
|
|
344
|
+
const occluded = reading.joints.filter((j) => j.state === 'occluded').map((j) => j.name);
|
|
345
|
+
const occ = occluded.length === 0 ? '' : `, ${occluded.length} occluded with a position (${occluded.join(', ')})`;
|
|
346
|
+
return [
|
|
347
|
+
` painting ${reading.width}x${reading.height} (form: illustration): opaque ${reading.opaquePx} px, unpainted ${unpainted} px; border ${reading.border} px, ${reading.borderNotGround} not near-white or unpainted`,
|
|
348
|
+
` joints: ${observed} observed${occ}, ${on} on painted canvas${on === reading.joints.length ? '' : ` (unpainted: ${reading.joints.filter((j) => !j.onFigure).map((j) => j.name).join(', ')})`}`,
|
|
349
|
+
` SKIP PAINTING_BACKGROUND_PLAIN: framed illustration; a painted room or a figure cut by the frame has non-ground border pixels (${reading.borderNotGround} of ${reading.border} border pixels not near-white or unpainted)`,
|
|
350
|
+
` SKIP PAINTING_JOINTS_STATED (standee gap joints): framed illustration; wrists, hips, knees and ankles may lie outside the frame or under clothing (${reading.joints.length} joint(s) stated with a position)`,
|
|
351
|
+
` SKIP PAINTING_JOINT_ON_FIGURE (near-white border flood): framed illustration; a painted room is not near-white ground and white clothing at the crop edge floods into the figure (opaque pixel alpha > ${OPAQUE_ALPHA_ABOVE} is still checked)`,
|
|
352
|
+
' SKIP PAINTING_ARM_CLEAR: framed illustration; arm-to-hip lines do not cross plain border-connected background over a painted room or a pose with arms against the body',
|
|
353
|
+
' SKIP PAINTING_HANDS_APART: framed illustration; the wrist-to-wrist line crosses the torso or painted room rather than plain border-connected background',
|
|
354
|
+
' SKIP PAINTING_LEGS_APART: framed illustration; knees and ankles may lie outside the frame or over a painted room without plain border-connected background',
|
|
355
|
+
];
|
|
356
|
+
}
|
|
258
357
|
const out = [
|
|
259
358
|
` painting ${reading.width}x${reading.height}: background ${reading.backgroundPx} px, figure ${reading.figurePx} px; border ${reading.border} px, ${reading.borderNotGround} not background-coloured`,
|
|
260
359
|
];
|
|
@@ -271,7 +370,13 @@ export function paintingLines(reading: PaintingReading): string[] {
|
|
|
271
370
|
}
|
|
272
371
|
|
|
273
372
|
/** Read and judge: the lines to print and every refusal, which the caller prints before it throws them at once. */
|
|
274
|
-
export function checkPainting(
|
|
275
|
-
|
|
276
|
-
|
|
373
|
+
export function checkPainting(
|
|
374
|
+
r: Raster,
|
|
375
|
+
person: KeypointPerson,
|
|
376
|
+
painting: string,
|
|
377
|
+
keypoints: string,
|
|
378
|
+
form: PaintingForm = DEFAULT_PAINTING_FORM,
|
|
379
|
+
): { lines: string[]; problems: Problem[] } {
|
|
380
|
+
const reading = readPainting(r, person, form);
|
|
381
|
+
return { lines: paintingLines(reading, form), problems: paintingProblems(reading, person, painting, keypoints, form) };
|
|
277
382
|
}
|