draftwright 0.4.7__tar.gz → 0.4.8__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.7 → draftwright-0.4.8}/CHANGELOG.md +40 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/PKG-INFO +1 -1
- {draftwright-0.4.7 → draftwright-0.4.8}/docs/adr/README.md +1 -1
- {draftwright-0.4.7 → draftwright-0.4.8}/pyproject.toml +1 -1
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/_geometry.py +91 -48
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/annotations/_common.py +56 -22
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/annotations/from_model.py +166 -13
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/annotations/holes.py +276 -8
- draftwright-0.4.8/src/draftwright/annotations/leaders.py +1928 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/annotations/orchestrator.py +10 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/annotations/sections.py +112 -31
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/drawing.py +8 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/layout.py +96 -17
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/linting/hole_coverage.py +3 -1
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/linting/issues.py +9 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/linting/quality.py +6 -18
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/recogniser_contract.py +1 -1
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/refactor_golden/bracket_section.json +19 -19
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/refactor_golden/grooved_shaft.json +14 -14
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/refactor_golden/pocketed.json +7 -7
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/refactor_golden/scattered_plate.json +42 -32
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/refactor_golden/side_drilled.json +8 -8
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_audit_differential.py +5 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_issue_1146_scale_completeness.py +3 -0
- draftwright-0.4.8/tests/test_issue_1166_cross_pass_feature_leaders.py +2343 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_issue_740_leader_assignment.py +133 -29
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_layout_cleanliness.py +4 -7
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_leader_footprint.py +10 -1
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_make_drawing.py +17 -12
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_pmi.py +4 -4
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_strip_layout.py +19 -10
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_two_repository_workflow.py +4 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/.gitignore +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/LICENSE +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/README.md +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/docs/reference/recogniser-capabilities.md +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/docs/reference/sheet.md +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/docs/research/1062-repeating-radial-profile-evidence.md +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/skills/SKILL.md +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/__init__.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/_core.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/_pmi_part21.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/_warnings.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/analysis.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/annotate.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/annotations/__init__.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/annotations/balloons.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/annotations/gears.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/audit.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/builder.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/cli.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/compose.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/evaluation/__init__.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/evaluation/step_analysis.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/export.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/fits.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/fonts/IBMPlexMono-Regular.ttf +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/fonts/IBMPlexSansCondensed-Regular.ttf +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/fonts/LICENSE-IBMPlexMono-OFL.txt +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/fonts/LICENSE-IBMPlexSansCondensed-OFL.txt +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/fonts/__init__.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/intents.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/linting/__init__.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/linting/channel_coverage.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/linting/coverage.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/linting/flat_coverage.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/linting/gear_coverage.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/linting/pmi_coverage.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/linting/polygonal_stock_coverage.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/linting/profiled_bore_coverage.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/linting/slot_coverage.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/linting/structural.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/linting/suggest.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/make_drawing.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/model/__init__.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/model/callout.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/model/compiled.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/model/declare.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/model/detect.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/model/ir.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/model/planner.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/pmi.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/projection.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/py.typed +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/recognition/__init__.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/recognition_cache.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/registry.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/repair.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/score.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/sheet.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/src/draftwright/sheet_emit.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/_kernel.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/_layout_sig.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/conftest.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/fixtures/evaluation/README.md +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/fixtures/evaluation/ambiguous-open-semicircle.step +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/fixtures/evaluation/blind-hole.step +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/fixtures/evaluation/corpus-v1.json +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/fixtures/evaluation/plain-block.step +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/fixtures/evaluation/topology-a.step +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/fixtures/evaluation/topology-b.step +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/fixtures/grm03_thumbwheel_drive_screw.step +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/fixtures/if_step_flat_across_cylinder.step +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/fixtures/issue_1058_wheel_rh.step +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/fixtures/issue_909_basic_part_design_017_body.step +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/fixtures/issue_915_case_study_2.step +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/fixtures/nist_ctc_01_asme1_ap203.stp +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/fixtures/nist_ctc_01_asme1_ap242.stp +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/fixtures/nist_ctc_02_asme1_ap203.stp +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/fixtures/nist_ctc_02_asme1_ap242.stp +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/fixtures/nist_ctc_03_asme1_ap203.stp +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/fixtures/nist_ctc_03_asme1_ap242.stp +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/fixtures/nist_ctc_04_asme1_ap203.stp +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/fixtures/nist_ctc_04_asme1_ap242.stp +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/fixtures/nist_ctc_05_asme1_ap203.stp +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/fixtures/nist_ctc_05_asme1_ap242.stp +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/fixtures/tuner_jig_blind_obround_pockets.step +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/refactor_golden/centered_rebate.json +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/refactor_golden/chamfered.json +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/refactor_golden/filleted.json +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/refactor_golden/flange_dense.json +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/refactor_golden/grid_plate.json +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/refactor_golden/hex_bar.json +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/refactor_golden/holed_slot.json +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/refactor_golden/prismatic_ladder.json +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/refactor_golden/turned_stepped.json +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_add_dimension.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_agents_guide.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_api_docs.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_architecture_docs.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_balloon_ring_standoff.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_carve_free_position_callers.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_compiled_plan_boundary.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_counting_calls.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_cross_repository_delivery_protocol.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_declare.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_declared_recognition_gate.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_dense_sheet_canary.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_deprecation_dates.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_detect_once.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_detect_registry.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_dimension_role_vocabulary.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_drawing_encapsulation.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_e2e_slice.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_e2e_standards.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_export_dxf_curves.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_export_dxf_zoom.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_external_recognition_boundary.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_fillets_adjacency.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_fits.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_flat_completeness.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_flat_stock_identity.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_flat_stock_opposition.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_gdt_placement.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_grid_lattice_convention.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_import_boundaries.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_issue_1000_envelope_reuse.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_issue_1058_wheel_profile.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_issue_1073_cross_body_patterns.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_issue_1082_polygonal_stock.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_issue_1086_declared_gears.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_issue_1142_hole_leader_labels.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_issue_1143_hole_completeness.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_issue_1144_transactional_hole_table.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_issue_1147_legibility_pair_aggregation.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_issue_367_leader_ink.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_issue_676_polygonal_boss.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_issue_676_polygonal_boss_declare.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_issue_885_prismatic_coverage.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_issue_909_sloped_profile.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_issue_915_dense_case.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_issue_916_pocket_leader.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_issue_917_open_channel.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_issue_955_height_contingency.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_issue_958_cross_solid_recognition.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_label_provenance.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_layout.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_layout_hypothesis.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_layout_property.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_lint_box_cache.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_lint_reconciliation.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_lint_structural.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_linting.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_location_vocabulary.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_note_verb.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_object_aspects.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_occupancy_boxes.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_parameter_id.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_part_model.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_pitch_dim_footprint.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_pmi_datum_lowering.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_pmi_gtol_lowering.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_pmi_part21.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_pocket_pattern.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_pocket_pattern_recognition.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_principal_profile_classifier.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_private_test_attr_reads.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_private_test_imports.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_quality_components.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_recogniser_capabilities.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_recogniser_contract.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_recognition.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_recognition_manifest.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_recognition_result.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_refactor_golden.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_registry.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_render_seam.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_repack_geometry_seam.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_score.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_script_detail_parity.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_shared_box_memo.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_sheet_emit.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_sheet_gdt.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_sheet_identity_invariant.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_sheet_notes.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_sheet_of.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_sheet_section.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_sheet_tables.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_slanted_blind_step.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_slot_completeness.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_slot_pattern.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_slot_pattern_recognition.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_soft_deprecation.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_solve_trace.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_step_analysis_evaluation.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_suppression_ledger.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_suppression_marks.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_tolerances.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_turned_steps.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_witness_label_reconciliation.py +0 -0
- {draftwright-0.4.7 → draftwright-0.4.8}/tests/test_workflows.py +0 -0
|
@@ -46,6 +46,46 @@
|
|
|
46
46
|
reached, placement retains the legacy greedy result or a result no worse than it
|
|
47
47
|
rather than losing additional callouts. Pre-drain diameter clearance ranking and
|
|
48
48
|
its Policy-B fallback remain unchanged.
|
|
49
|
+
- Compatible automatic/deferred sparse ordinary side/plan hole and post-drain
|
|
50
|
+
machined-feature callouts now share one bounded same-view leader inventory
|
|
51
|
+
(#1166). Pattern, profiled-bore, and dense table-eligible hole queues retain
|
|
52
|
+
their specialised established paths because their winners affect downstream
|
|
53
|
+
furniture/table semantics or await #798 silhouette-aware routing. The solve
|
|
54
|
+
preserves required/priority-ranked evidence, rejects new-leader crossings,
|
|
55
|
+
checks complete leader ink against actual-width fixed dimension/witness,
|
|
56
|
+
arrow, label, and centre-furniture components (including rendered arrows on
|
|
57
|
+
shifted dimensions and component-local dashed circular centre furniture);
|
|
58
|
+
fixed residual faces and the selected survivor both require each tessellation
|
|
59
|
+
triangle to fit one same analytical component rather than merely the union,
|
|
60
|
+
plus exact continuous OCC containment for curved faces; residual lowering
|
|
61
|
+
also retains filled datum/GD&T faces absent from segment metadata; if candidate
|
|
62
|
+
or selected-survivor construction raises, or a survivor fails validation, the
|
|
63
|
+
bad alternative fails closed and the canonical lazy producer tail is replayed
|
|
64
|
+
instead of falsely dropping an otherwise valid callout; an all-invalid tail is
|
|
65
|
+
reported as validation-stage geometry failure rather than no clear room (so
|
|
66
|
+
scale fallback does not retry a non-spatial helper defect), while compiler
|
|
67
|
+
invariant violations such as a missing semantic callout label remain loud;
|
|
68
|
+
only explicitly marked global turning axes permit an arrow-local leader-tip
|
|
69
|
+
attachment; near-collinear shaft travel and ownerless section/cutting lines
|
|
70
|
+
remain fixed ink. The solve prefers
|
|
71
|
+
clear routes over explicit Policy-B fallbacks, and then minimises real leader
|
|
72
|
+
length. Provisional sections cannot veto that primary result: one separately
|
|
73
|
+
bounded refinement may choose an equally complete leader inventory that clears
|
|
74
|
+
their exact components, after which the final section performs one bounded
|
|
75
|
+
end-symbol repair and yields if exact ink still conflicts. The selected OCC survivor is validated against the same analytical
|
|
76
|
+
ink contract. A retained fixed-ink crossing now emits the machine-readable
|
|
77
|
+
`feature_leader_crossing` info finding instead of appearing lint-clean. Candidate,
|
|
78
|
+
fixed-component lowering, fixed-obstacle-probe, pair, and exact-search work is
|
|
79
|
+
bounded; the fixed inventory is lowered once and reused with the producer's
|
|
80
|
+
legacy lazy result as the floor, including state-cap exhaustion; page,
|
|
81
|
+
own-silhouette, and title-reservation constraints remain hard in every fallback.
|
|
82
|
+
A replay that exhausts exact fixed-probe classification is retained visibly as
|
|
83
|
+
`feature_leader_fixed_ink_unverified` rather than rescanning past the cap, and
|
|
84
|
+
that uncertainty participates in the legibility quality inventory.
|
|
85
|
+
Trace output preserves an abandoned fully admitted inventory when state search
|
|
86
|
+
exhausts, records the producer-floor replay separately, and names exact committed/provisional components,
|
|
87
|
+
refinement status, and competing blockers; live
|
|
88
|
+
single-callout edits remain immediate.
|
|
49
89
|
|
|
50
90
|
## v0.4.6 — 2026-08-15
|
|
51
91
|
|
|
@@ -30,7 +30,7 @@ architecture** table; open retired or superseded records only for design history
|
|
|
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
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
|
-
| [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; post-drain
|
|
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; compatible sparse ordinary side/plan hole and post-drain machined leaders share one bounded late inventory. | Accepted; supersedes 0009; #740 Amendment 1; #1166 Amendment 2 | `test_carve_free_position_callers.py`, `test_strip_layout.py`, `test_layout_property.py`, `test_issue_740_leader_assignment.py`, `test_issue_1166_cross_pass_feature_leaders.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
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` |
|
|
@@ -327,6 +327,33 @@ 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_polygons_overlap(left, right) -> bool:
|
|
331
|
+
"""Whether two convex page-plane polygons have positive-area overlap.
|
|
332
|
+
|
|
333
|
+
The separating-axis test is strict: touching edges/vertices are clear, the
|
|
334
|
+
same placement convention as :func:`_boxes_overlap`.
|
|
335
|
+
"""
|
|
336
|
+
|
|
337
|
+
if len(left) < 3 or len(right) < 3:
|
|
338
|
+
return False
|
|
339
|
+
axes: list[tuple[float, float]] = []
|
|
340
|
+
for points in (left, right):
|
|
341
|
+
axes.extend(
|
|
342
|
+
(-(second[1] - first[1]), second[0] - first[0])
|
|
343
|
+
for first, second in zip(points, (*points[1:], points[0]), strict=True)
|
|
344
|
+
)
|
|
345
|
+
for ax, ay in axes:
|
|
346
|
+
if abs(ax) + abs(ay) <= 1e-12:
|
|
347
|
+
continue
|
|
348
|
+
left_projection = [x * ax + y * ay for x, y in left]
|
|
349
|
+
right_projection = [x * ax + y * ay for x, y in right]
|
|
350
|
+
if max(left_projection) <= min(right_projection) or max(right_projection) <= min(
|
|
351
|
+
left_projection
|
|
352
|
+
):
|
|
353
|
+
return False
|
|
354
|
+
return True
|
|
355
|
+
|
|
356
|
+
|
|
330
357
|
def _convex_polygon_overlaps_box(points, box) -> bool:
|
|
331
358
|
"""Whether a convex page-plane polygon has positive-area overlap with an AABB.
|
|
332
359
|
|
|
@@ -341,21 +368,63 @@ def _convex_polygon_overlaps_box(points, box) -> bool:
|
|
|
341
368
|
(box[2], box[3]),
|
|
342
369
|
(box[0], box[3]),
|
|
343
370
|
)
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
371
|
+
return _convex_polygons_overlap(points, corners)
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def _stroke_polygon(first, second, width: float):
|
|
375
|
+
"""The exact swept rectangle for one rendered straight stroke, or ``None``."""
|
|
376
|
+
|
|
377
|
+
dx, dy = float(second[0]) - float(first[0]), float(second[1]) - float(first[1])
|
|
378
|
+
length = math.hypot(dx, dy)
|
|
379
|
+
half = max(0.0, float(width)) / 2.0
|
|
380
|
+
if length <= 1e-12 or half <= 0.0:
|
|
381
|
+
return None
|
|
382
|
+
vx, vy = -dy / length * half, dx / length * half
|
|
383
|
+
return (
|
|
384
|
+
(float(first[0]) + vx, float(first[1]) + vy),
|
|
385
|
+
(float(second[0]) + vx, float(second[1]) + vy),
|
|
386
|
+
(float(second[0]) - vx, float(second[1]) - vy),
|
|
387
|
+
(float(first[0]) - vx, float(first[1]) - vy),
|
|
348
388
|
)
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def _leader_ink_polygons(tip, elbow, *, arrow_length: float, line_width: float):
|
|
392
|
+
"""Convex components of a leader's rendered tip→elbow shaft and arrow."""
|
|
393
|
+
|
|
394
|
+
dx, dy = float(elbow[0]) - float(tip[0]), float(elbow[1]) - float(tip[1])
|
|
395
|
+
length = math.hypot(dx, dy)
|
|
396
|
+
half_line = max(0.0, float(line_width)) / 2.0
|
|
397
|
+
head_length = max(0.0, float(arrow_length))
|
|
398
|
+
if length <= 1e-12:
|
|
399
|
+
half = max(half_line, head_length / 3.0)
|
|
400
|
+
if half <= 0.0:
|
|
401
|
+
return ()
|
|
402
|
+
return (
|
|
403
|
+
(
|
|
404
|
+
(float(tip[0]) - half, float(tip[1]) - half),
|
|
405
|
+
(float(tip[0]) + half, float(tip[1]) - half),
|
|
406
|
+
(float(tip[0]) + half, float(tip[1]) + half),
|
|
407
|
+
(float(tip[0]) - half, float(tip[1]) + half),
|
|
408
|
+
),
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
ux, uy = dx / length, dy / length
|
|
412
|
+
vx, vy = -uy, ux
|
|
413
|
+
polygons = []
|
|
414
|
+
shaft = _stroke_polygon(tip, elbow, line_width)
|
|
415
|
+
if shaft is not None:
|
|
416
|
+
polygons.append(shaft)
|
|
417
|
+
if head_length > 0.0:
|
|
418
|
+
half_head = head_length / 3.0
|
|
419
|
+
base_x, base_y = tip[0] + ux * head_length, tip[1] + uy * head_length
|
|
420
|
+
polygons.append(
|
|
421
|
+
(
|
|
422
|
+
(float(tip[0]), float(tip[1])),
|
|
423
|
+
(base_x + vx * half_head, base_y + vy * half_head),
|
|
424
|
+
(base_x - vx * half_head, base_y - vy * half_head),
|
|
425
|
+
)
|
|
426
|
+
)
|
|
427
|
+
return tuple(polygons)
|
|
359
428
|
|
|
360
429
|
|
|
361
430
|
def _leader_ink_crosses_box(
|
|
@@ -378,41 +447,15 @@ def _leader_ink_crosses_box(
|
|
|
378
447
|
Touching boundaries are clear, matching :func:`_boxes_overlap` and the placement-side
|
|
379
448
|
semantics of :func:`_segment_crosses_box`.
|
|
380
449
|
"""
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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),
|
|
450
|
+
return any(
|
|
451
|
+
_convex_polygon_overlaps_box(polygon, box)
|
|
452
|
+
for polygon in _leader_ink_polygons(
|
|
453
|
+
tip,
|
|
454
|
+
elbow,
|
|
455
|
+
arrow_length=arrow_length,
|
|
456
|
+
line_width=line_width,
|
|
400
457
|
)
|
|
401
|
-
|
|
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
|
|
458
|
+
)
|
|
416
459
|
|
|
417
460
|
|
|
418
461
|
def _segments_cross_or_overlap(a1, a2, b1, b2) -> bool:
|
|
@@ -786,6 +786,43 @@ def dim_footprint(p1, p2, side, distance, draft, label):
|
|
|
786
786
|
return (min(xs) - pad, min(ys) - pad, max(xs) + pad, max(ys) + pad)
|
|
787
787
|
|
|
788
788
|
|
|
789
|
+
def leader_callout_geometry(tip, elbow, draft, *, text_side="auto", callout_box=None):
|
|
790
|
+
"""Exact cheap label box and line segments for a callout-bearing ``Leader``.
|
|
791
|
+
|
|
792
|
+
This is the position-dependent subset of :func:`leader_footprint` needed by
|
|
793
|
+
the shared feature-leader solve. It mirrors the helper's two input edges and
|
|
794
|
+
the translation of the already-measured callout sketch, without constructing
|
|
795
|
+
the arrow/shelf OCC faces. ``None`` matches the helper's forced-side guard.
|
|
796
|
+
"""
|
|
797
|
+
|
|
798
|
+
gap = draft.pad_around_text
|
|
799
|
+
if text_side == "auto":
|
|
800
|
+
shelf_dir = 1.0 if elbow[0] >= tip[0] else -1.0
|
|
801
|
+
else:
|
|
802
|
+
shelf_dir = 1.0 if text_side == "right" else -1.0
|
|
803
|
+
shelf_end = (elbow[0] + shelf_dir * gap, elbow[1])
|
|
804
|
+
segments = (
|
|
805
|
+
((float(tip[0]), float(tip[1])), (float(elbow[0]), float(elbow[1]))),
|
|
806
|
+
((float(elbow[0]), float(elbow[1])), (float(shelf_end[0]), float(shelf_end[1]))),
|
|
807
|
+
)
|
|
808
|
+
|
|
809
|
+
label_box = None
|
|
810
|
+
if callout_box is not None:
|
|
811
|
+
cw = callout_box[2] - callout_box[0]
|
|
812
|
+
ch = callout_box[3] - callout_box[1]
|
|
813
|
+
if shelf_dir > 0:
|
|
814
|
+
cx0, cx1 = shelf_end[0], shelf_end[0] + cw
|
|
815
|
+
else:
|
|
816
|
+
cx0, cx1 = shelf_end[0] - cw, shelf_end[0]
|
|
817
|
+
cy0, cy1 = elbow[1] - ch / 2.0, elbow[1] + ch / 2.0
|
|
818
|
+
label_box = (cx0, cy0, cx1, cy1)
|
|
819
|
+
if text_side != "auto" and _segment_crosses_box(
|
|
820
|
+
(tip[0], tip[1]), (elbow[0], elbow[1]), label_box
|
|
821
|
+
):
|
|
822
|
+
return None
|
|
823
|
+
return label_box, segments
|
|
824
|
+
|
|
825
|
+
|
|
789
826
|
def leader_footprint(tip, elbow, draft, *, text_side="auto", callout_box=None):
|
|
790
827
|
"""Analytical page-mm AABB ``(x0, y0, x1, y1)`` of the :class:`Leader` that
|
|
791
828
|
``Leader(tip, elbow, label="", draft, text_side=…, callout=…)`` would build —
|
|
@@ -811,12 +848,17 @@ def leader_footprint(tip, elbow, draft, *, text_side="auto", callout_box=None):
|
|
|
811
848
|
that runs the shaft through the label — so callers omit the candidate exactly
|
|
812
849
|
as the built-geometry probe's except-handler did.
|
|
813
850
|
"""
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
851
|
+
geometry = leader_callout_geometry(
|
|
852
|
+
tip,
|
|
853
|
+
elbow,
|
|
854
|
+
draft,
|
|
855
|
+
text_side=text_side,
|
|
856
|
+
callout_box=callout_box,
|
|
857
|
+
)
|
|
858
|
+
if geometry is None:
|
|
859
|
+
return None
|
|
860
|
+
label_box, segments = geometry
|
|
861
|
+
shelf_end_x = segments[1][1][0]
|
|
820
862
|
|
|
821
863
|
# The arrowhead bounds the shaft only while it is the wider of the two; a style with a
|
|
822
864
|
# heavy line and a small arrow inverts that, and a diagonal shaft then pushes its stroke
|
|
@@ -829,22 +871,9 @@ def leader_footprint(tip, elbow, draft, *, text_side="auto", callout_box=None):
|
|
|
829
871
|
xs += [min(elbow[0], shelf_end_x) - half_line, max(elbow[0], shelf_end_x) + half_line]
|
|
830
872
|
ys += [elbow[1] - half_line, elbow[1] + half_line]
|
|
831
873
|
|
|
832
|
-
if
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
if shelf_dir > 0:
|
|
836
|
-
cx0, cx1 = shelf_end_x, shelf_end_x + cw
|
|
837
|
-
else:
|
|
838
|
-
cx0, cx1 = shelf_end_x - cw, shelf_end_x
|
|
839
|
-
cy0, cy1 = elbow[1] - ch / 2.0, elbow[1] + ch / 2.0
|
|
840
|
-
# Leader raises rather than draw a shaft through its own label; a forced side
|
|
841
|
-
# is the only way to get there, matching helpers' own guard condition.
|
|
842
|
-
if text_side != "auto" and _segment_crosses_box(
|
|
843
|
-
(tip[0], tip[1]), (elbow[0], elbow[1]), (cx0, cy0, cx1, cy1)
|
|
844
|
-
):
|
|
845
|
-
return None
|
|
846
|
-
xs += [cx0, cx1]
|
|
847
|
-
ys += [cy0, cy1]
|
|
874
|
+
if label_box is not None:
|
|
875
|
+
xs += [label_box[0], label_box[2]]
|
|
876
|
+
ys += [label_box[1], label_box[3]]
|
|
848
877
|
|
|
849
878
|
return (min(xs), min(ys), max(xs), max(ys))
|
|
850
879
|
|
|
@@ -1495,6 +1524,11 @@ class PlacementContext:
|
|
|
1495
1524
|
# drained (#684 review): a mid-drain carve could occupy space a later sibling
|
|
1496
1525
|
# corridor's force candidate needs; deferral makes "post-drain" literally true.
|
|
1497
1526
|
post_drain: list = field(default_factory=list)
|
|
1527
|
+
# Compatible automatic/deferred feature-callout jobs collected across the
|
|
1528
|
+
# hole + post-drain machined passes (#1166). ``None`` is intentional: direct
|
|
1529
|
+
# renderer calls and finished-sheet live verbs keep their immediate behavior;
|
|
1530
|
+
# the orchestrator/finalize paths opt in with ``[]`` and drain once.
|
|
1531
|
+
feature_leaders: list | None = None
|
|
1498
1532
|
# The opt-in solve-trace recorder (#736) — a :class:`SolveTrace` threaded off the
|
|
1499
1533
|
# drawing's build state by both entry paths, or ``None`` (the default: tracing off,
|
|
1500
1534
|
# every hook a bare None check). Ctx state, not a module global, so the finalize
|
|
@@ -20,7 +20,7 @@ from __future__ import annotations
|
|
|
20
20
|
|
|
21
21
|
import math
|
|
22
22
|
from dataclasses import dataclass, replace
|
|
23
|
-
from itertools import chain, islice
|
|
23
|
+
from itertools import chain, islice, tee
|
|
24
24
|
from typing import Any
|
|
25
25
|
|
|
26
26
|
from build123d_drafting import DatumFeature, FeatureControlFrame, SurfaceFinish, TextBlock
|
|
@@ -82,6 +82,7 @@ from draftwright.annotations._common import (
|
|
|
82
82
|
strip_free_span,
|
|
83
83
|
strip_obstacles,
|
|
84
84
|
)
|
|
85
|
+
from draftwright.annotations.leaders import FeatureLeaderJob, collect_feature_leader
|
|
85
86
|
from draftwright.layout import (
|
|
86
87
|
_LEADER_ASSIGN_MAX_JOBS,
|
|
87
88
|
StripCandidate,
|
|
@@ -1604,6 +1605,38 @@ def _corner_candidates(dwg, view, vb, members, reach, *, provenances=None):
|
|
|
1604
1605
|
yield (tip, elbow, owner)
|
|
1605
1606
|
|
|
1606
1607
|
|
|
1608
|
+
def _corner_escape_candidates(dwg, view, vb, members, reach, *, provenances=None):
|
|
1609
|
+
"""Corner leaders plus silhouette-outward horizontal/vertical escapes.
|
|
1610
|
+
|
|
1611
|
+
The diagonal remains the stable first choice. The two axis-aligned rays
|
|
1612
|
+
leave the same corner away from the view centre, so they add boundary/lane
|
|
1613
|
+
alternatives without introducing #798's through-silhouette routing problem.
|
|
1614
|
+
"""
|
|
1615
|
+
|
|
1616
|
+
members = list(members)
|
|
1617
|
+
owners = list(provenances if provenances is not None else members)
|
|
1618
|
+
yield from _corner_candidates(dwg, view, vb, members, reach, provenances=owners)
|
|
1619
|
+
x0, y0, x1, y1 = vb
|
|
1620
|
+
cx, cy = (x0 + x1) / 2, (y0 + y1) / 2
|
|
1621
|
+
for member, owner in zip(members, owners, strict=True):
|
|
1622
|
+
tip = dwg.at(view, *member.frame.origin)
|
|
1623
|
+
directions = (
|
|
1624
|
+
(1.0 if tip[0] >= cx else -1.0, 0.0),
|
|
1625
|
+
(0.0, 1.0 if tip[1] >= cy else -1.0),
|
|
1626
|
+
)
|
|
1627
|
+
for ux, uy in directions:
|
|
1628
|
+
exit_distance = _ray_exit_dist(tip[0], tip[1], ux, uy, vb)
|
|
1629
|
+
yield (
|
|
1630
|
+
tip,
|
|
1631
|
+
(
|
|
1632
|
+
tip[0] + ux * (exit_distance + reach),
|
|
1633
|
+
tip[1] + uy * (exit_distance + reach),
|
|
1634
|
+
0,
|
|
1635
|
+
),
|
|
1636
|
+
owner,
|
|
1637
|
+
)
|
|
1638
|
+
|
|
1639
|
+
|
|
1607
1640
|
def _flat_candidates(dwg, view, vb, members, reach, *, provenances):
|
|
1608
1641
|
"""Keep the established centre-out lead first, then try true margin escapes.
|
|
1609
1642
|
|
|
@@ -1706,6 +1739,88 @@ def _leader_callout_pass(
|
|
|
1706
1739
|
jobs = [(*job[:4], iter(job[4]), job[5]) for job in jobs]
|
|
1707
1740
|
if not jobs:
|
|
1708
1741
|
return 0
|
|
1742
|
+
if joint and getattr(ctx, "feature_leaders", None) is not None:
|
|
1743
|
+
for name, view, vb, label, raw_candidates, measurement in jobs:
|
|
1744
|
+
joint_candidates, fallback_candidates = tee(raw_candidates)
|
|
1745
|
+
|
|
1746
|
+
def _lane_candidates(_raw=joint_candidates):
|
|
1747
|
+
spacing = dwg.draft.font_size + 2 * dwg.draft.pad_around_text
|
|
1748
|
+
for tip, elbow, feature in _raw:
|
|
1749
|
+
dx, dy = float(elbow[0]) - float(tip[0]), float(elbow[1]) - float(tip[1])
|
|
1750
|
+
length = math.hypot(dx, dy)
|
|
1751
|
+
if length <= 1e-12:
|
|
1752
|
+
yield (tip, elbow, feature)
|
|
1753
|
+
continue
|
|
1754
|
+
ux, uy = dx / length, dy / length
|
|
1755
|
+
px, py = -dy / length, dx / length
|
|
1756
|
+
for outward, lane in (
|
|
1757
|
+
(0, 0),
|
|
1758
|
+
(0, 1),
|
|
1759
|
+
(0, -1),
|
|
1760
|
+
(0, 2),
|
|
1761
|
+
(0, -2),
|
|
1762
|
+
(1, 0),
|
|
1763
|
+
(2, 0),
|
|
1764
|
+
(1, 1),
|
|
1765
|
+
(1, -1),
|
|
1766
|
+
):
|
|
1767
|
+
yield (
|
|
1768
|
+
tip,
|
|
1769
|
+
(
|
|
1770
|
+
float(elbow[0]) + ux * spacing * outward + px * spacing * lane,
|
|
1771
|
+
float(elbow[1]) + uy * spacing * outward + py * spacing * lane,
|
|
1772
|
+
0,
|
|
1773
|
+
),
|
|
1774
|
+
feature,
|
|
1775
|
+
)
|
|
1776
|
+
|
|
1777
|
+
def _build(tip, elbow, _feature, *, _label=label):
|
|
1778
|
+
return Leader(
|
|
1779
|
+
tip=(tip[0], tip[1], 0),
|
|
1780
|
+
elbow=elbow,
|
|
1781
|
+
label=_label,
|
|
1782
|
+
draft=dwg.draft,
|
|
1783
|
+
)
|
|
1784
|
+
|
|
1785
|
+
def _fallback_accept(
|
|
1786
|
+
candidate,
|
|
1787
|
+
obstacles,
|
|
1788
|
+
page,
|
|
1789
|
+
*,
|
|
1790
|
+
_vb=vb,
|
|
1791
|
+
_geom_clear=geom_clear,
|
|
1792
|
+
):
|
|
1793
|
+
return _label_lands_clear(
|
|
1794
|
+
candidate.annotation,
|
|
1795
|
+
obstacles,
|
|
1796
|
+
_vb,
|
|
1797
|
+
page,
|
|
1798
|
+
geom_clear=_geom_clear,
|
|
1799
|
+
)
|
|
1800
|
+
|
|
1801
|
+
collect_feature_leader(
|
|
1802
|
+
ctx,
|
|
1803
|
+
FeatureLeaderJob(
|
|
1804
|
+
name=name,
|
|
1805
|
+
view=view,
|
|
1806
|
+
silhouette=vb,
|
|
1807
|
+
label=label,
|
|
1808
|
+
candidates=_lane_candidates(),
|
|
1809
|
+
build=_build,
|
|
1810
|
+
measurement=tuple(measurement),
|
|
1811
|
+
noun=noun,
|
|
1812
|
+
drop_code=drop_code,
|
|
1813
|
+
fallback_candidates=fallback_candidates,
|
|
1814
|
+
fallback_accept=_fallback_accept,
|
|
1815
|
+
# Fixed dimensions/witnesses are exact-ink constraints in
|
|
1816
|
+
# the shared solve. If every relocation is worse, retain
|
|
1817
|
+
# the established semantic callout only through explicit
|
|
1818
|
+
# Policy B: the solver penalises the crossing and the
|
|
1819
|
+
# emitter records a machine-readable warning (#1166).
|
|
1820
|
+
allow_policy_b_fixed=True,
|
|
1821
|
+
),
|
|
1822
|
+
)
|
|
1823
|
+
return 0
|
|
1709
1824
|
trace = getattr(ctx, "trace", None)
|
|
1710
1825
|
ev = trace.pass_event(f"{noun}_callouts") if trace is not None else None
|
|
1711
1826
|
page = (a.margin, a.margin, a.PAGE_W - a.margin, a.PAGE_H - a.margin)
|
|
@@ -1980,7 +2095,7 @@ def render_chamfers(dwg, plan, a, *, ctx, only=None) -> int:
|
|
|
1980
2095
|
view,
|
|
1981
2096
|
vb,
|
|
1982
2097
|
_chamfer_label(pd.value_text, pd.value, ch) + _tol_suffix(pd.tolerance, draft),
|
|
1983
|
-
|
|
2098
|
+
_corner_escape_candidates(dwg, view, vb, [ch], reach, provenances=[g.ref]),
|
|
1984
2099
|
(pd.id,),
|
|
1985
2100
|
)
|
|
1986
2101
|
)
|
|
@@ -2063,7 +2178,7 @@ def render_fillets(dwg, plan, a, *, ctx, only=None) -> int:
|
|
|
2063
2178
|
view,
|
|
2064
2179
|
vb,
|
|
2065
2180
|
_fillet_label(members[0][1].value_text, len(members)) + _tol_suffix(tol, draft),
|
|
2066
|
-
|
|
2181
|
+
_corner_escape_candidates(
|
|
2067
2182
|
dwg,
|
|
2068
2183
|
view,
|
|
2069
2184
|
vb,
|
|
@@ -4074,6 +4189,14 @@ def render_step_positions(dwg, plan, frame, *, ctx) -> int:
|
|
|
4074
4189
|
return n
|
|
4075
4190
|
|
|
4076
4191
|
|
|
4192
|
+
def _global_axis_centerline(first, second):
|
|
4193
|
+
"""A turning-axis centreline whose tip attachment is intentional (#1166)."""
|
|
4194
|
+
|
|
4195
|
+
centerline = Centerline(first, second)
|
|
4196
|
+
centerline.is_global_axis_centerline = True
|
|
4197
|
+
return centerline
|
|
4198
|
+
|
|
4199
|
+
|
|
4077
4200
|
def render_rotational(dwg, plan, a, *, ctx) -> int:
|
|
4078
4201
|
"""Rotational furniture from the IR `RotationalFeature` (#237): the OD dim (above
|
|
4079
4202
|
the front view), the rotation-axis centrelines (front + side), and the concentric
|
|
@@ -4117,12 +4240,18 @@ def render_rotational(dwg, plan, a, *, ctx) -> int:
|
|
|
4117
4240
|
)
|
|
4118
4241
|
n += 1
|
|
4119
4242
|
ctx.place(
|
|
4120
|
-
|
|
4243
|
+
_global_axis_centerline(
|
|
4244
|
+
(FX(a.cx), FZ(a.bb.min.Z) - 5, 0),
|
|
4245
|
+
(FX(a.cx), FZ(a.bb.max.Z) + 5, 0),
|
|
4246
|
+
),
|
|
4121
4247
|
"centerline_front",
|
|
4122
4248
|
view="front",
|
|
4123
4249
|
)
|
|
4124
4250
|
ctx.place(
|
|
4125
|
-
|
|
4251
|
+
_global_axis_centerline(
|
|
4252
|
+
(SX(a.cy), SZ(a.bb.min.Z) - 5, 0),
|
|
4253
|
+
(SX(a.cy), SZ(a.bb.max.Z) + 5, 0),
|
|
4254
|
+
),
|
|
4126
4255
|
"centerline_side",
|
|
4127
4256
|
view="side",
|
|
4128
4257
|
)
|
|
@@ -4207,12 +4336,18 @@ def render_rotational(dwg, plan, a, *, ctx) -> int:
|
|
|
4207
4336
|
)
|
|
4208
4337
|
n += 1
|
|
4209
4338
|
ctx.place(
|
|
4210
|
-
|
|
4339
|
+
_global_axis_centerline(
|
|
4340
|
+
(FX(a.bb.min.X) - 5, FZ(a.cz), 0),
|
|
4341
|
+
(FX(a.bb.max.X) + 5, FZ(a.cz), 0),
|
|
4342
|
+
),
|
|
4211
4343
|
"centerline_front",
|
|
4212
4344
|
view="front",
|
|
4213
4345
|
)
|
|
4214
4346
|
ctx.place(
|
|
4215
|
-
|
|
4347
|
+
_global_axis_centerline(
|
|
4348
|
+
(PX(a.bb.min.X) - 5, PY(a.cy), 0),
|
|
4349
|
+
(PX(a.bb.max.X) + 5, PY(a.cy), 0),
|
|
4350
|
+
),
|
|
4216
4351
|
"centerline_plan",
|
|
4217
4352
|
view="plan",
|
|
4218
4353
|
)
|
|
@@ -4236,12 +4371,18 @@ def render_rotational(dwg, plan, a, *, ctx) -> int:
|
|
|
4236
4371
|
)
|
|
4237
4372
|
n += 1
|
|
4238
4373
|
ctx.place(
|
|
4239
|
-
|
|
4374
|
+
_global_axis_centerline(
|
|
4375
|
+
(SX(a.bb.min.Y) - 5, SZ(a.cz), 0),
|
|
4376
|
+
(SX(a.bb.max.Y) + 5, SZ(a.cz), 0),
|
|
4377
|
+
),
|
|
4240
4378
|
"centerline_side",
|
|
4241
4379
|
view="side",
|
|
4242
4380
|
)
|
|
4243
4381
|
ctx.place(
|
|
4244
|
-
|
|
4382
|
+
_global_axis_centerline(
|
|
4383
|
+
(PX(a.cx), PY(a.bb.min.Y) - 5, 0),
|
|
4384
|
+
(PX(a.cx), PY(a.bb.max.Y) + 5, 0),
|
|
4385
|
+
),
|
|
4245
4386
|
"centerline_plan",
|
|
4246
4387
|
view="plan",
|
|
4247
4388
|
)
|
|
@@ -4275,23 +4416,35 @@ def render_local_turned_centerlines(dwg, a, *, ctx) -> int:
|
|
|
4275
4416
|
|
|
4276
4417
|
if axis == "x":
|
|
4277
4418
|
_place(
|
|
4278
|
-
|
|
4419
|
+
_global_axis_centerline(
|
|
4420
|
+
(FX(a.bb.min.X) - 5, FZ(a.cz), 0),
|
|
4421
|
+
(FX(a.bb.max.X) + 5, FZ(a.cz), 0),
|
|
4422
|
+
),
|
|
4279
4423
|
"centerline_front",
|
|
4280
4424
|
"front",
|
|
4281
4425
|
)
|
|
4282
4426
|
_place(
|
|
4283
|
-
|
|
4427
|
+
_global_axis_centerline(
|
|
4428
|
+
(PX(a.bb.min.X) - 5, PY(a.cy), 0),
|
|
4429
|
+
(PX(a.bb.max.X) + 5, PY(a.cy), 0),
|
|
4430
|
+
),
|
|
4284
4431
|
"centerline_plan",
|
|
4285
4432
|
"plan",
|
|
4286
4433
|
)
|
|
4287
4434
|
elif axis == "y":
|
|
4288
4435
|
_place(
|
|
4289
|
-
|
|
4436
|
+
_global_axis_centerline(
|
|
4437
|
+
(SX(a.bb.min.Y) - 5, SZ(a.cz), 0),
|
|
4438
|
+
(SX(a.bb.max.Y) + 5, SZ(a.cz), 0),
|
|
4439
|
+
),
|
|
4290
4440
|
"centerline_side",
|
|
4291
4441
|
"side",
|
|
4292
4442
|
)
|
|
4293
4443
|
_place(
|
|
4294
|
-
|
|
4444
|
+
_global_axis_centerline(
|
|
4445
|
+
(PX(a.cx), PY(a.bb.min.Y) - 5, 0),
|
|
4446
|
+
(PX(a.cx), PY(a.bb.max.Y) + 5, 0),
|
|
4447
|
+
),
|
|
4295
4448
|
"centerline_plan",
|
|
4296
4449
|
"plan",
|
|
4297
4450
|
)
|