draftwright 0.4.4__tar.gz → 0.4.5__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 (224) hide show
  1. {draftwright-0.4.4 → draftwright-0.4.5}/CHANGELOG.md +14 -0
  2. {draftwright-0.4.4 → draftwright-0.4.5}/PKG-INFO +1 -1
  3. {draftwright-0.4.4 → draftwright-0.4.5}/pyproject.toml +1 -1
  4. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/annotations/from_model.py +17 -12
  5. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/annotations/holes.py +43 -1
  6. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/linting/coverage.py +29 -6
  7. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/model/planner.py +11 -7
  8. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_declare.py +16 -0
  9. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_issue_885_prismatic_coverage.py +20 -0
  10. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_strip_layout.py +52 -0
  11. {draftwright-0.4.4 → draftwright-0.4.5}/.gitignore +0 -0
  12. {draftwright-0.4.4 → draftwright-0.4.5}/LICENSE +0 -0
  13. {draftwright-0.4.4 → draftwright-0.4.5}/README.md +0 -0
  14. {draftwright-0.4.4 → draftwright-0.4.5}/docs/adr/README.md +0 -0
  15. {draftwright-0.4.4 → draftwright-0.4.5}/skills/SKILL.md +0 -0
  16. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/__init__.py +0 -0
  17. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/_core.py +0 -0
  18. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/_geometry.py +0 -0
  19. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/_pmi_part21.py +0 -0
  20. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/_warnings.py +0 -0
  21. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/analysis.py +0 -0
  22. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/annotate.py +0 -0
  23. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/annotations/__init__.py +0 -0
  24. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/annotations/_common.py +0 -0
  25. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/annotations/balloons.py +0 -0
  26. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/annotations/gears.py +0 -0
  27. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/annotations/orchestrator.py +0 -0
  28. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/annotations/sections.py +0 -0
  29. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/audit.py +0 -0
  30. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/builder.py +0 -0
  31. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/cli.py +0 -0
  32. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/compose.py +0 -0
  33. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/drawing.py +0 -0
  34. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/export.py +0 -0
  35. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/fits.py +0 -0
  36. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/fonts/IBMPlexMono-Regular.ttf +0 -0
  37. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/fonts/IBMPlexSansCondensed-Regular.ttf +0 -0
  38. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/fonts/LICENSE-IBMPlexMono-OFL.txt +0 -0
  39. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/fonts/LICENSE-IBMPlexSansCondensed-OFL.txt +0 -0
  40. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/fonts/__init__.py +0 -0
  41. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/intents.py +0 -0
  42. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/layout.py +0 -0
  43. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/linting/__init__.py +0 -0
  44. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/linting/channel_coverage.py +0 -0
  45. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/linting/flat_coverage.py +0 -0
  46. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/linting/gear_coverage.py +0 -0
  47. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/linting/hole_coverage.py +0 -0
  48. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/linting/issues.py +0 -0
  49. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/linting/pmi_coverage.py +0 -0
  50. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/linting/polygonal_stock_coverage.py +0 -0
  51. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/linting/profiled_bore_coverage.py +0 -0
  52. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/linting/quality.py +0 -0
  53. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/linting/slot_coverage.py +0 -0
  54. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/linting/structural.py +0 -0
  55. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/linting/suggest.py +0 -0
  56. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/make_drawing.py +0 -0
  57. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/model/__init__.py +0 -0
  58. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/model/callout.py +0 -0
  59. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/model/compiled.py +0 -0
  60. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/model/declare.py +0 -0
  61. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/model/detect.py +0 -0
  62. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/model/ir.py +0 -0
  63. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/pmi.py +0 -0
  64. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/projection.py +0 -0
  65. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/py.typed +0 -0
  66. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/recognition/__init__.py +0 -0
  67. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/recognition/_features.py +0 -0
  68. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/recognition/_record.py +0 -0
  69. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/recognition/chamfers.py +0 -0
  70. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/recognition/countersinks.py +0 -0
  71. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/recognition/fillets.py +0 -0
  72. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/recognition/flats.py +0 -0
  73. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/recognition/grooves.py +0 -0
  74. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/recognition/levels.py +0 -0
  75. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/recognition/pads.py +0 -0
  76. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/recognition/plates.py +0 -0
  77. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/recognition/polygonal_bosses.py +0 -0
  78. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/recognition/profiled_bores.py +0 -0
  79. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/recognition/repeating_profiles.py +0 -0
  80. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/recognition/result.py +0 -0
  81. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/recognition/slots.py +0 -0
  82. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/recognition/turned.py +0 -0
  83. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/registry.py +0 -0
  84. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/repair.py +0 -0
  85. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/score.py +0 -0
  86. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/sheet.py +0 -0
  87. {draftwright-0.4.4 → draftwright-0.4.5}/src/draftwright/sheet_emit.py +0 -0
  88. {draftwright-0.4.4 → draftwright-0.4.5}/tests/_kernel.py +0 -0
  89. {draftwright-0.4.4 → draftwright-0.4.5}/tests/_layout_sig.py +0 -0
  90. {draftwright-0.4.4 → draftwright-0.4.5}/tests/conftest.py +0 -0
  91. {draftwright-0.4.4 → draftwright-0.4.5}/tests/fixtures/grm03_thumbwheel_drive_screw.step +0 -0
  92. {draftwright-0.4.4 → draftwright-0.4.5}/tests/fixtures/if_step_flat_across_cylinder.step +0 -0
  93. {draftwright-0.4.4 → draftwright-0.4.5}/tests/fixtures/issue_1058_wheel_rh.step +0 -0
  94. {draftwright-0.4.4 → draftwright-0.4.5}/tests/fixtures/issue_909_basic_part_design_017_body.step +0 -0
  95. {draftwright-0.4.4 → draftwright-0.4.5}/tests/fixtures/issue_915_case_study_2.step +0 -0
  96. {draftwright-0.4.4 → draftwright-0.4.5}/tests/fixtures/nist_ctc_01_asme1_ap203.stp +0 -0
  97. {draftwright-0.4.4 → draftwright-0.4.5}/tests/fixtures/nist_ctc_01_asme1_ap242.stp +0 -0
  98. {draftwright-0.4.4 → draftwright-0.4.5}/tests/fixtures/nist_ctc_02_asme1_ap203.stp +0 -0
  99. {draftwright-0.4.4 → draftwright-0.4.5}/tests/fixtures/nist_ctc_02_asme1_ap242.stp +0 -0
  100. {draftwright-0.4.4 → draftwright-0.4.5}/tests/fixtures/nist_ctc_03_asme1_ap203.stp +0 -0
  101. {draftwright-0.4.4 → draftwright-0.4.5}/tests/fixtures/nist_ctc_03_asme1_ap242.stp +0 -0
  102. {draftwright-0.4.4 → draftwright-0.4.5}/tests/fixtures/nist_ctc_04_asme1_ap203.stp +0 -0
  103. {draftwright-0.4.4 → draftwright-0.4.5}/tests/fixtures/nist_ctc_04_asme1_ap242.stp +0 -0
  104. {draftwright-0.4.4 → draftwright-0.4.5}/tests/fixtures/nist_ctc_05_asme1_ap203.stp +0 -0
  105. {draftwright-0.4.4 → draftwright-0.4.5}/tests/fixtures/nist_ctc_05_asme1_ap242.stp +0 -0
  106. {draftwright-0.4.4 → draftwright-0.4.5}/tests/fixtures/tuner_jig_blind_obround_pockets.step +0 -0
  107. {draftwright-0.4.4 → draftwright-0.4.5}/tests/refactor_golden/bracket_section.json +0 -0
  108. {draftwright-0.4.4 → draftwright-0.4.5}/tests/refactor_golden/centered_rebate.json +0 -0
  109. {draftwright-0.4.4 → draftwright-0.4.5}/tests/refactor_golden/chamfered.json +0 -0
  110. {draftwright-0.4.4 → draftwright-0.4.5}/tests/refactor_golden/filleted.json +0 -0
  111. {draftwright-0.4.4 → draftwright-0.4.5}/tests/refactor_golden/flange_dense.json +0 -0
  112. {draftwright-0.4.4 → draftwright-0.4.5}/tests/refactor_golden/grid_plate.json +0 -0
  113. {draftwright-0.4.4 → draftwright-0.4.5}/tests/refactor_golden/grooved_shaft.json +0 -0
  114. {draftwright-0.4.4 → draftwright-0.4.5}/tests/refactor_golden/hex_bar.json +0 -0
  115. {draftwright-0.4.4 → draftwright-0.4.5}/tests/refactor_golden/holed_slot.json +0 -0
  116. {draftwright-0.4.4 → draftwright-0.4.5}/tests/refactor_golden/pocketed.json +0 -0
  117. {draftwright-0.4.4 → draftwright-0.4.5}/tests/refactor_golden/prismatic_ladder.json +0 -0
  118. {draftwright-0.4.4 → draftwright-0.4.5}/tests/refactor_golden/scattered_plate.json +0 -0
  119. {draftwright-0.4.4 → draftwright-0.4.5}/tests/refactor_golden/side_drilled.json +0 -0
  120. {draftwright-0.4.4 → draftwright-0.4.5}/tests/refactor_golden/turned_stepped.json +0 -0
  121. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_add_dimension.py +0 -0
  122. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_agents_guide.py +0 -0
  123. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_api_docs.py +0 -0
  124. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_architecture_docs.py +0 -0
  125. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_audit_differential.py +0 -0
  126. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_balloon_ring_standoff.py +0 -0
  127. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_carve_free_position_callers.py +0 -0
  128. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_compiled_plan_boundary.py +0 -0
  129. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_counting_calls.py +0 -0
  130. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_declared_recognition_gate.py +0 -0
  131. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_dense_sheet_canary.py +0 -0
  132. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_deprecation_dates.py +0 -0
  133. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_detect_once.py +0 -0
  134. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_detect_registry.py +0 -0
  135. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_dimension_role_vocabulary.py +0 -0
  136. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_drawing_encapsulation.py +0 -0
  137. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_e2e_slice.py +0 -0
  138. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_e2e_standards.py +0 -0
  139. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_export_dxf_curves.py +0 -0
  140. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_export_dxf_zoom.py +0 -0
  141. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_fillets_adjacency.py +0 -0
  142. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_fits.py +0 -0
  143. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_flat_completeness.py +0 -0
  144. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_flat_stock_identity.py +0 -0
  145. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_flat_stock_opposition.py +0 -0
  146. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_gdt_placement.py +0 -0
  147. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_grid_lattice_convention.py +0 -0
  148. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_import_boundaries.py +0 -0
  149. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_issue_1000_envelope_reuse.py +0 -0
  150. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_issue_1058_wheel_profile.py +0 -0
  151. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_issue_1073_cross_body_patterns.py +0 -0
  152. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_issue_1082_polygonal_stock.py +0 -0
  153. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_issue_1086_declared_gears.py +0 -0
  154. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_issue_1142_hole_leader_labels.py +0 -0
  155. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_issue_1143_hole_completeness.py +0 -0
  156. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_issue_1144_transactional_hole_table.py +0 -0
  157. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_issue_1146_scale_completeness.py +0 -0
  158. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_issue_1147_legibility_pair_aggregation.py +0 -0
  159. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_issue_676_polygonal_boss.py +0 -0
  160. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_issue_676_polygonal_boss_declare.py +0 -0
  161. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_issue_909_sloped_profile.py +0 -0
  162. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_issue_915_dense_case.py +0 -0
  163. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_issue_916_pocket_leader.py +0 -0
  164. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_issue_917_open_channel.py +0 -0
  165. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_issue_955_height_contingency.py +0 -0
  166. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_issue_958_cross_solid_recognition.py +0 -0
  167. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_label_provenance.py +0 -0
  168. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_layout.py +0 -0
  169. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_layout_cleanliness.py +0 -0
  170. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_layout_hypothesis.py +0 -0
  171. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_layout_property.py +0 -0
  172. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_leader_footprint.py +0 -0
  173. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_lint_box_cache.py +0 -0
  174. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_lint_reconciliation.py +0 -0
  175. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_lint_structural.py +0 -0
  176. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_linting.py +0 -0
  177. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_location_vocabulary.py +0 -0
  178. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_make_drawing.py +0 -0
  179. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_note_verb.py +0 -0
  180. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_object_aspects.py +0 -0
  181. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_occupancy_boxes.py +0 -0
  182. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_parameter_id.py +0 -0
  183. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_part_model.py +0 -0
  184. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_pitch_dim_footprint.py +0 -0
  185. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_pmi.py +0 -0
  186. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_pmi_datum_lowering.py +0 -0
  187. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_pmi_gtol_lowering.py +0 -0
  188. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_pmi_part21.py +0 -0
  189. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_pocket_pattern.py +0 -0
  190. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_pocket_pattern_recognition.py +0 -0
  191. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_principal_profile_classifier.py +0 -0
  192. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_private_test_attr_reads.py +0 -0
  193. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_private_test_imports.py +0 -0
  194. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_quality_components.py +0 -0
  195. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_recogniser_contract.py +0 -0
  196. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_recognition.py +0 -0
  197. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_recognition_manifest.py +0 -0
  198. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_recognition_result.py +0 -0
  199. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_refactor_golden.py +0 -0
  200. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_registry.py +0 -0
  201. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_render_seam.py +0 -0
  202. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_repack_geometry_seam.py +0 -0
  203. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_score.py +0 -0
  204. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_script_detail_parity.py +0 -0
  205. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_shared_box_memo.py +0 -0
  206. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_sheet_emit.py +0 -0
  207. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_sheet_gdt.py +0 -0
  208. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_sheet_identity_invariant.py +0 -0
  209. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_sheet_notes.py +0 -0
  210. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_sheet_of.py +0 -0
  211. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_sheet_section.py +0 -0
  212. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_sheet_tables.py +0 -0
  213. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_slanted_blind_step.py +0 -0
  214. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_slot_completeness.py +0 -0
  215. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_slot_pattern.py +0 -0
  216. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_slot_pattern_recognition.py +0 -0
  217. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_soft_deprecation.py +0 -0
  218. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_solve_trace.py +0 -0
  219. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_suppression_ledger.py +0 -0
  220. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_suppression_marks.py +0 -0
  221. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_tolerances.py +0 -0
  222. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_turned_steps.py +0 -0
  223. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_witness_label_reconciliation.py +0 -0
  224. {draftwright-0.4.4 → draftwright-0.4.5}/tests/test_workflows.py +0 -0
