// ============================================================ // payload_bay_rail.scad — Modular Payload Bay Rail System // Issue: #170 Agent: sl-mechanical Date: 2026-03-01 // ============================================================ // // Dovetail rail mounted on robot top deck. Payload modules slide on // from either end and are retained by a spring-loaded ball detent plus // an optional M4 thumbscrew safety lock. // // Dovetail geometry (60° included angle — balanced for print + load): // // ← RAIL_W (50 mm) → // ┌──────────────────┐ ← rail top face (Z = RAIL_T) // │ ╲ ╱ │ // │ ╲__________╱ │ ← dovetail slot (female, cut into top) // │ (DOVE_SLOT) │ // └──────────────────┘ ← rail bottom face (Z = 0) // // DOVE_ANGLE = 30° from vertical (= 60° included). // Slot width at top = DOVE_SLOT_TOP (open face) // Slot width at bottom = DOVE_SLOT_BOT (inner base of slot) // Slot depth = DOVE_H // // Module tongue (male dovetail) slides in with 0.3 mm clearance each side. // // Spring detent: Ø6 mm steel ball in module, spring behind, seats in // Ø4.9 mm dimples drilled into rail slot bottom at DETENT_PITCH spacing. // Provides tactile click-lock at each indexed position. // // Safety lock: M4 thumbscrew through module side, tightens against rail // side wall. For vibration environments or >1 kg payload. // // Power+data connector: 4-pin pogo array in rail at fixed position. // Pins: GND | +5 V | +12 V | UART (half-duplex) // Module has matching brass target pads (Ø4 mm). // Connector position: centred in rail length, at CONN_Y from one end. // // Cross-variant adapter plates (this file): // lab_rail_adapter() — SaltyLab chassis top (Ø25 mm stem clear) // rover_rail_adapter() — SaltyRover deck (M4 grid) // tank_rail_adapter() — SaltyTank deck (M4 grid) // // Coordinate convention: // Rail runs along Y axis. Cross-section in X-Z plane. // Z = 0 at rail bottom face (= robot deck top face). // Module slides in +Y direction. // // RENDER options: // "assembly" rail + adapter + module ghost // "rail_2d" DXF — dovetail cross-section (CNC/waterjet) // "rail_stl" STL — printable rail section (PETG prototype) // "connector_stl" STL — pogo connector housing insert // "detent_plunger_stl" STL — spring detent plunger (print ×2 per module) // "lab_adapter_stl" STL — SaltyLab deck adapter // "rover_adapter_stl" STL — SaltyRover deck adapter // "tank_adapter_stl" STL — SaltyTank deck adapter // // Export: // openscad payload_bay_rail.scad -D 'RENDER="rail_2d"' -o payload_rail.dxf // openscad payload_bay_rail.scad -D 'RENDER="rail_stl"' -o payload_rail.stl // openscad payload_bay_rail.scad -D 'RENDER="connector_stl"' -o payload_connector.stl // openscad payload_bay_rail.scad -D 'RENDER="detent_plunger_stl"' -o payload_detent.stl // openscad payload_bay_rail.scad -D 'RENDER="lab_adapter_stl"' -o payload_lab_adapter.stl // openscad payload_bay_rail.scad -D 'RENDER="rover_adapter_stl"' -o payload_rover_adapter.stl // openscad payload_bay_rail.scad -D 'RENDER="tank_adapter_stl"' -o payload_tank_adapter.stl // ============================================================ $fn = 64; e = 0.01; // ── Dovetail rail cross-section ─────────────────────────────────────────────── RAIL_W = 50.0; // rail total width (X) RAIL_T = 12.0; // rail total height (Z) RAIL_R = 2.0; // outer corner radius RAIL_LEN = 200.0; // default rail section length (Y) // Dovetail slot geometry DOVE_ANGLE = 30.0; // degrees from vertical (60° included angle) DOVE_H = 8.0; // slot depth (Z into rail from top) DOVE_SLOT_BOT= 28.0; // slot width at bottom (inner) // Derived: slot width at top = DOVE_SLOT_BOT + 2 * DOVE_H * tan(DOVE_ANGLE) DOVE_SLOT_TOP= DOVE_SLOT_BOT + 2 * DOVE_H * tan(DOVE_ANGLE); // ≈ 37.2 mm // Module tongue (male) clearance: 0.3 mm per side DOVE_CLEAR = 0.3; // → module tongue: bot_w = DOVE_SLOT_BOT - 2*DOVE_CLEAR, top_w = DOVE_SLOT_TOP + 2*DOVE_CLEAR // ── Spring ball detent ──────────────────────────────────────────────────────── // Ø6 mm steel ball presses up through module tongue into dimples in rail slot. DETENT_BALL_D = 6.0; // ball diameter DETENT_HOLE_D = 4.9; // dimple bore in rail slot bottom (ball seats in) DETENT_DEPTH = 1.5; // dimple depth (ball sinks in this far) DETENT_PITCH = 50.0; // dimple spacing along rail (Y) — module index positions DETENT_SPG_OD = 6.2; // plunger bore OD (ball + spring housing in module) DETENT_SPG_L = 16.0; // spring pocket depth in module tongue // ── Safety lock (M4 thumbscrew through module side into rail side groove) ───── LOCK_GROOVE_D = 4.5; // groove in rail side wall (M4 thumbscrew tip seats in) LOCK_GROOVE_DEPTH = 1.5; // groove depth into rail side // Lock groove runs full rail length (continuous slot) for tool-free slide + lock anywhere // ── 4-pin power+data connector ─────────────────────────────────────────────── // Pogo pin array mounted in rail body at CONN_Y from entry end. // Pin map: 1=GND 2=+5V 3=+12V 4=UART // Pogo: Ø2 mm spring contact (P75-E2 style), rated 2 A (power), 0.5 A (signal) CONN_Y = RAIL_LEN / 2; // connector centred in rail section CONN_PIN_D = 2.2; // pogo bore (2 mm pin + 0.2 mm clearance) CONN_PIN_SPC = 5.0; // pin centre-to-centre spacing CONN_N_PINS = 4; // GND / +5V / +12V / UART CONN_HOUSING_W= CONN_N_PINS * CONN_PIN_SPC + 4; // housing width (X) CONN_HOUSING_D= 8.0; // housing depth (Y, inside rail) CONN_HOUSING_H= DOVE_H - 1.0; // housing height; sits inside slot (flush with slot floor) // Connector pogo pins point upward into module pad targets. // ── Deck mounting holes ─────────────────────────────────────────────────────── MOUNT_PITCH = 50.0; // M4 FHCS hole pitch along rail (countersunk from bottom) MOUNT_INSET = 25.0; // first hole Y from rail end MOUNT_D = 4.3; // M4 clearance CSINK_D = 8.0; // M4 FHCS head diameter // ── Cross-variant adapter plates ───────────────────────────────────────────── ADAPT_T = 4.0; // adapter plate thickness ADAPT_OVHG = 10.0; // adapter overhang past rail edge each side (flange width) // SaltyLab deck: stem bore at centre LAB_STEM_BORE = 26.0; // clear stem Ø25 mm // SaltyRover deck: M4 bolt grid (spacing from rover_chassis_r2.scad) ROVER_BOLT_SPC = 40.0; // SaltyTank deck: M4 bolt grid (spacing from saltytank_chassis.scad) TANK_BOLT_SPC = 40.0; // Fasteners M3_D = 3.3; M4_D = 4.3; // ============================================================ // RENDER DISPATCH // ============================================================ RENDER = "assembly"; if (RENDER == "assembly") assembly(); else if (RENDER == "rail_2d") projection(cut = true) translate([0, 0, -0.5]) linear_extrude(1) rail_profile_2d(); else if (RENDER == "rail_stl") rail_section(RAIL_LEN); else if (RENDER == "connector_stl") connector_housing(); else if (RENDER == "detent_plunger_stl") detent_plunger(); else if (RENDER == "lab_adapter_stl") lab_rail_adapter(); else if (RENDER == "rover_adapter_stl") rover_rail_adapter(); else if (RENDER == "tank_adapter_stl") tank_rail_adapter(); // ============================================================ // ASSEMBLY PREVIEW // ============================================================ module assembly() { // Rail section color("Silver", 0.85) rail_section(RAIL_LEN); // Rover adapter under rail color("SteelBlue", 0.70) translate([0, 0, -ADAPT_T]) rover_rail_adapter(); // Ghost module sliding on %color("OliveDrab", 0.3) translate([0, 60, RAIL_T]) cube([RAIL_W + 10, 100, 40], center = true); // Connector position marker %color("Gold", 0.5) translate([0, CONN_Y, RAIL_T - DOVE_H]) cube([CONN_HOUSING_W, CONN_HOUSING_D, CONN_HOUSING_H], center = true); // Detent dimple markers for (dy = [MOUNT_INSET : DETENT_PITCH : RAIL_LEN - MOUNT_INSET]) %color("Red", 0.6) translate([0, dy, RAIL_T - DOVE_H]) cylinder(d = DETENT_HOLE_D, h = DETENT_DEPTH); } // ============================================================ // RAIL CROSS-SECTION 2D (DXF export) // ============================================================ // Outer profile minus dovetail slot. // For CNC milling from 50×12 mm aluminium bar, or waterjet from plate. // Also used for PETG prototype extrusion. module rail_profile_2d() { difference() { // Outer rail cross-section (rounded rect) minkowski() { square([RAIL_W - 2*RAIL_R, RAIL_T - 2*RAIL_R], center = true); circle(r = RAIL_R); } // Dovetail slot (trapezoid, open at top) translate([0, RAIL_T/2]) _dovetail_slot_2d(); } } // ── Dovetail slot 2D (trapezoid with open top) ─────────────────────────────── module _dovetail_slot_2d() { // Trapezoid: wider at top (open face), narrower at bottom. // Points listed clockwise from top-left: polygon([ [-DOVE_SLOT_TOP/2, 0], // top-left [ DOVE_SLOT_TOP/2, 0], // top-right [ DOVE_SLOT_BOT/2, -DOVE_H], // bottom-right [-DOVE_SLOT_BOT/2, -DOVE_H], // bottom-left ]); } // ── Dovetail slot for difference() operations (3D volume) ──────────────────── module _dovetail_slot_3d(length) { translate([0, -e, RAIL_T]) linear_extrude(DOVE_H + e) offset(delta = e) _dovetail_slot_2d(); } // ============================================================ // RAIL SECTION (3D — printable or aluminium) // ============================================================ module rail_section(len = RAIL_LEN) { difference() { // ── Extruded profile ──────────────────────────────────────── linear_extrude(len) rotate([0, 0, 90]) rail_profile_2d(); // ── Dovetail slot ──────────────────────────────────────────── translate([0, -e, RAIL_T]) rotate([0, 0, 0]) linear_extrude(len + 2*e) rotate([0, 0, 90]) offset(delta = e) _dovetail_slot_2d(); // ── Deck mounting holes (M4 FHCS, from bottom) ─────────────── for (my = [MOUNT_INSET : MOUNT_PITCH : len - MOUNT_INSET]) translate([0, my, -e]) cylinder(d = MOUNT_D, h = RAIL_T - DOVE_H + 2*e); // Countersinks on bottom face for (my = [MOUNT_INSET : MOUNT_PITCH : len - MOUNT_INSET]) translate([0, my, -e]) cylinder(d1 = CSINK_D, d2 = MOUNT_D, h = (CSINK_D - MOUNT_D) / (2 * tan(41))); // ── Spring detent dimples (slot bottom, at DETENT_PITCH) ────── for (dy = [MOUNT_INSET : DETENT_PITCH : len - MOUNT_INSET]) translate([0, dy, RAIL_T - DOVE_H - e]) cylinder(d = DETENT_HOLE_D, h = DETENT_DEPTH + e); // ── Safety-lock groove (continuous slot, both sides of rail) ── // M4 thumbscrew tip seats anywhere along groove for (sx = [-1, 1]) translate([sx * (RAIL_W/2 + e), -e, RAIL_T - DOVE_H/2]) rotate([0, 90, 0]) hull() { translate([0, 0, 0]) cylinder(d = LOCK_GROOVE_D, h = RAIL_W + 2*e); } // ── Connector housing pocket (at CONN_Y) ────────────────────── translate([0, CONN_Y, RAIL_T - DOVE_H - e]) cube([CONN_HOUSING_W + 0.4, CONN_HOUSING_D + 0.4, CONN_HOUSING_H + e], center = true); // ── Lightening slots (rail body between mounting holes) ──────── for (my = [MOUNT_INSET + 25 : MOUNT_PITCH : len - MOUNT_INSET - 25]) translate([0, my, RAIL_T/4]) cube([RAIL_W - 16, 20, RAIL_T/2 + 2*e], center = true); } } // ============================================================ // CONNECTOR HOUSING (pogo-pin insert, press-fits into rail pocket) // ============================================================ // 4× spring-loaded pogo pins (P75-E2, Ø2 mm, 6 mm travel). // Printed housing press-fits into rail pocket; pins protrude up into module. // Module has 4× Ø4 mm brass target pads at matching pitch. // // Pin map (left to right, looking at rail top from +Z): // Pin 1: GND Pin 2: +5 V Pin 3: +12 V Pin 4: UART module connector_housing() { hw = CONN_HOUSING_W; hd = CONN_HOUSING_D; hh = CONN_HOUSING_H; difference() { // Housing body (press-fit into rail pocket) cube([hw, hd, hh], center = true); // 4× pogo pin bores (through housing, top to bottom) for (i = [0 : CONN_N_PINS - 1]) { px = (i - (CONN_N_PINS - 1) / 2) * CONN_PIN_SPC; translate([px, 0, -hh/2 - e]) cylinder(d = CONN_PIN_D, h = hh + 2*e); } // Wire exit slot (bottom, routes into rail body) translate([0, 0, -hh/2 - e]) cube([hw - 6, hd/2, hh/3 + e], center = true); // Retention barbs (prevent housing pulling out of pocket) for (sx = [-1, 1]) translate([sx * (hw/2 - 1), 0, hh/4]) rotate([0, sx * 15, 0]) cube([2, hd + 2*e, 2.5], center = true); } // Pin polarity label recess (on top face, GND side) difference() { translate([0, 0, 0]) cube([0, 0, 0]); // null translate([-(CONN_N_PINS * CONN_PIN_SPC)/2 + 1, 0, hh/2 - 0.4]) cube([3, hd * 0.6, 0.5 + e], center = true); } } // ============================================================ // DETENT PLUNGER (lives in module tongue; print 2× per module) // ============================================================ // Press-fit into Ø6.2 mm bore in module tongue. // Includes spring pocket; ball seated on top. // Spring: Ø5.5 mm OD, 12 mm free length, ~2 N/mm (light detent). // Ball: Ø6 mm steel (standard bearing ball, purchase). module detent_plunger() { bore_d = DETENT_BALL_D + 0.2; // 6.2 mm body_od = DETENT_SPG_OD; body_len = DETENT_SPG_L; spg_d = 5.8; // spring OD spg_pocket = 10.0; // spring pocket depth (bottom of housing) difference() { cylinder(d = body_od, h = body_len); // Ball socket (top — partial sphere, retains ball) translate([0, 0, body_len]) sphere(d = bore_d); translate([0, 0, body_len - bore_d/4]) cylinder(d = bore_d, h = bore_d/2 + e); // Spring pocket (bottom) translate([0, 0, -e]) cylinder(d = spg_d + 0.3, h = spg_pocket + e); // Retention lip (allows push-in but prevents pullout before spring seated) translate([0, 0, spg_pocket]) cylinder(d1 = spg_d + 0.3, d2 = spg_d - 1, h = 1.5); } } // ============================================================ // CROSS-VARIANT DECK ADAPTER PLATES // ============================================================ // Thin plates that bolt to the robot deck and provide M4 threaded // studs (or through holes) for the rail mounting holes. // All adapters: RAIL_LEN × (RAIL_W + 2×ADAPT_OVHG) footprint. module _adapter_base() { adapt_l = RAIL_LEN; adapt_w = RAIL_W + 2*ADAPT_OVHG; difference() { // Plate cube([adapt_w, adapt_l, ADAPT_T], center = true); // Rail mounting holes (M4 FHCS up through adapter into rail bottom) for (my = [MOUNT_INSET : MOUNT_PITCH : adapt_l - MOUNT_INSET]) translate([0, my - adapt_l/2, -ADAPT_T/2 - e]) cylinder(d = MOUNT_D, h = ADAPT_T + 2*e); // Corner lightening for (cx = [-1, 1]) for (cy = [-1, 1]) translate([cx * (adapt_w/2 - 12), cy * (adapt_l/2 - 20), 0]) cylinder(d = 10, h = ADAPT_T + 2*e, center = true); } } // SaltyLab adapter: clears Ø25 mm stem, 4× M4 to lab chassis ring module lab_rail_adapter() { difference() { _adapter_base(); // Stem bore clearance (at centre of adapter) cylinder(d = LAB_STEM_BORE, h = ADAPT_T + 2*e, center = true); // 4× M4 mounting to lab chassis top ring (Ø44 mm bolt circle) for (a = [45, 135, 225, 315]) translate([22*cos(a), 22*sin(a), -ADAPT_T/2 - e]) cylinder(d = M4_D, h = ADAPT_T + 2*e); } } // SaltyRover adapter: 4× M4 to rover deck bolt grid module rover_rail_adapter() { adapt_l = RAIL_LEN; adapt_w = RAIL_W + 2*ADAPT_OVHG; difference() { _adapter_base(); // 2 rows × 3 cols of M4 bolts into rover deck for (rx = [-ROVER_BOLT_SPC/2, ROVER_BOLT_SPC/2]) for (ry = [-adapt_l/3, 0, adapt_l/3]) translate([rx, ry, -ADAPT_T/2 - e]) cylinder(d = M4_D, h = ADAPT_T + 2*e); } } // SaltyTank adapter: M4 to tank deck; relieved for deck cable slots module tank_rail_adapter() { adapt_l = RAIL_LEN; adapt_w = RAIL_W + 2*ADAPT_OVHG; difference() { _adapter_base(); // 2 rows × 3 cols of M4 bolts into tank deck for (rx = [-TANK_BOLT_SPC/2, TANK_BOLT_SPC/2]) for (ry = [-adapt_l/3, 0, adapt_l/3]) translate([rx, ry, -ADAPT_T/2 - e]) cylinder(d = M4_D, h = ADAPT_T + 2*e); // Deck cable slot clearance (tank deck has centre cable channel) translate([0, 0, 0]) cube([10, adapt_l - 40, ADAPT_T + 2*e], center = true); } }