Files
autocad-mcp-pro/tests/test_standards_presets.py
T
Umut CANandClaude Opus 5 2e2cd2773d fix(task-9): signed DIMTP/DIMTM, read-side arrowhead spelling, and the Task 10 handshake
Two blocking review findings on the standards data layer:

* DIMTP and DIMTM were pinned to ("float", 0.0, 1e6), refusing every signed
  deviation with a false "must be at least 0". AutoCAD documents both as
  signed and the repo already relies on it: build_dim_override writes
  dimtm=-0.022 for ISO 286 p6 and dimtp=-0.020 for f7, and
  dimension_linear(fit=...) emits exactly those. A dimension style could
  therefore never hold a double-positive or double-negative fit that the same
  server draws correctly per dimension, and an agent obeying the message
  would ship a sign-flipped tolerance. Both are now ("float", -1e6, 1e6); a
  provenance test resolves the p6/f7/H7/h6 overrides build_dim_override
  emits through resolve_dimstyle and pins the signs.

* 827719f made check_dim_value return DIMBLK/DIMBLK1/DIMBLK2 in the
  canonical _OBLIQUE spelling and added ezdxf_arrowhead without telling
  Task 10, whose plan helpers store the value verbatim and read the raw
  string back - executed as written the headless drawing raised at render
  and at doc.write, a user block passed the data layer and failed at save,
  and ezdxf's OBLIQUE-in-session / _OBLIQUE-after-reload split made an
  idempotent re-set report a change (spec section 8.3). This commit adds the
  read-side twin reported_arrowhead (never raises; a foreign name passes
  through), pins it with an in-session-equals-reloaded round trip, and edits
  the plan: Task 9 Produces and Task 10 Consumes now name ARROWHEAD_BLOCKS,
  canonical_arrowhead, ezdxf_arrowhead and reported_arrowhead; Task 10's
  _write_dimstyle_values maps through ezdxf_arrowhead, _dimstyle_value and
  _com_dimvar_for_report map through reported_arrowhead, a new
  _require_arrowhead_blocks refuses a user block absent from doc.blocks
  before dimstyle_create/modify write anything (and rewrites the name to the
  block table's own spelling, measured to be what a reload reports), and two
  headless tests witness the rendered INSERTs, the save/reopen spelling and
  the refusal. The plan's own copy of the range table is corrected to match
  the module (arrow kind, signed tolerances). The edited helper block was
  executed against ezdxf 1.4.4 before being written into the plan.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
2026-09-16 21:52:01 +03:00

538 lines
19 KiB
Python

# tests/test_standards_presets.py
"""Provenance for the authored style presets, pinned by name.
Every ISO-25 and ANSI value is asserted against the table in spec §4.1 — a
reader with ISO 129-1 / ISO 3098-1 / ASME Y14.2 open can check each line.
The refusals are pinned too: a value the standards data lets through is a
value a backend writes, so the data is where "nothing written on refusal"
starts.
"""
from __future__ import annotations
import pytest
from engineering.standards.dimstyles import (
ARROWHEAD_BLOCKS,
DIM_VARIABLE_RANGES,
DIM_VARIABLE_WHITELIST,
LINEWEIGHT_CODES,
PRESET_VARIABLES,
PRESETS,
check_dim_value,
describe_preset,
ezdxf_arrowhead,
reported_arrowhead,
resolve_dimstyle,
validate_overrides,
)
from engineering.standards.mleaderstyles import (
MLEADER_KEYS,
MLEADER_PRESETS,
resolve_mleaderstyle,
)
from engineering.standards.textstyles import TEXT_PRESETS, resolve_font, validate_textstyle
# ── ISO-25: ISO 129-1:2018 at the 2.5 mm row of ISO 3098-1 ──────────────────
ISO_25_EXPECTED = {
"DIMTXT": 2.5,
"DIMASZ": 2.5,
"DIMEXE": 1.25,
"DIMEXO": 0.625,
"DIMGAP": 0.625,
"DIMTAD": 1,
"DIMTIH": 0,
"DIMTOH": 0,
"DIMDEC": 2,
"DIMDSEP": ",",
"DIMLUNIT": 2,
"DIMZIN": 8,
"DIMBLK": "",
"DIMTXSTY": "ISOCP",
"DIMLWD": -2,
"DIMLWE": -2,
"DIMSCALE": 1.0,
}
# ── ANSI: ASME Y14.2-2014, metric values ────────────────────────────────────
ANSI_EXPECTED = {
"DIMTXT": 3.0,
"DIMASZ": 3.0,
"DIMEXE": 1.5,
"DIMEXO": 1.5,
"DIMGAP": 1.0,
"DIMTAD": 0,
"DIMTIH": 1,
"DIMTOH": 1,
"DIMDEC": 2,
"DIMDSEP": ".",
"DIMLUNIT": 2,
"DIMZIN": 8,
"DIMBLK": "",
"DIMTXSTY": "ROMANS",
"DIMLWD": -2,
"DIMLWE": -2,
"DIMSCALE": 1.0,
}
@pytest.mark.parametrize(("variable", "expected"), sorted(ISO_25_EXPECTED.items()))
def test_iso25_value_by_name(variable, expected):
assert PRESETS["iso-25"][variable] == expected
assert type(PRESETS["iso-25"][variable]) is type(expected)
@pytest.mark.parametrize(("variable", "expected"), sorted(ANSI_EXPECTED.items()))
def test_ansi_value_by_name(variable, expected):
assert PRESETS["ansi"][variable] == expected
assert type(PRESETS["ansi"][variable]) is type(expected)
def test_presets_set_exactly_the_seventeen_spec_variables():
assert len(PRESET_VARIABLES) == 17
for name, preset in PRESETS.items():
assert tuple(preset) == PRESET_VARIABLES, name
def test_the_whitelist_is_the_seventeen_plus_the_sixteen_spec_extras():
extras = set(
"DIMTOL DIMTP DIMTM DIMTOLJ DIMTFAC DIMLFAC DIMRND DIMATFIT "
"DIMTMOVE DIMCLRD DIMCLRE DIMCLRT DIMSAH DIMBLK1 DIMBLK2 DIMCEN".split()
)
assert DIM_VARIABLE_WHITELIST == set(PRESET_VARIABLES) | extras
assert set(DIM_VARIABLE_RANGES) == DIM_VARIABLE_WHITELIST
def test_iso_lineweights_are_all_legal_codes():
for mm in (0.13, 0.18, 0.25, 0.35, 0.5, 0.7, 1.0, 1.4, 2.0):
assert round(mm * 100) in LINEWEIGHT_CODES
def test_describe_preset_names_the_standard():
iso = describe_preset("ISO-25")
assert iso["name"] == "iso-25"
assert "ISO 129-1" in iso["source"] and "ISO 3098-1" in iso["source"]
assert iso["values"] == ISO_25_EXPECTED and iso["textstyle"] == "ISOCP"
ansi = describe_preset("ansi")
assert "ASME Y14.2" in ansi["source"] and ansi["textstyle"] == "ROMANS"
with pytest.raises(ValueError, match="preset"):
describe_preset("din")
# ── resolve_dimstyle ────────────────────────────────────────────────────────
def test_none_preset_is_the_iso25_base():
assert resolve_dimstyle(None, None) == ISO_25_EXPECTED
assert resolve_dimstyle("ISO-25", {}) == ISO_25_EXPECTED
assert resolve_dimstyle("ansi", None) == ANSI_EXPECTED
def test_overrides_apply_on_top_and_are_upper_cased_and_typed():
values = resolve_dimstyle("iso-25", {"dimtxt": 3, "DIMDEC": 3.0, "dimdsep": "."})
assert values["DIMTXT"] == 3.0 and type(values["DIMTXT"]) is float
assert values["DIMDEC"] == 3 and type(values["DIMDEC"]) is int
assert values["DIMDSEP"] == "."
assert values["DIMASZ"] == 2.5, "untouched values stay the preset's"
def test_resolve_leaves_the_preset_table_untouched():
resolve_dimstyle("iso-25", {"DIMTXT": 9.0})
assert PRESETS["iso-25"]["DIMTXT"] == 2.5
@pytest.mark.parametrize(
("overrides", "fragment"),
[
({"DIMFOO": 1}, "DIMFOO"),
({"DIMDEC": 9}, "DIMDEC"),
({"DIMDEC": -1}, "DIMDEC"),
({"DIMDEC": 2.5}, "whole number"),
({"DIMTXT": 0}, "DIMTXT"),
({"DIMASZ": -1}, "DIMASZ"),
({"DIMTAD": 5}, "DIMTAD"),
({"DIMTIH": 2}, "DIMTIH"),
({"DIMDSEP": ",,"}, "DIMDSEP"),
({"DIMDSEP": ""}, "DIMDSEP"),
({"DIMLWD": 27}, "DIMLWD"),
({"DIMLUNIT": 0}, "DIMLUNIT"),
({"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"),
({"DIMRND": -0.5}, "DIMRND"),
({"DIMSCALE": float("nan")}, "DIMSCALE"),
],
)
def test_out_of_range_or_unknown_overrides_are_refused_by_name(overrides, fragment):
with pytest.raises(ValueError, match=fragment):
resolve_dimstyle("iso-25", overrides)
@pytest.mark.parametrize(
("overrides", "fragment"),
[
({"DIMTXT": "2.5"}, "DIMTXT"),
({"DIMTAD": True}, "DIMTAD"),
({"DIMDSEP": 44}, "DIMDSEP"),
({"DIMBLK": 0}, "DIMBLK"),
],
)
def test_wrong_types_are_a_type_error_naming_the_key(overrides, fragment):
with pytest.raises(TypeError, match=fragment):
resolve_dimstyle("iso-25", overrides)
def test_unknown_preset_is_refused():
with pytest.raises(ValueError, match="preset"):
resolve_dimstyle("din", None)
with pytest.raises(TypeError, match="overrides"):
resolve_dimstyle("iso-25", [("DIMTXT", 2.5)])
def test_validate_overrides_alone_is_what_modify_uses():
assert validate_overrides(None) == {}
assert validate_overrides({"dimgap": -1}) == {"DIMGAP": -1.0}, "negative gap boxes the text"
assert check_dim_value("dimlwe", 25) == 25
assert check_dim_value("DIMBLK", "_OBLIQUE") == "_OBLIQUE"
# ── tolerances: DIMTP / DIMTM are signed ────────────────────────────────────
@pytest.mark.parametrize(
("fit", "expected"),
[
("p6", {"DIMTP": 0.035, "DIMTM": -0.022}), # double positive: -DIMTM is +0.022
("f7", {"DIMTP": -0.02, "DIMTM": 0.041}), # double negative: DIMTP below zero
("H7", {"DIMTP": 0.021, "DIMTM": 0.0}),
("h6", {"DIMTP": 0.0, "DIMTM": 0.013}),
],
)
def test_a_style_holds_the_same_signed_deviations_a_dimension_gets(fit, expected):
"""``dimension_linear(fit=...)`` writes ``build_dim_override``'s DIMTP/DIMTM
onto one dimension; a dimension style must accept the identical numbers,
signs included, or a p6 / f7 fit can be drawn but never styled — and an
agent obeying an "at least 0" refusal would ship a sign-flipped tolerance."""
from engineering.fits import fit_lookup
from engineering.tolerances import build_dim_override
deviation = fit_lookup(fit, 20.0)
override, _ = build_dim_override(deviation.upper_mm, -deviation.lower_mm, "deviation", None)
styled = {k: v for k, v in override.items() if k.upper() in DIM_VARIABLE_WHITELIST}
assert {"dimtol", "dimtp", "dimtm"} <= set(styled)
typed = resolve_dimstyle("iso-25", styled)
assert typed["DIMTOL"] == 1
assert typed["DIMTP"] == pytest.approx(expected["DIMTP"])
assert typed["DIMTM"] == pytest.approx(expected["DIMTM"])
assert typed["DIMTM"] == pytest.approx(override["dimtm"]), "sign preserved, not abs()"
assert typed["DIMTP"] == pytest.approx(override["dimtp"])
@pytest.mark.parametrize("var", ["DIMTP", "DIMTM"])
def test_tolerance_limits_are_signed_finite_floats(var):
assert DIM_VARIABLE_RANGES[var] == ("float", -1e6, 1e6)
assert check_dim_value(var, -0.022) == -0.022
assert check_dim_value(var, 0) == 0.0
with pytest.raises(ValueError, match=var):
check_dim_value(var, float("-inf"))
with pytest.raises(ValueError, match=var):
check_dim_value(var, -1e7)
with pytest.raises(TypeError, match=var):
check_dim_value(var, "-0.022")
# ── 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())
@pytest.mark.parametrize(
("stored", "reported"),
[
("OBLIQUE", "_OBLIQUE"), # what ezdxf holds in-session after the writer set it
("_OBLIQUE", "_OBLIQUE"), # what ezdxf holds after a reload; what COM reports
("", ""),
(None, ""),
("_CLOSEDFILLED", ""),
("MYARROW", "MYARROW"),
("MY ARROW,1", "MY ARROW,1"), # a foreign drawing's name: passed through, never raised
(" ", " "),
],
)
def test_reported_arrowhead_spells_a_stored_name_one_way_and_never_raises(stored, reported):
assert reported_arrowhead(stored) == reported
@pytest.mark.parametrize("canonical", ["_OBLIQUE", "_DOT", "", "MYARROW"])
def test_a_style_reports_the_same_arrowhead_before_and_after_a_reload(canonical):
"""The spec §8.3 witness at the data layer: a raw read spells ``OBLIQUE``
in-session and ``_OBLIQUE`` after reopen, so ``dimstyle_modify`` re-setting
the same value would report a change. Through ``reported_arrowhead`` the two
reads agree with each other and with what ``check_dim_value`` accepts."""
import io
import ezdxf
doc = ezdxf.new("R2018")
if canonical == "MYARROW":
doc.blocks.new("MYARROW").add_line((0, 0), (1, 1))
style = doc.dimstyles.new("X")
style.dxf.dimblk = ezdxf_arrowhead(canonical)
in_session = reported_arrowhead(style.dxf.dimblk)
buffer = io.StringIO()
doc.write(buffer)
buffer.seek(0)
reloaded = reported_arrowhead(ezdxf.read(buffer).dimstyles.get("X").dxf.dimblk)
assert in_session == reloaded == canonical
assert check_dim_value("DIMBLK", in_session) == canonical
# ── 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 ─────────────────────────────────────────────────────────────
def test_text_presets_are_the_four_spec_fonts():
assert TEXT_PRESETS == {
"ISOCP": ("isocp.shx", 1.0, 0.0),
"ISOCPEUR": ("isocpeur.ttf", 1.0, 0.0),
"ARIAL": ("arial.ttf", 1.0, 0.0),
"ROMANS": ("romans.shx", 1.0, 0.0),
}
@pytest.mark.parametrize(
("font", "expected"),
[
("ISOCP", "isocp.shx"),
("isocp", "isocp.shx"),
("isocp.shx", "isocp.shx"),
("ISOCP.SHX", "isocp.shx"),
("ISOCPEUR", "isocpeur.ttf"),
("arial", "arial.ttf"),
("Arial.ttf", "arial.ttf"),
("romans.shx", "romans.shx"),
],
)
def test_a_preset_font_resolves_known(font, expected):
assert resolve_font(font) == (expected, True)
def test_an_unknown_font_passes_through_unknown_never_refused():
assert resolve_font("simplex.shx") == ("simplex.shx", False)
assert resolve_font(" c:/fonts/custom.ttf ") == ("c:/fonts/custom.ttf", False)
def test_empty_or_control_font_names_are_refused():
with pytest.raises(ValueError, match="font"):
resolve_font("")
with pytest.raises(ValueError, match="font"):
resolve_font("bad\nfont.shx")
with pytest.raises(TypeError, match="font"):
resolve_font(None)
def test_validate_textstyle_types_the_request():
spec = validate_textstyle(" ISOCP ", "isocp", 0, 1, 0)
assert spec == {
"name": "ISOCP",
"font_file": "isocp.shx",
"known": True,
"height": 0.0,
"width_factor": 1.0,
"oblique_deg": 0.0,
}
@pytest.mark.parametrize(
("kwargs", "fragment"),
[
({"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):
base = {
"name": "T",
"font": "arial.ttf",
"height": 0.0,
"width_factor": 1.0,
"oblique_deg": 0.0,
}
base.update(kwargs)
with pytest.raises(ValueError, match=fragment):
validate_textstyle(**base)
def test_validate_textstyle_refuses_wrong_types():
with pytest.raises(TypeError, match="width_factor"):
validate_textstyle("T", "arial.ttf", 0.0, "1", 0.0)
with pytest.raises(TypeError, match="height"):
validate_textstyle("T", "arial.ttf", True, 1.0, 0.0)
# ── multileader styles ──────────────────────────────────────────────────────
def test_mleader_presets_match_the_plan():
assert MLEADER_PRESETS == {
"iso": {"arrow_size": 2.5, "landing_gap": 1.0, "text_style": "ISOCP", "text_height": 2.5},
"ansi": {"arrow_size": 3.0, "landing_gap": 1.5, "text_style": "ROMANS", "text_height": 3.0},
}
assert MLEADER_KEYS == ("arrow_size", "landing_gap", "text_style", "text_height")
def test_resolve_mleaderstyle_applies_overrides():
values = resolve_mleaderstyle("ISO", {"arrow_size": 3, "text_style": " ARIAL "})
assert values == {
"arrow_size": 3.0,
"landing_gap": 1.0,
"text_style": "ARIAL",
"text_height": 2.5,
}
assert resolve_mleaderstyle("ansi") == MLEADER_PRESETS["ansi"]
assert resolve_mleaderstyle("iso", {"landing_gap": 0})["landing_gap"] == 0.0
@pytest.mark.parametrize(
("preset", "overrides", "exc", "fragment"),
[
("din", None, ValueError, "preset"),
(None, None, ValueError, "preset"),
("iso", {"colour": 1}, ValueError, "colour"),
("iso", {"arrow_size": 0}, ValueError, "arrow_size"),
("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"),
],
)
def test_resolve_mleaderstyle_refuses_by_name(preset, overrides, exc, fragment):
with pytest.raises(exc, match=fragment):
resolve_mleaderstyle(preset, overrides)