fix(task-14): refuse CANNOSCALE on an R12 file headlessly instead of losing it at save
Measured: an R12 (AC1009) document has no OBJECTS section, so ezdxf exports neither AcDbVariableDictionary/DICTIONARYVAR nor the seeded SCALE objects, and drawing_save_as keeps AC1009. `_set_annotation_scale` reported `ok: True, changed 1:1 -> 1:50` and the file reloaded as 1:1 -- the header-only write that vanishes on reload, the class this task exists to remove, and a breach of the backend's own R12 honesty convention (viewport_set_scale / lock). The write is now refused with a ValueError naming R12 and "save as R2000 or newer", after parse_scale (a malformed value is still named as such) and before either dictionary is touched, so a refused call leaves the root dictionary as it found it and drawing_settings still applies the sibling keys (limits / grid / ortho survive R12). The refusal is listed with the others in the contract and server docstrings. Pinned by two tests that save, reopen and read the R12 file back. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
This commit is contained in:
co-authored by
Claude Opus 5
parent
909bc47e5e
commit
f3ff703144
@@ -42,7 +42,9 @@ class SettingsContract:
|
||||
|
||||
Refusals (per key, in ``errors``; the other keys still apply): an
|
||||
unknown key; a value outside ``_SETTING_RANGES``; a malformed
|
||||
``limits`` / ``annotation_scale``; ``polar`` / ``polar_angle`` on the
|
||||
``limits`` / ``annotation_scale``; ``annotation_scale`` on an R12 file
|
||||
headlessly (no OBJECTS section, so the value would vanish at save —
|
||||
save as R2000 or newer first); ``polar`` / ``polar_angle`` on the
|
||||
headless engine (registry-saved — ``capability: registry_sysvar``);
|
||||
``dimstyle`` / ``textstyle`` when the backend has no styles contract.
|
||||
|
||||
|
||||
@@ -6045,6 +6045,18 @@ class EzdxfBackend(AutoCADBackend):
|
||||
|
||||
def _set_annotation_scale(self, doc, value: Any) -> str:
|
||||
name, paper, drawing = parse_scale(value) # refused before any write
|
||||
if self._is_r12():
|
||||
# Measured: an R12 (AC1009) file has no OBJECTS section, so ezdxf
|
||||
# exports neither AcDbVariableDictionary/DICTIONARYVAR nor the
|
||||
# SCALE objects. The in-memory write "succeeded" and reloaded as
|
||||
# 1:1 -- the header-only-write-that-vanishes class again. Refused
|
||||
# here, before either dictionary is created, so a refused call
|
||||
# leaves the root dictionary as it found it.
|
||||
raise ValueError(
|
||||
"CANNOSCALE: R12 has no OBJECTS section, so the annotation scale "
|
||||
"(AcDbVariableDictionary and ACAD_SCALELIST) would be lost on save. "
|
||||
"Save as R2000 or newer first."
|
||||
)
|
||||
self._ensure_scale_entry(doc, name, paper, drawing)
|
||||
vardict = self._variable_dictionary(doc, create=True)
|
||||
entry = vardict.get("CANNOSCALE")
|
||||
|
||||
@@ -5084,7 +5084,9 @@ async def drawing_settings(
|
||||
styles contract. On the live engine `annotation_scale` must name a scale in
|
||||
the drawing's scale list (SCALELISTEDIT) and AutoCAD refuses the write on a
|
||||
paper-space layout with no active viewport; the headless engine adds the
|
||||
scale, seeding AutoCAD's default list first when the drawing has none.
|
||||
scale, seeding AutoCAD's default list first when the drawing has none,
|
||||
and refuses it on an R12 file (no OBJECTS section, so the value would
|
||||
vanish at save — save as R2000 or newer first; limits/grid/ortho survive).
|
||||
Headlessly, grid/snap are stored on the active VPORT and the annotation
|
||||
scale in the variable dictionary — the places AutoCAD reads them from.
|
||||
"""
|
||||
|
||||
@@ -387,6 +387,66 @@ async def test_cannoscalevalue_is_read_only(backend):
|
||||
await backend.system_set_variable("CANNOSCALEVALUE", 0.5)
|
||||
|
||||
|
||||
async def _r12_backend(tmp_path):
|
||||
import ezdxf
|
||||
|
||||
from backends.ezdxf_backend import EzdxfBackend
|
||||
|
||||
path = tmp_path / "r12.dxf"
|
||||
ezdxf.new("R12").saveas(str(path))
|
||||
backend = EzdxfBackend()
|
||||
await backend.connect()
|
||||
await backend.drawing_open(str(path))
|
||||
return backend
|
||||
|
||||
|
||||
async def test_annotation_scale_on_r12_is_refused_rather_than_lost(tmp_path):
|
||||
"""Measured: an R12 (AC1009) file has no OBJECTS section, so ezdxf exports
|
||||
neither AcDbVariableDictionary nor ACAD_SCALELIST — a CANNOSCALE written to
|
||||
the in-memory document reported `changed 1:1 -> 1:50` and reloaded as 1:1.
|
||||
The write is refused up front, before any dictionary is created, and the
|
||||
other settings on the same call still apply (limits survive R12)."""
|
||||
backend = await _r12_backend(tmp_path)
|
||||
try:
|
||||
# ezdxf seeds an *empty* ACAD_SCALELIST at new(); the refusal must not
|
||||
# populate it (the default-list seeding) nor create the variable dict.
|
||||
assert len(backend._doc.rootdict.get("ACAD_SCALELIST")) == 0
|
||||
with pytest.raises(ValueError, match="R12") as excinfo:
|
||||
await backend.system_set_variable("CANNOSCALE", "1:50")
|
||||
assert "R2000 or newer" in str(excinfo.value)
|
||||
assert backend._doc.rootdict.get("AcDbVariableDictionary") is None
|
||||
assert len(backend._doc.rootdict.get("ACAD_SCALELIST")) == 0
|
||||
|
||||
res = await backend.drawing_settings(
|
||||
{"annotation_scale": "1:50", "limits": [[0, 0], [420, 297]]}
|
||||
)
|
||||
assert res["ok"] is False
|
||||
assert "R12" in res["errors"]["annotation_scale"]
|
||||
assert "annotation_scale" not in res["applied"]
|
||||
assert res["applied"]["limits"] == [[0, 0], [420, 297]]
|
||||
|
||||
out = tmp_path / "r12_out.dxf"
|
||||
await backend.drawing_save_as(str(out))
|
||||
await backend.drawing_open(str(out))
|
||||
assert backend._doc.dxfversion == "AC1009"
|
||||
read = (await backend.drawing_settings())["settings"]
|
||||
assert read["annotation_scale"] == {"name": "1:1", "value": 1.0}
|
||||
assert read["limits"] == [[0.0, 0.0], [420.0, 297.0]]
|
||||
finally:
|
||||
await backend.disconnect()
|
||||
|
||||
|
||||
async def test_annotation_scale_on_r12_refuses_a_malformed_value_by_the_value(tmp_path):
|
||||
"""A malformed scale is the caller's mistake whatever the file version, so
|
||||
it is named as such — the R12 refusal is not allowed to mask it."""
|
||||
backend = await _r12_backend(tmp_path)
|
||||
try:
|
||||
with pytest.raises(ValueError, match="paper:drawing"):
|
||||
await backend.system_set_variable("CANNOSCALE", "fifty")
|
||||
finally:
|
||||
await backend.disconnect()
|
||||
|
||||
|
||||
def test_both_capability_maps_declare_registry_sysvar():
|
||||
from backends.com_backend import ComBackend
|
||||
from backends.ezdxf_backend import EzdxfBackend
|
||||
|
||||
Reference in New Issue
Block a user