draftwright 0.3.2__tar.gz → 0.3.4__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 (139) hide show
  1. {draftwright-0.3.2 → draftwright-0.3.4}/CHANGELOG.md +302 -0
  2. {draftwright-0.3.2 → draftwright-0.3.4}/PKG-INFO +41 -11
  3. {draftwright-0.3.2 → draftwright-0.3.4}/README.md +40 -10
  4. draftwright-0.3.4/docs/adr/README.md +51 -0
  5. {draftwright-0.3.2 → draftwright-0.3.4}/pyproject.toml +1 -1
  6. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/__init__.py +0 -9
  7. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/_core.py +186 -30
  8. draftwright-0.3.4/src/draftwright/_geometry.py +142 -0
  9. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/analysis.py +11 -2
  10. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/annotations/_common.py +507 -96
  11. draftwright-0.3.4/src/draftwright/annotations/balloons.py +242 -0
  12. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/annotations/from_model.py +689 -328
  13. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/annotations/holes.py +42 -17
  14. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/annotations/orchestrator.py +327 -160
  15. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/annotations/sections.py +81 -1
  16. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/builder.py +178 -54
  17. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/cli.py +24 -3
  18. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/compose.py +18 -47
  19. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/drawing.py +507 -615
  20. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/intents.py +5 -4
  21. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/layout.py +114 -48
  22. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/linting/__init__.py +2 -0
  23. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/linting/coverage.py +36 -5
  24. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/linting/structural.py +216 -207
  25. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/model/__init__.py +10 -7
  26. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/model/declare.py +124 -47
  27. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/model/detect.py +31 -7
  28. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/model/ir.py +13 -6
  29. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/model/planner.py +38 -3
  30. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/projection.py +7 -3
  31. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/recognition/__init__.py +19 -5
  32. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/recognition/_features.py +1 -1
  33. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/recognition/chamfers.py +50 -24
  34. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/recognition/countersinks.py +19 -5
  35. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/recognition/fillets.py +23 -13
  36. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/recognition/flats.py +6 -3
  37. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/recognition/grooves.py +22 -9
  38. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/recognition/turned.py +5 -2
  39. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/registry.py +16 -0
  40. draftwright-0.3.4/src/draftwright/repair.py +198 -0
  41. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/score.py +13 -9
  42. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/sheet.py +61 -52
  43. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/sheet_dsl.py +1 -1
  44. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/sheet_emit.py +57 -5
  45. draftwright-0.3.4/tests/fixtures/grm03_thumbwheel_drive_screw.step +745 -0
  46. {draftwright-0.3.2 → draftwright-0.3.4}/tests/refactor_golden/centered_rebate.json +3 -3
  47. {draftwright-0.3.2 → draftwright-0.3.4}/tests/refactor_golden/grid_plate.json +6 -6
  48. {draftwright-0.3.2 → draftwright-0.3.4}/tests/refactor_golden/hex_bar.json +23 -5
  49. {draftwright-0.3.2 → draftwright-0.3.4}/tests/refactor_golden/prismatic_ladder.json +3 -3
  50. {draftwright-0.3.2 → draftwright-0.3.4}/tests/refactor_golden/scattered_plate.json +27 -27
  51. draftwright-0.3.4/tests/test_architecture_docs.py +43 -0
  52. {draftwright-0.3.2 → draftwright-0.3.4}/tests/test_carve_free_position_callers.py +22 -20
  53. {draftwright-0.3.2 → draftwright-0.3.4}/tests/test_declare.py +129 -14
  54. {draftwright-0.3.2 → draftwright-0.3.4}/tests/test_detect_once.py +37 -0
  55. draftwright-0.3.4/tests/test_drawing_encapsulation.py +370 -0
  56. {draftwright-0.3.2 → draftwright-0.3.4}/tests/test_e2e_slice.py +2 -2
  57. {draftwright-0.3.2 → draftwright-0.3.4}/tests/test_e2e_standards.py +10 -7
  58. {draftwright-0.3.2 → draftwright-0.3.4}/tests/test_fits.py +1 -1
  59. {draftwright-0.3.2 → draftwright-0.3.4}/tests/test_gdt_placement.py +17 -17
  60. {draftwright-0.3.2 → draftwright-0.3.4}/tests/test_import_boundaries.py +16 -1
  61. {draftwright-0.3.2 → draftwright-0.3.4}/tests/test_layout.py +1 -34
  62. {draftwright-0.3.2 → draftwright-0.3.4}/tests/test_layout_cleanliness.py +82 -58
  63. {draftwright-0.3.2 → draftwright-0.3.4}/tests/test_lint_structural.py +64 -0
  64. {draftwright-0.3.2 → draftwright-0.3.4}/tests/test_make_drawing.py +701 -294
  65. draftwright-0.3.4/tests/test_occupancy_boxes.py +39 -0
  66. {draftwright-0.3.2 → draftwright-0.3.4}/tests/test_part_model.py +24 -0
  67. {draftwright-0.3.2 → draftwright-0.3.4}/tests/test_pmi.py +8 -8
  68. {draftwright-0.3.2 → draftwright-0.3.4}/tests/test_private_test_imports.py +10 -1
  69. {draftwright-0.3.2 → draftwright-0.3.4}/tests/test_recogniser_contract.py +18 -0
  70. {draftwright-0.3.2 → draftwright-0.3.4}/tests/test_refactor_golden.py +1 -1
  71. {draftwright-0.3.2 → draftwright-0.3.4}/tests/test_registry.py +4 -4
  72. {draftwright-0.3.2 → draftwright-0.3.4}/tests/test_render_seam.py +1 -1
  73. draftwright-0.3.4/tests/test_script_detail_parity.py +185 -0
  74. {draftwright-0.3.2 → draftwright-0.3.4}/tests/test_sheet_emit.py +148 -10
  75. {draftwright-0.3.2 → draftwright-0.3.4}/tests/test_sheet_gdt.py +5 -5
  76. {draftwright-0.3.2 → draftwright-0.3.4}/tests/test_sheet_notes.py +18 -4
  77. {draftwright-0.3.2 → draftwright-0.3.4}/tests/test_sheet_of.py +1 -1
  78. {draftwright-0.3.2 → draftwright-0.3.4}/tests/test_sheet_tables.py +34 -8
  79. draftwright-0.3.4/tests/test_solve_trace.py +275 -0
  80. {draftwright-0.3.2 → draftwright-0.3.4}/tests/test_strip_layout.py +82 -66
  81. draftwright-0.3.4/tests/test_tolerances.py +890 -0
  82. draftwright-0.3.4/tests/test_witness_label_reconciliation.py +56 -0
  83. draftwright-0.3.2/src/draftwright/_geometry.py +0 -50
  84. draftwright-0.3.2/src/draftwright/repair.py +0 -91
  85. draftwright-0.3.2/tests/test_drawing_encapsulation.py +0 -193
  86. draftwright-0.3.2/tests/test_tolerances.py +0 -234
  87. {draftwright-0.3.2 → draftwright-0.3.4}/.gitignore +0 -0
  88. {draftwright-0.3.2 → draftwright-0.3.4}/LICENSE +0 -0
  89. {draftwright-0.3.2 → draftwright-0.3.4}/skills/SKILL.md +0 -0
  90. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/annotate.py +0 -0
  91. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/annotations/__init__.py +0 -0
  92. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/export.py +0 -0
  93. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/fits.py +0 -0
  94. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/fonts/IBMPlexMono-Regular.ttf +0 -0
  95. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/fonts/IBMPlexSansCondensed-Regular.ttf +0 -0
  96. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/fonts/LICENSE-IBMPlexMono-OFL.txt +0 -0
  97. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/fonts/LICENSE-IBMPlexSansCondensed-OFL.txt +0 -0
  98. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/fonts/__init__.py +0 -0
  99. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/linting/issues.py +0 -0
  100. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/linting/suggest.py +0 -0
  101. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/make_drawing.py +0 -0
  102. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/pmi.py +0 -0
  103. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/recognition/_record.py +0 -0
  104. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/recognition/levels.py +0 -0
  105. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/recognition/plates.py +0 -0
  106. {draftwright-0.3.2 → draftwright-0.3.4}/src/draftwright/recognition/slots.py +0 -0
  107. {draftwright-0.3.2 → draftwright-0.3.4}/tests/_kernel.py +0 -0
  108. {draftwright-0.3.2 → draftwright-0.3.4}/tests/_layout_sig.py +0 -0
  109. {draftwright-0.3.2 → draftwright-0.3.4}/tests/fixtures/nist_ctc_01_asme1_ap203.stp +0 -0
  110. {draftwright-0.3.2 → draftwright-0.3.4}/tests/fixtures/nist_ctc_01_asme1_ap242.stp +0 -0
  111. {draftwright-0.3.2 → draftwright-0.3.4}/tests/fixtures/nist_ctc_02_asme1_ap203.stp +0 -0
  112. {draftwright-0.3.2 → draftwright-0.3.4}/tests/fixtures/nist_ctc_02_asme1_ap242.stp +0 -0
  113. {draftwright-0.3.2 → draftwright-0.3.4}/tests/fixtures/nist_ctc_03_asme1_ap203.stp +0 -0
  114. {draftwright-0.3.2 → draftwright-0.3.4}/tests/fixtures/nist_ctc_03_asme1_ap242.stp +0 -0
  115. {draftwright-0.3.2 → draftwright-0.3.4}/tests/fixtures/nist_ctc_04_asme1_ap203.stp +0 -0
  116. {draftwright-0.3.2 → draftwright-0.3.4}/tests/fixtures/nist_ctc_04_asme1_ap242.stp +0 -0
  117. {draftwright-0.3.2 → draftwright-0.3.4}/tests/fixtures/nist_ctc_05_asme1_ap203.stp +0 -0
  118. {draftwright-0.3.2 → draftwright-0.3.4}/tests/fixtures/nist_ctc_05_asme1_ap242.stp +0 -0
  119. {draftwright-0.3.2 → draftwright-0.3.4}/tests/refactor_golden/bracket_section.json +0 -0
  120. {draftwright-0.3.2 → draftwright-0.3.4}/tests/refactor_golden/chamfered.json +0 -0
  121. {draftwright-0.3.2 → draftwright-0.3.4}/tests/refactor_golden/filleted.json +0 -0
  122. {draftwright-0.3.2 → draftwright-0.3.4}/tests/refactor_golden/flange_dense.json +0 -0
  123. {draftwright-0.3.2 → draftwright-0.3.4}/tests/refactor_golden/grooved_shaft.json +0 -0
  124. {draftwright-0.3.2 → draftwright-0.3.4}/tests/refactor_golden/holed_slot.json +0 -0
  125. {draftwright-0.3.2 → draftwright-0.3.4}/tests/refactor_golden/pocketed.json +0 -0
  126. {draftwright-0.3.2 → draftwright-0.3.4}/tests/refactor_golden/side_drilled.json +0 -0
  127. {draftwright-0.3.2 → draftwright-0.3.4}/tests/refactor_golden/turned_stepped.json +0 -0
  128. {draftwright-0.3.2 → draftwright-0.3.4}/tests/test_export_dxf_zoom.py +0 -0
  129. {draftwright-0.3.2 → draftwright-0.3.4}/tests/test_fillets_adjacency.py +0 -0
  130. {draftwright-0.3.2 → draftwright-0.3.4}/tests/test_layout_hypothesis.py +0 -0
  131. {draftwright-0.3.2 → draftwright-0.3.4}/tests/test_layout_property.py +0 -0
  132. {draftwright-0.3.2 → draftwright-0.3.4}/tests/test_lint_box_cache.py +0 -0
  133. {draftwright-0.3.2 → draftwright-0.3.4}/tests/test_lint_reconciliation.py +0 -0
  134. {draftwright-0.3.2 → draftwright-0.3.4}/tests/test_linting.py +0 -0
  135. {draftwright-0.3.2 → draftwright-0.3.4}/tests/test_object_aspects.py +0 -0
  136. {draftwright-0.3.2 → draftwright-0.3.4}/tests/test_pitch_dim_footprint.py +0 -0
  137. {draftwright-0.3.2 → draftwright-0.3.4}/tests/test_recognition.py +0 -0
  138. {draftwright-0.3.2 → draftwright-0.3.4}/tests/test_score.py +0 -0
  139. {draftwright-0.3.2 → draftwright-0.3.4}/tests/test_turned_steps.py +0 -0
