draftwright 0.1.8__tar.gz → 0.1.10__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 (26) hide show
  1. {draftwright-0.1.8 → draftwright-0.1.10}/CHANGELOG.md +83 -0
  2. {draftwright-0.1.8 → draftwright-0.1.10}/PKG-INFO +21 -9
  3. {draftwright-0.1.8 → draftwright-0.1.10}/README.md +18 -7
  4. {draftwright-0.1.8 → draftwright-0.1.10}/pyproject.toml +3 -2
  5. draftwright-0.1.10/skills/SKILL.md +273 -0
  6. {draftwright-0.1.8 → draftwright-0.1.10}/src/draftwright/__init__.py +2 -0
  7. draftwright-0.1.10/src/draftwright/layout.py +317 -0
  8. {draftwright-0.1.8 → draftwright-0.1.10}/src/draftwright/make_drawing.py +1350 -127
  9. {draftwright-0.1.8 → draftwright-0.1.10}/tests/test_e2e_standards.py +4 -4
  10. draftwright-0.1.10/tests/test_layout.py +254 -0
  11. {draftwright-0.1.8 → draftwright-0.1.10}/tests/test_make_drawing.py +1077 -30
  12. draftwright-0.1.8/skills/SKILL.md +0 -180
  13. {draftwright-0.1.8 → draftwright-0.1.10}/.gitignore +0 -0
  14. {draftwright-0.1.8 → draftwright-0.1.10}/LICENSE +0 -0
  15. {draftwright-0.1.8 → draftwright-0.1.10}/src/draftwright/pmi.py +0 -0
  16. {draftwright-0.1.8 → draftwright-0.1.10}/tests/fixtures/nist_ctc_01_asme1_ap203.stp +0 -0
  17. {draftwright-0.1.8 → draftwright-0.1.10}/tests/fixtures/nist_ctc_01_asme1_ap242.stp +0 -0
  18. {draftwright-0.1.8 → draftwright-0.1.10}/tests/fixtures/nist_ctc_02_asme1_ap203.stp +0 -0
  19. {draftwright-0.1.8 → draftwright-0.1.10}/tests/fixtures/nist_ctc_02_asme1_ap242.stp +0 -0
  20. {draftwright-0.1.8 → draftwright-0.1.10}/tests/fixtures/nist_ctc_03_asme1_ap203.stp +0 -0
  21. {draftwright-0.1.8 → draftwright-0.1.10}/tests/fixtures/nist_ctc_03_asme1_ap242.stp +0 -0
  22. {draftwright-0.1.8 → draftwright-0.1.10}/tests/fixtures/nist_ctc_04_asme1_ap203.stp +0 -0
  23. {draftwright-0.1.8 → draftwright-0.1.10}/tests/fixtures/nist_ctc_04_asme1_ap242.stp +0 -0
  24. {draftwright-0.1.8 → draftwright-0.1.10}/tests/fixtures/nist_ctc_05_asme1_ap203.stp +0 -0
  25. {draftwright-0.1.8 → draftwright-0.1.10}/tests/fixtures/nist_ctc_05_asme1_ap242.stp +0 -0
  26. {draftwright-0.1.8 → draftwright-0.1.10}/tests/test_pmi.py +0 -0
