# ocs_doc_api Typed document operations and queries for OpenCADStudio plugins and hosts. The same facade runs over an in-process backend or the plugin IPC channel. ```rust use ocs_doc_api::{ApiResult, DocApi}; fn intersect_solids(api: &DocApi) -> ApiResult { let doc = api.document(api.active_tab()); let a = doc.solids().create_cuboid([0.0; 3], [10.0; 3])?; let b = doc.solids().create_cuboid([5.0; 3], [10.0; 3])?; a.intersect(&b)?.volume() } ``` Each successful write marks the document changed and records one undo step. A failed operation leaves the document unchanged. Bulk creation and transforms prepare every entity before applying changes. Separate calls commit separately; `OpGroup` offers best-effort cleanup of creations, not rollback. Boolean `intersect`, `union` and `subtract` replace the first solid and erase the second. `intersects` only tests bounding-box overlap. Volume and centroid are computed by the kernel from a tessellated solid (chord tolerance 0.1 drawing units, angular tolerance 0.0001 radians), then cached per geometry revision. A request may compute at most 32 uncached mass properties; excess work returns an error. Bulk operations and query batches are capped at 100,000 items. 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` 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 { 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 [binding guide](bindings/README.md) for the Python facade and transport contract.