Protects expensive sensors when robot tips over during testing.
Files added:
chassis/bumper_frame.scad — roll cage (25 mm stem collar + 4 inclined
posts + crown ring + TPU snap pad). Posts at
45/135/225/315° between cameras. Crown at
Z=148 mm above cage collar, 18 mm above RPLIDAR
top. RPLIDAR FOV unobstructed.
chassis/base_bumper.scad — clip-on bumper ring for 270×240 mm base plate.
50 mm tall, 15 mm standoff, rounded corners.
4× corner + 4× side clip brackets. TPU snap
edge caps. FC status LEDs remain visible.
chassis/stem_protector.scad — split TPU sleeve for 25 mm stem. Snap-fit
closure (no hardware). Install 3–4 sleeves
along stem at ~200 mm intervals.
chassis/bumper_BOM.md — full BOM, print settings, install heights,
mass estimate (~500 g total, balanced).
All parts print without supports. Fully removable (clip/snap/clamp).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
261 lines
12 KiB
OpenSCAD
261 lines
12 KiB
OpenSCAD
// ============================================================
|
||
// 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();
|
||
}
|