feat: sensor head mounts — RPLIDAR, RealSense D435i, 4× IMX219 #48

Merged
seb merged 1 commits from sl-mechanical/sensor-mounts into main 2026-02-28 22:58:43 -05:00
5 changed files with 612 additions and 0 deletions
Showing only changes of commit 23f2daa3cd - Show all commits

104
chassis/imx219_mount.scad Normal file
View File

@ -0,0 +1,104 @@
// ============================================================
// imx219_mount.scad IMX219 Camera Radial Arm Rev A
// Agent: sl-mechanical 2026-02-28
// ============================================================
// Radial arm bracket for 32×32 mm IMX219 camera PCB.
// 4× arms at 0/90/180/270° around the sensor_head platform.
// Camera face tilted 10° nose-down. CSI ribbon slot along arm.
//
// Print: one arm at a time, flat-side-down no supports.
//
// RENDER options:
// "arm" single arm for slicing (default)
// "assembly" all 4 arms positioned around origin
// ============================================================
RENDER = "arm";
// Camera PCB (IMX219, 32×32 mm)
PCB_SIZE = 32.0; // PCB square side
// M2 hole pattern: 24×24 mm square (±12 mm from centre)
M2_SPACING = 24.0;
M2_D = 2.4; // M2 clearance hole
// Arm geometry
// Arm is centred on Y=0, extends in +X direction from origin.
// Base attachment holes near X=0; camera face plate at X=REACH_LEN.
ARM_W = 26.0; // arm width (Y direction, centred)
ARM_H = 5.0; // arm thickness (Z direction)
// Must match sensor_head.scad ARM_R and ARM_DY_CAM
ARM_R = 50.0; // platform attachment radius (used in assembly view)
ARM_DY = 9.0; // half-spacing of M4 base attachment holes (Y axis)
ARM_BOLT_X = 8.0; // base bolt CL from arm base (X)
REACH_LEN = 52.0; // arm extension (X)
FACE_SZ = PCB_SIZE + 6.0; // camera face plate side (38 mm)
FACE_THICK = 4.0; // face plate thickness (X)
TILT = 10.0; // nose-down tilt angle (degrees)
// CSI ribbon slot runs along bottom face of arm
CSI_W = 16.0; // ribbon clearance width
CSI_H = 2.0; // slot depth
// Fasteners
M4_D = 4.5; // M4 clearance hole
$fn = 64;
e = 0.01;
//
// imx219_arm()
// Arm centred on Y=0. Base at X=0, tip at X=REACH_LEN.
//
module imx219_arm() {
difference() {
union() {
// Arm body centred on Y axis
translate([0, -ARM_W/2, 0])
cube([REACH_LEN, ARM_W, ARM_H]);
// Camera face plate at tip, tilted TILT° nose-down
translate([REACH_LEN, 0, ARM_H/2])
rotate([0, TILT, 0])
translate([0, -FACE_SZ/2, -FACE_SZ/2])
cube([FACE_THICK, FACE_SZ, FACE_SZ]);
}
// 2× M4 base attachment holes
for (s = [-1, 1])
translate([ARM_BOLT_X, s * ARM_DY, -e])
cylinder(d = M4_D, h = ARM_H + 2*e);
// CSI ribbon slot bottom face of arm
translate([REACH_LEN * 0.2, -CSI_W/2, -e])
cube([REACH_LEN * 0.6, CSI_W, CSI_H + e]);
// 4× M2 camera PCB holes on face plate
translate([REACH_LEN, 0, ARM_H/2])
rotate([0, TILT, 0])
for (dy = [-M2_SPACING/2, M2_SPACING/2])
for (dz = [-M2_SPACING/2, M2_SPACING/2])
translate([-e, dy, dz])
rotate([0, 90, 0])
cylinder(d = M2_D, h = FACE_THICK + 2*e);
}
}
//
// Render selector
//
if (RENDER == "arm") {
imx219_arm();
} else if (RENDER == "assembly") {
// 4 arms at 90° intervals, base at ARM_R from stem CL
for (a = [0, 90, 180, 270])
rotate([0, 0, a])
translate([ARM_R, 0, 0])
color("SkyBlue", 0.9)
imx219_arm();
}

