This page contains various 3D designs I've made for either 3D printing or woodwork. There are more not listed here, but they are so application-specific they are bound not to be useful to anyone else. I hereby release these designs into the public domain - feel free to reuse these as you like.
Note that I typically write CAD designs as Python or SCAD programs, rather than construct them with CAD programs. This approach seems to interface with my brain better than the CAD GUIs I've used, but I know I'm weird (saying that, I would probably still use a GUI for a complex, mission critical design). If you want to reuse any of these designs you'll have to be (or become) acquainted with OpenSCAD or CadQuery.
A key
I needed another key for the basement. The key was a very simple design so I figured I didn't need a professional copy. I printed this using carbon fibre reinforced PLA filament to give it extra strength, and ensured that it was sliced in such a way that the weak axis (vertical as printed) was not along the barrel, where significant shear forces will be occur during locking/unlocking. The printed key works nicely and hasn't yet broken after a few hundred lockings and unlockings.
Code (OpenSCAD):
$fn = 100; // Barrel. cylinder(50, 3.25, 3.25); // Handle. translate([-10, -3.25, -20]) difference() { cube([20, 6.5, 20]); translate([5, -1, 5]) cube([10, 22, 10]); } // Bit. translate([0, -1, 36.5]) cube([7, 2, 9]); translate([6, -3, 36.5]) linear_extrude(9) polygon([[0, 6], [0, 0], [10, 0], [10, 6], [8, 6], [8, 2], [2, 2], [2, 6]]);
Bubble blower
Who doesn't want to blow bubbles?
Code (OpenSCAD):
$fn = 100; rotate_extrude(convexity = 2) translate([15, 0, 0]) circle(r = 1.5); rotate([90, 0, 0]) translate([0, 0, 15]) cylinder(75, 1.5, 1.5);
Heart soap mould
This is a heart shaped mould that I made for my soap making enthusiast partner.
Code (CadQuery):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | import cadquery as cq outer = ( cq.Workplane("XY") .lineTo(2, 2) .threePointArc((4, 1), (3.5, 0)) .mirrorX() .extrude(-2) .edges("<Z") .fillet(1.2) ) inner = ( cq.Workplane("XY") .lineTo(2, 2) .threePointArc((4, 1), (3.5, 0)) .mirrorX() .offset2D(-0.075, kind="intersection") .extrude(-1.925) .edges("<Z") .fillet(1.125) ) heart = outer - inner show_object(heart, name="heart") |
Entrance bag stand
A moderate woodwork project for a bag stand. Contains mortise-and-tenon slats, a nice table top and sturdy legs.
When I modelled this, I also added the hall and nearby doors to check it would fit.
Photo of the completed construction:
Code (OpenSCAD):
leg_thickness = 40; leg_height = 700; lower_stretcher_y_offset = 100; // Lower edge. stretcher_width = 20; stretcher_height = 40; stretcher_mortise_width = 10; stretcher_mortise_depth = 10; stretcher_mortise_height = 20; carriage_slat_width = 40; carriage_slat_height = 10; carriage_slat_mortise_width = 30; carriage_slat_mortise_depth = 10; carriage_slat_mortise_height = 5; top_plank_height = 20; n_slats = 6; width = 700; depth = 250; module leg() { difference() { cube([leg_thickness, leg_thickness, leg_height]); // Short edge mortises. // Upper. translate([leg_thickness/2, leg_thickness, leg_height-stretcher_height/2]) cube( [stretcher_mortise_width, stretcher_mortise_depth*2, stretcher_mortise_height], center = true ); // Lower. translate([leg_thickness/2, leg_thickness, lower_stretcher_y_offset]) cube( [stretcher_mortise_width, stretcher_mortise_depth*2, stretcher_mortise_height], center = true ); // Long edge mortises. // Upper. translate([leg_thickness, leg_thickness/2, leg_height-stretcher_height/2]) cube( [stretcher_mortise_depth*2, stretcher_mortise_width, stretcher_mortise_height], center = true ); // Lower. translate([leg_thickness, leg_thickness/2, lower_stretcher_y_offset]) cube( [stretcher_mortise_depth*2, stretcher_mortise_width, stretcher_mortise_height], center = true ); } } module slat(width, depth, height, tenon_width, tenon_depth, tenon_height) { union() { cube([width, depth, height]); translate([width/2-tenon_width/2, -tenon_depth, height/2-tenon_height/2]) cube([tenon_width, tenon_depth, tenon_height]); translate([width/2-tenon_width/2, depth, height/2-tenon_height/2]) cube([tenon_width, tenon_depth, tenon_height]); } } module stretcher_short() { slat( stretcher_width, depth - leg_thickness, stretcher_height, stretcher_mortise_width, stretcher_mortise_depth, stretcher_mortise_height ); } module slat_mortises() { for(i = [1:n_slats]) { translate( [ -stretcher_width / 2 + carriage_slat_mortise_depth - 1, (width - 2 * leg_thickness) / (2 * n_slats) * (2 * i - 1) - carriage_slat_mortise_width / 2, stretcher_height / 2 - carriage_slat_height / 2 + carriage_slat_mortise_height / 2 ] ) cube( [ carriage_slat_mortise_depth, carriage_slat_mortise_width, carriage_slat_mortise_height ] ); } } module stretcher_long() { difference() { slat( stretcher_width, width - 2 * leg_thickness, stretcher_height, stretcher_mortise_width, stretcher_mortise_depth, stretcher_mortise_height ); slat_mortises(); } } module carriage_slat() { slat( carriage_slat_width, depth, carriage_slat_height, carriage_slat_mortise_width, carriage_slat_mortise_depth, carriage_slat_mortise_height ); } module side() { translate([0, 0, 0]) leg(); rotate([0, 0, -90]) translate([-depth-leg_thickness, 0, 0]) leg(); // Lower stretcher. translate( [ leg_thickness / 2 - stretcher_width / 2, leg_thickness, lower_stretcher_y_offset - stretcher_height / 2 ] ) stretcher_short(); // Upper stretcher. translate( [ leg_thickness / 2 - stretcher_width / 2, leg_thickness, leg_height - stretcher_height ] ) stretcher_short(); } module carriage() { rotate([0, 0, -90]) // Left/back. stretcher_long(); rotate([0, 0, 90]) // Right/front. translate([depth - leg_thickness / 2, -width + 2 * leg_thickness, 0]) stretcher_long(); // Slats. for (i = [1:n_slats]) { translate( [ (width - 2 * leg_thickness) / (2 * n_slats) * (2 * i - 1) - carriage_slat_width / 2, -stretcher_width / 2, stretcher_height / 2 - carriage_slat_height / 2 ] ) carriage_slat(); } } module top() { rotate([0, 0, -90]) // Back. stretcher_long(); rotate([0, 0, -90]) // Front. translate([-depth, 0, 0]) stretcher_long(); } module table_top() { cube([width+leg_thickness, depth+2*leg_thickness, top_plank_height]); } color("yellow") // Left side. side(); // Right side. translate([width, depth+leg_thickness, 0]) rotate([0, 0, 180]) side(); // Carriage. translate( [leg_thickness, leg_thickness-stretcher_width/2, lower_stretcher_y_offset-stretcher_height/2] ) carriage(); // Top. translate([leg_thickness, leg_thickness-stretcher_width/2, leg_height-stretcher_height]) top(); translate([-leg_thickness/2, -leg_thickness/2, leg_height]) table_top(); // Walls. color("white") translate([-1120, depth+70, 0]) cube([2300, 1, 2500]); color("white") rotate([0, 0, 90]) translate([-750, -1160, 0]) cube([1070, 1, 2500]); // Doors. color("grey") translate([-920, depth+65, 0]) cube([860, 30, 2000]); color("grey") rotate([0, 0, 180]) translate([-1160, -100, 0]) rotate([0, 0, 30]) cube([750, 30, 2000]);
Matchstick light
A cool light holder made from rectangular wood. I've not built this large version yet, but I did make a smaller version:
Code (CadQuery):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | from functools import reduce from collections import defaultdict import cadquery as cq sticks_per_side = 7 stick_side_length = 14 layers = 70 x_length = (2*sticks_per_side+1)*stick_side_length y_length = 2*sticks_per_side*stick_side_length bom = defaultdict(lambda: 0) def long_stick(): bom["long"] += 1 return ( cq.Workplane("XY") .box(x_length, stick_side_length, stick_side_length) .translate((x_length/2, 0, 0)) ) def short_stick(): bom["short"] += 1 length = 2*stick_side_length return ( cq.Workplane("XY") .box(length, stick_side_length, stick_side_length) .translate((length/2, 0, 0)) ) def layer(): inner1 = reduce( lambda a, b: a + b, [ short_stick().translate((0, 2*(n+1)*stick_side_length, 0)) for n in range(sticks_per_side - 2) ] ) inner2 = reduce( lambda a, b: a + b, [ short_stick().translate((x_length - 2*stick_side_length, 2*(n+1)*stick_side_length, 0)) for n in range(sticks_per_side - 2) ] ) return ( long_stick() + inner1 + inner2 + long_stick().translate((0, y_length - 2*stick_side_length, 0)) ) def layer_stack(): return reduce( lambda a, b: a + b, [ layer().rotateAboutCenter((0, 0, 1), 90*(n%2)).translate((0, 0, n*stick_side_length)) for n in range(layers) ] ) part = layer_stack() total_short = bom['short']*2*stick_side_length total_long = bom['long']*x_length total = total_short + total_long log(f"{bom['short']} short sticks = {total_short}") log(f"{bom['long']} long sticks = {total_long}") log(f"Total: {total}") show_object(part, name="Light") |