ocs_doc_api: layer CRUD, per-curve layer assignment, XDATA/XRECORD support, and bulk SetXDataMany

This commit is contained in:
Sebastian committed 2026-09-10 10:43:15 +02:00
1 parent b19992cbcb
commit 8bf5eac3fc
27 files changed
+3340 -181

No files matched your search

+98
View File
@@ -30,11 +30,109 @@ Transports bind to one document tab. Typed handles from another transport and
requests for another tab are rejected. Raw `ObjectId` values are relative to the
receiving document. Errors are serialized as `ApiError` variants over IPC.
Layer CRUD is exposed through `Document::layers()`, `create_layer(info)`,
`update_layer(name, info)`, `delete_layer(name)` and through the `layer()` /
`set_layer(...)` methods on every entity handle (`Line`, `Circle`, `Solid`,
`Entity`, etc.). Layer properties are carried as the plain-data `LayerInfo` DTO
with `LayerFlags`, `Color` and `LineWeight`. The host enforces CAD-style rules:
layer "0" and the current layer cannot be deleted, duplicate names are rejected,
and a layer still referenced by entities cannot be removed.
XDATA and XRECORD payloads are carried as plain-data, serde-compatible DTOs
(`XDataRecord`, `XRecordSpec`, and their value/entry types) and roundtripped
verbatim by the host. Every `Entity` handle can read/write one XDATA record per
registered application name via `xdata(application_name)` and `set_xdata(...)`,
and standalone XRECORD objects can be created with `doc.entities().create_xrecord(...)`
and updated through the `XRecord` handle.
| Feature | Use |
|---|---|
| Default | DTOs, facade, transport trait and binding schema |
| `host` | Kernel and entity adapters, executor, `DocApiBackend`, `InProcess` |
| `ipc` | `OcsPluginApiIpc` adapter for a plugin connected to the host |
| `doc_api_host` | Convenience `doc_api_for_host(&dyn HostApi) -> Option<DocApi>` for out-of-process / worker-thread plugins |
### Connecting from a plugin
For out-of-process or worker-thread plugins that receive `&dyn HostApi` (or any
`ocs_plugin_api::host::HostApi` implementor), enable the `doc_api_host` feature
and use the helper instead of manually serializing `DocApiEnvelope`s:
```rust
use ocs_doc_api::doc_api_for_host;
use ocs_plugin_api::host::HostApi;
fn on_dispatch(host: &mut dyn HostApi) -> Option<()> {
let doc_api = doc_api_for_host(host)?;
let doc = doc_api.document(host.tab_id());
let cube = doc.solids().create_cuboid([0.0; 3], [10.0; 3]).ok()?;
Some(())
}
```
`doc_api_for_host` returns `None` for hosts that do not expose a
`PluginRequestSender` (for example the in-process `HostSession`). In-process code
should use `DocApi::in_process(backend, tab_id)` with a concrete
`DocApiBackend`.
### Plugin example: create, read, update and delete a line
```rust
use ocs_doc_api::{doc_api_for_host, ApiResult, Color, LayerInfo, LineWeight};
use ocs_plugin_api::host::HostApi;
fn draw_and_modify_line(host: &mut dyn HostApi) -> ApiResult<()> {
// Build a DocApi from the host's plugin request channel.
let api = doc_api_for_host(host).ok_or_else(|| {
ocs_doc_api::ApiError::Transport("host does not expose a DocApi channel".into())
})?;
let doc = api.document(api.active_tab());
// Ensure a target layer exists.
let mut info = LayerInfo::new("MyLayer");
info.color = Color::Index(1);
info.line_weight = LineWeight::Value(25);
let _ = doc.create_layer(&info); // ok if it already exists
// Create a line on that layer.
let line = doc.curves().create_line([0.0, 0.0, 0.0], [10.0, 0.0, 0.0])?;
line.set_layer("MyLayer")?;
// Read it back.
assert_eq!(line.layer()?, "MYLAYER");
// Move the endpoint in place (same ObjectId).
line.transform(ocs_doc_api::PlacementSpec::at([5.0, 0.0, 0.0]))?;
// Delete it; the document is left unchanged if this failed.
line.delete()?;
Ok(())
}
```
### Plugin example: create a box and read its volume
```rust
use ocs_doc_api::{doc_api_for_host, ApiResult};
use ocs_plugin_api::host::HostApi;
fn create_box_and_measure(host: &mut dyn HostApi) -> ApiResult<f64> {
let api = doc_api_for_host(host).ok_or_else(|| {
ocs_doc_api::ApiError::Transport("host does not expose a DocApi channel".into())
})?;
let doc = api.document(api.active_tab());
let box_ = doc.solids().create_cuboid([0.0; 3], [10.0; 3])?;
let volume = box_.volume()?;
// Boolean example: intersect with a second box.
let other = doc.solids().create_cuboid([5.0; 3], [10.0; 3])?;
let intersection = box_.intersect(&other)?;
let _intersection_volume = intersection.volume()?;
Ok(volume)
}
```
See the [API reference](src/gen/api_reference.md) for constructors and methods,
[architecture](ARCHITECTURE.md) for backend rules, and