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.
- {b3dkit-0.1.3 → b3dkit-0.1.5}/.coverage +0 -0
- {b3dkit-0.1.3 → b3dkit-0.1.5}/PKG-INFO +1 -1
- {b3dkit-0.1.3 → b3dkit-0.1.5}/docs/point.md +8 -0
- {b3dkit-0.1.3 → b3dkit-0.1.5}/pyproject.toml +1 -1
- {b3dkit-0.1.3 → b3dkit-0.1.5}/src/b3dkit/hexwall.py +5 -6
- {b3dkit-0.1.3 → b3dkit-0.1.5}/src/b3dkit/point.py +16 -0
- {b3dkit-0.1.3 → b3dkit-0.1.5}/src/b3dkit/slide_box.py +62 -12
- {b3dkit-0.1.3 → b3dkit-0.1.5}/tests/test_point.py +5 -0
- b3dkit-0.1.5/tests/test_slide_box.py +85 -0
- b3dkit-0.1.3/tests/test_slide_box.py +0 -37
- {b3dkit-0.1.3 → b3dkit-0.1.5}/.coveragerc +0 -0
- {b3dkit-0.1.3 → b3dkit-0.1.5}/.gitignore +0 -0
- {b3dkit-0.1.3 → b3dkit-0.1.5}/.vscode/settings.json +0 -0
- {b3dkit-0.1.3 → b3dkit-0.1.5}/LICENSE +0 -0
- {b3dkit-0.1.3 → b3dkit-0.1.5}/README.md +0 -0
- {b3dkit-0.1.3 → b3dkit-0.1.5}/build.bat +0 -0
- {b3dkit-0.1.3 → b3dkit-0.1.5}/build.sh +0 -0
- {b3dkit-0.1.3 → b3dkit-0.1.5}/docs/Makefile +0 -0
- {b3dkit-0.1.3 → b3dkit-0.1.5}/docs/antichamfer.md +0 -0
- {b3dkit-0.1.3 → b3dkit-0.1.5}/docs/ball_socket.md +0 -0
- {b3dkit-0.1.3 → b3dkit-0.1.5}/docs/basic_shapes.md +0 -0
- {b3dkit-0.1.3 → b3dkit-0.1.5}/docs/bolt_fittings.md +0 -0
- {b3dkit-0.1.3 → b3dkit-0.1.5}/docs/click_fit.md +0 -0
- {b3dkit-0.1.3 → b3dkit-0.1.5}/docs/conf.py +0 -0
- {b3dkit-0.1.3 → b3dkit-0.1.5}/docs/dovetail.md +0 -0
- {b3dkit-0.1.3 → b3dkit-0.1.5}/docs/dovetail.png +0 -0
- {b3dkit-0.1.3 → b3dkit-0.1.5}/docs/hexwall.md +0 -0
- {b3dkit-0.1.3 → b3dkit-0.1.5}/docs/high_top_slide_box.md +0 -0
- {b3dkit-0.1.3 → b3dkit-0.1.5}/docs/index.md +0 -0
- {b3dkit-0.1.3 → b3dkit-0.1.5}/docs/slide_box.md +0 -0
- {b3dkit-0.1.3 → b3dkit-0.1.5}/docs/twist_snap.md +0 -0
- {b3dkit-0.1.3 → b3dkit-0.1.5}/docs/twist_snap.png +0 -0
- {b3dkit-0.1.3 → b3dkit-0.1.5}/mkdocs.yml +0 -0
- {b3dkit-0.1.3 → b3dkit-0.1.5}/readthedocs.yaml +0 -0
- {b3dkit-0.1.3 → b3dkit-0.1.5}/src/b3dkit/__init__.py +0 -0
- {b3dkit-0.1.3 → b3dkit-0.1.5}/src/b3dkit/antichamfer.py +0 -0
- {b3dkit-0.1.3 → b3dkit-0.1.5}/src/b3dkit/ball_socket.py +0 -0
- {b3dkit-0.1.3 → b3dkit-0.1.5}/src/b3dkit/basic_shapes.py +0 -0
- {b3dkit-0.1.3 → b3dkit-0.1.5}/src/b3dkit/bolt_fittings.py +0 -0
- {b3dkit-0.1.3 → b3dkit-0.1.5}/src/b3dkit/click_fit.py +0 -0
- {b3dkit-0.1.3 → b3dkit-0.1.5}/src/b3dkit/dovetail.py +0 -0
- {b3dkit-0.1.3 → b3dkit-0.1.5}/src/b3dkit/high_top_slide_box.py +0 -0
- {b3dkit-0.1.3 → b3dkit-0.1.5}/src/b3dkit/twist_snap.py +0 -0
- {b3dkit-0.1.3 → b3dkit-0.1.5}/tests/conftest.py +0 -0
- {b3dkit-0.1.3 → b3dkit-0.1.5}/tests/test_antichamfer.py +0 -0
- {b3dkit-0.1.3 → b3dkit-0.1.5}/tests/test_ball_socket.py +0 -0
- {b3dkit-0.1.3 → b3dkit-0.1.5}/tests/test_basic_shapes.py +0 -0
- {b3dkit-0.1.3 → b3dkit-0.1.5}/tests/test_bolt_fittings.py +0 -0
- {b3dkit-0.1.3 → b3dkit-0.1.5}/tests/test_click_fit.py +0 -0
- {b3dkit-0.1.3 → b3dkit-0.1.5}/tests/test_dovetail.py +0 -0
- {b3dkit-0.1.3 → b3dkit-0.1.5}/tests/test_hexwall.py +0 -0
- {b3dkit-0.1.3 → b3dkit-0.1.5}/tests/test_high_top_slide_box.py +0 -0
- {b3dkit-0.1.3 → b3dkit-0.1.5}/tests/test_twist_snap.py +0 -0
|
Binary file
|
|
@@ -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.
|
|
@@ -220,11 +220,10 @@ class HexCylindrical(BasePartObject):
|
|
|
220
220
|
|
|
221
221
|
|
|
222
222
|
if __name__ == "__main__":
|
|
223
|
-
|
|
224
|
-
|
|
223
|
+
cone = Cone(100, 50, 100, align=(Align.CENTER, Align.CENTER, Align.MIN))
|
|
224
|
+
cylinder = Cylinder(100, 100)
|
|
225
225
|
|
|
226
|
-
|
|
227
|
-
|
|
226
|
+
part = cone
|
|
227
|
+
hexsample = HexCylindrical(part, 1, 2, 20, 10, 2, 20)
|
|
228
228
|
|
|
229
|
-
|
|
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(
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|