// ============================================================
// RPLIDAR A1 Mount Bracket — Issue #596
// Agent   : sl-mechanical
// Date    : 2026-03-14
// Part catalogue:
//   1. tnut_base     — 2020 T-slot rail interface plate with M5 T-nut captive pockets
//   2. column        — hollow elevation column, 120 mm tall, 3 stiffening ribs, cable bore
//   3. scan_platform — top plate with Ø40 mm BC M3 mounting pattern, vibration seats
//   4. vibe_ring     — silicone FC-grommet isolation ring for scan_platform bolts
//   5. cable_guide   — snap-on cable management clip for column body
//
// BOM:
//   2 × M5×10 BHCS + M5 T-nuts   (tnut_base to rail)
//   4 × M3×8  SHCS                (scan_platform to RPLIDAR A1)
//   4 × M3 silicone FC grommets Ø8.5 OD / Ø3.2 bore  (anti-vibe)
//   4 × M3 hex nuts               (captured in scan_platform)
//
// Print settings (PETG):
//   tnut_base / column / scan_platform : 5 perimeters, 40 % gyroid, no supports
//   vibe_ring                          : 3 perimeters, 20 % gyroid, no supports
//   cable_guide                        : 3 perimeters, 30 % gyroid, no supports
//
// Export commands:
//   openscad -D 'RENDER="tnut_base"'     -o tnut_base.stl     rplidar_mount.scad
//   openscad -D 'RENDER="column"'        -o column.stl        rplidar_mount.scad
//   openscad -D 'RENDER="scan_platform"' -o scan_platform.stl rplidar_mount.scad
//   openscad -D 'RENDER="vibe_ring"'     -o vibe_ring.stl     rplidar_mount.scad
//   openscad -D 'RENDER="cable_guide"'   -o cable_guide.stl   rplidar_mount.scad
//   openscad -D 'RENDER="assembly"'      -o assembly.png      rplidar_mount.scad
// ============================================================

// ── Render selector ─────────────────────────────────────────
RENDER = "assembly"; // tnut_base | column | scan_platform | vibe_ring | cable_guide | assembly

// ── Global constants ────────────────────────────────────────
$fn = 64;
EPS = 0.01;

// 2020 rail
RAIL_W       = 20.0;   // extrusion cross-section
RAIL_H       = 20.0;
SLOT_NECK_H  =  3.2;   // T-slot opening width
TNUT_W       =  9.8;   // M5 T-nut width
TNUT_H       =  5.5;   // T-nut height (depth into slot)
TNUT_L       = 12.0;   // T-nut body length
M5_D         =  5.2;   // M5 clearance bore
M5_HEAD_D    =  9.5;   // M5 BHCS head diameter
M5_HEAD_H    =  4.0;   // M5 BHCS head height

// Base plate
BASE_L       = 60.0;   // length along rail axis
BASE_W       = 30.0;   // width across rail
BASE_T       =  8.0;   // plate thickness
BOLT_PITCH   = 40.0;   // M5 bolt pitch along rail (centre-to-centre)

// Elevation column
COL_OD       = 25.0;   // column outer diameter
COL_ID       = 17.0;   // inner bore (cable routing)
ELEV_H       = 120.0;  // scan plane above rail top face
COL_WALL     = (COL_OD - COL_ID) / 2;
RIB_W        =  3.0;   // stiffening rib width
RIB_H        =  3.5;   // rib radial height
CABLE_SLOT_W =  8.0;   // cable entry slot width
CABLE_SLOT_H =  5.0;   // cable entry slot height

