diff --git a/crates/engine/src/canvas/shapebuilder.rs b/crates/engine/src/canvas/shapebuilder.rs index b68f1e71d..fb4c3d699 100644 --- a/crates/engine/src/canvas/shapebuilder.rs +++ b/crates/engine/src/canvas/shapebuilder.rs @@ -109,7 +109,9 @@ pub fn eligible(doc: &Document, ids: &[NodeId]) -> Vec { out.push(id); } } - let ids: Vec = ids.iter().copied().filter(|i| doc.contains(*i)).collect(); + // Each id counts once: a repeated group must not be walked once per repeat. + let mut seen = std::collections::BTreeSet::new(); + let ids: Vec = ids.iter().copied().filter(|i| doc.contains(*i) && seen.insert(*i)).collect(); let mut out = Vec::new(); for id in roots(doc, &ids) { walk(doc, id, 0, &mut out); diff --git a/crates/engine/src/shapebuilder_tests.rs b/crates/engine/src/shapebuilder_tests.rs index 3b0cc7efb..a52bf1767 100644 --- a/crates/engine/src/shapebuilder_tests.rs +++ b/crates/engine/src/shapebuilder_tests.rs @@ -217,6 +217,15 @@ fn remove_mode_and_escape() { assert!(s.execute("shapeBuilder.mode", &json!({"mode": "sideways"})).is_err()); } +#[test] +fn a_group_named_many_times_is_walked_once() { + let mut s = Session::new(); + let (a, b) = squares(&mut s); + let g = id(&ex(&mut s, "object.group", json!({"ids": [a, b]}))); + let r = ex(&mut s, "shapeBuilder.regions", json!({"ids": vec![g; 500]})); + assert_eq!(r["sources"], json!([a, b])); +} + #[test] fn groups_rotated_and_ineligible_layers() { let mut s = Session::new();