// ============================================================
// canable_mount.scad — CANable 2.0 USB-CAN Adapter Cradle
// Issue #654 / sl-mechanical  2026-03-16
// ============================================================
// Snap-fit cradle for CANable 2.0 PCB (~60 × 18 × 10 mm).
// Attaches to 2020 aluminium T-slot rail via 2× M5 T-nuts.
//
// Port access:
//   USB-C port       — X− end wall cutout (connector protrudes through)
//   CAN terminal     — X+ end wall cutout (CANH / CANL / GND wire exit)
//   LED status window— slot in Y+ side wall, PCB top-face LEDs visible
//
// Retention: snap-fit cantilever lips on both side walls (PETG flex).
// Cable strain relief: zip-tie boss pair on X+ shelf (CAN wires).
//
// ⚠  VERIFY WITH CALIPERS BEFORE PRINTING:
//    PCB_L, PCB_W          board outline
//    USBC_W, USBC_H        USB-C shell at X− edge
//    TERM_W, TERM_H        3-pos terminal block at X+ edge
//    LED_X_CTR, LED_WIN_W  LED window position on Y+ wall
//
// Print settings (PETG):
//   3 perimeters, 40 % gyroid infill, no supports, 0.2 mm layer
//   Print orientation: open face UP (as modelled)
//
// BOM:
//   2 × M5×10 BHCS  +  2 × M5 slide-in T-nut  (2020 rail)
//
// Export commands:
//   openscad -D 'RENDER="mount"'    -o canable_mount.stl    canable_mount.scad
//   openscad -D 'RENDER="assembly"' -o canable_assembly.png canable_mount.scad
// ============================================================

RENDER = "assembly"; // mount | assembly

$fn = 48;
EPS = 0.01;

// ── ⚠ Verify before printing ─────────────────────────────────
// CANable 2.0 PCB
PCB_L    = 60.0;   // board length  (X: USB-C end → terminal end)
PCB_W    = 18.0;   // board width   (Y)
PCB_T    =  1.6;   // board thickness
COMP_H   =  8.5;   // tallest component above board (USB-C shell ≈ 3.5 mm;
                   //   terminal block ≈ 8.5 mm)

// USB-C connector (at X− end face of PCB)
USBC_W   =  9.5;   // connector outer width
USBC_H   =  3.8;   // connector outer height above board surface
USBC_Z0  =  0.0;   // connector bottom offset above board surface

// CAN screw-terminal block (at X+ end face, 3-pos 5.0 mm pitch)
TERM_W   = 16.0;   // terminal block span (3 × 5 mm + housing)
TERM_H   =  9.0;   // terminal block height above board surface
TERM_Z0  =  0.5;   // terminal bottom offset above board surface

// Status LED window (LEDs near USB-C end on PCB top face)
//   Rectangular slot cut in Y+ side wall — LEDs visible from the side
LED_X_CTR  = 11.0;  // LED zone centre measured from PCB X− edge
LED_WIN_W  = 14.0;  // window width (X)
LED_WIN_H  =  5.5;  // window height (Z) — opens top portion of side wall

// ── Cradle geometry ──────────────────────────────────────────
WALL_T   = 2.5;    // side/end wall thickness
FLOOR_T  = 4.0;    // floor plate thickness (accommodates M5 BHCS head pocket)
CL_SIDE  = 0.30;   // Y clearance per side (total 0.6 mm play)
CL_END   = 0.40;   // X clearance per end

// Interior cavity
INN_W    = PCB_W + 2*CL_SIDE;          // Y span
INN_L    = PCB_L + 2*CL_END;           // X span
INN_H    = PCB_T + COMP_H + 1.2;       // Z height (board + tallest comp + margin)

// Outer body
OTR_W    = INN_W  + 2*WALL_T;          // Y
OTR_L    = INN_L  + 2*WALL_T;          // X
OTR_H    = FLOOR_T + INN_H;            // Z

// PCB reference origin within body (lower-left corner of board)
PCB_X0   = WALL_T + CL_END;            // board X start inside body
PCB_Y0   = WALL_T + CL_SIDE;           // board Y start inside body
PCB_Z0   = FLOOR_T;                    // board bottom sits on floor

// ── Snap-fit lips ─────────────────────────────────────────────
// Cantilever ledge on inner face of each side wall, at PCB-top Z.
// Tapered (chamfered) entry guides PCB in from above.
SNAP_IN  = 0.8;    // how far inward ledge protrudes over PCB edge
SNAP_T   = 1.2;    // snap-arm thickness (thin for PETG flex)
SNAP_H   = 4.0;    // cantilever arm height (root at OTR_H, tip near PCB_Z0+PCB_T)
SNAP_L   = 18.0;   // arm length along X (centred on PCB, shorter = more flex)
// Snap on Y− wall protrudes in +Y direction; Y+ wall protrudes in −Y direction

