Merge PR #1028: add document API with atomic edits

Add the typed document facade and plugin IPC adapter. Prepare bulk edits
before mutation, preserve undo and dirty state, keep profile elevations,
validate numeric inputs and session bindings, and generate Python constructors
with typed results.

Reuse existing entity transforms and profile conversion. Compute mass
properties and closed-surface bounds in cadkernel; pin its main revision
6a5e6fc2822f6af6b2611cd605b44437b35158f0. Keep plugin API metadata at version 6.

Validation: 646 native application tests, 597 kernel tests, 12 document API
tests, 91 plugin API tests, 3 Python tests, default/IPC feature builds and
wasm32 check passed.
This commit is contained in:
Hakan Seven committed 2026-09-06 21:43:02 +03:00
commit 3385160493
50 files changed
+11481 -6

No files matched your search

+323
View File
@@ -0,0 +1,323 @@
# ocs_doc_api — API reference (auto-generated)
Envelope version: `1`
Do NOT edit: regenerated by `build.rs` from `spec/entities.toml`.
## `Solid` (acadrust `Solid3D`, collection `solids`)
B-rep solid. Host resolves bodies via the solid_models cache (lift-on-miss from ACIS SAT).
### Constructors (`doc.solids()`)
- **create_cuboid**(origin: [f64; 3], size: [f64; 3]) -> `Solid` — `CreateSolid`; fixed primitive="Cuboid"
- **create_sphere**(centre: [f64; 3], radius: f64) -> `Solid` — `CreateSolid`; fixed primitive="Sphere"
- **create_cylinder**(base: [f64; 3], radius: f64, height: f64) -> `Solid` — `CreateSolid`; fixed primitive="Cylinder"
- **create_cone**(base: [f64; 3], radius: f64, height: f64) -> `Solid` — `CreateSolid`; fixed primitive="Cone"
- **create_torus**(centre: [f64; 3], major_radius: f64, minor_radius: f64) -> `Solid` — `CreateSolid`; fixed primitive="Torus"
- **create_wedge**(origin: [f64; 3], size: [f64; 3]) -> `Solid` — `CreateSolid`; fixed primitive="Wedge"
- **extrude**(profile: EntityRef, direction: [f64; 3]) -> `Solid` — `Extrude`
- **revolve**(profile: EntityRef, pivot: [f64; 3], axis: [f64; 3], angle: f64) -> `Solid` — `Revolve`
- **loft**(profiles: &[EntityRef]) -> `Solid` — `Loft`
### Methods
- **intersect**(other: Solid) -> `Solid` — op `SolidBoolean`; fixed erase_sources=true, op="Intersection"
- **union**(other: Solid) -> `Solid` — op `SolidBoolean`; fixed erase_sources=true, op="Union"
- **subtract**(other: Solid) -> `Solid` — op `SolidBoolean`; fixed erase_sources=true, op="Difference"
- **volume**() -> `f64` — query `GetVolume`
- **centroid**() -> `[f64; 3]` — query `GetCentroid`
- **intersects**(other: Solid) -> `bool` — query `GetIntersects`
## `Line` (acadrust `Line`, collection `curves`)
### Constructors (`doc.curves()`)
- **create_line**(start: [f64; 3], end: [f64; 3]) -> `Line` — `CreateCurve`; fixed curve="Line"
## `Circle` (acadrust `Circle`, collection `curves`)
### Constructors (`doc.curves()`)
- **create_circle**(centre: [f64; 3], radius: f64) -> `Circle` — `CreateCurve`; fixed curve="Circle"
## `Polyline` (acadrust `LwPolyline`, collection `curves`)
### Constructors (`doc.curves()`)
- **create_polyline**(points: &[[f64; 3]], closed: bool) -> `Polyline` — `CreateCurve`; fixed curve="Polyline"
### Methods
- **add_vertex**(at: usize, point: [f64; 3]) -> `()` — op `AddVertex`
## `Point` (acadrust `Point`, collection `curves`)
### Constructors (`doc.curves()`)
- **create_point**(position: [f64; 3]) -> `Point` — `CreateCurve`; fixed curve="Point"
- **create_points**(positions: &[[f64; 3]]) -> `Vec<Point>` *(bulk op)* — `CreateMany`; fixed curve="Point"
## `ArcCurve` (acadrust `Arc`, collection `curves`)
### Constructors (`doc.curves()`)
- **create_arc**(centre: [f64; 3], radius: f64, start_angle: f64, end_angle: f64) -> `ArcCurve` — `CreateCurve`; fixed curve="Arc"
## `Ellipse` (acadrust `Ellipse`, collection `curves`)
### Constructors (`doc.curves()`)
- **create_ellipse**(centre: [f64; 3], major_axis: [f64; 3], ratio: f64, start: f64, end: f64) -> `Ellipse` — `CreateCurve`; fixed curve="Ellipse"
## `Spline` (acadrust `Spline`, collection `curves`)
### Constructors (`doc.curves()`)
- **create_spline**(degree: i32, control_points: &[[f64; 3]], knots: &[f64], weights: &[f64]) -> `Spline` — `CreateCurve`; fixed curve="Spline"
## `Ray` (acadrust `Ray`, collection `curves`)
### Constructors (`doc.curves()`)
- **create_ray**(origin: [f64; 3], direction: [f64; 3]) -> `Ray` — `CreateCurve`; fixed curve="Ray"
## `XLine` (acadrust `XLine`, collection `curves`)
### Constructors (`doc.curves()`)
- **create_xline**(origin: [f64; 3], direction: [f64; 3]) -> `XLine` — `CreateCurve`; fixed curve="XLine"
## `Text` (acadrust `Text`, collection `curves`)
Single-line text annotation. content()/set_content() read+replace the value; transform moves insertion point.
### Constructors (`doc.curves()`)
- **create_text**(value: &str, insertion_point: [f64; 3], height: f64, rotation: f64) -> `Text` — `CreateText`
### Methods
- **content**() -> `String` — query `GetTextContent`
- **set_content**(value: &str) -> `()` — op `SetTextContent`
## `MText` (acadrust `MText`, collection `curves`)
Multi-line text annotation. content()/set_content() read+replace the value; transform moves insertion point.
### Constructors (`doc.curves()`)
- **create_mtext**(value: &str, insertion_point: [f64; 3], height: f64) -> `MText` — `CreateMText`
### Methods
- **content**() -> `String` — query `GetTextContent`
- **set_content**(value: &str) -> `()` — op `SetTextContent`
## `Entity` (acadrust `Hatch`, collection `curves`)
Filled region over a closed polyline boundary. boundary() returns the loops; bounds from boundary; generic delete.
### Constructors (`doc.curves()`)
- **create_hatch**(boundary: &[[f64; 2]], solid: bool) -> `Entity` — `CreateHatch`
### Methods
- **boundary**() -> `Vec<Vec<[f64; 2]>>` — query `GetHatchBoundary`
## `Dimension` (acadrust `Dimension`, collection `curves`)
Linear dimension between two measured points. measurement() reads the distance; bounds from the measured + definition points.
### Constructors (`doc.curves()`)
- **create_dimension_linear**(first_point: [f64; 3], second_point: [f64; 3], definition_point: [f64; 3]) -> `Dimension` — `CreateDimensionLinear`
- **create_dimension_radius**(center: [f64; 3], point: [f64; 3]) -> `Dimension` — `CreateDimensionRadius`
- **create_dimension_diameter**(center: [f64; 3], point: [f64; 3]) -> `Dimension` — `CreateDimensionDiameter`
- **create_dimension_angular**(vertex: [f64; 3], first_point: [f64; 3], second_point: [f64; 3], arc_location: [f64; 3]) -> `Dimension` — `CreateDimensionAngular`
- **create_dimension_angular2ln**(vertex: [f64; 3], first_point: [f64; 3], second_point: [f64; 3], arc_location: [f64; 3]) -> `Dimension` — `CreateDimensionAngular2Ln`
### Methods
- **measurement**() -> `f64` — query `GetDimensionMeasurement`
## `Entity` (acadrust `Insert`, collection `entities`)
Block reference. Place an existing BlockRecord by name; transform moves its insert point.
### Constructors (`doc.entities()`)
- **create_insert**(block_name: &str, insert_point: [f64; 3], scale: f64, rotation: f64) -> `Entity` — `CreateInsert`
### Methods
- **attributes**() -> `Vec<(String, String)>` — query `GetAttributes`
- **set_attribute**(tag: &str, value: &str) -> `()` — op `SetAttribute`
## `Entity` (acadrust `AttributeDefinition`, collection `entities`)
In-block attribute definition (ATTDEF): tag/prompt/default at an insertion point.
### Constructors (`doc.entities()`)
- **create_attribute_definition**(tag: &str, prompt: &str, default_value: &str, insertion_point: [f64; 3], height: f64, rotation: f64) -> `Entity` — `CreateAttributeDefinition`
## `Entity` (acadrust `Viewport`, collection `entities`)
Paper-space viewport: a width×height window at center looking at view_target with view_height zoom. bounds = center ± w/2,h/2; transform moves center.
### Constructors (`doc.entities()`)
- **create_viewport**(center: [f64; 3], width: f64, height: f64, view_target: [f64; 3], view_height: f64) -> `Entity` — `CreateViewport`
### Methods
- **viewport_view**() -> `([f64; 3], f64)` — query `GetViewportView`
- **set_view**(view_target: [f64; 3], view_height: f64) -> `()` — op `SetViewportView`
## `Entity` (acadrust `RasterImage`, collection `entities`)
Raster image: create_raster_image(file_path, insertion, u, v, size) places an image (host auto-registers the ImageDefinition); bounds (4-corner bracket); delete.
### Constructors (`doc.entities()`)
- **create_raster_image**(file_path: &str, insertion_point: [f64; 3], u_vector: [f64; 3], v_vector: [f64; 3], size: [f64; 2]) -> `Entity` — `CreateRasterImage`
## `Entity` (acadrust `Table`, collection `entities`)
Table: create_table(insertion_point, data[row][col]) builds rows x columns of text cells; non-rectangular grid -> Validation; delete.
### Constructors (`doc.entities()`)
- **create_table**(insertion_point: [f64; 3], data: &[Vec<String>]) -> `Entity` — `CreateTable`
## Generic methods (every handle)
Every typed handle (`Solid`, `Line`, `Circle`, `Polyline`, `Point`, `ArcCurve`,
`Ellipse`, `Spline`, `Ray`, `XLine`, `Text`, `MText`, `Dimension`, `Entity`) is a
`Clone`, `Send`, `Sync` token `{ ObjectId, session }` and supports:
- **`id()` -> `ObjectId`** — the stable entity identity.
- **`bounds()` -> `Aabb`** — coarse bounding box (solids lift-on-miss; 2D entities
computed; later families may be `Unsupported`).
- **`transform(placement)`** — in-place rigid similarity (same `ObjectId`). **v1:
solids only.**
- **`delete()`** — remove the entity (one undo step).
`Entity` additionally has `view()` (id + kind + bounds) and `as_solid()` (typed
downcast when `kind == "Solid3D"`).
## Collections & cross-cutting
- **`DocApi`** — root: `document(tab)`, `active_tab()`, `alive()`.
- **`Document`** — `solids()` / `curves()` / `entities()` factories + lookup,
`revision()`, `assert_revision(rev)` (read-guard), `query_batch(|q| …)` (read-only
batch, one round-trip, **no revision bump**).
- **`EntityCollection`** — `get(id) -> Entity`, `delete(id)`,
`transform_many(&ids, placement)`, `delete_many(&ids)`.
- **`OpGroup`** — best-effort client-side failure cleanup (`track` / `commit` /
`compensate`). **Not** a transaction: no atomicity, no rollback.
## Bulk ops — single op, all-or-nothing, item-capped
`CreateMany` / `TransformMany` / `DeleteMany` are **single** write ops: one undo
step, one publish, and **all-or-nothing via upfront validation** (no rollback).
Item-capped at `BULK_ITEM_CAP = 100_000` to bound host execution. Use them for
bulk data (N ≳ a few hundred); per-op autocommit is the right tool for
modeling-scale work.
## Semantics
- **Per-op autocommit, no write transactions.** Every write op is ONE atomic host
call = one undo step. Multi-op write transactions were removed (security/
integrity review).
- **Per-op atomicity.** Validate + compute geometry **before** any mutation; a
failed op applies nothing and records no undo step. `erase_sources` booleans run
only on success.
- **Structured errors.** `ApiError::{Validation, Geometry, UnknownId, Unsupported,
Transport}`; bulk validation names the failing index; a stale handle returns
`UnknownId` (never a panic).
## Kernel mapping
| op | kernel call | returns |
|---|---|---|
| CreateSolid.Cuboid | `cadkernel::brep::make::cuboid` | Option<Body> |
| CreateSolid.Sphere | `cadkernel::brep::make::sphere` | Option<Body> |
| SolidBoolean | `cadkernel::brep::combine` | Result<Body, Snag> |
| Transform | `cadkernel::brep::transform` | Option<Body> |
| GetBounds | `cadkernel::brep::body_bounds` | Option<Aabb> |
| GetVolume | `cadkernel::brep::mesh_body + divergence` | computed |
## Wire vocabulary
Every `Operation` is ONE atomic write op (one undo step); every `Query` is read-only (safe to batch). Variants are append-only (bincode discriminant stability). Requests ride `DocApiEnvelope`: `Op(Operation)` for a write, `Queries(Vec<Query>)` for a read batch.
### `Operation`
- `CreateSolid(SolidPrimitive)`
- `CreateCurve(Curve2Spec)`
- `Extrude { profile: ObjectId, direction: [f64; 3] }`
- `Revolve { profile: ObjectId, axis: ([f64; 3], [f64; 3]), angle: f64 }`
- `Transform { id: ObjectId, placement: PlacementSpec }`
- `SolidBoolean { op: BoolOp, a: ObjectId, b: ObjectId, erase_sources: bool }`
- `AddVertex { id: ObjectId, at: usize, point: [f64; 3] }`
- `Delete { id: ObjectId }`
- `CreateMany(Vec<EntitySpec>)`
- `TransformMany { ids: Vec<ObjectId>, placement: PlacementSpec }`
- `DeleteMany(Vec<ObjectId>)`
- `CreateInsert(InsertSpec)`
- `CreateViewport(ViewportSpec)`
- `CreateText(TextSpec)`
- `CreateMText(MTextSpec)`
- `SetTextContent { id: ObjectId, value: String }`
- `CreateHatch(HatchSpec)`
- `CreateDimensionLinear(DimensionSpec)`
- `SetAttribute { id: ObjectId, tag: String, value: String }`
- `SetViewportView { id: ObjectId, view_target: [f64; 3], view_height: f64 }`
- `CreateRasterImage(RasterImageSpec)`
- `Loft { profiles: Vec<ObjectId> }`
- `CreateDimensionRadius(DimensionRadialSpec)`
- `CreateDimensionDiameter(DimensionRadialSpec)`
- `CreateDimensionAngular(DimensionAngularSpec)`
- `CreateAttributeDefinition(AttributeDefinitionSpec)`
- `CreateTable(TableSpec)`
- `CreateDimensionAngular2Ln(DimensionAngularSpec)`
### `Query`
- `GetEntity { id: ObjectId }`
- `GetBounds { id: ObjectId }`
- `GetCentroid { id: ObjectId }`
- `GetVolume { id: ObjectId }`
- `GetIntersects { a: ObjectId, b: ObjectId }`
- `GetGeometryRevision`
- `GetTextContent { id: ObjectId }`
- `GetHatchBoundary { id: ObjectId }`
- `GetDimensionMeasurement { id: ObjectId }`
- `GetAttributes { id: ObjectId }`
- `GetBlockEntities { block_name: String }`
- `GetViewportView { id: ObjectId }`
## Errors (`ApiError`)
| Variant | Meaning |
|---|---|
| `Validation { op, reason }` | Input rejected before any document mutation. |
| `Geometry { kind, msg }` | The kernel failed (boolean refused, invalid input, ACIS lift/lower). |
| `UnknownId(ObjectId)` | The handle is stale / deleted / never existed (never a panic). |
| `Unsupported(String)` | Capability not supported for this entity or backend. |
| `Transport(String)` | The channel failed (disconnected / oversized / timeout). |
`ApiError` is serializable, so IPC returns the same structured error the in-process
executor produced.
## Envelope & transport
`DocApiEnvelope { version, body }` — `body` is `Op(Operation)` (a single write) or
`Queries(Vec<Query>)` (a read batch). bincode-serialized; `version` is the crate's
envelope version (bridges must check it). Transports: `InProcess` (host) and
`OcsPluginApiIpc` (plugin over `ocs_plugin_api`), both behind the `Transport:
Send + Sync` trait (`apply(envelope) -> Receipt`, `alive()`). `Receipt` carries
`outcome` (per-op result), `query_results`, and `new_revision`.