draftwright 0.4.5__tar.gz → 0.4.7__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 (247) hide show
  1. {draftwright-0.4.5 → draftwright-0.4.7}/CHANGELOG.md +71 -0
  2. {draftwright-0.4.5 → draftwright-0.4.7}/PKG-INFO +9 -5
  3. {draftwright-0.4.5 → draftwright-0.4.7}/README.md +7 -4
  4. {draftwright-0.4.5 → draftwright-0.4.7}/docs/adr/README.md +4 -4
  5. draftwright-0.4.7/docs/reference/recogniser-capabilities.md +52 -0
  6. draftwright-0.4.7/docs/reference/sheet.md +35 -0
  7. draftwright-0.4.7/docs/research/1062-repeating-radial-profile-evidence.md +102 -0
  8. {draftwright-0.4.5 → draftwright-0.4.7}/pyproject.toml +6 -1
  9. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/_core.py +2 -1
  10. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/_geometry.py +88 -0
  11. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/analysis.py +9 -9
  12. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/annotations/_common.py +14 -4
  13. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/annotations/from_model.py +355 -76
  14. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/annotations/holes.py +17 -8
  15. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/drawing.py +15 -6
  16. draftwright-0.4.7/src/draftwright/evaluation/__init__.py +1 -0
  17. draftwright-0.4.7/src/draftwright/evaluation/step_analysis.py +703 -0
  18. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/layout.py +193 -0
  19. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/linting/channel_coverage.py +2 -1
  20. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/linting/coverage.py +18 -16
  21. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/linting/flat_coverage.py +2 -1
  22. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/linting/hole_coverage.py +2 -1
  23. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/linting/polygonal_stock_coverage.py +2 -1
  24. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/linting/profiled_bore_coverage.py +2 -1
  25. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/linting/slot_coverage.py +2 -1
  26. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/model/compiled.py +8 -1
  27. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/model/declare.py +8 -9
  28. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/model/detect.py +36 -35
  29. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/model/ir.py +19 -1
  30. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/model/planner.py +3 -0
  31. draftwright-0.4.7/src/draftwright/recogniser_contract.py +526 -0
  32. draftwright-0.4.7/src/draftwright/recognition/__init__.py +15 -0
  33. draftwright-0.4.7/src/draftwright/recognition_cache.py +26 -0
  34. draftwright-0.4.7/src/draftwright/score.py +10 -0
  35. draftwright-0.4.7/tests/fixtures/evaluation/README.md +20 -0
  36. draftwright-0.4.7/tests/fixtures/evaluation/ambiguous-open-semicircle.step +597 -0
  37. draftwright-0.4.7/tests/fixtures/evaluation/blind-hole.step +517 -0
  38. draftwright-0.4.7/tests/fixtures/evaluation/corpus-v1.json +137 -0
  39. draftwright-0.4.7/tests/fixtures/evaluation/plain-block.step +415 -0
  40. draftwright-0.4.7/tests/fixtures/evaluation/topology-a.step +607 -0
  41. draftwright-0.4.7/tests/fixtures/evaluation/topology-b.step +607 -0
  42. {draftwright-0.4.5 → draftwright-0.4.7}/tests/refactor_golden/grooved_shaft.json +14 -14
  43. {draftwright-0.4.5 → draftwright-0.4.7}/tests/refactor_golden/pocketed.json +7 -7
  44. draftwright-0.4.7/tests/test_cross_repository_delivery_protocol.py +27 -0
  45. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_declare.py +13 -17
  46. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_declared_recognition_gate.py +43 -17
  47. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_detect_once.py +2 -2
  48. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_detect_registry.py +2 -2
  49. draftwright-0.4.7/tests/test_external_recognition_boundary.py +68 -0
  50. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_fillets_adjacency.py +1 -2
  51. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_flat_completeness.py +2 -2
  52. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_flat_stock_identity.py +21 -5
  53. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_flat_stock_opposition.py +1 -2
  54. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_grid_lattice_convention.py +3 -3
  55. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_import_boundaries.py +11 -3
  56. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_issue_1058_wheel_profile.py +23 -23
  57. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_issue_1073_cross_body_patterns.py +5 -5
  58. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_issue_1082_polygonal_stock.py +5 -5
  59. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_issue_1143_hole_completeness.py +7 -7
  60. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_issue_1146_scale_completeness.py +3 -3
  61. draftwright-0.4.7/tests/test_issue_367_leader_ink.py +137 -0
  62. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_issue_676_polygonal_boss.py +2 -2
  63. draftwright-0.4.7/tests/test_issue_740_leader_assignment.py +567 -0
  64. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_issue_885_prismatic_coverage.py +1 -1
  65. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_issue_909_sloped_profile.py +1 -1
  66. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_issue_917_open_channel.py +1 -1
  67. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_issue_958_cross_solid_recognition.py +4 -4
  68. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_make_drawing.py +34 -46
  69. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_part_model.py +1 -1
  70. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_pocket_pattern_recognition.py +9 -9
  71. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_private_test_imports.py +5 -0
  72. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_quality_components.py +2 -1
  73. draftwright-0.4.7/tests/test_recogniser_capabilities.py +531 -0
  74. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_recogniser_contract.py +3 -4
  75. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_recognition.py +28 -30
  76. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_recognition_manifest.py +14 -14
  77. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_recognition_result.py +13 -11
  78. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_refactor_golden.py +5 -2
  79. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_slanted_blind_step.py +3 -3
  80. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_slot_completeness.py +1 -1
  81. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_slot_pattern_recognition.py +6 -6
  82. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_solve_trace.py +3 -0
  83. draftwright-0.4.7/tests/test_step_analysis_evaluation.py +403 -0
  84. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_turned_steps.py +1 -2
  85. draftwright-0.4.7/tests/test_two_repository_workflow.py +270 -0
  86. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_workflows.py +20 -1
  87. draftwright-0.4.5/src/draftwright/recognition/__init__.py +0 -205
  88. draftwright-0.4.5/src/draftwright/recognition/_features.py +0 -1164
  89. draftwright-0.4.5/src/draftwright/recognition/_record.py +0 -28
  90. draftwright-0.4.5/src/draftwright/recognition/chamfers.py +0 -196
  91. draftwright-0.4.5/src/draftwright/recognition/countersinks.py +0 -206
  92. draftwright-0.4.5/src/draftwright/recognition/fillets.py +0 -183
  93. draftwright-0.4.5/src/draftwright/recognition/flats.py +0 -275
  94. draftwright-0.4.5/src/draftwright/recognition/grooves.py +0 -145
  95. draftwright-0.4.5/src/draftwright/recognition/levels.py +0 -346
  96. draftwright-0.4.5/src/draftwright/recognition/pads.py +0 -148
  97. draftwright-0.4.5/src/draftwright/recognition/plates.py +0 -154
  98. draftwright-0.4.5/src/draftwright/recognition/polygonal_bosses.py +0 -342
  99. draftwright-0.4.5/src/draftwright/recognition/profiled_bores.py +0 -368
  100. draftwright-0.4.5/src/draftwright/recognition/repeating_profiles.py +0 -393
  101. draftwright-0.4.5/src/draftwright/recognition/result.py +0 -293
  102. draftwright-0.4.5/src/draftwright/recognition/slots.py +0 -1465
  103. draftwright-0.4.5/src/draftwright/recognition/turned.py +0 -202
  104. draftwright-0.4.5/src/draftwright/score.py +0 -76
  105. {draftwright-0.4.5 → draftwright-0.4.7}/.gitignore +0 -0
  106. {draftwright-0.4.5 → draftwright-0.4.7}/LICENSE +0 -0
  107. {draftwright-0.4.5 → draftwright-0.4.7}/skills/SKILL.md +0 -0
  108. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/__init__.py +0 -0
  109. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/_pmi_part21.py +0 -0
  110. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/_warnings.py +0 -0
  111. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/annotate.py +0 -0
  112. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/annotations/__init__.py +0 -0
  113. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/annotations/balloons.py +0 -0
  114. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/annotations/gears.py +0 -0
  115. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/annotations/orchestrator.py +0 -0
  116. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/annotations/sections.py +0 -0
  117. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/audit.py +0 -0
  118. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/builder.py +0 -0
  119. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/cli.py +0 -0
  120. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/compose.py +0 -0
  121. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/export.py +0 -0
  122. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/fits.py +0 -0
  123. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/fonts/IBMPlexMono-Regular.ttf +0 -0
  124. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/fonts/IBMPlexSansCondensed-Regular.ttf +0 -0
  125. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/fonts/LICENSE-IBMPlexMono-OFL.txt +0 -0
  126. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/fonts/LICENSE-IBMPlexSansCondensed-OFL.txt +0 -0
  127. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/fonts/__init__.py +0 -0
  128. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/intents.py +0 -0
  129. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/linting/__init__.py +0 -0
  130. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/linting/gear_coverage.py +0 -0
  131. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/linting/issues.py +0 -0
  132. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/linting/pmi_coverage.py +0 -0
  133. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/linting/quality.py +0 -0
  134. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/linting/structural.py +0 -0
  135. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/linting/suggest.py +0 -0
  136. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/make_drawing.py +0 -0
  137. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/model/__init__.py +0 -0
  138. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/model/callout.py +0 -0
  139. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/pmi.py +0 -0
  140. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/projection.py +0 -0
  141. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/py.typed +0 -0
  142. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/registry.py +0 -0
  143. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/repair.py +0 -0
  144. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/sheet.py +0 -0
  145. {draftwright-0.4.5 → draftwright-0.4.7}/src/draftwright/sheet_emit.py +0 -0
  146. {draftwright-0.4.5 → draftwright-0.4.7}/tests/_kernel.py +0 -0
  147. {draftwright-0.4.5 → draftwright-0.4.7}/tests/_layout_sig.py +0 -0
  148. {draftwright-0.4.5 → draftwright-0.4.7}/tests/conftest.py +0 -0
  149. {draftwright-0.4.5 → draftwright-0.4.7}/tests/fixtures/grm03_thumbwheel_drive_screw.step +0 -0
  150. {draftwright-0.4.5 → draftwright-0.4.7}/tests/fixtures/if_step_flat_across_cylinder.step +0 -0
  151. {draftwright-0.4.5 → draftwright-0.4.7}/tests/fixtures/issue_1058_wheel_rh.step +0 -0
  152. {draftwright-0.4.5 → draftwright-0.4.7}/tests/fixtures/issue_909_basic_part_design_017_body.step +0 -0
  153. {draftwright-0.4.5 → draftwright-0.4.7}/tests/fixtures/issue_915_case_study_2.step +0 -0
  154. {draftwright-0.4.5 → draftwright-0.4.7}/tests/fixtures/nist_ctc_01_asme1_ap203.stp +0 -0
  155. {draftwright-0.4.5 → draftwright-0.4.7}/tests/fixtures/nist_ctc_01_asme1_ap242.stp +0 -0
  156. {draftwright-0.4.5 → draftwright-0.4.7}/tests/fixtures/nist_ctc_02_asme1_ap203.stp +0 -0
  157. {draftwright-0.4.5 → draftwright-0.4.7}/tests/fixtures/nist_ctc_02_asme1_ap242.stp +0 -0
  158. {draftwright-0.4.5 → draftwright-0.4.7}/tests/fixtures/nist_ctc_03_asme1_ap203.stp +0 -0
  159. {draftwright-0.4.5 → draftwright-0.4.7}/tests/fixtures/nist_ctc_03_asme1_ap242.stp +0 -0
  160. {draftwright-0.4.5 → draftwright-0.4.7}/tests/fixtures/nist_ctc_04_asme1_ap203.stp +0 -0
  161. {draftwright-0.4.5 → draftwright-0.4.7}/tests/fixtures/nist_ctc_04_asme1_ap242.stp +0 -0
  162. {draftwright-0.4.5 → draftwright-0.4.7}/tests/fixtures/nist_ctc_05_asme1_ap203.stp +0 -0
  163. {draftwright-0.4.5 → draftwright-0.4.7}/tests/fixtures/nist_ctc_05_asme1_ap242.stp +0 -0
  164. {draftwright-0.4.5 → draftwright-0.4.7}/tests/fixtures/tuner_jig_blind_obround_pockets.step +0 -0
  165. {draftwright-0.4.5 → draftwright-0.4.7}/tests/refactor_golden/bracket_section.json +0 -0
  166. {draftwright-0.4.5 → draftwright-0.4.7}/tests/refactor_golden/centered_rebate.json +0 -0
  167. {draftwright-0.4.5 → draftwright-0.4.7}/tests/refactor_golden/chamfered.json +0 -0
  168. {draftwright-0.4.5 → draftwright-0.4.7}/tests/refactor_golden/filleted.json +0 -0
  169. {draftwright-0.4.5 → draftwright-0.4.7}/tests/refactor_golden/flange_dense.json +0 -0
  170. {draftwright-0.4.5 → draftwright-0.4.7}/tests/refactor_golden/grid_plate.json +0 -0
  171. {draftwright-0.4.5 → draftwright-0.4.7}/tests/refactor_golden/hex_bar.json +0 -0
  172. {draftwright-0.4.5 → draftwright-0.4.7}/tests/refactor_golden/holed_slot.json +0 -0
  173. {draftwright-0.4.5 → draftwright-0.4.7}/tests/refactor_golden/prismatic_ladder.json +0 -0
  174. {draftwright-0.4.5 → draftwright-0.4.7}/tests/refactor_golden/scattered_plate.json +0 -0
  175. {draftwright-0.4.5 → draftwright-0.4.7}/tests/refactor_golden/side_drilled.json +0 -0
  176. {draftwright-0.4.5 → draftwright-0.4.7}/tests/refactor_golden/turned_stepped.json +0 -0
  177. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_add_dimension.py +0 -0
  178. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_agents_guide.py +0 -0
  179. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_api_docs.py +0 -0
  180. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_architecture_docs.py +0 -0
  181. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_audit_differential.py +0 -0
  182. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_balloon_ring_standoff.py +0 -0
  183. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_carve_free_position_callers.py +0 -0
  184. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_compiled_plan_boundary.py +0 -0
  185. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_counting_calls.py +0 -0
  186. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_dense_sheet_canary.py +0 -0
  187. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_deprecation_dates.py +0 -0
  188. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_dimension_role_vocabulary.py +0 -0
  189. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_drawing_encapsulation.py +0 -0
  190. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_e2e_slice.py +0 -0
  191. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_e2e_standards.py +0 -0
  192. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_export_dxf_curves.py +0 -0
  193. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_export_dxf_zoom.py +0 -0
  194. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_fits.py +0 -0
  195. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_gdt_placement.py +0 -0
  196. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_issue_1000_envelope_reuse.py +0 -0
  197. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_issue_1086_declared_gears.py +0 -0
  198. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_issue_1142_hole_leader_labels.py +0 -0
  199. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_issue_1144_transactional_hole_table.py +0 -0
  200. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_issue_1147_legibility_pair_aggregation.py +0 -0
  201. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_issue_676_polygonal_boss_declare.py +0 -0
  202. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_issue_915_dense_case.py +0 -0
  203. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_issue_916_pocket_leader.py +0 -0
  204. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_issue_955_height_contingency.py +0 -0
  205. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_label_provenance.py +0 -0
  206. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_layout.py +0 -0
  207. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_layout_cleanliness.py +0 -0
  208. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_layout_hypothesis.py +0 -0
  209. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_layout_property.py +0 -0
  210. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_leader_footprint.py +0 -0
  211. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_lint_box_cache.py +0 -0
  212. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_lint_reconciliation.py +0 -0
  213. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_lint_structural.py +0 -0
  214. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_linting.py +0 -0
  215. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_location_vocabulary.py +0 -0
  216. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_note_verb.py +0 -0
  217. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_object_aspects.py +0 -0
  218. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_occupancy_boxes.py +0 -0
  219. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_parameter_id.py +0 -0
  220. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_pitch_dim_footprint.py +0 -0
  221. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_pmi.py +0 -0
  222. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_pmi_datum_lowering.py +0 -0
  223. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_pmi_gtol_lowering.py +0 -0
  224. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_pmi_part21.py +0 -0
  225. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_pocket_pattern.py +0 -0
  226. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_principal_profile_classifier.py +0 -0
  227. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_private_test_attr_reads.py +0 -0
  228. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_registry.py +0 -0
  229. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_render_seam.py +0 -0
  230. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_repack_geometry_seam.py +0 -0
  231. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_score.py +0 -0
  232. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_script_detail_parity.py +0 -0
  233. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_shared_box_memo.py +0 -0
  234. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_sheet_emit.py +0 -0
  235. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_sheet_gdt.py +0 -0
  236. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_sheet_identity_invariant.py +0 -0
  237. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_sheet_notes.py +0 -0
  238. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_sheet_of.py +0 -0
  239. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_sheet_section.py +0 -0
  240. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_sheet_tables.py +0 -0
  241. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_slot_pattern.py +0 -0
  242. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_soft_deprecation.py +0 -0
  243. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_strip_layout.py +0 -0
  244. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_suppression_ledger.py +0 -0
  245. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_suppression_marks.py +0 -0
  246. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_tolerances.py +0 -0
  247. {draftwright-0.4.5 → draftwright-0.4.7}/tests/test_witness_label_reconciliation.py +0 -0
