pybosl2 0.5.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 (106) hide show
  1. pybosl2-0.5.0/LICENSE +24 -0
  2. pybosl2-0.5.0/PKG-INFO +130 -0
  3. pybosl2-0.5.0/README.md +96 -0
  4. pybosl2-0.5.0/bosl2/__init__.py +46 -0
  5. pybosl2-0.5.0/bosl2/_mctable.py +127 -0
  6. pybosl2-0.5.0/bosl2/ball_bearings.py +142 -0
  7. pybosl2-0.5.0/bosl2/beziers.py +778 -0
  8. pybosl2-0.5.0/bosl2/bottlecaps.py +229 -0
  9. pybosl2-0.5.0/bosl2/color.py +198 -0
  10. pybosl2-0.5.0/bosl2/comparisons.py +36 -0
  11. pybosl2-0.5.0/bosl2/constants.py +105 -0
  12. pybosl2-0.5.0/bosl2/cubetruss.py +391 -0
  13. pybosl2-0.5.0/bosl2/distributors.py +554 -0
  14. pybosl2-0.5.0/bosl2/drawing.py +712 -0
  15. pybosl2-0.5.0/bosl2/gears.py +876 -0
  16. pybosl2-0.5.0/bosl2/geometry.py +155 -0
  17. pybosl2-0.5.0/bosl2/hinges.py +134 -0
  18. pybosl2-0.5.0/bosl2/isosurface.py +457 -0
  19. pybosl2-0.5.0/bosl2/joiners.py +113 -0
  20. pybosl2-0.5.0/bosl2/linear_bearings.py +126 -0
  21. pybosl2-0.5.0/bosl2/masking.py +403 -0
  22. pybosl2-0.5.0/bosl2/math.py +154 -0
  23. pybosl2-0.5.0/bosl2/miscellaneous.py +417 -0
  24. pybosl2-0.5.0/bosl2/modular_hose.py +144 -0
  25. pybosl2-0.5.0/bosl2/nema_steppers.py +131 -0
  26. pybosl2-0.5.0/bosl2/nurbs.py +480 -0
  27. pybosl2-0.5.0/bosl2/partitions.py +541 -0
  28. pybosl2-0.5.0/bosl2/paths.py +1554 -0
  29. pybosl2-0.5.0/bosl2/polyhedra.py +179 -0
  30. pybosl2-0.5.0/bosl2/regions.py +115 -0
  31. pybosl2-0.5.0/bosl2/rounding.py +299 -0
  32. pybosl2-0.5.0/bosl2/screw_drive.py +348 -0
  33. pybosl2-0.5.0/bosl2/screws.py +431 -0
  34. pybosl2-0.5.0/bosl2/shapes2d.py +1508 -0
  35. pybosl2-0.5.0/bosl2/shapes3d.py +3062 -0
  36. pybosl2-0.5.0/bosl2/skin.py +735 -0
  37. pybosl2-0.5.0/bosl2/sliders.py +124 -0
  38. pybosl2-0.5.0/bosl2/threading.py +327 -0
  39. pybosl2-0.5.0/bosl2/transforms.py +189 -0
  40. pybosl2-0.5.0/bosl2/vectors.py +61 -0
  41. pybosl2-0.5.0/bosl2/version.py +89 -0
  42. pybosl2-0.5.0/bosl2/vnf.py +289 -0
  43. pybosl2-0.5.0/bosl2/walls.py +269 -0
  44. pybosl2-0.5.0/pybosl2.egg-info/PKG-INFO +130 -0
  45. pybosl2-0.5.0/pybosl2.egg-info/SOURCES.txt +104 -0
  46. pybosl2-0.5.0/pybosl2.egg-info/dependency_links.txt +1 -0
  47. pybosl2-0.5.0/pybosl2.egg-info/requires.txt +11 -0
  48. pybosl2-0.5.0/pybosl2.egg-info/top_level.txt +2 -0
  49. pybosl2-0.5.0/pyproject.toml +65 -0
  50. pybosl2-0.5.0/pysolidfive/__init__.py +246 -0
  51. pybosl2-0.5.0/pysolidfive/_constants.py +80 -0
  52. pybosl2-0.5.0/pysolidfive/_edges.py +181 -0
  53. pybosl2-0.5.0/pysolidfive/docs/_ext/__init__.py +0 -0
  54. pybosl2-0.5.0/pysolidfive/docs/_ext/pythonscad_example.py +128 -0
  55. pybosl2-0.5.0/pysolidfive/docs/conf.py +85 -0
  56. pybosl2-0.5.0/pysolidfive/joiners.py +236 -0
  57. pybosl2-0.5.0/pysolidfive/paths.py +803 -0
  58. pybosl2-0.5.0/pysolidfive/shapes2d.py +445 -0
  59. pybosl2-0.5.0/pysolidfive/shapes3d.py +1744 -0
  60. pybosl2-0.5.0/pysolidfive/tests/__init__.py +34 -0
  61. pybosl2-0.5.0/pysolidfive/tests/mock_libfive.py +421 -0
  62. pybosl2-0.5.0/pysolidfive/tests/render_pysolidfive.py +246 -0
  63. pybosl2-0.5.0/pysolidfive/tests/test_pysolidfive.py +1116 -0
  64. pybosl2-0.5.0/pysolidfive/tests/test_pysolidfive_render.py +171 -0
  65. pybosl2-0.5.0/setup.cfg +4 -0
  66. pybosl2-0.5.0/tests/test_ball_bearings.py +64 -0
  67. pybosl2-0.5.0/tests/test_beziers.py +233 -0
  68. pybosl2-0.5.0/tests/test_bottlecaps.py +69 -0
  69. pybosl2-0.5.0/tests/test_color.py +133 -0
  70. pybosl2-0.5.0/tests/test_comparisons.py +45 -0
  71. pybosl2-0.5.0/tests/test_constants.py +60 -0
  72. pybosl2-0.5.0/tests/test_cubetruss.py +129 -0
  73. pybosl2-0.5.0/tests/test_distributors.py +154 -0
  74. pybosl2-0.5.0/tests/test_drawing.py +242 -0
  75. pybosl2-0.5.0/tests/test_gears.py +300 -0
  76. pybosl2-0.5.0/tests/test_geometry.py +78 -0
  77. pybosl2-0.5.0/tests/test_hinges.py +50 -0
  78. pybosl2-0.5.0/tests/test_isosurface.py +139 -0
  79. pybosl2-0.5.0/tests/test_joiners.py +57 -0
  80. pybosl2-0.5.0/tests/test_linear_bearings.py +52 -0
  81. pybosl2-0.5.0/tests/test_masking.py +63 -0
  82. pybosl2-0.5.0/tests/test_math.py +92 -0
  83. pybosl2-0.5.0/tests/test_miscellaneous.py +127 -0
  84. pybosl2-0.5.0/tests/test_modular_hose.py +49 -0
  85. pybosl2-0.5.0/tests/test_native_ops.py +77 -0
  86. pybosl2-0.5.0/tests/test_nema_steppers.py +56 -0
  87. pybosl2-0.5.0/tests/test_nurbs.py +155 -0
  88. pybosl2-0.5.0/tests/test_partitions.py +133 -0
  89. pybosl2-0.5.0/tests/test_paths.py +295 -0
  90. pybosl2-0.5.0/tests/test_paths3d.py +142 -0
  91. pybosl2-0.5.0/tests/test_polyhedra.py +64 -0
  92. pybosl2-0.5.0/tests/test_regions.py +72 -0
  93. pybosl2-0.5.0/tests/test_rounding.py +128 -0
  94. pybosl2-0.5.0/tests/test_screw_drive.py +109 -0
  95. pybosl2-0.5.0/tests/test_screws.py +175 -0
  96. pybosl2-0.5.0/tests/test_shapes2d.py +155 -0
  97. pybosl2-0.5.0/tests/test_shapes3d.py +212 -0
  98. pybosl2-0.5.0/tests/test_skin.py +273 -0
  99. pybosl2-0.5.0/tests/test_sliders.py +45 -0
  100. pybosl2-0.5.0/tests/test_stl_render.py +965 -0
  101. pybosl2-0.5.0/tests/test_threading.py +121 -0
  102. pybosl2-0.5.0/tests/test_transforms.py +110 -0
  103. pybosl2-0.5.0/tests/test_vectors.py +56 -0
  104. pybosl2-0.5.0/tests/test_version.py +64 -0
  105. pybosl2-0.5.0/tests/test_vnf.py +115 -0
  106. pybosl2-0.5.0/tests/test_walls.py +79 -0
