Commit Graph
25 Commits
Author SHA1 Message Date
Umut CANandClaude Opus 5 56e6ff7206 feat(sheet): ISO 5457 frames and ISO 7200 title blocks for A4-A0
SECTION 24 opens with sheet_frame and titleblock_apply.

engineering/sheet/frames.py transcribes ISO 216's trimmed sizes, ISO 5457's
20 mm filing margin and 10 mm edges, the grid reference system (an even number
of divisions per side, that side of the frame over 50 mm; letters from the top,
numbers from the left), the centring marks (trimmed edge to 5 mm inside the
frame) and the 10 x 5 mm corner trimming marks. Every one of those numbers is
pinned by a test against the clause it comes from, because a wrong margin is
visible on every drawing this server makes.

engineering/sheet/titleblock.py draws the ISO 7200 block -- 180 mm wide, which
is exactly the drawing-space width of a portrait A4, which is why one block
fits every size -- and carries the ISO 128-30 projection-angle symbol. Which
symbol is which is derived rather than remembered: first-angle projection puts
the view seen from the left on the right, and the view of the cone from its
small end is the small circle inside the large one, so first angle draws the
cone left and the circles right.

The old A3 module folds in. apply_iso_a3_titleblock now forwards to
apply_titleblock(size="A3", projection=None, zones=False, marks=False), which is
the geometry it always drew, and tests/test_engineering_titleblock.py passes
unchanged -- that is the regression gate for the fold.

No new ActiveX member is called: both tools reach the drawing only through
contract members the A3 title block has used on both engines since v1.0
(entity_create_polyline / _line / _circle / _text, layout_list,
layout_set_current), so there was nothing to probe live.

Declared, not glossed: the zone divisions drawn here are equal, so ISO 5457's
allowance for shorter corner fields is not implemented; the division counts,
which are what a zone reference quotes, are the standard's. ISO 7200's full
optional-field list is not transcribed, only the fields this block has a cell
for. Coverage is A4-A0; an unknown size is refused by name with the five it has.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
2026-09-22 19:03:14 +03:00
Umut CANandClaude Opus 5 70afd57831 feat(server): settings pack — 22 environment tools behind TOOL_PACKS, lean grows to 55, three groups, golden set +10
Task 24 (merge sanity) on main after the five track E groups merged.

- TOOL_PACK_NAMES = ("core", "pid", "settings"); PACK_TOOL_NAMES["settings"]
  is exactly the 22 SECTION 20 tools. Styles and page setup stay in core.
- LEAN_TOOL_NAMES += page_setup_apply, batch_plot (53 -> 55).
- _GROUP_TAG_PRIORITY / _GROUP_TAG_LABELS: "style" moved up next to "pid",
  "pagesetup" -> page_setup and "environment" -> environment added; the 28
  SECTION 19/20 decorators gain their section tag, so _tool_groups() reports
  styles 10 / page_setup 6 / environment 22 (23 groups, 204 tools) and
  pack == group == section for the settings pack.
- SECTION headers, backends/base.py composition, contracts/__init__.py
  exports and the layer_states feature entry were already correct after the
  merge (read and confirmed, nothing edited).
- Pack text: config.py, .env.example, README config row; README <sub> line
  204 tools in 23 groups; release snapshot 3259 collected tests (measured).
- docs/tool-inventory.json regenerated; registry snapshot re-derived.
- tests: test_tool_packs.py replaced (11 tests), test_capability_contract.py
  +3 (composition, the agreed capability flags, layer_states honesty),
  golden set +10 tuning cases (81 = 66 tuning + 15 holdout); all ten route
  in the top 3 without alias edits.

Deviation from the plan text: TRACK_E_KEYS carries five keys, not six. The
plan's "mleaderstyle" (ezdxf True / com False) contradicts spec §8.1, which
retracted that key because the ActiveX route exists (§4.2); measured on the
tree: capability_keys() == {boundary_trace, chspace, interactive_prompt,
live_application, preferences, revcloud, wipeout}, and
ComBackend.mleaderstyle_create is a native implementation
(backends/com_backend.py:6752, is_capability_default -> False). The test now
also asserts the key is absent.

