├── .gitignore ├── demo_01.png ├── demo_02.png ├── Makefile ├── simple_box.scad ├── README.md ├── demo.scad └── boxmaker.scad /.gitignore: -------------------------------------------------------------------------------- 1 | gen/ 2 | -------------------------------------------------------------------------------- /demo_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxv/openscad-boxmaker/HEAD/demo_01.png -------------------------------------------------------------------------------- /demo_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxv/openscad-boxmaker/HEAD/demo_02.png -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: gen/boxmaker_thingiverse.scad 2 | 3 | gen: 4 | mkdir -p gen 5 | 6 | gen/boxmaker_thingiverse.scad: gen simple_box.scad boxmaker.scad 7 | cat simple_box.scad boxmaker.scad | sed -e '/^include/ d' > $@ 8 | -------------------------------------------------------------------------------- /simple_box.scad: -------------------------------------------------------------------------------- 1 | include ; 2 | 3 | // Inner Dimensions: Length (mm) 4 | x = 100; 5 | // Inner Dimensions: Width (mm) 6 | y = 70; 7 | // Inner Dimensions: Height (mm) 8 | z = 50; 9 | 10 | // Material thickness (mm) 11 | thickness = 3; 12 | 13 | // Lid? 14 | lid = 1; // [0:Closed box,1:Box with lid] 15 | 16 | // Render type 17 | show_3d = 0; // [0:2D for cutting,1:3D preview] 18 | 19 | ///////////////////////////////////////////////////////////////////// 20 | /* [Hidden] */ 21 | 22 | box_inner = [x, y, z]; 23 | 24 | // Tab width (mm) 25 | tab_width = thickness * 2; 26 | 27 | // Tab width (X, Y, Z, TopX, TopY) (mm) 28 | tabs = [tab_width, tab_width, tab_width, tab_width, tab_width]; 29 | 30 | // Tab width (X, Y, Z, TopX, TopY) (mm) 31 | tabs_with_lid = [tab_width, tab_width, tab_width, box_inner[0]/3, box_inner[1]/3]; 32 | 33 | 34 | if (show_3d) { 35 | if (lid) { 36 | box_3d(box_inner, thickness, tabs_with_lid); 37 | } else { 38 | box_3d(box_inner, thickness, tabs); 39 | } 40 | } else { 41 | if (lid) { 42 | box_2d(box_inner, thickness, tabs_with_lid); 43 | } else { 44 | box_2d(box_inner, thickness, tabs); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | OpenSCAD Boxmaker 2 | ================= 3 | 4 | A port of the classic laser cut finger joint boxmaker to OpenSCAD. 5 | 6 | See `demo.scad` for a complete demo. 7 | 8 | ![demo.scad in 3D preview](demo_01.png) 9 | 10 | ![demo.scad in 2D cut layout](demo_02.png) 11 | 12 | While this functions great as a one-off box generation tool, this is intended 13 | to be used as a library. Each face can be modified and then rendered either 14 | in the 2D cut layout or the 3D preview. All of the finger joints are 15 | customizable and there's an easy way to create an easy-to-remove lid if 16 | desired. 17 | 18 | License 19 | ------- 20 | 21 | This library is licensed under the [LGPL 2.1](http://www.gnu.org/licenses/lgpl-2.1.html). 22 | 23 | OpenSCAD Boxmaker 24 | Copyright (C) 2017 Steve Pomeroy 25 | 26 | This library is free software; you can redistribute it and/or 27 | modify it under the terms of the GNU Lesser General Public 28 | License as published by the Free Software Foundation; either 29 | version 2.1 of the License, or (at your option) any later version. 30 | 31 | This library is distributed in the hope that it will be useful, 32 | but WITHOUT ANY WARRANTY; without even the implied warranty of 33 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 34 | Lesser General Public License for more details. 35 | 36 | You should have received a copy of the GNU Lesser General Public 37 | License along with this library; if not, write to the Free Software 38 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 39 | -------------------------------------------------------------------------------- /demo.scad: -------------------------------------------------------------------------------- 1 | include ; 2 | 3 | // Box inner dimensions 4 | box_inner = [100, 70, 50]; 5 | // Material thickness (mm) 6 | thickness = 3; 7 | 8 | // Tab width (X, Y, Z, TopX, TopY) (mm) 9 | // The TopX and TopY here create a nice easy-to-open lid. 10 | tabs_with_lid = [7, 7, 7, box_inner[0]/3, box_inner[1]/3]; 11 | 12 | box_with_lid(box_inner, thickness, tabs_with_lid); 13 | 14 | // When there's no top, leave off the last two dimensions 15 | // Tab width (X, Y, Z) (mm) 16 | tabs_no_top = [7, 7, 7]; 17 | 18 | translate([box_inner[0] + 20, 0, 0]) 19 | box_without_top(box_inner, thickness, tabs_no_top); 20 | 21 | module box_with_lid(box_inner, thickness, tabs) { 22 | // Due to the limitations of OpenSCAD, the best way to allow for the same 23 | // geometry to be used in both the 3D and 2D previews is to comment / 24 | // uncomment portions of the code. Please comment/uncomment the appropriate 25 | // BEGIN/END sections below. 26 | 27 | // BEGIN 2D LAYOUT 28 | //layout_2d(box_inner, thickness) { 29 | // END 2D LAYOUT 30 | 31 | // BEGIN 3D PREVIEW 32 | color("black") cube(box_inner); layout_3d(box_inner, thickness) { 33 | // END 3D PREVIEW 34 | 35 | difference() { 36 | side_a_top(box_inner, thickness, tabs); 37 | text("top"); 38 | translate([box_inner[0] / 2, box_inner[1] / 2]) 39 | circle(r=10); 40 | } 41 | 42 | difference() { 43 | side_a(box_inner, thickness, tabs); 44 | text("bottom"); 45 | } 46 | 47 | difference() { 48 | side_b(box_inner, thickness, tabs); 49 | text("left"); 50 | } 51 | 52 | difference() { 53 | side_b(box_inner, thickness, tabs); 54 | text("right"); 55 | } 56 | 57 | difference() { 58 | side_c(box_inner, thickness, tabs); 59 | text("front"); 60 | } 61 | 62 | difference() { 63 | side_c(box_inner, thickness, tabs); 64 | text("back"); 65 | } 66 | } 67 | } 68 | 69 | module box_without_top(box_inner, thickness, tabs) { 70 | // Due to the limitations of OpenSCAD, the best way to allow for the same 71 | // geometry to be used in both the 3D and 2D previews is to comment / 72 | // uncomment portions of the code. Please comment/uncomment the appropriate 73 | // BEGIN/END sections below. 74 | 75 | // BEGIN 2D LAYOUT 76 | //layout_2d(box_inner, thickness) { 77 | // END 2D LAYOUT 78 | 79 | // BEGIN 3D PREVIEW 80 | color("black") cube(box_inner); layout_3d(box_inner, thickness) { 81 | // END 3D PREVIEW 82 | 83 | // put this here to leave the lid off 84 | // Top 85 | empty(); 86 | 87 | difference() { 88 | side_a(box_inner, thickness, tabs); 89 | text("bottom"); 90 | } 91 | 92 | difference() { 93 | side_b(box_inner, thickness, tabs); 94 | text("left"); 95 | } 96 | 97 | difference() { 98 | side_b(box_inner, thickness, tabs); 99 | text("right"); 100 | } 101 | 102 | difference() { 103 | side_c(box_inner, thickness, tabs); 104 | text("front"); 105 | } 106 | 107 | difference() { 108 | side_c(box_inner, thickness, tabs); 109 | text("back"); 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /boxmaker.scad: -------------------------------------------------------------------------------- 1 | /* 2 | * A finger-jointed lasercut box maker OpenSCAD library. 3 | * 4 | * Includes code from https://www.thingiverse.com/thing:448592/ 5 | * http://boxdesigner.connectionlab.org/ 6 | */ 7 | module box_2d(box_inner, thickness, tabs, margin=2) { 8 | layout_2d(box_inner, thickness) { 9 | side_a_top(box_inner, thickness, tabs); 10 | side_a(box_inner, thickness, tabs); 11 | side_b(box_inner, thickness, tabs); 12 | side_b(box_inner, thickness, tabs); 13 | side_c(box_inner, thickness, tabs); 14 | side_c(box_inner, thickness, tabs); 15 | } 16 | } 17 | 18 | // An assembled version of the box for previewing. 19 | module box_3d(inner, thickness, tabs) { 20 | layout_3d(box_inner, thickness) { 21 | side_a_top(box_inner, thickness, tabs); 22 | side_a(box_inner, thickness, tabs); 23 | side_b(box_inner, thickness, tabs); 24 | side_b(box_inner, thickness, tabs); 25 | side_c(box_inner, thickness, tabs); 26 | side_c(box_inner, thickness, tabs); 27 | } 28 | } 29 | 30 | module layout_2d(inner, thickness, margin=2) { 31 | spacingA = inner[0] + thickness * 2 + margin; 32 | spacingB = inner[1] + thickness * 2 + margin; 33 | 34 | // bottom 35 | children(0); 36 | // top 37 | translate([spacingA, 0, 0]) 38 | children(1); 39 | // left 40 | translate([spacingA * 2, inner[1], 0]) 41 | rotate([0, 0, -90]) 42 | children(2); 43 | // right 44 | translate([spacingA * 2 + inner[2] + thickness * 2 + margin, inner[1], 0]) 45 | rotate([0, 0, -90]) 46 | children(3); 47 | // front 48 | translate([0, inner[1] + margin + thickness * 2, 0]) 49 | children(4); 50 | // back 51 | translate([inner[0] + margin + thickness * 2, 52 | inner[1] + margin + thickness * 2, 0]) 53 | children(5); 54 | } 55 | 56 | module layout_2d_horizontal(inner, thickness, margin=2) { 57 | spacingA = inner[0] + thickness * 2 + margin; 58 | spacingB = inner[1] + thickness * 2 + margin; 59 | 60 | // bottom 61 | children(0); 62 | // top 63 | translate([spacingA, 0, 0]) 64 | children(1); 65 | // left 66 | translate([spacingA * 2, spacingB, 0]) 67 | children(2); 68 | // right 69 | translate([spacingA * 2 + spacingB, spacingB, 0]) 70 | children(3); 71 | // front 72 | translate([0, spacingB, 0]) 73 | children(4); 74 | // back 75 | translate([spacingA, spacingB, 0]) 76 | children(5); 77 | } 78 | 79 | // An assembled version of the box for previewing. 80 | module layout_3d(box_inner, thickness) { 81 | color("red") { 82 | // top 83 | translate([0, 0, box_inner[2]]) 84 | linear_extrude(height=thickness) 85 | children(0); 86 | 87 | // bottom 88 | translate([0, box_inner[1], 0]) 89 | rotate([180, 0, 0]) 90 | linear_extrude(height=thickness) 91 | children(1); 92 | } 93 | 94 | color("green") { 95 | // left 96 | translate([0, box_inner[1], 0]) 97 | rotate([90, 0, -90]) 98 | linear_extrude(height=thickness) 99 | children(2); 100 | 101 | // right 102 | translate([box_inner[0], 0, 0]) 103 | rotate([90, 0, 90]) 104 | linear_extrude(height=thickness) 105 | children(3); 106 | } 107 | 108 | // front 109 | color("blue") { 110 | translate([0, 0, 0]) 111 | rotate([90, 0, 0]) 112 | linear_extrude(height=thickness) 113 | children(4); 114 | 115 | // back 116 | translate([box_inner[0], box_inner[1], 0]) 117 | rotate([90, 0, 180]) 118 | linear_extrude(height=thickness) 119 | children(5); 120 | } 121 | } 122 | 123 | module side_a_top(inner, thickness, tabs) { 124 | side([inner[0], inner[1]], 125 | thickness, 126 | [tabs[3], tabs[4], tabs[3], tabs[4]], 127 | [0, 0, 0, 0]); 128 | } 129 | 130 | module side_a(inner, thickness, tabs) { 131 | side([inner[0], inner[1]], 132 | thickness, 133 | [tabs[0], tabs[1], tabs[0], tabs[1]], 134 | [0, 0, 0, 0]); 135 | } 136 | 137 | // left/right 138 | module side_b(inner, thickness, tabs) { 139 | side([inner[1], inner[2]], 140 | thickness, 141 | [tabs[4], tabs[2], tabs[1], tabs[2]], 142 | [1, 0, 1, 0]); 143 | } 144 | 145 | // front/back 146 | module side_c(inner, thickness, tabs) { 147 | side([inner[0], inner[2]], 148 | thickness, 149 | [tabs[3], tabs[2], tabs[0], tabs[2]], 150 | [1, 1, 1, 1]); 151 | } 152 | 153 | module side(inner, thickness, tabs, polarity) { 154 | SMIDGE = 0.1; 155 | x = inner[0] + thickness * 2; 156 | y = inner[1] + thickness * 2; 157 | 158 | translate([-thickness, -thickness]) 159 | difference() { 160 | square([x, y]); 161 | 162 | // bottom 163 | if (tabs[2] > 0) 164 | translate([0, -SMIDGE, 0]) 165 | edge_cuts(x, tabs[2], thickness + SMIDGE, polarity[2]); 166 | 167 | // top 168 | if (tabs[0] > 0) 169 | translate([0, y - thickness, 0]) 170 | edge_cuts(x, tabs[0], thickness + SMIDGE, polarity[0]); 171 | 172 | rotate([0, 0, -90]) { 173 | translate([-y, 0, 0]) { 174 | // left 175 | if (tabs[3] > 0) 176 | translate([0, -SMIDGE, 0]) 177 | edge_cuts(y, tabs[3], thickness + SMIDGE, polarity[3]); 178 | 179 | // right 180 | if (tabs[1] > 0) 181 | translate([0, x - thickness, 0]) 182 | edge_cuts(y, tabs[1], thickness + SMIDGE, polarity[1]); 183 | } 184 | } 185 | } 186 | } 187 | 188 | module edge_cuts(length, finger_width, cut_depth, polarity) { 189 | if (polarity == 0) { 190 | outside_cuts(length, finger_width, cut_depth); 191 | } else { 192 | difference() { 193 | square([length, cut_depth]); 194 | outside_cuts(length, finger_width, cut_depth); 195 | } 196 | } 197 | } 198 | 199 | // This module below based https://www.thingiverse.com/thing:448592/ 200 | // Customizable Box with Finger Joints by txoof 201 | // licensed under the Creative Commons - Attribution license. 202 | 203 | //cuts that fall at the end of an edge requiring an extra long cut 204 | module outside_cuts(length, finger_width, cut_depth) { 205 | //Calculate the maximum number of fingers and cuts possible 206 | maxDiv = floor(length / finger_width); 207 | 208 | //the usable divisions value must be odd for this layout 209 | uDiv = (maxDiv % 2) == 0 ? maxDiv - 3 : maxDiv - 2; 210 | 211 | numFinger = ceil(uDiv / 2); 212 | numCuts = floor(uDiv / 2); 213 | //calculate the length of the extra long cut 214 | endCut = (length - uDiv * finger_width)/2; 215 | //amount of padding to add to the iterative placement of cuts 216 | // this is the extra long cut at the beginning and end of the edge 217 | padding=endCut + finger_width; 218 | 219 | square([endCut, cut_depth]); 220 | 221 | for (i=[0:numCuts]) { 222 | if (i < numCuts) { 223 | translate([i*(finger_width*2)+padding, 0, 0]) 224 | square([finger_width, cut_depth]); 225 | } else { 226 | translate([i*(finger_width*2)+padding, 0, 0]) 227 | square([endCut, cut_depth]); 228 | } 229 | } 230 | } 231 | 232 | module empty() { 233 | // Used if you don't want a given side. 234 | } 235 | --------------------------------------------------------------------------------