@@ -2,6 +2,20 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## v0.4.5 — 2026-08-15
6
+
7
+ **A focused technical-drawing correctness patch.** Cross-drilled hole locations now remain
8
+ complete and ordered, datum-starting pocket annotations avoid redundant dimensions, and repeated
9
+ equal-radius fillets collapse into one manufacturing callout.
10
+
11
+ ### Fixed
12
+
13
+ - Side-drilled hole heights that relocate around callout leaders now join the alternate
14
+ view's shared dimension ladder before placement, preserving every height and keeping
15
+ feature locations inside the overall dimension. Datum-starting open pockets no longer
16
+ print a redundant half-length centre offset, and equal-radius fillets across different
17
+ edge axes collapse into one counted ``n× R`` callout.
18
+
5
19
  ## v0.4.4 — 2026-08-15
6
20
 
7
21
  **A semantic-completeness and fail-closed layout patch release.** Hole requirements and drawing
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: draftwright
3
- Version: 0.4.4
3
+ Version: 0.4.5
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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "draftwright"
7
- version = "0.4.4"
7
+ version = "0.4.5"
8
8
  description = "Automated technical-drawing generation for build123d"
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
@@ -1759,16 +1759,16 @@ def _fillet_label(radius_text, count) -> str:
1759
1759
 