View File

@ -0,0 +1,114 @@
// ============================================================
// realsense_mount.scad RealSense D435i Bracket Rev A
// Agent: sl-mechanical 2026-02-28
// ============================================================
// Single-piece bracket. Bolts to sensor_head platform front
// face via 2× M4 bolts. Holds D435i at 10° nose-down tilt.
//
// RealSense D435i specs used:
// Body: 90 × 25 × 25 mm (W × H × D)
// Mount: 1/4-20 UNC female (bottom centre)
//
// Print orientation: flat base face on build plate no supports.
//
// RENDER options:
// "bracket" print-ready single piece (default)
// "assembly" bracket + camera outline
// ============================================================
RENDER = "bracket";
// Camera body
CAM_W = 90.0; // camera width
CAM_H = 25.0; // camera height
CAM_D = 25.0; // camera depth
// Bracket geometry
BASE_W = 40.0; // base plate width (Y direction)
BASE_D = 20.0; // base plate depth (X direction)
BASE_H = 8.0; // base plate thickness (Z direction)
ARM_W = 24.0; // arm width
ARM_H = 8.0; // arm thickness
ARM_LEN = 75.0; // arm length (X direction from base edge)
FACE_W = 30.0; // camera face plate width
FACE_D = 8.0; // face plate thickness (holds camera)
TILT = 10.0; // nose-down tilt (degrees)
// Fasteners
M4_D = 4.5; // M4 clearance hole
M4_DY = 18.0; // half-spacing of base attachment holes
// must match sensor_head ARM_DY_RS
// 1/4-20 UNC: tap drill 5.5 mm.
// We print a nut pocket for a 1/4-20 hex nut captured in the face.
NUT1420_AF = 11.1; // 1/4-20 hex nut across-flats (7/16")
NUT1420_H = 5.6; // 1/4-20 hex nut thickness (7/32")
NUT1420_BORE= 6.5; // 1/4-20 bolt clearance through face
NUT_POCKET_EXTRA = 0.4; // print clearance on nut pocket
$fn = 64;
e = 0.01;
//
module realsense_bracket() {
union() {
// Base plate (attaches to platform top)
difference() {
translate([-BASE_D, -BASE_W/2, 0])
cube([BASE_D, BASE_W, BASE_H]);
// 2× M4 holes through base, ±ARM_DY_RS
for (dy = [-M4_DY, M4_DY])
translate([-BASE_D/2, dy, -e])
cylinder(d = M4_D, h = BASE_H + 2*e);
}
// Arm extending in +X direction
difference() {
hull() {
translate([0, -ARM_W/2, 0]) cube([e, ARM_W, ARM_H]);
translate([ARM_LEN,-ARM_W/2, 0]) cube([e, ARM_W, ARM_H]);
}
// No internal cuts here arm is solid for rigidity
}
// Camera face plate at arm tip, tilted TILT° down
translate([ARM_LEN, 0, ARM_H/2])
rotate([0, TILT, 0])
translate([0, 0, -ARM_H/2])
difference() {
translate([0, -FACE_W/2, 0])
cube([FACE_D, FACE_W, CAM_H + 6]);
// 1/4-20 hex nut pocket (captured, opens toward +X)
translate([FACE_D - NUT1420_H, 0, (CAM_H + 6)/2])
rotate([0, 90, 0])
cylinder(d = (NUT1420_AF + NUT_POCKET_EXTRA) / cos(30),
h = NUT1420_H + e, $fn = 6);
// 1/4-20 bore through full face depth
translate([-e, 0, (CAM_H + 6)/2])
rotate([0, 90, 0])
cylinder(d = NUT1420_BORE, h = FACE_D + 2*e);
}
}
}
//
// Render selector
//
if (RENDER == "bracket") {
realsense_bracket();
} else if (RENDER == "assembly") {
color("LightGray", 0.9) realsense_bracket();
// Camera body outline
translate([ARM_LEN, 0, ARM_H/2])
rotate([0, TILT, 0])
translate([FACE_D, -CAM_W/2, -(CAM_H/2 + 3)])
color("DimGray", 0.6)
cube([CAM_D, CAM_W, CAM_H]);
}

View File

