draftwright 0.4.10__tar.gz → 0.4.11__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (272) hide show
  1. {draftwright-0.4.10 → draftwright-0.4.11}/CHANGELOG.md +30 -0
  2. {draftwright-0.4.10 → draftwright-0.4.11}/PKG-INFO +2 -2
  3. {draftwright-0.4.10 → draftwright-0.4.11}/docs/reference/recogniser-capabilities.md +5 -7
  4. {draftwright-0.4.10 → draftwright-0.4.11}/pyproject.toml +2 -2
  5. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/_core.py +58 -11
  6. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/annotations/_common.py +19 -1
  7. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/annotations/from_model.py +2 -1
  8. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/annotations/holes.py +65 -3
  9. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/builder.py +335 -30
  10. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/drawing.py +6 -3
  11. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/linting/coverage.py +81 -14
  12. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/linting/hole_coverage.py +6 -7
  13. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/recogniser_contract.py +16 -50
  14. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/sheet_emit.py +71 -8
  15. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/view_plan.py +20 -0
  16. {draftwright-0.4.10 → draftwright-0.4.11}/tests/conftest.py +0 -2
  17. draftwright-0.4.11/tests/fixtures/grm04_drive_plate.step +1001 -0
  18. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_add_dimension.py +27 -1
  19. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_adr0018_arrangement_gate.py +6 -3
  20. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_external_recognition_boundary.py +5 -11
  21. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_1143_hole_completeness.py +5 -2
  22. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_1146_scale_completeness.py +29 -1
  23. draftwright-0.4.11/tests/test_issue_1155_grm04_sheet_use.py +44 -0
  24. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_1187_unroutable_leaders.py +38 -1
  25. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_1202_observed_drawing_consumer.py +10 -6
  26. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_1217_the_facility_is_shared.py +3 -35
  27. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_1219_location_claims_its_own_axis.py +7 -10
  28. draftwright-0.4.11/tests/test_issue_443_grm03_axial_coverage.py +59 -0
  29. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_make_drawing.py +49 -6
  30. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_note_verb.py +12 -0
  31. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_part_model.py +15 -23
  32. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_recogniser_adoption.py +3 -7
  33. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_recogniser_capabilities.py +15 -50
  34. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_sheet_notes.py +28 -0
  35. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_sheet_tables.py +28 -0
  36. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_solve_trace.py +31 -0
  37. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_workflows.py +39 -0
  38. draftwright-0.4.10/tests/test_cross_repository_delivery_protocol.py +0 -27
  39. draftwright-0.4.10/tests/test_two_repository_workflow.py +0 -343
  40. {draftwright-0.4.10 → draftwright-0.4.11}/.gitignore +0 -0
  41. {draftwright-0.4.10 → draftwright-0.4.11}/LICENSE +0 -0
  42. {draftwright-0.4.10 → draftwright-0.4.11}/README.md +0 -0
  43. {draftwright-0.4.10 → draftwright-0.4.11}/docs/adr/README.md +0 -0
  44. {draftwright-0.4.10 → draftwright-0.4.11}/docs/reference/sheet.md +0 -0
  45. {draftwright-0.4.10 → draftwright-0.4.11}/docs/research/1062-repeating-radial-profile-evidence.md +0 -0
  46. {draftwright-0.4.10 → draftwright-0.4.11}/skills/SKILL.md +0 -0
  47. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/__init__.py +0 -0
  48. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/_geometry.py +0 -0
  49. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/_pmi_part21.py +0 -0
  50. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/_warnings.py +0 -0
  51. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/analysis.py +0 -0
  52. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/annotate.py +0 -0
  53. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/annotations/__init__.py +0 -0
  54. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/annotations/balloons.py +0 -0
  55. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/annotations/gears.py +0 -0
  56. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/annotations/leaders.py +0 -0
  57. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/annotations/orchestrator.py +0 -0
  58. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/annotations/sections.py +0 -0
  59. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/audit.py +0 -0
  60. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/cli.py +0 -0
  61. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/compose.py +0 -0
  62. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/evaluation/__init__.py +0 -0
  63. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/evaluation/step_analysis.py +0 -0
  64. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/export.py +0 -0
  65. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/fits.py +0 -0
  66. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/fonts/IBMPlexMono-Regular.ttf +0 -0
  67. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/fonts/IBMPlexSansCondensed-Regular.ttf +0 -0
  68. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/fonts/LICENSE-IBMPlexMono-OFL.txt +0 -0
  69. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/fonts/LICENSE-IBMPlexSansCondensed-OFL.txt +0 -0
  70. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/fonts/__init__.py +0 -0
  71. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/intents.py +0 -0
  72. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/layout.py +0 -0
  73. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/linting/__init__.py +0 -0
  74. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/linting/channel_coverage.py +0 -0
  75. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/linting/evidence.py +0 -0
  76. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/linting/flat_coverage.py +0 -0
  77. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/linting/gear_coverage.py +0 -0
  78. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/linting/issues.py +0 -0
  79. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/linting/pmi_coverage.py +0 -0
  80. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/linting/polygonal_stock_coverage.py +0 -0
  81. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/linting/profiled_bore_coverage.py +0 -0
  82. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/linting/quality.py +0 -0
  83. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/linting/slot_coverage.py +0 -0
  84. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/linting/structural.py +0 -0
  85. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/linting/suggest.py +0 -0
  86. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/make_drawing.py +0 -0
  87. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/model/__init__.py +0 -0
  88. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/model/callout.py +0 -0
  89. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/model/compiled.py +0 -0
  90. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/model/declare.py +0 -0
  91. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/model/detect.py +0 -0
  92. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/model/ir.py +0 -0
  93. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/model/planner.py +0 -0
  94. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/model/pmi_lowering.py +0 -0
  95. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/pmi.py +0 -0
  96. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/projection.py +0 -0
  97. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/py.typed +0 -0
  98. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/recognition/__init__.py +0 -0
  99. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/recognition_cache.py +0 -0
  100. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/registry.py +0 -0
  101. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/repair.py +0 -0
  102. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/score.py +0 -0
  103. {draftwright-0.4.10 → draftwright-0.4.11}/src/draftwright/sheet.py +0 -0
  104. {draftwright-0.4.10 → draftwright-0.4.11}/tests/_kernel.py +0 -0
  105. {draftwright-0.4.10 → draftwright-0.4.11}/tests/_layout_sig.py +0 -0
  106. {draftwright-0.4.10 → draftwright-0.4.11}/tests/fixtures/evaluation/README.md +0 -0
  107. {draftwright-0.4.10 → draftwright-0.4.11}/tests/fixtures/evaluation/ambiguous-open-semicircle.step +0 -0
  108. {draftwright-0.4.10 → draftwright-0.4.11}/tests/fixtures/evaluation/blind-hole.step +0 -0
  109. {draftwright-0.4.10 → draftwright-0.4.11}/tests/fixtures/evaluation/corpus-v1.json +0 -0
  110. {draftwright-0.4.10 → draftwright-0.4.11}/tests/fixtures/evaluation/plain-block.step +0 -0
  111. {draftwright-0.4.10 → draftwright-0.4.11}/tests/fixtures/evaluation/topology-a.step +0 -0
  112. {draftwright-0.4.10 → draftwright-0.4.11}/tests/fixtures/evaluation/topology-b.step +0 -0
  113. {draftwright-0.4.10 → draftwright-0.4.11}/tests/fixtures/grm03_thumbwheel_drive_screw.step +0 -0
  114. {draftwright-0.4.10 → draftwright-0.4.11}/tests/fixtures/if_step_flat_across_cylinder.step +0 -0
  115. {draftwright-0.4.10 → draftwright-0.4.11}/tests/fixtures/issue_1058_wheel_rh.step +0 -0
  116. {draftwright-0.4.10 → draftwright-0.4.11}/tests/fixtures/issue_909_basic_part_design_017_body.step +0 -0
  117. {draftwright-0.4.10 → draftwright-0.4.11}/tests/fixtures/issue_915_case_study_2.step +0 -0
  118. {draftwright-0.4.10 → draftwright-0.4.11}/tests/fixtures/nist_ctc_01_asme1_ap203.stp +0 -0
  119. {draftwright-0.4.10 → draftwright-0.4.11}/tests/fixtures/nist_ctc_01_asme1_ap242.stp +0 -0
  120. {draftwright-0.4.10 → draftwright-0.4.11}/tests/fixtures/nist_ctc_02_asme1_ap203.stp +0 -0
  121. {draftwright-0.4.10 → draftwright-0.4.11}/tests/fixtures/nist_ctc_02_asme1_ap242.stp +0 -0
  122. {draftwright-0.4.10 → draftwright-0.4.11}/tests/fixtures/nist_ctc_03_asme1_ap203.stp +0 -0
  123. {draftwright-0.4.10 → draftwright-0.4.11}/tests/fixtures/nist_ctc_03_asme1_ap242.stp +0 -0
  124. {draftwright-0.4.10 → draftwright-0.4.11}/tests/fixtures/nist_ctc_04_asme1_ap203.stp +0 -0
  125. {draftwright-0.4.10 → draftwright-0.4.11}/tests/fixtures/nist_ctc_04_asme1_ap242.stp +0 -0
  126. {draftwright-0.4.10 → draftwright-0.4.11}/tests/fixtures/nist_ctc_05_asme1_ap203.stp +0 -0
  127. {draftwright-0.4.10 → draftwright-0.4.11}/tests/fixtures/nist_ctc_05_asme1_ap242.stp +0 -0
  128. {draftwright-0.4.10 → draftwright-0.4.11}/tests/fixtures/tuner_jig_blind_obround_pockets.step +0 -0
  129. {draftwright-0.4.10 → draftwright-0.4.11}/tests/refactor_golden/bracket_section.json +0 -0
  130. {draftwright-0.4.10 → draftwright-0.4.11}/tests/refactor_golden/centered_rebate.json +0 -0
  131. {draftwright-0.4.10 → draftwright-0.4.11}/tests/refactor_golden/chamfered.json +0 -0
  132. {draftwright-0.4.10 → draftwright-0.4.11}/tests/refactor_golden/filleted.json +0 -0
  133. {draftwright-0.4.10 → draftwright-0.4.11}/tests/refactor_golden/flange_dense.json +0 -0
  134. {draftwright-0.4.10 → draftwright-0.4.11}/tests/refactor_golden/grid_plate.json +0 -0
  135. {draftwright-0.4.10 → draftwright-0.4.11}/tests/refactor_golden/grooved_shaft.json +0 -0
  136. {draftwright-0.4.10 → draftwright-0.4.11}/tests/refactor_golden/hex_bar.json +0 -0
  137. {draftwright-0.4.10 → draftwright-0.4.11}/tests/refactor_golden/holed_slot.json +0 -0
  138. {draftwright-0.4.10 → draftwright-0.4.11}/tests/refactor_golden/pocketed.json +0 -0
  139. {draftwright-0.4.10 → draftwright-0.4.11}/tests/refactor_golden/prismatic_ladder.json +0 -0
  140. {draftwright-0.4.10 → draftwright-0.4.11}/tests/refactor_golden/scattered_plate.json +0 -0
  141. {draftwright-0.4.10 → draftwright-0.4.11}/tests/refactor_golden/side_drilled.json +0 -0
  142. {draftwright-0.4.10 → draftwright-0.4.11}/tests/refactor_golden/turned_stepped.json +0 -0
  143. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_adr0018_view_routing.py +0 -0
  144. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_adr0018_view_selection.py +0 -0
  145. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_agents_guide.py +0 -0
  146. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_api_docs.py +0 -0
  147. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_architecture_docs.py +0 -0
  148. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_audit_differential.py +0 -0
  149. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_balloon_ring_standoff.py +0 -0
  150. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_carve_free_position_callers.py +0 -0
  151. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_compiled_plan_boundary.py +0 -0
  152. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_counting_calls.py +0 -0
  153. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_declare.py +0 -0
  154. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_declared_recognition_gate.py +0 -0
  155. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_dense_sheet_canary.py +0 -0
  156. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_deprecation_dates.py +0 -0
  157. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_detect_once.py +0 -0
  158. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_detect_registry.py +0 -0
  159. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_dimension_role_vocabulary.py +0 -0
  160. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_drawing_encapsulation.py +0 -0
  161. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_e2e_slice.py +0 -0
  162. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_e2e_standards.py +0 -0
  163. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_export_dxf_curves.py +0 -0
  164. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_export_dxf_zoom.py +0 -0
  165. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_fillets_adjacency.py +0 -0
  166. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_fits.py +0 -0
  167. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_flat_completeness.py +0 -0
  168. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_flat_stock_identity.py +0 -0
  169. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_flat_stock_opposition.py +0 -0
  170. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_gdt_placement.py +0 -0
  171. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_grid_lattice_convention.py +0 -0
  172. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_import_boundaries.py +0 -0
  173. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_iso_nts_caption_placement.py +0 -0
  174. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_1000_envelope_reuse.py +0 -0
  175. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_1058_wheel_profile.py +0 -0
  176. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_1073_cross_body_patterns.py +0 -0
  177. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_1082_polygonal_stock.py +0 -0
  178. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_1086_declared_gears.py +0 -0
  179. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_1116_ap242_hole_tolerance_lowering.py +0 -0
  180. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_1130_view_planning_evidence.py +0 -0
  181. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_1142_hole_leader_labels.py +0 -0
  182. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_1144_transactional_hole_table.py +0 -0
  183. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_1147_legibility_pair_aggregation.py +0 -0
  184. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_1153_contradictory_dimensions.py +0 -0
  185. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_1154_one_owner_per_measurement.py +0 -0
  186. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_1166_cross_pass_feature_leaders.py +0 -0
  187. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_1176_quality_fidelity.py +0 -0
  188. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_1177_angular_dimensions.py +0 -0
  189. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_1188_per_view_assignment.py +0 -0
  190. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_1190_section_decision.py +0 -0
  191. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_1196_deterministic_view_names.py +0 -0
  192. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_1204_multiscale_view_issues.py +0 -0
  193. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_1215_envelope_tolerance.py +0 -0
  194. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_1215_no_approved_tolerance_is_dropped.py +0 -0
  195. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_1216_callout_reservation_measures_ink.py +0 -0
  196. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_1216_pairwise_across_scale_groups.py +0 -0
  197. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_1217_claimed_representations.py +0 -0
  198. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_1229_ledger_member_keying.py +0 -0
  199. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_1240_iso_above_strip_visibility.py +0 -0
  200. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_1254_turned_chamfers.py +0 -0
  201. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_1260_view_constraints.py +0 -0
  202. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_1276_turned_leader_targets.py +0 -0
  203. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_1281_turned_fillets.py +0 -0
  204. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_367_leader_ink.py +0 -0
  205. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_676_polygonal_boss.py +0 -0
  206. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_676_polygonal_boss_declare.py +0 -0
  207. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_740_leader_assignment.py +0 -0
  208. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_798_floor_cardinality.py +0 -0
  209. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_798_material_field.py +0 -0
  210. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_798_silhouette_lint.py +0 -0
  211. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_885_prismatic_coverage.py +0 -0
  212. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_909_sloped_profile.py +0 -0
  213. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_915_dense_case.py +0 -0
  214. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_916_pocket_leader.py +0 -0
  215. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_917_open_channel.py +0 -0
  216. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_924_zero_length_shoulders.py +0 -0
  217. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_955_height_contingency.py +0 -0
  218. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_issue_958_cross_solid_recognition.py +0 -0
  219. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_label_provenance.py +0 -0
  220. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_layout.py +0 -0
  221. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_layout_cleanliness.py +0 -0
  222. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_layout_hypothesis.py +0 -0
  223. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_layout_property.py +0 -0
  224. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_leader_footprint.py +0 -0
  225. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_lint_box_cache.py +0 -0
  226. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_lint_reconciliation.py +0 -0
  227. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_lint_structural.py +0 -0
  228. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_linting.py +0 -0
  229. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_location_vocabulary.py +0 -0
  230. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_object_aspects.py +0 -0
  231. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_occupancy_boxes.py +0 -0
  232. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_parameter_id.py +0 -0
  233. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_pitch_dim_footprint.py +0 -0
  234. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_pmi.py +0 -0
  235. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_pmi_datum_lowering.py +0 -0
  236. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_pmi_gtol_lowering.py +0 -0
  237. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_pmi_part21.py +0 -0
  238. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_pocket_pattern.py +0 -0
  239. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_pocket_pattern_recognition.py +0 -0
  240. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_principal_profile_classifier.py +0 -0
  241. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_private_test_attr_reads.py +0 -0
  242. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_private_test_imports.py +0 -0
  243. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_quality_components.py +0 -0
  244. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_recogniser_contract.py +0 -0
  245. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_recognition.py +0 -0
  246. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_recognition_manifest.py +0 -0
  247. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_recognition_result.py +0 -0
  248. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_refactor_golden.py +0 -0
  249. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_registry.py +0 -0
  250. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_render_seam.py +0 -0
  251. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_repack_geometry_seam.py +0 -0
  252. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_score.py +0 -0
  253. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_script_detail_parity.py +0 -0
  254. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_shared_box_memo.py +0 -0
  255. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_sheet_emit.py +0 -0
  256. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_sheet_gdt.py +0 -0
  257. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_sheet_identity_invariant.py +0 -0
  258. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_sheet_of.py +0 -0
  259. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_sheet_section.py +0 -0
  260. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_slanted_blind_step.py +0 -0
  261. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_slot_completeness.py +0 -0
  262. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_slot_pattern.py +0 -0
  263. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_slot_pattern_recognition.py +0 -0
  264. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_soft_deprecation.py +0 -0
  265. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_step_analysis_evaluation.py +0 -0
  266. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_strip_layout.py +0 -0
  267. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_suppression_ledger.py +0 -0
  268. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_suppression_marks.py +0 -0
  269. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_tolerances.py +0 -0
  270. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_turned_steps.py +0 -0
  271. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_view_plan.py +0 -0
  272. {draftwright-0.4.10 → draftwright-0.4.11}/tests/test_witness_label_reconciliation.py +0 -0
