fix(task-22): live named views read and restore the display (VIEWCTR/VIEWSIZE, ZOOM Window), UCS sysvars come from the document, and a frozen current layer is thawed before restore makes it current

Four review findings, all measured by the reviewer on AutoCAD 2026:

1. ucs_list and the ucs_restore('world') CMDACTIVE guard read
   app.GetVariable, but GetVariable is an AcadDocument member — the tool
   was dead live and the guard's swallowed read forced cmd_active to 0,
   sending `_.UCS _W` into any open prompt. Both read the document now,
   and a failed CMDACTIVE read refuses instead of proceeding. The fake
   app in tests/test_views_ucs.py has no GetVariable any more, so the
   old code cannot pass.

2. view_named_save defaulted from doc.ActiveViewport.Center/Height/Width,
   which does not track the display (it answered the initial view after
   `_.ZOOM _C 5,6 20`). The current view is VIEWCTR / VIEWSIZE at the
   SCREENSIZE aspect — what `-VIEW _S` records (width 40.267 for height
   20). The fake viewport is deliberately stale so a read of it fails.

3. view_named_restore wrote Center/Height/Width into the AcadViewport;
   AutoCAD anchors the lower-left corner and widens to the display
   aspect, landing a 10x20 view at (5, 6) on VIEWCTR (20.134, 6). It now
   zooms the saved rectangle (app.ZoomWindow), which centres and fits
   like `-VIEW _R` (VIEWCTR = saved centre, VIEWSIZE = max(h, w/aspect)),
   reports `applied: "zoom_window"` and reads VIEWCTR / VIEWSIZE back.
   Headless the same reconciliation moved the centre whenever the
   *Active VPORT's aspect was rewritten from w/h (even the 1.34 default
   opened at x = 11.734): the aspect the file carries is left alone —
   it is the display's, written by AutoCAD at save — and the window is
   fitted into it; the payload adds `vport_height` / `aspect_ratio`.

4. layer_state_restore set ActiveLayer before the loop and outside the
   try; AutoCAD refuses to make a frozen layer current, so the spec's
   own save → freeze → restore path raised an opaque COM error with
   nothing applied. When `frozen` is being restored the state's current
   layer is thawed first; the write is wrapped and a refusal lands in
   `warnings`. The fake document and layer now model both refusals.

Live AutoCAD was not running in this round (GetActiveObject failed, no
acad process), so the fix is measured against the reviewer's numbers,
not re-measured.

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-21 16:48:19 +03:00
co-authored by Claude Opus 5
parent bbc9aeeb63
commit c7dd72410a
6 changed files with 318 additions and 62 deletions
+77 -7
View File
@@ -10,7 +10,6 @@ the COM tests prove the ActiveX call shapes.
from __future__ import annotations
import json
import types
import pytest
@@ -338,15 +337,49 @@ class _FakeDictionaries:
class _FakeLayer:
"""Models the two refusals measured live (AutoCAD 2026): the active layer
cannot be frozen, and a frozen layer cannot be made active."""
def __init__(self, name, **props):
self.Name = name
self.Color = props.get("color", 7)
self.Linetype = props.get("linetype", "Continuous")
self.LineWeight = props.get("lineweight", -3)
self.LayerOn = props.get("on", True)
self.Freeze = props.get("frozen", False)
self._frozen = props.get("frozen", False)
self.Lock = props.get("locked", False)
self.Plottable = props.get("plot", True)
self.document = None
@property
def Freeze(self):
return self._frozen
@Freeze.setter
def Freeze(self, value):
if value and self.document is not None and self.document.ActiveLayer is self:
raise RuntimeError("AutoCAD COM error: cannot freeze the current layer")
self._frozen = bool(value)
class _FakeDocument:
def __init__(self, layers, current):
self.Dictionaries = _FakeDictionaries()
self.Layers = _FakeLayers(layers)
self._active = current
for lyr in layers:
lyr.document = self
@property
def ActiveLayer(self):
return self._active
@ActiveLayer.setter
def ActiveLayer(self, layer):
if layer.Freeze:
# Measured: com_error 'Error setting active layer' (-2145320874).
raise RuntimeError("AutoCAD COM error (-0x7ffdfff7): Error setting active layer")
self._active = layer
class _FakeLayers:
@@ -376,11 +409,7 @@ def com_backend(monkeypatch):
_FakeLayer("GEOMETRY", lineweight=50),
_FakeLayer("HIDDEN", color=8, linetype="HIDDEN", frozen=True, plot=False),
]
document = types.SimpleNamespace(
Dictionaries=_FakeDictionaries(),
Layers=_FakeLayers(layers),
ActiveLayer=layers[1],
)
document = _FakeDocument(layers, current=layers[1])
monkeypatch.setattr(module, "_acad_doc", lambda: document)
monkeypatch.setattr(module, "_ensure_linetype_loaded", lambda name: None)
monkeypatch.setattr(module, "_regen", lambda: None)
@@ -429,6 +458,47 @@ async def test_com_restore_reads_the_xrecord_and_writes_layer_properties(com_bac
assert result["missing_layers"] == [] and result["new_layers"] == []
async def test_com_restore_thaws_the_states_current_layer_before_making_it_current(com_backend):
"""The spec's own acceptance path, measured live: save with GEOMETRY
current and HIDDEN frozen → thaw HIDDEN, make 0 current, freeze GEOMETRY
→ restore. AutoCAD refuses ``ActiveLayer = <frozen>``, so the write
before the loop raised an opaque COM error and nothing was applied."""
backend, document = com_backend
await backend.layer_state_save("PLOT")
geometry = document.Layers.Item("GEOMETRY")
hidden = document.Layers.Item("HIDDEN")
hidden.Freeze = False
document.ActiveLayer = document.Layers.Item("0")
geometry.Freeze = True
result = await backend.layer_state_restore("PLOT")
assert "warnings" not in result, result
assert document.ActiveLayer is geometry and geometry.Freeze is False
assert hidden.Freeze is True
assert result["applied"] == {
"layers": 3,
"properties": list(PROPERTIES),
"current_layer": "GEOMETRY",
}
async def test_com_restore_reports_a_refused_current_layer_instead_of_failing(com_backend):
"""Without ``frozen`` in the requested properties the thaw is not ours to
make: AutoCAD's refusal lands in ``warnings`` and the rest still applies."""
backend, document = com_backend
await backend.layer_state_save("PLOT")
geometry = document.Layers.Item("GEOMETRY")
hidden = document.Layers.Item("HIDDEN")
hidden.Color = 3
document.ActiveLayer = document.Layers.Item("0")
geometry.Freeze = True
result = await backend.layer_state_restore("PLOT", properties=["color", "current"])
assert result["applied"]["current_layer"] is None
assert result["applied"]["layers"] == 3 and hidden.Color == 8
assert geometry.Freeze is True and document.ActiveLayer.Name == "0"
(warning,) = result["warnings"]
assert warning.startswith("GEOMETRY: cannot be made current: ")
async def test_com_list_and_delete(com_backend):
backend, document = com_backend
await backend.layer_state_save("A", description="one")