@ -0,0 +1,76 @@
// ============================================================
// rplidar_mount.scad RPLIDAR A1M8 Anti-Vibration Ring Rev A
// Agent: sl-mechanical 2026-02-28
// ============================================================
// Flat ring sits between platform and RPLIDAR A1M8.
// Anti-vibration isolation via 4× M3 silicone grommets
// (same type as FC vibration mounts Ø6 mm silicone, M3).
//
// Bolt stack (bottom top):
// M3×30 SHCS platform (8 mm) grommet (8 mm)
// ring (4 mm) RPLIDAR bottom (threaded M3, ~6 mm engagement)
//
// RENDER options:
// "ring" print-ready flat ring (default)
// "assembly" ring in position on platform stub
// ============================================================
RENDER = "ring";
// RPLIDAR A1M8
RPL_BODY_D = 70.0; // body diameter
RPL_BC = 58.0; // M3 mounting bolt circle
// Mount ring
RING_OD = 82.0; // outer diameter (RPL_BODY_D + 12 mm)
RING_ID = 30.0; // inner cutout (cable / airflow)
RING_H = 4.0; // ring thickness
BOLT_D = 3.3; // M3 clearance through-hole
GROMMET_D = 7.0; // silicone grommet OD (seat recess on bottom)
GROMMET_H = 1.0; // seating recess depth
$fn = 64;
e = 0.01;
//
module rplidar_ring() {
difference() {
cylinder(d = RING_OD, h = RING_H);
// Central cutout
translate([0, 0, -e])
cylinder(d = RING_ID, h = RING_H + 2*e);
// 4× M3 clearance holes on bolt circle
for (a = [45, 135, 225, 315]) {
translate([RPL_BC/2 * cos(a), RPL_BC/2 * sin(a), -e])
cylinder(d = BOLT_D, h = RING_H + 2*e);
}
// Grommet seating recesses bottom face
for (a = [45, 135, 225, 315]) {
translate([RPL_BC/2 * cos(a), RPL_BC/2 * sin(a), -e])
cylinder(d = GROMMET_D, h = GROMMET_H + e);
}
}
}
//
// Render selector
//
if (RENDER == "ring") {
rplidar_ring();
} else if (RENDER == "assembly") {
// Platform stub
color("Silver", 0.5)
difference() {
cylinder(d = 90, h = 8);
translate([0, 0, -e]) cylinder(d = 25.4, h = 8 + 2*e);
}
// Ring floating 8 mm above (grommet gap)
color("SkyBlue", 0.9)
translate([0, 0, 8 + 8])
rplidar_ring();
}

161
chassis/sensor_head.scad Normal file
View File

@ -0,0 +1,161 @@
// ============================================================
// 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();
}

View File

