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.
Files changed (87) hide show
  1. {draftwright-0.2.2 → draftwright-0.2.4}/CHANGELOG.md +78 -0
  2. {draftwright-0.2.2 → draftwright-0.2.4}/PKG-INFO +63 -49
  3. {draftwright-0.2.2 → draftwright-0.2.4}/README.md +62 -48
  4. {draftwright-0.2.2 → draftwright-0.2.4}/pyproject.toml +1 -1
  5. {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/_core.py +25 -51
  6. draftwright-0.2.4/src/draftwright/annotations/_common.py +540 -0
  7. {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/annotations/from_model.py +367 -116
  8. {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/annotations/holes.py +400 -204
  9. {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/annotations/orchestrator.py +160 -97
  10. {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/annotations/sections.py +121 -24
  11. {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/builder.py +34 -9
  12. {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/drawing.py +79 -37
  13. draftwright-0.2.4/src/draftwright/layout.py +676 -0
  14. {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/linting/coverage.py +15 -0
  15. {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/registry.py +14 -3
  16. {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/repair.py +2 -2
  17. {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/sheet.py +47 -0
  18. draftwright-0.2.4/tests/layout_snapshots/box.json +116 -0
  19. draftwright-0.2.4/tests/layout_snapshots/bracket.json +388 -0
  20. draftwright-0.2.4/tests/layout_snapshots/drive_screw_x.json +155 -0
  21. draftwright-0.2.4/tests/layout_snapshots/dshape.json +201 -0
  22. draftwright-0.2.4/tests/layout_snapshots/flange.json +287 -0
  23. draftwright-0.2.4/tests/layout_snapshots/holed_slot.json +299 -0
  24. draftwright-0.2.4/tests/layout_snapshots/plate_holes.json +258 -0
  25. draftwright-0.2.4/tests/layout_snapshots/side_drilled.json +183 -0
  26. draftwright-0.2.4/tests/layout_snapshots/slotted.json +170 -0
  27. draftwright-0.2.4/tests/layout_snapshots/turned_shaft.json +196 -0
  28. {draftwright-0.2.2 → draftwright-0.2.4}/tests/test_layout.py +176 -4
  29. draftwright-0.2.4/tests/test_layout_cleanliness.py +192 -0
  30. draftwright-0.2.4/tests/test_layout_property.py +201 -0
  31. draftwright-0.2.4/tests/test_layout_snapshot.py +231 -0
  32. {draftwright-0.2.2 → draftwright-0.2.4}/tests/test_make_drawing.py +213 -91
  33. {draftwright-0.2.2 → draftwright-0.2.4}/tests/test_pmi.py +19 -17
  34. {draftwright-0.2.2 → draftwright-0.2.4}/tests/test_registry.py +28 -0
  35. draftwright-0.2.4/tests/test_render_seam.py +200 -0
  36. draftwright-0.2.4/tests/test_strip_layout.py +768 -0
  37. draftwright-0.2.2/src/draftwright/annotations/_common.py +0 -51
  38. draftwright-0.2.2/src/draftwright/layout.py +0 -317
  39. draftwright-0.2.2/tests/test_render_seam.py +0 -63
  40. {draftwright-0.2.2 → draftwright-0.2.4}/.gitignore +0 -0
  41. {draftwright-0.2.2 → draftwright-0.2.4}/LICENSE +0 -0
  42. {draftwright-0.2.2 → draftwright-0.2.4}/skills/SKILL.md +0 -0
  43. {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/__init__.py +0 -0
  44. {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/analysis.py +0 -0
  45. {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/annotate.py +0 -0
  46. {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/annotations/__init__.py +0 -0
  47. {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/cli.py +0 -0
  48. {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/export.py +0 -0
  49. {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/features.py +0 -0
  50. {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/fonts/IBMPlexMono-Regular.ttf +0 -0
  51. {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/fonts/IBMPlexSansCondensed-Regular.ttf +0 -0
  52. {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/fonts/LICENSE-IBMPlexMono-OFL.txt +0 -0
  53. {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/fonts/LICENSE-IBMPlexSansCondensed-OFL.txt +0 -0
  54. {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/fonts/__init__.py +0 -0
  55. {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/linting/__init__.py +0 -0
  56. {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/linting/issues.py +0 -0
  57. {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/linting/structural.py +0 -0
  58. {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/linting/suggest.py +0 -0
  59. {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/make_drawing.py +0 -0
  60. {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/model/__init__.py +0 -0
  61. {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/model/detect.py +0 -0
  62. {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/model/ir.py +0 -0
  63. {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/model/planner.py +0 -0
  64. {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/pmi.py +0 -0
  65. {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/projection.py +0 -0
  66. {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/recognition/__init__.py +0 -0
  67. {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/recognition/_features.py +0 -0
  68. {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/recognition/levels.py +0 -0
  69. {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/recognition/slots.py +0 -0
  70. {draftwright-0.2.2 → draftwright-0.2.4}/src/draftwright/recognition/turned.py +0 -0
  71. {draftwright-0.2.2 → draftwright-0.2.4}/tests/fixtures/nist_ctc_01_asme1_ap203.stp +0 -0
  72. {draftwright-0.2.2 → draftwright-0.2.4}/tests/fixtures/nist_ctc_01_asme1_ap242.stp +0 -0
  73. {draftwright-0.2.2 → draftwright-0.2.4}/tests/fixtures/nist_ctc_02_asme1_ap203.stp +0 -0
  74. {draftwright-0.2.2 → draftwright-0.2.4}/tests/fixtures/nist_ctc_02_asme1_ap242.stp +0 -0
  75. {draftwright-0.2.2 → draftwright-0.2.4}/tests/fixtures/nist_ctc_03_asme1_ap203.stp +0 -0
  76. {draftwright-0.2.2 → draftwright-0.2.4}/tests/fixtures/nist_ctc_03_asme1_ap242.stp +0 -0
  77. {draftwright-0.2.2 → draftwright-0.2.4}/tests/fixtures/nist_ctc_04_asme1_ap203.stp +0 -0
  78. {draftwright-0.2.2 → draftwright-0.2.4}/tests/fixtures/nist_ctc_04_asme1_ap242.stp +0 -0
  79. {draftwright-0.2.2 → draftwright-0.2.4}/tests/fixtures/nist_ctc_05_asme1_ap203.stp +0 -0
  80. {draftwright-0.2.2 → draftwright-0.2.4}/tests/fixtures/nist_ctc_05_asme1_ap242.stp +0 -0
  81. {draftwright-0.2.2 → draftwright-0.2.4}/tests/test_e2e_slice.py +0 -0
  82. {draftwright-0.2.2 → draftwright-0.2.4}/tests/test_e2e_standards.py +0 -0
  83. {draftwright-0.2.2 → draftwright-0.2.4}/tests/test_lint_structural.py +0 -0
  84. {draftwright-0.2.2 → draftwright-0.2.4}/tests/test_linting.py +0 -0
  85. {draftwright-0.2.2 → draftwright-0.2.4}/tests/test_part_model.py +0 -0
  86. {draftwright-0.2.2 → draftwright-0.2.4}/tests/test_recognition.py +0 -0
  87. {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.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
+ ![Example engineering drawing generated by draftwright](docs/images/example.png)
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 = Box(80, 60, 20) - Pos(0, 0, 5) * Cylinder(8, 20)
710
- make_drawing(part, out="my_part", title="Mounting Block", number="DWG-001")
711
- # writes my_part.svg and my_part.dxf
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
- Or from the command line:
759
+ For inspection and editing, get a composable `Drawing`:
715
760
 
716
- ```
717
- draftwright my_part.step --title "Mounting Block" --number DWG-001
718
- # writes my_part.pdf (the default); add --format to choose:
719
- draftwright my_part.step --format pdf,dxf # PDF + DXF
720
- draftwright my_part.step --format all # PDF + SVG + DXF
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 export come from the same source
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
- which is installed automatically as a dependency.
749
-
750
- ## Usage
788
+ installed automatically as a dependency.
751
789
 
752
- ### From a build123d solid
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
+ ![Example engineering drawing generated by draftwright](docs/images/example.png)
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 = Box(80, 60, 20) - Pos(0, 0, 5) * Cylinder(8, 20)
19
- make_drawing(part, out="my_part", title="Mounting Block", number="DWG-001")
20
- # writes my_part.svg and my_part.dxf
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
- Or from the command line:
68
+ For inspection and editing, get a composable `Drawing`:
24
69
 
25
- ```
26
- draftwright my_part.step --title "Mounting Block" --number DWG-001
27
- # writes my_part.pdf (the default); add --format to choose:
28
- draftwright my_part.step --format pdf,dxf # PDF + DXF
29
- draftwright my_part.step --format all # PDF + SVG + DXF
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 export come from the same source
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
- which is installed automatically as a dependency.
58
-
59
- ## Usage
97
+ installed automatically as a dependency.
60
98
 
61
- ### From a build123d solid
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.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "draftwright"
7
- version = "0.2.2"
7
+ version = "0.2.4"
8
8
  description = "Automated technical-drawing generation for build123d"
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
@@ -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, field
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
- Annotations are stacked outward from the view edge by calling
170
- :meth:`allocate`. The cursor starts at ``anchor + direction * gap`` and
171
- advances after each successful allocation.
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`` — cursor moves away from anchor (right/above);
178
- ``-1`` — cursor retreats from anchor (left/below).
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
- _SCALES = [10.0, 5.0, 2.0, 1.0, 0.5, 0.2]
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