@@ -2,6 +2,77 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ### Added
6
+
7
+ - Recogniser co-development now has a one-command, wheel-based two-checkout check; an exact
8
+ registry-release evidence record; automated dependency PR preparation; and documented landing,
9
+ compatibility, deprecation, rollback, ownership, and CI-budget rules. Dependency PRs retain the
10
+ canonical coverage/Codecov gate while avoiding four duplicate platform suites and the slow tier
11
+ (#1170).
12
+ - A versioned, independently authored STEP-analysis corpus and evaluator now report detection
13
+ recall/false positives, parameter fidelity, and downstream usefulness as separate evidence
14
+ layers. Hash-pinned positive, negative, ambiguous, compound, and topology-order-variant fixtures,
15
+ per-layer diagnostics, and an anti-self-validation mutation prevent recogniser output from
16
+ defining its own denominator (#1169).
17
+ - Draftwright now validates the released `b123d-recognisers` 0.2.0 capability manifest against
18
+ an exhaustive consumer-owned declaration of IR, DSL, generated-code, drawing, completeness,
19
+ and documentation states. Unknown families, stale implementations, record-schema drift, and
20
+ unevidenced state transitions fail closed; intentionally geometry-only repeating-profile
21
+ evidence remains usable without acquiring invented drafting semantics.
22
+
23
+ ### Changed
24
+
25
+ - Updated the exact `b123d-recognisers` production lock from 0.2.0 to 0.2.1.
26
+ The immutable PyPI wheel/sdist hashes and capability-manifest digest
27
+ `383a196329ed6a3d1bf88f18ae4ff75613c032f35d40f2b53302b6744da4d19e` are
28
+ recorded in `.github/recogniser-release.json`; focused compatibility evidence and the
29
+ normal consumer gates run on the generated PR.
30
+
31
+ ### Fixed
32
+
33
+ - Stable release and TestPyPI snapshot builds now update package metadata and the independently
34
+ validated Draftwright consumer-contract identity atomically. This prevents stripped `.dev0`
35
+ and numbered development artifacts from failing their own capability-contract validation.
36
+ - Installed wheels validate every portable recogniser-contract property without requiring the
37
+ repository-only behavior-test files whose existence remains a strict source/CI gate. This keeps
38
+ the packaged STEP-analysis evaluator usable without weakening evidence checks in development.
39
+ - Post-release development-version bumps now update the consumer-contract identity on a branch,
40
+ open a protected PR, and dispatch the narrow maintenance gate instead of attempting a rejected
41
+ direct push to protected `main` (#1170).
42
+ - Machined-feature leader callouts now collect all clear alternatives within each
43
+ post-drain pass and use a bounded maximum-cardinality, minimum-leader-length
44
+ assignment instead of taking corners greedily in feature order (#740). If a
45
+ deterministic alternative-count, pass-size, candidate-pair, or search budget is
46
+ reached, placement retains the legacy greedy result or a result no worse than it
47
+ rather than losing additional callouts. Pre-drain diameter clearance ranking and
48
+ its Policy-B fallback remain unchanged.
49
+
50
+ ## v0.4.6 — 2026-08-15
51
+
52
+ ### Changed
53
+
54
+ - **Geometry recognition is supplied by the standalone Apache-2.0
55
+ [`b123d-recognisers`](https://github.com/pzfreo/b123d-recognisers) package.** The cutover
56
+ pins the published stable release `v0.1.0` (built from commit
57
+ `9e622716c14d491729b5191aa6e5d8351c982a51`), removes the
58
+ duplicate embedded implementation, and keeps only identity-preserving
59
+ `draftwright.recognition` / `draftwright.score` compatibility re-exports until 0.6.0.
60
+ Draftwright continues to own the one-result build/lint cache, record→IR conversion, drafting
61
+ policy, placement, and critique. The package migration is manifest-pinned and golden-backed;
62
+ its sole normalization makes an exact dominant-axis tie deterministic across operating
63
+ systems without changing feature policy.
64
+
65
+ ### Fixed
66
+
67
+ - Hole-callout obstacle checks now account for the rendered leader shaft width and
68
+ tip-local arrowhead flare instead of treating the leader as a zero-width centreline
69
+ (#367). The analytical check remains geometry-only and does not over-reserve the
70
+ arrowhead's clearance along the whole shaft.
71
+ - Exact X/Z slanted flats retain their established lint-clean side-view callout after the
72
+ recogniser package's cross-platform semantic-axis normalization. The normalized recognition
73
+ record remains unchanged; `FlatFeature.presentation_axis` carries the downstream drawing-view
74
+ choice, preventing a leader/silhouette crossing without duplicating recognition.
75
+
5
76
  ## v0.4.5 — 2026-08-15
6
77
 
7
78
  **A focused technical-drawing correctness patch.** Cross-drilled hole locations now remain
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: draftwright
3
- Version: 0.4.5
3
+ Version: 0.4.7
4
4
  Summary: Automated technical-drawing generation for build123d
5
5
  Project-URL: Homepage, https://github.com/pzfreo/draftwright
6
6
  Project-URL: Repository, https://github.com/pzfreo/draftwright
@@ -684,6 +684,7 @@ Classifier: Programming Language :: Python :: 3.14
684
684
  Classifier: Topic :: Scientific/Engineering
685
685
  Requires-Python: <3.15,>=3.10
686
686
  Requires-Dist: antlr4-python3-runtime<4.10
687
+ Requires-Dist: b123d-recognisers==0.2.1
687
688
  Requires-Dist: build123d-drafting-helpers>=0.14.1
688
689
  Requires-Dist: build123d<0.11,>=0.9.0; python_full_version < '3.13'
689
690
  Requires-Dist: build123d<0.12,>=0.11; python_full_version >= '3.13'
@@ -967,17 +968,20 @@ or declared features converge on a `PartModel` IR, then planner-fed and
967
968
  sanctioned model-routed render intents feed shared placement, projection, and
968
969
  export. Coverage lint independently compares recognised geometry with the
969
970
  placed drawing, so an upstream omission cannot hide from verification. It
970
- builds on two libraries:
971
+ builds on three libraries:
971
972
 
972
973
  ```
973
974
  draftwright
975
+ └── b123d-recognisers — deterministic geometry recognition
974
976
  └── build123d-drafting-helpers — Dimension, Leader, HoleCallout, …
975
977
  └── build123d — CAD kernel
976
978
  ```
977
979
 
978
- It owns feature recognition (`recognition/`) and linting (`linting/`); annotation
979
- primitives (`Dimension`, `Leader`, etc.) live in `build123d-drafting-helpers` and can
980
- be used independently. The compiler is largely converged in production — turned
980
+ Geometry recognition lives in the Apache-2.0 `b123d-recognisers` package. Draftwright owns
981
+ the per-build recognition cache, record→IR conversion, drafting policy, and linting
982
+ (`linting/`); annotation primitives (`Dimension`, `Leader`, etc.) live in
983
+ `build123d-drafting-helpers` and can be used independently. The compiler is largely converged
984
+ in production — turned
981
985
  dims/lengths, centre marks, envelope, slots, holes (callouts/locations/grouping),
982
986
  the section A–A trigger, the prismatic step-ladder + rotational furniture, and PMI/GD&T
983
987
  are all on the IR. See
@@ -268,17 +268,20 @@ or declared features converge on a `PartModel` IR, then planner-fed and
268
268
  sanctioned model-routed render intents feed shared placement, projection, and
269
269
  export. Coverage lint independently compares recognised geometry with the
270
270
  placed drawing, so an upstream omission cannot hide from verification. It
271
- builds on two libraries:
271
+ builds on three libraries:
272
272
 
273
273
  ```
274
274
  draftwright
275
+ └── b123d-recognisers — deterministic geometry recognition
275
276
  └── build123d-drafting-helpers — Dimension, Leader, HoleCallout, …
276
277
  └── build123d — CAD kernel
277
278
  ```
278
279
 
279
- It owns feature recognition (`recognition/`) and linting (`linting/`); annotation
280
- primitives (`Dimension`, `Leader`, etc.) live in `build123d-drafting-helpers` and can
281
- be used independently. The compiler is largely converged in production — turned
280
+ Geometry recognition lives in the Apache-2.0 `b123d-recognisers` package. Draftwright owns
281
+ the per-build recognition cache, record→IR conversion, drafting policy, and linting
282
+ (`linting/`); annotation primitives (`Dimension`, `Leader`, etc.) live in
283
+ `build123d-drafting-helpers` and can be used independently. The compiler is largely converged
284
+ in production — turned
282
285
  dims/lengths, centre marks, envelope, slots, holes (callouts/locations/grouping),
283
286
  the section A–A trigger, the prismatic step-ladder + rotational furniture, and PMI/GD&T
284
287
  are all on the IR. See
@@ -25,15 +25,15 @@ architecture** table; open retired or superseded records only for design history
25
25
  | [0004](0004-compose-then-pack-view-blocks.md) | Compose-then-pack: views as blocks carrying their annotation footprint | Compose each view with its annotation footprint, then pack fixed-topology blocks. | Accepted | `test_layout.py`, `test_layout_cleanliness.py`, `test_refactor_golden.py` |
26
26
  | [0005](0005-pipeline-architecture-and-state-ownership.md) | Compiler-pipeline module boundaries and single-owner build state | Give compiler stages explicit module homes and build-time state explicit owners. | Accepted; implemented; compatibility aliases tracked by #720 | `test_import_boundaries.py`, `test_drawing_encapsulation.py`, `test_registry.py` |
27
27
  | [0006](0006-deterministic-layout-via-bundled-fonts.md) | Deterministic cross-platform layout via bundled, path-pinned fonts | Pin bundled font files so text measurement and layout are cross-platform deterministic. | Accepted | `test_refactor_golden.py`, `test_layout_cleanliness.py` |
28
- | [0007](0007-own-recognition-and-linting.md) | draftwright owns feature recognition and linting; helpers becomes the rendering library | draftwright owns recognition and linting; helpers remains a rendering library. | Accepted | `test_import_boundaries.py`, `test_recognition.py`, `test_linting.py` |
28
+ | [0007](0007-own-recognition-and-linting.md) | draftwright owns feature recognition and linting; helpers becomes the rendering library | Draftwright owns linting, recognition lifecycle, IR conversion, and drafting policy; `b123d-recognisers` owns geometry recognition; helpers remains the rendering library. | Accepted; extraction deployed by Amendment 2 | `test_import_boundaries.py`, `test_external_recognition_boundary.py`, `test_linting.py` |
29
29
  | [0010](0010-annotation-provenance-seam.md) | Annotation provenance: record intent → annotation once, at the render seam | Record annotation provenance once at the render/add seam. | Accepted; landed | `test_render_seam.py`, `test_registry.py` |
30
30
  | [0011](0011-ir-as-public-input.md) | The IR as a public input: declare features, don't only detect them | Accept the feature IR as public input through declarations and the `Sheet` façade. | Accepted; core landed; #62/#462/#495 remain | `test_declare.py`, `test_object_aspects.py`, `test_sheet_gdt.py` |
31
31
  | [0012](0012-edits-as-pinned-priority-candidates-in-the-global-solve.md) | User annotation edits are pinned, priority-ranked corridor candidates | Drain recorded semantic edits through corridor placement with pin and priority. | Accepted; partially landed; full recomposition/parity remains #426/#707 (#661 detail views landed) | `test_make_drawing.py`, `test_sheet_emit.py` |
32
- | [0013](0013-uniform-recognition-and-shared-package.md) | A uniform recogniser/feature contract (with `b123d-recognisers` as its deferred shared deployment) | Enforce a uniform geometry-only recogniser contract internally; defer package extraction until there is a second consumer. | Accepted; Phase 1 in progress; extraction deferred | `test_recogniser_contract.py`, `test_import_boundaries.py` |
33
- | [0014](0014-collect-then-solve-annotation-placement.md) | Collect-then-solve annotation placement (as built) | Collect, select, assign, and deterministically solve annotations per corridor before rendering. | Accepted; supersedes 0009 | `test_carve_free_position_callers.py`, `test_strip_layout.py`, `test_layout_property.py`, `test_import_boundaries.py` |
32
+ | [0013](0013-uniform-recognition-and-shared-package.md) | A uniform recogniser/feature contract and shared `b123d-recognisers` deployment | Enforce the geometry-only contract in the shared package and consume it through Draftwright's record→IR boundary. | Accepted; Phases 1–2 deployed | `test_external_recognition_boundary.py`, `test_detect_registry.py`, package semantic goldens |
33
+ | [0014](0014-collect-then-solve-annotation-placement.md) | Collect-then-solve annotation placement (as built) | Collect, select, assign, and deterministically solve annotations per corridor before rendering; post-drain feature leaders also assign jointly within each pass. | Accepted; supersedes 0009; #740 Amendment 1 | `test_carve_free_position_callers.py`, `test_strip_layout.py`, `test_layout_property.py`, `test_issue_740_leader_assignment.py`, `test_import_boundaries.py` |
34
34
  | [0015](0015-part-drawing-compiler-as-built.md) | The part-drawing compiler, as built | Use one detected-or-declared feature IR and planner-fed dimension groups as the compiler waist. | Accepted; supersedes 0008 | `test_part_model.py`, `test_detect_once.py`, `test_import_boundaries.py` |
35
35
  | [0016](0016-declared-dimensioning-intent.md) | Declared dimensioning intent: capture what to measure, let the engine place it | Declare which measurements matter as scale-independent intent routed through the planner and corridor solve; never hardcode dimension geometry. | Accepted; epic #867 complete; phase 6 landed (#940) | `test_compiled_plan_boundary.py`, `test_label_provenance.py`, `test_sheet_emit.py`, `test_add_dimension.py` |
36
- | [0017](0017-recognition-inventory-correspondence-and-measurement-provenance.md) | One recognition result per run; correspondence is evidence-gated | Produce one explicit recognition result owned by `BuildState`; require vertical-slice evidence before generalising correspondence, identity, requirements, outcomes, or reconciliation. | Accepted; ownership phase landed, extensions gated by #1018 | `test_recognition_manifest.py`, `test_recognition_result.py`, `test_declared_recognition_gate.py` |
36
+ | [0017](0017-recognition-inventory-correspondence-and-measurement-provenance.md) | One recognition result per run; correspondence is evidence-gated | Produce one external immutable recognition result held by Draftwright's per-run cache; require vertical-slice evidence before generalising correspondence, identity, requirements, outcomes, or reconciliation. | Accepted; external cache ownership clarified, extensions gated by #1018 | `test_external_recognition_boundary.py`, `test_recognition_manifest.py`, `test_declared_recognition_gate.py` |
37
37
  | [0018](0018-requirement-driven-view-planning-and-editable-sheet-layout.md) | Requirement-driven view planning and editable sheet layout | Use one `ViewSpec` vocabulary and planner with distinct authored `ViewConstraints` and immutable `ResolvedViewPlan`; jointly validate views, typography, convention, scale, paper and layout against requirement survival. If accepted, supersedes ADR 0004's fixed-topology assumption while retaining compose-then-pack. | Proposed; tracked by #1130 | Required guards are listed in the ADR |
38
38
 
39
39
  ## Historical records
@@ -0,0 +1,52 @@
1
+ # Recogniser capability contract
2
+
3
+ Draftwright consumes the public capability manifest installed with `b123d-recognisers`; it never
4
+ reads a sibling checkout or a package-private file. The matching Draftwright-owned declaration is
5
+ implemented in `draftwright.recogniser_contract` and validated in CI.
6
+
7
+ The package manifest describes geometry evidence. Draftwright separately declares, for every
8
+ package family, its IR adapter, fluent `Sheet` surface, generated-Sheet behavior, drawing consumer,
9
+ completeness treatment, and documentation. A stage is either `supported`, `deferred`,
10
+ `not-applicable`, or `unsupported`, with the evidence required by that state. This separation lets
11
+ other CAD consumers apply different policy without making Draftwright policy part of the geometry
12
+ library.
13
+
14
+ ## What failures mean
15
+
16
+ Validation fails closed and names the family and boundary whenever possible:
17
+
18
+ - **family inventory mismatch** — the installed package added, removed, or renamed a family.
19
+ Update the package pin deliberately, add or remove the Draftwright declaration, and provide the
20
+ downstream behavior or an explicit non-supported state.
21
+ - **record schema mismatch** — a consumed record schema changed. Review the record fields and
22
+ compatibility notes, update the relevant adapter and tests, then pin the accepted schema version.
23
+ - **stale implementation** — a declared adapter, `Sheet` method, emitter, renderer, or completeness
24
+ function no longer resolves. Repair the implementation reference and its independent behavior
25
+ evidence; do not merely rename the string.
26
+ - **evidence is missing** — a supported claim points at a deleted test or document. Restore or
27
+ replace the behavior evidence before changing the path.
28
+ - **unknown state or unsupported format** — the installed contract uses semantics this Draftwright
29
+ version does not understand. Upgrade the validator before accepting the package version.
30
+ - **geometry-only family invents semantics** — geometry used for critique was incorrectly given an
31
+ inferred IR/DSL/code-generation/drawing path. Keep it geometry-only unless a separately reviewed
32
+ Draftwright feature introduces authored semantics and compatibility evidence.
33
+ - **reserved family claims supported downstream semantics** — a family-level `deferred` or
34
+ `unsupported` disposition still advertises an implemented downstream stage. Either make the
35
+ family disposition supported with behavior evidence, or defer/disable every semantic stage and
36
+ retain its tracking issue.
37
+ - **state transition lacks evidence** — a capability was strengthened or weakened without a named
38
+ Draftwright version, release notes, and compatibility tests. Supply all three as one reviewed
39
+ transition.
40
+
41
+ ## Adding or changing a recogniser
42
+
43
+ Land the package record, independent geometry evidence, manifest update, and compatible package
44
+ release first. Then update Draftwright against that immutable release. For each applicable stage,
45
+ add the adapter, declaration, emitted-Sheet round trip, drawing behavior, and completeness evidence;
46
+ for an inapplicable or deferred stage, state why and link its tracking issue. The contract test
47
+ derives package record outputs, converter registries, live `Sheet` methods, and emitter branches
48
+ independently, so copying a new name into the declaration alone cannot make CI pass.
49
+
50
+ `bosses` is the fully consumed reference family. `repeating-radial-profiles` is the opposite
51
+ reference: it remains geometry-only critique evidence for a separately authored gear declaration,
52
+ with no inferred gear feature added to fill the table.
@@ -0,0 +1,35 @@
1
+ # Sheet and fluent handles
2
+
3
+ `Sheet` is the declared authoring façade. Feature verbs return fluent handles; those handles
4
+ are documented here because they are part of normal use even though their Python names begin
5
+ with an underscore.
6
+
7
+ ## Sheet
8
+
9
+ ::: draftwright.sheet.Sheet
10
+ options:
11
+ filters: public
12
+
13
+ ## Hole handle
14
+
15
+ ::: draftwright.sheet._Hole
16
+ options:
17
+ filters: public
18
+
19
+ ## Diameter and step handle
20
+
21
+ ::: draftwright.sheet._Dim
22
+ options:
23
+ filters: public
24
+
25
+ ## Multi-parameter handle
26
+
27
+ ::: draftwright.sheet._Params
28
+ options:
29
+ filters: public
30
+
31
+ ## GD&T control builder
32
+
33
+ ::: draftwright.sheet._Control
34
+ options:
35
+ filters: public
@@ -0,0 +1,102 @@
1
+ # Repeating Radial Profile Evidence (#1062)
2
+
3
+ > **Status:** productionised by #1087, 2026-08-09. The executable mutations remain in
4
+ > `tests/test_issue_1058_wheel_profile.py`; the reusable evidence is owned by
5
+ > `recognition/repeating_profiles.py`. No automatic gear semantics are approved by this note.
6
+
7
+ ## Decision
8
+
9
+ The wheel fixture proves a **closed 13-fold repeating radial outer profile** about its Z
10
+ axis. It does not prove that the profile is a gear, nor does it establish module, pressure
11
+ angle, helix angle, backlash, tooth thickness, quality grade, or manufacturing process.
12
+
13
+ Keep `unrecognised_defining_geometry` for this part. Do not add an automatic `GearFeature`,
14
+ emit a gear callout, or treat outside/root diameters and repeat count as a complete
15
+ manufacturing definition. The useful next boundary is:
16
+
17
+ 1. reusable geometry-only correspondence for a repeating radial profile; and
18
+ 2. a separately designed, explicit declaration of gear requirements and their drawing
19
+ presentation.
20
+
21
+ The second may consume the first to check that declared tooth count and axis correspond to
22
+ the solid. Recognition must not manufacture the declaration.
23
+
24
+ ## Pinned Evidence
25
+
26
+ | Item | Observed fact |
27
+ |---|---|
28
+ | Fixture | `tests/fixtures/issue_1058_wheel_rh.step` |
29
+ | SHA-256 | `4911c06426f0ceeedc198416e058aabc1c1a6a65e9e766eca7efed5484a27cda` |
30
+ | STEP form | AP214, one solid, generic product name `SOLID` |
31
+ | Overall bounds | X -3.8000001..3.7733322; Y -3.8000001..3.8000001; Z -3.8500001..3.8500001 mm |
32
+ | Principal outer boundaries | two, at the opposed Z limits |
33
+ | Boundary topology | one closed 52-edge outer wire on each end face |
34
+ | Edge inventory | 13 circular edges and 39 B-splines |
35
+ | Circular edges | radius 3.8 mm, common XY centre (0, 0) |
36
+ | Rotational correspondence | four complete 13-edge orbits under `2*pi/13` rotation |
37
+ | Orbit roles | one circular-tip orbit and three B-spline orbits |
38
+
39
+ Both end faces prove the same correspondence despite different edge start and traversal.
40
+ The STEP header and entities contain no gear name, PMI, standard, or parameter metadata.
41
+
42
+ ## What The Proof Establishes
43
+
44
+ The test samples every curve in each outer wire, then requires a one-to-one mapping of every
45
+ curve under one sector rotation. Curve type, length, and sampled geometry must agree within
46
+ the fixed tolerance. The mapping must be bijective, each orbit must contain exactly 13
47
+ curves, and every endpoint must belong to one connected degree-two cycle.
48
+
49
+ This is stronger than the existing lint hint. `_radial_outer_arc_count` reports 13 equal,
50
+ equally spaced arcs on a common circle, but explicitly does not claim that the 39 intervening
51
+ B-splines repeat. The complete-wire proof closes that geometric gap without widening the
52
+ semantic claim.
53
+
54
+ The following mutations fail:
55
+
56
+ - retaining only the 13 common-circle arcs;
57
+ - altering one intervening B-spline sample while leaving all arcs unchanged;
58
+ - breaking one endpoint so the outer wire is open;
59
+ - changing one sector boundary while preserving a closed cycle; and
60
+ - relying on the wire's original start edge or traversal direction.
61
+
62
+ The changed-intervening-curve mutation is the decisive guard: an arc-count implementation
63
+ would still pass it.
64
+
65
+ ## Why This Is Not Gear Recognition
66
+
67
+ Many manufactured profiles can be cyclic without being gears. Even if a human recognises
68
+ the fixture's purpose, the B-rep alone does not uniquely identify the generating standard or
69
+ the requirements needed to reproduce and inspect it. Fitting a plausible standard from 13
70
+ tips and a diameter would turn a guess into normative drawing output.
71
+
72
+ A truthful technical drawing therefore needs explicit requirements supplied by the author,
73
+ usually including at least the gear system/type, tooth count, module or diametral pitch,
74
+ pressure angle, reference geometry, and the applicable accuracy or inspection requirement.
75
+ The exact public surface and table/callout form need a standards-backed design slice; this
76
+ discovery does not invent them.
77
+
78
+ ## ADR Fit
79
+
80
+ - **ADR 0013:** the proven repeat is lower-tier, geometry-only evidence. `gear` is an
81
+ application semantic and must not be smuggled into the recognition record.
82
+ - **ADR 0015:** any future automatic record must enter through the compiler's existing
83
+ recognition waist. A declared gear requirement belongs on the declared path.
84
+ - **ADR 0017:** if productionised, one orchestration owns the correspondence evidence and
85
+ consumers project from it. The mutation precedes trust in the guard, and a new identity
86
+ scheme is not justified by this single case.
87
+ - **ADR 0011:** explicit author intent is the appropriate source for requirements that the
88
+ B-rep cannot establish.
89
+
90
+ No ADR amendment is required. No placement decision is made, so ADR 0014 is unaffected.
91
+
92
+ ## Follow-up Slices
93
+
94
+ 1. **Productionised in #1087.** The pure full-wire correspondence is owned by the one
95
+ recognition orchestration and consumed by physical critique for declared-profile
96
+ correspondence. It does not clear completeness merely because the repeat exists.
97
+ 2. **Design declared gear requirements.** Start from the applicable drawing/gear standards
98
+ and define the minimum honest declaration, validation, IR, and table/callout output. Include
99
+ a mismatch diagnostic when declared count or axis disagrees with geometric evidence.
100
+ 3. **Only then assess automatic assistance.** Recognition may prefill proven geometric facts
101
+ such as axis and repeat count for user confirmation. It may not infer the normative gear
102
+ specification.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "draftwright"
7
- version = "0.4.5"
7
+ version = "0.4.7"
8
8
  description = "Automated technical-drawing generation for build123d"
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
@@ -18,6 +18,7 @@ dependencies = [
18
18
  "build123d>=0.9.0,<0.11 ; python_full_version < '3.13'",
19
19
  "build123d>=0.11,<0.12 ; python_full_version >= '3.13'",
20
20
  "build123d-drafting-helpers>=0.14.1",
21
+ "b123d-recognisers==0.2.1",
21
22
  "numpy>=1.24",
22
23
  # PNG raster export (SVG→PDF→PNG). Both permissively licensed (Pillow HPND; pypdfium2
23
24
  # Apache-2.0 wrapping Google PDFium BSD-3) and ship pre-built wheels with NO native system
@@ -68,6 +69,9 @@ include = [
68
69
  "src/draftwright",
69
70
  "tests",
70
71
  "skills",
72
+ "docs/reference/recogniser-capabilities.md",
73
+ "docs/reference/sheet.md",
74
+ "docs/research/1062-repeating-radial-profile-evidence.md",
71
75
  "README.md",
72
76
  "LICENSE",
73
77
  "CHANGELOG.md",
@@ -163,6 +167,7 @@ dev = [
163
167
  "pytest-timeout>=2",
164
168
  "pytest-xdist>=3",
165
169
  "ruff>=0.4",
170
+ "tomli>=2; python_version < '3.11'",
166
171
  ]
167
172
 
168
173
  [tool.codespell]
@@ -25,8 +25,9 @@ from types import SimpleNamespace
25
25
  from typing import TYPE_CHECKING, Literal
26
26
 
27
27
  if TYPE_CHECKING:
28
+ from b123d_recognisers import RecognitionResult, TurnedProfile
29
+
28
30
  from draftwright.compose import StripDepths
29
- from draftwright.recognition import RecognitionResult, TurnedProfile
30
31
 
31
32
  from build123d import Align, BoundBox, Compound, Edge, Location, Mode, Shape, Text, Vector
32
33
  from build123d_drafting.helpers import (
@@ -327,6 +327,94 @@ def _segment_clips_box(p, q, box, pad=0.0) -> bool:
327
327
  return _segment_clip_extent(p, q, box, pad=pad) is not None
328
328
 
329
329
 
330
+ def _convex_polygon_overlaps_box(points, box) -> bool:
331
+ """Whether a convex page-plane polygon has positive-area overlap with an AABB.
332
+
333
+ This is the small separating-axis primitive used by
334
+ :func:`_leader_ink_crosses_box`. Keeping it here makes the rendered-ink model pure
335
+ rectangle/vector maths: placement does not build temporary OCC geometry merely to ask
336
+ whether a candidate is clear (ADRs 0004/0014).
337
+ """
338
+ corners = (
339
+ (box[0], box[1]),
340
+ (box[2], box[1]),
341
+ (box[2], box[3]),
342
+ (box[0], box[3]),
343
+ )
344
+ axes = [(1.0, 0.0), (0.0, 1.0)]
345
+ axes.extend(
346
+ (-(second[1] - first[1]), second[0] - first[0])
347
+ for first, second in zip(points, (*points[1:], points[0]), strict=True)
348
+ )
349
+ for ax, ay in axes:
350
+ if abs(ax) + abs(ay) <= 1e-12:
351
+ continue
352
+ polygon_projection = [x * ax + y * ay for x, y in points]
353
+ box_projection = [x * ax + y * ay for x, y in corners]
354
+ if max(polygon_projection) <= min(box_projection) or max(box_projection) <= min(
355
+ polygon_projection
356
+ ):
357
+ return False
358
+ return True
359
+
360
+
361
+ def _leader_ink_crosses_box(
362
+ tip,
363
+ elbow,
364
+ box,
365
+ *,
366
+ arrow_length: float,
367
+ line_width: float,
368
+ ) -> bool:
369
+ """Whether a rendered leader's tip-to-elbow ink overlaps *box*.
370
+
371
+ A leader is not a zero-width segment. Its shaft is a swept rectangle of
372
+ ``line_width`` and each supported arrowhead style is contained by the triangle from
373
+ the tip to a base ``arrow_length`` along the shaft, with the helper's
374
+ ``arrow_length / 3`` lateral flare. Testing those two local convex footprints keeps
375
+ the arrow clearance near the tip; inflating the entire shaft by the arrow size would
376
+ reject genuinely clear obstacles near the elbow (#367).
377
+
378
+ Touching boundaries are clear, matching :func:`_boxes_overlap` and the placement-side
379
+ semantics of :func:`_segment_crosses_box`.
380
+ """
381
+ dx, dy = float(elbow[0]) - float(tip[0]), float(elbow[1]) - float(tip[1])
382
+ length = math.hypot(dx, dy)
383
+ half_line = max(0.0, float(line_width)) / 2.0
384
+ head_length = max(0.0, float(arrow_length))
385
+
386
+ if length <= 1e-12:
387
+ half = max(half_line, head_length / 3.0)
388
+ point_box = (tip[0] - half, tip[1] - half, tip[0] + half, tip[1] + half)
389
+ return _boxes_overlap(point_box, box)
390
+
391
+ ux, uy = dx / length, dy / length
392
+ vx, vy = -uy, ux
393
+
394
+ if half_line > 0.0:
395
+ shaft = (
396
+ (tip[0] + vx * half_line, tip[1] + vy * half_line),
397
+ (elbow[0] + vx * half_line, elbow[1] + vy * half_line),
398
+ (elbow[0] - vx * half_line, elbow[1] - vy * half_line),
399
+ (tip[0] - vx * half_line, tip[1] - vy * half_line),
400
+ )
401
+ if _convex_polygon_overlaps_box(shaft, box):
402
+ return True
403
+
404
+ if head_length > 0.0:
405
+ half_head = head_length / 3.0
406
+ base_x, base_y = tip[0] + ux * head_length, tip[1] + uy * head_length
407
+ arrow = (
408
+ (tip[0], tip[1]),
409
+ (base_x + vx * half_head, base_y + vy * half_head),
410
+ (base_x - vx * half_head, base_y - vy * half_head),
411
+ )
412
+ if _convex_polygon_overlaps_box(arrow, box):
413
+ return True
414
+
415
+ return False
416
+
417
+
330
418
  def _segments_cross_or_overlap(a1, a2, b1, b2) -> bool:
331
419
  """Whether two page-plane segments cross or overlap beyond a shared endpoint.
332
420
 
@@ -17,6 +17,14 @@ from collections.abc import Callable
17
17
  from dataclasses import dataclass, replace
18
18
  from typing import cast
19
19
 
20
+ from b123d_recognisers import (
21
+ RecognitionResult,
22
+ TurnedProfile,
23
+ TurnedStep,
24
+ analyse_cylinders,
25
+ build_recognition_result,
26
+ full_cylinders,
27
+ )
20
28
  from build123d import Compound, Shape
21
29
  from build123d_drafting.helpers import draft_preset
22
30
  from OCP.IFSelect import IFSelect_ReturnStatus
@@ -47,14 +55,6 @@ from draftwright.compose import (
47
55
  from draftwright.model import build_part_model
48
56
  from draftwright.model.ir import Datum, PartModel, StepFeature, StepLevelFeature
49
57
  from draftwright.model.planner import plan_dimensions
50
- from draftwright.recognition import (
51
- RecognitionResult,
52
- TurnedProfile,
53
- TurnedStep,
54
- analyse_cylinders,
55
- build_recognition_result,
56
- full_cylinders,
57
- )
58
58
 
59
59
  _log = logging.getLogger(__name__)
60
60
 
@@ -630,7 +630,7 @@ def _analyse(
630
630
  # by #1025). Both callers deriving this separately let lint project over a different
631
631
  # ladder than the model was sized from — which is exactly the divergence one waist is
632
632
  # supposed to prevent.
633
- step_zs = recognition.step_ladder(bb)
633
+ step_zs = recognition.step_ladder_for_z_span(bb.min.Z, bb.max.Z)
634
634
  # The aggregate owns the shared substrate from here on. Rebind the local projection so
635
635
  # model construction, Analysis and the finished BuildState all consume the same inventory
636
636
  # object rather than parallel list/tuple wrappers that merely happen to contain equal data.
@@ -970,6 +970,19 @@ def occupancy_boxes(o, stroke_pad=None):
970
970
  _STROKE_PAD = 1.2
971
971
 
972
972
 
973
+ def annotation_obstacle_boxes(dwg, annotation):
974
+ """One annotation's obstacle boxes under the shared strip-occupancy policy.
975
+
976
+ Candidate-based placers use this before an annotation is registered; factoring
977
+ the preset-aware stroke pad here keeps those candidate conflicts identical to
978
+ the boxes :func:`strip_obstacles` will expose after placement (#740).
979
+ """
980
+
981
+ arrow_length = getattr(getattr(dwg, "draft", None), "arrow_length", None)
982
+ pad = max(_STROKE_PAD, arrow_length / 2) if arrow_length else _STROKE_PAD
983
+ return occupancy_boxes(annotation, stroke_pad=pad)
984
+
985
+
973
986
  def strip_obstacles(dwg, view=None, *, crossable=(), named=False):
974
987
  """The COMPLETE occupancy for strip placement (ADR 0009): every placed
975
988
  annotation's full rendered footprint, optionally restricted to *view*, minus
@@ -1005,9 +1018,6 @@ def strip_obstacles(dwg, view=None, *, crossable=(), named=False):
1005
1018
 
1006
1019
  The occupancy source for the collect-then-solve carve — every migrated renderer's
1007
1020
  ``place_strip_candidates`` call wires this in (#321/#150/P3)."""
1008
- # Preset-aware stroke pad (#688 review): arrowheads scale with font_size.
1009
- al = getattr(getattr(dwg, "draft", None), "arrow_length", None)
1010
- pad = max(_STROKE_PAD, al / 2) if al else _STROKE_PAD
1011
1021
  boxes: list = []
1012
1022
  for name, o in dwg.iter_annotations():
1013
1023
  if view is not None:
@@ -1016,7 +1026,7 @@ def strip_obstacles(dwg, view=None, *, crossable=(), named=False):
1016
1026
  continue # owned by a different ortho view → its own (disjoint) block
1017
1027
  if type(o).__name__ in crossable:
1018
1028
  continue # this consumer may cross it (centre lines/marks for a dim)
1019
- occ = occupancy_boxes(o, stroke_pad=pad) # decomposed, not one hull (#685)
1029
+ occ = annotation_obstacle_boxes(dwg, o) # decomposed, not one hull (#685/#740)
1020
1030
  boxes.extend(((name, b) for b in occ) if named else occ)
1021
1031
  return boxes
1022
1032