forked from 1b-agent/opencadstudio-upstream
feat(blocks): synchronize definition geometry changes to evaluated references
Update evaluated references upon saving block definition edits while preserving each reference's independent parameter values, visibility states, and undo history. Reconcile representation members by native element identity rather than positional index, preserving handles, extension dictionaries, reactors, nested attribute handles, and 3D polyline vertex handles. Prune erased targets from action dependencies, synchronize anonymous block base points, and prevent element ID collisions across undo/redo. Coalesce representation updates into editor undo deltas and composite structure snapshots for symmetric undo/redo.
This commit is contained in:
1 parent
ee846914dc
commit
5a6a7fab93
10 files changed
+1317
-191
No files matched your search
+76
-14
@@ -161,6 +161,8 @@ impl OpenCADStudio {
|
||||
candidate_handles.insert(insert.common.handle);
|
||||
if let Some(record) = document.block_records.get(&insert.block_name) {
|
||||
candidate_handles.extend(record.entity_handles.iter().copied());
|
||||
candidate_handles.insert(record.block_entity_handle);
|
||||
candidate_handles.insert(record.block_end_handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -168,15 +170,10 @@ impl OpenCADStudio {
|
||||
}
|
||||
let mut sub_handles = Vec::new();
|
||||
for &h in &candidate_handles {
|
||||
let entity_opt = before_doc
|
||||
.get_entity(h)
|
||||
.or_else(|| tab.scene.document.get_entity(h));
|
||||
if let Some(EntityType::Insert(ins)) = entity_opt {
|
||||
for attr in &ins.attributes {
|
||||
sub_handles.push(attr.common.handle);
|
||||
}
|
||||
if let Some(seqend) = ins.seqend_handle {
|
||||
sub_handles.push(seqend);
|
||||
for document in [before_doc, &tab.scene.document] {
|
||||
if let Some(EntityType::Insert(insert)) = document.get_entity(h) {
|
||||
sub_handles.extend(insert.attributes.iter().map(|a| a.common.handle));
|
||||
sub_handles.extend(insert.seqend_handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -578,6 +575,49 @@ impl OpenCADStudio {
|
||||
true
|
||||
}
|
||||
|
||||
/// Save-time representation edits belong to the preceding editor command,
|
||||
/// so local Undo cannot leave untracked members or reuse their handles.
|
||||
fn coalesce_saved_representations(&mut self, i: usize, before: &CadDocument) {
|
||||
let tab = &mut self.tabs[i];
|
||||
let doc = &tab.scene.document;
|
||||
if before == doc {
|
||||
return;
|
||||
}
|
||||
let Some(HistorySnapshot::Delta(delta)) = tab.history.undo_stack.last_mut() else {
|
||||
return;
|
||||
};
|
||||
let handles = before
|
||||
.entities()
|
||||
.chain(doc.entities())
|
||||
.map(|e| e.common().handle)
|
||||
.collect::<FxHashSet<_>>();
|
||||
for handle in handles {
|
||||
let a = before.get_entity_arc(handle);
|
||||
let b = doc.get_entity_arc(handle);
|
||||
if a == b {
|
||||
continue;
|
||||
}
|
||||
if let Some((_, _, after)) = delta.entities.iter_mut().find(|(h, _, _)| *h == handle) {
|
||||
*after = b;
|
||||
} else {
|
||||
delta.entities.push((handle, a, b));
|
||||
}
|
||||
}
|
||||
if !delta
|
||||
.structure
|
||||
.as_ref()
|
||||
.is_some_and(StructureSnapshot::is_full)
|
||||
{
|
||||
let mut previous = before.clone();
|
||||
let broad = StructureSnapshot::Full(previous.snapshot_structure());
|
||||
delta.structure = Some(match delta.structure.take() {
|
||||
Some(specialized) => StructureSnapshot::Combined(vec![broad, specialized]),
|
||||
None => broad,
|
||||
});
|
||||
}
|
||||
delta.dirty_after = true;
|
||||
}
|
||||
|
||||
pub(super) fn save_block_edit(&mut self, i: usize) -> bool {
|
||||
let Some(session) = self.tabs[i].active_block_edit_session() else {
|
||||
self.command_line
|
||||
@@ -617,11 +657,33 @@ impl OpenCADStudio {
|
||||
// A dynamic block leaves the editor showing its first visibility
|
||||
// state, with erased objects dropped from its states.
|
||||
let definition = session.br_handle;
|
||||
crate::scene::block_visibility::finish_editing(
|
||||
&mut self.tabs[i].scene.document,
|
||||
definition,
|
||||
);
|
||||
self.tabs[i].scene.visibility_states.remove(&definition);
|
||||
if self.block_edit_delta(i, true).is_some() {
|
||||
self.reconcile_block_edit_membership(i);
|
||||
self.finish_pending_history(i);
|
||||
let before_sync = self.tabs[i].scene.document.clone();
|
||||
let mut staged = before_sync.clone();
|
||||
crate::scene::block_visibility::finish_editing(&mut staged, definition);
|
||||
crate::scene::block_actions::prune_erased_targets(&mut staged, definition);
|
||||
let before = &self.tabs[i]
|
||||
.block_edit_history
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.checkpoint
|
||||
.document;
|
||||
match crate::scene::block_actions::instance::refresh_after_edit(
|
||||
&staged, before, definition,
|
||||
) {
|
||||
Ok(document) => {
|
||||
self.apply_action_document(i, document);
|
||||
self.coalesce_saved_representations(i, &before_sync);
|
||||
}
|
||||
Err(error) => {
|
||||
self.command_line.push_error(crate::t!(error).as_ref());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
self.tabs[i].scene.visibility_states.remove(&definition);
|
||||
}
|
||||
if let Some(delta) = self.block_edit_delta(i, true) {
|
||||
let mut history = self.tabs[i].block_edit_history.take().unwrap();
|
||||
std::mem::swap(&mut self.tabs[i].history, &mut history.main);
|
||||
|
||||
Reference in new issue
Block a user