// Scan platform
PLAT_D       = 60.0;   // platform disc diameter (clears RPLIDAR body Ø100 mm well)
PLAT_T       =  6.0;   // platform thickness
RPL_BC_D     = 40.0;   // RPLIDAR M3 bolt circle diameter (4 bolts at 45 °)
RPL_BORE_D   = 36.0;   // central pass-through for scan motor cable
M3_D         =  3.2;   // M3 clearance bore
M3_NUT_W     =  5.5;   // M3 hex nut across-flats
M3_NUT_H     =  2.4;   // M3 hex nut height
GROM_OD      =  8.5;   // FC silicone grommet OD
GROM_ID      =  3.2;   // grommet bore
GROM_H       =  3.0;   // grommet seat depth
CONN_SLOT_W  = 12.0;   // connector side-exit slot width
CONN_SLOT_H  =  5.0;   // connector slot height

// Vibe ring
VRING_OD     = GROM_OD + 1.6;  // printed retainer OD
VRING_ID     = GROM_ID + 0.3;  // pass-through with grommet seated
VRING_T      =  2.0;            // ring flange thickness

// Cable guide clip
CLIP_W       = 14.0;
CLIP_T       =  3.5;
CLIP_GAP     = COL_OD + 0.4;   // snap-fit gap (slight interference)
SNAP_T       =  1.8;
CABLE_CH_W   =  8.0;
CABLE_CH_H   =  5.0;

// ── Utility modules ─────────────────────────────────────────
module chamfer_cube(size, ch=1.0) {
    // simple chamfered box (bottom edge only for printability)
    hull() {
        translate([ch, ch, 0])
            cube([size[0]-2*ch, size[1]-2*ch, EPS]);
        translate([0, 0, ch])
            cube(size - [0, 0, ch]);
    }
}

module hex_pocket(af, depth) {
    // hex nut pocket (flat-to-flat af)
    cylinder(d = af / cos(30), h = depth, $fn = 6);
}

// ── Part 1: tnut_base ───────────────────────────────────────
module tnut_base() {
    difference() {
        // Body
        union() {
            chamfer_cube([BASE_L, BASE_W, BASE_T], ch=1.5);
            // Column socket boss centred on plate top face
            translate([BASE_L/2, BASE_W/2, BASE_T])
                cylinder(d=COL_OD + 4.0, h=8.0);
        }

        // M5 bolt holes (counterbored for BHCS heads from underneath)
        for (x = [BASE_L/2 - BOLT_PITCH/2, BASE_L/2 + BOLT_PITCH/2])
            translate([x, BASE_W/2, -EPS]) {
                cylinder(d=M5_D, h=BASE_T + 8.0 + 2*EPS);
                // counterbore from bottom
                cylinder(d=M5_HEAD_D, h=M5_HEAD_H + EPS);
            }

        // T-nut captive pockets (accessible from bottom)
        for (x = [BASE_L/2 - BOLT_PITCH/2, BASE_L/2 + BOLT_PITCH/2])
            translate([x - TNUT_L/2, BASE_W/2 - TNUT_W/2, BASE_T - TNUT_H])
                cube([TNUT_L, TNUT_W, TNUT_H + EPS]);

        // Column bore into boss
        translate([BASE_L/2, BASE_W/2, BASE_T - EPS])
            cylinder(d=COL_OD + 0.3, h=8.0 + 2*EPS);

        // Cable exit slot through base (offset 5 mm from column centre)
        translate([BASE_L/2 - CABLE_SLOT_W/2, BASE_W/2 + COL_OD/4, -EPS])
            cube([CABLE_SLOT_W, CABLE_SLOT_H, BASE_T + 8.0 + 2*EPS]);

        // Weight relief pockets on underside
        for (x = [BASE_L/2 - BOLT_PITCH/2 + 10, BASE_L/2 + BOLT_PITCH/2 - 10])
            for (y = [7, BASE_W - 7])
                translate([x - 5, y - 5, -EPS])
                    cube([10, 10, BASE_T/2]);
    }
}

// ── Part 2: column ──────────────────────────────────────────
module column() {
    // Actual column height: ELEV_H minus base boss engagement (8 mm) and platform seating (6 mm)
    col_h = ELEV_H - 8.0 - PLAT_T;

