draftwright 0.4.9__tar.gz → 0.4.11__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 (274) hide show
  1. {draftwright-0.4.9 → draftwright-0.4.11}/CHANGELOG.md +115 -0
  2. {draftwright-0.4.9 → draftwright-0.4.11}/PKG-INFO +2 -2
  3. {draftwright-0.4.9 → draftwright-0.4.11}/docs/adr/README.md +1 -1
  4. {draftwright-0.4.9 → draftwright-0.4.11}/docs/reference/recogniser-capabilities.md +14 -7
  5. draftwright-0.4.11/docs/reference/sheet.md +71 -0
  6. {draftwright-0.4.9 → draftwright-0.4.11}/pyproject.toml +2 -2
  7. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/__init__.py +7 -0
  8. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/_core.py +107 -16
  9. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/_geometry.py +121 -1
  10. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/analysis.py +173 -3
  11. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/annotations/_common.py +19 -1
  12. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/annotations/from_model.py +236 -118
  13. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/annotations/holes.py +71 -7
  14. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/annotations/orchestrator.py +140 -5
  15. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/annotations/sections.py +157 -86
  16. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/builder.py +745 -54
  17. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/compose.py +251 -45
  18. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/drawing.py +75 -9
  19. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/linting/coverage.py +81 -14
  20. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/linting/hole_coverage.py +8 -9
  21. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/linting/pmi_coverage.py +45 -3
  22. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/linting/quality.py +8 -0
  23. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/model/__init__.py +5 -0
  24. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/model/compiled.py +64 -23
  25. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/model/declare.py +114 -13
  26. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/model/detect.py +55 -19
  27. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/model/ir.py +37 -8
  28. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/model/planner.py +293 -11
  29. draftwright-0.4.11/src/draftwright/model/pmi_lowering.py +276 -0
  30. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/recogniser_contract.py +56 -18
  31. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/sheet.py +558 -27
  32. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/sheet_emit.py +101 -12
  33. draftwright-0.4.11/src/draftwright/view_plan.py +730 -0
  34. {draftwright-0.4.9 → draftwright-0.4.11}/tests/conftest.py +0 -2
  35. draftwright-0.4.11/tests/fixtures/grm04_drive_plate.step +1001 -0
  36. {draftwright-0.4.9 → draftwright-0.4.11}/tests/refactor_golden/bracket_section.json +4 -0
  37. {draftwright-0.4.9 → draftwright-0.4.11}/tests/refactor_golden/centered_rebate.json +4 -0
  38. {draftwright-0.4.9 → draftwright-0.4.11}/tests/refactor_golden/chamfered.json +40 -36
  39. {draftwright-0.4.9 → draftwright-0.4.11}/tests/refactor_golden/filleted.json +40 -36
  40. {draftwright-0.4.9 → draftwright-0.4.11}/tests/refactor_golden/flange_dense.json +4 -0
  41. {draftwright-0.4.9 → draftwright-0.4.11}/tests/refactor_golden/grid_plate.json +9 -0
  42. {draftwright-0.4.9 → draftwright-0.4.11}/tests/refactor_golden/grooved_shaft.json +9 -0
  43. {draftwright-0.4.9 → draftwright-0.4.11}/tests/refactor_golden/hex_bar.json +4 -0
  44. {draftwright-0.4.9 → draftwright-0.4.11}/tests/refactor_golden/holed_slot.json +4 -0
  45. {draftwright-0.4.9 → draftwright-0.4.11}/tests/refactor_golden/pocketed.json +4 -0
  46. {draftwright-0.4.9 → draftwright-0.4.11}/tests/refactor_golden/prismatic_ladder.json +44 -40
  47. {draftwright-0.4.9 → draftwright-0.4.11}/tests/refactor_golden/scattered_plate.json +9 -0
  48. {draftwright-0.4.9 → draftwright-0.4.11}/tests/refactor_golden/side_drilled.json +4 -0
  49. {draftwright-0.4.9 → draftwright-0.4.11}/tests/refactor_golden/turned_stepped.json +4 -0
  50. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_add_dimension.py +27 -1
  51. draftwright-0.4.11/tests/test_adr0018_arrangement_gate.py +386 -0
  52. draftwright-0.4.11/tests/test_adr0018_view_routing.py +115 -0
  53. draftwright-0.4.11/tests/test_adr0018_view_selection.py +566 -0
  54. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_audit_differential.py +7 -6
  55. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_compiled_plan_boundary.py +7 -7
  56. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_drawing_encapsulation.py +5 -0
  57. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_e2e_standards.py +37 -19
  58. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_external_recognition_boundary.py +5 -11
  59. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_import_boundaries.py +13 -1
  60. draftwright-0.4.11/tests/test_issue_1116_ap242_hole_tolerance_lowering.py +395 -0
  61. draftwright-0.4.11/tests/test_issue_1130_view_planning_evidence.py +211 -0
  62. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_issue_1143_hole_completeness.py +5 -2
  63. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_issue_1146_scale_completeness.py +74 -4
  64. draftwright-0.4.11/tests/test_issue_1155_grm04_sheet_use.py +44 -0
  65. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_issue_1187_unroutable_leaders.py +38 -1
  66. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_issue_1188_per_view_assignment.py +19 -11
  67. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_issue_1202_observed_drawing_consumer.py +10 -6
  68. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_issue_1217_the_facility_is_shared.py +3 -35
  69. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_issue_1219_location_claims_its_own_axis.py +7 -10
  70. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_issue_1240_iso_above_strip_visibility.py +14 -6
  71. draftwright-0.4.11/tests/test_issue_1254_turned_chamfers.py +159 -0
  72. draftwright-0.4.11/tests/test_issue_1260_view_constraints.py +462 -0
  73. draftwright-0.4.11/tests/test_issue_1276_turned_leader_targets.py +332 -0
  74. draftwright-0.4.11/tests/test_issue_1281_turned_fillets.py +113 -0
  75. draftwright-0.4.11/tests/test_issue_443_grm03_axial_coverage.py +59 -0
  76. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_issue_798_silhouette_lint.py +10 -6
  77. draftwright-0.4.11/tests/test_issue_924_zero_length_shoulders.py +118 -0
  78. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_label_provenance.py +1 -1
  79. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_make_drawing.py +106 -31
  80. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_note_verb.py +12 -0
  81. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_part_model.py +15 -23
  82. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_pitch_dim_footprint.py +36 -2
  83. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_pmi.py +76 -32
  84. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_recogniser_adoption.py +3 -7
  85. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_recogniser_capabilities.py +93 -23
  86. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_recognition_manifest.py +18 -21
  87. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_recognition_result.py +9 -12
  88. draftwright-0.4.11/tests/test_refactor_golden.py +432 -0
  89. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_sheet_emit.py +8 -1
  90. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_sheet_gdt.py +2 -1
  91. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_sheet_identity_invariant.py +61 -7
  92. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_sheet_notes.py +32 -2
  93. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_sheet_tables.py +28 -0
  94. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_solve_trace.py +31 -0
  95. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_strip_layout.py +28 -10
  96. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_tolerances.py +34 -0
  97. draftwright-0.4.11/tests/test_view_plan.py +572 -0
  98. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_workflows.py +39 -0
  99. draftwright-0.4.9/docs/reference/sheet.md +0 -35
  100. draftwright-0.4.9/tests/test_cross_repository_delivery_protocol.py +0 -27
  101. draftwright-0.4.9/tests/test_refactor_golden.py +0 -261
  102. draftwright-0.4.9/tests/test_two_repository_workflow.py +0 -281
  103. {draftwright-0.4.9 → draftwright-0.4.11}/.gitignore +0 -0
  104. {draftwright-0.4.9 → draftwright-0.4.11}/LICENSE +0 -0
  105. {draftwright-0.4.9 → draftwright-0.4.11}/README.md +0 -0
  106. {draftwright-0.4.9 → draftwright-0.4.11}/docs/research/1062-repeating-radial-profile-evidence.md +0 -0
  107. {draftwright-0.4.9 → draftwright-0.4.11}/skills/SKILL.md +0 -0
  108. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/_pmi_part21.py +0 -0
  109. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/_warnings.py +0 -0
  110. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/annotate.py +0 -0
  111. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/annotations/__init__.py +0 -0
  112. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/annotations/balloons.py +0 -0
  113. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/annotations/gears.py +0 -0
  114. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/annotations/leaders.py +0 -0
  115. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/audit.py +0 -0
  116. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/cli.py +0 -0
  117. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/evaluation/__init__.py +0 -0
  118. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/evaluation/step_analysis.py +0 -0
  119. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/export.py +0 -0
  120. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/fits.py +0 -0
  121. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/fonts/IBMPlexMono-Regular.ttf +0 -0
  122. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/fonts/IBMPlexSansCondensed-Regular.ttf +0 -0
  123. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/fonts/LICENSE-IBMPlexMono-OFL.txt +0 -0
  124. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/fonts/LICENSE-IBMPlexSansCondensed-OFL.txt +0 -0
  125. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/fonts/__init__.py +0 -0
  126. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/intents.py +0 -0
  127. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/layout.py +0 -0
  128. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/linting/__init__.py +0 -0
  129. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/linting/channel_coverage.py +0 -0
  130. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/linting/evidence.py +0 -0
  131. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/linting/flat_coverage.py +0 -0
  132. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/linting/gear_coverage.py +0 -0
  133. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/linting/issues.py +0 -0
  134. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/linting/polygonal_stock_coverage.py +0 -0
  135. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/linting/profiled_bore_coverage.py +0 -0
  136. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/linting/slot_coverage.py +0 -0
  137. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/linting/structural.py +0 -0
  138. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/linting/suggest.py +0 -0
  139. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/make_drawing.py +0 -0
  140. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/model/callout.py +0 -0
  141. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/pmi.py +0 -0
  142. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/projection.py +0 -0
  143. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/py.typed +0 -0
  144. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/recognition/__init__.py +0 -0
  145. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/recognition_cache.py +0 -0
  146. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/registry.py +0 -0
  147. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/repair.py +0 -0
  148. {draftwright-0.4.9 → draftwright-0.4.11}/src/draftwright/score.py +0 -0
  149. {draftwright-0.4.9 → draftwright-0.4.11}/tests/_kernel.py +0 -0
  150. {draftwright-0.4.9 → draftwright-0.4.11}/tests/_layout_sig.py +0 -0
  151. {draftwright-0.4.9 → draftwright-0.4.11}/tests/fixtures/evaluation/README.md +0 -0
  152. {draftwright-0.4.9 → draftwright-0.4.11}/tests/fixtures/evaluation/ambiguous-open-semicircle.step +0 -0
  153. {draftwright-0.4.9 → draftwright-0.4.11}/tests/fixtures/evaluation/blind-hole.step +0 -0
  154. {draftwright-0.4.9 → draftwright-0.4.11}/tests/fixtures/evaluation/corpus-v1.json +0 -0
  155. {draftwright-0.4.9 → draftwright-0.4.11}/tests/fixtures/evaluation/plain-block.step +0 -0
  156. {draftwright-0.4.9 → draftwright-0.4.11}/tests/fixtures/evaluation/topology-a.step +0 -0
  157. {draftwright-0.4.9 → draftwright-0.4.11}/tests/fixtures/evaluation/topology-b.step +0 -0
  158. {draftwright-0.4.9 → draftwright-0.4.11}/tests/fixtures/grm03_thumbwheel_drive_screw.step +0 -0
  159. {draftwright-0.4.9 → draftwright-0.4.11}/tests/fixtures/if_step_flat_across_cylinder.step +0 -0
  160. {draftwright-0.4.9 → draftwright-0.4.11}/tests/fixtures/issue_1058_wheel_rh.step +0 -0
  161. {draftwright-0.4.9 → draftwright-0.4.11}/tests/fixtures/issue_909_basic_part_design_017_body.step +0 -0
  162. {draftwright-0.4.9 → draftwright-0.4.11}/tests/fixtures/issue_915_case_study_2.step +0 -0
  163. {draftwright-0.4.9 → draftwright-0.4.11}/tests/fixtures/nist_ctc_01_asme1_ap203.stp +0 -0
  164. {draftwright-0.4.9 → draftwright-0.4.11}/tests/fixtures/nist_ctc_01_asme1_ap242.stp +0 -0
  165. {draftwright-0.4.9 → draftwright-0.4.11}/tests/fixtures/nist_ctc_02_asme1_ap203.stp +0 -0
  166. {draftwright-0.4.9 → draftwright-0.4.11}/tests/fixtures/nist_ctc_02_asme1_ap242.stp +0 -0
  167. {draftwright-0.4.9 → draftwright-0.4.11}/tests/fixtures/nist_ctc_03_asme1_ap203.stp +0 -0
  168. {draftwright-0.4.9 → draftwright-0.4.11}/tests/fixtures/nist_ctc_03_asme1_ap242.stp +0 -0
  169. {draftwright-0.4.9 → draftwright-0.4.11}/tests/fixtures/nist_ctc_04_asme1_ap203.stp +0 -0
  170. {draftwright-0.4.9 → draftwright-0.4.11}/tests/fixtures/nist_ctc_04_asme1_ap242.stp +0 -0
  171. {draftwright-0.4.9 → draftwright-0.4.11}/tests/fixtures/nist_ctc_05_asme1_ap203.stp +0 -0
  172. {draftwright-0.4.9 → draftwright-0.4.11}/tests/fixtures/nist_ctc_05_asme1_ap242.stp +0 -0
  173. {draftwright-0.4.9 → draftwright-0.4.11}/tests/fixtures/tuner_jig_blind_obround_pockets.step +0 -0
  174. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_agents_guide.py +0 -0
  175. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_api_docs.py +0 -0
  176. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_architecture_docs.py +0 -0
  177. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_balloon_ring_standoff.py +0 -0
  178. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_carve_free_position_callers.py +0 -0
  179. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_counting_calls.py +0 -0
  180. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_declare.py +0 -0
  181. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_declared_recognition_gate.py +0 -0
  182. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_dense_sheet_canary.py +0 -0
  183. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_deprecation_dates.py +0 -0
  184. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_detect_once.py +0 -0
  185. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_detect_registry.py +0 -0
  186. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_dimension_role_vocabulary.py +0 -0
  187. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_e2e_slice.py +0 -0
  188. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_export_dxf_curves.py +0 -0
  189. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_export_dxf_zoom.py +0 -0
  190. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_fillets_adjacency.py +0 -0
  191. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_fits.py +0 -0
  192. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_flat_completeness.py +0 -0
  193. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_flat_stock_identity.py +0 -0
  194. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_flat_stock_opposition.py +0 -0
  195. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_gdt_placement.py +0 -0
  196. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_grid_lattice_convention.py +0 -0
  197. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_iso_nts_caption_placement.py +0 -0
  198. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_issue_1000_envelope_reuse.py +0 -0
  199. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_issue_1058_wheel_profile.py +0 -0
  200. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_issue_1073_cross_body_patterns.py +0 -0
  201. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_issue_1082_polygonal_stock.py +0 -0
  202. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_issue_1086_declared_gears.py +0 -0
  203. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_issue_1142_hole_leader_labels.py +0 -0
  204. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_issue_1144_transactional_hole_table.py +0 -0
  205. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_issue_1147_legibility_pair_aggregation.py +0 -0
  206. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_issue_1153_contradictory_dimensions.py +0 -0
  207. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_issue_1154_one_owner_per_measurement.py +0 -0
  208. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_issue_1166_cross_pass_feature_leaders.py +0 -0
  209. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_issue_1176_quality_fidelity.py +0 -0
  210. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_issue_1177_angular_dimensions.py +0 -0
  211. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_issue_1190_section_decision.py +0 -0
  212. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_issue_1196_deterministic_view_names.py +0 -0
  213. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_issue_1204_multiscale_view_issues.py +0 -0
  214. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_issue_1215_envelope_tolerance.py +0 -0
  215. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_issue_1215_no_approved_tolerance_is_dropped.py +0 -0
  216. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_issue_1216_callout_reservation_measures_ink.py +0 -0
  217. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_issue_1216_pairwise_across_scale_groups.py +0 -0
  218. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_issue_1217_claimed_representations.py +0 -0
  219. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_issue_1229_ledger_member_keying.py +0 -0
  220. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_issue_367_leader_ink.py +0 -0
  221. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_issue_676_polygonal_boss.py +0 -0
  222. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_issue_676_polygonal_boss_declare.py +0 -0
  223. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_issue_740_leader_assignment.py +0 -0
  224. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_issue_798_floor_cardinality.py +0 -0
  225. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_issue_798_material_field.py +0 -0
  226. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_issue_885_prismatic_coverage.py +0 -0
  227. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_issue_909_sloped_profile.py +0 -0
  228. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_issue_915_dense_case.py +0 -0
  229. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_issue_916_pocket_leader.py +0 -0
  230. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_issue_917_open_channel.py +0 -0
  231. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_issue_955_height_contingency.py +0 -0
  232. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_issue_958_cross_solid_recognition.py +0 -0
  233. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_layout.py +0 -0
  234. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_layout_cleanliness.py +0 -0
  235. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_layout_hypothesis.py +0 -0
  236. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_layout_property.py +0 -0
  237. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_leader_footprint.py +0 -0
  238. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_lint_box_cache.py +0 -0
  239. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_lint_reconciliation.py +0 -0
  240. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_lint_structural.py +0 -0
  241. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_linting.py +0 -0
  242. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_location_vocabulary.py +0 -0
  243. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_object_aspects.py +0 -0
  244. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_occupancy_boxes.py +0 -0
  245. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_parameter_id.py +0 -0
  246. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_pmi_datum_lowering.py +0 -0
  247. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_pmi_gtol_lowering.py +0 -0
  248. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_pmi_part21.py +0 -0
  249. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_pocket_pattern.py +0 -0
  250. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_pocket_pattern_recognition.py +0 -0
  251. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_principal_profile_classifier.py +0 -0
  252. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_private_test_attr_reads.py +0 -0
  253. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_private_test_imports.py +0 -0
  254. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_quality_components.py +0 -0
  255. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_recogniser_contract.py +0 -0
  256. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_recognition.py +0 -0
  257. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_registry.py +0 -0
  258. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_render_seam.py +0 -0
  259. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_repack_geometry_seam.py +0 -0
  260. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_score.py +0 -0
  261. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_script_detail_parity.py +0 -0
  262. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_shared_box_memo.py +0 -0
  263. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_sheet_of.py +0 -0
  264. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_sheet_section.py +0 -0
  265. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_slanted_blind_step.py +0 -0
  266. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_slot_completeness.py +0 -0
  267. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_slot_pattern.py +0 -0
  268. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_slot_pattern_recognition.py +0 -0
  269. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_soft_deprecation.py +0 -0
  270. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_step_analysis_evaluation.py +0 -0
  271. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_suppression_ledger.py +0 -0
  272. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_suppression_marks.py +0 -0
  273. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_turned_steps.py +0 -0
  274. {draftwright-0.4.9 → draftwright-0.4.11}/tests/test_witness_label_reconciliation.py +0 -0
