b3dkit 0.1.3__tar.gz → 0.1.5__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 (53) hide show
  1. {b3dkit-0.1.3 → b3dkit-0.1.5}/.coverage +0 -0
  2. {b3dkit-0.1.3 → b3dkit-0.1.5}/PKG-INFO +1 -1
  3. {b3dkit-0.1.3 → b3dkit-0.1.5}/docs/point.md +8 -0
  4. {b3dkit-0.1.3 → b3dkit-0.1.5}/pyproject.toml +1 -1
  5. {b3dkit-0.1.3 → b3dkit-0.1.5}/src/b3dkit/hexwall.py +5 -6
  6. {b3dkit-0.1.3 → b3dkit-0.1.5}/src/b3dkit/point.py +16 -0
  7. {b3dkit-0.1.3 → b3dkit-0.1.5}/src/b3dkit/slide_box.py +62 -12
  8. {b3dkit-0.1.3 → b3dkit-0.1.5}/tests/test_point.py +5 -0
  9. b3dkit-0.1.5/tests/test_slide_box.py +85 -0
  10. b3dkit-0.1.3/tests/test_slide_box.py +0 -37
  11. {b3dkit-0.1.3 → b3dkit-0.1.5}/.coveragerc +0 -0
  12. {b3dkit-0.1.3 → b3dkit-0.1.5}/.gitignore +0 -0
  13. {b3dkit-0.1.3 → b3dkit-0.1.5}/.vscode/settings.json +0 -0
  14. {b3dkit-0.1.3 → b3dkit-0.1.5}/LICENSE +0 -0
  15. {b3dkit-0.1.3 → b3dkit-0.1.5}/README.md +0 -0
  16. {b3dkit-0.1.3 → b3dkit-0.1.5}/build.bat +0 -0
  17. {b3dkit-0.1.3 → b3dkit-0.1.5}/build.sh +0 -0
  18. {b3dkit-0.1.3 → b3dkit-0.1.5}/docs/Makefile +0 -0
  19. {b3dkit-0.1.3 → b3dkit-0.1.5}/docs/antichamfer.md +0 -0
  20. {b3dkit-0.1.3 → b3dkit-0.1.5}/docs/ball_socket.md +0 -0
  21. {b3dkit-0.1.3 → b3dkit-0.1.5}/docs/basic_shapes.md +0 -0
  22. {b3dkit-0.1.3 → b3dkit-0.1.5}/docs/bolt_fittings.md +0 -0
  23. {b3dkit-0.1.3 → b3dkit-0.1.5}/docs/click_fit.md +0 -0
  24. {b3dkit-0.1.3 → b3dkit-0.1.5}/docs/conf.py +0 -0
  25. {b3dkit-0.1.3 → b3dkit-0.1.5}/docs/dovetail.md +0 -0
  26. {b3dkit-0.1.3 → b3dkit-0.1.5}/docs/dovetail.png +0 -0
  27. {b3dkit-0.1.3 → b3dkit-0.1.5}/docs/hexwall.md +0 -0
  28. {b3dkit-0.1.3 → b3dkit-0.1.5}/docs/high_top_slide_box.md +0 -0
  29. {b3dkit-0.1.3 → b3dkit-0.1.5}/docs/index.md +0 -0
  30. {b3dkit-0.1.3 → b3dkit-0.1.5}/docs/slide_box.md +0 -0
  31. {b3dkit-0.1.3 → b3dkit-0.1.5}/docs/twist_snap.md +0 -0
  32. {b3dkit-0.1.3 → b3dkit-0.1.5}/docs/twist_snap.png +0 -0
  33. {b3dkit-0.1.3 → b3dkit-0.1.5}/mkdocs.yml +0 -0
  34. {b3dkit-0.1.3 → b3dkit-0.1.5}/readthedocs.yaml +0 -0
  35. {b3dkit-0.1.3 → b3dkit-0.1.5}/src/b3dkit/__init__.py +0 -0
  36. {b3dkit-0.1.3 → b3dkit-0.1.5}/src/b3dkit/antichamfer.py +0 -0
  37. {b3dkit-0.1.3 → b3dkit-0.1.5}/src/b3dkit/ball_socket.py +0 -0
  38. {b3dkit-0.1.3 → b3dkit-0.1.5}/src/b3dkit/basic_shapes.py +0 -0
  39. {b3dkit-0.1.3 → b3dkit-0.1.5}/src/b3dkit/bolt_fittings.py +0 -0
  40. {b3dkit-0.1.3 → b3dkit-0.1.5}/src/b3dkit/click_fit.py +0 -0
  41. {b3dkit-0.1.3 → b3dkit-0.1.5}/src/b3dkit/dovetail.py +0 -0
  42. {b3dkit-0.1.3 → b3dkit-0.1.5}/src/b3dkit/high_top_slide_box.py +0 -0
  43. {b3dkit-0.1.3 → b3dkit-0.1.5}/src/b3dkit/twist_snap.py +0 -0
  44. {b3dkit-0.1.3 → b3dkit-0.1.5}/tests/conftest.py +0 -0
  45. {b3dkit-0.1.3 → b3dkit-0.1.5}/tests/test_antichamfer.py +0 -0
  46. {b3dkit-0.1.3 → b3dkit-0.1.5}/tests/test_ball_socket.py +0 -0
  47. {b3dkit-0.1.3 → b3dkit-0.1.5}/tests/test_basic_shapes.py +0 -0
  48. {b3dkit-0.1.3 → b3dkit-0.1.5}/tests/test_bolt_fittings.py +0 -0
  49. {b3dkit-0.1.3 → b3dkit-0.1.5}/tests/test_click_fit.py +0 -0
  50. {b3dkit-0.1.3 → b3dkit-0.1.5}/tests/test_dovetail.py +0 -0
  51. {b3dkit-0.1.3 → b3dkit-0.1.5}/tests/test_hexwall.py +0 -0
  52. {b3dkit-0.1.3 → b3dkit-0.1.5}/tests/test_high_top_slide_box.py +0 -0
  53. {b3dkit-0.1.3 → b3dkit-0.1.5}/tests/test_twist_snap.py +0 -0
