draftwright 0.2.2__tar.gz → 0.2.4__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.2 → draftwright-0.2.4}/CHANGELOG.md +78 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/PKG-INFO +63 -49
- {draftwright-0.2.2 → draftwright-0.2.4}/README.md +62 -48
- {draftwright-0.2.2 → draftwright-0.2.4}/pyproject.toml +1 -1
- {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/_core.py +25 -51
- draftwright-0.2.4/src/draftwright/annotations/_common.py +540 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/annotations/from_model.py +367 -116
- {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/annotations/holes.py +400 -204
- {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/annotations/orchestrator.py +160 -97
- {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/annotations/sections.py +121 -24
- {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/builder.py +34 -9
- {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/drawing.py +79 -37
- draftwright-0.2.4/src/draftwright/layout.py +676 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/linting/coverage.py +15 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/registry.py +14 -3
- {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/repair.py +2 -2
- {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/sheet.py +47 -0
- draftwright-0.2.4/tests/layout_snapshots/box.json +116 -0
- draftwright-0.2.4/tests/layout_snapshots/bracket.json +388 -0
- draftwright-0.2.4/tests/layout_snapshots/drive_screw_x.json +155 -0
- draftwright-0.2.4/tests/layout_snapshots/dshape.json +201 -0
- draftwright-0.2.4/tests/layout_snapshots/flange.json +287 -0
- draftwright-0.2.4/tests/layout_snapshots/holed_slot.json +299 -0
- draftwright-0.2.4/tests/layout_snapshots/plate_holes.json +258 -0
- draftwright-0.2.4/tests/layout_snapshots/side_drilled.json +183 -0
- draftwright-0.2.4/tests/layout_snapshots/slotted.json +170 -0
- draftwright-0.2.4/tests/layout_snapshots/turned_shaft.json +196 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/tests/test_layout.py +176 -4
- draftwright-0.2.4/tests/test_layout_cleanliness.py +192 -0
- draftwright-0.2.4/tests/test_layout_property.py +201 -0
- draftwright-0.2.4/tests/test_layout_snapshot.py +231 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/tests/test_make_drawing.py +213 -91
- {draftwright-0.2.2 → draftwright-0.2.4}/tests/test_pmi.py +19 -17
- {draftwright-0.2.2 → draftwright-0.2.4}/tests/test_registry.py +28 -0
- draftwright-0.2.4/tests/test_render_seam.py +200 -0
- draftwright-0.2.4/tests/test_strip_layout.py +768 -0
- draftwright-0.2.2/src/draftwright/annotations/_common.py +0 -51
- draftwright-0.2.2/src/draftwright/layout.py +0 -317
- draftwright-0.2.2/tests/test_render_seam.py +0 -63
- {draftwright-0.2.2 → draftwright-0.2.4}/.gitignore +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/LICENSE +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/skills/SKILL.md +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/__init__.py +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/analysis.py +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/annotate.py +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/annotations/__init__.py +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/cli.py +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/export.py +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/features.py +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/fonts/IBMPlexMono-Regular.ttf +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/fonts/IBMPlexSansCondensed-Regular.ttf +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/fonts/LICENSE-IBMPlexMono-OFL.txt +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/fonts/LICENSE-IBMPlexSansCondensed-OFL.txt +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/fonts/__init__.py +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/linting/__init__.py +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/linting/issues.py +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/linting/structural.py +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/linting/suggest.py +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/make_drawing.py +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/model/__init__.py +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/model/detect.py +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/model/ir.py +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/model/planner.py +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/pmi.py +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/projection.py +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/recognition/__init__.py +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/recognition/_features.py +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/recognition/levels.py +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/recognition/slots.py +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/recognition/turned.py +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/tests/fixtures/nist_ctc_01_asme1_ap203.stp +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/tests/fixtures/nist_ctc_01_asme1_ap242.stp +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/tests/fixtures/nist_ctc_02_asme1_ap203.stp +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/tests/fixtures/nist_ctc_02_asme1_ap242.stp +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/tests/fixtures/nist_ctc_03_asme1_ap203.stp +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/tests/fixtures/nist_ctc_03_asme1_ap242.stp +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/tests/fixtures/nist_ctc_04_asme1_ap203.stp +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/tests/fixtures/nist_ctc_04_asme1_ap242.stp +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/tests/fixtures/nist_ctc_05_asme1_ap203.stp +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/tests/fixtures/nist_ctc_05_asme1_ap242.stp +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/tests/test_e2e_slice.py +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/tests/test_e2e_standards.py +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/tests/test_lint_structural.py +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/tests/test_linting.py +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/tests/test_part_model.py +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/tests/test_recognition.py +0 -0
- {draftwright-0.2.2 → draftwright-0.2.4}/tests/test_turned_steps.py +0 -0
|
@@ -2,6 +2,84 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## v0.2.4 — 2026-07-03
|
|
6
|
+
|
|
7
|
+
A follow-up patch on the ADR 0009 placement rebuild in 0.2.3: it finishes unifying
|
|
8
|
+
the shared "above-view" dimension corridor, adds a layout-overflow safety net, and
|
|
9
|
+
makes two more drop paths non-silent.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- **Plan-view X location dimensions, side-view Y location dimensions, and a
|
|
14
|
+
coincident slot-position dimension now share one collect-then-solve pass** (ADR
|
|
15
|
+
0009 Amendment 6, #345/#346). Previously each pass carved the strip independently,
|
|
16
|
+
so a hole location and a slot position measuring the same datum span could both be
|
|
17
|
+
drawn, and the location ladder could come out non-monotonic. One solve now dedups
|
|
18
|
+
the coincident span (keeping the higher-priority location dimension) and orders the
|
|
19
|
+
whole ladder as segregated, monotonic runs — feature-size dimensions nearest the
|
|
20
|
+
view, datum locations nesting outward by distance.
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- **`choose_scale` never returns an overflowing layout** (#350). Scale selection
|
|
25
|
+
could pick a scale whose composed block layout exceeded the drawable area; it now
|
|
26
|
+
rejects any overflowing candidate.
|
|
27
|
+
- **A hole location and a coincident slot position are no longer drawn twice** (#345),
|
|
28
|
+
including at fractional datum distances where a display-value snap gap previously let
|
|
29
|
+
the duplicate escape deduplication.
|
|
30
|
+
- **The plan-view location ladder is monotonic** (#346) — running dimensions off a
|
|
31
|
+
shared datum stack outward in ascending order instead of interleaving.
|
|
32
|
+
- **A dropped balloon is non-silent** (#387). A balloon that cannot be placed now
|
|
33
|
+
reports the drop and clears its `callout_dropped` state precisely, instead of
|
|
34
|
+
vanishing with no on-sheet signal.
|
|
35
|
+
|
|
36
|
+
## v0.2.3 — 2026-07-03
|
|
37
|
+
|
|
38
|
+
A large patch release: the **annotation-placement engine was rebuilt** as a
|
|
39
|
+
collect-then-solve *boundary-labeling* stage (ADR 0009). Placement is now
|
|
40
|
+
deterministic and minimises total leader length, and the recurring class of
|
|
41
|
+
overlaps where a label was drawn on top of an "invisible" occupant — a leader
|
|
42
|
+
shaft, a witness/extension line, the section hatch — is removed by construction.
|
|
43
|
+
Drawing output changes for many turned, cross-drilled, and multi-feature parts.
|
|
44
|
+
|
|
45
|
+
### Changed
|
|
46
|
+
|
|
47
|
+
- **Every annotation in a view's margin is now placed by one collect-then-solve
|
|
48
|
+
pass** (ADR 0009, #317–#323). Dimensions, hole callouts, turned-diameter
|
|
49
|
+
leaders, and the section hatch share a single occupancy model instead of several
|
|
50
|
+
independent passes each blind to the others. When a strip is over capacity it now
|
|
51
|
+
drops the *lowest-priority* annotation (smallest bore first) rather than whichever
|
|
52
|
+
pass happened to run last. The legacy strip cursor is retired.
|
|
53
|
+
- **Leader placement minimises total leader length, deterministically** (P4,
|
|
54
|
+
#318). A per-strip solve places each label at the shortest-leader position that
|
|
55
|
+
keeps the labels in order and clear of keep-out rows (a view centre-line, a
|
|
56
|
+
dimension's extension line); central/coaxial callouts are anchored to the
|
|
57
|
+
view-centre row. Output is reproducible across platforms and Python versions.
|
|
58
|
+
- **`scipy` is no longer a dependency** — the leader solve is a small deterministic
|
|
59
|
+
algorithm (weighted-median isotonic regression), not a linear program.
|
|
60
|
+
- **Output changes** for turned, cross-drilled, and multi-feature parts whose
|
|
61
|
+
margin annotations are now positioned by the unified solver.
|
|
62
|
+
|
|
63
|
+
### Fixed
|
|
64
|
+
|
|
65
|
+
- **A PMI bore-diameter dimension spans the bore radius, not the full diameter**
|
|
66
|
+
(#360). A `pmi="annotate"` diameter callout drew its witness lines at ±diameter
|
|
67
|
+
from the centre — twice too wide, missing the bore edges.
|
|
68
|
+
- **A bore coaxial with a rotational part's turning axis is no longer
|
|
69
|
+
over-dimensioned** (#309). It carried a redundant offset *and* height location
|
|
70
|
+
dimension even though its centre mark already locates it.
|
|
71
|
+
- **A dropped turned step-length chain is no longer silent** (#362). When a turned
|
|
72
|
+
head's shoulders are too crowded to dimension, the drop is now reported
|
|
73
|
+
(`step_dim_dropped`) instead of vanishing with no lint or on-sheet signal.
|
|
74
|
+
- **A diameter callout can no longer overprint a bore callout's leader shaft**
|
|
75
|
+
(#358). The turned-diameter column now avoids the *full* footprint of existing
|
|
76
|
+
annotations, not just their text boxes.
|
|
77
|
+
- **The balloon ring hugs its dimensions on a cramped sheet** (#349) — its band
|
|
78
|
+
depth is clamped to the drawable area.
|
|
79
|
+
- **Dimension detection is robust to `SafeDimension`** (#335/#349) — the corridor
|
|
80
|
+
and balloon-ring filters test the dimension *type*, not a class-name string, so a
|
|
81
|
+
future dimension subclass can't slip through.
|
|
82
|
+
|
|
5
83
|
## v0.2.2 — 2026-06-30
|
|
6
84
|
|
|
7
85
|
A patch release of turned-part dimension-placement fixes and a CLI start-up
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: draftwright
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
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
|
|
@@ -700,24 +700,70 @@ Description-Content-Type: text/markdown
|
|
|
700
700
|
Automated technical-drawing generation for [build123d](https://github.com/gumyr/build123d).
|
|
701
701
|
Point it at a solid (or a STEP file) and get a fully-annotated multi-view engineering
|
|
702
702
|
drawing — orthographic views, dimensions, section A–A, ISO hatching, title block — ready
|
|
703
|
-
to export as SVG and DXF.
|
|
703
|
+
to export as PDF, SVG, and DXF.
|
|
704
|
+
|
|
705
|
+

|
|
706
|
+
|
|
707
|
+
*A mounting plate, generated automatically: three dimensioned orthographic views, a
|
|
708
|
+
counterbored bore callout (`⌀18 THRU ⊔ ⌀30 ↓14`), a `4× ⌀8 THRU` hole-pattern callout,
|
|
709
|
+
Section A–A with ISO hatching, an isometric, and an ISO 7200 title block — every
|
|
710
|
+
annotation placed by the engine.*
|
|
711
|
+
|
|
712
|
+
## Quick start
|
|
713
|
+
|
|
714
|
+
```
|
|
715
|
+
pip install draftwright
|
|
716
|
+
```
|
|
717
|
+
|
|
718
|
+
### Command line
|
|
719
|
+
|
|
720
|
+
Point it at a STEP file — that's the whole workflow:
|
|
721
|
+
|
|
722
|
+
```
|
|
723
|
+
draftwright my_part.step --title "Mounting Plate" --number DWG-001
|
|
724
|
+
# writes my_part.pdf (the default)
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
Choose formats, scale, and page; or emit an editable drawing script:
|
|
728
|
+
|
|
729
|
+
```
|
|
730
|
+
draftwright my_part.step --format pdf,dxf # also: svg, all
|
|
731
|
+
draftwright my_part.step --scale 2 --page A3 # override the auto scale / page
|
|
732
|
+
draftwright my_part.step --script # write an editable .py drawing script
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
`draftwright --help` lists every flag; `--version` prints the version.
|
|
736
|
+
|
|
737
|
+
### Python
|
|
738
|
+
|
|
739
|
+
One call turns a build123d solid (or a STEP file) into a drawing. This is the exact
|
|
740
|
+
part in the image above:
|
|
704
741
|
|
|
705
742
|
```python
|
|
706
743
|
from build123d import Box, Cylinder, Pos
|
|
707
744
|
from draftwright import make_drawing
|
|
708
745
|
|
|
709
|
-
part =
|
|
710
|
-
|
|
711
|
-
#
|
|
746
|
+
part = (
|
|
747
|
+
Box(100, 70, 24)
|
|
748
|
+
- Pos(0, 0, 0) * Cylinder(9, 40) # central bore, counterbored below
|
|
749
|
+
- Pos(0, 0, 8) * Cylinder(15, 20) # counterbore → triggers Section A–A
|
|
750
|
+
- Pos(-38, 24, 0) * Cylinder(4, 40) # 4× corner holes (recognised as a pattern)
|
|
751
|
+
- Pos(38, 24, 0) * Cylinder(4, 40)
|
|
752
|
+
- Pos(-38, -24, 0) * Cylinder(4, 40)
|
|
753
|
+
- Pos(38, -24, 0) * Cylinder(4, 40)
|
|
754
|
+
)
|
|
755
|
+
make_drawing(part, out="my_part", title="Mounting Plate", number="DWG-001")
|
|
756
|
+
# writes my_part.svg and my_part.dxf (or from a STEP file: make_drawing(step_file="p.step", out="my_part"))
|
|
712
757
|
```
|
|
713
758
|
|
|
714
|
-
|
|
759
|
+
For inspection and editing, get a composable `Drawing`:
|
|
715
760
|
|
|
716
|
-
```
|
|
717
|
-
draftwright
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
761
|
+
```python
|
|
762
|
+
from draftwright import build_drawing
|
|
763
|
+
|
|
764
|
+
dwg = build_drawing(part, title="Mounting Plate")
|
|
765
|
+
issues = dwg.lint() # list[LintIssue] — coverage, page bounds, ISO
|
|
766
|
+
svg_path, dxf_path = dwg.export("my_part")
|
|
721
767
|
```
|
|
722
768
|
|
|
723
769
|
## What it produces
|
|
@@ -734,48 +780,14 @@ draftwright my_part.step --format all # PDF + SVG + DXF
|
|
|
734
780
|
- **Lint** — `Drawing.lint()` checks annotation coverage, page bounds, and ISO compliance
|
|
735
781
|
and returns structured `LintIssue` objects
|
|
736
782
|
|
|
737
|
-
All output is real build123d geometry, so SVG and DXF
|
|
738
|
-
and dimensions are live on the DXF layer.
|
|
739
|
-
|
|
740
|
-
## Installation
|
|
741
|
-
|
|
742
|
-
```
|
|
743
|
-
pip install draftwright
|
|
744
|
-
```
|
|
783
|
+
All output is real build123d geometry, so PDF, SVG, and DXF all come from the same
|
|
784
|
+
source and dimensions are live on the DXF layer.
|
|
745
785
|
|
|
746
786
|
Requires Python ≥ 3.10 and build123d ≥ 0.9.0. Annotation primitives are provided by
|
|
747
787
|
[`build123d-drafting-helpers`](https://github.com/pzfreo/build123d-drafting-helpers),
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
## Usage
|
|
788
|
+
installed automatically as a dependency.
|
|
751
789
|
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
```python
|
|
755
|
-
from draftwright import make_drawing, build_drawing, Drawing
|
|
756
|
-
|
|
757
|
-
# One-shot: write SVG + DXF
|
|
758
|
-
make_drawing(part, out="drawing", title="My Part", number="DWG-001")
|
|
759
|
-
|
|
760
|
-
# Composable: get a Drawing object to inspect or extend
|
|
761
|
-
dwg = build_drawing(part, title="My Part")
|
|
762
|
-
issues = dwg.lint() # list[LintIssue]
|
|
763
|
-
svg_path, dxf_path = dwg.export("drawing")
|
|
764
|
-
```
|
|
765
|
-
|
|
766
|
-
### From a STEP file
|
|
767
|
-
|
|
768
|
-
```python
|
|
769
|
-
from draftwright import make_drawing
|
|
770
|
-
make_drawing(step_file="part.step", out="drawing")
|
|
771
|
-
```
|
|
772
|
-
|
|
773
|
-
Or via the CLI:
|
|
774
|
-
|
|
775
|
-
```
|
|
776
|
-
draftwright part.step --out drawing --scale 2 --page A3
|
|
777
|
-
draftwright part.step --script # write an editable .py drawing script instead
|
|
778
|
-
```
|
|
790
|
+
## Going further
|
|
779
791
|
|
|
780
792
|
### Scale and page control
|
|
781
793
|
|
|
@@ -833,3 +845,5 @@ build → plan → render). See
|
|
|
833
845
|
[`docs/target-architecture.md`](docs/target-architecture.md) and
|
|
834
846
|
[`docs/adr/`](docs/adr/). The engine handles view layout (strip/zone model), scale
|
|
835
847
|
selection, annotation placement, and section rendering.
|
|
848
|
+
|
|
849
|
+
The codebase has been entirely written by LLM (Claude Code) under detailed guidance from a person.
|
|
@@ -9,24 +9,70 @@
|
|
|
9
9
|
Automated technical-drawing generation for [build123d](https://github.com/gumyr/build123d).
|
|
10
10
|
Point it at a solid (or a STEP file) and get a fully-annotated multi-view engineering
|
|
11
11
|
drawing — orthographic views, dimensions, section A–A, ISO hatching, title block — ready
|
|
12
|
-
to export as SVG and DXF.
|
|
12
|
+
to export as PDF, SVG, and DXF.
|
|
13
|
+
|
|
14
|
+

|
|
15
|
+
|
|
16
|
+
*A mounting plate, generated automatically: three dimensioned orthographic views, a
|
|
17
|
+
counterbored bore callout (`⌀18 THRU ⊔ ⌀30 ↓14`), a `4× ⌀8 THRU` hole-pattern callout,
|
|
18
|
+
Section A–A with ISO hatching, an isometric, and an ISO 7200 title block — every
|
|
19
|
+
annotation placed by the engine.*
|
|
20
|
+
|
|
21
|
+
## Quick start
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
pip install draftwright
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Command line
|
|
28
|
+
|
|
29
|
+
Point it at a STEP file — that's the whole workflow:
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
draftwright my_part.step --title "Mounting Plate" --number DWG-001
|
|
33
|
+
# writes my_part.pdf (the default)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Choose formats, scale, and page; or emit an editable drawing script:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
draftwright my_part.step --format pdf,dxf # also: svg, all
|
|
40
|
+
draftwright my_part.step --scale 2 --page A3 # override the auto scale / page
|
|
41
|
+
draftwright my_part.step --script # write an editable .py drawing script
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`draftwright --help` lists every flag; `--version` prints the version.
|
|
45
|
+
|
|
46
|
+
### Python
|
|
47
|
+
|
|
48
|
+
One call turns a build123d solid (or a STEP file) into a drawing. This is the exact
|
|
49
|
+
part in the image above:
|
|
13
50
|
|
|
14
51
|
```python
|
|
15
52
|
from build123d import Box, Cylinder, Pos
|
|
16
53
|
from draftwright import make_drawing
|
|
17
54
|
|
|
18
|
-
part =
|
|
19
|
-
|
|
20
|
-
#
|
|
55
|
+
part = (
|
|
56
|
+
Box(100, 70, 24)
|
|
57
|
+
- Pos(0, 0, 0) * Cylinder(9, 40) # central bore, counterbored below
|
|
58
|
+
- Pos(0, 0, 8) * Cylinder(15, 20) # counterbore → triggers Section A–A
|
|
59
|
+
- Pos(-38, 24, 0) * Cylinder(4, 40) # 4× corner holes (recognised as a pattern)
|
|
60
|
+
- Pos(38, 24, 0) * Cylinder(4, 40)
|
|
61
|
+
- Pos(-38, -24, 0) * Cylinder(4, 40)
|
|
62
|
+
- Pos(38, -24, 0) * Cylinder(4, 40)
|
|
63
|
+
)
|
|
64
|
+
make_drawing(part, out="my_part", title="Mounting Plate", number="DWG-001")
|
|
65
|
+
# writes my_part.svg and my_part.dxf (or from a STEP file: make_drawing(step_file="p.step", out="my_part"))
|
|
21
66
|
```
|
|
22
67
|
|
|
23
|
-
|
|
68
|
+
For inspection and editing, get a composable `Drawing`:
|
|
24
69
|
|
|
25
|
-
```
|
|
26
|
-
draftwright
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
70
|
+
```python
|
|
71
|
+
from draftwright import build_drawing
|
|
72
|
+
|
|
73
|
+
dwg = build_drawing(part, title="Mounting Plate")
|
|
74
|
+
issues = dwg.lint() # list[LintIssue] — coverage, page bounds, ISO
|
|
75
|
+
svg_path, dxf_path = dwg.export("my_part")
|
|
30
76
|
```
|
|
31
77
|
|
|
32
78
|
## What it produces
|
|
@@ -43,48 +89,14 @@ draftwright my_part.step --format all # PDF + SVG + DXF
|
|
|
43
89
|
- **Lint** — `Drawing.lint()` checks annotation coverage, page bounds, and ISO compliance
|
|
44
90
|
and returns structured `LintIssue` objects
|
|
45
91
|
|
|
46
|
-
All output is real build123d geometry, so SVG and DXF
|
|
47
|
-
and dimensions are live on the DXF layer.
|
|
48
|
-
|
|
49
|
-
## Installation
|
|
50
|
-
|
|
51
|
-
```
|
|
52
|
-
pip install draftwright
|
|
53
|
-
```
|
|
92
|
+
All output is real build123d geometry, so PDF, SVG, and DXF all come from the same
|
|
93
|
+
source and dimensions are live on the DXF layer.
|
|
54
94
|
|
|
55
95
|
Requires Python ≥ 3.10 and build123d ≥ 0.9.0. Annotation primitives are provided by
|
|
56
96
|
[`build123d-drafting-helpers`](https://github.com/pzfreo/build123d-drafting-helpers),
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
## Usage
|
|
97
|
+
installed automatically as a dependency.
|
|
60
98
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
```python
|
|
64
|
-
from draftwright import make_drawing, build_drawing, Drawing
|
|
65
|
-
|
|
66
|
-
# One-shot: write SVG + DXF
|
|
67
|
-
make_drawing(part, out="drawing", title="My Part", number="DWG-001")
|
|
68
|
-
|
|
69
|
-
# Composable: get a Drawing object to inspect or extend
|
|
70
|
-
dwg = build_drawing(part, title="My Part")
|
|
71
|
-
issues = dwg.lint() # list[LintIssue]
|
|
72
|
-
svg_path, dxf_path = dwg.export("drawing")
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
### From a STEP file
|
|
76
|
-
|
|
77
|
-
```python
|
|
78
|
-
from draftwright import make_drawing
|
|
79
|
-
make_drawing(step_file="part.step", out="drawing")
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
Or via the CLI:
|
|
83
|
-
|
|
84
|
-
```
|
|
85
|
-
draftwright part.step --out drawing --scale 2 --page A3
|
|
86
|
-
draftwright part.step --script # write an editable .py drawing script instead
|
|
87
|
-
```
|
|
99
|
+
## Going further
|
|
88
100
|
|
|
89
101
|
### Scale and page control
|
|
90
102
|
|
|
@@ -142,3 +154,5 @@ build → plan → render). See
|
|
|
142
154
|
[`docs/target-architecture.md`](docs/target-architecture.md) and
|
|
143
155
|
[`docs/adr/`](docs/adr/). The engine handles view layout (strip/zone model), scale
|
|
144
156
|
selection, annotation placement, and section rendering.
|
|
157
|
+
|
|
158
|
+
The codebase has been entirely written by LLM (Claude Code) under detailed guidance from a person.
|
|
@@ -14,7 +14,7 @@ import functools
|
|
|
14
14
|
import logging
|
|
15
15
|
import re
|
|
16
16
|
from collections.abc import Callable
|
|
17
|
-
from dataclasses import dataclass
|
|
17
|
+
from dataclasses import dataclass
|
|
18
18
|
from pathlib import Path
|
|
19
19
|
from types import SimpleNamespace
|
|
20
20
|
from typing import TYPE_CHECKING
|
|
@@ -166,16 +166,17 @@ def _dim(p1, p2, side, distance, draft, **kwargs):
|
|
|
166
166
|
class Strip:
|
|
167
167
|
"""A one-dimensional annotation band adjacent to an orthographic view.
|
|
168
168
|
|
|
169
|
-
|
|
170
|
-
:
|
|
171
|
-
|
|
169
|
+
A plain geometry record: the collect-then-solve placers (ADR 0009) read its
|
|
170
|
+
bounds (:func:`~draftwright.annotations._common.strip_free_span`) and carve
|
|
171
|
+
around the placed annotations. The mutable ``allocate``/``peek`` cursor was
|
|
172
|
+
retired once every placer moved to the carve (#150).
|
|
172
173
|
|
|
173
174
|
Attributes:
|
|
174
175
|
anchor: Page coordinate of the view edge this strip starts from.
|
|
175
176
|
outer_limit: Page coordinate at which the strip ends (page margin,
|
|
176
177
|
neighbouring view, or title-block boundary).
|
|
177
|
-
direction: ``+1`` —
|
|
178
|
-
``-1`` —
|
|
178
|
+
direction: ``+1`` — stacks away from anchor (right/above);
|
|
179
|
+
``-1`` — stacks back toward smaller coords (left/below).
|
|
179
180
|
gap: Clearance between the view edge and the first annotation.
|
|
180
181
|
spacing: Clearance between successive annotations.
|
|
181
182
|
"""
|
|
@@ -185,56 +186,12 @@ class Strip:
|
|
|
185
186
|
direction: float = 1.0
|
|
186
187
|
gap: float = 8.0
|
|
187
188
|
spacing: float = 4.0
|
|
188
|
-
_cursor: float = field(init=False, compare=False, repr=False)
|
|
189
|
-
|
|
190
|
-
def __post_init__(self):
|
|
191
|
-
self._cursor = self.anchor + self.direction * self.gap
|
|
192
|
-
|
|
193
|
-
# ------------------------------------------------------------------
|
|
194
|
-
# Public API
|
|
195
189
|
|
|
196
190
|
@property
|
|
197
191
|
def available(self) -> float:
|
|
198
192
|
"""Total space available in this strip (mm)."""
|
|
199
193
|
return abs(self.outer_limit - self.anchor)
|
|
200
194
|
|
|
201
|
-
@property
|
|
202
|
-
def depth_used(self) -> float:
|
|
203
|
-
"""How far the cursor has advanced from the anchor (mm)."""
|
|
204
|
-
return abs(self._cursor - self.anchor)
|
|
205
|
-
|
|
206
|
-
def peek(self, size: float) -> float | None:
|
|
207
|
-
"""Return what ``allocate(size)`` would return without advancing the cursor."""
|
|
208
|
-
if self.direction == 1:
|
|
209
|
-
start = self._cursor
|
|
210
|
-
return start if (start + size) <= self.outer_limit else None
|
|
211
|
-
else:
|
|
212
|
-
end = self._cursor
|
|
213
|
-
return end if (end - size) >= self.outer_limit else None
|
|
214
|
-
|
|
215
|
-
def allocate(self, size: float) -> float | None:
|
|
216
|
-
"""Reserve *size* mm; return the near-edge page coordinate, or ``None`` if full.
|
|
217
|
-
|
|
218
|
-
The returned value is the page coordinate of the annotation's
|
|
219
|
-
dimension line (or leader elbow). Convert to a relative offset with::
|
|
220
|
-
|
|
221
|
-
distance = abs(page_coord - strip.anchor)
|
|
222
|
-
"""
|
|
223
|
-
if self.direction == 1:
|
|
224
|
-
start = self._cursor
|
|
225
|
-
end = start + size
|
|
226
|
-
if end > self.outer_limit:
|
|
227
|
-
return None
|
|
228
|
-
self._cursor = end + self.spacing
|
|
229
|
-
return start
|
|
230
|
-
else:
|
|
231
|
-
end = self._cursor
|
|
232
|
-
start = end - size
|
|
233
|
-
if start < self.outer_limit:
|
|
234
|
-
return None
|
|
235
|
-
self._cursor = start - self.spacing
|
|
236
|
-
return end
|
|
237
|
-
|
|
238
195
|
|
|
239
196
|
@dataclass
|
|
240
197
|
class ViewZones:
|
|
@@ -626,7 +583,11 @@ _PAGE_SIZES = {
|
|
|
626
583
|
"A0": (1189.0, 841.0),
|
|
627
584
|
}
|
|
628
585
|
|
|
629
|
-
|
|
586
|
+
# ISO 5455 scale series (1-2-5 decades). Enlargements + 1:1 first, then reductions
|
|
587
|
+
# down to 1:10000 so a very large part still gets a scale that FITS rather than an
|
|
588
|
+
# overflowing layout (#350). Ordered largest-scale-first for "least reduction first".
|
|
589
|
+
_SCALES = [10.0, 5.0, 2.0, 1.0]
|
|
590
|
+
_SCALES += [0.5, 0.2, 0.1, 0.05, 0.02, 0.01, 0.005, 0.002, 0.001, 0.0005, 0.0002, 0.0001]
|
|
630
591
|
|
|
631
592
|
# Horizontal page budget to reserve for the isometric view during scale
|
|
632
593
|
# selection and view placement, as a fraction of bbox_max * scale. This is a
|
|
@@ -677,6 +638,19 @@ _LADDER = [
|
|
|
677
638
|
(0.2, 841.0, 594.0, 150.0), # A1 1:5
|
|
678
639
|
(0.5, 1189.0, 841.0, 150.0), # A0 1:2
|
|
679
640
|
(0.2, 1189.0, 841.0, 150.0), # A0 1:5
|
|
641
|
+
# Past 1:5 keep reducing on A0 (the largest sheet) through the rest of the ISO 5455
|
|
642
|
+
# series, so a part too big for A0 1:5 still gets a scale that FITS rather than an
|
|
643
|
+
# overflowing layout (#350). A0 1:10000 holds anything up to ~8.4 m of drawn height.
|
|
644
|
+
(0.1, 1189.0, 841.0, 150.0), # A0 1:10
|
|
645
|
+
(0.05, 1189.0, 841.0, 150.0), # A0 1:20
|
|
646
|
+
(0.02, 1189.0, 841.0, 150.0), # A0 1:50
|
|
647
|
+
(0.01, 1189.0, 841.0, 150.0), # A0 1:100
|
|
648
|
+
(0.005, 1189.0, 841.0, 150.0), # A0 1:200
|
|
649
|
+
(0.002, 1189.0, 841.0, 150.0), # A0 1:500
|
|
650
|
+
(0.001, 1189.0, 841.0, 150.0), # A0 1:1000
|
|
651
|
+
(0.0005, 1189.0, 841.0, 150.0), # A0 1:2000
|
|
652
|
+
(0.0002, 1189.0, 841.0, 150.0), # A0 1:5000
|
|
653
|
+
(0.0001, 1189.0, 841.0, 150.0), # A0 1:10000
|
|
680
654
|
]
|
|
681
655
|
|
|
682
656
|
|