// ============================================================
// Jetson Orin Nano Carrier Board Mount — Issue #612
// Agent   : sl-mechanical
// Date    : 2026-03-15
// Part catalogue:
//   1. tnut_base     — 2020 T-slot rail interface plate, M5 T-nut captive pockets
//   2. standoff_post — M2.5 captive-nut standoff post (×4), 10 mm airflow gap
//   3. side_brace    — lateral stiffening brace with port-access cutouts (×2)
//   4. duct_shroud   — optional top heatsink duct / fan-exhaust channel
//   5. cable_clip    — snap-on cable management clip for brace edge
//
// BOM:
//   4 × M5×10 BHCS + M5 T-nuts          (tnut_base to rail, 2 per rail)
//   4 × M2.5×20 SHCS                    (board to standoff posts)
//   4 × M2.5 hex nuts                   (captured in standoff posts)
//   4 × M3×8  SHCS + washers            (side_brace to tnut_base)
//   2 × M3×16 SHCS                      (duct_shroud to side_brace tops)
//
// Jetson Orin Nano carrier board (Seeed reComputer / official dev kit):
//   Board dims   : 100 × 80 mm
//   Mounting hole pattern : 86 × 58 mm (centre-to-centre), M2.5, Ø3.5 pad
//   PCB thickness: 1.6 mm
//   Connector side: -Y (USB-A, USB-C, HDMI, DP, GbE, SD on one long edge)
//   Fan header & PWM header: +X short edge
//   M.2 / NVMe: bottom face
//
// Print settings (PETG):
//   tnut_base / standoff_post / side_brace / duct_shroud : 5 perimeters, 40 % gyroid, no supports
//   cable_clip                                            : 3 perimeters, 30 % gyroid, no supports
//
// Export commands:
//   openscad -D 'RENDER="tnut_base"'     -o tnut_base.stl     jetson_orin_mount.scad
//   openscad -D 'RENDER="standoff_post"' -o standoff_post.stl jetson_orin_mount.scad
//   openscad -D 'RENDER="side_brace"'    -o side_brace.stl    jetson_orin_mount.scad
//   openscad -D 'RENDER="duct_shroud"'   -o duct_shroud.stl   jetson_orin_mount.scad
//   openscad -D 'RENDER="cable_clip"'    -o cable_clip.stl    jetson_orin_mount.scad
//   openscad -D 'RENDER="assembly"'      -o assembly.png      jetson_orin_mount.scad
// ============================================================

// ── Render selector ─────────────────────────────────────────
RENDER = "assembly"; // tnut_base | standoff_post | side_brace | duct_shroud | cable_clip | assembly

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

// 2020 rail
RAIL_W      = 20.0;
SLOT_NECK_H =  3.2;
TNUT_W      =  9.8;
TNUT_H      =  5.5;
TNUT_L      = 12.0;
M5_D        =  5.2;
M5_HEAD_D   =  9.5;
M5_HEAD_H   =  4.0;

// Jetson Orin Nano carrier board
BOARD_L     = 100.0;  // board X
BOARD_W     =  80.0;  // board Y
BOARD_T     =   1.6;  // PCB thickness
MH_SX       =  86.0;  // mounting hole span X (centre-to-centre)
MH_SY       =  58.0;  // mounting hole span Y
M25_D       =   2.7;  // M2.5 clearance bore
M25_NUT_W   =   5.0;  // M2.5 hex nut across-flats
M25_NUT_H   =   2.0;  // M2.5 hex nut height
M25_HEAD_D  =   5.0;  // M2.5 SHCS head diameter
M25_HEAD_H  =   2.5;

// Base plate
BASE_L      = 120.0;  // length along X (covers board + overhang for braces)
BASE_W      =  50.0;  // width along Y (rail mount footprint)
BASE_T      =   6.0;  // plate thickness
BOLT_PITCH  =  40.0;  // M5 rail bolt pitch (per rail, 2 rails at Y=0 & Y=BASE_W)
M3_D        =   3.2;
M3_HEAD_D   =   6.0;
M3_HEAD_H   =   3.0;

// Standoff posts
POST_H      =  12.0;  // airflow gap + PCB seating (>= 10 mm clearance spec)
POST_OD     =   8.0;  // outer diameter
POST_BASE_D =  11.0;  // flange diameter
POST_BASE_H =   3.0;  // flange height
NUT_TRAP_H  = M25_NUT_H + 0.3;
NUT_TRAP_W  = M25_NUT_W + 0.4;

