OpenCADStudio/plugins#901 (comment)
- Add HostApi::document_path(tab_id) returning Option<PathBuf>.
- Add PluginRequest::DocumentPath and PluginResponse::DocumentPath for
out-of-process plugins.
- Implement the request in the IPC server and in both V3/V4 plugin-side
HostApi proxies.
- Implement HostSession::document_path by looking up the tab by id and
returning its current_path.
An entity added via a plugin's AddEntity with a common.layer name that no
LAYER command ever created kept its layer in-session, but the DWG writer
resolves an unknown layer name to a NULL handle, so on reopen the entity
collapsed to layer 0.
Add a shared Scene::ensure_layer(name) that registers a missing layer with a
real handle (allocate_handle, per #67) and call it from add_entity and
update_entity. This covers the plugin AddEntity/UpdateEntity IPC and every
internal add path without a new IPC request. Existing/empty/"0" layers are
no-ops, so normal draw commands are unaffected.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
#249 — plugin XDATA written via write_record survived only in memory: the
acadrust DWG writer dropped ExtendedData::records on save. Bump acadrust to
e88a9a6 (records now encode to EED and decode back on read) and fix the host
side that fed it:
- ensure_app_id allocates a real APPID handle; a null handle serializes as
0 and the EED reference can't resolve, so the XDATA vanished on reopen.
- write_record / remove_record drop stale raw_dwg_eed for the target app so
an edit made after a save/reopen wins over the pre-edit bytes.
#250 — out-of-process plugins got a throwaway document_mut() snapshot, so
edits to existing entities were silently discarded and deletion wasn't
expressible at all. Add the missing mutation surface:
- UpdateEntity / RemoveEntity IPC requests + HostApi::update_entity /
remove_entity (default in-process impls, RPC overrides on the client that
invalidate the stale document cache).
- Scene::update_entity replaces the entity in place, preserving its handle
and owning block, and reseeds only its derived caches; remove reuses the
cache-coherent erase_entities (which also honours layer locks).
- document_mut() is documented as a local-only snapshot out-of-process.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Follow-ups after merging the out-of-process plugin isolation work:
- Convert the out-of-process interactive adapter's `on_point` /
`on_entity_pick` to `glam::DVec3`, matching the `CadCommand` trait,
which moved to f64 pick coordinates. The branch predated that change
and no longer compiled against the trait.
- Contain plugin constructor panics in `export_plugin!`: the constructor
runs across the C ABI boundary in `ocs_plugin_register`, where an
unwinding panic is undefined behavior. Catch it and return null, which
the loader already treats as a failed registration.
- Reap killed runner processes. The spawn-timeout and disconnect paths
called `child.kill()` without `wait()`, leaving a zombie on Unix until
the host exited. Route both — and `shutdown()` — through a shared
`reap()` that kills and waits on a detached thread.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Clipped blocks were broken at UTM and lost their clip when pasted into
another drawing.
- Clip geometry: world_clip_polygon_f64 keeps the boundary in absolute f64;
clip_wires now clips in a frame relative to the boundary's first vertex
(small coords → exact f32 Sutherland–Hodgman) and re-splits the result
into the double-single high/low pair the relative-to-eye renderer needs,
so the clipped block draws correctly and no longer poisons ZOOM Extents.
- ZOOM Extents (fit_all): the outlier reject compared absolute UTM
coordinates against a centre-RELATIVE span (local_extent_max), rejecting
the whole drawing → no-op. Made the reject distance-from-median-centre.
- Cross-drawing paste: clipboard now snapshots each copied entity's whole
xdictionary object graph (ClipboardDeps.ext_objects) and recreates it on
paste with fresh handles + remapped references, re-pointing the pasted
entity's xdictionary at the new root. Wired into PASTECLIP and PASTEORIG.
Fixed handle allocation (allocate_handle, not the non-advancing
next_handle) so the dictionary chain doesn't collapse onto one handle.
- Updated hit_test / plugin_host tests for the eye-relative + DVec3 signatures.
Known gaps: PASTEBLOCK does not yet carry the clip (clip would need to live
inside the new wrapper block definition); hatch-clip path still f32.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Whole command/snap/grip/cursor chain now builds on f64 DVec3 end to end,
so picked points, snaps and grip drags stay precise at UTM-scale
coordinates instead of quantizing to ~0.5 m through f32.
- All ~80 command files migrated: draw (line/circle/arc/ellipse/shapes
in full f64; others via point methods), modify, annotate, insert, view,
inquiry, layers, groups, clipboard, layout.
- Entity apply_grip impls updated for GripApply::{Absolute,Translate}(DVec3).
- Construction sites (EntityTransform/CmdResult/DynAnchor) emit DVec3;
wire-preview boundaries cast to f32 only at the GPU edge.
- Modify/annotate commands keep internal Vec3 geometry where precision is
non-critical, casting at the DVec3 boundary; key draw commands store f64.
Builds clean (no warnings). Visual verification of crosshair/snap/grip at
extreme UTM zoom pending.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
A buggy external plugin that panicked anywhere — during load (version,
register, manifest), during ribbon build / dispatch, or inside an
interactive command (prompt, on_point, on_enter, on_object_pick) — took
the host down with it (#134). Run every cdylib entry point under
catch_unwind so the host stays up: the plugin's call is treated as
inert (load fails, dispatch returns "didn't handle it", interactive
hooks resolve to Cancel) and a "Plugin <hook> panicked: …" line lands
in the command line on the next tick. (#145)
Scope is Rust panics across the cdylib boundary, not genuine UB —
real isolation (segfault-proof) still requires out-of-process plugins.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Extend InteractiveCommand with needs_object_pick / on_object_pick so a
plugin tool can prompt the user to pick an existing entity (handle + point)
rather than a free point — the structure-pick path Storm Sewer's SS_PIPE
needs to connect existing structures. The host adapter delegates to the
internal entity-pick flow; over --serve the pick is fed as a hex handle.
Stays API v2 (no released plugin implements InteractiveCommand yet).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
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 <[email protected]>
OpenCADStudio no longer bundles plugins — every add-on is an external
cdylib loaded from the plugins folder or installed via the marketplace.
- Remove the demo plugin and the in-tree ocs_example_plugin crate.
- Remove the inventory-based built-in registration path (PluginRegistration,
all_plugins, installed_manifests); registry now serves core ribbon tabs
plus loaded external plugins, and try_dispatch routes only to externals.
- Plugin Manager drops the 'compiled into this build' section; the
enable/disable toggle moves onto loaded external cards.
- Prune the now-dead HostSession/DocumentTab helpers and re-exports.
Part of the #100 extensibility epic (phase 2).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Plugins targeted the host's concrete HostSession type. Add a HostApi
trait in ocs_plugin_api behind an optional `host` feature (the only thing
that pulls acadrust, so the core crate stays dependency-free). HostSession
implements it and BuiltinPlugin::dispatch now takes `&mut dyn HostApi`, so
an out-of-tree add-on compiles against the contract crate alone. Per-tab
plugin state is reached through object-safe plugin_state* helpers.
Completes the phase-1 host-surface extraction in the #100 epic.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Plugins persisted domain data via the raw acadrust::xdata API with
boilerplate and no APPID registration. Add read_record / write_record /
remove_record keyed by entity handle; write_record replaces an existing
record for the same app and registers the application in the APPID table
so the data round-trips through DWG/DXF for other CAD apps.
Part of the #100 extensibility epic (surfaced in #106).
Co-Authored-By: Claude Opus 4.8 <[email protected]>