1760
1760
  def render_fillets(dwg, plan, a, *, ctx, only=None) -> int:
1761
1761
  """Fillet radius callouts (#561): a leader from an external edge fillet to its
1762
- ``R{radius}`` label — the arc analog of :func:`render_chamfers`. Equal-radius fillets on
1763
- the same edge axis share ONE ``n× R`` callout (#561 acceptance), placed in the view
1764
- normal to the rounded edge, led diagonally out of the corner into clear margin, and
1762
+ ``R{radius}`` label — the arc analog of :func:`render_chamfers`. Equal-radius fillets
1763
+ share ONE ``n× R`` callout (#561 acceptance), placed in the view normal to a
1764
+ representative rounded edge, led diagonally out of the corner into clear margin, and
1765
1765
  dropped (lint, not silently) if it would overprint placed geometry. Returns the count.
1766
1766
 
1767
1767
  Planner-fed (#725 / #698): the radius VALUE + its tolerance come from the planner's
1768
1768
  ``DimParameter`` (as in ``render_chamfers``), bound explicitly by ``(role, kind)``,
1769
1769
  never positionally — formatting ``fl.radius`` directly dropped an authored tolerance
1770
1770
  (the #629 class). The equal-radius ``n× R`` COLLAPSE stays render-side (grouping by
1771
- ``(axis, value)`` here, as before) — planner-side grouping is a structural change,
1771
+ radius here) — planner-side grouping is a structural change,
1772
1772
  explicitly out of scope for this migration (#698). Where grouped members' authored
1773
1773
  tolerances differ, the first (member-order) authored one wins — the documented
1774
1774
  ``render_diameters`` shared-ø precedent. ``g.view`` is safe: a FilletFeature's frame
@@ -1785,9 +1785,9 @@ def render_fillets(dwg, plan, a, *, ctx, only=None) -> int:
1785
1785
  )
1786
1786
  if pd is None:
1787
1787
  continue
1788
- collapse.setdefault((g.facts.axis, round(pd.value, 3)), []).append((g, pd))
1788
+ collapse.setdefault(round(pd.value, 3), []).append((g, pd))
1789
1789
  jobs = []
1790
- for gi, ((axis, _radius), members) in enumerate(sorted(collapse.items())):
1790
+ for gi, (_radius, members) in enumerate(sorted(collapse.items())):
1791
1791
  if only is not None:
1792
1792
  # #426 Ph2b subset (finalize): filter members AFTER the collapse is enumerated so
1793
1793
  # gi stays the full-drawing group index — a survivor keeps its m_fillet name even
@@ -1795,10 +1795,15 @@ def render_fillets(dwg, plan, a, *, ctx, only=None) -> int:
1795
1795
  members = [gp for gp in members if gp[0].ref in only]
1796
1796
  if not members:
1797
1797
  continue
1798
- # One grouped ``n× R`` callout, but its leader may anchor at ANY of the equal fillets
1799
- # _corner_candidates tries each corner (nearest-clear first) so the group is not
1800
- # dropped just because its first corner leads into an occupied region.
1801
- ordered = sorted(members, key=lambda gp: gp[0].facts.frame.origin)
1798
+ # Point the leader at one coherent visible set. Members on other edge axes still
1799
+ # contribute to the printed count and semantic measurements, but mixing their 3-D
1800
+ # origins into this view could point at unrelated projected corners. Prefer the
1801
+ # axis with the most members; ties are deterministic.
1802
+ by_axis: dict[str, list] = {}
1803
+ for gp in members:
1804
+ by_axis.setdefault(gp[0].facts.axis, []).append(gp)
1805
+ axis, visible = min(by_axis.items(), key=lambda item: (-len(item[1]), item[0]))
1806
+ ordered = sorted(visible, key=lambda gp: gp[0].facts.frame.origin)
1802
1807
  view = ordered[0][0].view
1803
1808
  vb = dwg.view_bounds(view)
1804
1809
  if vb is None:
@@ -1812,7 +1817,7 @@ def render_fillets(dwg, plan, a, *, ctx, only=None) -> int:
1812
1817
  f"m_fillet_{axis}{gi}",
1813
1818
  view,
1814
1819
  vb,
1815
- _fillet_label(members[0][1].value_text, len(ordered)) + _tol_suffix(tol, draft),
1820
+ _fillet_label(members[0][1].value_text, len(members)) + _tol_suffix(tol, draft),
1816
1821
  _corner_candidates(
1817
1822
  dwg,
1818
1823
  view,
@@ -1823,7 +1828,7 @@ def render_fillets(dwg, plan, a, *, ctx, only=None) -> int:
1823
1828
  ),
1824
1829
  # One `n× R` callout stands for EVERY collapsed member, so it draws all of
1825
1830
  # their radii — the tuple storage exists for exactly this (#1002).
1826
- tuple(pd.id for _, pd in ordered),
1831
+ tuple(pd.id for _, pd in members),
1827
1832
  )
1828
1833
  )
1829
1834
  return _leader_callout_pass(dwg, a, jobs, noun="fillet", drop_code="fillet_dropped", ctx=ctx)
@@ -49,10 +49,12 @@ from draftwright.annotations._common import (
49
49
  carve_free_position,
50
50
  carve_free_segments,
51
51
  clear_label_of_centerlines,
52
+ corridor_blockers,
52
53
  dim_footprint,
53
54
  leader_footprint,
54
55
  place_strip_candidates,
55
56
  register_corridor,
57
+ strip_free_span,
56
58
  strip_obstacles,
57
59
  )
58
60
  from draftwright.annotations.from_model import (
@@ -967,6 +969,42 @@ def _locate_along_z(dwg, ctx, a: Analysis, off):
967
969
  strip, view, p_lo, p_hi, edge = primary
968
970
  primary_cand = _zc(view, p_lo, p_hi, edge)
969
971
 
972
+ # Choose the alternate BEFORE the shared corridor solve when the natural
973
+ # witness corridor is already known to cross a placed leader. A post-drain
974
+ # relocation cannot join the alternate view's existing ladder, so it used to
975
+ # land outside the overall height and cross its witness line. The inner-tier
976
+ # footprint is decisive: moving a right-side dimension farther out only grows
977
+ # the corridor back to the view and therefore cannot clear an existing blocker.
978
+ if strip is not None and alternates:
979
+ _lo, _hi, inner = strip_free_span(strip)
980
+ blocked = _box_hits(_geom_box(primary_cand[1](inner)), corridor_blockers(dwg, view))
981
+ if blocked:
982
+ alt_strip, alt_view, alt_p_lo, alt_p_hi, alt_edge = alternates[0]
983
+ alt_cand = _zc(alt_view, alt_p_lo, alt_p_hi, alt_edge)
984
+ alt_blocked = alt_strip is None
985
+ if alt_strip is not None:
986
+ _alo, _ahi, alt_inner = strip_free_span(alt_strip)
987
+ alt_blocked = _box_hits(
988
+ _geom_box(alt_cand[1](alt_inner)),
989
+ corridor_blockers(dwg, alt_view),
990
+ )
991
+ # Do not pre-route into a corridor that cannot hold its existing
992
+ # shared ladder plus this candidate. In that case the historical
993
+ # alternate/force path remains the honest policy-B fallback.
994
+ existing = len(
995
+ ctx.corridor_batch.get((alt_view, "right"), {}).get("cands", ())
996
+ )
997
+ pad = tier + alt_strip.spacing
998
+ usable = max(0.0, _ahi - _alo - tier)
999
+ capacity = int(usable / pad) + 1
1000
+ alt_blocked = alt_blocked or capacity < existing + 1
1001
+ if not alt_blocked:
1002
+ original = primary
1003
+ primary = alternates[0]
1004
+ alternates = [original, *alternates[1:]]
1005
+ strip, view, p_lo, p_hi, edge = primary
1006
+ primary_cand = alt_cand
1007
+
970
1008
  def _fallback(
971
1009
  _nm,
972
1010
  _primary=primary,
@@ -974,10 +1012,14 @@ def _locate_along_z(dwg, ctx, a: Analysis, off):
974
1012
  _cand=primary_cand,
975
1013
  _feature_map=_zf,
976
1014
  _measurement_map=_zm,
1015
+ _candidate_factory=_zc,
977
1016
  _zo=zo,
978
1017
  ):
979
1018
  for alt_strip, alt_view, alt_p_lo, alt_p_hi, alt_edge in _alts:
980
- alt = _zc(alt_view, alt_p_lo, alt_p_hi, alt_edge)
1019
+ # Capture this loop member's factory. Referring to `_zc` directly
1020
+ # late-bound every callback to the final height, so two dropped hole
1021
+ # heights retried the last one twice and silently lost the first.
1022
+ alt = _candidate_factory(alt_view, alt_p_lo, alt_p_hi, alt_edge)
981
1023
  if not _off_axis_emit(
982
1024
  dwg,
983
1025
  tier,
@@ -963,17 +963,40 @@ def lint_prismatic_coverage(
963
963
  unlocated += 1
964
964
  continue
965
965
  ps = pairs(view)
966
+ # A datum-starting pocket needs no redundant long-axis centre dimension: the
967
+ # coincident edge plus the length callout defines it. Ordinary inset pockets
968
+ # retain their established centre-location scheme. Keep critique on the same
969
+ # conditional target as the compiler.
970
+ target_world = {"x": x, "y": y, "z": z}
971
+ long_datum = float(getattr(bb.min, pocket.long_axis.upper()))
972
+ if abs(pocket.lo - long_datum) <= tol:
973
+ target_world[pocket.long_axis] = pocket.lo
966
974
  # Projection axes by principal view: plan=(x,y), front=(x,z), side=(y,z).
967
975
  coordinates = {
968
- "plan": ((x, centre.X, bb.min.X), (y, centre.Y, bb.min.Y)),
969
- "front": ((x, centre.X, bb.min.X), (z, centre.Z, bb.min.Z)),
970
- "side": ((y, centre.Y, bb.min.Y), (z, centre.Z, bb.min.Z)),
976
+ "plan": (
977
+ (target_world["x"], centre.X, bb.min.X),
978
+ (target_world["y"], centre.Y, bb.min.Y),
979
+ ),
980
+ "front": (
981
+ (target_world["x"], centre.X, bb.min.X),
982
+ (target_world["z"], centre.Z, bb.min.Z),
983
+ ),
984
+ "side": (
985
+ (target_world["y"], centre.Y, bb.min.Y),
986
+ (target_world["z"], centre.Z, bb.min.Z),
987
+ ),
971
988
  }[view]
972
989
  datum_page = dwg.at(view, bb.min.X, bb.min.Y, bb.min.Z)
973
- target_page = dwg.at(view, x, y, z)
990
+ target_page = dwg.at(
991
+ view,
992
+ target_world["x"],
993
+ target_world["y"],
994
+ target_world["z"],
995
+ )
974
996
  covered = []
975
- for axis, (coord, mid, _datum) in enumerate(coordinates):
997
+ for axis, (coord, mid, datum) in enumerate(coordinates):
976
998
  symmetric = abs(coord - mid) <= 1.0
999
+ datum_coincident = abs(coord - datum) <= tol
977
1000
  witnessed = _pair_covers(
978
1001
  ps,
979
1002
  axis,
@@ -981,7 +1004,7 @@ def lint_prismatic_coverage(
981
1004
  target_page[axis],
982
1005
  tol,
983
1006
  )
984
- covered.append(symmetric or witnessed)
1007
+ covered.append(symmetric or datum_coincident or witnessed)
985
1008
  if not all(covered):
986
1009
  unlocated += 1
987
1010
  if unlocated:
@@ -552,13 +552,17 @@ def plan_locations(model: PartModel) -> list[PlannedDimension]:
552
552
  # diagnostic to say why (Codex #996 r2).
553
553
  dropped.append((f, role, "edge-anchored; the edge conveys the position"))
554
554
  continue
555
- # A recognised pocket frame may be anchored at an opening corner;
556
- # location furniture defines the in-plane centre, which is expressed
557
- # explicitly by lo/hi and w_center for every principal orientation.
558
- centre = list(f.frame.origin)
559
- centre["xyz".index(f.long_axis)] = (f.lo + f.hi) / 2
560
- centre["xyz".index(f.width_axis)] = f.w_center
561
- ref = (centre[0], centre[1], centre[2])
555
+ # An ordinary pocket keeps its established in-plane centre location. When
556
+ # its long end already starts on the lower datum, however, that edge plus
557
+ # the length callout fully defines the long position: printing half the
558
+ # length as a datum-to-centre offset is redundant and opaque (a datum-starting
559
+ # 62.1 mm pocket otherwise acquires a seemingly arbitrary 31.1 mm mark).
560
+ ref_point = list(f.frame.origin)
561
+ long_index = "xyz".index(f.long_axis)
562
+ datum_coord = (dx, dy, dz)[long_index]
563
+ ref_point[long_index] = f.lo if abs(f.lo - datum_coord) <= 1e-6 else (f.lo + f.hi) / 2
564
+ ref_point["xyz".index(f.width_axis)] = f.w_center
565
+ ref = (ref_point[0], ref_point[1], ref_point[2])
562
566
  refs.append((ref, role, f))
563
567
  else:
564
568
  refs.append((f.frame.origin, role, f))
@@ -518,6 +518,22 @@ class TestFillet:
518
518
  assert len(names) == 1 # ONE grouped callout, not four
519
519
  assert dwg.get_annotation(names[0]).label == "4× R3"
520
520
 
521
+ def test_equal_radii_across_edge_axes_share_one_callout(self):
522
+ # A rounded end can expose equal-R edges on X, Y and Z. Repeating the same R
523
+ # note once per projection over-specifies one manufacturing instruction.
524
+ features = [
525
+ fillet(axis="x", radius=1, at=(-20, -15, -10)),
526
+ fillet(axis="x", radius=1, at=(20, -15, -10)),
527
+ fillet(axis="y", radius=1, at=(-20, -15, -10)),
528
+ fillet(axis="z", radius=1, at=(-20, -15, -10)),
529
+ fillet(axis="z", radius=1, at=(20, 15, -10)),
530
+ ]
531
+ dwg = build_drawing(Box(40, 30, 20), model=features, number="X")
532
+ names = [name for name in dwg.annotations() if name.startswith("m_fillet")]
533
+ assert len(names) == 1
534
+ assert dwg.get_annotation(names[0]).label == "5× R1"
535
+ assert len(dwg.measurement_keys(names[0])) == 5
536
+
521
537
  def test_needs_axis_radius_at(self):
522
538
  with pytest.raises(ValueError):
523
539
  fillet(radius=3) # no axis / at
@@ -110,6 +110,26 @@ def test_side_opening_pocket_gets_two_in_plane_location_dimensions():
110
110
  assert "pocket_not_located" not in drawing.lint_summary()["by_code"]
111
111
 
112
112
 
113
+ def test_datum_starting_side_pocket_does_not_print_half_its_length_as_a_position():
114
+ minimum = (Align.MIN, Align.CENTER, Align.MIN)
115
+ part = Box(13.55, 11, 80, align=minimum)
116
+ part -= Pos(12.61, -1, 0) * Box(
117
+ 0.94,
118
+ 2,
119
+ 62.13,
120
+ align=(Align.MIN, Align.MIN, Align.MIN),
121
+ )
122
+ drawing = build_drawing(part)
123
+ labels = {
124
+ drawing.get_annotation(name).label
125
+ for name in drawing.annotations()
126
+ if name.startswith("m_pocket")
127
+ }
128
+ assert "31.1" not in labels
129
+ assert "2 × 62.1 × 0.9 DEEP" in labels
130
+ assert "pocket_not_located" not in drawing.lint_summary()["by_code"]
131
+
132
+
113
133
  def test_unrelated_dimension_endpoint_does_not_locate_side_pocket():
114
134
  part = Box(60, 50, 40) - Pos(20, 8, 5) * Box(20, 16, 18)
115
135
  drawing = build_drawing(part)
@@ -1002,6 +1002,58 @@ def test_side_hole_z_dim_is_kept_not_dropped_under_policy_b():
1002
1002
  assert any(n.startswith("dim_loc_") and "_z" in n for n in names), "Z location dim was dropped"
1003
1003
 
1004
1004
 
1005
+ def test_two_cross_hole_heights_join_overall_ladder_without_crossing():
1006
+ """The GRM-01 class: two X bores plus a long side pocket and equal-R end rounds.
1007
+
1008
+ Side-view callout leaders block the natural height corridor. Both heights must route
1009
+ into the front corridor before its one shared solve, otherwise the first height can
1010
+ vanish and a late retry lands outside/crosses the 80 mm overall witness line.
1011
+ """
1012
+ from dataclasses import replace
1013
+
1014
+ from build123d import Align, Rot
1015
+
1016
+ from draftwright.model import fillet
1017
+
1018
+ part = Box(13.55, 11, 80, align=(Align.MIN, Align.CENTER, Align.MIN))
1019
+ part -= Pos(12.61, -1, 0) * Box(
1020
+ 0.94,
1021
+ 2,
1022
+ 62.13,
1023
+ align=(Align.MIN, Align.MIN, Align.MIN),
1024
+ )
1025
+ for z, radius in ((66, 4), (75.5, 1.25)):
1026
+ part -= (
1027
+ Pos(0, 0, z)
1028
+ * Rot(0, 90, 0)
1029
+ * Cylinder(
1030
+ radius,
1031
+ 13.55,
1032
+ align=(Align.CENTER, Align.CENTER, Align.MIN),
1033
+ )
1034
+ )
1035
+ detected = build_drawing(part, auto_dims=False).model()
1036
+ rounded = [
1037
+ fillet(axis="x", radius=1, at=(0, -5.5, 0)),
1038
+ fillet(axis="x", radius=1, at=(13.55, -5.5, 0)),
1039
+ fillet(axis="y", radius=1, at=(0, -5.5, 0)),
1040
+ fillet(axis="z", radius=1, at=(0, -5.5, 0)),
1041
+ fillet(axis="z", radius=1, at=(13.55, 5.5, 0)),
1042
+ ]
1043
+ drawing = build_drawing(
1044
+ part,
1045
+ model=replace(detected, features=[*detected.features, *rounded]),
1046
+ page="A4",
1047
+ )
1048
+
1049
+ names = ("dim_loc_front_z6600", "dim_loc_front_z7550", "dim_height")
1050
+ assert [drawing.get_annotation(name).label for name in names] == ["66", "75.5", "80"]
1051
+ # Inner-to-outer line order: both feature locations inside the overall dimension.
1052
+ xmax = [drawing.get_annotation(name).bounding_box().max.X for name in names]
1053
+ assert xmax[0] < xmax[1] < xmax[2]
1054
+ assert drawing.lint() == []
1055
+
1056
+
1005
1057
  # --- unified above-corridor solve (ADR 0009 end state, #345/#346) -----------
1006
1058
 
1007
1059
 
File without changes
File without changes
File without changes
File without changes