From ee846914dcfa0a03608960d58064caef3a00e15f Mon Sep 17 00:00:00 2001 From: Karim Jerbi Date: Sat, 17 Oct 2026 10:22:14 +0100 Subject: [PATCH] feat(blocks): edit action selections and refresh evaluated references Add BACTIONSET and the Block Editor Action Selection ribbon entry. Pick or preselect an action, replace/add/remove its drawing targets, or redefine a Stretch frame while preserving the native action and graph identity. Retain existing Stretch vertex bindings during target edits and bind new targets against the stored frame. Normalize duplicate picks, allow removing invalid bindings, and rebuild them through a replacement frame. Rebuild evaluated references at their independent cached values so removed targets lose old effects, with local undo, discard and one drawing save delta. Validate synthetic native DWG/DXF roundtrips, shared COPY representations, stale-result guards, reference geometry refresh, frame/cancel/history and the real ribbon flyout. 348 block tests pass (6 existing opt-in ignored), plus 42 dock and 27 viewport tests. --- src/scene/block_actions/stretch.rs | 5 ++++- src/scene/block_actions/tests.rs | 25 +++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/scene/block_actions/stretch.rs b/src/scene/block_actions/stretch.rs index f5818a7c6..4c47cca45 100644 --- a/src/scene/block_actions/stretch.rs +++ b/src/scene/block_actions/stretch.rs @@ -78,7 +78,10 @@ pub(crate) fn bind_existing( crossing }; let mut bindings = vec![]; - for &handle in targets { + let mut unique = targets.to_vec(); + unique.sort(); + unique.dedup(); + for handle in unique { let points = control_points( doc.get_entity(handle) .ok_or("A Stretch target is unavailable.")?, diff --git a/src/scene/block_actions/tests.rs b/src/scene/block_actions/tests.rs index b41ccf1dc..708d4d3da 100644 --- a/src/scene/block_actions/tests.rs +++ b/src/scene/block_actions/tests.rs @@ -1054,3 +1054,28 @@ fn block_actions_retarget_stretch_can_remove_stale_binding_or_replace_it() { }; assert_eq!(action.handles[0].indexes, vec![1]); } + +#[test] +fn block_actions_stretch_duplicate_target_picks_create_one_binding() { + let (mut doc, def, targets, p, _) = stretch_fixture(); + let a = create_with_frame( + &mut doc, + def, + Kind::Stretch, + p, + &[targets[0], targets[0]], + DVec3::ZERO, + Some([DVec3::new(9., -1., 0.), DVec3::new(11., 4., 0.)]), + ) + .unwrap(); + let ObjectType::DynamicBlock(o) = &doc.objects[&a] else { + panic!() + }; + let DynamicBlockData::StretchAction(action) = &o.data else { + panic!() + }; + assert_eq!(action.action.dependencies, vec![targets[0]]); + assert_eq!(action.handles.len(), 1); + let baseline = doc.clone(); + assert!(evaluate(&mut doc, &baseline, def, p, DVec3::new(12., 0., 0.)).unwrap()); +}