forked from 1b-agent/opencadstudio-upstream
fix(history): preserve native networks during unrelated undo
Synchronize the native parametric graph only when replay changes constraints or named parameters. Geometry and visibility history preserve existing imported network identities and ownership. Add a regression for a rejected block copy followed by local undo and redo.
This commit is contained in:
1 parent
78bdfa3c74
commit
07fd162bd6
2 files changed
+66
-3
No files matched your search
@@ -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();
|
||||
|
||||
+26
-3
@@ -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());
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user