Files
autocad-mcp-pro/docs/superpowers/specs/2026-09-22-v1.6-mech-design.md
T
Umut CANandClaude Opus 5 a8482a5f0d docs(spec): align tracks B+G with the written plan
The five gaps the plan's reconciliation found, each resolved in the spec:
part placement lives in draw_part(at=, rotation=) so the dataclasses carry no
origin; engineering/sheet/refs.py is gone with the contract correction;
image_attach takes no clip and titleblock_apply no position (the ISO 7200
corner is fixed by the standard) — both recorded as out of 1.6; balloon_add
places one balloon per call against the item numbers bom_extract already
assigned.

Records the four feature tables that ship empty (DIN 509, DIN 471/472,
DIN 332-1, ISO 3601-2): no row could be verified against the standard here, so
the structure, the row-key contract and the refusal ship without rows, and the
features draw from explicit dimensions until the tables are transcribed.

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

487 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# v1.6 Tracks B + G — the complete mechanical 2D part drawer and the sheet standard
**Date:** 2026-09-22 · **Status:** design approved in conversation ·
**Parent:** `2026-09-15-v1.6-roadmap-design.md` (tracks B and G, merged into one
spec by the user's decision) · **Baseline:** main after Track E
(`39e6e2f`, 204 tools, 8 resources, 3283 tests)
## 1. Summary
v1.5 can draw a gear, a keyed bore and an A3 title block. Everything else a
machine drawing needs — a stepped shaft, a plate with holes, a flange, a
section view, a hidden line, a surface-texture symbol, a weld symbol, a parts
list, a sheet frame that is not A3 — a model has to hand-draw from primitives.
That is the failure this track closes, and the repository already knows why it
must: `CLAUDE.md` forbids hand-drawing teeth, keyways and section hatches
because a model inventing an involute flank or a DIN 509 undercut from memory
produces geometry that *looks* like a drawing and is wrong.
The design is **not** a zoo of per-part generators. It is one **part model** —
a profile plus a list of typed **features** — and one **view engine** that
turns that model into front / side / top / section / detail views with correct
hidden lines, cut faces, hatch and dimensions. The model decides *what the part
is*; the tools guarantee *what the standards say*. A "flanged shaft with a
keyway and a bolt circle" needs no new code: it is a revolved profile with
three features.
Around it: a standard-parts catalogue authored as code (fasteners, bearings,
retaining rings, undercuts, centre holes, O-ring grooves) inserted as real
blocks with attributes — the Track A pattern, which gives the parts list for
free; the ISO annotation symbols; and the sheet standard (ISO 5457 frames A4–A0,
ISO 7200 title blocks for every size, revisions, parts list, balloons, xrefs,
images, DWG output).
**Tool budget: +25 → 229.** New pack `mech` (sections 21–23, 14 tools); the
sheet and delivery section is `core` — a frame, a title block, a parts list and
an xref are universal drafting, not a vertical.
**The user's principle for this track (2026-09-16, restated 2026-09-21):** tool
count is not the goal. Where several operations act on the same object they are
one tool with an enum (`mech_view_add(kind=…)`, `xref_manage(action=…)`), never
three thin ones; every tool carries a rich alias record (AutoCAD command names
plus shop-floor Turkish/English synonyms), a docstring that names its refusals,
a pack, and a lean decision.
## 2. Scope
**In:** sections 4–10 of this document, registered as SECTION 21 Mechanical
parts (6), SECTION 22 Standard parts (3), SECTION 23 Mechanical annotation (5),
SECTION 24 Sheet and delivery (11).
**Out of 1.6 (recorded, with the reason):**
- A general 3D → 2D projection kernel. Views come from the part model, which is
2.5D by construction (revolved or prismatic). An arbitrary ACIS solid has no
view path and is not pretended to have one.
- Sheet-metal flat patterns and bend allowance (K-factor). Bend lines are drawn
as a *representation*; no developed length is computed, because a wrong
developed length is a scrapped part.
- Splines (DIN 5480 / ISO 4156), springs (DIN 2089), gear tooth *profiles* other
than the existing involute spur/helical generator.
- Assembly constraint solving. `mech_part_from_spec` places parts at given
coordinates; it does not mate them.
- Third-angle projection *as the default* — it is available, but the default is
first angle (ISO 128-30, European practice, which is what this shop draws).
- Real DWG output from the headless engine unless the ODA File Converter is
installed (the user chose this: COM writes DWG natively, ezdxf refuses with
the `dwg_write` capability and names the ODA route).
- XLSX export unless `openpyxl` is present (the `[pdf]`-extra pattern); CSV is
always available.
- Clipping a raster underlay (`image_attach` places and scales; a clip boundary
is a 1.7 item), and placing a title block anywhere but the ISO 7200 corner the
standard fixes.
- **Four feature tables ship empty** (recorded 2026-09-22, during planning):
DIN 509 E/F, DIN 471 / DIN 472, DIN 332-1 A/B and ISO 3601-2. Their structure,
SOURCE, row-key contract and coverage refusal ship; no row does, because no
row could be verified against the standard's own table on this machine (the
repository's web tool is out of credits). The features draw correctly from
explicit dimensions — `Undercut(profile=…)`, `RetainingGroove(m=, d2=)`,
`CentreHole(d1=, d2=, d3=, t=)`, `ORingGroove(b=, h=)` — and every lookup is
refused by name until a row is transcribed. Filling a table later is a data
edit with no code change, and a shape test already fails any row that lands
missing a column the drawing code reads. The alternative — plausible numbers —
ships a wrong workshop drawing, which is the one failure this repository
refuses to risk.
## 3. Architecture
```
engineering/mech/
__init__.py public surface: build_part, draw_part, add_view, …
part.py the part model: RevolvedPart, PrismaticPart, Feature protocol
features.py every feature dataclass + its standard lookup + validation
views.py the view engine: silhouette, hidden lines, section, detail
dimension.py dimension intents per feature → laid-out DIMENSION calls
draw.py async draw_*(backend, …) — the only module that touches a backend
primitives.py the typed primitive descriptions the pure modules emit
(Line, Arc, Circle, Poly, HatchArea, DimIntent, Role → layer)
xdata.py ACADMCP_MECH payload: the part model written onto the drawing
critique.py the six mech_* critique focuses
standards/
__init__.py registry + `lookup(standard, size)` with coverage refusal
fasteners.py ISO 4014, ISO 4017, ISO 4032, ISO 7089, ISO 4762
bearings.py ISO 15 series 60xx/62xx/63xx + ISO 8826-1 representation
rings.py DIN 471 / DIN 472 groove dimensions
undercuts.py DIN 509 forms E and F
centres.py DIN 332 forms A, B, R
orings.py ISO 3601-2 groove dimensions
threads.py ISO 261 / ISO 965 coarse + fine pitch, ISO 6410 representation
materials.py ISO 128-50 material → hatch pattern/angle/scale map
engineering/sheet/
__init__.py
frames.py ISO 5457 sheet sizes, margins, zone grid, centring/trim marks
titleblock.py ISO 7200 title block for A4–A0 (today's A3 module folds in here)
revision.py revision block rows + revcloud + triangle tags
bom.py ISO 7573 parts list: extract → rows → table; balloons ISO 6433
extract.py attributes/XDATA → CSV (always) / XLSX (optional dependency)
backends/contracts/refs.py (new) xref_attach, xref_manage, image_attach, drawing_export_dwg
server.py SECTION 21 (6) · SECTION 22 (3) · SECTION 23 (5) · SECTION 24 (11)
scripts/smoke_mech_com.py live COM execution of every new COM path, once
```
**Only four new contract members.** Corrected while planning, 2026-09-22: the
existing engineering tools (`gear_draw_*`, `keyway_draw_*`,
`titleblock_apply_iso_a3`) are **not** contract members — they take the backend
and compose the generic ones (`entity_create_*`, `dimension_*`, `block_define`,
`entity_set_xdata`). Every mechanical, annotation, parts-list and frame tool
follows that pattern, so it is written **once** and works on both engines by
construction. Only the four operations that genuinely differ per engine become
contract members with an implementation each: `xref_attach`, `xref_manage`,
`image_attach`, `drawing_export_dwg`. This removes two contract files, halves
the backend work, and keeps the capability map to the three keys of §12.
The split that matters: **`part.py`, `features.py`, `views.py`, `dimension.py`
and everything under `standards/` are pure** — no backend, no async, no DXF.
They take numbers and return primitive *descriptions* (typed tuples: lines,
arcs, circles, polylines, hatch boundaries, dimension intents, each with a
layer and a line role). `draw.py` is the only module that turns those
descriptions into backend calls. This is what makes the view engine testable
without a drawing: a test asserts the emitted primitive list, not a screenshot.
Both engines implement every contract member. The COM paths are fake-tested and
executed live once by `scripts/smoke_mech_com.py` on AutoCAD 2026.
## 4. The part model
```python
class Segment(NamedTuple):
length: float
d_outer: float
d_inner: float = 0.0 # 0 = solid; > 0 = a bore through this segment
taper_to: float | None = None # cone: outer diameter at the far end
@dataclass(frozen=True)
class RevolvedPart:
name: str
segments: tuple[Segment, ...] # left to right along +X
features: tuple[Feature, ...] = ()
material: str = "steel" # drives the ISO 128-50 hatch
@dataclass(frozen=True)
class PrismaticPart:
name: str
outline: tuple[tuple[float, float], ...] # closed polygon, CCW, part-local XY
thickness: float # along +Z
features: tuple[Feature, ...] = ()
material: str = "steel"
```
Rules the model enforces **before any write** (the repository's validate-first
rule):
- every number finite and positive where positivity is physical (a segment
length, a diameter, a thickness); a non-finite value is refused by path
(`segments[2].d_outer`), the way `pid_from_spec` refuses;
- `d_inner < d_outer` per segment; a bore that changes diameter is two segments;
- the outline is closed, non-self-intersecting (reuse `engineering/measure.py`'s
`is_self_intersecting`), and has ≥ 3 distinct vertices;
- every feature's placement lies on the part: a keyway on segment 5 of a
three-segment shaft is refused by name, not clamped;
- overlapping features that would remove the same material twice (two grooves at
the same x, a hole inside a pocket's wall) are refused with both names.
The part is written onto the drawing as `ACADMCP_MECH` XDATA on the view's
anchor block (the Track A chunking codec, ≤ 255 chars per chunk, 16 KB per
entity), so `mech_part_inspect` reads the model back and `mech_view_add` can add
a second view months later without the caller re-describing the part.
## 5. Feature catalogue
Every feature is a frozen dataclass that answers three questions: *where am I on
the part*, *what geometry do I contribute in a given view* (visible / hidden /
cut), and *what dimension do I want*. Features applicable to revolved parts:
| Feature | Parameters | Standard | Coverage |
|---|---|---|---|
| `Chamfer` | at (`start`/`end`/`step:i`), size, angle=45 | ISO 128-3 | any |
| `Fillet` | at `step:i`, radius | — | any |
| `Keyway` | segment, length, from_end, width/depth or derived | DIN 6885-1 | d 6–130 mm (existing `keyway.py` table) |
| `Thread` | segment, designation (`M20x1.5`), length, external/internal, hand | ISO 261/965 pitch, ISO 6410 representation | M1.6–M64 coarse, M8–M64 fine |
| `Undercut` | at `step:i`, form `E`/`F` | DIN 509 | d 18–80 mm |
| `RetainingGroove` | x, shaft or bore, nominal d | DIN 471 / DIN 472 | d 8–100 mm |
| `CentreHole` | at `start`/`end`, form `A`/`B`/`R`, size | DIN 332-1/2 | d 1–10 mm |
| `ORingGroove` | x, cord diameter, static radial/axial | ISO 3601-2 | cord 1.78–8.40 mm |
| `CrossHole` | x, diameter, angle, through/blind depth | — | any |
| `HoleCircle` | x (face), pcd, count, diameter, start angle, thread? | — | any |
| `AxialBore` | from end, diameter, depth (or through) | — | any |
| `GearTeeth` | module, teeth, pressure/helix angle | existing `gear.py` | as today |
Applicable to prismatic parts:
| Feature | Parameters | Standard | Coverage |
|---|---|---|---|
| `Hole` | x, y, diameter, through/depth, counterbore/countersink/thread | ISO 6410 for tapped | any |
| `Slot` | x, y, length, width, angle | — | any |
| `Pocket` | outline, depth | — | any |
| `CornerFillet` / `CornerChamfer` | vertex index, r / size | — | any |
| `BendLine` | p1, p2, angle, inside radius | representation only | any |
| `HolePattern` | kind `linear`/`polar`/`grid`, base feature, counts, spacings | — | expands to `Hole`s before drawing |
`mech_hole_pattern` is the same expansion exposed as a tool, for holes that are
not part of a modelled part: it takes a base hole and a pattern and draws the
circles, centre marks and (optionally) the dimensions, on any drawing.
**The table rule, non-negotiable:** every row of every table under
`standards/` is transcribed from the named standard's own table, carries a
`SOURCE` docstring naming the standard, edition and table, and is pinned by a
provenance test that asserts named values. A size outside the transcribed range
is refused by name (`ValueError: DIN 509 form E covers d 18–80 mm; 95 mm is
outside the table`) — never interpolated, never "approximately". If the
implementer cannot verify a row against the standard, the row is not shipped and
the coverage note says so. This is the same discipline `engineering/fits.py`
(ISO 286) already follows, and it is what keeps a fabricated number out of a
workshop drawing.
## 6. The view engine
A view is a projection of the part model onto a plane, plus the features that
land on it. The engine emits primitive descriptions on ISO 128 line roles, which
map to the engineering layers the server already bootstraps:
| Role | Layer | Line |
|---|---|---|
| visible outline | `GEOMETRY` | continuous, 0.50 mm |
| hidden edge | `HIDDEN` | HIDDEN, 0.25 mm |
| centre line / axis | `CENTER` | CENTER, 0.25 mm |
| section cut face | `HATCH` | pattern per material, 0.25 mm |
| dimension | `DIM` | 0.25 mm |
**Revolved parts.** `front` is the axial view: the profile silhouette and its
mirror about the axis, with the axis on `CENTER`. Steps produce vertical edges;
chamfers and fillets replace the corner; a `Thread` draws the ISO 6410
representation (major continuous, minor thin at 0.8·d, runout at 30°); a
`Keyway` shows its two visible edges plus the hidden depth line; a `CrossHole`
appears as two hidden lines; grooves cut a notch in the silhouette. `side` (the
end view, left or right) is the set of concentric circles the profile crosses at
that end, plus hole circles, plus the keyway notch, with centre marks.
**Prismatic parts.** `front` is the outline with features drawn in true shape
(holes as circles, slots as obrounds, pockets as their outline). `side` and
`top` are the thickness rectangles with a hidden line per through feature — this
is the case where a hand-drawing model reliably forgets the hidden lines.
**Sections.** `mech_view_add(kind="section", plane=…, style="full"|"half"|
"offset"|"revolved")`. The engine decides per feature whether the plane cuts it;
cut faces are closed boundaries handed to `entity_create_hatch` with the
material's ISO 128-50 pattern; a threaded hole in section follows ISO 6410's
section convention (hatch runs to the major diameter). Ribs and webs are *not*
hatched when cut longitudinally (ISO 128-3) — declared by a `no_section_hatch`
flag on the feature.
**Details.** `kind="detail"` copies a circular region at a larger scale, draws
the ISO 128-34 detail circle and both labels (`A` on the parent, `A (5:1)` on
the detail).
**Projection layout.** Views are placed in first-angle positions by default
(the right view goes to the left of the front view); `projection="third"` flips
them. The chosen convention is recorded in the title block symbol. Views stay
aligned: a `mech_view_add` computes its origin from the parent view's bounding
box plus a gap, so the alignment critique focus has something true to check.
**The honesty rule.** If a feature cannot be projected into the requested view,
the view result lists it in `omitted: [{feature, reason}]` and the tool says so
in its payload. Nothing is silently dropped, and nothing is approximated into
a shape it is not.
## 7. Dimensioning
Each feature declares *dimension intents* — (kind, from, to, text override,
placement preference). `mech_dimension_part` collects them, resolves ISO 286
fits through the existing `engineering/fits.py`, and lays them out: chain or
baseline for the axial lengths, diameter dimensions stacked outside the
silhouette by size, radius for fillets, a hole table when a prismatic part has
more than `hole_table_threshold` (default 8) holes. It reuses the existing
`dimension_auto` machinery and the ISO 129 tolerance path (`tol_mode`, `fit=`)
rather than re-implementing either.
Overlap is not a matter of taste: the existing `dim_overlap` critique focus is
the gate, and the layout algorithm iterates placement until that focus is
silent or reports what it could not place.
## 8. Standard parts (SECTION 22)
`std_part_list(query)` searches the catalogue by designation, standard or size
(`"M12"`, `"ISO 4014"`, `"6205"`) and returns rows with the dimensions that
matter for drawing. `std_part_insert(designation, at, view, rotation, layer)`
builds the part once as a real block through `block_define` (typed primitives +
ATTDEFs for designation/standard/size), inserts it, and writes `ACADMCP_MECH`
XDATA so `bom_extract` finds it without a naming convention. Views: `side`
(elevation) and `top` (plan) for fasteners; `simplified` and `detailed`
(ISO 8826-1/-2) for bearings.
`std_feature_draw(kind, …)` draws a standard *feature* onto geometry that
already exists — a thread on a drawn cylinder, a DIN 509 undercut at a drawn
step, a retaining-ring groove, a centre hole, an O-ring groove — for the case
where the part was not built from the model (a foreign drawing being finished).
## 9. Mechanical annotation (SECTION 23)
- `surface_texture(at, leader_to, ra|rz, process, lay, machining, all_around)` —
ISO 21920-1 symbol geometry derived from the current text height (60° V,
proportions per the standard), with ISO 1302's older `Ra` form accepted as an
alias for drawings that still use it.
- `weld_symbol(at, leader_to, kind, size, length, pitch, side, field_weld,
all_around, process)` — ISO 2553 reference line + dashed identification line,
symbol above/below per side, staggered intermittent notation.
- `centre_marks(handles|centers, style="mark"|"lines", extension)` — ISO 128-23
proportions on `CENTER`; the `mech_missing_centreline` focus is what makes it
matter.
- `section_line(p1, p2, label, direction, style)` — ISO 128-40 cutting-plane
line with thick ends and arrows; returns the plane definition that
`mech_view_add(kind="section")` consumes, so the label and the view cannot
disagree.
- `hatch_material(boundary|handles, material, scale, angle)` — the ISO 128-50
map (steel 45° ANSI31, cast iron ANSI32-style cross, aluminium, copper alloy,
plastic/rubber, concrete, wood, insulation); an unknown material name is
refused with the list, not silently hatched as steel.
## 10. Sheet and delivery (SECTION 24, track G)
- `sheet_frame(size, orientation, zones, marks)` — ISO 5457 A4–A0:
20 mm filing margin, 10 mm elsewhere, zone grid (letters up the side, numbers
along the top, 50 mm modules), centring marks, trimming marks. Geometry is
pinned against the ISO 5457 table by test — the frame is the one thing every
sheet has, so a wrong margin is visible on every drawing this server makes.
- `titleblock_apply(size, fields)` — ISO 7200 data fields for every
sheet size; the existing `titleblock_apply_iso_a3` becomes a thin alias that
calls it with `size="A3"` so no caller breaks. Carries the projection-angle
symbol chosen by the part tools.
- `revision_add(rev, description, date, by, clouds, tags)` — appends a row to the
revision block (created on first use), optionally draws revision clouds over
the given regions and triangular revision tags. Idempotent per revision letter.
- `bom_extract(scope, columns, group_by)` — walks INSERTs' attributes and
`ACADMCP_MECH` XDATA and returns parts-list rows (item, quantity, designation,
standard, material, mass if known). Never modifies the drawing.
- `bom_table(rows|auto, at, standard="ISO 7573", direction)` — draws the parts
list as a real TABLE, growing upwards from the title block per ISO 7573.
- `balloon_add(item, at, leader_to, targets)` — one ISO 6433 item reference per
call (the caller loops over the rows `bom_extract` already numbered), linked to
its targets by XDATA so a rerun renumbers in place instead of duplicating. The
`mech_bom_balloon_mismatch` focus is what proves a balloon and its row agree.
- `data_extract(scope, format, path)` — the same rows to CSV (always) or XLSX
(refused with `xlsx_write` when `openpyxl` is absent, naming the extra).
- `xref_attach(path, at, scale, rotation, kind)` — external reference as
attachment or overlay; both engines (ezdxf `add_xref_def` + blockref).
- `xref_manage(name, action)` — `list` / `reload` / `bind` / `detach` / `path`.
`list`, `detach` and `path` work headlessly; `reload` and `bind` on a live seat
or headlessly through `ezdxf.xref` where it is faithful — where it is not, the
`xref_live` capability refuses honestly rather than pretending.
- `image_attach(path, at, scale, rotation)` — raster underlay
(`add_image_def` + `add_image`), both engines.
- `drawing_export_dwg(path, version)` — COM `SaveAs` natively; headless through
`ezdxf.addons.odafc` **only when the ODA File Converter is installed**
(measured on this machine: it is not), otherwise the `dwg_write` capability
refuses and names the install route. This was the user's explicit choice.
## 11. Tool surface, packs, profiles
| Section | Tools | Pack |
|---|---|---|
| 21 Mechanical parts | `mech_part_draw`, `mech_view_add`, `mech_dimension_part`, `mech_hole_pattern`, `mech_part_from_spec`, `mech_part_inspect` | `mech` |
| 22 Standard parts | `std_part_list`, `std_part_insert`, `std_feature_draw` | `mech` |
| 23 Mechanical annotation | `surface_texture`, `weld_symbol`, `centre_marks`, `section_line`, `hatch_material` | `mech` |
| 24 Sheet and delivery | `sheet_frame`, `titleblock_apply`, `revision_add`, `bom_extract`, `bom_table`, `balloon_add`, `data_extract`, `xref_attach`, `xref_manage`, `image_attach`, `drawing_export_dwg` | `core` |
`TOOL_PACK_NAMES` becomes `("core", "pid", "settings", "mech")`. Lean gains five
— `mech_part_draw`, `mech_view_add`, `mech_dimension_part`, `sheet_frame`,
`titleblock_apply` — reaching 60; the catalogue, annotation and delivery tools
stay full-profile, because a lean client draws a part and a frame and leaves the
paperwork to a full one.
Discovery: every new tool gets an alias record with its AutoCAD command
equivalents (`SECTIONPLANE`, `XATTACH`, `IMAGEATTACH`, `TABLE`, `REVCLOUD`,
`WBLOCK`) and shop synonyms (mil/shaft, plaka/plate, flanş/flange, kama/keyway,
pah/chamfer, kaynak/weld, yüzey pürüzlülüğü/surface finish, parça listesi/BOM);
ten new golden queries join the tuning set.
## 12. Capabilities and refusals
Three keys, each a genuine engine boundary, declared in both maps (the
`tests/test_capability_contract.py` gate):
| Key | ezdxf | COM | Reason |
|---|---|---|---|
| `dwg_write` | ✗ (✓ if ODA installed) | ✓ | headless DWG needs the ODA File Converter, measured absent on this machine; the key's reason names the install route and `system_capabilities` re-evaluates it at connect time, the way `viewport_render` re-evaluates matplotlib |
| `xref_live` | ✗ | ✓ | `reload` and `bind` need a live seat's xref manager; `list`, `detach` and `path` are headless and are *not* behind the key |
| `xlsx_write` | ✓ only with `openpyxl` | ✓ only with `openpyxl` | an optional dependency, not an engine difference; same shape as the `[pdf]` extra, and CSV is always available so the refusal never blocks the deliverable |
Refusals that are *not* capabilities — they are validation, and they fire before
any write: an unknown material, a size outside a standard's table, a feature off
the part, a non-finite number, an outline that self-intersects, a balloon
without a parts-list row.
## 13. Critique focuses
Six new focuses in the closed enum, dispatched like the P&ID ones (they return
nothing on a drawing with no mechanical content, so `focus=None` stays cheap):
- `mech_missing_centreline` — a circle or a cylindrical silhouette with no centre
mark or axis (ISO 128-23).
- `mech_unhatched_section` — a view declared as a section whose cut faces carry
no hatch.
- `mech_view_misaligned` — two views of the same part off their projection axis
by more than a tolerance, or placed in the wrong quadrant for the declared
projection angle.
- `mech_duplicate_dimension` — the same feature dimensioned twice (ISO 129-1:
each dimension appears once).
- `mech_thread_unrepresented` — a threaded feature drawn as a plain circle or
cylinder with no ISO 6410 representation.
- `mech_bom_balloon_mismatch` — a balloon with no parts-list row, a row with no
balloon, or a quantity that disagrees with the INSERT count.
## 14. Evidence
- **Benchmark task `mech_assembly`** (roadmap criterion 3): a flange-coupling
sheet built from the tools — two parts, a section, a bolt circle with ISO 4014
bolts and ISO 4032 nuts, ISO 286 fits on the bearing seats, ISO 129 dimensions,
ISO 5457 A3 frame, ISO 7200 title block, ISO 7573 parts list with balloons.
Gate: `drawing_critique(focus=None)` returns 0 issues and
`drawing_finalize` scores ≥ 90. Matrix v5 (`benchmarks/tasks_v5.py`,
18 tasks); the published competitor reports stay `not run` for it.
- **Correctness checks** (suite grows 32 → 38): `mech_part_roundtrip`
(draw → `mech_part_inspect` returns the same feature list),
`mech_section_hatch_area` (the hatched area equals the computed cut area
through `analysis_measure_entity` — a number that cannot be faked),
`mech_iso286_on_dimension` (a ⌀40 H7 bore dimension carries +0.025/0),
`sheet_frame_iso5457_a3` (frame geometry against the standard's table),
`bom_balloon_link` (every balloon resolves to its row after a renumber),
`std_part_iso4014_m12` (the inserted bolt's across-flats and head height match
the table).
- **Live COM smoke** `scripts/smoke_mech_com.py` on AutoCAD 2026: a shaft with a
section, a standard bolt, a weld symbol, a frame, a parts list, an xref
attach/detach and a real `.dwg` export — executed once, recorded in the
CHANGELOG the way tracks A and E were.
- **A/B against v1.5.1** stays at zero regressions (release gate).
## 15. Testing
- Pure modules (`part`, `features`, `views`, `dimension`, `standards/*`,
`sheet/frames`, `sheet/bom`) are tested without a backend: given a model,
assert the emitted primitive descriptions — count, layer, geometry to 1e-9.
This is where the standards are pinned.
- Provenance tests per table: named values from the standard, plus the coverage
refusal at both ends of the range.
- Backend integration per engine: ezdxf against a real document; COM against
fakes that model the *measured* ActiveX member names (the Track E rule: a fake
that invents a member enshrines a bug).
- The critique focuses get a fixture drawing each, positive and negative.
- Every task ends with the repository's gate set (ruff, inventory, registry,
aliases, capability contract, release consistency).
## 16. Decisions recorded
1. **One part model, not per-part generators.** The alternative (a
`shaft_draw`/`plate_draw`/`flange_draw` zoo) duplicates the hidden-line,
section and dimension logic three times and still cannot draw a flanged shaft.
Named entry points remain for discoverability, but they build the same model.
2. **First angle by default**, third angle available, the choice recorded in the
title block symbol.
3. **Standard parts are blocks with attributes**, exactly like the P&ID
catalogue, so the parts list is a read of the drawing rather than a
bookkeeping side-channel.
4. **The tables are transcribed, not derived.** No interpolation; coverage is
declared and refused at its edges.
5. **B and G ship together** because criterion 3's sheet needs the frame, the
title block, the parts list and the balloons to exist.