Computer-controlled Machining
Table of Contents
1 Design
This week, I designed a shelving unit out of wood. I used OpenSCAD for the design process. Particular challenges / milestones I achieved were that I:
- Designed the individual modular components, consisting of a shelf and a vertical support. The components are highly parametric, allowing you to change the number of shelves or the stock/mill thickness at will.
- Wrote code that would either spread out the components in a compact 2D form for printing, or extrude and assemble them into the completed 3D shelf.
- Carefully designed the shelves according to a common scale so that the parts would fit perfectly together on a 2D board. This was intended to save on the number of cuts, though because of the fixturing (we didn't have a vaccuum board, so cut out pieces would tend to move around), it turns out that the recommended plan is allowing 1" clearance between separate pieces.
- Incorporated post-processing adjustments, including so-called "dog bone" joins. Dog bone joins accomodate the diameter of the mill: when following the perimeter of a rectangle, a thick round mill will actually end up cutting a rounded rectangle. Adding a circular extrusion ("dog bone") incorporates and compensates for the thickness of the end mill, resulting in a clean sharp corner and a better press fit.
2 Post-processing
The benefits of parametric design (changing the num_shelves
parameter.)
The following figure shows a standard rectangle, a correct dog-bone rectangle, and an incorrect "mickey mouse" rectangle.
To be correct, dog bones should be a circle with radius proportional to your end mill radius. The circle must be inset, as in the second figure—the corner of the circle must coincide with the relevant corner of the rectangle.
In the incorrect version, the circle is simply centered on the rectangle's corner rather than inset. The result is protruding circles reminiscent of "mickey mouse" ears.
For dog bones, I wrote a "dog-boned rectangle" function:
module square_dog_bone(size, radius=mill_radius) { offset = radius * sin(45); // move inward at a 45 degree angle union() { square(size); translate([offset,offset]) circle(radius); translate([size[0]-offset,offset]) circle(radius); translate([offset,size[1]-offset]) circle(radius); translate([size[0]-offset,size[1]-offset]) circle(radius); } }
3 Code
in_to_mm = 25.4; board_thickness = 0.5 * in_to_mm; board_width_in = 96 * in_to_mm; board_height_in = 48 * in_to_mm; unit = 4 * in_to_mm; //shelf_width = 31.5 * in_to_mm; //shelf_depth = 19 * in_to_mm; margin = 1 * in_to_mm; notch_offset_horizontal=3*in_to_mm; //notch_width=2*in_to_mm; notch_width=board_thickness; //notch_height=4*in_to_mm; //hole_offset=3*in_to_mm; hole_height=3*in_to_mm; //back_foot_height = 2 * in_to_mm; back_foot_height = unit; peg_height = unit; hole_offset = unit; notch_height = unit; notch_chamfer_width = board_thickness/4; notch_chamfer_height = board_thickness/2; shelf_width = (6.5-0.1) * unit; shelf_depth = 4.5 * unit; num_shelves=5; cutting_offset= 1 * in_to_mm; mill_radius = 0.75/4 * in_to_mm; // new parameters // http://archive.fabacademy.org/archives/2017/fablaberfindergarden/students/260/fabacademy/week-7/ module square_dog_bone(size, radius=mill_radius) { offset = radius * sin(45); union() { square(size); translate([offset,offset]) circle(radius); translate([size[0]-offset,offset]) circle(radius); translate([offset,size[1]-offset]) circle(radius); translate([size[0]-offset,size[1]-offset]) circle(radius); } } module shelf(shelf_width, shelf_depth, notch_offset_horizontal, notch_width, notch_height, hole_offset, hole_height) { difference() { square(size=[shelf_width, shelf_depth]); // NOTCHES * translate([notch_offset_horizontal, 0, 0]) square_dog_bone(size=[notch_width, notch_height]);//square(size=[notch_width,notch_height]); translate([notch_offset_horizontal, 0,0]) polygon(points=[ [-notch_chamfer_width,0], [0,notch_chamfer_height], [0,notch_height], [notch_width,notch_height], [notch_width,notch_chamfer_height], [notch_width+notch_chamfer_width,0]]); * polygon(points=[[-notch_chamfer_width,0],[notch_width,0],[notch_width,notch_height],[0,notch_height]]); translate([(shelf_width-notch_width-notch_offset_horizontal), 0, 0]) polygon(points=[ [-notch_chamfer_width,0], [0,notch_chamfer_height], [0,notch_height], [notch_width,notch_height], [notch_width,notch_chamfer_height], [notch_width+notch_chamfer_width,0]]); //square(size=[notch_width,notch_height]); // HOLES translate([notch_offset_horizontal, shelf_depth-hole_height-hole_offset,0]) square_dog_bone(size=[notch_width,hole_height]); translate([shelf_width-notch_width-notch_offset_horizontal, shelf_depth-hole_height-hole_offset,0]) square_dog_bone(size=[notch_width,hole_height]); } } module default_shelf() { shelf(shelf_width, shelf_depth, notch_offset_horizontal= notch_offset_horizontal, notch_width=notch_width, notch_height=notch_height, hole_offset=hole_offset, hole_height=hole_height); } module spine(shelf_width, shelf_depth, notch_offset_horizontal, notch_width, notch_height, hole_offset, hole_height, back_foot_height, num_shelves=num_shelves) { module vertebra(n=num_shelves) { if(n > 0) { union() { // back-to-front rectangle square(size=[shelf_depth-hole_offset, peg_height]); // back tooth if(n == 1) { union() { translate([0,peg_height]) square(size=[notch_height,peg_height]); translate([0,peg_height*2,0]) polygon(points=[[0,0],[peg_height,0],[0,peg_height]]); } } else { translate([0,peg_height,0]) square(size=[notch_height,peg_height*2]); } // front tooth translate([shelf_depth-hole_offset-hole_height,peg_height,0]) square(size=[hole_height,peg_height]); translate([0,peg_height*3]) { vertebra(n-1); } } } } // foot polygon(points=[[0,0], [shelf_depth,0], [shelf_depth-hole_offset,back_foot_height], [0,back_foot_height]]); // bottom_shelf vertebra(); } module default_spine() { spine(shelf_width, shelf_depth, notch_offset_horizontal= notch_offset_horizontal, notch_width=notch_width, notch_height=notch_height, hole_offset=hole_offset, hole_height=hole_height, back_foot_height=back_foot_height); } module assembled_unit(num_shelves=num_shelves) { // place two spines translate([notch_offset_horizontal,0,0]) rotate([90,0,90]) union() { linear_extrude(height=board_thickness){ default_spine(); } translate([0,0,shelf_width-2*notch_offset_horizontal-board_thickness]) linear_extrude(height=board_thickness){ default_spine(); } } module shelves_iter(n=num_shelves) { if(n > 0) { linear_extrude(height=board_thickness){ default_shelf(); } translate([0,0,back_foot_height*3]) shelves_iter(n-1); } } // place shelves translate([0,0,back_foot_height]) shelves_iter(); } module 2D_assembled_unit() { module make_shelves_iter(n=num_shelves,cutting_offset=0) { if(n>0) { if(n%2==1) { default_shelf(); } else { # translate([0,shelf_depth]) scale([1,-1,1]) default_shelf(); } translate([0,shelf_depth]) make_shelves_iter(n-1); } } translate([0,0]) make_shelves_iter(cutting_offset=cutting_offset); //rotate([0,0,-90]) translate([-shelf_depth,0,0]) union() { translate([board_height_in-2*margin-shelf_depth,0]) union() { default_spine(); translate([0,0,0]) rotate([0,0,180]) translate([-shelf_depth,-(back_foot_height+num_shelves*peg_height*3)+peg_height]) % default_spine(); } } module 2D_assembled_unit_nooverlap() { module make_shelves_iter(n=num_shelves,cutting_offset=0) { if(n>0) { default_shelf(); translate([0,shelf_depth+margin]) make_shelves_iter(n-1); } } translate([0,0]) make_shelves_iter(cutting_offset=cutting_offset); //rotate([0,0,-90]) translate([-shelf_depth,0,0]) union() { translate([board_height_in-2*margin-shelf_depth,0]) union() { translate([shelf_depth+margin,0]) default_spine(); translate([0,0,0]) rotate([0,0,180]) translate([-shelf_depth,-(back_foot_height+num_shelves*peg_height*3)+peg_height]) % default_spine(); } } // THE UNDERLYING BOARD # translate([0,0,-10]) square(size=[board_height_in , board_width_in]); // THE ASSEMBLED SHELF assembled_unit(); // THE 2D VERSION translate([margin,margin]) 2D_assembled_unit_nooverlap();