spine-rigc 1.2.3 → 1.4.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 -2
- package/cli.ts +124 -20
- package/docs/AUTHORING.md +471 -19
- package/docs/FACE.md +15 -0
- package/docs/INGEST.md +1 -1
- package/package.json +17 -1
- package/src/atlas.ts +151 -2
- package/src/compile.ts +335 -0
- package/src/mesh.ts +405 -1
- package/src/pose.ts +297 -10
- package/src/render.ts +376 -21
- package/src/rig.ts +140 -7
- package/src/types.ts +30 -0
- package/src/validate.ts +101 -24
package/README.md
CHANGED
|
@@ -530,12 +530,12 @@ commands take it and what its default is.
|
|
|
530
530
|
| Command | Does |
|
|
531
531
|
| --- | --- |
|
|
532
532
|
| `build --rig … --motion … --out …` | compiles, gates, and **writes only if the gate is green**. `--images <dir>` says where the rig spec's `image` names resolve, `--manifest` adds measured art, and `--copy-images` copies every page PNG into `--out` so the directory is self-contained, with `skeleton.images` pointing at it so the editor's import finds the parts |
|
|
533
|
-
| `build … --pack` | the same build with every part arranged onto **shared** atlas pages, written into `--out` — losslessly, and gated a second time as the pair that ships. `--page-size` and `--
|
|
533
|
+
| `build … --pack` | the same build with every part arranged onto **shared** atlas pages, written into `--out` — losslessly, and gated a second time as the pair that ships. `--page-size`, `--padding` and `--page-edges` tune it |
|
|
534
534
|
| `build … --atlas-in <file.atlas>` | the same build with every part resolved to a **region of an existing pack** instead of a loose PNG; a name the atlas lacks, a size the spec disagrees with or a rectangle off its page is refused by name |
|
|
535
535
|
| `validate <dir>` | re-gates artifacts already on disk |
|
|
536
536
|
| `ingest <skeleton.json> --out <dir>` | `build` run backwards: reads a Spine 4.3 skeleton and writes the rig spec and motion spec that **rebuild it**, plus a findings report naming everything it could not carry. a skeleton that declares no stage is carried as declaring none, `--stage x,y,w,h` adds a box to one — and is refused, rather than ignored, beside one that declares a box — and `--images <dir>` writes the spec's own images directory — the opposite direction from `build --images`, which overrides it — so the rebuild carries no flag at all |
|
|
537
537
|
| `explain --rig … --motion …` | the compiled rig as a table — every bone with its resolved parent, the slots in draw order, every timeline key by key. Writes nothing. What to reach for when a rig compiles and still looks wrong |
|
|
538
|
-
| `render --candidate <dir>` | PNG frames plus a contact sheet, in `render/`. `--hide <slot,…>` or `--slot <slot,…>` draws part of the rig on the **same grid** as the whole, so the two frames overlay and the difference is the part; `frames.json` records the subset and `check` refuses such a set as a reference |
|
|
538
|
+
| `render --candidate <dir>` | PNG frames plus a contact sheet, in `render/`. `--hide <slot,…>` or `--slot <slot,…>` draws part of the rig on the **same grid** as the whole, so the two frames overlay and the difference is the part; `frames.json` records the subset and `check` refuses such a set as a reference. `--geometry` adds a `geometry.json` per set: every frame's bone world transforms and skinned attachment vertices, with the rest geometry, on the frames' own grid |
|
|
539
539
|
| `preview --candidate <dir>` | one self-contained `.html` that plays it, headed by the line `validate <dir>` prints for it and the rigc version — a refused candidate is still previewed, and its header says so in the gate's words. Repeat `--candidate` for one page with a pane per candidate, in the order given; a green `build` ends by naming this command for its own `--out` |
|
|
540
540
|
| `vote --candidate a --candidate b` | one `.html` that asks a human which; `vote --record <file>` checks the answer into `votes.jsonl` |
|
|
541
541
|
| `pose --images <dir> --frame <png>` | reads part placements **out of** a picture |
|
package/cli.ts
CHANGED
|
@@ -101,8 +101,12 @@ import { NotAPngError } from './src/png.ts';
|
|
|
101
101
|
import { copyAtlasPages } from './src/emit.ts';
|
|
102
102
|
import {
|
|
103
103
|
DEFAULT_PADDING,
|
|
104
|
+
DEFAULT_PAGE_EDGES,
|
|
104
105
|
DEFAULT_PAGE_SIZE,
|
|
106
|
+
FREE_EDGE_STEP,
|
|
107
|
+
PAGE_EDGES,
|
|
105
108
|
packAtlas,
|
|
109
|
+
type PageEdges,
|
|
106
110
|
pageFootprint,
|
|
107
111
|
parseAtlasText,
|
|
108
112
|
type AtlasRegion,
|
|
@@ -141,6 +145,10 @@ import {
|
|
|
141
145
|
FRAMES_SIDECAR,
|
|
142
146
|
FRAMES_SPEC,
|
|
143
147
|
framingViewport,
|
|
148
|
+
GEOMETRY_FILE,
|
|
149
|
+
GeometryError,
|
|
150
|
+
geometryFileOf,
|
|
151
|
+
geometryText,
|
|
144
152
|
loadPosable,
|
|
145
153
|
PROTOCOL_FPS,
|
|
146
154
|
renderFrame,
|
|
@@ -149,6 +157,7 @@ import {
|
|
|
149
157
|
SETUP_POSE_DIR,
|
|
150
158
|
SHEET_FILE,
|
|
151
159
|
SHEET_TILE,
|
|
160
|
+
sidecarViewport,
|
|
152
161
|
SlotSubsetError,
|
|
153
162
|
slotSubsetOf,
|
|
154
163
|
type Frame,
|
|
@@ -255,7 +264,7 @@ function repositoryUrl(): string {
|
|
|
255
264
|
* needs a value` (issue #328). `CLI10`/`CLI11` in `selftest.ts` now hold the two
|
|
256
265
|
* halves together by reading `--help` rather than by naming a flag.
|
|
257
266
|
*/
|
|
258
|
-
const BOOLEAN_FLAGS = new Set(['all-frames', 'all-bones', 'help', 'copy-images', 'again', 'pack', 'copy']);
|
|
267
|
+
const BOOLEAN_FLAGS = new Set(['all-frames', 'all-bones', 'help', 'copy-images', 'again', 'pack', 'copy', 'geometry']);
|
|
259
268
|
|
|
260
269
|
/**
|
|
261
270
|
* The flags a command is allowed to spell more than once.
|
|
@@ -612,9 +621,37 @@ const MESH_KIND_NOTES: Record<CompileResult['meshes'][number]['kind'], string> =
|
|
|
612
621
|
ribbon: 'ribbon entry row pinned, rows share their weights so the strip lengthens without widening',
|
|
613
622
|
contour: 'contour the art\'s own silhouette, every vertex pinned to the slot bone (geometry, not a deformation)',
|
|
614
623
|
grid: 'grid a lattice over the part window at stated column and row positions, every vertex pinned to the slot bone',
|
|
624
|
+
segments: 'segments a lattice over the part\'s alpha, every vertex weighted by distance to the bone segments it names',
|
|
615
625
|
authored: 'authored geometry rigc did not build; it assumes nothing about the topology',
|
|
616
626
|
};
|
|
617
627
|
|
|
628
|
+
/**
|
|
629
|
+
* The line under a `segments` mesh: how its bones share the vertices.
|
|
630
|
+
*
|
|
631
|
+
* ⭐ The weights are this generator's whole output, and the per-vertex numbers
|
|
632
|
+
* are nowhere a reader can see them — so the three figures that say whether the
|
|
633
|
+
* falloff did what was meant are printed where the mesh is: the most bones any
|
|
634
|
+
* vertex binds (against `maxBones`), the mean, and how many vertices a single
|
|
635
|
+
* bone owns outright. A named bone no vertex binds is said by name, because it
|
|
636
|
+
* is in no weight and so in no other figure either.
|
|
637
|
+
*/
|
|
638
|
+
function meshInfluenceNote(m: CompileResult['meshes'][number]): string {
|
|
639
|
+
const inf = m.influence;
|
|
640
|
+
if (inf === undefined) return '';
|
|
641
|
+
const unbound = m.bones.filter((b) => !inf.bound.includes(b));
|
|
642
|
+
const single = m.vertices === 0 ? 0 : (inf.singleBone / m.vertices) * 100;
|
|
643
|
+
const joined = inf.keptCells - inf.artCells;
|
|
644
|
+
return (
|
|
645
|
+
`\n influence max ${inf.maxBones} bone(s) per vertex, mean ${inf.meanBones.toFixed(2)}, ` +
|
|
646
|
+
`${inf.singleBone} of ${m.vertices} vertices (${single.toFixed(2)}%) on a single bone` +
|
|
647
|
+
(unbound.length ? `; named and bound by no vertex: ${unbound.join(', ')}` : '') +
|
|
648
|
+
`\n lattice cell ${inf.cell}px, ${inf.cols}x${inf.rows} cells, ${inf.artCells} with art, ${inf.keptCells} kept` +
|
|
649
|
+
(inf.islands > 1 || joined > 0
|
|
650
|
+
? ` (${inf.islands} island(s) joined into one outline, ${joined} cell(s) added that hold no art)`
|
|
651
|
+
: '')
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
|
|
618
655
|
/**
|
|
619
656
|
* Why a figure taken off a part's texels is withheld on a page whose file is
|
|
620
657
|
* not the size its atlas declares — the tail of every line that withholds one
|
|
@@ -1260,6 +1297,21 @@ function readIntFlag(flags: Record<string, string>, name: string, fallback: numb
|
|
|
1260
1297
|
return Number(raw);
|
|
1261
1298
|
}
|
|
1262
1299
|
|
|
1300
|
+
/**
|
|
1301
|
+
* Read `--page-edges`, or its default — `pot`.
|
|
1302
|
+
*
|
|
1303
|
+
* An unknown value is a usage error for `readProfile`'s reason: a typo that fell
|
|
1304
|
+
* back to `pot` would hand the caller who asked for the smaller page the bigger
|
|
1305
|
+
* one, green, and say nothing.
|
|
1306
|
+
*/
|
|
1307
|
+
function readPageEdges(flags: Record<string, string>): PageEdges {
|
|
1308
|
+
const raw = flags['page-edges'];
|
|
1309
|
+
if (raw === undefined) return DEFAULT_PAGE_EDGES;
|
|
1310
|
+
const found = PAGE_EDGES.find((e) => e === raw);
|
|
1311
|
+
if (!found) throw new UsageError(`--page-edges ${JSON.stringify(raw)}; known values: ${PAGE_EDGES.join(', ')}`);
|
|
1312
|
+
return found;
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1263
1315
|
/**
|
|
1264
1316
|
* One `DROP` line, written once because two outcomes print it.
|
|
1265
1317
|
*
|
|
@@ -1343,7 +1395,7 @@ function cmdBuild(flags: Record<string, string>): void {
|
|
|
1343
1395
|
// now a build like any other — and it is the only one that puts the renderer's
|
|
1344
1396
|
// own rulebook over shared-page sampling.
|
|
1345
1397
|
if (!packing) {
|
|
1346
|
-
for (const name of ['page-size', 'padding'] as const) {
|
|
1398
|
+
for (const name of ['page-size', 'padding', 'page-edges'] as const) {
|
|
1347
1399
|
if (flags[name] !== undefined) throw new UsageError(`--${name} only means something with --pack`);
|
|
1348
1400
|
}
|
|
1349
1401
|
}
|
|
@@ -1394,7 +1446,8 @@ function cmdBuild(flags: Record<string, string>): void {
|
|
|
1394
1446
|
console.log(
|
|
1395
1447
|
` MESH ${m.slot.padEnd(12)} ${m.kind.padEnd(8)} ${m.vertices} vertices / ${m.triangles} triangles ` +
|
|
1396
1448
|
`${meshBudget(result.rig)} bones=[${m.bones.join(', ')}] attachments=[${m.attachments.join(', ')}]${meshFit(m)}` +
|
|
1397
|
-
meshDepthNote(m)
|
|
1449
|
+
meshDepthNote(m) +
|
|
1450
|
+
meshInfluenceNote(m),
|
|
1398
1451
|
);
|
|
1399
1452
|
}
|
|
1400
1453
|
for (const ph of result.physics) {
|
|
@@ -1445,6 +1498,7 @@ function cmdBuild(flags: Record<string, string>): void {
|
|
|
1445
1498
|
const packOpts = {
|
|
1446
1499
|
pageSize: readIntFlag(flags, 'page-size', DEFAULT_PAGE_SIZE),
|
|
1447
1500
|
padding: readIntFlag(flags, 'padding', DEFAULT_PADDING),
|
|
1501
|
+
pageEdges: readPageEdges(flags),
|
|
1448
1502
|
pageStem: 'skeleton',
|
|
1449
1503
|
};
|
|
1450
1504
|
const inputs = result.images.map((img) => ({
|
|
@@ -1460,7 +1514,8 @@ function cmdBuild(flags: Record<string, string>): void {
|
|
|
1460
1514
|
console.log(
|
|
1461
1515
|
` .. pack: ${page.name} ${page.width}x${page.height}, ` +
|
|
1462
1516
|
`${packed.placements.filter((p) => packed.pages[p.page].name === page.name).length} region(s), ` +
|
|
1463
|
-
`${(page.occupancy * 100).toFixed(1)}% covered, padding ${packed.padding}
|
|
1517
|
+
`${(page.occupancy * 100).toFixed(1)}% covered, padding ${packed.padding}` +
|
|
1518
|
+
(packOpts.pageEdges === 'free' ? ', page edges free' : ''),
|
|
1464
1519
|
);
|
|
1465
1520
|
}
|
|
1466
1521
|
for (const place of packed.placements) {
|
|
@@ -1960,6 +2015,24 @@ function cmdRender(flags: Record<string, string>): void {
|
|
|
1960
2015
|
const fps = readPositiveNumber(flags, 'fps', PROTOCOL_FPS, 1);
|
|
1961
2016
|
const maxSide = readPositiveNumber(flags, 'max', 256, 16);
|
|
1962
2017
|
const outRoot = resolve(flags.out ?? 'render');
|
|
2018
|
+
const geometry = flags.geometry !== undefined;
|
|
2019
|
+
// Refused together rather than one of them ignored (issue #864). The export
|
|
2020
|
+
// records every slot's whole geometry, because a subset is a statement about
|
|
2021
|
+
// which pixels are DRAWN and the pose is the same whatever a picture leaves
|
|
2022
|
+
// out — so a geometry.json beside a subset's frames would describe slots
|
|
2023
|
+
// those frames do not draw, and one that dropped them would stop being the
|
|
2024
|
+
// pose. The whole-rig run writes the same file either way.
|
|
2025
|
+
if (geometry) {
|
|
2026
|
+
for (const flag of ['slot', 'hide'] as const) {
|
|
2027
|
+
if (flags[flag] !== undefined) {
|
|
2028
|
+
throw new UsageError(
|
|
2029
|
+
`--geometry takes no --${flag}: it records every slot's whole geometry, which a subset of the drawn slots ` +
|
|
2030
|
+
`does not change. Run \`rigc render --geometry\` on the whole rig for the file, and --${flag} ` +
|
|
2031
|
+
`${flags[flag]} without --geometry for the pictures — both land on the same grid`,
|
|
2032
|
+
);
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
1963
2036
|
|
|
1964
2037
|
console.log('rigc render');
|
|
1965
2038
|
console.log(` .. skeleton ${skeletonPath}`);
|
|
@@ -1993,8 +2066,24 @@ function cmdRender(flags: Record<string, string>): void {
|
|
|
1993
2066
|
// `sampleAll` covers the skeleton with no animation at all, which files its one
|
|
1994
2067
|
// setup-pose frame under the reserved name. Narrowing to one animation reuses
|
|
1995
2068
|
// the same sampler rather than a second path through it.
|
|
2069
|
+
//
|
|
2070
|
+
// `--geometry` rides on the SAME call (issue #864): the bones and the whole
|
|
2071
|
+
// attachments are read off the skeleton at the step that drew each frame, so
|
|
2072
|
+
// the export's grid is this frame set's by construction.
|
|
2073
|
+
const sampling = geometry ? { ...pose, bones: true, geometry: true } : pose;
|
|
1996
2074
|
const sampled: Map<string, Frame[]> =
|
|
1997
|
-
only === undefined
|
|
2075
|
+
only === undefined
|
|
2076
|
+
? sampleAll(data, fps, sampling)
|
|
2077
|
+
: new Map([[only, sampleAnimation(data, only, fps, sampling)]]);
|
|
2078
|
+
// Every file's text before the first write, so a refused number leaves the
|
|
2079
|
+
// output directory as it was rather than half of a frame set behind it.
|
|
2080
|
+
const geometryTexts = new Map<string, string>();
|
|
2081
|
+
if (geometry) {
|
|
2082
|
+
for (const [name, frames] of sampled) {
|
|
2083
|
+
const animation = name === SETUP_POSE_DIR && data.animations.length === 0 ? null : name;
|
|
2084
|
+
geometryTexts.set(name, geometryText(geometryFileOf(data, animation, fps, frames, viewport, skin)));
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
1998
2087
|
console.log(` .. ${viewport.width}x${viewport.height}px at ${fps} fps, ${sampled.size} set(s) -> ${outRoot}`);
|
|
1999
2088
|
if (!declaresSetupStage(data)) console.log(` .. ${STAGELESS_FRAMING.render}`);
|
|
2000
2089
|
|
|
@@ -2017,6 +2106,8 @@ function cmdRender(flags: Record<string, string>): void {
|
|
|
2017
2106
|
// would be the same picture with a border and a "0" on it.
|
|
2018
2107
|
const sheet = frames.length > 1;
|
|
2019
2108
|
if (sheet) contactSheet(frames, pages, viewport, SHEET_TILE).writePng(join(dir, SHEET_FILE));
|
|
2109
|
+
const geometryOut = geometryTexts.get(name);
|
|
2110
|
+
if (geometryOut !== undefined) writeFileSync(join(dir, GEOMETRY_FILE), geometryOut);
|
|
2020
2111
|
const duration = frames[frames.length - 1].time;
|
|
2021
2112
|
sets.push({
|
|
2022
2113
|
dir: dirName,
|
|
@@ -2028,7 +2119,8 @@ function cmdRender(flags: Record<string, string>): void {
|
|
|
2028
2119
|
duration,
|
|
2029
2120
|
});
|
|
2030
2121
|
const how = frames.length === 1 ? 'a single pose' : `${duration.toFixed(3)}s`;
|
|
2031
|
-
|
|
2122
|
+
const extras = `${sheet ? ` + ${SHEET_FILE}` : ''}${geometryOut === undefined ? '' : ` + ${GEOMETRY_FILE}`}`;
|
|
2123
|
+
console.log(` .. ${name.padEnd(16)} ${frames.length} frame(s), ${how}${extras} -> ${dir}`);
|
|
2032
2124
|
}
|
|
2033
2125
|
|
|
2034
2126
|
// The sidecar is what makes this a frame SET rather than a pile of pictures:
|
|
@@ -2045,15 +2137,9 @@ function cmdRender(flags: Record<string, string>): void {
|
|
|
2045
2137
|
// every slot says nothing and stays the bytes it always was (issue #835).
|
|
2046
2138
|
...subsetFields(subset),
|
|
2047
2139
|
background: BACKGROUND,
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
width: viewport.maxX - viewport.minX,
|
|
2052
|
-
height: viewport.maxY - viewport.minY,
|
|
2053
|
-
scale: viewport.scale,
|
|
2054
|
-
pixelWidth: viewport.width,
|
|
2055
|
-
pixelHeight: viewport.height,
|
|
2056
|
-
},
|
|
2140
|
+
// The one spelling `geometry.json` repeats, so the two files cannot state
|
|
2141
|
+
// two boxes for one grid.
|
|
2142
|
+
viewport: sidecarViewport(viewport),
|
|
2057
2143
|
sets: [...sets].sort((a, b) => a.dir.localeCompare(b.dir)),
|
|
2058
2144
|
};
|
|
2059
2145
|
writeFileSync(join(outRoot, FRAMES_SIDECAR), `${JSON.stringify(sidecar, null, 2)}\n`);
|
|
@@ -3327,7 +3413,8 @@ function cmdExplain(flags: Record<string, string>): void {
|
|
|
3327
3413
|
console.log(
|
|
3328
3414
|
` ${m.slot.padEnd(12)} ${m.kind.padEnd(8)} ${m.vertices} vertices / ${m.triangles} triangles ` +
|
|
3329
3415
|
`${meshBudget(result.rig)} bones=[${m.bones.join(', ')}]${meshFit(m)}` +
|
|
3330
|
-
meshDepthNote(m)
|
|
3416
|
+
meshDepthNote(m) +
|
|
3417
|
+
meshInfluenceNote(m),
|
|
3331
3418
|
);
|
|
3332
3419
|
}
|
|
3333
3420
|
}
|
|
@@ -3737,6 +3824,10 @@ const FLAG_MEANINGS: Record<string, string> = {
|
|
|
3737
3824
|
'one written is the smallest that holds the pack, spilling to more pages only when the set will not fit',
|
|
3738
3825
|
padding: `gutter each region reserves on every side, --pack only (default ${DEFAULT_PADDING}); it is filled by ` +
|
|
3739
3826
|
"extending the region's own edge pixels outwards, which is what stops a neighbour bleeding in",
|
|
3827
|
+
'page-edges': `what a page's edges may be, --pack only (default ${DEFAULT_PAGE_EDGES}): pot is a power of two on ` +
|
|
3828
|
+
`both; free tries every width on a ${FREE_EDGE_STEP}px step, takes the height the placement needs and keeps ` +
|
|
3829
|
+
'the least area — a smaller page, at the cost of region attachments sampling within 1 LSB of the loose ' +
|
|
3830
|
+
'build rather than exactly',
|
|
3740
3831
|
'atlas-in':
|
|
3741
3832
|
'resolve every part against the regions of this pre-packed .atlas instead of against loose PNGs — region ' +
|
|
3742
3833
|
'geometry (bounds/offsets/rotate) is read from the file and the atlas is re-emitted into --out, re-anchored',
|
|
@@ -3753,6 +3844,10 @@ const FLAG_MEANINGS: Record<string, string> = {
|
|
|
3753
3844
|
'derived: a candidate is entitled to its own vocabulary, so a mapping worked out here would be a guess reported ' +
|
|
3754
3845
|
'as a measurement',
|
|
3755
3846
|
'all-bones': 'print every bone pair, not just the worst by position',
|
|
3847
|
+
geometry:
|
|
3848
|
+
`also write ${GEOMETRY_FILE} into each frame directory: per frame, every bone's world transform and every ` +
|
|
3849
|
+
"slot's region or mesh vertices in world units after skinning, plus each attachment's rest geometry — on the " +
|
|
3850
|
+
'frames\' own grid and viewport. Not with --slot/--hide: the geometry is the whole pose whatever is drawn',
|
|
3756
3851
|
'texture-from':
|
|
3757
3852
|
"also measure this run through this atlas's texels, keeping the candidate's own geometry, and report how much " +
|
|
3758
3853
|
'of the MAE is texture resampling rather than the rig — pass the atlas the reference frames were rendered ' +
|
|
@@ -3855,6 +3950,7 @@ const FLAG_VALUES: Record<string, string> = {
|
|
|
3855
3950
|
'atlas-in': '<file.atlas>',
|
|
3856
3951
|
'page-size': '<px>',
|
|
3857
3952
|
padding: '<px>',
|
|
3953
|
+
'page-edges': 'pot|free',
|
|
3858
3954
|
'texture-from': '<path>',
|
|
3859
3955
|
reference: '<dir|skeleton.json>',
|
|
3860
3956
|
'reference-atlas': '<path>',
|
|
@@ -3941,7 +4037,7 @@ const COMMANDS: CommandDoc[] = [
|
|
|
3941
4037
|
name: 'build',
|
|
3942
4038
|
usage: [
|
|
3943
4039
|
'rigc build --rig <path> --motion <path> --out <dir> [--manifest <path>] [--images <dir>] [--profile spine|spine-html] [--copy-images]',
|
|
3944
|
-
`rigc build … --pack [--page-size ${DEFAULT_PAGE_SIZE}] [--padding ${DEFAULT_PADDING}] (parts onto shared pages, written into --out)`,
|
|
4040
|
+
`rigc build … --pack [--page-size ${DEFAULT_PAGE_SIZE}] [--padding ${DEFAULT_PADDING}] [--page-edges pot|free] (parts onto shared pages, written into --out)`,
|
|
3945
4041
|
'rigc build … --atlas-in <skeleton.atlas> (resolve the parts against a pack somebody already made)',
|
|
3946
4042
|
'rigc build --cut <name> --cuts <cuts.json>',
|
|
3947
4043
|
],
|
|
@@ -3955,6 +4051,7 @@ const COMMANDS: CommandDoc[] = [
|
|
|
3955
4051
|
'pack',
|
|
3956
4052
|
'page-size',
|
|
3957
4053
|
'padding',
|
|
4054
|
+
'page-edges',
|
|
3958
4055
|
'atlas-in',
|
|
3959
4056
|
'cut',
|
|
3960
4057
|
'cuts',
|
|
@@ -3973,7 +4070,7 @@ const COMMANDS: CommandDoc[] = [
|
|
|
3973
4070
|
'this line said "the same arguments as build, minus --profile" and was false in both',
|
|
3974
4071
|
'directions (issue #697): --atlas-in was not listed here, so the one flag that lets this',
|
|
3975
4072
|
'command read what `ingest --art none` writes was reachable and undocumented, while',
|
|
3976
|
-
'--pack, --page-size, --padding and --copy-images are build\'s and do nothing here —',
|
|
4073
|
+
'--pack, --page-size, --padding, --page-edges and --copy-images are build\'s and do nothing here —',
|
|
3977
4074
|
'they decide what is WRITTEN, and this command writes nothing. What it takes is listed',
|
|
3978
4075
|
'above, and that is now the whole of it.',
|
|
3979
4076
|
],
|
|
@@ -4091,10 +4188,10 @@ const COMMANDS: CommandDoc[] = [
|
|
|
4091
4188
|
{
|
|
4092
4189
|
name: 'render',
|
|
4093
4190
|
usage: [
|
|
4094
|
-
'rigc render --candidate <dir | skeleton.json> [--animation <name>] [--skin <name>] [--fps 12] [--max 256] [--out render/]',
|
|
4191
|
+
'rigc render --candidate <dir | skeleton.json> [--animation <name>] [--skin <name>] [--fps 12] [--max 256] [--geometry] [--out render/]',
|
|
4095
4192
|
'rigc render … --slot <name[,name…]> | --hide <name[,name…]> (a subset of the slots, on the whole rig\'s grid)',
|
|
4096
4193
|
],
|
|
4097
|
-
flags: ['candidate', 'atlas', 'animation', 'skin', 'slot', 'hide', 'fps', 'max', 'out'],
|
|
4194
|
+
flags: ['candidate', 'atlas', 'animation', 'skin', 'slot', 'hide', 'fps', 'max', 'geometry', 'out'],
|
|
4098
4195
|
overrides: {
|
|
4099
4196
|
out: { value: '<dir>', meaning: 'directory to write the frame series into (default `render/`)' },
|
|
4100
4197
|
fps: { meaning: `frames per second to sample the animation at (default ${PROTOCOL_FPS})` },
|
|
@@ -4442,6 +4539,13 @@ try {
|
|
|
4442
4539
|
// the one reader's and already names the file, what it is and what rigc
|
|
4443
4540
|
// reads; a stack under it is the tool describing its own internals instead.
|
|
4444
4541
|
// Exit 1, like a compile error: the invocation was fine, a file was not.
|
|
4542
|
+
// A pose the geometry export cannot write as numbers (issue #864): the
|
|
4543
|
+
// invocation was fine and the skeleton posed a NaN or an infinity, so exit 1
|
|
4544
|
+
// like a file that is not a PNG. Raised before the first file is written.
|
|
4545
|
+
if (err instanceof GeometryError) {
|
|
4546
|
+
console.error(`rigc render: ${err.message}`);
|
|
4547
|
+
process.exit(1);
|
|
4548
|
+
}
|
|
4445
4549
|
if (err instanceof NotAPngError) {
|
|
4446
4550
|
console.error(`rigc: ${err.message}`);
|
|
4447
4551
|
process.exit(1);
|