@rig-c/core 2.42.0 → 2.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/docs/AUTHORING.md +17 -2
- package/package.json +1 -1
- package/src/cli/core_commands.ts +7 -0
- package/src/cli/shared.ts +13 -1
- package/src/inspect.ts +58 -6
package/README.md
CHANGED
|
@@ -416,7 +416,8 @@ and over it any of `bones` (origin to tip, a bone of length 0 marked as one),
|
|
|
416
416
|
(each `invariants.axes` line and the bones held to it), at the setup pose, at
|
|
417
417
|
`--animation <name> --at <s[,s…]>` — one picture, or a sheet — or zoomed with
|
|
418
418
|
`--zoom <bone|slot>[:pad]` or `--viewport x,y,w,h`; `--bone`/`--hide-bone` pick the
|
|
419
|
-
bones by glob,
|
|
419
|
+
bones by glob, `--overlay-slot`/`--hide-overlay-slot` the slots whose mesh, hull,
|
|
420
|
+
weights and number are drawn over the whole painting, and `--compare <dir>` draws a second build beside the first on one
|
|
420
421
|
transform, to see what a change moved. Beside the PNG it writes a legend:
|
|
421
422
|
the world-to-pixel transform both ways, what each colour means, and every bone's
|
|
422
423
|
origin and tip in world units and in pixels, so a position read off the picture goes
|
package/docs/AUTHORING.md
CHANGED
|
@@ -10094,6 +10094,7 @@ pixel (`IN09`).
|
|
|
10094
10094
|
| Is this axis declared, and held? | `--show art,declared,bones` | each `invariants.axes` line (§3.7) through its two bones' origins, drawn across the whole tile, and the bones it holds drawn in their own colour; the legend states each held bone's world x axis and how far off the line it lies, as `A51` measures it, at the pose drawn |
|
|
10095
10095
|
| Did it break during the move? | any of the above `--animation <name> --at <s,s,…>` | one tile per time, left to right on one grid, each labelled with its time and frame |
|
|
10096
10096
|
| What does this bone move? | `--show art,mesh,weights:<bone> --dim` | the bone's weight over every region and mesh it moves, as a heat: a pixel whose centre lies in a triangle is painted the ramp's colour at the blend of its corners' weights — blue near 0, yellow at 0.5, red at 1 (the stops are in the legend) — and a weight of 0 is not painted, so the art shows where the bone does not reach. A region and an unweighted mesh are moved whole by their slot's bone. The weights are read off `skeleton.json`, the file both posers skin from; a bone that weights nothing paints nothing and the run says so on a line of its own |
|
|
10097
|
+
| Does this part's edge follow that bone? | `--show art,weights:<bone>,mesh --overlay-slot <part> --zoom slot:<part>` | the part's own triangles and the bone's weight over them alone, over the whole painting. Where slots stack — a neck under the face, the hair, a collar — every mesh crossing the box is drawn and the heat is the union of every slot the bone moves, so the part's edge has no pixel of its own until the others are left out. The legend's `meshes[]` gives the part's weight for the bone at every vertex, so "does the top row follow the head" is read as numbers as well as colour |
|
|
10097
10098
|
| How is this part triangulated? | `--show art,mesh,hull --zoom slot:<slot>` | each mesh's triangle edges, and over them its hull — vertex 0 to 1 to … to `hull − 1` and back. Regions are not drawn: a region is not a mesh. The legend lists every vertex in world units and in pixels |
|
|
10098
10099
|
| Which is drawn over which? | `--show art,slots` | each slot that shows a region or mesh numbered by its place in the draw order **at the pose drawn** (0 is drawn first), on a white box at the centre of its attachment's box — so a `drawOrder` key (§4.7) shows from the first frame `render` draws it on |
|
|
10099
10100
|
| Did this change move anything? | `--compare <dir>` beside any view | the same view of a second build beside the first, at the same times, on **one** world box and one transform, each half labelled `A`/`B` with the poser that posed it. A mark on a different pixel in the two halves is at a different place in the two rigs |
|
|
@@ -10128,7 +10129,7 @@ pixel (`IN09`).
|
|
|
10128
10129
|
- `--dim` fades the art toward white (each channel keeps 0.4 of its distance from
|
|
10129
10130
|
white) so what is drawn over it reads.
|
|
10130
10131
|
|
|
10131
|
-
### 13.3 Labels, and which bones are drawn
|
|
10132
|
+
### 13.3 Labels, and which bones and slots are drawn
|
|
10132
10133
|
|
|
10133
10134
|
- **One label per pixel.** Bones whose origins land on one pixel — a `_ctl` bone and
|
|
10134
10135
|
its identity child, a chain's joint — are one label: the first by declaration
|
|
@@ -10149,6 +10150,16 @@ pixel (`IN09`).
|
|
|
10149
10150
|
the shell. The filter is on `bones`, `axes` and `names`; every bone stays in the
|
|
10150
10151
|
legend, and a left-out one says which flag left it out in `undrawn`. A pattern
|
|
10151
10152
|
that matches no bone is refused naming the bones there are.
|
|
10153
|
+
- **`--overlay-slot <glob[,glob…]>`** draws the per-slot views — `mesh`, `hull`,
|
|
10154
|
+
`weights:<bone>` and `slots` — only for the slots a pattern matches, and
|
|
10155
|
+
**`--hide-overlay-slot <glob[,glob…]>`** for none of the ones it matches, applied
|
|
10156
|
+
after `--overlay-slot`; the patterns are globs, as `--bone`'s are. The **art is not
|
|
10157
|
+
filtered**: the painting is what the edge is read against, and `--dim` already
|
|
10158
|
+
quiets it. A slot left out is absent from `meshes[]` and `slots[]`, and a slot kept
|
|
10159
|
+
keeps its number — its place in the whole draw order, not among the slots drawn. A
|
|
10160
|
+
pattern that matches no slot is refused naming the slots there are. It is not
|
|
10161
|
+
`--slot`: on `render` that flag draws a subset of the *art*, by exact name, which is
|
|
10162
|
+
the opposite of what this keeps whole.
|
|
10152
10163
|
|
|
10153
10164
|
### 13.4 The legend
|
|
10154
10165
|
|
|
@@ -10186,6 +10197,8 @@ pixel (`IN09`).
|
|
|
10186
10197
|
- `weights` — with `weights:<bone>`: the bone, the ramp's stops as RGBA, the floor
|
|
10187
10198
|
below which nothing is painted, and how a pixel's weight is read.
|
|
10188
10199
|
- `boneFilter` — `--bone` and `--hide-bone` as given.
|
|
10200
|
+
- `slotFilter` — `--overlay-slot` and `--hide-overlay-slot` as given; with it,
|
|
10201
|
+
`meshes[]` and `slots[]` list only the slots it keeps.
|
|
10189
10202
|
- `builds` — with `--compare`: each build (`A`, `B`), the poser that posed it, and
|
|
10190
10203
|
its `skeleton.json`'s sha256. `poser` is build `A`'s.
|
|
10191
10204
|
|
|
@@ -10201,7 +10214,9 @@ or past the end, `--at` without `--animation` and the reverse, `--zoom` beside
|
|
|
10201
10214
|
`--viewport`, `--show declared` on a skeleton with no `skeleton.model.json`
|
|
10202
10215
|
beside it whose `spine.sha256` is its own — a Spine export states no rig invariants —
|
|
10203
10216
|
`weights` with no bone or with a bone the build lacks (the bones listed), a
|
|
10204
|
-
`--bone`/`--hide-bone` pattern that matches no bone (the bones listed),
|
|
10217
|
+
`--bone`/`--hide-bone` pattern that matches no bone (the bones listed), an
|
|
10218
|
+
`--overlay-slot`/`--hide-overlay-slot` pattern that matches no slot (the slots
|
|
10219
|
+
listed), and, with
|
|
10205
10220
|
`--compare`, any of these that holds of the second build, said as `the --compare
|
|
10206
10221
|
build: …`.
|
|
10207
10222
|
A rig that declares no `invariants.axes` is not refused: the picture draws nothing
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rig-c/core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.43.0",
|
|
4
4
|
"description": "AI-authored 2D rigging and animation, verified before it is written. rigc compiles a rig spec into skeleton data in the Spine 4.3 format, gates it with named assertions held to a spine-core round trip's verdicts, and emits nothing that fails. Ships as an agent skill.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/cli/core_commands.ts
CHANGED
|
@@ -680,6 +680,12 @@ export function cmdInspect(flags: Record<string, string>): void {
|
|
|
680
680
|
const include = readBonePatterns('bone', flags.bone);
|
|
681
681
|
const exclude = readBonePatterns('hide-bone', flags['hide-bone']);
|
|
682
682
|
const boneFilter = include === undefined && exclude === undefined ? undefined : { ...(include === undefined ? {} : { include }), ...(exclude === undefined ? {} : { exclude }) };
|
|
683
|
+
const slotInclude = readBonePatterns('overlay-slot', flags['overlay-slot'], 'neck,hair_*');
|
|
684
|
+
const slotExclude = readBonePatterns('hide-overlay-slot', flags['hide-overlay-slot'], 'neck,hair_*');
|
|
685
|
+
const slotFilter =
|
|
686
|
+
slotInclude === undefined && slotExclude === undefined
|
|
687
|
+
? undefined
|
|
688
|
+
: { ...(slotInclude === undefined ? {} : { include: slotInclude }), ...(slotExclude === undefined ? {} : { exclude: slotExclude }) };
|
|
683
689
|
const times = readTimes(flags.at);
|
|
684
690
|
const fps = readPositiveNumber(flags, 'fps', INSPECT_DEFAULT_FPS, 1);
|
|
685
691
|
const maxSide = readPositiveNumber(flags, 'max', 256, 16);
|
|
@@ -733,6 +739,7 @@ export function cmdInspect(flags: Record<string, string>): void {
|
|
|
733
739
|
show,
|
|
734
740
|
...(weightsBone === undefined ? {} : { weightsBone }),
|
|
735
741
|
...(boneFilter === undefined ? {} : { boneFilter }),
|
|
742
|
+
...(slotFilter === undefined ? {} : { slotFilter }),
|
|
736
743
|
dim: flags.dim !== undefined,
|
|
737
744
|
animation,
|
|
738
745
|
times,
|
package/src/cli/shared.ts
CHANGED
|
@@ -1088,6 +1088,15 @@ const FLAG_MEANINGS: Record<string, string> = {
|
|
|
1088
1088
|
'hide-bone':
|
|
1089
1089
|
"draw none of the bones these globs match — `--bone` the other way round, applied after it, so `--bone 'bang_*' " +
|
|
1090
1090
|
"--hide-bone '*_ctl'` is the fringe chains without their control bones",
|
|
1091
|
+
'overlay-slot':
|
|
1092
|
+
"draw the per-slot views — mesh, hull, weights:<bone> and slots — only for the slots these globs match (`*` any run of " +
|
|
1093
|
+
"characters, `?` one, the whole name; comma-separated); the art is every slot's still, since it is what the geometry is read " +
|
|
1094
|
+
"against. Not `--slot`: that flag draws a subset of the art on `render`, by exact name. The legend's meshes[] and slots[] list " +
|
|
1095
|
+
'only the slots kept, a slot number stays its place in the whole draw order, and a pattern that matches no slot is refused ' +
|
|
1096
|
+
'naming the slots there are',
|
|
1097
|
+
'hide-overlay-slot':
|
|
1098
|
+
"draw none of the per-slot views for the slots these globs match — `--overlay-slot` the other way round, applied after it, " +
|
|
1099
|
+
"so `--overlay-slot 'hair_*' --hide-overlay-slot hair_back` is the hair but its back layer",
|
|
1091
1100
|
compare:
|
|
1092
1101
|
'a second build to draw beside --candidate, the same view at the same times on one world box and one transform — the union ' +
|
|
1093
1102
|
'of both framings unless --zoom or --viewport pins it — each half labelled with its build and the poser that posed it. A ' +
|
|
@@ -1194,6 +1203,8 @@ const FLAG_VALUES: Record<string, string> = {
|
|
|
1194
1203
|
zoom: '<name>[:<pad>]',
|
|
1195
1204
|
bone: '<glob[,glob…]>',
|
|
1196
1205
|
'hide-bone': '<glob[,glob…]>',
|
|
1206
|
+
'overlay-slot': '<glob[,glob…]>',
|
|
1207
|
+
'hide-overlay-slot': '<glob[,glob…]>',
|
|
1197
1208
|
compare: '<dir|skeleton.json>',
|
|
1198
1209
|
record: '<result.json>',
|
|
1199
1210
|
ballot: '<ballot.html>',
|
|
@@ -1582,9 +1593,10 @@ export const COMMANDS: CommandDoc[] = [
|
|
|
1582
1593
|
'rigc inspect … --animation <name> --at <s[,s…]> [--fps 12] [--skin <name>] (one time, or a sheet of several on one grid)',
|
|
1583
1594
|
'rigc inspect … --zoom <bone|slot>[:<pad>] | --viewport <x,y,w,h> (a box, in world units)',
|
|
1584
1595
|
"rigc inspect … --bone 'bang_*' --hide-bone '*_ctl' (which bones bones, axes and names draw)",
|
|
1596
|
+
'rigc inspect … --overlay-slot neck --hide-overlay-slot <glob> (which slots mesh, hull, weights and slots draw; the art stays whole)',
|
|
1585
1597
|
'rigc inspect … --compare <dir | skeleton.json> (a second build beside the first, on one transform)',
|
|
1586
1598
|
],
|
|
1587
|
-
flags: ['candidate', 'atlas', 'show', 'dim', 'animation', 'at', 'fps', 'skin', 'zoom', 'viewport', 'bone', 'hide-bone', 'compare', 'max', 'poser', 'out'],
|
|
1599
|
+
flags: ['candidate', 'atlas', 'show', 'dim', 'animation', 'at', 'fps', 'skin', 'zoom', 'viewport', 'bone', 'hide-bone', 'overlay-slot', 'hide-overlay-slot', 'compare', 'max', 'poser', 'out'],
|
|
1588
1600
|
overrides: {
|
|
1589
1601
|
animation: { meaning: 'the animation --at reads its times from (default: the setup pose, with no --at)' },
|
|
1590
1602
|
fps: { meaning: `the frame grid --at is read on, which is the grid \`render --fps\` draws (default ${INSPECT_DEFAULT_FPS})` },
|
package/src/inspect.ts
CHANGED
|
@@ -170,6 +170,18 @@ export interface BoneFilter {
|
|
|
170
170
|
exclude?: readonly string[];
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
+
/**
|
|
174
|
+
* `--overlay-slot` / `--hide-overlay-slot`: which slots the per-slot views
|
|
175
|
+
* (`mesh`, `hull`, `weights:<bone>`, `slots`) draw, by glob. The art is not
|
|
176
|
+
* filtered — it is what the geometry is read against.
|
|
177
|
+
*/
|
|
178
|
+
export interface SlotFilter {
|
|
179
|
+
/** Draw only the slots some pattern matches; absent draws every slot. */
|
|
180
|
+
include?: readonly string[];
|
|
181
|
+
/** Draw none of the slots a pattern matches — applied after `include`. */
|
|
182
|
+
exclude?: readonly string[];
|
|
183
|
+
}
|
|
184
|
+
|
|
173
185
|
/** One `rigc inspect` invocation, its flags read but not yet checked against the build. */
|
|
174
186
|
export interface InspectRequest {
|
|
175
187
|
show: readonly ShowWord[];
|
|
@@ -177,6 +189,8 @@ export interface InspectRequest {
|
|
|
177
189
|
weightsBone?: string;
|
|
178
190
|
/** Which bones `bones`, `axes` and `names` draw; absent is every one. */
|
|
179
191
|
boneFilter?: BoneFilter;
|
|
192
|
+
/** Which slots `mesh`, `hull`, `weights` and `slots` draw; absent is every one. */
|
|
193
|
+
slotFilter?: SlotFilter;
|
|
180
194
|
dim: boolean;
|
|
181
195
|
/** `null` is the setup pose. */
|
|
182
196
|
animation: string | null;
|
|
@@ -232,6 +246,8 @@ export interface InspectPlant {
|
|
|
232
246
|
noAvoid?: boolean;
|
|
233
247
|
/** Draw every bone whatever `--bone`/`--hide-bone` say. */
|
|
234
248
|
ignoreFilter?: boolean;
|
|
249
|
+
/** Draw every slot's geometry whatever `--overlay-slot`/`--hide-overlay-slot` say. */
|
|
250
|
+
ignoreSlotFilter?: boolean;
|
|
235
251
|
/** Draw the first build in both halves of a `--compare`. */
|
|
236
252
|
compareFirstTwice?: boolean;
|
|
237
253
|
}
|
|
@@ -283,7 +299,7 @@ export interface LegendLabel {
|
|
|
283
299
|
overlaps: boolean;
|
|
284
300
|
}
|
|
285
301
|
|
|
286
|
-
/** One region or mesh the frame shows, when `mesh`, `hull` or `weights` is drawn. */
|
|
302
|
+
/** One region or mesh the frame shows, when `mesh`, `hull` or `weights` is drawn — and, with a slot filter, that it keeps. */
|
|
287
303
|
export interface LegendMesh {
|
|
288
304
|
slot: string;
|
|
289
305
|
attachment: string;
|
|
@@ -367,6 +383,8 @@ export interface InspectLegend {
|
|
|
367
383
|
| { kind: 'viewport' };
|
|
368
384
|
/** `--bone` / `--hide-bone` as given — each bone they leave out says so in its `undrawn`. */
|
|
369
385
|
boneFilter?: { include: string[] | null; exclude: string[] | null };
|
|
386
|
+
/** `--overlay-slot` / `--hide-overlay-slot` as given — `meshes[]` and `slots[]` then list only the slots they keep. */
|
|
387
|
+
slotFilter?: { include: string[] | null; exclude: string[] | null };
|
|
370
388
|
/** With `weights:<bone>`: the bone, the ramp a weight is painted with, and how a pixel's weight is read. */
|
|
371
389
|
weights?: { bone: string; ramp: Array<{ weight: number; rgba: number[] }>; floor: number; reading: string };
|
|
372
390
|
/** With `--compare`: each build, the poser that posed it and its `skeleton.json`'s sha256; a tile names its own in `build`. */
|
|
@@ -434,10 +452,10 @@ function showSpelled(request: InspectRequest, weightsBone: string | undefined):
|
|
|
434
452
|
* characters and `?` one, matched against a bone's whole name. Checked against
|
|
435
453
|
* the build later, where a pattern that matches no bone is refused.
|
|
436
454
|
*/
|
|
437
|
-
export function readBonePatterns(flag: string, raw: string | undefined): string[] | undefined {
|
|
455
|
+
export function readBonePatterns(flag: string, raw: string | undefined, example = 'bang_*,*_ctl'): string[] | undefined {
|
|
438
456
|
if (raw === undefined) return undefined;
|
|
439
457
|
const patterns = raw.split(',').map((p) => p.trim());
|
|
440
|
-
if (patterns.some((p) => p === '')) throw new InspectError(`--${flag} ${JSON.stringify(raw)}: an empty pattern; it takes globs, comma-separated, e.g. --${flag} '
|
|
458
|
+
if (patterns.some((p) => p === '')) throw new InspectError(`--${flag} ${JSON.stringify(raw)}: an empty pattern; it takes globs, comma-separated, e.g. --${flag} '${example}'`);
|
|
441
459
|
return patterns;
|
|
442
460
|
}
|
|
443
461
|
|
|
@@ -776,6 +794,31 @@ function filterOut(bones: ReadonlyArray<{ name: string }>, filter: BoneFilter |
|
|
|
776
794
|
return out;
|
|
777
795
|
}
|
|
778
796
|
|
|
797
|
+
/**
|
|
798
|
+
* `--overlay-slot` / `--hide-overlay-slot` against one build's slots: the slots
|
|
799
|
+
* whose geometry is not drawn. A pattern matching no slot is refused naming the
|
|
800
|
+
* slots there are — checked even when `apply` is off, so a mutant that ignores
|
|
801
|
+
* the filter still refuses a typo.
|
|
802
|
+
*/
|
|
803
|
+
function slotsLeftOut(slots: ReadonlyArray<{ name: string }>, filter: SlotFilter | undefined, apply: boolean): Set<string> {
|
|
804
|
+
const out = new Set<string>();
|
|
805
|
+
if (filter === undefined) return out;
|
|
806
|
+
const names = slots.map((s) => s.name);
|
|
807
|
+
const matchers = (flag: string, patterns: readonly string[]): Array<(name: string) => boolean> =>
|
|
808
|
+
patterns.map((p) => {
|
|
809
|
+
const m = globMatcher(p);
|
|
810
|
+
if (!names.some(m)) throw new InspectError(`--${flag} ${JSON.stringify(p)} matches no slot; the slots are [${names.join(', ')}]`);
|
|
811
|
+
return m;
|
|
812
|
+
});
|
|
813
|
+
const include = filter.include === undefined ? null : matchers('overlay-slot', filter.include);
|
|
814
|
+
const exclude = filter.exclude === undefined ? null : matchers('hide-overlay-slot', filter.exclude);
|
|
815
|
+
if (!apply) return out;
|
|
816
|
+
for (const name of names) {
|
|
817
|
+
if ((include !== null && !include.some((m) => m(name))) || (exclude !== null && exclude.some((m) => m(name)))) out.add(name);
|
|
818
|
+
}
|
|
819
|
+
return out;
|
|
820
|
+
}
|
|
821
|
+
|
|
779
822
|
// ---------------------------------------------------------------------------
|
|
780
823
|
// the view
|
|
781
824
|
// ---------------------------------------------------------------------------
|
|
@@ -970,6 +1013,7 @@ function view(builds: readonly Gathered[], request: InspectRequest, plant: Inspe
|
|
|
970
1013
|
}
|
|
971
1014
|
}
|
|
972
1015
|
const filtered = builds.map((b) => aboutBuild(b.tag, () => filterOut(b.bones, request.boneFilter, plant.ignoreFilter !== true)));
|
|
1016
|
+
const slotsOut = builds.map((b) => aboutBuild(b.tag, () => slotsLeftOut(b.slots, request.slotFilter, plant.ignoreSlotFilter !== true)));
|
|
973
1017
|
// The world box and its pixels: one for every tile of both builds, so a sheet's tiles and a compare's halves overlay.
|
|
974
1018
|
let viewport: Viewport;
|
|
975
1019
|
let framing: InspectLegend['framing'];
|
|
@@ -1034,19 +1078,20 @@ function view(builds: readonly Gathered[], request: InspectRequest, plant: Inspe
|
|
|
1034
1078
|
const row = compare ? Math.floor(k / pairs) : Math.floor(k / columns);
|
|
1035
1079
|
const x0 = gap + column * (width + gap);
|
|
1036
1080
|
const y0 = gap + row * (height + gap);
|
|
1037
|
-
const drawnTile = drawTile(build, build.poses[k], filtered[bi], { x0, y0, viewport, sheet, compare, weightsBone, first: k === 0 && bi === 0 }, request, plant, notes);
|
|
1081
|
+
const drawnTile = drawTile(build, build.poses[k], filtered[bi], slotsOut[bi], { x0, y0, viewport, sheet, compare, weightsBone, first: k === 0 && bi === 0 }, request, plant, notes);
|
|
1038
1082
|
if (drawnTile.weighted) weighted[bi] = true;
|
|
1039
1083
|
if (drawnTile.anyMesh) anyMesh = true;
|
|
1040
1084
|
for (let y = 0; y < height; y++) plate.data.set(drawnTile.tile.data.subarray(y * width * 4, (y + 1) * width * 4), ((y0 + y) * plate.width + x0) * 4);
|
|
1041
1085
|
tiles.push(drawnTile.legend);
|
|
1042
1086
|
}
|
|
1043
1087
|
}
|
|
1088
|
+
// With a slot filter the sentence says the filter is part of why: the same build unfiltered may paint.
|
|
1044
1089
|
if (weightsBone !== undefined) {
|
|
1045
1090
|
for (const [bi, b] of builds.entries()) {
|
|
1046
|
-
if (!weighted[bi]) notes.push(`weights: bone "${weightsBone}" weights no vertex of any region or mesh ${compare ? `build ${b.tag} ` : ''}shows here — nothing painted`);
|
|
1091
|
+
if (!weighted[bi]) notes.push(`weights: bone "${weightsBone}" weights no vertex of any region or mesh${request.slotFilter === undefined ? '' : ' of the slots the filter keeps'} ${compare ? `build ${b.tag} ` : ''}shows here — nothing painted`);
|
|
1047
1092
|
}
|
|
1048
1093
|
}
|
|
1049
|
-
if ((request.show.includes('mesh') || request.show.includes('hull')) && !anyMesh) notes.push(
|
|
1094
|
+
if ((request.show.includes('mesh') || request.show.includes('hull')) && !anyMesh) notes.push(`mesh/hull: no slot${request.slotFilter === undefined ? '' : ' the filter keeps'} shows a mesh here — nothing drawn for it`);
|
|
1050
1095
|
const legend: InspectLegend = {
|
|
1051
1096
|
spec: INSPECT_SPEC,
|
|
1052
1097
|
poser: builds[0].poser,
|
|
@@ -1059,6 +1104,9 @@ function view(builds: readonly Gathered[], request: InspectRequest, plant: Inspe
|
|
|
1059
1104
|
...(request.boneFilter === undefined
|
|
1060
1105
|
? {}
|
|
1061
1106
|
: { boneFilter: { include: request.boneFilter.include === undefined ? null : [...request.boneFilter.include], exclude: request.boneFilter.exclude === undefined ? null : [...request.boneFilter.exclude] } }),
|
|
1107
|
+
...(request.slotFilter === undefined
|
|
1108
|
+
? {}
|
|
1109
|
+
: { slotFilter: { include: request.slotFilter.include === undefined ? null : [...request.slotFilter.include], exclude: request.slotFilter.exclude === undefined ? null : [...request.slotFilter.exclude] } }),
|
|
1062
1110
|
...(weightsBone === undefined
|
|
1063
1111
|
? {}
|
|
1064
1112
|
: {
|
|
@@ -1103,6 +1151,7 @@ function drawTile(
|
|
|
1103
1151
|
build: Gathered,
|
|
1104
1152
|
pose: { frame: Frame; index: number },
|
|
1105
1153
|
filtered: Map<string, string>,
|
|
1154
|
+
slotsOut: ReadonlySet<string>,
|
|
1106
1155
|
place: TilePlace,
|
|
1107
1156
|
request: InspectRequest,
|
|
1108
1157
|
plant: InspectPlant,
|
|
@@ -1164,6 +1213,7 @@ function drawTile(
|
|
|
1164
1213
|
legendTile.meshes = [];
|
|
1165
1214
|
const [mx, my] = plant.meshShift ?? [0, 0];
|
|
1166
1215
|
for (const shown of frame.attachments ?? []) {
|
|
1216
|
+
if (slotsOut.has(shown.slot)) continue;
|
|
1167
1217
|
const rest = build.rest.get(restKey(shown.slot, shown.attachment));
|
|
1168
1218
|
const vertexCount = shown.vertices.length / 2;
|
|
1169
1219
|
if (rest === undefined || rest.vertices.length !== shown.vertices.length) {
|
|
@@ -1314,6 +1364,8 @@ function drawTile(
|
|
|
1314
1364
|
legendTile.slots = [];
|
|
1315
1365
|
const shown = frame.attachments ?? [];
|
|
1316
1366
|
for (const [i, att] of shown.entries()) {
|
|
1367
|
+
// A left-out slot keeps its place: a number is the slot's place in the whole draw order, filtered or not.
|
|
1368
|
+
if (slotsOut.has(att.slot)) continue;
|
|
1317
1369
|
const order = plant.slotsReversed ? shown.length - 1 - i : i;
|
|
1318
1370
|
let minX = Infinity;
|
|
1319
1371
|
let minY = Infinity;
|