b3dkit 0.1.4__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.4 → b3dkit-0.1.5}/.coverage +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/PKG-INFO +1 -1
- {b3dkit-0.1.4 → b3dkit-0.1.5}/pyproject.toml +1 -1
- {b3dkit-0.1.4 → b3dkit-0.1.5}/src/b3dkit/slide_box.py +62 -12
- b3dkit-0.1.5/tests/test_slide_box.py +85 -0
- b3dkit-0.1.4/tests/test_slide_box.py +0 -37
- {b3dkit-0.1.4 → b3dkit-0.1.5}/.coveragerc +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/.gitignore +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/.vscode/settings.json +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/LICENSE +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/README.md +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/build.bat +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/build.sh +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/docs/Makefile +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/docs/antichamfer.md +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/docs/ball_socket.md +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/docs/basic_shapes.md +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/docs/bolt_fittings.md +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/docs/click_fit.md +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/docs/conf.py +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/docs/dovetail.md +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/docs/dovetail.png +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/docs/hexwall.md +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/docs/high_top_slide_box.md +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/docs/index.md +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/docs/point.md +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/docs/slide_box.md +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/docs/twist_snap.md +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/docs/twist_snap.png +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/mkdocs.yml +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/readthedocs.yaml +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/src/b3dkit/__init__.py +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/src/b3dkit/antichamfer.py +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/src/b3dkit/ball_socket.py +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/src/b3dkit/basic_shapes.py +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/src/b3dkit/bolt_fittings.py +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/src/b3dkit/click_fit.py +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/src/b3dkit/dovetail.py +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/src/b3dkit/hexwall.py +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/src/b3dkit/high_top_slide_box.py +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/src/b3dkit/point.py +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/src/b3dkit/twist_snap.py +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/tests/conftest.py +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/tests/test_antichamfer.py +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/tests/test_ball_socket.py +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/tests/test_basic_shapes.py +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/tests/test_bolt_fittings.py +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/tests/test_click_fit.py +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/tests/test_dovetail.py +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/tests/test_hexwall.py +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/tests/test_high_top_slide_box.py +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/tests/test_point.py +0 -0
- {b3dkit-0.1.4 → b3dkit-0.1.5}/tests/test_twist_snap.py +0 -0
|
Binary file
|
|
@@ -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,
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|