// ============================================================ // bumper_frame.scad — Sensor Head Roll Cage Rev A 2026-03-01 // Agent: sl-mechanical // ============================================================ // Roll cage for SaltyBot sensor head (25 mm OD stem). // Protects RPLIDAR A1M8, RealSense D435i, and 4× IMX219 cameras // when the robot tips over. // // Cage posts sit at 45°/135°/225°/315° (between camera arms at // 0°/90°/180°/270°), minimising camera FOV obstruction. // Crown ring sits ABOVE the RPLIDAR top — no scan-plane crossing. // Posts lean outward so they are outside RPLIDAR body radius at // scan-plane height. // // Component overview: // cage_collar_half() — split stem collar, print 2× // cage_post() — inclined rib, print 4×, lay flat // cage_crown() — flat ring above RPLIDAR, print 1× // tpu_crown_pad() — snap-on TPU impact strip, print in TPU // // RENDER options: // "assembly" full 3-D view (default) // "collar_front" front collar half for slicing // "collar_rear" rear collar half for slicing // "post" single post for slicing (print 4×) // "crown" crown ring for slicing // "tpu_pad" TPU snap strip for slicing // ============================================================ RENDER = "assembly"; // ── Stem ───────────────────────────────────────────────────── STEM_OD = 25.0; STEM_BORE = 25.5; // +0.5 mm clearance (collar slides easily) // ── Cage collar ────────────────────────────────────────────── COL_OD = 52.0; COL_H = 24.0; // height (shorter than sensor-head collar) COL_BOLT_X = 19.0; // M4 clamping bolt CL from stem axis (X) COL_BOLT_D = 4.5; // M4 clearance COL_NUT_W = 7.0; // M4 hex nut A/F COL_NUT_H = 3.4; // M4 hex nut thickness // Post attachment boss on collar outer surface // Boss at 45°/135°/225°/315°, protrudes POST_BOSS_H radially POST_BOSS_H = 6.0; POST_BOSS_W = 14.0; // boss width (tangential) POST_BOSS_Z = 6.0; // boss bottom offset from collar base POST_BOSS_HT = COL_H - POST_BOSS_Z - 4; // boss height on collar // ── Cage post ──────────────────────────────────────────────── POST_BASE_R = COL_OD/2 + POST_BOSS_H; // radial start of post CL POST_TIP_R = 108.0; // radial end of post at crown height CROWN_Z = 148.0; // height of crown ring (above collar base) // RPLIDAR top is ~120 mm above cage collar POST_W = 12.0; // post width (tangential) POST_T = 8.0; // post thickness (radial) POST_LEN = sqrt(pow(POST_TIP_R - POST_BASE_R, 2) + pow(CROWN_Z, 2)); POST_LEAN = atan((POST_TIP_R - POST_BASE_R) / CROWN_Z); // ~25° from vertical // At RPLIDAR scan height (~105 mm), post is at radius: // POST_BASE_R + (POST_TIP_R - POST_BASE_R) * (105/CROWN_Z) ≈ 78 mm // >> RPLIDAR body radius 35 mm — no scan-plane intersection POST_BOLT_D = 4.5; // M4 clearance at each post end // ── Crown ring ─────────────────────────────────────────────── CROWN_OD = 230.0; // crown outer diameter CROWN_ID = 206.0; // crown inner diameter CROWN_RH = 12.0; // crown ring height (thickness) CROWN_PAD_D = 10.0; // TPU pad snap channel diameter (semi-circle) CROWN_PAD_INSET = 5.0; // inset from crown outer edge // Post socket: slot in crown ring where post tip inserts SOCK_W = POST_W + 0.4; // slot width (clearance) SOCK_D = POST_T + 0.4; // slot depth SOCK_H = CROWN_RH; // full ring thickness // ── TPU crown pad ──────────────────────────────────────────── PAD_ID = CROWN_OD - 2 * CROWN_PAD_INSET; PAD_OD = CROWN_OD + 2.0; PAD_H = CROWN_PAD_D + 2.0; $fn = 64; e = 0.01; // ───────────────────────────────────────────────────────────── // cage_collar_half(side) // side = "front" (+Y) | "rear" (-Y) // Print flat-face-down. // ───────────────────────────────────────────────────────────── module cage_collar_half(side = "front") { y_front = (side == "front"); y_sign = y_front ? 1 : -1; difference() { union() { // D-shaped half body intersection() { cylinder(d = COL_OD, h = COL_H); translate([-COL_OD/2, y_front ? 0 : -COL_OD/2, 0]) cube([COL_OD, COL_OD/2, COL_H]); } // Post attachment bosses at 45° and 135° (front) / 225° and 315° (rear) for (a = y_front ? [45, -45] : [135, -135]) { rotate([0, 0, a]) translate([COL_OD/2, -POST_BOSS_W/2, POST_BOSS_Z]) cube([POST_BOSS_H, POST_BOSS_W, POST_BOSS_HT]); } } // Stem bore translate([0, 0, -e]) cylinder(d = STEM_BORE, h = COL_H + 2*e); // M4 clamping bolt holes (Y direction through mating face) for (bx = [-COL_BOLT_X, COL_BOLT_X]) { translate([bx, y_front ? COL_OD/2 : 0, COL_H/2]) rotate([90, 0, 0]) cylinder(d = COL_BOLT_D, h = COL_OD/2 + e); } // M4 hex nut pockets in rear half if (!y_front) { for (bx = [-COL_BOLT_X, COL_BOLT_X]) { translate([bx, -(COL_OD/4 + e), COL_H/2]) rotate([90, 0, 0]) cylinder(d = COL_NUT_W / cos(30), h = COL_NUT_H + e, $fn = 6); } } // M4 set screw (front only, outer face → stem bore) if (y_front) { translate([0, COL_OD/2, COL_H * 0.7]) rotate([90, 0, 0]) cylinder(d = COL_BOLT_D, h = COL_OD/2 - STEM_BORE/2 + e); } // M4 post attachment hole through each boss (radial direction) for (a = y_front ? [45, -45] : [135, -135]) { rotate([0, 0, a]) translate([COL_OD/2 + e, 0, POST_BOSS_Z + POST_BOSS_HT/2]) rotate([0, -90, 0]) cylinder(d = POST_BOLT_D, h = POST_BOSS_H + COL_OD/8 + e); } } } // ───────────────────────────────────────────────────────────── // cage_post() // For assembly: placed radially at angle a. // For slicing ("post" RENDER): printed lying flat on XY plane. // ───────────────────────────────────────────────────────────── module cage_post_geometry() { // Post spans from base (r = POST_BASE_R, z = 0) to tip (r = POST_TIP_R, z = CROWN_Z) // Width POST_W in tangential direction, thickness POST_T in radial direction difference() { hull() { translate([POST_BASE_R, -POST_W/2, 0]) cube([POST_T, POST_W, 1]); translate([POST_TIP_R, -POST_W/2, CROWN_Z - 1]) cube([POST_T, POST_W, 1]); } // M4 hole at base end (through POST_T, radially) translate([POST_BASE_R + POST_T/2, 0, POST_BOSS_Z + POST_BOSS_HT/2]) rotate([0, 90, 0]) cylinder(d = POST_BOLT_D, h = POST_T + 2*e, center = true); // M4 hole at tip end translate([POST_TIP_R + POST_T/2, 0, CROWN_Z - CROWN_RH/2]) rotate([0, 90, 0]) cylinder(d = POST_BOLT_D, h = POST_T + 2*e, center = true); // TPU pad snap groove on outer (high-r) face — runs along post length // Groove: 5 mm wide × 2.5 mm deep semi-channel at midspan translate([POST_TIP_R + POST_T - 0.1, -2.5, CROWN_Z * 0.3]) cube([3, 5, CROWN_Z * 0.4]); } } // ───────────────────────────────────────────────────────────── // cage_crown() // Flat ring. 4 post sockets at 45/135/225/315°. // TPU snap channel on outer rim. // ───────────────────────────────────────────────────────────── module cage_crown() { difference() { cylinder(d = CROWN_OD, h = CROWN_RH, $fn = 8); // Hollow centre translate([0, 0, -e]) cylinder(d = CROWN_ID, h = CROWN_RH + 2*e); // 4 post-tip sockets at 45/135/225/315° for (a = [45, 135, 225, 315]) { rotate([0, 0, a]) translate([CROWN_ID/2 + (CROWN_OD-CROWN_ID)/4 - SOCK_D/2, -SOCK_W/2, -e]) cube([SOCK_D + e, SOCK_W, SOCK_H + 2*e]); } // 2× M4 bolts per post socket (through ring top to post) for (a = [45, 135, 225, 315]) { rotate([0, 0, a]) for (dw = [-POST_W/4, POST_W/4]) translate([CROWN_ID/2 + (CROWN_OD-CROWN_ID)/4, dw, -e]) cylinder(d = POST_BOLT_D, h = CROWN_RH + 2*e); } // TPU snap channel — semicircular groove on outer rim top face translate([0, 0, CROWN_RH - CROWN_PAD_D/2]) rotate_extrude() translate([CROWN_OD/2 - CROWN_PAD_INSET, 0]) circle(d = CROWN_PAD_D); } } // ───────────────────────────────────────────────────────────── // tpu_crown_pad() — print in TPU, snaps into crown channel // ───────────────────────────────────────────────────────────── module tpu_crown_pad() { // Ring with round cross-section slightly larger than channel rotate_extrude() translate([PAD_ID/2 + (PAD_OD - PAD_ID)/2 - CROWN_PAD_INSET, 0]) circle(d = CROWN_PAD_D * 1.15); // slight interference for snap } // ───────────────────────────────────────────────────────────── // Render selector // ───────────────────────────────────────────────────────────── if (RENDER == "assembly") { color("SkyBlue", 0.9) cage_collar_half("front"); color("DodgerBlue", 0.9) mirror([0,1,0]) cage_collar_half("rear"); for (a = [45, 135, 225, 315]) color("LightGray", 0.85) rotate([0, 0, a]) cage_post_geometry(); color("Silver", 0.9) translate([0, 0, CROWN_Z]) cage_crown(); color("OrangeRed", 0.7) translate([0, 0, CROWN_Z + CROWN_RH - CROWN_PAD_D/2]) tpu_crown_pad(); } else if (RENDER == "collar_front") { cage_collar_half("front"); } else if (RENDER == "collar_rear") { cage_collar_half("rear"); } else if (RENDER == "post") { // Orient flat for slicing: long axis along X, flat face on Z=0 rotate([0, 0, -45]) // un-rotate from assembly angle rotate([-90, 0, 0]) // lay flat cage_post_geometry(); } else if (RENDER == "crown") { cage_crown(); } else if (RENDER == "tpu_pad") { tpu_crown_pad(); }