// Side braces
BRACE_T     =   5.0;  // brace thickness (X)
BRACE_H     = POST_H + POST_BASE_H + BOARD_T + 4.0;  // full height
BRACE_W     = BASE_W;  // same width as base

// Port-access cutouts (connector side -Y)
USB_CUT_W   =  60.0;  // wide cutout for USB-A stack + HDMI + DP
USB_CUT_H   =  22.0;
GBE_CUT_W   =  20.0;  // GbE jack
GBE_CUT_H   =  18.0;

// Duct shroud
DUCT_T      =   3.0;  // wall thickness
DUCT_FLANGE =   6.0;  // side tab width for M3 attachment
FAN_W       =  40.0;  // standard 40 mm blower clearance cutout
FAN_H       =  10.0;  // duct outlet height

// Cable clip
CLIP_OD     =  12.0;
CLIP_ID     =   7.0;
CLIP_GAP    =   7.5;
CLIP_W      =  10.0;
SNAP_T      =   1.8;

// ── Utilities ───────────────────────────────────────────────
module chamfer_cube(size, ch=1.0) {
    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) {
    cylinder(d=af/cos(30), h=depth, $fn=6);
}

// ── Part 1: tnut_base ───────────────────────────────────────
module tnut_base() {
    difference() {
        union() {
            chamfer_cube([BASE_L, BASE_W, BASE_T], ch=1.5);

            // Raised mounting bosses for M3 brace attachment (4 corners)
            for (x = [8, BASE_L-8])
                for (y = [8, BASE_W-8])
                    translate([x, y, BASE_T])
                        cylinder(d=10, h=2.5);
        }

        // T-nut pockets and M5 bolts — front rail (y = BASE_W/4)
        for (x = [BASE_L/2 - BOLT_PITCH/2, BASE_L/2 + BOLT_PITCH/2]) {
            translate([x, BASE_W/4, -EPS]) {
                cylinder(d=M5_D, h=BASE_T + 2*EPS);
                cylinder(d=M5_HEAD_D, h=M5_HEAD_H + EPS);
            }
            translate([x - TNUT_L/2, BASE_W/4 - TNUT_W/2, BASE_T - TNUT_H])
                cube([TNUT_L, TNUT_W, TNUT_H + EPS]);
        }

        // T-nut pockets and M5 bolts — rear rail (y = 3*BASE_W/4)
        for (x = [BASE_L/2 - BOLT_PITCH/2, BASE_L/2 + BOLT_PITCH/2]) {
            translate([x, 3*BASE_W/4, -EPS]) {
                cylinder(d=M5_D, h=BASE_T + 2*EPS);
                cylinder(d=M5_HEAD_D, h=M5_HEAD_H + EPS);
            }
            translate([x - TNUT_L/2, 3*BASE_W/4 - TNUT_W/2, BASE_T - TNUT_H])
                cube([TNUT_L, TNUT_W, TNUT_H + EPS]);
        }

        // M3 boss bolt holes (corner braces)
        for (x = [8, BASE_L-8])
            for (y = [8, BASE_W-8])
                translate([x, y, -EPS])
                    cylinder(d=M3_D, h=BASE_T + 2.5 + 2*EPS);

        // M3 boss counterbores (head from bottom)
        for (x = [8, BASE_L-8])
            for (y = [8, BASE_W-8])
                translate([x, y, -EPS])
                    cylinder(d=M3_HEAD_D, h=M3_HEAD_H + EPS);

        // Standoff post seating holes (board hole pattern, centred on plate)
        bx0 = BASE_L/2 - MH_SX/2;
        by0 = BASE_W/2 - MH_SY/2;
        for (dx = [0, MH_SX])
            for (dy = [0, MH_SY])
                translate([bx0+dx, by0+dy, -EPS])
                    cylinder(d=POST_BASE_D + 0.4, h=BASE_T + 2*EPS);

        // Weight relief grid (2 pockets)
        translate([20, 12, -EPS]) cube([30, BASE_W-24, BASE_T/2]);
        translate([BASE_L-50, 12, -EPS]) cube([30, BASE_W-24, BASE_T/2]);

        // Cable pass-through slot
        translate([BASE_L/2 - 8, BASE_W/2 - 3, -EPS])
            cube([16, 6, BASE_T + 2*EPS]);
    }
}

