Add csi_rain_shield.scad with parametric visor-style protective shields for all 4 Raspberry Pi CSI cameras (IMX219). Features: - Visor-style overhang (32mm forward extension) shields lens from rain and direct sunlight - Drip edge prevents water from trickling back onto PCB - Snap-fit tabs (4 per shield, no fasteners required) - Ventilation slots prevent moisture condensation - Optional transparent polycarbonate lens cover insert - Radially symmetric design fits all 4 cameras Includes comprehensive BOM with: - Print settings and material specifications - Assembly instructions for snap-fit installation - Maintenance and troubleshooting guide - Weather resistance performance notes - Design variants for different conditions - Post-print finishing recommendations Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
222 lines
10 KiB
OpenSCAD
222 lines
10 KiB
OpenSCAD
// ============================================================
|
||
// csi_rain_shield.scad — CSI Camera Rain/Sun Shield
|
||
// Issue: #254 Agent: sl-mechanical Date: 2026-03-02
|
||
// ============================================================
|
||
//
|
||
// Parametric visor-style rain and sun shield for SaltyBot's
|
||
// 4× Raspberry Pi CSI IMX219 cameras (32×32 mm PCB).
|
||
//
|
||
// Features:
|
||
// • Visor-style overhang — protects lens from rain & sun
|
||
// • Drip edge — prevents water from running back onto PCB
|
||
// • Snap-fit attachment — clips to camera housing, no fasteners
|
||
// • Transparent polycarbonate compatible — removable lens cover
|
||
// • Radial symmetry — same design fits all 4 cameras
|
||
//
|
||
// Design for IMX219:
|
||
// • Camera face plate: 38×38 mm (32 mm PCB + 3 mm frame per side)
|
||
// • Arm depth: 52 mm from sensor head centre to camera
|
||
// • 10° nose-down tilt on radial arms
|
||
//
|
||
// Assembly:
|
||
// 1. Print shield body (flat-side-down, minimal supports)
|
||
// 2. Optional: Insert clear polycarbonate sheet in front
|
||
// 3. Snap shield onto camera housing (4 tabs lock into grooves)
|
||
// 4. Repeat for all 4 cameras
|
||
//
|
||
// RENDER options:
|
||
// "shield" single shield unit (default)
|
||
// "assembly" 4 shields on camera arms (visual reference)
|
||
// "lens_cover" removable transparent polycarbonate insert
|
||
// ============================================================
|
||
|
||
RENDER = "shield";
|
||
|
||
// ── Camera housing dimensions ──────────────────────────────
|
||
// IMX219 camera with face-plate protection.
|
||
CAM_PCB_SIZE = 32.0; // PCB square side
|
||
CAM_FACE_SIZE = 38.0; // face plate (PCB + 3mm bezel each side)
|
||
CAM_FACE_THICK = 4.0; // face plate thickness (front-to-back)
|
||
|
||
// M2 hole pattern on PCB (for reference, not used in shield)
|
||
M2_SPACING = 24.0; // hole pattern spacing
|
||
|
||
// ── Radial arm geometry (from imx219_mount.scad) ───────────
|
||
ARM_R = 50.0; // platform radius (used in assembly view)
|
||
REACH_LEN = 52.0; // arm extension from sensor head
|
||
ARM_TILT = 10.0; // nose-down tilt angle
|
||
|
||
// ── Rain shield overhang ───────────────────────────────────
|
||
// Visor extends forward and downward from camera housing.
|
||
|
||
VISOR_OVERHANG = 32.0; // forward extension from camera face
|
||
VISOR_H = 18.0; // overhang height (downward from top)
|
||
VISOR_DROP = 16.0; // bottom lip drop (prevents water runoff)
|
||
VISOR_THICK = 3.0; // wall thickness
|
||
|
||
// Drip edge: curved lip at bottom to prevent water trickling back
|
||
DRIP_RADIUS = 3.0; // radius of drip edge curve
|
||
DRIP_HEIGHT = 4.0; // height of drip lip
|
||
|
||
// ── Snap-fit attachment tabs ───────────────────────────────
|
||
// Tabs clip into grooves on camera housing side walls.
|
||
|
||
SNAP_TAB_H = 6.0; // tab height (above housing)
|
||
SNAP_TAB_W = 8.0; // tab width
|
||
SNAP_TAB_D = 2.0; // tab depth (into groove)
|
||
SNAP_CLEARANCE = 0.4; // clearance for easy snap/unsnap
|
||
|
||
NUM_TABS = 4; // 4 tabs around perimeter
|
||
|
||
// ── Lens cover (polycarbonate insert) ──────────────────────
|
||
// Optional transparent front cover — slides into grooves.
|
||
|
||
LENS_COVER_T = 2.0; // polycarbonate sheet thickness
|
||
LENS_COVER_W = CAM_FACE_SIZE + 6.0; // cover width (with edge flanges)
|
||
LENS_COVER_FLANGE = 3.0; // flange width on each side
|
||
|
||
// ── Ventilation ────────────────────────────────────────────
|
||
// Small vent slots prevent moisture condensation inside shield.
|
||
|
||
VENT_SLOT_W = 3.0; // vent slot width
|
||
VENT_SLOT_H = 2.0; // vent slot height
|
||
NUM_VENTS = 3; // slots per side (3 on left, 3 on right)
|
||
|
||
// ── General ────────────────────────────────────────────────
|
||
$fn = 64;
|
||
e = 0.01;
|
||
|
||
// ─────────────────────────────────────────────────────────────
|
||
// csi_rain_shield()
|
||
// Single shield unit — visor with snap-fit tabs.
|
||
// Print flat-side-down (visor facing down).
|
||
//
|
||
// Coordinate system:
|
||
// X = left-right (camera width direction)
|
||
// Y = front-back (camera pointing direction)
|
||
// Z = up-down
|
||
// Camera face at Y=0, lens center at origin.
|
||
// ─────────────────────────────────────────────────────────────
|
||
module csi_rain_shield() {
|
||
// Main visor body — asymmetric overhang
|
||
difference() {
|
||
union() {
|
||
// Visor base plate (behind camera, attaches to housing)
|
||
translate([-CAM_FACE_SIZE/2 - 4, -CAM_FACE_THICK - 2, 0])
|
||
cube([CAM_FACE_SIZE + 8, 6, VISOR_THICK]);
|
||
|
||
// Forward overhang — curved top for water runoff
|
||
translate([-VISOR_H/2, -CAM_FACE_THICK, 0]) {
|
||
difference() {
|
||
// Main overhang volume
|
||
cube([VISOR_H, VISOR_OVERHANG, VISOR_THICK]);
|
||
|
||
// Curved top surface (water sheds outward)
|
||
translate([VISOR_H/2, 0, -e])
|
||
rotate([90, 0, 0])
|
||
cylinder(r = VISOR_H/2 + 2, h = VISOR_OVERHANG + 2*e);
|
||
}
|
||
}
|
||
|
||
// Side flanges (stabilize against wind, provide snap-tab base)
|
||
for (sx = [-1, 1])
|
||
translate([sx * (CAM_FACE_SIZE/2 + 2), -CAM_FACE_THICK, 0])
|
||
cube([4, CAM_FACE_THICK + VISOR_OVERHANG/2, VISOR_THICK]);
|
||
|
||
// Drip edge — bottom lip curves downward & forward
|
||
translate([-(VISOR_H-2)/2, -CAM_FACE_THICK + VISOR_OVERHANG - 2, -DRIP_HEIGHT])
|
||
rotate([0, 90, 0])
|
||
cylinder(r = DRIP_RADIUS, h = VISOR_H - 4);
|
||
}
|
||
|
||
// Lens opening — lets camera see through (frame around camera face)
|
||
translate([-(CAM_FACE_SIZE-2)/2, -CAM_FACE_THICK - 2, -e])
|
||
cube([CAM_FACE_SIZE-2, 2, VISOR_THICK + 2*e]);
|
||
|
||
// Vent slots (prevent condensation) — left side
|
||
for (i = [0 : NUM_VENTS-1]) {
|
||
vent_y = -CAM_FACE_THICK + (VISOR_OVERHANG * (i+1) / (NUM_VENTS+1));
|
||
translate([-(CAM_FACE_SIZE/2 + 8), vent_y, VISOR_THICK/2 - VENT_SLOT_H/2])
|
||
cube([2, VENT_SLOT_W, VENT_SLOT_H]);
|
||
translate([(CAM_FACE_SIZE/2 + 6), vent_y, VISOR_THICK/2 - VENT_SLOT_H/2])
|
||
cube([2, VENT_SLOT_W, VENT_SLOT_H]);
|
||
}
|
||
}
|
||
|
||
// Snap-fit tabs — clip into camera housing grooves
|
||
for (i = [0 : NUM_TABS-1]) {
|
||
angle = (360 / NUM_TABS) * i - 45; // 4 tabs at 90° intervals, rotated -45°
|
||
translate([-CAM_FACE_SIZE/2 - 3, -CAM_FACE_THICK/2, 0])
|
||
rotate([0, 0, angle])
|
||
translate([CAM_FACE_SIZE/2 + 3 + SNAP_TAB_D/2, 0, -SNAP_TAB_H/2])
|
||
cube([SNAP_TAB_W, SNAP_TAB_D + SNAP_CLEARANCE, SNAP_TAB_H], center=true);
|
||
}
|
||
|
||
// Lens cover groove — retains transparent polycarbonate insert
|
||
translate([-(LENS_COVER_W/2 + 1), -CAM_FACE_THICK - 1.5, -e])
|
||
cube([LENS_COVER_W + 2, 1.5, LENS_COVER_T + 0.5]);
|
||
}
|
||
|
||
// ─────────────────────────────────────────────────────────────
|
||
// csi_lens_cover()
|
||
// Removable transparent polycarbonate insert.
|
||
// This is a phantom/visual-only part; actual cover is cut from
|
||
// 2.0 mm clear polycarbonate sheet and trimmed to size.
|
||
// ─────────────────────────────────────────────────────────────
|
||
module csi_lens_cover() {
|
||
// Clear lens cover — sits in grooves on shield front edge
|
||
difference() {
|
||
union() {
|
||
// Main cover plate (represents polycarbonate insert)
|
||
translate([-(LENS_COVER_W/2), -CAM_FACE_THICK - 2, 0])
|
||
cube([LENS_COVER_W, 2, LENS_COVER_T]);
|
||
|
||
// Side flanges (grip in shield grooves)
|
||
for (sx = [-1, 1])
|
||
translate([sx * (LENS_COVER_W/2 - LENS_COVER_FLANGE/2),
|
||
-CAM_FACE_THICK - 2, 0])
|
||
cube([LENS_COVER_FLANGE, 2, LENS_COVER_T]);
|
||
}
|
||
|
||
// Lens opening (clear aperture in front of camera)
|
||
translate([-(CAM_FACE_SIZE-4)/2, -CAM_FACE_THICK - 3, -e])
|
||
cube([CAM_FACE_SIZE-4, 4, LENS_COVER_T + 2*e]);
|
||
}
|
||
}
|
||
|
||
// ─────────────────────────────────────────────────────────────
|
||
// assembly_4x_shields()
|
||
// All 4 shields mounted on radial arms (reference visualization).
|
||
// ─────────────────────────────────────────────────────────────
|
||
module assembly_4x_shields() {
|
||
// 4 shields at 90° intervals (matches 4x CSI cameras)
|
||
for (angle = [0, 90, 180, 270]) {
|
||
color("LightSteelBlue", 0.85)
|
||
rotate([0, 0, angle])
|
||
translate([ARM_R, 0, 0])
|
||
rotate([ARM_TILT, 0, 0])
|
||
csi_rain_shield();
|
||
|
||
// Phantom camera face (reference only)
|
||
color("DarkGray", 0.5)
|
||
rotate([0, 0, angle])
|
||
translate([ARM_R, 0, 0])
|
||
rotate([ARM_TILT, 0, 0])
|
||
translate([-(CAM_FACE_SIZE/2), 0, -CAM_FACE_THICK/2])
|
||
cube([CAM_FACE_SIZE, 1, CAM_FACE_SIZE], center=false);
|
||
}
|
||
}
|
||
|
||
// ─────────────────────────────────────────────────────────────
|
||
// Render selector
|
||
// ─────────────────────────────────────────────────────────────
|
||
if (RENDER == "shield") {
|
||
csi_rain_shield();
|
||
|
||
} else if (RENDER == "assembly") {
|
||
assembly_4x_shields();
|
||
|
||
} else if (RENDER == "lens_cover") {
|
||
csi_lens_cover();
|
||
}
|