This document is the **authoritative spec** for how add-on packages integrate with Open CAD Studio. It follows patterns familiar from [QGIS](https://plugins.qgis.org/) and other open-source extensibility models: a small metadata file, a single entry-point registration, optional separate engine crate, and user-installable packages in a later phase.
> **Scope:** Generic host runtime only (`src/plugin/`, `src/app/plugin_host.rs`). Domain plugins (Storm Sewer, future sanitary/geotech) live under `src/modules/<name>/` and optional `crates/<engine>/`. They **consume** this API; they are **not** part of the framework source.
---
## Design goals
| Goal | Rationale |
|------|-----------|
| **One package, one registration** | Ribbon tab, commands, and manifest ship together — no duplicate hooks in `build.rs` and `commands.rs`. |
QGIS separates **core application** from **Python plugins** loaded at runtime. Open CAD Studio phase 1 compiles add-ons **in-tree** (same ergonomics, static linking). Phase 2 adds dynamic `.dll`/`.so` with the **same**`plugin.toml` and C ABI entry point.
---
## Add-on package layout
Every add-on — whether in-tree or external — uses this directory shape:
```
<plugin_id>/ # e.g. storm_sewer or opencad-storm-sewer repo
plugin.toml # human metadata (mirrors manifest.rs)
PLUGIN.md # XDATA schemas, command reference
register.rs # ONLY inventory::submit! — no domain logic
**Discovery rule:** If `src/modules/<dir>/plugin.toml` exists, `build.rs`**excludes** that directory from the auto-generated ribbon registry. The tab is registered only via `BuiltinPlugin::ribbon()`.
| XDATA | `read_record(handle, app)`, `write_record(handle, record)`, `remove_record(handle, app)` — keyed by entity handle; `write_record` registers the APPID so the file stays standard |
Autocomplete: each plugin submits `inventory::submit!(CommandRegistration { names: &[…] })` in `mod.rs` or `register.rs`.
Interactive acquisition (C3D-style orange ObjectPick) stays in the **host** via generic `CadCommand` hooks — `resolve_object_pick`, `object_pick_hover_previews`, `entity_pick_acquire_previews` — so `app/update.rs` never imports domain modules.
### Per-document state
```rust
DocumentTab {
plugin_state: HashMap<&'static str, Box<dyn Any + Send + Sync>>,
}
```
Store under `manifest.id` (e.g. `opencad.storm_sewer`), not ad hoc globals.
### XDATA contract
Domain persistence lives on entities. Document schemas in `PLUGIN.md`:
`HostSession` provides `read_record` / `write_record` / `remove_record` helpers (keyed by entity handle) over the `acadrust` XDATA API; `write_record` also registers the application in the APPID table so the data survives a DWG/DXF round-trip. Plugins may still use the raw `acadrust` XDATA APIs directly.
- [x] Plugin manager UI (list installed, versions) — `PLUGINS` / `PLUGINMANAGER` command, or the Start-page "Plugins" button
- [x] Enable/disable plugins from the manager — a disabled plugin drops its ribbon tab and command dispatch; persisted in `settings.txt` (`disabled_plugins=`)
- [x]`ModuleEvent::PluginFileDialog` — a plugin tool requests a native file picker; the host opens it and dispatches `"<command> <path>"` back to the plugin with original case preserved (bypasses the command-line upper-casing)
- [x] XDATA convenience on `HostSession` — `read_record` / `write_record` / `remove_record`; `write_record` registers the APPID so plugin data round-trips through DWG/DXF
- Enable/disable in settings (like QGIS plugin manager)
### Phase 3 — Interchange & QA
- LandXML / SWMM export as plugins or engine features
- Golden-file tests per plugin
- Public plugin index (optional)
### Phase 4 — Live analysis & WASM
-`on_entity_committed` hooks
- WASM-hosted engines on hydrocomplete.com
---
## Authoring a new add-on (checklist)
1. Copy `docs/plugin-template/` into `src/modules/<name>/`.
2. Fill `plugin.toml` and `manifest.rs` (keep in sync).
3. Implement `CadModule` in `mod.rs` (ribbon).
4. Implement `dispatch.rs` (all commands for your prefixes).
5. Add `plugin.rs` + `register.rs`.
6. Add `pub mod <name>;` to `src/modules/mod.rs`.
7. Document XDATA in `PLUGIN.md`.
8. Optional: add `crates/<engine>/` and depend from the plugin package only.
9.`cargo build` — tab appears via plugin registry; `commands.rs` untouched.
**External repo:** Publish the engine crate to crates.io; depend on `ocs_plugin_api` (when extracted) + ship a `cdylib` for phase 2. In-tree path: add as a git submodule under `src/modules/<name>/` or `plugins/`.