rig-c 2.28.0 → 2.30.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 +1 -1
- package/src/mesh.ts +3 -1
- package/src/meshallocation.ts +18 -4
- package/src/meshcarriers.ts +93 -0
- package/src/meshcompare.ts +60 -73
- package/src/meshquality.ts +143 -13
- package/src/meshskinning.ts +708 -0
package/src/meshquality.ts
CHANGED
|
@@ -71,6 +71,7 @@ import {
|
|
|
71
71
|
} from './mesh.ts';
|
|
72
72
|
import { areaBand, triangleAreas } from './areaband.ts';
|
|
73
73
|
import { allocationContrast, boundaryNecessityOnce, deformLoad, gradeMax, minAngleP10, type AllocationArt, type AllocationPlant } from './meshallocation.ts';
|
|
74
|
+
import { skinningEchoOf, skinningResidual, validateSkinning, type SkinningDetail, type SkinningEcho, type SkinningPlant, type SkinningResidualInput } from './meshskinning.ts';
|
|
74
75
|
import { artRastersOf, type ArtRasters, type CoverageReading, type OutlineMemo, type RegionEdgeReading, type SilhouetteReading, type StepRasters } from './meshrasters.ts';
|
|
75
76
|
import { cropToSpineY } from './transform.ts';
|
|
76
77
|
|
|
@@ -369,6 +370,16 @@ export interface MeshMeasureInput {
|
|
|
369
370
|
* `null` art bound is declared absent, #1257). It declares no bound: every allocation row stays `undeclared`.
|
|
370
371
|
*/
|
|
371
372
|
motionAmplitude?: MotionAmplitude | null;
|
|
373
|
+
/**
|
|
374
|
+
* Issue #1294 (§7, mechanism 1): the skinning-envelope residual of this mesh — the candidate — against the original
|
|
375
|
+
* source it was made from, under a declared envelope (`SkinningResidualInput`, `src/meshskinning.ts`). Left out, it
|
|
376
|
+
* is not asked for: no `MQ_SKINNING_RESIDUAL` row and no echo, so the report is the one written without the field,
|
|
377
|
+
* byte for byte. `null` declares it absent: the row is `not-measurable` saying so, and the echo is `null`. Set, the
|
|
378
|
+
* row is measured over §3's samples at `art.threshold` and is required when `maxResidual` is a number; a source or an
|
|
379
|
+
* envelope the measurement cannot read refuses the row by name. Anything that is not a `SkinningResidualInput` in
|
|
380
|
+
* full is refused `REDUCE_INPUT_MISSING` naming its path, before any work.
|
|
381
|
+
*/
|
|
382
|
+
skinning?: SkinningResidualInput | null;
|
|
372
383
|
}
|
|
373
384
|
|
|
374
385
|
/**
|
|
@@ -379,10 +390,20 @@ export interface MotionAmplitude {
|
|
|
379
390
|
/** One entry per track the caller declares (an animation, a parameter axis — its name is echoed, never read). */
|
|
380
391
|
tracks: TrackAmplitude[];
|
|
381
392
|
/**
|
|
382
|
-
* G, px per px
|
|
383
|
-
*
|
|
393
|
+
* G, px per px — an author's number, read by `MQ_ALLOCATION_CONTRAST` only (issue #1291). Δ asks of each vertex
|
|
394
|
+
* whether the mesh still meets every declared need without it; a need — a silhouette edge of the B\* outline, a
|
|
395
|
+
* deforming triangle sized by θ and ε, a region — allows edges of its own size h at its source and h + G × d at
|
|
396
|
+
* distance d from it, the form of a region's `grade`. So G is the fastest rate at which the author allows the mesh to
|
|
397
|
+
* coarsen away from what needs density: a larger G lets the need relax sooner, so more dense vertices read as
|
|
398
|
+
* removable and Δ and economy E rise (graded layers read as slack); a smaller G keeps more of them needed, and at 0
|
|
399
|
+
* every point needs the smallest size any source declares, so nothing reads as removable. The value an author
|
|
400
|
+
* declares on a region's `grade` for the same mesh is the same quantity; §8's fixtures used 0.75 because it is the
|
|
401
|
+
* one `grade` any of them declares. Nothing a rig or a reduction declares fixes G — docs/MESH_REDUCTION.md §8
|
|
402
|
+
* measured every candidate derivation, and Δ moves with G on every input there — so none is assumed: left out, it is
|
|
403
|
+
* not declared, and `null` declares it absent; either leaves `MQ_ALLOCATION_CONTRAST` `not-measurable` naming this
|
|
404
|
+
* field, and `MQ_DEFORM_LOAD`, which does not read G, measured. Set, finite and 0 or more.
|
|
384
405
|
*/
|
|
385
|
-
gradation
|
|
406
|
+
gradation?: number | null;
|
|
386
407
|
}
|
|
387
408
|
|
|
388
409
|
/** One track's amplitude: the pairs of bones a share moves between in it, each with θ, and the track's ε. */
|
|
@@ -432,6 +453,8 @@ export interface MeasureRow {
|
|
|
432
453
|
motion?: MotionRowDetail;
|
|
433
454
|
/** The five allocation rows only (issue #1280): what the value is a reading of, and what it was read from. */
|
|
434
455
|
allocation?: AllocationDetail;
|
|
456
|
+
/** `MQ_SKINNING_RESIDUAL` when measured (issue #1294): the source, the samples, the worst sample's terms and the contract's own figures. */
|
|
457
|
+
skinning?: SkinningDetail;
|
|
435
458
|
}
|
|
436
459
|
|
|
437
460
|
/** Issue #1280: an allocation row's own fields. Written on those five rows, measured or not, and on no other. */
|
|
@@ -600,6 +623,8 @@ export interface EffectiveSettings {
|
|
|
600
623
|
retriangulate?: 'delaunay';
|
|
601
624
|
/** A reduction's `removalOrder`, echoed when the input set it (issue #1283); absent otherwise — the order was ascending source index. */
|
|
602
625
|
removalOrder?: 'deformation-load';
|
|
626
|
+
/** A measurement's `skinning`, echoed when the input set it — `null` included (issue #1294): the source by id and digest. Absent otherwise. */
|
|
627
|
+
skinning?: SkinningEcho | null;
|
|
603
628
|
}
|
|
604
629
|
|
|
605
630
|
export interface MeshCounts {
|
|
@@ -853,6 +878,7 @@ function validateInput(input: MeshMeasureInput): void {
|
|
|
853
878
|
refuse('REDUCE_INPUT_MISSING', `${who}: preset is not { name, version }; required that, or null`);
|
|
854
879
|
}
|
|
855
880
|
validateMotionAmplitude(who, input.motionAmplitude);
|
|
881
|
+
validateSkinning(who, input.skinning);
|
|
856
882
|
}
|
|
857
883
|
|
|
858
884
|
/**
|
|
@@ -865,8 +891,11 @@ export function validateMotionAmplitude(who: string, amplitude: unknown): void {
|
|
|
865
891
|
if (amplitude === undefined || amplitude === null) return;
|
|
866
892
|
const shape = '{ tracks: [{ track, pairs: [{ bones: [a, b], theta }], epsilon }], gradation }';
|
|
867
893
|
if (!isObject(amplitude) || !Array.isArray(amplitude.tracks)) refuse('REDUCE_INPUT_MISSING', `${who}: motionAmplitude is ${JSON.stringify(amplitude)}; required ${shape}, null, or the field left out`);
|
|
868
|
-
|
|
869
|
-
|
|
894
|
+
// Issue #1291: `gradation` left out or `null` is accepted — MQ_ALLOCATION_CONTRAST then reads not-measurable naming it.
|
|
895
|
+
if (amplitude.gradation !== undefined && amplitude.gradation !== null && (!isFiniteNumber(amplitude.gradation) || amplitude.gradation < 0)) {
|
|
896
|
+
// A non-finite number is named as itself: JSON would print NaN as null, which this field now accepts.
|
|
897
|
+
const found = typeof amplitude.gradation === 'number' ? String(amplitude.gradation) : JSON.stringify(amplitude.gradation);
|
|
898
|
+
refuse('REDUCE_INPUT_MISSING', `${who}: motionAmplitude.gradation is ${found}; required a finite number of px per px, 0 or more, null, or the field left out — no gradation is assumed`);
|
|
870
899
|
}
|
|
871
900
|
amplitude.tracks.forEach((track: unknown, i: number) => {
|
|
872
901
|
const at = `motionAmplitude.tracks[${i}]`;
|
|
@@ -1490,6 +1519,15 @@ export function measureMeshQualityPlanted(input: MeshMeasureInput, plant: Alloca
|
|
|
1490
1519
|
return measureValidated(input, artRastersOf(input.art), null, plant);
|
|
1491
1520
|
}
|
|
1492
1521
|
|
|
1522
|
+
/**
|
|
1523
|
+
* `measureMeshQuality` with a fault planted in the skinning residual or its report (issue #1294, `SkinningPlant` in
|
|
1524
|
+
* `src/meshskinning.ts`) — the `mesh-compare` suite's negative controls. Internal, as `measureMeshQualityWith` is.
|
|
1525
|
+
*/
|
|
1526
|
+
export function measureMeshQualitySkinningPlanted(input: MeshMeasureInput, plant: SkinningPlant): MeshQualityReport {
|
|
1527
|
+
validateInput(input);
|
|
1528
|
+
return measureValidated(input, artRastersOf(input.art), null, null, plant);
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1493
1531
|
/**
|
|
1494
1532
|
* `measureMeshQuality` over art rasters the caller already holds
|
|
1495
1533
|
* (`src/meshrasters.ts`) — what `reduceMesh` calls for every measurement of
|
|
@@ -1599,12 +1637,12 @@ function checkArtRasters(input: MeshMeasureInput, rasters: ArtRasters): void {
|
|
|
1599
1637
|
}
|
|
1600
1638
|
|
|
1601
1639
|
/** The measurement proper, over an input `validateInput` accepted and rasters taken from its art. */
|
|
1602
|
-
function measureValidated(input: MeshMeasureInput, rasters: ArtRasters, steps: StepRasters | null, plant: AllocationPlant | null): MeshQualityReport {
|
|
1640
|
+
function measureValidated(input: MeshMeasureInput, rasters: ArtRasters, steps: StepRasters | null, plant: AllocationPlant | null, skinningPlant: SkinningPlant | null = null): MeshQualityReport {
|
|
1603
1641
|
rasters.tally.uses++;
|
|
1604
1642
|
const { attachment, art, source, targets } = input;
|
|
1605
1643
|
const { mask, threshold, frame } = art;
|
|
1606
1644
|
const scale = frame.pageScale;
|
|
1607
|
-
const effective = effectiveOf(input);
|
|
1645
|
+
const effective = effectiveOf(input, skinningPlant);
|
|
1608
1646
|
const report = (sourceCounts: MeshCounts | null, candidate: CandidateReport, termination: Termination | null): MeshQualityReport => ({
|
|
1609
1647
|
spec: MESH_QUALITY_REPORT_SPEC,
|
|
1610
1648
|
operation: 'measure',
|
|
@@ -1641,6 +1679,9 @@ function measureValidated(input: MeshMeasureInput, rasters: ArtRasters, steps: S
|
|
|
1641
1679
|
// --- allocation rows (issue #1280): undeclared or not-measurable, never required ------------
|
|
1642
1680
|
if (plant !== 'omit-rows') rows.push(...allocationRows(input, outline, rasters, steps, plant));
|
|
1643
1681
|
|
|
1682
|
+
// --- the skinning residual (issue #1294): only when the input asked for it ------------------
|
|
1683
|
+
if (input.skinning !== undefined || skinningPlant === 'row-when-unset') rows.push(skinningRow(input, rasters, skinningPlant));
|
|
1684
|
+
|
|
1644
1685
|
// --- raster rows ---------------------------------------------------------
|
|
1645
1686
|
const w = mask.width;
|
|
1646
1687
|
const h = mask.height;
|
|
@@ -1792,6 +1833,53 @@ function measureValidated(input: MeshMeasureInput, rasters: ArtRasters, steps: S
|
|
|
1792
1833
|
return report(counts, { id: input.id, counts, geometry, motion: null, accepted }, null);
|
|
1793
1834
|
}
|
|
1794
1835
|
|
|
1836
|
+
/**
|
|
1837
|
+
* `MQ_SKINNING_RESIDUAL` (issue #1294, `src/meshskinning.ts`): a geometry row in drawing px, required when
|
|
1838
|
+
* `skinning.maxResidual` is a number. `null` for the field reads `not-measurable` saying it was declared absent;
|
|
1839
|
+
* a refusal of the source or the envelope is a `refused` row whose reason opens with its code. The samples are §3's:
|
|
1840
|
+
* art pixel centres at `art.threshold` and the source hull's UVs.
|
|
1841
|
+
*/
|
|
1842
|
+
function skinningRow(input: MeshMeasureInput, rasters: ArtRasters, plant: SkinningPlant | null): Built {
|
|
1843
|
+
const { attachment, art } = input;
|
|
1844
|
+
const spec: RowSpec = { code: 'MQ_SKINNING_RESIDUAL', unit: 'px', region: null, attachment };
|
|
1845
|
+
const skinning = input.skinning;
|
|
1846
|
+
if (skinning === undefined || skinning === null) {
|
|
1847
|
+
const why =
|
|
1848
|
+
skinning === null
|
|
1849
|
+
? `attachment ${nameOf(attachment)}: skinning is null — the residual is declared absent, so there is no source or envelope to measure against`
|
|
1850
|
+
: `attachment ${nameOf(attachment)}: skinning was not set`;
|
|
1851
|
+
return unmeasuredRow(spec, 'not-measurable', why, false);
|
|
1852
|
+
}
|
|
1853
|
+
const bound = skinning.maxResidual === null ? null : { op: '<=' as const, value: skinning.maxResidual };
|
|
1854
|
+
const reading = skinningResidual(
|
|
1855
|
+
{
|
|
1856
|
+
attachment,
|
|
1857
|
+
frame: art.frame,
|
|
1858
|
+
artBits: rasters.artBits(),
|
|
1859
|
+
maskWidth: art.mask.width,
|
|
1860
|
+
maskHeight: art.mask.height,
|
|
1861
|
+
minArtSamples: input.minArtSamples,
|
|
1862
|
+
threshold: art.threshold,
|
|
1863
|
+
candidate: input.source,
|
|
1864
|
+
skinning,
|
|
1865
|
+
},
|
|
1866
|
+
plant,
|
|
1867
|
+
);
|
|
1868
|
+
const sampling = { domain: `art pixel centres at alpha >= ${art.threshold} and the source's hull UVs, on the region UV square, carried by the source's and this mesh's UV triangles`, count: reading.samples };
|
|
1869
|
+
const artField = { threshold: art.threshold, connectivity: null, samples: reading.art };
|
|
1870
|
+
if (reading.state !== 'measured') {
|
|
1871
|
+
const built = unmeasuredRow(spec, reading.state, reading.reason, bound !== null);
|
|
1872
|
+
built.row.art = artField;
|
|
1873
|
+
built.row.sampling = sampling;
|
|
1874
|
+
return built;
|
|
1875
|
+
}
|
|
1876
|
+
const built = measuredRow(spec, reading.value, bound, reading.worst, true);
|
|
1877
|
+
built.row.art = artField;
|
|
1878
|
+
built.row.sampling = sampling;
|
|
1879
|
+
built.row.skinning = reading.detail;
|
|
1880
|
+
return built;
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1795
1883
|
/** What each allocation row is a reading of — `AllocationDetail.reading`, one fixed sentence per code. */
|
|
1796
1884
|
const ALLOCATION_READINGS: Record<string, string> = {
|
|
1797
1885
|
MQ_GRADE:
|
|
@@ -1858,7 +1946,7 @@ function allocationRows(input: MeshMeasureInput, outline: MeshOutline, rasters:
|
|
|
1858
1946
|
const allocationArt: AllocationArt = { mask: art.mask, bits: rasters.artBits(), scale: art.frame.pageScale };
|
|
1859
1947
|
|
|
1860
1948
|
// MQ_DEFORM_LOAD.
|
|
1861
|
-
const loadWhy = amplitudeMissing ?? unweighted;
|
|
1949
|
+
const loadWhy = amplitudeMissing ?? unweighted ?? (plant === 'load-needs-gradation' && (amplitude?.gradation === undefined || amplitude.gradation === null) ? `${who}: planted — the load refused for want of a gradation it does not read` : null);
|
|
1862
1950
|
if (loadWhy !== null || amplitude === undefined || amplitude === null || source.weights === null) {
|
|
1863
1951
|
row('MQ_DEFORM_LOAD', 'px', missing('MQ_DEFORM_LOAD', 'px', loadWhy ?? ''));
|
|
1864
1952
|
} else {
|
|
@@ -1875,6 +1963,15 @@ function allocationRows(input: MeshMeasureInput, outline: MeshOutline, rasters:
|
|
|
1875
1963
|
// MQ_ALLOCATION_CONTRAST.
|
|
1876
1964
|
const planted = plant === 'contrast-without-amplitude' && (amplitude === undefined || amplitude === null) ? { tracks: [], gradation: 0 } : null;
|
|
1877
1965
|
const declared = planted ?? amplitude;
|
|
1966
|
+
// Issue #1291: G is the author's; an amplitude without it measures the load and leaves Δ unread, naming the field.
|
|
1967
|
+
const declaredGradation = declared === undefined || declared === null ? undefined : declared.gradation;
|
|
1968
|
+
const gradation = plant === 'gradation-defaulted' && (declaredGradation === undefined || declaredGradation === null) ? 0.75 : declaredGradation;
|
|
1969
|
+
const gradationMissing =
|
|
1970
|
+
declared === undefined || declared === null || (gradation !== undefined && gradation !== null)
|
|
1971
|
+
? null
|
|
1972
|
+
: gradation === undefined
|
|
1973
|
+
? `${who}: motionAmplitude.gradation is not declared (the field is left out), so no rate says how fast a declared need relaxes away from its source; required G, px per px, 0 or more — none is assumed, because Δ moves with G and nothing a rig declares fixes it (docs/MESH_REDUCTION.md §8)`
|
|
1974
|
+
: `${who}: motionAmplitude.gradation is declared absent (null), so no rate says how fast a declared need relaxes away from its source; required G, px per px, 0 or more, for this row — none is assumed`;
|
|
1878
1975
|
let regionWhy: string | null = null;
|
|
1879
1976
|
for (const region of targets.regions) {
|
|
1880
1977
|
const refusal = regionRefusal(region, art.frame.pageScale, who, steps);
|
|
@@ -1883,13 +1980,13 @@ function allocationRows(input: MeshMeasureInput, outline: MeshOutline, rasters:
|
|
|
1883
1980
|
break;
|
|
1884
1981
|
}
|
|
1885
1982
|
}
|
|
1886
|
-
const contrastWhy = (planted === null ? amplitudeMissing : null) ?? unweighted ?? noDelta ?? fewArt ?? regionWhy;
|
|
1887
|
-
if (contrastWhy !== null || declared === undefined || declared === null || source.weights === null || delta === null) {
|
|
1983
|
+
const contrastWhy = (planted === null ? amplitudeMissing : null) ?? gradationMissing ?? unweighted ?? noDelta ?? fewArt ?? regionWhy;
|
|
1984
|
+
if (contrastWhy !== null || declared === undefined || declared === null || gradation === undefined || gradation === null || source.weights === null || delta === null) {
|
|
1888
1985
|
row('MQ_ALLOCATION_CONTRAST', 'fraction', missing('MQ_ALLOCATION_CONTRAST', 'fraction', contrastWhy ?? ''));
|
|
1889
1986
|
} else {
|
|
1890
1987
|
const ownHull = points.slice(0, outline.hull);
|
|
1891
1988
|
const silhouette = boundaryNecessityOnce(rasters, ownHull, delta, allocationArt, null).path;
|
|
1892
|
-
const contrast = allocationContrast(points, source.triangles, outline.hull, source.weights, delta, allocationArt, targets.regions, declared, silhouette, plant);
|
|
1989
|
+
const contrast = allocationContrast(points, source.triangles, outline.hull, source.weights, delta, allocationArt, targets.regions, declared, gradation, silhouette, plant);
|
|
1893
1990
|
if (contrast.measured) {
|
|
1894
1991
|
const c = contrast.value;
|
|
1895
1992
|
row('MQ_ALLOCATION_CONTRAST', 'fraction', measured('MQ_ALLOCATION_CONTRAST', 'fraction', c.contrast, { at: { vertex: c.worstVertex } }), {
|
|
@@ -2201,7 +2298,7 @@ function regionRows(input: MeshMeasureInput, outline: MeshOutline, hullPolygon:
|
|
|
2201
2298
|
// the echo, and the document's text
|
|
2202
2299
|
// ---------------------------------------------------------------------------
|
|
2203
2300
|
|
|
2204
|
-
function effectiveOf(input: MeshMeasureInput): EffectiveSettings {
|
|
2301
|
+
function effectiveOf(input: MeshMeasureInput, skinningPlant: SkinningPlant | null = null): EffectiveSettings {
|
|
2205
2302
|
const { art } = input;
|
|
2206
2303
|
return {
|
|
2207
2304
|
preset: input.preset,
|
|
@@ -2229,6 +2326,7 @@ function effectiveOf(input: MeshMeasureInput): EffectiveSettings {
|
|
|
2229
2326
|
schedule: null,
|
|
2230
2327
|
budget: null,
|
|
2231
2328
|
...(input.motionAmplitude === undefined ? {} : { motionAmplitude: input.motionAmplitude }),
|
|
2329
|
+
...(input.skinning === undefined ? (skinningPlant === 'echo-when-unset' ? { skinning: null } : {}) : { skinning: skinningEchoOf(input.skinning) }),
|
|
2232
2330
|
};
|
|
2233
2331
|
}
|
|
2234
2332
|
|
|
@@ -2322,6 +2420,7 @@ function effectiveJson(e: EffectiveSettings): Json {
|
|
|
2322
2420
|
...(e.motionAmplitude === undefined ? {} : { motionAmplitude: amplitudeJson(e.motionAmplitude) }),
|
|
2323
2421
|
...(e.retriangulate === undefined ? {} : { retriangulate: e.retriangulate }),
|
|
2324
2422
|
...(e.removalOrder === undefined ? {} : { removalOrder: e.removalOrder }),
|
|
2423
|
+
...(e.skinning === undefined ? {} : { skinning: skinningEchoJson(e.skinning) }),
|
|
2325
2424
|
};
|
|
2326
2425
|
}
|
|
2327
2426
|
|
|
@@ -2366,9 +2465,40 @@ function rowJson(r: MeasureRow): Json {
|
|
|
2366
2465
|
if (r.sampling !== undefined) out.sampling = { domain: r.sampling.domain, count: r.sampling.count };
|
|
2367
2466
|
if (r.motion !== undefined) out.motion = motionDetailJson(r.motion);
|
|
2368
2467
|
if (r.allocation !== undefined) out.allocation = allocationDetailJson(r.allocation);
|
|
2468
|
+
if (r.skinning !== undefined) out.skinning = skinningDetailJson(r.skinning);
|
|
2369
2469
|
return out;
|
|
2370
2470
|
}
|
|
2371
2471
|
|
|
2472
|
+
function deformJson(t: DeformTimelineInput): Json {
|
|
2473
|
+
return {
|
|
2474
|
+
animation: t.animation,
|
|
2475
|
+
attachment: attachmentJson(t.attachment),
|
|
2476
|
+
keys: t.keys.map((k): Json => (k.kind === 'vertices' ? { time: k.time, kind: k.kind, offset: k.offset, vertices: [...k.vertices] } : { time: k.time, kind: k.kind })),
|
|
2477
|
+
};
|
|
2478
|
+
}
|
|
2479
|
+
|
|
2480
|
+
function skinningEchoJson(e: SkinningEcho | null): Json {
|
|
2481
|
+
if (e === null) return null;
|
|
2482
|
+
return {
|
|
2483
|
+
source: { id: e.source.id, digest: e.source.digest },
|
|
2484
|
+
envelope: { reference: e.envelope.reference, bones: e.envelope.bones.map((b) => ({ bone: b.bone, linear: b.linear, pivot: pair(b.pivot), translation: b.translation })) },
|
|
2485
|
+
maxResidual: e.maxResidual,
|
|
2486
|
+
deform: e.deform.map(deformJson),
|
|
2487
|
+
};
|
|
2488
|
+
}
|
|
2489
|
+
|
|
2490
|
+
function skinningDetailJson(d: SkinningDetail): Json {
|
|
2491
|
+
return {
|
|
2492
|
+
reading: d.reading,
|
|
2493
|
+
source: { id: d.source.id, digest: d.source.digest },
|
|
2494
|
+
reference: d.reference,
|
|
2495
|
+
samples: { measured: d.samples.measured, uncarried: { source: d.samples.uncarried.source.map(pair), candidate: d.samples.uncarried.candidate.map(pair) } },
|
|
2496
|
+
worst: d.worst === null ? null : { covariance: d.worst.covariance, lever: d.worst.lever, bones: d.worst.bones.map((b) => ({ bone: b.bone, covariance: b.covariance, lever: b.lever })) },
|
|
2497
|
+
weightField: { l1: d.weightField.l1, lInf: d.weightField.lInf },
|
|
2498
|
+
setup: { vertexGap: d.setup.vertexGap, vertexSlack: pair(d.setup.vertexSlack), sampleGap: d.setup.sampleGap, weightSumGap: d.setup.weightSumGap },
|
|
2499
|
+
};
|
|
2500
|
+
}
|
|
2501
|
+
|
|
2372
2502
|
function allocationDetailJson(a: AllocationDetail): Json {
|
|
2373
2503
|
const out: { [key: string]: Json } = { reading: a.reading };
|
|
2374
2504
|
if (a.contrast !== undefined) {
|
|
@@ -2387,7 +2517,7 @@ function amplitudeJson(a: MotionAmplitude | null): Json {
|
|
|
2387
2517
|
if (a === null) return null;
|
|
2388
2518
|
return {
|
|
2389
2519
|
tracks: a.tracks.map((t) => ({ track: t.track, pairs: t.pairs.map((p) => ({ bones: [p.bones[0], p.bones[1]], theta: p.theta })), epsilon: t.epsilon })),
|
|
2390
|
-
gradation: a.gradation,
|
|
2520
|
+
...(a.gradation === undefined ? {} : { gradation: a.gradation }),
|
|
2391
2521
|
};
|
|
2392
2522
|
}
|
|
2393
2523
|
|