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
|
unknown key; a value outside ``_SETTING_RANGES``; a malformed
|
||||||
``limits`` / ``annotation_scale``; ``annotation_scale`` on an R12 file
|
``limits`` / ``annotation_scale``; ``annotation_scale`` on an R12 file
|
||||||
headlessly (no OBJECTS section, so the value would vanish at save —
|
headlessly (no OBJECTS section, so the value would vanish at save —
|
||||||
save as R2000 or newer first); ``polar`` / ``polar_angle`` on the
|
save as R2000 or newer first); ``osmode`` / ``polar`` / ``polar_angle``
|
||||||
headless engine (registry-saved — ``capability: registry_sysvar``);
|
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.
|
``dimstyle`` / ``textstyle`` when the backend has no styles contract.
|
||||||
|
|
||||||
Note that a no-argument snapshot is one ``system_get_variable`` per key
|
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"),
|
"text_size": ("TEXTSIZE", "float"),
|
||||||
"point_mode": ("PDMODE", "int"),
|
"point_mode": ("PDMODE", "int"),
|
||||||
"point_size": ("PDSIZE", "float"),
|
"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"),
|
"osmode": ("OSMODE", "int"),
|
||||||
"fillet_radius": ("FILLETRAD", "float"),
|
"fillet_radius": ("FILLETRAD", "float"),
|
||||||
# ── track E: the environment a drawing lives in ──────────────────────
|
# ── 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
|
#: Registry-saved or never-saved (AutoCAD System Variables reference, "Saved
|
||||||
#: in" column). A file has nowhere to keep them, so the headless engine refuses
|
#: 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
|
#: 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(
|
_UNSAVED_SYSVARS: frozenset[str] = frozenset(
|
||||||
{
|
{
|
||||||
"AUTOSNAP",
|
"AUTOSNAP",
|
||||||
"POLARANG",
|
"POLARANG",
|
||||||
"POLARMODE",
|
"POLARMODE",
|
||||||
|
"OSMODE",
|
||||||
"ATTREQ",
|
"ATTREQ",
|
||||||
"ATTDIA",
|
"ATTDIA",
|
||||||
"SAVETIME",
|
"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__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@@ -5573,7 +5586,12 @@ class EzdxfBackend(AutoCADBackend):
|
|||||||
return self._dictionary_variable(doc, "CANNOSCALE", "1:1")
|
return self._dictionary_variable(doc, "CANNOSCALE", "1:1")
|
||||||
if key == "CANNOSCALEVALUE":
|
if key == "CANNOSCALEVALUE":
|
||||||
return scale_value(self._dictionary_variable(doc, "CANNOSCALE", "1:1"))
|
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)
|
return await self._async(_sync)
|
||||||
|
|
||||||
@@ -5607,8 +5625,11 @@ class EzdxfBackend(AutoCADBackend):
|
|||||||
"CANNOSCALEVALUE is read-only; set CANNOSCALE (e.g. '1:50') instead."
|
"CANNOSCALEVALUE is read-only; set CANNOSCALE (e.g. '1:50') instead."
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
stored = value
|
||||||
|
if key in _HEADER_DEGREE_SYSVARS:
|
||||||
|
stored = math.degrees(float(value))
|
||||||
try:
|
try:
|
||||||
doc.header[f"${key}"] = value
|
doc.header[f"${key}"] = stored
|
||||||
except DXFKeyError as exc:
|
except DXFKeyError as exc:
|
||||||
# Keeps the pinned "$$DIMTXT" text (tests/test_dimension_header_vars.py)
|
# Keeps the pinned "$$DIMTXT" text (tests/test_dimension_header_vars.py)
|
||||||
# while saying why the write is refused rather than dropped.
|
# while saying why the write is refused rather than dropped.
|
||||||
|
|||||||
@@ -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
|
dictionary (CANNOSCALE) or the model-space layout (limits). Registry-saved
|
||||||
and never-saved variables are ``False`` because a file has nowhere to keep
|
and never-saved variables are ``False`` because a file has nowhere to keep
|
||||||
them, and ``EzdxfBackend.system_set_variable`` refuses them with
|
them, and ``EzdxfBackend.system_set_variable`` refuses them with
|
||||||
``capability: registry_sysvar``. A handful of drawing-saved variables are also
|
``capability: registry_sysvar`` (OSMODE is one of them: ``$OSMODE`` is an R12
|
||||||
``False`` because ezdxf 1.4 has no header slot for them (CTAB, VIEWCTR,
|
header variable that ezdxf never exports for R2000+, so a headless write
|
||||||
VIEWSIZE, ISOLINES, FACETRES); that refusal is a plain ``ValueError``.
|
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 /
|
Sources: the AutoCAD 2026 System Variables reference (type / saved-in /
|
||||||
initial value columns), the DXF Reference HEADER and VPORT sections, and
|
initial value columns), the DXF Reference HEADER and VPORT sections, and
|
||||||
@@ -464,9 +476,10 @@ _ENTRIES: tuple[SysVar, ...] = (
|
|||||||
_v(
|
_v(
|
||||||
"OSMODE",
|
"OSMODE",
|
||||||
"int",
|
"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),
|
range_=(0, 32767),
|
||||||
default=4133,
|
default=4133,
|
||||||
|
saved_in="registry",
|
||||||
friendly_key="osmode",
|
friendly_key="osmode",
|
||||||
),
|
),
|
||||||
# ── current properties ───────────────────────────────────────────────────
|
# ── current properties ───────────────────────────────────────────────────
|
||||||
@@ -543,7 +556,7 @@ _ENTRIES: tuple[SysVar, ...] = (
|
|||||||
_v(
|
_v(
|
||||||
"ANGBASE",
|
"ANGBASE",
|
||||||
"float",
|
"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,
|
default=0.0,
|
||||||
),
|
),
|
||||||
_v("ANGDIR", "bool", "positive angle direction: 0 counter-clockwise, 1 clockwise", default=0),
|
_v("ANGDIR", "bool", "positive angle direction: 0 counter-clockwise, 1 clockwise", default=0),
|
||||||
@@ -655,7 +668,7 @@ _ENTRIES: tuple[SysVar, ...] = (
|
|||||||
_v(
|
_v(
|
||||||
"HPANG",
|
"HPANG",
|
||||||
"float",
|
"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,
|
default=0.0,
|
||||||
saved_in="not_saved",
|
saved_in="not_saved",
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -5093,11 +5093,12 @@ async def drawing_settings(
|
|||||||
|
|
||||||
Refusals, per key, nothing else rolled back: an unknown key; a value outside
|
Refusals, per key, nothing else rolled back: an unknown key; a value outside
|
||||||
its range (grid/snap spacing > 0, polar_angle 0–360, precision 0–8, …); a
|
its range (grid/snap spacing > 0, polar_angle 0–360, precision 0–8, …); a
|
||||||
malformed `limits` or `annotation_scale`; `polar` / `polar_angle` on the
|
malformed `limits` or `annotation_scale`; `osmode` / `polar` / `polar_angle`
|
||||||
headless engine (`capability: registry_sysvar` — AutoCAD keeps them in the
|
on the headless engine (`capability: registry_sysvar` — AutoCAD keeps them in
|
||||||
registry, a file cannot); `dimstyle` / `textstyle` when the backend has no
|
the registry, a file cannot, and a headless snapshot reports them as `None`);
|
||||||
styles contract. On the live engine `annotation_scale` must name a scale in
|
`dimstyle` / `textstyle` when the backend has no styles contract. On the
|
||||||
the drawing's scale list (SCALELISTEDIT) and AutoCAD refuses the write on a
|
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
|
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
|
and refuses it on an R12 file (no OBJECTS section, so the value would
|
||||||
|
|||||||
+121
-21
@@ -1,14 +1,18 @@
|
|||||||
"""SYSVAR_CATALOG: authored facts about AutoCAD system variables, pinned to the engines.
|
"""SYSVAR_CATALOG: authored facts about AutoCAD system variables, pinned to the engines.
|
||||||
|
|
||||||
Every `engines["ezdxf"]` claim is checked against the real headless backend
|
Every `engines["ezdxf"]` claim is checked against the real headless backend
|
||||||
(read, and write of the default where the variable is writable); every
|
(read, and - where the variable is writable - a write of a value that differs
|
||||||
`saved_in` that is not "drawing" is checked to be exactly the set the backend
|
from the fresh document's, followed by a save and a reopen, so a header slot
|
||||||
refuses with `capability: registry_sysvar`. The catalogue cannot say a thing
|
ezdxf holds in memory but never exports cannot pass); every `saved_in` that is
|
||||||
the backend does not do.
|
not "drawing" is checked to be exactly the set the backend refuses with
|
||||||
|
`capability: registry_sysvar`. The catalogue cannot say a thing the backend
|
||||||
|
does not do.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import math
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from backends.base import UnsupportedCapabilityError
|
from backends.base import UnsupportedCapabilityError
|
||||||
@@ -71,10 +75,47 @@ SPEC_NAMES = (
|
|||||||
"ISOLINES FACETRES DISPSILH"
|
"ISOLINES FACETRES DISPSILH"
|
||||||
).split()
|
).split()
|
||||||
|
|
||||||
#: The two engines legitimately represent these differently (a character vs.
|
#: A value that is not the fresh document's, for every writable headless entry
|
||||||
#: its code; a colour name vs. its ACI number), so their default cannot be
|
#: the generic rule below cannot pick one for: the two engines represent DIMDSEP
|
||||||
#: written verbatim headlessly. Everything else round-trips its default.
|
#: and CECOLOR differently (a character vs. its code; a colour name vs. its ACI
|
||||||
_REPRESENTATION_DIFFERS = {"DIMDSEP", "CECOLOR"}
|
#: number), so the headless form is written; the name-valued ones must name
|
||||||
|
#: something the drawing has (`drawing_new` bootstraps GEOMETRY and CENTER;
|
||||||
|
#: `_set_annotation_scale` seeds AutoCAD's default scale list).
|
||||||
|
_PROBE_VALUES: dict[str, object] = {
|
||||||
|
"DIMDSEP": 44, # ',' as the character code the header holds (a fresh document has 46)
|
||||||
|
"CECOLOR": 1, # ACI red; the header holds the number
|
||||||
|
"CLAYER": "GEOMETRY",
|
||||||
|
"CELTYPE": "CENTER",
|
||||||
|
"CANNOSCALE": "1:2",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _probe_value(var: SysVar, fresh):
|
||||||
|
"""A legal value for ``var`` that differs from ``fresh``, so the write is not a no-op."""
|
||||||
|
if var.name in _PROBE_VALUES:
|
||||||
|
return _PROBE_VALUES[var.name]
|
||||||
|
if var.type == "bool":
|
||||||
|
return 0 if int(fresh) else 1
|
||||||
|
if var.type == "enum":
|
||||||
|
return next(code for code in var.enum if code != int(fresh))
|
||||||
|
if var.type in ("int", "float"):
|
||||||
|
step = 1 if var.type == "int" else 1.0
|
||||||
|
low, high = var.range if var.range is not None else (-math.inf, math.inf)
|
||||||
|
candidate = fresh + step
|
||||||
|
return candidate if candidate <= high else fresh - step
|
||||||
|
if var.type == "point":
|
||||||
|
return (float(fresh[0]) + 1.0, float(fresh[1]) + 2.0)
|
||||||
|
return f"{fresh}_PROBE"
|
||||||
|
|
||||||
|
|
||||||
|
def _same(var: SysVar, back, expected) -> bool:
|
||||||
|
if var.type == "point":
|
||||||
|
return tuple(float(v) for v in back[:2]) == tuple(float(v) for v in expected[:2])
|
||||||
|
if var.type in ("bool", "int", "enum"):
|
||||||
|
return int(back) == int(expected)
|
||||||
|
if var.type == "float":
|
||||||
|
return float(back) == pytest.approx(float(expected))
|
||||||
|
return back == expected
|
||||||
|
|
||||||
|
|
||||||
# ── the data is complete ────────────────────────────────────────────────────
|
# ── the data is complete ────────────────────────────────────────────────────
|
||||||
@@ -200,20 +241,79 @@ async def test_every_ezdxf_true_entry_reads_on_a_fresh_document(backend):
|
|||||||
|
|
||||||
|
|
||||||
@asyncio_test
|
@asyncio_test
|
||||||
async def test_every_writable_ezdxf_true_entry_round_trips_its_default(backend):
|
async def test_every_writable_ezdxf_true_entry_survives_a_save_and_reopen(backend, tmp_path):
|
||||||
for name, var in SYSVAR_CATALOG.items():
|
"""The write must land in the file, not only in memory.
|
||||||
if not var.engines["ezdxf"] or var.read_only or name in _REPRESENTATION_DIFFERS:
|
|
||||||
continue
|
The earlier form of this gate wrote each entry's *default* and read it back
|
||||||
await backend.system_set_variable(name, var.default)
|
without saving: 56 of its 66 writes were no-ops (default == fresh value)
|
||||||
|
and the rest could not tell a header slot ezdxf exports from one it holds
|
||||||
|
in memory and drops at save (`$OSMODE`, R12-only). Every write here moves
|
||||||
|
the value, and every read-back is from a reopened file.
|
||||||
|
"""
|
||||||
|
writable = {
|
||||||
|
name: var
|
||||||
|
for name, var in SYSVAR_CATALOG.items()
|
||||||
|
if var.engines["ezdxf"] and not var.read_only
|
||||||
|
}
|
||||||
|
probes: dict[str, object] = {}
|
||||||
|
for name, var in writable.items():
|
||||||
|
fresh = await backend.system_get_variable(name)
|
||||||
|
probe = _probe_value(var, fresh)
|
||||||
|
assert not _same(var, fresh, probe), f"{name}: the probe is a no-op write"
|
||||||
|
out = await backend.system_set_variable(name, probe)
|
||||||
|
assert out["ok"] is True, name
|
||||||
|
probes[name] = probe
|
||||||
|
|
||||||
|
path = str(tmp_path / "sysvars.dxf")
|
||||||
|
await backend.drawing_save_as(path)
|
||||||
|
await backend.drawing_open(path)
|
||||||
|
|
||||||
|
lost = {}
|
||||||
|
for name, probe in probes.items():
|
||||||
back = await backend.system_get_variable(name)
|
back = await backend.system_get_variable(name)
|
||||||
if var.type == "point":
|
if back is None or not _same(writable[name], back, probe):
|
||||||
assert tuple(float(v) for v in back[:2]) == tuple(float(v) for v in var.default), name
|
lost[name] = (probe, back)
|
||||||
elif var.type in ("bool", "int", "enum"):
|
assert lost == {}, f"engines.ezdxf is True but the value did not survive the file: {lost}"
|
||||||
assert int(back) == int(var.default), name
|
|
||||||
elif var.type == "float":
|
|
||||||
assert float(back) == pytest.approx(float(var.default)), name
|
@asyncio_test
|
||||||
else:
|
async def test_osmode_is_registry_saved_and_the_headless_engine_says_so(backend):
|
||||||
assert back == var.default, name
|
"""`$OSMODE` is an R12 header variable; ezdxf keeps it in memory and never
|
||||||
|
exports it for R2000+, so a write used to report `ok: True` and vanish at
|
||||||
|
save. It is registry-saved in AutoCAD and refused headlessly like POLARANG;
|
||||||
|
the read reports `None` instead of the template's memory."""
|
||||||
|
row = describe_sysvar("OSMODE")
|
||||||
|
assert row["saved_in"] == "registry" and row["engines"] == {"ezdxf": False, "com": True}
|
||||||
|
assert await backend.system_get_variable("OSMODE") is None
|
||||||
|
with pytest.raises(UnsupportedCapabilityError) as excinfo:
|
||||||
|
await backend.system_set_variable("OSMODE", 4134)
|
||||||
|
assert excinfo.value.capability == "registry_sysvar"
|
||||||
|
# the facade follows: no guess in the snapshot, a per-key refusal on write
|
||||||
|
assert (await backend.drawing_settings())["settings"]["osmode"] is None
|
||||||
|
res = await backend.drawing_settings({"osmode": 4134})
|
||||||
|
assert "registry" in res["errors"]["osmode"]
|
||||||
|
|
||||||
|
|
||||||
|
@asyncio_test
|
||||||
|
async def test_angbase_is_radians_at_the_boundary_and_degrees_in_the_file(backend, tmp_path):
|
||||||
|
"""ActiveX `GetVariable("ANGBASE")` and AutoLISP `getvar` hold radians
|
||||||
|
(measured: `SETVAR ANGBASE 90` reads back 1.5707963267948966), while the
|
||||||
|
DXF header stores `$ANGBASE` in degrees (the same drawing saved by AutoCAD
|
||||||
|
carries 90.0). The headless engine translates, so both engines report the
|
||||||
|
same number and the catalogue's "radians" is true on both."""
|
||||||
|
assert "radians" in describe_sysvar("ANGBASE")["meaning"]
|
||||||
|
assert "radians" in describe_sysvar("HPANG")["meaning"]
|
||||||
|
await backend.system_set_variable("ANGBASE", math.pi / 2)
|
||||||
|
assert backend._doc.header["$ANGBASE"] == pytest.approx(90.0)
|
||||||
|
assert await backend.system_get_variable("ANGBASE") == pytest.approx(math.pi / 2)
|
||||||
|
|
||||||
|
path = str(tmp_path / "angbase.dxf")
|
||||||
|
await backend.drawing_save_as(path)
|
||||||
|
text = open(path, encoding="utf-8").read()
|
||||||
|
start = text.index("$ANGBASE")
|
||||||
|
assert "90.0" in text[start : start + 40], text[start : start + 40]
|
||||||
|
await backend.drawing_open(path)
|
||||||
|
assert await backend.system_get_variable("ANGBASE") == pytest.approx(math.pi / 2)
|
||||||
|
|
||||||
|
|
||||||
@asyncio_test
|
@asyncio_test
|
||||||
|
|||||||
Reference in New Issue
Block a user