// CieloTrack receiver enclosure — CARRIER-BOARD revision. // // FINALIZED 2026-08-24 against the routed Flux board: outline 60x30 mm and the four // M3 mounting holes at (+/-26.5, +/-11.5) mm are confirmed, not placeholders. // // This supersedes cielotrack-case.scad (which was built for the STACKED, hand-wired // pair). The receiver is now one flat PCB: two Seeed XIAO ESP32-S3 modules side by // side on a 60 x 30 mm carrier (Flux project "Cielo Track Receiver"), a 2-pin power // header, and four M3 mounting holes. So the box is short and wide, not tall. // // openscad -D 'part="base"' -o base.stl cielotrack-carrier-case.scad // openscad -D 'part="lid"' -o lid.stl cielotrack-carrier-case.scad // // Print in PETG or ASA, never PLA: an enclosed box in the Texas sun passes PLA's // softening point and sags. Never carbon-filled filament — it is conductive and will // shield and detune the two on-board antennas, and the board will look healthy while // hearing less. /* [Board — from the finished Flux PCB] */ board_l = 60.0; // X - CONFIRMED from the Flux board outline board_w = 30.0; // Y - CONFIRMED from the Flux board outline board_t = 1.6; // PCB thickness // Tallest thing standing above the PCB. Two XIAOs SOCKETED on 2x7 female headers // dominate this. If you solder the modules flat instead, drop this to about 4. comp_h = 12.0; // MEASURE the populated board and set this /* [Board mounting — MATCH the four M3 holes in Flux] */ // CONFIRMED from Flux: four M3 NPTH holes at (+/-26.5, +/-11.5) mm, 3.2 mm dia // (H1 Top-Left, H2 Top-Right, H3 Bottom-Left, H4 Bottom-Right). inset 3.5 reproduces // them exactly (30-3.5=26.5, 15-3.5=11.5). The board holes are clearance; the M3 screw // passes through them and threads into the boss pilot below. hole_inset_x = 3.5; hole_inset_y = 3.5; boss_h = 4.0; // standoff under the board: clears the through-hole pin tails below boss_d = 6.0; boss_pilot = 2.6; // pilot for an M3 self-tapper (or open to 4.0 for a heat-set insert) /* [Fit] */ clearance = 1.0; // around the board, per side wall = 2.4; // 3 perimeters at a 0.4 nozzle; weatherproof rather than minimal floor_t = 2.4; lid_t = 2.4; lip_h = 3.0; // lid skirt depth lip_gap = 0.25; // printed fit; loosen to 0.35 if your printer runs tight /* [Which edge faces out] */ // The XIAO USB-C and its on-board 2.4 GHz antenna share the module's short end, and // both modules face the SAME board edge. That edge is the "front": RF radiates through // it, and it is where the USB ports sit. front = -1 is the -Y wall, +1 the +Y wall. // Flip it to match however the board sits in your build. front = -1; /* [Openings] */ // USB: default is NO permanent USB holes — you flash by lifting the lid (occasional), // and a sealed box beats two more water paths. Set usb_slots=true for standing ports. usb_slots = false; usb_offset = 13.0; // |X| of each module's USB port from board centre — VERIFY on PCB usb_w = 11.0; // USB-C plug body plus a cable boot usb_slot_h = 8.0; // Power (the 2-pin header) leaves through one gland low on the BACK wall, so water // cannot track in along the cable. J1 sits at the board's front-centre, so the power // wires make a short internal run back to the gland (kept clear of the antennas). gland_d = 6.0; gland_from_bottom = 5.0; vent_d = 3.0; // low on both short walls; nothing open facing up drain_d = 4.0; // one hole at the lowest point /* [Mounting ears] */ ear_t = 3.0; ear_r = 7.0; screw_d = 4.4; // M4 wall-mount, outside the sealed volume /* [Render] */ part = "both"; // "base" | "lid" | "both" $fn = 48; // ---- derived ------------------------------------------------------------------ inner_l = board_l + 2*clearance; inner_w = board_w + 2*clearance; inner_h = boss_h + board_t + comp_h + 2; // 2 mm headroom under the lid outer_l = inner_l + 2*wall; outer_w = inner_w + 2*wall; outer_h = inner_h + floor_t; hx = board_l/2 - hole_inset_x; hy = board_w/2 - hole_inset_y; holes = [[hx,hy],[hx,-hy],[-hx,hy],[-hx,-hy]]; module rrect(l,w,h,r=2.5){ hull() for(x=[r-l/2,l/2-r], y=[r-w/2,w/2-r]) translate([x,y,0]) cylinder(r=r,h=h); } module base(){ difference(){ union(){ rrect(outer_l, outer_w, outer_h); // mounting ears, outside the sealed volume so no screw enters the box for(s=[-1,1]) translate([s*(outer_l/2 + ear_r - 1.5), 0, 0]) cylinder(r=ear_r, h=ear_t); } // cavity translate([0,0,floor_t]) rrect(inner_l, inner_w, inner_h + 1, r=2.0); // USB-C openings in the front wall (optional) if(usb_slots) for(sx=[-1,1]) translate([sx*usb_offset, front*outer_w/2, floor_t + boss_h + board_t + usb_slot_h/2]) cube([usb_w, wall*3, usb_slot_h], center=true); // power-cable gland, low on the back wall (opposite the RF/USB front) translate([0, -front*outer_w/2, floor_t + gland_from_bottom]) rotate([90,0,0]) cylinder(d=gland_d, h=wall*3, center=true); // vents low on both short walls for(s=[-1,1]) for(i=[-1,0,1]) translate([s*outer_l/2, i*8, floor_t + 3]) rotate([0,90,0]) cylinder(d=vent_d, h=wall*3, center=true); // drain at the lowest point translate([0,0,-1]) cylinder(d=drain_d, h=floor_t+2); // ear screw holes for(s=[-1,1]) translate([s*(outer_l/2 + ear_r - 1.5), 0, -1]) cylinder(d=screw_d, h=ear_t+2); } // board-support bosses (added AFTER the difference so their pilots survive the cavity) for(h=holes) translate([h[0], h[1], floor_t]) difference(){ cylinder(d=boss_d, h=boss_h); translate([0,0,-1]) cylinder(d=boss_pilot, h=boss_h+2); } } module lid(){ difference(){ union(){ rrect(outer_l, outer_w, lid_t); // skirt that drops into the cavity translate([0,0,-lip_h]) rrect(inner_l - lip_gap*2, inner_w - lip_gap*2, lip_h, r=2.0); } // hollow the skirt so it is a lip, not a plug translate([0,0,-lip_h-0.5]) rrect(inner_l - lip_gap*2 - 2*1.6, inner_w - lip_gap*2 - 2*1.6, lip_h+1, r=1.2); } } if (part == "base") base(); else if (part == "lid") lid(); else { base(); translate([0, outer_w + 6, 0]) lid(); }