@hatiolab/figure-model 0.1.29 → 0.1.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -1
- package/dist/release-contract.d.ts +2 -2
- package/dist/release-contract.d.ts.map +1 -1
- package/dist/release-contract.js +45 -3
- package/dist/release-contract.js.map +1 -1
- package/dist/types.d.ts +16 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +15 -4
- package/dist/types.js.map +1 -1
- package/dist/v3-asset-types.d.ts +116 -0
- package/dist/v3-asset-types.d.ts.map +1 -0
- package/dist/v3-asset-types.js +2 -0
- package/dist/v3-asset-types.js.map +1 -0
- package/dist/v3-asset.d.ts +8 -0
- package/dist/v3-asset.d.ts.map +1 -0
- package/dist/v3-asset.js +135 -0
- package/dist/v3-asset.js.map +1 -0
- package/dist/v3-authoring-session.d.ts +119 -0
- package/dist/v3-authoring-session.d.ts.map +1 -0
- package/dist/v3-authoring-session.js +132 -0
- package/dist/v3-authoring-session.js.map +1 -0
- package/dist/v3-capabilities.d.ts +3 -0
- package/dist/v3-capabilities.d.ts.map +1 -0
- package/dist/v3-capabilities.js +221 -0
- package/dist/v3-capabilities.js.map +1 -0
- package/dist/v3-contract.d.ts +7 -0
- package/dist/v3-contract.d.ts.map +1 -0
- package/dist/v3-contract.js +12 -0
- package/dist/v3-contract.js.map +1 -0
- package/dist/v3-create.d.ts +25 -0
- package/dist/v3-create.d.ts.map +1 -0
- package/dist/v3-create.js +50 -0
- package/dist/v3-create.js.map +1 -0
- package/dist/v3-document.d.ts +5 -0
- package/dist/v3-document.d.ts.map +1 -0
- package/dist/v3-document.js +31 -0
- package/dist/v3-document.js.map +1 -0
- package/dist/v3-editor-workspace.d.ts +334 -0
- package/dist/v3-editor-workspace.d.ts.map +1 -0
- package/dist/v3-editor-workspace.js +130 -0
- package/dist/v3-editor-workspace.js.map +1 -0
- package/dist/v3-graph-types.d.ts +91 -0
- package/dist/v3-graph-types.d.ts.map +1 -0
- package/dist/v3-graph-types.js +2 -0
- package/dist/v3-graph-types.js.map +1 -0
- package/dist/v3-graph.d.ts +5 -0
- package/dist/v3-graph.d.ts.map +1 -0
- package/dist/v3-graph.js +631 -0
- package/dist/v3-graph.js.map +1 -0
- package/dist/v3-json.d.ts +13 -0
- package/dist/v3-json.d.ts.map +1 -0
- package/dist/v3-json.js +94 -0
- package/dist/v3-json.js.map +1 -0
- package/dist/v3-part-edit.d.ts +12 -0
- package/dist/v3-part-edit.d.ts.map +1 -0
- package/dist/v3-part-edit.js +53 -0
- package/dist/v3-part-edit.js.map +1 -0
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +12 -11
- package/dist/validate.js.map +1 -1
- package/docs/assembly-constraints-v3.md +141 -0
- package/docs/prototypes/README.md +73 -0
- package/docs/prototypes/render-dryroom.py +60 -0
- package/docs/prototypes/render-full-dryroom.mjs +86 -0
- package/docs/prototypes/shapeassembly-probe-results.json +325 -0
- package/docs/prototypes/shapeassembly-probe.py +84 -0
- package/docs/prototypes/v3-asset-samples.mjs +15 -0
- package/docs/prototypes/v3-asset-store.mjs +52 -0
- package/docs/prototypes/v3-asset-store.test.mjs +59 -0
- package/docs/prototypes/v3-asset.mjs +1 -0
- package/docs/prototypes/v3-asset.schema.json +44 -0
- package/docs/prototypes/v3-asset.test.mjs +68 -0
- package/docs/prototypes/v3-authoring-session.mjs +1 -0
- package/docs/prototypes/v3-authoring-session.test.mjs +65 -0
- package/docs/prototypes/v3-capability-plan.mjs +1 -0
- package/docs/prototypes/v3-capability-plan.test.mjs +56 -0
- package/docs/prototypes/v3-capability-samples.mjs +23 -0
- package/docs/prototypes/v3-document.mjs +2 -0
- package/docs/prototypes/v3-document.schema.json +66 -0
- package/docs/prototypes/v3-document.test.mjs +55 -0
- package/docs/prototypes/v3-dryroom-mesh.mjs +60 -0
- package/docs/prototypes/v3-dryroom-mesh.test.mjs +30 -0
- package/docs/prototypes/v3-editor-browser.mjs +97 -0
- package/docs/prototypes/v3-editor-workspace.mjs +1 -0
- package/docs/prototypes/v3-editor-workspace.test.mjs +35 -0
- package/docs/prototypes/v3-feature-pattern.test.mjs +131 -0
- package/docs/prototypes/v3-full-conveyor.mjs +60 -0
- package/docs/prototypes/v3-full-conveyor.test.mjs +61 -0
- package/docs/prototypes/v3-full-dryroom.mjs +67 -0
- package/docs/prototypes/v3-full-dryroom.test.mjs +54 -0
- package/docs/prototypes/v3-full-forklift.mjs +40 -0
- package/docs/prototypes/v3-full-forklift.test.mjs +37 -0
- package/docs/prototypes/v3-full-robot.mjs +35 -0
- package/docs/prototypes/v3-full-robot.test.mjs +68 -0
- package/docs/prototypes/v3-kernel.mjs +147 -0
- package/docs/prototypes/v3-kernel.test.mjs +144 -0
- package/docs/prototypes/v3-layout.test.mjs +281 -0
- package/docs/prototypes/v3-linear-comparison.py +183 -0
- package/docs/prototypes/v3-more-samples.test.mjs +182 -0
- package/docs/prototypes/v3-payload-collision.mjs +54 -0
- package/docs/prototypes/v3-payload-collision.test.mjs +68 -0
- package/docs/prototypes/v3-payload-fit.mjs +58 -0
- package/docs/prototypes/v3-payload-fit.test.mjs +59 -0
- package/docs/prototypes/v3-repeated-binding.mjs +13 -0
- package/docs/prototypes/v3-repeated-binding.test.mjs +46 -0
- package/docs/prototypes/v3-runtime-browser.mjs +76 -0
- package/docs/prototypes/v3-samples.test.mjs +144 -0
- package/docs/prototypes/v3-semantics.test.mjs +55 -0
- package/docs/prototypes/v3-spatial-plan.test.mjs +102 -0
- package/docs/prototypes/v3-typed-plan.mjs +3 -0
- package/docs/prototypes/v3-typed-plan.test.mjs +70 -0
- package/docs/prototypes/v3-typed-repeat.test.mjs +96 -0
- package/docs/v3-asset-persistence.md +66 -0
- package/docs/v3-authoring-transactions.md +57 -0
- package/docs/v3-capability-bindings.md +109 -0
- package/docs/v3-common-kernel-experiment.md +116 -0
- package/docs/v3-core-semantics.md +303 -0
- package/docs/v3-cutover.md +27 -0
- package/docs/v3-design.md +388 -0
- package/docs/v3-document-contract.md +55 -0
- package/docs/v3-dryroom-profile-validation.md +87 -0
- package/docs/v3-editor-integration.md +59 -0
- package/docs/v3-full-conveyor-validation.md +69 -0
- package/docs/v3-full-dryroom-validation.md +55 -0
- package/docs/v3-full-forklift-validation.md +41 -0
- package/docs/v3-full-robot-validation.md +64 -0
- package/docs/v3-layout-system.md +318 -0
- package/docs/v3-minimal-model.md +255 -0
- package/docs/v3-operator-contracts.md +148 -0
- package/docs/v3-payload-collision-validation.md +42 -0
- package/docs/v3-payload-contact-validation.md +56 -0
- package/docs/v3-repeated-binding-validation.md +42 -0
- package/docs/v3-research-review.md +160 -0
- package/docs/v3-runtime-extraction.md +107 -0
- package/docs/v3-sample-validation-expanded.md +106 -0
- package/docs/v3-sample-validation.md +97 -0
- package/docs/v3-shapeassembly-reproduction.md +99 -0
- package/docs/v3-spatial-contracts.md +84 -0
- package/docs/v3-status.md +41 -0
- package/docs/v3-storage-integration.md +58 -0
- package/docs/v3-typed-repeat-contracts.md +79 -0
- package/package.json +1 -1
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
{
|
|
2
|
+
"revision": "cc22bd84afb65e8e37c7d14bebd9a4a629b5d943",
|
|
3
|
+
"python": "3.12.14 (main, Aug 25 2026, 13:50:33) [Clang 22.1.3 ]",
|
|
4
|
+
"torch": "2.8.0",
|
|
5
|
+
"numpy": "2.2.6",
|
|
6
|
+
"faiss": "1.12.0",
|
|
7
|
+
"units": "native abstract units, not Figure mm",
|
|
8
|
+
"results": [
|
|
9
|
+
{
|
|
10
|
+
"case": "lamp_fresh_replay_height_1",
|
|
11
|
+
"lines": [
|
|
12
|
+
"bbox = Cuboid(4, 1, 4, True)",
|
|
13
|
+
"body = Cuboid(1, 1, 1, True)",
|
|
14
|
+
"lamp = Cuboid(.2, .2, .2, True)",
|
|
15
|
+
"attach(body, bbox, .5, 0, .5, .5, 0, .5)",
|
|
16
|
+
"attach(lamp, body, .5, 0, .5, .5, 1, .5)"
|
|
17
|
+
],
|
|
18
|
+
"result": {
|
|
19
|
+
"body": {
|
|
20
|
+
"size": [
|
|
21
|
+
1,
|
|
22
|
+
1,
|
|
23
|
+
1
|
|
24
|
+
],
|
|
25
|
+
"center": [
|
|
26
|
+
0,
|
|
27
|
+
0,
|
|
28
|
+
0
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
"lamp": {
|
|
32
|
+
"size": [
|
|
33
|
+
0.20000000298023224,
|
|
34
|
+
0.20000000298023224,
|
|
35
|
+
0.20000000298023224
|
|
36
|
+
],
|
|
37
|
+
"center": [
|
|
38
|
+
0,
|
|
39
|
+
0.6000000238418579,
|
|
40
|
+
0
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"contact_point_residuals": [
|
|
45
|
+
0
|
|
46
|
+
]
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"case": "lamp_fresh_replay_height_2",
|
|
50
|
+
"lines": [
|
|
51
|
+
"bbox = Cuboid(4, 2, 4, True)",
|
|
52
|
+
"body = Cuboid(1, 2, 1, True)",
|
|
53
|
+
"lamp = Cuboid(.2, .2, .2, True)",
|
|
54
|
+
"attach(body, bbox, .5, 0, .5, .5, 0, .5)",
|
|
55
|
+
"attach(lamp, body, .5, 0, .5, .5, 1, .5)"
|
|
56
|
+
],
|
|
57
|
+
"result": {
|
|
58
|
+
"body": {
|
|
59
|
+
"size": [
|
|
60
|
+
1,
|
|
61
|
+
2,
|
|
62
|
+
1
|
|
63
|
+
],
|
|
64
|
+
"center": [
|
|
65
|
+
0,
|
|
66
|
+
0,
|
|
67
|
+
0
|
|
68
|
+
]
|
|
69
|
+
},
|
|
70
|
+
"lamp": {
|
|
71
|
+
"size": [
|
|
72
|
+
0.20000000298023224,
|
|
73
|
+
0.20000000298023224,
|
|
74
|
+
0.20000000298023224
|
|
75
|
+
],
|
|
76
|
+
"center": [
|
|
77
|
+
0,
|
|
78
|
+
1.100000023841858,
|
|
79
|
+
0
|
|
80
|
+
]
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
"contact_point_residuals": [
|
|
84
|
+
0
|
|
85
|
+
]
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"case": "lamp_fresh_replay_height_4",
|
|
89
|
+
"lines": [
|
|
90
|
+
"bbox = Cuboid(4, 4, 4, True)",
|
|
91
|
+
"body = Cuboid(1, 4, 1, True)",
|
|
92
|
+
"lamp = Cuboid(.2, .2, .2, True)",
|
|
93
|
+
"attach(body, bbox, .5, 0, .5, .5, 0, .5)",
|
|
94
|
+
"attach(lamp, body, .5, 0, .5, .5, 1, .5)"
|
|
95
|
+
],
|
|
96
|
+
"result": {
|
|
97
|
+
"body": {
|
|
98
|
+
"size": [
|
|
99
|
+
1,
|
|
100
|
+
4,
|
|
101
|
+
1
|
|
102
|
+
],
|
|
103
|
+
"center": [
|
|
104
|
+
0,
|
|
105
|
+
0,
|
|
106
|
+
0
|
|
107
|
+
]
|
|
108
|
+
},
|
|
109
|
+
"lamp": {
|
|
110
|
+
"size": [
|
|
111
|
+
0.20000000298023224,
|
|
112
|
+
0.20000000298023224,
|
|
113
|
+
0.20000000298023224
|
|
114
|
+
],
|
|
115
|
+
"center": [
|
|
116
|
+
0,
|
|
117
|
+
2.0999999046325684,
|
|
118
|
+
0
|
|
119
|
+
]
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
"contact_point_residuals": [
|
|
123
|
+
1.1920928955078125e-7
|
|
124
|
+
]
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"case": "squeeze_height_0.5_aligned_True",
|
|
128
|
+
"lines": [
|
|
129
|
+
"bbox = Cuboid(1, 1, 1, True)",
|
|
130
|
+
"beam = Cuboid(.2, 0.5, .2, True)",
|
|
131
|
+
"squeeze(beam, bbox, bbox, top, .5, .5)"
|
|
132
|
+
],
|
|
133
|
+
"result": {
|
|
134
|
+
"beam": {
|
|
135
|
+
"size": [
|
|
136
|
+
0.20000000298023224,
|
|
137
|
+
0.5,
|
|
138
|
+
0.20000000298023224
|
|
139
|
+
],
|
|
140
|
+
"center": [
|
|
141
|
+
0,
|
|
142
|
+
0.25,
|
|
143
|
+
0
|
|
144
|
+
]
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
"contact_point_residuals": [
|
|
148
|
+
0,
|
|
149
|
+
0.5
|
|
150
|
+
]
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
"case": "squeeze_height_2_aligned_True",
|
|
154
|
+
"lines": [
|
|
155
|
+
"bbox = Cuboid(1, 1, 1, True)",
|
|
156
|
+
"beam = Cuboid(.2, 2, .2, True)",
|
|
157
|
+
"squeeze(beam, bbox, bbox, top, .5, .5)"
|
|
158
|
+
],
|
|
159
|
+
"result": {
|
|
160
|
+
"beam": {
|
|
161
|
+
"size": [
|
|
162
|
+
0.20000000298023224,
|
|
163
|
+
2,
|
|
164
|
+
0.20000000298023224
|
|
165
|
+
],
|
|
166
|
+
"center": [
|
|
167
|
+
0,
|
|
168
|
+
-0.5,
|
|
169
|
+
0
|
|
170
|
+
]
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
"contact_point_residuals": [
|
|
174
|
+
0,
|
|
175
|
+
1
|
|
176
|
+
]
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
"case": "squeeze_height_0.5_aligned_False",
|
|
180
|
+
"lines": [
|
|
181
|
+
"bbox = Cuboid(1, 1, 1, True)",
|
|
182
|
+
"beam = Cuboid(.2, 0.5, .2, False)",
|
|
183
|
+
"squeeze(beam, bbox, bbox, top, .5, .5)"
|
|
184
|
+
],
|
|
185
|
+
"result": {
|
|
186
|
+
"beam": {
|
|
187
|
+
"size": [
|
|
188
|
+
0.20000000298023224,
|
|
189
|
+
1,
|
|
190
|
+
0.20000000298023224
|
|
191
|
+
],
|
|
192
|
+
"center": [
|
|
193
|
+
0,
|
|
194
|
+
0,
|
|
195
|
+
0
|
|
196
|
+
]
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
"contact_point_residuals": [
|
|
200
|
+
0,
|
|
201
|
+
0
|
|
202
|
+
]
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
"case": "squeeze_height_2_aligned_False",
|
|
206
|
+
"lines": [
|
|
207
|
+
"bbox = Cuboid(1, 1, 1, True)",
|
|
208
|
+
"beam = Cuboid(.2, 2, .2, False)",
|
|
209
|
+
"squeeze(beam, bbox, bbox, top, .5, .5)"
|
|
210
|
+
],
|
|
211
|
+
"result": {
|
|
212
|
+
"beam": {
|
|
213
|
+
"size": [
|
|
214
|
+
0.20000000298023224,
|
|
215
|
+
1,
|
|
216
|
+
0.20000000298023224
|
|
217
|
+
],
|
|
218
|
+
"center": [
|
|
219
|
+
0,
|
|
220
|
+
0,
|
|
221
|
+
0
|
|
222
|
+
]
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
"contact_point_residuals": [
|
|
226
|
+
0,
|
|
227
|
+
0
|
|
228
|
+
]
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
"case": "direct_resize_after_attachment",
|
|
232
|
+
"lines": [
|
|
233
|
+
"bbox = Cuboid(4, 4, 4, True)",
|
|
234
|
+
"body = Cuboid(1, 1, 1, True)",
|
|
235
|
+
"lamp = Cuboid(.2, .2, .2, True)",
|
|
236
|
+
"attach(body, bbox, .5, 0, .5, .5, 0, .5)",
|
|
237
|
+
"attach(lamp, body, .5, 0, .5, .5, 1, .5)",
|
|
238
|
+
"body = Cuboid(1, 2, 1, True)"
|
|
239
|
+
],
|
|
240
|
+
"result": {
|
|
241
|
+
"body": {
|
|
242
|
+
"size": [
|
|
243
|
+
1,
|
|
244
|
+
2,
|
|
245
|
+
1
|
|
246
|
+
],
|
|
247
|
+
"center": [
|
|
248
|
+
0,
|
|
249
|
+
-1.5,
|
|
250
|
+
0
|
|
251
|
+
]
|
|
252
|
+
},
|
|
253
|
+
"lamp": {
|
|
254
|
+
"size": [
|
|
255
|
+
0.20000000298023224,
|
|
256
|
+
0.20000000298023224,
|
|
257
|
+
0.20000000298023224
|
|
258
|
+
],
|
|
259
|
+
"center": [
|
|
260
|
+
0,
|
|
261
|
+
-0.8999999761581421,
|
|
262
|
+
0
|
|
263
|
+
]
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
"contact_point_residuals": [
|
|
267
|
+
0.5
|
|
268
|
+
]
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
"case": "conflicting_same_point_reverse_False",
|
|
272
|
+
"lines": [
|
|
273
|
+
"bbox = Cuboid(1, 1, 1, True)",
|
|
274
|
+
"part = Cuboid(.2, .2, .2, True)",
|
|
275
|
+
"attach(part, bbox, .5, 0, .5, .5, 0, .5)",
|
|
276
|
+
"attach(part, bbox, .5, 0, .5, .5, 1, .5)"
|
|
277
|
+
],
|
|
278
|
+
"result": {
|
|
279
|
+
"part": {
|
|
280
|
+
"size": [
|
|
281
|
+
0.20000000298023224,
|
|
282
|
+
0.20000000298023224,
|
|
283
|
+
0.20000000298023224
|
|
284
|
+
],
|
|
285
|
+
"center": [
|
|
286
|
+
0,
|
|
287
|
+
-0.4000000059604645,
|
|
288
|
+
0
|
|
289
|
+
]
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
"contact_point_residuals": [
|
|
293
|
+
0,
|
|
294
|
+
1
|
|
295
|
+
]
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
"case": "conflicting_same_point_reverse_True",
|
|
299
|
+
"lines": [
|
|
300
|
+
"bbox = Cuboid(1, 1, 1, True)",
|
|
301
|
+
"part = Cuboid(.2, .2, .2, True)",
|
|
302
|
+
"attach(part, bbox, .5, 0, .5, .5, 1, .5)",
|
|
303
|
+
"attach(part, bbox, .5, 0, .5, .5, 0, .5)"
|
|
304
|
+
],
|
|
305
|
+
"result": {
|
|
306
|
+
"part": {
|
|
307
|
+
"size": [
|
|
308
|
+
0.20000000298023224,
|
|
309
|
+
0.20000000298023224,
|
|
310
|
+
0.20000000298023224
|
|
311
|
+
],
|
|
312
|
+
"center": [
|
|
313
|
+
0,
|
|
314
|
+
0.6000000238418579,
|
|
315
|
+
0
|
|
316
|
+
]
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
"contact_point_residuals": [
|
|
320
|
+
1,
|
|
321
|
+
0
|
|
322
|
+
]
|
|
323
|
+
}
|
|
324
|
+
]
|
|
325
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Independent research probe. Requires an external checkout; not product code.
|
|
2
|
+
|
|
3
|
+
Usage: python shapeassembly-probe.py /path/to/ShapeAssembly
|
|
4
|
+
No upstream source is copied or modified. No models are trained/downloaded.
|
|
5
|
+
Reports observed behavior, not a claim that Figure or the paper is fully tested.
|
|
6
|
+
"""
|
|
7
|
+
import json
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
checkout = Path(sys.argv[1]).resolve()
|
|
13
|
+
expected = 'cc22bd84afb65e8e37c7d14bebd9a4a629b5d943'
|
|
14
|
+
revision = subprocess.check_output(['git', '-C', str(checkout), 'rev-parse', 'HEAD'], text=True).strip()
|
|
15
|
+
if revision != expected:
|
|
16
|
+
raise RuntimeError(f'Expected {expected}, found {revision}')
|
|
17
|
+
sys.path.insert(0, str(checkout / 'code'))
|
|
18
|
+
import torch
|
|
19
|
+
import numpy as np
|
|
20
|
+
import faiss
|
|
21
|
+
from ShapeAssembly import Program
|
|
22
|
+
|
|
23
|
+
torch.set_num_threads(1)
|
|
24
|
+
|
|
25
|
+
def point(p, name, xyz):
|
|
26
|
+
return p.cuboids[name].getPos(*(torch.tensor(float(v)) for v in xyz))
|
|
27
|
+
|
|
28
|
+
def snapshot(p):
|
|
29
|
+
return {name: {'size': c.dims.tolist(), 'center': c.pos.tolist()}
|
|
30
|
+
for name, c in p.cuboids.items() if name != 'bbox'}
|
|
31
|
+
|
|
32
|
+
def run(name, lines, contacts):
|
|
33
|
+
p = Program()
|
|
34
|
+
try:
|
|
35
|
+
for line in lines:
|
|
36
|
+
p.execute(line)
|
|
37
|
+
residuals = [float(torch.linalg.vector_norm(point(p,a,u)-point(p,b,v)))
|
|
38
|
+
for a,u,b,v in contacts]
|
|
39
|
+
return {'case': name, 'lines': lines, 'result': snapshot(p),
|
|
40
|
+
'contact_point_residuals': residuals}
|
|
41
|
+
except Exception as exc:
|
|
42
|
+
return {'case': name, 'lines': lines, 'error': type(exc).__name__ + ': ' + str(exc)}
|
|
43
|
+
|
|
44
|
+
results = []
|
|
45
|
+
for height in [1, 2, 4]:
|
|
46
|
+
results.append(run(f'lamp_fresh_replay_height_{height}', [
|
|
47
|
+
f'bbox = Cuboid(4, {height}, 4, True)',
|
|
48
|
+
f'body = Cuboid(1, {height}, 1, True)',
|
|
49
|
+
'lamp = Cuboid(.2, .2, .2, True)',
|
|
50
|
+
'attach(body, bbox, .5, 0, .5, .5, 0, .5)',
|
|
51
|
+
'attach(lamp, body, .5, 0, .5, .5, 1, .5)'
|
|
52
|
+
], [('lamp',(.5,0,.5),'body',(.5,1,.5))]))
|
|
53
|
+
|
|
54
|
+
for aligned in ['True', 'False']:
|
|
55
|
+
for size in [.5, 2]:
|
|
56
|
+
results.append(run(f'squeeze_height_{size}_aligned_{aligned}', [
|
|
57
|
+
'bbox = Cuboid(1, 1, 1, True)',
|
|
58
|
+
f'beam = Cuboid(.2, {size}, .2, {aligned})',
|
|
59
|
+
'squeeze(beam, bbox, bbox, top, .5, .5)'
|
|
60
|
+
], [('beam',(.5,1,.5),'bbox',(.5,1,.5)),
|
|
61
|
+
('beam',(.5,0,.5),'bbox',(.5,0,.5))]))
|
|
62
|
+
|
|
63
|
+
results.append(run('direct_resize_after_attachment', [
|
|
64
|
+
'bbox = Cuboid(4, 4, 4, True)',
|
|
65
|
+
'body = Cuboid(1, 1, 1, True)',
|
|
66
|
+
'lamp = Cuboid(.2, .2, .2, True)',
|
|
67
|
+
'attach(body, bbox, .5, 0, .5, .5, 0, .5)',
|
|
68
|
+
'attach(lamp, body, .5, 0, .5, .5, 1, .5)',
|
|
69
|
+
'body = Cuboid(1, 2, 1, True)'
|
|
70
|
+
], [('lamp',(.5,0,.5),'body',(.5,1,.5))]))
|
|
71
|
+
|
|
72
|
+
for reverse in [False, True]:
|
|
73
|
+
attach = ['attach(part, bbox, .5, 0, .5, .5, 0, .5)',
|
|
74
|
+
'attach(part, bbox, .5, 0, .5, .5, 1, .5)']
|
|
75
|
+
results.append(run(f'conflicting_same_point_reverse_{reverse}', [
|
|
76
|
+
'bbox = Cuboid(1, 1, 1, True)',
|
|
77
|
+
'part = Cuboid(.2, .2, .2, True)',
|
|
78
|
+
*(reversed(attach) if reverse else attach)
|
|
79
|
+
], [('part',(.5,0,.5),'bbox',(.5,0,.5)),
|
|
80
|
+
('part',(.5,0,.5),'bbox',(.5,1,.5))]))
|
|
81
|
+
|
|
82
|
+
print(json.dumps({'revision': revision, 'python': sys.version,
|
|
83
|
+
'torch': torch.__version__, 'numpy': np.__version__, 'faiss': faiss.__version__,
|
|
84
|
+
'units': 'native abstract units, not Figure mm', 'results': results}, indent=2))
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as dryroom from './v3-full-dryroom.mjs'
|
|
2
|
+
import * as conveyor from './v3-full-conveyor.mjs'
|
|
3
|
+
import * as robot from './v3-full-robot.mjs'
|
|
4
|
+
import * as forklift from './v3-full-forklift.mjs'
|
|
5
|
+
import {robotBinding,forkliftBindings} from './v3-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-v3-asset-experiment-1',name:sample.seed.type,document:{version:'figure-v3-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
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
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 './v3-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!=='v3-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:'v3-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
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
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 './v3-asset-store.mjs'
|
|
9
|
+
import {sampleAsset} from './v3-asset-samples.mjs'
|
|
10
|
+
import {serializeAsset,parseAsset} from './v3-asset.mjs'
|
|
11
|
+
import {createEditorWorkspace} from './v3-editor-workspace.mjs'
|
|
12
|
+
async function fixture(t){const dir=await mkdtemp(join(tmpdir(),'v3-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('./v3-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
|
+
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {compileV3Asset as compileAsset,parseV3Asset as parseAsset,serializeV3Asset as serializeAsset,captureV3AuthoredInputs as captureAuthoredInputs} from '../../src/v3-asset.ts'
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"title": "Experimental V3 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-v3-asset-experiment-1"},
|
|
8
|
+
"name": {"type": "string", "minLength": 1},
|
|
9
|
+
"document": {"$ref": "v3-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
|
+
"legacyCapability": {
|
|
22
|
+
"type": "object", "additionalProperties": false, "required": ["roles", "accepts", "capacity"],
|
|
23
|
+
"properties": {
|
|
24
|
+
"roles": {"type": "array", "uniqueItems": true, "items": {"type": "string", "minLength": 1}},
|
|
25
|
+
"accepts": {"type": "array", "uniqueItems": true, "items": {"type": "string", "minLength": 1}},
|
|
26
|
+
"capacity": {"type": "integer", "minimum": 1, "maximum": 9007199254740991}
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"material": {
|
|
30
|
+
"type": "object", "additionalProperties": false, "required": ["token"],
|
|
31
|
+
"properties": {
|
|
32
|
+
"token": {"type": "string", "minLength": 1},
|
|
33
|
+
"preset": {"enum": ["default", "metal", "glass", "plastic", "wood", "ceramic", "rubber"]},
|
|
34
|
+
"transparent": {"type": "boolean"},
|
|
35
|
+
"emissive": {
|
|
36
|
+
"type": "object", "additionalProperties": false, "required": ["token", "intensity", "on"],
|
|
37
|
+
"properties": {"token": {"type": "string", "minLength": 1}, "intensity": {"type": "number", "minimum": 0, "maximum": 5}, "on": {"type": "boolean"}}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import {test} from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import {compileAsset,serializeAsset,parseAsset,captureAuthoredInputs} from './v3-asset.mjs'
|
|
4
|
+
import {sampleAsset} from './v3-asset-samples.mjs'
|
|
5
|
+
import {createAuthoringSession} from './v3-authoring-session.mjs'
|
|
6
|
+
import * as dryroom from './v3-full-dryroom.mjs'
|
|
7
|
+
import * as conveyor from './v3-full-conveyor.mjs'
|
|
8
|
+
import * as robot from './v3-full-robot.mjs'
|
|
9
|
+
import * as forklift from './v3-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('./v3-document.schema.json',import.meta.url))),'v3-document.schema.json')
|
|
59
|
+
const validate=ajv.compile(JSON.parse(await fs.readFile(new URL('./v3-asset.schema.json',import.meta.url))))
|
|
60
|
+
const directory=await fs.mkdtemp(path.join(os.tmpdir(),'figure-v3-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
|
+
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {createV3AuthoringSession as createAuthoringSession} from '../../src/v3-authoring-session.ts'
|