Files
autocad-mcp-pro/engineering/mech/standards/centres.py
T
Umut CANandClaude Opus 5 ac6c4075ba feat(task-3): the feature catalogue and its standards tables
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
2026-09-22 19:12:00 +03:00

68 lines
2.5 KiB
Python

"""DIN 332-1 centre holes, forms A and B.
SOURCE
------
DIN 332-1:1986-04, *Centre holes 60 degrees - Forms R, A and B*, Table 1.
COVERAGE
--------
**No rows are transcribed in this build** (same rule as ``undercuts.py``).
Form R is not drawn by this build at all: its profile is an arc form whose
radius is not transcribed here, and it is refused outright rather than
approximated by the straight 60 degree cone of forms A and B.
Extending is a data edit: fill ``ROWS_A`` / ``ROWS_B`` with
``{pilot_diameter_mm: {"d1": ..., "d2": ..., "d3": ..., "t": ...}}`` (``d3``
only for form B's protective 120 degree countersink) and widen the coverage.
"""
from __future__ import annotations
from engineering.mech.standards import Coverage, register
SOURCE = "DIN 332-1:1986-04, Table 1 (forms A and B) - NOT TRANSCRIBED in this build"
#: The keys a DIN 332 row must carry; form A rows may omit "d3".
CENTRE_ROW_KEYS: tuple[str, ...] = ("d1", "d2", "d3", "t")
FORMS: tuple[str, ...] = ("A", "B")
ROWS_A: dict[float, dict] = {}
ROWS_B: dict[float, dict] = {}
COVERAGE_A = Coverage(0.0, 0.0, "mm")
COVERAGE_B = Coverage(0.0, 0.0, "mm")
register("DIN 332-A", ROWS_A, COVERAGE_A, SOURCE)
register("DIN 332-B", ROWS_B, COVERAGE_B, SOURCE)
_ROWS = {"A": ROWS_A, "B": ROWS_B}
def centre_hole_dims(form: str, size: float) -> dict:
"""``{"d1","d2","d3","t"}`` for a pilot diameter, or a named refusal."""
key = str(form or "").strip().upper()
if key == "R":
raise ValueError(
"DIN 332 form R: this build draws forms A and B only. Form R's arc profile "
"is not transcribed here and is refused rather than approximated by the "
"straight 60 degree cone."
)
if key not in FORMS:
raise ValueError(f"DIN 332: expected form A or B, got {form!r}.")
rows = _ROWS[key]
if not rows:
raise ValueError(
f"DIN 332 form {key}: no rows are transcribed in this build, so no centre hole "
f"can be looked up for d1 = {float(size):g} mm. Pass d1, d2 (and d3 for form B) "
f"and t explicitly, or transcribe {SOURCE.split(' - ')[0]} into "
"engineering/mech/standards/centres.py. Nothing is interpolated."
)
pilot = float(size)
if pilot in rows:
row = rows[pilot]
return {name: (float(row[name]) if name in row else None) for name in CENTRE_ROW_KEYS}
raise ValueError(
f"DIN 332 form {key}: d1 = {pilot:g} mm has no transcribed row (sizes: {sorted(rows)})."
)