fix(task-14): seed AutoCAD's default scale list before adding an annotation scale headlessly
An ezdxf-authored drawing has no ACAD_SCALELIST entries, and
`_ensure_scale_entry` appended only the requested scale. AutoCAD repopulates
its default list only when the dictionary is *empty*, so the saved file
opened with a one-entry scale list and `SetVariable("CANNOSCALE", "1:1")`
failed with `Error setting system variable` -- the annotation scale could
no longer be changed in AutoCAD, while the tool reported `ok: True` and the
test pinned the degraded state (`len(keys) == 1`).
An empty list is now seeded with AutoCAD's own default metric list before
the requested scale is considered: the 17 entries a fresh AutoCAD 2026
drawing carries (`Documents.Add()` saved as DXF, MEASUREMENT 1), in that
order and under AutoCAD's own keys A0 ... A9, B0 ... B6. New entries take
the next key in that sequence (`_scale_list_key`) instead of being keyed by
their name; presence is still decided by group 300, so a non-empty
AutoCAD-authored list is never re-seeded or duplicated. Verified live: the
headless file now opens in AutoCAD 2026 with the 17 keys, CANNOSCALE 1:50
current, and switching to 1:1 and 1:100 works.
Tests: the fresh-drawing test asserts the seeded names and keys, that
setting 1:50 appends nothing (it is a default), that 1:25 lands under B7
once, and that the list survives save + reopen with 1:1 present; a new test
pins that *reading* the annotation scale creates nothing (the read side
stays pure, AutoCAD fills an empty list itself); the AutoCAD-keyed list test
now expects the A3 key. The `drawing_settings` docstring says what each
engine does and that AutoCAD refuses the live write on a paper-space layout
with no active viewport (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:
co-authored by
Claude Opus 5
parent
33604c4408
commit
909bc47e5e
+68
-24
@@ -5954,39 +5954,52 @@ class EzdxfBackend(AutoCADBackend):
|
||||
return str(tag.value)
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def _ensure_scale_entry(cls, doc, name: str, paper: float, drawing: float) -> bool:
|
||||
"""Add `name` to ACAD_SCALELIST unless present; True when added.
|
||||
# AutoCAD's default metric ACAD_SCALELIST, in its own order (measured:
|
||||
# AutoCAD 2026, MEASUREMENT 1, `Documents.Add()` saved as DXF — 17 SCALE
|
||||
# objects keyed A0 … A9, B0 … B6). An imperial seat (MEASUREMENT 0) adds the
|
||||
# architectural fractions on top; those are not seeded — every ratio here
|
||||
# is valid in either unit system, and AutoCAD's SCALELISTEDIT can add more.
|
||||
_DEFAULT_SCALE_LIST: tuple[str, ...] = (
|
||||
"1:1",
|
||||
"1:2",
|
||||
"1:4",
|
||||
"1:5",
|
||||
"1:8",
|
||||
"1:10",
|
||||
"1:16",
|
||||
"1:20",
|
||||
"1:30",
|
||||
"1:40",
|
||||
"1:50",
|
||||
"1:100",
|
||||
"2:1",
|
||||
"4:1",
|
||||
"8:1",
|
||||
"10:1",
|
||||
"100:1",
|
||||
)
|
||||
|
||||
Presence is decided by the SCALE objects' group-300 names, never by
|
||||
the dictionary key — an AutoCAD-authored drawing keys the list `A0`,
|
||||
`A1`, … (its default metric list already carries 1:1 … 1:100, 2:1),
|
||||
and matching on the key appended a duplicate `1:50` to every one of
|
||||
them. A new entry is keyed by its name unless that key is taken, in
|
||||
which case it takes the next free `A<n>` slot like AutoCAD does.
|
||||
@staticmethod
|
||||
def _scale_list_key(index: int) -> str:
|
||||
"""The dictionary key AutoCAD gives the ``index``-th SCALE: A0 … A9, B0 …"""
|
||||
return f"{chr(ord('A') + index // 10)}{index % 10}"
|
||||
|
||||
@classmethod
|
||||
def _add_scale_object(cls, doc, scales, name: str, paper: float, drawing: float) -> None:
|
||||
"""Author one SCALE object and file it under the next free AutoCAD-style key.
|
||||
|
||||
ezdxf has no SCALE entity class, so the object is authored as raw tags
|
||||
and loaded through the factory (a `DXFTagStorage` that exports
|
||||
verbatim). Measured: it round-trips, ezdxf registers the CLASS, and
|
||||
`doc.audit()` is clean after reopen. AutoCAD only honours a CANNOSCALE
|
||||
that names an entry of this list.
|
||||
`doc.audit()` is clean after reopen.
|
||||
"""
|
||||
from ezdxf.entities import factory
|
||||
from ezdxf.lldxf.extendedtags import ExtendedTags
|
||||
|
||||
scales = doc.rootdict.get("ACAD_SCALELIST")
|
||||
if scales is None:
|
||||
scales = doc.rootdict.add_new_dict("ACAD_SCALELIST")
|
||||
existing_keys = set(scales.keys())
|
||||
for _key, entry in scales.items():
|
||||
if cls._scale_entry_name(entry) == name:
|
||||
return False
|
||||
key = name
|
||||
if key in existing_keys:
|
||||
index = 0
|
||||
while f"A{index}" in existing_keys:
|
||||
index += 1
|
||||
key = f"A{index}"
|
||||
index = 0
|
||||
while cls._scale_list_key(index) in existing_keys:
|
||||
index += 1
|
||||
handle = doc.entitydb.next_handle()
|
||||
unit = 1 if paper == drawing else 0
|
||||
text = (
|
||||
@@ -5996,7 +6009,38 @@ class EzdxfBackend(AutoCADBackend):
|
||||
entry = factory.load(ExtendedTags.from_text(text), doc)
|
||||
doc.entitydb.add(entry)
|
||||
doc.objects.add_object(entry)
|
||||
scales.add(key, entry)
|
||||
scales.add(cls._scale_list_key(index), entry)
|
||||
|
||||
@classmethod
|
||||
def _ensure_scale_entry(cls, doc, name: str, paper: float, drawing: float) -> bool:
|
||||
"""Add `name` to ACAD_SCALELIST unless present; True when added.
|
||||
|
||||
Presence is decided by the SCALE objects' group-300 names, never by
|
||||
the dictionary key — an AutoCAD-authored drawing keys the list `A0`,
|
||||
`A1`, … and only group 300 says `1:50`; matching on the key appended a
|
||||
duplicate `1:50` to every one of them.
|
||||
|
||||
An ezdxf-authored drawing has **no** ACAD_SCALELIST entries at all.
|
||||
AutoCAD repopulates its default list only when the dictionary is
|
||||
empty, so appending the one requested scale to an empty list shipped a
|
||||
file whose scale list *was* that one entry — measured (AutoCAD 2026):
|
||||
`SetVariable("CANNOSCALE", "1:1")` then fails with `Error setting
|
||||
system variable` and the annotation scale cannot be changed in the
|
||||
UI. An empty list is therefore seeded with AutoCAD's own default list
|
||||
first, so the saved file offers what a fresh AutoCAD drawing offers.
|
||||
AutoCAD only honours a CANNOSCALE that names an entry of this list.
|
||||
"""
|
||||
scales = doc.rootdict.get("ACAD_SCALELIST")
|
||||
if scales is None:
|
||||
scales = doc.rootdict.add_new_dict("ACAD_SCALELIST")
|
||||
if len(scales) == 0:
|
||||
for default in cls._DEFAULT_SCALE_LIST:
|
||||
_, default_paper, default_drawing = parse_scale(default)
|
||||
cls._add_scale_object(doc, scales, default, default_paper, default_drawing)
|
||||
for _key, entry in scales.items():
|
||||
if cls._scale_entry_name(entry) == name:
|
||||
return False
|
||||
cls._add_scale_object(doc, scales, name, paper, drawing)
|
||||
return True
|
||||
|
||||
def _set_annotation_scale(self, doc, value: Any) -> str:
|
||||
|
||||
Reference in New Issue
Block a user