// ── Part 2: standoff_post ───────────────────────────────────
module standoff_post() {
    difference() {
        union() {
            // Flange
            cylinder(d=POST_BASE_D, h=POST_BASE_H);
            // Post body
            translate([0, 0, POST_BASE_H])
                cylinder(d=POST_OD, h=POST_H);
        }

        // M2.5 through bore
        translate([0, 0, -EPS])
            cylinder(d=M25_D, h=POST_BASE_H + POST_H + 2*EPS);

        // Captured hex nut trap (from top)
        translate([0, 0, POST_BASE_H + POST_H - NUT_TRAP_H])
            hex_pocket(NUT_TRAP_W, NUT_TRAP_H + EPS);

        // Anti-rotation flat on nut pocket
        translate([-M25_NUT_W/2 - 0.2, -POST_OD/2 - EPS,
                   POST_BASE_H + POST_H - NUT_TRAP_H])
            cube([M25_NUT_W + 0.4, 2.0, NUT_TRAP_H + EPS]);
    }
}

// ── Part 3: side_brace ──────────────────────────────────────
// Printed as +X face. Mirror for -X side.
module side_brace() {
    difference() {
        union() {
            chamfer_cube([BRACE_T, BRACE_W, BRACE_H], ch=1.0);

            // Top lip to retain board edge
            translate([0, 0, BRACE_H])
                cube([BRACE_T + 8.0, BRACE_W, 2.5]);
        }

        // M3 bolt holes at base (attach to tnut_base bosses)
        for (y = [8, BRACE_W-8])
            translate([-EPS, y, 4])
                rotate([0, 90, 0])
                    cylinder(d=M3_D, h=BRACE_T + 2*EPS);

        // M3 counterbore from outer face
        for (y = [8, BRACE_W-8])
            translate([-EPS, y, 4])
                rotate([0, 90, 0])
                    cylinder(d=M3_HEAD_D, h=M3_HEAD_H + EPS);

        // Port-access cutout — USB/HDMI/DP cluster (centred on brace face)
        translate([-EPS, BRACE_W/2 - USB_CUT_W/2, POST_BASE_H + 2.0])
            cube([BRACE_T + 2*EPS, USB_CUT_W, USB_CUT_H]);

        // GbE cutout (offset toward +Y)
        translate([-EPS, BRACE_W/2 + USB_CUT_W/2 - GBE_CUT_W - 2, POST_BASE_H + 2.0])
            cube([BRACE_T + 2*EPS, GBE_CUT_W, GBE_CUT_H]);

        // M3 duct attachment holes (top edge)
        for (y = [BRACE_W/4, 3*BRACE_W/4])
            translate([BRACE_T/2, y, BRACE_H - 2])
                cylinder(d=M3_D, h=10);

        // Ventilation slots (3 tall slots for airflow)
        for (i = [0:2])
            translate([-EPS,
                       (BRACE_W - 3*8 - 2*4) / 2 + i*(8+4),
                       POST_BASE_H + USB_CUT_H + 6])
                cube([BRACE_T + 2*EPS, 8, BRACE_H - POST_BASE_H - USB_CUT_H - 10]);
    }
}

// ── Part 4: duct_shroud ─────────────────────────────────────
// Top cap that channels fan exhaust away from board; optional print.
module duct_shroud() {
    duct_l = BASE_L - 2*BRACE_T - 1.0;  // span between inner brace faces
    duct_w = BRACE_W;

