draftwright 0.3.9__tar.gz → 0.4.0__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 (166) hide show
  1. {draftwright-0.3.9 → draftwright-0.4.0}/CHANGELOG.md +156 -0
  2. {draftwright-0.3.9 → draftwright-0.4.0}/PKG-INFO +15 -1
  3. {draftwright-0.3.9 → draftwright-0.4.0}/README.md +14 -0
  4. {draftwright-0.3.9 → draftwright-0.4.0}/docs/adr/README.md +2 -0
  5. {draftwright-0.3.9 → draftwright-0.4.0}/pyproject.toml +4 -1
  6. {draftwright-0.3.9 → draftwright-0.4.0}/skills/SKILL.md +99 -43
  7. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/__init__.py +1 -3
  8. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/_core.py +134 -11
  9. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/_geometry.py +57 -1
  10. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/analysis.py +13 -20
  11. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/annotate.py +1 -1
  12. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/annotations/_common.py +75 -12
  13. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/annotations/balloons.py +125 -25
  14. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/annotations/from_model.py +1264 -615
  15. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/annotations/holes.py +371 -124
  16. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/annotations/orchestrator.py +52 -25
  17. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/annotations/sections.py +137 -32
  18. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/builder.py +154 -427
  19. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/cli.py +36 -63
  20. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/compose.py +23 -47
  21. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/drawing.py +424 -153
  22. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/layout.py +81 -6
  23. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/linting/__init__.py +2 -0
  24. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/linting/coverage.py +283 -18
  25. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/make_drawing.py +1 -2
  26. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/model/__init__.py +16 -2
  27. draftwright-0.4.0/src/draftwright/model/callout.py +335 -0
  28. draftwright-0.4.0/src/draftwright/model/compiled.py +1060 -0
  29. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/model/declare.py +148 -19
  30. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/model/detect.py +82 -25
  31. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/model/ir.py +214 -6
  32. draftwright-0.4.0/src/draftwright/model/planner.py +750 -0
  33. draftwright-0.4.0/src/draftwright/py.typed +0 -0
  34. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/recognition/__init__.py +4 -1
  35. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/recognition/_features.py +89 -24
  36. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/recognition/levels.py +65 -20
  37. draftwright-0.4.0/src/draftwright/recognition/pads.py +137 -0
  38. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/recognition/slots.py +99 -2
  39. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/registry.py +11 -3
  40. draftwright-0.4.0/src/draftwright/sheet.py +1764 -0
  41. draftwright-0.4.0/src/draftwright/sheet_emit.py +1231 -0
  42. {draftwright-0.3.9 → draftwright-0.4.0}/tests/refactor_golden/pocketed.json +37 -1
  43. draftwright-0.4.0/tests/test_add_dimension.py +1332 -0
  44. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_agents_guide.py +2 -2
  45. draftwright-0.4.0/tests/test_balloon_ring_standoff.py +78 -0
  46. draftwright-0.4.0/tests/test_compiled_plan_boundary.py +1192 -0
  47. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_declare.py +438 -63
  48. draftwright-0.4.0/tests/test_deprecation_dates.py +153 -0
  49. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_detect_once.py +3 -3
  50. draftwright-0.4.0/tests/test_dimension_role_vocabulary.py +378 -0
  51. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_drawing_encapsulation.py +77 -3
  52. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_e2e_slice.py +3 -1
  53. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_e2e_standards.py +70 -16
  54. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_fits.py +3 -3
  55. draftwright-0.4.0/tests/test_grid_lattice_convention.py +327 -0
  56. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_import_boundaries.py +0 -1
  57. draftwright-0.4.0/tests/test_issue_885_prismatic_coverage.py +126 -0
  58. draftwright-0.4.0/tests/test_label_provenance.py +206 -0
  59. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_layout.py +80 -0
  60. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_lint_reconciliation.py +3 -3
  61. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_make_drawing.py +937 -293
  62. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_object_aspects.py +6 -6
  63. draftwright-0.4.0/tests/test_parameter_id.py +510 -0
  64. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_pmi.py +3 -1
  65. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_pocket_pattern.py +8 -7
  66. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_pocket_pattern_recognition.py +29 -1
  67. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_private_test_attr_reads.py +0 -7
  68. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_render_seam.py +36 -0
  69. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_script_detail_parity.py +79 -50
  70. draftwright-0.4.0/tests/test_sheet_emit.py +3361 -0
  71. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_sheet_gdt.py +12 -12
  72. draftwright-0.4.0/tests/test_sheet_identity_invariant.py +805 -0
  73. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_sheet_notes.py +14 -14
  74. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_sheet_of.py +3 -3
  75. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_sheet_section.py +1 -1
  76. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_sheet_tables.py +3 -1
  77. draftwright-0.4.0/tests/test_slanted_blind_step.py +283 -0
  78. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_slot_pattern.py +10 -8
  79. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_solve_trace.py +5 -2
  80. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_strip_layout.py +64 -0
  81. draftwright-0.4.0/tests/test_suppression_marks.py +553 -0
  82. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_tolerances.py +64 -40
  83. draftwright-0.3.9/src/draftwright/model/planner.py +0 -320
  84. draftwright-0.3.9/src/draftwright/sheet.py +0 -1016
  85. draftwright-0.3.9/src/draftwright/sheet_dsl.py +0 -35
  86. draftwright-0.3.9/src/draftwright/sheet_emit.py +0 -766
  87. draftwright-0.3.9/tests/test_sheet_emit.py +0 -1280
  88. {draftwright-0.3.9 → draftwright-0.4.0}/.gitignore +0 -0
  89. {draftwright-0.3.9 → draftwright-0.4.0}/LICENSE +0 -0
  90. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/annotations/__init__.py +0 -0
  91. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/export.py +0 -0
  92. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/fits.py +0 -0
  93. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/fonts/IBMPlexMono-Regular.ttf +0 -0
  94. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/fonts/IBMPlexSansCondensed-Regular.ttf +0 -0
  95. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/fonts/LICENSE-IBMPlexMono-OFL.txt +0 -0
  96. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/fonts/LICENSE-IBMPlexSansCondensed-OFL.txt +0 -0
  97. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/fonts/__init__.py +0 -0
  98. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/intents.py +0 -0
  99. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/linting/issues.py +0 -0
  100. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/linting/structural.py +0 -0
  101. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/linting/suggest.py +0 -0
  102. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/pmi.py +0 -0
  103. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/projection.py +0 -0
  104. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/recognition/_record.py +0 -0
  105. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/recognition/chamfers.py +0 -0
  106. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/recognition/countersinks.py +0 -0
  107. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/recognition/fillets.py +0 -0
  108. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/recognition/flats.py +0 -0
  109. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/recognition/grooves.py +0 -0
  110. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/recognition/plates.py +0 -0
  111. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/recognition/turned.py +0 -0
  112. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/repair.py +0 -0
  113. {draftwright-0.3.9 → draftwright-0.4.0}/src/draftwright/score.py +0 -0
  114. {draftwright-0.3.9 → draftwright-0.4.0}/tests/_kernel.py +0 -0
  115. {draftwright-0.3.9 → draftwright-0.4.0}/tests/_layout_sig.py +0 -0
  116. {draftwright-0.3.9 → draftwright-0.4.0}/tests/fixtures/grm03_thumbwheel_drive_screw.step +0 -0
  117. {draftwright-0.3.9 → draftwright-0.4.0}/tests/fixtures/nist_ctc_01_asme1_ap203.stp +0 -0
  118. {draftwright-0.3.9 → draftwright-0.4.0}/tests/fixtures/nist_ctc_01_asme1_ap242.stp +0 -0
  119. {draftwright-0.3.9 → draftwright-0.4.0}/tests/fixtures/nist_ctc_02_asme1_ap203.stp +0 -0
  120. {draftwright-0.3.9 → draftwright-0.4.0}/tests/fixtures/nist_ctc_02_asme1_ap242.stp +0 -0
  121. {draftwright-0.3.9 → draftwright-0.4.0}/tests/fixtures/nist_ctc_03_asme1_ap203.stp +0 -0
  122. {draftwright-0.3.9 → draftwright-0.4.0}/tests/fixtures/nist_ctc_03_asme1_ap242.stp +0 -0
  123. {draftwright-0.3.9 → draftwright-0.4.0}/tests/fixtures/nist_ctc_04_asme1_ap203.stp +0 -0
  124. {draftwright-0.3.9 → draftwright-0.4.0}/tests/fixtures/nist_ctc_04_asme1_ap242.stp +0 -0
  125. {draftwright-0.3.9 → draftwright-0.4.0}/tests/fixtures/nist_ctc_05_asme1_ap203.stp +0 -0
  126. {draftwright-0.3.9 → draftwright-0.4.0}/tests/fixtures/nist_ctc_05_asme1_ap242.stp +0 -0
  127. {draftwright-0.3.9 → draftwright-0.4.0}/tests/fixtures/tuner_jig_blind_obround_pockets.step +0 -0
  128. {draftwright-0.3.9 → draftwright-0.4.0}/tests/refactor_golden/bracket_section.json +0 -0
  129. {draftwright-0.3.9 → draftwright-0.4.0}/tests/refactor_golden/centered_rebate.json +0 -0
  130. {draftwright-0.3.9 → draftwright-0.4.0}/tests/refactor_golden/chamfered.json +0 -0
  131. {draftwright-0.3.9 → draftwright-0.4.0}/tests/refactor_golden/filleted.json +0 -0
  132. {draftwright-0.3.9 → draftwright-0.4.0}/tests/refactor_golden/flange_dense.json +0 -0
  133. {draftwright-0.3.9 → draftwright-0.4.0}/tests/refactor_golden/grid_plate.json +0 -0
  134. {draftwright-0.3.9 → draftwright-0.4.0}/tests/refactor_golden/grooved_shaft.json +0 -0
  135. {draftwright-0.3.9 → draftwright-0.4.0}/tests/refactor_golden/hex_bar.json +0 -0
  136. {draftwright-0.3.9 → draftwright-0.4.0}/tests/refactor_golden/holed_slot.json +0 -0
  137. {draftwright-0.3.9 → draftwright-0.4.0}/tests/refactor_golden/prismatic_ladder.json +0 -0
  138. {draftwright-0.3.9 → draftwright-0.4.0}/tests/refactor_golden/scattered_plate.json +0 -0
  139. {draftwright-0.3.9 → draftwright-0.4.0}/tests/refactor_golden/side_drilled.json +0 -0
  140. {draftwright-0.3.9 → draftwright-0.4.0}/tests/refactor_golden/turned_stepped.json +0 -0
  141. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_architecture_docs.py +0 -0
  142. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_carve_free_position_callers.py +0 -0
  143. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_dense_sheet_canary.py +0 -0
  144. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_detect_registry.py +0 -0
  145. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_export_dxf_zoom.py +0 -0
  146. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_fillets_adjacency.py +0 -0
  147. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_gdt_placement.py +0 -0
  148. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_layout_cleanliness.py +0 -0
  149. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_layout_hypothesis.py +0 -0
  150. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_layout_property.py +0 -0
  151. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_lint_box_cache.py +0 -0
  152. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_lint_structural.py +0 -0
  153. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_linting.py +0 -0
  154. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_note_verb.py +0 -0
  155. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_occupancy_boxes.py +0 -0
  156. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_part_model.py +0 -0
  157. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_pitch_dim_footprint.py +0 -0
  158. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_private_test_imports.py +0 -0
  159. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_recogniser_contract.py +0 -0
  160. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_recognition.py +0 -0
  161. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_refactor_golden.py +0 -0
  162. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_registry.py +0 -0
  163. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_score.py +0 -0
  164. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_slot_pattern_recognition.py +0 -0
  165. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_turned_steps.py +0 -0
  166. {draftwright-0.3.9 → draftwright-0.4.0}/tests/test_witness_label_reconciliation.py +0 -0
