Files
autocad-mcp-pro/docs/superpowers/specs/2026-09-16-v1.6-settings-design.md
T
Umut CANandClaude Opus 5 e204f227b4 fix(task-10): $DIMBLK header carries the canonical arrowhead name; MLeaderStyle is authored on COM through the ACAD_MLEADERSTYLE dictionary
Two review findings, both root causes.

1. `_sync_header_to_dimstyle` copied the DIMSTYLE entry's ezdxf-internal
   arrowhead spelling (OBLIQUE) into the DXF-facing $DIMBLK/$DIMBLK1/$DIMBLK2
   header, so the saved file named a block it did not hold (the blocks are
   _OBLIQUE / _DOT) and `system_get_variable("DIMBLK")` answered "OBLIQUE"
   headlessly against "_OBLIQUE" on COM and in the same engine's own
   `dimstyle_list` row. The header write now goes through
   `reported_arrowhead` — the same read-side rule `_dimstyle_value` applies.
   Pinned by header, `system_get_variable`, save/reopen (`ezdxf.readfile`
   header equals the block the file holds) and the Restore-to-Standard
   empty-string case in `test_builtin_arrowheads_render_save_and_report_one_spelling`.

2. The `mleaderstyle` capability refusal rested on a false engine boundary.
   acax25enu.tlb (AutoCAD 2026) exposes IAcadMLeaderStyle with Name,
   ArrowSize, LandingGap, TextHeight, TextStyle, ... — exactly what
   `Dictionaries.Item("ACAD_MLEADERSTYLE").Item(i)` returns — and
   IAcadDictionary.AddObject(keyword, "AcDbMLeaderStyle") creates one.
   COM `mleaderstyle_list` now reads the four values off those objects
   (`values_available: True`); COM `mleaderstyle_create` validates every value
   (`validate_mleaderstyle`, shared with ezdxf), checks the clash and the text
   style before AddObject, then writes the four properties. The contract
   method is `@abstractmethod` again, the key is gone from both capability
   maps, the fake ActiveX surface models the real call shape, and the spec
   (§4.2, §8.1, §9) states the ActiveX route.

Live AutoCAD was not running on this machine (GetActiveObject: operation
unavailable; no acad.exe), so the COM route is type-library-verified and
fake-tested, not executed live — scripts/smoke_settings_com.py (Task 24)
owns that.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
2026-09-21 16:48:20 +03:00