@@ -1,5 +1,120 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ### Changed
6
+
7
+ - Recogniser integration now uses only the exact `b123d-recognisers` dependency in
8
+ `pyproject.toml` and `uv.lock`. The separate release-evidence record, dependency-bump workflow,
9
+ candidate-checkout harness, and release-time capability-version rewrites have been removed;
10
+ ordinary compatibility tests validate the installed pinned package.
11
+ - Automatic composition now measures the next larger preferred scale when a conservative
12
+ crowded-detail reservation leaves a sparse sheet. A candidate wins only on the same paper
13
+ size, without the recovery detail, structural errors, or required-placement loss. GRM-04 now
14
+ uses A4 at 5:1 instead of 2:1, re-homes its shared side-hole Y location into the plan-view
15
+ corridor, and retains both the Ø2.4 diameter and location requirements (#1155).
16
+ - Closed the remaining dense-sheet leader cases as deliberate Policy-B behavior: exhaustive
17
+ route sweeps prove there is no route clear of both material and committed sheet ink, so the
18
+ required callout remains with an explicit crossing diagnostic instead of being dropped. A
19
+ fast synthetic release gate complements the retained exhaustive corpus characterization
20
+ (#1187).
21
+
22
+ ### Fixed
23
+
24
+ - Notes and generic table cells now render U+2300 `⌀` with the established supported drafting
25
+ glyph instead of IBM Plex Mono's missing-glyph square. The substitution happens at the one
26
+ shared measurement/render seam, so source rows remain unchanged and table sizing stays exact
27
+ (#1275).
28
+ - Automatic turned-part composition now gives true shoulder coverage priority over optional
29
+ pictorial content. GRM-03 replans to 5:1 and renders its 0.5, 2, 3, and 18 mm axial lengths;
30
+ `axial_length_missing` disappears because the dimensions are present, not because the
31
+ diagnostic was suppressed (#443).
32
+
33
+ ## v0.4.10 — 2026-08-22
34
+
35
+ **A view-set-aware planning and turned-edge-treatment patch.** Authored view constraints now
36
+ compile into an explicit resolved plan, automatic drawings fail closed when required content is
37
+ lost, AP242 hole tolerances reach their semantic feature owners, and the exact
38
+ `b123d-recognisers` 0.3.0 release supplies turned chamfer and fillet evidence through truthful
39
+ profile-view callouts.
40
+
41
+ ### Added
42
+
43
+ - `Sheet` can now author a complete or augmenting semantic view set with `authored_views()`,
44
+ `auto_views()`, `view()`, `section_view()`, `detail_view()`, and their `add_*` forms. Fluent
45
+ view handles declare relationships, alignment, scale, and whole-view pins without exposing
46
+ annotation coordinates; unsupported or infeasible constraints fail at their declaration
47
+ source instead of being silently relaxed. `sheet.view_constraints` exposes the immutable
48
+ request and `drawing.view_plan` the distinct resolved result (#1260).
49
+ - Drawings now retain their resolved view topology, per-view requirement coverage, and
50
+ arrangement decision as inspectable values. The engine can build and semantically compare
51
+ alternative arrangements and requested view sets while keeping projection and page placement
52
+ derived from the same plan (#1130).
53
+
54
+ ### Changed
55
+
56
+ - Dimension planning is now aware of the selected principal view set. Requirements supported by
57
+ another planned view are re-homed before projection; an unsupported set raises one aggregated
58
+ `ViewPlanIncomplete` naming every unmet requirement rather than failing late or producing a
59
+ plausible-looking drawing with missing dimensions (#1259).
60
+ - Three sparse corpus drawings now use a `stacked-iso` arrangement and fit A4 instead of A3 at
61
+ the same scale, with identical annotations, labels, and build issues. An alternative may only
62
+ compact the scale already selected by the default arrangement and must survive a real compile
63
+ without semantic loss; otherwise the default is retained (#1130).
64
+ - Updated the exact `b123d-recognisers` production lock from 0.2.9 to 0.3.0.
65
+ The immutable PyPI wheel/sdist hashes and capability-manifest digest `6645def94cbabaca7b9676fdb029b02e467266d1f020ea536e903b7d86b52685` are
66
+ recorded in `.github/recogniser-release.json`; focused compatibility evidence and the
67
+ normal consumer gates run on the generated PR.
68
+ - Accepted the additive Chamfer and Fillet schema-2 recogniser contract alongside schema 1 for
69
+ the `b123d-recognisers` 0.3.0 cutover; all other record schemas remain fail-closed at version 1
70
+ (#1276).
71
+
72
+ - Updated the exact `b123d-recognisers` production lock from 0.2.6 to 0.2.9.
73
+ The immutable PyPI wheel/sdist hashes and capability-manifest digest `805876905d548fd0ef1301e2cf24427a86d34bc2e592b8d2d0cb43db94e1d3bd` are
74
+ recorded in `.github/recogniser-release.json`; focused compatibility evidence and the
75
+ normal consumer gates run on the generated PR. The aggregate now inventories chamfers and
76
+ fillets on turned parts, and the fixed coaxial-post slot regression retires its downstream
77
+ strict xfail.
78
+
79
+ ### Fixed
80
+
81
+ - Automatic builds now apply the same required-outcome gate as explicit-scale builds. A settled
82
+ drawing that lost required placement outcomes records `plan_incomplete`, reports an
83
+ `incomplete` scale decision, preserves measurement/hole/source provenance, and fails lint
84
+ rather than presenting a diagnostic drawing as complete (#1250).
85
+
86
+ - Supported AP242 diameter tolerances now correlate with their canonical hole or pattern owner at
87
+ the completed IR boundary and round-trip through emitted `Sheet` declarations with source
88
+ provenance. Unmatched, ambiguous, conflicting, partial-pattern, and unsupported requirements
89
+ fail closed to provenance-preserving measured dimensions rather than being duplicated or lost
90
+ (#1116).
91
+
92
+ - A declared step shoulder coincident with its datum is now pruned before rendering instead of
93
+ sending identical endpoints to the border-dimension helper and raising `ValueError`. Valid
94
+ siblings remain, while the omission is recorded in `Drawing.suppressions()` and reported as
95
+ `step_position_coincident_with_datum` (#924).
96
+
97
+ - Turned chamfer and fillet leaders now read in a shaft profile view and land on their physical
98
+ edge-treatment sites instead of collapsing axial stations in the end view. Cylindrical
99
+ feature notes, knurl callouts, and surface finishes likewise derive a model-space surface
100
+ target rather than pointing at the shaft axis; all continue through the shared placement
101
+ solves, with X/Y/Z parity and declared Sheet round-trip (#1276).
102
+
103
+ - Toroidal fillets returned for turned parts by the shared recognition inventory now lower into
104
+ the existing one-feature-per-round IR and render through the common leader solve. Equal radii
105
+ share one `n× R` callout carrying every physical fillet's measurement provenance, and emitted
106
+ Sheet programs preserve the full inventory without rescanning geometry (#1281).
107
+
108
+ - Turned chamfers returned by the shared recognition inventory now lower into the same
109
+ one-feature-per-edge-treatment IR as prismatic chamfers. Equal specifications render as one
110
+ `n×` callout carrying every member's measurement provenance; different chamfer forms do not
111
+ collapse merely because their first legs match, and distinct tolerances split into truthful
112
+ requirements instead of being lost or applied to untoleranced siblings (#1254).
113
+
114
+ - Recogniser dependency automation now starts a fresh `Unreleased` section when the previous
115
+ Draftwright release has finalized the changelog, so a published recogniser can enter through
116
+ the documented immutable update workflow instead of failing before it opens a PR (#1279).
117
+
3
118
  ## v0.4.9 — 2026-08-21
4
119
 
5
120
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: draftwright
3
- Version: 0.4.9
3
+ Version: 0.4.11
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,7 +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.6
687
+ Requires-Dist: b123d-recognisers==0.3.1
688
688
  Requires-Dist: build123d-drafting-helpers>=0.14.1
689
689
  Requires-Dist: build123d<0.11,>=0.9.0; python_full_version < '3.13'
690
690
  Requires-Dist: build123d<0.12,>=0.11; python_full_version >= '3.13'
@@ -34,7 +34,7 @@ architecture** table; open retired or superseded records only for design history
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_issue_1215_no_approved_tolerance_is_dropped.py`, `test_label_provenance.py`, `test_sheet_emit.py`, `test_add_dimension.py` |
36
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
- | [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. Supersedes ADR 0004's fixed-topology assumption while retaining compose-then-pack. | Accepted 2026-08-16 (nothing implemented yet); tracked by #1130 | Required guards are listed in the ADR |
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. Supersedes ADR 0004's fixed-topology assumption while retaining compose-then-pack. | Accepted 2026-08-16; partially implemented (`ViewSpec`, `ResolvedViewPlan`, `ViewCoverage`, arrangement choice and requirement gate); tracked by #1130/#1259-#1262 | Required guards are listed in the ADR |
38
38
  | [0019](0019-display-complete-labels-and-dimension-outcomes.md) | Display-complete labels and a dimension-outcome ledger | The compiled plan carries the full label text (tolerance and collapse wording included) so renderers render and never compose; dimension outcomes reconcile at one seam on both build routes; ladder rungs get per-mark identity. Finishes the ADR 0016 Amdt 1 boundary; supersedes Amdt 6's enforcement mechanism. | Proposed | `test_issue_1215_no_approved_tolerance_is_dropped.py` reduces to plan-equality when implemented |
39
39
 
40
40
  ## Historical records
@@ -26,7 +26,9 @@ Validation fails closed and names the family and boundary whenever possible:
26
26
  CI instead — adoption is still required, and is enforced where the decision is made. Provide the
27
27
  downstream behavior or an explicit non-supported state as before.
28
28
  - **record schema mismatch** — a consumed record schema changed. Review the record fields and
29
- compatibility notes, update the relevant adapter and tests, then pin the accepted schema version.
29
+ compatibility notes, update the relevant adapter and tests, then explicitly list the accepted
30
+ schema version. A bounded dual-readable list may name the current and next additive schemas
31
+ during a provider-first release; it is never an open-ended range.
30
32
  - **stale implementation** — a declared adapter, `Sheet` method, emitter, renderer, or completeness
31
33
  function no longer resolves. Repair the implementation reference and its independent behavior
32
34
  evidence; do not merely rename the string.
@@ -47,12 +49,17 @@ Validation fails closed and names the family and boundary whenever possible:
47
49
 
48
50
  ## Adding or changing a recogniser
49
51
 
50
- Land the package record, independent geometry evidence, manifest update, and compatible package
51
- release first. Then update Draftwright against that immutable release. For each applicable stage,
52
- add the adapter, declaration, emitted-Sheet round trip, drawing behavior, and completeness evidence;
53
- for an inapplicable or deferred stage, state why and link its tracking issue. The contract test
54
- derives package record outputs, converter registries, live `Sheet` methods, and emitter branches
55
- independently, so copying a new name into the declaration alone cannot make CI pass.
52
+ For an additive field that increments an existing record schema, update the relevant Draftwright
53
+ adapter and declaration when advancing the exact `b123d-recognisers` dependency pin. Tests must
54
+ prove the installed schema is accepted while later schemas still fail. The package version belongs
55
+ only in `pyproject.toml` and `uv.lock`; the capability declaration derives runtime identities from
56
+ installed package metadata.
57
+
58
+ For each applicable stage, add the adapter, declaration, emitted-Sheet round trip, drawing behavior,
59
+ and completeness evidence; for an inapplicable or deferred stage, state why and link its tracking
60
+ issue. The contract test derives package record outputs, converter registries, live `Sheet` methods,
61
+ and emitter branches independently, so copying a new name into the declaration alone cannot make CI
62
+ pass.
56
63
 
57
64
  `bosses` is the fully consumed reference family. `repeating-radial-profiles` is the opposite
58
65
  reference: it remains geometry-only critique evidence for a separately authored gear declaration,
@@ -0,0 +1,71 @@
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
+ ### Authored views and layout
14
+
15
+ View declarations are semantic constraints. They name projections, relationships and whole-view
16
+ anchors; the layout solve owns the resulting page positions. Authored views must be paired with
17
+ authored dimensions so a deliberately omitted view cannot strand planner-selected annotations:
18
+
19
+ ```python
20
+ s = Sheet(part).authored_dimensions().authored_views()
21
+ front = s.view("front")
22
+ plan = s.view("plan").above(front, gap=4).align_x(front)
23
+ s.view("iso").scale(0.75)
24
+
25
+ section = s.section_view("A", through=hole)
26
+ detail = s.detail_view("B", around=hole).scale(2)
27
+ dwg = s.build()
28
+ ```
29
+
30
+ `view(...)`, `section_view(...)` and `detail_view(...)` define complete authored sets;
31
+ omission suppresses a view. `add_view(...)`, `add_section_view(...)` and
32
+ `add_detail_view(...)` augment an explicitly selected `auto_views()` source. `row(...)` and
33
+ `column(...)` are shorthand for whole-view relations. A principal-view handle's `pin((x, y))`
34
+ anchors its projection origin in page millimetres; it never positions an annotation.
35
+
36
+ Principal orthographic views share the sheet scale and reject `.scale(...)`. Detail and
37
+ isometric handles may carry an independent positive factor. Infeasible relations, scales and
38
+ pins raise with their declaration source; unsupported pins on derived or pictorial views are
39
+ refused at declaration and constraints are never silently relaxed. The immutable authored
40
+ snapshot is available as `sheet.view_constraints`, while `drawing.view_plan` is the distinct
41
+ resolved result.
42
+
43
+ ## View handle
44
+
45
+ ::: draftwright.sheet._View
46
+ options:
47
+ filters: public
48
+
49
+ ## Hole handle
50
+
51
+ ::: draftwright.sheet._Hole
52
+ options:
53
+ filters: public
54
+
55
+ ## Diameter and step handle
56
+
57
+ ::: draftwright.sheet._Dim
58
+ options:
59
+ filters: public
60
+
61
+ ## Multi-parameter handle
62
+
63
+ ::: draftwright.sheet._Params
64
+ options:
65
+ filters: public
66
+
67
+ ## GD&T control builder
68
+
69
+ ::: draftwright.sheet._Control
70
+ options:
71
+ filters: public
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "draftwright"
7
- version = "0.4.9"
7
+ version = "0.4.11"
8
8
  description = "Automated technical-drawing generation for build123d"
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
@@ -18,7 +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.6",
21
+ "b123d-recognisers==0.3.1",
22
22
  "numpy>=1.24",
23
23
  # PNG raster export (SVG→PDF→PNG). Both permissively licensed (Pillow HPND; pypdfium2
24
24
  # Apache-2.0 wrapping Google PDFium BSD-3) and ship pre-built wheels with NO native system
@@ -36,6 +36,9 @@ _LAZY = {
36
36
  "extract_pmi": "draftwright.pmi",
37
37
  "extract_pmi_report": "draftwright.pmi",
38
38
  "choose_scale": "draftwright.compose",
39
+ "ViewPlanIncomplete": "draftwright.view_plan",
40
+ "ViewConstraints": "draftwright.view_plan",
41
+ "ViewSpec": "draftwright.view_plan",
39
42
  # A warning category users are told to filter must be importable without reaching into a
40
43
  # private module (#1043 review) — and without paying for the CAD kernel. It lives in the
41
44
  # dependency-free `_warnings` leaf for that second reason: defined in `_core` it cost ~6 s
@@ -91,6 +94,7 @@ if TYPE_CHECKING: # static analysers / IDEs — no runtime import, no kernel co
91
94
  extract_pmi_report,
92
95
  )
93
96
  from draftwright.sheet import Sheet
97
+ from draftwright.view_plan import ViewConstraints, ViewPlanIncomplete, ViewSpec
94
98
 
95
99
 
96
100
  def __dir__():
@@ -110,6 +114,9 @@ __all__ = [
110
114
  "Sheet",
111
115
  "ScaleIncompatibilityError",
112
116
  "ScaleCompletenessWarning",
117
+ "ViewPlanIncomplete",
118
+ "ViewConstraints",
119
+ "ViewSpec",
113
120
  "build_drawing",
114
121
  "choose_scale",
115
122
  "extract_pmi",
@@ -42,7 +42,14 @@ from build123d_drafting.helpers import (
42
42
  # now live in the leaf `draftwright._geometry` so the IR waist (`model/`) can use them
43
43
  # without importing this stage-level grab-bag (ADR 0008; #584 WP2). Re-exported here for
44
44
  # the above-`_core` consumers (annotations/sheet/drawing/linting) that already import them.
45
- from draftwright._geometry import _END_ON, HoleRef, _axis_letter, _fmt, _xyz # noqa: F401
45
+ from draftwright._geometry import ( # noqa: F401
46
+ _EDGE_ON,
47
+ _END_ON,
48
+ HoleRef,
49
+ _axis_letter,
50
+ _fmt,
51
+ _xyz,
52
+ )
46
53
  from draftwright.fits import FitClass
47
54
  from draftwright.fonts import PLEX_MONO, PLEX_SANS_CONDENSED
48
55
  from draftwright.layout import _greedy_strip_1d, _solve_strip_1d
@@ -50,6 +57,35 @@ from draftwright.layout import _greedy_strip_1d, _solve_strip_1d
50
57
  _log = logging.getLogger(__name__)
51
58
 
52
59
 
60
+ def _decode_hole_location_fact(fact):
61
+ """Decode a current or legacy ``covers_hole_locations`` fact.
62
+
63
+ Current facts carry ``(feature, parameter, point)``. Legacy/external facts
64
+ carry ``(measurement, point)`` and derive ownership from the measurement.
65
+ Invalid or incomplete riders return ``None`` so consumers can retain their
66
+ geometric fallback instead of treating malformed metadata as authoritative.
67
+ """
68
+ try:
69
+ size = len(fact)
70
+ except TypeError:
71
+ return None
72
+ if size == 3:
73
+ feature, parameter, point = fact
74
+ elif size == 2:
75
+ measurement, point = fact
76
+ feature = getattr(measurement, "feature", None)
77
+ parameter = getattr(measurement, "parameter", None)
78
+ else:
79
+ return None
80
+ if feature is None or not isinstance(parameter, str):
81
+ return None
82
+ try:
83
+ HoleRef.of(point)
84
+ except (AttributeError, TypeError, ValueError):
85
+ return None
86
+ return feature, parameter, point
87
+
88
+
53
89
  def place_annotation(registry, items, obj, name=None, view=None, feature=None, measurement=None):
54
90
  """The annotation-placement primitive (#817): register *obj* under *name* — replacing any
55
91
  prior object of that name (dropped from the render list *items*) so a name maps to one
@@ -161,6 +197,19 @@ def _wrap_rows(header, data, ncols):
161
197
  return wide
162
198
 
163
199
 
200
+ def _font_safe_text(value) -> str:
201
+ """Return the font-safe spelling used to measure and draw user text.
202
+
203
+ IBM Plex Mono does not contain U+2300 DIAMETER SIGN, so FreeType substitutes
204
+ its missing-glyph box. The drafting surface already uses U+00F8 LATIN SMALL
205
+ LETTER O WITH STROKE for diameter labels; use that established glyph at the
206
+ shared render seams as well. Keeping the substitution here makes free notes,
207
+ declared notes, title blocks, BOMs, revision tables, and hole tables measure
208
+ exactly the text they render while preserving the caller's semantic source data.
209
+ """
210
+ return str(value).replace("⌀", "ø")
211
+
212
+
164
213
  def _table_metrics(rows, font_size, pad_around_text, block_cols=None):
165
214
  """The sizing model of a data table: per-column left/right edges (page-mm,
166
215
  block gaps inserted), total width/height, row height and effective block
@@ -179,7 +228,11 @@ def _table_metrics(rows, font_size, pad_around_text, block_cols=None):
179
228
  bc = block_cols if (block_cols and ncol % block_cols == 0 and block_cols < ncol) else ncol
180
229
  block_gap = 3 * pad # whitespace between side-by-side blocks
181
230
  col_w = [
182
- max(max(_text_width(str(r[c]), fs) for r in rows) + 2 * pad, fs * 2.5) for c in range(ncol)
231
+ max(
232
+ max(_text_width(_font_safe_text(r[c]), fs) for r in rows) + 2 * pad,
233
+ fs * 2.5,
234
+ )
235
+ for c in range(ncol)
183
236
  ]
184
237
  # Per-column left/right edges, inserting block_gap before each new block.
185
238
  lefts, rights, cursor = [], [], 0.0
@@ -225,11 +278,12 @@ def _build_table(rows, draft, block_cols=None):
225
278
  for ri, row in enumerate(rows): # rows[0] (header) sits at the top
226
279
  cy = total_h - (ri + 0.5) * row_h
227
280
  for ci, cell in enumerate(row):
228
- if not str(cell):
281
+ display = _font_safe_text(cell)
282
+ if not display:
229
283
  continue
230
284
  cx = (lefts[ci] + rights[ci]) / 2
231
285
  text = Text(
232
- txt=str(cell),
286
+ txt=display,
233
287
  font_size=fs,
234
288
  font_path=PLEX_MONO,
235
289
  align=(Align.CENTER, Align.CENTER),
@@ -804,8 +858,8 @@ class DetailRequest:
804
858
  footprint that depends on the chosen scale (the prismatic
805
859
  ladder reserves one rung per *legible-at-that-scale* step, so it
806
860
  shrinks with the scale during the fit). Overrides ``pad_top``.
807
- source_view: orthographic direction to preserve in the detail (``"front"``
808
- or ``"side"``). The latter is used for Y-turned axial profiles.
861
+ source_view: orthographic direction to preserve in the detail (``"front"``,
862
+ ``"plan"`` or ``"side"``).
809
863
  crop_lo/crop_hi: optional crop bounds along ``axis`` when the projected
810
864
  detail must include context outside the marked feature band.
811
865
  Prismatic absolute-height details use this to retain their
@@ -823,13 +877,23 @@ class DetailRequest:
823
877
  redraw: Callable[..., int]
824
878
  pad_top: float = 0.0
825
879
  pads: Callable[[float], tuple[float, float]] | None = None
826
- source_view: Literal["front", "side"] = "front"
880
+ source_view: Literal["front", "plan", "side"] = "front"
827
881
  crop_lo: float | None = None
828
882
  crop_hi: float | None = None
829
883
  cross_axis: Literal["x", "y", "z"] | None = None
830
884
  cross_lo: float | None = None
831
885
  cross_hi: float | None = None
832
886
  kind: str = "detail"
887
+ #: Authored detail identity/label. ``None`` lets the automatic resolver assign A-H.
888
+ view_name: str | None = None
889
+ label: str | None = None
890
+ #: Exact authored multiple of sheet scale; automatic requests keep ``None`` and use the
891
+ #: legibility-derived preferred series.
892
+ scale_factor: float | None = None
893
+ #: An authored orientation crop is defining geometry even when it carries no redrawn
894
+ #: dimension. Automatic recovery details retain the historical "no dimension, no view" gate.
895
+ keep_without_annotations: bool = False
896
+ source: object | None = None
833
897
 
834
898
 
835
899
  @dataclass(frozen=True)
@@ -1021,7 +1085,7 @@ class Analysis:
1021
1085
  step_zs: list[float]
1022
1086
  layout_strips: StripDepths
1023
1087
  layout_n_steps: int
1024
- layout_section: bool
1088
+ layout_section: int
1025
1089
  layout_table_sizes: tuple[tuple[float, float], ...]
1026
1090
  layout_required_tables: tuple[tuple[tuple[float, float], str], ...]
1027
1091
  sv_right: float
@@ -1090,6 +1154,28 @@ class Analysis:
1090
1154
  # declared a model (ADR 0011) or on a manually-built Analysis — consumers fall
1091
1155
  # back to build_model(a).
1092
1156
  model: object | None = None
1157
+ #: The relational arrangement the sheet was composed under — ADR 0018 §5's fourth
1158
+ #: dimension, decided once by `compose.choose_scale` and carried here so that placement
1159
+ #: and the repack loop compose under the arrangement whose feasibility was actually
1160
+ #: established. They call `_layout_geometry` with MEASURED strip depths where selection
1161
+ #: passed estimates, so a stage that re-derives this instead of reading it can reach a
1162
+ #: different answer for the same sheet and lose dimensions to the mismatch (#1130).
1163
+ #: Defaulted for hand-built `Analysis` objects, which mean the long-standing arrangement.
1164
+ arrangement: str = "columns"
1165
+ #: The principal views this sheet carries, or None for the third-angle three (ADR 0018).
1166
+ #: Decided once alongside scale/page/arrangement and carried, for the same reason: the
1167
+ #: layout must reserve space for exactly the views the builder creates, or dropping one
1168
+ #: costs its annotations without reclaiming its paper.
1169
+ planned_views: tuple[str, ...] | None = None
1170
+ #: Whether the orientation isometric participates in this authored plan. It is separate
1171
+ #: from ``planned_views`` because that tuple is the principal set consumed by the
1172
+ #: requirement compiler.
1173
+ planned_iso: bool = True
1174
+ #: ``None`` lets the orientation iso auto-fit; a number is an authored exact factor.
1175
+ planned_iso_scale: float | None = None
1176
+ #: Immutable ADR 0018 authored input, retained for the resolver/diagnostics without making
1177
+ #: this low-level module depend on the view-planning leaf at runtime.
1178
+ view_constraints: object | None = None
1093
1179
 
1094
1180
  @property
1095
1181
  def pmi(self) -> list:
@@ -1119,15 +1205,15 @@ def _make_title_block(dwg, a: Analysis):
1119
1205
  it, last) and :func:`_title_block_box` (which measures its footprint for GD&T avoidance, #481)
1120
1206
  so the two never drift."""
1121
1207
  tb = TitleBlock(
1122
- a.title,
1123
- a.number,
1208
+ _font_safe_text(a.title),
1209
+ _font_safe_text(a.number),
1124
1210
  scale=format_drawing_scale(a.SCALE),
1125
- general_tolerance=a.tolerance,
1126
- designed_by=_attribution_author(a.drawn_by),
1127
- material=a.material,
1128
- date=a.date,
1129
- revision=a.revision,
1130
- legal_owner=a.company,
1211
+ general_tolerance=_font_safe_text(a.tolerance),
1212
+ designed_by=_font_safe_text(_attribution_author(a.drawn_by)),
1213
+ material=_font_safe_text(a.material),
1214
+ date=_font_safe_text(a.date),
1215
+ revision=_font_safe_text(a.revision),
1216
+ legal_owner=_font_safe_text(a.company),
1131
1217
  width=a.TB_W,
1132
1218
  # Title block renders in condensed sans (the tight ISO 7200 cells), a
1133
1219
  # different face from the monospace dimensions — so it carries its own
@@ -1276,6 +1362,11 @@ def _add_zone_grid(dwg, a: Analysis):
1276
1362
 
1277
1363
  def _iso_bbox(dwg):
1278
1364
  """(min_x, min_y, max_x, max_y) of the placed iso view, hidden lines included."""
1365
+ if "iso" not in dwg.views:
1366
+ # A deliberately omitted orientation view contributes no obstacle. Infinity makes
1367
+ # every existing iso-clearance comparison a no-op without teaching annotation passes
1368
+ # a second page-edge convention.
1369
+ return (math.inf, math.inf, math.inf, math.inf)
1279
1370
  return dwg.view_bounds("iso")
1280
1371
 
1281
1372
 
@@ -15,13 +15,133 @@ import logging
15
15
  import math
16
16
  from dataclasses import dataclass
17
17
 
18
+ from b123d_recognisers import full_cylinders
18
19
  from build123d import Compound
19
20
 
20
21
  _log = logging.getLogger(__name__)
21
22
 
22
- # Axis letter -> the orthographic view a feature on that axis reads end-on in.
23
+ # Axis letter -> the orthographic view a feature on that axis reads end-on in: a z-hole is
24
+ # a circle in plan, an x-channel is a cross-section in side.
25
+ #
26
+ # The ONE owner of that routing. It was duplicated as a bare literal at eight sites across
27
+ # six modules, which is ADR 0018's own sentence about itself — "which views should exist is
28
+ # a decision nothing currently owns" — in miniature: a mapping copied eight times cannot be
29
+ # taught that a view is absent, and the engine's answer to a missing view was a `KeyError`
30
+ # raised inside a centermark pass. Route through this, never re-spell it (#1130).
23
31
  _END_ON = {"x": "side", "y": "front", "z": "plan"}
24
32
 
33
+ # Axis letter -> the orthographic view a planar face with that NORMAL shows as an edge in
34
+ # (prefer front, else side). The companion routing to `_END_ON`, and its second owner: this
35
+ # one was spelled out three times — the groove profile callouts, `declare._EDGE_ON_VIEW` and
36
+ # `planner._PROFILE`, the last of which documented the duplication in a comment rather than
37
+ # resolving it. Same reasoning as above: route through it, never re-spell it (#1130).
38
+ _EDGE_ON = {"x": "front", "y": "side", "z": "front"}
39
+
40
+ # Model axes retained by each orthographic projection. Besides documenting the projection
41
+ # convention once, this lets surface-bound annotations choose a radial direction that the
42
+ # requested view can actually show (#1276).
43
+ _VIEW_AXES = {"plan": ("x", "y"), "front": ("x", "z"), "side": ("y", "z")}
44
+
45
+
46
+ def _radial_axis_in_view(axis: str, view: str) -> str:
47
+ """Return a principal radial axis visible in *view* for a shaft along *axis*.
48
+
49
+ Profile views contain the shaft axis and exactly one radial axis. A caller may also
50
+ explicitly choose the face-on view, where both projected axes are radial; the first is a
51
+ deterministic, equally physical surface direction.
52
+ """
53
+ try:
54
+ return next(candidate for candidate in _VIEW_AXES[view] if candidate != axis)
55
+ except (KeyError, StopIteration) as e:
56
+ raise ValueError(f"no radial axis for shaft axis {axis!r} in view {view!r}") from e
57
+
58
+
59
+ def _canonical_profile_site(site, centre, axis: str, view: str) -> tuple[float, float, float]:
60
+ """Rotate a turned surface *site* about its shaft onto the selected profile plane.
61
+
62
+ Conical and toroidal recognisers may return any circumferential point on the same physical
63
+ edge treatment. Orthographic projection can discard that point's sole radial component
64
+ (notably an X-offset site viewed in the Y-Z side view). Preserve its axial station and
65
+ radial distance while rotating it to the radial axis visible in *view*.
66
+ """
67
+ values = [float(value) for value in site]
68
+ centre_values = [float(value) for value in centre]
69
+ axis_i = "xyz".index(axis)
70
+ radial = [i for i in (0, 1, 2) if i != axis_i]
71
+ visible_i = "xyz".index(_radial_axis_in_view(axis, view))
72
+ if visible_i not in radial:
73
+ raise ValueError(f"view {view!r} is not a profile view of axis {axis!r}")
74
+ hidden_i = next(i for i in radial if i != visible_i)
75
+ visible_delta = values[visible_i] - centre_values[visible_i]
76
+ hidden_delta = values[hidden_i] - centre_values[hidden_i]
77
+ radius = math.hypot(visible_delta, hidden_delta)
78
+ direction = visible_delta if abs(visible_delta) > 1e-12 else hidden_delta
79
+ values[visible_i] = centre_values[visible_i] + math.copysign(radius, direction or 1.0)
80
+ values[hidden_i] = centre_values[hidden_i]
81
+ return (values[0], values[1], values[2])
82
+
83
+
84
+ def _turned_profile_site(site, axis: str, view: str, cylinders) -> tuple[float, float, float]:
85
+ """Return *site* rotated onto *view* about its nearest external shaft axis.
86
+
87
+ A part may contain several parallel shafts, so the part bounding-box centre is not an
88
+ axis. Rank the shared cylinder substrate by the site's distance from each finite cylinder
89
+ patch (radial surface gap plus axial-span gap), then rotate about that cylinder's own
90
+ ``axis_xyz``. The finite span matters for compounds: an unrelated cylinder at a remote
91
+ axial station can coincidentally have the exact radial distance (#1276 review).
92
+ When no matching external cylinder exists, preserve the physical site: inventing an axis
93
+ would be worse than retaining a possibly edge-on circumferential anchor.
94
+ """
95
+ values = (float(site[0]), float(site[1]), float(site[2]))
96
+ axis_i = "xyz".index(axis)
97
+ radial = tuple(i for i in (0, 1, 2) if i != axis_i)
98
+ candidates = []
99
+ # ``analyse_cylinders`` is deliberately a complete face inventory: longitudinal blends
100
+ # and slot caps are cylindrical too. Only its public substrate filter proves which
101
+ # records collectively describe a shaft, including an OD split by a keyway/slot.
102
+ substrates = (full_cylinders(list(group)) for group in cylinders)
103
+ for cylinder in (item for group in substrates for item in group):
104
+ if not cylinder.get("external") or cylinder.get("axis") != axis:
105
+ continue
106
+ centre = tuple(float(value) for value in cylinder["axis_xyz"])
107
+ radial_distance = math.hypot(
108
+ values[radial[0]] - centre[radial[0]],
109
+ values[radial[1]] - centre[radial[1]],
110
+ )
111
+ surface_gap = abs(radial_distance - float(cylinder["diameter"]) / 2)
112
+ direction = tuple(float(value) for value in cylinder["dir_xyz"])
113
+ axial_station = sum(value * component for value, component in zip(values, direction))
114
+ s_lo = float(cylinder["s_lo"])
115
+ s_hi = float(cylinder["s_hi"])
116
+ axial_gap = max(s_lo - axial_station, 0.0, axial_station - s_hi)
117
+ patch_distance = math.hypot(surface_gap, axial_gap)
118
+ # A complete cylinder elsewhere in a compound is not evidence for this feature. If
119
+ # the recogniser's physical point is more than one radius from the finite patch, keep
120
+ # that point rather than rotating it around an invented remote axis. This also keeps
121
+ # a chamfer on a partial OD (for example a D-shaft) safe when ``full_cylinders`` quite
122
+ # correctly declines to call the partial wall a complete shaft substrate.
123
+ radius = float(cylinder["diameter"]) / 2
124
+ if patch_distance > radius:
125
+ continue
126
+ candidates.append(
127
+ (
128
+ (
129
+ patch_distance,
130
+ axial_gap,
131
+ surface_gap,
132
+ radial_distance,
133
+ int(cylinder["solid_idx"]),
134
+ centre[radial[0]],
135
+ centre[radial[1]],
136
+ ),
137
+ centre,
138
+ )
139
+ )
140
+ if not candidates:
141
+ return values
142
+ _score, centre = min(candidates, key=lambda candidate: candidate[0])
143
+ return _canonical_profile_site(values, centre, axis, view)
144
+
25
145
 
26
146
  def _xyz(loc) -> tuple[float, float, float]:
27
147
  """A build123d ``Vector`` (has ``.X/.Y/.Z``) or an ``(x, y, z)`` sequence → an