Plugins can now declare the entity classes they identify via XDATA, and the
host resolves them to display-only type labels while the declaring plugin is
loaded.
- Append `BuiltinPlugin::entity_classes()` (a guarded trailing vtable slot,
the same additive pattern as `on_load` in v5) returning a &'static
`EntityClasses { app_id, class_prefix, labels }`. The static
`PluginManifest` struct is deliberately untouched: older cdylibs share it
by pointer and its layout is frozen ABI since v1, so adding a field would
read past their allocation.
- `OwnedEntityClasses` travels on the registered manifest as
`OwnedPluginManifest.entity_classes` with `#[serde(default)]`, so manifests
serialized before the field keep decoding (older runners tolerate it
both ways).
- The runner folds the declaration into the GetManifest registration only
when the loaded cdylib's exported `ocs_plugin_api_version` symbol reports
>= 10 — an older binary's vtable predates the slot, so it is never called;
its entities simply keep their native type names.
- `PluginManager::entity_class_declarations` exposes the live set, filtered
to alive, non-disabled plugins in load order. The registered manifests are
the only source and nothing is cached: not installed, disabled,
cap-excluded (`OCS_PLUGIN_MAX_API_VERSION`), failed at load, removed, or a
dead process all contribute nothing.
- Embedded version info `api_version` follows the bump to 10;
`API_VERSION_MIN_SUPPORTED` stays 3.
Tests: runner fold (v10 folds, pre-v10 skipped, `entity_classes()` panic is
contained and reported), manager filtering (disabled / dead / undeclaring
skipped; declarations disappear once the process dies — verified with a real
killed child), bincode registration round-trip, and older-manifest serde
default.
Plugins read the host's document through a shared reference across the
dynamic-library boundary, and the entity index hashed with foldhash's
per-process seed, so every handle lookup from the plugin missed. The
codec now uses a fixed hasher (opencadcodec f385f75; kernel 9841749,
graph e9c23ad).
The Python IPC suite also caught up with intended changes: ATTDEF
renders its tag, DXF now keeps the lock-position flag, and the modify
audit turns associative arrays off since it checks loose copies.
The rate-limiter test waits for the host's check instead of a fixed
sleep that raced on macOS, and ENV_LOCK no longer cascades a poisoned
lock into every later test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The HostApi gains set_reference_only(handle, reference_only): a plugin can
designate display geometry it owns and refreshes. The entity renders like any
other, but the built-in edit paths treat it like an entity on a locked layer,
while the owning plugin keeps full write access (update, erase, XDATA).
- Appended at the trait tail with a refusing default so plugins keep compiling
against older hosts; the loader gate range is untouched.
- IPC: SetReferenceOnly / SetReferenceOnlyResult round-trip through the
shared dispatch, the V3 client and the V4 client, with the shared document
cache invalidated on success.
- build.rs version_info and manifest API_VERSION bumped to 9 in lockstep.
- Evidence: reference_only_request_and_response_roundtrip (bincode round-trip
of the request and both response payloads).
Add create/read/update/delete/list for XRecord objects under
named-object-dictionary paths to the plugin HostApi, with five additive
PluginRequest/PluginResponse variants, dispatch through the shared
handler, in-process HostSession methods and both out-of-process proxies.
- dictionary paths walk from the NOD root; create seeds missing
dictionaries (ensure_image_dictionary bootstrap) and refuses existing
names; update refuses missing ones; refusals precede the undo step
- entry group codes validated (1..=369, never 5/105); payload content is
the plugin's own
- each mutating call is one undo step: the pending history snapshot
restores the objects map byte-exactly, auto-created dictionaries
included (tested)
- typed entries survive DWG and DXF write/reload round-trips (tested);
update drops verbatim raw bytes so saves emit the new content
- bump API_VERSION 7 -> 8 with the build.rs version_info in lockstep,
mirroring the v7 addition; MIN_SUPPORTED stays 3
A V2 plugin passed the version gate and then failed silently while
registering its ribbon, since the ribbon wire types have moved on. The
host now loads V3 and newer, so V2 plugins are rejected up front with a
version message. The V2 compatibility fixture crate goes too.
Fixes#1085Fixes#987
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Drop crates/ocs_doc_api from workspace and host dependency.
- Remove src/app/doc_api.rs and its module declaration.
- Strip DocApi v2 undo/dispatch plumbing from HostSession.
- Remove HostApi::doc_api_dispatch default method.
- Remove PluginRequest::DocApiRequest / PluginResponse::DocApiResponse.
- Revert plugin API version from 6 back to 5.
Sync current main and companion codec changes before preserving unsupported frame rows, rendering in the entity plane, and sharing final layout metrics with placement preview.
Plugins could dispatch commands but not register interactive (click-to-
place) tools — the gap mf4633 flagged on #100 for Storm Sewer's SS_INLET
/ SS_PIPE. Add an InteractiveCommand trait + CommandStep to ocs_plugin_api
and HostApi::start_interactive; a host adapter bridges it to the internal
CadCommand, so a plugin tool drives the host's point-collection flow.
Hybrid by construction: the same command works by clicking in the viewport
AND by feeding coordinates over --serve (run "CMD x,y x,y"). Adding a
HostApi method changes the contract vtable, so API_VERSION bumps to 2 —
v1 plugin binaries are now refused at load.
Part of the #100 extensibility epic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the dependency-free, semver-versioned half of the plugin contract into a
standalone workspace crate `crates/ocs_plugin_api`:
- manifest types: PluginManifest, ApiVersion, API_VERSION
- ribbon vocabulary: CadModule trait + ToolDef/RibbonGroup/RibbonItem/IconKind/
ModuleEvent/StyleKey
The host re-exports them from `crate::plugin::manifest` and `crate::modules`, so
every existing call site is unchanged. The acadrust-typed runtime surface
(HostSession) stays in the host binary; lifting it behind a HostApi trait in the
same crate is the remaining phase-1b step.
Part of #100. Docs updated in docs/plugin-architecture.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>