@@ -1,5 +1,307 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ## v0.3.4 — 2026-07-20
6
+
7
+ ### Changed
8
+
9
+ - **Imperative `--script` output defaults to PDF and the modern export form**
10
+ (#709). **Breaking** for regenerated imperative scripts: without `-f`, a
11
+ regenerated script now writes `<stem>.pdf` (was SVG+DXF via the deprecated
12
+ legacy tuple), and the script-visible variables changed from
13
+ `svg_path, dxf_path` to a `paths` dict printed in the requested-format order.
14
+ This aligns the imperative flavour with the Sheet flavour and the direct CLI,
15
+ which have defaulted to PDF since #702/#288. Migration: regenerate with
16
+ `--format svg,dxf` to keep the old outputs. Previously-generated scripts are
17
+ unaffected (they carry their own export lines).
18
+ - **`finalize()` drains in the auto-pass's order** (#699 slice b): both build
19
+ paths now execute the orchestrator's one canonical `_PASS_SEQUENCE` (via the
20
+ shared `run_stages`), removing the hand-mirrored second orchestration in
21
+ `Drawing._drain_intents`. For the deferred/finalize path this reorders three
22
+ things toward auto-pass parity: the turned diameter/step-length set-solves now
23
+ place *before* the corridor drain (matching the auto-pass's obstacle
24
+ visibility); slots register after them; and the register-only height-ladder /
25
+ step-position stages register *after* locations — observable too, since
26
+ registration order decides corridor key-creation (= drain) order and
27
+ same-priority tie-breaks (Codex review; the new order is exactly the
28
+ auto-pass's). Recompose output may shift accordingly; the auto-pass order is
29
+ unchanged.
30
+ - **`Sheet.export` speaks the modern export API** (#702): it now takes
31
+ `formats=` (any of svg/dxf/pdf/png, default **PDF** — matching the CLI
32
+ default) and `dpi=`, and returns the `{format: path}` dict. Previously it
33
+ rode the deprecated legacy path, could only write SVG+DXF — the flagship
34
+ facade couldn't produce a PDF — and returned the old `(svg_path, dxf_path)`
35
+ tuple. **Breaking** for callers unpacking that tuple: use
36
+ `sheet.export(stem, formats=("svg", "dxf"))` and read the dict.
37
+
38
+ ### Added
39
+
40
+ - **Solve-trace / explain mode** (#736, from the #733 post-mortem): opt-in
41
+ observability for strip placement. `build_drawing(trace=…)` or
42
+ `DRAFTWRIGHT_TRACE=<path-or-dir>` writes ONE JSON file per build (schema v2)
43
+ with two record types: `solves` — every corridor solve's key, strip bounds,
44
+ candidate set, the obstacles that carved the strip (with owning annotation
45
+ names), the free segments, and each candidate's outcome (placed /
46
+ dropped-with-reason / deduped / promoted / deferred-to-post-drain) — and
47
+ `pass_events` — the standalone strip passes plus the *immediate* placers
48
+ (post-drain machined-feature leader callouts, the turned diameter row/column
49
+ and step-length set-solves), each with per-item placed/dropped outcomes. So
50
+ "why did X drop" is one `jq` query (`.solves[].outcomes[]` /
51
+ `.pass_events[].items[]`). A `placement_unsatisfiable` strip-full drop now
52
+ also names the top occupants in its lint message (`…strip full; occupied
53
+ by: …`). Recording-only: an unwritable path logs a warning (never aborts a
54
+ build), writes are atomic, and the recorder joins `finalize()`'s #647
55
+ rollback. Default off; zero output change, nil cost when off.
56
+
57
+ - **`draftwright.model.authored_dimension`** (#704): the IR constructor behind
58
+ `Sheet.dimension()`, extracted so `build_drawing(model=…)` callers can author
59
+ a pre-measured drafting dimension without the `Sheet` façade.
60
+
61
+ ### Added
62
+
63
+ - **Object-reference script workflow: docs + an inline tip** (#770/#771): a
64
+ generated `Sheet` script from a **live-source** input (a `module:attr` /
65
+ `file.py:attr` object spec, or a build123d object) now carries an inline tip
66
+ showing how to swap each detected-numbers line for a reference to your object
67
+ (ADR 0011 declare — the size is read off the object). A STEP-sourced script is
68
+ unchanged (byte-stable — no object to reference). The README documents the full
69
+ from-a-part-to-an-object-referenced-script workflow.
70
+ - **Knurl callout verb** (#765): `sheet.diameter(shaft).knurl("0.8")` →
71
+ `KNURL 0.8 STRAIGHT` (or `.knurl("0.8", "DIAMOND")`). Named sugar over
72
+ `.note()` with canonical formatting — a knurl is a text callout on a leader,
73
+ not modelled geometry, so it flows through the existing note path (no new
74
+ IR/render).
75
+ - **Standing ISO 7200 title-block fields** (#766): `material`, `date`, `revision`,
76
+ and `company` (legal owner) are now first-class — settable on `build_drawing` /
77
+ `make_drawing`, the `Sheet(...)` constructor, and the CLI (`--material` /
78
+ `--date` / `--revision` / `--company`), and emitted into the generated `Sheet`
79
+ script when non-default so a re-run reproduces them. Defaults preserve the prior
80
+ output (revision `A`, the rest blank). The `revision`/`legal_owner` cells the
81
+ renderer previously hardcoded now flow from these. (The imperative `--script`
82
+ flavour's title-block round-trip of these fields is a follow-up.)
83
+ - **First-class thread/tap callout on a hole** (#764): ``sheet.hole(...).thread("M3x0.5")``
84
+ (and ``declare.hole(..., thread=)``) folds a tap/thread spec onto the hole's existing
85
+ compound leader (e.g. ``ø2.5 THRU M3x0.5``) — a structured aspect that round-trips, so
86
+ ``.thread(...).finish(...)`` yields Ra-on-thread. A declaration-only aspect (ADR 0011
87
+ side-layer: threads are cosmetic, not modelled geometry — no recogniser). The layout
88
+ width estimator (``_est_planned_bore_callout_width``) now accounts for the thread text so
89
+ the wider callout is reserved room and not dropped (the #261 estimator/render agreement).
90
+
91
+ ### Fixed
92
+
93
+ - **Generated imperative script reconstructs side-drilled hole locations**
94
+ (#426/#133): an X/Y-axis (side-drilled) bore gets `dim_loc_*` position dims in
95
+ the direct build, but the `--script` emitter emitted a gap comment instead of a
96
+ `locate()` for non-Z holes (because `locate()`/`render_locations` are Z-plan
97
+ only, #133). The emitter now emits `dwg.locate(f)` for holes on any axis;
98
+ `finalize()` routes by the feature's axis — Z-plan holes drain through
99
+ `render_locations` as before, side-drilled bores through the whole-model
100
+ `_locate_off_axis_holes` pass (registered at the auto-pass's own
101
+ `off_axis_across`/`off_axis_along` stages, placed at the shared drain). The
102
+ Z-only `locate()` live contract is untouched (side-drilled locates never reach
103
+ it — they route to the off-axis stages). Closes the last direct-vs-generated
104
+ script parity gap tracked by #426.
105
+ - **Generated imperative script reconstructs rotational furniture** (#426/#424):
106
+ a turned/cylindrical part's `dwg.model()` carries a `RotationalFeature`, but the
107
+ `--script` emitter parked it in the gap-comment list, so an executed
108
+ reconstruction of e.g. a plain cylinder was missing `dim_od` and the
109
+ `centerline_front`/`centerline_side` marks the direct build draws. A new
110
+ `Drawing.rotational(feature)` add-verb records the intent, and `finalize()`
111
+ drains it through the shared whole-model `render_rotational` at the auto-pass's
112
+ own `"rotational"` stage — byte-identical to the direct build (no `only=` subset,
113
+ fixed literal names, so no naming-seam or `⊇`-vs-`==` gap). The emitter now emits
114
+ `dwg.rotational(f)` for rotational features. Closes the rotational half of the
115
+ direct-vs-generated-script convergence.
116
+ - **Emitted `Sheet` script now faithfully reproduces the direct CLI drawing**
117
+ (#707): the divergence reported against 0.3.3 on the Maquetto GRM-03 part was
118
+ closed by #709 (script `--format` forwarding) + #661 (finalize detail drain).
119
+ Locked with a vendored-fixture regression that asserts the full invariant the
120
+ issue named — same views, annotation inventory, page, scale **and** lint
121
+ between `build_drawing(...)` and the executed emitted `Sheet` — not just the
122
+ annotation signature the synthetic parity cases check.
123
+ - **`_largest_empty_rect` no longer blows up on crowded detail views** (#661):
124
+ the largest-empty-rectangle placer enumerated every candidate rectangle
125
+ (O(N⁴) in the coordinate-cut count) — fine for the iso view's handful of
126
+ obstacles, but the detail-view placer feeds it *every* placed-annotation
127
+ footprint, so a two-detail turned part reached ~70 obstacles and spent ~13 s
128
+ in a single call (multi-minute on the slower Windows CI runners, which tipped
129
+ some jobs over their wall-clock budget). It now skips candidates that provably
130
+ can't beat the best found so far (a `bisect` past every too-narrow pair, an
131
+ early `break` once even the widest is too small): the same exact result and
132
+ tie-break — verified identical to the naïve search across 3 000 random cases —
133
+ at ~85× the speed on the pathological input (12.6 s → 0.15 s).
134
+ - **Detail-view height demotion is pin- and provenance-safe** (#661, user
135
+ review): when a crowded prismatic detail needs the overall-height dim's room it
136
+ is demoted and the detail retried, but (a) the canonical `dim_height` name was
137
+ handed to that retry unconditionally — a *pinned* or user-replaced height could
138
+ be removed; it now passes the same pin/label guards as the generalised
139
+ `dim_length{n}` path (a pin, ADR 0012, is never demoted), and (b) if the retry
140
+ also failed, the restore re-added only object/name/view, orphaning the dim from
141
+ its envelope feature (lost `annotations_of`/`drop`/re-discovery); it now
142
+ snapshots and restores the feature provenance and pin too.
143
+ - **`finalize()` resolves queued detail requests — detail views now exist on the
144
+ edit path** (#661): the finalize drain was missing the auto pass's
145
+ `detail_request`/`details` stages, so a crowded turned head's queued
146
+ `DetailRequest` died with the per-run context and a prismatic
147
+ `detail_view=True` build never even queued one — generated scripts silently
148
+ lost every DETAIL A/B view. `build_drawing(detail_view=…)` is now persisted on
149
+ the build state so the drain gates the prismatic request exactly as the auto
150
+ pass does; the details stage re-projects the iso at sheet scale for the
151
+ free-rectangle search and refits it after (mirroring the auto pass's ordering,
152
+ where details place before the iso is fitted); and the overall-height demotion
153
+ retry now finds the height dim by its envelope attribution, not only the
154
+ auto-pass `dim_height` name. Three script-parity characterisation tests
155
+ (single detail, two details, turned head) flip from xfail to passing.
156
+ - **Y-turned stepped shafts no longer crash** (#661): `render_step_lengths`
157
+ projected every step span into the front view, where a Y-axis span is end-on
158
+ (a point) — `Dimension` then raised "start and end points must be different"
159
+ on BOTH the direct build and a generated script's finalize. Y-axis steps are
160
+ now skipped (no step render pipeline consumes Y, #731 — mirroring
161
+ `render_diameters`' x/z bucketing), the emitter flags the gap as a comment
162
+ instead of emitting an unreplayable `dimension()` verb, and a hand-written
163
+ Y step-length intent live-replays (surfacing the verb's own behaviour) rather
164
+ than vanishing into the routed no-op. The Y-axis script-parity
165
+ characterisation test flips from xfail to passing.
166
+ - **Boss heights are modeled, rendered, and coverage-checked** (#632): detected
167
+ and object-declared cylindrical bosses now carry their axial extent through
168
+ `BossFeature` and the dimension planner. Prismatic bosses receive a linear
169
+ height dimension in a profile view in addition to their end-on diameter
170
+ leader. `Drawing.lint()` reconciles that modeled height against the live
171
+ feature-owned `Dimension`, reporting `boss_height_missing` if it is removed
172
+ or otherwise absent (demoted to `info` on assembly drawings). This closes the
173
+ declarative false-negative where a boss's diameter and the overall envelope
174
+ were documented but its own independent height was not.
175
+
176
+ - **`--format` now reaches `--script` output** (#709, from the #702 adversarial
177
+ review): `--script -f svg` used to silently emit a PDF-producing script — the
178
+ CLI parsed the flag but never forwarded it. Both emitters now thread it
179
+ through: the Sheet script emits `sheet.export(stem, formats=(…,))` when
180
+ non-default (bare call for the PDF default), the imperative template embeds
181
+ the requested tuple and prints in the **requested** order, and
182
+ `generate_script` / `generate_sheet_script` grow a `formats=` parameter. The
183
+ dormant script-parity characterisation module
184
+ (`test_script_detail_parity.py`) is also un-skipped so its strict xfails give
185
+ the remaining gaps CI signal.
186
+
187
+ - **Authored fillet, flat, groove, pocket, plate and slot tolerances now render**
188
+ (#725/#726/#727/#728/#729/#730 / #698): the four leader-callout kinds join
189
+ `_CONVENTION` and their renderers (`render_fillets`/`render_flats`/
190
+ `render_grooves`/`render_pockets`) — and the plate-thickness and slot
191
+ width/length linear passes
192
+ (`render_plates`/`render_slots`, explicit `"linear"` entries) — consume the
193
+ planner's `DimensionGroup`s, binding each
194
+ planned dim explicitly by `(role, kind)` — so a `decorations`-authored ± /
195
+ limit tolerance reaches the placed callout (`R8 ±0.1`, `17 ±0.2 A/F`,
196
+ `4 ±0.1 WIDE × ø16 ±0.5`, `18 ±0.2 × 30 ±0.2 × 5 ±0.2 DEEP`, a `10 ±0.1`
197
+ thickness dim, an `8 ±0.1` slot width; on a
198
+ multi-value label each suffix rides its own number). Previously all six
199
+ passes formatted raw feature fields and silently dropped it — the latent #629
200
+ bug class the ADR 0015 bypass list documents. The fillet `n× R` and flat
201
+ double-D/hex collapses stay render-side (first-authored tolerance wins, the
202
+ `render_diameters` precedent); a pocket's three values — and a slot's
203
+ width + length — share one authored
204
+ tolerance (all kind `"length"`). The slot's model-derived datum position dim
205
+ and its corridor/immediate placement mechanics are untouched. Untolerated
206
+ labels/views/placement are unchanged.
207
+ - **Authored chamfer tolerances now render** (#724 / #698): `render_chamfers`
208
+ consumes the planner's `DimensionGroup` (chamfer joins `_CONVENTION` as a
209
+ leader), so a `decorations`-authored ± / limit tolerance on a chamfer leg
210
+ reaches the placed callout (`C12 ±0.2`). Previously the pass formatted raw
211
+ feature fields and silently dropped it — the latent #629 bug class the ADR
212
+ 0015 bypass list documents. Untolerated chamfer labels/views are unchanged.
213
+ - **`model.chamfer(face=…)` rejects a non-planar face** (#704): the declared
214
+ front-end silently accepted a curved face (`normal_at()` does not fail on
215
+ one) and read garbage legs off it — e.g. a fillet or countersink face became
216
+ a bogus `ChamferFeature`. It now raises the documented `ValueError`, using
217
+ the recogniser's own planarity gate (`classify_bevel`), so declared and
218
+ detected chamfers classify identically by construction.
219
+
220
+ ### Fixed
221
+
222
+ - **CTC-02/04 no longer drop step-height / overall-height dims** (#733): when
223
+ the height ladder joined the corridor solve (#689) it silently moved from
224
+ "places early" to "registers early, places at the drain" — so the immediate
225
+ machined-feature leader callouts (pockets on CTC-04) filled the front-right
226
+ strip first and the FORCED principal dims hard-dropped
227
+ (`placement_unsatisfiable`), a priority inversion the old stage ordering had
228
+ prevented implicitly. The leader-callout stages (chamfers/fillets/flats/
229
+ pockets) now sit **after the corridor drain** in `_PASS_SEQUENCE` (joining
230
+ grooves, which already placed post-drain for exactly this reason): principal
231
+ dims win by construction, and a callout with no clear room yields with a
232
+ warning, never a principal-dim error. Callout leaders may shift (they now
233
+ route around the drained dims); on the `hex_bar` golden this *recovers* a
234
+ silently starved `m_env_width` envelope dim.
235
+
236
+ ### Removed
237
+
238
+ - **Three orphaned root exports** (#704): `draftwright.recognise_face_levels`
239
+ (one recogniser of ~12 — incoherent as a top-level surface; import it from
240
+ `draftwright.recognition`), `draftwright.dedup_diams` (an analysis internal),
241
+ and `draftwright.fix_svg_page_size` (still available from `draftwright.export`
242
+ and the `draftwright.make_drawing` facade). None had any internal or test
243
+ consumer via the root API.
244
+
245
+ ### Deprecated
246
+
247
+ - **The seven `Drawing` compat aliases** (`_named`/`_anno_view`/`_pinned`/
248
+ `_build_issues`/`_pattern_callouts`/`_patterned_holes`/
249
+ `_dropped_callout_diams`) are formally on notice (#699 slice c, tracked by
250
+ #720): removal target **0.4.0**, per ADR 0005 §4. Read through the public
251
+ surface instead — `annotations()`, `iter_annotations()`, `get_annotation()`,
252
+ `view_of()`, `registry.pinned_names()`/`is_pinned()`, and the new
253
+ `registry.issues`. All external production and test call sites in this repo
254
+ are already redirected (`drawing.py` internals ride the aliases until the
255
+ #720 deletion).
256
+ - **`draftwright.sheet_dsl`** (belated announcement — the alias shim shipped
257
+ in 0.3.1 when #640 renamed the module to `draftwright.sheet`): importing it
258
+ warns; it will be removed in **0.4.0**. Import from `draftwright.sheet`, or
259
+ just `from draftwright import Sheet`.
260
+
261
+ ## v0.3.3 — 2026-07-18
262
+
263
+ The **layout-quality release**: the occupancy model gets honest about ink shape,
264
+ the last solver-invisible pass joins the corridor solve, and crossed labels shift
265
+ themselves clear. Also completes the #635 consolidation epic (ADR 0005 fully
266
+ executed; ADR 0009's no-invisible-occupant guarantee now holds by construction
267
+ for every auto-pass).
268
+
269
+ ### Added
270
+
271
+ - **Witness-crossing label shifts** (#690): a dimension label crossed by another
272
+ dimension's witness stroke now shifts along its own line to clear it — a
273
+ deterministic post-drain pass using the repair machinery (destination-aware,
274
+ axis-aligned only, pinned dims exempt).
275
+
276
+ ### Changed
277
+
278
+ - **L-shaped occupancy** (#685): placement obstacles decompose into per-stroke
279
+ boxes + label box (helpers ≥0.14 `segments`) instead of one hull, with
280
+ preset-aware arrowhead pads. View-corner contention between perpendicular
281
+ strips disappears by construction (a plate-thickness dim returns to its
282
+ natural strip); packing tightens. The cleanliness ratchet mirrors the model —
283
+ only transverse stroke crossings are exempt, so parallel overprints and label
284
+ contacts are detected honestly (twelve hull-artifact allowlist entries burned
285
+ down; the legitimate shared-datum running-dimension pairs are documented).
286
+ - **Height ladder joins the corridor solve** (#636): step heights + overall
287
+ height are corridor candidates (the leapfrog witness cursor survives as a
288
+ deterministic build-time chain; the overall height stacks outermost by
289
+ construction and packs tighter). An explicitly-requested crowded-step detail
290
+ view now *deliberately* demotes the overall-height dim when the sheet cannot
291
+ hold both (previously the carve dropped it silently); a Y-drilled hole's Z
292
+ location joins the front running ladder per the documented routing.
293
+ - **One typed `BuildState`** (#639): the drawing's build context (analysis,
294
+ part model, lint geometry caches) consolidates into a single object filled at
295
+ one builder site, single-writer-guarded; the render passes make zero private
296
+ `Drawing` reads (fail-closed empty allowlist).
297
+
298
+ ### Fixed
299
+
300
+ - A prismatic detail view no longer loses both the detail *and* the height dim
301
+ when space is tight (transactional demotion).
302
+ - The GD&T alternate-strip fallback defers until every corridor has drained, so
303
+ it can never preempt a sibling strip's reserved corner.
304
+
3
305
  ## v0.3.2 — 2026-07-18
4
306
 
5
307
  The **performance release**: the #602 perf loop (six PRs) plus the
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: draftwright
3
- Version: 0.3.2
3
+ Version: 0.3.4
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
@@ -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.2"
7
+ version = "0.3.4"
8
8
  description = "Automated technical-drawing generation for build123d"
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
@@ -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",