// ============================================================
// 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);
    }
}
