saltylab-firmware/chassis/rplidar_mount.scad

344 lines
14 KiB
OpenSCAD
Raw 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.

// ============================================================
// RPLIDAR A1 Mount Bracket — Issue #596
// Agent : sl-mechanical
// Date : 2026-03-14
// Part catalogue:
// 1. tnut_base — 2020 T-slot rail interface plate with M5 T-nut captive pockets
// 2. column — hollow elevation column, 120 mm tall, 3 stiffening ribs, cable bore
// 3. scan_platform — top plate with Ø40 mm BC M3 mounting pattern, vibration seats
// 4. vibe_ring — silicone FC-grommet isolation ring for scan_platform bolts
// 5. cable_guide — snap-on cable management clip for column body
//
// BOM:
// 2 × M5×10 BHCS + M5 T-nuts (tnut_base to rail)
// 4 × M3×8 SHCS (scan_platform to RPLIDAR A1)
// 4 × M3 silicone FC grommets Ø8.5 OD / Ø3.2 bore (anti-vibe)
// 4 × M3 hex nuts (captured in scan_platform)
//
// Print settings (PETG):
// tnut_base / column / scan_platform : 5 perimeters, 40 % gyroid, no supports
// vibe_ring : 3 perimeters, 20 % gyroid, no supports
// cable_guide : 3 perimeters, 30 % gyroid, no supports
//
// Export commands:
// openscad -D 'RENDER="tnut_base"' -o tnut_base.stl rplidar_mount.scad
// openscad -D 'RENDER="column"' -o column.stl rplidar_mount.scad
// openscad -D 'RENDER="scan_platform"' -o scan_platform.stl rplidar_mount.scad
// openscad -D 'RENDER="vibe_ring"' -o vibe_ring.stl rplidar_mount.scad
// openscad -D 'RENDER="cable_guide"' -o cable_guide.stl rplidar_mount.scad
// openscad -D 'RENDER="assembly"' -o assembly.png rplidar_mount.scad
// ============================================================
// ── Render selector ─────────────────────────────────────────
RENDER = "assembly"; // tnut_base | column | scan_platform | vibe_ring | cable_guide | assembly
// ── Global constants ────────────────────────────────────────
$fn = 64;
EPS = 0.01;
// 2020 rail
RAIL_W = 20.0; // extrusion cross-section
RAIL_H = 20.0;
SLOT_NECK_H = 3.2; // T-slot opening width
TNUT_W = 9.8; // M5 T-nut width
TNUT_H = 5.5; // T-nut height (depth into slot)
TNUT_L = 12.0; // T-nut body length
M5_D = 5.2; // M5 clearance bore
M5_HEAD_D = 9.5; // M5 BHCS head diameter
M5_HEAD_H = 4.0; // M5 BHCS head height
// Base plate
BASE_L = 60.0; // length along rail axis
BASE_W = 30.0; // width across rail
BASE_T = 8.0; // plate thickness
BOLT_PITCH = 40.0; // M5 bolt pitch along rail (centre-to-centre)
// Elevation column
COL_OD = 25.0; // column outer diameter
COL_ID = 17.0; // inner bore (cable routing)
ELEV_H = 120.0; // scan plane above rail top face
COL_WALL = (COL_OD - COL_ID) / 2;
RIB_W = 3.0; // stiffening rib width
RIB_H = 3.5; // rib radial height
CABLE_SLOT_W = 8.0; // cable entry slot width
CABLE_SLOT_H = 5.0; // cable entry slot height
// Scan platform
PLAT_D = 60.0; // platform disc diameter (clears RPLIDAR body Ø100 mm well)
PLAT_T = 6.0; // platform thickness
RPL_BC_D = 40.0; // RPLIDAR M3 bolt circle diameter (4 bolts at 45 °)
RPL_BORE_D = 36.0; // central pass-through for scan motor cable
M3_D = 3.2; // M3 clearance bore
M3_NUT_W = 5.5; // M3 hex nut across-flats
M3_NUT_H = 2.4; // M3 hex nut height
GROM_OD = 8.5; // FC silicone grommet OD
GROM_ID = 3.2; // grommet bore
GROM_H = 3.0; // grommet seat depth
CONN_SLOT_W = 12.0; // connector side-exit slot width
CONN_SLOT_H = 5.0; // connector slot height
// Vibe ring
VRING_OD = GROM_OD + 1.6; // printed retainer OD
VRING_ID = GROM_ID + 0.3; // pass-through with grommet seated
VRING_T = 2.0; // ring flange thickness
// Cable guide clip
CLIP_W = 14.0;
CLIP_T = 3.5;
CLIP_GAP = COL_OD + 0.4; // snap-fit gap (slight interference)
SNAP_T = 1.8;
CABLE_CH_W = 8.0;
CABLE_CH_H = 5.0;
// ── Utility modules ─────────────────────────────────────────
module chamfer_cube(size, ch=1.0) {
// simple chamfered box (bottom edge only for printability)
hull() {
translate([ch, ch, 0])
cube([size[0]-2*ch, size[1]-2*ch, EPS]);
translate([0, 0, ch])
cube(size - [0, 0, ch]);
}
}
module hex_pocket(af, depth) {
// hex nut pocket (flat-to-flat af)
cylinder(d = af / cos(30), h = depth, $fn = 6);
}
// ── Part 1: tnut_base ───────────────────────────────────────
module tnut_base() {
difference() {
// Body
union() {
chamfer_cube([BASE_L, BASE_W, BASE_T], ch=1.5);
// Column socket boss centred on plate top face
translate([BASE_L/2, BASE_W/2, BASE_T])
cylinder(d=COL_OD + 4.0, h=8.0);
}
// M5 bolt holes (counterbored for BHCS heads from underneath)
for (x = [BASE_L/2 - BOLT_PITCH/2, BASE_L/2 + BOLT_PITCH/2])
translate([x, BASE_W/2, -EPS]) {
cylinder(d=M5_D, h=BASE_T + 8.0 + 2*EPS);
// counterbore from bottom
cylinder(d=M5_HEAD_D, h=M5_HEAD_H + EPS);
}
// T-nut captive pockets (accessible from bottom)
for (x = [BASE_L/2 - BOLT_PITCH/2, BASE_L/2 + BOLT_PITCH/2])
translate([x - TNUT_L/2, BASE_W/2 - TNUT_W/2, BASE_T - TNUT_H])
cube([TNUT_L, TNUT_W, TNUT_H + EPS]);
// Column bore into boss
translate([BASE_L/2, BASE_W/2, BASE_T - EPS])
cylinder(d=COL_OD + 0.3, h=8.0 + 2*EPS);
// Cable exit slot through base (offset 5 mm from column centre)
translate([BASE_L/2 - CABLE_SLOT_W/2, BASE_W/2 + COL_OD/4, -EPS])
cube([CABLE_SLOT_W, CABLE_SLOT_H, BASE_T + 8.0 + 2*EPS]);
// Weight relief pockets on underside
for (x = [BASE_L/2 - BOLT_PITCH/2 + 10, BASE_L/2 + BOLT_PITCH/2 - 10])
for (y = [7, BASE_W - 7])
translate([x - 5, y - 5, -EPS])
cube([10, 10, BASE_T/2]);
}
}
// ── Part 2: column ──────────────────────────────────────────
module column() {
// Actual column height: ELEV_H minus base boss engagement (8 mm) and platform seating (6 mm)
col_h = ELEV_H - 8.0 - PLAT_T;
difference() {
union() {
// Hollow tube
cylinder(d=COL_OD, h=col_h);
// Three 120°-spaced stiffening ribs along full height
for (a = [0, 120, 240])
rotate([0, 0, a])
translate([COL_OD/2 - EPS, -RIB_W/2, 0])
cube([RIB_H, RIB_W, col_h]);
// Bottom spigot (fits into base boss bore)
translate([0, 0, -6.0])
cylinder(d=COL_OD - 0.4, h=6.0 + EPS);
// Top spigot (seats into scan_platform recess)
translate([0, 0, col_h - EPS])
cylinder(d=COL_OD - 0.4, h=6.0);
}
// Inner cable bore
translate([0, 0, -6.0 - EPS])
cylinder(d=COL_ID, h=col_h + 12.0 + 2*EPS);
// Cable entry slot at bottom (aligns with base slot)
translate([-CABLE_SLOT_W/2, -COL_OD/2 - EPS, 2.0])
cube([CABLE_SLOT_W, CABLE_SLOT_H + EPS, CABLE_SLOT_H]);
// Cable exit slot at top (90° rotated for tidy routing)
rotate([0, 0, 90])
translate([-CABLE_SLOT_W/2, -COL_OD/2 - EPS, col_h - CABLE_SLOT_H - 4.0])
cube([CABLE_SLOT_W, CABLE_SLOT_H + EPS, CABLE_SLOT_H]);
// Cable clip snap groove (at mid-height)
translate([0, 0, col_h / 2])
difference() {
cylinder(d=COL_OD + 2*RIB_H + 0.8, h=4.0, center=true);
cylinder(d=COL_OD - 0.2, h=4.0 + 2*EPS, center=true);
}
}
}
// ── Part 3: scan_platform ───────────────────────────────────
module scan_platform() {
difference() {
union() {
// Main disc
cylinder(d=PLAT_D, h=PLAT_T);
// Rim lip for stiffness
translate([0, 0, PLAT_T])
difference() {
cylinder(d=PLAT_D, h=2.0);
cylinder(d=PLAT_D - 4.0, h=2.0 + EPS);
}
}
// Central cable pass-through
translate([0, 0, -EPS])
cylinder(d=RPL_BORE_D, h=PLAT_T + 4.0);
// Column spigot socket (bottom recess)
translate([0, 0, -EPS])
cylinder(d=COL_OD - 0.4 + 0.4, h=6.0);
// RPLIDAR M3 mounting holes — 4× on Ø40 BC at 45°/135°/225°/315°
for (a = [45, 135, 225, 315])
rotate([0, 0, a])
translate([RPL_BC_D/2, 0, -EPS]) {
// Through bore
cylinder(d=M3_D, h=PLAT_T + 2*EPS);
// Grommet seat (countersunk from top)
translate([0, 0, PLAT_T - GROM_H])
cylinder(d=GROM_OD + 0.3, h=GROM_H + EPS);
// Captured M3 hex nut pocket (from bottom)
translate([0, 0, 1.5])
hex_pocket(M3_NUT_W + 0.3, M3_NUT_H + 0.2);
}
// Connector side-exit slots (2× opposing, at 0° and 180°)
for (a = [0, 180])
rotate([0, 0, a])
translate([-CONN_SLOT_W/2, PLAT_D/2 - CONN_SLOT_H, -EPS])
cube([CONN_SLOT_W, CONN_SLOT_H + EPS, PLAT_T + 2*EPS]);
// Weight relief pockets (2× lateral)
for (a = [90, 270])
rotate([0, 0, a])
translate([-10, 15, 1.5])
cube([20, 8, PLAT_T - 3.0]);
}
}
// ── Part 4: vibe_ring ───────────────────────────────────────
// Printed silicone-grommet retainer ring — press-fits over M3 bolt with grommet seated
module vibe_ring() {
difference() {
union() {
cylinder(d=VRING_OD, h=VRING_T + GROM_H);
// Flange
cylinder(d=VRING_OD + 2.0, h=VRING_T);
}
// Bore
translate([0, 0, -EPS])
cylinder(d=VRING_ID, h=VRING_T + GROM_H + 2*EPS);
}
}
// ── Part 5: cable_guide ─────────────────────────────────────
// Snap-on cable clip for column mid-section
module cable_guide() {
arm_t = SNAP_T;
gap = CLIP_GAP;
difference() {
union() {
// Saddle body (U-shape wrapping column)
difference() {
cylinder(d=gap + 2*CLIP_T, h=CLIP_W);
translate([0, 0, -EPS])
cylinder(d=gap, h=CLIP_W + 2*EPS);
// Open front slot for snap insertion
translate([-gap/2, 0, -EPS])
cube([gap, gap/2 + CLIP_T + EPS, CLIP_W + 2*EPS]);
}
// Snap arms
for (s = [-1, 1])
translate([s*(gap/2 - arm_t), 0, 0])
mirror([s < 0 ? 1 : 0, 0, 0])
translate([0, -arm_t/2, 0])
cube([arm_t + 1.5, arm_t, CLIP_W]);
// Cable channel bracket (side-mounted)
translate([gap/2 + CLIP_T, -(CABLE_CH_W/2 + CLIP_T), 0])
cube([CLIP_T + CABLE_CH_H, CABLE_CH_W + 2*CLIP_T, CLIP_W]);
}
// Cable channel cutout
translate([gap/2 + CLIP_T + CLIP_T - EPS, -CABLE_CH_W/2, -EPS])
cube([CABLE_CH_H + EPS, CABLE_CH_W, CLIP_W + 2*EPS]);
// Snap tip undercut (both arms)
for (s = [-1, 1])
translate([s*(gap/2 + CLIP_T + 1.0), -arm_t, -EPS])
rotate([0, 0, s*30])
cube([2, arm_t*2, CLIP_W + 2*EPS]);
}
}
// ── Assembly / render dispatch ───────────────────────────────
module assembly() {
// tnut_base at origin
color("SteelBlue")
tnut_base();
// column rising from base boss
color("DodgerBlue")
translate([BASE_L/2, BASE_W/2, BASE_T + 8.0 - 6.0])
column();
// scan_platform at top of column
col_h_actual = ELEV_H - 8.0 - PLAT_T;
color("CornflowerBlue")
translate([BASE_L/2, BASE_W/2, BASE_T + 8.0 - 6.0 + col_h_actual + 6.0 - EPS])
scan_platform();
// vibe rings (4×) seated in platform holes
for (a = [45, 135, 225, 315])
color("Gray", 0.7)
translate([BASE_L/2, BASE_W/2,
BASE_T + 8.0 - 6.0 + col_h_actual + 6.0 + PLAT_T - GROM_H])
rotate([0, 0, a])
translate([RPL_BC_D/2, 0, 0])
vibe_ring();
// cable_guide clipped at column mid-height
color("LightSteelBlue")
translate([BASE_L/2, BASE_W/2,
BASE_T + 8.0 - 6.0 + (ELEV_H - 8.0 - PLAT_T)/2 - CLIP_W/2])
cable_guide();
}
// ── Dispatch ────────────────────────────────────────────────
if (RENDER == "tnut_base") tnut_base();
else if (RENDER == "column") column();
else if (RENDER == "scan_platform") scan_platform();
else if (RENDER == "vibe_ring") vibe_ring();
else if (RENDER == "cable_guide") cable_guide();
else assembly();