/////////////////////////////////////////////////////////////////////////////// // Atreides case file, OpenSCAD laser cut format // // Copyright 2014-2020 Phil Hagelberg, 2021 Armaan Bhojwani, GPLv3 // // All distances are in mm // /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// // VARIABLES // /////////////////////////////////////////////////////////////////////////////// /* Set output quality */ $fn = 150; /* Distance between key centers. */ column_spacing = 19; row_spacing = column_spacing; /* This number should exceed row_spacing and column_spacing. The default gives a 1mm = (20mm - 19mm) gap between keycaps and cuts in the top plate. */ key_hole_size = 20; /* The angle between the halves is twice this number */ angle = 10; /* This constant allows tweaking the location of the screw holes near the USB cable. Only useful with small `angle` values. Try the value of 10 with angle=0. */ back_screw_hole_offset = 0; /* The radius of screw holes. Holes will be slightly bigger due to the cut width. */ screw_hole_radius = 1.5; /* Each screw hole is a hole in a "washer". How big these "washers" depends on the material used: this parameter and the `switch_hole_size` determine the spacer wall thickness. */ washer_radius = 4 * screw_hole_radius; /* The approximate size of switch holes. Used to determine how thick walls can be, i.e. how much room around each switch hole to leave. See spacer(). */ switch_hole_size = 14; /* Distance between halves. */ hand_separation = key_hole_size - switch_hole_size; /* Sets whether the case should use notched holes. As far as I can tell these notches are not all that useful... */ use_notched_holes = true; /* Number of rows and columns in the matrix. You need to update staggering_offsets if you change n_cols. */ n_rows = 4; n_cols = 5; /* The width of the USB cable hole in the spacer. */ cable_hole_width = 12; /* Vertical column staggering offsets. The first element should be zero. */ staggering_offsets = [0, 5, 11, 6, 3]; /* Width and height between screw holes of OLED */ oled_width = 20; oled_height = 20; /* Width and height between screw holes of pointer */ pointer_width = 10.5; pointer_height = 10.5; /////////////////////////////////////////////////////////////////////////////// // META MODULES // /////////////////////////////////////////////////////////////////////////////// module rz(angle, center=undef) { /* Rotate children `angle` degrees around `center`. */ translate(center) { rotate(angle) { translate(-center) { for (i=[0:$children-1]) children(i); } } } } /* Compute coordinates of a point obtained by rotating p angle degrees around center. Used to compute locations of screw holes near the USB cable hole. */ function rz_fun(p, angle, center) = [ cos(angle) * (p[0] - center[0]) - sin(angle) * (p[1] - center[1]) + center[0], sin(angle) * (p[0] - center[0]) + cos(angle) * (p[1] - center[1])+ center[1]]; /* Cherry MX switch hole with the center at `position`. Sizes come from the ErgoDox design. */ module switch_hole(position, notches=use_notched_holes) { hole_size = 13.97; notch_width = 3.5001; notch_offset = 4.2545; notch_depth = 0.8128; translate(position) { union() { square([hole_size, hole_size], center=true); if (notches == true) { translate([0, notch_offset]) { square([hole_size+2*notch_depth, notch_width], center=true); } translate([0, -notch_offset]) { square([hole_size+2*notch_depth, notch_width], center=true); } } } } }; /* Create a hole for a regular key */ module regular_key(position, size) { translate(position) { square([size, size], center=true); } } /* Create a column of keys */ module column (bottom_position, switch_holes, key_size=key_hole_size, rows=n_rows) { translate(bottom_position) { for (i = [0:(rows-1)]) { if (switch_holes == true) { switch_hole([0, i*column_spacing]); } else { regular_key([0, i*column_spacing], key_size); } } } } /* Rotate the right half of the keys around the top left corner of the thumb key. Assumes that the thumb key is a 1x1.5 key and that it is shifted 0.5*column_spacing up relative to the nearest column. */ module rotate_half() { rotation_y_offset = 1.75 * column_spacing; for (i=[0:$children-1]) { rz(angle, [hand_separation, rotation_y_offset]) { children(i); } } } /* Shift everything right to get desired hand separation */ module add_hand_separation() { for (i=[0:$children-1]) { /* Half the value because when it is mirrored, it gets cancelled out */ translate([0.5*hand_separation, 0]) children(i); } } module screw_hole(radius, offset_radius, position, direction) { /* Create a screw hole of radius `radius` at a location `offset_radius` from `position`, (diagonally), in the direction `direction`. Oh, what a mess this is. direction is the 2-element vector specifying to which side of position to move to, [-1, -1] for bottom left, etc. radius_offset is the offset in the x (or y) direction so that we're offset_radius from position */ radius_offset = offset_radius / sqrt(2); /* key_hole_offset if the difference between key spacing and key hole edge */ key_hole_offset = 0.5*(row_spacing - key_hole_size); x = position[0] + (radius_offset - key_hole_offset) * direction[0]; y = position[1] + (radius_offset - key_hole_offset) * direction[1]; translate([x,y]) { circle(radius); } } /* Create screw holes for the oled screen */ module oled_holes(hole_radius) { translate([0,85]) { translate([-oled_width/2,-oled_height/2]) { translate([0, oled_height]) { screw_hole(hole_radius, washer_radius); } translate([oled_width, oled_height]) { screw_hole(hole_radius, washer_radius); } translate([oled_width, 0]) { screw_hole(hole_radius, washer_radius); } screw_hole(hole_radius, washer_radius); } } oled_passthrough(); } /* Passthrough in switch plate for the OLED cables */ module oled_passthrough() { translate([0, 97]) { square([14, 8], center=true); } } /* Create screw holes for the pointer module */ module pointer_holes(hole_radius) { rotate(angle) { translate([column_spacing*1.95, row_spacing*2.5]) { translate([-pointer_width/2,-pointer_height/2]) { translate([0, pointer_height]) { screw_hole(hole_radius, washer_radius); } translate([pointer_width, pointer_height]) { screw_hole(hole_radius, washer_radius); } translate([pointer_width, 0]) { screw_hole(hole_radius, washer_radius); } screw_hole(hole_radius, washer_radius); } } } pointer_passthrough(); } module pointer_passthrough() { rotate(angle) { translate([column_spacing*1.95-4, 16+row_spacing*2.5]) { square([pointer_width+5, 15], center=true); } } } // Arduino Pro mini module promini(){ square([20, 30], center=true); } module prominis(){ translate([-18, 60]) { rotate(-angle-90) { promini(); } } translate([30, 95]) { rotate(90) { promini(); } } } module right_screw_holes(hole_radius) { /* Coordinates of the back right screw hole before rotation... */ back_right = [(n_cols+2)*row_spacing, staggering_offsets[n_cols-1] + n_rows * column_spacing]; /* ...and after */ tmp = rz_fun(back_right, angle, [0, 2.25*column_spacing]); nudge = 0.75; rotate_half() { add_hand_separation() { screw_hole(hole_radius, washer_radius, [row_spacing, 0], [-nudge, -nudge]); screw_hole(hole_radius, washer_radius, [(n_cols+2)*row_spacing, staggering_offsets[n_cols-1]], [nudge, -nudge]); screw_hole(hole_radius, washer_radius, back_right, [nudge, nudge]); } } /* Add the screw hole near the cable hole */ translate([washer_radius - tmp[0] - 0.5*hand_separation, back_screw_hole_offset]) { rotate_half() { add_hand_separation() { screw_hole(hole_radius, washer_radius, back_right, [nudge, nudge]); } } } } /* Create all the screw holes */ module screw_holes(hole_radius) { right_screw_holes(hole_radius); mirror ([1,0,0]) { right_screw_holes(hole_radius); } } /////////////////////////////////////////////////////////////////////////////// // PLATE MODULES // /////////////////////////////////////////////////////////////////////////////// /* Create switch holes or key holes for the right half of the keyboard. Different key_sizes are used in top_plate() and spacer(). */ module right_half(switch_holes=true, key_size=key_hole_size) { x_offset = 0.5 * row_spacing; y_offset = 0.5 * column_spacing; thumb_key_offset = y_offset + 0.25 * column_spacing; rotate_half() { add_hand_separation() { // Add thumb keys for (j=[0:(2-1)]) { column([x_offset + (j)*row_spacing, y_offset], switch_holes, key_size, 2); } // Add thumb keys for (j=[0:(n_cols-1)]) { column([x_offset + (j+2)*row_spacing, y_offset + staggering_offsets[j]], switch_holes, key_size); } } } } /* Mirror the right half to create the left half */ module left_half(switch_holes=true, key_size=key_hole_size) { mirror ([1,0,0]) { right_half(switch_holes, key_size); } } /* Create the bottom layer of the case */ module bottom_plate() { difference() { hull() { screw_holes(washer_radius); } screw_holes(screw_hole_radius); } } /* Create the top layer of the case */ module top_plate() { difference() { bottom_plate(); right_half(false); left_half(false); oled_holes(screw_hole_radius); pointer_holes(screw_hole_radius); } } /* Create the switch plate */ module switch_plate() { difference() { bottom_plate(); right_half(); left_half(); oled_passthrough(); pointer_passthrough(); /* prominis(); */ } } /* Create a spacer layer */ module spacer() { difference() { union() { difference() { bottom_plate(); hull() { right_half(switch_holes=false, key_size=switch_hole_size + 3); left_half(switch_holes=false, key_size=switch_hole_size + 3); } /* Add the USB cable hole */ translate([-0.5*cable_hole_width+100, 2*column_spacing]) { square([cable_hole_width, (2*n_rows) * column_spacing]); } } screw_holes(washer_radius); } screw_holes(screw_hole_radius); } } /* Display all four layers */ top_plate(); translate([350, 0]) { switch_plate(); } translate([0, 175]) { bottom_plate(); } translate([350, 175]) { spacer(); } ///////////////////////////////////////////////////////////////////////////////