fix(sketch): a side put onto a projected line keeps its closed profile (#635) (#780)

A side constrained onto a projected line keeps the sketch's closed profile. Fixes #635.
This commit is contained in:
semmelrinde authored and GitHub committed 2026-10-13 12:35:33 -04:00
1 parent dfa9e094cd
commit 159f9913c1
4 files changed
+441 -6

No files matched your search

@@ -526,3 +526,62 @@ fn faces_split_by_hidden_seams_project_whole() {
assert_eq!(radii.len(), 4, "{round}");
assert!(radii[..2].iter().all(|r| (r - 17.).abs() < 1e-6) && radii[2..].iter().all(|r| (r - 20.).abs() < 1e-6), "{round}");
}
/// #635: a rectangle whose corner is made coincident with a projected edge of the solid under
/// the sketch, so that its bottom side lies on that edge, is still one closed profile (the
/// projected line stays, and names the shared edge as it was there first), and it extrudes to
/// its own volume, also when the design is opened again.
#[test]
fn a_rectangle_put_onto_a_projected_edge_stays_a_closed_profile() {
let mut s = Session::default();
run(&mut s, "sketch.create", json!({"plane": "XY"}));
run(&mut s, "sketch.rectangle.two_point", json!({"p0": [0, 0], "p1": [40, 30]}));
run(&mut s, "sketch.finish", json!({}));
run(&mut s, "solid.extrude", json!({"distance": 20}));
let block = s.model.state().bodies[0].name.clone();
let id = run(&mut s, "sketch.create", json!({"plane": "XY"}))["sketch"].as_u64().unwrap();
// The solid's bottom edge along X, from (0, 0) to (40, 0).
let r = run(&mut s, "sketch.project", json!({"refs": [{"edge": [20, 0, 0]}]}));
let projected = r["links"][0]["curves"][0].as_str().unwrap().to_string();
// A 20 x 15 rectangle clear of it is a closed profile of its own.
let sides = ids(&run(&mut s, "sketch.rectangle.two_point", json!({"p0": [5, 10], "p1": [25, 25]}))["curves"]);
run(&mut s, "sketch.dimension", json!({"entities": [sides[0]], "value": 20}));
run(&mut s, "sketch.dimension", json!({"entities": [sides[1]], "value": 15}));
let mut own = sides.clone();
own.sort();
let profile_curves = |si: &Value| -> Vec<Vec<String>> {
si["profiles"]
.as_array()
.unwrap()
.iter()
.map(|p| {
let mut c = ids(&p["curves"]);
c.sort();
c
})
.collect()
};
assert_eq!(profile_curves(&inspect(&mut s)), vec![own.clone()]);
// Its bottom-left corner onto the projected line: the bottom side lies on it.
run(&mut s, "sketch.constraint.coincident", json!({"a": format!("{}.start", sides[0]), "b": projected}));
let si = inspect(&mut s);
assert_eq!(si["status"], "solved", "{si}");
let curve = |id: &str| si["curves"].as_array().unwrap().iter().find(|c| c["id"] == id).unwrap().clone();
let bottom = curve(&sides[0]);
for end in ["start_at", "end_at"] {
assert!(bottom[end][1].as_f64().unwrap().abs() < 1e-9, "{bottom}");
}
assert!(curve(&projected).get("link").is_some(), "the projected line stays");
let mut on_line = vec![projected.clone(), sides[1].clone(), sides[2].clone(), sides[3].clone()];
on_line.sort();
assert_eq!(profile_curves(&si), vec![on_line], "{si}");
assert!((si["profiles"][0]["area_mm2"].as_f64().unwrap() - 300.0).abs() < 1e-6, "{si}");
// Picked by a point inside, it extrudes to a 20 x 15 x 10 block of its own.
run(&mut s, "sketch.finish", json!({}));
run(&mut s, "solid.extrude", json!({"sketch": id, "profiles": [{"point": [15, 7.5]}], "distance": 10, "operation": "new", "body_name": "Pad"}));
let volume = |s: &Session, name: &str| s.model.state().body(name).unwrap().body.tessellate_with(1e-4, true).unwrap().measure().volume;
assert!((volume(&s, "Pad") - 3000.0).abs() < 1e-6, "{}", volume(&s, "Pad"));
assert!((volume(&s, &block) - 24000.0).abs() < 1e-6, "{}", volume(&s, &block));
let reopened = Session::new(solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap());
assert!((volume(&reopened, "Pad") - 3000.0).abs() < 1e-6, "{}", volume(&reopened, "Pad"));
}