Design file format 2 with a migration chain (doc::format: older formats upgraded step by step, newer refused with a message, out-of-range numbers and broken occurrence placements refused); a corpus of designs saved by the M0, components-era, joints-era and current builds that must open and evaluate as saved (two pre-versioning behaviour changes listed with their reasons); damaged and hostile variants of every corpus file never panic

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
This commit is contained in:
bherbruckandClaude Opus 5.5 committed 2026-10-08 11:37:33 +00:00
1 parent f50d743c55
commit f413a86d3d
27 files changed
+3109 -17

No files matched your search

@@ -0,0 +1,20 @@
# Design file corpus
Designs saved by older SolveCraft builds. `tests/design_corpus.rs` opens each one with the
current build and checks that it evaluates as it did when saved (the `.expected.json` next to
it is `solvecraft-cli eval` of that file by the build that saved it: bodies, volumes, areas,
world bounding boxes), that it is upgraded to the current file format, and that damaged and
hostile variants of it never panic.
| File | Saved by | Era |
|---|---|---|
| `m0-box-fillet-hole` | 3714342 | M0: sketch, parameters, extrude, fillet, cut |
| `m0-revolve-primitives` | 3714342 | M0: revolve, primitives, combine, chamfer, timeline edit |
| `components-arm` | 97d03e3 | components and occurrences: move, copy, Paste New, ground |
| `joints-hinge` | c2309eb | joints: revolute hinge driven by a parameter |
| `modern-sheet-metal` | bd98035 | sheet metal contour flange (oracle 72) |
| `modern-path-pattern` | bd98035 | pattern on path (oracle 53) |
| `modern-plastic` | bd98035 | shell, lip, boss, rest, snap fit, plastic rule |
The scripts that made them are in `scripts/`. Never regenerate a file with a newer build: the
point is that it was written by the old one. Add new files for new eras instead.
@@ -0,0 +1,134 @@
{
"bodies": [
{
"area_mm2": 700.5299915783324,
"bbox": {
"max": [
10.0,
10.0,
10.000000000000007
],
"min": [
0.0,
0.0,
-6.080836915950756e-16
]
},
"center_of_mass": [
4.999999998722992,
4.999999999366269,
4.999999996470206
],
"density_g_cm3": 1.29,
"edges": 14,
"face_types": {
"cylinder": 1,
"plane": 6
},
"faces": 7,
"kernel": {
"edges": 18,
"faces": 8,
"shells": 1,
"vertices": 12
},
"mass_g": 1.1279207478827844,
"material": "Default",
"name": "Base",
"vertices": 10,
"volume_mm3": 874.3571689013833
}
],
"body_count": 1,
"params": [],
"sketches": [
{
"curves": 1,
"dof": 3,
"id": 11,
"name": "S1",
"profiles": 1,
"status": "solved"
}
],
"timeline": [
{
"error": null,
"id": 1,
"ms": 13.568090999999999,
"name": "Box1",
"rolled_back": false,
"suppressed": false,
"type": "BoxFeature",
"warning": null
},
{
"error": null,
"id": 4,
"ms": 1.004123,
"name": "Box2",
"rolled_back": false,
"suppressed": false,
"type": "BoxFeature",
"warning": null
},
{
"error": null,
"id": 5,
"ms": 6.818065,
"name": "Fillet1",
"rolled_back": false,
"suppressed": false,
"type": "FilletFeature",
"warning": null
},
{
"error": null,
"id": 9,
"ms": 0.223829,
"name": "Box2 (copy)",
"rolled_back": false,
"suppressed": false,
"type": "BoxFeature",
"warning": null
},
{
"error": null,
"id": 10,
"ms": 6.139636,
"name": "Fillet1 (copy)",
"rolled_back": false,
"suppressed": false,
"type": "FilletFeature",
"warning": null
},
{
"error": null,
"id": 11,
"ms": 0.045728,
"name": "S1",
"rolled_back": false,
"suppressed": false,
"type": "Sketch",
"warning": null
},
{
"error": null,
"id": 12,
"ms": 1537.323948,
"name": "Extrude1",
"rolled_back": false,
"suppressed": false,
"type": "ExtrudeFeature",
"warning": null
}
],
"total": {
"area_mm2": 700.5299915783324,
"edges": 14,
"faces": 7,
"mass_g": 1.1279207478827844,
"vertices": 10,
"volume_mm3": 874.3571689013833
}
}
@@ -0,0 +1,263 @@
{
"format": "solvecraft/1",
"name": "Untitled",
"units": "mm",
"params": [],
"features": [
{
"id": 1,
"name": "Box1",
"suppressed": false,
"body_names": [
"Base"
],
"type": "box",
"corner": [
0.0,
0.0,
0.0
],
"length": "10",
"width": "10",
"height": "10",
"operation": "new_body"
},
{
"id": 4,
"name": "Box2",
"suppressed": false,
"body_names": [
"ArmBody"
],
"component": 2,
"type": "box",
"corner": [
20.0,
0.0,
0.0
],
"length": "10",
"width": "4",
"height": "4",
"operation": "new_body"
},
{
"id": 5,
"name": "Fillet1",
"suppressed": false,
"component": 2,
"type": "fillet",
"edges": [
[
25.0,
4.0,
4.0
]
],
"radius": "1"
},
{
"id": 9,
"name": "Box2 (copy)",
"suppressed": false,
"component": 7,
"type": "box",
"corner": [
20.0,
0.0,
0.0
],
"length": "10",
"width": "4",
"height": "4",
"operation": "new_body"
},
{
"id": 10,
"name": "Fillet1 (copy)",
"suppressed": false,
"component": 7,
"type": "fillet",
"edges": [
[
25.0,
4.0,
4.0
]
],
"radius": "1"
},
{
"id": 11,
"name": "S1",
"suppressed": false,
"type": "sketch",
"plane": {
"type": "origin",
"name": "XY"
},
"sketch": {
"points": [
{
"id": "origin",
"pos": [
0.0,
0.0
],
"fixed": true
},
{
"id": "c1.center",
"pos": [
5.0,
5.0
],
"fixed": false
}
],
"curves": [
{
"id": "c1",
"type": "circle",
"c": 1,
"r": 2.0,
"construction": false
}
],
"constraints": [],
"counters": {
"c": 1
}
}
},
{
"id": 12,
"name": "Extrude1",
"suppressed": false,
"type": "extrude",
"sketch": 11,
"profiles": {
"type": "all"
},
"extent": {
"distance": "10",
"direction": "positive"
},
"operation": "cut"
}
],
"next_id": 13,
"components": [
{
"id": 2,
"name": "Arm",
"parent": 0
},
{
"id": 7,
"name": "Arm (copy)",
"parent": 0
}
],
"occurrences": [
{
"id": 3,
"component": 2,
"parent": 0,
"name": "Arm:1",
"transform": [
[
1.0,
0.0,
0.0,
0.0
],
[
0.0,
1.0,
0.0,
0.0
],
[
0.0,
0.0,
1.0,
0.0
],
[
0.0,
50.0,
0.0,
1.0
]
],
"grounded": true
},
{
"id": 6,
"component": 2,
"parent": 0,
"name": "Arm:2",
"transform": [
[
1.0,
0.0,
0.0,
0.0
],
[
0.0,
1.0,
0.0,
0.0
],
[
0.0,
0.0,
1.0,
0.0
],
[
0.0,
70.0,
0.0,
1.0
]
],
"grounded": false
},
{
"id": 8,
"component": 7,
"parent": 0,
"name": "Arm (copy):1",
"transform": [
[
1.0,
0.0,
0.0,
0.0
],
[
0.0,
1.0,
0.0,
0.0
],
[
0.0,
0.0,
1.0,
0.0
],
[
0.0,
20.0,
0.0,
1.0
]
],
"grounded": false
}
]
}
@@ -0,0 +1,217 @@
{
"bodies": [
{
"area_mm2": 2820.0,
"bbox": {
"max": [
40.0,
30.0,
3.0
],
"min": [
0.0,
0.0,
0.0
]
},
"center_of_mass": [
20.0,
15.0,
1.5
],
"density_g_cm3": 1.29,
"edges": 12,
"face_types": {
"plane": 6
},
"faces": 6,
"kernel": {
"edges": 12,
"faces": 6,
"shells": 1,
"vertices": 8
},
"mass_g": 4.644,
"material": "Default",
"name": "Body1",
"vertices": 8,
"volume_mm3": 3600.0
},
{
"area_mm2": 1105.7821673177034,
"bbox": {
"max": [
40.00000000000002,
37.0,
8.000000000000002
],
"min": [
0.0,
29.0,
-6.661338147750943e-16
]
},
"center_of_mass": [
20.00000000000423,
32.99999999999611,
3.9999999999995404
],
"density_g_cm3": 1.29,
"edges": 2,
"face_types": {
"cylinder": 1,
"plane": 2
},
"faces": 3,
"kernel": {
"edges": 6,
"faces": 4,
"shells": 1,
"vertices": 4
},
"mass_g": 2.593268071855663,
"material": "Default",
"name": "Body2",
"vertices": 2,
"volume_mm3": 2010.2853270198937
},
{
"area_mm2": 2820.0,
"bbox": {
"max": [
40.0,
32.00000000000008,
37.000000000000064
],
"min": [
0.0,
29.000000000000075,
7.000000000000064
]
},
"center_of_mass": [
20.0,
30.500000000000075,
22.00000000000007
],
"density_g_cm3": 1.29,
"edges": 12,
"face_types": {
"plane": 6
},
"faces": 6,
"kernel": {
"edges": 12,
"faces": 6,
"shells": 1,
"vertices": 8
},
"mass_g": 4.643999999999996,
"material": "Default",
"name": "Body3",
"vertices": 8,
"volume_mm3": 3599.9999999999964
},
{
"area_mm2": 1105.782167317711,
"bbox": {
"max": [
40.00000000000002,
37.000000000000085,
8.000000000000064
],
"min": [
0.0,
29.000000000000075,
6.394884621840902e-14
]
},
"center_of_mass": [
20.000000000004935,
32.99999999999709,
3.999999999999749
],
"density_g_cm3": 1.29,
"edges": 2,
"face_types": {
"cylinder": 1,
"plane": 2
},
"faces": 3,
"kernel": {
"edges": 6,
"faces": 4,
"shells": 1,
"vertices": 4
},
"mass_g": 2.59326807185558,
"material": "Default",
"name": "Body4",
"vertices": 2,
"volume_mm3": 2010.2853270198293
}
],
"body_count": 4,
"params": [
{
"comment": "",
"error": null,
"expression": "90 deg",
"model": false,
"name": "open_angle",
"unit": "deg",
"value": 1.5707963267948966
}
],
"sketches": [],
"timeline": [
{
"error": null,
"id": 3,
"ms": 1.1335799999999998,
"name": "Box1",
"rolled_back": false,
"suppressed": false,
"type": "BoxFeature",
"warning": null
},
{
"error": null,
"id": 4,
"ms": 0.580462,
"name": "Cylinder1",
"rolled_back": false,
"suppressed": false,
"type": "CylinderFeature",
"warning": null
},
{
"error": null,
"id": 7,
"ms": 0.257673,
"name": "Box2",
"rolled_back": false,
"suppressed": false,
"type": "BoxFeature",
"warning": null
},
{
"error": null,
"id": 8,
"ms": 0.5234019999999999,
"name": "Cylinder2",
"rolled_back": false,
"suppressed": false,
"type": "CylinderFeature",
"warning": null
}
],
"total": {
"area_mm2": 7851.564334635414,
"edges": 28,
"faces": 18,
"mass_g": 14.474536143711237,
"vertices": 20,
"volume_mm3": 11220.570654039719
}
}
@@ -0,0 +1,225 @@
{
"format": "solvecraft/1",
"name": "Untitled",
"units": "mm",
"params": [
{
"name": "open_angle",
"expr": "90 deg",
"unit": "deg",
"model": false
}
],
"features": [
{
"id": 3,
"name": "Box1",
"suppressed": false,
"param_names": [
"d1",
"d2",
"d3"
],
"component": 1,
"type": "box",
"corner": [
0.0,
0.0,
0.0
],
"length": "40",
"width": "30",
"height": "3",
"operation": "new_body"
},
{
"id": 4,
"name": "Cylinder1",
"suppressed": false,
"param_names": [
"d4",
"d5"
],
"component": 1,
"type": "cylinder",
"base": [
0.0,
33.0,
4.0
],
"axis": [
1.0,
0.0,
0.0
],
"radius": "4",
"height": "40",
"operation": "new_body"
},
{
"id": 7,
"name": "Box2",
"suppressed": false,
"param_names": [
"d6",
"d7",
"d8"
],
"component": 5,
"type": "box",
"corner": [
0.0,
0.0,
0.0
],
"length": "40",
"width": "30",
"height": "3",
"operation": "new_body"
},
{
"id": 8,
"name": "Cylinder2",
"suppressed": false,
"param_names": [
"d9",
"d10"
],
"component": 5,
"type": "cylinder",
"base": [
0.0,
33.0,
4.0
],
"axis": [
1.0,
0.0,
0.0
],
"radius": "4",
"height": "40",
"operation": "new_body"
}
],
"next_id": 9,
"components": [
{
"id": 1,
"name": "LeafA",
"parent": 0
},
{
"id": 5,
"name": "LeafB",
"parent": 0
}
],
"occurrences": [
{
"id": 2,
"component": 1,
"parent": 0,
"name": "LeafA:1",
"transform": [
[
1.0,
0.0,
0.0,
0.0
],
[
0.0,
1.0,
0.0,
0.0
],
[
0.0,
0.0,
1.0,
0.0
],
[
0.0,
0.0,
0.0,
1.0
]
],
"grounded": true
},
{
"id": 6,
"component": 5,
"parent": 0,
"name": "LeafB:1",
"transform": [
[
1.0,
0.0,
0.0,
0.0
],
[
0.0,
6.123233995736766e-17,
-1.0,
0.0
],
[
0.0,
1.0,
6.123233995736766e-17,
0.0
],
[
0.0,
29.000000000000075,
37.000000000000064,
1.0
]
],
"grounded": false
}
],
"assembly": {
"joints": [
{
"id": 1,
"name": "Hinge",
"kind": "revolute",
"a": {
"occurrence": 2,
"snap": {
"type": "circle_center",
"pick": [
1.0,
37.0,
4.0
]
}
},
"b": {
"occurrence": 6,
"snap": {
"type": "circle_center",
"pick": [
1.0,
37.0,
4.0
]
}
},
"values": [
1.5707963267948966
],
"limits": [
null
],
"offset": 0.0,
"angle": 0.0
}
]
}
}
@@ -0,0 +1,128 @@
{
"bodies": [
{
"area_mm2": 5641.5699696719,
"bbox": {
"max": [
40.0,
30.0,
20.00000000000001
],
"min": [
0.0,
0.0,
-2.331468351865795e-15
]
},
"center_of_mass": [
20.0333953680467,
15.02475683626249,
9.999999936169297
],
"edges": 17,
"face_types": {
"cylinder": 2,
"plane": 6
},
"faces": 8,
"kernel": {
"edges": 21,
"faces": 9,
"shells": 1,
"vertices": 14
},
"name": "Body1",
"vertices": 12,
"volume_mm3": 22391.21832759239
}
],
"body_count": 1,
"params": [
{
"comment": "",
"error": null,
"expression": "40 mm",
"model": false,
"name": "width",
"unit": "mm",
"value": 40.0
}
],
"sketches": [
{
"curves": 4,
"dof": 4,
"id": 1,
"name": "Sketch1",
"profiles": 1,
"status": "solved"
},
{
"curves": 1,
"dof": 3,
"id": 4,
"name": "Sketch2",
"profiles": 1,
"status": "solved"
}
],
"timeline": [
{
"error": null,
"id": 1,
"ms": 0.043782999999999996,
"name": "Sketch1",
"rolled_back": false,
"suppressed": false,
"type": "Sketch",
"warning": null
},
{
"error": null,
"id": 2,
"ms": 1.609402,
"name": "Extrude1",
"rolled_back": false,
"suppressed": false,
"type": "ExtrudeFeature",
"warning": null
},
{
"error": null,
"id": 3,
"ms": 11.196359,
"name": "Fillet1",
"rolled_back": false,
"suppressed": false,
"type": "FilletFeature",
"warning": null
},
{
"error": null,
"id": 4,
"ms": 0.031249,
"name": "Sketch2",
"rolled_back": false,
"suppressed": false,
"type": "Sketch",
"warning": null
},
{
"error": null,
"id": 5,
"ms": 1378.231746,
"name": "Extrude2",
"rolled_back": false,
"suppressed": false,
"type": "ExtrudeFeature",
"warning": null
}
],
"total": {
"area_mm2": 5641.5699696719,
"edges": 17,
"faces": 8,
"vertices": 12,
"volume_mm3": 22391.21832759239
}
}
@@ -0,0 +1,208 @@
{
"format": "solvecraft/1",
"name": "Untitled",
"units": "mm",
"params": [
{
"name": "width",
"expr": "40 mm",
"unit": "mm",
"model": false
}
],
"features": [
{
"id": 1,
"name": "Sketch1",
"suppressed": false,
"type": "sketch",
"plane": {
"type": "origin",
"name": "XY"
},
"sketch": {
"points": [
{
"id": "origin",
"pos": [
0.0,
0.0
],
"fixed": true
},
{
"id": "p1",
"pos": [
0.0,
0.0
],
"fixed": false
},
{
"id": "p2",
"pos": [
40.0,
0.0
],
"fixed": false
},
{
"id": "p3",
"pos": [
40.0,
30.0
],
"fixed": false
},
{
"id": "p4",
"pos": [
0.0,
30.0
],
"fixed": false
}
],
"curves": [
{
"id": "l5",
"type": "line",
"a": 1,
"b": 2,
"construction": false
},
{
"id": "l6",
"type": "line",
"a": 2,
"b": 3,
"construction": false
},
{
"id": "l7",
"type": "line",
"a": 3,
"b": 4,
"construction": false
},
{
"id": "l8",
"type": "line",
"a": 4,
"b": 1,
"construction": false
}
],
"constraints": [
{
"id": "k9",
"type": "horizontal",
"l": 0
},
{
"id": "k10",
"type": "horizontal",
"l": 2
},
{
"id": "k11",
"type": "vertical",
"l": 1
},
{
"id": "k12",
"type": "vertical",
"l": 3
}
],
"next_id": 13
}
},
{
"id": 2,
"name": "Extrude1",
"suppressed": false,
"type": "extrude",
"sketch": 1,
"profiles": {
"type": "all"
},
"extent": {
"distance": "width / 2",
"direction": "positive"
},
"operation": "new_body"
},
{
"id": 3,
"name": "Fillet1",
"suppressed": false,
"type": "fillet",
"edges": [
[
0.0,
0.0,
10.0
]
],
"radius": "3"
},
{
"id": 4,
"name": "Sketch2",
"suppressed": false,
"type": "sketch",
"plane": {
"type": "origin",
"name": "XY"
},
"sketch": {
"points": [
{
"id": "origin",
"pos": [
0.0,
0.0
],
"fixed": true
},
{
"id": "c1.center",
"pos": [
20.0,
15.0
],
"fixed": false
}
],
"curves": [
{
"id": "c1",
"type": "circle",
"c": 1,
"r": 5.0,
"construction": false
}
],
"constraints": [],
"next_id": 2
}
},
{
"id": 5,
"name": "Extrude2",
"suppressed": false,
"type": "extrude",
"sketch": 4,
"profiles": {
"type": "all"
},
"extent": {
"distance": "20",
"direction": "positive"
},
"operation": "cut"
}
],
"next_id": 6
}
@@ -0,0 +1,155 @@
{
"bodies": [
{
"area_mm2": 835.4130709318358,
"bbox": {
"max": [
15.0,
15.0,
5.0
],
"min": [
-15.0,
0.0,
0.0
]
},
"center_of_mass": [
-5.1971813422033816e-8,
8.063488509334698,
2.500000003353729
],
"edges": 12,
"face_types": {
"cylinder": 2,
"plane": 4
},
"faces": 6,
"kernel": {
"edges": 28,
"faces": 14,
"shells": 1,
"vertices": 16
},
"name": "Body1",
"vertices": 8,
"volume_mm3": 981.8090960455798
},
{
"area_mm2": 693.6702600465544,
"bbox": {
"max": [
40.0,
10.0,
10.000000000000007
],
"min": [
30.0,
0.0,
-2.3775115272352145e-15
]
},
"center_of_mass": [
35.02683928802334,
4.999999999290639,
4.973160725428021
],
"edges": 17,
"face_types": {
"cylinder": 1,
"plane": 7
},
"faces": 8,
"kernel": {
"edges": 21,
"faces": 9,
"shells": 1,
"vertices": 14
},
"name": "Body2",
"vertices": 12,
"volume_mm3": 869.3724925404262
}
],
"body_count": 2,
"params": [],
"sketches": [
{
"curves": 4,
"dof": 8,
"id": 1,
"name": "Sketch1",
"profiles": 1,
"status": "solved"
}
],
"timeline": [
{
"error": null,
"id": 1,
"ms": 0.034476,
"name": "Sketch1",
"rolled_back": false,
"suppressed": false,
"type": "Sketch",
"warning": null
},
{
"error": null,
"id": 2,
"ms": 7.732595999999999,
"name": "Revolve1",
"rolled_back": false,
"suppressed": false,
"type": "RevolveFeature",
"warning": null
},
{
"error": null,
"id": 3,
"ms": 2.5985959999999997,
"name": "Box1",
"rolled_back": false,
"suppressed": false,
"type": "BoxFeature",
"warning": null
},
{
"error": null,
"id": 4,
"ms": 1.6926910000000002,
"name": "Cylinder1",
"rolled_back": false,
"suppressed": false,
"type": "CylinderFeature",
"warning": null
},
{
"error": null,
"id": 5,
"ms": 384.534019,
"name": "Combine1",
"rolled_back": false,
"suppressed": false,
"type": "CombineFeature",
"warning": null
},
{
"error": null,
"id": 6,
"ms": 150.763588,
"name": "Chamfer1",
"rolled_back": false,
"suppressed": false,
"type": "ChamferFeature",
"warning": null
}
],
"total": {
"area_mm2": 1529.0833309783902,
"edges": 29,
"faces": 14,
"vertices": 20,
"volume_mm3": 1851.181588586006
}
}
@@ -0,0 +1,171 @@
{
"format": "solvecraft/1",
"name": "Untitled",
"units": "mm",
"params": [],
"features": [
{
"id": 1,
"name": "Sketch1",
"suppressed": false,
"type": "sketch",
"plane": {
"type": "origin",
"name": "XZ"
},
"sketch": {
"points": [
{
"id": "origin",
"pos": [
0.0,
0.0
],
"fixed": true
},
{
"id": "l1.start",
"pos": [
10.0,
0.0
],
"fixed": false
},
{
"id": "l1.end",
"pos": [
15.0,
0.0
],
"fixed": false
},
{
"id": "l2.end",
"pos": [
15.0,
5.0
],
"fixed": false
},
{
"id": "l3.end",
"pos": [
10.0,
5.0
],
"fixed": false
}
],
"curves": [
{
"id": "l1",
"type": "line",
"a": 1,
"b": 2,
"construction": false
},
{
"id": "l2",
"type": "line",
"a": 2,
"b": 3,
"construction": false
},
{
"id": "l3",
"type": "line",
"a": 3,
"b": 4,
"construction": false
},
{
"id": "l4",
"type": "line",
"a": 4,
"b": 1,
"construction": false
}
],
"constraints": [],
"next_id": 5
}
},
{
"id": 2,
"name": "Revolve1",
"suppressed": false,
"type": "revolve",
"sketch": 1,
"profiles": {
"type": "all"
},
"axis": {
"type": "sketch_axis",
"axis": "y"
},
"angle": "180 deg",
"operation": "new_body"
},
{
"id": 3,
"name": "Box1",
"suppressed": false,
"type": "box",
"corner": [
30.0,
0.0,
0.0
],
"length": "10",
"width": "10",
"height": "10",
"operation": "new_body"
},
{
"id": 4,
"name": "Cylinder1",
"suppressed": false,
"type": "cylinder",
"base": [
35.0,
5.0,
-5.0
],
"axis": [
0.0,
0.0,
1.0
],
"radius": "(4) / 2",
"height": "20",
"operation": "new_body"
},
{
"id": 5,
"name": "Combine1",
"suppressed": false,
"type": "combine",
"target": "Body2",
"tools": [
"Body3"
],
"operation": "cut",
"keep_tools": false
},
{
"id": 6,
"name": "Chamfer1",
"suppressed": false,
"type": "chamfer",
"edges": [
[
30.0,
5.0,
10.0
]
],
"distance": "1"
}
],
"next_id": 7
}
@@ -0,0 +1,122 @@
{
"bodies": [
{
"area_mm2": 16463.932136252846,
"bbox": {
"max": [
120.0,
60.0,
5.0000000000000036
],
"min": [
0.0,
0.0,
0.0
]
},
"center_of_mass": [
60.000000010005735,
29.965234604788655,
2.4999999999999867
],
"density_g_cm3": 1.29,
"edges": 26,
"face_types": {
"cylinder": 7,
"plane": 6
},
"faces": 13,
"kernel": {
"edges": 54,
"faces": 20,
"shells": 1,
"vertices": 36
},
"mass_g": 45.163625870882676,
"material": "Default",
"name": "Plate",
"vertices": 22,
"volume_mm3": 35010.56269060673
}
],
"body_count": 1,
"params": [],
"sketches": [
{
"curves": 4,
"dof": 8,
"id": 1,
"name": "Plate_sketch",
"profiles": 1,
"status": "solved"
},
{
"curves": 2,
"dof": 6,
"id": 3,
"name": "PathSketch",
"profiles": 1,
"status": "solved"
}
],
"timeline": [
{
"error": null,
"id": 1,
"ms": 0.093199,
"name": "Plate_sketch",
"rolled_back": false,
"suppressed": false,
"type": "Sketch",
"warning": null
},
{
"error": null,
"id": 2,
"ms": 9.166392,
"name": "Plate",
"rolled_back": false,
"suppressed": false,
"type": "ExtrudeFeature",
"warning": null
},
{
"error": null,
"id": 3,
"ms": 0.057569999999999996,
"name": "PathSketch",
"rolled_back": false,
"suppressed": false,
"type": "Sketch",
"warning": null
},
{
"error": null,
"id": 4,
"ms": 84.237259,
"name": "Hole1",
"rolled_back": false,
"suppressed": false,
"type": "ExtrudeFeature",
"warning": null
},
{
"error": null,
"id": 5,
"ms": 1093.817516,
"name": "PathPattern1",
"rolled_back": false,
"suppressed": false,
"type": "PathPatternFeature",
"warning": null
}
],
"total": {
"area_mm2": 16463.932136252846,
"edges": 26,
"faces": 13,
"mass_g": 45.163625870882676,
"vertices": 22,
"volume_mm3": 35010.56269060673
}
}
@@ -0,0 +1,238 @@
{
"format": "solvecraft/1",
"name": "Untitled",
"units": "mm",
"params": [],
"features": [
{
"id": 1,
"name": "Plate_sketch",
"suppressed": false,
"type": "sketch",
"plane": {
"type": "origin",
"name": "XY"
},
"sketch": {
"points": [
{
"id": "origin",
"pos": [
0.0,
0.0
],
"fixed": true
},
{
"id": "l1.start",
"pos": [
0.0,
0.0
],
"fixed": false
},
{
"id": "l1.end",
"pos": [
120.0,
0.0
],
"fixed": false
},
{
"id": "l2.end",
"pos": [
120.0,
60.0
],
"fixed": false
},
{
"id": "l3.end",
"pos": [
0.0,
60.0
],
"fixed": false
}
],
"curves": [
{
"id": "l1",
"type": "line",
"a": 1,
"b": 2,
"construction": false
},
{
"id": "l2",
"type": "line",
"a": 2,
"b": 3,
"construction": false
},
{
"id": "l3",
"type": "line",
"a": 3,
"b": 4,
"construction": false
},
{
"id": "l4",
"type": "line",
"a": 4,
"b": 1,
"construction": false
}
],
"constraints": [],
"counters": {}
}
},
{
"id": 2,
"name": "Plate",
"suppressed": false,
"body_names": [
"Plate"
],
"param_names": [
"d1"
],
"type": "extrude",
"sketch": 1,
"profiles": {
"type": "curves",
"loops": [
[
"l1",
"l2",
"l3",
"l4"
]
]
},
"extent": {
"distance": "5",
"direction": "positive"
},
"operation": "new_body"
},
{
"id": 3,
"name": "PathSketch",
"suppressed": false,
"type": "sketch",
"plane": {
"type": "origin",
"name": "XY"
},
"sketch": {
"points": [
{
"id": "origin",
"pos": [
0.0,
0.0
],
"fixed": true
},
{
"id": "a1.center",
"pos": [
60.0,
3.3333333333333335
],
"fixed": false
},
{
"id": "a1.start",
"pos": [
100.0,
15.0
],
"fixed": false
},
{
"id": "a1.end",
"pos": [
20.0,
15.000000000000002
],
"fixed": false
}
],
"curves": [
{
"id": "a1",
"type": "arc",
"c": 1,
"a": 2,
"b": 3,
"construction": false
},
{
"id": "c1",
"type": "circle",
"c": 2,
"r": 3.0,
"construction": false
}
],
"constraints": [],
"counters": {}
}
},
{
"id": 4,
"name": "Hole1",
"suppressed": false,
"body_names": [
"Plate"
],
"param_names": [
"d2"
],
"type": "extrude",
"sketch": 3,
"profiles": {
"type": "curves",
"loops": [
[
"c1"
]
]
},
"extent": {
"distance": "0",
"direction": "positive",
"through_all": true
},
"operation": "cut"
},
{
"id": 5,
"name": "PathPattern1",
"suppressed": false,
"param_names": [
"d3",
"d4"
],
"type": "pattern",
"features": [
"Hole1"
],
"pattern": {
"type": "path",
"path_sketch": 3,
"path": [
"a1"
],
"count": "7",
"spacing": "17.875031"
}
}
],
"next_id": 6
}
@@ -0,0 +1,126 @@
{
"bodies": [
{
"area_mm2": 24280.380636378435,
"bbox": {
"max": [
80.0,
50.0,
32.0
],
"min": [
0.0,
0.0,
0.0
]
},
"center_of_mass": [
39.89510389356416,
24.906013036240918,
10.936558895792075
],
"density_g_cm3": 1.29,
"edges": 82,
"face_types": {
"cone": 1,
"cylinder": 1,
"plane": 35
},
"faces": 37,
"kernel": {
"edges": 90,
"faces": 39,
"shells": 1,
"vertices": 60
},
"mass_g": 29.716687268283682,
"material": "Default",
"name": "Body1",
"vertices": 56,
"volume_mm3": 23036.19168084006
}
],
"body_count": 1,
"params": [
{
"comment": "",
"error": null,
"expression": "2 mm",
"model": false,
"name": "wall",
"unit": "mm",
"value": 2.0
}
],
"sketches": [],
"timeline": [
{
"error": null,
"id": 1,
"ms": 13.671479,
"name": "Box1",
"rolled_back": false,
"suppressed": false,
"type": "BoxFeature",
"warning": null
},
{
"error": null,
"id": 2,
"ms": 15.729548999999999,
"name": "Shell1",
"rolled_back": false,
"suppressed": false,
"type": "ShellFeature",
"warning": null
},
{
"error": null,
"id": 3,
"ms": 136.407014,
"name": "Lip1",
"rolled_back": false,
"suppressed": false,
"type": "LipFeature",
"warning": null
},
{
"error": null,
"id": 4,
"ms": 530.869306,
"name": "Boss1",
"rolled_back": false,
"suppressed": false,
"type": "BossFeature",
"warning": null
},
{
"error": null,
"id": 5,
"ms": 176.568115,
"name": "Rest1",
"rolled_back": false,
"suppressed": false,
"type": "RestFeature",
"warning": null
},
{
"error": null,
"id": 6,
"ms": 260.395779,
"name": "SnapFit1",
"rolled_back": false,
"suppressed": false,
"type": "SnapFitFeature",
"warning": null
}
],
"total": {
"area_mm2": 24280.380636378435,
"edges": 82,
"faces": 37,
"mass_g": 29.716687268283682,
"vertices": 56,
"volume_mm3": 23036.19168084006
}
}
@@ -0,0 +1,158 @@
{
"format": "solvecraft/1",
"name": "Untitled",
"units": "mm",
"params": [
{
"name": "wall",
"expr": "2 mm",
"unit": "mm",
"model": false
}
],
"features": [
{
"id": 1,
"name": "Box1",
"suppressed": false,
"param_names": [
"d1",
"d2",
"d3"
],
"type": "box",
"corner": [
0.0,
0.0,
0.0
],
"length": "80",
"width": "50",
"height": "30",
"operation": "new_body"
},
{
"id": 2,
"name": "Shell1",
"suppressed": false,
"param_names": [
"d4"
],
"type": "shell",
"faces": [
[
40.0,
25.0,
30.0
]
],
"thickness": "wall"
},
{
"id": 3,
"name": "Lip1",
"suppressed": false,
"param_names": [
"d5",
"d6"
],
"type": "lip",
"face": [
1.0,
25.0,
30.0
],
"width": "1",
"height": "2"
},
{
"id": 4,
"name": "Boss1",
"suppressed": false,
"param_names": [
"d7",
"d8",
"d9",
"d10"
],
"type": "boss",
"position": [
20.0,
20.0,
2.0
],
"direction": [
0.0,
0.0,
1.0
],
"diameter": "6",
"height": "10",
"hole_diameter": "2.5",
"draft": "1 deg"
},
{
"id": 5,
"name": "Rest1",
"suppressed": false,
"param_names": [
"d11",
"d12",
"d13",
"d14"
],
"type": "rest",
"position": [
60.0,
30.0,
2.0
],
"direction": [
0.0,
0.0,
1.0
],
"width": "8",
"length": "12",
"height": "3",
"thickness": "1"
},
{
"id": 6,
"name": "SnapFit1",
"suppressed": false,
"param_names": [
"d15",
"d16",
"d17",
"d18",
"d19"
],
"type": "snap_fit",
"position": [
40.0,
12.0,
2.0
],
"direction": [
0.0,
0.0,
1.0
],
"hook": [
0.0,
1.0,
0.0
],
"length": "12",
"thickness": "1.5",
"width": "6",
"catch_depth": "1",
"catch_length": "2"
}
],
"next_id": 7,
"plastic": {
"active": "ABS (1.5mm)"
}
}
@@ -0,0 +1,85 @@
{
"bodies": [
{
"area_mm2": 10411.967792188118,
"bbox": {
"max": [
70.0,
50.00000000000002,
32.500000000000014
],
"min": [
0.0,
-4.440892098500626e-14,
-5.329070518200751e-15
]
},
"center_of_mass": [
36.03765771659564,
24.999999999999982,
15.08756759282672
],
"density_g_cm3": 1.29,
"edges": 44,
"face_types": {
"cylinder": 4,
"plane": 10
},
"faces": 22,
"kernel": {
"edges": 44,
"faces": 22,
"shells": 1,
"vertices": 24
},
"mass_g": 15.605746095907175,
"material": "Default",
"name": "Body1",
"sheet_metal": true,
"vertices": 24,
"volume_mm3": 12097.477593726493
}
],
"body_count": 1,
"params": [],
"sketches": [
{
"curves": 3,
"dof": 8,
"id": 1,
"name": "Contour",
"profiles": 0,
"status": "solved"
}
],
"timeline": [
{
"error": null,
"id": 1,
"ms": 0.074984,
"name": "Contour",
"rolled_back": false,
"suppressed": false,
"type": "Sketch",
"warning": null
},
{
"error": null,
"id": 2,
"ms": 37.187197,
"name": "ContourFlange",
"rolled_back": false,
"suppressed": false,
"type": "ContourFlangeFeature",
"warning": null
}
],
"total": {
"area_mm2": 10411.967792188118,
"edges": 44,
"faces": 22,
"mass_g": 15.605746095907175,
"vertices": 24,
"volume_mm3": 12097.477593726493
}
}
@@ -0,0 +1,120 @@
{
"format": "solvecraft/1",
"name": "Untitled",
"units": "mm",
"params": [],
"features": [
{
"id": 1,
"name": "Contour",
"suppressed": false,
"type": "sketch",
"plane": {
"type": "custom",
"plane": {
"origin": [
0.0,
0.0,
0.0
],
"x_dir": [
1.0,
0.0,
0.0
],
"y_dir": [
0.0,
0.0,
-1.0
]
}
},
"sketch": {
"points": [
{
"id": "origin",
"pos": [
0.0,
0.0
],
"fixed": true
},
{
"id": "l1.start",
"pos": [
0.0,
0.0
],
"fixed": false
},
{
"id": "l1.end",
"pos": [
40.0,
0.0
],
"fixed": false
},
{
"id": "l2.end",
"pos": [
40.0,
-30.0
],
"fixed": false
},
{
"id": "l3.end",
"pos": [
70.0,
-30.0
],
"fixed": false
}
],
"curves": [
{
"id": "l1",
"type": "line",
"a": 1,
"b": 2,
"construction": false
},
{
"id": "l2",
"type": "line",
"a": 2,
"b": 3,
"construction": false
},
{
"id": "l3",
"type": "line",
"a": 3,
"b": 4,
"construction": false
}
],
"constraints": [],
"counters": {}
}
},
{
"id": 2,
"name": "ContourFlange",
"suppressed": false,
"param_names": [
"d1"
],
"type": "sheet_contour",
"sketch": 1,
"curves": [
"l1",
"l2",
"l3"
],
"distance": "50"
}
],
"next_id": 3
}
@@ -0,0 +1,15 @@
{"commands": [
{"command": "PrimitiveBox", "params": {"length": 10, "width": 10, "height": 10, "body_name": "Base"}},
{"command": "FusionCreateNewComponentCommand", "params": {"name": "Arm"}},
{"command": "PrimitiveBox", "params": {"corner": [20, 0, 0], "length": 10, "width": 4, "height": 4, "body_name": "ArmBody"}},
{"command": "FusionFilletEdgesCommand", "params": {"edges": [[25, 4, 4]], "radius": 1}},
{"command": "component.activate", "params": {"component": 0}},
{"command": "occurrence.move", "params": {"occurrence": 3, "translate": [0, 50, 0]}},
{"command": "occurrence.copy", "params": {"component": "Arm", "translate": [0, 20, 0]}},
{"command": "component.paste_new", "params": {"component": "Arm", "translate": [0, -30, 0]}},
{"command": "occurrence.ground", "params": {"occurrence": 3}},
{"command": "SketchCreate", "params": {"plane": "XY", "name": "S1"}},
{"command": "CircleCenterRadius", "params": {"center": [5, 5], "radius": 2}},
{"command": "SketchStop", "params": {}},
{"command": "Extrude", "params": {"distance": 10, "operation": "cut"}}
]}
@@ -0,0 +1,14 @@
{"commands": [
{"command": "ChangeParameterCommand", "params": {"name": "open_angle", "expression": "90 deg"}},
{"command": "FusionCreateNewComponentCommand", "params": {"name": "LeafA"}},
{"command": "PrimitiveBox", "params": {"length": 40, "width": 30, "height": 3, "corner": [0, 0, 0]}},
{"command": "PrimitiveCylinder", "params": {"base": [0, 33, 4], "axis": [1, 0, 0], "radius": 4, "height": 40, "operation": "new"}},
{"command": "component.activate", "params": {"component": "root"}},
{"command": "FusionCreateNewComponentCommand", "params": {"name": "LeafB"}},
{"command": "PrimitiveBox", "params": {"length": 40, "width": 30, "height": 3, "corner": [0, 0, 0]}},
{"command": "PrimitiveCylinder", "params": {"base": [0, 33, 4], "axis": [1, 0, 0], "radius": 4, "height": 40, "operation": "new"}},
{"command": "component.activate", "params": {"component": "root"}},
{"command": "occurrence.ground", "params": {"occurrence": 2, "grounded": true}},
{"command": "JointAssembleCmdNew", "params": {"type": "revolute", "a": {"occurrence": "LeafA", "circle": [1, 37, 4]}, "b": {"occurrence": "LeafB", "circle": [1, 37, 4]}, "name": "Hinge"}},
{"command": "FusionMoveJointsCommand", "params": {"joint": "Hinge", "value": "open_angle"}}
]}
@@ -0,0 +1,12 @@
{"commands": [
{"command": "ChangeParameterCommand", "params": {"name": "width", "expression": "40 mm"}},
{"command": "SketchCreate", "params": {"plane": "XY"}},
{"command": "ShapeRectangleTwoPoint", "params": {"p0": [0, 0], "p1": [40, 30]}},
{"command": "SketchStop", "params": {}},
{"command": "Extrude", "params": {"distance": "width / 2"}},
{"command": "FusionFilletEdgesCommand", "params": {"edges": [[0, 0, 10]], "radius": 3}},
{"command": "SketchCreate", "params": {"plane": "XY"}},
{"command": "CircleCenterRadius", "params": {"center": [20, 15], "radius": 5}},
{"command": "SketchStop", "params": {}},
{"command": "Extrude", "params": {"distance": 20, "operation": "cut"}}
]}
@@ -0,0 +1,11 @@
{"commands": [
{"command": "SketchCreate", "params": {"plane": "XZ"}},
{"command": "DrawPolyline", "params": {"points": [[10, 0], [15, 0], [15, 5], [10, 5]], "closed": true}},
{"command": "SketchStop", "params": {}},
{"command": "Revolve", "params": {"axis": "y"}},
{"command": "PrimitiveBox", "params": {"length": 10, "width": 10, "height": 10, "corner": [30, 0, 0]}},
{"command": "PrimitiveCylinder", "params": {"diameter": 4, "height": 20, "base": [35, 5, -5]}},
{"command": "FusionCombineCommand", "params": {"target": "Body2", "tools": ["Body3"], "operation": "cut"}},
{"command": "FusionChamferCommand", "params": {"edges": [[30, 5, 10]], "distance": 1}},
{"command": "timeline.edit", "params": {"feature": "Revolve1", "set": {"angle": "180 deg"}}}
]}
@@ -0,0 +1,197 @@
{
"commands": [
{
"command": "SketchCreate",
"params": {
"name": "Plate_sketch",
"plane": "XY"
}
},
{
"command": "DrawPolyline",
"params": {
"construction": false,
"ids": [
"l1"
],
"points": [
[
0,
0
],
[
120,
0
]
]
}
},
{
"command": "DrawPolyline",
"params": {
"construction": false,
"ids": [
"l2"
],
"points": [
"l1.end",
[
120,
60
]
]
}
},
{
"command": "DrawPolyline",
"params": {
"construction": false,
"ids": [
"l3"
],
"points": [
"l2.end",
[
0,
60
]
]
}
},
{
"command": "DrawPolyline",
"params": {
"construction": false,
"ids": [
"l4"
],
"points": [
"l3.end",
"l1.start"
]
}
},
{
"command": "SketchStop",
"params": {}
},
{
"command": "Extrude",
"params": {
"body_names": [
"Plate"
],
"direction": "positive",
"distance": 5,
"name": "Plate",
"operation": "new",
"profiles": [
{
"area_mm2": 7200.0,
"centroid_sketch": [
60.0,
30.0
],
"loops": [
{
"curves": [
"l1",
"l2",
"l3",
"l4"
],
"outer": true
}
],
"sketch": "Plate_sketch"
}
],
"sketch": "Plate_sketch"
}
},
{
"command": "SketchCreate",
"params": {
"name": "PathSketch",
"plane": "XY"
}
},
{
"command": "ArcThreePoint",
"params": {
"end": [
20,
15
],
"id": "a1",
"start": [
100,
15
],
"through": [
60,
45
]
}
},
{
"command": "CircleCenterRadius",
"params": {
"center": "a1.start",
"construction": false,
"id": "c1",
"radius": 3
}
},
{
"command": "SketchStop",
"params": {}
},
{
"command": "Extrude",
"params": {
"body_names": [
"Plate"
],
"direction": "positive",
"name": "Hole1",
"operation": "cut",
"profiles": [
{
"area_mm2": 28.274334,
"centroid_sketch": [
100.0,
15.0
],
"loops": [
{
"curves": [
"c1"
],
"outer": true
}
],
"sketch": "PathSketch"
}
],
"sketch": "PathSketch",
"through_all": true
}
},
{
"command": "PatternOnPath",
"params": {
"count": 7,
"features": [
"Hole1"
],
"name": "PathPattern1",
"path": [
"a1"
],
"path_sketch": "PathSketch",
"spacing": 17.875031
}
}
]
}
@@ -0,0 +1,10 @@
{"commands": [
{"command": "ChangeParameterCommand", "params": {"name": "wall", "expression": "2 mm"}},
{"command": "PrimitiveBox", "params": {"length": 80, "width": 50, "height": 30}},
{"command": "FusionShellBodyCommand", "params": {"faces": [[40, 25, 30]], "thickness": "wall"}},
{"command": "FusionLipCommand", "params": {"face": [1, 25, 30], "width": 1, "height": 2}},
{"command": "FusionBossCommand", "params": {"position": [20, 20, 2], "diameter": 6, "height": 10, "hole_diameter": 2.5, "draft": "1 deg"}},
{"command": "FusionRestCommand", "params": {"position": [60, 30, 2], "width": 8, "length": 12, "height": 3, "thickness": 1}},
{"command": "FusionSnapFitCommand", "params": {"position": [40, 12, 2], "direction": [0, 0, 1], "hook": [0, 1, 0], "length": 12, "thickness": 1.5, "width": 6, "catch_depth": 1, "catch_length": 2}},
{"command": "FusionManagePlasticRuleCommand", "params": {"name": "ABS (1.5mm)", "active": true}}
]}
@@ -0,0 +1,96 @@
{
"commands": [
{
"command": "SketchCreate",
"params": {
"name": "Contour",
"plane": {
"origin": [
0.0,
0.0,
0.0
],
"x_dir": [
1.0,
0.0,
0.0
],
"y_dir": [
0.0,
0.0,
-1.0
]
}
}
},
{
"command": "DrawPolyline",
"params": {
"construction": false,
"ids": [
"l1"
],
"points": [
[
0.0,
0.0
],
[
40.0,
0.0
]
]
}
},
{
"command": "DrawPolyline",
"params": {
"construction": false,
"ids": [
"l2"
],
"points": [
"l1.end",
[
40.0,
-30.0
]
]
}
},
{
"command": "DrawPolyline",
"params": {
"construction": false,
"ids": [
"l3"
],
"points": [
"l2.end",
[
70.0,
-30.0
]
]
}
},
{
"command": "SketchStop",
"params": {}
},
{
"command": "FusionSheetMetalFlangeCommand",
"params": {
"curves": [
"l1",
"l2",
"l3"
],
"distance": 50,
"name": "ContourFlange",
"sketch": "Contour",
"type": "contour"
}
}
]
}
+211
View File
@@ -0,0 +1,211 @@
//! Designs saved by older SolveCraft builds (tests/corpus, see its README) open and evaluate as
//! they did when they were saved; damaged and hostile design files never panic.
use std::path::{Path, PathBuf};
use serde_json::{Value, json};
use solvecraft_engine::Session;
fn corpus() -> Vec<PathBuf> {
let dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/corpus");
let mut v: Vec<PathBuf> =
std::fs::read_dir(&dir).unwrap().flatten().map(|e| e.path()).filter(|p| p.extension().is_some_and(|x| x == "solvecraft")).collect();
v.sort();
assert!(v.len() >= 7, "corpus files in {}", dir.display());
v
}
fn open(path: &Path) -> Session {
let mut s = Session::default();
s.execute("doc.open", &json!({"path": path.to_string_lossy()})).unwrap_or_else(|e| panic!("{}: {e}", path.display()));
s
}
/// Differences that are deliberate behaviour changes made before the file format had versions
/// (no migration can tell those files apart): (file, check, why).
const KNOWN: [(&str, &str, &str); 2] = [
(
"m0-revolve-primitives",
"Body1 bbox",
"partial revolves turn toward the sketch normal since ac43365 (as Fusion's do); M0 turned them the other way",
),
("components-arm", "body_count", "Measure counted only the root component's bodies then; it measures every placed body now"),
];
fn known(file: &str, check: &str) -> bool {
KNOWN.iter().any(|(f, c, _)| *f == file && *c == check)
}
/// Volumes and areas then came from coarser tessellation (and curved booleans and fillets have
/// become exact since): the same part within this relative tolerance (the oracle's).
const REL: f64 = 1e-3;
fn close(a: f64, b: f64, rel: f64) -> bool {
(a - b).abs() <= rel * b.abs().max(1.0)
}
#[test]
fn old_designs_open_and_evaluate_as_saved() {
let mut problems = Vec::new();
for p in corpus() {
let name = p.file_stem().unwrap_or_default().to_string_lossy().to_string();
let want: Value = serde_json::from_slice(&std::fs::read(p.with_extension("expected.json")).unwrap()).unwrap();
let mut s = open(&p);
for r in &s.model.results {
if let Some(e) = &r.error {
problems.push(format!("{name}: feature {}: {e}", r.id));
}
}
assert_eq!(s.doc.format, solvecraft_engine::doc::format::format_string(solvecraft_engine::doc::format::FORMAT), "{name}: upgraded");
let got = s.execute("MeasureCommand", &json!({})).unwrap();
if got["body_count"] != want["body_count"] && !known(&name, "body_count") {
problems.push(format!("{name}: {} bodies, saved with {}", got["body_count"], want["body_count"]));
}
for w in want["bodies"].as_array().into_iter().flatten() {
let bname = w["name"].as_str().unwrap_or_default();
let Some(g) = got["bodies"].as_array().into_iter().flatten().find(|g| g["name"] == w["name"]) else {
problems.push(format!("{name}: body {bname} missing"));
continue;
};
for k in ["volume_mm3", "area_mm2"] {
let (a, b) = (g[k].as_f64().unwrap_or(f64::NAN), w[k].as_f64().unwrap_or(f64::NAN));
if !close(a, b, REL) {
problems.push(format!("{name}: {bname} {k} {a} (saved {b})"));
}
}
if known(&name, &format!("{bname} bbox")) {
continue;
}
for (end, i) in [("min", 0), ("min", 1), ("min", 2), ("max", 0), ("max", 1), ("max", 2)] {
let (a, b) = (g["bbox"][end][i].as_f64().unwrap_or(f64::NAN), w["bbox"][end][i].as_f64().unwrap_or(f64::NAN));
if (a - b).abs() > 1e-3 * b.abs().max(1.0) {
problems.push(format!("{name}: {bname} bbox {end}[{i}] {a} (saved {b})"));
}
}
}
// Saved again, it reads back the same.
let again = solvecraft_engine::doc::Document::from_json(&s.doc.to_json()).unwrap();
assert_eq!(again, *s.doc, "{name}: round trip");
}
assert!(problems.is_empty(), "{}", problems.join("\n"));
}
/// A small deterministic generator (xorshift).
struct Rng(u64);
impl Rng {
fn next(&mut self) -> u64 {
self.0 ^= self.0 << 13;
self.0 ^= self.0 >> 7;
self.0 ^= self.0 << 17;
self.0
}
fn below(&mut self, n: usize) -> usize {
(self.next() % n.max(1) as u64) as usize
}
}
const HOSTILE: [&str; 14] =
["0", "-1", "1e308", "-1e308", "1e9", "4294967297", "\"\"", "\"1/0\"", "\"((((1\"", "null", "true", "[]", "{}", "\"nope\""];
/// Every JSON value in a document, as a path of keys and indices.
fn paths(v: &Value, at: &mut Vec<Value>, out: &mut Vec<Vec<Value>>) {
out.push(at.clone());
match v {
Value::Object(m) => {
for (k, x) in m {
at.push(json!(k));
paths(x, at, out);
at.pop();
}
}
Value::Array(a) => {
for (i, x) in a.iter().enumerate() {
at.push(json!(i));
paths(x, at, out);
at.pop();
}
}
_ => {}
}
}
fn at_mut<'a>(v: &'a mut Value, path: &[Value]) -> Option<&'a mut Value> {
let mut cur = v;
for k in path {
cur = match k {
Value::String(s) => cur.get_mut(s.as_str())?,
Value::Number(n) => cur.get_mut(n.as_u64()? as usize)?,
_ => return None,
};
}
Some(cur)
}
/// Open a file's bytes as File → Open does and evaluate them; any panic is a failure.
fn try_bytes(bytes: &[u8]) {
if let Ok(doc) = solvecraft_engine::io::read_design(bytes) {
let mut s = Session::new(doc);
let _ = s.execute("MeasureCommand", &json!({}));
let _ = s.execute("document.inspect", &json!({}));
}
}
#[test]
fn damaged_and_hostile_design_files_never_panic() {
let mut rng = Rng(0x5eed_cafe_f00d_d00d);
let mut failures = Vec::new();
for p in corpus() {
let name = p.file_name().unwrap_or_default().to_string_lossy().to_string();
let bytes = std::fs::read(&p).unwrap();
let doc: Value = serde_json::from_slice(&bytes).unwrap();
let mut all = Vec::new();
paths(&doc, &mut Vec::new(), &mut all);
let mut cases: Vec<(String, Vec<u8>)> = Vec::new();
// Truncated and bit-flipped files.
for _ in 0..8 {
let cut = rng.below(bytes.len());
cases.push((format!("truncated at {cut}"), bytes[..cut].to_vec()));
let mut b = bytes.clone();
let i = rng.below(b.len());
b[i] ^= 1 << rng.below(8);
cases.push((format!("bit flip at {i}"), b));
}
// Values replaced by hostile ones, and keys removed.
for _ in 0..25 {
let path = &all[rng.below(all.len())];
let mut v = doc.clone();
let h = HOSTILE[rng.below(HOSTILE.len())];
if let Some(slot) = at_mut(&mut v, path) {
*slot = serde_json::from_str(h).unwrap();
}
cases.push((format!("{path:?} = {h}"), v.to_string().into_bytes()));
let mut v = doc.clone();
if let Some((last, parent)) = path.split_last()
&& let (Some(Value::Object(m)), Value::String(k)) = (at_mut(&mut v, parent), last)
{
m.remove(k);
}
cases.push((format!("{path:?} removed"), v.to_string().into_bytes()));
}
// Features repeated many times; deep nesting; not JSON at all.
let mut v = doc.clone();
if let Some(Value::Array(fs)) = v.get_mut("features") {
let copy = fs.clone();
for _ in 0..2 {
fs.extend(copy.iter().cloned());
}
}
cases.push(("features repeated".into(), v.to_string().into_bytes()));
cases.push(("deep nesting".into(), format!("{}{}", "[".repeat(100_000), "]".repeat(100_000)).into_bytes()));
cases.push(("binary".into(), (0..4096u32).map(|i| (i.wrapping_mul(2_654_435_761) >> 24) as u8).collect()));
for (what, b) in cases {
if std::env::var_os("CORPUS_TRACE").is_some() {
eprintln!("{name}: {what}");
}
if std::panic::catch_unwind(|| try_bytes(&b)).is_err() {
failures.push(format!("{name}: {what}"));
}
}
}
assert!(failures.is_empty(), "panics:\n{}", failures.join("\n"));
}