forked from 1b-agent/opencadstudio-upstream
The codec and kernel repositories and crates are renamed opencadcodec and opencadkernel. Inside OCS they are depended on under short keys, so code reads `codec::`, `kernel::` and the new `graph::` (opencadgraph, the node graph engine). File-format and protocol spellings stay as they were: the ACIS product id `acadrust`, the ACADRUST_* switches and the plugin version keys `acadrust_version` / `acadrust_source`. A button left of MCP on the command line opens a translucent node graph over the viewport, with a palette of object, input, math, logic, list, text and geometry nodes added by click or drag. An object node owns drawing entities and its rows are the Properties panel's own rows for them: the single-object page is extracted from refresh_properties as entity_property_sections, values use the control protocol's property_json, and writes go through set_property_value with a property target override, so the panel, MCP and the graph share one read/write path. A list reaching an object row makes one entity per element. Each graph action is one undo step; the canvas pans and zooms. Co-Authored-By: Claude Opus 5.5 <[email protected]>
471 lines
21 KiB
Markdown
471 lines
21 KiB
Markdown
# Open CAD Studio — Plugin Architecture
|
|
|
|
**Status:** Accepted
|
|
**Author:** Open CAD Studio contributors
|
|
**Date:** June 2026
|
|
|
|
This document is the **authoritative spec** for how add-on packages integrate
|
|
with Open CAD Studio. The model follows [QGIS](https://plugins.qgis.org/)-style
|
|
extensibility: a small metadata file, a single entry point, an optional separate
|
|
engine crate, and user-installable packages from a curated index.
|
|
|
|
> Plugins are **out-of-process dynamic libraries** (`cdylib`). The host loads
|
|
> first-party packages from its application resources and third-party packages
|
|
> from the user plugins folder. Both use the generic runtime
|
|
> (`src/plugin/`, `src/app/plugin_host.rs`) and the stable contract crate
|
|
> (`crates/ocs_plugin_api`). A user package with the same id explicitly
|
|
> overrides a bundled package.
|
|
|
|
---
|
|
|
|
## Design goals
|
|
|
|
| Goal | Rationale |
|
|
|------|-----------|
|
|
| **One package, one entry point** | Manifest, ribbon tab and commands ship together in the plugin crate; no edits to the host. |
|
|
| **Stable contract** | Authors target the semver-versioned `ocs_plugin_api` crate, not `OpenCADStudio` internals. |
|
|
| **Out-of-tree by default** | Third-party plugins remain independent; selected first-party plugins may build and ship with OCS. |
|
|
| **DWG round-trip** | Domain data lives on entities as XDATA, not in an opaque side database. |
|
|
| **Engine reuse** | A headless `std`-only engine crate can run in WASM/CLI without the CAD host. |
|
|
|
|
## Non-goals
|
|
|
|
- Signature verification (installing a plugin runs native code; the user trusts
|
|
the repos they install from). Process isolation limits the blast radius of a
|
|
buggy or malicious plugin, but it is not a security sandbox.
|
|
- Cross-toolchain binary compatibility — see [Compatibility](#compatibility--abi).
|
|
- Sandboxed scripting (Python/Lua); replacing the `opencadcodec` entity model.
|
|
|
|
---
|
|
|
|
## Three layers
|
|
|
|
```
|
|
┌────────────────────────────────────────────────────────────────────┐
|
|
│ Layer A — Host (OpenCADStudio) │
|
|
│ iced UI · Scene · Document · Undo · Command line │
|
|
│ Core ribbon tabs: Home, Model, View, … (NOT plugins) │
|
|
│ Generic plugin runtime: discovery, spawn, dispatch │
|
|
└───────────────────────────────┬────────────────────────────────────┘
|
|
│ IPC over local socket (ocs_plugin_api)
|
|
┌───────────────────────────────▼────────────────────────────────────┐
|
|
│ Layer B — Plugin process (external repo, cdylib) │
|
|
│ host spawns itself in runner mode · Cargo.toml · plugin.toml │
|
|
│ PluginManifest · CadModule ribbon · BuiltinPlugin · export_plugin! │
|
|
└───────────────────────────────┬────────────────────────────────────┘
|
|
│ pure Rust API
|
|
┌───────────────────────────────▼────────────────────────────────────┐
|
|
│ Layer C — Domain engine crate (optional) │
|
|
│ hydraulics / COGO / … — `std` only, no iced/opencadcodec │
|
|
└────────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
| Layer | Lives in | May depend on |
|
|
|-------|----------|---------------|
|
|
| **A — Host** | this repo: `src/`, `crates/ocs_plugin_api` runtime | everything |
|
|
| **B — Plugin** | a separate repo (cdylib), spawned by the host in runner mode | `ocs_plugin_api` + optional engine |
|
|
| **C — Engine** | the plugin's own crate or crates.io | `std` only (WASM/CLI-capable) |
|
|
|
|
**Hard rules**
|
|
|
|
1. The host (`src/plugin/`) imports no plugin code — it only knows the contract.
|
|
2. Engine crates import neither `iced`, `opencadcodec`, nor `OpenCADStudio`.
|
|
3. A plugin never edits host source; it runs entirely from its own crate.
|
|
|
|
---
|
|
|
|
## The contract crate — `ocs_plugin_api`
|
|
|
|
[`crates/ocs_plugin_api`](../crates/ocs_plugin_api) is the semver-versioned API a
|
|
plugin compiles against. Two tiers:
|
|
|
|
- **Dependency-free core** (default): `PluginManifest` / `ApiVersion` and the
|
|
ribbon vocabulary — `CadModule`, `ToolDef`, `RibbonGroup`, `RibbonItem`,
|
|
`IconKind`, `ModuleEvent`, `StyleKey`. Engine crates and tooling depend on this
|
|
cheaply.
|
|
- **`host` feature** (pulls `opencadcodec`): the runtime surface — the `HostApi`
|
|
trait, the `BuiltinPlugin` entry-point trait, the `export_plugin!` macro, and
|
|
the out-of-process plugin runtime (`PluginProcess`, `runner`).
|
|
|
|
A plugin enables the `host` feature.
|
|
|
|
### Build-time ABI metadata
|
|
|
|
For API v4 and later the host records two pieces of build-time metadata that
|
|
affect whether a prebuilt plugin can safely be loaded:
|
|
|
|
- `acadrust_source` — the exact git source of the `opencadcodec` crate the host was
|
|
built against.
|
|
- `rustc_version` — the output of `rustc --version` for the compiler that built
|
|
the host.
|
|
|
|
A plugin's `plugin.toml` declares the same values under `[opencad]`. For API v4
|
|
and later, `rustc_version` is required and the host refuses to load the plugin
|
|
unless it matches exactly — omitting it is treated as a mismatch, not skipped.
|
|
This turns the current silent runner crash into a one-line diagnosis such as:
|
|
|
|
```
|
|
Plugin built with rustc 1.96.0, host requires 1.98.0 - rebuild required
|
|
```
|
|
|
|
`acadrust_source` is also required for API v4 and later. Missing, empty, or
|
|
mismatched metadata makes the package incompatible.
|
|
|
|
### `PluginManifest`
|
|
|
|
```rust
|
|
pub struct PluginManifest {
|
|
pub id: &'static str, // reverse-DNS: "opencad.example"
|
|
pub name: &'static str,
|
|
pub version: &'static str,
|
|
pub description: &'static str,
|
|
pub api_version: ApiVersion, // host ABI major; must match the host
|
|
pub ribbon_order: i32, // sort key among add-on tabs
|
|
pub xdata_apps: &'static [&'static str],
|
|
pub command_prefixes: &'static [&'static str],
|
|
}
|
|
```
|
|
|
|
### `BuiltinPlugin` — the entry point
|
|
|
|
```rust
|
|
pub trait BuiltinPlugin: Send + Sync {
|
|
fn manifest(&self) -> &'static PluginManifest;
|
|
fn ribbon(&self) -> Box<dyn CadModule>; // the ribbon tab
|
|
fn dispatch(&self, host: &mut dyn HostApi, cmd: &str) -> bool;
|
|
fn on_load(&mut self, host: &mut dyn HostApi) {}
|
|
}
|
|
```
|
|
|
|
### `HostApi` — the plugin-facing runtime surface
|
|
|
|
`dispatch` receives `&mut dyn HostApi`, so a plugin never touches the host's
|
|
concrete types:
|
|
|
|
| Category | Methods |
|
|
|----------|---------|
|
|
| Document | `document()` / `document_mut()` return a **local cached copy** of the document (API v3); the first access in a dispatch clones the full `CadDocument` over IPC. Use `add_entity()` / `write_record()` for host-visible mutations. `add_entity()`, `bump_geometry()` |
|
|
| XDATA | `read_record(handle, app)`, `write_record(handle, record)`, `remove_record(handle, app)` — keyed by entity handle; `write_record` registers the APPID so data round-trips through DWG/DXF |
|
|
| Tab state | object-safe `plugin_state_any*` helpers exist for in-process use; out-of-process plugins should keep state inside the plugin crate because `dyn Any` is not serializable |
|
|
| Command line | `push_info`, `push_output`, `push_error` |
|
|
| Undo / dirty | `push_undo`, `set_dirty` |
|
|
| Tab | `tab_index()` |
|
|
| Document identity (V5) | `document_path(tab_id)` returns the saved path for a document tab. |
|
|
| Notifications (V4) | `on_notification` receives `HostNotification::SelectionChangedV4 { tab_id, handles }` when the active tab's selection changes, plus `DocumentChangedV4` / `DocumentTabClosed`. |
|
|
|
|
### `export_plugin!` — the C-ABI export
|
|
|
|
```rust
|
|
ocs_plugin_api::export_plugin!(MyPlugin);
|
|
```
|
|
|
|
emits the two symbols the loader looks for:
|
|
|
|
- `ocs_plugin_api_version() -> u32` — checked **first**, so an API-incompatible
|
|
build never runs its code.
|
|
- `ocs_plugin_register() -> *mut Box<dyn BuiltinPlugin>` — constructs the plugin
|
|
and hands ownership to the host.
|
|
|
|
---
|
|
|
|
## Writing a plugin
|
|
|
|
A plugin is a standalone crate that builds a `cdylib`:
|
|
|
|
```toml
|
|
# Cargo.toml
|
|
[lib]
|
|
crate-type = ["cdylib"]
|
|
|
|
[dependencies]
|
|
ocs_plugin_api = { git = "https://github.com/HakanSeven12/OpenCADStudio", features = ["host"] }
|
|
|
|
# Match the host's opencadcodec so the loaded library is binary-compatible.
|
|
[patch.crates-io]
|
|
opencadcodec = { git = "https://github.com/HakanSeven12/opencadcodec", branch = "main" }
|
|
```
|
|
|
|
Your release build must also use the same `rustc` as the host. Record it in
|
|
`plugin.toml` (see below) and pin the toolchain in your CI matrix so every
|
|
published asset matches the host's compiler.
|
|
|
|
```rust
|
|
// src/lib.rs
|
|
use ocs_plugin_api::host::{BuiltinPlugin, HostApi};
|
|
use ocs_plugin_api::manifest::{ApiVersion, PluginManifest};
|
|
use ocs_plugin_api::ribbon::{CadModule, IconKind, ModuleEvent, RibbonGroup, RibbonItem, ToolDef};
|
|
|
|
static MANIFEST: PluginManifest = PluginManifest {
|
|
id: "opencad.example", name: "Example Plugin", version: "0.1.0",
|
|
description: "…", api_version: ApiVersion::CURRENT,
|
|
ribbon_order: 50, xdata_apps: &[], command_prefixes: &["EX_"],
|
|
};
|
|
|
|
struct ExampleModule;
|
|
impl CadModule for ExampleModule {
|
|
fn id(&self) -> &'static str { "example" }
|
|
fn title(&self) -> &'static str { "Example" }
|
|
// Returns a slice, not an owned Vec — build once into a static and hand
|
|
// back a reference, rather than rebuilding (and dropping) it every call.
|
|
fn ribbon_groups(&self) -> &[RibbonGroup] {
|
|
static GROUPS: std::sync::OnceLock<Vec<RibbonGroup>> = std::sync::OnceLock::new();
|
|
GROUPS.get_or_init(|| vec![RibbonGroup { title: "Demo", tools: vec![RibbonItem::LargeTool(ToolDef {
|
|
id: "EX_HELLO", label: "Hello", icon: IconKind::Glyph("◆"),
|
|
event: ModuleEvent::Command("EX_HELLO".to_string()),
|
|
})]}]})
|
|
}
|
|
}
|
|
|
|
struct ExamplePlugin;
|
|
impl BuiltinPlugin for ExamplePlugin {
|
|
fn manifest(&self) -> &'static PluginManifest { &MANIFEST }
|
|
fn ribbon(&self) -> Box<dyn CadModule> { Box::new(ExampleModule) }
|
|
fn dispatch(&self, host: &mut dyn HostApi, cmd: &str) -> bool {
|
|
match cmd { "EX_HELLO" => { host.push_info("Hello"); true } _ => false }
|
|
}
|
|
}
|
|
|
|
ocs_plugin_api::export_plugin!(ExamplePlugin);
|
|
```
|
|
|
|
```toml
|
|
# plugin.toml — shipped beside the binary; values mirror MANIFEST
|
|
[plugin]
|
|
id = "opencad.example"
|
|
name = "Example Plugin"
|
|
version = "0.1.0"
|
|
description = "…"
|
|
|
|
[opencad]
|
|
api_version = 5
|
|
ribbon_order = 50
|
|
command_prefixes = ["EX_"]
|
|
xdata_apps = []
|
|
# Both are filled at release-build time; see the CI step below.
|
|
rustc_version = "rustc 1.98.0 (hash date)"
|
|
acadrust_source = "git+https://github.com/HakanSeven12/opencadcodec.git?rev=<short>#<full-40-char-commit>"
|
|
```
|
|
|
|
The full, buildable scaffold is in [`docs/plugin-template/`](plugin-template);
|
|
the live reference is the
|
|
[`opencad-example-plugin`](https://github.com/HakanSeven12/opencad-example-plugin)
|
|
repository.
|
|
|
|
### Commands
|
|
|
|
A plugin owns its `command_prefixes` (e.g. `EX_`). The host's command router
|
|
calls `try_dispatch` first; a returning `true` consumes the command. A plugin
|
|
tool fires `ModuleEvent::Command("EX_FOO")`, which round-trips to
|
|
`dispatch(host, "EX_FOO")`.
|
|
|
|
`ModuleEvent::PluginFileDialog { command, title, filter_name, extensions }` lets a
|
|
tool request a native file picker; on selection the host dispatches
|
|
`"<command> <path>"` back with the path's original case preserved.
|
|
|
|
A leading `>` or the persistent literal-input toggle preserves both spaces
|
|
and case. Automation and `ModuleEvent::PluginFileDialog` already preserve
|
|
case without this prefix.
|
|
|
|
### Interactive (click-to-place) commands
|
|
|
|
For tools that collect points — placing a structure, drawing a pipe — call
|
|
`host.start_interactive(Box::new(my_cmd))` from `dispatch`, where `my_cmd`
|
|
implements `ocs_plugin_api::host::InteractiveCommand`:
|
|
|
|
```rust
|
|
fn on_point(&mut self, pt: [f64; 3]) -> CommandStep {
|
|
// … return NeedPoint, Commit(entity), CommitAndEnd(entity), Done, or Cancel
|
|
}
|
|
```
|
|
|
|
The host drives it through its normal point-collection flow, so the **same**
|
|
command works by clicking in the viewport and by feeding coordinates over the
|
|
`--serve` automation API (`run "MY_CMD 0,0 10,10"`). This is what bumped the
|
|
API to **v2** — the added `HostApi` method changes the contract's vtable, so v1
|
|
binaries are refused at load.
|
|
|
|
To reference **existing** geometry (e.g. connect a pipe between two structures),
|
|
set `needs_object_pick() -> true`; the host then calls
|
|
`on_object_pick(handle, pt)` with the clicked entity's handle (read its
|
|
XDATA/geometry via `HostApi`). Over `--serve` the pick is supplied as a hex
|
|
handle: `run "MY_CMD 2F 30"`.
|
|
|
|
### XDATA — domain persistence
|
|
|
|
Store domain data on entities as XDATA (under your `xdata_apps` ids), not in a
|
|
side database, so it round-trips through DWG/DXF. `write_record` also registers
|
|
the APPID. Document your schemas in the plugin's own `PLUGIN.md`.
|
|
|
|
---
|
|
|
|
## Building & distribution
|
|
|
|
Build per platform and publish to **GitHub Releases**:
|
|
|
|
```
|
|
cargo build --release # → target/release/lib<crate>.so | <crate>.dll | lib<crate>.dylib
|
|
```
|
|
|
|
A release attaches one binary per platform plus `plugin.toml`, with the platform
|
|
in the asset name so the host can pick the right one:
|
|
|
|
```
|
|
opencad.example-linux-x86_64.so
|
|
opencad.example-windows-x86_64.dll
|
|
opencad.example-macos-aarch64.dylib
|
|
plugin.toml
|
|
```
|
|
|
|
A GitHub Actions matrix workflow (see the example repo / template) cross-builds
|
|
and uploads these on a `v*` tag. It also fills `plugin.toml`'s `rustc_version`
|
|
and `acadrust_source` placeholders from the CI run's actual toolchain and
|
|
resolved dependency, rather than hand-editing them per release — see
|
|
`docs/plugin-template/.github/workflows/release.yml` for the exact `sed`
|
|
substitutions, including reading `acadrust_source` out of `Cargo.lock`'s
|
|
resolved `opencadcodec` package entry (`source = "git+...#<hash>"`) since that's
|
|
the same value the host embeds at its own build time. The template's CI pins
|
|
`rust-toolchain@stable`, which is a floating target — a real release pipeline
|
|
should pin the exact toolchain version the host was built with (check
|
|
`crates/ocs_plugin_api/src/version_info.rs`'s embedded value, or the host's
|
|
own CI config) rather than trusting "whatever's current stable today" to
|
|
keep matching it.
|
|
|
|
---
|
|
|
|
## Loading
|
|
|
|
On startup the host scans `<config>/OpenCADStudio/plugins/<id>/` for a
|
|
`plugin.toml` + native library (`src/plugin/external.rs`):
|
|
|
|
```
|
|
<config>/OpenCADStudio/plugins/
|
|
opencad.example/
|
|
plugin.toml
|
|
libocs_example_plugin.so # any name with the platform extension
|
|
```
|
|
|
|
For each compatible package the host spawns **itself** in runner mode
|
|
(`--ocs-plugin-runner <socket> <cdylib>`). The child process loads the `cdylib`
|
|
in its own address space and connects back to the host over an `interprocess`
|
|
local socket. The runner checks `ocs_plugin_api_version` and refuses on
|
|
mismatch, then calls `ocs_plugin_register` to obtain the boxed `BuiltinPlugin`.
|
|
Each plugin runs in a separate OS process, so a plugin crash or memory
|
|
corruption cannot affect the host or other plugins. Plugin processes stay
|
|
**resident for the session**; external plugins merge into the same ribbon and
|
|
`try_dispatch` path the host uses and honour the enable/disable set
|
|
(`disabled_plugins` in `settings.txt`).
|
|
|
|
Two timeouts protect the host from a stuck runner:
|
|
|
|
| Timeout | Env var | Default | Floor |
|
|
|---|---|---|---|
|
|
| Spawn (connection) | `OCS_PLUGIN_SPAWN_TIMEOUT_SECS` | 30 s | — |
|
|
| Per-call | `OCS_PLUGIN_CALL_TIMEOUT_SECS` | 30 s | `GetManifest`/`GetRibbon` ≥ 5 s, `Dispatch` ≥ 10 s, interactive events/prompt/pick ≥ 2 s |
|
|
|
|
A call timeout covers the full round-trip, including any nested plugin→host
|
|
requests handled inline. When it fires the host kills the runner, marks the
|
|
plugin dead, and reports a `CallTimeout` error via the normal plugin error path.
|
|
|
|
`<config>` is `%APPDATA%` (Windows), `~/Library/Application Support` (macOS), or
|
|
`$XDG_CONFIG_HOME` / `~/.config` (Linux).
|
|
|
|
## Failure handling
|
|
|
|
| Failure | Behavior |
|
|
|---|---|
|
|
| Plugin panics | Caught inside the plugin runner child; an error response is returned to the host and stays alive. |
|
|
| Plugin crash / hang / malformed message | The host detects a dead process via `try_wait` on the next dispatch or ribbon rebuild; the tab is dropped and an error is logged. |
|
|
| Slow or non-responsive call | The per-call timeout (`OCS_PLUGIN_CALL_TIMEOUT_SECS`) fires, kills the runner, and surfaces a `CallTimeout` error. |
|
|
| Spawn failure | Reported per-plugin during startup and surfaced in the Plugin Manager / command line. |
|
|
| Oversized message | The length-framed transport rejects messages larger than 64 MiB. |
|
|
|
|
---
|
|
|
|
## Marketplace
|
|
|
|
The desktop **Plugin Manager** (`PLUGINS` / `PLUGINMANAGER`, or the Start-page
|
|
button) installs plugins from GitHub Releases. In the browser, the same entry
|
|
points show a desktop-download notice because marketplace packages are native
|
|
dynamic libraries:
|
|
|
|
- **Curated registry** — [`plugins/registry.json`](../plugins/registry.json) in
|
|
this repo lists discoverable plugins. The host fetches it from `main` at
|
|
runtime and shows each entry under *Available plugins*. To list a plugin, open
|
|
a PR adding `{ "repo", "name", "description" }` (see
|
|
[`plugins/README.md`](../plugins/README.md)); merged PRs reach every user with
|
|
no app update.
|
|
- **Manual link** — *Add a repository* (`owner/repo`) for unlisted or private
|
|
dev repos; linked repos persist in `settings.json`.
|
|
- **Install / upgrade / reinstall** — pick a release from the dropdown and
|
|
*Install*; the host downloads the platform asset + `plugin.toml` into the
|
|
plugins folder, checking `api_version` first. Reinstalling overwrites and
|
|
clears any stale library; picking a newer release upgrades. Changes take effect
|
|
on the next restart (the running library stays resident).
|
|
- **Uninstall** — removes the package folder (effective next restart).
|
|
- **Enable/disable** — toggles a loaded plugin's ribbon tab + dispatch without
|
|
uninstalling.
|
|
|
|
One linked repository is one marketplace entry. Publish separate plugins from
|
|
separate repositories so release and update histories stay unambiguous.
|
|
|
|
---
|
|
|
|
## Compatibility & ABI
|
|
|
|
Plugins are loaded as `cdylib`s by a plugin-runner child process. The host spawns
|
|
this child from its own executable (`--ocs-plugin-runner` mode), so the runner
|
|
and host always share the same `ocs_plugin_api` build. The runner checks
|
|
`ocs_plugin_api_version` before any plugin code runs. Each plugin runs in its
|
|
own OS process, so a plugin crash or memory-corruption bug cannot take down the
|
|
host process itself. The runner loads the plugin library in-process, however,
|
|
so API v4+ requires matching `rustc_version` and `acadrust_source` metadata.
|
|
|
|
A future hardening step is a `#[repr(C)]` vtable (a true C ABI) so binaries built
|
|
by any toolchain interoperate — required before trusting prebuilt binaries from
|
|
arbitrary build environments.
|
|
|
|
---
|
|
|
|
## Roadmap
|
|
|
|
Done:
|
|
|
|
- [x] Stable `ocs_plugin_api` crate — dependency-free core + `host` feature
|
|
(`HostApi` / `BuiltinPlugin` / `export_plugin!`).
|
|
- [x] Runtime discovery + out-of-process loading (host spawns itself in runner
|
|
mode) with an `api_version` gate and `interprocess` local-socket IPC.
|
|
- [x] XDATA helpers, `ModuleEvent::PluginFileDialog`, per-tab plugin state.
|
|
- [x] Marketplace — curated registry + manual repo link, install / upgrade /
|
|
reinstall / uninstall, enable/disable.
|
|
- [x] Interactive command round-trip over IPC (prompt, point/enter/object-pick).
|
|
- [x] Spawn and per-call IPC timeouts so a stuck runner cannot freeze the host.
|
|
- [x] External automation (`--mcp` — local stdio MCP/JSON-RPC server for AI
|
|
clients; `--serve` — headless JSON automation server; both drive OCS
|
|
from a separate process without the GUI, per `src/cli.rs`/`src/mcp.rs`/
|
|
`app::serve`). This closes the "drive OCS headless from a process" goal
|
|
of issue #29; this line previously and incorrectly listed it as not yet
|
|
started.
|
|
|
|
Next:
|
|
|
|
- [ ] Incremental document snapshots instead of cloning `CadDocument` over IPC.
|
|
- [ ] `#[repr(C)]` vtable / strict handshake for cross-toolchain binaries.
|
|
- [ ] Trust: checksums / signatures before spawning plugin processes.
|
|
- [ ] Interchange (LandXML / SWMM) and live `on_entity_committed` hooks.
|
|
|
|
---
|
|
|
|
## Reference
|
|
|
|
| Piece | Location |
|
|
|-------|----------|
|
|
| Contract crate + runtime | [`crates/ocs_plugin_api`](../crates/ocs_plugin_api) |
|
|
| Internal API architecture | [`crates/ocs_plugin_api/ARCHITECTURE.md`](../crates/ocs_plugin_api/ARCHITECTURE.md) |
|
|
| Plugin runner implementation | [`crates/ocs_plugin_api/src/runner.rs`](../crates/ocs_plugin_api/src/runner.rs) |
|
|
| Host spawn logic | [`crates/ocs_plugin_api/src/process.rs`](../crates/ocs_plugin_api/src/process.rs) |
|
|
| Host plugin integration | `src/plugin/`, `src/app/plugin_host.rs` |
|
|
| Core module registry generator | `build.rs` (writes to `OUT_DIR`, included by `src/modules/registry.rs`) |
|
|
| Marketplace + registry | `src/plugin/marketplace.rs`, [`plugins/registry.json`](../plugins/registry.json) |
|
|
| Template scaffold | [`docs/plugin-template/`](plugin-template) |
|
|
| Live example plugin | [`opencad-example-plugin`](https://github.com/HakanSeven12/opencad-example-plugin) |
|