// // NopSCADlib Copyright Chris Palmer 2018 // nop.head@gmail.com // hydraraptor.blogspot.com // // This file is part of NopSCADlib. // // NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the // GNU General Public License as published by the Free Software Foundation, either version 3 of // the License, or (at your option) any later version. // // NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; // without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License along with NopSCADlib. // If not, see . // // //! A box made from routed or laser cut sheet sheets and printed profiles and bezels. It can be arbitrarily large //! compared to the 3D printed parts because they can be cut into interlocking sections and solvent welded //! together. The box panels can be customised to have holes and parts mounted on them by overriding the //! definitions of `box_base()`, `box_front()`, etc. //! //! `box.scad` should be ```use```d and `box_assembly.scad` ```include```d. //! //! A box is defined with a list that specifies the inside dimensions, top, bottom and side sheet materials, the //! screw type and printed part wall thickness. This diagram shows how the various dimensions are labelled: //! ![](docs/box.png) //! //! Normally the side sheets are the same type but they can be overridden individually as long as the substitute has the same thickness. // include <../utils/core/core.scad> use <../vitamins/sheet.scad> use <../vitamins/screw.scad> use <../vitamins/washer.scad> use <../vitamins/insert.scad> use <../utils/quadrant.scad> bezel_clearance = 0.2; sheet_end_clearance = 1; sheet_slot_clearance = 0.2; function box_screw(type) = type[0]; //! Screw type to be used at the corners function box_wall(type) = type[1]; //! Wall thickness of 3D parts function box_sheets(type) = type[2]; //! Sheet type used for the sides function box_top_sheet(type) = type[3]; //! Sheet type for the top function box_base_sheet(type)= type[4]; //! Sheet type for the bottom function box_feet(type) = type[5]; //! True to enable feet on the bottom bezel function box_width(type) = type[6]; //! Internal width function box_depth(type) = type[7]; //! Internal depth function box_height(type) = type[8]; //! Internal height function box_bezel_clearance(type) = bezel_clearance; function box_corner_gap(type) = 3; //! Gap between box_sheets at the corners to connect inside and outside profiles function box_profile_overlap(type) = 3 + sheet_end_clearance / 2; function box_washer(type) = screw_washer(box_screw(type)); function box_insert(type) = screw_insert(box_screw(type)); function box_hole_inset(type) = washer_radius(box_washer(type)) + 1; function box_insert_r(type) = insert_hole_radius(box_insert(type)); function box_insert_l(type) = insert_length(box_insert(type)); function box_boss_r(type) = ceil(corrected_radius(box_insert_r(type)) + box_wall(type)); function box_sheet_slot(type) = sheet_thickness(box_sheets(type)) + sheet_slot_clearance; // add some clearance function box_corner_overlap(type) = box_wall(type); function box_corner_rad(type) = box_sheet_slot(type) - sheet_slot_clearance / 2 + box_corner_gap(type) + box_corner_overlap(type); function box_sheet_r(type) = box_corner_rad(type) - box_sheet_slot(type) - box_corner_overlap(type); function box_screw_length(type, top) = screw_longer_than(2 * washer_thickness(box_washer(type)) + sheet_thickness(top ? box_top_sheet(type) : box_base_sheet(type)) + box_corner_gap(type) + box_profile_overlap(type) + box_insert_l(type) - 1); function box_wall_clearance(type) = box_sheet_slot(type) / 2 - sheet_thickness(box_sheets(type)) / 2; function box_margin(type) = box_profile_overlap(type) + box_corner_gap(type); //! How much the bezel intrudes on the specified height function box_intrusion(type) = box_hole_inset(type) + box_boss_r(type); //! Corner profile intrusion function sheet_reduction(type) = 2 * box_corner_gap(type) + sheet_end_clearance; function box_outset(type) = box_sheet_slot(type) + box_wall(type) - sheet_slot_clearance / 2; //! How much the bezel expands the specified internal size function box_inset(type) = box_wall(type) + sheet_slot_clearance / 2; //! How much the bezel intrudes on the specified width and length, away from the corners function box_bezel_height(type, bottom) = //! Bezel height for top or bottom let(t1 = sheet_thickness(box_base_sheet(type)), t2 = sheet_thickness(box_top_sheet(type))) box_corner_rad(type) + box_profile_overlap(type) + (bottom ? max(t1, t2) : t2) - sheet_thickness(box_sheets(type)); grill_hole = 5; grill_gap = 1.9; module grill(width, height, r = 1000, poly = false, h = 0) { //! A staggered array of 5mm holes to make grills in sheets. Can be constrained to be circular. Set ```poly``` ```true``` for printing, ```false``` for milling. nx = floor(width / (grill_hole + grill_gap)); xpitch = width / nx; ny = floor(height / ((grill_hole + grill_gap) * cos(30))); ypitch = height / ny; extrude_if(h) for(y = [0 : ny - 1], x = [0 : nx - 1 - (y % 2)]) { x = -width / 2 + (x + 0.5 + (y % 2) / 2) * xpitch; y = -height / 2 + (y + 0.5) * ypitch; if(sqrt(sqr(x) + sqr(y)) + grill_hole / 2 <= r) translate([x, y]) if(poly) poly_circle(r = grill_hole / 2); else circle(d = grill_hole); } } module box_corner_profile_2D(type) { //! The 2D shape of the corner profile. t = box_sheet_slot(type); difference() { union() { quadrant(box_hole_inset(type) + box_boss_r(type), box_boss_r(type)); // inside corner translate([box_corner_gap(type) + box_profile_overlap(type), box_corner_gap(type) + box_profile_overlap(type)]) rotate(180) quadrant(box_profile_overlap(type) + box_corner_rad(type), box_corner_rad(type)); // outside corner } translate([box_corner_gap(type), -t + sheet_slot_clearance / 2]) square([100, t]); translate([-t + sheet_slot_clearance / 2, box_corner_gap(type)]) square([t, 100]); } } module box_corner_profile(type) { //! Generates the corner profile STL for 3D printing. stl("box_corner_profile"); length = box_height(type) - 2 * box_margin(type); difference() { linear_extrude(height = length, center = true, convexity = 5) box_corner_profile_2D(type); for(z = [-1, 1]) translate([box_hole_inset(type), box_hole_inset(type), z * length / 2]) insert_hole(box_insert(type), 5); } } module box_corner_profile_section(type, section, sections) { //! Generates interlocking sections of the corner profile to allow it to be taller than the printer overlap = 4; length = box_height(type) - 2 * box_margin(type); section_length = round_to_layer((length - overlap) / sections); last_section = section >= sections - 1; h = last_section ? length - (sections - 1) * section_length : section_length; overlap_wall = 2; difference() { union() { linear_extrude(height = h, convexity = 5) box_corner_profile_2D(type); if(!last_section) // male end always at the top translate_z(section_length - 1) for(i = [0 : 1], offset = i * layer_height) linear_extrude(height = overlap + 1 - offset) offset(1 + offset - layer_height) offset(-overlap_wall - 1) box_corner_profile_2D(type); } if(section > 0) translate_z(last_section ? h : 0) { // female at bottom unless last section linear_extrude(height = 2 * (overlap + layer_height), center = true, convexity = 5) offset(-overlap_wall) box_corner_profile_2D(type); linear_extrude(height = 2 * layer_height, center = true, convexity = 5) offset(-overlap_wall + layer_height) box_corner_profile_2D(type); } if(!section || last_section) // insert holes always at the bottom translate([box_hole_inset(type), box_hole_inset(type)]) insert_hole(box_insert(type), 5); } } module box_corner_quadrants(type, width, depth) for(corner = [0:3]) { x = [-1,1,1,-1][corner]; y = [-1,-1,1,1][corner]; translate([x * width / 2, y * depth / 2, 0]) rotate(corner * 90) quadrant(box_intrusion(type), box_boss_r(type)); } module box_bezel(type, bottom) { //! Generates top and bottom bezel STLs stl(bottom ? "bottom_bezel" : "top_bezel"); feet = bottom && box_feet(type); t = box_sheet_slot(type); outset = box_outset(type); inner_r = box_sheet_r(type); foot_height = box_corner_gap(type) + sheet_thickness(box_base_sheet(type)) + washer_thickness(box_washer(type)) + screw_head_height(box_screw(type)) + box_profile_overlap(type) + 2; foot_length = box_corner_rad(type) * 2; height = box_bezel_height(type, bottom); foot_extension = foot_height - height; difference() { translate_z(-box_profile_overlap(type)) difference() { rounded_rectangle([box_width(type) + 2 * outset, box_depth(type) + 2 * outset, feet ? foot_height : height], box_corner_rad(type), false); // // Remove edges between the feet // if(feet) hull() { translate_z(height + 0.5) cube([box_width(type) - 2 * foot_length, box_depth(type) + 2 * outset + 1, 1], center = true); translate_z(foot_height + 1) cube([box_width(type) - 2 * (foot_length - foot_extension), box_depth(type) + 2 * outset + 1, 1], center = true); } if(feet) hull() { translate_z(height + 0.5) cube([box_width(type) + 2 * outset + 1, box_depth(type) - 2 * foot_length, 1], center = true); translate_z(foot_height + 1) cube([box_width(type) + 2 * outset + 1, box_depth(type) - 2 * (foot_length - foot_extension), 1], center = true); } } // // slots for side panels // translate_z(-box_profile_overlap(type)) linear_extrude(height = 2 * box_profile_overlap(type), center = true) for(i = [-1, 1]) { translate([i * (box_width(type) / 2 + t / 2 - sheet_slot_clearance / 2), 0]) square([t, box_depth(type) - 2 * box_corner_gap(type)], center = true); translate([0, i * (box_depth(type) / 2 + t / 2 - sheet_slot_clearance / 2)]) square([box_width(type) - 2 * box_corner_gap(type), t], center = true); } // // recess for top / bottom panel // translate_z(box_corner_gap(type)) rounded_rectangle([box_width(type) + bezel_clearance, box_depth(type) + bezel_clearance, height], inner_r + bezel_clearance / 2, false); // // leave plastic over the corner profiles // translate_z(-box_profile_overlap(type) - 1) linear_extrude(height = box_profile_overlap(type) + box_corner_gap(type) + 2) union() { difference() { square([box_width(type) - 2 * box_inset(type), box_depth(type) - 2 * box_inset(type)], center = true); box_corner_quadrants(type, box_width(type), box_depth(type)); } box_screw_hole_positions(type) poly_circle(screw_clearance_radius(box_screw(type))); } } } dowel_length = 20; dowel_wall = extrusion_width * 3; dowel_h_wall = layer_height * 6; module box_bezel_section(type, bottom, rows, cols, x, y) { //! Generates interlocking sections of the bezel to allow it to be bigger than the printer w = (box_width(type) + 2 * box_outset(type)) / cols; h = (box_depth(type) + 2 * box_outset(type)) / rows; bw = box_outset(type) - bezel_clearance / 2; bw2 = box_outset(type) + box_inset(type); dw = bw - 2 * dowel_wall; dh = box_bezel_height(type, bottom) - dowel_h_wall; dh2 = box_profile_overlap(type) + box_corner_gap(type) - dowel_h_wall; end_clearance = 0.5; module male() { rotate([90, 0, 90]) linear_extrude(height = dowel_length - 2 * end_clearance, center = true) difference() { union() { h = dh - layer_height; h2 = dh2 - layer_height; hull() { translate([bw / 2, h / 2]) square([dw - 1, h], center = true); translate([bw / 2, (h - 1) / 2]) square([dw, h - 1], center = true); } hull() { translate([bw2 / 2, h2 / 2]) square([bw2 - 2 * dowel_wall - 1, h2], center = true); translate([bw2 / 2, (h2 - 1) / 2]) square([bw2 - 2 * dowel_wall, h2 - 1], center = true); } } translate([bw2 / 2, 0]) square([box_sheet_slot(type), 2 * box_profile_overlap(type)], center = true); } } module female() { union() { translate([0, bw / 2, dh / 2]) cube([dowel_length, dw, dh], center = true); translate([0, bw2 / 2]) cube([dowel_length, bw2 - 2 * dowel_wall, dh2 * 2], center = true); hull() { translate([0, bw / 2, dh / 2]) cube([2, dw, dh], center = true); translate([0, bw / 2, dh / 2]) cube([eps, dw + 2 * extrusion_width, dh], center = true); } hull() { translate([0, bw2 / 2, dh2 / 2]) cube([2, bw2 - 2 * dowel_wall, dh2], center = true); translate([0, bw2 / 2, dh2 / 2]) cube([eps, bw2 - 2 * dowel_wall + 2 * extrusion_width, dh2], center = true); } } } module support() { if(!$preview) translate([0, bw / 2 + dw / 2]) cube([dowel_length / 2 - 0.25, 2 * extrusion_width + 0.2, dh2]); } union() { render() difference() { union() { clip(xmin = 0, xmax = w, ymin = 0, ymax = h) translate([box_width(type) / 2 + box_outset(type) - x * w, box_depth(type) / 2 + box_outset(type) - y * h, box_profile_overlap(type)]) box_bezel(type, bottom); if(x < cols - 1 && y == 0) translate([w, 0]) male(); if(x > 0 && y == rows - 1) translate([0, h]) rotate(180) male(); if(y < rows - 1 && x == cols - 1) translate([w, h]) rotate(90) male(); if(y > 0 && x == 0) rotate(-90) male(); } if(x < cols - 1 && y == rows - 1) translate([w, h]) rotate(180) female(); if(x > 0 && y == 0) female(); if(y < rows - 1 && x == 0) translate([0, h]) rotate(-90) female(); if(y > 0 && x == cols - 1) translate([w, 0]) rotate(90) female(); } if(x < cols - 1 && y == rows - 1) translate([w, h]) rotate(180) support(); if(x > 0 && y == 0) support(); if(y < rows - 1 && x == 0) translate([0, h]) rotate(-90) support(); if(y > 0 && x == cols - 1) translate([w, 0]) rotate(90) support(); } } module box_screw_hole_positions(type) for(x = [-1, 1], y = [-1, 1]) translate([x * (box_width(type) / 2 - box_hole_inset(type)), y * (box_depth(type) / 2 - box_hole_inset(type))]) children(); module box_base_blank(type) { //! Generates a 2D template for the base sheet dxf("box_base"); difference() { sheet_2D(box_base_sheet(type), box_width(type), box_depth(type), box_sheet_r(type)); box_screw_hole_positions(type) drill(screw_clearance_radius(box_screw(type)), 0); } } module box_top_blank(type) { //! Generates a 2D template for the top sheet dxf("box_top"); difference() { sheet_2D(box_top_sheet(type), box_width(type), box_depth(type), box_sheet_r(type)); box_screw_hole_positions(type) drill(screw_clearance_radius(box_screw(type)), 0); } } function subst_sheet(type, sheet) = let(s = box_sheets(type)) sheet ? assert(sheet_thickness(sheet) == sheet_thickness(s)) sheet : s; module box_shelf_blank(type, sheet = false) { //! Generates a 2D template for a shelf sheet dxf("box_shelf"); difference() { sheet_2D(subst_sheet(type, sheet), box_width(type) - bezel_clearance, box_depth(type) - bezel_clearance, 1); offset(bezel_clearance / 2) box_corner_quadrants(type, box_width(type), box_depth(type)); } } module box_left_blank(type, sheet = false) { //! Generates a 2D template for the left sheet, ```sheet``` can be set to override the type dxf("box_left"); sheet_2D(subst_sheet(type, sheet), box_depth(type) - sheet_reduction(type), box_height(type) - sheet_reduction(type), 1); } module box_right_blank(type, sheet = false) { //! Generates a 2D template for the right sheet, ```sheet``` can be set to override the type dxf("box_right"); sheet_2D(subst_sheet(type, sheet), box_depth(type) - sheet_reduction(type), box_height(type) - sheet_reduction(type), 1); } module box_front_blank(type, sheet = false) { //! Generates a 2D template for the front sheet, ```sheet``` can be set to override the type dxf("box_front"); sheet_2D(subst_sheet(type, sheet), box_width(type) - sheet_reduction(type), box_height(type) - sheet_reduction(type), 1); } module box_back_blank(type, sheet = false) { //! Generates a 2D template for the back sheet, ```sheet``` can be set to override the type dxf("box_back"); sheet_2D(subst_sheet(type, sheet), box_width(type) - sheet_reduction(type), box_height(type) - sheet_reduction(type), 1); } module box_base(type) render_2D_sheet(box_base_sheet(type)) box_base_blank(type); //! Default base, can be overridden to customise module box_top(type) render_2D_sheet(box_top_sheet(type)) box_top_blank(type); //! Default top, can be overridden to customise module box_back(type) render_2D_sheet(box_sheets(type)) box_back_blank(type); //! Default back, can be overridden to customise module box_front(type) render_2D_sheet(box_sheets(type)) box_front_blank(type); //! Default front, can be overridden to customise module box_left(type) render_2D_sheet(box_sheets(type)) box_left_blank(type); //! Default left side, can be overridden to customise module box_right(type) render_2D_sheet(box_sheets(type)) box_right_blank(type); //! Default right side, can be overridden to customise