b3dkit 0.1.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.
- b3dkit/__init__.py +10 -0
- b3dkit/antichamfer.py +79 -0
- b3dkit/ball_socket.py +177 -0
- b3dkit/basic_shapes.py +397 -0
- b3dkit/bolt_fittings.py +351 -0
- b3dkit/click_fit.py +78 -0
- b3dkit/dovetail.py +1338 -0
- b3dkit/hexwall.py +102 -0
- b3dkit/high_top_slide_box.py +504 -0
- b3dkit/point.py +196 -0
- b3dkit/slide_box.py +210 -0
- b3dkit/twist_snap.py +302 -0
- b3dkit-0.1.0.dist-info/METADATA +54 -0
- b3dkit-0.1.0.dist-info/RECORD +16 -0
- b3dkit-0.1.0.dist-info/WHEEL +4 -0
- b3dkit-0.1.0.dist-info/licenses/LICENSE +7 -0
b3dkit/hexwall.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""
|
|
2
|
+
HexWall
|
|
3
|
+
|
|
4
|
+
name: hexwall.py
|
|
5
|
+
by: x0pherl
|
|
6
|
+
date: Jan 19th 2025
|
|
7
|
+
|
|
8
|
+
desc:
|
|
9
|
+
This build123d python module creates a hexagonal pattern within a box, resulting
|
|
10
|
+
in a grid of hexagons.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from math import sqrt
|
|
14
|
+
from typing import Union
|
|
15
|
+
|
|
16
|
+
from build123d import (
|
|
17
|
+
Align,
|
|
18
|
+
Axis,
|
|
19
|
+
BasePartObject,
|
|
20
|
+
Box,
|
|
21
|
+
BuildPart,
|
|
22
|
+
BuildSketch,
|
|
23
|
+
HexLocations,
|
|
24
|
+
Mode,
|
|
25
|
+
Part,
|
|
26
|
+
RegularPolygon,
|
|
27
|
+
RotationLike,
|
|
28
|
+
extrude,
|
|
29
|
+
tuplify,
|
|
30
|
+
)
|
|
31
|
+
from ocp_vscode import Camera, show
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class HexWall(BasePartObject):
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
length: float,
|
|
39
|
+
width: float,
|
|
40
|
+
height: float,
|
|
41
|
+
apothem: float,
|
|
42
|
+
wall_thickness: float,
|
|
43
|
+
inverse=False,
|
|
44
|
+
rotation: RotationLike = (0, 0, 0),
|
|
45
|
+
align: Union[None, Align, tuple[Align, Align, Align]] = None,
|
|
46
|
+
mode: Mode = Mode.ADD,
|
|
47
|
+
):
|
|
48
|
+
"""
|
|
49
|
+
Part Object: hexwall
|
|
50
|
+
-------
|
|
51
|
+
arguments:
|
|
52
|
+
- length (float): box size
|
|
53
|
+
- width (float): box size
|
|
54
|
+
- height (float): box size
|
|
55
|
+
- apothem (float): the distance between two paralel edges of the hexagon
|
|
56
|
+
- rotation (RotationLike, optional): angles to rotate about axes. Defaults to (0, 0, 0)
|
|
57
|
+
- align (Align | tuple[Align, Align, Align] | None, optional): align MIN, CENTER,
|
|
58
|
+
or MAX of object. Defaults to (Align.CENTER, Align.CENTER, Align.CENTER)
|
|
59
|
+
- mode (Mode, optional): combine mode. Defaults to Mode.ADD
|
|
60
|
+
"""
|
|
61
|
+
with BuildPart() as wall:
|
|
62
|
+
hexwall_radius = 2 * sqrt(3) / 3 * apothem
|
|
63
|
+
hexwall_xcount = int(length // ((sqrt(3) / 2 * apothem) / 2)) + 2
|
|
64
|
+
if hexwall_xcount % 2 == 0:
|
|
65
|
+
hexwall_xcount += 1
|
|
66
|
+
Box(length=length, width=width, height=height, align=tuplify(align, 3))
|
|
67
|
+
combine_mode = Mode.INTERSECT if inverse else Mode.SUBTRACT
|
|
68
|
+
with BuildPart(mode=combine_mode):
|
|
69
|
+
with BuildSketch(wall.faces().sort_by(Axis.Z)[0]) as sk:
|
|
70
|
+
with HexLocations(
|
|
71
|
+
radius=hexwall_radius,
|
|
72
|
+
x_count=hexwall_xcount,
|
|
73
|
+
y_count=int(width // apothem / 2) + 2,
|
|
74
|
+
align=(Align.CENTER, Align.CENTER),
|
|
75
|
+
):
|
|
76
|
+
RegularPolygon(
|
|
77
|
+
radius=2 * sqrt(3) / 3 * (apothem - wall_thickness / 2),
|
|
78
|
+
major_radius=False,
|
|
79
|
+
side_count=6,
|
|
80
|
+
)
|
|
81
|
+
extrude(sk.sketch, -height)
|
|
82
|
+
part = wall.part
|
|
83
|
+
part.label = "hexwall"
|
|
84
|
+
|
|
85
|
+
super().__init__(
|
|
86
|
+
part=part, rotation=rotation, align=tuplify(align, 3), mode=mode
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
if __name__ == "__main__":
|
|
91
|
+
show(
|
|
92
|
+
HexWall(
|
|
93
|
+
width=200,
|
|
94
|
+
length=400,
|
|
95
|
+
height=2,
|
|
96
|
+
apothem=10,
|
|
97
|
+
wall_thickness=2,
|
|
98
|
+
inverse=True,
|
|
99
|
+
align=(Align.CENTER, Align.CENTER, Align.MIN),
|
|
100
|
+
),
|
|
101
|
+
reset_camera=Camera.KEEP,
|
|
102
|
+
)
|
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
from build123d import (
|
|
2
|
+
Align,
|
|
3
|
+
Axis,
|
|
4
|
+
Box,
|
|
5
|
+
BuildLine,
|
|
6
|
+
BuildPart,
|
|
7
|
+
BuildSketch,
|
|
8
|
+
Compound,
|
|
9
|
+
GridLocations,
|
|
10
|
+
Location,
|
|
11
|
+
Part,
|
|
12
|
+
PolarLocations,
|
|
13
|
+
Polyline,
|
|
14
|
+
Mode,
|
|
15
|
+
add,
|
|
16
|
+
extrude,
|
|
17
|
+
fillet,
|
|
18
|
+
make_face,
|
|
19
|
+
pack,
|
|
20
|
+
)
|
|
21
|
+
from dataclasses import field
|
|
22
|
+
from b3dkit.point import Point, midpoint
|
|
23
|
+
from b3dkit.basic_shapes import (
|
|
24
|
+
DiamondCylinder,
|
|
25
|
+
opposite_length,
|
|
26
|
+
)
|
|
27
|
+
from b3dkit.click_fit import Divot
|
|
28
|
+
|
|
29
|
+
from ocp_vscode import show, Camera
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _slide_top_rail_cut(
|
|
33
|
+
part_width: float,
|
|
34
|
+
part_depth: float,
|
|
35
|
+
rail_height: float,
|
|
36
|
+
wall_thickness: float,
|
|
37
|
+
rail_angle: float = 0,
|
|
38
|
+
effective_tolerance: float = 0.05,
|
|
39
|
+
):
|
|
40
|
+
"""
|
|
41
|
+
Creates the rail cut geometry for the sliding mechanism of a high top slide box.
|
|
42
|
+
|
|
43
|
+
This internal function generates the precise cut pattern needed for the sliding rails,
|
|
44
|
+
including diamond-shaped guides and tolerance adjustments. The rails allow smooth
|
|
45
|
+
sliding motion while maintaining secure positioning.
|
|
46
|
+
|
|
47
|
+
args:
|
|
48
|
+
- part_width: the width of the base part in millimeters
|
|
49
|
+
- part_depth: the depth of the base part in millimeters
|
|
50
|
+
- rail_height: the height of the rail system in millimeters
|
|
51
|
+
- wall_thickness: the thickness of the box walls in millimeters
|
|
52
|
+
- rail_angle: the angle of the rails in degrees for improved sliding
|
|
53
|
+
- effective_tolerance: the calculated tolerance for the sliding fit
|
|
54
|
+
"""
|
|
55
|
+
rail_front_right = Point(
|
|
56
|
+
(part_width - wall_thickness * 2 / 3 + effective_tolerance) / 2,
|
|
57
|
+
(part_depth - wall_thickness) / 2,
|
|
58
|
+
)
|
|
59
|
+
rail_back_right = Point(
|
|
60
|
+
(
|
|
61
|
+
rail_front_right.x
|
|
62
|
+
- (
|
|
63
|
+
0
|
|
64
|
+
if (rail_angle == 0)
|
|
65
|
+
else opposite_length(
|
|
66
|
+
rail_angle, adjacent_length=part_depth - wall_thickness
|
|
67
|
+
)
|
|
68
|
+
)
|
|
69
|
+
),
|
|
70
|
+
-(part_depth - wall_thickness) / 2,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
with BuildPart(
|
|
74
|
+
Location((0, wall_thickness / 2, 0)),
|
|
75
|
+
mode=Mode.SUBTRACT,
|
|
76
|
+
) as rail_cut:
|
|
77
|
+
with BuildSketch(Location((0, wall_thickness / 2))):
|
|
78
|
+
with BuildLine():
|
|
79
|
+
|
|
80
|
+
Polyline(
|
|
81
|
+
rail_front_right,
|
|
82
|
+
rail_back_right,
|
|
83
|
+
Point(-(rail_back_right.x), rail_back_right.y),
|
|
84
|
+
Point(-(rail_front_right.x), rail_front_right.y),
|
|
85
|
+
rail_front_right,
|
|
86
|
+
)
|
|
87
|
+
make_face()
|
|
88
|
+
extrude(amount=rail_height)
|
|
89
|
+
guide_radius = wall_thickness / 3 - effective_tolerance / 2
|
|
90
|
+
with BuildPart(
|
|
91
|
+
(
|
|
92
|
+
Location(
|
|
93
|
+
(
|
|
94
|
+
rail_front_right.x,
|
|
95
|
+
rail_front_right.y + wall_thickness / 2,
|
|
96
|
+
rail_height,
|
|
97
|
+
)
|
|
98
|
+
)
|
|
99
|
+
),
|
|
100
|
+
mode=Mode.ADD,
|
|
101
|
+
):
|
|
102
|
+
DiamondCylinder(
|
|
103
|
+
radius=wall_thickness - effective_tolerance / 2,
|
|
104
|
+
height=part_depth - wall_thickness,
|
|
105
|
+
align=(Align.MAX, Align.CENTER, Align.MIN),
|
|
106
|
+
rotation=(90, 0, -rail_angle),
|
|
107
|
+
)
|
|
108
|
+
with BuildPart(
|
|
109
|
+
(
|
|
110
|
+
Location(
|
|
111
|
+
(
|
|
112
|
+
-rail_front_right.x,
|
|
113
|
+
rail_front_right.y + wall_thickness / 2,
|
|
114
|
+
rail_height,
|
|
115
|
+
)
|
|
116
|
+
)
|
|
117
|
+
),
|
|
118
|
+
mode=Mode.ADD,
|
|
119
|
+
):
|
|
120
|
+
DiamondCylinder(
|
|
121
|
+
radius=wall_thickness - effective_tolerance / 2,
|
|
122
|
+
height=part_depth - wall_thickness,
|
|
123
|
+
align=(Align.MIN, Align.CENTER, Align.MIN),
|
|
124
|
+
rotation=(90, 0, rail_angle),
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
with BuildPart(
|
|
128
|
+
(
|
|
129
|
+
Location(
|
|
130
|
+
(
|
|
131
|
+
rail_front_right.x,
|
|
132
|
+
rail_front_right.y + wall_thickness / 2,
|
|
133
|
+
rail_height / 2 - effective_tolerance,
|
|
134
|
+
)
|
|
135
|
+
)
|
|
136
|
+
),
|
|
137
|
+
mode=Mode.SUBTRACT,
|
|
138
|
+
):
|
|
139
|
+
DiamondCylinder(
|
|
140
|
+
radius=guide_radius,
|
|
141
|
+
height=part_depth,
|
|
142
|
+
align=(Align.CENTER, Align.CENTER, Align.MIN),
|
|
143
|
+
rotation=(90, 0, -rail_angle),
|
|
144
|
+
)
|
|
145
|
+
with BuildPart(
|
|
146
|
+
(
|
|
147
|
+
Location(
|
|
148
|
+
(
|
|
149
|
+
-rail_front_right.x,
|
|
150
|
+
rail_front_right.y + wall_thickness / 2,
|
|
151
|
+
rail_height / 2 - effective_tolerance,
|
|
152
|
+
)
|
|
153
|
+
)
|
|
154
|
+
),
|
|
155
|
+
mode=Mode.SUBTRACT,
|
|
156
|
+
):
|
|
157
|
+
DiamondCylinder(
|
|
158
|
+
radius=guide_radius,
|
|
159
|
+
height=part_depth,
|
|
160
|
+
align=(Align.CENTER, Align.CENTER, Align.MIN),
|
|
161
|
+
rotation=(90, 0, rail_angle),
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
return rail_cut.part
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _high_top_slide_box_top(
|
|
168
|
+
base_part: Part,
|
|
169
|
+
top_height: float,
|
|
170
|
+
rail_height: float,
|
|
171
|
+
wall_thickness: float,
|
|
172
|
+
rail_angle: float = 0,
|
|
173
|
+
divot_radius: float = 0.5,
|
|
174
|
+
thumb_radius: float = 0,
|
|
175
|
+
tolerance: float = 0.2,
|
|
176
|
+
cut_template: bool = False,
|
|
177
|
+
) -> Part:
|
|
178
|
+
"""
|
|
179
|
+
Creates the top component for a high top slide box with rails and optional divots.
|
|
180
|
+
|
|
181
|
+
This internal function generates either the actual sliding lid or a template for cutting
|
|
182
|
+
the base, depending on the cut_template parameter. It includes rail geometry, divots for
|
|
183
|
+
positioning feedback, and proper tolerance adjustments.
|
|
184
|
+
|
|
185
|
+
args:
|
|
186
|
+
- base_part: the base part that defines the outer dimensions
|
|
187
|
+
- top_height: the height of the sliding top portion in millimeters
|
|
188
|
+
- rail_height: the height of the rail system in millimeters
|
|
189
|
+
- wall_thickness: the thickness of the box walls in millimeters
|
|
190
|
+
- rail_angle: the angle of the rails in degrees for smoother sliding
|
|
191
|
+
- divot_radius: the radius of positioning divots, set to 0 to disable
|
|
192
|
+
- thumb_radius: the radius for thumb grips (currently unused)
|
|
193
|
+
- tolerance: the clearance between moving parts in millimeters
|
|
194
|
+
- cut_template: whether this is for cutting (True) or building the lid (False)
|
|
195
|
+
"""
|
|
196
|
+
effective_tolerance = abs(tolerance) / (-2 if cut_template else 2)
|
|
197
|
+
with BuildPart() as top:
|
|
198
|
+
part_width = base_part.bounding_box().size.X
|
|
199
|
+
part_depth = base_part.bounding_box().size.Y
|
|
200
|
+
part_height = base_part.bounding_box().size.Z
|
|
201
|
+
part_max_dimension = max(part_width, part_depth, part_height)
|
|
202
|
+
with BuildPart(
|
|
203
|
+
Location(
|
|
204
|
+
(
|
|
205
|
+
0,
|
|
206
|
+
0,
|
|
207
|
+
-part_height
|
|
208
|
+
+ top_height
|
|
209
|
+
+ rail_height
|
|
210
|
+
+ wall_thickness
|
|
211
|
+
- effective_tolerance * 4,
|
|
212
|
+
)
|
|
213
|
+
)
|
|
214
|
+
):
|
|
215
|
+
add(base_part)
|
|
216
|
+
with BuildPart(mode=Mode.INTERSECT):
|
|
217
|
+
Box(
|
|
218
|
+
part_max_dimension,
|
|
219
|
+
part_max_dimension,
|
|
220
|
+
part_max_dimension,
|
|
221
|
+
align=(Align.CENTER, Align.CENTER, Align.MIN),
|
|
222
|
+
)
|
|
223
|
+
with BuildPart(
|
|
224
|
+
Location((0, wall_thickness / 2, rail_height)),
|
|
225
|
+
mode=Mode.SUBTRACT,
|
|
226
|
+
) as top_cut:
|
|
227
|
+
Box(
|
|
228
|
+
part_width - wall_thickness * 2 + effective_tolerance * 2,
|
|
229
|
+
part_depth - wall_thickness,
|
|
230
|
+
top_height,
|
|
231
|
+
align=(Align.CENTER, Align.CENTER, Align.MIN),
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
with BuildPart(
|
|
235
|
+
Location((0, 0, 0)),
|
|
236
|
+
mode=Mode.SUBTRACT,
|
|
237
|
+
) as rail_cut:
|
|
238
|
+
add(
|
|
239
|
+
_slide_top_rail_cut(
|
|
240
|
+
part_width=part_width,
|
|
241
|
+
part_depth=part_depth,
|
|
242
|
+
rail_height=rail_height,
|
|
243
|
+
wall_thickness=wall_thickness,
|
|
244
|
+
rail_angle=rail_angle,
|
|
245
|
+
effective_tolerance=effective_tolerance,
|
|
246
|
+
)
|
|
247
|
+
)
|
|
248
|
+
if divot_radius > 0:
|
|
249
|
+
with BuildPart(
|
|
250
|
+
Location(
|
|
251
|
+
(0, (part_depth - wall_thickness) / 2, top_height + rail_height),
|
|
252
|
+
(180, 0, 0),
|
|
253
|
+
),
|
|
254
|
+
):
|
|
255
|
+
with PolarLocations(radius=part_width / 3 - wall_thickness, count=2):
|
|
256
|
+
Divot(
|
|
257
|
+
radius=divot_radius,
|
|
258
|
+
positive=not cut_template,
|
|
259
|
+
extend_base=True,
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
with BuildPart(
|
|
263
|
+
Location(
|
|
264
|
+
(0, (-part_depth + wall_thickness) / 2, 0),
|
|
265
|
+
(180, 0, 0),
|
|
266
|
+
),
|
|
267
|
+
):
|
|
268
|
+
with PolarLocations(radius=part_width / 3 - wall_thickness, count=2):
|
|
269
|
+
Divot(
|
|
270
|
+
radius=divot_radius,
|
|
271
|
+
positive=not cut_template,
|
|
272
|
+
extend_base=True,
|
|
273
|
+
)
|
|
274
|
+
top.part.label = "lid"
|
|
275
|
+
return top.part
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def high_top_slide_box_lid(
|
|
279
|
+
base_part: Part,
|
|
280
|
+
top_height: float,
|
|
281
|
+
rail_height: float,
|
|
282
|
+
wall_thickness: float,
|
|
283
|
+
rail_angle: float = 0,
|
|
284
|
+
divot_radius: float = 0.5,
|
|
285
|
+
thumb_radius: float = 0,
|
|
286
|
+
tolerance: float = 0.2,
|
|
287
|
+
) -> Part:
|
|
288
|
+
"""
|
|
289
|
+
Creates the sliding lid component for a high top slide box.
|
|
290
|
+
|
|
291
|
+
The lid features rails that slide smoothly into the base component, with optional
|
|
292
|
+
divots for tactile positioning feedback. The lid maintains the top portion of the
|
|
293
|
+
original part while adding the necessary sliding mechanism.
|
|
294
|
+
|
|
295
|
+
args:
|
|
296
|
+
- base_part: the base part that defines the outer dimensions
|
|
297
|
+
- top_height: the height of the sliding top portion in millimeters
|
|
298
|
+
- rail_height: the height of the rail system in millimeters
|
|
299
|
+
- wall_thickness: the thickness of the box walls in millimeters
|
|
300
|
+
- rail_angle: the angle of the rails in degrees for smoother sliding
|
|
301
|
+
- divot_radius: the radius of positioning divots, set to 0 to disable
|
|
302
|
+
- thumb_radius: the radius for thumb grips (currently unused)
|
|
303
|
+
- tolerance: the clearance between moving parts in millimeters
|
|
304
|
+
"""
|
|
305
|
+
lid = _high_top_slide_box_top(
|
|
306
|
+
base_part,
|
|
307
|
+
top_height,
|
|
308
|
+
rail_height,
|
|
309
|
+
wall_thickness,
|
|
310
|
+
rail_angle,
|
|
311
|
+
divot_radius,
|
|
312
|
+
thumb_radius,
|
|
313
|
+
tolerance,
|
|
314
|
+
cut_template=False,
|
|
315
|
+
)
|
|
316
|
+
lid.label = "box top"
|
|
317
|
+
return lid
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def high_top_slide_box_base(
|
|
321
|
+
base_part: Part,
|
|
322
|
+
top_height: float,
|
|
323
|
+
rail_height: float,
|
|
324
|
+
wall_thickness: float,
|
|
325
|
+
rail_angle: float = 0,
|
|
326
|
+
divot_radius: float = 0.5,
|
|
327
|
+
thumb_radius: float = 0,
|
|
328
|
+
tolerance: float = 0.2,
|
|
329
|
+
) -> Part:
|
|
330
|
+
"""
|
|
331
|
+
Creates the base component for a high top slide box with rail channels.
|
|
332
|
+
|
|
333
|
+
The base is hollowed out to create storage space and includes channels that receive
|
|
334
|
+
the sliding lid's rails. Diamond-shaped guide cylinders help ensure smooth operation
|
|
335
|
+
and proper alignment of the sliding mechanism.
|
|
336
|
+
|
|
337
|
+
args:
|
|
338
|
+
- base_part: the base part that defines the outer dimensions
|
|
339
|
+
- top_height: the height of the sliding top portion in millimeters
|
|
340
|
+
- rail_height: the height of the rail system in millimeters
|
|
341
|
+
- wall_thickness: the thickness of the box walls in millimeters
|
|
342
|
+
- rail_angle: the angle of the rails in degrees for smoother sliding
|
|
343
|
+
- divot_radius: the radius of positioning divots, set to 0 to disable
|
|
344
|
+
- thumb_radius: the radius for thumb grips (currently unused)
|
|
345
|
+
- tolerance: the clearance between moving parts in millimeters
|
|
346
|
+
"""
|
|
347
|
+
part_width = base_part.bounding_box().size.X
|
|
348
|
+
part_depth = base_part.bounding_box().size.Y
|
|
349
|
+
part_height = base_part.bounding_box().size.Z
|
|
350
|
+
|
|
351
|
+
with BuildPart() as boxbottom:
|
|
352
|
+
add(base_part)
|
|
353
|
+
with BuildPart(
|
|
354
|
+
Location(
|
|
355
|
+
(
|
|
356
|
+
0,
|
|
357
|
+
0,
|
|
358
|
+
wall_thickness,
|
|
359
|
+
),
|
|
360
|
+
(0, 0, 0),
|
|
361
|
+
),
|
|
362
|
+
mode=Mode.SUBTRACT,
|
|
363
|
+
):
|
|
364
|
+
Box(
|
|
365
|
+
part_width - wall_thickness * 2,
|
|
366
|
+
part_depth - wall_thickness * 2,
|
|
367
|
+
part_height - wall_thickness,
|
|
368
|
+
align=(Align.CENTER, Align.CENTER, Align.MIN),
|
|
369
|
+
)
|
|
370
|
+
with BuildPart(
|
|
371
|
+
Location(
|
|
372
|
+
(
|
|
373
|
+
0,
|
|
374
|
+
0,
|
|
375
|
+
part_height
|
|
376
|
+
- top_height
|
|
377
|
+
- rail_height
|
|
378
|
+
- wall_thickness
|
|
379
|
+
+ tolerance * 2,
|
|
380
|
+
),
|
|
381
|
+
(0, 0, 0),
|
|
382
|
+
),
|
|
383
|
+
mode=Mode.SUBTRACT,
|
|
384
|
+
):
|
|
385
|
+
add(
|
|
386
|
+
_high_top_slide_box_top(
|
|
387
|
+
base_part,
|
|
388
|
+
top_height,
|
|
389
|
+
rail_height,
|
|
390
|
+
wall_thickness,
|
|
391
|
+
rail_angle,
|
|
392
|
+
divot_radius,
|
|
393
|
+
thumb_radius,
|
|
394
|
+
tolerance,
|
|
395
|
+
cut_template=True,
|
|
396
|
+
)
|
|
397
|
+
)
|
|
398
|
+
with BuildPart(
|
|
399
|
+
Location(
|
|
400
|
+
(
|
|
401
|
+
0,
|
|
402
|
+
-part_depth / 2 + wall_thickness,
|
|
403
|
+
part_height
|
|
404
|
+
- top_height
|
|
405
|
+
- rail_height
|
|
406
|
+
- wall_thickness
|
|
407
|
+
+ tolerance * 2,
|
|
408
|
+
)
|
|
409
|
+
),
|
|
410
|
+
mode=Mode.SUBTRACT,
|
|
411
|
+
):
|
|
412
|
+
with GridLocations(part_width, 0, 2, 1) as grid_locs:
|
|
413
|
+
DiamondCylinder(
|
|
414
|
+
radius=wall_thickness,
|
|
415
|
+
height=part_height * 2,
|
|
416
|
+
align=(Align.CENTER, Align.CENTER, Align.MIN),
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
boxbottom.part.label = "box bottom"
|
|
420
|
+
return boxbottom.part
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def high_top_slide_box(
|
|
424
|
+
base_part: Part,
|
|
425
|
+
top_height: float,
|
|
426
|
+
rail_height: float,
|
|
427
|
+
wall_thickness: float,
|
|
428
|
+
rail_angle: float = 0,
|
|
429
|
+
divot_radius: float = 0.5,
|
|
430
|
+
thumb_radius: float = 0,
|
|
431
|
+
tolerance: float = 0.2,
|
|
432
|
+
) -> Compound:
|
|
433
|
+
"""
|
|
434
|
+
Creates a complete high top slide box with both lid and base components.
|
|
435
|
+
|
|
436
|
+
This function generates a sliding box system where the lid has significant height
|
|
437
|
+
and slides on rails into the base. The system uses diamond-shaped rails for smooth
|
|
438
|
+
operation and includes optional divots for positioning feedback. The lid is automatically
|
|
439
|
+
oriented for 3D printing.
|
|
440
|
+
|
|
441
|
+
args:
|
|
442
|
+
- base_part: the base part that defines the outer dimensions of the box
|
|
443
|
+
- top_height: the height of the sliding top portion in millimeters
|
|
444
|
+
- rail_height: the height of the rail system that guides sliding motion
|
|
445
|
+
- wall_thickness: the thickness of the box walls in millimeters
|
|
446
|
+
- rail_angle: the angle of the rails in degrees for smoother sliding
|
|
447
|
+
- divot_radius: the radius of positioning divots, set to 0 to disable
|
|
448
|
+
- thumb_radius: the radius for thumb grips (currently unused)
|
|
449
|
+
- tolerance: the clearance between moving parts in millimeters
|
|
450
|
+
"""
|
|
451
|
+
|
|
452
|
+
return Compound(
|
|
453
|
+
label="slide box",
|
|
454
|
+
children=[
|
|
455
|
+
high_top_slide_box_lid(
|
|
456
|
+
base_part=base_part,
|
|
457
|
+
top_height=top_height,
|
|
458
|
+
rail_height=rail_height,
|
|
459
|
+
wall_thickness=wall_thickness,
|
|
460
|
+
rail_angle=rail_angle,
|
|
461
|
+
divot_radius=divot_radius,
|
|
462
|
+
thumb_radius=thumb_radius,
|
|
463
|
+
tolerance=tolerance,
|
|
464
|
+
).rotate(Axis.X, 180),
|
|
465
|
+
high_top_slide_box_base(
|
|
466
|
+
base_part=base_part,
|
|
467
|
+
top_height=top_height,
|
|
468
|
+
rail_height=rail_height,
|
|
469
|
+
wall_thickness=wall_thickness,
|
|
470
|
+
rail_angle=rail_angle,
|
|
471
|
+
divot_radius=divot_radius,
|
|
472
|
+
thumb_radius=thumb_radius,
|
|
473
|
+
tolerance=tolerance,
|
|
474
|
+
),
|
|
475
|
+
],
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
if __name__ == "__main__":
|
|
480
|
+
with BuildPart() as base_box:
|
|
481
|
+
Box(44, 44, 44, align=(Align.CENTER, Align.CENTER, Align.MIN))
|
|
482
|
+
fillet(base_box.part.edges().filter_by(Axis.Z), radius=1.5)
|
|
483
|
+
|
|
484
|
+
sb = high_top_slide_box(
|
|
485
|
+
base_part=base_box.part,
|
|
486
|
+
top_height=5,
|
|
487
|
+
rail_height=10,
|
|
488
|
+
wall_thickness=4,
|
|
489
|
+
rail_angle=0.5,
|
|
490
|
+
divot_radius=0.5,
|
|
491
|
+
thumb_radius=0,
|
|
492
|
+
tolerance=0.1,
|
|
493
|
+
)
|
|
494
|
+
top = (
|
|
495
|
+
sb.children[0].move(Location((0, 0, 19)))
|
|
496
|
+
# .rotate(Axis.Z, 180)
|
|
497
|
+
# .rotate(Axis.Y, 180)
|
|
498
|
+
# .move(Location((-44 - 5, 44, sb.children[1].bounding_box().size.Z + 4 - 0.1)))
|
|
499
|
+
)
|
|
500
|
+
# show(sb, reset_camera=Camera.KEEP)
|
|
501
|
+
show(
|
|
502
|
+
pack([top, sb.children[1]], padding=5, align_z=True),
|
|
503
|
+
reset_camera=Camera.KEEP,
|
|
504
|
+
)
|