@@ -1,5 +1,88 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.1.10 — 2026-06-18
4
+
5
+ ### Added
6
+
7
+ - **Constraint-based layout engine (ADR 0003).** A new `draftwright.layout`
8
+ module with a `Placeable` protocol and a `LayoutSolver`: a 1D Cassowary strip
9
+ solver (`solve_strip`, with per-pair gaps) and a 2D free-rectangle placer
10
+ (`place_box` / `fit_box`) that positions a box in a free part of the page
11
+ clear of the views, title block, and existing annotations. Hole-callout and
12
+ turned-diameter placement now run on the solver. The engine grows per real
13
+ consumer; a monolithic global 2D solve is deferred (see the ADR).
14
+ - **Hole table + balloons (#93).** `dwg.add_table(rows)` places a generic data
15
+ table (gear data, BOM, revision block, …) in a free corner via `place_box`;
16
+ `dwg.add_hole_table(view)` builds a hole chart from the detected holes with a
17
+ circled balloon tag at each hole. A **too-dense plan view now auto-escalates**:
18
+ a part the layout cannot legibly dimension hole-by-hole is replaced by a
19
+ complete per-instance hole chart (`TAG | ⌀ | X | Y`, datum-relative) plus
20
+ balloons, instead of silently dropping callouts and location dims. The chart
21
+ wraps into multiple column-blocks to fit the page.
22
+ - **External turned diameters (#77).** A turned part lying along the X axis now
23
+ gets ø leader-callouts for its external stepped diameters, with thread/worm
24
+ patches collapsed into a single boss.
25
+ - **Pin / manual override (#89).** `dwg.pin(name)` / `dwg.unpin(name)` fix an
26
+ annotation's position so `repair()` — and the layout engine — never move it; a
27
+ deliberate (human or AI) placement wins over automatic layout.
28
+
29
+ ### Changed
30
+
31
+ - Hole-callout and turned-diameter placement is deconflicted through the shared
32
+ `LayoutSolver` instead of ad-hoc per-pass logic (no output change).
33
+
34
+ ### Docs
35
+
36
+ - The skill and generated-script header now lead with the domain API
37
+ (`features` / `place_dim` / `repair` / `lint_summary`) and the
38
+ build → critique → fix loop. ADR 0003 records the layout architecture; ADRs
39
+ 0001/0002 remain the editing-model and lint→repair foundations.
40
+
41
+ ## v0.1.9 — 2026-06-16
42
+
43
+ ### Added
44
+
45
+ - **Domain-semantic editing API.** `dwg.features(view)` returns detected holes
46
+ and features grouped by machining spec in page coordinates, and
47
+ `dwg.place_dim(p1, p2, side, view, draft, name=…)` places a dimension from
48
+ domain inputs — the vocabulary a script (or an AI assistant) needs to edit a
49
+ drawing without hand-computing page geometry (#25, #26).
50
+ - **`dwg.annotations()` and `dwg.get_annotation(name)`.** Introspect what is
51
+ already on the drawing — a `{name: type}` map and a name lookup — so a script
52
+ can make incremental edits without risking a silent name-collision replace
53
+ (#27).
54
+ - **`dwg.view_bounds(view)`.** Returns `(x_min, y_min, x_max, y_max)`, the page
55
+ bounding box of a view's projected geometry (or `None` for an unknown view),
56
+ so free-form notes and leader elbows can be placed just outside a view without
57
+ guessing offsets from `dwg.at()` (#28).
58
+ - **Lint findings carry a suggested fix.** Each repairable lint issue now
59
+ includes a ready-to-run domain-API call snippet, so acting on a finding is one
60
+ copy-paste away (#29).
61
+ - **Lint→repair loop.** `Drawing.repair()` — run by default in `build_drawing` —
62
+ mechanically resolves the lint codes that have a deterministic placement fix:
63
+ overlapping labels are pushed apart and wrong-side dimensions are flipped. A
64
+ pass that would net-increase the issue count is rolled back, so repair never
65
+ makes a drawing worse (#30).
66
+ - **TYP / representative dimensioning for uniform step patterns.** A run of
67
+ equal-rise, equal-going steps is dimensioned once and labelled representative
68
+ (TYP) instead of repeating identical dimensions down the ladder (#45).
69
+ - **Enlarged detail view for crowded step clusters (MVP).** When shoulders are
70
+ too closely spaced to dimension legibly at sheet scale, an opt-in
71
+ (`detail_view=True`) detail view re-draws them at a larger scale (#42).
72
+
73
+ ### Changed
74
+
75
+ - **BREAKING: the annotation list `dwg.annotations` is renamed to `dwg.items`.**
76
+ `dwg.annotations` is now a method (see Added); the ordered, mutable list of
77
+ annotation objects it used to be is now `dwg.items`. Pre-1.0 with no published
78
+ consumers, so the clearer name was taken now rather than spelling the new query
79
+ method awkwardly (#27).
80
+
81
+ ### Documentation
82
+
83
+ - ADRs 0001 (editing model) and 0002 (iteration loop) record the design
84
+ direction behind the domain API and the lint→repair loop (#51).
85
+
3
86
  ## v0.1.8 — 2026-06-16
4
87
 
5
88
  ### Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: draftwright
3
- Version: 0.1.8
3
+ Version: 0.1.10
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
@@ -680,9 +680,10 @@ Classifier: Programming Language :: Python :: 3.11
680
680
  Classifier: Programming Language :: Python :: 3.12
681
681
  Classifier: Topic :: Scientific/Engineering
682
682
  Requires-Python: >=3.10
683
- Requires-Dist: build123d-drafting-helpers>=0.10.0
683
+ Requires-Dist: build123d-drafting-helpers>=0.10.1
684
684
  Requires-Dist: build123d>=0.9.0
685
685
  Requires-Dist: kiwisolver<2,>=1.4
686
+ Requires-Dist: numpy>=1.24
686
687
  Provides-Extra: pdf
687
688
  Requires-Dist: cairosvg>=2.7; extra == 'pdf'
688
689
  Description-Content-Type: text/markdown
@@ -720,7 +721,8 @@ draftwright my_part.step --title "Mounting Block" --number DWG-001
720
721
  - **Three orthographic views** (front, plan, side) sized and scaled automatically to the
721
722
  page
722
723
  - **Dimensions** on every principal envelope face, plus bore callouts (diameter, depth,
723
- counterbore, spotface) on all holes
724
+ counterbore, spotface) on all holes, and ø leader-callouts for the external stepped
725
+ diameters of turned parts
724
726
  - **Section A–A** with ISO 128-44 solid filled cutting-plane arrows and ISO 128-50 45°
725
727
  hatching on the cut face, triggered automatically when blind or stepped holes would
726
728
  otherwise be hidden-line-only
@@ -784,17 +786,27 @@ scale, page_w, page_h, n_steps = choose_scale(80, 60, 20, page="A3")
784
786
  make_drawing(part, out="drawing", scale=2.0, page="A2")
785
787
  ```
786
788
 
787
- ### Lint and coverage
789
+ ### Edit, critique, and self-repair
788
790
 
789
- ```python
790
- from draftwright import build_drawing, lint_feature_coverage
791
+ Edit a `Drawing` in **domain vocabulary** — the engine places annotations
792
+ automatically, so you say *what* to dimension, not *where*:
791
793
 
794
+ ```python
792
795
  dwg = build_drawing(part)
793
- issues = dwg.lint() # geometry lint + feature-coverage check
794
- for i in issues:
795
- print(i.severity, i.message)
796
+
797
+ # Inspect detected features and add a dimension in domain terms (auto-placed):
798
+ for f in dwg.features("plan"):
799
+ dwg.place_dim(f.page_pos, (f.page_pos[0] + f.diameter, f.page_pos[1]),
800
+ "below", "plan", dwg.draft, name="dim_pocket")
801
+
802
+ crit = dwg.lint_summary() # {"passed", "score", "by_code", "issues":[…suggestion]}
803
+ dwg.repair() # auto-fix mechanically-fixable lint; never worsens
796
804
  ```
797
805
 
806
+ Each `LintIssue` carries a domain-meaningful `code` and, when computable, a
807
+ ready-to-apply `suggestion`. See `docs/adr/` for the design (deterministic
808
+ generation, the lint→repair loop, and the constraint-based layout engine).
809
+
798
810
  ## Architecture
799
811
 
800
812
  draftwright is a single module (`make_drawing.py`) on top of two Apache 2.0 libraries:
@@ -31,7 +31,8 @@ draftwright my_part.step --title "Mounting Block" --number DWG-001
31
31
  - **Three orthographic views** (front, plan, side) sized and scaled automatically to the
32
32
  page
33
33
  - **Dimensions** on every principal envelope face, plus bore callouts (diameter, depth,
34
- counterbore, spotface) on all holes
34
+ counterbore, spotface) on all holes, and ø leader-callouts for the external stepped
35
+ diameters of turned parts
35
36
  - **Section A–A** with ISO 128-44 solid filled cutting-plane arrows and ISO 128-50 45°
36
37
  hatching on the cut face, triggered automatically when blind or stepped holes would
37
38
  otherwise be hidden-line-only
@@ -95,17 +96,27 @@ scale, page_w, page_h, n_steps = choose_scale(80, 60, 20, page="A3")
95
96
  make_drawing(part, out="drawing", scale=2.0, page="A2")
96
97
  ```
97
98
 
98
- ### Lint and coverage
99
+ ### Edit, critique, and self-repair
99
100
 
100
- ```python
101
- from draftwright import build_drawing, lint_feature_coverage
101
+ Edit a `Drawing` in **domain vocabulary** — the engine places annotations
102
+ automatically, so you say *what* to dimension, not *where*:
102
103
 
104
+ ```python
103
105
  dwg = build_drawing(part)
104
- issues = dwg.lint() # geometry lint + feature-coverage check
105
- for i in issues:
106
- print(i.severity, i.message)
106
+
107
+ # Inspect detected features and add a dimension in domain terms (auto-placed):
108
+ for f in dwg.features("plan"):
109
+ dwg.place_dim(f.page_pos, (f.page_pos[0] + f.diameter, f.page_pos[1]),
110
+ "below", "plan", dwg.draft, name="dim_pocket")
111
+
112
+ crit = dwg.lint_summary() # {"passed", "score", "by_code", "issues":[…suggestion]}
113
+ dwg.repair() # auto-fix mechanically-fixable lint; never worsens
107
114
  ```
108
115
 
116
+ Each `LintIssue` carries a domain-meaningful `code` and, when computable, a
117
+ ready-to-apply `suggestion`. See `docs/adr/` for the design (deterministic
118
+ generation, the lint→repair loop, and the constraint-based layout engine).
119
+
109
120
  ## Architecture
110
121
 
111
122
  draftwright is a single module (`make_drawing.py`) on top of two Apache 2.0 libraries:
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "draftwright"
7
- version = "0.1.8"
7
+ version = "0.1.10"
8
8
  description = "Automated technical-drawing generation for build123d"
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
@@ -12,8 +12,9 @@ authors = [{ name = "Paul Fremantle", email = "pzfreo@gmail.com" }]
12
12
  requires-python = ">=3.10"
13
13
  dependencies = [
14
14
  "build123d>=0.9.0",
15
- "build123d-drafting-helpers>=0.10.0",
15
+ "build123d-drafting-helpers>=0.10.1",
16
16
  "kiwisolver>=1.4,<2",
17
+ "numpy>=1.24",
17
18
  ]
18
19
  keywords = ["build123d", "cad", "drafting", "engineering-drawing", "technical-drawing", "automation"]
19
20
  classifiers = [
@@ -0,0 +1,273 @@
1
+ # Automated Engineering Drawing with draftwright
2
+
3
+ Use this skill when asked to create an engineering drawing automatically from a
4
+ build123d solid or STEP file. It requires the **draftwright** package (AGPL-3.0)
5
+ to be installed in the execution environment.
6
+
7
+ > **License notice**: draftwright is AGPL-3.0. If you deploy code that uses it
8
+ > as part of a network service you must make your application source available.
9
+ > For Apache 2.0 annotation primitives only (`Dimension`, `Leader`, `TitleBlock`,
10
+ > etc.), use `build123d-drafting-helpers` instead without this obligation.
11
+
12
+ **There are two paths — start with the automatic one.**
13
+
14
+ 1. **Automatic (`make_drawing`)** — one call turns a part (or STEP file) into a
15
+ four-view SVG + DXF with dimensions, centrelines, and an ISO 7200 title
16
+ block.
17
+ 2. **Builder (`build_drawing`)** — the same pipeline, but it hands back a live
18
+ `Drawing` you can edit before export.
19
+
20
+ Requires `draftwright >= 0.1.9` and `build123d-drafting-helpers >= 0.10.1`.
21
+ Install: `pip install draftwright`.
22
+
23
+ **Design model (worth knowing before you edit):** the engine is *deterministic*
24
+ — no AI inside it — and you refine a drawing by **stating domain intent**
25
+ (dimension this feature, section through here) and letting the engine **place
26
+ everything automatically** (placement is constraint-based; you never compute page
27
+ coordinates). When the first pass isn't perfect, you drive a **build → critique
28
+ → fix** loop (Step "Lint → critique → fix" below), not a hand-layout edit. The
29
+ rationale lives in `docs/adr/`: 0001 (deterministic generation over an editable
30
+ DSL), 0002 (the lint critique → domain-repair loop), 0003 (the constraint-based
31
+ layout engine). Edit through the domain API; treat `Placeable`/page mechanics as
32
+ internals.
33
+
34
+ ---
35
+
36
+ ## Step 0 — Understand the part first
37
+
38
+ ```
39
+ mcp__build123d-mcp__execute — build the part in the session
40
+ mcp__build123d-mcp__measure — confirm volume, bbox, face count
41
+ mcp__build123d-mcp__render_view (save_to='/tmp/preview.png') — visual sanity check
42
+ ```
43
+
44
+ Register the part under a stable name with `show(part, "part")`.
45
+
46
+ ---
47
+
48
+ ## Step 1 — Generate the drawing automatically (start here)
49
+
50
+ ```python
51
+ from draftwright import make_drawing
52
+
53
+ svg, dxf = make_drawing(
54
+ part, # an in-session build123d object, OR a "path/to/part.step"
55
+ out="drawings/bracket", # output stem; ".svg"/".dxf" are appended
56
+ title="BRACKET", # ISO 7200 document title
57
+ number="DWG-042", # ISO 7200 document identifier
58
+ tolerance="ISO 2768-f", # general tolerance
59
+ drawn_by="Your Name",
60
+ )
61
+ ```
62
+
63
+ `make_drawing` chooses the scale + ISO page size, projects front/plan/side/iso
64
+ views, and annotates automatically — then lints and writes both SVG and DXF.
65
+
66
+ Automatic annotation covers **prismatic parts in full**: every recognised hole
67
+ gets a grouped callout ("4× ø10 THRU", counterbore/depth symbols), bolt circles
68
+ get "EQ SP ON øD BC" callouts with a pitch-circle centreline, linear arrays get
69
+ pitch dims, every hole gets a centre mark and baseline X/Y location dims from the
70
+ min-X/Y datum corner, and blind/counterbored holes trigger an automatic SECTION
71
+ A–A with ISO 128-44 solid filled arrows and ISO 128-50 45° hatching. Turned
72
+ parts get OD/length dims, centrelines, bore leaders, and — for parts turned
73
+ about a horizontal (X) axis — their external stepped diameters as ø
74
+ leader-callouts.
75
+
76
+ Then verify (Step 3). For most parts you are done here.
77
+
78
+ ---
79
+
80
+ ## Step 2 — Customise with the Drawing builder
81
+
82
+ `build_drawing(...)` returns a live `Drawing`. **Edit it in *domain* vocabulary
83
+ — locate things with `features()`, add dimensions with `place_dim()`, choose a
84
+ side and view.** You give *what* and *where on the part*; the engine decides the
85
+ *offset, stacking, and strip slot* (placement is automatic and constraint-based).
86
+ You still pass page-point endpoints, but you get them from `features()` or
87
+ `dwg.at(...)` — you never compute offsets or pick a strip. Hand-building a raw
88
+ `Leader` at `dwg.at(...)` coordinates is the escape hatch, not the default.
89
+
90
+ ```python
91
+ from draftwright import build_drawing
92
+
93
+ dwg = build_drawing(part, out="drawings/bracket", title="BRACKET",
94
+ number="DWG-042", tolerance="ISO 2768-f", drawn_by="Your Name")
95
+ ```
96
+
97
+ **Inspect what the engine found and placed (read APIs):**
98
+
99
+ ```python
100
+ dwg.features("plan") # detected features in a view → [FeatureInfo(...)]
101
+ # each: .type .diameter .through .depth .count .page_pos
102
+ dwg.annotations() # {name: type} of every named annotation already on the sheet
103
+ dwg.get_annotation(name) # the named annotation object, or None
104
+ dwg.view_bounds("front") # (x_min, y_min, x_max, y_max) page bbox of a view, or None
105
+ dwg.items # the ordered, mutable list of annotation objects
106
+ dwg.views # {"front","plan","side","iso"} → (visible, hidden) compounds
107
+ dwg.draft / dwg.scale / dwg.page_w / dwg.page_h
108
+ ```
109
+
110
+ **Add a linear dimension with `place_dim`** — it allocates the offset and stacks
111
+ clear of existing dims; you give two page-point endpoints and a side/view:
112
+
113
+ ```python
114
+ # side ∈ {"above","below","left","right"}; view ∈ {"front","plan","side"}.
115
+ p1 = dwg.at("front", 0, 0, 0) # world → page point
116
+ p2 = dwg.at("front", 40, 0, 0)
117
+ dwg.place_dim(p1, p2, "above", "front", dwg.draft, name="dim_len")
118
+
119
+ dwg.remove("dim_od") # drop an automatic annotation by name
120
+ ```
121
+
122
+ **Add a diameter callout on a hole the auto-pass missed** — locate it with
123
+ `features()` and attach a `HoleCallout` (this is what the `feature_not_dimensioned`
124
+ lint suggestion hands you verbatim — see the loop below):
125
+
126
+ ```python
127
+ from build123d_drafting import HoleCallout, Leader
128
+
129
+ for f in dwg.features("plan"): # plan→Z holes, front→Y, side→X
130
+ if abs(f.diameter - 4.0) < 0.2:
131
+ callout = HoleCallout(f.diameter, count=f.count, through=f.through,
132
+ depth=f.depth, draft=dwg.draft)
133
+ elbow = (f.page_pos[0] + 15, f.page_pos[1] + 10, 0)
134
+ dwg.add(Leader((*f.page_pos, 0), elbow, "", dwg.draft, callout=callout),
135
+ name="hole_4")
136
+ ```
137
+
138
+ **Escape hatch** — only when no domain verb fits (e.g. a free-form note at an
139
+ exact spot). Prefer the above; this couples you to page mechanics:
140
+
141
+ ```python
142
+ from build123d_drafting import Leader
143
+ dwg.add(Leader(tip=dwg.at("front", 10, 0, 5), elbow=(8, 40, 0),
144
+ label="ø4 BORE", draft=dwg.draft), "ldr_bore")
145
+ ```
146
+
147
+ Then re-lint and export:
148
+
149
+ ```python
150
+ issues = dwg.lint() # list of LintIssue; [] when clean
151
+ svg, dxf = dwg.export("drawings/bracket")
152
+ ```
153
+
154
+ `make_drawing(...)` is exactly `build_drawing(...).export()`.
155
+
156
+ **Add a section or auxiliary view** with `add_view()`:
157
+
158
+ ```python
159
+ look = dwg.look_at
160
+ bottom = (look[0], look[1], look[2] - dwg.dist)
161
+ vc = dwg.add_view("bottom", part, bottom, (0, 1, 0), (260.0, 60.0))
162
+ px, py = vc.pp(world_x, world_y, world_z)
163
+ ```
164
+
165
+ ---
166
+
167
+ ## Step 3 — Verify
168
+
169
+ ```
170
+ mcp__build123d-mcp__render_drawing(svg_path='drawings/part_name.svg', save_to='/tmp/dwg.png')
171
+ mcp__build123d-mcp__save_drawing_annotations(svg_path='drawings/part_name.svg')
172
+ mcp__build123d-mcp__inspect_drawing(svg_path='drawings/part_name.svg')
173
+ ```
174
+
175
+ ---
176
+
177
+ ## Step 4 — Save a standalone regeneration script (default)
178
+
179
+ **A — Drawing from a STEP file**:
180
+
181
+ ```python
182
+ from draftwright import generate_script
183
+
184
+ generate_script(
185
+ "path/to/part.step",
186
+ out="scripts/drawings/bracket",
187
+ title="BRACKET", number="DWG-042",
188
+ tolerance="ISO 2768-f", drawn_by="Your Name",
189
+ )
190
+ ```
191
+
192
+ **B — Drawing an in-session object** (hand-write the script):
193
+
194
+ ```python
195
+ #!/usr/bin/env python3
196
+ """BRACKET — regenerates drawings/bracket.svg + .dxf in one run."""
197
+ from draftwright import make_drawing
198
+ from myproject.bracket import build_bracket
199
+
200
+ part = build_bracket()
201
+ make_drawing(part, out="drawings/bracket", title="BRACKET",
202
+ number="DWG-042", tolerance="ISO 2768-f", drawn_by="Your Name")
203
+ ```
204
+
205
+ ---
206
+
207
+ ## Scale and page control
208
+
209
+ ```python
210
+ from draftwright import choose_scale
211
+
212
+ SCALE, PAGE_W, PAGE_H, TB_W = choose_scale(x_size, y_size, z_size)
213
+ make_drawing(part, out="drawing", scale=2.0, page="A2")
214
+ ```
215
+
216
+ ---
217
+
218
+ ## Lint → critique → fix (the loop to drive as an AI)
219
+
220
+ draftwright is built to be *iterated*: build, read a machine-readable critique,
221
+ apply a domain-level fix, re-build. This is the supported refinement model —
222
+ prefer it over eyeballing the SVG and hand-placing annotations.
223
+
224
+ ```python
225
+ dwg = build_drawing(part)
226
+
227
+ # 1. Critique — the machine channel. JSON-friendly aggregate of lint().
228
+ crit = dwg.lint_summary()
229
+ # {"passed": bool, "score": 0..1, "errors": n, "warnings": n, "infos": n,
230
+ # "by_code": {code: n}, "issues": [{code, severity, message, suggestion?}, ...]}
231
+ # Gate on the severity/code COUNTS, not the scalar score.
232
+
233
+ # 2. Each issue names the problem in DOMAIN terms and (when computable) carries a
234
+ # ready-to-apply suggestion — a domain-API call you paste in, not page maths.
235
+ for i in dwg.lint():
236
+ print(i.severity, i.code, i.message)
237
+ if getattr(i, "suggestion", None):
238
+ print(" fix:", i.suggestion) # e.g. dwg.place_dim(...) / dwg.add_view(...)
239
+
240
+ # 3. Self-repair — auto-applies the mechanically-fixable issues (overlapping
241
+ # labels pushed apart, wrong-side dims flipped). Runs by default inside
242
+ # build_drawing; call again after manual edits. It never makes a sheet worse.
243
+ dwg.repair()
244
+
245
+ # Pin a deliberate placement so repair won't move it (the constraint solver will
246
+ # honour it too as it lands — ADR 0003):
247
+ dwg.pin(name) # name from dwg.annotations(); dwg.unpin(name) to release
248
+ ```
249
+
250
+ Codes are domain-meaningful (`feature_not_dimensioned`, `feature_count_mismatch`,
251
+ `callout_dropped`, `location_ref_dropped`, `step_dim_dropped`, …), so a fix is
252
+ always expressible through the domain API (`place_dim`, `features`, `add_view`),
253
+ never the page-layout internals. Loop until `passed` (or the score plateaus).
254
+
255
+ Coverage-only check, standalone:
256
+
257
+ ```python
258
+ from draftwright import build_drawing, lint_feature_coverage
259
+ issues = build_drawing(part).lint() # geometry lint + feature-coverage check
260
+ ```
261
+
262
+ ---
263
+
264
+ ## Using this skill with build123d-mcp
265
+
266
+ This skill requires `draftwright` to be in the server's import allowlist.
267
+ The build123d-mcp server allows it by default from v0.3.51 onwards. If you see
268
+ `ImportError: draftwright is not in the import allowlist`, pass
269
+ `--allow-imports draftwright` to the server CLI or set
270
+ `BUILD123D_ALLOW_IMPORTS=draftwright` in the environment.
271
+
272
+ For annotation primitives (`Dimension`, `Leader`, `TitleBlock`, `lint_drawing`,
273
+ `ViewCoordinates`, etc.) continue to import from `build123d_drafting`.
@@ -13,6 +13,7 @@ Licensed under the GNU Affero General Public License v3 (AGPL-3.0).
13
13
 
14
14
  from draftwright.make_drawing import (
15
15
  Drawing,
16
+ FeatureInfo,
16
17
  analyse_face_levels,
17
18
  build_drawing,
18
19
  choose_scale,
@@ -26,6 +27,7 @@ from draftwright.pmi import PmiRecord, extract_pmi
26
27
 
27
28
  __all__ = [
28
29
  "Drawing",
30
+ "FeatureInfo",
29
31
  "PmiRecord",
30
32
  "analyse_face_levels",
31
33
  "build_drawing",