diff --git a/backends/com_backend.py b/backends/com_backend.py index 4474c2a..1594725 100644 --- a/backends/com_backend.py +++ b/backends/com_backend.py @@ -1662,6 +1662,9 @@ class ComBackend(AutoCADBackend): view = self._com_view(doc, clean) replaced = view is not None if view is None: + # Measured (AutoCAD 2026): Views.Add creates a plan view — + # Direction (0, 0, 1), Target (0, 0, 0) — so unlike ezdxf's + # VIEW default of (1, 1, 1) nothing has to be forced here. view = doc.Views.Add(clean) view.Center = _av([cx, cy]) view.Height = float(h) diff --git a/backends/ezdxf_backend.py b/backends/ezdxf_backend.py index 73b3339..287e3e9 100644 --- a/backends/ezdxf_backend.py +++ b/backends/ezdxf_backend.py @@ -2576,7 +2576,22 @@ class EzdxfBackend(AutoCADBackend): stored = existing.dxf.name if replaced else clean if replaced: doc.views.remove(stored) - doc.views.add(stored, dxfattribs={"center": (cx, cy, 0.0), "height": h, "width": w}) + # The tool stores a plan window. ezdxf's VIEW default is + # direction=(1, 1, 1) — an isometric view — so the orientation must + # be written explicitly or AutoCAD restores the entry oblique and + # reads ``center`` in that DCS. Measured: the VPORT default is plan, + # which is why a save→restore round trip alone never shows it. + doc.views.add( + stored, + dxfattribs={ + "center": (cx, cy, 0.0), + "height": h, + "width": w, + "direction": (0.0, 0.0, 1.0), + "target": (0.0, 0.0, 0.0), + "view_twist": 0.0, + }, + ) self._mark_dirty() return { "ok": True, @@ -2610,6 +2625,14 @@ class EzdxfBackend(AutoCADBackend): vport.dxf.height = h if h > 0: vport.dxf.aspect_ratio = w / h + # The orientation travels with the window: a foreign drawing's + # VIEW may be oblique or twisted, and flattening it to a plan view + # of the same center would be a silent lie. The attribute read + # mirrors what the file carries: ezdxf forces these tags on export, + # so an unset direction is (1, 1, 1) on disk as well as here. + vport.dxf.direction = tuple(view.dxf.direction) + vport.dxf.target = tuple(view.dxf.target) + vport.dxf.view_twist = float(view.dxf.view_twist) self._mark_dirty() return { "ok": True, diff --git a/tests/test_views_ucs.py b/tests/test_views_ucs.py index 3670595..6943f85 100644 --- a/tests/test_views_ucs.py +++ b/tests/test_views_ucs.py @@ -106,6 +106,57 @@ async def test_view_replace_restore_and_list(backend): assert "BAD" not in backend._doc.views +async def test_view_is_written_as_a_plan_view_on_disk(backend, tmp_path): + """ezdxf's VIEW default is ``direction=(1, 1, 1)`` — an isometric view. The + tool stores the plan window of ``center``/``height``, so the entry must + say so explicitly (DXF 11/21/31, 12/22/32 and 50) or AutoCAD restores it + oblique and reads ``center`` in that DCS.""" + await backend.entity_create_line(0, 0, 100, 50) + await backend.view_named_save("OVERALL") + view = backend._doc.views.get("OVERALL") + assert tuple(view.dxf.direction) == (0.0, 0.0, 1.0) + assert tuple(view.dxf.target) == (0.0, 0.0, 0.0) + assert view.dxf.view_twist == 0.0 + + import ezdxf + + path = tmp_path / "views.dxf" + await backend.drawing_save_as(str(path)) + reopened = ezdxf.readfile(str(path)).views.get("OVERALL") + assert tuple(reopened.dxf.direction) == (0.0, 0.0, 1.0) + assert tuple(reopened.dxf.target) == (0.0, 0.0, 0.0) + assert tuple(reopened.dxf.center)[:2] == (50.0, 25.0) + + +async def test_view_restore_carries_the_orientation_into_the_vport(backend): + """A foreign drawing's VIEW may be oblique or twisted; restoring it must + not silently flatten it to a plan view of the same center.""" + backend._doc.views.add( + "ISO", + dxfattribs={ + "center": (5.0, 6.0, 0.0), + "height": 20.0, + "width": 30.0, + "direction": (1.0, 1.0, 1.0), + "target": (1.0, 2.0, 3.0), + "view_twist": 15.0, + }, + ) + result = await backend.view_named_restore("iso") + assert result["name"] == "ISO" and result["center"] == [5.0, 6.0] + (vport,) = backend._doc.viewports.get("*Active") + assert tuple(vport.dxf.direction) == (1.0, 1.0, 1.0) + assert tuple(vport.dxf.target) == (1.0, 2.0, 3.0) + assert vport.dxf.view_twist == 15.0 + # ...and restoring a view the tool saved puts the VPORT back on plan. + await backend.view_named_save("PLAN", center=[0, 0], height=10) + await backend.view_named_restore("PLAN") + (vport,) = backend._doc.viewports.get("*Active") + assert tuple(vport.dxf.direction) == (0.0, 0.0, 1.0) + assert tuple(vport.dxf.target) == (0.0, 0.0, 0.0) + assert vport.dxf.view_twist == 0.0 + + # ── headless: UCS ───────────────────────────────────────────────────────────