No new ActiveX call was written in this task, so no live probe was needed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
2026-09-22 11:35:00 +03:00
Umut CANandClaude Opus 5 90c1194f7f merge: track-e group V
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
2026-09-22 11:18:03 +03:00
Umut CANandClaude Opus 5 8e05b5e8eb merge: track-e group P
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
2026-09-22 11:14:02 +03:00
Umut CANandClaude Opus 5 3278586c6e merge: track-e group C
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
2026-09-22 11:12:15 +03:00
Umut CANandClaude Opus 5 a5d166f365 feat(styles): drawing_apply_standard — ISO or ANSI styles, units and layers in one idempotent call
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
2026-09-21 22:49:12 +03:00
Umut CANandClaude Opus 5 b458923e19 feat(templates): drawing_template_save on both engines — dwt_write is a declared capability
Live AutoCAD writes a real .dwt through SaveAs(path, ac2018_Template = 66) and
puts the description into the summary Comments; headlessly a .dxf template is
written through drawing_save_as and a .dwt request is refused with capability
dwt_write, naming the DXF route and the COM route. drawing_save_as's "dwt"
entry was 5 (acR13_dxf): a DWT request wrote an R13 DXF under a .dwt name.

Probed live on AutoCAD 2026 (typelib: ac2018_Template = 66, acR13_dxf = 5,
acTemplateDwg absent; the saved file starts AC1032, the document is rebound,
Comments takes). SECTION 19 is now 6 tools; SAVEAS joins SHARED_ACAD_COMMANDS;
templates/README.md documents the two files per template.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
2026-09-21 22:48:31 +03:00
Umut CANandClaude Opus 5 a76d9e08a3 feat(templates): five bundled templates built by the server's own tools; drawing_new(template=name)
SECTION 19 grows to five tools with drawing_template_list; drawing_new takes a
bundled name (iso_a3_mech, iso_a1_arch, iso_a3_pid, ansi_b_mech, ansi_d_arch)
or a .dwt/.dxf path and reports template: {name|path, path, source}.

