fix(task-9): finite text-style numbers, one symbol-name rule, canonical DIMBLK arrowheads
Three review findings on the standards data layer, each a value the layer
let through that a backend would then write as a silent wrong number or a
drawing that can no longer be saved:
* validate_textstyle never checked math.isfinite, unlike its siblings; nan
and inf passed every comparison and reached the STYLE record verbatim.
It now has the same _number helper dimstyles and mleaderstyles use.
* The three private _ILLEGAL_NAME_CHARS copies omitted \n \r \t that
security._FORBIDDEN_SYMBOL_CHARS holds, so 'a\nb' was accepted as a text
style, DIMTXSTY or leader text style and ezdxf refused the save. The rule
now lives once in engineering/standards/names.py (fastmcp-free), the
three modules import it, and a test pins its character set equal to
security's and its output equal to illegal_symbol_name_chars.
* DIMBLK/DIMBLK1/DIMBLK2 were ("str",): any string went through, and the
test pinned '_OBLIQUE', which ezdxf's writer refuses at doc.write while
the COM engine wants exactly that spelling. They are now ("arrow",):
canonical_arrowhead accepts a built-in with or without its underscore in
any case (and '', '.', CLOSEDFILLED for the closed filled default), emits
AutoCAD's documented block-name spelling (also what ezdxf reports after a
load), and holds user block names to the symbol-name rule; ARROWHEAD_BLOCKS
is pinned equal to ezdxf's ARROWS vocabulary, and ezdxf_arrowhead is the
one translation the headless writer needs, proved by exporting all twenty
headlessly and reading each back as its canonical name.
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
54fac471a9
commit
827719fdd0
@@ -13,6 +13,7 @@ from __future__ import annotations
|
||||
import pytest
|
||||
|
||||
from engineering.standards.dimstyles import (
|
||||
ARROWHEAD_BLOCKS,
|
||||
DIM_VARIABLE_RANGES,
|
||||
DIM_VARIABLE_WHITELIST,
|
||||
LINEWEIGHT_CODES,
|
||||
@@ -20,6 +21,7 @@ from engineering.standards.dimstyles import (
|
||||
PRESETS,
|
||||
check_dim_value,
|
||||
describe_preset,
|
||||
ezdxf_arrowhead,
|
||||
resolve_dimstyle,
|
||||
validate_overrides,
|
||||
)
|
||||
@@ -158,6 +160,12 @@ def test_resolve_leaves_the_preset_table_untouched():
|
||||
({"DIMZIN": 16}, "DIMZIN"),
|
||||
({"DIMTXSTY": ""}, "DIMTXSTY"),
|
||||
({"DIMTXSTY": "a/b"}, "DIMTXSTY"),
|
||||
({"DIMTXSTY": "a\nb"}, "DIMTXSTY"),
|
||||
({"DIMTXSTY": "a\tb"}, "DIMTXSTY"),
|
||||
({"DIMBLK": "a/b"}, "DIMBLK"),
|
||||
({"DIMBLK": "a,b"}, "DIMBLK"),
|
||||
({"DIMBLK1": "x\ny"}, "DIMBLK1"),
|
||||
({"DIMBLK2": " "}, "DIMBLK2"),
|
||||
({"DIMLFAC": 0}, "DIMLFAC"),
|
||||
({"DIMTFAC": 0}, "DIMTFAC"),
|
||||
({"DIMCLRT": 257}, "DIMCLRT"),
|
||||
@@ -198,6 +206,110 @@ def test_validate_overrides_alone_is_what_modify_uses():
|
||||
assert check_dim_value("DIMBLK", "_OBLIQUE") == "_OBLIQUE"
|
||||
|
||||
|
||||
# ── arrowheads: DIMBLK / DIMBLK1 / DIMBLK2 ──────────────────────────────────
|
||||
|
||||
|
||||
def test_the_builtin_arrowheads_are_autocads_nineteen_named_blocks():
|
||||
assert ARROWHEAD_BLOCKS == frozenset(
|
||||
"_ARCHTICK _BOXBLANK _BOXFILLED _CLOSED _CLOSEDBLANK _DATUMBLANK _DATUMFILLED "
|
||||
"_DOT _DOTBLANK _DOTSMALL _INTEGRAL _NONE _OBLIQUE _OPEN _OPEN30 _OPEN90 "
|
||||
"_ORIGIN _ORIGIN2 _SMALL".split()
|
||||
)
|
||||
|
||||
|
||||
def test_the_builtin_arrowheads_are_exactly_ezdxfs_vocabulary():
|
||||
from ezdxf.render.arrows import ARROWS
|
||||
|
||||
assert ARROWHEAD_BLOCKS == {ARROWS.block_name(name) for name in ARROWS.__acad__ if name}
|
||||
assert ARROWS.block_name("") == "_CLOSEDFILLED", "closed filled is the nameless default"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("given", "canonical"),
|
||||
[
|
||||
("", ""),
|
||||
(".", ""),
|
||||
("CLOSEDFILLED", ""),
|
||||
("_ClosedFilled", ""),
|
||||
("_OBLIQUE", "_OBLIQUE"),
|
||||
("OBLIQUE", "_OBLIQUE"),
|
||||
("oblique", "_OBLIQUE"),
|
||||
(" _dot ", "_DOT"),
|
||||
("Open30", "_OPEN30"),
|
||||
("none", "_NONE"),
|
||||
("MYARROW", "MYARROW"),
|
||||
(" my_arrow-1 ", "my_arrow-1"),
|
||||
],
|
||||
)
|
||||
def test_arrowhead_names_canonicalise_to_autocads_documented_spelling(given, canonical):
|
||||
for var in ("DIMBLK", "DIMBLK1", "DIMBLK2"):
|
||||
assert check_dim_value(var, given) == canonical, var
|
||||
|
||||
|
||||
@pytest.mark.parametrize("bad", ["a/b", "a,b", "x\ny", "x\ty", "x\rx", "a\x01b", " ", "\t"])
|
||||
def test_arrowhead_user_blocks_obey_the_symbol_name_rule(bad):
|
||||
for var in ("DIMBLK", "DIMBLK1", "DIMBLK2"):
|
||||
with pytest.raises(ValueError, match=var):
|
||||
check_dim_value(var, bad)
|
||||
|
||||
|
||||
def test_ezdxf_arrowhead_strips_the_underscore_only_for_built_ins():
|
||||
assert ezdxf_arrowhead("") == ""
|
||||
assert ezdxf_arrowhead("_OBLIQUE") == "OBLIQUE"
|
||||
assert ezdxf_arrowhead("_DOT") == "DOT"
|
||||
assert ezdxf_arrowhead("MYARROW") == "MYARROW"
|
||||
assert ezdxf_arrowhead("_MYBLOCK") == "_MYBLOCK", (
|
||||
"a user block with an underscore is not built in"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("canonical", [*sorted(ARROWHEAD_BLOCKS), ""])
|
||||
def test_every_canonical_arrowhead_exports_headlessly_and_reads_back_as_itself(canonical):
|
||||
"""The canonical spelling is what ezdxf itself reports after a load; only
|
||||
its *writer* wants the underscore-less name, and ``ezdxf_arrowhead`` is that
|
||||
one translation."""
|
||||
import io
|
||||
|
||||
import ezdxf
|
||||
|
||||
doc = ezdxf.new("R2018")
|
||||
style = doc.dimstyles.new("X")
|
||||
style.dxf.dimblk = ezdxf_arrowhead(canonical)
|
||||
style.dxf.dimblk1 = ezdxf_arrowhead(canonical)
|
||||
buffer = io.StringIO()
|
||||
doc.write(buffer)
|
||||
buffer.seek(0)
|
||||
reloaded = ezdxf.read(buffer).dimstyles.get("X")
|
||||
assert reloaded.dxf.dimblk == canonical
|
||||
assert reloaded.dxf.dimblk1 == canonical
|
||||
|
||||
|
||||
def test_the_canonical_spelling_is_not_what_the_headless_writer_takes():
|
||||
"""Why ``ezdxf_arrowhead`` exists: ``_OBLIQUE`` stored verbatim raises at
|
||||
``doc.write``, after ``dimstyle_create`` would already have said ``ok``."""
|
||||
import io
|
||||
|
||||
import ezdxf
|
||||
from ezdxf.lldxf.const import DXFValueError
|
||||
|
||||
doc = ezdxf.new("R2018")
|
||||
doc.dimstyles.new("X").dxf.dimblk = "_OBLIQUE"
|
||||
with pytest.raises(DXFValueError, match="_OBLIQUE"):
|
||||
doc.write(io.StringIO())
|
||||
|
||||
|
||||
# ── the symbol-name rule ────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def test_the_name_rule_is_the_one_security_applies():
|
||||
import security
|
||||
from engineering.standards.names import ILLEGAL_NAME_CHARS, illegal_name_chars
|
||||
|
||||
assert ILLEGAL_NAME_CHARS == frozenset(security._FORBIDDEN_SYMBOL_CHARS)
|
||||
for sample in ("a\nb", "a\tb", "a\rb", "a\x01b", "a/b", "a<b>c", "ok_name-1", "ISO-25"):
|
||||
assert illegal_name_chars(sample) == security.illegal_symbol_name_chars(sample), sample
|
||||
|
||||
|
||||
# ── text styles ─────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -258,10 +370,19 @@ def test_validate_textstyle_types_the_request():
|
||||
[
|
||||
({"name": ""}, "name"),
|
||||
({"name": "A:B"}, "name"),
|
||||
({"name": "a\nb"}, "name"),
|
||||
({"name": "a\tb"}, "name"),
|
||||
({"name": "a\rb"}, "name"),
|
||||
({"height": -1}, "height"),
|
||||
({"height": float("nan")}, "height"),
|
||||
({"height": float("inf")}, "height"),
|
||||
({"width_factor": 0}, "width_factor"),
|
||||
({"width_factor": float("nan")}, "width_factor"),
|
||||
({"width_factor": float("inf")}, "width_factor"),
|
||||
({"oblique_deg": 86}, "oblique_deg"),
|
||||
({"oblique_deg": -90}, "oblique_deg"),
|
||||
({"oblique_deg": float("nan")}, "oblique_deg"),
|
||||
({"oblique_deg": float("-inf")}, "oblique_deg"),
|
||||
],
|
||||
)
|
||||
def test_validate_textstyle_refuses_by_field(kwargs, fragment):
|
||||
@@ -317,6 +438,9 @@ def test_resolve_mleaderstyle_applies_overrides():
|
||||
("iso", {"text_height": -2}, ValueError, "text_height"),
|
||||
("iso", {"text_style": ""}, ValueError, "text_style"),
|
||||
("iso", {"text_style": "a|b"}, ValueError, "text_style"),
|
||||
("iso", {"text_style": "a\nb"}, ValueError, "text_style"),
|
||||
("iso", {"text_style": "a\tb"}, ValueError, "text_style"),
|
||||
("iso", {"arrow_size": float("nan")}, ValueError, "arrow_size"),
|
||||
("iso", {"arrow_size": "2.5"}, TypeError, "arrow_size"),
|
||||
("iso", ["arrow_size"], TypeError, "overrides"),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user