draftwright 0.2.13__tar.gz → 0.3.0__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 (119) hide show
  1. {draftwright-0.2.13 → draftwright-0.3.0}/CHANGELOG.md +80 -0
  2. {draftwright-0.2.13 → draftwright-0.3.0}/PKG-INFO +2 -2
  3. {draftwright-0.2.13 → draftwright-0.3.0}/pyproject.toml +12 -2
  4. {draftwright-0.2.13 → draftwright-0.3.0}/src/draftwright/__init__.py +4 -3
  5. {draftwright-0.2.13 → draftwright-0.3.0}/src/draftwright/_core.py +7 -43
  6. draftwright-0.3.0/src/draftwright/_geometry.py +50 -0
  7. {draftwright-0.2.13 → draftwright-0.3.0}/src/draftwright/analysis.py +215 -142
  8. {draftwright-0.2.13 → draftwright-0.3.0}/src/draftwright/annotations/_common.py +85 -0
  9. {draftwright-0.2.13 → draftwright-0.3.0}/src/draftwright/annotations/from_model.py +518 -3
  10. {draftwright-0.2.13 → draftwright-0.3.0}/src/draftwright/annotations/holes.py +246 -105
  11. {draftwright-0.2.13 → draftwright-0.3.0}/src/draftwright/annotations/orchestrator.py +61 -36
  12. {draftwright-0.2.13 → draftwright-0.3.0}/src/draftwright/annotations/sections.py +27 -23
  13. {draftwright-0.2.13 → draftwright-0.3.0}/src/draftwright/builder.py +8 -1
  14. {draftwright-0.2.13 → draftwright-0.3.0}/src/draftwright/drawing.py +228 -114
  15. draftwright-0.3.0/src/draftwright/layout.py +404 -0
  16. {draftwright-0.2.13 → draftwright-0.3.0}/src/draftwright/linting/coverage.py +15 -11
  17. {draftwright-0.2.13 → draftwright-0.3.0}/src/draftwright/linting/suggest.py +26 -0
  18. {draftwright-0.2.13 → draftwright-0.3.0}/src/draftwright/model/__init__.py +26 -0
  19. {draftwright-0.2.13 → draftwright-0.3.0}/src/draftwright/model/declare.py +532 -2
  20. {draftwright-0.2.13 → draftwright-0.3.0}/src/draftwright/model/detect.py +216 -24
  21. {draftwright-0.2.13 → draftwright-0.3.0}/src/draftwright/model/ir.py +182 -2
  22. {draftwright-0.2.13 → draftwright-0.3.0}/src/draftwright/model/planner.py +1 -1
  23. {draftwright-0.2.13 → draftwright-0.3.0}/src/draftwright/projection.py +4 -1
  24. draftwright-0.3.0/src/draftwright/recognition/__init__.py +125 -0
  25. {draftwright-0.2.13 → draftwright-0.3.0}/src/draftwright/recognition/_features.py +87 -32
  26. draftwright-0.3.0/src/draftwright/recognition/_record.py +28 -0
  27. draftwright-0.3.0/src/draftwright/recognition/chamfers.py +170 -0
  28. draftwright-0.3.0/src/draftwright/recognition/countersinks.py +145 -0
  29. draftwright-0.3.0/src/draftwright/recognition/fillets.py +161 -0
  30. draftwright-0.3.0/src/draftwright/recognition/flats.py +172 -0
  31. draftwright-0.3.0/src/draftwright/recognition/grooves.py +132 -0
  32. draftwright-0.3.0/src/draftwright/recognition/levels.py +175 -0
  33. draftwright-0.3.0/src/draftwright/recognition/plates.py +149 -0
  34. draftwright-0.3.0/src/draftwright/recognition/slots.py +675 -0
  35. {draftwright-0.2.13 → draftwright-0.3.0}/src/draftwright/recognition/turned.py +46 -18
  36. draftwright-0.3.0/src/draftwright/score.py +70 -0
  37. {draftwright-0.2.13 → draftwright-0.3.0}/src/draftwright/sheet.py +79 -178
  38. {draftwright-0.2.13 → draftwright-0.3.0}/src/draftwright/sheet_dsl.py +88 -1
  39. {draftwright-0.2.13 → draftwright-0.3.0}/src/draftwright/sheet_emit.py +42 -8
  40. {draftwright-0.2.13 → draftwright-0.3.0}/tests/layout_snapshots/dshape.json +51 -33
  41. draftwright-0.3.0/tests/test_declare.py +1385 -0
  42. {draftwright-0.2.13 → draftwright-0.3.0}/tests/test_gdt_placement.py +1 -1
  43. draftwright-0.3.0/tests/test_import_boundaries.py +114 -0
  44. {draftwright-0.2.13 → draftwright-0.3.0}/tests/test_layout.py +3 -202
  45. {draftwright-0.2.13 → draftwright-0.3.0}/tests/test_make_drawing.py +1148 -102
  46. {draftwright-0.2.13 → draftwright-0.3.0}/tests/test_part_model.py +15 -10
  47. draftwright-0.3.0/tests/test_recogniser_contract.py +242 -0
  48. {draftwright-0.2.13 → draftwright-0.3.0}/tests/test_recognition.py +89 -89
  49. draftwright-0.3.0/tests/test_score.py +38 -0
  50. {draftwright-0.2.13 → draftwright-0.3.0}/tests/test_sheet_emit.py +235 -7
  51. {draftwright-0.2.13 → draftwright-0.3.0}/tests/test_strip_layout.py +327 -16
  52. draftwright-0.3.0/tests/test_turned_steps.py +118 -0
  53. draftwright-0.2.13/src/draftwright/features.py +0 -23
  54. draftwright-0.2.13/src/draftwright/layout.py +0 -640
  55. draftwright-0.2.13/src/draftwright/recognition/__init__.py +0 -54
  56. draftwright-0.2.13/src/draftwright/recognition/levels.py +0 -51
  57. draftwright-0.2.13/src/draftwright/recognition/slots.py +0 -317
  58. draftwright-0.2.13/tests/test_declare.py +0 -647
  59. draftwright-0.2.13/tests/test_turned_steps.py +0 -87
  60. {draftwright-0.2.13 → draftwright-0.3.0}/.gitignore +0 -0
  61. {draftwright-0.2.13 → draftwright-0.3.0}/LICENSE +0 -0
  62. {draftwright-0.2.13 → draftwright-0.3.0}/README.md +0 -0
  63. {draftwright-0.2.13 → draftwright-0.3.0}/skills/SKILL.md +0 -0
  64. {draftwright-0.2.13 → draftwright-0.3.0}/src/draftwright/annotate.py +0 -0
  65. {draftwright-0.2.13 → draftwright-0.3.0}/src/draftwright/annotations/__init__.py +0 -0
  66. {draftwright-0.2.13 → draftwright-0.3.0}/src/draftwright/cli.py +0 -0
  67. {draftwright-0.2.13 → draftwright-0.3.0}/src/draftwright/export.py +0 -0
  68. {draftwright-0.2.13 → draftwright-0.3.0}/src/draftwright/fits.py +0 -0
  69. {draftwright-0.2.13 → draftwright-0.3.0}/src/draftwright/fonts/IBMPlexMono-Regular.ttf +0 -0
  70. {draftwright-0.2.13 → draftwright-0.3.0}/src/draftwright/fonts/IBMPlexSansCondensed-Regular.ttf +0 -0
  71. {draftwright-0.2.13 → draftwright-0.3.0}/src/draftwright/fonts/LICENSE-IBMPlexMono-OFL.txt +0 -0
  72. {draftwright-0.2.13 → draftwright-0.3.0}/src/draftwright/fonts/LICENSE-IBMPlexSansCondensed-OFL.txt +0 -0
  73. {draftwright-0.2.13 → draftwright-0.3.0}/src/draftwright/fonts/__init__.py +0 -0
  74. {draftwright-0.2.13 → draftwright-0.3.0}/src/draftwright/intents.py +0 -0
  75. {draftwright-0.2.13 → draftwright-0.3.0}/src/draftwright/linting/__init__.py +0 -0
  76. {draftwright-0.2.13 → draftwright-0.3.0}/src/draftwright/linting/issues.py +0 -0
  77. {draftwright-0.2.13 → draftwright-0.3.0}/src/draftwright/linting/structural.py +0 -0
  78. {draftwright-0.2.13 → draftwright-0.3.0}/src/draftwright/make_drawing.py +0 -0
  79. {draftwright-0.2.13 → draftwright-0.3.0}/src/draftwright/pmi.py +0 -0
  80. {draftwright-0.2.13 → draftwright-0.3.0}/src/draftwright/registry.py +0 -0
  81. {draftwright-0.2.13 → draftwright-0.3.0}/src/draftwright/repair.py +0 -0
  82. {draftwright-0.2.13 → draftwright-0.3.0}/tests/_kernel.py +0 -0
  83. {draftwright-0.2.13 → draftwright-0.3.0}/tests/fixtures/nist_ctc_01_asme1_ap203.stp +0 -0
  84. {draftwright-0.2.13 → draftwright-0.3.0}/tests/fixtures/nist_ctc_01_asme1_ap242.stp +0 -0
  85. {draftwright-0.2.13 → draftwright-0.3.0}/tests/fixtures/nist_ctc_02_asme1_ap203.stp +0 -0
  86. {draftwright-0.2.13 → draftwright-0.3.0}/tests/fixtures/nist_ctc_02_asme1_ap242.stp +0 -0
  87. {draftwright-0.2.13 → draftwright-0.3.0}/tests/fixtures/nist_ctc_03_asme1_ap203.stp +0 -0
  88. {draftwright-0.2.13 → draftwright-0.3.0}/tests/fixtures/nist_ctc_03_asme1_ap242.stp +0 -0
  89. {draftwright-0.2.13 → draftwright-0.3.0}/tests/fixtures/nist_ctc_04_asme1_ap203.stp +0 -0
  90. {draftwright-0.2.13 → draftwright-0.3.0}/tests/fixtures/nist_ctc_04_asme1_ap242.stp +0 -0
  91. {draftwright-0.2.13 → draftwright-0.3.0}/tests/fixtures/nist_ctc_05_asme1_ap203.stp +0 -0
  92. {draftwright-0.2.13 → draftwright-0.3.0}/tests/fixtures/nist_ctc_05_asme1_ap242.stp +0 -0
  93. {draftwright-0.2.13 → draftwright-0.3.0}/tests/layout_snapshots/box.json +0 -0
  94. {draftwright-0.2.13 → draftwright-0.3.0}/tests/layout_snapshots/bracket.json +0 -0
  95. {draftwright-0.2.13 → draftwright-0.3.0}/tests/layout_snapshots/drive_screw_x.json +0 -0
  96. {draftwright-0.2.13 → draftwright-0.3.0}/tests/layout_snapshots/flange.json +0 -0
  97. {draftwright-0.2.13 → draftwright-0.3.0}/tests/layout_snapshots/holed_slot.json +0 -0
  98. {draftwright-0.2.13 → draftwright-0.3.0}/tests/layout_snapshots/plate_holes.json +0 -0
  99. {draftwright-0.2.13 → draftwright-0.3.0}/tests/layout_snapshots/side_drilled.json +0 -0
  100. {draftwright-0.2.13 → draftwright-0.3.0}/tests/layout_snapshots/slotted.json +0 -0
  101. {draftwright-0.2.13 → draftwright-0.3.0}/tests/layout_snapshots/turned_shaft.json +0 -0
  102. {draftwright-0.2.13 → draftwright-0.3.0}/tests/test_e2e_slice.py +0 -0
  103. {draftwright-0.2.13 → draftwright-0.3.0}/tests/test_e2e_standards.py +0 -0
  104. {draftwright-0.2.13 → draftwright-0.3.0}/tests/test_fits.py +0 -0
  105. {draftwright-0.2.13 → draftwright-0.3.0}/tests/test_layout_cleanliness.py +0 -0
  106. {draftwright-0.2.13 → draftwright-0.3.0}/tests/test_layout_property.py +0 -0
  107. {draftwright-0.2.13 → draftwright-0.3.0}/tests/test_layout_snapshot.py +0 -0
  108. {draftwright-0.2.13 → draftwright-0.3.0}/tests/test_lint_reconciliation.py +0 -0
  109. {draftwright-0.2.13 → draftwright-0.3.0}/tests/test_lint_structural.py +0 -0
  110. {draftwright-0.2.13 → draftwright-0.3.0}/tests/test_linting.py +0 -0
  111. {draftwright-0.2.13 → draftwright-0.3.0}/tests/test_object_aspects.py +0 -0
  112. {draftwright-0.2.13 → draftwright-0.3.0}/tests/test_pmi.py +0 -0
  113. {draftwright-0.2.13 → draftwright-0.3.0}/tests/test_registry.py +0 -0
  114. {draftwright-0.2.13 → draftwright-0.3.0}/tests/test_render_seam.py +0 -0
  115. {draftwright-0.2.13 → draftwright-0.3.0}/tests/test_sheet_gdt.py +0 -0
  116. {draftwright-0.2.13 → draftwright-0.3.0}/tests/test_sheet_notes.py +0 -0
  117. {draftwright-0.2.13 → draftwright-0.3.0}/tests/test_sheet_of.py +0 -0
  118. {draftwright-0.2.13 → draftwright-0.3.0}/tests/test_sheet_tables.py +0 -0
  119. {draftwright-0.2.13 → draftwright-0.3.0}/tests/test_tolerances.py +0 -0
