draftwright 0.1.4__tar.gz → 0.1.6__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.4 → draftwright-0.1.6}/CHANGELOG.md +21 -0
- {draftwright-0.1.4 → draftwright-0.1.6}/PKG-INFO +1 -1
- {draftwright-0.1.4 → draftwright-0.1.6}/pyproject.toml +6 -2
- {draftwright-0.1.4 → draftwright-0.1.6}/src/draftwright/make_drawing.py +91 -1
- draftwright-0.1.6/tests/fixtures/nist_ctc_01_asme1_ap203.stp +5506 -0
- draftwright-0.1.6/tests/fixtures/nist_ctc_02_asme1_ap203.stp +24369 -0
- draftwright-0.1.6/tests/fixtures/nist_ctc_02_asme1_ap242.stp +22900 -0
- draftwright-0.1.6/tests/fixtures/nist_ctc_03_asme1_ap203.stp +5833 -0
- draftwright-0.1.6/tests/fixtures/nist_ctc_03_asme1_ap242.stp +6325 -0
- draftwright-0.1.6/tests/fixtures/nist_ctc_04_asme1_ap203.stp +14677 -0
- draftwright-0.1.6/tests/fixtures/nist_ctc_04_asme1_ap242.stp +21445 -0
- draftwright-0.1.6/tests/fixtures/nist_ctc_05_asme1_ap203.stp +7005 -0
- draftwright-0.1.6/tests/fixtures/nist_ctc_05_asme1_ap242.stp +14204 -0
- {draftwright-0.1.4 → draftwright-0.1.6}/tests/test_e2e_standards.py +45 -0
- {draftwright-0.1.4 → draftwright-0.1.6}/tests/test_make_drawing.py +46 -0
- {draftwright-0.1.4 → draftwright-0.1.6}/.gitignore +0 -0
- {draftwright-0.1.4 → draftwright-0.1.6}/LICENSE +0 -0
- {draftwright-0.1.4 → draftwright-0.1.6}/README.md +0 -0
- {draftwright-0.1.4 → draftwright-0.1.6}/skills/SKILL.md +0 -0
- {draftwright-0.1.4 → draftwright-0.1.6}/src/draftwright/__init__.py +0 -0
- {draftwright-0.1.4 → draftwright-0.1.6}/src/draftwright/pmi.py +0 -0
- {draftwright-0.1.4 → draftwright-0.1.6}/tests/fixtures/nist_ctc_01_asme1_ap242.stp +0 -0
- {draftwright-0.1.4 → draftwright-0.1.6}/tests/test_pmi.py +0 -0
|
@@ -2,6 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## v0.1.5 — 2026-06-15
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- CTC-02 spurious full-page line: build123d's `ExportSVG` projected
|
|
10
|
+
circle-edge-on edges (hole/fillet rims seen edge-on) as elliptical arcs with
|
|
11
|
+
a near-zero minor radius, which renderers blow up into full-page lines.
|
|
12
|
+
`sanitize_svg_arcs()` rewrites any arc with a sub-1e-3 mm radius into the
|
|
13
|
+
straight line it actually is, leaving real arcs untouched (#19). Not a PMI
|
|
14
|
+
issue — the file is AP203 geometry-only.
|
|
15
|
+
|
|
16
|
+
### Tests
|
|
17
|
+
|
|
18
|
+
- Added the full NIST CTC set (01–05) as fixtures, both AP203 geometry-only and
|
|
19
|
+
AP242 (with-PMI) variants.
|
|
20
|
+
- Heavy end-to-end CTC fixture builds are marked `slow` and deselected from the
|
|
21
|
+
default `pytest` run (fast normal run, ~4.5 min); CI runs the fast tier across
|
|
22
|
+
the OS/Python matrix and the slow tier once.
|
|
23
|
+
- Known: AP242 CTC-02 and both CTC-04 variants crash OCCT on import (#20); their
|
|
24
|
+
fixtures are excluded from build tests.
|
|
25
|
+
|
|
5
26
|
## v0.1.4 — 2026-06-15
|
|
6
27
|
|
|
7
28
|
### Changed
|
|
@@ -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.6"
|
|
8
8
|
description = "Automated technical-drawing generation for build123d"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { file = "LICENSE" }
|
|
@@ -57,7 +57,11 @@ include = [
|
|
|
57
57
|
[tool.pytest.ini_options]
|
|
58
58
|
testpaths = ["tests"]
|
|
59
59
|
timeout = 300
|
|
60
|
-
|
|
60
|
+
# `slow` covers the heavy end-to-end builds over the large NIST CTC fixtures
|
|
61
|
+
# (minutes each). They are deselected by default for a fast normal run; run the
|
|
62
|
+
# full suite with `-m ""` or just the slow tier with `-m slow` (CI runs both).
|
|
63
|
+
markers = ["slow: heavy end-to-end CTC fixture builds (deselected by default)"]
|
|
64
|
+
addopts = "--tb=short --cov=src/draftwright --cov-report=term-missing --cov-report=xml -m 'not slow'"
|
|
61
65
|
|
|
62
66
|
[tool.coverage.run]
|
|
63
67
|
source = ["src/draftwright"]
|
|
@@ -71,7 +71,9 @@ from build123d_drafting.helpers import (
|
|
|
71
71
|
view_axes,
|
|
72
72
|
)
|
|
73
73
|
from OCP.BRepAdaptor import BRepAdaptor_Surface
|
|
74
|
+
from OCP.BRepAlgoAPI import BRepAlgoAPI_Cut
|
|
74
75
|
from OCP.GeomAbs import GeomAbs_Plane
|
|
76
|
+
from OCP.TopTools import TopTools_ListOfShape
|
|
75
77
|
|
|
76
78
|
_log = logging.getLogger(__name__)
|
|
77
79
|
|
|
@@ -118,6 +120,53 @@ def fix_svg_page_size(svg_path: str, page_w: float, page_h: float) -> None:
|
|
|
118
120
|
Path(svg_path).write_text(data, encoding="utf-8")
|
|
119
121
|
|
|
120
122
|
|
|
123
|
+
# Below this, an elliptical-arc radius (page-mm) is treated as degenerate.
|
|
124
|
+
# Real feature arcs are orders of magnitude larger; the bad ones are ~1e-7.
|
|
125
|
+
_MIN_ARC_RADIUS = 1e-3
|
|
126
|
+
|
|
127
|
+
_SVG_NUM = r"(-?\d+\.?\d*(?:[eE][-+]?\d+)?)"
|
|
128
|
+
_SVG_ARC_RE = re.compile(
|
|
129
|
+
r"A\s*"
|
|
130
|
+
+ _SVG_NUM
|
|
131
|
+
+ r"[ ,]+"
|
|
132
|
+
+ _SVG_NUM
|
|
133
|
+
+ r"[ ,]+"
|
|
134
|
+
+ _SVG_NUM
|
|
135
|
+
+ r"[ ,]+([01])[ ,]*([01])[ ,]+"
|
|
136
|
+
+ _SVG_NUM
|
|
137
|
+
+ r"[ ,]+"
|
|
138
|
+
+ _SVG_NUM
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def sanitize_svg_arcs(svg_path: str) -> int:
|
|
143
|
+
"""Rewrite near-degenerate elliptical arcs as straight line segments.
|
|
144
|
+
|
|
145
|
+
build123d's ``ExportSVG`` projects a circular edge seen edge-on (a hole or
|
|
146
|
+
fillet rim whose plane is parallel to the view direction) as an elliptical
|
|
147
|
+
arc with a vanishing minor radius (``ry`` ≈ 1e-7). The SVG spec says a
|
|
148
|
+
zero-radius arc is a straight line, but because the radius is not *exactly*
|
|
149
|
+
zero, renderers (librsvg, cairosvg) treat it as a hugely eccentric ellipse
|
|
150
|
+
and draw a spurious full-page line. Each such arc (``A rx ry rot lf sf x
|
|
151
|
+
y``) with ``rx`` or ``ry`` below :data:`_MIN_ARC_RADIUS` is replaced by
|
|
152
|
+
``L x y`` — its true geometry. Returns the number of arcs rewritten.
|
|
153
|
+
"""
|
|
154
|
+
data = Path(svg_path).read_text(encoding="utf-8")
|
|
155
|
+
n = 0
|
|
156
|
+
|
|
157
|
+
def _repl(m):
|
|
158
|
+
nonlocal n
|
|
159
|
+
if abs(float(m.group(1))) < _MIN_ARC_RADIUS or abs(float(m.group(2))) < _MIN_ARC_RADIUS:
|
|
160
|
+
n += 1
|
|
161
|
+
return f"L {m.group(6)} {m.group(7)}"
|
|
162
|
+
return m.group(0)
|
|
163
|
+
|
|
164
|
+
fixed = _SVG_ARC_RE.sub(_repl, data)
|
|
165
|
+
if n:
|
|
166
|
+
Path(svg_path).write_text(fixed, encoding="utf-8")
|
|
167
|
+
return n
|
|
168
|
+
|
|
169
|
+
|
|
121
170
|
# ---------------------------------------------------------------------------
|
|
122
171
|
# Geometry analysis
|
|
123
172
|
# ---------------------------------------------------------------------------
|
|
@@ -1305,6 +1354,9 @@ class Drawing:
|
|
|
1305
1354
|
svg_path = out + ".svg"
|
|
1306
1355
|
svg.write(svg_path)
|
|
1307
1356
|
fix_svg_page_size(svg_path, self.page_w, self.page_h)
|
|
1357
|
+
n_arcs = sanitize_svg_arcs(svg_path)
|
|
1358
|
+
if n_arcs:
|
|
1359
|
+
_log.info("Rewrote %d degenerate (near-zero-radius) arc(s) as line segments", n_arcs)
|
|
1308
1360
|
_log.info("SVG → %s", svg_path)
|
|
1309
1361
|
|
|
1310
1362
|
dxf = ExportDXF()
|
|
@@ -2261,6 +2313,39 @@ def _section_hatch_edges(face, SX, SZ, spacing):
|
|
|
2261
2313
|
return result
|
|
2262
2314
|
|
|
2263
2315
|
|
|
2316
|
+
def _fuzzy_cut(body, cutter, fuzzy: float = 1e-3):
|
|
2317
|
+
"""Boolean-subtract *cutter* from *body* with a fuzzy tolerance.
|
|
2318
|
+
|
|
2319
|
+
Returns a build123d ``Solid`` (or ``Compound`` of solids), or ``None`` if the
|
|
2320
|
+
boolean fails or yields no solid.
|
|
2321
|
+
|
|
2322
|
+
build123d's plain ``body - cutter`` runs an exact (zero-fuzzy) boolean which
|
|
2323
|
+
raises an *uncatchable* ``Standard_DomainError`` on some cast geometry — the
|
|
2324
|
+
C++ exception escapes to ``libc++abi: terminating`` (SIGABRT) and a
|
|
2325
|
+
surrounding ``try/except`` never sees it, killing the whole drawing (NIST
|
|
2326
|
+
CTC-04 section cut, #20). A small fuzzy value makes ``BRepAlgoAPI_Cut`` robust
|
|
2327
|
+
on the same input. We drive the OCCT op directly (build123d's
|
|
2328
|
+
``Shape._bool_op`` result-processing aborts on the resulting compound) and
|
|
2329
|
+
keep only the solids, so non-solid boolean artefacts can't crash the
|
|
2330
|
+
downstream hidden-line projection.
|
|
2331
|
+
"""
|
|
2332
|
+
args = TopTools_ListOfShape()
|
|
2333
|
+
args.Append(body.wrapped)
|
|
2334
|
+
tools = TopTools_ListOfShape()
|
|
2335
|
+
tools.Append(cutter.wrapped)
|
|
2336
|
+
op = BRepAlgoAPI_Cut()
|
|
2337
|
+
op.SetArguments(args)
|
|
2338
|
+
op.SetTools(tools)
|
|
2339
|
+
op.SetFuzzyValue(fuzzy)
|
|
2340
|
+
op.Build()
|
|
2341
|
+
if not op.IsDone():
|
|
2342
|
+
return None
|
|
2343
|
+
solids = Compound(op.Shape()).solids()
|
|
2344
|
+
if not solids:
|
|
2345
|
+
return None
|
|
2346
|
+
return solids[0] if len(solids) == 1 else Compound(children=list(solids))
|
|
2347
|
+
|
|
2348
|
+
|
|
2264
2349
|
def _add_section_view(dwg, a, axis_letter, holes=None):
|
|
2265
2350
|
"""Full section A–A when blind or stepped holes hide their structure (#94).
|
|
2266
2351
|
|
|
@@ -2330,10 +2415,15 @@ def _add_section_view(dwg, a, axis_letter, holes=None):
|
|
|
2330
2415
|
return
|
|
2331
2416
|
body = solids[0] if len(solids) == 1 else Compound(children=list(solids))
|
|
2332
2417
|
try:
|
|
2333
|
-
|
|
2418
|
+
# Fuzzy boolean: the exact `body - Box(...)` aborts uncatchably
|
|
2419
|
+
# (Standard_DomainError) on some cast geometry — see _fuzzy_cut / #20.
|
|
2420
|
+
keep_behind = _fuzzy_cut(body, Pos(a.cx, y_star - big / 2, a.cz) * Box(big, big, big))
|
|
2334
2421
|
except Exception as exc: # noqa: BLE001 — OCC booleans raise broadly
|
|
2335
2422
|
_log.warning("Section A–A skipped (cut failed: %s)", exc)
|
|
2336
2423
|
return
|
|
2424
|
+
if keep_behind is None:
|
|
2425
|
+
_log.warning("Section A–A skipped (boolean cut produced no solid)")
|
|
2426
|
+
return
|
|
2337
2427
|
camera = (dwg.look_at[0], dwg.look_at[1] - dwg.dist, dwg.look_at[2])
|
|
2338
2428
|
dwg.add_view("section_aa", keep_behind, camera, (0, 0, 1), (pos_x, a.FV_Y))
|
|
2339
2429
|
dwg.add(
|