- 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.
ocs_doc_api — binding generator contract
How to build a REPL/scripting bridge to ocs_doc_api in any language
(Python, Lua, JS, …) — no Rust FFI, one versioned schema. This is the binding
handover defined in the plan (§10.2, decision #13).
What you consume
One artifact: gen/binding_schema.json (also exposed as
ocs_doc_api::binding_schema_json()). It is self-contained:
{
"schema": "ocs_doc_api/binding_schema",
"envelope_version": 1,
"object_model": { "root": "DocApi", "document": "Document",
"collections": ["solids","curves","entities"],
"handles": ["Entity","Solid","Line","Circle","Polyline","Point"] },
"families": [ /* per family: handle, collection, methods[] */ ],
"layouts": { /* traced wire layouts for ObjectId / Operation / Query */ }
}
Each families[].methods[] entry:
{ "name": "intersect", "kind": "op", // "op" (write) | "query" (read)
"op": "SolidBoolean", "query": null, // the Operation/Query it maps to
"args": [{ "name": "other", "ty": "Solid" }],
"returns": "Solid",
"bulk": false, "read_only": false,
"fixed": { "op": "Intersection", "erase_sources": true } }
What you implement (3 things)
-
Code generator — read
binding_schema.json, emit language classes:DocApi→document(tab)→solids()/curves()/entities()→ handles (Solid,Line, …). Each generated method builds theOperation/Queryits schema node names (applyingfixedfields) and ships it (step 2). Handles are{ ObjectId, kind }— anObjectIdis a plainu64. -
DocApiEnvelope(de)serializer — the wire unit (bincode over the host's plugin channel). Layout:DocApiEnvelope { version: u16, body: EnvelopeBody } EnvelopeBody = Op(Operation) | Queries(Vec<Query>)A write request carries exactly ONE
Operation; a read request carries one-or-moreQuery(read-only batching is safe). Serialize with bincode semantics matching thelayoutsmap (enum variants are appended at the END only, so discriminants are stable). The host replies with a bincodeResult<Receipt, ApiError>;Receiptcarriesoutcome,query_results,new_revision. -
Session glue — send the envelope over the existing
ocs_plugin_apichannel. The host already exposes the DocApi executor viaPluginRequest::DocApiRequest { tab_id, bytes }→PluginResponse::DocApiResponse { bytes }. A REPL bridge typically rides the V4ExecuteCodeloop (the mechanism theocs_python_replplugin uses) or a directPluginRequestSender.
Errors → language exceptions
Map the structured ApiError to your language's exceptions:
ApiError variant |
meaning |
|---|---|
Validation { op, reason } |
input rejected before any mutation (bulk op names the failing index) |
Geometry { kind, msg } |
the kernel failed (boolean refused, invalid input, ACIS) |
UnknownId(ObjectId) |
the handle is stale/deleted/never existed |
Unsupported(msg) |
capability not implemented (e.g. a later-phase family) |
Transport(msg) |
the channel failed (disconnected/oversized/timeout) |
Version / compatibility rule
- Check
binding_schema.json.envelope_versionagainstDocApiEnvelope.version(currently1) before speaking; a mismatch is a hard bridge error. Operation/Queryvariants are append-only: a bridge built against an older schema keeps working; unknown new variants are simply not generated until you regenerate.
Reference implementation
See bindings/python/generate.py — it reads binding_schema.json and emits
ocs_doc.py with DocApi/Document/Solid/Line/… classes whose methods build
and ship DocApiEnvelope ops. Lua/JS follow the same contract; only the
serializer + codegen differ.
Semantics to honour
- Per-op autocommit: every write method is one atomic host call = one undo step. There are no multi-op write transactions. A failed op applies nothing and records no undo step; earlier ops stay committed (no rollback).
erase_sourcesbooleans replace the first input (a) in place and erase the second (b); the result is returned ata's id.- Bulk ops (
CreateMany/TransformMany/DeleteMany) are single ops, all-or-nothing via upfront validation (no rollback), item-cap100_000. - Query batching is read-only and safe; it never bumps the geometry revision.