draftwright 0.2.13__tar.gz → 0.3.1__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.2.13 → draftwright-0.3.1}/CHANGELOG.md +110 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/PKG-INFO +4 -2
- {draftwright-0.2.13 → draftwright-0.3.1}/pyproject.toml +18 -2
- {draftwright-0.2.13 → draftwright-0.3.1}/src/draftwright/__init__.py +8 -7
- {draftwright-0.2.13 → draftwright-0.3.1}/src/draftwright/_core.py +45 -45
- draftwright-0.3.1/src/draftwright/_geometry.py +50 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/src/draftwright/analysis.py +226 -153
- {draftwright-0.2.13 → draftwright-0.3.1}/src/draftwright/annotations/_common.py +159 -7
- {draftwright-0.2.13 → draftwright-0.3.1}/src/draftwright/annotations/from_model.py +1074 -469
- {draftwright-0.2.13 → draftwright-0.3.1}/src/draftwright/annotations/holes.py +1033 -742
- {draftwright-0.2.13 → draftwright-0.3.1}/src/draftwright/annotations/orchestrator.py +112 -81
- {draftwright-0.2.13 → draftwright-0.3.1}/src/draftwright/annotations/sections.py +38 -37
- {draftwright-0.2.13 → draftwright-0.3.1}/src/draftwright/builder.py +15 -8
- {draftwright-0.2.13 → draftwright-0.3.1}/src/draftwright/cli.py +7 -13
- draftwright-0.2.13/src/draftwright/sheet.py → draftwright-0.3.1/src/draftwright/compose.py +84 -180
- {draftwright-0.2.13 → draftwright-0.3.1}/src/draftwright/drawing.py +603 -323
- {draftwright-0.2.13 → draftwright-0.3.1}/src/draftwright/export.py +21 -0
- draftwright-0.3.1/src/draftwright/layout.py +404 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/src/draftwright/linting/coverage.py +47 -14
- {draftwright-0.2.13 → draftwright-0.3.1}/src/draftwright/linting/suggest.py +26 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/src/draftwright/model/__init__.py +26 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/src/draftwright/model/declare.py +532 -2
- {draftwright-0.2.13 → draftwright-0.3.1}/src/draftwright/model/detect.py +216 -24
- {draftwright-0.2.13 → draftwright-0.3.1}/src/draftwright/model/ir.py +182 -2
- {draftwright-0.2.13 → draftwright-0.3.1}/src/draftwright/model/planner.py +1 -1
- {draftwright-0.2.13 → draftwright-0.3.1}/src/draftwright/projection.py +4 -1
- draftwright-0.3.1/src/draftwright/recognition/__init__.py +125 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/src/draftwright/recognition/_features.py +87 -32
- draftwright-0.3.1/src/draftwright/recognition/_record.py +28 -0
- draftwright-0.3.1/src/draftwright/recognition/chamfers.py +170 -0
- draftwright-0.3.1/src/draftwright/recognition/countersinks.py +145 -0
- draftwright-0.3.1/src/draftwright/recognition/fillets.py +161 -0
- draftwright-0.3.1/src/draftwright/recognition/flats.py +172 -0
- draftwright-0.3.1/src/draftwright/recognition/grooves.py +132 -0
- draftwright-0.3.1/src/draftwright/recognition/levels.py +175 -0
- draftwright-0.3.1/src/draftwright/recognition/plates.py +149 -0
- draftwright-0.3.1/src/draftwright/recognition/slots.py +675 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/src/draftwright/recognition/turned.py +46 -18
- {draftwright-0.2.13 → draftwright-0.3.1}/src/draftwright/registry.py +5 -0
- draftwright-0.3.1/src/draftwright/score.py +70 -0
- draftwright-0.2.13/src/draftwright/sheet_dsl.py → draftwright-0.3.1/src/draftwright/sheet.py +94 -2
- draftwright-0.3.1/src/draftwright/sheet_dsl.py +35 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/src/draftwright/sheet_emit.py +42 -8
- draftwright-0.3.1/tests/_kernel.py +32 -0
- draftwright-0.2.13/tests/test_layout_snapshot.py → draftwright-0.3.1/tests/_layout_sig.py +22 -72
- draftwright-0.2.13/tests/layout_snapshots/bracket.json → draftwright-0.3.1/tests/refactor_golden/bracket_section.json +1 -0
- draftwright-0.2.13/tests/layout_snapshots/slotted.json → draftwright-0.3.1/tests/refactor_golden/centered_rebate.json +86 -85
- draftwright-0.3.1/tests/refactor_golden/chamfered.json +135 -0
- draftwright-0.3.1/tests/refactor_golden/filleted.json +135 -0
- draftwright-0.2.13/tests/layout_snapshots/flange.json → draftwright-0.3.1/tests/refactor_golden/flange_dense.json +1 -0
- draftwright-0.3.1/tests/refactor_golden/grooved_shaft.json +149 -0
- draftwright-0.2.13/tests/layout_snapshots/box.json → draftwright-0.3.1/tests/refactor_golden/hex_bar.json +52 -51
- {draftwright-0.2.13/tests/layout_snapshots → draftwright-0.3.1/tests/refactor_golden}/holed_slot.json +1 -0
- draftwright-0.2.13/tests/layout_snapshots/dshape.json → draftwright-0.3.1/tests/refactor_golden/pocketed.json +113 -112
- draftwright-0.3.1/tests/refactor_golden/prismatic_ladder.json +153 -0
- {draftwright-0.2.13/tests/layout_snapshots → draftwright-0.3.1/tests/refactor_golden}/side_drilled.json +1 -0
- draftwright-0.2.13/tests/layout_snapshots/turned_shaft.json → draftwright-0.3.1/tests/refactor_golden/turned_stepped.json +1 -0
- draftwright-0.3.1/tests/test_carve_free_position_callers.py +161 -0
- draftwright-0.3.1/tests/test_declare.py +1414 -0
- draftwright-0.3.1/tests/test_drawing_encapsulation.py +193 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/tests/test_e2e_standards.py +5 -5
- {draftwright-0.2.13 → draftwright-0.3.1}/tests/test_gdt_placement.py +1 -1
- draftwright-0.3.1/tests/test_import_boundaries.py +462 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/tests/test_layout.py +3 -202
- {draftwright-0.2.13 → draftwright-0.3.1}/tests/test_layout_cleanliness.py +4 -3
- draftwright-0.3.1/tests/test_layout_hypothesis.py +205 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/tests/test_layout_property.py +1 -1
- {draftwright-0.2.13 → draftwright-0.3.1}/tests/test_make_drawing.py +1585 -214
- {draftwright-0.2.13 → draftwright-0.3.1}/tests/test_part_model.py +15 -10
- {draftwright-0.2.13 → draftwright-0.3.1}/tests/test_pmi.py +33 -0
- draftwright-0.3.1/tests/test_private_test_imports.py +138 -0
- draftwright-0.3.1/tests/test_recogniser_contract.py +242 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/tests/test_recognition.py +89 -89
- draftwright-0.3.1/tests/test_refactor_golden.py +219 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/tests/test_render_seam.py +7 -2
- draftwright-0.3.1/tests/test_score.py +38 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/tests/test_sheet_emit.py +235 -7
- {draftwright-0.2.13 → draftwright-0.3.1}/tests/test_sheet_gdt.py +1 -1
- {draftwright-0.2.13 → draftwright-0.3.1}/tests/test_sheet_notes.py +1 -1
- {draftwright-0.2.13 → draftwright-0.3.1}/tests/test_strip_layout.py +381 -85
- draftwright-0.3.1/tests/test_turned_steps.py +118 -0
- draftwright-0.2.13/src/draftwright/features.py +0 -23
- draftwright-0.2.13/src/draftwright/layout.py +0 -640
- draftwright-0.2.13/src/draftwright/recognition/__init__.py +0 -54
- draftwright-0.2.13/src/draftwright/recognition/levels.py +0 -51
- draftwright-0.2.13/src/draftwright/recognition/slots.py +0 -317
- draftwright-0.2.13/tests/_kernel.py +0 -19
- draftwright-0.2.13/tests/layout_snapshots/drive_screw_x.json +0 -155
- draftwright-0.2.13/tests/layout_snapshots/plate_holes.json +0 -258
- draftwright-0.2.13/tests/test_declare.py +0 -647
- draftwright-0.2.13/tests/test_turned_steps.py +0 -87
- {draftwright-0.2.13 → draftwright-0.3.1}/.gitignore +0 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/LICENSE +0 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/README.md +0 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/skills/SKILL.md +0 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/src/draftwright/annotate.py +0 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/src/draftwright/annotations/__init__.py +0 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/src/draftwright/fits.py +0 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/src/draftwright/fonts/IBMPlexMono-Regular.ttf +0 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/src/draftwright/fonts/IBMPlexSansCondensed-Regular.ttf +0 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/src/draftwright/fonts/LICENSE-IBMPlexMono-OFL.txt +0 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/src/draftwright/fonts/LICENSE-IBMPlexSansCondensed-OFL.txt +0 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/src/draftwright/fonts/__init__.py +0 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/src/draftwright/intents.py +0 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/src/draftwright/linting/__init__.py +0 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/src/draftwright/linting/issues.py +0 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/src/draftwright/linting/structural.py +0 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/src/draftwright/make_drawing.py +0 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/src/draftwright/pmi.py +0 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/src/draftwright/repair.py +0 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/tests/fixtures/nist_ctc_01_asme1_ap203.stp +0 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/tests/fixtures/nist_ctc_01_asme1_ap242.stp +0 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/tests/fixtures/nist_ctc_02_asme1_ap203.stp +0 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/tests/fixtures/nist_ctc_02_asme1_ap242.stp +0 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/tests/fixtures/nist_ctc_03_asme1_ap203.stp +0 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/tests/fixtures/nist_ctc_03_asme1_ap242.stp +0 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/tests/fixtures/nist_ctc_04_asme1_ap203.stp +0 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/tests/fixtures/nist_ctc_04_asme1_ap242.stp +0 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/tests/fixtures/nist_ctc_05_asme1_ap203.stp +0 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/tests/fixtures/nist_ctc_05_asme1_ap242.stp +0 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/tests/test_e2e_slice.py +0 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/tests/test_fits.py +0 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/tests/test_lint_reconciliation.py +0 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/tests/test_lint_structural.py +0 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/tests/test_linting.py +0 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/tests/test_object_aspects.py +0 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/tests/test_registry.py +0 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/tests/test_sheet_of.py +0 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/tests/test_sheet_tables.py +0 -0
- {draftwright-0.2.13 → draftwright-0.3.1}/tests/test_tolerances.py +0 -0
|
@@ -1,5 +1,115 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v0.3.1 — 2026-07-17
|
|
4
|
+
|
|
5
|
+
Also lands the **#635 consolidation epic** (P1–P6, all six children closed): the render
|
|
6
|
+
layer's state bus is retired (`annotations/` reads zero `Drawing` privates,
|
|
7
|
+
machine-enforced), the three complexity hotspots are split (nesting ≤2), the whole-package
|
|
8
|
+
import DAG + a test-import ratchet are enforced, and the test suite gains a Hypothesis fuzz
|
|
9
|
+
tier + relational cross-kernel placement gates. All internal / behaviour-preserving
|
|
10
|
+
(byte-identical golden) — no output change.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **PNG raster export** — `--format png` on the CLI, or `Drawing.export(formats=("png",))`.
|
|
15
|
+
Rendered SVG → PDF → PNG via **pypdfium2** (Apache-2.0, Google PDFium BSD-3) + **Pillow**
|
|
16
|
+
(HPND) — both permissively licensed and pure-wheel, so PNG works cross-platform with **no
|
|
17
|
+
native cairo** (ADR 0006) and keeps the render path dual-license-friendly. `dpi=` sets the
|
|
18
|
+
raster resolution.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- **Unified export API** — `Drawing.export(out, *, formats=("pdf",)) → {format: path}` is now
|
|
23
|
+
the single entry point over `svg`/`dxf`/`pdf`/`png`; it returns a dict and internally handles
|
|
24
|
+
the SVG→PDF→PNG intermediate chain (writing/cleaning up intermediates it wasn't asked to keep).
|
|
25
|
+
The CLI `--format` gains `png`.
|
|
26
|
+
|
|
27
|
+
### Deprecated
|
|
28
|
+
|
|
29
|
+
- `Drawing.export_pdf()` — use `export(formats=("pdf",))["pdf"]`; the method now warns.
|
|
30
|
+
- The legacy `export(svg=, dxf=)` boolean keywords and their `(svg_path, dxf_path)` tuple return
|
|
31
|
+
— kept for back-compat; prefer `formats=[...]` (the dict API).
|
|
32
|
+
|
|
33
|
+
## v0.3.0 — 2026-07-14
|
|
34
|
+
|
|
35
|
+
**Breaking — feature-recogniser API renamed** (ADR 0013 / #568): *deliberate* breaks with
|
|
36
|
+
**no compatibility aliases**, hence the minor bump. Only import paths and callable
|
|
37
|
+
signatures change — but note this release also **changes drawing output**: new machined-
|
|
38
|
+
feature callouts, corrected leader anchors, and a reoriented isometric (see Added / Fixed).
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- **Machined-feature recognition — the #148 epic.** #135 deliberately handled only enclosed
|
|
43
|
+
rectangular through-slots; this broadens recognition, and each feature is recognised **and**
|
|
44
|
+
dimensioned **and** authorable (recognise + emit + declare, ADR 0011):
|
|
45
|
+
- **Blind slots + pockets** — floored recesses, `W × L × D DEEP` (#603).
|
|
46
|
+
- **Cross / intersecting slots** collapse into single continuous channels (#604).
|
|
47
|
+
- **Machined flats** on round stock — `{across} A/F` (#605).
|
|
48
|
+
- **Turned / circlip grooves** — `{width} WIDE × ø{floor}` (#606).
|
|
49
|
+
- **Slots / pockets in curved stock** — arc-bounded walls (#607).
|
|
50
|
+
- **Radiused-end (obround) slots** now report their **overall** length (#613).
|
|
51
|
+
- **Chamfer callouts** — `C{leg}` / `{leg}×{angle}°` off each bevel (#560).
|
|
52
|
+
- **Fillet radius callouts** — `R{radius}` off each rounded edge (#561).
|
|
53
|
+
- **Plate / wall thickness** dimensions on multi-plate prismatics (#559).
|
|
54
|
+
- **Prismatic step-position** dimensions locating each shoulder (#555).
|
|
55
|
+
- **Countersink callouts** — `ø6 THRU ⌵ ø14 × 90°` (major-ø + included angle), like the
|
|
56
|
+
counterbore `⊔ ø.. ↧..` (#558). The countersink rides on `HoleRecord`/`HoleFeature`, so
|
|
57
|
+
grouping and the callout-width layout estimate account for it.
|
|
58
|
+
- **Every recognised feature round-trips all three ADR-0011 surfaces (#574).** Beyond
|
|
59
|
+
detection, each feature now **emits** into the generated `--script` Sheet and is
|
|
60
|
+
**declarable** on the fluent `Sheet` surface — authored from scratch or read off a
|
|
61
|
+
build123d object: chamfer (#576), countersink (#575), plate (#577), step-position (#585),
|
|
62
|
+
fillet (#561), plus all the #148 features above.
|
|
63
|
+
- **`draftwright.score.feature_census(part)`** — a per-kind recognised-feature census, a
|
|
64
|
+
measurement tool for tracking recognition coverage (#608).
|
|
65
|
+
|
|
66
|
+
### Changed
|
|
67
|
+
|
|
68
|
+
- **Feature recognisers renamed `find_`/`analyse_` → `recognise_`** and their
|
|
69
|
+
tuning/dependencies made keyword-only: `find_holes`→`recognise_holes`,
|
|
70
|
+
`find_bosses`→`recognise_bosses`, `find_hole_patterns`→`recognise_hole_patterns`,
|
|
71
|
+
`find_slots`→`recognise_slots`, `find_plates`→`recognise_plates`,
|
|
72
|
+
`find_chamfers`→`recognise_chamfers`, `find_turned_steps`→`recognise_turned_steps`,
|
|
73
|
+
`find_step_shoulders`→`recognise_step_shoulders`, `analyse_face_levels`→
|
|
74
|
+
`recognise_face_levels` (the last was also re-exported at top level as
|
|
75
|
+
`draftwright.analyse_face_levels` → now `draftwright.recognise_face_levels`).
|
|
76
|
+
Cylinder-analysis substrate (`analyse_cylinders`, `full_cylinders`,
|
|
77
|
+
`feature_diameters`) keeps its names.
|
|
78
|
+
- **`recognise_step_shoulders` now returns `list[StepShoulder]`** (a frozen dataclass)
|
|
79
|
+
instead of raw `(axis, position)` tuples; `levels` is keyword-only.
|
|
80
|
+
- **`recognise_turned_steps` now returns `list[TurnedStep]`** (empty for a non-turned
|
|
81
|
+
part) instead of `TurnedProfile | None`. `TurnedStep` gained an `axis` field (each step
|
|
82
|
+
is now a self-contained record); `TurnedProfile` remains only as a pipeline aggregate
|
|
83
|
+
(`TurnedProfile.from_steps(steps)`), no longer a recogniser return.
|
|
84
|
+
- **`recognise_face_levels` now returns `list[FaceLevel]`** (a frozen `FaceLevel(z)`
|
|
85
|
+
record) instead of `list[float]`.
|
|
86
|
+
- **Recognition record classes renamed to avoid clashing with the IR `Feature` types:**
|
|
87
|
+
`draftwright.recognition.HoleFeature` → `HoleRecord`, `BossFeature` → `BossRecord`
|
|
88
|
+
(the IR `draftwright.model.ir.HoleFeature`/`BossFeature` are unchanged).
|
|
89
|
+
- **Page/scale sizing now reads the IR, not recognition records (ADR 0008 / #588).** The
|
|
90
|
+
part model is built up front and drives sheet sizing, so detected and declared parts share
|
|
91
|
+
one path and no recogniser record reaches the sheet estimators. Byte-identical to the old
|
|
92
|
+
estimators except a hole pattern that shares a machining spec with loose holes now sizes
|
|
93
|
+
separately (no phantom merged `N×`), which can shift a tightly-packed such part's layout.
|
|
94
|
+
|
|
95
|
+
### Fixed
|
|
96
|
+
|
|
97
|
+
- **Isometric view orientation (#620).** The iso camera viewed the *rear* (+Y) side against a
|
|
98
|
+
front-view orthographic set, mirroring asymmetric features; it now views from the same
|
|
99
|
+
front (−Y) / plan (+Z) / right (+X) combination.
|
|
100
|
+
- **Chamfer leaders (#621)** anchor on the bevel-face centroid (the diagonal middle), not the
|
|
101
|
+
supporting plane's parametric origin (which projected to a corner/endpoint).
|
|
102
|
+
- **Fillet leaders (#622)** anchor on the radius surface, not the face bounding-box centre
|
|
103
|
+
(which sat off the round, near the virtual sharp corner).
|
|
104
|
+
- **Authored `ref_pts`-only dimensions** now render (with Z-strip sizing) (#562).
|
|
105
|
+
- **Pitch-dim labels** are cleared off centrelines / bolt circles at creation time, not via
|
|
106
|
+
the repair loop (#129).
|
|
107
|
+
|
|
108
|
+
### Removed
|
|
109
|
+
|
|
110
|
+
- **`draftwright.features` compatibility module** — deleted. Import `recognise_slots`/
|
|
111
|
+
`Slot` from `draftwright.recognition` instead.
|
|
112
|
+
|
|
3
113
|
## v0.2.13 — 2026-07-09
|
|
4
114
|
|
|
5
115
|
**Compose-then-pack is now the layout authority.** This patch finishes the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: draftwright
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
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
|
|
@@ -682,10 +682,12 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
682
682
|
Classifier: Programming Language :: Python :: 3.14
|
|
683
683
|
Classifier: Topic :: Scientific/Engineering
|
|
684
684
|
Requires-Python: <3.15,>=3.10
|
|
685
|
-
Requires-Dist: build123d-drafting-helpers>=0.13.
|
|
685
|
+
Requires-Dist: build123d-drafting-helpers>=0.13.1
|
|
686
686
|
Requires-Dist: build123d<0.11,>=0.9.0; python_full_version < '3.13'
|
|
687
687
|
Requires-Dist: build123d<0.12,>=0.11; python_full_version >= '3.13'
|
|
688
688
|
Requires-Dist: numpy>=1.24
|
|
689
|
+
Requires-Dist: pillow>=10
|
|
690
|
+
Requires-Dist: pypdfium2>=4
|
|
689
691
|
Requires-Dist: reportlab>=4.0
|
|
690
692
|
Requires-Dist: svglib>=1.5
|
|
691
693
|
Requires-Dist: typer>=0.12
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "draftwright"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.1"
|
|
8
8
|
description = "Automated technical-drawing generation for build123d"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { file = "LICENSE" }
|
|
@@ -17,8 +17,13 @@ dependencies = [
|
|
|
17
17
|
# 3.13+. Mirrors pzfreo/build123d-mcp's dependency handling.
|
|
18
18
|
"build123d>=0.9.0,<0.11 ; python_full_version < '3.13'",
|
|
19
19
|
"build123d>=0.11,<0.12 ; python_full_version >= '3.13'",
|
|
20
|
-
"build123d-drafting-helpers>=0.13.
|
|
20
|
+
"build123d-drafting-helpers>=0.13.1",
|
|
21
21
|
"numpy>=1.24",
|
|
22
|
+
# PNG raster export (SVG→PDF→PNG). Both permissively licensed (Pillow HPND; pypdfium2
|
|
23
|
+
# Apache-2.0 wrapping Google PDFium BSD-3) and ship pre-built wheels with NO native system
|
|
24
|
+
# deps — so PNG works cross-platform without cairo (ADR 0006) and stays dual-license-clean.
|
|
25
|
+
"pillow>=10",
|
|
26
|
+
"pypdfium2>=4",
|
|
22
27
|
"reportlab>=4.0",
|
|
23
28
|
"svglib>=1.5",
|
|
24
29
|
"typer>=0.12",
|
|
@@ -111,6 +116,8 @@ ignore = ["E501"]
|
|
|
111
116
|
|
|
112
117
|
[dependency-groups]
|
|
113
118
|
dev = [
|
|
119
|
+
"codespell>=2.3.0",
|
|
120
|
+
"hypothesis>=6",
|
|
114
121
|
"mypy>=2.1.0",
|
|
115
122
|
"pytest>=8",
|
|
116
123
|
"pytest-cov>=5",
|
|
@@ -119,6 +126,15 @@ dev = [
|
|
|
119
126
|
"ruff>=0.4",
|
|
120
127
|
]
|
|
121
128
|
|
|
129
|
+
[tool.codespell]
|
|
130
|
+
# Spelling enforcement for the ADR 0013 contract (epic #584 WP3). Skips binary /
|
|
131
|
+
# generated / vendored assets: STEP fixtures, the lockfile, fonts, git internals.
|
|
132
|
+
skip = "*.stp,*.step,*.svg,*.dxf,*.pdf,uv.lock,.git,*.ttf,*.otf,src/draftwright/fonts/*"
|
|
133
|
+
# Valid words/identifiers codespell's dictionary misflags (calibrated from a clean run):
|
|
134
|
+
# `nd` a Dijkstra distance var, `tread` a real word (stair-tread faces), `accreting`
|
|
135
|
+
# a real word, `testof` the `TestOf` class name, and the valid hyphenated re-* forms.
|
|
136
|
+
ignore-words-list = "nd,tread,accreting,testof,re-using,re-declare,re-declared,re-declaring"
|
|
137
|
+
|
|
122
138
|
[tool.uv]
|
|
123
139
|
# On Python 3.13/3.14, build123d resolves to 0.11, whose OCP kernel is
|
|
124
140
|
# cadquery-ocp-novtk (VTK dropped from the transitive tree — fine here, draftwright
|
|
@@ -23,19 +23,19 @@ from typing import TYPE_CHECKING
|
|
|
23
23
|
# none of these names, so they stay sub-second instead of paying for the kernel
|
|
24
24
|
# on every TAB press (#313). Each name maps to the submodule that provides it.
|
|
25
25
|
_LAZY = {
|
|
26
|
-
"
|
|
26
|
+
"recognise_face_levels": "draftwright.recognition",
|
|
27
27
|
"dedup_diams": "draftwright.analysis",
|
|
28
28
|
"build_drawing": "draftwright.builder",
|
|
29
29
|
"generate_script": "draftwright.builder",
|
|
30
30
|
"make_drawing": "draftwright.builder",
|
|
31
31
|
"Drawing": "draftwright.drawing",
|
|
32
32
|
"FeatureInfo": "draftwright.drawing",
|
|
33
|
-
"Sheet": "draftwright.
|
|
33
|
+
"Sheet": "draftwright.sheet",
|
|
34
34
|
"fix_svg_page_size": "draftwright.export",
|
|
35
35
|
"lint_feature_coverage": "draftwright.linting",
|
|
36
36
|
"PmiRecord": "draftwright.pmi",
|
|
37
37
|
"extract_pmi": "draftwright.pmi",
|
|
38
|
-
"choose_scale": "draftwright.
|
|
38
|
+
"choose_scale": "draftwright.compose",
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
|
|
@@ -72,14 +72,15 @@ _sys.modules[__name__].__class__ = _DraftwrightModule
|
|
|
72
72
|
|
|
73
73
|
|
|
74
74
|
if TYPE_CHECKING: # static analysers / IDEs — no runtime import, no kernel cost
|
|
75
|
-
from draftwright.analysis import
|
|
75
|
+
from draftwright.analysis import dedup_diams
|
|
76
76
|
from draftwright.builder import build_drawing, generate_script, make_drawing
|
|
77
|
+
from draftwright.compose import choose_scale
|
|
77
78
|
from draftwright.drawing import Drawing, FeatureInfo
|
|
78
79
|
from draftwright.export import fix_svg_page_size
|
|
79
80
|
from draftwright.linting import lint_feature_coverage
|
|
80
81
|
from draftwright.pmi import PmiRecord, extract_pmi
|
|
81
|
-
from draftwright.
|
|
82
|
-
from draftwright.
|
|
82
|
+
from draftwright.recognition import recognise_face_levels
|
|
83
|
+
from draftwright.sheet import Sheet
|
|
83
84
|
|
|
84
85
|
|
|
85
86
|
def __dir__():
|
|
@@ -94,7 +95,7 @@ __all__ = [
|
|
|
94
95
|
"FeatureInfo",
|
|
95
96
|
"PmiRecord",
|
|
96
97
|
"Sheet",
|
|
97
|
-
"
|
|
98
|
+
"recognise_face_levels",
|
|
98
99
|
"build_drawing",
|
|
99
100
|
"choose_scale",
|
|
100
101
|
"dedup_diams",
|
|
@@ -12,6 +12,7 @@ from __future__ import annotations
|
|
|
12
12
|
|
|
13
13
|
import functools
|
|
14
14
|
import logging
|
|
15
|
+
import math
|
|
15
16
|
import re
|
|
16
17
|
from collections.abc import Callable
|
|
17
18
|
from dataclasses import dataclass
|
|
@@ -20,8 +21,8 @@ from types import SimpleNamespace
|
|
|
20
21
|
from typing import TYPE_CHECKING
|
|
21
22
|
|
|
22
23
|
if TYPE_CHECKING:
|
|
24
|
+
from draftwright.compose import StripDepths
|
|
23
25
|
from draftwright.recognition import TurnedProfile
|
|
24
|
-
from draftwright.sheet import StripDepths
|
|
25
26
|
|
|
26
27
|
from build123d import Align, BoundBox, Location, Mode, Shape, Text
|
|
27
28
|
from build123d_drafting.helpers import (
|
|
@@ -31,6 +32,11 @@ from build123d_drafting.helpers import (
|
|
|
31
32
|
format_drawing_scale,
|
|
32
33
|
)
|
|
33
34
|
|
|
35
|
+
# The model-neutral geometry primitives (`_END_ON`, `_xyz`, `HoleRef`, `_axis_letter`)
|
|
36
|
+
# now live in the leaf `draftwright._geometry` so the IR waist (`model/`) can use them
|
|
37
|
+
# without importing this stage-level grab-bag (ADR 0008; #584 WP2). Re-exported here for
|
|
38
|
+
# the above-`_core` consumers (annotations/sheet/drawing/linting) that already import them.
|
|
39
|
+
from draftwright._geometry import _END_ON, HoleRef, _axis_letter, _xyz # noqa: F401
|
|
34
40
|
from draftwright.fits import FitClass
|
|
35
41
|
from draftwright.fonts import PLEX_MONO, PLEX_SANS_CONDENSED
|
|
36
42
|
from draftwright.layout import _greedy_strip_1d, _solve_strip_1d
|
|
@@ -43,39 +49,6 @@ _MARGIN = 10.0
|
|
|
43
49
|
|
|
44
50
|
_TB_CLEAR = _MARGIN + 1.0 # title-block inset: one extra mm over _MARGIN for clearance
|
|
45
51
|
|
|
46
|
-
# The orthographic view a feature is dimensioned end-on in — the view normal to its
|
|
47
|
-
# axis. Single source of truth shared by the planner (view choice) and the lint
|
|
48
|
-
# coverage checks (where to look for a feature's dims). Orientation is data.
|
|
49
|
-
_END_ON = {"x": "side", "y": "front", "z": "plan"}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def _xyz(loc) -> tuple[float, float, float]:
|
|
53
|
-
"""A build123d ``Vector`` (has ``.X/.Y/.Z``) or an ``(x, y, z)`` sequence → an
|
|
54
|
-
``(x, y, z)`` float tuple. Shared by the detectors and the lint coverage checks
|
|
55
|
-
so the Vector-unpacking idiom lives in one place."""
|
|
56
|
-
if hasattr(loc, "X"):
|
|
57
|
-
return (loc.X, loc.Y, loc.Z)
|
|
58
|
-
x, y, z = loc
|
|
59
|
-
return (float(x), float(y), float(z))
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
@dataclass(frozen=True)
|
|
63
|
-
class HoleRef:
|
|
64
|
-
"""A position-keyed reference to a hole — the IR-typed value the cover / hole-table
|
|
65
|
-
bookkeeping matches on, so the shared escalation never needs a recogniser ``Hole``
|
|
66
|
-
object (ADR 0008 Amendment 6). Built from any location via :meth:`of` (rounded, so
|
|
67
|
-
two references at the same position compare equal)."""
|
|
68
|
-
|
|
69
|
-
x: float
|
|
70
|
-
y: float
|
|
71
|
-
z: float
|
|
72
|
-
|
|
73
|
-
@classmethod
|
|
74
|
-
def of(cls, loc) -> HoleRef:
|
|
75
|
-
x, y, z = _xyz(loc)
|
|
76
|
-
return cls(round(x, 3), round(y, 3), round(z, 3))
|
|
77
|
-
|
|
78
|
-
|
|
79
52
|
_FONT_SIZE = 3.0 # annotation text height (page-mm); the draft preset is built with this
|
|
80
53
|
|
|
81
54
|
|
|
@@ -161,17 +134,40 @@ def _text_width(text: str, font_size: float, font_path: str = PLEX_MONO) -> floa
|
|
|
161
134
|
)
|
|
162
135
|
|
|
163
136
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
137
|
+
# Cheap mean glyph advance as a fraction of the em (font size), for label-width ESTIMATES in
|
|
138
|
+
# the diameter row/column capacity checks. Plex Mono is monospaced at ~0.6 em, padded a touch.
|
|
139
|
+
# The exact metric is `_text_width()` above (measured per string), which the corridor/label
|
|
140
|
+
# placers use; these capacity gates only need a close bound, not the metric, so they trade
|
|
141
|
+
# exactness for not rasterising a text layout per candidate. The two may diverge for unusually
|
|
142
|
+
# wide/narrow glyph runs.
|
|
143
|
+
_EST_CHAR_WIDTH_EM = 0.62
|
|
170
144
|
|
|
171
145
|
|
|
172
146
|
_CONCENTRIC_TOL_MM = 0.5
|
|
173
147
|
|
|
174
148
|
|
|
149
|
+
def _first_free_index(prefix: str, taken) -> int:
|
|
150
|
+
"""The lowest ``j`` for which ``f"{prefix}{j}"`` is not in *taken* (a set or any container
|
|
151
|
+
supporting ``in``). The shared kernel of the first-free annotation-name allocators
|
|
152
|
+
(``_loc_name``/``_uniq``/``_hc_name``) — used where reusing a freed gap is fine. NOTE: the
|
|
153
|
+
step-length / diameter runs deliberately use ``max+1`` instead (``_next_start`` /
|
|
154
|
+
``_next_steplen_start``), because a contiguous run started at a gap below an occupied index
|
|
155
|
+
would wrap onto it (#432); don't fold those onto this."""
|
|
156
|
+
j = 0
|
|
157
|
+
while f"{prefix}{j}" in taken:
|
|
158
|
+
j += 1
|
|
159
|
+
return j
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _concentric_with_axis(a, x: float, y: float) -> bool:
|
|
163
|
+
"""True when the page/world point ``(x, y)`` lies on the rotational part's turned axis
|
|
164
|
+
(within :data:`_CONCENTRIC_TOL_MM`). A bore/pattern centred on the axis needs no location
|
|
165
|
+
dim — its position is the axis — so several passes filter such refs; this is the single
|
|
166
|
+
radial test they share (the perpendicular-plane distance to the axis centre ``(a.cx,
|
|
167
|
+
a.cy)``). Callers still gate on ``a.is_rotational`` / role / axis as their context needs."""
|
|
168
|
+
return math.hypot(x - a.cx, y - a.cy) <= _CONCENTRIC_TOL_MM
|
|
169
|
+
|
|
170
|
+
|
|
175
171
|
def _dim(p1, p2, side, distance, draft, **kwargs):
|
|
176
172
|
"""Build a :class:`Dimension`, tagged with its placement spec.
|
|
177
173
|
|
|
@@ -190,11 +186,14 @@ def _dim(p1, p2, side, distance, draft, **kwargs):
|
|
|
190
186
|
|
|
191
187
|
# Dimension-line spacing (page-mm, scale-independent), the single source of truth for
|
|
192
188
|
# BOTH the ADR 0009 strip carve (via the `Strip` dataclass defaults below) and the
|
|
193
|
-
#
|
|
189
|
+
# compose.py halo/depth estimates that must reserve the same space. Per ISO 129-1 / ASME
|
|
194
190
|
# Y14.5, the FIRST dimension line sits furthest from the outline (clears the outline +
|
|
195
191
|
# extension-line origins) and subsequent parallel lines stack tighter and uniform (#347).
|
|
196
192
|
_STRIP_GAP = 10.0 # clearance between the view outline and the first dimension line
|
|
197
193
|
_STRIP_SPACING = 2.5 # clear gap between successive parallel dimension lines (beyond the label)
|
|
194
|
+
# Small lift (page-mm) of an overall/envelope witness line off the projected silhouette edge,
|
|
195
|
+
# so its extension line reads as distinct from the outline rather than sitting on top of it.
|
|
196
|
+
_WITNESS_LIFT_MM = 2.0
|
|
198
197
|
|
|
199
198
|
|
|
200
199
|
@dataclass
|
|
@@ -389,7 +388,8 @@ def _largest_empty_rect(drawable, obstacles, *, warn: bool = True):
|
|
|
389
388
|
@dataclass
|
|
390
389
|
class DetailRequest:
|
|
391
390
|
"""A renderer's request for an enlarged detail of a region it could not draw
|
|
392
|
-
legibly at sheet scale (#307). Renderers append these to
|
|
391
|
+
legibly at sheet scale (#307). Renderers append these to the run's
|
|
392
|
+
``PlacementContext.detail_requests``
|
|
393
393
|
instead of building bespoke detail views; ``_resolve_details`` resolves them all
|
|
394
394
|
through one generic detailer (crop → project → place → caption → marker), then
|
|
395
395
|
calls ``redraw`` to draw the feature's own dims inside the placed detail view.
|
|
@@ -506,12 +506,12 @@ class Analysis:
|
|
|
506
506
|
bbox_max: float
|
|
507
507
|
holes: list
|
|
508
508
|
patterns: list
|
|
509
|
-
bosses: list # external bosses (
|
|
509
|
+
bosses: list # external bosses (recognise_bosses), detected once — the one inventory (#244)
|
|
510
510
|
slots: list
|
|
511
511
|
z_diams: list[float]
|
|
512
512
|
cross_diams: list[float]
|
|
513
513
|
cyls: tuple[list, list]
|
|
514
|
-
prof: TurnedProfile | None # turned step profile (
|
|
514
|
+
prof: TurnedProfile | None # turned step profile (recognise_turned_steps), detected once
|
|
515
515
|
od_diam: float | None
|
|
516
516
|
is_rotational: bool
|
|
517
517
|
od_axis: str # rotation/turning axis of a rotational part ("z" default; "x"/"y" #222)
|
|
@@ -636,7 +636,7 @@ def _iso_bbox(dwg):
|
|
|
636
636
|
|
|
637
637
|
|
|
638
638
|
# --- page/scale selection + sheet-layout constants and helpers --------------
|
|
639
|
-
# Relocated from make_drawing for the sheet.py split (#162). Shared by
|
|
639
|
+
# Relocated from make_drawing for the compose.py (née sheet.py) split (#162). Shared by compose.py
|
|
640
640
|
# (choose_scale/_layout_geometry) and make_drawing's repack pass, so they live
|
|
641
641
|
# here in the shared base to keep the DAG acyclic.
|
|
642
642
|
# The base inter-view corridor: one first-line gap + one dimension tier. Tracks
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Model-neutral geometry primitives — the leaf below both ``_core`` and ``model``.
|
|
2
|
+
|
|
3
|
+
These four helpers read an axis / coordinate / position off a build123d object
|
|
4
|
+
(or the IR) and carry no drawing, layout or page knowledge. They live here, not
|
|
5
|
+
in :mod:`draftwright._core`, so the IR waist (:mod:`draftwright.model`) can use
|
|
6
|
+
them without importing the stage-level drawing grab-bag (ADR 0008; #584 WP2).
|
|
7
|
+
This module imports nothing from ``draftwright`` — it is the bottom of the DAG.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
|
|
14
|
+
# Axis letter -> the orthographic view a feature on that axis reads end-on in.
|
|
15
|
+
_END_ON = {"x": "side", "y": "front", "z": "plan"}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _xyz(loc) -> tuple[float, float, float]:
|
|
19
|
+
"""A build123d ``Vector`` (has ``.X/.Y/.Z``) or an ``(x, y, z)`` sequence → an
|
|
20
|
+
``(x, y, z)`` float tuple. Shared by the detectors and the lint coverage checks
|
|
21
|
+
so the Vector-unpacking idiom lives in one place."""
|
|
22
|
+
if hasattr(loc, "X"):
|
|
23
|
+
return (loc.X, loc.Y, loc.Z)
|
|
24
|
+
x, y, z = loc
|
|
25
|
+
return (float(x), float(y), float(z))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True)
|
|
29
|
+
class HoleRef:
|
|
30
|
+
"""A position-keyed reference to a hole — the IR-typed value the cover / hole-table
|
|
31
|
+
bookkeeping matches on, so the shared escalation never needs a recogniser ``Hole``
|
|
32
|
+
object (ADR 0008 Amendment 6). Built from any location via :meth:`of` (rounded, so
|
|
33
|
+
two references at the same position compare equal)."""
|
|
34
|
+
|
|
35
|
+
x: float
|
|
36
|
+
y: float
|
|
37
|
+
z: float
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def of(cls, loc) -> HoleRef:
|
|
41
|
+
x, y, z = _xyz(loc)
|
|
42
|
+
return cls(round(x, 3), round(y, 3), round(z, 3))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _axis_letter(obj) -> str:
|
|
46
|
+
"""Letter (``"x"``/``"y"``/``"z"``) of ``obj.axis``'s dominant component.
|
|
47
|
+
|
|
48
|
+
``obj`` is anything carrying an ``.axis`` 3-vector (a hole or a boss).
|
|
49
|
+
"""
|
|
50
|
+
return max(zip("xyz", obj.axis, strict=True), key=lambda t: abs(t[1]))[0]
|