Skip to content

Commit 3902f2b

Browse files
authored
Merge pull request #15 from CadQuery/marcus7070/hex-modular-drawers
hexagonal modular drawers
2 parents 8a2b754 + b8f0178 commit 3902f2b

File tree

8 files changed

+340
-0
lines changed

8 files changed

+340
-0
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ A place to share CadQuery scripts, modules, tutorials and projects
6565

6666
<img src="examples/images/Thread.png" width="600"/>
6767

68+
* [Hexagonal modular drawers](examples/hexagonal_drawers/assembly.py) - Inspired by [this on Prusa Printers](https://www.prusaprinters.org/prints/54113-hexagonal-organizer-system), these drawers are 3D printed (without needing supports) and clip together.
69+
70+
<img src="examples/hexagonal_drawers/hmd.png" width="600"/>
71+
<img src="examples/hexagonal_drawers/hmd.jpg" width="600"/>
72+
6873
### Tutorials
6974

7075
* [Ex000 Start Here.ipynb](tutorials/Ex000%20Start%20Here.ipynb) - iPython notebook that is the entry point for a set of CadQuery tutorials
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
"""
2+
Display the drawers
3+
"""
4+
5+
import cadquery as cq
6+
import importlib
7+
import base
8+
import organiser_collets
9+
import organiser_3_125_bits
10+
importlib.reload(base)
11+
importlib.reload(organiser_collets)
12+
importlib.reload(organiser_3_125_bits)
13+
14+
sep = 20 # seperation between parts
15+
big_sep = 100 # extra for the organisers
16+
17+
assy = cq.Assembly()
18+
assy.add(base.frame, name="frame")
19+
assy.add(base.drawer, name="drawer")
20+
assy.add(organiser_collets.collet_organiser, name="collet organiser")
21+
assy.add(organiser_3_125_bits.bit_organiser, name="bit organiser")
22+
23+
# pull the drawer out
24+
assy.constrain(
25+
"frame",
26+
base.frame.faces("<Y").val().translate((0, -sep, 0)),
27+
"drawer",
28+
base.drawer.faces(">Y").val(),
29+
"Point",
30+
)
31+
32+
# bit organiser aligns with back face of drawer and is big_sep above
33+
assy.constrain(
34+
"drawer",
35+
base.drawer.faces("<Y[1]").val().translate((0, 0, big_sep)),
36+
"bit organiser",
37+
organiser_3_125_bits.bit_organiser.faces(">Y").val(),
38+
"Point",
39+
)
40+
assy.constrain(
41+
"bit organiser",
42+
organiser_3_125_bits.bit_organiser.faces("<Y").val().translate((0, -sep, 0)),
43+
"collet organiser",
44+
organiser_collets.collet_organiser.faces(">Y").val(),
45+
"Point",
46+
)
47+
48+
# align the z and x axes between obj0 and obj1
49+
align_these = (
50+
("frame", "drawer"),
51+
("drawer", "collet organiser"),
52+
("drawer", "bit organiser"),
53+
)
54+
for obj0, obj1 in align_these:
55+
assy.constrain(
56+
obj0,
57+
cq.Face.makePlane(),
58+
obj1,
59+
cq.Face.makePlane(),
60+
"Axis",
61+
0,
62+
)
63+
assy.constrain(
64+
obj0,
65+
cq.Face.makePlane(dir=(1, 0, 0)),
66+
obj1,
67+
cq.Face.makePlane(dir=(1, 0, 0)),
68+
"Axis",
69+
0,
70+
)
71+
72+
assy.solve()
73+
if "show_object" in locals():
74+
show_object(assy)

examples/hexagonal_drawers/base.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
"""
2+
The basic frame and drawer.
3+
"""
4+
5+
import cadquery as cq
6+
from types import SimpleNamespace
7+
from math import tan, radians
8+
9+
hex_diam = 80 # outside of the drawer frame
10+
wall_thick = 3
11+
clearance = SimpleNamespace(tight=0.3)
12+
clearance.loose = clearance.tight * 2
13+
drawer_length = 150
14+
dovetail_min_thick = wall_thick * 2
15+
16+
frame_y = drawer_length + clearance.loose + 2 * wall_thick
17+
18+
frame = (
19+
cq.Workplane("XZ")
20+
.polygon(6, hex_diam)
21+
.extrude(frame_y)
22+
.faces("<Y")
23+
.shell(-2 * wall_thick)
24+
)
25+
26+
drawer = (
27+
frame
28+
.faces("<Y[1]")
29+
.wires()
30+
.translate((0, -clearance.loose, 0))
31+
.toPending()
32+
.offset2D(-clearance.loose)
33+
.extrude(drawer_length, combine=False)
34+
.faces(">Z or >>Z[-2]")
35+
.shell(-wall_thick)
36+
)
37+
38+
handle = (
39+
drawer
40+
.faces("<Y")
41+
.edges("<Z")
42+
)
43+
handle_width = handle.val().Length()
44+
handle = (
45+
handle
46+
.workplane(centerOption="CenterOfMass")
47+
.transformed(rotate=(-90, 0, 180))
48+
.circle(handle_width / 2)
49+
.circle(handle_width / 2 - wall_thick)
50+
.transformed(rotate=(30, 0, 0))
51+
.extrude(hex_diam / 2, combine=False)
52+
.newObject([drawer.faces("<Y").val()])
53+
.workplane(centerOption="CenterOfMass")
54+
.split(keepTop=True)
55+
)
56+
57+
drawer = drawer.union(handle)
58+
del handle
59+
60+
top_length = frame.faces(">Z").val().BoundingBox().ylen
61+
dovetail_base_radius = frame.faces("<Y").edges(">Z").val().Center().z
62+
dovetail_length = 0.9 * top_length
63+
64+
# make the male dovetail join
65+
# should extend wall_thick out from the frame
66+
dovetail_positive = (
67+
cq.Workplane()
68+
.hLine(dovetail_min_thick / 2)
69+
.line(wall_thick * tan(radians(30)), wall_thick)
70+
.hLineTo(0)
71+
.mirrorY()
72+
.extrude(-dovetail_length)
73+
.faces("<Z")
74+
.edges("<Y")
75+
.workplane()
76+
.transformed(rotate=(60, 0, 0))
77+
.split(keepBottom=True)
78+
)
79+
# provide some clearance around the straight section, but leave the sloped
80+
# plane at the back alone so it mates as a backstop
81+
# dovetail_straight_length = dovetail_positive.edges(">Y and |Z").val().Length()
82+
dovetail_negative = (
83+
dovetail_positive
84+
.tag("dovetail_positive")
85+
.faces(">Z")
86+
.wires()
87+
.toPending()
88+
.offset2D(clearance.tight)
89+
.faces(">Z", tag="dovetail_positive")
90+
.workplane()
91+
.extrude(-(dovetail_length + clearance.tight))
92+
.faces("<Z")
93+
.edges("<Y")
94+
.workplane()
95+
.transformed(rotate=(60, 0, 0))
96+
.split(keepBottom=True)
97+
.mirror("XZ")
98+
)
99+
dovetail_baseplane = (
100+
frame
101+
.faces("<Y")
102+
.workplane(centerOption="CenterOfMass")
103+
)
104+
dovetail_positive = (
105+
dovetail_baseplane
106+
.polarArray(dovetail_base_radius, startAngle=-60, angle=120, count=3)
107+
.eachpoint(lambda loc: dovetail_positive.val().located(loc), useLocalCoordinates=True)
108+
)
109+
110+
dovetail_negative = (
111+
dovetail_baseplane
112+
.polarArray(dovetail_base_radius, startAngle=120, angle=120, count=3)
113+
.eachpoint(lambda loc: dovetail_negative.val().located(loc), useLocalCoordinates=True)
114+
)
115+
frame = frame.union(dovetail_positive, glue=True).cut(dovetail_negative)
116+
117+
if "show_object" in locals():
118+
show_object(frame, "frame", options={"alpha": 0.5, "color": "black"})
119+
show_object(drawer, "drawer", options={"color": "green"})

examples/hexagonal_drawers/hmd.jpg

360 KB
Loading

examples/hexagonal_drawers/hmd.png

88.4 KB
Loading
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"""
2+
Organiser for cutters with a shank of 3.125mm
3+
"""
4+
5+
import cadquery as cq
6+
import importlib
7+
import base
8+
import organiser_blank
9+
importlib.reload(organiser_blank)
10+
11+
base_wp = (
12+
organiser_blank.organiser
13+
.faces(">Z")
14+
.workplane(centerOption="CenterOfMass")
15+
)
16+
17+
bit_3_125_points = (
18+
base_wp
19+
.rarray(
20+
3.125 * 3,
21+
3.125 * 4,
22+
4,
23+
4,
24+
)
25+
.vals()
26+
)
27+
bit_3_125_points.extend(
28+
base_wp
29+
.rarray(
30+
3.125 * 3,
31+
3.125 * 4,
32+
3,
33+
3,
34+
)
35+
.vals()
36+
)
37+
bit_organiser = (
38+
base_wp
39+
.newObject(bit_3_125_points)
40+
.hole(
41+
3.125 + 2 * base.clearance.loose,
42+
depth=organiser_blank.organiser.val().BoundingBox().zlen - 1.5 * base.wall_thick,
43+
)
44+
)
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
"""
2+
An organiser that takes up 1/3 of a drawer and has no cutouts yet.
3+
"""
4+
5+
import cadquery as cq
6+
import importlib
7+
import base
8+
importlib.reload(base)
9+
10+
11+
# organiser base
12+
# first grab the inner profile of the drawer
13+
lines = (
14+
base.drawer
15+
.faces(">Y")
16+
.workplane(offset=-base.drawer_length / 2)
17+
.section()
18+
.edges()
19+
.vals()
20+
)
21+
assert len(lines) == 8
22+
# sort lines by radius about y axis
23+
lines.sort(
24+
key=lambda x: cq.Vector(x.Center().x, 0, x.Center().z).Center().Length
25+
)
26+
wire = cq.Wire.assembleEdges(lines[0:3])
27+
wire = cq.Wire.combine(
28+
[wire, cq.Edge.makeLine(wire.endPoint(), wire.startPoint())]
29+
)[0]
30+
wire_center = wire.Center()
31+
wire = wire.translate(cq.Vector(0, -wire_center.y, 0))
32+
organiser = (
33+
cq.Workplane("XZ")
34+
.newObject([wire])
35+
.toPending()
36+
.extrude(base.drawer_length / 3 - base.clearance.loose)
37+
)
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"""
2+
An organiser with holes for ER11 collets.
3+
"""
4+
5+
import cadquery as cq
6+
from types import SimpleNamespace
7+
import importlib
8+
import organiser_blank
9+
importlib.reload(organiser_blank)
10+
11+
12+
collet_dims = SimpleNamespace(
13+
upper_diam=11.35,
14+
cone_height=13.55,
15+
lower_diam=7.8,
16+
)
17+
collet = (
18+
cq.Solid.makeCone(
19+
collet_dims.upper_diam / 2,
20+
collet_dims.lower_diam / 2,
21+
collet_dims.cone_height,
22+
)
23+
.mirror("XY")
24+
.translate(cq.Vector(0, 0, collet_dims.cone_height / 3))
25+
)
26+
collet_organiser_points = (
27+
organiser_blank.organiser
28+
.faces(">Z")
29+
.workplane(centerOption="CenterOfMass")
30+
.rarray(
31+
collet_dims.upper_diam * 1.5,
32+
collet_dims.upper_diam * 2.3,
33+
3,
34+
2,
35+
)
36+
.vals()
37+
)
38+
collet_organiser_points.extend(
39+
organiser_blank.organiser
40+
.faces(">Z")
41+
.workplane(centerOption="CenterOfMass")
42+
.rarray(
43+
collet_dims.upper_diam * 1.5,
44+
collet_dims.upper_diam * 2.3,
45+
2,
46+
1,
47+
)
48+
.vals()
49+
)
50+
collets = (
51+
cq.Workplane()
52+
.pushPoints(collet_organiser_points)
53+
.eachpoint(lambda loc: collet.located(loc))
54+
)
55+
collet_organiser = (
56+
organiser_blank.organiser
57+
.cut(collets)
58+
)
59+
60+
if "show_object" in locals():
61+
show_object(collet_organiser, "collet organiser")

0 commit comments

Comments
 (0)