fix(sketch): merged adjacent profiles have whole edges where shared ones ended (#509)

When adjacent profiles are merged for one extrude, collinear lines on either side of where a shared boundary ended are joined back into one edge, so the body has no split faces or extra vertices for later booleans to trip on.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
MqriusandClaude Opus 5.5 authored and GitHub committed 2026-10-13 00:11:59 -04:00
1 parent 2f7046efac
commit bb431f7ac2
3 files changed
+82 -1

No files matched your search

+45
View File
@@ -363,6 +363,51 @@ fn revolve_a_spherical_shell_off_the_origin() {
}
}
#[test]
fn extrude_of_adjacent_profiles_has_whole_edges() {
// A holder from two regions of one sketch (a disc and the ring around it, both cut off by a
// chord): the chord used to stay split where the inner circle met it, leaving vertices right
// on the surface of a handle of that radius, and cutting the handle out of the holder failed.
let mut s = Session::default();
run(&mut s, "sketch.create", json!({"plane": "XZ", "name": "Holder"}));
run(&mut s, "sketch.circle.center", json!({"center": [0, 0], "radius": 40}));
run(&mut s, "sketch.circle.center", json!({"center": [0, 0], "radius": 35}));
let x = (40.0f64 * 40.0 - 33.0 * 33.0).sqrt();
run(&mut s, "sketch.line", json!({"points": [[-x, -33], [x, -33]]}));
run(&mut s, "sketch.finish", json!({}));
let si = run(&mut s, "sketch.inspect", json!({"sketch": "Holder"}));
let below: Vec<Value> = si["profiles"]
.as_array()
.unwrap()
.iter()
.filter(|p| (500.0..4900.0).contains(&p["area_mm2"].as_f64().unwrap()))
.map(|p| p["index"].clone())
.collect();
assert_eq!(below.len(), 2, "{si}");
run(&mut s, "solid.extrude", json!({"sketch": "Holder", "profiles": below, "distance": 23, "body_name": "Holder"}));
let topo = run(&mut s, "model.faces", json!({"body": "Holder"}));
assert_eq!(topo["faces"].as_array().unwrap().len(), 5, "a cut-off cylinder: {topo}");
let lo = run(&mut s, "inspect.measure", json!({"bodies": ["Holder"]}))["bodies"][0]["bbox"]["min"][1].as_f64().unwrap();
run(&mut s, "solid.move", json!({"bodies": ["Holder"], "translate": [0, -5.0 - lo, 0]}));
// The handle: r35 on the Y axis with a bevelled end.
run(&mut s, "sketch.create", json!({"plane": "XY", "name": "Handle"}));
for pts in [
[[0.0, 0.0], [0.0, 230.0]],
[[0.0, 0.0], [25.0, 0.0]],
[[25.0, 0.0], [35.0, 11.5]],
[[35.0, 11.5], [35.0, 230.0]],
[[35.0, 230.0], [0.0, 230.0]],
] {
run(&mut s, "sketch.line", json!({ "points": pts }));
}
run(&mut s, "sketch.finish", json!({}));
run(&mut s, "solid.revolve", json!({"sketch": "Handle", "profiles": [0], "axis": "Y", "body_name": "Handle"}));
let before = run(&mut s, "inspect.measure", json!({"bodies": ["Holder"]}))["total"]["volume_mm3"].as_f64().unwrap();
run(&mut s, "solid.combine", json!({"target": "Holder", "tools": ["Handle"], "operation": "cut", "keep_tools": true}));
let after = run(&mut s, "inspect.measure", json!({"bodies": ["Holder"]}))["total"]["volume_mm3"].as_f64().unwrap();
assert!(after > 1000.0 && after < before * 0.5, "{before} -> {after}");
}
#[test]
fn sketch_tools_and_constraints() {
let mut s = Session::default();