forked from 1b-agent/opencadstudio-upstream
docs(plugins): entity-class label convention; demo in template v2
- plugin-architecture.md: new "Entity class display labels (API v10)" section documenting the split (stable identity keys in XDATA, display labels in the plugin declaration), the live-loaded-plugin gating (missing, disabled, cap-excluded, failed, removed, or dead → bare native type), the "<Label> (<Native>)" display rule, that raw schema fields (`type`, `dxf_name`, `record_type`) and LIST are unchanged, and that the label is display-only. `plugin.toml` deliberately does not carry the table: it is the pre-load discovery file and labels must activate on live manifests. - Template v2 demonstrates the whole convention: declares `SURVEY-CLASS:POINT -> "Survey Point"` and stamps the identity key in MARK_FIRST_SURVEY_POINT, so marked points show as "Survey Point (Point)" while the plugin is loaded. plugin.toml's api_version is bumped to 10 (keep in sync with ApiVersion::CURRENT) with a NOTE on where the class table lives. DESIGN.md documents the flow. - Template v2's stale `acadrust` pin (8a28c21, predating the host's codec bump to 278c49a) is realigned with the host's pin: without it the template no longer compiles on main — its `acadrust::EntityType` and the host re-exports are distinct types when the revisions disagree (verified the same four type errors occur before and after this feature's template edit). - changelogs/unreleased.md entry (Plugins section).
This commit is contained in:
1 parent
ab3e0723fd
commit
be675f2824
6 files changed
+110
-4
No files matched your search
@@ -52,6 +52,9 @@
|
||||
* **Selection Event Streaming:** Automation clients now receive real-time selection change notifications. *(Contributed by [@YsDirector](https://github.com/YsDirector))*
|
||||
* **Host API v8:** Added name-addressed `XRecord` CRUD operations on `HostApi`. *(Contributed by [@schoeller](https://github.com/schoeller))*
|
||||
|
||||
#### Plugins
|
||||
* **Plugin-Declared Entity Class Labels (Host API v10):** Plugins can now give their XDATA-stamped entities human type names: the plugin stamps a stable class-identity key (e.g. `CVIFC-CLASS:LINE`) into entity XDATA and declares key → label mappings via the new `BuiltinPlugin::entity_classes()` manifest accessor; while the plugin is loaded, the Properties panel title and the records/automation `display_type` field show `<Label> (<Native>)` (e.g. "Linear Element (Line)"). Labels are display-only, resolved live from the loaded plugin set (not installed / disabled / dead process → bare native type), and never round-trip the drawing — identity keys do, so labels stay plugin-localizable without rewriting files. Raw schema fields (`type`, `dxf_name`, `record_type`) and the `LIST` command output are unchanged. *(Contributed by [@schoeller](https://github.com/schoeller))*
|
||||
|
||||
---
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
@@ -134,6 +134,7 @@ pub trait BuiltinPlugin: Send + Sync {
|
||||
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) {}
|
||||
fn entity_classes(&self) -> Option<&'static EntityClasses> {} // API v10
|
||||
}
|
||||
```
|
||||
|
||||
@@ -296,6 +297,57 @@ 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`.
|
||||
|
||||
### Entity class display labels (API v10)
|
||||
|
||||
A plugin can give its XDATA-stamped entities human type names in the host UI.
|
||||
The split between *identity* (in the drawing) and *labels* (in the plugin) is
|
||||
the point:
|
||||
|
||||
- **Identity** — the plugin stamps one string value into an entity's XDATA
|
||||
record: `"<class_prefix><KEY>"`, e.g. `"CVIFC-CLASS:LINE"`. The key is
|
||||
stable, meaningless to the host, and round-trips the DWG/DXF verbatim.
|
||||
- **Labels** — the plugin declares a table and exposes it through
|
||||
`BuiltinPlugin::entity_classes()`:
|
||||
|
||||
```rust
|
||||
use ocs_plugin_api::manifest::EntityClasses;
|
||||
|
||||
static ENTITY_CLASSES: EntityClasses = EntityClasses {
|
||||
app_id: "CVIFC", // one of the manifest's xdata_apps
|
||||
class_prefix: "CVIFC-CLASS:",
|
||||
labels: &[
|
||||
("LINE", "Linear Element"),
|
||||
("CIRCULARARC", "Circular Arc Element"),
|
||||
],
|
||||
};
|
||||
|
||||
impl BuiltinPlugin for MyPlugin {
|
||||
fn entity_classes(&self) -> Option<&'static EntityClasses> {
|
||||
Some(&ENTITY_CLASSES)
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
The host resolves them at display time only: the single-entity Properties
|
||||
panel title becomes `"Linear Element (Line)"`, and the records/automation
|
||||
`display_type` field takes the same form. The rule is display-only — a label
|
||||
never governs behavior, and the raw schema fields (`type`, `dxf_name`,
|
||||
`record_type`, the command-line `LIST` output) stay native so existing scripts
|
||||
and filters keep working.
|
||||
|
||||
**Gating.** Declarations are consulted through the live plugin manager, never
|
||||
cached: a plugin that is not installed, is disabled, is excluded by
|
||||
`OCS_PLUGIN_MAX_API_VERSION`, failed at load, has been removed, or whose
|
||||
process has died contributes nothing — its entities display as their bare
|
||||
native type ("Line"), immediately, with no re-open needed. An `app_id` claimed
|
||||
by more than one loaded plugin resolves through the first-loaded declaration;
|
||||
later duplicates are ignored (debug-logged). A class key that is empty,
|
||||
missing from the table, or mapped to an empty label falls back to the native
|
||||
type. Because labels are display text resolved from the loaded plugin, they
|
||||
are plugin-localizable and fixable without rewriting drawings — and
|
||||
`plugin.toml` (the pre-load discovery file) deliberately does not carry them.
|
||||
|
||||
---
|
||||
|
||||
## Building & distribution
|
||||
|
||||
@@ -12,4 +12,8 @@ crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
ocs_plugin_api = { path = "../../crates/ocs_plugin_api", features = ["host"] }
|
||||
acadrust = { git = "https://github.com/HakanSeven12/opencadcodec.git", rev = "8a28c21" }
|
||||
# Must match the host's opencadcodec pin exactly (its Cargo.toml's `codec`
|
||||
# entry): the plugin's entities and the host's re-exports are the same types
|
||||
# only when both resolve the same revision. The registration `rustc` /
|
||||
# `acadrust_source` gates enforce this at load time too.
|
||||
acadrust = { package = "opencadcodec", git = "https://github.com/HakanSeven12/opencadcodec.git", rev = "278c49a" }
|
||||
@@ -20,7 +20,7 @@ rather than a runtime dependency of the host.
|
||||
|---|---|
|
||||
| `COUNT_SURVEY_POINTS` | Count point entities on the `SURVEY` layer via `document_reader()`. |
|
||||
| `ADD_SURVEY_POINT` | Add a new point entity on the `SURVEY` layer via `host.add_entity()`. |
|
||||
| `MARK_FIRST_SURVEY_POINT` | Read the first `SURVEY` point and mark it with an XDATA record. |
|
||||
| `MARK_FIRST_SURVEY_POINT` | Read the first `SURVEY` point and mark it with an XDATA record (including the `SURVEY-CLASS:POINT` identity key). |
|
||||
| `COUNT_MARKED_SURVEY_POINTS` | Count `SURVEY` points that carry the `SURVEYMARK` XDATA record. |
|
||||
|
||||
---
|
||||
@@ -48,6 +48,24 @@ Each [`ReaderEntity`](ocs_plugin_api::host::ReaderEntity) exposes the entity
|
||||
|
||||
This is the primary read→write round-trip pattern the template demonstrates.
|
||||
|
||||
### Entity class display labels
|
||||
|
||||
The template also demonstrates the entity-class label convention (API v10):
|
||||
|
||||
- the plugin declares `ENTITY_CLASSES` and exposes it through
|
||||
`BuiltinPlugin::entity_classes()`: app `SURVEYMARK`, prefix
|
||||
`SURVEY-CLASS:`, and key → label mappings;
|
||||
- `MARK_FIRST_SURVEY_POINT` stamps a `SURVEY-CLASS:POINT` string value into
|
||||
the point's XDATA alongside the existing marker value.
|
||||
|
||||
While the plugin is loaded, the host shows marked points as
|
||||
"Survey Point (Point)" in the Properties panel title and in the
|
||||
`display_type` field of the records/automation APIs; without the plugin they
|
||||
are plain "Point" again. Only the stable key round-trips the drawing — the
|
||||
label text lives in the plugin and can be reworded or localized without
|
||||
touching saved files. The label is display-only: it never governs snapping,
|
||||
selection, or any other behavior.
|
||||
|
||||
### Ribbon integration
|
||||
|
||||
The plugin registers one ribbon module (`SurveyTools`) with one group
|
||||
|
||||
@@ -8,7 +8,9 @@ license = "GPL-3.0-only"
|
||||
repository = "your-account/your-plugin-repo"
|
||||
|
||||
[opencad]
|
||||
api_version = 5
|
||||
# Keep in sync with the Rust manifest's `ApiVersion::CURRENT` — this is the
|
||||
# pre-load gate; the runner also checks the compiled-in symbol.
|
||||
api_version = 10
|
||||
# Replace both placeholders in the packaging step. The v1 template's
|
||||
# release workflow contains a working example.
|
||||
rustc_version = "__RUSTC_VERSION__"
|
||||
@@ -21,3 +23,7 @@ command_prefixes = [
|
||||
"COUNT_MARKED_SURVEY_POINTS",
|
||||
]
|
||||
xdata_apps = ["SURVEYMARK"]
|
||||
# NOTE: the plugin's entity-class display labels (see `ENTITY_CLASSES` in
|
||||
# src/lib.rs) travel on the registered manifest over IPC, not in this file —
|
||||
# they only apply while the plugin is loaded, so plugin.toml (the pre-load
|
||||
# discovery file) deliberately does not carry them.
|
||||
@@ -8,7 +8,7 @@ use acadrust::xdata::{ExtendedDataRecord, XDataValue};
|
||||
use acadrust::{EntityType, Handle};
|
||||
use ocs_plugin_api::export_plugin;
|
||||
use ocs_plugin_api::host::{BuiltinPlugin, HostApi, ReaderEntityKind};
|
||||
use ocs_plugin_api::manifest::{ApiVersion, PluginManifest};
|
||||
use ocs_plugin_api::manifest::{ApiVersion, EntityClasses, PluginManifest};
|
||||
use ocs_plugin_api::ribbon::{CadModule, IconKind, ModuleEvent, RibbonGroup, RibbonItem, ToolDef};
|
||||
|
||||
static MANIFEST: PluginManifest = PluginManifest {
|
||||
@@ -27,6 +27,18 @@ static MANIFEST: PluginManifest = PluginManifest {
|
||||
],
|
||||
};
|
||||
|
||||
/// Entity-class display labels (API v10): entities carrying a
|
||||
/// `SURVEY-CLASS:<key>` string in their SURVEYMARK XDATA display the mapped
|
||||
/// label (e.g. "Survey Point (Point)") in the host's Properties panel and
|
||||
/// `display_type` — *while this plugin is loaded*. The key is the only thing
|
||||
/// stored in the drawing; the label text lives here, so it can be reworded or
|
||||
/// localized without rewriting drawings.
|
||||
static ENTITY_CLASSES: EntityClasses = EntityClasses {
|
||||
app_id: "SURVEYMARK",
|
||||
class_prefix: "SURVEY-CLASS:",
|
||||
labels: &[("POINT", "Survey Point")],
|
||||
};
|
||||
|
||||
struct PluginTemplateV2;
|
||||
|
||||
impl BuiltinPlugin for PluginTemplateV2 {
|
||||
@@ -34,6 +46,12 @@ impl BuiltinPlugin for PluginTemplateV2 {
|
||||
&MANIFEST
|
||||
}
|
||||
|
||||
// API v10: expose the entity-class labels declared above. The default
|
||||
// (not overriding this) declares none.
|
||||
fn entity_classes(&self) -> Option<&'static EntityClasses> {
|
||||
Some(&ENTITY_CLASSES)
|
||||
}
|
||||
|
||||
fn ribbon(&self) -> Box<dyn CadModule> {
|
||||
Box::new(TemplateModule)
|
||||
}
|
||||
@@ -106,6 +124,11 @@ fn first_survey_point_handle(host: &mut dyn HostApi) -> Option<Handle> {
|
||||
fn mark_point(host: &mut dyn HostApi, handle: Handle) {
|
||||
let mut record = ExtendedDataRecord::new("SURVEYMARK");
|
||||
record.add_value(XDataValue::Integer32(1));
|
||||
// Class-identity key for the entity-class labels declared above: the
|
||||
// host shows marked points as "Survey Point (Point)" while this plugin is
|
||||
// loaded, and as plain "Point" when it is not. Keys are stable identity —
|
||||
// display text never goes into the drawing.
|
||||
record.add_value(XDataValue::String("SURVEY-CLASS:POINT".to_string()));
|
||||
if host.write_record(handle, record) {
|
||||
host.push_info(&format!("Marked SURVEY point {handle}"));
|
||||
} else {
|
||||
|
||||
Reference in new issue
Block a user