2023-04-20 17:41:18 +02:00
// 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 mini PCIe slot
base_ext = [ 56.4 , 33 , 13 ] ;
// The default wall thickness
side_wall = 1.5 ;
// 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_y = 10.5 ;
boss_x = 4.2 ;
boss_r = 2.95 ;
// 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 ] ) ;
if ( make_printable ) {
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 , use_insert , make_printable ) {
difference ( ) {
cylinder ( r = radius , h = pcb_h , $fn = 64 ) ;
translate ( [ 0 , 0 , side_wall ] ) cylinder ( r = 1.25 , h = pcb_h , $fn = 64 ) ;
}
}
// Incomplete implementation of a lid to use with this shell
module expansion_card_lid ( ) {
gap = 0.25 ;
difference ( ) {
union ( ) {
// makes lower lid
translate ( [ side_wall + gap , side_wall + gap , base [ 2 ] - side_wall ] ) cube ( [ base [ 0 ] - side_wall * 2 - gap * 2 , base [ 1 ] - side_wall * 2 - gap * 2 , side_wall ] ) ;
// makes upper lid
translate ( [ base [ 0 ] - base_ext [ 0 ] + side_wall + gap , - base_ext [ 1 ] + 2 * ( 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 ] ) ;
// joins them together
translate ( [ side_wall + gap , 0 , base_ext [ 2 ] - ( base_ext [ 2 ] - base [ 2 ] ) - side_wall ] ) cube ( [ base [ 0 ] - side_wall * 2 - gap * 2 , side_wall + gap , base_ext [ 2 ] - base [ 2 ] + 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 ) ;
}
2023-04-24 12:45:15 +02:00
// Adds screw holder to lid
screw_holders ( ) ;
2023-04-20 17:41:18 +02:00
}
// engrave LES logo into lid
translate ( [ 3 , 20 , 7 ] ) linear_extrude ( height = 1.5 , center = true ) {
offset ( 0.01 ) import ( "LES.svg" ) ; // the offset fixes a weird error about the svg's mesh being incomplete
}
}
}
// 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_insert" ) {
// Hollowing of the inside
extra = 0.1 ;
inner = [ base [ 0 ] - side_wall * 2 , base [ 1 ] - side_wall * 2 , base [ 2 ] - side_wall + extra ] ;
difference ( ) {
cube ( base ) ;
difference ( ) {
notch = 1.0 ;
notch_l = 3.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 ] ) ;
}
// 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
ledge_cut = 0.6 ;
ledge_cut_d = 3.2 ;
ledge_fillet_r = 0.3 ;
translate ( [ 0 , base [ 1 ] - ledge_cut_d , 0 ] ) cube ( [ base [ 0 ] , ledge_cut_d , ledge_cut ] ) ;
// The fillet on that cover
translate ( [ base [ 0 ] , base [ 1 ] - ledge_cut_d , 0 ] ) rotate ( [ 0 , 0 , 180 ] ) fillet ( ledge_cut / 2 , base [ 0 ] ) ;
}
if ( pcb_mount = = "boss" || pcb_mount = = "boss_insert" ) {
// Add the screw bosses
translate ( [ boss_x , boss_y , 0 ] ) boss ( boss_r , pcb_mount = = "boss_insert" , make_printable ) ; // left int screwhole
translate ( [ base [ 0 ] - boss_x , boss_y , 0 ] ) boss ( boss_r , pcb_mount = = "boss_insert" , make_printable ) ; // right int screwhole
translate ( [ base [ 0 ] - boss_x , boss_y - base_ext [ 1 ] - boss_y + 2 + 2.2 , 0 ] ) boss ( boss_r , pcb_mount = = "boss_insert" , make_printable ) ; // right bottom ext screwhole
translate ( [ base [ 0 ] - base_ext [ 0 ] + boss_x , boss_y - base_ext [ 1 ] - boss_y + 2 + 2.2 , 0 ] ) boss ( boss_r , pcb_mount = = "boss_insert" , make_printable ) ; // left bottom ext screwhole
translate ( [ base [ 0 ] - base_ext [ 0 ] + boss_x , base_ext [ 1 ] + boss_y - base_ext [ 1 ] - boss_y - 2 - 2.2 , 0 ] ) boss ( boss_r , pcb_mount = = "boss_insert" , make_printable ) ; // left top ext screwhole
translate ( [ base [ 0 ] - boss_x , base_ext [ 1 ] + boss_y - base_ext [ 1 ] - boss_y - 2 - 2.2 , 0 ] ) boss ( boss_r , pcb_mount = = "boss_insert" , make_printable ) ; // right top ext screwhole
} else if ( pcb_mount = = "clip" ) {
clip_w = 1.5 ;
clip_gap = 0.5 ;
translate ( [ boss_x - boss_r , boss_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_x - boss_r , boss_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 ) ;
}
}
}
module expansion_card_ext ( ) {
// 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 ] ;
boss_r = 2.3 ;
difference ( ) {
cube ( base_ext ) ;
// cutout to bring base and ext card together
// not sure why it needs 0.5mm added, rounding error maybe?
translate ( [ base [ 0 ] - 2.1 , 5 , side_wall ] ) cube ( [ cutout [ 0 ] , cutout [ 1 ] , cutout [ 2 ] ] ) ;
difference ( ) {
notch = 1.0 ;
notch_l = 3.0 ;
notch_h = 3.8 ;
// The main hollow
translate ( [ side_wall , side_wall , side_wall ] ) cube ( [ inner [ 0 ] , inner [ 1 ] , 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 ] ) rib ( notch_h , notch ) ;
translate ( [ inner [ 0 ] + side_wall - notch , inner [ 1 ] + side_wall - notch_l , side_wall ] ) cube ( [ notch , notch_l , notch_h ] ) ; * /
}
}
}
2023-04-24 12:45:15 +02:00
module screw_holders ( ) {
outline = [ 1 , 5 , 5 ] ;
difference ( ) {
cube ( [ outline [ 0 ] , outline [ 1 ] , outline [ 2 ] ] ) ;
radius = 2 ;
height = 10 ;
rotate ( [ 0 , 90 , 0 ] ) translate ( [ ( - outline [ 0 ] * 2 ) - 0.5 , outline [ 1 ] / 2 , ( outline [ 2 ] / 2 ) - 5 ] ) cylinder ( h = height , r = radius ) ;
}
}
2023-04-20 17:41:18 +02:00
translate ( [ 0 , - base [ 1 ] , 0 ] ) expansion_card_base ( open_end = false , make_printable = true , pcb_mount = "boss" ) ;
translate ( [ - base_ext [ 0 ] + base [ 0 ] , - base_ext [ 1 ] - base [ 1 ] , 0 ] ) expansion_card_ext ( ) ;
2023-04-24 12:45:15 +02:00
translate ( [ - 60 , - 35 , 0 ] ) expansion_card_lid ( ) ;