// -*- mode: c -*- /* All distances are in mm. */ /* set output quality */ $fn = 50; /* 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; /* rotation angle; the angle between the halves is twice this number */ angle = 10; /* 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" should be depends on the material used: this parameter and the `switch_hole_size` determine the spacer wall thickness. */ washer_radius = 4 * screw_hole_radius; /* 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; /* Distance between halves. */ hand_separation = 0; /* 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; /* 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; /* Number of thumb keys (per hand), try 1 or 2. */ n_thumb_keys = 1; /* 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]; /* Whether or not to split the spacer into quarters. */ quarter_spacer = false; /* Where the top/bottom split of a quartered spacer will be. */ spacer_quartering_offset = 60; /* 2D design for cutting or 3D design for printing */ dimensions = 3; /* Following variables configure a 3D Atreus case */ /* Height for the (aesthetic) top piece */ 3d_top_height = 3; /* Height for the switch plate; Cherry MX wants 1.5 */ 3d_plate_height = 1.5; /* Height of ribs for supporting the switch plate; 3 is the max for Cherry MX */ 3d_ribs_height = 3; /* Height of the spacer layer */ 3d_spacer_height = 4.5; /* Height of the bottom piece */ 3d_bottom_height = 3; /* Whether or not to split all pieces into left and right halves */ 3d_split = true; /* How far to separate the halves if split */ 3d_split_offset = 5; module rz(angle, center=undef) { /* Rotate children `angle` degrees around `center`. */ translate(center) { rotate(angle) { translate(-center) { for (i=[0:$children-1]) child(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]]; module switch_hole(position, notches=use_notched_holes) { /* Cherry MX switch hole with the center at `position`. Sizes come from the ErgoDox design. */ 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); } } } } }; module regular_key(position, size) { /* Create a hole for a regular key. */ translate(position) { square([size, size], center=true); } } module thumb_key(position, size) { /* Create a hole for a 1x1.5 unit thumb key. */ translate(position) { scale([1, 1.5]) { translate(-position) { regular_key(position, size); } } } } module column (bottom_position, switch_holes, key_size=key_hole_size) { /* Create a column of keys. */ translate(bottom_position) { for (i = [0:(n_rows-1)]) { if (switch_holes == true) { switch_hole([0, i*column_spacing]); } else { regular_key([0, i*column_spacing], key_size); } } } } module column_rib (bottom_position, switch_holes, key_size=key_hole_size) { /* Create a column of ribbing. */ translate(bottom_position) { for (i = [0:(n_rows-1)]) { if (switch_holes == true) { translate([0, i*column_spacing]) square([16,16], center=true); } else { translate([0, i*column_spacing]) square([16,16], center=true); } } } } module rotate_half() { /* 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. */ rotation_y_offset = 1.75 * column_spacing; for (i=[0:$children-1]) { rz(angle, [hand_separation, rotation_y_offset]) { child(i); } } } module add_hand_separation() { /* Shift everything right to get desired hand separation. */ for (i=[0:$children-1]) { translate([0.5*hand_separation, /* we get back the full separation because of mirroring */ 0]) child(i); } } module right_half (switch_holes=true, key_size=key_hole_size) { /* Create switch holes or key holes for the right half of the keyboard. Different key_sizes are used in top_plate() and spacer(). */ x_offset = 0.5 * row_spacing; y_offset = 0.5 * column_spacing; thumb_key_offset = y_offset + 0.5 * column_spacing; rotate_half() { add_hand_separation() { for (j=[0:(n_thumb_keys-1)]) { if (switch_holes == true) { switch_hole([x_offset + j*row_spacing, thumb_key_offset]); } else { thumb_key([x_offset + j*row_spacing, thumb_key_offset], key_size); } } for (j=[0:(n_cols-1)]) { column([x_offset + (j+n_thumb_keys)*row_spacing, y_offset + staggering_offsets[j]], switch_holes, key_size); } } } } 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); } } module right_screw_holes(hole_radius) { /* coordinates of the back right screw hole before rotation... */ back_right = [(n_cols+n_thumb_keys)*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+n_thumb_keys)*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], back_screw_hole_offset]) { rotate_half() { add_hand_separation() { screw_hole(hole_radius, washer_radius, back_right, [nudge, nudge]); } } } } module screw_holes(hole_radius) { /* Create all the screw holes. */ right_screw_holes(hole_radius); mirror ([1,0,0]) { right_screw_holes(hole_radius); } } module left_half(switch_holes=true, key_size=key_hole_size) { mirror ([1,0,0]) { right_half(switch_holes, key_size); } } module bottom_plate() { /* bottom layer of the case */ difference() { hull() { screw_holes(washer_radius); } screw_holes(screw_hole_radius); } } module top_plate() { /* top layer of the case */ difference() { bottom_plate(); right_half(false); left_half(false); } } module switch_plate() { /* the switch plate */ difference() { bottom_plate(); right_half(); left_half(); } } module right_half_ribs(switch_holes=true, key_size=key_hole_size) { /* Make ribbing for the 3D case */ x_offset = 0.5 * row_spacing; y_offset = 0.5 * column_spacing; thumb_key_offset = y_offset + 0.5 * column_spacing; rotate_half() { add_hand_separation() { for (j=[0:(n_thumb_keys-1)]) { if (switch_holes == true) { translate([x_offset + j*row_spacing, thumb_key_offset]) square([16,16], center=true); } else { translate([x_offset + j*row_spacing, thumb_key_offset]); square([16,16], center=true); } } for (j=[0:(n_cols-1)]) { column_rib([x_offset + (j+n_thumb_keys)*row_spacing, y_offset + staggering_offsets[j]], switch_holes, key_size); } } } } module left_half_ribs(switch_holes=true, key_size=key_hole_size) { mirror ([1,0,0]) { right_half_ribs(switch_holes, key_size); } } module switch_plate_ribs() { /* Make the ribs layer for the 3D case */ difference() { bottom_plate(); right_half_ribs(); left_half_ribs(); } } module spacer() { /* Create a 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, 2*column_spacing]) { square([cable_hole_width, (2*n_rows) * column_spacing]); } } screw_holes(washer_radius); } screw_holes(screw_hole_radius); } } module spacer_quadrant(spacer_quadrant_number) { /* Cut a quarter of a spacer. */ translate([0, spacer_quartering_offset]) { intersection() { translate([0, -spacer_quartering_offset]) { spacer(); } rotate([0, 0, spacer_quadrant_number * 90]) { square([1000, 1000]); } } } } module quartered_spacer() { /* Assemble all four quarters of a spacer. */ spacer_quadrant(0); spacer_quadrant(1); translate([-5,-10]) spacer_quadrant(2); translate([5,-10]) spacer_quadrant(3); } module 3d_plate() { /* Assemble the plate and ribs for 3D */ translate([0,0,0]) linear_extrude(3d_plate_height) switch_plate(); translate([0, 0, 3d_plate_height]) linear_extrude(3d_ribs_height) switch_plate_ribs(); } module 3d_bottom() { /* Assemble the bottom and spacer for 3D */ linear_extrude(3d_bottom_height) bottom_plate(); translate([0, 0, 3d_bottom_height]) linear_extrude(3d_spacer_height) spacer(); } module 3d_right_parts() { /* Assemble all 3D parts and take the right half */ intersection() { translate([0, -500, -500]) cube([1000, 1000, 1000]); union() { 3d_plate(); translate([0, 150]) 3d_bottom(); translate([0, 300]) linear_extrude(3d_top_height) top_plate(); } } } module 3d_left_parts() { /* Mirror right half of 3D parts into left half */ mirror([1, 0, 0]) 3d_right_parts(); } if (dimensions == 2) { /* Lay out the 2D case */ top_plate(); translate([300, 0]) switch_plate(); translate([0, 150]) bottom_plate(); translate([300, 150]) { if (quarter_spacer == true) { quartered_spacer(); } else { spacer(); } } } else { /* Lay out the 3D case */ if (3d_split) { translate([3d_split_offset, 0, 0]) 3d_right_parts(); translate([-3d_split_offset, 0, 0]) 3d_left_parts(); } else { 3d_right_parts(); 3d_left_parts(); } }