refactor(scene): reference-only designation becomes a scene-scoped set (API v9)

Per the upstream review of the XDATA-based storage: the host no longer writes
its own state into the saved file. The marker record under the host-owned OCS
APPID, the on-first-use APPID registration and the verbatim-EED rewrite
handling are removed; the single source of truth is a scene-scoped in-memory
set of entity handles (Scene::reference_only) that dies with the drawing tab,
so a reopened file starts undesignated and the owning plugin re-marks on the
document-opened notification.

All behaviour sites are unchanged, driven by the set: the wire stamp moves
from an XDATA read to a set lookup threaded through the tessellation
pipelines (tessellate_entity and the dimension text path take the handle set
as a plain parameter), and the pick filters, selection retains and
update/erase refusals read Scene::is_reference_only.

Undo keeps the entity-write contract: set mutations are recorded as explicit
first-touch before/after values on each history delta (the parametric-
constraints channel pattern). A designation inside a plugin push_undo scope
joins it, a scope-less change is one step of its own, an idempotent re-mark
records nothing, erasing a designated entity drops the set entry with the
entity, and undo/redo restores entity and designation together.

The round-trip test is inverted: reference_only_designation_is_not_written_
to_dwg_or_dxf now asserts the marker appears in neither format's bytes, that
no OCS APPID or marker record survives a reload, and that the designation
stays live in memory.
This commit is contained in:
Sebastian committed 2026-10-14 21:12:17 +02:00
1 parent 06ee0798a8
commit 50db12b169
17 files changed
+623 -232

No files matched your search

+23 -17
View File
@@ -226,7 +226,7 @@ fn record(
value
}
fn entity_record(entity: &codec::EntityType) -> Result<Value, String> {
fn entity_record(entity: &codec::EntityType, reference_only: bool) -> Result<Value, String> {
let (kind, properties) = enum_parts(entity)?;
let mut value = record(
"entities",
@@ -245,7 +245,8 @@ fn entity_record(entity: &codec::EntityType) -> Result<Value, String> {
"record_type".into(),
json!(crate::entities::names::dxf_name(entity)),
);
let reference_only = crate::scene::reference_only::entity_is_reference_only(entity);
// Scene-scoped designation (API v9): the set membership of this entity's
// handle, resolved by the caller from the live scene.
object.insert("reference_only".into(), json!(reference_only));
Ok(value)
}
@@ -307,12 +308,13 @@ fn map_records<T: Serialize>(
.collect()
}
fn collection_records(
document: &codec::CadDocument,
collection: &str,
) -> Result<Vec<Value>, String> {
fn collection_records(scene: &crate::scene::Scene, collection: &str) -> Result<Vec<Value>, String> {
let document = &scene.document;
match collection {
"entities" => document.entities().map(entity_record).collect(),
"entities" => document
.entities()
.map(|entity| entity_record(entity, scene.is_reference_only(entity.common().handle)))
.collect(),
"objects" => {
let mut objects: Vec<_> = document.objects.iter().collect();
objects.sort_by_key(|(handle, _)| handle.value());
@@ -1387,7 +1389,7 @@ impl OpenCADStudio {
}
let definitions = referenced_definitions(&root_type);
let runtime_schema = collection.and_then(|collection| {
collection_records(&self.tabs[self.active_tab].scene.document, collection)
collection_records(&self.tabs[self.active_tab].scene, collection)
.ok()?
.into_iter()
.find(|record| {
@@ -1431,20 +1433,20 @@ impl OpenCADStudio {
}
pub(super) fn record_query(&self, request: &Value) -> Value {
let document = &self.tabs[self.active_tab].scene.document;
let scene = &self.tabs[self.active_tab].scene;
let Some(collection) = request["collection"].as_str() else {
return self.record_capabilities();
};
let mut records = Vec::new();
if collection == "all" {
for (name, _) in COLLECTIONS {
match collection_records(document, name) {
match collection_records(scene, name) {
Ok(mut values) => records.append(&mut values),
Err(error) => return failure("serialization_failed", error),
}
}
} else {
match collection_records(document, collection) {
match collection_records(scene, collection) {
Ok(values) => records = values,
Err(error) => return failure("unknown_collection", error),
}
@@ -1712,14 +1714,18 @@ mod tests {
Vector3::new(10.0, 0.0, 0.0),
),
));
fn mark(app: &mut OpenCADStudio, i: usize, handle: Handle, on: bool) {
if let Some(entity) = app.tabs[i].scene.document.get_entity_mut(handle) {
crate::scene::reference_only::set_entity_reference_only(entity, on);
}
// The designation is plugin-mutated scene state: mark through the
// same host API a plugin uses, so the read surface reflects the
// live set rather than any stored marker.
fn mark(app: &mut OpenCADStudio, handle: Handle, on: bool) {
let i = app.active_tab;
crate::app::plugin_host::HostSession::new(app, i)
.set_reference_only(handle, on)
.expect("mark through the plugin host API");
}
// The read surface reports the designation alongside the record.
mark(&mut app, i, handle, true);
mark(&mut app, handle, true);
let records = app.record_query(&json!({"collection":"entities"}));
assert_eq!(records["count"], 1);
assert_eq!(
@@ -1744,7 +1750,7 @@ mod tests {
assert_eq!(refused["code"], json!("reference_only"));
// Clearing the designation is reported too.
mark(&mut app, i, handle, false);
mark(&mut app, handle, false);
let records = app.record_query(&json!({"collection":"entities"}));
assert_eq!(records["records"][0]["reference_only"], json!(false));
}