@hatiolab/figure-model 0.6.6 → 0.7.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/CHANGELOG.md +49 -0
- package/README.md +8 -4
- package/dist/asset-types.d.ts +8 -13
- package/dist/asset-types.d.ts.map +1 -1
- package/dist/asset-types.js.map +1 -1
- package/dist/asset.js +4 -4
- package/dist/asset.js.map +1 -1
- package/dist/authoring-actions.js +18 -5
- package/dist/authoring-actions.js.map +1 -1
- package/dist/authoring-types.d.ts +6 -11
- package/dist/authoring-types.d.ts.map +1 -1
- package/dist/authoring-types.js.map +1 -1
- package/dist/data-interface.d.ts.map +1 -1
- package/dist/data-interface.js +8 -4
- package/dist/data-interface.js.map +1 -1
- package/dist/editor-workspace.d.ts +1 -1
- package/dist/editor-workspace.d.ts.map +1 -1
- package/dist/editor-workspace.js +2 -5
- package/dist/editor-workspace.js.map +1 -1
- package/dist/extensions.d.ts +15 -0
- package/dist/extensions.d.ts.map +1 -0
- package/dist/extensions.js +63 -0
- package/dist/extensions.js.map +1 -0
- package/dist/gate.d.ts +1 -1
- package/dist/gate.d.ts.map +1 -1
- package/dist/gate.js +28 -33
- package/dist/gate.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/input-preview.d.ts +48 -0
- package/dist/input-preview.d.ts.map +1 -0
- package/dist/{authoring-session.js → input-preview.js} +7 -63
- package/dist/input-preview.js.map +1 -0
- package/dist/kernel-version.d.ts +1 -1
- package/dist/kernel-version.js +1 -1
- package/dist/names.d.ts.map +1 -1
- package/dist/names.js +5 -1
- package/dist/names.js.map +1 -1
- package/dist/package.d.ts.map +1 -1
- package/dist/package.js +18 -15
- package/dist/package.js.map +1 -1
- package/dist/proposal.d.ts +5 -1
- package/dist/proposal.d.ts.map +1 -1
- package/dist/proposal.js +18 -5
- package/dist/proposal.js.map +1 -1
- package/dist/release-contract.d.ts +1 -1
- package/dist/release-contract.d.ts.map +1 -1
- package/dist/release-contract.js +13 -24
- package/dist/release-contract.js.map +1 -1
- package/dist/shape-edit.d.ts +1 -1
- package/dist/shape-edit.d.ts.map +1 -1
- package/dist/size-edit.d.ts +1 -1
- package/dist/size-edit.d.ts.map +1 -1
- package/docs/asset-persistence.md +6 -3
- package/docs/authoring-transactions.md +21 -19
- package/docs/capability-bindings.md +6 -3
- package/docs/common-kernel-experiment.md +3 -0
- package/docs/document-contract.md +6 -3
- package/docs/dryroom-profile-validation.md +3 -0
- package/docs/editor-integration.md +5 -2
- package/docs/full-conveyor-validation.md +5 -2
- package/docs/full-dryroom-validation.md +6 -3
- package/docs/full-forklift-validation.md +5 -2
- package/docs/full-robot-validation.md +5 -2
- package/docs/payload-collision-validation.md +5 -2
- package/docs/payload-contact-validation.md +5 -2
- package/docs/prototypes/README.md +27 -61
- package/docs/repeated-binding-validation.md +6 -3
- package/docs/review-2026-09-22.md +3 -0
- package/docs/runtime-extraction.md +9 -2
- package/docs/sample-validation-expanded.md +3 -0
- package/docs/sample-validation.md +3 -0
- package/docs/shape-contract.md +7 -5
- package/docs/spatial-contracts.md +3 -0
- package/docs/status.md +2 -1
- package/docs/storage-integration.md +6 -3
- package/docs/typed-repeat-contracts.md +3 -0
- package/package.json +9 -9
- package/dist/authoring-session.d.ts +0 -121
- package/dist/authoring-session.d.ts.map +0 -1
- package/dist/authoring-session.js.map +0 -1
- package/dist/carrier.d.ts +0 -23
- package/dist/carrier.d.ts.map +0 -1
- package/dist/carrier.js +0 -65
- package/dist/carrier.js.map +0 -1
- package/docs/prototypes/asset-samples.mjs +0 -15
- package/docs/prototypes/asset-store.mjs +0 -52
- package/docs/prototypes/asset-store.test.mjs +0 -59
- package/docs/prototypes/asset.mjs +0 -1
- package/docs/prototypes/asset.schema.json +0 -36
- package/docs/prototypes/asset.test.mjs +0 -68
- package/docs/prototypes/authoring-session.mjs +0 -1
- package/docs/prototypes/authoring-session.test.mjs +0 -65
- package/docs/prototypes/capability-plan.mjs +0 -1
- package/docs/prototypes/capability-plan.test.mjs +0 -56
- package/docs/prototypes/capability-samples.mjs +0 -23
- package/docs/prototypes/document.mjs +0 -2
- package/docs/prototypes/document.schema.json +0 -66
- package/docs/prototypes/document.test.mjs +0 -55
- package/docs/prototypes/dryroom-mesh.mjs +0 -60
- package/docs/prototypes/dryroom-mesh.test.mjs +0 -30
- package/docs/prototypes/editor-browser.mjs +0 -97
- package/docs/prototypes/editor-workspace.mjs +0 -1
- package/docs/prototypes/editor-workspace.test.mjs +0 -35
- package/docs/prototypes/feature-pattern.test.mjs +0 -131
- package/docs/prototypes/full-conveyor.mjs +0 -60
- package/docs/prototypes/full-conveyor.test.mjs +0 -61
- package/docs/prototypes/full-dryroom.mjs +0 -67
- package/docs/prototypes/full-dryroom.test.mjs +0 -54
- package/docs/prototypes/full-forklift.mjs +0 -40
- package/docs/prototypes/full-forklift.test.mjs +0 -37
- package/docs/prototypes/full-robot.mjs +0 -35
- package/docs/prototypes/full-robot.test.mjs +0 -68
- package/docs/prototypes/kernel.mjs +0 -147
- package/docs/prototypes/kernel.test.mjs +0 -144
- package/docs/prototypes/more-samples.test.mjs +0 -182
- package/docs/prototypes/payload-collision.mjs +0 -54
- package/docs/prototypes/payload-collision.test.mjs +0 -68
- package/docs/prototypes/payload-fit.mjs +0 -58
- package/docs/prototypes/payload-fit.test.mjs +0 -59
- package/docs/prototypes/render-dryroom.py +0 -60
- package/docs/prototypes/render-full-dryroom.mjs +0 -86
- package/docs/prototypes/repeated-binding.mjs +0 -13
- package/docs/prototypes/repeated-binding.test.mjs +0 -46
- package/docs/prototypes/runtime-browser.mjs +0 -76
- package/docs/prototypes/samples.test.mjs +0 -144
- package/docs/prototypes/spatial-plan.test.mjs +0 -102
- package/docs/prototypes/typed-repeat.test.mjs +0 -96
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import type { FigureDocument, FigureCapabilityEvaluation } from './asset-types.ts';
|
|
2
|
-
import type { FigureUnit } from './graph-types.ts';
|
|
3
|
-
export interface FigureEditControl {
|
|
4
|
-
origin: 'ui' | 'ai';
|
|
5
|
-
expectedRevision: number;
|
|
6
|
-
/** The clips the author agreed to let go, as a CLIPS_DROPPED refusal named them (clip-keeping.ts). */
|
|
7
|
-
dropClips?: readonly string[];
|
|
8
|
-
}
|
|
9
|
-
export interface FigureInputChange {
|
|
10
|
-
kind: 'set-design' | 'set-state';
|
|
11
|
-
input: string;
|
|
12
|
-
unit: FigureUnit;
|
|
13
|
-
value: number;
|
|
14
|
-
}
|
|
15
|
-
export interface FigureEditRequest extends FigureEditControl {
|
|
16
|
-
changes: FigureInputChange[];
|
|
17
|
-
}
|
|
18
|
-
export declare function createFigureAuthoringSession(document: FigureDocument, initialInputs: Record<string, number>): {
|
|
19
|
-
read: () => {
|
|
20
|
-
revision: number;
|
|
21
|
-
inputs: Record<string, number>;
|
|
22
|
-
evaluation: FigureCapabilityEvaluation;
|
|
23
|
-
canUndo: boolean;
|
|
24
|
-
canRedo: boolean;
|
|
25
|
-
};
|
|
26
|
-
preview(q: FigureEditRequest): {
|
|
27
|
-
candidate: {
|
|
28
|
-
inputs: Record<string, number>;
|
|
29
|
-
evaluation: FigureCapabilityEvaluation;
|
|
30
|
-
};
|
|
31
|
-
baseRevision: number;
|
|
32
|
-
origin: "ui" | "ai";
|
|
33
|
-
changes: {
|
|
34
|
-
before: number | undefined;
|
|
35
|
-
kind: "set-design" | "set-state";
|
|
36
|
-
input: string;
|
|
37
|
-
unit: FigureUnit;
|
|
38
|
-
value: number;
|
|
39
|
-
}[];
|
|
40
|
-
effects: {
|
|
41
|
-
geometry: {
|
|
42
|
-
added: string[];
|
|
43
|
-
removed: string[];
|
|
44
|
-
changed: string[];
|
|
45
|
-
};
|
|
46
|
-
bindingsChanged: string[];
|
|
47
|
-
};
|
|
48
|
-
validation: {
|
|
49
|
-
status: string;
|
|
50
|
-
spatial: string;
|
|
51
|
-
collision: string;
|
|
52
|
-
safety: string;
|
|
53
|
-
};
|
|
54
|
-
};
|
|
55
|
-
apply(q: FigureEditRequest): {
|
|
56
|
-
revision: number;
|
|
57
|
-
inputs: Record<string, number>;
|
|
58
|
-
evaluation: FigureCapabilityEvaluation;
|
|
59
|
-
canUndo: boolean;
|
|
60
|
-
canRedo: boolean;
|
|
61
|
-
report: {
|
|
62
|
-
baseRevision: number;
|
|
63
|
-
origin: "ui" | "ai";
|
|
64
|
-
changes: {
|
|
65
|
-
before: number | undefined;
|
|
66
|
-
kind: "set-design" | "set-state";
|
|
67
|
-
input: string;
|
|
68
|
-
unit: FigureUnit;
|
|
69
|
-
value: number;
|
|
70
|
-
}[];
|
|
71
|
-
effects: {
|
|
72
|
-
geometry: {
|
|
73
|
-
added: string[];
|
|
74
|
-
removed: string[];
|
|
75
|
-
changed: string[];
|
|
76
|
-
};
|
|
77
|
-
bindingsChanged: string[];
|
|
78
|
-
};
|
|
79
|
-
validation: {
|
|
80
|
-
status: string;
|
|
81
|
-
spatial: string;
|
|
82
|
-
collision: string;
|
|
83
|
-
safety: string;
|
|
84
|
-
};
|
|
85
|
-
};
|
|
86
|
-
};
|
|
87
|
-
undo(q: FigureEditControl): {
|
|
88
|
-
revision: number;
|
|
89
|
-
inputs: Record<string, number>;
|
|
90
|
-
evaluation: FigureCapabilityEvaluation;
|
|
91
|
-
canUndo: boolean;
|
|
92
|
-
canRedo: boolean;
|
|
93
|
-
origin: "ui" | "ai";
|
|
94
|
-
effects: {
|
|
95
|
-
geometry: {
|
|
96
|
-
added: string[];
|
|
97
|
-
removed: string[];
|
|
98
|
-
changed: string[];
|
|
99
|
-
};
|
|
100
|
-
bindingsChanged: string[];
|
|
101
|
-
};
|
|
102
|
-
};
|
|
103
|
-
redo(q: FigureEditControl): {
|
|
104
|
-
revision: number;
|
|
105
|
-
inputs: Record<string, number>;
|
|
106
|
-
evaluation: FigureCapabilityEvaluation;
|
|
107
|
-
canUndo: boolean;
|
|
108
|
-
canRedo: boolean;
|
|
109
|
-
origin: "ui" | "ai";
|
|
110
|
-
effects: {
|
|
111
|
-
geometry: {
|
|
112
|
-
added: string[];
|
|
113
|
-
removed: string[];
|
|
114
|
-
changed: string[];
|
|
115
|
-
};
|
|
116
|
-
bindingsChanged: string[];
|
|
117
|
-
};
|
|
118
|
-
};
|
|
119
|
-
};
|
|
120
|
-
export type FigureAuthoringSession = ReturnType<typeof createFigureAuthoringSession>;
|
|
121
|
-
//# sourceMappingURL=authoring-session.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"authoring-session.d.ts","sourceRoot":"","sources":["../src/authoring-session.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAA;AAClF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,IAAI,GAAG,IAAI,CAAA;IACnB,gBAAgB,EAAE,MAAM,CAAA;IACxB,sGAAsG;IACtG,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAC9B;AACD,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,YAAY,GAAG,WAAW,CAAA;IAChC,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,UAAU,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;CACd;AACD,MAAM,WAAW,iBAAkB,SAAQ,iBAAiB;IAC1D,OAAO,EAAE,iBAAiB,EAAE,CAAA;CAC7B;AA6BD,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;;;;;;;;eAmE7F,iBAAiB;;;;;;;;;kBAvGxB,YAAY,GAAG,WAAW;mBACzB,MAAM;kBACP,UAAU;mBACT,MAAM;;;;;;;;;;;;;;;;;aAwGF,iBAAiB;;;;;;;;;;;sBA3GtB,YAAY,GAAG,WAAW;uBACzB,MAAM;sBACP,UAAU;uBACT,MAAM;;;;;;;;;;;;;;;;;;YAkHH,iBAAiB;;;;;;;;;;;;;;;;YAYjB,iBAAiB;;;;;;;;;;;;;;;;EAa5B;AAED,MAAM,MAAM,sBAAsB,GAAG,UAAU,CAAC,OAAO,4BAA4B,CAAC,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"authoring-session.js","sourceRoot":"","sources":["../src/authoring-session.ts"],"names":[],"mappings":"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"}
|
package/dist/carrier.d.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import type { FigureCarrierDeclaration } from './asset-types.ts';
|
|
2
|
-
/** The longest CARRIERTYPE code a figure may declare. */
|
|
3
|
-
export declare const FIGURE_CARRIER_TYPE_MAX = 64;
|
|
4
|
-
/** How CARRIERTYPE codes are compared: without the spaces around them, in upper case. */
|
|
5
|
-
export declare function normalizeCarrierType(code: unknown): string;
|
|
6
|
-
/** Why a value is not a carrier declaration, with the path under `path`, or null when it is. */
|
|
7
|
-
export declare function carrierDeclarationRefusal(value: unknown, path?: string): {
|
|
8
|
-
code: string;
|
|
9
|
-
path: string;
|
|
10
|
-
message: string;
|
|
11
|
-
} | null;
|
|
12
|
-
/** The refusal as the error it is. */
|
|
13
|
-
export declare function checkCarrierDeclaration(value: unknown, path?: string): asserts value is FigureCarrierDeclaration;
|
|
14
|
-
/** A declaration as stored: the code compared form, the three lengths, nothing else. */
|
|
15
|
-
export declare function carrierDeclarationOf(value: {
|
|
16
|
-
type: string;
|
|
17
|
-
dimensions: {
|
|
18
|
-
width: number;
|
|
19
|
-
length: number;
|
|
20
|
-
height: number;
|
|
21
|
-
};
|
|
22
|
-
}): FigureCarrierDeclaration;
|
|
23
|
-
//# sourceMappingURL=carrier.d.ts.map
|
package/dist/carrier.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"carrier.d.ts","sourceRoot":"","sources":["../src/carrier.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAA;AAEhE,yDAAyD;AACzD,eAAO,MAAM,uBAAuB,KAAK,CAAA;AAEzC,yFAAyF;AACzF,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAI1D;AAID,gGAAgG;AAChG,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,SAAY,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAuBlI;AAED,sCAAsC;AACtC,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,SAAY,GAAG,OAAO,CAAC,KAAK,IAAI,wBAAwB,CAGnH;AAED,wFAAwF;AACxF,wBAAgB,oBAAoB,CAAC,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GAAG,wBAAwB,CAOrJ"}
|
package/dist/carrier.js
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright © HatioLab Inc. All rights reserved.
|
|
3
|
-
*/
|
|
4
|
-
/*
|
|
5
|
-
* A figure that is a carrier type says so (ADR-0123): the CARRIERTYPE code a material handling system reports, and the
|
|
6
|
-
* carrier's physical size. Both are declared on the release. The size is never read off the occupied room, which may
|
|
7
|
-
* hold clearance and placement conditions; a figure that is not a carrier declares nothing.
|
|
8
|
-
*
|
|
9
|
-
* One rule for reading an asset and for the command that declares it, so a refused declaration is refused, never
|
|
10
|
-
* dropped quietly.
|
|
11
|
-
*/
|
|
12
|
-
import { FigureContractError } from "./contract.js";
|
|
13
|
-
/** The longest CARRIERTYPE code a figure may declare. */
|
|
14
|
-
export const FIGURE_CARRIER_TYPE_MAX = 64;
|
|
15
|
-
/** How CARRIERTYPE codes are compared: without the spaces around them, in upper case. */
|
|
16
|
-
export function normalizeCarrierType(code) {
|
|
17
|
-
return String(code ?? '')
|
|
18
|
-
.trim()
|
|
19
|
-
.toUpperCase();
|
|
20
|
-
}
|
|
21
|
-
const positive = (v) => typeof v === 'number' && Number.isFinite(v) && v > 0;
|
|
22
|
-
/** Why a value is not a carrier declaration, with the path under `path`, or null when it is. */
|
|
23
|
-
export function carrierDeclarationRefusal(value, path = 'carrier') {
|
|
24
|
-
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
25
|
-
return { code: 'ASSET_SCHEMA', path, message: 'object required' };
|
|
26
|
-
const given = value;
|
|
27
|
-
for (const key of Object.keys(given))
|
|
28
|
-
if (key !== 'type' && key !== 'dimensions')
|
|
29
|
-
return { code: 'ASSET_SCHEMA', path, message: `unknown ${key}` };
|
|
30
|
-
const type = given.type;
|
|
31
|
-
if (typeof type !== 'string' || !normalizeCarrierType(type))
|
|
32
|
-
return { code: 'CARRIER_TYPE', path: `${path}.type`, message: 'a CARRIERTYPE code is required' };
|
|
33
|
-
if (type !== normalizeCarrierType(type))
|
|
34
|
-
return { code: 'CARRIER_TYPE', path: `${path}.type`, message: `${type} is not written as compared; ${normalizeCarrierType(type)}` };
|
|
35
|
-
if (type.length > FIGURE_CARRIER_TYPE_MAX)
|
|
36
|
-
return { code: 'CARRIER_TYPE', path: `${path}.type`, message: `at most ${FIGURE_CARRIER_TYPE_MAX} characters` };
|
|
37
|
-
const dims = given.dimensions;
|
|
38
|
-
if (!dims || typeof dims !== 'object' || Array.isArray(dims))
|
|
39
|
-
return { code: 'ASSET_SCHEMA', path: `${path}.dimensions`, message: 'object required' };
|
|
40
|
-
for (const key of Object.keys(dims))
|
|
41
|
-
if (key !== 'width' && key !== 'length' && key !== 'height')
|
|
42
|
-
return { code: 'ASSET_SCHEMA', path: `${path}.dimensions`, message: `unknown ${key}` };
|
|
43
|
-
for (const axis of ['width', 'length', 'height']) {
|
|
44
|
-
const v = dims[axis];
|
|
45
|
-
if (!positive(v))
|
|
46
|
-
return { code: 'CARRIER_DIMENSIONS', path: `${path}.dimensions.${axis}`, message: 'a finite positive length in mm is required' };
|
|
47
|
-
}
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
/** The refusal as the error it is. */
|
|
51
|
-
export function checkCarrierDeclaration(value, path = 'carrier') {
|
|
52
|
-
const refusal = carrierDeclarationRefusal(value, path);
|
|
53
|
-
if (refusal)
|
|
54
|
-
throw new FigureContractError(refusal.code, refusal.path, refusal.message);
|
|
55
|
-
}
|
|
56
|
-
/** A declaration as stored: the code compared form, the three lengths, nothing else. */
|
|
57
|
-
export function carrierDeclarationOf(value) {
|
|
58
|
-
const declaration = {
|
|
59
|
-
type: normalizeCarrierType(value?.type),
|
|
60
|
-
dimensions: { width: value?.dimensions?.width, length: value?.dimensions?.length, height: value?.dimensions?.height }
|
|
61
|
-
};
|
|
62
|
-
checkCarrierDeclaration(declaration);
|
|
63
|
-
return declaration;
|
|
64
|
-
}
|
|
65
|
-
//# sourceMappingURL=carrier.js.map
|
package/dist/carrier.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"carrier.js","sourceRoot":"","sources":["../src/carrier.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import * as dryroom from './full-dryroom.mjs'
|
|
2
|
-
import * as conveyor from './full-conveyor.mjs'
|
|
3
|
-
import * as robot from './full-robot.mjs'
|
|
4
|
-
import * as forklift from './full-forklift.mjs'
|
|
5
|
-
import {robotBinding,forkliftBindings} from './capability-samples.mjs'
|
|
6
|
-
export function sampleAsset(kind){
|
|
7
|
-
const modules={dryroom,conveyor,robot,forklift},sample=modules[kind]
|
|
8
|
-
if(!sample)throw Error('unknown sample')
|
|
9
|
-
const pair=kind==='robot'?robotBinding():kind==='forklift'?forkliftBindings():{model:sample.fullModel(),declaration:{version:'r3-capability-3',assetFrame:'world',bindings:[]}}
|
|
10
|
-
const designInputs={},stateDefaults={}
|
|
11
|
-
for(const i of pair.model.inputs)(i.role==='state'?stateDefaults:designInputs)[i.id]=sample.defaults[i.id]
|
|
12
|
-
return {version:'figure-asset-1',name:sample.seed.type,document:{version:'figure-prototype-1',model:pair.model,capabilities:pair.declaration},designInputs,stateDefaults,
|
|
13
|
-
palette:{'palette.primary':'#154473','palette.accent':'#f79c40','palette.neutral':'#96989b','palette.dark':'#27292c','palette.light':'#d6d7d9','palette.surface':'#56585b','palette.status-danger':'#f61b30','palette.status-warning':'#e68d1a','palette.status-info':'#158bde','palette.status-success':'#1cb648'},
|
|
14
|
-
appearance:sample.seed.parts.map(p=>({target:p.name,material:structuredClone(p.material??{token:'palette.neutral'}),...(p.primitive==='cylinder'?{segments:p.segments??8}:{}),...(p.capability?{legacyCapability:structuredClone(p.capability)}:{})}))}
|
|
15
|
-
}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
// Single-process, isolated disk store. Not the product database or an authorization layer.
|
|
2
|
-
import {readFile, open, rename, unlink} from 'node:fs/promises'
|
|
3
|
-
import {join, resolve} from 'node:path'
|
|
4
|
-
import {randomUUID, createHash} from 'node:crypto'
|
|
5
|
-
import {parseAsset, serializeAsset} from './asset.mjs'
|
|
6
|
-
|
|
7
|
-
export class StoreError extends Error {
|
|
8
|
-
constructor(code,message){super(message);this.code=code}
|
|
9
|
-
}
|
|
10
|
-
const fail=(code,message)=>{throw new StoreError(code,message)}
|
|
11
|
-
// Shared across store handles in this process; a multi-process deployment needs database CAS.
|
|
12
|
-
const queues=new Map()
|
|
13
|
-
async function exclusive(key,operation){
|
|
14
|
-
const previous=queues.get(key)||Promise.resolve()
|
|
15
|
-
const next=previous.catch(()=>{}).then(operation);queues.set(key,next)
|
|
16
|
-
try{return await next}finally{if(queues.get(key)===next)queues.delete(key)}
|
|
17
|
-
}
|
|
18
|
-
export function createAssetStore(directory){
|
|
19
|
-
const root=resolve(directory)
|
|
20
|
-
function file(scope,id){
|
|
21
|
-
for(const v of [scope,id])if(typeof v!=='string'||!v.trim()||v.length>200)fail('IDENTITY','scope and asset ID required')
|
|
22
|
-
return join(root,createHash('sha256').update(JSON.stringify([scope,id])).digest('hex')+'.json')
|
|
23
|
-
}
|
|
24
|
-
async function load(path,scope,id){
|
|
25
|
-
let raw;try{raw=await readFile(path,'utf8')}catch(e){if(e.code==='ENOENT')return null;throw e}
|
|
26
|
-
let record;try{record=JSON.parse(raw)}catch{fail('CORRUPT','stored record is not JSON')}
|
|
27
|
-
if(record?.format!=='store-experiment-1'||record.scope!==scope||record.id!==id||typeof record.revision!=='string'||!record.revision)fail('CORRUPT','stored record identity or revision invalid')
|
|
28
|
-
// Stored content must pass the same semantic validation as incoming content.
|
|
29
|
-
parseAsset(record.text)
|
|
30
|
-
return record
|
|
31
|
-
}
|
|
32
|
-
return {
|
|
33
|
-
async read({scope,id}){const record=await load(file(scope,id),scope,id);if(!record)fail('NOT_FOUND','asset not found');return record},
|
|
34
|
-
async save({scope,id,expectedRevision,text}){
|
|
35
|
-
if(expectedRevision!==null&&(typeof expectedRevision!=='string'||!expectedRevision))fail('PRECONDITION','expectedRevision required; null means create only')
|
|
36
|
-
const path=file(scope,id),canonical=serializeAsset(parseAsset(text))
|
|
37
|
-
return exclusive(path,async()=>{
|
|
38
|
-
const current=await load(path,scope,id)
|
|
39
|
-
if((current?.revision??null)!==expectedRevision)fail('CONFLICT','asset changed; reload before saving')
|
|
40
|
-
const record={format:'store-experiment-1',scope,id,revision:randomUUID(),text:canonical}
|
|
41
|
-
const temporary=path+'.'+randomUUID()+'.tmp';let handle
|
|
42
|
-
try{
|
|
43
|
-
handle=await open(temporary,'wx',0o600);await handle.writeFile(JSON.stringify(record));await handle.sync();await handle.close();handle=null
|
|
44
|
-
await rename(temporary,path)
|
|
45
|
-
}finally{
|
|
46
|
-
await handle?.close();await unlink(temporary).catch(e=>{if(e.code!=='ENOENT')throw e})
|
|
47
|
-
}
|
|
48
|
-
return record
|
|
49
|
-
})
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import {test} from 'node:test'
|
|
2
|
-
import assert from 'node:assert/strict'
|
|
3
|
-
import {mkdtemp,rm,readdir} from 'node:fs/promises'
|
|
4
|
-
import {tmpdir} from 'node:os'
|
|
5
|
-
import {join} from 'node:path'
|
|
6
|
-
import {execFile} from 'node:child_process'
|
|
7
|
-
import {promisify} from 'node:util'
|
|
8
|
-
import {createAssetStore} from './asset-store.mjs'
|
|
9
|
-
import {sampleAsset} from './asset-samples.mjs'
|
|
10
|
-
import {serializeAsset,parseAsset} from './asset.mjs'
|
|
11
|
-
import {createEditorWorkspace} from './editor-workspace.mjs'
|
|
12
|
-
async function fixture(t){const dir=await mkdtemp(join(tmpdir(),'store-'));t.after(()=>rm(dir,{recursive:true,force:true}));return {dir,store:createAssetStore(dir)}}
|
|
13
|
-
const body=()=>serializeAsset(sampleAsset('forklift'))
|
|
14
|
-
const key={scope:'test-domain',id:'forklift'}
|
|
15
|
-
test('disk save survives independent store handle; explicit create and scoped identity',async t=>{
|
|
16
|
-
const {dir,store}=await fixture(t),saved=await store.save({...key,expectedRevision:null,text:body()})
|
|
17
|
-
assert.deepEqual(await createAssetStore(dir).read(key),saved)
|
|
18
|
-
await assert.rejects(store.read({...key,scope:'other-domain'}),e=>e.code==='NOT_FOUND')
|
|
19
|
-
await assert.rejects(store.save({...key,expectedRevision:null,text:body()}),e=>e.code==='CONFLICT')
|
|
20
|
-
await assert.rejects(store.save({...key,text:body()}),e=>e.code==='PRECONDITION')
|
|
21
|
-
// Raw identifiers never become filesystem paths.
|
|
22
|
-
await store.save({scope:'../other',id:'../../escape',expectedRevision:null,text:body()})
|
|
23
|
-
assert.equal((await readdir(dir)).length,2)
|
|
24
|
-
assert.ok((await readdir(dir)).every(f=>/^[0-9a-f]{64}\.json$/.test(f)))
|
|
25
|
-
})
|
|
26
|
-
test('two writers cannot silently overwrite; content ABA still gets new revision',async t=>{
|
|
27
|
-
const {dir,store}=await fixture(t),a=await store.save({...key,expectedRevision:null,text:body()})
|
|
28
|
-
const asset=parseAsset(body());asset.designInputs.forkLength=1275;const changed=serializeAsset(asset)
|
|
29
|
-
const results=await Promise.allSettled([store,createAssetStore(dir)].map(s=>s.save({...key,expectedRevision:a.revision,text:changed})))
|
|
30
|
-
assert.equal(results.filter(r=>r.status==='fulfilled').length,1)
|
|
31
|
-
assert.equal(results.find(r=>r.status==='rejected').reason.code,'CONFLICT')
|
|
32
|
-
const b=await store.read(key),c=await store.save({...key,expectedRevision:b.revision,text:body()})
|
|
33
|
-
assert.notEqual(c.revision,a.revision)
|
|
34
|
-
await assert.rejects(store.save({...key,expectedRevision:a.revision,text:changed}),e=>e.code==='CONFLICT')
|
|
35
|
-
})
|
|
36
|
-
test('invalid source leaves prior snapshot intact and produces no temporary file',async t=>{
|
|
37
|
-
const {dir,store}=await fixture(t),a=await store.save({...key,expectedRevision:null,text:body()})
|
|
38
|
-
await assert.rejects(store.save({...key,expectedRevision:a.revision,text:'{"version":2}'}))
|
|
39
|
-
assert.deepEqual(await store.read(key),a);assert.equal((await readdir(dir)).length,1)
|
|
40
|
-
})
|
|
41
|
-
test('failed persistence does not clear editor dirty state',async t=>{
|
|
42
|
-
const {store}=await fixture(t),original=await store.save({...key,expectedRevision:null,text:body()})
|
|
43
|
-
const w=createEditorWorkspace(),v=w.open(original.text)
|
|
44
|
-
w.apply({context:v.context,origin:'ui',expectedRevision:0,changes:[{kind:'set-design',input:'forkLength',unit:'mm',value:1275}]})
|
|
45
|
-
const snapshot=w.export({context:v.context})
|
|
46
|
-
await store.save({...key,expectedRevision:original.revision,text:body()})
|
|
47
|
-
await assert.rejects(store.save({...key,expectedRevision:original.revision,text:snapshot.text}),e=>e.code==='CONFLICT')
|
|
48
|
-
assert.equal(w.read().dirty,true)
|
|
49
|
-
assert.equal(w.read().fields.find(f=>f.id==='forkLength').value,1275)
|
|
50
|
-
})
|
|
51
|
-
test('a separate Node process can restore the saved asset without editor memory',async t=>{
|
|
52
|
-
const {dir,store}=await fixture(t),asset=parseAsset(body());asset.designInputs.forkLength=1275
|
|
53
|
-
const saved=await store.save({...key,expectedRevision:null,text:serializeAsset(asset)})
|
|
54
|
-
const script=`import {createAssetStore} from ${JSON.stringify(new URL('./asset-store.mjs',import.meta.url).href)};
|
|
55
|
-
const record=await createAssetStore(process.argv[1]).read(${JSON.stringify(key)});
|
|
56
|
-
console.log(JSON.stringify({revision:record.revision,length:JSON.parse(record.text).designInputs.forkLength}));`
|
|
57
|
-
const {stdout}=await promisify(execFile)(process.execPath,['--input-type=module','-e',script,dir])
|
|
58
|
-
assert.deepEqual(JSON.parse(stdout),{revision:saved.revision,length:1275})
|
|
59
|
-
})
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {compileFigureAsset as compileAsset,parseFigureAsset as parseAsset,serializeFigureAsset as serializeAsset,captureFigureAuthoredInputs as captureAuthoredInputs} from '../../src/asset.ts'
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
-
"title": "Experimental Figure asset envelope; not a complete V2 migration contract",
|
|
4
|
-
"type": "object", "additionalProperties": false,
|
|
5
|
-
"required": ["version", "name", "document", "designInputs", "stateDefaults", "palette", "appearance"],
|
|
6
|
-
"properties": {
|
|
7
|
-
"version": {"const": "figure-asset-1"},
|
|
8
|
-
"name": {"type": "string", "minLength": 1},
|
|
9
|
-
"document": {"$ref": "document.schema.json"},
|
|
10
|
-
"designInputs": {"type": "object", "additionalProperties": {"type": "number"}},
|
|
11
|
-
"stateDefaults": {"type": "object", "additionalProperties": {"type": "number"}},
|
|
12
|
-
"palette": {"type": "object", "additionalProperties": {"type": "string", "pattern": "^#[0-9a-fA-F]{6}$"}},
|
|
13
|
-
"appearance": {"type": "array", "items": {"$ref": "#/definitions/appearance"}}
|
|
14
|
-
},
|
|
15
|
-
"definitions": {
|
|
16
|
-
"appearance": {
|
|
17
|
-
"type": "object", "additionalProperties": false, "required": ["target", "material"],
|
|
18
|
-
"properties": {
|
|
19
|
-
"target": {"type": "string", "minLength": 1},
|
|
20
|
-
"segments": {"enum": [8,12,16,24,32,48]},
|
|
21
|
-
"material": {
|
|
22
|
-
"type": "object", "additionalProperties": false, "required": ["token"],
|
|
23
|
-
"properties": {
|
|
24
|
-
"token": {"type": "string", "minLength": 1},
|
|
25
|
-
"preset": {"enum": ["default", "metal", "glass", "plastic", "wood", "ceramic", "rubber"]},
|
|
26
|
-
"transparent": {"type": "boolean"},
|
|
27
|
-
"emissive": {
|
|
28
|
-
"type": "object", "additionalProperties": false, "required": ["token", "intensity", "on"],
|
|
29
|
-
"properties": {"token": {"type": "string", "minLength": 1}, "intensity": {"type": "number", "minimum": 0, "maximum": 5}, "on": {"type": "boolean"}}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import {test} from 'node:test'
|
|
2
|
-
import assert from 'node:assert/strict'
|
|
3
|
-
import {compileAsset,serializeAsset,parseAsset,captureAuthoredInputs} from './asset.mjs'
|
|
4
|
-
import {sampleAsset} from './asset-samples.mjs'
|
|
5
|
-
import {createAuthoringSession} from './authoring-session.mjs'
|
|
6
|
-
import * as dryroom from './full-dryroom.mjs'
|
|
7
|
-
import * as conveyor from './full-conveyor.mjs'
|
|
8
|
-
import * as robot from './full-robot.mjs'
|
|
9
|
-
import * as forklift from './full-forklift.mjs'
|
|
10
|
-
import fs from 'node:fs/promises'
|
|
11
|
-
import os from 'node:os'
|
|
12
|
-
import path from 'node:path'
|
|
13
|
-
import Ajv from '../../../things-factory/node_modules/ajv/lib/ajv.js'
|
|
14
|
-
const samples={dryroom,conveyor,robot,forklift}
|
|
15
|
-
test('four complete sample assets round-trip: geometry, bindings, materials, quality and palette',()=>{
|
|
16
|
-
for(const [kind,sample] of Object.entries(samples)){
|
|
17
|
-
const asset=sampleAsset(kind),text=serializeAsset(asset),restored=parseAsset(text),a=compileAsset(asset).evaluate(),b=compileAsset(restored).evaluate()
|
|
18
|
-
assert.deepEqual(restored,asset);assert.deepEqual(a,b)
|
|
19
|
-
const expected=sample.evaluateFull()
|
|
20
|
-
for(const g of b.geometry){const source=expected.find(x=>x.id===g.id);assert.ok(source);assert.deepEqual(g.shape,source.shape);assert.deepEqual(g.pose,source.pose);assert.deepEqual(g.material,source.material);if(g.shape.provider==='cylinder')assert.equal(g.segments,source.segments??sample.seed.parts.find(p=>p.name===g.id)?.segments??16)}
|
|
21
|
-
console.log({asset:kind,bytes:Buffer.byteLength(text),meshes:b.geometry.length})
|
|
22
|
-
}
|
|
23
|
-
const r=compileAsset(parseAsset(serializeAsset(sampleAsset('robot')))).evaluate()
|
|
24
|
-
assert.deepEqual(r.geometry.find(g=>g.id==='tool-seat').legacyCapability,robot.seed.parts.find(p=>p.name==='tool-seat').capability)
|
|
25
|
-
})
|
|
26
|
-
test('save edited design but reset transient motion unless explicitly captured as defaults',()=>{
|
|
27
|
-
const asset=sampleAsset('forklift'),session=createAuthoringSession(asset.document,{...asset.designInputs,...asset.stateDefaults})
|
|
28
|
-
session.apply({origin:'ai',expectedRevision:0,changes:[{kind:'set-design',input:'forkLength',unit:'mm',value:1275},{kind:'set-state',input:'lift',unit:'mm',value:1000}]})
|
|
29
|
-
const saved=captureAuthoredInputs(asset,session.read().inputs),r=compileAsset(parseAsset(serializeAsset(saved))).evaluate()
|
|
30
|
-
assert.equal(r.inputs.forkLength,1275);assert.equal(r.inputs.lift,0)
|
|
31
|
-
assert.equal(r.bindings[0].pose.t[1],57)
|
|
32
|
-
const captured=captureAuthoredInputs(asset,session.read().inputs,{captureStateDefaults:true})
|
|
33
|
-
assert.equal(compileAsset(parseAsset(serializeAsset(captured))).evaluate().bindings[0].pose.t[1],1057)
|
|
34
|
-
assert.equal(asset.designInputs.forkLength,850);assert.equal(asset.stateDefaults.lift,0)
|
|
35
|
-
})
|
|
36
|
-
test('saved repeat appearance applies to every new occurrence after dimension edit',()=>{
|
|
37
|
-
const a=sampleAsset('conveyor');a.designInputs.length=4800
|
|
38
|
-
const r=compileAsset(parseAsset(serializeAsset(a))).evaluate({angle:37}),rollers=r.geometry.filter(g=>g.key)
|
|
39
|
-
assert.equal(rollers.length,30);assert.ok(rollers.every(g=>g.segments===12&&g.material.preset==='metal'))
|
|
40
|
-
})
|
|
41
|
-
test('unknown or missing appearance, unresolved palette, invalid quality and mismatched inputs reject',()=>{
|
|
42
|
-
const edits=[a=>a.appearance.pop(),a=>a.appearance.push(a.appearance[0]),a=>a.appearance[0].target='missing',a=>delete a.palette['palette.primary'],a=>a.appearance.find(x=>x.segments).segments=13,a=>a.designInputs.lift=1,a=>delete a.stateDefaults.lift,a=>a.stateDefaults.lift=2001,a=>a.appearance[0].material.extra=true]
|
|
43
|
-
for(const edit of edits){const a=sampleAsset('forklift');edit(a);assert.throws(()=>serializeAsset(a))}
|
|
44
|
-
})
|
|
45
|
-
test('runtime cannot override design or mutate saved defaults through returned data',()=>{
|
|
46
|
-
const asset=sampleAsset('forklift'),p=compileAsset(asset),first=p.evaluate()
|
|
47
|
-
assert.throws(()=>p.evaluate({forkLength:1700}))
|
|
48
|
-
first.palette['palette.primary']='#000000';first.geometry[0].material.token='invalid';asset.designInputs.forkLength=0
|
|
49
|
-
assert.equal(p.evaluate().inputs.forkLength,850);assert.equal(p.evaluate().palette['palette.primary'],'#154473')
|
|
50
|
-
})
|
|
51
|
-
test('asset parser retains duplicate-key and invalid JSON protections',()=>{
|
|
52
|
-
const text=serializeAsset(sampleAsset('robot'))
|
|
53
|
-
assert.throws(()=>parseAsset('{"version":"fake",'+text.slice(1)),e=>e.code==='JSON_DUPLICATE_KEY')
|
|
54
|
-
assert.throws(()=>parseAsset(text.slice(0,-1)),e=>e.code==='JSON_SYNTAX')
|
|
55
|
-
const a=sampleAsset('forklift');a.stateDefaults.lift=NaN;assert.throws(()=>serializeAsset(a),e=>e.code==='JSON_VALUE')
|
|
56
|
-
})
|
|
57
|
-
test('schema validation and real temporary JSON file round-trip, without seed lookups on reload',async()=>{
|
|
58
|
-
const ajv=new Ajv({allErrors:true});ajv.addSchema(JSON.parse(await fs.readFile(new URL('./document.schema.json',import.meta.url))),'document.schema.json')
|
|
59
|
-
const validate=ajv.compile(JSON.parse(await fs.readFile(new URL('./asset.schema.json',import.meta.url))))
|
|
60
|
-
const directory=await fs.mkdtemp(path.join(os.tmpdir(),'figure-asset-'))
|
|
61
|
-
try{
|
|
62
|
-
for(const kind of Object.keys(samples)){
|
|
63
|
-
const asset=sampleAsset(kind);assert.ok(validate(asset),JSON.stringify(validate.errors))
|
|
64
|
-
const file=path.join(directory,kind+'.json')
|
|
65
|
-
try{await fs.writeFile(file,serializeAsset(asset),'utf8');const loaded=parseAsset(await fs.readFile(file,'utf8'));assert.deepEqual(compileAsset(loaded).evaluate(),compileAsset(asset).evaluate())}finally{await fs.unlink(file).catch(()=>{})}
|
|
66
|
-
}
|
|
67
|
-
}finally{await fs.rmdir(directory)}
|
|
68
|
-
})
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {createFigureAuthoringSession as createAuthoringSession} from '../../src/authoring-session.ts'
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import {test} from 'node:test'
|
|
2
|
-
import assert from 'node:assert/strict'
|
|
3
|
-
import {createAuthoringSession} from './authoring-session.mjs'
|
|
4
|
-
import {repeatedBinding} from './repeated-binding.mjs'
|
|
5
|
-
import {forkliftBindings} from './capability-samples.mjs'
|
|
6
|
-
import {defaults as conveyorDefaults} from './full-conveyor.mjs'
|
|
7
|
-
import {defaults as forkliftDefaults} from './full-forklift.mjs'
|
|
8
|
-
const doc=s=>({version:'figure-prototype-1',model:s.model,capabilities:s.declaration})
|
|
9
|
-
const edit=(input,value,unit='mm',kind='set-design')=>({kind,input,value,unit})
|
|
10
|
-
const request=(changes,expectedRevision=0,origin='ui')=>({origin,expectedRevision,changes})
|
|
11
|
-
const session=()=>createAuthoringSession(doc(repeatedBinding()),{...conveyorDefaults,selectedIndex:13})
|
|
12
|
-
test('UI and AI use identical transactions and get identical candidate geometry',()=>{
|
|
13
|
-
const ui=session(),ai=session(),changes=[edit('length',3000)]
|
|
14
|
-
const a=ui.apply(request(changes,0,'ui')),b=ai.apply(request(changes,0,'ai'))
|
|
15
|
-
assert.deepEqual(a.evaluation,b.evaluation);assert.deepEqual(a.inputs,b.inputs);assert.deepEqual(a.report.effects,b.report.effects)
|
|
16
|
-
assert.equal(a.report.effects.geometry.added.length,4);assert.equal(a.revision,1)
|
|
17
|
-
assert.equal(a.report.validation.collision,'unverified')
|
|
18
|
-
})
|
|
19
|
-
test('preview is detached and read-only; apply reevaluates the request instead of trusting preview result',()=>{
|
|
20
|
-
const s=session(),before=s.read(),q=request([edit('length',3000)]),preview=s.preview(q)
|
|
21
|
-
assert.deepEqual(s.read(),before);preview.candidate.inputs.length=1;preview.candidate.evaluation.geometry.length=0
|
|
22
|
-
const result=s.apply(q);assert.equal(result.inputs.length,3000);assert.ok(result.evaluation.geometry.length>0)
|
|
23
|
-
})
|
|
24
|
-
test('broken repeated reference rolls back everything; a valid batch retargets explicitly',()=>{
|
|
25
|
-
const s=session(),before=s.read()
|
|
26
|
-
assert.throws(()=>s.apply(request([edit('length',900)])),e=>e.code==='TARGET_ABSENT');assert.deepEqual(s.read(),before)
|
|
27
|
-
const r=s.apply(request([edit('length',900),edit('selectedIndex',0,'count')]))
|
|
28
|
-
assert.equal(r.revision,1);assert.equal(r.inputs.selectedIndex,0);assert.equal(r.report.effects.geometry.removed.length,10)
|
|
29
|
-
assert.notEqual(r.evaluation.bindings[0].targetGeometryId,before.evaluation.bindings[0].targetGeometryId)
|
|
30
|
-
})
|
|
31
|
-
test('stale AI proposal cannot overwrite a UI edit, including after undo (no revision reuse)',()=>{
|
|
32
|
-
const s=session(),old=request([edit('length',3000)],0,'ai');s.preview(old)
|
|
33
|
-
s.apply(request([edit('length',2550)]));assert.throws(()=>s.apply(old),e=>e.code==='REVISION_CONFLICT')
|
|
34
|
-
const restored=s.undo({origin:'ui',expectedRevision:1});assert.equal(restored.inputs.length,2400);assert.equal(restored.revision,2)
|
|
35
|
-
assert.throws(()=>s.apply(old),e=>e.code==='REVISION_CONFLICT')
|
|
36
|
-
})
|
|
37
|
-
test('undo/redo restore geometry and binding; failed edits and no-ops preserve redo',()=>{
|
|
38
|
-
const s=session(),base=s.read(),changed=s.apply(request([edit('length',3000)]))
|
|
39
|
-
const back=s.undo({origin:'ai',expectedRevision:1});assert.deepEqual(back.evaluation,base.evaluation)
|
|
40
|
-
assert.throws(()=>s.apply(request([edit('length',900)],2)),e=>e.code==='TARGET_ABSENT')
|
|
41
|
-
const noop=s.apply(request([edit('length',2400)],2));assert.equal(noop.revision,2);assert.equal(noop.canRedo,true)
|
|
42
|
-
const redone=s.redo({origin:'ui',expectedRevision:2});assert.deepEqual(redone.evaluation,changed.evaluation)
|
|
43
|
-
s.undo({origin:'ui',expectedRevision:3});s.apply(request([edit('length',2550)],4));assert.equal(s.read().canRedo,false)
|
|
44
|
-
})
|
|
45
|
-
test('role, unit, duplicate, range and unknown input errors cannot partially edit',()=>{
|
|
46
|
-
const s=session(),before=s.read()
|
|
47
|
-
for(const changes of [[edit('angle',37,'deg')],[edit('length',3,'m')],[edit('length',3000),edit('length',2400)],[edit('width',0)],[edit('missing',1)],[edit('length',3000),edit('width',NaN)]]){
|
|
48
|
-
assert.throws(()=>s.apply(request(changes)));assert.deepEqual(s.read(),before)
|
|
49
|
-
}
|
|
50
|
-
})
|
|
51
|
-
test('simultaneous forklift design/state edit moves bindings once and survives undo',()=>{
|
|
52
|
-
const s=createAuthoringSession(doc(forkliftBindings()),forkliftDefaults),before=s.read()
|
|
53
|
-
const r=s.apply(request([edit('forkLength',1275),edit('lift',1000,'mm','set-state'),edit('reach',200,'mm','set-state')],0,'ai'))
|
|
54
|
-
assert.deepEqual(r.evaluation.bindings[0].pose.t,[-260,1057,1197.5]);assert.equal(r.revision,1)
|
|
55
|
-
assert.deepEqual(s.undo({origin:'ui',expectedRevision:1}).evaluation,before.evaluation)
|
|
56
|
-
})
|
|
57
|
-
test('bounded history and externally mutated snapshots cannot corrupt future edits',()=>{
|
|
58
|
-
const s=session()
|
|
59
|
-
for(let i=0;i<55;i++)s.apply(request([edit('length',i%2?2400:2550)],i))
|
|
60
|
-
let view=s.read();view.inputs.length=0;view.evaluation.geometry.length=0
|
|
61
|
-
assert.ok(s.read().evaluation.geometry.length>0)
|
|
62
|
-
for(let i=0;i<50;i++)s.undo({origin:'ui',expectedRevision:55+i})
|
|
63
|
-
assert.equal(s.read().canUndo,false);assert.equal(s.read().canRedo,true)
|
|
64
|
-
assert.throws(()=>s.undo({origin:'ui',expectedRevision:105}),e=>e.code==='NOTHING_TO_UNDO')
|
|
65
|
-
})
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {compileFigureCapabilities as compileCapabilities} from '../../src/capabilities.ts'
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import {test} from 'node:test'
|
|
2
|
-
import assert from 'node:assert/strict'
|
|
3
|
-
import {compileCapabilities} from './capability-plan.mjs'
|
|
4
|
-
import {robotBinding,forkliftBindings} from './capability-samples.mjs'
|
|
5
|
-
import {defaults as robotDefaults} from './full-robot.mjs'
|
|
6
|
-
import {defaults as forkDefaults} from './full-forklift.mjs'
|
|
7
|
-
import {compile} from './typed-plan.mjs'
|
|
8
|
-
const near=(a,b)=>assert.ok(Math.abs(a-b)<1e-7)
|
|
9
|
-
const run=s=>compileCapabilities(s.model,s.declaration)
|
|
10
|
-
test('robot grip follows changing links and six joints without creating or hiding solids',()=>{
|
|
11
|
-
const s=robotBinding(),p=run(s),core=compile(s.model);let cases=0
|
|
12
|
-
for(const upperLength of [210,420,840])for(const foreLength of [190,380,760])for(const angle of [-60,0,60])for(const gripper of [0,50,100]){
|
|
13
|
-
const args={...robotDefaults,upperLength,foreLength,gripper,'base-pan':angle,'shoulder-lift':angle,'elbow-flex':angle,'wrist-pitch':angle,'wrist-yaw':angle,'tool-roll':angle}
|
|
14
|
-
const result=p.evaluate(args),base=core.evaluate(args)
|
|
15
|
-
assert.deepEqual(result.geometry,base.geometry);assert.equal(result.geometry.length,10)
|
|
16
|
-
assert.ok(result.geometry.some(g=>g.id==='tool-seat'))
|
|
17
|
-
assert.deepEqual(result.bindings[0].pose,base.values.flangeWorldTop)
|
|
18
|
-
for(const key of ['spatial','collision','safety'])assert.equal(result.bindings[0].checks[key].status,'unverified')
|
|
19
|
-
cases++
|
|
20
|
-
}
|
|
21
|
-
console.log({robotBindingStates:cases})
|
|
22
|
-
})
|
|
23
|
-
test('both forklift seats follow actual top faces through lift, reach and fork length exactly once',()=>{
|
|
24
|
-
const s=forkliftBindings(),p=run(s);let cases=0
|
|
25
|
-
for(const forkLength of [425,850,1700])for(const lift of [0,1000,2000])for(const reach of [0,200,400]){
|
|
26
|
-
const r=p.evaluate({...forkDefaults,forkLength,lift,reach});assert.equal(r.geometry.length,20)
|
|
27
|
-
for(const b of r.bindings){near(b.pose.t[0],-260);near(b.pose.t[1],(b.id==='load-low'?57:457)+lift);near(b.pose.t[2],360+forkLength/2+reach);assert.equal(b.contacts.length,2);near(b.contacts[1].pose.t[0],260);assert.equal(b.region,null)}
|
|
28
|
-
cases++
|
|
29
|
-
}
|
|
30
|
-
console.log({forkliftBindingStates:cases})
|
|
31
|
-
})
|
|
32
|
-
test('eligibility is separate from fit/collision/safety, no occupancy mutation or reservation',()=>{
|
|
33
|
-
const p=run(robotBinding())
|
|
34
|
-
for(const [request,status] of [[undefined,'unverified'],[{type:'BOX',occupied:0},'satisfied'],[{type:'PALLET',occupied:0},'failed'],[{type:'BOX',occupied:1},'failed']]){
|
|
35
|
-
const requests=request?{'tool-grip':request}:{},before=structuredClone(requests),r=p.evaluate(robotDefaults,requests)
|
|
36
|
-
assert.equal(r.bindings[0].checks.eligibility.status,status);assert.equal(r.bindings[0].checks.collision.status,'unverified');assert.deepEqual(requests,before)
|
|
37
|
-
}
|
|
38
|
-
assert.throws(()=>p.evaluate(robotDefaults,{'tool-grip':{type:'BOX',occupied:-1}}),e=>e.code==='OCCUPANCY')
|
|
39
|
-
assert.throws(()=>p.evaluate(robotDefaults,{unknown:{type:'BOX',occupied:0}}),e=>e.code==='SCHEMA')
|
|
40
|
-
})
|
|
41
|
-
test('missing references, wrong frame, wrong units, duplicate ids and ambiguous roles fail closed',()=>{
|
|
42
|
-
const checks=[['TARGET_ABSENT',b=>b.pose='deleted'],['FRAME_TYPE',b=>b.frame='wrong'],['ROLE',b=>b.role='slot'],['CAPACITY',b=>b.capacity=1.5],['TYPE',b=>b.region={kind:'centered-box',dimensions:['gripper','upperLength','foreLength']}],['SCHEMA',b=>b.region=null],['SCHEMA',b=>b.hidden=true]]
|
|
43
|
-
for(const [code,edit] of checks){const s=robotBinding();edit(s.declaration.bindings[0]);assert.throws(()=>run(s),e=>e.code===code)}
|
|
44
|
-
const s=robotBinding();s.declaration.bindings.push(structuredClone(s.declaration.bindings[0]));assert.throws(()=>run(s),e=>e.code==='DUPLICATE_BINDING')
|
|
45
|
-
})
|
|
46
|
-
test('region values have canonical mm, are not solid meshes, reject zero and recover atomically',()=>{
|
|
47
|
-
const s=robotBinding();s.model.inputs.push({id:'clearance',unit:'cm',min:0,max:10});s.declaration.bindings[0].region={kind:'centered-box',dimensions:['upperLength','foreLength','clearance']}
|
|
48
|
-
const p=run(s),args={...robotDefaults,clearance:2},first=p.evaluate(args)
|
|
49
|
-
assert.deepEqual(first.bindings[0].region.dimensions,[420,380,20]);assert.equal(first.geometry.length,10);assert.equal(first.bindings[0].checks.spatial.status,'unverified')
|
|
50
|
-
assert.throws(()=>p.evaluate({...args,clearance:0}),e=>e.code==='REGION_DOMAIN');assert.deepEqual(p.evaluate(args),first)
|
|
51
|
-
})
|
|
52
|
-
test('compiled declaration and returned results are isolated from external mutation',()=>{
|
|
53
|
-
const s=robotBinding(),p=run(s),a=p.evaluate(robotDefaults),snapshot=structuredClone(a)
|
|
54
|
-
s.declaration.bindings[0].pose='deleted';s.model.nodes.length=0;a.bindings[0].pose.t[0]=999;a.geometry.length=0
|
|
55
|
-
assert.deepEqual(p.evaluate(robotDefaults),snapshot)
|
|
56
|
-
})
|