docs(plugin): reference-only entities (API v9)

Document the designation in the host-model capability table and its own
section, and record the increment in the completion-plan ledger with the
storage decision, the behaviour split and the test evidence.
This commit is contained in:
Sebastian committed 2026-10-13 12:38:53 +02:00
1 parent d24adc39b5
commit 856b7bc8a4
2 files changed
+65 -1

No files matched your search

+38
View File
@@ -362,3 +362,41 @@ in one scope = one step, undone and redone whole),
`xrecords_survive_dxf_and_dwg_round_trips` (typed entries reload from both writers), the IPC round-trips `xrecords_survive_dxf_and_dwg_round_trips` (typed entries reload from both writers), the IPC round-trips
`xrecord_request_and_response_roundtrip` and `xrecord_requests_await_their_typed_responses`. The API is `xrecord_request_and_response_roundtrip` and `xrecord_requests_await_their_typed_responses`. The API is
host-level and stands on its own; a Python mapping (`doc.xrecords`-style) is future work. host-level and stands on its own; a Python mapping (`doc.xrecords`-style) is future work.
**Reference-only entities (2026-10-13, API v9).** The host gained a per-entity "reference-only"
designation for plugin-owned display geometry: `HostApi::set_reference_only` marks (or unmarks) an
entity as display data the person at the screen must not edit, while the owning plugin keeps full
write access. Behaviour split: a reference-only entity renders and snaps normally but is not
grip-editable (`refresh_selected_grips` yields nothing for it and an active grip edit on a newly
designated target is cancelled like a locked-layer one), not click-pickable (the pick-side wire
flag skips it, so a click passes through to the editable entity underneath), and skipped by box,
window, lasso, fence and select-all selection gathers. Built-in user write paths treat it like a
locked-layer entity — `Scene::update_entity` / `update_entities`, `erase_entities`, MCP
`set_properties` and modify machinery all refuse or skip it — while the plugin-owned variants
(`Scene::update_entity_plugin` / `erase_entities_plugin`, mirroring the
`remove_entities(handles, respect_layer_locks)` editor split) and the whole `HostSession` surface
(`add_entity`, `update_entity`, `update_entities_transaction`, `remove_entity`, XDATA records,
layer/block table operations) write through, so a preview refresh never needs unlock/relock cycles.
Locks still apply to the plugin: marking and clearing refuse absent entities and locked layers,
and every refusal is decided before the undo scope is touched. Undo is the entity-write contract:
a change inside a plugin-opened `push_undo` scope joins it (entity writes plus designations are one
step under the plugin's label); a scope-less change is one undo step of its own; an idempotent
re-designation records nothing. Storage: a human-readable marker value in an XDATA record under the
host-owned `OCS` APPID (registered on first use, one record per application per entity, foreign
values preserved verbatim; stale verbatim DWG EED is dropped on rewrite), so the flag round-trips
DWG and DXF save/reload with the entity and cannot collide with plugin APPID namespaces — chosen
over a codec common-data bit because the codec is a pinned external dependency. The MCP `entities`
read exposes the flag as a top-level `reference_only` boolean on every entity record. Known limit:
entities nested inside block instances (the block cache rebuilds instance wires without the flag) —
reference-only targets top-level geometry. Evidence: the storage unit tests
`marking_flags_and_unmarking_clears`, `foreign_values_under_the_host_app_id_survive`,
`other_apps_are_untouched`; the host behaviour tests `reference_only_refusals_record_no_undo_step`,
`reference_only_designation_composes_undo_like_entity_writes` (own step, scope join, whole-scope
undo), `reference_only_survives_dxf_and_dwg_round_trips` (both writers, APPID table included),
`user_paths_refuse_and_plugin_paths_write_reference_only_entities` (grips suppressed, user
update/erase refused, plugin update/erase through, designation survives a refresh-style replace),
`select_all_visible_skips_reference_only_entities`; the pick tests `clicks_pass_through_reference_only_wires`
and `box_selections_skip_reference_only_wires`; the record surface test
`entities_records_expose_the_reference_only_designation`; and the IPC round-trip
`reference_only_request_and_response_roundtrip`. Like the XRecord API, a Python mapping is future
work.
+27 -1
View File
@@ -22,7 +22,7 @@ without it keeps working, and the default `HostApi` methods refuse.
| Area | `HostApi` entry | What it does | | Area | `HostApi` entry | What it does |
|---|---|---| |---|---|---|
| Read | `document()`, `entity_snapshot` | The full `CadDocument` snapshot; a detached JSON view of any typed entity. | | Read | `document()`, `entity_snapshot` | The full `CadDocument` snapshot; a detached JSON view of any typed entity. |
| Entities | `add_entity`, `remove_entity`, `update_entities_transaction` | Create, delete and replace entities. A transaction validates every replacement first and either commits all of them as one undo step or none. | | Entities | `add_entity`, `remove_entity`, `update_entities_transaction`, `set_reference_only` | Create, delete and replace entities. A transaction validates every replacement first and either commits all of them as one undo step or none. `set_reference_only` designates plugin-owned display geometry (API v9, see below). |
| Selection | `selection`, `set_selection` | Ordered, tab-scoped, exact (missing or duplicate handles reject the request). | | Selection | `selection`, `set_selection` | Ordered, tab-scoped, exact (missing or duplicate handles reject the request). |
| Solids | `solid_operation` | Kernel-backed create, transform, booleans, extrude, region and surface from a profile, picture embedding. Every result is verified to lift back from its ACIS payload without loss; a refusal leaves the operands untouched. | | Solids | `solid_operation` | Kernel-backed create, transform, booleans, extrude, region and surface from a profile, picture embedding. Every result is verified to lift back from its ACIS payload without loss; a refusal leaves the operands untouched. |
| Tables | `table_operation` | Layers, text and dimension styles, blocks and their contents, linetypes, layouts (see below). | | Tables | `table_operation` | Layers, text and dimension styles, blocks and their contents, linetypes, layouts (see below). |
@@ -105,6 +105,32 @@ objects map byte-exactly, the auto-created dictionaries included.
dictionary's own order, so audits need no prior knowledge. Not yet mapped to dictionary's own order, so audits need no prior knowledge. Not yet mapped to
Python. Python.
## Reference-only entities (API v9)
`set_reference_only` designates plugin-owned display geometry: a
reference-only entity renders and snaps normally, but the built-in edit paths
treat it like an entity on a locked layer — it is not grip-editable, not
click-pickable (a click passes through to the editable entity underneath),
and window, box, lasso and fence selections skip it. User-driven writes
(grip drags, modify commands, the properties panel, user erase, MCP
`set_properties`) refuse it; the owning plugin keeps full write access:
`add_entity`, `update_entity`, `update_entities_transaction`, `remove_entity`
and the XDATA record methods are unaffected, so a plugin can refresh derived
display geometry in place. The record-method policy applies first: absent
entities and locked layers refuse, and like them every refusal is decided
before the undo scope is touched, so a failure changes nothing. A
re-designation that would not change the state succeeds without recording an
undo step. Undo composition is the entity-write contract: a change inside a
plugin-opened `push_undo` scope joins it, so an operation composed of several
writes is a single undo step under the plugin's label; with no scope open a
change is one undo step of its own. The designation survives DWG and DXF
save/reload, stored as a marker value in an XDATA record under the host-owned
`OCS` application namespace — plugins register their own APPIDs through their
manifest, so the two cannot collide. Out of scope today: entities nested
inside block instances (the block cache rebuilds instance wires without the
flag) and a Python mapping. The MCP `entities` read exposes the flag as a
`reference_only` boolean on every entity record.
## Commands ## Commands
`run_command` takes a `CommandRequest` (`Run`, `Start`, `Point`, `Text`, `Token`, `run_command` takes a `CommandRequest` (`Run`, `Start`, `Point`, `Text`, `Token`,