feat: bumper + frame crash protection (roll cage, base bumper, stem sleeves) #56
291
chassis/base_bumper.scad
Normal file
291
chassis/base_bumper.scad
Normal file
@ -0,0 +1,291 @@
|
||||
// ============================================================
|
||||
// base_bumper.scad — Base Plate Impact Bumper Rev A 2026-03-01
|
||||
// Agent: sl-mechanical
|
||||
// ============================================================
|
||||
// Clip-on bumper system for the 270×240 mm SaltyBot base plate.
|
||||
// Protects wheels, FC board, and wiring when robot tips.
|
||||
//
|
||||
// System overview:
|
||||
// corner_bracket() — L-clip, grips plate corner, print 4×
|
||||
// side_bracket() — straight clip for mid-sides, print 4×
|
||||
// tpu_edge_cap() — soft snap-on edge cap, print in TPU
|
||||
// (same profile fits corner and side)
|
||||
// bumper_rail_2d() — 2-D cross-section DXF for straight rails
|
||||
//
|
||||
// Assembly: 4 corner brackets + 4 side brackets clip onto the
|
||||
// plate bottom edge without drilling. Rails (printed or cut from
|
||||
// 25×8 mm aluminium bar) bridge between clips. TPU edge caps
|
||||
// snap onto rail outer face.
|
||||
//
|
||||
// FC status LEDs: FC is front-centre of plate. Bumper is below
|
||||
// and outside the plate footprint — LEDs remain visible.
|
||||
//
|
||||
// RENDER options:
|
||||
// "assembly" 3-D view with phantom plate (default)
|
||||
// "corner" one corner bracket (print 4×)
|
||||
// "side" one side bracket (print 4×)
|
||||
// "tpu_cap" TPU edge cap cross-section extruded (print N×)
|
||||
// "rail_2d" flat rail profile → DXF
|
||||
// ============================================================
|
||||
|
||||
RENDER = "assembly";
|
||||
|
||||
// ── Base plate (reference only — not printed) ─────────────────
|
||||
PLATE_W = 270.0; // width (motor axle direction, X)
|
||||
PLATE_D = 240.0; // depth (fore-aft, Y)
|
||||
PLATE_H = 6.0; // plate thickness
|
||||
PLATE_R = 10.0; // plate corner radius
|
||||
|
||||
// ── Bumper geometry ──────────────────────────────────────────
|
||||
BUMP_H = 50.0; // bumper height below plate bottom face
|
||||
BUMP_T = 8.0; // rail/bracket wall thickness
|
||||
BUMP_CR = 22.0; // outer corner radius of bumper frame
|
||||
BUMP_EXTEND = 15.0; // how far bumper extends beyond plate edge
|
||||
// (absorbs impact before plate corner hits)
|
||||
|
||||
// ── Clip channel (grips plate edge) ──────────────────────────
|
||||
CLIP_SLOT_W = PLATE_H + 0.6; // slot width (6 mm plate + 0.6 clearance)
|
||||
CLIP_SLOT_D = 12.0; // how deep clip grips plate edge
|
||||
CLIP_GRIP_T = 4.0; // clip wall above plate (keeps clip aligned)
|
||||
|
||||
// ── Corner bracket ────────────────────────────────────────────
|
||||
CORNER_ARM = 45.0; // length of each L-arm along plate edge
|
||||
CORNER_H = BUMP_H + CLIP_GRIP_T + CLIP_SLOT_W;
|
||||
|
||||
// ── Side bracket ─────────────────────────────────────────────
|
||||
SIDE_ARM = 30.0; // side bracket total width along plate edge
|
||||
SIDE_H = CORNER_H;
|
||||
|
||||
// ── Rail (reference dims for procurement / DXF) ───────────────
|
||||
RAIL_W = BUMP_T; // 8 mm
|
||||
RAIL_H = BUMP_H; // 50 mm
|
||||
|
||||
// ── TPU edge cap ─────────────────────────────────────────────
|
||||
CAP_W = BUMP_T + 4.0; // wraps around rail outer face
|
||||
CAP_H = 14.0; // cap height (bottom of bumper profile)
|
||||
CAP_R = 8.0; // outer impact radius
|
||||
CAP_CLIP_W = BUMP_T + 0.6; // inner clip width (snaps on rail)
|
||||
CAP_CLIP_D = 5.0; // clip depth
|
||||
|
||||
// ── Fasteners ─────────────────────────────────────────────────
|
||||
M4_D = 4.5; // M4 clearance hole (rail → bracket)
|
||||
M3_D = 3.3; // M3 set screw (lock bracket on plate)
|
||||
|
||||
$fn = 48;
|
||||
e = 0.01;
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// clip_slot_2d()
|
||||
// 2-D profile of the plate-edge gripping channel.
|
||||
// Origin at plate bottom face, +Y toward plate interior.
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
module clip_slot_2d() {
|
||||
// Slot opens from outside (-Y), grips plate edge
|
||||
translate([0, -(BUMP_EXTEND + BUMP_T)])
|
||||
square([CLIP_SLOT_W, CLIP_SLOT_D + BUMP_EXTEND + BUMP_T]);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// corner_bracket()
|
||||
// L-shaped clip bracket. Internal coordinate:
|
||||
// Corner at origin, arms along +X and +Y.
|
||||
// Plate sits at Z = BUMP_H (above bracket base).
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
module corner_bracket() {
|
||||
arm_w = BUMP_T; // wall thickness
|
||||
arm_h = CORNER_H; // full height
|
||||
|
||||
difference() {
|
||||
union() {
|
||||
// X arm (along plate width edge)
|
||||
translate([-BUMP_EXTEND, -BUMP_EXTEND, 0])
|
||||
hull() {
|
||||
cube([CORNER_ARM + BUMP_EXTEND, arm_w, arm_h]);
|
||||
translate([0, arm_w, 0])
|
||||
cylinder(r = BUMP_CR, h = arm_h);
|
||||
translate([CORNER_ARM + BUMP_EXTEND, arm_w, 0])
|
||||
cylinder(r = BUMP_CR, h = arm_h);
|
||||
}
|
||||
|
||||
// Y arm (along plate depth edge)
|
||||
translate([-BUMP_EXTEND, -BUMP_EXTEND, 0])
|
||||
hull() {
|
||||
cube([arm_w, CORNER_ARM + BUMP_EXTEND, arm_h]);
|
||||
translate([arm_w, 0, 0])
|
||||
cylinder(r = BUMP_CR, h = arm_h);
|
||||
translate([arm_w, CORNER_ARM + BUMP_EXTEND, 0])
|
||||
cylinder(r = BUMP_CR, h = arm_h);
|
||||
}
|
||||
}
|
||||
|
||||
// Clip slot in X arm (grips plate edge)
|
||||
// Slot at Z = BUMP_H (plate bottom face height)
|
||||
translate([CORNER_ARM - CLIP_SLOT_D, -(BUMP_EXTEND + arm_w + e), BUMP_H])
|
||||
cube([CLIP_SLOT_D + BUMP_EXTEND + e,
|
||||
arm_w + 2*e, CLIP_SLOT_W]);
|
||||
|
||||
// Clip slot in Y arm
|
||||
translate([-(BUMP_EXTEND + arm_w + e), CORNER_ARM - CLIP_SLOT_D, BUMP_H])
|
||||
cube([arm_w + 2*e,
|
||||
CLIP_SLOT_D + BUMP_EXTEND + e, CLIP_SLOT_W]);
|
||||
|
||||
// M3 set screw through clip above slot (X arm, 2 per arm)
|
||||
for (xoff = [CORNER_ARM * 0.3, CORNER_ARM * 0.7]) {
|
||||
translate([xoff - BUMP_EXTEND, -(BUMP_EXTEND + arm_w/2), BUMP_H + CLIP_SLOT_W + CLIP_GRIP_T/2])
|
||||
rotate([90, 0, 0])
|
||||
cylinder(d = M3_D, h = arm_w + 2*e);
|
||||
}
|
||||
for (yoff = [CORNER_ARM * 0.3, CORNER_ARM * 0.7]) {
|
||||
translate([-(BUMP_EXTEND + arm_w/2), yoff - BUMP_EXTEND, BUMP_H + CLIP_SLOT_W + CLIP_GRIP_T/2])
|
||||
rotate([0, 90, 0])
|
||||
cylinder(d = M3_D, h = arm_w + 2*e);
|
||||
}
|
||||
|
||||
// M4 holes for rail attachment (outer face, pairs per arm)
|
||||
for (xoff = [15, CORNER_ARM - 10]) {
|
||||
translate([xoff - BUMP_EXTEND, -(BUMP_EXTEND + arm_w + e), BUMP_H * 0.3])
|
||||
rotate([90, 0, 0])
|
||||
cylinder(d = M4_D, h = arm_w + 2*e);
|
||||
translate([xoff - BUMP_EXTEND, -(BUMP_EXTEND + arm_w + e), BUMP_H * 0.7])
|
||||
rotate([90, 0, 0])
|
||||
cylinder(d = M4_D, h = arm_w + 2*e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// side_bracket()
|
||||
// Straight clip for mid-side. Grips plate edge, M4 holes
|
||||
// for rail, M3 set screws. Placed between corner brackets.
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
module side_bracket() {
|
||||
arm_w = BUMP_T;
|
||||
arm_h = SIDE_H;
|
||||
|
||||
difference() {
|
||||
// Outer body
|
||||
translate([-SIDE_ARM/2, -(BUMP_EXTEND + arm_w), 0])
|
||||
hull() {
|
||||
cube([SIDE_ARM, arm_w, arm_h]);
|
||||
translate([0, arm_w, 0])
|
||||
cylinder(r = BUMP_CR * 0.6, h = arm_h);
|
||||
translate([SIDE_ARM, arm_w, 0])
|
||||
cylinder(r = BUMP_CR * 0.6, h = arm_h);
|
||||
}
|
||||
|
||||
// Clip slot
|
||||
translate([-SIDE_ARM/2 + 5, -(BUMP_EXTEND + arm_w + e), BUMP_H])
|
||||
cube([SIDE_ARM - 10, arm_w + 2*e, CLIP_SLOT_W]);
|
||||
|
||||
// M3 set screws (2×)
|
||||
for (xoff = [-SIDE_ARM/4, SIDE_ARM/4]) {
|
||||
translate([xoff, -(BUMP_EXTEND + arm_w/2), BUMP_H + CLIP_SLOT_W + CLIP_GRIP_T/2])
|
||||
rotate([90, 0, 0])
|
||||
cylinder(d = M3_D, h = arm_w + 2*e);
|
||||
}
|
||||
|
||||
// M4 rail holes (2 heights × 1 column)
|
||||
for (zh = [BUMP_H * 0.3, BUMP_H * 0.7]) {
|
||||
translate([0, -(BUMP_EXTEND + arm_w + e), zh])
|
||||
rotate([90, 0, 0])
|
||||
cylinder(d = M4_D, h = arm_w + 2*e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// tpu_edge_cap()
|
||||
// Snap-on soft edge cap — print in TPU.
|
||||
// Snaps onto outer face of rail (or corner bracket outer face).
|
||||
// Orient: cap opening faces -Y (toward bracket), print flat.
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
module tpu_edge_cap() {
|
||||
// Length — 40 mm segment; print multiple, install end-to-end
|
||||
cap_len = 40.0;
|
||||
|
||||
difference() {
|
||||
union() {
|
||||
// Rounded outer impact face
|
||||
hull() {
|
||||
translate([-CAP_W/2, 0, 0])
|
||||
cylinder(r = CAP_R, h = cap_len);
|
||||
translate([ CAP_W/2, 0, 0])
|
||||
cylinder(r = CAP_R, h = cap_len);
|
||||
translate([-CAP_W/2, CAP_H - CAP_R, 0])
|
||||
cylinder(r = CAP_R, h = cap_len);
|
||||
translate([ CAP_W/2, CAP_H - CAP_R, 0])
|
||||
cylinder(r = CAP_R, h = cap_len);
|
||||
}
|
||||
}
|
||||
|
||||
// Inner clip slot (snaps onto rail outer face)
|
||||
translate([-CAP_CLIP_W/2, -e, -e])
|
||||
cube([CAP_CLIP_W, CAP_CLIP_D + e, cap_len + 2*e]);
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// bumper_rail_2d()
|
||||
// 2-D profile for straight bumper rail sections.
|
||||
// Extrude to length per side: front/rear = PLATE_W, sides = PLATE_D.
|
||||
// Use "rail_2d" RENDER to export DXF.
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
module bumper_rail_2d() {
|
||||
// Simple rectangular profile 8 × 50 mm
|
||||
square([RAIL_W, RAIL_H]);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Phantom plate (reference, assembly view only)
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
module phantom_plate() {
|
||||
color("Gray", 0.25)
|
||||
linear_extrude(PLATE_H)
|
||||
offset(r = PLATE_R, $fn = 32)
|
||||
offset(-PLATE_R)
|
||||
square([PLATE_W, PLATE_D], center = true);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Render selector
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
if (RENDER == "assembly") {
|
||||
// Phantom plate
|
||||
translate([0, 0, BUMP_H]) phantom_plate();
|
||||
|
||||
// 4 corner brackets (NW / NE / SW / SE)
|
||||
half_w = PLATE_W/2;
|
||||
half_d = PLATE_D/2;
|
||||
color("SteelBlue", 0.9) {
|
||||
// NW corner (X−, Y+) → rotate 90°
|
||||
translate([-half_w, half_d, 0]) rotate([0,0,180]) corner_bracket();
|
||||
// NE corner (X+, Y+) → rotate 90°
|
||||
translate([ half_w, half_d, 0]) rotate([0,0,270]) corner_bracket();
|
||||
// SW corner (X−, Y−) → no rotation
|
||||
translate([-half_w,-half_d, 0]) rotate([0,0, 90]) corner_bracket();
|
||||
// SE corner (X+, Y−)
|
||||
translate([ half_w,-half_d, 0]) corner_bracket();
|
||||
}
|
||||
|
||||
// 4 side brackets (front, rear, left, right midpoints)
|
||||
color("CornflowerBlue", 0.9) {
|
||||
translate([0, half_d, 0]) rotate([0,0,180]) side_bracket(); // front
|
||||
translate([0, -half_d, 0]) side_bracket(); // rear
|
||||
translate([-half_w, 0, 0]) rotate([0,0, 90]) side_bracket(); // left
|
||||
translate([ half_w, 0, 0]) rotate([0,0,270]) side_bracket(); // right
|
||||
}
|
||||
|
||||
} else if (RENDER == "corner") {
|
||||
corner_bracket();
|
||||
|
||||
} else if (RENDER == "side") {
|
||||
side_bracket();
|
||||
|
||||
} else if (RENDER == "tpu_cap") {
|
||||
tpu_edge_cap();
|
||||
|
||||
} else if (RENDER == "rail_2d") {
|
||||
projection(cut = false)
|
||||
linear_extrude(1)
|
||||
bumper_rail_2d();
|
||||
}
|
||||
169
chassis/bumper_BOM.md
Normal file
169
chassis/bumper_BOM.md
Normal file
@ -0,0 +1,169 @@
|
||||
# SaltyBot Bumper & Crash Protection — BOM + Assembly Notes
|
||||
**Rev A — 2026-03-01 — sl-mechanical**
|
||||
|
||||
---
|
||||
|
||||
## System Overview
|
||||
|
||||
Three independent protection layers:
|
||||
|
||||
| Layer | File | Protects |
|
||||
|-------|------|----------|
|
||||
| Sensor head roll cage | `bumper_frame.scad` | RPLIDAR, RealSense, 4× IMX219 |
|
||||
| Base plate bumper ring | `base_bumper.scad` | FC, motors, wiring, wheels |
|
||||
| Stem impact sleeves | `stem_protector.scad` | 25 mm stem tube along length |
|
||||
|
||||
---
|
||||
|
||||
## Design Constraints Met
|
||||
|
||||
| Constraint | How addressed |
|
||||
|-----------|---------------|
|
||||
| RPLIDAR 360° FOV | Roll cage posts at 45°/135°/225°/315° (between cameras). At RPLIDAR scan-plane height (~105 mm from cage collar), posts are ~78 mm from stem CL — well outside RPLIDAR body (35 mm radius). Crown ring sits at Z=148 mm, above RPLIDAR top. |
|
||||
| Camera views | Posts avoid all four camera directions (0°/90°/180°/270°). |
|
||||
| FC status LEDs | Base bumper clips below plate edge — does not cover plate top face or sides. LEDs remain visible. |
|
||||
| Removable | All parts clip or clamp; no permanent fasteners into robot structure. |
|
||||
| No supports | All parts print flat-face-down (collar halves, posts, crown ring, brackets, sleeves). |
|
||||
| Lightweight | PLA frame + TPU pads only where needed; crown ring is hollow, brackets are thin-wall. |
|
||||
|
||||
---
|
||||
|
||||
## Part A — Sensor Head Roll Cage (`bumper_frame.scad`)
|
||||
|
||||
### Printed parts
|
||||
|
||||
| # | RENDER | Qty | Material | Settings | Notes |
|
||||
|---|--------|-----|----------|----------|-------|
|
||||
| 1 | `collar_front` | 1 | PETG | 5 perims, 40% infill | Flat-face-down |
|
||||
| 2 | `collar_rear` | 1 | PETG | 5 perims, 40% infill | Flat-face-down; mirror of front |
|
||||
| 3 | `post` | 4 | PETG or PLA | 4 perims, 25% infill | Lay flat on narrow face |
|
||||
| 4 | `crown` | 1 | PETG or PLA | 4 perims, 30% infill | Flat (natural print orientation) |
|
||||
| 5 | `tpu_pad` | 1 | TPU 95A | 3 perims, 20% infill, 25 mm/s | Snap-on crown impact ring |
|
||||
|
||||
### Fasteners
|
||||
|
||||
| # | Spec | Qty | Use |
|
||||
|---|------|-----|-----|
|
||||
| 6 | M4×25 SHCS | 2 | Cage collar clamping bolts |
|
||||
| 7 | M4 hex nut | 2 | Captured in collar rear half |
|
||||
| 8 | M4×10 set screw | 1 | Height/rotation lock (front collar) |
|
||||
| 9 | M4×20 SHCS | 8 | Post-to-collar (2 per post, through boss) |
|
||||
| 10 | M4×16 SHCS | 8 | Post-to-crown (2 per post, through crown top) |
|
||||
| 11 | M4 hex nut | 8 | Under collar for post bolts |
|
||||
| 12 | M4 flat washer | 16 | All M4 bolt heads |
|
||||
|
||||
### Cage installation — height reference
|
||||
|
||||
```
|
||||
Z from cage collar base:
|
||||
Z = 0 mm — cage collar bottom (clamp to stem here)
|
||||
Z = 24 mm — cage collar top
|
||||
Z = 54 mm — sensor-head collar bottom (≈30 mm above cage collar)
|
||||
Z = 90 mm — sensor-head collar top + platform
|
||||
Z = 100 mm — RPLIDAR base (above anti-vibration ring)
|
||||
Z = 130 mm — RPLIDAR top
|
||||
Z = 148 mm — cage crown ring (12 mm clear of RPLIDAR)
|
||||
```
|
||||
|
||||
> **Install the cage collar ~30 mm BELOW the sensor-head collar on the same 25 mm stem.**
|
||||
> Slide collar down stem, position posts, attach crown ring, then tighten collar clamp bolts.
|
||||
|
||||
---
|
||||
|
||||
## Part B — Base Plate Bumper (`base_bumper.scad`)
|
||||
|
||||
### Printed parts
|
||||
|
||||
| # | RENDER | Qty | Material | Notes |
|
||||
|---|--------|-----|----------|-------|
|
||||
| 13 | `corner` | 4 | PETG | Print upright (Z = tall dimension); 4 perims, 40% infill |
|
||||
| 14 | `side` | 4 | PETG | Same orientation; 4 perims, 40% infill |
|
||||
| 15 | `tpu_cap` | ~14 | TPU 95A | 40 mm segments; install end-to-end along all rails |
|
||||
|
||||
### Rails (straight sections between clips)
|
||||
|
||||
Rails can be 3D-printed from SCAD profile or procured:
|
||||
|
||||
| # | Description | Qty | Size | Alternative |
|
||||
|---|-------------|-----|------|-------------|
|
||||
| 16 | Front rail | 1 | 8×50×270 mm PLA/PETG extrusion | 25×8 mm aluminium flat bar |
|
||||
| 17 | Rear rail | 1 | 8×50×270 mm | same |
|
||||
| 18 | Left side rail | 1 | 8×50×240 mm | same |
|
||||
| 19 | Right side rail | 1 | 8×50×240 mm | same |
|
||||
|
||||
> **DXF export:** `RENDER="rail_2d"` gives the 8×50 mm cross-section. Extrude to required length in CAM software.
|
||||
|
||||
### Fasteners
|
||||
|
||||
| # | Spec | Qty | Use |
|
||||
|---|------|-----|-----|
|
||||
| 20 | M4×16 SHCS | 16 | Rail to corner bracket (2 heights × 2 rails per corner = 4 per corner × 4) |
|
||||
| 21 | M4×16 SHCS | 8 | Rail to side bracket (2 per side bracket × 4) |
|
||||
| 22 | M3×8 set screw | 16 | Bracket lock to plate edge (2 per clip × 8 clips) |
|
||||
| 23 | M4 hex nut | 24 | All M4 rail bolts |
|
||||
|
||||
### Installation — clip orientation
|
||||
|
||||
```
|
||||
TOP VIEW of base plate corner:
|
||||
|
||||
plate edge (Y direction)
|
||||
│
|
||||
───────────┤
|
||||
│◄── corner_bracket arm sits here, M3 set screw bites plate edge
|
||||
plate │
|
||||
───────────┘
|
||||
↑
|
||||
plate edge (X direction)
|
||||
```
|
||||
|
||||
The clip slot engages the BOTTOM face of the plate. Slide bracket onto corner from below; tighten M3 set screws finger-tight → hand-tight.
|
||||
|
||||
---
|
||||
|
||||
## Part C — Stem Protector (`stem_protector.scad`)
|
||||
|
||||
### Printed parts
|
||||
|
||||
| # | RENDER | Qty | Material | Notes |
|
||||
|---|--------|-----|----------|-------|
|
||||
| 24 | `front` | 3–4 | TPU 95A | Flat-face-down; qty = no. of stem zones to protect |
|
||||
| 25 | `rear` | 3–4 | TPU 95A | Mirror; snap together around stem |
|
||||
|
||||
### Recommended placement (25 mm stem, ~1000 mm total stem)
|
||||
|
||||
| Position | Z from base plate | Purpose |
|
||||
|----------|-----------------|---------|
|
||||
| Sleeve 1 | 50–130 mm | Lowest zone — hit first in a side-fall |
|
||||
| Sleeve 2 | 300–380 mm | Mid stem (near battery carousel) |
|
||||
| Sleeve 3 | 600–680 mm | Upper stem |
|
||||
| Sleeve 4 | 850–930 mm | Below sensor head collar |
|
||||
|
||||
### No fasteners required
|
||||
Sleeves snap closed around stem. For extra security on smooth aluminium stem: wrap one loop of 25 mm Velcro strap over each sleeve before snapping closed.
|
||||
|
||||
---
|
||||
|
||||
## Tools Required for Assembly
|
||||
|
||||
- M2.5 / M3 / M4 hex drivers
|
||||
- Torque wrench: M4 cage-collar bolts 2 N·m, M3 set screws 0.8 N·m
|
||||
- Thread locker (Loctite 243 blue) on cage crown M4 bolts
|
||||
|
||||
---
|
||||
|
||||
## Mass Estimate
|
||||
|
||||
| Part | Material | Est. mass |
|
||||
|------|----------|-----------|
|
||||
| Roll cage collar (×2 halves) | PETG | ~60 g |
|
||||
| Roll cage posts (×4) | PETG | ~80 g |
|
||||
| Crown ring | PETG | ~45 g |
|
||||
| TPU crown pad | TPU | ~15 g |
|
||||
| Base bumper (×4 corners + ×4 sides) | PETG | ~160 g |
|
||||
| Base bumper TPU caps | TPU | ~40 g |
|
||||
| Stem sleeves (×8 halves) | TPU | ~60 g |
|
||||
| Fasteners | SS | ~40 g |
|
||||
| **Total** | | **~500 g** |
|
||||
|
||||
> **Balance note:** All protection hardware is symmetric about the robot's fore-aft axis. Roll cage is symmetric about all axes. Net CG shift is negligible (<5 mm vertical, <2 mm lateral).
|
||||
260
chassis/bumper_frame.scad
Normal file
260
chassis/bumper_frame.scad
Normal file
@ -0,0 +1,260 @@
|
||||
// ============================================================
|
||||
// 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();
|
||||
}
|
||||
119
chassis/stem_protector.scad
Normal file
119
chassis/stem_protector.scad
Normal file
@ -0,0 +1,119 @@
|
||||
// ============================================================
|
||||
// stem_protector.scad — Clip-on Stem Sleeve Rev A 2026-03-01
|
||||
// Agent: sl-mechanical
|
||||
// ============================================================
|
||||
// Split-sleeve impact protector for the 25 mm OD vertical stem.
|
||||
// Print in TPU (Shore 95A) for energy absorption.
|
||||
// Snap-fit closure — no tools, no hardware.
|
||||
// Install 3–4 sleeves along the stem at 200 mm spacing.
|
||||
//
|
||||
// RENDER options:
|
||||
// "assembly" both halves closed around stem (default)
|
||||
// "front" front half for slicing
|
||||
// "rear" rear half for slicing
|
||||
// ============================================================
|
||||
|
||||
RENDER = "assembly";
|
||||
|
||||
// ── Stem ─────────────────────────────────────────────────────
|
||||
STEM_OD = 25.0;
|
||||
STEM_BORE = 25.6; // +0.6 mm — TPU flexible, snaps closed
|
||||
|
||||
// ── Sleeve ───────────────────────────────────────────────────
|
||||
SLEEVE_OD = 38.0; // outer diameter
|
||||
SLEEVE_H = 80.0; // height (print and trim to taste)
|
||||
|
||||
WALL = (SLEEVE_OD - STEM_BORE) / 2; // ~6.2 mm wall
|
||||
|
||||
// ── Snap-fit ─────────────────────────────────────────────────
|
||||
// Tab on front half snaps into recess on rear half.
|
||||
// Placed at both Z = SLEEVE_H*0.25 and Z = SLEEVE_H*0.75.
|
||||
SNAP_W = 8.0; // snap tab width
|
||||
SNAP_H = 3.0; // snap tab height (protrusion)
|
||||
SNAP_T = 2.5; // snap tab thickness
|
||||
SNAP_CL = 0.3; // recess clearance for TPU flex
|
||||
|
||||
// ── Ribbing ──────────────────────────────────────────────────
|
||||
// Optional radial ribs for grip and stiffness
|
||||
RIB_N = 8; // number of ribs around circumference
|
||||
RIB_H = 1.2; // rib height
|
||||
RIB_W = 2.0; // rib width
|
||||
|
||||
$fn = 64;
|
||||
e = 0.01;
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// sleeve_half(side)
|
||||
// side = "front" (+Y) | "rear" (-Y)
|
||||
// Split at Y=0 plane. Print flat-face-down.
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
module sleeve_half(side = "front") {
|
||||
y_front = (side == "front");
|
||||
y_sign = y_front ? 1 : -1;
|
||||
|
||||
difference() {
|
||||
union() {
|
||||
// Main D-shaped half cylinder
|
||||
intersection() {
|
||||
cylinder(d = SLEEVE_OD, h = SLEEVE_H);
|
||||
translate([-SLEEVE_OD/2, y_front ? 0 : -SLEEVE_OD/2, 0])
|
||||
cube([SLEEVE_OD, SLEEVE_OD/2, SLEEVE_H]);
|
||||
}
|
||||
|
||||
// Radial grip ribs on outer curved face
|
||||
for (i = [0 : RIB_N/2 - 1]) {
|
||||
ang = (i / (RIB_N/2 - 1)) * 150 - 75; // spread 150° on each half
|
||||
rotate([0, 0, y_sign * ang + (y_front ? 0 : 180)])
|
||||
translate([SLEEVE_OD/2, -RIB_W/2, 0])
|
||||
cube([RIB_H, RIB_W, SLEEVE_H]);
|
||||
}
|
||||
|
||||
// Snap tab (front half) — 2 positions
|
||||
if (y_front) {
|
||||
for (zh = [SLEEVE_H * 0.25, SLEEVE_H * 0.75]) {
|
||||
translate([-SNAP_W/2, SLEEVE_OD/2 - SNAP_T, zh - SNAP_H/2])
|
||||
cube([SNAP_W, SNAP_T + SNAP_H, SNAP_H]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stem bore
|
||||
translate([0, 0, -e])
|
||||
cylinder(d = STEM_BORE, h = SLEEVE_H + 2*e);
|
||||
|
||||
// Snap tab recess in rear half
|
||||
if (!y_front) {
|
||||
for (zh = [SLEEVE_H * 0.25, SLEEVE_H * 0.75]) {
|
||||
translate([-(SNAP_W + SNAP_CL)/2,
|
||||
SLEEVE_OD/2 - (SNAP_T + SNAP_H + SNAP_CL + e),
|
||||
zh - (SNAP_H + SNAP_CL)/2])
|
||||
cube([SNAP_W + SNAP_CL, SNAP_H + SNAP_CL + e, SNAP_H + SNAP_CL]);
|
||||
}
|
||||
}
|
||||
|
||||
// Mating face: very small 0.2 mm chamfer to prevent elephant-foot binding
|
||||
translate([0, 0, -e])
|
||||
rotate([0, 0, y_front ? 0 : 180])
|
||||
translate([-SLEEVE_OD/2, -0.2, 0])
|
||||
cube([SLEEVE_OD, 0.2, SLEEVE_H + 2*e]);
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Render selector
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
if (RENDER == "assembly") {
|
||||
color("OrangeRed", 0.85) sleeve_half("front");
|
||||
color("Tomato", 0.85) mirror([0,1,0]) sleeve_half("rear");
|
||||
|
||||
// Reference stem
|
||||
color("Silver", 0.3)
|
||||
translate([0, 0, -10])
|
||||
cylinder(d = STEM_OD, h = SLEEVE_H + 20);
|
||||
|
||||
} else if (RENDER == "front") {
|
||||
sleeve_half("front");
|
||||
|
||||
} else if (RENDER == "rear") {
|
||||
sleeve_half("rear");
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user