draftwright 0.1.9__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.
- {draftwright-0.1.9 → draftwright-0.1.10}/CHANGELOG.md +38 -0
- {draftwright-0.1.9 → draftwright-0.1.10}/PKG-INFO +21 -9
- {draftwright-0.1.9 → draftwright-0.1.10}/README.md +18 -7
- {draftwright-0.1.9 → draftwright-0.1.10}/pyproject.toml +3 -2
- draftwright-0.1.10/skills/SKILL.md +273 -0
- draftwright-0.1.10/src/draftwright/layout.py +317 -0
- {draftwright-0.1.9 → draftwright-0.1.10}/src/draftwright/make_drawing.py +643 -65
- {draftwright-0.1.9 → draftwright-0.1.10}/tests/test_e2e_standards.py +2 -2
- draftwright-0.1.10/tests/test_layout.py +254 -0
- {draftwright-0.1.9 → draftwright-0.1.10}/tests/test_make_drawing.py +412 -7
- draftwright-0.1.9/skills/SKILL.md +0 -183
- {draftwright-0.1.9 → draftwright-0.1.10}/.gitignore +0 -0
- {draftwright-0.1.9 → draftwright-0.1.10}/LICENSE +0 -0
- {draftwright-0.1.9 → draftwright-0.1.10}/src/draftwright/__init__.py +0 -0
- {draftwright-0.1.9 → draftwright-0.1.10}/src/draftwright/pmi.py +0 -0
- {draftwright-0.1.9 → draftwright-0.1.10}/tests/fixtures/nist_ctc_01_asme1_ap203.stp +0 -0
- {draftwright-0.1.9 → draftwright-0.1.10}/tests/fixtures/nist_ctc_01_asme1_ap242.stp +0 -0
- {draftwright-0.1.9 → draftwright-0.1.10}/tests/fixtures/nist_ctc_02_asme1_ap203.stp +0 -0
- {draftwright-0.1.9 → draftwright-0.1.10}/tests/fixtures/nist_ctc_02_asme1_ap242.stp +0 -0
- {draftwright-0.1.9 → draftwright-0.1.10}/tests/fixtures/nist_ctc_03_asme1_ap203.stp +0 -0
- {draftwright-0.1.9 → draftwright-0.1.10}/tests/fixtures/nist_ctc_03_asme1_ap242.stp +0 -0
- {draftwright-0.1.9 → draftwright-0.1.10}/tests/fixtures/nist_ctc_04_asme1_ap203.stp +0 -0
- {draftwright-0.1.9 → draftwright-0.1.10}/tests/fixtures/nist_ctc_04_asme1_ap242.stp +0 -0
- {draftwright-0.1.9 → draftwright-0.1.10}/tests/fixtures/nist_ctc_05_asme1_ap203.stp +0 -0
- {draftwright-0.1.9 → draftwright-0.1.10}/tests/fixtures/nist_ctc_05_asme1_ap242.stp +0 -0
- {draftwright-0.1.9 → draftwright-0.1.10}/tests/test_pmi.py +0 -0
|
@@ -1,5 +1,43 @@
|
|
|
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
|
+
|
|
3
41
|
## v0.1.9 — 2026-06-16
|
|
4
42
|
|
|
5
43
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: draftwright
|
|
3
|
-
Version: 0.1.
|
|
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.
|
|
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
|
-
###
|
|
789
|
+
### Edit, critique, and self-repair
|
|
788
790
|
|
|
789
|
-
|
|
790
|
-
|
|
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
|
-
|
|
794
|
-
|
|
795
|
-
|
|
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
|
-
###
|
|
99
|
+
### Edit, critique, and self-repair
|
|
99
100
|
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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.
|
|
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.
|
|
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`.
|