    difference() {
        union() {
            // Hollow tube
            cylinder(d=COL_OD, h=col_h);

            // Three 120°-spaced stiffening ribs along full height
            for (a = [0, 120, 240])
                rotate([0, 0, a])
                    translate([COL_OD/2 - EPS, -RIB_W/2, 0])
                        cube([RIB_H, RIB_W, col_h]);

            // Bottom spigot (fits into base boss bore)
            translate([0, 0, -6.0])
                cylinder(d=COL_OD - 0.4, h=6.0 + EPS);

            // Top spigot (seats into scan_platform recess)
            translate([0, 0, col_h - EPS])
                cylinder(d=COL_OD - 0.4, h=6.0);
        }

        // Inner cable bore
        translate([0, 0, -6.0 - EPS])
            cylinder(d=COL_ID, h=col_h + 12.0 + 2*EPS);

        // Cable entry slot at bottom (aligns with base slot)
        translate([-CABLE_SLOT_W/2, -COL_OD/2 - EPS, 2.0])
            cube([CABLE_SLOT_W, CABLE_SLOT_H + EPS, CABLE_SLOT_H]);

        // Cable exit slot at top (90° rotated for tidy routing)
        rotate([0, 0, 90])
            translate([-CABLE_SLOT_W/2, -COL_OD/2 - EPS, col_h - CABLE_SLOT_H - 4.0])
                cube([CABLE_SLOT_W, CABLE_SLOT_H + EPS, CABLE_SLOT_H]);

        // Cable clip snap groove (at mid-height)
        translate([0, 0, col_h / 2])
            difference() {
                cylinder(d=COL_OD + 2*RIB_H + 0.8, h=4.0, center=true);
                cylinder(d=COL_OD - 0.2, h=4.0 + 2*EPS, center=true);
            }
    }
}

// ── Part 3: scan_platform ───────────────────────────────────
module scan_platform() {
    difference() {
        union() {
            // Main disc
            cylinder(d=PLAT_D, h=PLAT_T);

            // Rim lip for stiffness
            translate([0, 0, PLAT_T])
                difference() {
                    cylinder(d=PLAT_D, h=2.0);
                    cylinder(d=PLAT_D - 4.0, h=2.0 + EPS);
                }
        }

        // Central cable pass-through
        translate([0, 0, -EPS])
            cylinder(d=RPL_BORE_D, h=PLAT_T + 4.0);

        // Column spigot socket (bottom recess)
        translate([0, 0, -EPS])
            cylinder(d=COL_OD - 0.4 + 0.4, h=6.0);

        // RPLIDAR M3 mounting holes — 4× on Ø40 BC at 45°/135°/225°/315°
        for (a = [45, 135, 225, 315])
            rotate([0, 0, a])
                translate([RPL_BC_D/2, 0, -EPS]) {
                    // Through bore
                    cylinder(d=M3_D, h=PLAT_T + 2*EPS);
                    // Grommet seat (countersunk from top)
                    translate([0, 0, PLAT_T - GROM_H])
                        cylinder(d=GROM_OD + 0.3, h=GROM_H + EPS);
                    // Captured M3 hex nut pocket (from bottom)
                    translate([0, 0, 1.5])
                        hex_pocket(M3_NUT_W + 0.3, M3_NUT_H + 0.2);
                }

        // Connector side-exit slots (2× opposing, at 0° and 180°)
        for (a = [0, 180])
            rotate([0, 0, a])
                translate([-CONN_SLOT_W/2, PLAT_D/2 - CONN_SLOT_H, -EPS])
                    cube([CONN_SLOT_W, CONN_SLOT_H + EPS, PLAT_T + 2*EPS]);

        // Weight relief pockets (2× lateral)
        for (a = [90, 270])
            rotate([0, 0, a])
                translate([-10, 15, 1.5])
                    cube([20, 8, PLAT_T - 3.0]);
    }
}

