draftwright 0.4.28__tar.gz → 0.4.30__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 (879) hide show
  1. draftwright-0.4.30/CHANGELOG.md +3104 -0
  2. draftwright-0.4.30/PKG-INFO +1039 -0
  3. draftwright-0.4.30/README.md +336 -0
  4. draftwright-0.4.30/docs/adr/README.md +48 -0
  5. draftwright-0.4.30/docs/reference/recogniser-capabilities.md +773 -0
  6. draftwright-0.4.30/docs/reference/sheet.md +834 -0
  7. draftwright-0.4.30/pyproject.toml +197 -0
  8. draftwright-0.4.30/src/draftwright/_core.py +1960 -0
  9. draftwright-0.4.30/src/draftwright/_pmi_part21.py +1533 -0
  10. draftwright-0.4.30/src/draftwright/analysis.py +1523 -0
  11. draftwright-0.4.30/src/draftwright/annotations/_common.py +3200 -0
  12. draftwright-0.4.30/src/draftwright/annotations/angular.py +140 -0
  13. draftwright-0.4.30/src/draftwright/annotations/balloons.py +1172 -0
  14. draftwright-0.4.30/src/draftwright/annotations/from_model.py +8708 -0
  15. draftwright-0.4.30/src/draftwright/annotations/holes.py +3361 -0
  16. draftwright-0.4.30/src/draftwright/annotations/leaders.py +2400 -0
  17. draftwright-0.4.30/src/draftwright/annotations/orchestrator.py +1645 -0
  18. draftwright-0.4.30/src/draftwright/annotations/sections.py +1266 -0
  19. draftwright-0.4.30/src/draftwright/builder.py +3144 -0
  20. draftwright-0.4.30/src/draftwright/cli.py +457 -0
  21. draftwright-0.4.30/src/draftwright/compose.py +1972 -0
  22. draftwright-0.4.30/src/draftwright/drawing.py +5635 -0
  23. draftwright-0.4.30/src/draftwright/evaluation/step_analysis.py +5896 -0
  24. draftwright-0.4.30/src/draftwright/export.py +843 -0
  25. draftwright-0.4.30/src/draftwright/feature_identity.py +43 -0
  26. draftwright-0.4.30/src/draftwright/inspection_contract.py +278 -0
  27. draftwright-0.4.30/src/draftwright/linting/_registry.py +201 -0
  28. draftwright-0.4.30/src/draftwright/linting/angular.py +629 -0
  29. draftwright-0.4.30/src/draftwright/linting/circular_blind_step_coverage.py +339 -0
  30. draftwright-0.4.30/src/draftwright/linting/evidence.py +575 -0
  31. draftwright-0.4.30/src/draftwright/linting/hole_coverage.py +1535 -0
  32. draftwright-0.4.30/src/draftwright/linting/pmi_coverage.py +458 -0
  33. draftwright-0.4.30/src/draftwright/linting/quality.py +917 -0
  34. draftwright-0.4.30/src/draftwright/linting/requirements.py +198 -0
  35. draftwright-0.4.30/src/draftwright/linting/section_recess_coverage.py +402 -0
  36. draftwright-0.4.30/src/draftwright/linting/structural.py +1467 -0
  37. draftwright-0.4.30/src/draftwright/model/__init__.py +247 -0
  38. draftwright-0.4.30/src/draftwright/model/callout.py +705 -0
  39. draftwright-0.4.30/src/draftwright/model/compiled.py +2442 -0
  40. draftwright-0.4.30/src/draftwright/model/declare.py +2729 -0
  41. draftwright-0.4.30/src/draftwright/model/detect.py +2796 -0
  42. draftwright-0.4.30/src/draftwright/model/ir.py +3846 -0
  43. draftwright-0.4.30/src/draftwright/model/planner.py +2119 -0
  44. draftwright-0.4.30/src/draftwright/model/pmi_lowering.py +928 -0
  45. draftwright-0.4.30/src/draftwright/pmi.py +3093 -0
  46. draftwright-0.4.30/src/draftwright/recogniser_contract.py +1222 -0
  47. draftwright-0.4.30/src/draftwright/recognition_ownership.py +1149 -0
  48. draftwright-0.4.30/src/draftwright/repair.py +298 -0
  49. draftwright-0.4.30/src/draftwright/section_recess_contract.py +978 -0
  50. draftwright-0.4.30/src/draftwright/sheet.py +3350 -0
  51. draftwright-0.4.30/src/draftwright/sheet_emit.py +2915 -0
  52. draftwright-0.4.30/tests/_burden_report.py +260 -0
  53. draftwright-0.4.30/tests/_corpus_cover.py +114 -0
  54. draftwright-0.4.30/tests/_drawing_helpers.py +65 -0
  55. draftwright-0.4.30/tests/_evidence_contract.py +291 -0
  56. draftwright-0.4.30/tests/_layout_helpers.py +19 -0
  57. draftwright-0.4.30/tests/_parts.py +206 -0
  58. draftwright-0.4.30/tests/_tier_manifest.py +225 -0
  59. draftwright-0.4.30/tests/_unit_manifest.py +31 -0
  60. draftwright-0.4.30/tests/conftest.py +403 -0
  61. draftwright-0.4.30/tests/fixtures/evaluation/README.md +248 -0
  62. draftwright-0.4.30/tests/fixtures/evaluation/corpus-hole-patterns-v1.json +151 -0
  63. draftwright-0.4.30/tests/fixtures/evaluation/pattern-topology-a.step +1192 -0
  64. draftwright-0.4.30/tests/fixtures/evaluation/pattern-topology-b.step +1192 -0
  65. draftwright-0.4.30/tests/fixtures/issue_1595_whistle_key_frame.step +19649 -0
  66. draftwright-0.4.30/tests/fixtures/issue_1595_whistle_key_lever.step +5407 -0
  67. draftwright-0.4.30/tests/refactor_golden/bracket_section.json +430 -0
  68. draftwright-0.4.30/tests/refactor_golden/centered_rebate.json +206 -0
  69. draftwright-0.4.30/tests/refactor_golden/chamfered.json +170 -0
  70. draftwright-0.4.30/tests/refactor_golden/filleted.json +170 -0
  71. draftwright-0.4.30/tests/refactor_golden/flange_dense.json +323 -0
  72. draftwright-0.4.30/tests/refactor_golden/grid_plate.json +520 -0
  73. draftwright-0.4.30/tests/refactor_golden/grooved_shaft.json +267 -0
  74. draftwright-0.4.30/tests/refactor_golden/hex_bar.json +170 -0
  75. draftwright-0.4.30/tests/refactor_golden/holed_slot.json +335 -0
  76. draftwright-0.4.30/tests/refactor_golden/pocketed.json +255 -0
  77. draftwright-0.4.30/tests/refactor_golden/prismatic_ladder.json +164 -0
  78. draftwright-0.4.30/tests/refactor_golden/scattered_plate.json +699 -0
  79. draftwright-0.4.30/tests/refactor_golden/side_drilled.json +219 -0
  80. draftwright-0.4.30/tests/refactor_golden/turned_stepped.json +213 -0
  81. draftwright-0.4.30/tests/test_add_dimension.py +1538 -0
  82. draftwright-0.4.30/tests/test_adr0018_view_routing.py +115 -0
  83. draftwright-0.4.30/tests/test_adr0018_view_selection.py +695 -0
  84. draftwright-0.4.30/tests/test_analysis_geometry.py +121 -0
  85. draftwright-0.4.30/tests/test_angle_patterns.py +220 -0
  86. draftwright-0.4.30/tests/test_angular_references.py +253 -0
  87. draftwright-0.4.30/tests/test_angular_rendering.py +415 -0
  88. draftwright-0.4.30/tests/test_annotation_box_composition.py +131 -0
  89. draftwright-0.4.30/tests/test_annotation_box_corpus.py +81 -0
  90. draftwright-0.4.30/tests/test_arrangement_gate.py +399 -0
  91. draftwright-0.4.30/tests/test_audit_differential.py +796 -0
  92. draftwright-0.4.30/tests/test_axial_dimensions.py +104 -0
  93. draftwright-0.4.30/tests/test_build_drawing_entrypoints.py +99 -0
  94. draftwright-0.4.30/tests/test_burden_report.py +85 -0
  95. draftwright-0.4.30/tests/test_canonical_angles.py +767 -0
  96. draftwright-0.4.30/tests/test_cli_behavior.py +114 -0
  97. draftwright-0.4.30/tests/test_clone_budget.py +222 -0
  98. draftwright-0.4.30/tests/test_compiled_plan_boundary.py +1339 -0
  99. draftwright-0.4.30/tests/test_compose_then_pack.py +504 -0
  100. draftwright-0.4.30/tests/test_corpus_cover.py +164 -0
  101. draftwright-0.4.30/tests/test_coverage_mode.py +53 -0
  102. draftwright-0.4.30/tests/test_declare.py +2153 -0
  103. draftwright-0.4.30/tests/test_deferred_edits.py +416 -0
  104. draftwright-0.4.30/tests/test_deprecation_dates.py +156 -0
  105. draftwright-0.4.30/tests/test_depth_estimators.py +72 -0
  106. draftwright-0.4.30/tests/test_detail_views.py +453 -0
  107. draftwright-0.4.30/tests/test_diameter_deduplication.py +30 -0
  108. draftwright-0.4.30/tests/test_diameter_leaders.py +215 -0
  109. draftwright-0.4.30/tests/test_document_claim_authority.py +183 -0
  110. draftwright-0.4.30/tests/test_document_claims.py +268 -0
  111. draftwright-0.4.30/tests/test_document_report.py +389 -0
  112. draftwright-0.4.30/tests/test_document_report_authority.py +142 -0
  113. draftwright-0.4.30/tests/test_drawing_encapsulation.py +557 -0
  114. draftwright-0.4.30/tests/test_drawing_operations.py +116 -0
  115. draftwright-0.4.30/tests/test_drawing_state.py +177 -0
  116. draftwright-0.4.30/tests/test_dynamic_corridors.py +84 -0
  117. draftwright-0.4.30/tests/test_e2e_standards.py +513 -0
  118. draftwright-0.4.30/tests/test_export_dxf_curves.py +137 -0
  119. draftwright-0.4.30/tests/test_export_dxf_zoom.py +108 -0
  120. draftwright-0.4.30/tests/test_export_formats.py +327 -0
  121. draftwright-0.4.30/tests/test_export_reproducible.py +720 -0
  122. draftwright-0.4.30/tests/test_export_shape.py +140 -0
  123. draftwright-0.4.30/tests/test_feature_edit_corridors.py +200 -0
  124. draftwright-0.4.30/tests/test_feature_edit_replay.py +226 -0
  125. draftwright-0.4.30/tests/test_feature_edit_verbs.py +376 -0
  126. draftwright-0.4.30/tests/test_feature_provenance.py +289 -0
  127. draftwright-0.4.30/tests/test_feature_queries.py +99 -0
  128. draftwright-0.4.30/tests/test_fit_calculations.py +132 -0
  129. draftwright-0.4.30/tests/test_fits.py +94 -0
  130. draftwright-0.4.30/tests/test_flat_stock_identity.py +255 -0
  131. draftwright-0.4.30/tests/test_format_selector.py +76 -0
  132. draftwright-0.4.30/tests/test_formatting.py +19 -0
  133. draftwright-0.4.30/tests/test_groove_completeness_evidence.py +661 -0
  134. draftwright-0.4.30/tests/test_hole_annotations.py +305 -0
  135. draftwright-0.4.30/tests/test_hole_pattern_callouts.py +136 -0
  136. draftwright-0.4.30/tests/test_hole_table.py +732 -0
  137. draftwright-0.4.30/tests/test_import_boundaries.py +1253 -0
  138. draftwright-0.4.30/tests/test_inspection_contract.py +182 -0
  139. draftwright-0.4.30/tests/test_iso_layout.py +106 -0
  140. draftwright-0.4.30/tests/test_iso_nts_caption_placement.py +623 -0
  141. draftwright-0.4.30/tests/test_isometric_orientation.py +25 -0
  142. draftwright-0.4.30/tests/test_isometric_placement.py +138 -0
  143. draftwright-0.4.30/tests/test_issue_1006_measurement_comparison.py +283 -0
  144. draftwright-0.4.30/tests/test_issue_1058_wheel_profile.py +911 -0
  145. draftwright-0.4.30/tests/test_issue_1086_declared_gears.py +291 -0
  146. draftwright-0.4.30/tests/test_issue_1116_ap242_hole_tolerance_lowering.py +459 -0
  147. draftwright-0.4.30/tests/test_issue_1143_hole_completeness.py +2871 -0
  148. draftwright-0.4.30/tests/test_issue_1146_scale_completeness.py +632 -0
  149. draftwright-0.4.30/tests/test_issue_1166_cross_pass_feature_leaders.py +2525 -0
  150. draftwright-0.4.30/tests/test_issue_1176_quality_fidelity.py +995 -0
  151. draftwright-0.4.30/tests/test_issue_1177_angular_dimensions.py +534 -0
  152. draftwright-0.4.30/tests/test_issue_1187_unroutable_leaders.py +145 -0
  153. draftwright-0.4.30/tests/test_issue_1190_section_decision.py +255 -0
  154. draftwright-0.4.30/tests/test_issue_1209_linear_pmi_witnesses.py +406 -0
  155. draftwright-0.4.30/tests/test_issue_1215_envelope_tolerance.py +321 -0
  156. draftwright-0.4.30/tests/test_issue_1215_no_approved_tolerance_is_dropped.py +936 -0
  157. draftwright-0.4.30/tests/test_issue_1217_claimed_representations.py +517 -0
  158. draftwright-0.4.30/tests/test_issue_1217_the_facility_is_shared.py +418 -0
  159. draftwright-0.4.30/tests/test_issue_1246_prismatic_pocket_disposition.py +202 -0
  160. draftwright-0.4.30/tests/test_issue_1296_cylindrical_diameter_pmi.py +1212 -0
  161. draftwright-0.4.30/tests/test_issue_1299_page_escalation.py +846 -0
  162. draftwright-0.4.30/tests/test_issue_1308_machined_leader_analytics.py +316 -0
  163. draftwright-0.4.30/tests/test_issue_1325_nominal_agreement.py +106 -0
  164. draftwright-0.4.30/tests/test_issue_1333_bounded_ink_repair.py +295 -0
  165. draftwright-0.4.30/tests/test_issue_1338_scale_before_page_escalation.py +110 -0
  166. draftwright-0.4.30/tests/test_issue_1349_precision_display.py +597 -0
  167. draftwright-0.4.30/tests/test_issue_1352_searchable_pdf_text.py +1001 -0
  168. draftwright-0.4.30/tests/test_issue_1357_framed_activation.py +237 -0
  169. draftwright-0.4.30/tests/test_issue_1357_pmi_frame.py +207 -0
  170. draftwright-0.4.30/tests/test_issue_1370_countersink_completeness_evidence.py +522 -0
  171. draftwright-0.4.30/tests/test_issue_1370_double_d_completeness_evidence.py +731 -0
  172. draftwright-0.4.30/tests/test_issue_1370_hole_pattern_completeness_evidence.py +330 -0
  173. draftwright-0.4.30/tests/test_issue_1371_flat_completeness_evidence.py +255 -0
  174. draftwright-0.4.30/tests/test_issue_1371_polygonal_stock_completeness_evidence.py +1184 -0
  175. draftwright-0.4.30/tests/test_issue_1373_plate_completeness_evidence.py +1474 -0
  176. draftwright-0.4.30/tests/test_issue_1374_chamfer_completeness_evidence.py +530 -0
  177. draftwright-0.4.30/tests/test_issue_1374_fillet_completeness_evidence.py +496 -0
  178. draftwright-0.4.30/tests/test_issue_1374_turned_step_completeness_evidence.py +1566 -0
  179. draftwright-0.4.30/tests/test_issue_1396_layout_advisories.py +165 -0
  180. draftwright-0.4.30/tests/test_issue_1432_oriented_slot_semantics.py +1255 -0
  181. draftwright-0.4.30/tests/test_issue_1450_boss_blend_ownership.py +346 -0
  182. draftwright-0.4.30/tests/test_issue_1460_step_inspection.py +833 -0
  183. draftwright-0.4.30/tests/test_issue_1466_dimension_options.py +311 -0
  184. draftwright-0.4.30/tests/test_issue_1466_semantic_sides.py +323 -0
  185. draftwright-0.4.30/tests/test_issue_1479_radius_leader_targets.py +274 -0
  186. draftwright-0.4.30/tests/test_issue_1485_curved_pockets.py +322 -0
  187. draftwright-0.4.30/tests/test_issue_1485_rounded_pockets.py +287 -0
  188. draftwright-0.4.30/tests/test_issue_1505_short_axial_chains.py +310 -0
  189. draftwright-0.4.30/tests/test_issue_1511_turned_boss_heights.py +338 -0
  190. draftwright-0.4.30/tests/test_issue_1516_dimension_text.py +244 -0
  191. draftwright-0.4.30/tests/test_issue_1517_through_indicator.py +495 -0
  192. draftwright-0.4.30/tests/test_issue_1518_explicit_rear.py +685 -0
  193. draftwright-0.4.30/tests/test_issue_1536_title_overflow.py +162 -0
  194. draftwright-0.4.30/tests/test_issue_1543_schedule_requirements.py +596 -0
  195. draftwright-0.4.30/tests/test_issue_1544_frame_document_canary.py +419 -0
  196. draftwright-0.4.30/tests/test_issue_1560_envelope_axes.py +69 -0
  197. draftwright-0.4.30/tests/test_issue_1585_title_block_date.py +183 -0
  198. draftwright-0.4.30/tests/test_issue_1593_title_block_keepout.py +145 -0
  199. draftwright-0.4.30/tests/test_issue_1596_bolt_circle_corroboration.py +452 -0
  200. draftwright-0.4.30/tests/test_issue_1598_hex_pockets.py +388 -0
  201. draftwright-0.4.30/tests/test_issue_1599_duplicate_slot_widths.py +41 -0
  202. draftwright-0.4.30/tests/test_issue_1601_flat_precision.py +47 -0
  203. draftwright-0.4.30/tests/test_issue_1603_cli_output_paths.py +122 -0
  204. draftwright-0.4.30/tests/test_issue_1604_internal_section.py +80 -0
  205. draftwright-0.4.30/tests/test_issue_1612_frame_sergio_coverage.py +32 -0
  206. draftwright-0.4.30/tests/test_issue_1612_sheet_margins.py +210 -0
  207. draftwright-0.4.30/tests/test_issue_1613_circular_channel.py +638 -0
  208. draftwright-0.4.30/tests/test_issue_740_leader_assignment.py +718 -0
  209. draftwright-0.4.30/tests/test_issue_798_silhouette_lint.py +343 -0
  210. draftwright-0.4.30/tests/test_issue_883_location_identity.py +574 -0
  211. draftwright-0.4.30/tests/test_issue_909_sloped_profile.py +196 -0
  212. draftwright-0.4.30/tests/test_issue_915_dense_case.py +214 -0
  213. draftwright-0.4.30/tests/test_label_provenance.py +186 -0
  214. draftwright-0.4.30/tests/test_layout_cleanliness.py +89 -0
  215. draftwright-0.4.30/tests/test_layout_constants.py +49 -0
  216. draftwright-0.4.30/tests/test_layout_generalisation.py +177 -0
  217. draftwright-0.4.30/tests/test_layout_property.py +201 -0
  218. draftwright-0.4.30/tests/test_lint_coverage.py +249 -0
  219. draftwright-0.4.30/tests/test_lint_structural.py +841 -0
  220. draftwright-0.4.30/tests/test_lint_suggestions.py +178 -0
  221. draftwright-0.4.30/tests/test_lint_summary.py +321 -0
  222. draftwright-0.4.30/tests/test_location_dimensions.py +262 -0
  223. draftwright-0.4.30/tests/test_location_vocabulary.py +438 -0
  224. draftwright-0.4.30/tests/test_machined_feature_callouts.py +600 -0
  225. draftwright-0.4.30/tests/test_make_drawing_entrypoints.py +93 -0
  226. draftwright-0.4.30/tests/test_overall_height_coverage.py +199 -0
  227. draftwright-0.4.30/tests/test_pad_completeness_evidence.py +763 -0
  228. draftwright-0.4.30/tests/test_parameter_id.py +733 -0
  229. draftwright-0.4.30/tests/test_part_classification.py +299 -0
  230. draftwright-0.4.30/tests/test_place_dimension.py +115 -0
  231. draftwright-0.4.30/tests/test_pmi.py +2481 -0
  232. draftwright-0.4.30/tests/test_pmi_common_labels.py +89 -0
  233. draftwright-0.4.30/tests/test_pmi_datum_lowering.py +480 -0
  234. draftwright-0.4.30/tests/test_pmi_gtol_lowering.py +279 -0
  235. draftwright-0.4.30/tests/test_pmi_part21.py +1506 -0
  236. draftwright-0.4.30/tests/test_pocket_completeness_evidence.py +487 -0
  237. draftwright-0.4.30/tests/test_pocket_pattern_completeness_evidence.py +989 -0
  238. draftwright-0.4.30/tests/test_polygonal_boss_completeness_evidence.py +816 -0
  239. draftwright-0.4.30/tests/test_prismatic_boss_diameter.py +223 -0
  240. draftwright-0.4.30/tests/test_prismatic_classification.py +177 -0
  241. draftwright-0.4.30/tests/test_prismatic_dimensions.py +362 -0
  242. draftwright-0.4.30/tests/test_private_test_attr_reads.py +303 -0
  243. draftwright-0.4.30/tests/test_private_test_imports.py +214 -0
  244. draftwright-0.4.30/tests/test_projection_symbol_default.py +178 -0
  245. draftwright-0.4.30/tests/test_real_part_fixtures.py +32 -0
  246. draftwright-0.4.30/tests/test_recogniser_capabilities.py +1663 -0
  247. draftwright-0.4.30/tests/test_recogniser_policy.py +50 -0
  248. draftwright-0.4.30/tests/test_recogniser_step_inventory.py +66 -0
  249. draftwright-0.4.30/tests/test_recognition_boundary_failures.py +231 -0
  250. draftwright-0.4.30/tests/test_recognition_policy_dispositions.py +281 -0
  251. draftwright-0.4.30/tests/test_refactor_golden.py +451 -0
  252. draftwright-0.4.30/tests/test_render_seam.py +430 -0
  253. draftwright-0.4.30/tests/test_repair.py +130 -0
  254. draftwright-0.4.30/tests/test_report_projection.py +935 -0
  255. draftwright-0.4.30/tests/test_requirement_catalog_families.py +130 -0
  256. draftwright-0.4.30/tests/test_scale_policy.py +68 -0
  257. draftwright-0.4.30/tests/test_scale_selection.py +206 -0
  258. draftwright-0.4.30/tests/test_section_hatching.py +27 -0
  259. draftwright-0.4.30/tests/test_section_recess_pockets.py +370 -0
  260. draftwright-0.4.30/tests/test_shared_drawing_cache.py +107 -0
  261. draftwright-0.4.30/tests/test_sheet_emit.py +4657 -0
  262. draftwright-0.4.30/tests/test_sheet_furniture.py +145 -0
  263. draftwright-0.4.30/tests/test_sheet_identity_invariant.py +918 -0
  264. draftwright-0.4.30/tests/test_slot_completeness.py +691 -0
  265. draftwright-0.4.30/tests/test_slot_recognition.py +513 -0
  266. draftwright-0.4.30/tests/test_step_analysis_evaluation.py +417 -0
  267. draftwright-0.4.30/tests/test_strip_layout.py +1591 -0
  268. draftwright-0.4.30/tests/test_strip_zones.py +295 -0
  269. draftwright-0.4.30/tests/test_suite_shape.py +63 -0
  270. draftwright-0.4.30/tests/test_tier_manifest.py +105 -0
  271. draftwright-0.4.30/tests/test_turned_diameters.py +766 -0
  272. draftwright-0.4.30/tests/test_turned_lengths.py +299 -0
  273. draftwright-0.4.30/tests/test_two_pass_layout.py +122 -0
  274. draftwright-0.4.30/tests/test_typ_dimensions.py +80 -0
  275. draftwright-0.4.30/tests/test_version_bump_ci.py +259 -0
  276. draftwright-0.4.30/tests/test_view_blocks.py +137 -0
  277. draftwright-0.4.30/tests/test_view_coordinates.py +162 -0
  278. draftwright-0.4.30/tests/test_workflows.py +587 -0
  279. draftwright-0.4.28/CHANGELOG.md +0 -2851
  280. draftwright-0.4.28/PKG-INFO +0 -1029
  281. draftwright-0.4.28/README.md +0 -326
  282. draftwright-0.4.28/docs/adr/README.md +0 -48
  283. draftwright-0.4.28/docs/reference/recogniser-capabilities.md +0 -763
  284. draftwright-0.4.28/docs/reference/sheet.md +0 -776
  285. draftwright-0.4.28/pyproject.toml +0 -195
  286. draftwright-0.4.28/src/draftwright/_core.py +0 -1706
  287. draftwright-0.4.28/src/draftwright/_pmi_part21.py +0 -653
  288. draftwright-0.4.28/src/draftwright/analysis.py +0 -1472
  289. draftwright-0.4.28/src/draftwright/annotations/_common.py +0 -3109
  290. draftwright-0.4.28/src/draftwright/annotations/angular.py +0 -136
  291. draftwright-0.4.28/src/draftwright/annotations/balloons.py +0 -1171
  292. draftwright-0.4.28/src/draftwright/annotations/from_model.py +0 -8223
  293. draftwright-0.4.28/src/draftwright/annotations/holes.py +0 -3339
  294. draftwright-0.4.28/src/draftwright/annotations/leaders.py +0 -2333
  295. draftwright-0.4.28/src/draftwright/annotations/orchestrator.py +0 -1614
  296. draftwright-0.4.28/src/draftwright/annotations/sections.py +0 -1254
  297. draftwright-0.4.28/src/draftwright/builder.py +0 -2907
  298. draftwright-0.4.28/src/draftwright/cli.py +0 -389
  299. draftwright-0.4.28/src/draftwright/compose.py +0 -1841
  300. draftwright-0.4.28/src/draftwright/drawing.py +0 -5532
  301. draftwright-0.4.28/src/draftwright/evaluation/step_analysis.py +0 -5886
  302. draftwright-0.4.28/src/draftwright/export.py +0 -835
  303. draftwright-0.4.28/src/draftwright/feature_identity.py +0 -26
  304. draftwright-0.4.28/src/draftwright/inspection_contract.py +0 -278
  305. draftwright-0.4.28/src/draftwright/linting/_registry.py +0 -177
  306. draftwright-0.4.28/src/draftwright/linting/angular.py +0 -569
  307. draftwright-0.4.28/src/draftwright/linting/circular_blind_step_coverage.py +0 -361
  308. draftwright-0.4.28/src/draftwright/linting/evidence.py +0 -556
  309. draftwright-0.4.28/src/draftwright/linting/hole_coverage.py +0 -1503
  310. draftwright-0.4.28/src/draftwright/linting/pmi_coverage.py +0 -457
  311. draftwright-0.4.28/src/draftwright/linting/quality.py +0 -879
  312. draftwright-0.4.28/src/draftwright/linting/requirements.py +0 -182
  313. draftwright-0.4.28/src/draftwright/linting/section_recess_coverage.py +0 -102
  314. draftwright-0.4.28/src/draftwright/linting/structural.py +0 -1271
  315. draftwright-0.4.28/src/draftwright/model/__init__.py +0 -235
  316. draftwright-0.4.28/src/draftwright/model/callout.py +0 -615
  317. draftwright-0.4.28/src/draftwright/model/compiled.py +0 -2294
  318. draftwright-0.4.28/src/draftwright/model/declare.py +0 -2599
  319. draftwright-0.4.28/src/draftwright/model/detect.py +0 -2726
  320. draftwright-0.4.28/src/draftwright/model/ir.py +0 -3536
  321. draftwright-0.4.28/src/draftwright/model/planner.py +0 -2028
  322. draftwright-0.4.28/src/draftwright/model/pmi_lowering.py +0 -879
  323. draftwright-0.4.28/src/draftwright/pmi.py +0 -1783
  324. draftwright-0.4.28/src/draftwright/recogniser_contract.py +0 -1222
  325. draftwright-0.4.28/src/draftwright/recognition_ownership.py +0 -1073
  326. draftwright-0.4.28/src/draftwright/repair.py +0 -293
  327. draftwright-0.4.28/src/draftwright/section_recess_contract.py +0 -714
  328. draftwright-0.4.28/src/draftwright/sheet.py +0 -3272
  329. draftwright-0.4.28/src/draftwright/sheet_emit.py +0 -2660
  330. draftwright-0.4.28/tests/_evidence_contract.py +0 -61
  331. draftwright-0.4.28/tests/conftest.py +0 -225
  332. draftwright-0.4.28/tests/fixtures/evaluation/README.md +0 -243
  333. draftwright-0.4.28/tests/fixtures/evaluation/corpus-hole-patterns-v1.json +0 -151
  334. draftwright-0.4.28/tests/fixtures/evaluation/pattern-topology-a.step +0 -1090
  335. draftwright-0.4.28/tests/fixtures/evaluation/pattern-topology-b.step +0 -1090
  336. draftwright-0.4.28/tests/refactor_golden/bracket_section.json +0 -399
  337. draftwright-0.4.28/tests/refactor_golden/centered_rebate.json +0 -175
  338. draftwright-0.4.28/tests/refactor_golden/chamfered.json +0 -139
  339. draftwright-0.4.28/tests/refactor_golden/filleted.json +0 -139
  340. draftwright-0.4.28/tests/refactor_golden/flange_dense.json +0 -273
  341. draftwright-0.4.28/tests/refactor_golden/grid_plate.json +0 -489
  342. draftwright-0.4.28/tests/refactor_golden/grooved_shaft.json +0 -236
  343. draftwright-0.4.28/tests/refactor_golden/hex_bar.json +0 -139
  344. draftwright-0.4.28/tests/refactor_golden/holed_slot.json +0 -304
  345. draftwright-0.4.28/tests/refactor_golden/pocketed.json +0 -248
  346. draftwright-0.4.28/tests/refactor_golden/prismatic_ladder.json +0 -157
  347. draftwright-0.4.28/tests/refactor_golden/scattered_plate.json +0 -668
  348. draftwright-0.4.28/tests/refactor_golden/side_drilled.json +0 -188
  349. draftwright-0.4.28/tests/refactor_golden/turned_stepped.json +0 -182
  350. draftwright-0.4.28/tests/test_add_dimension.py +0 -1509
  351. draftwright-0.4.28/tests/test_adr0018_arrangement_gate.py +0 -386
  352. draftwright-0.4.28/tests/test_adr0018_view_routing.py +0 -115
  353. draftwright-0.4.28/tests/test_adr0018_view_selection.py +0 -623
  354. draftwright-0.4.28/tests/test_audit_differential.py +0 -795
  355. draftwright-0.4.28/tests/test_clone_budget.py +0 -149
  356. draftwright-0.4.28/tests/test_compiled_plan_boundary.py +0 -1328
  357. draftwright-0.4.28/tests/test_declare.py +0 -2147
  358. draftwright-0.4.28/tests/test_deprecation_dates.py +0 -154
  359. draftwright-0.4.28/tests/test_drawing_encapsulation.py +0 -551
  360. draftwright-0.4.28/tests/test_e2e_standards.py +0 -450
  361. draftwright-0.4.28/tests/test_export_dxf_curves.py +0 -135
  362. draftwright-0.4.28/tests/test_export_dxf_zoom.py +0 -65
  363. draftwright-0.4.28/tests/test_export_reproducible.py +0 -699
  364. draftwright-0.4.28/tests/test_fits.py +0 -225
  365. draftwright-0.4.28/tests/test_flat_stock_identity.py +0 -250
  366. draftwright-0.4.28/tests/test_import_boundaries.py +0 -1236
  367. draftwright-0.4.28/tests/test_inspection_contract.py +0 -182
  368. draftwright-0.4.28/tests/test_iso_nts_caption_placement.py +0 -611
  369. draftwright-0.4.28/tests/test_issue_1006_measurement_comparison.py +0 -269
  370. draftwright-0.4.28/tests/test_issue_1058_wheel_profile.py +0 -902
  371. draftwright-0.4.28/tests/test_issue_1086_declared_gears.py +0 -291
  372. draftwright-0.4.28/tests/test_issue_1116_ap242_hole_tolerance_lowering.py +0 -395
  373. draftwright-0.4.28/tests/test_issue_1143_hole_completeness.py +0 -2867
  374. draftwright-0.4.28/tests/test_issue_1146_scale_completeness.py +0 -628
  375. draftwright-0.4.28/tests/test_issue_1166_cross_pass_feature_leaders.py +0 -2477
  376. draftwright-0.4.28/tests/test_issue_1176_quality_fidelity.py +0 -995
  377. draftwright-0.4.28/tests/test_issue_1177_angular_dimensions.py +0 -526
  378. draftwright-0.4.28/tests/test_issue_1187_unroutable_leaders.py +0 -147
  379. draftwright-0.4.28/tests/test_issue_1190_section_decision.py +0 -253
  380. draftwright-0.4.28/tests/test_issue_1209_linear_pmi_witnesses.py +0 -181
  381. draftwright-0.4.28/tests/test_issue_1215_envelope_tolerance.py +0 -314
  382. draftwright-0.4.28/tests/test_issue_1215_no_approved_tolerance_is_dropped.py +0 -929
  383. draftwright-0.4.28/tests/test_issue_1217_claimed_representations.py +0 -490
  384. draftwright-0.4.28/tests/test_issue_1217_the_facility_is_shared.py +0 -412
  385. draftwright-0.4.28/tests/test_issue_1246_prismatic_pocket_disposition.py +0 -202
  386. draftwright-0.4.28/tests/test_issue_1296_cylindrical_diameter_pmi.py +0 -849
  387. draftwright-0.4.28/tests/test_issue_1299_page_escalation.py +0 -503
  388. draftwright-0.4.28/tests/test_issue_1308_machined_leader_analytics.py +0 -307
  389. draftwright-0.4.28/tests/test_issue_1325_nominal_agreement.py +0 -107
  390. draftwright-0.4.28/tests/test_issue_1333_bounded_ink_repair.py +0 -287
  391. draftwright-0.4.28/tests/test_issue_1338_scale_before_page_escalation.py +0 -110
  392. draftwright-0.4.28/tests/test_issue_1349_precision_display.py +0 -506
  393. draftwright-0.4.28/tests/test_issue_1352_searchable_pdf_text.py +0 -989
  394. draftwright-0.4.28/tests/test_issue_1357_framed_activation.py +0 -235
  395. draftwright-0.4.28/tests/test_issue_1357_pmi_frame.py +0 -171
  396. draftwright-0.4.28/tests/test_issue_1370_countersink_completeness_evidence.py +0 -534
  397. draftwright-0.4.28/tests/test_issue_1370_double_d_completeness_evidence.py +0 -706
  398. draftwright-0.4.28/tests/test_issue_1370_hole_pattern_completeness_evidence.py +0 -341
  399. draftwright-0.4.28/tests/test_issue_1371_flat_completeness_evidence.py +0 -285
  400. draftwright-0.4.28/tests/test_issue_1371_polygonal_stock_completeness_evidence.py +0 -1202
  401. draftwright-0.4.28/tests/test_issue_1372_groove_completeness_evidence.py +0 -686
  402. draftwright-0.4.28/tests/test_issue_1372_pad_completeness_evidence.py +0 -784
  403. draftwright-0.4.28/tests/test_issue_1372_pocket_completeness_evidence.py +0 -497
  404. draftwright-0.4.28/tests/test_issue_1372_pocket_pattern_completeness_evidence.py +0 -941
  405. draftwright-0.4.28/tests/test_issue_1372_polygonal_boss_completeness_evidence.py +0 -844
  406. draftwright-0.4.28/tests/test_issue_1373_plate_completeness_evidence.py +0 -1502
  407. draftwright-0.4.28/tests/test_issue_1374_chamfer_completeness_evidence.py +0 -559
  408. draftwright-0.4.28/tests/test_issue_1374_fillet_completeness_evidence.py +0 -525
  409. draftwright-0.4.28/tests/test_issue_1374_turned_step_completeness_evidence.py +0 -1527
  410. draftwright-0.4.28/tests/test_issue_1382_046_step_inventory.py +0 -66
  411. draftwright-0.4.28/tests/test_issue_1396_layout_advisories.py +0 -134
  412. draftwright-0.4.28/tests/test_issue_1432_oriented_slot_semantics.py +0 -1249
  413. draftwright-0.4.28/tests/test_issue_1438_policy_dispositions.py +0 -326
  414. draftwright-0.4.28/tests/test_issue_1438_report_projection.py +0 -922
  415. draftwright-0.4.28/tests/test_issue_1450_boss_blend_ownership.py +0 -345
  416. draftwright-0.4.28/tests/test_issue_1460_step_inspection.py +0 -796
  417. draftwright-0.4.28/tests/test_issue_1466_dimension_options.py +0 -311
  418. draftwright-0.4.28/tests/test_issue_1466_semantic_sides.py +0 -324
  419. draftwright-0.4.28/tests/test_issue_1471_boundary_failures.py +0 -231
  420. draftwright-0.4.28/tests/test_issue_1471_section_recess_pockets.py +0 -371
  421. draftwright-0.4.28/tests/test_issue_1479_radius_leader_targets.py +0 -273
  422. draftwright-0.4.28/tests/test_issue_1485_curved_pockets.py +0 -321
  423. draftwright-0.4.28/tests/test_issue_1485_rounded_pockets.py +0 -286
  424. draftwright-0.4.28/tests/test_issue_1504_angle_patterns.py +0 -219
  425. draftwright-0.4.28/tests/test_issue_1504_angular_references.py +0 -130
  426. draftwright-0.4.28/tests/test_issue_1504_angular_rendering.py +0 -322
  427. draftwright-0.4.28/tests/test_issue_1504_canonical_angles.py +0 -763
  428. draftwright-0.4.28/tests/test_issue_1505_short_axial_chains.py +0 -303
  429. draftwright-0.4.28/tests/test_issue_1511_turned_boss_heights.py +0 -340
  430. draftwright-0.4.28/tests/test_issue_1516_dimension_text.py +0 -213
  431. draftwright-0.4.28/tests/test_issue_1517_through_indicator.py +0 -473
  432. draftwright-0.4.28/tests/test_issue_1518_explicit_rear.py +0 -673
  433. draftwright-0.4.28/tests/test_issue_1536_title_overflow.py +0 -144
  434. draftwright-0.4.28/tests/test_issue_1542_catalog_families.py +0 -130
  435. draftwright-0.4.28/tests/test_issue_1542_claim_authority.py +0 -183
  436. draftwright-0.4.28/tests/test_issue_1542_document_claims.py +0 -268
  437. draftwright-0.4.28/tests/test_issue_1542_document_report.py +0 -389
  438. draftwright-0.4.28/tests/test_issue_1542_report_authority.py +0 -142
  439. draftwright-0.4.28/tests/test_issue_1543_schedule_requirements.py +0 -596
  440. draftwright-0.4.28/tests/test_issue_1544_frame_document_canary.py +0 -359
  441. draftwright-0.4.28/tests/test_issue_740_leader_assignment.py +0 -717
  442. draftwright-0.4.28/tests/test_issue_798_silhouette_lint.py +0 -342
  443. draftwright-0.4.28/tests/test_issue_883_location_identity.py +0 -575
  444. draftwright-0.4.28/tests/test_issue_909_sloped_profile.py +0 -191
  445. draftwright-0.4.28/tests/test_issue_915_dense_case.py +0 -202
  446. draftwright-0.4.28/tests/test_label_provenance.py +0 -193
  447. draftwright-0.4.28/tests/test_layout_cleanliness.py +0 -217
  448. draftwright-0.4.28/tests/test_layout_property.py +0 -201
  449. draftwright-0.4.28/tests/test_lint_structural.py +0 -557
  450. draftwright-0.4.28/tests/test_location_vocabulary.py +0 -400
  451. draftwright-0.4.28/tests/test_make_drawing.py +0 -11955
  452. draftwright-0.4.28/tests/test_overall_height_coverage.py +0 -199
  453. draftwright-0.4.28/tests/test_parameter_id.py +0 -706
  454. draftwright-0.4.28/tests/test_pmi.py +0 -1619
  455. draftwright-0.4.28/tests/test_pmi_datum_lowering.py +0 -463
  456. draftwright-0.4.28/tests/test_pmi_gtol_lowering.py +0 -244
  457. draftwright-0.4.28/tests/test_pmi_part21.py +0 -634
  458. draftwright-0.4.28/tests/test_private_test_attr_reads.py +0 -301
  459. draftwright-0.4.28/tests/test_private_test_imports.py +0 -196
  460. draftwright-0.4.28/tests/test_projection_symbol_default.py +0 -164
  461. draftwright-0.4.28/tests/test_recogniser_capabilities.py +0 -1663
  462. draftwright-0.4.28/tests/test_refactor_golden.py +0 -437
  463. draftwright-0.4.28/tests/test_render_seam.py +0 -429
  464. draftwright-0.4.28/tests/test_sheet_emit.py +0 -4388
  465. draftwright-0.4.28/tests/test_sheet_identity_invariant.py +0 -913
  466. draftwright-0.4.28/tests/test_slot_completeness.py +0 -691
  467. draftwright-0.4.28/tests/test_step_analysis_evaluation.py +0 -416
  468. draftwright-0.4.28/tests/test_strip_layout.py +0 -1588
  469. draftwright-0.4.28/tests/test_version_bump_ci.py +0 -250
  470. draftwright-0.4.28/tests/test_workflows.py +0 -525
  471. {draftwright-0.4.28 → draftwright-0.4.30}/.gitignore +0 -0
  472. {draftwright-0.4.28 → draftwright-0.4.30}/LICENSE +0 -0
  473. {draftwright-0.4.28 → draftwright-0.4.30}/docs/research/1062-repeating-radial-profile-evidence.md +0 -0
  474. {draftwright-0.4.28 → draftwright-0.4.30}/skills/SKILL.md +0 -0
  475. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/__init__.py +0 -0
  476. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/_build_profile.py +0 -0
  477. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/_geometry.py +0 -0
  478. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/_warnings.py +0 -0
  479. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/angular_geometry.py +0 -0
  480. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/annotate.py +0 -0
  481. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/annotations/__init__.py +0 -0
  482. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/annotations/gears.py +0 -0
  483. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/audit.py +0 -0
  484. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/blend_contract.py +0 -0
  485. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/document.py +0 -0
  486. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/document_evidence.py +0 -0
  487. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/document_input.py +0 -0
  488. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/evaluation/__init__.py +0 -0
  489. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/fits.py +0 -0
  490. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/fonts/IBMPlexMono-Regular.ttf +0 -0
  491. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/fonts/IBMPlexSansCondensed-Regular.ttf +0 -0
  492. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/fonts/LICENSE-IBMPlexMono-OFL.txt +0 -0
  493. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/fonts/LICENSE-IBMPlexSansCondensed-OFL.txt +0 -0
  494. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/fonts/__init__.py +0 -0
  495. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/inspection.py +0 -0
  496. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/intents.py +0 -0
  497. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/layout.py +0 -0
  498. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/linting/__init__.py +0 -0
  499. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/linting/angled_step_coverage.py +0 -0
  500. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/linting/blend_coverage.py +0 -0
  501. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/linting/chamfer_coverage.py +0 -0
  502. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/linting/channel_coverage.py +0 -0
  503. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/linting/coverage.py +0 -0
  504. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/linting/fillet_coverage.py +0 -0
  505. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/linting/flat_coverage.py +0 -0
  506. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/linting/gear_coverage.py +0 -0
  507. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/linting/groove_coverage.py +0 -0
  508. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/linting/ink_overlap.py +0 -0
  509. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/linting/issues.py +0 -0
  510. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/linting/oriented_slot_coverage.py +0 -0
  511. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/linting/pad_coverage.py +0 -0
  512. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/linting/paired_ramp_step_coverage.py +0 -0
  513. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/linting/passage_coverage.py +0 -0
  514. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/linting/plate_coverage.py +0 -0
  515. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/linting/pocket_coverage.py +0 -0
  516. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/linting/pocket_pattern_coverage.py +0 -0
  517. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/linting/polygonal_boss_coverage.py +0 -0
  518. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/linting/polygonal_stock_coverage.py +0 -0
  519. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/linting/prismatic_pocket_coverage.py +0 -0
  520. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/linting/profiled_bore_coverage.py +0 -0
  521. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/linting/rectangular_blind_slot_coverage.py +0 -0
  522. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/linting/round_bottom_blind_slot_coverage.py +0 -0
  523. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/linting/schedule_evidence.py +0 -0
  524. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/linting/slot_coverage.py +0 -0
  525. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/linting/suggest.py +0 -0
  526. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/linting/through_step_coverage.py +0 -0
  527. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/linting/turned_step_coverage.py +0 -0
  528. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/location_contract.py +0 -0
  529. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/make_drawing.py +0 -0
  530. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/measurement_support.py +0 -0
  531. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/oriented_slot_contract.py +0 -0
  532. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/plate_correspondence.py +0 -0
  533. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/profile_angles.py +0 -0
  534. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/progress.py +0 -0
  535. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/projection.py +0 -0
  536. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/py.typed +0 -0
  537. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/recogniser_policy.py +0 -0
  538. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/recogniser_schema.py +0 -0
  539. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/recognition/__init__.py +0 -0
  540. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/recognition_cache.py +0 -0
  541. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/recognition_frame.py +0 -0
  542. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/registry.py +0 -0
  543. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/reporting.py +0 -0
  544. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/score.py +0 -0
  545. {draftwright-0.4.28 → draftwright-0.4.30}/src/draftwright/view_plan.py +0 -0
  546. {draftwright-0.4.28 → draftwright-0.4.30}/tests/_kernel.py +0 -0
  547. {draftwright-0.4.28 → draftwright-0.4.30}/tests/_layout_sig.py +0 -0
  548. {draftwright-0.4.28 → draftwright-0.4.30}/tests/_mutation_corpus.py +0 -0
  549. {draftwright-0.4.28 → draftwright-0.4.30}/tests/_recogniser_public_contract.py +0 -0
  550. {draftwright-0.4.28 → draftwright-0.4.30}/tests/_section_recess_cases.py +0 -0
  551. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/ap242_single_cylinder_diameter.step +0 -0
  552. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/ambiguous-open-semicircle.step +0 -0
  553. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/blind-hole.step +0 -0
  554. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/chamfer-asymmetric.step +0 -0
  555. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/chamfer-compound.step +0 -0
  556. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/chamfer-overlap.step +0 -0
  557. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/chamfer-plain.step +0 -0
  558. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/chamfer-planar-x.step +0 -0
  559. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/chamfer-planar-y.step +0 -0
  560. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/chamfer-planar-z.step +0 -0
  561. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/chamfer-topology-a.step +0 -0
  562. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/chamfer-topology-b.step +0 -0
  563. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/chamfer-turned.step +0 -0
  564. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/corpus-chamfers-v1.json +0 -0
  565. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/corpus-countersinks-v1.json +0 -0
  566. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/corpus-double-d-bores-v1.json +0 -0
  567. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/corpus-fillets-v1.json +0 -0
  568. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/corpus-flats-v1.json +0 -0
  569. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/corpus-grooves-v1.json +0 -0
  570. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/corpus-plates-v1.json +0 -0
  571. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/corpus-pocket-patterns-v1.json +0 -0
  572. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/corpus-pockets-v1.json +0 -0
  573. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/corpus-polygonal-bosses-v1.json +0 -0
  574. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/corpus-polygonal-stock-v1.json +0 -0
  575. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/corpus-rectangular-pads-v1.json +0 -0
  576. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/corpus-turned-steps-v1.json +0 -0
  577. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/corpus-v1.json +0 -0
  578. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/countersink-deburr.step +0 -0
  579. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/countersink-external-cone.step +0 -0
  580. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/countersink-mixed-pair.step +0 -0
  581. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/countersink-single.step +0 -0
  582. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/countersink-topology-a.step +0 -0
  583. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/countersink-topology-b.step +0 -0
  584. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/double-d-axis-x.step +0 -0
  585. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/double-d-axis-y.step +0 -0
  586. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/double-d-blind.step +0 -0
  587. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/double-d-coaxial-compound.step +0 -0
  588. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/double-d-opposed-blind.step +0 -0
  589. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/double-d-roll-30.step +0 -0
  590. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/double-d-round-bore.step +0 -0
  591. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/double-d-single-z.step +0 -0
  592. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/double-d-topology-a.step +0 -0
  593. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/double-d-topology-b.step +0 -0
  594. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/fillet-compound.step +0 -0
  595. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/fillet-overlap.step +0 -0
  596. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/fillet-plain.step +0 -0
  597. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/fillet-planar-x.step +0 -0
  598. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/fillet-planar-y.step +0 -0
  599. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/fillet-planar-z.step +0 -0
  600. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/fillet-repeated.step +0 -0
  601. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/fillet-topology-a.step +0 -0
  602. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/fillet-topology-b.step +0 -0
  603. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/fillet-turned.step +0 -0
  604. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/flat-coaxial.step +0 -0
  605. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/flat-double-d.step +0 -0
  606. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/flat-lone-d.step +0 -0
  607. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/flat-nonround.step +0 -0
  608. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/flat-slanted-double-d.step +0 -0
  609. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/flat-topology-a.step +0 -0
  610. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/flat-topology-b.step +0 -0
  611. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/groove-compound.step +0 -0
  612. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/groove-lone-x.step +0 -0
  613. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/groove-lone-y.step +0 -0
  614. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/groove-lone-z.step +0 -0
  615. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/groove-monotonic-negative.step +0 -0
  616. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/groove-narrow.step +0 -0
  617. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/groove-topology-a.step +0 -0
  618. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/groove-topology-b.step +0 -0
  619. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/pad-compound-equal.step +0 -0
  620. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/pad-detached-body-negative.step +0 -0
  621. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/pad-full-span-ledge-negative.step +0 -0
  622. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/pad-nested-staircase-negative.step +0 -0
  623. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/pad-topology-a.step +0 -0
  624. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/pad-topology-b.step +0 -0
  625. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/pad-x-negative.step +0 -0
  626. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/pad-x-positive.step +0 -0
  627. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/pad-y-negative.step +0 -0
  628. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/pad-y-positive.step +0 -0
  629. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/pad-z-negative.step +0 -0
  630. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/pad-z-positive.step +0 -0
  631. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/pattern-ambiguous.step +0 -0
  632. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/pattern-grid.step +0 -0
  633. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/plain-block.step +0 -0
  634. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/plate-compound-equal.step +0 -0
  635. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/plate-cross-topology-a.step +0 -0
  636. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/plate-cross-topology-b.step +0 -0
  637. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/plate-detached-negative.step +0 -0
  638. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/plate-rotational-negative.step +0 -0
  639. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/plate-single-negative.step +0 -0
  640. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/plate-t-xz.step +0 -0
  641. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/plate-t-yz.step +0 -0
  642. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/plate-thick-negative.step +0 -0
  643. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/plate-u-additive.step +0 -0
  644. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/plate-u-cut.step +0 -0
  645. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/pocket-compound.step +0 -0
  646. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/pocket-edge-anchored.step +0 -0
  647. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/pocket-lone.step +0 -0
  648. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/pocket-opposed.step +0 -0
  649. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/pocket-pattern-ambiguous.step +0 -0
  650. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/pocket-pattern-grid.step +0 -0
  651. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/pocket-pattern-linear.step +0 -0
  652. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/pocket-pattern-pair.step +0 -0
  653. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/pocket-pattern-topology-a.step +0 -0
  654. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/pocket-pattern-topology-b.step +0 -0
  655. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/pocket-prismatic-negative.step +0 -0
  656. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/pocket-side.step +0 -0
  657. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/pocket-through-negative.step +0 -0
  658. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/pocket-topology-a.step +0 -0
  659. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/pocket-topology-b.step +0 -0
  660. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/pocket-two-equal.step +0 -0
  661. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/polygonal-boss-compound-equal.step +0 -0
  662. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/polygonal-boss-detached-negative.step +0 -0
  663. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/polygonal-boss-in-plane-rotated.step +0 -0
  664. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/polygonal-boss-other-protrusions-negative.step +0 -0
  665. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/polygonal-boss-recess-negative.step +0 -0
  666. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/polygonal-boss-stock-negative.step +0 -0
  667. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/polygonal-boss-topology-a.step +0 -0
  668. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/polygonal-boss-topology-b.step +0 -0
  669. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/polygonal-boss-x.step +0 -0
  670. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/polygonal-boss-y.step +0 -0
  671. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/polygonal-boss-z.step +0 -0
  672. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/polygonal-stock-compound-negative.step +0 -0
  673. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/polygonal-stock-cylinder-negative.step +0 -0
  674. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/polygonal-stock-irregular-negative.step +0 -0
  675. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/polygonal-stock-octagon-negative.step +0 -0
  676. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/polygonal-stock-recess-negative.step +0 -0
  677. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/polygonal-stock-roll17.step +0 -0
  678. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/polygonal-stock-topology-a.step +0 -0
  679. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/polygonal-stock-topology-b.step +0 -0
  680. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/polygonal-stock-translated.step +0 -0
  681. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/polygonal-stock-x.step +0 -0
  682. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/polygonal-stock-y.step +0 -0
  683. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/topology-a.step +0 -0
  684. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/topology-b.step +0 -0
  685. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/turned-step-axis-x.step +0 -0
  686. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/turned-step-axis-y.step +0 -0
  687. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/turned-step-axis-z.step +0 -0
  688. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/turned-step-compound.step +0 -0
  689. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/turned-step-repeated-lengths.step +0 -0
  690. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/turned-step-through-bore.step +0 -0
  691. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/turned-step-topology-a.step +0 -0
  692. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/turned-step-topology-b.step +0 -0
  693. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/evaluation/turned-step-translated-blind-bore.step +0 -0
  694. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/grm03_thumbwheel_drive_screw.step +0 -0
  695. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/grm03_thumbwheel_drive_screw_ap242_pmi.step +0 -0
  696. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/grm04_drive_plate.step +0 -0
  697. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/if_step_flat_across_cylinder.step +0 -0
  698. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/issue_1058_wheel_rh.step +0 -0
  699. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/issue_1247_angled_blind_step.step +0 -0
  700. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/issue_909_basic_part_design_017_body.step +0 -0
  701. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/issue_915_case_study_2.step +0 -0
  702. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/nist_ctc_01_asme1_ap203.stp +0 -0
  703. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/nist_ctc_01_asme1_ap242.stp +0 -0
  704. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/nist_ctc_02_asme1_ap203.stp +0 -0
  705. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/nist_ctc_02_asme1_ap242.stp +0 -0
  706. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/nist_ctc_03_asme1_ap203.stp +0 -0
  707. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/nist_ctc_03_asme1_ap242.stp +0 -0
  708. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/nist_ctc_04_asme1_ap203.stp +0 -0
  709. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/nist_ctc_04_asme1_ap242.stp +0 -0
  710. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/nist_ctc_05_asme1_ap203.stp +0 -0
  711. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/nist_ctc_05_asme1_ap242.stp +0 -0
  712. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/tuner_jig_blind_obround_pockets.step +0 -0
  713. {draftwright-0.4.28 → draftwright-0.4.30}/tests/fixtures/whistle_frame_reference.step +0 -0
  714. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_adr_corpus.py +0 -0
  715. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_agents_guide.py +0 -0
  716. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_api_docs.py +0 -0
  717. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_architecture_docs.py +0 -0
  718. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_balloon_ring_standoff.py +0 -0
  719. /draftwright-0.4.28/tests/test_issue_1438_boss_ownership.py → /draftwright-0.4.30/tests/test_boss_ownership.py +0 -0
  720. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_build_profile_harness.py +0 -0
  721. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_carve_free_position_callers.py +0 -0
  722. /draftwright-0.4.28/tests/test_issue_1438_channel_ownership.py → /draftwright-0.4.30/tests/test_channel_ownership.py +0 -0
  723. /draftwright-0.4.28/tests/test_issue_1382_circular_blind_step_semantics.py → /draftwright-0.4.30/tests/test_circular_blind_step_semantics.py +0 -0
  724. /draftwright-0.4.28/tests/test_issue_1438_cli_report_sidecar.py → /draftwright-0.4.30/tests/test_cli_report_sidecar.py +0 -0
  725. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_counting_calls.py +0 -0
  726. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_declared_recognition_gate.py +0 -0
  727. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_dense_sheet_canary.py +0 -0
  728. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_detect_once.py +0 -0
  729. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_detect_registry.py +0 -0
  730. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_dimension_role_vocabulary.py +0 -0
  731. /draftwright-0.4.28/tests/test_issue_1542_document.py → /draftwright-0.4.30/tests/test_document_build.py +0 -0
  732. /draftwright-0.4.28/tests/test_issue_1542_document_coverage.py → /draftwright-0.4.30/tests/test_document_coverage.py +0 -0
  733. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_e2e_slice.py +0 -0
  734. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_external_recognition_boundary.py +0 -0
  735. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_fillets_adjacency.py +0 -0
  736. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_flat_completeness.py +0 -0
  737. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_flat_stock_opposition.py +0 -0
  738. /draftwright-0.4.28/tests/test_issue_1382_framed_step_family_evidence.py → /draftwright-0.4.30/tests/test_framed_step_family_evidence.py +0 -0
  739. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_gdt_placement.py +0 -0
  740. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_geometry_graph_spike.py +0 -0
  741. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_grid_lattice_convention.py +0 -0
  742. /draftwright-0.4.28/tests/test_issue_1471_groove_profile.py → /draftwright-0.4.30/tests/test_groove_profile.py +0 -0
  743. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1000_envelope_reuse.py +0 -0
  744. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1073_cross_body_patterns.py +0 -0
  745. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1082_polygonal_stock.py +0 -0
  746. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1126_requesting_less_never_scores_better.py +0 -0
  747. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1130_view_planning_evidence.py +0 -0
  748. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1132_turned_profile_span_is_reported.py +0 -0
  749. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1142_hole_leader_labels.py +0 -0
  750. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1144_transactional_hole_table.py +0 -0
  751. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1147_legibility_pair_aggregation.py +0 -0
  752. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1153_contradictory_dimensions.py +0 -0
  753. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1154_one_owner_per_measurement.py +0 -0
  754. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1155_grm04_sheet_use.py +0 -0
  755. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1157_no_implicit_general_tolerance.py +0 -0
  756. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1188_per_view_assignment.py +0 -0
  757. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1196_deterministic_view_names.py +0 -0
  758. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1202_observed_drawing_consumer.py +0 -0
  759. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1204_multiscale_view_issues.py +0 -0
  760. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1216_callout_reservation_measures_ink.py +0 -0
  761. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1216_pairwise_across_scale_groups.py +0 -0
  762. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1219_location_claims_its_own_axis.py +0 -0
  763. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1229_ledger_member_keying.py +0 -0
  764. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1240_iso_above_strip_visibility.py +0 -0
  765. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1245_passage_disposition.py +0 -0
  766. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1247_angled_step_disposition.py +0 -0
  767. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1254_turned_chamfers.py +0 -0
  768. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1260_view_constraints.py +0 -0
  769. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1262_automatic_turned_views.py +0 -0
  770. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1276_turned_leader_targets.py +0 -0
  771. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1281_turned_fillets.py +0 -0
  772. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1298_manufacturing_requirements.py +0 -0
  773. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1312_engine_costs.py +0 -0
  774. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1332_overlap_remedy.py +0 -0
  775. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1332_zone_labels.py +0 -0
  776. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1334_prevent_ink.py +0 -0
  777. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1336_grm03_ap242_pmi_fixture.py +0 -0
  778. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1350_detected_takeover.py +0 -0
  779. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1351_structured_note_coverage.py +0 -0
  780. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1353_cnc_authoritative_workflow.py +0 -0
  781. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1357_framed_boundary.py +0 -0
  782. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1357_plural_turned_profiles.py +0 -0
  783. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1360_thread_depth.py +0 -0
  784. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1369_hole_completeness_evidence.py +0 -0
  785. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1378_diameter_leader_targets.py +0 -0
  786. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1390_sheet_registration.py +0 -0
  787. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1392_recognisers_048.py +0 -0
  788. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1395_degenerate_iso_region.py +0 -0
  789. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1397_unverifiable_requirement_message.py +0 -0
  790. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1421_rectangular_blind_slot_completeness.py +0 -0
  791. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1421_rectangular_blind_slot_semantics.py +0 -0
  792. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1421_round_bottom_blind_slot_completeness.py +0 -0
  793. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1421_round_bottom_blind_slot_semantics.py +0 -0
  794. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1432_adoption.py +0 -0
  795. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1432_boundary_failures.py +0 -0
  796. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1433_blend_semantics.py +0 -0
  797. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1485_quiddity_024_boundary.py +0 -0
  798. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1505_diameter_coverage.py +0 -0
  799. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1514_projection_safeguard.py +0 -0
  800. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1515_first_angle.py +0 -0
  801. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1524_tolerance_magnitude.py +0 -0
  802. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1530_section_provenance.py +0 -0
  803. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1531_semantic_hole_counts.py +0 -0
  804. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1532_authored_section_examples.py +0 -0
  805. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1533_review_diagnostics.py +0 -0
  806. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1534_build_progress.py +0 -0
  807. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1543_feature_schedules.py +0 -0
  808. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1555_turned_recognition_format_invariance.py +0 -0
  809. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_1563_sheet_pmi_reconciliation.py +0 -0
  810. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_367_leader_ink.py +0 -0
  811. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_443_grm03_axial_coverage.py +0 -0
  812. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_563_placement_intent.py +0 -0
  813. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_676_polygonal_boss.py +0 -0
  814. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_676_polygonal_boss_declare.py +0 -0
  815. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_798_floor_cardinality.py +0 -0
  816. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_798_material_field.py +0 -0
  817. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_827_real_part_canary.py +0 -0
  818. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_885_prismatic_coverage.py +0 -0
  819. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_916_pocket_leader.py +0 -0
  820. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_917_open_channel.py +0 -0
  821. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_924_zero_length_shoulders.py +0 -0
  822. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_955_height_contingency.py +0 -0
  823. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_issue_958_cross_solid_recognition.py +0 -0
  824. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_layout.py +0 -0
  825. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_layout_hypothesis.py +0 -0
  826. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_leader_footprint.py +0 -0
  827. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_lint_box_cache.py +0 -0
  828. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_lint_ink_overlap.py +0 -0
  829. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_lint_reconciliation.py +0 -0
  830. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_linting.py +0 -0
  831. /draftwright-0.4.28/tests/test_issue_1438_nested_ownership.py → /draftwright-0.4.30/tests/test_nested_recognition_ownership.py +0 -0
  832. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_note_verb.py +0 -0
  833. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_object_aspects.py +0 -0
  834. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_occupancy_boxes.py +0 -0
  835. /draftwright-0.4.28/tests/test_issue_1438_occurrence_ownership.py → /draftwright-0.4.30/tests/test_occurrence_recognition_ownership.py +0 -0
  836. /draftwright-0.4.28/tests/test_issue_1382_paired_ramp_semantics.py → /draftwright-0.4.30/tests/test_paired_ramp_semantics.py +0 -0
  837. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_part_model.py +0 -0
  838. /draftwright-0.4.28/tests/test_issue_1438_pattern_ownership.py → /draftwright-0.4.30/tests/test_pattern_ownership.py +0 -0
  839. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_pitch_dim_footprint.py +0 -0
  840. /draftwright-0.4.28/tests/test_issue_1438_plate_ownership.py → /draftwright-0.4.30/tests/test_plate_ownership.py +0 -0
  841. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_pocket_pattern.py +0 -0
  842. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_pocket_pattern_recognition.py +0 -0
  843. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_principal_profile_classifier.py +0 -0
  844. /draftwright-0.4.28/tests/test_issue_1504_profile_angles.py → /draftwright-0.4.30/tests/test_profile_angles.py +0 -0
  845. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_quality_components.py +0 -0
  846. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_recogniser_adoption.py +0 -0
  847. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_recogniser_contract.py +0 -0
  848. /draftwright-0.4.28/tests/test_issue_1471_evidence_schema.py → /draftwright-0.4.30/tests/test_recognition_evidence_schema.py +0 -0
  849. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_recognition_result.py +0 -0
  850. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_registry.py +0 -0
  851. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_repack_geometry_seam.py +0 -0
  852. /draftwright-0.4.28/tests/test_issue_1438_report_writer.py → /draftwright-0.4.30/tests/test_report_writer.py +0 -0
  853. /draftwright-0.4.28/tests/test_issue_1542_applicability.py → /draftwright-0.4.30/tests/test_requirement_applicability.py +0 -0
  854. /draftwright-0.4.28/tests/test_issue_1542_requirement_carriers.py → /draftwright-0.4.30/tests/test_requirement_carriers.py +0 -0
  855. /draftwright-0.4.28/tests/test_issue_1542_catalog.py → /draftwright-0.4.30/tests/test_requirement_catalog.py +0 -0
  856. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_score.py +0 -0
  857. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_script_detail_parity.py +0 -0
  858. /draftwright-0.4.28/tests/test_issue_1471_section_recess_contract.py → /draftwright-0.4.30/tests/test_section_recess_contract.py +0 -0
  859. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_shared_box_memo.py +0 -0
  860. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_sheet_gdt.py +0 -0
  861. /draftwright-0.4.28/tests/test_issue_1438_generation_snapshot.py → /draftwright-0.4.30/tests/test_sheet_generation_snapshot.py +0 -0
  862. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_sheet_notes.py +0 -0
  863. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_sheet_of.py +0 -0
  864. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_sheet_section.py +0 -0
  865. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_sheet_tables.py +0 -0
  866. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_slanted_blind_step.py +0 -0
  867. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_slot_pattern.py +0 -0
  868. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_slot_pattern_recognition.py +0 -0
  869. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_soft_deprecation.py +0 -0
  870. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_solve_trace.py +0 -0
  871. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_suppression_ledger.py +0 -0
  872. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_suppression_marks.py +0 -0
  873. /draftwright-0.4.28/tests/test_issue_1438_through_step_ownership.py → /draftwright-0.4.30/tests/test_through_step_ownership.py +0 -0
  874. /draftwright-0.4.28/tests/test_issue_1382_through_step_semantics.py → /draftwright-0.4.30/tests/test_through_step_semantics.py +0 -0
  875. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_tolerances.py +0 -0
  876. /draftwright-0.4.28/tests/test_issue_1438_turned_step_ownership.py → /draftwright-0.4.30/tests/test_turned_step_ownership.py +0 -0
  877. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_turned_steps.py +0 -0
  878. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_view_plan.py +0 -0
  879. {draftwright-0.4.28 → draftwright-0.4.30}/tests/test_witness_label_reconciliation.py +0 -0
