draftwright 0.4.9__tar.gz → 0.4.10__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 (271) hide show
  1. {draftwright-0.4.9 → draftwright-0.4.10}/CHANGELOG.md +85 -0
  2. {draftwright-0.4.9 → draftwright-0.4.10}/PKG-INFO +2 -2
  3. {draftwright-0.4.9 → draftwright-0.4.10}/docs/adr/README.md +1 -1
  4. {draftwright-0.4.9 → draftwright-0.4.10}/docs/reference/recogniser-capabilities.md +16 -7
  5. draftwright-0.4.10/docs/reference/sheet.md +71 -0
  6. {draftwright-0.4.9 → draftwright-0.4.10}/pyproject.toml +2 -2
  7. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/__init__.py +7 -0
  8. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/_core.py +49 -5
  9. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/_geometry.py +121 -1
  10. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/analysis.py +173 -3
  11. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/annotations/from_model.py +234 -117
  12. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/annotations/holes.py +6 -4
  13. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/annotations/orchestrator.py +140 -5
  14. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/annotations/sections.py +157 -86
  15. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/builder.py +437 -51
  16. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/compose.py +251 -45
  17. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/drawing.py +69 -6
  18. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/linting/hole_coverage.py +2 -2
  19. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/linting/pmi_coverage.py +45 -3
  20. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/linting/quality.py +8 -0
  21. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/model/__init__.py +5 -0
  22. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/model/compiled.py +64 -23
  23. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/model/declare.py +114 -13
  24. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/model/detect.py +55 -19
  25. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/model/ir.py +37 -8
  26. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/model/planner.py +293 -11
  27. draftwright-0.4.10/src/draftwright/model/pmi_lowering.py +276 -0
  28. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/recogniser_contract.py +83 -11
  29. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/sheet.py +558 -27
  30. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/sheet_emit.py +30 -4
  31. draftwright-0.4.10/src/draftwright/view_plan.py +710 -0
  32. {draftwright-0.4.9 → draftwright-0.4.10}/tests/refactor_golden/bracket_section.json +4 -0
  33. {draftwright-0.4.9 → draftwright-0.4.10}/tests/refactor_golden/centered_rebate.json +4 -0
  34. {draftwright-0.4.9 → draftwright-0.4.10}/tests/refactor_golden/chamfered.json +40 -36
  35. {draftwright-0.4.9 → draftwright-0.4.10}/tests/refactor_golden/filleted.json +40 -36
  36. {draftwright-0.4.9 → draftwright-0.4.10}/tests/refactor_golden/flange_dense.json +4 -0
  37. {draftwright-0.4.9 → draftwright-0.4.10}/tests/refactor_golden/grid_plate.json +9 -0
  38. {draftwright-0.4.9 → draftwright-0.4.10}/tests/refactor_golden/grooved_shaft.json +9 -0
  39. {draftwright-0.4.9 → draftwright-0.4.10}/tests/refactor_golden/hex_bar.json +4 -0
  40. {draftwright-0.4.9 → draftwright-0.4.10}/tests/refactor_golden/holed_slot.json +4 -0
  41. {draftwright-0.4.9 → draftwright-0.4.10}/tests/refactor_golden/pocketed.json +4 -0
  42. {draftwright-0.4.9 → draftwright-0.4.10}/tests/refactor_golden/prismatic_ladder.json +44 -40
  43. {draftwright-0.4.9 → draftwright-0.4.10}/tests/refactor_golden/scattered_plate.json +9 -0
  44. {draftwright-0.4.9 → draftwright-0.4.10}/tests/refactor_golden/side_drilled.json +4 -0
  45. {draftwright-0.4.9 → draftwright-0.4.10}/tests/refactor_golden/turned_stepped.json +4 -0
  46. draftwright-0.4.10/tests/test_adr0018_arrangement_gate.py +383 -0
  47. draftwright-0.4.10/tests/test_adr0018_view_routing.py +115 -0
  48. draftwright-0.4.10/tests/test_adr0018_view_selection.py +566 -0
  49. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_audit_differential.py +7 -6
  50. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_compiled_plan_boundary.py +7 -7
  51. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_drawing_encapsulation.py +5 -0
  52. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_e2e_standards.py +37 -19
  53. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_import_boundaries.py +13 -1
  54. draftwright-0.4.10/tests/test_issue_1116_ap242_hole_tolerance_lowering.py +395 -0
  55. draftwright-0.4.10/tests/test_issue_1130_view_planning_evidence.py +211 -0
  56. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_issue_1146_scale_completeness.py +46 -4
  57. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_issue_1188_per_view_assignment.py +19 -11
  58. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_issue_1240_iso_above_strip_visibility.py +14 -6
  59. draftwright-0.4.10/tests/test_issue_1254_turned_chamfers.py +159 -0
  60. draftwright-0.4.10/tests/test_issue_1260_view_constraints.py +462 -0
  61. draftwright-0.4.10/tests/test_issue_1276_turned_leader_targets.py +332 -0
  62. draftwright-0.4.10/tests/test_issue_1281_turned_fillets.py +113 -0
  63. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_issue_798_silhouette_lint.py +10 -6
  64. draftwright-0.4.10/tests/test_issue_924_zero_length_shoulders.py +118 -0
  65. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_label_provenance.py +1 -1
  66. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_make_drawing.py +57 -25
  67. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_pitch_dim_footprint.py +36 -2
  68. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_pmi.py +76 -32
  69. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_recogniser_capabilities.py +124 -19
  70. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_recognition_manifest.py +18 -21
  71. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_recognition_result.py +9 -12
  72. draftwright-0.4.10/tests/test_refactor_golden.py +432 -0
  73. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_sheet_emit.py +8 -1
  74. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_sheet_gdt.py +2 -1
  75. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_sheet_identity_invariant.py +61 -7
  76. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_sheet_notes.py +4 -2
  77. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_strip_layout.py +28 -10
  78. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_tolerances.py +34 -0
  79. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_two_repository_workflow.py +62 -0
  80. draftwright-0.4.10/tests/test_view_plan.py +572 -0
  81. draftwright-0.4.9/docs/reference/sheet.md +0 -35
  82. draftwright-0.4.9/tests/test_refactor_golden.py +0 -261
  83. {draftwright-0.4.9 → draftwright-0.4.10}/.gitignore +0 -0
  84. {draftwright-0.4.9 → draftwright-0.4.10}/LICENSE +0 -0
  85. {draftwright-0.4.9 → draftwright-0.4.10}/README.md +0 -0
  86. {draftwright-0.4.9 → draftwright-0.4.10}/docs/research/1062-repeating-radial-profile-evidence.md +0 -0
  87. {draftwright-0.4.9 → draftwright-0.4.10}/skills/SKILL.md +0 -0
  88. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/_pmi_part21.py +0 -0
  89. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/_warnings.py +0 -0
  90. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/annotate.py +0 -0
  91. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/annotations/__init__.py +0 -0
  92. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/annotations/_common.py +0 -0
  93. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/annotations/balloons.py +0 -0
  94. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/annotations/gears.py +0 -0
  95. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/annotations/leaders.py +0 -0
  96. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/audit.py +0 -0
  97. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/cli.py +0 -0
  98. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/evaluation/__init__.py +0 -0
  99. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/evaluation/step_analysis.py +0 -0
  100. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/export.py +0 -0
  101. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/fits.py +0 -0
  102. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/fonts/IBMPlexMono-Regular.ttf +0 -0
  103. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/fonts/IBMPlexSansCondensed-Regular.ttf +0 -0
  104. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/fonts/LICENSE-IBMPlexMono-OFL.txt +0 -0
  105. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/fonts/LICENSE-IBMPlexSansCondensed-OFL.txt +0 -0
  106. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/fonts/__init__.py +0 -0
  107. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/intents.py +0 -0
  108. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/layout.py +0 -0
  109. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/linting/__init__.py +0 -0
  110. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/linting/channel_coverage.py +0 -0
  111. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/linting/coverage.py +0 -0
  112. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/linting/evidence.py +0 -0
  113. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/linting/flat_coverage.py +0 -0
  114. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/linting/gear_coverage.py +0 -0
  115. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/linting/issues.py +0 -0
  116. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/linting/polygonal_stock_coverage.py +0 -0
  117. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/linting/profiled_bore_coverage.py +0 -0
  118. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/linting/slot_coverage.py +0 -0
  119. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/linting/structural.py +0 -0
  120. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/linting/suggest.py +0 -0
  121. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/make_drawing.py +0 -0
  122. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/model/callout.py +0 -0
  123. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/pmi.py +0 -0
  124. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/projection.py +0 -0
  125. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/py.typed +0 -0
  126. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/recognition/__init__.py +0 -0
  127. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/recognition_cache.py +0 -0
  128. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/registry.py +0 -0
  129. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/repair.py +0 -0
  130. {draftwright-0.4.9 → draftwright-0.4.10}/src/draftwright/score.py +0 -0
  131. {draftwright-0.4.9 → draftwright-0.4.10}/tests/_kernel.py +0 -0
  132. {draftwright-0.4.9 → draftwright-0.4.10}/tests/_layout_sig.py +0 -0
  133. {draftwright-0.4.9 → draftwright-0.4.10}/tests/conftest.py +0 -0
  134. {draftwright-0.4.9 → draftwright-0.4.10}/tests/fixtures/evaluation/README.md +0 -0
  135. {draftwright-0.4.9 → draftwright-0.4.10}/tests/fixtures/evaluation/ambiguous-open-semicircle.step +0 -0
  136. {draftwright-0.4.9 → draftwright-0.4.10}/tests/fixtures/evaluation/blind-hole.step +0 -0
  137. {draftwright-0.4.9 → draftwright-0.4.10}/tests/fixtures/evaluation/corpus-v1.json +0 -0
  138. {draftwright-0.4.9 → draftwright-0.4.10}/tests/fixtures/evaluation/plain-block.step +0 -0
  139. {draftwright-0.4.9 → draftwright-0.4.10}/tests/fixtures/evaluation/topology-a.step +0 -0
  140. {draftwright-0.4.9 → draftwright-0.4.10}/tests/fixtures/evaluation/topology-b.step +0 -0
  141. {draftwright-0.4.9 → draftwright-0.4.10}/tests/fixtures/grm03_thumbwheel_drive_screw.step +0 -0
  142. {draftwright-0.4.9 → draftwright-0.4.10}/tests/fixtures/if_step_flat_across_cylinder.step +0 -0
  143. {draftwright-0.4.9 → draftwright-0.4.10}/tests/fixtures/issue_1058_wheel_rh.step +0 -0
  144. {draftwright-0.4.9 → draftwright-0.4.10}/tests/fixtures/issue_909_basic_part_design_017_body.step +0 -0
  145. {draftwright-0.4.9 → draftwright-0.4.10}/tests/fixtures/issue_915_case_study_2.step +0 -0
  146. {draftwright-0.4.9 → draftwright-0.4.10}/tests/fixtures/nist_ctc_01_asme1_ap203.stp +0 -0
  147. {draftwright-0.4.9 → draftwright-0.4.10}/tests/fixtures/nist_ctc_01_asme1_ap242.stp +0 -0
  148. {draftwright-0.4.9 → draftwright-0.4.10}/tests/fixtures/nist_ctc_02_asme1_ap203.stp +0 -0
  149. {draftwright-0.4.9 → draftwright-0.4.10}/tests/fixtures/nist_ctc_02_asme1_ap242.stp +0 -0
  150. {draftwright-0.4.9 → draftwright-0.4.10}/tests/fixtures/nist_ctc_03_asme1_ap203.stp +0 -0
  151. {draftwright-0.4.9 → draftwright-0.4.10}/tests/fixtures/nist_ctc_03_asme1_ap242.stp +0 -0
  152. {draftwright-0.4.9 → draftwright-0.4.10}/tests/fixtures/nist_ctc_04_asme1_ap203.stp +0 -0
  153. {draftwright-0.4.9 → draftwright-0.4.10}/tests/fixtures/nist_ctc_04_asme1_ap242.stp +0 -0
  154. {draftwright-0.4.9 → draftwright-0.4.10}/tests/fixtures/nist_ctc_05_asme1_ap203.stp +0 -0
  155. {draftwright-0.4.9 → draftwright-0.4.10}/tests/fixtures/nist_ctc_05_asme1_ap242.stp +0 -0
  156. {draftwright-0.4.9 → draftwright-0.4.10}/tests/fixtures/tuner_jig_blind_obround_pockets.step +0 -0
  157. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_add_dimension.py +0 -0
  158. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_agents_guide.py +0 -0
  159. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_api_docs.py +0 -0
  160. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_architecture_docs.py +0 -0
  161. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_balloon_ring_standoff.py +0 -0
  162. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_carve_free_position_callers.py +0 -0
  163. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_counting_calls.py +0 -0
  164. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_cross_repository_delivery_protocol.py +0 -0
  165. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_declare.py +0 -0
  166. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_declared_recognition_gate.py +0 -0
  167. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_dense_sheet_canary.py +0 -0
  168. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_deprecation_dates.py +0 -0
  169. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_detect_once.py +0 -0
  170. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_detect_registry.py +0 -0
  171. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_dimension_role_vocabulary.py +0 -0
  172. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_e2e_slice.py +0 -0
  173. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_export_dxf_curves.py +0 -0
  174. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_export_dxf_zoom.py +0 -0
  175. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_external_recognition_boundary.py +0 -0
  176. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_fillets_adjacency.py +0 -0
  177. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_fits.py +0 -0
  178. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_flat_completeness.py +0 -0
  179. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_flat_stock_identity.py +0 -0
  180. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_flat_stock_opposition.py +0 -0
  181. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_gdt_placement.py +0 -0
  182. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_grid_lattice_convention.py +0 -0
  183. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_iso_nts_caption_placement.py +0 -0
  184. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_issue_1000_envelope_reuse.py +0 -0
  185. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_issue_1058_wheel_profile.py +0 -0
  186. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_issue_1073_cross_body_patterns.py +0 -0
  187. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_issue_1082_polygonal_stock.py +0 -0
  188. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_issue_1086_declared_gears.py +0 -0
  189. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_issue_1142_hole_leader_labels.py +0 -0
  190. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_issue_1143_hole_completeness.py +0 -0
  191. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_issue_1144_transactional_hole_table.py +0 -0
  192. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_issue_1147_legibility_pair_aggregation.py +0 -0
  193. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_issue_1153_contradictory_dimensions.py +0 -0
  194. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_issue_1154_one_owner_per_measurement.py +0 -0
  195. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_issue_1166_cross_pass_feature_leaders.py +0 -0
  196. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_issue_1176_quality_fidelity.py +0 -0
  197. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_issue_1177_angular_dimensions.py +0 -0
  198. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_issue_1187_unroutable_leaders.py +0 -0
  199. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_issue_1190_section_decision.py +0 -0
  200. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_issue_1196_deterministic_view_names.py +0 -0
  201. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_issue_1202_observed_drawing_consumer.py +0 -0
  202. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_issue_1204_multiscale_view_issues.py +0 -0
  203. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_issue_1215_envelope_tolerance.py +0 -0
  204. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_issue_1215_no_approved_tolerance_is_dropped.py +0 -0
  205. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_issue_1216_callout_reservation_measures_ink.py +0 -0
  206. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_issue_1216_pairwise_across_scale_groups.py +0 -0
  207. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_issue_1217_claimed_representations.py +0 -0
  208. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_issue_1217_the_facility_is_shared.py +0 -0
  209. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_issue_1219_location_claims_its_own_axis.py +0 -0
  210. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_issue_1229_ledger_member_keying.py +0 -0
  211. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_issue_367_leader_ink.py +0 -0
  212. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_issue_676_polygonal_boss.py +0 -0
  213. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_issue_676_polygonal_boss_declare.py +0 -0
  214. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_issue_740_leader_assignment.py +0 -0
  215. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_issue_798_floor_cardinality.py +0 -0
  216. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_issue_798_material_field.py +0 -0
  217. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_issue_885_prismatic_coverage.py +0 -0
  218. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_issue_909_sloped_profile.py +0 -0
  219. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_issue_915_dense_case.py +0 -0
  220. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_issue_916_pocket_leader.py +0 -0
  221. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_issue_917_open_channel.py +0 -0
  222. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_issue_955_height_contingency.py +0 -0
  223. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_issue_958_cross_solid_recognition.py +0 -0
  224. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_layout.py +0 -0
  225. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_layout_cleanliness.py +0 -0
  226. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_layout_hypothesis.py +0 -0
  227. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_layout_property.py +0 -0
  228. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_leader_footprint.py +0 -0
  229. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_lint_box_cache.py +0 -0
  230. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_lint_reconciliation.py +0 -0
  231. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_lint_structural.py +0 -0
  232. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_linting.py +0 -0
  233. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_location_vocabulary.py +0 -0
  234. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_note_verb.py +0 -0
  235. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_object_aspects.py +0 -0
  236. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_occupancy_boxes.py +0 -0
  237. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_parameter_id.py +0 -0
  238. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_part_model.py +0 -0
  239. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_pmi_datum_lowering.py +0 -0
  240. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_pmi_gtol_lowering.py +0 -0
  241. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_pmi_part21.py +0 -0
  242. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_pocket_pattern.py +0 -0
  243. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_pocket_pattern_recognition.py +0 -0
  244. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_principal_profile_classifier.py +0 -0
  245. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_private_test_attr_reads.py +0 -0
  246. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_private_test_imports.py +0 -0
  247. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_quality_components.py +0 -0
  248. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_recogniser_adoption.py +0 -0
  249. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_recogniser_contract.py +0 -0
  250. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_recognition.py +0 -0
  251. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_registry.py +0 -0
  252. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_render_seam.py +0 -0
  253. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_repack_geometry_seam.py +0 -0
  254. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_score.py +0 -0
  255. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_script_detail_parity.py +0 -0
  256. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_shared_box_memo.py +0 -0
  257. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_sheet_of.py +0 -0
  258. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_sheet_section.py +0 -0
  259. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_sheet_tables.py +0 -0
  260. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_slanted_blind_step.py +0 -0
  261. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_slot_completeness.py +0 -0
  262. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_slot_pattern.py +0 -0
  263. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_slot_pattern_recognition.py +0 -0
  264. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_soft_deprecation.py +0 -0
  265. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_solve_trace.py +0 -0
  266. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_step_analysis_evaluation.py +0 -0
  267. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_suppression_ledger.py +0 -0
  268. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_suppression_marks.py +0 -0
  269. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_turned_steps.py +0 -0
  270. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_witness_label_reconciliation.py +0 -0
  271. {draftwright-0.4.9 → draftwright-0.4.10}/tests/test_workflows.py +0 -0
