ocs_doc_api: update docs + fix review-drift findings

- lib.rs: export all 14 facade handles (add ArcCurve/Ellipse/Spline/Ray/XLine/Text/
  MText/Dimension + Circle) — public surface matches collection return types.
- build.rs: fix step-1 comment (enums are hand-maintained, not 'regenerated');
  generic_reference_md: Clone (not Copy) + full 14-handle list (was stale 6).
- ops.rs/query.rs: correct 'hand-maintained' module doc comments (were 'GENERATED').
- layouts.json: extend to all 22 Operation + 12 Query variants (was stale phase-1).
- binding_schema.json regenerated (complete layouts + derived handles).
- generate.py: inject 'id': self.id for SetTextContent/SetAttribute/SetViewportView/
  Transform/Delete/AddVertex (was malformed id-less payloads); _apply_op/_apply_query
  accept want=None; extend PY_TYPES (String/&str/Vec returns/new handles).
- README.md: Clone (not Copy) + full scope (2D curves, annotations, containers,
  viewports, media) replacing stale 'Not yet' list.
- Plan (.kilo): mark implemented & published; pointer to live crate docs.
This commit is contained in:
Sebastian committed 2026-09-06 11:22:01 +02:00
1 parent 8417375e5a
commit a9c278e32a
9 files changed
+90 -32

No files matched your search

