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
271 lines
23 KiB
Markdown
271 lines
23 KiB
Markdown
# 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.
|