draftwright 0.3.7__tar.gz → 0.3.9__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.3.7 → draftwright-0.3.9}/.gitignore +3 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/CHANGELOG.md +106 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/PKG-INFO +12 -2
- {draftwright-0.3.7 → draftwright-0.3.9}/README.md +10 -1
- {draftwright-0.3.7 → draftwright-0.3.9}/pyproject.toml +12 -1
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/_core.py +29 -12
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/analysis.py +4 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/annotations/_common.py +25 -2
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/annotations/balloons.py +1 -1
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/annotations/from_model.py +182 -77
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/annotations/holes.py +243 -17
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/annotations/orchestrator.py +33 -11
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/annotations/sections.py +61 -46
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/builder.py +46 -31
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/cli.py +9 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/drawing.py +383 -91
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/linting/coverage.py +7 -1
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/linting/suggest.py +9 -9
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/make_drawing.py +5 -4
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/model/__init__.py +8 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/model/declare.py +258 -5
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/model/detect.py +391 -135
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/model/ir.py +79 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/model/planner.py +18 -5
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/projection.py +48 -7
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/recognition/__init__.py +18 -1
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/recognition/_features.py +42 -21
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/recognition/slots.py +506 -11
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/recognition/turned.py +8 -2
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/sheet.py +191 -12
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/sheet_emit.py +240 -4
- draftwright-0.3.9/tests/fixtures/tuner_jig_blind_obround_pockets.step +4109 -0
- draftwright-0.3.9/tests/test_agents_guide.py +113 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/test_declare.py +112 -1
- draftwright-0.3.9/tests/test_detect_registry.py +166 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/test_drawing_encapsulation.py +65 -9
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/test_e2e_standards.py +3 -3
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/test_export_dxf_zoom.py +2 -2
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/test_gdt_placement.py +41 -10
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/test_import_boundaries.py +4 -5
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/test_make_drawing.py +354 -103
- draftwright-0.3.9/tests/test_note_verb.py +59 -0
- draftwright-0.3.9/tests/test_pocket_pattern.py +259 -0
- draftwright-0.3.9/tests/test_pocket_pattern_recognition.py +191 -0
- draftwright-0.3.9/tests/test_private_test_attr_reads.py +298 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/test_recogniser_contract.py +56 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/test_render_seam.py +17 -6
- draftwright-0.3.9/tests/test_script_detail_parity.py +400 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/test_sheet_emit.py +45 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/test_sheet_notes.py +35 -0
- draftwright-0.3.9/tests/test_sheet_section.py +103 -0
- draftwright-0.3.9/tests/test_slot_pattern.py +151 -0
- draftwright-0.3.9/tests/test_slot_pattern_recognition.py +181 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/test_strip_layout.py +24 -16
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/test_tolerances.py +118 -7
- draftwright-0.3.7/tests/test_script_detail_parity.py +0 -185
- {draftwright-0.3.7 → draftwright-0.3.9}/LICENSE +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/docs/adr/README.md +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/skills/SKILL.md +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/__init__.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/_geometry.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/annotate.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/annotations/__init__.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/compose.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/export.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/fits.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/fonts/IBMPlexMono-Regular.ttf +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/fonts/IBMPlexSansCondensed-Regular.ttf +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/fonts/LICENSE-IBMPlexMono-OFL.txt +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/fonts/LICENSE-IBMPlexSansCondensed-OFL.txt +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/fonts/__init__.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/intents.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/layout.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/linting/__init__.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/linting/issues.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/linting/structural.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/pmi.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/recognition/_record.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/recognition/chamfers.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/recognition/countersinks.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/recognition/fillets.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/recognition/flats.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/recognition/grooves.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/recognition/levels.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/recognition/plates.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/registry.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/repair.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/score.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/src/draftwright/sheet_dsl.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/_kernel.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/_layout_sig.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/fixtures/grm03_thumbwheel_drive_screw.step +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/fixtures/nist_ctc_01_asme1_ap203.stp +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/fixtures/nist_ctc_01_asme1_ap242.stp +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/fixtures/nist_ctc_02_asme1_ap203.stp +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/fixtures/nist_ctc_02_asme1_ap242.stp +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/fixtures/nist_ctc_03_asme1_ap203.stp +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/fixtures/nist_ctc_03_asme1_ap242.stp +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/fixtures/nist_ctc_04_asme1_ap203.stp +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/fixtures/nist_ctc_04_asme1_ap242.stp +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/fixtures/nist_ctc_05_asme1_ap203.stp +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/fixtures/nist_ctc_05_asme1_ap242.stp +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/refactor_golden/bracket_section.json +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/refactor_golden/centered_rebate.json +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/refactor_golden/chamfered.json +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/refactor_golden/filleted.json +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/refactor_golden/flange_dense.json +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/refactor_golden/grid_plate.json +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/refactor_golden/grooved_shaft.json +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/refactor_golden/hex_bar.json +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/refactor_golden/holed_slot.json +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/refactor_golden/pocketed.json +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/refactor_golden/prismatic_ladder.json +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/refactor_golden/scattered_plate.json +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/refactor_golden/side_drilled.json +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/refactor_golden/turned_stepped.json +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/test_architecture_docs.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/test_carve_free_position_callers.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/test_dense_sheet_canary.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/test_detect_once.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/test_e2e_slice.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/test_fillets_adjacency.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/test_fits.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/test_layout.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/test_layout_cleanliness.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/test_layout_hypothesis.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/test_layout_property.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/test_lint_box_cache.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/test_lint_reconciliation.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/test_lint_structural.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/test_linting.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/test_object_aspects.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/test_occupancy_boxes.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/test_part_model.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/test_pitch_dim_footprint.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/test_pmi.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/test_private_test_imports.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/test_recognition.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/test_refactor_golden.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/test_registry.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/test_score.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/test_sheet_gdt.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/test_sheet_of.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/test_sheet_tables.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/test_solve_trace.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/test_turned_steps.py +0 -0
- {draftwright-0.3.7 → draftwright-0.3.9}/tests/test_witness_label_reconciliation.py +0 -0
|
@@ -1,5 +1,111 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v0.3.9 — 2026-07-26
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **Repeated recesses are dimensioned as one pattern, not N competing callouts**
|
|
8
|
+
(#841): identical blind pockets and identical through-slots now collapse to a single
|
|
9
|
+
grouped `5× 7.9 × 13.6 × 19 DEEP` / `4× SLOT 8 × 30` leader plus `(n-1)× pitch`
|
|
10
|
+
dim(s). Previously each member competed for its own size dims and some silently
|
|
11
|
+
dropped for want of strip room — five declared slots rendered only three length dims.
|
|
12
|
+
Both new kinds (`PocketPatternFeature`, `SlotPatternFeature`) round-trip every
|
|
13
|
+
surface: declare (`sheet.pocket_pattern(...)` / `sheet.slot_pattern(...)`),
|
|
14
|
+
recognition from a real solid (coplanar, same-facing members only), the emitted
|
|
15
|
+
Sheet script, and the editable surface (`dwg.callout(feature)` + `finalize()`).
|
|
16
|
+
- **`Sheet.section()` and `Sheet.detail()` — ask for the view you need** (#841/#847):
|
|
17
|
+
a section A–A auto-fires only for a Z-axis hole with a counterbore, spotface, or
|
|
18
|
+
blind bottom, so a blind pocket's floor and depth stayed hidden-line-only with no
|
|
19
|
+
supported way to request a cut. `sheet.section(feature)` / `section(at=y)` /
|
|
20
|
+
bare `section()` force one (the cut plane is validated to lie strictly inside the
|
|
21
|
+
part), and `sheet.detail()` exposes the existing `detail_view=True` opt-in as a
|
|
22
|
+
chainable verb.
|
|
23
|
+
- **External threads are a first-class declared aspect** (#859): the turned analog of
|
|
24
|
+
the #764 internal thread — `sheet.step(shaft).thread("M3x0.5")`,
|
|
25
|
+
`sheet.diameter(...).thread(...)`, and `sheet.boss(...).thread(...)` append the
|
|
26
|
+
spec to the ⌀ callout (`ø3 M3x0.5`) instead of needing a free-text `.note()`. It
|
|
27
|
+
composes with `.finish()` for Ra-on-thread, keys the callout bucket on `(⌀, thread)`
|
|
28
|
+
so a threaded and a plain ø6 stay distinct, and now round-trips through the Sheet
|
|
29
|
+
emitter — including pattern-member holes, closing the symmetric latent #764 gap.
|
|
30
|
+
Declaration-only: a plain cylinder is geometrically indistinguishable from a
|
|
31
|
+
threaded one, so there is no recogniser. *(Known limitation: the incremental
|
|
32
|
+
`callout`/`finalize(only=…)` paths still dedup thread-blind — #863.)*
|
|
33
|
+
- **Blind obround pocket recognition, including imported STEP** (#837): the blind
|
|
34
|
+
counterpart of the #816 through-slot work. A stubby floored obround has side walls
|
|
35
|
+
too short to pair, so it is recovered from its semicircular end caps — clustering
|
|
36
|
+
faces by axis proximity so the quarter-cylinder split a STEP importer commonly
|
|
37
|
+
produces recognises as well as build123d's half-cylinder. A sealed internal void
|
|
38
|
+
(capped at both ends) is correctly *not* a pocket. A real tuner-jig STEP with five
|
|
39
|
+
blind pockets ships as a fixture.
|
|
40
|
+
- **`sheet.slot(...).note(...)` / `sheet.pocket(...).note(...)`** (#841/#845): the
|
|
41
|
+
slot/pocket handle now anchors a note to its own feature, with no `ref=` needed
|
|
42
|
+
(an explicit `ref` still forwards, as before).
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
|
|
46
|
+
- **An anchored note, GD&T frame, or finish relaxes its side instead of silently
|
|
47
|
+
vanishing** (#841/#855): an annotation declared with an explicit `view=`/`side=`
|
|
48
|
+
used to drop with only a warning when that strip was full, while the export still
|
|
49
|
+
reported success. The requested side is now a *preference* — when the strip has no
|
|
50
|
+
room the placer tries the opposite side, then the two perpendicular sides, taking
|
|
51
|
+
the first with room (via the same bounds and title-block checks, so it never
|
|
52
|
+
overshoots). A relaxed placement records an INFO `gdt_side_relaxed` issue naming
|
|
53
|
+
requested versus actual. Only when no strip fits does it drop.
|
|
54
|
+
|
|
55
|
+
### Internal
|
|
56
|
+
|
|
57
|
+
- **The state-bus endgame is closed to its permanent seam** (#830/#840): the
|
|
58
|
+
solve trace is filled into `BuildState` at the one construction site, the redundant
|
|
59
|
+
part-model re-attach is gone, and the detail view is now *transactional* — geometry
|
|
60
|
+
is projected at the detail scale and committed only if dimensions land, so
|
|
61
|
+
`_drop_view_coordinates` has zero engine callers. With those removed, the #817
|
|
62
|
+
decision-D method-call exemption narrows from a blanket pass to a named
|
|
63
|
+
`_LAYOUT_SEAM` allowlist (`_add_view`, `_set_view_coordinates`), accepted as the
|
|
64
|
+
permanent interactive-layout seam; every other private-method call on the drawing
|
|
65
|
+
is now flagged.
|
|
66
|
+
- **A draftwright logo set** (#843) — drafting-idiom mark, all text outlined so the
|
|
67
|
+
SVGs render font-independently; the README now leads with the lockup.
|
|
68
|
+
- **A worked multi-feature object-reference example** (#853) in
|
|
69
|
+
`docs/multi-feature-object-reference-workflow.md`.
|
|
70
|
+
|
|
71
|
+
## v0.3.8 — 2026-07-23
|
|
72
|
+
|
|
73
|
+
### Added
|
|
74
|
+
|
|
75
|
+
- **`Drawing.note()` — a public free-text note verb** (#817/#820): place a free-form
|
|
76
|
+
`Note` at a page point without reaching for the low-level placement primitive —
|
|
77
|
+
`dwg.note("SEE NOTE 1", at=(x, y))`. This is the safe door for free text that the
|
|
78
|
+
privatisation below needs.
|
|
79
|
+
- **Obround (racetrack) through-slot recognition** (#816): a part whose only features
|
|
80
|
+
are obround through-slots is now recognised, dimensioned, and referenceable for GD&T.
|
|
81
|
+
Previously such slots were silently undetected — and lint falsely reported the drawing
|
|
82
|
+
complete. Stubby obrounds, whose straight side walls are too short to pair, are recovered
|
|
83
|
+
from their two semicircular end caps (a round hole is never mistaken for a slot end).
|
|
84
|
+
|
|
85
|
+
### Changed
|
|
86
|
+
|
|
87
|
+
- **The generated Sheet script is now self-describing** (#833): `--script --style sheet`
|
|
88
|
+
emits a feature-census header, section sub-headers with repeat tallies (`8× R50`), and a
|
|
89
|
+
plain-language comment on each verb line (`# ⌀8 THRU ×4`, `# slot 40 × 120`, `# R50`).
|
|
90
|
+
All additions are comment-only, so re-run fidelity is unchanged.
|
|
91
|
+
|
|
92
|
+
### Deprecated
|
|
93
|
+
|
|
94
|
+
- **The low-level `Drawing` placement API is now private — the public surface is
|
|
95
|
+
safe-by-default** (#817): `add`, `place_dim`, and the view-coordinate / build-state
|
|
96
|
+
plumbing (`set_view_coordinates`, `drop_view_coordinates`, `attach_part_model`,
|
|
97
|
+
`attach_solve_trace`, `add_view`, `clear_annotations`) are deprecated in favour of the
|
|
98
|
+
feature-backed verbs (`callout` / `dimension` / `locate` / `note` / …), which route
|
|
99
|
+
through the layout solve. Each keeps a `@deprecated` (PEP 702) shim for one release —
|
|
100
|
+
so type-checkers and IDEs flag call sites, not just a runtime warning — and is slated
|
|
101
|
+
for removal in ~0.5.0.
|
|
102
|
+
|
|
103
|
+
### Internal
|
|
104
|
+
|
|
105
|
+
- **`AGENTS.md` — an AI-agent usage guide** (#818) with an executable check, steering
|
|
106
|
+
agents toward the safe declarative surface rather than the low-level primitives.
|
|
107
|
+
- **Coverage reporting is governed and a Codecov badge added** (#825/#832).
|
|
108
|
+
|
|
3
109
|
## v0.3.7 — 2026-07-21
|
|
4
110
|
|
|
5
111
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: draftwright
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.9
|
|
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
|
|
@@ -691,11 +691,15 @@ Requires-Dist: pypdfium2>=4
|
|
|
691
691
|
Requires-Dist: reportlab>=4.0
|
|
692
692
|
Requires-Dist: svglib>=1.5
|
|
693
693
|
Requires-Dist: typer>=0.12
|
|
694
|
+
Requires-Dist: typing-extensions>=4.5; python_full_version < '3.13'
|
|
694
695
|
Description-Content-Type: text/markdown
|
|
695
696
|
|
|
696
|
-
|
|
697
|
+
<h1 align="center">
|
|
698
|
+
<img src="docs/images/draftwright-logo.png" alt="draftwright" width="460">
|
|
699
|
+
</h1>
|
|
697
700
|
|
|
698
701
|
[](https://github.com/pzfreo/draftwright/actions/workflows/ci.yml)
|
|
702
|
+
[](https://codecov.io/gh/pzfreo/draftwright)
|
|
699
703
|
[](https://pypi.org/project/draftwright/)
|
|
700
704
|
[](https://pypi.org/project/draftwright/)
|
|
701
705
|
[](LICENSE)
|
|
@@ -830,6 +834,12 @@ sheet.hole(features.m3_bore).thread("M3x0.5").finish("1.6") # tapped + Ra on t
|
|
|
830
834
|
An object-sourced script carries an inline tip pointing at exactly this edit; a STEP-sourced
|
|
831
835
|
script keeps the detected numbers (there's no object to reference).
|
|
832
836
|
|
|
837
|
+
See [`docs/multi-feature-object-reference-workflow.md`](docs/multi-feature-object-reference-workflow.md)
|
|
838
|
+
for a complete, runnable walkthrough of this pattern on a multi-feature turned part —
|
|
839
|
+
including the backward-compatible refactor, external threads (no `.thread()` on a step —
|
|
840
|
+
use `.note(...)`), and why an object-sourced script should point at the cheap geometry
|
|
841
|
+
variant of your source.
|
|
842
|
+
|
|
833
843
|
## What it produces
|
|
834
844
|
|
|
835
845
|
- **Three orthographic views** (front, plan, side) sized and scaled automatically to the
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
<h1 align="center">
|
|
2
|
+
<img src="docs/images/draftwright-logo.png" alt="draftwright" width="460">
|
|
3
|
+
</h1>
|
|
2
4
|
|
|
3
5
|
[](https://github.com/pzfreo/draftwright/actions/workflows/ci.yml)
|
|
6
|
+
[](https://codecov.io/gh/pzfreo/draftwright)
|
|
4
7
|
[](https://pypi.org/project/draftwright/)
|
|
5
8
|
[](https://pypi.org/project/draftwright/)
|
|
6
9
|
[](LICENSE)
|
|
@@ -135,6 +138,12 @@ sheet.hole(features.m3_bore).thread("M3x0.5").finish("1.6") # tapped + Ra on t
|
|
|
135
138
|
An object-sourced script carries an inline tip pointing at exactly this edit; a STEP-sourced
|
|
136
139
|
script keeps the detected numbers (there's no object to reference).
|
|
137
140
|
|
|
141
|
+
See [`docs/multi-feature-object-reference-workflow.md`](docs/multi-feature-object-reference-workflow.md)
|
|
142
|
+
for a complete, runnable walkthrough of this pattern on a multi-feature turned part —
|
|
143
|
+
including the backward-compatible refactor, external threads (no `.thread()` on a step —
|
|
144
|
+
use `.note(...)`), and why an object-sourced script should point at the cheap geometry
|
|
145
|
+
variant of your source.
|
|
146
|
+
|
|
138
147
|
## What it produces
|
|
139
148
|
|
|
140
149
|
- **Three orthographic views** (front, plan, side) sized and scaled automatically to the
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "draftwright"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.9"
|
|
8
8
|
description = "Automated technical-drawing generation for build123d"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { file = "LICENSE" }
|
|
@@ -27,6 +27,8 @@ dependencies = [
|
|
|
27
27
|
"reportlab>=4.0",
|
|
28
28
|
"svglib>=1.5",
|
|
29
29
|
"typer>=0.12",
|
|
30
|
+
# PEP 702 @deprecated: stdlib `warnings.deprecated` lands in 3.13; backport for 3.10–3.12.
|
|
31
|
+
"typing_extensions>=4.5 ; python_full_version < '3.13'",
|
|
30
32
|
]
|
|
31
33
|
keywords = ["build123d", "cad", "drafting", "engineering-drawing", "technical-drawing", "automation"]
|
|
32
34
|
classifiers = [
|
|
@@ -85,8 +87,17 @@ addopts = "--tb=short -m 'not slow'"
|
|
|
85
87
|
[tool.coverage.run]
|
|
86
88
|
source = ["src/draftwright"]
|
|
87
89
|
omit = ["src/draftwright/__init__.py"]
|
|
90
|
+
# Branch coverage makes the regression gate sensitive to untested decision paths,
|
|
91
|
+
# which matter more than raw statement execution in the recognition/layout code.
|
|
92
|
+
branch = true
|
|
88
93
|
|
|
89
94
|
[tool.coverage.report]
|
|
95
|
+
# Baseline measured on the full fast tier on Linux/Python 3.13 (#825):
|
|
96
|
+
# 92.05% combined line+branch coverage (93.90% statements, 86.89% branches).
|
|
97
|
+
# Keep two points of cross-platform/kernel headroom; ratchet upward only after the
|
|
98
|
+
# lowest CI matrix result has stayed above the proposed floor.
|
|
99
|
+
fail_under = 90
|
|
100
|
+
precision = 1
|
|
90
101
|
exclude_lines = [
|
|
91
102
|
"pragma: no cover",
|
|
92
103
|
"if TYPE_CHECKING:",
|
|
@@ -32,6 +32,7 @@ from build123d import Align, BoundBox, Compound, Edge, Location, Mode, Shape, Te
|
|
|
32
32
|
from build123d_drafting.helpers import (
|
|
33
33
|
Dimension,
|
|
34
34
|
TitleBlock,
|
|
35
|
+
annotate,
|
|
35
36
|
draft_preset,
|
|
36
37
|
format_drawing_scale,
|
|
37
38
|
)
|
|
@@ -48,6 +49,21 @@ from draftwright.layout import _greedy_strip_1d, _solve_strip_1d
|
|
|
48
49
|
_log = logging.getLogger(__name__)
|
|
49
50
|
|
|
50
51
|
|
|
52
|
+
def place_annotation(registry, items, obj, name=None, view=None, feature=None):
|
|
53
|
+
"""The annotation-placement primitive (#817): register *obj* under *name* — replacing any
|
|
54
|
+
prior object of that name (dropped from the render list *items*) so a name maps to one
|
|
55
|
+
object — append it to *items*, and record its owning *view* + source *feature* in *registry*.
|
|
56
|
+
Shared by :meth:`Drawing._add` and :meth:`PlacementContext.place` so the render passes place
|
|
57
|
+
annotations without reaching into the ``Drawing``. Returns *obj*."""
|
|
58
|
+
displaced = registry.named(name) if name is not None else None
|
|
59
|
+
if displaced is not None:
|
|
60
|
+
items.remove(displaced)
|
|
61
|
+
annotate(obj, name)
|
|
62
|
+
items.append(obj)
|
|
63
|
+
registry.add(obj, name, view, feature)
|
|
64
|
+
return obj
|
|
65
|
+
|
|
66
|
+
|
|
51
67
|
_MARGIN = 10.0
|
|
52
68
|
|
|
53
69
|
# When a sheet frame is drawn (#767), content reserves this extra band inside the border so
|
|
@@ -588,13 +604,14 @@ class DetailRequest:
|
|
|
588
604
|
axis: part axis the band spans / is cropped along ("x"/"y"/"z").
|
|
589
605
|
lo, hi: band bounds along ``axis`` (world mm).
|
|
590
606
|
scale_needed: detail world→page scale that makes the region legible.
|
|
591
|
-
redraw: ``redraw(dwg, view_name, detail_scale) -> int`` — draws the
|
|
592
|
-
detail's dimensions
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
607
|
+
redraw: ``redraw(dwg, view_name, coords, detail_scale) -> int`` — draws the
|
|
608
|
+
detail's dimensions against *coords* (the detail's
|
|
609
|
+
:class:`ViewCoordinates`, mapped via ``coords.pp``) and returns the count
|
|
610
|
+
placed (0 → the detailer drops the detail without committing a view, #840).
|
|
611
|
+
Called on the SCRATCH-projected coords before the view is committed, so a
|
|
612
|
+
zero result needs no rollback; the main view always carries the located
|
|
613
|
+
head/block inline regardless, so a placement failure loses no coverage
|
|
614
|
+
(lint reports the un-located interior instead).
|
|
598
615
|
pad_top: page-mm band reserved above the detail view (a horizontal
|
|
599
616
|
chain); reserved in the fit + placement.
|
|
600
617
|
pads: optional ``pads(detail_scale) -> (pad_right, pad_top)`` for a
|
|
@@ -841,7 +858,7 @@ def _add_title_block(dwg, a: Analysis):
|
|
|
841
858
|
bx + cell["max_x"],
|
|
842
859
|
_TB_CLEAR + cell["max_y"],
|
|
843
860
|
)
|
|
844
|
-
dwg.
|
|
861
|
+
place_annotation(dwg.registry, dwg.items, tb, "title_block")
|
|
845
862
|
|
|
846
863
|
|
|
847
864
|
def _make_sheet_frame(a: Analysis) -> Compound:
|
|
@@ -866,7 +883,7 @@ def _make_sheet_frame(a: Analysis) -> Compound:
|
|
|
866
883
|
def _add_sheet_frame(dwg, a: Analysis):
|
|
867
884
|
"""Add the sheet border (#767), drawn last like the title block. No-op is the caller's
|
|
868
885
|
(gated on ``a.frame``)."""
|
|
869
|
-
dwg.
|
|
886
|
+
place_annotation(dwg.registry, dwg.items, _make_sheet_frame(a), "sheet_frame")
|
|
870
887
|
|
|
871
888
|
|
|
872
889
|
def _add_projection_symbol(dwg, a: Analysis):
|
|
@@ -893,7 +910,7 @@ def _add_projection_symbol(dwg, a: Analysis):
|
|
|
893
910
|
cy = _TB_CLEAR + _TB_H - h / 2 - 2
|
|
894
911
|
sym = sym.locate(Location((cx - bx, cy - by, 0)))
|
|
895
912
|
sym.is_projection_symbol = True
|
|
896
|
-
dwg.
|
|
913
|
+
place_annotation(dwg.registry, dwg.items, sym, "projection_symbol")
|
|
897
914
|
|
|
898
915
|
|
|
899
916
|
def _add_zone_grid(dwg, a: Analysis):
|
|
@@ -926,12 +943,12 @@ def _add_zone_grid(dwg, a: Analysis):
|
|
|
926
943
|
ticks.append(Edge.make_line(Vector(x1, yb, 0), Vector(x1 + tick, yb, 0)))
|
|
927
944
|
grid = Compound(children=ticks)
|
|
928
945
|
grid.is_zone_grid = True
|
|
929
|
-
dwg.
|
|
946
|
+
place_annotation(dwg.registry, dwg.items, grid, "zone_grid")
|
|
930
947
|
|
|
931
948
|
def _label(text, cx, cy, name):
|
|
932
949
|
note = Note(text, (cx, cy), draft, align=(Align.CENTER, Align.CENTER))
|
|
933
950
|
note.is_zone_label = True
|
|
934
|
-
dwg.
|
|
951
|
+
place_annotation(dwg.registry, dwg.items, note, name)
|
|
935
952
|
|
|
936
953
|
for i in range(cols): # numbers 1.. left→right, in the bottom + top bands
|
|
937
954
|
cx = x0 + (i + 0.5) * cw
|
|
@@ -93,6 +93,10 @@ def _will_section(model, *, is_rotational=False, cx=0.0, cy=0.0) -> bool:
|
|
|
93
93
|
|
|
94
94
|
if model is None:
|
|
95
95
|
return False
|
|
96
|
+
# An explicit Sheet.section() request (ADR 0011, #841) reserves the row even when no
|
|
97
|
+
# hole gate qualifies — a blind pocket's floor/depth section has no driving Z hole.
|
|
98
|
+
if getattr(model, "decorations", {}).get("section") is not None:
|
|
99
|
+
return True
|
|
96
100
|
features = getattr(model, "features", model)
|
|
97
101
|
|
|
98
102
|
def feature_member(pt) -> bool:
|
|
@@ -18,7 +18,11 @@ from typing import Any
|
|
|
18
18
|
|
|
19
19
|
from build123d_drafting.helpers import DEFAULT_FONT_PATH, Dimension, SafeDimension
|
|
20
20
|
|
|
21
|
-
from draftwright._core import
|
|
21
|
+
from draftwright._core import ( # noqa: F401 — _anno_box re-exported (#700)
|
|
22
|
+
_anno_box,
|
|
23
|
+
_text_size,
|
|
24
|
+
place_annotation,
|
|
25
|
+
)
|
|
22
26
|
from draftwright._geometry import _boxes_overlap, _segment_crosses_box # noqa: F401
|
|
23
27
|
from draftwright.layout import StripCandidate, plan_strip
|
|
24
28
|
from draftwright.linting.issues import LintIssue
|
|
@@ -908,6 +912,7 @@ def solve_corridor(dwg, strip, view, axis, cands, tier, corner_reserves=(), *, k
|
|
|
908
912
|
axis,
|
|
909
913
|
pairs,
|
|
910
914
|
tier,
|
|
915
|
+
ctx=ctx,
|
|
911
916
|
features=feats,
|
|
912
917
|
sizes=sizes,
|
|
913
918
|
forbid=forbid,
|
|
@@ -930,6 +935,7 @@ def solve_corridor(dwg, strip, view, axis, cands, tier, corner_reserves=(), *, k
|
|
|
930
935
|
axis,
|
|
931
936
|
force_pairs,
|
|
932
937
|
tier,
|
|
938
|
+
ctx=ctx,
|
|
933
939
|
force=True,
|
|
934
940
|
footprints=foots,
|
|
935
941
|
corner_reserves=corner_reserves,
|
|
@@ -996,6 +1002,9 @@ class PlacementContext:
|
|
|
996
1002
|
# so mypy does not reject the delegating calls below.
|
|
997
1003
|
registry: Any = None # the drawing's AnnotationRegistry: build-issue sink + names
|
|
998
1004
|
coverage: Any = None # the drawing's CoverageState
|
|
1005
|
+
# The drawing's render list (#817): :meth:`place` appends here, so a render pass places an
|
|
1006
|
+
# annotation through the ctx seam (``ctx.place(...)``) instead of reaching into the drawing.
|
|
1007
|
+
items: Any = None
|
|
999
1008
|
# The ensured PartModel (ADR 0008 IR) the run's passes read, threaded off the drawing so
|
|
1000
1009
|
# they no longer reach into ``dwg._part_model`` (#639). Both entry paths set it from the
|
|
1001
1010
|
# PUBLIC ``dwg.model()`` after the model is ensured/attached.
|
|
@@ -1007,6 +1016,19 @@ class PlacementContext:
|
|
|
1007
1016
|
# per-ctx (per-run) index is correct (mirrors the old ``Drawing._hole_feature_index``).
|
|
1008
1017
|
_hole_feature_index: Any = field(default=None, repr=False)
|
|
1009
1018
|
|
|
1019
|
+
def place(self, obj, name=None, view=None, feature=None):
|
|
1020
|
+
"""Place an annotation onto the drawing through this context (#817) — the render passes'
|
|
1021
|
+
door to the placement primitive, so a pass never reaches into the ``Drawing`` (ADR 0005
|
|
1022
|
+
§2). Registers *obj* under *name* (owning *view* + source *feature*) and appends it to the
|
|
1023
|
+
render list. Returns *obj*."""
|
|
1024
|
+
if self.items is None:
|
|
1025
|
+
raise ValueError(
|
|
1026
|
+
"PlacementContext.place() needs the drawing's render list — construct the context "
|
|
1027
|
+
"with items=dwg.items (the orchestrator and Drawing verbs already do; a unit test "
|
|
1028
|
+
"calling a render helper must pass it too)."
|
|
1029
|
+
)
|
|
1030
|
+
return place_annotation(self.registry, self.items, obj, name, view, feature)
|
|
1031
|
+
|
|
1010
1032
|
def feature_of_hole_at(self, location):
|
|
1011
1033
|
"""The IR hole/pattern feature whose member sits at model-space *location*, or ``None``
|
|
1012
1034
|
(#408/#639). Attributes a balloon (which carries a recognition hole, not the IR feature)
|
|
@@ -1107,6 +1129,7 @@ def place_strip_candidates(
|
|
|
1107
1129
|
cands,
|
|
1108
1130
|
tier,
|
|
1109
1131
|
*,
|
|
1132
|
+
ctx,
|
|
1110
1133
|
force=False,
|
|
1111
1134
|
features=None,
|
|
1112
1135
|
sizes=None,
|
|
@@ -1416,7 +1439,7 @@ def place_strip_candidates(
|
|
|
1416
1439
|
for name, dim in placed:
|
|
1417
1440
|
# Record feature provenance (ADR 0010): the drain-time seam for corridor-placed
|
|
1418
1441
|
# dims — `features` maps this batch's names to their source IR feature.
|
|
1419
|
-
|
|
1442
|
+
ctx.place(dim, name, view=view, feature=(features or {}).get(name))
|
|
1420
1443
|
if tp is not None:
|
|
1421
1444
|
tp["unplaced"] = [n for n, _ in todo]
|
|
1422
1445
|
trace.end_pass(tp) # folds a standalone pass's items; no-op when corridor-nested
|
|
@@ -234,7 +234,7 @@ def _render_balloon(dwg, view, tag, j, hole, cx, cy, bx, by, fs, r, ctx):
|
|
|
234
234
|
# Furniture that legitimately sits on the view geometry — exempt from the
|
|
235
235
|
# annotation-overlap / centreline lint, as the section arrows do.
|
|
236
236
|
balloon.is_centerline = True
|
|
237
|
-
|
|
237
|
+
ctx.place(
|
|
238
238
|
balloon,
|
|
239
239
|
f"balloon_{view}_{tag}_{j}",
|
|
240
240
|
view=view,
|