docs: tracks B+G (mechanical 2D and the sheet standard) design spec
One part model — a revolved or prismatic profile plus typed features — and one view engine that projects it into front/side/section/detail views with hidden lines, cut faces and dimensions, instead of a generator per part shape. Around it: the standard-parts catalogue as code inserted as blocks with attributes, the ISO annotation symbols, and the sheet standard (ISO 5457 frames A4-A0, ISO 7200 title blocks, revisions, ISO 7573 parts list, balloons, xrefs, DWG). 25 tools in four sections, new pack `mech`, three capability keys, six mech_* critique focuses, benchmark task `mech_assembly` and six correctness checks. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
This commit is contained in:
co-authored by
Claude Opus 5
parent
39e6e2f9e4
commit
5ff66b641f
@@ -0,0 +1,461 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
## 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
|
||||||
|
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)
|
||||||
|
refs.py xref attach/list/reload/bind/detach, image attach
|
||||||
|
|
||||||
|
backends/contracts/mech.py (new) part/view/dimension/standard-part/annotation members
|
||||||
|
backends/contracts/sheet.py (new) frame/titleblock/revision/bom/xref/image/dwg members
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
||||||
|
origin: tuple[float, float] = (0.0, 0.0) # part-local; the tool places it in WCS
|
||||||
|
|
||||||
|
@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"
|
||||||
|
origin: tuple[float, float] = (0.0, 0.0)
|
||||||
|
```
|
||||||
|
|
||||||
|
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, position)` — 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(handles|rows, at, start, leader_to, arrangement)` — ISO 6433 item
|
||||||
|
references, auto-numbered to match the parts list, linked by XDATA so a rerun
|
||||||
|
renumbers instead of duplicating.
|
||||||
|
- `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, clip)` — 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.
|
||||||
Reference in New Issue
Block a user