Binary file
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: b3dkit
3
- Version: 0.1.3
3
+ Version: 0.1.5
4
4
  Summary: build123d libraries and utilities
5
5
  Project-URL: Homepage, https://github.com/x0pherl/b3dkit
6
6
  Project-URL: Issues, https://github.com/x0pherl/b3dkit/issues
@@ -41,6 +41,14 @@ p[1] # returns the y coordinate (3)
41
41
  Point(0,10).distance_to(Point(10,10)) # returns 10.0
42
42
  ```
43
43
 
44
+ #### axial)distance_to
45
+ - `axial_distance_to(point: Point, axis: Axis) -> float`
46
+ - Identifies the distance along a single Axis to a second point from the current point.
47
+ ```
48
+ Point(0,10).axial_distance_to(Point(10,-10), Axis.X) # returns 10.0
49
+ Point(0,10).axial_distance_to(Point(10,-10), Axis.Y) # returns 20.0
50
+ ```
51
+
44
52
  #### related_point
45
53
  - `related_point(angle: float, distance: float) -> Point`
46
54
  - Identifies a second point at a specified angle and distance from the current point.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "b3dkit"
3
- version = "0.1.3"
3
+ version = "0.1.5"
4
4
  authors = [
5
5
  { name="x0pherl"},
6
6
  ]
@@ -220,11 +220,10 @@ class HexCylindrical(BasePartObject):
220
220
 
221
221
 
222
222
  if __name__ == "__main__":
223
- # cone = Cone(100, 50, 100, align=(Align.CENTER, Align.CENTER, Align.MIN))
224
- # cylinder = Cylinder(100, 100)
223
+ cone = Cone(100, 50, 100, align=(Align.CENTER, Align.CENTER, Align.MIN))
224
+ cylinder = Cylinder(100, 100)
225
225
 
226
- # part = cone
227
- # hexsample = HexCylindrical(part, 1, 2, 20, 10, 2, 20)
226
+ part = cone
227
+ hexsample = HexCylindrical(part, 1, 2, 20, 10, 2, 20)
228
228
 
229
- # show(part, hexsample)
230
- show(HexWall(100, 200, 3, 4, 1))
229
+ show(part, hexsample)
@@ -91,6 +91,22 @@ class Point:
91
91
  - float: The Euclidean distance between the two points"""
92
92
  return ((self.x - point.x) ** 2 + (self.y - point.y) ** 2) ** 0.5
93
93
 
94
+ def axial_distance_to(self, point: "Point", axis: Axis) -> float:
95
+ """from the point, identify the distance to a second point
96
+ ----------
97
+ Arguments:
98
+ - point: Point
99
+ The target point to calculate distance to
100
+ - axis: Axis
101
+ The Axis along which to measure the difference
102
+ Returns:
103
+ - float: The distance along the passed axis between the two points"""
104
+ return (
105
+ Point(self.X, 0).distance_to(Point(point.X, 0))
106
+ if axis == Axis.X
107
+ else Point(0, self.y).distance_to(Point(0, point.Y))
108
+ )
109
+
94
110
  def related_point(self, angle: float, distance: float) -> "Point":
95
111
  """from the point, identify a second point at a specified angle and distance
96
112
  ----------
@@ -1,3 +1,5 @@
1
+ from math import radians, tan
2
+
1
3
  from build123d import (
2
4
  Align,
3
5
  BuildPart,
@@ -25,6 +27,45 @@ from build123d import (
25
27
  from ocp_vscode import show, Camera
26
28
  from b3dkit import Divot
27
29
 
30
+ #: the draft applied to the sliding faces; the divot placement below depends on
31
+ #: this matching the taper passed to extrude()
32
+ SLIDER_TAPER_ANGLE = 22.5
33
+
34
+
35
+ def _divot_spacing(
36
+ sketch_width: float,
37
+ wall_thickness: float,
38
+ tolerance: float,
39
+ x_straighten_distance: float,
40
+ divot_radius: float,
41
+ ) -> float:
42
+ """
43
+ Determine the x distance between the pair of divots on a slider template.
44
+
45
+ The divots would naturally sit one wall thickness inboard of the sketch, but
46
+ the sliding faces are tapered, so the widest part of a divot -- the top of its
47
+ extended base, one radius above the underside of the rail -- can graze the
48
+ tapered face. A grazing intersection leaves needle-thin slivers behind the
49
+ boolean, so pull the pair far enough inboard that a divot only ever meets the
50
+ flat underside.
51
+ """
52
+ half_width = sketch_width / 2
53
+ # depth below the top of the template at the widest point of the divot
54
+ widest_depth = max(wall_thickness - divot_radius, 0)
55
+ tapered_face = (
56
+ half_width
57
+ - wall_thickness
58
+ - abs(tolerance)
59
+ + widest_depth * tan(radians(SLIDER_TAPER_ANGLE))
60
+ )
61
+ return (
62
+ min(
63
+ half_width - x_straighten_distance - wall_thickness,
64
+ tapered_face - divot_radius - divot_radius / 5,
65
+ )
66
+ * 2
67
+ )
68
+
28
69
 
29
70
  def slider_template(
30
71
  sketch: Sketch,
@@ -41,7 +82,11 @@ def slider_template(
41
82
  with BuildPart() as slider_part:
42
83
  with BuildSketch() as top_sketch:
43
84
  offset(sketch, amount=-abs(tolerance) - (abs(wall_thickness)))
44
- extrude(top_sketch.sketch, amount=-wall_thickness - abs(tolerance), taper=-22.5)
85
+ extrude(
86
+ top_sketch.sketch,
87
+ amount=-wall_thickness - abs(tolerance),
88
+ taper=-SLIDER_TAPER_ANGLE,
89
+ )
45
90
  cross_section = section(
46
91
  obj=slider_part.part,
47
92
  section_by=Plane.XZ.offset(
@@ -56,21 +101,26 @@ def slider_template(
56
101
  )
57
102
  )
58
103
  if divot_radius > 0:
104
+ # a divot wider than half the wall would overhang the open front of
105
+ # the template; keep it far enough back to meet only the underside
106
+ divot_y = max(
107
+ sketch.bounding_box().min.Y + wall_thickness / 2,
108
+ slider_part.part.bounding_box().min.Y
109
+ + divot_radius
110
+ + divot_radius / 5,
111
+ )
59
112
  with BuildPart(
60
- Location(
61
- (
62
- 0,
63
- sketch.bounding_box().min.Y + wall_thickness / 2,
64
- -wall_thickness,
65
- ),
66
- (180, 0, 0),
67
- ),
113
+ Location((0, divot_y, -wall_thickness), (180, 0, 0)),
68
114
  mode=Mode.ADD,
69
115
  ):
70
116
  with GridLocations(
71
- sketch.bounding_box().size.X
72
- - x_straighten_distance * 2
73
- - wall_thickness * 2,
117
+ _divot_spacing(
118
+ sketch.bounding_box().size.X,
119
+ wall_thickness,
120
+ tolerance,
121
+ x_straighten_distance,
122
+ divot_radius,
123
+ ),
74
124
  0,
75
125
  2,
76
126
  1,
@@ -41,6 +41,11 @@ class TestPoint:
41
41
  angle = p1.angle_to(p2)
42
42
  assert angle == pytest.approx(45.0)
43
43
 
44
+ def test_axial_distance_to(self):
45
+ assert Point(20, 2).axial_distance_to(Point(-20, 2), Axis.X) == pytest.approx(
46
+ 40.0
47
+ )
48
+
44
49
  def test_distance_to(self):
45
50
  p1 = Point(0, 0)
46
51
  p2 = Point(3, 4)
@@ -0,0 +1,85 @@
1
+ from dataclasses import dataclass, field
2
+ from enum import Enum, auto
3
+ from importlib.machinery import SourceFileLoader
4
+ from importlib.util import module_from_spec, spec_from_loader
5
+ import pytest
6
+ from unittest.mock import patch
7
+ from pathlib import Path
8
+
9
+ from build123d import Axis, BuildPart, Box, Align, Plane, fillet, section
10
+
11
+ from b3dkit.slide_box import slide_box, slider_template
12
+
13
+
14
+ class TestSlideBox:
15
+ def test_slide_box(self):
16
+ with BuildPart() as base_box:
17
+ Box(20, 44, 14, align=(Align.CENTER, Align.CENTER, Align.MIN))
18
+ fillet(base_box.part.edges().filter_by(Axis.Z), radius=1.5)
19
+
20
+ sb = slide_box(
21
+ base_box.part, wall_thickness=2, thumb_radius=3.5, divot_radius=0.5
22
+ )
23
+ assert sb.children[0].is_valid
24
+ assert sb.children[1].is_valid
25
+
26
+ def test_direct_run(self):
27
+
28
+ with (
29
+ patch("build123d.export_stl"),
30
+ patch("pathlib.Path.mkdir"),
31
+ patch("pathlib.Path.exists"),
32
+ patch("pathlib.Path.is_dir"),
33
+ patch("ocp_vscode.show"),
34
+ patch("ocp_vscode.save_screenshot"),
35
+ ):
36
+ loader = SourceFileLoader("__main__", "src/b3dkit/slide_box.py")
37
+ loader.exec_module(module_from_spec(spec_from_loader(loader.name, loader)))
38
+
39
+
40
+ class TestSliderDivots:
41
+ """
42
+ The divots sit against the tapered sliding faces of the template. Placing one
43
+ so that it merely grazes a tapered face leaves needle-thin slivers behind the
44
+ boolean, which show up as a nick in the rail the lid slides through.
45
+
46
+ A pair of divots must therefore contribute exactly two solids, and each must
47
+ be a real divot rather than a sliver -- the two failure modes are a doubled
48
+ solid count and a solid orders of magnitude too small.
49
+ """
50
+
51
+ @pytest.fixture(scope="class")
52
+ def sketch(self):
53
+ with BuildPart() as blank:
54
+ Box(60, 100, 20, align=(Align.CENTER, Align.CENTER, Align.MIN))
55
+ fillet(blank.part.edges().filter_by(Axis.Z), radius=1.5)
56
+ return section(obj=blank.part, section_by=Plane.XY.offset(20))
57
+
58
+ @staticmethod
59
+ def _divot_solids(sketch, wall, radius, tolerance):
60
+ """the geometry the divots add to an otherwise identical template"""
61
+ with_divots = slider_template(
62
+ sketch, wall, tolerance=tolerance, divot_radius=radius, cut_template=True
63
+ )
64
+ without_divots = slider_template(
65
+ sketch, wall, tolerance=tolerance, divot_radius=0, cut_template=True
66
+ )
67
+ return (with_divots - without_divots).solids()
68
+
69
+ @pytest.mark.parametrize("wall", [2.0, 3.0, 4.0])
70
+ @pytest.mark.parametrize("radius", [0.5, 0.75, 0.9, 1.0, 1.25])
71
+ def test_divots_contribute_exactly_two_solids(self, sketch, wall, radius):
72
+ # the sweep matters -- the tangency only bites on some wall/radius
73
+ # combinations, so any single pairing passes on its own
74
+ solids = self._divot_solids(sketch, wall, radius, tolerance=0.2)
75
+
76
+ assert len(solids) == 2
77
+ assert min(s.volume for s in solids) > radius**3 * 1e-2
78
+
79
+ def test_wide_divot_clears_the_open_front(self, sketch):
80
+ # a divot wider than half the wall used to overhang the front face of
81
+ # the template, slivering against it the same way the taper did
82
+ solids = self._divot_solids(sketch, wall=1.5, radius=1.25, tolerance=0.3)
83
+
84
+ assert len(solids) == 2
85
+ assert min(s.volume for s in solids) > 1.25**3 * 1e-2
@@ -1,37 +0,0 @@
1
- from dataclasses import dataclass, field
2
- from enum import Enum, auto
3
- from importlib.machinery import SourceFileLoader
4
- from importlib.util import module_from_spec, spec_from_loader
5
- import pytest
6
- from unittest.mock import patch
7
- from pathlib import Path
8
-
9
- from build123d import Axis, BuildPart, Box, Align, fillet
10
-
11
- from b3dkit.slide_box import slide_box
12
-
13
-
14
- class TestSlideBox:
15
- def test_slide_box(self):
16
- with BuildPart() as base_box:
17
- Box(20, 44, 14, align=(Align.CENTER, Align.CENTER, Align.MIN))
18
- fillet(base_box.part.edges().filter_by(Axis.Z), radius=1.5)
19
-
20
- sb = slide_box(
21
- base_box.part, wall_thickness=2, thumb_radius=3.5, divot_radius=0.5
22
- )
23
- assert sb.children[0].is_valid
24
- assert sb.children[1].is_valid
25
-
26
- def test_direct_run(self):
27
-
28
- with (
29
- patch("build123d.export_stl"),
30
- patch("pathlib.Path.mkdir"),
31
- patch("pathlib.Path.exists"),
32
- patch("pathlib.Path.is_dir"),
33
- patch("ocp_vscode.show"),
34
- patch("ocp_vscode.save_screenshot"),
35
- ):
36
- loader = SourceFileLoader("__main__", "src/b3dkit/slide_box.py")
37
- loader.exec_module(module_from_spec(spec_from_loader(loader.name, loader)))
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes