perf: cut many clear tools of any cut in one boolean (#368) (#387)

One extrude cutting many circles from one sketch still cut the plate once per
circle, each boolean rebuilding and re-meshing a plate grown by every hole before
it (32 circles 2 s, 128 circles 60 s). The #261 fix joined clear tools with
kernel::union_apart only in pattern replay; apply_traced now does it for every
cut with 8 or more tools (patterns included), keeping each piece's face lineage.
This commit is contained in:
Jonas Wrede authored and GitHub committed 2026-10-12 16:09:46 -04:00
1 parent 6090e89b3f
commit 829912c6c5
2 files changed
+16 -11

No files matched your search

+16 -10
View File
@@ -1132,6 +1132,21 @@ fn apply_op(state: &mut ModelState, f: &Feature, tools: Vec<Body>, op: Operation
apply_traced(state, f, tools.into_iter().map(Traced::unnamed).collect(), op, targets)
}
/// Many cut tools clear of each other (a pattern's copies, the circles of a vent field in one
/// sketch) as one tool of many pieces, so the target is cut once instead of rebuilding an ever
/// more complex body for every tool (#261, #368). Tools that may meet are left as they are.
fn clear_tools_as_one(tools: Vec<Traced>) -> Vec<Traced> {
if tools.len() < 8 {
return tools;
}
let bodies: Vec<Body> = tools.iter().map(|t| t.body.clone()).collect();
match kernel::union_apart(&bodies) {
// Each piece's faces keep the names of the tool it is.
Ok(Some(one)) => vec![Traced::step(one, &tools.iter().collect::<Vec<_>>())],
_ => tools,
}
}
/// [`apply_op`] with tools whose lineage is known: the bodies it makes or changes keep theirs.
fn apply_traced(state: &mut ModelState, f: &Feature, tools: Vec<Traced>, op: Operation, targets: &[String]) -> Result<()> {
let tools = if op == Operation::Intersect { intersection_tool_union(tools)? } else { tools };
@@ -1213,6 +1228,7 @@ fn apply_traced(state: &mut ModelState, f: &Feature, tools: Vec<Traced>, op: Ope
}
Operation::Cut | Operation::Intersect => {
let bop = if op == Operation::Cut { BoolOp::Cut } else { BoolOp::Intersect };
let tools = if op == Operation::Cut { clear_tools_as_one(tools) } else { tools };
let affected: std::collections::BTreeSet<String> =
target_idx.iter().filter_map(|i| state.bodies.get(*i).map(|b| b.name.clone())).collect();
let mut remove = Vec::new();
@@ -2798,16 +2814,6 @@ fn replay(
}
}
}
// Many cut copies clear of each other: one tool of many pieces, so the target is cut
// once instead of rebuilding an ever more complex body for every copy (#261).
if op == Operation::Cut && copies.len() >= 8 {
let bodies: Vec<Body> = copies.iter().map(|t| t.body.clone()).collect();
if let Ok(Some(one)) = kernel::union_apart(&bodies) {
// Each piece's faces keep the names of the copy it is.
let joined = Traced::step(one, &copies.iter().collect::<Vec<_>>());
copies = vec![joined];
}
}
// New bodies from a pattern join their source when they touch it (as one feature would).
if f.body_names.is_empty() {
apply_traced(st, f, copies, op, &targets)?;
-1
View File
@@ -285,7 +285,6 @@ fn sketch_hole_cut(n: usize) -> std::time::Duration {
/// field: 32 took 2 s and 128 took 60 s, though a feature pattern of the same holes is near
/// linear since #261. Linear is 4×. A ratio, not a time (see the 512-hole pattern test).
#[test]
#[ignore = "fails until #368 is fixed"]
fn one_extrude_of_many_sketch_circles_scales_linearly() {
let t32 = sketch_hole_cut(32);
let t128 = sketch_hole_cut(128);