The evidence for tracks B and G, in the shape tracks A and E used.
- benchmarks/tasks_v5.py: 18 tasks, DEFAULT_MATRIX = "v5"; v4, v3 and v2 stay
addressable so an earlier report reproduces exactly rather than being only
described. mech_assembly is roadmap criterion 3 - a flange-coupling sheet
built from the part model and the sheet standard, through the code the
tools call (apply_titleblock, draw_part/add_view/dimension_part,
insert_std_part, extract_records -> draw_bom_table, add_balloon), gated on
two numbers that cannot be negotiated: drawing_critique(focus=None) returns
zero issues and the finalize score is at least 90 (it scores 100, 18/18 on
the v5 matrix). The adapter returns the focuses that fired, so a
regression names itself, and a test drops one balloon to prove the gate
fails and names mech_bom_balloon_mismatch.
- Seven correctness checks (32 -> 39). mech_section_hatch_area is the one
that cannot be faked: a sleeve of length 60, outside diameter 40 and bore
20 cuts two faces totalling 1200 mm^2 exactly, read back out of the drawing
through entity_measure. sheet_frame_iso5457_a3 pins the A3 frame (20,10 to
410,287 inside the 420 x 297 trimmed sheet); mech_iso286_on_dimension pins
40 H7 at +0.025/0 through the layout; std_part_iso4014_m12 pins s = 18 and
k = 7.5 by value; mech_thread_unrepresented_is_caught is the seventh: a
quality gate that never fires is not a gate.
- scripts/smoke_mech_com.py executed every new COM path once on AutoCAD 2026
(25.1s): frame, title block, a sectioned shaft with 12 dimensions, an ISO
4014 block with 1 ACADMCP_MECH chunk, a weld symbol, the parts list read off
the drawing, a linked balloon, an xref attached / listed / detached, a
31687-byte .dwg (AC1032) through ActiveX SaveAs, critique 0 issues, exit 0,
every scratch document closed. It retries RPC_E_CALL_REJECTED (measured:
the first call after Documents.Add is refused) and tracks its documents by
the document list, since SaveAs renames them.
- Defects the evidence found, each fixed with a test that fails on the old
code:
* ezdxf dimension_diameter put the text beyond the SECOND chord point;
ActiveX AddDimDiametric puts it beyond the first (measured live: a d50
from (x,170) to (x,220), leader 10, text at (x,160)). Headlessly every
front-view diameter stood on its own chain dimension. ezdxf now follows
ActiveX.
* draw_part's anchor goes on MECH; ActiveX refuses entity.Layer on a
missing layer (measured -0x7ffdfff7 'Key not found'). draw.py ensures
every target layer before the first entity.
* bom.extract_records filtered on INSERT; ActiveX names it BLOCKREFERENCE
(ObjectName AcDbBlockReference) - the live parts list was empty.
* COM _entity_info reported no dimension text_position: ModelSpace.Item
narrows to IAcadEntity, which has no TextPosition (measured
AttributeError); read through _com_unnarrow (TextPosition measured on
AcDbDiametricDimension, WCS). dim_overlap compared bbox centres and a
diameter collided with its own bore.
* pid graph raised KeyError 'start' mid-critique on a LINE whose point read
AutoCAD refused while busy; such a line is left out.
* the validator read "ISO 4014 - M12x60" as fake dimension text.
- The published A/B report is re-run over the grown suite rather than
re-typed (26/39 -> 39/39, 13 fixed, 0 regressed), check_doc_numbers derives
the task count from the v5 matrix, the token table is remeasured with a
TOOL_PACKS=core,mech row (193 tools / 61,673 tokens), and the README's
collected-tests figure is retaken (3990).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
28 KiB
v1.6 Tracks B + G — the complete mechanical 2D part drawer and the sheet standard
Date: 2026-09-22 · Status: shipped (v1.6.0-dev, tracks B + G) ·
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_specplaces 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_writecapability and names the ODA route). - XLSX export unless
openpyxlis present (the[pdf]-extra pattern); CSV is always available. - Clipping a raster underlay (
image_attachplaces 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
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 waypid_from_specrefuses; d_inner < d_outerper segment; a bore that changes diameter is two segments;- the outline is closed, non-self-intersecting (reuse
engineering/measure.py'sis_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 Holes 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 olderRaform 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 onCENTER; themech_missing_centrelinefocus 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 thatmech_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 existingtitleblock_apply_iso_a3becomes a thin alias that calls it withsize="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 andACADMCP_MECHXDATA 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 rowsbom_extractalready numbered), linked to its targets by XDATA so a rerun renumbers in place instead of duplicating. Themech_bom_balloon_mismatchfocus is what proves a balloon and its row agree.data_extract(scope, format, path)— the same rows to CSV (always) or XLSX (refused withxlsx_writewhenopenpyxlis absent, naming the extra).xref_attach(path, at, scale, rotation, kind)— external reference as attachment or overlay; both engines (ezdxfadd_xref_def+ blockref).xref_manage(name, action)—list/reload/bind/detach/path.list,detachandpathwork headlessly;reloadandbindon a live seat or headlessly throughezdxf.xrefwhere it is faithful — where it is not, thexref_livecapability 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)— COMSaveAsnatively; headless throughezdxf.addons.odafconly when the ODA File Converter is installed (measured on this machine: it is not), otherwise thedwg_writecapability 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 anddrawing_finalizescores ≥ 90. Matrix v5 (benchmarks/tasks_v5.py, 18 tasks); the published competitor reports staynot runfor it. - Correctness checks (suite grows 32 → 38):
mech_part_roundtrip(draw →mech_part_inspectreturns the same feature list),mech_section_hatch_area(the hatched area equals the computed cut area throughanalysis_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.pyon 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.dwgexport — 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
- One part model, not per-part generators. The alternative (a
shaft_draw/plate_draw/flange_drawzoo) 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. - First angle by default, third angle available, the choice recorded in the title block symbol.
- 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.
- The tables are transcribed, not derived. No interpolation; coverage is declared and refused at its edges.
- B and G ship together because criterion 3's sheet needs the frame, the title block, the parts list and the balloons to exist.