Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ A place to share CadQuery scripts, modules, tutorials and projects

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

* [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.

<img src="examples/hexagonal_drawers/hmd.png" width="600"/>
<img src="examples/hexagonal_drawers/hmd.jpg" width="600"/>

### Tutorials

* [Ex000 Start Here.ipynb](tutorials/Ex000%20Start%20Here.ipynb) - iPython notebook that is the entry point for a set of CadQuery tutorials
Expand Down
74 changes: 74 additions & 0 deletions examples/hexagonal_drawers/assembly.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""
Display the drawers
"""

import cadquery as cq
import importlib
import base
import organiser_collets
import organiser_3_125_bits
importlib.reload(base)
importlib.reload(organiser_collets)
importlib.reload(organiser_3_125_bits)

sep = 20 # seperation between parts
big_sep = 100 # extra for the organisers

assy = cq.Assembly()
assy.add(base.frame, name="frame")
assy.add(base.drawer, name="drawer")
assy.add(organiser_collets.collet_organiser, name="collet organiser")
assy.add(organiser_3_125_bits.bit_organiser, name="bit organiser")

# pull the drawer out
assy.constrain(
"frame",
base.frame.faces("<Y").val().translate((0, -sep, 0)),
"drawer",
base.drawer.faces(">Y").val(),
"Point",
)

# bit organiser aligns with back face of drawer and is big_sep above
assy.constrain(
"drawer",
base.drawer.faces("<Y[1]").val().translate((0, 0, big_sep)),
"bit organiser",
organiser_3_125_bits.bit_organiser.faces(">Y").val(),
"Point",
)
assy.constrain(
"bit organiser",
organiser_3_125_bits.bit_organiser.faces("<Y").val().translate((0, -sep, 0)),
"collet organiser",
organiser_collets.collet_organiser.faces(">Y").val(),
"Point",
)

# align the z and x axes between obj0 and obj1
align_these = (
("frame", "drawer"),
("drawer", "collet organiser"),
("drawer", "bit organiser"),
)
for obj0, obj1 in align_these:
assy.constrain(
obj0,
cq.Face.makePlane(),
obj1,
cq.Face.makePlane(),
"Axis",
0,
)
assy.constrain(
obj0,
cq.Face.makePlane(dir=(1, 0, 0)),
obj1,
cq.Face.makePlane(dir=(1, 0, 0)),
"Axis",
0,
)

assy.solve()
if "show_object" in locals():
show_object(assy)
119 changes: 119 additions & 0 deletions examples/hexagonal_drawers/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
"""
The basic frame and drawer.
"""

import cadquery as cq
from types import SimpleNamespace
from math import tan, radians

hex_diam = 80 # outside of the drawer frame
wall_thick = 3
clearance = SimpleNamespace(tight=0.3)
clearance.loose = clearance.tight * 2
drawer_length = 150
dovetail_min_thick = wall_thick * 2

frame_y = drawer_length + clearance.loose + 2 * wall_thick

frame = (
cq.Workplane("XZ")
.polygon(6, hex_diam)
.extrude(frame_y)
.faces("<Y")
.shell(-2 * wall_thick)
)

drawer = (
frame
.faces("<Y[1]")
.wires()
.translate((0, -clearance.loose, 0))
.toPending()
.offset2D(-clearance.loose)
.extrude(drawer_length, combine=False)
.faces(">Z or >>Z[-2]")
.shell(-wall_thick)
)

handle = (
drawer
.faces("<Y")
.edges("<Z")
)
handle_width = handle.val().Length()
handle = (
handle
.workplane(centerOption="CenterOfMass")
.transformed(rotate=(-90, 0, 180))
.circle(handle_width / 2)
.circle(handle_width / 2 - wall_thick)
.transformed(rotate=(30, 0, 0))
.extrude(hex_diam / 2, combine=False)
.newObject([drawer.faces("<Y").val()])
.workplane(centerOption="CenterOfMass")
.split(keepTop=True)
)

drawer = drawer.union(handle)
del handle

top_length = frame.faces(">Z").val().BoundingBox().ylen
dovetail_base_radius = frame.faces("<Y").edges(">Z").val().Center().z
dovetail_length = 0.9 * top_length

# make the male dovetail join
# should extend wall_thick out from the frame
dovetail_positive = (
cq.Workplane()
.hLine(dovetail_min_thick / 2)
.line(wall_thick * tan(radians(30)), wall_thick)
.hLineTo(0)
.mirrorY()
.extrude(-dovetail_length)
.faces("<Z")
.edges("<Y")
.workplane()
.transformed(rotate=(60, 0, 0))
.split(keepBottom=True)
)
# provide some clearance around the straight section, but leave the sloped
# plane at the back alone so it mates as a backstop
# dovetail_straight_length = dovetail_positive.edges(">Y and |Z").val().Length()
dovetail_negative = (
dovetail_positive
.tag("dovetail_positive")
.faces(">Z")
.wires()
.toPending()
.offset2D(clearance.tight)
.faces(">Z", tag="dovetail_positive")
.workplane()
.extrude(-(dovetail_length + clearance.tight))
.faces("<Z")
.edges("<Y")
.workplane()
.transformed(rotate=(60, 0, 0))
.split(keepBottom=True)
.mirror("XZ")
)
dovetail_baseplane = (
frame
.faces("<Y")
.workplane(centerOption="CenterOfMass")
)
dovetail_positive = (
dovetail_baseplane
.polarArray(dovetail_base_radius, startAngle=-60, angle=120, count=3)
.eachpoint(lambda loc: dovetail_positive.val().located(loc), useLocalCoordinates=True)
)

dovetail_negative = (
dovetail_baseplane
.polarArray(dovetail_base_radius, startAngle=120, angle=120, count=3)
.eachpoint(lambda loc: dovetail_negative.val().located(loc), useLocalCoordinates=True)
)
frame = frame.union(dovetail_positive, glue=True).cut(dovetail_negative)

if "show_object" in locals():
show_object(frame, "frame", options={"alpha": 0.5, "color": "black"})
show_object(drawer, "drawer", options={"color": "green"})
Binary file added examples/hexagonal_drawers/hmd.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/hexagonal_drawers/hmd.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
44 changes: 44 additions & 0 deletions examples/hexagonal_drawers/organiser_3_125_bits.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""
Organiser for cutters with a shank of 3.125mm
"""

import cadquery as cq
import importlib
import base
import organiser_blank
importlib.reload(organiser_blank)

base_wp = (
organiser_blank.organiser
.faces(">Z")
.workplane(centerOption="CenterOfMass")
)

bit_3_125_points = (
base_wp
.rarray(
3.125 * 3,
3.125 * 4,
4,
4,
)
.vals()
)
bit_3_125_points.extend(
base_wp
.rarray(
3.125 * 3,
3.125 * 4,
3,
3,
)
.vals()
)
bit_organiser = (
base_wp
.newObject(bit_3_125_points)
.hole(
3.125 + 2 * base.clearance.loose,
depth=organiser_blank.organiser.val().BoundingBox().zlen - 1.5 * base.wall_thick,
)
)
37 changes: 37 additions & 0 deletions examples/hexagonal_drawers/organiser_blank.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""
An organiser that takes up 1/3 of a drawer and has no cutouts yet.
"""

import cadquery as cq
import importlib
import base
importlib.reload(base)


# organiser base
# first grab the inner profile of the drawer
lines = (
base.drawer
.faces(">Y")
.workplane(offset=-base.drawer_length / 2)
.section()
.edges()
.vals()
)
assert len(lines) == 8
# sort lines by radius about y axis
lines.sort(
key=lambda x: cq.Vector(x.Center().x, 0, x.Center().z).Center().Length
)
wire = cq.Wire.assembleEdges(lines[0:3])
wire = cq.Wire.combine(
[wire, cq.Edge.makeLine(wire.endPoint(), wire.startPoint())]
)[0]
wire_center = wire.Center()
wire = wire.translate(cq.Vector(0, -wire_center.y, 0))
organiser = (
cq.Workplane("XZ")
.newObject([wire])
.toPending()
.extrude(base.drawer_length / 3 - base.clearance.loose)
)
61 changes: 61 additions & 0 deletions examples/hexagonal_drawers/organiser_collets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"""
An organiser with holes for ER11 collets.
"""

import cadquery as cq
from types import SimpleNamespace
import importlib
import organiser_blank
importlib.reload(organiser_blank)


collet_dims = SimpleNamespace(
upper_diam=11.35,
cone_height=13.55,
lower_diam=7.8,
)
collet = (
cq.Solid.makeCone(
collet_dims.upper_diam / 2,
collet_dims.lower_diam / 2,
collet_dims.cone_height,
)
.mirror("XY")
.translate(cq.Vector(0, 0, collet_dims.cone_height / 3))
)
collet_organiser_points = (
organiser_blank.organiser
.faces(">Z")
.workplane(centerOption="CenterOfMass")
.rarray(
collet_dims.upper_diam * 1.5,
collet_dims.upper_diam * 2.3,
3,
2,
)
.vals()
)
collet_organiser_points.extend(
organiser_blank.organiser
.faces(">Z")
.workplane(centerOption="CenterOfMass")
.rarray(
collet_dims.upper_diam * 1.5,
collet_dims.upper_diam * 2.3,
2,
1,
)
.vals()
)
collets = (
cq.Workplane()
.pushPoints(collet_organiser_points)
.eachpoint(lambda loc: collet.located(loc))
)
collet_organiser = (
organiser_blank.organiser
.cut(collets)
)

if "show_object" in locals():
show_object(collet_organiser, "collet organiser")