@@ -1,5 +1,90 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.4.10 — 2026-08-22
4
+
5
+ **A view-set-aware planning and turned-edge-treatment patch.** Authored view constraints now
6
+ compile into an explicit resolved plan, automatic drawings fail closed when required content is
7
+ lost, AP242 hole tolerances reach their semantic feature owners, and the exact
8
+ `b123d-recognisers` 0.3.0 release supplies turned chamfer and fillet evidence through truthful
9
+ profile-view callouts.
10
+
11
+ ### Added
12
+
13
+ - `Sheet` can now author a complete or augmenting semantic view set with `authored_views()`,
14
+ `auto_views()`, `view()`, `section_view()`, `detail_view()`, and their `add_*` forms. Fluent
15
+ view handles declare relationships, alignment, scale, and whole-view pins without exposing
16
+ annotation coordinates; unsupported or infeasible constraints fail at their declaration
17
+ source instead of being silently relaxed. `sheet.view_constraints` exposes the immutable
18
+ request and `drawing.view_plan` the distinct resolved result (#1260).
19
+ - Drawings now retain their resolved view topology, per-view requirement coverage, and
20
+ arrangement decision as inspectable values. The engine can build and semantically compare
21
+ alternative arrangements and requested view sets while keeping projection and page placement
22
+ derived from the same plan (#1130).
23
+
24
+ ### Changed
25
+
26
+ - Dimension planning is now aware of the selected principal view set. Requirements supported by
27
+ another planned view are re-homed before projection; an unsupported set raises one aggregated
28
+ `ViewPlanIncomplete` naming every unmet requirement rather than failing late or producing a
29
+ plausible-looking drawing with missing dimensions (#1259).
30
+ - Three sparse corpus drawings now use a `stacked-iso` arrangement and fit A4 instead of A3 at
31
+ the same scale, with identical annotations, labels, and build issues. An alternative may only
32
+ compact the scale already selected by the default arrangement and must survive a real compile
33
+ without semantic loss; otherwise the default is retained (#1130).
34
+ - Updated the exact `b123d-recognisers` production lock from 0.2.9 to 0.3.0.
35
+ The immutable PyPI wheel/sdist hashes and capability-manifest digest `6645def94cbabaca7b9676fdb029b02e467266d1f020ea536e903b7d86b52685` are
36
+ recorded in `.github/recogniser-release.json`; focused compatibility evidence and the
37
+ normal consumer gates run on the generated PR.
38
+ - Accepted the additive Chamfer and Fillet schema-2 recogniser contract alongside schema 1 for
39
+ the `b123d-recognisers` 0.3.0 cutover; all other record schemas remain fail-closed at version 1
40
+ (#1276).
41
+
42
+ - Updated the exact `b123d-recognisers` production lock from 0.2.6 to 0.2.9.
43
+ The immutable PyPI wheel/sdist hashes and capability-manifest digest `805876905d548fd0ef1301e2cf24427a86d34bc2e592b8d2d0cb43db94e1d3bd` are
44
+ recorded in `.github/recogniser-release.json`; focused compatibility evidence and the
45
+ normal consumer gates run on the generated PR. The aggregate now inventories chamfers and
46
+ fillets on turned parts, and the fixed coaxial-post slot regression retires its downstream
47
+ strict xfail.
48
+
49
+ ### Fixed
50
+
51
+ - Automatic builds now apply the same required-outcome gate as explicit-scale builds. A settled
52
+ drawing that lost required placement outcomes records `plan_incomplete`, reports an
53
+ `incomplete` scale decision, preserves measurement/hole/source provenance, and fails lint
54
+ rather than presenting a diagnostic drawing as complete (#1250).
55
+
56
+ - Supported AP242 diameter tolerances now correlate with their canonical hole or pattern owner at
57
+ the completed IR boundary and round-trip through emitted `Sheet` declarations with source
58
+ provenance. Unmatched, ambiguous, conflicting, partial-pattern, and unsupported requirements
59
+ fail closed to provenance-preserving measured dimensions rather than being duplicated or lost
60
+ (#1116).
61
+
62
+ - A declared step shoulder coincident with its datum is now pruned before rendering instead of
63
+ sending identical endpoints to the border-dimension helper and raising `ValueError`. Valid
64
+ siblings remain, while the omission is recorded in `Drawing.suppressions()` and reported as
65
+ `step_position_coincident_with_datum` (#924).
66
+
67
+ - Turned chamfer and fillet leaders now read in a shaft profile view and land on their physical
68
+ edge-treatment sites instead of collapsing axial stations in the end view. Cylindrical
69
+ feature notes, knurl callouts, and surface finishes likewise derive a model-space surface
70
+ target rather than pointing at the shaft axis; all continue through the shared placement
71
+ solves, with X/Y/Z parity and declared Sheet round-trip (#1276).
72
+
73
+ - Toroidal fillets returned for turned parts by the shared recognition inventory now lower into
74
+ the existing one-feature-per-round IR and render through the common leader solve. Equal radii
75
+ share one `n× R` callout carrying every physical fillet's measurement provenance, and emitted
76
+ Sheet programs preserve the full inventory without rescanning geometry (#1281).
77
+
78
+ - Turned chamfers returned by the shared recognition inventory now lower into the same
79
+ one-feature-per-edge-treatment IR as prismatic chamfers. Equal specifications render as one
80
+ `n×` callout carrying every member's measurement provenance; different chamfer forms do not
81
+ collapse merely because their first legs match, and distinct tolerances split into truthful
82
+ requirements instead of being lost or applied to untoleranced siblings (#1254).
83
+
84
+ - Recogniser dependency automation now starts a fresh `Unreleased` section when the previous
85
+ Draftwright release has finalized the changelog, so a published recogniser can enter through
86
+ the documented immutable update workflow instead of failing before it opens a PR (#1279).
87
+
3
88
  ## v0.4.9 — 2026-08-21
4
89
 
5
90
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: draftwright
3
- Version: 0.4.9
3
+ Version: 0.4.10
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.0
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,19 @@ 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, first land a narrow Draftwright
53
+ declaration accepting both the installed schema and the named candidate schema. The existing adapter
54
+ must remain valid when the new field is ignored, and tests must prove the next schema is accepted
55
+ only by that explicit declaration while later schemas still fail. The package canary then proves
56
+ the real candidate wheel against that committed consumer point. After the package release, the
57
+ Draftwright dependency/behavior PR locks the immutable artifact, adopts the field, and narrows the
58
+ declaration to the new schema.
59
+
60
+ For each applicable stage, add the adapter, declaration, emitted-Sheet round trip, drawing behavior,
61
+ and completeness evidence; for an inapplicable or deferred stage, state why and link its tracking
62
+ issue. The contract test derives package record outputs, converter registries, live `Sheet` methods,
63
+ and emitter branches independently, so copying a new name into the declaration alone cannot make CI
64
+ pass.
56
65
 
57
66
  `bosses` is the fully consumed reference family. `repeating-radial-profiles` is the opposite
58
67
  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.10"
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.0",
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
@@ -804,8 +811,8 @@ class DetailRequest:
804
811
  footprint that depends on the chosen scale (the prismatic
805
812
  ladder reserves one rung per *legible-at-that-scale* step, so it
806
813
  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.
814
+ source_view: orthographic direction to preserve in the detail (``"front"``,
815
+ ``"plan"`` or ``"side"``).
809
816
  crop_lo/crop_hi: optional crop bounds along ``axis`` when the projected
810
817
  detail must include context outside the marked feature band.
811
818
  Prismatic absolute-height details use this to retain their
@@ -823,13 +830,23 @@ class DetailRequest:
823
830
  redraw: Callable[..., int]
824
831
  pad_top: float = 0.0
825
832
  pads: Callable[[float], tuple[float, float]] | None = None
826
- source_view: Literal["front", "side"] = "front"
833
+ source_view: Literal["front", "plan", "side"] = "front"
827
834
  crop_lo: float | None = None
828
835
  crop_hi: float | None = None
829
836
  cross_axis: Literal["x", "y", "z"] | None = None
830
837
  cross_lo: float | None = None
831
838
  cross_hi: float | None = None
832
839
  kind: str = "detail"
840
+ #: Authored detail identity/label. ``None`` lets the automatic resolver assign A-H.
841
+ view_name: str | None = None
842
+ label: str | None = None
843
+ #: Exact authored multiple of sheet scale; automatic requests keep ``None`` and use the
844
+ #: legibility-derived preferred series.
845
+ scale_factor: float | None = None
846
+ #: An authored orientation crop is defining geometry even when it carries no redrawn
847
+ #: dimension. Automatic recovery details retain the historical "no dimension, no view" gate.
848
+ keep_without_annotations: bool = False
849
+ source: object | None = None
833
850
 
834
851
 
835
852
  @dataclass(frozen=True)
@@ -1021,7 +1038,7 @@ class Analysis:
1021
1038
  step_zs: list[float]
1022
1039
  layout_strips: StripDepths
1023
1040
  layout_n_steps: int
1024
- layout_section: bool
1041
+ layout_section: int
1025
1042
  layout_table_sizes: tuple[tuple[float, float], ...]
1026
1043
  layout_required_tables: tuple[tuple[tuple[float, float], str], ...]
1027
1044
  sv_right: float
@@ -1090,6 +1107,28 @@ class Analysis:
1090
1107
  # declared a model (ADR 0011) or on a manually-built Analysis — consumers fall
1091
1108
  # back to build_model(a).
1092
1109
  model: object | None = None
1110
+ #: The relational arrangement the sheet was composed under — ADR 0018 §5's fourth
1111
+ #: dimension, decided once by `compose.choose_scale` and carried here so that placement
1112
+ #: and the repack loop compose under the arrangement whose feasibility was actually
1113
+ #: established. They call `_layout_geometry` with MEASURED strip depths where selection
1114
+ #: passed estimates, so a stage that re-derives this instead of reading it can reach a
1115
+ #: different answer for the same sheet and lose dimensions to the mismatch (#1130).
1116
+ #: Defaulted for hand-built `Analysis` objects, which mean the long-standing arrangement.
1117
+ arrangement: str = "columns"
1118
+ #: The principal views this sheet carries, or None for the third-angle three (ADR 0018).
1119
+ #: Decided once alongside scale/page/arrangement and carried, for the same reason: the
1120
+ #: layout must reserve space for exactly the views the builder creates, or dropping one
1121
+ #: costs its annotations without reclaiming its paper.
1122
+ planned_views: tuple[str, ...] | None = None
1123
+ #: Whether the orientation isometric participates in this authored plan. It is separate
1124
+ #: from ``planned_views`` because that tuple is the principal set consumed by the
1125
+ #: requirement compiler.
1126
+ planned_iso: bool = True
1127
+ #: ``None`` lets the orientation iso auto-fit; a number is an authored exact factor.
1128
+ planned_iso_scale: float | None = None
1129
+ #: Immutable ADR 0018 authored input, retained for the resolver/diagnostics without making
1130
+ #: this low-level module depend on the view-planning leaf at runtime.
1131
+ view_constraints: object | None = None
1093
1132
 
1094
1133
  @property
1095
1134
  def pmi(self) -> list:
@@ -1276,6 +1315,11 @@ def _add_zone_grid(dwg, a: Analysis):
1276
1315
 
1277
1316
  def _iso_bbox(dwg):
1278
1317
  """(min_x, min_y, max_x, max_y) of the placed iso view, hidden lines included."""
1318
+ if "iso" not in dwg.views:
1319
+ # A deliberately omitted orientation view contributes no obstacle. Infinity makes
1320
+ # every existing iso-clearance comparison a no-op without teaching annotation passes
1321
+ # a second page-edge convention.
1322
+ return (math.inf, math.inf, math.inf, math.inf)
1279
1323
  return dwg.view_bounds("iso")
1280
1324
 
1281
1325
 
@@ -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