@@ -1,5 +1,85 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.3.0 — 2026-07-14
4
+
5
+ **Breaking — feature-recogniser API renamed** (ADR 0013 / #568): *deliberate* breaks with
6
+ **no compatibility aliases**, hence the minor bump. Only import paths and callable
7
+ signatures change — but note this release also **changes drawing output**: new machined-
8
+ feature callouts, corrected leader anchors, and a reoriented isometric (see Added / Fixed).
9
+
10
+ ### Added
11
+
12
+ - **Machined-feature recognition — the #148 epic.** #135 deliberately handled only enclosed
13
+ rectangular through-slots; this broadens recognition, and each feature is recognised **and**
14
+ dimensioned **and** authorable (recognise + emit + declare, ADR 0011):
15
+ - **Blind slots + pockets** — floored recesses, `W × L × D DEEP` (#603).
16
+ - **Cross / intersecting slots** collapse into single continuous channels (#604).
17
+ - **Machined flats** on round stock — `{across} A/F` (#605).
18
+ - **Turned / circlip grooves** — `{width} WIDE × ø{floor}` (#606).
19
+ - **Slots / pockets in curved stock** — arc-bounded walls (#607).
20
+ - **Radiused-end (obround) slots** now report their **overall** length (#613).
21
+ - **Chamfer callouts** — `C{leg}` / `{leg}×{angle}°` off each bevel (#560).
22
+ - **Fillet radius callouts** — `R{radius}` off each rounded edge (#561).
23
+ - **Plate / wall thickness** dimensions on multi-plate prismatics (#559).
24
+ - **Prismatic step-position** dimensions locating each shoulder (#555).
25
+ - **Countersink callouts** — `ø6 THRU ⌵ ø14 × 90°` (major-ø + included angle), like the
26
+ counterbore `⊔ ø.. ↧..` (#558). The countersink rides on `HoleRecord`/`HoleFeature`, so
27
+ grouping and the callout-width layout estimate account for it.
28
+ - **Every recognised feature round-trips all three ADR-0011 surfaces (#574).** Beyond
29
+ detection, each feature now **emits** into the generated `--script` Sheet and is
30
+ **declarable** on the fluent `Sheet` surface — authored from scratch or read off a
31
+ build123d object: chamfer (#576), countersink (#575), plate (#577), step-position (#585),
32
+ fillet (#561), plus all the #148 features above.
33
+ - **`draftwright.score.feature_census(part)`** — a per-kind recognised-feature census, a
34
+ measurement tool for tracking recognition coverage (#608).
35
+
36
+ ### Changed
37
+
38
+ - **Feature recognisers renamed `find_`/`analyse_` → `recognise_`** and their
39
+ tuning/dependencies made keyword-only: `find_holes`→`recognise_holes`,
40
+ `find_bosses`→`recognise_bosses`, `find_hole_patterns`→`recognise_hole_patterns`,
41
+ `find_slots`→`recognise_slots`, `find_plates`→`recognise_plates`,
42
+ `find_chamfers`→`recognise_chamfers`, `find_turned_steps`→`recognise_turned_steps`,
43
+ `find_step_shoulders`→`recognise_step_shoulders`, `analyse_face_levels`→
44
+ `recognise_face_levels` (the last was also re-exported at top level as
45
+ `draftwright.analyse_face_levels` → now `draftwright.recognise_face_levels`).
46
+ Cylinder-analysis substrate (`analyse_cylinders`, `full_cylinders`,
47
+ `feature_diameters`) keeps its names.
48
+ - **`recognise_step_shoulders` now returns `list[StepShoulder]`** (a frozen dataclass)
49
+ instead of raw `(axis, position)` tuples; `levels` is keyword-only.
50
+ - **`recognise_turned_steps` now returns `list[TurnedStep]`** (empty for a non-turned
51
+ part) instead of `TurnedProfile | None`. `TurnedStep` gained an `axis` field (each step
52
+ is now a self-contained record); `TurnedProfile` remains only as a pipeline aggregate
53
+ (`TurnedProfile.from_steps(steps)`), no longer a recogniser return.
54
+ - **`recognise_face_levels` now returns `list[FaceLevel]`** (a frozen `FaceLevel(z)`
55
+ record) instead of `list[float]`.
56
+ - **Recognition record classes renamed to avoid clashing with the IR `Feature` types:**
57
+ `draftwright.recognition.HoleFeature` → `HoleRecord`, `BossFeature` → `BossRecord`
58
+ (the IR `draftwright.model.ir.HoleFeature`/`BossFeature` are unchanged).
59
+ - **Page/scale sizing now reads the IR, not recognition records (ADR 0008 / #588).** The
60
+ part model is built up front and drives sheet sizing, so detected and declared parts share
61
+ one path and no recogniser record reaches the sheet estimators. Byte-identical to the old
62
+ estimators except a hole pattern that shares a machining spec with loose holes now sizes
63
+ separately (no phantom merged `N×`), which can shift a tightly-packed such part's layout.
64
+
65
+ ### Fixed
66
+
67
+ - **Isometric view orientation (#620).** The iso camera viewed the *rear* (+Y) side against a
68
+ front-view orthographic set, mirroring asymmetric features; it now views from the same
69
+ front (−Y) / plan (+Z) / right (+X) combination.
70
+ - **Chamfer leaders (#621)** anchor on the bevel-face centroid (the diagonal middle), not the
71
+ supporting plane's parametric origin (which projected to a corner/endpoint).
72
+ - **Fillet leaders (#622)** anchor on the radius surface, not the face bounding-box centre
73
+ (which sat off the round, near the virtual sharp corner).
74
+ - **Authored `ref_pts`-only dimensions** now render (with Z-strip sizing) (#562).
75
+ - **Pitch-dim labels** are cleared off centrelines / bolt circles at creation time, not via
76
+ the repair loop (#129).
77
+
78
+ ### Removed
79
+
80
+ - **`draftwright.features` compatibility module** — deleted. Import `recognise_slots`/
81
+ `Slot` from `draftwright.recognition` instead.
82
+
3
83
  ## v0.2.13 — 2026-07-09
4
84
 
5
85
  **Compose-then-pack is now the layout authority.** This patch finishes the
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: draftwright
3
- Version: 0.2.13
3
+ Version: 0.3.0
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.13.0
685
+ Requires-Dist: build123d-drafting-helpers>=0.13.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.2.13"
7
+ version = "0.3.0"
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.13.0",
20
+ "build123d-drafting-helpers>=0.13.1",
21
21
  "numpy>=1.24",
22
22
  "reportlab>=4.0",
23
23
  "svglib>=1.5",
@@ -111,6 +111,7 @@ ignore = ["E501"]
111
111
 
112
112
  [dependency-groups]
113
113
  dev = [
114
+ "codespell>=2.3.0",
114
115
  "mypy>=2.1.0",
115
116
  "pytest>=8",
116
117
  "pytest-cov>=5",
@@ -119,6 +120,15 @@ dev = [
119
120
  "ruff>=0.4",
120
121
  ]
121
122
 
123
+ [tool.codespell]
124
+ # Spelling enforcement for the ADR 0013 contract (epic #584 WP3). Skips binary /
125
+ # generated / vendored assets: STEP fixtures, the lockfile, fonts, git internals.
126
+ skip = "*.stp,*.step,*.svg,*.dxf,*.pdf,uv.lock,.git,*.ttf,*.otf,src/draftwright/fonts/*"
127
+ # Valid words/identifiers codespell's dictionary misflags (calibrated from a clean run):
128
+ # `nd` a Dijkstra distance var, `tread` a real word (stair-tread faces), `accreting`
129
+ # a real word, `testof` the `TestOf` class name, and the valid hyphenated re-* forms.
130
+ ignore-words-list = "nd,tread,accreting,testof,re-using,re-declare,re-declared,re-declaring"
131
+
122
132
  [tool.uv]
123
133
  # On Python 3.13/3.14, build123d resolves to 0.11, whose OCP kernel is
124
134
  # cadquery-ocp-novtk (VTK dropped from the transitive tree — fine here, draftwright
@@ -23,7 +23,7 @@ from typing import TYPE_CHECKING
23
23
  # none of these names, so they stay sub-second instead of paying for the kernel
24
24
  # on every TAB press (#313). Each name maps to the submodule that provides it.
25
25
  _LAZY = {
26
- "analyse_face_levels": "draftwright.analysis",
26
+ "recognise_face_levels": "draftwright.recognition",
27
27
  "dedup_diams": "draftwright.analysis",
28
28
  "build_drawing": "draftwright.builder",
29
29
  "generate_script": "draftwright.builder",
@@ -72,12 +72,13 @@ _sys.modules[__name__].__class__ = _DraftwrightModule
72
72
 
73
73
 
74
74
  if TYPE_CHECKING: # static analysers / IDEs — no runtime import, no kernel cost
75
- from draftwright.analysis import analyse_face_levels, dedup_diams
75
+ from draftwright.analysis import dedup_diams
76
76
  from draftwright.builder import build_drawing, generate_script, make_drawing
77
77
  from draftwright.drawing import Drawing, FeatureInfo
78
78
  from draftwright.export import fix_svg_page_size
79
79
  from draftwright.linting import lint_feature_coverage
80
80
  from draftwright.pmi import PmiRecord, extract_pmi
81
+ from draftwright.recognition import recognise_face_levels
81
82
  from draftwright.sheet import choose_scale
82
83
  from draftwright.sheet_dsl import Sheet
83
84
 
@@ -94,7 +95,7 @@ __all__ = [
94
95
  "FeatureInfo",
95
96
  "PmiRecord",
96
97
  "Sheet",
97
- "analyse_face_levels",
98
+ "recognise_face_levels",
98
99
  "build_drawing",
99
100
  "choose_scale",
100
101
  "dedup_diams",
@@ -31,6 +31,11 @@ from build123d_drafting.helpers import (
31
31
  format_drawing_scale,
32
32
  )
33
33
 
34
+ # The model-neutral geometry primitives (`_END_ON`, `_xyz`, `HoleRef`, `_axis_letter`)
35
+ # now live in the leaf `draftwright._geometry` so the IR waist (`model/`) can use them
36
+ # without importing this stage-level grab-bag (ADR 0008; #584 WP2). Re-exported here for
37
+ # the above-`_core` consumers (annotations/sheet/drawing/linting) that already import them.
38
+ from draftwright._geometry import _END_ON, HoleRef, _axis_letter, _xyz # noqa: F401
34
39
  from draftwright.fits import FitClass
35
40
  from draftwright.fonts import PLEX_MONO, PLEX_SANS_CONDENSED
36
41
  from draftwright.layout import _greedy_strip_1d, _solve_strip_1d
@@ -43,39 +48,6 @@ _MARGIN = 10.0
43
48
 
44
49
  _TB_CLEAR = _MARGIN + 1.0 # title-block inset: one extra mm over _MARGIN for clearance
45
50
 
46
- # The orthographic view a feature is dimensioned end-on in — the view normal to its
47
- # axis. Single source of truth shared by the planner (view choice) and the lint
48
- # coverage checks (where to look for a feature's dims). Orientation is data.
49
- _END_ON = {"x": "side", "y": "front", "z": "plan"}
50
-
51
-
52
- def _xyz(loc) -> tuple[float, float, float]:
53
- """A build123d ``Vector`` (has ``.X/.Y/.Z``) or an ``(x, y, z)`` sequence → an
54
- ``(x, y, z)`` float tuple. Shared by the detectors and the lint coverage checks
55
- so the Vector-unpacking idiom lives in one place."""
56
- if hasattr(loc, "X"):
57
- return (loc.X, loc.Y, loc.Z)
58
- x, y, z = loc
59
- return (float(x), float(y), float(z))
60
-
61
-
62
- @dataclass(frozen=True)
63
- class HoleRef:
64
- """A position-keyed reference to a hole — the IR-typed value the cover / hole-table
65
- bookkeeping matches on, so the shared escalation never needs a recogniser ``Hole``
66
- object (ADR 0008 Amendment 6). Built from any location via :meth:`of` (rounded, so
67
- two references at the same position compare equal)."""
68
-
69
- x: float
70
- y: float
71
- z: float
72
-
73
- @classmethod
74
- def of(cls, loc) -> HoleRef:
75
- x, y, z = _xyz(loc)
76
- return cls(round(x, 3), round(y, 3), round(z, 3))
77
-
78
-
79
51
  _FONT_SIZE = 3.0 # annotation text height (page-mm); the draft preset is built with this
80
52
 
81
53
 
@@ -161,14 +133,6 @@ def _text_width(text: str, font_size: float, font_path: str = PLEX_MONO) -> floa
161
133
  )
162
134
 
163
135
 
164
- def _axis_letter(obj) -> str:
165
- """Letter (``"x"``/``"y"``/``"z"``) of ``obj.axis``'s dominant component.
166
-
167
- ``obj`` is anything carrying an ``.axis`` 3-vector (a hole or a boss).
168
- """
169
- return max(zip("xyz", obj.axis, strict=True), key=lambda t: abs(t[1]))[0]
170
-
171
-
172
136
  _CONCENTRIC_TOL_MM = 0.5
173
137
 
174
138
 
@@ -506,12 +470,12 @@ class Analysis:
506
470
  bbox_max: float
507
471
  holes: list
508
472
  patterns: list
509
- bosses: list # external bosses (find_bosses), detected once — the one inventory (#244)
473
+ bosses: list # external bosses (recognise_bosses), detected once — the one inventory (#244)
510
474
  slots: list
511
475
  z_diams: list[float]
512
476
  cross_diams: list[float]
513
477
  cyls: tuple[list, list]
514
- prof: TurnedProfile | None # turned step profile (find_turned_steps), detected once
478
+ prof: TurnedProfile | None # turned step profile (recognise_turned_steps), detected once
515
479
  od_diam: float | None
516
480
  is_rotational: bool
517
481
  od_axis: str # rotation/turning axis of a rotational part ("z" default; "x"/"y" #222)
@@ -0,0 +1,50 @@
1
+ """Model-neutral geometry primitives — the leaf below both ``_core`` and ``model``.
2
+
3
+ These four helpers read an axis / coordinate / position off a build123d object
4
+ (or the IR) and carry no drawing, layout or page knowledge. They live here, not
5
+ in :mod:`draftwright._core`, so the IR waist (:mod:`draftwright.model`) can use
6
+ them without importing the stage-level drawing grab-bag (ADR 0008; #584 WP2).
7
+ This module imports nothing from ``draftwright`` — it is the bottom of the DAG.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from dataclasses import dataclass
13
+
14
+ # Axis letter -> the orthographic view a feature on that axis reads end-on in.
15
+ _END_ON = {"x": "side", "y": "front", "z": "plan"}
16
+
17
+
18
+ def _xyz(loc) -> tuple[float, float, float]:
19
+ """A build123d ``Vector`` (has ``.X/.Y/.Z``) or an ``(x, y, z)`` sequence → an
20
+ ``(x, y, z)`` float tuple. Shared by the detectors and the lint coverage checks
21
+ so the Vector-unpacking idiom lives in one place."""
22
+ if hasattr(loc, "X"):
23
+ return (loc.X, loc.Y, loc.Z)
24
+ x, y, z = loc
25
+ return (float(x), float(y), float(z))
26
+
27
+
28
+ @dataclass(frozen=True)
29
+ class HoleRef:
30
+ """A position-keyed reference to a hole — the IR-typed value the cover / hole-table
31
+ bookkeeping matches on, so the shared escalation never needs a recogniser ``Hole``
32
+ object (ADR 0008 Amendment 6). Built from any location via :meth:`of` (rounded, so
33
+ two references at the same position compare equal)."""
34
+
35
+ x: float
36
+ y: float
37
+ z: float
38
+
39
+ @classmethod
40
+ def of(cls, loc) -> HoleRef:
41
+ x, y, z = _xyz(loc)
42
+ return cls(round(x, 3), round(y, 3), round(z, 3))
43
+
44
+
45
+ def _axis_letter(obj) -> str:
46
+ """Letter (``"x"``/``"y"``/``"z"``) of ``obj.axis``'s dominant component.
47
+
48
+ ``obj`` is anything carrying an ``.axis`` 3-vector (a hole or a boss).
49
+ """
50
+ return max(zip("xyz", obj.axis, strict=True), key=lambda t: abs(t[1]))[0]