pybosl2-0.5.0/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ BSD 2-Clause License
2
+
3
+ Copyright (c) 2026, pinkfish
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
pybosl2-0.5.0/PKG-INFO ADDED
@@ -0,0 +1,130 @@
1
+ Metadata-Version: 2.4
2
+ Name: pybosl2
3
+ Version: 0.5.0
4
+ Summary: Python ports of the BOSL2 OpenSCAD library for use with PythonSCAD
5
+ Author: pinkfish
6
+ License-Expression: BSD-2-Clause
7
+ Project-URL: Homepage, https://github.com/pinkfish/pybosl2
8
+ Project-URL: Repository, https://github.com/pinkfish/pybosl2
9
+ Keywords: 3d,openscad,pythonscad,bosl2,cad,3d-printing
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: Manufacturing
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Multimedia :: Graphics :: 3D Modeling
20
+ Classifier: Topic :: Scientific/Engineering
21
+ Requires-Python: >=3.10
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: numpy
25
+ Provides-Extra: test
26
+ Requires-Dist: pytest>=7; extra == "test"
27
+ Requires-Dist: numpy; extra == "test"
28
+ Requires-Dist: pythonscad; extra == "test"
29
+ Provides-Extra: docs
30
+ Requires-Dist: sphinx>=7; extra == "docs"
31
+ Requires-Dist: numpy; extra == "docs"
32
+ Requires-Dist: pythonscad; extra == "docs"
33
+ Dynamic: license-file
34
+
35
+ # pybosl2
36
+
37
+ Python ports of the [BOSL2](https://github.com/BelfrySCAD/BOSL2) OpenSCAD library,
38
+ for use with [PythonSCAD](https://pythonscad.org). The package is imported as
39
+ `bosl2`, with one module per wrapped/ported `.scad` file so each Python module can
40
+ be read side by side with its OpenSCAD source.
41
+
42
+ Most modules (constants, math, vectors, paths, shapes2d, …) are standalone pure-Python
43
+ ports that work in plain CPython. The modules that build native geometry
44
+ (`shapes3d`, `masking`, and the `.polygon()`/`.polyhedron()` boundaries) import the
45
+ `pythonscad`/`openscad` native modules at load time.
46
+
47
+ ## Installation
48
+
49
+ ```bash
50
+ pip install pybosl2
51
+ ```
52
+
53
+ This pulls in `numpy`. To build native geometry you also need PythonSCAD:
54
+
55
+ ```bash
56
+ pip install pythonscad
57
+ ```
58
+
59
+ ## Usage
60
+
61
+ Import by submodule (the `square`/`circle`/`cube`/`text` names intentionally shadow
62
+ the plain OpenSCAD builtins with BOSL2's anchor/spin/orient-aware versions, so the
63
+ package is deliberately not wildcard-re-exported):
64
+
65
+ ```python
66
+ import bosl2.shapes3d as s3
67
+ import bosl2.shapes2d as s2
68
+
69
+ part = s3.cuboid([20, 20, 10]).up(5)
70
+ ```
71
+
72
+ ## Development & tests
73
+
74
+ The test-suite runs against a real, pip-installed `pythonscad` in a virtualenv:
75
+
76
+ ```bash
77
+ python -m venv .venv # create from outside the repo dir, or the local
78
+ # bosl2/math.py etc. can shadow stdlib modules
79
+ source .venv/bin/activate
80
+ pip install -e '.[test]' # installs pybosl2 + pytest + numpy + pythonscad
81
+ pytest
82
+ ```
83
+
84
+ The `[test]` extra installs the `pythonscad` wheel, which provides the real
85
+ `pythonscad`/`openscad` native modules the geometry code imports.
86
+
87
+ A small number of tests exercise the full **PythonSCAD app** rather than the pip
88
+ wheel:
89
+
90
+ - STL-render tests (`tests/test_stl_render.py`) drive the real PythonSCAD binary
91
+ in a subprocess to export and measure meshes. They skip unless a binary is found
92
+ (set `PYTHONSCAD_BIN`, or install the app to `/Applications`).
93
+ - App-only native ops (e.g. `roof()`) skip when the pip wheel does not provide them.
94
+
95
+ ## `pysolidfive` (bundled separate package)
96
+
97
+ The repo also vendors [`pysolidfive`](pysolidfive/) — a small, self-contained
98
+ libfive / signed-distance-function shape library for PythonSCAD. It is a **separate
99
+ piece**: it does not depend on `bosl2`, ships in the same wheel as its own top-level
100
+ `pysolidfive` package, and keeps its own tests, docs, and `pyproject.toml`.
101
+
102
+ Its test-suite runs against the numeric `mock_libfive` stand-in bundled in
103
+ `pysolidfive/tests`, so it needs only `numpy` (no `pythonscad` wheel). It uses
104
+ `unittest` rather than `pytest` — the package eagerly `import libfive`s at load, which
105
+ is incompatible with pytest's package-based collection:
106
+
107
+ ```bash
108
+ cd pysolidfive/tests
109
+ python -m unittest discover -s . -t .
110
+ ```
111
+
112
+ ## Documentation
113
+
114
+ The API docs are built with Sphinx (autodoc + napoleon) straight from the module
115
+ docstrings:
116
+
117
+ ```bash
118
+ pip install -e '.[docs]'
119
+ make -C docs html # renders into wiki/
120
+ ```
121
+
122
+ On push to `main`, the `docs` GitHub Actions workflow builds and publishes them to
123
+ **GitHub Pages** (enable Pages with *Settings → Pages → Source: GitHub Actions*).
124
+ Examples in the docs embed interactive 3-D STL viewers; the renders are cached under
125
+ `docs/_generated/` and `docs/_extra/_stl/` — commit those caches so the images appear
126
+ on Pages, since CI has no PythonSCAD binary to render them.
127
+
128
+ ## License
129
+
130
+ BSD 2-Clause License — see [LICENSE](LICENSE).
@@ -0,0 +1,96 @@
1
+ # pybosl2
2
+
3
+ Python ports of the [BOSL2](https://github.com/BelfrySCAD/BOSL2) OpenSCAD library,
4
+ for use with [PythonSCAD](https://pythonscad.org). The package is imported as
5
+ `bosl2`, with one module per wrapped/ported `.scad` file so each Python module can
6
+ be read side by side with its OpenSCAD source.
7
+
8
+ Most modules (constants, math, vectors, paths, shapes2d, …) are standalone pure-Python
9
+ ports that work in plain CPython. The modules that build native geometry
10
+ (`shapes3d`, `masking`, and the `.polygon()`/`.polyhedron()` boundaries) import the
11
+ `pythonscad`/`openscad` native modules at load time.
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ pip install pybosl2
17
+ ```
18
+
19
+ This pulls in `numpy`. To build native geometry you also need PythonSCAD:
20
+
21
+ ```bash
22
+ pip install pythonscad
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ Import by submodule (the `square`/`circle`/`cube`/`text` names intentionally shadow
28
+ the plain OpenSCAD builtins with BOSL2's anchor/spin/orient-aware versions, so the
29
+ package is deliberately not wildcard-re-exported):
30
+
31
+ ```python
32
+ import bosl2.shapes3d as s3
33
+ import bosl2.shapes2d as s2
34
+
35
+ part = s3.cuboid([20, 20, 10]).up(5)
36
+ ```
37
+
38
+ ## Development & tests
39
+
40
+ The test-suite runs against a real, pip-installed `pythonscad` in a virtualenv:
41
+
42
+ ```bash
43
+ python -m venv .venv # create from outside the repo dir, or the local
44
+ # bosl2/math.py etc. can shadow stdlib modules
45
+ source .venv/bin/activate
46
+ pip install -e '.[test]' # installs pybosl2 + pytest + numpy + pythonscad
47
+ pytest
48
+ ```
49
+
50
+ The `[test]` extra installs the `pythonscad` wheel, which provides the real
51
+ `pythonscad`/`openscad` native modules the geometry code imports.
52
+
53
+ A small number of tests exercise the full **PythonSCAD app** rather than the pip
54
+ wheel:
55
+
56
+ - STL-render tests (`tests/test_stl_render.py`) drive the real PythonSCAD binary
57
+ in a subprocess to export and measure meshes. They skip unless a binary is found
58
+ (set `PYTHONSCAD_BIN`, or install the app to `/Applications`).
59
+ - App-only native ops (e.g. `roof()`) skip when the pip wheel does not provide them.
60
+
61
+ ## `pysolidfive` (bundled separate package)
62
+
63
+ The repo also vendors [`pysolidfive`](pysolidfive/) — a small, self-contained
64
+ libfive / signed-distance-function shape library for PythonSCAD. It is a **separate
65
+ piece**: it does not depend on `bosl2`, ships in the same wheel as its own top-level
66
+ `pysolidfive` package, and keeps its own tests, docs, and `pyproject.toml`.
67
+
68
+ Its test-suite runs against the numeric `mock_libfive` stand-in bundled in
69
+ `pysolidfive/tests`, so it needs only `numpy` (no `pythonscad` wheel). It uses
70
+ `unittest` rather than `pytest` — the package eagerly `import libfive`s at load, which
71
+ is incompatible with pytest's package-based collection:
72
+
73
+ ```bash
74
+ cd pysolidfive/tests
75
+ python -m unittest discover -s . -t .
76
+ ```
77
+
78
+ ## Documentation
79
+
80
+ The API docs are built with Sphinx (autodoc + napoleon) straight from the module
81
+ docstrings:
82
+
83
+ ```bash
84
+ pip install -e '.[docs]'
85
+ make -C docs html # renders into wiki/
86
+ ```
87
+
88
+ On push to `main`, the `docs` GitHub Actions workflow builds and publishes them to
89
+ **GitHub Pages** (enable Pages with *Settings → Pages → Source: GitHub Actions*).
90
+ Examples in the docs embed interactive 3-D STL viewers; the renders are cached under
91
+ `docs/_generated/` and `docs/_extra/_stl/` — commit those caches so the images appear
92
+ on Pages, since CI has no PythonSCAD binary to render them.
93
+
94
+ ## License
95
+
96
+ BSD 2-Clause License — see [LICENSE](LICENSE).
@@ -0,0 +1,46 @@
1
+ # Copyright (c) 2026, pinkfish
2
+ #
3
+ # Licensed under the BSD 2-Clause License. See the LICENSE file in the project
4
+ # root for the full license text.
5
+ # SPDX-License-Identifier: BSD-2-Clause
6
+
7
+ # LibFile: bosl2/__init__.py
8
+ # Python ports of the BOSL2 OpenSCAD library, one file per wrapped/ported
9
+ # .scad file, so each Python file can be read side by side with its .scad
10
+ # source:
11
+ # bosl2/constants.py <- BOSL2/constants.scad (pure Python)
12
+ # bosl2/math.py <- BOSL2/math.scad (pure Python)
13
+ # bosl2/vectors.py <- BOSL2/vectors.scad (pure Python)
14
+ # bosl2/lists.py <- BOSL2/lists.scad (pure Python)
15
+ # bosl2/comparisons.py <- BOSL2/comparisons.scad (pure Python)
16
+ # bosl2/geometry.py <- BOSL2/geometry.scad (pure Python, partial)
17
+ # bosl2/paths.py <- BOSL2/paths.scad (pure Python)
18
+ # bosl2/shapes2d.py <- BOSL2/shapes2d.scad (pure Python)
19
+ # bosl2/shapes3d.py <- BOSL2/shapes3d.scad (thin osuse() wrapper)
20
+ #
21
+ # shapes3d.py is a thin wrapper that forwards every call to BOSL2 already
22
+ # loaded via osuse() -- it only works inside the real PythonSCAD app.
23
+ # Every other file here, including shapes2d.py, is a real, standalone
24
+ # port with no osuse()/BOSL2 runtime dependency at all, so it works in
25
+ # plain Python (useful for testing) as well as inside PythonSCAD:
26
+ # shapes2d.py computes each shape's outline in pure Python and then
27
+ # builds it with direct openscad primitive calls (square()/circle()/
28
+ # polygon()/text()/hull()/.offset()) instead of delegating to BOSL2.
29
+ #
30
+ # Names like square(), circle(), cube() and text() in shapes2d/shapes3d
31
+ # intentionally shadow the plain OpenSCAD builtins with BOSL2's
32
+ # anchor/spin/orient-aware versions, so this package is always imported
33
+ # by submodule (`from bosl2 import shapes3d`) rather than re-exported
34
+ # with a wildcard here. Submodules are also deliberately NOT imported
35
+ # eagerly below: shapes3d.py calls osuse() at import time and would
36
+ # raise outside the real PythonSCAD app, which would otherwise break
37
+ # `from bosl2 import <pure-python-module>` too.
38
+ #
39
+ # FileSummary: BOSL2 library ports, one file per wrapped/ported .scad file.
40
+ # FileGroup: BOSL2
41
+
42
+ # Version metadata. version.py has no heavy/native imports, so exposing it here is
43
+ # safe despite the deliberate no-eager-submodule-import policy noted above.
44
+ from bosl2.version import Version, __version__, version # noqa: E402,F401
45
+
46
+ __all__ = ["Version", "version", "__version__"]
@@ -0,0 +1,127 @@
1
+ # Copyright (c) 2026, pinkfish
2
+ #
3
+ # Licensed under the BSD 2-Clause License. See the LICENSE file in the project
4
+ # root for the full license text.
5
+ # SPDX-License-Identifier: BSD-2-Clause
6
+
7
+ # The standard Marching Cubes triangle table (Paul Bourke, "Polygonising a scalar field").
8
+ # TRI_TABLE[cubeindex] is a flat list of edge indices, in groups of three, terminated by -1;
9
+ # each group is one triangle whose vertices lie on those cube edges. Corner and edge numbering:
10
+ # corners: v0(0,0,0) v1(1,0,0) v2(1,1,0) v3(0,1,0) v4(0,0,1) v5(1,0,1) v6(1,1,1) v7(0,1,1)
11
+ # edges: 0:(0,1) 1:(1,2) 2:(2,3) 3:(3,0) 4:(4,5) 5:(5,6) 6:(6,7) 7:(7,4) 8:(0,4) 9:(1,5) 10:(2,6) 11:(3,7)
12
+ # cubeindex bit i is set when corner i is "inside" (below the isolevel).
13
+
14
+ EDGE_CORNERS = [(0, 1), (1, 2), (2, 3), (3, 0), (4, 5), (5, 6), (6, 7), (7, 4),
15
+ (0, 4), (1, 5), (2, 6), (3, 7)]
16
+
17
+ CORNER_OFFSETS = [(0, 0, 0), (1, 0, 0), (1, 1, 0), (0, 1, 0),
18
+ (0, 0, 1), (1, 0, 1), (1, 1, 1), (0, 1, 1)]
19
+
20
+ TRI_TABLE = [
21
+ [], [0, 8, 3], [0, 1, 9], [1, 8, 3, 9, 8, 1], [1, 2, 10], [0, 8, 3, 1, 2, 10],
22
+ [9, 2, 10, 0, 2, 9], [2, 8, 3, 2, 10, 8, 10, 9, 8], [3, 11, 2], [0, 11, 2, 8, 11, 0],
23
+ [1, 9, 0, 2, 3, 11], [1, 11, 2, 1, 9, 11, 9, 8, 11], [3, 10, 1, 11, 10, 3],
24
+ [0, 10, 1, 0, 8, 10, 8, 11, 10], [3, 9, 0, 3, 11, 9, 11, 10, 9],
25
+ [9, 8, 10, 10, 8, 11], [4, 7, 8], [4, 3, 0, 7, 3, 4], [0, 1, 9, 8, 4, 7],
26
+ [4, 1, 9, 4, 7, 1, 7, 3, 1], [1, 2, 10, 8, 4, 7], [3, 4, 7, 3, 0, 4, 1, 2, 10],
27
+ [9, 2, 10, 9, 0, 2, 8, 4, 7], [2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4],
28
+ [8, 4, 7, 3, 11, 2], [11, 4, 7, 11, 2, 4, 2, 0, 4], [9, 0, 1, 8, 4, 7, 2, 3, 11],
29
+ [4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1], [3, 10, 1, 3, 11, 10, 7, 8, 4],
30
+ [1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4], [4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3],
31
+ [4, 7, 11, 4, 11, 9, 9, 11, 10], [9, 5, 4], [9, 5, 4, 0, 8, 3], [0, 5, 4, 1, 5, 0],
32
+ [8, 5, 4, 8, 3, 5, 3, 1, 5], [1, 2, 10, 9, 5, 4], [3, 0, 8, 1, 2, 10, 4, 9, 5],
33
+ [5, 2, 10, 5, 4, 2, 4, 0, 2], [2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8],
34
+ [9, 5, 4, 2, 3, 11], [0, 11, 2, 0, 8, 11, 4, 9, 5], [0, 5, 4, 0, 1, 5, 2, 3, 11],
35
+ [2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5], [10, 3, 11, 10, 1, 3, 9, 5, 4],
36
+ [4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10], [5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3],
37
+ [5, 4, 8, 5, 8, 10, 10, 8, 11], [9, 7, 8, 5, 7, 9], [9, 3, 0, 9, 5, 3, 5, 7, 3],
38
+ [0, 7, 8, 0, 1, 7, 1, 5, 7], [1, 5, 3, 3, 5, 7], [9, 7, 8, 9, 5, 7, 10, 1, 2],
39
+ [10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3], [8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2],
40
+ [2, 10, 5, 2, 5, 3, 3, 5, 7], [7, 9, 5, 7, 8, 9, 3, 11, 2],
41
+ [9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11], [2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7],
42
+ [11, 2, 1, 11, 1, 7, 7, 1, 5], [9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11],
43
+ [5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0],
44
+ [11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0], [11, 10, 5, 7, 11, 5],
45
+ [10, 6, 5], [0, 8, 3, 5, 10, 6], [9, 0, 1, 5, 10, 6], [1, 8, 3, 1, 9, 8, 5, 10, 6],
46
+ [1, 6, 5, 2, 6, 1], [1, 6, 5, 1, 2, 6, 3, 0, 8], [9, 6, 5, 9, 0, 6, 0, 2, 6],
47
+ [5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8], [2, 3, 11, 10, 6, 5],
48
+ [11, 0, 8, 11, 2, 0, 10, 6, 5], [0, 1, 9, 2, 3, 11, 5, 10, 6],
49
+ [5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11], [6, 3, 11, 6, 5, 3, 5, 1, 3],
50
+ [0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6], [3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9],
51
+ [6, 5, 9, 6, 9, 11, 11, 9, 8], [5, 10, 6, 4, 7, 8], [4, 3, 0, 4, 7, 3, 6, 5, 10],
52
+ [1, 9, 0, 5, 10, 6, 8, 4, 7], [10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4],
53
+ [6, 1, 2, 6, 5, 1, 4, 7, 8], [1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7],
54
+ [8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6], [7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9],
55
+ [3, 11, 2, 7, 8, 4, 10, 6, 5], [5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11],
56
+ [0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6],
57
+ [9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6],
58
+ [8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6],
59
+ [5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11],
60
+ [0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7],
61
+ [6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9], [10, 4, 9, 6, 4, 10],
62
+ [4, 10, 6, 4, 9, 10, 0, 8, 3], [10, 0, 1, 10, 6, 0, 6, 4, 0],
63
+ [8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10], [1, 4, 9, 1, 2, 4, 2, 6, 4],
64
+ [3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4], [0, 2, 4, 4, 2, 6],
65
+ [8, 3, 2, 8, 2, 4, 4, 2, 6], [10, 4, 9, 10, 6, 4, 11, 2, 3],
66
+ [0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6], [3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10],
67
+ [6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1], [9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3],
68
+ [8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1], [3, 11, 6, 3, 6, 0, 0, 6, 4],
69
+ [6, 4, 8, 11, 6, 8], [7, 10, 6, 7, 8, 10, 8, 9, 10], [0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10],
70
+ [10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0], [10, 6, 7, 10, 7, 1, 1, 7, 3],
71
+ [1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7], [2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9],
72
+ [7, 8, 0, 7, 0, 6, 6, 0, 2], [7, 3, 2, 6, 7, 2], [2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7],
73
+ [2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7],
74
+ [1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11], [11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1],
75
+ [8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6], [0, 9, 1, 11, 6, 7], [7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0],
76
+ [7, 11, 6], [7, 6, 11], [3, 0, 8, 11, 7, 6], [0, 1, 9, 11, 7, 6], [8, 1, 9, 8, 3, 1, 11, 7, 6],
77
+ [10, 1, 2, 6, 11, 7], [1, 2, 10, 3, 0, 8, 6, 11, 7], [2, 9, 0, 2, 10, 9, 6, 11, 7],
78
+ [6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8], [7, 2, 3, 6, 2, 7], [7, 0, 8, 7, 6, 0, 6, 2, 0],
79
+ [2, 7, 6, 2, 3, 7, 0, 1, 9], [1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6], [10, 7, 6, 10, 1, 7, 1, 3, 7],
80
+ [10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8], [0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7],
81
+ [7, 6, 10, 7, 10, 8, 8, 10, 9], [6, 8, 4, 11, 8, 6], [3, 6, 11, 3, 0, 6, 0, 4, 6],
82
+ [8, 6, 11, 8, 4, 6, 9, 0, 1], [9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6], [6, 8, 4, 6, 11, 8, 2, 10, 1],
83
+ [1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6], [4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9],
84
+ [10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3], [8, 2, 3, 8, 4, 2, 4, 6, 2],
85
+ [0, 4, 2, 4, 6, 2], [1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8], [1, 9, 4, 1, 4, 2, 2, 4, 6],
86
+ [8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1], [10, 1, 0, 10, 0, 6, 6, 0, 4],
87
+ [4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3], [10, 9, 4, 6, 10, 4], [4, 9, 5, 7, 6, 11],
88
+ [0, 8, 3, 4, 9, 5, 11, 7, 6], [5, 0, 1, 5, 4, 0, 7, 6, 11], [11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5],
89
+ [9, 5, 4, 10, 1, 2, 7, 6, 11], [6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5],
90
+ [7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2], [3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6],
91
+ [7, 2, 3, 7, 6, 2, 5, 4, 9], [9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7],
92
+ [3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0], [6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8],
93
+ [9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7], [1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4],
94
+ [4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10], [7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10],
95
+ [6, 9, 5, 6, 11, 9, 11, 8, 9], [3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5],
96
+ [0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11], [6, 11, 3, 6, 3, 5, 5, 3, 1],
97
+ [1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6], [0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10],
98
+ [11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5], [6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3],
99
+ [5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2], [9, 5, 6, 9, 6, 0, 0, 6, 2],
100
+ [1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8], [1, 5, 6, 2, 1, 6],
101
+ [1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6], [10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0],
102
+ [0, 3, 8, 5, 6, 10], [10, 5, 6], [11, 5, 10, 7, 5, 11], [11, 5, 10, 11, 7, 5, 8, 3, 0],
103
+ [5, 11, 7, 5, 10, 11, 1, 9, 0], [10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1],
104
+ [11, 1, 2, 11, 7, 1, 7, 5, 1], [0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11],
105
+ [9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7], [7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2],
106
+ [2, 5, 10, 2, 3, 5, 3, 7, 5], [8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5],
107
+ [9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2], [9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2],
108
+ [1, 3, 5, 3, 7, 5], [0, 8, 7, 0, 7, 1, 1, 7, 5], [9, 0, 3, 9, 3, 5, 5, 3, 7], [9, 8, 7, 5, 9, 7],
109
+ [5, 8, 4, 5, 10, 8, 10, 11, 8], [5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0],
110
+ [0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5], [10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4],
111
+ [2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8], [0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11],
112
+ [0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5], [9, 4, 5, 2, 11, 3],
113
+ [2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4], [5, 10, 2, 5, 2, 4, 4, 2, 0],
114
+ [3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9], [5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2],
115
+ [8, 4, 5, 8, 5, 3, 3, 5, 1], [0, 4, 5, 1, 0, 5], [8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5], [9, 4, 5],
116
+ [4, 11, 7, 4, 9, 11, 9, 10, 11], [0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11],
117
+ [1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11], [3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4],
118
+ [4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2], [9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3],
119
+ [11, 7, 4, 11, 4, 2, 2, 4, 0], [11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4],
120
+ [2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9], [9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7],
121
+ [3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10], [1, 10, 2, 8, 7, 4], [4, 9, 1, 4, 1, 7, 7, 1, 3],
122
+ [4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1], [4, 0, 3, 7, 4, 3], [4, 8, 7], [9, 10, 8, 10, 11, 8],
123
+ [3, 0, 9, 3, 9, 11, 11, 9, 10], [0, 1, 10, 0, 10, 8, 8, 10, 11], [3, 1, 10, 11, 3, 10],
124
+ [1, 2, 11, 1, 11, 9, 9, 11, 8], [3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9], [0, 2, 11, 8, 0, 11],
125
+ [3, 2, 11], [2, 3, 8, 2, 8, 10, 10, 8, 9], [9, 10, 2, 0, 9, 2], [2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8],
126
+ [1, 10, 2], [1, 3, 8, 9, 1, 8], [0, 9, 1], [0, 3, 8], [],
127
+ ]
@@ -0,0 +1,142 @@
1
+ # Copyright (c) 2026, pinkfish
2
+ #
3
+ # Licensed under the BSD 2-Clause License. See the LICENSE file in the project
4
+ # root for the full license text.
5
+ # SPDX-License-Identifier: BSD-2-Clause
6
+
7
+ # LibFile: bosl2/ball_bearings.py
8
+ # Pure-Python port of BOSL2's ball_bearings.scad: models of standard ball-bearing cartridges.
9
+ # :meth:`BallBearings.ball_bearing` builds a bearing -- either a sealed/shielded cartridge (nested
10
+ # rings plus a shield face) or an open one (inner and outer races, a ball-race groove, and the
11
+ # balls) -- from a trade-size name (``"608"``, ``"6902ZZ"``, ``"R8"``) or explicit id/od/width.
12
+ # :meth:`BallBearings.ball_bearing_info` returns the tabulated dimensions as a :class:`BearingSpec`.
13
+ #
14
+ # The trade-size table is transcribed verbatim from ball_bearings.scad.
15
+ #
16
+ # FileSummary: Standard ball-bearing cartridge models.
17
+ # FileGroup: BOSL2
18
+
19
+ from __future__ import annotations
20
+
21
+ import math
22
+ import operator
23
+ from dataclasses import dataclass
24
+ from functools import reduce
25
+
26
+ from bosl2.constants import INCH
27
+ from bosl2.shapes3d import Bosl2Solid, tube, torus, sphere
28
+
29
+ __all__ = ["BallBearings", "BearingSpec"]
30
+
31
+
32
+ @dataclass(frozen=True)
33
+ class BearingSpec:
34
+ """Dimensions of a standard ball-bearing cartridge (BOSL2 ball_bearing_info())."""
35
+
36
+ id: float # inner (shaft) diameter
37
+ od: float # outer diameter
38
+ width: float # axial width
39
+ shielded: bool # True for a sealed/shielded (ZZ) cartridge
40
+
41
+
42
+ _I = INCH
43
+ # trade size -> BearingSpec, transcribed from ball_bearings.scad.
44
+ _BEARINGS = {
45
+ "R2": BearingSpec(1 / 8 * _I, 3 / 8 * _I, 5 / 32 * _I, False),
46
+ "R3": BearingSpec(3 / 16 * _I, 1 / 2 * _I, 5 / 32 * _I, False),
47
+ "R4": BearingSpec(1 / 4 * _I, 5 / 8 * _I, 0.196 * _I, False),
48
+ "R6": BearingSpec(3 / 8 * _I, 7 / 8 * _I, 7 / 32 * _I, False),
49
+ "R8": BearingSpec(1 / 2 * _I, 9 / 8 * _I, 1 / 4 * _I, False),
50
+ "R10": BearingSpec(5 / 8 * _I, 11 / 8 * _I, 9 / 32 * _I, False),
51
+ "R12": BearingSpec(3 / 4 * _I, 13 / 8 * _I, 5 / 16 * _I, False),
52
+ "R14": BearingSpec(7 / 8 * _I, 15 / 8 * _I, 3 / 8 * _I, False),
53
+ "R16": BearingSpec(8 / 8 * _I, 16 / 8 * _I, 3 / 8 * _I, False),
54
+ "R18": BearingSpec(9 / 8 * _I, 17 / 8 * _I, 3 / 8 * _I, False),
55
+ "R20": BearingSpec(10 / 8 * _I, 18 / 8 * _I, 3 / 8 * _I, False),
56
+ "R22": BearingSpec(11 / 8 * _I, 20 / 8 * _I, 7 / 16 * _I, False),
57
+ "R24": BearingSpec(12 / 8 * _I, 21 / 8 * _I, 7 / 16 * _I, False),
58
+ "608": BearingSpec(8, 22, 7, False), "629": BearingSpec(9, 26, 8, False),
59
+ "635": BearingSpec(5, 19, 6, False), "6000": BearingSpec(10, 26, 8, False),
60
+ "6001": BearingSpec(12, 28, 8, False), "6002": BearingSpec(15, 32, 9, False),
61
+ "6003": BearingSpec(17, 35, 10, False), "6007": BearingSpec(35, 62, 14, False),
62
+ "6200": BearingSpec(10, 30, 9, False), "6201": BearingSpec(12, 32, 10, False),
63
+ "6202": BearingSpec(15, 35, 11, False), "6203": BearingSpec(17, 40, 12, False),
64
+ "6204": BearingSpec(20, 47, 14, False), "6205": BearingSpec(25, 52, 15, False),
65
+ "6206": BearingSpec(30, 62, 16, False), "6207": BearingSpec(35, 72, 17, False),
66
+ "6208": BearingSpec(40, 80, 18, False), "6209": BearingSpec(45, 85, 19, False),
67
+ "6210": BearingSpec(50, 90, 20, False), "6211": BearingSpec(55, 100, 21, False),
68
+ "6212": BearingSpec(60, 110, 22, False), "6301": BearingSpec(12, 37, 12, False),
69
+ "6302": BearingSpec(15, 42, 13, False), "6303": BearingSpec(17, 47, 14, False),
70
+ "6304": BearingSpec(20, 52, 15, False), "6305": BearingSpec(25, 62, 17, False),
71
+ "6306": BearingSpec(30, 72, 19, False), "6307": BearingSpec(35, 80, 21, False),
72
+ "6308": BearingSpec(40, 90, 23, False), "6309": BearingSpec(45, 100, 25, False),
73
+ "6310": BearingSpec(50, 110, 27, False), "6311": BearingSpec(55, 120, 29, False),
74
+ "6312": BearingSpec(60, 130, 31, False), "6403": BearingSpec(17, 62, 17, False),
75
+ "6800": BearingSpec(10, 19, 5, False), "6801": BearingSpec(12, 21, 5, False),
76
+ "6802": BearingSpec(15, 24, 5, False), "6803": BearingSpec(17, 26, 5, False),
77
+ "6804": BearingSpec(20, 32, 7, False), "6805": BearingSpec(25, 37, 7, False),
78
+ "6806": BearingSpec(30, 42, 7, False), "6900": BearingSpec(10, 22, 6, False),
79
+ "6901": BearingSpec(12, 24, 6, False), "6902": BearingSpec(15, 28, 7, False),
80
+ "6903": BearingSpec(17, 30, 7, False), "6904": BearingSpec(20, 37, 9, False),
81
+ "6905": BearingSpec(25, 42, 9, False), "6906": BearingSpec(30, 47, 9, False),
82
+ "6907": BearingSpec(35, 55, 10, False), "6908": BearingSpec(40, 62, 12, False),
83
+ "16002": BearingSpec(15, 22, 8, False), "16004": BearingSpec(20, 42, 8, False),
84
+ "16005": BearingSpec(25, 47, 8, False), "16100": BearingSpec(10, 28, 8, False),
85
+ "16101": BearingSpec(12, 30, 8, False),
86
+ }
87
+ # The "...ZZ" shielded variants share the open variant's dimensions.
88
+ _BEARINGS.update({
89
+ name + "ZZ": BearingSpec(s.id, s.od, s.width, True)
90
+ for name, s in list(_BEARINGS.items())
91
+ })
92
+
93
+
94
+ class BallBearings:
95
+ """Standard ball-bearing cartridge models (BOSL2 ball_bearings.scad)."""
96
+
97
+ @staticmethod
98
+ def ball_bearing_info(trade_size: str) -> BearingSpec:
99
+ """The :class:`BearingSpec` for a standard trade size, e.g. ``"608"`` / ``"6902ZZ"`` / ``"R8"``."""
100
+ try:
101
+ return _BEARINGS[str(trade_size)]
102
+ except KeyError:
103
+ raise ValueError(f"Unsupported ball bearing trade size: {trade_size!r}")
104
+
105
+ @staticmethod
106
+ def ball_bearing(trade_size: str | None = None, id: float | None = None, od: float | None = None,
107
+ width: float | None = None, shield: bool = True, color: str | None = "silver",
108
+ _fn=None, _fa=None, _fs=None) -> Bosl2Solid:
109
+ """A ball-bearing cartridge model (BOSL2 ball_bearing()).
110
+
111
+ Give a *trade_size* name, or explicit *id*/*od*/*width* (with *shield*). Returns a
112
+ :class:`~bosl2.shapes3d.Bosl2Solid` centered on the origin.
113
+
114
+ Examples:
115
+ A common 608 skate bearing:
116
+
117
+ .. pythonscad-example::
118
+
119
+ from bosl2.ball_bearings import BallBearings
120
+ BallBearings.ball_bearing("608").show()
121
+ """
122
+ if trade_size is not None:
123
+ spec = BallBearings.ball_bearing_info(trade_size)
124
+ id, od, width, shield = spec.id, spec.od, spec.width, spec.shielded
125
+ assert None not in (id, od, width), "ball_bearing(): give a trade_size or id/od/width."
126
+
127
+ mid_d = (id + od) / 2
128
+ wall = (od - id) / 2 / 3
129
+ if shield:
130
+ result = (tube(id=id, wall=wall, h=width)
131
+ | tube(od=od, wall=wall, h=width)
132
+ | tube(id=id + 0.1, od=od - 0.1, h=(wall * 2 + width) / 2))
133
+ else:
134
+ ball_cnt = int(math.floor(math.pi * mid_d * 0.95 / (wall * 2)))
135
+ races = tube(id=id, wall=wall, h=width) | tube(od=od, wall=wall, h=width)
136
+ races = races - torus(r_maj=mid_d / 2, r_min=wall)
137
+ balls = reduce(operator.or_, (
138
+ sphere(d=wall * 2, _fn=_fn, _fa=_fa, _fs=_fs).right(mid_d / 2).rotate([0, 0, i * 360 / ball_cnt])
139
+ for i in range(ball_cnt)))
140
+ result = races | balls
141
+ result = Bosl2Solid(result.shape, size=[od, od, width])
142
+ return result.color(color) if color else result