A cut from a component without bodies cuts the parts it meets (#1234) (#1407)

This commit is contained in:
bherbruck authored and GitHub committed 2026-10-15 10:27:27 -04:00
1 parent 762b6fe934
commit 4823c293dd
2 files changed
+35

No files matched your search

+14
View File
@@ -447,6 +447,20 @@ fn add_feature_in(s: &mut Session, p: &Value, kind: FeatureKind, world: bool) ->
if participants.len() > 10_000 || participants.iter().any(|b| b != "*" && s.model.state().body(b).is_none()) {
return Err(EngineError::Other(format!("`{key}` must list existing bodies")));
}
// A cut or intersect made where the active component has no body of its own (a sketch in
// the assembly's root, cutting through the parts, #1234) would have nothing to work on: it
// takes part in every body instead, which it changes only where it meets them.
let participants = if participants.is_empty()
&& !primitive
&& matches!(str_(p, "operation").and_then(Operation::parse), Some(Operation::Cut | Operation::Intersect))
&& {
let st = s.model.state();
!st.bodies.is_empty() && !st.bodies.iter().any(|b| s.doc.model_body_component(b) == s.active_component)
} {
vec!["*".to_string()]
} else {
participants
};
if matches!(kind, FeatureKind::ConstructionPlane { .. })
&& let Some(n) = name
{
+21
View File
@@ -2346,6 +2346,27 @@ fn booleans_stay_in_their_component() {
assert!(s.execute("solid.extrude", &json!({"sketch": "Slot", "distance": 1, "operation": "cut", "participants": ["Nope"]})).is_err());
}
/// #1234: a cut sketched in the root, where there is no body, through a body in each of two
/// components cuts both (the root has nothing of its own to cut). A cut in a component with a
/// body of its own still stays there (`booleans_stay_in_their_component`).
#[test]
fn a_cut_from_a_component_without_bodies_cuts_the_parts_it_meets() {
let mut s = Session::default();
for (name, x) in [("A", 0), ("B", 30)] {
run(&mut s, "component.activate", json!({"component": "root"}));
run(&mut s, "component.create", json!({"name": name}));
run(&mut s, "solid.box", json!({"length": 20, "width": 10, "height": 10, "corner": [x, 0, 0], "body_name": format!("{name}Box")}));
}
run(&mut s, "component.activate", json!({"component": "root"}));
run(&mut s, "sketch.create", json!({"plane": "XY", "name": "Slot"}));
run(&mut s, "sketch.rectangle.two_point", json!({"p0": [10, 2], "p1": [40, 8]}));
run(&mut s, "sketch.finish", json!({}));
run(&mut s, "solid.extrude", json!({"sketch": "Slot", "distance": 10, "operation": "cut"}));
let vol = |s: &mut Session, b: &str| run(s, "inspect.measure", json!({"bodies": [b]}))["total"]["volume_mm3"].as_f64().unwrap_or(0.0);
assert!((vol(&mut s, "ABox") - (2000.0 - 10.0 * 6.0 * 10.0)).abs() < 1e-6, "{}", vol(&mut s, "ABox"));
assert!((vol(&mut s, "BBox") - (2000.0 - 10.0 * 6.0 * 10.0)).abs() < 1e-6, "{}", vol(&mut s, "BBox"));
}
/// A unit-less parameter used as a length means the design's units.
#[test]
fn unitless_parameters_used_as_lengths_take_the_design_units() {