scripts/build_templates.py builds templates/*.dxf through EzdxfBackend
(layer set, header DIM variables until group S's styles land, drawing_settings,
sheet tab, ISO 5457 frame on A3, page_setup_apply) and --check pins them
reproducible. One drift the plan's same-process measurement could not see is
fixed at the source rather than masked: ezdxf orders CLASSES by set iteration,
so two processes disagreed on LAYOUT / ACDBPLACEHOLDER; the section is re-keyed
(required list first, then by name) before the save.

The root .gitignore ignores *.dxf as generated output; templates/*.dxf is
un-ignored there, since the bundled set is the one DXF that is committed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
2026-09-21 22:03:41 +03:00
Umut CANandClaude Opus 5 a60f801205 feat(server): SECTION 18 styles — dimstyle/textstyle/mleaderstyle tools with DIMSTYLE/STYLE/MLEADERSTYLE aliases
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
2026-09-21 21:44:02 +03:00
Umut CANandClaude Opus 5 f25421709f feat(pagesetup): SECTION 19 — page_setup_list/apply, plot_style_list, batch_plot with /MediaBox read-back
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
2026-09-21 18:39:34 +03:00
Umut CANandClaude Opus 5 b83a74e969 feat(environment): system_launch, preference whitelist and operator prompts on COM; declared refusals headlessly
user_pick_point / user_select answer {cancelled: true} on ESC (DISP_E_EXCEPTION)
and {timed_out: true} past COM_CALL_TIMEOUT; preferences_set refuses read-only,
unknown and out-of-range keys before any ActiveX call.

The read-only key is Files.PrinterStyleSheetPath, not the spec's
PrintStyleSheetPath: IAcadPreferencesFiles has no member of that name
(measured live on AutoCAD 2026 / R25.1), and a misspelt key would make
preferences_get() raise on every live call.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
2026-09-21 18:34:09 +03:00
Umut CANandClaude Opus 5 10a7372ce7 feat(settings): drawing_properties_get/set — SummaryInfo on COM, custom properties on both engines
Summary fields are refused headlessly with capability dwgprops before any
write; custom properties are $CUSTOMPROPERTYTAG/$CUSTOMPROPERTY pairs and
survive save + reopen. Validation is shared, by name, and never str()-coerces.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
2026-09-21 16:59:19 +03:00
Umut CANandClaude Opus 5 125a8ed3b8 feat(settings): SYSVAR_CATALOG + system_variable_describe; system_set_variable refuses out-of-range values
90 authored rows (type, range/enum, default, meaning, saved-in, per-engine
support, friendly key); every ezdxf claim is checked against the real
backend and the registry-saved set is pinned to what the backend refuses.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
2026-09-17 14:56:25 +03:00
Umut CANandClaude Opus 5 dcc8f07bf4 feat(environment): portable layer states, named views and UCS on both engines
Layer states are ACADMCP_LAYERSTATES XRECORD chunks (<= 255 ASCII chars each),
in the file and not in AutoCAD's Layer States Manager. Headless view restore
writes the *Active VPORT (ezdxf's header has no $VIEWCTR; measured). UCS is
stored and made current; tool coordinates stay WCS.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
2026-09-17 09:47:39 +03:00
Umut CANandClaude Opus 5 174fb8010c feat(environment): multi-document on both engines — ezdxf document registry, document_list/activate/close, drawing_close folded in
EnvironmentContract carries every member of the track E interface; the two
drawing_properties_* members are @capability('dwgprops') in this worktree only
and become @abstractmethod when group C's implementations merge.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
2026-09-16 21:20:21 +03:00
Umut CANandClaude Opus 5 c441609254 feat(pid): pid_tag_parse, catalogue and ISA-5.1 resources, prompt rewritten onto the tools
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
2026-09-16 12:29:04 +03:00
Umut CANandClaude Opus 5 d80ae17411 feat(pid): pid_from_spec — a whole P&ID in one transaction, graph and critique returned
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
2026-09-16 12:10:14 +03:00
Umut CANandClaude Opus 5 4e523ac6ed feat(pid): instrument index, line list and equipment list from the graph
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
2026-09-16 10:49:30 +03:00
Umut CANandClaude Opus 5 e246b5e86a feat(pid): pid_graph reads the drawing back with ports, junctions, dangling ends and confidence
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
2026-09-16 00:13:42 +03:00
Umut CANandClaude Opus 5 e8f1a666f8 feat(pid): pid_line_draw — port-to-port orthogonal lines with class, number, markers
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
2026-09-15 23:26:09 +03:00
Umut CANandClaude Opus 5 4b6abf7b92 feat(pid): pid_symbol_list and pid_symbol_insert — blocks with tags, ports and XDATA
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
2026-09-15 16:44:06 +03:00
Umut CANandClaude Opus 5 cda8b69dba feat(entity): entity_get_xdata / entity_set_xdata on both engines
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
2026-09-15 14:16:12 +03:00
Umut CANandClaude Opus 5 a84cae2641 feat(blocks): block_define — typed primitives + ATTDEFs on both engines
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
2026-09-15 13:58:09 +03:00
Umut CANandClaude Opus 5 25f8737132 release: v1.5.1 — bind guard failed open, [full] would not install, DIMSCALE did nothing
Three defects found after 1.5.0 reached PyPI, two of them by the adversarial
pass over the rewritten README.

The HTTP bind guard failed open on the launch path it was written for.
`_guarded_run_async` read the bind address from the `host` kwarg and treated an
absent one as loopback. It is not: `fastmcp run` forwards that kwarg only when
`--host` was passed, and fastmcp otherwise resolves the host from
`fastmcp.settings.host`, which is environment-backed. So

    FASTMCP_HOST=0.0.0.0 fastmcp run server.py:mcp --transport http

bound every interface -- 149 tools including file open/save and AutoCAD command
execution, no token -- while the guard inspected the literal "127.0.0.1" and
waved it through. v1.4.0's changelog claimed this guard "fires on every launch
path"; it fired, and looked at the wrong host. It now resolves the effective
host, and warns rather than staying silent if it cannot.

`[com]` and `[full]` could not be installed off Windows. Both listed
`pywin32>=306` with no environment marker, and pywin32 publishes Windows wheels
and no sdist, so `pip install autocad-mcp-pro[full]` died at resolution on
Linux and macOS -- and `[full]` is the extra the README recommended to
contributors. CI never caught it because every Linux lane installs `[pdf]` or
syncs from uv.lock, whose resolution markers hide the gap. Both entries carry
`; sys_platform == "win32"` now, with a release-consistency test to keep them.

`drawing_settings({"dimscale": ...})` reported success and changed nothing
headlessly. It writes `$DIMSCALE`, which is what AutoCAD reads when it draws a
dimension, so the call worked on COM; ezdxf renders from the dimstyle table
entry and never consults the header. One call, one reported success, two
different drawings. The header value now folds into the per-dimension override
on all five dimension tools -- including `dimension_aligned` and
`dimension_angular`, which do not share the tolerance-override path and the
obvious fix would have missed. DIMSCALE 1.0 writes nothing.

README: every engine-conditional family in the feature table now says which
engine (boundary tracing, WIPEOUT/REVCLOUD/MTEXT masks, CHSPACE, typed hatch
edge paths, headless undo), and the checkout install line stopped recommending
an extra that could not resolve on the reader's platform.

1257 passed, 1 xfailed. 1.5.0 is to be yanked: its diameter and radius callouts
put wrong numbers on drawings at default settings.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 14:28:35 +03:00
Umut CANandClaude Opus 5 b03a7615b1 release: v1.5.0 — discovery layer, honesty debt closed, Wave A
Tool count 131 -> 154; 474 -> 1242 collected tests.

The release exists for two measured reasons. A 149-tool catalog costs a client
~40,300 tokens before it has asked for anything, and a drafter searching the way
drafters think -- FILLET, BPOLY, QSELECT, WBLOCK -- scored df=0 against the stock
index, because those words were simply not in the data.

Discovery and batching
- DISCOVERY_MODE=search replaces the catalog with search_tools + call_tool,
  ranked over an AutoCAD command and synonym corpus (discovery/aliases.py) on
  top of fastmcp's BM25 index. Measured: 40,305 -> 356 tokens advertised.
- cad_batch runs a step list in one round trip; fields= projects result-heavy
  responses. The `core` profile is gone -- discovery replaced it.

Honesty debt (Tier 0)
- OCS/ECS: `extrusion` appeared nowhere in this codebase, so mirrored and
  rotated geometry reported coordinates that were quietly wrong. backends/ocs.py
  translates at the backend boundary; every tool coordinate is WCS on both
  engines, and a tilted plane refuses rather than answering with a number that
  looks fine.
- drawing_save_as(fmt="dwg") wrote DXF bytes to a .dwg path. drawing_audit never
  repaired. analysis_measure_area shoelaced points the caller typed in.
- ezdxf calls gained a per-call timeout, so one hung call can no longer wedge a
  server whose document lock is a single asyncio.Lock.

Architecture
- The 1854-line backend ABC is 19 contract modules; @capability(key, reason=)
  makes optional methods declarative and a gate fails on undeclared holes.
- 8 layout/viewport tools, CHSPACE, CAD_PROGID escape hatch.
- Wave A: selection filters, boundary tracing, hatch depth, annotation objects,
  measurement -- 13 tools of a planned 39. The REGION and 2D-boolean family was
  cut on evidence: add_region() yields zero ACIS bytes and the greiner_hormann
  substitute loses 28.2% of the area on a square with one semicircular edge.

Verified against a live AutoCAD 2026, which found four COM defects no amount of
reading would have caught -- AcadPViewport has no ViewCenter, and the line that
set it had been swallowed by a bare except since v1.4.

Benchmark v3 (15 tasks) and what it caught
- v2 scored this server 10/10, which carried no information: every task in it
  exercised something the server was built around. Five tasks were added because
  they can fail, and three did while being written -- a hatch could not be
  measured though the refusal listed HATCH as measurable, the boundary tools and
  the measure tool disagreed about the same shape (139.2177 vs 139.2699), and a
  circle stored as two bulged vertices measured 0.0.
- Competitor reports are NOT re-scored. They are pinned v1.4 runs against v2 and
  were never asked these five questions; the chart shows those rows `not run`.
- Correctness A/B: 21/24 -> 24/24, zero regressions.

Performance, stated plainly
The published v1.4 perf report was recorded on CPython 3.14 and the new one on
3.11.15, so the apparent 2-6x speedup is the interpreter, not this release.
Re-measured on one interpreter, v1.5.0 is 28% slower creating entities and 22%
slower on the 10k roundtrip. Fully attributed: EZDXF_CALL_TIMEOUT=0 restores
v1.4.0's exact numbers. That is the deadlock fix being paid for, the knob is
documented, and winning it back is on the 1.6 roadmap.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 09:45:20 +03:00