draftwright 0.3.3__tar.gz → 0.3.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. {draftwright-0.3.3 → draftwright-0.3.5}/CHANGELOG.md +297 -0
  2. {draftwright-0.3.3 → draftwright-0.3.5}/PKG-INFO +42 -12
  3. {draftwright-0.3.3 → draftwright-0.3.5}/README.md +40 -10
  4. draftwright-0.3.5/docs/adr/README.md +51 -0
  5. {draftwright-0.3.3 → draftwright-0.3.5}/pyproject.toml +2 -2
  6. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/__init__.py +0 -9
  7. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/_core.py +330 -30
  8. draftwright-0.3.5/src/draftwright/_geometry.py +142 -0
  9. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/analysis.py +30 -3
  10. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/annotations/_common.py +458 -94
  11. draftwright-0.3.5/src/draftwright/annotations/balloons.py +242 -0
  12. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/annotations/from_model.py +554 -221
  13. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/annotations/holes.py +42 -17
  14. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/annotations/orchestrator.py +350 -164
  15. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/annotations/sections.py +67 -6
  16. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/builder.py +193 -48
  17. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/cli.py +42 -3
  18. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/compose.py +41 -49
  19. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/drawing.py +441 -605
  20. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/intents.py +5 -4
  21. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/layout.py +114 -48
  22. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/linting/__init__.py +2 -0
  23. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/linting/coverage.py +36 -5
  24. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/linting/structural.py +232 -208
  25. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/model/__init__.py +10 -7
  26. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/model/declare.py +124 -47
  27. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/model/detect.py +31 -7
  28. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/model/ir.py +13 -6
  29. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/model/planner.py +38 -3
  30. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/projection.py +7 -3
  31. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/recognition/__init__.py +19 -5
  32. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/recognition/_features.py +1 -1
  33. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/recognition/chamfers.py +50 -24
  34. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/recognition/countersinks.py +19 -5
  35. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/recognition/fillets.py +23 -13
  36. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/recognition/flats.py +6 -3
  37. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/recognition/grooves.py +22 -9
  38. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/recognition/turned.py +5 -2
  39. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/registry.py +16 -0
  40. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/repair.py +5 -5
  41. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/score.py +13 -9
  42. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/sheet.py +67 -52
  43. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/sheet_dsl.py +1 -1
  44. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/sheet_emit.py +72 -5
  45. draftwright-0.3.5/tests/fixtures/grm03_thumbwheel_drive_screw.step +745 -0
  46. {draftwright-0.3.3 → draftwright-0.3.5}/tests/refactor_golden/hex_bar.json +23 -5
  47. draftwright-0.3.5/tests/test_architecture_docs.py +43 -0
  48. {draftwright-0.3.3 → draftwright-0.3.5}/tests/test_carve_free_position_callers.py +17 -18
  49. {draftwright-0.3.3 → draftwright-0.3.5}/tests/test_declare.py +129 -14
  50. {draftwright-0.3.3 → draftwright-0.3.5}/tests/test_detect_once.py +37 -0
  51. {draftwright-0.3.3 → draftwright-0.3.5}/tests/test_drawing_encapsulation.py +117 -5
  52. {draftwright-0.3.3 → draftwright-0.3.5}/tests/test_e2e_slice.py +2 -2
  53. {draftwright-0.3.3 → draftwright-0.3.5}/tests/test_e2e_standards.py +10 -7
  54. {draftwright-0.3.3 → draftwright-0.3.5}/tests/test_fits.py +1 -1
  55. {draftwright-0.3.3 → draftwright-0.3.5}/tests/test_gdt_placement.py +17 -17
  56. {draftwright-0.3.3 → draftwright-0.3.5}/tests/test_import_boundaries.py +16 -1
  57. {draftwright-0.3.3 → draftwright-0.3.5}/tests/test_layout.py +1 -34
  58. {draftwright-0.3.3 → draftwright-0.3.5}/tests/test_lint_structural.py +64 -0
  59. {draftwright-0.3.3 → draftwright-0.3.5}/tests/test_make_drawing.py +843 -295
  60. {draftwright-0.3.3 → draftwright-0.3.5}/tests/test_part_model.py +24 -0
  61. {draftwright-0.3.3 → draftwright-0.3.5}/tests/test_pmi.py +8 -8
  62. {draftwright-0.3.3 → draftwright-0.3.5}/tests/test_private_test_imports.py +10 -1
  63. {draftwright-0.3.3 → draftwright-0.3.5}/tests/test_recogniser_contract.py +18 -0
  64. {draftwright-0.3.3 → draftwright-0.3.5}/tests/test_refactor_golden.py +1 -1
  65. {draftwright-0.3.3 → draftwright-0.3.5}/tests/test_registry.py +4 -4
  66. {draftwright-0.3.3 → draftwright-0.3.5}/tests/test_render_seam.py +1 -1
  67. draftwright-0.3.5/tests/test_script_detail_parity.py +185 -0
  68. {draftwright-0.3.3 → draftwright-0.3.5}/tests/test_sheet_emit.py +186 -10
  69. {draftwright-0.3.3 → draftwright-0.3.5}/tests/test_sheet_gdt.py +5 -5
  70. {draftwright-0.3.3 → draftwright-0.3.5}/tests/test_sheet_notes.py +18 -4
  71. {draftwright-0.3.3 → draftwright-0.3.5}/tests/test_sheet_of.py +1 -1
  72. {draftwright-0.3.3 → draftwright-0.3.5}/tests/test_sheet_tables.py +34 -8
  73. draftwright-0.3.5/tests/test_solve_trace.py +275 -0
  74. {draftwright-0.3.3 → draftwright-0.3.5}/tests/test_strip_layout.py +48 -67
  75. draftwright-0.3.5/tests/test_tolerances.py +890 -0
  76. draftwright-0.3.3/src/draftwright/_geometry.py +0 -50
  77. draftwright-0.3.3/tests/test_tolerances.py +0 -234
  78. {draftwright-0.3.3 → draftwright-0.3.5}/.gitignore +0 -0
  79. {draftwright-0.3.3 → draftwright-0.3.5}/LICENSE +0 -0
  80. {draftwright-0.3.3 → draftwright-0.3.5}/skills/SKILL.md +0 -0
  81. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/annotate.py +0 -0
  82. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/annotations/__init__.py +0 -0
  83. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/export.py +0 -0
  84. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/fits.py +0 -0
  85. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/fonts/IBMPlexMono-Regular.ttf +0 -0
  86. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/fonts/IBMPlexSansCondensed-Regular.ttf +0 -0
  87. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/fonts/LICENSE-IBMPlexMono-OFL.txt +0 -0
  88. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/fonts/LICENSE-IBMPlexSansCondensed-OFL.txt +0 -0
  89. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/fonts/__init__.py +0 -0
  90. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/linting/issues.py +0 -0
  91. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/linting/suggest.py +0 -0
  92. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/make_drawing.py +0 -0
  93. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/pmi.py +0 -0
  94. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/recognition/_record.py +0 -0
  95. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/recognition/levels.py +0 -0
  96. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/recognition/plates.py +0 -0
  97. {draftwright-0.3.3 → draftwright-0.3.5}/src/draftwright/recognition/slots.py +0 -0
  98. {draftwright-0.3.3 → draftwright-0.3.5}/tests/_kernel.py +0 -0
  99. {draftwright-0.3.3 → draftwright-0.3.5}/tests/_layout_sig.py +0 -0
  100. {draftwright-0.3.3 → draftwright-0.3.5}/tests/fixtures/nist_ctc_01_asme1_ap203.stp +0 -0
  101. {draftwright-0.3.3 → draftwright-0.3.5}/tests/fixtures/nist_ctc_01_asme1_ap242.stp +0 -0
  102. {draftwright-0.3.3 → draftwright-0.3.5}/tests/fixtures/nist_ctc_02_asme1_ap203.stp +0 -0
  103. {draftwright-0.3.3 → draftwright-0.3.5}/tests/fixtures/nist_ctc_02_asme1_ap242.stp +0 -0
  104. {draftwright-0.3.3 → draftwright-0.3.5}/tests/fixtures/nist_ctc_03_asme1_ap203.stp +0 -0
  105. {draftwright-0.3.3 → draftwright-0.3.5}/tests/fixtures/nist_ctc_03_asme1_ap242.stp +0 -0
  106. {draftwright-0.3.3 → draftwright-0.3.5}/tests/fixtures/nist_ctc_04_asme1_ap203.stp +0 -0
  107. {draftwright-0.3.3 → draftwright-0.3.5}/tests/fixtures/nist_ctc_04_asme1_ap242.stp +0 -0
  108. {draftwright-0.3.3 → draftwright-0.3.5}/tests/fixtures/nist_ctc_05_asme1_ap203.stp +0 -0
  109. {draftwright-0.3.3 → draftwright-0.3.5}/tests/fixtures/nist_ctc_05_asme1_ap242.stp +0 -0
  110. {draftwright-0.3.3 → draftwright-0.3.5}/tests/refactor_golden/bracket_section.json +0 -0
  111. {draftwright-0.3.3 → draftwright-0.3.5}/tests/refactor_golden/centered_rebate.json +0 -0
  112. {draftwright-0.3.3 → draftwright-0.3.5}/tests/refactor_golden/chamfered.json +0 -0
  113. {draftwright-0.3.3 → draftwright-0.3.5}/tests/refactor_golden/filleted.json +0 -0
  114. {draftwright-0.3.3 → draftwright-0.3.5}/tests/refactor_golden/flange_dense.json +0 -0
  115. {draftwright-0.3.3 → draftwright-0.3.5}/tests/refactor_golden/grid_plate.json +0 -0
  116. {draftwright-0.3.3 → draftwright-0.3.5}/tests/refactor_golden/grooved_shaft.json +0 -0
  117. {draftwright-0.3.3 → draftwright-0.3.5}/tests/refactor_golden/holed_slot.json +0 -0
  118. {draftwright-0.3.3 → draftwright-0.3.5}/tests/refactor_golden/pocketed.json +0 -0
  119. {draftwright-0.3.3 → draftwright-0.3.5}/tests/refactor_golden/prismatic_ladder.json +0 -0
  120. {draftwright-0.3.3 → draftwright-0.3.5}/tests/refactor_golden/scattered_plate.json +0 -0
  121. {draftwright-0.3.3 → draftwright-0.3.5}/tests/refactor_golden/side_drilled.json +0 -0
  122. {draftwright-0.3.3 → draftwright-0.3.5}/tests/refactor_golden/turned_stepped.json +0 -0
  123. {draftwright-0.3.3 → draftwright-0.3.5}/tests/test_export_dxf_zoom.py +0 -0
  124. {draftwright-0.3.3 → draftwright-0.3.5}/tests/test_fillets_adjacency.py +0 -0
  125. {draftwright-0.3.3 → draftwright-0.3.5}/tests/test_layout_cleanliness.py +0 -0
  126. {draftwright-0.3.3 → draftwright-0.3.5}/tests/test_layout_hypothesis.py +0 -0
  127. {draftwright-0.3.3 → draftwright-0.3.5}/tests/test_layout_property.py +0 -0
  128. {draftwright-0.3.3 → draftwright-0.3.5}/tests/test_lint_box_cache.py +0 -0
  129. {draftwright-0.3.3 → draftwright-0.3.5}/tests/test_lint_reconciliation.py +0 -0
  130. {draftwright-0.3.3 → draftwright-0.3.5}/tests/test_linting.py +0 -0
  131. {draftwright-0.3.3 → draftwright-0.3.5}/tests/test_object_aspects.py +0 -0
  132. {draftwright-0.3.3 → draftwright-0.3.5}/tests/test_occupancy_boxes.py +0 -0
  133. {draftwright-0.3.3 → draftwright-0.3.5}/tests/test_pitch_dim_footprint.py +0 -0
  134. {draftwright-0.3.3 → draftwright-0.3.5}/tests/test_recognition.py +0 -0
  135. {draftwright-0.3.3 → draftwright-0.3.5}/tests/test_score.py +0 -0
  136. {draftwright-0.3.3 → draftwright-0.3.5}/tests/test_turned_steps.py +0 -0
  137. {draftwright-0.3.3 → draftwright-0.3.5}/tests/test_witness_label_reconciliation.py +0 -0
