spine-rigc 2.19.1 → 2.20.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/package.json +2 -1
- package/src/areaband.ts +64 -1
- package/src/cli/shared.ts +4 -31
- package/src/deformsurvey.ts +6 -64
- package/src/mesh.ts +52 -12
- package/src/meshcompare.ts +1188 -0
- package/src/meshquality.ts +104 -6
- package/src/package_meta.ts +48 -0
package/src/meshquality.ts
CHANGED
|
@@ -299,6 +299,8 @@ export interface MeasureRow {
|
|
|
299
299
|
raster?: RasterSensitivity;
|
|
300
300
|
/** Sampled rows — correction 2. */
|
|
301
301
|
sampling?: { domain: string; count: number };
|
|
302
|
+
/** Motion rows only (stage C, `src/meshcompare.ts`): how the row's value was taken over the schedule. */
|
|
303
|
+
motion?: MotionRowDetail;
|
|
302
304
|
}
|
|
303
305
|
|
|
304
306
|
/** Correction 2: the spatial grid and the value's own granularity are two fields. */
|
|
@@ -353,9 +355,10 @@ export interface MotionSchedule {
|
|
|
353
355
|
|
|
354
356
|
/**
|
|
355
357
|
* The schedule a motion section was measured under. The contract names this
|
|
356
|
-
* type without defining it;
|
|
358
|
+
* type without defining it; stage C (issue #1230) defines it as the schedule as
|
|
359
|
+
* given plus what was walked from it (`ScheduleWalked`, at the end of this file).
|
|
357
360
|
*/
|
|
358
|
-
export type ScheduleUsed = MotionSchedule;
|
|
361
|
+
export type ScheduleUsed = MotionSchedule & ScheduleWalked;
|
|
359
362
|
|
|
360
363
|
/** One candidate's evidence — correction 1. `reduce` and `measure` carry exactly one. */
|
|
361
364
|
export interface CandidateReport {
|
|
@@ -421,8 +424,8 @@ export interface EffectiveSettings {
|
|
|
421
424
|
frame: SourceFrame;
|
|
422
425
|
maskSize: [number, number];
|
|
423
426
|
minArtSamples: number;
|
|
424
|
-
/** P9: each region's own floor, in the order the regions were given. */
|
|
425
|
-
regions: Array<{ name: string; minArtSamples: number }>;
|
|
427
|
+
/** P9: each region's own floor, in the order the regions were given — and, on a `compare`, its polygon. */
|
|
428
|
+
regions: Array<{ name: string; minArtSamples: number; polygon?: Array<[number, number]> }>;
|
|
426
429
|
}>;
|
|
427
430
|
sourceBounds: ArtFitBounds | null;
|
|
428
431
|
referenceArtFit: ArtFitBounds | null;
|
|
@@ -1576,7 +1579,7 @@ function effectiveJson(e: EffectiveSettings): Json {
|
|
|
1576
1579
|
frame: frameJson(a.frame),
|
|
1577
1580
|
maskSize: pair(a.maskSize),
|
|
1578
1581
|
minArtSamples: a.minArtSamples,
|
|
1579
|
-
regions: a.regions.map((r) => ({ name: r.name, minArtSamples: r.minArtSamples })),
|
|
1582
|
+
regions: a.regions.map((r): Json => (r.polygon === undefined ? { name: r.name, minArtSamples: r.minArtSamples } : { name: r.name, minArtSamples: r.minArtSamples, polygon: r.polygon.map(pair) })),
|
|
1580
1583
|
})),
|
|
1581
1584
|
sourceBounds: fitJson(e.sourceBounds),
|
|
1582
1585
|
referenceArtFit: fitJson(e.referenceArtFit),
|
|
@@ -1631,6 +1634,7 @@ function rowJson(r: MeasureRow): Json {
|
|
|
1631
1634
|
};
|
|
1632
1635
|
}
|
|
1633
1636
|
if (r.sampling !== undefined) out.sampling = { domain: r.sampling.domain, count: r.sampling.count };
|
|
1637
|
+
if (r.motion !== undefined) out.motion = motionDetailJson(r.motion);
|
|
1634
1638
|
return out;
|
|
1635
1639
|
}
|
|
1636
1640
|
|
|
@@ -1651,7 +1655,7 @@ function candidateJson(c: CandidateReport): Json {
|
|
|
1651
1655
|
id: c.id,
|
|
1652
1656
|
counts: countsJson(c.counts),
|
|
1653
1657
|
geometry: c.geometry === null ? null : sectionJson(c.geometry),
|
|
1654
|
-
motion: c.motion === null ? null : { ...sectionJson(c.motion), schedule:
|
|
1658
|
+
motion: c.motion === null ? null : { ...sectionJson(c.motion), schedule: scheduleUsedJson(c.motion.schedule) },
|
|
1655
1659
|
accepted: c.accepted,
|
|
1656
1660
|
};
|
|
1657
1661
|
if (c.perFrame !== undefined) out.perFrame = c.perFrame.map((p) => ({ code: p.code, frame: p.frame, value: p.value }));
|
|
@@ -1702,3 +1706,97 @@ export function writeMeshQualityReport(report: MeshQualityReport): string {
|
|
|
1702
1706
|
};
|
|
1703
1707
|
return `${JSON.stringify(doc, null, 2)}\n`;
|
|
1704
1708
|
}
|
|
1709
|
+
|
|
1710
|
+
// ---------------------------------------------------------------------------
|
|
1711
|
+
// stage C — the motion section's own fields (issue #1230, `src/meshcompare.ts`)
|
|
1712
|
+
// ---------------------------------------------------------------------------
|
|
1713
|
+
//
|
|
1714
|
+
// Additive: nothing above reads these, a `measure` and a `reduce` never write
|
|
1715
|
+
// them, and every one is written only when present.
|
|
1716
|
+
|
|
1717
|
+
/** One group of frames a motion row was also taken over — a phase, or a role (P11). */
|
|
1718
|
+
export interface MotionReading {
|
|
1719
|
+
/** The group's worst value; null when no frame of the group drew the attachment. */
|
|
1720
|
+
value: number | null;
|
|
1721
|
+
/** The value against the row's bound, by the row's own rule; `undeclared` with no bound, `not-measurable` with no value. */
|
|
1722
|
+
state: MeasureState;
|
|
1723
|
+
/** The frame the group's worst value was taken at, by `FrameRef.id`. */
|
|
1724
|
+
frame: string | null;
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
/**
|
|
1728
|
+
* How a motion row's value was taken over the schedule (stage C). The row's own
|
|
1729
|
+
* `value` is the worst over every frame measured; these keep apart what the
|
|
1730
|
+
* contract says must not be folded together.
|
|
1731
|
+
*/
|
|
1732
|
+
export interface MotionRowDetail {
|
|
1733
|
+
/** Frames at which the attachment was drawn and measured, and the ids of those at which its slot drew something else or nothing. */
|
|
1734
|
+
frames: { measured: number; notDrawn: string[] };
|
|
1735
|
+
/**
|
|
1736
|
+
* §4 *Transition in time*: the row taken per sample phase — `grid` and `irr`
|
|
1737
|
+
* for the frames a rate generated, `null` for the setup frame and for frames
|
|
1738
|
+
* the caller gave as explicit times (no phase applies to those).
|
|
1739
|
+
*/
|
|
1740
|
+
byPhase: Array<{ phase: 'grid' | 'irr' | null } & MotionReading>;
|
|
1741
|
+
/** When one phase passes and another fails: the worst frame of each, by id, and a sentence naming both. Null otherwise. */
|
|
1742
|
+
phasesDisagree: { pass: string; fail: string; sentence: string } | null;
|
|
1743
|
+
/** P11: the row per role. `heldOut` is null when no held-out frame was measured — no held-out claim is made. */
|
|
1744
|
+
byRole: { baseline: MotionReading | null; selection: MotionReading | null; heldOut: MotionReading | null };
|
|
1745
|
+
/**
|
|
1746
|
+
* Correction 4, world rows: the declared setup map of the attachment's slot
|
|
1747
|
+
* bone — its world 2×2 at the setup pose, `[a, b, c, d]` as the core poses it
|
|
1748
|
+
* from the bones' declared fields — and its two singular scales. Never a
|
|
1749
|
+
* single world-per-pixel ratio, and never fitted to output vertices.
|
|
1750
|
+
*/
|
|
1751
|
+
setupMap?: { bone: string; linear: [number, number, number, number]; singularScales: [number, number] };
|
|
1752
|
+
/** §3: samples no non-degenerate UV triangle carries, listed by UV — in the reference's mesh and in this candidate's. */
|
|
1753
|
+
uncarried?: { reference: Array<[number, number]>; candidate: Array<[number, number]> };
|
|
1754
|
+
/** §3, `MQ_INVERSION` on a slot `invariants.deformMayFold` names: every reversed triangle with its frame — listed, never zeroed. */
|
|
1755
|
+
folds?: Array<{ triangle: number; frame: string }>;
|
|
1756
|
+
/** Triangles that have no area at the setup pose (the A39 band), so no sign and no stretch is read off them. */
|
|
1757
|
+
degenerateAtSetup?: number;
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
/** What a motion section walked from the schedule it was given (`ScheduleUsed`). */
|
|
1761
|
+
export interface ScheduleWalked {
|
|
1762
|
+
/** Every frame the schedule names, in walk order, each with its role (P11). */
|
|
1763
|
+
walked: FrameRef[];
|
|
1764
|
+
/** The roles among those frames — what the section has evidence for. */
|
|
1765
|
+
roles: Array<FrameRef['role']>;
|
|
1766
|
+
/** P11: true only when at least one held-out frame, disjoint from `selection`, was walked. */
|
|
1767
|
+
heldOutClaim: boolean;
|
|
1768
|
+
/** P10: every walk resets physics at time 0 and steps from there, the same steps for the reference and every candidate. */
|
|
1769
|
+
reset: 'physics reset at time 0';
|
|
1770
|
+
/** Each walk: the animation, the phase (null for explicit times), and how many steps reached its last frame. */
|
|
1771
|
+
walks: Array<{ animation: string; phase: 'grid' | 'irr' | null; steps: number }>;
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
function readingJson(r: MotionReading | null): Json {
|
|
1775
|
+
return r === null ? null : { value: r.value, state: r.state, frame: r.frame };
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
function motionDetailJson(m: MotionRowDetail): Json {
|
|
1779
|
+
const out: { [key: string]: Json } = {
|
|
1780
|
+
frames: { measured: m.frames.measured, notDrawn: [...m.frames.notDrawn] },
|
|
1781
|
+
byPhase: m.byPhase.map((p) => ({ phase: p.phase, value: p.value, state: p.state, frame: p.frame })),
|
|
1782
|
+
phasesDisagree: m.phasesDisagree === null ? null : { pass: m.phasesDisagree.pass, fail: m.phasesDisagree.fail, sentence: m.phasesDisagree.sentence },
|
|
1783
|
+
byRole: { baseline: readingJson(m.byRole.baseline), selection: readingJson(m.byRole.selection), heldOut: readingJson(m.byRole.heldOut) },
|
|
1784
|
+
};
|
|
1785
|
+
if (m.setupMap !== undefined) out.setupMap = { bone: m.setupMap.bone, linear: [...m.setupMap.linear], singularScales: pair(m.setupMap.singularScales) };
|
|
1786
|
+
if (m.uncarried !== undefined) out.uncarried = { reference: m.uncarried.reference.map(pair), candidate: m.uncarried.candidate.map(pair) };
|
|
1787
|
+
if (m.folds !== undefined) out.folds = m.folds.map((f) => ({ triangle: f.triangle, frame: f.frame }));
|
|
1788
|
+
if (m.degenerateAtSetup !== undefined) out.degenerateAtSetup = m.degenerateAtSetup;
|
|
1789
|
+
return out;
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
function scheduleUsedJson(s: ScheduleUsed): Json {
|
|
1793
|
+
const given = scheduleJson(s) as { [key: string]: Json };
|
|
1794
|
+
return {
|
|
1795
|
+
...given,
|
|
1796
|
+
walked: s.walked.map(frameRefJson),
|
|
1797
|
+
roles: [...s.roles],
|
|
1798
|
+
heldOutClaim: s.heldOutClaim,
|
|
1799
|
+
reset: s.reset,
|
|
1800
|
+
walks: s.walks.map((w) => ({ animation: w.animation, phase: w.phase, steps: w.steps })),
|
|
1801
|
+
};
|
|
1802
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The package's own metadata — its installed version and repository — read
|
|
3
|
+
* once from the `package.json` beside the code.
|
|
4
|
+
*
|
|
5
|
+
* Moved here unchanged from `src/cli/shared.ts` (issue #1230), which imports
|
|
6
|
+
* these back and re-exports every name it exported before, so no caller
|
|
7
|
+
* changed an import. They moved because the motion comparison
|
|
8
|
+
* (`src/meshcompare.ts`) records the version of the poser that ran
|
|
9
|
+
* (`MeshQualityReport.poser`, P2 of docs/MESH_REDUCTION.md), and a library
|
|
10
|
+
* module that read it through `src/cli/shared.ts` would load the whole CLI —
|
|
11
|
+
* the compiler, `check` and every command body — to read one string. One
|
|
12
|
+
* reader, in the one place both can reach.
|
|
13
|
+
*
|
|
14
|
+
* Imports nothing but `node:fs` and `node:path`: no clock, no runtime.
|
|
15
|
+
*/
|
|
16
|
+
import { readFileSync } from 'node:fs';
|
|
17
|
+
import { join } from 'node:path';
|
|
18
|
+
|
|
19
|
+
interface PackageMeta {
|
|
20
|
+
version?: string;
|
|
21
|
+
repository?: string | { url?: string };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let packageMeta: PackageMeta | null | undefined;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The directory `cli.ts` sits in: `package.json`, `skills/` and, in a
|
|
28
|
+
* checkout, `examples/` and `scripts/` are beside it, in the repository and
|
|
29
|
+
* once installed. One level above this file (`src/`) — the same directory
|
|
30
|
+
* `src/cli/shared.ts` reached two levels above itself before the reader moved.
|
|
31
|
+
*/
|
|
32
|
+
export const PACKAGE_ROOT = join(import.meta.dir, '..');
|
|
33
|
+
|
|
34
|
+
/** `package.json` sits next to `cli.ts` both in the repo and once installed (`PACKAGE_ROOT`). */
|
|
35
|
+
export function readPackageMeta(): PackageMeta | null {
|
|
36
|
+
if (packageMeta === undefined) {
|
|
37
|
+
try {
|
|
38
|
+
packageMeta = JSON.parse(readFileSync(join(PACKAGE_ROOT, 'package.json'), 'utf8')) as PackageMeta;
|
|
39
|
+
} catch {
|
|
40
|
+
packageMeta = null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return packageMeta;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function readVersion(): string {
|
|
47
|
+
return readPackageMeta()?.version ?? 'unknown';
|
|
48
|
+
}
|