From b20fd3d9d69c26fbe55418d559d53e9f0bc4aac2 Mon Sep 17 00:00:00 2001 From: Erikas <5955795+erkexzcx@users.noreply.github.com> Date: Fri, 16 Oct 2026 05:19:15 +0300 Subject: [PATCH] Booleans: when the last-resort nudge fails both ways, try two more screw axes (#1490) (#1527) Thanks @erkexzcx. --- crates/engine/src/tests.rs | 1 + .../fixtures/battery-tpu-case-v2.solidcraft | 2501 +++++++++++++++++ crates/engine/src/tests/pleat_join.rs | 47 + crates/kernel/src/ops.rs | 39 +- tests/robustness/goldens.json | 5 +- 5 files changed, 2585 insertions(+), 8 deletions(-) create mode 100644 crates/engine/src/tests/fixtures/battery-tpu-case-v2.solidcraft create mode 100644 crates/engine/src/tests/pleat_join.rs diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index c39a5148..a1b92233 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -317,6 +317,7 @@ mod mirror_planes; #[cfg(not(target_arch = "wasm32"))] mod native_cad_import; mod path_scope; +mod pleat_join; mod primitive_points; mod rim_cut; mod robustness; diff --git a/crates/engine/src/tests/fixtures/battery-tpu-case-v2.solidcraft b/crates/engine/src/tests/fixtures/battery-tpu-case-v2.solidcraft new file mode 100644 index 00000000..198e620d --- /dev/null +++ b/crates/engine/src/tests/fixtures/battery-tpu-case-v2.solidcraft @@ -0,0 +1,2501 @@ +{ + "format": "solidcraft/21", + "name": "Battery TPU Case v2", + "units": "mm", + "params": [ + { + "name": "batt_w", + "expr": "290 mm", + "unit": "mm", + "comment": "Battery flat top/bottom width", + "model": false + }, + { + "name": "batt_bulge", + "expr": "10 mm", + "unit": "mm", + "comment": "Side bulge past the top/bottom corners", + "model": false + }, + { + "name": "batt_h", + "expr": "92 mm", + "unit": "mm", + "comment": "Battery height", + "model": false + }, + { + "name": "peak_left", + "expr": "27 mm", + "unit": "mm", + "comment": "Height of the left side's widest point", + "model": false + }, + { + "name": "peak_right", + "expr": "batt_h - peak_left", + "unit": "mm", + "comment": "Height of the right side's widest point", + "model": false + }, + { + "name": "batt_len", + "expr": "198 mm", + "unit": "mm", + "comment": "Battery length", + "model": false + }, + { + "name": "tab_w", + "expr": "15 mm", + "unit": "mm", + "comment": "Connector tab width", + "model": false + }, + { + "name": "tab_depth", + "expr": "5.5 mm", + "unit": "mm", + "comment": "Connector tab protrusion", + "model": false + }, + { + "name": "ramp_len", + "expr": "35 mm", + "unit": "mm", + "comment": "Ramp from tab back to the wall", + "model": false + }, + { + "name": "ramp_blend_wall", + "expr": "80 mm", + "unit": "mm", + "comment": "Blend radius ramp to wall", + "model": false + }, + { + "name": "ramp_blend_tab", + "expr": "40 mm", + "unit": "mm", + "comment": "Blend radius tab to ramp", + "model": false + }, + { + "name": "wall", + "expr": "2.5 mm", + "unit": "mm", + "comment": "TPU wall thickness", + "model": false + }, + { + "name": "r_big", + "expr": "5 mm", + "unit": "mm", + "comment": "Fillet on the 4 long edges", + "model": false + }, + { + "name": "r_small", + "expr": "1 mm", + "unit": "mm", + "comment": "Fillet on all other edges", + "model": false + }, + { + "name": "clear", + "expr": "1 mm", + "unit": "mm", + "comment": "Tool overshoot", + "model": false + }, + { + "name": "pleat_top", + "expr": "batt_h - 2 * r_small", + "unit": "mm", + "comment": "Height where the pleat fades out", + "model": false + }, + { + "name": "d1", + "expr": "batt_w", + "unit": "mm", + "model": true + }, + { + "name": "d2", + "expr": "batt_w", + "unit": "mm", + "model": true + }, + { + "name": "d3", + "expr": "batt_h", + "unit": "mm", + "model": true + }, + { + "name": "d4", + "expr": "batt_bulge", + "unit": "mm", + "model": true + }, + { + "name": "d5", + "expr": "peak_right", + "unit": "mm", + "model": true + }, + { + "name": "d6", + "expr": "batt_bulge", + "unit": "mm", + "model": true + }, + { + "name": "d7", + "expr": "peak_left", + "unit": "mm", + "model": true + }, + { + "name": "d10", + "expr": "batt_w + 2 * batt_bulge + 10 * clear", + "unit": "mm", + "model": true + }, + { + "name": "d11", + "expr": "clear", + "unit": "mm", + "model": true + }, + { + "name": "d12", + "expr": "batt_len", + "unit": "mm", + "model": true + }, + { + "name": "d13", + "expr": "batt_len + tab_depth", + "unit": "mm", + "model": true + }, + { + "name": "d14", + "expr": "batt_w / 2 - tab_w", + "unit": "mm", + "model": true + }, + { + "name": "d15", + "expr": "ramp_len", + "unit": "mm", + "model": true + }, + { + "name": "d16", + "expr": "ramp_blend_wall", + "unit": "mm", + "model": true + }, + { + "name": "d17", + "expr": "ramp_blend_tab", + "unit": "mm", + "model": true + } + ], + "features": [ + { + "id": 1, + "name": "BatteryProfile", + "suppressed": false, + "type": "sketch", + "plane": { + "type": "custom", + "plane": { + "origin": [ + 0.0, + 0.0, + 0.0 + ], + "x_dir": [ + 1.0, + 0.0, + 0.0 + ], + "y_dir": [ + -0.0, + 0.0, + 1.0 + ] + } + }, + "sketch": { + "points": [ + { + "id": "origin", + "pos": [ + 0.0, + 0.0 + ], + "fixed": true + }, + { + "id": "l1.start", + "pos": [ + -145.0, + 92.0 + ], + "fixed": false + }, + { + "id": "l1.end", + "pos": [ + 145.0, + 92.0 + ], + "fixed": false + }, + { + "id": "l2.end", + "pos": [ + 155.0, + 65.0 + ], + "fixed": false + }, + { + "id": "l3.end", + "pos": [ + 145.0, + 0.0 + ], + "fixed": false + }, + { + "id": "l4.end", + "pos": [ + -145.0, + 0.0 + ], + "fixed": false + }, + { + "id": "l5.end", + "pos": [ + -155.0, + 27.0 + ], + "fixed": false + }, + { + "id": "p1", + "pos": [ + -100.0, + 0.0 + ], + "fixed": true, + "link": "j1" + }, + { + "id": "p2", + "pos": [ + 100.0, + 0.0 + ], + "fixed": true, + "link": "j1" + }, + { + "id": "p3", + "pos": [ + 0.0, + -100.0 + ], + "fixed": true, + "link": "j2" + }, + { + "id": "p4", + "pos": [ + 0.0, + 100.0 + ], + "fixed": true, + "link": "j2" + } + ], + "curves": [ + { + "id": "l1", + "type": "line", + "a": 1, + "b": 2, + "construction": false + }, + { + "id": "l2", + "type": "line", + "a": 2, + "b": 3, + "construction": false + }, + { + "id": "l3", + "type": "line", + "a": 3, + "b": 4, + "construction": false + }, + { + "id": "l4", + "type": "line", + "a": 4, + "b": 5, + "construction": false + }, + { + "id": "l5", + "type": "line", + "a": 5, + "b": 6, + "construction": false + }, + { + "id": "l6", + "type": "line", + "a": 6, + "b": 1, + "construction": false + }, + { + "id": "l7", + "type": "line", + "a": 7, + "b": 8, + "construction": true, + "link": "j1" + }, + { + "id": "l8", + "type": "line", + "a": 9, + "b": 10, + "construction": true, + "link": "j2" + } + ], + "constraints": [ + { + "id": "k1", + "type": "horizontal", + "l": 0 + }, + { + "id": "k2", + "type": "horizontal", + "l": 3 + }, + { + "id": "k3", + "type": "collinear", + "a": 3, + "b": 6 + }, + { + "id": "k4", + "type": "symmetric", + "p": 1, + "q": 2, + "l": 7 + }, + { + "id": "k5", + "type": "symmetric", + "p": 4, + "q": 5, + "l": 7 + }, + { + "id": "k6", + "type": "length", + "l": 0, + "value": 290.0, + "param": "d1" + }, + { + "id": "k7", + "type": "length", + "l": 3, + "value": 290.0, + "param": "d2" + }, + { + "id": "k8", + "type": "distance_y", + "p": 1, + "q": 5, + "value": 92.0, + "param": "d3" + }, + { + "id": "k9", + "type": "distance_x", + "p": 2, + "q": 3, + "value": 10.0, + "param": "d4" + }, + { + "id": "k10", + "type": "distance_y", + "p": 4, + "q": 3, + "value": 65.0, + "param": "d5" + }, + { + "id": "k11", + "type": "distance_x", + "p": 5, + "q": 6, + "value": 10.0, + "param": "d6" + }, + { + "id": "k12", + "type": "distance_y", + "p": 5, + "q": 6, + "value": 27.0, + "param": "d7" + } + ], + "links": [ + { + "id": "j1", + "kind": "project", + "source": { + "type": "sketch_axis", + "name": "x" + } + }, + { + "id": "j2", + "kind": "project", + "source": { + "type": "sketch_axis", + "name": "y" + } + } + ], + "counters": { + "j": 2, + "k": 12, + "l": 8, + "p": 4 + } + } + }, + { + "id": 2, + "name": "BatteryStock", + "suppressed": false, + "body_names": [ + "Battery" + ], + "param_names": [ + "d8" + ], + "type": "extrude", + "sketch": 1, + "profiles": { + "type": "all" + }, + "extent": { + "distance": "batt_len + tab_depth + clear", + "convention": "total_distance", + "direction": "positive" + }, + "operation": "new_body" + }, + { + "id": 3, + "name": "PlanOutline", + "suppressed": false, + "param_names": [ + "d9" + ], + "type": "sketch", + "plane": { + "type": "offset", + "base": { + "type": "origin", + "name": "XY" + }, + "distance": "-clear" + }, + "sketch": { + "points": [ + { + "id": "origin", + "pos": [ + 0.0, + 0.0 + ], + "fixed": true + }, + { + "id": "l1.start", + "pos": [ + -160.0, + 1.0 + ], + "fixed": false + }, + { + "id": "l1.end", + "pos": [ + 160.0, + 1.0 + ], + "fixed": false + }, + { + "id": "l2.end", + "pos": [ + 160.0, + -198.0 + ], + "fixed": false + }, + { + "id": "corner1", + "pos": [ + -95.0, + -198.0 + ], + "fixed": false + }, + { + "id": "corner2", + "pos": [ + -130.0, + -203.5 + ], + "fixed": false + }, + { + "id": "l5.end", + "pos": [ + -160.0, + -203.5 + ], + "fixed": false + }, + { + "id": "p1", + "pos": [ + 0.0, + -100.0 + ], + "fixed": true, + "link": "j1" + }, + { + "id": "p2", + "pos": [ + 0.0, + 100.0 + ], + "fixed": true, + "link": "j1" + }, + { + "id": "p3", + "pos": [ + -88.75261852925456, + -198.0 + ], + "fixed": false + }, + { + "id": "p4", + "pos": [ + -101.17164524862525, + -198.96982996764112 + ], + "fixed": false + }, + { + "id": "a1.center", + "pos": [ + -88.75261852925458, + -278.0 + ], + "fixed": false + }, + { + "id": "p5", + "pos": [ + -126.91417737568737, + -203.01508501617946 + ], + "fixed": false + }, + { + "id": "p6", + "pos": [ + -133.12369073537272, + -203.5 + ], + "fixed": false + }, + { + "id": "a2.center", + "pos": [ + -133.12369073537272, + -163.5 + ], + "fixed": false + } + ], + "curves": [ + { + "id": "l1", + "type": "line", + "a": 1, + "b": 2, + "construction": false + }, + { + "id": "l2", + "type": "line", + "a": 2, + "b": 3, + "construction": false + }, + { + "id": "l3", + "type": "line", + "a": 3, + "b": 9, + "construction": false + }, + { + "id": "l4", + "type": "line", + "a": 10, + "b": 12, + "construction": false + }, + { + "id": "l5", + "type": "line", + "a": 13, + "b": 6, + "construction": false + }, + { + "id": "l6", + "type": "line", + "a": 6, + "b": 1, + "construction": false + }, + { + "id": "l7", + "type": "line", + "a": 7, + "b": 8, + "construction": true, + "link": "j1" + }, + { + "id": "a1", + "type": "arc", + "c": 11, + "a": 9, + "b": 10, + "construction": false + }, + { + "id": "a2", + "type": "arc", + "c": 14, + "a": 13, + "b": 12, + "construction": false + } + ], + "constraints": [ + { + "id": "k1", + "type": "horizontal", + "l": 0 + }, + { + "id": "k2", + "type": "vertical", + "l": 1 + }, + { + "id": "k3", + "type": "horizontal", + "l": 2 + }, + { + "id": "k4", + "type": "horizontal", + "l": 4 + }, + { + "id": "k5", + "type": "vertical", + "l": 5 + }, + { + "id": "k6", + "type": "symmetric", + "p": 1, + "q": 2, + "l": 6 + }, + { + "id": "k7", + "type": "length", + "l": 0, + "value": 320.0, + "param": "d10" + }, + { + "id": "k8", + "type": "distance_y", + "p": 0, + "q": 1, + "value": 1.0, + "param": "d11" + }, + { + "id": "k9", + "type": "distance_y", + "p": 0, + "q": 3, + "value": 198.0, + "param": "d12" + }, + { + "id": "k10", + "type": "distance_y", + "p": 0, + "q": 5, + "value": 203.5, + "param": "d13" + }, + { + "id": "k11", + "type": "distance_x", + "p": 0, + "q": 5, + "value": 130.0, + "param": "d14" + }, + { + "id": "k12", + "type": "distance_x", + "p": 5, + "q": 4, + "value": 35.0, + "param": "d15" + }, + { + "id": "k13", + "type": "point_on_curve", + "p": 4, + "c": 2 + }, + { + "id": "k14", + "type": "point_on_curve", + "p": 4, + "c": 3 + }, + { + "id": "k15", + "type": "tangent", + "a": 2, + "b": 7 + }, + { + "id": "k16", + "type": "tangent", + "a": 3, + "b": 7 + }, + { + "id": "k17", + "type": "radius", + "c": 7, + "value": 80.0, + "param": "d16" + }, + { + "id": "k18", + "type": "point_on_curve", + "p": 5, + "c": 3 + }, + { + "id": "k19", + "type": "point_on_curve", + "p": 5, + "c": 4 + }, + { + "id": "k20", + "type": "tangent", + "a": 3, + "b": 8 + }, + { + "id": "k21", + "type": "tangent", + "a": 4, + "b": 8 + }, + { + "id": "k22", + "type": "radius", + "c": 8, + "value": 40.0, + "param": "d17" + } + ], + "links": [ + { + "id": "j1", + "kind": "project", + "source": { + "type": "sketch_axis", + "name": "y" + } + } + ], + "counters": { + "a": 2, + "corner": 2, + "j": 1, + "k": 22, + "l": 7, + "p": 6 + } + } + }, + { + "id": 4, + "name": "TabAndRamp", + "suppressed": false, + "param_names": [ + "d18" + ], + "type": "extrude", + "sketch": 3, + "profiles": { + "type": "all" + }, + "extent": { + "distance": "batt_h + 2 * clear", + "convention": "total_distance", + "direction": "positive" + }, + "operation": "intersect", + "targets": [ + "Battery" + ] + }, + { + "id": 5, + "name": "CopyForCase", + "suppressed": false, + "body_names": [ + "Case" + ], + "param_names": [ + "d19", + "d20", + "d21" + ], + "type": "move", + "bodies": [ + "Battery" + ], + "translate": [ + "0", + "0", + "0" + ], + "copy": true + }, + { + "id": 6, + "name": "CaseShell", + "suppressed": false, + "param_names": [ + "d22" + ], + "face_names": [ + "F2:side:l4" + ], + "type": "shell", + "faces": [ + [ + 0.0, + -100.0, + 0.0 + ] + ], + "thickness": "wall", + "body": "Case", + "direction": "outside" + }, + { + "id": 7, + "name": "PleatCutSketch", + "suppressed": false, + "param_names": [ + "d23" + ], + "type": "sketch", + "plane": { + "type": "offset", + "base": { + "type": "origin", + "name": "XY" + }, + "distance": "-clear" + }, + "sketch": { + "points": [ + { + "id": "origin", + "pos": [ + 0.0, + 0.0 + ], + "fixed": true + }, + { + "id": "p1", + "pos": [ + -24.0, + -0.5 + ], + "fixed": false + }, + { + "id": "p2", + "pos": [ + 24.0, + -0.5 + ], + "fixed": false + }, + { + "id": "p3", + "pos": [ + 24.0, + 3.5 + ], + "fixed": false + }, + { + "id": "p4", + "pos": [ + -24.0, + 3.5 + ], + "fixed": false + } + ], + "curves": [ + { + "id": "l1", + "type": "line", + "a": 1, + "b": 2, + "construction": false + }, + { + "id": "l2", + "type": "line", + "a": 2, + "b": 3, + "construction": false + }, + { + "id": "l3", + "type": "line", + "a": 3, + "b": 4, + "construction": false + }, + { + "id": "l4", + "type": "line", + "a": 4, + "b": 1, + "construction": false + } + ], + "constraints": [ + { + "id": "k1", + "type": "horizontal", + "l": 0 + }, + { + "id": "k2", + "type": "horizontal", + "l": 2 + }, + { + "id": "k3", + "type": "vertical", + "l": 1 + }, + { + "id": "k4", + "type": "vertical", + "l": 3 + } + ], + "counters": { + "k": 4, + "l": 4, + "p": 4 + } + } + }, + { + "id": 8, + "name": "PleatCut", + "suppressed": false, + "param_names": [ + "d24" + ], + "type": "extrude", + "sketch": 7, + "profiles": { + "type": "all" + }, + "extent": { + "distance": "pleat_top + clear", + "convention": "total_distance", + "direction": "positive" + }, + "operation": "cut", + "targets": [ + "Case" + ] + }, + { + "id": 9, + "name": "PleatBottom", + "suppressed": false, + "type": "sketch", + "plane": { + "type": "origin", + "name": "XY" + }, + "sketch": { + "points": [ + { + "id": "origin", + "pos": [ + 0.0, + 0.0 + ], + "fixed": true + }, + { + "id": "l1.start", + "pos": [ + -26.0, + 3.0 + ], + "fixed": false + }, + { + "id": "l1.end", + "pos": [ + -24.76384, + 3.0 + ], + "fixed": false + }, + { + "id": "l2.end", + "pos": [ + -16.0568, + 14.97217 + ], + "fixed": false + }, + { + "id": "l3.end", + "pos": [ + -8.0, + 4.90117 + ], + "fixed": false + }, + { + "id": "l4.end", + "pos": [ + 0.0, + 14.90117 + ], + "fixed": false + }, + { + "id": "l5.end", + "pos": [ + 8.0, + 4.90117 + ], + "fixed": false + }, + { + "id": "l6.end", + "pos": [ + 16.0568, + 14.97217 + ], + "fixed": false + }, + { + "id": "l7.end", + "pos": [ + 24.76384, + 3.0 + ], + "fixed": false + }, + { + "id": "l8.end", + "pos": [ + 26.0, + 3.0 + ], + "fixed": false + }, + { + "id": "l9.end", + "pos": [ + 26.0, + 0.0 + ], + "fixed": false + }, + { + "id": "l10.end", + "pos": [ + 23.23616, + 0.0 + ], + "fixed": false + }, + { + "id": "l11.end", + "pos": [ + 15.9432, + 10.02783 + ], + "fixed": false + }, + { + "id": "l12.end", + "pos": [ + 8.0, + 0.09883 + ], + "fixed": false + }, + { + "id": "l13.end", + "pos": [ + 0.0, + 10.09883 + ], + "fixed": false + }, + { + "id": "l14.end", + "pos": [ + -8.0, + 0.09883 + ], + "fixed": false + }, + { + "id": "l15.end", + "pos": [ + -15.9432, + 10.02783 + ], + "fixed": false + }, + { + "id": "l16.end", + "pos": [ + -23.23616, + 0.0 + ], + "fixed": false + }, + { + "id": "l17.end", + "pos": [ + -26.0, + 0.0 + ], + "fixed": false + } + ], + "curves": [ + { + "id": "l1", + "type": "line", + "a": 1, + "b": 2, + "construction": false + }, + { + "id": "l2", + "type": "line", + "a": 2, + "b": 3, + "construction": false + }, + { + "id": "l3", + "type": "line", + "a": 3, + "b": 4, + "construction": false + }, + { + "id": "l4", + "type": "line", + "a": 4, + "b": 5, + "construction": false + }, + { + "id": "l5", + "type": "line", + "a": 5, + "b": 6, + "construction": false + }, + { + "id": "l6", + "type": "line", + "a": 6, + "b": 7, + "construction": false + }, + { + "id": "l7", + "type": "line", + "a": 7, + "b": 8, + "construction": false + }, + { + "id": "l8", + "type": "line", + "a": 8, + "b": 9, + "construction": false + }, + { + "id": "l9", + "type": "line", + "a": 9, + "b": 10, + "construction": false + }, + { + "id": "l10", + "type": "line", + "a": 10, + "b": 11, + "construction": false + }, + { + "id": "l11", + "type": "line", + "a": 11, + "b": 12, + "construction": false + }, + { + "id": "l12", + "type": "line", + "a": 12, + "b": 13, + "construction": false + }, + { + "id": "l13", + "type": "line", + "a": 13, + "b": 14, + "construction": false + }, + { + "id": "l14", + "type": "line", + "a": 14, + "b": 15, + "construction": false + }, + { + "id": "l15", + "type": "line", + "a": 15, + "b": 16, + "construction": false + }, + { + "id": "l16", + "type": "line", + "a": 16, + "b": 17, + "construction": false + }, + { + "id": "l17", + "type": "line", + "a": 17, + "b": 18, + "construction": false + }, + { + "id": "l18", + "type": "line", + "a": 18, + "b": 1, + "construction": false + } + ], + "constraints": [], + "counters": { + "l": 18 + } + } + }, + { + "id": 10, + "name": "PleatTop", + "suppressed": false, + "param_names": [ + "d25" + ], + "type": "sketch", + "plane": { + "type": "offset", + "base": { + "type": "origin", + "name": "XY" + }, + "distance": "pleat_top + 0.5 mm" + }, + "sketch": { + "points": [ + { + "id": "origin", + "pos": [ + 0.0, + 0.0 + ], + "fixed": true + }, + { + "id": "l1.start", + "pos": [ + -26.0, + 3.0 + ], + "fixed": false + }, + { + "id": "l1.end", + "pos": [ + -24.0, + 3.0 + ], + "fixed": false + }, + { + "id": "l2.end", + "pos": [ + -16.0, + 3.0 + ], + "fixed": false + }, + { + "id": "l3.end", + "pos": [ + -8.0, + 3.0 + ], + "fixed": false + }, + { + "id": "l4.end", + "pos": [ + 0.0, + 3.0 + ], + "fixed": false + }, + { + "id": "l5.end", + "pos": [ + 8.0, + 3.0 + ], + "fixed": false + }, + { + "id": "l6.end", + "pos": [ + 16.0, + 3.0 + ], + "fixed": false + }, + { + "id": "l7.end", + "pos": [ + 24.0, + 3.0 + ], + "fixed": false + }, + { + "id": "l8.end", + "pos": [ + 26.0, + 3.0 + ], + "fixed": false + }, + { + "id": "l9.end", + "pos": [ + 26.0, + 0.0 + ], + "fixed": false + }, + { + "id": "l10.end", + "pos": [ + 24.0, + 0.0 + ], + "fixed": false + }, + { + "id": "l11.end", + "pos": [ + 16.0, + 0.0 + ], + "fixed": false + }, + { + "id": "l12.end", + "pos": [ + 8.0, + 0.0 + ], + "fixed": false + }, + { + "id": "l13.end", + "pos": [ + 0.0, + 0.0 + ], + "fixed": false + }, + { + "id": "l14.end", + "pos": [ + -8.0, + 0.0 + ], + "fixed": false + }, + { + "id": "l15.end", + "pos": [ + -16.0, + 0.0 + ], + "fixed": false + }, + { + "id": "l16.end", + "pos": [ + -24.0, + 0.0 + ], + "fixed": false + }, + { + "id": "l17.end", + "pos": [ + -26.0, + 0.0 + ], + "fixed": false + } + ], + "curves": [ + { + "id": "l1", + "type": "line", + "a": 1, + "b": 2, + "construction": false + }, + { + "id": "l2", + "type": "line", + "a": 2, + "b": 3, + "construction": false + }, + { + "id": "l3", + "type": "line", + "a": 3, + "b": 4, + "construction": false + }, + { + "id": "l4", + "type": "line", + "a": 4, + "b": 5, + "construction": false + }, + { + "id": "l5", + "type": "line", + "a": 5, + "b": 6, + "construction": false + }, + { + "id": "l6", + "type": "line", + "a": 6, + "b": 7, + "construction": false + }, + { + "id": "l7", + "type": "line", + "a": 7, + "b": 8, + "construction": false + }, + { + "id": "l8", + "type": "line", + "a": 8, + "b": 9, + "construction": false + }, + { + "id": "l9", + "type": "line", + "a": 9, + "b": 10, + "construction": false + }, + { + "id": "l10", + "type": "line", + "a": 10, + "b": 11, + "construction": false + }, + { + "id": "l11", + "type": "line", + "a": 11, + "b": 12, + "construction": false + }, + { + "id": "l12", + "type": "line", + "a": 12, + "b": 13, + "construction": false + }, + { + "id": "l13", + "type": "line", + "a": 13, + "b": 14, + "construction": false + }, + { + "id": "l14", + "type": "line", + "a": 14, + "b": 15, + "construction": false + }, + { + "id": "l15", + "type": "line", + "a": 15, + "b": 16, + "construction": false + }, + { + "id": "l16", + "type": "line", + "a": 16, + "b": 17, + "construction": false + }, + { + "id": "l17", + "type": "line", + "a": 17, + "b": 18, + "construction": false + }, + { + "id": "l18", + "type": "line", + "a": 18, + "b": 1, + "construction": false + } + ], + "constraints": [], + "counters": { + "l": 18 + } + } + }, + { + "id": 11, + "name": "PleatNear", + "suppressed": false, + "type": "loft", + "sections": [ + { + "sketch": 9, + "profiles": { + "type": "all" + } + }, + { + "sketch": 10, + "profiles": { + "type": "all" + } + } + ], + "operation": "join", + "targets": [ + "Case" + ] + }, + { + "id": 12, + "name": "MidLength", + "suppressed": false, + "param_names": [ + "d26" + ], + "type": "construction_plane", + "plane": { + "type": "offset", + "base": { + "type": "origin", + "name": "XZ" + }, + "distance": "-batt_len / 2" + } + }, + { + "id": 13, + "name": "PleatFar", + "suppressed": false, + "type": "mirror", + "features": [ + "PleatCut", + "PleatNear" + ], + "plane": { + "type": "construction", + "name": "MidLength" + } + }, + { + "id": 14, + "name": "FilletBig", + "suppressed": false, + "param_names": [ + "d27" + ], + "edge_refs": [ + { + "dir": [ + 0.0, + -1.0, + 0.0 + ], + "min": [ + -158.0833834441611, + -212.97245548710006, + -0.0005980913533997968 + ], + "max": [ + 158.08338344416106, + 14.97245064486031, + 95.00000000000003 + ] + }, + { + "dir": [ + 0.0, + -1.0, + 0.0 + ], + "min": [ + -158.0833834441611, + -212.97245548710006, + -0.0005980913533997968 + ], + "max": [ + 158.08338344416106, + 14.97245064486031, + 95.00000000000003 + ] + }, + { + "dir": [ + 0.0, + 1.0, + 0.0 + ], + "min": [ + -158.0833834441611, + -212.97245548710006, + -0.0005980913533997968 + ], + "max": [ + 158.08338344416106, + 14.97245064486031, + 95.00000000000003 + ] + }, + { + "dir": [ + 0.0, + -1.0, + 0.0 + ], + "min": [ + -158.0833834441611, + -212.97245548710006, + -0.0005980913533997968 + ], + "max": [ + 158.08338344416106, + 14.97245064486031, + 95.00000000000003 + ] + }, + { + "dir": [ + 0.0, + 1.0, + 0.0 + ], + "min": [ + -158.0833834441611, + -212.97245548710006, + -0.0005980913533997968 + ], + "max": [ + 158.08338344416106, + 14.97245064486031, + 95.00000000000003 + ] + }, + { + "dir": [ + 0.0, + 1.0, + 0.0 + ], + "min": [ + -158.0833834441611, + -212.97245548710006, + -0.0005980913533997968 + ], + "max": [ + 158.08338344416106, + 14.97245064486031, + 95.00000000000003 + ] + }, + { + "dir": [ + 0.0, + -1.0, + 0.0 + ], + "min": [ + -158.0833834441611, + -212.97245548710006, + -0.0005980913533997968 + ], + "max": [ + 158.08338344416106, + 14.97245064486031, + 95.00000000000003 + ] + }, + { + "dir": [ + 0.0, + 1.0, + 0.0 + ], + "min": [ + -158.0833834441611, + -212.97245548710006, + -0.0005980913533997968 + ], + "max": [ + 158.08338344416106, + 14.97245064486031, + 95.00000000000003 + ] + } + ], + "edge_names": [ + "F2:side:l5@F5.1|F2:side:l6@F5.1", + "F2:side:l1@F5.1|F2:side:l6@F5.1", + "F2:side:l1@F5.1|F2:side:l2@F5.1", + "F2:side:l2@F5.1|F2:side:l3@F5.1", + "F6:inner:F2:side:l5@F5.1|F6:inner:F2:side:l6@F5.1", + "F6:inner:F2:side:l1@F5.1|F6:inner:F2:side:l6@F5.1", + "F6:inner:F2:side:l1@F5.1|F6:inner:F2:side:l2@F5.1", + "F6:inner:F2:side:l2@F5.1|F6:inner:F2:side:l3@F5.1" + ], + "type": "fillet", + "edges": [ + [ + -158.0834, + -101.75, + 26.6874 + ], + [ + -147.5738, + -101.75, + 95.0 + ], + [ + 147.088, + -99.0, + 95.0 + ], + [ + 158.0834, + -99.0, + 65.3126 + ], + [ + -155.0, + -101.75, + 27.0 + ], + [ + -145.0, + -101.75, + 92.0 + ], + [ + 145.0, + -99.0, + 92.0 + ], + [ + 155.0, + -99.0, + 65.0 + ] + ], + "radius": "r_big", + "body": "Case" + }, + { + "id": 15, + "name": "FilletFrontOuter", + "suppressed": false, + "param_names": [ + "d28" + ], + "edge_refs": [ + { + "dir": [ + 0.34731435582359377, + 0.0, + -0.9377487607237037 + ], + "min": [ + -157.9189160095873, + -212.97245548710006, + -0.0005980913533997968 + ], + "max": [ + 157.9189160095873, + 14.97245064486031, + 95.00000000000003 + ] + }, + { + "dir": [ + -0.1520571842539413, + 6.854850442197856e-18, + -0.9883716976506173 + ], + "min": [ + -157.9189160095873, + -212.97245548710006, + -0.0005980913533997968 + ], + "max": [ + 157.9189160095873, + 14.97245064486031, + 95.00000000000003 + ] + }, + { + "dir": [ + -0.3473143558235947, + 0.0, + 0.9377487607237034 + ], + "min": [ + -157.9189160095873, + -212.97245548710006, + -0.0005980913533997968 + ], + "max": [ + 157.9189160095873, + 14.97245064486031, + 95.00000000000003 + ] + }, + { + "dir": [ + 0.15205718425394107, + 6.990187846251234e-18, + 0.9883716976506172 + ], + "min": [ + -157.9189160095873, + -212.97245548710006, + -0.0005980913533997968 + ], + "max": [ + 157.9189160095873, + 14.97245064486031, + 95.00000000000003 + ] + }, + { + "dir": [ + 1.0, + -1.5479309295511088e-18, + 0.0 + ], + "min": [ + -157.9189160095873, + -212.97245548710006, + -0.0005980913533997968 + ], + "max": [ + 157.9189160095873, + 14.97245064486031, + 95.00000000000003 + ] + } + ], + "edge_names": [ + "F2:side:l2@F5.1|F2:start@F5.1", + "F2:side:l3@F5.1|F2:start@F5.1", + "F2:side:l5@F5.1|F2:start@F5.1", + "F2:side:l6@F5.1|F2:start@F5.1", + "F2:side:l1@F5.1|F2:start@F5.1" + ], + "type": "fillet", + "edges": [ + [ + 152.5857, + 3.0, + 80.1563 + ], + [ + 153.0593, + 3.0, + 32.6563 + ], + [ + -153.1413, + 3.0, + 13.3437 + ], + [ + -152.8286, + 3.0, + 60.8437 + ], + [ + -80.0, + 3.0, + 95.0 + ] + ], + "radius": "r_small", + "body": "Case" + }, + { + "id": 16, + "name": "FilletRearOuter", + "suppressed": false, + "param_names": [ + "d29" + ], + "edge_refs": [ + { + "dir": [ + -0.1520571842539413, + 0.0, + -0.9883716976506173 + ], + "min": [ + -157.92605659069406, + -212.97245548710006, + -0.0005980913533997968 + ], + "max": [ + 157.92605659069403, + 14.97245064486031, + 95.00000000000003 + ] + }, + { + "dir": [ + 0.34731435582359377, + 0.0, + -0.9377487607237037 + ], + "min": [ + -157.92605659069406, + -212.97245548710006, + -0.0005980913533997968 + ], + "max": [ + 157.92605659069403, + 14.97245064486031, + 95.00000000000003 + ] + }, + { + "dir": [ + -0.3473143558235947, + 0.0, + 0.9377487607237034 + ], + "min": [ + -157.92605659069406, + -212.97245548710006, + -0.0005980913533997968 + ], + "max": [ + 157.92605659069403, + 14.97245064486031, + 95.00000000000003 + ] + }, + { + "dir": [ + 0.15205718425394107, + 0.0, + 0.9883716976506172 + ], + "min": [ + -157.92605659069406, + -212.97245548710006, + -0.0005980913533997968 + ], + "max": [ + 157.92605659069403, + 14.97245064486031, + 95.00000000000003 + ] + }, + { + "dir": [ + 1.0, + 2.4463450711209976e-16, + 0.0 + ], + "min": [ + -157.92605659069406, + -212.97245548710006, + -0.0005980913533997968 + ], + "max": [ + 157.92605659069403, + 14.97245064486031, + 95.00000000000003 + ] + } + ], + "edge_names": [ + "F2:side:l3@F5.1|F4:side:l3@F5.1", + "F2:side:l2@F5.1|F4:side:l3@F5.1", + "F2:side:l5@F5.1|F4:side:l5@F5.1", + "F2:side:l6@F5.1|F4:side:l5@F5.1", + "F2:side:l1@F5.1|F4:side:l3@F5.1" + ], + "type": "fillet", + "edges": [ + [ + 153.0593, + -201.0, + 32.6563 + ], + [ + 152.5857, + -201.0, + 80.1563 + ], + [ + -153.1413, + -206.5, + 13.3437 + ], + [ + -152.8286, + -206.5, + 60.8437 + ], + [ + 50.0, + -201.0, + 95.0 + ] + ], + "radius": "r_small", + "body": "Case" + }, + { + "id": 17, + "name": "FilletFrontInner", + "suppressed": false, + "param_names": [ + "d30" + ], + "edge_refs": [ + { + "dir": [ + 0.1520571842539411, + 0.0, + 0.988371697650617 + ], + "min": [ + -157.9260565909712, + -212.97245548710006, + -0.0005980913533997968 + ], + "max": [ + 157.92605659097126, + 14.97245064486031, + 95.00000000000001 + ] + }, + { + "dir": [ + -0.34731435582359377, + 0.0, + 0.9377487607237037 + ], + "min": [ + -157.9260565909712, + -212.97245548710006, + -0.0005980913533997968 + ], + "max": [ + 157.92605659097126, + 14.97245064486031, + 95.00000000000001 + ] + }, + { + "dir": [ + -1.0, + 0.0, + 0.0 + ], + "min": [ + -157.9260565909712, + -212.97245548710006, + -0.0005980913533997968 + ], + "max": [ + 157.92605659097126, + 14.97245064486031, + 95.00000000000001 + ] + }, + { + "dir": [ + -0.15205718425394102, + 0.0, + -0.9883716976506172 + ], + "min": [ + -157.9260565909712, + -212.97245548710006, + -0.0005980913533997968 + ], + "max": [ + 157.92605659097126, + 14.97245064486031, + 95.00000000000001 + ] + }, + { + "dir": [ + 0.3473143558235949, + 0.0, + -0.9377487607237033 + ], + "min": [ + -157.9260565909712, + -212.97245548710006, + -0.0005980913533997968 + ], + "max": [ + 157.92605659097126, + 14.97245064486031, + 95.00000000000001 + ] + } + ], + "edge_names": [ + "F6:inner:F2:side:l3@F5.1|F6:inner:F2:start@F5.1", + "F6:inner:F2:side:l2@F5.1|F6:inner:F2:start@F5.1", + "F6:inner:F2:side:l1@F5.1|F6:inner:F2:start@F5.1", + "F6:inner:F2:side:l6@F5.1|F6:inner:F2:start@F5.1", + "F6:inner:F2:side:l5@F5.1|F6:inner:F2:start@F5.1" + ], + "type": "fillet", + "edges": [ + [ + 149.9014, + 0.0, + 31.8594 + ], + [ + 150.3792, + 0.0, + 77.4761 + ], + [ + 80.0, + 0.0, + 92.0 + ], + [ + -150.2276, + 0.0, + 58.0208 + ], + [ + -149.7749, + 0.0, + 12.8922 + ] + ], + "radius": "r_small", + "body": "Case" + }, + { + "id": 18, + "name": "FilletRearInner", + "suppressed": false, + "param_names": [ + "d31" + ], + "edge_refs": [ + { + "dir": [ + 0.1520571842539411, + 0.0, + 0.988371697650617 + ], + "min": [ + -157.9260565909712, + -212.97245548710006, + -0.0005980913533997968 + ], + "max": [ + 157.92605659097126, + 14.97245064486031, + 95.00000000000001 + ] + }, + { + "dir": [ + -0.34731435582359377, + 0.0, + 0.9377487607237037 + ], + "min": [ + -157.9260565909712, + -212.97245548710006, + -0.0005980913533997968 + ], + "max": [ + 157.92605659097126, + 14.97245064486031, + 95.00000000000001 + ] + }, + { + "dir": [ + -1.0, + -2.4685277687458984e-16, + 0.0 + ], + "min": [ + -157.9260565909712, + -212.97245548710006, + -0.0005980913533997968 + ], + "max": [ + 157.92605659097126, + 14.97245064486031, + 95.00000000000001 + ] + }, + { + "dir": [ + -0.15205718425394102, + 0.0, + -0.9883716976506172 + ], + "min": [ + -157.9260565909712, + -212.97245548710006, + -0.0005980913533997968 + ], + "max": [ + 157.92605659097126, + 14.97245064486031, + 95.00000000000001 + ] + }, + { + "dir": [ + 0.347314355823594, + 0.0, + -0.9377487607237036 + ], + "min": [ + -157.9260565909712, + -212.97245548710006, + -0.0005980913533997968 + ], + "max": [ + 157.92605659097126, + 14.97245064486031, + 95.00000000000001 + ] + } + ], + "edge_names": [ + "F6:inner:F2:side:l3@F5.1|F6:inner:F4:side:l3@F5.1", + "F6:inner:F2:side:l2@F5.1|F6:inner:F4:side:l3@F5.1", + "F6:inner:F2:side:l1@F5.1|F6:inner:F4:side:l3@F5.1", + "F6:inner:F2:side:l6@F5.1|F6:inner:F4:side:l5@F5.1", + "F6:inner:F2:side:l5@F5.1|F6:inner:F4:side:l5@F5.1" + ], + "type": "fillet", + "edges": [ + [ + 149.9014, + -198.0, + 31.8594 + ], + [ + 150.3792, + -198.0, + 77.4761 + ], + [ + 80.0, + -198.0, + 92.0 + ], + [ + -150.2276, + -203.5, + 58.0208 + ], + [ + -149.7749, + -203.5, + 12.8922 + ] + ], + "radius": "r_small", + "body": "Case" + } + ], + "next_id": 19, + "visibility": { + "hidden_bodies": [ + "Battery" + ], + "hidden_occurrences": [], + "hidden_origin": [], + "hidden_sketches": [], + "shown_sketches": [] + } +} \ No newline at end of file diff --git a/crates/engine/src/tests/pleat_join.rs b/crates/engine/src/tests/pleat_join.rs new file mode 100644 index 00000000..9ddaeaa5 --- /dev/null +++ b/crates/engine/src/tests/pleat_join.rs @@ -0,0 +1,47 @@ +//! #1490: the owner's shelled battery case (`battery-tpu-case-v2.solidcraft`, format 21, saved +//! at `wall = 2.5 mm`) with a pleat loft joined into a window in one end wall. The loft stands on +//! the case's open rim and its end strips lie in the wall's inner face. At walls of 2.4–2.6 mm +//! the join failed with "no result": every exact route was refused, and the last resort's +//! nudge failed both ways about its one screw axis. The other screws are now tried too. + +use std::path::Path; + +use serde_json::json; + +use super::run; +use crate::Session; + +fn open() -> Session { + let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("src/tests/fixtures/battery-tpu-case-v2.solidcraft"); + let mut s = Session::default(); + run(&mut s, "doc.open", json!({"path": path.to_string_lossy()})); + s +} + +fn errors(s: &Session) -> Vec<(String, String)> { + s.model.results.iter().filter_map(|r| Some((r.name.clone(), r.error.clone()?))).collect() +} + +fn case_volume(s: &mut Session) -> f64 { + let m = run(s, "inspect.measure", json!({"bodies": ["Case"]})); + m["total"]["volume_mm3"].as_f64().unwrap() +} + +/// The design rebuilds as saved, and at 2.4 and 2.6 mm, with both pleats and every round; the +/// case grows with the wall between them. +#[test] +fn the_pleats_join_at_every_wall_from_2_4_to_2_6_mm() { + let mut s = open(); + assert_eq!(errors(&s), Vec::new(), "as saved, at 2.5 mm"); + let at = case_volume(&mut s); + let mut volumes = Vec::new(); + for wall in ["2.4 mm", "2.6 mm"] { + run(&mut s, "parameters.change", json!({"name": "wall", "expression": wall})); + assert_eq!(errors(&s), Vec::new(), "at {wall}"); + volumes.push(case_volume(&mut s)); + } + // About 14.8 cm³ of wall per tenth of a millimetre here. + let (thin, thick) = (volumes[0], volumes[1]); + assert!(thin < at && at < thick, "{thin} < {at} < {thick}"); + assert!(((at - thin) - (thick - at)).abs() < 0.01 * (thick - thin), "{thin} {at} {thick}"); +} diff --git a/crates/kernel/src/ops.rs b/crates/kernel/src/ops.rs index 102b6e98..0afa9ee2 100644 --- a/crates/kernel/src/ops.rs +++ b/crates/kernel/src/ops.rs @@ -903,14 +903,15 @@ fn boolean_whole(a: &Body, b: &Body, op: BoolOp) -> Result> { if !APART.with(|c| c.get()) { APART.with(|c| c.set(true)); let c = b.solid.vertex_iter().fold(Vec3::ZERO, |acc, v| acc + from_p3(v.point())) * (1.0 / b.solid.vertex_iter().count().max(1) as f64); - let translation = Vec3::new(0.61, -0.37, 0.71) * (size * 2e-6); - let nudge = |sign: f64| { + let translation = |screw: usize| SCREWS.get(screw).map_or(Vec3::ZERO, |s| Vec3::new(s.0[0], s.0[1], s.0[2]) * (size * 2e-6)); + let nudge = |screw: usize, sign: f64| { + let axis = SCREWS.get(screw).map_or(Vec3::Z, |s| Vec3::new(s.1[0], s.1[1], s.1[2])); capture_boolean_diagnostics(|| { - transform(b, translation * sign, c, Vec3::new(0.27, 0.83, -0.49), 2e-6 * sign) - .and_then(|nb| Ok(boolean(a, &nb, op)?.map(|r| nudged(r, &nb, &counts)))) + transform(b, translation(screw) * sign, c, axis, 2e-6 * sign).and_then(|nb| Ok(boolean(a, &nb, op)?.map(|r| nudged(r, &nb, &counts)))) }) }; - let (mut r, mut diagnostics) = nudge(1.0); + let mut used = 0; + let (mut r, mut diagnostics) = nudge(used, 1.0); // A nudge that pushes a face into a curved face it only touched (a cut whose end lies // tangent to a round side) leaves a sliver of that face behind, a fraction of a // millimetre wide, which later features (a shell, a fillet) can't work with. The @@ -923,7 +924,7 @@ fn boolean_whole(a: &Body, b: &Body, op: BoolOp) -> Result> { }; let made = slivers.is_none_or(|n| n > 0 && n > sliver_faces(a, size).saturating_add(sliver_faces(b, size))); if !matches!(r, Ok(None)) && made { - let (r2, diagnostics2) = nudge(-1.0); + let (r2, diagnostics2) = nudge(used, -1.0); if let Ok(Some(body)) = &r2 && slivers.is_none_or(|n| sliver_faces(body, size) < n) && check(body).0 @@ -931,6 +932,26 @@ fn boolean_whole(a: &Body, b: &Body, op: BoolOp) -> Result> { (r, diagnostics) = (r2, diagnostics2); } } + // Neither way worked: the screw can turn the tool along a seam it lies in (a loft + // standing in the rim of a shelled case with its ends in the wall's inner face, #1490). + // The other screws are tried in turn, and the first result that checks is taken. + let works = |r: &Result>| match r { + Ok(None) => true, + Ok(Some(body)) => check(body).0, + Err(_) => false, + }; + if !works(&r) { + 'screws: for screw in 1..SCREWS.len() { + for sign in [1.0, -1.0] { + let (r2, diagnostics2) = nudge(screw, sign); + if matches!(&r2, Ok(Some(body)) if check(body).0) { + (r, diagnostics, used) = (r2, diagnostics2, screw); + break 'screws; + } + } + } + } + let translation = translation(used); APART.with(|c| c.set(false)); match r { Ok(Some(body)) => { @@ -972,6 +993,12 @@ fn boolean_whole(a: &Body, b: &Body, op: BoolOp) -> Result> { } } +/// The screw motions a tool is nudged by as a last resort: (direction of the move, axis of the +/// turn), each tried both ways. The first is the one every nudge used before #1490; the others +/// are asked only when it fails both ways. +const SCREWS: [([f64; 3], [f64; 3]); 3] = + [([0.61, -0.37, 0.71], [0.27, 0.83, -0.49]), ([-0.42, 0.68, 0.6], [0.77, -0.18, 0.61]), ([0.31, 0.52, -0.79], [-0.58, 0.41, 0.70])]; + /// Faces of `b` narrower than 1/200 of `size` (twice their area over their boundary's /// length): the slivers a nudged tool leaves where it was pushed into a face it touched. fn sliver_faces(b: &Body, size: f64) -> usize { diff --git a/tests/robustness/goldens.json b/tests/robustness/goldens.json index 0728e06e..cc943915 100644 --- a/tests/robustness/goldens.json +++ b/tests/robustness/goldens.json @@ -64,7 +64,7 @@ "60": {"area":3443.328607,"bodies":1,"edges":39,"error":null,"faces":15,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","cut sphere"],"unsupported":0,"volume":11779.204543}, "61": {"area":5020.413153,"bodies":1,"edges":36,"error":"boolean Cut: the operation failed: no result","faces":15,"outcome":"failed","rejected":0,"shells":1,"steps":["extruded profile","cut cylinder","shell","rectangular pattern"],"unsupported":0,"volume":4906.628538}, "62": {"area":8771.003732,"bodies":1,"edges":60,"error":null,"faces":23,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","cut cylinder","circular pattern","cut box","cut box","join cylinder"],"unsupported":2,"volume":40107.001728}, - "63": {"area":3060.37707,"bodies":1,"edges":57,"error":"boolean Union: implausible result volume ","faces":21,"outcome":"failed","rejected":1,"shells":1,"steps":["revolved section","join box","cut cylinder","hole","cut cylinder","join box"],"unsupported":0,"volume":6534.395036}, + "63": {"area":3198.797259,"bodies":1,"edges":84,"error":null,"faces":30,"outcome":"ok","rejected":1,"shells":1,"steps":["revolved section","join box","cut cylinder","hole","cut cylinder","join box","fillet"],"unsupported":0,"volume":7075.892751}, "64": {"area":4088.335645,"bodies":1,"edges":36,"error":null,"faces":14,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","fillet","join cylinder"],"unsupported":0,"volume":8495.220295}, "65": {"area":8442.175249,"bodies":1,"edges":33,"error":null,"faces":13,"outcome":"ok","rejected":0,"shells":1,"steps":["box","cut cylinder","fillet","rectangular pattern","circular pattern","circular pattern","fillet","cut box"],"unsupported":0,"volume":45117.305023}, "66": {"area":9222.76638,"bodies":1,"edges":99,"error":null,"faces":37,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","join box","join box","join box","cut sphere","cut box"],"unsupported":1,"volume":35217.203139}, @@ -122,6 +122,7 @@ {"reason":"#391 after merging main with #1213: seed 41 now reaches its second fillet (built since #1213), and the end-snap fix again drops a sliver edge the cuts left (94 to 93 edges); its steps, outcome, faces and shells are unchanged, volume moves by 0.005 mm³ (5185.215737 to 5185.221068) and area by 0.0014 mm²","seeds":[41]}, {"reason":"the chamfer bug is on main too, see #1390","seeds":[57]}, {"reason":"#1390: a chamfer of a convex edge that would grow the part is refused (TooLarge). Seed 57's 0.5 mm chamfer on the sphere cut's rim, which grew the part since #391 reordered its steps, is now refused, so the run goes on to its later steps and ends ok (16017.21 mm³, 24 faces)","seeds":[57]}, - {"reason":"seed 15 now cuts around a sealed void","seeds":[15]} + {"reason":"seed 15 now cuts around a sealed void","seeds":[15]}, + {"reason":"#1490: when the last-resort nudge fails both ways, two more screw axes are tried; seed 63's final box join (its face 0.00005 mm off the half-ring's cut face) now builds instead of being refused with an implausible volume, and the design goes on to its fillet","seeds":[63]} ] }