2019-06-08 17:10:47 -04:00
//
// 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 <https://www.gnu.org/licenses/>.
//
//
//! 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.
//
2020-02-29 12:52:36 -05:00
include < ../utils/core/core.scad >
2019-06-11 18:20:28 -04:00
use < ../vitamins/sheet.scad >
use < ../vitamins/screw.scad >
use < ../vitamins/washer.scad >
2020-02-26 08:46:11 -05:00
use < ../vitamins/insert.scad >
2019-06-11 18:20:28 -04:00
use < ../utils/quadrant.scad >
2019-06-08 17:10:47 -04:00
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 ;
2019-07-01 10:05:06 -04:00
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 ) ) ;
}
}
2019-06-08 17:10:47 -04:00
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