// ── M5 T-nut mount (2020 rail) ────────────────────────────────
M5_D       =  5.3;   // M5 bolt clearance bore
M5_HEAD_D  =  9.5;   // M5 BHCS head pocket diameter (from bottom face)
M5_HEAD_H  =  3.0;   // BHCS head pocket depth
M5_SPAC    = 20.0;   // bolt spacing along X (centred on cradle)
// Standard M5 slide-in T-nuts used — no T-nut pocket moulded in.

// ── Cable strain relief ───────────────────────────────────────
// Two zip-tie anchor bosses on a shelf inside the X+ end, straddling
// the CAN terminal wires.
SR_BOSS_OD =  7.0;   // boss outer diameter
SR_BOSS_H  =  5.5;   // boss height above floor
SR_SLOT_W  =  3.5;   // zip-tie slot width
SR_SLOT_T  =  2.2;   // zip-tie slot through-height
// Boss Y positions (straddle terminal block)
SR_Y1      = WALL_T + INN_W * 0.25;
SR_Y2      = WALL_T + INN_W * 0.75;
SR_X       = OTR_L - WALL_T - SR_BOSS_OD/2 - 2.5;  // just inside X+ end wall

// ─────────────────────────────────────────────────────────────
module canable_mount() {
    difference() {
        // ── Outer solid body ──────────────────────────────────
        union() {
            cube([OTR_L, OTR_W, OTR_H]);

            // ── Snap cantilever arms on Y− wall (protrude inward +Y) ──
            // Arms hang down from top of Y− wall inner face.
            // Root is flush with inner face (Y = WALL_T); tip at PCB level.
            translate([OTR_L/2 - SNAP_L/2, WALL_T - SNAP_T, OTR_H - SNAP_H])
                cube([SNAP_L, SNAP_T, SNAP_H]);

            // ── Snap cantilever arms on Y+ wall (protrude inward −Y) ──
            translate([OTR_L/2 - SNAP_L/2, OTR_W - WALL_T, OTR_H - SNAP_H])
                cube([SNAP_L, SNAP_T, SNAP_H]);

            // ── Cable strain relief bosses (X+ end, inside) ────
            for (sy = [SR_Y1, SR_Y2])
                translate([SR_X, sy, 0])
                    cylinder(d=SR_BOSS_OD, h=SR_BOSS_H);
        }

        // ── Interior cavity ───────────────────────────────────
        translate([WALL_T, WALL_T, FLOOR_T])
            cube([INN_L, INN_W, INN_H + EPS]);

        // ── USB-C cutout — X− end wall ────────────────────────
        // Centred on PCB width; opened from board surface upward
        translate([-EPS,
                   PCB_Y0 + PCB_W/2 - (USBC_W + 1.5)/2,
                   PCB_Z0 + USBC_Z0 - 0.5])
            cube([WALL_T + 2*EPS, USBC_W + 1.5, USBC_H + 2.5]);

        // ── CAN terminal cutout — X+ end wall ─────────────────
        // Full terminal width + 2 mm margin for screwdriver access;
        // height clears terminal block + wire bend radius
        translate([OTR_L - WALL_T - EPS,
                   PCB_Y0 + PCB_W/2 - (TERM_W + 2.0)/2,
                   PCB_Z0 + TERM_Z0 - 0.5])
            cube([WALL_T + 2*EPS, TERM_W + 2.0, TERM_H + 5.0]);

        // ── LED status window — Y+ side wall ─────────────────
        // Rectangular slot; LEDs at top-face of PCB are visible through it
        translate([PCB_X0 + LED_X_CTR - LED_WIN_W/2,
                   OTR_W - WALL_T - EPS,
                   OTR_H - LED_WIN_H])
            cube([LED_WIN_W, WALL_T + 2*EPS, LED_WIN_H + EPS]);

        // ── M5 BHCS head pockets (from bottom face of floor) ──
        for (mx = [OTR_L/2 - M5_SPAC/2, OTR_L/2 + M5_SPAC/2])
            translate([mx, OTR_W/2, -EPS]) {
                // Clearance bore through full floor
                cylinder(d=M5_D, h=FLOOR_T + 2*EPS);
                // BHCS head pocket from bottom face
                cylinder(d=M5_HEAD_D, h=M5_HEAD_H + EPS);
            }

        // ── Snap-arm ledge slot — Y− arm (hollow out to thin arm) ──
        // Arm is SNAP_T thick; cut away material behind arm
        translate([OTR_L/2 - SNAP_L/2 - EPS, EPS, OTR_H - SNAP_H])
            cube([SNAP_L + 2*EPS, WALL_T - SNAP_T - EPS, SNAP_H + EPS]);

        // ── Snap-arm ledge slot — Y+ arm ──────────────────────
        translate([OTR_L/2 - SNAP_L/2 - EPS, OTR_W - WALL_T + SNAP_T, OTR_H - SNAP_H])
            cube([SNAP_L + 2*EPS, WALL_T - SNAP_T - EPS, SNAP_H + EPS]);

        // ── Snap-arm inward ledge notch (entry chamfer removed) ─
        // Chamfer top of snap arm so PCB slides in easily
        // Y− arm: chamfer on upper-inner edge → 45° wedge on +Y/+Z corner
        translate([OTR_L/2 - SNAP_L/2 - EPS,
                   WALL_T - SNAP_T - EPS,
                   OTR_H - SNAP_IN])
            rotate([0, 0, 0])
            rotate([45, 0, 0])
                cube([SNAP_L + 2*EPS, SNAP_IN * 1.5, SNAP_IN * 1.5]);

        // Y+ arm: chamfer on upper-inner edge
        translate([OTR_L/2 - SNAP_L/2 - EPS,
                   OTR_W - WALL_T + SNAP_T - SNAP_IN * 1.5 + EPS,
                   OTR_H - SNAP_IN])
            rotate([45, 0, 0])
                cube([SNAP_L + 2*EPS, SNAP_IN * 1.5, SNAP_IN * 1.5]);

        // ── Snap ledge cutout on Y− arm inner tip ─────────────
        // Creates inward nub: remove top portion of arm inner tip
        // leaving bottom SNAP_IN height as the retaining ledge
        translate([OTR_L/2 - SNAP_L/2 - EPS,
                   WALL_T - SNAP_T - EPS,
                   PCB_Z0 + PCB_T + SNAP_IN])
            cube([SNAP_L + 2*EPS, SNAP_T + 2*EPS,
                  OTR_H - (PCB_Z0 + PCB_T + SNAP_IN) + EPS]);

        // ── Snap ledge cutout on Y+ arm inner tip ─────────────
        translate([OTR_L/2 - SNAP_L/2 - EPS,
                   OTR_W - WALL_T - EPS,
                   PCB_Z0 + PCB_T + SNAP_IN])
            cube([SNAP_L + 2*EPS, SNAP_T + 2*EPS,
                  OTR_H - (PCB_Z0 + PCB_T + SNAP_IN) + EPS]);

        // ── Zip-tie slots through strain relief bosses ─────────
        for (sy = [SR_Y1, SR_Y2])
            translate([SR_X, sy,
                       SR_BOSS_H/2 - SR_SLOT_T/2])
                rotate([0, 90, 0])
                    cube([SR_SLOT_T, SR_SLOT_W,
                          SR_BOSS_OD + 2*EPS],
                         center=true);

        // ── Weight relief pocket in floor (underside) ─────────
        translate([WALL_T + 8, WALL_T + 3, -EPS])
            cube([OTR_L - 2*WALL_T - 16, OTR_W - 2*WALL_T - 6,
                  FLOOR_T - 1.5 + EPS]);
    }
}