271 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# v1.6 Track E — AutoCAD settings and environment
**Date:** 2026-09-16 · **Status:** design approved in conversation; plan at
`docs/superpowers/plans/2026-09-16-v1.6-settings-implementation.md` · **Parent:**
`2026-09-15-v1.6-roadmap-design.md` (track E) · **Baseline:** main after
Track A (166 tools, 8 resources)
## 1. Summary
v1.5 lets a model read and write raw system variables and a friendly
`drawing_settings` facade, and nothing else about the environment a drawing
lives in: no dimension/text/leader styles (every dimension inherits
`Standard`), no page setup (a PDF is whatever the renderer's default is), no
templates beyond a layer list, one document at a time, no named views, UCS or
layer states, and no way to ask AutoCAD itself anything (preferences, document
properties, a point picked on screen).
This track adds those as **38 tools** in three new sections, on both engines
wherever a headless file can hold the result, with an honest `@capability`
refusal where only a live AutoCAD can (preferences, document properties'
summary fields, launching, prompting the operator). Authored standards data —
ISO-25 and ANSI dimension styles, text styles, five drawing templates built by
the server's own tools — is pinned by tests. `drawing_apply_standard` makes the
common case one call.
**The user's principle for this track (2026-09-16):** tool count is not the
goal. Every tool here has a rich alias record, a docstring that names its
refusals, a pack, and a place in the `lean` profile decision; a near-duplicate
is folded into an `action` enum rather than registered twice.
Tool budget: **+38** → 204 registered. Packs: styles, settings, page setup and
templates are `core` (drafting essentials); the environment and interactive
groups form the new `settings` pack.
## 2. Scope
**In:** sections 4–10. **Track A hardening (wave 0 of this track's plan):** the
review findings from Track A that are real defects — `entity_create_block_ref`
accepts an undefined block, `block_explode` drops ATTRIB text, `pid_from_spec`
lets NaN reach the drawing and does not roll back on `BaseException`, signal
lines receive a fabricated line number and label, a bubble's exit point ignores
the first waypoint, `block_insert` coerces attribute values with `str()`,
`block_define` writes NaN base points and dangling layer references — each a
fix with a test; the rest of the ~80 findings are filed in
`docs/analysis/track-a-review-backlog.md` with a one-line disposition each.
**Out of 1.6 (recorded):** TABLESTYLE objects (ezdxf cannot author them),
AutoCAD-native `LAYERSTATE` entries (our layer states are portable XRECORDs,
see §8.3), CUI/ribbon/workspace changes, plotter (`.pc3`) authoring, annotative
object *creation* (scales are settable; entities stay non-annotative), ANSI
*inch* templates (ANSI templates here are metric ANSI: `ansi` dimstyle with
decimal inches is a 1.7 item once an inch drawing benchmark exists).
## 3. Architecture
```
engineering/standards/
__init__.py
dimstyles.py ISO_25, ANSI presets as data; DIM_VARIABLE_WHITELIST; apply/describe
textstyles.py ISOCP, ISOCPEUR, ARIAL, ROMANS presets (font file, width, oblique)
mleaderstyles.py ISO / ANSI leader presets
sysvars.py SYSVAR_CATALOG (~60 entries: name, type, range, meaning, engines)
papers.py PAPER_SIZES (ISO A0–A4, ANSI A–E), CTB_CATALOG, scale vocabulary
templates.py TEMPLATE_CATALOG (5 bundled templates: what each contains)
engineering/environment/
layer_states.py portable layer-state snapshot <-> XRECORD codec
templates/ built artefacts: <name>.dxf (headless) + <name>.dwt (COM), README
scripts/build_templates.py, scripts/smoke_settings_com.py
backends/contracts/styles.py (new) dimstyle_*, textstyle_*, mleaderstyle_*
backends/contracts/pagesetup.py (new) page_setup_*, plot_style_list, batch_plot, template_save
backends/contracts/environment.py (new) document_*, layer_state_*, view_named_*, ucs_*,
preferences_*, drawing_properties_*, system_launch,
user_pick_point, user_select, system_prompt_message
backends/contracts/settings.py (edit) drawing_settings facade grows
server.py SECTION 18 Styles (10) · SECTION 19 Page setup & templates (6) · SECTION 20 Environment (22)
```
Every new contract method is `@abstractmethod` unless the table in §9 marks it
`@capability`. Both backends implement every method; COM paths are fake-tested
and executed once live by `scripts/smoke_settings_com.py`.
## 4. Styles (SECTION 18, 10 tools, pack `core`)
### 4.1 Authored presets (`engineering/standards/dimstyles.py`)
| Variable | `iso-25` | `ansi` (metric) | Meaning |
|---|---|---|---|
| DIMTXT | 2.5 | 3.0 | text height (mm) |
| DIMASZ | 2.5 | 3.0 | arrow size |
| DIMEXE | 1.25 | 1.5 | extension beyond dim line |
| DIMEXO | 0.625 | 1.5 | extension line offset |
| DIMGAP | 0.625 | 1.0 | text gap |
| DIMTAD | 1 (above) | 0 (centred) | text vertical placement |
| DIMTIH / DIMTOH | 0 / 0 (aligned) | 1 / 1 (horizontal) | text orientation |
| DIMDEC | 2 | 2 | linear decimals |
| DIMDSEP | `,` | `.` | decimal separator |
| DIMLUNIT | 2 (decimal) | 2 | linear unit format |
| DIMZIN | 8 (trailing zeros suppressed) | 8 | zero suppression |
| DIMBLK | `""` (closed filled) | `""` | arrowhead |
| DIMTXSTY | `ISOCP` | `ROMANS` | text style (created if missing) |
| DIMLWD / DIMLWE | -2 (ByBlock) | -2 | line/extension lineweight |
| DIMSCALE | 1.0 | 1.0 | overall scale |
Source: ISO 129-1:2018 (text and arrow sizes for the 2.5 mm lettering row of
ISO 3098-1) and ASME Y14.2-2014 (line conventions and lettering: 3 mm / .12 in
lettering, .125 in arrowheads), metric values.
A provenance test asserts each preset's values against this table by name.
`DIM_VARIABLE_WHITELIST` = the 15 variables above plus DIMTOL/DIMTP/DIMTM/
DIMTOLJ/DIMTFAC/DIMLFAC/DIMRND/DIMATFIT/DIMTMOVE/DIMCLRD/DIMCLRE/DIMCLRT/
DIMSAH/DIMBLK1/DIMBLK2/DIMCEN — `dimstyle_modify` refuses anything else by
name.
### 4.2 Tools
- `dimstyle_list()` → `[{name, current, values: {15 whitelist vars}}]`
- `dimstyle_create(name, preset: "iso-25"|"ansi"|None = None, overrides: dict|None = None, set_current=False)` — preset values then overrides; unknown override key or a value outside the variable's range (`DIMDEC` 0–8, sizes > 0, DIMTAD 0–4, DIMDSEP one char) → `ValueError` naming it, nothing written; name clash → `ValueError` (use `dimstyle_modify`).
- `dimstyle_modify(name, overrides)` — `changed` reports only variables that actually moved (the `hatch_edit` rule). Dimensions already using the style are listed in `dimensions_using_style` with `rerender_required: true` on the headless engine: AutoCAD re-renders them on the next regen, but a headless DIMENSION carries its rendered block, so the drawing shows the old style until the dimension is redrawn — stated rather than silent.
- `dimstyle_set_current(name)` — `$DIMSTYLE`; the dimension tools then use it (the existing per-dimension override path stays as is).
- `textstyle_list()`, `textstyle_create(name, font: "isocp.shx"|"isocpeur.ttf"|"arial.ttf"|"romans.shx"|<file>, height=0.0, width_factor=1.0, oblique_deg=0.0, set_current=False)`, `textstyle_set_current(name)` — `$TEXTSTYLE`; a font file that is neither a bundled preset nor present in the engine's font path is written but reported `font_resolved: false` (AutoCAD substitutes at open; the headless renderer falls back), never refused, because DXF only stores the name.
- `mleaderstyle_list()`, `mleaderstyle_create(name, preset: "iso"|"ansi", overrides)` — arrow size, landing gap, text style, text height, both engines: ezdxf on `doc.mleader_styles`; COM through the `ACAD_MLEADERSTYLE` dictionary, whose items are `IAcadMLeaderStyle` objects (`ArrowSize` / `LandingGap` / `TextHeight` in drawing units, `TextStyle` a name — verified against the AutoCAD 2026 type library), created with `Dictionaries.Item("ACAD_MLEADERSTYLE").AddObject(name, "AcDbMLeaderStyle")` followed by the property writes. ActiveX has no `MLeaderStyles` *collection* property, which is not an engine boundary — no `mleaderstyle` capability key exists.
- `drawing_apply_standard(standard: "iso"|"ansi", layers: bool = True, units: bool = True)` — one call: the dimstyle preset (created and set current), its text style, `drawing_settings` (units mm, decimal linear and angular units, LTSCALE 1, DIMSCALE 1; the decimal separator comes from the preset) and, with `layers`, the `mech` layer set for both standards (ANSI layer naming is company-specific; the set reuses the mech names with ISO 128 lineweights — only styles and the separator differ between the two). Returns, per item, whether it was created or was already present.
## 5. Settings (SECTION 20 shares these; 3 tools + facade growth)
- `drawing_settings` facade gains: `limits` (`[[xmin,ymin],[xmax,ymax]]` → `$LIMMIN/$LIMMAX`), `grid` (bool → GRIDMODE) and `grid_spacing`, `snap` (bool) and `snap_spacing`, `ortho` (bool), `polar` (bool + `polar_angle`), `psltscale` (0/1), `annotation_scale` (`"1:50"` → CANNOSCALE string + CANNOSCALEVALUE), `linear_units` (`decimal|engineering|architectural|fractional|scientific` → LUNITS 2/3/4/5/1), `angular_units` (`degrees|dms|grads|radians|surveyor` → AUNITS), `dimstyle` and `textstyle` (current names). Ranges enforced as today (`_RANGES`), unknown keys refused, and the read side reports every key.
- `system_variable_describe(name=None, search=None)` — from `SYSVAR_CATALOG`: `{name, type, range|enum, default, meaning, saved_in: drawing|registry|not_saved, engines: {ezdxf: bool, com: bool}, friendly_key: str|None}`; `search` matches names and meanings; unknown name → `known: false` plus the nearest names, never an error. The catalog is authored (~60: the DIM* whitelist, LTSCALE, PSLTSCALE, INSUNITS, LUNITS, LUPREC, AUNITS, AUPREC, LIMMIN/LIMMAX, GRIDMODE/GRIDUNIT, SNAPMODE/SNAPUNIT, ORTHOMODE, POLARMODE/POLARANG, OSMODE, CANNOSCALE, TEXTSIZE, TEXTSTYLE, DIMSTYLE, CLAYER, CELTYPE, CECOLOR, CELWEIGHT, PDMODE/PDSIZE, FILLETRAD, MIRRTEXT, ATTREQ/ATTDIA, TILEMODE, CTAB, DWGNAME/DWGPREFIX, SAVETIME, FILEDIA, CMDECHO, HIGHLIGHT, REGENMODE, UCSNAME, VIEWCTR/VIEWSIZE, ANGBASE/ANGDIR, PLINEWID, HPNAME/HPSCALE/HPANG, LWDISPLAY, XREFCTL, PROXYSHOW, ISOLINES, FACETRES, DISPSILH). `system_set_variable` gains a soft check: a name in the catalog with a range refuses out-of-range values before writing; a name not in the catalog behaves as today.
- `drawing_properties_get()` / `drawing_properties_set(title=None, subject=None, author=None, keywords=None, comments=None, custom: dict|None=None)` — COM: `SummaryInfo` (+ `AddCustomInfo/SetCustomByKey`); ezdxf: `custom` through `doc.header.custom_vars` (`$CUSTOMPROPERTYTAG`/`$CUSTOMPROPERTY` pairs, both engines) and the five summary fields refused with `capability: "dwgprops"` when any of them is passed headlessly (`get` returns them as `null` with `summary_available: false`).
## 6. Page setup, plotting, templates (SECTION 19, 6 tools, pack `core`)
- `page_setup_list(layout=None)` → per layout: `{paper: "ISO_A3", size_mm: [420,297], orientation, plot_style, scale, plot_area, device, margins_mm}` — read from LAYOUT plot settings (ezdxf `dxf_layout.dxf.*`; COM `Layout.*`).
- `page_setup_apply(layout, paper: "ISO_A4"|"ISO_A3"|"ISO_A2"|"ISO_A1"|"ISO_A0"|"ANSI_A"|…|"ANSI_E", orientation: "landscape"|"portrait" = "landscape", plot_style: str = "monochrome.ctb", scale: "fit"|"1:1"|"1:2"|"1:5"|"1:10"|"1:20"|"1:50"|"1:100"|"2:1"|"5:1"|"10:1" = "fit", plot_area: "layout"|"extents" = "layout", device: str = "DWG To PDF.pc3", margins_mm=None, center=True)` — `PAPER_SIZES` authored (ISO 216 / ANSI Y14.1 sizes in mm), `CTB_CATALOG` names the ctb files AutoCAD ships (`monochrome.ctb`, `acad.ctb`, `Grayscale.ctb`, `Screening 100%.ctb`, …); an unknown ctb is written and reported `plot_style_known: false` (a missing ctb only matters at plot time). Returns the applied setup and `changed`.
- `plot_style_list()` — the authored catalog plus, on COM, the files actually present in `Preferences.Files.PrintStyleSheetPath` (`source: "catalog"|"installed"`).
- `batch_plot(layouts: list[str]|None = None, output_dir: str, format: "pdf" = "pdf", file_pattern: "{drawing}-{layout}.pdf")` — every named (default: every paper) layout through the existing `drawing_export_pdf(layout=)` path; returns one row per layout `{layout, path, bytes, mediabox_mm}` where `mediabox_mm` is **read back from the PDF** (`/MediaBox` parsed from the file) so the sheet size is verified, not assumed. Model space is included only when named explicitly.
- `drawing_template_list()` → `TEMPLATE_CATALOG` rows `{name, standard, sheet, layer_set, dimstyle, textstyle, title_block, files: {dxf, dwt}}`.
- `drawing_new(template=…)` (existing tool, parameter grows): a catalog name resolves to the bundled file (`templates/<name>.dxf` headless, `templates/<name>.dwt` on COM); a path is used as today. Returns `template: {name|path, source: "bundled"|"path"}`.
- `drawing_template_save(path, name=None, description=None)` — current drawing as a template: ezdxf writes DXF (a `.dwt` request headlessly is refused with `capability: "dwt_write"` naming the DXF route); COM `SaveAs(path, acTemplateDwg)` and the description goes to the template's summary. Path validated for write.
**Bundled templates** (`scripts/build_templates.py` builds all five through the
server's own backend methods — layers, styles, settings, ISO 5457 frame from
`titleblock_apply_iso_a3` where the sheet is A3, page setup — then saves DXF;
the `.dwt` twins are produced on the live machine by opening each DXF over COM
and `SaveAs(..., acTemplateDwg)`, committed once, and a test asserts the DXF
side is byte-reproducible from the script):
| name | standard | sheet | layer set | styles | page setup |
|---|---|---|---|---|---|
| `iso_a3_mech` | ISO | A3 landscape | mech | ISO-25 / ISOCP | ISO_A3, monochrome.ctb, 1:1 |
| `iso_a1_arch` | ISO | A1 landscape | iso13567 | ISO-25 / ISOCP, CANNOSCALE 1:50 | ISO_A1, monochrome.ctb, fit |
| `iso_a3_pid` | ISO | A3 landscape | pid | ISO-25 / ISOCP | ISO_A3, monochrome.ctb, 1:1 |
| `ansi_b_mech` | ANSI | ANSI B landscape | mech | ANSI / ROMANS | ANSI_B, monochrome.ctb, 1:1 |
| `ansi_d_arch` | ANSI | ANSI D landscape | iso13567 | ANSI / ROMANS | ANSI_D, monochrome.ctb, fit |
## 7. Environment (SECTION 20, 22 tools, pack `settings`)
### 7.1 Documents (3, both engines)
- `document_list()` → `[{name, path, active, saved, entity_count}]`.
- `document_activate(name_or_path)` → switches the backend's active document; every later tool call targets it. ezdxf: the backend keeps `_docs: dict[key, DocState]` (document, path, dirty flag, undo stack, plan spec, transaction snapshot) and `_doc` becomes a property over the active key — `drawing_new` / `drawing_open` register a new entry instead of replacing the only one; `drawing_close` removes the active entry and activates the most recent other one (or none). COM: `Documents.Item(...).Activate()`.
- `document_close(name_or_path, save=False)` — refuses to discard unsaved changes unless `discard=True` (the message names the unsaved document); closing the last document leaves the backend in the "no document" state every tool already handles.
- `drawing_close` (existing) becomes the active-document case of `document_close`.
### 7.2 Layer states (4, both engines, portable)
`layer_state_save(name, description=None)`, `layer_state_restore(name, properties: list|None = None)`, `layer_state_list()`, `layer_state_delete(name)`.
A state is the layer table snapshot `{layer: {on, frozen, locked, color, linetype, lineweight, plot}}` plus `current_layer`, stored as JSON chunks in an XRECORD under the drawing's named object dictionary `ACADMCP_LAYERSTATES` → `<name>` (ezdxf `rootdict.add_new_dict` / `add_xrecord`; COM `Dictionaries.Add` / `AddXRecord` / `SetXRecordData` with 1000-code chunks ≤ 255 chars). Restore applies only the requested `properties` (default all) and reports `applied`, `missing_layers` (in the state, not in the drawing — skipped, never created) and `new_layers` (in the drawing, not in the state — untouched). The docstring and `system_capabilities` say plainly that these states live in the file and travel with it but **do not appear in AutoCAD's Layer States Manager**.
### 7.3 Named views (3, both engines)
`view_named_save(name, center=None, height=None, width=None)` (defaults: the current view on COM; on ezdxf the drawing's extents), `view_named_restore(name)` (COM: `ActiveViewport` set; ezdxf: `$VIEWCTR/$VIEWSIZE` and the reported no-op rule of the other view tools), `view_named_list()`. Refuses `height <= 0`; a name clash replaces (`replaced: true`).
### 7.4 UCS (3, both engines)
`ucs_list()`, `ucs_set(name, origin, x_axis, y_axis)` (unit vectors normalised; non-orthogonal axes refused with the measured angle), `ucs_restore(name|"world")`. **Tool coordinates stay WCS** (the repository rule) — a UCS is stored and made current for the operator's benefit; no tool starts interpreting inputs in UCS. The docstring says so.
### 7.5 Application, preferences, properties, interactive (9)
- `system_launch(visible=True, open_path=None)` — COM: connect or start the ProgID, optionally open a file; returns `{launched: bool, attached: bool, version, document}`; ezdxf: `capability: "live_application"`.
- `system_preferences_get(keys=None)` / `system_preferences_set(key, value)` — whitelist (COM `Preferences.*`):
`OpenSave.SaveAsType` (enum name ↔ value), `OpenSave.AutoSaveInterval` (0–600 min), `OpenSave.CreateBackup`, `OpenSave.IncrementalSavePercent` (0–100), `Display.CursorSize` (1–100), `Drafting.AutoSnapMarkerSize` (1–20), `Drafting.AutoSnapTooltip`, `Selection.PickBoxSize` (0–50), `Output.DefaultPlotStyleTable`, `Output.DefaultOutputDevice`, and read-only `Files.SupportPath`, `Files.TemplateDwgPath`, `Files.PrintStyleSheetPath`, `Files.PrinterConfigPath`. Writes report `old`/`new`; a read-only key or a value outside its range is refused by name; ezdxf: `capability: "preferences"`.
- `drawing_properties_get/set` — §5.
- `user_pick_point(prompt)`, `user_select(prompt, mode: "single"|"multiple")`, `system_prompt_message(text)` — COM `Utility.GetPoint` / `GetEntity` + `SelectionSets.Add(...).SelectOnScreen` / `Utility.Prompt`; a pick cancelled by the operator returns `{cancelled: true}` (not an error); a wall-clock timeout (`COM_CALL_TIMEOUT` applies) reports `timed_out`; ezdxf: `capability: "interactive_prompt"`.
## 8. Cross-cutting
### 8.1 Capability keys (added to **both** maps)
`dwgprops` (ezdxf false: summary fields), `dwt_write` (ezdxf false),
`live_application`, `preferences`, `interactive_prompt` (ezdxf false).
`tests/test_capability_contract.py` enforces both maps. (`mleaderstyle` was
listed here as COM false; the ActiveX route exists — §4.2 — so there is no key.)
### 8.2 Packs, profiles, discovery
`PACK_TOOL_NAMES["settings"]` = the 22 SECTION 20 tools; SECTION 18/19 are
core. `LEAN_TOOL_NAMES` gains `drawing_apply_standard`, `page_setup_apply`,
`batch_plot`, `dimstyle_set_current`, `textstyle_set_current` (lean = 55).
Alias records for all 38 (AutoCAD commands: `DIMSTYLE`, `DDIM`, `STYLE`,
`MLEADERSTYLE`, `PAGESETUP`, `PLOT`, `PUBLISH`, `NEW`, `QNEW`, `SAVEAS`,
`OPTIONS`, `DWGPROPS`, `LAYERSTATE`, `VIEW`, `UCS`, `UCSMAN`, `LIMITS`,
`GRID`, `SNAP`, `ORTHO`, `UNITS`, `CANNOSCALE`, `SYSVAR`, `SETVAR`) and
shop-floor synonyms; the discovery golden set gains ≥ 10 queries for them
(`tests/test_discovery_transform.py` golden file).
### 8.3 Honesty rules specific to this track
- A style or setting write is reported with `changed` (what moved) — re-setting
a value to itself is not a change.
- Page setup evidence is the PDF's own `/MediaBox`, read back.
- Layer states are ours, not AutoCAD's; documented in three places (tool
docstring, `system_capabilities`, README).
- Nothing here starts interpreting coordinates in UCS.
## 9. Testing and evidence
- Presets provenance: `tests/test_standards_presets.py` asserts every ISO-25 /
ANSI value in §4.1 by name, every paper size against ISO 216 / ANSI Y14.1
(A0 841×1189 … A4 210×297; ANSI A 216×279 … E 864×1118), every catalog
sysvar has type+meaning+engines.
- Dual-engine tests for every tool (ezdxf real; COM fake-ActiveX asserting call
shapes: `DimStyles.Add`, `TextStyles.Add`,
`Dictionaries.Item("ACAD_MLEADERSTYLE").AddObject(name, "AcDbMLeaderStyle")`,
`Layout.ConfigName/…`,
`Documents.Item().Activate`, `Dictionaries.Add`+`AddXRecord`, `Views.Add`,
`UserCoordinateSystems.Add`, `Preferences.OpenSave.*`, `SummaryInfo.*`,
`Utility.GetPoint`).
- Multi-document: two headless documents open, entity created in one is not in
the other, activate switches, close-with-unsaved refuses, closing the last
leaves "no document".
- Layer states: save → change layers → restore → table equals snapshot;
`missing_layers`/`new_layers` reported; the XRECORD survives save/reopen.
- Page setup: apply ISO_A3 landscape → `batch_plot` → parsed `/MediaBox` is
420×297 mm (±0.5 mm at 72 dpi units); ANSI_B → 432×279.
- Templates: `scripts/build_templates.py --check` proves the five DXFs are
reproducible; `drawing_new(template="iso_a3_mech")` yields the mech layers,
ISO-25 current, ISO_A3 page setup; a `.dwt` twin exists for each and opens on
COM in the smoke.
- `drawing_apply_standard("iso")` then `dimension_linear` → the DIMENSION carries
DIMTXT 2.5 / DIMDSEP `,`; `"ansi"` → 3.0 / `.`.
- Live COM smoke `scripts/smoke_settings_com.py`: styles, page setup + PDF
mediabox, template `.dwt` open, layer state round trip, named view, UCS,
preferences get (+ one reversible set), `SummaryInfo`, `system_prompt_message`;
`user_pick_point` is exercised only if `--interactive` is passed (it needs the
operator). Result recorded in the CHANGELOG.
- Benchmark v4 gains `page_setup_truth` (`benchmarks/tasks_v4.py`, the same
v3→v4 rule: competitors `not run`); correctness suite +3
(`settings_dimstyle_iso25_values`, `settings_layer_state_roundtrip`,
`settings_pdf_mediabox_a3`).
- Docs: CLAUDE.md sections 18–20 + packs, README rows + config table
(`TOOL_PACKS=settings`), CHANGELOG `Unreleased`.
## 10. Milestones (input to the plan)
0. Track A hardening (7 fixes + backlog file) — independent, parallel wave.
1. `engineering/standards/` data modules + provenance tests (pure).
2. Styles contract + both engines + SECTION 18 tools.
3. Settings facade growth + `system_variable_describe` + properties.
4. Page setup + plot style list + batch_plot with mediabox read-back.
5. Templates: build script, bundled DXF, catalog, `drawing_new(template=)`, `drawing_template_save`; `.dwt` twins from the live machine.
6. Multi-document on both engines (+ `drawing_close` alignment).
7. Layer states, named views, UCS.
8. Launch, preferences, interactive tools, capability keys.
9. Packs/lean/aliases/golden queries, benchmark + correctness, live smoke, docs, gates.
Groups 2, 4, 6+7 touch disjoint modules and get their own SECTIONs, so the
plan runs them in parallel worktrees (the Track A wave-1 pattern) and a merge
agent regenerates the three count files (`docs/tool-inventory.json`, README
snapshot line, `tests/test_tool_registry.py`) after merging.