engineering/mech/features.py is the nineteen typed features of spec section 5,
each answering the same four questions - is my placement legal, what do I
contribute to this view, to a cut face, and what dimension do I want - plus the
five structural hooks the view engine needs: removal_box, radial_edit,
corner_edit, outline_edit and omission. Pure arithmetic; a test asserts emitted
primitive tuples, not a screenshot.
Nothing restates a table the repository already owns: Keyway takes its width
and depth from engineering.keyway (DIN 6885-1) and is refused by name outside
that table's bore range; GearTeeth takes its outline from engineering.gear's
involute generator and is refused when its tip diameter disagrees with the
segment it sits on.
standards/threads.py transcribes ISO 261 Table 1 and Table 2 - first-choice
M1.6-M64 plus second-choice M14/M18/M22/M27/M33/M39/M45/M52/M60 - and computes
ISO 724's basic minor and pitch diameters from ISO 68-1's own formula, pinned
against the published values (M20x2.5 -> 17.294 / 18.376). The remaining
second-choice diameters, every third-choice diameter and fine pitches below M8
are not transcribed and are refused by name, never interpolated. The drawing
rules are ISO 6410: minor at 0.8 x major, a thread-length limit line across the
major, a three-quarter circle in the end view, and an internal thread whose
section hatch runs to the major diameter.
standards/{undercuts,rings,centres,orings}.py ship DIN 509, DIN 471/472,
DIN 332-1 and ISO 3601-2 with their SOURCE, their row-key contract and an
**empty** row set. That is deliberate and is the track's table rule taken at its
word: a row that could not be verified against the standard is not shipped,
because a wrong relief depth or groove diameter is a wrong workshop drawing
while a narrow table is merely narrow. Every lookup is refused by name and says
what to transcribe and where; the features accept explicit dimensions so the
geometry is fully exercised, and DIN 332 form R is refused outright rather than
approximated by the straight 60 degree cone of forms A and B. Filling any of
these tables later is a data edit with no code change, and a shape test already
fails any row that lands without a column the drawing code reads.
Three deviations from the plan text, each measured rather than assumed
(the merge agent should keep exactly one copy of the first):
1. standards/__init__.py (Task 1, on main) refused a registration with no rows
("registered no rows"), which would have made the four not-transcribed
tables impossible to register at all - and registering them is what keeps a
later transcription a data edit. register() now refuses a rows argument that
is not a dict and accepts an empty one; two tests in tests/test_mech_standards.py
pin both halves. No existing test asserted the old refusal.
2. ORingGroove's housing kind is carried by `housing`, not `kind`. A dataclass
field named `kind` overwrites the discriminator _Base.to_dict() writes:
measured, Probe(id='og').to_dict() == {'kind': 'radial', 'id': 'og'}, so
feature_from_dict would then refuse its own payload as an unknown feature
kind and no part carrying an O-ring groove could round-trip through XDATA.
A new test pins the round trip.
3. The plan's end-chamfer test asserted after == (75.0, 10.0) - the corner
itself - and before == (72.0, 8.268), a point on neither surface. The real
edit is before (72.0, 10.0) on the cylinder and after (75.0, 8.268) on the
end face, consistent with the 45-degree start chamfer the same file pins.
The test was corrected to the measured geometry; the behaviour is unchanged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
68 lines
2.7 KiB
Python
68 lines
2.7 KiB
Python
"""DIN 509 relief grooves (Freistiche), forms E and F.
|
|
|
|
SOURCE
|
|
------
|
|
DIN 509:1998-06, *Technische Zeichnungen - Freistiche*, Table 1 (forms E and F).
|
|
|
|
COVERAGE
|
|
--------
|
|
**No rows are transcribed in this build.** The track's table rule is absolute:
|
|
a row that cannot be verified against the standard's own table is not shipped,
|
|
because a wrong relief depth is a wrong workshop drawing while a narrow table
|
|
is merely narrow. Every lookup here is therefore refused by name and names the
|
|
route to fix it.
|
|
|
|
Extending this module is a **data edit with no code change**: fill ``ROWS_E`` /
|
|
``ROWS_F`` with ``{shaft_diameter_mm: {"profile": [[along, depth], ...]}}`` and
|
|
widen ``COVERAGE_E`` / ``COVERAGE_F`` to the transcribed range. ``profile`` is
|
|
the standard's own published relief profile expressed as (distance along the
|
|
axis from the shoulder corner, depth below the smaller cylinder) pairs in
|
|
millimetres - the same ``profile`` key group S's ``std_feature_draw`` reads -
|
|
so nothing downstream ever reconstructs a profile it was not handed.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from engineering.mech.standards import Coverage, register
|
|
|
|
SOURCE = "DIN 509:1998-06, Table 1 (forms E and F) - NOT TRANSCRIBED in this build"
|
|
|
|
#: The keys every DIN 509 row must carry for the drawing code to use it.
|
|
UNDERCUT_ROW_KEYS: tuple[str, ...] = ("profile",)
|
|
|
|
FORMS: tuple[str, ...] = ("E", "F")
|
|
|
|
ROWS_E: dict[float, dict] = {}
|
|
ROWS_F: dict[float, dict] = {}
|
|
|
|
COVERAGE_E = Coverage(0.0, 0.0, "mm")
|
|
COVERAGE_F = Coverage(0.0, 0.0, "mm")
|
|
|
|
register("DIN 509-E", ROWS_E, COVERAGE_E, SOURCE)
|
|
register("DIN 509-F", ROWS_F, COVERAGE_F, SOURCE)
|
|
|
|
_ROWS = {"E": ROWS_E, "F": ROWS_F}
|
|
|
|
|
|
def undercut_profile(form: str, d: float) -> tuple[tuple[float, float], ...]:
|
|
"""The published relief profile for a shaft diameter, or a named refusal."""
|
|
key = str(form or "").strip().upper()
|
|
if key not in FORMS:
|
|
raise ValueError(f"DIN 509 defines forms E and F; got {form!r}.")
|
|
rows = _ROWS[key]
|
|
if not rows:
|
|
raise ValueError(
|
|
f"DIN 509 form {key}: no rows are transcribed in this build, so no relief "
|
|
f"profile can be looked up for d = {float(d):g} mm. Pass the profile "
|
|
"explicitly (Undercut(profile=[[along, depth], ...])), or transcribe "
|
|
f"{SOURCE.split(' - ')[0]} into engineering/mech/standards/undercuts.py. "
|
|
"Nothing is interpolated and no profile is invented."
|
|
)
|
|
size = float(d)
|
|
if size in rows:
|
|
return tuple(tuple(float(v) for v in pair) for pair in rows[size]["profile"])
|
|
raise ValueError(
|
|
f"DIN 509 form {key}: d = {size:g} mm has no transcribed row "
|
|
f"(transcribed sizes: {sorted(rows)})."
|
|
)
|