Add sealed enclosures and sensor housings for outdoor IP54 protection: - ip54_enclosure.scad: main electronics box (Jetson/FC/ESC), O-ring lid, fan+filter duct, PG7/PG9 cable glands, quarter-turn latches, heat sink recesses; gasket DXF export - ip54_sensor_housings.scad: IMX219 clear PC dome (O-ring + anti-fog pocket), D435i IR-transparent window housing (PG7 rear cap), RPLIDAR static clear PC dome base ring (120 mm OD, O-ring, quarter-turn clips) - ip54_BOM.md: hardware list, thermal analysis (≤52°C at 40°C ambient), IP54 compliance checklist, mass ~930g total kit Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
540 lines
24 KiB
OpenSCAD
540 lines
24 KiB
OpenSCAD
// ============================================================
|
||
// ip54_enclosure.scad — IP54 Main Electronics Enclosure
|
||
// Issue: #144 Agent: sl-mechanical Date: 2026-03-01
|
||
// ============================================================
|
||
//
|
||
// Sealed electronics bay for Jetson Orin NX + FC + ESC stack.
|
||
// IP54 rating: dust-protected, splash-proof from all directions.
|
||
//
|
||
// Protection method:
|
||
// • 4 mm PETG walls (5 perims, 40 % infill)
|
||
// • 2 mm silicone O-ring (Ø2 mm cord) in lid groove → IP54 seal
|
||
// • PG7 cable glands (Ø3–6 mm cables) on rear wall × 4
|
||
// • PG9 cable glands (Ø4–8 mm cables) on rear wall × 2
|
||
// • 40 mm axial fan + foam filter panel on lid (positive pressure)
|
||
// • Thermal: 2× Al heat sink pads on lid underside over Jetson/ESC
|
||
//
|
||
// Internal envelope: 220 × 160 × 90 mm (W × D × H, internal)
|
||
// Fits: Jetson Orin NX (58 × 49 mm), FC 30.5 × 30.5 mm,
|
||
// dual ESC (~80 × 40 mm each)
|
||
//
|
||
// Quick-release lid: 4× spring-loaded quarter-turn latches.
|
||
// Tool-free. Lid lifts straight up after 90° rotation of each latch.
|
||
//
|
||
// Coordinates: Z = 0 at box floor (internal), Z+ upward.
|
||
// Box centred on X=0, Y=0.
|
||
//
|
||
// RENDER options:
|
||
// "assembly" full box + lid + fans + glands preview
|
||
// "body_stl" box body (print 1×)
|
||
// "lid_stl" lid with fan mount (print 1×)
|
||
// "fan_duct_stl" filtered fan inlet duct (print 1×)
|
||
// "latch_stl" quarter-turn latch knob (print 4×)
|
||
// "gasket_2d" DXF — lid O-ring groove outline + cable gland panel
|
||
//
|
||
// Export:
|
||
// openscad ip54_enclosure.scad -D 'RENDER="body_stl"' -o ip54_body.stl
|
||
// openscad ip54_enclosure.scad -D 'RENDER="lid_stl"' -o ip54_lid.stl
|
||
// openscad ip54_enclosure.scad -D 'RENDER="fan_duct_stl"' -o ip54_fan_duct.stl
|
||
// openscad ip54_enclosure.scad -D 'RENDER="latch_stl"' -o ip54_latch.stl
|
||
// openscad ip54_enclosure.scad -D 'RENDER="gasket_2d"' -o ip54_gasket.dxf
|
||
// ============================================================
|
||
|
||
$fn = 64;
|
||
e = 0.01;
|
||
|
||
// ── Internal cavity ───────────────────────────────────────────────────────────
|
||
INT_W = 220.0; // internal width (X)
|
||
INT_D = 160.0; // internal depth (Y)
|
||
INT_H = 90.0; // internal height (Z)
|
||
|
||
// ── Wall / structural ─────────────────────────────────────────────────────────
|
||
WALL = 4.0; // box wall thickness
|
||
LID_T = 5.0; // lid thickness
|
||
BOX_R = 8.0; // outer corner radius (XY)
|
||
|
||
// Derived outer dims
|
||
OUT_W = INT_W + 2*WALL;
|
||
OUT_D = INT_D + 2*WALL;
|
||
OUT_H = INT_H + WALL; // wall on floor + sides; lid closes top
|
||
|
||
// ── O-ring seal ───────────────────────────────────────────────────────────────
|
||
// 2 mm cord silicone O-ring in a groove on the lid flange inner face.
|
||
// Groove: 2.2 mm wide × 1.7 mm deep (standard 70 % compression for IP54).
|
||
ORING_D = 2.0;
|
||
ORING_GROOVE_W = 2.2;
|
||
ORING_GROOVE_D = 1.7;
|
||
ORING_INSET = 6.0; // groove CL from inner box wall edge
|
||
|
||
// ── Lid flange (overlap joint) ────────────────────────────────────────────────
|
||
// Lid has a stepped rim that overlaps the box top edge.
|
||
// Seal groove is cut into the underside of this rim.
|
||
FLANGE_T = 3.0; // vertical flange depth (how far rim drops into box)
|
||
FLANGE_WALL = 3.0; // rim wall thickness
|
||
|
||
// ── Quarter-turn latches ──────────────────────────────────────────────────────
|
||
// 4× positions: one per side (front/rear/left/right centre).
|
||
// Spring-loaded bayonet latch post on box side; rotating knob on lid flange.
|
||
LATCH_POST_D = 10.0; // latch post OD
|
||
LATCH_BOSS_H = 8.0; // boss height above box top flange
|
||
LATCH_KNOB_D = 18.0; // knob OD
|
||
LATCH_SLOT_W = 2.5; // bayonet slot width
|
||
|
||
// ── Cable glands ──────────────────────────────────────────────────────────────
|
||
// All glands on rear wall (Y = -OUT_D/2).
|
||
// PG7 thread OD = 12.5 mm; PG9 thread OD = 15.2 mm.
|
||
PG7_BORE = 12.7; // drill diameter for PG7 panel hole
|
||
PG9_BORE = 15.4; // drill diameter for PG9 panel hole
|
||
PG7_COUNT = 4;
|
||
PG9_COUNT = 2;
|
||
|
||
// Gland layout on rear wall (Y = -OUT_D/2 face), evenly spaced in X.
|
||
// Z centre = 30 mm from floor (lower half of box, cable routing stays low).
|
||
GLAND_Z = 30.0;
|
||
|
||
// ── Fan ───────────────────────────────────────────────────────────────────────
|
||
// 40 mm axial fan in lid, front-left quadrant.
|
||
// Positive pressure: fan blows IN, filtered foam panel on top.
|
||
// Exit: passive vent slots on rear lid (over gland panel), IP54 labyrinth.
|
||
FAN_SZ = 40.0; // 40 mm fan
|
||
FAN_BORE_D = 36.5; // airflow bore
|
||
FAN_BOLT_SPC= 32.0; // M3 bolt square (32 × 32 mm)
|
||
FAN_BOLT_D = 3.3;
|
||
FAN_POS_X = -INT_W/2 + FAN_SZ/2 + 10; // fan X offset from centre (front-left)
|
||
FAN_POS_Y = -INT_D/2 + FAN_SZ/2 + 10; // fan Y offset
|
||
|
||
// ── Fan filter duct ───────────────────────────────────────────────────────────
|
||
DUCT_H = 20.0; // filter duct height above lid
|
||
FOAM_T = 5.0; // foam filter thickness
|
||
|
||
// ── Exhaust labyrinth slots ───────────────────────────────────────────────────
|
||
// Baffle-protected exhaust on lid rear. 2-row labyrinth prevents direct
|
||
// water ingress while maintaining IP54 (deflects splash from all angles).
|
||
EXH_SLOT_W = 3.0; // slot width
|
||
EXH_SLOT_L = 30.0; // slot length
|
||
EXH_ROWS = 2; // number of baffle rows
|
||
EXH_BAFFLE_H= 8.0; // baffle height above lid
|
||
EXH_N_SLOTS = 4; // number of exhaust slots per row
|
||
|
||
// ── Internal standoffs ────────────────────────────────────────────────────────
|
||
// Jetson Orin NX: 58 × 49 mm M3 hole pattern (4 holes), Z = WALL from floor
|
||
ORIN_HOLE_X = 58.0 / 2;
|
||
ORIN_HOLE_Y = 49.0 / 2;
|
||
ORIN_STOFF_H= 8.0; // standoff height (PCB float)
|
||
ORIN_POS_X = +INT_W/4; // offset from box centre: right half
|
||
ORIN_POS_Y = 0;
|
||
|
||
// FC: 30.5 × 30.5 mm M3 pattern
|
||
FC_HOLE_SPC = 30.5 / 2;
|
||
FC_STOFF_H = 6.0;
|
||
FC_POS_X = -INT_W/4; // left half
|
||
FC_POS_Y = -INT_D/4;
|
||
|
||
// ESC pair: 2× ESC ~80 × 40 mm; 4× M3 holes at corners
|
||
ESC_W = 80.0;
|
||
ESC_D = 40.0;
|
||
ESC_STOFF_H = 6.0;
|
||
ESC_POS_X = -INT_W/4;
|
||
ESC_POS_Y = +INT_D/4;
|
||
|
||
// ── Heat sink pads ────────────────────────────────────────────────────────────
|
||
// Recesses in lid underside that accept adhesive Al heat sink pads.
|
||
// Thermal path: board → heat sink → lid → ambient convection + fan.
|
||
// Pad size: 60 × 40 × 2 mm for Jetson, 50 × 30 × 2 mm for ESC.
|
||
HSINK_JETSON_W = 60.0;
|
||
HSINK_JETSON_D = 40.0;
|
||
HSINK_ESC_W = 50.0;
|
||
HSINK_ESC_D = 30.0;
|
||
HSINK_T = 2.2; // recess depth (pad sits flush in lid)
|
||
|
||
// Fasteners
|
||
M3_D = 3.3;
|
||
M4_D = 4.3;
|
||
M5_D = 5.3;
|
||
|
||
// ============================================================
|
||
// RENDER DISPATCH
|
||
// ============================================================
|
||
RENDER = "assembly";
|
||
|
||
if (RENDER == "assembly") {
|
||
assembly();
|
||
} else if (RENDER == "body_stl") {
|
||
box_body();
|
||
} else if (RENDER == "lid_stl") {
|
||
box_lid();
|
||
} else if (RENDER == "fan_duct_stl") {
|
||
fan_filter_duct();
|
||
} else if (RENDER == "latch_stl") {
|
||
latch_knob();
|
||
} else if (RENDER == "gasket_2d") {
|
||
projection(cut = true)
|
||
translate([0, 0, -0.5])
|
||
linear_extrude(1)
|
||
gasket_profile_2d();
|
||
}
|
||
|
||
// ============================================================
|
||
// ASSEMBLY PREVIEW
|
||
// ============================================================
|
||
module assembly() {
|
||
// Box body
|
||
color("DarkOliveGreen", 0.85) box_body();
|
||
|
||
// Lid (lifted 5 mm to show interior)
|
||
color("OliveDrab", 0.70)
|
||
translate([0, 0, OUT_H + 5])
|
||
box_lid();
|
||
|
||
// Fan duct on lid
|
||
color("SaddleBrown", 0.80)
|
||
translate([FAN_POS_X, FAN_POS_Y, OUT_H + LID_T + 5])
|
||
fan_filter_duct();
|
||
|
||
// Ghost latch knobs
|
||
for (lpos = latch_positions())
|
||
%color("DimGray", 0.60)
|
||
translate([lpos[0], lpos[1], OUT_H + 3])
|
||
latch_knob();
|
||
|
||
// Ghost Jetson PCB
|
||
%color("Green", 0.3)
|
||
translate([ORIN_POS_X, ORIN_POS_Y, WALL + ORIN_STOFF_H])
|
||
cube([58, 49, 3], center = true);
|
||
|
||
// Ghost FC
|
||
%color("Orange", 0.3)
|
||
translate([FC_POS_X, FC_POS_Y, WALL + FC_STOFF_H])
|
||
cube([30.5, 30.5, 3], center = true);
|
||
|
||
// Ghost ESC
|
||
%color("Red", 0.3)
|
||
translate([ESC_POS_X, ESC_POS_Y, WALL + ESC_STOFF_H])
|
||
cube([ESC_W, ESC_D, 3], center = true);
|
||
|
||
// Index annotations (cable gland markers)
|
||
for (gpos = cable_gland_positions())
|
||
%color("Yellow", 0.5)
|
||
translate([gpos[0], -OUT_D/2 - 5, gpos[1]])
|
||
rotate([90, 0, 0])
|
||
cylinder(d = gpos[2], h = 3);
|
||
}
|
||
|
||
// ============================================================
|
||
// BOX BODY
|
||
// ============================================================
|
||
module box_body() {
|
||
difference() {
|
||
union() {
|
||
// ── Outer shell (rounded rect, open top) ────────────────────
|
||
_rounded_box(OUT_W, OUT_D, OUT_H, BOX_R);
|
||
|
||
// ── Latch posts on top flange ────────────────────────────────
|
||
for (lpos = latch_positions())
|
||
translate([lpos[0], lpos[1], OUT_H])
|
||
_latch_post();
|
||
|
||
// ── Cable gland boss pads (rear wall reinforcement) ──────────
|
||
translate([0, -OUT_D/2, GLAND_Z])
|
||
rotate([90, 0, 0])
|
||
_gland_boss_array();
|
||
}
|
||
|
||
// ── Internal cavity ──────────────────────────────────────────────
|
||
translate([0, 0, WALL])
|
||
cube([INT_W, INT_D, INT_H + e], center = true);
|
||
|
||
// ── Cable gland holes (rear wall) ────────────────────────────────
|
||
for (gpos = cable_gland_positions())
|
||
translate([gpos[0], -OUT_D/2 - e, GLAND_Z])
|
||
rotate([90, 0, 0])
|
||
cylinder(d = gpos[2], h = WALL + 2*e);
|
||
|
||
// ── Internal standoffs (subtracted from floor thickness) ─────────
|
||
// These are ADDED in the union; we only subtract if needed for wires.
|
||
}
|
||
|
||
// ── Internal PCB standoffs (added in separate union) ─────────────────
|
||
// Jetson Orin NX standoffs
|
||
for (sx = [-1, 1]) for (sy = [-1, 1])
|
||
translate([ORIN_POS_X + sx*ORIN_HOLE_X,
|
||
ORIN_POS_Y + sy*ORIN_HOLE_Y,
|
||
WALL])
|
||
difference() {
|
||
cylinder(d = 7, h = ORIN_STOFF_H);
|
||
cylinder(d = M3_D, h = ORIN_STOFF_H + e);
|
||
}
|
||
|
||
// FC standoffs
|
||
for (sx = [-1, 1]) for (sy = [-1, 1])
|
||
translate([FC_POS_X + sx*FC_HOLE_SPC,
|
||
FC_POS_Y + sy*FC_HOLE_SPC,
|
||
WALL])
|
||
difference() {
|
||
cylinder(d = 7, h = FC_STOFF_H);
|
||
cylinder(d = M3_D, h = FC_STOFF_H + e);
|
||
}
|
||
|
||
// ESC standoffs (4 corners of ESC_W × ESC_D)
|
||
for (sx = [-1, 1]) for (sy = [-1, 1])
|
||
translate([ESC_POS_X + sx*(ESC_W/2 - 5),
|
||
ESC_POS_Y + sy*(ESC_D/2 - 5),
|
||
WALL])
|
||
difference() {
|
||
cylinder(d = 7, h = ESC_STOFF_H);
|
||
cylinder(d = M3_D, h = ESC_STOFF_H + e);
|
||
}
|
||
}
|
||
|
||
// ── Rounded box shell (open top) ─────────────────────────────────────────────
|
||
module _rounded_box(w, d, h, r) {
|
||
linear_extrude(h)
|
||
minkowski() {
|
||
square([w - 2*r, d - 2*r], center = true);
|
||
circle(r = r);
|
||
}
|
||
}
|
||
|
||
// ── Latch post (on top rim of box) ───────────────────────────────────────────
|
||
module _latch_post() {
|
||
difference() {
|
||
cylinder(d = LATCH_POST_D + 4, h = LATCH_BOSS_H);
|
||
// Central latch bore
|
||
translate([0, 0, -e])
|
||
cylinder(d = LATCH_POST_D, h = LATCH_BOSS_H + 2*e);
|
||
// Bayonet slot (cross-slot in post top, for knob lug engagement)
|
||
for (a = [0, 90])
|
||
rotate([0, 0, a])
|
||
translate([0, 0, LATCH_BOSS_H/2])
|
||
cube([LATCH_SLOT_W, LATCH_POST_D + 2, LATCH_BOSS_H],
|
||
center = true);
|
||
}
|
||
}
|
||
|
||
// ── Cable gland boss array (pad behind gland holes on outer wall) ─────────────
|
||
module _gland_boss_array() {
|
||
for (gpos = cable_gland_positions())
|
||
translate([gpos[0], 0, 0])
|
||
cylinder(d = gpos[2] + 8, h = 2);
|
||
}
|
||
|
||
// ── Latch positions (4 sides, centred) ───────────────────────────────────────
|
||
function latch_positions() = [
|
||
[ 0, +OUT_D/2 - 3 ], // front
|
||
[ 0, -OUT_D/2 + 3 ], // rear
|
||
[ +OUT_W/2 - 3, 0 ], // right
|
||
[ -OUT_W/2 + 3, 0 ], // left
|
||
];
|
||
|
||
// ── Cable gland positions [x, z, bore_d] on rear wall ────────────────────────
|
||
// 4× PG7 + 2× PG9, arranged in a row at GLAND_Z height.
|
||
// PG7 for signal / small power; PG9 for main drive harness.
|
||
function cable_gland_positions() = [
|
||
[ -INT_W/2 + 15, GLAND_Z, PG7_BORE ], // PG7 #1
|
||
[ -INT_W/2 + 40, GLAND_Z, PG7_BORE ], // PG7 #2
|
||
[ -INT_W/2 + 65, GLAND_Z, PG7_BORE ], // PG7 #3
|
||
[ -INT_W/2 + 90, GLAND_Z, PG7_BORE ], // PG7 #4
|
||
[ INT_W/2 - 30, GLAND_Z, PG9_BORE ], // PG9 #1 (main battery harness)
|
||
[ INT_W/2 - 60, GLAND_Z, PG9_BORE ], // PG9 #2 (motor harness)
|
||
];
|
||
|
||
// ============================================================
|
||
// BOX LID
|
||
// ============================================================
|
||
module box_lid() {
|
||
difference() {
|
||
union() {
|
||
// ── Top plate ───────────────────────────────────────────────
|
||
_rounded_box(OUT_W, OUT_D, LID_T, BOX_R);
|
||
|
||
// ── Flanged rim (overlaps box top; O-ring groove cut into it) ─
|
||
difference() {
|
||
translate([0, 0, -FLANGE_T])
|
||
_rounded_box(INT_W + 2*FLANGE_WALL,
|
||
INT_D + 2*FLANGE_WALL,
|
||
FLANGE_T + e, BOX_R - 1);
|
||
|
||
// Hollow interior of flange (sits over box rim)
|
||
translate([0, 0, -FLANGE_T - e])
|
||
cube([INT_W, INT_D, FLANGE_T + 2*e], center = true);
|
||
}
|
||
|
||
// ── Exhaust labyrinth baffles on rear of lid ─────────────────
|
||
_exhaust_baffles();
|
||
}
|
||
|
||
// ── Fan bore (through lid) ────────────────────────────────────────
|
||
translate([FAN_POS_X, FAN_POS_Y, -e])
|
||
cylinder(d = FAN_BORE_D, h = LID_T + 2*e);
|
||
|
||
// ── Fan bolt holes ────────────────────────────────────────────────
|
||
for (fx = [-1, 1]) for (fy = [-1, 1])
|
||
translate([FAN_POS_X + fx*FAN_BOLT_SPC/2,
|
||
FAN_POS_Y + fy*FAN_BOLT_SPC/2, -e])
|
||
cylinder(d = FAN_BOLT_D, h = LID_T + 2*e);
|
||
|
||
// ── O-ring groove in flange underside ─────────────────────────────
|
||
// Groove runs along the inner perimeter of the flange.
|
||
translate([0, 0, -ORING_GROOVE_D])
|
||
difference() {
|
||
_rounded_box(
|
||
INT_W + 2*FLANGE_WALL - 2*(FLANGE_WALL - ORING_INSET),
|
||
INT_D + 2*FLANGE_WALL - 2*(FLANGE_WALL - ORING_INSET),
|
||
ORING_GROOVE_D + e, BOX_R - 2);
|
||
_rounded_box(
|
||
INT_W + 2*FLANGE_WALL - 2*(FLANGE_WALL - ORING_INSET) - 2*ORING_GROOVE_W,
|
||
INT_D + 2*FLANGE_WALL - 2*(FLANGE_WALL - ORING_INSET) - 2*ORING_GROOVE_W,
|
||
ORING_GROOVE_D + 2*e, BOX_R - 3);
|
||
}
|
||
|
||
// ── Latch knob counterbores in lid flange (knob sits flush) ──────
|
||
for (lpos = latch_positions())
|
||
translate([lpos[0], lpos[1], -FLANGE_T - e])
|
||
cylinder(d = LATCH_KNOB_D + 1, h = FLANGE_T + 2*e);
|
||
|
||
// ── Heat sink pad recesses (underside of lid) ─────────────────────
|
||
// Jetson pad recess
|
||
translate([ORIN_POS_X, ORIN_POS_Y, -e])
|
||
cube([HSINK_JETSON_W, HSINK_JETSON_D, HSINK_T + e], center = true);
|
||
// ESC pad recess
|
||
translate([ESC_POS_X, ESC_POS_Y, -e])
|
||
cube([HSINK_ESC_W, HSINK_ESC_D, HSINK_T + e], center = true);
|
||
|
||
// ── Exhaust labyrinth slot through-holes ──────────────────────────
|
||
for (slot = exhaust_slot_positions())
|
||
translate([slot[0], slot[1], -e])
|
||
cube([EXH_SLOT_W, EXH_SLOT_L, LID_T + 2*e], center = true);
|
||
}
|
||
|
||
// ── Latch knob receiver rings in flange ─────────────────────────────────
|
||
for (lpos = latch_positions())
|
||
translate([lpos[0], lpos[1], -FLANGE_T])
|
||
difference() {
|
||
cylinder(d = LATCH_KNOB_D, h = FLANGE_T - 0.5);
|
||
// Bayonet lugs engage latch post slot
|
||
translate([0, 0, FLANGE_T - 0.5 - 3])
|
||
cylinder(d = LATCH_POST_D + 0.4, h = 3 + e);
|
||
cylinder(d = LATCH_POST_D - 3, h = FLANGE_T + e);
|
||
}
|
||
}
|
||
|
||
// ── Exhaust baffle array ──────────────────────────────────────────────────────
|
||
// Raised wall baffles on lid rear-right quadrant provide labyrinth exhaust path.
|
||
module _exhaust_baffles() {
|
||
exh_x = INT_W/4;
|
||
exh_y = INT_D/2 - 40;
|
||
|
||
for (row = [0 : EXH_ROWS - 1])
|
||
translate([exh_x, exh_y - row * (EXH_SLOT_W + 4), LID_T])
|
||
cube([EXH_N_SLOTS * (EXH_SLOT_W + 6), EXH_BAFFLE_H/3, EXH_BAFFLE_H]);
|
||
}
|
||
|
||
// ── Exhaust slot positions [x, y] (in lid top surface) ───────────────────────
|
||
function exhaust_slot_positions() = [
|
||
let(base_x = INT_W/4, base_y = INT_D/2 - 40)
|
||
for (i = [0 : EXH_N_SLOTS - 1])
|
||
[base_x - (EXH_N_SLOTS - 1)/2 * (EXH_SLOT_W + 6) + i * (EXH_SLOT_W + 6),
|
||
base_y - EXH_SLOT_L/2]
|
||
];
|
||
|
||
// ============================================================
|
||
// FAN FILTER DUCT (Part C — print 1×)
|
||
// ============================================================
|
||
// Sits on top of lid fan bore. Contains 5 mm foam filter pad.
|
||
// Labyrinth inlet around sides prevents direct splash ingress.
|
||
module fan_filter_duct() {
|
||
duct_od_w = FAN_SZ + 2*WALL;
|
||
foam_slot = FOAM_T + 0.5; // foam insert slot depth
|
||
|
||
difference() {
|
||
// Outer duct body
|
||
cube([duct_od_w, duct_od_w, DUCT_H], center = true);
|
||
|
||
// Foam filter slot (open at top for insert/remove)
|
||
translate([0, 0, DUCT_H/2 - foam_slot - 1])
|
||
cube([FAN_SZ, FAN_SZ, foam_slot + 2*e], center = true);
|
||
|
||
// Airflow bore below foam (connects to fan bore in lid)
|
||
translate([0, 0, -DUCT_H/2 - e])
|
||
cylinder(d = FAN_BORE_D, h = DUCT_H/2 + 2*e);
|
||
|
||
// Inlet slots on all 4 sides (labyrinth — no direct top-spray path)
|
||
for (a = [0, 90, 180, 270])
|
||
rotate([0, 0, a])
|
||
translate([0, duct_od_w/2 - WALL/2, 0]) {
|
||
// Three inlet slots, staggered vertically
|
||
for (sz = [-DUCT_H/2 + 5, 0, DUCT_H/2 - 8])
|
||
translate([0, 0, sz])
|
||
cube([FAN_SZ * 0.5, WALL + 2*e, 5], center = true);
|
||
}
|
||
|
||
// Fan bolt holes (align to lid bolt holes)
|
||
for (fx = [-1, 1]) for (fy = [-1, 1])
|
||
translate([fx*FAN_BOLT_SPC/2, fy*FAN_BOLT_SPC/2,
|
||
-DUCT_H/2 - e])
|
||
cylinder(d = FAN_BOLT_D, h = DUCT_H + 2*e);
|
||
}
|
||
}
|
||
|
||
// ============================================================
|
||
// QUARTER-TURN LATCH KNOB (Part D — print 4×)
|
||
// ============================================================
|
||
// Screws onto latch post from outside. 90° rotation latches/unlatches.
|
||
// Spring washer (not printed) provides axial preload.
|
||
module latch_knob() {
|
||
knob_h = 12.0;
|
||
grip_h = 8.0;
|
||
|
||
difference() {
|
||
union() {
|
||
// Body disc
|
||
cylinder(d = LATCH_KNOB_D, h = knob_h);
|
||
// Grip ridges (6×, for finger purchase)
|
||
for (i = [0:5])
|
||
rotate([0, 0, i * 60])
|
||
translate([LATCH_KNOB_D/2 - 1, 0, 0])
|
||
cylinder(d = 2.5, h = grip_h);
|
||
}
|
||
|
||
// Latch post bore (clearance)
|
||
translate([0, 0, -e])
|
||
cylinder(d = LATCH_POST_D + 0.5, h = knob_h + 2*e);
|
||
|
||
// Bayonet lug slot (catches latch post cross-slot)
|
||
// 2 lugs at 180° — quarter-turn locks both simultaneously
|
||
for (a = [0, 180])
|
||
rotate([0, 0, a])
|
||
translate([LATCH_POST_D/2 + LATCH_SLOT_W/2, 0, 5])
|
||
cube([LATCH_SLOT_W + 0.3, LATCH_POST_D, knob_h],
|
||
center = true);
|
||
}
|
||
}
|
||
|
||
// ============================================================
|
||
// GASKET PROFILE 2D (for DXF export)
|
||
// ============================================================
|
||
// Outputs the O-ring groove path as a 2D outline suitable for
|
||
// laser-cutting a flat silicone sheet gasket (alternative to O-ring cord).
|
||
// Sheet gasket: 2 mm silicone sheet, cut to this profile.
|
||
module gasket_profile_2d() {
|
||
oring_cl_offset = FLANGE_WALL - ORING_INSET;
|
||
outer_w = INT_W + 2*FLANGE_WALL - 2*oring_cl_offset;
|
||
inner_w = outer_w - 2*ORING_GROOVE_W;
|
||
r_outer = BOX_R - 2;
|
||
r_inner = r_outer - ORING_GROOVE_W;
|
||
|
||
difference() {
|
||
minkowski() {
|
||
square([outer_w - 2*r_outer, INT_D + 2*FLANGE_WALL - 2*oring_cl_offset - 2*r_outer],
|
||
center = true);
|
||
circle(r = r_outer);
|
||
}
|
||
minkowski() {
|
||
square([inner_w - 2*r_inner,
|
||
INT_D + 2*FLANGE_WALL - 2*oring_cl_offset - 2*r_inner - 2*ORING_GROOVE_W],
|
||
center = true);
|
||
circle(r = r_inner);
|
||
}
|
||
}
|
||
}
|