diff --git a/kallax-organization/cable-hanger-insert.scad b/kallax-organization/cable-hanger-insert.scad new file mode 100644 index 0000000..4d152cc --- /dev/null +++ b/kallax-organization/cable-hanger-insert.scad @@ -0,0 +1,23 @@ +include <./insert-box.scad>; + +$fn=64; +dowelDiameter = 12.7; // 0.5 inch +module dowel() { + color("Sienna") rotate([0, 90, ]) + cylinder(h=innerSide, d=dowelDiameter, center=true); +} + +outerBox(); +supportingFelt(); + +translate([feltThickness + boardThickness, 0, feltThickness + boardThickness]) +union() { + // Everything within this union is referenced off of the inner box. The outer + // translate accounts for the felt and walls. + + translate([innerSide/2, 0, innerSide - dowelDiameter*1.5]) // center dowels on X, raise Z + union() { + translate([0, kallaxDepth*0.25, 0]) dowel(); + translate([0, kallaxDepth*0.75, 0]) dowel(); + } +} diff --git a/kallax-organization/cable-hanger.scad b/kallax-organization/cable-hanger.scad new file mode 100644 index 0000000..cd98a50 --- /dev/null +++ b/kallax-organization/cable-hanger.scad @@ -0,0 +1 @@ +dowelDiameter = 12.7; // 1/2 inch in mm diff --git a/kallax-organization/cloth-box-labels.scad b/kallax-organization/cloth-box-labels.scad new file mode 100644 index 0000000..37868e8 --- /dev/null +++ b/kallax-organization/cloth-box-labels.scad @@ -0,0 +1,8 @@ +$fn = 32; + +l = 76; +w = 50; +th = 1; + +color("black") cube([l, w, th], center=true); +// color("white") translate([0, 0, th]) linear_extrude(0.2) text("Ethernet", font="monofur", halign="center", valign="center",size=12); diff --git a/kallax-organization/constants.scad b/kallax-organization/constants.scad new file mode 100644 index 0000000..113a5fd --- /dev/null +++ b/kallax-organization/constants.scad @@ -0,0 +1,5 @@ +kallaxSide = 335; // Full width of a Kallax shelf +kallaxDepth = 390; // Full depth of a Kallax shelf + +shelfHeight = 160; // Height of a Kallax half-shelf (shelf insert) +shelfWidth = 320; // Width of a Kallax half-shelf (shelf insert) diff --git a/kallax-organization/insert-box.scad b/kallax-organization/insert-box.scad new file mode 100644 index 0000000..91381ed --- /dev/null +++ b/kallax-organization/insert-box.scad @@ -0,0 +1,61 @@ +include <./constants.scad>; + +boardThickness = 6.4; +feltThickness = 2.4; +feltWidth = 12; +innerSide = kallaxSide - 2 * (feltThickness + boardThickness); + +module feltStrip() { + color("DimGray") cube([feltThickness, kallaxDepth, feltWidth]); +} + +module sideBoard() { + difference() { + color ("BurlyWood") cube([boardThickness, kallaxDepth, kallaxSide - 2*feltThickness]); + color("black") translate([0.2, 100, 30]) rotate([90, 0, -90]) linear_extrude(0.3) + text(font="Iosevka", size=16, halign="center", valign="center", "side - plywood"); + } +} + +module horizontalBoard() { + difference() { + color ("Wheat") cube([innerSide, kallaxDepth, boardThickness]); + color("black") translate([110, 30, boardThickness - 0.2]) rotate([0, 0, 0]) linear_extrude(0.3) + text(font="Iosevka", size=16, halign="center", valign="center", "top/bot - plywood"); + } +} + +module outerBox() { + translate([feltThickness, 0, feltThickness]) union() { + // left and right side boards + translate([0, 0, 0]) sideBoard(); + translate([innerSide + boardThickness , 0, 0]) sideBoard(); + + // top and bottom horizontal boards + translate([boardThickness, 0, 0]) horizontalBoard(); + translate([boardThickness, 0, innerSide + boardThickness]) horizontalBoard(); + + } +} + +module supportingFelt() { + // felt strips for the left side + translate([0, 0, feltThickness + 20]) feltStrip(); + translate([0, 0, (innerSide - feltThickness)/2]) feltStrip(); + translate([0, 0, feltThickness + innerSide - feltWidth - 20]) feltStrip(); + + // felt strips for the right side + translate([innerSide + feltThickness + 2*boardThickness, 0, feltThickness + 20]) feltStrip(); + translate([innerSide + feltThickness + 2*boardThickness, 0, (innerSide - feltThickness)/2]) feltStrip(); + translate([innerSide + feltThickness + 2*boardThickness, 0, feltThickness + innerSide - feltWidth - 20]) feltStrip(); + + // felt strips for the top + translate([feltWidth + boardThickness + 20, 0, 0]) rotate([0, -90, 0]) feltStrip(); + translate([(kallaxSide - feltWidth)/2, 0, 0]) rotate([0, -90, 0]) feltStrip(); + translate([kallaxSide - feltWidth - 20, 0, 0]) rotate([0, -90, 0]) feltStrip(); + + // felt strips for the bottom + translate([feltWidth + 20, 0, kallaxSide - feltThickness]) rotate([0, -90, 0]) feltStrip(); + translate([(kallaxSide - feltWidth)/2, 0, kallaxSide - feltThickness]) rotate([0, -90, 0]) feltStrip(); + translate([kallaxSide - feltWidth - 20, 0, kallaxSide - feltThickness]) rotate([0, -90, 0]) feltStrip(); +} diff --git a/kallax-organization/quarter-drawer-structural-clips.scad b/kallax-organization/quarter-drawer-structural-clips.scad new file mode 100644 index 0000000..464aa69 --- /dev/null +++ b/kallax-organization/quarter-drawer-structural-clips.scad @@ -0,0 +1,91 @@ +include <./constants.scad> + +// all measurements in mm +boardThickness = 4.7; // 1/8" plywood +feltThickness = 2.5; +clipThickness = boardThickness + 2*feltThickness; + +// measurements for the wooden panels to cut: +// +// ───────────── - top board (A) +// │ │ │ +// │───────────│ +// │ │ │ +// │───────────│ +// +// +// top board: 320.0 x 370.0 +// bottom board: 305.6 x 370.0 (320 - 2*4.7 - 2*2.5) x 370 +// side boards: 152.8 x 370.0 (160 - 4.7 - 2.5) x 370 +// inner boards: 70.45 x 370.0 ((160 - 3*4.7 - 2*2.5)/2) x 370 +// + +module customSupport() { + // 45-degree supports + difference() { + translate([clipThickness/2, -clipThickness/2, 0]) + cube([1, clipThickness, clipThickness - 2]); + + rotate([45, 0, 0]) + translate([0, -0.1, -0.1]) + cube([clipThickness, clipThickness, clipThickness]); + } +} + +module cornerClip() { + difference() { + cube([clipThickness, clipThickness, clipThickness]); + + translate([feltThickness, feltThickness, feltThickness]) + cube([boardThickness, clipThickness, clipThickness]); + + translate([feltThickness, feltThickness, feltThickness]) + cube([clipThickness, boardThickness, clipThickness]); + } +} + +module sideClip() { + difference() { + cube([clipThickness, 2 * clipThickness, clipThickness]); + + translate([-0.1, feltThickness, feltThickness]) + cube([clipThickness + 0.2, boardThickness, clipThickness]); + + translate([feltThickness, feltThickness, feltThickness]) + cube([boardThickness, 2 * clipThickness, clipThickness]); + } +} + +module crossClip() { + difference() { + cube([2 * clipThickness, clipThickness, clipThickness]); + + translate([-0.1, feltThickness, feltThickness]) + cube([2 * clipThickness + 0.2, boardThickness, clipThickness]); + + translate([clipThickness - boardThickness/2, -0.1, feltThickness]) + cube([boardThickness, clipThickness + 0.2, clipThickness]); + } +} + +difference() { + translate([0, 0, -1.4]) + union() { + for (i = [0:7]) + translate([i * (clipThickness + 2), clipThickness, 0]) + rotate([45, 0, 0]) + cornerClip(); + + for (i = [0:7]) + translate([i * (clipThickness + 2), 3 * clipThickness, 0]) + rotate([0, 0, 0]) + sideClip(); + + for (i = [0:1]) + translate([i * (2 * clipThickness + 2), 5 * clipThickness, 0]) + rotate([45, 0, 0]) + crossClip(); + } + + translate([-1, -1, -1.4]) cube([100, 100, 1.4]); +} diff --git a/kallax-organization/quarter-drawers.scad b/kallax-organization/quarter-drawers.scad new file mode 100644 index 0000000..77be4e6 --- /dev/null +++ b/kallax-organization/quarter-drawers.scad @@ -0,0 +1,525 @@ +// System to divide a Kallax half-shelf into four equal drawers +// All units measured in millimeters +include <../lib/mortice-and-tenon.scad> +include <./constants.scad> + +$fn = 32; + +module sampleBox(dims, wallThickness) { + // base + cube([dims[0], dims[1], wallThickness]); + + // top + translate([0, 0, dims[2] - wallThickness]) + cube([dims[0], dims[1], wallThickness]); + + // middle shelf + translate([0, 0, (dims[2] - wallThickness) / 2]) + cube([dims[0], dims[1], wallThickness]); + + // left wall + cube([wallThickness, dims[1], dims[2]]); + + // middle divider + translate([(dims[0] - wallThickness) / 2, 0, 0]) + cube([wallThickness, dims[1], dims[2]]); + + // right wall + translate([dims[0] - wallThickness, 0, 0]) + cube([wallThickness, dims[1], dims[2]]); + +} + +module tally(count, rightAlign = false) { + groups = floor(count / 5); + rem = count % 5; + xAlign = rightAlign ? -(6 * groups) - (rem * 1.2) : 0; + translate([xAlign, 0, 0]) + union() { + if (count > 4) { + for (i = [0: max(0, groups - 1)]) { + translate([6*i, 0, 0]) for (j = [0:3]) + translate([j*1.2, 0, 0]) + cube([0.6, 4, 0.41]); + translate([6*i - 0.8, 0.6, 0]) rotate([0, 0, -60]) cube([0.6, 6.2, 0.41]); + } + } + + if (rem != 0) { + translate([6*groups, 0, 0]) + for (j = [0: rem - 1]) + translate([j*1.2, 0, 0]) + cube([0.6, 4, 0.41]); + } + } +} + +wallThickness = 4; // Thickness of walls +feltClearance = 2.5; // Clearance for felt pads + +// Dimensions for the box that fits inside a Kallax half-shelf +boxWidth = kallaxSide - 2 * feltClearance; +boxDepth = kallaxDepth; +boxHeight = shelfHeight - 2 * feltClearance; + +drawerWidth = (boxWidth - 3 * wallThickness) / 2; +drawerHeight = (boxHeight - 3 * wallThickness) / 2; + +tenonThickness = wallThickness * 0.6; +//tenon([6, 8, 2.6]); +//morticeBlank([6, 8, 2.6], 0.1); + + +// Frame for the box will be made in sections to fit on the print bed +// There are three identical horizontal layers (base, middle shelf, top) that +// will each be made of four pieces. Looking down overhead from the Z axis they +// are: +// +// x1 x2 +// ├──┴──┼─┴─┤ +// ┌─────┬───┐ ┬ +// │ C │ D │ │ +// ├─────┼───┤ ├y +// │ A │ B │ │ +// └─────┴───┘ ┴ +// +// There are three identical drawer sides for the lower drawers that are split +// into two pieces. Looking from the side, along the X axis they are: +// +// ├───y───┤ +// ┌───┬───┐ ┬ +// │ F │ E │ ├drawerHeight +// └───┴───┘ ┴ +// +// There are three identical drawer sides for the upper drawers that are split +// into two pieces. Looking from the side, along the X axis they are: +// +// ├───y───┤ +// ┌───┬───┐ ┬ +// │ H │ G │ ├drawerHeight +// └───┴───┘ ┴ + +x1 = boxWidth * 0.6; +x2 = boxWidth - x1; +y = boxDepth / 2; +tenonDims = [6, 8, tenonThickness]; +tenonZOffset = (wallThickness - tenonThickness) / 2; +verticalTenonDims = [6, wallThickness, tenonThickness]; +throughTenonDims = [6, 8 + wallThickness, tenonThickness]; +morticeClearance = 0.1; + +module leftSideHorizontalPiece(cutWeight = true) { + difference() { + cube([x1, y, wallThickness]); + + if (cutWeight) { + union() { + // cutouts to save materials + translate([2*wallThickness, 2*wallThickness, -0.1]) + cube([drawerWidth - 2*wallThickness, y/2 - 3*wallThickness, wallThickness + 0.2]); + + translate([2*wallThickness, y/2 + wallThickness, -0.1]) + cube([drawerWidth - 2*wallThickness, y/2 - 3*wallThickness, wallThickness + 0.2]); + + translate([drawerWidth + 3*wallThickness, 2*wallThickness, -0.1]) + cube([x1 - drawerWidth - 5*wallThickness, y/2 - 3*wallThickness, wallThickness + 0.2]); + + translate([drawerWidth + 3*wallThickness, y/2 + 2*wallThickness, -0.1]) + cube([x1 - drawerWidth - 5*wallThickness, y/2 - 3*wallThickness, wallThickness + 0.2]); + } + } + } + +} + +module rightSideHorizontalPiece(cutWeight = true) { + difference() { + cube([x2, y, wallThickness]); + + if (cutWeight) { + union() { + // cutouts to save materials + translate([2*wallThickness, 2*wallThickness, -0.1]) + cube([x2 - 4*wallThickness, y/2 - 3*wallThickness, wallThickness + 0.2]); + translate([2*wallThickness, y/2 + wallThickness, -0.1]) + cube([x2 - 4*wallThickness, y/2 - 3*wallThickness, wallThickness + 0.2]); + } + } + } +} + +module sidePiece(cutWeight = true) { + union() { + difference() { + cube([wallThickness, y, drawerHeight]); + + if (cutWeight) { + // cutouts to save materials + translate([-0.1, 2*wallThickness, 2*wallThickness]) + cube([wallThickness + 0.2, y - 4*wallThickness, drawerHeight - 3*wallThickness]); + } + } + + if (cutWeight) { + translate([0, wallThickness, 1.414*wallThickness]) + rotate([-41, 0, 0]) + cube([wallThickness, wallThickness, drawerHeight * 1.19]); + + translate([0, y - 2*wallThickness, 1.414*wallThickness]) + rotate([65, 0, 0]) + cube([wallThickness, wallThickness, drawerHeight * 1.97]); + } + } +} + +module pieceA(cutWeight = true) { + difference() { + union() { + leftSideHorizontalPiece(cutWeight); + + // tenons for joining to piece C + translate([x1*0.2, y, tenonZOffset]) tenon(tenonDims); + translate([x1*0.7, y, tenonZOffset]) tenon(tenonDims); + + // tenons for joining to piece B + translate([x1, y*0.2, tenonZOffset]) + mirror([0, 1, 0]) rotate([0, 0, -90]) tenon(tenonDims); + translate([x1, y*0.7, tenonZOffset]) + mirror([0, 1, 0]) rotate([0, 0, -90]) tenon(tenonDims); + } + + union() { + // mortices for joining to piece vertical pieces + translate([tenonZOffset, y* 0.3, 0]) rotate([90, 0, 90]) + morticeBlank(verticalTenonDims, morticeClearance); + translate([tenonZOffset, y* 0.8, 0]) rotate([90, 0, 90]) + morticeBlank(verticalTenonDims, morticeClearance); + + translate([drawerWidth + wallThickness + tenonZOffset, y* 0.3, 0]) + rotate([90, 0, 90]) morticeBlank(verticalTenonDims, morticeClearance); + translate([drawerWidth + wallThickness + tenonZOffset, y* 0.8, 0]) + rotate([90, 0, 90]) morticeBlank(verticalTenonDims, morticeClearance); + + // piece label + translate([2, 2, wallThickness - 0.4]) tally(1); + } + } +} + +module pieceB(cutWeight = true) { + difference() { + union() { + rightSideHorizontalPiece(cutWeight); + + // tenons for joining to piece D + translate([x2*0.2, y, tenonZOffset]) tenon(tenonDims); + translate([x2*0.7, y, tenonZOffset]) tenon(tenonDims); + } + + union() { + // mortices for joining to piece A + translate([0, y*0.2, tenonZOffset]) + mirror([0, 1, 0]) rotate([0, 0, -90]) + morticeBlank(tenonDims, morticeClearance); + translate([0, y*0.7, tenonZOffset]) + mirror([0, 1, 0]) rotate([0, 0, -90]) + morticeBlank(tenonDims, morticeClearance); + + // mortices for joining to piece vertical pieces + translate([x2 - wallThickness + tenonZOffset, y* 0.3, 0]) + rotate([90, 0, 90]) morticeBlank(tenonDims, morticeClearance); + + translate([x2 - wallThickness + tenonZOffset, y* 0.8, 0]) + rotate([90, 0, 90]) morticeBlank(tenonDims, morticeClearance); + + // piece label + translate([2, 2, wallThickness - 0.4]) tally(2); + } + } +} + +module pieceC(cutWeight = true) { + difference() { + union() { + leftSideHorizontalPiece(cutWeight); + + // tenons for joining to piece D + translate([x1, y*0.2, tenonZOffset]) + mirror([0, 1, 0]) rotate([0, 0, -90]) tenon(tenonDims); + translate([x1, y*0.7, tenonZOffset]) + mirror([0, 1, 0]) rotate([0, 0, -90]) tenon(tenonDims); + } + + union() { + + // mortices for joining to piece A + translate([x1*0.2, 0, tenonZOffset]) + morticeBlank(tenonDims, morticeClearance); + translate([x1*0.7, 0, tenonZOffset]) + morticeBlank(tenonDims, morticeClearance); + + // mortices for joining to piece F/H + translate([tenonZOffset, y* 0.3, 0]) rotate([90, 0, 90]) + morticeBlank(verticalTenonDims, morticeClearance); + translate([tenonZOffset, y* 0.8, 0]) rotate([90, 0, 90]) + morticeBlank(verticalTenonDims, morticeClearance); + + translate([drawerWidth + wallThickness + tenonZOffset, y* 0.3, 0]) + rotate([90, 0, 90]) morticeBlank(verticalTenonDims, morticeClearance); + translate([drawerWidth + wallThickness + tenonZOffset, y* 0.8, 0]) + rotate([90, 0, 90]) morticeBlank(verticalTenonDims, morticeClearance); + + // piece label + translate([2, 2, wallThickness - 0.4]) tally(3); + } + } +} + +module pieceD(cutWeight = true) { + difference() { + rightSideHorizontalPiece(cutWeight); + + union() { + // mortices for joining to piece C + translate([0, y*0.2, tenonZOffset]) + mirror([0, 1, 0]) rotate([0, 0, -90]) + morticeBlank(tenonDims, morticeClearance); + translate([0, y*0.7, tenonZOffset]) + mirror([0, 1, 0]) rotate([0, 0, -90]) + morticeBlank(tenonDims, morticeClearance); + + // mortices for joining to piece B + translate([x2*0.2, 0, tenonZOffset]) + morticeBlank(tenonDims, morticeClearance); + translate([x2*0.7, 0, tenonZOffset]) + morticeBlank(tenonDims, morticeClearance); + // mortices for joining to piece F/H + translate([x2 - wallThickness + tenonZOffset, y* 0.3, 0]) + rotate([90, 0, 90]) morticeBlank(tenonDims, morticeClearance); + + translate([x2 - wallThickness + tenonZOffset, y* 0.8, 0]) + rotate([90, 0, 90]) morticeBlank(tenonDims, morticeClearance); + + // piece label + translate([2, 2, wallThickness - 0.4]) tally(4); + } + } +} + +module pieceE(cutWeight = true) { + difference() { + union() { + sidePiece(cutWeight); + + // tenons for joining to horizontal pieces + translate([tenonZOffset, y*0.3, 0]) mirror([1, 0, 0]) + rotate([-90, 0, 90]) tenon(verticalTenonDims); + translate([tenonZOffset, y*0.8, 0]) mirror([1, 0, 0]) + rotate([-90, 0, 90]) tenon(verticalTenonDims); + + // tenon for joining to piece F + translate([tenonZOffset, y, drawerHeight/2]) mirror([0, 0, 1]) + rotate([0, 90, 00]) tenon(tenonDims); + + // tenons for joining to piece G + translate([tenonZOffset, y*0.3, drawerHeight]) rotate([90, 0, 90]) + tenon(throughTenonDims); + translate([tenonZOffset, y*0.8, drawerHeight]) rotate([90, 0, 90]) + tenon(throughTenonDims); + //translate([tenonZOffset, y*0.8, 0]) mirror([1, 0, 0]) + // rotate([-90, 0, 90]) tenon(verticalTenonDims); + } + + // piece label + translate([0.4, 2, 2]) rotate([90, 0, -90]) tally(5, true); + } +} + +module pieceF(cutWeight = true) { + difference() { + union() { + sidePiece(cutWeight); + + // tenons for joining to horizontal pieces + translate([tenonZOffset, y*0.3, 0]) mirror([1, 0, 0]) + rotate([-90, 0, 90]) tenon(verticalTenonDims); + translate([tenonZOffset, y*0.8, 0]) mirror([1, 0, 0]) + rotate([-90, 0, 90]) tenon(verticalTenonDims); + + // tenons for joining to piece H + translate([tenonZOffset, y*0.3, drawerHeight]) rotate([90, 0, 90]) + tenon(throughTenonDims); + translate([tenonZOffset, y*0.8, drawerHeight]) rotate([90, 0, 90]) + tenon(throughTenonDims); + + } + + union() { + // mortice for joining to piece E + translate([tenonZOffset, 0, drawerHeight/2]) mirror([0, 0, 1]) + rotate([0, 90, 00]) tenon(tenonDims); + + // piece label + translate([0.4, 2, 2]) rotate([90, 0, -90]) tally(6, true); + } + } +} + +module pieceG(cutWeight = true) { + difference() { + union() { + sidePiece(cutWeight); + + // tenons for joining to vertical pieces above + translate([tenonZOffset, y*0.3, drawerHeight]) rotate([90, 0, 90]) + tenon(verticalTenonDims); + translate([tenonZOffset, y*0.8, drawerHeight]) rotate([90, 0, 90]) + tenon(verticalTenonDims); + + // tenon for joining to piece H + translate([tenonZOffset, y, drawerHeight/2]) mirror([0, 0, 1]) + rotate([0, 90, 00]) tenon(tenonDims); + + + } + + union() { + // mortices for joining to piece E + translate([tenonZOffset, y*0.3, 0]) rotate([90, 0, 90]) + morticeBlank(verticalTenonDims, morticeClearance); + translate([tenonZOffset, y*0.8, 0]) rotate([90, 0, 90]) + morticeBlank(verticalTenonDims, morticeClearance); + + // piece label + translate([0.4, 2, 2]) rotate([90, 0, -90]) tally(7, true); + } + } +} + +module pieceH(cutWeight = true) { + difference() { + union() { + sidePiece(cutWeight); + + // tenons for joining to vertical pieces above + translate([tenonZOffset, y*0.3, drawerHeight]) rotate([90, 0, 90]) + tenon(verticalTenonDims); + translate([tenonZOffset, y*0.8, drawerHeight]) rotate([90, 0, 90]) + tenon(verticalTenonDims); + } + + union() { + // mortices for joining to piece F + translate([tenonZOffset, y*0.3, 0]) rotate([90, 0, 90]) + morticeBlank(verticalTenonDims, morticeClearance); + translate([tenonZOffset, y*0.8, 0]) rotate([90, 0, 90]) + morticeBlank(verticalTenonDims, morticeClearance); + + // mortice for joining to piece G + translate([tenonZOffset, 0, drawerHeight/2]) mirror([0, 0, 1]) + rotate([0, 90, 00]) tenon(tenonDims); + + // piece label + translate([0.4, 2, 2]) rotate([90, 0, -90]) tally(8, true); + } + } +} + + +// Mortice and tenon test pieces +// rotate([0, 90, 0]) union() { +// cube([10, 16, wallThickness]); +// translate([2, 16, tenonZOffset]) tenon(tenonDims); +// } +// +// translate([10, 0, 0]) rotate([0, 90, 0]) difference() { +// cube([10, 16, wallThickness]); +// translate([2 - 0.1, 16 - 0.1, tenonZOffset - 0.1]) +// mirror([0, 1, 0]) morticeBlank(tenonDims, morticeClearance); +// } + +module completeBox(gap = 10, cutWeight = true) { + + // vertical layers + for (i = [0:2]) { + z = i * (wallThickness + 1.5*gap + drawerHeight); + translate([0, 0, z]) pieceA(cutWeight); + translate([x1 + gap, 0, z]) pieceB(cutWeight); + translate([0, y + gap, z]) pieceC(cutWeight); + translate([x1 + gap, y + gap, z]) pieceD(cutWeight); + } + + // lower box walls + for (i = [0:2]) { + x = i * (wallThickness + drawerWidth) + + (i == 2 ? gap : 0); + + translate([x, 0, wallThickness + gap]) pieceE(cutWeight); + translate([x, y + gap, wallThickness + gap]) pieceF(cutWeight); + } + + // upper box walls + for (i = [0:2]) { + x = i * (wallThickness + drawerWidth) + + (i == 2 ? gap : 0); + + translate([x, 0, drawerHeight + 2*wallThickness + 2*gap]) pieceG(cutWeight); + translate([x, y + gap, drawerHeight + 2*wallThickness + 2*gap]) pieceH(cutWeight); + } + +} + +module printStack() { + for (i = [0:2]) { + translate([0, 0, i * (wallThickness + 0.4)]) pieceA(); + translate([0, 0, (i+3) * (wallThickness + 0.4)]) pieceC(); + translate([0, 0, (i+6) * (wallThickness + 0.4)]) pieceB(); + translate([0, 0, (i+9) * (wallThickness + 0.4)]) pieceD(); + + translate([ + 0, + y + drawerHeight +throughTenonDims[1] + 4, + i * (wallThickness + 0.4) + wallThickness]) + rotate([90, 90, 0]) pieceE(); + + translate([0, + y + drawerHeight +throughTenonDims[1] + 4, + (i+3) * (wallThickness + 0.4) + wallThickness]) + rotate([90, 90, 0]) pieceF(); + + translate([ + 0, + y + drawerHeight +throughTenonDims[1] + 4, + (i+6) * (wallThickness + 0.4) + wallThickness]) + rotate([90, 90, 0]) pieceG(); + + translate([ + 0, + y + drawerHeight +throughTenonDims[1] + 4, + (i+9) * (wallThickness + 0.4) + wallThickness]) + rotate([90, 90, 0]) pieceH(); + + } +} + +module testStack() { + for (i = [0:2]) { + translate([0, 0, i * (wallThickness + 0.4)]) pieceA(); + translate([0, 0, (i+3) * (wallThickness + 0.4)]) pieceB(); + + translate([ + 0, + y + drawerHeight +throughTenonDims[1] + 4, + i * (wallThickness + 0.4) + wallThickness]) + rotate([90, 90, 0]) pieceE(); + + translate([0, + y + drawerHeight +throughTenonDims[1] + 4, + (i+3) * (wallThickness + 0.4) + wallThickness]) + rotate([90, 90, 0]) pieceF(); + + } +} + +completeBox(gap = 10, cutWeight = false); +// testStack(); diff --git a/lib/joints.scad b/lib/joints.scad new file mode 100644 index 0000000..08a5934 --- /dev/null +++ b/lib/joints.scad @@ -0,0 +1,140 @@ +// Create a tenon with chamfered edges on the front face. The tenon is facing +// the positive Y direction. It is offset by -0.01 in Y to make it easy to +// ensure that it intersects with the main body it is attached to. +module tenon(dims) { + translate([0, -0.01, 0]) + difference() { + cube([dims[0], dims[1] + 0.01, dims[2]]); + + // chamfers + color("blue") union() { + translate([-0.5, dims[1] + 0.1, dims[2] - 1.1]) rotate([45, 0, 0]) cube([dims[0] + 1, 2, 2]); + translate([-0.5, dims[1] + 0.1, -1.7]) rotate([45, 0, 0]) cube([dims[0] + 1, 2, 2]); + translate([-0.4, dims[1]-1, -0.5]) rotate([0, 0, 45]) cube([2, 2, dims[2] + 1]); + translate([dims[0]+0.4, dims[1]-1, -0.5]) rotate([0, 0, 45]) cube([2, 2, dims[2] + 1]); + } + } +} + +module morticeBlank(dims, clearance = 0) { + translate([-clearance, -clearance-0.01, -clearance]) + cube([dims[0] + 2 * clearance, dims[1] + 2 * clearance+0.01, dims[2] + 2 * clearance]); +} + + + +module rectDowel(dim = [6, 16, 3]) { + // rectangular dowel + translate([-dim[0]/2, -dim[1]/2, -dim[2]/2]) + cube(dim); +} + +// Create a cube with a mortice cut out of the center for a snap fit tenon. +// The mortice is aligned on the Y-axis: the dimension in X determines the +// width of the piece and mortice, the dimension in Y determines the depth or +// length of the piece, and the dimension in Z determines the height thickness. +// +// The morticeDim dimensions will be enlarged slightly to allow for adequate +// fitment of the tenon based on the clearance (defaults to 0.2). +module morticeWithSnap( + dim = [10, 20, 5], + morticeDim = [6, 8, 3], + snapDepth = 2, + snapHeight = 1, + clearance = 0.3) { + + mDim = [morticeDim[0] + 2*clearance, + morticeDim[1] + clearance, + morticeDim[2] + clearance]; + difference() { + // block + translate([-dim[0]/2, 0, -dim[2]/2]) cube(dim); + + // cutout + translate([-mDim[0]/2, -clearance, -mDim[2]/2]) union() { + cube(mDim); // mortice cutout + + // snap fit cutout + translate([0, morticeDim[1] - snapDepth, morticeDim[2] + clearance - 0.01]) + cube([morticeDim[0] + clearance, + snapDepth + clearance, + snapHeight + clearance + 0.01]); + } + } +} + +// Create a cube with a tenon and cantilevered snap fit on one side. +// The tenon is aligned on the Y-axis: the dimension in X determines the +// width of the piece and tenon, the dimension in Y determines the depth or +// length of the piece, and the dimension in Z determines the height thickness. +module tenonWithSnap( + dim = [10, 20, 5], + tenonDim = [6, 8, 3], + snapDepth = 2, + snapHeight = 1) { + + // block + translate([-dim[0]/2, 0, -dim[2]/2]) cube(dim); + + // tenon + translate([-tenonDim[0]/2, dim[1] - 0.01, -tenonDim[2]/2]) union() { + // main tenon body + tenonFlexArmThickness = min(tenonDim[2] / 2, 1); + color("blue") translate([0, 0, tenonDim[2] - tenonFlexArmThickness]) + cube([tenonDim[0], tenonDim[1], tenonFlexArmThickness]); + + // fillet on front edge of tenon + /* + translate([0, tenonDim[1] / 4 - 0.05, tenonDim[2] / 4 + 0.03]) + difference() { + color("blue") cube([tenonDim[0], tenonDim[1] / 8, tenonDim[2] / 4]); + color("green") translate([tenonDim[0] /2, tenonDim[1]/8 - tenonDim[2]/16, 0]) rotate([0, 90, 0]) + cylinder(r = tenonDim[2] / 4, h = tenonDim[0] + 0.02, center=true); + } + */ + + // 45-degree chamfer on top edges of tenon base (alignment feature) + difference() { + cube([tenonDim[0], tenonDim[1]*0.4, tenonDim[2]]); + translate([-0.01, tenonDim[1]*0.4, -tenonDim[2]/2]) rotate([45, 0, 0]) + cube([tenonDim[0] + 0.02, tenonDim[1] / 4, tenonDim[2] / 2]); + } + + // cantilevered snap fit + translate([0, tenonDim[1] - snapDepth, tenonDim[2] - 0.01]) + difference() { + cube([tenonDim[0], snapDepth, snapHeight + 0.01]); + color("red") union() { + // front-edge chamfer + translate([-0.01, -snapDepth, snapHeight*0.66]) rotate([-10, 0, 0]) + cube([tenonDim[0] + 0.02, snapDepth, snapHeight + 0.01]); + + // back-edge chamfer + translate([-0.01, snapDepth, snapHeight / 8]) rotate([15, 0, 0]) + cube([tenonDim[0] + 0.02, snapDepth, snapHeight + 0.01]); + + // side chamfers + translate([-tenonDim[0]/4, 0, snapHeight*0.66]) rotate([0, 10, 0]) + cube([tenonDim[0] / 4, snapDepth + 0.02, snapHeight]); + translate([tenonDim[0], 0, snapHeight*0.60]) rotate([0, -10, 0]) + cube([tenonDim[0] / 4, snapDepth + 0.02, snapHeight]); + } + } + } +} + +// Creates the tail of a dovetail for a dovetail joint. The tail is oriented in +// the positive Y direction. It is exactly (tailHeight + 0.01) tall (in the Y +// direction) and positioned so that its base is at Y=-0.01 to make it easy to +// overlap with the main body it is attached to. +module dovetail(tailHeight, tailWidthMin, tailWidthMax, depth) { + angle = atan((tailWidthMax - tailWidthMin) / (2 * tailHeight)); + + translate([0, tailHeight, 0]) + mirror([0, 1, 0]) + difference() { + cube([tailWidthMax, tailHeight + 0.01, depth + 0.01]); + color("blue") translate([tailWidthMax, 0, -0.1]) rotate([0, 0, angle]) cube([tailWidthMax, tailHeight*2, depth+0.2]); + color("blue") rotate([0, 0, -angle]) translate([-tailWidthMax, 0, -0.1]) cube([tailWidthMax, tailHeight*2, depth+0.2]); + } +} diff --git a/lib/solids.scad b/lib/solids.scad new file mode 100644 index 0000000..54e8c12 --- /dev/null +++ b/lib/solids.scad @@ -0,0 +1,14 @@ +module prism(l, w, h) { + polyhedron(// pt 0 1 2 3 4 5 + points=[[0,0,0], [0,w,h], [l,w,h], [l,0,0], [0,w,0], [l,w,0]], + // top sloping face (A) + faces=[[0,1,2,3], + // vertical rectangular face (B) + [2,1,4,5], + // bottom face (C) + [0,3,5,4], + // rear triangular face (D) + [0,4,1], + // front triangular face (E) + [3,2,5]] + );}