// ── Assembly preview ─────────────────────────────────────────
if (RENDER == "assembly") {
    color("DimGray", 0.93) canable_mount();

    // Phantom PCB
    color("MidnightBlue", 0.35)
        translate([PCB_X0, PCB_Y0, PCB_Z0])
            cube([PCB_L, PCB_W, PCB_T]);

    // Phantom component block (top of PCB)
    color("DarkSlateGray", 0.25)
        translate([PCB_X0, PCB_Y0, PCB_Z0 + PCB_T])
            cube([PCB_L, PCB_W, COMP_H]);

    // USB-C port highlight
    color("Gold", 0.8)
        translate([-1,
                   PCB_Y0 + PCB_W/2 - USBC_W/2,
                   PCB_Z0 + USBC_Z0])
            cube([WALL_T + 2, USBC_W, USBC_H]);

    // Terminal block highlight
    color("Tomato", 0.7)
        translate([OTR_L - WALL_T - 1,
                   PCB_Y0 + PCB_W/2 - TERM_W/2,
                   PCB_Z0 + TERM_Z0])
            cube([WALL_T + 2, TERM_W, TERM_H]);

    // LED zone highlight
    color("LimeGreen", 0.9)
        translate([PCB_X0 + LED_X_CTR - LED_WIN_W/2,
                   OTR_W - WALL_T - 0.5,
                   OTR_H - LED_WIN_H + 1])
            cube([LED_WIN_W, 1, LED_WIN_H - 2]);

} else {
    canable_mount();
}
