38 tools in three sections: ISO-25/ANSI dimension, text and leader styles with drawing_apply_standard; drawing_settings growth, a system-variable catalogue and document properties; page setup with PDF mediabox read-back, batch plot and five bundled templates built by the server's own tools; multi-document on both engines, portable layer states, named views, UCS, launch, preferences and operator prompts. Track A hardening is wave 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
23 KiB
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 (DIMDEC0–8, sizes > 0, DIMTAD 0–4, DIMDSEP one char) →ValueErrornaming it, nothing written; name clash →ValueError(usedimstyle_modify).dimstyle_modify(name, overrides)—changedreports only variables that actually moved (thehatch_editrule). Dimensions already using the style are listed indimensions_using_stylewithrerender_required: trueon 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 reportedfont_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 on ezdxf'sdoc.mleader_styles. ActiveX exposes no MLeaderStyle collection, so the COM engine refuses both withcapability: "mleaderstyle"(the reason namesleader_create_mleader's existing per-leader parameters as the route);mleaderstyle_liston COM still reads the names from theACAD_MLEADERSTYLEdictionary.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, withlayers, themechlayer 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_settingsfacade gains:limits([[xmin,ymin],[xmax,ymax]]→$LIMMIN/$LIMMAX),grid(bool → GRIDMODE) andgrid_spacing,snap(bool) andsnap_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),dimstyleandtextstyle(current names). Ranges enforced as today (_RANGES), unknown keys refused, and the read side reports every key.system_variable_describe(name=None, search=None)— fromSYSVAR_CATALOG:{name, type, range|enum, default, meaning, saved_in: drawing|registry|not_saved, engines: {ezdxf: bool, com: bool}, friendly_key: str|None};searchmatches names and meanings; unknown name →known: falseplus 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_variablegains 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:customthroughdoc.header.custom_vars($CUSTOMPROPERTYTAG/$CUSTOMPROPERTYpairs, both engines) and the five summary fields refused withcapability: "dwgprops"when any of them is passed headlessly (getreturns them asnullwithsummary_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 (ezdxfdxf_layout.dxf.*; COMLayout.*).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_SIZESauthored (ISO 216 / ANSI Y14.1 sizes in mm),CTB_CATALOGnames the ctb files AutoCAD ships (monochrome.ctb,acad.ctb,Grayscale.ctb,Screening 100%.ctb, …); an unknown ctb is written and reportedplot_style_known: false(a missing ctb only matters at plot time). Returns the applied setup andchanged.plot_style_list()— the authored catalog plus, on COM, the files actually present inPreferences.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 existingdrawing_export_pdf(layout=)path; returns one row per layout{layout, path, bytes, mediabox_mm}wheremediabox_mmis read back from the PDF (/MediaBoxparsed from the file) so the sheet size is verified, not assumed. Model space is included only when named explicitly.drawing_template_list()→TEMPLATE_CATALOGrows{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>.dxfheadless,templates/<name>.dwton COM); a path is used as today. Returnstemplate: {name|path, source: "bundled"|"path"}.drawing_template_save(path, name=None, description=None)— current drawing as a template: ezdxf writes DXF (a.dwtrequest headlessly is refused withcapability: "dwt_write"naming the DXF route); COMSaveAs(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_docbecomes a property over the active key —drawing_new/drawing_openregister a new entry instead of replacing the only one;drawing_closeremoves 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 unlessdiscard=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 ofdocument_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 (COMPreferences.*):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-onlyFiles.SupportPath,Files.TemplateDwgPath,Files.PrintStyleSheetPath,Files.PrinterConfigPath. Writes reportold/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)— COMUtility.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_TIMEOUTapplies) reportstimed_out; ezdxf:capability: "interactive_prompt".
8. Cross-cutting
8.1 Capability keys (added to both maps)
mleaderstyle (COM false), dwgprops (ezdxf false: summary fields), dwt_write
(ezdxf false), live_application, preferences, interactive_prompt (ezdxf
false). tests/test_capability_contract.py enforces both maps.
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.pyasserts 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,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_layersreported; the XRECORD survives save/reopen. - Page setup: apply ISO_A3 landscape →
batch_plot→ parsed/MediaBoxis 420×297 mm (±0.5 mm at 72 dpi units); ANSI_B → 432×279. - Templates:
scripts/build_templates.py --checkproves the five DXFs are reproducible;drawing_new(template="iso_a3_mech")yields the mech layers, ISO-25 current, ISO_A3 page setup; a.dwttwin exists for each and opens on COM in the smoke. drawing_apply_standard("iso")thendimension_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.dwtopen, layer state round trip, named view, UCS, preferences get (+ one reversible set),SummaryInfo,system_prompt_message;user_pick_pointis exercised only if--interactiveis 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: competitorsnot 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), CHANGELOGUnreleased.
10. Milestones (input to the plan)
- Track A hardening (7 fixes + backlog file) — independent, parallel wave.
engineering/standards/data modules + provenance tests (pure).- Styles contract + both engines + SECTION 18 tools.
- Settings facade growth +
system_variable_describe+ properties. - Page setup + plot style list + batch_plot with mediabox read-back.
- Templates: build script, bundled DXF, catalog,
drawing_new(template=),drawing_template_save;.dwttwins from the live machine. - Multi-document on both engines (+
drawing_closealignment). - Layer states, named views, UCS.
- Launch, preferences, interactive tools, capability keys.
- 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.