diff --git a/src/app/block_editor_tests.rs b/src/app/block_editor_tests.rs index 48d452a52..26bdd79a8 100644 --- a/src/app/block_editor_tests.rs +++ b/src/app/block_editor_tests.rs @@ -54,6 +54,46 @@ fn main_depth(app: &OpenCADStudio) -> usize { .unwrap_or(tab.history.undo_stack.len()) } +#[test] +fn local_undo_preserves_imported_native_networks_when_constraints_are_unchanged() { + use codec::objects::{AssocNetwork, AssociativeData, AssociativeObject, Dictionary, ObjectType}; + let (mut app, reference) = fixture(); + let i = app.active_tab; + let doc = &mut app.tabs[i].scene.document; + let owner = doc.block_records.get("EDIT_ME").unwrap().handle; + let dictionary_handle = doc.allocate_handle(); + let network_handle = doc.allocate_handle(); + let mut dictionary = Dictionary::new(); + dictionary.handle = dictionary_handle; + dictionary.owner = owner; + dictionary.hard_owner = true; + dictionary.add_entry("ACAD_ASSOCNETWORK", network_handle); + doc.objects + .insert(dictionary_handle, ObjectType::Dictionary(dictionary)); + let network = AssociativeObject { + handle: network_handle, + owner: dictionary_handle, + reactors: vec![dictionary_handle], + dxf_name: "ASSOCNETWORK".into(), + cpp_class_name: "AcDbAssocNetwork".into(), + data: AssociativeData::Network(AssocNetwork::default()), + ..Default::default() + }; + doc.objects + .insert(network_handle, ObjectType::Associative(network)); + begin(&mut app, reference); + let original_objects = app.tabs[i].scene.document.objects.clone(); + circle(&mut app, 1.0); + assert!(!app.save_block_edit_as(i, "COPY")); + assert_eq!(app.tabs[i].scene.document.objects, original_objects); + app.undo_steps(1); + assert_eq!(members(&app).len(), 1); + assert_eq!(app.tabs[i].scene.document.objects, original_objects); + app.redo_steps(1); + assert_eq!(members(&app).len(), 2); + assert_eq!(app.tabs[i].scene.document.objects, original_objects); +} + #[test] fn save_as_forks_unsaved_edits_and_undoes_as_one_drawing_step() { let (mut app, reference) = fixture(); diff --git a/src/app/history.rs b/src/app/history.rs index 04b2fb37f..649135591 100644 --- a/src/app/history.rs +++ b/src/app/history.rs @@ -1126,6 +1126,7 @@ impl OpenCADStudio { i: usize, had_full: bool, layer_panel_changed: bool, + native_graph_changed: bool, changes: &[(Handle, crate::scene::ChangeKind)], ) { self.tabs[i].edit_revision = self.tabs[i].edit_revision.wrapping_add(1); @@ -1173,7 +1174,11 @@ impl OpenCADStudio { } } } - scene.sync_native_parametric_graph(); + // Geometry or visibility replay does not change the constraint + // model. Rebuilding its graph would replace imported networks. + if native_graph_changed { + scene.sync_native_parametric_graph(); + } scene.clear_preview_wire(); } // UCS state is cached separately from its persisted header/viewport @@ -1216,6 +1221,7 @@ impl OpenCADStudio { let mut last_label = String::new(); let mut had_full = false; let mut layer_panel_changed = false; + let mut native_graph_changed = false; let mut changes = Vec::new(); for _ in 0..steps { let Some(snapshot) = self.tabs[i].history.undo_stack.pop() else { @@ -1225,6 +1231,8 @@ impl OpenCADStudio { let step_label = last_label.clone(); match snapshot { HistorySnapshot::Delta(mut d) => { + native_graph_changed |= + !d.parametric_constraints.is_empty() || d.named_parameters.is_some(); // Symmetric: undo applies the before side, then the same // delta rides to the redo stack (it still holds the after // side) — no current-state capture needed. @@ -1259,7 +1267,13 @@ impl OpenCADStudio { } self.refedit_after_history_step(i, &step_label, true); } - self.finish_history_apply(i, had_full, layer_panel_changed, &changes); + self.finish_history_apply( + i, + had_full, + layer_panel_changed, + native_graph_changed, + &changes, + ); self.command_line .push_output(crate::tf!("Undo: {last_label}").as_ref()); } @@ -1278,6 +1292,7 @@ impl OpenCADStudio { let mut last_label = String::new(); let mut had_full = false; let mut layer_panel_changed = false; + let mut native_graph_changed = false; let mut changes = Vec::new(); for _ in 0..steps { let Some(snapshot) = self.tabs[i].history.redo_stack.pop() else { @@ -1287,6 +1302,8 @@ impl OpenCADStudio { let step_label = last_label.clone(); match snapshot { HistorySnapshot::Delta(mut d) => { + native_graph_changed |= + !d.parametric_constraints.is_empty() || d.named_parameters.is_some(); layer_panel_changed |= d.structure.as_ref().is_some_and(|structure| { matches!( structure, @@ -1318,7 +1335,13 @@ impl OpenCADStudio { } self.refedit_after_history_step(i, &step_label, false); } - self.finish_history_apply(i, had_full, layer_panel_changed, &changes); + self.finish_history_apply( + i, + had_full, + layer_panel_changed, + native_graph_changed, + &changes, + ); self.command_line .push_output(crate::tf!("Redo: {last_label}").as_ref()); }