    difference() {
        union() {
            // Top plate
            cube([duct_l, duct_w, DUCT_T]);

            // Front wall (fan inlet side)
            translate([0, 0, -FAN_H])
                cube([DUCT_T, duct_w, FAN_H + DUCT_T]);

            // Rear wall (exhaust side — open centre)
            translate([duct_l - DUCT_T, 0, -FAN_H])
                cube([DUCT_T, duct_w, FAN_H + DUCT_T]);

            // Side flanges for M3 attachment
            translate([-DUCT_FLANGE, 0, -FAN_H])
                cube([DUCT_FLANGE, duct_w, FAN_H + DUCT_T]);
            translate([duct_l, 0, -FAN_H])
                cube([DUCT_FLANGE, duct_w, FAN_H + DUCT_T]);
        }

        // Fan cutout on top plate (centred)
        translate([duct_l/2 - FAN_W/2, duct_w/2 - FAN_W/2, -EPS])
            cube([FAN_W, FAN_W, DUCT_T + 2*EPS]);

        // Fan screw holes (40 mm fan, Ø3.2 at 32 mm BC)
        for (dx = [-16, 16])
            for (dy = [-16, 16])
                translate([duct_l/2 + dx, duct_w/2 + dy, -EPS])
                    cylinder(d=M3_D, h=DUCT_T + 2*EPS);

        // Exhaust slot on rear wall (full width minus corners)
        translate([duct_l - DUCT_T - EPS, 4, -FAN_H + 2])
            cube([DUCT_T + 2*EPS, duct_w - 8, FAN_H - 2]);

        // M3 flange attachment holes
        for (y = [duct_w/4, 3*duct_w/4]) {
            translate([-DUCT_FLANGE - EPS, y, -FAN_H/2])
                rotate([0, 90, 0])
                    cylinder(d=M3_D, h=DUCT_FLANGE + 2*EPS);
            translate([duct_l + DUCT_T - EPS, y, -FAN_H/2])
                rotate([0, 90, 0])
                    cylinder(d=M3_D, h=DUCT_FLANGE + 2*EPS);
        }
    }
}

// ── Part 5: cable_clip ──────────────────────────────────────
module cable_clip() {
    difference() {
        union() {
            // Snap-wrap body
            difference() {
                cylinder(d=CLIP_OD + 2*SNAP_T, h=CLIP_W);
                translate([0, 0, -EPS])
                    cylinder(d=CLIP_ID, h=CLIP_W + 2*EPS);
                // Front gap
                translate([-CLIP_GAP/2, 0, -EPS])
                    cube([CLIP_GAP, CLIP_OD, CLIP_W + 2*EPS]);
            }

            // Mounting tab for brace edge
            translate([CLIP_OD/2 + SNAP_T - EPS, -SNAP_T, 0])
                cube([8, SNAP_T*2, CLIP_W]);
        }

        // Tab screw hole
        translate([CLIP_OD/2 + SNAP_T + 4, 0, CLIP_W/2])
            rotate([90, 0, 0])
                cylinder(d=M3_D, h=SNAP_T*2 + 2*EPS, center=true);
    }
}

// ── Assembly ────────────────────────────────────────────────
module assembly() {
    // Base plate
    color("SteelBlue")
        tnut_base();

    // Standoff posts (board hole pattern)
    bx0 = BASE_L/2 - MH_SX/2;
    by0 = BASE_W/2 - MH_SY/2;
    for (dx = [0, MH_SX])
        for (dy = [0, MH_SY])
            color("DodgerBlue")
                translate([bx0+dx, by0+dy, BASE_T])
                    standoff_post();

    // Side braces (left and right)
    color("CornflowerBlue")
        translate([0, 0, BASE_T])
            side_brace();
    color("CornflowerBlue")
        translate([BASE_L, BRACE_W, BASE_T])
            mirror([1, 0, 0])
                mirror([0, 1, 0])
                    side_brace();

    // Board silhouette (translucent, for clearance visualisation)
    color("ForestGreen", 0.25)
        translate([BASE_L/2 - BOARD_L/2, BASE_W/2 - BOARD_W/2,
                   BASE_T + POST_BASE_H + POST_H])
            cube([BOARD_L, BOARD_W, BOARD_T]);

    // Duct shroud (above board)
    color("LightSteelBlue", 0.7)
        translate([BRACE_T + 0.5, 0,
                   BASE_T + POST_BASE_H + POST_H + BOARD_T + 2.0])
            duct_shroud();

    // Cable clips (on brace edge, 2×)
    for (y = [BRACE_W/3, 2*BRACE_W/3])
        color("SlateGray")
            translate([BASE_L + 2, y, BASE_T + BRACE_H/2 - CLIP_W/2])
                rotate([0, 90, 0])
                    cable_clip();
}

// ── Dispatch ────────────────────────────────────────────────
if      (RENDER == "tnut_base")     tnut_base();
else if (RENDER == "standoff_post") standoff_post();
else if (RENDER == "side_brace")    side_brace();
else if (RENDER == "duct_shroud")   duct_shroud();
else if (RENDER == "cable_clip")    cable_clip();
else                                assembly();
