spine-rigc 2.11.1 → 2.13.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/NOTICE.md +2 -2
- package/README.md +19 -8
- package/docs/AUTHORING.md +294 -10
- package/docs/INGEST.md +18 -1
- package/package.json +1 -1
- package/skills/rigc/SKILL.md +10 -2
- package/src/assertions/bodies/a50.ts +97 -0
- package/src/assertions/facts/stage_box.ts +65 -0
- package/src/assertions/kinds.ts +1 -0
- package/src/assertions/model/index.ts +7 -0
- package/src/assertions/model/stage_box.ts +51 -0
- package/src/atlas.ts +6 -1
- package/src/cli/core_commands.ts +11 -2
- package/src/cli/repack.ts +408 -0
- package/src/cli/shared.ts +96 -3
- package/src/cli/spine_commands.ts +2 -0
- package/src/compile.ts +119 -3
- package/src/core/index.ts +16 -2
- package/src/ingest.ts +154 -8
- package/src/model.ts +21 -2
- package/src/repack.ts +495 -0
- package/src/rig.ts +80 -2
- package/src/validate.ts +61 -0
package/skills/rigc/SKILL.md
CHANGED
|
@@ -49,7 +49,10 @@ through `@esotericsoftware/spine-core`: install it beside the package
|
|
|
49
49
|
(`bun add -d @esotericsoftware/spine-core`) and the same `rigc` runs them, and
|
|
50
50
|
`build` runs the round trip as well. Without it they are refused by name, and
|
|
51
51
|
`build` writes the same files gated without the parse — `rigc --version` names the
|
|
52
|
-
entry that ran.
|
|
52
|
+
entry that ran. In the parse's place that `build` runs two rules of its own over
|
|
53
|
+
rigc's model document, `A00_MODEL_READ` and `A00_MODEL_REGIONS_ON_PAGES`; they are
|
|
54
|
+
not registry assertions, so its summary counts two more, and AUTHORING §5.2 has a
|
|
55
|
+
row for each.
|
|
53
56
|
|
|
54
57
|
## The loop
|
|
55
58
|
|
|
@@ -81,7 +84,12 @@ entry that ran.
|
|
|
81
84
|
`rigc vote --candidate <a> --candidate <b>` when several candidates are green and
|
|
82
85
|
only a person can choose between them.
|
|
83
86
|
6. `rigc validate <out>` re-gates artifacts already on disk, and
|
|
84
|
-
`rigc <command> --help` is each command's own flag table.
|
|
87
|
+
`rigc <command> --help` is each command's own flag table. `rigc repack <out>
|
|
88
|
+
--out <dir>` packs a build again under `build --pack`'s packing flags when its
|
|
89
|
+
parts were not kept, and writes only after every region and the skeleton are
|
|
90
|
+
shown unchanged (`--accept-skeleton-differences` writes a rebuild whose skeleton
|
|
91
|
+
differs, every difference printed; `--stage-box <slot>` reads a build's stage box)
|
|
92
|
+
— AUTHORING §0.4.
|
|
85
93
|
7. Every finished unit ends with `rigc preview --candidate <out>`, and the report
|
|
86
94
|
names the `.html` it wrote. The hand-off to a person is part of the work.
|
|
87
95
|
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A50, the body (issue #1168): the stage box a rig asked for is the stage —
|
|
3
|
+
* in the Spine files a consumer ships, read back the way that consumer reads
|
|
4
|
+
* it.
|
|
5
|
+
*
|
|
6
|
+
* Since issue #907 the Spine header carries the setup-pose bounding box and the
|
|
7
|
+
* stage is stated only in `skeleton.model.json`, which a consumer does not ship.
|
|
8
|
+
* A rig that asks (`skeleton.stageBox`) carries the stage as a bounding-box
|
|
9
|
+
* attachment, whose numbers `compile` writes from the stage. This rule holds
|
|
10
|
+
* that box to the stage the document states, on both readings a consumer has:
|
|
11
|
+
*
|
|
12
|
+
* 1. **The data.** The slot carries, in the `default` skin under the asked
|
|
13
|
+
* name, an unweighted bounding box of four vertices, and its stored
|
|
14
|
+
* vertices are the stage's corners — `(x, y)`, `(x + w, y)`,
|
|
15
|
+
* `(x + w, y + h)`, `(x, y + h)` — each exactly, as the float32 the
|
|
16
|
+
* runtime loads. That is what a reader of the attachment gets without
|
|
17
|
+
* posing anything.
|
|
18
|
+
* 2. **The setup pose.** Its world vertices at the setup pose — constraints
|
|
19
|
+
* applied, no physics, no skin set — are those corners as the runtime
|
|
20
|
+
* poses them on an unrotated root: through the frame the core computes
|
|
21
|
+
* for a root that states no transform (`worldTransforms` in
|
|
22
|
+
* `../../core/world.ts`, whose unrotated `b` is cos 90° at the runtime's
|
|
23
|
+
* pi, −2.3e-8, not 0), compared on the header's grid
|
|
24
|
+
* (`headerBoxNumber` in `../../compile.ts`) — the tolerance the header's
|
|
25
|
+
* own box is held at. A constraint that moves the box's bone at setup
|
|
26
|
+
* is caught here and nowhere else: `compile` poses no constraint.
|
|
27
|
+
*
|
|
28
|
+
* **SKIP** where nothing asks: an export or a directory with no `/3` document,
|
|
29
|
+
* and a document that states a stage and no box — a rig that does not ask is
|
|
30
|
+
* unmeasured, never certified.
|
|
31
|
+
*
|
|
32
|
+
* Links nothing from the runtime.
|
|
33
|
+
*/
|
|
34
|
+
import type { Verdicts } from '../harness.ts';
|
|
35
|
+
import type { StageBoxFacts } from '../facts/stage_box.ts';
|
|
36
|
+
import { worldTransforms } from '../../core/world.ts';
|
|
37
|
+
import { headerBoxNumber } from '../../compile.ts';
|
|
38
|
+
|
|
39
|
+
const CODE = 'A50_STAGE_BOX_IS_THE_STAGE';
|
|
40
|
+
|
|
41
|
+
/** The stage's four corners in Spine world, bottom-left first and counter-clockwise — the order `compile` writes. */
|
|
42
|
+
export function stageCorners(stage: { x: number; y: number; width: number; height: number }): number[] {
|
|
43
|
+
const right = stage.x + stage.width;
|
|
44
|
+
const top = stage.y + stage.height;
|
|
45
|
+
return [stage.x, stage.y, right, stage.y, right, top, stage.x, top];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function a50StageBoxIsTheStage({ fail, skip }: Verdicts, facts: StageBoxFacts): void {
|
|
49
|
+
const { asked } = facts;
|
|
50
|
+
if (asked === null) return skip(CODE, facts.why);
|
|
51
|
+
const where = `the stage box (slot "${asked.slot}", attachment "${asked.attachment}")`;
|
|
52
|
+
const { x, y, width, height } = asked.stage;
|
|
53
|
+
const required = `the stage ${x},${y} ${width}x${height}`;
|
|
54
|
+
if (!facts.slot) return fail(CODE, `${where}: the skeleton has no slot "${asked.slot}", so a consumer finds no stage — ${required} — in it`);
|
|
55
|
+
const held = facts.held;
|
|
56
|
+
if (held === null) {
|
|
57
|
+
return fail(CODE, `${where}: the default skin holds no attachment "${asked.attachment}" on slot "${asked.slot}", so a consumer finds no stage — ${required} — in it`);
|
|
58
|
+
}
|
|
59
|
+
if (held.type !== 'boundingbox') {
|
|
60
|
+
return fail(CODE, `${where}: the attachment is a ${held.type}, not a bounding box — ${required} travels as a boundingbox attachment, which every runtime returns by slot and name`);
|
|
61
|
+
}
|
|
62
|
+
if (held.weighted) {
|
|
63
|
+
return fail(CODE, `${where}: the box's vertices are bound to bones, and the stage is four unweighted corners in the root's space — ${required}`);
|
|
64
|
+
}
|
|
65
|
+
const corners = stageCorners(asked.stage);
|
|
66
|
+
if (held.stored.length !== corners.length) {
|
|
67
|
+
return fail(CODE, `${where}: the box holds ${held.stored.length / 2} vertices, and the stage is 4 corners — ${required}`);
|
|
68
|
+
}
|
|
69
|
+
let faults = 0;
|
|
70
|
+
for (let i = 0; i < corners.length; i += 2) {
|
|
71
|
+
const want = [Math.fround(corners[i]), Math.fround(corners[i + 1])];
|
|
72
|
+
const found = [held.stored[i], held.stored[i + 1]];
|
|
73
|
+
if (found[0] !== want[0] || found[1] !== want[1]) {
|
|
74
|
+
faults++;
|
|
75
|
+
fail(CODE, `${where}: vertex ${i / 2} is stored as (${found[0]}, ${found[1]}) and the stage's corner is (${want[0]}, ${want[1]}) — ${required}, bottom-left first and counter-clockwise`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (faults > 0) return;
|
|
79
|
+
if (typeof held.world === 'string') return fail(CODE, `${where}: its setup pose cannot be read — ${held.world}`);
|
|
80
|
+
// The runtime's frame of a root that states no transform, as the core computes it.
|
|
81
|
+
const frame = worldTransforms([{ name: 'root' }]).get('root');
|
|
82
|
+
if (frame === undefined) throw new Error('internal: the core posed no frame for an unrotated root');
|
|
83
|
+
for (let i = 0; i < corners.length; i += 2) {
|
|
84
|
+
const cx = Math.fround(corners[i]);
|
|
85
|
+
const cy = Math.fround(corners[i + 1]);
|
|
86
|
+
const want = [headerBoxNumber(cx * frame.a + cy * frame.b + frame.worldX), headerBoxNumber(cx * frame.c + cy * frame.d + frame.worldY)];
|
|
87
|
+
const found = [headerBoxNumber(held.world[i]), headerBoxNumber(held.world[i + 1])];
|
|
88
|
+
if (found[0] !== want[0] || found[1] !== want[1]) {
|
|
89
|
+
fail(
|
|
90
|
+
CODE,
|
|
91
|
+
`${where}: vertex ${i / 2} poses at (${found[0]}, ${found[1]}) at the setup pose and the stage's corner poses at ` +
|
|
92
|
+
`(${want[0]}, ${want[1]}) on an unrotated root, on the header's grid — something moves the box's bone at setup ` +
|
|
93
|
+
'(a constraint, or a pose the root does not state), so the box a consumer reads is not the stage',
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The stage box a rig asked for and what the skeleton holds there (issue
|
|
3
|
+
* #1168) — what `A50_STAGE_BOX_IS_THE_STAGE` reads.
|
|
4
|
+
*
|
|
5
|
+
* ⭐ **The question is asked by the model document and answered by the
|
|
6
|
+
* skeleton.** The document states which slot and attachment carry the stage
|
|
7
|
+
* (`stage.box`, `ModelStage.box`) and the stage they were written from; a Spine
|
|
8
|
+
* file carries no such statement, so an export, a bare directory and a
|
|
9
|
+
* document that states no box all ask nothing and the body SKIPs, naming which.
|
|
10
|
+
* What answers is read off the skeleton: through spine-core's loaded
|
|
11
|
+
* `SkeletonData` and a skeleton posed at setup (`spineStageBox` in
|
|
12
|
+
* `../../validate.ts`), or through the document's records and rigc's core
|
|
13
|
+
* poser (`../model/stage_box.ts`).
|
|
14
|
+
*
|
|
15
|
+
* Links nothing from the runtime.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** The box the document says the rig asked for, and the stage it was written from. */
|
|
19
|
+
export interface StageBoxAsked {
|
|
20
|
+
readonly slot: string;
|
|
21
|
+
readonly attachment: string;
|
|
22
|
+
readonly stage: { readonly x: number; readonly y: number; readonly width: number; readonly height: number };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** The attachment the slot carries under the asked name, in the `default` skin. */
|
|
26
|
+
export interface StageBoxHeld {
|
|
27
|
+
/**
|
|
28
|
+
* The attachment's type in the format's words — `boundingbox`, `region`,
|
|
29
|
+
* `mesh`, `clipping`, `path`, `point` — as the runtime loads it: a linked
|
|
30
|
+
* mesh loads as a `mesh`, so both suppliers spell it so.
|
|
31
|
+
*/
|
|
32
|
+
readonly type: string;
|
|
33
|
+
/** Whether its vertices are bound to bones (a weighted run) rather than stated in the slot bone's space. */
|
|
34
|
+
readonly weighted: boolean;
|
|
35
|
+
/** The vertex numbers it holds as loaded — `x, y` per vertex for an unweighted box. Empty for a type that holds none. */
|
|
36
|
+
readonly stored: readonly number[];
|
|
37
|
+
/**
|
|
38
|
+
* Its world vertices at the setup pose — constraints applied, no physics,
|
|
39
|
+
* no skin set — `x, y` per vertex; or why there are none (its bone is
|
|
40
|
+
* inactive, the setup pose cannot be posed).
|
|
41
|
+
*/
|
|
42
|
+
readonly world: readonly number[] | string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** What A50 reads. */
|
|
46
|
+
export interface StageBoxFacts {
|
|
47
|
+
/** The box the rig asked for, or `null` where nothing asks — and then `why` says what asked nothing. */
|
|
48
|
+
readonly asked: StageBoxAsked | null;
|
|
49
|
+
/** The SKIP's reason where `asked` is `null`; empty otherwise. */
|
|
50
|
+
readonly why: string;
|
|
51
|
+
/** Whether the skeleton has a slot of the asked name. */
|
|
52
|
+
readonly slot: boolean;
|
|
53
|
+
/** The `default` skin's attachment of the asked name on that slot, or `null` for none (no default skin, nothing under that name). */
|
|
54
|
+
readonly held: StageBoxHeld | null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The SKIP wherever nothing asks: a `/3` document whose stage states no box, a
|
|
59
|
+
* document that declares no stage, a `/2` or `/1` document, or none at all (an
|
|
60
|
+
* export, a bare directory). One sentence for all of them, because the rule
|
|
61
|
+
* reads one fact — that no box was asked for — and two suppliers that differ
|
|
62
|
+
* only in whether a caller handed the document over must print one line.
|
|
63
|
+
*/
|
|
64
|
+
export const SKIP_NO_STAGE_BOX =
|
|
65
|
+
'nothing asks for a stage box: no rigc-compiled/3 model document beside this skeleton states one (stage.box, from the rig spec\'s skeleton.stageBox) — a rig that does not ask, or an export, carries no stage in its Spine files to measure';
|
package/src/assertions/kinds.ts
CHANGED
|
@@ -97,6 +97,7 @@ export const ASSERTION_KIND: Readonly<Record<string, AssertionKind>> = {
|
|
|
97
97
|
A47_IK_CONSTRAINT_NOT_MUTED_THROUGHOUT: 'validity',
|
|
98
98
|
A48_TRANSFORM_CONSTRAINT_NOT_MUTED_THROUGHOUT: 'validity',
|
|
99
99
|
A49_PACKED_FOOTPRINTS_DO_NOT_OVERLAP: 'renderer',
|
|
100
|
+
A50_STAGE_BOX_IS_THE_STAGE: 'validity',
|
|
100
101
|
};
|
|
101
102
|
|
|
102
103
|
/** Whether `profile` runs a rule of `kind`: `spine` runs validity alone, `spine-html` every kind. */
|
|
@@ -63,6 +63,9 @@ import { a38SkinMembersAreSkinRequired } from '../bodies/a38.ts';
|
|
|
63
63
|
import { a06AtlasPageSizeMatchesPng } from '../bodies/a06.ts';
|
|
64
64
|
import { a19OverlayPngsHaveAlpha } from '../bodies/a19.ts';
|
|
65
65
|
import { a49PackedFootprintsDoNotOverlap } from '../bodies/a49.ts';
|
|
66
|
+
import { a50StageBoxIsTheStage } from '../bodies/a50.ts';
|
|
67
|
+
import { modelStageBox } from './stage_box.ts';
|
|
68
|
+
import type { StageBoxFacts } from '../facts/stage_box.ts';
|
|
66
69
|
import { a27RegionNameMatchesPageFilename } from '../bodies/a27.ts';
|
|
67
70
|
import { modelAnimatedBones } from './animated_bones.ts';
|
|
68
71
|
import { modelSkinMembers } from './skin_members.ts';
|
|
@@ -193,6 +196,8 @@ export interface ModelSupply {
|
|
|
193
196
|
sliderComposition: (read: ReadDocument) => SliderCompositionFacts;
|
|
194
197
|
constraintTargets: (read: ReadDocument) => ConstraintTargetFacts;
|
|
195
198
|
steppedPoses: (read: ReadDocument) => SteppedPoseFacts;
|
|
199
|
+
/** The stage box the document asks for and what its default skin holds there (issue #1168, `./stage_box.ts`). */
|
|
200
|
+
stageBox: (read: ReadDocument) => StageBoxFacts;
|
|
196
201
|
/** The rig info the document declares (issue #1054, `./declared.ts`); a caller's `rig` beside it is refused by name. */
|
|
197
202
|
rigInfo: (read: ReadDocument, input: ModelValidateInput) => RigInfo;
|
|
198
203
|
/** Each animation's declared duration as the document states it (issue #1054); a caller's `declaredDurations` beside it is refused by name. */
|
|
@@ -223,6 +228,7 @@ export const MODEL_SUPPLY: ModelSupply = {
|
|
|
223
228
|
sliderComposition: modelSliderComposition,
|
|
224
229
|
constraintTargets: modelConstraintTargets,
|
|
225
230
|
steppedPoses: modelSteppedPoses,
|
|
231
|
+
stageBox: modelStageBox,
|
|
226
232
|
rigInfo: (read, input) => {
|
|
227
233
|
refuseDeclaredBeside(read, input, 'rig');
|
|
228
234
|
return modelRigInfo(read);
|
|
@@ -306,6 +312,7 @@ export const MOVED_ASSERTIONS: readonly MovedAssertion[] = [
|
|
|
306
312
|
{ code: 'A06_ATLAS_PAGE_SIZE_MATCHES_PNG', run: (v, read, input, supply) => a06AtlasPageSizeMatchesPng(v, supply.atlasRegions(read, input), input, input.profile === 'spine-html'), unread: SKIP_NO_ATLAS },
|
|
307
313
|
{ code: 'A19_OVERLAY_PNGS_HAVE_ALPHA', run: (v, read, input, supply) => a19OverlayPngsHaveAlpha(v, supply.atlasRegions(read, input), supply.stage(read, input), supply.skinEntries(read), { atlasDir: input.atlasDir, rig: supply.rigInfo(read, input) }), unread: SKIP_NO_ATLAS },
|
|
308
314
|
{ code: 'A49_PACKED_FOOTPRINTS_DO_NOT_OVERLAP', run: (v, read, input, supply) => a49PackedFootprintsDoNotOverlap(v, supply.atlasRegions(read, input), supply.regionJoins(read), supply.meshes(read)), unread: SKIP_NO_ATLAS },
|
|
315
|
+
{ code: 'A50_STAGE_BOX_IS_THE_STAGE', run: (v, read, _input, supply) => a50StageBoxIsTheStage(v, supply.stageBox(read)), unread: SKIP_NO_MODEL },
|
|
309
316
|
{ code: 'A24_AXIS_SPACE_STROKE', run: (v, read, input, supply) => a24AxisSpaceStroke(v, supply.boneTimelines(read), { rig: supply.rigInfo(read, input) }), unread: SKIP_NO_MODEL, beforeTheParse: true },
|
|
310
317
|
{ code: 'A25_DETACHED_BONE_PARENTAGE', run: (v, read, input, supply) => a25DetachedBoneParentage(v, supply.skeletonRoster(read), { rig: supply.rigInfo(read, input) }), unread: SKIP_NO_MODEL, beforeTheParse: true },
|
|
311
318
|
{ code: 'A26_SLOT_DRAW_ORDER', run: (v, read, input, supply) => a26SlotDrawOrder(v, supply.skeletonRoster(read), { rig: supply.rigInfo(read, input) }), unread: SKIP_NO_MODEL, beforeTheParse: true },
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The model side's supply of `StageBoxFacts` (issue #1168): the box the
|
|
3
|
+
* document's `stage.box` asks for, and what the document's `default` skin
|
|
4
|
+
* holds there — its record as the runtime would load it, and its world
|
|
5
|
+
* vertices at the setup pose as rigc's core poses them (`poseSetup` under no
|
|
6
|
+
* skin, constraints applied, then `worldVertices`, the function every vertex
|
|
7
|
+
* attachment of the core is posed by — no second arithmetic for the box).
|
|
8
|
+
*
|
|
9
|
+
* A `/2` or `/1` document states no stage and asks nothing; a `/3` document
|
|
10
|
+
* whose stage states no box asks nothing, and neither does one that declares no
|
|
11
|
+
* stage. The core's poses are held to spine-core's at tolerance 0 by
|
|
12
|
+
* `tools/core_gate.ts`, which is what lets this side and the round trip print
|
|
13
|
+
* one line.
|
|
14
|
+
*
|
|
15
|
+
* Links nothing from the runtime.
|
|
16
|
+
*/
|
|
17
|
+
import { activeBones, poseSetup, rawNumber, underNoSkin } from '../../core/index.ts';
|
|
18
|
+
import { worldVertices } from '../../core/vertices.ts';
|
|
19
|
+
import { SKIP_NO_STAGE_BOX, type StageBoxFacts, type StageBoxHeld } from '../facts/stage_box.ts';
|
|
20
|
+
import type { ReadDocument } from './parse.ts';
|
|
21
|
+
|
|
22
|
+
export function modelStageBox(read: ReadDocument): StageBoxFacts {
|
|
23
|
+
const doc = read.doc;
|
|
24
|
+
const stage = doc.stated?.stage;
|
|
25
|
+
if (doc.stated === null || stage === undefined || stage === null) return { asked: null, why: SKIP_NO_STAGE_BOX, slot: false, held: null };
|
|
26
|
+
if (stage.box === undefined) return { asked: null, why: SKIP_NO_STAGE_BOX, slot: false, held: null };
|
|
27
|
+
const asked = { slot: stage.box.slot, attachment: stage.box.attachment, stage: { x: stage.x, y: stage.y, width: stage.width, height: stage.height } };
|
|
28
|
+
const slot = doc.slots.find((s) => s.name === asked.slot);
|
|
29
|
+
if (slot === undefined) return { asked, why: '', slot: false, held: null };
|
|
30
|
+
const record = doc.skins.find((k) => k.name === 'default')?.attachments[asked.slot]?.[asked.attachment];
|
|
31
|
+
if (record === undefined) return { asked, why: '', slot: true, held: null };
|
|
32
|
+
const g = record.geometry;
|
|
33
|
+
const type = record.kind === 'linkedmesh' ? 'mesh' : record.kind;
|
|
34
|
+
if (g === undefined || g.kind !== 'boundingbox') return { asked, why: '', slot: true, held: { type, weighted: false, stored: [], world: '' } };
|
|
35
|
+
const vertices = g.vertices;
|
|
36
|
+
const stored = vertices.weighted ? [] : vertices.xy.map(Math.fround);
|
|
37
|
+
const held: StageBoxHeld = { type, weighted: vertices.weighted, stored, world: poseWorld(read, slot.bone, vertices) };
|
|
38
|
+
return { asked, why: '', slot: true, held };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** The box's world vertices at the setup pose under no skin, or why there are none. */
|
|
42
|
+
function poseWorld(read: ReadDocument, bone: string, vertices: Parameters<typeof worldVertices>[0]): number[] | string {
|
|
43
|
+
const view = underNoSkin(read.doc);
|
|
44
|
+
if (!activeBones(view).has(bone)) return `its bone "${bone}" is inactive with no skin set, so nothing poses it`;
|
|
45
|
+
const posed = poseSetup(view, { round: rawNumber });
|
|
46
|
+
const world = posed.world;
|
|
47
|
+
if (world === null) return posed.absent.find(([block]) => block === 'setup.bones')?.[1] ?? "rigc's core does not pose this setup pose";
|
|
48
|
+
const frame = world.get(bone);
|
|
49
|
+
if (frame === undefined) return `its bone "${bone}" has no setup world transform`;
|
|
50
|
+
return worldVertices(vertices, frame, world);
|
|
51
|
+
}
|
package/src/atlas.ts
CHANGED
|
@@ -249,8 +249,13 @@ export function pageFootprint(region: { width: number; height: number; degrees:
|
|
|
249
249
|
* field". Four values maximum, because that is where the runtime stops (`if (i
|
|
250
250
|
* === 4) return 4`) and a fifth would silently mean something here that it does
|
|
251
251
|
* not mean there.
|
|
252
|
+
*
|
|
253
|
+
* Exported for `src/repack.ts` (issue #1169), which reads the keys a page and a
|
|
254
|
+
* region state — the ones this parser drops (`filter`, `format`, `repeat`,
|
|
255
|
+
* `split`, `pad`) included — with this function rather than a second spelling
|
|
256
|
+
* of the format, and holds what it walked to `parseAtlasText`'s regions.
|
|
252
257
|
*/
|
|
253
|
-
function readEntry(line: string | null): { key: string; values: string[] } | null {
|
|
258
|
+
export function readEntry(line: string | null): { key: string; values: string[] } | null {
|
|
254
259
|
if (line === null) return null;
|
|
255
260
|
const trimmed = line.trim();
|
|
256
261
|
if (trimmed.length === 0) return null;
|
package/src/cli/core_commands.ts
CHANGED
|
@@ -34,6 +34,7 @@ import { estimatePose, poseLines, type PoseOptions } from '../pose.ts';
|
|
|
34
34
|
import { BACKGROUND, contactSheet, type Frame, FRAMES_SIDECAR, FRAMES_SPEC, type FrameSet, type FramesSidecar, framingViewport, GEOMETRY_FILE, geometryFileOf, geometryText, loadCandidate, POSER_NAMES, type PoserName, PROTOCOL_FPS, refuseUnchosen, renderFrame, sampleAll, sampleAnimation, SETUP_POSE_DIR, SHEET_FILE, SHEET_TILE, sidecarViewport, type SkeletonFacts, type SlotSubset, SlotSubsetError, throughPoser } from '../render_shared.ts';
|
|
35
35
|
import { type CompileResult } from '../types.ts';
|
|
36
36
|
import { attachmentRegionJoins } from '../region_joins.ts';
|
|
37
|
+
import { cmdRepack } from './repack.ts';
|
|
37
38
|
import { ATLAS_ABSENT, COMMANDS, type CommandRun, resolveBuild, type BuildGate, runBuild, PACKAGE_ROOT, DEFAULT_CHAINFIT_OUT, DEFAULT_POSE_OUT, DEFAULT_SKILLS_DIR, ExplainError, meshBudget, meshDepthNote, meshFit, meshInfluenceNote, PAGE_GRID_UNLOCATED, readAnimationFlag, readJsonFile, readPoserFlag, readSkeletonText, readVersion, resolveCut, resolveDrawable, runCheck, SkillsInstallError, STAGELESS_FRAMING, UsageError, writeJson, documentStageBeside } from './shared.ts';
|
|
38
39
|
import { cpSync, existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, readlinkSync, realpathSync, rmSync, statSync, symlinkSync, writeFileSync } from 'node:fs';
|
|
39
40
|
import { basename, dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
@@ -1299,6 +1300,7 @@ export function cmdIngest(flags: Record<string, string>, positional: string[]):
|
|
|
1299
1300
|
if (documentStage !== undefined) {
|
|
1300
1301
|
console.log(` .. stage ${documentStage === null ? 'none declared' : `${documentStage.width} x ${documentStage.height}`} — read from the ${MODEL_DOCUMENT_FILE} beside it (its spine.sha256 is this skeleton's); the header's box is the setup-pose bounding box`);
|
|
1301
1302
|
}
|
|
1303
|
+
if (flags['stage-box'] !== undefined) console.log(` .. stage read from the bounding box in slot "${flags['stage-box']}" (--stage-box); the header's box is the setup-pose bounding box`);
|
|
1302
1304
|
let result: { rig: unknown; motion: unknown; findings: IngestFinding[] };
|
|
1303
1305
|
try {
|
|
1304
1306
|
result = ingest(readJsonFile(skeletonPath), {
|
|
@@ -1307,6 +1309,7 @@ export function cmdIngest(flags: Record<string, string>, positional: string[]):
|
|
|
1307
1309
|
art,
|
|
1308
1310
|
images: specImages,
|
|
1309
1311
|
stage,
|
|
1312
|
+
...(flags['stage-box'] === undefined ? {} : { stageBox: flags['stage-box'] }),
|
|
1310
1313
|
source: basename(skeletonPath),
|
|
1311
1314
|
version: readVersion(),
|
|
1312
1315
|
});
|
|
@@ -1552,8 +1555,11 @@ export function cmdSkills(flags: Record<string, string>, positional: string[]):
|
|
|
1552
1555
|
* the pages where the compile measured them — the files the round trip's own
|
|
1553
1556
|
* A17 reads on `cli.ts` — and A18 compares the documents as written, the
|
|
1554
1557
|
* copies' names in them. Under `--pack` the two are one text.
|
|
1558
|
+
*
|
|
1559
|
+
* Exported for the selftest's `RPK` plants (issue #1169), which run `repack`
|
|
1560
|
+
* through this entry's dispatch with a hook between the lift and the build.
|
|
1555
1561
|
*/
|
|
1556
|
-
const MODEL_AND_TEXT_GATE: BuildGate = {
|
|
1562
|
+
export const MODEL_AND_TEXT_GATE: BuildGate = {
|
|
1557
1563
|
heading: (profile) =>
|
|
1558
1564
|
` .. validate (the model side over the document + the round trip's rules restated over the emitted text, profile ${profile}; this entry links no spine-core, so the round trip does not run)`,
|
|
1559
1565
|
run: ({ result, atlasText, atlasDir, modelText, reEmit, profile }) => {
|
|
@@ -1592,10 +1598,13 @@ const MODEL_AND_TEXT_GATE: BuildGate = {
|
|
|
1592
1598
|
* The bodies the second entry runs under a name whose full-entry body is the
|
|
1593
1599
|
* runtime's (`runtime.core` in `COMMANDS`, issue #1060) — registered by
|
|
1594
1600
|
* `cli_core.ts` alone, beside `CORE_COMMAND_RUNS`. `build` is `runBuild` with
|
|
1595
|
-
* this entry's gate: it writes what `cli.ts build` writes.
|
|
1601
|
+
* this entry's gate: it writes what `cli.ts build` writes. `repack` (issue
|
|
1602
|
+
* #1169) is `./repack.ts`'s body with the same gate, for the same reason: it
|
|
1603
|
+
* is `build --pack` run over a build's own lifted regions.
|
|
1596
1604
|
*/
|
|
1597
1605
|
export const CORE_ENTRY_RUNS: Readonly<Record<string, CommandRun>> = {
|
|
1598
1606
|
build: ({ flags }) => runBuild(flags, MODEL_AND_TEXT_GATE),
|
|
1607
|
+
repack: (args) => cmdRepack(args, MODEL_AND_TEXT_GATE),
|
|
1599
1608
|
};
|
|
1600
1609
|
|
|
1601
1610
|
/**
|