CLAUDE.md gains items 18-20 (styles, page setup & templates, environment), the grown drawing_settings facade on item 11, lean 55 / 52 and TOOL_PACKS=all|core,pid,settings, and three conventions: layer states are ours (ACADMCP_LAYERSTATES XRECORD, mode "xrecord"), page-setup evidence is the PDF's own /MediaBox, nothing interprets coordinates in UCS. README token table re-measured on this tree with benchmarks/token_suite.py (ratio/v1 estimates): full 199 tools / 59,315 tokens, core,settings 190 / 55,593, core 168 / 48,825, lean 55 / 16,360, search 2 / 356; corpus re-derived from discovery.aliases: 186 AutoCAD command names, 933 synonym phrases, 204 tools. Styles & standards row, page setup & templates row, final environment wording, and "Known limitations of the settings track". CHANGELOG Unreleased: the track E intro, Added (styles, page setup & templates, environment, drawing_settings growth, benchmark evidence), Fixed (the Track A hardening list), Changed (three groups, packs, measured idle cost, release snapshot 204 · 8 · 5 · 3284). Spec status: implemented. Where the plan's text and the tree disagreed, the tree wins: mleaderstyle_create is implemented on both engines through the ACAD_MLEADERSTYLE dictionary and no `mleaderstyle` capability key exists (spec §4, capability maps), so the docs say so instead of calling it ezdxf-only; the Task 7 fix (tolerant ACADMCP_PID payloads, crossings tested against the arc) joins the hardening list the plan left it out of. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
188 lines
21 KiB
Markdown
188 lines
21 KiB
Markdown
# CLAUDE.md
|
||
|
||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||
|
||
## Project Overview
|
||
|
||
AutoCAD MCP Pro is a FastMCP 3.0 server that exposes ~166 tools, 8 resources, and 5 prompt templates for AutoCAD automation. (The exact tool count is reported dynamically by `system_status` / `system_about` — never hardcode it.) It runs with a dual-engine architecture: a live COM backend (Windows/AutoCAD required) and a headless ezdxf backend (works anywhere).
|
||
|
||
## Running the Server
|
||
|
||
```bash
|
||
# STDIO mode (default, for MCP clients like Claude Desktop)
|
||
python server.py
|
||
|
||
# HTTP mode
|
||
fastmcp run server.py:mcp --transport http --port 8000
|
||
|
||
# Force a specific backend
|
||
AUTOCAD_MCP_BACKEND=ezdxf python server.py # headless file ops
|
||
AUTOCAD_MCP_BACKEND=com python server.py # live AutoCAD (Windows only)
|
||
AUTOCAD_MCP_BACKEND=auto python server.py # auto-detect (default)
|
||
```
|
||
|
||
## Installation
|
||
|
||
```bash
|
||
# Core (ezdxf backend only)
|
||
pip install -e .
|
||
|
||
# With COM backend (Windows + AutoCAD)
|
||
pip install -e ".[com]"
|
||
|
||
# With PDF/screenshot support via matplotlib
|
||
pip install -e ".[pdf]"
|
||
|
||
# Everything
|
||
pip install -e ".[full]"
|
||
```
|
||
|
||
## Running Tests
|
||
|
||
```bash
|
||
pytest
|
||
pytest tests/test_specific.py::test_name # single test
|
||
pytest -x # stop on first failure
|
||
```
|
||
|
||
Uses `pytest-asyncio` for async test support.
|
||
|
||
## Architecture
|
||
|
||
### Dual-Backend Pattern
|
||
|
||
The server uses a strategy pattern with an abstract base:
|
||
|
||
- `backends/contracts/` — the interface, one module per domain (`drawing.py`, `layouts.py`, `entity_creation.py`, …). Split in v1.5.0 out of a single 1854-line ABC; some slices (premium, gdt, settings) also carry shared *concrete* implementations, so they are mixins rather than pure interfaces.
|
||
- `backends/capability.py` — the refusal vocabulary (`UnsupportedCapabilityError`) plus `@capability(key, reason=...)`, which marks a contract method optional and gives it a default that **raises** that refusal. Use it only for genuine engine boundaries, never for an unfinished method; `@abstractmethod` stays the default. Every key used must be declared in *both* capability maps — `tests/test_capability_contract.py` fails otherwise, which is what stops the decorator becoming a way to ship undeclared holes.
|
||
- `backends/base.py` — shared dataclasses (`EntityInfo`, `LayerInfo`, `BlockInfo`, `DrawingInfo`, `CapabilityMap`) and the `AutoCADBackend` composition of every contract. Import surface is unchanged: `from backends.base import AutoCADBackend, UnsupportedCapabilityError` still works.
|
||
- `backends/ezdxf_backend.py` — `EzdxfBackend`: file-based DXF operations using the `ezdxf` library. All sync ezdxf calls are wrapped with `asyncio.to_thread` via `_async()`. Transactions are implemented as full DXF snapshots on an `_undo_stack`.
|
||
- `backends/com_backend.py` — `ComBackend`: live AutoCAD control via `pywin32` COM. All COM calls are routed through a `ThreadPoolExecutor` with a single thread to satisfy AutoCAD's STA (Single-Threaded Apartment) COM requirement.
|
||
|
||
Backend selection at startup (in `server.py::_make_backend`):
|
||
1. Check `AUTOCAD_MCP_BACKEND` env var
|
||
2. On Windows with `auto`/`com`: try COM first, fall back to ezdxf
|
||
3. On non-Windows: always use ezdxf
|
||
|
||
### FastMCP Server Structure (`server.py`)
|
||
|
||
The `mcp` FastMCP instance is configured with:
|
||
- **Lifespan** (`autocad_lifespan`): initializes the backend singleton; stores it in `ctx.lifespan_context["backend"]`
|
||
- **Middleware stack**: `ErrorHandlingMiddleware` → `AuditMiddleware` (custom timing/audit log) → `TimingMiddleware` → `LoggingMiddleware`
|
||
- **`_backend(ctx)`** helper: retrieves the backend from lifespan context, raises `ToolError` if not ready
|
||
|
||
Tools are organized into sections (counts are indicative — `system_about` is authoritative):
|
||
1. Drawing Management (12 tools): `drawing_*` (includes `drawing_redo`)
|
||
2. Entity Creation (14 tools): `entity_create_*` (includes `entity_create_table`, `leader_create_mleader`)
|
||
3. Dimensions (5 tools): `dimension_*`
|
||
4. Entity Modification (16 tools): `entity_move/copy/rotate/scale/mirror/offset/delete/array_*`, corner ops (`entity_trim/extend/fillet/chamfer`), plus in-place `entity_edit_text` (TEXT/MTEXT content/height/rotation) and `entity_edit_geometry` (CIRCLE/LINE/ARC — center/radius/endpoints/angles), both handle-preserving
|
||
5. Entity Query (9 tools): `entity_get`, `entity_list`, `entity_delete_many`, `selection_get`, `selection_window/polygon/filter`, and `entity_get_xdata` / `entity_set_xdata` (typed by group code)
|
||
6. Layer Management (14 tools incl. 2 linetype_*): `layer_*`, `linetype_list`, `linetype_load`
|
||
7. Block Operations (8 tools): `block_*` — includes `block_define` (a block from typed primitive specs + ATTDEFs, no source entities needed)
|
||
8. Analysis & Query (8 tools): `analysis_*` — plus Batch (2), Templates (2), Validation (1)
|
||
9. View & Screenshot (4 tools — includes `view_zoom_and_screenshot`): `view_*`
|
||
10. Transactions (3 tools): `transaction_begin/commit/rollback`
|
||
11. System (8 tools): `system_status/get_variable/set_variable/run_command/run_lisp/about/capabilities`, plus `drawing_settings` (friendly units/scale/precision/osnap facade over system variables; the facade now also covers limits, grid/snap/ortho/polar, PSLTSCALE, annotation scale (`"1:50"`), linear/angular unit formats and the current dimstyle/textstyle, with unknown keys refused and every key reported on read)
|
||
12. Engineering / Deterministic CAD (8 tools): `gear_draw_*`, `keyway_draw_*`, `titleblock_apply_iso_a3`, `drawing_finalize`
|
||
13. Premium meta-tools (12): `drawing_preflight`, `drawing_plan`, `drawing_critique`, `drawing_refine`, `drawing_deliver`, `point_from_snap/intersection/tangent`, `construction_*`, `drawing_apply_iso_layers`, `dimension_auto`, `entity_select_smart`
|
||
14. GD&T (ISO 1101 / ASME Y14.5): `gd_frame` (feature control frames), `datum_feature` — enforced by the `gdt` critique focus
|
||
15. Layouts & Paper Space (12 tools): tab lifecycle `layout_list/create/set_current/delete/rename/copy`, viewports `viewport_create/list/set_scale/lock/delete`, and `entity_change_space` (CHSPACE — tagged `layout` *and* `modify`, and `layout` wins the group priority, so it files here). `drawing_export_pdf` takes an optional `layout` param and **does** project model content through viewports on both engines (`viewport_render` — v1.4 declared this COM-only and was wrong; the headless renderer only omits the viewport borders). `entity_change_space` is the mirror case — ezdxf-only (`chspace`), because ActiveX exposes no change-space member and the COM route is unverified.
|
||
16. 3D Solids (5 tools, opt-in via `ENABLE_3D=true`, COM only): `solid_box/cylinder/extrude/revolve/boolean` — hidden from discovery and rejected while disabled; ezdxf reports `solid_3d` as unsupported (no headless ACIS).
|
||
17. P&ID (9 tools, pack `pid`): `pid_symbol_list/insert` (ISO 10628-2 + ISA-5.1 catalogue authored in code, real blocks with TAG attributes, named ports, ACADMCP_PID XDATA), `pid_line_draw` (port-to-port orthogonal routing, ISA-5.1 line classes → layer/linetype/marker blocks, line numbers), `pid_tag_parse` (ISA-5.1 grammar), the reader `pid_graph` and its deliverables `pid_instrument_index/line_list/equipment_list`, and `pid_from_spec` (whole sheet, one transaction). Six `pid_*` critique focuses (`pid_dangling_line`, `pid_duplicate_tag`, `pid_incompatible_connection`, `pid_untagged_instrument`, `pid_illegal_tag`, `pid_unconnected_equipment`) run under `focus=None` and inside `drawing_finalize`; they return nothing on a drawing without P&ID nodes. Two resources (`autocad://pid/symbols`, `autocad://standards/isa51`) carry the catalogue and the ISA-5.1 letter tables. Both engines implement every P&ID contract; the COM paths were executed live once by `scripts/smoke_pid_com.py` (AutoCAD 2026).
|
||
18. Styles (10 tools, pack `core`): `dimstyle_list/create/modify/set_current` (authored ISO-25 / ANSI presets from `engineering/standards/dimstyles.py`; `dimstyle_modify` accepts only `DIM_VARIABLE_WHITELIST` names (33) and refuses out-of-range values by name before writing; `changed` lists only variables that moved, and `rerender_required: true` says a headless DIMENSION keeps its rendered block until redrawn), `textstyle_list/create/set_current` (`font_resolved: false` is reported, never refused — DXF stores only the name), `mleaderstyle_list/create` (both engines: ezdxf on `doc.mleader_styles`, live through the `ACAD_MLEADERSTYLE` dictionary — ActiveX has no MLeaderStyle *collection*, which is not an engine boundary, so no `mleaderstyle` capability key exists), and `drawing_apply_standard("iso"|"ansi")` — dimstyle + text style + units + the `mech` layer set in one call, each item reporting `created` or already present.
|
||
19. Page setup & templates (6 tools, pack `core`): `page_setup_list/apply` (ISO 216 / ANSI Y14.1 sizes from `PAPER_SIZES`; an unknown ctb is written and reported `plot_style_known: false`), `plot_style_list` (catalog + the files actually installed on a live seat), `batch_plot` (every paper layout through `drawing_export_pdf(layout=)`; `mediabox_mm` is **read back from the PDF's own `/MediaBox`**, so the sheet size is verified, not assumed), `drawing_template_list` (the five bundled templates: `iso_a3_mech`, `iso_a1_arch`, `iso_a3_pid`, `ansi_b_mech`, `ansi_d_arch` — DXF headless, `.dwt` twin on COM, `scripts/build_templates.py --check` proves the DXFs reproducible), `drawing_template_save` (a `.dwt` request headlessly is refused with capability `dwt_write` naming the DXF route). `drawing_new(template=)` resolves a catalog name to the bundled file and reports `template: {name, source}`.
|
||
20. Environment (22 tools, pack `settings`): documents `document_list/activate/close` (both engines — the headless backend keeps a document registry; `document_close` refuses to discard unsaved work unless `discard=True`), layer states `layer_state_save/restore/list/delete` (portable: JSON chunks in an `ACADMCP_LAYERSTATES` XRECORD that travels with the file and **does not appear in AutoCAD's Layer States Manager** — `system_capabilities.layer_states` says so), named views `view_named_save/restore/list` (headless restore writes `$VIEWCTR/$VIEWSIZE` and reports `applied: "header_only"`), UCS `ucs_list/set/restore` (non-orthogonal axes refused with the measured angle; **tool coordinates stay WCS** — a UCS is stored for the operator, no tool interprets input in it), `system_variable_describe` (the 90-entry `SYSVAR_CATALOG`; an unknown name answers `known: false` plus nearest names, never an error; `system_set_variable` now refuses an out-of-range value for a catalogued name before writing), `drawing_properties_get/set` (custom properties on both engines through `$CUSTOMPROPERTYTAG`; the five summary fields are COM-only, capability `dwgprops`), and the live-only group `system_launch` (`live_application`), `system_preferences_get/set` (`preferences`; a whitelist of `Preferences.*` keys with ranges, read-only keys refused by name, writes report `old`/`new`), `user_pick_point/select`, `system_prompt_message` (`interactive_prompt`; a cancelled pick returns `{cancelled: true}`, a `COM_CALL_TIMEOUT` overrun reports `timed_out`). The COM paths were executed live once by `scripts/smoke_settings_com.py` (AutoCAD 2026), which also built the `.dwt` twins.
|
||
|
||
**Tool profiles:** `TOOL_PROFILE=lean|full` (default `full`) controls the advertised surface — `lean` = 55 curated drafting tools (52 with `TOOL_PACKS=core`; the five settings essentials — `drawing_apply_standard`, `dimstyle_set_current`, `textstyle_set_current`, `page_setup_apply`, `batch_plot` — are core-pack tools and stay; including `cad_batch`, since a client with a tight tool cap is exactly the client paying most per turn). Applied in the lifespan; reported by `system_about`. The `core` profile was removed in v1.5.0 (the discovery layer replaced it); `TOOL_PROFILE=core` falls back to `full` with a warning.
|
||
|
||
**Tool packs:** `TOOL_PACKS=all|core,pid,settings` (default `all`) advertises only the vertical packs a client uses; `core` is always on and is everything no other pack claims; `settings` is the 22 SECTION 20 environment tools, `pid` the nine P&ID tools; SECTION 18/19 are core; unknown names are ignored with a warning; a `lean` profile intersects with the enabled packs. `system_about` reports `tool_packs`.
|
||
|
||
**Tool discovery:** `DISCOVERY_MODE=off|search` (default `off`). `search` replaces the advertised catalog with `search_tools` + `call_tool`, ranking hits over an AutoCAD command / synonym corpus (`discovery/aliases.py`) layered on fastmcp's BM25 index.
|
||
|
||
**ISO 129 tolerances:** `dimension_linear` / `dimension_radius` / `dimension_diameter` take `tol_upper` / `tol_lower` / `tol_mode` (`symmetric` ± / `deviation` +a/-b / `limit` / `basic`) and `text_override` (e.g. `⌀20 H7`). Prefer these over hand-drawn tolerance text.
|
||
|
||
**ISO 286 fits:** the same dimension tools take `fit="H7"` (mutually exclusive with `tol_*`) — deviations are resolved from authored ISO 286 tables (`engineering/fits.py`; shafts d/e/f/g/h/js/k/m/n/p, holes D/E/F/G/H/JS, sizes 1–500 mm) against the measured nominal and the fit code is appended to the dimension text.
|
||
|
||
**Drawing score:** `drawing_finalize` returns `payload["score"]` — a 0-100 scalar + `invalidity_ratio` + A-F `grade` over the validator + critique union. Use it as the objective quality metric.
|
||
|
||
**Measuring:** never read vertices back and shoelace them — that loses 28.2% of the area on a semicircular edge, silently. `analysis_measure_entity(handle)` reads the real geometry, and its payload states its own accuracy: `exact`, `flatten_tolerance`, `assumed_closed`, `self_intersecting`. A HATCH reports the area it *fills* (outer loops minus islands) plus `hatch_style`; on a curved hatch edge `flatten_tolerance` is not the accuracy knob, because ezdxf hands boundaries over as cubic Beziers whose ~0.028% circle error is already baked in. `boundary_trace` / `boundary_from_entities` report the same number `analysis_measure_entity` gives for the polyline they just drew — if those ever diverge, one of them is approximating and neither says so.
|
||
|
||
**Benchmark matrix:** `benchmarks/tasks_v4.py` is the current set (17 tasks; `--matrix v3` / `--matrix v2` reproduce the earlier sets). The published competitor reports are pinned v1.4 runs against v2 and are **never** back-filled for the five v3 tasks or the v4 `pid_roundtrip` / `page_setup_truth` — the chart shows them `not run`, not zero. `benchmarks/correctness_suite.py` (32 checks) is the release A/B gate: `python benchmarks/compare_versions.py v1.5.1` (the published report is `benchmarks/results/published/ab-v1.5.1-vs-v1.6.0-dev.json`; `scripts/check_doc_numbers.py` names it in `AB_REPORT`).
|
||
|
||
### Adding a New Tool
|
||
|
||
1. Add the method to the matching module in `backends/contracts/` — `@abstractmethod` if both engines must have it, `@capability("key", reason=...)` only if it is a real engine boundary (and then declare `key` in both capability maps, or the gate fails)
|
||
2. Implement it in `EzdxfBackend` (`backends/ezdxf_backend.py`) using the `_async(func)` wrapper
|
||
3. Implement it in `ComBackend` (`backends/com_backend.py`) using `self._com(func)`
|
||
4. Register the tool in `server.py` with `@mcp.tool(...)` calling `_backend(ctx).your_method(...)`
|
||
5. Use `_dc(result)` to convert dataclass returns to dicts
|
||
|
||
### Key Conventions
|
||
|
||
- **Entity handles**: hex strings (e.g. `"1A2B"`). Always returned from create/copy operations; required by all modify/query operations.
|
||
- **Coordinates**: drawing units (mm by default); angles in degrees, counter-clockwise from X axis.
|
||
**Every coordinate in or out of a tool is WCS on both engines.** DXF stores
|
||
CIRCLE/ARC/LWPOLYLINE/TEXT/INSERT geometry in a per-entity frame (`extrusion`);
|
||
`backends/ocs.py` translates at the backend boundary, so OCS never reaches a
|
||
tool. An entity in a plane tilted out of WCS XY reports `plane_normal` and omits
|
||
the fields xy cannot express; writing an xy into such an entity is refused with
|
||
`capability: "ocs_tilted_plane"` — use `entity_move` instead.
|
||
- **ACI colors**: 1–255 for specific colors, 256=ByLayer, 0=ByBlock.
|
||
- **COM backend only**: `system_run_command`, `system_run_lisp`, and `view_zoom_extents/window` (view ops are no-ops in ezdxf).
|
||
- **Screenshot**: COM uses Win32 window capture (Pillow required); ezdxf renders via matplotlib.
|
||
- **`_dc(obj)`**: converts dataclasses to dicts recursively for JSON serialization.
|
||
- **Engineering layer scaffold**: `drawing_new` auto-bootstraps standard linetypes (CENTER, HIDDEN, PHANTOM) and engineering layers (GEOMETRY, DIM, CENTER, HIDDEN, PHANTOM, HATCH, TEXT, TITLEBLOCK). Pass `bootstrap=False` to opt out.
|
||
- **Generic contracts added in 1.6:** `block_define(name, entities, attdefs)` (typed primitives + ATTDEFs, both engines; `overwrite` replaces contents so INSERTs keep the name) and `entity_get/set_xdata` (typed by group code; 255-char strings, 16 KB per entity, enforced before writing).
|
||
- **P&ID reader rules:** `pid_graph` never modifies the drawing, reads geometry from the drawing (an INSERT's real rotation/scale) rather than the catalogue, and reports `source`/`confidence` per node (catalog 1.0, xdata 0.95, heuristic 0.6, inferred 0.3) — never raised by agreement between heuristics. Read `stats.confidence_min` before trusting a foreign drawing.
|
||
- **`TOOL_PACKS=all|core,pid,settings`** advertises only the packs a client uses; `core` is always on; `system_about` reports `tool_packs`.
|
||
- **Layer states are ours, not AutoCAD's.** `layer_state_*` stores a portable snapshot in an `ACADMCP_LAYERSTATES` XRECORD; it survives save/reopen on both engines and travels with the file, and it is not listed in AutoCAD's Layer States Manager. Said in the tool docstring, in `system_capabilities.layer_states` (`mode: "xrecord"`, reason `portable_acadmcp_xrecord;not_listed_in_autocad_layer_states_manager`) and in the README.
|
||
- **Page-setup evidence is the PDF.** `batch_plot` reports `mediabox_mm` parsed from the file's `/MediaBox` (`engineering/standards/pdfinfo.py`); a page setup is verified by what a printer would get, not by the setter's return value. A style or setting write reports `changed` — re-setting a value to itself is not a change.
|
||
- **Nothing interprets coordinates in UCS.** `ucs_set` / `ucs_restore` store and activate a UCS for the operator's benefit; every tool input and output stays WCS (the repository rule above).
|
||
- **Production drawings**: For real engineering output, use the `engineering/` package primitives via the `gear_*` / `keyway_*` / `titleblock_*` MCP tools — do NOT hand-draw teeth/keyways/sections with raw `entity_create_*` calls. Always end with `drawing_finalize` for the 8-step validator.
|
||
|
||
### Premium Drawing Rules
|
||
|
||
These rules are non-negotiable for production engineering output.
|
||
|
||
1. **Plan before draw**: Call `drawing_plan(intent, scale, sheet)` *before* any
|
||
`entity_create_*`. The returned PlanSpec is held by the backend and replayed
|
||
by `drawing_critique` at finalize time.
|
||
2. **No coordinate guessing**: Never compute snap points (endpoints, midpoints,
|
||
intersections, perpendicular feet) from memory. Use
|
||
`point_from_snap(handle, "end"|"mid"|"center"|"quad"|"perp"|"near", ref_x, ref_y)`.
|
||
3. **Layer discipline**: All geometry on engineering layers (GEOMETRY, HIDDEN,
|
||
CENTER, ...). Construction geometry on `CONSTRUCTION` layer (color 250,
|
||
lightest weight); wipe with `construction_clear()` before finalize.
|
||
4. **Lineweights are ISO 128**: 0.13/0.18/0.25/0.35/0.50/0.70/1.00/1.40/2.00 mm
|
||
only. Use `drawing_apply_iso_layers("mech"|"pid"|"iso13567")` to bootstrap
|
||
correct lineweights per layer; never set lineweight manually outside this set.
|
||
5. **Corners must be explicit**: Two intersecting lines that should meet at a
|
||
sharp corner must use `entity_trim` (with `keep_x/keep_y`) — leaving overshoot
|
||
is reported as `untrimmed_corner` by `drawing_critique`. Rounded/beveled
|
||
corners use `entity_fillet` / `entity_chamfer`.
|
||
6. **Dimensions via auto**: Prefer `dimension_auto(handles, "chain"|"baseline"|
|
||
"ordinate")` over individual `dimension_linear` calls. Manual dimensions only
|
||
for special cases (leader notes, ordinate origins).
|
||
7. **Critique-then-finalize**: `drawing_critique(focus=[...])` must return zero
|
||
issues before `drawing_finalize`. Available focuses (closed enum): `iso128`,
|
||
`layer_color`, `dim_overlap`, `untrimmed_corner`, `duplicate_entities`,
|
||
`construction_left`. Pass `focus=None` for all.
|
||
8. **Meta-tools over raw**: When a meta-tool exists for a workflow, use it; do
|
||
not reimplement with low-level primitives. Same rule as the engineering-
|
||
primitives line above.
|
||
|
||
### Standard Premium Workflow (template)
|
||
|
||
```python
|
||
1. plan = drawing_plan(intent, sheet_size="A3", scale=1.0)
|
||
2. drawing_apply_iso_layers("mech") # or "pid", "iso13567"
|
||
3. construction_xline(...) # scaffolding (optional)
|
||
4. entity_create_line / circle / ... # main geometry on engineering layers
|
||
5. entity_trim / fillet / chamfer # close corners explicitly
|
||
6. handles = entity_select_smart({...}) # avoid handle-memorization
|
||
7. dimension_auto(handles, style="chain") # ISO 129 dims
|
||
8. issues = drawing_critique(focus=None) # must be []
|
||
9. construction_clear() # wipe scaffolding
|
||
10. drawing_finalize(save_path=..., screenshot_path=...)
|
||
```
|