docs: final review pass over the Python host documentation

Rewrite the host-model design note for the current architecture (entities, tables,
commands, nested validation, known limits), refresh the plan's current-state header,
the ledger and gaps-doc status lines (cadcodec PR #48), the README status and the
superseded ocs.command notes, and drop dangling references to a sibling project that
is not in this repository.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Felix RiestraandClaude Sonnet 5 committed 2026-09-21 18:27:59 +02:00
1 parent b180dce1a1
commit 8eb85dab8d
7 files changed
+179 -164

No files matched your search

+5 -2
View File
@@ -1,7 +1,10 @@
# cadcodec DXF reader/writer gaps found by the OCS Python host audit
Draft issue reports for `HakanSeven12/cadcodec` (the `acadrust` crate). Nothing
here has been filed. Every finding was made against **cadcodec revision
Reports on `HakanSeven12/cadcodec` (the `acadrust` crate). **Status (21 September
2026):** fixes for issues 1-6 below, with regression tests, are submitted as
[HakanSeven12/cadcodec#48](https://github.com/HakanSeven12/cadcodec/pull/48) (branch
`fix/dxf-reader-writer-roundtrip` on `felixriestra/cadcodec`); the five findings under
"Observed but not filed" and the style and block ones added later are not filed. Every finding was made against **cadcodec revision
`5b682ed`** (OCS pins `acadrust` to it) by saving a document with
`DxfWriter` or `DwgWriter`, reloading it, and comparing entity fields.
+9 -16
View File
@@ -2,20 +2,14 @@
## Goal and current state
Build a general Python document model over the OCS host API for all **43 canvas kinds**. A kind is **complete** only after the per-kind gate below passes. Merely appearing in a converter or allowing a `layer` edit is partial coverage. Keep a property-level record of read/write, read-only, snapshot-only, and unsupported fields in the [43-kind coverage ledger](plugin-host-model-coverage-ledger.md). Do not resume the separate Lisp-to-Python conversion work.
Build a general Python document model over the OCS host API so a script can control OCS: read and change every canvas entity, manage the drawing's tables, and run OCS's own commands. Keep a property-level record of read/write, read-only, snapshot-only, and unsupported fields in the [43-kind coverage ledger](plugin-host-model-coverage-ledger.md). The architecture and guarantees are in [plugin-host-model.md](plugin-host-model.md); the progress log at the end of this file records each increment with its evidence. Do not resume the separate Lisp-to-Python conversion work.
As of 20 September 2026, the working branch is `plugin/host-model-api` at
`a8dd3640` plus the Leader increment, published as draft PR
[#1391](https://github.com/HakanSeven12/OpenCADStudio/pull/1391). Host API is
v7. The Python feature has explicit writes for **43 of 43** canvas kinds: 41
are creatable (the legacy `Polyline` and `Body` are update-only). This is **not** 41 completed
kinds: Tolerance, Shape, AttributeEntity, Hatch, MLine, Dimension, MultiLeader, Table, PolygonMesh, PolyfaceMesh, Mesh, Wipeout, Point, Line, Circle, Arc, Ellipse, LwPolyline, Spline, Text, MText, Ray, XLine, Solid, Face3D, Solid3D, Region, Surface, Ole2Frame and Viewport pass every completion gate; Insert now passes every gate too; the audited Polyline (update-only by decision), Polyline2D and Polyline3D pass every gate that their format allows.
AttributeDefinition passes its host and real IPC lifecycle but remains short of
`Complete` because the previously pinned CAD codec dropped optional ATTDEF
fields during DXF reads; revalidate that blocker against the current
`acadrust` revision `7ea4247`. Leader is mapped and integration-tested but blocked at W by three acadrust persistence gaps (see the ledger). Leader, Helix, Underlay, ViewBorder, Light and SectionSymbol are mapped and integration-tested but blocked at W by acadrust persistence gaps, and RasterImage is held short of `Complete` by unverifiable image-dictionary linkage (see the ledger). RasterImage is mapped and integration-tested, held short of `Complete` by unverifiable image-dictionary linkage. Every canvas kind now has explicit writes; SectionSymbol is mapped and integration-tested but blocked at DXF persistence by cadcodec
writes through the document model. All 43 have raw typed snapshots. There are
three internal records and two opaque fallbacks outside the 43 canvas kinds.
**State as of 21 September 2026.** The working branch is `plugin/host-model-api` (host API v7), backed up on the `felixriestra/OpenCADStudio` fork. The first draft PR, [#1391](https://github.com/HakanSeven12/OpenCADStudio/pull/1391), was closed on 21 September with a note that a more complete version will follow; no new PR will be opened until the work is finished and tested as far as it can be.
- **Entities:** explicit writes for all 43 canvas kinds (41 creatable; the legacy `Polyline` and `Body` are update-only). **31 kinds are `Complete`** (every C/R/E/D/U/I/W/V/P gate passes). The remaining kinds are integration-tested and held short of `Complete` by a named blocker in the ledger: seven by cadcodec DXF bugs (fixes submitted as [cadcodec#48](https://github.com/HakanSeven12/cadcodec/pull/48), not yet merged, so OCS still pins the unfixed revision), Polyline3D by the DWG format, Polyline2D by a kind change on DXF save, RasterImage by an image-reactor structure that cannot be verified, and Body by having no creation path.
- **Drawing tables:** layers, text and dimension styles, blocks and their contents, linetypes and layouts, through the additive `TableOperation` request (see the log entries dated 2026-09-21).
- **Commands:** `HostApi::run_command` drives the real OCS commands step by step; `doc.command`, `doc.start_command` and `doc.modify` build on it.
- **Known open items:** the unfiled cadcodec findings (`cadcodec-reader-gaps.md`), complex (text and shape) linetypes, plot devices and named page setups, PEDIT's remaining options, and an on/off setting for script-driven commands.
Repositories:
@@ -31,9 +25,8 @@ Repositories:
Preserve unrelated untracked files. Host and adapter changes now live in the
same OCS repository and commit. Push the feature branch only to the
`felixriestra/OpenCADStudio` fork; PR #1391 updates automatically. Never push a
feature commit directly to H7's `main`. Verify both upstream `main` and the fork
branch before each push.
`felixriestra/OpenCADStudio` fork, and only when asked. Never push to H7's
repository. Verify both upstream `main` and the fork branch before each push.
## First: complete the originally agreed Phase 8
+3 -1
View File
@@ -1,6 +1,8 @@
# OCS canvas kind coverage ledger
Use with [the completion plan](plugin-host-model-completion-plan.md). This ledger is current as of 20 September 2026: **43 of 43 canvas kinds have explicit geometry writes** (41 creatable, plus update-only legacy `Polyline` and `Body`), **31 kinds are complete**, and none remains layer-only. `Mapped` means at least one non-layer field is exposed for Python writes; it does **not** mean the kind passes the completion gate. `Update-only` means Python creation is unavailable. `Layer-only` means raw inspection and canvas layer edits exist, but geometry is not mapped. Record actual evidence or a blocking issue before changing any status to `Complete`.
Use with [the completion plan](plugin-host-model-completion-plan.md). This ledger is current as of 21 September 2026 (the object tables, block contents and command runner added since are described in [the design note](plugin-host-model.md) and the plan's progress log; they are not entity kinds): **43 of 43 canvas kinds have explicit geometry writes** (41 creatable, plus update-only legacy `Polyline` and `Body`), **31 kinds are complete**, and none remains layer-only. `Mapped` means at least one non-layer field is exposed for Python writes; it does **not** mean the kind passes the completion gate. `Update-only` means Python creation is unavailable. `Layer-only` means raw inspection and canvas layer edits exist, but geometry is not mapped. Record actual evidence or a blocking issue before changing any status to `Complete`.
**Rows blocked at W by cadcodec.** Fixes for six of the DXF bugs behind them are submitted as [HakanSeven12/cadcodec#48](https://github.com/HakanSeven12/cadcodec/pull/48) (ATTDEF fields, boolean group codes for Helix and Light, UNDERLAY rotation, LEADER link and vectors, DRAWINGVIEW/SECTIONLINE dispatch, TABLE merged ranges). OCS still pins the unfixed revision `5b682ed`, so those rows stay blocked and their canaries still assert the wrong result; the expectation is that they flip and the rows move to `Complete` once #48 is merged and OCS repins. That has been checked in cadcodec's own round-trip tests, not yet in OCS.
Completion gate codes: **C** = create through Python document model; **R** = read; **E** = edit defining geometry and another property; **D** = delete; **U** = undo/redo and atomic failure; **I** = real IPC plus GUI/canvas pick or observable behavior; **W** = DWG and DXF save/reopen; **V** = invalid-input checks and untouched-field/reference preservation; **P** = build and load the in-tree bundled plugin with a manifest matching the host toolchain and acadrust source. A kind is `Complete` only when **C/R/E/D/U/I/W/V/P** all pass and its property catalog matches implementation. `Blocked` requires a specific dependency or failing fixture.
+125 -91
View File
@@ -1,99 +1,133 @@
# Scripting host model (API v7)
The host owns the drawing. Plugins can read the full `CadDocument` snapshot and
commit cloned `EntityType` values through IPC. `update_entities_transaction`
validates every replacement before changing the drawing, records one undo
entry, refreshes scene caches and the shared document view, and marks the
document dirty. The request preserves handles, owners, and entity kinds. A
missing or duplicate handle, a kind/owner change, or a locked source layer
rejects the whole batch. An empty batch does nothing.
This note describes how a script controls OCS and what the host guarantees. The
per-kind evidence is in the [coverage ledger](plugin-host-model-coverage-ledger.md),
the increment-by-increment record in the [completion plan](plugin-host-model-completion-plan.md),
and the script-facing reference in `plugins/opencad-python/PLUGIN.md`.
This is a full-entity replacement API, not a promise that every CAD property
is editable through Python. A client should clone the current value and patch
only fields it understands; unknown fields remain intact. The host can read
all `acadrust::EntityType` variants through `document()`. Its generic write
path accepts existing variants subject to the validation above. Entity
creation and deletion still use `add_entity` and `remove_entity` and are not
part of this transaction request.
## Shape
The host embeds a versioned coverage catalog generated from its traced
`EntityType` registry and `crates/ocs_plugin_api/entity_coverage_policy.json`.
It classifies all 48 variants: 43 canvas kinds, three internal records
(`Block`, `BlockEnd`, `Seqend`), and two opaque fallbacks (`Extended`,
`Unknown`). Each property records its source field, type, optional/sequence
shape, snapshot readability, Python access (`read_write`, `read_only`, or
`unmapped`), and validation status. `unmapped` means the typed snapshot carries
the field but the Python document model has no getter or setter for it.
The v7 transaction path checks changed Point locations, Line endpoints,
Circle/Arc centers, Ray/XLine base points, and Solid/Face3D corners for finite
coordinates. Circle/Arc radii must be finite and positive; Ray/XLine directions
must be unit vectors; Solid normals must be nonzero, its thickness finite,
and Face3D invisible-edge flags limited to four known bits. Insert transactions
validate placement, finite rotation and spacing, nonzero finite scale and normal,
and positive array counts. They reject changes to the referenced block and
attached attribute and sequence records. Those fields report `transaction_geometry` in the
catalog. All 43 canvas kinds also support nonempty `layer` changes through
the same undoable transaction; internal and opaque variants remain read-only
through the document model. The adapter accepts only `layer` patches for
canvas kinds outside its 43 geometry converters. Other writable properties
still report `type_conversion_only`; broader CAD range and cross-property
checks remain outstanding.
The host's `entity_snapshot` helper serializes any typed entity as a detached
JSON value keyed by its catalog variant name. This read path carries every
serializable field, including nested and unmapped values, without granting a
write path for them. The Python adapter exposes its variant payload through
`entity.snapshot`; changing that dictionary does not change the drawing.
It is an inspection view: JSON converts non-finite floating-point values to
null and cannot be used as a lossless replacement entity.
Python runs in RustPython inside a separate plugin process
(`plugins/opencad-python`), so a script cannot crash the editor. It talks to the
host over the versioned IPC in `crates/ocs_plugin_api`. **The host owns the
drawing**: it validates every request, records the undo step, refreshes scene
caches and the shared document view, and marks the document dirty. The Python
adapter is thin: it turns dictionaries into typed values and back, and holds no
CAD rules of its own. Everything below is additive to API v7; a plugin built
without it keeps working, and the default `HostApi` methods refuse.
The bundled first-party adapter under `plugins/opencad-python` currently maps
these entity kinds to Python dictionaries: Point, Line, Circle, Arc, Ellipse,
Polyline, Polyline2D, Polyline3D, LwPolyline, Spline, Text, MText, Ray, XLine,
Solid, Face3D, Insert, Tolerance, Shape, AttributeDefinition,
AttributeEntity, and Hatch. An Insert is created by naming an existing
ordinary block (the host refuses unknown blocks, model or paper space and
containment cycles); the block name cannot be changed afterwards. Attached
attributes stay with the AttributeEntity flow and remain in the raw snapshot.
The legacy Polyline is update-only: create a Polyline2D or Polyline3D. The authoritative per-field mapping is
`plugins/opencad-python/entity_manifest.json`. The generated
mapping excludes fields it cannot represent, including common color, line
weight, transparency, Polyline3D smooth type, and MText background color.
Absent geometry fields are not editable through the Python document model.
A feature test compares the generated Python keys with the
host catalog. The adapter's `experimental-host-model` feature requires API
v7 and builds against the repository-relative `ocs_plugin_api`. RustPython is
loaded inside the separate plugin runner process; OCS does not link the Python
runtime into the editor executable.
## What the host offers
API v7 also exposes a synchronous, ordered selection query and an atomic
selection replacement scoped to the session tab. All handles must exist before
replacement begins, duplicate handles are rejected, and the scripted
replacement keeps exactly the requested handles and order. UI picks continue
to group linked leaders and annotations. The V4 notification channel still broadcasts best-effort
document and selection changes. A new `DrawingChanged` notification carries
the scene epoch even when no shared document view is open. A new `CommandStateChanged` notification
reports transitions of the active interactive command at app message
boundaries, with the tab id and command name (or `None` when it ends). A
noninteractive command that starts and finishes within one message does not
produce an active-state transition.
| Area | `HostApi` entry | What it does |
|---|---|---|
| Read | `document()`, `entity_snapshot` | The full `CadDocument` snapshot; a detached JSON view of any typed entity. |
| Entities | `add_entity`, `remove_entity`, `update_entities_transaction` | Create, delete and replace entities. A transaction validates every replacement first and either commits all of them as one undo step or none. |
| Selection | `selection`, `set_selection` | Ordered, tab-scoped, exact (missing or duplicate handles reject the request). |
| Solids | `solid_operation` | Kernel-backed create, transform, booleans, extrude, region and surface from a profile, picture embedding. Every result is verified to lift back from its ACIS payload without loss; a refusal leaves the operands untouched. |
| Tables | `table_operation` | Layers, text and dimension styles, blocks and their contents, linetypes, layouts (see below). |
| Commands | `run_command` | Drives the real OCS command one step at a time (see below). |
| Settings | `system_variable`, `set_system_variable` | `CLAYER`, `SNAPANG`, and a read-only `CTAB` (the current layout). |
The host's existing `InteractiveCommand` machinery collects either a point or
an entity pick. The bundled v7 adapter exposes a token-based request and
poll API because `PY_RUN` uses a fresh Python interpreter per invocation; a
script cannot suspend in place while the user clicks. Enter and command
cancellation report a cancelled result. The host releases the runner's pick
command when it ends, including Escape cancellation. Drawing, selection, and command
notifications are retained in a bounded 256-event queue per tab between runs
and can be drained with `doc.poll_events()`. If a tab exceeds that bound, the
next poll begins with `{"type": "overflow", "dropped": n}`; scripts should
refresh drawing and selection state when they receive it. Closing a tab clears
its pending events, and a script can poll only its active tab.
Input tokens are bound to the drawing tab that requested the pick. Polling a
token from another tab returns no result and does not consume it.
Entity kinds outside a generated schema still read (handle, kind, layer) and take
`layer` changes; nothing is writable that the host has not validated.
Python `doc.entities[handle]` returns a descriptor for every entity. For kinds
outside the 43-kind generated schema it contains only handle, kind, and layer;
`layer` is editable on canvas kinds. `doc.coverage()` enumerates the
catalog; `doc.coverage("Line")` and `line.coverage` return one entry. For
covered kinds, the coverage object lists actual readable and editable
dictionary keys, plus the unmapped snapshot fields and their types.
## Entity validation
The host embeds a coverage catalog generated from its traced `EntityType`
registry and `crates/ocs_plugin_api/entity_coverage_policy.json`. It classifies all
48 variants: 43 canvas kinds, three internal records (`Block`, `BlockEnd`,
`Seqend`) and two opaque fallbacks (`Extended`, `Unknown`), and records for each
property its source field, type, shape, snapshot readability, Python access
(`read_write`, `read_only`, `unmapped`) and validation status.
Creation and edits are validated per kind (finite coordinates, positive radii and
scales, unit directions, non-parallel vectors, in-range flags, reference targets
that exist and have the right kind, and so on). Creation must be valid; an edit
is refused only for problems it introduces, so already-invalid legacy values stay
editable. Derived state is computed by the host, never trusted from a script (MLine
geometry, Dimension measurement, Helix spline, Table merge dimensions, Spline
rational flag, SectionSymbol counts, Viewport id and scale).
Nested records are checked too. A dictionary for a vertex, edge or face may not
carry an unknown key, and the fields that define its geometry are required
(`entity_manifest.json`, `required_struct_fields`); a typo can no longer turn into
a zero coordinate. Everything else in a nested record defaults when absent.
Every kind is exercised through the real plugin runner by a per-kind audit that
checks create, read, edit, delete, undo, invalid input with an atomic rollback,
and a DWG and DXF round trip. A kind is `Complete` in the ledger only when all of
those pass.
## Tables
`TableOperation` is one additive request with typed variants, executed by
`HostSession::table_operation`. Every refusal is decided **before** the undo step is
recorded, so a failure changes nothing and leaves the history alone.
- **Layers:** create, modify, rename (entities follow), delete (refused while it
holds objects unless erasing them is asked for), set current. Layer `0`,
`Defpoints` and the current layer are protected.
- **Text and dimension styles:** create, modify, rename, delete, set current. Dimension
style fields travel as a validated JSON object; handles, xref fields and the name are
host-managed. Renaming a text style updates the dimension styles that name it.
`Standard` and any style that is current or in use are protected.
- **Blocks:** create from entities (copied in shifted by the base point, optionally
erasing the originals), modify, rename (inserts follow), delete (refused while
referenced), and add an entity to a definition through the normal validation path.
A block may not nest itself, directly or through other blocks.
- **Linetypes:** simple dash, gap and dot patterns; rename follows layers and
entities; delete is refused while anything uses it.
- **Layouts:** create with the default page setup and sheet viewport, rename, delete,
switch (through OCS's own layout switch), and page setup (paper size, rotation,
custom scale). `Model` is protected.
Style and layout operations act on the active document, because they use the
application's shared style and view machinery.
## Commands
`run_command` takes a `CommandRequest` (`Run`, `Start`, `Point`, `Text`, `Token`,
`Entity`, `Selection`, `Enter`, `Cancel`) and answers with a `CommandOutcome`:
whether the command is done or waiting, its prompt, what input it accepts, its
keyword options, how many entities it added, tokens nobody asked for, and any
error. It uses the same primitives as the automation channel and finishes each
step synchronously on the host thread, so a step never waits for anything that has
to arrive from outside; an earlier fire-and-forget replay could hang, this does not.
Guards: refused while another command is active or off the active tab; refused for
commands that could end the session or re-enter Python (`QUIT`, `EXIT`, `CLOSE*`,
`NEW`, `QNEW`, `OPEN`, `SAVE*`, `RECOVER`, `SCRIPT`, `RUNSCRIPT`, `PY_*`); an editor or
dialog a command opens is reported and closed by `Cancel`. It does not consult the
automation on/off switch, which governs the external MCP and serve channels; a script
is something the user chose to run. The Python layer cancels any step sequence that
leaves a command waiting.
## Python surface
`ocs.active_document` exposes `entities`, `create_entity`, `delete_entity`,
`transaction`, `selection`, `solids`, `layers`, `text_styles`, `dim_styles`, `blocks`,
`linetypes`, `layouts`, `command`, `start_command` and `modify` (offset, trim, extend,
fillet, chamfer, move, copy, rotate, scale, mirror, erase, rectangular, polar, path and
3-D arrays, explode, join, break, stretch, lengthen and polyline edits). `doc.coverage()`
lists the catalog, and `doc.entities[handle].coverage` one entry, with the readable and
editable keys and the fields the typed snapshot carries but the model does not map.
Interactive picks use a token-based request and poll API because `PY_RUN` uses a
fresh interpreter per invocation and a script cannot suspend while the user clicks.
Drawing, selection and command notifications are kept in a bounded 256-event queue
per tab between runs and drained with `doc.poll_events()`; an overflow is reported
as `{"type": "overflow", "dropped": n}`. Tokens are bound to the tab that asked.
## Known limits
- Twelve of the 43 canvas kinds are not yet `Complete`; each has a named blocker in the
ledger. Seven are cadcodec DXF bugs whose fixes are submitted upstream but not yet adopted.
- The DXF codec also drops some table properties (text style generation flags, block
descriptions, a dimension style's text-style name) and mis-scales one angle; these are
pinned by canaries and listed in `cadcodec-reader-gaps.md`.
- Not available from a script: complex (text and shape) linetypes, plot devices and named
page setups, PEDIT's fit and spline options, interactive-only commands such as HATCH, and
a setting to turn script-driven commands off.
- A command step nests inside the editor's message handling; in a debug build a deep step
(PEDIT converting a line to a polyline) needs more than 2 MiB of stack, which the
application's 8 MiB main thread provides and a release build needs far less of.
+14 -36
View File
@@ -117,8 +117,8 @@ expression, not statements). Phases 1.2 (read), 1.3 (2D write) and part of
| `ocs.read_record(handle, app_name)` | Returns `{app_name, values}` for the XDATA record `app_name` on `handle`, or `None` if the entity or the record doesn't exist. See "XDATA" below for the `values` shape. |
| `ocs.write_record(handle, app_name, values)` | Attaches an XDATA record to `handle` for `app_name`, replacing any existing record for that application. Raises if `handle` doesn't exist. |
| `ocs.remove_record(handle, app_name)` | Removes the XDATA record for `app_name` from `handle`, if any. Returns `True` if a record was actually removed. |
| `ocs.command(cmd)` | Experimental only; absent from the portable default build. Requires unreleased host API support and can hang on OCS 2026.37. See historical notes below. |
| `ocs.select(handles)` | Experimental only; absent from the portable default build. Requires unreleased host API support. |
| `ocs.command(cmd)` | Superseded. Only the `experimental-command-replay` build has it (the old fire-and-forget replay). Use `doc.command`, `doc.start_command` and `doc.modify` from the document model (see "Running OCS commands" below). |
| `ocs.select(handles)` | Replaces the selection with exactly these handles, in order (document-model build); `doc.selection = [...]` is the same. |
| `ocs.system_variable(name)` / `ocs.set_system_variable(name, value)` | Development-only `experimental-host-settings` feature. Reads or sets host-managed CLAYER (text) and SNAPANG (degrees) without nested command dispatch. Requires the Felix OCS fork's `plugin/host-model-api` branch; absent from the portable default build. |
`add_line`/`add_circle`/`add_arc` take plain Python numbers —
@@ -325,10 +325,13 @@ is the raw single step behind all of this.
### Historical command-replay experiment (not in the default build)
The following records an earlier experiment. The bundled build does **not**
expose `ocs.command()` or `ocs.select()`. An OCS 2026.37 diagnostic found
nested `ocs.command("CLAYER ...")` could hang, so these
examples are not instructions to run against the released host.
The following records an earlier experiment and is kept for the findings, not as
instructions. It used a fire-and-forget `ocs.command()` that replayed a line through the
message loop; on OCS 2026.37 that could hang. It has been superseded by
`doc.command` / `doc.start_command` (see "Running OCS commands" above), which run the
real command synchronously through the host's `run_command` step API and were exercised
against every command tried without a hang. The prompt-sequence findings below (selection
prompts, `ALIGN`'s extra Enters) still describe how the commands behave.
**`ocs.command()` + `ocs.select()` together can apply a real, persistent
constraint, entirely from a script** — this required two host changes
@@ -504,37 +507,12 @@ Script…` (above) is the most a plugin can do today: reuse the host's own
native file picker via the existing `PluginFileDialog` event, which was
already there for other plugins to import files.
## lisp2py — AutoLISP macro transpiler
## AutoLISP migration
`../lisp2py/` (an independent sibling workspace — zero dependency on this
crate or `ocs_plugin_api`) is a **best-effort, offline, source-to-source**
translator from AutoLISP (`.lsp`) to the `ocs.*` Python dialect documented
above. Not an interpreter: it never executes AutoLISP, and its output is a
draft a human reviews and finishes, then runs through `PY_RUN` like any
other script. This is Phase 2.2/2.3 of `ROADMAP.md` — see
[`../lisp2py/README.md`](../lisp2py/README.md)
for the full pipeline, the corpus-measured builtin coverage, and every
known limitation; this is just the quick-start.
```sh
cargo run --manifest-path ../lisp2py/Cargo.toml --bin ocs-lisp2py -- macro.lsp > macro.py
# then, inside OCS:
PY_RUN macro.py
```
(Or pipe source on stdin: `cat macro.lsp | cargo run --manifest-path ../lisp2py/Cargo.toml --bin
ocs-lisp2py -- > macro.py`.)
Anything the translator can't confidently map — an unrecognized builtin, a
construct with no Python equivalent — becomes a `_todo_manual_port(...)`
call in the generated file (raises `NotImplementedError` if actually run)
plus a warning printed to stderr, never a silent guess. `vla-*`/`vlax-*`
(ActiveX/COM) and `vlr-*` (reactors) are explicitly out of scope, for the
same reason as everywhere else in this plugin's design: COM doesn't run
under AutoCAD for Mac either. Builtin coverage was widened using measured
call-frequency data from a real AutoLISP corpus sample rather than
guesswork, biased toward 2D geometry (`angle`/`distance`/`polar`) over
3D/solid-modeling per current project priority.
An AutoLISP-to-Python translator is a separate project and is not part of this
repository: scripts written for this plugin use the `ocs` module and the document
model described above, and a translated macro would run through `PY_RUN` like any
other script. Nothing in the plugin depends on it.
## XDATA
+16 -7
View File
@@ -17,13 +17,22 @@ found while building this: `PLUGIN.md` in this directory.
## Status
The API v7 build provides `PY_EVAL`/`PY_RUN`, the read/write `ocs` module, and
the ribbon integration; these have live OCS 2026.37 tests. Experimental
`ocs.command()`/`ocs.select()` constraint work
is documented in `PLUGIN.md` and `examples/example.py`, but those functions
are not in the default build: their host APIs are unreleased, and nested
command replay can hang. The general document model is built through the
`experimental-host-model` feature until that name is retired in a later API
cleanup.
the ribbon integration. On top of that, the `experimental-host-model` feature
(the name stays until a later API cleanup; the staged bundled plugin is built with
it) adds a general Python document model over the host API:
- **Entities:** create, read, edit, delete and undo for all 43 canvas kinds
(41 creatable), validated by the host, with per-kind DWG/DXF round-trip audits.
- **Drawing tables:** `doc.layers`, `text_styles`, `dim_styles`, `blocks` (and their
contents), `linetypes` and `layouts`, plus kernel-backed solids (`doc.solids`).
- **Commands:** `doc.command`, `doc.start_command` and `doc.modify` (offset, trim,
extend, fillet, chamfer, move, copy, rotate, scale, mirror, arrays, explode, join,
break, stretch, lengthen, PEDIT polyline edits) drive the real OCS commands.
The design and guarantees are in `../../docs/plugin-host-model.md`, the per-kind
evidence in `../../docs/plugin-host-model-coverage-ledger.md`, and the script-facing
reference in `PLUGIN.md`. The older experimental `ocs.command()` replay is
superseded and not in the default build.
## Files
+7 -11
View File
@@ -1,13 +1,10 @@
# Open CAD Studio — Python Scripting & AutoLISP Migration Plan
**Status:** Phase 1 done and host-verified; Phase 2 (the `lisp2py`
transpiler) has progressed well past what's checked off below — see
[`../lisp2py/README.md`](../lisp2py/README.md) and
[`../../PLUGIN_DEVELOPMENT_RECIPE.md`](../../PLUGIN_DEVELOPMENT_RECIPE.md) for
current status. Kept here as the original planning document and Phase 1
build log, moved from `OpenCADStudio`'s `docs/` (where it was originally
drafted, before this repo held the plugin work) unchanged except for
cross-repo reference fixes below.
**Status:** Phase 1 is done and host-verified, and the general document model that
followed (entities, drawing tables, block contents and the command runner) is described in
[`../../docs/plugin-host-model.md`](../../docs/plugin-host-model.md). Phase 2 (an AutoLISP
translator) is a separate project that is not part of this repository. This file is kept as
the original planning document and Phase 1 build log.
**Author:** Open CAD Studio contributors
**Date:** September 2026
@@ -305,8 +302,7 @@ cost.
## Phase 2 — AutoLISP → Python parser/translator
**See [`../lisp2py/`](../lisp2py) — this phase has its own crate, README,
and builtin-coverage writeup now.** The sub-goals below are the original
**This phase lives in a separate project that is not part of this repository.** The sub-goals below are the original
scoping; §2.2/2.3 shipped with substantially wider builtin coverage than
first planned (measured from a real AutoLISP corpus sample, not just the
constructs listed here).
@@ -347,7 +343,7 @@ them).
- [x] `getvar`/`setvar` — confirmed no host system-variable surface exists;
documented as unsupported rather than guessed at.
- [x] Widened well past this original list via measured corpus frequency —
see `lisp2py`'s own README for the full table.
see that project's own README for the full table.
### 2.4 — Validation loop