Merge pull request #1747 from schoeller/reference_only_entities

Reference-only entities (plugin API v9): a scene-scoped, in-memory
designation for plugin-owned display geometry, plus HostNotification
dispatch to native plugins so they can re-mark on DocumentOpened.

Resolves the new DeltaSnapshot site in block_editor.rs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Opus 5.5 committed 2026-10-14 23:46:42 +03:00
commit 68330ce39e
40 files changed
+1977 -85

No files matched your search

+84 -5
View File
@@ -1,8 +1,8 @@
use super::{
document::{
DeltaSnapshot, HistorySnapshot, ObjectEntryDelta, ObjectVisibilitySnapshot,
ParametricConstraintsEntryDelta, PendingHistorySnapshot, StructureSnapshot,
TableEntryDelta,
ParametricConstraintsEntryDelta, PendingHistorySnapshot, ReferenceDesignationDelta,
StructureSnapshot, TableEntryDelta,
},
OpenCADStudio,
};
@@ -226,6 +226,7 @@ impl OpenCADStudio {
active_layer: None,
structure: (!objects.is_empty()).then_some(StructureSnapshot::Objects(objects)),
parametric_constraints: Vec::new(),
reference_designations: Vec::new(),
named_parameters: None,
label: label.into(),
};
@@ -328,8 +329,26 @@ impl OpenCADStudio {
let selected_after: Vec<Handle> = self.tabs[i].scene.selected.iter().copied().collect();
let dirty_after = self.tabs[i].dirty;
let active_layer_after = self.tabs[i].active_layer.clone();
// The designation set is scene state mutated through the plugin API:
// a scope that (un)marked entities carries the first-touch before
// values here; the after value is whatever the set holds now, and
// only untouched-value entries drop out (an idempotent re-mark never
// got recorded in the first place).
let reference_designations: Vec<_> = pending
.reference_designations_before
.into_iter()
.filter_map(|(handle, before)| {
let after = self.tabs[i].scene.reference_only.is_marked(handle);
(before != after).then_some(ReferenceDesignationDelta {
handle,
before,
after,
})
})
.collect();
if !pending.keep_empty
&& entities.is_empty()
&& reference_designations.is_empty()
&& !structure_changed
&& pending.selected_before == selected_after
&& pending.dirty_before == dirty_after
@@ -350,6 +369,7 @@ impl OpenCADStudio {
structure: structure_changed
.then_some(StructureSnapshot::Full(pending.structure_before)),
parametric_constraints: Vec::new(),
reference_designations,
named_parameters: None,
label: pending.label,
};
@@ -390,10 +410,33 @@ impl OpenCADStudio {
dirty_before,
structure_before,
recorder,
reference_designations_before: Vec::new(),
keep_empty: false,
});
}
/// Record the pre-mutation value of a reference-only designation (API v9)
/// into the open plugin pending scope — first touch wins. No-op when no
/// scope is open: like the XRecord methods, a designation change must open
/// its own scope before mutating, so by the time this runs a scope either
/// covers it or the caller owns one.
pub(super) fn record_reference_designation_before_pending(
&mut self,
i: usize,
handle: Handle,
before: bool,
) {
if let Some(pending) = self.tabs[i].history.pending.as_mut() {
if !pending
.reference_designations_before
.iter()
.any(|(recorded, _)| *recorded == handle)
{
pending.reference_designations_before.push((handle, before));
}
}
}
/// [`Self::push_undo_snapshot`] for a command that is an undo step even
/// when it changes nothing (UNDO and U then name it and undo nothing).
pub(super) fn push_undo_mark(&mut self, i: usize, label: impl Into<String>) {
@@ -481,6 +524,7 @@ impl OpenCADStudio {
active_layer: None,
structure: Some(StructureSnapshot::Layers(entries)),
parametric_constraints: Vec::new(),
reference_designations: Vec::new(),
named_parameters: None,
label: pending.label,
};
@@ -538,6 +582,7 @@ impl OpenCADStudio {
active_layer: None,
structure: Some(StructureSnapshot::TextStyles(entries)),
parametric_constraints: Vec::new(),
reference_designations: Vec::new(),
named_parameters: None,
label: pending.label,
};
@@ -595,6 +640,7 @@ impl OpenCADStudio {
active_layer: None,
structure: Some(StructureSnapshot::DimStyles(entries)),
parametric_constraints: Vec::new(),
reference_designations: Vec::new(),
named_parameters: None,
label: pending.label,
};
@@ -660,6 +706,7 @@ impl OpenCADStudio {
active_layer: None,
structure: Some(StructureSnapshot::Objects(entries)),
parametric_constraints: Vec::new(),
reference_designations: Vec::new(),
named_parameters: None,
label: pending.label,
};
@@ -695,6 +742,7 @@ impl OpenCADStudio {
object_handles,
}),
parametric_constraints: Vec::new(),
reference_designations: Vec::new(),
named_parameters: None,
label: "STYLE".to_string(),
};
@@ -777,8 +825,13 @@ impl OpenCADStudio {
pending.label
);
}
let (entity_before, object_before, parametric_constraints_before, named_parameters_before) =
rec.into_recorded_images();
let (
entity_before,
object_before,
parametric_constraints_before,
named_parameters_before,
reference_designations_before,
) = rec.into_recorded_images();
let entities: Vec<(Handle, Option<Arc<EntityType>>, Option<Arc<EntityType>>)> =
entity_before
.into_iter()
@@ -824,6 +877,20 @@ impl OpenCADStudio {
let after = self.tabs[i].scene.named_parameters().clone();
(before != after).then_some((before, after))
});
// Designations recorded by `remove_entities`'s delta path (the
// first-touch before values) close symmetric with the set's current
// state as the after image.
let reference_designations: Vec<ReferenceDesignationDelta> = reference_designations_before
.into_iter()
.filter_map(|(handle, before)| {
let after = self.tabs[i].scene.reference_only.is_marked(handle);
(before != after).then_some(ReferenceDesignationDelta {
handle,
before,
after,
})
})
.collect();
let selected_after = self.tabs[i].scene.selected.iter().copied().collect();
let dirty_after = self.tabs[i].dirty;
let mut structure = pending.structure_before.map(StructureSnapshot::Full);
@@ -875,6 +942,7 @@ impl OpenCADStudio {
active_layer: None,
structure,
parametric_constraints,
reference_designations,
named_parameters,
label: pending.label,
};
@@ -1029,8 +1097,19 @@ impl OpenCADStudio {
}
}
}
let changes = self.tabs[i].scene.apply_entity_delta(&d.entities, undo);
let mut changes = self.tabs[i].scene.apply_entity_delta(&d.entities, undo);
let scene = &mut self.tabs[i].scene;
// Designations: restore the chosen side's value on the scene-owned set
// (the entity images above already applied, so a restored entity is
// present before its designation is). Each actual flip joins the
// returned per-handle change list so the deferred cache reseed and
// geometry bump re-stamp that entity's wires.
for entry in &d.reference_designations {
let value = if undo { entry.before } else { entry.after };
if scene.set_reference_only_membership(entry.handle, value) {
changes.push((entry.handle, crate::scene::ChangeKind::Modified));
}
}
if !d.parametric_constraints.is_empty() {
scene.bump_constraints_epoch();
}