draftwright 0.4.4__tar.gz → 0.4.6__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.
- {draftwright-0.4.4 → draftwright-0.4.6}/CHANGELOG.md +39 -1
- {draftwright-0.4.4 → draftwright-0.4.6}/PKG-INFO +9 -5
- {draftwright-0.4.4 → draftwright-0.4.6}/README.md +7 -4
- {draftwright-0.4.4 → draftwright-0.4.6}/docs/adr/README.md +3 -3
- {draftwright-0.4.4 → draftwright-0.4.6}/pyproject.toml +3 -1
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/_core.py +2 -1
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/_geometry.py +88 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/analysis.py +8 -8
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/annotations/from_model.py +20 -14
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/annotations/holes.py +60 -9
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/drawing.py +15 -6
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/linting/channel_coverage.py +2 -1
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/linting/coverage.py +44 -21
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/linting/flat_coverage.py +2 -1
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/linting/hole_coverage.py +2 -1
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/linting/polygonal_stock_coverage.py +2 -1
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/linting/profiled_bore_coverage.py +2 -1
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/linting/slot_coverage.py +2 -1
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/model/compiled.py +8 -1
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/model/declare.py +8 -9
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/model/detect.py +35 -34
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/model/ir.py +19 -1
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/model/planner.py +14 -7
- draftwright-0.4.6/src/draftwright/recognition/__init__.py +15 -0
- draftwright-0.4.6/src/draftwright/recognition_cache.py +26 -0
- draftwright-0.4.6/src/draftwright/score.py +10 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_declare.py +29 -17
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_declared_recognition_gate.py +9 -9
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_detect_once.py +2 -2
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_detect_registry.py +2 -2
- draftwright-0.4.6/tests/test_external_recognition_boundary.py +69 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_fillets_adjacency.py +1 -2
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_flat_completeness.py +2 -2
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_flat_stock_identity.py +21 -5
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_flat_stock_opposition.py +1 -2
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_grid_lattice_convention.py +3 -3
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_import_boundaries.py +1 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_issue_1058_wheel_profile.py +23 -23
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_issue_1073_cross_body_patterns.py +5 -5
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_issue_1082_polygonal_stock.py +5 -5
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_issue_1143_hole_completeness.py +7 -7
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_issue_1146_scale_completeness.py +3 -3
- draftwright-0.4.6/tests/test_issue_367_leader_ink.py +137 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_issue_676_polygonal_boss.py +2 -2
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_issue_885_prismatic_coverage.py +21 -1
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_issue_909_sloped_profile.py +1 -1
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_issue_917_open_channel.py +1 -1
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_issue_958_cross_solid_recognition.py +4 -4
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_make_drawing.py +34 -46
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_part_model.py +1 -1
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_pocket_pattern_recognition.py +9 -9
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_private_test_imports.py +5 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_quality_components.py +2 -1
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_recogniser_contract.py +3 -4
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_recognition.py +28 -30
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_recognition_manifest.py +14 -14
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_recognition_result.py +9 -8
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_slanted_blind_step.py +3 -3
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_slot_completeness.py +1 -1
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_slot_pattern_recognition.py +6 -6
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_strip_layout.py +52 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_turned_steps.py +1 -2
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_workflows.py +14 -0
- draftwright-0.4.4/src/draftwright/recognition/__init__.py +0 -205
- draftwright-0.4.4/src/draftwright/recognition/_features.py +0 -1164
- draftwright-0.4.4/src/draftwright/recognition/_record.py +0 -28
- draftwright-0.4.4/src/draftwright/recognition/chamfers.py +0 -196
- draftwright-0.4.4/src/draftwright/recognition/countersinks.py +0 -206
- draftwright-0.4.4/src/draftwright/recognition/fillets.py +0 -183
- draftwright-0.4.4/src/draftwright/recognition/flats.py +0 -275
- draftwright-0.4.4/src/draftwright/recognition/grooves.py +0 -145
- draftwright-0.4.4/src/draftwright/recognition/levels.py +0 -346
- draftwright-0.4.4/src/draftwright/recognition/pads.py +0 -148
- draftwright-0.4.4/src/draftwright/recognition/plates.py +0 -154
- draftwright-0.4.4/src/draftwright/recognition/polygonal_bosses.py +0 -342
- draftwright-0.4.4/src/draftwright/recognition/profiled_bores.py +0 -368
- draftwright-0.4.4/src/draftwright/recognition/repeating_profiles.py +0 -393
- draftwright-0.4.4/src/draftwright/recognition/result.py +0 -293
- draftwright-0.4.4/src/draftwright/recognition/slots.py +0 -1465
- draftwright-0.4.4/src/draftwright/recognition/turned.py +0 -202
- draftwright-0.4.4/src/draftwright/score.py +0 -76
- {draftwright-0.4.4 → draftwright-0.4.6}/.gitignore +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/LICENSE +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/skills/SKILL.md +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/__init__.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/_pmi_part21.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/_warnings.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/annotate.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/annotations/__init__.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/annotations/_common.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/annotations/balloons.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/annotations/gears.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/annotations/orchestrator.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/annotations/sections.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/audit.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/builder.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/cli.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/compose.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/export.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/fits.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/fonts/IBMPlexMono-Regular.ttf +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/fonts/IBMPlexSansCondensed-Regular.ttf +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/fonts/LICENSE-IBMPlexMono-OFL.txt +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/fonts/LICENSE-IBMPlexSansCondensed-OFL.txt +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/fonts/__init__.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/intents.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/layout.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/linting/__init__.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/linting/gear_coverage.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/linting/issues.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/linting/pmi_coverage.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/linting/quality.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/linting/structural.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/linting/suggest.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/make_drawing.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/model/__init__.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/model/callout.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/pmi.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/projection.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/py.typed +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/registry.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/repair.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/sheet.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/src/draftwright/sheet_emit.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/_kernel.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/_layout_sig.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/conftest.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/fixtures/grm03_thumbwheel_drive_screw.step +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/fixtures/if_step_flat_across_cylinder.step +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/fixtures/issue_1058_wheel_rh.step +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/fixtures/issue_909_basic_part_design_017_body.step +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/fixtures/issue_915_case_study_2.step +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/fixtures/nist_ctc_01_asme1_ap203.stp +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/fixtures/nist_ctc_01_asme1_ap242.stp +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/fixtures/nist_ctc_02_asme1_ap203.stp +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/fixtures/nist_ctc_02_asme1_ap242.stp +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/fixtures/nist_ctc_03_asme1_ap203.stp +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/fixtures/nist_ctc_03_asme1_ap242.stp +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/fixtures/nist_ctc_04_asme1_ap203.stp +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/fixtures/nist_ctc_04_asme1_ap242.stp +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/fixtures/nist_ctc_05_asme1_ap203.stp +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/fixtures/nist_ctc_05_asme1_ap242.stp +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/fixtures/tuner_jig_blind_obround_pockets.step +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/refactor_golden/bracket_section.json +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/refactor_golden/centered_rebate.json +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/refactor_golden/chamfered.json +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/refactor_golden/filleted.json +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/refactor_golden/flange_dense.json +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/refactor_golden/grid_plate.json +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/refactor_golden/grooved_shaft.json +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/refactor_golden/hex_bar.json +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/refactor_golden/holed_slot.json +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/refactor_golden/pocketed.json +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/refactor_golden/prismatic_ladder.json +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/refactor_golden/scattered_plate.json +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/refactor_golden/side_drilled.json +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/refactor_golden/turned_stepped.json +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_add_dimension.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_agents_guide.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_api_docs.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_architecture_docs.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_audit_differential.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_balloon_ring_standoff.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_carve_free_position_callers.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_compiled_plan_boundary.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_counting_calls.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_dense_sheet_canary.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_deprecation_dates.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_dimension_role_vocabulary.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_drawing_encapsulation.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_e2e_slice.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_e2e_standards.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_export_dxf_curves.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_export_dxf_zoom.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_fits.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_gdt_placement.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_issue_1000_envelope_reuse.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_issue_1086_declared_gears.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_issue_1142_hole_leader_labels.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_issue_1144_transactional_hole_table.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_issue_1147_legibility_pair_aggregation.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_issue_676_polygonal_boss_declare.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_issue_915_dense_case.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_issue_916_pocket_leader.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_issue_955_height_contingency.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_label_provenance.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_layout.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_layout_cleanliness.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_layout_hypothesis.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_layout_property.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_leader_footprint.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_lint_box_cache.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_lint_reconciliation.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_lint_structural.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_linting.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_location_vocabulary.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_note_verb.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_object_aspects.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_occupancy_boxes.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_parameter_id.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_pitch_dim_footprint.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_pmi.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_pmi_datum_lowering.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_pmi_gtol_lowering.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_pmi_part21.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_pocket_pattern.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_principal_profile_classifier.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_private_test_attr_reads.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_refactor_golden.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_registry.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_render_seam.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_repack_geometry_seam.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_score.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_script_detail_parity.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_shared_box_memo.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_sheet_emit.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_sheet_gdt.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_sheet_identity_invariant.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_sheet_notes.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_sheet_of.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_sheet_section.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_sheet_tables.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_slot_pattern.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_soft_deprecation.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_solve_trace.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_suppression_ledger.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_suppression_marks.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_tolerances.py +0 -0
- {draftwright-0.4.4 → draftwright-0.4.6}/tests/test_witness_label_reconciliation.py +0 -0
|
@@ -1,6 +1,44 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## v0.4.6 — 2026-08-15
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- **Geometry recognition is supplied by the standalone Apache-2.0
|
|
8
|
+
[`b123d-recognisers`](https://github.com/pzfreo/b123d-recognisers) package.** The cutover
|
|
9
|
+
pins the published stable release `v0.1.0` (built from commit
|
|
10
|
+
`9e622716c14d491729b5191aa6e5d8351c982a51`), removes the
|
|
11
|
+
duplicate embedded implementation, and keeps only identity-preserving
|
|
12
|
+
`draftwright.recognition` / `draftwright.score` compatibility re-exports until 0.6.0.
|
|
13
|
+
Draftwright continues to own the one-result build/lint cache, record→IR conversion, drafting
|
|
14
|
+
policy, placement, and critique. The package migration is manifest-pinned and golden-backed;
|
|
15
|
+
its sole normalization makes an exact dominant-axis tie deterministic across operating
|
|
16
|
+
systems without changing feature policy.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- Hole-callout obstacle checks now account for the rendered leader shaft width and
|
|
21
|
+
tip-local arrowhead flare instead of treating the leader as a zero-width centreline
|
|
22
|
+
(#367). The analytical check remains geometry-only and does not over-reserve the
|
|
23
|
+
arrowhead's clearance along the whole shaft.
|
|
24
|
+
- Exact X/Z slanted flats retain their established lint-clean side-view callout after the
|
|
25
|
+
recogniser package's cross-platform semantic-axis normalization. The normalized recognition
|
|
26
|
+
record remains unchanged; `FlatFeature.presentation_axis` carries the downstream drawing-view
|
|
27
|
+
choice, preventing a leader/silhouette crossing without duplicating recognition.
|
|
28
|
+
|
|
29
|
+
## v0.4.5 — 2026-08-15
|
|
30
|
+
|
|
31
|
+
**A focused technical-drawing correctness patch.** Cross-drilled hole locations now remain
|
|
32
|
+
complete and ordered, datum-starting pocket annotations avoid redundant dimensions, and repeated
|
|
33
|
+
equal-radius fillets collapse into one manufacturing callout.
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
|
|
37
|
+
- Side-drilled hole heights that relocate around callout leaders now join the alternate
|
|
38
|
+
view's shared dimension ladder before placement, preserving every height and keeping
|
|
39
|
+
feature locations inside the overall dimension. Datum-starting open pockets no longer
|
|
40
|
+
print a redundant half-length centre offset, and equal-radius fillets across different
|
|
41
|
+
edge axes collapse into one counted ``n× R`` callout.
|
|
4
42
|
|
|
5
43
|
## v0.4.4 — 2026-08-15
|
|
6
44
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: draftwright
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.6
|
|
4
4
|
Summary: Automated technical-drawing generation for build123d
|
|
5
5
|
Project-URL: Homepage, https://github.com/pzfreo/draftwright
|
|
6
6
|
Project-URL: Repository, https://github.com/pzfreo/draftwright
|
|
@@ -684,6 +684,7 @@ Classifier: Programming Language :: Python :: 3.14
|
|
|
684
684
|
Classifier: Topic :: Scientific/Engineering
|
|
685
685
|
Requires-Python: <3.15,>=3.10
|
|
686
686
|
Requires-Dist: antlr4-python3-runtime<4.10
|
|
687
|
+
Requires-Dist: b123d-recognisers==0.1.0
|
|
687
688
|
Requires-Dist: build123d-drafting-helpers>=0.14.1
|
|
688
689
|
Requires-Dist: build123d<0.11,>=0.9.0; python_full_version < '3.13'
|
|
689
690
|
Requires-Dist: build123d<0.12,>=0.11; python_full_version >= '3.13'
|
|
@@ -967,17 +968,20 @@ or declared features converge on a `PartModel` IR, then planner-fed and
|
|
|
967
968
|
sanctioned model-routed render intents feed shared placement, projection, and
|
|
968
969
|
export. Coverage lint independently compares recognised geometry with the
|
|
969
970
|
placed drawing, so an upstream omission cannot hide from verification. It
|
|
970
|
-
builds on
|
|
971
|
+
builds on three libraries:
|
|
971
972
|
|
|
972
973
|
```
|
|
973
974
|
draftwright
|
|
975
|
+
└── b123d-recognisers — deterministic geometry recognition
|
|
974
976
|
└── build123d-drafting-helpers — Dimension, Leader, HoleCallout, …
|
|
975
977
|
└── build123d — CAD kernel
|
|
976
978
|
```
|
|
977
979
|
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
980
|
+
Geometry recognition lives in the Apache-2.0 `b123d-recognisers` package. Draftwright owns
|
|
981
|
+
the per-build recognition cache, record→IR conversion, drafting policy, and linting
|
|
982
|
+
(`linting/`); annotation primitives (`Dimension`, `Leader`, etc.) live in
|
|
983
|
+
`build123d-drafting-helpers` and can be used independently. The compiler is largely converged
|
|
984
|
+
in production — turned
|
|
981
985
|
dims/lengths, centre marks, envelope, slots, holes (callouts/locations/grouping),
|
|
982
986
|
the section A–A trigger, the prismatic step-ladder + rotational furniture, and PMI/GD&T
|
|
983
987
|
are all on the IR. See
|
|
@@ -268,17 +268,20 @@ or declared features converge on a `PartModel` IR, then planner-fed and
|
|
|
268
268
|
sanctioned model-routed render intents feed shared placement, projection, and
|
|
269
269
|
export. Coverage lint independently compares recognised geometry with the
|
|
270
270
|
placed drawing, so an upstream omission cannot hide from verification. It
|
|
271
|
-
builds on
|
|
271
|
+
builds on three libraries:
|
|
272
272
|
|
|
273
273
|
```
|
|
274
274
|
draftwright
|
|
275
|
+
└── b123d-recognisers — deterministic geometry recognition
|
|
275
276
|
└── build123d-drafting-helpers — Dimension, Leader, HoleCallout, …
|
|
276
277
|
└── build123d — CAD kernel
|
|
277
278
|
```
|
|
278
279
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
280
|
+
Geometry recognition lives in the Apache-2.0 `b123d-recognisers` package. Draftwright owns
|
|
281
|
+
the per-build recognition cache, record→IR conversion, drafting policy, and linting
|
|
282
|
+
(`linting/`); annotation primitives (`Dimension`, `Leader`, etc.) live in
|
|
283
|
+
`build123d-drafting-helpers` and can be used independently. The compiler is largely converged
|
|
284
|
+
in production — turned
|
|
282
285
|
dims/lengths, centre marks, envelope, slots, holes (callouts/locations/grouping),
|
|
283
286
|
the section A–A trigger, the prismatic step-ladder + rotational furniture, and PMI/GD&T
|
|
284
287
|
are all on the IR. See
|
|
@@ -25,15 +25,15 @@ architecture** table; open retired or superseded records only for design history
|
|
|
25
25
|
| [0004](0004-compose-then-pack-view-blocks.md) | Compose-then-pack: views as blocks carrying their annotation footprint | Compose each view with its annotation footprint, then pack fixed-topology blocks. | Accepted | `test_layout.py`, `test_layout_cleanliness.py`, `test_refactor_golden.py` |
|
|
26
26
|
| [0005](0005-pipeline-architecture-and-state-ownership.md) | Compiler-pipeline module boundaries and single-owner build state | Give compiler stages explicit module homes and build-time state explicit owners. | Accepted; implemented; compatibility aliases tracked by #720 | `test_import_boundaries.py`, `test_drawing_encapsulation.py`, `test_registry.py` |
|
|
27
27
|
| [0006](0006-deterministic-layout-via-bundled-fonts.md) | Deterministic cross-platform layout via bundled, path-pinned fonts | Pin bundled font files so text measurement and layout are cross-platform deterministic. | Accepted | `test_refactor_golden.py`, `test_layout_cleanliness.py` |
|
|
28
|
-
| [0007](0007-own-recognition-and-linting.md) | draftwright owns feature recognition and linting; helpers becomes the rendering library |
|
|
28
|
+
| [0007](0007-own-recognition-and-linting.md) | draftwright owns feature recognition and linting; helpers becomes the rendering library | Draftwright owns linting, recognition lifecycle, IR conversion, and drafting policy; `b123d-recognisers` owns geometry recognition; helpers remains the rendering library. | Accepted; extraction deployed by Amendment 2 | `test_import_boundaries.py`, `test_external_recognition_boundary.py`, `test_linting.py` |
|
|
29
29
|
| [0010](0010-annotation-provenance-seam.md) | Annotation provenance: record intent → annotation once, at the render seam | Record annotation provenance once at the render/add seam. | Accepted; landed | `test_render_seam.py`, `test_registry.py` |
|
|
30
30
|
| [0011](0011-ir-as-public-input.md) | The IR as a public input: declare features, don't only detect them | Accept the feature IR as public input through declarations and the `Sheet` façade. | Accepted; core landed; #62/#462/#495 remain | `test_declare.py`, `test_object_aspects.py`, `test_sheet_gdt.py` |
|
|
31
31
|
| [0012](0012-edits-as-pinned-priority-candidates-in-the-global-solve.md) | User annotation edits are pinned, priority-ranked corridor candidates | Drain recorded semantic edits through corridor placement with pin and priority. | Accepted; partially landed; full recomposition/parity remains #426/#707 (#661 detail views landed) | `test_make_drawing.py`, `test_sheet_emit.py` |
|
|
32
|
-
| [0013](0013-uniform-recognition-and-shared-package.md) | A uniform recogniser/feature contract
|
|
32
|
+
| [0013](0013-uniform-recognition-and-shared-package.md) | A uniform recogniser/feature contract and shared `b123d-recognisers` deployment | Enforce the geometry-only contract in the shared package and consume it through Draftwright's record→IR boundary. | Accepted; Phases 1–2 deployed | `test_external_recognition_boundary.py`, `test_detect_registry.py`, package semantic goldens |
|
|
33
33
|
| [0014](0014-collect-then-solve-annotation-placement.md) | Collect-then-solve annotation placement (as built) | Collect, select, assign, and deterministically solve annotations per corridor before rendering. | Accepted; supersedes 0009 | `test_carve_free_position_callers.py`, `test_strip_layout.py`, `test_layout_property.py`, `test_import_boundaries.py` |
|
|
34
34
|
| [0015](0015-part-drawing-compiler-as-built.md) | The part-drawing compiler, as built | Use one detected-or-declared feature IR and planner-fed dimension groups as the compiler waist. | Accepted; supersedes 0008 | `test_part_model.py`, `test_detect_once.py`, `test_import_boundaries.py` |
|
|
35
35
|
| [0016](0016-declared-dimensioning-intent.md) | Declared dimensioning intent: capture what to measure, let the engine place it | Declare which measurements matter as scale-independent intent routed through the planner and corridor solve; never hardcode dimension geometry. | Accepted; epic #867 complete; phase 6 landed (#940) | `test_compiled_plan_boundary.py`, `test_label_provenance.py`, `test_sheet_emit.py`, `test_add_dimension.py` |
|
|
36
|
-
| [0017](0017-recognition-inventory-correspondence-and-measurement-provenance.md) | One recognition result per run; correspondence is evidence-gated | Produce one
|
|
36
|
+
| [0017](0017-recognition-inventory-correspondence-and-measurement-provenance.md) | One recognition result per run; correspondence is evidence-gated | Produce one external immutable recognition result held by Draftwright's per-run cache; require vertical-slice evidence before generalising correspondence, identity, requirements, outcomes, or reconciliation. | Accepted; external cache ownership clarified, extensions gated by #1018 | `test_external_recognition_boundary.py`, `test_recognition_manifest.py`, `test_declared_recognition_gate.py` |
|
|
37
37
|
| [0018](0018-requirement-driven-view-planning-and-editable-sheet-layout.md) | Requirement-driven view planning and editable sheet layout | Use one `ViewSpec` vocabulary and planner with distinct authored `ViewConstraints` and immutable `ResolvedViewPlan`; jointly validate views, typography, convention, scale, paper and layout against requirement survival. If accepted, supersedes ADR 0004's fixed-topology assumption while retaining compose-then-pack. | Proposed; tracked by #1130 | Required guards are listed in the ADR |
|
|
38
38
|
|
|
39
39
|
## Historical records
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "draftwright"
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.6"
|
|
8
8
|
description = "Automated technical-drawing generation for build123d"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { file = "LICENSE" }
|
|
@@ -18,6 +18,7 @@ dependencies = [
|
|
|
18
18
|
"build123d>=0.9.0,<0.11 ; python_full_version < '3.13'",
|
|
19
19
|
"build123d>=0.11,<0.12 ; python_full_version >= '3.13'",
|
|
20
20
|
"build123d-drafting-helpers>=0.14.1",
|
|
21
|
+
"b123d-recognisers==0.1.0",
|
|
21
22
|
"numpy>=1.24",
|
|
22
23
|
# PNG raster export (SVG→PDF→PNG). Both permissively licensed (Pillow HPND; pypdfium2
|
|
23
24
|
# Apache-2.0 wrapping Google PDFium BSD-3) and ship pre-built wheels with NO native system
|
|
@@ -163,6 +164,7 @@ dev = [
|
|
|
163
164
|
"pytest-timeout>=2",
|
|
164
165
|
"pytest-xdist>=3",
|
|
165
166
|
"ruff>=0.4",
|
|
167
|
+
"tomli>=2; python_version < '3.11'",
|
|
166
168
|
]
|
|
167
169
|
|
|
168
170
|
[tool.codespell]
|
|
@@ -25,8 +25,9 @@ from types import SimpleNamespace
|
|
|
25
25
|
from typing import TYPE_CHECKING, Literal
|
|
26
26
|
|
|
27
27
|
if TYPE_CHECKING:
|
|
28
|
+
from b123d_recognisers import RecognitionResult, TurnedProfile
|
|
29
|
+
|
|
28
30
|
from draftwright.compose import StripDepths
|
|
29
|
-
from draftwright.recognition import RecognitionResult, TurnedProfile
|
|
30
31
|
|
|
31
32
|
from build123d import Align, BoundBox, Compound, Edge, Location, Mode, Shape, Text, Vector
|
|
32
33
|
from build123d_drafting.helpers import (
|
|
@@ -327,6 +327,94 @@ def _segment_clips_box(p, q, box, pad=0.0) -> bool:
|
|
|
327
327
|
return _segment_clip_extent(p, q, box, pad=pad) is not None
|
|
328
328
|
|
|
329
329
|
|
|
330
|
+
def _convex_polygon_overlaps_box(points, box) -> bool:
|
|
331
|
+
"""Whether a convex page-plane polygon has positive-area overlap with an AABB.
|
|
332
|
+
|
|
333
|
+
This is the small separating-axis primitive used by
|
|
334
|
+
:func:`_leader_ink_crosses_box`. Keeping it here makes the rendered-ink model pure
|
|
335
|
+
rectangle/vector maths: placement does not build temporary OCC geometry merely to ask
|
|
336
|
+
whether a candidate is clear (ADRs 0004/0014).
|
|
337
|
+
"""
|
|
338
|
+
corners = (
|
|
339
|
+
(box[0], box[1]),
|
|
340
|
+
(box[2], box[1]),
|
|
341
|
+
(box[2], box[3]),
|
|
342
|
+
(box[0], box[3]),
|
|
343
|
+
)
|
|
344
|
+
axes = [(1.0, 0.0), (0.0, 1.0)]
|
|
345
|
+
axes.extend(
|
|
346
|
+
(-(second[1] - first[1]), second[0] - first[0])
|
|
347
|
+
for first, second in zip(points, (*points[1:], points[0]), strict=True)
|
|
348
|
+
)
|
|
349
|
+
for ax, ay in axes:
|
|
350
|
+
if abs(ax) + abs(ay) <= 1e-12:
|
|
351
|
+
continue
|
|
352
|
+
polygon_projection = [x * ax + y * ay for x, y in points]
|
|
353
|
+
box_projection = [x * ax + y * ay for x, y in corners]
|
|
354
|
+
if max(polygon_projection) <= min(box_projection) or max(box_projection) <= min(
|
|
355
|
+
polygon_projection
|
|
356
|
+
):
|
|
357
|
+
return False
|
|
358
|
+
return True
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def _leader_ink_crosses_box(
|
|
362
|
+
tip,
|
|
363
|
+
elbow,
|
|
364
|
+
box,
|
|
365
|
+
*,
|
|
366
|
+
arrow_length: float,
|
|
367
|
+
line_width: float,
|
|
368
|
+
) -> bool:
|
|
369
|
+
"""Whether a rendered leader's tip-to-elbow ink overlaps *box*.
|
|
370
|
+
|
|
371
|
+
A leader is not a zero-width segment. Its shaft is a swept rectangle of
|
|
372
|
+
``line_width`` and each supported arrowhead style is contained by the triangle from
|
|
373
|
+
the tip to a base ``arrow_length`` along the shaft, with the helper's
|
|
374
|
+
``arrow_length / 3`` lateral flare. Testing those two local convex footprints keeps
|
|
375
|
+
the arrow clearance near the tip; inflating the entire shaft by the arrow size would
|
|
376
|
+
reject genuinely clear obstacles near the elbow (#367).
|
|
377
|
+
|
|
378
|
+
Touching boundaries are clear, matching :func:`_boxes_overlap` and the placement-side
|
|
379
|
+
semantics of :func:`_segment_crosses_box`.
|
|
380
|
+
"""
|
|
381
|
+
dx, dy = float(elbow[0]) - float(tip[0]), float(elbow[1]) - float(tip[1])
|
|
382
|
+
length = math.hypot(dx, dy)
|
|
383
|
+
half_line = max(0.0, float(line_width)) / 2.0
|
|
384
|
+
head_length = max(0.0, float(arrow_length))
|
|
385
|
+
|
|
386
|
+
if length <= 1e-12:
|
|
387
|
+
half = max(half_line, head_length / 3.0)
|
|
388
|
+
point_box = (tip[0] - half, tip[1] - half, tip[0] + half, tip[1] + half)
|
|
389
|
+
return _boxes_overlap(point_box, box)
|
|
390
|
+
|
|
391
|
+
ux, uy = dx / length, dy / length
|
|
392
|
+
vx, vy = -uy, ux
|
|
393
|
+
|
|
394
|
+
if half_line > 0.0:
|
|
395
|
+
shaft = (
|
|
396
|
+
(tip[0] + vx * half_line, tip[1] + vy * half_line),
|
|
397
|
+
(elbow[0] + vx * half_line, elbow[1] + vy * half_line),
|
|
398
|
+
(elbow[0] - vx * half_line, elbow[1] - vy * half_line),
|
|
399
|
+
(tip[0] - vx * half_line, tip[1] - vy * half_line),
|
|
400
|
+
)
|
|
401
|
+
if _convex_polygon_overlaps_box(shaft, box):
|
|
402
|
+
return True
|
|
403
|
+
|
|
404
|
+
if head_length > 0.0:
|
|
405
|
+
half_head = head_length / 3.0
|
|
406
|
+
base_x, base_y = tip[0] + ux * head_length, tip[1] + uy * head_length
|
|
407
|
+
arrow = (
|
|
408
|
+
(tip[0], tip[1]),
|
|
409
|
+
(base_x + vx * half_head, base_y + vy * half_head),
|
|
410
|
+
(base_x - vx * half_head, base_y - vy * half_head),
|
|
411
|
+
)
|
|
412
|
+
if _convex_polygon_overlaps_box(arrow, box):
|
|
413
|
+
return True
|
|
414
|
+
|
|
415
|
+
return False
|
|
416
|
+
|
|
417
|
+
|
|
330
418
|
def _segments_cross_or_overlap(a1, a2, b1, b2) -> bool:
|
|
331
419
|
"""Whether two page-plane segments cross or overlap beyond a shared endpoint.
|
|
332
420
|
|
|
@@ -17,6 +17,14 @@ from collections.abc import Callable
|
|
|
17
17
|
from dataclasses import dataclass, replace
|
|
18
18
|
from typing import cast
|
|
19
19
|
|
|
20
|
+
from b123d_recognisers import (
|
|
21
|
+
RecognitionResult,
|
|
22
|
+
TurnedProfile,
|
|
23
|
+
TurnedStep,
|
|
24
|
+
analyse_cylinders,
|
|
25
|
+
build_recognition_result,
|
|
26
|
+
full_cylinders,
|
|
27
|
+
)
|
|
20
28
|
from build123d import Compound, Shape
|
|
21
29
|
from build123d_drafting.helpers import draft_preset
|
|
22
30
|
from OCP.IFSelect import IFSelect_ReturnStatus
|
|
@@ -47,14 +55,6 @@ from draftwright.compose import (
|
|
|
47
55
|
from draftwright.model import build_part_model
|
|
48
56
|
from draftwright.model.ir import Datum, PartModel, StepFeature, StepLevelFeature
|
|
49
57
|
from draftwright.model.planner import plan_dimensions
|
|
50
|
-
from draftwright.recognition import (
|
|
51
|
-
RecognitionResult,
|
|
52
|
-
TurnedProfile,
|
|
53
|
-
TurnedStep,
|
|
54
|
-
analyse_cylinders,
|
|
55
|
-
build_recognition_result,
|
|
56
|
-
full_cylinders,
|
|
57
|
-
)
|
|
58
58
|
|
|
59
59
|
_log = logging.getLogger(__name__)
|
|
60
60
|
|
|
@@ -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
|
|
1763
|
-
|
|
1764
|
-
|
|
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
|
-
|
|
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(
|
|
1788
|
+
collapse.setdefault(round(pd.value, 3), []).append((g, pd))
|
|
1789
1789
|
jobs = []
|
|
1790
|
-
for gi, (
|
|
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
|
-
#
|
|
1799
|
-
#
|
|
1800
|
-
#
|
|
1801
|
-
|
|
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(
|
|
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
|
|
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)
|
|
@@ -1876,6 +1881,7 @@ def render_flats(dwg, plan, a, *, ctx, only=None) -> int:
|
|
|
1876
1881
|
collapse.setdefault(
|
|
1877
1882
|
(
|
|
1878
1883
|
g.facts.axis,
|
|
1884
|
+
g.facts.presentation_axis,
|
|
1879
1885
|
g.facts.axis_direction,
|
|
1880
1886
|
g.facts.axis_line,
|
|
1881
1887
|
g.facts.stock_span,
|
|
@@ -1884,7 +1890,7 @@ def render_flats(dwg, plan, a, *, ctx, only=None) -> int:
|
|
|
1884
1890
|
[],
|
|
1885
1891
|
).append((g, pd))
|
|
1886
1892
|
jobs = []
|
|
1887
|
-
for gi, ((
|
|
1893
|
+
for gi, ((_axis, presentation_axis, _direction, _line, _span, _across), members) in enumerate(
|
|
1888
1894
|
sorted(collapse.items())
|
|
1889
1895
|
):
|
|
1890
1896
|
if only is not None:
|
|
@@ -1915,7 +1921,7 @@ def render_flats(dwg, plan, a, *, ctx, only=None) -> int:
|
|
|
1915
1921
|
)
|
|
1916
1922
|
jobs.append(
|
|
1917
1923
|
(
|
|
1918
|
-
f"m_flat_{
|
|
1924
|
+
f"m_flat_{presentation_axis}{gi}",
|
|
1919
1925
|
view,
|
|
1920
1926
|
vb,
|
|
1921
1927
|
_flat_label(members[0][1].value_text, _tol_suffix(tol, draft)),
|
|
@@ -33,7 +33,7 @@ from draftwright._core import (
|
|
|
33
33
|
_iso_bbox,
|
|
34
34
|
_log,
|
|
35
35
|
)
|
|
36
|
-
from draftwright._geometry import plane_axes
|
|
36
|
+
from draftwright._geometry import _leader_ink_crosses_box, plane_axes
|
|
37
37
|
from draftwright.annotations._common import (
|
|
38
38
|
CROSSABLE_TYPES,
|
|
39
39
|
CorridorCandidate,
|
|
@@ -42,17 +42,18 @@ from draftwright.annotations._common import (
|
|
|
42
42
|
_geom_box,
|
|
43
43
|
_hole_location_coverage_fact,
|
|
44
44
|
_same_location_ordinate,
|
|
45
|
-
_segment_crosses_box,
|
|
46
45
|
_with_hole_center_coverage,
|
|
47
46
|
_with_hole_location_coverage,
|
|
48
47
|
box_within_page_and_clear,
|
|
49
48
|
carve_free_position,
|
|
50
49
|
carve_free_segments,
|
|
51
50
|
clear_label_of_centerlines,
|
|
51
|
+
corridor_blockers,
|
|
52
52
|
dim_footprint,
|
|
53
53
|
leader_footprint,
|
|
54
54
|
place_strip_candidates,
|
|
55
55
|
register_corridor,
|
|
56
|
+
strip_free_span,
|
|
56
57
|
strip_obstacles,
|
|
57
58
|
)
|
|
58
59
|
from draftwright.annotations.from_model import (
|
|
@@ -967,6 +968,42 @@ def _locate_along_z(dwg, ctx, a: Analysis, off):
|
|
|
967
968
|
strip, view, p_lo, p_hi, edge = primary
|
|
968
969
|
primary_cand = _zc(view, p_lo, p_hi, edge)
|
|
969
970
|
|
|
971
|
+
# Choose the alternate BEFORE the shared corridor solve when the natural
|
|
972
|
+
# witness corridor is already known to cross a placed leader. A post-drain
|
|
973
|
+
# relocation cannot join the alternate view's existing ladder, so it used to
|
|
974
|
+
# land outside the overall height and cross its witness line. The inner-tier
|
|
975
|
+
# footprint is decisive: moving a right-side dimension farther out only grows
|
|
976
|
+
# the corridor back to the view and therefore cannot clear an existing blocker.
|
|
977
|
+
if strip is not None and alternates:
|
|
978
|
+
_lo, _hi, inner = strip_free_span(strip)
|
|
979
|
+
blocked = _box_hits(_geom_box(primary_cand[1](inner)), corridor_blockers(dwg, view))
|
|
980
|
+
if blocked:
|
|
981
|
+
alt_strip, alt_view, alt_p_lo, alt_p_hi, alt_edge = alternates[0]
|
|
982
|
+
alt_cand = _zc(alt_view, alt_p_lo, alt_p_hi, alt_edge)
|
|
983
|
+
alt_blocked = alt_strip is None
|
|
984
|
+
if alt_strip is not None:
|
|
985
|
+
_alo, _ahi, alt_inner = strip_free_span(alt_strip)
|
|
986
|
+
alt_blocked = _box_hits(
|
|
987
|
+
_geom_box(alt_cand[1](alt_inner)),
|
|
988
|
+
corridor_blockers(dwg, alt_view),
|
|
989
|
+
)
|
|
990
|
+
# Do not pre-route into a corridor that cannot hold its existing
|
|
991
|
+
# shared ladder plus this candidate. In that case the historical
|
|
992
|
+
# alternate/force path remains the honest policy-B fallback.
|
|
993
|
+
existing = len(
|
|
994
|
+
ctx.corridor_batch.get((alt_view, "right"), {}).get("cands", ())
|
|
995
|
+
)
|
|
996
|
+
pad = tier + alt_strip.spacing
|
|
997
|
+
usable = max(0.0, _ahi - _alo - tier)
|
|
998
|
+
capacity = int(usable / pad) + 1
|
|
999
|
+
alt_blocked = alt_blocked or capacity < existing + 1
|
|
1000
|
+
if not alt_blocked:
|
|
1001
|
+
original = primary
|
|
1002
|
+
primary = alternates[0]
|
|
1003
|
+
alternates = [original, *alternates[1:]]
|
|
1004
|
+
strip, view, p_lo, p_hi, edge = primary
|
|
1005
|
+
primary_cand = alt_cand
|
|
1006
|
+
|
|
970
1007
|
def _fallback(
|
|
971
1008
|
_nm,
|
|
972
1009
|
_primary=primary,
|
|
@@ -974,10 +1011,14 @@ def _locate_along_z(dwg, ctx, a: Analysis, off):
|
|
|
974
1011
|
_cand=primary_cand,
|
|
975
1012
|
_feature_map=_zf,
|
|
976
1013
|
_measurement_map=_zm,
|
|
1014
|
+
_candidate_factory=_zc,
|
|
977
1015
|
_zo=zo,
|
|
978
1016
|
):
|
|
979
1017
|
for alt_strip, alt_view, alt_p_lo, alt_p_hi, alt_edge in _alts:
|
|
980
|
-
|
|
1018
|
+
# Capture this loop member's factory. Referring to `_zc` directly
|
|
1019
|
+
# late-bound every callback to the final height, so two dropped hole
|
|
1020
|
+
# heights retried the last one twice and silently lost the first.
|
|
1021
|
+
alt = _candidate_factory(alt_view, alt_p_lo, alt_p_hi, alt_edge)
|
|
981
1022
|
if not _off_axis_emit(
|
|
982
1023
|
dwg,
|
|
983
1024
|
tier,
|
|
@@ -1843,15 +1884,25 @@ def _needs_section(feat: HoleFeature | PatternFeature):
|
|
|
1843
1884
|
return bore.cbore is not None or bore.spotface is not None or not bore.through
|
|
1844
1885
|
|
|
1845
1886
|
|
|
1846
|
-
def _leader_hits(leader, tip, elbow, side, obstacles):
|
|
1887
|
+
def _leader_hits(leader, tip, elbow, side, obstacles, draft):
|
|
1847
1888
|
"""True when *leader*'s rendered footprint truly overlaps any *obstacles* box — split into
|
|
1848
|
-
the diagonal tip→elbow SHAFT (checked
|
|
1849
|
-
over-claims the empty triangle it doesn't occupy — #305) and
|
|
1850
|
-
(axis-aligned, so the coarse AABB check there is already exact).
|
|
1889
|
+
the diagonal tip→elbow SHAFT + ARROWHEAD (checked against their analytical rendered ink,
|
|
1890
|
+
since the composite AABB over-claims the empty triangle it doesn't occupy — #305/#367) and
|
|
1891
|
+
the elbow→label shelf+text (axis-aligned, so the coarse AABB check there is already exact).
|
|
1892
|
+
Promoted (#638; pure)."""
|
|
1851
1893
|
full_box = _geom_box(leader)
|
|
1852
1894
|
if full_box is None or not _box_hits(full_box, obstacles):
|
|
1853
1895
|
return False # fast reject: nowhere near any obstacle
|
|
1854
|
-
if any(
|
|
1896
|
+
if any(
|
|
1897
|
+
_leader_ink_crosses_box(
|
|
1898
|
+
tip,
|
|
1899
|
+
elbow,
|
|
1900
|
+
obstacle,
|
|
1901
|
+
arrow_length=draft.arrow_length,
|
|
1902
|
+
line_width=draft.line_width,
|
|
1903
|
+
)
|
|
1904
|
+
for obstacle in obstacles
|
|
1905
|
+
):
|
|
1855
1906
|
return True
|
|
1856
1907
|
label_box = (
|
|
1857
1908
|
(elbow[0], full_box[1], full_box[2], full_box[3])
|
|
@@ -2391,7 +2442,7 @@ def _place_queue(
|
|
|
2391
2442
|
continue
|
|
2392
2443
|
y = final_y[tid]
|
|
2393
2444
|
leader, tip, elbow = _build_leader_at(s, edge, side, y, to_page, elbow_dx, draft, a.SCALE)
|
|
2394
|
-
if _leader_hits(leader, tip, elbow, side, occupied):
|
|
2445
|
+
if _leader_hits(leader, tip, elbow, side, occupied, draft):
|
|
2395
2446
|
crossing.append((s, y, leader))
|
|
2396
2447
|
else:
|
|
2397
2448
|
placed.append((s, y, leader))
|
|
@@ -20,7 +20,7 @@ from dataclasses import field as dataclasses_field
|
|
|
20
20
|
from typing import TYPE_CHECKING, Any, NamedTuple
|
|
21
21
|
|
|
22
22
|
if TYPE_CHECKING:
|
|
23
|
-
from
|
|
23
|
+
from b123d_recognisers import RecognitionResult
|
|
24
24
|
|
|
25
25
|
# PEP 702 @deprecated. A `sys.version_info` guard (not try/except) so the type checker,
|
|
26
26
|
# which targets the 3.10 floor, resolves the backport branch instead of `warnings.deprecated`
|
|
@@ -30,6 +30,7 @@ if sys.version_info >= (3, 13):
|
|
|
30
30
|
else:
|
|
31
31
|
from typing_extensions import deprecated
|
|
32
32
|
|
|
33
|
+
from b123d_recognisers import analyse_cylinders
|
|
33
34
|
from build123d import (
|
|
34
35
|
Color,
|
|
35
36
|
ExportSVG,
|
|
@@ -98,7 +99,7 @@ from draftwright.linting.quality import quality_components
|
|
|
98
99
|
from draftwright.projection import (
|
|
99
100
|
project_view_geometry,
|
|
100
101
|
)
|
|
101
|
-
from draftwright.
|
|
102
|
+
from draftwright.recognition_cache import RecognitionCache
|
|
102
103
|
from draftwright.registry import AnnotationRegistry
|
|
103
104
|
from draftwright.repair import repair_drawing
|
|
104
105
|
|
|
@@ -320,7 +321,7 @@ class BuildState:
|
|
|
320
321
|
"""
|
|
321
322
|
|
|
322
323
|
analysis: Analysis | None = None
|
|
323
|
-
|
|
324
|
+
recognition_cache: RecognitionCache = dataclasses_field(default_factory=RecognitionCache)
|
|
324
325
|
part_model: object | None = None
|
|
325
326
|
view_edge_cache: dict = dataclasses_field(default_factory=dict)
|
|
326
327
|
ann_box_cache: dict = dataclasses_field(default_factory=dict)
|
|
@@ -335,6 +336,16 @@ class BuildState:
|
|
|
335
336
|
#: suppression rule produced four issue reports before anyone found the rule (#997).
|
|
336
337
|
omissions: tuple = ()
|
|
337
338
|
|
|
339
|
+
@property
|
|
340
|
+
def recognition(self) -> RecognitionResult | None:
|
|
341
|
+
"""The immutable result held by Draftwright's consumer-owned lifecycle cache."""
|
|
342
|
+
|
|
343
|
+
return self.recognition_cache.result
|
|
344
|
+
|
|
345
|
+
@recognition.setter
|
|
346
|
+
def recognition(self, value: RecognitionResult | None) -> None:
|
|
347
|
+
self.recognition_cache.result = value
|
|
348
|
+
|
|
338
349
|
def clear_geometry_caches(self) -> None:
|
|
339
350
|
"""The one invalidation seam (finalize rollback): view edges + annotation
|
|
340
351
|
boxes together — a rolled-back drawing must re-measure everything."""
|
|
@@ -353,9 +364,7 @@ class BuildState:
|
|
|
353
364
|
On a detected build ``recognition`` is already filled by the builder, so this returns
|
|
354
365
|
it and recognises nothing.
|
|
355
366
|
"""
|
|
356
|
-
|
|
357
|
-
self.recognition = build_recognition_result(part)
|
|
358
|
-
return self.recognition
|
|
367
|
+
return self.recognition_cache.ensure(part)
|
|
359
368
|
|
|
360
369
|
|
|
361
370
|
class Drawing:
|
|
@@ -5,8 +5,9 @@ from __future__ import annotations
|
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
from typing import Literal
|
|
7
7
|
|
|
8
|
+
from b123d_recognisers import RecognitionResult, has_multi_axis_plates
|
|
9
|
+
|
|
8
10
|
from draftwright.linting.issues import LintIssue
|
|
9
|
-
from draftwright.recognition import RecognitionResult, has_multi_axis_plates
|
|
10
11
|
|
|
11
12
|
ChannelRequirementState = Literal["placed", "suppressed", "dropped", "missing", "unverifiable"]
|
|
12
13
|
|