forked from 1b-agent/opencadstudio-upstream
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 <[email protected]>
This commit is contained in:
1 parent
b180dce1a1
commit
8eb85dab8d
7 files changed
+179
-164
No files matched your search
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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.
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in new issue
Block a user