fix(com): every system variable is read and written on the ActiveX document, not the Application

AcadApplication has no GetVariable/SetVariable members; they live on
AcadDocument. Measured on the running AutoCAD 2026 (25.1s, 2026-09-22,
scratch Documents.Add(), Close(False)):

    hasattr(app, 'GetVariable')  -> False
    hasattr(app, 'SetVariable')  -> False
    app.GetVariable('INSUNITS')  -> AttributeError: ... IAcadApplication
                                    instance ... has no attribute 'GetVariable'
    doc.GetVariable('INSUNITS')  -> 4
    doc.GetVariable('MEASUREMENT') -> 1
    doc.GetVariable('FILEDIA')   -> 1; doc.SetVariable('FILEDIA', 0) -> None,
                                    reads back 0, restored to 1
    doc.GetVariable('AUDITCTL')  -> 0, 'CMDACTIVE' -> 0, 'USERS1' -> ''

Fifteen call sites in backends/com_backend.py still went through the
Application inside `try/except ... log.debug`, so on a live seat they
silently degraded: MEASUREMENT defaulted every seat to metric,
drawing_info reported units from a failed INSUNITS read, drawing_audit
never set AUDITCTL, the CMDACTIVE guards of system_run_command /
system_run_lisp never fired and run_lisp never read USERS1 back. All are
rerouted through `_acad_doc()`; the try/except stays only where an
optional read is defaulted (INSUNITS -> "Unknown", AUDITCTL absent on a
host that lacks it, USERS1 after the expression ran) and the log line
says so.

Linetype loading, measured the same way: `SendCommand("_-LINETYPE _LOAD
CENTER acadiso.lin\n\n")` loads nothing with FILEDIA=0 and leaves the
command at `Enter an option [?/Create/Load/Set]:` (LASTPROMPT), which
makes the whole seat answer RPC_E_CALL_REJECTED to every client until
ESC is pressed; with FILEDIA=1 it opens the modal "Select Linetype File"
dialog. `doc.Linetypes.Load("CENTER", "acadiso.lin")` returns None and
the table grows 3 -> 4; a repeat raises 'Duplicate record name', an
unknown name 'Undefined linetype', a missing file 'File system error'.
`_ensure_linetype_loaded` and `linetype_load` now call `Linetypes.Load`
(MEASUREMENT still read on the document to pick the .lin), touch no
FILEDIA, and report a failure with the English excepinfo description.

`apply_standard` reads every variable it will write before the first
style is created, so a read the engine refuses is a refusal with nothing
written (it used to fail after ISO-25 / ISOCP were created and current).

Fakes: every application fake refuses GetVariable/SetVariable
(tests/test_styles.py's COM fixture carried them on the Application,
enshrining the wrong shape); the members exist on document fakes only.
Regression tests per rerouted site: linetype load via Linetypes.Load with
MEASUREMENT on the document, drawing_info units from the document,
AUDITCTL set/restored on the document, CMDACTIVE/USERS1 on the document,
apply_standard refusing before any write.

Live end-to-end with the real ComBackend on a scratch document:
apply_standard('iso') -> INSUNITS 4, ISO-25 (already in acadiso.dwt,
created: false) and ISOCP (created: true) current, second call created:
false / changed: {}; linetype_load('CENTER') -> Linetypes 3 -> 4, FILEDIA
stays 1, repeat already_loaded, 'NOSUCHLT' refused by name;
_ensure_linetype_loaded('HIDDEN') -> 3 -> 4; drawing_info units 'mm';
drawing_audit AUDITCTL 0 -> 0; system_run_command ok;
system_run_lisp('(+ 40 2)') -> '42'.

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-22 12:25:11 +03:00
co-authored by Claude Opus 5
parent 70afd57831
commit f7528b2fa5
9 changed files with 531 additions and 119 deletions
+10 -6
View File
@@ -3044,10 +3044,13 @@ async def linetype_load(
"""Load a single linetype safely.
Use this instead of `system_run_command('_-LINETYPE _LOAD ...')` — that
raw form can deadlock on the FILEDIA file-picker dialog and on the
-LINETYPE option-menu prompt. This tool sets FILEDIA=0 around the call,
raw form deadlocks on the FILEDIA file-picker dialog and on the
-LINETYPE option-menu prompt (measured on AutoCAD 2026: it loads nothing
either way). This tool loads through the ActiveX `Linetypes.Load` member,
picks the right .lin file from MEASUREMENT, and verifies the linetype
actually loaded.
actually loaded. Refuses a name that is not a valid symbol name, a file
outside the allowed paths, and reports a name the file does not carry
or a file AutoCAD cannot find as an error naming both.
"""
return await _backend(ctx).linetype_load(name, file)
@@ -7300,9 +7303,10 @@ async def drawing_apply_standard(
Every item reports `created` or already present; `settings.changed`
names only the variables that moved, so a second call reports nothing.
An existing ISO-25 / ANSI style is reused as it is, not reset — use
`dimstyle_modify` to change one. Refuses an unknown standard before any
write; the underlying style refusals (`dimstyle_create`,
`textstyle_create`) apply unchanged. In the `lean` profile (Task 13).
`dimstyle_modify` to change one. Refuses an unknown standard, or a units
variable it cannot read back, before any write; the underlying style
refusals (`dimstyle_create`, `textstyle_create`) apply unchanged. In the
`lean` profile (Task 13).
"""
from engineering.standards.apply import apply_standard