feat: CANable 2.0 mount (Issue #654) #659

Merged
sl-jetson merged 1 commits from sl-mechanical/issue-654-canable-mount into main 2026-03-18 07:55:31 -04:00

265
chassis/canable_mount.scad Normal file
View File

@ -0,0 +1,265 @@
// ============================================================
// 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();
}