@@ -1,5 +1,35 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ### Changed
6
+
7
+ - Recogniser integration now uses only the exact `b123d-recognisers` dependency in
8
+ `pyproject.toml` and `uv.lock`. The separate release-evidence record, dependency-bump workflow,
9
+ candidate-checkout harness, and release-time capability-version rewrites have been removed;
10
+ ordinary compatibility tests validate the installed pinned package.
11
+ - Automatic composition now measures the next larger preferred scale when a conservative
12
+ crowded-detail reservation leaves a sparse sheet. A candidate wins only on the same paper
13
+ size, without the recovery detail, structural errors, or required-placement loss. GRM-04 now
14
+ uses A4 at 5:1 instead of 2:1, re-homes its shared side-hole Y location into the plan-view
15
+ corridor, and retains both the Ø2.4 diameter and location requirements (#1155).
16
+ - Closed the remaining dense-sheet leader cases as deliberate Policy-B behavior: exhaustive
17
+ route sweeps prove there is no route clear of both material and committed sheet ink, so the
18
+ required callout remains with an explicit crossing diagnostic instead of being dropped. A
19
+ fast synthetic release gate complements the retained exhaustive corpus characterization
20
+ (#1187).
21
+
22
+ ### Fixed
23
+
24
+ - Notes and generic table cells now render U+2300 `⌀` with the established supported drafting
25
+ glyph instead of IBM Plex Mono's missing-glyph square. The substitution happens at the one
26
+ shared measurement/render seam, so source rows remain unchanged and table sizing stays exact
27
+ (#1275).
28
+ - Automatic turned-part composition now gives true shoulder coverage priority over optional
29
+ pictorial content. GRM-03 replans to 5:1 and renders its 0.5, 2, 3, and 18 mm axial lengths;
30
+ `axial_length_missing` disappears because the dimensions are present, not because the
31
+ diagnostic was suppressed (#443).
32
+
3
33
  ## v0.4.10 — 2026-08-22
4
34
 
5
35
  **A view-set-aware planning and turned-edge-treatment patch.** Authored view constraints now
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: draftwright
3
- Version: 0.4.10
3
+ Version: 0.4.11
4
4
  Summary: Automated technical-drawing generation for build123d
5
5
  Project-URL: Homepage, https://github.com/pzfreo/draftwright
6
6
  Project-URL: Repository, https://github.com/pzfreo/draftwright
@@ -684,7 +684,7 @@ Classifier: Programming Language :: Python :: 3.14
684
684
  Classifier: Topic :: Scientific/Engineering
685
685
  Requires-Python: <3.15,>=3.10
686
686
  Requires-Dist: antlr4-python3-runtime<4.10
687
- Requires-Dist: b123d-recognisers==0.3.0
687
+ Requires-Dist: b123d-recognisers==0.3.1
688
688
  Requires-Dist: build123d-drafting-helpers>=0.14.1
689
689
  Requires-Dist: build123d<0.11,>=0.9.0; python_full_version < '3.13'
690
690
  Requires-Dist: build123d<0.12,>=0.11; python_full_version >= '3.13'
@@ -49,13 +49,11 @@ Validation fails closed and names the family and boundary whenever possible:
49
49
 
50
50
  ## Adding or changing a recogniser
51
51
 
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.
52
+ For an additive field that increments an existing record schema, update the relevant Draftwright
53
+ adapter and declaration when advancing the exact `b123d-recognisers` dependency pin. Tests must
54
+ prove the installed schema is accepted while later schemas still fail. The package version belongs
55
+ only in `pyproject.toml` and `uv.lock`; the capability declaration derives runtime identities from
56
+ installed package metadata.
59
57
 
60
58
  For each applicable stage, add the adapter, declaration, emitted-Sheet round trip, drawing behavior,
61
59
  and completeness evidence; for an inapplicable or deferred stage, state why and link its tracking
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "draftwright"
7
- version = "0.4.10"
7
+ version = "0.4.11"
8
8
  description = "Automated technical-drawing generation for build123d"
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
@@ -18,7 +18,7 @@ dependencies = [
18
18
  "build123d>=0.9.0,<0.11 ; python_full_version < '3.13'",
19
19
  "build123d>=0.11,<0.12 ; python_full_version >= '3.13'",
20
20
  "build123d-drafting-helpers>=0.14.1",
21
- "b123d-recognisers==0.3.0",
21
+ "b123d-recognisers==0.3.1",
22
22
  "numpy>=1.24",
23
23
  # PNG raster export (SVG→PDF→PNG). Both permissively licensed (Pillow HPND; pypdfium2
24
24
  # Apache-2.0 wrapping Google PDFium BSD-3) and ship pre-built wheels with NO native system
@@ -57,6 +57,35 @@ from draftwright.layout import _greedy_strip_1d, _solve_strip_1d
57
57
  _log = logging.getLogger(__name__)
58
58
 
59
59
 
60
+ def _decode_hole_location_fact(fact):
61
+ """Decode a current or legacy ``covers_hole_locations`` fact.
62
+
63
+ Current facts carry ``(feature, parameter, point)``. Legacy/external facts
64
+ carry ``(measurement, point)`` and derive ownership from the measurement.
65
+ Invalid or incomplete riders return ``None`` so consumers can retain their
66
+ geometric fallback instead of treating malformed metadata as authoritative.
67
+ """
68
+ try:
69
+ size = len(fact)
70
+ except TypeError:
71
+ return None
72
+ if size == 3:
73
+ feature, parameter, point = fact
74
+ elif size == 2:
75
+ measurement, point = fact
76
+ feature = getattr(measurement, "feature", None)
77
+ parameter = getattr(measurement, "parameter", None)
78
+ else:
79
+ return None
80
+ if feature is None or not isinstance(parameter, str):
81
+ return None
82
+ try:
83
+ HoleRef.of(point)
84
+ except (AttributeError, TypeError, ValueError):
85
+ return None
86
+ return feature, parameter, point
87
+
88
+
60
89
  def place_annotation(registry, items, obj, name=None, view=None, feature=None, measurement=None):
61
90
  """The annotation-placement primitive (#817): register *obj* under *name* — replacing any
62
91
  prior object of that name (dropped from the render list *items*) so a name maps to one
@@ -168,6 +197,19 @@ def _wrap_rows(header, data, ncols):
168
197
  return wide
169
198
 
170
199
 
200
+ def _font_safe_text(value) -> str:
201
+ """Return the font-safe spelling used to measure and draw user text.
202
+
203
+ IBM Plex Mono does not contain U+2300 DIAMETER SIGN, so FreeType substitutes
204
+ its missing-glyph box. The drafting surface already uses U+00F8 LATIN SMALL
205
+ LETTER O WITH STROKE for diameter labels; use that established glyph at the
206
+ shared render seams as well. Keeping the substitution here makes free notes,
207
+ declared notes, title blocks, BOMs, revision tables, and hole tables measure
208
+ exactly the text they render while preserving the caller's semantic source data.
209
+ """
210
+ return str(value).replace("⌀", "ø")
211
+
212
+
171
213
  def _table_metrics(rows, font_size, pad_around_text, block_cols=None):
172
214
  """The sizing model of a data table: per-column left/right edges (page-mm,
173
215
  block gaps inserted), total width/height, row height and effective block
@@ -186,7 +228,11 @@ def _table_metrics(rows, font_size, pad_around_text, block_cols=None):
186
228
  bc = block_cols if (block_cols and ncol % block_cols == 0 and block_cols < ncol) else ncol
187
229
  block_gap = 3 * pad # whitespace between side-by-side blocks
188
230
  col_w = [
189
- max(max(_text_width(str(r[c]), fs) for r in rows) + 2 * pad, fs * 2.5) for c in range(ncol)
231
+ max(
232
+ max(_text_width(_font_safe_text(r[c]), fs) for r in rows) + 2 * pad,
233
+ fs * 2.5,
234
+ )
235
+ for c in range(ncol)
190
236
  ]
191
237
  # Per-column left/right edges, inserting block_gap before each new block.
192
238
  lefts, rights, cursor = [], [], 0.0
@@ -232,11 +278,12 @@ def _build_table(rows, draft, block_cols=None):
232
278
  for ri, row in enumerate(rows): # rows[0] (header) sits at the top
233
279
  cy = total_h - (ri + 0.5) * row_h
234
280
  for ci, cell in enumerate(row):
235
- if not str(cell):
281
+ display = _font_safe_text(cell)
282
+ if not display:
236
283
  continue
237
284
  cx = (lefts[ci] + rights[ci]) / 2
238
285
  text = Text(
239
- txt=str(cell),
286
+ txt=display,
240
287
  font_size=fs,
241
288
  font_path=PLEX_MONO,
242
289
  align=(Align.CENTER, Align.CENTER),
@@ -1158,15 +1205,15 @@ def _make_title_block(dwg, a: Analysis):
1158
1205
  it, last) and :func:`_title_block_box` (which measures its footprint for GD&T avoidance, #481)
1159
1206
  so the two never drift."""
1160
1207
  tb = TitleBlock(
1161
- a.title,
1162
- a.number,
1208
+ _font_safe_text(a.title),
1209
+ _font_safe_text(a.number),
1163
1210
  scale=format_drawing_scale(a.SCALE),
1164
- general_tolerance=a.tolerance,
1165
- designed_by=_attribution_author(a.drawn_by),
1166
- material=a.material,
1167
- date=a.date,
1168
- revision=a.revision,
1169
- legal_owner=a.company,
1211
+ general_tolerance=_font_safe_text(a.tolerance),
1212
+ designed_by=_font_safe_text(_attribution_author(a.drawn_by)),
1213
+ material=_font_safe_text(a.material),
1214
+ date=_font_safe_text(a.date),
1215
+ revision=_font_safe_text(a.revision),
1216
+ legal_owner=_font_safe_text(a.company),
1170
1217
  width=a.TB_W,
1171
1218
  # Title block renders in condensed sans (the tight ISO 7200 cells), a
1172
1219
  # different face from the monospace dimensions — so it carries its own
@@ -20,6 +20,7 @@ from build123d_drafting.helpers import DEFAULT_FONT_PATH, Dimension, Note, SafeD
20
20
 
21
21
  from draftwright._core import ( # noqa: F401 — _anno_box re-exported (#700)
22
22
  _anno_box,
23
+ _decode_hole_location_fact,
23
24
  _text_size,
24
25
  place_annotation,
25
26
  )
@@ -129,7 +130,24 @@ def _annotation_hole_features(registry, name, annotation) -> frozenset:
129
130
  if getattr(feature, "kind", None) in {"hole", "pattern"}:
130
131
  features.add(feature)
131
132
  for fact in getattr(annotation, "covers_hole_locations", ()):
132
- feature = fact[0] if len(fact) == 3 else getattr(fact[0], "feature", None)
133
+ decoded = _decode_hole_location_fact(fact)
134
+ if decoded is not None:
135
+ feature = decoded[0]
136
+ else:
137
+ # Replacement ownership is a broader question than whether a fact can
138
+ # prove axis-specific coverage. Legacy/external riders may carry only
139
+ # ``(measurement, point)``; retain that measurement's semantic owner
140
+ # even when it has no parameter and the strict decoder rightly refuses
141
+ # to treat it as location evidence.
142
+ try:
143
+ candidate = fact[0]
144
+ except (IndexError, TypeError):
145
+ continue
146
+ feature = (
147
+ candidate
148
+ if getattr(candidate, "kind", None) in {"hole", "pattern"}
149
+ else getattr(candidate, "feature", None)
150
+ )
133
151
  if getattr(feature, "kind", None) in {"hole", "pattern"}:
134
152
  features.add(feature)
135
153
  for feature, _requirement, _count in getattr(
@@ -52,6 +52,7 @@ from draftwright._core import (
52
52
  _dim,
53
53
  _first_free_index,
54
54
  _fmt,
55
+ _font_safe_text,
55
56
  _greedy_strip_ys,
56
57
  _iso_bbox,
57
58
  _legible_locations,
@@ -5685,7 +5686,7 @@ def _gdt_glyph(item, draft):
5685
5686
  if item.kind == "datum_ref":
5686
5687
  return DatumFeature(item.letter, draft=draft)
5687
5688
  if item.kind == "note": # free-text manufacturing note (#488) — a single-line text glyph
5688
- return TextBlock([item.text], position=(0.0, 0.0), draft=draft)
5689
+ return TextBlock([_font_safe_text(item.text)], position=(0.0, 0.0), draft=draft)
5689
5690
  return SurfaceFinish(item.ra, position=(0.0, 0.0), draft=draft)
5690
5691
 
5691
5692
 
@@ -816,6 +816,7 @@ def _locate_across(dwg, ctx, a: Analysis, off):
816
816
  _locate_off_axis_holes (#638)."""
817
817
  draft = dwg.draft
818
818
  SX, SZ = a.proj.side_x, a.proj.side_z
819
+ PX, PY = a.proj.plan_x, a.proj.plan_y
819
820
  dy, dz = a.bb.min.Y, a.bb.min.Z
820
821
  tier = draft.font_size + 2 * draft.pad_around_text
821
822
  yw = SZ(dz) - _WITNESS_LIFT_MM
@@ -825,6 +826,7 @@ def _locate_across(dwg, ctx, a: Analysis, off):
825
826
  loc_by_name: dict = {} # dim name -> contributing hole locations (for provenance)
826
827
  mids_by_name: dict = {}
827
828
  coverage_by_name: dict = {}
829
+ plan_alternates: dict = {}
828
830
  for h in (h for h in off if h.axis == "x"):
829
831
  # The VALUE is the approved entry's; `dy` survives only as the witness anchor.
830
832
  entry = h.approved.get("y")
@@ -854,8 +856,70 @@ def _locate_across(dwg, ctx, a: Analysis, off):
854
856
  ),
855
857
  )
856
858
  )
859
+ # #1155: the same Y ordinate reads vertically in plan. Keep the
860
+ # side-view location as the natural first choice, but retain this
861
+ # requirement-driven alternate for a full below-side corridor (GRM-04
862
+ # at 5:1 shares that short strip with the overall depth). This is a
863
+ # view reassignment through the shared strip solver, not a raw position.
864
+ plan_edge = PX(a.bb.max.X)
865
+ plan_lo = (plan_edge, PY(dy), 0)
866
+ plan_hi = (plan_edge, PY(h.location[1]), 0)
867
+ alt_name = f"dim_loc_plan_y{round(yo * 100)}"
868
+ plan_alternates[name] = (
869
+ alt_name,
870
+ lambda pos, pl=plan_lo, ph=plan_hi, e=plan_edge, lb=entry.value_text, nm=name: (
871
+ _with_hole_location_coverage(
872
+ _dim(pl, ph, "right", pos - e, draft, label=lb),
873
+ coverage_by_name[nm],
874
+ )
875
+ ),
876
+ )
857
877
  feats = {nm: _off_axis_owner(ctx, locs) for nm, locs in loc_by_name.items()}
858
878
  measurements = {name: tuple(ids) for name, ids in mids_by_name.items()}
879
+
880
+ def _fallback(name):
881
+ # Every queued side candidate is created in the same block as its plan
882
+ # alternate above; a missing key is an internal invariant violation.
883
+ alt = plan_alternates[name]
884
+ alt_name = alt[0]
885
+
886
+ def _retry():
887
+ # A fallback is never allowed to create an annotation in a view the
888
+ # resolved plan omitted. It also runs only after every shared corridor
889
+ # has drained, so its solitary carve cannot preempt a later candidate.
890
+ if "plan" not in dwg.views:
891
+ _off_axis_drop(
892
+ dwg,
893
+ "y",
894
+ "side",
895
+ ctx=ctx,
896
+ measurement=measurements.get(name, ()),
897
+ )
898
+ return
899
+ if not _off_axis_emit(
900
+ dwg,
901
+ tier,
902
+ a.pv_zones.right,
903
+ "plan",
904
+ "x",
905
+ [alt],
906
+ force=False,
907
+ features={alt_name: feats.get(name)},
908
+ measurements={alt_name: measurements.get(name, ())},
909
+ ctx=ctx,
910
+ trace=ctx.trace,
911
+ ):
912
+ return
913
+ _off_axis_drop(
914
+ dwg,
915
+ "y",
916
+ "side",
917
+ ctx=ctx,
918
+ measurement=measurements.get(name, ()),
919
+ )
920
+
921
+ ctx.post_drain.append(_retry)
922
+
859
923
  _off_axis_queue(
860
924
  dwg,
861
925
  ctx,
@@ -867,9 +931,7 @@ def _locate_across(dwg, ctx, a: Analysis, off):
867
931
  cands,
868
932
  features=feats,
869
933
  measurements=measurements,
870
- on_drop=lambda nm: _off_axis_drop(
871
- dwg, "y", "side", ctx=ctx, measurement=measurements.get(nm, ())
872
- ),
934
+ on_drop=_fallback,
873
935
  order_key=lambda nm, _i: order_y.get(nm, _i),
874
936
  )
875
937