Source files / shroud · raw
p100_front_duct.scad
// Dual Tesla P100 front intake duct for one 120 mm fan (Arctic S12038-4K) // Board: ASUS Z10PA-U8, cards in PCIE6 + PCIE4, in a tower case. // // Stack along the cards' axis, from the case front: // // case wall | wall plate | 120x38 fan | duct ............ | card tails // 0 plate_t + fan_t + duct_len = wall_to_tail // // - The wall plate screws onto the case's existing 92 mm fan holes (from // outside), and holds four M4 nuts in traps on its wall side. // - M4 x 50 bolts go from inside the duct flange through the fan into those // nuts, so the fan + duct can be mounted after the plate is on the wall. // - The duct ends at the card tails (seal the last mm with foam tape). Its // top wall stays below the power plugs, so the cables leave upwards. // // Print the duct card end DOWN (flange on top). The fingers and the gap // blocker then grow from the bed, the taper stays steeper than 45°, and the // flange corners have 45° chamfers. The plate prints flat, either side down. // // Coordinates: z = airflow axis, z = 0 is the duct flange face (fan outlet side). // x = across the cards (stacking direction), y = card height, // +y = the cards' top edge (away from the board, where the plugs are). /* [Case] */ wall_to_tail = 178; // inner case wall to the card tail faces (measured ~180) x_off = 0; // card stack centre relative to the fan axis, across the cards y_off = 0; // ... along the card height (+ = towards the cards' top edge) case_hole_sp = 82.5; // the case's 92 mm fan holes, centre to centre case_dx = 0; // centre of those holes relative to the 120 mm fan axis case_dy = 0; case_pilot_d = 3.4; // pilot hole for the screws from outside (self-tapping into the plate) /* [Cards] */ card_h = 111.2; // tail face: PCB edge to top edge card_t = 37.0; // thickness of one card's cooler slot_pitch = 40.64; // centre-to-centre of the two cards n_cards = 2; clearance = 0.6; // play per side at the fingers plug_zone = 24; // MEASURE: card top edge to the lowest point of the power plug (incl. latch) /* [Duct] */ wall = 2.0; flange_t = 4; chamfer_h = 25; // 45° support under the flange corners finger_len = 12; // how far the side fingers and the gap blocker reach along the cards bottom_finger = true; // also a finger under the cards' bottom (PCB) edge; avoids a bridge when printing loft_steps = 32; r_end = 3; // corner radius at the card end /* [Fan] */ fan_size = 120; fan_t = 38; fan_hole_sp = 106; // measured on the actual fan fan_hole_d = 4.6; fan_open_d = 116; head_d = 8.5; // counterbore for the M4 bolt heads (cap or button head) in the chamfer /* [Wall plate] */ plate_t = 8; nut_af = 7.0; // M4 nut across flats nut_clear = 0.3; nut_h = 3.4; // trap depth on the wall side boss_r = 5.5; // material around the case screw holes inside the opening /* [Gap blocker] */ gap_block = true; gap_block_clear = 0.3; nose_len = 25; nose_edge = 0.6; /* [Output] */ part = "both"; // "duct", "plate", "both" (side by side), "assembly" (as installed) cutaway = false; $fn = 96; e = 0.01; // ---- derived ---- duct_len = wall_to_tail - plate_t - fan_t; // flange face to card tails stack_x = slot_pitch * (n_cards - 1) + card_t; in_x = stack_x + 2 * clearance; // mouth: from the cards' bottom edge up to just below the plugs m_bot = -card_h/2 - clearance; m_top = card_h/2 - plug_zone - wall - 0.5; in_y = m_top - m_bot; m_cx = x_off; m_cy = y_off + (m_top + m_bot) / 2; z_t0 = flange_t; z_t1 = duct_len; gap = slot_pitch - card_t; bt = gap - 2 * gap_block_clear; function lerp(a, b, s) = a + (b - a) * s; function ease(t) = t * t * (3 - 2 * t); 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); translate([lerp(0, m_cx, s), lerp(0, m_cy, 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 duct_outer() { // flange with 45° chamfered corners underneath hull() { translate([-fan_size/2, -fan_size/2, 0]) cube([fan_size, fan_size, flange_t]); translate([0, 0, flange_t + chamfer_h]) cylinder(d = fan_open_d + 2*wall, h = e); } loft(wall); } module duct_air() { translate([0, 0, -e]) cylinder(d = fan_open_d, h = flange_t + 2*e); loft(0); // straight continuation past the tails, so the fingers stay open inside translate([m_cx - in_x/2, m_cy - in_y/2, z_t1 - e]) cube([in_x, in_y, finger_len + 1]); } module fingers() { for (sx = [-1, 1]) // side walls along the outer faces of the stack translate([m_cx + sx * (in_x/2 + wall/2) - wall/2, m_cy - in_y/2 - wall, z_t1 - e]) cube([wall, in_y + wall, finger_len + e]); if (bottom_finger) // under the cards' bottom edge translate([m_cx - in_x/2 - wall, m_cy - in_y/2 - wall, z_t1 - e]) cube([in_x + 2*wall, wall, finger_len + e]); } function gap_x(i) = x_off - stack_x/2 + i * slot_pitch - gap/2; module gap_blockers() { if (bt > 0.8) for (i = [1 : n_cards - 1]) { xc = gap_x(i); translate([xc - bt/2, m_cy - in_y/2 - e, z_t1 - e]) cube([bt, in_y + 2*e, finger_len + e]); intersection() { loft(0); hull() { translate([xc - bt/2, -fan_size, z_t1 - e]) cube([bt, 2*fan_size, e]); translate([xc - nose_edge/2, -fan_size, z_t1 - nose_len]) cube([nose_edge, 2*fan_size, e]); } } } } module fan_bolt_holes() { for (sx = [-1, 1], sy = [-1, 1]) translate([sx * fan_hole_sp/2, sy * fan_hole_sp/2, 0]) { translate([0, 0, -e]) cylinder(d = fan_hole_d, h = flange_t + 2*e); translate([0, 0, flange_t]) cylinder(d = head_d, h = chamfer_h + 1); // head access } } module duct() { difference() { duct_outer(); duct_air(); fan_bolt_holes(); } fingers(); if (gap_block && n_cards > 1) gap_blockers(); } // plate: z = 0 is the wall side module plate() { difference() { translate([-fan_size/2, -fan_size/2, 0]) cube([fan_size, fan_size, plate_t]); // opening, minus small bosses around the case screws difference() { translate([0, 0, -e]) cylinder(d = fan_open_d, h = plate_t + 2*e); for (sx = [-1, 1], sy = [-1, 1]) translate([case_dx + sx * case_hole_sp/2, case_dy + sy * case_hole_sp/2, -1]) cylinder(r = boss_r, h = plate_t + 2); } for (sx = [-1, 1], sy = [-1, 1]) { // case screws, from outside translate([case_dx + sx * case_hole_sp/2, case_dy + sy * case_hole_sp/2, -e]) cylinder(d = case_pilot_d, h = plate_t + 2*e); // fan bolts + nut traps on the wall side translate([sx * fan_hole_sp/2, sy * fan_hole_sp/2, -e]) { cylinder(d = fan_hole_d, h = plate_t + 2*e); cylinder(d = (nut_af + 2*nut_clear) / cos(30), h = nut_h + e, $fn = 6); } } } } module cut() { translate([0, -150, -50]) cube([300, 300, 400]); } if (part == "assembly") { color("silver") translate([0, 0, -plate_t - fan_t]) plate(); color("dimgray", 0.5) translate([-fan_size/2, -fan_size/2, -fan_t]) cube([fan_size, fan_size, fan_t]); color("orange") duct(); color("green", 0.4) for (i = [0 : n_cards - 1]) // card tails translate([x_off - stack_x/2 + i * slot_pitch, y_off - card_h/2, duct_len]) cube([card_t, card_h, 60]); } else { if (part == "duct" || part == "both") if (cutaway) difference() { duct(); cut(); } else duct(); if (part == "plate" || part == "both") translate([part == "both" ? fan_size + 15 : 0, 0, 0]) plate(); } echo(str("duct length (flange face to tails): ", duct_len, " mm; mouth ", in_x, " x ", in_y, " mm; centre offset ", m_cx, " / ", m_cy, " mm")); echo(str("bolts: M4 x 50, grip to the nut ", flange_t + fan_t + plate_t - nut_h, " mm"));