draftwright 0.3.4__tar.gz → 0.3.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.3.4 → draftwright-0.3.6}/CHANGELOG.md +83 -1
- {draftwright-0.3.4 → draftwright-0.3.6}/PKG-INFO +2 -2
- {draftwright-0.3.4 → draftwright-0.3.6}/pyproject.toml +2 -2
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/_core.py +144 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/analysis.py +19 -1
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/annotations/orchestrator.py +25 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/annotations/sections.py +17 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/builder.py +98 -2
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/cli.py +25 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/compose.py +23 -2
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/linting/structural.py +16 -1
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/sheet.py +6 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/sheet_emit.py +15 -0
- draftwright-0.3.6/tests/test_dense_sheet_canary.py +99 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/test_layout_hypothesis.py +30 -1
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/test_make_drawing.py +250 -1
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/test_sheet_emit.py +43 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/.gitignore +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/LICENSE +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/README.md +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/docs/adr/README.md +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/skills/SKILL.md +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/__init__.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/_geometry.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/annotate.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/annotations/__init__.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/annotations/_common.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/annotations/balloons.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/annotations/from_model.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/annotations/holes.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/drawing.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/export.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/fits.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/fonts/IBMPlexMono-Regular.ttf +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/fonts/IBMPlexSansCondensed-Regular.ttf +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/fonts/LICENSE-IBMPlexMono-OFL.txt +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/fonts/LICENSE-IBMPlexSansCondensed-OFL.txt +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/fonts/__init__.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/intents.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/layout.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/linting/__init__.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/linting/coverage.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/linting/issues.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/linting/suggest.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/make_drawing.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/model/__init__.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/model/declare.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/model/detect.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/model/ir.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/model/planner.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/pmi.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/projection.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/recognition/__init__.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/recognition/_features.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/recognition/_record.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/recognition/chamfers.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/recognition/countersinks.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/recognition/fillets.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/recognition/flats.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/recognition/grooves.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/recognition/levels.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/recognition/plates.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/recognition/slots.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/recognition/turned.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/registry.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/repair.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/score.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/src/draftwright/sheet_dsl.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/_kernel.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/_layout_sig.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/fixtures/grm03_thumbwheel_drive_screw.step +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/fixtures/nist_ctc_01_asme1_ap203.stp +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/fixtures/nist_ctc_01_asme1_ap242.stp +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/fixtures/nist_ctc_02_asme1_ap203.stp +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/fixtures/nist_ctc_02_asme1_ap242.stp +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/fixtures/nist_ctc_03_asme1_ap203.stp +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/fixtures/nist_ctc_03_asme1_ap242.stp +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/fixtures/nist_ctc_04_asme1_ap203.stp +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/fixtures/nist_ctc_04_asme1_ap242.stp +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/fixtures/nist_ctc_05_asme1_ap203.stp +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/fixtures/nist_ctc_05_asme1_ap242.stp +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/refactor_golden/bracket_section.json +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/refactor_golden/centered_rebate.json +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/refactor_golden/chamfered.json +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/refactor_golden/filleted.json +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/refactor_golden/flange_dense.json +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/refactor_golden/grid_plate.json +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/refactor_golden/grooved_shaft.json +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/refactor_golden/hex_bar.json +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/refactor_golden/holed_slot.json +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/refactor_golden/pocketed.json +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/refactor_golden/prismatic_ladder.json +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/refactor_golden/scattered_plate.json +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/refactor_golden/side_drilled.json +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/refactor_golden/turned_stepped.json +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/test_architecture_docs.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/test_carve_free_position_callers.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/test_declare.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/test_detect_once.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/test_drawing_encapsulation.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/test_e2e_slice.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/test_e2e_standards.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/test_export_dxf_zoom.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/test_fillets_adjacency.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/test_fits.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/test_gdt_placement.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/test_import_boundaries.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/test_layout.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/test_layout_cleanliness.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/test_layout_property.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/test_lint_box_cache.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/test_lint_reconciliation.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/test_lint_structural.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/test_linting.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/test_object_aspects.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/test_occupancy_boxes.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/test_part_model.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/test_pitch_dim_footprint.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/test_pmi.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/test_private_test_imports.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/test_recogniser_contract.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/test_recognition.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/test_refactor_golden.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/test_registry.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/test_render_seam.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/test_score.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/test_script_detail_parity.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/test_sheet_gdt.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/test_sheet_notes.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/test_sheet_of.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/test_sheet_tables.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/test_solve_trace.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/test_strip_layout.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/test_tolerances.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/test_turned_steps.py +0 -0
- {draftwright-0.3.4 → draftwright-0.3.6}/tests/test_witness_label_reconciliation.py +0 -0
|
@@ -1,6 +1,88 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## v0.3.6 — 2026-07-21
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **`Sheet.step(boss)` on a prismatic part fails loudly instead of silently
|
|
8
|
+
dropping the height** (#631): declaring a turned `.step()` at a cylinder that is
|
|
9
|
+
really an external boss set `orientation="z"`, which made `render_height_ladder`
|
|
10
|
+
suppress the overall-height dim on the assumption the OD/step chain conveyed it —
|
|
11
|
+
but on a prismatic box that chain never renders, so the height vanished and
|
|
12
|
+
nothing replaced it (11 → 10 annotations). The build now detects a `StepFeature`
|
|
13
|
+
coincident with a `BossFeature` (same axis, origin, diameter) — a combination
|
|
14
|
+
that can never be legitimate — and raises a clear `ValueError` pointing to
|
|
15
|
+
`.boss()` (which renders its own ø and height per #632). Legitimate turned shafts
|
|
16
|
+
have steps but no coincident boss, so they are unaffected.
|
|
17
|
+
- **`detail_view=True` no longer a silent no-op when the detail can't be placed**
|
|
18
|
+
(#630): a part whose lint recommends a detail view (`step_dim_dropped`) but whose
|
|
19
|
+
crowded band is full-width (e.g. a shelled cover's stacked face levels) can't be
|
|
20
|
+
enlarged legibly *and* still fit alongside the main views — so `detail_view=True`
|
|
21
|
+
produced a drawing byte-identical to `False`, silently ignoring the opt-in. It
|
|
22
|
+
now records a `detail_unplaceable` warning naming the reason and an actionable
|
|
23
|
+
remedy (dimension manually / move onto its own sheet), so the opt-in is
|
|
24
|
+
observable. Parts whose detail *does* fit are unchanged.
|
|
25
|
+
- **Imperative `--script` round-trips the title-block fields + sheet furniture**
|
|
26
|
+
(#775): the imperative flavour reproduced only title/number/tolerance/drawn_by/
|
|
27
|
+
scale/page, dropping the `material`/`date`/`revision`/`company` fields (#766) and
|
|
28
|
+
the `frame`/`zones`/`projection` furniture (#767/#768/#769) — so a regenerated
|
|
29
|
+
imperative script didn't match the direct CLI. They now ride the emitted script's
|
|
30
|
+
cog config block and its `build_drawing(...)` call (the Sheet flavour already
|
|
31
|
+
round-tripped them), and the CLI forwards them to `generate_script`.
|
|
32
|
+
|
|
33
|
+
### Internal
|
|
34
|
+
|
|
35
|
+
- **Fast-tier dense-sheet canary for the #733/#734 strip-pressure regression**
|
|
36
|
+
(#737): a synthetic contended-front-strip fixture that drops a step-height
|
|
37
|
+
principal dim on the pre-#734 commit and stays clean on `main` — closing the
|
|
38
|
+
fast-tier gap the #733 regression fell through (only the CI-only slow CTC
|
|
39
|
+
fixtures exercised real strip pressure before).
|
|
40
|
+
- **Layout-hypothesis fuzz tier is now a reproducible PR gate** (#692):
|
|
41
|
+
`derandomize=True` makes every run generate the same fixed example set (with
|
|
42
|
+
`print_blob` for paste-able reproduction), so a failure recurs deterministically
|
|
43
|
+
instead of surfacing on a seed-dependent minority of runs; exploratory fuzzing
|
|
44
|
+
moves behind `DRAFTWRIGHT_FUZZ_EXPLORE=1`. The two `test_make_drawing.py` xdist
|
|
45
|
+
observations (#669) were investigated and found non-reproducible — both tests are
|
|
46
|
+
non-flaky by construction/measurement on current `main`.
|
|
47
|
+
|
|
48
|
+
## v0.3.5 — 2026-07-20
|
|
49
|
+
|
|
50
|
+
### Fixed
|
|
51
|
+
|
|
52
|
+
- **Generated `Sheet` script round-trips `--frame` / `--zones` / `--projection`**:
|
|
53
|
+
the sheet-furniture flags added in #767/#768/#769 were not emitted into the
|
|
54
|
+
generated `Sheet(...)` constructor (nor forwarded by the CLI's `--script` path),
|
|
55
|
+
so a regenerated script dropped the border / zone ruler / projection symbol —
|
|
56
|
+
the script no longer matched the direct CLI drawing. The emitter now emits
|
|
57
|
+
`frame=True` / `zones=True` / `projection=…` when set (a plain drawing keeps a
|
|
58
|
+
clean constructor), and the CLI forwards them to `generate_sheet_script`.
|
|
59
|
+
|
|
60
|
+
### Added
|
|
61
|
+
|
|
62
|
+
- **ISO 5457 zone-grid border ruler** (#768): `build_drawing(zones=True)` /
|
|
63
|
+
`Sheet(zones=True)` / `--zones` draws the grid reference system — numbers 1..
|
|
64
|
+
along the top/bottom edges, letters A.. (skipping I/O) down the sides — in the
|
|
65
|
+
band between the frame and the page edge, with the standard division count per
|
|
66
|
+
A-series page (~50 mm zones). Implies a frame (the ticks sit on it). Off by
|
|
67
|
+
default → byte-identical. The border ticks are lint-exempt like the frame; the
|
|
68
|
+
labels are exempt only from the page-bounds check (they legitimately sit outside
|
|
69
|
+
the drawable), still covered by overlap lint.
|
|
70
|
+
- **Projection-method symbol** (#769): `build_drawing(projection="third"|"first")` /
|
|
71
|
+
`Sheet(projection=…)` / `--projection` draws the ISO 5456-2 third-/first-angle
|
|
72
|
+
glyph in the reserved title-block band (above the title block). Uses the new
|
|
73
|
+
`ProjectionSymbol` primitive from build123d-drafting-helpers **0.14.1** (the pin
|
|
74
|
+
is bumped). Off by default → byte-identical. Unlike the page-spanning frame, the
|
|
75
|
+
small well-placed glyph is *not* lint-exempt — lint covers it so a future
|
|
76
|
+
mispositioning is caught.
|
|
77
|
+
- **Opt-in drawn sheet frame / border** (#767): `build_drawing(frame=True)` /
|
|
78
|
+
`Sheet(frame=True)` / `--frame` draws a border rectangle at the page margin. It's
|
|
79
|
+
the *content boundary* (Option B), not a rectangle over the drawing: turning it on
|
|
80
|
+
raises the effective content margin, and because `compose._layout_geometry` is the
|
|
81
|
+
single authority shared by scale/page selection **and** placement, the reservation
|
|
82
|
+
flows through `choose_scale` — so a framed drawing may pick a smaller scale / larger
|
|
83
|
+
page (ADR 0004), with content clearing the border. Default `frame=False` is
|
|
84
|
+
byte-identical (guarded by the golden + layout-cleanliness suites). The page-spanning
|
|
85
|
+
border carries an `is_sheet_frame` rider so lint skips it.
|
|
4
86
|
|
|
5
87
|
## v0.3.4 — 2026-07-20
|
|
6
88
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: draftwright
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.6
|
|
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
|
|
@@ -682,7 +682,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
682
682
|
Classifier: Programming Language :: Python :: 3.14
|
|
683
683
|
Classifier: Topic :: Scientific/Engineering
|
|
684
684
|
Requires-Python: <3.15,>=3.10
|
|
685
|
-
Requires-Dist: build123d-drafting-helpers>=0.14.
|
|
685
|
+
Requires-Dist: build123d-drafting-helpers>=0.14.1
|
|
686
686
|
Requires-Dist: build123d<0.11,>=0.9.0; python_full_version < '3.13'
|
|
687
687
|
Requires-Dist: build123d<0.12,>=0.11; python_full_version >= '3.13'
|
|
688
688
|
Requires-Dist: numpy>=1.24
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "draftwright"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.6"
|
|
8
8
|
description = "Automated technical-drawing generation for build123d"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { file = "LICENSE" }
|
|
@@ -17,7 +17,7 @@ dependencies = [
|
|
|
17
17
|
# 3.13+. Mirrors pzfreo/build123d-mcp's dependency handling.
|
|
18
18
|
"build123d>=0.9.0,<0.11 ; python_full_version < '3.13'",
|
|
19
19
|
"build123d>=0.11,<0.12 ; python_full_version >= '3.13'",
|
|
20
|
-
"build123d-drafting-helpers>=0.14.
|
|
20
|
+
"build123d-drafting-helpers>=0.14.1",
|
|
21
21
|
"numpy>=1.24",
|
|
22
22
|
# PNG raster export (SVG→PDF→PNG). Both permissively licensed (Pillow HPND; pypdfium2
|
|
23
23
|
# Apache-2.0 wrapping Google PDFium BSD-3) and ship pre-built wheels with NO native system
|
|
@@ -50,6 +50,44 @@ _log = logging.getLogger(__name__)
|
|
|
50
50
|
|
|
51
51
|
_MARGIN = 10.0
|
|
52
52
|
|
|
53
|
+
# When a sheet frame is drawn (#767), content reserves this extra band inside the border so
|
|
54
|
+
# it clears the drawn line rather than sitting on it. The frame draws AT _MARGIN (the old
|
|
55
|
+
# drawable boundary); content insets to _content_margin(frame).
|
|
56
|
+
_FRAME_BAND = 6.0
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _content_margin(frame: bool) -> float:
|
|
60
|
+
"""The effective content margin: ``_MARGIN``, plus the frame clearance band when a sheet
|
|
61
|
+
frame is drawn (#767). Threaded into the layout authority so the reservation flows through
|
|
62
|
+
scale/page selection (ADR 0004), not just the render."""
|
|
63
|
+
return _MARGIN + (_FRAME_BAND if frame else 0.0)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# ISO 5457 zone-grid letters (vertical edges): A.. skipping I and O (confusable with 1 / 0).
|
|
67
|
+
_ZONE_LETTERS = "ABCDEFGHJKLMNPQRSTUVWXYZ"
|
|
68
|
+
|
|
69
|
+
# ISO 5457 reference-grid division counts (columns=numbers × rows=letters) per A-series page,
|
|
70
|
+
# keyed by the full (width, height) so a same-width custom page doesn't borrow the count.
|
|
71
|
+
_ZONE_DIVISIONS = {
|
|
72
|
+
(297, 210): (6, 4),
|
|
73
|
+
(420, 297): (8, 6),
|
|
74
|
+
(594, 420): (12, 8),
|
|
75
|
+
(841, 594): (16, 12),
|
|
76
|
+
(1189, 841): (24, 16),
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _zone_divisions(page_w: float, page_h: float) -> tuple[int, int]:
|
|
81
|
+
"""``(columns, rows)`` for the ISO 5457 zone grid (#768) — the standard count for an
|
|
82
|
+
A-series page (matched on BOTH dimensions), else ~50 mm zones for a custom page. Rows are
|
|
83
|
+
clamped to the available letters (I/O skipped) so a very tall custom page can't over-index."""
|
|
84
|
+
std = _ZONE_DIVISIONS.get((int(round(page_w)), int(round(page_h))))
|
|
85
|
+
if std is not None:
|
|
86
|
+
return std
|
|
87
|
+
cols = max(2, round(page_w / 49.5))
|
|
88
|
+
rows = min(len(_ZONE_LETTERS), max(2, round(page_h / 49.5)))
|
|
89
|
+
return (cols, rows)
|
|
90
|
+
|
|
53
91
|
|
|
54
92
|
_TB_CLEAR = _MARGIN + 1.0 # title-block inset: one extra mm over _MARGIN for clearance
|
|
55
93
|
|
|
@@ -711,6 +749,13 @@ class Analysis:
|
|
|
711
749
|
date: str = ""
|
|
712
750
|
revision: str = "A"
|
|
713
751
|
company: str = ""
|
|
752
|
+
# Draw a sheet border/frame (#767). When True, `margin` is already the reserved content
|
|
753
|
+
# margin (`_content_margin(True)`), so content clears the frame drawn at `_MARGIN`.
|
|
754
|
+
frame: bool = False
|
|
755
|
+
# Projection-method symbol (#769): "third" / "first" (ISO 5456-2) or None (omit).
|
|
756
|
+
projection: str | None = None
|
|
757
|
+
# Draw the ISO 5457 zone-grid border ruler (#768). Implies a frame (the ticks sit on it).
|
|
758
|
+
zones: bool = False
|
|
714
759
|
# The PartModel built by _analyse's pre-scale sizing pass (#584 WP1 A) — stored so
|
|
715
760
|
# the render path reuses it instead of re-running the detectors (ADR 0008 Amdt 5:
|
|
716
761
|
# one inventory, detected once; #602). Typed `object` to keep _core free of a
|
|
@@ -799,6 +844,105 @@ def _add_title_block(dwg, a: Analysis):
|
|
|
799
844
|
dwg.add(tb, "title_block")
|
|
800
845
|
|
|
801
846
|
|
|
847
|
+
def _make_sheet_frame(a: Analysis) -> Compound:
|
|
848
|
+
"""The sheet border rectangle (#767) — a closed outline at the ``_MARGIN`` inset (the old
|
|
849
|
+
drawable boundary). Content clears it because ``a.margin`` is the reserved content margin.
|
|
850
|
+
Carries an ``is_sheet_frame`` rider (like ``is_centerline``) so lint skips its page-spanning
|
|
851
|
+
box, and so ``get_annotation`` / a removed frame drop it cleanly."""
|
|
852
|
+
x0, y0 = _MARGIN, _MARGIN
|
|
853
|
+
x1, y1 = a.PAGE_W - _MARGIN, a.PAGE_H - _MARGIN
|
|
854
|
+
frame = Compound(
|
|
855
|
+
children=[
|
|
856
|
+
Edge.make_line(Vector(x0, y0, 0), Vector(x1, y0, 0)),
|
|
857
|
+
Edge.make_line(Vector(x1, y0, 0), Vector(x1, y1, 0)),
|
|
858
|
+
Edge.make_line(Vector(x1, y1, 0), Vector(x0, y1, 0)),
|
|
859
|
+
Edge.make_line(Vector(x0, y1, 0), Vector(x0, y0, 0)),
|
|
860
|
+
]
|
|
861
|
+
)
|
|
862
|
+
frame.is_sheet_frame = True # furniture, not a dimension/view — exempt from overlap lint
|
|
863
|
+
return frame
|
|
864
|
+
|
|
865
|
+
|
|
866
|
+
def _add_sheet_frame(dwg, a: Analysis):
|
|
867
|
+
"""Add the sheet border (#767), drawn last like the title block. No-op is the caller's
|
|
868
|
+
(gated on ``a.frame``)."""
|
|
869
|
+
dwg.add(_make_sheet_frame(a), "sheet_frame")
|
|
870
|
+
|
|
871
|
+
|
|
872
|
+
def _add_projection_symbol(dwg, a: Analysis):
|
|
873
|
+
"""Place the ISO 5456-2 projection-method glyph (#769) in the reserved title-block band,
|
|
874
|
+
just above the drawn title block (deterministic empty space — the block reserves _TB_H but
|
|
875
|
+
draws shorter). Registered ``projection_symbol`` with an ``is_projection_symbol`` identity
|
|
876
|
+
rider. Unlike the page-spanning frame it is NOT lint-exempt: it's a small, well-placed glyph,
|
|
877
|
+
so lint covers it and a future mispositioning is caught. Gated on ``a.projection``."""
|
|
878
|
+
from build123d_drafting import ProjectionSymbol
|
|
879
|
+
|
|
880
|
+
sym = ProjectionSymbol(
|
|
881
|
+
a.projection,
|
|
882
|
+
draft=draft_preset(
|
|
883
|
+
font_size=dwg.draft.font_size,
|
|
884
|
+
decimal_precision=dwg.draft.decimal_precision,
|
|
885
|
+
font_path=PLEX_SANS_CONDENSED,
|
|
886
|
+
),
|
|
887
|
+
)
|
|
888
|
+
b = sym.bounding_box()
|
|
889
|
+
bx, by = (b.min.X + b.max.X) / 2, (b.min.Y + b.max.Y) / 2
|
|
890
|
+
w, h = b.max.X - b.min.X, b.max.Y - b.min.Y
|
|
891
|
+
# Right side of the title-block column, near the top of its reserved band.
|
|
892
|
+
cx = a.PAGE_W - _TB_CLEAR - w / 2 - 3
|
|
893
|
+
cy = _TB_CLEAR + _TB_H - h / 2 - 2
|
|
894
|
+
sym = sym.locate(Location((cx - bx, cy - by, 0)))
|
|
895
|
+
sym.is_projection_symbol = True
|
|
896
|
+
dwg.add(sym, "projection_symbol")
|
|
897
|
+
|
|
898
|
+
|
|
899
|
+
def _add_zone_grid(dwg, a: Analysis):
|
|
900
|
+
"""Draw the ISO 5457 zone-grid border ruler (#768) — numbers 1.. along the top/bottom
|
|
901
|
+
edges, letters A.. (skipping I/O) down the left/right — in the band between the frame
|
|
902
|
+
(at ``_MARGIN``) and the page edge. Requires ``a.frame`` (the ticks sit on the border);
|
|
903
|
+
the caller gates on ``a.zones`` and ensures the frame. Tick lines register as ``zone_grid``
|
|
904
|
+
(``is_zone_grid``); each label carries an ``is_zone_label`` rider so it is exempt from the
|
|
905
|
+
page-bounds lint (it legitimately sits outside the drawable) — but NOT from overlap lint."""
|
|
906
|
+
from build123d_drafting import Note
|
|
907
|
+
|
|
908
|
+
cols, rows = _zone_divisions(a.PAGE_W, a.PAGE_H)
|
|
909
|
+
x0, y0, x1, y1 = _MARGIN, _MARGIN, a.PAGE_W - _MARGIN, a.PAGE_H - _MARGIN
|
|
910
|
+
cw, rh = (x1 - x0) / cols, (y1 - y0) / rows
|
|
911
|
+
band = _MARGIN
|
|
912
|
+
tick = min(3.0, band * 0.6)
|
|
913
|
+
draft = draft_preset(
|
|
914
|
+
font_size=dwg.draft.font_size * 0.8,
|
|
915
|
+
decimal_precision=dwg.draft.decimal_precision,
|
|
916
|
+
font_path=PLEX_SANS_CONDENSED,
|
|
917
|
+
)
|
|
918
|
+
ticks = []
|
|
919
|
+
for i in range(1, cols): # interior column boundaries → ticks on top + bottom edges
|
|
920
|
+
xb = x0 + i * cw
|
|
921
|
+
ticks.append(Edge.make_line(Vector(xb, y0, 0), Vector(xb, y0 - tick, 0)))
|
|
922
|
+
ticks.append(Edge.make_line(Vector(xb, y1, 0), Vector(xb, y1 + tick, 0)))
|
|
923
|
+
for j in range(1, rows): # interior row boundaries → ticks on left + right edges
|
|
924
|
+
yb = y0 + j * rh
|
|
925
|
+
ticks.append(Edge.make_line(Vector(x0, yb, 0), Vector(x0 - tick, yb, 0)))
|
|
926
|
+
ticks.append(Edge.make_line(Vector(x1, yb, 0), Vector(x1 + tick, yb, 0)))
|
|
927
|
+
grid = Compound(children=ticks)
|
|
928
|
+
grid.is_zone_grid = True
|
|
929
|
+
dwg.add(grid, "zone_grid")
|
|
930
|
+
|
|
931
|
+
def _label(text, cx, cy, name):
|
|
932
|
+
note = Note(text, (cx, cy), draft, align=(Align.CENTER, Align.CENTER))
|
|
933
|
+
note.is_zone_label = True
|
|
934
|
+
dwg.add(note, name)
|
|
935
|
+
|
|
936
|
+
for i in range(cols): # numbers 1.. left→right, in the bottom + top bands
|
|
937
|
+
cx = x0 + (i + 0.5) * cw
|
|
938
|
+
_label(str(i + 1), cx, y0 - band / 2, f"zone_num_b_{i}")
|
|
939
|
+
_label(str(i + 1), cx, y1 + band / 2, f"zone_num_t_{i}")
|
|
940
|
+
for j in range(rows): # letters A.. top→bottom, in the left + right bands
|
|
941
|
+
cy = y1 - (j + 0.5) * rh
|
|
942
|
+
_label(_ZONE_LETTERS[j], x0 - band / 2, cy, f"zone_ltr_l_{j}")
|
|
943
|
+
_label(_ZONE_LETTERS[j], x1 + band / 2, cy, f"zone_ltr_r_{j}")
|
|
944
|
+
|
|
945
|
+
|
|
802
946
|
def _iso_bbox(dwg):
|
|
803
947
|
"""(min_x, min_y, max_x, max_y) of the placed iso view, hidden lines included."""
|
|
804
948
|
return dwg.view_bounds("iso")
|
|
@@ -29,6 +29,7 @@ from draftwright._core import (
|
|
|
29
29
|
_MIN_RENDER_MM,
|
|
30
30
|
_MIN_VIEW_MM,
|
|
31
31
|
Analysis,
|
|
32
|
+
_content_margin,
|
|
32
33
|
_legible_steps,
|
|
33
34
|
_Projector,
|
|
34
35
|
)
|
|
@@ -409,6 +410,7 @@ def _validate_explicit_scale(
|
|
|
409
410
|
strips_i,
|
|
410
411
|
layout_section,
|
|
411
412
|
layout_table_sizes,
|
|
413
|
+
margin=_MARGIN,
|
|
412
414
|
) -> None:
|
|
413
415
|
"""Enforce the two scale floors when the caller pinned an explicit *scale* (#489, #590 split
|
|
414
416
|
of :func:`_analyse`). An explicit scale is the user's call — honour it, subject to:
|
|
@@ -441,6 +443,7 @@ def _validate_explicit_scale(
|
|
|
441
443
|
strips=strips_i,
|
|
442
444
|
section=layout_section,
|
|
443
445
|
table_sizes=layout_table_sizes,
|
|
446
|
+
margin=margin,
|
|
444
447
|
)
|
|
445
448
|
# Warn only when omitting the scale would truly give a legible fit (auto scale itself is
|
|
446
449
|
# legible) but the requested scale is below the floor. A part illegible at every
|
|
@@ -474,11 +477,20 @@ def _analyse(
|
|
|
474
477
|
date="",
|
|
475
478
|
revision="A",
|
|
476
479
|
company="",
|
|
480
|
+
frame: bool = False,
|
|
481
|
+
projection: str | None = None,
|
|
482
|
+
zones: bool = False,
|
|
477
483
|
) -> Analysis:
|
|
478
484
|
"""Load STEP or use a build123d Shape, analyse geometry, compute layout.
|
|
479
485
|
|
|
480
486
|
Returns an :class:`Analysis`.
|
|
481
487
|
"""
|
|
488
|
+
# The zone-grid ruler (#768) draws its ticks on the frame, so it implies one.
|
|
489
|
+
frame = frame or zones
|
|
490
|
+
# The content margin — raised by the sheet-frame band (#767) so scale/page selection and
|
|
491
|
+
# placement both reserve room for the border. Computed up front so the choose_scale inside
|
|
492
|
+
# step-count convergence sees it too.
|
|
493
|
+
margin = _content_margin(frame)
|
|
482
494
|
if isinstance(step_file, Shape):
|
|
483
495
|
part = step_file
|
|
484
496
|
src = "build123d object"
|
|
@@ -609,6 +621,7 @@ def _analyse(
|
|
|
609
621
|
strips=strips_i,
|
|
610
622
|
section=layout_section,
|
|
611
623
|
table_sizes=layout_table_sizes,
|
|
624
|
+
margin=margin,
|
|
612
625
|
)
|
|
613
626
|
|
|
614
627
|
(SCALE, PAGE_W, PAGE_H, TB_W), strips_i, n_for_sizing = _converge_step_sizing(
|
|
@@ -628,9 +641,10 @@ def _analyse(
|
|
|
628
641
|
strips_i,
|
|
629
642
|
layout_section,
|
|
630
643
|
layout_table_sizes,
|
|
644
|
+
margin=margin,
|
|
631
645
|
)
|
|
632
646
|
DIM_PAD = _DIM_PAD
|
|
633
|
-
margin
|
|
647
|
+
# margin was computed up front (_content_margin(frame)) so scale selection already saw it.
|
|
634
648
|
# Refine: apply the same legibility gate _auto_annotate uses for dim_step.
|
|
635
649
|
n_steps = len(_legible_steps(step_zs, bb.min.Z, SCALE)[0])
|
|
636
650
|
strips = _measure_strips(
|
|
@@ -656,6 +670,7 @@ def _analyse(
|
|
|
656
670
|
n_steps,
|
|
657
671
|
section=layout_section,
|
|
658
672
|
table_sizes=layout_table_sizes,
|
|
673
|
+
margin=margin,
|
|
659
674
|
)
|
|
660
675
|
fv_hw = _g.fv_hw
|
|
661
676
|
fv_hh = _g.fv_hh
|
|
@@ -778,6 +793,9 @@ def _analyse(
|
|
|
778
793
|
date=date,
|
|
779
794
|
revision=revision,
|
|
780
795
|
company=company,
|
|
796
|
+
frame=frame,
|
|
797
|
+
projection=projection,
|
|
798
|
+
zones=zones,
|
|
781
799
|
out=out,
|
|
782
800
|
pmi=pmi_records,
|
|
783
801
|
pmi_mode=pmi,
|
|
@@ -21,7 +21,10 @@ from draftwright._core import (
|
|
|
21
21
|
_TABULATE_MIN_HOLES,
|
|
22
22
|
Analysis,
|
|
23
23
|
HoleRef,
|
|
24
|
+
_add_projection_symbol,
|
|
25
|
+
_add_sheet_frame,
|
|
24
26
|
_add_title_block,
|
|
27
|
+
_add_zone_grid,
|
|
25
28
|
_concentric_with_axis,
|
|
26
29
|
_fmt,
|
|
27
30
|
_iso_bbox,
|
|
@@ -125,6 +128,9 @@ _PASS_SEQUENCE: tuple[str, ...] = (
|
|
|
125
128
|
"details",
|
|
126
129
|
"title_block",
|
|
127
130
|
"tabulate",
|
|
131
|
+
"sheet_frame",
|
|
132
|
+
"zone_grid",
|
|
133
|
+
"projection_symbol",
|
|
128
134
|
)
|
|
129
135
|
|
|
130
136
|
|
|
@@ -508,6 +514,22 @@ def _auto_annotate(dwg, a: Analysis, *, detail_view: bool = False):
|
|
|
508
514
|
def _s_title_block():
|
|
509
515
|
_add_title_block(dwg, a)
|
|
510
516
|
|
|
517
|
+
def _s_sheet_frame():
|
|
518
|
+
# The sheet border, drawn LAST (#767) — gated on the frame opt-in; content already
|
|
519
|
+
# reserved room via the raised a.margin, so this only draws.
|
|
520
|
+
if a.frame:
|
|
521
|
+
_add_sheet_frame(dwg, a)
|
|
522
|
+
|
|
523
|
+
def _s_zone_grid():
|
|
524
|
+
# ISO 5457 zone-grid border ruler (#768), on the frame (a.zones implies a.frame).
|
|
525
|
+
if a.zones:
|
|
526
|
+
_add_zone_grid(dwg, a)
|
|
527
|
+
|
|
528
|
+
def _s_projection_symbol():
|
|
529
|
+
# ISO 5456-2 projection-method glyph (#769) in the reserved title-block band.
|
|
530
|
+
if a.projection:
|
|
531
|
+
_add_projection_symbol(dwg, a)
|
|
532
|
+
|
|
511
533
|
def _s_tabulate():
|
|
512
534
|
# Escalate to a hole table when the plan view is too dense to dimension
|
|
513
535
|
# every hole — runs last so the table avoids every placed annotation
|
|
@@ -545,6 +567,9 @@ def _auto_annotate(dwg, a: Analysis, *, detail_view: bool = False):
|
|
|
545
567
|
"details": _s_details,
|
|
546
568
|
"title_block": _s_title_block,
|
|
547
569
|
"tabulate": _s_tabulate,
|
|
570
|
+
"sheet_frame": _s_sheet_frame,
|
|
571
|
+
"zone_grid": _s_zone_grid,
|
|
572
|
+
"projection_symbol": _s_projection_symbol,
|
|
548
573
|
}
|
|
549
574
|
)
|
|
550
575
|
if ctx.trace is not None: # snapshot the run's escalations into the trace (#736)
|
|
@@ -597,6 +597,23 @@ def _resolve_details(dwg, a: Analysis, *, ctx) -> None:
|
|
|
597
597
|
dwg.pin(hname)
|
|
598
598
|
if placed:
|
|
599
599
|
n_placed += 1
|
|
600
|
+
elif req.kind == "prismatic-steps":
|
|
601
|
+
# (#630) The prismatic-steps request is the one queued only under the
|
|
602
|
+
# build_drawing(detail_view=True) opt-in (_request_prismatic_detail's gate),
|
|
603
|
+
# so a bail-out here means that opt-in produced nothing. Say so — with an
|
|
604
|
+
# actionable remedy — rather than returning a drawing byte-identical to
|
|
605
|
+
# detail_view=False (a full-width crowded band, e.g. a shelled cover's stacked
|
|
606
|
+
# face levels, can't be enlarged legibly and still fit alongside the main views).
|
|
607
|
+
# The automatic turned-head requests (queued unconditionally by
|
|
608
|
+
# render_step_lengths) are NOT opt-in — their bail-out is the normal path (the
|
|
609
|
+
# main view locates the head/block inline), so they stay silent.
|
|
610
|
+
ctx.record_issue(
|
|
611
|
+
"warning",
|
|
612
|
+
"detail_unplaceable",
|
|
613
|
+
f"{req.kind} detail view requested but could not be placed legibly on this "
|
|
614
|
+
"sheet (the crowded band is too wide to enlarge and still fit); dimension the "
|
|
615
|
+
"feature manually or move it onto its own sheet",
|
|
616
|
+
)
|
|
600
617
|
|
|
601
618
|
|
|
602
619
|
def _overall_height_name(dwg, a: Analysis) -> str | None:
|
|
@@ -29,7 +29,10 @@ from draftwright._core import (
|
|
|
29
29
|
_PAGE_SIZES,
|
|
30
30
|
_SCALES,
|
|
31
31
|
Analysis,
|
|
32
|
+
_add_projection_symbol,
|
|
33
|
+
_add_sheet_frame,
|
|
32
34
|
_add_title_block,
|
|
35
|
+
_add_zone_grid,
|
|
33
36
|
_iso_bbox,
|
|
34
37
|
_log,
|
|
35
38
|
_parse_page,
|
|
@@ -284,6 +287,33 @@ def _assemble(
|
|
|
284
287
|
rot = build_rotational_feature(a)
|
|
285
288
|
if rot is not None:
|
|
286
289
|
pm = replace(pm, features=[*pm.features, rot])
|
|
290
|
+
# A z step declares a segment of a z-turned profile; the height ladder then suppresses
|
|
291
|
+
# the overall height on the premise the step-length chain conveys it (from_model
|
|
292
|
+
# render_height_ladder). That premise holds only if the declared steps span the part's
|
|
293
|
+
# FULL z-extent — a boss, or even a stepped boss on a plate, declared via .step()
|
|
294
|
+
# leaves the bulk unspanned, so suppression silently drops the overall height (#631).
|
|
295
|
+
# Guard on that exact condition rather than a classifier proxy (is_rotational / prof
|
|
296
|
+
# both have blind spots): raise when the z-steps don't reach both ends of the part.
|
|
297
|
+
z_steps = [f for f in pm.features if isinstance(f, StepFeature) and f.frame.axis == "z"]
|
|
298
|
+
if pm.orientation == "z" and z_steps:
|
|
299
|
+
tol = 1e-3 * max(a.z_size, 1.0) # small absolute float epsilon, floored
|
|
300
|
+
# The step lengths convey the overall height only if the steps TILE the z-extent
|
|
301
|
+
# contiguously — a single reach to each end isn't enough (an interior gap would
|
|
302
|
+
# still leave height unconveyed). Walk the spans low→high, extending coverage.
|
|
303
|
+
spans = sorted(
|
|
304
|
+
(min(p0[2], p1[2]), max(p0[2], p1[2])) for f in z_steps for (p0, p1) in [f.span]
|
|
305
|
+
)
|
|
306
|
+
covered = a.bb.min.Z
|
|
307
|
+
for lo, hi in spans:
|
|
308
|
+
if lo <= covered + tol:
|
|
309
|
+
covered = max(covered, hi)
|
|
310
|
+
if spans[0][0] > a.bb.min.Z + tol or covered < a.bb.max.Z - tol:
|
|
311
|
+
raise ValueError(
|
|
312
|
+
"step() declares a segment of a turned profile, but the declared steps "
|
|
313
|
+
"don't span this part's full height — it is not a turned (rotational) "
|
|
314
|
+
"body. For a boss (an external cylinder on a prismatic part) use .boss() "
|
|
315
|
+
"— it renders its own ø and height."
|
|
316
|
+
)
|
|
287
317
|
# PMI (STEP AP242) is likewise detection-sourced, so a declared / emitted-script model
|
|
288
318
|
# carries none. When PMI annotation is on, synthesise the same imported drafting
|
|
289
319
|
# annotations detection would (render_pmi reads them off the model, gated on a.pmi_mode)
|
|
@@ -336,6 +366,12 @@ def _assemble(
|
|
|
336
366
|
else:
|
|
337
367
|
_fit_iso_view(dwg, a, annotate=False)
|
|
338
368
|
_add_title_block(dwg, a)
|
|
369
|
+
if a.frame: # sheet border, drawn last (#767) — auto path adds it via the orchestrator
|
|
370
|
+
_add_sheet_frame(dwg, a)
|
|
371
|
+
if a.zones: # zone-grid ruler (#768), on the frame
|
|
372
|
+
_add_zone_grid(dwg, a)
|
|
373
|
+
if a.projection: # projection-method glyph (#769) — auto path adds it via the orchestrator
|
|
374
|
+
_add_projection_symbol(dwg, a)
|
|
339
375
|
return dwg
|
|
340
376
|
|
|
341
377
|
|
|
@@ -409,6 +445,7 @@ def _repack(
|
|
|
409
445
|
section=a.layout_section,
|
|
410
446
|
table_sizes=a.layout_table_sizes,
|
|
411
447
|
warn_no_iso=False,
|
|
448
|
+
margin=a.margin,
|
|
412
449
|
)
|
|
413
450
|
|
|
414
451
|
candidates = _repack_candidates(a, scale, page)
|
|
@@ -603,6 +640,9 @@ def build_drawing(
|
|
|
603
640
|
date: str = "",
|
|
604
641
|
revision: str = "A",
|
|
605
642
|
company: str = "",
|
|
643
|
+
frame: bool = False,
|
|
644
|
+
projection: str | None = None,
|
|
645
|
+
zones: bool = False,
|
|
606
646
|
) -> Drawing:
|
|
607
647
|
"""Build a customisable 4-view :class:`Drawing` without exporting it.
|
|
608
648
|
|
|
@@ -690,6 +730,9 @@ def build_drawing(
|
|
|
690
730
|
date=date,
|
|
691
731
|
revision=revision,
|
|
692
732
|
company=company,
|
|
733
|
+
frame=frame,
|
|
734
|
+
projection=projection,
|
|
735
|
+
zones=zones,
|
|
693
736
|
)
|
|
694
737
|
|
|
695
738
|
# Pass 1: place + annotate from the estimated layout, then measure the real
|
|
@@ -754,6 +797,9 @@ def make_drawing(
|
|
|
754
797
|
date: str = "",
|
|
755
798
|
revision: str = "A",
|
|
756
799
|
company: str = "",
|
|
800
|
+
frame: bool = False,
|
|
801
|
+
projection: str | None = None,
|
|
802
|
+
zones: bool = False,
|
|
757
803
|
) -> tuple[str, str]:
|
|
758
804
|
"""Generate a 4-view technical drawing from a STEP file or build123d object.
|
|
759
805
|
|
|
@@ -799,6 +845,9 @@ def make_drawing(
|
|
|
799
845
|
date=date,
|
|
800
846
|
revision=revision,
|
|
801
847
|
company=company,
|
|
848
|
+
frame=frame,
|
|
849
|
+
projection=projection,
|
|
850
|
+
zones=zones,
|
|
802
851
|
).export()
|
|
803
852
|
assert svg_path is not None and dxf_path is not None # export() writes both by default
|
|
804
853
|
return svg_path, dxf_path
|
|
@@ -953,6 +1002,13 @@ def _write_script(
|
|
|
953
1002
|
f"NUMBER = {a.number!r}",
|
|
954
1003
|
f"TOLERANCE = {a.tolerance!r}",
|
|
955
1004
|
f"DRAWN_BY = {a.drawn_by!r}",
|
|
1005
|
+
f"MATERIAL = {a.material!r}",
|
|
1006
|
+
f"DATE = {a.date!r}",
|
|
1007
|
+
f"REVISION = {a.revision!r}",
|
|
1008
|
+
f"COMPANY = {a.company!r}",
|
|
1009
|
+
f"FRAME = {a.frame!r}",
|
|
1010
|
+
f"ZONES = {a.zones!r}",
|
|
1011
|
+
f"PROJECTION = {a.projection!r}",
|
|
956
1012
|
f"PMI = {a.pmi_mode!r}",
|
|
957
1013
|
f"SCALE = {scale!r}",
|
|
958
1014
|
f"PAGE = {page!r}",
|
|
@@ -967,6 +1023,13 @@ def _write_script(
|
|
|
967
1023
|
f"_NUMBER = {a.number!r}\n"
|
|
968
1024
|
f"_TOLERANCE = {a.tolerance!r}\n"
|
|
969
1025
|
f"_DRAWN_BY = {a.drawn_by!r}\n"
|
|
1026
|
+
f"_MATERIAL = {a.material!r}\n"
|
|
1027
|
+
f"_DATE = {a.date!r}\n"
|
|
1028
|
+
f"_REVISION = {a.revision!r}\n"
|
|
1029
|
+
f"_COMPANY = {a.company!r}\n"
|
|
1030
|
+
f"_FRAME = {a.frame!r} # draw a sheet border (#767)\n"
|
|
1031
|
+
f"_ZONES = {a.zones!r} # ISO 5457 zone ruler (implies frame, #768)\n"
|
|
1032
|
+
f"_PROJECTION = {a.projection!r} # 'third' | 'first' | None (#769)\n"
|
|
970
1033
|
f"_PMI = {a.pmi_mode!r} # 'off' | 'report' | 'annotate'\n"
|
|
971
1034
|
f"_SCALE = {scale!r} # None = auto; e.g. 5 for 5:1, 0.5 for 1:2\n"
|
|
972
1035
|
f"_PAGE = {page!r} # None = auto; e.g. 'A3' or (297, 210)\n"
|
|
@@ -975,7 +1038,11 @@ def _write_script(
|
|
|
975
1038
|
" for _k, _v in [\n"
|
|
976
1039
|
" ('STEP_FILE', repr(_STEP_FILE)), ('TITLE', repr(_TITLE)),\n"
|
|
977
1040
|
" ('NUMBER', repr(_NUMBER)), ('TOLERANCE', repr(_TOLERANCE)),\n"
|
|
978
|
-
" ('DRAWN_BY', repr(_DRAWN_BY)), ('
|
|
1041
|
+
" ('DRAWN_BY', repr(_DRAWN_BY)), ('MATERIAL', repr(_MATERIAL)),\n"
|
|
1042
|
+
" ('DATE', repr(_DATE)), ('REVISION', repr(_REVISION)),\n"
|
|
1043
|
+
" ('COMPANY', repr(_COMPANY)), ('FRAME', repr(_FRAME)),\n"
|
|
1044
|
+
" ('ZONES', repr(_ZONES)), ('PROJECTION', repr(_PROJECTION)),\n"
|
|
1045
|
+
" ('PMI', repr(_PMI)),\n"
|
|
979
1046
|
" ('SCALE', repr(_SCALE)), ('PAGE', repr(_PAGE)),\n"
|
|
980
1047
|
" ]:\n"
|
|
981
1048
|
" cog.outl(f'{_k} = {_v}')\n"
|
|
@@ -1020,6 +1087,13 @@ def _write_script(
|
|
|
1020
1087
|
" number=NUMBER,\n"
|
|
1021
1088
|
" tolerance=TOLERANCE,\n"
|
|
1022
1089
|
" drawn_by=DRAWN_BY,\n"
|
|
1090
|
+
" material=MATERIAL,\n"
|
|
1091
|
+
" date=DATE,\n"
|
|
1092
|
+
" revision=REVISION,\n"
|
|
1093
|
+
" company=COMPANY,\n"
|
|
1094
|
+
" frame=FRAME,\n"
|
|
1095
|
+
" zones=ZONES,\n"
|
|
1096
|
+
" projection=PROJECTION,\n"
|
|
1023
1097
|
" pmi=PMI,\n"
|
|
1024
1098
|
" scale=SCALE,\n"
|
|
1025
1099
|
" page=PAGE,\n"
|
|
@@ -1081,6 +1155,13 @@ def generate_script(
|
|
|
1081
1155
|
pmi: Literal["off", "report", "annotate"] = "off",
|
|
1082
1156
|
scale: float | None = None,
|
|
1083
1157
|
page: str | None = None,
|
|
1158
|
+
material: str = "",
|
|
1159
|
+
date: str = "",
|
|
1160
|
+
revision: str = "A",
|
|
1161
|
+
company: str = "",
|
|
1162
|
+
frame: bool = False,
|
|
1163
|
+
zones: bool = False,
|
|
1164
|
+
projection: str | None = None,
|
|
1084
1165
|
formats: Sequence[str] = ("pdf",),
|
|
1085
1166
|
) -> str:
|
|
1086
1167
|
"""Generate an editable Cog-enabled drawing script from a STEP file.
|
|
@@ -1109,7 +1190,22 @@ def generate_script(
|
|
|
1109
1190
|
# here too would crash generation on an out-of-range value (e.g. --script --scale
|
|
1110
1191
|
# 0.001 / --page A9) instead of writing the script and deferring — inconsistent with
|
|
1111
1192
|
# a large unfittable scale, which already defers (review #401).
|
|
1112
|
-
a = _analyse(
|
|
1193
|
+
a = _analyse(
|
|
1194
|
+
step_file,
|
|
1195
|
+
title,
|
|
1196
|
+
number,
|
|
1197
|
+
tolerance,
|
|
1198
|
+
drawn_by,
|
|
1199
|
+
out,
|
|
1200
|
+
pmi=pmi,
|
|
1201
|
+
material=material,
|
|
1202
|
+
date=date,
|
|
1203
|
+
revision=revision,
|
|
1204
|
+
company=company,
|
|
1205
|
+
frame=frame,
|
|
1206
|
+
zones=zones,
|
|
1207
|
+
projection=projection,
|
|
1208
|
+
)
|
|
1113
1209
|
return _write_script(a, scale=scale, page=page, formats=formats)
|
|
1114
1210
|
|
|
1115
1211
|
|