draftwright 0.4.2__tar.gz → 0.4.3__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.2 → draftwright-0.4.3}/CHANGELOG.md +31 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/PKG-INFO +1 -1
- {draftwright-0.4.2 → draftwright-0.4.3}/pyproject.toml +1 -1
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/analysis.py +2 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/annotations/from_model.py +57 -16
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/annotations/orchestrator.py +3 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/compose.py +9 -6
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/drawing.py +11 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/linting/__init__.py +2 -0
- draftwright-0.4.3/src/draftwright/linting/polygonal_stock_coverage.py +114 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/model/__init__.py +4 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/model/compiled.py +7 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/model/declare.py +29 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/model/detect.py +30 -1
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/model/ir.py +48 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/recognition/__init__.py +8 -1
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/recognition/polygonal_bosses.py +80 -10
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/recognition/result.py +7 -1
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/recognition/slots.py +48 -2
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/sheet.py +6 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/sheet_emit.py +16 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_add_dimension.py +9 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_compiled_plan_boundary.py +1 -0
- draftwright-0.4.3/tests/test_issue_1073_cross_body_patterns.py +95 -0
- draftwright-0.4.3/tests/test_issue_1082_polygonal_stock.py +232 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_make_drawing.py +31 -4
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_parameter_id.py +27 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_recognition_manifest.py +7 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_recognition_result.py +2 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_sheet_emit.py +10 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_sheet_identity_invariant.py +1 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_workflows.py +8 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/.gitignore +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/LICENSE +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/README.md +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/docs/adr/README.md +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/skills/SKILL.md +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/__init__.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/_core.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/_geometry.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/_pmi_part21.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/_warnings.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/annotate.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/annotations/__init__.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/annotations/_common.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/annotations/balloons.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/annotations/holes.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/annotations/sections.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/audit.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/builder.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/cli.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/export.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/fits.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/fonts/IBMPlexMono-Regular.ttf +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/fonts/IBMPlexSansCondensed-Regular.ttf +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/fonts/LICENSE-IBMPlexMono-OFL.txt +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/fonts/LICENSE-IBMPlexSansCondensed-OFL.txt +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/fonts/__init__.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/intents.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/layout.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/linting/channel_coverage.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/linting/coverage.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/linting/flat_coverage.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/linting/issues.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/linting/pmi_coverage.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/linting/profiled_bore_coverage.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/linting/slot_coverage.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/linting/structural.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/linting/suggest.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/make_drawing.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/model/callout.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/model/planner.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/pmi.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/projection.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/py.typed +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/recognition/_features.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/recognition/_record.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/recognition/chamfers.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/recognition/countersinks.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/recognition/fillets.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/recognition/flats.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/recognition/grooves.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/recognition/levels.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/recognition/pads.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/recognition/plates.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/recognition/profiled_bores.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/recognition/turned.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/registry.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/repair.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/src/draftwright/score.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/_kernel.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/_layout_sig.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/conftest.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/fixtures/grm03_thumbwheel_drive_screw.step +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/fixtures/if_step_flat_across_cylinder.step +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/fixtures/issue_1058_wheel_rh.step +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/fixtures/issue_909_basic_part_design_017_body.step +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/fixtures/issue_915_case_study_2.step +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/fixtures/nist_ctc_01_asme1_ap203.stp +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/fixtures/nist_ctc_01_asme1_ap242.stp +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/fixtures/nist_ctc_02_asme1_ap203.stp +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/fixtures/nist_ctc_02_asme1_ap242.stp +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/fixtures/nist_ctc_03_asme1_ap203.stp +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/fixtures/nist_ctc_03_asme1_ap242.stp +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/fixtures/nist_ctc_04_asme1_ap203.stp +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/fixtures/nist_ctc_04_asme1_ap242.stp +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/fixtures/nist_ctc_05_asme1_ap203.stp +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/fixtures/nist_ctc_05_asme1_ap242.stp +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/fixtures/tuner_jig_blind_obround_pockets.step +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/refactor_golden/bracket_section.json +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/refactor_golden/centered_rebate.json +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/refactor_golden/chamfered.json +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/refactor_golden/filleted.json +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/refactor_golden/flange_dense.json +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/refactor_golden/grid_plate.json +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/refactor_golden/grooved_shaft.json +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/refactor_golden/hex_bar.json +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/refactor_golden/holed_slot.json +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/refactor_golden/pocketed.json +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/refactor_golden/prismatic_ladder.json +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/refactor_golden/scattered_plate.json +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/refactor_golden/side_drilled.json +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/refactor_golden/turned_stepped.json +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_agents_guide.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_api_docs.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_architecture_docs.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_audit_differential.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_balloon_ring_standoff.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_carve_free_position_callers.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_counting_calls.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_declare.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_declared_recognition_gate.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_dense_sheet_canary.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_deprecation_dates.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_detect_once.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_detect_registry.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_dimension_role_vocabulary.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_drawing_encapsulation.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_e2e_slice.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_e2e_standards.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_export_dxf_curves.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_export_dxf_zoom.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_fillets_adjacency.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_fits.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_flat_completeness.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_flat_stock_identity.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_flat_stock_opposition.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_gdt_placement.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_grid_lattice_convention.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_import_boundaries.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_issue_1000_envelope_reuse.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_issue_1058_wheel_profile.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_issue_676_polygonal_boss.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_issue_676_polygonal_boss_declare.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_issue_885_prismatic_coverage.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_issue_909_sloped_profile.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_issue_915_dense_case.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_issue_916_pocket_leader.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_issue_917_open_channel.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_issue_955_height_contingency.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_issue_958_cross_solid_recognition.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_label_provenance.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_layout.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_layout_cleanliness.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_layout_hypothesis.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_layout_property.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_lint_box_cache.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_lint_reconciliation.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_lint_structural.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_linting.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_location_vocabulary.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_note_verb.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_object_aspects.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_occupancy_boxes.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_part_model.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_pitch_dim_footprint.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_pmi.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_pmi_datum_lowering.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_pmi_gtol_lowering.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_pmi_part21.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_pocket_pattern.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_pocket_pattern_recognition.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_principal_profile_classifier.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_private_test_attr_reads.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_private_test_imports.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_recogniser_contract.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_recognition.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_refactor_golden.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_registry.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_render_seam.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_score.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_script_detail_parity.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_sheet_gdt.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_sheet_notes.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_sheet_of.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_sheet_section.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_sheet_tables.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_slanted_blind_step.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_slot_completeness.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_slot_pattern.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_slot_pattern_recognition.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_soft_deprecation.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_solve_trace.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_strip_layout.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_suppression_ledger.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_suppression_marks.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_tolerances.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_turned_steps.py +0 -0
- {draftwright-0.4.2 → draftwright-0.4.3}/tests/test_witness_label_reconciliation.py +0 -0
|
@@ -2,6 +2,37 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## v0.4.3 — 2026-08-09
|
|
6
|
+
|
|
7
|
+
**A trust and validation-throughput patch release.** Whole-part hexagonal stock now receives its
|
|
8
|
+
manufacturing definition, assembly recognition no longer combines evidence across bodies, and
|
|
9
|
+
performance validation is both more stable and faster to run.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **Whole-part regular hexagonal stock is recognised independently of an attached boss and
|
|
14
|
+
dimensioned as `HEX … A/F` plus its axial length** (#1082). In-plane rotation preserves the
|
|
15
|
+
geometric across-flats size instead of substituting an orientation-dependent bounding-box
|
|
16
|
+
width. Automatic and declared paths share the same compiler and placement machinery, while
|
|
17
|
+
completeness lint distinguishes placed, authored-suppressed, dropped, missing, and ambiguous
|
|
18
|
+
outcomes.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- **Slot and pocket patterns cannot group members from separate assembly bodies** (#1073).
|
|
23
|
+
Recognition carries solid ownership through grouping and fails closed when ownership is
|
|
24
|
+
unavailable, preventing plausible-looking phantom patterns across disconnected parts.
|
|
25
|
+
|
|
26
|
+
- **The annotation-obstacle performance guard uses deterministic work-count evidence instead of
|
|
27
|
+
a wall-clock threshold** (#1071). The regression test remains load-bearing under mutation but
|
|
28
|
+
no longer flakes because of machine load or suite ordering.
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- **The post-merge slow tier distributes individual tests across workers** (#1108), using
|
|
33
|
+
`pytest-xdist`'s load scheduling after local benchmark evidence showed a material speed-up
|
|
34
|
+
without weakening fixture coverage. Hosted slow validation remains the single post-merge gate.
|
|
35
|
+
|
|
5
36
|
## v0.4.2 — 2026-08-09
|
|
6
37
|
|
|
7
38
|
**The trustworthy manufacturing drawings release.** AP242 PMI is now accounted for from its
|
|
@@ -627,6 +627,7 @@ def _analyse(
|
|
|
627
627
|
patterns = list(recognition.hole_patterns) if recognition else []
|
|
628
628
|
bosses = list(recognition.bosses) if recognition else []
|
|
629
629
|
polygonal_bosses = list(recognition.polygonal_bosses) if recognition else []
|
|
630
|
+
polygonal_stock = list(recognition.polygonal_stock) if recognition else []
|
|
630
631
|
slots = list(recognition.slots) if recognition else []
|
|
631
632
|
pockets = list(recognition.pockets) if recognition else []
|
|
632
633
|
pocket_patterns = list(recognition.pocket_patterns) if recognition else []
|
|
@@ -655,6 +656,7 @@ def _analyse(
|
|
|
655
656
|
patterns=patterns,
|
|
656
657
|
bosses=bosses,
|
|
657
658
|
polygonal_bosses=polygonal_bosses,
|
|
659
|
+
polygonal_stock=polygonal_stock,
|
|
658
660
|
channels=list(recognition.channels) if recognition else None,
|
|
659
661
|
slots=slots,
|
|
660
662
|
# Injected from the aggregate since #1026 — `build_part_model` detected these
|
|
@@ -2195,13 +2195,14 @@ def _polygonal_boss_candidates(dwg, view, vb, boss, reach, *, provenance):
|
|
|
2195
2195
|
yield (tip, elbow, provenance)
|
|
2196
2196
|
|
|
2197
2197
|
|
|
2198
|
-
def
|
|
2199
|
-
|
|
2198
|
+
def _render_polygonal_prisms(
|
|
2199
|
+
dwg, plan, a, *, ctx, kind: str, noun: str, name_prefix: str, drop_code: str
|
|
2200
|
+
) -> int:
|
|
2200
2201
|
draft = dwg.draft
|
|
2201
2202
|
reach = _leader_callout_reach(draft)
|
|
2202
2203
|
jobs = []
|
|
2203
2204
|
groups = sorted(
|
|
2204
|
-
plan.of_kind(
|
|
2205
|
+
plan.of_kind(kind),
|
|
2205
2206
|
key=lambda group: (group.facts.frame.axis, group.facts.frame.origin),
|
|
2206
2207
|
)
|
|
2207
2208
|
for index, group in enumerate(groups):
|
|
@@ -2226,7 +2227,7 @@ def render_polygonal_bosses(dwg, plan, a, *, ctx) -> int:
|
|
|
2226
2227
|
label = f"{prefix} {dimension.value_text}{_tol_suffix(dimension.tolerance, draft)} A/F"
|
|
2227
2228
|
jobs.append(
|
|
2228
2229
|
(
|
|
2229
|
-
f"
|
|
2230
|
+
f"{name_prefix}_{boss.frame.axis}{index}",
|
|
2230
2231
|
view,
|
|
2231
2232
|
bounds,
|
|
2232
2233
|
label,
|
|
@@ -2245,15 +2246,43 @@ def render_polygonal_bosses(dwg, plan, a, *, ctx) -> int:
|
|
|
2245
2246
|
dwg,
|
|
2246
2247
|
a,
|
|
2247
2248
|
jobs,
|
|
2249
|
+
noun=noun,
|
|
2250
|
+
drop_code=drop_code,
|
|
2251
|
+
ctx=ctx,
|
|
2252
|
+
geom_clear=True,
|
|
2253
|
+
)
|
|
2254
|
+
|
|
2255
|
+
|
|
2256
|
+
def render_polygonal_bosses(dwg, plan, a, *, ctx) -> int:
|
|
2257
|
+
"""Across-flats leaders for bounded regular polygonal bosses (#676)."""
|
|
2258
|
+
return _render_polygonal_prisms(
|
|
2259
|
+
dwg,
|
|
2260
|
+
plan,
|
|
2261
|
+
a,
|
|
2262
|
+
ctx=ctx,
|
|
2263
|
+
kind="polygonal_boss",
|
|
2248
2264
|
noun="polygonal boss",
|
|
2265
|
+
name_prefix="m_polygonal_boss",
|
|
2249
2266
|
drop_code="polygonal_boss_dropped",
|
|
2267
|
+
)
|
|
2268
|
+
|
|
2269
|
+
|
|
2270
|
+
def render_polygonal_stock(dwg, plan, a, *, ctx) -> int:
|
|
2271
|
+
"""Across-flats leaders for whole regular polygonal stock (#1082)."""
|
|
2272
|
+
return _render_polygonal_prisms(
|
|
2273
|
+
dwg,
|
|
2274
|
+
plan,
|
|
2275
|
+
a,
|
|
2250
2276
|
ctx=ctx,
|
|
2251
|
-
|
|
2277
|
+
kind="polygonal_stock",
|
|
2278
|
+
noun="polygonal stock",
|
|
2279
|
+
name_prefix="m_polygonal_stock",
|
|
2280
|
+
drop_code="polygonal_stock_dropped",
|
|
2252
2281
|
)
|
|
2253
2282
|
|
|
2254
2283
|
|
|
2255
2284
|
def render_boss_heights(dwg, plan, a, *, ctx) -> int:
|
|
2256
|
-
"""Queue
|
|
2285
|
+
"""Queue prismatic boss heights and polygonal-stock lengths in a profile corridor."""
|
|
2257
2286
|
if a.is_rotational or a.prof is not None:
|
|
2258
2287
|
return 0
|
|
2259
2288
|
tier = dwg.draft.font_size + 2 * dwg.draft.pad_around_text
|
|
@@ -2265,15 +2294,17 @@ def render_boss_heights(dwg, plan, a, *, ctx) -> int:
|
|
|
2265
2294
|
n = 0
|
|
2266
2295
|
for bi, g in enumerate(
|
|
2267
2296
|
sorted(
|
|
2268
|
-
[
|
|
2297
|
+
[
|
|
2298
|
+
*plan.of_kind("boss"),
|
|
2299
|
+
*plan.of_kind("polygonal_boss"),
|
|
2300
|
+
*plan.of_kind("polygonal_stock"),
|
|
2301
|
+
],
|
|
2269
2302
|
key=lambda g: (g.facts.frame.axis, g.facts.frame.origin),
|
|
2270
2303
|
)
|
|
2271
2304
|
):
|
|
2272
2305
|
b = g.facts
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
None,
|
|
2276
|
-
)
|
|
2306
|
+
role = "stock_length" if g.ref.kind == "polygonal_stock" else "boss_height"
|
|
2307
|
+
pd = next((d for d in g.dims if (d.role, d.kind) == (role, "length")), None)
|
|
2277
2308
|
if pd is None or pd.span is None:
|
|
2278
2309
|
continue
|
|
2279
2310
|
spec = specs.get(b.frame.axis)
|
|
@@ -2284,7 +2315,8 @@ def render_boss_heights(dwg, plan, a, *, ctx) -> int:
|
|
|
2284
2315
|
p2 = dwg.at(view, *pd.span[1])
|
|
2285
2316
|
edge = max(p1[0], p2[0]) if side == "right" else max(p1[1], p2[1])
|
|
2286
2317
|
label = pd.value_text + _tol_suffix(pd.tolerance, dwg.draft)
|
|
2287
|
-
|
|
2318
|
+
prefix = "m_stocklength" if g.ref.kind == "polygonal_stock" else "m_bossheight"
|
|
2319
|
+
name = f"{prefix}_{b.frame.axis}{bi}"
|
|
2288
2320
|
|
|
2289
2321
|
def build(pos, p1=p1, p2=p2, side=side, edge=edge, label=label):
|
|
2290
2322
|
return _dim(p1, p2, side, abs(pos - edge), dwg.draft, label=label)
|
|
@@ -2292,6 +2324,15 @@ def render_boss_heights(dwg, plan, a, *, ctx) -> int:
|
|
|
2292
2324
|
def footprint(pos, p1=p1, p2=p2, side=side, edge=edge, label=label):
|
|
2293
2325
|
return dim_footprint(p1, p2, side, abs(pos - edge), dwg.draft, label)
|
|
2294
2326
|
|
|
2327
|
+
def dropped(_name, *, stock=g.ref.kind == "polygonal_stock", measurement=pd.id):
|
|
2328
|
+
if stock:
|
|
2329
|
+
ctx.record_issue(
|
|
2330
|
+
"warning",
|
|
2331
|
+
"polygonal_stock_length_dropped",
|
|
2332
|
+
"polygonal stock axial length was not placed (profile strip full)",
|
|
2333
|
+
measurement=measurement,
|
|
2334
|
+
)
|
|
2335
|
+
|
|
2295
2336
|
register_corridor(
|
|
2296
2337
|
ctx,
|
|
2297
2338
|
(view, side),
|
|
@@ -2304,10 +2345,10 @@ def render_boss_heights(dwg, plan, a, *, ctx) -> int:
|
|
|
2304
2345
|
build=build,
|
|
2305
2346
|
order=(_SIZE_SUBCHAIN, bi, name),
|
|
2306
2347
|
on_place=lambda _nm: None,
|
|
2307
|
-
#
|
|
2308
|
-
#
|
|
2309
|
-
#
|
|
2310
|
-
on_drop=
|
|
2348
|
+
# Boss heights retain their historical reconciliation-only outcome. Stock
|
|
2349
|
+
# length additionally records the compiler measurement identity so its
|
|
2350
|
+
# completeness check can distinguish a placement drop from missing output.
|
|
2351
|
+
on_drop=dropped,
|
|
2311
2352
|
force=True,
|
|
2312
2353
|
feature=g.ref,
|
|
2313
2354
|
measurement=pd.id,
|
|
@@ -55,6 +55,7 @@ from draftwright.annotations.from_model import (
|
|
|
55
55
|
render_pmi,
|
|
56
56
|
render_pockets,
|
|
57
57
|
render_polygonal_bosses,
|
|
58
|
+
render_polygonal_stock,
|
|
58
59
|
render_rotational,
|
|
59
60
|
render_slots,
|
|
60
61
|
render_step_lengths,
|
|
@@ -214,6 +215,7 @@ def build_model(a: Analysis):
|
|
|
214
215
|
patterns=a.patterns,
|
|
215
216
|
bosses=a.bosses,
|
|
216
217
|
polygonal_bosses=a.recognition.polygonal_bosses,
|
|
218
|
+
polygonal_stock=a.recognition.polygonal_stock,
|
|
217
219
|
channels=a.recognition.channels,
|
|
218
220
|
slots=a.slots,
|
|
219
221
|
# The engine's SECOND build_part_model call site; unthreaded, these three were
|
|
@@ -522,6 +524,7 @@ def _auto_annotate(dwg, a: Analysis, *, detail_view: bool = False):
|
|
|
522
524
|
|
|
523
525
|
def _s_polygonal_bosses():
|
|
524
526
|
render_polygonal_bosses(dwg, _compiled, a, ctx=ctx)
|
|
527
|
+
render_polygonal_stock(dwg, _compiled, a, ctx=ctx)
|
|
525
528
|
|
|
526
529
|
def _s_diameters():
|
|
527
530
|
# Turned-part dimensions via the IR (ADR 0008 convergence). The model is built
|
|
@@ -72,7 +72,7 @@ def _est_right_strip_depth(n_steps: int, n_extra: int = 0) -> float:
|
|
|
72
72
|
|
|
73
73
|
|
|
74
74
|
def _n_right_strip_boss_heights(model) -> int:
|
|
75
|
-
"""How many
|
|
75
|
+
"""How many axial-size dims need slots beyond the canonical overall-height slot (#1022).
|
|
76
76
|
|
|
77
77
|
``render_boss_heights`` puts a Z-axis boss's height there, sharing the strip with the step
|
|
78
78
|
ladder — but the depth estimate counted only ladder steps, so the strip was sized for one
|
|
@@ -88,13 +88,16 @@ def _n_right_strip_boss_heights(model) -> int:
|
|
|
88
88
|
"""
|
|
89
89
|
if any(getattr(f, "kind", None) == "step" for f in model.features):
|
|
90
90
|
return 0
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
occupants = [
|
|
92
|
+
f
|
|
93
93
|
for f in model.features
|
|
94
|
-
if getattr(f, "kind", None) in ("boss", "polygonal_boss")
|
|
95
|
-
and getattr(f, "height", None) is not None
|
|
94
|
+
if getattr(f, "kind", None) in ("boss", "polygonal_boss", "polygonal_stock")
|
|
95
|
+
and (getattr(f, "height", None) is not None or getattr(f, "length", None) is not None)
|
|
96
96
|
and f.frame.axis == "z"
|
|
97
|
-
|
|
97
|
+
]
|
|
98
|
+
# Polygonal stock replaces (rather than supplements) `dim_height`, so its axial length
|
|
99
|
+
# uses the estimator's canonical first slot. Other boss heights remain extra occupants.
|
|
100
|
+
return max(0, len(occupants) - any(f.kind == "polygonal_stock" for f in occupants))
|
|
98
101
|
|
|
99
102
|
|
|
100
103
|
def _est_pv_below_depth() -> float:
|
|
@@ -83,6 +83,7 @@ from draftwright.linting import (
|
|
|
83
83
|
lint_pmi_ignored,
|
|
84
84
|
lint_pmi_lowering,
|
|
85
85
|
lint_pmi_rendering,
|
|
86
|
+
lint_polygonal_stock_coverage,
|
|
86
87
|
lint_principal_profile_coverage,
|
|
87
88
|
lint_prismatic_coverage,
|
|
88
89
|
lint_profiled_bore_coverage,
|
|
@@ -129,6 +130,8 @@ _GEOMETRY_AWARE_CODES = frozenset(
|
|
|
129
130
|
"channel_width_dropped",
|
|
130
131
|
"flat_dropped",
|
|
131
132
|
"polygonal_boss_dropped",
|
|
133
|
+
"polygonal_stock_dropped",
|
|
134
|
+
"polygonal_stock_length_dropped",
|
|
132
135
|
"pmi_not_extracted",
|
|
133
136
|
"placement_unsatisfiable",
|
|
134
137
|
"pmi_dropped",
|
|
@@ -2876,6 +2879,14 @@ class Drawing:
|
|
|
2876
2879
|
omissions=self._build.omissions,
|
|
2877
2880
|
assembly=self.assembly,
|
|
2878
2881
|
)
|
|
2882
|
+
issues += lint_polygonal_stock_coverage(
|
|
2883
|
+
self.part,
|
|
2884
|
+
recognition=recognition,
|
|
2885
|
+
features=getattr(model, "features", ()) if model is not None else (),
|
|
2886
|
+
registry=self._registry,
|
|
2887
|
+
omissions=self._build.omissions,
|
|
2888
|
+
assembly=self.assembly,
|
|
2889
|
+
)
|
|
2879
2890
|
# Reverse direction (#487): a DECLARED feature with no matching geometry (a stale
|
|
2880
2891
|
# phantom callout). Only for a caller-supplied model — detection can't over-declare.
|
|
2881
2892
|
# _part_model is typed `object` (deliberately loose, #397); read features duck-typed.
|
|
@@ -36,6 +36,7 @@ from draftwright.linting.pmi_coverage import (
|
|
|
36
36
|
lint_pmi_rendering,
|
|
37
37
|
pmi_stage_summary,
|
|
38
38
|
)
|
|
39
|
+
from draftwright.linting.polygonal_stock_coverage import lint_polygonal_stock_coverage
|
|
39
40
|
from draftwright.linting.profiled_bore_coverage import lint_profiled_bore_coverage
|
|
40
41
|
from draftwright.linting.slot_coverage import lint_slot_coverage
|
|
41
42
|
from draftwright.linting.structural import lint_drawing
|
|
@@ -44,6 +45,7 @@ from draftwright.linting.suggest import _suggest_fix
|
|
|
44
45
|
__all__ = [
|
|
45
46
|
"CoverageState",
|
|
46
47
|
"LintIssue",
|
|
48
|
+
"lint_polygonal_stock_coverage",
|
|
47
49
|
"_suggest_fix",
|
|
48
50
|
"lint_axial_coverage",
|
|
49
51
|
"lint_boss_height_coverage",
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""Semantic completeness for whole-part polygonal stock (#1082)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Literal
|
|
7
|
+
|
|
8
|
+
from draftwright.linting.issues import LintIssue
|
|
9
|
+
from draftwright.recognition import RecognitionResult
|
|
10
|
+
|
|
11
|
+
PolygonalStockState = Literal["placed", "suppressed", "dropped", "missing", "unverifiable"]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class PolygonalStockOutcome:
|
|
16
|
+
parameter_id: str
|
|
17
|
+
state: PolygonalStockState
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _rounded(value) -> float:
|
|
21
|
+
return round(float(value), 3)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _source_key(stock) -> tuple:
|
|
25
|
+
return (
|
|
26
|
+
stock.axis,
|
|
27
|
+
tuple(_rounded(value) for value in stock.center),
|
|
28
|
+
stock.side_count,
|
|
29
|
+
_rounded(stock.across_flats),
|
|
30
|
+
_rounded(stock.length),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _feature_key(feature) -> tuple:
|
|
35
|
+
return (
|
|
36
|
+
feature.frame.axis,
|
|
37
|
+
tuple(_rounded(value) for value in feature.frame.origin),
|
|
38
|
+
feature.side_count,
|
|
39
|
+
_rounded(feature.across_flats),
|
|
40
|
+
_rounded(feature.length),
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def polygonal_stock_outcomes(recognition, features, registry, omissions=()):
|
|
45
|
+
"""Join each geometric stock requirement to its compiler/placement outcome."""
|
|
46
|
+
if recognition is None:
|
|
47
|
+
return []
|
|
48
|
+
if not isinstance(recognition, RecognitionResult):
|
|
49
|
+
raise TypeError("polygonal_stock_outcomes() requires the run's RecognitionResult")
|
|
50
|
+
by_key: dict[tuple, list] = {}
|
|
51
|
+
for feature in features:
|
|
52
|
+
if getattr(feature, "kind", None) == "polygonal_stock":
|
|
53
|
+
by_key.setdefault(_feature_key(feature), []).append(feature)
|
|
54
|
+
placed = {
|
|
55
|
+
measurement for name in registry.names() for measurement in registry.measurement_of(name)
|
|
56
|
+
}
|
|
57
|
+
suppressed = {
|
|
58
|
+
(omission.feature, omission.parameter_id)
|
|
59
|
+
for omission in omissions
|
|
60
|
+
if omission.feature is not None and omission.authored
|
|
61
|
+
}
|
|
62
|
+
dropped = {
|
|
63
|
+
measurement
|
|
64
|
+
for issue in registry.issues
|
|
65
|
+
for measurement in getattr(issue, "measurement_ids", ())
|
|
66
|
+
}
|
|
67
|
+
outcomes = []
|
|
68
|
+
for source in recognition.polygonal_stock:
|
|
69
|
+
matches = by_key.get(_source_key(source), ())
|
|
70
|
+
feature = matches[0] if len(matches) == 1 else None
|
|
71
|
+
for parameter in ("polygon_across_flats.length", "stock_length.length"):
|
|
72
|
+
if feature is None:
|
|
73
|
+
state: PolygonalStockState = "unverifiable"
|
|
74
|
+
elif any(
|
|
75
|
+
measurement.feature == feature and measurement.parameter == parameter
|
|
76
|
+
for measurement in placed
|
|
77
|
+
):
|
|
78
|
+
state = "placed"
|
|
79
|
+
elif (feature, parameter) in suppressed:
|
|
80
|
+
state = "suppressed"
|
|
81
|
+
elif any(
|
|
82
|
+
measurement.feature == feature and measurement.parameter == parameter
|
|
83
|
+
for measurement in dropped
|
|
84
|
+
):
|
|
85
|
+
state = "dropped"
|
|
86
|
+
else:
|
|
87
|
+
state = "missing"
|
|
88
|
+
outcomes.append(PolygonalStockOutcome(parameter, state))
|
|
89
|
+
return outcomes
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def lint_polygonal_stock_coverage(
|
|
93
|
+
part, *, recognition, features, registry, omissions=(), assembly=None
|
|
94
|
+
) -> list[LintIssue]:
|
|
95
|
+
"""Require A/F and axial length, without duplicating an explicit placement drop."""
|
|
96
|
+
if assembly is None:
|
|
97
|
+
assembly = len(part.solids()) > 1
|
|
98
|
+
messages = {
|
|
99
|
+
"suppressed": "was deliberately omitted by the authored dimension set",
|
|
100
|
+
"missing": "has no placed, suppressed, or dropped measurement outcome",
|
|
101
|
+
"unverifiable": "cannot be joined to imported geometry without guessing",
|
|
102
|
+
}
|
|
103
|
+
issues = []
|
|
104
|
+
for outcome in polygonal_stock_outcomes(recognition, features, registry, omissions):
|
|
105
|
+
if outcome.state in {"placed", "dropped"}:
|
|
106
|
+
continue
|
|
107
|
+
issues.append(
|
|
108
|
+
LintIssue(
|
|
109
|
+
severity="info" if assembly else "warning",
|
|
110
|
+
code=f"polygonal_stock_requirement_{outcome.state}",
|
|
111
|
+
message=f"polygonal stock {outcome.parameter_id} {messages[outcome.state]}",
|
|
112
|
+
)
|
|
113
|
+
)
|
|
114
|
+
return issues
|
|
@@ -43,6 +43,7 @@ from draftwright.model.declare import (
|
|
|
43
43
|
pocket,
|
|
44
44
|
pocket_pattern,
|
|
45
45
|
polygonal_boss,
|
|
46
|
+
polygonal_stock,
|
|
46
47
|
rotational,
|
|
47
48
|
slot,
|
|
48
49
|
slot_pattern,
|
|
@@ -78,6 +79,7 @@ from draftwright.model.ir import (
|
|
|
78
79
|
PocketFeature,
|
|
79
80
|
PocketPatternFeature,
|
|
80
81
|
PolygonalBossFeature,
|
|
82
|
+
PolygonalStockFeature,
|
|
81
83
|
RotationalFeature,
|
|
82
84
|
SlotFeature,
|
|
83
85
|
SlotPatternFeature,
|
|
@@ -131,6 +133,7 @@ __all__ = [
|
|
|
131
133
|
"StepFeature",
|
|
132
134
|
"PlateFeature",
|
|
133
135
|
"PolygonalBossFeature",
|
|
136
|
+
"PolygonalStockFeature",
|
|
134
137
|
"StepLevelFeature",
|
|
135
138
|
"measured_dimension",
|
|
136
139
|
"build_part_model",
|
|
@@ -155,6 +158,7 @@ __all__ = [
|
|
|
155
158
|
"pocket",
|
|
156
159
|
"pocket_pattern",
|
|
157
160
|
"polygonal_boss",
|
|
161
|
+
"polygonal_stock",
|
|
158
162
|
"slot",
|
|
159
163
|
"slot_pattern",
|
|
160
164
|
"step",
|
|
@@ -60,6 +60,7 @@ from draftwright.model.ir import (
|
|
|
60
60
|
PartModel,
|
|
61
61
|
PocketFeature,
|
|
62
62
|
Point,
|
|
63
|
+
PolygonalStockFeature,
|
|
63
64
|
RotationalFeature,
|
|
64
65
|
SlotFeature,
|
|
65
66
|
SlotPatternFeature,
|
|
@@ -254,6 +255,7 @@ _FACTS: dict[str, tuple[str, ...]] = {
|
|
|
254
255
|
"pad": ("frame", "width_axis", "long_axis"),
|
|
255
256
|
"boss": ("frame", "thread"),
|
|
256
257
|
"polygonal_boss": ("frame", "side_count", "flat_directions", "flat_centres"),
|
|
258
|
+
"polygonal_stock": ("frame", "side_count", "flat_directions", "flat_centres"),
|
|
257
259
|
"step": ("frame", "thread"),
|
|
258
260
|
"step_level": ("frame",),
|
|
259
261
|
"envelope": ("frame",),
|
|
@@ -806,6 +808,11 @@ def _compile_overall_height(
|
|
|
806
808
|
`.envelope()` — has no parameter naming its height, and the value falls back to the
|
|
807
809
|
bounding box. The COMPILER may do that; a renderer may not, which is the whole point.
|
|
808
810
|
"""
|
|
811
|
+
# Whole polygonal stock already owns the same axial extent as `stock_length.length`.
|
|
812
|
+
# Letting the bbox fallback add `dim_height` would state one physical requirement twice,
|
|
813
|
+
# and would make authored suppression ineffective through an unrelated synthetic value.
|
|
814
|
+
if any(isinstance(feature, PolygonalStockFeature) for feature in model.features):
|
|
815
|
+
return None, None, []
|
|
809
816
|
env = next((f for f in model.features if isinstance(f, EnvelopeFeature)), None)
|
|
810
817
|
bb: Any = model.bbox # build123d BoundBox
|
|
811
818
|
rot = next((f for f in model.features if isinstance(f, RotationalFeature)), None)
|
|
@@ -55,6 +55,7 @@ from draftwright.model.ir import (
|
|
|
55
55
|
PocketPatternFeature,
|
|
56
56
|
Point,
|
|
57
57
|
PolygonalBossFeature,
|
|
58
|
+
PolygonalStockFeature,
|
|
58
59
|
RotationalFeature,
|
|
59
60
|
SlotFeature,
|
|
60
61
|
SlotPatternFeature,
|
|
@@ -411,6 +412,34 @@ def polygonal_boss(
|
|
|
411
412
|
)
|
|
412
413
|
|
|
413
414
|
|
|
415
|
+
def polygonal_stock(
|
|
416
|
+
*,
|
|
417
|
+
side_count,
|
|
418
|
+
across_flats,
|
|
419
|
+
length,
|
|
420
|
+
at,
|
|
421
|
+
axis,
|
|
422
|
+
flat_directions,
|
|
423
|
+
flat_centres,
|
|
424
|
+
span=None,
|
|
425
|
+
) -> PolygonalStockFeature:
|
|
426
|
+
"""Declare whole regular polygonal-prism stock by its manufacturing definition."""
|
|
427
|
+
axis = _norm_axis(axis)
|
|
428
|
+
_require_positive(across_flats=across_flats, length=length)
|
|
429
|
+
_require_point("at", at)
|
|
430
|
+
if span is None:
|
|
431
|
+
span = _span(at, axis, length)
|
|
432
|
+
return PolygonalStockFeature(
|
|
433
|
+
frame=Frame(tuple(at), axis),
|
|
434
|
+
side_count=side_count,
|
|
435
|
+
across_flats=across_flats,
|
|
436
|
+
length=length,
|
|
437
|
+
span=span,
|
|
438
|
+
flat_directions=flat_directions,
|
|
439
|
+
flat_centres=flat_centres,
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
|
|
414
443
|
def step(
|
|
415
444
|
obj=None, *, diameter=None, length=None, at=None, axis=None, span=None, thread=None
|
|
416
445
|
) -> StepFeature:
|
|
@@ -44,6 +44,7 @@ from draftwright.model.ir import (
|
|
|
44
44
|
PocketFeature,
|
|
45
45
|
PocketPatternFeature,
|
|
46
46
|
PolygonalBossFeature,
|
|
47
|
+
PolygonalStockFeature,
|
|
47
48
|
RotationalFeature,
|
|
48
49
|
SlotFeature,
|
|
49
50
|
SlotPatternFeature,
|
|
@@ -69,6 +70,7 @@ from draftwright.recognition import (
|
|
|
69
70
|
PocketArray,
|
|
70
71
|
PocketGrid,
|
|
71
72
|
PolygonalBoss,
|
|
73
|
+
PolygonalStock,
|
|
72
74
|
RaisedPad,
|
|
73
75
|
RectGrid,
|
|
74
76
|
RiserEvidence,
|
|
@@ -94,6 +96,7 @@ from draftwright.recognition import (
|
|
|
94
96
|
recognise_pocket_patterns,
|
|
95
97
|
recognise_pockets,
|
|
96
98
|
recognise_polygonal_bosses,
|
|
99
|
+
recognise_polygonal_stock,
|
|
97
100
|
recognise_rectangular_pads,
|
|
98
101
|
recognise_risers,
|
|
99
102
|
recognise_slot_patterns,
|
|
@@ -581,6 +584,24 @@ def _convert_polygonal_boss(boss: PolygonalBoss, ctx: ConvContext) -> PolygonalB
|
|
|
581
584
|
)
|
|
582
585
|
|
|
583
586
|
|
|
587
|
+
def _convert_polygonal_stock(stock: PolygonalStock, ctx: ConvContext) -> PolygonalStockFeature:
|
|
588
|
+
centre = stock.center
|
|
589
|
+
lo = list(centre)
|
|
590
|
+
hi = list(centre)
|
|
591
|
+
axis_index = "xyz".index(stock.axis)
|
|
592
|
+
lo[axis_index] = stock.base
|
|
593
|
+
hi[axis_index] = stock.top
|
|
594
|
+
return PolygonalStockFeature(
|
|
595
|
+
frame=Frame(origin=centre, axis=stock.axis),
|
|
596
|
+
side_count=stock.side_count,
|
|
597
|
+
across_flats=stock.across_flats,
|
|
598
|
+
length=stock.length,
|
|
599
|
+
span=((lo[0], lo[1], lo[2]), (hi[0], hi[1], hi[2])),
|
|
600
|
+
flat_directions=stock.flat_directions,
|
|
601
|
+
flat_centres=stock.flat_centres,
|
|
602
|
+
)
|
|
603
|
+
|
|
604
|
+
|
|
584
605
|
def _convert_plate(pl: Plate, ctx: ConvContext) -> PlateFeature:
|
|
585
606
|
c = ctx.bbox.center()
|
|
586
607
|
return PlateFeature(
|
|
@@ -643,6 +664,7 @@ _CONVERTERS: dict[type, Converter] = {
|
|
|
643
664
|
TurnedStep: _convert_step,
|
|
644
665
|
BossRecord: _convert_boss,
|
|
645
666
|
PolygonalBoss: _convert_polygonal_boss,
|
|
667
|
+
PolygonalStock: _convert_polygonal_stock,
|
|
646
668
|
Plate: _convert_plate,
|
|
647
669
|
Chamfer: _convert_chamfer,
|
|
648
670
|
Fillet: _convert_fillet,
|
|
@@ -700,6 +722,7 @@ def build_part_model(
|
|
|
700
722
|
patterns=None,
|
|
701
723
|
bosses=None,
|
|
702
724
|
polygonal_bosses=None,
|
|
725
|
+
polygonal_stock=None,
|
|
703
726
|
channels=None,
|
|
704
727
|
slots=None,
|
|
705
728
|
slot_patterns=None,
|
|
@@ -860,6 +883,12 @@ def build_part_model(
|
|
|
860
883
|
polygonal_bosses = recognise_polygonal_bosses(part)
|
|
861
884
|
features.extend(convert(boss, ctx) for boss in polygonal_bosses)
|
|
862
885
|
|
|
886
|
+
# A whole regular polygonal prism is stock, not a boss: it owns the form/A-F
|
|
887
|
+
# definition and its axial stock length independently of attachment evidence.
|
|
888
|
+
if polygonal_stock is None:
|
|
889
|
+
polygonal_stock = recognise_polygonal_stock(part)
|
|
890
|
+
features.extend(convert(stock, ctx) for stock in polygonal_stock)
|
|
891
|
+
|
|
863
892
|
# Turned / circlip grooves (#148c) — recognised up front so the turned-step chain can
|
|
864
893
|
# exclude any band a groove already dimensions: a groove floor is an annular band, and
|
|
865
894
|
# its two walls read as shoulders, so recognise_turned_steps also delimits it as a
|
|
@@ -914,7 +943,7 @@ def build_part_model(
|
|
|
914
943
|
# Overall envelope dims for a *prismatic* part — not a round single-OD body
|
|
915
944
|
# (a boss whose diameter fills the footprint is the body, dimensioned by its
|
|
916
945
|
# OD, not a box).
|
|
917
|
-
if not _is_round(bbox, bosses_d):
|
|
946
|
+
if not _is_round(bbox, bosses_d) and not polygonal_stock:
|
|
918
947
|
# The same construction the declared verb and the emitter's synthesis use.
|
|
919
948
|
# Detection was the REFERENCE the other two were fixed to match (#977/#976); with
|
|
920
949
|
# three independent producers, "matches the detector" was a property to re-verify
|