fix(task-15): angle sysvars are radians on both engines; OSMODE is registry-saved and refused headlessly
Three authored claims in SYSVAR_CATALOG were false against the engines: - HPANG said "degrees" but ActiveX GetVariable/SetVariable (and AutoLISP getvar) hold radians - measured on AutoCAD 2026 in a scratch document: SetVariable(pi/4) reads back 0.7853981633974483 on both paths. The row now says radians, like POLARANG. - ANGBASE said "radians" with both engines True, but the DXF header stores $ANGBASE in degrees (AutoCAD saved the 1.5708-radian seat value as 90.0) and ezdxf read/wrote it raw, so the two engines disagreed by a factor of 57.3. EzdxfBackend now translates that header variable at its boundary (_HEADER_DEGREE_SYSVARS): the tool speaks radians on both engines, the file keeps degrees. - OSMODE claimed engines.ezdxf True, but $OSMODE is an R12-only header variable ezdxf never exports for R2000+: a headless write reported ok: True and read back None after save + reopen. It is registry-saved in AutoCAD, so it joins _UNSAVED_SYSVARS (refused with capability: registry_sysvar; the facade's `osmode` key refuses and snapshots None headlessly, like polar / polar_angle). Headless reads of every registry-saved variable now report None instead of whatever the ezdxf template holds in memory. The gate that should have caught all three, tests/test_sysvar_catalog.py's default round-trip, wrote 56 no-ops out of 66 and never saved: it now writes a value that differs from the fresh document's for every writable ezdxf-True entry, saves, reopens and reads back from the file. Two new tests pin OSMODE's refusal and ANGBASE's radians-at-the-tool / degrees-in-the-file split. 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
125a8ed3b8
commit
75c13196b9
@@ -44,8 +44,9 @@ class SettingsContract:
|
||||
unknown key; a value outside ``_SETTING_RANGES``; a malformed
|
||||
``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``);
|
||||
save as R2000 or newer first); ``osmode`` / ``polar`` / ``polar_angle``
|
||||
on the headless engine (registry-saved — ``capability: registry_sysvar``;
|
||||
a snapshot reports them as ``None`` there, a file holds no value);
|
||||
``dimstyle`` / ``textstyle`` when the backend has no styles contract.
|
||||
|
||||
Note that a no-argument snapshot is one ``system_get_variable`` per key
|
||||
@@ -145,6 +146,8 @@ _SETTING_MAP: dict[str, tuple[str, str]] = {
|
||||
"text_size": ("TEXTSIZE", "float"),
|
||||
"point_mode": ("PDMODE", "int"),
|
||||
"point_size": ("PDSIZE", "float"),
|
||||
# OSMODE is registry-saved (a DXF R2000+ file has no $OSMODE), so the
|
||||
# headless engine refuses it like the polar pair below.
|
||||
"osmode": ("OSMODE", "int"),
|
||||
"fillet_radius": ("FILLETRAD", "float"),
|
||||
# ── track E: the environment a drawing lives in ──────────────────────
|
||||
|
||||
@@ -73,12 +73,18 @@ _VPORT_SYSVARS: dict[str, tuple[str, str]] = {
|
||||
#: Registry-saved or never-saved (AutoCAD System Variables reference, "Saved
|
||||
#: in" column). A file has nowhere to keep them, so the headless engine refuses
|
||||
#: a write with `capability: registry_sysvar` instead of storing a value that
|
||||
#: would vanish; `engineering.standards.sysvars` pins this set to its catalogue.
|
||||
#: would vanish, and reads report `None` rather than the value ezdxf's document
|
||||
#: template happens to hold in memory; `engineering.standards.sysvars` pins this
|
||||
#: set to its catalogue. OSMODE / ATTREQ / ATTDIA are the trap: ezdxf accepts
|
||||
#: `doc.header["$OSMODE"]` (an R12 header variable, `maxdxf="AC1009"` in
|
||||
#: `HEADER_VAR_MAP`) but never exports it for R2000+ - measured 2026-09-17: a
|
||||
#: write of 4134 to a fresh AC1024 document read back `None` after save + open.
|
||||
_UNSAVED_SYSVARS: frozenset[str] = frozenset(
|
||||
{
|
||||
"AUTOSNAP",
|
||||
"POLARANG",
|
||||
"POLARMODE",
|
||||
"OSMODE",
|
||||
"ATTREQ",
|
||||
"ATTDIA",
|
||||
"SAVETIME",
|
||||
@@ -95,6 +101,13 @@ _UNSAVED_SYSVARS: frozenset[str] = frozenset(
|
||||
}
|
||||
)
|
||||
|
||||
#: Header variables the DXF file stores in degrees (group code 50) while the
|
||||
#: sysvar boundary speaks radians - the unit ActiveX `GetVariable` and AutoLISP
|
||||
#: `getvar` use, so both engines report the same number. Measured on AutoCAD
|
||||
#: 2026 (2026-09-17): `SETVAR ANGBASE 90` reads back 1.5707963267948966 over
|
||||
#: COM and the DXF AutoCAD saves carries `$ANGBASE = 90.0`.
|
||||
_HEADER_DEGREE_SYSVARS: frozenset[str] = frozenset({"ANGBASE"})
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -5573,7 +5586,12 @@ class EzdxfBackend(AutoCADBackend):
|
||||
return self._dictionary_variable(doc, "CANNOSCALE", "1:1")
|
||||
if key == "CANNOSCALEVALUE":
|
||||
return scale_value(self._dictionary_variable(doc, "CANNOSCALE", "1:1"))
|
||||
return doc.header.get(f"${key}", None)
|
||||
if key in _UNSAVED_SYSVARS:
|
||||
return None # a file holds no value; the template's memory is not one
|
||||
raw = doc.header.get(f"${key}", None)
|
||||
if key in _HEADER_DEGREE_SYSVARS and raw is not None:
|
||||
return math.radians(float(raw))
|
||||
return raw
|
||||
|
||||
return await self._async(_sync)
|
||||
|
||||
@@ -5607,8 +5625,11 @@ class EzdxfBackend(AutoCADBackend):
|
||||
"CANNOSCALEVALUE is read-only; set CANNOSCALE (e.g. '1:50') instead."
|
||||
)
|
||||
else:
|
||||
stored = value
|
||||
if key in _HEADER_DEGREE_SYSVARS:
|
||||
stored = math.degrees(float(value))
|
||||
try:
|
||||
doc.header[f"${key}"] = value
|
||||
doc.header[f"${key}"] = stored
|
||||
except DXFKeyError as exc:
|
||||
# Keeps the pinned "$$DIMTXT" text (tests/test_dimension_header_vars.py)
|
||||
# while saying why the write is refused rather than dropped.
|
||||
|
||||
Reference in New Issue
Block a user