// Parametric Expansion Card // An OpenSCAD implementation of a basic enclosure of an Expansion Card for // use with Framework products like the Framework Laptop. // // See https://frame.work for more information about Framework products and // additional documentation around Expansion Cards. // Parametric Expansion Card © 2021 by Nirav Patel at Framework Computer LLC // is licensed under Attribution 4.0 International. // To view a copy of this license, visit http://creativecommons.org/licenses/by/4.0/ // The basic dimensions of an Expansion Card base = [30.0, 32.0, 6.8]; // The extension for the SMA connectors & EG95 base_ext = [40, 20.2, 16.5]; // The default wall thickness side_wall = 1.5; // The dimensions of the sim holder bay bay = [side_wall, 9.62, 0.79]; // Size and location of the typical PCB pcb_gap = 0.5; pcb = [26.0, 30.0, 0.8]; pcb_h = 3.05; // USB-C plug dimensions usb_c_r = 1.315; usb_c_w = 5.86+usb_c_r*2; usb_c_h = 2.2; rail_h = 4.25; // to top of rail // Boss locations matching the other Framework Expansion Cards boss_inc_x = 4.2; boss_r = 1.5; boss_inc_y = 18.5+boss_r; boss_ext_y = 8.57; boss_ext_x = 4; boss_ext_r = 2.5; boss_ext_inner = 2.1; boss_ext_top_y = 6; lid_boss_r = 1.05; lid_wall_thickness = 3+side_wall; gap = 0.25; sma_height = 11.28; sma = 10.035; hollow_bottom_z = 0.7; bottom_thickness = hollow_bottom_z+0.2; ext_screw_hole = 2.5+side_wall; ledge_cut = 0.6; ledge_cut_d = 3.2; led_guide = [1, 11.95]; led_guide_stopper = [1.3, 1]; led_h = 0.7; // The rail cutout in the sides of the card module rail(make_printable) { rail_depth = 0.81; rail_flat_h = 0.32; mirror([0, 1, 0]) { // The rail that holds the card bottom_angle = 43.54; difference() { union() { translate([rail_depth, 0, -rail_flat_h]) rotate([0, 180+bottom_angle, 0]) cube([2, base[1]-side_wall, 2]); translate([-2+rail_depth, 0, -rail_flat_h]) cube([2, base[1]-side_wall, rail_flat_h]); } translate([-5+rail_depth, 0, 0]) cube([5, base[1]-side_wall, 5]); } pyramid_b = 3.23*sqrt(2); pyramid_t = 1.75*sqrt(2); pyramid_h = 1.0; pyramid_inset = 1.3+0.5; pyramid_step = 3.06; // The ramps to make slotting into the latch smooth translate([-1.75/2+pyramid_inset, 0, -1.75/2]) rotate([-90, 0, 0]) rotate([0, 0, 45]) { cylinder(r1 = pyramid_b/2, r2 = pyramid_t/2, h = pyramid_h, $fn=4); cylinder(r = pyramid_t/2, h = pyramid_step+pyramid_h, $fn=4); translate([-0.1, 0.1, 0])cylinder(r = pyramid_t/2, h = pyramid_step+pyramid_h, $fn=4); } latch_l = 2.67; latch_d = pyramid_inset; latch_h = 2.85; latch_wall = 1.39; // The pocket that the latch bar drops into, including a 45 degree cut for printability translate([0, latch_wall, -latch_h]) cube([latch_d, latch_l, latch_h]); translate([latch_d, latch_wall+latch_l, -latch_h]) rotate([0, 0, -180+45]) translate([0, -latch_l, 0]) cube([latch_d*2, latch_l, latch_h]); } } // A simple cylinder cutout to fillet edges module fillet(radius, length) { translate([length, 0, 0]) rotate([0, -90, 0]) difference() { cube([radius, radius, length]); translate([radius, radius, -1]) cylinder(h = length+2, r = radius, $fn = 64); } } // The cutout for the USB-C plug module usb_c_cutout(open_top) { // The plug is "pushed in" by an extra 0.6 to account for 3d printing tolerances translate([-usb_c_w/2+usb_c_r, 7-10+0.6, usb_c_r]) rotate([-90, 0, 0]) union() { translate([0, usb_c_r, 0]) cylinder(r = usb_c_r, h = 10, $fn = 64); translate([usb_c_w-usb_c_r*2, usb_c_r, 0]) cylinder(r = usb_c_r, h = 10, $fn = 64); cube([usb_c_w-usb_c_r*2, usb_c_r*2, 10]); // Cutout for the pin side of the shell that expands out translate([0, usb_c_r, 0]) cylinder(r2 = usb_c_r, r1 = 3.84/2, h = 10-7.7, $fn = 64); translate([usb_c_w-usb_c_r*2, usb_c_r, 0]) cylinder(r2 = usb_c_r, r1 = 3.84/2, h = 10-7.7, $fn = 64); translate([usb_c_w/2-usb_c_r, usb_c_r, 0]) scale([1.8, 1, 1]) rotate([0, 0, 45]) cylinder(r2 = usb_c_r*sqrt(2), r1 = 3.84/2*sqrt(2), h = 10-7.7, $fn = 4); // If the card drops in from the top rather than sliding in from the front, // cut out a slot for the USB-C plug to drop into. if (open_top) { translate([-usb_c_r, -10+usb_c_r, 0]) cube([usb_c_w, 10, 10]); } } } // Quarter section of a sphere, used as a clip module qsphere(radius = 1) { difference() { sphere(r = radius, $fn = 32); translate([-radius-1, -radius-1, -radius]) cube([radius*2+2, radius*2+2, radius]); translate([0, -radius-1, -radius-1]) cube([radius, radius*2+2, radius*2+2]); } } // The screw boss for the PCB, optionally with space for a threaded insert module boss(radius, height = pcb_h, inner_size, make_printable) { difference() { cylinder(r = radius, h = height, $fn = 64); translate([0, 0, side_wall]) cylinder(r = inner_size/2, h = pcb_h, $fn = 64); } } module sma_hole() { radius = 3.15; rotate([90,0,0]) cylinder(h = side_wall,r = radius, $fn = 64); } // make expansion card lid module expansion_card_lid() { lower_lid_z = 0.6; ext_side_extension = 5; difference() { union() { // makes lower lid translate([side_wall+gap, 0, base[2]-lower_lid_z]) cube([base[0]-side_wall*2-gap*2, base[1]-side_wall-gap, lower_lid_z]); // makes upper lid difference() { translate([-side_wall*2-gap, -base_ext[1]+side_wall+gap, base_ext[2]-side_wall]) cube([base_ext[0]-side_wall*2-gap*2, base_ext[1]-side_wall*2-gap*2, side_wall]); translate([-0.5,-16.4,8]) led_cylinder(); // hollow for D2 LED cylinder translate([30.60,-16.4,8]) led_cylinder(); // hollow for D3 LED cylinder } // joins them together translate([side_wall+gap, -side_wall, base[2]-lower_lid_z]) cube([base[0]-side_wall*2-gap*2, side_wall, base_ext[2]-base[2]+gap*2+0.1]); translate([side_wall+gap,-gap-side_wall,base_ext[2]-side_wall]) cube([base[0]-side_wall*2-gap*2, side_wall, side_wall]); difference() { translate([base[0]/2-usb_c_w/2+gap, base[1]-side_wall-gap, usb_c_r+usb_c_h]) cube([usb_c_w-gap*2, side_wall+gap, base[2]-(usb_c_r+usb_c_h)]); translate([base[0]/2, base[1], usb_c_r+usb_c_h]) usb_c_cutout(false); } // join on sma cover translate([-side_wall*2-gap,-base_ext[1],base_ext[2]-side_wall]) cube([base_ext[0]-side_wall*2-gap*2, side_wall+gap, side_wall]); // make sma cover half translate([-side_wall*2-gap, -base_ext[1], sma_height]) cube ([base_ext[0]-side_wall*2-gap*2, side_wall, base_ext[2]-sma_height]); // add screw holder to lid difference() { translate([-3.25,-base_ext[2]-side_wall-gap*3,sma_height]) cube([lid_wall_thickness, base_ext[2]+gap*2, base_ext[2]-sma_height-side_wall]); rotate([0,90,0]) translate([-base_ext[2]+3,-6,-3.25]) cylinder(r = lid_boss_r, h = 3, $fn = 64); translate([-0.5,-16.4,8]) led_cylinder(); // hollow for D2 LED cylinder } difference() { translate([base_ext[0]-3.25*2-side_wall*3-gap,-base_ext[2]-side_wall-gap*3,sma_height]) cube([lid_wall_thickness, base_ext[2]+gap*2, base_ext[2]-sma_height-side_wall]); rotate([0,90,0]) translate([-base_ext[2]+3,-6,base_ext[0]-2.5*2-side_wall*3-gap]) cylinder(r = lid_boss_r, h = 3, $fn = 64); translate([30.55,-16.4,8]) led_cylinder(); // hollow for D3 LED cylinder } } // make sma holes translate([sma-side_wall*2, -base_ext[2]-side_wall-side_wall/2+0.05, sma_height]) sma_hole(); translate([base_ext[0]-sma-side_wall*4-gap*4, -base_ext[2]-side_wall-side_wall/2+0.05, sma_height]) sma_hole(); } } // A basic, printable Expansion Card enclosure // open_end - A boolean to make the end of the card that is exposed when inserted open // make_printable - Adds ribs to improve printability // pcb_mount - The method the PCB is held in with, "boss" for self-threading screws, // "boss_insert" for fastener with a threaded insert, // "clip" for a fastener-less clip, or // "" for no PCB mounting structure module expansion_card_base(open_end, make_printable, pcb_mount="boss") { // Hollowing of the inside extra = 0.1; inner = [base[0]-side_wall*2, base[1]-side_wall*2, base[2]-side_wall+extra]; ledge_fillet_r = 0.3; notch_l = 3.0; difference() { cube(base); difference() { notch = 1.0; notch_h = 3.8; // The main hollow translate([side_wall, 0, side_wall]) cube([inner[0], inner[1]+side_wall, inner[2]]); // Extra wall thickness where the latch cutouts are translate([side_wall, inner[1]+side_wall-notch_l, side_wall+notch_h/2]) rotate([0, 0, -90]) rotate([0, 90, 0]); translate([side_wall, inner[1]+side_wall-notch_l, side_wall]) cube([notch, notch_l, notch_h]); translate([inner[0]+side_wall, inner[1]+side_wall-notch_l, side_wall+notch_h/2]) rotate([0, 0, 180]) rotate([0, 90, 0]); translate([inner[0]+side_wall-notch, inner[1]+side_wall-notch_l, side_wall]) cube([notch, notch_l, notch_h]); translate([side_wall, inner[1]+side_wall-notch_l, side_wall+notch_h/2]) rotate([0, 0, -90]) rotate([0, 90, 0]) rib(notch_h, notch); translate([inner[0]+side_wall, inner[1]+side_wall-notch_l, side_wall+notch_h/2]) rotate([0, 0, 180]) rotate([0, 90, 0]) rib(notch_h, notch); } // cutout for sim card access translate([0,1.6,pcb_h-1.4]) cube(bay); // The rounded front edge to match the laptop edge_r = 0.8; // The USB-C plug cutout translate([base[0]/2, base[1], usb_c_r+usb_c_h]) usb_c_cutout(!open_end); // The sliding rails translate([0, base[1], rail_h]) rail(make_printable); translate([base[0], base[1], rail_h]) mirror([1, 0, 0]) rail(make_printable); // Cut out the end of what is normally the aluminum cover translate([0, base[1]-ledge_cut_d, 0]) cube([base[0], ledge_cut_d, ledge_cut]); // hollow bottom to provide room for back of board //translate([side_wall,-notch_l,-hollow_bottom_z+1.5]) cube([base[0]-side_wall*2, base[1]-side_wall, hollow_bottom_z]); } difference() { // add extra bottom thickness to provide room for back of board translate([0,0,-hollow_bottom_z]) cube([base[0], base[1]-ledge_cut_d, bottom_thickness]); // engrave LES logo into bottom translate([base[0]-3, 17, -0.8]) rotate([0,180,0]) linear_extrude(height=bottom_thickness, center=true) { offset(0.01) import("LES.svg"); // the offset fixes a weird error about the svg's mesh being incomplete } // The fillets on the aluminum cover translate([base[0], base[1]-ledge_cut_d, -ledge_cut/2-hollow_bottom_z+0.3]) rotate([0, 0, 180]) fillet(ledge_cut/2, base[0]); } if (pcb_mount == "boss" || pcb_mount == "boss_insert") { // Add the screw bosses translate([boss_inc_x, boss_inc_y, 0]) boss(boss_r, pcb_h, 0, make_printable); // left int screwhole translate([base[0]-boss_inc_x, boss_inc_y, 0]) boss(boss_r, pcb_h, 0, make_printable); // right int screwhole } else if (pcb_mount == "clip") { clip_w = 1.5; clip_gap = 0.5; translate([boss_inc_x-boss_r, boss_inc_y-boss_r, 0]) { cube([boss_r*2, boss_r*2, pcb_h]); if (make_printable) translate([boss_r, boss_r*2, 0]) rib(boss_r*2, pcb_h); translate([0, boss_r-clip_w, pcb_h+pcb[2]+clip_gap]) rotate([0, 0, 180]) qsphere(clip_w); } translate([base[0]-boss_inc_x-boss_r, boss_inc_y-boss_r, 0]) { cube([boss_r*2, boss_r*2, pcb_h]); if (make_printable) translate([boss_r, boss_r*2, 0]) rib(boss_r*2, pcb_h); translate([boss_r*2, boss_r-clip_w, pcb_h+pcb[2]+clip_gap]) qsphere(clip_w); } } } // A simple 45 degree rib to improve printability module rib(thickness, height) { translate([-thickness/2, 0, 0]) difference() { cube([thickness, height, height]); translate([-thickness/2, height, 0]) rotate([45, 0, 0]) cube([thickness*2, height*2, height*2]); } } module expansion_card_ext(make_printable, pcb_mount="boss_insert") { // Hollowing of the inside extra = 0.1; inner = [base_ext[0]-side_wall*2, base_ext[1]-side_wall*2, base_ext[2]-side_wall+extra]; cutout = [base[0]-side_wall*2, base_ext[1]-side_wall*2, base_ext[2]-side_wall+extra]; sma_cutout = [base_ext[0]-side_wall*2, base_ext[1]-side_wall*2, base_ext[2]]; translate([boss_ext_x, boss_ext_y, 0]) boss(boss_ext_r, pcb_h, boss_ext_inner, make_printable); // left ext screwhole translate([base_ext[0]-boss_ext_x, boss_ext_y, 0]) boss(boss_ext_r, pcb_h, boss_ext_inner, make_printable); // right ext screwhole difference() { cube(base_ext); // cutout to bring base and ext card together // not sure why it needs 0.5mm added, rounding error maybe? <---- FIX THIS translate([base[0]-23.5, 5, side_wall]) cube([cutout[0], cutout[1], cutout[2]]); // half cut out for sma connectors translate([side_wall, 0-1, sma_height]) cube([sma_cutout[0], sma_cutout[1], sma_cutout[2]]); // sma holes translate([sma+side_wall+gap*2, side_wall, sma_height]) sma_hole(); translate([base_ext[0]-sma-side_wall-gap*2, side_wall, sma_height]) sma_hole(); // screw holes translate([0,base_ext[1]-boss_ext_top_y,base_ext[2]-boss_r-1.5]) rotate([0,90,0]) cylinder(r = 1, h = side_wall, $fn = 64); translate([base_ext[0]-side_wall,base_ext[1]-boss_ext_top_y,base_ext[2]-boss_r-1.5]) rotate([0,90,0]) cylinder(r = 1, h = side_wall, $fn = 64); difference() { // The main hollow translate([side_wall, side_wall, side_wall]) cube([inner[0], inner[1], inner[2]]); } // hollow bottom to provide room for back of board translate([(base_ext[0]-base[0])/2+side_wall,base_ext[1]-base_ext[1]/2+gap*2,-hollow_bottom_z+1.5]) cube([base[0]-side_wall*2, base_ext[1]/2, hollow_bottom_z]); } difference() { // add extra bottom thickness to provide room for back of board translate([(base_ext[0]-base[0])/2,base_ext[1]-base_ext[1]/2+side_wall-1,-hollow_bottom_z]) cube([base[0], (base_ext[1]/2)-side_wall+1, hollow_bottom_z]); translate([5, base_ext[1]-base_ext[1]/2+side_wall-0.001-1, -ledge_cut/2-hollow_bottom_z+0.3]) fillet(ledge_cut/2, base[0]); } } module led_cylinder(stopper) { if (stopper) { // if a stopper should be inserted cylinder(h = led_guide[1], r = led_guide[0], $fn = 64); translate([0,0,led_guide[1]-led_guide_stopper[1]-(base_ext[2]-sma_height)+0.03]) cylinder(h = led_guide_stopper[1], r = led_guide_stopper[0], $fn = 64); } else { cylinder(h = led_guide[1], r = led_guide[0], $fn = 64); } } translate([base_ext[0]-6.4-side_wall*2, -base[1]-base_ext[1]+side_wall*2+0.8, pcb_h+0.78+led_h]) led_cylinder(true); // D2 LED column translate([-0.5, -base[1]-base_ext[1]+side_wall*2+0.8, pcb_h+0.78+led_h]) led_cylinder(true); // D3 LED column translate([0, -base[1], 0]) expansion_card_base(open_end = false, make_printable = true, pcb_mount="boss"); translate([-base_ext[0]+base[0]+5, -base_ext[1]-base[1], 0]) expansion_card_ext(make_printable = true, pcb_mount="boss"); translate([0, -32, 0]) expansion_card_lid(); translate([-128.75, 95.6, pcb_h]) import("PCB.stl");