draftwright 0.3.0__tar.gz → 0.3.2__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.0 → draftwright-0.3.2}/CHANGELOG.md +75 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/PKG-INFO +4 -2
- {draftwright-0.3.0 → draftwright-0.3.2}/pyproject.toml +8 -2
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/__init__.py +4 -4
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/_core.py +69 -20
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/analysis.py +18 -13
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/annotations/_common.py +300 -22
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/annotations/from_model.py +945 -694
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/annotations/holes.py +971 -803
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/annotations/orchestrator.py +61 -55
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/annotations/sections.py +11 -14
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/builder.py +15 -10
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/cli.py +7 -13
- draftwright-0.3.0/src/draftwright/sheet.py → draftwright-0.3.2/src/draftwright/compose.py +5 -2
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/drawing.py +418 -234
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/export.py +48 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/linting/coverage.py +32 -3
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/linting/structural.py +102 -35
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/recognition/fillets.py +23 -11
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/registry.py +5 -0
- draftwright-0.3.0/src/draftwright/sheet_dsl.py → draftwright-0.3.2/src/draftwright/sheet.py +6 -1
- draftwright-0.3.2/src/draftwright/sheet_dsl.py +35 -0
- draftwright-0.3.2/tests/_kernel.py +32 -0
- draftwright-0.3.0/tests/test_layout_snapshot.py → draftwright-0.3.2/tests/_layout_sig.py +22 -72
- draftwright-0.3.0/tests/layout_snapshots/bracket.json → draftwright-0.3.2/tests/refactor_golden/bracket_section.json +1 -0
- draftwright-0.3.0/tests/layout_snapshots/slotted.json → draftwright-0.3.2/tests/refactor_golden/centered_rebate.json +86 -85
- draftwright-0.3.2/tests/refactor_golden/chamfered.json +135 -0
- draftwright-0.3.2/tests/refactor_golden/filleted.json +135 -0
- draftwright-0.3.0/tests/layout_snapshots/flange.json → draftwright-0.3.2/tests/refactor_golden/flange_dense.json +1 -0
- draftwright-0.3.2/tests/refactor_golden/grid_plate.json +474 -0
- draftwright-0.3.2/tests/refactor_golden/grooved_shaft.json +149 -0
- draftwright-0.3.0/tests/layout_snapshots/box.json → draftwright-0.3.2/tests/refactor_golden/hex_bar.json +52 -51
- {draftwright-0.3.0/tests/layout_snapshots → draftwright-0.3.2/tests/refactor_golden}/holed_slot.json +3 -2
- draftwright-0.3.2/tests/refactor_golden/pocketed.json +202 -0
- draftwright-0.3.2/tests/refactor_golden/prismatic_ladder.json +153 -0
- draftwright-0.3.2/tests/refactor_golden/scattered_plate.json +649 -0
- {draftwright-0.3.0/tests/layout_snapshots → draftwright-0.3.2/tests/refactor_golden}/side_drilled.json +1 -0
- draftwright-0.3.0/tests/layout_snapshots/turned_shaft.json → draftwright-0.3.2/tests/refactor_golden/turned_stepped.json +1 -0
- draftwright-0.3.2/tests/test_carve_free_position_callers.py +167 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/tests/test_declare.py +32 -3
- draftwright-0.3.2/tests/test_detect_once.py +81 -0
- draftwright-0.3.2/tests/test_drawing_encapsulation.py +193 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/tests/test_e2e_standards.py +5 -5
- draftwright-0.3.2/tests/test_export_dxf_zoom.py +65 -0
- draftwright-0.3.2/tests/test_fillets_adjacency.py +53 -0
- draftwright-0.3.2/tests/test_import_boundaries.py +462 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/tests/test_layout_cleanliness.py +12 -4
- draftwright-0.3.2/tests/test_layout_hypothesis.py +205 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/tests/test_layout_property.py +1 -1
- draftwright-0.3.2/tests/test_lint_box_cache.py +117 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/tests/test_make_drawing.py +439 -114
- draftwright-0.3.2/tests/test_pitch_dim_footprint.py +157 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/tests/test_pmi.py +33 -0
- draftwright-0.3.2/tests/test_private_test_imports.py +138 -0
- draftwright-0.3.2/tests/test_refactor_golden.py +258 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/tests/test_render_seam.py +7 -2
- {draftwright-0.3.0 → draftwright-0.3.2}/tests/test_sheet_gdt.py +1 -1
- {draftwright-0.3.0 → draftwright-0.3.2}/tests/test_sheet_notes.py +1 -1
- {draftwright-0.3.0 → draftwright-0.3.2}/tests/test_strip_layout.py +54 -69
- draftwright-0.3.0/tests/_kernel.py +0 -19
- draftwright-0.3.0/tests/layout_snapshots/drive_screw_x.json +0 -155
- draftwright-0.3.0/tests/layout_snapshots/dshape.json +0 -219
- draftwright-0.3.0/tests/layout_snapshots/plate_holes.json +0 -258
- draftwright-0.3.0/tests/test_import_boundaries.py +0 -114
- {draftwright-0.3.0 → draftwright-0.3.2}/.gitignore +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/LICENSE +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/README.md +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/skills/SKILL.md +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/_geometry.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/annotate.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/annotations/__init__.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/fits.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/fonts/IBMPlexMono-Regular.ttf +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/fonts/IBMPlexSansCondensed-Regular.ttf +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/fonts/LICENSE-IBMPlexMono-OFL.txt +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/fonts/LICENSE-IBMPlexSansCondensed-OFL.txt +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/fonts/__init__.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/intents.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/layout.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/linting/__init__.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/linting/issues.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/linting/suggest.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/make_drawing.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/model/__init__.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/model/declare.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/model/detect.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/model/ir.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/model/planner.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/pmi.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/projection.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/recognition/__init__.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/recognition/_features.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/recognition/_record.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/recognition/chamfers.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/recognition/countersinks.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/recognition/flats.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/recognition/grooves.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/recognition/levels.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/recognition/plates.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/recognition/slots.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/recognition/turned.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/repair.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/score.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/src/draftwright/sheet_emit.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/tests/fixtures/nist_ctc_01_asme1_ap203.stp +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/tests/fixtures/nist_ctc_01_asme1_ap242.stp +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/tests/fixtures/nist_ctc_02_asme1_ap203.stp +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/tests/fixtures/nist_ctc_02_asme1_ap242.stp +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/tests/fixtures/nist_ctc_03_asme1_ap203.stp +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/tests/fixtures/nist_ctc_03_asme1_ap242.stp +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/tests/fixtures/nist_ctc_04_asme1_ap203.stp +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/tests/fixtures/nist_ctc_04_asme1_ap242.stp +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/tests/fixtures/nist_ctc_05_asme1_ap203.stp +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/tests/fixtures/nist_ctc_05_asme1_ap242.stp +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/tests/test_e2e_slice.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/tests/test_fits.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/tests/test_gdt_placement.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/tests/test_layout.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/tests/test_lint_reconciliation.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/tests/test_lint_structural.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/tests/test_linting.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/tests/test_object_aspects.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/tests/test_part_model.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/tests/test_recogniser_contract.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/tests/test_recognition.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/tests/test_registry.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/tests/test_score.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/tests/test_sheet_emit.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/tests/test_sheet_of.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/tests/test_sheet_tables.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/tests/test_tolerances.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.2}/tests/test_turned_steps.py +0 -0
|
@@ -1,5 +1,80 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v0.3.2 — 2026-07-18
|
|
4
|
+
|
|
5
|
+
The **performance release**: the #602 perf loop (six PRs) plus the
|
|
6
|
+
build123d-drafting-helpers 0.14 upgrade. A dimension-dense drawing now builds
|
|
7
|
+
roughly **14× faster** than v0.3.1 (grid-plate benchmark 40 s → 2.8 s; NIST
|
|
8
|
+
CTC-02 ~107 s → well under half); the full test tier dropped ~14 min → ~3.5 min.
|
|
9
|
+
|
|
10
|
+
### Performance
|
|
11
|
+
|
|
12
|
+
- **Placement measures, then builds** (#678, #679): corridor/strip candidates are
|
|
13
|
+
evaluated on analytical footprints (`dim_footprint`) or a single probe box; an
|
|
14
|
+
accepted candidate builds its OCC geometry exactly once and re-validates the
|
|
15
|
+
real box. Dimension constructions on the scattered-plate benchmark: 59 → 21
|
|
16
|
+
(= the placed count).
|
|
17
|
+
- **Detect once** (#682): the sizing model `_analyse` builds pre-scale is stored
|
|
18
|
+
on `Analysis.model` and reused as the render model — restoring ADR 0008
|
|
19
|
+
Amendment 5 ("one inventory, detected once"). ~13 s off CTC-02.
|
|
20
|
+
- **Fillet recognition de-quadratised** (#683): edge→faces adjacency map replaces
|
|
21
|
+
the O(faces²×edges²) `IsSame` sweep (3.7 M calls on CTC-02); 10.8 s → 4.6 s,
|
|
22
|
+
byte-identical records.
|
|
23
|
+
- **Lint bounding boxes memoised** (#681): `Drawing.lint()` 0.88 s → 0.07 s per
|
|
24
|
+
call (identity+location-token-checked cache beside the #143 view-edge cache).
|
|
25
|
+
- **DXF viewport from the known page window** (#680): skips `ezdxf.zoom.extents`'
|
|
26
|
+
per-entity walk; ~2.4 s per DXF export.
|
|
27
|
+
- **helpers 0.14** (#684): boolean-free dimension ink (upstream #177, filed and
|
|
28
|
+
fixed same-day) — ~25 ms per `Dimension`, was ~234 ms.
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- **Tight-span dimensions now render honestly** (helpers 0.14): ISO outside
|
|
33
|
+
arrows where 0.13 silently degraded to a bare line. Ink extents grow on such
|
|
34
|
+
dims; placement models the flip analytically. Two placement-golden fixtures
|
|
35
|
+
re-blessed (ink extent only).
|
|
36
|
+
- Diameter-row leader labels space by **measured text width** and repair
|
|
37
|
+
helpers' shelf-flip collisions (crowded rows only; ordinary rows unchanged).
|
|
38
|
+
- A plate-thickness dim whose home strip corner is structurally contested falls
|
|
39
|
+
through to the opposite strip / side view instead of dropping (#684; the
|
|
40
|
+
underlying one-box-occupancy artifact is tracked as #685).
|
|
41
|
+
|
|
42
|
+
### Fixed
|
|
43
|
+
|
|
44
|
+
- `recognise_fillets` neighbour tie-break is order-independent (#683 review).
|
|
45
|
+
- Lint's annotation-box cache prunes departed objects and detects in-place
|
|
46
|
+
relocation (#681 review).
|
|
47
|
+
|
|
48
|
+
## v0.3.1 — 2026-07-17
|
|
49
|
+
|
|
50
|
+
Also lands the **#635 consolidation epic** (P1–P6, all six children closed): the render
|
|
51
|
+
layer's state bus is retired (`annotations/` reads zero `Drawing` privates,
|
|
52
|
+
machine-enforced), the three complexity hotspots are split (nesting ≤2), the whole-package
|
|
53
|
+
import DAG + a test-import ratchet are enforced, and the test suite gains a Hypothesis fuzz
|
|
54
|
+
tier + relational cross-kernel placement gates. All internal / behaviour-preserving
|
|
55
|
+
(byte-identical golden) — no output change.
|
|
56
|
+
|
|
57
|
+
### Added
|
|
58
|
+
|
|
59
|
+
- **PNG raster export** — `--format png` on the CLI, or `Drawing.export(formats=("png",))`.
|
|
60
|
+
Rendered SVG → PDF → PNG via **pypdfium2** (Apache-2.0, Google PDFium BSD-3) + **Pillow**
|
|
61
|
+
(HPND) — both permissively licensed and pure-wheel, so PNG works cross-platform with **no
|
|
62
|
+
native cairo** (ADR 0006) and keeps the render path dual-license-friendly. `dpi=` sets the
|
|
63
|
+
raster resolution.
|
|
64
|
+
|
|
65
|
+
### Changed
|
|
66
|
+
|
|
67
|
+
- **Unified export API** — `Drawing.export(out, *, formats=("pdf",)) → {format: path}` is now
|
|
68
|
+
the single entry point over `svg`/`dxf`/`pdf`/`png`; it returns a dict and internally handles
|
|
69
|
+
the SVG→PDF→PNG intermediate chain (writing/cleaning up intermediates it wasn't asked to keep).
|
|
70
|
+
The CLI `--format` gains `png`.
|
|
71
|
+
|
|
72
|
+
### Deprecated
|
|
73
|
+
|
|
74
|
+
- `Drawing.export_pdf()` — use `export(formats=("pdf",))["pdf"]`; the method now warns.
|
|
75
|
+
- The legacy `export(svg=, dxf=)` boolean keywords and their `(svg_path, dxf_path)` tuple return
|
|
76
|
+
— kept for back-compat; prefer `formats=[...]` (the dict API).
|
|
77
|
+
|
|
3
78
|
## v0.3.0 — 2026-07-14
|
|
4
79
|
|
|
5
80
|
**Breaking — feature-recogniser API renamed** (ADR 0013 / #568): *deliberate* breaks with
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: draftwright
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
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.
|
|
685
|
+
Requires-Dist: build123d-drafting-helpers>=0.14.0
|
|
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.3.
|
|
7
|
+
version = "0.3.2"
|
|
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.
|
|
20
|
+
"build123d-drafting-helpers>=0.14.0",
|
|
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",
|
|
@@ -112,6 +117,7 @@ ignore = ["E501"]
|
|
|
112
117
|
[dependency-groups]
|
|
113
118
|
dev = [
|
|
114
119
|
"codespell>=2.3.0",
|
|
120
|
+
"hypothesis>=6",
|
|
115
121
|
"mypy>=2.1.0",
|
|
116
122
|
"pytest>=8",
|
|
117
123
|
"pytest-cov>=5",
|
|
@@ -30,12 +30,12 @@ _LAZY = {
|
|
|
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
|
|
|
@@ -74,13 +74,13 @@ _sys.modules[__name__].__class__ = _DraftwrightModule
|
|
|
74
74
|
if TYPE_CHECKING: # static analysers / IDEs — no runtime import, no kernel cost
|
|
75
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
82
|
from draftwright.recognition import recognise_face_levels
|
|
82
|
-
from draftwright.sheet import
|
|
83
|
-
from draftwright.sheet_dsl import Sheet
|
|
83
|
+
from draftwright.sheet import Sheet
|
|
84
84
|
|
|
85
85
|
|
|
86
86
|
def __dir__():
|
|
@@ -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 (
|
|
@@ -106,8 +107,10 @@ _QUOTED_RE = re.compile(r"'([^']*)'")
|
|
|
106
107
|
|
|
107
108
|
|
|
108
109
|
@functools.lru_cache(maxsize=512)
|
|
109
|
-
def
|
|
110
|
-
|
|
110
|
+
def _text_size(
|
|
111
|
+
text: str, font_size: float, font_path: str | None = PLEX_MONO, font: str = "Arial"
|
|
112
|
+
) -> tuple[float, float]:
|
|
113
|
+
"""Measured rendered (width, height) (page-mm) of *text* at *font_size*.
|
|
111
114
|
|
|
112
115
|
Uses build123d's ``Text`` — the same primitive ``Dimension``/``HoleCallout``
|
|
113
116
|
stroke their labels with — so callout-width estimates use real glyph metrics
|
|
@@ -115,27 +118,63 @@ def _text_width(text: str, font_size: float, font_path: str = PLEX_MONO) -> floa
|
|
|
115
118
|
(``font_path``), not a system font *name*: name resolution substitutes a
|
|
116
119
|
different font on Linux, which makes this estimate — and the layout it feeds —
|
|
117
120
|
platform-variant (#149). The default is the same face the annotations render
|
|
118
|
-
with, so estimate and render agree.
|
|
119
|
-
|
|
121
|
+
with, so estimate and render agree. *font* only matters when a caller opts out
|
|
122
|
+
of path-pinning (``font_path=None``) — then the *name* resolves through the OS
|
|
123
|
+
font stack, exactly as the renderer would. Cached because the same numeric
|
|
124
|
+
labels recur across holes and the rasterisation is the costly part.
|
|
120
125
|
"""
|
|
121
126
|
if not text:
|
|
122
|
-
return 0.0
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
127
|
+
return (0.0, 0.0)
|
|
128
|
+
bb = Text(
|
|
129
|
+
txt=text,
|
|
130
|
+
font_size=font_size,
|
|
131
|
+
font=font,
|
|
132
|
+
font_path=font_path,
|
|
133
|
+
align=(Align.CENTER, Align.CENTER),
|
|
134
|
+
mode=Mode.PRIVATE,
|
|
135
|
+
).bounding_box()
|
|
136
|
+
return (bb.size.X, bb.size.Y)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _text_width(text: str, font_size: float, font_path: str = PLEX_MONO) -> float:
|
|
140
|
+
"""Measured rendered width (page-mm) of *text* — see :func:`_text_size`."""
|
|
141
|
+
return _text_size(text, font_size, font_path)[0]
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
# Cheap mean glyph advance as a fraction of the em (font size), for label-width ESTIMATES in
|
|
145
|
+
# the diameter row/column capacity checks. Plex Mono is monospaced at ~0.6 em, padded a touch.
|
|
146
|
+
# The exact metric is `_text_width()` above (measured per string), which the corridor/label
|
|
147
|
+
# placers use; these capacity gates only need a close bound, not the metric, so they trade
|
|
148
|
+
# exactness for not rasterising a text layout per candidate. The two may diverge for unusually
|
|
149
|
+
# wide/narrow glyph runs.
|
|
150
|
+
_EST_CHAR_WIDTH_EM = 0.62
|
|
134
151
|
|
|
135
152
|
|
|
136
153
|
_CONCENTRIC_TOL_MM = 0.5
|
|
137
154
|
|
|
138
155
|
|
|
156
|
+
def _first_free_index(prefix: str, taken) -> int:
|
|
157
|
+
"""The lowest ``j`` for which ``f"{prefix}{j}"`` is not in *taken* (a set or any container
|
|
158
|
+
supporting ``in``). The shared kernel of the first-free annotation-name allocators
|
|
159
|
+
(``_loc_name``/``_uniq``/``_hc_name``) — used where reusing a freed gap is fine. NOTE: the
|
|
160
|
+
step-length / diameter runs deliberately use ``max+1`` instead (``_next_start`` /
|
|
161
|
+
``_next_steplen_start``), because a contiguous run started at a gap below an occupied index
|
|
162
|
+
would wrap onto it (#432); don't fold those onto this."""
|
|
163
|
+
j = 0
|
|
164
|
+
while f"{prefix}{j}" in taken:
|
|
165
|
+
j += 1
|
|
166
|
+
return j
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _concentric_with_axis(a, x: float, y: float) -> bool:
|
|
170
|
+
"""True when the page/world point ``(x, y)`` lies on the rotational part's turned axis
|
|
171
|
+
(within :data:`_CONCENTRIC_TOL_MM`). A bore/pattern centred on the axis needs no location
|
|
172
|
+
dim — its position is the axis — so several passes filter such refs; this is the single
|
|
173
|
+
radial test they share (the perpendicular-plane distance to the axis centre ``(a.cx,
|
|
174
|
+
a.cy)``). Callers still gate on ``a.is_rotational`` / role / axis as their context needs."""
|
|
175
|
+
return math.hypot(x - a.cx, y - a.cy) <= _CONCENTRIC_TOL_MM
|
|
176
|
+
|
|
177
|
+
|
|
139
178
|
def _dim(p1, p2, side, distance, draft, **kwargs):
|
|
140
179
|
"""Build a :class:`Dimension`, tagged with its placement spec.
|
|
141
180
|
|
|
@@ -154,11 +193,14 @@ def _dim(p1, p2, side, distance, draft, **kwargs):
|
|
|
154
193
|
|
|
155
194
|
# Dimension-line spacing (page-mm, scale-independent), the single source of truth for
|
|
156
195
|
# BOTH the ADR 0009 strip carve (via the `Strip` dataclass defaults below) and the
|
|
157
|
-
#
|
|
196
|
+
# compose.py halo/depth estimates that must reserve the same space. Per ISO 129-1 / ASME
|
|
158
197
|
# Y14.5, the FIRST dimension line sits furthest from the outline (clears the outline +
|
|
159
198
|
# extension-line origins) and subsequent parallel lines stack tighter and uniform (#347).
|
|
160
199
|
_STRIP_GAP = 10.0 # clearance between the view outline and the first dimension line
|
|
161
200
|
_STRIP_SPACING = 2.5 # clear gap between successive parallel dimension lines (beyond the label)
|
|
201
|
+
# Small lift (page-mm) of an overall/envelope witness line off the projected silhouette edge,
|
|
202
|
+
# so its extension line reads as distinct from the outline rather than sitting on top of it.
|
|
203
|
+
_WITNESS_LIFT_MM = 2.0
|
|
162
204
|
|
|
163
205
|
|
|
164
206
|
@dataclass
|
|
@@ -353,7 +395,8 @@ def _largest_empty_rect(drawable, obstacles, *, warn: bool = True):
|
|
|
353
395
|
@dataclass
|
|
354
396
|
class DetailRequest:
|
|
355
397
|
"""A renderer's request for an enlarged detail of a region it could not draw
|
|
356
|
-
legibly at sheet scale (#307). Renderers append these to
|
|
398
|
+
legibly at sheet scale (#307). Renderers append these to the run's
|
|
399
|
+
``PlacementContext.detail_requests``
|
|
357
400
|
instead of building bespoke detail views; ``_resolve_details`` resolves them all
|
|
358
401
|
through one generic detailer (crop → project → place → caption → marker), then
|
|
359
402
|
calls ``redraw`` to draw the feature's own dims inside the placed detail view.
|
|
@@ -520,6 +563,12 @@ class Analysis:
|
|
|
520
563
|
out: str
|
|
521
564
|
pmi: list
|
|
522
565
|
pmi_mode: str
|
|
566
|
+
# The PartModel built by _analyse's pre-scale sizing pass (#584 WP1 A) — stored so
|
|
567
|
+
# the render path reuses it instead of re-running the detectors (ADR 0008 Amdt 5:
|
|
568
|
+
# one inventory, detected once; #602). Typed `object` because model/ sits above
|
|
569
|
+
# _core in the DAG. None when the caller declared a model (ADR 0011) or on a
|
|
570
|
+
# manually-built Analysis — consumers fall back to build_model(a).
|
|
571
|
+
model: object | None = None
|
|
523
572
|
|
|
524
573
|
|
|
525
574
|
_greedy_strip_ys = _greedy_strip_1d
|
|
@@ -600,7 +649,7 @@ def _iso_bbox(dwg):
|
|
|
600
649
|
|
|
601
650
|
|
|
602
651
|
# --- page/scale selection + sheet-layout constants and helpers --------------
|
|
603
|
-
# Relocated from make_drawing for the sheet.py split (#162). Shared by
|
|
652
|
+
# Relocated from make_drawing for the compose.py (née sheet.py) split (#162). Shared by compose.py
|
|
604
653
|
# (choose_scale/_layout_geometry) and make_drawing's repack pass, so they live
|
|
605
654
|
# here in the shared base to keep the DAG acyclic.
|
|
606
655
|
# The base inter-view corridor: one first-line gap + one dimension tier. Tracks
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
`_analyse` imports the part (STEP or Shape), runs feature detection (holes,
|
|
4
4
|
patterns, cylinders, face levels), classifies it (rotational vs prismatic),
|
|
5
|
-
chooses the sheet (scale/page via `
|
|
6
|
-
zones (`
|
|
7
|
-
namespace the rest of the pipeline reads. Sits above `
|
|
8
|
-
`
|
|
5
|
+
chooses the sheet (scale/page via `compose.choose_scale`) and lays out the view
|
|
6
|
+
zones (`compose._layout_geometry`/`_build_zones`) — returning the `Analysis`
|
|
7
|
+
namespace the rest of the pipeline reads. Sits above `compose` (née `sheet`,
|
|
8
|
+
#640) and below `builder` in the DAG.
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
from __future__ import annotations
|
|
@@ -32,6 +32,15 @@ from draftwright._core import (
|
|
|
32
32
|
_legible_steps,
|
|
33
33
|
_Projector,
|
|
34
34
|
)
|
|
35
|
+
from draftwright.compose import (
|
|
36
|
+
StripDepths,
|
|
37
|
+
_build_zones,
|
|
38
|
+
_est_hole_table_sizes,
|
|
39
|
+
_est_planned_bore_callout_width,
|
|
40
|
+
_layout_geometry,
|
|
41
|
+
_measure_strips,
|
|
42
|
+
choose_scale,
|
|
43
|
+
)
|
|
35
44
|
from draftwright.model import build_part_model
|
|
36
45
|
from draftwright.model.ir import Datum, PartModel, StepFeature
|
|
37
46
|
from draftwright.model.planner import plan_dimensions
|
|
@@ -47,15 +56,6 @@ from draftwright.recognition import (
|
|
|
47
56
|
recognise_turned_steps,
|
|
48
57
|
step_level_zs,
|
|
49
58
|
)
|
|
50
|
-
from draftwright.sheet import (
|
|
51
|
-
StripDepths,
|
|
52
|
-
_build_zones,
|
|
53
|
-
_est_hole_table_sizes,
|
|
54
|
-
_est_planned_bore_callout_width,
|
|
55
|
-
_layout_geometry,
|
|
56
|
-
_measure_strips,
|
|
57
|
-
choose_scale,
|
|
58
|
-
)
|
|
59
59
|
|
|
60
60
|
_log = logging.getLogger(__name__)
|
|
61
61
|
|
|
@@ -772,4 +772,9 @@ def _analyse(
|
|
|
772
772
|
out=out,
|
|
773
773
|
pmi=pmi_records,
|
|
774
774
|
pmi_mode=pmi,
|
|
775
|
+
# The sizing model IS the render model when detection ran (identical inputs by
|
|
776
|
+
# construction — #584 WP1 A); store it so the pipeline never detects twice
|
|
777
|
+
# (ADR 0008 Amdt 5, #602). A declared model (layout_model) is NOT stored: the
|
|
778
|
+
# builder coerces + decorates the caller's model itself.
|
|
779
|
+
model=sizing_model if layout_model is None else None,
|
|
775
780
|
)
|