pybosl2 0.5.0__py3-none-any.whl

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 (60) hide show
  1. bosl2/__init__.py +46 -0
  2. bosl2/_mctable.py +127 -0
  3. bosl2/ball_bearings.py +142 -0
  4. bosl2/beziers.py +778 -0
  5. bosl2/bottlecaps.py +229 -0
  6. bosl2/color.py +198 -0
  7. bosl2/comparisons.py +36 -0
  8. bosl2/constants.py +105 -0
  9. bosl2/cubetruss.py +391 -0
  10. bosl2/distributors.py +554 -0
  11. bosl2/drawing.py +712 -0
  12. bosl2/gears.py +876 -0
  13. bosl2/geometry.py +155 -0
  14. bosl2/hinges.py +134 -0
  15. bosl2/isosurface.py +457 -0
  16. bosl2/joiners.py +113 -0
  17. bosl2/linear_bearings.py +126 -0
  18. bosl2/masking.py +403 -0
  19. bosl2/math.py +154 -0
  20. bosl2/miscellaneous.py +417 -0
  21. bosl2/modular_hose.py +144 -0
  22. bosl2/nema_steppers.py +131 -0
  23. bosl2/nurbs.py +480 -0
  24. bosl2/partitions.py +541 -0
  25. bosl2/paths.py +1554 -0
  26. bosl2/polyhedra.py +179 -0
  27. bosl2/regions.py +115 -0
  28. bosl2/rounding.py +299 -0
  29. bosl2/screw_drive.py +348 -0
  30. bosl2/screws.py +431 -0
  31. bosl2/shapes2d.py +1508 -0
  32. bosl2/shapes3d.py +3062 -0
  33. bosl2/skin.py +735 -0
  34. bosl2/sliders.py +124 -0
  35. bosl2/threading.py +327 -0
  36. bosl2/transforms.py +189 -0
  37. bosl2/vectors.py +61 -0
  38. bosl2/version.py +89 -0
  39. bosl2/vnf.py +289 -0
  40. bosl2/walls.py +269 -0
  41. pybosl2-0.5.0.dist-info/METADATA +130 -0
  42. pybosl2-0.5.0.dist-info/RECORD +60 -0
  43. pybosl2-0.5.0.dist-info/WHEEL +5 -0
  44. pybosl2-0.5.0.dist-info/licenses/LICENSE +24 -0
  45. pybosl2-0.5.0.dist-info/top_level.txt +2 -0
  46. pysolidfive/__init__.py +246 -0
  47. pysolidfive/_constants.py +80 -0
  48. pysolidfive/_edges.py +181 -0
  49. pysolidfive/docs/_ext/__init__.py +0 -0
  50. pysolidfive/docs/_ext/pythonscad_example.py +128 -0
  51. pysolidfive/docs/conf.py +85 -0
  52. pysolidfive/joiners.py +236 -0
  53. pysolidfive/paths.py +803 -0
  54. pysolidfive/shapes2d.py +445 -0
  55. pysolidfive/shapes3d.py +1744 -0
  56. pysolidfive/tests/__init__.py +34 -0
  57. pysolidfive/tests/mock_libfive.py +421 -0
  58. pysolidfive/tests/render_pysolidfive.py +246 -0
  59. pysolidfive/tests/test_pysolidfive.py +1116 -0
  60. pysolidfive/tests/test_pysolidfive_render.py +171 -0
bosl2/__init__.py ADDED
@@ -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__"]
bosl2/_mctable.py ADDED
@@ -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
+ ]
bosl2/ball_bearings.py ADDED
@@ -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