forked from 1b-agent/opencadstudio-upstream
New crate crates/ocs_doc_api: object-centric facade (DocApi -> Document -> collections -> typed handles), transport-agnostic wire layer (DocApiEnvelope = single op | read-only query batch), per-op autocommit (no write transactions), bulk ops with upfront-validation all-or-nothing, structured ApiError, and build-time codegen (spec/entities.toml -> ops/query enums, api_reference.md, binding_schema.json). ocs_plugin_api: append-only PluginRequest::DocApiRequest / PluginResponse:: DocApiResponse (serde_bytes payload) + defaulted HostApi::doc_api_dispatch. Host: DocApiBackend impl for HostSession (src/app/doc_api.rs) mapping to scene primitives (solid_models lift-on-miss, register_prepared_solid_model, meshes metrics), entity conversions (doc_api_convert.rs), HostSession accessors + doc_api_dispatch override. Tests: 592 host lib (incl. 7 DocApi integration tests: create/boolean/query, unknown-id, transform-many all-or-nothing, query-batch cap, 2D/geometric/DWG roundtrips), 8 crate unit tests. Examples + README/ARCHITECTURE/IMPLEMENTATION docs.
96 lines
4.4 KiB
Markdown
96 lines
4.4 KiB
Markdown
# ocs_doc_api
|
|
|
|
Object-oriented, transport-agnostic CAD entity API for OpenCADStudio plugins and
|
|
hosts (v2). One typed vocabulary — **Entity**, **ObjectId**, **Operation**,
|
|
**Query**, **Transport** — that runs identically in-process and over IPC.
|
|
|
|
```rust
|
|
use ocs_doc_api::{DocApi, Solid, OpGroup, ApiResult};
|
|
|
|
fn intersect_solids(api: &DocApi) -> ApiResult<()> {
|
|
let doc = api.document(api.active_tab());
|
|
let mut grp = OpGroup::new();
|
|
|
|
// Each call is ONE atomic host op = one undo step. Per-op autocommit; no
|
|
// multi-op write transactions. OpGroup is best-effort cleanup on failure.
|
|
let block = grp.track(doc.solids().create_cuboid([0.0, 0.0, 0.0], [10.0, 10.0, 10.0]))?;
|
|
let other = grp.track(doc.solids().create_cuboid([5.0, 5.0, 5.0], [10.0, 10.0, 10.0]))?;
|
|
|
|
let lens: Solid = match block.intersect(&other) { // host runs brep::combine
|
|
Ok(l) => { grp.commit(); l }
|
|
Err(e) => { grp.compensate(&doc)?; return Err(e); }
|
|
};
|
|
|
|
// Read-only query batch (one round-trip, no revision bump).
|
|
let (bb, vol) = {
|
|
let r = doc.query_batch(|q| { q.bounds(&lens); q.volume(&lens); })?;
|
|
(r.bounds(0)?, r.volume(1)?)
|
|
};
|
|
api.push_info(&format!("lens vol={vol:.1} bounds={bb:?}"));
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
## What it is
|
|
|
|
- **Object-centric facade** — `DocApi` (root) → `Document` → collections
|
|
(`solids()`, `curves()`, `entities()`) → typed handles (`Solid`, `Line`,
|
|
`Circle`, `Polyline`, `Point`, `Entity`) that carry geometric methods
|
|
(`intersect`/`union`/`subtract`/`transform`/`bounds`/`volume`/`centroid`/`delete`).
|
|
- **Transport-agnostic** — the same typed requests run in-process
|
|
([`InProcess`]) or over the `ocs_plugin_api` IPC channel ([`OcsPluginApiIpc`]);
|
|
only the `Transport` differs. The host executes them through one crate-owned
|
|
executor.
|
|
- **Stable across async IPC** — handles are `Copy`, `Send`, `Sync` `{ObjectId,
|
|
Arc<Session>}` tokens, safe to share across plugin worker threads. A stale
|
|
handle's methods return `ApiError::UnknownId`, never a panic.
|
|
|
|
## Core guarantees
|
|
|
|
- **Per-op autocommit, no write transactions.** Every write op is ONE atomic
|
|
host call = one undo step. Multi-op write transactions were removed after a
|
|
security/integrity review (rollback-correctness of `erase_sources` + unbounded
|
|
synchronous batch execution). There is no `doc.transaction`.
|
|
- **Per-op atomicity.** An op validates its inputs and computes its geometry
|
|
*before* any mutation; a failed op applies nothing and records no undo step.
|
|
`brep::combine` is pure; `erase_sources` runs only on success.
|
|
- **Bulk ops** (`CreateMany`/`TransformMany`/`DeleteMany`, and read-only query
|
|
batching) are single ops, **all-or-nothing via upfront validation** (no
|
|
rollback), item-capped — for bulk data (see `ARCHITECTURE.md` § Performance).
|
|
- **Structured errors** — `ApiError::{Validation, Geometry, UnknownId,
|
|
Unsupported, Transport}`, serializable so IPC returns the same error the
|
|
in-process executor produced.
|
|
|
|
## Scope (v1)
|
|
|
|
- **Solids** — primitives (cuboid, sphere, cylinder, cone, torus, wedge),
|
|
booleans (union/intersect/subtract), transform, and queries
|
|
(bounds/volume/centroid). Host resolves bodies via the `solid_models` cache
|
|
(lift-on-miss from ACIS SAT).
|
|
- **Minimal 2D** — line, circle, polyline, point (construction + bounds); used as
|
|
boolean/sweep inputs.
|
|
- Not yet: sweep/revolve profiles, `AddVertex`, non-solid transforms, annotations,
|
|
containers (insert/block), viewports, media. See `IMPLEMENTATION.md`.
|
|
|
|
## Features
|
|
|
|
| Feature | Pulls | Use |
|
|
|---|---|---|
|
|
| *(default)* | — | Pure data types (ops/query/envelope/errors) — binding generators, tooling |
|
|
| `host` | `acadrust` + `cadkernel` | The executor + `DocApiBackend` trait + in-process transport (the host side) |
|
|
| `ipc` | `acadrust` + `ocs_plugin_api` | The IPC transport adapter (the plugin side) |
|
|
|
|
## For REPL/scripting bridges (Python, Lua, …)
|
|
|
|
The object model is handed over as a single versioned schema
|
|
(`binding_schema_json()`, generated by `build.rs`). A bridge in any language is a
|
|
thin serializer + schema-driven generator + session glue over V4 `ExecuteCode` —
|
|
no Rust FFI. See `bindings/README.md` and `bindings/python/generate.py`.
|
|
|
|
## Links
|
|
|
|
- `ARCHITECTURE.md` — design decisions, the per-op execution model, how to add a
|
|
new entity/family/method, performance notes.
|
|
- `IMPLEMENTATION.md` — what's supported now vs. the phased roadmap.
|
|
- `spec/entities.toml` — the curated source of truth for codegen.
|