saltylab-firmware/chassis/jetson_orin_mount.scad

387 lines
15 KiB
OpenSCAD
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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