diff --git a/tests/kernel-cases/ladder/01-primitives/001-box/expected.json b/tests/kernel-cases/ladder/01-primitives/001-box/expected.json new file mode 100644 index 0000000000..3c66f255ad --- /dev/null +++ b/tests/kernel-cases/ladder/01-primitives/001-box/expected.json @@ -0,0 +1,24 @@ +{ + "sources": [], + "what": "a 60x40x20 box: the stock every later tier starts from", + "source": "analytic", + "method": "V = 60*40*20; A = 2(60*40 + 60*20 + 40*20); a box has 6 faces, 12 edges, 8 vertices", + "body_count": 1, + "volume_mm3": 48000, + "area_mm2": 8800, + "faces": 6, + "edges": 12, + "vertices": 8, + "bbox": { + "min": [ + 0, + 0, + 0 + ], + "max": [ + 60, + 40, + 20 + ] + } +} diff --git a/tests/kernel-cases/ladder/01-primitives/001-box/recipe.json b/tests/kernel-cases/ladder/01-primitives/001-box/recipe.json new file mode 100644 index 0000000000..e09d4e82ce --- /dev/null +++ b/tests/kernel-cases/ladder/01-primitives/001-box/recipe.json @@ -0,0 +1,18 @@ +{ + "commands": [ + { + "command": "solid.box", + "params": { + "corner": [ + 0, + 0, + 0 + ], + "length": 60, + "width": 40, + "height": 20, + "body_name": "Part" + } + } + ] +} diff --git a/tests/kernel-cases/ladder/01-primitives/002-cylinder/expected.json b/tests/kernel-cases/ladder/01-primitives/002-cylinder/expected.json new file mode 100644 index 0000000000..a428611f87 --- /dev/null +++ b/tests/kernel-cases/ladder/01-primitives/002-cylinder/expected.json @@ -0,0 +1,22 @@ +{ + "sources": [], + "what": "a cylinder r10 h30", + "source": "analytic", + "method": "V = pi r^2 h; A = 2 pi r^2 + 2 pi r h; faces: two disks and one whole cylinder", + "body_count": 1, + "volume_mm3": 9424.777960769, + "area_mm2": 2513.274122872, + "faces": 3, + "bbox": { + "min": [ + -10, + -10, + 0 + ], + "max": [ + 10, + 10, + 30 + ] + } +} diff --git a/tests/kernel-cases/ladder/01-primitives/002-cylinder/recipe.json b/tests/kernel-cases/ladder/01-primitives/002-cylinder/recipe.json new file mode 100644 index 0000000000..2ba461ebb9 --- /dev/null +++ b/tests/kernel-cases/ladder/01-primitives/002-cylinder/recipe.json @@ -0,0 +1,16 @@ +{ + "commands": [ + { + "command": "solid.cylinder", + "params": { + "base": [ + 0, + 0, + 0 + ], + "radius": 10, + "height": 30 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/01-primitives/003-cone/expected.json b/tests/kernel-cases/ladder/01-primitives/003-cone/expected.json new file mode 100644 index 0000000000..993d89cfb2 --- /dev/null +++ b/tests/kernel-cases/ladder/01-primitives/003-cone/expected.json @@ -0,0 +1,22 @@ +{ + "sources": [], + "what": "a cone r10 h30 (no cone primitive: a revolved right triangle, its axis a sketch line)", + "source": "analytic", + "method": "V = pi r^2 h / 3; A = pi r^2 + pi r sqrt(r^2 + h^2); a disk and the cone face", + "body_count": 1, + "volume_mm3": 3141.59265359, + "area_mm2": 1307.618091939, + "faces": 2, + "bbox": { + "min": [ + -10, + -10, + 0 + ], + "max": [ + 10, + 10, + 30 + ] + } +} diff --git a/tests/kernel-cases/ladder/01-primitives/003-cone/recipe.json b/tests/kernel-cases/ladder/01-primitives/003-cone/recipe.json new file mode 100644 index 0000000000..9f01d22f5c --- /dev/null +++ b/tests/kernel-cases/ladder/01-primitives/003-cone/recipe.json @@ -0,0 +1,61 @@ +{ + "commands": [ + { + "command": "sketch.create", + "params": { + "plane": { + "origin": [ + 0, + 0, + 0 + ], + "x_dir": [ + 1, + 0, + 0 + ], + "y_dir": [ + 0, + 0, + 1 + ] + } + } + }, + { + "command": "sketch.line", + "params": { + "points": [ + [ + 0, + 0 + ], + [ + 10, + 0 + ], + [ + 0, + 30 + ] + ], + "ids": [ + "b", + "s", + "ax" + ], + "closed": true + } + }, + { + "command": "sketch.finish", + "params": {} + }, + { + "command": "solid.revolve", + "params": { + "axis": "ax" + } + } + ] +} diff --git a/tests/kernel-cases/ladder/01-primitives/004-frustum/expected.json b/tests/kernel-cases/ladder/01-primitives/004-frustum/expected.json new file mode 100644 index 0000000000..1ca43df7fa --- /dev/null +++ b/tests/kernel-cases/ladder/01-primitives/004-frustum/expected.json @@ -0,0 +1,10 @@ +{ + "sources": [], + "what": "a cone frustum r10 -> r5, h20 (revolved trapezoid)", + "source": "analytic", + "method": "V = pi h/3 (R^2 + R r + r^2); A = pi (R^2 + r^2) + pi (R + r) sqrt((R-r)^2 + h^2)", + "body_count": 1, + "volume_mm3": 3665.191429188, + "area_mm2": 1364.182957455, + "faces": 3 +} diff --git a/tests/kernel-cases/ladder/01-primitives/004-frustum/recipe.json b/tests/kernel-cases/ladder/01-primitives/004-frustum/recipe.json new file mode 100644 index 0000000000..d304a2c22e --- /dev/null +++ b/tests/kernel-cases/ladder/01-primitives/004-frustum/recipe.json @@ -0,0 +1,66 @@ +{ + "commands": [ + { + "command": "sketch.create", + "params": { + "plane": { + "origin": [ + 0, + 0, + 0 + ], + "x_dir": [ + 1, + 0, + 0 + ], + "y_dir": [ + 0, + 0, + 1 + ] + } + } + }, + { + "command": "sketch.line", + "params": { + "points": [ + [ + 0, + 0 + ], + [ + 10, + 0 + ], + [ + 5, + 20 + ], + [ + 0, + 20 + ] + ], + "ids": [ + "b", + "s", + "t", + "ax" + ], + "closed": true + } + }, + { + "command": "sketch.finish", + "params": {} + }, + { + "command": "solid.revolve", + "params": { + "axis": "ax" + } + } + ] +} diff --git a/tests/kernel-cases/ladder/01-primitives/005-sphere/expected.json b/tests/kernel-cases/ladder/01-primitives/005-sphere/expected.json new file mode 100644 index 0000000000..6ef0502c1e --- /dev/null +++ b/tests/kernel-cases/ladder/01-primitives/005-sphere/expected.json @@ -0,0 +1,22 @@ +{ + "sources": [], + "what": "a sphere r10", + "source": "analytic", + "method": "V = 4/3 pi r^3; A = 4 pi r^2; one face", + "body_count": 1, + "volume_mm3": 4188.790204786, + "area_mm2": 1256.637061436, + "faces": 1, + "bbox": { + "min": [ + -10, + -10, + -10 + ], + "max": [ + 10, + 10, + 10 + ] + } +} diff --git a/tests/kernel-cases/ladder/01-primitives/005-sphere/recipe.json b/tests/kernel-cases/ladder/01-primitives/005-sphere/recipe.json new file mode 100644 index 0000000000..c57daaba83 --- /dev/null +++ b/tests/kernel-cases/ladder/01-primitives/005-sphere/recipe.json @@ -0,0 +1,10 @@ +{ + "commands": [ + { + "command": "solid.sphere", + "params": { + "radius": 10 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/01-primitives/006-torus/expected.json b/tests/kernel-cases/ladder/01-primitives/006-torus/expected.json new file mode 100644 index 0000000000..8038ebdb1f --- /dev/null +++ b/tests/kernel-cases/ladder/01-primitives/006-torus/expected.json @@ -0,0 +1,22 @@ +{ + "sources": [], + "what": "a torus R20 r5", + "source": "analytic", + "method": "V = 2 pi^2 R r^2; A = 4 pi^2 R r; one face", + "body_count": 1, + "volume_mm3": 9869.604401089, + "area_mm2": 3947.841760436, + "faces": 1, + "bbox": { + "min": [ + -25, + -25, + -5 + ], + "max": [ + 25, + 25, + 5 + ] + } +} diff --git a/tests/kernel-cases/ladder/01-primitives/006-torus/recipe.json b/tests/kernel-cases/ladder/01-primitives/006-torus/recipe.json new file mode 100644 index 0000000000..21cca87c9f --- /dev/null +++ b/tests/kernel-cases/ladder/01-primitives/006-torus/recipe.json @@ -0,0 +1,11 @@ +{ + "commands": [ + { + "command": "solid.torus", + "params": { + "major": 20, + "minor": 5 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/02-booleans/001-coincident-face-join/expected.json b/tests/kernel-cases/ladder/02-booleans/001-coincident-face-join/expected.json new file mode 100644 index 0000000000..9694d3ac38 --- /dev/null +++ b/tests/kernel-cases/ladder/02-booleans/001-coincident-face-join/expected.json @@ -0,0 +1,23 @@ +{ + "sources": [], + "what": "a 60x40x10 box joined exactly on the stock's top face: one 60x40x30 box", + "source": "analytic", + "method": "the boxes share the whole face z=20 and every side is coplanar: the union is the box 60x40x30, V = 72000, its coplanar sides merged into 6 faces", + "body_count": 1, + "volume_mm3": 72000.0, + "faces": 6, + "edges": 12, + "vertices": 8, + "bbox": { + "min": [ + 0, + 0, + 0 + ], + "max": [ + 60, + 40, + 30 + ] + } +} diff --git a/tests/kernel-cases/ladder/02-booleans/001-coincident-face-join/recipe.json b/tests/kernel-cases/ladder/02-booleans/001-coincident-face-join/recipe.json new file mode 100644 index 0000000000..bf3f885624 --- /dev/null +++ b/tests/kernel-cases/ladder/02-booleans/001-coincident-face-join/recipe.json @@ -0,0 +1,22 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.box", + "params": { + "corner": [ + 0, + 0, + 20 + ], + "length": 60, + "width": 40, + "height": 10, + "operation": "join", + "targets": [ + "Part" + ] + } + } + ] +} diff --git a/tests/kernel-cases/ladder/02-booleans/002-coplanar-overlap-join/expected.json b/tests/kernel-cases/ladder/02-booleans/002-coplanar-overlap-join/expected.json new file mode 100644 index 0000000000..f982eb985e --- /dev/null +++ b/tests/kernel-cases/ladder/02-booleans/002-coplanar-overlap-join/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "two 60x40x20 boxes overlapping 30x20 in plan, tops and bottoms coplanar", + "source": "analytic", + "method": "V = 2*48000 - 30*20*20; the plan outline is an 8-sided polygon: 8 sides + top + bottom", + "body_count": 1, + "volume_mm3": 84000, + "faces": 10 +} diff --git a/tests/kernel-cases/ladder/02-booleans/002-coplanar-overlap-join/recipe.json b/tests/kernel-cases/ladder/02-booleans/002-coplanar-overlap-join/recipe.json new file mode 100644 index 0000000000..91d74fbf3a --- /dev/null +++ b/tests/kernel-cases/ladder/02-booleans/002-coplanar-overlap-join/recipe.json @@ -0,0 +1,22 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.box", + "params": { + "corner": [ + 30, + 20, + 0 + ], + "length": 60, + "width": 40, + "height": 20, + "operation": "join", + "targets": [ + "Part" + ] + } + } + ] +} diff --git a/tests/kernel-cases/ladder/02-booleans/003-pocket-flush-with-top/expected.json b/tests/kernel-cases/ladder/02-booleans/003-pocket-flush-with-top/expected.json new file mode 100644 index 0000000000..7e4ef6a11a --- /dev/null +++ b/tests/kernel-cases/ladder/02-booleans/003-pocket-flush-with-top/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "a 20x20x10 pocket whose tool's top is the stock's top face", + "source": "analytic", + "method": "V = 48000 - 20*20*10; faces: 6 + 4 pocket walls + pocket floor", + "body_count": 1, + "volume_mm3": 44000, + "faces": 11 +} diff --git a/tests/kernel-cases/ladder/02-booleans/003-pocket-flush-with-top/recipe.json b/tests/kernel-cases/ladder/02-booleans/003-pocket-flush-with-top/recipe.json new file mode 100644 index 0000000000..15c86b84cd --- /dev/null +++ b/tests/kernel-cases/ladder/02-booleans/003-pocket-flush-with-top/recipe.json @@ -0,0 +1,22 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.box", + "params": { + "corner": [ + 20, + 10, + 10 + ], + "length": 20, + "width": 20, + "height": 10, + "operation": "cut", + "targets": [ + "Part" + ] + } + } + ] +} diff --git a/tests/kernel-cases/ladder/02-booleans/004-edge-on-edge-no-overlap/expected.json b/tests/kernel-cases/ladder/02-booleans/004-edge-on-edge-no-overlap/expected.json new file mode 100644 index 0000000000..9174788efd --- /dev/null +++ b/tests/kernel-cases/ladder/02-booleans/004-edge-on-edge-no-overlap/expected.json @@ -0,0 +1,11 @@ +{ + "sources": [], + "what": "a cut tool that shares only the top front edge of the stock", + "source": "analytic", + "method": "the tool touches the stock only along the edge y=0, z=20: nothing is removed, the box stays as it was", + "body_count": 1, + "volume_mm3": 48000, + "faces": 6, + "edges": 12, + "vertices": 8 +} diff --git a/tests/kernel-cases/ladder/02-booleans/004-edge-on-edge-no-overlap/recipe.json b/tests/kernel-cases/ladder/02-booleans/004-edge-on-edge-no-overlap/recipe.json new file mode 100644 index 0000000000..3d7a8b1d19 --- /dev/null +++ b/tests/kernel-cases/ladder/02-booleans/004-edge-on-edge-no-overlap/recipe.json @@ -0,0 +1,22 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.box", + "params": { + "corner": [ + 0, + -10, + 20 + ], + "length": 60, + "width": 10, + "height": 10, + "operation": "cut", + "targets": [ + "Part" + ] + } + } + ] +} diff --git a/tests/kernel-cases/ladder/02-booleans/005-vertex-on-face-no-overlap/expected.json b/tests/kernel-cases/ladder/02-booleans/005-vertex-on-face-no-overlap/expected.json new file mode 100644 index 0000000000..da79eb9526 --- /dev/null +++ b/tests/kernel-cases/ladder/02-booleans/005-vertex-on-face-no-overlap/expected.json @@ -0,0 +1,11 @@ +{ + "sources": [], + "what": "an inverted cone whose apex touches the middle of the top face, cut", + "source": "analytic", + "method": "the cone (apex (30,20,20), base r10 at z=30) meets the stock only at its apex: the cut removes nothing", + "body_count": 1, + "volume_mm3": 48000, + "faces": 6, + "edges": 12, + "vertices": 8 +} diff --git a/tests/kernel-cases/ladder/02-booleans/005-vertex-on-face-no-overlap/recipe.json b/tests/kernel-cases/ladder/02-booleans/005-vertex-on-face-no-overlap/recipe.json new file mode 100644 index 0000000000..844d03269f --- /dev/null +++ b/tests/kernel-cases/ladder/02-booleans/005-vertex-on-face-no-overlap/recipe.json @@ -0,0 +1,66 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "sketch.create", + "params": { + "plane": { + "origin": [ + 30, + 20, + 0 + ], + "x_dir": [ + 1, + 0, + 0 + ], + "y_dir": [ + 0, + 0, + 1 + ] + } + } + }, + { + "command": "sketch.line", + "params": { + "points": [ + [ + 0, + 20 + ], + [ + 10, + 30 + ], + [ + 0, + 30 + ] + ], + "ids": [ + "s", + "t", + "ax" + ], + "closed": true + } + }, + { + "command": "sketch.finish", + "params": {} + }, + { + "command": "solid.revolve", + "params": { + "axis": "ax", + "operation": "cut", + "targets": [ + "Part" + ] + } + } + ] +} diff --git a/tests/kernel-cases/ladder/02-booleans/006-tangent-along-line-no-overlap/expected.json b/tests/kernel-cases/ladder/02-booleans/006-tangent-along-line-no-overlap/expected.json new file mode 100644 index 0000000000..9d5ba0d265 --- /dev/null +++ b/tests/kernel-cases/ladder/02-booleans/006-tangent-along-line-no-overlap/expected.json @@ -0,0 +1,11 @@ +{ + "sources": [], + "what": "a cylinder r10 along X lying on the top face (tangent along a line), cut", + "source": "analytic", + "method": "the cylinder (axis y=20, z=30) touches the top face z=20 only along the line y=20: nothing is removed", + "body_count": 1, + "volume_mm3": 48000, + "faces": 6, + "edges": 12, + "vertices": 8 +} diff --git a/tests/kernel-cases/ladder/02-booleans/006-tangent-along-line-no-overlap/recipe.json b/tests/kernel-cases/ladder/02-booleans/006-tangent-along-line-no-overlap/recipe.json new file mode 100644 index 0000000000..e6bd77e90b --- /dev/null +++ b/tests/kernel-cases/ladder/02-booleans/006-tangent-along-line-no-overlap/recipe.json @@ -0,0 +1,22 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.cylinder", + "params": { + "base": [ + -5, + 20, + 30 + ], + "radius": 10, + "height": 70, + "axis": "X", + "operation": "cut", + "targets": [ + "Part" + ] + } + } + ] +} diff --git a/tests/kernel-cases/ladder/02-booleans/007-near-tangent-gap-1e-3/expected.json b/tests/kernel-cases/ladder/02-booleans/007-near-tangent-gap-1e-3/expected.json new file mode 100644 index 0000000000..a6121d36c9 --- /dev/null +++ b/tests/kernel-cases/ladder/02-booleans/007-near-tangent-gap-1e-3/expected.json @@ -0,0 +1,11 @@ +{ + "sources": [], + "what": "a cylinder r10 along X 1e-3 mm above the top face, cut", + "source": "analytic", + "method": "gap 1e-3 mm: no contact, nothing is removed", + "body_count": 1, + "volume_mm3": 48000, + "faces": 6, + "edges": 12, + "vertices": 8 +} diff --git a/tests/kernel-cases/ladder/02-booleans/007-near-tangent-gap-1e-3/recipe.json b/tests/kernel-cases/ladder/02-booleans/007-near-tangent-gap-1e-3/recipe.json new file mode 100644 index 0000000000..4f36f17b6a --- /dev/null +++ b/tests/kernel-cases/ladder/02-booleans/007-near-tangent-gap-1e-3/recipe.json @@ -0,0 +1,22 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.cylinder", + "params": { + "base": [ + -5, + 20, + 30.001 + ], + "radius": 10, + "height": 70, + "axis": "X", + "operation": "cut", + "targets": [ + "Part" + ] + } + } + ] +} diff --git a/tests/kernel-cases/ladder/02-booleans/008-near-tangent-gap-1e-6/expected.json b/tests/kernel-cases/ladder/02-booleans/008-near-tangent-gap-1e-6/expected.json new file mode 100644 index 0000000000..8af81dadd8 --- /dev/null +++ b/tests/kernel-cases/ladder/02-booleans/008-near-tangent-gap-1e-6/expected.json @@ -0,0 +1,11 @@ +{ + "sources": [], + "what": "a cylinder r10 along X 1e-6 mm above the top face, cut", + "source": "analytic", + "method": "gap 1e-6 mm: no contact; even a kernel that treats it as touching must remove nothing", + "body_count": 1, + "volume_mm3": 48000, + "faces": 6, + "edges": 12, + "vertices": 8 +} diff --git a/tests/kernel-cases/ladder/02-booleans/008-near-tangent-gap-1e-6/recipe.json b/tests/kernel-cases/ladder/02-booleans/008-near-tangent-gap-1e-6/recipe.json new file mode 100644 index 0000000000..4d6b6c0888 --- /dev/null +++ b/tests/kernel-cases/ladder/02-booleans/008-near-tangent-gap-1e-6/recipe.json @@ -0,0 +1,22 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.cylinder", + "params": { + "base": [ + -5, + 20, + 30.000001 + ], + "radius": 10, + "height": 70, + "axis": "X", + "operation": "cut", + "targets": [ + "Part" + ] + } + } + ] +} diff --git a/tests/kernel-cases/ladder/02-booleans/009-near-tangent-overlap-1e-3/expected.json b/tests/kernel-cases/ladder/02-booleans/009-near-tangent-overlap-1e-3/expected.json new file mode 100644 index 0000000000..0291a3613d --- /dev/null +++ b/tests/kernel-cases/ladder/02-booleans/009-near-tangent-overlap-1e-3/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "a cylinder r10 along X dipping 1e-3 mm into the top face, cut: a groove 1e-3 deep", + "source": "analytic", + "method": "circular segment of depth h=1e-3 in r=10: A = r^2 acos((r-h)/r) - (r-h) sqrt(2rh - h^2) = 1.886e-04 mm^2, times 60; the groove runs the whole length, so it splits the top face in two and adds one cylinder face: 6 - 1 + 2 + 1", + "body_count": 1, + "volume_mm3": 47999.988686461, + "faces": 8 +} diff --git a/tests/kernel-cases/ladder/02-booleans/009-near-tangent-overlap-1e-3/recipe.json b/tests/kernel-cases/ladder/02-booleans/009-near-tangent-overlap-1e-3/recipe.json new file mode 100644 index 0000000000..4767605af1 --- /dev/null +++ b/tests/kernel-cases/ladder/02-booleans/009-near-tangent-overlap-1e-3/recipe.json @@ -0,0 +1,22 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.cylinder", + "params": { + "base": [ + -5, + 20, + 29.999 + ], + "radius": 10, + "height": 70, + "axis": "X", + "operation": "cut", + "targets": [ + "Part" + ] + } + } + ] +} diff --git a/tests/kernel-cases/ladder/02-booleans/010-near-tangent-overlap-1e-6/expected.json b/tests/kernel-cases/ladder/02-booleans/010-near-tangent-overlap-1e-6/expected.json new file mode 100644 index 0000000000..ce1eabac14 --- /dev/null +++ b/tests/kernel-cases/ladder/02-booleans/010-near-tangent-overlap-1e-6/expected.json @@ -0,0 +1,8 @@ +{ + "sources": [], + "what": "a cylinder r10 along X dipping 1e-6 mm into the top face, cut", + "source": "invariants", + "method": "a groove 1e-6 deep (V change ~1e-8) is at the kernels' tolerance: a groove or no change are both right; graded: one valid body, V = 48000", + "body_count": 1, + "volume_mm3": 48000 +} diff --git a/tests/kernel-cases/ladder/02-booleans/010-near-tangent-overlap-1e-6/recipe.json b/tests/kernel-cases/ladder/02-booleans/010-near-tangent-overlap-1e-6/recipe.json new file mode 100644 index 0000000000..2aa7b64efa --- /dev/null +++ b/tests/kernel-cases/ladder/02-booleans/010-near-tangent-overlap-1e-6/recipe.json @@ -0,0 +1,22 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.cylinder", + "params": { + "base": [ + -5, + 20, + 29.999999 + ], + "radius": 10, + "height": 70, + "axis": "X", + "operation": "cut", + "targets": [ + "Part" + ] + } + } + ] +} diff --git a/tests/kernel-cases/ladder/02-booleans/011-tangent-along-circle-through-hole/expected.json b/tests/kernel-cases/ladder/02-booleans/011-tangent-along-circle-through-hole/expected.json new file mode 100644 index 0000000000..f3b159967e --- /dev/null +++ b/tests/kernel-cases/ladder/02-booleans/011-tangent-along-circle-through-hole/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "a sphere r10 sitting in a through hole r10 at the top edge (tangent along the hole's top circle), cut", + "source": "analytic", + "method": "the lower half of the sphere lies inside the hole, tangent to its wall along the circle z=20, the upper half is outside: the sphere removes nothing more; V = 48000 - pi 100 20", + "body_count": 1, + "volume_mm3": 41716.81469282, + "faces": 7 +} diff --git a/tests/kernel-cases/ladder/02-booleans/011-tangent-along-circle-through-hole/recipe.json b/tests/kernel-cases/ladder/02-booleans/011-tangent-along-circle-through-hole/recipe.json new file mode 100644 index 0000000000..a5b1f8988c --- /dev/null +++ b/tests/kernel-cases/ladder/02-booleans/011-tangent-along-circle-through-hole/recipe.json @@ -0,0 +1,36 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.cylinder", + "params": { + "base": [ + 30, + 20, + -1 + ], + "radius": 10, + "height": 22, + "operation": "cut", + "targets": [ + "Part" + ] + } + }, + { + "command": "solid.sphere", + "params": { + "radius": 10, + "center": [ + 30, + 20, + 20 + ], + "operation": "cut", + "targets": [ + "Part" + ] + } + } + ] +} diff --git a/tests/kernel-cases/ladder/02-booleans/012-tangent-along-circle-bowl/expected.json b/tests/kernel-cases/ladder/02-booleans/012-tangent-along-circle-bowl/expected.json new file mode 100644 index 0000000000..84dda3357c --- /dev/null +++ b/tests/kernel-cases/ladder/02-booleans/012-tangent-along-circle-bowl/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "a blind hole r10 10 deep, then a sphere r10 centred on its floor: the floor disk vanishes exactly", + "source": "analytic", + "method": "the sphere's equator is the floor's edge circle (tangent to the hole wall along it): removed = pi r^2 10 + 2/3 pi r^3; faces 6 + wall + bowl", + "body_count": 1, + "volume_mm3": 42764.012244017, + "faces": 8 +} diff --git a/tests/kernel-cases/ladder/02-booleans/012-tangent-along-circle-bowl/recipe.json b/tests/kernel-cases/ladder/02-booleans/012-tangent-along-circle-bowl/recipe.json new file mode 100644 index 0000000000..2ef6620364 --- /dev/null +++ b/tests/kernel-cases/ladder/02-booleans/012-tangent-along-circle-bowl/recipe.json @@ -0,0 +1,36 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.cylinder", + "params": { + "base": [ + 30, + 20, + 10 + ], + "radius": 10, + "height": 11, + "operation": "cut", + "targets": [ + "Part" + ] + } + }, + { + "command": "solid.sphere", + "params": { + "radius": 10, + "center": [ + 30, + 20, + 10 + ], + "operation": "cut", + "targets": [ + "Part" + ] + } + } + ] +} diff --git a/tests/kernel-cases/ladder/02-booleans/013-cut-across-cylinder-seam/expected.json b/tests/kernel-cases/ladder/02-booleans/013-cut-across-cylinder-seam/expected.json new file mode 100644 index 0000000000..d297621693 --- /dev/null +++ b/tests/kernel-cases/ladder/02-booleans/013-cut-across-cylinder-seam/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "a 10x4 notch through a cylinder r10 across its +X side (where the seam lies)", + "source": "analytic", + "method": "removed section = int_-2^2 (sqrt(100 - y^2) - 5) dy = 19.731710021 (Gauss), times 20; faces: cylinder, 2 disks, 3 notch planes", + "body_count": 1, + "volume_mm3": 5888.551106754, + "faces": 6 +} diff --git a/tests/kernel-cases/ladder/02-booleans/013-cut-across-cylinder-seam/recipe.json b/tests/kernel-cases/ladder/02-booleans/013-cut-across-cylinder-seam/recipe.json new file mode 100644 index 0000000000..e083151c2d --- /dev/null +++ b/tests/kernel-cases/ladder/02-booleans/013-cut-across-cylinder-seam/recipe.json @@ -0,0 +1,34 @@ +{ + "commands": [ + { + "command": "solid.cylinder", + "params": { + "base": [ + 0, + 0, + 0 + ], + "radius": 10, + "height": 20, + "body_name": "Part" + } + }, + { + "command": "solid.box", + "params": { + "corner": [ + 5, + -2, + -1 + ], + "length": 10, + "width": 4, + "height": 22, + "operation": "cut", + "targets": [ + "Part" + ] + } + } + ] +} diff --git a/tests/kernel-cases/ladder/02-booleans/014-cut-through-sphere-pole/expected.json b/tests/kernel-cases/ladder/02-booleans/014-cut-through-sphere-pole/expected.json new file mode 100644 index 0000000000..a0a1b5eb8b --- /dev/null +++ b/tests/kernel-cases/ladder/02-booleans/014-cut-through-sphere-pole/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "a 4x4 square post cut off the top of a sphere r10 through its pole", + "source": "analytic", + "method": "removed = int over the square |x|,|y|<=2 of (sqrt(100 - x^2 - y^2) - 5) = 77.846302682 (nested Gauss); faces: sphere, 4 sides, floor z=5", + "body_count": 1, + "volume_mm3": 4110.943902105, + "faces": 6 +} diff --git a/tests/kernel-cases/ladder/02-booleans/014-cut-through-sphere-pole/recipe.json b/tests/kernel-cases/ladder/02-booleans/014-cut-through-sphere-pole/recipe.json new file mode 100644 index 0000000000..6f43f4dd9d --- /dev/null +++ b/tests/kernel-cases/ladder/02-booleans/014-cut-through-sphere-pole/recipe.json @@ -0,0 +1,28 @@ +{ + "commands": [ + { + "command": "solid.sphere", + "params": { + "radius": 10, + "body_name": "Part" + } + }, + { + "command": "solid.box", + "params": { + "corner": [ + -2, + -2, + 5 + ], + "length": 4, + "width": 4, + "height": 6, + "operation": "cut", + "targets": [ + "Part" + ] + } + } + ] +} diff --git a/tests/kernel-cases/ladder/02-booleans/015-knife-edge-wedge-cut/expected.json b/tests/kernel-cases/ladder/02-booleans/015-knife-edge-wedge-cut/expected.json new file mode 100644 index 0000000000..09bd14a586 --- /dev/null +++ b/tests/kernel-cases/ladder/02-booleans/015-knife-edge-wedge-cut/expected.json @@ -0,0 +1,11 @@ +{ + "sources": [], + "what": "a triangular prism cut whose slope meets the bottom face along one line (a knife edge), its other faces on the stock's", + "source": "analytic", + "method": "the tool removes the half above the plane through the edge x=0,z=0 and the edge x=60,z=20: a wedge 60x40x20/2 = 24000 with a knife edge at x=0, z=0", + "body_count": 1, + "volume_mm3": 24000.0, + "faces": 5, + "edges": 9, + "vertices": 6 +} diff --git a/tests/kernel-cases/ladder/02-booleans/015-knife-edge-wedge-cut/recipe.json b/tests/kernel-cases/ladder/02-booleans/015-knife-edge-wedge-cut/recipe.json new file mode 100644 index 0000000000..a3da2b0f9b --- /dev/null +++ b/tests/kernel-cases/ladder/02-booleans/015-knife-edge-wedge-cut/recipe.json @@ -0,0 +1,61 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "sketch.create", + "params": { + "plane": { + "origin": [ + 0, + 41, + 0 + ], + "x_dir": [ + 1, + 0, + 0 + ], + "y_dir": [ + 0, + 0, + 1 + ] + } + } + }, + { + "command": "sketch.line", + "params": { + "points": [ + [ + 0, + 0 + ], + [ + 60, + 20 + ], + [ + 0, + 20 + ] + ], + "closed": true + } + }, + { + "command": "sketch.finish", + "params": {} + }, + { + "command": "solid.extrude", + "params": { + "distance": 42, + "operation": "cut", + "targets": [ + "Part" + ] + } + } + ] +} diff --git a/tests/kernel-cases/ladder/02-booleans/016-v-groove-pinches-bottom-to-line/expected.json b/tests/kernel-cases/ladder/02-booleans/016-v-groove-pinches-bottom-to-line/expected.json new file mode 100644 index 0000000000..c46bd014b8 --- /dev/null +++ b/tests/kernel-cases/ladder/02-booleans/016-v-groove-pinches-bottom-to-line/expected.json @@ -0,0 +1,8 @@ +{ + "sources": [], + "what": "a V groove whose apex touches the bottom face along a line, cut through: two pieces", + "source": "analytic", + "method": "apex (30, 0) on the bottom face, flanks through (18, 20) and (42, 20): removed triangle 24 wide x 20 high, times 40; the pieces meet only along the line x=30, z=0, so the regularized result is two solids", + "body_count": 2, + "volume_mm3": 38400.0 +} diff --git a/tests/kernel-cases/ladder/02-booleans/016-v-groove-pinches-bottom-to-line/recipe.json b/tests/kernel-cases/ladder/02-booleans/016-v-groove-pinches-bottom-to-line/recipe.json new file mode 100644 index 0000000000..ccd46a1299 --- /dev/null +++ b/tests/kernel-cases/ladder/02-booleans/016-v-groove-pinches-bottom-to-line/recipe.json @@ -0,0 +1,61 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "sketch.create", + "params": { + "plane": { + "origin": [ + 0, + 41, + 0 + ], + "x_dir": [ + 1, + 0, + 0 + ], + "y_dir": [ + 0, + 0, + 1 + ] + } + } + }, + { + "command": "sketch.line", + "params": { + "points": [ + [ + 15, + 25 + ], + [ + 30, + 0 + ], + [ + 45, + 25 + ] + ], + "closed": true + } + }, + { + "command": "sketch.finish", + "params": {} + }, + { + "command": "solid.extrude", + "params": { + "distance": 42, + "operation": "cut", + "targets": [ + "Part" + ] + } + } + ] +} diff --git a/tests/kernel-cases/ladder/02-booleans/017-cylinder-tangent-top-and-bottom/expected.json b/tests/kernel-cases/ladder/02-booleans/017-cylinder-tangent-top-and-bottom/expected.json new file mode 100644 index 0000000000..10ce5a5f1b --- /dev/null +++ b/tests/kernel-cases/ladder/02-booleans/017-cylinder-tangent-top-and-bottom/expected.json @@ -0,0 +1,8 @@ +{ + "sources": [], + "what": "a cylinder r10 along X tangent to both the top and bottom faces, cut: two pieces with zero-angle cusps", + "source": "analytic", + "method": "the cylinder (axis y=20, z=10) spans the full height at y=20: removed pi r^2 60; the halves meet only along the lines y=20, z=0 and z=20: two solids", + "body_count": 2, + "volume_mm3": 29150.444078461 +} diff --git a/tests/kernel-cases/ladder/02-booleans/017-cylinder-tangent-top-and-bottom/recipe.json b/tests/kernel-cases/ladder/02-booleans/017-cylinder-tangent-top-and-bottom/recipe.json new file mode 100644 index 0000000000..3967da3708 --- /dev/null +++ b/tests/kernel-cases/ladder/02-booleans/017-cylinder-tangent-top-and-bottom/recipe.json @@ -0,0 +1,22 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.cylinder", + "params": { + "base": [ + -5, + 20, + 10 + ], + "radius": 10, + "height": 70, + "axis": "X", + "operation": "cut", + "targets": [ + "Part" + ] + } + } + ] +} diff --git a/tests/kernel-cases/ladder/02-booleans/018-hole-tangent-to-side-face/expected.json b/tests/kernel-cases/ladder/02-booleans/018-hole-tangent-to-side-face/expected.json new file mode 100644 index 0000000000..3511d82783 --- /dev/null +++ b/tests/kernel-cases/ladder/02-booleans/018-hole-tangent-to-side-face/expected.json @@ -0,0 +1,8 @@ +{ + "sources": [], + "what": "a through hole r10 tangent to the front face: the front face pinched to a line", + "source": "analytic", + "method": "the hole touches y=0 along the line x=30: removed pi r^2 20, the material behind keeps one body", + "body_count": 1, + "volume_mm3": 41716.81469282 +} diff --git a/tests/kernel-cases/ladder/02-booleans/018-hole-tangent-to-side-face/recipe.json b/tests/kernel-cases/ladder/02-booleans/018-hole-tangent-to-side-face/recipe.json new file mode 100644 index 0000000000..6bc8475497 --- /dev/null +++ b/tests/kernel-cases/ladder/02-booleans/018-hole-tangent-to-side-face/recipe.json @@ -0,0 +1,21 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.cylinder", + "params": { + "base": [ + 30, + 10, + -1 + ], + "radius": 10, + "height": 22, + "operation": "cut", + "targets": [ + "Part" + ] + } + } + ] +} diff --git a/tests/kernel-cases/ladder/02-booleans/019-notch-flush-with-end-face/expected.json b/tests/kernel-cases/ladder/02-booleans/019-notch-flush-with-end-face/expected.json new file mode 100644 index 0000000000..2145ae0dca --- /dev/null +++ b/tests/kernel-cases/ladder/02-booleans/019-notch-flush-with-end-face/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "a notch 40x20 through the height, its end flush with the stock's end face: a U", + "source": "analytic", + "method": "the tool's end face is x=60, the stock's: V = 48000 - 16000; the U outline has 8 sides, + top + bottom", + "body_count": 1, + "volume_mm3": 32000, + "faces": 10 +} diff --git a/tests/kernel-cases/ladder/02-booleans/019-notch-flush-with-end-face/recipe.json b/tests/kernel-cases/ladder/02-booleans/019-notch-flush-with-end-face/recipe.json new file mode 100644 index 0000000000..7b212c0c55 --- /dev/null +++ b/tests/kernel-cases/ladder/02-booleans/019-notch-flush-with-end-face/recipe.json @@ -0,0 +1,22 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.box", + "params": { + "corner": [ + 20, + 10, + -1 + ], + "length": 40, + "width": 20, + "height": 22, + "operation": "cut", + "targets": [ + "Part" + ] + } + } + ] +} diff --git a/tests/kernel-cases/ladder/02-booleans/020-part-back-wall/expected.json b/tests/kernel-cases/ladder/02-booleans/020-part-back-wall/expected.json new file mode 100644 index 0000000000..65e617c272 --- /dev/null +++ b/tests/kernel-cases/ladder/02-booleans/020-part-back-wall/expected.json @@ -0,0 +1,21 @@ +{ + "sources": [], + "what": "the running part: a 60x10x15 back wall joined on the stock's top, flush with its back and ends", + "source": "analytic", + "method": "an L section (6 sides) extruded along X: 8 faces; the coplanar back and end faces merge", + "body_count": 1, + "volume_mm3": 57000, + "faces": 8, + "bbox": { + "min": [ + 0, + 0, + 0 + ], + "max": [ + 60, + 40, + 35 + ] + } +} diff --git a/tests/kernel-cases/ladder/02-booleans/020-part-back-wall/recipe.json b/tests/kernel-cases/ladder/02-booleans/020-part-back-wall/recipe.json new file mode 100644 index 0000000000..d4f9eab390 --- /dev/null +++ b/tests/kernel-cases/ladder/02-booleans/020-part-back-wall/recipe.json @@ -0,0 +1,22 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.box", + "params": { + "corner": [ + 0, + 30, + 20 + ], + "length": 60, + "width": 10, + "height": 15, + "operation": "join", + "targets": [ + "Part" + ] + } + } + ] +} diff --git a/tests/kernel-cases/ladder/03-analytic-profiles/001-extrude-rect-with-arc-end/expected.json b/tests/kernel-cases/ladder/03-analytic-profiles/001-extrude-rect-with-arc-end/expected.json new file mode 100644 index 0000000000..46cd162cbd --- /dev/null +++ b/tests/kernel-cases/ladder/03-analytic-profiles/001-extrude-rect-with-arc-end/expected.json @@ -0,0 +1,10 @@ +{ + "sources": [], + "what": "a 60x40 profile with a semicircular end r20, extruded 20", + "source": "analytic", + "method": "A = 60*40 + pi 20^2 / 2; V = 20 A; sides: 3 planes and a half cylinder", + "body_count": 1, + "volume_mm3": 60566.370614359, + "area_mm2": 10513.274122872, + "faces": 6 +} diff --git a/tests/kernel-cases/ladder/03-analytic-profiles/001-extrude-rect-with-arc-end/recipe.json b/tests/kernel-cases/ladder/03-analytic-profiles/001-extrude-rect-with-arc-end/recipe.json new file mode 100644 index 0000000000..62cd1d765f --- /dev/null +++ b/tests/kernel-cases/ladder/03-analytic-profiles/001-extrude-rect-with-arc-end/recipe.json @@ -0,0 +1,60 @@ +{ + "commands": [ + { + "command": "sketch.create", + "params": { + "plane": "XY" + } + }, + { + "command": "sketch.line", + "params": { + "points": [ + [ + 60, + 40 + ], + [ + 0, + 40 + ], + [ + 0, + 0 + ], + [ + 60, + 0 + ] + ] + } + }, + { + "command": "sketch.arc.three_point", + "params": { + "start": [ + 60, + 0 + ], + "end": [ + 60, + 40 + ], + "through": [ + 80, + 20 + ] + } + }, + { + "command": "sketch.finish", + "params": {} + }, + { + "command": "solid.extrude", + "params": { + "distance": 20 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/03-analytic-profiles/002-extrude-annulus/expected.json b/tests/kernel-cases/ladder/03-analytic-profiles/002-extrude-annulus/expected.json new file mode 100644 index 0000000000..b028a76155 --- /dev/null +++ b/tests/kernel-cases/ladder/03-analytic-profiles/002-extrude-annulus/expected.json @@ -0,0 +1,10 @@ +{ + "sources": [], + "what": "an annulus r20/r10 extruded 15", + "source": "analytic", + "method": "V = pi (20^2 - 10^2) 15; faces: two annuli, two cylinders", + "body_count": 1, + "volume_mm3": 14137.166941154, + "area_mm2": 4712.388980385, + "faces": 4 +} diff --git a/tests/kernel-cases/ladder/03-analytic-profiles/002-extrude-annulus/recipe.json b/tests/kernel-cases/ladder/03-analytic-profiles/002-extrude-annulus/recipe.json new file mode 100644 index 0000000000..bc44e4393a --- /dev/null +++ b/tests/kernel-cases/ladder/03-analytic-profiles/002-extrude-annulus/recipe.json @@ -0,0 +1,48 @@ +{ + "commands": [ + { + "command": "sketch.create", + "params": { + "plane": "XY" + } + }, + { + "command": "sketch.circle.center", + "params": { + "center": [ + 0, + 0 + ], + "radius": 20 + } + }, + { + "command": "sketch.circle.center", + "params": { + "center": [ + 0, + 0 + ], + "radius": 10 + } + }, + { + "command": "sketch.finish", + "params": {} + }, + { + "command": "solid.extrude", + "params": { + "distance": 15, + "profiles": [ + { + "point": [ + 15, + 0 + ] + } + ] + } + } + ] +} diff --git a/tests/kernel-cases/ladder/03-analytic-profiles/003-extrude-tapered-circle/expected.json b/tests/kernel-cases/ladder/03-analytic-profiles/003-extrude-tapered-circle/expected.json new file mode 100644 index 0000000000..1ca25896fd --- /dev/null +++ b/tests/kernel-cases/ladder/03-analytic-profiles/003-extrude-tapered-circle/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "a circle r10 extruded 20 with a -5 deg taper (inward): a cone frustum", + "source": "analytic", + "method": "positive taper grows outward (tests/oracle/05): top radius 10 - 20 tan5 = 8.250226729; V = pi h/3 (R^2 + R r + r^2)", + "body_count": 1, + "volume_mm3": 5247.894567706, + "faces": 3 +} diff --git a/tests/kernel-cases/ladder/03-analytic-profiles/003-extrude-tapered-circle/recipe.json b/tests/kernel-cases/ladder/03-analytic-profiles/003-extrude-tapered-circle/recipe.json new file mode 100644 index 0000000000..bc75906be5 --- /dev/null +++ b/tests/kernel-cases/ladder/03-analytic-profiles/003-extrude-tapered-circle/recipe.json @@ -0,0 +1,31 @@ +{ + "commands": [ + { + "command": "sketch.create", + "params": { + "plane": "XY" + } + }, + { + "command": "sketch.circle.center", + "params": { + "center": [ + 0, + 0 + ], + "radius": 10 + } + }, + { + "command": "sketch.finish", + "params": {} + }, + { + "command": "solid.extrude", + "params": { + "distance": 20, + "taper": -5 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/03-analytic-profiles/004-revolve-full-off-axis/expected.json b/tests/kernel-cases/ladder/03-analytic-profiles/004-revolve-full-off-axis/expected.json new file mode 100644 index 0000000000..1b7c3dba11 --- /dev/null +++ b/tests/kernel-cases/ladder/03-analytic-profiles/004-revolve-full-off-axis/expected.json @@ -0,0 +1,10 @@ +{ + "sources": [], + "what": "a 10x30 rectangle 10 mm off the Z axis revolved 360 deg: a tube", + "source": "analytic", + "method": "V = pi (20^2 - 10^2) 30; faces: two annuli, two cylinders", + "body_count": 1, + "volume_mm3": 28274.333882308, + "area_mm2": 7539.822368616, + "faces": 4 +} diff --git a/tests/kernel-cases/ladder/03-analytic-profiles/004-revolve-full-off-axis/recipe.json b/tests/kernel-cases/ladder/03-analytic-profiles/004-revolve-full-off-axis/recipe.json new file mode 100644 index 0000000000..f343b556f4 --- /dev/null +++ b/tests/kernel-cases/ladder/03-analytic-profiles/004-revolve-full-off-axis/recipe.json @@ -0,0 +1,60 @@ +{ + "commands": [ + { + "command": "sketch.create", + "params": { + "plane": { + "origin": [ + 0, + 0, + 0 + ], + "x_dir": [ + 1, + 0, + 0 + ], + "y_dir": [ + 0, + 0, + 1 + ] + } + } + }, + { + "command": "sketch.line", + "params": { + "points": [ + [ + 10, + 0 + ], + [ + 20, + 0 + ], + [ + 20, + 30 + ], + [ + 10, + 30 + ] + ], + "closed": true + } + }, + { + "command": "sketch.finish", + "params": {} + }, + { + "command": "solid.revolve", + "params": { + "axis": "Z" + } + } + ] +} diff --git a/tests/kernel-cases/ladder/03-analytic-profiles/005-revolve-touching-axis-hemisphere/expected.json b/tests/kernel-cases/ladder/03-analytic-profiles/005-revolve-touching-axis-hemisphere/expected.json new file mode 100644 index 0000000000..71d16fce5d --- /dev/null +++ b/tests/kernel-cases/ladder/03-analytic-profiles/005-revolve-touching-axis-hemisphere/expected.json @@ -0,0 +1,10 @@ +{ + "sources": [], + "what": "a quarter disk r20 with an edge on the Z axis revolved 360 deg: a hemisphere", + "source": "analytic", + "method": "V = 2/3 pi r^3; A = 2 pi r^2 + pi r^2; the profile edge on the axis makes no face", + "body_count": 1, + "volume_mm3": 16755.160819146, + "area_mm2": 3769.911184308, + "faces": 2 +} diff --git a/tests/kernel-cases/ladder/03-analytic-profiles/005-revolve-touching-axis-hemisphere/recipe.json b/tests/kernel-cases/ladder/03-analytic-profiles/005-revolve-touching-axis-hemisphere/recipe.json new file mode 100644 index 0000000000..a4a64e54f8 --- /dev/null +++ b/tests/kernel-cases/ladder/03-analytic-profiles/005-revolve-touching-axis-hemisphere/recipe.json @@ -0,0 +1,72 @@ +{ + "commands": [ + { + "command": "sketch.create", + "params": { + "plane": { + "origin": [ + 0, + 0, + 0 + ], + "x_dir": [ + 1, + 0, + 0 + ], + "y_dir": [ + 0, + 0, + 1 + ] + } + } + }, + { + "command": "sketch.line", + "params": { + "points": [ + [ + 0, + 20 + ], + [ + 0, + 0 + ], + [ + 20, + 0 + ] + ] + } + }, + { + "command": "sketch.arc.center_point", + "params": { + "center": [ + 0, + 0 + ], + "start": [ + 20, + 0 + ], + "end": [ + 0, + 20 + ] + } + }, + { + "command": "sketch.finish", + "params": {} + }, + { + "command": "solid.revolve", + "params": { + "axis": "Z" + } + } + ] +} diff --git a/tests/kernel-cases/ladder/03-analytic-profiles/006-revolve-partial-90/expected.json b/tests/kernel-cases/ladder/03-analytic-profiles/006-revolve-partial-90/expected.json new file mode 100644 index 0000000000..a8076ffe82 --- /dev/null +++ b/tests/kernel-cases/ladder/03-analytic-profiles/006-revolve-partial-90/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "a 10x10 rectangle 10 mm off the axis revolved 90 deg", + "source": "analytic", + "method": "a quarter of the tube: V = (90/360) pi (20^2 - 10^2) 10; faces: 2 ends, 2 cylinders, top, bottom", + "body_count": 1, + "volume_mm3": 2356.194490192, + "faces": 6 +} diff --git a/tests/kernel-cases/ladder/03-analytic-profiles/006-revolve-partial-90/recipe.json b/tests/kernel-cases/ladder/03-analytic-profiles/006-revolve-partial-90/recipe.json new file mode 100644 index 0000000000..0646a720d9 --- /dev/null +++ b/tests/kernel-cases/ladder/03-analytic-profiles/006-revolve-partial-90/recipe.json @@ -0,0 +1,61 @@ +{ + "commands": [ + { + "command": "sketch.create", + "params": { + "plane": { + "origin": [ + 0, + 0, + 0 + ], + "x_dir": [ + 1, + 0, + 0 + ], + "y_dir": [ + 0, + 0, + 1 + ] + } + } + }, + { + "command": "sketch.line", + "params": { + "points": [ + [ + 10, + 0 + ], + [ + 20, + 0 + ], + [ + 20, + 10 + ], + [ + 10, + 10 + ] + ], + "closed": true + } + }, + { + "command": "sketch.finish", + "params": {} + }, + { + "command": "solid.revolve", + "params": { + "axis": "Z", + "angle": 90 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/03-analytic-profiles/007-revolve-180-touching-axis/expected.json b/tests/kernel-cases/ladder/03-analytic-profiles/007-revolve-180-touching-axis/expected.json new file mode 100644 index 0000000000..16aeb5283c --- /dev/null +++ b/tests/kernel-cases/ladder/03-analytic-profiles/007-revolve-180-touching-axis/expected.json @@ -0,0 +1,10 @@ +{ + "sources": [], + "what": "a 15x20 rectangle on the axis revolved 180 deg: a half cylinder", + "source": "analytic", + "method": "V = pi r^2 h / 2; the two end rectangles are coplanar and meet on the axis: one 30x20 face; + half disks top and bottom + half cylinder", + "body_count": 1, + "volume_mm3": 7068.583470577, + "area_mm2": 2249.336143135, + "faces": 4 +} diff --git a/tests/kernel-cases/ladder/03-analytic-profiles/007-revolve-180-touching-axis/recipe.json b/tests/kernel-cases/ladder/03-analytic-profiles/007-revolve-180-touching-axis/recipe.json new file mode 100644 index 0000000000..1305704708 --- /dev/null +++ b/tests/kernel-cases/ladder/03-analytic-profiles/007-revolve-180-touching-axis/recipe.json @@ -0,0 +1,61 @@ +{ + "commands": [ + { + "command": "sketch.create", + "params": { + "plane": { + "origin": [ + 0, + 0, + 0 + ], + "x_dir": [ + 1, + 0, + 0 + ], + "y_dir": [ + 0, + 0, + 1 + ] + } + } + }, + { + "command": "sketch.line", + "params": { + "points": [ + [ + 0, + 0 + ], + [ + 15, + 0 + ], + [ + 15, + 20 + ], + [ + 0, + 20 + ] + ], + "closed": true + } + }, + { + "command": "sketch.finish", + "params": {} + }, + { + "command": "solid.revolve", + "params": { + "axis": "Z", + "angle": 180 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/03-analytic-profiles/008-extrude-slot/expected.json b/tests/kernel-cases/ladder/03-analytic-profiles/008-extrude-slot/expected.json new file mode 100644 index 0000000000..0930aa71b9 --- /dev/null +++ b/tests/kernel-cases/ladder/03-analytic-profiles/008-extrude-slot/expected.json @@ -0,0 +1,10 @@ +{ + "sources": [], + "what": "a slot (centres 40 apart, width 20) extruded 10", + "source": "analytic", + "method": "A = 40*20 + pi 10^2; faces: 2 planes, 2 half cylinders, top, bottom", + "body_count": 1, + "volume_mm3": 11141.59265359, + "area_mm2": 3656.637061436, + "faces": 6 +} diff --git a/tests/kernel-cases/ladder/03-analytic-profiles/008-extrude-slot/recipe.json b/tests/kernel-cases/ladder/03-analytic-profiles/008-extrude-slot/recipe.json new file mode 100644 index 0000000000..8dddd4606f --- /dev/null +++ b/tests/kernel-cases/ladder/03-analytic-profiles/008-extrude-slot/recipe.json @@ -0,0 +1,34 @@ +{ + "commands": [ + { + "command": "sketch.create", + "params": { + "plane": "XY" + } + }, + { + "command": "sketch.slot.center_to_center", + "params": { + "p0": [ + 0, + 0 + ], + "p1": [ + 40, + 0 + ], + "width": 20 + } + }, + { + "command": "sketch.finish", + "params": {} + }, + { + "command": "solid.extrude", + "params": { + "distance": 10 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/03-analytic-profiles/009-part-boss-and-slot/expected.json b/tests/kernel-cases/ladder/03-analytic-profiles/009-part-boss-and-slot/expected.json new file mode 100644 index 0000000000..04a585c6ec --- /dev/null +++ b/tests/kernel-cases/ladder/03-analytic-profiles/009-part-boss-and-slot/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "the running part: a revolved boss r8 h10 joined on the top, a slot 15x8 cut through the base", + "source": "analytic", + "method": "back-wall part (57000) + pi 8^2 10 - 20 (15*8 + pi 4^2); faces 8 + boss side and top + slot's 2 planes and 2 half cylinders", + "body_count": 1, + "volume_mm3": 55605.309649149, + "faces": 14 +} diff --git a/tests/kernel-cases/ladder/03-analytic-profiles/009-part-boss-and-slot/recipe.json b/tests/kernel-cases/ladder/03-analytic-profiles/009-part-boss-and-slot/recipe.json new file mode 100644 index 0000000000..e3a9c72690 --- /dev/null +++ b/tests/kernel-cases/ladder/03-analytic-profiles/009-part-boss-and-slot/recipe.json @@ -0,0 +1,107 @@ +{ + "base": "../../02-booleans/020-part-back-wall", + "commands": [ + { + "command": "sketch.create", + "params": { + "plane": { + "origin": [ + 15, + 15, + 0 + ], + "x_dir": [ + 1, + 0, + 0 + ], + "y_dir": [ + 0, + 0, + 1 + ] + } + } + }, + { + "command": "sketch.line", + "params": { + "points": [ + [ + 0, + 20 + ], + [ + 8, + 20 + ], + [ + 8, + 30 + ], + [ + 0, + 30 + ] + ], + "ids": [ + "b", + "s", + "t", + "ax" + ], + "closed": true + } + }, + { + "command": "sketch.finish", + "params": {} + }, + { + "command": "solid.revolve", + "params": { + "axis": "ax", + "operation": "join", + "targets": [ + "Part" + ], + "name": "Boss" + } + }, + { + "command": "sketch.create", + "params": { + "plane": "XY" + } + }, + { + "command": "sketch.slot.center_to_center", + "params": { + "p0": [ + 35, + 10 + ], + "p1": [ + 50, + 10 + ], + "width": 8 + } + }, + { + "command": "sketch.finish", + "params": {} + }, + { + "command": "solid.extrude", + "params": { + "distance": 20, + "operation": "cut", + "targets": [ + "Part" + ], + "name": "Slot" + } + } + ] +} diff --git a/tests/kernel-cases/ladder/04-spline-profiles/001-extrude-bezier-arch/expected.json b/tests/kernel-cases/ladder/04-spline-profiles/001-extrude-bezier-arch/expected.json new file mode 100644 index 0000000000..aa57404538 --- /dev/null +++ b/tests/kernel-cases/ladder/04-spline-profiles/001-extrude-bezier-arch/expected.json @@ -0,0 +1,21 @@ +{ + "sources": [], + "what": "a profile closed by a cubic Bezier arch (control-point spline, 4 points), extruded 10", + "source": "analytic", + "method": "a 4-point cubic control-point spline is one Bezier segment; A = 400 + int (y-10) dx over the arch (Green, exact Gauss) = 820.000000000; max y = 10 + 0.75*20", + "body_count": 1, + "volume_mm3": 8200.0, + "faces": 6, + "bbox": { + "min": [ + 0, + 0, + 0 + ], + "max": [ + 40, + 25, + 10 + ] + } +} diff --git a/tests/kernel-cases/ladder/04-spline-profiles/001-extrude-bezier-arch/recipe.json b/tests/kernel-cases/ladder/04-spline-profiles/001-extrude-bezier-arch/recipe.json new file mode 100644 index 0000000000..3dd959a439 --- /dev/null +++ b/tests/kernel-cases/ladder/04-spline-profiles/001-extrude-bezier-arch/recipe.json @@ -0,0 +1,66 @@ +{ + "commands": [ + { + "command": "sketch.create", + "params": { + "plane": "XY" + } + }, + { + "command": "sketch.line", + "params": { + "points": [ + [ + 0, + 10 + ], + [ + 0, + 0 + ], + [ + 40, + 0 + ], + [ + 40, + 10 + ] + ] + } + }, + { + "command": "sketch.spline.control_point", + "params": { + "points": [ + [ + 40, + 10 + ], + [ + 30, + 30 + ], + [ + 10, + 30 + ], + [ + 0, + 10 + ] + ] + } + }, + { + "command": "sketch.finish", + "params": {} + }, + { + "command": "solid.extrude", + "params": { + "distance": 10 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/04-spline-profiles/002-revolve-bezier-profile/expected.json b/tests/kernel-cases/ladder/04-spline-profiles/002-revolve-bezier-profile/expected.json new file mode 100644 index 0000000000..58fd5fc39d --- /dev/null +++ b/tests/kernel-cases/ladder/04-spline-profiles/002-revolve-bezier-profile/expected.json @@ -0,0 +1,10 @@ +{ + "sources": [], + "what": "a profile with a Bezier side (r from 20 to 15 over z 0..30) revolved 360 deg about Z", + "source": "analytic", + "method": "z(t) = 30 t is linear, so V = pi int r(t)^2 z'(t) dt (exact Gauss) = 34131.160186501; A = disks pi 20^2 + pi 15^2 + 2 pi int r |B'| dt", + "body_count": 1, + "volume_mm3": 34131.160186501, + "area_mm2": 5856.98245447, + "faces": 3 +} diff --git a/tests/kernel-cases/ladder/04-spline-profiles/002-revolve-bezier-profile/recipe.json b/tests/kernel-cases/ladder/04-spline-profiles/002-revolve-bezier-profile/recipe.json new file mode 100644 index 0000000000..5ba63e10aa --- /dev/null +++ b/tests/kernel-cases/ladder/04-spline-profiles/002-revolve-bezier-profile/recipe.json @@ -0,0 +1,82 @@ +{ + "commands": [ + { + "command": "sketch.create", + "params": { + "plane": { + "origin": [ + 0, + 0, + 0 + ], + "x_dir": [ + 1, + 0, + 0 + ], + "y_dir": [ + 0, + 0, + 1 + ] + } + } + }, + { + "command": "sketch.line", + "params": { + "points": [ + [ + 15, + 30 + ], + [ + 0, + 30 + ], + [ + 0, + 0 + ], + [ + 20, + 0 + ] + ] + } + }, + { + "command": "sketch.spline.control_point", + "params": { + "points": [ + [ + 20, + 0 + ], + [ + 30, + 10 + ], + [ + 10, + 20 + ], + [ + 15, + 30 + ] + ] + } + }, + { + "command": "sketch.finish", + "params": {} + }, + { + "command": "solid.revolve", + "params": { + "axis": "Z" + } + } + ] +} diff --git a/tests/kernel-cases/ladder/04-spline-profiles/003-pipe-along-bezier/expected.json b/tests/kernel-cases/ladder/04-spline-profiles/003-pipe-along-bezier/expected.json new file mode 100644 index 0000000000..acba21b1ea --- /dev/null +++ b/tests/kernel-cases/ladder/04-spline-profiles/003-pipe-along-bezier/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "a pipe d4 along a planar Bezier path", + "source": "analytic", + "method": "a disk swept square to a planar path whose curvature radius stays above 20.85 > 2: V = pi r^2 L, A = 2 pi r L + 2 pi r^2, L = 46.220575543", + "body_count": 1, + "volume_mm3": 580.824882281, + "area_mm2": 605.95762351 +} diff --git a/tests/kernel-cases/ladder/04-spline-profiles/003-pipe-along-bezier/recipe.json b/tests/kernel-cases/ladder/04-spline-profiles/003-pipe-along-bezier/recipe.json new file mode 100644 index 0000000000..9a5082db18 --- /dev/null +++ b/tests/kernel-cases/ladder/04-spline-profiles/003-pipe-along-bezier/recipe.json @@ -0,0 +1,64 @@ +{ + "commands": [ + { + "command": "sketch.create", + "params": { + "plane": { + "origin": [ + 0, + 0, + 0 + ], + "x_dir": [ + 1, + 0, + 0 + ], + "y_dir": [ + 0, + 0, + 1 + ] + }, + "name": "Path" + } + }, + { + "command": "sketch.spline.control_point", + "params": { + "points": [ + [ + 0, + 0 + ], + [ + 0, + 20 + ], + [ + 20, + 20 + ], + [ + 20, + 40 + ] + ] + } + }, + { + "command": "sketch.finish", + "params": {} + }, + { + "command": "solid.pipe", + "params": { + "path_sketch": "Path", + "path": [ + "s1" + ], + "diameter": 4 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/04-spline-profiles/004-sweep-square-along-bezier/expected.json b/tests/kernel-cases/ladder/04-spline-profiles/004-sweep-square-along-bezier/expected.json new file mode 100644 index 0000000000..57f6a23e2a --- /dev/null +++ b/tests/kernel-cases/ladder/04-spline-profiles/004-sweep-square-along-bezier/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "a 4x4 square swept along the same Bezier path", + "source": "analytic", + "method": "the square's centre is on the planar path and it stays square to it: V = A L; side faces: the inner and outer walls have s(L -/+ a dtheta), the two others s L each, total 4 s L; + 2 ends", + "body_count": 1, + "volume_mm3": 739.529208687, + "area_mm2": 771.529208687 +} diff --git a/tests/kernel-cases/ladder/04-spline-profiles/004-sweep-square-along-bezier/recipe.json b/tests/kernel-cases/ladder/04-spline-profiles/004-sweep-square-along-bezier/recipe.json new file mode 100644 index 0000000000..39eb310a7f --- /dev/null +++ b/tests/kernel-cases/ladder/04-spline-profiles/004-sweep-square-along-bezier/recipe.json @@ -0,0 +1,88 @@ +{ + "commands": [ + { + "command": "sketch.create", + "params": { + "plane": { + "origin": [ + 0, + 0, + 0 + ], + "x_dir": [ + 1, + 0, + 0 + ], + "y_dir": [ + 0, + 0, + 1 + ] + }, + "name": "Path" + } + }, + { + "command": "sketch.spline.control_point", + "params": { + "points": [ + [ + 0, + 0 + ], + [ + 0, + 20 + ], + [ + 20, + 20 + ], + [ + 20, + 40 + ] + ] + } + }, + { + "command": "sketch.finish", + "params": {} + }, + { + "command": "sketch.create", + "params": { + "plane": "XY", + "name": "Square" + } + }, + { + "command": "sketch.rectangle.center", + "params": { + "center": [ + 0, + 0 + ], + "corner": [ + 2, + 2 + ] + } + }, + { + "command": "sketch.finish", + "params": {} + }, + { + "command": "solid.sweep", + "params": { + "sketch": "Square", + "path_sketch": "Path", + "path": [ + "s1" + ] + } + } + ] +} diff --git a/tests/kernel-cases/ladder/04-spline-profiles/005-loft-square-frustum/expected.json b/tests/kernel-cases/ladder/04-spline-profiles/005-loft-square-frustum/expected.json new file mode 100644 index 0000000000..b2c13fd55d --- /dev/null +++ b/tests/kernel-cases/ladder/04-spline-profiles/005-loft-square-frustum/expected.json @@ -0,0 +1,10 @@ +{ + "sources": [], + "what": "a loft between a 40 square at z=0 and a 20 square at z=30", + "source": "analytic", + "method": "two sections loft ruled: a square frustum, V = h/3 (A0 + A1 + sqrt(A0 A1)); sides 4 trapezoids (40+20)/2 * sqrt(30^2+10^2)", + "body_count": 1, + "volume_mm3": 28000.0, + "area_mm2": 5794.733192202, + "faces": 6 +} diff --git a/tests/kernel-cases/ladder/04-spline-profiles/005-loft-square-frustum/recipe.json b/tests/kernel-cases/ladder/04-spline-profiles/005-loft-square-frustum/recipe.json new file mode 100644 index 0000000000..cdaabccb20 --- /dev/null +++ b/tests/kernel-cases/ladder/04-spline-profiles/005-loft-square-frustum/recipe.json @@ -0,0 +1,81 @@ +{ + "commands": [ + { + "command": "sketch.create", + "params": { + "plane": "XY", + "name": "S0" + } + }, + { + "command": "sketch.rectangle.center", + "params": { + "center": [ + 0, + 0 + ], + "corner": [ + 20, + 20 + ] + } + }, + { + "command": "sketch.finish", + "params": {} + }, + { + "command": "sketch.create", + "params": { + "plane": { + "origin": [ + 0, + 0, + 30 + ], + "x_dir": [ + 1, + 0, + 0 + ], + "y_dir": [ + 0, + 1, + 0 + ] + }, + "name": "S1" + } + }, + { + "command": "sketch.rectangle.center", + "params": { + "center": [ + 0, + 0 + ], + "corner": [ + 10, + 10 + ] + } + }, + { + "command": "sketch.finish", + "params": {} + }, + { + "command": "solid.loft", + "params": { + "sections": [ + { + "sketch": "S0" + }, + { + "sketch": "S1" + } + ] + } + } + ] +} diff --git a/tests/kernel-cases/ladder/04-spline-profiles/006-loft-circle-frustum/expected.json b/tests/kernel-cases/ladder/04-spline-profiles/006-loft-circle-frustum/expected.json new file mode 100644 index 0000000000..4555a79043 --- /dev/null +++ b/tests/kernel-cases/ladder/04-spline-profiles/006-loft-circle-frustum/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "a loft between circles r20 at z=0 and r10 at z=30", + "source": "analytic", + "method": "a ruled loft between coaxial circles is a cone frustum: V = pi h/3 (R^2 + R r + r^2)", + "body_count": 1, + "volume_mm3": 21991.148575129, + "faces": 3 +} diff --git a/tests/kernel-cases/ladder/04-spline-profiles/006-loft-circle-frustum/recipe.json b/tests/kernel-cases/ladder/04-spline-profiles/006-loft-circle-frustum/recipe.json new file mode 100644 index 0000000000..aee8d1912f --- /dev/null +++ b/tests/kernel-cases/ladder/04-spline-profiles/006-loft-circle-frustum/recipe.json @@ -0,0 +1,75 @@ +{ + "commands": [ + { + "command": "sketch.create", + "params": { + "plane": "XY", + "name": "S0" + } + }, + { + "command": "sketch.circle.center", + "params": { + "center": [ + 0, + 0 + ], + "radius": 20 + } + }, + { + "command": "sketch.finish", + "params": {} + }, + { + "command": "sketch.create", + "params": { + "plane": { + "origin": [ + 0, + 0, + 30 + ], + "x_dir": [ + 1, + 0, + 0 + ], + "y_dir": [ + 0, + 1, + 0 + ] + }, + "name": "S1" + } + }, + { + "command": "sketch.circle.center", + "params": { + "center": [ + 0, + 0 + ], + "radius": 10 + } + }, + { + "command": "sketch.finish", + "params": {} + }, + { + "command": "solid.loft", + "params": { + "sections": [ + { + "sketch": "S0" + }, + { + "sketch": "S1" + } + ] + } + } + ] +} diff --git a/tests/kernel-cases/ladder/04-spline-profiles/007-loft-three-circles/expected.json b/tests/kernel-cases/ladder/04-spline-profiles/007-loft-three-circles/expected.json new file mode 100644 index 0000000000..cd27d05f2d --- /dev/null +++ b/tests/kernel-cases/ladder/04-spline-profiles/007-loft-three-circles/expected.json @@ -0,0 +1,8 @@ +{ + "sources": [], + "what": "a smooth loft through circles r20, r10, r20 at z = 0, 20, 40", + "source": "invariants", + "method": "the interpolation between sections is the loft's own: no closed-form volume; one valid body bounded by two disks and one lofted face", + "body_count": 1, + "faces": 3 +} diff --git a/tests/kernel-cases/ladder/04-spline-profiles/007-loft-three-circles/recipe.json b/tests/kernel-cases/ladder/04-spline-profiles/007-loft-three-circles/recipe.json new file mode 100644 index 0000000000..a7985ad3da --- /dev/null +++ b/tests/kernel-cases/ladder/04-spline-profiles/007-loft-three-circles/recipe.json @@ -0,0 +1,115 @@ +{ + "commands": [ + { + "command": "sketch.create", + "params": { + "plane": "XY", + "name": "S0" + } + }, + { + "command": "sketch.circle.center", + "params": { + "center": [ + 0, + 0 + ], + "radius": 20 + } + }, + { + "command": "sketch.finish", + "params": {} + }, + { + "command": "sketch.create", + "params": { + "plane": { + "origin": [ + 0, + 0, + 20 + ], + "x_dir": [ + 1, + 0, + 0 + ], + "y_dir": [ + 0, + 1, + 0 + ] + }, + "name": "S1" + } + }, + { + "command": "sketch.circle.center", + "params": { + "center": [ + 0, + 0 + ], + "radius": 10 + } + }, + { + "command": "sketch.finish", + "params": {} + }, + { + "command": "sketch.create", + "params": { + "plane": { + "origin": [ + 0, + 0, + 40 + ], + "x_dir": [ + 1, + 0, + 0 + ], + "y_dir": [ + 0, + 1, + 0 + ] + }, + "name": "S2" + } + }, + { + "command": "sketch.circle.center", + "params": { + "center": [ + 0, + 0 + ], + "radius": 20 + } + }, + { + "command": "sketch.finish", + "params": {} + }, + { + "command": "solid.loft", + "params": { + "sections": [ + { + "sketch": "S0" + }, + { + "sketch": "S1" + }, + { + "sketch": "S2" + } + ] + } + } + ] +} diff --git a/tests/kernel-cases/ladder/04-spline-profiles/008-loft-square-to-circle/expected.json b/tests/kernel-cases/ladder/04-spline-profiles/008-loft-square-to-circle/expected.json new file mode 100644 index 0000000000..f49090a7cb --- /dev/null +++ b/tests/kernel-cases/ladder/04-spline-profiles/008-loft-square-to-circle/expected.json @@ -0,0 +1,7 @@ +{ + "sources": [], + "what": "a loft from a 40 square at z=0 to a circle r10 at z=30", + "source": "invariants", + "method": "sections with different edge counts: the matching is the loft's own; one valid body", + "body_count": 1 +} diff --git a/tests/kernel-cases/ladder/04-spline-profiles/008-loft-square-to-circle/recipe.json b/tests/kernel-cases/ladder/04-spline-profiles/008-loft-square-to-circle/recipe.json new file mode 100644 index 0000000000..09597853de --- /dev/null +++ b/tests/kernel-cases/ladder/04-spline-profiles/008-loft-square-to-circle/recipe.json @@ -0,0 +1,78 @@ +{ + "commands": [ + { + "command": "sketch.create", + "params": { + "plane": "XY", + "name": "S0" + } + }, + { + "command": "sketch.rectangle.center", + "params": { + "center": [ + 0, + 0 + ], + "corner": [ + 20, + 20 + ] + } + }, + { + "command": "sketch.finish", + "params": {} + }, + { + "command": "sketch.create", + "params": { + "plane": { + "origin": [ + 0, + 0, + 30 + ], + "x_dir": [ + 1, + 0, + 0 + ], + "y_dir": [ + 0, + 1, + 0 + ] + }, + "name": "S1" + } + }, + { + "command": "sketch.circle.center", + "params": { + "center": [ + 0, + 0 + ], + "radius": 10 + } + }, + { + "command": "sketch.finish", + "params": {} + }, + { + "command": "solid.loft", + "params": { + "sections": [ + { + "sketch": "S0" + }, + { + "sketch": "S1" + } + ] + } + } + ] +} diff --git a/tests/kernel-cases/ladder/04-spline-profiles/009-extrude-periodic-fit-spline/expected.json b/tests/kernel-cases/ladder/04-spline-profiles/009-extrude-periodic-fit-spline/expected.json new file mode 100644 index 0000000000..99d482ee20 --- /dev/null +++ b/tests/kernel-cases/ladder/04-spline-profiles/009-extrude-periodic-fit-spline/expected.json @@ -0,0 +1,7 @@ +{ + "sources": [], + "what": "a closed periodic fit-point spline through 6 points, extruded 10", + "source": "invariants", + "method": "the fit's parametrisation is the spline's own: no closed-form area, and how many side faces a periodic spline makes is the kernel's own; one valid body", + "body_count": 1 +} diff --git a/tests/kernel-cases/ladder/04-spline-profiles/009-extrude-periodic-fit-spline/recipe.json b/tests/kernel-cases/ladder/04-spline-profiles/009-extrude-periodic-fit-spline/recipe.json new file mode 100644 index 0000000000..8a1ca13a34 --- /dev/null +++ b/tests/kernel-cases/ladder/04-spline-profiles/009-extrude-periodic-fit-spline/recipe.json @@ -0,0 +1,52 @@ +{ + "commands": [ + { + "command": "sketch.create", + "params": { + "plane": "XY" + } + }, + { + "command": "sketch.spline.fit_point", + "params": { + "points": [ + [ + 20, + 0 + ], + [ + 10, + 17 + ], + [ + -10, + 17 + ], + [ + -20, + 0 + ], + [ + -10, + -17 + ], + [ + 10, + -17 + ] + ], + "periodic": true + } + }, + { + "command": "sketch.finish", + "params": {} + }, + { + "command": "solid.extrude", + "params": { + "distance": 10 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/04-spline-profiles/010-part-bezier-window/expected.json b/tests/kernel-cases/ladder/04-spline-profiles/010-part-bezier-window/expected.json new file mode 100644 index 0000000000..94792737c3 --- /dev/null +++ b/tests/kernel-cases/ladder/04-spline-profiles/010-part-bezier-window/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "the running part: a Bezier-arched window cut through the back wall", + "source": "analytic", + "method": "the arch over z=22 from x=20 to 40 rises to z = 22 + 0.75*12 = 31, under the wall top (35): removed 10 * 133.200000000; + window floor and arch faces", + "body_count": 1, + "volume_mm3": 54273.309649149, + "faces": 16 +} diff --git a/tests/kernel-cases/ladder/04-spline-profiles/010-part-bezier-window/recipe.json b/tests/kernel-cases/ladder/04-spline-profiles/010-part-bezier-window/recipe.json new file mode 100644 index 0000000000..7b4e8a799d --- /dev/null +++ b/tests/kernel-cases/ladder/04-spline-profiles/010-part-bezier-window/recipe.json @@ -0,0 +1,80 @@ +{ + "base": "../../03-analytic-profiles/009-part-boss-and-slot", + "commands": [ + { + "command": "sketch.create", + "params": { + "plane": { + "origin": [ + 0, + 40, + 0 + ], + "x_dir": [ + 1, + 0, + 0 + ], + "y_dir": [ + 0, + 0, + 1 + ] + } + } + }, + { + "command": "sketch.line", + "params": { + "points": [ + [ + 20, + 22 + ], + [ + 40, + 22 + ] + ] + } + }, + { + "command": "sketch.spline.control_point", + "params": { + "points": [ + [ + 40, + 22 + ], + [ + 37, + 34 + ], + [ + 23, + 34 + ], + [ + 20, + 22 + ] + ] + } + }, + { + "command": "sketch.finish", + "params": {} + }, + { + "command": "solid.extrude", + "params": { + "distance": 10, + "operation": "cut", + "targets": [ + "Part" + ], + "name": "Window" + } + } + ] +} diff --git a/tests/kernel-cases/ladder/05-holes-and-taps/001-simple-through/expected.json b/tests/kernel-cases/ladder/05-holes-and-taps/001-simple-through/expected.json new file mode 100644 index 0000000000..fbfc67c4ab --- /dev/null +++ b/tests/kernel-cases/ladder/05-holes-and-taps/001-simple-through/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "a d10 through hole in the stock's top", + "source": "analytic", + "method": "removed pi 5^2 20; + one cylinder face", + "body_count": 1, + "volume_mm3": 46429.203673205, + "faces": 7 +} diff --git a/tests/kernel-cases/ladder/05-holes-and-taps/001-simple-through/recipe.json b/tests/kernel-cases/ladder/05-holes-and-taps/001-simple-through/recipe.json new file mode 100644 index 0000000000..0174b9dfae --- /dev/null +++ b/tests/kernel-cases/ladder/05-holes-and-taps/001-simple-through/recipe.json @@ -0,0 +1,16 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.hole", + "params": { + "position": [ + 30, + 20, + 20 + ], + "diameter": 10 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/05-holes-and-taps/002-simple-blind-flat/expected.json b/tests/kernel-cases/ladder/05-holes-and-taps/002-simple-blind-flat/expected.json new file mode 100644 index 0000000000..a62c7155e7 --- /dev/null +++ b/tests/kernel-cases/ladder/05-holes-and-taps/002-simple-blind-flat/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "a d10 blind hole 10 deep, flat bottom", + "source": "analytic", + "method": "removed pi 5^2 10; + wall and floor", + "body_count": 1, + "volume_mm3": 47214.601836603, + "faces": 8 +} diff --git a/tests/kernel-cases/ladder/05-holes-and-taps/002-simple-blind-flat/recipe.json b/tests/kernel-cases/ladder/05-holes-and-taps/002-simple-blind-flat/recipe.json new file mode 100644 index 0000000000..6ccac3b40f --- /dev/null +++ b/tests/kernel-cases/ladder/05-holes-and-taps/002-simple-blind-flat/recipe.json @@ -0,0 +1,17 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.hole", + "params": { + "position": [ + 30, + 20, + 20 + ], + "diameter": 10, + "depth": 10 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/05-holes-and-taps/003-drilled-blind-118/expected.json b/tests/kernel-cases/ladder/05-holes-and-taps/003-drilled-blind-118/expected.json new file mode 100644 index 0000000000..a9dbff77db --- /dev/null +++ b/tests/kernel-cases/ladder/05-holes-and-taps/003-drilled-blind-118/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "a d10 drilled hole 10 deep to the shoulder with a 118 deg point", + "source": "analytic", + "method": "depth to the shoulder (tests/oracle/18); point cone h = r / tan59; removed pi r^2 10 + pi r^2 h/3; + wall and cone", + "body_count": 1, + "volume_mm3": 47135.949365496, + "faces": 8 +} diff --git a/tests/kernel-cases/ladder/05-holes-and-taps/003-drilled-blind-118/recipe.json b/tests/kernel-cases/ladder/05-holes-and-taps/003-drilled-blind-118/recipe.json new file mode 100644 index 0000000000..d0dff35023 --- /dev/null +++ b/tests/kernel-cases/ladder/05-holes-and-taps/003-drilled-blind-118/recipe.json @@ -0,0 +1,19 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.hole", + "params": { + "position": [ + 30, + 20, + 20 + ], + "diameter": 10, + "depth": 10, + "type": "drilled", + "tip_angle": 118 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/05-holes-and-taps/004-counterbore/expected.json b/tests/kernel-cases/ladder/05-holes-and-taps/004-counterbore/expected.json new file mode 100644 index 0000000000..2af87323d1 --- /dev/null +++ b/tests/kernel-cases/ladder/05-holes-and-taps/004-counterbore/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "a counterbored hole: d6.6 through, d11 x 6.4 counterbore", + "source": "analytic", + "method": "removed pi 3.3^2 (20 - 6.4) + pi 5.5^2 6.4; + bore, counterbore wall, its floor annulus", + "body_count": 1, + "volume_mm3": 46926.505223898, + "faces": 9 +} diff --git a/tests/kernel-cases/ladder/05-holes-and-taps/004-counterbore/recipe.json b/tests/kernel-cases/ladder/05-holes-and-taps/004-counterbore/recipe.json new file mode 100644 index 0000000000..2dab94f8df --- /dev/null +++ b/tests/kernel-cases/ladder/05-holes-and-taps/004-counterbore/recipe.json @@ -0,0 +1,19 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.hole", + "params": { + "position": [ + 30, + 20, + 20 + ], + "diameter": 6.6, + "type": "counterbore", + "cb_diameter": 11, + "cb_depth": 6.4 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/05-holes-and-taps/005-countersink/expected.json b/tests/kernel-cases/ladder/05-holes-and-taps/005-countersink/expected.json new file mode 100644 index 0000000000..0a58eb6b2a --- /dev/null +++ b/tests/kernel-cases/ladder/05-holes-and-taps/005-countersink/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "a countersunk hole: d6.6 through, d12 x 90 deg countersink", + "source": "analytic", + "method": "countersink frustum r6 -> r3.3 over (6-3.3)/tan45 = 2.7; + bore below it; + cone and bore faces", + "body_count": 1, + "volume_mm3": 47219.571836181, + "faces": 8 +} diff --git a/tests/kernel-cases/ladder/05-holes-and-taps/005-countersink/recipe.json b/tests/kernel-cases/ladder/05-holes-and-taps/005-countersink/recipe.json new file mode 100644 index 0000000000..b832a0ff0d --- /dev/null +++ b/tests/kernel-cases/ladder/05-holes-and-taps/005-countersink/recipe.json @@ -0,0 +1,19 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.hole", + "params": { + "position": [ + 30, + 20, + 20 + ], + "diameter": 6.6, + "type": "countersink", + "cs_diameter": 12, + "cs_angle": 90 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/05-holes-and-taps/006-tapped-m6-through/expected.json b/tests/kernel-cases/ladder/05-holes-and-taps/006-tapped-m6-through/expected.json new file mode 100644 index 0000000000..028f4fa6a9 --- /dev/null +++ b/tests/kernel-cases/ladder/05-holes-and-taps/006-tapped-m6-through/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "an M6 tapped hole through: the tap drill d5 (major - pitch), cosmetic thread", + "source": "analytic", + "method": "M6 coarse pitch 1: tap drill 6 - 1 = 5; removed pi 2.5^2 20; the cosmetic thread adds no geometry", + "body_count": 1, + "volume_mm3": 47607.300918301, + "faces": 7 +} diff --git a/tests/kernel-cases/ladder/05-holes-and-taps/006-tapped-m6-through/recipe.json b/tests/kernel-cases/ladder/05-holes-and-taps/006-tapped-m6-through/recipe.json new file mode 100644 index 0000000000..843006b44e --- /dev/null +++ b/tests/kernel-cases/ladder/05-holes-and-taps/006-tapped-m6-through/recipe.json @@ -0,0 +1,16 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.hole", + "params": { + "position": [ + 30, + 20, + 20 + ], + "thread": "M6" + } + } + ] +} diff --git a/tests/kernel-cases/ladder/05-holes-and-taps/007-tapped-m8-blind/expected.json b/tests/kernel-cases/ladder/05-holes-and-taps/007-tapped-m8-blind/expected.json new file mode 100644 index 0000000000..c2569389e0 --- /dev/null +++ b/tests/kernel-cases/ladder/05-holes-and-taps/007-tapped-m8-blind/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "an M8 tapped blind hole 12 deep with a 118 deg drill point", + "source": "analytic", + "method": "M8 coarse pitch 1.25: tap drill (major - pitch) 6.75; removed pi r^2 12 to the shoulder + the point cone pi r^2 (r/tan59)/3", + "body_count": 1, + "volume_mm3": 47546.394232462, + "faces": 8 +} diff --git a/tests/kernel-cases/ladder/05-holes-and-taps/007-tapped-m8-blind/recipe.json b/tests/kernel-cases/ladder/05-holes-and-taps/007-tapped-m8-blind/recipe.json new file mode 100644 index 0000000000..b97f7ebaa2 --- /dev/null +++ b/tests/kernel-cases/ladder/05-holes-and-taps/007-tapped-m8-blind/recipe.json @@ -0,0 +1,19 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.hole", + "params": { + "position": [ + 30, + 20, + 20 + ], + "thread": "M8", + "depth": 12, + "type": "drilled", + "tip_angle": 118 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/05-holes-and-taps/008-tapped-m5-into-curved-face/expected.json b/tests/kernel-cases/ladder/05-holes-and-taps/008-tapped-m5-into-curved-face/expected.json new file mode 100644 index 0000000000..dc575a77c0 --- /dev/null +++ b/tests/kernel-cases/ladder/05-holes-and-taps/008-tapped-m5-into-curved-face/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "an M5 tapped hole drilled radially through a cylinder r20 lying along X", + "source": "analytic", + "method": "tap drill 5 - 0.8 = 4.2; the hole (axis Z) crosses the cylinder (axis X, R=20): V_int = int_-r^r 2 sqrt(r^2 - y^2) 2 sqrt(R^2 - y^2) dy = 553.412299039", + "body_count": 1, + "volume_mm3": 74844.811387116, + "faces": 4 +} diff --git a/tests/kernel-cases/ladder/05-holes-and-taps/008-tapped-m5-into-curved-face/recipe.json b/tests/kernel-cases/ladder/05-holes-and-taps/008-tapped-m5-into-curved-face/recipe.json new file mode 100644 index 0000000000..a2071f09b1 --- /dev/null +++ b/tests/kernel-cases/ladder/05-holes-and-taps/008-tapped-m5-into-curved-face/recipe.json @@ -0,0 +1,29 @@ +{ + "commands": [ + { + "command": "solid.cylinder", + "params": { + "base": [ + 0, + 0, + 0 + ], + "radius": 20, + "height": 60, + "axis": "X", + "body_name": "Part" + } + }, + { + "command": "solid.hole", + "params": { + "position": [ + 30, + 0, + 20 + ], + "thread": "M5" + } + } + ] +} diff --git a/tests/kernel-cases/ladder/05-holes-and-taps/009-hole-0.5-from-edge/expected.json b/tests/kernel-cases/ladder/05-holes-and-taps/009-hole-0.5-from-edge/expected.json new file mode 100644 index 0000000000..0aa39b9767 --- /dev/null +++ b/tests/kernel-cases/ladder/05-holes-and-taps/009-hole-0.5-from-edge/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "a d10 through hole whose wall is 0.5 mm from the end face", + "source": "analytic", + "method": "removed pi 5^2 20, a 0.5 mm web left", + "body_count": 1, + "volume_mm3": 46429.203673205, + "faces": 7 +} diff --git a/tests/kernel-cases/ladder/05-holes-and-taps/009-hole-0.5-from-edge/recipe.json b/tests/kernel-cases/ladder/05-holes-and-taps/009-hole-0.5-from-edge/recipe.json new file mode 100644 index 0000000000..e9396a157b --- /dev/null +++ b/tests/kernel-cases/ladder/05-holes-and-taps/009-hole-0.5-from-edge/recipe.json @@ -0,0 +1,16 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.hole", + "params": { + "position": [ + 5.5, + 20, + 20 + ], + "diameter": 10 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/05-holes-and-taps/010-hole-tangent-to-end-face/expected.json b/tests/kernel-cases/ladder/05-holes-and-taps/010-hole-tangent-to-end-face/expected.json new file mode 100644 index 0000000000..05767bcbc9 --- /dev/null +++ b/tests/kernel-cases/ladder/05-holes-and-taps/010-hole-tangent-to-end-face/expected.json @@ -0,0 +1,8 @@ +{ + "sources": [], + "what": "a d10 through hole tangent to the end face x=0", + "source": "analytic", + "method": "the wall touches x=0 along a line: removed pi 5^2 20, one body", + "body_count": 1, + "volume_mm3": 46429.203673205 +} diff --git a/tests/kernel-cases/ladder/05-holes-and-taps/010-hole-tangent-to-end-face/recipe.json b/tests/kernel-cases/ladder/05-holes-and-taps/010-hole-tangent-to-end-face/recipe.json new file mode 100644 index 0000000000..d8ff954830 --- /dev/null +++ b/tests/kernel-cases/ladder/05-holes-and-taps/010-hole-tangent-to-end-face/recipe.json @@ -0,0 +1,16 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.hole", + "params": { + "position": [ + 5, + 20, + 20 + ], + "diameter": 10 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/05-holes-and-taps/011-crossing-holes-steinmetz/expected.json b/tests/kernel-cases/ladder/05-holes-and-taps/011-crossing-holes-steinmetz/expected.json new file mode 100644 index 0000000000..842f4aaf17 --- /dev/null +++ b/tests/kernel-cases/ladder/05-holes-and-taps/011-crossing-holes-steinmetz/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "two d10 through holes whose axes cross: vertical and along Y at z=10", + "source": "analytic", + "method": "equal radii, crossing axes: the common part is the Steinmetz solid 16 r^3 / 3; removed pi r^2 (20 + 40) - 16 r^3/3", + "body_count": 1, + "volume_mm3": 43954.277686282, + "faces": 8 +} diff --git a/tests/kernel-cases/ladder/05-holes-and-taps/011-crossing-holes-steinmetz/recipe.json b/tests/kernel-cases/ladder/05-holes-and-taps/011-crossing-holes-steinmetz/recipe.json new file mode 100644 index 0000000000..7138f25a28 --- /dev/null +++ b/tests/kernel-cases/ladder/05-holes-and-taps/011-crossing-holes-steinmetz/recipe.json @@ -0,0 +1,27 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.hole", + "params": { + "position": [ + 30, + 20, + 20 + ], + "diameter": 10 + } + }, + { + "command": "solid.hole", + "params": { + "position": [ + 30, + 0, + 10 + ], + "diameter": 10 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/05-holes-and-taps/012-crossing-tapped-holes-offset/expected.json b/tests/kernel-cases/ladder/05-holes-and-taps/012-crossing-tapped-holes-offset/expected.json new file mode 100644 index 0000000000..03656b237f --- /dev/null +++ b/tests/kernel-cases/ladder/05-holes-and-taps/012-crossing-tapped-holes-offset/expected.json @@ -0,0 +1,8 @@ +{ + "sources": [], + "what": "two M6 tapped holes (d5) crossing with axes 2 mm apart", + "source": "analytic", + "method": "common part of perpendicular cylinders r=2.5 with axes 2 apart: int 2 sqrt(r^2-u^2) 2 sqrt(r^2-(u-2)^2) du = 48.316350808", + "body_count": 1, + "volume_mm3": 46870.219105712 +} diff --git a/tests/kernel-cases/ladder/05-holes-and-taps/012-crossing-tapped-holes-offset/recipe.json b/tests/kernel-cases/ladder/05-holes-and-taps/012-crossing-tapped-holes-offset/recipe.json new file mode 100644 index 0000000000..690b638e53 --- /dev/null +++ b/tests/kernel-cases/ladder/05-holes-and-taps/012-crossing-tapped-holes-offset/recipe.json @@ -0,0 +1,27 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.hole", + "params": { + "position": [ + 30, + 20, + 20 + ], + "thread": "M6" + } + }, + { + "command": "solid.hole", + "params": { + "position": [ + 32, + 0, + 10 + ], + "thread": "M6" + } + } + ] +} diff --git a/tests/kernel-cases/ladder/05-holes-and-taps/013-part-holes/expected.json b/tests/kernel-cases/ladder/05-holes-and-taps/013-part-holes/expected.json new file mode 100644 index 0000000000..87a31a9345 --- /dev/null +++ b/tests/kernel-cases/ladder/05-holes-and-taps/013-part-holes/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "the running part: an M6 tapped through hole, an M5 tapped blind hole in the boss, a d3.3 hole down through wall and base", + "source": "analytic", + "method": "removed pi 2.5^2 20 (M6, tap drill 5) + pi 2.1^2 8 (M5, tap drill 4.2, flat) + pi 1.65^2 35; faces +1 +2 +1", + "body_count": 1, + "volume_mm3": 53470.420668652, + "faces": 20 +} diff --git a/tests/kernel-cases/ladder/05-holes-and-taps/013-part-holes/recipe.json b/tests/kernel-cases/ladder/05-holes-and-taps/013-part-holes/recipe.json new file mode 100644 index 0000000000..4fb4d6f766 --- /dev/null +++ b/tests/kernel-cases/ladder/05-holes-and-taps/013-part-holes/recipe.json @@ -0,0 +1,39 @@ +{ + "base": "../../04-spline-profiles/010-part-bezier-window", + "commands": [ + { + "command": "solid.hole", + "params": { + "position": [ + 52, + 22, + 20 + ], + "thread": "M6" + } + }, + { + "command": "solid.hole", + "params": { + "position": [ + 15, + 15, + 30 + ], + "thread": "M5", + "depth": 8 + } + }, + { + "command": "solid.hole", + "params": { + "position": [ + 5, + 35, + 35 + ], + "diameter": 3.3 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/06-threads/001-external-m10-full/expected.json b/tests/kernel-cases/ladder/06-threads/001-external-m10-full/expected.json new file mode 100644 index 0000000000..d4619ea3e6 --- /dev/null +++ b/tests/kernel-cases/ladder/06-threads/001-external-m10-full/expected.json @@ -0,0 +1,11 @@ +{ + "sources": [], + "what": "a modelled M10 thread over the whole of a d10 x 30 cylinder", + "source": "analytic", + "method": "ISO 68-1 basic profile (60 deg, root flat P/4 at d - 5H/4 ... minor d1 = d - 1.0825 P, crest flat P/8): a helical groove of fixed section is a screw motion, so averaged over the turn every meridian half-plane holds the same section and V = 2 pi int int r m(r) dr dz over the stock's section, m the material fraction per pitch; the groove runs out past free ends. Tolerance 0.5%: solid.thread cuts faceted by design (chords within P/200 on the body, P/100 on the tool); P = 1.5, L = 30", + "body_count": 1, + "volume_mm3": 1954.24591725, + "tolerance": { + "rel": 0.005 + } +} diff --git a/tests/kernel-cases/ladder/06-threads/001-external-m10-full/recipe.json b/tests/kernel-cases/ladder/06-threads/001-external-m10-full/recipe.json new file mode 100644 index 0000000000..672e2f49c1 --- /dev/null +++ b/tests/kernel-cases/ladder/06-threads/001-external-m10-full/recipe.json @@ -0,0 +1,29 @@ +{ + "commands": [ + { + "command": "solid.cylinder", + "params": { + "base": [ + 0, + 0, + 0 + ], + "radius": 5, + "height": 30, + "body_name": "Part" + } + }, + { + "command": "solid.thread", + "params": { + "face": [ + 5, + 0, + 15 + ], + "modeled": true, + "designation": "M10" + } + } + ] +} diff --git a/tests/kernel-cases/ladder/06-threads/002-external-m10x1-fine/expected.json b/tests/kernel-cases/ladder/06-threads/002-external-m10x1-fine/expected.json new file mode 100644 index 0000000000..ce60ee9e6d --- /dev/null +++ b/tests/kernel-cases/ladder/06-threads/002-external-m10x1-fine/expected.json @@ -0,0 +1,11 @@ +{ + "sources": [], + "what": "a modelled fine M10x1 thread over a d10 x 30 cylinder", + "source": "analytic", + "method": "ISO 68-1 basic profile (60 deg, root flat P/4 at d - 5H/4 ... minor d1 = d - 1.0825 P, crest flat P/8): a helical groove of fixed section is a screw motion, so averaged over the turn every meridian half-plane holds the same section and V = 2 pi int int r m(r) dr dz over the stock's section, m the material fraction per pitch; the groove runs out past free ends. Tolerance 0.5%: solid.thread cuts faceted by design (chords within P/200 on the body, P/100 on the tool); P = 1, L = 30", + "body_count": 1, + "volume_mm3": 2081.901104148, + "tolerance": { + "rel": 0.005 + } +} diff --git a/tests/kernel-cases/ladder/06-threads/002-external-m10x1-fine/recipe.json b/tests/kernel-cases/ladder/06-threads/002-external-m10x1-fine/recipe.json new file mode 100644 index 0000000000..7b0d89b0cf --- /dev/null +++ b/tests/kernel-cases/ladder/06-threads/002-external-m10x1-fine/recipe.json @@ -0,0 +1,29 @@ +{ + "commands": [ + { + "command": "solid.cylinder", + "params": { + "base": [ + 0, + 0, + 0 + ], + "radius": 5, + "height": 30, + "body_name": "Part" + } + }, + { + "command": "solid.thread", + "params": { + "face": [ + 5, + 0, + 15 + ], + "modeled": true, + "designation": "M10x1" + } + } + ] +} diff --git a/tests/kernel-cases/ladder/06-threads/003-external-m10-partial-length/expected.json b/tests/kernel-cases/ladder/06-threads/003-external-m10-partial-length/expected.json new file mode 100644 index 0000000000..e66fde016a --- /dev/null +++ b/tests/kernel-cases/ladder/06-threads/003-external-m10-partial-length/expected.json @@ -0,0 +1,19 @@ +{ + "sources": [], + "what": "a modelled M10 thread over 15 of the 30 mm: it stops square in the middle of the face", + "source": "invariants", + "method": "the square stop has no closed form; one valid body inside the stock's box", + "body_count": 1, + "bbox": { + "min": [ + -5, + -5, + 0 + ], + "max": [ + 5, + 5, + 30 + ] + } +} diff --git a/tests/kernel-cases/ladder/06-threads/003-external-m10-partial-length/recipe.json b/tests/kernel-cases/ladder/06-threads/003-external-m10-partial-length/recipe.json new file mode 100644 index 0000000000..0631c540b9 --- /dev/null +++ b/tests/kernel-cases/ladder/06-threads/003-external-m10-partial-length/recipe.json @@ -0,0 +1,30 @@ +{ + "commands": [ + { + "command": "solid.cylinder", + "params": { + "base": [ + 0, + 0, + 0 + ], + "radius": 5, + "height": 30, + "body_name": "Part" + } + }, + { + "command": "solid.thread", + "params": { + "face": [ + 5, + 0, + 15 + ], + "modeled": true, + "designation": "M10", + "length": 15 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/06-threads/004-external-m10-to-shoulder/expected.json b/tests/kernel-cases/ladder/06-threads/004-external-m10-to-shoulder/expected.json new file mode 100644 index 0000000000..130cc469d3 --- /dev/null +++ b/tests/kernel-cases/ladder/06-threads/004-external-m10-to-shoulder/expected.json @@ -0,0 +1,19 @@ +{ + "sources": [], + "what": "a modelled M10 thread on a d10 shaft running down to a d16 shoulder", + "source": "invariants", + "method": "the thread must end at the shoulder, leaving the d16 collar whole; no closed form for the end; one valid body", + "body_count": 1, + "bbox": { + "min": [ + -8, + -8, + 0 + ], + "max": [ + 8, + 8, + 35 + ] + } +} diff --git a/tests/kernel-cases/ladder/06-threads/004-external-m10-to-shoulder/recipe.json b/tests/kernel-cases/ladder/06-threads/004-external-m10-to-shoulder/recipe.json new file mode 100644 index 0000000000..71722173f6 --- /dev/null +++ b/tests/kernel-cases/ladder/06-threads/004-external-m10-to-shoulder/recipe.json @@ -0,0 +1,45 @@ +{ + "commands": [ + { + "command": "solid.cylinder", + "params": { + "base": [ + 0, + 0, + 0 + ], + "radius": 8, + "height": 10, + "body_name": "Part" + } + }, + { + "command": "solid.cylinder", + "params": { + "base": [ + 0, + 0, + 10 + ], + "radius": 5, + "height": 25, + "operation": "join", + "targets": [ + "Part" + ] + } + }, + { + "command": "solid.thread", + "params": { + "face": [ + 5, + 0, + 25 + ], + "modeled": true, + "designation": "M10" + } + } + ] +} diff --git a/tests/kernel-cases/ladder/06-threads/005-internal-m10-through/expected.json b/tests/kernel-cases/ladder/06-threads/005-internal-m10-through/expected.json new file mode 100644 index 0000000000..05f5744895 --- /dev/null +++ b/tests/kernel-cases/ladder/06-threads/005-internal-m10-through/expected.json @@ -0,0 +1,11 @@ +{ + "sources": [], + "what": "a modelled internal M10 thread in a d8.5 (tap drill) hole through a 30x30x20 block", + "source": "analytic", + "method": "ISO 68-1 internal basic profile: groove P/8 wide at the major diameter, growing 2 tan30 per mm inward; the drilled hole r4.25 is above the minor r4.188, so the groove removes 2 pi int_4.25^5 r g(r) dr per mm; screw motion argument as in 001; both ends run out", + "body_count": 1, + "volume_mm3": 16688.181199219, + "tolerance": { + "rel": 0.005 + } +} diff --git a/tests/kernel-cases/ladder/06-threads/005-internal-m10-through/recipe.json b/tests/kernel-cases/ladder/06-threads/005-internal-m10-through/recipe.json new file mode 100644 index 0000000000..47fdf9a355 --- /dev/null +++ b/tests/kernel-cases/ladder/06-threads/005-internal-m10-through/recipe.json @@ -0,0 +1,41 @@ +{ + "commands": [ + { + "command": "solid.box", + "params": { + "corner": [ + 0, + 0, + 0 + ], + "length": 30, + "width": 30, + "height": 20, + "body_name": "Part" + } + }, + { + "command": "solid.hole", + "params": { + "position": [ + 15, + 15, + 20 + ], + "diameter": 8.5 + } + }, + { + "command": "solid.thread", + "params": { + "face": [ + 19.25, + 15, + 10 + ], + "modeled": true, + "designation": "M10" + } + } + ] +} diff --git a/tests/kernel-cases/ladder/06-threads/006-external-m10-into-chamfer/expected.json b/tests/kernel-cases/ladder/06-threads/006-external-m10-into-chamfer/expected.json new file mode 100644 index 0000000000..90e9661a9d --- /dev/null +++ b/tests/kernel-cases/ladder/06-threads/006-external-m10-into-chamfer/expected.json @@ -0,0 +1,11 @@ +{ + "sources": [], + "what": "a d10 x 30 cylinder with a 1 mm chamfer on top, then a modelled M10 thread over its cylinder face, running out through the chamfer", + "source": "analytic", + "method": "ISO 68-1 basic profile (60 deg, root flat P/4 at d - 5H/4 ... minor d1 = d - 1.0825 P, crest flat P/8): a helical groove of fixed section is a screw motion, so averaged over the turn every meridian half-plane holds the same section and V = 2 pi int int r m(r) dr dz over the stock's section, m the material fraction per pitch; the groove runs out past free ends. Tolerance 0.5%: solid.thread cuts faceted by design (chords within P/200 on the body, P/100 on the tool); the stock's section is r <= 5 below z=29 and r <= 5 - (z-29) in the chamfer; the groove runs one pitch past z=29, which covers the chamfer down to r=4.2 (above the root r=4.188 only for 0.01 mm)", + "body_count": 1, + "volume_mm3": 1948.692985041, + "tolerance": { + "rel": 0.005 + } +} diff --git a/tests/kernel-cases/ladder/06-threads/006-external-m10-into-chamfer/recipe.json b/tests/kernel-cases/ladder/06-threads/006-external-m10-into-chamfer/recipe.json new file mode 100644 index 0000000000..eaee8caed8 --- /dev/null +++ b/tests/kernel-cases/ladder/06-threads/006-external-m10-into-chamfer/recipe.json @@ -0,0 +1,42 @@ +{ + "commands": [ + { + "command": "solid.cylinder", + "params": { + "base": [ + 0, + 0, + 0 + ], + "radius": 5, + "height": 30, + "body_name": "Part" + } + }, + { + "command": "solid.chamfer", + "params": { + "edges": [ + [ + 5, + 0, + 30 + ] + ], + "distance": 1 + } + }, + { + "command": "solid.thread", + "params": { + "face": [ + 5, + 0, + 15 + ], + "modeled": true, + "designation": "M10" + } + } + ] +} diff --git a/tests/kernel-cases/ladder/06-threads/007-external-m10-at-filleted-shoulder/expected.json b/tests/kernel-cases/ladder/06-threads/007-external-m10-at-filleted-shoulder/expected.json new file mode 100644 index 0000000000..a603fa17f5 --- /dev/null +++ b/tests/kernel-cases/ladder/06-threads/007-external-m10-at-filleted-shoulder/expected.json @@ -0,0 +1,19 @@ +{ + "sources": [], + "what": "a modelled M10 thread on a d10 shaft whose root at the d16 shoulder has an r1 fillet", + "source": "invariants", + "method": "thread meeting a concave fillet; one valid body", + "body_count": 1, + "bbox": { + "min": [ + -8, + -8, + 0 + ], + "max": [ + 8, + 8, + 35 + ] + } +} diff --git a/tests/kernel-cases/ladder/06-threads/007-external-m10-at-filleted-shoulder/recipe.json b/tests/kernel-cases/ladder/06-threads/007-external-m10-at-filleted-shoulder/recipe.json new file mode 100644 index 0000000000..b476b7e9ef --- /dev/null +++ b/tests/kernel-cases/ladder/06-threads/007-external-m10-at-filleted-shoulder/recipe.json @@ -0,0 +1,58 @@ +{ + "commands": [ + { + "command": "solid.cylinder", + "params": { + "base": [ + 0, + 0, + 0 + ], + "radius": 8, + "height": 10, + "body_name": "Part" + } + }, + { + "command": "solid.cylinder", + "params": { + "base": [ + 0, + 0, + 10 + ], + "radius": 5, + "height": 25, + "operation": "join", + "targets": [ + "Part" + ] + } + }, + { + "command": "solid.fillet", + "params": { + "edges": [ + [ + 5, + 0, + 10 + ] + ], + "radius": 1 + } + }, + { + "command": "solid.thread", + "params": { + "face": [ + 5, + 0, + 25 + ], + "modeled": true, + "designation": "M10" + } + } + ] +} diff --git a/tests/kernel-cases/ladder/06-threads/008-multi-start-thread/expected.json b/tests/kernel-cases/ladder/06-threads/008-multi-start-thread/expected.json new file mode 100644 index 0000000000..08b368180a --- /dev/null +++ b/tests/kernel-cases/ladder/06-threads/008-multi-start-thread/expected.json @@ -0,0 +1,7 @@ +{ + "sources": [], + "what": "a two-start M10 thread (lead 3, pitch 1.5)", + "source": "invariants", + "method": "would be graded as 001 with the material fraction of two grooves per lead", + "missing": "solid.thread has no start count: multi-start threads" +} diff --git a/tests/kernel-cases/ladder/06-threads/008-multi-start-thread/recipe.json b/tests/kernel-cases/ladder/06-threads/008-multi-start-thread/recipe.json new file mode 100644 index 0000000000..fe7861aa13 --- /dev/null +++ b/tests/kernel-cases/ladder/06-threads/008-multi-start-thread/recipe.json @@ -0,0 +1,3 @@ +{ + "commands": [] +} diff --git a/tests/kernel-cases/ladder/06-threads/009-thread-runout-length/expected.json b/tests/kernel-cases/ladder/06-threads/009-thread-runout-length/expected.json new file mode 100644 index 0000000000..4b4ef51201 --- /dev/null +++ b/tests/kernel-cases/ladder/06-threads/009-thread-runout-length/expected.json @@ -0,0 +1,7 @@ +{ + "sources": [], + "what": "an M10 thread that vanishes over a 2-pitch runout before a shoulder", + "source": "invariants", + "method": "a runout would let 004 end without cutting the shoulder", + "missing": "solid.thread has no runout (vanishing thread) length" +} diff --git a/tests/kernel-cases/ladder/06-threads/009-thread-runout-length/recipe.json b/tests/kernel-cases/ladder/06-threads/009-thread-runout-length/recipe.json new file mode 100644 index 0000000000..fe7861aa13 --- /dev/null +++ b/tests/kernel-cases/ladder/06-threads/009-thread-runout-length/recipe.json @@ -0,0 +1,3 @@ +{ + "commands": [] +} diff --git a/tests/kernel-cases/ladder/06-threads/010-part-threaded-boss/expected.json b/tests/kernel-cases/ladder/06-threads/010-part-threaded-boss/expected.json new file mode 100644 index 0000000000..e51163ae31 --- /dev/null +++ b/tests/kernel-cases/ladder/06-threads/010-part-threaded-boss/expected.json @@ -0,0 +1,7 @@ +{ + "sources": [], + "what": "the running part with a modelled M16 thread on its d16 boss", + "source": "invariants", + "method": "the boss stands on the base (thread to a shoulder) and has an M5 hole in its top; modelled threads are faceted, so later tiers build on the part before this step and 11-cumulative-parts adds it last", + "body_count": 1 +} diff --git a/tests/kernel-cases/ladder/06-threads/010-part-threaded-boss/recipe.json b/tests/kernel-cases/ladder/06-threads/010-part-threaded-boss/recipe.json new file mode 100644 index 0000000000..ca5fbd7230 --- /dev/null +++ b/tests/kernel-cases/ladder/06-threads/010-part-threaded-boss/recipe.json @@ -0,0 +1,17 @@ +{ + "base": "../../05-holes-and-taps/013-part-holes", + "commands": [ + { + "command": "solid.thread", + "params": { + "face": [ + 23, + 15, + 25 + ], + "modeled": true, + "designation": "M16" + } + } + ] +} diff --git a/tests/kernel-cases/ladder/07-fillets/001-single-edge-r5/expected.json b/tests/kernel-cases/ladder/07-fillets/001-single-edge-r5/expected.json new file mode 100644 index 0000000000..54e6f43338 --- /dev/null +++ b/tests/kernel-cases/ladder/07-fillets/001-single-edge-r5/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "an r5 round on the stock's top front edge", + "source": "analytic", + "method": "removed (1 - pi/4) r^2 L", + "body_count": 1, + "volume_mm3": 47678.097245096, + "faces": 7 +} diff --git a/tests/kernel-cases/ladder/07-fillets/001-single-edge-r5/recipe.json b/tests/kernel-cases/ladder/07-fillets/001-single-edge-r5/recipe.json new file mode 100644 index 0000000000..8108bff3da --- /dev/null +++ b/tests/kernel-cases/ladder/07-fillets/001-single-edge-r5/recipe.json @@ -0,0 +1,18 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.fillet", + "params": { + "edges": [ + [ + 30, + 0, + 20 + ] + ], + "radius": 5 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/07-fillets/002-four-vertical-edges-r5/expected.json b/tests/kernel-cases/ladder/07-fillets/002-four-vertical-edges-r5/expected.json new file mode 100644 index 0000000000..68efd1aa5c --- /dev/null +++ b/tests/kernel-cases/ladder/07-fillets/002-four-vertical-edges-r5/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "r5 rounds on the four vertical edges", + "source": "analytic", + "method": "removed 4 (1 - pi/4) r^2 20", + "body_count": 1, + "volume_mm3": 47570.796326795, + "faces": 10 +} diff --git a/tests/kernel-cases/ladder/07-fillets/002-four-vertical-edges-r5/recipe.json b/tests/kernel-cases/ladder/07-fillets/002-four-vertical-edges-r5/recipe.json new file mode 100644 index 0000000000..65fc21193f --- /dev/null +++ b/tests/kernel-cases/ladder/07-fillets/002-four-vertical-edges-r5/recipe.json @@ -0,0 +1,33 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.fillet", + "params": { + "edges": [ + [ + 0, + 0, + 10 + ], + [ + 60, + 0, + 10 + ], + [ + 60, + 40, + 10 + ], + [ + 0, + 40, + 10 + ] + ], + "radius": 5 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/07-fillets/003-tangent-chain-r3/expected.json b/tests/kernel-cases/ladder/07-fillets/003-tangent-chain-r3/expected.json new file mode 100644 index 0000000000..150213d8a1 --- /dev/null +++ b/tests/kernel-cases/ladder/07-fillets/003-tangent-chain-r3/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "an r3 round all round the top of the box with r5 vertical rounds: a tangent chain of lines and arcs", + "source": "analytic", + "method": "straight runs 2*50 + 2*30 = 160 remove (1-pi/4) r^2 each mm; at each corner the round is a torus quarter: Pappus, the section (1-pi/4) r^2 with its centroid r(10-3pi)/(12-3pi) in from the wall, at radius 5 - that, over a quarter turn", + "body_count": 1, + "volume_mm3": 47209.224452285, + "faces": 18 +} diff --git a/tests/kernel-cases/ladder/07-fillets/003-tangent-chain-r3/recipe.json b/tests/kernel-cases/ladder/07-fillets/003-tangent-chain-r3/recipe.json new file mode 100644 index 0000000000..ee0cb3dfb0 --- /dev/null +++ b/tests/kernel-cases/ladder/07-fillets/003-tangent-chain-r3/recipe.json @@ -0,0 +1,53 @@ +{ + "base": "../../07-fillets/002-four-vertical-edges-r5", + "commands": [ + { + "command": "solid.fillet", + "params": { + "edges": [ + [ + 30, + 0, + 20 + ], + [ + 60, + 20, + 20 + ], + [ + 30, + 40, + 20 + ], + [ + 0, + 20, + 20 + ], + [ + 1.4644660940672627, + 1.4644660940672627, + 20 + ], + [ + 58.53553390593274, + 1.4644660940672627, + 20 + ], + [ + 58.53553390593274, + 38.53553390593274, + 20 + ], + [ + 1.4644660940672627, + 38.53553390593274, + 20 + ] + ], + "radius": 3 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/07-fillets/004-convex-and-concave-r5/expected.json b/tests/kernel-cases/ladder/07-fillets/004-convex-and-concave-r5/expected.json new file mode 100644 index 0000000000..a4327818be --- /dev/null +++ b/tests/kernel-cases/ladder/07-fillets/004-convex-and-concave-r5/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "an L block: r5 on its inner (concave) vertical edge and on an outer (convex) one", + "source": "analytic", + "method": "the concave round adds (1-pi/4) r^2 20, the convex one removes the same: V = 700*20", + "body_count": 1, + "volume_mm3": 14000, + "faces": 10 +} diff --git a/tests/kernel-cases/ladder/07-fillets/004-convex-and-concave-r5/recipe.json b/tests/kernel-cases/ladder/07-fillets/004-convex-and-concave-r5/recipe.json new file mode 100644 index 0000000000..dfadfab28a --- /dev/null +++ b/tests/kernel-cases/ladder/07-fillets/004-convex-and-concave-r5/recipe.json @@ -0,0 +1,70 @@ +{ + "commands": [ + { + "command": "sketch.create", + "params": { + "plane": "XY" + } + }, + { + "command": "sketch.line", + "params": { + "points": [ + [ + 0, + 0 + ], + [ + 40, + 0 + ], + [ + 40, + 10 + ], + [ + 10, + 10 + ], + [ + 10, + 40 + ], + [ + 0, + 40 + ] + ], + "closed": true + } + }, + { + "command": "sketch.finish", + "params": {} + }, + { + "command": "solid.extrude", + "params": { + "distance": 20 + } + }, + { + "command": "solid.fillet", + "params": { + "edges": [ + [ + 10, + 10, + 10 + ], + [ + 0, + 0, + 10 + ] + ], + "radius": 5 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/07-fillets/005-variable-r5-to-r1/expected.json b/tests/kernel-cases/ladder/07-fillets/005-variable-r5-to-r1/expected.json new file mode 100644 index 0000000000..427fc65d5d --- /dev/null +++ b/tests/kernel-cases/ladder/07-fillets/005-variable-r5-to-r1/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "a variable round on the top front edge, r5 at x=0 to r1 at x=60", + "source": "analytic", + "method": "linear radius, quarter-round sections (tests/oracle/77): V = abc - (1-pi/4) L (a^2 + ab + b^2)/3", + "body_count": 1, + "volume_mm3": 47866.946861306, + "faces": 7 +} diff --git a/tests/kernel-cases/ladder/07-fillets/005-variable-r5-to-r1/recipe.json b/tests/kernel-cases/ladder/07-fillets/005-variable-r5-to-r1/recipe.json new file mode 100644 index 0000000000..22b4eaba5a --- /dev/null +++ b/tests/kernel-cases/ladder/07-fillets/005-variable-r5-to-r1/recipe.json @@ -0,0 +1,25 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.fillet", + "params": { + "edges": [ + [ + 30, + 0, + 20 + ] + ], + "radius": 5, + "type": "variable", + "radius2": 1, + "start": [ + 0, + 0, + 20 + ] + } + } + ] +} diff --git a/tests/kernel-cases/ladder/07-fillets/006-variable-r5-to-zero/expected.json b/tests/kernel-cases/ladder/07-fillets/006-variable-r5-to-zero/expected.json new file mode 100644 index 0000000000..d240ef49b2 --- /dev/null +++ b/tests/kernel-cases/ladder/07-fillets/006-variable-r5-to-zero/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "a variable round on the top front edge, r5 at x=0 down to 0 at the x=60 vertex", + "source": "analytic", + "method": "as 005 with b=0 (tests/oracle/78): the round closes to the corner, which stays", + "body_count": 1, + "volume_mm3": 47892.699081699, + "faces": 7 +} diff --git a/tests/kernel-cases/ladder/07-fillets/006-variable-r5-to-zero/recipe.json b/tests/kernel-cases/ladder/07-fillets/006-variable-r5-to-zero/recipe.json new file mode 100644 index 0000000000..41d69c0242 --- /dev/null +++ b/tests/kernel-cases/ladder/07-fillets/006-variable-r5-to-zero/recipe.json @@ -0,0 +1,25 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.fillet", + "params": { + "edges": [ + [ + 30, + 0, + 20 + ] + ], + "radius": 5, + "type": "variable", + "radius2": 0, + "start": [ + 0, + 0, + 20 + ] + } + } + ] +} diff --git a/tests/kernel-cases/ladder/07-fillets/007-variable-r5-to-1e-3/expected.json b/tests/kernel-cases/ladder/07-fillets/007-variable-r5-to-1e-3/expected.json new file mode 100644 index 0000000000..86e1a22789 --- /dev/null +++ b/tests/kernel-cases/ladder/07-fillets/007-variable-r5-to-1e-3/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "a variable round on the top front edge, r5 down to 1e-3 mm", + "source": "analytic", + "method": "as 005 with b = 1e-3: the round's end is a 1e-3 mm quarter circle on the end face", + "body_count": 1, + "volume_mm3": 47892.677617223, + "faces": 7 +} diff --git a/tests/kernel-cases/ladder/07-fillets/007-variable-r5-to-1e-3/recipe.json b/tests/kernel-cases/ladder/07-fillets/007-variable-r5-to-1e-3/recipe.json new file mode 100644 index 0000000000..4daf4f0806 --- /dev/null +++ b/tests/kernel-cases/ladder/07-fillets/007-variable-r5-to-1e-3/recipe.json @@ -0,0 +1,25 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.fillet", + "params": { + "edges": [ + [ + 30, + 0, + 20 + ] + ], + "radius": 5, + "type": "variable", + "radius2": 0.001, + "start": [ + 0, + 0, + 20 + ] + } + } + ] +} diff --git a/tests/kernel-cases/ladder/07-fillets/008-constant-r1e-3/expected.json b/tests/kernel-cases/ladder/07-fillets/008-constant-r1e-3/expected.json new file mode 100644 index 0000000000..b21bbce2da --- /dev/null +++ b/tests/kernel-cases/ladder/07-fillets/008-constant-r1e-3/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "a constant 1e-3 mm round on the top front edge", + "source": "analytic", + "method": "removed (1-pi/4) 1e-6 60, a valid 1e-3 round face", + "body_count": 1, + "volume_mm3": 47999.999987124, + "faces": 7 +} diff --git a/tests/kernel-cases/ladder/07-fillets/008-constant-r1e-3/recipe.json b/tests/kernel-cases/ladder/07-fillets/008-constant-r1e-3/recipe.json new file mode 100644 index 0000000000..17d2e6d357 --- /dev/null +++ b/tests/kernel-cases/ladder/07-fillets/008-constant-r1e-3/recipe.json @@ -0,0 +1,18 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.fillet", + "params": { + "edges": [ + [ + 30, + 0, + 20 + ] + ], + "radius": 0.001 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/07-fillets/009-corner-three-equal-r5/expected.json b/tests/kernel-cases/ladder/07-fillets/009-corner-three-equal-r5/expected.json new file mode 100644 index 0000000000..931f6d61bb --- /dev/null +++ b/tests/kernel-cases/ladder/07-fillets/009-corner-three-equal-r5/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "r5 rounds on the three edges meeting at the corner (60,40,20): a sphere corner", + "source": "analytic", + "method": "each edge zone outside the corner cube removes (1-pi/4) r^2 (L - r); the corner cube r^3 keeps a sphere octant pi r^3/6", + "body_count": 1, + "volume_mm3": 47377.120025868, + "faces": 10 +} diff --git a/tests/kernel-cases/ladder/07-fillets/009-corner-three-equal-r5/recipe.json b/tests/kernel-cases/ladder/07-fillets/009-corner-three-equal-r5/recipe.json new file mode 100644 index 0000000000..3cc953421f --- /dev/null +++ b/tests/kernel-cases/ladder/07-fillets/009-corner-three-equal-r5/recipe.json @@ -0,0 +1,28 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.fillet", + "params": { + "edges": [ + [ + 30, + 40, + 20 + ], + [ + 60, + 20, + 20 + ], + [ + 60, + 40, + 10 + ] + ], + "radius": 5 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/07-fillets/010-corner-unequal-r7-r3/expected.json b/tests/kernel-cases/ladder/07-fillets/010-corner-unequal-r7-r3/expected.json new file mode 100644 index 0000000000..001e4958d0 --- /dev/null +++ b/tests/kernel-cases/ladder/07-fillets/010-corner-unequal-r7-r3/expected.json @@ -0,0 +1,7 @@ +{ + "sources": [], + "what": "an r7 vertical round at a corner, then r3 along a top edge, which runs on round the r7 arc to the other top edge", + "source": "invariants", + "method": "two radii meeting at a corner over a tangent chain; one valid body", + "body_count": 1 +} diff --git a/tests/kernel-cases/ladder/07-fillets/010-corner-unequal-r7-r3/recipe.json b/tests/kernel-cases/ladder/07-fillets/010-corner-unequal-r7-r3/recipe.json new file mode 100644 index 0000000000..e002f9a297 --- /dev/null +++ b/tests/kernel-cases/ladder/07-fillets/010-corner-unequal-r7-r3/recipe.json @@ -0,0 +1,31 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.fillet", + "params": { + "edges": [ + [ + 60, + 40, + 10 + ] + ], + "radius": 7 + } + }, + { + "command": "solid.fillet", + "params": { + "edges": [ + [ + 60, + 15, + 20 + ] + ], + "radius": 3 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/07-fillets/011-chain-ending-at-corner/expected.json b/tests/kernel-cases/ladder/07-fillets/011-chain-ending-at-corner/expected.json new file mode 100644 index 0000000000..24844b9ba5 --- /dev/null +++ b/tests/kernel-cases/ladder/07-fillets/011-chain-ending-at-corner/expected.json @@ -0,0 +1,7 @@ +{ + "sources": [], + "what": "r5 rounds on two top edges meeting at a corner whose vertical edge stays sharp", + "source": "invariants", + "method": "two rounds ending at a sharp vertical edge; one valid body", + "body_count": 1 +} diff --git a/tests/kernel-cases/ladder/07-fillets/011-chain-ending-at-corner/recipe.json b/tests/kernel-cases/ladder/07-fillets/011-chain-ending-at-corner/recipe.json new file mode 100644 index 0000000000..d136522e39 --- /dev/null +++ b/tests/kernel-cases/ladder/07-fillets/011-chain-ending-at-corner/recipe.json @@ -0,0 +1,23 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.fillet", + "params": { + "edges": [ + [ + 30, + 40, + 20 + ], + [ + 60, + 20, + 20 + ] + ], + "radius": 5 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/07-fillets/012-variable-into-rounded-corner/expected.json b/tests/kernel-cases/ladder/07-fillets/012-variable-into-rounded-corner/expected.json new file mode 100644 index 0000000000..68e6664597 --- /dev/null +++ b/tests/kernel-cases/ladder/07-fillets/012-variable-into-rounded-corner/expected.json @@ -0,0 +1,7 @@ +{ + "sources": [], + "what": "a vertical r5 round, then a variable round r1 -> r5 along the top back edge into it", + "source": "invariants", + "method": "a variable round running into a constant one at a corner; one valid body", + "body_count": 1 +} diff --git a/tests/kernel-cases/ladder/07-fillets/012-variable-into-rounded-corner/recipe.json b/tests/kernel-cases/ladder/07-fillets/012-variable-into-rounded-corner/recipe.json new file mode 100644 index 0000000000..3d421b34d0 --- /dev/null +++ b/tests/kernel-cases/ladder/07-fillets/012-variable-into-rounded-corner/recipe.json @@ -0,0 +1,38 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.fillet", + "params": { + "edges": [ + [ + 60, + 40, + 10 + ] + ], + "radius": 5 + } + }, + { + "command": "solid.fillet", + "params": { + "edges": [ + [ + 30, + 40, + 20 + ] + ], + "radius": 1, + "type": "variable", + "radius2": 5, + "start": [ + 0, + 40, + 20 + ] + } + } + ] +} diff --git a/tests/kernel-cases/ladder/07-fillets/013-corner-setback/expected.json b/tests/kernel-cases/ladder/07-fillets/013-corner-setback/expected.json new file mode 100644 index 0000000000..5b6f1ab579 --- /dev/null +++ b/tests/kernel-cases/ladder/07-fillets/013-corner-setback/expected.json @@ -0,0 +1,7 @@ +{ + "sources": [], + "what": "three r5 rounds at a corner with setbacks of 8 mm along each edge", + "source": "invariants", + "method": "the setback corner patch would be graded by invariants", + "missing": "solid.fillet has no corner setback distances" +} diff --git a/tests/kernel-cases/ladder/07-fillets/013-corner-setback/recipe.json b/tests/kernel-cases/ladder/07-fillets/013-corner-setback/recipe.json new file mode 100644 index 0000000000..fe7861aa13 --- /dev/null +++ b/tests/kernel-cases/ladder/07-fillets/013-corner-setback/recipe.json @@ -0,0 +1,3 @@ +{ + "commands": [] +} diff --git a/tests/kernel-cases/ladder/07-fillets/014-curved-edge-r2/expected.json b/tests/kernel-cases/ladder/07-fillets/014-curved-edge-r2/expected.json new file mode 100644 index 0000000000..e38eb9bac6 --- /dev/null +++ b/tests/kernel-cases/ladder/07-fillets/014-curved-edge-r2/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "an r2 round on the top edge of a cylinder r10 h20", + "source": "analytic", + "method": "a torus round: Pappus on the removed section (1-pi/4) r^2 at radius 10 - r(10-3pi)/(12-3pi)", + "body_count": 1, + "volume_mm3": 6231.659467358, + "faces": 4 +} diff --git a/tests/kernel-cases/ladder/07-fillets/014-curved-edge-r2/recipe.json b/tests/kernel-cases/ladder/07-fillets/014-curved-edge-r2/recipe.json new file mode 100644 index 0000000000..1983429fae --- /dev/null +++ b/tests/kernel-cases/ladder/07-fillets/014-curved-edge-r2/recipe.json @@ -0,0 +1,30 @@ +{ + "commands": [ + { + "command": "solid.cylinder", + "params": { + "base": [ + 0, + 0, + 0 + ], + "radius": 10, + "height": 20, + "body_name": "Part" + } + }, + { + "command": "solid.fillet", + "params": { + "edges": [ + [ + 10, + 0, + 20 + ] + ], + "radius": 2 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/07-fillets/015-corner-g2-patch/expected.json b/tests/kernel-cases/ladder/07-fillets/015-corner-g2-patch/expected.json new file mode 100644 index 0000000000..a5246f6977 --- /dev/null +++ b/tests/kernel-cases/ladder/07-fillets/015-corner-g2-patch/expected.json @@ -0,0 +1,8 @@ +{ + "sources": [], + "what": "three r5 rounds at a corner with the g2 corner patch", + "source": "invariants", + "method": "the g2 patch replaces the sphere octant; no closed-form volume; one valid body, the same faces as 009", + "body_count": 1, + "faces": 10 +} diff --git a/tests/kernel-cases/ladder/07-fillets/015-corner-g2-patch/recipe.json b/tests/kernel-cases/ladder/07-fillets/015-corner-g2-patch/recipe.json new file mode 100644 index 0000000000..e9b7ce09f1 --- /dev/null +++ b/tests/kernel-cases/ladder/07-fillets/015-corner-g2-patch/recipe.json @@ -0,0 +1,29 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.fillet", + "params": { + "edges": [ + [ + 30, + 40, + 20 + ], + [ + 60, + 20, + 20 + ], + [ + 60, + 40, + 10 + ] + ], + "radius": 5, + "corner": "g2" + } + } + ] +} diff --git a/tests/kernel-cases/ladder/07-fillets/016-corner-three-sequential-r5-r3-r7/expected.json b/tests/kernel-cases/ladder/07-fillets/016-corner-three-sequential-r5-r3-r7/expected.json new file mode 100644 index 0000000000..c2d865f92a --- /dev/null +++ b/tests/kernel-cases/ladder/07-fillets/016-corner-three-sequential-r5-r3-r7/expected.json @@ -0,0 +1,7 @@ +{ + "sources": [], + "what": "rounds r5 and r3 on the two top edges at a corner, then r7 on the vertical edge under them", + "source": "invariants", + "method": "three unequal radii at one corner, one after another; the vertical edge stays sharp below the two top rounds (x=60, y=40, z from 0 to 15), so it can be picked at z=8; one valid body", + "body_count": 1 +} diff --git a/tests/kernel-cases/ladder/07-fillets/016-corner-three-sequential-r5-r3-r7/recipe.json b/tests/kernel-cases/ladder/07-fillets/016-corner-three-sequential-r5-r3-r7/recipe.json new file mode 100644 index 0000000000..7b04d0d113 --- /dev/null +++ b/tests/kernel-cases/ladder/07-fillets/016-corner-three-sequential-r5-r3-r7/recipe.json @@ -0,0 +1,44 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.fillet", + "params": { + "edges": [ + [ + 30, + 40, + 20 + ] + ], + "radius": 5 + } + }, + { + "command": "solid.fillet", + "params": { + "edges": [ + [ + 60, + 20, + 20 + ] + ], + "radius": 3 + } + }, + { + "command": "solid.fillet", + "params": { + "edges": [ + [ + 60, + 40, + 8 + ] + ], + "radius": 7 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/07-fillets/017-part-rounds/expected.json b/tests/kernel-cases/ladder/07-fillets/017-part-rounds/expected.json new file mode 100644 index 0000000000..9140c4494e --- /dev/null +++ b/tests/kernel-cases/ladder/07-fillets/017-part-rounds/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "the running part: r5 on the front vertical edges, r3 on the back wall's top front edge, r1 where the boss meets the top", + "source": "analytic", + "method": "removed 2 (1-pi/4) 25 20 + (1-pi/4) 9 60; the concave r1 at the boss adds (1-pi/4) 1 2 pi (8 + (10-3pi)/(12-3pi)); none of them reaches a hole, the slot or the window", + "body_count": 1, + "volume_mm3": 53151.022090693, + "faces": 24 +} diff --git a/tests/kernel-cases/ladder/07-fillets/017-part-rounds/recipe.json b/tests/kernel-cases/ladder/07-fillets/017-part-rounds/recipe.json new file mode 100644 index 0000000000..0c7fd671b4 --- /dev/null +++ b/tests/kernel-cases/ladder/07-fillets/017-part-rounds/recipe.json @@ -0,0 +1,49 @@ +{ + "base": "../../05-holes-and-taps/013-part-holes", + "commands": [ + { + "command": "solid.fillet", + "params": { + "edges": [ + [ + 0, + 0, + 10 + ], + [ + 60, + 0, + 10 + ] + ], + "radius": 5 + } + }, + { + "command": "solid.fillet", + "params": { + "edges": [ + [ + 30, + 30, + 35 + ] + ], + "radius": 3 + } + }, + { + "command": "solid.fillet", + "params": { + "edges": [ + [ + 23, + 15, + 20 + ] + ], + "radius": 1 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/08-chamfers/001-equal-5/expected.json b/tests/kernel-cases/ladder/08-chamfers/001-equal-5/expected.json new file mode 100644 index 0000000000..1e239c3f9d --- /dev/null +++ b/tests/kernel-cases/ladder/08-chamfers/001-equal-5/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "a 5 mm chamfer on the top front edge", + "source": "analytic", + "method": "removed d^2/2 L", + "body_count": 1, + "volume_mm3": 47250.0, + "faces": 7 +} diff --git a/tests/kernel-cases/ladder/08-chamfers/001-equal-5/recipe.json b/tests/kernel-cases/ladder/08-chamfers/001-equal-5/recipe.json new file mode 100644 index 0000000000..0563cff415 --- /dev/null +++ b/tests/kernel-cases/ladder/08-chamfers/001-equal-5/recipe.json @@ -0,0 +1,18 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.chamfer", + "params": { + "edges": [ + [ + 30, + 0, + 20 + ] + ], + "distance": 5 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/08-chamfers/002-two-distances-5-3/expected.json b/tests/kernel-cases/ladder/08-chamfers/002-two-distances-5-3/expected.json new file mode 100644 index 0000000000..d92ce8edef --- /dev/null +++ b/tests/kernel-cases/ladder/08-chamfers/002-two-distances-5-3/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "a 5 x 3 chamfer on the top front edge", + "source": "analytic", + "method": "removed d1 d2 / 2 L", + "body_count": 1, + "volume_mm3": 47550.0, + "faces": 7 +} diff --git a/tests/kernel-cases/ladder/08-chamfers/002-two-distances-5-3/recipe.json b/tests/kernel-cases/ladder/08-chamfers/002-two-distances-5-3/recipe.json new file mode 100644 index 0000000000..4711ba7808 --- /dev/null +++ b/tests/kernel-cases/ladder/08-chamfers/002-two-distances-5-3/recipe.json @@ -0,0 +1,19 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.chamfer", + "params": { + "edges": [ + [ + 30, + 0, + 20 + ] + ], + "distance": 5, + "distance2": 3 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/08-chamfers/003-distance-angle-5-30/expected.json b/tests/kernel-cases/ladder/08-chamfers/003-distance-angle-5-30/expected.json new file mode 100644 index 0000000000..8ebb21a21d --- /dev/null +++ b/tests/kernel-cases/ladder/08-chamfers/003-distance-angle-5-30/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "a chamfer 5 mm at 30 deg on the top front edge", + "source": "analytic", + "method": "legs 5 and 5 tan30: removed 5 * 5 tan30 / 2 * 60", + "body_count": 1, + "volume_mm3": 47566.987298108, + "faces": 7 +} diff --git a/tests/kernel-cases/ladder/08-chamfers/003-distance-angle-5-30/recipe.json b/tests/kernel-cases/ladder/08-chamfers/003-distance-angle-5-30/recipe.json new file mode 100644 index 0000000000..534bd80a50 --- /dev/null +++ b/tests/kernel-cases/ladder/08-chamfers/003-distance-angle-5-30/recipe.json @@ -0,0 +1,19 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.chamfer", + "params": { + "edges": [ + [ + 30, + 0, + 20 + ] + ], + "distance": 5, + "angle": 30 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/08-chamfers/004-corner-three-equal-3/expected.json b/tests/kernel-cases/ladder/08-chamfers/004-corner-three-equal-3/expected.json new file mode 100644 index 0000000000..e8e33fc5a7 --- /dev/null +++ b/tests/kernel-cases/ladder/08-chamfers/004-corner-three-equal-3/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "3 mm chamfers on the three edges meeting at a corner", + "source": "analytic", + "method": "the union of the three prisms {b+c<=d}, {a+c<=d}, {a+b<=d} (a, b, c distances from the corner faces): sum d^2/2 L - 3 d^3/3 + the triple part d^3/4 (exact integral)", + "body_count": 1, + "volume_mm3": 47480.25, + "faces": 9 +} diff --git a/tests/kernel-cases/ladder/08-chamfers/004-corner-three-equal-3/recipe.json b/tests/kernel-cases/ladder/08-chamfers/004-corner-three-equal-3/recipe.json new file mode 100644 index 0000000000..24cd50f283 --- /dev/null +++ b/tests/kernel-cases/ladder/08-chamfers/004-corner-three-equal-3/recipe.json @@ -0,0 +1,28 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.chamfer", + "params": { + "edges": [ + [ + 30, + 40, + 20 + ], + [ + 60, + 20, + 20 + ], + [ + 60, + 40, + 10 + ] + ], + "distance": 3 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/08-chamfers/005-corner-two-mitred-3/expected.json b/tests/kernel-cases/ladder/08-chamfers/005-corner-two-mitred-3/expected.json new file mode 100644 index 0000000000..b06eaa38df --- /dev/null +++ b/tests/kernel-cases/ladder/08-chamfers/005-corner-two-mitred-3/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "3 mm chamfers on two top edges meeting at a corner (mitred)", + "source": "analytic", + "method": "two prisms meeting in a mitre: d^2/2 (60 + 40) - d^3/3", + "body_count": 1, + "volume_mm3": 47559.0, + "faces": 8 +} diff --git a/tests/kernel-cases/ladder/08-chamfers/005-corner-two-mitred-3/recipe.json b/tests/kernel-cases/ladder/08-chamfers/005-corner-two-mitred-3/recipe.json new file mode 100644 index 0000000000..d2cad51fde --- /dev/null +++ b/tests/kernel-cases/ladder/08-chamfers/005-corner-two-mitred-3/recipe.json @@ -0,0 +1,23 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.chamfer", + "params": { + "edges": [ + [ + 30, + 40, + 20 + ], + [ + 60, + 20, + 20 + ] + ], + "distance": 3 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/08-chamfers/006-curved-edge-2/expected.json b/tests/kernel-cases/ladder/08-chamfers/006-curved-edge-2/expected.json new file mode 100644 index 0000000000..4d1b04875c --- /dev/null +++ b/tests/kernel-cases/ladder/08-chamfers/006-curved-edge-2/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "a 2 mm chamfer on the top edge of a cylinder r10 h20", + "source": "analytic", + "method": "a cone chamfer: Pappus on the triangle (area 2) with its centroid at radius 10 - 2/3", + "body_count": 1, + "volume_mm3": 6165.899181446, + "faces": 4 +} diff --git a/tests/kernel-cases/ladder/08-chamfers/006-curved-edge-2/recipe.json b/tests/kernel-cases/ladder/08-chamfers/006-curved-edge-2/recipe.json new file mode 100644 index 0000000000..37acce4fd9 --- /dev/null +++ b/tests/kernel-cases/ladder/08-chamfers/006-curved-edge-2/recipe.json @@ -0,0 +1,30 @@ +{ + "commands": [ + { + "command": "solid.cylinder", + "params": { + "base": [ + 0, + 0, + 0 + ], + "radius": 10, + "height": 20, + "body_name": "Part" + } + }, + { + "command": "solid.chamfer", + "params": { + "edges": [ + [ + 10, + 0, + 20 + ] + ], + "distance": 2 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/08-chamfers/007-chamfer-then-fillets/expected.json b/tests/kernel-cases/ladder/08-chamfers/007-chamfer-then-fillets/expected.json new file mode 100644 index 0000000000..e7de267c6d --- /dev/null +++ b/tests/kernel-cases/ladder/08-chamfers/007-chamfer-then-fillets/expected.json @@ -0,0 +1,7 @@ +{ + "sources": [], + "what": "a 3 mm chamfer on the top front edge, then r2 rounds on the front vertical edges that meet it", + "source": "invariants", + "method": "rounds running into a chamfer face; one valid body", + "body_count": 1 +} diff --git a/tests/kernel-cases/ladder/08-chamfers/007-chamfer-then-fillets/recipe.json b/tests/kernel-cases/ladder/08-chamfers/007-chamfer-then-fillets/recipe.json new file mode 100644 index 0000000000..3c3f559880 --- /dev/null +++ b/tests/kernel-cases/ladder/08-chamfers/007-chamfer-then-fillets/recipe.json @@ -0,0 +1,36 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.chamfer", + "params": { + "edges": [ + [ + 30, + 0, + 20 + ] + ], + "distance": 3 + } + }, + { + "command": "solid.fillet", + "params": { + "edges": [ + [ + 0, + 0, + 5 + ], + [ + 60, + 0, + 5 + ] + ], + "radius": 2 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/08-chamfers/008-part-chamfers/expected.json b/tests/kernel-cases/ladder/08-chamfers/008-part-chamfers/expected.json new file mode 100644 index 0000000000..ad5c4b2680 --- /dev/null +++ b/tests/kernel-cases/ladder/08-chamfers/008-part-chamfers/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "the running part: 1 mm on the boss top edge, 2 mm on the back wall's top back edge", + "source": "analytic", + "method": "removed 0.5 * 2 pi (8 - 1/3) (Pappus) + 2 * 60", + "body_count": 1, + "volume_mm3": 53006.936547015, + "faces": 26 +} diff --git a/tests/kernel-cases/ladder/08-chamfers/008-part-chamfers/recipe.json b/tests/kernel-cases/ladder/08-chamfers/008-part-chamfers/recipe.json new file mode 100644 index 0000000000..27e17e15cc --- /dev/null +++ b/tests/kernel-cases/ladder/08-chamfers/008-part-chamfers/recipe.json @@ -0,0 +1,31 @@ +{ + "base": "../../07-fillets/017-part-rounds", + "commands": [ + { + "command": "solid.chamfer", + "params": { + "edges": [ + [ + 23, + 15, + 30 + ] + ], + "distance": 1 + } + }, + { + "command": "solid.chamfer", + "params": { + "edges": [ + [ + 30, + 40, + 35 + ] + ], + "distance": 2 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/09-shells-and-offsets/001-box-open-top-t2/expected.json b/tests/kernel-cases/ladder/09-shells-and-offsets/001-box-open-top-t2/expected.json new file mode 100644 index 0000000000..5ed314108f --- /dev/null +++ b/tests/kernel-cases/ladder/09-shells-and-offsets/001-box-open-top-t2/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "the stock shelled 2 mm, open at the top", + "source": "analytic", + "method": "cavity 56x36x18; faces 5 outer + rim + 5 inner", + "body_count": 1, + "volume_mm3": 11712, + "faces": 11 +} diff --git a/tests/kernel-cases/ladder/09-shells-and-offsets/001-box-open-top-t2/recipe.json b/tests/kernel-cases/ladder/09-shells-and-offsets/001-box-open-top-t2/recipe.json new file mode 100644 index 0000000000..c946bfb121 --- /dev/null +++ b/tests/kernel-cases/ladder/09-shells-and-offsets/001-box-open-top-t2/recipe.json @@ -0,0 +1,18 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.shell", + "params": { + "faces": [ + [ + 30, + 20, + 20 + ] + ], + "thickness": 2 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/09-shells-and-offsets/002-cylinder-open-top-t1/expected.json b/tests/kernel-cases/ladder/09-shells-and-offsets/002-cylinder-open-top-t1/expected.json new file mode 100644 index 0000000000..233c9232d6 --- /dev/null +++ b/tests/kernel-cases/ladder/09-shells-and-offsets/002-cylinder-open-top-t1/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "a cylinder r10 h30 shelled 1 mm, open at the top", + "source": "analytic", + "method": "cavity r9 x 29", + "body_count": 1, + "volume_mm3": 2045.176817487, + "faces": 5 +} diff --git a/tests/kernel-cases/ladder/09-shells-and-offsets/002-cylinder-open-top-t1/recipe.json b/tests/kernel-cases/ladder/09-shells-and-offsets/002-cylinder-open-top-t1/recipe.json new file mode 100644 index 0000000000..1754604b39 --- /dev/null +++ b/tests/kernel-cases/ladder/09-shells-and-offsets/002-cylinder-open-top-t1/recipe.json @@ -0,0 +1,30 @@ +{ + "commands": [ + { + "command": "solid.cylinder", + "params": { + "base": [ + 0, + 0, + 0 + ], + "radius": 10, + "height": 30, + "body_name": "Part" + } + }, + { + "command": "solid.shell", + "params": { + "faces": [ + [ + 0, + 0, + 30 + ] + ], + "thickness": 1 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/09-shells-and-offsets/003-tube-two-open-faces/expected.json b/tests/kernel-cases/ladder/09-shells-and-offsets/003-tube-two-open-faces/expected.json new file mode 100644 index 0000000000..5e3c168a6b --- /dev/null +++ b/tests/kernel-cases/ladder/09-shells-and-offsets/003-tube-two-open-faces/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "the stock shelled 2 mm, open top and bottom", + "source": "analytic", + "method": "a rectangular tube: 20 (60*40 - 56*36)", + "body_count": 1, + "volume_mm3": 7680, + "faces": 10 +} diff --git a/tests/kernel-cases/ladder/09-shells-and-offsets/003-tube-two-open-faces/recipe.json b/tests/kernel-cases/ladder/09-shells-and-offsets/003-tube-two-open-faces/recipe.json new file mode 100644 index 0000000000..9b5c5cb8b6 --- /dev/null +++ b/tests/kernel-cases/ladder/09-shells-and-offsets/003-tube-two-open-faces/recipe.json @@ -0,0 +1,23 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.shell", + "params": { + "faces": [ + [ + 30, + 20, + 20 + ], + [ + 30, + 20, + 0 + ] + ], + "thickness": 2 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/09-shells-and-offsets/004-thin-wall-0.2/expected.json b/tests/kernel-cases/ladder/09-shells-and-offsets/004-thin-wall-0.2/expected.json new file mode 100644 index 0000000000..5eef1988ac --- /dev/null +++ b/tests/kernel-cases/ladder/09-shells-and-offsets/004-thin-wall-0.2/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "the stock shelled 0.2 mm, open at the top", + "source": "analytic", + "method": "cavity 59.6 x 39.6 x 19.8", + "body_count": 1, + "volume_mm3": 1268.832, + "faces": 11 +} diff --git a/tests/kernel-cases/ladder/09-shells-and-offsets/004-thin-wall-0.2/recipe.json b/tests/kernel-cases/ladder/09-shells-and-offsets/004-thin-wall-0.2/recipe.json new file mode 100644 index 0000000000..26961794d4 --- /dev/null +++ b/tests/kernel-cases/ladder/09-shells-and-offsets/004-thin-wall-0.2/recipe.json @@ -0,0 +1,18 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.shell", + "params": { + "faces": [ + [ + 30, + 20, + 20 + ] + ], + "thickness": 0.2 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/09-shells-and-offsets/005-shell-rounds-r5-t2/expected.json b/tests/kernel-cases/ladder/09-shells-and-offsets/005-shell-rounds-r5-t2/expected.json new file mode 100644 index 0000000000..a96829cc0b --- /dev/null +++ b/tests/kernel-cases/ladder/09-shells-and-offsets/005-shell-rounds-r5-t2/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "the box with r5 vertical rounds shelled 2 mm open at the top: the inner corners r3", + "source": "analytic", + "method": "the cavity is the 56x36 rounded rectangle with r 5-2 = 3 corners, 18 deep", + "body_count": 1, + "volume_mm3": 11421.858316913, + "faces": 19 +} diff --git a/tests/kernel-cases/ladder/09-shells-and-offsets/005-shell-rounds-r5-t2/recipe.json b/tests/kernel-cases/ladder/09-shells-and-offsets/005-shell-rounds-r5-t2/recipe.json new file mode 100644 index 0000000000..cb7c218fc2 --- /dev/null +++ b/tests/kernel-cases/ladder/09-shells-and-offsets/005-shell-rounds-r5-t2/recipe.json @@ -0,0 +1,18 @@ +{ + "base": "../../07-fillets/002-four-vertical-edges-r5", + "commands": [ + { + "command": "solid.shell", + "params": { + "faces": [ + [ + 30, + 20, + 20 + ] + ], + "thickness": 2 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/09-shells-and-offsets/006-shell-rounds-r1-thinner-than-t2/expected.json b/tests/kernel-cases/ladder/09-shells-and-offsets/006-shell-rounds-r1-thinner-than-t2/expected.json new file mode 100644 index 0000000000..61e303a9d4 --- /dev/null +++ b/tests/kernel-cases/ladder/09-shells-and-offsets/006-shell-rounds-r1-thinner-than-t2/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "the box with r1 vertical rounds shelled 2 mm: the inner corners close sharp", + "source": "analytic", + "method": "an inward offset of r1 by 2 vanishes: the cavity is a sharp 56x36x18 box", + "body_count": 1, + "volume_mm3": 11694.831853072, + "faces": 15 +} diff --git a/tests/kernel-cases/ladder/09-shells-and-offsets/006-shell-rounds-r1-thinner-than-t2/recipe.json b/tests/kernel-cases/ladder/09-shells-and-offsets/006-shell-rounds-r1-thinner-than-t2/recipe.json new file mode 100644 index 0000000000..d2c1ff9b65 --- /dev/null +++ b/tests/kernel-cases/ladder/09-shells-and-offsets/006-shell-rounds-r1-thinner-than-t2/recipe.json @@ -0,0 +1,46 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.fillet", + "params": { + "edges": [ + [ + 0, + 0, + 10 + ], + [ + 60, + 0, + 10 + ], + [ + 60, + 40, + 10 + ], + [ + 0, + 40, + 10 + ] + ], + "radius": 1 + } + }, + { + "command": "solid.shell", + "params": { + "faces": [ + [ + 30, + 20, + 20 + ] + ], + "thickness": 2 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/09-shells-and-offsets/007-offset-top-face-5/expected.json b/tests/kernel-cases/ladder/09-shells-and-offsets/007-offset-top-face-5/expected.json new file mode 100644 index 0000000000..9d19364dec --- /dev/null +++ b/tests/kernel-cases/ladder/09-shells-and-offsets/007-offset-top-face-5/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "the stock's top face offset 5 mm", + "source": "analytic", + "method": "a 60x40x25 box", + "body_count": 1, + "volume_mm3": 60000, + "faces": 6 +} diff --git a/tests/kernel-cases/ladder/09-shells-and-offsets/007-offset-top-face-5/recipe.json b/tests/kernel-cases/ladder/09-shells-and-offsets/007-offset-top-face-5/recipe.json new file mode 100644 index 0000000000..d8107672e3 --- /dev/null +++ b/tests/kernel-cases/ladder/09-shells-and-offsets/007-offset-top-face-5/recipe.json @@ -0,0 +1,18 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.offset_face", + "params": { + "faces": [ + [ + 30, + 20, + 20 + ] + ], + "distance": 5 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/09-shells-and-offsets/008-offset-cylinder-wall-2/expected.json b/tests/kernel-cases/ladder/09-shells-and-offsets/008-offset-cylinder-wall-2/expected.json new file mode 100644 index 0000000000..19cac57831 --- /dev/null +++ b/tests/kernel-cases/ladder/09-shells-and-offsets/008-offset-cylinder-wall-2/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "a cylinder r10 h20 with its side face offset 2 mm", + "source": "analytic", + "method": "a cylinder r12 h20", + "body_count": 1, + "volume_mm3": 9047.786842339, + "faces": 3 +} diff --git a/tests/kernel-cases/ladder/09-shells-and-offsets/008-offset-cylinder-wall-2/recipe.json b/tests/kernel-cases/ladder/09-shells-and-offsets/008-offset-cylinder-wall-2/recipe.json new file mode 100644 index 0000000000..73b15eaaa1 --- /dev/null +++ b/tests/kernel-cases/ladder/09-shells-and-offsets/008-offset-cylinder-wall-2/recipe.json @@ -0,0 +1,30 @@ +{ + "commands": [ + { + "command": "solid.cylinder", + "params": { + "base": [ + 0, + 0, + 0 + ], + "radius": 10, + "height": 20, + "body_name": "Part" + } + }, + { + "command": "solid.offset_face", + "params": { + "faces": [ + [ + 10, + 0, + 10 + ] + ], + "distance": 2 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/09-shells-and-offsets/009-shell-outside-t2/expected.json b/tests/kernel-cases/ladder/09-shells-and-offsets/009-shell-outside-t2/expected.json new file mode 100644 index 0000000000..5b8ea58526 --- /dev/null +++ b/tests/kernel-cases/ladder/09-shells-and-offsets/009-shell-outside-t2/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "the stock shelled 2 mm outward, open at the top", + "source": "analytic", + "method": "walls grown out of the closed faces: 64 x 44 x 22 less the original box", + "body_count": 1, + "volume_mm3": 13952, + "faces": 11 +} diff --git a/tests/kernel-cases/ladder/09-shells-and-offsets/009-shell-outside-t2/recipe.json b/tests/kernel-cases/ladder/09-shells-and-offsets/009-shell-outside-t2/recipe.json new file mode 100644 index 0000000000..288680fefe --- /dev/null +++ b/tests/kernel-cases/ladder/09-shells-and-offsets/009-shell-outside-t2/recipe.json @@ -0,0 +1,19 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.shell", + "params": { + "faces": [ + [ + 30, + 20, + 20 + ] + ], + "thickness": 2, + "direction": "outside" + } + } + ] +} diff --git a/tests/kernel-cases/ladder/09-shells-and-offsets/010-shell-under-rounded-top/expected.json b/tests/kernel-cases/ladder/09-shells-and-offsets/010-shell-under-rounded-top/expected.json new file mode 100644 index 0000000000..49b86fa354 --- /dev/null +++ b/tests/kernel-cases/ladder/09-shells-and-offsets/010-shell-under-rounded-top/expected.json @@ -0,0 +1,7 @@ +{ + "sources": [], + "what": "the stock with r3 rounds on its four top edges, shelled 2 mm open at the bottom", + "source": "invariants", + "method": "walls meeting rounds and their corner patches; one valid body", + "body_count": 1 +} diff --git a/tests/kernel-cases/ladder/09-shells-and-offsets/010-shell-under-rounded-top/recipe.json b/tests/kernel-cases/ladder/09-shells-and-offsets/010-shell-under-rounded-top/recipe.json new file mode 100644 index 0000000000..762a08a48b --- /dev/null +++ b/tests/kernel-cases/ladder/09-shells-and-offsets/010-shell-under-rounded-top/recipe.json @@ -0,0 +1,46 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.fillet", + "params": { + "edges": [ + [ + 30, + 0, + 20 + ], + [ + 60, + 20, + 20 + ], + [ + 30, + 40, + 20 + ], + [ + 0, + 20, + 20 + ] + ], + "radius": 3 + } + }, + { + "command": "solid.shell", + "params": { + "faces": [ + [ + 30, + 20, + 0 + ] + ], + "thickness": 2 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/09-shells-and-offsets/011-part-shell/expected.json b/tests/kernel-cases/ladder/09-shells-and-offsets/011-part-shell/expected.json new file mode 100644 index 0000000000..e9f80b1605 --- /dev/null +++ b/tests/kernel-cases/ladder/09-shells-and-offsets/011-part-shell/expected.json @@ -0,0 +1,7 @@ +{ + "sources": [], + "what": "the running part shelled 1.5 mm open at the bottom", + "source": "invariants", + "method": "a shell around holes, a slot, a window, rounds and chamfers; one valid body", + "body_count": 1 +} diff --git a/tests/kernel-cases/ladder/09-shells-and-offsets/011-part-shell/recipe.json b/tests/kernel-cases/ladder/09-shells-and-offsets/011-part-shell/recipe.json new file mode 100644 index 0000000000..1fa2cc3e37 --- /dev/null +++ b/tests/kernel-cases/ladder/09-shells-and-offsets/011-part-shell/recipe.json @@ -0,0 +1,18 @@ +{ + "base": "../../08-chamfers/008-part-chamfers", + "commands": [ + { + "command": "solid.shell", + "params": { + "faces": [ + [ + 30, + 20, + 0 + ] + ], + "thickness": 1.5 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/10-patterns-and-mirrors/001-rect-pattern-holes/expected.json b/tests/kernel-cases/ladder/10-patterns-and-mirrors/001-rect-pattern-holes/expected.json new file mode 100644 index 0000000000..056c7a9377 --- /dev/null +++ b/tests/kernel-cases/ladder/10-patterns-and-mirrors/001-rect-pattern-holes/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "a d5 hole patterned 3 x 2 at 20 mm in a 60x40x10 plate", + "source": "analytic", + "method": "six holes pi 2.5^2 10", + "body_count": 1, + "volume_mm3": 22821.902754904, + "faces": 12 +} diff --git a/tests/kernel-cases/ladder/10-patterns-and-mirrors/001-rect-pattern-holes/recipe.json b/tests/kernel-cases/ladder/10-patterns-and-mirrors/001-rect-pattern-holes/recipe.json new file mode 100644 index 0000000000..76094fb93f --- /dev/null +++ b/tests/kernel-cases/ladder/10-patterns-and-mirrors/001-rect-pattern-holes/recipe.json @@ -0,0 +1,52 @@ +{ + "commands": [ + { + "command": "solid.box", + "params": { + "corner": [ + 0, + 0, + 0 + ], + "length": 60, + "width": 40, + "height": 10, + "body_name": "Part" + } + }, + { + "command": "solid.hole", + "params": { + "position": [ + 10, + 10, + 10 + ], + "diameter": 5, + "name": "H" + } + }, + { + "command": "solid.pattern.rectangular", + "params": { + "features": [ + "H" + ], + "dir1": [ + 1, + 0, + 0 + ], + "count1": 3, + "spacing1": 20, + "dir2": [ + 0, + 1, + 0 + ], + "count2": 2, + "spacing2": 20 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/10-patterns-and-mirrors/002-circular-pattern-holes/expected.json b/tests/kernel-cases/ladder/10-patterns-and-mirrors/002-circular-pattern-holes/expected.json new file mode 100644 index 0000000000..f218265b98 --- /dev/null +++ b/tests/kernel-cases/ladder/10-patterns-and-mirrors/002-circular-pattern-holes/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "a d6 hole patterned 8 times round a disk r30 h10", + "source": "analytic", + "method": "eight holes pi 3^2 10", + "body_count": 1, + "volume_mm3": 26012.387171723, + "faces": 11 +} diff --git a/tests/kernel-cases/ladder/10-patterns-and-mirrors/002-circular-pattern-holes/recipe.json b/tests/kernel-cases/ladder/10-patterns-and-mirrors/002-circular-pattern-holes/recipe.json new file mode 100644 index 0000000000..c6105b3739 --- /dev/null +++ b/tests/kernel-cases/ladder/10-patterns-and-mirrors/002-circular-pattern-holes/recipe.json @@ -0,0 +1,39 @@ +{ + "commands": [ + { + "command": "solid.cylinder", + "params": { + "base": [ + 0, + 0, + 0 + ], + "radius": 30, + "height": 10, + "body_name": "Part" + } + }, + { + "command": "solid.hole", + "params": { + "position": [ + 20, + 0, + 10 + ], + "diameter": 6, + "name": "H" + } + }, + { + "command": "solid.pattern.circular", + "params": { + "features": [ + "H" + ], + "axis": "Z", + "count": 8 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/10-patterns-and-mirrors/003-overlapping-pattern-leaves-core/expected.json b/tests/kernel-cases/ladder/10-patterns-and-mirrors/003-overlapping-pattern-leaves-core/expected.json new file mode 100644 index 0000000000..945c246d73 --- /dev/null +++ b/tests/kernel-cases/ladder/10-patterns-and-mirrors/003-overlapping-pattern-leaves-core/expected.json @@ -0,0 +1,8 @@ +{ + "sources": [], + "what": "six r5 cuts patterned round a circle r6 in a disk r20: they overlap and cut a core free", + "source": "analytic", + "method": "neighbouring cuts overlap (centres 6 apart < 10) but none reaches the axis (6 > 5): a core is cut free; the union's area 337.050332638 by a polar integral of the covered radial intervals", + "body_count": 2, + "volume_mm3": 9195.867287979 +} diff --git a/tests/kernel-cases/ladder/10-patterns-and-mirrors/003-overlapping-pattern-leaves-core/recipe.json b/tests/kernel-cases/ladder/10-patterns-and-mirrors/003-overlapping-pattern-leaves-core/recipe.json new file mode 100644 index 0000000000..99b75978f9 --- /dev/null +++ b/tests/kernel-cases/ladder/10-patterns-and-mirrors/003-overlapping-pattern-leaves-core/recipe.json @@ -0,0 +1,44 @@ +{ + "commands": [ + { + "command": "solid.cylinder", + "params": { + "base": [ + 0, + 0, + 0 + ], + "radius": 20, + "height": 10, + "body_name": "Part" + } + }, + { + "command": "solid.cylinder", + "params": { + "base": [ + 6, + 0, + -1 + ], + "radius": 5, + "height": 12, + "operation": "cut", + "targets": [ + "Part" + ], + "name": "H" + } + }, + { + "command": "solid.pattern.circular", + "params": { + "features": [ + "H" + ], + "axis": "Z", + "count": 6 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/10-patterns-and-mirrors/004-mirror-body-joined/expected.json b/tests/kernel-cases/ladder/10-patterns-and-mirrors/004-mirror-body-joined/expected.json new file mode 100644 index 0000000000..3924603bfe --- /dev/null +++ b/tests/kernel-cases/ladder/10-patterns-and-mirrors/004-mirror-body-joined/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "a 30x40x20 half with a d10 hole mirrored about x=30 and joined: the faces at x=30 coincide", + "source": "analytic", + "method": "the stock with two holes at x=20 and 40; the mirror face merges away", + "body_count": 1, + "volume_mm3": 44858.40734641, + "faces": 8 +} diff --git a/tests/kernel-cases/ladder/10-patterns-and-mirrors/004-mirror-body-joined/recipe.json b/tests/kernel-cases/ladder/10-patterns-and-mirrors/004-mirror-body-joined/recipe.json new file mode 100644 index 0000000000..014a80deb7 --- /dev/null +++ b/tests/kernel-cases/ladder/10-patterns-and-mirrors/004-mirror-body-joined/recipe.json @@ -0,0 +1,50 @@ +{ + "commands": [ + { + "command": "solid.box", + "params": { + "corner": [ + 0, + 0, + 0 + ], + "length": 30, + "width": 40, + "height": 20, + "body_name": "Part" + } + }, + { + "command": "solid.hole", + "params": { + "position": [ + 20, + 20, + 20 + ], + "diameter": 10 + } + }, + { + "command": "solid.mirror", + "params": { + "bodies": [ + "Part" + ], + "plane": { + "origin": [ + 30, + 0, + 0 + ], + "normal": [ + 1, + 0, + 0 + ] + }, + "combine": true + } + } + ] +} diff --git a/tests/kernel-cases/ladder/10-patterns-and-mirrors/005-mirror-feature/expected.json b/tests/kernel-cases/ladder/10-patterns-and-mirrors/005-mirror-feature/expected.json new file mode 100644 index 0000000000..e319819afc --- /dev/null +++ b/tests/kernel-cases/ladder/10-patterns-and-mirrors/005-mirror-feature/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "a d10 hole in the stock mirrored about x=30 as a feature", + "source": "analytic", + "method": "two holes pi 5^2 20", + "body_count": 1, + "volume_mm3": 44858.40734641, + "faces": 8 +} diff --git a/tests/kernel-cases/ladder/10-patterns-and-mirrors/005-mirror-feature/recipe.json b/tests/kernel-cases/ladder/10-patterns-and-mirrors/005-mirror-feature/recipe.json new file mode 100644 index 0000000000..1ae12b2044 --- /dev/null +++ b/tests/kernel-cases/ladder/10-patterns-and-mirrors/005-mirror-feature/recipe.json @@ -0,0 +1,37 @@ +{ + "base": "../../01-primitives/001-box", + "commands": [ + { + "command": "solid.hole", + "params": { + "position": [ + 20, + 20, + 20 + ], + "diameter": 10, + "name": "H" + } + }, + { + "command": "solid.mirror", + "params": { + "features": [ + "H" + ], + "plane": { + "origin": [ + 30, + 0, + 0 + ], + "normal": [ + 1, + 0, + 0 + ] + } + } + } + ] +} diff --git a/tests/kernel-cases/ladder/10-patterns-and-mirrors/006-circular-pattern-counterbores/expected.json b/tests/kernel-cases/ladder/10-patterns-and-mirrors/006-circular-pattern-counterbores/expected.json new file mode 100644 index 0000000000..48f56a4150 --- /dev/null +++ b/tests/kernel-cases/ladder/10-patterns-and-mirrors/006-circular-pattern-counterbores/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "a counterbored hole (d5, d9 x 3) patterned 6 times round a disk r30 h10", + "source": "analytic", + "method": "each removes pi 2.5^2 7 + pi 4.5^2 3; 3 faces each", + "body_count": 1, + "volume_mm3": 26304.555288507, + "faces": 21 +} diff --git a/tests/kernel-cases/ladder/10-patterns-and-mirrors/006-circular-pattern-counterbores/recipe.json b/tests/kernel-cases/ladder/10-patterns-and-mirrors/006-circular-pattern-counterbores/recipe.json new file mode 100644 index 0000000000..8bb170ab71 --- /dev/null +++ b/tests/kernel-cases/ladder/10-patterns-and-mirrors/006-circular-pattern-counterbores/recipe.json @@ -0,0 +1,42 @@ +{ + "commands": [ + { + "command": "solid.cylinder", + "params": { + "base": [ + 0, + 0, + 0 + ], + "radius": 30, + "height": 10, + "body_name": "Part" + } + }, + { + "command": "solid.hole", + "params": { + "position": [ + 20, + 0, + 10 + ], + "diameter": 5, + "type": "counterbore", + "cb_diameter": 9, + "cb_depth": 3, + "name": "CB" + } + }, + { + "command": "solid.pattern.circular", + "params": { + "features": [ + "CB" + ], + "axis": "Z", + "count": 6 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/10-patterns-and-mirrors/007-pattern-bodies/expected.json b/tests/kernel-cases/ladder/10-patterns-and-mirrors/007-pattern-bodies/expected.json new file mode 100644 index 0000000000..8729214073 --- /dev/null +++ b/tests/kernel-cases/ladder/10-patterns-and-mirrors/007-pattern-bodies/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "a pin r3 h10 patterned 4 times at 10 mm as bodies", + "source": "analytic", + "method": "four separate pins", + "body_count": 4, + "volume_mm3": 1130.973355292, + "faces": 12 +} diff --git a/tests/kernel-cases/ladder/10-patterns-and-mirrors/007-pattern-bodies/recipe.json b/tests/kernel-cases/ladder/10-patterns-and-mirrors/007-pattern-bodies/recipe.json new file mode 100644 index 0000000000..6b5da053c2 --- /dev/null +++ b/tests/kernel-cases/ladder/10-patterns-and-mirrors/007-pattern-bodies/recipe.json @@ -0,0 +1,32 @@ +{ + "commands": [ + { + "command": "solid.cylinder", + "params": { + "base": [ + 0, + 0, + 0 + ], + "radius": 3, + "height": 10, + "body_name": "Pin" + } + }, + { + "command": "solid.pattern.rectangular", + "params": { + "bodies": [ + "Pin" + ], + "dir1": [ + 1, + 0, + 0 + ], + "count1": 4, + "spacing1": 10 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/10-patterns-and-mirrors/008-part-vents/expected.json b/tests/kernel-cases/ladder/10-patterns-and-mirrors/008-part-vents/expected.json new file mode 100644 index 0000000000..fa109e963a --- /dev/null +++ b/tests/kernel-cases/ladder/10-patterns-and-mirrors/008-part-vents/expected.json @@ -0,0 +1,7 @@ +{ + "sources": [], + "what": "the running part with a d3 vent hole in its top patterned 4 times at 5 mm", + "source": "invariants", + "method": "holes through the shelled top skin; one valid body", + "body_count": 1 +} diff --git a/tests/kernel-cases/ladder/10-patterns-and-mirrors/008-part-vents/recipe.json b/tests/kernel-cases/ladder/10-patterns-and-mirrors/008-part-vents/recipe.json new file mode 100644 index 0000000000..1c59a59eab --- /dev/null +++ b/tests/kernel-cases/ladder/10-patterns-and-mirrors/008-part-vents/recipe.json @@ -0,0 +1,32 @@ +{ + "base": "../../09-shells-and-offsets/011-part-shell", + "commands": [ + { + "command": "solid.hole", + "params": { + "position": [ + 30, + 24, + 20 + ], + "diameter": 3, + "name": "Vent" + } + }, + { + "command": "solid.pattern.rectangular", + "params": { + "features": [ + "Vent" + ], + "dir1": [ + 1, + 0, + 0 + ], + "count1": 4, + "spacing1": 5 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/11-cumulative-parts/001-bracket/expected.json b/tests/kernel-cases/ladder/11-cumulative-parts/001-bracket/expected.json new file mode 100644 index 0000000000..cd370fddde --- /dev/null +++ b/tests/kernel-cases/ladder/11-cumulative-parts/001-bracket/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "an L bracket: concave r5 round, 5 mm outer chamfer, a hole in each leg", + "source": "analytic", + "method": "L section 60*8 + 8*42 = 816 times 40; + (1-pi/4) 25 40 (concave round) - 12.5*40 (chamfer) - 2 pi 4^2 8 (holes, 8 thick legs); features apart from each other", + "body_count": 1, + "volume_mm3": 31550.354117284, + "faces": 12 +} diff --git a/tests/kernel-cases/ladder/11-cumulative-parts/001-bracket/recipe.json b/tests/kernel-cases/ladder/11-cumulative-parts/001-bracket/recipe.json new file mode 100644 index 0000000000..05d90b772f --- /dev/null +++ b/tests/kernel-cases/ladder/11-cumulative-parts/001-bracket/recipe.json @@ -0,0 +1,117 @@ +{ + "commands": [ + { + "command": "sketch.create", + "params": { + "plane": { + "origin": [ + 0, + 40, + 0 + ], + "x_dir": [ + 1, + 0, + 0 + ], + "y_dir": [ + 0, + 0, + 1 + ] + } + } + }, + { + "command": "sketch.line", + "params": { + "points": [ + [ + 0, + 0 + ], + [ + 60, + 0 + ], + [ + 60, + 8 + ], + [ + 8, + 8 + ], + [ + 8, + 50 + ], + [ + 0, + 50 + ] + ], + "closed": true + } + }, + { + "command": "sketch.finish", + "params": {} + }, + { + "command": "solid.extrude", + "params": { + "distance": 40, + "body_name": "Part" + } + }, + { + "command": "solid.fillet", + "params": { + "edges": [ + [ + 8, + 20, + 8 + ] + ], + "radius": 5 + } + }, + { + "command": "solid.chamfer", + "params": { + "edges": [ + [ + 0, + 20, + 0 + ] + ], + "distance": 5 + } + }, + { + "command": "solid.hole", + "params": { + "position": [ + 40, + 20, + 8 + ], + "diameter": 8 + } + }, + { + "command": "solid.hole", + "params": { + "position": [ + 8, + 20, + 30 + ], + "diameter": 8 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/11-cumulative-parts/002-flange/expected.json b/tests/kernel-cases/ladder/11-cumulative-parts/002-flange/expected.json new file mode 100644 index 0000000000..a919854934 --- /dev/null +++ b/tests/kernel-cases/ladder/11-cumulative-parts/002-flange/expected.json @@ -0,0 +1,9 @@ +{ + "sources": [], + "what": "a revolved flange and hub with a bore, six bolt holes, a hub chamfer and a hub-root round", + "source": "analytic", + "method": "pi((40^2-8^2)10 + (15^2-8^2)20) - 6 pi 4^2 10 - 1.125 * 2 pi 14.5 (chamfer, Pappus) + (1-pi/4) 9 * 2 pi (15 + 3(10-3pi)/(12-3pi)) (concave round)", + "body_count": 1, + "volume_mm3": 55442.531825321, + "faces": 14 +} diff --git a/tests/kernel-cases/ladder/11-cumulative-parts/002-flange/recipe.json b/tests/kernel-cases/ladder/11-cumulative-parts/002-flange/recipe.json new file mode 100644 index 0000000000..c554a8f6ed --- /dev/null +++ b/tests/kernel-cases/ladder/11-cumulative-parts/002-flange/recipe.json @@ -0,0 +1,117 @@ +{ + "commands": [ + { + "command": "sketch.create", + "params": { + "plane": { + "origin": [ + 0, + 0, + 0 + ], + "x_dir": [ + 1, + 0, + 0 + ], + "y_dir": [ + 0, + 0, + 1 + ] + } + } + }, + { + "command": "sketch.line", + "params": { + "points": [ + [ + 8, + 0 + ], + [ + 40, + 0 + ], + [ + 40, + 10 + ], + [ + 15, + 10 + ], + [ + 15, + 30 + ], + [ + 8, + 30 + ] + ], + "closed": true + } + }, + { + "command": "sketch.finish", + "params": {} + }, + { + "command": "solid.revolve", + "params": { + "axis": "Z", + "body_name": "Part" + } + }, + { + "command": "solid.hole", + "params": { + "position": [ + 28, + 0, + 10 + ], + "diameter": 8, + "name": "Bolt" + } + }, + { + "command": "solid.pattern.circular", + "params": { + "features": [ + "Bolt" + ], + "axis": "Z", + "count": 6 + } + }, + { + "command": "solid.chamfer", + "params": { + "edges": [ + [ + 15, + 0, + 30 + ] + ], + "distance": 1.5 + } + }, + { + "command": "solid.fillet", + "params": { + "edges": [ + [ + 15, + 0, + 10 + ] + ], + "radius": 3 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/11-cumulative-parts/003-threaded-flange/expected.json b/tests/kernel-cases/ladder/11-cumulative-parts/003-threaded-flange/expected.json new file mode 100644 index 0000000000..ee716837de --- /dev/null +++ b/tests/kernel-cases/ladder/11-cumulative-parts/003-threaded-flange/expected.json @@ -0,0 +1,7 @@ +{ + "sources": [], + "what": "the flange with a modelled M30 thread on its hub, between the round and the chamfer, and M8 tapped holes in the disk", + "source": "invariants", + "method": "a thread meeting a round and a chamfer, tapped holes; one valid body", + "body_count": 1 +} diff --git a/tests/kernel-cases/ladder/11-cumulative-parts/003-threaded-flange/recipe.json b/tests/kernel-cases/ladder/11-cumulative-parts/003-threaded-flange/recipe.json new file mode 100644 index 0000000000..374d038748 --- /dev/null +++ b/tests/kernel-cases/ladder/11-cumulative-parts/003-threaded-flange/recipe.json @@ -0,0 +1,39 @@ +{ + "base": "../../11-cumulative-parts/002-flange", + "commands": [ + { + "command": "solid.thread", + "params": { + "face": [ + 15, + 0, + 20 + ], + "modeled": true, + "designation": "M30" + } + }, + { + "command": "solid.hole", + "params": { + "position": [ + 0, + 28, + 10 + ], + "thread": "M8", + "name": "Tap" + } + }, + { + "command": "solid.pattern.circular", + "params": { + "features": [ + "Tap" + ], + "axis": "Z", + "count": 3 + } + } + ] +} diff --git a/tests/kernel-cases/ladder/11-cumulative-parts/004-housing/expected.json b/tests/kernel-cases/ladder/11-cumulative-parts/004-housing/expected.json new file mode 100644 index 0000000000..86d052e430 --- /dev/null +++ b/tests/kernel-cases/ladder/11-cumulative-parts/004-housing/expected.json @@ -0,0 +1,7 @@ +{ + "sources": [], + "what": "the running part with every tier: back wall, boss, slot, spline window, tapped holes, rounds, chamfers, shell, vents, and a modelled M16 thread on the boss", + "source": "invariants", + "method": "the last case carries every tier; one valid body", + "body_count": 1 +} diff --git a/tests/kernel-cases/ladder/11-cumulative-parts/004-housing/recipe.json b/tests/kernel-cases/ladder/11-cumulative-parts/004-housing/recipe.json new file mode 100644 index 0000000000..dd3e6d322e --- /dev/null +++ b/tests/kernel-cases/ladder/11-cumulative-parts/004-housing/recipe.json @@ -0,0 +1,17 @@ +{ + "base": "../../10-patterns-and-mirrors/008-part-vents", + "commands": [ + { + "command": "solid.thread", + "params": { + "face": [ + 23, + 15, + 25 + ], + "modeled": true, + "designation": "M16" + } + } + ] +} diff --git a/tests/kernel-cases/ladder/golden.json b/tests/kernel-cases/ladder/golden.json new file mode 100644 index 0000000000..9c5c69eccb --- /dev/null +++ b/tests/kernel-cases/ladder/golden.json @@ -0,0 +1,2017 @@ +{ + "cases": { + "ladder/01-primitives/001-box": { + "default": { + "measured": { + "area_mm2": 8800.0, + "body_count": 1, + "edges": 12, + "faces": 6, + "vertices": 8, + "volume_mm3": 48000.0 + }, + "seconds": 0.00093575, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/01-primitives/002-cylinder": { + "default": { + "measured": { + "area_mm2": 2513.147252648079, + "body_count": 1, + "edges": 2, + "faces": 3, + "vertices": 2, + "volume_mm3": 9423.690515173837 + }, + "seconds": 0.0014005, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/01-primitives/003-cone": { + "default": { + "measured": { + "area_mm2": 1307.5019487288487, + "body_count": 1, + "edges": 2, + "faces": 2, + "vertices": 2, + "volume_mm3": 3141.019886181097 + }, + "seconds": 0.002922667, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/01-primitives/004-frustum": { + "default": { + "measured": { + "area_mm2": 1364.104142163897, + "body_count": 1, + "edges": 2, + "faces": 3, + "vertices": 2, + "volume_mm3": 3664.869667183306 + }, + "seconds": 0.002304042, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/01-primitives/005-sphere": { + "default": { + "measured": { + "area_mm2": 1256.5932642692544, + "body_count": 1, + "edges": 0, + "faces": 1, + "vertices": 0, + "volume_mm3": 4188.493085661013 + }, + "seconds": 0.004350375, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/01-primitives/006-torus": { + "default": { + "measured": { + "area_mm2": 3947.6911127816757, + "body_count": 1, + "edges": 0, + "faces": 1, + "vertices": 0, + "volume_mm3": 9868.15605110991 + }, + "seconds": 0.0115975, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/02-booleans/001-coincident-face-join": { + "default": { + "measured": { + "area_mm2": 10800.0, + "body_count": 1, + "edges": 12, + "faces": 6, + "vertices": 8, + "volume_mm3": 72000.0 + }, + "seconds": 0.006791125, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/02-booleans/002-coplanar-overlap-join": { + "default": { + "measured": { + "area_mm2": 14400.0, + "body_count": 1, + "edges": 24, + "faces": 10, + "vertices": 16, + "volume_mm3": 84000.0 + }, + "seconds": 0.005450958, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/02-booleans/003-pocket-flush-with-top": { + "default": { + "measured": { + "area_mm2": 9600.0, + "body_count": 1, + "edges": 24, + "faces": 11, + "vertices": 16, + "volume_mm3": 44000.0 + }, + "seconds": 0.004264125, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/02-booleans/004-edge-on-edge-no-overlap": { + "default": { + "measured": { + "area_mm2": 8800.0, + "body_count": 1, + "edges": 12, + "faces": 6, + "vertices": 8, + "volume_mm3": 48000.0 + }, + "seconds": 0.003738709, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/02-booleans/005-vertex-on-face-no-overlap": { + "default": { + "measured": { + "area_mm2": 8800.0, + "body_count": 1, + "edges": 12, + "faces": 6, + "vertices": 8, + "volume_mm3": 48000.0 + }, + "seconds": 0.007946417, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/02-booleans/006-tangent-along-line-no-overlap": { + "default": { + "measured": { + "area_mm2": 8800.0, + "body_count": 1, + "edges": 12, + "faces": 6, + "vertices": 8, + "volume_mm3": 48000.0 + }, + "seconds": 0.010566583, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/02-booleans/007-near-tangent-gap-1e-3": { + "default": { + "measured": { + "area_mm2": 8800.0, + "body_count": 1, + "edges": 12, + "faces": 6, + "vertices": 8, + "volume_mm3": 48000.0 + }, + "seconds": 0.010871792, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/02-booleans/008-near-tangent-gap-1e-6": { + "default": { + "measured": { + "area_mm2": 8800.0, + "body_count": 1, + "edges": 12, + "faces": 6, + "vertices": 8, + "volume_mm3": 48000.0 + }, + "seconds": 0.010626125, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/02-booleans/009-near-tangent-overlap-1e-3": { + "default": { + "measured": { + "area_mm2": 8799.999999999882, + "body_count": 1, + "edges": 18, + "faces": 8, + "vertices": 12, + "volume_mm3": 47999.99999999921 + }, + "seconds": 0.007120167, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/02-booleans/010-near-tangent-overlap-1e-6": { + "default": { + "measured": { + "area_mm2": 8800.0, + "body_count": 1, + "edges": 12, + "faces": 6, + "vertices": 8, + "volume_mm3": 48000.0 + }, + "seconds": 0.010546, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/02-booleans/011-tangent-along-circle-through-hole": { + "default": { + "measured": { + "area_mm2": 9428.370711515512, + "body_count": 1, + "edges": 14, + "faces": 7, + "vertices": 10, + "volume_mm3": 41717.85835306114 + }, + "seconds": 0.048083, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/02-booleans/012-tangent-along-circle-bowl": { + "default": { + "seconds": 3.343824333, + "status": "fail" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/02-booleans/013-cut-across-cylinder-seam": { + "default": { + "measured": { + "area_mm2": 2036.78119982643, + "body_count": 1, + "edges": 12, + "faces": 6, + "vertices": 8, + "volume_mm3": 5887.9766980280665 + }, + "seconds": 0.006591084, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/02-booleans/014-cut-through-sphere-pole": { + "default": { + "measured": { + "area_mm2": 1332.0313215995125, + "body_count": 1, + "edges": 12, + "faces": 6, + "vertices": 8, + "volume_mm3": 4110.623680055086 + }, + "seconds": 0.334621125, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/02-booleans/015-knife-edge-wedge-cut": { + "default": { + "measured": { + "area_mm2": 6929.822128134703, + "body_count": 1, + "edges": 9, + "faces": 5, + "vertices": 6, + "volume_mm3": 24000.0 + }, + "seconds": 0.00465175, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/02-booleans/016-v-groove-pinches-bottom-to-line": { + "default": { + "measured": { + "area_mm2": 9225.904606350496, + "body_count": 1, + "edges": 23, + "faces": 11, + "vertices": 14, + "volume_mm3": 38400.0 + }, + "seconds": 0.00609325, + "status": "fail" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/02-booleans/017-cylinder-tangent-top-and-bottom": { + "default": { + "measured": { + "area_mm2": 11941.534849086385, + "body_count": 1, + "edges": 22, + "faces": 9, + "vertices": 12, + "volume_mm3": 29153.91062636393 + }, + "seconds": 0.014504542, + "status": "fail" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/02-booleans/018-hole-tangent-to-side-face": { + "default": { + "measured": { + "area_mm2": 9428.373503399229, + "body_count": 1, + "edges": 14, + "faces": 7, + "vertices": 10, + "volume_mm3": 41717.91429077657 + }, + "seconds": 0.009196667, + "status": "fail" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/02-booleans/019-notch-flush-with-end-face": { + "default": { + "measured": { + "area_mm2": 8800.0, + "body_count": 1, + "edges": 24, + "faces": 10, + "vertices": 16, + "volume_mm3": 32000.0 + }, + "seconds": 0.005541791, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/02-booleans/020-part-back-wall": { + "default": { + "measured": { + "area_mm2": 10900.0, + "body_count": 1, + "edges": 18, + "faces": 8, + "vertices": 12, + "volume_mm3": 57000.0 + }, + "seconds": 0.00790175, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/03-analytic-profiles/001-extrude-rect-with-arc-end": { + "default": { + "measured": { + "area_mm2": 10513.088490445303, + "body_count": 1, + "edges": 12, + "faces": 6, + "vertices": 8, + "volume_mm3": 60564.88556370952 + }, + "seconds": 0.001467834, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/03-analytic-profiles/002-extrude-annulus": { + "default": { + "measured": { + "area_mm2": 4712.234879709544, + "body_count": 1, + "edges": 4, + "faces": 4, + "vertices": 4, + "volume_mm3": 14136.480098042548 + }, + "seconds": 0.002882042, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/03-analytic-profiles/003-extrude-tapered-circle": { + "default": { + "measured": { + "area_mm2": 1678.9787421189897, + "body_count": 1, + "edges": 2, + "faces": 3, + "vertices": 2, + "volume_mm3": 5247.30382974983 + }, + "seconds": 0.001769, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/03-analytic-profiles/004-revolve-full-off-axis": { + "default": { + "measured": { + "area_mm2": 7539.473905549408, + "body_count": 1, + "edges": 4, + "faces": 4, + "vertices": 4, + "volume_mm3": 28269.679056500216 + }, + "seconds": 0.014281334, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/03-analytic-profiles/005-revolve-touching-axis-hemisphere": { + "default": { + "measured": { + "area_mm2": 3769.6980702123765, + "body_count": 1, + "edges": 1, + "faces": 2, + "vertices": 1, + "volume_mm3": 16753.416629342133 + }, + "seconds": 0.015970542, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/03-analytic-profiles/006-revolve-partial-90": { + "default": { + "measured": { + "area_mm2": 1142.457601758199, + "body_count": 1, + "edges": 12, + "faces": 6, + "vertices": 8, + "volume_mm3": 2356.190459014158 + }, + "seconds": 0.014789083, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/03-analytic-profiles/007-revolve-180-touching-axis": { + "default": { + "measured": { + "area_mm2": 2249.241075072362, + "body_count": 1, + "edges": 6, + "faces": 4, + "vertices": 4, + "volume_mm3": 7067.894042431624 + }, + "seconds": 0.002638875, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/03-analytic-profiles/008-extrude-slot": { + "default": { + "measured": { + "area_mm2": 3656.5330999392927, + "body_count": 1, + "edges": 12, + "faces": 6, + "vertices": 8, + "volume_mm3": 11141.176810322673 + }, + "seconds": 0.002881375, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/03-analytic-profiles/009-part-boss-and-slot": { + "default": { + "measured": { + "area_mm2": 12164.760282822132, + "body_count": 1, + "edges": 32, + "faces": 14, + "vertices": 22, + "volume_mm3": 55605.24907108947 + }, + "seconds": 0.12831875, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/04-spline-profiles/001-extrude-bezier-arch": { + "default": { + "measured": { + "area_mm2": 2766.769042067676, + "body_count": 1, + "edges": 12, + "faces": 6, + "vertices": 8, + "volume_mm3": 8199.694831123337 + }, + "seconds": 0.002273333, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/04-spline-profiles/002-revolve-bezier-profile": { + "default": { + "measured": { + "area_mm2": 5856.745425824418, + "body_count": 1, + "edges": 2, + "faces": 3, + "vertices": 2, + "volume_mm3": 34130.36142790597 + }, + "seconds": 0.031965375, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/04-spline-profiles/003-pipe-along-bezier": { + "default": { + "measured": { + "area_mm2": 605.6085153085496, + "body_count": 1, + "edges": 2, + "faces": 3, + "vertices": 2, + "volume_mm3": 580.4692497771506 + }, + "seconds": 0.040463833, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/04-spline-profiles/004-sweep-square-along-bezier": { + "default": { + "measured": { + "area_mm2": 770.7553071789786, + "body_count": 1, + "edges": 12, + "faces": 6, + "vertices": 8, + "volume_mm3": 738.4215101809574 + }, + "seconds": 0.026751333, + "status": "fail" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/04-spline-profiles/005-loft-square-frustum": { + "default": { + "measured": { + "area_mm2": 5794.733192202047, + "body_count": 1, + "edges": 12, + "faces": 6, + "vertices": 8, + "volume_mm3": 27999.999999999887 + }, + "seconds": 0.003325292, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/04-spline-profiles/006-loft-circle-frustum": { + "default": { + "measured": { + "area_mm2": 4550.945564916025, + "body_count": 1, + "edges": 2, + "faces": 3, + "vertices": 2, + "volume_mm3": 21990.05727759267 + }, + "seconds": 0.003139958, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/04-spline-profiles/007-loft-three-circles": { + "default": { + "measured": { + "area_mm2": 6453.8181258884515, + "body_count": 1, + "edges": 2, + "faces": 3, + "vertices": 2, + "volume_mm3": 23459.759039219105 + }, + "seconds": 0.003198208, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/04-spline-profiles/008-loft-square-to-circle": { + "default": { + "measured": { + "area_mm2": 5455.544745750849, + "body_count": 1, + "edges": 12, + "faces": 6, + "vertices": 8, + "volume_mm3": 26343.66696300323 + }, + "seconds": 0.002894375, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/04-spline-profiles/009-extrude-periodic-fit-spline": { + "default": { + "measured": { + "area_mm2": 3698.2551381710246, + "body_count": 1, + "edges": 18, + "faces": 8, + "vertices": 12, + "volume_mm3": 12279.46663039849 + }, + "seconds": 0.003586333, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/04-spline-profiles/010-part-bezier-window": { + "default": { + "measured": { + "area_mm2": 12389.025843079045, + "body_count": 1, + "edges": 38, + "faces": 16, + "vertices": 26, + "volume_mm3": 54273.36245081687 + }, + "seconds": 0.280533708, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/05-holes-and-taps/001-simple-through": { + "default": { + "measured": { + "area_mm2": 9271.238896931009, + "body_count": 1, + "edges": 14, + "faces": 7, + "vertices": 10, + "volume_mm3": 46429.46458826573 + }, + "seconds": 0.009346917, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/05-holes-and-taps/002-simple-blind-flat": { + "default": { + "measured": { + "area_mm2": 9114.146219052129, + "body_count": 1, + "edges": 14, + "faces": 8, + "vertices": 10, + "volume_mm3": 47214.732294132635 + }, + "seconds": 0.009338, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/05-holes-and-taps/003-drilled-blind-118": { + "default": { + "measured": { + "area_mm2": 9127.233531923126, + "body_count": 1, + "edges": 15, + "faces": 8, + "vertices": 11, + "volume_mm3": 47136.10165280392 + }, + "seconds": 0.081221125, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/05-holes-and-taps/004-counterbore": { + "default": { + "measured": { + "area_mm2": 9234.726866633784, + "body_count": 1, + "edges": 16, + "faces": 9, + "vertices": 12, + "volume_mm3": 46926.65268551433 + }, + "seconds": 0.017519167, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/05-holes-and-taps/005-countersink": { + "default": { + "measured": { + "area_mm2": 9122.958367725925, + "body_count": 1, + "edges": 15, + "faces": 8, + "vertices": 11, + "volume_mm3": 47219.68222572232 + }, + "seconds": 0.189785083, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/05-holes-and-taps/006-tapped-m6-through": { + "default": { + "measured": { + "area_mm2": 9074.882833758822, + "body_count": 1, + "edges": 14, + "faces": 7, + "vertices": 10, + "volume_mm3": 47607.36614706644 + }, + "seconds": 0.011172708, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/05-holes-and-taps/007-tapped-m8-blind": { + "default": { + "measured": { + "area_mm2": 9060.420997695732, + "body_count": 1, + "edges": 15, + "faces": 8, + "vertices": 11, + "volume_mm3": 47546.474471511 + }, + "seconds": 0.07102625, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/05-holes-and-taps/008-tapped-m5-into-curved-face": { + "default": { + "measured": { + "area_mm2": 10551.5388137954, + "body_count": 1, + "edges": 4, + "faces": 4, + "vertices": 4, + "volume_mm3": 74842.7885077873 + }, + "seconds": 0.373989209, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/05-holes-and-taps/009-hole-0.5-from-edge": { + "default": { + "measured": { + "area_mm2": 9271.238896931007, + "body_count": 1, + "edges": 14, + "faces": 7, + "vertices": 10, + "volume_mm3": 46429.46458826572 + }, + "seconds": 0.009341583, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/05-holes-and-taps/010-hole-tangent-to-end-face": { + "default": { + "measured": { + "area_mm2": 9271.238892022762, + "body_count": 1, + "edges": 14, + "faces": 7, + "vertices": 10, + "volume_mm3": 46429.543922858364 + }, + "seconds": 0.009070708, + "status": "fail" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/05-holes-and-taps/011-crossing-holes-steinmetz": { + "default": { + "measured": { + "area_mm2": 9970.801353767854, + "body_count": 1, + "edges": 17, + "faces": 8, + "vertices": 13, + "volume_mm3": 43954.432213307766 + }, + "seconds": 0.494772542, + "status": "fail" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/05-holes-and-taps/012-crossing-tapped-holes-offset": { + "default": { + "measured": { + "area_mm2": 9586.60796229969, + "body_count": 1, + "edges": 17, + "faces": 8, + "vertices": 13, + "volume_mm3": 46870.36213666151 + }, + "seconds": 0.636756584, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/05-holes-and-taps/013-part-holes": { + "default": { + "measured": { + "area_mm2": 13115.19280667113, + "body_count": 1, + "edges": 44, + "faces": 20, + "vertices": 32, + "volume_mm3": 53470.583718975606 + }, + "seconds": 2.9695160830000003, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/06-threads/001-external-m10-full": { + "default": { + "measured": { + "area_mm2": 1529.4042487894849, + "body_count": 1, + "edges": 16354, + "faces": 6730, + "vertices": 9616, + "volume_mm3": 1948.741057097491 + }, + "seconds": 2.172735334, + "status": "fail" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/06-threads/002-external-m10x1-fine": { + "default": { + "measured": { + "area_mm2": 1584.174824512129, + "body_count": 1, + "edges": 26902, + "faces": 10956, + "vertices": 15906, + "volume_mm3": 2076.804702383333 + }, + "seconds": 2.68426125, + "status": "fail" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/06-threads/003-external-m10-partial-length": { + "default": { + "measured": { + "area_mm2": 1314.9911695222038, + "body_count": 1, + "edges": 8302, + "faces": 3409, + "vertices": 4894, + "volume_mm3": 2151.422182916339 + }, + "seconds": 0.590479958, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/06-threads/004-external-m10-to-shoulder": { + "default": { + "measured": { + "area_mm2": 2169.9815981466654, + "body_count": 1, + "edges": 14480, + "faces": 6021, + "vertices": 8454, + "volume_mm3": 3610.2325548497306 + }, + "seconds": 1.5457759580000001, + "status": "fail" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/06-threads/005-internal-m10-through": { + "default": { + "measured": { + "area_mm2": 4975.095933679673, + "body_count": 1, + "edges": 10555, + "faces": 4364, + "vertices": 6173, + "volume_mm3": 16691.009759504228 + }, + "seconds": 0.805998584, + "status": "fail" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/06-threads/006-external-m10-into-chamfer": { + "default": { + "measured": { + "area_mm2": 1508.5872297957983, + "body_count": 1, + "edges": 16358, + "faces": 6738, + "vertices": 9614, + "volume_mm3": 1943.2536102766353 + }, + "seconds": 2.40492525, + "status": "fail" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/06-threads/007-external-m10-at-filleted-shoulder": { + "default": { + "measured": { + "area_mm2": 2121.5899255556014, + "body_count": 1, + "edges": 16693, + "faces": 7123, + "vertices": 9555, + "volume_mm3": 3631.4173516267765 + }, + "seconds": 9.833373459, + "status": "fail" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/06-threads/008-multi-start-thread": { + "default": { + "seconds": null, + "status": "unsupported" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/06-threads/009-thread-runout-length": { + "default": { + "seconds": null, + "status": "unsupported" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/06-threads/010-part-threaded-boss": { + "default": { + "measured": { + "area_mm2": 13583.96229831793, + "body_count": 1, + "edges": 6255, + "faces": 2683, + "vertices": 3567, + "volume_mm3": 53114.87966146553 + }, + "seconds": 2.9410735409999997, + "status": "fail" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/07-fillets/001-single-edge-r5": { + "default": { + "measured": { + "area_mm2": 8660.480655132957, + "body_count": 1, + "edges": 15, + "faces": 7, + "vertices": 10, + "volume_mm3": 47677.886125669735 + }, + "seconds": 0.016275167, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/07-fillets/002-four-vertical-edges-r5": { + "default": { + "measured": { + "area_mm2": 8585.34186016752, + "body_count": 1, + "edges": 24, + "faces": 10, + "vertices": 16, + "volume_mm3": 47570.514759229154 + }, + "seconds": 0.032937417, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/07-fillets/003-tangent-chain-r3": { + "default": { + "measured": { + "area_mm2": 8334.875841941504, + "body_count": 1, + "edges": 40, + "faces": 18, + "vertices": 24, + "volume_mm3": 47208.95029779139 + }, + "seconds": 0.061546042, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/07-fillets/004-convex-and-concave-r5": { + "default": { + "measured": { + "area_mm2": 4514.145186810165, + "body_count": 1, + "edges": 24, + "faces": 10, + "vertices": 16, + "volume_mm3": 13999.99998749348 + }, + "seconds": 0.0121775, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/07-fillets/005-variable-r5-to-r1": { + "default": { + "measured": { + "area_mm2": 8717.215054233078, + "body_count": 1, + "edges": 15, + "faces": 7, + "vertices": 10, + "volume_mm3": 47866.92526435128 + }, + "seconds": 0.004042125, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/07-fillets/006-variable-r5-to-zero": { + "default": { + "measured": { + "area_mm2": 8730.44634473237, + "body_count": 1, + "edges": 14, + "faces": 7, + "vertices": 9, + "volume_mm3": 47892.61481331315 + }, + "seconds": 0.004036291, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/07-fillets/007-variable-r5-to-1e-3": { + "default": { + "measured": { + "area_mm2": 8730.321541410496, + "body_count": 1, + "edges": 16, + "faces": 7, + "vertices": 11, + "volume_mm3": 47892.606779337424 + }, + "seconds": 0.004170334, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/07-fillets/008-constant-r1e-3": { + "default": { + "measured": { + "area_mm2": 8799.964851815777, + "body_count": 1, + "edges": 15, + "faces": 7, + "vertices": 10, + "volume_mm3": 47999.999970019206 + }, + "seconds": 0.011696125, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/07-fillets/009-corner-three-equal-r5": { + "default": { + "measured": { + "area_mm2": 8522.789688489427, + "body_count": 1, + "edges": 21, + "faces": 10, + "vertices": 13, + "volume_mm3": 47376.731598742124 + }, + "seconds": 0.0360895, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/07-fillets/010-corner-unequal-r7-r3": { + "default": { + "measured": { + "area_mm2": 8589.092595801692, + "body_count": 1, + "edges": 22, + "faces": 10, + "vertices": 14, + "volume_mm3": 47604.19999568275 + }, + "seconds": 0.046529333, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/07-fillets/011-chain-ending-at-corner": { + "default": { + "measured": { + "area_mm2": 8571.103566007207, + "body_count": 1, + "edges": 17, + "faces": 8, + "vertices": 11, + "volume_mm3": 47475.28654740171 + }, + "seconds": 0.031272708, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/07-fillets/012-variable-into-rounded-corner": { + "default": { + "seconds": 0.019334792, + "status": "unsupported" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/07-fillets/013-corner-setback": { + "default": { + "seconds": null, + "status": "unsupported" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/07-fillets/014-curved-edge-r2": { + "default": { + "measured": { + "area_mm2": 1829.210906234833, + "body_count": 1, + "edges": 3, + "faces": 4, + "vertices": 3, + "volume_mm3": 6231.437377339935 + }, + "seconds": 0.023461666, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/07-fillets/015-corner-g2-patch": { + "default": { + "seconds": 0.009254333, + "status": "unsupported" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/07-fillets/016-corner-three-sequential-r5-r3-r7": { + "default": { + "seconds": 0.080988792, + "status": "fail" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/07-fillets/017-part-rounds": { + "default": { + "measured": { + "area_mm2": 12905.615501917395, + "body_count": 1, + "edges": 54, + "faces": 24, + "vertices": 39, + "volume_mm3": 53151.04538041395 + }, + "seconds": 4.101630291, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/08-chamfers/001-equal-5": { + "default": { + "measured": { + "area_mm2": 8599.264068711927, + "body_count": 1, + "edges": 15, + "faces": 7, + "vertices": 10, + "volume_mm3": 47250.0 + }, + "seconds": 0.012236375, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/08-chamfers/002-two-distances-5-3": { + "default": { + "measured": { + "area_mm2": 8654.857113690718, + "body_count": 1, + "edges": 15, + "faces": 7, + "vertices": 10, + "volume_mm3": 47550.0 + }, + "seconds": 0.012206292, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/08-chamfers/003-distance-angle-5-30": { + "default": { + "measured": { + "area_mm2": 8658.771324027148, + "body_count": 1, + "edges": 15, + "faces": 7, + "vertices": 10, + "volume_mm3": 47566.987298107786 + }, + "seconds": 0.012881625, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/08-chamfers/004-corner-three-equal-3": { + "default": { + "measured": { + "area_mm2": 8573.979057816261, + "body_count": 1, + "edges": 21, + "faces": 9, + "vertices": 14, + "volume_mm3": 47480.25 + }, + "seconds": 0.035578625, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/08-chamfers/005-corner-two-mitred-3": { + "default": { + "measured": { + "area_mm2": 8611.536146650571, + "body_count": 1, + "edges": 17, + "faces": 8, + "vertices": 11, + "volume_mm3": 47559.0 + }, + "seconds": 0.0224305, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/08-chamfers/006-curved-edge-2": { + "default": { + "measured": { + "area_mm2": 1806.037992350068, + "body_count": 1, + "edges": 3, + "faces": 4, + "vertices": 3, + "volume_mm3": 6165.23680786219 + }, + "seconds": 0.060670625, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/08-chamfers/007-chamfer-then-fillets": { + "default": { + "measured": { + "area_mm2": 8652.790296884197, + "body_count": 1, + "edges": 21, + "faces": 9, + "vertices": 14, + "volume_mm3": 47700.035898423615 + }, + "seconds": 0.040296208, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/08-chamfers/008-part-chamfers": { + "default": { + "measured": { + "area_mm2": 12800.576019818938, + "body_count": 1, + "edges": 58, + "faces": 26, + "vertices": 42, + "volume_mm3": 53006.96565814107 + }, + "seconds": 7.966854958, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/09-shells-and-offsets/001-box-open-top-t2": { + "default": { + "measured": { + "area_mm2": 12112.0, + "body_count": 1, + "edges": 24, + "faces": 11, + "vertices": 16, + "volume_mm3": 11712.0 + }, + "seconds": 0.004860833, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/09-shells-and-offsets/002-cylinder-open-top-t1": { + "default": { + "measured": { + "area_mm2": 4153.008908296044, + "body_count": 1, + "edges": 4, + "faces": 5, + "vertices": 4, + "volume_mm3": 2044.9841165189566 + }, + "seconds": 0.024618375, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/09-shells-and-offsets/003-tube-two-open-faces": { + "default": { + "measured": { + "area_mm2": 8448.0, + "body_count": 1, + "edges": 24, + "faces": 10, + "vertices": 16, + "volume_mm3": 7680.0 + }, + "seconds": 0.004447042, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/09-shells-and-offsets/004-thin-wall-0.2": { + "default": { + "measured": { + "area_mm2": 12728.32, + "body_count": 1, + "edges": 24, + "faces": 11, + "vertices": 16, + "volume_mm3": 1268.8320000000078 + }, + "seconds": 0.004979333, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/09-shells-and-offsets/005-shell-rounds-r5-t2": { + "default": { + "measured": { + "area_mm2": 11804.643214143103, + "body_count": 1, + "edges": 48, + "faces": 19, + "vertices": 32, + "volume_mm3": 11421.790820329137 + }, + "seconds": 0.046784125, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/09-shells-and-offsets/006-shell-rounds-r1-thinner-than-t2": { + "default": { + "measured": { + "area_mm2": 12075.93165009534, + "body_count": 1, + "edges": 36, + "faces": 15, + "vertices": 24, + "volume_mm3": 11694.806432750607 + }, + "seconds": 0.043761042, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/09-shells-and-offsets/007-offset-top-face-5": { + "default": { + "measured": { + "area_mm2": 9800.0, + "body_count": 1, + "edges": 12, + "faces": 6, + "vertices": 8, + "volume_mm3": 60000.0 + }, + "seconds": 0.002552917, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/09-shells-and-offsets/008-offset-cylinder-wall-2": { + "default": { + "measured": { + "area_mm2": 2412.5985226719185, + "body_count": 1, + "edges": 2, + "faces": 3, + "vertices": 2, + "volume_mm3": 9046.765907054167 + }, + "seconds": 0.009921875, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/09-shells-and-offsets/009-shell-outside-t2": { + "default": { + "measured": { + "area_mm2": 14384.0, + "body_count": 1, + "edges": 24, + "faces": 11, + "vertices": 16, + "volume_mm3": 13952.0 + }, + "seconds": 0.006274125, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/09-shells-and-offsets/010-shell-under-rounded-top": { + "default": { + "measured": { + "area_mm2": 11769.800232101234, + "body_count": 1, + "edges": 40, + "faces": 19, + "vertices": 24, + "volume_mm3": 11375.069952750084 + }, + "seconds": 0.08896025, + "status": "fail" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/09-shells-and-offsets/011-part-shell": { + "default": { + "seconds": 8.184976875, + "status": "fail" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/10-patterns-and-mirrors/001-rect-pattern-holes": { + "default": { + "measured": { + "area_mm2": 7506.858345396518, + "body_count": 1, + "edges": 24, + "faces": 12, + "vertices": 20, + "volume_mm3": 22822.098441199283 + }, + "seconds": 0.041283709, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/10-patterns-and-mirrors/002-circular-pattern-holes": { + "default": { + "measured": { + "area_mm2": 8595.26292897519, + "body_count": 1, + "edges": 18, + "faces": 11, + "vertices": 18, + "volume_mm3": 26012.083999715745 + }, + "seconds": 0.060355292, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/10-patterns-and-mirrors/003-overlapping-pattern-leaves-core": { + "default": { + "measured": { + "area_mm2": 3867.92079909876, + "body_count": 2, + "edges": 38, + "faces": 17, + "vertices": 26, + "volume_mm3": 9195.609192170054 + }, + "seconds": 0.044988292, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/10-patterns-and-mirrors/004-mirror-body-joined": { + "default": { + "measured": { + "area_mm2": 9742.477793862017, + "body_count": 1, + "edges": 16, + "faces": 8, + "vertices": 12, + "volume_mm3": 44858.92917653146 + }, + "seconds": 0.025089084, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/10-patterns-and-mirrors/005-mirror-feature": { + "default": { + "measured": { + "area_mm2": 9742.477793862021, + "body_count": 1, + "edges": 16, + "faces": 8, + "vertices": 12, + "volume_mm3": 44858.929176531434 + }, + "seconds": 0.016752334, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/10-patterns-and-mirrors/006-circular-pattern-counterbores": { + "default": { + "measured": { + "area_mm2": 8472.778471680762, + "body_count": 1, + "edges": 26, + "faces": 21, + "vertices": 26, + "volume_mm3": 26304.386263968252 + }, + "seconds": 0.138256875, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/10-patterns-and-mirrors/007-pattern-bodies": { + "default": { + "measured": { + "area_mm2": 980.1266278426201, + "body_count": 4, + "edges": 8, + "faces": 12, + "vertices": 8, + "volume_mm3": 1130.836229678923 + }, + "seconds": 0.001980667, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/10-patterns-and-mirrors/008-part-vents": { + "default": { + "seconds": 8.422307333, + "status": "fail" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/11-cumulative-parts/001-bracket": { + "default": { + "measured": { + "area_mm2": 10415.806728296244, + "body_count": 1, + "edges": 28, + "faces": 12, + "vertices": 20, + "volume_mm3": 31550.56707386659 + }, + "seconds": 0.194415458, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/11-cumulative-parts/002-flange": { + "default": { + "measured": { + "area_mm2": 16258.711483873363, + "body_count": 1, + "edges": 20, + "faces": 14, + "vertices": 20, + "volume_mm3": 55442.22875030476 + }, + "seconds": 0.175310166, + "status": "pass" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/11-cumulative-parts/003-threaded-flange": { + "default": { + "measured": { + "area_mm2": 18077.627618549766, + "body_count": 1, + "edges": 9955, + "faces": 4307, + "vertices": 5649, + "volume_mm3": 52427.566640422 + }, + "seconds": 5.985274333, + "status": "fail" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + }, + "ladder/11-cumulative-parts/004-housing": { + "default": { + "seconds": 8.909698459, + "status": "fail" + }, + "smlib": { + "seconds": null, + "status": "unsupported" + } + } + }, + "kernels": [ + "default", + "smlib" + ], + "reason": "first recording of the ladder (truck; SMLib booleans not on this branch yet)" +} diff --git a/tests/kernel-cases/ladder/ladder.json b/tests/kernel-cases/ladder/ladder.json new file mode 100644 index 0000000000..50a8ebb4b4 --- /dev/null +++ b/tests/kernel-cases/ladder/ladder.json @@ -0,0 +1,187 @@ +{ + "tiers": [ + { + "cases": [ + "ladder/01-primitives/001-box", + "ladder/01-primitives/002-cylinder", + "ladder/01-primitives/004-frustum", + "ladder/01-primitives/003-cone", + "ladder/01-primitives/005-sphere", + "ladder/01-primitives/006-torus" + ], + "tier": "01-primitives", + "what": "primitives with closed-form volumes" + }, + { + "cases": [ + "ladder/02-booleans/004-edge-on-edge-no-overlap", + "ladder/02-booleans/003-pocket-flush-with-top", + "ladder/02-booleans/015-knife-edge-wedge-cut", + "ladder/02-booleans/002-coplanar-overlap-join", + "ladder/02-booleans/019-notch-flush-with-end-face", + "ladder/02-booleans/016-v-groove-pinches-bottom-to-line", + "ladder/02-booleans/013-cut-across-cylinder-seam", + "ladder/02-booleans/001-coincident-face-join", + "ladder/02-booleans/009-near-tangent-overlap-1e-3", + "ladder/02-booleans/020-part-back-wall", + "ladder/02-booleans/005-vertex-on-face-no-overlap", + "ladder/02-booleans/018-hole-tangent-to-side-face", + "ladder/02-booleans/010-near-tangent-overlap-1e-6", + "ladder/02-booleans/006-tangent-along-line-no-overlap", + "ladder/02-booleans/008-near-tangent-gap-1e-6", + "ladder/02-booleans/007-near-tangent-gap-1e-3", + "ladder/02-booleans/017-cylinder-tangent-top-and-bottom", + "ladder/02-booleans/011-tangent-along-circle-through-hole", + "ladder/02-booleans/014-cut-through-sphere-pole", + "ladder/02-booleans/012-tangent-along-circle-bowl" + ], + "tier": "02-booleans", + "what": "booleans in the degeneracy classes, and cuts that end in a single line" + }, + { + "cases": [ + "ladder/03-analytic-profiles/001-extrude-rect-with-arc-end", + "ladder/03-analytic-profiles/003-extrude-tapered-circle", + "ladder/03-analytic-profiles/007-revolve-180-touching-axis", + "ladder/03-analytic-profiles/008-extrude-slot", + "ladder/03-analytic-profiles/002-extrude-annulus", + "ladder/03-analytic-profiles/004-revolve-full-off-axis", + "ladder/03-analytic-profiles/006-revolve-partial-90", + "ladder/03-analytic-profiles/005-revolve-touching-axis-hemisphere", + "ladder/03-analytic-profiles/009-part-boss-and-slot" + ], + "tier": "03-analytic-profiles", + "what": "extrudes and revolves of line-and-arc profiles" + }, + { + "cases": [ + "ladder/04-spline-profiles/001-extrude-bezier-arch", + "ladder/04-spline-profiles/008-loft-square-to-circle", + "ladder/04-spline-profiles/006-loft-circle-frustum", + "ladder/04-spline-profiles/007-loft-three-circles", + "ladder/04-spline-profiles/005-loft-square-frustum", + "ladder/04-spline-profiles/009-extrude-periodic-fit-spline", + "ladder/04-spline-profiles/004-sweep-square-along-bezier", + "ladder/04-spline-profiles/002-revolve-bezier-profile", + "ladder/04-spline-profiles/003-pipe-along-bezier", + "ladder/04-spline-profiles/010-part-bezier-window" + ], + "tier": "04-spline-profiles", + "what": "spline profiles, sweeps and lofts" + }, + { + "cases": [ + "ladder/05-holes-and-taps/010-hole-tangent-to-end-face", + "ladder/05-holes-and-taps/002-simple-blind-flat", + "ladder/05-holes-and-taps/009-hole-0.5-from-edge", + "ladder/05-holes-and-taps/001-simple-through", + "ladder/05-holes-and-taps/006-tapped-m6-through", + "ladder/05-holes-and-taps/004-counterbore", + "ladder/05-holes-and-taps/007-tapped-m8-blind", + "ladder/05-holes-and-taps/003-drilled-blind-118", + "ladder/05-holes-and-taps/005-countersink", + "ladder/05-holes-and-taps/008-tapped-m5-into-curved-face", + "ladder/05-holes-and-taps/011-crossing-holes-steinmetz", + "ladder/05-holes-and-taps/012-crossing-tapped-holes-offset", + "ladder/05-holes-and-taps/013-part-holes" + ], + "tier": "05-holes-and-taps", + "what": "holes and tapped holes" + }, + { + "cases": [ + "ladder/06-threads/003-external-m10-partial-length", + "ladder/06-threads/005-internal-m10-through", + "ladder/06-threads/004-external-m10-to-shoulder", + "ladder/06-threads/001-external-m10-full", + "ladder/06-threads/006-external-m10-into-chamfer", + "ladder/06-threads/002-external-m10x1-fine", + "ladder/06-threads/010-part-threaded-boss", + "ladder/06-threads/007-external-m10-at-filleted-shoulder", + "ladder/06-threads/008-multi-start-thread", + "ladder/06-threads/009-thread-runout-length" + ], + "tier": "06-threads", + "what": "modelled threads" + }, + { + "cases": [ + "ladder/07-fillets/006-variable-r5-to-zero", + "ladder/07-fillets/005-variable-r5-to-r1", + "ladder/07-fillets/007-variable-r5-to-1e-3", + "ladder/07-fillets/015-corner-g2-patch", + "ladder/07-fillets/008-constant-r1e-3", + "ladder/07-fillets/004-convex-and-concave-r5", + "ladder/07-fillets/001-single-edge-r5", + "ladder/07-fillets/012-variable-into-rounded-corner", + "ladder/07-fillets/014-curved-edge-r2", + "ladder/07-fillets/011-chain-ending-at-corner", + "ladder/07-fillets/002-four-vertical-edges-r5", + "ladder/07-fillets/009-corner-three-equal-r5", + "ladder/07-fillets/010-corner-unequal-r7-r3", + "ladder/07-fillets/003-tangent-chain-r3", + "ladder/07-fillets/016-corner-three-sequential-r5-r3-r7", + "ladder/07-fillets/017-part-rounds", + "ladder/07-fillets/013-corner-setback" + ], + "tier": "07-fillets", + "what": "constant, variable and corner fillets" + }, + { + "cases": [ + "ladder/08-chamfers/002-two-distances-5-3", + "ladder/08-chamfers/001-equal-5", + "ladder/08-chamfers/003-distance-angle-5-30", + "ladder/08-chamfers/005-corner-two-mitred-3", + "ladder/08-chamfers/004-corner-three-equal-3", + "ladder/08-chamfers/007-chamfer-then-fillets", + "ladder/08-chamfers/006-curved-edge-2", + "ladder/08-chamfers/008-part-chamfers" + ], + "tier": "08-chamfers", + "what": "chamfers" + }, + { + "cases": [ + "ladder/09-shells-and-offsets/007-offset-top-face-5", + "ladder/09-shells-and-offsets/003-tube-two-open-faces", + "ladder/09-shells-and-offsets/001-box-open-top-t2", + "ladder/09-shells-and-offsets/004-thin-wall-0.2", + "ladder/09-shells-and-offsets/009-shell-outside-t2", + "ladder/09-shells-and-offsets/008-offset-cylinder-wall-2", + "ladder/09-shells-and-offsets/002-cylinder-open-top-t1", + "ladder/09-shells-and-offsets/006-shell-rounds-r1-thinner-than-t2", + "ladder/09-shells-and-offsets/005-shell-rounds-r5-t2", + "ladder/09-shells-and-offsets/010-shell-under-rounded-top", + "ladder/09-shells-and-offsets/011-part-shell" + ], + "tier": "09-shells-and-offsets", + "what": "shells and offsets" + }, + { + "cases": [ + "ladder/10-patterns-and-mirrors/007-pattern-bodies", + "ladder/10-patterns-and-mirrors/005-mirror-feature", + "ladder/10-patterns-and-mirrors/004-mirror-body-joined", + "ladder/10-patterns-and-mirrors/001-rect-pattern-holes", + "ladder/10-patterns-and-mirrors/003-overlapping-pattern-leaves-core", + "ladder/10-patterns-and-mirrors/002-circular-pattern-holes", + "ladder/10-patterns-and-mirrors/006-circular-pattern-counterbores", + "ladder/10-patterns-and-mirrors/008-part-vents" + ], + "tier": "10-patterns-and-mirrors", + "what": "patterns and mirrors" + }, + { + "cases": [ + "ladder/11-cumulative-parts/002-flange", + "ladder/11-cumulative-parts/001-bracket", + "ladder/11-cumulative-parts/003-threaded-flange", + "ladder/11-cumulative-parts/004-housing" + ], + "tier": "11-cumulative-parts", + "what": "parts combining every tier" + } + ], + "what": "the kernel complexity ladder: tiers in order of complexity, each building on the shapes before it; cases within a tier in order of measured time (cargo xtask kernel-cases --ladder --record)" +} diff --git a/tools/kernel-ladder/cases.py b/tools/kernel-ladder/cases.py new file mode 100644 index 0000000000..df546855e8 --- /dev/null +++ b/tools/kernel-ladder/cases.py @@ -0,0 +1,596 @@ +"""The kernel complexity ladder's cases (tests/kernel-cases/ladder): every recipe and every +expected answer, with its derivation in `method`. Plain Python 3, standard library only. + + python3 tools/kernel-ladder/cases.py + +rewrites each case's recipe.json and expected.json and adds new cases to ladder.json (at the end +of their tier, until `cargo xtask kernel-cases --ladder --record` sorts them by time). It never +touches golden.json, and keeps the recorded order of cases already listed. Never change a value +to match a kernel's output: if they disagree, find out which one is wrong.""" +import json +import math +import os +import sys + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from ladder_lib import * # noqa: E402,F403 + +def up(tier, name): + return f"../../{tier}/{name}" + +T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11 = ( + "01-primitives", "02-booleans", "03-analytic-profiles", "04-spline-profiles", "05-holes-and-taps", + "06-threads", "07-fillets", "08-chamfers", "09-shells-and-offsets", "10-patterns-and-mirrors", "11-cumulative-parts") +BOX = box([0, 0, 0], 60, 40, 20, body_name="Part") +VB = 60 * 40 * 20 +BBOX = {"min": [0, 0, 0], "max": [60, 40, 20]} + +# ---------------------------------------------------------------- 01 primitives +case(T1, "001-box", "a 60x40x20 box: the stock every later tier starts from", [BOX], + {"body_count": 1, "volume_mm3": VB, "area_mm2": 2 * (2400 + 1200 + 800), "faces": 6, "edges": 12, "vertices": 8, "bbox": BBOX}, + "V = 60*40*20; A = 2(60*40 + 60*20 + 40*20); a box has 6 faces, 12 edges, 8 vertices") +case(T1, "002-cylinder", "a cylinder r10 h30", [cyl([0, 0, 0], 10, 30)], + {"body_count": 1, "volume_mm3": PI * 100 * 30, "area_mm2": 2 * PI * 100 + 2 * PI * 10 * 30, "faces": 3, + "bbox": {"min": [-10, -10, 0], "max": [10, 10, 30]}}, + "V = pi r^2 h; A = 2 pi r^2 + 2 pi r h; faces: two disks and one whole cylinder") +l = math.hypot(10, 30) +case(T1, "003-cone", "a cone r10 h30 (no cone primitive: a revolved right triangle, its axis a sketch line)", + sketch(vplane([0, 0, 0]), line([[0, 0], [10, 0], [0, 30]], ids=["b", "s", "ax"], closed=True)) + [C("solid.revolve", axis="ax")], + {"body_count": 1, "volume_mm3": PI * 100 * 30 / 3, "area_mm2": PI * 100 + PI * 10 * l, "faces": 2, + "bbox": {"min": [-10, -10, 0], "max": [10, 10, 30]}}, + "V = pi r^2 h / 3; A = pi r^2 + pi r sqrt(r^2 + h^2); a disk and the cone face") +R, r, h = 10, 5, 20 +l = math.hypot(R - r, h) +case(T1, "004-frustum", "a cone frustum r10 -> r5, h20 (revolved trapezoid)", + sketch(vplane([0, 0, 0]), line([[0, 0], [10, 0], [5, 20], [0, 20]], ids=["b", "s", "t", "ax"], closed=True)) + [C("solid.revolve", axis="ax")], + {"body_count": 1, "volume_mm3": PI * h / 3 * (R * R + R * r + r * r), "area_mm2": PI * (R * R + r * r) + PI * (R + r) * l, "faces": 3}, + "V = pi h/3 (R^2 + R r + r^2); A = pi (R^2 + r^2) + pi (R + r) sqrt((R-r)^2 + h^2)") +case(T1, "005-sphere", "a sphere r10", [C("solid.sphere", radius=10)], + {"body_count": 1, "volume_mm3": 4 / 3 * PI * 1000, "area_mm2": 4 * PI * 100, "faces": 1, + "bbox": {"min": [-10, -10, -10], "max": [10, 10, 10]}}, + "V = 4/3 pi r^3; A = 4 pi r^2; one face") +case(T1, "006-torus", "a torus R20 r5", [C("solid.torus", major=20, minor=5)], + {"body_count": 1, "volume_mm3": 2 * PI * PI * 20 * 25, "area_mm2": 4 * PI * PI * 20 * 5, "faces": 1, + "bbox": {"min": [-25, -25, -5], "max": [25, 25, 5]}}, + "V = 2 pi^2 R r^2; A = 4 pi^2 R r; one face") +B1 = up(T1, "001-box") + +# ---------------------------------------------------------------- 02 booleans +case(T2, "001-coincident-face-join", "a 60x40x10 box joined exactly on the stock's top face: one 60x40x30 box", + [box([0, 0, 20], 60, 40, 10, operation="join", targets=["Part"])], + {"body_count": 1, "volume_mm3": 72000.0, "faces": 6, "edges": 12, "vertices": 8, "bbox": {"min": [0, 0, 0], "max": [60, 40, 30]}}, + "the boxes share the whole face z=20 and every side is coplanar: the union is the box 60x40x30, V = 72000, its coplanar sides merged into 6 faces", base=B1) +case(T2, "002-coplanar-overlap-join", "two 60x40x20 boxes overlapping 30x20 in plan, tops and bottoms coplanar", + [box([30, 20, 0], 60, 40, 20, operation="join", targets=["Part"])], + {"body_count": 1, "volume_mm3": 2 * VB - 30 * 20 * 20, "faces": 10}, + "V = 2*48000 - 30*20*20; the plan outline is an 8-sided polygon: 8 sides + top + bottom", base=B1) +case(T2, "003-pocket-flush-with-top", "a 20x20x10 pocket whose tool's top is the stock's top face", + [box([20, 10, 10], 20, 20, 10, operation="cut", targets=["Part"])], + {"body_count": 1, "volume_mm3": VB - 4000, "faces": 11}, + "V = 48000 - 20*20*10; faces: 6 + 4 pocket walls + pocket floor", base=B1) +case(T2, "004-edge-on-edge-no-overlap", "a cut tool that shares only the top front edge of the stock", + [box([0, -10, 20], 60, 10, 10, operation="cut", targets=["Part"])], + {"body_count": 1, "volume_mm3": VB, "faces": 6, "edges": 12, "vertices": 8}, + "the tool touches the stock only along the edge y=0, z=20: nothing is removed, the box stays as it was", base=B1) +case(T2, "005-vertex-on-face-no-overlap", "an inverted cone whose apex touches the middle of the top face, cut", + sketch(vplane([30, 20, 0]), line([[0, 20], [10, 30], [0, 30]], ids=["s", "t", "ax"], closed=True)) + [C("solid.revolve", axis="ax", operation="cut", targets=["Part"])], + {"body_count": 1, "volume_mm3": VB, "faces": 6, "edges": 12, "vertices": 8}, + "the cone (apex (30,20,20), base r10 at z=30) meets the stock only at its apex: the cut removes nothing", base=B1) +case(T2, "006-tangent-along-line-no-overlap", "a cylinder r10 along X lying on the top face (tangent along a line), cut", + [cyl([-5, 20, 30], 10, 70, axis="X", operation="cut", targets=["Part"])], + {"body_count": 1, "volume_mm3": VB, "faces": 6, "edges": 12, "vertices": 8}, + "the cylinder (axis y=20, z=30) touches the top face z=20 only along the line y=20: nothing is removed", base=B1) +case(T2, "007-near-tangent-gap-1e-3", "a cylinder r10 along X 1e-3 mm above the top face, cut", + [cyl([-5, 20, 30.001], 10, 70, axis="X", operation="cut", targets=["Part"])], + {"body_count": 1, "volume_mm3": VB, "faces": 6, "edges": 12, "vertices": 8}, + "gap 1e-3 mm: no contact, nothing is removed", base=B1) +case(T2, "008-near-tangent-gap-1e-6", "a cylinder r10 along X 1e-6 mm above the top face, cut", + [cyl([-5, 20, 30.000001], 10, 70, axis="X", operation="cut", targets=["Part"])], + {"body_count": 1, "volume_mm3": VB, "faces": 6, "edges": 12, "vertices": 8}, + "gap 1e-6 mm: no contact; even a kernel that treats it as touching must remove nothing", base=B1) +hh = 1e-3 +seg = 100 * math.acos((10 - hh) / 10) - (10 - hh) * math.sqrt(2 * 10 * hh - hh * hh) +case(T2, "009-near-tangent-overlap-1e-3", "a cylinder r10 along X dipping 1e-3 mm into the top face, cut: a groove 1e-3 deep", + [cyl([-5, 20, 29.999], 10, 70, axis="X", operation="cut", targets=["Part"])], + {"body_count": 1, "volume_mm3": VB - 60 * seg, "faces": 8}, + f"circular segment of depth h=1e-3 in r=10: A = r^2 acos((r-h)/r) - (r-h) sqrt(2rh - h^2) = {seg:.3e} mm^2, times 60; the groove runs the whole length, so it splits the top face in two and adds one cylinder face: 6 - 1 + 2 + 1", base=B1) +case(T2, "010-near-tangent-overlap-1e-6", "a cylinder r10 along X dipping 1e-6 mm into the top face, cut", + [cyl([-5, 20, 29.999999], 10, 70, axis="X", operation="cut", targets=["Part"])], + {"body_count": 1, "volume_mm3": VB}, + "a groove 1e-6 deep (V change ~1e-8) is at the kernels' tolerance: a groove or no change are both right; graded: one valid body, V = 48000", base=B1, source="invariants") +case(T2, "011-tangent-along-circle-through-hole", "a sphere r10 sitting in a through hole r10 at the top edge (tangent along the hole's top circle), cut", + [cyl([30, 20, -1], 10, 22, operation="cut", targets=["Part"]), C("solid.sphere", radius=10, center=[30, 20, 20], operation="cut", targets=["Part"])], + {"body_count": 1, "volume_mm3": VB - PI * 100 * 20, "faces": 7}, + "the lower half of the sphere lies inside the hole, tangent to its wall along the circle z=20, the upper half is outside: the sphere removes nothing more; V = 48000 - pi 100 20", base=B1) +case(T2, "012-tangent-along-circle-bowl", "a blind hole r10 10 deep, then a sphere r10 centred on its floor: the floor disk vanishes exactly", + [cyl([30, 20, 10], 10, 11, operation="cut", targets=["Part"]), C("solid.sphere", radius=10, center=[30, 20, 10], operation="cut", targets=["Part"])], + {"body_count": 1, "volume_mm3": VB - PI * 100 * 10 - 2 / 3 * PI * 1000, "faces": 8}, + "the sphere's equator is the floor's edge circle (tangent to the hole wall along it): removed = pi r^2 10 + 2/3 pi r^3; faces 6 + wall + bowl", base=B1) +area = quad(lambda y: math.sqrt(100 - y * y) - 5, -2, 2) +case(T2, "013-cut-across-cylinder-seam", "a 10x4 notch through a cylinder r10 across its +X side (where the seam lies)", + [cyl([0, 0, 0], 10, 20, body_name="Part"), box([5, -2, -1], 10, 4, 22, operation="cut", targets=["Part"])], + {"body_count": 1, "volume_mm3": PI * 100 * 20 - 20 * area, "faces": 6}, + f"removed section = int_-2^2 (sqrt(100 - y^2) - 5) dy = {area:.9f} (Gauss), times 20; faces: cylinder, 2 disks, 3 notch planes") +def cap(x): + return quad(lambda y: math.sqrt(100 - x * x - y * y) - 5, -2, 2) +pole = quad(cap, -2, 2) +case(T2, "014-cut-through-sphere-pole", "a 4x4 square post cut off the top of a sphere r10 through its pole", + [C("solid.sphere", radius=10, body_name="Part"), box([-2, -2, 5], 4, 4, 6, operation="cut", targets=["Part"])], + {"body_count": 1, "volume_mm3": 4 / 3 * PI * 1000 - pole, "faces": 6}, + f"removed = int over the square |x|,|y|<=2 of (sqrt(100 - x^2 - y^2) - 5) = {pole:.9f} (nested Gauss); faces: sphere, 4 sides, floor z=5") +case(T2, "015-knife-edge-wedge-cut", "a triangular prism cut whose slope meets the bottom face along one line (a knife edge), its other faces on the stock's", + sketch(plane([0, 41, 0], [1, 0, 0], [0, 0, 1]), line([[0, 0], [60, 20], [0, 20]], closed=True)) + [C("solid.extrude", distance=42, operation="cut", targets=["Part"])], + {"body_count": 1, "volume_mm3": VB / 2, "faces": 5, "edges": 9, "vertices": 6}, + "the tool removes the half above the plane through the edge x=0,z=0 and the edge x=60,z=20: a wedge 60x40x20/2 = 24000 with a knife edge at x=0, z=0", base=B1) +case(T2, "016-v-groove-pinches-bottom-to-line", "a V groove whose apex touches the bottom face along a line, cut through: two pieces", + sketch(plane([0, 41, 0], [1, 0, 0], [0, 0, 1]), line([[15, 25], [30, 0], [45, 25]], closed=True)) + [C("solid.extrude", distance=42, operation="cut", targets=["Part"])], + {"body_count": 2, "volume_mm3": VB - 0.5 * 24 * 20 * 40}, + "apex (30, 0) on the bottom face, flanks through (18, 20) and (42, 20): removed triangle 24 wide x 20 high, times 40; the pieces meet only along the line x=30, z=0, so the regularized result is two solids", base=B1) +case(T2, "017-cylinder-tangent-top-and-bottom", "a cylinder r10 along X tangent to both the top and bottom faces, cut: two pieces with zero-angle cusps", + [cyl([-5, 20, 10], 10, 70, axis="X", operation="cut", targets=["Part"])], + {"body_count": 2, "volume_mm3": VB - PI * 100 * 60}, + "the cylinder (axis y=20, z=10) spans the full height at y=20: removed pi r^2 60; the halves meet only along the lines y=20, z=0 and z=20: two solids", base=B1) +case(T2, "018-hole-tangent-to-side-face", "a through hole r10 tangent to the front face: the front face pinched to a line", + [cyl([30, 10, -1], 10, 22, operation="cut", targets=["Part"])], + {"body_count": 1, "volume_mm3": VB - PI * 100 * 20}, + "the hole touches y=0 along the line x=30: removed pi r^2 20, the material behind keeps one body", base=B1) +case(T2, "019-notch-flush-with-end-face", "a notch 40x20 through the height, its end flush with the stock's end face: a U", + [box([20, 10, -1], 40, 20, 22, operation="cut", targets=["Part"])], + {"body_count": 1, "volume_mm3": VB - 40 * 20 * 20, "faces": 10}, + "the tool's end face is x=60, the stock's: V = 48000 - 16000; the U outline has 8 sides, + top + bottom", base=B1) +case(T2, "020-part-back-wall", "the running part: a 60x10x15 back wall joined on the stock's top, flush with its back and ends", + [box([0, 30, 20], 60, 10, 15, operation="join", targets=["Part"])], + {"body_count": 1, "volume_mm3": VB + 60 * 10 * 15, "faces": 8, "bbox": {"min": [0, 0, 0], "max": [60, 40, 35]}}, + "an L section (6 sides) extruded along X: 8 faces; the coplanar back and end faces merge", base=B1) +C2 = up(T2, "020-part-back-wall") +V2 = VB + 9000 + +# ---------------------------------------------------------------- 03 analytic profiles +A = 2400 + PI * 400 / 2 +case(T3, "001-extrude-rect-with-arc-end", "a 60x40 profile with a semicircular end r20, extruded 20", + sketch("XY", line([[60, 40], [0, 40], [0, 0], [60, 0]]), C("sketch.arc.three_point", start=[60, 0], end=[60, 40], through=[80, 20])) + [C("solid.extrude", distance=20)], + {"body_count": 1, "volume_mm3": A * 20, "area_mm2": 2 * A + 20 * (60 + 40 + 60 + PI * 20), "faces": 6}, + "A = 60*40 + pi 20^2 / 2; V = 20 A; sides: 3 planes and a half cylinder") +case(T3, "002-extrude-annulus", "an annulus r20/r10 extruded 15", + sketch("XY", C("sketch.circle.center", center=[0, 0], radius=20), C("sketch.circle.center", center=[0, 0], radius=10)) + [C("solid.extrude", distance=15, profiles=[{"point": [15, 0]}])], + {"body_count": 1, "volume_mm3": PI * 300 * 15, "area_mm2": 2 * PI * 300 + 2 * PI * 30 * 15, "faces": 4}, + "V = pi (20^2 - 10^2) 15; faces: two annuli, two cylinders") +rt = 10 - 20 * math.tan(math.radians(5)) +case(T3, "003-extrude-tapered-circle", "a circle r10 extruded 20 with a -5 deg taper (inward): a cone frustum", + sketch("XY", C("sketch.circle.center", center=[0, 0], radius=10)) + [C("solid.extrude", distance=20, taper=-5)], + {"body_count": 1, "volume_mm3": PI * 20 / 3 * (100 + 10 * rt + rt * rt), "faces": 3}, + f"positive taper grows outward (tests/oracle/05): top radius 10 - 20 tan5 = {rt:.9f}; V = pi h/3 (R^2 + R r + r^2)") +case(T3, "004-revolve-full-off-axis", "a 10x30 rectangle 10 mm off the Z axis revolved 360 deg: a tube", + sketch(vplane([0, 0, 0]), line([[10, 0], [20, 0], [20, 30], [10, 30]], closed=True)) + [C("solid.revolve", axis="Z")], + {"body_count": 1, "volume_mm3": PI * 300 * 30, "area_mm2": 2 * PI * 300 + 2 * PI * 30 * 30, "faces": 4}, + "V = pi (20^2 - 10^2) 30; faces: two annuli, two cylinders") +case(T3, "005-revolve-touching-axis-hemisphere", "a quarter disk r20 with an edge on the Z axis revolved 360 deg: a hemisphere", + sketch(vplane([0, 0, 0]), line([[0, 20], [0, 0], [20, 0]]), C("sketch.arc.center_point", center=[0, 0], start=[20, 0], end=[0, 20])) + [C("solid.revolve", axis="Z")], + {"body_count": 1, "volume_mm3": 2 / 3 * PI * 8000, "area_mm2": 3 * PI * 400, "faces": 2}, + "V = 2/3 pi r^3; A = 2 pi r^2 + pi r^2; the profile edge on the axis makes no face") +case(T3, "006-revolve-partial-90", "a 10x10 rectangle 10 mm off the axis revolved 90 deg", + sketch(vplane([0, 0, 0]), line([[10, 0], [20, 0], [20, 10], [10, 10]], closed=True)) + [C("solid.revolve", axis="Z", angle=90)], + {"body_count": 1, "volume_mm3": PI * 300 * 10 / 4, "faces": 6}, + "a quarter of the tube: V = (90/360) pi (20^2 - 10^2) 10; faces: 2 ends, 2 cylinders, top, bottom") +case(T3, "007-revolve-180-touching-axis", "a 15x20 rectangle on the axis revolved 180 deg: a half cylinder", + sketch(vplane([0, 0, 0]), line([[0, 0], [15, 0], [15, 20], [0, 20]], closed=True)) + [C("solid.revolve", axis="Z", angle=180)], + {"body_count": 1, "volume_mm3": PI * 225 * 20 / 2, "area_mm2": PI * 225 + PI * 15 * 20 + 30 * 20, "faces": 4}, + "V = pi r^2 h / 2; the two end rectangles are coplanar and meet on the axis: one 30x20 face; + half disks top and bottom + half cylinder") +case(T3, "008-extrude-slot", "a slot (centres 40 apart, width 20) extruded 10", + sketch("XY", C("sketch.slot.center_to_center", p0=[0, 0], p1=[40, 0], width=20)) + [C("solid.extrude", distance=10)], + {"body_count": 1, "volume_mm3": (800 + PI * 100) * 10, "area_mm2": 2 * (800 + PI * 100) + 10 * (80 + 2 * PI * 10), "faces": 6}, + "A = 40*20 + pi 10^2; faces: 2 planes, 2 half cylinders, top, bottom") +Vboss = PI * 64 * 10 +Aslot = 15 * 8 + PI * 16 +case(T3, "009-part-boss-and-slot", "the running part: a revolved boss r8 h10 joined on the top, a slot 15x8 cut through the base", + sketch(vplane([15, 15, 0]), line([[0, 20], [8, 20], [8, 30], [0, 30]], ids=["b", "s", "t", "ax"], closed=True)) + [C("solid.revolve", axis="ax", operation="join", targets=["Part"], name="Boss")] + + sketch("XY", C("sketch.slot.center_to_center", p0=[35, 10], p1=[50, 10], width=8)) + [C("solid.extrude", distance=20, operation="cut", targets=["Part"], name="Slot")], + {"body_count": 1, "volume_mm3": V2 + Vboss - 20 * Aslot, "faces": 14}, + "back-wall part (57000) + pi 8^2 10 - 20 (15*8 + pi 4^2); faces 8 + boss side and top + slot's 2 planes and 2 half cylinders", base=C2) +C3 = up(T3, "009-part-boss-and-slot") +V3 = V2 + Vboss - 20 * Aslot + +# ---------------------------------------------------------------- 04 spline profiles +P = [[40, 10], [30, 30], [10, 30], [0, 10]] +Ab = 40 * 10 + bez_area_under(P, 10) +case(T4, "001-extrude-bezier-arch", "a profile closed by a cubic Bezier arch (control-point spline, 4 points), extruded 10", + sketch("XY", line([[0, 10], [0, 0], [40, 0], [40, 10]]), C("sketch.spline.control_point", points=P)) + [C("solid.extrude", distance=10)], + {"body_count": 1, "volume_mm3": Ab * 10, "faces": 6, "bbox": {"min": [0, 0, 0], "max": [40, 25, 10]}}, + "a 4-point cubic control-point spline is one Bezier segment; A = 400 + int (y-10) dx over the arch (Green, exact Gauss) = %.9f; max y = 10 + 0.75*20" % Ab) +Q = [[20, 0], [30, 10], [10, 20], [15, 30]] +Vq = PI * quad(lambda t: bez(Q, t)[0] ** 2 * dbez(Q, t)[1], 0, 1) +Aq = PI * 400 + PI * 225 + 2 * PI * quad_split(lambda t: bez(Q, t)[0] * norm(dbez(Q, t)), 0, 1, 50) +case(T4, "002-revolve-bezier-profile", "a profile with a Bezier side (r from 20 to 15 over z 0..30) revolved 360 deg about Z", + sketch(vplane([0, 0, 0]), line([[15, 30], [0, 30], [0, 0], [20, 0]]), C("sketch.spline.control_point", points=Q)) + [C("solid.revolve", axis="Z")], + {"body_count": 1, "volume_mm3": Vq, "area_mm2": Aq, "faces": 3}, + "z(t) = 30 t is linear, so V = pi int r(t)^2 z'(t) dt (exact Gauss) = %.9f; A = disks pi 20^2 + pi 15^2 + 2 pi int r |B'| dt" % Vq) +W = [[0, 0], [0, 20], [20, 20], [20, 40]] +Lw = bez_len(W) +assert bez_min_radius(W) > 3 +case(T4, "003-pipe-along-bezier", "a pipe d4 along a planar Bezier path", + sketch(vplane([0, 0, 0]), C("sketch.spline.control_point", points=W), name="Path") + [C("solid.pipe", path_sketch="Path", path=["s1"], diameter=4)], + {"body_count": 1, "volume_mm3": PI * 4 * Lw, "area_mm2": 2 * PI * 2 * Lw + 2 * PI * 4}, + f"a disk swept square to a planar path whose curvature radius stays above {bez_min_radius(W):.2f} > 2: V = pi r^2 L, A = 2 pi r L + 2 pi r^2, L = {Lw:.9f}") +case(T4, "004-sweep-square-along-bezier", "a 4x4 square swept along the same Bezier path", + sketch(vplane([0, 0, 0]), C("sketch.spline.control_point", points=W), name="Path") + + sketch("XY", C("sketch.rectangle.center", center=[0, 0], corner=[2, 2]), name="Square") + + [C("solid.sweep", sketch="Square", path_sketch="Path", path=["s1"])], + {"body_count": 1, "volume_mm3": 16 * Lw, "area_mm2": 16 * Lw + 2 * 16}, + "the square's centre is on the planar path and it stays square to it: V = A L; side faces: the inner and outer walls have s(L -/+ a dtheta), the two others s L each, total 4 s L; + 2 ends") +case(T4, "005-loft-square-frustum", "a loft between a 40 square at z=0 and a 20 square at z=30", + sketch("XY", C("sketch.rectangle.center", center=[0, 0], corner=[20, 20]), name="S0") + + sketch(plane([0, 0, 30], [1, 0, 0], [0, 1, 0]), C("sketch.rectangle.center", center=[0, 0], corner=[10, 10]), name="S1") + + [C("solid.loft", sections=[{"sketch": "S0"}, {"sketch": "S1"}])], + {"body_count": 1, "volume_mm3": 30 / 3 * (1600 + 400 + 800), "area_mm2": 1600 + 400 + 4 * 30 * math.hypot(30, 10), "faces": 6}, + "two sections loft ruled: a square frustum, V = h/3 (A0 + A1 + sqrt(A0 A1)); sides 4 trapezoids (40+20)/2 * sqrt(30^2+10^2)") +case(T4, "006-loft-circle-frustum", "a loft between circles r20 at z=0 and r10 at z=30", + sketch("XY", C("sketch.circle.center", center=[0, 0], radius=20), name="S0") + + sketch(plane([0, 0, 30], [1, 0, 0], [0, 1, 0]), C("sketch.circle.center", center=[0, 0], radius=10), name="S1") + + [C("solid.loft", sections=[{"sketch": "S0"}, {"sketch": "S1"}])], + {"body_count": 1, "volume_mm3": PI * 30 / 3 * 700, "faces": 3}, + "a ruled loft between coaxial circles is a cone frustum: V = pi h/3 (R^2 + R r + r^2)") +case(T4, "007-loft-three-circles", "a smooth loft through circles r20, r10, r20 at z = 0, 20, 40", + sketch("XY", C("sketch.circle.center", center=[0, 0], radius=20), name="S0") + + sketch(plane([0, 0, 20], [1, 0, 0], [0, 1, 0]), C("sketch.circle.center", center=[0, 0], radius=10), name="S1") + + sketch(plane([0, 0, 40], [1, 0, 0], [0, 1, 0]), C("sketch.circle.center", center=[0, 0], radius=20), name="S2") + + [C("solid.loft", sections=[{"sketch": "S0"}, {"sketch": "S1"}, {"sketch": "S2"}])], + {"body_count": 1, "faces": 3}, + "the interpolation between sections is the loft's own: no closed-form volume; one valid body bounded by two disks and one lofted face", source="invariants") +case(T4, "008-loft-square-to-circle", "a loft from a 40 square at z=0 to a circle r10 at z=30", + sketch("XY", C("sketch.rectangle.center", center=[0, 0], corner=[20, 20]), name="S0") + + sketch(plane([0, 0, 30], [1, 0, 0], [0, 1, 0]), C("sketch.circle.center", center=[0, 0], radius=10), name="S1") + + [C("solid.loft", sections=[{"sketch": "S0"}, {"sketch": "S1"}])], + {"body_count": 1}, + "sections with different edge counts: the matching is the loft's own; one valid body", source="invariants") +case(T4, "009-extrude-periodic-fit-spline", "a closed periodic fit-point spline through 6 points, extruded 10", + sketch("XY", C("sketch.spline.fit_point", points=[[20, 0], [10, 17], [-10, 17], [-20, 0], [-10, -17], [10, -17]], periodic=True)) + [C("solid.extrude", distance=10)], + {"body_count": 1}, + "the fit's parametrisation is the spline's own: no closed-form area, and how many side faces a periodic spline makes is the kernel's own; one valid body", source="invariants") +Pw = [[40, 22], [37, 34], [23, 34], [20, 22]] +Awin = bez_area_under(Pw, 22) +case(T4, "010-part-bezier-window", "the running part: a Bezier-arched window cut through the back wall", + sketch(plane([0, 40, 0], [1, 0, 0], [0, 0, 1]), line([[20, 22], [40, 22]]), C("sketch.spline.control_point", points=Pw)) + [C("solid.extrude", distance=10, operation="cut", targets=["Part"], name="Window")], + {"body_count": 1, "volume_mm3": V3 - 10 * Awin, "faces": 16}, + "the arch over z=22 from x=20 to 40 rises to z = 22 + 0.75*12 = 31, under the wall top (35): removed 10 * %.9f; + window floor and arch faces" % Awin, base=C3) +C4 = up(T4, "010-part-bezier-window") +V4 = V3 - 10 * Awin + +# ---------------------------------------------------------------- 05 holes and taps +def H(**p): + return C("solid.hole", **p) +top = [30, 20, 20] +case(T5, "001-simple-through", "a d10 through hole in the stock's top", [H(position=top, diameter=10)], + {"body_count": 1, "volume_mm3": VB - PI * 25 * 20, "faces": 7}, "removed pi 5^2 20; + one cylinder face", base=B1) +case(T5, "002-simple-blind-flat", "a d10 blind hole 10 deep, flat bottom", [H(position=top, diameter=10, depth=10)], + {"body_count": 1, "volume_mm3": VB - PI * 25 * 10, "faces": 8}, "removed pi 5^2 10; + wall and floor", base=B1) +ch = 5 / math.tan(math.radians(59)) +case(T5, "003-drilled-blind-118", "a d10 drilled hole 10 deep to the shoulder with a 118 deg point", + [H(position=top, diameter=10, depth=10, type="drilled", tip_angle=118)], + {"body_count": 1, "volume_mm3": VB - PI * 25 * 10 - PI * 25 * ch / 3, "faces": 8}, + "depth to the shoulder (tests/oracle/18); point cone h = r / tan59; removed pi r^2 10 + pi r^2 h/3; + wall and cone", base=B1) +case(T5, "004-counterbore", "a counterbored hole: d6.6 through, d11 x 6.4 counterbore", + [H(position=top, diameter=6.6, type="counterbore", cb_diameter=11, cb_depth=6.4)], + {"body_count": 1, "volume_mm3": VB - PI * 3.3 ** 2 * (20 - 6.4) - PI * 5.5 ** 2 * 6.4, "faces": 9}, + "removed pi 3.3^2 (20 - 6.4) + pi 5.5^2 6.4; + bore, counterbore wall, its floor annulus", base=B1) +hc = (6 - 3.3) / math.tan(math.radians(45)) +case(T5, "005-countersink", "a countersunk hole: d6.6 through, d12 x 90 deg countersink", + [H(position=top, diameter=6.6, type="countersink", cs_diameter=12, cs_angle=90)], + {"body_count": 1, "volume_mm3": VB - PI * hc / 3 * (36 + 6 * 3.3 + 3.3 ** 2) - PI * 3.3 ** 2 * (20 - hc), "faces": 8}, + "countersink frustum r6 -> r3.3 over (6-3.3)/tan45 = 2.7; + bore below it; + cone and bore faces", base=B1) +case(T5, "006-tapped-m6-through", "an M6 tapped hole through: the tap drill d5 (major - pitch), cosmetic thread", + [H(position=top, thread="M6")], + {"body_count": 1, "volume_mm3": VB - PI * 6.25 * 20, "faces": 7}, "M6 coarse pitch 1: tap drill 6 - 1 = 5; removed pi 2.5^2 20; the cosmetic thread adds no geometry", base=B1) +ch8 = 3.375 / math.tan(math.radians(59)) +case(T5, "007-tapped-m8-blind", "an M8 tapped blind hole 12 deep with a 118 deg drill point", + [H(position=top, thread="M8", depth=12, type="drilled", tip_angle=118)], + {"body_count": 1, "volume_mm3": VB - PI * 3.375 ** 2 * 12 - PI * 3.375 ** 2 * ch8 / 3, "faces": 8}, + "M8 coarse pitch 1.25: tap drill (major - pitch) 6.75; removed pi r^2 12 to the shoulder + the point cone pi r^2 (r/tan59)/3", base=B1) +r, Rc = 2.1, 20 +vint = quad(lambda y: 2 * math.sqrt(max(r * r - y * y, 0)) * 2 * math.sqrt(Rc * Rc - y * y), -r, r, 128) +case(T5, "008-tapped-m5-into-curved-face", "an M5 tapped hole drilled radially through a cylinder r20 lying along X", + [cyl([0, 0, 0], 20, 60, axis="X", body_name="Part"), H(position=[30, 0, 20], thread="M5")], + {"body_count": 1, "volume_mm3": PI * 400 * 60 - vint, "faces": 4}, + f"tap drill 5 - 0.8 = 4.2; the hole (axis Z) crosses the cylinder (axis X, R=20): V_int = int_-r^r 2 sqrt(r^2 - y^2) 2 sqrt(R^2 - y^2) dy = {vint:.9f}") +case(T5, "009-hole-0.5-from-edge", "a d10 through hole whose wall is 0.5 mm from the end face", + [H(position=[5.5, 20, 20], diameter=10)], + {"body_count": 1, "volume_mm3": VB - PI * 25 * 20, "faces": 7}, "removed pi 5^2 20, a 0.5 mm web left", base=B1) +case(T5, "010-hole-tangent-to-end-face", "a d10 through hole tangent to the end face x=0", + [H(position=[5, 20, 20], diameter=10)], + {"body_count": 1, "volume_mm3": VB - PI * 25 * 20}, "the wall touches x=0 along a line: removed pi 5^2 20, one body", base=B1) +case(T5, "011-crossing-holes-steinmetz", "two d10 through holes whose axes cross: vertical and along Y at z=10", + [H(position=top, diameter=10), H(position=[30, 0, 10], diameter=10)], + {"body_count": 1, "volume_mm3": VB - PI * 25 * 20 - PI * 25 * 40 + 16 * 125 / 3, "faces": 8}, + "equal radii, crossing axes: the common part is the Steinmetz solid 16 r^3 / 3; removed pi r^2 (20 + 40) - 16 r^3/3", base=B1) +rr, off = 2.5, 2.0 +lo, hi = max(-rr, off - rr), min(rr, off + rr) +vx = quad(lambda u: 4 * math.sqrt(max(rr * rr - u * u, 0)) * math.sqrt(max(rr * rr - (u - off) ** 2, 0)), lo, hi, 256) +case(T5, "012-crossing-tapped-holes-offset", "two M6 tapped holes (d5) crossing with axes 2 mm apart", + [H(position=top, thread="M6"), H(position=[32, 0, 10], thread="M6")], + {"body_count": 1, "volume_mm3": VB - PI * 6.25 * 20 - PI * 6.25 * 40 + vx}, + f"common part of perpendicular cylinders r=2.5 with axes 2 apart: int 2 sqrt(r^2-u^2) 2 sqrt(r^2-(u-2)^2) du = {vx:.9f}", base=B1) +V5 = V4 - PI * 6.25 * 20 - PI * 2.1 ** 2 * 8 - PI * 1.65 ** 2 * 35 +case(T5, "013-part-holes", "the running part: an M6 tapped through hole, an M5 tapped blind hole in the boss, a d3.3 hole down through wall and base", + [H(position=[52, 22, 20], thread="M6"), H(position=[15, 15, 30], thread="M5", depth=8), H(position=[5, 35, 35], diameter=3.3)], + {"body_count": 1, "volume_mm3": V5, "faces": 20}, + "removed pi 2.5^2 20 (M6, tap drill 5) + pi 2.1^2 8 (M5, tap drill 4.2, flat) + pi 1.65^2 35; faces +1 +2 +1", base=C4) +C5 = up(T5, "013-part-holes") + +# ---------------------------------------------------------------- 06 threads +def thr(face, **p): + return C("solid.thread", face=face, modeled=True, **p) +def ext_volume(d, P, sections): + """2 pi int int r m(r) over the stock's meridian section, given as [(z0, z1, rmax(z))].""" + r1, m = iso_material_fraction_ext(d, P) + def ring(rmax): + a = quad(lambda r: r, 0, min(rmax, r1)) if rmax > 0 else 0.0 + b = quad(lambda r: r * m(r), r1, rmax, 64) if rmax > r1 else 0.0 + return a + b + return 2 * PI * sum(quad(lambda z: ring(f(z)), z0, z1, 64) for z0, z1, f in sections) +TOL_T = {"rel": 5e-3} +TMETH = ("ISO 68-1 basic profile (60 deg, root flat P/4 at d - 5H/4 ... minor d1 = d - 1.0825 P, crest flat P/8): a helical groove of fixed section is a screw motion, " + "so averaged over the turn every meridian half-plane holds the same section and V = 2 pi int int r m(r) dr dz over the stock's section, m the material fraction per pitch; " + "the groove runs out past free ends. Tolerance 0.5%: solid.thread cuts faceted by design (chords within P/200 on the body, P/100 on the tool)") +cylT = [cyl([0, 0, 0], 5, 30, body_name="Part")] +case(T6, "001-external-m10-full", "a modelled M10 thread over the whole of a d10 x 30 cylinder", cylT + [thr([5, 0, 15], designation="M10")], + {"body_count": 1, "volume_mm3": ext_volume(10, 1.5, [(0, 30, lambda z: 5)]), "tolerance": TOL_T}, + TMETH + "; P = 1.5, L = 30") +case(T6, "002-external-m10x1-fine", "a modelled fine M10x1 thread over a d10 x 30 cylinder", cylT + [thr([5, 0, 15], designation="M10x1")], + {"body_count": 1, "volume_mm3": ext_volume(10, 1.0, [(0, 30, lambda z: 5)]), "tolerance": TOL_T}, + TMETH + "; P = 1, L = 30") +case(T6, "003-external-m10-partial-length", "a modelled M10 thread over 15 of the 30 mm: it stops square in the middle of the face", cylT + [thr([5, 0, 15], designation="M10", length=15)], + {"body_count": 1, "bbox": {"min": [-5, -5, 0], "max": [5, 5, 30]}}, + "the square stop has no closed form; one valid body inside the stock's box", source="invariants") +shaft = [cyl([0, 0, 0], 8, 10, body_name="Part"), cyl([0, 0, 10], 5, 25, operation="join", targets=["Part"])] +case(T6, "004-external-m10-to-shoulder", "a modelled M10 thread on a d10 shaft running down to a d16 shoulder", shaft + [thr([5, 0, 25], designation="M10")], + {"body_count": 1, "bbox": {"min": [-8, -8, 0], "max": [8, 8, 35]}}, + "the thread must end at the shoulder, leaving the d16 collar whole; no closed form for the end; one valid body", source="invariants") +dI, PI_ = 10, 1.5 +g = iso_groove_fraction_int(dI, PI_) +vg = 20 * 2 * PI * quad(lambda r: r * g(r), 4.25, 5) +case(T6, "005-internal-m10-through", "a modelled internal M10 thread in a d8.5 (tap drill) hole through a 30x30x20 block", + [box([0, 0, 0], 30, 30, 20, body_name="Part"), H(position=[15, 15, 20], diameter=8.5), thr([19.25, 15, 10], designation="M10")], + {"body_count": 1, "volume_mm3": 18000 - PI * 4.25 ** 2 * 20 - vg, "tolerance": TOL_T}, + "ISO 68-1 internal basic profile: groove P/8 wide at the major diameter, growing 2 tan30 per mm inward; the drilled hole r4.25 is above the minor r4.188, so the groove removes 2 pi int_4.25^5 r g(r) dr per mm; screw motion argument as in 001; both ends run out") +case(T6, "006-external-m10-into-chamfer", "a d10 x 30 cylinder with a 1 mm chamfer on top, then a modelled M10 thread over its cylinder face, running out through the chamfer", + cylT + [C("solid.chamfer", edges=[[5, 0, 30]], distance=1), thr([5, 0, 15], designation="M10")], + {"body_count": 1, "volume_mm3": ext_volume(10, 1.5, [(0, 29, lambda z: 5), (29, 30, lambda z: 5 - (z - 29))]), "tolerance": TOL_T}, + TMETH + "; the stock's section is r <= 5 below z=29 and r <= 5 - (z-29) in the chamfer; the groove runs one pitch past z=29, which covers the chamfer down to r=4.2 (above the root r=4.188 only for 0.01 mm)") +case(T6, "007-external-m10-at-filleted-shoulder", "a modelled M10 thread on a d10 shaft whose root at the d16 shoulder has an r1 fillet", + shaft + [C("solid.fillet", edges=[[5, 0, 10]], radius=1), thr([5, 0, 25], designation="M10")], + {"body_count": 1, "bbox": {"min": [-8, -8, 0], "max": [8, 8, 35]}}, + "thread meeting a concave fillet; one valid body", source="invariants") +missing(T6, "008-multi-start-thread", "a two-start M10 thread (lead 3, pitch 1.5)", "solid.thread has no start count: multi-start threads", + "would be graded as 001 with the material fraction of two grooves per lead") +missing(T6, "009-thread-runout-length", "an M10 thread that vanishes over a 2-pitch runout before a shoulder", "solid.thread has no runout (vanishing thread) length", + "a runout would let 004 end without cutting the shoulder") +case(T6, "010-part-threaded-boss", "the running part with a modelled M16 thread on its d16 boss", + [thr([23, 15, 25], designation="M16")], {"body_count": 1}, + "the boss stands on the base (thread to a shoulder) and has an M5 hole in its top; modelled threads are faceted, so later tiers build on the part before this step and 11-cumulative-parts adds it last", base=C5, source="invariants") + +# ---------------------------------------------------------------- 07 fillets +F = lambda edges, r, **p: C("solid.fillet", edges=edges, radius=r, **p) +case(T7, "001-single-edge-r5", "an r5 round on the stock's top front edge", [F([[30, 0, 20]], 5)], + {"body_count": 1, "volume_mm3": VB - SPANDREL * 25 * 60, "faces": 7}, "removed (1 - pi/4) r^2 L", base=B1) +V72 = VB - 4 * SPANDREL * 25 * 20 +case(T7, "002-four-vertical-edges-r5", "r5 rounds on the four vertical edges", [F([[0, 0, 10], [60, 0, 10], [60, 40, 10], [0, 40, 10]], 5)], + {"body_count": 1, "volume_mm3": V72, "faces": 10}, "removed 4 (1 - pi/4) r^2 20", base=B1) +c = 5 - 5 / math.sqrt(2) +case(T7, "003-tangent-chain-r3", "an r3 round all round the top of the box with r5 vertical rounds: a tangent chain of lines and arcs", + [F([[30, 0, 20], [60, 20, 20], [30, 40, 20], [0, 20, 20], [c, c, 20], [60 - c, c, 20], [60 - c, 40 - c, 20], [c, 40 - c, 20]], 3)], + {"body_count": 1, "volume_mm3": V72 - SPANDREL * 9 * 160 - SPANDREL * 9 * 2 * PI * (5 - XBAR * 3), "faces": 18}, + "straight runs 2*50 + 2*30 = 160 remove (1-pi/4) r^2 each mm; at each corner the round is a torus quarter: Pappus, the section (1-pi/4) r^2 with its centroid r(10-3pi)/(12-3pi) in from the wall, at radius 5 - that, over a quarter turn", + base=up(T7, "002-four-vertical-edges-r5")) +case(T7, "004-convex-and-concave-r5", "an L block: r5 on its inner (concave) vertical edge and on an outer (convex) one", + sketch("XY", line([[0, 0], [40, 0], [40, 10], [10, 10], [10, 40], [0, 40]], closed=True)) + [C("solid.extrude", distance=20), F([[10, 10, 10], [0, 0, 10]], 5)], + {"body_count": 1, "volume_mm3": 700 * 20, "faces": 10}, + "the concave round adds (1-pi/4) r^2 20, the convex one removes the same: V = 700*20") +case(T7, "005-variable-r5-to-r1", "a variable round on the top front edge, r5 at x=0 to r1 at x=60", + [F([[30, 0, 20]], 5, type="variable", radius2=1, start=[0, 0, 20])], + {"body_count": 1, "volume_mm3": VB - SPANDREL * 60 * (25 + 5 + 1) / 3, "faces": 7}, + "linear radius, quarter-round sections (tests/oracle/77): V = abc - (1-pi/4) L (a^2 + ab + b^2)/3", base=B1) +case(T7, "006-variable-r5-to-zero", "a variable round on the top front edge, r5 at x=0 down to 0 at the x=60 vertex", + [F([[30, 0, 20]], 5, type="variable", radius2=0, start=[0, 0, 20])], + {"body_count": 1, "volume_mm3": VB - SPANDREL * 60 * 25 / 3, "faces": 7}, + "as 005 with b=0 (tests/oracle/78): the round closes to the corner, which stays", base=B1) +case(T7, "007-variable-r5-to-1e-3", "a variable round on the top front edge, r5 down to 1e-3 mm", + [F([[30, 0, 20]], 5, type="variable", radius2=0.001, start=[0, 0, 20])], + {"body_count": 1, "volume_mm3": VB - SPANDREL * 60 * (25 + 5e-3 + 1e-6) / 3, "faces": 7}, + "as 005 with b = 1e-3: the round's end is a 1e-3 mm quarter circle on the end face", base=B1) +case(T7, "008-constant-r1e-3", "a constant 1e-3 mm round on the top front edge", + [F([[30, 0, 20]], 0.001)], + {"body_count": 1, "volume_mm3": VB - SPANDREL * 1e-6 * 60, "faces": 7}, "removed (1-pi/4) 1e-6 60, a valid 1e-3 round face", base=B1) +corner = [[30, 40, 20], [60, 20, 20], [60, 40, 10]] +case(T7, "009-corner-three-equal-r5", "r5 rounds on the three edges meeting at the corner (60,40,20): a sphere corner", + [F(corner, 5)], + {"body_count": 1, "volume_mm3": VB - SPANDREL * 25 * (60 + 40 + 20 - 15) - 125 * (1 - PI / 6), "faces": 10}, + "each edge zone outside the corner cube removes (1-pi/4) r^2 (L - r); the corner cube r^3 keeps a sphere octant pi r^3/6", base=B1) +case(T7, "010-corner-unequal-r7-r3", "an r7 vertical round at a corner, then r3 along a top edge, which runs on round the r7 arc to the other top edge", + [F([corner[2]], 7), F([[60, 15, 20]], 3)], {"body_count": 1}, + "two radii meeting at a corner over a tangent chain; one valid body", base=B1, source="invariants") +case(T7, "016-corner-three-sequential-r5-r3-r7", "rounds r5 and r3 on the two top edges at a corner, then r7 on the vertical edge under them", + [F([corner[0]], 5), F([corner[1]], 3), F([[60, 40, 8]], 7)], {"body_count": 1}, + "three unequal radii at one corner, one after another; the vertical edge stays sharp below the two top rounds (x=60, y=40, z from 0 to 15), so it can be picked at z=8; one valid body", base=B1, source="invariants") +case(T7, "011-chain-ending-at-corner", "r5 rounds on two top edges meeting at a corner whose vertical edge stays sharp", + [F([corner[0], corner[1]], 5)], {"body_count": 1}, + "two rounds ending at a sharp vertical edge; one valid body", base=B1, source="invariants") +case(T7, "012-variable-into-rounded-corner", "a vertical r5 round, then a variable round r1 -> r5 along the top back edge into it", + [F([corner[2]], 5), F([[30, 40, 20]], 1, type="variable", radius2=5, start=[0, 40, 20])], {"body_count": 1}, + "a variable round running into a constant one at a corner; one valid body", base=B1, source="invariants") +missing(T7, "013-corner-setback", "three r5 rounds at a corner with setbacks of 8 mm along each edge", "solid.fillet has no corner setback distances", + "the setback corner patch would be graded by invariants") +case(T7, "014-curved-edge-r2", "an r2 round on the top edge of a cylinder r10 h20", + [cyl([0, 0, 0], 10, 20, body_name="Part"), F([[10, 0, 20]], 2)], + {"body_count": 1, "volume_mm3": PI * 100 * 20 - SPANDREL * 4 * 2 * PI * (10 - XBAR * 2), "faces": 4}, + "a torus round: Pappus on the removed section (1-pi/4) r^2 at radius 10 - r(10-3pi)/(12-3pi)") +case(T7, "015-corner-g2-patch", "three r5 rounds at a corner with the g2 corner patch", + [F(corner, 5, corner="g2")], {"body_count": 1, "faces": 10}, + "the g2 patch replaces the sphere octant; no closed-form volume; one valid body, the same faces as 009", base=B1, source="invariants") +V7 = V5 - 2 * SPANDREL * 25 * 20 - SPANDREL * 9 * 60 + SPANDREL * 1 * 2 * PI * (8 + XBAR * 1) +case(T7, "017-part-rounds", "the running part: r5 on the front vertical edges, r3 on the back wall's top front edge, r1 where the boss meets the top", + [F([[0, 0, 10], [60, 0, 10]], 5), F([[30, 30, 35]], 3), F([[23, 15, 20]], 1)], + {"body_count": 1, "volume_mm3": V7, "faces": 24}, + "removed 2 (1-pi/4) 25 20 + (1-pi/4) 9 60; the concave r1 at the boss adds (1-pi/4) 1 2 pi (8 + (10-3pi)/(12-3pi)); none of them reaches a hole, the slot or the window", base=C5) +C7 = up(T7, "017-part-rounds") + +# ---------------------------------------------------------------- 08 chamfers +K = lambda edges, d, **p: C("solid.chamfer", edges=edges, distance=d, **p) +case(T8, "001-equal-5", "a 5 mm chamfer on the top front edge", [K([[30, 0, 20]], 5)], + {"body_count": 1, "volume_mm3": VB - 12.5 * 60, "faces": 7}, "removed d^2/2 L", base=B1) +case(T8, "002-two-distances-5-3", "a 5 x 3 chamfer on the top front edge", [K([[30, 0, 20]], 5, distance2=3)], + {"body_count": 1, "volume_mm3": VB - 7.5 * 60, "faces": 7}, "removed d1 d2 / 2 L", base=B1) +case(T8, "003-distance-angle-5-30", "a chamfer 5 mm at 30 deg on the top front edge", [K([[30, 0, 20]], 5, angle=30)], + {"body_count": 1, "volume_mm3": VB - 0.5 * 5 * 5 * math.tan(math.radians(30)) * 60, "faces": 7}, "legs 5 and 5 tan30: removed 5 * 5 tan30 / 2 * 60", base=B1) +d = 3 +case(T8, "004-corner-three-equal-3", "3 mm chamfers on the three edges meeting at a corner", + [K(corner, 3)], + {"body_count": 1, "volume_mm3": VB - (d * d / 2 * 120 - d ** 3 + d ** 3 / 4), "faces": 9}, + "the union of the three prisms {b+c<=d}, {a+c<=d}, {a+b<=d} (a, b, c distances from the corner faces): sum d^2/2 L - 3 d^3/3 + the triple part d^3/4 (exact integral)", base=B1) +case(T8, "005-corner-two-mitred-3", "3 mm chamfers on two top edges meeting at a corner (mitred)", + [K(corner[:2], 3)], + {"body_count": 1, "volume_mm3": VB - (d * d / 2 * 100 - d ** 3 / 3), "faces": 8}, + "two prisms meeting in a mitre: d^2/2 (60 + 40) - d^3/3", base=B1) +case(T8, "006-curved-edge-2", "a 2 mm chamfer on the top edge of a cylinder r10 h20", + [cyl([0, 0, 0], 10, 20, body_name="Part"), K([[10, 0, 20]], 2)], + {"body_count": 1, "volume_mm3": PI * 100 * 20 - 2 * 2 * PI * (10 - 2 / 3), "faces": 4}, + "a cone chamfer: Pappus on the triangle (area 2) with its centroid at radius 10 - 2/3") +case(T8, "007-chamfer-then-fillets", "a 3 mm chamfer on the top front edge, then r2 rounds on the front vertical edges that meet it", + [K([[30, 0, 20]], 3), F([[0, 0, 5], [60, 0, 5]], 2)], {"body_count": 1}, + "rounds running into a chamfer face; one valid body", base=B1, source="invariants") +V8 = V7 - 0.5 * 2 * PI * (8 - 1 / 3) - 2 * 60 +case(T8, "008-part-chamfers", "the running part: 1 mm on the boss top edge, 2 mm on the back wall's top back edge", + [K([[23, 15, 30]], 1), K([[30, 40, 35]], 2)], + {"body_count": 1, "volume_mm3": V8, "faces": 26}, + "removed 0.5 * 2 pi (8 - 1/3) (Pappus) + 2 * 60", base=C7) +C8 = up(T8, "008-part-chamfers") + +# ---------------------------------------------------------------- 09 shells and offsets +S = lambda faces, t, **p: C("solid.shell", faces=faces, thickness=t, **p) +case(T9, "001-box-open-top-t2", "the stock shelled 2 mm, open at the top", [S([[30, 20, 20]], 2)], + {"body_count": 1, "volume_mm3": VB - 56 * 36 * 18, "faces": 11}, "cavity 56x36x18; faces 5 outer + rim + 5 inner", base=B1) +case(T9, "002-cylinder-open-top-t1", "a cylinder r10 h30 shelled 1 mm, open at the top", + [cyl([0, 0, 0], 10, 30, body_name="Part"), S([[0, 0, 30]], 1)], + {"body_count": 1, "volume_mm3": PI * 100 * 30 - PI * 81 * 29, "faces": 5}, "cavity r9 x 29") +case(T9, "003-tube-two-open-faces", "the stock shelled 2 mm, open top and bottom", [S([[30, 20, 20], [30, 20, 0]], 2)], + {"body_count": 1, "volume_mm3": 20 * (2400 - 56 * 36), "faces": 10}, "a rectangular tube: 20 (60*40 - 56*36)", base=B1) +case(T9, "004-thin-wall-0.2", "the stock shelled 0.2 mm, open at the top", [S([[30, 20, 20]], 0.2)], + {"body_count": 1, "volume_mm3": VB - 59.6 * 39.6 * 19.8, "faces": 11}, "cavity 59.6 x 39.6 x 19.8", base=B1) +case(T9, "005-shell-rounds-r5-t2", "the box with r5 vertical rounds shelled 2 mm open at the top: the inner corners r3", + [S([[30, 20, 20]], 2)], + {"body_count": 1, "volume_mm3": V72 - 18 * (56 * 36 - 4 * SPANDREL * 9), "faces": 19}, + "the cavity is the 56x36 rounded rectangle with r 5-2 = 3 corners, 18 deep", base=up(T7, "002-four-vertical-edges-r5")) +case(T9, "006-shell-rounds-r1-thinner-than-t2", "the box with r1 vertical rounds shelled 2 mm: the inner corners close sharp", + [F([[0, 0, 10], [60, 0, 10], [60, 40, 10], [0, 40, 10]], 1), S([[30, 20, 20]], 2)], + {"body_count": 1, "volume_mm3": 20 * (2400 - 4 * SPANDREL) - 18 * 56 * 36, "faces": 15}, + "an inward offset of r1 by 2 vanishes: the cavity is a sharp 56x36x18 box", base=B1) +case(T9, "007-offset-top-face-5", "the stock's top face offset 5 mm", [C("solid.offset_face", faces=[[30, 20, 20]], distance=5)], + {"body_count": 1, "volume_mm3": 60 * 40 * 25, "faces": 6}, "a 60x40x25 box", base=B1) +case(T9, "008-offset-cylinder-wall-2", "a cylinder r10 h20 with its side face offset 2 mm", + [cyl([0, 0, 0], 10, 20, body_name="Part"), C("solid.offset_face", faces=[[10, 0, 10]], distance=2)], + {"body_count": 1, "volume_mm3": PI * 144 * 20, "faces": 3}, "a cylinder r12 h20") +case(T9, "009-shell-outside-t2", "the stock shelled 2 mm outward, open at the top", [S([[30, 20, 20]], 2, direction="outside")], + {"body_count": 1, "volume_mm3": 64 * 44 * 22 - VB, "faces": 11}, "walls grown out of the closed faces: 64 x 44 x 22 less the original box", base=B1) +case(T9, "010-shell-under-rounded-top", "the stock with r3 rounds on its four top edges, shelled 2 mm open at the bottom", + [F([[30, 0, 20], [60, 20, 20], [30, 40, 20], [0, 20, 20]], 3), S([[30, 20, 0]], 2)], {"body_count": 1}, + "walls meeting rounds and their corner patches; one valid body", base=B1, source="invariants") +case(T9, "011-part-shell", "the running part shelled 1.5 mm open at the bottom", + [S([[30, 20, 0]], 1.5)], {"body_count": 1}, + "a shell around holes, a slot, a window, rounds and chamfers; one valid body", base=C8, source="invariants") +C9 = up(T9, "011-part-shell") + +# ---------------------------------------------------------------- 10 patterns and mirrors +case(T10, "001-rect-pattern-holes", "a d5 hole patterned 3 x 2 at 20 mm in a 60x40x10 plate", + [box([0, 0, 0], 60, 40, 10, body_name="Part"), H(position=[10, 10, 10], diameter=5, name="H"), + C("solid.pattern.rectangular", features=["H"], dir1=[1, 0, 0], count1=3, spacing1=20, dir2=[0, 1, 0], count2=2, spacing2=20)], + {"body_count": 1, "volume_mm3": 24000 - 6 * PI * 6.25 * 10, "faces": 12}, "six holes pi 2.5^2 10") +case(T10, "002-circular-pattern-holes", "a d6 hole patterned 8 times round a disk r30 h10", + [cyl([0, 0, 0], 30, 10, body_name="Part"), H(position=[20, 0, 10], diameter=6, name="H"), C("solid.pattern.circular", features=["H"], axis="Z", count=8)], + {"body_count": 1, "volume_mm3": PI * 900 * 10 - 8 * PI * 9 * 10, "faces": 11}, "eight holes pi 3^2 10") +# union of 6 disks r5 centred on a circle r6: polar integral of the covered radial intervals +def covered(theta): + iv = [] + for k in range(6): + a = theta - k * PI / 3 + disc = 36 * math.cos(a) ** 2 - 11 + if disc > 0 and math.cos(a) > 0: + iv.append((6 * math.cos(a) - math.sqrt(disc), 6 * math.cos(a) + math.sqrt(disc))) + iv.sort() + tot, cur = 0.0, None + for a, b in iv: + if cur is None or a > cur[1]: + if cur: tot += (cur[1] ** 2 - cur[0] ** 2) / 2 + cur = [a, b] + else: + cur[1] = max(cur[1], b) + return tot + ((cur[1] ** 2 - cur[0] ** 2) / 2 if cur else 0) +Au = 12 * quad_split(covered, 0, PI / 6, 400, 32) +Au_check = 6 * PI * 25 # sanity: less than the disks' sum +assert Au < Au_check +case(T10, "003-overlapping-pattern-leaves-core", "six r5 cuts patterned round a circle r6 in a disk r20: they overlap and cut a core free", + [cyl([0, 0, 0], 20, 10, body_name="Part"), cyl([6, 0, -1], 5, 12, operation="cut", targets=["Part"], name="H"), C("solid.pattern.circular", features=["H"], axis="Z", count=6)], + {"body_count": 2, "volume_mm3": PI * 400 * 10 - Au * 10}, + f"neighbouring cuts overlap (centres 6 apart < 10) but none reaches the axis (6 > 5): a core is cut free; the union's area {Au:.9f} by a polar integral of the covered radial intervals") +case(T10, "004-mirror-body-joined", "a 30x40x20 half with a d10 hole mirrored about x=30 and joined: the faces at x=30 coincide", + [box([0, 0, 0], 30, 40, 20, body_name="Part"), H(position=[20, 20, 20], diameter=10), + C("solid.mirror", bodies=["Part"], plane={"origin": [30, 0, 0], "normal": [1, 0, 0]}, combine=True)], + {"body_count": 1, "volume_mm3": VB - 2 * PI * 25 * 20, "faces": 8}, "the stock with two holes at x=20 and 40; the mirror face merges away") +case(T10, "005-mirror-feature", "a d10 hole in the stock mirrored about x=30 as a feature", + [H(position=[20, 20, 20], diameter=10, name="H"), C("solid.mirror", features=["H"], plane={"origin": [30, 0, 0], "normal": [1, 0, 0]})], + {"body_count": 1, "volume_mm3": VB - 2 * PI * 25 * 20, "faces": 8}, "two holes pi 5^2 20", base=B1) +case(T10, "006-circular-pattern-counterbores", "a counterbored hole (d5, d9 x 3) patterned 6 times round a disk r30 h10", + [cyl([0, 0, 0], 30, 10, body_name="Part"), H(position=[20, 0, 10], diameter=5, type="counterbore", cb_diameter=9, cb_depth=3, name="CB"), + C("solid.pattern.circular", features=["CB"], axis="Z", count=6)], + {"body_count": 1, "volume_mm3": PI * 900 * 10 - 6 * (PI * 6.25 * 7 + PI * 20.25 * 3), "faces": 21}, "each removes pi 2.5^2 7 + pi 4.5^2 3; 3 faces each") +case(T10, "007-pattern-bodies", "a pin r3 h10 patterned 4 times at 10 mm as bodies", + [cyl([0, 0, 0], 3, 10, body_name="Pin"), C("solid.pattern.rectangular", bodies=["Pin"], dir1=[1, 0, 0], count1=4, spacing1=10)], + {"body_count": 4, "volume_mm3": 4 * PI * 9 * 10, "faces": 12}, "four separate pins") +case(T10, "008-part-vents", "the running part with a d3 vent hole in its top patterned 4 times at 5 mm", + [H(position=[30, 24, 20], diameter=3, name="Vent"), C("solid.pattern.rectangular", features=["Vent"], dir1=[1, 0, 0], count1=4, spacing1=5)], + {"body_count": 1}, "holes through the shelled top skin; one valid body", base=C9, source="invariants") +C10 = up(T10, "008-part-vents") + +# ---------------------------------------------------------------- 11 cumulative parts +VL = 40 * 816 + SPANDREL * 25 * 40 - 12.5 * 40 - 2 * PI * 16 * 8 +case(T11, "001-bracket", "an L bracket: concave r5 round, 5 mm outer chamfer, a hole in each leg", + sketch(plane([0, 40, 0], [1, 0, 0], [0, 0, 1]), line([[0, 0], [60, 0], [60, 8], [8, 8], [8, 50], [0, 50]], closed=True)) + [C("solid.extrude", distance=40, body_name="Part"), + F([[8, 20, 8]], 5), K([[0, 20, 0]], 5), H(position=[40, 20, 8], diameter=8), H(position=[8, 20, 30], diameter=8)], + {"body_count": 1, "volume_mm3": VL, "faces": 12}, + "L section 60*8 + 8*42 = 816 times 40; + (1-pi/4) 25 40 (concave round) - 12.5*40 (chamfer) - 2 pi 4^2 8 (holes, 8 thick legs); features apart from each other") +Vf = PI * ((1600 - 64) * 10 + (225 - 64) * 20) - 6 * PI * 16 * 10 - 1.125 * 2 * PI * 14.5 + SPANDREL * 9 * 2 * PI * (15 + 3 * XBAR) +FL = sketch(vplane([0, 0, 0]), line([[8, 0], [40, 0], [40, 10], [15, 10], [15, 30], [8, 30]], closed=True)) + [C("solid.revolve", axis="Z", body_name="Part"), + H(position=[28, 0, 10], diameter=8, name="Bolt"), C("solid.pattern.circular", features=["Bolt"], axis="Z", count=6), + K([[15, 0, 30]], 1.5), F([[15, 0, 10]], 3)] +case(T11, "002-flange", "a revolved flange and hub with a bore, six bolt holes, a hub chamfer and a hub-root round", FL, + {"body_count": 1, "volume_mm3": Vf, "faces": 14}, + "pi((40^2-8^2)10 + (15^2-8^2)20) - 6 pi 4^2 10 - 1.125 * 2 pi 14.5 (chamfer, Pappus) + (1-pi/4) 9 * 2 pi (15 + 3(10-3pi)/(12-3pi)) (concave round)") +case(T11, "003-threaded-flange", "the flange with a modelled M30 thread on its hub, between the round and the chamfer, and M8 tapped holes in the disk", + [thr([15, 0, 20], designation="M30"), H(position=[0, 28, 10], thread="M8", name="Tap"), C("solid.pattern.circular", features=["Tap"], axis="Z", count=3)], + {"body_count": 1}, "a thread meeting a round and a chamfer, tapped holes; one valid body", base=up(T11, "002-flange"), source="invariants") +case(T11, "004-housing", "the running part with every tier: back wall, boss, slot, spline window, tapped holes, rounds, chamfers, shell, vents, and a modelled M16 thread on the boss", + [thr([23, 15, 25], designation="M16")], {"body_count": 1}, + "the last case carries every tier; one valid body", base=C10, source="invariants") + +# manifest: tiers in order, cases in authoring order until --record sorts them by time +WHAT = { + T1: "primitives with closed-form volumes", T2: "booleans in the degeneracy classes, and cuts that end in a single line", + T3: "extrudes and revolves of line-and-arc profiles", T4: "spline profiles, sweeps and lofts", T5: "holes and tapped holes", + T6: "modelled threads", T7: "constant, variable and corner fillets", T8: "chamfers", T9: "shells and offsets", + T10: "patterns and mirrors", T11: "parts combining every tier"} +mpath = os.path.join(ROOT, "ladder.json") +order = [T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11] +try: + with open(mpath) as f: + listed = {t["tier"]: t["cases"] for t in json.load(f)["tiers"]} +except FileNotFoundError: + listed = {} +tiers = [] +for t in order: + made = [f"ladder/{t}/{n}" for tt, n in CASES if tt == t] + # The recorded order first, then cases new to the tier in the order they are made here. + kept = [c for c in listed.get(t, []) if c in made or not c.startswith("ladder/")] + tiers.append({"tier": t, "what": WHAT[t], "cases": kept + [c for c in made if c not in kept]}) +m = {"what": "the kernel complexity ladder: tiers in order of complexity, each building on the shapes before it; cases within a tier in order of measured time (cargo xtask kernel-cases --ladder --record)", + "tiers": tiers} +with open(mpath, "w") as f: + json.dump(m, f, indent=2, sort_keys=True) + f.write("\n") +print(len(CASES), "cases") diff --git a/tools/kernel-ladder/ladder_lib.py b/tools/kernel-ladder/ladder_lib.py new file mode 100644 index 0000000000..2833187111 --- /dev/null +++ b/tools/kernel-ladder/ladder_lib.py @@ -0,0 +1,152 @@ +"""Helpers for the kernel ladder's cases (tests/kernel-cases/ladder): command builders, plain +Python 3 quadrature (standard library only) and the case writers. See cases.py.""" +import json +import math +import os + +PI = math.pi +ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..", "tests", "kernel-cases", "ladder") +CASES = [] # (tier, name) +TIERS = {} + +def C(cmd, **p): + return {"command": cmd, "params": p} + +def box(corner, l, w, h, **kw): + return C("solid.box", corner=corner, length=l, width=w, height=h, **kw) + +def cyl(base, r, h, **kw): + return C("solid.cylinder", base=base, radius=r, height=h, **kw) + +def sketch(plane, *items, name=None): + p = {"plane": plane} + if name: + p["name"] = name + return [C("sketch.create", **p), *items, C("sketch.finish")] + +def line(points, ids=None, closed=False): + p = {"points": points} + if ids: + p["ids"] = ids + if closed: + p["closed"] = True + return C("sketch.line", **p) + +def plane(origin, x, y): + return {"origin": origin, "x_dir": x, "y_dir": y} + +def vplane(origin): + """A vertical sketch plane: sketch x = world x, sketch y = world z (normal -Y).""" + return plane(origin, [1, 0, 0], [0, 0, 1]) + +def flat(*cmds): + out = [] + for c in cmds: + out.extend(c if isinstance(c, list) else [c]) + return out + +def leggauss(n): + """Gauss-Legendre nodes and weights on [-1, 1] (Newton on the Legendre polynomial).""" + xs, ws = [], [] + for i in range(1, n + 1): + x = math.cos(math.pi * (i - 0.25) / (n + 0.5)) + for _ in range(100): + p0, p1 = 1.0, x + for k in range(2, n + 1): + p0, p1 = p1, ((2 * k - 1) * x * p1 - (k - 1) * p0) / k + dp = n * (x * p1 - p0) / (x * x - 1) + dx = p1 / dp + x -= dx + if abs(dx) < 1e-16: + break + xs.append(x) + ws.append(2 / ((1 - x * x) * dp * dp)) + return xs, ws + +_GL = {} + +def quad(f, a, b, n=64): + if n not in _GL: + _GL[n] = leggauss(n) + x, w = _GL[n] + return sum(wi * f(a + (b - a) * (xi + 1) / 2) for xi, wi in zip(x, w)) * (b - a) / 2 + +def quad_split(f, a, b, pieces=200, n=32): + h = (b - a) / pieces + return sum(quad(f, a + i * h, a + (i + 1) * h, n) for i in range(pieces)) + +def bez(P, t): + c = ((1 - t) ** 3, 3 * t * (1 - t) ** 2, 3 * t * t * (1 - t), t ** 3) + return tuple(sum(ci * p[k] for ci, p in zip(c, P)) for k in range(2)) + +def dbez(P, t): + c = (3 * (1 - t) ** 2, 6 * t * (1 - t), 3 * t * t) + d = [tuple(P[i + 1][k] - P[i][k] for k in range(2)) for i in range(3)] + return tuple(sum(ci * q[k] for ci, q in zip(c, d)) for k in range(2)) + +def norm(v): + return math.hypot(*v) + +def bez_len(P): + return quad_split(lambda t: norm(dbez(P, t)), 0, 1, 50) + +def bez_min_radius(P): + best = 1e18 + for i in range(2001): + t = i / 2000 + d = dbez(P, t) + dd = tuple(6 * (1 - t) * (P[2][k] - 2 * P[1][k] + P[0][k]) + 6 * t * (P[3][k] - 2 * P[2][k] + P[1][k]) for k in range(2)) + kap = abs(d[0] * dd[1] - d[1] * dd[0]) / norm(d) ** 3 + if kap > 0: + best = min(best, 1 / kap) + return best + +def bez_area_under(P, base): + """Area between a Bezier arch (ends on y = base) and the line y = base: -int (y-base) dx.""" + return abs(quad(lambda t: (bez(P, t)[1] - base) * dbez(P, t)[0], 0, 1)) + +SPANDREL = 1 - PI / 4 # area factor of a fillet's removed section, times r^2 +XBAR = (10 - 3 * PI) / (12 - 3 * PI) # spandrel centroid from the corner, times r + +def case(tier, name, what, commands, expected, method, source="analytic", base=None, sources=None): + d = os.path.join(ROOT, tier, name) + os.makedirs(d, exist_ok=True) + recipe = {} + if base: + recipe["base"] = base + recipe["commands"] = commands + exp = {"sources": sources or [], "what": what, "source": source, "method": method} + for k, v in expected.items(): + if isinstance(v, float): + v = round(v, 9) + exp[k] = v + with open(os.path.join(d, "recipe.json"), "w") as f: + json.dump(recipe, f, indent=1) + f.write("\n") + with open(os.path.join(d, "expected.json"), "w") as f: + json.dump(exp, f, indent=1) + f.write("\n") + CASES.append((tier, name)) + +def missing(tier, name, what, why, method): + d = os.path.join(ROOT, tier, name) + os.makedirs(d, exist_ok=True) + with open(os.path.join(d, "recipe.json"), "w") as f: + json.dump({"commands": []}, f, indent=1) + f.write("\n") + with open(os.path.join(d, "expected.json"), "w") as f: + json.dump({"sources": [], "what": what, "source": "invariants", "method": method, "missing": why}, f, indent=1) + f.write("\n") + CASES.append((tier, name)) + +def iso_material_fraction_ext(d, P): + """ISO 68-1 basic external profile: material fraction m(r) per pitch, r in [r_root, d/2].""" + H = math.sqrt(3) / 2 * P + r1 = d / 2 - 5 * H / 8 + t = math.tan(PI / 6) + return r1, (lambda r: 1.0 if r <= r1 else 1 - (P / 4 + 2 * (r - r1) * t) / P) + +def iso_groove_fraction_int(d, P): + """ISO 68-1 basic internal profile: fraction of a pitch the groove takes at radius r <= d/2.""" + t = math.tan(PI / 6) + return lambda r: (P / 8 + 2 * (d / 2 - r) * t) / P