diff --git a/crates/doc/src/eval.rs b/crates/doc/src/eval.rs
index df660ad1..c038fc51 100644
--- a/crates/doc/src/eval.rs
+++ b/crates/doc/src/eval.rs
@@ -1132,6 +1132,21 @@ fn apply_op(state: &mut ModelState, f: &Feature, tools: Vec
, 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) -> Vec {
+ if tools.len() < 8 {
+ return tools;
+ }
+ let bodies: Vec = 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::>())],
+ _ => 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, 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, 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 =
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 = 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::>());
- 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)?;
diff --git a/crates/engine/src/cmd/patterns_tests.rs b/crates/engine/src/cmd/patterns_tests.rs
index 08313c3f..33a94461 100644
--- a/crates/engine/src/cmd/patterns_tests.rs
+++ b/crates/engine/src/cmd/patterns_tests.rs
@@ -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);