Source files / shroud · raw
p100_shroud.scad
// Dual Tesla P100 intake shroud for one 120 mm fan (e.g. Arctic S12038-4K) // Board: ASUS Z10PA-U8, cards in PCIE6 + PCIE4 (2 slots apart). // // The shroud slides over the tail ends of both cards (the end opposite // the slot bracket). The fan blows into the fins, and the air leaves // through the bracket vents. // // Print with the sleeve opening on the bed (fan flange on top): then the // gap blocker and the tabs grow from the bed, and no supports are needed. // // Coordinates: z = airflow axis, z = 0 is the fan face. // x = stacking direction (across the cards), y = card height. // // MEASURE YOUR CARDS AND OVERWRITE THE DEFAULTS BELOW. // The defaults are nominal values, not measured. /* [Cards: measure these] */ card_h = 111.2; // tail face: lowest to highest point of the card (PCB edge to top) card_t = 37.0; // tail face: thickness of one card's cooler shroud slot_pitch = 40.64; // centre-to-centre spacing of the two cards (2 slots x 20.32) n_cards = 2; clearance = 0.6; // gap per side in the sleeve (add 1-2 mm foam tape to seal) /* [Shroud] */ wall = 2.0; // wall thickness (PETG/ASA recommended, not PLA) trans_len = 60; // length of the taper from the fan to the sleeve sleeve_len = 18; // how far the shroud slides over the card tails x_off = 0; // shift the card opening relative to the fan centre y_off = 0; /* [Streamlining] */ // The taper morphs smoothly from the round fan opening to a rectangle with // slightly rounded corners. An S-curve (smoothstep) along the length means // it starts and ends tangentially: no sharp kinks where flow could separate. loft_steps = 24; // more = smoother r_end = 3; // corner radius of the taper at the sleeve smooth = true; // false = straight (linear) taper /* [Card stops] */ // Four small corner stops instead of a full lip: the card ends rest on them, // while the fin face stays almost completely open. 45° bevel on the fan side. stops = true; stop_size = 6; // edge length of a stop stop_t = 1.5; // thickness of the stop face /* [Fan] */ fan_size = 120; fan_hole_sp = 106; // measured on the actual fan, centre to centre (nominal 120 mm spec: 105) fan_hole_d = 4.6; // M4 clearance (fan holes are 4.5 mm; printed holes shrink ~0.1-0.2 mm) // M4 x 45 through-bolts + nuts: 4 mm flange + 38 mm fan fan_open_d = 116; // round inlet opening (slightly smaller than the fan) flange_t = 4; /* [Gap blocker] */ // Closes the air gap between the two coolers. Without it a lot of air // takes this low-resistance path instead of going through the fins. // The tapered nose splits the flow towards the two fin faces smoothly. gap_block = true; gap_block_clear = 0.3; // play per side in the gap nose_len = 20; // length of the tapered nose (0 = flat face) nose_edge = 0.6; // thickness of the leading edge (printable minimum) /* [Splitter] */ // Full dividing wall in the taper. Usually not needed: the fan hub sits // in front of the gap anyway, so the airflow splits symmetrically. // Only enable it if the two cards run more than ~5 °C apart. splitter = false; splitter_t = 1.6; splitter_start = 30; // distance from the fan face; further away = quieter /* [Mounting] */ ears = true; // two tabs on the underside for zip ties / a support strut ear_hole_d = 4.5; $fn = 96; e = 0.01; // ---- derived dimensions ---- stack_x = slot_pitch * (n_cards - 1) + card_t; // width of the stacked tail face in_x = stack_x + 2 * clearance; in_y = card_h + 2 * clearance; out_x = in_x + 2 * wall; out_y = in_y + 2 * wall; z_t0 = flange_t; // taper start z_t1 = flange_t + trans_len; // taper end = sleeve start z_end = z_t1 + sleeve_len; gap = slot_pitch - card_t; // air gap between the two coolers bt = gap - 2 * gap_block_clear; // gap blocker thickness // ---- smooth loft ---- function lerp(a, b, s) = a + (b - a) * s; function ease(t) = smooth ? t * t * (3 - 2 * t) : t; // smoothstep // cross section at loft fraction s: rounded rectangle, s = 0 is a circle module section(s, grow = 0) { w = lerp(fan_open_d, in_x, s); h = lerp(fan_open_d, in_y, s); r = lerp(fan_open_d / 2, r_end, s); // hull of four corner circles: stays valid when it degenerates to a circle translate([lerp(0, x_off, s), lerp(0, y_off, s)]) hull() for (sx = [-1, 1], sy = [-1, 1]) translate([sx * max(w/2 - r, 0), sy * max(h/2 - r, 0)]) circle(r = r + grow); } module slice(t, grow = 0) { translate([0, 0, lerp(z_t0, z_t1, t) - e/2]) linear_extrude(e) section(ease(t), grow); } module loft(grow = 0) { for (i = [0 : loft_steps - 1]) hull() { slice(i / loft_steps, grow); slice((i + 1) / loft_steps, grow); } } module outer_body() { translate([-fan_size/2, -fan_size/2, 0]) cube([fan_size, fan_size, flange_t]); // flange loft(wall); // taper translate([x_off - out_x/2, y_off - out_y/2, z_t1 - e]) cube([out_x, out_y, sleeve_len + e]); // sleeve } module inner_air() { translate([0, 0, -e]) cylinder(d = fan_open_d, h = flange_t + 2*e); // fan inlet loft(0); // taper translate([x_off - in_x/2, y_off - in_y/2, z_t1 - e]) cube([in_x, in_y, sleeve_len + 2*e]); // sleeve } module fan_holes() { for (sx = [-1, 1], sy = [-1, 1]) translate([sx * fan_hole_sp/2, sy * fan_hole_sp/2, -e]) cylinder(d = fan_hole_d, h = flange_t + 2*e); } // x centre of the gap between card i and card i+1 function gap_x(i) = x_off - stack_x/2 + i * slot_pitch - gap/2; module splitters() { for (i = [1 : n_cards - 1]) intersection() { inner_air(); translate([gap_x(i) - splitter_t/2, -fan_size, splitter_start]) cube([splitter_t, 2*fan_size, z_t1 - splitter_start]); } } // bar that fills the gap between the coolers, with a tapered nose towards the fan module gap_blockers() { if (bt > 0.8) for (i = [1 : n_cards - 1]) { xc = gap_x(i); // bar inside the sleeve translate([xc - bt/2, y_off - in_y/2 - 0.5, z_t1]) cube([bt, in_y + 1, sleeve_len]); // nose: wedge from bar thickness to a thin leading edge if (nose_len > 0) intersection() { inner_air(); hull() { translate([xc - bt/2, -fan_size, z_t1]) cube([bt, 2*fan_size, e]); translate([xc - nose_edge/2, -fan_size, z_t1 - nose_len]) cube([nose_edge, 2*fan_size, e]); } } } } // corner stops: flat face towards the cards, 45° bevel towards the fan module corner_stops() { intersection() { outer_body(); for (sx = [-1, 1], sy = [-1, 1]) { cx = x_off + sx * in_x/2; cy = y_off + sy * in_y/2; hull() { // flat block in the corner (cards rest on its lower face) translate([cx - (sx > 0 ? stop_size : 0.5), cy - (sy > 0 ? stop_size : 0.5), z_t1]) cube([stop_size + 0.5, stop_size + 0.5, stop_t]); // tip of the bevel, back in the corner, towards the fan translate([cx + sx * 0.5 - (sx > 0 ? e : 0), cy + sy * 0.5 - (sy > 0 ? e : 0), z_t1 - stop_size]) cube([e, e, e]); } } } } module ears_mod() { for (sx = [-1, 1]) difference() { translate([x_off + sx * (out_x/2 - 6) - 6, y_off - out_y/2 - 10, z_end - 12]) cube([12, 10 + e, 12]); translate([x_off + sx * (out_x/2 - 6), y_off - out_y/2 - 5, z_end - 13]) cylinder(d = ear_hole_d, h = 14); } } module shroud() { difference() { union() { outer_body(); if (ears) ears_mod(); } inner_air(); fan_holes(); } if (stops) corner_stops(); if (splitter && n_cards > 1) splitters(); if (gap_block && n_cards > 1) gap_blockers(); } /* [Preview] */ cutaway = false; // true = show a cross-section (for checking, not for printing) if (cutaway) difference() { shroud(); translate([-150, 0, -10]) cube([300, 150, 200]); } else shroud(); // Print info in the console echo(str("Stack tail face (nominal): ", stack_x, " x ", card_h, " mm; cooler gap: ", gap, " mm; blocker: ", bt, " mm")); echo(str("Total length: ", z_end, " mm (+ fan ", 38, " mm)"));