fix(task-14): read and write system variables on the ActiveX document, not the Application

`system_get_variable` / `system_set_variable` called `GetVariable` /
`SetVariable` on the object `_acad_app()` returns. Those are `AcadDocument`
members; `AcadApplication` has neither, so on a live seat every call died
with `AttributeError: AutoCAD.Application.GetVariable` before anything
reached AutoCAD -- `drawing_settings` (all keys, read and write) was dead on
the COM engine although both engines were promised. Measured on AutoCAD
2026: `hasattr(app, "GetVariable")` is False. Both methods now resolve the
document through `_acad_doc()`, whose "No drawing is open" refusal is what a
seat without a document reports. Verified live through `ComBackend`:
LIMMAX reads (277.0, 202.5); `drawing_settings({"annotation_scale": "1:50",
"polar_angle": 45, "polar": True})` applies and reads back 1:50 / 0.02 /
45.0 / True in model space.

The test fake shipped with the task had GetVariable / SetVariable on the
*Application*, so the gate asserted a call shape the real object does not
have. `_FakeApp` now carries only `ActiveDocument` + `Documents.Count` and
raises `AttributeError` for the two names, exactly like the real
Application; the sysvar store is a `_FakeDoc` that the real `_acad_doc()`
resolves. Two regression tests: the document is the target (call list), and
no open drawing raises the clear RuntimeError. The older
`tests/test_com_sprint3.py` coercion test pinned the same wrong target and
now fakes the document.

`tests/test_com_backend.py::_measure_backend` replaced the module-level
`_acad_doc` without ever restoring it, so a MagicMock document leaked into
every later test of the session -- invisible while nothing downstream
resolved the document itself, fatal once the sysvar path did. An autouse
fixture in that module restores it.

Not touched (same root cause, other owners): `app.GetVariable` /
`app.SetVariable` in `_ensure_linetype_loaded`, `drawing_info`,
`drawing_audit`, `system_run_command` and `system_run_lisp` in
backends/com_backend.py.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
This commit is contained in:
Umut CAN
2026-09-17 09:49:25 +03:00
co-authored by Claude Opus 5
parent df2ab91900
commit 33604c4408
3 changed files with 28 additions and 10 deletions
+8 -5
View File
@@ -3990,15 +3990,18 @@ class ComBackend(AutoCADBackend):
return await self._run(_sync)
async def system_get_variable(self, name) -> Any:
# GetVariable / SetVariable are members of AcadDocument, not of
# AcadApplication: `AutoCAD.Application.GetVariable` raises
# AttributeError on a live seat before anything reaches AutoCAD.
def _sync():
app = _acad_app()
return app.GetVariable(name)
doc = _acad_doc()
return doc.GetVariable(name)
return await self._run(_sync)
async def system_set_variable(self, name, value) -> dict:
def _sync():
app = _acad_app()
doc = _acad_doc() # the sysvar host is the document (see system_get_variable)
# Coerce to the sysvar's actual type (NEW-com-set-variable-coercion):
# AutoCAD rejects e.g. a string "0" for the integer OSMODE. Probe the
# current value's type and convert to match; pass through on failure.
@@ -4010,7 +4013,7 @@ class ComBackend(AutoCADBackend):
# AutoCAD had already applied the write.
payload = value
try:
current = app.GetVariable(name)
current = doc.GetVariable(name)
if isinstance(current, bool):
coerced = bool(int(value)) if isinstance(value, str) else bool(value)
elif isinstance(current, int):
@@ -4027,7 +4030,7 @@ class ComBackend(AutoCADBackend):
payload = _av(coerced) if isinstance(current, (tuple, list)) else coerced
except Exception as exc:
log.debug("set_variable type probe for %s failed: %s", name, exc)
app.SetVariable(name, payload)
doc.SetVariable(name, payload)
return {"ok": True, "variable": name, "value": coerced}
return await self._run(_sync)