// ============================================================ // payload_bay_modules.scad — Payload Bay Module Template + Examples // Issue: #170 Agent: sl-mechanical Date: 2026-03-01 // ============================================================ // // ── HOW TO CREATE A NEW MODULE ────────────────────────────────────────────── // // 1. Copy the "MODULE TEMPLATE" section at the bottom of this file. // 2. Set MODULE_L to your module's Y length (min 60 mm). // 3. Add your payload geometry on top of the _module_base() call. // 4. The _module_base() provides: // • Male dovetail tongue (slides into rail slot) // • Spring detent bore(s) (for Ø6 mm ball + spring plunger) // • Connector target pads (4× Ø4 mm brass, matching rail pogo pins) // • Safety-lock M4 thumbscrew bore (side of tongue) // • Bottom-face flush with rail top (Z = 0 at rail top / module base) // 5. Your payload geometry sits at Z ≥ 0 (above the rail top face). // 6. Add a RENDER dispatch entry and export command. // // ── DOVETAIL TONGUE GEOMETRY ──────────────────────────────────────────────── // // TONGUE_BOT = DOVE_SLOT_BOT - 2*DOVE_CLEAR = 28.0 - 0.6 = 27.4 mm // TONGUE_TOP = DOVE_SLOT_TOP + 2*DOVE_CLEAR = 37.2 + 0.6 = 37.8 mm // TONGUE_H = DOVE_H + 0.2 (slight extra depth, no binding at corners) // // Tongue runs full module length (-Y to +Y in module coords). // Module body sits on top of tongue at Z = 0. // // ── CONNECTOR PADS ────────────────────────────────────────────────────────── // 4× Ø4 mm brass discs press-fit into tongue bottom face. // Pad positions: must align with rail connector at CONN_Y when module // is slid to its intended position (any detent step). // Default: pads centred in module length → module must be placed so its // centre aligns with CONN_Y on rail. Or: set MODULE_CONN_OFFSET to shift. // // ── PAYLOAD RATING ────────────────────────────────────────────────────────── // 2 kg rated payload when safety-lock thumbscrew is tightened. // Detent-only (no thumbscrew): ~0.5 kg (impact / vibration condition). // // ── RENDERED EXAMPLES ──────────────────────────────────────────────────────── // Part 1 — cargo_tray() 200×100 mm cargo tray, 30 mm walls // Part 2 — camera_boom() L-arm with sensor_rail-compatible head // Part 3 — cup_holder() Ø80 mm tapered cup cradle // // RENDER options: // "assembly" all 3 modules on ghost rail // "base_stl" module base / tongue only (universal) // "cargo_tray_stl" cargo tray module // "camera_boom_stl" camera boom module // "cup_holder_stl" cup holder module // "target_pad_2d" DXF — Ø4 mm brass target pad profile // // Export: // openscad payload_bay_modules.scad -D 'RENDER="base_stl"' -o payload_base.stl // openscad payload_bay_modules.scad -D 'RENDER="cargo_tray_stl"' -o payload_cargo_tray.stl // openscad payload_bay_modules.scad -D 'RENDER="camera_boom_stl"' -o payload_camera_boom.stl // openscad payload_bay_modules.scad -D 'RENDER="cup_holder_stl"' -o payload_cup_holder.stl // ============================================================ $fn = 64; e = 0.01; // ── Rail geometry constants (must match payload_bay_rail.scad) ──────────────── RAIL_W = 50.0; RAIL_T = 12.0; DOVE_ANGLE = 30.0; DOVE_H = 8.0; DOVE_SLOT_BOT = 28.0; DOVE_SLOT_TOP = DOVE_SLOT_BOT + 2 * DOVE_H * tan(DOVE_ANGLE); DOVE_CLEAR = 0.3; DETENT_PITCH = 50.0; DETENT_BALL_D = 6.0; DETENT_SPG_OD = 6.2; DETENT_SPG_L = 16.0; CONN_PIN_SPC = 5.0; CONN_N_PINS = 4; CONN_HOUSING_D = 8.0; // ── Module tongue (male dovetail) geometry ──────────────────────────────────── TONGUE_BOT = DOVE_SLOT_BOT - 2*DOVE_CLEAR; // 27.4 mm TONGUE_TOP = DOVE_SLOT_TOP + 2*DOVE_CLEAR; // 37.8 mm TONGUE_H = DOVE_H + 0.2; // 8.2 mm (slight extra depth) // ── Connector target pad ────────────────────────────────────────────────────── TARGET_PAD_D = 4.0; // brass pad OD (slightly larger than pogo Ø2 mm) TARGET_PAD_T = 1.5; // brass pad thickness TARGET_PAD_RECESS = 1.3; // press-fit recess depth (pad is 0.2 mm proud) // 4 pads at CONN_PIN_SPC pitch, centred in module tongue CONN_SPAN = (CONN_N_PINS - 1) * CONN_PIN_SPC; // 15 mm // ── Module safety lock ──────────────────────────────────────────────────────── LOCK_BOLT_D = 4.3; // M4 thumbscrew bore through tongue side LOCK_BOLT_Z = TONGUE_H/2; // Z of thumbscrew CL from tongue bottom // Thumbscrew tightens against continuous groove in rail side. // Fasteners M3_D = 3.3; M4_D = 4.3; // ============================================================ // RENDER DISPATCH // ============================================================ RENDER = "assembly"; if (RENDER == "assembly") assembly(); else if (RENDER == "base_stl") _module_base(80); else if (RENDER == "cargo_tray_stl") cargo_tray(); else if (RENDER == "camera_boom_stl") camera_boom(); else if (RENDER == "cup_holder_stl") cup_holder(); else if (RENDER == "target_pad_2d") { projection(cut = true) translate([0, 0, -0.5]) linear_extrude(1) circle(d = TARGET_PAD_D); } // ============================================================ // ASSEMBLY PREVIEW // ============================================================ module assembly() { // Ghost rail %color("Silver", 0.25) translate([0, 0, -RAIL_T]) cube([RAIL_W, 600, RAIL_T], center = true); // Cargo tray at Y=50 (first detent position) color("OliveDrab", 0.85) translate([0, 50, 0]) cargo_tray(); // Cup holder at Y=300 color("RoyalBlue", 0.85) translate([0, 300, 0]) cup_holder(); // Camera boom at Y=500 color("DarkSlateGray", 0.85) translate([0, 500, 0]) camera_boom(); } // ============================================================ // UNIVERSAL MODULE BASE (_module_base) // ============================================================ // All modules use this as their foundation. // Provides: male dovetail tongue, detent bore, connector pads, lock bore. // // module_len : module length in Y (≥ 60 mm) // n_detents : how many ball detent bores to include (1 or 2) // conn_offset: Y offset of connector pads from module centre (default 0) // // After calling _module_base(), add payload geometry at Z = 0 and above. // The tongue occupies Z = -(TONGUE_H) to Z = 0. // Rail top face is at Z = 0. module _module_base(module_len, n_detents = 1, conn_offset = 0) { ml = module_len; difference() { // ── Dovetail tongue (male) ──────────────────────────────────── translate([0, ml/2, -TONGUE_H/2]) linear_extrude(TONGUE_H, center = true, twist = 0, convexity = 2) _tongue_profile_2d(); // ── Spring detent bore(s) (up through tongue top face) ──────── // 1 detent: at module centre; 2 detents: at ±25 mm from centre for (i = [0 : n_detents - 1]) { dy = (n_detents == 1) ? ml/2 : ml/2 + (i - (n_detents-1)/2) * DETENT_PITCH; translate([0, dy, -(TONGUE_H/2) - DETENT_SPG_L/2]) cylinder(d = DETENT_SPG_OD, h = DETENT_SPG_L + TONGUE_H/2 + e); } // ── Connector target pad recesses (tongue bottom face) ──────── for (i = [0 : CONN_N_PINS - 1]) { px = (i - (CONN_N_PINS-1)/2) * CONN_PIN_SPC; translate([px, ml/2 + conn_offset, -TONGUE_H + e]) cylinder(d = TARGET_PAD_D + 0.1, h = TARGET_PAD_RECESS + e); } // ── Safety-lock M4 thumbscrew bore (right side of tongue) ───── // Bore exits tongue right face, tip bears on rail side groove translate([TONGUE_TOP/2 + e, ml/2, LOCK_BOLT_Z - TONGUE_H]) rotate([0, 90, 0]) cylinder(d = LOCK_BOLT_D, h = (TONGUE_TOP - TONGUE_BOT)/2 + 6 + e); // ── Lead-in chamfer on entry end of tongue ──────────────────── // 2 mm chamfer on bottom corners of tongue at Y=0 end translate([0, 0, -TONGUE_H]) rotate([45, 0, 0]) cube([TONGUE_TOP + 2*e, 4, 4], center = true); } } // ── Tongue 2D cross-section (male dovetail, trapezoid wider at bottom) ──────── // Module tongue: narrower at top (entering slot), wider at bottom (interlocking). // Note orientation: tongue points DOWN (-Z), so wider face is at bottom (-Z). module _tongue_profile_2d() { polygon([ [-TONGUE_TOP/2, 0], // top-left (at Z = 0, flush with rail top) [ TONGUE_TOP/2, 0], // top-right [ TONGUE_BOT/2, -TONGUE_H], // bottom-right (interlocking face) [-TONGUE_BOT/2, -TONGUE_H], // bottom-left ]); } // ============================================================ // PART 1 — CARGO TRAY // ============================================================ // 200×100 mm open tray for transporting small items. // 30 mm walls, chamfered rim, 4× drainage holes. // Two Velcro strip slots on tray floor for cargo retention. // Module length: 200 mm (4 detent positions). // Weight budget: tray printed ~180 g; payload up to 2 kg. TRAY_L = 200.0; // module Y length TRAY_W = 100.0; // tray interior width (X) TRAY_WALL = 3.0; // tray wall thickness TRAY_H = 30.0; // tray wall height above module base TRAY_FLOOR_T = 3.0; // tray floor thickness module cargo_tray() { // Mount base on rail (2 detents at ±50 mm from module centre) _module_base(TRAY_L, n_detents = 2); difference() { union() { // ── Tray body on top of base ──────────────────────────── translate([-TRAY_W/2 - TRAY_WALL, 0, 0]) cube([TRAY_W + 2*TRAY_WALL, TRAY_L, TRAY_FLOOR_T + TRAY_H]); // ── Corner gussets (stiffening for 2 kg load) ─────────── for (cx = [-1, 1]) for (cy = [0, 1]) hull() { translate([cx * TRAY_W/2, cy * (TRAY_L - TRAY_WALL), 0]) cube([TRAY_WALL + e, TRAY_WALL + e, TRAY_FLOOR_T + TRAY_H * 0.6], center = true); translate([cx * (TRAY_W/2 - 10), cy * (TRAY_L - TRAY_WALL), 0]) cylinder(d = TRAY_WALL * 2, h = e); } } // ── Tray interior cavity ───────────────────────────────────── translate([-TRAY_W/2, TRAY_WALL, TRAY_FLOOR_T]) cube([TRAY_W, TRAY_L - 2*TRAY_WALL, TRAY_H + e]); // ── Drainage holes (4× Ø8 mm in floor) ────────────────────── for (dx = [-TRAY_W/4, TRAY_W/4]) for (dy = [TRAY_L/4, 3*TRAY_L/4]) translate([dx, dy, -e]) cylinder(d = 8, h = TRAY_FLOOR_T + 2*e); // ── Velcro slot × 2 (25 mm wide grooves in floor) ──────────── for (dy = [TRAY_L/3, 2*TRAY_L/3]) translate([0, dy, TRAY_FLOOR_T - 1.5]) cube([TRAY_W - 10, 25, 2 + e], center = true); // ── Rim chamfer (top inner edge, ergonomic) ─────────────────── translate([0, TRAY_L/2, TRAY_FLOOR_T + TRAY_H + e]) cube([TRAY_W + 2*TRAY_WALL + 2*e, TRAY_L + 2*e, 4], center = true); // ── Side slots for bungee cord retention (3× each long side) ─ for (sx = [-1, 1]) for (sy = [TRAY_L/4, TRAY_L/2, 3*TRAY_L/4]) translate([sx * (TRAY_W/2 + TRAY_WALL/2), sy, TRAY_FLOOR_T + TRAY_H/2]) cube([TRAY_WALL + 2*e, 8, 6], center = true); } } // ============================================================ // PART 2 — CAMERA BOOM // ============================================================ // L-shaped arm: vertical mast + horizontal boom. // Boom head accepts sensor_rail 2020 T-slot (RAIL_W/2 bolt pattern). // Sensor head can be rotated 0/90/180° and locked with M4 bolt. // Module length: 80 mm; arm rises 120 mm, boom extends 80 mm forward. BOOM_MODULE_L = 80.0; BOOM_MAST_H = 120.0; // mast height above rail top (Z) BOOM_ARM_L = 80.0; // horizontal boom length (+Y forward) BOOM_ARM_W = 20.0; // arm cross-section width BOOM_ARM_T = 20.0; // arm cross-section height BOOM_HEAD_W = 50.0; // sensor head width (matches 2020 rail flange) BOOM_HEAD_H = 20.0; // sensor head plate height BOOM_HEAD_T = 5.0; // sensor head plate thickness module camera_boom() { _module_base(BOOM_MODULE_L, n_detents = 1); difference() { union() { // ── Mast (vertical column from module body) ────────────── translate([-BOOM_ARM_W/2, BOOM_MODULE_L/2 - BOOM_ARM_T/2, 0]) cube([BOOM_ARM_W, BOOM_ARM_T, BOOM_MAST_H]); // ── Horizontal boom (from mast top, extends +Y) ────────── translate([-BOOM_ARM_W/2, BOOM_MODULE_L/2 - BOOM_ARM_T/2, BOOM_MAST_H - BOOM_ARM_W]) cube([BOOM_ARM_W, BOOM_ARM_L, BOOM_ARM_W]); // ── Sensor head plate (at boom tip) ────────────────────── translate([-BOOM_HEAD_W/2, BOOM_MODULE_L/2 - BOOM_ARM_T/2 + BOOM_ARM_L, BOOM_MAST_H - BOOM_ARM_W - BOOM_HEAD_H/2 + BOOM_ARM_W/2]) cube([BOOM_HEAD_W, BOOM_HEAD_T, BOOM_HEAD_H]); // ── Junction gussets (mast + horizontal boom) ──────────── translate([-BOOM_ARM_W/2, BOOM_MODULE_L/2 - BOOM_ARM_T/2, BOOM_MAST_H - BOOM_ARM_W]) rotate([45, 0, 0]) cube([BOOM_ARM_W, BOOM_ARM_W * 0.7, BOOM_ARM_W * 0.7]); } // ── 2020 sensor-rail bolt pattern in head plate ─────────────── // 2× M5 slots (matches sensor_rail.scad tank_clamp slot geometry) for (sz = [-BOOM_HEAD_H/4, BOOM_HEAD_H/4]) translate([0, BOOM_MODULE_L/2 - BOOM_ARM_T/2 + BOOM_ARM_L + BOOM_HEAD_T + e, BOOM_MAST_H - BOOM_ARM_W/2 + sz]) rotate([90, 0, 0]) hull() { translate([-6, 0, 0]) cylinder(d = 5.3, h = BOOM_HEAD_T + 2*e); translate([+6, 0, 0]) cylinder(d = 5.3, h = BOOM_HEAD_T + 2*e); } // ── Tilt angle slots (3 positions: 0°, ±15°) ───────────────── for (ta = [-15, 0, 15]) { translate([0, BOOM_MODULE_L/2 - BOOM_ARM_T/2 + BOOM_ARM_L/2, BOOM_MAST_H - BOOM_ARM_W/2]) rotate([ta, 0, 0]) translate([0, BOOM_ARM_L/2, 0]) cylinder(d = 4.3, h = BOOM_ARM_W + 2*e, center = true); } // ── Cable tie slots in mast ─────────────────────────────────── for (cz = [BOOM_MAST_H * 0.3, BOOM_MAST_H * 0.6]) translate([0, BOOM_MODULE_L/2, cz]) cube([BOOM_ARM_W + 2*e, 4, 4], center = true); // ── Lightening pockets in mast ──────────────────────────────── translate([0, BOOM_MODULE_L/2, BOOM_MAST_H * 0.4]) cube([BOOM_ARM_W - 6, BOOM_ARM_T - 6, BOOM_MAST_H * 0.4], center = true); } } // ============================================================ // PART 3 — CUP HOLDER // ============================================================ // Tapered cup cradle for standard travel mugs / water bottles. // Inner diameter: 80 mm at top, 68 mm at bottom (matches Ø70 mm typical mug). // Flexible gripper ribs (cut slots) provide spring retention. // Drain hole at bottom for condensation. // Module length: 80 mm. CUP_MODULE_L = 80.0; CUP_INNER_TOP = 80.0; // inner bore OD at top CUP_INNER_BOT = 68.0; // inner bore OD at bottom (taper for grip) CUP_OUTER_T = 4.0; // wall thickness CUP_H = 80.0; // cup holder height CUP_GRIP_SLOTS = 8; // number of flex slots (spring grip) CUP_SLOT_W = 2.5; // flex slot width CUP_SLOT_H = 40.0; // flex slot height (from top) module cup_holder() { _module_base(CUP_MODULE_L, n_detents = 1); difference() { union() { // ── Outer shell (tapered cylinder) ─────────────────────── cylinder(d1 = CUP_INNER_BOT + 2*CUP_OUTER_T, d2 = CUP_INNER_TOP + 2*CUP_OUTER_T, h = CUP_H); // ── Base flange (connects to module body footprint) ─────── hull() { cylinder(d = CUP_INNER_BOT + 2*CUP_OUTER_T + 4, h = 4); translate([0, CUP_MODULE_L/2, 0]) cube([RAIL_W, CUP_MODULE_L, 4], center = true); } } // ── Inner bore (tapered cup cavity) ────────────────────────── translate([0, 0, CUP_OUTER_T]) cylinder(d1 = CUP_INNER_BOT, d2 = CUP_INNER_TOP, h = CUP_H + e); // ── Base drain hole ────────────────────────────────────────── translate([0, 0, -e]) cylinder(d = 12, h = CUP_OUTER_T + 2*e); // ── Flex grip slots (from top down) ────────────────────────── // Slots allow upper rim to flex inward and grip cup body for (i = [0 : CUP_GRIP_SLOTS - 1]) { angle = i * 360 / CUP_GRIP_SLOTS; rotate([0, 0, angle]) translate([CUP_INNER_TOP/2 + CUP_OUTER_T/2, 0, CUP_H - CUP_SLOT_H]) cube([CUP_OUTER_T + 2*e, CUP_SLOT_W, CUP_SLOT_H + e], center = true); } // ── Exterior branding recess (optional label area) ──────────── translate([CUP_INNER_BOT/2 + CUP_OUTER_T/2 - 0.5, 0, CUP_H/2]) rotate([0, 90, 0]) cube([25, 40, 1 + e], center = true); } } // ============================================================ // ══ MODULE TEMPLATE ═══════════════════════════════════════════ // ══ Copy this block to create a new payload module ════════════ // ============================================================ // // module my_new_module() { // MY_LEN = 120.0; // module length — must be multiple of DETENT_PITCH (50 mm) // // // Always start with the base (provides tongue, pads, detent bore) // _module_base(MY_LEN, n_detents = 2); // // // Add your payload geometry here. // // Z = 0 is the rail top face / module mounting face. // // Build upward from Z = 0. // // difference() { // union() { // // Example: a simple platform // translate([-(RAIL_W + 10)/2, 0, 0]) // cube([RAIL_W + 10, MY_LEN, 10]); // // // Add your geometry... // } // // // Add your cutouts... // } // } // // ── Don't forget to: ────────────────────────────────────────── // 1. Add else if (RENDER == "my_module_stl") my_new_module(); // in the RENDER DISPATCH block above. // 2. Add export command in BOM / README. // 3. Test: verify tongue fits rail slot (should slide with 0.3 mm clearance). // 4. Verify connector pad positions align with CONN_Y on rail. // ============================================================