Merge pull request 'feat(mechanical): universal sensor mount rail system (Issue #138)' (#151) from sl-mechanical/issue-138-sensor-rail into main
This commit is contained in:
commit
07b4edaa2d
564
chassis/sensor_rail.scad
Normal file
564
chassis/sensor_rail.scad
Normal file
@ -0,0 +1,564 @@
|
||||
// ============================================================
|
||||
// sensor_rail.scad — Universal Sensor Mount Rail System
|
||||
// Issue: #138 Agent: sl-mechanical Date: 2026-03-01
|
||||
// ============================================================
|
||||
//
|
||||
// T-slot 20×20 mm rail system for quick-swap sensor mounting
|
||||
// across all SaltyLab robot variants.
|
||||
//
|
||||
// Rail profile: compatible with OpenBuilds 2020 / MISUMI HFS5-2020
|
||||
// standard aluminium extrusion (off-the-shelf preferred).
|
||||
// Printable PETG sections provided for rapid prototyping only.
|
||||
//
|
||||
// Height indexing: M5 cross-holes every 25 mm along rail length.
|
||||
// Brackets can be positioned at any height (stepless) and locked
|
||||
// with a thumbscrew, or indexed to 25 mm grid by aligning to holes.
|
||||
//
|
||||
// Bracket retention: M3 thumbscrew (no tools, ¼ turn).
|
||||
// Each bracket has a printed T-nut in the rail T-groove.
|
||||
// Thumbscrew clamps T-nut against groove walls from outside.
|
||||
//
|
||||
// Cross-variant base adapters (this file):
|
||||
// • stem_adapter() — Ø25 mm stem (SaltyLab / SaltyRover mast)
|
||||
// • post_adapter() — square tube (SaltyRover vertical posts)
|
||||
// • tank_clamp() — flat plate clamp (SaltyTank uprights)
|
||||
//
|
||||
// Sensor brackets → see sensor_rail_brackets.scad
|
||||
//
|
||||
// Coordinate convention:
|
||||
// Rail runs along Z (vertical).
|
||||
// Rail cross-section in X-Y plane.
|
||||
// Front face (sensor side) faces +Y.
|
||||
// Z = 0 at rail bottom.
|
||||
//
|
||||
// RENDER options:
|
||||
// "assembly" full rail + adapters + bracket ghosts (default)
|
||||
// "rail_2d" DXF — rail profile cross-section (spec for extrusion)
|
||||
// "rail_section_stl" STL — printable rail section (prototype only)
|
||||
// "stem_adapter_stl" STL — Ø25 mm stem adapter (print 1×)
|
||||
// "post_adapter_stl" STL — square-tube post adapter (print 1×)
|
||||
// "tank_clamp_stl" STL — flat-plate tank upright clamp (print 1×)
|
||||
// "end_cap_stl" STL — rail end cap (print 2× per rail)
|
||||
// "index_pin_stl" STL — 25 mm index pin set (print 1 set)
|
||||
//
|
||||
// ── Export commands ─────────────────────────────────────────
|
||||
// Rail profile DXF (send to extrusion supplier):
|
||||
// openscad sensor_rail.scad -D 'RENDER="rail_2d"' -o sensor_rail_profile.dxf
|
||||
// Printable rail section STL (200 mm, prototype):
|
||||
// openscad sensor_rail.scad -D 'RENDER="rail_section_stl"' -o sensor_rail_200.stl
|
||||
// Stem adapter:
|
||||
// openscad sensor_rail.scad -D 'RENDER="stem_adapter_stl"' -o sensor_rail_stem_adapter.stl
|
||||
// Post adapter:
|
||||
// openscad sensor_rail.scad -D 'RENDER="post_adapter_stl"' -o sensor_rail_post_adapter.stl
|
||||
// Tank clamp:
|
||||
// openscad sensor_rail.scad -D 'RENDER="tank_clamp_stl"' -o sensor_rail_tank_clamp.stl
|
||||
// End cap:
|
||||
// openscad sensor_rail.scad -D 'RENDER="end_cap_stl"' -o sensor_rail_end_cap.stl
|
||||
// ============================================================
|
||||
|
||||
$fn = 64;
|
||||
e = 0.01;
|
||||
|
||||
// ── 2020 T-slot profile geometry ─────────────────────────────────────────────
|
||||
// Matches OpenBuilds V-Slot 2020 / MISUMI HFS5-2020 / standard 2020 T-slot.
|
||||
// ⚠ Do NOT modify these — they must match the aluminium extrusion you purchase.
|
||||
RAIL_W = 20.0; // outer width/height of profile
|
||||
SLOT_OPEN = 6.0; // T-groove opening width at outer face
|
||||
SLOT_INNER_W = 10.2; // T-groove inner width
|
||||
SLOT_INNER_H = 5.8; // T-groove inner height (depth)
|
||||
SLOT_NECK_H = 3.2; // distance from outer face to T-groove inner
|
||||
CENTRAL_BORE = 4.2; // central M5 bore diameter (tap drill)
|
||||
CORNER_NOTCH = 1.6; // corner chamfer radius (standard 2020)
|
||||
|
||||
// ── Rail section parameters ───────────────────────────────────────────────────
|
||||
RAIL_LEN = 200.0; // default section length (printable prototype)
|
||||
// For aluminium: order in 200 / 250 / 300 mm lengths as needed.
|
||||
|
||||
// ── Index holes ───────────────────────────────────────────────────────────────
|
||||
INDEX_PITCH = 25.0; // index hole spacing (mm) — height adjustment grid
|
||||
INDEX_HOLE_D = 5.3; // M5 clearance through rail (perpendicular to Z)
|
||||
// Index holes are on the LEFT and RIGHT faces of the rail (±X faces),
|
||||
// perpendicular to Z (rail axis). Bracket T-nut has an M5 registration
|
||||
// peg that drops into these holes for repeatable indexed positioning.
|
||||
|
||||
// ── Printable T-nut (used by sensor_rail_brackets.scad) ──────────────────────
|
||||
// These dims define the T-nut that slides in the SLOT_INNER T-groove.
|
||||
TNUT_W = SLOT_INNER_W - 0.4; // 9.8 mm — 0.4 clearance per side
|
||||
TNUT_H = SLOT_INNER_H - 0.3; // 5.5 mm
|
||||
TNUT_L = 12.0; // T-nut body length
|
||||
TNUT_M3_NUT_AF = 5.5; // M3 hex nut across-flats (DIN 934)
|
||||
TNUT_M3_NUT_H = 2.5; // M3 hex nut thickness
|
||||
TNUT_BOLT_D = 3.3; // M3 clearance bore through T-nut
|
||||
|
||||
// ── Thumbscrew (M3 × 16 SHCS + printed thumbwheel) ───────────────────────────
|
||||
THUMB_D = 16.0; // thumbwheel OD
|
||||
THUMB_H = 8.0; // thumbwheel height
|
||||
THUMB_KNURL = 12; // number of knurl ridges on thumbwheel
|
||||
|
||||
// ── Stem adapter (Ø25 mm — SaltyLab / SaltyRover mast) ───────────────────────
|
||||
STEM_OD = 25.0; // SaltyLab / SaltyRover stem OD
|
||||
STEM_BORE = 25.4; // collar bore with clearance
|
||||
STEM_COL_OD = 46.0; // collar outer diameter
|
||||
STEM_COL_H = 40.0; // collar height
|
||||
STEM_BOLT_X = 17.0; // M4 clamping bolt CL from stem axis
|
||||
STEM_RAIL_W = 60.0; // rail-mounting flange width
|
||||
STEM_RAIL_H = 30.0; // rail-mounting flange height
|
||||
|
||||
// ── Post adapter (square tube, SaltyRover frame posts) ───────────────────────
|
||||
POST_W = 20.0; // square tube OD (20×20 mm aluminium post)
|
||||
POST_WALL = 2.0; // tube wall thickness
|
||||
POST_CLAMP_T = 4.0; // clamp plate thickness
|
||||
POST_CLAMP_W = 50.0; // clamp plate width
|
||||
POST_CLAMP_H = 60.0; // clamp plate height
|
||||
|
||||
// ── Tank clamp (flat plate, SaltyTank side frame uprights) ───────────────────
|
||||
TANK_PLATE_T = 6.0; // SaltyTank side frame plate thickness (matches FRAME_T)
|
||||
TANK_CLAMP_W = 50.0; // clamp body width
|
||||
TANK_CLAMP_H = 60.0; // clamp body height
|
||||
TANK_BOLT_SPC= 30.0; // M4 bolt spacing for clamp-to-frame attachment
|
||||
|
||||
// ── Fasteners ─────────────────────────────────────────────────────────────────
|
||||
M3_D = 3.3;
|
||||
M4_D = 4.3;
|
||||
M5_D = 5.3;
|
||||
|
||||
// ============================================================
|
||||
// RENDER DISPATCH
|
||||
// ============================================================
|
||||
RENDER = "assembly";
|
||||
|
||||
if (RENDER == "assembly") {
|
||||
assembly();
|
||||
} else if (RENDER == "rail_2d") {
|
||||
projection(cut = true)
|
||||
translate([0, 0, -RAIL_W/2])
|
||||
rotate([90, 0, 0])
|
||||
rail_section(RAIL_W);
|
||||
} else if (RENDER == "rail_section_stl") {
|
||||
rail_section(RAIL_LEN);
|
||||
} else if (RENDER == "stem_adapter_stl") {
|
||||
stem_adapter();
|
||||
} else if (RENDER == "post_adapter_stl") {
|
||||
post_adapter();
|
||||
} else if (RENDER == "tank_clamp_stl") {
|
||||
tank_clamp();
|
||||
} else if (RENDER == "end_cap_stl") {
|
||||
rail_end_cap();
|
||||
} else if (RENDER == "index_pin_stl") {
|
||||
index_pin_set();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// ASSEMBLY PREVIEW
|
||||
// ============================================================
|
||||
module assembly() {
|
||||
// Rail section (200 mm, vertical)
|
||||
color("Silver", 0.85) rail_section(200);
|
||||
|
||||
// Stem adapter at base
|
||||
color("SteelBlue", 0.80)
|
||||
translate([0, 0, -STEM_COL_H - 10])
|
||||
stem_adapter();
|
||||
|
||||
// End cap at top
|
||||
color("DimGray", 0.80)
|
||||
translate([0, 0, 200])
|
||||
rail_end_cap();
|
||||
|
||||
// Ghost sensor brackets (from sensor_rail_brackets.scad)
|
||||
// RPLIDAR at top
|
||||
%color("OliveDrab", 0.3)
|
||||
translate([0, RAIL_W/2 + 5, 170])
|
||||
cube([90, 20, 60], center = true);
|
||||
|
||||
// D435i at middle
|
||||
%color("DarkSlateGray", 0.3)
|
||||
translate([0, RAIL_W/2 + 5, 100])
|
||||
cube([95, 20, 30], center = true);
|
||||
|
||||
// IMX219 at lower
|
||||
%color("Teal", 0.3)
|
||||
translate([0, RAIL_W/2 + 5, 50])
|
||||
cube([40, 20, 40], center = true);
|
||||
|
||||
// Index hole markers
|
||||
for (z = [0 : INDEX_PITCH : 200])
|
||||
%color("Red", 0.4)
|
||||
translate([RAIL_W/2 + 2, 0, z])
|
||||
rotate([0, 90, 0])
|
||||
cylinder(d = 3, h = 4);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// RAIL SECTION (Part A — aluminium extrusion or PETG print)
|
||||
// ============================================================
|
||||
// Standard 2020 T-slot profile extruded along Z axis.
|
||||
// This OpenSCAD module is primarily for:
|
||||
// 1. DXF export to spec the extrusion cross-section for a supplier
|
||||
// 2. Printable prototype sections (print in PETG, 5 perims, 60% infill)
|
||||
//
|
||||
// For production: purchase OpenBuilds V-Slot 2020 in desired length.
|
||||
// Search: "2020 V-slot aluminium extrusion" or "2020 T-slot rail"
|
||||
// Cut to length with a mitre saw. Tap central M5 bore at ends for
|
||||
// end-mounting and rail-to-adapter connections.
|
||||
//
|
||||
// Index holes: drilled/tapped M5 on LEFT and RIGHT faces (±X)
|
||||
// at 25 mm pitch. Specify when ordering pre-drilled extrusion,
|
||||
// or drill manually after cutting.
|
||||
module rail_section(length = RAIL_LEN) {
|
||||
difference() {
|
||||
// ── Extrude the 2020 profile ─────────────────────────────────
|
||||
linear_extrude(length)
|
||||
tslot_profile_2d();
|
||||
|
||||
// ── Central bore (M5 tap drill, both ends) ────────────────────
|
||||
translate([0, 0, -e])
|
||||
cylinder(d = CENTRAL_BORE, h = length + 2*e);
|
||||
|
||||
// ── Index holes (M5 clearance, ±X faces, every 25 mm) ─────────
|
||||
for (z = [INDEX_PITCH/2 : INDEX_PITCH : length - INDEX_PITCH/2])
|
||||
translate([-RAIL_W/2 - e, 0, z])
|
||||
rotate([0, 90, 0])
|
||||
cylinder(d = INDEX_HOLE_D, h = RAIL_W + 2*e);
|
||||
}
|
||||
}
|
||||
|
||||
// ── 2020 T-slot cross-section (2D profile for linear_extrude) ─────────────────
|
||||
// Matches OpenBuilds V-Slot 2020 profile.
|
||||
module tslot_profile_2d() {
|
||||
difference() {
|
||||
// Outer square with corner notches
|
||||
difference() {
|
||||
square([RAIL_W, RAIL_W], center = true);
|
||||
// Corner notches (standard 2020 chamfer)
|
||||
for (cx = [-1, 1])
|
||||
for (cy = [-1, 1])
|
||||
translate([cx * (RAIL_W/2 - CORNER_NOTCH/2),
|
||||
cy * (RAIL_W/2 - CORNER_NOTCH/2)])
|
||||
rotate([0, 0, 45])
|
||||
square([CORNER_NOTCH * 1.41, CORNER_NOTCH * 1.41],
|
||||
center = true);
|
||||
}
|
||||
|
||||
// Central lightening bore
|
||||
circle(d = CENTRAL_BORE);
|
||||
|
||||
// Internal corner channels (weight reduction, standard in 2020)
|
||||
for (cx = [-1, 1])
|
||||
for (cy = [-1, 1])
|
||||
translate([cx * (RAIL_W/4 + 0.5), cy * (RAIL_W/4 + 0.5)])
|
||||
circle(d = 3.2);
|
||||
|
||||
// 4× T-grooves (one per face)
|
||||
for (rot = [0, 90, 180, 270])
|
||||
rotate([0, 0, rot])
|
||||
tslot_groove_2d(face_dist = RAIL_W/2);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Single T-groove profile (2D, centred on face at face_dist from origin) ────
|
||||
module tslot_groove_2d(face_dist) {
|
||||
// Outer slot opening (tapered/chamfered entry)
|
||||
translate([0, face_dist - SLOT_NECK_H])
|
||||
square([SLOT_OPEN, SLOT_NECK_H + e], center = true);
|
||||
|
||||
// Inner T-groove
|
||||
translate([0, face_dist - SLOT_NECK_H - SLOT_INNER_H + e])
|
||||
square([SLOT_INNER_W, SLOT_INNER_H + e], center = true);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// PRINTABLE T-NUT (Part B — print ×N as needed in PETG)
|
||||
// ============================================================
|
||||
// Slides into the T-groove of the 2020 rail.
|
||||
// Captured M3 hex nut allows a thumbscrew to clamp from outside.
|
||||
// The T-nut has a registration peg that drops into index holes.
|
||||
//
|
||||
// Print: PETG, flat face down, 5 perims, 60% infill.
|
||||
// Standard M3 hex nut pressed in from top after printing.
|
||||
module printable_tnut() {
|
||||
difference() {
|
||||
union() {
|
||||
// Main body (fits inside T-groove)
|
||||
cube([TNUT_W, TNUT_L, TNUT_H], center = true);
|
||||
|
||||
// Wings that sit behind slot opening (wider than SLOT_OPEN)
|
||||
// These wings bear against the T-groove inner walls
|
||||
translate([0, 0, TNUT_H/2 - 0.8])
|
||||
cube([TNUT_W, TNUT_L, 1.6], center = true);
|
||||
}
|
||||
|
||||
// M3 hex nut pocket (press-fit from top)
|
||||
translate([0, 0, TNUT_H/2 - TNUT_M3_NUT_H - 0.3])
|
||||
cylinder(d = TNUT_M3_NUT_AF / cos(30),
|
||||
h = TNUT_M3_NUT_H + 0.4,
|
||||
$fn = 6);
|
||||
|
||||
// M3 clearance bore (through T-nut, for thumbscrew shank)
|
||||
cylinder(d = TNUT_BOLT_D, h = TNUT_H + 2*e, center = true);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// THUMBSCREW WHEEL (Part C — print ×N as needed in PETG)
|
||||
// ============================================================
|
||||
// Press-fit onto M3×16 SHCS head. Provides finger grip for
|
||||
// tool-free tightening. One per bracket.
|
||||
// Press-fit bore: 5.6 mm (M3 SHCS head hex socket OD ≈ 5.5 mm)
|
||||
module thumbscrew_wheel() {
|
||||
difference() {
|
||||
union() {
|
||||
cylinder(d = THUMB_D, h = THUMB_H);
|
||||
// Knurl ridges (cosmetic grooves — helps grip)
|
||||
for (i = [0 : THUMB_KNURL - 1])
|
||||
rotate([0, 0, i * 360 / THUMB_KNURL])
|
||||
translate([THUMB_D/2 - 1, 0, 0])
|
||||
cylinder(d = 2.0, h = THUMB_H);
|
||||
}
|
||||
// M3 SHCS head hex socket bore (press-fit)
|
||||
translate([0, 0, -e])
|
||||
cylinder(d = 5.7, h = 4);
|
||||
// M3 shank clearance bore (bolt passes through)
|
||||
cylinder(d = M3_D, h = THUMB_H + 2*e);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// RAIL END CAP (Part D — print 2× per rail in PETG)
|
||||
// ============================================================
|
||||
// Safety end cap — prevents T-nuts from sliding off end.
|
||||
// Snap-friction fit (no fasteners needed for prototyping).
|
||||
// Or: drill M5 through cap + rail end, use M5×10 set screw.
|
||||
module rail_end_cap() {
|
||||
cap_h = 8;
|
||||
cap_plug_h = 6; // depth inserted into rail end
|
||||
|
||||
difference() {
|
||||
union() {
|
||||
// Outer flange (≥ rail OD, prevents over-insertion)
|
||||
cylinder(d = RAIL_W + 6, h = cap_h);
|
||||
|
||||
// Inner plug (fits inside rail with 0.3 mm clearance per side)
|
||||
translate([0, 0, cap_h - e])
|
||||
cube([RAIL_W - 0.6, RAIL_W - 0.6, cap_plug_h], center = true);
|
||||
}
|
||||
|
||||
// Central M5 bore (for optional end-bolt)
|
||||
cylinder(d = M5_D, h = cap_h + cap_plug_h + e);
|
||||
|
||||
// Lightening cutout
|
||||
translate([0, 0, 2])
|
||||
cube([RAIL_W - 4, RAIL_W - 4, cap_h + cap_plug_h], center = true);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// INDEX PIN SET (Part E — print 1 set in PETG)
|
||||
// ============================================================
|
||||
// Small pins that insert through M5 index holes on the rail.
|
||||
// When a bracket T-nut has a matching recess, the pin provides
|
||||
// positive indexed positioning (true 25 mm grid lock).
|
||||
// For non-indexed positioning: leave pins out, use thumbscrew only.
|
||||
module index_pin_set() {
|
||||
// Print 4 pins on a carrier plate for easy identification
|
||||
for (i = [0:3])
|
||||
translate([i * 20, 0, 0])
|
||||
index_pin();
|
||||
}
|
||||
|
||||
module index_pin() {
|
||||
pin_od = 4.9; // M5 hole clearance (5.3 - 0.4 mm)
|
||||
pin_len = RAIL_W + 2; // spans full rail width + 1 mm each side
|
||||
head_od = 8.0; // knurled head OD (finger pull)
|
||||
head_h = 5.0;
|
||||
|
||||
union() {
|
||||
// Pin shaft
|
||||
cylinder(d = pin_od, h = pin_len);
|
||||
// Knurled head (at one end)
|
||||
translate([0, 0, pin_len])
|
||||
difference() {
|
||||
cylinder(d = head_od, h = head_h);
|
||||
for (j = [0:7])
|
||||
rotate([0, 0, j*45])
|
||||
translate([head_od/2 - 1.2, 0, 0])
|
||||
cylinder(d = 2, h = head_h + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// STEM ADAPTER (Part F — SaltyLab / SaltyRover Ø25 mm mast)
|
||||
// ============================================================
|
||||
// Split collar clamps to the robot's Ø25 mm vertical stem.
|
||||
// Two rail-mounting flanges (front + rear) allow one or two
|
||||
// sensor rails to be mounted at 180° apart.
|
||||
//
|
||||
// Print 2× halves (front + rear), join with 2× M4×30 SHCS.
|
||||
// Each half is printed flat-face-down with no supports needed.
|
||||
module stem_adapter() {
|
||||
// Front half only — print 2× and assemble around stem
|
||||
stem_adapter_half("front");
|
||||
}
|
||||
|
||||
module stem_adapter_half(side = "front") {
|
||||
sy = (side == "front") ? 1 : -1;
|
||||
|
||||
difference() {
|
||||
union() {
|
||||
// ── Collar half (semicircle) ─────────────────────────────
|
||||
rotate_extrude(angle = 180)
|
||||
translate([STEM_COL_OD/2 - (STEM_COL_OD - STEM_BORE)/2, 0, 0])
|
||||
square([(STEM_COL_OD - STEM_BORE)/2,
|
||||
STEM_COL_H], center = false);
|
||||
|
||||
// ── Rail mounting flange (extends forward from collar) ────
|
||||
// Single flange on front face, centred left-right
|
||||
translate([-STEM_RAIL_W/2,
|
||||
sy * (STEM_COL_OD/2 - 2),
|
||||
STEM_COL_H/2 - STEM_RAIL_H/2])
|
||||
cube([STEM_RAIL_W, STEM_COL_OD/4 + 5, STEM_RAIL_H]);
|
||||
}
|
||||
|
||||
// ── Stem bore ────────────────────────────────────────────────
|
||||
translate([0, 0, -e])
|
||||
cylinder(d = STEM_BORE, h = STEM_COL_H + 2*e);
|
||||
|
||||
// ── M4 clamping bolt holes (through collar flanges) ───────────
|
||||
for (sx = [-1, 1])
|
||||
translate([sx * STEM_BOLT_X, 0, STEM_COL_H / 2])
|
||||
rotate([90, 0, 0])
|
||||
cylinder(d = M4_D, h = STEM_COL_OD + 2*e, center = true);
|
||||
|
||||
// ── M4 nut pocket (rear half) ─────────────────────────────────
|
||||
if (side == "rear") {
|
||||
for (sx = [-1, 1])
|
||||
translate([sx * STEM_BOLT_X, -STEM_COL_OD/2 + 4,
|
||||
STEM_COL_H/2])
|
||||
rotate([90, 0, 0]) {
|
||||
cylinder(d = 7.5/cos(30), h = 4, $fn = 6); // M4 nut
|
||||
}
|
||||
}
|
||||
|
||||
// ── Rail attachment slots in flange (M5 × 2) ─────────────────
|
||||
// Slots allow ±5 mm front-back fine adjustment of rail position
|
||||
for (rz = [-STEM_RAIL_H/4, STEM_RAIL_H/4])
|
||||
translate([0,
|
||||
sy * (STEM_COL_OD/2 - 2 + STEM_COL_OD/8 + 5/2),
|
||||
STEM_COL_H/2 + rz])
|
||||
rotate([90, 0, 0])
|
||||
hull() {
|
||||
translate([-6, 0, 0])
|
||||
cylinder(d = M5_D, h = 10, center = true);
|
||||
translate([+6, 0, 0])
|
||||
cylinder(d = M5_D, h = 10, center = true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// POST ADAPTER (Part G — SaltyRover 20×20 mm square post)
|
||||
// ============================================================
|
||||
// C-clamp style bracket that wraps around a 20×20 mm aluminium
|
||||
// post (common in rover extrusion frames).
|
||||
// M4 × 2 clamping bolts through the clamp flanges.
|
||||
// Rail mounts forward via 2× M5 slots in the front face.
|
||||
module post_adapter() {
|
||||
// Outer clamp body
|
||||
difference() {
|
||||
union() {
|
||||
// Main C-clamp body
|
||||
translate([-POST_CLAMP_W/2, -POST_W/2 - POST_CLAMP_T,
|
||||
0])
|
||||
cube([POST_CLAMP_W,
|
||||
POST_W + 2*POST_CLAMP_T + POST_CLAMP_W/3,
|
||||
POST_CLAMP_H]);
|
||||
}
|
||||
|
||||
// Post cavity (POST_W × POST_W + clearance)
|
||||
translate([0, 0, -e])
|
||||
cube([POST_W + 0.4, POST_W + 0.4, POST_CLAMP_H + 2*e],
|
||||
center = true);
|
||||
|
||||
// C-clamp opening (rear gap — allows clamping around post)
|
||||
translate([-POST_W/2 - POST_CLAMP_T/2 - e, -POST_W/2 - e, -e])
|
||||
cube([POST_CLAMP_T + 2*e,
|
||||
POST_W + 0.2,
|
||||
POST_CLAMP_H + 2*e]);
|
||||
|
||||
// M4 clamping bolt holes
|
||||
for (cz = [POST_CLAMP_H/4, 3*POST_CLAMP_H/4])
|
||||
translate([-POST_CLAMP_W/2 - e, 0, cz])
|
||||
rotate([0, 90, 0])
|
||||
cylinder(d = M4_D, h = POST_CLAMP_W + 2*e);
|
||||
|
||||
// M4 nut pockets (right flange)
|
||||
for (cz = [POST_CLAMP_H/4, 3*POST_CLAMP_H/4])
|
||||
translate([POST_CLAMP_W/2 - 5, 0, cz])
|
||||
rotate([0, 90, 0])
|
||||
cylinder(d = 7.5/cos(30), h = 5, $fn = 6);
|
||||
|
||||
// Rail mounting slots (M5 × 2, front face)
|
||||
for (cz = [POST_CLAMP_H/3, 2*POST_CLAMP_H/3])
|
||||
translate([0,
|
||||
POST_W/2 + POST_CLAMP_T + POST_CLAMP_W/3 - e,
|
||||
cz])
|
||||
rotate([90, 0, 0])
|
||||
hull() {
|
||||
translate([-5, 0, 0])
|
||||
cylinder(d = M5_D,
|
||||
h = POST_CLAMP_T + 2*e);
|
||||
translate([+5, 0, 0])
|
||||
cylinder(d = M5_D,
|
||||
h = POST_CLAMP_T + 2*e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// TANK CLAMP (Part H — SaltyTank side frame upright)
|
||||
// ============================================================
|
||||
// Flat plate bracket bolts directly to SaltyTank side frame
|
||||
// (6 mm Al plate) via 4× M4 SHCS.
|
||||
// Rail attaches to the front face via 2× M5 slots.
|
||||
module tank_clamp() {
|
||||
difference() {
|
||||
union() {
|
||||
// Main back plate (bolts to tank frame)
|
||||
translate([-TANK_CLAMP_W/2, -TANK_PLATE_T - 2, 0])
|
||||
cube([TANK_CLAMP_W,
|
||||
TANK_PLATE_T + RAIL_W + 10,
|
||||
TANK_CLAMP_H]);
|
||||
}
|
||||
|
||||
// Tank frame plate slot (open at rear, receives frame edge)
|
||||
translate([-TANK_CLAMP_W/2 + 5, -TANK_PLATE_T - e, -e])
|
||||
cube([TANK_CLAMP_W - 10,
|
||||
TANK_PLATE_T + 0.5,
|
||||
TANK_CLAMP_H + 2*e]);
|
||||
|
||||
// 4× M4 frame attachment bolts
|
||||
for (bx = [-TANK_BOLT_SPC/2, TANK_BOLT_SPC/2])
|
||||
for (bz = [TANK_CLAMP_H/4, 3*TANK_CLAMP_H/4])
|
||||
translate([bx, -TANK_PLATE_T/2, bz])
|
||||
rotate([90, 0, 0])
|
||||
cylinder(d = M4_D, h = TANK_PLATE_T + 4, center = true);
|
||||
|
||||
// Rail mounting slots (M5 × 2, front face)
|
||||
for (bz = [TANK_CLAMP_H/3, 2*TANK_CLAMP_H/3])
|
||||
translate([0, RAIL_W + 9 - e, bz])
|
||||
rotate([90, 0, 0])
|
||||
hull() {
|
||||
translate([-6, 0, 0])
|
||||
cylinder(d = M5_D, h = 10);
|
||||
translate([+6, 0, 0])
|
||||
cylinder(d = M5_D, h = 10);
|
||||
}
|
||||
|
||||
// Weight-reduction pockets (back plate)
|
||||
translate([0, -TANK_PLATE_T/2 - 1, TANK_CLAMP_H/2])
|
||||
cube([TANK_CLAMP_W - 20,
|
||||
4, TANK_CLAMP_H - 30],
|
||||
center = true);
|
||||
}
|
||||
}
|
||||
115
chassis/sensor_rail_BOM.md
Normal file
115
chassis/sensor_rail_BOM.md
Normal file
@ -0,0 +1,115 @@
|
||||
# Sensor Rail BOM — Issue #138
|
||||
**Agent:** sl-mechanical | **Date:** 2026-03-01
|
||||
|
||||
Universal 2020 T-slot sensor mount rail system.
|
||||
Compatible with SaltyLab (stem), SaltyRover (square posts), SaltyTank (uprights).
|
||||
|
||||
---
|
||||
|
||||
## Purchased Hardware
|
||||
|
||||
| # | Description | Spec | Qty (per robot) | Notes |
|
||||
|---|-------------|------|-----------------|-------|
|
||||
| H1 | 2020 T-slot aluminium extrusion | 20×20 mm, 200 mm length | 1–2 | OpenBuilds V-Slot or MISUMI HFS5-2020. Order pre-cut or cut with mitre saw. |
|
||||
| H2 | M5×10 BHCS | Stainless | 4 | Rail-to-adapter attachment (M5 T-nuts in rail) |
|
||||
| H3 | M5 T-nut (drop-in) | For 2020 slot | 4 | Off-the-shelf; used for adapter attachment |
|
||||
| H4 | M3×16 SHCS | Stainless | N | One per sensor bracket (thumbscrew + clamping) |
|
||||
| H5 | M3 hex nut | DIN 934, stainless | N | One per bracket T-nut (press-fit in pocket) |
|
||||
| H6 | M3×6 BHCS | Stainless | 2N | Bracket arm-to-base plate (2 per bracket) |
|
||||
| H7 | M4×30 SHCS | Stainless | 2 | Stem adapter clamping bolts |
|
||||
| H8 | M4 hex nut | DIN 934, stainless | 2 | Stem adapter nut pockets |
|
||||
| H9 | M4×12 BHCS | Stainless | 4 | Post adapter clamping |
|
||||
| H10 | M4×12 BHCS | Stainless | 4 | Tank clamp frame attachment |
|
||||
| H11 | M3×8 SHCS | Stainless | 4 | RPLIDAR platform bolts |
|
||||
| H12 | 1/4-20 UNC hex nut | Zinc or SS | 1 | D435i tripod nut (captured in bracket) |
|
||||
| H13 | M2×6 SHCS | Stainless | 4 | IMX219 PCB mounting |
|
||||
| H14 | M3×8 SHCS | Stainless | 4 | UWB PCB mounting |
|
||||
| H15 | M5×6 set screw | Cup point, SS | 2 | Optional rail end cap retention |
|
||||
|
||||
*N = number of brackets installed; typical per robot: 3–5.*
|
||||
|
||||
---
|
||||
|
||||
## Printed Parts (PETG unless noted)
|
||||
|
||||
| Part | File | Qty | Print settings | Mass est. |
|
||||
|------|------|-----|----------------|-----------|
|
||||
| T-nut base | sensor_rail_brackets.scad `base_stl` | N brackets | 5 perims, 60% infill | ~6 g |
|
||||
| RPLIDAR bracket | sensor_rail_brackets.scad `rplidar_stl` | 1 | 4 perims, 40% infill | ~18 g |
|
||||
| D435i bracket | sensor_rail_brackets.scad `d435i_stl` | 1 | 4 perims, 40% infill | ~14 g |
|
||||
| IMX219 bracket | sensor_rail_brackets.scad `imx219_stl` | 1–4 | 4 perims, 40% infill | ~8 g |
|
||||
| UWB bracket | sensor_rail_brackets.scad `uwb_stl` | 1–4 | 4 perims, 40% infill | ~10 g |
|
||||
| Cable clip | sensor_rail_brackets.scad `cable_clip_stl` | 6–10 | 3 perims, 20% infill | ~2 g |
|
||||
| Rail end cap | sensor_rail.scad `end_cap_stl` | 2 | 5 perims, 40% infill | ~3 g |
|
||||
| Index pin set (×4) | sensor_rail.scad `index_pin_stl` | 1 set | 4 perims, 40% infill | ~4 g |
|
||||
| Thumbscrew wheel | sensor_rail.scad (thumbscrew_wheel) | N | 4 perims, 30% infill | ~1.5 g |
|
||||
| Stem adapter half | sensor_rail.scad `stem_adapter_stl` | 2 | 5 perims, 60% infill | ~22 g |
|
||||
| Post adapter | sensor_rail.scad `post_adapter_stl` | 1–2 | 5 perims, 60% infill | ~28 g |
|
||||
| Tank clamp | sensor_rail.scad `tank_clamp_stl` | 1–2 | 5 perims, 60% infill | ~20 g |
|
||||
|
||||
---
|
||||
|
||||
## Rail Length Reference
|
||||
|
||||
| Robot variant | Recommended rail length | Notes |
|
||||
|---------------|------------------------|-------|
|
||||
| SaltyLab | 200 mm | On Ø25 mm stem; 1 rail |
|
||||
| SaltyRover | 200 mm | On rear sensor mast posts |
|
||||
| SaltyTank | 150–200 mm | On tank side uprights or top bar |
|
||||
|
||||
---
|
||||
|
||||
## Mass Summary (typical install: 1 rail + 3 brackets + 8 clips)
|
||||
|
||||
| Item | Mass |
|
||||
|------|------|
|
||||
| 200 mm 2020 Al extrusion | ~110 g |
|
||||
| 3× bracket assemblies (base + arm) | ~60 g |
|
||||
| 8× cable clips | ~16 g |
|
||||
| 1× adapter (stem) | ~44 g |
|
||||
| Hardware (bolts/nuts) | ~25 g |
|
||||
| **Total** | **~255 g** |
|
||||
|
||||
---
|
||||
|
||||
## Export Commands
|
||||
|
||||
```bash
|
||||
# Rail profile DXF (spec for extrusion supplier)
|
||||
openscad sensor_rail.scad -D 'RENDER="rail_2d"' -o sensor_rail_profile.dxf
|
||||
|
||||
# Printable rail section (prototype)
|
||||
openscad sensor_rail.scad -D 'RENDER="rail_section_stl"' -o sensor_rail_200.stl
|
||||
|
||||
# Adapters
|
||||
openscad sensor_rail.scad -D 'RENDER="stem_adapter_stl"' -o sensor_rail_stem_adapter.stl
|
||||
openscad sensor_rail.scad -D 'RENDER="post_adapter_stl"' -o sensor_rail_post_adapter.stl
|
||||
openscad sensor_rail.scad -D 'RENDER="tank_clamp_stl"' -o sensor_rail_tank_clamp.stl
|
||||
openscad sensor_rail.scad -D 'RENDER="end_cap_stl"' -o sensor_rail_end_cap.stl
|
||||
openscad sensor_rail.scad -D 'RENDER="index_pin_stl"' -o sensor_rail_index_pins.stl
|
||||
|
||||
# Brackets
|
||||
openscad sensor_rail_brackets.scad -D 'RENDER="base_stl"' -o srb_tnut_base.stl
|
||||
openscad sensor_rail_brackets.scad -D 'RENDER="rplidar_stl"' -o srb_rplidar.stl
|
||||
openscad sensor_rail_brackets.scad -D 'RENDER="d435i_stl"' -o srb_d435i.stl
|
||||
openscad sensor_rail_brackets.scad -D 'RENDER="imx219_stl"' -o srb_imx219.stl
|
||||
openscad sensor_rail_brackets.scad -D 'RENDER="uwb_stl"' -o srb_uwb.stl
|
||||
openscad sensor_rail_brackets.scad -D 'RENDER="cable_clip_stl"' -o srb_cable_clip.stl
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Assembly Notes
|
||||
|
||||
1. **Rail purchase**: Order OpenBuilds V-Slot 2020 or MISUMI HFS5-2020 in desired length.
|
||||
Specify M5 tapped centre bore at both ends. Index holes optional (can drill manually at 25 mm pitch).
|
||||
2. **T-nut bases**: Press M3 hex nut into pocket before inserting into rail.
|
||||
Insert T-nut tongue into T-groove from end of rail before fitting end caps.
|
||||
3. **Thumbscrew retention**: Thread M3×16 SHCS through thumbwheel, then through bracket arm, into T-nut.
|
||||
Tighten hand-tight (¼ turn from snug). No tools required.
|
||||
4. **Indexed positioning**: Insert Ø4.9 mm index pin through rail M5 cross-hole and bracket index pocket for repeatable 25 mm grid positioning.
|
||||
5. **Adapter installation**:
|
||||
- *Stem*: clamp 2 halves around Ø25 mm stem with M4×30 bolts; torque 1.5 N·m.
|
||||
- *Post*: slide C-clamp around 20×20 post; tighten M4×12 bolts; torque 1.2 N·m.
|
||||
- *Tank*: slide clamp onto 6 mm frame plate edge; tighten M4×12 bolts.
|
||||
6. **Cable clips**: Push cable into C-channel from front. Snap into T-groove. No fasteners needed.
|
||||
533
chassis/sensor_rail_brackets.scad
Normal file
533
chassis/sensor_rail_brackets.scad
Normal file
@ -0,0 +1,533 @@
|
||||
// ============================================================
|
||||
// sensor_rail_brackets.scad — Quick-Swap Sensor Brackets
|
||||
// Issue: #138 Agent: sl-mechanical Date: 2026-03-01
|
||||
// ============================================================
|
||||
//
|
||||
// Slide-on T-slot brackets for the 2020 sensor rail defined in
|
||||
// sensor_rail.scad. All brackets share a common T-nut base that
|
||||
// clamps to the rail with a single M3 thumbscrew (tool-free, ¼ turn).
|
||||
//
|
||||
// Bracket catalogue:
|
||||
// Part 1 — universal_tnut_base() shared base for all brackets
|
||||
// Part 2 — rplidar_bracket() RPLIDAR A1M8 Ø70 mm scanner
|
||||
// Part 3 — d435i_bracket() Intel RealSense D435i (90×25×25 mm)
|
||||
// Part 4 — imx219_bracket() IMX219 CSI camera (32×32 mm PCB)
|
||||
// Part 5 — uwb_bracket() MaUWB ESP32-S3 anchor (~50×25 mm)
|
||||
// Part 6 — cable_clip() Cable management clip
|
||||
// Part 7 — assembly_preview() Full rail + all brackets
|
||||
//
|
||||
// Sensor interface dimensions (caliper-verified):
|
||||
// RPLIDAR A1M8 : body Ø70 mm, bolt circle Ø58 mm, 4× M3 at 45°
|
||||
// D435i : 90×25×25 mm body, 1/4-20 UNC female bottom port
|
||||
// IMX219 : 32×32 mm PCB, M2 holes 24×24 mm (±12 mm grid)
|
||||
// UWB ESP32-S3 : 50×25 mm PCB, M3 holes at corners (42×17 mm)
|
||||
//
|
||||
// T-nut base interface (matches sensor_rail.scad constants):
|
||||
// TNUT_W = 9.8 mm, TNUT_H = 5.5 mm, TNUT_L = 12 mm
|
||||
// M3 thumbscrew (M3×16 SHCS + printed thumbwheel)
|
||||
//
|
||||
// Coordinate convention (same as sensor_rail.scad):
|
||||
// Rail runs along Z (vertical); front sensor face faces +Y.
|
||||
// Bracket Z=0 at the bottom of the bracket body.
|
||||
//
|
||||
// RENDER options:
|
||||
// "assembly" full preview — rail + all brackets
|
||||
// "base_stl" T-nut base only (universal — print ×N)
|
||||
// "rplidar_stl" RPLIDAR bracket arm + platform
|
||||
// "d435i_stl" D435i bracket arm + plate
|
||||
// "imx219_stl" IMX219 bracket arm + PCB cradle
|
||||
// "uwb_stl" UWB bracket arm + PCB cradle
|
||||
// "cable_clip_stl" cable management clip (print ×6–10)
|
||||
//
|
||||
// Export commands:
|
||||
// openscad sensor_rail_brackets.scad -D 'RENDER="base_stl"' -o srb_tnut_base.stl
|
||||
// openscad sensor_rail_brackets.scad -D 'RENDER="rplidar_stl"' -o srb_rplidar.stl
|
||||
// openscad sensor_rail_brackets.scad -D 'RENDER="d435i_stl"' -o srb_d435i.stl
|
||||
// openscad sensor_rail_brackets.scad -D 'RENDER="imx219_stl"' -o srb_imx219.stl
|
||||
// openscad sensor_rail_brackets.scad -D 'RENDER="uwb_stl"' -o srb_uwb.stl
|
||||
// openscad sensor_rail_brackets.scad -D 'RENDER="cable_clip_stl"' -o srb_cable_clip.stl
|
||||
// ============================================================
|
||||
|
||||
$fn = 64;
|
||||
e = 0.01;
|
||||
|
||||
// ── Rail geometry constants (must match sensor_rail.scad) ────────────────────
|
||||
RAIL_W = 20.0;
|
||||
SLOT_OPEN = 6.0;
|
||||
SLOT_INNER_W = 10.2;
|
||||
SLOT_INNER_H = 5.8;
|
||||
SLOT_NECK_H = 3.2;
|
||||
INDEX_PITCH = 25.0;
|
||||
INDEX_HOLE_D = 5.3;
|
||||
|
||||
// ── T-nut constants (must match sensor_rail.scad) ────────────────────────────
|
||||
TNUT_W = 9.8;
|
||||
TNUT_H = 5.5;
|
||||
TNUT_L = 12.0;
|
||||
TNUT_M3_NUT_AF = 5.5;
|
||||
TNUT_M3_NUT_H = 2.5;
|
||||
TNUT_BOLT_D = 3.3;
|
||||
|
||||
// ── Bracket base geometry ────────────────────────────────────────────────────
|
||||
BASE_FACE_W = 30.0; // width of base body on rail face
|
||||
BASE_FACE_H = 20.0; // height of base body (along rail Z axis)
|
||||
BASE_FACE_T = SLOT_NECK_H + 1.5; // depth: neck + small flange overstand
|
||||
|
||||
// ── Bracket arm geometry ─────────────────────────────────────────────────────
|
||||
ARM_T = 4.0; // arm wall thickness
|
||||
ARM_OUT = 30.0; // arm reach out from rail face (+Y)
|
||||
|
||||
// ── Sensor interface constants ───────────────────────────────────────────────
|
||||
// RPLIDAR A1M8
|
||||
RPL_BODY_D = 70.0; // scanner body OD
|
||||
RPL_BC_D = 58.0; // bolt circle diameter
|
||||
RPL_BOLT_D = 3.3; // M3 clearance
|
||||
RPL_PLAT_T = 4.0; // platform plate thickness
|
||||
RPL_PLAT_D = 76.0; // platform OD (6 mm clearance around body)
|
||||
|
||||
// D435i RealSense
|
||||
D4_BODY_W = 90.0; // body width (X)
|
||||
D4_BODY_D = 25.0; // body depth (Y)
|
||||
D4_BODY_H = 25.0; // body height (Z)
|
||||
D4_MOUNT_D = 6.5; // 1/4-20 UNC clearance bore (6.35 mm + 0.15)
|
||||
D4_PLATE_W = 96.0; // mounting plate width
|
||||
D4_PLATE_T = 3.0; // mounting plate thickness
|
||||
D4_TILT_DEG = 8.0; // nose-down tilt (matches rover/tank D435i mounts)
|
||||
|
||||
// IMX219 CSI camera
|
||||
IMX_PCB_W = 32.0;
|
||||
IMX_PCB_H = 32.0;
|
||||
IMX_HOLE_SPC = 24.0; // M2 hole pattern (±12 mm, square)
|
||||
IMX_BOLT_D = 2.4; // M2 clearance
|
||||
IMX_TILT_DEG = 10.0; // slight downward tilt for terrain view
|
||||
IMX_CRADLE_T = 3.0; // cradle plate thickness
|
||||
|
||||
// UWB anchor (MaUWB ESP32-S3 ~50×25 mm)
|
||||
UWB_PCB_W = 50.0;
|
||||
UWB_PCB_H = 25.0;
|
||||
UWB_HOLE_X = 42.0; // hole pattern X span (±21 mm)
|
||||
UWB_HOLE_Y = 17.0; // hole pattern Y span (±8.5 mm)
|
||||
UWB_BOLT_D = 3.3; // M3 clearance
|
||||
UWB_CRADLE_T = 3.0;
|
||||
|
||||
// Cable clip
|
||||
CLIP_CABLE_D = 6.5; // max cable bundle OD (6 mm typ)
|
||||
CLIP_T = 2.5; // clip wall thickness
|
||||
|
||||
// Fasteners
|
||||
M2_D = 2.4;
|
||||
M3_D = 3.3;
|
||||
M4_D = 4.3;
|
||||
M5_D = 5.3;
|
||||
|
||||
// ============================================================
|
||||
// RENDER DISPATCH
|
||||
// ============================================================
|
||||
RENDER = "assembly";
|
||||
|
||||
if (RENDER == "assembly") {
|
||||
assembly_preview();
|
||||
} else if (RENDER == "base_stl") {
|
||||
universal_tnut_base();
|
||||
} else if (RENDER == "rplidar_stl") {
|
||||
rplidar_bracket();
|
||||
} else if (RENDER == "d435i_stl") {
|
||||
d435i_bracket();
|
||||
} else if (RENDER == "imx219_stl") {
|
||||
imx219_bracket();
|
||||
} else if (RENDER == "uwb_stl") {
|
||||
uwb_bracket();
|
||||
} else if (RENDER == "cable_clip_stl") {
|
||||
cable_clip();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// ASSEMBLY PREVIEW
|
||||
// ============================================================
|
||||
module assembly_preview() {
|
||||
// Ghost rail section
|
||||
%color("Silver", 0.35) {
|
||||
linear_extrude(250)
|
||||
square([RAIL_W, RAIL_W], center = true);
|
||||
}
|
||||
|
||||
// RPLIDAR bracket at top (Z=180)
|
||||
color("OliveDrab", 0.85)
|
||||
translate([0, 0, 180])
|
||||
rplidar_bracket();
|
||||
|
||||
// D435i bracket (Z=120)
|
||||
color("DarkSlateGray", 0.85)
|
||||
translate([0, 0, 120])
|
||||
d435i_bracket();
|
||||
|
||||
// IMX219 bracket (Z=75)
|
||||
color("Teal", 0.85)
|
||||
translate([0, 0, 75])
|
||||
imx219_bracket();
|
||||
|
||||
// UWB bracket (Z=30)
|
||||
color("SaddleBrown", 0.85)
|
||||
translate([0, 0, 30])
|
||||
uwb_bracket();
|
||||
|
||||
// Cable clips
|
||||
for (cz = [50, 100, 150, 200])
|
||||
color("DimGray", 0.70)
|
||||
translate([RAIL_W/2, 0, cz])
|
||||
rotate([0, -90, 0])
|
||||
cable_clip();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// PART 1 — UNIVERSAL T-NUT BASE
|
||||
// ============================================================
|
||||
// Common base used by all sensor brackets.
|
||||
// Slides into the 2020 rail T-groove from the end.
|
||||
// M3×16 SHCS + thumbwheel clamps the T-nut from outside the rail.
|
||||
//
|
||||
// Print: PETG, 5 perims, 60 % infill, flat face (face plate) down.
|
||||
// Orientation: face plate is the -Y face (against rail), T-nut protrudes +Y.
|
||||
//
|
||||
// The face plate provides a flat surface for bracket arm attachment via
|
||||
// 2× M3 bolts (inset into the bracket arm from the +Y side).
|
||||
module universal_tnut_base() {
|
||||
difference() {
|
||||
union() {
|
||||
// ── Face plate (sits flush against rail outer face) ──────────
|
||||
// Width = BASE_FACE_W, height = BASE_FACE_H, thin (BASE_FACE_T)
|
||||
translate([-BASE_FACE_W/2, -BASE_FACE_T, 0])
|
||||
cube([BASE_FACE_W, BASE_FACE_T, BASE_FACE_H]);
|
||||
|
||||
// ── T-nut tongue (protrudes into rail T-groove) ──────────────
|
||||
// Centred on face plate; sized to TNUT_W × TNUT_H
|
||||
translate([-TNUT_W/2, 0, (BASE_FACE_H - TNUT_L)/2])
|
||||
cube([TNUT_W, SLOT_NECK_H + e, TNUT_L]);
|
||||
|
||||
// ── T-nut inner body (wider, inside T-groove) ────────────────
|
||||
translate([-TNUT_W/2, SLOT_NECK_H - e, (BASE_FACE_H - TNUT_L)/2])
|
||||
cube([TNUT_W, TNUT_H - SLOT_NECK_H + e, TNUT_L]);
|
||||
}
|
||||
|
||||
// ── M3 thumbscrew bore (centre of T-nut, through face plate) ────
|
||||
translate([0, -BASE_FACE_T - e, BASE_FACE_H/2])
|
||||
rotate([-90, 0, 0])
|
||||
cylinder(d = TNUT_BOLT_D, h = BASE_FACE_T + TNUT_H + 2*e);
|
||||
|
||||
// ── M3 hex nut pocket (inside T-nut body, for thumbscrew) ────────
|
||||
translate([0, SLOT_NECK_H + 0.3, BASE_FACE_H/2])
|
||||
rotate([-90, 0, 0])
|
||||
cylinder(d = TNUT_M3_NUT_AF / cos(30),
|
||||
h = TNUT_M3_NUT_H + 0.3,
|
||||
$fn = 6);
|
||||
|
||||
// ── 2× M3 bolt holes in face plate (bracket arm attachment) ─────
|
||||
for (hz = [BASE_FACE_H * 0.28, BASE_FACE_H * 0.72])
|
||||
translate([0, -BASE_FACE_T - e, hz])
|
||||
rotate([-90, 0, 0])
|
||||
cylinder(d = M3_D, h = BASE_FACE_T + 2*e);
|
||||
|
||||
// ── Index pin pocket (optional 25 mm grid lock) ──────────────────
|
||||
// Shallow Ø5 mm pocket on right side — aligns with rail index hole
|
||||
translate([BASE_FACE_W/2 - 3, -BASE_FACE_T/2, BASE_FACE_H/2])
|
||||
rotate([0, 90, 0])
|
||||
cylinder(d = 5.1, h = 3 + e);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Internal helper: base with bolt holes for arm attachment ─────────────────
|
||||
// Used by all sensor brackets — arms bolt to this face plate.
|
||||
// Returns the face plate at Y=0 (rail face), arm extends in +Y.
|
||||
module _base_with_arm_holes() {
|
||||
universal_tnut_base();
|
||||
}
|
||||
|
||||
// ── Internal helper: arm stem from rail face to sensor platform ───────────────
|
||||
// arm_len: reach in +Y from rail face
|
||||
// arm_w : arm width in X
|
||||
// arm_h : arm height in Z (same as BASE_FACE_H unless overridden)
|
||||
module _arm(arm_len, arm_w, arm_h = BASE_FACE_H) {
|
||||
// Chamfered arm block
|
||||
hull() {
|
||||
translate([-arm_w/2, 0, 0])
|
||||
cube([arm_w, ARM_T, arm_h]);
|
||||
translate([-arm_w/2, arm_len - ARM_T, (arm_h - arm_h*0.6)/2])
|
||||
cube([arm_w, ARM_T, arm_h * 0.6]);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// PART 2 — RPLIDAR A1M8 BRACKET
|
||||
// ============================================================
|
||||
// Circular platform that holds the RPLIDAR A1M8 scanner on top
|
||||
// of the sensor rail. The scanner sits with its scan plane
|
||||
// perpendicular to the rail (horizontal plane).
|
||||
//
|
||||
// RPLIDAR bolt pattern: 4× M3 on Ø58 mm BC at 45°/135°/225°/315°.
|
||||
// Motor connector exits through a slot in the rear of the platform.
|
||||
//
|
||||
// Print: PETG, 4 perims, 40 % infill. Arm + platform in one piece.
|
||||
module rplidar_bracket() {
|
||||
union() {
|
||||
// T-nut base
|
||||
_base_with_arm_holes();
|
||||
|
||||
// Vertical arm rising above rail centre
|
||||
translate([0, 0, 0])
|
||||
difference() {
|
||||
// Arm + top platform merge via hull
|
||||
union() {
|
||||
// Arm
|
||||
translate([-BASE_FACE_W/2, 0, 0])
|
||||
cube([BASE_FACE_W, ARM_OUT, ARM_T]);
|
||||
|
||||
// Platform disc at arm end
|
||||
translate([0, ARM_OUT, BASE_FACE_H/2 + 5])
|
||||
cylinder(d = RPL_PLAT_D, h = RPL_PLAT_T);
|
||||
}
|
||||
|
||||
// ── Central bore (RPLIDAR body clears through) ───────────
|
||||
// Not needed — scanner sits ON platform, not through it.
|
||||
// Slot for motor/USB connector exit at rear
|
||||
translate([0, ARM_OUT - RPL_PLAT_D/2 - e,
|
||||
BASE_FACE_H/2 + 5 - e])
|
||||
cube([20, 15, RPL_PLAT_T + 2*e], center = true);
|
||||
}
|
||||
|
||||
// ── Bolt holes subtracted from platform (separate difference) ────
|
||||
translate([0, ARM_OUT, BASE_FACE_H/2 + 5])
|
||||
difference() {
|
||||
cylinder(d = RPL_PLAT_D, h = RPL_PLAT_T);
|
||||
|
||||
// 4× M3 bolt holes on Ø58 mm bolt circle at 45°
|
||||
for (a = [45, 135, 225, 315])
|
||||
translate([RPL_BC_D/2 * cos(a),
|
||||
RPL_BC_D/2 * sin(a), -e])
|
||||
cylinder(d = RPL_BOLT_D, h = RPL_PLAT_T + 2*e);
|
||||
|
||||
// Lightening pockets (reduces print time/weight)
|
||||
for (a = [0, 90, 180, 270])
|
||||
translate([RPL_PLAT_D/2 * 0.5 * cos(a),
|
||||
RPL_PLAT_D/2 * 0.5 * sin(a), 1])
|
||||
cylinder(d = 14, h = RPL_PLAT_T + e);
|
||||
|
||||
// Connector slot
|
||||
translate([0, -RPL_PLAT_D/2 + 5, -e])
|
||||
cube([20, 15, RPL_PLAT_T + 2*e], center = true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// PART 3 — D435i REALSENSE BRACKET
|
||||
// ============================================================
|
||||
// Angled mounting plate for the Intel RealSense D435i.
|
||||
// Camera attaches via captured 1/4-20 UNC hex nut (standard tripod).
|
||||
// 8° nose-down tilt for forward terrain view (matches rover/tank mounts).
|
||||
//
|
||||
// Print: PETG, 4 perims, 40 % infill.
|
||||
module d435i_bracket() {
|
||||
union() {
|
||||
// T-nut base
|
||||
_base_with_arm_holes();
|
||||
|
||||
// Horizontal arm + tilted mounting plate
|
||||
difference() {
|
||||
union() {
|
||||
// Arm from rail face to camera plate
|
||||
translate([-D4_PLATE_W/2, 0, 0])
|
||||
cube([D4_PLATE_W, ARM_OUT, ARM_T]);
|
||||
|
||||
// Camera mounting plate (tilted 8° nose-down)
|
||||
translate([0, ARM_OUT, BASE_FACE_H/2])
|
||||
rotate([D4_TILT_DEG, 0, 0])
|
||||
translate([-D4_PLATE_W/2, 0, -D4_PLATE_T/2])
|
||||
cube([D4_PLATE_W, D4_PLATE_T, BASE_FACE_H]);
|
||||
}
|
||||
|
||||
// 1/4-20 UNC clearance bore (6.5 mm through mounting plate)
|
||||
translate([0, ARM_OUT + D4_PLATE_T/2, BASE_FACE_H/2])
|
||||
rotate([D4_TILT_DEG + 90, 0, 0])
|
||||
cylinder(d = D4_MOUNT_D, h = D4_PLATE_T + 2*e,
|
||||
center = true);
|
||||
|
||||
// 1/4-20 hex nut pocket (rear of plate — 11.1 mm AF UNC)
|
||||
translate([0, ARM_OUT - D4_PLATE_T/2 - 3, BASE_FACE_H/2])
|
||||
rotate([D4_TILT_DEG + 90, 0, 0])
|
||||
cylinder(d = 11.4 / cos(30), h = 5,
|
||||
$fn = 6, center = true);
|
||||
|
||||
// Cable routing notch at plate edge
|
||||
translate([D4_PLATE_W/2 - 8, ARM_OUT,
|
||||
BASE_FACE_H/2 - BASE_FACE_H * 0.3])
|
||||
cube([10, ARM_T + 2*e, 8], center = true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// PART 4 — IMX219 CSI CAMERA BRACKET
|
||||
// ============================================================
|
||||
// Small cradle for 32×32 mm IMX219 CSI camera PCB.
|
||||
// 10° downward tilt for terrain view.
|
||||
// PCB attaches with 4× M2 bolts (24×24 mm pattern, countersunk).
|
||||
// FFC cable exits through open bottom of cradle.
|
||||
//
|
||||
// Print: PETG, 4 perims, 40 % infill.
|
||||
module imx219_bracket() {
|
||||
pcb_h = IMX_PCB_H + 4; // cradle height (PCB + rim)
|
||||
pcb_w = IMX_PCB_W + 4; // cradle width
|
||||
|
||||
union() {
|
||||
// T-nut base
|
||||
_base_with_arm_holes();
|
||||
|
||||
difference() {
|
||||
union() {
|
||||
// Short arm (IMX219 is compact, less reach needed)
|
||||
translate([-pcb_w/2, 0, 0])
|
||||
cube([pcb_w, ARM_OUT * 0.7, ARM_T]);
|
||||
|
||||
// Tilted PCB cradle
|
||||
translate([0, ARM_OUT * 0.7, BASE_FACE_H/2])
|
||||
rotate([IMX_TILT_DEG, 0, 0])
|
||||
translate([-pcb_w/2, 0, -pcb_h/2])
|
||||
cube([pcb_w, IMX_CRADLE_T + 2, pcb_h]);
|
||||
}
|
||||
|
||||
// 4× M2 clearance bores (24×24 mm pattern)
|
||||
for (hx = [-IMX_HOLE_SPC/2, IMX_HOLE_SPC/2])
|
||||
for (hz = [-IMX_HOLE_SPC/2, IMX_HOLE_SPC/2])
|
||||
translate([hx,
|
||||
ARM_OUT * 0.7 + IMX_CRADLE_T + 2 + e,
|
||||
BASE_FACE_H/2 + hz])
|
||||
rotate([90 - IMX_TILT_DEG, 0, 0])
|
||||
cylinder(d = IMX_BOLT_D,
|
||||
h = IMX_CRADLE_T + 4, center = true);
|
||||
|
||||
// FFC cable exit slot (bottom of cradle)
|
||||
translate([0, ARM_OUT * 0.7 + (IMX_CRADLE_T + 2)/2,
|
||||
BASE_FACE_H/2 - pcb_h/2 - e])
|
||||
rotate([IMX_TILT_DEG, 0, 0])
|
||||
cube([12, IMX_CRADLE_T + 4, 8], center = true);
|
||||
|
||||
// Lens window (open centre of cradle face)
|
||||
translate([0, ARM_OUT * 0.7 - e, BASE_FACE_H/2])
|
||||
rotate([IMX_TILT_DEG, 0, 0])
|
||||
cube([20, IMX_CRADLE_T + 4 + 2*e, 18], center = true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// PART 5 — UWB ANCHOR BRACKET
|
||||
// ============================================================
|
||||
// Open cradle for MaUWB ESP32-S3 PCB (~50×25 mm).
|
||||
// Antenna patch faces forward (+Y); PCB attaches with 4× M3 bolts.
|
||||
// Bracket angled 0° (vertical face) for panoramic UWB coverage.
|
||||
//
|
||||
// Print: PETG, 4 perims, 40 % infill.
|
||||
module uwb_bracket() {
|
||||
rim = 3.0; // cradle rim thickness
|
||||
crd_w = UWB_PCB_W + 2*rim;
|
||||
crd_h = UWB_PCB_H + 2*rim;
|
||||
|
||||
union() {
|
||||
// T-nut base
|
||||
_base_with_arm_holes();
|
||||
|
||||
difference() {
|
||||
union() {
|
||||
// Arm
|
||||
translate([-crd_w/2, 0, 0])
|
||||
cube([crd_w, ARM_OUT, ARM_T]);
|
||||
|
||||
// Vertical cradle plate at arm end
|
||||
translate([-crd_w/2, ARM_OUT, 0])
|
||||
cube([crd_w, UWB_CRADLE_T, crd_h]);
|
||||
}
|
||||
|
||||
// 4× M3 clearance bores (UWB_HOLE_X × UWB_HOLE_Y pattern)
|
||||
for (hx = [-UWB_HOLE_X/2, UWB_HOLE_X/2])
|
||||
for (hz = [-UWB_HOLE_Y/2 + crd_h/2, UWB_HOLE_Y/2 + crd_h/2])
|
||||
translate([hx, ARM_OUT - e, hz])
|
||||
rotate([-90, 0, 0])
|
||||
cylinder(d = UWB_BOLT_D,
|
||||
h = UWB_CRADLE_T + 2*e);
|
||||
|
||||
// M3 nut pockets (rear of cradle)
|
||||
for (hx = [-UWB_HOLE_X/2, UWB_HOLE_X/2])
|
||||
for (hz = [-UWB_HOLE_Y/2 + crd_h/2, UWB_HOLE_Y/2 + crd_h/2])
|
||||
translate([hx, ARM_OUT + UWB_CRADLE_T - 3, hz])
|
||||
rotate([-90, 0, 0])
|
||||
cylinder(d = 6.4 / cos(30), h = 3 + e, $fn = 6);
|
||||
|
||||
// Open centre window (antenna / component clearance)
|
||||
translate([0, ARM_OUT - e,
|
||||
crd_h/2])
|
||||
cube([UWB_PCB_W - 2*rim,
|
||||
UWB_CRADLE_T + 2*e,
|
||||
UWB_PCB_H - 2*rim], center = true);
|
||||
|
||||
// USB / wire exit slot at bottom
|
||||
translate([0, ARM_OUT + UWB_CRADLE_T/2, rim/2])
|
||||
cube([12, UWB_CRADLE_T + 2*e, rim + 2*e], center = true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// PART 6 — CABLE CLIP
|
||||
// ============================================================
|
||||
// Tool-free push-in cable retainer that snaps into the 2020 rail
|
||||
// T-groove. Accepts a single cable bundle up to CLIP_CABLE_D mm.
|
||||
// Snap-fit tongue grips the T-groove without fasteners.
|
||||
// Print in PETG for flexibility (snap-fit requires some elasticity).
|
||||
//
|
||||
// Print: PETG, 3 perims, 20 % infill (flexibility matters).
|
||||
// Orientation: flat face (channel face) down.
|
||||
module cable_clip() {
|
||||
snap_t = 1.4; // snap tongue thickness (springy)
|
||||
snap_h = SLOT_INNER_H - 0.3;
|
||||
snap_w = SLOT_OPEN - 0.4; // narrow enough to enter slot
|
||||
body_w = 18.0;
|
||||
body_h = 14.0;
|
||||
ch_d = CLIP_CABLE_D;
|
||||
|
||||
difference() {
|
||||
union() {
|
||||
// ── Body plate (sits on rail face) ──────────────────────────
|
||||
translate([-body_w/2, 0, 0])
|
||||
cube([body_w, CLIP_T, body_h]);
|
||||
|
||||
// ── Snap tongue (inserts into T-groove) ─────────────────────
|
||||
// Centred, protrudes into rail slot
|
||||
translate([-snap_w/2, CLIP_T - e, (body_h - TNUT_L)/2])
|
||||
cube([snap_w, SLOT_NECK_H + e, TNUT_L]);
|
||||
|
||||
// Barb: slightly wider than slot opening — snaps into T-groove
|
||||
translate([-TNUT_W/2 + 0.4, CLIP_T + SLOT_NECK_H - e,
|
||||
(body_h - TNUT_L)/2])
|
||||
cube([TNUT_W - 0.8, snap_h + e, TNUT_L]);
|
||||
|
||||
// ── Cable channel (C-clip shape) ─────────────────────────────
|
||||
translate([0, CLIP_T + SLOT_NECK_H + snap_h + 2, body_h/2])
|
||||
rotate([0, 90, 0])
|
||||
difference() {
|
||||
cylinder(d = ch_d + 2*CLIP_T, h = body_w,
|
||||
center = true);
|
||||
cylinder(d = ch_d, h = body_w + 2*e,
|
||||
center = true);
|
||||
// Open front for push-in insertion
|
||||
translate([0, -(ch_d/2 + CLIP_T + e), 0])
|
||||
cube([ch_d * 0.8,
|
||||
ch_d + 2*CLIP_T + 2*e,
|
||||
body_w + 2*e], center = true);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Lightening slot in body plate ───────────────────────────────
|
||||
translate([0, -e, body_h/2])
|
||||
cube([body_w - 8, CLIP_T + 2*e, body_h - 8], center = true);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user