draftwright 0.3.0__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.3.0 → draftwright-0.3.1}/CHANGELOG.md +30 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/PKG-INFO +3 -1
- {draftwright-0.3.0 → draftwright-0.3.1}/pyproject.toml +7 -1
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/__init__.py +4 -4
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/_core.py +40 -4
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/analysis.py +13 -13
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/annotations/_common.py +74 -7
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/annotations/from_model.py +778 -688
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/annotations/holes.py +948 -798
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/annotations/orchestrator.py +61 -55
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/annotations/sections.py +11 -14
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/builder.py +7 -7
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/cli.py +7 -13
- draftwright-0.3.0/src/draftwright/sheet.py → draftwright-0.3.1/src/draftwright/compose.py +5 -2
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/drawing.py +400 -234
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/export.py +21 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/linting/coverage.py +32 -3
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/registry.py +5 -0
- draftwright-0.3.0/src/draftwright/sheet_dsl.py → draftwright-0.3.1/src/draftwright/sheet.py +6 -1
- draftwright-0.3.1/src/draftwright/sheet_dsl.py +35 -0
- draftwright-0.3.1/tests/_kernel.py +32 -0
- draftwright-0.3.0/tests/test_layout_snapshot.py → draftwright-0.3.1/tests/_layout_sig.py +22 -72
- draftwright-0.3.0/tests/layout_snapshots/bracket.json → draftwright-0.3.1/tests/refactor_golden/bracket_section.json +1 -0
- draftwright-0.3.0/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.3.0/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.3.0/tests/layout_snapshots/box.json → draftwright-0.3.1/tests/refactor_golden/hex_bar.json +52 -51
- {draftwright-0.3.0/tests/layout_snapshots → draftwright-0.3.1/tests/refactor_golden}/holed_slot.json +1 -0
- draftwright-0.3.1/tests/refactor_golden/pocketed.json +202 -0
- draftwright-0.3.1/tests/refactor_golden/prismatic_ladder.json +153 -0
- {draftwright-0.3.0/tests/layout_snapshots → draftwright-0.3.1/tests/refactor_golden}/side_drilled.json +1 -0
- draftwright-0.3.0/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.0 → draftwright-0.3.1}/tests/test_declare.py +32 -3
- draftwright-0.3.1/tests/test_drawing_encapsulation.py +193 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/tests/test_e2e_standards.py +5 -5
- draftwright-0.3.1/tests/test_import_boundaries.py +462 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/tests/test_layout_cleanliness.py +4 -3
- draftwright-0.3.1/tests/test_layout_hypothesis.py +205 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/tests/test_layout_property.py +1 -1
- {draftwright-0.3.0 → draftwright-0.3.1}/tests/test_make_drawing.py +439 -114
- {draftwright-0.3.0 → 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_refactor_golden.py +219 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/tests/test_render_seam.py +7 -2
- {draftwright-0.3.0 → draftwright-0.3.1}/tests/test_sheet_gdt.py +1 -1
- {draftwright-0.3.0 → draftwright-0.3.1}/tests/test_sheet_notes.py +1 -1
- {draftwright-0.3.0 → draftwright-0.3.1}/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.1}/.gitignore +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/LICENSE +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/README.md +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/skills/SKILL.md +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/_geometry.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/annotate.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/annotations/__init__.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/fits.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/fonts/IBMPlexMono-Regular.ttf +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/fonts/IBMPlexSansCondensed-Regular.ttf +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/fonts/LICENSE-IBMPlexMono-OFL.txt +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/fonts/LICENSE-IBMPlexSansCondensed-OFL.txt +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/fonts/__init__.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/intents.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/layout.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/linting/__init__.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/linting/issues.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/linting/structural.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/linting/suggest.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/make_drawing.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/model/__init__.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/model/declare.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/model/detect.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/model/ir.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/model/planner.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/pmi.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/projection.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/recognition/__init__.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/recognition/_features.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/recognition/_record.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/recognition/chamfers.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/recognition/countersinks.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/recognition/fillets.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/recognition/flats.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/recognition/grooves.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/recognition/levels.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/recognition/plates.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/recognition/slots.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/recognition/turned.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/repair.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/score.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/src/draftwright/sheet_emit.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/tests/fixtures/nist_ctc_01_asme1_ap203.stp +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/tests/fixtures/nist_ctc_01_asme1_ap242.stp +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/tests/fixtures/nist_ctc_02_asme1_ap203.stp +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/tests/fixtures/nist_ctc_02_asme1_ap242.stp +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/tests/fixtures/nist_ctc_03_asme1_ap203.stp +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/tests/fixtures/nist_ctc_03_asme1_ap242.stp +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/tests/fixtures/nist_ctc_04_asme1_ap203.stp +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/tests/fixtures/nist_ctc_04_asme1_ap242.stp +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/tests/fixtures/nist_ctc_05_asme1_ap203.stp +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/tests/fixtures/nist_ctc_05_asme1_ap242.stp +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/tests/test_e2e_slice.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/tests/test_fits.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/tests/test_gdt_placement.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/tests/test_layout.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/tests/test_lint_reconciliation.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/tests/test_lint_structural.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/tests/test_linting.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/tests/test_object_aspects.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/tests/test_part_model.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/tests/test_recogniser_contract.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/tests/test_recognition.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/tests/test_registry.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/tests/test_score.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/tests/test_sheet_emit.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/tests/test_sheet_of.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/tests/test_sheet_tables.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/tests/test_tolerances.py +0 -0
- {draftwright-0.3.0 → draftwright-0.3.1}/tests/test_turned_steps.py +0 -0
|
@@ -1,5 +1,35 @@
|
|
|
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
|
+
|
|
3
33
|
## v0.3.0 — 2026-07-14
|
|
4
34
|
|
|
5
35
|
**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.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
|
|
@@ -686,6 +686,8 @@ 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.3.
|
|
7
|
+
version = "0.3.1"
|
|
8
8
|
description = "Automated technical-drawing generation for build123d"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { file = "LICENSE" }
|
|
@@ -19,6 +19,11 @@ dependencies = [
|
|
|
19
19
|
"build123d>=0.11,<0.12 ; python_full_version >= '3.13'",
|
|
20
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",
|
|
@@ -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 (
|
|
@@ -133,9 +134,40 @@ def _text_width(text: str, font_size: float, font_path: str = PLEX_MONO) -> floa
|
|
|
133
134
|
)
|
|
134
135
|
|
|
135
136
|
|
|
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
|
|
144
|
+
|
|
145
|
+
|
|
136
146
|
_CONCENTRIC_TOL_MM = 0.5
|
|
137
147
|
|
|
138
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
|
+
|
|
139
171
|
def _dim(p1, p2, side, distance, draft, **kwargs):
|
|
140
172
|
"""Build a :class:`Dimension`, tagged with its placement spec.
|
|
141
173
|
|
|
@@ -154,11 +186,14 @@ def _dim(p1, p2, side, distance, draft, **kwargs):
|
|
|
154
186
|
|
|
155
187
|
# Dimension-line spacing (page-mm, scale-independent), the single source of truth for
|
|
156
188
|
# BOTH the ADR 0009 strip carve (via the `Strip` dataclass defaults below) and the
|
|
157
|
-
#
|
|
189
|
+
# compose.py halo/depth estimates that must reserve the same space. Per ISO 129-1 / ASME
|
|
158
190
|
# Y14.5, the FIRST dimension line sits furthest from the outline (clears the outline +
|
|
159
191
|
# extension-line origins) and subsequent parallel lines stack tighter and uniform (#347).
|
|
160
192
|
_STRIP_GAP = 10.0 # clearance between the view outline and the first dimension line
|
|
161
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
|
|
162
197
|
|
|
163
198
|
|
|
164
199
|
@dataclass
|
|
@@ -353,7 +388,8 @@ def _largest_empty_rect(drawable, obstacles, *, warn: bool = True):
|
|
|
353
388
|
@dataclass
|
|
354
389
|
class DetailRequest:
|
|
355
390
|
"""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
|
|
391
|
+
legibly at sheet scale (#307). Renderers append these to the run's
|
|
392
|
+
``PlacementContext.detail_requests``
|
|
357
393
|
instead of building bespoke detail views; ``_resolve_details`` resolves them all
|
|
358
394
|
through one generic detailer (crop → project → place → caption → marker), then
|
|
359
395
|
calls ``redraw`` to draw the feature's own dims inside the placed detail view.
|
|
@@ -600,7 +636,7 @@ def _iso_bbox(dwg):
|
|
|
600
636
|
|
|
601
637
|
|
|
602
638
|
# --- page/scale selection + sheet-layout constants and helpers --------------
|
|
603
|
-
# 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
|
|
604
640
|
# (choose_scale/_layout_geometry) and make_drawing's repack pass, so they live
|
|
605
641
|
# here in the shared base to keep the DAG acyclic.
|
|
606
642
|
# 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
|
|
|
@@ -9,10 +9,12 @@ from __future__ import annotations
|
|
|
9
9
|
|
|
10
10
|
import logging
|
|
11
11
|
from dataclasses import dataclass, field
|
|
12
|
+
from typing import Any
|
|
12
13
|
|
|
13
14
|
from build123d_drafting.helpers import Dimension, SafeDimension
|
|
14
15
|
|
|
15
16
|
from draftwright.layout import StripCandidate, plan_strip
|
|
17
|
+
from draftwright.linting.issues import LintIssue
|
|
16
18
|
from draftwright.linting.structural import _centerline_extent
|
|
17
19
|
|
|
18
20
|
_log = logging.getLogger(__name__)
|
|
@@ -22,7 +24,8 @@ _log = logging.getLogger(__name__)
|
|
|
22
24
|
class Escalation:
|
|
23
25
|
"""A first-class "could not place this here" signal (ADR 0009 Amendment 1, P5-strand-2).
|
|
24
26
|
|
|
25
|
-
Placers *collect* one of these into ``
|
|
27
|
+
Placers *collect* one of these into the run's ``PlacementContext.escalations`` at the point
|
|
28
|
+
of failure —
|
|
26
29
|
instead of recording a stringly-typed ``*_dropped`` lint code and letting the escalators
|
|
27
30
|
grep for it — and one later resolver pass groups them by ``(view, feature-or-pattern)``,
|
|
28
31
|
picks a remedy per group (ISO pattern-grouped balloon / table / detail / drop), and emits
|
|
@@ -519,25 +522,89 @@ def solve_corridor(dwg, strip, view, axis, cands, tier):
|
|
|
519
522
|
_promote_losers(c)
|
|
520
523
|
|
|
521
524
|
|
|
522
|
-
|
|
525
|
+
@dataclass
|
|
526
|
+
class PlacementContext:
|
|
527
|
+
"""The per-run placement scratch a build's passes share — plus references to the drawing's
|
|
528
|
+
build-state stores (the ``registry`` build-issue sink + ``coverage`` bookkeeping) — threaded
|
|
529
|
+
to the passes explicitly instead of hung on the ``Drawing`` result object (ADR 0005 §2, #639):
|
|
530
|
+
the corridor batch
|
|
531
|
+
(:func:`register_corridor`/:func:`drain_corridors`), the escalation list (ADR 0009 Amdt 1,
|
|
532
|
+
#351), and the enlarged-detail request list (#307).
|
|
533
|
+
|
|
534
|
+
All three are per-run — both entry paths (:func:`_auto_annotate` and ``Drawing.finalize``)
|
|
535
|
+
make a fresh one each build and discard it after draining/consuming. The corridor batch is a
|
|
536
|
+
pure function of the still-present intents; escalations/detail-requests need no cross-retry
|
|
537
|
+
persistence either, because finalize is transactional (#647): a raised drain rolls the drawing
|
|
538
|
+
back and the retry re-runs from a clean slate, re-generating them."""
|
|
539
|
+
|
|
540
|
+
corridor_batch: dict = field(default_factory=dict)
|
|
541
|
+
escalations: list = field(default_factory=list)
|
|
542
|
+
detail_requests: list = field(default_factory=list)
|
|
543
|
+
# The drawing's build-state stores, referenced (not owned) by the run's passes (#639).
|
|
544
|
+
# Duck-typed as ``Any`` — matching the untyped ``Drawing._record_build_issue`` they replace —
|
|
545
|
+
# so mypy does not reject the delegating calls below.
|
|
546
|
+
registry: Any = None # the drawing's AnnotationRegistry: build-issue sink + names
|
|
547
|
+
coverage: Any = None # the drawing's CoverageState
|
|
548
|
+
# The ensured PartModel (ADR 0008 IR) the run's passes read, threaded off the drawing so
|
|
549
|
+
# they no longer reach into ``dwg._part_model`` (#639). Both entry paths set it from the
|
|
550
|
+
# PUBLIC ``dwg.model()`` after the model is ensured/attached.
|
|
551
|
+
part_model: Any = None
|
|
552
|
+
# Whether the model was DECLARED (vs detected) — the ADR 0011 gate the orchestrator reads,
|
|
553
|
+
# threaded off ``getattr(dwg, "_model_declared")`` (#639).
|
|
554
|
+
model_declared: bool = False
|
|
555
|
+
# Per-run cache for :meth:`feature_of_hole_at` — the model is fixed after build, so a
|
|
556
|
+
# per-ctx (per-run) index is correct (mirrors the old ``Drawing._hole_feature_index``).
|
|
557
|
+
_hole_feature_index: Any = field(default=None, repr=False)
|
|
558
|
+
|
|
559
|
+
def feature_of_hole_at(self, location):
|
|
560
|
+
"""The IR hole/pattern feature whose member sits at model-space *location*, or ``None``
|
|
561
|
+
(#408/#639). Attributes a balloon (which carries a recognition hole, not the IR feature)
|
|
562
|
+
to its feature so :meth:`Drawing.drop` clears it. Cached on the run's ctx — the model is
|
|
563
|
+
fixed after build."""
|
|
564
|
+
m = self.part_model
|
|
565
|
+
if m is None:
|
|
566
|
+
return None
|
|
567
|
+
if self._hole_feature_index is None:
|
|
568
|
+
idx: dict = {}
|
|
569
|
+
for f in getattr(m, "features", []):
|
|
570
|
+
if getattr(f, "kind", None) in ("hole", "pattern"):
|
|
571
|
+
for loc in getattr(f, "members", None) or (f.frame.origin,):
|
|
572
|
+
idx[tuple(round(c, 3) for c in loc)] = f
|
|
573
|
+
self._hole_feature_index = idx
|
|
574
|
+
return self._hole_feature_index.get(tuple(round(c, 3) for c in location))
|
|
575
|
+
|
|
576
|
+
def record_issue(self, severity, code, message) -> None:
|
|
577
|
+
"""Record a build-time lint issue on the run's registry (#639). Replaces the passes'
|
|
578
|
+
old `dwg._record_build_issue`."""
|
|
579
|
+
self.registry.record_issue(LintIssue(severity=severity, code=code, message=message))
|
|
580
|
+
|
|
581
|
+
def reset_issues(self) -> None:
|
|
582
|
+
self.registry.reset_issues()
|
|
583
|
+
|
|
584
|
+
def drop_issues(self, *codes) -> None:
|
|
585
|
+
self.registry.drop_issues(codes)
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
def register_corridor(ctx, key, strip, view, axis, tier, cand):
|
|
523
589
|
"""Queue a :class:`CorridorCandidate` under a shared corridor *key* so one
|
|
524
590
|
:func:`drain_corridors` places the whole cross-pass set together (ADR 0009 end state).
|
|
525
591
|
The first registration for a key fixes its ``(strip, view, axis)``; mixed producers on
|
|
526
592
|
the same corridor use the largest requested tier so spacing is not registration-order
|
|
527
593
|
dependent."""
|
|
528
|
-
b =
|
|
594
|
+
b = ctx.corridor_batch.setdefault(
|
|
529
595
|
key, {"strip": strip, "view": view, "axis": axis, "tier": tier, "cands": []}
|
|
530
596
|
)
|
|
531
597
|
b["tier"] = max(b["tier"], tier)
|
|
532
598
|
b["cands"].append(cand)
|
|
533
599
|
|
|
534
600
|
|
|
535
|
-
def drain_corridors(dwg):
|
|
601
|
+
def drain_corridors(ctx, dwg):
|
|
536
602
|
"""Solve every registered corridor (one :func:`solve_corridor` per strip), then clear
|
|
537
|
-
the batch. Called once, after all corridor-feeding passes have registered.
|
|
538
|
-
|
|
603
|
+
the batch. Called once, after all corridor-feeding passes have registered. Takes both the
|
|
604
|
+
scratch *ctx* (the batch) and *dwg* (the drawing :func:`solve_corridor` places onto)."""
|
|
605
|
+
for b in ctx.corridor_batch.values():
|
|
539
606
|
solve_corridor(dwg, b["strip"], b["view"], b["axis"], b["cands"], b["tier"])
|
|
540
|
-
|
|
607
|
+
ctx.corridor_batch = {}
|
|
541
608
|
|
|
542
609
|
|
|
543
610
|
def place_strip_candidates(
|