@@ -1,5 +1,302 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ## v0.3.5 — 2026-07-20
6
+
7
+ ### Fixed
8
+
9
+ - **Generated `Sheet` script round-trips `--frame` / `--zones` / `--projection`**:
10
+ the sheet-furniture flags added in #767/#768/#769 were not emitted into the
11
+ generated `Sheet(...)` constructor (nor forwarded by the CLI's `--script` path),
12
+ so a regenerated script dropped the border / zone ruler / projection symbol —
13
+ the script no longer matched the direct CLI drawing. The emitter now emits
14
+ `frame=True` / `zones=True` / `projection=…` when set (a plain drawing keeps a
15
+ clean constructor), and the CLI forwards them to `generate_sheet_script`.
16
+
17
+ ### Added
18
+
19
+ - **ISO 5457 zone-grid border ruler** (#768): `build_drawing(zones=True)` /
20
+ `Sheet(zones=True)` / `--zones` draws the grid reference system — numbers 1..
21
+ along the top/bottom edges, letters A.. (skipping I/O) down the sides — in the
22
+ band between the frame and the page edge, with the standard division count per
23
+ A-series page (~50 mm zones). Implies a frame (the ticks sit on it). Off by
24
+ default → byte-identical. The border ticks are lint-exempt like the frame; the
25
+ labels are exempt only from the page-bounds check (they legitimately sit outside
26
+ the drawable), still covered by overlap lint.
27
+ - **Projection-method symbol** (#769): `build_drawing(projection="third"|"first")` /
28
+ `Sheet(projection=…)` / `--projection` draws the ISO 5456-2 third-/first-angle
29
+ glyph in the reserved title-block band (above the title block). Uses the new
30
+ `ProjectionSymbol` primitive from build123d-drafting-helpers **0.14.1** (the pin
31
+ is bumped). Off by default → byte-identical. Unlike the page-spanning frame, the
32
+ small well-placed glyph is *not* lint-exempt — lint covers it so a future
33
+ mispositioning is caught.
34
+ - **Opt-in drawn sheet frame / border** (#767): `build_drawing(frame=True)` /
35
+ `Sheet(frame=True)` / `--frame` draws a border rectangle at the page margin. It's
36
+ the *content boundary* (Option B), not a rectangle over the drawing: turning it on
37
+ raises the effective content margin, and because `compose._layout_geometry` is the
38
+ single authority shared by scale/page selection **and** placement, the reservation
39
+ flows through `choose_scale` — so a framed drawing may pick a smaller scale / larger
40
+ page (ADR 0004), with content clearing the border. Default `frame=False` is
41
+ byte-identical (guarded by the golden + layout-cleanliness suites). The page-spanning
42
+ border carries an `is_sheet_frame` rider so lint skips it.
43
+
44
+ ## v0.3.4 — 2026-07-20
45
+
46
+ ### Changed
47
+
48
+ - **Imperative `--script` output defaults to PDF and the modern export form**
49
+ (#709). **Breaking** for regenerated imperative scripts: without `-f`, a
50
+ regenerated script now writes `<stem>.pdf` (was SVG+DXF via the deprecated
51
+ legacy tuple), and the script-visible variables changed from
52
+ `svg_path, dxf_path` to a `paths` dict printed in the requested-format order.
53
+ This aligns the imperative flavour with the Sheet flavour and the direct CLI,
54
+ which have defaulted to PDF since #702/#288. Migration: regenerate with
55
+ `--format svg,dxf` to keep the old outputs. Previously-generated scripts are
56
+ unaffected (they carry their own export lines).
57
+ - **`finalize()` drains in the auto-pass's order** (#699 slice b): both build
58
+ paths now execute the orchestrator's one canonical `_PASS_SEQUENCE` (via the
59
+ shared `run_stages`), removing the hand-mirrored second orchestration in
60
+ `Drawing._drain_intents`. For the deferred/finalize path this reorders three
61
+ things toward auto-pass parity: the turned diameter/step-length set-solves now
62
+ place *before* the corridor drain (matching the auto-pass's obstacle
63
+ visibility); slots register after them; and the register-only height-ladder /
64
+ step-position stages register *after* locations — observable too, since
65
+ registration order decides corridor key-creation (= drain) order and
66
+ same-priority tie-breaks (Codex review; the new order is exactly the
67
+ auto-pass's). Recompose output may shift accordingly; the auto-pass order is
68
+ unchanged.
69
+ - **`Sheet.export` speaks the modern export API** (#702): it now takes
70
+ `formats=` (any of svg/dxf/pdf/png, default **PDF** — matching the CLI
71
+ default) and `dpi=`, and returns the `{format: path}` dict. Previously it
72
+ rode the deprecated legacy path, could only write SVG+DXF — the flagship
73
+ facade couldn't produce a PDF — and returned the old `(svg_path, dxf_path)`
74
+ tuple. **Breaking** for callers unpacking that tuple: use
75
+ `sheet.export(stem, formats=("svg", "dxf"))` and read the dict.
76
+
77
+ ### Added
78
+
79
+ - **Solve-trace / explain mode** (#736, from the #733 post-mortem): opt-in
80
+ observability for strip placement. `build_drawing(trace=…)` or
81
+ `DRAFTWRIGHT_TRACE=<path-or-dir>` writes ONE JSON file per build (schema v2)
82
+ with two record types: `solves` — every corridor solve's key, strip bounds,
83
+ candidate set, the obstacles that carved the strip (with owning annotation
84
+ names), the free segments, and each candidate's outcome (placed /
85
+ dropped-with-reason / deduped / promoted / deferred-to-post-drain) — and
86
+ `pass_events` — the standalone strip passes plus the *immediate* placers
87
+ (post-drain machined-feature leader callouts, the turned diameter row/column
88
+ and step-length set-solves), each with per-item placed/dropped outcomes. So
89
+ "why did X drop" is one `jq` query (`.solves[].outcomes[]` /
90
+ `.pass_events[].items[]`). A `placement_unsatisfiable` strip-full drop now
91
+ also names the top occupants in its lint message (`…strip full; occupied
92
+ by: …`). Recording-only: an unwritable path logs a warning (never aborts a
93
+ build), writes are atomic, and the recorder joins `finalize()`'s #647
94
+ rollback. Default off; zero output change, nil cost when off.
95
+
96
+ - **`draftwright.model.authored_dimension`** (#704): the IR constructor behind
97
+ `Sheet.dimension()`, extracted so `build_drawing(model=…)` callers can author
98
+ a pre-measured drafting dimension without the `Sheet` façade.
99
+
100
+ ### Added
101
+
102
+ - **Object-reference script workflow: docs + an inline tip** (#770/#771): a
103
+ generated `Sheet` script from a **live-source** input (a `module:attr` /
104
+ `file.py:attr` object spec, or a build123d object) now carries an inline tip
105
+ showing how to swap each detected-numbers line for a reference to your object
106
+ (ADR 0011 declare — the size is read off the object). A STEP-sourced script is
107
+ unchanged (byte-stable — no object to reference). The README documents the full
108
+ from-a-part-to-an-object-referenced-script workflow.
109
+ - **Knurl callout verb** (#765): `sheet.diameter(shaft).knurl("0.8")` →
110
+ `KNURL 0.8 STRAIGHT` (or `.knurl("0.8", "DIAMOND")`). Named sugar over
111
+ `.note()` with canonical formatting — a knurl is a text callout on a leader,
112
+ not modelled geometry, so it flows through the existing note path (no new
113
+ IR/render).
114
+ - **Standing ISO 7200 title-block fields** (#766): `material`, `date`, `revision`,
115
+ and `company` (legal owner) are now first-class — settable on `build_drawing` /
116
+ `make_drawing`, the `Sheet(...)` constructor, and the CLI (`--material` /
117
+ `--date` / `--revision` / `--company`), and emitted into the generated `Sheet`
118
+ script when non-default so a re-run reproduces them. Defaults preserve the prior
119
+ output (revision `A`, the rest blank). The `revision`/`legal_owner` cells the
120
+ renderer previously hardcoded now flow from these. (The imperative `--script`
121
+ flavour's title-block round-trip of these fields is a follow-up.)
122
+ - **First-class thread/tap callout on a hole** (#764): ``sheet.hole(...).thread("M3x0.5")``
123
+ (and ``declare.hole(..., thread=)``) folds a tap/thread spec onto the hole's existing
124
+ compound leader (e.g. ``ø2.5 THRU M3x0.5``) — a structured aspect that round-trips, so
125
+ ``.thread(...).finish(...)`` yields Ra-on-thread. A declaration-only aspect (ADR 0011
126
+ side-layer: threads are cosmetic, not modelled geometry — no recogniser). The layout
127
+ width estimator (``_est_planned_bore_callout_width``) now accounts for the thread text so
128
+ the wider callout is reserved room and not dropped (the #261 estimator/render agreement).
129
+
130
+ ### Fixed
131
+
132
+ - **Generated imperative script reconstructs side-drilled hole locations**
133
+ (#426/#133): an X/Y-axis (side-drilled) bore gets `dim_loc_*` position dims in
134
+ the direct build, but the `--script` emitter emitted a gap comment instead of a
135
+ `locate()` for non-Z holes (because `locate()`/`render_locations` are Z-plan
136
+ only, #133). The emitter now emits `dwg.locate(f)` for holes on any axis;
137
+ `finalize()` routes by the feature's axis — Z-plan holes drain through
138
+ `render_locations` as before, side-drilled bores through the whole-model
139
+ `_locate_off_axis_holes` pass (registered at the auto-pass's own
140
+ `off_axis_across`/`off_axis_along` stages, placed at the shared drain). The
141
+ Z-only `locate()` live contract is untouched (side-drilled locates never reach
142
+ it — they route to the off-axis stages). Closes the last direct-vs-generated
143
+ script parity gap tracked by #426.
144
+ - **Generated imperative script reconstructs rotational furniture** (#426/#424):
145
+ a turned/cylindrical part's `dwg.model()` carries a `RotationalFeature`, but the
146
+ `--script` emitter parked it in the gap-comment list, so an executed
147
+ reconstruction of e.g. a plain cylinder was missing `dim_od` and the
148
+ `centerline_front`/`centerline_side` marks the direct build draws. A new
149
+ `Drawing.rotational(feature)` add-verb records the intent, and `finalize()`
150
+ drains it through the shared whole-model `render_rotational` at the auto-pass's
151
+ own `"rotational"` stage — byte-identical to the direct build (no `only=` subset,
152
+ fixed literal names, so no naming-seam or `⊇`-vs-`==` gap). The emitter now emits
153
+ `dwg.rotational(f)` for rotational features. Closes the rotational half of the
154
+ direct-vs-generated-script convergence.
155
+ - **Emitted `Sheet` script now faithfully reproduces the direct CLI drawing**
156
+ (#707): the divergence reported against 0.3.3 on the Maquetto GRM-03 part was
157
+ closed by #709 (script `--format` forwarding) + #661 (finalize detail drain).
158
+ Locked with a vendored-fixture regression that asserts the full invariant the
159
+ issue named — same views, annotation inventory, page, scale **and** lint
160
+ between `build_drawing(...)` and the executed emitted `Sheet` — not just the
161
+ annotation signature the synthetic parity cases check.
162
+ - **`_largest_empty_rect` no longer blows up on crowded detail views** (#661):
163
+ the largest-empty-rectangle placer enumerated every candidate rectangle
164
+ (O(N⁴) in the coordinate-cut count) — fine for the iso view's handful of
165
+ obstacles, but the detail-view placer feeds it *every* placed-annotation
166
+ footprint, so a two-detail turned part reached ~70 obstacles and spent ~13 s
167
+ in a single call (multi-minute on the slower Windows CI runners, which tipped
168
+ some jobs over their wall-clock budget). It now skips candidates that provably
169
+ can't beat the best found so far (a `bisect` past every too-narrow pair, an
170
+ early `break` once even the widest is too small): the same exact result and
171
+ tie-break — verified identical to the naïve search across 3 000 random cases —
172
+ at ~85× the speed on the pathological input (12.6 s → 0.15 s).
173
+ - **Detail-view height demotion is pin- and provenance-safe** (#661, user
174
+ review): when a crowded prismatic detail needs the overall-height dim's room it
175
+ is demoted and the detail retried, but (a) the canonical `dim_height` name was
176
+ handed to that retry unconditionally — a *pinned* or user-replaced height could
177
+ be removed; it now passes the same pin/label guards as the generalised
178
+ `dim_length{n}` path (a pin, ADR 0012, is never demoted), and (b) if the retry
179
+ also failed, the restore re-added only object/name/view, orphaning the dim from
180
+ its envelope feature (lost `annotations_of`/`drop`/re-discovery); it now
181
+ snapshots and restores the feature provenance and pin too.
182
+ - **`finalize()` resolves queued detail requests — detail views now exist on the
183
+ edit path** (#661): the finalize drain was missing the auto pass's
184
+ `detail_request`/`details` stages, so a crowded turned head's queued
185
+ `DetailRequest` died with the per-run context and a prismatic
186
+ `detail_view=True` build never even queued one — generated scripts silently
187
+ lost every DETAIL A/B view. `build_drawing(detail_view=…)` is now persisted on
188
+ the build state so the drain gates the prismatic request exactly as the auto
189
+ pass does; the details stage re-projects the iso at sheet scale for the
190
+ free-rectangle search and refits it after (mirroring the auto pass's ordering,
191
+ where details place before the iso is fitted); and the overall-height demotion
192
+ retry now finds the height dim by its envelope attribution, not only the
193
+ auto-pass `dim_height` name. Three script-parity characterisation tests
194
+ (single detail, two details, turned head) flip from xfail to passing.
195
+ - **Y-turned stepped shafts no longer crash** (#661): `render_step_lengths`
196
+ projected every step span into the front view, where a Y-axis span is end-on
197
+ (a point) — `Dimension` then raised "start and end points must be different"
198
+ on BOTH the direct build and a generated script's finalize. Y-axis steps are
199
+ now skipped (no step render pipeline consumes Y, #731 — mirroring
200
+ `render_diameters`' x/z bucketing), the emitter flags the gap as a comment
201
+ instead of emitting an unreplayable `dimension()` verb, and a hand-written
202
+ Y step-length intent live-replays (surfacing the verb's own behaviour) rather
203
+ than vanishing into the routed no-op. The Y-axis script-parity
204
+ characterisation test flips from xfail to passing.
205
+ - **Boss heights are modeled, rendered, and coverage-checked** (#632): detected
206
+ and object-declared cylindrical bosses now carry their axial extent through
207
+ `BossFeature` and the dimension planner. Prismatic bosses receive a linear
208
+ height dimension in a profile view in addition to their end-on diameter
209
+ leader. `Drawing.lint()` reconciles that modeled height against the live
210
+ feature-owned `Dimension`, reporting `boss_height_missing` if it is removed
211
+ or otherwise absent (demoted to `info` on assembly drawings). This closes the
212
+ declarative false-negative where a boss's diameter and the overall envelope
213
+ were documented but its own independent height was not.
214
+
215
+ - **`--format` now reaches `--script` output** (#709, from the #702 adversarial
216
+ review): `--script -f svg` used to silently emit a PDF-producing script — the
217
+ CLI parsed the flag but never forwarded it. Both emitters now thread it
218
+ through: the Sheet script emits `sheet.export(stem, formats=(…,))` when
219
+ non-default (bare call for the PDF default), the imperative template embeds
220
+ the requested tuple and prints in the **requested** order, and
221
+ `generate_script` / `generate_sheet_script` grow a `formats=` parameter. The
222
+ dormant script-parity characterisation module
223
+ (`test_script_detail_parity.py`) is also un-skipped so its strict xfails give
224
+ the remaining gaps CI signal.
225
+
226
+ - **Authored fillet, flat, groove, pocket, plate and slot tolerances now render**
227
+ (#725/#726/#727/#728/#729/#730 / #698): the four leader-callout kinds join
228
+ `_CONVENTION` and their renderers (`render_fillets`/`render_flats`/
229
+ `render_grooves`/`render_pockets`) — and the plate-thickness and slot
230
+ width/length linear passes
231
+ (`render_plates`/`render_slots`, explicit `"linear"` entries) — consume the
232
+ planner's `DimensionGroup`s, binding each
233
+ planned dim explicitly by `(role, kind)` — so a `decorations`-authored ± /
234
+ limit tolerance reaches the placed callout (`R8 ±0.1`, `17 ±0.2 A/F`,
235
+ `4 ±0.1 WIDE × ø16 ±0.5`, `18 ±0.2 × 30 ±0.2 × 5 ±0.2 DEEP`, a `10 ±0.1`
236
+ thickness dim, an `8 ±0.1` slot width; on a
237
+ multi-value label each suffix rides its own number). Previously all six
238
+ passes formatted raw feature fields and silently dropped it — the latent #629
239
+ bug class the ADR 0015 bypass list documents. The fillet `n× R` and flat
240
+ double-D/hex collapses stay render-side (first-authored tolerance wins, the
241
+ `render_diameters` precedent); a pocket's three values — and a slot's
242
+ width + length — share one authored
243
+ tolerance (all kind `"length"`). The slot's model-derived datum position dim
244
+ and its corridor/immediate placement mechanics are untouched. Untolerated
245
+ labels/views/placement are unchanged.
246
+ - **Authored chamfer tolerances now render** (#724 / #698): `render_chamfers`
247
+ consumes the planner's `DimensionGroup` (chamfer joins `_CONVENTION` as a
248
+ leader), so a `decorations`-authored ± / limit tolerance on a chamfer leg
249
+ reaches the placed callout (`C12 ±0.2`). Previously the pass formatted raw
250
+ feature fields and silently dropped it — the latent #629 bug class the ADR
251
+ 0015 bypass list documents. Untolerated chamfer labels/views are unchanged.
252
+ - **`model.chamfer(face=…)` rejects a non-planar face** (#704): the declared
253
+ front-end silently accepted a curved face (`normal_at()` does not fail on
254
+ one) and read garbage legs off it — e.g. a fillet or countersink face became
255
+ a bogus `ChamferFeature`. It now raises the documented `ValueError`, using
256
+ the recogniser's own planarity gate (`classify_bevel`), so declared and
257
+ detected chamfers classify identically by construction.
258
+
259
+ ### Fixed
260
+
261
+ - **CTC-02/04 no longer drop step-height / overall-height dims** (#733): when
262
+ the height ladder joined the corridor solve (#689) it silently moved from
263
+ "places early" to "registers early, places at the drain" — so the immediate
264
+ machined-feature leader callouts (pockets on CTC-04) filled the front-right
265
+ strip first and the FORCED principal dims hard-dropped
266
+ (`placement_unsatisfiable`), a priority inversion the old stage ordering had
267
+ prevented implicitly. The leader-callout stages (chamfers/fillets/flats/
268
+ pockets) now sit **after the corridor drain** in `_PASS_SEQUENCE` (joining
269
+ grooves, which already placed post-drain for exactly this reason): principal
270
+ dims win by construction, and a callout with no clear room yields with a
271
+ warning, never a principal-dim error. Callout leaders may shift (they now
272
+ route around the drained dims); on the `hex_bar` golden this *recovers* a
273
+ silently starved `m_env_width` envelope dim.
274
+
275
+ ### Removed
276
+
277
+ - **Three orphaned root exports** (#704): `draftwright.recognise_face_levels`
278
+ (one recogniser of ~12 — incoherent as a top-level surface; import it from
279
+ `draftwright.recognition`), `draftwright.dedup_diams` (an analysis internal),
280
+ and `draftwright.fix_svg_page_size` (still available from `draftwright.export`
281
+ and the `draftwright.make_drawing` facade). None had any internal or test
282
+ consumer via the root API.
283
+
284
+ ### Deprecated
285
+
286
+ - **The seven `Drawing` compat aliases** (`_named`/`_anno_view`/`_pinned`/
287
+ `_build_issues`/`_pattern_callouts`/`_patterned_holes`/
288
+ `_dropped_callout_diams`) are formally on notice (#699 slice c, tracked by
289
+ #720): removal target **0.4.0**, per ADR 0005 §4. Read through the public
290
+ surface instead — `annotations()`, `iter_annotations()`, `get_annotation()`,
291
+ `view_of()`, `registry.pinned_names()`/`is_pinned()`, and the new
292
+ `registry.issues`. All external production and test call sites in this repo
293
+ are already redirected (`drawing.py` internals ride the aliases until the
294
+ #720 deletion).
295
+ - **`draftwright.sheet_dsl`** (belated announcement — the alias shim shipped
296
+ in 0.3.1 when #640 renamed the module to `draftwright.sheet`): importing it
297
+ warns; it will be removed in **0.4.0**. Import from `draftwright.sheet`, or
298
+ just `from draftwright import Sheet`.
299
+
3
300
  ## v0.3.3 — 2026-07-18
4
301
 
5
302
  The **layout-quality release**: the occupancy model gets honest about ink shape,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: draftwright
3
- Version: 0.3.3
3
+ Version: 0.3.5
4
4
  Summary: Automated technical-drawing generation for build123d
5
5
  Project-URL: Homepage, https://github.com/pzfreo/draftwright
6
6
  Project-URL: Repository, https://github.com/pzfreo/draftwright
@@ -682,7 +682,7 @@ Classifier: Programming Language :: Python :: 3.13
682
682
  Classifier: Programming Language :: Python :: 3.14
683
683
  Classifier: Topic :: Scientific/Engineering
684
684
  Requires-Python: <3.15,>=3.10
685
- Requires-Dist: build123d-drafting-helpers>=0.14.0
685
+ Requires-Dist: build123d-drafting-helpers>=0.14.1
686
686
  Requires-Dist: build123d<0.11,>=0.9.0; python_full_version < '3.13'
687
687
  Requires-Dist: build123d<0.12,>=0.11; python_full_version >= '3.13'
688
688
  Requires-Dist: numpy>=1.24
@@ -789,18 +789,47 @@ sheet.envelope()
789
789
  sheet.datum("A", plate.faces().sort_by()[-1]) # datum A on the top face
790
790
  hole = sheet.hole(bore).finish("1.6").note("M3x0.5 TAP") # ⌀8 bore, Ra 1.6, tapped
791
791
  sheet.control(hole).position(0.1, to="A", diameter=True) # ⌀0.1 position wrt A
792
- sheet.export("plate") # writes plate.svg + plate.dxf
792
+ sheet.export("plate") # writes plate.pdf
793
793
  ```
794
794
 
795
795
  Every aspect the geometry can't carry is a declared verb: `.tolerance(±)` / `.fit("H7")`
796
796
  (ISO 286), `.finish("Ra")` (ISO 1302), `sheet.datum(letter, ref)` (ISO 5459), and
797
797
  `sheet.control(ref)` with all 14 ISO 1101 characteristics
798
- (`.position`/`.flatness`/`.perpendicularity`/`.circular_runout`/…), plus `sheet.note(text, ref)`
799
- / `.note(text)` for free-text shop callouts (thread specs, `DEBURR`, knurl). A feature verb returns
798
+ (`.position`/`.flatness`/`.perpendicularity`/`.circular_runout`/…), plus `.thread("M3x0.5")`
799
+ (a tap/thread spec folded onto the hole callout), `.knurl("0.8")`, and `sheet.note(text, ref)`
800
+ / `.note(text)` for any other free-text shop callout (`DEBURR`, chip-relief). A feature verb returns
800
801
  a chainable handle (`sheet.hole(bore)`) that the aspect and `control(...)` verbs decorate;
801
802
  targets are placed automatically — the view and strip are derived from the referenced
802
803
  feature or face, with `view=`/`side=` overrides.
803
804
 
805
+ ### From a part to an object-referenced script
806
+
807
+ `draftwright part.step --script` writes an editable `Sheet` script. When you built the part
808
+ yourself (rather than importing a STEP), pass your **live source** as a `module:attr` (or
809
+ `file.py:attr`) object spec, and the script binds `part` back to your real object:
810
+
811
+ ```bash
812
+ draftwright mymodule:thumbwheel --script
813
+ ```
814
+
815
+ The emitted values are *detected* off the geometry — honest, and a good starting point. Since
816
+ you have the objects, swap each numbered line for a reference so the object stays the single
817
+ source of truth (ADR 0011 — the size is read off the object, no numbers restated):
818
+
819
+ ```python
820
+ # generated (detected):
821
+ sheet.step(diameter=8, length=25, at=(0, 0, 12), axis="z")
822
+
823
+ # edited to reference your objects:
824
+ features = mymodule.thumbwheel_features()
825
+ part = features.part
826
+ sheet.step(features.journal)
827
+ sheet.hole(features.m3_bore).thread("M3x0.5").finish("1.6") # tapped + Ra on the same hole
828
+ ```
829
+
830
+ An object-sourced script carries an inline tip pointing at exactly this edit; a STEP-sourced
831
+ script keeps the detected numbers (there's no object to reference).
832
+
804
833
  ## What it produces
805
834
 
806
835
  - **Three orthographic views** (front, plan, side) sized and scaled automatically to the
@@ -854,14 +883,16 @@ dwg.repair() # auto-fix mechanically-fixable lint; never worsens
854
883
 
855
884
  Each `LintIssue` carries a domain-meaningful `code` and, when computable, a
856
885
  ready-to-apply `suggestion`. See `docs/adr/` for the design (deterministic
857
- generation, the lint→repair loop, and the constraint-based layout engine).
886
+ generation, the lint→repair loop, and collect-then-solve placement).
858
887
 
859
888
  ## Architecture
860
889
 
861
- draftwright is structured as a **part-drawing compiler** (ADR 0008): feature
862
- detectors a `PartModel` IR (`Feature`s exposing `DimParameter`s) → a dimensioning
863
- planner renderers, feeding a shared layout/projection/export stack. It builds on
864
- two libraries:
890
+ draftwright is structured as a **part-drawing compiler** (ADR 0015): recognised
891
+ or declared features converge on a `PartModel` IR, then planner-fed and
892
+ sanctioned model-routed render intents feed shared placement, projection, and
893
+ export. Coverage lint independently compares recognised geometry with the
894
+ placed drawing, so an upstream omission cannot hide from verification. It
895
+ builds on two libraries:
865
896
 
866
897
  ```
867
898
  draftwright
@@ -874,8 +905,7 @@ primitives (`Dimension`, `Leader`, etc.) live in `build123d-drafting-helpers` an
874
905
  be used independently. The compiler is largely converged in production — turned
875
906
  dims/lengths, centre marks, envelope, slots, holes (callouts/locations/grouping),
876
907
  the section A–A trigger, the prismatic step-ladder + rotational furniture, and PMI/GD&T
877
- are all on the IR — the migration is complete (one path; the orchestrator is
878
- build → plan → render). See
908
+ are all on the IR. See
879
909
  [`docs/target-architecture.md`](docs/target-architecture.md) and
880
910
  [`docs/layout-algorithm-primer.md`](docs/layout-algorithm-primer.md) for a short walkthrough, plus
881
911
  [`docs/adr/`](docs/adr/). The engine handles view layout (strip/zone model), scale
@@ -94,18 +94,47 @@ sheet.envelope()
94
94
  sheet.datum("A", plate.faces().sort_by()[-1]) # datum A on the top face
95
95
  hole = sheet.hole(bore).finish("1.6").note("M3x0.5 TAP") # ⌀8 bore, Ra 1.6, tapped
96
96
  sheet.control(hole).position(0.1, to="A", diameter=True) # ⌀0.1 position wrt A
97
- sheet.export("plate") # writes plate.svg + plate.dxf
97
+ sheet.export("plate") # writes plate.pdf
98
98
  ```
99
99
 
100
100
  Every aspect the geometry can't carry is a declared verb: `.tolerance(±)` / `.fit("H7")`
101
101
  (ISO 286), `.finish("Ra")` (ISO 1302), `sheet.datum(letter, ref)` (ISO 5459), and
102
102
  `sheet.control(ref)` with all 14 ISO 1101 characteristics
103
- (`.position`/`.flatness`/`.perpendicularity`/`.circular_runout`/…), plus `sheet.note(text, ref)`
104
- / `.note(text)` for free-text shop callouts (thread specs, `DEBURR`, knurl). A feature verb returns
103
+ (`.position`/`.flatness`/`.perpendicularity`/`.circular_runout`/…), plus `.thread("M3x0.5")`
104
+ (a tap/thread spec folded onto the hole callout), `.knurl("0.8")`, and `sheet.note(text, ref)`
105
+ / `.note(text)` for any other free-text shop callout (`DEBURR`, chip-relief). A feature verb returns
105
106
  a chainable handle (`sheet.hole(bore)`) that the aspect and `control(...)` verbs decorate;
106
107
  targets are placed automatically — the view and strip are derived from the referenced
107
108
  feature or face, with `view=`/`side=` overrides.
108
109
 
110
+ ### From a part to an object-referenced script
111
+
112
+ `draftwright part.step --script` writes an editable `Sheet` script. When you built the part
113
+ yourself (rather than importing a STEP), pass your **live source** as a `module:attr` (or
114
+ `file.py:attr`) object spec, and the script binds `part` back to your real object:
115
+
116
+ ```bash
117
+ draftwright mymodule:thumbwheel --script
118
+ ```
119
+
120
+ The emitted values are *detected* off the geometry — honest, and a good starting point. Since
121
+ you have the objects, swap each numbered line for a reference so the object stays the single
122
+ source of truth (ADR 0011 — the size is read off the object, no numbers restated):
123
+
124
+ ```python
125
+ # generated (detected):
126
+ sheet.step(diameter=8, length=25, at=(0, 0, 12), axis="z")
127
+
128
+ # edited to reference your objects:
129
+ features = mymodule.thumbwheel_features()
130
+ part = features.part
131
+ sheet.step(features.journal)
132
+ sheet.hole(features.m3_bore).thread("M3x0.5").finish("1.6") # tapped + Ra on the same hole
133
+ ```
134
+
135
+ An object-sourced script carries an inline tip pointing at exactly this edit; a STEP-sourced
136
+ script keeps the detected numbers (there's no object to reference).
137
+
109
138
  ## What it produces
110
139
 
111
140
  - **Three orthographic views** (front, plan, side) sized and scaled automatically to the
@@ -159,14 +188,16 @@ dwg.repair() # auto-fix mechanically-fixable lint; never worsens
159
188
 
160
189
  Each `LintIssue` carries a domain-meaningful `code` and, when computable, a
161
190
  ready-to-apply `suggestion`. See `docs/adr/` for the design (deterministic
162
- generation, the lint→repair loop, and the constraint-based layout engine).
191
+ generation, the lint→repair loop, and collect-then-solve placement).
163
192
 
164
193
  ## Architecture
165
194
 
166
- draftwright is structured as a **part-drawing compiler** (ADR 0008): feature
167
- detectors a `PartModel` IR (`Feature`s exposing `DimParameter`s) → a dimensioning
168
- planner renderers, feeding a shared layout/projection/export stack. It builds on
169
- two libraries:
195
+ draftwright is structured as a **part-drawing compiler** (ADR 0015): recognised
196
+ or declared features converge on a `PartModel` IR, then planner-fed and
197
+ sanctioned model-routed render intents feed shared placement, projection, and
198
+ export. Coverage lint independently compares recognised geometry with the
199
+ placed drawing, so an upstream omission cannot hide from verification. It
200
+ builds on two libraries:
170
201
 
171
202
  ```
172
203
  draftwright
@@ -179,8 +210,7 @@ primitives (`Dimension`, `Leader`, etc.) live in `build123d-drafting-helpers` an
179
210
  be used independently. The compiler is largely converged in production — turned
180
211
  dims/lengths, centre marks, envelope, slots, holes (callouts/locations/grouping),
181
212
  the section A–A trigger, the prismatic step-ladder + rotational furniture, and PMI/GD&T
182
- are all on the IR — the migration is complete (one path; the orchestrator is
183
- build → plan → render). See
213
+ are all on the IR. See
184
214
  [`docs/target-architecture.md`](docs/target-architecture.md) and
185
215
  [`docs/layout-algorithm-primer.md`](docs/layout-algorithm-primer.md) for a short walkthrough, plus
186
216
  [`docs/adr/`](docs/adr/). The engine handles view layout (strip/zone model), scale
@@ -0,0 +1,51 @@
1
+ # Architecture decision records
2
+
3
+ This is the front door to draftwright's ADR corpus. Start with the **Current
4
+ architecture** table; open retired or superseded records only for design history.
5
+
6
+ ## Maintenance rules
7
+
8
+ - Keep the status header to one load-bearing statement.
9
+ - At roughly four amendments, write a successor ADR and freeze the old record.
10
+ The superseded file remains the historical why-trail; do not rewrite it.
11
+ - Cite symbols and test names, never source line numbers.
12
+ - If an invariant can be checked mechanically, guard it with a test. A claim
13
+ that something is machine-checked must name the real check.
14
+ - Preserve reversals in the frozen record. The successor compresses each to one
15
+ line and points back to the history.
16
+ - Update this index whenever an ADR is accepted, retired, or superseded, or when
17
+ its guarding test changes.
18
+
19
+ ## Current architecture
20
+
21
+ | ADR | Title | Decision | Status | Representative guards |
22
+ | --- | --- | --- | --- | --- |
23
+ | [0001](0001-deterministic-generation-over-editable-dsl.md) | Deterministic generation and domain-semantic editing over a bespoke editable-code DSL | Prefer deterministic generation and domain-semantic editing over a primitive editable-code DSL. | Accepted | `test_sheet_emit.py`, `test_make_drawing.py` |
24
+ | [0002](0002-iterate-via-lint-critique-and-domain-repair.md) | Iterate via lint critique and domain-semantic repair, not by editing generated code | Refine drawings through machine-readable lint and narrowly allowlisted domain repair. | Accepted | `test_linting.py`, `test_lint_structural.py`, `test_make_drawing.py` |
25
+ | [0004](0004-compose-then-pack-view-blocks.md) | Compose-then-pack: views as blocks carrying their annotation footprint | Compose each view with its annotation footprint, then pack fixed-topology blocks. | Accepted | `test_layout.py`, `test_layout_cleanliness.py`, `test_refactor_golden.py` |
26
+ | [0005](0005-pipeline-architecture-and-state-ownership.md) | Compiler-pipeline module boundaries and single-owner build state | Give compiler stages explicit module homes and build-time state explicit owners. | Accepted; implemented; compatibility aliases tracked by #720 | `test_import_boundaries.py`, `test_drawing_encapsulation.py`, `test_registry.py` |
27
+ | [0006](0006-deterministic-layout-via-bundled-fonts.md) | Deterministic cross-platform layout via bundled, path-pinned fonts | Pin bundled font files so text measurement and layout are cross-platform deterministic. | Accepted | `test_refactor_golden.py`, `test_layout_cleanliness.py` |
28
+ | [0007](0007-own-recognition-and-linting.md) | draftwright owns feature recognition and linting; helpers becomes the rendering library | draftwright owns recognition and linting; helpers remains a rendering library. | Accepted | `test_import_boundaries.py`, `test_recognition.py`, `test_linting.py` |
29
+ | [0010](0010-annotation-provenance-seam.md) | Annotation provenance: record intent → annotation once, at the render seam | Record annotation provenance once at the render/add seam. | Accepted; landed | `test_render_seam.py`, `test_registry.py` |
30
+ | [0011](0011-ir-as-public-input.md) | The IR as a public input: declare features, don't only detect them | Accept the feature IR as public input through declarations and the `Sheet` façade. | Accepted; core landed; #62/#462/#495 remain | `test_declare.py`, `test_object_aspects.py`, `test_sheet_gdt.py` |
31
+ | [0012](0012-edits-as-pinned-priority-candidates-in-the-global-solve.md) | User annotation edits are pinned, priority-ranked corridor candidates | Drain recorded semantic edits through corridor placement with pin and priority. | Accepted; partially landed; full recomposition/parity remains #426/#707 (#661 detail views landed) | `test_make_drawing.py`, `test_sheet_emit.py` |
32
+ | [0013](0013-uniform-recognition-and-shared-package.md) | A uniform recogniser/feature contract (with `b123d-recognisers` as its deferred shared deployment) | Enforce a uniform geometry-only recogniser contract internally; defer package extraction until there is a second consumer. | Accepted; Phase 1 in progress; extraction deferred | `test_recogniser_contract.py`, `test_import_boundaries.py` |
33
+ | [0014](0014-collect-then-solve-annotation-placement.md) | Collect-then-solve annotation placement (as built) | Collect, select, assign, and deterministically solve annotations per corridor before rendering. | Accepted; supersedes 0009 | `test_carve_free_position_callers.py`, `test_strip_layout.py`, `test_layout_property.py`, `test_import_boundaries.py` |
34
+ | [0015](0015-part-drawing-compiler-as-built.md) | The part-drawing compiler, as built | Use one detected-or-declared feature IR and planner-fed dimension groups as the compiler waist. | Accepted; supersedes 0008 | `test_part_model.py`, `test_detect_once.py`, `test_import_boundaries.py` |
35
+
36
+ ## Historical records
37
+
38
+ | ADR | Title | Historical decision | Status | Read instead |
39
+ | --- | --- | --- | --- | --- |
40
+ | [0003](0003-constraint-based-layout.md) | Constraint-based layout: one solver for every placeable | Explored a universal `Placeable`/`LayoutSolver` and page-global constraint solve. | Retired; carrier deleted and #94 closed as unnecessary | [0004](0004-compose-then-pack-view-blocks.md) for outer layout and [0014](0014-collect-then-solve-annotation-placement.md) for inner placement |
41
+ | [0008](0008-unified-feature-model-and-dimensioning-planner.md) | The part-drawing compiler: a Feature/DimParameter IR and a dimensioning planner | Introduced the feature/parameter IR and dimensioning-planner direction. | Superseded by 0015; frozen | [0015](0015-part-drawing-compiler-as-built.md) |
42
+ | [0009](0009-boundary-labeling-strip-placement.md) | Boundary labeling: collect-then-solve per-strip annotation placement | Developed collect-then-solve boundary-label placement through nine amendments. | Superseded by 0014; frozen | [0014](0014-collect-then-solve-annotation-placement.md) |
43
+
44
+ ## Reading paths
45
+
46
+ - Compiler and state ownership: 0001 → 0005 → 0015.
47
+ - Recognition and public declaration: 0007 → 0013 → 0011 → 0015.
48
+ - Layout and placement: 0004 → 0014 → 0012.
49
+ - Quality and correction: 0002, with provenance from 0010.
50
+
51
+ Tracking issue: #745.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "draftwright"
7
- version = "0.3.3"
7
+ version = "0.3.5"
8
8
  description = "Automated technical-drawing generation for build123d"
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
@@ -17,7 +17,7 @@ dependencies = [
17
17
  # 3.13+. Mirrors pzfreo/build123d-mcp's dependency handling.
18
18
  "build123d>=0.9.0,<0.11 ; python_full_version < '3.13'",
19
19
  "build123d>=0.11,<0.12 ; python_full_version >= '3.13'",
20
- "build123d-drafting-helpers>=0.14.0",
20
+ "build123d-drafting-helpers>=0.14.1",
21
21
  "numpy>=1.24",
22
22
  # PNG raster export (SVG→PDF→PNG). Both permissively licensed (Pillow HPND; pypdfium2
23
23
  # Apache-2.0 wrapping Google PDFium BSD-3) and ship pre-built wheels with NO native system
@@ -23,15 +23,12 @@ from typing import TYPE_CHECKING
23
23
  # none of these names, so they stay sub-second instead of paying for the kernel
24
24
  # on every TAB press (#313). Each name maps to the submodule that provides it.
25
25
  _LAZY = {
26
- "recognise_face_levels": "draftwright.recognition",
27
- "dedup_diams": "draftwright.analysis",
28
26
  "build_drawing": "draftwright.builder",
29
27
  "generate_script": "draftwright.builder",
30
28
  "make_drawing": "draftwright.builder",
31
29
  "Drawing": "draftwright.drawing",
32
30
  "FeatureInfo": "draftwright.drawing",
33
31
  "Sheet": "draftwright.sheet",
34
- "fix_svg_page_size": "draftwright.export",
35
32
  "lint_feature_coverage": "draftwright.linting",
36
33
  "PmiRecord": "draftwright.pmi",
37
34
  "extract_pmi": "draftwright.pmi",
@@ -72,14 +69,11 @@ _sys.modules[__name__].__class__ = _DraftwrightModule
72
69
 
73
70
 
74
71
  if TYPE_CHECKING: # static analysers / IDEs — no runtime import, no kernel cost
75
- from draftwright.analysis import dedup_diams
76
72
  from draftwright.builder import build_drawing, generate_script, make_drawing
77
73
  from draftwright.compose import choose_scale
78
74
  from draftwright.drawing import Drawing, FeatureInfo
79
- from draftwright.export import fix_svg_page_size
80
75
  from draftwright.linting import lint_feature_coverage
81
76
  from draftwright.pmi import PmiRecord, extract_pmi
82
- from draftwright.recognition import recognise_face_levels
83
77
  from draftwright.sheet import Sheet
84
78
 
85
79
 
@@ -95,12 +89,9 @@ __all__ = [
95
89
  "FeatureInfo",
96
90
  "PmiRecord",
97
91
  "Sheet",
98
- "recognise_face_levels",
99
92
  "build_drawing",
100
93
  "choose_scale",
101
- "dedup_diams",
102
94
  "extract_pmi",
103
- "fix_svg_page_size",
104
95
  "generate_script",
105
96
  "lint_feature_coverage",
106
97
  "make_drawing",