@@ -1,5 +1,161 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ## v0.4.0 — 2026-08-02
6
+
7
+ **The compat-exit release.** Every surface whose documented removal target was 0.4.0 is gone,
8
+ and every deprecation that remains now says when it goes — in the message the caller sees, not
9
+ only in a release note. ADR 0005 §4's rule is that an alias carries a tracking issue *and* a
10
+ removal date, because "a facade with no exit date is a failure mode"; this release is that rule
11
+ being kept rather than restated. `docs/deprecations.md` is the new index, and
12
+ `tests/test_deprecation_dates.py` fails any deprecation that names no removal.
13
+
14
+ Also lands ADR 0016's declared-dimensioning work (#867): a `dimension(feature, role)` is
15
+ referential — it names a measurement and carries no number — and an authored set means omission
16
+ is suppression, enforced at the compiled-plan boundary rather than by a flag renderers check.
17
+
18
+ **Read this before upgrading:** two removals break *without a release that warned you*, because
19
+ their deprecation never appeared in a shipped version. See *Removed (breaking)* below and
20
+ `docs/deprecations.md`.
21
+
22
+ ### Removed (breaking)
23
+
24
+ Compat surfaces whose documented removal target was 0.4.0 (#720, ADR 0005 §4 — every alias
25
+ carries a tracking issue *and* a removal date, or the facade is permanent by accident):
26
+
27
+ - **The seven `Drawing` compat aliases** — `_named` / `_anno_view` / `_pinned` /
28
+ `_build_issues`, and `_pattern_callouts` / `_patterned_holes` / `_dropped_callout_diams`.
29
+ These were private, and the public reads have existed since #699: use `dwg.registry`
30
+ (`in reg`, `names()`, `issues`, `pinned_names()`) and `dwg.coverage`, or the `Drawing` verbs
31
+ `annotations()` / `iter_annotations()` / `get_annotation()` / `view_of()`.
32
+ **Note the asymmetry.** A *read* — `dwg._named` — now raises `AttributeError`. A *write* —
33
+ `dwg._pinned = {...}` — does **not**: these were properties with setters, and `Drawing` has
34
+ no `__slots__`, so assignment now quietly creates an unrelated instance attribute that no
35
+ longer reaches the registry or coverage owner. Writes therefore fail silently rather than
36
+ loudly. Grep for `\._(named|anno_view|pinned|build_issues|pattern_callouts|patterned_holes|dropped_callout_diams)\b`
37
+ before upgrading; mutate through `registry.add()` / `pin()` / `record_issue()` /
38
+ `restore_issues()` and the `CoverageState` methods instead.
39
+ - **The `draftwright.sheet_dsl` module** — an import alias for `draftwright.sheet` since the
40
+ #640 rename. Import `Sheet` from `draftwright` or `draftwright.sheet`.
41
+ - **`generate_script`**, including its `draftwright.__all__` entry. It has raised since #940
42
+ retired the imperative emitter; it is now simply absent, so the failure is an `ImportError`
43
+ at the top of a script rather than a `RuntimeError` part-way through one. Use
44
+ `--script` / `emit_sheet_script`.
45
+ - **The bespoke `--style imperative` error message.** `--style` itself is unchanged and still
46
+ accepts its sole value `sheet`; `imperative` is now an unrecognised value like any typo,
47
+ rather than one carrying its own explanation of the #940 retirement.
48
+
49
+ - **Bare dimension-role spellings** — `sheet.dimension(f, "width")`. Use the parameter id,
50
+ `"width.length"`; `dimension_ids()` on a handle lists the valid ones. The bare role is the
51
+ *family* spelling: it selects every parameter carrying it, which is how
52
+ `dimension(step, "step")` quietly declared two measurements. In an authored set, where
53
+ omission means suppression, silently declaring an extra one is the mirror image of the rule
54
+ — so it now raises rather than resolving. (A *discriminated* bare role, used with `axis=`,
55
+ is unaffected: that is how variants like `grid_pitch.length.row` are addressed, and it never
56
+ warned.)
57
+ - **The `Sheet.dimension(kind=…, value=…)` call shape** — use `Sheet.measured_dimension(...)`.
58
+ `dimension` is now solely the ADR 0016 referential verb: it names a feature and a parameter
59
+ id and reads the value off the geometry.
60
+
61
+ Those last two break **without a release that warns you**. They were deprecated after v0.3.9
62
+ and removed in 0.4.0, so the `DeprecationWarning` never appeared in a released version —
63
+ upgrading from v0.3.9 or earlier goes straight from working to a raise. That is deliberate
64
+ (ADR 0016); this entry and `docs/deprecations.md` are the only notice you get, so both
65
+ failures name their replacement rather than raising about argument counts.
66
+
67
+ ### Deprecated
68
+
69
+ - **The legacy `Drawing.export` shapes now emit `DeprecationWarning`** (#987) — they were
70
+ announced as deprecated in v0.3.1 and then said nothing at runtime for four minor releases,
71
+ which would have made their 0.5.0 removal a silent break. Three cases warn:
72
+ - `export()` with `formats=` **omitted or `None`** — the legacy default, which writes
73
+ SVG + DXF and returns an `(svg, dxf)` **tuple** rather than the `{format: path}` dict.
74
+ - the **`svg=` / `dxf=` booleans**. The suggested replacement names the formats *that call*
75
+ selected, so following it cannot change what gets written.
76
+ - passing a boolean **alongside** `formats=`, where it is silently ignored — `formats` wins,
77
+ and now says so.
78
+
79
+ **If you promote `DeprecationWarning` to an error, previously-passing exports will now
80
+ raise.** Migration: `export(out, formats=("svg", "dxf"))` and read the dict. `make_drawing`
81
+ is unaffected — it returns the same tuple and does not warn.
82
+
83
+ ### Added
84
+
85
+ - **`sheet.add_dimension(feature, role)` — ask the planner to carry one more
86
+ measurement** (ADR 0016 / #872). Referential: it names a feature and a role and
87
+ carries **no number**, so the value still comes from the geometry and a size lives in
88
+ exactly one place. It changes *selection*, not derivation — a request can never
89
+ introduce a number the part does not have. Requesting something the planner already
90
+ emits is a deliberate no-op, so a script can ask without first knowing the rule set's
91
+ mind. `sheet.auto_dimensions()` states the source explicitly (optional in this
92
+ release; #874 makes it mandatory).
93
+ - **Suppressing a dimension marks it; it no longer leaks into the callout** (ADR 0016 /
94
+ #875). `PlannedDimension.suppressed` was honoured at thirteen render sites but not by
95
+ the compound hole-callout path, so a suppressed counterbore still printed. The group
96
+ keeps its engineering data either way — what changes is whether a value reaches the
97
+ page. Suppressing the bore ⌀ while a counterbore, spotface or countersink segment
98
+ remains now **raises** and names the orphan: `⌀20 THRU ⌴ ⌀32 ↓ 1.5` has no reading with
99
+ its leading term removed, and silently dropping the segments would discard authored
100
+ intent while silently restoring the head would make the drawing say something the
101
+ script does not.
102
+ - **Addressable dimension identity** (#869/#870/#871): every planned measurement now has
103
+ a stable `DimensionId(feature, parameter)`, and correlated measurements that must be
104
+ named as one — a grid pattern's row and column pitch, a step ladder — group into an
105
+ `AddressableDimension`. This is what a `dimension(...)` line will name, and what
106
+ suppression and provenance key on.
107
+
108
+ ### Changed
109
+
110
+ - **`Sheet.dimension(kind=…, value=…)` is now `Sheet.measured_dimension(...)`**
111
+ (ADR 0016 / #873), and `model.declare.authored_dimension` is
112
+ `model.declare.measured_dimension` to match. This **reserves** the name `dimension` for
113
+ the referential verb ADR 0016 defines — name a feature and a role, carry no number, let
114
+ the engine read the value off the geometry — which `Drawing.dimension` already is and
115
+ `Sheet.dimension` will become. The verb that carries an explicit number needed a name
116
+ saying so first. The old spelling was a transitional overload during development and is
117
+ **removed in this same release** (#720) rather than shipping — `Sheet.dimension` is solely
118
+ the referential verb, and the old keyword call raises with `measured_dimension` named. See
119
+ *Removed (breaking)* above. Generated AP242 scripts emit `measured_dimension` and so arrive
120
+ un-deprecated.
121
+ - **`Sheet` handles address features by identity, not position** (#908/#910/#912). A
122
+ handle, tolerance, GD&T origin, section request or dimension intent now follows *its*
123
+ feature through a `features` reorder instead of naming whatever took the slot.
124
+ Reordering with `reverse()`/`sort()` preserves every reference; deleting or replacing
125
+ a referenced feature raises rather than silently retargeting a neighbour. `of()` now
126
+ accepts a handle, and negative indices resolve uniformly across `of()`,
127
+ `add_dimension()` and the GD&T verbs.
128
+
129
+ ### Fixed
130
+
131
+ - **`sheet.envelope()` measured the file, not the part** (#977). An AP242 STEP import is a
132
+ compound of the solid *plus its PMI presentation geometry* — annotation planes, leader
133
+ curves — and the declared verb measured all of it. On the NIST CTC-01 fixture it declared
134
+ 1170 × 650 where the part is 800 × 450: an envelope 370 mm too wide, silently, in anything
135
+ declared by hand. `step_level()` had the same exposure, offsetting every step position by
136
+ the annotation overhang. Both now measure the solid body, sharing the helper the engine
137
+ already used for exactly this (`_analyse` and `Sheet.model`).
138
+ - **A declared envelope's frame origin sat a half-height below a detected one** (#977). It
139
+ used `bbox_min.Z` where the detector uses the bbox centre. **Dimension output is unchanged** —
140
+ envelope sizes derive from `bbox_min`/`bbox_max`, and the overall height is compiled from the
141
+ model bbox — but `frame.origin` is the generic feature *site*, so **a GD&T control frame,
142
+ surface finish or note targeting a hand-declared `sheet.envelope()` was anchored at the
143
+ bottom face rather than the centre, and its leader now moves to the centre**. That is a
144
+ visible change on drawings carrying such an annotation; drawings without one are unaffected.
145
+ Scripts generated by `--script` were never affected either way: they bake the detected frame
146
+ explicitly rather than calling the verb.
147
+
148
+
149
+ - **Front-view dimensions join the placement solve instead of committing to the strip**
150
+ (#894): they previously took strip space before the global solve ran, so a
151
+ higher-ranked dimension could find its space already gone. Priority only ranks
152
+ candidates that are *in* the solve.
153
+ - **A hole callout reads the feature's own `through` fact** (#868) rather than inferring
154
+ it from whether a depth parameter happens to be present — so a suppressed depth can no
155
+ longer make a blind hole print `THRU`.
156
+ - **The balloon ring clears the view it annotates after a hole-table escalation**
157
+ (#901/#903).
158
+
3
159
  ## v0.3.9 — 2026-07-26
4
160
 
5
161
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: draftwright
3
- Version: 0.3.9
3
+ Version: 0.4.0
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
@@ -834,6 +834,20 @@ sheet.hole(features.m3_bore).thread("M3x0.5").finish("1.6") # tapped + Ra on t
834
834
  An object-sourced script carries an inline tip pointing at exactly this edit; a STEP-sourced
835
835
  script keeps the detected numbers (there's no object to reference).
836
836
 
837
+ Every generated script ends by naming the drawing it built, so you can critique or inspect it
838
+ before (or instead of) exporting — one build, not two:
839
+
840
+ ```python
841
+ drawing = sheet.build()
842
+
843
+ for issue in drawing.lint(): # ISO/coverage critique of THIS drawing
844
+ print(issue.severity, issue.code, issue.message)
845
+
846
+ drawing.export("part", formats=("pdf",))
847
+ ```
848
+
849
+ `sheet.export(...)` remains the one-liner for handwritten scripts that just want the file.
850
+
837
851
  See [`docs/multi-feature-object-reference-workflow.md`](docs/multi-feature-object-reference-workflow.md)
838
852
  for a complete, runnable walkthrough of this pattern on a multi-feature turned part —
839
853
  including the backward-compatible refactor, external threads (no `.thread()` on a step —
@@ -138,6 +138,20 @@ sheet.hole(features.m3_bore).thread("M3x0.5").finish("1.6") # tapped + Ra on t
138
138
  An object-sourced script carries an inline tip pointing at exactly this edit; a STEP-sourced
139
139
  script keeps the detected numbers (there's no object to reference).
140
140
 
141
+ Every generated script ends by naming the drawing it built, so you can critique or inspect it
142
+ before (or instead of) exporting — one build, not two:
143
+
144
+ ```python
145
+ drawing = sheet.build()
146
+
147
+ for issue in drawing.lint(): # ISO/coverage critique of THIS drawing
148
+ print(issue.severity, issue.code, issue.message)
149
+
150
+ drawing.export("part", formats=("pdf",))
151
+ ```
152
+
153
+ `sheet.export(...)` remains the one-liner for handwritten scripts that just want the file.
154
+
141
155
  See [`docs/multi-feature-object-reference-workflow.md`](docs/multi-feature-object-reference-workflow.md)
142
156
  for a complete, runnable walkthrough of this pattern on a multi-feature turned part —
143
157
  including the backward-compatible refactor, external threads (no `.thread()` on a step —
@@ -32,6 +32,7 @@ architecture** table; open retired or superseded records only for design history
32
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
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
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
+ | [0016](0016-declared-dimensioning-intent.md) | Declared dimensioning intent: capture what to measure, let the engine place it | Declare which measurements matter as scale-independent intent routed through the planner and corridor solve; never hardcode dimension geometry. | Accepted; epic #867 complete; phase 6 landed (#940) | `test_compiled_plan_boundary.py`, `test_label_provenance.py`, `test_sheet_emit.py`, `test_add_dimension.py` |
35
36
 
36
37
  ## Historical records
37
38
 
@@ -46,6 +47,7 @@ architecture** table; open retired or superseded records only for design history
46
47
  - Compiler and state ownership: 0001 → 0005 → 0015.
47
48
  - Recognition and public declaration: 0007 → 0013 → 0011 → 0015.
48
49
  - Layout and placement: 0004 → 0014 → 0012.
50
+ - Declared intent and the editable surface: 0001 → 0011 → 0012 → 0016.
49
51
  - Quality and correction: 0002, with provenance from 0010.
50
52
 
51
53
  Tracking issue: #745.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "draftwright"
7
- version = "0.3.9"
7
+ version = "0.4.0"
8
8
  description = "Automated technical-drawing generation for build123d"
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
@@ -101,6 +101,9 @@ precision = 1
101
101
  exclude_lines = [
102
102
  "pragma: no cover",
103
103
  "if TYPE_CHECKING:",
104
+ # `@overload` stubs are typing declarations: Python discards the bodies, so they can
105
+ # never execute and counting them as missed coverage measures nothing (#963).
106
+ "@overload",
104
107
  ]
105
108
 
106
109
  [tool.mypy]
@@ -17,9 +17,12 @@ to be installed in the execution environment.
17
17
  2. **Builder (`build_drawing`)** — the same pipeline, but it hands back a live
18
18
  `Drawing` you can edit before export.
19
19
 
20
- Requires `draftwright >= 0.1.9` and `build123d-drafting-helpers >= 0.10.1`.
20
+ Requires `draftwright >= 0.4.0` and `build123d-drafting-helpers >= 0.14.1`.
21
21
  Install: `pip install draftwright`.
22
22
 
23
+ (The old floor said 0.1.9, which cannot run this guide: none of the feature-backed
24
+ verbs existed then, and the dotted parameter ids in Step 2 are new in 0.4.0.)
25
+
23
26
  **Design model (worth knowing before you edit):** the engine is *deterministic*
24
27
  — no AI inside it — and you refine a drawing by **stating domain intent**
25
28
  (dimension this feature, section through here) and letting the engine **place
@@ -79,13 +82,16 @@ Then verify (Step 3). For most parts you are done here.
79
82
 
80
83
  ## Step 2 — Customise with the Drawing builder
81
84
 
82
- `build_drawing(...)` returns a live `Drawing`. **Edit it in *domain* vocabulary
83
- locate things with `features()`, add dimensions with `place_dim()`, choose a
84
- side and view.** You give *what* and *where on the part*; the engine decides the
85
- *offset, stacking, and strip slot* (placement is automatic and constraint-based).
86
- You still pass page-point endpoints, but you get them from `features()` or
87
- `dwg.at(...)` you never compute offsets or pick a strip. Hand-building a raw
88
- `Leader` at `dwg.at(...)` coordinates is the escape hatch, not the default.
85
+ `build_drawing(...)` returns a live `Drawing`. **Edit it in *domain* vocabulary
86
+ name a detected *feature* and the measurement you want; the engine decides the
87
+ offset, stacking, and strip slot.** You give *what*, never *where on the page*:
88
+ placement is automatic and constraint-based.
89
+
90
+ Prefer the feature-backed verbs (`dimension` / `locate` / `callout` / `note` /
91
+ `drop`) over the raw page-coordinate primitives. The low-level API (`place_dim`,
92
+ `add`, `add_view`, the view-coordinate plumbing) is **deprecated** — see
93
+ `docs/deprecations.md` — because a raw coordinate does not route through the
94
+ layout solve, so it cannot be re-flowed when anything around it moves.
89
95
 
90
96
  ```python
91
97
  from draftwright import build_drawing
@@ -107,61 +113,103 @@ dwg.views # {"front","plan","side","iso"} → (visible, hidden
107
113
  dwg.draft / dwg.scale / dwg.page_w / dwg.page_h
108
114
  ```
109
115
 
110
- **Add a linear dimension with `place_dim`** it allocates the offset and stacks
111
- clear of existing dims; you give two page-point endpoints and a side/view:
116
+ **Add a dimension by naming a feature and a measurement** — the engine derives the
117
+ value from the geometry and owns the placement:
112
118
 
113
119
  ```python
114
120
  # side ∈ {"above","below","left","right"}; view ∈ {"front","plan","side"}.
115
- p1 = dwg.at("front", 0, 0, 0) # world page point
116
- p2 = dwg.at("front", 40, 0, 0)
117
- dwg.place_dim(p1, p2, "above", "front", dwg.draft, name="dim_len")
121
+ env = next(f for f in dwg.model().features if f.kind == "envelope")
122
+ hole = next(f for f in dwg.model().features if f.kind == "hole")
123
+
124
+ dwg.dimension(env, "length", role="width", side="above", view="front")
125
+ dwg.locate(hole) # locating dimensions for the feature
126
+ dwg.callout(hole) # a ⌀ callout the auto-pass missed
118
127
 
119
- dwg.remove("dim_od") # drop an automatic annotation by name
128
+ dwg.drop(hole) # stop dimensioning this feature
129
+
130
+ name = next(iter(dwg.annotations())) # names come from annotations(); they are
131
+ dwg.remove(name) # engine-assigned, so don't guess one
120
132
  ```
121
133
 
122
- **Add a diameter callout on a hole the auto-pass missed** locate it with
123
- `features()` and attach a `HoleCallout` (this is what the `feature_not_dimensioned`
124
- lint suggestion hands you verbatim see the loop below):
134
+ **Mind the two spellings.** On `Drawing.dimension` the second argument is the
135
+ parameter *kind* (`"length"`, `"diameter"`) and `role=` discriminates between
136
+ same-kind parameters an envelope has three `length` params with roles `width` /
137
+ `height` / `depth`, so `dimension(env, "length")` alone is ambiguous and raises,
138
+ naming them. `feature.parameters()` returns `DimParameter` objects carrying
139
+ `.kind` and `.role`, so `[(p.kind, p.role) for p in feature.parameters()]` is how
140
+ to see what a feature accepts.
141
+
142
+ On the `Sheet` facade the same measurement is one dotted **parameter id** —
143
+ `"width.length"` — and `dimension_ids()` on a handle lists them. Use the id there
144
+ rather than the bare family role (`"width"`): the bare spelling is deprecated,
145
+ because it is what let a single call silently declare two dimensions.
146
+
147
+ `place_dim(p1, p2, side, view, …)` still exists for raw page coordinates, but it is
148
+ the **escape hatch of last resort** (ADR 0012) and is deprecated: it bypasses the
149
+ layout solve, so nothing re-flows around it.
150
+
151
+ **Add a diameter callout on a hole the auto-pass missed** — find the bore in the
152
+ model IR and hand it to `callout()`. This is what the `feature_not_dimensioned`
153
+ lint suggestion hands you (it emits `dwg.callout(f)` — say *what*, not *where*):
125
154
 
126
155
  ```python
127
- from build123d_drafting import HoleCallout, Leader
128
-
129
- for f in dwg.features("plan"): # plan→Z holes, front→Y, side→X
130
- if abs(f.diameter - 4.0) < 0.2:
131
- callout = HoleCallout(f.diameter, count=f.count, through=f.through,
132
- depth=f.depth, draft=dwg.draft)
133
- elbow = (f.page_pos[0] + 15, f.page_pos[1] + 10, 0)
134
- dwg.add(Leader((*f.page_pos, 0), elbow, "", dwg.draft, callout=callout),
135
- name="hole_4")
156
+ for f in dwg.model().features:
157
+ if f.kind == "hole" and abs(f.diameter - 4.0) < 0.2:
158
+ dwg.callout(f) # engine picks the view, leader and elbow
136
159
  ```
137
160
 
138
- **Escape hatch** only when no domain verb fits (e.g. a free-form note at an
139
- exact spot). Prefer the above; this couples you to page mechanics:
161
+ **Free text** at a chosen point is `note()` a domain verb, not an escape hatch:
140
162
 
141
163
  ```python
142
- from build123d_drafting import Leader
143
- dwg.add(Leader(tip=dwg.at("front", 10, 0, 5), elbow=(8, 40, 0),
144
- label="ø4 BORE", draft=dwg.draft), "ldr_bore")
164
+ dwg.note("ø4 BORE", dwg.at("front", 10, 0, 5), view="front")
145
165
  ```
146
166
 
147
167
  Then re-lint and export:
148
168
 
149
169
  ```python
150
170
  issues = dwg.lint() # list of LintIssue; [] when clean
151
- svg, dxf = dwg.export("drawings/bracket")
171
+ paths = dwg.export("drawings/bracket", formats=("svg", "dxf", "pdf"))
172
+ svg, dxf = paths["svg"], paths["dxf"]
152
173
  ```
153
174
 
154
- `make_drawing(...)` is exactly `build_drawing(...).export()`.
175
+ Pass `formats=` and read the `{format: path}` dict. Calling `export()` with no
176
+ `formats` — or with the `svg=`/`dxf=` booleans — takes the legacy path and returns
177
+ a `(svg, dxf)` tuple. Both are deprecated (v0.3.1) and removed in 0.5.0, and both
178
+ warn from 0.4.0. See `docs/deprecations.md`.
179
+
180
+ `make_drawing(...)` is unaffected and does not warn: it still returns
181
+ `(svg_path, dxf_path)`, and passes `formats=` internally to get them.
182
+
183
+ `make_drawing(...)` is `build_drawing(...).export(formats=("svg", "dxf"))`, unpacked to a
184
+ tuple — not a bare `.export()`, which is the deprecated shape above.
155
185
 
156
- **Add a section or auxiliary view** with `add_view()`:
186
+ **Section views** come from the section verb rather than from projecting a view by
187
+ hand. The two entry points are *not* equivalent:
157
188
 
158
189
  ```python
159
- look = dwg.look_at
160
- bottom = (look[0], look[1], look[2] - dwg.dist)
161
- vc = dwg.add_view("bottom", part, bottom, (0, 1, 0), (260.0, 60.0))
162
- px, py = vc.pp(world_x, world_y, world_z)
190
+ from draftwright import Sheet
191
+
192
+ # Sheet.section() FORCES a cut, wherever you point it.
193
+ cut = Sheet.from_part(part, number="DWG-042").section().build()
194
+ "section_aa" in cut.views # True
195
+ # section(feature) cuts through that feature; section(at=y) at an explicit Y.
196
+
197
+ # Drawing.section() adds only the AUTOMATIC A–A, which fires just for qualifying
198
+ # hidden internal geometry (a counterbore, spotface, or blind bottom). It returns
199
+ # the placed annotation names, or [] when no section is warranted — so on a plain
200
+ # through-holed block it does nothing at all.
201
+ dwg.section()
163
202
  ```
164
203
 
204
+ Arbitrary **auxiliary** views have no public verb: `add_view()` was the way to
205
+ project one and is deprecated, so a custom viewing direction is not currently part
206
+ of the supported surface.
207
+
208
+ `add_view()` and the view-coordinate plumbing (`set_view_coordinates`,
209
+ `drop_view_coordinates`, and the `vc.pp(...)` projector) are **deprecated** (#817):
210
+ view projection is engine plumbing, and hand-placed views do not participate in the
211
+ compose-then-pack layout.
212
+
165
213
  ---
166
214
 
167
215
  ## Step 3 — Verify
@@ -179,9 +227,9 @@ mcp__build123d-mcp__inspect_drawing(svg_path='drawings/part_name.svg')
179
227
  **A — Drawing from a STEP file**:
180
228
 
181
229
  ```python
182
- from draftwright import generate_script
230
+ from draftwright.sheet_emit import generate_sheet_script
183
231
 
184
- generate_script(
232
+ generate_sheet_script(
185
233
  "path/to/part.step",
186
234
  out="scripts/drawings/bracket",
187
235
  title="BRACKET", number="DWG-042",
@@ -189,6 +237,13 @@ generate_script(
189
237
  )
190
238
  ```
191
239
 
240
+ This writes a declarative `Sheet` script: one named line per detected feature, then the
241
+ dimension set it draws. Comment a feature line out to drop that feature; edit a value
242
+ freely; re-run the file. (`generate_script`, the older imperative flavour, was retired in
243
+ #940 and deleted in 0.4.0 — importing it now fails; use `generate_sheet_script` above.) The
244
+ CLI equivalent is `draftwright part.step
245
+ --script --out scripts/drawings/bracket`.
246
+
192
247
  **B — Drawing an in-session object** (hand-write the script):
193
248
 
194
249
  ```python
@@ -235,7 +290,7 @@ crit = dwg.lint_summary()
235
290
  for i in dwg.lint():
236
291
  print(i.severity, i.code, i.message)
237
292
  if getattr(i, "suggestion", None):
238
- print(" fix:", i.suggestion) # e.g. dwg.place_dim(...) / dwg.add_view(...)
293
+ print(" fix:", i.suggestion) # e.g. dwg.callout(...) / dwg.dimension(...)
239
294
 
240
295
  # 3. Self-repair — auto-applies the mechanically-fixable issues (overlapping
241
296
  # labels pushed apart, wrong-side dims flipped). Runs by default inside
@@ -249,8 +304,9 @@ dwg.pin(name) # name from dwg.annotations(); dwg.unpin(name) to rele
249
304
 
250
305
  Codes are domain-meaningful (`feature_not_dimensioned`, `feature_count_mismatch`,
251
306
  `callout_dropped`, `location_ref_dropped`, `step_dim_dropped`, …), so a fix is
252
- always expressible through the domain API (`place_dim`, `features`, `add_view`),
253
- never the page-layout internals. Loop until `passed` (or the score plateaus).
307
+ always expressible through the domain API (`dimension`, `locate`, `callout`,
308
+ `features`), never the page-layout internals. Loop until `passed` (or the score
309
+ plateaus).
254
310
 
255
311
  Coverage-only check, standalone:
256
312
 
@@ -24,7 +24,6 @@ from typing import TYPE_CHECKING
24
24
  # on every TAB press (#313). Each name maps to the submodule that provides it.
25
25
  _LAZY = {
26
26
  "build_drawing": "draftwright.builder",
27
- "generate_script": "draftwright.builder",
28
27
  "make_drawing": "draftwright.builder",
29
28
  "Drawing": "draftwright.drawing",
30
29
  "FeatureInfo": "draftwright.drawing",
@@ -69,7 +68,7 @@ _sys.modules[__name__].__class__ = _DraftwrightModule
69
68
 
70
69
 
71
70
  if TYPE_CHECKING: # static analysers / IDEs — no runtime import, no kernel cost
72
- from draftwright.builder import build_drawing, generate_script, make_drawing
71
+ from draftwright.builder import build_drawing, make_drawing
73
72
  from draftwright.compose import choose_scale
74
73
  from draftwright.drawing import Drawing, FeatureInfo
75
74
  from draftwright.linting import lint_feature_coverage
@@ -92,7 +91,6 @@ __all__ = [
92
91
  "build_drawing",
93
92
  "choose_scale",
94
93
  "extract_pmi",
95
- "generate_script",
96
94
  "lint_feature_coverage",
97
95
  "make_drawing",
98
96
  ]