From b1c93cdd2a71ab8483b3110db092d7ef30f50abc Mon Sep 17 00:00:00 2001 From: Umut CAN Date: Thu, 17 Sep 2026 23:17:39 +0300 Subject: [PATCH] =?UTF-8?q?fix(task-21):=20the=20applied=20ISO=20layer=20s?= =?UTF-8?q?et=20is=20per=20document=20=E2=80=94=20it=20follows=20document?= =?UTF-8?q?=5Factivate=20on=20ezdxf=20and=20is=20cleared=20on=20every=20do?= =?UTF-8?q?cument=20switch=20on=20both=20engines?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `drawing_apply_iso_layers` recorded its standard in `_active_layer_set` on the backend, and `_role_layer` reads it ahead of the PlanSpec to decide where `dimension_auto` / `construction_xline` file their output. It was neither a `_DocState` field nor cleared by `_reset_document_state`, so an iso13567 sheet next to a mech one pushed the mech drawing's dimensions onto `M-DIMEN-T-N` (created on demand), silently — and `drawing_critique` could not see it because the same stale set drove the critique. The attribute predates the registry (v1.5 `drawing_new` did not reset it either), but the per-document claim of Task 21 covered it and the measured attribute list missed it. - `_DocState.active_layer_set` + `_ActiveField("active_layer_set")` on `EzdxfBackend`: the set follows the active entry, bidirectionally. - `PremiumContract._reset_document_state` clears it (declared on the mixin like `_plan_spec`), which fixes COM's `_ensure_document_state` switch and `drawing_new` / `drawing_open` / `disconnect` on both engines. - tests: `test_active_layer_set_is_per_document` (headless registry, the reviewer's repro plus the reverse direction) and the COM activate test now also asserts the set is cleared and `_role_layer('dim')` is back on DIM. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd --- backends/contracts/premium.py | 7 +++++++ backends/ezdxf_backend.py | 6 ++++++ tests/test_documents.py | 36 +++++++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+) diff --git a/backends/contracts/premium.py b/backends/contracts/premium.py index a33679b..f1f090b 100644 --- a/backends/contracts/premium.py +++ b/backends/contracts/premium.py @@ -36,11 +36,18 @@ class PremiumContract(ABC): _plan_spec: PlanSpec | None = None _preflight_result: Any = None + # The standard `drawing_apply_iso_layers` last bootstrapped on *this* + # drawing (None until it runs) — `_active_layer_set_id` reads it ahead of + # the PlanSpec. Drawing-scoped like the plan: a live backend clears it + # when AutoCAD's active document changes, the headless registry keeps one + # per open document. + _active_layer_set: LayerSetId | None = None def _reset_document_state(self) -> None: """Clear metadata that is valid only for the current drawing.""" self._plan_spec = None self._preflight_result = None + self._active_layer_set = None self._gdt_datums_defined = set() self._gdt_datums_referenced = set() diff --git a/backends/ezdxf_backend.py b/backends/ezdxf_backend.py index 92fa017..b119a58 100644 --- a/backends/ezdxf_backend.py +++ b/backends/ezdxf_backend.py @@ -977,6 +977,11 @@ class _DocState: transaction_stack: list[Path] = field(default_factory=list) plan_spec: Any = None preflight_result: Any = None + # `drawing_apply_iso_layers` records the standard here so `_role_layer` + # files dims / scaffolding on that standard's layer; it is per drawing, + # not per backend (an iso13567 sheet next to a mech one must not push + # the mech drawing's dimensions onto M-DIMEN-T-N). + active_layer_set: str | None = None gdt_datums_defined: set = field(default_factory=set) gdt_datums_referenced: set = field(default_factory=set) activated_seq: int = 0 @@ -1033,6 +1038,7 @@ class EzdxfBackend(AutoCADBackend): _transaction_stack = _ActiveField("transaction_stack") _plan_spec = _ActiveField("plan_spec") _preflight_result = _ActiveField("preflight_result") + _active_layer_set = _ActiveField("active_layer_set") _gdt_datums_defined = _ActiveField("gdt_datums_defined") _gdt_datums_referenced = _ActiveField("gdt_datums_referenced") diff --git a/tests/test_documents.py b/tests/test_documents.py index 7b3b600..4a159d3 100644 --- a/tests/test_documents.py +++ b/tests/test_documents.py @@ -88,6 +88,39 @@ async def test_per_document_state_follows_the_active_entry(backend): assert backend._dirty is False +async def test_active_layer_set_is_per_document(backend): + """The standard ``drawing_apply_iso_layers`` bootstrapped is per drawing. + + It drives ``_role_layer`` — where ``dimension_auto`` and + ``construction_xline`` file their output — and the critique that checks + them. Held on the backend it bled across documents: an iso13567 sheet + next to a mech one pushed the mech drawing's dimensions onto + ``M-DIMEN-T-N`` (created on demand), silently, and ``drawing_critique`` + could not see it because the same stale set drove the critique. + """ + await backend.drawing_apply_iso_layers("iso13567") + assert backend._role_layer("dim") == "M-DIMEN-T-N" + + second = (await backend.drawing_new())["document"] + assert backend._active_layer_set is None, "a fresh drawing has no explicit set" + assert backend._role_layer("dim") == "DIM" + line = await backend.entity_create_line(0, 0, 50, 0) + dims = await backend.dimension_auto([line.handle], style="chain") + assert dims[0].layer == "DIM" + assert "M-DIMEN-T-N" not in [lyr.name for lyr in await backend.layer_list()] + + # Bidirectional: switching back restores the first document's set … + await backend.document_activate("untitled-1") + assert backend._active_layer_set == "iso13567" + assert backend._role_layer("construction") == "M-CONST-E-N" + # … and a set applied on B while A is open does not reach A. + await backend.document_activate(second) + await backend.drawing_apply_iso_layers("pid") + assert backend._role_layer("dim") == "DIM" + await backend.document_activate("untitled-1") + assert backend._active_layer_set == "iso13567" + + async def test_opening_a_file_twice_reloads_it_instead_of_registering_twice(backend, tmp_path): path = tmp_path / "part.dxf" await backend.drawing_save(str(path)) @@ -495,6 +528,7 @@ async def test_com_activate_calls_activate_and_resets_document_scope(com_backend backend, app = com_backend await backend._ensure_document_state() backend._plan_spec = object() + backend._active_layer_set = "iso13567" result = await backend.document_activate("Second.dwg") assert result == { "ok": True, @@ -505,6 +539,8 @@ async def test_com_activate_calls_activate_and_resets_document_scope(com_backend } assert app.Documents._docs[1].calls == [("Activate", ())] assert backend._plan_spec is None, "a document switch clears drawing-scoped state" + assert backend._active_layer_set is None, "the layer set is drawing-scoped too" + assert backend._role_layer("dim") == "DIM" with pytest.raises(ValueError, match="no open document named 'Third.dwg'"): await backend.document_activate("Third.dwg")