spine-rigc 0.33.1 → 0.35.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 +5 -5
- package/cli.ts +68 -13
- package/docs/AUTHORING.md +504 -94
- package/docs/FACE.md +8 -7
- package/docs/INGEST.md +79 -20
- package/docs/SPEC_COVERAGE.md +1 -1
- package/package.json +1 -1
- package/src/chainfit.ts +64 -12
- package/src/compile.ts +503 -183
- package/src/deformgen.ts +14 -12
- package/src/diff.ts +24 -10
- package/src/ingest.ts +196 -26
- package/src/keyorder.ts +547 -0
- package/src/mesh.ts +60 -18
- package/src/pose.ts +147 -35
- package/src/rig.ts +119 -9
- package/src/timelines.ts +37 -20
- package/src/trackgen.ts +3 -2
- package/src/transform.ts +18 -10
- package/src/types.ts +73 -19
- package/src/validate.ts +185 -56
package/README.md
CHANGED
|
@@ -578,11 +578,11 @@ is in no field of the skeleton.
|
|
|
578
578
|
**And over twelve skeletons nobody here wrote.** The same run ingests every editor
|
|
579
579
|
export in the fetched example corpus, rebuilds it through the pack beside it and
|
|
580
580
|
`diff`s the result against the source: **12 of 12, no blockers, 1.000 on every measure
|
|
581
|
-
the report carries
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
[INGEST.md §2.3](docs/INGEST.md)
|
|
585
|
-
|
|
581
|
+
the report carries** — and each rebuild is its export's own text in canonical form,
|
|
582
|
+
apart from the header's `hash` and `spine`: the editor's project hash and the runtime
|
|
583
|
+
version rigc stamps, the two keys the rig spec has no field for by design (`IG83`,
|
|
584
|
+
12 of 12). [INGEST.md §2.3](docs/INGEST.md) states that pass line and why those two
|
|
585
|
+
are the exceptions.
|
|
586
586
|
|
|
587
587
|
**What it reads is skeleton JSON and nothing else** — no `.spine` project, no binary
|
|
588
588
|
`.skel`, no atlas, no art. So it never invents, and the things it cannot get out of
|
package/cli.ts
CHANGED
|
@@ -86,7 +86,8 @@ import {
|
|
|
86
86
|
type AtlasRegion,
|
|
87
87
|
} from './src/atlas.ts';
|
|
88
88
|
import { parseJsonWithPosition } from './src/json-position.ts';
|
|
89
|
-
import {
|
|
89
|
+
import { PARSER_DEFAULTS, parserReading } from './src/keyorder.ts';
|
|
90
|
+
import { float32Step } from './src/timelines.ts';
|
|
90
91
|
import { findRung, RUNG_IDS, type RungSkeleton } from './src/ladder.ts';
|
|
91
92
|
import {
|
|
92
93
|
DEFAULT_MAX_RESIDUAL,
|
|
@@ -623,8 +624,30 @@ function meshFit(m: CompileResult['meshes'][number]): string {
|
|
|
623
624
|
// why, in the place the figures stood, and prints no figure.
|
|
624
625
|
if (m.fitWithheld !== undefined) return ` fit not measured: ${m.fitWithheld}. ${PAGE_GRID_UNLOCATED}`;
|
|
625
626
|
if (m.coverage === undefined) return '';
|
|
626
|
-
|
|
627
|
-
|
|
627
|
+
// A count of the plate's own cells, so on a `scale:` page it is texels and
|
|
628
|
+
// says so rather than borrowing the overshoot's unit beside it (issue #762).
|
|
629
|
+
const hole = m.holePixels ? `, enclosing ${m.holePixels}${m.pageScale === undefined ? 'px' : ' texel(s)'} of hole` : '';
|
|
630
|
+
return ` covers ${(m.coverage * 100).toFixed(2)}% of the art, reaching ${m.overshoot?.toFixed(2) ?? '?'}px past it${meshFitGrid(m)}${hole}`;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* The grid a fit was taken on, when it is not the drawing's own (issue #762).
|
|
635
|
+
*
|
|
636
|
+
* The overshoot is stated in the drawing's pixels on every route — the unit an
|
|
637
|
+
* attachment's size is in — and on a page that declares a `scale:` other than
|
|
638
|
+
* 1 it was measured on the page's texels and divided by that scale. So it
|
|
639
|
+
* carries the coarser grid's step: on `scale: 0.5` a figure moves in steps of
|
|
640
|
+
* 2.00px of the drawing, and it need not equal the figure the page it was
|
|
641
|
+
* packed from reads except where the distance falls on whole texels. Said
|
|
642
|
+
* beside the figure, and nothing at all on a loose part or a page at scale 1,
|
|
643
|
+
* where the line is the one it always was.
|
|
644
|
+
*/
|
|
645
|
+
function meshFitGrid(m: CompileResult['meshes'][number]): string {
|
|
646
|
+
if (m.pageScale === undefined) return '';
|
|
647
|
+
return (
|
|
648
|
+
` (the drawing's pixels, measured on the page's texels at scale: ${m.pageScale} — a texel is ` +
|
|
649
|
+
`${(1 / m.pageScale).toFixed(2)}px of the drawing)`
|
|
650
|
+
);
|
|
628
651
|
}
|
|
629
652
|
|
|
630
653
|
/**
|
|
@@ -739,12 +762,14 @@ function deformKeyName(key: DeformKeyMeasure): string {
|
|
|
739
762
|
* The report's is the spec's own `t`; the survey's came back through
|
|
740
763
|
* `Float32Array`, because that is what `spine-core` reads a timeline's frames
|
|
741
764
|
* into — a key written `0.62` arrives as `0.6200000047683716`. So the tolerance
|
|
742
|
-
* is
|
|
743
|
-
*
|
|
744
|
-
*
|
|
765
|
+
* is one float32 step at this magnitude (`float32Step`): the compiler emits a
|
|
766
|
+
* key time as a float's name — the spec's own time when it names one, the float
|
|
767
|
+
* below it when it does not (issue #716) — so the loaded float is within one
|
|
768
|
+
* step of the spec's `t` either way, which is narrower than any key spacing the
|
|
769
|
+
* format can hold.
|
|
745
770
|
*/
|
|
746
771
|
function sameKeyTime(specTime: number, loaded: number): boolean {
|
|
747
|
-
return Math.abs(specTime - loaded) <=
|
|
772
|
+
return Math.abs(specTime - loaded) <= float32Step(loaded);
|
|
748
773
|
}
|
|
749
774
|
|
|
750
775
|
/**
|
|
@@ -1159,6 +1184,30 @@ const SCALE_PRODUCT_NOTE =
|
|
|
1159
1184
|
* foreign data. There is no honest SKIP either: an absent declaration is not
|
|
1160
1185
|
* "nothing to measure", it is "no way to know what was meant".
|
|
1161
1186
|
*/
|
|
1187
|
+
/**
|
|
1188
|
+
* An emitted key as the 4.3 parser reads it: every field its kind's
|
|
1189
|
+
* `PARSER_DEFAULTS` row lists and the key leaves out, at the row's value, then
|
|
1190
|
+
* the key's own fields. The emitter leaves a field at its parser default out
|
|
1191
|
+
* (issue #716), so a report that reads the file needs the row to say what the
|
|
1192
|
+
* runtime reads there — and reads it from the one table the emitter used.
|
|
1193
|
+
*/
|
|
1194
|
+
function asParsed(kind: string, key: Record<string, unknown>): Record<string, unknown> {
|
|
1195
|
+
const row = PARSER_DEFAULTS[kind];
|
|
1196
|
+
if (row === undefined) return key;
|
|
1197
|
+
const site = { object: key, previous: () => null };
|
|
1198
|
+
const filled: Record<string, unknown> = {};
|
|
1199
|
+
for (const field of Object.keys(row)) {
|
|
1200
|
+
const value = parserReading(row, site, field);
|
|
1201
|
+
if (value !== undefined) filled[field] = value;
|
|
1202
|
+
}
|
|
1203
|
+
return { ...filled, ...key };
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
/** An emitted key's time as the parser reads it — `0` where the key leaves it out. */
|
|
1207
|
+
function keyTimeOf(kind: string, key: Record<string, unknown>): unknown {
|
|
1208
|
+
return asParsed(kind, key).time;
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1162
1211
|
function scaleProduct(timelineName: string, key: Record<string, unknown>): string {
|
|
1163
1212
|
if (timelineName !== 'scale') return '';
|
|
1164
1213
|
const x = key.x;
|
|
@@ -2806,7 +2855,11 @@ function cmdExplain(flags: Record<string, string>): void {
|
|
|
2806
2855
|
for (const [timelineName, keys] of Object.entries(timelines)) {
|
|
2807
2856
|
console.log(` ${boneName}.${timelineName} ${keys.length} key(s)${drives}`);
|
|
2808
2857
|
if (timelineName === 'scale') console.log(` ${SCALE_PRODUCT_NOTE}`);
|
|
2809
|
-
for (const
|
|
2858
|
+
for (const emitted of keys) {
|
|
2859
|
+
// Every channel, as the parser reads it: a channel at its parser
|
|
2860
|
+
// default is not in the file (issue #716), and this list shows the
|
|
2861
|
+
// value a reader has to reason about rather than the bytes.
|
|
2862
|
+
const key = asParsed(`bone ${timelineName} key`, emitted);
|
|
2810
2863
|
const fields = Object.entries(key)
|
|
2811
2864
|
.filter(([k]) => k !== 'time' && k !== 'curve')
|
|
2812
2865
|
.map(([k, v]) => `${k}=${String(v)}`)
|
|
@@ -2823,7 +2876,8 @@ function cmdExplain(flags: Record<string, string>): void {
|
|
|
2823
2876
|
for (const [slotName, timelines] of Object.entries(anim.slots ?? {})) {
|
|
2824
2877
|
for (const [timelineName, keys] of Object.entries(timelines)) {
|
|
2825
2878
|
console.log(` ${slotName}.${timelineName} ${keys.length} key(s)`);
|
|
2826
|
-
for (const
|
|
2879
|
+
for (const emitted of keys) {
|
|
2880
|
+
const key = asParsed(`slot ${timelineName} key`, emitted);
|
|
2827
2881
|
const curve = key.curve;
|
|
2828
2882
|
const shape = Array.isArray(curve)
|
|
2829
2883
|
? `bezier[${curve.length}] ${curve.slice(12).join(', ')} <- alpha channel, absolute (t,v)`
|
|
@@ -2851,7 +2905,7 @@ function cmdExplain(flags: Record<string, string>): void {
|
|
|
2851
2905
|
.map(([k, v]) => `${k}=${String(v)}`)
|
|
2852
2906
|
.join(' ');
|
|
2853
2907
|
const curve = Array.isArray(key.curve) ? `bezier[${key.curve.length}]` : key.curve === 'stepped' ? 'stepped' : 'linear';
|
|
2854
|
-
console.log(` t=${String(key
|
|
2908
|
+
console.log(` t=${String(keyTimeOf(`${group} key`, key)).padEnd(7)} ${(fields || '(all defaults)').padEnd(46)} ${curve}`);
|
|
2855
2909
|
}
|
|
2856
2910
|
}
|
|
2857
2911
|
}
|
|
@@ -2868,7 +2922,7 @@ function cmdExplain(flags: Record<string, string>): void {
|
|
|
2868
2922
|
.map(([k, v]) => `${k}=${String(v)}`)
|
|
2869
2923
|
.join(' ');
|
|
2870
2924
|
const curve = Array.isArray(key.curve) ? `bezier[${key.curve.length}]` : key.curve === 'stepped' ? 'stepped' : 'linear';
|
|
2871
|
-
console.log(` t=${String(key
|
|
2925
|
+
console.log(` t=${String(keyTimeOf(`${group} ${timelineName} key`, key)).padEnd(7)} ${(fields || '(all defaults)').padEnd(46)} ${curve}`);
|
|
2872
2926
|
}
|
|
2873
2927
|
}
|
|
2874
2928
|
}
|
|
@@ -2882,7 +2936,8 @@ function cmdExplain(flags: Record<string, string>): void {
|
|
|
2882
2936
|
for (const [attName, timelines] of Object.entries(attMap)) {
|
|
2883
2937
|
for (const [timelineName, keys] of Object.entries(timelines)) {
|
|
2884
2938
|
console.log(` ${skinName}/${slotName}/${attName}.${timelineName} ${keys.length} key(s)`);
|
|
2885
|
-
for (const
|
|
2939
|
+
for (const emitted of keys) {
|
|
2940
|
+
const key = asParsed(`attachment ${timelineName} key`, emitted);
|
|
2886
2941
|
const run = Array.isArray(key.vertices) ? (key.vertices as number[]) : null;
|
|
2887
2942
|
const offset = typeof key.offset === 'number' ? key.offset : 0;
|
|
2888
2943
|
const span = run
|
|
@@ -2948,7 +3003,7 @@ function cmdExplain(flags: Record<string, string>): void {
|
|
|
2948
3003
|
.map((o) => `${o.slot}${o.offset >= 0 ? '+' : ''}${o.offset}`)
|
|
2949
3004
|
.join(' ')
|
|
2950
3005
|
: 'back to the setup order';
|
|
2951
|
-
console.log(` t=${String(key
|
|
3006
|
+
console.log(` t=${String(keyTimeOf('drawOrder key', key)).padEnd(7)} ${offsets}`);
|
|
2952
3007
|
}
|
|
2953
3008
|
}
|
|
2954
3009
|
}
|