+15 -5
View File
@@ -41,7 +41,7 @@ fn intersect_solids(api: &DocApi) -> ApiResult<()> {
([`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,
- **Stable across async IPC** — handles are `Clone`, `Send`, `Sync` `{ObjectId,
Arc<Session>}` tokens, safe to share across plugin worker threads. A stale
handle's methods return `ApiError::UnknownId`, never a panic.
@@ -67,10 +67,20 @@ fn intersect_solids(api: &DocApi) -> ApiResult<()> {
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`.
- **Solids** — primitives (cuboid, sphere, cylinder, cone, torus, wedge), sweep
(`extrude`/`revolve` from profiles, `loft` multi-profile), booleans
(union/intersect/subtract), transform, and queries (bounds/volume/centroid).
Host resolves bodies via the `solid_models` cache (lift-on-miss from ACIS SAT).
- **2D curves** — line, circle, arc, polyline (`add_vertex`), point (incl. bulk),
ellipse, spline, ray, xline; construction + bounds + transform (solids & 2D via
`transform`/`transform_many`).
- **Annotations** — `Text`/`MText` (create + `content`/`set_content`), `Hatch`
(create + `boundary` query), linear `Dimension` (create + `measurement`).
- **Containers** — `create_insert` (block references) + `attributes`/`set_attribute`
+ `block_entities` (read-only nested block traversal).
- **Paper-space** — `create_viewport` + `set_view` (retarget/re-zoom) + `viewport_view`.
- **Media** — `create_raster_image`; other media families read-only (`EntityView`).
- See `IMPLEMENTATION.md` for the full capability matrix and phased roadmap.
## Features
+16 -9
View File
@@ -62,8 +62,14 @@ class {handle}:
PY_TYPES = {
"Solid": "'Solid'", "Line": "'Line'", "Circle": "'Circle'",
"Polyline": "'Polyline'", "Point": "'Point'", "Entity": "'Entity'",
"f64": "float", "bool": "bool", "usize": "int",
"[f64; 3]": "tuple", "ObjectId": "ObjectId", "()": "None", "None": "None",
"ArcCurve": "'ArcCurve'", "Ellipse": "'Ellipse'", "Spline": "'Spline'",
"Ray": "'Ray'", "XLine": "'XLine'", "Text": "'Text'", "MText": "'MText'",
"Dimension": "'Dimension'",
"f64": "float", "bool": "bool", "usize": "int", "i32": "int",
"[f64; 3]": "tuple", "[f64; 2]": "tuple", "ObjectId": "ObjectId",
"String": "str", "&str": "str", "()": "None", "None": "None",
"Vec<(String, String)>": "list", "Vec<Vec<[f64; 2]>>": "list",
"([f64; 3], f64)": "tuple", "Vec<Point>": "list", "Vec<ObjectId>": "list",
}
def py_type(rust_ty: str) -> str:
@@ -115,16 +121,17 @@ def render_method(m: Dict[str, Any]) -> str:
if args:
body_items.append(f"'b': {args[0]['name']}.id")
body_items.append(f"'erase_sources': {py_lit((m.get('fixed') or {}).get('erase_sources', True))}")
elif variant in ("GetVolume", "GetCentroid", "GetBounds", "GetEntity"):
elif variant in ("GetVolume", "GetCentroid", "GetBounds", "GetEntity", "GetTextContent", "GetHatchBoundary", "GetDimensionMeasurement", "GetAttributes", "GetViewportView"):
body_items.append("'id': self.id")
elif variant in ("SetTextContent", "SetAttribute", "SetViewportView", "Transform", "Delete", "AddVertex"):
# Op variants that carry the receiver ObjectId: inject it first.
body_items.append("'id': self.id")
for a in args:
body_items.append(f"'{a['name']}': {a['name']}")
elif variant == "GetIntersects":
body_items.append("'a': self.id")
if args:
body_items.append(f"'b': {args[0]['name']}.id")
elif variant == "AddVertex":
body_items.append("'id': self.id")
for a in args:
body_items.append(f"'{a['name']}': {a['name']}")
else:
for a in args:
body_items.append(f"'{a['name']}': {a['name']}")
@@ -151,11 +158,11 @@ class Document:
_transport: Any
_tab: int
def _apply_op(self, op, want):
def _apply_op(self, op, want=None):
env = DocApiEnvelope(ENVELOPE_VERSION, ("Op", op))
return self._transport.apply(env)
def _apply_query(self, q, want):
def _apply_query(self, q, want=None):
env = DocApiEnvelope(ENVELOPE_VERSION, ("Queries", [q]))
return self._transport.apply(env)
+7 -5
View File
@@ -110,9 +110,10 @@ fn main() {
let gen_dir = manifest_dir.join("src/gen");
fs::create_dir_all(&gen_dir).unwrap();
// 1. Regenerate the ops/query enum snapshots (deterministic; these are the
// curated v1 variants — the spec drives docs/schema, the enums are the
// stable wire vocabulary appended at end only).
// 1. Emit the HAND-MAINTAINED ops/query enum snapshots verbatim. The enums are
// NOT derived from the spec here — they are the canonical append-only wire
// vocabulary; the spec's `op`/`query` names must map to variants (asserted
// by a test). `write_if_changed` is effectively a no-op round-trip.
write_if_changed(&gen_dir.join("ops_gen.rs"), &ops_gen_src());
write_if_changed(&gen_dir.join("query_gen.rs"), &query_gen_src());
@@ -149,8 +150,9 @@ fn fmt_args(args: &[Arg]) -> String {
fn generic_reference_md() -> String {
r#"## Generic methods (every handle)
Every typed handle (`Solid`, `Line`, `Circle`, `Polyline`, `Point`, `Entity`) is a
`Copy`, `Send`, `Sync` token `{ ObjectId, session }` and supports:
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
+3 -2
View File
@@ -178,8 +178,9 @@ Table (read-only): GetEntity kind; generic delete. No typed create/bounds in v1.
## Generic methods (every handle)
Every typed handle (`Solid`, `Line`, `Circle`, `Polyline`, `Point`, `Entity`) is a
`Copy`, `Send`, `Sync` token `{ ObjectId, session }` and supports:
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
+19 -2
View File
@@ -410,7 +410,18 @@
"Delete { id: ObjectId }",
"CreateMany(Vec<EntitySpec>)",
"TransformMany { ids: Vec<ObjectId>, placement: PlacementSpec }",
"DeleteMany(Vec<ObjectId>)"
"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> }"
]
},
"Query": {
@@ -420,7 +431,13 @@
"GetCentroid { id: ObjectId }",
"GetVolume { id: ObjectId }",
"GetIntersects { a: ObjectId, b: ObjectId }",
"GetGeometryRevision"
"GetGeometryRevision",
"GetTextContent { id: ObjectId }",
"GetHatchBoundary { id: ObjectId }",
"GetDimensionMeasurement { id: ObjectId }",
"GetAttributes { id: ObjectId }",
"GetBlockEntities { block_name: String }",
"GetViewportView { id: ObjectId }"
]
}
},
+19 -2
View File
@@ -12,7 +12,18 @@
"Delete { id: ObjectId }",
"CreateMany(Vec<EntitySpec>)",
"TransformMany { ids: Vec<ObjectId>, placement: PlacementSpec }",
"DeleteMany(Vec<ObjectId>)"
"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> }"
]
},
"Query": {
@@ -22,7 +33,13 @@
"GetCentroid { id: ObjectId }",
"GetVolume { id: ObjectId }",
"GetIntersects { a: ObjectId, b: ObjectId }",
"GetGeometryRevision"
"GetGeometryRevision",
"GetTextContent { id: ObjectId }",
"GetHatchBoundary { id: ObjectId }",
"GetDimensionMeasurement { id: ObjectId }",
"GetAttributes { id: ObjectId }",
"GetBlockEntities { block_name: String }",
"GetViewportView { id: ObjectId }"
]
}
}
+3 -2
View File
@@ -46,8 +46,9 @@ pub use revision::GeometryRevision;
pub use envelope::{DocApiEnvelope, EnvelopeBody, OpOutcome, Receipt};
pub use facade::{
CurveCollection, DocApi, Document, Entity, EntityCollection, HasId, Line, OpGroup, Point,
Polyline, QueryBatch, QueryResults, Solid, SolidCollection,
ArcCurve, Circle, CurveCollection, Dimension, DocApi, Document, Ellipse, Entity,
EntityCollection, HasId, Line, MText, OpGroup, Point, Polyline, QueryBatch, QueryResults, Ray,
Solid, SolidCollection, Spline, Text, XLine,
};
pub use ops::{BoolOp, Curve2Spec, EntitySpec, Operation, PlacementSpec, SolidPrimitive};
pub use query::{Aabb, EntityView, Query, QueryResult};
+4 -3
View File
@@ -1,7 +1,8 @@
//! Write operations — the typed, transport-agnostic vocabulary for changing the
//! document (plan §5). The `Operation` enum itself is GENERATED into
//! `crate::gen` by `build.rs` from `spec/entities.toml`; this module re-exports
//! it and holds the hand-written payload mirrors.
//! document (plan §5). The `Operation` enum is **hand-maintained** (append-only)
//! in `src/gen/ops_gen.rs` — it is NOT derived from the spec by build.rs; the
//! spec's `op` names must each map to a variant here (asserted by a test). This
//! module re-exports it and holds the hand-written payload mirrors.
use serde::{Deserialize, Serialize};
+4 -2
View File
@@ -1,6 +1,8 @@
//! Read queries — typed, transport-agnostic reads of entity state (plan §5).
//! The `Query` enum is GENERATED into `crate::gen` by `build.rs`; this module
//! re-exports it and holds the hand-written result DTOs.
//! The `Query` enum is **hand-maintained** (append-only) in `src/gen/query_gen.rs`
//! — NOT derived from the spec by build.rs (the spec's `query` names must map to
//! a variant here, asserted by a test). This module re-exports it and holds the
//! hand-written result DTOs.
use serde::{Deserialize, Serialize};