spine-rigc 1.5.1 → 1.5.2
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/docs/AUTHORING.md +31 -1
- package/docs/MOTION.md +107 -68
- package/package.json +1 -1
- package/src/compile.ts +30 -1
- package/src/keys.ts +212 -4
- package/src/motion.ts +117 -3
- package/src/pose.ts +53 -0
- package/src/rig.ts +197 -31
- package/src/types.ts +42 -0
package/src/rig.ts
CHANGED
|
@@ -60,7 +60,8 @@
|
|
|
60
60
|
* with no manifest at all declares them here.
|
|
61
61
|
*/
|
|
62
62
|
import { CompileError, NotImplementedError } from './errors.ts';
|
|
63
|
-
import { dottedPath, refuseNumbersTheFileCannotCarry, refuseUnknownKeys } from './keys.ts';
|
|
63
|
+
import { dottedPath, refuseNumbersTheFileCannotCarry, refuseUnknownKeys, refuseValuesOfTheWrongType } from './keys.ts';
|
|
64
|
+
import type { ShapeVisit, SpecValueType } from './keys.ts';
|
|
64
65
|
|
|
65
66
|
export { CompileError, NotImplementedError };
|
|
66
67
|
|
|
@@ -1776,11 +1777,11 @@ function isObj(v: unknown): v is Record<string, unknown> {
|
|
|
1776
1777
|
* Every key each shape of this format owns, keyed by the interface above that
|
|
1777
1778
|
* declares it — the runtime shadow of the types, which TypeScript erases.
|
|
1778
1779
|
*
|
|
1779
|
-
* 🔒 **Hand-written and mechanically held to the interfaces.** `
|
|
1780
|
+
* 🔒 **Hand-written and mechanically held to the interfaces.** `CUR17` in
|
|
1780
1781
|
* `selftest.ts` reads this file's own source, extracts each named interface's
|
|
1781
1782
|
* field list and compares it to the entry here, so the pair cannot drift: adding
|
|
1782
1783
|
* a field and forgetting this table is a red run, not a key an author cannot
|
|
1783
|
-
* write. `
|
|
1784
|
+
* write. `CUR18` closes the other direction — a key declared here and occurring
|
|
1784
1785
|
* nowhere else in the tree is refused, which is exactly what `scaleYMode` was.
|
|
1785
1786
|
*
|
|
1786
1787
|
* ⚠️ `RigUnimplementedAttachment` is deliberately absent. It carries
|
|
@@ -1854,6 +1855,149 @@ export const RIG_KEYS = {
|
|
|
1854
1855
|
RigSoftRegion: ['bone', 'mask'],
|
|
1855
1856
|
} as const satisfies Record<string, readonly string[]>;
|
|
1856
1857
|
|
|
1858
|
+
/**
|
|
1859
|
+
* The type every key of `RIG_KEYS` holds, shape by shape — what
|
|
1860
|
+
* `refuseValuesOfTheWrongType` refuses a value against (issue #890).
|
|
1861
|
+
*
|
|
1862
|
+
* 🔒 **Beside the key table, and held equal to it twice.** `satisfies` over
|
|
1863
|
+
* `RigTypeTable` makes a key typed here and absent there, or admitted there and
|
|
1864
|
+
* untyped here, a type error; the selftest compares the two at runtime as well,
|
|
1865
|
+
* and derives each checked type from the interface the row is named for, so a
|
|
1866
|
+
* field declared `number` and typed `string` here is a red run rather than a
|
|
1867
|
+
* refusal of correct work.
|
|
1868
|
+
*
|
|
1869
|
+
* Where an unchecked type (`object`, `enum`, `mixed` — see `SPEC_VALUE_TYPES`)
|
|
1870
|
+
* is refused, and by what:
|
|
1871
|
+
*
|
|
1872
|
+
* - the version tag `spec` — `parseRigSpec`, before anything else;
|
|
1873
|
+
* - `inherit` — `resolveBoneInherit`; `blend` — the slot loop
|
|
1874
|
+
* (`RIG_SLOT_BLEND`); `consumerDrivenMix[].type` — the invariants block;
|
|
1875
|
+
* - a constraint's `type` — `buildRigConstraint`; `scaleY` on ik, a path
|
|
1876
|
+
* constraint's three modes and a slider's `property` — the builder of that
|
|
1877
|
+
* constraint kind, each listing the names that exist;
|
|
1878
|
+
* - an attachment's `type` — the attachment reader; `depth.near` — the depth
|
|
1879
|
+
* map reader;
|
|
1880
|
+
* - `RigSegmentsGenerator.bones` (`mixed`: a name, a chain of names or a span)
|
|
1881
|
+
* — the segments generator, by index.
|
|
1882
|
+
*
|
|
1883
|
+
* 🚨 Measured when this table was written, three `enum` keys have **no** owner,
|
|
1884
|
+
* and the table does not pretend otherwise: `boneIndexing` built green at `5`
|
|
1885
|
+
* and at `"foo"` (on a weighted mesh too), `from.rotation` built green at `5`
|
|
1886
|
+
* and at `"foo"` on an anchored bone, and a generator `kind` of `5` or `"foo"`
|
|
1887
|
+
* threw a `TypeError` (*undefined is not an object (evaluating
|
|
1888
|
+
* 'generator.size')*) rather than the mesh builder's refusal. Each is a closed
|
|
1889
|
+
* set of names with nobody holding the name, which is a value refusal and not a
|
|
1890
|
+
* type one, so it is not fixed here. `scaleY` on a physics constraint was not
|
|
1891
|
+
* measured.
|
|
1892
|
+
*/
|
|
1893
|
+
type RigTypeTable = {
|
|
1894
|
+
readonly [S in keyof typeof RIG_KEYS]: { readonly [K in (typeof RIG_KEYS)[S][number]]: SpecValueType };
|
|
1895
|
+
};
|
|
1896
|
+
|
|
1897
|
+
export const RIG_TYPES = {
|
|
1898
|
+
RigSpec: {
|
|
1899
|
+
spec: 'enum', name: 'string', note: 'string', skeleton: 'object', images: 'string', bones: 'object[]', slots: 'object[]',
|
|
1900
|
+
skins: 'map of object', constraints: 'object[]', events: 'map of object', invariants: 'object',
|
|
1901
|
+
},
|
|
1902
|
+
RigSkeletonHeader: {
|
|
1903
|
+
x: 'number', y: 'number', width: 'number | null', height: 'number | null', fps: 'number', referenceScale: 'number',
|
|
1904
|
+
images: 'string', audio: 'string | null',
|
|
1905
|
+
},
|
|
1906
|
+
RigBone: {
|
|
1907
|
+
name: 'string', parent: 'string', length: 'number', x: 'number', y: 'number', rotation: 'number', scaleX: 'number',
|
|
1908
|
+
scaleY: 'number', shearX: 'number', shearY: 'number', inherit: 'enum', skin: 'boolean', color: 'string', icon: 'string',
|
|
1909
|
+
from: 'object',
|
|
1910
|
+
},
|
|
1911
|
+
RigBoneFrom: { anchor: 'string', slotWindow: 'string', meshCenter: 'string', rotation: 'enum' },
|
|
1912
|
+
RigSlot: { name: 'string', bone: 'string', attachment: 'string | null', color: 'string', dark: 'string', blend: 'enum' },
|
|
1913
|
+
RigEvent: { int: 'number', float: 'number', string: 'string', audio: 'string', volume: 'number', balance: 'number' },
|
|
1914
|
+
RigInvariants: {
|
|
1915
|
+
meshSlots: 'number', meshTriangles: 'number', axisBone: 'string', massBone: 'string', detached: 'object[]',
|
|
1916
|
+
deformMayFold: 'object[]', editorRoundTrip: 'boolean', consumerDrivenMix: 'object[]', idleDrivesMeshes: 'object',
|
|
1917
|
+
},
|
|
1918
|
+
RigIdleDrivesMeshes: { why: 'string' },
|
|
1919
|
+
RigDetachedRule: { bone: 'string', notUnder: 'string', why: 'string' },
|
|
1920
|
+
RigDeformFoldExemption: { slot: 'string', why: 'string' },
|
|
1921
|
+
RigConsumerDrivenMix: { constraint: 'string', type: 'enum', why: 'string' },
|
|
1922
|
+
RigSkinEntry: {
|
|
1923
|
+
attachments: 'map of object', bones: 'string[]', ik: 'string[]', transform: 'string[]', path: 'string[]',
|
|
1924
|
+
physics: 'string[]', slider: 'string[]',
|
|
1925
|
+
},
|
|
1926
|
+
RigIkConstraint: {
|
|
1927
|
+
name: 'string', skin: 'boolean', type: 'enum', bones: 'string[]', target: 'string', scaleY: 'enum', mix: 'number',
|
|
1928
|
+
softness: 'number', bendPositive: 'boolean', compress: 'boolean', stretch: 'boolean',
|
|
1929
|
+
},
|
|
1930
|
+
RigTransformConstraint: {
|
|
1931
|
+
name: 'string', skin: 'boolean', type: 'enum', bones: 'string[]', source: 'string', localSource: 'boolean',
|
|
1932
|
+
localTarget: 'boolean', additive: 'boolean', clamp: 'boolean', properties: 'map of object',
|
|
1933
|
+
rotation: 'number', x: 'number', y: 'number', scaleX: 'number', scaleY: 'number', shearY: 'number',
|
|
1934
|
+
mixRotate: 'number', mixX: 'number', mixY: 'number', mixScaleX: 'number', mixScaleY: 'number', mixShearY: 'number',
|
|
1935
|
+
},
|
|
1936
|
+
RigPathConstraint: {
|
|
1937
|
+
name: 'string', skin: 'boolean', type: 'enum', bones: 'string[]', slot: 'string', positionMode: 'enum',
|
|
1938
|
+
spacingMode: 'enum', rotateMode: 'enum', rotation: 'number', position: 'number', spacing: 'number',
|
|
1939
|
+
mixRotate: 'number', mixX: 'number', mixY: 'number',
|
|
1940
|
+
},
|
|
1941
|
+
RigPhysicsConstraint: {
|
|
1942
|
+
name: 'string', skin: 'boolean', type: 'enum', bone: 'string', x: 'number', y: 'number', rotate: 'number',
|
|
1943
|
+
scaleX: 'number', shearX: 'number', scaleY: 'enum', limit: 'number', fps: 'number', inertia: 'number',
|
|
1944
|
+
strength: 'number', damping: 'number', mass: 'number', wind: 'number', gravity: 'number', mix: 'number',
|
|
1945
|
+
inertiaGlobal: 'boolean', strengthGlobal: 'boolean', dampingGlobal: 'boolean', massGlobal: 'boolean',
|
|
1946
|
+
windGlobal: 'boolean', gravityGlobal: 'boolean', mixGlobal: 'boolean',
|
|
1947
|
+
},
|
|
1948
|
+
RigSliderConstraint: {
|
|
1949
|
+
name: 'string', skin: 'boolean', type: 'enum', animation: 'string', mix: 'number', additive: 'boolean',
|
|
1950
|
+
loop: 'boolean', bone: 'string', property: 'enum', from: 'number', to: 'number', scale: 'number', max: 'number',
|
|
1951
|
+
local: 'boolean', time: 'number',
|
|
1952
|
+
},
|
|
1953
|
+
RigTransformProperty: { offset: 'number', to: 'map of object' },
|
|
1954
|
+
RigTransformTo: { offset: 'number', max: 'number', scale: 'number' },
|
|
1955
|
+
RigRegionAttachment: {
|
|
1956
|
+
type: 'enum', name: 'string', path: 'string', image: 'string', x: 'number', y: 'number', rotation: 'number',
|
|
1957
|
+
scaleX: 'number', scaleY: 'number', width: 'number', height: 'number', color: 'string', sequence: 'object',
|
|
1958
|
+
},
|
|
1959
|
+
RigMeshAttachment: {
|
|
1960
|
+
type: 'enum', name: 'string', path: 'string', image: 'string', uvs: 'number[]', triangles: 'number[]',
|
|
1961
|
+
vertices: 'number[]', weights: 'object[][]', boneIndexing: 'enum', hull: 'number', edges: 'number[]',
|
|
1962
|
+
width: 'number', height: 'number', color: 'string', generator: 'object', sequence: 'object',
|
|
1963
|
+
},
|
|
1964
|
+
RigLinkedMeshAttachment: {
|
|
1965
|
+
type: 'enum', name: 'string', path: 'string', image: 'string', source: 'string', slot: 'string', skin: 'string',
|
|
1966
|
+
timelines: 'boolean', width: 'number', height: 'number', color: 'string', sequence: 'object',
|
|
1967
|
+
uvs: 'number[]', triangles: 'number[]', vertices: 'number[]', weights: 'object[][]', boneIndexing: 'enum',
|
|
1968
|
+
hull: 'number', edges: 'number[]', generator: 'object',
|
|
1969
|
+
},
|
|
1970
|
+
RigMeshBinding: { bone: 'string', x: 'number', y: 'number', weight: 'number' },
|
|
1971
|
+
RigSequence: { count: 'number', start: 'number', digits: 'number', setup: 'number' },
|
|
1972
|
+
RigBoundingBoxAttachment: {
|
|
1973
|
+
vertexCount: 'number', vertices: 'number[]', weights: 'object[][]', boneIndexing: 'enum', color: 'string', type: 'enum', name: 'string',
|
|
1974
|
+
},
|
|
1975
|
+
RigClippingAttachment: {
|
|
1976
|
+
vertexCount: 'number', vertices: 'number[]', weights: 'object[][]', boneIndexing: 'enum', color: 'string', type: 'enum', name: 'string',
|
|
1977
|
+
end: 'string', convex: 'boolean', inverse: 'boolean',
|
|
1978
|
+
},
|
|
1979
|
+
RigPathAttachment: {
|
|
1980
|
+
vertexCount: 'number', vertices: 'number[]', weights: 'object[][]', boneIndexing: 'enum', color: 'string', type: 'enum', name: 'string',
|
|
1981
|
+
closed: 'boolean', constantSpeed: 'boolean', lengths: 'number[]',
|
|
1982
|
+
},
|
|
1983
|
+
RigRingGenerator: {
|
|
1984
|
+
kind: 'enum', hull: 'number[][]', center: 'number[]', inner: 'number', size: 'number[]', bias: 'object',
|
|
1985
|
+
controls: 'string[]',
|
|
1986
|
+
},
|
|
1987
|
+
RigRibbonGenerator: { kind: 'enum', size: 'number[]', rows: 'number', chain: 'string[]' },
|
|
1988
|
+
RigContourGenerator: {
|
|
1989
|
+
kind: 'enum', tolerance: 'number', margin: 'number', maxVertices: 'number', alpha: 'number', depth: 'object',
|
|
1990
|
+
soft: 'object',
|
|
1991
|
+
},
|
|
1992
|
+
RigGridGenerator: { kind: 'enum', us: 'number[]', vs: 'number[]', cols: 'number', rows: 'number', depth: 'object', soft: 'object' },
|
|
1993
|
+
RigSegmentsGenerator: { kind: 'enum', cell: 'number', bones: 'mixed', falloff: 'object', alpha: 'number', anchor: 'number[]' },
|
|
1994
|
+
RigSegmentsFalloff: { power: 'number', radius: 'number', maxBones: 'number', minWeight: 'number' },
|
|
1995
|
+
RigSegmentSpan: { bone: 'string', from: 'number[]', to: 'number[]' },
|
|
1996
|
+
RigMeshBias: { axis_deg: 'number', ramp: 'number[]' },
|
|
1997
|
+
RigDepthMap: { image: 'string', near: 'enum', zScale: 'number', gamma: 'number', contrast: 'number', bias: 'number' },
|
|
1998
|
+
RigSoftRegion: { bone: 'string', mask: 'string' },
|
|
1999
|
+
} as const satisfies RigTypeTable;
|
|
2000
|
+
|
|
1857
2001
|
/**
|
|
1858
2002
|
* The five constraint `type` names, and the shape each one's keys come from.
|
|
1859
2003
|
*
|
|
@@ -1984,22 +2128,25 @@ function checkRigSequence(att: Record<string, unknown>, who: string, where: stri
|
|
|
1984
2128
|
* refusal, and naming its keys would be a second opinion on a fault already
|
|
1985
2129
|
* reported (see the note at the head of `parseMotionSpec`).
|
|
1986
2130
|
*/
|
|
1987
|
-
function checkRigSpecKeys(raw: Record<string, unknown>, where: string):
|
|
1988
|
-
const
|
|
1989
|
-
|
|
2131
|
+
function checkRigSpecKeys(raw: Record<string, unknown>, where: string): Array<{ node: Record<string, unknown>; shape: keyof typeof RIG_KEYS; path: Array<string | number> }> {
|
|
2132
|
+
const visits: Array<{ node: Record<string, unknown>; shape: keyof typeof RIG_KEYS; path: Array<string | number> }> = [];
|
|
2133
|
+
const at = (node: unknown, shape: keyof typeof RIG_KEYS, what: string, path: Array<string | number>): void => {
|
|
2134
|
+
if (!isObj(node)) return;
|
|
2135
|
+
refuseUnknownKeys(node, RIG_KEYS[shape], where, what);
|
|
2136
|
+
visits.push({ node, shape, path });
|
|
1990
2137
|
};
|
|
1991
2138
|
|
|
1992
|
-
at(raw, 'RigSpec', 'this rig spec');
|
|
1993
|
-
at(raw.skeleton, 'RigSkeletonHeader', '"skeleton"');
|
|
2139
|
+
at(raw, 'RigSpec', 'this rig spec', []);
|
|
2140
|
+
at(raw.skeleton, 'RigSkeletonHeader', '"skeleton"', ['skeleton']);
|
|
1994
2141
|
|
|
1995
2142
|
for (const [i, bone] of (Array.isArray(raw.bones) ? raw.bones : []).entries()) {
|
|
1996
2143
|
const who = isObj(bone) && typeof bone.name === 'string' ? `bone "${bone.name}"` : `bones[${i}]`;
|
|
1997
|
-
at(bone, 'RigBone', who);
|
|
1998
|
-
if (isObj(bone)) at(bone.from, 'RigBoneFrom', `${who}'s "from"
|
|
2144
|
+
at(bone, 'RigBone', who, ['bones', i]);
|
|
2145
|
+
if (isObj(bone)) at(bone.from, 'RigBoneFrom', `${who}'s "from"`, ['bones', i, 'from']);
|
|
1999
2146
|
}
|
|
2000
2147
|
|
|
2001
2148
|
for (const [i, slot] of (Array.isArray(raw.slots) ? raw.slots : []).entries()) {
|
|
2002
|
-
at(slot, 'RigSlot', isObj(slot) && typeof slot.name === 'string' ? `slot "${slot.name}"` : `slots[${i}]
|
|
2149
|
+
at(slot, 'RigSlot', isObj(slot) && typeof slot.name === 'string' ? `slot "${slot.name}"` : `slots[${i}]`, ['slots', i]);
|
|
2003
2150
|
}
|
|
2004
2151
|
|
|
2005
2152
|
for (const [i, constraint] of (Array.isArray(raw.constraints) ? raw.constraints : []).entries()) {
|
|
@@ -2010,32 +2157,32 @@ function checkRigSpecKeys(raw: Record<string, unknown>, where: string): void {
|
|
|
2010
2157
|
// that exist; there is no key set to check it against and no honest one to
|
|
2011
2158
|
// guess, so it goes past here to the message that can say something.
|
|
2012
2159
|
if (shape === undefined) continue;
|
|
2013
|
-
at(constraint, shape, `${named} (${String(constraint.type)})
|
|
2160
|
+
at(constraint, shape, `${named} (${String(constraint.type)})`, ['constraints', i]);
|
|
2014
2161
|
for (const [from, entry] of Object.entries(isObj(constraint.properties) ? constraint.properties : {})) {
|
|
2015
|
-
at(entry, 'RigTransformProperty', `${named} properties."${from}"
|
|
2162
|
+
at(entry, 'RigTransformProperty', `${named} properties."${from}"`, ['constraints', i, 'properties', from]);
|
|
2016
2163
|
if (!isObj(entry)) continue;
|
|
2017
2164
|
for (const [to, driven] of Object.entries(isObj(entry.to) ? entry.to : {})) {
|
|
2018
|
-
at(driven, 'RigTransformTo', `${named} properties."${from}".to."${to}"
|
|
2165
|
+
at(driven, 'RigTransformTo', `${named} properties."${from}".to."${to}"`, ['constraints', i, 'properties', from, 'to', to]);
|
|
2019
2166
|
}
|
|
2020
2167
|
}
|
|
2021
2168
|
}
|
|
2022
2169
|
|
|
2023
2170
|
for (const [name, event] of Object.entries(isObj(raw.events) ? raw.events : {})) {
|
|
2024
|
-
at(event, 'RigEvent', `event "${name}"
|
|
2171
|
+
at(event, 'RigEvent', `event "${name}"`, ['events', name]);
|
|
2025
2172
|
}
|
|
2026
2173
|
|
|
2027
2174
|
if (isObj(raw.invariants)) {
|
|
2028
|
-
at(raw.invariants, 'RigInvariants', '"invariants"');
|
|
2175
|
+
at(raw.invariants, 'RigInvariants', '"invariants"', ['invariants']);
|
|
2029
2176
|
for (const [i, rule] of (Array.isArray(raw.invariants.detached) ? raw.invariants.detached : []).entries()) {
|
|
2030
|
-
at(rule, 'RigDetachedRule', `invariants.detached[${i}]
|
|
2177
|
+
at(rule, 'RigDetachedRule', `invariants.detached[${i}]`, ['invariants', 'detached', i]);
|
|
2031
2178
|
}
|
|
2032
2179
|
for (const [i, rule] of (Array.isArray(raw.invariants.deformMayFold) ? raw.invariants.deformMayFold : []).entries()) {
|
|
2033
|
-
at(rule, 'RigDeformFoldExemption', `invariants.deformMayFold[${i}]
|
|
2180
|
+
at(rule, 'RigDeformFoldExemption', `invariants.deformMayFold[${i}]`, ['invariants', 'deformMayFold', i]);
|
|
2034
2181
|
}
|
|
2035
2182
|
for (const [i, rule] of (Array.isArray(raw.invariants.consumerDrivenMix) ? raw.invariants.consumerDrivenMix : []).entries()) {
|
|
2036
|
-
at(rule, 'RigConsumerDrivenMix', `invariants.consumerDrivenMix[${i}]
|
|
2183
|
+
at(rule, 'RigConsumerDrivenMix', `invariants.consumerDrivenMix[${i}]`, ['invariants', 'consumerDrivenMix', i]);
|
|
2037
2184
|
}
|
|
2038
|
-
at(raw.invariants.idleDrivesMeshes, 'RigIdleDrivesMeshes', 'invariants.idleDrivesMeshes');
|
|
2185
|
+
at(raw.invariants.idleDrivesMeshes, 'RigIdleDrivesMeshes', 'invariants.idleDrivesMeshes', ['invariants', 'idleDrivesMeshes']);
|
|
2039
2186
|
}
|
|
2040
2187
|
|
|
2041
2188
|
for (const [skinName, skin] of Object.entries(isObj(raw.skins) ? raw.skins : {})) {
|
|
@@ -2044,6 +2191,10 @@ function checkRigSpecKeys(raw: Record<string, unknown>, where: string): void {
|
|
|
2044
2191
|
// likeliest cause (a slot left outside `attachments`), so it is reused
|
|
2045
2192
|
// rather than restated — one refusal per fault.
|
|
2046
2193
|
const parts = splitRigSkin(skin as RigSkin, `${where}: skin "${skinName}"`);
|
|
2194
|
+
// The long form's own lists are typed too; `splitRigSkin` has just refused
|
|
2195
|
+
// a key outside them, so the row's keys are the node's keys.
|
|
2196
|
+
if (parts.explicit) visits.push({ node: skin, shape: 'RigSkinEntry', path: ['skins', skinName] });
|
|
2197
|
+
const base: Array<string | number> = parts.explicit ? ['skins', skinName, 'attachments'] : ['skins', skinName];
|
|
2047
2198
|
for (const [slot, placeholders] of Object.entries(parts.attachments)) {
|
|
2048
2199
|
if (!isObj(placeholders)) continue;
|
|
2049
2200
|
for (const [placeholder, att] of Object.entries(placeholders)) {
|
|
@@ -2075,14 +2226,15 @@ function checkRigSpecKeys(raw: Record<string, unknown>, where: string): void {
|
|
|
2075
2226
|
'series would be dropped in silence. Remove it, or put it on a region or a mesh.',
|
|
2076
2227
|
);
|
|
2077
2228
|
}
|
|
2078
|
-
|
|
2229
|
+
const here = [...base, slot, placeholder];
|
|
2230
|
+
at(att, shape, `${who} (${type})`, here);
|
|
2079
2231
|
if (att.sequence !== undefined) {
|
|
2080
|
-
at(att.sequence, 'RigSequence', `${who} "sequence"
|
|
2232
|
+
at(att.sequence, 'RigSequence', `${who} "sequence"`, [...here, 'sequence']);
|
|
2081
2233
|
checkRigSequence(att, who, where);
|
|
2082
2234
|
}
|
|
2083
2235
|
for (const [i, vertex] of (Array.isArray(att.weights) ? att.weights : []).entries()) {
|
|
2084
2236
|
for (const [j, binding] of (Array.isArray(vertex) ? vertex : []).entries()) {
|
|
2085
|
-
at(binding, 'RigMeshBinding', `${who} weights[${i}][${j}]
|
|
2237
|
+
at(binding, 'RigMeshBinding', `${who} weights[${i}][${j}]`, [...here, 'weights', i, j]);
|
|
2086
2238
|
}
|
|
2087
2239
|
}
|
|
2088
2240
|
if (!isObj(att.generator)) continue;
|
|
@@ -2091,17 +2243,18 @@ function checkRigSpecKeys(raw: Record<string, unknown>, where: string): void {
|
|
|
2091
2243
|
// Same rule as an unknown attachment type: an unknown `kind` is the mesh
|
|
2092
2244
|
// builder's refusal, which can name the four that exist.
|
|
2093
2245
|
if (genShape === undefined) continue;
|
|
2094
|
-
at(gen, genShape, `${who} generator (${String(gen.kind)})
|
|
2095
|
-
at(gen.bias, 'RigMeshBias', `${who} generator.bias
|
|
2096
|
-
at(gen.depth, 'RigDepthMap', `${who} generator.depth
|
|
2097
|
-
at(gen.soft, 'RigSoftRegion', `${who} generator.soft
|
|
2098
|
-
at(gen.falloff, 'RigSegmentsFalloff', `${who} generator.falloff
|
|
2246
|
+
at(gen, genShape, `${who} generator (${String(gen.kind)})`, [...here, 'generator']);
|
|
2247
|
+
at(gen.bias, 'RigMeshBias', `${who} generator.bias`, [...here, 'generator', 'bias']);
|
|
2248
|
+
at(gen.depth, 'RigDepthMap', `${who} generator.depth`, [...here, 'generator', 'depth']);
|
|
2249
|
+
at(gen.soft, 'RigSoftRegion', `${who} generator.soft`, [...here, 'generator', 'soft']);
|
|
2250
|
+
at(gen.falloff, 'RigSegmentsFalloff', `${who} generator.falloff`, [...here, 'generator', 'falloff']);
|
|
2099
2251
|
for (const [i, entry] of (Array.isArray(gen.bones) ? gen.bones : []).entries()) {
|
|
2100
|
-
at(entry, 'RigSegmentSpan', `${who} generator.bones[${i}]
|
|
2252
|
+
at(entry, 'RigSegmentSpan', `${who} generator.bones[${i}]`, [...here, 'generator', 'bones', i]);
|
|
2101
2253
|
}
|
|
2102
2254
|
}
|
|
2103
2255
|
}
|
|
2104
2256
|
}
|
|
2257
|
+
return visits;
|
|
2105
2258
|
}
|
|
2106
2259
|
|
|
2107
2260
|
|
|
@@ -2176,11 +2329,24 @@ export function parseRigSpec(raw: unknown, where: string): RigSpec {
|
|
|
2176
2329
|
// compiler does not read" for a format it has never read at all. The three
|
|
2177
2330
|
// throws directly below are the required-key checks, and each of them is the
|
|
2178
2331
|
// consequence a typo at the root produces.
|
|
2179
|
-
checkRigSpecKeys(raw, where);
|
|
2332
|
+
const visits = checkRigSpecKeys(raw, where);
|
|
2333
|
+
const place = rigPlace(raw);
|
|
2334
|
+
// Every value the scan admitted, against the type its key holds, before any
|
|
2335
|
+
// reader — here or in `compile` — has done arithmetic on it (issue #890).
|
|
2336
|
+
// ⚠️ It runs ahead of this function's own field checks on purpose: those read
|
|
2337
|
+
// values too, and a reader that meets a wrong type names the wrong fault —
|
|
2338
|
+
// `constraint.skin === true` reads `"skin": "true"` as a constraint that asks
|
|
2339
|
+
// for no skin. The price, measured, is three controls whose type half pinned an
|
|
2340
|
+
// older sentence (RF86, PS177, PS189); their other halves are unchanged.
|
|
2341
|
+
refuseValuesOfTheWrongType(
|
|
2342
|
+
visits.map((v) => ({ node: v.node, shape: v.shape, name: (tail) => place([...v.path, ...tail]) })),
|
|
2343
|
+
RIG_TYPES,
|
|
2344
|
+
where,
|
|
2345
|
+
);
|
|
2180
2346
|
// After the key check, so a misspelled key is named as a misspelling before
|
|
2181
2347
|
// its value is judged, and before every reader below, so no range rule or
|
|
2182
2348
|
// derived number ever meets a value the file cannot carry (issue #881).
|
|
2183
|
-
refuseNumbersTheFileCannotCarry(raw, where,
|
|
2349
|
+
refuseNumbersTheFileCannotCarry(raw, where, place);
|
|
2184
2350
|
|
|
2185
2351
|
if (typeof raw.name !== 'string' || raw.name.length === 0) {
|
|
2186
2352
|
throw new CompileError(`${where}: a rig spec needs a "name" — a motion spec names it to pick this rig`);
|
package/src/types.ts
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
import type { AtlasRegion } from './atlas.ts';
|
|
18
18
|
import type { DeformTransform, DeformTransformReport } from './deformgen.ts';
|
|
19
19
|
import type { TurnCeiling } from './depth.ts';
|
|
20
|
+
import type { SpecTypeRow } from './keys.ts';
|
|
20
21
|
import type { MeshKind } from './mesh.ts';
|
|
21
22
|
import type { TrackDerive, TrackDeriveReport } from './trackgen.ts';
|
|
22
23
|
|
|
@@ -169,6 +170,47 @@ export interface FaceManifest {
|
|
|
169
170
|
anchors?: Record<string, number[]>;
|
|
170
171
|
}
|
|
171
172
|
|
|
173
|
+
/**
|
|
174
|
+
* The type every field of the cut manifest holds, shape by shape — what
|
|
175
|
+
* `refuseValuesOfTheWrongType` refuses a manifest value against (issue #890).
|
|
176
|
+
* The three interfaces above name three rows; the inline object types they
|
|
177
|
+
* declare (`crop`, `axis`, `stroke`, `roi`, `mesh.bias`) are rows named
|
|
178
|
+
* `<interface>.<field>`, and the selftest derives every row's keys and checked
|
|
179
|
+
* types from the declarations here.
|
|
180
|
+
*
|
|
181
|
+
* ⚠️ A manifest has no key scan, and this table does not add one: a manifest is
|
|
182
|
+
* often also the record of the pipeline that made the art and carries fields
|
|
183
|
+
* rigc does not read. A key outside a row is left alone, exactly as before; a
|
|
184
|
+
* key inside one must hold its type. Measured before the walk existed, 62 of 84
|
|
185
|
+
* wrong-typed plants on the three fixture manifests built green —
|
|
186
|
+
* `crop.h: "256"` among them — and 4 threw a TypeError from `node:path`.
|
|
187
|
+
*
|
|
188
|
+
* Unchecked: `mesh.kind` and `mesh.hull` (`enum`) — the manifest mesh reader;
|
|
189
|
+
* `crop` and the other nested shapes, `parts` and `mesh` (`object`) — the
|
|
190
|
+
* readers that walk them.
|
|
191
|
+
*/
|
|
192
|
+
export const MANIFEST_TYPES = {
|
|
193
|
+
FaceManifest: {
|
|
194
|
+
schema: 'string', crop: 'object', base: 'string', state_machine: 'map of string[]', parts: 'object[]',
|
|
195
|
+
insertion: 'number[]', axis: 'object', stroke: 'object', roi: 'object', anchors: 'map of number[]',
|
|
196
|
+
},
|
|
197
|
+
'FaceManifest.crop': { x: 'number', y: 'number', w: 'number', h: 'number', resample: 'string' },
|
|
198
|
+
'FaceManifest.axis': { deg: 'number', unit: 'number[]' },
|
|
199
|
+
'FaceManifest.stroke': {
|
|
200
|
+
amplitude: 'number', extension: 'number', contact_depth: 'number | null', cap_containment_ceiling: 'number | null',
|
|
201
|
+
},
|
|
202
|
+
'FaceManifest.roi': { x: 'number', y: 'number', w: 'number', h: 'number' },
|
|
203
|
+
FaceManifestPart: {
|
|
204
|
+
slot: 'string', rig_slot: 'string', draw_order: 'number', image: 'string | null', offset: 'number[]', size: 'number[]',
|
|
205
|
+
state_key: 'string', states: 'map of string | null', polygon: 'number[][]', mesh: 'object',
|
|
206
|
+
},
|
|
207
|
+
FaceManifestMesh: {
|
|
208
|
+
kind: 'enum', hull: 'enum', center: 'number[]', inner: 'number', control_bone: 'string', control_bones: 'string[]',
|
|
209
|
+
rows: 'number', chain: 'string[]', bias: 'object',
|
|
210
|
+
},
|
|
211
|
+
'FaceManifestMesh.bias': { axis_deg: 'number', ramp: 'number[]', note: 'string' },
|
|
212
|
+
} as const satisfies Record<string, SpecTypeRow>;
|
|
213
|
+
|
|
172
214
|
// ---------------------------------------------------------------------------
|
|
173
215
|
// Motion spec (spec: "rigc-motion/1")
|
|
174
216
|
// ---------------------------------------------------------------------------
|