diff --git a/README.md b/README.md index 507b047..6080861 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,11 @@ A place to share CadQuery scripts, modules, tutorials and projects +* [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. + + + + ### Tutorials * [Ex000 Start Here.ipynb](tutorials/Ex000%20Start%20Here.ipynb) - iPython notebook that is the entry point for a set of CadQuery tutorials diff --git a/examples/hexagonal_drawers/assembly.py b/examples/hexagonal_drawers/assembly.py new file mode 100644 index 0000000..9fb503b --- /dev/null +++ b/examples/hexagonal_drawers/assembly.py @@ -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(), + "Point", +) + +# bit organiser aligns with back face of drawer and is big_sep above +assy.constrain( + "drawer", + base.drawer.faces("Y").val(), + "Point", +) +assy.constrain( + "bit organiser", + organiser_3_125_bits.bit_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) diff --git a/examples/hexagonal_drawers/base.py b/examples/hexagonal_drawers/base.py new file mode 100644 index 0000000..d1a7814 --- /dev/null +++ b/examples/hexagonal_drawers/base.py @@ -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("Z or >>Z[-2]") + .shell(-wall_thick) +) + +handle = ( + drawer + .faces("Z").val().BoundingBox().ylen +dovetail_base_radius = frame.faces("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("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") + .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, + ) +) diff --git a/examples/hexagonal_drawers/organiser_blank.py b/examples/hexagonal_drawers/organiser_blank.py new file mode 100644 index 0000000..688cefa --- /dev/null +++ b/examples/hexagonal_drawers/organiser_blank.py @@ -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) +) diff --git a/examples/hexagonal_drawers/organiser_collets.py b/examples/hexagonal_drawers/organiser_collets.py new file mode 100644 index 0000000..473d02e --- /dev/null +++ b/examples/hexagonal_drawers/organiser_collets.py @@ -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")