// ── Part 4: vibe_ring ───────────────────────────────────────
// Printed silicone-grommet retainer ring — press-fits over M3 bolt with grommet seated
module vibe_ring() {
    difference() {
        union() {
            cylinder(d=VRING_OD, h=VRING_T + GROM_H);
            // Flange
            cylinder(d=VRING_OD + 2.0, h=VRING_T);
        }
        // Bore
        translate([0, 0, -EPS])
            cylinder(d=VRING_ID, h=VRING_T + GROM_H + 2*EPS);
    }
}

// ── Part 5: cable_guide ─────────────────────────────────────
// Snap-on cable clip for column mid-section
module cable_guide() {
    arm_t = SNAP_T;
    gap   = CLIP_GAP;

    difference() {
        union() {
            // Saddle body (U-shape wrapping column)
            difference() {
                cylinder(d=gap + 2*CLIP_T, h=CLIP_W);
                translate([0, 0, -EPS])
                    cylinder(d=gap, h=CLIP_W + 2*EPS);
                // Open front slot for snap insertion
                translate([-gap/2, 0, -EPS])
                    cube([gap, gap/2 + CLIP_T + EPS, CLIP_W + 2*EPS]);
            }

            // Snap arms
            for (s = [-1, 1])
                translate([s*(gap/2 - arm_t), 0, 0])
                    mirror([s < 0 ? 1 : 0, 0, 0])
                        translate([0, -arm_t/2, 0])
                            cube([arm_t + 1.5, arm_t, CLIP_W]);

            // Cable channel bracket (side-mounted)
            translate([gap/2 + CLIP_T, -(CABLE_CH_W/2 + CLIP_T), 0])
                cube([CLIP_T + CABLE_CH_H, CABLE_CH_W + 2*CLIP_T, CLIP_W]);
        }

        // Cable channel cutout
        translate([gap/2 + CLIP_T + CLIP_T - EPS, -CABLE_CH_W/2, -EPS])
            cube([CABLE_CH_H + EPS, CABLE_CH_W, CLIP_W + 2*EPS]);

        // Snap tip undercut (both arms)
        for (s = [-1, 1])
            translate([s*(gap/2 + CLIP_T + 1.0), -arm_t, -EPS])
                rotate([0, 0, s*30])
                    cube([2, arm_t*2, CLIP_W + 2*EPS]);
    }
}

// ── Assembly / render dispatch ───────────────────────────────
module assembly() {
    // tnut_base at origin
    color("SteelBlue")
        tnut_base();

    // column rising from base boss
    color("DodgerBlue")
        translate([BASE_L/2, BASE_W/2, BASE_T + 8.0 - 6.0])
            column();

    // scan_platform at top of column
    col_h_actual = ELEV_H - 8.0 - PLAT_T;
    color("CornflowerBlue")
        translate([BASE_L/2, BASE_W/2, BASE_T + 8.0 - 6.0 + col_h_actual + 6.0 - EPS])
            scan_platform();

    // vibe rings (4×) seated in platform holes
    for (a = [45, 135, 225, 315])
        color("Gray", 0.7)
            translate([BASE_L/2, BASE_W/2,
                       BASE_T + 8.0 - 6.0 + col_h_actual + 6.0 + PLAT_T - GROM_H])
                rotate([0, 0, a])
                    translate([RPL_BC_D/2, 0, 0])
                        vibe_ring();

    // cable_guide clipped at column mid-height
    color("LightSteelBlue")
        translate([BASE_L/2, BASE_W/2,
                   BASE_T + 8.0 - 6.0 + (ELEV_H - 8.0 - PLAT_T)/2 - CLIP_W/2])
            cable_guide();
}

// ── Dispatch ────────────────────────────────────────────────
if      (RENDER == "tnut_base")     tnut_base();
else if (RENDER == "column")        column();
else if (RENDER == "scan_platform") scan_platform();
else if (RENDER == "vibe_ring")     vibe_ring();
else if (RENDER == "cable_guide")   cable_guide();
else                                assembly();
