feat(scene): reference-only entities refuse the edit paths (API v9 host)

Host side of HostApi::set_reference_only: plugin-owned display geometry that
renders and snaps normally but must not be edited or picked by the person at
the screen, while the owning plugin keeps full write access through the host.

- Storage: a human-readable marker value in an XDATA record under the
  host-owned OCS APPID (registered on first use; foreign values survive
  verbatim; stale verbatim DWG EED is dropped on rewrite), so the flag
  round-trips DWG and DXF with the entity and cannot collide with plugin
  APPID namespaces.
- Grips: refresh_selected_grips yields none for a reference-only entity and
  an active grip edit on a newly designated target is cancelled like a
  locked-layer one.
- Picking: a reference-only wire flag extends the snap_only skips in every
  hit test, so a click passes through to the editable entity underneath, and
  box, window, lasso, fence and select-all gathers skip the designation;
  fills, solids and instanced meshes are excluded through the shared
  candidate handle set.
- Write policy: built-in user paths treat a reference-only entity like a
  locked-layer one (Scene::update_entity / update_entities refuse,
  erase_entities skips), while the plugin-owned variants
  (update_entity_plugin / erase_entities_plugin, mirroring the
  remove_entities editor split) and the whole HostSession surface write
  through. The MCP entities read exposes a top-level reference_only boolean
  and set_properties refuses with the reference_only failure code.
- Undo: the entity-write contract - a change joins a plugin-opened push_undo
  scope, a scope-less change is one undo step of its own, refusals and
  idempotent re-marks record nothing.
- Known limit: entities nested inside block instances (the block cache
  rebuilds instance wires without the flag).

Evidence: marking_flags_and_unmarking_clears,
foreign_values_under_the_host_app_id_survive, other_apps_are_untouched,
reference_only_refusals_record_no_undo_step,
reference_only_designation_composes_undo_like_entity_writes,
reference_only_survives_dxf_and_dwg_round_trips,
user_paths_refuse_and_plugin_paths_write_reference_only_entities,
select_all_visible_skips_reference_only_entities,
clicks_pass_through_reference_only_wires,
box_selections_skip_reference_only_wires,
entities_records_expose_the_reference_only_designation.
This commit is contained in:
Sebastian committed 2026-10-13 12:38:53 +02:00
1 parent 1f552038a8
commit d24adc39b5
16 files changed
+806 -61

No files matched your search

+31 -7
View File
@@ -519,23 +519,47 @@ impl Scene {
/// existing entity.
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
pub fn update_entity(&mut self, entity: EntityType) -> bool {
let mut changed = Vec::with_capacity(1);
let mut block_change = false;
if !self.update_entity_impl(entity, &mut changed, &mut block_change) {
return false;
}
self.publish_entity_updates(changed, block_change);
true
self.update_entities_policy(vec![entity], false) == 1
}
/// Batched variant of [`update_entity`]: writes every replacement, then
/// issues a single geometry bump for the whole set instead of one bump per
/// entity.
pub fn update_entities(&mut self, updates: Vec<EntityType>) -> usize {
self.update_entities_policy(updates, false)
}
/// Like [`update_entity`](Self::update_entity) but without the
/// reference-only refusal. The plugin owns reference-only entities and
/// keeps full write access through the plugin host API, so a plugin's own
/// refresh of derived display geometry must not be refused. Locked-layer
/// policy stays enforced either way.
pub fn update_entity_plugin(&mut self, entity: EntityType) -> bool {
self.update_entities_policy(vec![entity], true) == 1
}
/// Like [`update_entities`](Self::update_entities) but without the
/// reference-only refusal (see [`update_entity_plugin`](Self::update_entity_plugin)).
pub fn update_entities_plugin(&mut self, updates: Vec<EntityType>) -> usize {
self.update_entities_policy(updates, true)
}
/// Shared write path of the four `update_entity*` entry points. Locked
/// layers always refuse (inside `update_entity_impl`); reference-only
/// entities refuse every caller except the plugin-owned `_plugin`
/// variants, mirroring the `remove_entities` edit-policy split.
fn update_entities_policy(
&mut self,
updates: Vec<EntityType>,
allow_reference_only: bool,
) -> usize {
let mut moved = 0usize;
let mut changed = Vec::with_capacity(updates.len());
let mut block_change = false;
for entity in updates {
if !allow_reference_only && self.is_reference_only(entity.common().handle) {
continue;
}
if self.update_entity_impl(entity, &mut changed, &mut block_change) {
moved += 1;
}