saltylab-firmware/chassis/sensor_head.scad
sl-mechanical 23f2daa3cd feat: sensor head mounts — stem collar, RPLIDAR, RealSense, 4× IMX219
Part of Phase 2 sensor mount designs for SaltyBot 25 mm mast.

Files added:
  chassis/sensor_head.scad        — split collar (25 mm OD stem) + octagonal platform
  chassis/rplidar_mount.scad      — anti-vibration ring for RPLIDAR A1M8 (Ø58 mm BC)
  chassis/realsense_mount.scad    — RealSense D435i arm bracket, 10° tilt, 1/4-20 nut
  chassis/imx219_mount.scad       — 4× IMX219 radial arms, 10° tilt, CSI ribbon slot
  chassis/sensor_head_assembly.md — assembly diagram + fastener BOM + print settings

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 22:53:18 -05:00

162 lines
7.9 KiB
OpenSCAD
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ============================================================
// sensor_head.scad — Sensor Head Platform Rev A 2026-02-28
// Agent: sl-mechanical
// ============================================================
// Clamps to 25 mm OD vertical stem (top of mast).
// Platform hosts:
// • RPLIDAR A1M8 — centred, topmost
// • RealSense D435i — front arm, 10° down tilt
// • 4× IMX219 cameras — radial arms, 90° apart, 10° down
//
// RENDER options:
// "assembly" 3-D exploded-ish view (default)
// "collar_front" front collar half for slicing (print 2)
// "collar_rear" rear collar half for slicing
// "platform_2d" flat projection → DXF
// ============================================================
RENDER = "assembly";
// ── Stem ─────────────────────────────────────────────────────
STEM_OD = 25.0; // actual stem OD (mm)
STEM_BORE = 25.4; // collar bore (+ 0.4 mm clearance for FDM)
// ── Collar ───────────────────────────────────────────────────
COL_OD = 52.0; // outer diameter
COL_H = 36.0; // height
COL_BOLT_X = 19.0; // M4 clamping bolt CL from stem axis
COL_BOLT_D = 4.5; // M4 clearance hole
COL_NUT_W = 7.0; // M4 hex nut A/F
COL_NUT_H = 3.4; // M4 hex nut thickness
// ── Platform ─────────────────────────────────────────────────
PLAT_OD = 120.0; // octagonal platform, across-flats ≈ OD
PLAT_H = 8.0; // plate thickness
PLAT_Z = COL_H; // platform bottom sits atop collar
// ── RPLIDAR bolt circle ───────────────────────────────────────
RPL_BC = 58.0; // M3 hole bolt circle diameter
RPL_HOLE_D = 3.3; // M3 clearance
RPL_RECESS_D = 72.0; // alignment lip diameter (body + 2 mm clearance)
RPL_RECESS_H = 1.5; // recess depth
// ── Sensor arm attachment holes ───────────────────────────────
// RealSense arm: 2× M4 holes at X = +ARM_R, Y = ±ARM_DY_RS
ARM_R = 50.0; // radial distance from stem CL
ARM_DY_RS = 18.0; // half-spacing for RealSense pair
// IMX219 arms: 4 × 2 M4 holes at each 90° cardinal, tangential offset ±ARM_DY_CAM
ARM_DY_CAM = 9.0; // half-spacing for IMX219 pair
ARM_HOLE_D = 4.5; // M4 clearance
$fn = 64;
e = 0.01;
// ─────────────────────────────────────────────────────────────
// collar_half(side)
// side = "front" (+Y) | "rear" (-Y)
// Print flat-face-down (mating face on build plate).
// ─────────────────────────────────────────────────────────────
module collar_half(side = "front") {
difference() {
// ── Body: D-shaped half-cylinder ─────────────────────
intersection() {
cylinder(d = COL_OD, h = COL_H);
translate([-COL_OD/2, (side == "front") ? 0 : -COL_OD/2, 0])
cube([COL_OD, COL_OD/2, COL_H]);
}
// ── Stem bore ────────────────────────────────────────
translate([0, 0, -e])
cylinder(d = STEM_BORE, h = COL_H + 2*e);
// ── M4 clamping bolt clearance holes (Y direction) ───
for (bx = [-COL_BOLT_X, COL_BOLT_X]) {
translate([bx, (side == "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 (rear half only) ──────────────
if (side == "rear") {
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 — height / rotation lock (front half) ─
if (side == "front") {
translate([0, COL_OD/2, COL_H * 0.72])
rotate([90, 0, 0])
cylinder(d = COL_BOLT_D, h = COL_OD/2 - STEM_BORE/2 + e);
}
}
}
// ─────────────────────────────────────────────────────────────
// platform()
// ─────────────────────────────────────────────────────────────
module platform() {
difference() {
// ── Octagonal disc ───────────────────────────────────
cylinder(d = PLAT_OD, h = PLAT_H, $fn = 8);
// ── Stem passage ─────────────────────────────────────
translate([0, 0, -e])
cylinder(d = STEM_BORE, h = PLAT_H + 2*e);
// ── RPLIDAR alignment recess (top face) ──────────────
translate([0, 0, PLAT_H - RPL_RECESS_H])
cylinder(d = RPL_RECESS_D, h = RPL_RECESS_H + e);
// ── RPLIDAR M3 bolt holes — 58 mm BC ─────────────────
for (a = [45, 135, 225, 315])
translate([RPL_BC/2 * cos(a), RPL_BC/2 * sin(a), -e])
cylinder(d = RPL_HOLE_D, h = PLAT_H + 2*e);
// ── RealSense arm M4 holes — front (+X), ±ARM_DY_RS ──
for (dy = [-ARM_DY_RS, ARM_DY_RS])
translate([ARM_R, dy, -e])
cylinder(d = ARM_HOLE_D, h = PLAT_H + 2*e);
// ── IMX219 arm M4 holes — 4 × cardinal, ±ARM_DY_CAM ─
// Tangential perpendicular for angle a: (sin(a), -cos(a))
for (a = [0, 90, 180, 270])
for (s = [-1, 1])
translate([
ARM_R * cos(a) + s * ARM_DY_CAM * sin(a),
ARM_R * sin(a) - s * ARM_DY_CAM * cos(a),
-e
])
cylinder(d = ARM_HOLE_D, h = PLAT_H + 2*e);
// ── Lightening pockets — between arm attachment holes ─
for (a = [45, 135, 225, 315])
translate([(PLAT_OD/2 - 22) * cos(a),
(PLAT_OD/2 - 22) * sin(a), -e])
cylinder(d = 20, h = PLAT_H + 2*e);
}
}
// ─────────────────────────────────────────────────────────────
// Render selector
// ─────────────────────────────────────────────────────────────
if (RENDER == "assembly") {
color("SkyBlue", 0.9) collar_half("front");
color("DodgerBlue", 0.9) mirror([0, 1, 0]) collar_half("rear");
color("Silver", 0.85) translate([0, 0, PLAT_Z]) platform();
} else if (RENDER == "collar_front") {
collar_half("front");
} else if (RENDER == "collar_rear") {
collar_half("rear");
} else if (RENDER == "platform_2d") {
projection(cut = true)
translate([0, 0, -(PLAT_Z + PLAT_H / 2)])
platform();
}