From 1ee432ac645197db0c818c1c4cf9f7cab32df840 Mon Sep 17 00:00:00 2001 From: Karim Jerbi Date: Sat, 17 Oct 2026 09:33:29 +0100 Subject: [PATCH] fix(blocks): reopen evaluated references in their authoring definition Resolve BEDIT reference entry through the native dynamic-definition link instead of editing the anonymous geometry variant. Preserve static-block fallback and existing lock/xref checks. Add synthetic regressions for evaluated-reference DWG reload and save/close/reedit, plus Rotation-only save/close/reopen followed by Flip placement. Validated with 334 block tests passing (6 existing opt-in tests ignored). --- src/app/block_editor_tests.rs | 111 ++++++++++++++++++++++++++++++++++ src/app/commands/inquiry.rs | 12 +++- 2 files changed, 122 insertions(+), 1 deletion(-) diff --git a/src/app/block_editor_tests.rs b/src/app/block_editor_tests.rs index ece0b61ee..b23de5435 100644 --- a/src/app/block_editor_tests.rs +++ b/src/app/block_editor_tests.rs @@ -59,6 +59,117 @@ fn feed(app: &mut OpenCADStudio, text: &str) { let _ = app.update(Message::CommandSubmit); } +#[test] +fn block_parameters_rotation_only_save_close_reopen_then_flip() { + use crate::scene::block_actions::{self as actions, Kind}; + for save_command in ["BEDIT_SAVE", "BSAVE"] { + let (mut app, reference) = fixture(); + begin(&mut app, reference); + let i = app.active_tab; + let def = app.tabs[i].active_block_edit_session().unwrap().br_handle; + let _ = app.run_command_line("BPARAMETER ROTATION 0,0 10 0"); + assert_eq!( + actions::parameters(&app.tabs[i].scene.document, def, Kind::Rotate).len(), + 1 + ); + let _ = app.dispatch_command(save_command); + if save_command == "BSAVE" { + let _ = app.dispatch_command("BCLOSE"); + } + assert!(app.tabs[i].active_block_edit.is_none()); + begin(&mut app, reference); + assert_eq!( + actions::parameters(&app.tabs[i].scene.document, def, Kind::Rotate).len(), + 1, + "{save_command}" + ); + let _ = app.run_command_line("BPARAMETER FLIP 0,0 10,0 -2,2"); + assert_eq!( + actions::parameters(&app.tabs[i].scene.document, def, Kind::Flip).len(), + 1 + ); + let _ = app.dispatch_command("BEDIT_SAVE"); + begin(&mut app, reference); + assert_eq!( + actions::parameters(&app.tabs[i].scene.document, def, Kind::Rotate).len(), + 1 + ); + assert_eq!( + actions::parameters(&app.tabs[i].scene.document, def, Kind::Flip).len(), + 1 + ); + } +} + +#[test] +fn block_actions_save_close_and_reedit_evaluated_reference() { + use crate::scene::block_actions::{self as actions, Kind}; + let (mut app, reference) = fixture(); + begin(&mut app, reference); + let i = app.active_tab; + let def = app.tabs[i].active_block_edit_session().unwrap().br_handle; + let _ = app.run_command_line("BPARAMETER LINEAR 0,0 10,0 5,2"); + let p = actions::parameters(&app.tabs[i].scene.document, def, Kind::Move)[0].clone(); + let targets = members(&app); + actions::create( + &mut app.tabs[i].scene.document, + def, + Kind::Move, + p.handle, + &targets, + glam::DVec3::ZERO, + ) + .unwrap(); + let _ = app.dispatch_command("BSAVE"); + let _ = app.dispatch_command("BCLOSE"); + assert!(app.tabs[i].active_block_edit.is_none()); + assert_eq!( + actions::parameters(&app.tabs[i].scene.document, def, Kind::Move).len(), + 1 + ); + assert_eq!(actions::actions(&app.tabs[i].scene.document, def).len(), 1); + app.tabs[i].scene.selected.insert(reference); + app.refresh_properties(); + let grip = app.tabs[i] + .selected_grips + .iter() + .find(|g| super::block_actions::is_test_grip(g.id)) + .unwrap() + .id; + assert!(app.start_test_action_grip(reference, grip)); + feed(&mut app, "12,0"); + let EntityType::Insert(insert) = app.tabs[i].scene.document.get_entity(reference).unwrap() + else { + panic!() + }; + assert_ne!(insert.block_name, "EDIT_ME"); + // Native save/reload must preserve the link from the evaluated variant to + // the definition that owns the parameters and actions. + let bytes = codec::DwgWriter::write_to_vec(&app.tabs[i].scene.document).unwrap(); + app.tabs[i].scene.document = codec::DwgReader::from_stream(std::io::Cursor::new(bytes)) + .read() + .unwrap(); + app.tabs[i].scene.selected.insert(reference); + let _ = app.dispatch_command("BEDIT"); + assert_eq!( + app.tabs[i].active_block_edit_session().unwrap().br_handle, + def + ); + assert_eq!( + actions::parameters(&app.tabs[i].scene.document, def, Kind::Move).len(), + 1 + ); + assert_eq!(actions::actions(&app.tabs[i].scene.document, def).len(), 1); + let _ = app.dispatch_command("BSAVE"); + let _ = app.dispatch_command("BCLOSE"); + app.tabs[i].scene.selected.insert(reference); + app.refresh_properties(); + assert!(app.tabs[i] + .selected_grips + .iter() + .any(|g| super::block_actions::is_test_grip(g.id))); +} + #[test] fn block_actions_rotate_flip_mouse_grips_authoring_history_and_reopen() { use crate::scene::block_actions::{self as actions, Kind}; diff --git a/src/app/commands/inquiry.rs b/src/app/commands/inquiry.rs index 6aa81515b..9adf0b41e 100644 --- a/src/app/commands/inquiry.rs +++ b/src/app/commands/inquiry.rs @@ -536,7 +536,17 @@ impl OpenCADStudio { return Some(Task::none()); } match self.tabs[i].scene.document.get_entity(insert_handle) { - Some(codec::EntityType::Insert(ins)) => ins.block_name.clone(), + Some(codec::EntityType::Insert(ins)) => { + // Evaluated INSERTs point at anonymous geometry variants. + // Their authoring graph belongs to the original definition. + let doc = &self.tabs[i].scene.document; + doc.dynamic_definition_for_insert(insert_handle) + .and_then(|definition| { + doc.block_records.iter().find(|br| br.handle == definition) + }) + .map(|br| br.name.clone()) + .unwrap_or_else(|| ins.block_name.clone()) + } _ => { self.command_line.push_error( crate::t!("BEDIT: selected object is not a block reference.")