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
487 lines
28 KiB
Markdown
487 lines
28 KiB
Markdown
# 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.
|