docs(spec): align tracks B+G with the written plan

The five gaps the plan's reconciliation found, each resolved in the spec:
part placement lives in draw_part(at=, rotation=) so the dataclasses carry no
origin; engineering/sheet/refs.py is gone with the contract correction;
image_attach takes no clip and titleblock_apply no position (the ISO 7200
corner is fixed by the standard) — both recorded as out of 1.6; balloon_add
places one balloon per call against the item numbers bom_extract already
assigned.

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

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
This commit is contained in:
Umut CAN
2026-09-22 16:52:23 +03:00
co-authored by Claude Opus 5
parent 5497dc01d9
commit a8482a5f0d
@@ -67,6 +67,21 @@ SECTION 24 Sheet and delivery (11).
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
@@ -99,7 +114,6 @@ engineering/sheet/
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/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)
@@ -143,7 +157,6 @@ class RevolvedPart:
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:
@@ -152,7 +165,6 @@ class PrismaticPart:
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
@@ -331,7 +343,7 @@ where the part was not built from the model (a foreign drawing being finished).
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
- `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.
@@ -343,9 +355,10 @@ where the part was not built from the model (a foreign drawing being finished).
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.
- `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
@@ -354,7 +367,7 @@ where the part was not built from the model (a foreign drawing being finished).
`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
- `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**