@@ -0,0 +1,3104 @@
1
+ # Changelog
2
+
3
+ ## Unreleased
4
+
5
+ ### Added
6
+
7
+ - Circular-seat recognition, declared features, and generated-script replay (#1613).
8
+ Diameter, open run, arc sweep, and three datum-to-axis coordinates retain each physical
9
+ seat's measurement identities, including counted coaxial callouts and shared axis marks.
10
+ - Opt-in independent sheet margins and exact title-block width on `Sheet`, `build_drawing`,
11
+ `make_drawing`, and the CLI, including generated-script replay (#1612, #1589). Sergio's
12
+ 25/10/10/10 mm margins and 175 mm block are explicit options; existing defaults remain.
13
+
14
+ ### Fixed
15
+
16
+ - Direct and generated builds use the same final IR step ladder for page layout (#1592).
17
+ Raw face levels owned by another feature no longer reserve a phantom step corridor at
18
+ the title-block clearance threshold.
19
+
20
+ - Location intents now accept `.format(decimals=...)` for all selected directions (#1610),
21
+ including plan locations, side-drilled holes and slots. Compiler-owned labels reach
22
+ placement footprints, live location edits and generated scripts. Coincident marks with
23
+ incompatible explicit display text are refused instead of overriding an intent.
24
+
25
+ - Explicit dimension precision, including `.format(decimals=0)`, now reaches geometric
26
+ label critique (#1620). Correct rounding is reported as information; labels outside
27
+ the requested rounding interval still produce `label_vs_measured`. Height and step
28
+ ladders retain the same compiler policy as ordinary dimensions.
29
+
30
+ - **`label_vs_measured` no longer reports the drawing's own rounding as a topology error
31
+ (#1600).** A sheet at one decimal place prints 4.450 as `4.5`; the check then compared
32
+ `4.5` against 4.450 and said "possible axis swap or wrong endpoint". The comparison was
33
+ *relative*, so the same 0.05 mm of rounding passed silently on a 100 mm dimension and was
34
+ an error on a 4 mm one — it fired on smallness, not on wrongness.
35
+
36
+ A label now states its own precision: `4.5` shows one place, `4.45` two, `12` none. A
37
+ difference within half of the last displayed place is the label doing its job; anything
38
+ beyond it is still reported, and an axis swap or wrong endpoint misses by far more.
39
+
40
+ This is a prerequisite for #1602's proposed CI gate: `label_vs_measured` sits in the
41
+ register of codes meaning *the sheet says something untrue*, and a code that fires on
42
+ correct rounding cannot be hard-failed on.
43
+
44
+ ### Added
45
+
46
+ - **`nominal_rounded` says where the sheet's precision loses the model value.** Silencing
47
+ the false alarm above left nothing reporting it at all, and `4.5` for 4.450 is still a
48
+ real 0.05 mm between drawing and model — on a part carrying 0.05 mm clearances, the whole
49
+ clearance. One `info` per sheet, naming the worst case:
50
+
51
+ > 7 dimension(s) print a nominal the model does not have, rounded to the sheet's
52
+ > precision; the largest is '15.7' for 15.6500 (0.0500 mm). Within a general tolerance
53
+ > this is ordinary; where a fit depends on it, raise that dimension's places with
54
+ > `.format(decimals=...)`
55
+
56
+ `.format(decimals=…)` cannot yet reach a **location** dimension (#1610), and on the
57
+ fixture this was measured against six of the seven offenders are locations — so the
58
+ message says so rather than prescribing a remedy that raises `ValueError`.
59
+
60
+ It does **not** guess, and does not change any rounding. `4.450` locating a hinge axis is
61
+ design intent; `71.595` as an overall envelope is parametric fallout no drafter would
62
+ print in full — and telling them apart needs to know what the part is *for*, which a STEP
63
+ file does not carry (the same gap as #1597). So the engine reports and the author decides:
64
+ `.format(decimals=…)` already sets places per dimension and survives the `--script` round
65
+ trip.
66
+
67
+ Measured: 7 of 25 numeric dimension labels on `whistle_frame_reference.step`; silent on
68
+ every golden fixture, all of which are built from round numbers.
69
+
70
+ ### Fixed
71
+ - Adopt published Quiddity 0.2.9 (#1619). The frame document now retains six separate
72
+ hinge bores instead of a single path spanning air gaps. Three cylindrical seat occurrences
73
+ are reported explicitly; their drawing grammar remains pending #1613.
74
+ - Refused hole patterns retain their members in the hole-completeness ledger (#1607).
75
+ The adapter records the refusal against the exact recognition run, so a six-hole group
76
+ is no longer reported as an unverifiable four-hole pattern and two loose holes. Declared
77
+ pattern mismatches still undergo the independent correspondence checks.
78
+
79
+ - **A required dimension with nowhere to go now re-plans the sheet instead of being
80
+ reported and left (#1590).** The automatic recovery ladder — larger scales on the
81
+ selected page, then dropping the optional pictorial, then a larger page — already
82
+ existed and already refused any candidate that had not fixed the problem. It ran on
83
+ two symptoms: a turned part missing an axial station, and an **authored** intent that
84
+ failed to place. A required *envelope* dimension that found no room was neither, so
85
+ these drawings never entered the ladder at all — `scale_decision["attempts"]` came
86
+ back empty and an `overall_dim_withheld` error was reported on the first sheet tried.
87
+
88
+ `overall_dim_withheld` is deliberately not a scale blocker, and that is right:
89
+ reporting it through `placement_unsatisfiable` once made `build_drawing(part,
90
+ scale=...)` raise on parts that had always built. But that argument is about refusing
91
+ the scale a **caller asked for**. Choosing one automatically is a different question,
92
+ and one predicate was answering both.
93
+
94
+ What it changes, measured:
95
+
96
+ | part | before | after |
97
+ |---|---|---|
98
+ | stepped `Box(80, 60, 30)` | A4 1:1, overall depth withheld (error) | A4 1:1, **clean**, pictorial dropped |
99
+ | 80 × 60 plate, two close bores | A4 1:1, `incomplete`: unlocated hole, dropped location, withheld depth | A2 2:1, **clean**, every dimension placed |
100
+
101
+ **ADR 2 invariant 13 is amended** to match. It read "the optional isometric yields only
102
+ to close missing axial coverage", which had been one trigger behind the code since
103
+ 2026-08-23 — the record was consolidated twelve days later and transcribed the narrower
104
+ claim. It now states the principle the triggers share: the isometric yields only to
105
+ preserve manufacturing completeness, never for space, crowding or preference. The list
106
+ itself is `builder._ISO_YIELD_TRIGGERS`, and a guard fails if it changes, so the next
107
+ widening is caught by a test rather than by a reader.
108
+
109
+ Dropping the optional pictorial is the first lever, which is the order #443/#1299
110
+ already argue for — a pictorial cannot outrank a dimension needed to manufacture the
111
+ part. Pin `page=` or `scale=` to opt out; a pinned sheet still reports honestly rather
112
+ than replanning.
113
+
114
+ - **A generated `--script` reproduces a re-planned layout.** A declared build does what
115
+ it is told and never enters the recovery ladder, so `generate_sheet_script` pins the
116
+ settled page, scale and view set into the script. It used to do that only for the two
117
+ families whose replan could be predicted from the model; nothing predicts the new
118
+ trigger, because whether a mark fits is a fact about a measured sheet. The reference
119
+ build is now unconditional — one extra `build_drawing` per generated script, measured
120
+ at 0.19 s for a plain box, 0.34 s for a pocket and 1.17 s for a part that actually
121
+ replans — and an **undrawable** source can never be the reason a script is not
122
+ written, the standard the inspection sidecar beside it already meets. A deliberate
123
+ refusal (an unknown page size, `ScaleIncompatibilityError`, `ViewPlanIncomplete`)
124
+ still reaches the caller.
125
+
126
+ A script that keeps `auto_dimensions()` is not handed a pinned view set: `Sheet`
127
+ refuses `authored_views()` beside it, so the two together wrote a script that raised
128
+ when run. The settled set is reported in a comment instead.
129
+
130
+ - **A bolt circle is no longer stated unless something other than the fit supports it
131
+ (#1596).** `EQ SP ON ø… BC` tells a machinist to work from a centre; #1595 found a real
132
+ part where that centre was in mid-air. Six ⌀2.4 holes in a 2×3 rectangular grid came out
133
+ as `4× ⌀2.4 THRU EQ SP ON ø34.4 BC` plus an unrelated `2× ⌀2.4 THRU`, the circle centred
134
+ on (0, −20.11) — nothing is concentric with it.
135
+
136
+ The fit was not a near miss. **Any three non-collinear points are concyclic, and so are the
137
+ four corners of any rectangle** — for those arrangements the residual is zero by
138
+ construction, so no tolerance on it can help. Nor does equal spacing rescue it: that
139
+ rectangle is near-square (24.30 × 24.38), so its corners sit 90.19° / 89.81° apart and
140
+ would pass an `EQ SP` check too.
141
+
142
+ What gives it away is the leftover callout beside it. Corroboration must now come from
143
+ somewhere the fit cannot reach — any one of:
144
+
145
+ 1. **five or more members** — a circle through five holes could have failed to fit;
146
+ 2. **a concentric physical feature** — a boss, spigot, central bore, or a round body whose
147
+ own axis is the circle's, something a machinist can indicate off;
148
+ 3. **it accounts for every hole of its kind** — no identical hole left off the circle.
149
+
150
+ (3) is the discriminator here: six identical holes and the circle claimed four. A drawing
151
+ does not put two of six bolts on a different plan. It is also a question about the part
152
+ rather than about its angle to the axes, unlike the row/column test it replaced.
153
+
154
+ Refused patterns keep every hole: the members fall through to ordinary grouping, so the
155
+ #1595 part now reads `6× ⌀2.4 THRU` — which also repairs the fragmentation into 4 + 2.
156
+
157
+ **What refusing costs, measured.** `hole_requirement_outcomes` builds its pattern-member
158
+ set from the recogniser's patterns rather than the ones the IR adopted, so after a refusal
159
+ it keeps those holes out of the loose groups, finds no `PatternFeature` to join, and warns
160
+ `hole_requirement_unverifiable` about holes the sheet states correctly — two on this part,
161
+ 26 issues against 23. The pre-existing oblique-pattern refusal (#971) has the same hole and
162
+ was simply never reachable; #1596 makes it so. Tracked as **#1607** with three attempted
163
+ fixes recorded, and guarded by an `xfail(strict)` here.
164
+
165
+ Refused at the recognition→IR adapter, not in the recogniser: a circle through those holes
166
+ is genuinely findable, and ADR 3 leaves the recogniser to report the geometry it finds.
167
+ Whether it may be *stated as a drafting datum* is drafting policy, which is draftwright's.
168
+
169
+ ### Changed
170
+
171
+ - **The title block carries every ISO 7200:2004 mandatory data field.** Three had
172
+ no representation at all before — segment/sheet number (5.1.6), approval person
173
+ (5.3.4) and document type (5.3.6) — and are now `sheet=`, `approved_by=` and
174
+ `document_type=` on `build_drawing`, `make_drawing`, `Sheet`, the CLI and the
175
+ `--script` emitter. Cell widths come from the standard's own recommended
176
+ character counts (via `build123d-drafting-helpers>=0.15.4`), so they follow ISO
177
+ 7200 and the font rather than a chosen proportion.
178
+
179
+ Eleven fields need four rows, so **the block is 32 mm tall rather than 16**, and
180
+ the band reserved for it goes 35 → 40 mm. `material` and `general_tolerance`
181
+ stay although neither is an ISO 7200 title-block field; they are on every real
182
+ drawing.
183
+
184
+ - **The scale is stated outside the title block**, beside the projection symbol,
185
+ per ISO 7200 §4 ("presented outside the title block only when used, e.g. scale,
186
+ projection symbol"). It is drawn unconditionally, and the new **`scale_not_stated`
187
+ error** checks the rendered sheet rather than trusting the code that draws it.
188
+ The `SCALE` cell is gone from the block.
189
+
190
+ **Two consequences worth knowing before you upgrade:**
191
+
192
+ - A dense sheet may escalate a page size. In the golden corpus one fixture of
193
+ fourteen (`flange_dense`) moves A4 → A3; the other thirteen keep their page
194
+ and scale, and a sweep of ten ordinary shapes showed no change at all.
195
+ - **Dimension placement now avoids the title block (#1593)**, which it never
196
+ did: the block is drawn after placement, so only GD&T frames ever avoided it
197
+ (#481). A two-row block kept that out of reach; a four-row one does not.
198
+ `build_drawing(Box(40, 30, 12))` used to run its `30` dimension 2.1 mm into
199
+ the block with `lint()` reporting a clean sheet; a stepped
200
+ `Box(80, 60, 30)` put the whole `60` label inside it, reported only as an
201
+ `annotation_overlap` between the labels `60` and `DRAWING`.
202
+
203
+ Most displaced dimensions move to the view's opposite strip and stay on the
204
+ sheet. Where both strips are full the dimension is **withheld as an error
205
+ naming the block**, rather than drawn through it: in the golden corpus
206
+ `pocketed` and `prismatic_ladder` lose their overall depth at the automatic
207
+ A4/1:1, and place it on A3 or at 1:2. Likewise a **pinned** A4 for a
208
+ five-step turned shaft no longer reaches 5:1, because at that scale the front
209
+ view runs down past the block and the overall height cannot be placed; the
210
+ 2:1 sheet is returned with `axial_length_missing` stated. Leaving the page
211
+ automatic still carries every station, on A3 at 5:1.
212
+
213
+ Choosing a page and scale without knowing which dimensions will still need
214
+ room is #1590.
215
+
216
+ ### Changed
217
+
218
+ - **Exports are reproducible by default.** `reproducible` now defaults to `True`
219
+ on `build_drawing`, `make_drawing`, `Drawing` and `Drawing.export`, so two
220
+ exports of one drawing are byte-identical without asking. Pass
221
+ `reproducible=False` to opt out.
222
+
223
+ It was opt-in on a cost argument, and the cost turns out to be small at the
224
+ scale most drawings are made: on the NIST CTC-01 AP242 fixture, +0.27 s on a
225
+ 13.4 s job (+3.2% of export, +2.0% overall). The +32% of export measured when
226
+ the flag was introduced was a 358-part assembly; the ordering work is one
227
+ bounding box and one edge walk per part, so it scales with part count and a
228
+ part-heavy sheet is still the case to opt out of.
229
+
230
+ Two things pushed the default over. A file that changes between runs cannot be
231
+ diffed, checksummed or cached — the reason to want it is not speed-shaped. And
232
+ neither the CLI nor `Sheet` exposes the flag at all, so every drawing made
233
+ through those surfaces was non-reproducible with no way to change it.
234
+
235
+ One behaviour follows from the default rather than from this change: a DXF
236
+ export that cannot reach the ezdxf document to pin its metadata now raises
237
+ rather than writing live data, because every export now claims
238
+ reproducibility. Opting out restores the old fallback.
239
+ ### Fixed
240
+
241
+ - **A supplied title-block `date` is stated on the sheet** (#1585). `TitleBlock`
242
+ could show only one of `revision`/`date` in its top-right cell and revision
243
+ won, while draftwright defaults `revision` to `"A"` — so every drawing that
244
+ set a date lost it, silently, in every version that has had the parameter.
245
+ Adopting `build123d-drafting-helpers>=0.15.4` gives the date a cell of its
246
+ own whenever a revision is also set, and `_make_title_block` now names that
247
+ cell, so the date reaches the PDF text layer and the cell-overflow lint
248
+ rather than only the rendered geometry.
249
+
250
+ `date`, `revision` and `company` are stripped before use, matching what the
251
+ block itself does: a whitespace-only `company` previously raised `KeyError`
252
+ from `cell_bbox`, and padded values were measured at a width the sheet never
253
+ drew. (The DRAWN BY narrowing this entry originally described, and the shared
254
+ top-right cell it named, are both gone with the ISO 7200 layout above — the
255
+ date has its own cell in its own row, so it costs DRAWN BY nothing.)
256
+
257
+
258
+ ### Added
259
+
260
+ - Report schema v1 now projects one recognition-owned physical requirement ledger shared with
261
+ completeness. Each bounded requirement identifies its accepted source occurrence(s), final IR
262
+ owner(s), semantic parameter where applicable, placement/satisfaction state, annotation names,
263
+ and representation rationale. The roster is evaluated once per report and fails closed when
264
+ exact same-run evidence or provenance is absent; it does not create a second recognition scan,
265
+ persistent topology identity, inferred manufacturing intent, or visual change (#1438, ADR 0017
266
+ Amendment 26).
267
+
268
+ - Direct CLI rendering now writes `<output>.draftwright.json` by default, after successful visual
269
+ export, and prints its path with the requested artefacts. `--no-report` is the explicit opt-out;
270
+ library `Drawing.export()` remains free of report-writing side effects (#1438, ADR 0017
271
+ Amendment 25).
272
+
273
+ - Generated Sheet Python now embeds a deterministic, versioned, JSON-compatible snapshot of
274
+ actionable accepted-occurrence gaps from the exact recognition run that produced its model.
275
+ The compact snapshot carries only unsupported, deferred, evidence-only, and unexpectedly
276
+ missing public records; STEP inputs are read once and recognition uses a private copy of the
277
+ exact bytes named by the embedded basename and SHA-256, with a best-effort final replay-target
278
+ check before writing. The hash is a later reconciliation boundary, not a lock on the mutable
279
+ source path. It is explicitly generation-time evidence, never current completeness authority
280
+ (#1438, ADR 0017 Amendment 24).
281
+
282
+ - Accepted Plate occurrences now retain exact conditional ownership. Genuine multi-axis plates
283
+ keep their direct `PlateFeature`; other slabs are absorbed only when released same-run AAG
284
+ evidence joins them to the exact retained face-level, riser-shoulder, or slot-pattern owner.
285
+ Multi-owner claims are conjunctive, while whole-envelope, polygonal-boss, disconnected,
286
+ ambiguous, or malformed cases remain visibly `unexpectedly_missing` rather than borrowing an
287
+ owner from matching values or traversal order (#1438, ADR 0017 Amendment 23).
288
+
289
+ - `Drawing.write_report(path)` now atomically writes the versioned report as deterministic,
290
+ strict UTF-8 JSON without coupling library export to sidecar generation. Report and filesystem
291
+ failures leave any existing destination untouched, with best-effort temporary cleanup that does
292
+ not mask the primary error (#1438, ADR 0017 Amendment 22).
293
+
294
+ - Raw automatic drawings now expose `Drawing.report()`, a strict version-1 JSON-compatible
295
+ projection of every accepted recognition occurrence, its explicit consumer disposition and
296
+ report-local final IR owner(s), plus the existing lint summary. The published schema never
297
+ serializes provider references, topology IDs, object addresses, or transient member positions;
298
+ it makes requirement coverage an explicit field (subsequently populated by Amendment 26) and
299
+ calls a clean result `bounded-clear`, not manufacturing-ready. Paths without exact occurrence
300
+ ownership and raw paths with an unclassified accepted occurrence fail explicitly rather than
301
+ emit an empty or inferred report. Rendering remains unchanged (#1438, ADR 0017 Amendment 21).
302
+
303
+ - Accepted `FaceLevel` and `RiserEvidence` occurrences now receive explicit `evidence_only`
304
+ outcomes. The released records remain shared substrate for the supported correlated height
305
+ ladder and shoulder projection, not independent inferred features or drawing requirements; no
306
+ IR, Sheet DSL, renderer, or recogniser API change is introduced (#1438, ADR 0017 Amendment 20).
307
+
308
+ - Accepted round-boss occurrences now retain the exact existing consumer outcome: a singleton
309
+ `BossFeature`, one diameter-consolidated representative with transient member lineage, the exact
310
+ turned-step owner, or the exact groove owner reached through that step or the profile-gate
311
+ fallback. Ambiguous or missing correspondence fails closed without another scan, persistent ID,
312
+ generated artefact, or visual change (#1438, ADR 0017 Amendment 19).
313
+
314
+ - Accepted through-step occurrences now retain either their exact `ThroughStepFeature` or every
315
+ exact final `StepLevelFeature`, `PlateFeature`, and `EnvelopeFeature` that jointly carries the
316
+ established compatibility projection. Plate claims are body-local, replacement is atomic, and a
317
+ missing or ambiguous selected owner fails closed rather than granting partial credit. One
318
+ occurrence can name multiple run-local IR owners without a second scan, persistent ID, generated
319
+ artefact, or change to rendering machinery (#1438, ADR 0017 Amendment 18).
320
+
321
+ - Accepted turned-step occurrences now retain the conversion decision that already gives an
322
+ ordinary profile band its exact `StepFeature` or absorbs a groove-floor band into the exact
323
+ same-run `GrooveFeature`. The takeover must be one-to-one in both directions, so an ambiguous
324
+ groove, an adjacent band inside its positional tolerance, or any missing final owner fails closed
325
+ as `unexpectedly_missing`, with no second scan, persistent ID, generated artefact, or visual
326
+ change (#1438, ADR 0017 Amendment 17).
327
+
328
+ - Accepted channel occurrences now receive an exact conditional consumer outcome. A genuine
329
+ multi-axis plate channel is `represented` by its `ChannelFeature`; a monolithic Z-depth rebate is
330
+ `absorbed` only when one retained body-local support carries its floor and both shoulders into the
331
+ final step ladder. Cross-axis, disconnected, or filtered channels fail closed as
332
+ `unexpectedly_missing` instead of inheriting an unrelated ladder, without a second scan,
333
+ persistent ID, generated artefact, or visual change (#1438, ADR 0017 Amendment 16).
334
+
335
+ - Accepted countersink occurrences now receive an exact run-local outcome. A seat carried by a
336
+ `HoleRecord` is `absorbed` by that hole's final feature and transient member lineage; an unattached,
337
+ unowned, or duplicate nested occurrence fails closed without dimension matching, topology IDs, a
338
+ second scan, duplicate IR requirements, or visual changes (#1438, ADR 0017 Amendment 15).
339
+
340
+ - Accepted angled-step, passage, prismatic-pocket, oriented-slot, and repeating-radial-profile
341
+ occurrences now receive exact run-local ownerless outcomes from Draftwright's existing consumer
342
+ capability declaration: `unsupported`, `deferred`, or report-facing `evidence_only`, with closed
343
+ reason codes and existing tracking issues. Equal-valued occurrences stay distinct; policy does
344
+ not invent IR owners, requirements, topology IDs, recognition scans, generated artefacts, or
345
+ visual output (#1438, ADR 0017 Amendment 14).
346
+
347
+ - Accepted physical hole, slot, and pocket occurrences now retain explicit run-local ownership
348
+ when conversion represents a singleton or absorbs members into one same-spec hole group or
349
+ derived pattern feature. Family-specific reason codes and transient member lineage follow
350
+ member-specific AP242 splits to the exact final IR objects; missing lineage fails closed rather
351
+ than retaining a stale owner. Provider-derived patterns do not become invented occurrences, and
352
+ no report ID, extra recognition scan, generated artefact, or visual output is introduced (#1438,
353
+ ADR 0017 Amendment 13).
354
+
355
+ - Raw automatic builds now capture run-local, conversion-time ownership from accepted provider
356
+ occurrences to their exact Draftwright IR features for unconditional one-to-one adapters. Equal
357
+ record values, order, coordinates and topology indices cannot create a binding; an omitted direct
358
+ binding fails closed as unexpectedly missing, while ownership rules not yet implemented remain
359
+ explicitly unclassified. The experimental read-only `Drawing.recognition_ownership()` exposes
360
+ the non-serializable ledger; it stays outside the IR/compiler and changes no generated or visual
361
+ artefact (#1438, ADR 0017 Amendment 12).
362
+
363
+ - Raw automatic recognition now retains the provider's run-scoped `RecognitionEvidence` beside
364
+ its exact `RecognitionResult`, exposed through the experimental read-only
365
+ `Drawing.recognition_evidence()`. Declared drawings acquire the pair lazily on first physical
366
+ critique; bare and framed results remain evidence-less, and Draftwright never rescans merely to
367
+ backfill evidence. This is a non-visual reporting foundation and changes no rendered artefact
368
+ (#1438, ADRs 0015, 0017, 0020).
369
+
370
+ - Automatic drawings can opt into the provider-owned local recognition frame with
371
+ `framed_recognition=True`. The returned drawing exposes caller provenance, the exact downstream
372
+ working solid, frame, and a copied framed/raw/refusal decision. PMI correlation, off-axis pattern
373
+ placement, compilation, projection and physical lint remain in one coordinate system; typed frame
374
+ refusal performs one visible top-level raw fallback. Raw remains the rollout default (#1357,
375
+ ADR 0020).
376
+
377
+ - Referential and measured Sheet dimensions accept optional `view=` / `side=` placement
378
+ intent. Supported pairs survive IR/compiler and generated-script round trips, constrain
379
+ ordinary corridor candidates without exposing page coordinates, and fail clearly when a
380
+ renderer cannot honour the requested view/strip. Compound hole callouts can therefore be
381
+ authored to opposite sides of one end view while retaining normal solve and lint behavior
382
+ (#563).
383
+ - Feature-linked Sheet notes can explicitly satisfy one or more canonical requirement ids with
384
+ `.note(..., satisfies=(...))`. Placed structured notes retain each requirement's identity in
385
+ coverage reports and generated scripts without masquerading as dimensions or parsing prose;
386
+ removing the note restores the ordinary missing/suppressed diagnostics (#1351).
387
+ - `Sheet.from_part(...).take_over(...)` now adopts detected features in place while choosing
388
+ authored or automatic dimension, principal-view, and derived-view sources explicitly. This
389
+ preserves detected handles, supports automatic principal views with authored requirements,
390
+ replaces or suppresses inferred sections without duplicates, and round-trips the adopted view
391
+ request through generated Sheet code (#1350).
392
+ - Referential Sheet dimensions can preserve an explicit number of decimal places with
393
+ `sheet.dimension(feature, "parameter.id").format(decimals=n)`. The policy changes only
394
+ printed display text: numeric reconciliation, tolerances, suppression and dimension
395
+ provenance continue to use the feature parameter, and generated Sheet scripts round-trip the
396
+ policy (#1349).
397
+ - New lint code `annotation_ink_overlap`: reports another annotation's line-work —
398
+ a dimension line, an extension line or a leader shaft — drawn through a label's
399
+ text, which `annotation_overlap` cannot see because it compares label boxes to
400
+ each other and to nothing else. Measured as the longest connected stroke path
401
+ crossing the label's exact polygon (or its axis-aligned box), in millimetres,
402
+ with a 0.5 mm floor. It scores on legibility, so
403
+ dense sheets that previously scored clean now score lower — 17 findings on
404
+ NIST CTC-02 AP203 alone, every sampled one confirmed a real defect by rendering.
405
+ Repair does not yet act on it (#1333) and the corridor solve does not yet avoid
406
+ it (#1334) (#1321, #1332).
407
+
408
+ ### Changed
409
+
410
+ - Updated the exact Quiddity runtime and declared-inspection contract to **0.2.8**, which stops
411
+ a modelled thread being read as a stack of turned shoulders and coalesces contiguous
412
+ equal-diameter bands into the one span they physically are. Measured on the repo corpus,
413
+ NIST CTC-05 is again the only fixture that moves: its ⌀304.8 region was three adjacent
414
+ rungs and is now one, in both STEP encodings (#1135, quiddity#587).
415
+
416
+ - Updated the exact Quiddity runtime and declared-inspection contract to **0.2.7**. Unlike the
417
+ 0.2.5 adoption below, this one changes drawings: the provider now requires circumferential
418
+ cylinder support to establish a turned-profile axis line, so rounded plate corners and parallel
419
+ offset cylinders no longer form a false stepped shaft. Measured on the repo corpus, NIST CTC-05
420
+ is the only fixture whose recognised turned profile moves — its two STEP encodings previously
421
+ disagreed (3 steps and 9 steps for the same solid) and now agree on 5 — and a plate part that
422
+ previously raised out of `generate_sheet_script` now emits and builds. No family, record schema
423
+ or adapter changed (#1555, quiddity#586).
424
+
425
+ - Updated the exact Quiddity runtime and declared-inspection contract to 0.2.5 for the
426
+ provider's recognition performance improvements.
427
+
428
+ - Production recognition now consumes published `quiddity==0.2.2`. Unified section-recess
429
+ occurrences and their same-run patterns feed the existing pocket, channel and blind-slot
430
+ drawing grammar. Original occurrence ownership, independent completeness and explicit
431
+ unsupported/refusal outcomes follow that single aggregate (#1471).
432
+ - Machine-readable reports and STEP inspection documents advance to schema v2 because their
433
+ closed producer object now names `quiddity`. The schema-v1 documents remain available for
434
+ existing files; readers must select the schema using `schema_version` (#1471).
435
+ - Groove membership now retains Quiddity's body-local profile identity. Generated declarations
436
+ use `profile_group` to associate coaxial grooves and steps without copying provider keys.
437
+ Omitted steps do not cause a groove to attach to another body (#1471).
438
+ - Known limitations in Quiddity 0.2.2: some mixed-profile/nested pockets, bore-intersected channels
439
+ and edge-open recesses, and pockets in cylindrical stock can lose automatic annotations.
440
+ Refusals remain visible in lint and evidence. Regression cases are retained as strict expected
441
+ failures while upstream [#536](https://github.com/pzfreo/quiddity/issues/536),
442
+ [#538](https://github.com/pzfreo/quiddity/issues/538) and
443
+ [#541](https://github.com/pzfreo/quiddity/issues/541) are open.
444
+
445
+ - Accepted schema-v3 straight or circular `Blend` chains, including concave occurrences, now cross
446
+ a dedicated end-to-end consumer path:
447
+ `BlendFeature`, explicit `Sheet.blend(...)`, executable generated declarations, one
448
+ compiler-approved `blend.radius` requirement, and a solver-owned grouped `n× R` leader. The
449
+ discriminated path kind, full canonical direction/normal, circular-path major radius,
450
+ rolling-ball radius, and material side remain in IR instead of being coerced to legacy Fillet
451
+ semantics; the provider aggregate remains the sole exact Fillet/Blend precedence authority.
452
+ Exact public-record/IR validation and completeness follow every accepted occurrence to placed,
453
+ structured-note, authored-suppressed, dropped, missing, or unverifiable outcomes. Raw and
454
+ provider-framed arbitrary rigid motions retain straight-path radius meaning; framed recognition
455
+ also retains circular paths, while raw arbitrarily rotated circular recognition remains an
456
+ upstream limitation (b123d-recognisers#491) (#1433; ADRs 0011, 0013–0017, 0020).
457
+
458
+ - The preceding recognition update pinned the immutable `b123d-recognisers` 0.4.14 release and advanced its
459
+ fail-closed capability and inspection joins. `Blend` schema v3 replaces the old flat cylindrical
460
+ fields with exact `StraightBlendPath` and `CircularBlendPath` records and admits proved concave
461
+ and toroidal occurrences; Draftwright preserves those semantics through IR, `Sheet.blend(...)`,
462
+ generated replay, reporting, drawing, and completeness. Passage nested records advance to schema
463
+ v2 without changing Draftwright's unsupported passage policy. The provider's previously
464
+ recognised shallow nonzero `PairedRampStep` keeps its existing
465
+ two-requirement IR, public `Sheet.paired_ramp_step(...)` declaration, generated-code, compiler
466
+ callout, solver placement, and completeness meaning (#1430; ADRs 0011, 0013–0017, 0020).
467
+
468
+ - Rectangular blind slots recognised by `b123d-recognisers` 0.4.10 now cross a dedicated
469
+ Draftwright semantic path: `RectangularBlindSlotFeature`, the explicit
470
+ `Sheet.rectangular_blind_slot(...)` word, generated-code and role-specific tolerance round
471
+ trips, compiler-approved width/capped-run/depth measurements, and one solver-owned
472
+ `OPEN SLOT … DEEP` leader. Authored subsets retain every approved value with explicit
473
+ `WIDE`/`LONG`/`DEEP` roles, and leaders target proved cap/side material rather than the open
474
+ mouth. Native raw and rigidly moved framed builds retain the same three
475
+ measurement outcomes. A seven-case independently authored corpus now pins all six run/open-side
476
+ orientations plus an independently sized specimen: each occurrence contributes separate width,
477
+ capped-run and depth requirements followed by exact structural correspondence and measurement
478
+ provenance to placed, structured-note, suppressed, dropped, missing or unverifiable outcomes.
479
+ Removing recognition cannot shrink that 21-requirement benchmark (#1421; ADRs 0011,
480
+ 0013–0017, 0020).
481
+
482
+ - Round-bottom blind slots recognised by `b123d-recognisers` 0.4.10 now have their own complete
483
+ Draftwright path rather than borrowing through-slot, pocket or rectangular-blind-slot grammar.
484
+ `RoundBottomBlindSlotFeature` retains the released run/opening topology and exposes the three
485
+ independent manufacturing requirements: capped run length, straight bottom-flat width and equal
486
+ side radius. `Sheet.round_bottom_blind_slot(...)`, generated code, role-specific tolerances and
487
+ one solver-owned `ROUND-BOTTOM OPEN SLOT …` leader all preserve those exact identities; derived
488
+ total width and depth are not double-counted. A seven-case independently authored corpus covers
489
+ all six signed principal orientations plus a separately sized specimen (21 physical requirements),
490
+ proves raw/framed drawing parity, and follows exact source-to-IR correspondence to placed,
491
+ structured-note, suppressed, dropped, missing or unverifiable outcomes without labels, views or
492
+ page coordinates. The released 0.4.10 API is sufficient; no recogniser change is required (#1421;
493
+ ADRs 0011, 0013–0017, 0020).
494
+
495
+ - Turned-step completeness is now backed by an independently authored, hash-pinned 11-case STEP
496
+ corpus with 26 physical outside-diameter bands, 52 parameter checks and 104 downstream checks.
497
+ Axis line and station preserve body-local occurrence identity across automatic IR, the existing
498
+ public `Sheet.step(...)` DSL word, executed-and-built generated code, and exact compiler-confirmed
499
+ length and OD drawing evidence. Grouped equal-length chains retain every adjacent member identity
500
+ and their exact compiler-approved multiplier and value; native and global OD labels are checked
501
+ in full. Generated declarations preserve the half-thousandth midpoint of odd-thousandth spans,
502
+ complete body-local profile ownership is compared at its full published precision, and malformed
503
+ or surplus records fail closed—including coercible or non-finite groove evidence that controls
504
+ band ownership. The root turned-step and groove inventories must remain immutable tuples; a
505
+ mutable or one-shot substitute is snapshotted once only to preserve its observable requirement
506
+ denominator, then remains unverifiable; a non-iterable substitute retains one explicit aggregate
507
+ contract outcome rather than masquerading as an empty family. A profile OD may
508
+ satisfy one uniquely identifiable equivalent largest-band diameter
509
+ without multiplying across disjoint bodies; and uniquely groove-owned narrow
510
+ bands are excluded once by a shared fail-closed Groove schema and ownership rule used by lowering,
511
+ lint and the evidence ledger. That join accounts for the provider's 0.001 mm Groove coordinate
512
+ quantum against its eight-decimal profile axis without widening either published identity
513
+ (#1374; ADRs 0010, 0011, 0013, 0015, 0017, 0020).
514
+
515
+ - Framed recognition now carries all three 0.4.6 step families through their complete existing
516
+ consumer paths. `CircularBlindStep`, `PairedRampStep` and `ThroughStep` retain the same
517
+ manufacturing requirements from raw recognition through their public `Sheet` words, generated
518
+ code, compiler-owned drawing ink and lint; the provider-owned local records and Draftwright IR
519
+ remain invariant under a combined non-principal rotation and translation. Circular-step
520
+ validation now derives its bound from each independently published endpoint, centre, radius and
521
+ depth value's six-significant-figure decimal cell, while outside-cell geometric inconsistencies
522
+ still fail closed (#1382; ADRs 0011, 0013, 0015, 0017, 0018, 0020).
523
+
524
+ - Countersink completeness is now backed by an independently authored, hash-pinned seven-case STEP
525
+ corpus covering functional seats, solid external-cone and deburr negatives, grouped equal seats,
526
+ and reverse-serialized topology. Detection identity and all four provider measurements are scored
527
+ separately from the two actual drawing requirements. Exact aggregate object ownership follows
528
+ each seat once, validates the provider-selected hole semantically, and fails closed on swapped,
529
+ duplicated, or canonically colliding owners. It then crosses automatic IR, public `Sheet.hole`,
530
+ generated code, and role-specific confirmed placed diameter/angle ink without recounting the parent bore. The known second seat of a two-sided bore
531
+ remains explicitly unverifiable rather than hidden or heuristically attached (#1370; ADRs 0010,
532
+ 0013, 0015, 0017).
533
+
534
+ - Double-D completeness is now backed by an independently authored, hash-pinned 11-case STEP
535
+ corpus with ten physical profile facts, 50 provider-parameter checks and 40 downstream checks.
536
+ Full-frame identity preserves disconnected coaxial bodies; exact multiplicity crosses automatic
537
+ IR, public `Sheet.double_d_bore`, generated code and role-specific compiler-confirmed
538
+ `⌀major THRU DOUBLE-D across A/F` ink. Round, blind, opposed-blind, malformed, duplicated and
539
+ foreign-owner mutations fail closed, while the provider's exclusive aggregate ownership keeps
540
+ each profile from being recounted as an ordinary hole (#1370; ADRs 0010, 0011, 0013, 0015,
541
+ 0017).
542
+ - Updated the immutable `b123d-recognisers` pin and fail-closed inspection/capability joins to
543
+ 0.4.10. All 28 previously consumed family schemas remain unchanged. The two new blind-slot
544
+ families were adopted fail-closed under #1421 and remain visible as unscored recognised
545
+ inventories until each gains independent completeness evidence; Draftwright does not coerce
546
+ their depth, terminal/open-end, or round-floor evidence into its ordinary through-slot grammar.
547
+ The provider's additive evidence API is not consumed by
548
+ this bounded adoption slice. Recognition output does change under the unchanged schemas: the
549
+ release closes slot-depth, subdivided paired-ramp/AngledStep, and noisy stubby-pocket gaps; fixes
550
+ Double-D/Hole ownership, external-cone countersink false positives, Plate tie covariance, and
551
+ turned-step translation covariance; and can move an edge-open rectangular recess from `Pocket`
552
+ to the dedicated blind-slot inventory. Draftwright regression tests pin the consumer-visible
553
+ ownership changes while the provider release suite owns its private predicates
554
+ ([provider release](https://github.com/pzfreo/b123d-recognisers/releases/tag/v0.4.10); ADRs 0013,
555
+ 0015, 0017, 0020).
556
+
557
+ - Principal-profile lint now correlates its independent extremal-face evidence with the cached
558
+ public `DoubleDBore` aggregate instead of importing provider-private profile helpers. Exact
559
+ axis/span, end-plane, centre, radius, A/F, chord, arc, and flat-direction checks fail closed;
560
+ occurrence mouths are consumed with matching multiplicity, and malformed records cannot earn
561
+ coverage. The exported low-level check now requires the aggregate projection explicitly so an
562
+ omitted inventory fails loudly; lint still owns its physical denominator and never reruns
563
+ recognition (#1411, ADRs 0013, 0015, 0017).
564
+
565
+ - Whole polygonal-stock completeness is now backed by an independently authored, hash-pinned
566
+ 13-case STEP corpus covering all principal axes, in-plane rotation, translation, topology
567
+ order and circular/rectangular/irregular/octagonal/attached/recessed/compound negatives. The
568
+ provider's exact six-side stock boundary, A/F, axial length and coupled physical flat supports
569
+ are scored separately from four downstream boundaries. A fail-closed two-requirement ledger
570
+ follows A/F and stock length through automatic IR, public `Sheet.polygonal_stock`, generated
571
+ code and solver-placed semantic ink; malformed or duplicate correspondence cannot borrow
572
+ credit from another support ring (#1371).
573
+
574
+ - Multi-plate slab completeness is now backed by an independently authored, hash-pinned 11-case
575
+ STEP corpus covering all principal orientations, additive/cut channels, equal compound
576
+ occurrences, topology order and envelope/detached/thick/rotational negatives. A fail-closed
577
+ one-requirement ledger follows each body-local `thickness.length` through automatic IR, public
578
+ `Sheet.plate`, generated code and solver-placed Dimension provenance; the compiler-derived
579
+ opposite U-channel wall is represented explicitly without inventing duplicate ink. The corpus
580
+ owns 20 physical occurrences and cannot shrink when provider output is weakened or removed.
581
+ Exact step-level, slot-pattern, polygonal-boss and envelope ownership prevents derived material
582
+ spans from becoming duplicate Plate requirements while failing closed when that ownership cannot
583
+ be established (#1373).
584
+
585
+ - Polygonal-boss completeness is now backed by an independently authored, hash-pinned STEP
586
+ corpus covering all three principal axes, in-plane rotation, equal compound occurrences,
587
+ topology order, and recess/detached/stock/other-protrusion negatives. Detection, the provider's
588
+ six-side schema invariant, across-flats, height, physical flat supports, and four downstream
589
+ boundaries are observed; a
590
+ two-requirement fail-closed ledger follows across-flats and boss height through the existing
591
+ IR/compiler/shared-solve path. The corpus owns the independent evaluation denominator; runtime
592
+ drawing lint remains explicitly denominated by the recognition inventory (#1372).
593
+
594
+ - Updated the immutable `b123d-recognisers` pin and fail-closed manifest joins to 0.4.9. The
595
+ provider's body-local FaceLevel, RiserEvidence, and TurnedProfile schemas now cross an owned
596
+ prepared-frame boundary that normalizes, reuses one cylinder substrate, classifies locally,
597
+ and preserves the exact working solid/result pair. Every frame gauge and typed refusal has an
598
+ explicit conservative policy; refusal never hides a raw retry. Multiple physical turned
599
+ profiles fail explicitly where a singular adapter is required, while the unchanged raw
600
+ production route preserves its historical no-global-profile compound behavior with a visible
601
+ #1357 deferral. Production drawing remains on the explicit raw boundary until rigid-motion,
602
+ PMI, platform, and canary activation evidence is complete (ADR 0020, #1357).
603
+
604
+ - Updated the immutable `b123d-recognisers` pin and lock to 0.4.8. Draftwright now names
605
+ its existing caller/world-coordinate aggregate boundary explicitly with
606
+ `build_raw_recognition_result`; framed recognition remains disabled until #1357's
607
+ frame-to-IR adapter is reviewed end to end. Installed-wheel tests exercise the public
608
+ framed fixes for rectangular pads, polygonal bosses, and nested multi-body plates without
609
+ making that API the production path (#1392).
610
+
611
+ - Rectangular pads now consume `RaisedPad` schema v2 without assuming +Z. The IR, public
612
+ `Sheet.pad`, generated code, compiler, semantic-view planner, shared placement solve, and
613
+ lint correspondence preserve the exact footprint, terminal-to-attachment height, two
614
+ in-plane locations, axis, direction, and world bounds for all six signed principal
615
+ orientations. Every height uses one solver-placed `… HIGH` leader with stable measurement
616
+ identity; side-normal pad footprints no longer leak into the unrelated Z-level grammar
617
+ (#1392).
618
+
619
+ - Rectangular-pad completeness is now independently evidenced by a hash-pinned 12-case STEP
620
+ corpus covering all six signed principal orientations, equal compound occurrences, topology
621
+ order, and ledge/staircase/detached-body negatives. Detection, width/length/height fidelity,
622
+ and four downstream boundaries are scored separately; a five-requirement fail-closed ledger
623
+ follows both footprint sizes, height and directional location evidence through the shared
624
+ compiler and placement solve (#1372).
625
+
626
+ - Circular blind steps recognised by `b123d-recognisers` now have a complete consumer path.
627
+ The oriented terminal-to-open centreline and transverse quarter-arc lower without rescanning
628
+ to an explicit-only IR/Sheet feature; generated code preserves the full correspondence.
629
+ Radius and stopped depth remain independently addressable and tolerance-aware while sharing
630
+ one solver-placed end-view `R… × … DEEP` leader, and a fail-closed two-requirement ledger
631
+ follows each outcome without parsing labels or guessing by order (#1382).
632
+
633
+ - Paired-ramp steps recognised by `b123d-recognisers` now have one complete consumer path:
634
+ exact IR conversion, explicit `Sheet.paired_ramp_step(...)` declaration, generated-code
635
+ round trip, end-view solver-placed `2× angle × run RUN` leader, independent angle/run
636
+ tolerances and authored suppression, and a two-requirement fail-closed completeness ledger
637
+ (#1382).
638
+
639
+ - Rectangular through steps recognised by `b123d-recognisers` now lower without rescanning to
640
+ an explicit-only IR/Sheet feature. Automatic and explicit lowering support every principal
641
+ axis; an X/Y occurrence stays with the established face-level and shoulder/plate grammar only
642
+ when that grammar proves both exact leg intervals, avoiding both silent loss and duplicate
643
+ dimensions. The two
644
+ orthogonal open-section legs are independently addressable, tolerance-aware, solver-placed in
645
+ the end view, preserved by generated code and scored by a fail-closed two-requirement
646
+ completeness ledger. The through extent stays owned by the envelope (#1382).
647
+
648
+ - Hole completeness is now supported by a versioned, independently authored STEP corpus rather
649
+ than a Draftwright-derived census. Detection, parameter fidelity and downstream usefulness stay
650
+ separate, while the IR adapter, public Sheet declaration, executed generated code and placed
651
+ drawing are each observed through their real seam. Mutations prove that a missing provider hole
652
+ or a broken downstream boundary lowers the relevant evidence even when internal state remains
653
+ self-consistent (#1369).
654
+ - Hole-pattern completeness is now supported by a separate independently authored STEP
655
+ arrangement corpus covering grids, bolt circles, linear arrays, false-positive controls,
656
+ compound cases and topology-order variants. It scores one grouping fact per pattern while the
657
+ hole corpus remains the sole owner of member diameter/depth/location requirements, and follows
658
+ each group through the IR, public Sheet declaration, generated code and placed pattern
659
+ dimensions (#1370).
660
+ - Flat completeness is now supported by an independently authored STEP corpus covering lone,
661
+ opposed, slanted, parallel and coaxial stock plus negative and topology-order controls. It
662
+ counts one physical across-flats requirement per stock line and span, then observes automatic
663
+ IR, public `Sheet.flat`, executed generated code and placed measurement provenance independently
664
+ (#1371).
665
+ - Lone-pocket completeness is now supported by an independently authored STEP corpus covering
666
+ off-centre, edge-anchored, equal, opposed-opening, side-opening, compound, ownership-negative
667
+ and topology-order cases. Provider `open_sign` now survives the IR, declaration and generated
668
+ code boundaries; pocket sizes and both physical location ordinates are followed through exact
669
+ semantic placement evidence without counting pocket-pattern members twice (#1372).
670
+ - Pocket-pattern completeness is now supported by a separate independently authored STEP corpus
671
+ covering axis-aligned and 30° linear/grid arrangements, threshold and unequal-spacing
672
+ negatives, a compound underside pattern and reverse-serialized topology variants. One grouped
673
+ count/size/lattice/location requirement reaches automatic IR, public `Sheet.pocket_pattern`,
674
+ generated code and exact placed ink—including approved pitch tolerances—without recounting its
675
+ member pockets. Diagonal pocket-pattern dimensions use exact label-ink clearance instead of a
676
+ false-blocking hull; generated code preserves the member opening side and grouped callouts
677
+ retain structured count provenance (#1372).
678
+ - Groove completeness is now supported by an independently authored STEP corpus covering a
679
+ monotonic-step negative, all principal axes, a narrow circlip ownership contest, equal grooves
680
+ on separate bodies and reversed Boolean order. Each physical groove's width and floor diameter
681
+ pass independently through automatic IR, public `Sheet.groove`, executed generated code and
682
+ one exact compiler-backed `WIDE × ø` callout; mutations prove that recognition, identity,
683
+ parameters, provenance and printed syntax cannot disappear without lowering evidence (#1372).
684
+ - Chamfer completeness is now supported by an independently authored STEP corpus covering all
685
+ principal axes, equal and asymmetric planar bevels, conical turned treatments, equal features
686
+ on separate bodies, topology-order variants, a plain negative and the AngledStep ownership
687
+ boundary. Each physical bevel is followed through exact IR, public `Sheet.chamfer`, executed
688
+ generated code, compiler measurement provenance, callout syntax and its physical leader target;
689
+ equal requirements may share ink without collapsing occurrence identity (#1374).
690
+ - Fillet completeness is now supported by an independently authored STEP corpus covering all
691
+ principal axes, repeated equal rounds, turned toroidal treatments, separate bodies,
692
+ topology-order variants, a plain negative and the CircularBlindStep ownership boundary. Each
693
+ physical round is followed through exact IR, public `Sheet.fillet`, executed generated code,
694
+ compiler measurement provenance, `R`/`n× R` syntax and its physical leader target; grouped ink
695
+ retains every rounded edge's measurement identity (#1374).
696
+
697
+ - Recognised angled blind steps now fail visibly instead of remaining an undecided inventory.
698
+ Each aggregate-reconciled `AngledStep` produces an actionable warning and one `unsupported`
699
+ completeness outcome. A frozen STEP corpus fixture pins the AngledStep/Chamfer ownership
700
+ boundary, while Draftwright deliberately invents no angle/run, IR, Sheet, generated-code,
701
+ section/detail, or annotation semantics (#1247).
702
+
703
+ - Aggregate-reconciled `PrismaticPocket` occurrences now fail visibly instead of
704
+ remaining an unscored inventory. Each produces an actionable warning and one `unsupported`
705
+ completeness outcome, without repeating the provider's cross-family reconciliation or
706
+ inventing width/length, across-flats, IR, Sheet, generated-code, or annotation semantics (#1246).
707
+
708
+ - Recognised prismatic passages now fail visibly instead of disappearing into an unscored
709
+ inventory. Each authoritative `SectionPassage` produces an actionable warning and an
710
+ `unsupported` completeness outcome; the legacy `Passage` compatibility projection is never
711
+ double-counted. Draftwright retains the family-level unsupported disposition rather than
712
+ claiming a regular-polygon callout represents the provider's complete line/arc schema (#1245).
713
+
714
+ - Declared Double-D bore, countersink, chamfer, fillet, and groove geometry reads now use the
715
+ stable `b123d_recognisers.inspection` namespace from the exact 0.4.6 wheel. A separate
716
+ Draftwright-owned format-1 contract fails closed on changes to the consumed signatures, result
717
+ schemas, units, bevel rejection reasons, or cylindrical surface layout (#1362).
718
+
719
+ - Updated the exact `b123d-recognisers` production lock to 0.4.6. At adoption time, circular
720
+ blind, paired ramp, and through-step inventories were carried from the one aggregate and
721
+ surfaced as unscored completeness evidence pending the #1382 semantic slices now recorded
722
+ above. Rectangular-grid pitch placement also uses a
723
+ deterministic outer witness line across plan, front, and side views despite the release's
724
+ coordinate-rounding differences.
725
+
726
+ - `annotation_overlap` now reports the whole defect when the same pair also draws
727
+ line-work through one of the two labels: the message names the crosser, the label
728
+ crossed and the millimetres, and replaces the "use `label_offset_x`" remedy, which
729
+ is the advice that case contradicts. `LintIssue.suggestion` is `None` for those
730
+ issues rather than a snippet that re-places the text (#1321, #1332).
731
+
732
+ - Recogniser integration now uses only the exact `b123d-recognisers` dependency in
733
+ `pyproject.toml` and `uv.lock`. The separate release-evidence record, dependency-bump workflow,
734
+ candidate-checkout harness, and release-time capability-version rewrites have been removed;
735
+ ordinary compatibility tests validate the installed pinned package.
736
+ - Automatic composition now measures the next larger preferred scale when a conservative
737
+ crowded-detail reservation leaves a sparse sheet. A candidate wins only on the same paper
738
+ size, without the recovery detail, structural errors, or required-placement loss. GRM-04 now
739
+ uses A4 at 5:1 instead of 2:1, re-homes its shared side-hole Y location into the plan-view
740
+ corridor, and retains both the Ø2.4 diameter and location requirements (#1155).
741
+ - Closed the remaining dense-sheet leader cases as deliberate Policy-B behavior: exhaustive
742
+ route sweeps prove there is no route clear of both material and committed sheet ink, so the
743
+ required callout remains with an explicit crossing diagnostic instead of being dropped. A
744
+ fast synthetic release gate complements the retained exhaustive corpus characterization
745
+ (#1187).
746
+
747
+ ### Fixed
748
+
749
+ - Overall height and envelope width remain dimensioned when approved turned-step lengths
750
+ cover only part of the respective extent. Redundancy suppression now requires adjoining
751
+ measurements on one profile to reach both overall endpoints; STC618 retains its missing
752
+ 62 mm height (#1509).
753
+ - Non-1:1 orthographic and isometric projection now scales extracted solids about the world
754
+ origin, matching the view-coordinate mapper and solver zones. Solids carrying a non-zero
755
+ build123d `Location` no longer shift their silhouette away from dimensions and callouts.
756
+ - Notes and generic table cells now render U+2300 `⌀` with the established supported drafting
757
+ glyph instead of IBM Plex Mono's missing-glyph square. The substitution happens at the one
758
+ shared measurement/render seam, so source rows remain unchanged and table sizing stays exact
759
+ (#1275).
760
+ - Automatic turned-part composition now gives true shoulder coverage priority over optional
761
+ pictorial content. GRM-03 replans to 5:1 and renders its 0.5, 2, 3, and 18 mm axial lengths;
762
+ `axial_length_missing` disappears because the dimensions are present, not because the
763
+ diagnostic was suppressed (#443).
764
+
765
+ ## v0.4.10 — 2026-08-22
766
+
767
+ **A view-set-aware planning and turned-edge-treatment patch.** Authored view constraints now
768
+ compile into an explicit resolved plan, automatic drawings fail closed when required content is
769
+ lost, AP242 hole tolerances reach their semantic feature owners, and the exact
770
+ `b123d-recognisers` 0.3.0 release supplies turned chamfer and fillet evidence through truthful
771
+ profile-view callouts.
772
+
773
+ ### Added
774
+
775
+ - `Sheet` can now author a complete or augmenting semantic view set with `authored_views()`,
776
+ `auto_views()`, `view()`, `section_view()`, `detail_view()`, and their `add_*` forms. Fluent
777
+ view handles declare relationships, alignment, scale, and whole-view pins without exposing
778
+ annotation coordinates; unsupported or infeasible constraints fail at their declaration
779
+ source instead of being silently relaxed. `sheet.view_constraints` exposes the immutable
780
+ request and `drawing.view_plan` the distinct resolved result (#1260).
781
+ - Drawings now retain their resolved view topology, per-view requirement coverage, and
782
+ arrangement decision as inspectable values. The engine can build and semantically compare
783
+ alternative arrangements and requested view sets while keeping projection and page placement
784
+ derived from the same plan (#1130).
785
+
786
+ ### Changed
787
+
788
+ - Dimension planning is now aware of the selected principal view set. Requirements supported by
789
+ another planned view are re-homed before projection; an unsupported set raises one aggregated
790
+ `ViewPlanIncomplete` naming every unmet requirement rather than failing late or producing a
791
+ plausible-looking drawing with missing dimensions (#1259).
792
+ - Three sparse corpus drawings now use a `stacked-iso` arrangement and fit A4 instead of A3 at
793
+ the same scale, with identical annotations, labels, and build issues. An alternative may only
794
+ compact the scale already selected by the default arrangement and must survive a real compile
795
+ without semantic loss; otherwise the default is retained (#1130).
796
+ - Updated the exact `b123d-recognisers` production lock from 0.2.9 to 0.3.0.
797
+ The immutable PyPI wheel/sdist hashes and capability-manifest digest `6645def94cbabaca7b9676fdb029b02e467266d1f020ea536e903b7d86b52685` are
798
+ recorded in `.github/recogniser-release.json`; focused compatibility evidence and the
799
+ normal consumer gates run on the generated PR.
800
+ - Accepted the additive Chamfer and Fillet schema-2 recogniser contract alongside schema 1 for
801
+ the `b123d-recognisers` 0.3.0 cutover; all other record schemas remain fail-closed at version 1
802
+ (#1276).
803
+
804
+ - Updated the exact `b123d-recognisers` production lock from 0.2.6 to 0.2.9.
805
+ The immutable PyPI wheel/sdist hashes and capability-manifest digest `805876905d548fd0ef1301e2cf24427a86d34bc2e592b8d2d0cb43db94e1d3bd` are
806
+ recorded in `.github/recogniser-release.json`; focused compatibility evidence and the
807
+ normal consumer gates run on the generated PR. The aggregate now inventories chamfers and
808
+ fillets on turned parts, and the fixed coaxial-post slot regression retires its downstream
809
+ strict xfail.
810
+
811
+ ### Fixed
812
+
813
+ - Automatic builds now apply the same required-outcome gate as explicit-scale builds. A settled
814
+ drawing that lost required placement outcomes records `plan_incomplete`, reports an
815
+ `incomplete` scale decision, preserves measurement/hole/source provenance, and fails lint
816
+ rather than presenting a diagnostic drawing as complete (#1250).
817
+
818
+ - Supported AP242 diameter tolerances now correlate with their canonical hole or pattern owner at
819
+ the completed IR boundary and round-trip through emitted `Sheet` declarations with source
820
+ provenance. Unmatched, ambiguous, conflicting, partial-pattern, and unsupported requirements
821
+ fail closed to provenance-preserving measured dimensions rather than being duplicated or lost
822
+ (#1116).
823
+
824
+ - A declared step shoulder coincident with its datum is now pruned before rendering instead of
825
+ sending identical endpoints to the border-dimension helper and raising `ValueError`. Valid
826
+ siblings remain, while the omission is recorded in `Drawing.suppressions()` and reported as
827
+ `step_position_coincident_with_datum` (#924).
828
+
829
+ - Turned chamfer and fillet leaders now read in a shaft profile view and land on their physical
830
+ edge-treatment sites instead of collapsing axial stations in the end view. Cylindrical
831
+ feature notes, knurl callouts, and surface finishes likewise derive a model-space surface
832
+ target rather than pointing at the shaft axis; all continue through the shared placement
833
+ solves, with X/Y/Z parity and declared Sheet round-trip (#1276).
834
+
835
+ - Toroidal fillets returned for turned parts by the shared recognition inventory now lower into
836
+ the existing one-feature-per-round IR and render through the common leader solve. Equal radii
837
+ share one `n× R` callout carrying every physical fillet's measurement provenance, and emitted
838
+ Sheet programs preserve the full inventory without rescanning geometry (#1281).
839
+
840
+ - Turned chamfers returned by the shared recognition inventory now lower into the same
841
+ one-feature-per-edge-treatment IR as prismatic chamfers. Equal specifications render as one
842
+ `n×` callout carrying every member's measurement provenance; different chamfer forms do not
843
+ collapse merely because their first legs match, and distinct tolerances split into truthful
844
+ requirements instead of being lost or applied to untoleranced siblings (#1254).
845
+
846
+ - Recogniser dependency automation now starts a fresh `Unreleased` section when the previous
847
+ Draftwright release has finalized the changelog, so a published recogniser can enter through
848
+ the documented immutable update workflow instead of failing before it opens a PR (#1279).
849
+
850
+ ## v0.4.9 — 2026-08-21
851
+
852
+ ### Added
853
+
854
+ - Lint now verifies that an annotation claiming to carry a measurement actually renders it
855
+ (#1217). Coverage checks read provenance riders the annotations carry about themselves and
856
+ believed them; nothing checked the claim against what the annotation draws. Four new codes —
857
+ `claimed_value_absent`, `claimed_measurement_not_compiled`, and two that report an
858
+ unverifiable claim rather than passing it — and `Drawing.add_table` now keeps the rows it
859
+ draws so a table's claims are readable back. Measured over all 21 STEP fixtures: 1,081
860
+ claims, 1,078 confirmed, and three findings — all instances of one genuine provenance
861
+ defect, see #1219.
862
+
863
+ - `lint_summary()["quality"]` gains a fourth component, `fidelity`: whether what the drawing
864
+ says is TRUE. The existing three ask whether required content landed (completeness),
865
+ whether there is too much of it (restraint), and whether a reader can make it out
866
+ (legibility) — none asked whether it is correct, so a drawing labelled 99 over a 16 mm
867
+ path reported a perfect score on every component available to it. Also covers a callout
868
+ drawn for a feature the part does not have, and a gear data table stating a tooth count
869
+ the geometry contradicts — the last of which reports `passed: True` and zero errors, so
870
+ the only existing channel that names it is `by_code`. Completeness, restraint and fidelity
871
+ report `available: False` with a `reason` rather than a flattering number when they have no
872
+ evidence, and every lint code the engine emits is classified onto exactly one component,
873
+ an explicit unscored register, or a register of codes whose component depends on the
874
+ issue's `outcome_stage`. `quality["unscored"]` reports the findings that reached no
875
+ component at all and flags any whose classification nobody made — `section_dropped` was
876
+ found scoring on nothing while a comment beside its emission said the opposite, and eleven
877
+ more `*_dropped` codes reach the engine as `drop_code` data with the same shape (#1176).
878
+
879
+ ### Changed
880
+
881
+ - Updated the exact `b123d-recognisers` production lock from 0.2.4 to 0.2.6.
882
+ The immutable PyPI wheel/sdist hashes and capability-manifest digest `aa52bc04216cd1eccc05796d2262547c7f476b80a3ec77334a77b9d8e3241a58` are
883
+ recorded in `.github/recogniser-release.json`; focused compatibility evidence and the
884
+ normal consumer gates run on the generated PR.
885
+ - `HoleRequirementOutcome` gains `members` and `features`, `ClaimOutcome` gains `measurement`,
886
+ and `linting.hole_coverage.canonical_hole_sites` is public (#1217). A consumer attributing a
887
+ requirement outcome to a specific recognised hole needs the evidence the outcome accounts
888
+ for, and needs to key into the canonical space the ledger publishes it in — a through hole's
889
+ own-axis coordinate is zeroed there.
890
+ - The STEP-analysis benchmark's `drawing_consumer` boundary now consumes
891
+ `hole_requirement_outcomes` instead of a second correspondence implementation of its own
892
+ (#1206), and follows the ledger's pointer through `linting.evidence` rather than trusting it.
893
+ Holes the ledger reports `unverifiable` now score `unknown` where the duplicate credited them
894
+ by guessing: 26 holes, five on each CTC-03 fixture and eight on each CTC-04 fixture. The
895
+ five-case evaluation corpus is unchanged, and one lint message on CTC-03 now reports its
896
+ coordinates in the ledger's own canonical space.
897
+
898
+ - `Drawing.suppressions()` rows gain a `conveyed_by` key: the dimension that states a
899
+ withheld measurement instead, or `None` when nothing takes the fact over (#1154). It is not
900
+ a synonym for the existing `authored` flag — that says whose decision it was, this says
901
+ where the measurement went.
902
+
903
+ - Updated the exact `b123d-recognisers` production lock from 0.2.2 to 0.2.4.
904
+ The immutable PyPI wheel/sdist hashes and capability-manifest digest `d0023d30e583c03abc55f09dfeaaa56fddf56334afa0417818480b2fa2ce3f0f` are
905
+ recorded in `.github/recogniser-release.json`; focused compatibility evidence and the
906
+ normal consumer gates run on the generated PR.
907
+ - Updated the exact `b123d-recognisers` production lock from 0.2.1 to 0.2.2.
908
+ The immutable PyPI wheel/sdist hashes and capability-manifest digest `7985befc8572bfe6d1c0805dfdf690d74a5a4a5f14d5988cc29c366315ff04ca` are
909
+ recorded in `.github/recogniser-release.json`; focused compatibility evidence and the
910
+ normal consumer gates run on the generated PR.
911
+
912
+ ### Fixed
913
+
914
+ - An authored tolerance the compiler approved now reaches the sheet wherever the measurement
915
+ is drawn (#1215, #1216). `build123d_drafting` resolves a dimension's text as
916
+ `label if label is not None else _number_with_units(measured, tolerance)`, and every
917
+ dimension this engine emits passes a label — so a forwarded `tolerance=` rendered nothing
918
+ while type-checking and reading back correctly from `Dimension.label`. Ten render paths
919
+ discarded the band that way: envelope extents, the height ladder, the turned-step chain, two
920
+ public `Drawing` verbs, the deferred corridor route, the detail-view redraw, the short-rise
921
+ escape, step shoulders, pattern pitch, compound-callout recess terms, grid pitch, and both
922
+ hole tables. A general guard now decorates every parameter of every feature across a corpus,
923
+ through both `decorations=` key shapes, and joins what the compiler approved against what the
924
+ claiming annotation renders.
925
+
926
+ - Collapsed `N×` marks no longer state a tolerance their members do not share (#1216). A
927
+ pattern's pitch claimed the authored band of every gap although the recogniser admits 2%
928
+ jitter — holes at −30, −10, 10.3, 30 printed `3× 20 ±0.1` over a 20.3 mm gap — and fillet and
929
+ flat collapses took the "first-authored" band, so one of four fillets at ±0.1 printed
930
+ `4× R5 ±0.1`. Equality is now judged at the drawn precision, and a withheld band is reported
931
+ (`pattern_pitch_tolerance_withheld`, `collapsed_tolerance_withheld`) rather than dropped
932
+ silently.
933
+
934
+ - An approved dimension that cannot be placed is now reported instead of vanishing (#1216).
935
+ The overall extents' corridor candidate carried a no-op drop handler — the only drop in the
936
+ engine that recorded nothing — and the step ladder's legibility floor skipped rungs without
937
+ incrementing any counter. Both now record against the measurement they concern, and a
938
+ withholding is withdrawn if a later pass draws the measurement after all.
939
+
940
+ - A feature leader filling a view's below corridor no longer costs the part its overall extent
941
+ (#1236). The mandatory width or depth now retries on the view's above strip after every
942
+ corridor has drained, the fallthrough starved slot and plate dimensions already use. Live on
943
+ three NIST CTC fixtures, which drew no overall width.
944
+
945
+ - The iso view and the above strips are no longer invisible to each other (#1240). Strip
946
+ placement never saw the iso, and the iso fit re-scaled after annotation without consulting
947
+ what had been placed — so growth could invade dimension ink, which it did on CTC-01. The fit
948
+ is now capped by measured re-projection rather than by a linear model of it: the projected
949
+ bbox translates as it scales, by up to 7.35 mm on a part carrying a Location, which no
950
+ prediction about the page centre can express.
951
+
952
+
953
+ - A part whose feature-local extent runs between the same two faces as an overall extent
954
+ is no longer dimensioned twice (#1154). GRM-04's drive plate printed `4.5` twice — once
955
+ as the hub's height and once as the overall thickness — because two detected records
956
+ claim one physical fact. The overall extent keeps it; the feature-local one is withheld
957
+ and records which dimension the reader finds it on. Reconciliation is by exact
958
+ support-plane coincidence, never by value: a square part's two equal extents run between
959
+ different faces and remain two facts (#997). A measurement is handed over only when the
960
+ extent receiving it is actually drawn — by the planner's rules, by the compiler's
961
+ overall-height rules, and by an authored set — and never when the yielding dimension
962
+ carries a tolerance, since a toleranced dimension and an untoleranced one are not the same
963
+ requirement. (A tolerance on the *receiving* extent is the same requirement on the same two
964
+ faces, and does not prevent the consolidation.)
965
+
966
+ ## v0.4.8 — 2026-08-16
967
+
968
+ ### Fixed
969
+
970
+ - Compatible automatic/deferred sparse ordinary side/plan hole and post-drain
971
+ machined-feature callouts now share one bounded same-view leader inventory
972
+ (#1166). Pattern, profiled-bore, and dense table-eligible hole queues retain
973
+ their specialised established paths because their winners affect downstream
974
+ furniture/table semantics or await #798 silhouette-aware routing. The solve
975
+ preserves required/priority-ranked evidence, rejects new-leader crossings,
976
+ checks complete leader ink against actual-width fixed dimension/witness,
977
+ arrow, label, and centre-furniture components (including rendered arrows on
978
+ shifted dimensions and component-local dashed circular centre furniture);
979
+ fixed residual faces and the selected survivor both require each tessellation
980
+ triangle to fit one same analytical component rather than merely the union,
981
+ plus exact continuous OCC containment for curved faces; residual lowering
982
+ also retains filled datum/GD&T faces absent from segment metadata; if candidate
983
+ or selected-survivor construction raises, or a survivor fails validation, the
984
+ bad alternative fails closed and the canonical lazy producer tail is replayed
985
+ instead of falsely dropping an otherwise valid callout; an all-invalid tail is
986
+ reported as validation-stage geometry failure rather than no clear room (so
987
+ scale fallback does not retry a non-spatial helper defect), while compiler
988
+ invariant violations such as a missing semantic callout label remain loud;
989
+ only explicitly marked global turning axes permit an arrow-local leader-tip
990
+ attachment; near-collinear shaft travel and ownerless section/cutting lines
991
+ remain fixed ink. The solve prefers
992
+ clear routes over explicit Policy-B fallbacks, and then minimises real leader
993
+ length. Provisional sections cannot veto that primary result: one separately
994
+ bounded refinement may choose an equally complete leader inventory that clears
995
+ their exact components, after which the final section performs one bounded
996
+ end-symbol repair and yields if exact ink still conflicts. The selected OCC survivor is validated against the same analytical
997
+ ink contract. A retained fixed-ink crossing now emits the machine-readable
998
+ `feature_leader_crossing` info finding instead of appearing lint-clean. Candidate,
999
+ fixed-component lowering, fixed-obstacle-probe, pair, and exact-search work is
1000
+ bounded; the fixed inventory is lowered once and reused with the producer's
1001
+ legacy lazy result as the floor, including state-cap exhaustion; page,
1002
+ own-silhouette, and title-reservation constraints remain hard in every fallback.
1003
+ A replay that exhausts exact fixed-probe classification is retained visibly as
1004
+ `feature_leader_fixed_ink_unverified` rather than rescanning past the cap, and
1005
+ that uncertainty participates in the legibility quality inventory.
1006
+ Trace output preserves an abandoned fully admitted inventory when state search
1007
+ exhausts, records the producer-floor replay separately, and names exact committed/provisional components,
1008
+ refinement status, and competing blockers; live
1009
+ single-callout edits remain immediate.
1010
+
1011
+ ## v0.4.7 — 2026-08-16
1012
+
1013
+ ### Added
1014
+
1015
+ - Recogniser co-development now has a one-command, wheel-based two-checkout check; an exact
1016
+ registry-release evidence record; automated dependency PR preparation; and documented landing,
1017
+ compatibility, deprecation, rollback, ownership, and CI-budget rules. Dependency PRs retain the
1018
+ canonical coverage/Codecov gate while avoiding four duplicate platform suites and the slow tier
1019
+ (#1170).
1020
+ - A versioned, independently authored STEP-analysis corpus and evaluator now report detection
1021
+ recall/false positives, parameter fidelity, and downstream usefulness as separate evidence
1022
+ layers. Hash-pinned positive, negative, ambiguous, compound, and topology-order-variant fixtures,
1023
+ per-layer diagnostics, and an anti-self-validation mutation prevent recogniser output from
1024
+ defining its own denominator (#1169).
1025
+ - Draftwright now validates the released `b123d-recognisers` 0.2.0 capability manifest against
1026
+ an exhaustive consumer-owned declaration of IR, DSL, generated-code, drawing, completeness,
1027
+ and documentation states. Unknown families, stale implementations, record-schema drift, and
1028
+ unevidenced state transitions fail closed; intentionally geometry-only repeating-profile
1029
+ evidence remains usable without acquiring invented drafting semantics.
1030
+
1031
+ ### Changed
1032
+
1033
+ - Updated the exact `b123d-recognisers` production lock from 0.2.0 to 0.2.1.
1034
+ The immutable PyPI wheel/sdist hashes and capability-manifest digest
1035
+ `383a196329ed6a3d1bf88f18ae4ff75613c032f35d40f2b53302b6744da4d19e` are
1036
+ recorded in `.github/recogniser-release.json`; focused compatibility evidence and the
1037
+ normal consumer gates run on the generated PR.
1038
+
1039
+ ### Fixed
1040
+
1041
+ - Stable release and TestPyPI snapshot builds now update package metadata and the independently
1042
+ validated Draftwright consumer-contract identity atomically. This prevents stripped `.dev0`
1043
+ and numbered development artifacts from failing their own capability-contract validation.
1044
+ - Installed wheels validate every portable recogniser-contract property without requiring the
1045
+ repository-only behavior-test files whose existence remains a strict source/CI gate. This keeps
1046
+ the packaged STEP-analysis evaluator usable without weakening evidence checks in development.
1047
+ - Post-release development-version bumps now update the consumer-contract identity on a branch,
1048
+ open a protected PR, and dispatch the narrow maintenance gate instead of attempting a rejected
1049
+ direct push to protected `main` (#1170).
1050
+ - Machined-feature leader callouts now collect all clear alternatives within each
1051
+ post-drain pass and use a bounded maximum-cardinality, minimum-leader-length
1052
+ assignment instead of taking corners greedily in feature order (#740). If a
1053
+ deterministic alternative-count, pass-size, candidate-pair, or search budget is
1054
+ reached, placement retains the legacy greedy result or a result no worse than it
1055
+ rather than losing additional callouts. Pre-drain diameter clearance ranking and
1056
+ its Policy-B fallback remain unchanged.
1057
+
1058
+ ## v0.4.6 — 2026-08-15
1059
+
1060
+ ### Changed
1061
+
1062
+ - **Geometry recognition is supplied by the standalone Apache-2.0
1063
+ [`b123d-recognisers`](https://github.com/pzfreo/b123d-recognisers) package.** The cutover
1064
+ pins the published stable release `v0.1.0` (built from commit
1065
+ `9e622716c14d491729b5191aa6e5d8351c982a51`), removes the
1066
+ duplicate embedded implementation, and keeps only identity-preserving
1067
+ `draftwright.recognition` / `draftwright.score` compatibility re-exports until 0.6.0.
1068
+ Draftwright continues to own the one-result build/lint cache, record→IR conversion, drafting
1069
+ policy, placement, and critique. The package migration is manifest-pinned and golden-backed;
1070
+ its sole normalization makes an exact dominant-axis tie deterministic across operating
1071
+ systems without changing feature policy.
1072
+
1073
+ ### Fixed
1074
+
1075
+ - Hole-callout obstacle checks now account for the rendered leader shaft width and
1076
+ tip-local arrowhead flare instead of treating the leader as a zero-width centreline
1077
+ (#367). The analytical check remains geometry-only and does not over-reserve the
1078
+ arrowhead's clearance along the whole shaft.
1079
+ - Exact X/Z slanted flats retain their established lint-clean side-view callout after the
1080
+ recogniser package's cross-platform semantic-axis normalization. The normalized recognition
1081
+ record remains unchanged; `FlatFeature.presentation_axis` carries the downstream drawing-view
1082
+ choice, preventing a leader/silhouette crossing without duplicating recognition.
1083
+
1084
+ ## v0.4.5 — 2026-08-15
1085
+
1086
+ **A focused technical-drawing correctness patch.** Cross-drilled hole locations now remain
1087
+ complete and ordered, datum-starting pocket annotations avoid redundant dimensions, and repeated
1088
+ equal-radius fillets collapse into one manufacturing callout.
1089
+
1090
+ ### Fixed
1091
+
1092
+ - Side-drilled hole heights that relocate around callout leaders now join the alternate
1093
+ view's shared dimension ladder before placement, preserving every height and keeping
1094
+ feature locations inside the overall dimension. Datum-starting open pockets no longer
1095
+ print a redundant half-length centre offset, and equal-radius fillets across different
1096
+ edge axes collapse into one counted ``n× R`` callout.
1097
+
1098
+ ## v0.4.4 — 2026-08-15
1099
+
1100
+ **A semantic-completeness and fail-closed layout patch release.** Hole requirements and drawing
1101
+ quality are now machine-readable, hole-table replacement is transactional, explicit scales
1102
+ preserve required output, notes tables use all viable sheet space, and gear declarations,
1103
+ recognition evidence, and callout metadata improve diagnostic trust.
1104
+
1105
+ ### Added
1106
+
1107
+ - **External spur gears can be declared through the `Sheet` façade** (#1086). The declaration
1108
+ records the correlated ISO 1328-1 manufacturing requirements, renders a structured gear-data
1109
+ table, and survives script emission, declaration, export, and lint without inferring gear data
1110
+ that the caller did not supply.
1111
+
1112
+ - **Recognition carries serialisable repeating radial-profile evidence** (#1087). Exact
1113
+ rotational whole-wire proof can reconcile a declared gear with its physical profile while
1114
+ preserving ambiguity across separate bodies and without inventing module, pressure angle, or
1115
+ other manufacturing intent.
1116
+
1117
+ - **Explicit drawing scales now protect required annotation completeness** (#1146).
1118
+ `build_drawing(...)`, `make_drawing(...)`, `Sheet(...)`, and the CLI accept
1119
+ `scale_policy="fallback"|"strict"|"permissive"`. The safe default retries preferred ISO
1120
+ 5455 reductions and returns the largest complete scale; strict mode raises with structured
1121
+ requirement blockers, while permissive mode is an explicit warned opt-in to the historical
1122
+ best-effort result. Every returned `Drawing.scale_decision` reports the requested and
1123
+ effective scales, status, attempted scales, and any semantic placement blockers.
1124
+
1125
+ - **Hole and hole-pattern requirements now participate in semantic completeness accounting**
1126
+ (#1143). `lint_summary()["quality"]["completeness"]` reconciles recognition-owned bore,
1127
+ depth/through, grouping, pattern, and location requirements to placed, suppressed, dropped,
1128
+ missing, or explicitly unverifiable outcomes. Ambiguous declared/automatic correspondence
1129
+ fails closed without shrinking the physical denominator. Z-normal hole locations retain ADR
1130
+ 0016's feature-level `location.location` addressability while structured physical evidence
1131
+ distinguishes the independently required X and Y ordinates for critique.
1132
+
1133
+ - **`lint_summary()` exposes drawing-quality evidence as separate completeness, restraint,
1134
+ and legibility components** (#1127). The legacy `score` remains byte-compatible and is also
1135
+ returned as the honestly named `diagnostic_score`; no composite quality verdict is invented.
1136
+ Completeness scores recognition-owned requirements in the families with semantic outcome
1137
+ ledgers and states that conditional scope explicitly; recognized families not yet scored are
1138
+ listed separately. Its scalar is `audited_score`, not `score`, because a feature recognition
1139
+ missed never became a requirement — so a part with a recogniser gap can reach 1.0, and the
1140
+ qualifier belongs where it survives being quoted. The block also lists what its denominator
1141
+ `excludes` and counts `unrecognised_geometry_reports` (a floor on that gap, not a measure).
1142
+ It is not a completion gate. Legibility includes only layout and placement diagnostics, and
1143
+ scores information-severity ones — "place what fits" drops, a leader crossing a silhouette —
1144
+ against the warning floor, so it cannot read 1.0 while itemising output it calls unreadable.
1145
+ A drop is identified by its `*_dropped` code suffix unless its producer records an explicit
1146
+ `outcome_stage`, so a drop code added later counts without being registered anywhere.
1147
+ Restraint fails closed as unavailable until measurement provenance can classify every
1148
+ annotation.
1149
+
1150
+ ### Fixed
1151
+
1152
+ - **Generic notes/data tables use every viable free sheet region and explain failed placement**
1153
+ (#1145). A drawn sheet frame or zone ruler now defines the usable inset instead of becoming a
1154
+ page-sized solid obstacle, so a measured four-row notes block can use clear lower-left A4
1155
+ space. Tables retain the drafting preset's external text clearance from named and anonymous
1156
+ existing annotations. Failed placement diagnostics report the measured page-space footprint,
1157
+ attempted candidate regions, and the named obstacles or clearance bands that blocked the
1158
+ nearest candidates without materialising the candidate Cartesian product.
1159
+
1160
+ - **Automatic hole-table escalation is transactional per semantic requirement** (#1144). The
1161
+ engine suppresses replaceable callouts and location dimensions only after the table fits and
1162
+ every visible row has its required feature-owned balloon. A failed or partial balloon attempt
1163
+ rolls the shared table back as a unit and restores the exact annotations, ordering, registry
1164
+ identities, coverage, and issues. Precise ring, glyph, and leader-segment tests keep placement
1165
+ clear of retained callout labels, leader shafts, and existing public-balloon components without
1166
+ changing the existing additive `add_hole_table()`/`add_balloons()` behavior; malformed component
1167
+ metadata—including Boolean cardinalities or coordinate payloads—falls back conservatively or fails the
1168
+ optional replacement closed. New balloon shafts must also remain mutually crossing-free.
1169
+ Compound, thread,
1170
+ profile, pattern, tolerance, and fit facts the table cannot state remain active, while
1171
+ independently replaceable X/Y locations may still move to the table. Guarded interval solving
1172
+ has deterministic pre-lane, pre-carve, and work/state budgets; an adversarially fragmented label
1173
+ inventory or band fails the replacement closed before excessive work or allocation. Required row-key
1174
+ balloons take priority over optional non-certifying pattern markers, so an independently complete
1175
+ table cannot be displaced by an auxiliary marker.
1176
+ Hole outcomes identify the winning representation per requirement;
1177
+ mixed table/feature evidence deliberately claims no single winner. Exceptions restore those
1178
+ semantic markers, and automatic escalation fails closed rather than overwrite an existing
1179
+ annotation that owns a reserved table or balloon name.
1180
+
1181
+ - **Generated hole-callout leaders expose their exact rendered semantic text through the public
1182
+ `Leader.label` field** (#1142). Geometry and placement are unchanged, and structured coverage
1183
+ remains authoritative instead of requiring downstream tools to parse display text.
1184
+
1185
+ - **Leader placement probes use analytic footprint arithmetic instead of constructing temporary
1186
+ OCCT geometry** (#1138). Representative probe-heavy layouts are substantially faster while
1187
+ preserving custom-style containment and placement behavior.
1188
+
1189
+ ### Changed
1190
+
1191
+ - **Pairwise lint detail no longer multiplies the legibility score penalty** (#1147). Raw
1192
+ severity and code counts retain their existing finding-level semantics, and every offending
1193
+ pair remains in `issues`. The legibility component now also reports its primary issue counts
1194
+ and scores one producer-identified annotation/failure mechanism once, with `affected_pairs`
1195
+ exposing how many raw pair findings were aggregated. The legacy top-level `score` and
1196
+ `diagnostic_score` remain unchanged.
1197
+
1198
+ - **Pull-request validation now reports a stable aggregate `ci-ok` check and runs the supported
1199
+ Python matrix on Linux** (#1134/#1136). Weekly, manual, and labelled workflows retain the
1200
+ cross-platform escape hatch, reducing routine Actions cost without narrowing supported Python
1201
+ coverage.
1202
+
1203
+ ## v0.4.3 — 2026-08-09
1204
+
1205
+ **A trust and validation-throughput patch release.** Whole-part hexagonal stock now receives its
1206
+ manufacturing definition, assembly recognition no longer combines evidence across bodies, and
1207
+ performance validation is both more stable and faster to run.
1208
+
1209
+ ### Added
1210
+
1211
+ - **Whole-part regular hexagonal stock is recognised independently of an attached boss and
1212
+ dimensioned as `HEX … A/F` plus its axial length** (#1082). In-plane rotation preserves the
1213
+ geometric across-flats size instead of substituting an orientation-dependent bounding-box
1214
+ width. Automatic and declared paths share the same compiler and placement machinery, while
1215
+ completeness lint distinguishes placed, authored-suppressed, dropped, missing, and ambiguous
1216
+ outcomes.
1217
+
1218
+ ### Fixed
1219
+
1220
+ - **Slot and pocket patterns cannot group members from separate assembly bodies** (#1073).
1221
+ Recognition carries solid ownership through grouping and fails closed when ownership is
1222
+ unavailable, preventing plausible-looking phantom patterns across disconnected parts.
1223
+
1224
+ - **The annotation-obstacle performance guard uses deterministic work-count evidence instead of
1225
+ a wall-clock threshold** (#1071). The regression test remains load-bearing under mutation but
1226
+ no longer flakes because of machine load or suite ordering.
1227
+
1228
+ ### Changed
1229
+
1230
+ - **The post-merge slow tier distributes individual tests across workers** (#1108), using
1231
+ `pytest-xdist`'s load scheduling after local benchmark evidence showed a material speed-up
1232
+ without weakening fixture coverage. Hosted slow validation remains the single post-merge gate.
1233
+
1234
+ ## v0.4.2 — 2026-08-09
1235
+
1236
+ **The trustworthy manufacturing drawings release.** AP242 PMI is now accounted for from its
1237
+ source records through lowering and rendering, imported datum faces retain an auditable topology
1238
+ identity, and several drawing paths that previously omitted, invented, or stalled on defining
1239
+ geometry now fail closed or produce the required annotation.
1240
+
1241
+ ### Added
1242
+
1243
+ - **AP242 geometric tolerances and datum features survive as source-backed manufacturing
1244
+ requirements** (#62/#675/#1094/#1095/#1099/#1100). The importer recovers range-encoded
1245
+ dimensional tolerances, tolerance characteristics, magnitudes, datum chains, and supported
1246
+ all-around/all-over modifiers. Datum representation-item faces are resolved to imported
1247
+ topology through OCCT's exact transfer relationship rather than geometric resemblance, so the
1248
+ NIST CTC-01 datum definitions `A`, `B`, and `C` lower and render without guessing.
1249
+
1250
+ - **PMI completeness lint reconciles every AP242 source record through extraction, projection,
1251
+ lowering, and rendering** (#623). Unsupported or incomplete requirements remain explicit with
1252
+ source-specific outcomes instead of allowing a partial PMI transcription to report clean.
1253
+
1254
+ - **Hexagonal and other regular polygonal bosses are recognised and dimensioned by their
1255
+ across-flats size** (#676). Automatic and declared paths share the same feature model, and
1256
+ mutation tests reject incomplete, unequal, non-planar, and ambiguous face sets.
1257
+
1258
+ - **Generated `Sheet` scripts preserve references to named source objects where correspondence
1259
+ is proven** (#1041). The `--script` path emits declarations such as
1260
+ `sheet.hole(features.tap)` instead of copying detected numbers; ambiguous, stale, non-shape, or
1261
+ unused mappings fail closed.
1262
+
1263
+ - **A curated API reference is published at
1264
+ <https://pzfreo.github.io/draftwright/>** (#846), covering the supported entry points,
1265
+ `Sheet` declaration surface and fluent handles, `Drawing` results, and feature constructors.
1266
+
1267
+ ### Fixed
1268
+
1269
+ - **Recognition no longer combines faces from different solids into phantom slots or pads**
1270
+ (#958). Each physical solid is recognised independently before the results are combined.
1271
+
1272
+ - **`Sheet.from_part(...).envelope()` reuses the detected whole-part envelope** (#1000) instead
1273
+ of appending a duplicate feature whose dimensions were then silently deduplicated on the page.
1274
+
1275
+ - **Crowded Z-turned step chains recover the independently approved overall height when the
1276
+ chain cannot be placed** (#955), and placed step lengths retain measurement identity for
1277
+ differential audit (#1004).
1278
+
1279
+ - **All-over GD&T leaders and complex STEP B-spline paths have bounded, export-safe DXF
1280
+ conversion** (#1097/#1070). All-over scope now reaches the existing export-safe leader
1281
+ representation, while spline conversion reads OCCT knots, poles, and weights through its
1282
+ indexed API instead of expensive sequence wrappers; visible curve geometry is preserved.
1283
+
1284
+ ### Changed
1285
+
1286
+ - **Hosted CI retains every supported Python version and both dependency/kernel generations on
1287
+ macOS and Windows with a smaller compatibility matrix** (#1103). PRs still require lint,
1288
+ coverage, Codecov, and the compatibility gates; the slow standards tier runs once after merge.
1289
+
1290
+ - **The pinned wheel fixture now records mutation-gated evidence for a repeating 13-fold radial
1291
+ profile without claiming gear semantics** (#1085). Runtime recognition remains fail-closed
1292
+ until a separate feature contract has independently sufficient evidence.
1293
+
1294
+ ## v0.4.1 — 2026-08-07
1295
+
1296
+ **The recognition trust release.** Automatic drawings now fail closed on unsupported inner
1297
+ profiles, prove semantic coverage for flats, slots and patterns, and retain the measurement
1298
+ identity needed to explain placed, suppressed, dropped and missing definitions. Several real
1299
+ STEP case studies also move from clean-looking but incomplete output to complete drawings or
1300
+ actionable lint.
1301
+
1302
+ ### Discouraged (supported)
1303
+
1304
+ - **`Sheet.auto_dimensions()` and `Sheet.add_dimension()` now emit
1305
+ `SoftDeprecationWarning`** (#1043). They remain supported with no removal planned; the
1306
+ warning steers declared scripts toward `authored_dimensions()` plus explicit
1307
+ `dimension(feature, parameter_id)` lines, where omission can mean suppression. Automatic
1308
+ `build_drawing(part)` and the `Sheet.from_part(part)` on-ramp remain silent. See
1309
+ `docs/deprecations.md` for filtering and migration guidance.
1310
+
1311
+ ### Added
1312
+
1313
+ - **Finished drawings expose why measurements were omitted and, on covered placement paths,
1314
+ what an annotation measures** (#996/#1002). `Drawing.suppressions()` returns the compiler's
1315
+ omission ledger, `Drawing.measurement_keys(name)` exposes recorded measurement provenance, and
1316
+ `draftwright.audit.diff_builds(before, after)` reports losses, substitutions and candidate
1317
+ suppression explanations. The differential is deliberately a triage aid, not a proof where
1318
+ renderer identity remains incomplete.
1319
+
1320
+ - **Physical-completeness lint now follows semantic correspondence for flats, lone slots and
1321
+ slot patterns** (#1018). Coverage is joined through recognition, IR and compiler identities
1322
+ rather than inferred from label text or page geometry, and distinguishes authored
1323
+ suppression, placement drops, missing definitions and unverifiable provenance.
1324
+
1325
+ - **Full-span floored open channels are recognised and dimensioned as first-class features**
1326
+ (#917). Automatic and declared drawings state the independent overall extent, one wall and
1327
+ channel width, suppress the derived opposite wall, and report any missing member of that
1328
+ defining chain.
1329
+
1330
+ ### Fixed
1331
+
1332
+ - **Square and near-square parts retain both independent planar extents** (#997). A plain
1333
+ square plate could previously lose both plan dimensions, while parts up to five percent off
1334
+ square could lose one and be silently represented as square. The unsafe suppression rule is
1335
+ removed; explicit square notation remains tracked separately in #918.
1336
+
1337
+ - **Machined flats retain their physical stock identity from recognition through placement
1338
+ and completeness lint** (#1013/#1015/#1034/#1036). Flats on separate parallel, coaxial or
1339
+ slanted stock no longer collapse into one definition or borrow an opposite face from another
1340
+ stock region. Independent callouts receive clear-margin candidates, while double-D faces on
1341
+ one stock still form one A/F requirement.
1342
+
1343
+ - **Dense callout and step-detail cases keep their complete defining set** (#915). Hole
1344
+ callout batches are reconciled as a queue, detail views fit their actual aspect, step levels
1345
+ retain the supporting-face correspondence needed for truthful crops, and a detail redraws
1346
+ only rungs omitted from its parent view.
1347
+
1348
+ - **Pocket leaders start on the physical opening rim** (#916), removing the spurious inner
1349
+ silhouette crossing while preserving the solved label corridor and approved dimensions on
1350
+ X-, Y- and Z-depth pockets.
1351
+
1352
+ - **Wedge-mounted raised pads survive recognition** (#909). Lower ledges that touch only in
1353
+ plan no longer make the recogniser discard a valid upper pad; true staircase tiers remain
1354
+ excluded.
1355
+
1356
+ - **Annotation-dense detail placement no longer stalls the build** (#1065). Detail views
1357
+ avoid every decomposed annotation shaft, witness, label and item footprint; CTC-02
1358
+ supplies 571 such boxes. The free-rectangle search previously treated that set as tiny
1359
+ and admitted roughly 34 billion candidate rectangles, exhausting the slow tier's
1360
+ ten-minute test budget before export. A compressed-coordinate sweep preserves the same
1361
+ deterministic winning rectangle while completing the real search in seconds.
1362
+
1363
+ - **Through double-D bores are recognised and called out as first-class hole profiles**
1364
+ (#1061). Automatic and declared builds preserve the parent-circle diameter, A/F size,
1365
+ orientation and depth, and render one compound `DOUBLE-D ... A/F` bore callout. Physical
1366
+ critique now accepts that supported inner profile while retaining a separate warning for
1367
+ 13 evenly spaced common-circle arcs on the unresolved outer boundary; it does not infer a
1368
+ repeating full profile or a gear standard.
1369
+
1370
+ - **Unsupported internal profiles no longer pass physical-completeness lint silently**
1371
+ (#1058). Inner loops on principal boundary faces are checked against the current
1372
+ circular-hole, rectangular-opening, true-obround, and proven through-double-D vocabulary.
1373
+ Profiles outside it now produce
1374
+ `unrecognised_defining_geometry` and reduce the lint score instead of allowing an
1375
+ envelope-only drawing to score 1.0.
1376
+
1377
+ ### Changed
1378
+
1379
+ - **Crowded prismatic step dimensions recover into an enlarged detail view by default**
1380
+ (#909). The automatic `build_drawing` / `make_drawing` / CLI paths and generated
1381
+ `Sheet` scripts now preserve the omitted dimensions instead of returning
1382
+ `step_dim_dropped` unless the caller remembered `detail_view=True`. Pass
1383
+ `detail_view=False` to retain the parent-view-only behavior and its lint warning.
1384
+
1385
+ ## v0.4.0 — 2026-08-02
1386
+
1387
+ **The compat-exit release.** Every surface whose documented removal target was 0.4.0 is gone,
1388
+ and every deprecation that remains now says when it goes — in the message the caller sees, not
1389
+ only in a release note. ADR 0005 §4's rule is that an alias carries a tracking issue *and* a
1390
+ removal date, because "a facade with no exit date is a failure mode"; this release is that rule
1391
+ being kept rather than restated. `docs/deprecations.md` is the new index, and
1392
+ `tests/test_deprecation_dates.py` fails any deprecation that names no removal.
1393
+
1394
+ Also lands ADR 0016's declared-dimensioning work (#867): a `dimension(feature, role)` is
1395
+ referential — it names a measurement and carries no number — and an authored set means omission
1396
+ is suppression, enforced at the compiled-plan boundary rather than by a flag renderers check.
1397
+
1398
+ **Read this before upgrading:** two removals break *without a release that warned you*, because
1399
+ their deprecation never appeared in a shipped version. See *Removed (breaking)* below and
1400
+ `docs/deprecations.md`.
1401
+
1402
+ ### Removed (breaking)
1403
+
1404
+ Compat surfaces whose documented removal target was 0.4.0 (#720, ADR 0005 §4 — every alias
1405
+ carries a tracking issue *and* a removal date, or the facade is permanent by accident):
1406
+
1407
+ - **The seven `Drawing` compat aliases** — `_named` / `_anno_view` / `_pinned` /
1408
+ `_build_issues`, and `_pattern_callouts` / `_patterned_holes` / `_dropped_callout_diams`.
1409
+ These were private, and the public reads have existed since #699: use `dwg.registry`
1410
+ (`in reg`, `names()`, `issues`, `pinned_names()`) and `dwg.coverage`, or the `Drawing` verbs
1411
+ `annotations()` / `iter_annotations()` / `get_annotation()` / `view_of()`.
1412
+ **Note the asymmetry.** A *read* — `dwg._named` — now raises `AttributeError`. A *write* —
1413
+ `dwg._pinned = {...}` — does **not**: these were properties with setters, and `Drawing` has
1414
+ no `__slots__`, so assignment now quietly creates an unrelated instance attribute that no
1415
+ longer reaches the registry or coverage owner. Writes therefore fail silently rather than
1416
+ loudly. Grep for `\._(named|anno_view|pinned|build_issues|pattern_callouts|patterned_holes|dropped_callout_diams)\b`
1417
+ before upgrading; mutate through `registry.add()` / `pin()` / `record_issue()` /
1418
+ `restore_issues()` and the `CoverageState` methods instead.
1419
+ - **The `draftwright.sheet_dsl` module** — an import alias for `draftwright.sheet` since the
1420
+ #640 rename. Import `Sheet` from `draftwright` or `draftwright.sheet`.
1421
+ - **`generate_script`**, including its `draftwright.__all__` entry. It has raised since #940
1422
+ retired the imperative emitter; it is now simply absent, so the failure is an `ImportError`
1423
+ at the top of a script rather than a `RuntimeError` part-way through one. Use
1424
+ `--script` / `emit_sheet_script`.
1425
+ - **The bespoke `--style imperative` error message.** `--style` itself is unchanged and still
1426
+ accepts its sole value `sheet`; `imperative` is now an unrecognised value like any typo,
1427
+ rather than one carrying its own explanation of the #940 retirement.
1428
+
1429
+ - **Bare dimension-role spellings** — `sheet.dimension(f, "width")`. Use the parameter id,
1430
+ `"width.length"`; `dimension_ids()` on a handle lists the valid ones. The bare role is the
1431
+ *family* spelling: it selects every parameter carrying it, which is how
1432
+ `dimension(step, "step")` quietly declared two measurements. In an authored set, where
1433
+ omission means suppression, silently declaring an extra one is the mirror image of the rule
1434
+ — so it now raises rather than resolving. (A *discriminated* bare role, used with `axis=`,
1435
+ is unaffected: that is how variants like `grid_pitch.length.row` are addressed, and it never
1436
+ warned.)
1437
+ - **The `Sheet.dimension(kind=…, value=…)` call shape** — use `Sheet.measured_dimension(...)`.
1438
+ `dimension` is now solely the ADR 0016 referential verb: it names a feature and a parameter
1439
+ id and reads the value off the geometry.
1440
+
1441
+ Those last two break **without a release that warns you**. They were deprecated after v0.3.9
1442
+ and removed in 0.4.0, so the `DeprecationWarning` never appeared in a released version —
1443
+ upgrading from v0.3.9 or earlier goes straight from working to a raise. That is deliberate
1444
+ (ADR 0016); this entry and `docs/deprecations.md` are the only notice you get, so both
1445
+ failures name their replacement rather than raising about argument counts.
1446
+
1447
+ ### Deprecated
1448
+
1449
+ - **The legacy `Drawing.export` shapes now emit `DeprecationWarning`** (#987) — they were
1450
+ announced as deprecated in v0.3.1 and then said nothing at runtime for four minor releases,
1451
+ which would have made their 0.5.0 removal a silent break. Three cases warn:
1452
+ - `export()` with `formats=` **omitted or `None`** — the legacy default, which writes
1453
+ SVG + DXF and returns an `(svg, dxf)` **tuple** rather than the `{format: path}` dict.
1454
+ - the **`svg=` / `dxf=` booleans**. The suggested replacement names the formats *that call*
1455
+ selected, so following it cannot change what gets written.
1456
+ - passing a boolean **alongside** `formats=`, where it is silently ignored — `formats` wins,
1457
+ and now says so.
1458
+
1459
+ **If you promote `DeprecationWarning` to an error, previously-passing exports will now
1460
+ raise.** Migration: `export(out, formats=("svg", "dxf"))` and read the dict. `make_drawing`
1461
+ is unaffected — it returns the same tuple and does not warn.
1462
+
1463
+ ### Added
1464
+
1465
+ - **`sheet.add_dimension(feature, role)` — ask the planner to carry one more
1466
+ measurement** (ADR 0016 / #872). Referential: it names a feature and a role and
1467
+ carries **no number**, so the value still comes from the geometry and a size lives in
1468
+ exactly one place. It changes *selection*, not derivation — a request can never
1469
+ introduce a number the part does not have. Requesting something the planner already
1470
+ emits is a deliberate no-op, so a script can ask without first knowing the rule set's
1471
+ mind. `sheet.auto_dimensions()` states the source explicitly (optional in this
1472
+ release; #874 makes it mandatory).
1473
+ - **Suppressing a dimension marks it; it no longer leaks into the callout** (ADR 0016 /
1474
+ #875). `PlannedDimension.suppressed` was honoured at thirteen render sites but not by
1475
+ the compound hole-callout path, so a suppressed counterbore still printed. The group
1476
+ keeps its engineering data either way — what changes is whether a value reaches the
1477
+ page. Suppressing the bore ⌀ while a counterbore, spotface or countersink segment
1478
+ remains now **raises** and names the orphan: `⌀20 THRU ⌴ ⌀32 ↓ 1.5` has no reading with
1479
+ its leading term removed, and silently dropping the segments would discard authored
1480
+ intent while silently restoring the head would make the drawing say something the
1481
+ script does not.
1482
+ - **Addressable dimension identity** (#869/#870/#871): every planned measurement now has
1483
+ a stable `DimensionId(feature, parameter)`, and correlated measurements that must be
1484
+ named as one — a grid pattern's row and column pitch, a step ladder — group into an
1485
+ `AddressableDimension`. This is what a `dimension(...)` line will name, and what
1486
+ suppression and provenance key on.
1487
+
1488
+ ### Changed
1489
+
1490
+ - **`Sheet.dimension(kind=…, value=…)` is now `Sheet.measured_dimension(...)`**
1491
+ (ADR 0016 / #873), and `model.declare.authored_dimension` is
1492
+ `model.declare.measured_dimension` to match. This **reserves** the name `dimension` for
1493
+ the referential verb ADR 0016 defines — name a feature and a role, carry no number, let
1494
+ the engine read the value off the geometry — which `Drawing.dimension` already is and
1495
+ `Sheet.dimension` will become. The verb that carries an explicit number needed a name
1496
+ saying so first. The old spelling was a transitional overload during development and is
1497
+ **removed in this same release** (#720) rather than shipping — `Sheet.dimension` is solely
1498
+ the referential verb, and the old keyword call raises with `measured_dimension` named. See
1499
+ *Removed (breaking)* above. Generated AP242 scripts emit `measured_dimension` and so arrive
1500
+ un-deprecated.
1501
+ - **`Sheet` handles address features by identity, not position** (#908/#910/#912). A
1502
+ handle, tolerance, GD&T origin, section request or dimension intent now follows *its*
1503
+ feature through a `features` reorder instead of naming whatever took the slot.
1504
+ Reordering with `reverse()`/`sort()` preserves every reference; deleting or replacing
1505
+ a referenced feature raises rather than silently retargeting a neighbour. `of()` now
1506
+ accepts a handle, and negative indices resolve uniformly across `of()`,
1507
+ `add_dimension()` and the GD&T verbs.
1508
+
1509
+ ### Fixed
1510
+
1511
+ - **`sheet.envelope()` measured the file, not the part** (#977). An AP242 STEP import is a
1512
+ compound of the solid *plus its PMI presentation geometry* — annotation planes, leader
1513
+ curves — and the declared verb measured all of it. On the NIST CTC-01 fixture it declared
1514
+ 1170 × 650 where the part is 800 × 450: an envelope 370 mm too wide, silently, in anything
1515
+ declared by hand. `step_level()` had the same exposure, offsetting every step position by
1516
+ the annotation overhang. Both now measure the solid body, sharing the helper the engine
1517
+ already used for exactly this (`_analyse` and `Sheet.model`).
1518
+ - **A declared envelope's frame origin sat a half-height below a detected one** (#977). It
1519
+ used `bbox_min.Z` where the detector uses the bbox centre. **Dimension output is unchanged** —
1520
+ envelope sizes derive from `bbox_min`/`bbox_max`, and the overall height is compiled from the
1521
+ model bbox — but `frame.origin` is the generic feature *site*, so **a GD&T control frame,
1522
+ surface finish or note targeting a hand-declared `sheet.envelope()` was anchored at the
1523
+ bottom face rather than the centre, and its leader now moves to the centre**. That is a
1524
+ visible change on drawings carrying such an annotation; drawings without one are unaffected.
1525
+ Scripts generated by `--script` were never affected either way: they bake the detected frame
1526
+ explicitly rather than calling the verb.
1527
+
1528
+
1529
+ - **Front-view dimensions join the placement solve instead of committing to the strip**
1530
+ (#894): they previously took strip space before the global solve ran, so a
1531
+ higher-ranked dimension could find its space already gone. Priority only ranks
1532
+ candidates that are *in* the solve.
1533
+ - **A hole callout reads the feature's own `through` fact** (#868) rather than inferring
1534
+ it from whether a depth parameter happens to be present — so a suppressed depth can no
1535
+ longer make a blind hole print `THRU`.
1536
+ - **The balloon ring clears the view it annotates after a hole-table escalation**
1537
+ (#901/#903).
1538
+
1539
+ ## v0.3.9 — 2026-07-26
1540
+
1541
+ ### Added
1542
+
1543
+ - **Repeated recesses are dimensioned as one pattern, not N competing callouts**
1544
+ (#841): identical blind pockets and identical through-slots now collapse to a single
1545
+ grouped `5× 7.9 × 13.6 × 19 DEEP` / `4× SLOT 8 × 30` leader plus `(n-1)× pitch`
1546
+ dim(s). Previously each member competed for its own size dims and some silently
1547
+ dropped for want of strip room — five declared slots rendered only three length dims.
1548
+ Both new kinds (`PocketPatternFeature`, `SlotPatternFeature`) round-trip every
1549
+ surface: declare (`sheet.pocket_pattern(...)` / `sheet.slot_pattern(...)`),
1550
+ recognition from a real solid (coplanar, same-facing members only), the emitted
1551
+ Sheet script, and the editable surface (`dwg.callout(feature)` + `finalize()`).
1552
+ - **`Sheet.section()` and `Sheet.detail()` — ask for the view you need** (#841/#847):
1553
+ a section A–A auto-fires only for a Z-axis hole with a counterbore, spotface, or
1554
+ blind bottom, so a blind pocket's floor and depth stayed hidden-line-only with no
1555
+ supported way to request a cut. `sheet.section(feature)` / `section(at=y)` /
1556
+ bare `section()` force one (the cut plane is validated to lie strictly inside the
1557
+ part), and `sheet.detail()` exposes the existing `detail_view=True` opt-in as a
1558
+ chainable verb.
1559
+ - **External threads are a first-class declared aspect** (#859): the turned analog of
1560
+ the #764 internal thread — `sheet.step(shaft).thread("M3x0.5")`,
1561
+ `sheet.diameter(...).thread(...)`, and `sheet.boss(...).thread(...)` append the
1562
+ spec to the ⌀ callout (`ø3 M3x0.5`) instead of needing a free-text `.note()`. It
1563
+ composes with `.finish()` for Ra-on-thread, keys the callout bucket on `(⌀, thread)`
1564
+ so a threaded and a plain ø6 stay distinct, and now round-trips through the Sheet
1565
+ emitter — including pattern-member holes, closing the symmetric latent #764 gap.
1566
+ Declaration-only: a plain cylinder is geometrically indistinguishable from a
1567
+ threaded one, so there is no recogniser. *(Known limitation: the incremental
1568
+ `callout`/`finalize(only=…)` paths still dedup thread-blind — #863.)*
1569
+ - **Blind obround pocket recognition, including imported STEP** (#837): the blind
1570
+ counterpart of the #816 through-slot work. A stubby floored obround has side walls
1571
+ too short to pair, so it is recovered from its semicircular end caps — clustering
1572
+ faces by axis proximity so the quarter-cylinder split a STEP importer commonly
1573
+ produces recognises as well as build123d's half-cylinder. A sealed internal void
1574
+ (capped at both ends) is correctly *not* a pocket. A real tuner-jig STEP with five
1575
+ blind pockets ships as a fixture.
1576
+ - **`sheet.slot(...).note(...)` / `sheet.pocket(...).note(...)`** (#841/#845): the
1577
+ slot/pocket handle now anchors a note to its own feature, with no `ref=` needed
1578
+ (an explicit `ref` still forwards, as before).
1579
+
1580
+ ### Changed
1581
+
1582
+ - **An anchored note, GD&T frame, or finish relaxes its side instead of silently
1583
+ vanishing** (#841/#855): an annotation declared with an explicit `view=`/`side=`
1584
+ used to drop with only a warning when that strip was full, while the export still
1585
+ reported success. The requested side is now a *preference* — when the strip has no
1586
+ room the placer tries the opposite side, then the two perpendicular sides, taking
1587
+ the first with room (via the same bounds and title-block checks, so it never
1588
+ overshoots). A relaxed placement records an INFO `gdt_side_relaxed` issue naming
1589
+ requested versus actual. Only when no strip fits does it drop.
1590
+
1591
+ ### Internal
1592
+
1593
+ - **The state-bus endgame is closed to its permanent seam** (#830/#840): the
1594
+ solve trace is filled into `BuildState` at the one construction site, the redundant
1595
+ part-model re-attach is gone, and the detail view is now *transactional* — geometry
1596
+ is projected at the detail scale and committed only if dimensions land, so
1597
+ `_drop_view_coordinates` has zero engine callers. With those removed, the #817
1598
+ decision-D method-call exemption narrows from a blanket pass to a named
1599
+ `_LAYOUT_SEAM` allowlist (`_add_view`, `_set_view_coordinates`), accepted as the
1600
+ permanent interactive-layout seam; every other private-method call on the drawing
1601
+ is now flagged.
1602
+ - **A draftwright logo set** (#843) — drafting-idiom mark, all text outlined so the
1603
+ SVGs render font-independently; the README now leads with the lockup.
1604
+ - **A worked multi-feature object-reference example** (#853) in
1605
+ `docs/multi-feature-object-reference-workflow.md`.
1606
+
1607
+ ## v0.3.8 — 2026-07-23
1608
+
1609
+ ### Added
1610
+
1611
+ - **`Drawing.note()` — a public free-text note verb** (#817/#820): place a free-form
1612
+ `Note` at a page point without reaching for the low-level placement primitive —
1613
+ `dwg.note("SEE NOTE 1", at=(x, y))`. This is the safe door for free text that the
1614
+ privatisation below needs.
1615
+ - **Obround (racetrack) through-slot recognition** (#816): a part whose only features
1616
+ are obround through-slots is now recognised, dimensioned, and referenceable for GD&T.
1617
+ Previously such slots were silently undetected — and lint falsely reported the drawing
1618
+ complete. Stubby obrounds, whose straight side walls are too short to pair, are recovered
1619
+ from their two semicircular end caps (a round hole is never mistaken for a slot end).
1620
+
1621
+ ### Changed
1622
+
1623
+ - **The generated Sheet script is now self-describing** (#833): `--script --style sheet`
1624
+ emits a feature-census header, section sub-headers with repeat tallies (`8× R50`), and a
1625
+ plain-language comment on each verb line (`# ⌀8 THRU ×4`, `# slot 40 × 120`, `# R50`).
1626
+ All additions are comment-only, so re-run fidelity is unchanged.
1627
+
1628
+ ### Deprecated
1629
+
1630
+ - **The low-level `Drawing` placement API is now private — the public surface is
1631
+ safe-by-default** (#817): `add`, `place_dim`, and the view-coordinate / build-state
1632
+ plumbing (`set_view_coordinates`, `drop_view_coordinates`, `attach_part_model`,
1633
+ `attach_solve_trace`, `add_view`, `clear_annotations`) are deprecated in favour of the
1634
+ feature-backed verbs (`callout` / `dimension` / `locate` / `note` / …), which route
1635
+ through the layout solve. Each keeps a `@deprecated` (PEP 702) shim for one release —
1636
+ so type-checkers and IDEs flag call sites, not just a runtime warning — and is slated
1637
+ for removal in ~0.5.0.
1638
+
1639
+ ### Internal
1640
+
1641
+ - **`AGENTS.md` — an AI-agent usage guide** (#818) with an executable check, steering
1642
+ agents toward the safe declarative surface rather than the low-level primitives.
1643
+ - **Coverage reporting is governed and a Codecov badge added** (#825/#832).
1644
+
1645
+ ## v0.3.7 — 2026-07-21
1646
+
1647
+ ### Fixed
1648
+
1649
+ - **Turned ⌀ leaders point at the feature, not a corner or across the body**
1650
+ (#794/#798): the diameter callouts on a turned part now centre on the step's
1651
+ mid-length (a boss keeps its frame origin; a ⌀ shared by disjoint steps centres
1652
+ on the longest run), so the arrow lands on the middle of the feature's edge
1653
+ rather than a step corner. A leader that would otherwise cut diagonally through
1654
+ the body — a nested or end feature such as a ⌀6 boss stub — is re-routed out to
1655
+ the nearest clear margin (axis-aware: X-turned → left/right, Z-turned → top/
1656
+ bottom), pointing straight at the feature instead of clipping the silhouette. A
1657
+ re-route that can't be placed clear-and-safe is restored and flagged, and a
1658
+ pinned leader is never moved.
1659
+
1660
+ ### Added
1661
+
1662
+ - **`leader_crosses_silhouette` lint notice** (#796): an info-level structural
1663
+ notice when a leader shaft passes *through* the projected part body (as opposed
1664
+ to a hole callout legitimately exiting an internal hole), so the gap is
1665
+ observable even where routing cannot clear it.
1666
+
1667
+ ## v0.3.6 — 2026-07-21
1668
+
1669
+ ### Fixed
1670
+
1671
+ - **`Sheet.step(boss)` on a prismatic part fails loudly instead of silently
1672
+ dropping the height** (#631): declaring a turned `.step()` at a cylinder that is
1673
+ really an external boss set `orientation="z"`, which made `render_height_ladder`
1674
+ suppress the overall-height dim on the assumption the OD/step chain conveyed it —
1675
+ but on a prismatic box that chain never renders, so the height vanished and
1676
+ nothing replaced it (11 → 10 annotations). The build now detects a `StepFeature`
1677
+ coincident with a `BossFeature` (same axis, origin, diameter) — a combination
1678
+ that can never be legitimate — and raises a clear `ValueError` pointing to
1679
+ `.boss()` (which renders its own ø and height per #632). Legitimate turned shafts
1680
+ have steps but no coincident boss, so they are unaffected.
1681
+ - **`detail_view=True` no longer a silent no-op when the detail can't be placed**
1682
+ (#630): a part whose lint recommends a detail view (`step_dim_dropped`) but whose
1683
+ crowded band is full-width (e.g. a shelled cover's stacked face levels) can't be
1684
+ enlarged legibly *and* still fit alongside the main views — so `detail_view=True`
1685
+ produced a drawing byte-identical to `False`, silently ignoring the opt-in. It
1686
+ now records a `detail_unplaceable` warning naming the reason and an actionable
1687
+ remedy (dimension manually / move onto its own sheet), so the opt-in is
1688
+ observable. Parts whose detail *does* fit are unchanged.
1689
+ - **Imperative `--script` round-trips the title-block fields + sheet furniture**
1690
+ (#775): the imperative flavour reproduced only title/number/tolerance/drawn_by/
1691
+ scale/page, dropping the `material`/`date`/`revision`/`company` fields (#766) and
1692
+ the `frame`/`zones`/`projection` furniture (#767/#768/#769) — so a regenerated
1693
+ imperative script didn't match the direct CLI. They now ride the emitted script's
1694
+ cog config block and its `build_drawing(...)` call (the Sheet flavour already
1695
+ round-tripped them), and the CLI forwards them to `generate_script`.
1696
+
1697
+ ### Internal
1698
+
1699
+ - **Fast-tier dense-sheet canary for the #733/#734 strip-pressure regression**
1700
+ (#737): a synthetic contended-front-strip fixture that drops a step-height
1701
+ principal dim on the pre-#734 commit and stays clean on `main` — closing the
1702
+ fast-tier gap the #733 regression fell through (only the CI-only slow CTC
1703
+ fixtures exercised real strip pressure before).
1704
+ - **Layout-hypothesis fuzz tier is now a reproducible PR gate** (#692):
1705
+ `derandomize=True` makes every run generate the same fixed example set (with
1706
+ `print_blob` for paste-able reproduction), so a failure recurs deterministically
1707
+ instead of surfacing on a seed-dependent minority of runs; exploratory fuzzing
1708
+ moves behind `DRAFTWRIGHT_FUZZ_EXPLORE=1`. The two `test_make_drawing.py` xdist
1709
+ observations (#669) were investigated and found non-reproducible — both tests are
1710
+ non-flaky by construction/measurement on current `main`.
1711
+
1712
+ ## v0.3.5 — 2026-07-20
1713
+
1714
+ ### Fixed
1715
+
1716
+ - **Generated `Sheet` script round-trips `--frame` / `--zones` / `--projection`**:
1717
+ the sheet-furniture flags added in #767/#768/#769 were not emitted into the
1718
+ generated `Sheet(...)` constructor (nor forwarded by the CLI's `--script` path),
1719
+ so a regenerated script dropped the border / zone ruler / projection symbol —
1720
+ the script no longer matched the direct CLI drawing. The emitter now emits
1721
+ `frame=True` / `zones=True` / `projection=…` when set (a plain drawing keeps a
1722
+ clean constructor), and the CLI forwards them to `generate_sheet_script`.
1723
+
1724
+ ### Added
1725
+
1726
+ - **ISO 5457 zone-grid border ruler** (#768): `build_drawing(zones=True)` /
1727
+ `Sheet(zones=True)` / `--zones` draws the grid reference system — numbers 1..
1728
+ along the top/bottom edges, letters A.. (skipping I/O) down the sides — in the
1729
+ band between the frame and the page edge, with the standard division count per
1730
+ A-series page (~50 mm zones). Implies a frame (the ticks sit on it). Off by
1731
+ default → byte-identical. The border ticks are lint-exempt like the frame; the
1732
+ labels are exempt only from the page-bounds check (they legitimately sit outside
1733
+ the drawable), still covered by overlap lint.
1734
+ - **Projection-method symbol** (#769): `build_drawing(projection="third"|"first")` /
1735
+ `Sheet(projection=…)` / `--projection` draws the ISO 5456-2 third-/first-angle
1736
+ glyph in the reserved title-block band (above the title block). Uses the new
1737
+ `ProjectionSymbol` primitive from build123d-drafting-helpers **0.14.1** (the pin
1738
+ is bumped). Off by default → byte-identical. Unlike the page-spanning frame, the
1739
+ small well-placed glyph is *not* lint-exempt — lint covers it so a future
1740
+ mispositioning is caught.
1741
+ - **Opt-in drawn sheet frame / border** (#767): `build_drawing(frame=True)` /
1742
+ `Sheet(frame=True)` / `--frame` draws a border rectangle at the page margin. It's
1743
+ the *content boundary* (Option B), not a rectangle over the drawing: turning it on
1744
+ raises the effective content margin, and because `compose._layout_geometry` is the
1745
+ single authority shared by scale/page selection **and** placement, the reservation
1746
+ flows through `choose_scale` — so a framed drawing may pick a smaller scale / larger
1747
+ page (ADR 0004), with content clearing the border. Default `frame=False` is
1748
+ byte-identical (guarded by the golden + layout-cleanliness suites). The page-spanning
1749
+ border carries an `is_sheet_frame` rider so lint skips it.
1750
+
1751
+ ## v0.3.4 — 2026-07-20
1752
+
1753
+ ### Changed
1754
+
1755
+ - **Imperative `--script` output defaults to PDF and the modern export form**
1756
+ (#709). **Breaking** for regenerated imperative scripts: without `-f`, a
1757
+ regenerated script now writes `<stem>.pdf` (was SVG+DXF via the deprecated
1758
+ legacy tuple), and the script-visible variables changed from
1759
+ `svg_path, dxf_path` to a `paths` dict printed in the requested-format order.
1760
+ This aligns the imperative flavour with the Sheet flavour and the direct CLI,
1761
+ which have defaulted to PDF since #702/#288. Migration: regenerate with
1762
+ `--format svg,dxf` to keep the old outputs. Previously-generated scripts are
1763
+ unaffected (they carry their own export lines).
1764
+ - **`finalize()` drains in the auto-pass's order** (#699 slice b): both build
1765
+ paths now execute the orchestrator's one canonical `_PASS_SEQUENCE` (via the
1766
+ shared `run_stages`), removing the hand-mirrored second orchestration in
1767
+ `Drawing._drain_intents`. For the deferred/finalize path this reorders three
1768
+ things toward auto-pass parity: the turned diameter/step-length set-solves now
1769
+ place *before* the corridor drain (matching the auto-pass's obstacle
1770
+ visibility); slots register after them; and the register-only height-ladder /
1771
+ step-position stages register *after* locations — observable too, since
1772
+ registration order decides corridor key-creation (= drain) order and
1773
+ same-priority tie-breaks (Codex review; the new order is exactly the
1774
+ auto-pass's). Recompose output may shift accordingly; the auto-pass order is
1775
+ unchanged.
1776
+ - **`Sheet.export` speaks the modern export API** (#702): it now takes
1777
+ `formats=` (any of svg/dxf/pdf/png, default **PDF** — matching the CLI
1778
+ default) and `dpi=`, and returns the `{format: path}` dict. Previously it
1779
+ rode the deprecated legacy path, could only write SVG+DXF — the flagship
1780
+ facade couldn't produce a PDF — and returned the old `(svg_path, dxf_path)`
1781
+ tuple. **Breaking** for callers unpacking that tuple: use
1782
+ `sheet.export(stem, formats=("svg", "dxf"))` and read the dict.
1783
+
1784
+ ### Added
1785
+
1786
+ - **Solve-trace / explain mode** (#736, from the #733 post-mortem): opt-in
1787
+ observability for strip placement. `build_drawing(trace=…)` or
1788
+ `DRAFTWRIGHT_TRACE=<path-or-dir>` writes ONE JSON file per build (schema v2)
1789
+ with two record types: `solves` — every corridor solve's key, strip bounds,
1790
+ candidate set, the obstacles that carved the strip (with owning annotation
1791
+ names), the free segments, and each candidate's outcome (placed /
1792
+ dropped-with-reason / deduped / promoted / deferred-to-post-drain) — and
1793
+ `pass_events` — the standalone strip passes plus the *immediate* placers
1794
+ (post-drain machined-feature leader callouts, the turned diameter row/column
1795
+ and step-length set-solves), each with per-item placed/dropped outcomes. So
1796
+ "why did X drop" is one `jq` query (`.solves[].outcomes[]` /
1797
+ `.pass_events[].items[]`). A `placement_unsatisfiable` strip-full drop now
1798
+ also names the top occupants in its lint message (`…strip full; occupied
1799
+ by: …`). Recording-only: an unwritable path logs a warning (never aborts a
1800
+ build), writes are atomic, and the recorder joins `finalize()`'s #647
1801
+ rollback. Default off; zero output change, nil cost when off.
1802
+
1803
+ - **`draftwright.model.authored_dimension`** (#704): the IR constructor behind
1804
+ `Sheet.dimension()`, extracted so `build_drawing(model=…)` callers can author
1805
+ a pre-measured drafting dimension without the `Sheet` façade.
1806
+
1807
+ ### Added
1808
+
1809
+ - **Object-reference script workflow: docs + an inline tip** (#770/#771): a
1810
+ generated `Sheet` script from a **live-source** input (a `module:attr` /
1811
+ `file.py:attr` object spec, or a build123d object) now carries an inline tip
1812
+ showing how to swap each detected-numbers line for a reference to your object
1813
+ (ADR 0011 declare — the size is read off the object). A STEP-sourced script is
1814
+ unchanged (byte-stable — no object to reference). The README documents the full
1815
+ from-a-part-to-an-object-referenced-script workflow.
1816
+ - **Knurl callout verb** (#765): `sheet.diameter(shaft).knurl("0.8")` →
1817
+ `KNURL 0.8 STRAIGHT` (or `.knurl("0.8", "DIAMOND")`). Named sugar over
1818
+ `.note()` with canonical formatting — a knurl is a text callout on a leader,
1819
+ not modelled geometry, so it flows through the existing note path (no new
1820
+ IR/render).
1821
+ - **Standing ISO 7200 title-block fields** (#766): `material`, `date`, `revision`,
1822
+ and `company` (legal owner) are now first-class — settable on `build_drawing` /
1823
+ `make_drawing`, the `Sheet(...)` constructor, and the CLI (`--material` /
1824
+ `--date` / `--revision` / `--company`), and emitted into the generated `Sheet`
1825
+ script when non-default so a re-run reproduces them. Defaults preserve the prior
1826
+ output (revision `A`, the rest blank). The `revision`/`legal_owner` cells the
1827
+ renderer previously hardcoded now flow from these. (The imperative `--script`
1828
+ flavour's title-block round-trip of these fields is a follow-up.)
1829
+ - **First-class thread/tap callout on a hole** (#764): ``sheet.hole(...).thread("M3x0.5")``
1830
+ (and ``declare.hole(..., thread=)``) folds a tap/thread spec onto the hole's existing
1831
+ compound leader (e.g. ``ø2.5 THRU M3x0.5``) — a structured aspect that round-trips, so
1832
+ ``.thread(...).finish(...)`` yields Ra-on-thread. A declaration-only aspect (ADR 0011
1833
+ side-layer: threads are cosmetic, not modelled geometry — no recogniser). The layout
1834
+ width estimator (``_est_planned_bore_callout_width``) now accounts for the thread text so
1835
+ the wider callout is reserved room and not dropped (the #261 estimator/render agreement).
1836
+
1837
+ ### Fixed
1838
+
1839
+ - **Generated imperative script reconstructs side-drilled hole locations**
1840
+ (#426/#133): an X/Y-axis (side-drilled) bore gets `dim_loc_*` position dims in
1841
+ the direct build, but the `--script` emitter emitted a gap comment instead of a
1842
+ `locate()` for non-Z holes (because `locate()`/`render_locations` are Z-plan
1843
+ only, #133). The emitter now emits `dwg.locate(f)` for holes on any axis;
1844
+ `finalize()` routes by the feature's axis — Z-plan holes drain through
1845
+ `render_locations` as before, side-drilled bores through the whole-model
1846
+ `_locate_off_axis_holes` pass (registered at the auto-pass's own
1847
+ `off_axis_across`/`off_axis_along` stages, placed at the shared drain). The
1848
+ Z-only `locate()` live contract is untouched (side-drilled locates never reach
1849
+ it — they route to the off-axis stages). Closes the last direct-vs-generated
1850
+ script parity gap tracked by #426.
1851
+ - **Generated imperative script reconstructs rotational furniture** (#426/#424):
1852
+ a turned/cylindrical part's `dwg.model()` carries a `RotationalFeature`, but the
1853
+ `--script` emitter parked it in the gap-comment list, so an executed
1854
+ reconstruction of e.g. a plain cylinder was missing `dim_od` and the
1855
+ `centerline_front`/`centerline_side` marks the direct build draws. A new
1856
+ `Drawing.rotational(feature)` add-verb records the intent, and `finalize()`
1857
+ drains it through the shared whole-model `render_rotational` at the auto-pass's
1858
+ own `"rotational"` stage — byte-identical to the direct build (no `only=` subset,
1859
+ fixed literal names, so no naming-seam or `⊇`-vs-`==` gap). The emitter now emits
1860
+ `dwg.rotational(f)` for rotational features. Closes the rotational half of the
1861
+ direct-vs-generated-script convergence.
1862
+ - **Emitted `Sheet` script now faithfully reproduces the direct CLI drawing**
1863
+ (#707): the divergence reported against 0.3.3 on the Maquetto GRM-03 part was
1864
+ closed by #709 (script `--format` forwarding) + #661 (finalize detail drain).
1865
+ Locked with a vendored-fixture regression that asserts the full invariant the
1866
+ issue named — same views, annotation inventory, page, scale **and** lint
1867
+ between `build_drawing(...)` and the executed emitted `Sheet` — not just the
1868
+ annotation signature the synthetic parity cases check.
1869
+ - **`_largest_empty_rect` no longer blows up on crowded detail views** (#661):
1870
+ the largest-empty-rectangle placer enumerated every candidate rectangle
1871
+ (O(N⁴) in the coordinate-cut count) — fine for the iso view's handful of
1872
+ obstacles, but the detail-view placer feeds it *every* placed-annotation
1873
+ footprint, so a two-detail turned part reached ~70 obstacles and spent ~13 s
1874
+ in a single call (multi-minute on the slower Windows CI runners, which tipped
1875
+ some jobs over their wall-clock budget). It now skips candidates that provably
1876
+ can't beat the best found so far (a `bisect` past every too-narrow pair, an
1877
+ early `break` once even the widest is too small): the same exact result and
1878
+ tie-break — verified identical to the naïve search across 3 000 random cases —
1879
+ at ~85× the speed on the pathological input (12.6 s → 0.15 s).
1880
+ - **Detail-view height demotion is pin- and provenance-safe** (#661, user
1881
+ review): when a crowded prismatic detail needs the overall-height dim's room it
1882
+ is demoted and the detail retried, but (a) the canonical `dim_height` name was
1883
+ handed to that retry unconditionally — a *pinned* or user-replaced height could
1884
+ be removed; it now passes the same pin/label guards as the generalised
1885
+ `dim_length{n}` path (a pin, ADR 0012, is never demoted), and (b) if the retry
1886
+ also failed, the restore re-added only object/name/view, orphaning the dim from
1887
+ its envelope feature (lost `annotations_of`/`drop`/re-discovery); it now
1888
+ snapshots and restores the feature provenance and pin too.
1889
+ - **`finalize()` resolves queued detail requests — detail views now exist on the
1890
+ edit path** (#661): the finalize drain was missing the auto pass's
1891
+ `detail_request`/`details` stages, so a crowded turned head's queued
1892
+ `DetailRequest` died with the per-run context and a prismatic
1893
+ `detail_view=True` build never even queued one — generated scripts silently
1894
+ lost every DETAIL A/B view. `build_drawing(detail_view=…)` is now persisted on
1895
+ the build state so the drain gates the prismatic request exactly as the auto
1896
+ pass does; the details stage re-projects the iso at sheet scale for the
1897
+ free-rectangle search and refits it after (mirroring the auto pass's ordering,
1898
+ where details place before the iso is fitted); and the overall-height demotion
1899
+ retry now finds the height dim by its envelope attribution, not only the
1900
+ auto-pass `dim_height` name. Three script-parity characterisation tests
1901
+ (single detail, two details, turned head) flip from xfail to passing.
1902
+ - **Y-turned stepped shafts no longer crash** (#661): `render_step_lengths`
1903
+ projected every step span into the front view, where a Y-axis span is end-on
1904
+ (a point) — `Dimension` then raised "start and end points must be different"
1905
+ on BOTH the direct build and a generated script's finalize. Y-axis steps are
1906
+ now skipped (no step render pipeline consumes Y, #731 — mirroring
1907
+ `render_diameters`' x/z bucketing), the emitter flags the gap as a comment
1908
+ instead of emitting an unreplayable `dimension()` verb, and a hand-written
1909
+ Y step-length intent live-replays (surfacing the verb's own behaviour) rather
1910
+ than vanishing into the routed no-op. The Y-axis script-parity
1911
+ characterisation test flips from xfail to passing.
1912
+ - **Boss heights are modeled, rendered, and coverage-checked** (#632): detected
1913
+ and object-declared cylindrical bosses now carry their axial extent through
1914
+ `BossFeature` and the dimension planner. Prismatic bosses receive a linear
1915
+ height dimension in a profile view in addition to their end-on diameter
1916
+ leader. `Drawing.lint()` reconciles that modeled height against the live
1917
+ feature-owned `Dimension`, reporting `boss_height_missing` if it is removed
1918
+ or otherwise absent (demoted to `info` on assembly drawings). This closes the
1919
+ declarative false-negative where a boss's diameter and the overall envelope
1920
+ were documented but its own independent height was not.
1921
+
1922
+ - **`--format` now reaches `--script` output** (#709, from the #702 adversarial
1923
+ review): `--script -f svg` used to silently emit a PDF-producing script — the
1924
+ CLI parsed the flag but never forwarded it. Both emitters now thread it
1925
+ through: the Sheet script emits `sheet.export(stem, formats=(…,))` when
1926
+ non-default (bare call for the PDF default), the imperative template embeds
1927
+ the requested tuple and prints in the **requested** order, and
1928
+ `generate_script` / `generate_sheet_script` grow a `formats=` parameter. The
1929
+ dormant script-parity characterisation module
1930
+ (`test_script_detail_parity.py`) is also un-skipped so its strict xfails give
1931
+ the remaining gaps CI signal.
1932
+
1933
+ - **Authored fillet, flat, groove, pocket, plate and slot tolerances now render**
1934
+ (#725/#726/#727/#728/#729/#730 / #698): the four leader-callout kinds join
1935
+ `_CONVENTION` and their renderers (`render_fillets`/`render_flats`/
1936
+ `render_grooves`/`render_pockets`) — and the plate-thickness and slot
1937
+ width/length linear passes
1938
+ (`render_plates`/`render_slots`, explicit `"linear"` entries) — consume the
1939
+ planner's `DimensionGroup`s, binding each
1940
+ planned dim explicitly by `(role, kind)` — so a `decorations`-authored ± /
1941
+ limit tolerance reaches the placed callout (`R8 ±0.1`, `17 ±0.2 A/F`,
1942
+ `4 ±0.1 WIDE × ø16 ±0.5`, `18 ±0.2 × 30 ±0.2 × 5 ±0.2 DEEP`, a `10 ±0.1`
1943
+ thickness dim, an `8 ±0.1` slot width; on a
1944
+ multi-value label each suffix rides its own number). Previously all six
1945
+ passes formatted raw feature fields and silently dropped it — the latent #629
1946
+ bug class the ADR 0015 bypass list documents. The fillet `n× R` and flat
1947
+ double-D/hex collapses stay render-side (first-authored tolerance wins, the
1948
+ `render_diameters` precedent); a pocket's three values — and a slot's
1949
+ width + length — share one authored
1950
+ tolerance (all kind `"length"`). The slot's model-derived datum position dim
1951
+ and its corridor/immediate placement mechanics are untouched. Untolerated
1952
+ labels/views/placement are unchanged.
1953
+ - **Authored chamfer tolerances now render** (#724 / #698): `render_chamfers`
1954
+ consumes the planner's `DimensionGroup` (chamfer joins `_CONVENTION` as a
1955
+ leader), so a `decorations`-authored ± / limit tolerance on a chamfer leg
1956
+ reaches the placed callout (`C12 ±0.2`). Previously the pass formatted raw
1957
+ feature fields and silently dropped it — the latent #629 bug class the ADR
1958
+ 0015 bypass list documents. Untolerated chamfer labels/views are unchanged.
1959
+ - **`model.chamfer(face=…)` rejects a non-planar face** (#704): the declared
1960
+ front-end silently accepted a curved face (`normal_at()` does not fail on
1961
+ one) and read garbage legs off it — e.g. a fillet or countersink face became
1962
+ a bogus `ChamferFeature`. It now raises the documented `ValueError`, using
1963
+ the recogniser's own planarity gate (`classify_bevel`), so declared and
1964
+ detected chamfers classify identically by construction.
1965
+
1966
+ ### Fixed
1967
+
1968
+ - **CTC-02/04 no longer drop step-height / overall-height dims** (#733): when
1969
+ the height ladder joined the corridor solve (#689) it silently moved from
1970
+ "places early" to "registers early, places at the drain" — so the immediate
1971
+ machined-feature leader callouts (pockets on CTC-04) filled the front-right
1972
+ strip first and the FORCED principal dims hard-dropped
1973
+ (`placement_unsatisfiable`), a priority inversion the old stage ordering had
1974
+ prevented implicitly. The leader-callout stages (chamfers/fillets/flats/
1975
+ pockets) now sit **after the corridor drain** in `_PASS_SEQUENCE` (joining
1976
+ grooves, which already placed post-drain for exactly this reason): principal
1977
+ dims win by construction, and a callout with no clear room yields with a
1978
+ warning, never a principal-dim error. Callout leaders may shift (they now
1979
+ route around the drained dims); on the `hex_bar` golden this *recovers* a
1980
+ silently starved `m_env_width` envelope dim.
1981
+
1982
+ ### Removed
1983
+
1984
+ - **Three orphaned root exports** (#704): `draftwright.recognise_face_levels`
1985
+ (one recogniser of ~12 — incoherent as a top-level surface; import it from
1986
+ `draftwright.recognition`), `draftwright.dedup_diams` (an analysis internal),
1987
+ and `draftwright.fix_svg_page_size` (still available from `draftwright.export`
1988
+ and the `draftwright.make_drawing` facade). None had any internal or test
1989
+ consumer via the root API.
1990
+
1991
+ ### Deprecated
1992
+
1993
+ - **The seven `Drawing` compat aliases** (`_named`/`_anno_view`/`_pinned`/
1994
+ `_build_issues`/`_pattern_callouts`/`_patterned_holes`/
1995
+ `_dropped_callout_diams`) are formally on notice (#699 slice c, tracked by
1996
+ #720): removal target **0.4.0**, per ADR 0005 §4. Read through the public
1997
+ surface instead — `annotations()`, `iter_annotations()`, `get_annotation()`,
1998
+ `view_of()`, `registry.pinned_names()`/`is_pinned()`, and the new
1999
+ `registry.issues`. All external production and test call sites in this repo
2000
+ are already redirected (`drawing.py` internals ride the aliases until the
2001
+ #720 deletion).
2002
+ - **`draftwright.sheet_dsl`** (belated announcement — the alias shim shipped
2003
+ in 0.3.1 when #640 renamed the module to `draftwright.sheet`): importing it
2004
+ warns; it will be removed in **0.4.0**. Import from `draftwright.sheet`, or
2005
+ just `from draftwright import Sheet`.
2006
+
2007
+ ## v0.3.3 — 2026-07-18
2008
+
2009
+ The **layout-quality release**: the occupancy model gets honest about ink shape,
2010
+ the last solver-invisible pass joins the corridor solve, and crossed labels shift
2011
+ themselves clear. Also completes the #635 consolidation epic (ADR 0005 fully
2012
+ executed; ADR 0009's no-invisible-occupant guarantee now holds by construction
2013
+ for every auto-pass).
2014
+
2015
+ ### Added
2016
+
2017
+ - **Witness-crossing label shifts** (#690): a dimension label crossed by another
2018
+ dimension's witness stroke now shifts along its own line to clear it — a
2019
+ deterministic post-drain pass using the repair machinery (destination-aware,
2020
+ axis-aligned only, pinned dims exempt).
2021
+
2022
+ ### Changed
2023
+
2024
+ - **L-shaped occupancy** (#685): placement obstacles decompose into per-stroke
2025
+ boxes + label box (helpers ≥0.14 `segments`) instead of one hull, with
2026
+ preset-aware arrowhead pads. View-corner contention between perpendicular
2027
+ strips disappears by construction (a plate-thickness dim returns to its
2028
+ natural strip); packing tightens. The cleanliness ratchet mirrors the model —
2029
+ only transverse stroke crossings are exempt, so parallel overprints and label
2030
+ contacts are detected honestly (twelve hull-artifact allowlist entries burned
2031
+ down; the legitimate shared-datum running-dimension pairs are documented).
2032
+ - **Height ladder joins the corridor solve** (#636): step heights + overall
2033
+ height are corridor candidates (the leapfrog witness cursor survives as a
2034
+ deterministic build-time chain; the overall height stacks outermost by
2035
+ construction and packs tighter). An explicitly-requested crowded-step detail
2036
+ view now *deliberately* demotes the overall-height dim when the sheet cannot
2037
+ hold both (previously the carve dropped it silently); a Y-drilled hole's Z
2038
+ location joins the front running ladder per the documented routing.
2039
+ - **One typed `BuildState`** (#639): the drawing's build context (analysis,
2040
+ part model, lint geometry caches) consolidates into a single object filled at
2041
+ one builder site, single-writer-guarded; the render passes make zero private
2042
+ `Drawing` reads (fail-closed empty allowlist).
2043
+
2044
+ ### Fixed
2045
+
2046
+ - A prismatic detail view no longer loses both the detail *and* the height dim
2047
+ when space is tight (transactional demotion).
2048
+ - The GD&T alternate-strip fallback defers until every corridor has drained, so
2049
+ it can never preempt a sibling strip's reserved corner.
2050
+
2051
+ ## v0.3.2 — 2026-07-18
2052
+
2053
+ The **performance release**: the #602 perf loop (six PRs) plus the
2054
+ build123d-drafting-helpers 0.14 upgrade. A dimension-dense drawing now builds
2055
+ roughly **14× faster** than v0.3.1 (grid-plate benchmark 40 s → 2.8 s; NIST
2056
+ CTC-02 ~107 s → well under half); the full test tier dropped ~14 min → ~3.5 min.
2057
+
2058
+ ### Performance
2059
+
2060
+ - **Placement measures, then builds** (#678, #679): corridor/strip candidates are
2061
+ evaluated on analytical footprints (`dim_footprint`) or a single probe box; an
2062
+ accepted candidate builds its OCC geometry exactly once and re-validates the
2063
+ real box. Dimension constructions on the scattered-plate benchmark: 59 → 21
2064
+ (= the placed count).
2065
+ - **Detect once** (#682): the sizing model `_analyse` builds pre-scale is stored
2066
+ on `Analysis.model` and reused as the render model — restoring ADR 0008
2067
+ Amendment 5 ("one inventory, detected once"). ~13 s off CTC-02.
2068
+ - **Fillet recognition de-quadratised** (#683): edge→faces adjacency map replaces
2069
+ the O(faces²×edges²) `IsSame` sweep (3.7 M calls on CTC-02); 10.8 s → 4.6 s,
2070
+ byte-identical records.
2071
+ - **Lint bounding boxes memoised** (#681): `Drawing.lint()` 0.88 s → 0.07 s per
2072
+ call (identity+location-token-checked cache beside the #143 view-edge cache).
2073
+ - **DXF viewport from the known page window** (#680): skips `ezdxf.zoom.extents`'
2074
+ per-entity walk; ~2.4 s per DXF export.
2075
+ - **helpers 0.14** (#684): boolean-free dimension ink (upstream #177, filed and
2076
+ fixed same-day) — ~25 ms per `Dimension`, was ~234 ms.
2077
+
2078
+ ### Changed
2079
+
2080
+ - **Tight-span dimensions now render honestly** (helpers 0.14): ISO outside
2081
+ arrows where 0.13 silently degraded to a bare line. Ink extents grow on such
2082
+ dims; placement models the flip analytically. Two placement-golden fixtures
2083
+ re-blessed (ink extent only).
2084
+ - Diameter-row leader labels space by **measured text width** and repair
2085
+ helpers' shelf-flip collisions (crowded rows only; ordinary rows unchanged).
2086
+ - A plate-thickness dim whose home strip corner is structurally contested falls
2087
+ through to the opposite strip / side view instead of dropping (#684; the
2088
+ underlying one-box-occupancy artifact is tracked as #685).
2089
+
2090
+ ### Fixed
2091
+
2092
+ - `recognise_fillets` neighbour tie-break is order-independent (#683 review).
2093
+ - Lint's annotation-box cache prunes departed objects and detects in-place
2094
+ relocation (#681 review).
2095
+
2096
+ ## v0.3.1 — 2026-07-17
2097
+
2098
+ Also lands the **#635 consolidation epic** (P1–P6, all six children closed): the render
2099
+ layer's state bus is retired (`annotations/` reads zero `Drawing` privates,
2100
+ machine-enforced), the three complexity hotspots are split (nesting ≤2), the whole-package
2101
+ import DAG + a test-import ratchet are enforced, and the test suite gains a Hypothesis fuzz
2102
+ tier + relational cross-kernel placement gates. All internal / behaviour-preserving
2103
+ (byte-identical golden) — no output change.
2104
+
2105
+ ### Added
2106
+
2107
+ - **PNG raster export** — `--format png` on the CLI, or `Drawing.export(formats=("png",))`.
2108
+ Rendered SVG → PDF → PNG via **pypdfium2** (Apache-2.0, Google PDFium BSD-3) + **Pillow**
2109
+ (HPND) — both permissively licensed and pure-wheel, so PNG works cross-platform with **no
2110
+ native cairo** (ADR 0006) and keeps the render path dual-license-friendly. `dpi=` sets the
2111
+ raster resolution.
2112
+
2113
+ ### Changed
2114
+
2115
+ - **Unified export API** — `Drawing.export(out, *, formats=("pdf",)) → {format: path}` is now
2116
+ the single entry point over `svg`/`dxf`/`pdf`/`png`; it returns a dict and internally handles
2117
+ the SVG→PDF→PNG intermediate chain (writing/cleaning up intermediates it wasn't asked to keep).
2118
+ The CLI `--format` gains `png`.
2119
+
2120
+ ### Deprecated
2121
+
2122
+ - `Drawing.export_pdf()` — use `export(formats=("pdf",))["pdf"]`; the method now warns.
2123
+ - The legacy `export(svg=, dxf=)` boolean keywords and their `(svg_path, dxf_path)` tuple return
2124
+ — kept for back-compat; prefer `formats=[...]` (the dict API).
2125
+
2126
+ ## v0.3.0 — 2026-07-14
2127
+
2128
+ **Breaking — feature-recogniser API renamed** (ADR 0013 / #568): *deliberate* breaks with
2129
+ **no compatibility aliases**, hence the minor bump. Only import paths and callable
2130
+ signatures change — but note this release also **changes drawing output**: new machined-
2131
+ feature callouts, corrected leader anchors, and a reoriented isometric (see Added / Fixed).
2132
+
2133
+ ### Added
2134
+
2135
+ - **Machined-feature recognition — the #148 epic.** #135 deliberately handled only enclosed
2136
+ rectangular through-slots; this broadens recognition, and each feature is recognised **and**
2137
+ dimensioned **and** authorable (recognise + emit + declare, ADR 0011):
2138
+ - **Blind slots + pockets** — floored recesses, `W × L × D DEEP` (#603).
2139
+ - **Cross / intersecting slots** collapse into single continuous channels (#604).
2140
+ - **Machined flats** on round stock — `{across} A/F` (#605).
2141
+ - **Turned / circlip grooves** — `{width} WIDE × ø{floor}` (#606).
2142
+ - **Slots / pockets in curved stock** — arc-bounded walls (#607).
2143
+ - **Radiused-end (obround) slots** now report their **overall** length (#613).
2144
+ - **Chamfer callouts** — `C{leg}` / `{leg}×{angle}°` off each bevel (#560).
2145
+ - **Fillet radius callouts** — `R{radius}` off each rounded edge (#561).
2146
+ - **Plate / wall thickness** dimensions on multi-plate prismatics (#559).
2147
+ - **Prismatic step-position** dimensions locating each shoulder (#555).
2148
+ - **Countersink callouts** — `ø6 THRU ⌵ ø14 × 90°` (major-ø + included angle), like the
2149
+ counterbore `⊔ ø.. ↧..` (#558). The countersink rides on `HoleRecord`/`HoleFeature`, so
2150
+ grouping and the callout-width layout estimate account for it.
2151
+ - **Every recognised feature round-trips all three ADR-0011 surfaces (#574).** Beyond
2152
+ detection, each feature now **emits** into the generated `--script` Sheet and is
2153
+ **declarable** on the fluent `Sheet` surface — authored from scratch or read off a
2154
+ build123d object: chamfer (#576), countersink (#575), plate (#577), step-position (#585),
2155
+ fillet (#561), plus all the #148 features above.
2156
+ - **`draftwright.score.feature_census(part)`** — a per-kind recognised-feature census, a
2157
+ measurement tool for tracking recognition coverage (#608).
2158
+
2159
+ ### Changed
2160
+
2161
+ - **Feature recognisers renamed `find_`/`analyse_` → `recognise_`** and their
2162
+ tuning/dependencies made keyword-only: `find_holes`→`recognise_holes`,
2163
+ `find_bosses`→`recognise_bosses`, `find_hole_patterns`→`recognise_hole_patterns`,
2164
+ `find_slots`→`recognise_slots`, `find_plates`→`recognise_plates`,
2165
+ `find_chamfers`→`recognise_chamfers`, `find_turned_steps`→`recognise_turned_steps`,
2166
+ `find_step_shoulders`→`recognise_step_shoulders`, `analyse_face_levels`→
2167
+ `recognise_face_levels` (the last was also re-exported at top level as
2168
+ `draftwright.analyse_face_levels` → now `draftwright.recognise_face_levels`).
2169
+ Cylinder-analysis substrate (`analyse_cylinders`, `full_cylinders`,
2170
+ `feature_diameters`) keeps its names.
2171
+ - **`recognise_step_shoulders` now returns `list[StepShoulder]`** (a frozen dataclass)
2172
+ instead of raw `(axis, position)` tuples; `levels` is keyword-only.
2173
+ - **`recognise_turned_steps` now returns `list[TurnedStep]`** (empty for a non-turned
2174
+ part) instead of `TurnedProfile | None`. `TurnedStep` gained an `axis` field (each step
2175
+ is now a self-contained record); `TurnedProfile` remains only as a pipeline aggregate
2176
+ (`TurnedProfile.from_steps(steps)`), no longer a recogniser return.
2177
+ - **`recognise_face_levels` now returns `list[FaceLevel]`** (a frozen `FaceLevel(z)`
2178
+ record) instead of `list[float]`.
2179
+ - **Recognition record classes renamed to avoid clashing with the IR `Feature` types:**
2180
+ `draftwright.recognition.HoleFeature` → `HoleRecord`, `BossFeature` → `BossRecord`
2181
+ (the IR `draftwright.model.ir.HoleFeature`/`BossFeature` are unchanged).
2182
+ - **Page/scale sizing now reads the IR, not recognition records (ADR 0008 / #588).** The
2183
+ part model is built up front and drives sheet sizing, so detected and declared parts share
2184
+ one path and no recogniser record reaches the sheet estimators. Byte-identical to the old
2185
+ estimators except a hole pattern that shares a machining spec with loose holes now sizes
2186
+ separately (no phantom merged `N×`), which can shift a tightly-packed such part's layout.
2187
+
2188
+ ### Fixed
2189
+
2190
+ - **Isometric view orientation (#620).** The iso camera viewed the *rear* (+Y) side against a
2191
+ front-view orthographic set, mirroring asymmetric features; it now views from the same
2192
+ front (−Y) / plan (+Z) / right (+X) combination.
2193
+ - **Chamfer leaders (#621)** anchor on the bevel-face centroid (the diagonal middle), not the
2194
+ supporting plane's parametric origin (which projected to a corner/endpoint).
2195
+ - **Fillet leaders (#622)** anchor on the radius surface, not the face bounding-box centre
2196
+ (which sat off the round, near the virtual sharp corner).
2197
+ - **Authored `ref_pts`-only dimensions** now render (with Z-strip sizing) (#562).
2198
+ - **Pitch-dim labels** are cleared off centrelines / bolt circles at creation time, not via
2199
+ the repair loop (#129).
2200
+
2201
+ ### Removed
2202
+
2203
+ - **`draftwright.features` compatibility module** — deleted. Import `recognise_slots`/
2204
+ `Slot` from `draftwright.recognition` instead.
2205
+
2206
+ ## v0.2.13 — 2026-07-09
2207
+
2208
+ **Compose-then-pack is now the layout authority.** This patch finishes the
2209
+ ViewBlock footprint migration: estimated and measured layout paths now use the
2210
+ same composed block model, furniture declarations reserve their real footprint,
2211
+ and measure/repack iterates until stable instead of trusting one measured pass.
2212
+
2213
+ ### Changed
2214
+
2215
+ - **ViewBlock footprints now drive the orthographic stack.** The old
2216
+ estimator-only plan-view balloon lift is gone; plan balloon headroom is part
2217
+ of the plan block in both estimated and measured/repack layout. (#112)
2218
+ - **Measure/repack iterates to a fixed point.** Repack now keeps measuring and
2219
+ repacking while view-owned footprints continue to grow, with bounded
2220
+ convergence and tests for multi-pass growth. (#302)
2221
+ - **Estimated view-block composition is explicit.** Scale/page fitness, repack,
2222
+ and furniture placement share the same composed block semantics instead of
2223
+ rebuilding corridor estimates independently.
2224
+
2225
+ ### Fixed
2226
+
2227
+ - **Declared callout furniture reserves layout footprint.** User-declared
2228
+ furniture is now measured and fed into the layout model, reducing post-hoc
2229
+ collisions from declarations that previously existed only at render time.
2230
+ - **Annotation-box composition owns strip sizing.** Strip depths reduce from
2231
+ composed annotation boxes, making footprint sizing less dependent on divergent
2232
+ scalar estimates.
2233
+
2234
+ ### Documentation
2235
+
2236
+ - **Layout architecture docs reflect the current state.** ADR 0004 and the
2237
+ layout primer now mark the core layout-authority tranche complete and separate
2238
+ remaining work into hardening, coverage, detail-view, and manual-intent phases.
2239
+
2240
+ ## v0.2.12 — 2026-07-09
2241
+
2242
+ **Sheet scripts now round-trip authored layout intent.** This patch finishes the next
2243
+ slice of the layout-authority work: below/right ladders, pinned edits, height ladders,
2244
+ detected envelopes, and AP242 authored dimensions now survive the declarative Sheet path
2245
+ without being invalidated by post-hoc placement or raw imported metadata.
2246
+
2247
+ ### Added
2248
+
2249
+ - **Pinned edit intents route through the corridor solve.** `locate(..., pin=True)` and
2250
+ pinned dimensions now become priority-ranked candidates in the shared corridor instead
2251
+ of fixed post-hoc edits, so user intent participates in the same ordering/spacing model
2252
+ as automatic dimensions. (#511)
2253
+ - **AP242 dimensional PMI lowers to authored drafting dimensions.** Imported AP242
2254
+ size/location dimensions now become `AuthoredDimension` IR and generated Sheet scripts
2255
+ emit `sheet.dimension(...)`; unsupported PMI records remain explicit raw fallbacks.
2256
+ (#503, #422, #62)
2257
+
2258
+ ### Changed
2259
+
2260
+ - **`place_dim` is deprecated.** Manual dimensions should use pinned candidate/dimension
2261
+ intent instead of the old incremental edit path.
2262
+ - **Prismatic height ladders and detected envelopes round-trip through Sheet scripts.**
2263
+ Generated scripts now preserve `StepLevelFeature` and detected `EnvelopeFeature`
2264
+ fallbacks, preventing CTC01-style ladder swaps and raw STEP-envelope remeasurement
2265
+ drift.
2266
+
2267
+ ### Fixed
2268
+
2269
+ - **Below/right corridors now share one solve.** Side-hole locations, height ladders,
2270
+ PMI/GD&T, envelope dims, and related below/right strip occupants negotiate in the shared
2271
+ corridor rather than competing through separate late passes. (#477)
2272
+ - **Sheet script parity is tighter.** Generated scripts preserve member positions, step
2273
+ levels, AP242 authored dimensions, raw PMI fallbacks, and detected envelopes so
2274
+ direct-vs-script CTC01 output has matching annotation names.
2275
+
2276
+ ## v0.2.11 — 2026-07-08
2277
+
2278
+ **Automatic layout now has one authority.** Page/scale selection, section placement,
2279
+ furniture footprints, and table/balloon escalation now negotiate through the same layout
2280
+ fitness model instead of using separate fixed offsets or first-fit policies after the main
2281
+ solve.
2282
+
2283
+ ### Fixed
2284
+
2285
+ - **Solver placement paths are more consistent.** STEP PMI dimensions now queue into
2286
+ the shared ADR 0009 corridor solve instead of carving after the drain; front-view
2287
+ hole callouts use the strip solver instead of fixed row stepping; pitch-dimension
2288
+ fallback searches bounded, obstacle-aware positions; repair no longer hides
2289
+ `annotation_overlap` with a fixed-step nudge; and step-count sizing now handles
2290
+ non-convergence conservatively. (#524)
2291
+ - **Page/scale fitness is shared across initial selection and repack.** Later layout
2292
+ passes now compare candidates with the same model as the page chooser, preventing a
2293
+ nominally "better" repack from invalidating the original layout decision. (#519)
2294
+ - **Section A-A participates in layout selection.** Section placement is measured as
2295
+ part of page/scale fitness, so a section view is no longer a fixed-offset afterthought
2296
+ that can disappear on dense sheets. (#515)
2297
+ - **Furniture placement reserves full rendered footprints.** Section arrows, detail
2298
+ views, balloons, hole/data tables, and other furniture now reserve/check their true
2299
+ rendered footprint instead of just their labels. (#518)
2300
+ - **Hole/data tables and balloon rings escalate through the layout model.** Dense table
2301
+ and balloon outputs can move or negotiate for available room rather than dropping from
2302
+ greedy first-fit placement when a small adjustment would fit. (#516, #517)
2303
+
2304
+ ## v0.2.10 — 2026-07-07
2305
+
2306
+ **Declarative-surface fidelity and layout-engine unification.** Editing and re-running a
2307
+ declarative `Sheet` script now reproduces more of the drawing (title block, layout, PMI on the
2308
+ declared path), a new lint catches stale declarations, and an intentional scale is respected.
2309
+ Internally the annotation placer moves further onto the single collect-then-solve (ADR 0009),
2310
+ and `kiwisolver` is dropped as a dependency.
2311
+
2312
+ ### Added
2313
+
2314
+ - **Declaration-vs-geometry reconciliation lint** — `lint()` now flags a declared cylindrical
2315
+ feature (hole/boss/step) with **no matching geometry** in the part (`declared_feature_absent`):
2316
+ a callout drawn over empty space because the part was edited but the declaration went stale.
2317
+ Closes the last gap in the "did my edit break the drawing?" loop (over-declaration; coverage
2318
+ lint already caught under-declaration). Matches on axis + ⌀ + in-plane position + bore/boss
2319
+ polarity. Gated on a caller-supplied `model=` (detection can't over-declare). (#487)
2320
+ - **Title-block + layout aspects on the `Sheet` DSL** — `Sheet(drawn_by=…, tolerance=…, scale=…,
2321
+ page=…)`; the generated `--script` reproduces them, and the CLI forwards
2322
+ `--drawn-by`/`--tolerance`/`--scale`/`--page` on the sheet path (no more inert-flag warning). (#474)
2323
+ - **PMI reproduced on the declared-model path** — `build_drawing(path, model=…, pmi="annotate")`
2324
+ synthesises the STEP AP242 PMI into the declared model, so a declared build draws the same PMI
2325
+ dimensions as the detection path. (#472)
2326
+
2327
+ ### Changed
2328
+
2329
+ - **An intentional explicit `scale=` below the legibility floor is honoured with a warning**
2330
+ rather than rejected. A part deliberately drawn at 1:1 (or `Sheet(scale="1:10")`) whose smallest
2331
+ feature falls under the 10 mm legibility floor now renders (annotations may crowd) instead of
2332
+ raising `ValueError`; only a genuinely degenerate scale (< 0.1 mm projected, where OCCT arcs
2333
+ collapse) is rejected. The floor now binds only the *automatic* scale. (#489)
2334
+ - **`kiwisolver` is no longer a dependency.** The 1-D strip solve delegates to the built-in
2335
+ deterministic minimum-total-leader-length PAVA algorithm, which supersedes the Cassowary
2336
+ solver — same placement contract, one fewer third-party dependency. (#507)
2337
+
2338
+ ### Fixed
2339
+
2340
+ - **Dimension placement is more robust under contention.** Candidate **priority** is plumbed
2341
+ through the shared corridor solve, so an authored GD&T frame is kept over a lower-value
2342
+ automatic dimension when a strip is over capacity (#357). Rotational concentric-bore leaders
2343
+ are now bounded to the front-view height with ranked drops, and pitch dimensions are placed
2344
+ onto the obstacle-aware per-side zone-strip solve — retiring the last fixed-offset,
2345
+ count-based stacking placers (the shape behind earlier dense-sheet overruns). (#374)
2346
+
2347
+ ## v0.2.9 — 2026-07-06
2348
+
2349
+ **Declarative GD&T, datums, and surface finish (ADR 0011 Phase 2b/2c).** The `Sheet` DSL can
2350
+ now author the drawing information geometry can't carry — geometric tolerances, datum feature
2351
+ symbols, and surface-finish marks — by pointing at a build123d feature or face. They render
2352
+ through the auto-layout engine as first-class ADR 0009 corridor candidates, placed and spaced
2353
+ crossing-free alongside the automatic dimensions (not a post-hoc overlay).
2354
+
2355
+ ```python
2356
+ sheet.datum("A", base_face)
2357
+ sheet.control(bore).position(0.1, to="A", diameter=True).perpendicularity(0.05, to="A")
2358
+ sheet.diameter(journal).finish("0.8")
2359
+ ```
2360
+
2361
+ ### Added
2362
+
2363
+ - **Feature control frames (ISO 1101)** — `sheet.control(ref)` returns a chainable builder with
2364
+ one method per **all 14** characteristics (`position`, `flatness`, `perpendicularity`,
2365
+ `cylindricity`, `circular_runout`, `total_runout`, …); `to=` names the referenced datum(s),
2366
+ `diameter=` prefixes the ⌀ tolerance zone, `modifier=` adds a material-condition symbol (Ⓜ/Ⓛ/Ⓟ).
2367
+ - **Datum feature symbols (ISO 5459)** — `sheet.datum("A", face_or_feature)`.
2368
+ - **Surface finish (ISO 1302, Ra)** — `.finish("1.6")` on a hole / diameter / step handle, or
2369
+ `sheet.finish(ra, face)`.
2370
+ - The target view + strip side are **derived from the referenced geometry** (a feature's axis →
2371
+ face-on view, a planar face's normal → edge-on view); `view=`/`side=` override.
2372
+ - Render core: `ControlFrame` / `DatumRef` / `Finish` IR features placed by `render_gdt` as
2373
+ first-class corridor candidates, with the real-footprint plumbing needed to space wide frames.
2374
+
2375
+ ### Fixed
2376
+
2377
+ - A control frame referencing an **undeclared datum letter** now warns at build.
2378
+ - GD&T placement is **title-block aware** and **falls through to the opposite strip side** before
2379
+ dropping a frame, so stacked control frames place robustly rather than vanishing or overlapping
2380
+ the title block.
2381
+ - An invalid glyph spec / degenerate leader in a caller-supplied model **drops the one item with a
2382
+ warning** instead of crashing the whole build (the IR is public input).
2383
+ - Turned-shaft rotational furniture now reproduces correctly through the declared-model path
2384
+ (`model=` / `Sheet`) (#476).
2385
+
2386
+ ## v0.2.8 — 2026-07-06
2387
+
2388
+ **`--script` now defaults to the declarative `Sheet` DSL.** The editable script the CLI
2389
+ writes is the beautiful-Python surface — one commentable line per feature — for both STEP and
2390
+ build123d-object input.
2391
+
2392
+ ### Changed
2393
+
2394
+ - **`draftwright … --script` now emits the declarative `Sheet` script by default** (was the
2395
+ imperative edit-verb reconstruction). The imperative reconstruction is still available via
2396
+ `--script --style imperative`. For prismatic parts the generated script reproduces the same
2397
+ drawing as a direct build; a turned shaft's centre lines and base-diameter style are not yet
2398
+ identical (the remaining validity gap, tracked separately). `--style imperative` now errors
2399
+ clearly on a `module:attr` object spec (it reads a STEP file; use the default `sheet`).
2400
+ The Sheet DSL doesn't yet model the title-block / layout aspects the imperative script
2401
+ embeds, so `--script --style sheet` **warns** when `--drawn-by`, `--tolerance`, `--scale`
2402
+ or `--page` is set rather than silently ignoring it (use `--style imperative` to embed them).
2403
+
2404
+ ## v0.2.7 — 2026-07-05
2405
+
2406
+ **The three authoring modes (ADR 0011 Amendment 1).** Naming how you drive draftwright —
2407
+ *just do it* / *auto then tweak* / *generate an editable script* — and filling in the third:
2408
+ a declarative `Sheet`-DSL generator, and number-free authoring against the build123d objects
2409
+ you built.
2410
+
2411
+ ### Added
2412
+
2413
+ - **`draftwright part.step --script --style sheet`** — generate an **editable declarative
2414
+ `Sheet` script** from a detected part: one commentable line per feature (hole / diameter /
2415
+ step / slot / pattern / envelope), views noted, the auto-section flagged. Comment a line out
2416
+ to drop that feature; the script re-runs to the drawing. The existing `--script` (imperative
2417
+ edit-verb reconstruction) stays the default (#461).
2418
+ - **Object-reading `.cbore(tool)` / `.spotface(tool)`** on a declared hole — read the
2419
+ counterbore/spotface's ⌀ **and** depth off the tool object + part (depth measured from the
2420
+ local open face, so a rib/wall elsewhere doesn't skew it). A `Sheet`-declared drawing can now
2421
+ carry **zero magic numbers** and track the geometry parametrically (#462).
2422
+ - **`Sheet.of(feature)`** — a fluent handle onto an **existing / detection-seeded** feature (by
2423
+ the build123d object, an index, or the `Feature` itself), so you can `.fit(...)` /
2424
+ `.tolerance(...)` a feature from `Sheet.from_part()` without re-declaring it (#463).
2425
+
2426
+ ### Docs
2427
+
2428
+ - **ADR 0011 Amendment 1** — the three authoring modes and the mode-3 generation surface;
2429
+ records the decision to emit **honest detected numbers** for a detected part rather than
2430
+ fabricate a build123d reconstruction that misrepresents the geometry (#464).
2431
+
2432
+ ## v0.2.6 — 2026-07-05
2433
+
2434
+ **ADR 0011 — the IR as a public input: declare features, don't only detect them.**
2435
+ Detection stays the default, but the caller can now *supply* the feature model, so a
2436
+ part you built parametrically reads as its own drawing and misdetection becomes
2437
+ recoverable by construction. Plus the first Phase-2 aspect layer (tolerances + fits).
2438
+
2439
+ ### Added
2440
+
2441
+ - **`build_drawing(part, model=…)`** accepts a caller-supplied `PartModel` (or a
2442
+ `Sequence[Feature]`); when given, **detection is skipped** and the auto-pass dimensions
2443
+ the declared features. Detection and declaration are two producers of the same IR (#447,
2444
+ ADR 0011 Phase 0).
2445
+ - **Object → feature constructors** `draftwright.model.hole` / `boss` / `step` / `slot` /
2446
+ `pattern` / `envelope` — read a feature's geometry off the build123d object you built (⌀
2447
+ from the cylindrical face, axis/location from the bbox), or supply explicit values (#447).
2448
+ - **The fluent `Sheet` façade** (`draftwright.Sheet`) — reference the objects you built,
2449
+ declare their drawing aspects, `.build()` / `.export()`. `Sheet.from_part()` seeds the
2450
+ hybrid override mode from detection (#447, Phase 1).
2451
+ - **Toleranced dimensions** — a `±` / limit tolerance on a diameter, step, or hole bore,
2452
+ rendered on both the linear and ⌀-callout paths (`Sheet.tolerance(...)`, or a
2453
+ `decorations=` side-layer) (#28, Phase 2 P2a).
2454
+ - **Fit-class deviation** — `Sheet.fit("H7")` resolves an ISO 286 fit class to its limit
2455
+ deviation for the feature's nominal ⌀, rendered as the class code (`ø20 H7`, default) or
2456
+ the signed deviations (`show="deviation"` → `ø20 +0.021/0`). Common classes; fails loud
2457
+ outside its table (#29, Phase 2 P2a.2).
2458
+
2459
+ ### Changed
2460
+
2461
+ - **Object constructors honour explicit overrides** — a passed object supplies *defaults*;
2462
+ each explicit keyword overrides that field independently, and invalid public input fails
2463
+ at declaration with a clear `ValueError` (#451, #452).
2464
+ - **A declared hole/pattern renders at its declared position** even where detection missed
2465
+ it — the callout membership is sourced from the declared model, not only detection (#448).
2466
+ - **`Sheet.model()` / `Sheet.from_part()`** no longer build a full drawing just to return
2467
+ the IR — feature inspection/seeding is now a cheap, no-render path (#453).
2468
+
2469
+ ### Fixed
2470
+
2471
+ - A narrow diameter band hidden under a larger OD silhouette is no longer silently
2472
+ undimensioned (the two feature inventories agreed) (#298).
2473
+
2474
+ ## v0.2.5 — 2026-07-04
2475
+
2476
+ **The editable write API and record-then-finalize.** A detected drawing became an editable
2477
+ object, and a generated `--script` became a runnable reconstruction that reaches auto-pass
2478
+ quality.
2479
+
2480
+ ### Added
2481
+
2482
+ - **`dwg.model()`** exposes the detected `PartModel` as a read surface, plus feature-
2483
+ referenced add verbs `dimension()` / `callout()` / `locate()` / `furniture()` /
2484
+ `section()` and `drop(feature)`, with a machine-checked completeness audit (#400).
2485
+ - **Record-then-finalize** (#426): the verbs record intents in deferred mode and
2486
+ `dwg.finalize()` (auto-run by export) drains them through the auto-pass's own solvers, so
2487
+ a reconstruction reaches auto-pass quality; `--script` now emits a runnable detect-only
2488
+ reconstruction.
2489
+
2490
+ ### Changed
2491
+
2492
+ - **Dimension-line spacing now follows ISO 129-1 / ASME Y14.5 convention** (#347): a wider
2493
+ first-line gap (8 → 10 mm) and tighter, uniform parallel stacking (between-line clear gap
2494
+ 4 → 2.5 mm), with the inter-view corridor widened in step. Re-drifts every drawing.
2495
+
2496
+ ## v0.2.4 — 2026-07-03
2497
+
2498
+ A follow-up patch on the ADR 0009 placement rebuild in 0.2.3: it finishes unifying
2499
+ the shared "above-view" dimension corridor, adds a layout-overflow safety net, and
2500
+ makes two more drop paths non-silent.
2501
+
2502
+ ### Changed
2503
+
2504
+ - **Plan-view X location dimensions, side-view Y location dimensions, and a
2505
+ coincident slot-position dimension now share one collect-then-solve pass** (ADR
2506
+ 0009 Amendment 6, #345/#346). Previously each pass carved the strip independently,
2507
+ so a hole location and a slot position measuring the same datum span could both be
2508
+ drawn, and the location ladder could come out non-monotonic. One solve now dedups
2509
+ the coincident span (keeping the higher-priority location dimension) and orders the
2510
+ whole ladder as segregated, monotonic runs — feature-size dimensions nearest the
2511
+ view, datum locations nesting outward by distance.
2512
+
2513
+ ### Fixed
2514
+
2515
+ - **`choose_scale` never returns an overflowing layout** (#350). Scale selection
2516
+ could pick a scale whose composed block layout exceeded the drawable area; it now
2517
+ rejects any overflowing candidate.
2518
+ - **A hole location and a coincident slot position are no longer drawn twice** (#345),
2519
+ including at fractional datum distances where a display-value snap gap previously let
2520
+ the duplicate escape deduplication.
2521
+ - **The plan-view location ladder is monotonic** (#346) — running dimensions off a
2522
+ shared datum stack outward in ascending order instead of interleaving.
2523
+ - **A dropped balloon is non-silent** (#387). A balloon that cannot be placed now
2524
+ reports the drop and clears its `callout_dropped` state precisely, instead of
2525
+ vanishing with no on-sheet signal.
2526
+
2527
+ ## v0.2.3 — 2026-07-03
2528
+
2529
+ A large patch release: the **annotation-placement engine was rebuilt** as a
2530
+ collect-then-solve *boundary-labeling* stage (ADR 0009). Placement is now
2531
+ deterministic and minimises total leader length, and the recurring class of
2532
+ overlaps where a label was drawn on top of an "invisible" occupant — a leader
2533
+ shaft, a witness/extension line, the section hatch — is removed by construction.
2534
+ Drawing output changes for many turned, cross-drilled, and multi-feature parts.
2535
+
2536
+ ### Changed
2537
+
2538
+ - **Every annotation in a view's margin is now placed by one collect-then-solve
2539
+ pass** (ADR 0009, #317–#323). Dimensions, hole callouts, turned-diameter
2540
+ leaders, and the section hatch share a single occupancy model instead of several
2541
+ independent passes each blind to the others. When a strip is over capacity it now
2542
+ drops the *lowest-priority* annotation (smallest bore first) rather than whichever
2543
+ pass happened to run last. The legacy strip cursor is retired.
2544
+ - **Leader placement minimises total leader length, deterministically** (P4,
2545
+ #318). A per-strip solve places each label at the shortest-leader position that
2546
+ keeps the labels in order and clear of keep-out rows (a view centre-line, a
2547
+ dimension's extension line); central/coaxial callouts are anchored to the
2548
+ view-centre row. Output is reproducible across platforms and Python versions.
2549
+ - **`scipy` is no longer a dependency** — the leader solve is a small deterministic
2550
+ algorithm (weighted-median isotonic regression), not a linear program.
2551
+ - **Output changes** for turned, cross-drilled, and multi-feature parts whose
2552
+ margin annotations are now positioned by the unified solver.
2553
+
2554
+ ### Fixed
2555
+
2556
+ - **A PMI bore-diameter dimension spans the bore radius, not the full diameter**
2557
+ (#360). A `pmi="annotate"` diameter callout drew its witness lines at ±diameter
2558
+ from the centre — twice too wide, missing the bore edges.
2559
+ - **A bore coaxial with a rotational part's turning axis is no longer
2560
+ over-dimensioned** (#309). It carried a redundant offset *and* height location
2561
+ dimension even though its centre mark already locates it.
2562
+ - **A dropped turned step-length chain is no longer silent** (#362). When a turned
2563
+ head's shoulders are too crowded to dimension, the drop is now reported
2564
+ (`step_dim_dropped`) instead of vanishing with no lint or on-sheet signal.
2565
+ - **A diameter callout can no longer overprint a bore callout's leader shaft**
2566
+ (#358). The turned-diameter column now avoids the *full* footprint of existing
2567
+ annotations, not just their text boxes.
2568
+ - **The balloon ring hugs its dimensions on a cramped sheet** (#349) — its band
2569
+ depth is clamped to the drawable area.
2570
+ - **Dimension detection is robust to `SafeDimension`** (#335/#349) — the corridor
2571
+ and balloon-ring filters test the dimension *type*, not a class-name string, so a
2572
+ future dimension subclass can't slip through.
2573
+
2574
+ ## v0.2.2 — 2026-06-30
2575
+
2576
+ A patch release of turned-part dimension-placement fixes and a CLI start-up
2577
+ speed-up. Drawing output changes for the affected turned/cross-drilled parts.
2578
+
2579
+ ### Fixed
2580
+
2581
+ - **A coaxial bore callout on a *stepped* turned shaft is now lifted off the round
2582
+ view's centre axis** (#305). The earlier fix only triggered for a uniform
2583
+ (`is_rotational`) cylinder; a stepped shaft (e.g. the gramel GRM-03 drive screw)
2584
+ has a turned step profile but isn't classified rotational, so its `⌀… ↓…` bore
2585
+ callout was still leadered straight along the centreline, with the centre mark
2586
+ running through the text. The lift now also fires for a turned-profile part.
2587
+ - **A side-drilled hole's location dimension now stacks *inside* the overall
2588
+ envelope dimension** (ISO order — overall dim outermost, feature/location dims
2589
+ nearer the view). It was placed *outside* the envelope, which forced the shorter
2590
+ location dim's arrowheads to flip outward and clash (seen on GRM-01 and GRM-02).
2591
+ The mandatory overall dimension is still guaranteed placement.
2592
+
2593
+ ### Changed
2594
+
2595
+ - **CLI shell completion and `--help` are fast again** (#313). The Typer CLI and
2596
+ the heavy CAD engine are now imported lazily, so tab-completion and `--help` no
2597
+ longer pay a ~6 s engine-import cost; a real drawing run is unaffected.
2598
+
2599
+ ## v0.2.1 — 2026-06-30
2600
+
2601
+ A patch release focused on **turned-part dimensioning legibility**: crowded
2602
+ step-length chains and fine turned heads are now drawn legibly instead of crammed.
2603
+ Drawing output changes for affected turned parts.
2604
+
2605
+ ### Added
2606
+
2607
+ - **Automatic enlarged detail view for a crowded turned head** (#304). A turned
2608
+ part with a fine cluster of steps near one end and a long shaft (e.g. a thumbwheel
2609
+ drive screw) cannot have its head dimensioned legibly in line at any sensible
2610
+ scale. The head is now located as one block on the main view and broken out into
2611
+ an enlarged **DETAIL A — SCALE n:1** — the textbook treatment — firing
2612
+ automatically when a head's shoulders fall below the page legibility floor.
2613
+
2614
+ ### Changed
2615
+
2616
+ - **Crowded turned step-length chains stagger across two tiers** (#293) instead of
2617
+ cramming or being skipped. When the labels would collide on one line, the ISO
2618
+ 129-1 staggered convention alternates them between a near and a far tier so every
2619
+ step length stays legible at the drawing's own scale — no rescale needed. A roomy
2620
+ chain stays on a single tier.
2621
+ - **Detail views are now one unified pipeline** (#307). The prismatic step-height
2622
+ detail and the new turned-head detail flow through a single
2623
+ detect → request → render path; several crowded regions become DETAIL A/B/…
2624
+ - **Output changes** for turned parts whose step chains were previously crammed, or
2625
+ whose fine heads are now broken out into a detail view.
2626
+
2627
+ ### Fixed
2628
+
2629
+ - **A coaxial bore callout no longer overlaps the round view's centreline** (#305):
2630
+ its leader is angled off the centre axis so the callout text sits in clear space.
2631
+
2632
+ ### Internal
2633
+
2634
+ - A new **layout-cleanliness invariant test** asserts that finished drawings have no
2635
+ view/annotation collisions across representative part archetypes, and the
2636
+ measure-and-repack pass gained a trigger for an annotation growing into a
2637
+ neighbouring view's line-work (so the views spread to make room).
2638
+
2639
+ ## v0.2.0 — 2026-06-30
2640
+
2641
+ A major release. draftwright took ownership of feature recognition and linting
2642
+ (ADR 0007) and was re-architected onto a feature-IR + dimensioning-planner
2643
+ "compiler" (ADR 0008), gaining a Typer CLI and a portable, pure-Python PDF path
2644
+ along the way. **Generated drawings change** versus v0.1.13: the new pipeline
2645
+ dimensions parts more completely and consistently, so placement and the set of
2646
+ dimensions can differ — output is not byte-compatible with prior releases.
2647
+
2648
+ ### Changed
2649
+
2650
+ - **Re-architected onto a feature-IR + dimensioning planner** (ADR 0008). The
2651
+ engine is now a compiler: detectors build one feature inventory → a typed IR /
2652
+ `PartModel` → a dimensioning planner emits render-intents → shared
2653
+ layout/projection/export. Orientation and feature-kind are *data in the IR*,
2654
+ not code branches, and every feature class (holes, patterns, counterbores,
2655
+ slots, turned diameters/steps, centre marks, location dims, envelope/OD,
2656
+ section A–A, PMI/GD&T) was migrated onto this one path; the old parallel
2657
+ recognisers and placement passes were deleted as each was replaced. Net effect
2658
+ for users: more complete, more consistent drawings — but output differs from
2659
+ v0.1.13.
2660
+ - **draftwright owns feature recognition and linting** (ADR 0007). The hole/
2661
+ boss/cylinder/pattern recognisers, the slot/turned-step recognisers, and the
2662
+ feature-coverage lint engine are vendored into `recognition/` and `linting.py`;
2663
+ `build123d-drafting-helpers` is now purely the rendering library.
2664
+ - **The CLI writes a PDF by default** and takes a `--format` selector (#288).
2665
+ Previously `draftwright part.step` emitted SVG + DXF; it now emits a single PDF.
2666
+ Choose outputs with `--format` (a comma-list, with an `all` alias) —
2667
+ `--format pdf,dxf`, `--format svg`, `--format all`. The library API is
2668
+ unchanged: `make_drawing(...)` / `Drawing.export()` still write SVG + DXF.
2669
+ - **PDF export is now pure-Python and a core capability** (#288). The renderer
2670
+ moved from `cairosvg` (which `dlopen`s the native `libcairo` system library —
2671
+ absent on stock macOS/Windows, so PDF-by-default would have crashed there) to
2672
+ `svglib` + `reportlab`, both pip-installable wheels with no system dependency.
2673
+ PDF therefore works out of the box on every platform; output is visually
2674
+ identical to the cairo renderer.
2675
+
2676
+ ### Added
2677
+
2678
+ - **A Typer command-line interface** (#289/#291): shell completion
2679
+ (`--install-completion` / `--show-completion`), rich `--help`, and `--version`
2680
+ (reports the installed distribution version). All existing flags are preserved.
2681
+ - **`--format` output selector** (#288) — `pdf` (default), `svg`, `dxf`, or `all`,
2682
+ as a comma-list.
2683
+ - **Turned-part dimensioning**: axial step-length recognition and chains
2684
+ (#188/#189/#231), step-diameter callouts, collapse of a uniform step
2685
+ staircase to an "N× length" note (#290), and OD of a horizontal (X/Y) round
2686
+ body dimensioned on the profile view (#292).
2687
+ - **Slot recognition and dimensioning** converged onto the IR as `SlotFeature`
2688
+ (#242), and **section A–A** is now triggered by the planner (#271).
2689
+ - **A Contributor License Agreement** (#183).
2690
+
2691
+ ### Removed
2692
+
2693
+ - **The `--pdf` flag** (use `--format pdf`, the new default), the **`[pdf]`
2694
+ install extra**, and the **`cairosvg` dependency** (#288).
2695
+ - The byte-exact golden-output test harness (#190) — regression coverage rests on
2696
+ the geometry-level and standards suites (ADR 0005 §3 / ADR 0007).
2697
+
2698
+ ### Fixed
2699
+
2700
+ - Locate **every** side-drilled (off-axis) hole, not just the first (#225/#286).
2701
+ - Don't mis-detect a prismatic part with incidental cylinders as a turned part
2702
+ (#293/#294); drop phantom zero-diameter turned steps (#279/#284); skip an
2703
+ illegibly-dense step-length chain rather than cram it (#293/#296).
2704
+ - Degraded hole-pattern callout consistency (#262/#274).
2705
+ - Windows `python -m draftwright.make_drawing` CLI smoke / entrypoint (#181/#182).
2706
+
2707
+ ## v0.1.13 — 2026-06-27
2708
+
2709
+ ### Changed
2710
+
2711
+ - **Requires `build123d-drafting-helpers>=0.13.0`; text pinned to bundled fonts**
2712
+ (#149, ADR 0006). draftwright now vendors IBM Plex (OFL-1.1) and renders and
2713
+ measures all text via `font_path` — IBM Plex Mono for dimensions/callouts/notes,
2714
+ IBM Plex Sans Condensed for the title block — instead of resolving the system
2715
+ font name `"Arial"`. Resolving a name substitutes a different font on Linux,
2716
+ which shifted the whole sheet ~1 mm; pinning a bundled font file makes generated
2717
+ layout **deterministic across Linux/macOS/Windows** and gives a consistent
2718
+ typeface. **Drawing output changes**: positions shift slightly from prior
2719
+ releases and labels render in IBM Plex (helpers #172).
2720
+
2721
+ ### Internal
2722
+
2723
+ - **Compiler-pipeline module split** (#138, ADR 0005). The two large modules
2724
+ `make_drawing.py` (3,907 lines) and `annotate.py` (2,587) were decomposed into a
2725
+ DAG of focused stage modules — `projection`, `sheet`, `analysis`, `drawing`,
2726
+ `builder`, the `annotations/` subpackage (sections/turned/pmi/holes/orchestrator),
2727
+ alongside the existing `registry`/`linting`/`repair`/`export`/`fonts`. Annotation
2728
+ identity, the lint coverage signal, and the deterministic repair loop each gained a
2729
+ single owner; `make_drawing.py` / `annotate.py` are now thin compat facades, so all
2730
+ existing imports and the `draftwright` CLI entry point keep working. A golden-output
2731
+ regression gate verified every step is behaviour-preserving (output byte-identical),
2732
+ and mypy was tightened on the settled contracts. No public API or drawing-output
2733
+ change. (Phases #160–#166.)
2734
+
2735
+ ## v0.1.12 — 2026-06-21
2736
+
2737
+ ### Changed
2738
+
2739
+ - **Requires `build123d-drafting-helpers>=0.12.0`** (#92, #122). draftwright now
2740
+ consumes the new sub-clustered hole-pattern recognition, the
2741
+ `feature_diameters()` coverage inventory, the persistent `view_edge_cache`,
2742
+ and the `ViewCoordinates.from_viewport()` ISO projection basis.
2743
+ - **Grouped hole-pattern callouts** (#92, #111, #114). A recognised perimeter,
2744
+ grid, or bolt circle collapses to a single `n× ⌀ …` callout plus its pattern
2745
+ dimensions instead of a balloon on every hole. A spec group now sub-clusters
2746
+ into multiple patterns (a perimeter → its edge `LinearArray` rows, a filled
2747
+ lattice → one `RectGrid` with a `(rows×cols)` callout and both pitch
2748
+ dimensions); only genuinely unpatterned holes fall back to the per-hole table.
2749
+ On NIST CTC-02 the table shrinks from 61 rows to the unpatterned remainder.
2750
+ - **Layout overhaul — compose-then-pack** (#121, #112, ADR 0004). Each view owns
2751
+ the annotations created against it, and the resulting view blocks are packed
2752
+ disjoint with automatic page/scale escalation. This eliminates cross-view
2753
+ overlap — most visibly, plan-view balloons landing on front-view dimensions.
2754
+ - **Drawing attribution** (#120). The title block records the author, the SVG
2755
+ and PDF carry a clickable draftwright hyperlink, and a "generated by
2756
+ draftwright" note is written to the SVG/DXF/PDF file metadata.
2757
+ - **Gap between wrapped hole-table column blocks** (#123) so a chart that wraps
2758
+ into several blocks reads as distinct columns.
2759
+
2760
+ ### Fixed
2761
+
2762
+ - **Plan-view top balloon ring no longer floats over a phantom corridor** (#125).
2763
+ The hole-table escalation deletes the X-location dimensions but left their
2764
+ stale depth in the strip cursor, so the top balloons were parked far above the
2765
+ view. The ring is now sized to the real dimension stack, so the top-side
2766
+ leaders are short like the other three sides.
2767
+ - **No more phantom `feature_not_dimensioned` warnings** on slot-ends and shallow
2768
+ recesses, via the helpers 0.12.0 `feature_diameters()` coverage inventory
2769
+ (#92).
2770
+
2771
+ ### Internal
2772
+
2773
+ - `AnnoBox` box-model footprint foundation and the four-side balloon ring placed
2774
+ in a reserved view halo (#111, #112); the title block is pinned as a
2775
+ first-class layout block (#112).
2776
+
2777
+ ## v0.1.11 — 2026-06-19
2778
+
2779
+ ### Changed
2780
+
2781
+ - **Feature-coverage lint is assembly-aware.** A general-arrangement drawing of
2782
+ a multi-solid part deliberately omits each part's bores (they belong on detail
2783
+ sheets), so `feature_not_dimensioned` / `feature_count_mismatch` are now
2784
+ emitted at `info` rather than `warning` when the part is multi-solid — out of
2785
+ the warning count and quality score, but still queryable. Auto-detected;
2786
+ override with `build_drawing(..., assembly=True/False)` or
2787
+ `lint_feature_coverage(..., assembly=...)` (#69).
2788
+
2789
+ ### Fixed
2790
+
2791
+ - **`place_dim` now labels the real-world length, not the page distance**, at
2792
+ non-1:1 scale. Previously a dimension placed at a scale other than 1:1 showed
2793
+ the on-page millimetre span instead of the true model dimension (#104).
2794
+
2795
+ ### Internal
2796
+
2797
+ - **`make_drawing.py` decomposed (#98).** The per-view projection math and the
2798
+ analysis namespace were deduplicated and typed (the namespace is now a frozen
2799
+ `Analysis` dataclass), and the annotation passes were extracted into a new
2800
+ `draftwright.annotate` module on top of a shared `draftwright._core`. The
2801
+ module graph is a DAG (`layout → _core → {make_drawing, annotate}`) and
2802
+ `make_drawing.py` shrank from ~5,270 to ~2,930 lines. No public API or
2803
+ behaviour change.
2804
+
2805
+ ## v0.1.10 — 2026-06-18
2806
+
2807
+ ### Added
2808
+
2809
+ - **Constraint-based layout engine (ADR 0003).** A new `draftwright.layout`
2810
+ module with a `Placeable` protocol and a `LayoutSolver`: a 1D Cassowary strip
2811
+ solver (`solve_strip`, with per-pair gaps) and a 2D free-rectangle placer
2812
+ (`place_box` / `fit_box`) that positions a box in a free part of the page
2813
+ clear of the views, title block, and existing annotations. Hole-callout and
2814
+ turned-diameter placement now run on the solver. The engine grows per real
2815
+ consumer; a monolithic global 2D solve is deferred (see the ADR).
2816
+ - **Hole table + balloons (#93).** `dwg.add_table(rows)` places a generic data
2817
+ table (gear data, BOM, revision block, …) in a free corner via `place_box`;
2818
+ `dwg.add_hole_table(view)` builds a hole chart from the detected holes with a
2819
+ circled balloon tag at each hole. A **too-dense plan view now auto-escalates**:
2820
+ a part the layout cannot legibly dimension hole-by-hole is replaced by a
2821
+ complete per-instance hole chart (`TAG | ⌀ | X | Y`, datum-relative) plus
2822
+ balloons, instead of silently dropping callouts and location dims. The chart
2823
+ wraps into multiple column-blocks to fit the page.
2824
+ - **External turned diameters (#77).** A turned part lying along the X axis now
2825
+ gets ø leader-callouts for its external stepped diameters, with thread/worm
2826
+ patches collapsed into a single boss.
2827
+ - **Pin / manual override (#89).** `dwg.pin(name)` / `dwg.unpin(name)` fix an
2828
+ annotation's position so `repair()` — and the layout engine — never move it; a
2829
+ deliberate (human or AI) placement wins over automatic layout.
2830
+
2831
+ ### Changed
2832
+
2833
+ - Hole-callout and turned-diameter placement is deconflicted through the shared
2834
+ `LayoutSolver` instead of ad-hoc per-pass logic (no output change).
2835
+
2836
+ ### Fixed
2837
+
2838
+ - **Exact circles recovered for revolution silhouettes.** `project_to_viewport`'s
2839
+ HLR returns the on-axis silhouette of a turned feature (or a concentric
2840
+ gear-tooth-tip arc) as an approximating spline, not a true circle — splines in
2841
+ the DXF where CAM expects `CIRCLE`/`ARC`, and fitted rather than exact radii.
2842
+ `add_view` now refits any silhouette whose samples are equidistant from a
2843
+ recognised revolution axis back to an exact circle/arc (#67).
2844
+ - **Blind-hole depth no longer measured across solid boundaries.** On a
2845
+ multi-solid assembly, coaxial bores in different bodies were merged into one
2846
+ hole, reporting a depth spanning the inter-body gap (the ⌀9.8 ↓111.4 symptom).
2847
+ Fixed upstream in `build123d-drafting-helpers` 0.10.1; the dependency pin is
2848
+ bumped to `>=0.10.1` to pick it up (#68).
2849
+
2850
+ ### Docs
2851
+
2852
+ - The skill and generated-script header now lead with the domain API
2853
+ (`features` / `place_dim` / `repair` / `lint_summary`) and the
2854
+ build → critique → fix loop. ADR 0003 records the layout architecture; ADRs
2855
+ 0001/0002 remain the editing-model and lint→repair foundations.
2856
+
2857
+ ## v0.1.9 — 2026-06-16
2858
+
2859
+ ### Added
2860
+
2861
+ - **Domain-semantic editing API.** `dwg.features(view)` returns detected holes
2862
+ and features grouped by machining spec in page coordinates, and
2863
+ `dwg.place_dim(p1, p2, side, view, draft, name=…)` places a dimension from
2864
+ domain inputs — the vocabulary a script (or an AI assistant) needs to edit a
2865
+ drawing without hand-computing page geometry (#25, #26).
2866
+ - **`dwg.annotations()` and `dwg.get_annotation(name)`.** Introspect what is
2867
+ already on the drawing — a `{name: type}` map and a name lookup — so a script
2868
+ can make incremental edits without risking a silent name-collision replace
2869
+ (#27).
2870
+ - **`dwg.view_bounds(view)`.** Returns `(x_min, y_min, x_max, y_max)`, the page
2871
+ bounding box of a view's projected geometry (or `None` for an unknown view),
2872
+ so free-form notes and leader elbows can be placed just outside a view without
2873
+ guessing offsets from `dwg.at()` (#28).
2874
+ - **Lint findings carry a suggested fix.** Each repairable lint issue now
2875
+ includes a ready-to-run domain-API call snippet, so acting on a finding is one
2876
+ copy-paste away (#29).
2877
+ - **Lint→repair loop.** `Drawing.repair()` — run by default in `build_drawing` —
2878
+ mechanically resolves the lint codes that have a deterministic placement fix:
2879
+ overlapping labels are pushed apart and wrong-side dimensions are flipped. A
2880
+ pass that would net-increase the issue count is rolled back, so repair never
2881
+ makes a drawing worse (#30).
2882
+ - **TYP / representative dimensioning for uniform step patterns.** A run of
2883
+ equal-rise, equal-going steps is dimensioned once and labelled representative
2884
+ (TYP) instead of repeating identical dimensions down the ladder (#45).
2885
+ - **Enlarged detail view for crowded step clusters (MVP).** When shoulders are
2886
+ too closely spaced to dimension legibly at sheet scale, an opt-in
2887
+ (`detail_view=True`) detail view re-draws them at a larger scale (#42).
2888
+
2889
+ ### Changed
2890
+
2891
+ - **BREAKING: the annotation list `dwg.annotations` is renamed to `dwg.items`.**
2892
+ `dwg.annotations` is now a method (see Added); the ordered, mutable list of
2893
+ annotation objects it used to be is now `dwg.items`. Pre-1.0 with no published
2894
+ consumers, so the clearer name was taken now rather than spelling the new query
2895
+ method awkwardly (#27).
2896
+
2897
+ ### Documentation
2898
+
2899
+ - ADRs 0001 (editing model) and 0002 (iteration loop) record the design
2900
+ direction behind the domain API and the lint→repair loop (#51).
2901
+
2902
+ ## v0.1.8 — 2026-06-16
2903
+
2904
+ ### Changed
2905
+
2906
+ - **Automatic scale selection now minimises the sheet size.** The preference
2907
+ ladder is page-major: every standard scale on the smallest sheet is tried
2908
+ before the next sheet up, so a part lands on the smallest sheet it fits at the
2909
+ largest scale that sheet allows. A 20 × 15 × 10 mm part is now drawn 2:1 on A4
2910
+ instead of 5:1 on A3 — a smaller sheet is preferred over a larger enlargement
2911
+ scale. Reductions keep their legibility-first balance, so a too-big part is
2912
+ not over-reduced onto a small sheet.
2913
+ - **A specified page now enlarges to the best fitting scale.** When the caller
2914
+ fixes the page (`--page A3`) or scale, scale selection packs the isometric
2915
+ view into the largest empty rectangle the placement engine actually uses (it
2916
+ may sit in vertical headroom above the views), instead of charging it a column
2917
+ in the view row. A long, short part — e.g. a 100 × 10 × 11 mm staircase — now
2918
+ fills a requested A3 at 2:1 where it was previously under-scaled to 1:1.
2919
+ Automatic selection (no page/scale given) keeps the conservative row model,
2920
+ which reserves enough room to place every annotation rather than dropping some
2921
+ onto a tighter sheet (staircase review).
2922
+ - **Isometric view growth is capped.** The iso is fitted to fill its zone but no
2923
+ longer grows past 1.3× sheet scale; on an oversized sheet it could previously
2924
+ balloon to ~8× and dwarf the dimensioned orthographic views. Shrinking to fit
2925
+ a small zone is unchanged.
2926
+ - **Step heights are dimensioned only where legibly separable.** After the
2927
+ adaptive cap (#36), a part with many closely-spaced shoulders (e.g. NIST
2928
+ CTC-02 at 1:5) tried to dimension faces only ~1 mm apart on the page. A step
2929
+ is now dimensioned only if it is both tall enough from the base *and* at least
2930
+ one legible step-height above the previously dimensioned one; the rest surface
2931
+ as `step_dim_dropped` (use a detail view). "Fits" is not the same as
2932
+ "legible" (#41).
2933
+ - **Hole-location dimensions are gated for legibility.** A hole-dense part (e.g.
2934
+ NIST CTC-02, ~38 distinct hole locations) previously stacked every location
2935
+ reference into a tall, busy tower above the views — "fits" is not "legible".
2936
+ Each axis's references are now gated by inter-dimension page spacing
2937
+ (`_legible_locations`, analogous to the step-height gate #41): only locations
2938
+ at least one value-label footprint apart on the page are dimensioned; the rest
2939
+ surface as `location_ref_dropped` (full fidelity belongs in a detail view,
2940
+ #42). Sparse parts are unchanged (#43).
2941
+ - **Tighter location-dimension tier pitch.** The vertical pitch between stacked
2942
+ X/Y location dimensions is now derived from the label footprint
2943
+ (`font_size + 2·pad_around_text`, ≈7 mm) instead of a looser `font_size·3`,
2944
+ so location stacks pack closer (#41).
2945
+
2946
+ ### Fixed
2947
+
2948
+ - **Phantom step corridor no longer blocks a larger scale.** Page/scale
2949
+ selection reserved a step-ladder corridor sized for *every* candidate
2950
+ horizontal face, including ones the legibility gate would never dimension. A
2951
+ part with many sub-legible faces (e.g. a staircase with 15 tiny treads) was
2952
+ forced onto an oversized sheet at 1:1. Scale selection now iterates so the
2953
+ reserved corridor matches the step count actually placed, freeing the part to
2954
+ pick a tighter sheet (staircase.step review).
2955
+ - **Engraved-text faces are no longer dimensioned as steps.** `analyse_face_levels`
2956
+ gained a `min_area_frac` filter; a horizontal face counts as a step only if
2957
+ its area is at least 1% of the part's plan footprint. This drops sub-feature
2958
+ faces (fragments of engraved numbers/text) that were surfacing as phantom
2959
+ shoulders — e.g. a 0.57 mm² digit face dimensioned as z=6.4 on staircase.step.
2960
+ - **Overall-height dimension nests outside the step dims.** The overall height
2961
+ is now placed last on the front view's right ladder so it sits outermost, with
2962
+ the step-height dims inside it; extension lines nest instead of leapfrogging
2963
+ (staircase.step review).
2964
+
2965
+ ## v0.1.7 — 2026-06-15
2966
+
2967
+ ### Added
2968
+
2969
+ - `Drawing.lint_summary()` — a JSON-friendly aggregate of `lint()` for
2970
+ non-interactive callers (scripts, or an LLM via the API): severity counts,
2971
+ per-code counts, a `geometry_issues` tally (standards/geometry checks vs pure
2972
+ layout), a `passed` flag, a coarse 0–1 `score`, and the full issue list. Gives
2973
+ a single signal to gate and optimise on without rendering the SVG (#32).
2974
+
2975
+ ### Changed
2976
+
2977
+ - **Adaptive annotation placement.** The three hard-coded cardinality caps —
2978
+ four hole callouts per view, four hole location references per part, and three
2979
+ step-height dimensions — are removed. The engine now places as many as the
2980
+ available strip/corridor space allows (callouts largest-first, locations
2981
+ nearest-datum-first, every legible step), so a part with room is dimensioned
2982
+ completely instead of dropped to an arbitrary count. An annotation that
2983
+ genuinely doesn't fit is never force-placed; it surfaces via lint
2984
+ (`callout_dropped` / `location_ref_dropped`, warning severity). On the NIST
2985
+ CTC parts this raises coverage substantially (e.g. CTC-02: 4 → 36 location
2986
+ dimensions, 4 → 9 callouts) with no error-severity lint (#36).
2987
+ - **No silent annotation drops.** Every place the layout has to drop an
2988
+ annotation now records a machine-readable lint issue, surfaced by `lint()`,
2989
+ so a short drawing always carries a reason. A dropped callout names its
2990
+ diameter and is excluded from `feature_not_dimensioned` (no double-report).
2991
+ `placement_unsatisfiable` (error severity) is reserved for the degenerate
2992
+ case where space was reserved but an annotation still could not be placed
2993
+ (#32).
2994
+ - **Layout constants derived from first principles.** Bare, fixture-tuned
2995
+ constants (strip slot widths, callout label widths, isometric fit factor) are
2996
+ now computed from text metrics and page size rather than hard-coded, so the
2997
+ layout generalises to unseen geometry instead of fitting the test cases (#31).
2998
+ - `_auto_annotate` clears its build-time lint records on re-entry, and repeated
2999
+ `lint()` calls are stable (#32).
3000
+
3001
+ ### Fixed
3002
+
3003
+ - AP242 / PMI STEP import segfault: STEP geometry is now read directly via
3004
+ `STEPControl_Reader`, avoiding the XCAF/PMI read that crashed (SIGSEGV) on
3005
+ with-PMI files such as NIST CTC-02 (#20).
3006
+
3007
+ ### Tests
3008
+
3009
+ - Overfitting guards pin the general layout behaviour on turned/hybrid parts
3010
+ (flange OD + bolt circle), multi-bore parts, and the step-legibility boundary
3011
+ (#13).
3012
+ - The full NIST CTC set (AP203 and AP242) builds and is covered by the slow
3013
+ end-to-end tier.
3014
+
3015
+ ## v0.1.6 — 2026-06-15
3016
+
3017
+ ### Fixed
3018
+
3019
+ - Section-view boolean cut on cast geometry: the exact `body - Box(...)` boolean
3020
+ raised an uncatchable `Standard_DomainError` (C++ abort, SIGABRT) on some parts
3021
+ (NIST CTC-04), crashing the whole drawing. `_fuzzy_cut()` now runs
3022
+ `BRepAlgoAPI_Cut` with a small fuzzy tolerance and keeps solids-only, making
3023
+ the section cut robust (#20, #22).
3024
+
3025
+ ### Tests
3026
+
3027
+ - NIST CTC-04 (both AP203 and AP242) now build with a clean section view and are
3028
+ covered by the CTC build tests.
3029
+ - Known: CTC-02 AP242 still segfaults inside OCCT's AP242/PMI STEP read (#20),
3030
+ excluded from build tests.
3031
+
3032
+ ## v0.1.5 — 2026-06-15
3033
+
3034
+ ### Fixed
3035
+
3036
+ - CTC-02 spurious full-page line: build123d's `ExportSVG` projected
3037
+ circle-edge-on edges (hole/fillet rims seen edge-on) as elliptical arcs with
3038
+ a near-zero minor radius, which renderers blow up into full-page lines.
3039
+ `sanitize_svg_arcs()` rewrites any arc with a sub-1e-3 mm radius into the
3040
+ straight line it actually is, leaving real arcs untouched (#19). Not a PMI
3041
+ issue — the file is AP203 geometry-only.
3042
+
3043
+ ### Tests
3044
+
3045
+ - Added the full NIST CTC set (01–05) as fixtures, both AP203 geometry-only and
3046
+ AP242 (with-PMI) variants.
3047
+ - Heavy end-to-end CTC fixture builds are marked `slow` and deselected from the
3048
+ default `pytest` run (fast normal run, ~4.5 min); CI runs the fast tier across
3049
+ the OS/Python matrix and the slow tier once.
3050
+ - Known: AP242 CTC-02 and both CTC-04 variants crash OCCT on import (#20); their
3051
+ fixtures are excluded from build tests.
3052
+
3053
+ ## v0.1.4 — 2026-06-15
3054
+
3055
+ ### Changed
3056
+
3057
+ - Feature annotations (hole callouts, location dimensions, section view) now
3058
+ fire on feature presence independent of the turned/prismatic classification,
3059
+ so turned-and-drilled parts (e.g. flanges) get both the OD/centreline base
3060
+ set and per-hole callouts plus bolt-circle furniture (#10).
3061
+ - Isometric view placement now uses a general largest-empty-rectangle search in
3062
+ place of the wide/flat-on-A3 special case (#11).
3063
+ - Concentric bore-leader stacking is generalised beyond three, and the
3064
+ step-height dimension gate is now a single derived constant (#10, #12).
3065
+
3066
+ ### Internal
3067
+
3068
+ - Single-sourced duplicated geometry constants from the draft preset (#12).
3069
+ - Minor comment and logging cleanups.
3070
+
3071
+ ## v0.1.0 — 2026-06-14
3072
+
3073
+ Initial release — spun out of `build123d-drafting-helpers` v0.9.1.
3074
+
3075
+ The automated drawing engine (`make_drawing`, `build_drawing`, `Drawing`)
3076
+ was previously part of `build123d-drafting-helpers`. It is now a separate
3077
+ AGPL-licensed package that depends on `build123d-drafting-helpers>=0.9.1`
3078
+ for annotation primitives.
3079
+
3080
+ ### Migration from build123d-drafting-helpers
3081
+
3082
+ ```python
3083
+ # Before
3084
+ from build123d_drafting import make_drawing, Drawing, build_drawing
3085
+
3086
+ # After
3087
+ from draftwright import make_drawing, Drawing, build_drawing
3088
+ ```
3089
+
3090
+ ### Features (carried over from build123d-drafting-helpers)
3091
+
3092
+ - **`make_drawing`** / **`build_drawing`** — automatic multi-view technical
3093
+ drawing from a build123d solid: view layout, scale selection, orthographic
3094
+ projection, dimension placement, title block.
3095
+ - **`Drawing`** — composable drawing object with `.lint()`, `.add()`,
3096
+ `.export_svg()`, `.export_dxf()`.
3097
+ - **`choose_scale`** — ISO/ASME standard scale selection.
3098
+ - **`lint_feature_coverage`** — checks annotation coverage against detected
3099
+ part features (holes, bosses, bolt circles).
3100
+ - **Section A–A views** — automatic section view for blind/stepped holes,
3101
+ with ISO 128-44 solid filled cutting-plane arrows and ISO 128-50 45°
3102
+ hatching on the cut face.
3103
+ - **`generate_script`** — generates a standalone drawing script from a STEP
3104
+ file.