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:
Umut CAN
2026-09-17 15:20:02 +03:00
co-authored by Claude Opus 5
parent 125a8ed3b8
commit 75c13196b9
5 changed files with 175 additions and 37 deletions
+19 -6
View File
@@ -12,9 +12,21 @@ variable ezdxf will export, the active VPORT (grid/snap), the variable
dictionary (CANNOSCALE) or the model-space layout (limits). Registry-saved
and never-saved variables are ``False`` because a file has nowhere to keep
them, and ``EzdxfBackend.system_set_variable`` refuses them with
``capability: registry_sysvar``. A handful of drawing-saved variables are also
``False`` because ezdxf 1.4 has no header slot for them (CTAB, VIEWCTR,
VIEWSIZE, ISOLINES, FACETRES); that refusal is a plain ``ValueError``.
``capability: registry_sysvar`` (OSMODE is one of them: ``$OSMODE`` is an R12
header variable that ezdxf never exports for R2000+, so a headless write
looked accepted and vanished at save until it joined the refused set). A
handful of drawing-saved variables are also ``False`` because ezdxf 1.4 has no
header slot for them (CTAB, VIEWCTR, VIEWSIZE, ISOLINES, FACETRES); that
refusal is a plain ``ValueError``.
Angles are **radians** at the ``system_get_variable`` / ``system_set_variable``
boundary on both engines - the unit ActiveX ``GetVariable`` / ``SetVariable``
and AutoLISP ``getvar`` use (measured on AutoCAD 2026: ``SETVAR ANGBASE 90``
reads back 1.5707963267948966, and so does HPANG after ``SETVAR HPANG 45``).
The DXF header stores ``$ANGBASE`` in degrees (group code 50; the same
document saved by AutoCAD carries ``$ANGBASE = 90.0``), so the headless engine
translates that one variable at its boundary and the two engines report the
same number. ``drawing_settings`` speaks degrees for its friendly keys.
Sources: the AutoCAD 2026 System Variables reference (type / saved-in /
initial value columns), the DXF Reference HEADER and VPORT sections, and
@@ -464,9 +476,10 @@ _ENTRIES: tuple[SysVar, ...] = (
_v(
"OSMODE",
"int",
"running object snap bitmask (1 end, 2 mid, 4 cen, 8 nod, 16 qua, 32 int, 64 ins, 128 per, 256 tan, 512 nea, 1024 quick, 2048 app, 4096 ext, 8192 par, 16384 off); headlessly held in memory only, DXF R2000+ does not carry it",
"running object snap bitmask (1 end, 2 mid, 4 cen, 8 nod, 16 qua, 32 int, 64 ins, 128 per, 256 tan, 512 nea, 1024 quick, 2048 app, 4096 ext, 8192 par, 16384 off); registry-saved, and a DXF R2000+ file carries no $OSMODE, so the headless engine refuses it",
range_=(0, 32767),
default=4133,
saved_in="registry",
friendly_key="osmode",
),
# ── current properties ───────────────────────────────────────────────────
@@ -543,7 +556,7 @@ _ENTRIES: tuple[SysVar, ...] = (
_v(
"ANGBASE",
"float",
"direction of angle 0 relative to the current UCS, in radians",
"direction of angle 0 relative to the current UCS, in radians on both engines (90 degrees = 1.5708; the DXF header stores degrees and the headless engine translates)",
default=0.0,
),
_v("ANGDIR", "bool", "positive angle direction: 0 counter-clockwise, 1 clockwise", default=0),
@@ -655,7 +668,7 @@ _ENTRIES: tuple[SysVar, ...] = (
_v(
"HPANG",
"float",
"default hatch pattern angle in degrees",
"default hatch pattern angle in radians, as ActiveX and AutoLISP hold it (45 degrees = 0.7854)",
default=0.0,
saved_in="not_saved",
),