@ -0,0 +1,157 @@
# SaltyBot Sensor Head — Assembly Diagram
**Rev A — 2026-02-28 — sl-mechanical**
---
## Overview
The sensor head mounts on top of the 25 mm OD vertical stem.
Five printed/machined parts stack on a split collar platform.
---
## TOP VIEW (schematic)
```
FRONT (+X)
[IMX219 arm] | [IMX219 arm]
90° | 0°
↑ +---------+ ↑
| | | |
[IMX219]←----[•]---| RPLIDAR |---[•]----→[IMX219]
270° | | Ø70mm | | 90°
↓ | | ↓
+---------+
| PLAT |
| Ø120mm |
| (octag) |
+---------+
↑ | | ↑
[RS arm]---[•]---| COLLAR |---[•]
| Ø52mm |
+---------+
|
25mm stem
```
---
## SIDE VIEW (section through front, to scale)
```
Z
│ RPLIDAR A1M8 (Ø70 × 30 mm)
│ ┌────────────────────┐ ← Z ≈ 52 mm (top of RPLIDAR)
│ │ ██████████████ │
│ │ ██ A1M8 ██ │ ← Z ≈ 22 mm (bottom of RPLIDAR)
│ └────────────────────┘
│ │ │ │ │ ← M3×30 bolts (4 off)
│ ┌────┴────┴────┴────┐
│ │ rplidar_ring.scad│ ← Z = 14..18 mm (4 mm ring)
│ │ [silicone gromm]│ ← grommets 8 mm below ring
│ └───────────────────┘
│ ┌───────────────────┐
│ │ P L A T F O R M │ ← Z = 36..44 mm
│ │ (octag, Ø120mm) │
│ └───────────────────┘
│ │ COLLAR │
│ ┌────┴────────┴────┐
│ │ COLLAR FRONT │ ← Z = 0..36 mm
│ │ │ stem bore │ │
│ │ COLLAR REAR │
│ └──────────────────┘
│ │
│ 25 mm STEM
└─────────────────────────────────────── X
```
> **Note on Z references:** Z=0 is the bottom of the collar.
> Stem clamp bolts lock the sensor head at chosen height on the mast.
---
## IMX219 Arm (side view — one arm shown)
```
Platform edge Camera PCB
│ │
─────┤══════════════════════════════════════╗
│ ←── 52 mm arm ──────────────────► ║ IMX219
─────┤══════════════════════════════════════╝ ← 10° nose down
│ 2× M4 4× M2
│ base bolts PCB bolts
│ [slot: CSI ribbon through arm length]
```
---
## RealSense Arm (side view)
```
Platform edge
│ 2× M4
─────┤
│ Base ←── 75 mm arm ──────────────────►
─────┤ plate
│ (flat) ══════════════════════════════════╗
║ ← 10° nose down
║ D435i
║ 90×25×25
1/4-20 nut (captured)
```
---
## Assembly Sequence
| Step | Action |
|------|--------|
| 1 | Print 2× `collar_half` (flat-face-down; mirror one in slicer) |
| 2 | Print 1× `platform` |
| 3 | Print 1× `rplidar_ring` |
| 4 | Print 1× `realsense_bracket` |
| 5 | Print 4× `imx219_arm` (one per direction) |
| 6 | Seat 4× M3 silicone anti-vibration grommets in platform holes |
| 7 | Place rplidar_ring on grommets; pass 4× M3×30 SHCS from platform bottom up through ring |
| 8 | Set RPLIDAR A1M8 on ring; tighten M3×30 into RPLIDAR tapped holes (fingertight) |
| 9 | Thread 1/4-20 hex nut into realsense_bracket pocket; bolt D435i onto bracket |
| 10 | Bolt realsense_bracket to platform front with 2× M4×16 SHCS |
| 11 | Bolt 4× imx219_arm to platform with 2× M4×16 SHCS each |
| 12 | Snap IMX219 PCBs onto arm face plates with 4× M2×6 BHCS each |
| 13 | Route CSI ribbon cables through arm slots toward platform centre |
| 14 | Assemble collar halves around stem (flat faces mating); tighten M4×30 clamping bolts |
| 15 | Tighten M4 set screw in front collar half to lock height |
| 16 | Torque RPLIDAR M3 bolts: 0.5 N·m |
---
## Fastener Summary
| Qty | Size | Use |
|-----|------|-----|
| 2 | M4×30 SHCS | Collar clamping bolts |
| 1 | M4×10 set screw | Collar height / rotation lock |
| 2 | M4×16 SHCS | RealSense bracket to platform |
| 8 | M4×16 SHCS | IMX219 arms to platform (2 per arm) |
| 4 | M2×6 BHCS | IMX219 PCB to face plate (per arm; ×4 arms = 16 total) |
| 4 | M3×30 SHCS | RPLIDAR ring to platform (through grommet) |
| 4 | M3 anti-vibration grommet | Silicone, M3, Ø6 | Between platform and RPLIDAR ring |
| 1 | 1/4-20 hex nut | Captured in RealSense bracket |
| 2 | M4 hex nut | Captured in collar rear half |
| 8 | M4 flat washer | Under head of all M4 bolts |
---
## Print Settings
| Part | Material | Infill | Perimeters | Notes |
|------|----------|--------|-----------|-------|
| collar_half (×2) | PETG | 40% | 5 | Flat-face-down; print support-free |
| platform | PETG or PLA | 30% | 4 | Top-face-up |
| rplidar_ring | PETG | 30% | 3 | Flat |
| realsense_bracket | PETG | 35% | 4 | Base on bed; arm horizontal |
| imx219_arm (×4) | PETG or PLA | 30% | 3 | Flat, arm along X |