draftwright 0.4.13__tar.gz → 0.4.14__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.13 → draftwright-0.4.14}/CHANGELOG.md +19 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/PKG-INFO +2 -2
- {draftwright-0.4.13 → draftwright-0.4.14}/docs/reference/recogniser-capabilities.md +31 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/pyproject.toml +2 -2
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/_core.py +4 -1
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/annotations/_common.py +386 -33
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/annotations/from_model.py +16 -1
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/builder.py +67 -26
- draftwright-0.4.14/src/draftwright/linting/ink_overlap.py +745 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/linting/quality.py +1 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/linting/structural.py +161 -13
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/linting/suggest.py +7 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/repair.py +26 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/conftest.py +2 -0
- draftwright-0.4.14/tests/fixtures/grm03_thumbwheel_drive_screw_ap242_pmi.step +32991 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_1166_cross_pass_feature_leaders.py +7 -1
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_1296_cylindrical_diameter_pmi.py +1 -2
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_1298_manufacturing_requirements.py +1 -3
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_1299_page_escalation.py +37 -13
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_1308_machined_leader_analytics.py +8 -0
- draftwright-0.4.14/tests/test_issue_1332_overlap_remedy.py +89 -0
- draftwright-0.4.14/tests/test_issue_1332_overlap_remedy_fast.py +106 -0
- draftwright-0.4.14/tests/test_issue_1332_zone_labels.py +49 -0
- draftwright-0.4.14/tests/test_issue_1334_prevent_ink.py +158 -0
- draftwright-0.4.14/tests/test_issue_1336_grm03_ap242_pmi_fixture.py +77 -0
- draftwright-0.4.14/tests/test_issue_1338_scale_before_page_escalation.py +103 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_443_grm03_axial_coverage.py +7 -4
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_915_dense_case.py +62 -2
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_916_pocket_leader.py +29 -1
- draftwright-0.4.14/tests/test_lint_ink_overlap.py +727 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_lint_structural.py +56 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_make_drawing.py +72 -4
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_recogniser_capabilities.py +40 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_strip_layout.py +10 -2
- {draftwright-0.4.13 → draftwright-0.4.14}/.gitignore +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/LICENSE +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/README.md +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/docs/adr/README.md +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/docs/reference/sheet.md +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/docs/research/1062-repeating-radial-profile-evidence.md +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/skills/SKILL.md +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/__init__.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/_build_profile.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/_geometry.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/_pmi_part21.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/_warnings.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/analysis.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/annotate.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/annotations/__init__.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/annotations/balloons.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/annotations/gears.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/annotations/holes.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/annotations/leaders.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/annotations/orchestrator.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/annotations/sections.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/audit.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/cli.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/compose.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/drawing.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/evaluation/__init__.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/evaluation/step_analysis.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/export.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/fits.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/fonts/IBMPlexMono-Regular.ttf +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/fonts/IBMPlexSansCondensed-Regular.ttf +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/fonts/LICENSE-IBMPlexMono-OFL.txt +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/fonts/LICENSE-IBMPlexSansCondensed-OFL.txt +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/fonts/__init__.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/intents.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/layout.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/linting/__init__.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/linting/channel_coverage.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/linting/coverage.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/linting/evidence.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/linting/flat_coverage.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/linting/gear_coverage.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/linting/hole_coverage.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/linting/issues.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/linting/pmi_coverage.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/linting/polygonal_stock_coverage.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/linting/profiled_bore_coverage.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/linting/slot_coverage.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/make_drawing.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/model/__init__.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/model/callout.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/model/compiled.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/model/declare.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/model/detect.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/model/ir.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/model/planner.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/model/pmi_lowering.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/pmi.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/projection.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/py.typed +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/recogniser_contract.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/recognition/__init__.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/recognition_cache.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/registry.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/score.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/sheet.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/sheet_emit.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/src/draftwright/view_plan.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/_kernel.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/_layout_sig.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/fixtures/ap242_single_cylinder_diameter.step +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/fixtures/evaluation/README.md +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/fixtures/evaluation/ambiguous-open-semicircle.step +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/fixtures/evaluation/blind-hole.step +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/fixtures/evaluation/corpus-v1.json +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/fixtures/evaluation/plain-block.step +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/fixtures/evaluation/topology-a.step +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/fixtures/evaluation/topology-b.step +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/fixtures/grm03_thumbwheel_drive_screw.step +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/fixtures/grm04_drive_plate.step +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/fixtures/if_step_flat_across_cylinder.step +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/fixtures/issue_1058_wheel_rh.step +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/fixtures/issue_909_basic_part_design_017_body.step +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/fixtures/issue_915_case_study_2.step +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/fixtures/nist_ctc_01_asme1_ap203.stp +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/fixtures/nist_ctc_01_asme1_ap242.stp +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/fixtures/nist_ctc_02_asme1_ap203.stp +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/fixtures/nist_ctc_02_asme1_ap242.stp +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/fixtures/nist_ctc_03_asme1_ap203.stp +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/fixtures/nist_ctc_03_asme1_ap242.stp +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/fixtures/nist_ctc_04_asme1_ap203.stp +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/fixtures/nist_ctc_04_asme1_ap242.stp +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/fixtures/nist_ctc_05_asme1_ap203.stp +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/fixtures/nist_ctc_05_asme1_ap242.stp +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/fixtures/tuner_jig_blind_obround_pockets.step +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/refactor_golden/bracket_section.json +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/refactor_golden/centered_rebate.json +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/refactor_golden/chamfered.json +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/refactor_golden/filleted.json +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/refactor_golden/flange_dense.json +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/refactor_golden/grid_plate.json +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/refactor_golden/grooved_shaft.json +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/refactor_golden/hex_bar.json +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/refactor_golden/holed_slot.json +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/refactor_golden/pocketed.json +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/refactor_golden/prismatic_ladder.json +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/refactor_golden/scattered_plate.json +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/refactor_golden/side_drilled.json +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/refactor_golden/turned_stepped.json +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_add_dimension.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_adr0018_arrangement_gate.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_adr0018_view_routing.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_adr0018_view_selection.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_agents_guide.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_api_docs.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_architecture_docs.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_audit_differential.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_balloon_ring_standoff.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_build_profile_harness.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_carve_free_position_callers.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_compiled_plan_boundary.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_counting_calls.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_declare.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_declared_recognition_gate.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_dense_sheet_canary.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_deprecation_dates.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_detect_once.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_detect_registry.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_dimension_role_vocabulary.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_drawing_encapsulation.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_e2e_slice.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_e2e_standards.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_export_dxf_curves.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_export_dxf_zoom.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_external_recognition_boundary.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_fillets_adjacency.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_fits.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_flat_completeness.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_flat_stock_identity.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_flat_stock_opposition.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_gdt_placement.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_grid_lattice_convention.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_import_boundaries.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_iso_nts_caption_placement.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_1000_envelope_reuse.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_1058_wheel_profile.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_1073_cross_body_patterns.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_1082_polygonal_stock.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_1086_declared_gears.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_1116_ap242_hole_tolerance_lowering.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_1130_view_planning_evidence.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_1142_hole_leader_labels.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_1143_hole_completeness.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_1144_transactional_hole_table.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_1146_scale_completeness.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_1147_legibility_pair_aggregation.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_1153_contradictory_dimensions.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_1154_one_owner_per_measurement.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_1155_grm04_sheet_use.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_1176_quality_fidelity.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_1177_angular_dimensions.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_1187_unroutable_leaders.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_1188_per_view_assignment.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_1190_section_decision.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_1196_deterministic_view_names.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_1202_observed_drawing_consumer.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_1204_multiscale_view_issues.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_1209_linear_pmi_witnesses.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_1215_envelope_tolerance.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_1215_no_approved_tolerance_is_dropped.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_1216_callout_reservation_measures_ink.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_1216_pairwise_across_scale_groups.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_1217_claimed_representations.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_1217_the_facility_is_shared.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_1219_location_claims_its_own_axis.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_1229_ledger_member_keying.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_1240_iso_above_strip_visibility.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_1254_turned_chamfers.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_1260_view_constraints.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_1276_turned_leader_targets.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_1281_turned_fillets.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_1312_engine_costs.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_367_leader_ink.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_676_polygonal_boss.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_676_polygonal_boss_declare.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_740_leader_assignment.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_798_floor_cardinality.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_798_material_field.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_798_silhouette_lint.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_885_prismatic_coverage.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_909_sloped_profile.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_917_open_channel.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_924_zero_length_shoulders.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_955_height_contingency.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_issue_958_cross_solid_recognition.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_label_provenance.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_layout.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_layout_cleanliness.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_layout_hypothesis.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_layout_property.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_leader_footprint.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_lint_box_cache.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_lint_reconciliation.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_linting.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_location_vocabulary.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_note_verb.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_object_aspects.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_occupancy_boxes.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_parameter_id.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_part_model.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_pitch_dim_footprint.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_pmi.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_pmi_datum_lowering.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_pmi_gtol_lowering.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_pmi_part21.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_pocket_pattern.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_pocket_pattern_recognition.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_principal_profile_classifier.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_private_test_attr_reads.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_private_test_imports.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_quality_components.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_recogniser_adoption.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_recogniser_contract.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_recognition.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_recognition_manifest.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_recognition_result.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_refactor_golden.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_registry.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_render_seam.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_repack_geometry_seam.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_score.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_script_detail_parity.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_shared_box_memo.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_sheet_emit.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_sheet_gdt.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_sheet_identity_invariant.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_sheet_notes.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_sheet_of.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_sheet_section.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_sheet_tables.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_slanted_blind_step.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_slot_completeness.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_slot_pattern.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_slot_pattern_recognition.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_soft_deprecation.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_solve_trace.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_step_analysis_evaluation.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_suppression_ledger.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_suppression_marks.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_tolerances.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_turned_steps.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_view_plan.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_witness_label_reconciliation.py +0 -0
- {draftwright-0.4.13 → draftwright-0.4.14}/tests/test_workflows.py +0 -0
|
@@ -2,8 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- New lint code `annotation_ink_overlap`: reports another annotation's line-work —
|
|
8
|
+
a dimension line, an extension line or a leader shaft — drawn through a label's
|
|
9
|
+
text, which `annotation_overlap` cannot see because it compares label boxes to
|
|
10
|
+
each other and to nothing else. Measured as the longest connected stroke path
|
|
11
|
+
crossing the label's exact polygon (or its axis-aligned box), in millimetres,
|
|
12
|
+
with a 0.5 mm floor. It scores on legibility, so
|
|
13
|
+
dense sheets that previously scored clean now score lower — 17 findings on
|
|
14
|
+
NIST CTC-02 AP203 alone, every sampled one confirmed a real defect by rendering.
|
|
15
|
+
Repair does not yet act on it (#1333) and the corridor solve does not yet avoid
|
|
16
|
+
it (#1334) (#1321, #1332).
|
|
17
|
+
|
|
5
18
|
### Changed
|
|
6
19
|
|
|
20
|
+
- `annotation_overlap` now reports the whole defect when the same pair also draws
|
|
21
|
+
line-work through one of the two labels: the message names the crosser, the label
|
|
22
|
+
crossed and the millimetres, and replaces the "use `label_offset_x`" remedy, which
|
|
23
|
+
is the advice that case contradicts. `LintIssue.suggestion` is `None` for those
|
|
24
|
+
issues rather than a snippet that re-places the text (#1321, #1332).
|
|
25
|
+
|
|
7
26
|
- Recogniser integration now uses only the exact `b123d-recognisers` dependency in
|
|
8
27
|
`pyproject.toml` and `uv.lock`. The separate release-evidence record, dependency-bump workflow,
|
|
9
28
|
candidate-checkout harness, and release-time capability-version rewrites have been removed;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: draftwright
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.14
|
|
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
|
|
@@ -685,7 +685,7 @@ Classifier: Topic :: Scientific/Engineering
|
|
|
685
685
|
Requires-Python: <3.15,>=3.10
|
|
686
686
|
Requires-Dist: antlr4-python3-runtime<4.10
|
|
687
687
|
Requires-Dist: b123d-recognisers==0.3.1
|
|
688
|
-
Requires-Dist: build123d-drafting-helpers>=0.14.
|
|
688
|
+
Requires-Dist: build123d-drafting-helpers>=0.14.2
|
|
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'
|
|
691
691
|
Requires-Dist: numpy>=1.24
|
|
@@ -64,3 +64,34 @@ pass.
|
|
|
64
64
|
`bosses` is the fully consumed reference family. `repeating-radial-profiles` is the opposite
|
|
65
65
|
reference: it remains geometry-only critique evidence for a separately authored gear declaration,
|
|
66
66
|
with no inferred gear feature added to fill the table.
|
|
67
|
+
|
|
68
|
+
## Passage compatibility boundary
|
|
69
|
+
|
|
70
|
+
The installed `b123d-recognisers==0.3.1` release already contains the `passages` family introduced
|
|
71
|
+
in 0.2.6. Draftwright declares its schema-v1 `Passage` output exhaustively but deliberately keeps
|
|
72
|
+
the family `unsupported`, with the drafting decision tracked by issue #1245. This is a truthful
|
|
73
|
+
consumer disposition: the geometry remains visible in the aggregate inventory, while Draftwright
|
|
74
|
+
does not invent an IR feature, DSL declaration, generated code, drawing annotation, or completeness
|
|
75
|
+
requirement for it.
|
|
76
|
+
|
|
77
|
+
Draftwright's exact dependency pin must remain on a reviewed released version in the interval
|
|
78
|
+
`>=0.2.6,<0.4.0` until the separately reviewed F4b transition is ready. The lower boundary ensures
|
|
79
|
+
the installed manifest really contains `passages`; the upper boundary prevents accidental adoption
|
|
80
|
+
of the planned 0.4 compatibility change.
|
|
81
|
+
|
|
82
|
+
The reviewed future 0.4 model is a distinct migration, not latent behavior in this declaration:
|
|
83
|
+
|
|
84
|
+
- `SectionPassage` will be the authoritative physical output and aggregate census source;
|
|
85
|
+
- legacy `Passage` values will be an accepted-only compatibility projection;
|
|
86
|
+
- `recognise_passages(..., ledger=...)` will be a fail-loud unavailable compatibility operation;
|
|
87
|
+
- the writer-free `recognise_passages` name will remain public but non-authoritative; and
|
|
88
|
+
- rich split-junction passages can supersede a currently rendered Slot claim, moving ownership to
|
|
89
|
+
the Passage family through `SLOT_SUPERSEDED_BY_PASSAGE`; and
|
|
90
|
+
- the capability contract must represent those API and per-output roles exhaustively before the
|
|
91
|
+
dependency pin can cross 0.4.
|
|
92
|
+
|
|
93
|
+
That migration must update the package release, Draftwright adapter and declaration model,
|
|
94
|
+
validator, tests, exact pin, and lockfile together. The current validator remains unchanged and
|
|
95
|
+
fail closed; there is no forward declaration or stale-manifest exception. Crossing 0.4 also
|
|
96
|
+
requires #1245 to decide what those newly Passage-owned occurrences draw, or an explicit reviewed
|
|
97
|
+
decision to accept the resulting loss of the existing Slot callout.
|
|
@@ -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.14"
|
|
8
8
|
description = "Automated technical-drawing generation for build123d"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { file = "LICENSE" }
|
|
@@ -17,7 +17,7 @@ dependencies = [
|
|
|
17
17
|
# 3.13+. Mirrors pzfreo/build123d-mcp's dependency handling.
|
|
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
|
-
"build123d-drafting-helpers>=0.14.
|
|
20
|
+
"build123d-drafting-helpers>=0.14.2",
|
|
21
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
|
|
@@ -358,7 +358,10 @@ _QUOTED_RE = re.compile(r"'([^']*)'")
|
|
|
358
358
|
|
|
359
359
|
@functools.lru_cache(maxsize=512)
|
|
360
360
|
def _text_size(
|
|
361
|
-
text: str,
|
|
361
|
+
text: str,
|
|
362
|
+
font_size: float,
|
|
363
|
+
font_path: str | None = PLEX_MONO,
|
|
364
|
+
font: str = "Arial",
|
|
362
365
|
) -> tuple[float, float]:
|
|
363
366
|
"""Measured rendered (width, height) (page-mm) of *text* at *font_size*.
|
|
364
367
|
|
|
@@ -19,17 +19,21 @@ from typing import Any
|
|
|
19
19
|
from build123d_drafting.helpers import DEFAULT_FONT_PATH, Dimension, Note, SafeDimension
|
|
20
20
|
|
|
21
21
|
from draftwright._core import ( # noqa: F401 — _anno_box re-exported (#700)
|
|
22
|
+
_MARGIN,
|
|
22
23
|
_anno_box,
|
|
23
24
|
_decode_hole_location_fact,
|
|
25
|
+
_dim,
|
|
24
26
|
_text_size,
|
|
25
27
|
place_annotation,
|
|
26
28
|
)
|
|
27
29
|
from draftwright._geometry import ( # noqa: F401
|
|
28
30
|
_boxes_overlap,
|
|
31
|
+
_segment_clip_extent,
|
|
29
32
|
_segment_clips_box,
|
|
30
33
|
_segment_crosses_box,
|
|
31
34
|
)
|
|
32
35
|
from draftwright.layout import StripCandidate, plan_strip
|
|
36
|
+
from draftwright.linting.ink_overlap import MIN_CROSSING_MM, crossable_region, crossing_length
|
|
33
37
|
from draftwright.linting.issues import LintIssue
|
|
34
38
|
from draftwright.linting.structural import (
|
|
35
39
|
_ann_box,
|
|
@@ -40,6 +44,9 @@ from draftwright.model.compiled import resolve_feature
|
|
|
40
44
|
_log = logging.getLogger(__name__)
|
|
41
45
|
|
|
42
46
|
|
|
47
|
+
_LABEL_INK_CLEARANCE_MM = 0.25
|
|
48
|
+
|
|
49
|
+
|
|
43
50
|
def _with_hole_location_coverage(annotation, coverage):
|
|
44
51
|
"""Attach exact compiler location/member facts to a rendered annotation.
|
|
45
52
|
|
|
@@ -1458,6 +1465,321 @@ def box_within_page_and_clear(bb, page_box, obstacles) -> bool:
|
|
|
1458
1465
|
)
|
|
1459
1466
|
|
|
1460
1467
|
|
|
1468
|
+
def prevent_dimension_label_ink(
|
|
1469
|
+
dimensions,
|
|
1470
|
+
*,
|
|
1471
|
+
page=None,
|
|
1472
|
+
immutable=(),
|
|
1473
|
+
obstacles=(),
|
|
1474
|
+
):
|
|
1475
|
+
"""Choose small along-line label offsets for a just-built dimension batch.
|
|
1476
|
+
|
|
1477
|
+
Corridor candidates used to reserve only their stacking tier while they were being
|
|
1478
|
+
solved. Their *eventual* decomposed ink became an obstacle after commit, but siblings
|
|
1479
|
+
built in the same batch could therefore put an extension line or an external-arrow tip
|
|
1480
|
+
through one another's labels (#1334). Immediate step-length chains have the identical
|
|
1481
|
+
gap: they build the whole row before any member is visible to strip occupancy.
|
|
1482
|
+
|
|
1483
|
+
This is the shared, bounded candidate-selection seam for both producers. It inspects
|
|
1484
|
+
the same public ``segments``/exact-label-region arithmetic as the lint backstop.
|
|
1485
|
+
A clean batch returns the same objects immediately. Only a conflicting batch explores
|
|
1486
|
+
a bounded set of analytically-derived label centres, rebuilding the selected survivors
|
|
1487
|
+
through their ``_dw_spec``. No full lint scan and no CAD boolean participates.
|
|
1488
|
+
|
|
1489
|
+
The label *centre* stays within half a millimetre of its measured span (rather than
|
|
1490
|
+
requiring the whole label to fit inside it). That distinction matters for short
|
|
1491
|
+
dimensions: their text is wider than the measured span by construction, yet a small
|
|
1492
|
+
shift can keep a shared witness out of the digits without making the label read as its
|
|
1493
|
+
neighbour's. If the bounded choices cannot improve the batch, the natural deterministic
|
|
1494
|
+
placement survives and the normal ``annotation_ink_overlap`` lint remains explicit
|
|
1495
|
+
evidence of the infeasible fallback. Names in *immutable* are never shifted (pins win).
|
|
1496
|
+
Fixed *obstacles* do not make an existing contact this local batch's responsibility, but
|
|
1497
|
+
no selected move may introduce a new label contact with one.
|
|
1498
|
+
|
|
1499
|
+
Returns ``[(name, dimension), ...]`` in input order.
|
|
1500
|
+
"""
|
|
1501
|
+
|
|
1502
|
+
original = list(dimensions)
|
|
1503
|
+
if len(original) < 2:
|
|
1504
|
+
return original
|
|
1505
|
+
immutable = set(immutable)
|
|
1506
|
+
obstacles = tuple(obstacles)
|
|
1507
|
+
|
|
1508
|
+
def _axis_info(dim):
|
|
1509
|
+
spec = getattr(dim, "_dw_spec", None)
|
|
1510
|
+
label = getattr(dim, "label_bbox", None)
|
|
1511
|
+
if spec is None or label is None:
|
|
1512
|
+
return None
|
|
1513
|
+
dx = float(spec.p2[0]) - float(spec.p1[0])
|
|
1514
|
+
dy = float(spec.p2[1]) - float(spec.p1[1])
|
|
1515
|
+
if min(abs(dx), abs(dy)) > 0.1 or max(abs(dx), abs(dy)) <= 1e-9:
|
|
1516
|
+
return None # rotated labels need polygonal motion, not an AABB-axis solve
|
|
1517
|
+
axis = 1 if abs(dy) > abs(dx) else 0
|
|
1518
|
+
other = 1 - axis
|
|
1519
|
+
return axis, other, spec
|
|
1520
|
+
|
|
1521
|
+
infos = [_axis_info(dim) for _name, dim in original]
|
|
1522
|
+
if not any(info is not None for info in infos):
|
|
1523
|
+
return original
|
|
1524
|
+
|
|
1525
|
+
box_cache: dict[int, Any] = {}
|
|
1526
|
+
segments_cache: dict[int, Any] = {}
|
|
1527
|
+
tips_cache: dict[int, Any] = {}
|
|
1528
|
+
|
|
1529
|
+
def _box(dim):
|
|
1530
|
+
key = id(dim)
|
|
1531
|
+
if key in box_cache:
|
|
1532
|
+
return box_cache[key]
|
|
1533
|
+
raw = getattr(dim, "label_bbox", None)
|
|
1534
|
+
box_cache[key] = tuple(float(value) for value in raw) if raw is not None else None
|
|
1535
|
+
return box_cache[key]
|
|
1536
|
+
|
|
1537
|
+
def _segments(dim):
|
|
1538
|
+
key = id(dim)
|
|
1539
|
+
if key in segments_cache:
|
|
1540
|
+
return segments_cache[key]
|
|
1541
|
+
try:
|
|
1542
|
+
segments_cache[key] = tuple(
|
|
1543
|
+
(
|
|
1544
|
+
(float(first[0]), float(first[1])),
|
|
1545
|
+
(float(second[0]), float(second[1])),
|
|
1546
|
+
)
|
|
1547
|
+
for first, second in (getattr(dim, "segments", ()) or ())
|
|
1548
|
+
)
|
|
1549
|
+
except Exception: # noqa: BLE001 — optional metadata fails closed to lint
|
|
1550
|
+
segments_cache[key] = ()
|
|
1551
|
+
return segments_cache[key]
|
|
1552
|
+
|
|
1553
|
+
def _arrow_tips(dim, info):
|
|
1554
|
+
"""Dimension terminator tips from its measured endpoints and dim-line ordinate.
|
|
1555
|
+
|
|
1556
|
+
Helpers expose line pieces but not the filled arrow triangles. Their tips remain
|
|
1557
|
+
exactly the two measured ordinates on the label's dimension line; treating the tip
|
|
1558
|
+
attachment as label-blocking catches the short-span arrow-through-digit case without
|
|
1559
|
+
inflating every shaft into a coarse full-geometry box.
|
|
1560
|
+
"""
|
|
1561
|
+
key = id(dim)
|
|
1562
|
+
if key in tips_cache:
|
|
1563
|
+
return tips_cache[key]
|
|
1564
|
+
if info is None or (label := _box(dim)) is None:
|
|
1565
|
+
return ()
|
|
1566
|
+
axis, other, spec = info
|
|
1567
|
+
line = (label[other] + label[other + 2]) / 2.0
|
|
1568
|
+
result = []
|
|
1569
|
+
for point in (spec.p1, spec.p2):
|
|
1570
|
+
tip = [0.0, 0.0]
|
|
1571
|
+
tip[axis] = float(point[axis])
|
|
1572
|
+
tip[other] = line
|
|
1573
|
+
result.append(tuple(tip))
|
|
1574
|
+
tips_cache[key] = tuple(result)
|
|
1575
|
+
return tips_cache[key]
|
|
1576
|
+
|
|
1577
|
+
natural_obstacle_hits = [
|
|
1578
|
+
frozenset(
|
|
1579
|
+
obstacle_index
|
|
1580
|
+
for obstacle_index, obstacle in enumerate(obstacles)
|
|
1581
|
+
if label is not None and _boxes_overlap(label, obstacle)
|
|
1582
|
+
)
|
|
1583
|
+
for _name, dim in original
|
|
1584
|
+
for label in (_box(dim),)
|
|
1585
|
+
]
|
|
1586
|
+
|
|
1587
|
+
def _conflicts(batch):
|
|
1588
|
+
"""Stable conflict tokens; their count is the local solve's primary objective."""
|
|
1589
|
+
labels = [_box(dim) for _name, dim in batch]
|
|
1590
|
+
segs = [_segments(dim) for _name, dim in batch]
|
|
1591
|
+
regions = [
|
|
1592
|
+
crossable_region(label, item=dim, segments=segments)
|
|
1593
|
+
for (_name, dim), label, segments in zip(batch, labels, segs, strict=True)
|
|
1594
|
+
]
|
|
1595
|
+
tips = [_arrow_tips(dim, info) for (_name, dim), info in zip(batch, infos, strict=True)]
|
|
1596
|
+
found: set[tuple] = set()
|
|
1597
|
+
for target, (label, region) in enumerate(zip(labels, regions, strict=True)):
|
|
1598
|
+
if label is None or region is None:
|
|
1599
|
+
continue
|
|
1600
|
+
# Helpers expose no arrow polygons. The foreign dimension's exact
|
|
1601
|
+
# attachment tips still participate, closing the line-metadata gap without
|
|
1602
|
+
# guessing the orientation of this dimension's own inside/outside arrows.
|
|
1603
|
+
for source, source_tips in enumerate(tips):
|
|
1604
|
+
if source == target:
|
|
1605
|
+
continue
|
|
1606
|
+
info = infos[target]
|
|
1607
|
+
if info is None:
|
|
1608
|
+
continue
|
|
1609
|
+
axis, other, _spec = info
|
|
1610
|
+
for tip_index, tip in enumerate(source_tips):
|
|
1611
|
+
if (
|
|
1612
|
+
label[axis] + _LABEL_INK_CLEARANCE_MM
|
|
1613
|
+
< tip[axis]
|
|
1614
|
+
< label[axis + 2] - _LABEL_INK_CLEARANCE_MM
|
|
1615
|
+
and label[other] - 1e-6 < tip[other] < label[other + 2] + 1e-6
|
|
1616
|
+
):
|
|
1617
|
+
found.add(("arrow", source, tip_index, target))
|
|
1618
|
+
for source, source_segments in enumerate(segs):
|
|
1619
|
+
if source == target:
|
|
1620
|
+
continue # the helper deliberately cuts its own line around its label
|
|
1621
|
+
if crossing_length(source_segments, region) >= MIN_CROSSING_MM:
|
|
1622
|
+
found.add(("line", source, target))
|
|
1623
|
+
for obstacle_index, obstacle in enumerate(obstacles):
|
|
1624
|
+
if obstacle_index not in natural_obstacle_hits[target] and _boxes_overlap(
|
|
1625
|
+
label, obstacle
|
|
1626
|
+
):
|
|
1627
|
+
found.add(("fixed", obstacle_index, target))
|
|
1628
|
+
for left, left_box in enumerate(labels):
|
|
1629
|
+
if left_box is None:
|
|
1630
|
+
continue
|
|
1631
|
+
for right in range(left + 1, len(labels)):
|
|
1632
|
+
right_box = labels[right]
|
|
1633
|
+
if right_box is not None and _boxes_overlap(left_box, right_box):
|
|
1634
|
+
found.add(("label", left, right))
|
|
1635
|
+
return frozenset(found)
|
|
1636
|
+
|
|
1637
|
+
natural_centres = []
|
|
1638
|
+
for (_name, dim), info in zip(original, infos, strict=True):
|
|
1639
|
+
label = _box(dim)
|
|
1640
|
+
natural_centres.append(
|
|
1641
|
+
None if info is None or label is None else (label[info[0]] + label[info[0] + 2]) / 2.0
|
|
1642
|
+
)
|
|
1643
|
+
|
|
1644
|
+
def _objective(batch):
|
|
1645
|
+
conflicts = _conflicts(batch)
|
|
1646
|
+
fixed_conflicts = sum(conflict[0] == "fixed" for conflict in conflicts)
|
|
1647
|
+
offsets = []
|
|
1648
|
+
for (_name, dim), info, natural in zip(batch, infos, natural_centres, strict=True):
|
|
1649
|
+
label = _box(dim)
|
|
1650
|
+
offsets.append(
|
|
1651
|
+
0.0
|
|
1652
|
+
if info is None or natural is None or label is None
|
|
1653
|
+
else abs((label[info[0]] + label[info[0] + 2]) / 2.0 - natural)
|
|
1654
|
+
)
|
|
1655
|
+
return (
|
|
1656
|
+
fixed_conflicts,
|
|
1657
|
+
len(conflicts),
|
|
1658
|
+
round(sum(offsets), 9),
|
|
1659
|
+
round(max(offsets, default=0.0), 9),
|
|
1660
|
+
tuple(round(value, 9) for value in offsets),
|
|
1661
|
+
), conflicts
|
|
1662
|
+
|
|
1663
|
+
current = list(original)
|
|
1664
|
+
current_objective, conflicts = _objective(current)
|
|
1665
|
+
if not conflicts:
|
|
1666
|
+
return current # overwhelmingly common path: no extra Dimension construction
|
|
1667
|
+
|
|
1668
|
+
cache: dict[tuple[int, float], Any] = {}
|
|
1669
|
+
|
|
1670
|
+
def _rebuild(index, centre):
|
|
1671
|
+
key = (index, round(centre, 6))
|
|
1672
|
+
if key in cache:
|
|
1673
|
+
return cache[key]
|
|
1674
|
+
name, dim = original[index]
|
|
1675
|
+
info = infos[index]
|
|
1676
|
+
natural = natural_centres[index]
|
|
1677
|
+
if info is None or natural is None:
|
|
1678
|
+
return dim
|
|
1679
|
+
axis, _other, spec = info
|
|
1680
|
+
direction = 1.0 if float(spec.p2[axis]) >= float(spec.p1[axis]) else -1.0
|
|
1681
|
+
kwargs = dict(spec.kwargs)
|
|
1682
|
+
kwargs["label_offset_x"] = (
|
|
1683
|
+
kwargs.get("label_offset_x", 0.0) + (centre - natural) * direction
|
|
1684
|
+
)
|
|
1685
|
+
rebuilt = _dim(spec.p1, spec.p2, spec.side, spec.distance, spec.draft, **kwargs)
|
|
1686
|
+
# The producer attaches semantic evidence before the corridor commits the
|
|
1687
|
+
# survivor. Rebuilding only the rendered Dimension must not erase that evidence:
|
|
1688
|
+
# hole-table escalation and coverage lint read these riders from the final object.
|
|
1689
|
+
# Copy semantic namespaces, never helper geometry internals; `_dw_spec` belongs to
|
|
1690
|
+
# the rebuilt placement and must remain the one `_dim` just created.
|
|
1691
|
+
for attr, value in vars(dim).items():
|
|
1692
|
+
if attr.startswith("covers_") or (attr.startswith("_dw_") and attr != "_dw_spec"):
|
|
1693
|
+
setattr(rebuilt, attr, value)
|
|
1694
|
+
cache[key] = rebuilt
|
|
1695
|
+
return rebuilt
|
|
1696
|
+
|
|
1697
|
+
def _centres_for(index, batch):
|
|
1698
|
+
info = infos[index]
|
|
1699
|
+
target = batch[index][1]
|
|
1700
|
+
label = _box(target)
|
|
1701
|
+
if info is None or label is None:
|
|
1702
|
+
return ()
|
|
1703
|
+
axis, other, spec = info
|
|
1704
|
+
current_centre = (label[axis] + label[axis + 2]) / 2.0
|
|
1705
|
+
half = (label[axis + 2] - label[axis]) / 2.0
|
|
1706
|
+
# The centre remains attached to its own measured span. A half-millimetre
|
|
1707
|
+
# overhang admits the minimum clear position for text wider than a short span.
|
|
1708
|
+
lo, hi = sorted((float(spec.p1[axis]), float(spec.p2[axis])))
|
|
1709
|
+
lo -= MIN_CROSSING_MM
|
|
1710
|
+
hi += MIN_CROSSING_MM
|
|
1711
|
+
if page is not None:
|
|
1712
|
+
lo = max(lo, float(page[axis]) + half)
|
|
1713
|
+
hi = min(hi, float(page[axis + 2]) - half)
|
|
1714
|
+
if lo > hi:
|
|
1715
|
+
return ()
|
|
1716
|
+
choices = {min(max(current_centre, lo), hi), lo, hi}
|
|
1717
|
+
for source, ((_name, dim), source_info) in enumerate(zip(batch, infos, strict=True)):
|
|
1718
|
+
source_label = _box(dim)
|
|
1719
|
+
if source != index and source_label is not None:
|
|
1720
|
+
# Existing labels are obstacles too; moving out of line-work must not
|
|
1721
|
+
# exchange one illegibility defect for text-on-text.
|
|
1722
|
+
if (
|
|
1723
|
+
source_label[other] < label[other + 2]
|
|
1724
|
+
and source_label[other + 2] > label[other]
|
|
1725
|
+
):
|
|
1726
|
+
choices.add(source_label[axis] - half - _LABEL_INK_CLEARANCE_MM)
|
|
1727
|
+
choices.add(source_label[axis + 2] + half + _LABEL_INK_CLEARANCE_MM)
|
|
1728
|
+
if source == index:
|
|
1729
|
+
continue
|
|
1730
|
+
for tip in _arrow_tips(dim, source_info):
|
|
1731
|
+
if label[other] - 1e-6 <= tip[other] <= label[other + 2] + 1e-6:
|
|
1732
|
+
choices.add(tip[axis] - half - _LABEL_INK_CLEARANCE_MM)
|
|
1733
|
+
choices.add(tip[axis] + half + _LABEL_INK_CLEARANCE_MM)
|
|
1734
|
+
# Clip each foreign segment only to this label's fixed-axis band. The
|
|
1735
|
+
# resulting projection is the exact interval its centre must clear.
|
|
1736
|
+
band = [-1e9, -1e9, 1e9, 1e9]
|
|
1737
|
+
band[other], band[other + 2] = label[other], label[other + 2]
|
|
1738
|
+
for segment in _segments(dim):
|
|
1739
|
+
clipped = _segment_clip_extent(segment[0], segment[1], tuple(band), pad=0.0)
|
|
1740
|
+
if clipped is None:
|
|
1741
|
+
continue
|
|
1742
|
+
choices.add(clipped[axis] - half - _LABEL_INK_CLEARANCE_MM)
|
|
1743
|
+
choices.add(clipped[axis + 2] + half + _LABEL_INK_CLEARANCE_MM)
|
|
1744
|
+
bounded = {min(max(value, lo), hi) for value in choices if math.isfinite(value)}
|
|
1745
|
+
# Bound exceptional work. Nearest candidates are the meaningful drafting
|
|
1746
|
+
# alternatives; the two association bounds remain present explicitly.
|
|
1747
|
+
nearest = sorted(bounded, key=lambda value: (abs(value - current_centre), value))[:16]
|
|
1748
|
+
return tuple(dict.fromkeys([*nearest, lo, hi]))
|
|
1749
|
+
|
|
1750
|
+
# Deterministic steepest descent. Every accepted move strictly improves the
|
|
1751
|
+
# lexicographic objective, so it terminates even when the layout is infeasible.
|
|
1752
|
+
for _iteration in range(max(1, 2 * len(current))):
|
|
1753
|
+
involved: set[int] = set()
|
|
1754
|
+
for conflict in conflicts:
|
|
1755
|
+
if conflict[0] == "arrow":
|
|
1756
|
+
involved.update((conflict[1], conflict[3])) # source + crossed label
|
|
1757
|
+
elif conflict[0] == "line":
|
|
1758
|
+
involved.update((conflict[1], conflict[2])) # source + crossed label
|
|
1759
|
+
elif conflict[0] == "fixed":
|
|
1760
|
+
involved.add(conflict[2])
|
|
1761
|
+
else: # label/label
|
|
1762
|
+
involved.update((conflict[1], conflict[2]))
|
|
1763
|
+
best = None
|
|
1764
|
+
for index in sorted(involved):
|
|
1765
|
+
name, _dim_obj = original[index]
|
|
1766
|
+
if name in immutable or infos[index] is None:
|
|
1767
|
+
continue
|
|
1768
|
+
for centre in _centres_for(index, current):
|
|
1769
|
+
trial = list(current)
|
|
1770
|
+
trial[index] = (name, _rebuild(index, centre))
|
|
1771
|
+
objective, trial_conflicts = _objective(trial)
|
|
1772
|
+
key = (objective, index, round(centre, 9))
|
|
1773
|
+
if objective < current_objective and (best is None or key < best[0]):
|
|
1774
|
+
best = (key, trial, objective, trial_conflicts)
|
|
1775
|
+
if best is None:
|
|
1776
|
+
break
|
|
1777
|
+
_key, current, current_objective, conflicts = best
|
|
1778
|
+
if not conflicts:
|
|
1779
|
+
break
|
|
1780
|
+
return current
|
|
1781
|
+
|
|
1782
|
+
|
|
1461
1783
|
# ── The corridor priority ladder (#894) ───────────────────────────────────────
|
|
1462
1784
|
# `CorridorCandidate.priority` is an over-capacity survival rank, and a rank only
|
|
1463
1785
|
# means anything RELATIVE to the other rungs. Defining the whole ladder here — beside
|
|
@@ -2126,6 +2448,19 @@ def place_strip_candidates(
|
|
|
2126
2448
|
tp["free_segments"] = [list(s) for s in segs]
|
|
2127
2449
|
todo = list(cands)
|
|
2128
2450
|
|
|
2451
|
+
def _real_box_conflict(name, real):
|
|
2452
|
+
"""Return the hard-obstacle reason for a built survivor, if any."""
|
|
2453
|
+
if real is None:
|
|
2454
|
+
return None
|
|
2455
|
+
if not force and _box_hits(real, blockers):
|
|
2456
|
+
return "real_box_corridor_blocked"
|
|
2457
|
+
fb = (forbid or {}).get(name)
|
|
2458
|
+
if fb is not None and _box_hits(real, (fb,)):
|
|
2459
|
+
return "real_box_forbid"
|
|
2460
|
+
if _box_hits(real, out_of_band):
|
|
2461
|
+
return "real_box_out_of_band"
|
|
2462
|
+
return None
|
|
2463
|
+
|
|
2129
2464
|
def _take_for_segment(items, n):
|
|
2130
2465
|
if len(items) <= n:
|
|
2131
2466
|
return items, []
|
|
@@ -2210,6 +2545,7 @@ def place_strip_candidates(
|
|
|
2210
2545
|
accepted.append(((name, build), pos))
|
|
2211
2546
|
return accepted, rejected
|
|
2212
2547
|
|
|
2548
|
+
solved = []
|
|
2213
2549
|
for seg_lo, seg_hi in segs:
|
|
2214
2550
|
if not todo:
|
|
2215
2551
|
break
|
|
@@ -2231,43 +2567,60 @@ def place_strip_candidates(
|
|
|
2231
2567
|
for (name, build), pos in accepted:
|
|
2232
2568
|
dim = build(pos)
|
|
2233
2569
|
real = _geom_box(dim)
|
|
2234
|
-
if
|
|
2235
|
-
if
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
)
|
|
2240
|
-
rejected_total.append((name, build))
|
|
2241
|
-
continue
|
|
2242
|
-
fb = (forbid or {}).get(name)
|
|
2243
|
-
if fb is not None and _box_hits(real, (fb,)):
|
|
2244
|
-
if tp is not None:
|
|
2245
|
-
tp["rejected"].append({"name": name, "reason": "real_box_forbid"})
|
|
2246
|
-
rejected_total.append((name, build))
|
|
2247
|
-
continue
|
|
2248
|
-
# A survivor whose real geometry escaped the batch's predicted
|
|
2249
|
-
# perpendicular band could overlap an obstacle the carve was never
|
|
2250
|
-
# shown (review #679) — the one collision class the stacking-axis
|
|
2251
|
-
# model cannot tolerate. Never fires while predictions are accurate.
|
|
2252
|
-
if _box_hits(real, out_of_band):
|
|
2253
|
-
if tp is not None:
|
|
2254
|
-
tp["rejected"].append({"name": name, "reason": "real_box_out_of_band"})
|
|
2255
|
-
rejected_total.append((name, build))
|
|
2256
|
-
continue
|
|
2570
|
+
if reason := _real_box_conflict(name, real):
|
|
2571
|
+
if tp is not None:
|
|
2572
|
+
tp["rejected"].append({"name": name, "reason": reason})
|
|
2573
|
+
rejected_total.append((name, build))
|
|
2574
|
+
continue
|
|
2257
2575
|
placed.append((name, dim))
|
|
2258
2576
|
if tp is not None:
|
|
2259
2577
|
tp["placed"].append({"name": name, "pos": pos})
|
|
2260
2578
|
todo = todo + rejected_total
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2579
|
+
solved.extend(placed)
|
|
2580
|
+
|
|
2581
|
+
if len(solved) > 1:
|
|
2582
|
+
assert len({name for name, _dim_obj in solved}) == len(solved), (
|
|
2583
|
+
"strip survivor names must be unique before label selection"
|
|
2584
|
+
)
|
|
2585
|
+
natural_solved = dict(solved)
|
|
2586
|
+
adjusted = prevent_dimension_label_ink(
|
|
2587
|
+
solved,
|
|
2588
|
+
page=(
|
|
2589
|
+
(
|
|
2590
|
+
_MARGIN,
|
|
2591
|
+
_MARGIN,
|
|
2592
|
+
float(dwg.page_w) - _MARGIN,
|
|
2593
|
+
float(dwg.page_h) - _MARGIN,
|
|
2594
|
+
)
|
|
2595
|
+
if hasattr(dwg, "page_w") and hasattr(dwg, "page_h")
|
|
2596
|
+
else None
|
|
2597
|
+
),
|
|
2598
|
+
immutable={name for name, _dim_obj in solved if (anchored or {}).get(name, False)},
|
|
2599
|
+
)
|
|
2600
|
+
# A label shift normally stays inside the dimension's measured span and
|
|
2601
|
+
# therefore inside its already-validated footprint. Keep the validation
|
|
2602
|
+
# contract explicit nevertheless: if an unusual helper/style grows the real
|
|
2603
|
+
# box into a hard obstacle, retain the natural survivor. The lint backstop then
|
|
2604
|
+
# records the infeasible ink conflict instead of this prevention step trading
|
|
2605
|
+
# it for a structural collision.
|
|
2606
|
+
solved = []
|
|
2607
|
+
for name, dim in adjusted:
|
|
2608
|
+
natural = natural_solved[name]
|
|
2609
|
+
if dim is natural:
|
|
2610
|
+
solved.append((name, dim))
|
|
2611
|
+
continue
|
|
2612
|
+
real = _geom_box(dim)
|
|
2613
|
+
solved.append((name, natural if _real_box_conflict(name, real) else dim))
|
|
2614
|
+
for name, dim in solved:
|
|
2615
|
+
# Record feature provenance (ADR 0010): the drain-time seam for corridor-placed
|
|
2616
|
+
# dims — `features` maps this batch's names to their source IR feature.
|
|
2617
|
+
ctx.place(
|
|
2618
|
+
dim,
|
|
2619
|
+
name,
|
|
2620
|
+
view=view,
|
|
2621
|
+
feature=(features or {}).get(name),
|
|
2622
|
+
measurement=(measurements or {}).get(name),
|
|
2623
|
+
)
|
|
2271
2624
|
if tp is not None:
|
|
2272
2625
|
tp["unplaced"] = [n for n, _ in todo]
|
|
2273
2626
|
trace.end_pass(tp) # folds a standalone pass's items; no-op when corridor-nested
|
|
@@ -81,6 +81,7 @@ from draftwright.annotations._common import (
|
|
|
81
81
|
full_strip_message,
|
|
82
82
|
leader_callout_geometry,
|
|
83
83
|
place_strip_candidates,
|
|
84
|
+
prevent_dimension_label_ink,
|
|
84
85
|
register_corridor,
|
|
85
86
|
strip_free_span,
|
|
86
87
|
strip_obstacles,
|
|
@@ -3686,8 +3687,22 @@ def _draw_step_chain(
|
|
|
3686
3687
|
)
|
|
3687
3688
|
)
|
|
3688
3689
|
|
|
3689
|
-
# Room guard: if any dim would fall off the drawable page, place NONE.
|
|
3690
3690
|
page = (_MARGIN, _MARGIN, dwg.page_w - _MARGIN, dwg.page_h - _MARGIN)
|
|
3691
|
+
# The chain is one placement batch: until commit, no sibling's extension line or
|
|
3692
|
+
# terminator exists in strip occupancy. Select small along-line label offsets against
|
|
3693
|
+
# the complete batch before the room guard (#1334). Measurement provenance stays paired
|
|
3694
|
+
# by name; only the rendered Dimension survivor changes.
|
|
3695
|
+
measurements_by_name = {name: measurements for name, _dim_obj, measurements in candidates}
|
|
3696
|
+
candidates = [
|
|
3697
|
+
(name, dim, measurements_by_name[name])
|
|
3698
|
+
for name, dim in prevent_dimension_label_ink(
|
|
3699
|
+
[(name, dim) for name, dim, _measurements in candidates],
|
|
3700
|
+
page=page,
|
|
3701
|
+
obstacles=strip_obstacles(dwg, view=view, crossable=CROSSABLE_TYPES),
|
|
3702
|
+
)
|
|
3703
|
+
]
|
|
3704
|
+
|
|
3705
|
+
# Room guard: if any dim would fall off the drawable page, place NONE.
|
|
3691
3706
|
for _, dim, _measurements in candidates:
|
|
3692
3707
|
box = _anno_box(dim)
|
|
3693
3708
|
if box is not None and not (
|