diff --git a/chassis/chassis_frame.scad b/chassis/chassis_frame.scad index 8c96c82..6f9065b 100644 --- a/chassis/chassis_frame.scad +++ b/chassis/chassis_frame.scad @@ -1,340 +1,339 @@ // ============================================================================= -// SaltyBot — Parametric Chassis Frame -// Task: bd-1iy5 -// Agent: sl-mechanical -// Date: 2026-02-28 +// SaltyBot — Full Chassis Frame (Rev B — correct 270 × 240 mm envelope) +// Agent: sl-mechanical | 2026-02-28 | Fixes issue #18 // -// Self-balancing two-wheeled robot chassis -// Requirements: -// - 600mm wheelbase -// - 2x hoverboard hub motors (170mm OD) -// - STM32 MAMBA F722S FC mount (30.5x30.5mm pattern) -// - Battery tray (24V 4Ah — ~180x70x50mm pack) -// - Jetson Nano B01 mount plate (100x80mm, M3 holes) -// - Front/rear bumper brackets +// Parametric OpenSCAD model. Plate geometry corrected per issue #18: +// Width (axle direction, X): 270 mm ← was wrong 640 mm +// Depth (front-to-rear, Y): 240 mm ← was wrong 220 mm +// Wheels extend beyond plate edges — expected. +// +// Motor axle dimensions updated to caliper-verified values (see PR #7 / #11). +// +// NOTE: For the prototype, use prototype_baseplate.scad + stem_battery_clamp.scad. +// This file models the full machined chassis for production reference. // ============================================================================= -// ─── RENDER QUALITY ────────────────────────────────────────────────────────── $fn = 64; // ============================================================================= -// PARAMETERS — edit here to adjust the whole model +// PLATE DIMENSIONS ← primary parameters fixed for issue #18 // ============================================================================= -// ── Wheelbase / overall geometry ───────────────────────────────────────────── -WHEELBASE = 600; // mm, center-to-center axle distance (lateral) -FRAME_WIDTH = 220; // mm, front-to-back depth of main deck -DECK_THICKNESS = 6; // mm, main deck plate thickness -WALL_T = 4; // mm, general wall thickness for brackets/ribs +PLATE_W = 270.0; // mm width (axle / left-right direction) +PLATE_D = 240.0; // mm depth (front-to-rear) +DECK_THICK = 6.0; // mm deck plate thickness +WALL_T = 4.0; // mm general wall / rib thickness -// ── Hub motor parameters ───────────────────────────────────────────────────── -MOTOR_OD = 170; // mm, motor housing outer diameter -MOTOR_AXLE_D = 14; // mm, axle bolt diameter -MOTOR_AXLE_FLAT = 10; // mm, axle flat-to-flat (for anti-rotation) -MOTOR_FORK_WIDTH = 24; // mm, dropout slot width (fits 10-14mm axle + spacers) -MOTOR_FORK_DEPTH = 60; // mm, dropout slot depth from fork tip -MOTOR_FORK_H = 80; // mm, total height of motor fork bracket -MOTOR_FORK_T = 8; // mm, fork plate thickness -AXLE_HEIGHT = 310; // mm, axle CL above ground (motor radius + clearance) - -// ── FC mount (MAMBA F722S — 30.5 × 30.5 mm M3 pattern) ────────────────────── -FC_MOUNT_SPACING = 30.5; // mm, hole pattern pitch -FC_MOUNT_HOLE_D = 3.2; // mm, M3 clearance -FC_STANDOFF_H = 6; // mm, standoff height -FC_PAD_T = 3; // mm, mounting pad thickness - -// ── Battery tray (24V 4Ah LiPo / LiFePO4) ─────────────────────────────────── -BATT_L = 185; // mm, cell pack length -BATT_W = 72; // mm, cell pack width -BATT_H = 52; // mm, cell pack height -BATT_WALL = 3; // mm, tray wall thickness -BATT_FLOOR = 4; // mm, tray floor thickness -BATT_STRAP_W = 20; // mm, Velcro strap slot width -BATT_STRAP_T = 2; // mm, strap slot depth - -// ── Jetson Nano B01 mount plate ────────────────────────────────────────────── -// B01 carrier board hole pattern: 58 x 58 mm M3 (inner) + corner pass-throughs -JETSON_HOLE_PITCH = 58; // mm, M3 mounting hole pattern -JETSON_HOLE_D = 3.2; // mm -JETSON_PLATE_L = 105; // mm, plate length -JETSON_PLATE_W = 90; // mm, plate width -JETSON_PLATE_T = 4; // mm, plate thickness -JETSON_STANDOFF_H = 8; // mm - -// ── Bumper bracket ──────────────────────────────────────────────────────────── -BUMPER_L = WHEELBASE + 60; // mm, bumper rail length (overhangs wheel CL) -BUMPER_H = 40; // mm, bracket vertical height -BUMPER_T = 5; // mm, bracket plate thickness -BUMPER_TUBE_OD = 22; // mm, 3/4" EMT conduit OD for bumper rail - -// ── Rib / gusset parameters ─────────────────────────────────────────────────── -RIB_W = 20; // mm, longitudinal rib width -RIB_H = 40; // mm, rib height below deck -RIB_T = 4; // mm, rib plate thickness (laser/router cut) - -// ── Fastener helpers ───────────────────────────────────────────────────────── -M3_D = 3.2; -M4_D = 4.3; -M5_D = 5.3; -M6_D = 6.5; +// Fork slot geometry — slot opens at plate edge, axle seats at bottom +FORK_SLOT_D = 50.0; // mm fork slot depth (inward from plate edge) +PLATE_X_HALF = PLATE_W / 2; // 135 mm — plate half-width / edge +AXLE_X = PLATE_X_HALF - FORK_SLOT_D; // 85 mm — axle C/L from plate centre // ============================================================================= -// MAIN ASSEMBLY — comment/uncomment parts as needed +// HUB MOTOR — VERIFIED AXLE DIMENSIONS (caliper) // ============================================================================= -color("Silver", 0.9) main_deck(); -color("DimGray") motor_fork(side= 1); // right (+X) -color("DimGray") motor_fork(side=-1); // left (-X) -color("SteelBlue") battery_tray(); -color("OliveDrab") fc_mount_plate(); -color("DarkOrange") jetson_mount_plate(); -color("Tomato") bumper_bracket(front= 1); -color("Tomato") bumper_bracket(front=-1); -color("WhiteSmoke") longitudinal_ribs(); +AXLE_BASE_DIA = 16.11; // mm round section near hub +AXLE_BASE_LEN = 15.00; +AXLE_DCUT_DIA = 15.95; // mm D-cut round OD +AXLE_DCUT_FLAT = 13.00; // mm flat chord +AXLE_DCUT_LEN = 43.35; +AXLE_TOTAL = 65.50; // mm protrusion from hub face +BEARING_SEAT_OD = 37.80; // mm hub centre collar OD +TIRE_OD = 254.0; // mm 10 × 2.125" pneumatic +TIRE_WIDTH = 54.0; // mm + +AXLE_HEIGHT = TIRE_OD / 2; // 127 mm — axle C/L above ground + +// Fork slot width (base section + clearance) +FORK_SLOT_W = AXLE_BASE_DIA + 0.4; // 16.51 mm + +// Vertical motor fork bracket +FORK_BKT_H = 100.0; // mm bracket height below deck to axle level +FORK_BKT_T = 8.0; // mm bracket plate thickness +FORK_BKT_W = 28.0; // mm bracket width (Y) // ============================================================================= -// MODULES +// FC MOUNT — MAMBA F722S 30.5 × 30.5 mm M3 +// ============================================================================= + +FC_PITCH = 30.5; +FC_HOLE_D = 3.2; +FC_STANDOFF = 6.0; +FC_X = -40.0; // mm from plate centre (front of plate) + +// ============================================================================= +// BATTERY TRAY (legacy flat — superseded by stem carousel architecture) +// ============================================================================= + +BATT_L = 185.0; +BATT_W = 72.0; +BATT_H = 52.0; +BATT_WALL = 3.0; +BATT_FLOOR = 4.0; + +// ============================================================================= +// JETSON NANO B01 MOUNT +// ============================================================================= + +JETSON_PITCH = 58.0; +JETSON_HOLE_D = 3.2; +JETSON_PL = 100.0; +JETSON_PW = 86.0; +JETSON_PT = 4.0; +JETSON_STOFF = 8.0; +JETSON_X = 50.0; // mm from plate centre (rear) + +// ============================================================================= +// BUMPER BRACKETS +// ============================================================================= + +BUMPER_L = PLATE_D + 60; // 300 mm — spans plate depth + 30 mm each end +BUMPER_H = 40.0; +BUMPER_T = 5.0; +BUMPER_TUBE_OD = 22.0; // 3/4" EMT + +// ============================================================================= +// LONGITUDINAL RIBS +// ============================================================================= + +RIB_T = 4.0; +RIB_H = 40.0; + +// ============================================================================= +// FASTENERS +// ============================================================================= + +M3 = 3.2; M4 = 4.3; M5 = 5.3; + +// ============================================================================= +// ASSEMBLY +// ============================================================================= + +color("Silver", 0.90) main_deck(); +color("DimGray", 1.00) motor_fork_bracket(side= 1); +color("DimGray", 1.00) motor_fork_bracket(side=-1); +color("SteelBlue", 1.00) battery_tray(); +color("OliveDrab", 1.00) fc_mount_plate(); +color("DarkOrange", 1.00) jetson_mount_plate(); +color("Tomato", 1.00) bumper_bracket(front= 1); +color("Tomato", 1.00) bumper_bracket(front=-1); +color("WhiteSmoke", 1.00) longitudinal_ribs(); + +// ============================================================================= +// MAIN DECK (270 × 240 mm) // ============================================================================= -// ─── Main deck plate ───────────────────────────────────────────────────────── module main_deck() { + R = 10; difference() { - // Deck plate - translate([-WHEELBASE/2 - MOTOR_FORK_T, -FRAME_WIDTH/2, 0]) - cube([WHEELBASE + 2*MOTOR_FORK_T, FRAME_WIDTH, DECK_THICKNESS]); + linear_extrude(DECK_THICK) + minkowski() { + square([PLATE_W - 2*R, PLATE_D - 2*R], center=true); + circle(r=R); + } - // Lightening holes — 3 rows × 4 cols - for (x = [-WHEELBASE/3, 0, WHEELBASE/3]) - for (y = [-FRAME_WIDTH/4, FRAME_WIDTH/4]) - translate([x, y, -1]) - cylinder(d=50, h=DECK_THICKNESS+2); + // Fork slots (open at ±X plate edges) + for (side = [-1, 1]) { + translate([side*(PLATE_X_HALF - FORK_SLOT_D), -FORK_SLOT_W/2, -1]) + cube([FORK_SLOT_D + 1, FORK_SLOT_W, DECK_THICK + 2]); + translate([side*(PLATE_X_HALF - FORK_SLOT_D), 0, -1]) + cylinder(d=FORK_SLOT_W, h=DECK_THICK + 2); + } + + // Bearing seat relief + for (side = [-1, 1]) + translate([side*(PLATE_X_HALF - BEARING_SEAT_OD/2 - 1), + -BEARING_SEAT_OD/2, -1]) + cube([BEARING_SEAT_OD/2 + 2, BEARING_SEAT_OD, DECK_THICK + 2]); + + // Fork bracket bolt holes (M5 × 4 per side) + for (side = [-1, 1]) + for (dx = [-15, 15]) + for (dy = [-12, 12]) + translate([side * AXLE_X + dx, dy, -1]) + cylinder(d=M5, h=DECK_THICK + 2); // FC mount holes - fc_mount_holes(z_offset=-1, depth=DECK_THICKNESS+2); + fc_holes(z=-1, h=DECK_THICK+2); - // Battery tray floor cutout for wire pass-through - translate([-BATT_L/2 + 40, -BATT_W/2 + 40, -1]) - cube([BATT_L - 80, BATT_W - 80, DECK_THICKNESS+2]); + // Battery tray anchor holes + for (x = [-BATT_L/2 + 12, BATT_L/2 - 12]) + for (y = [-BATT_W/2 + 10, BATT_W/2 - 10]) + translate([x, y, -1]) cylinder(d=M4, h=DECK_THICK+2); + + // Lightening ovals (front/rear open zones) + for (dy = [-80, 80]) + hull() { + translate([ 25, dy, -1]) cylinder(d=22, h=DECK_THICK+2); + translate([-25, dy, -1]) cylinder(d=22, h=DECK_THICK+2); + } // Cable routing slots - for (x = [-60, 60]) - translate([x, -FRAME_WIDTH/2 - 1, -1]) - cube([14, 18, DECK_THICKNESS+2]); + for (dy = [-55, 55]) + hull() { + translate([ 12, dy, -1]) cylinder(d=12, h=DECK_THICK+2); + translate([-12, dy, -1]) cylinder(d=12, h=DECK_THICK+2); + } } } -// ─── Longitudinal ribs (×2, symmetric F/R) ─────────────────────────────────── +// ============================================================================= +// LONGITUDINAL RIBS (×2) +// ============================================================================= + module longitudinal_ribs() { - for (y = [-FRAME_WIDTH/2 + RIB_W/2 + WALL_T, - FRAME_WIDTH/2 - RIB_W/2 - WALL_T]) - translate([-WHEELBASE/2, y - RIB_T/2, -RIB_H]) - cube([WHEELBASE, RIB_T, RIB_H]); + rib_y = PLATE_D/2 - WALL_T - RIB_T/2; + for (y = [-rib_y, rib_y]) + translate([-PLATE_W/2, y - RIB_T/2, -RIB_H]) + cube([PLATE_W, RIB_T, RIB_H]); } -// ─── Motor fork dropout bracket ────────────────────────────────────────────── -// Mounts to deck edge; provides a CNC-milled or FDM dropout slot for the axle. -// `side` = +1 (right/+X) or -1 (left/-X) -module motor_fork(side = 1) { - x_pos = side * (WHEELBASE/2); +// ============================================================================= +// MOTOR FORK BRACKET (vertical, bolts to deck edge at ±PLATE_X_HALF) +// ============================================================================= - translate([x_pos, 0, 0]) { +module motor_fork_bracket(side = 1) { + translate([side * PLATE_X_HALF, 0, 0]) { + s = side; difference() { union() { - // Vertical fork body - translate([side*(DECK_THICKNESS/2), -MOTOR_FORK_WIDTH/2 - 4, -MOTOR_FORK_H]) - cube([side * MOTOR_FORK_T, MOTOR_FORK_WIDTH + 8, MOTOR_FORK_H + DECK_THICKNESS]); + // Main bracket plate (extends below deck) + translate([s > 0 ? 0 : -FORK_BKT_T, + -FORK_BKT_W/2, -FORK_BKT_H]) + cube([FORK_BKT_T, FORK_BKT_W, FORK_BKT_H + DECK_THICK]); - // Gusset triangles - for (g = [-1, 1]) - translate([side*(DECK_THICKNESS/2), g*(MOTOR_FORK_WIDTH/2 + 2), -MOTOR_FORK_H]) - linear_extrude(height=MOTOR_FORK_T) - polygon([[0,0],[0, g*20],[side*-30, 0]]); + // Diagonal gussets + for (gy = [-1, 1]) + translate([s > 0 ? 0 : -FORK_BKT_T, + gy * FORK_BKT_W/2, -FORK_BKT_H/2]) + linear_extrude(FORK_BKT_T) + polygon([[0,0],[0, gy*20],[s * -25, 0]]); } - // Axle dropout slot (open at bottom) - translate([side*(DECK_THICKNESS/2) - 1, - -MOTOR_AXLE_FLAT/2, - -MOTOR_FORK_DEPTH - MOTOR_AXLE_D/2]) - cube([MOTOR_FORK_T + 2, MOTOR_AXLE_FLAT, MOTOR_FORK_DEPTH + 1]); + // Axle dropout slot (open at bracket bottom) + translate([s > 0 ? -1 : -FORK_BKT_T - 1, + -AXLE_BASE_DIA/2 - 0.2, -FORK_BKT_H]) + cube([FORK_BKT_T + 2, AXLE_BASE_DIA + 0.4, FORK_SLOT_D + 1]); + translate([s > 0 ? -1 : -FORK_BKT_T - 1, + 0, -FORK_BKT_H + FORK_SLOT_D]) + rotate([-90, 0, 0]) + cylinder(d=AXLE_BASE_DIA + 0.4, + h=FORK_BKT_T + 2, center=true); - // Axle through-hole at slot top - translate([side*(DECK_THICKNESS/2) - 1, 0, - -MOTOR_FORK_DEPTH - MOTOR_AXLE_D/2]) - rotate([0, 90, 0]) - cylinder(d=MOTOR_AXLE_D + 1, h=MOTOR_FORK_T + 2); - - // Bolt holes for deck attachment (M5 × 4) - for (y = [-20, 20]) - for (z = [4, 12]) - translate([side*(DECK_THICKNESS/2) - 1, y, z - MOTOR_FORK_H + MOTOR_FORK_H/2]) + // Deck attachment bolts (M5 × 4) + for (dy = [-12, 12]) + for (dz = [DECK_THICK * 0.3, DECK_THICK * 0.7]) + translate([s > 0 ? -1 : -FORK_BKT_T - 1, dy, dz]) rotate([0, 90, 0]) - cylinder(d=M5_D, h=MOTOR_FORK_T + 2); + cylinder(d=M5, h=FORK_BKT_T + 2); } } } -// ─── Battery tray ───────────────────────────────────────────────────────────── -// Positioned in centre of deck, recessed 10mm below deck surface +// ============================================================================= +// FC MOUNT HELPERS +// ============================================================================= + +module fc_holes(z=0, h=10) { + for (x = [FC_X - FC_PITCH/2, FC_X + FC_PITCH/2]) + for (y = [-FC_PITCH/2, FC_PITCH/2]) + translate([x, y, z]) cylinder(d=FC_HOLE_D, h=h); +} + +module fc_mount_plate() { + translate([FC_X, 0, 0]) { + difference() { + union() { + for (x = [-FC_PITCH/2, FC_PITCH/2]) + for (y = [-FC_PITCH/2, FC_PITCH/2]) + translate([x, y, DECK_THICK]) cylinder(d=8, h=FC_STANDOFF); + translate([-(FC_PITCH/2+8), -(FC_PITCH/2+8), DECK_THICK]) + cube([FC_PITCH+16, FC_PITCH+16, 3]); + } + fc_holes(z=DECK_THICK-1, h=FC_STANDOFF+4); + fc_holes(z=-1, h=DECK_THICK+2); + } + } +} + +// ============================================================================= +// BATTERY TRAY +// ============================================================================= + module battery_tray() { - z_base = -BATT_FLOOR - BATT_H - 5; // hang below deck + z_base = -BATT_FLOOR - BATT_H - 5; translate([-BATT_L/2 - BATT_WALL, -BATT_W/2 - BATT_WALL, z_base]) { difference() { - // Outer tray body - cube([BATT_L + 2*BATT_WALL, - BATT_W + 2*BATT_WALL, - BATT_H + BATT_FLOOR]); - - // Inner cavity + cube([BATT_L+2*BATT_WALL, BATT_W+2*BATT_WALL, BATT_H+BATT_FLOOR]); translate([BATT_WALL, BATT_WALL, BATT_FLOOR]) - cube([BATT_L, BATT_W, BATT_H + 1]); - - // Strap slots (2× longitudinal) - for (x = [BATT_L/2 - BATT_STRAP_W*2, - BATT_L/2 + BATT_STRAP_W]) - translate([x, -1, BATT_FLOOR + BATT_H/2 - BATT_STRAP_W/2]) - cube([BATT_STRAP_W, BATT_W + 2*BATT_WALL + 2, BATT_STRAP_W]); - - // Ventilation slots bottom (3×) - for (i = [0:2]) - translate([BATT_WALL + 20 + i*50, BATT_WALL + 10, -1]) - cube([30, BATT_W - 20, BATT_FLOOR + 2]); - - // Mount holes to deck (M4 × 4 corners) - for (x = [10, BATT_L + BATT_WALL - 2]) - for (y = [10, BATT_W + BATT_WALL - 2]) - translate([x, y, BATT_H + BATT_FLOOR - 1]) - cylinder(d=M4_D, h=BATT_FLOOR + 2); + cube([BATT_L, BATT_W, BATT_H+1]); + for (x = [BATT_L/2-30, BATT_L/2+8]) + translate([x, -1, BATT_FLOOR+BATT_H/2-10]) + cube([20, BATT_W+2*BATT_WALL+2, 20]); + for (i = [0:1]) + translate([BATT_WALL+12+i*68, BATT_WALL+8, -1]) + cube([38, BATT_W-16, BATT_FLOOR+2]); + for (x=[10,BATT_L+BATT_WALL-2]) for (y=[10,BATT_W+BATT_WALL-2]) + translate([x,y,BATT_H+BATT_FLOOR-1]) cylinder(d=M4,h=BATT_FLOOR+2); } } } -// ─── FC mount holes helper ──────────────────────────────────────────────────── -module fc_mount_holes(z_offset=0, depth=10) { - // MAMBA F722S: 30.5×30.5 mm M3 pattern, centred at origin - for (x = [-FC_MOUNT_SPACING/2, FC_MOUNT_SPACING/2]) - for (y = [-FC_MOUNT_SPACING/2, FC_MOUNT_SPACING/2]) - translate([x, y, z_offset]) - cylinder(d=FC_MOUNT_HOLE_D, h=depth); -} +// ============================================================================= +// JETSON NANO B01 MOUNT PLATE +// ============================================================================= -// ─── FC mount plate (raised 40mm above deck centre) ────────────────────────── -module fc_mount_plate() { - fc_x = -50; // offset toward front from deck centre - fc_y = 0; - plate_z = DECK_THICKNESS + FC_STANDOFF_H; - - translate([fc_x, fc_y, 0]) { - difference() { - union() { - // Mount pads with standoffs - for (x = [-FC_MOUNT_SPACING/2, FC_MOUNT_SPACING/2]) - for (y = [-FC_MOUNT_SPACING/2, FC_MOUNT_SPACING/2]) { - translate([x, y, DECK_THICKNESS]) - cylinder(d=8, h=FC_STANDOFF_H); // standoff column - translate([x - 5, y - 5, DECK_THICKNESS]) - cube([10, 10, FC_PAD_T]); // pad base - } - // Base plate - translate([-FC_MOUNT_SPACING/2 - 8, -FC_MOUNT_SPACING/2 - 8, DECK_THICKNESS]) - cube([FC_MOUNT_SPACING + 16, FC_MOUNT_SPACING + 16, FC_PAD_T]); - } - // M3 through-holes in standoffs - fc_mount_holes(z_offset=DECK_THICKNESS - 1, depth=FC_STANDOFF_H + FC_PAD_T + 2); - - // Deck anchor holes - fc_mount_holes(z_offset=-1, depth=DECK_THICKNESS + 2); - } - } -} - -// ─── Jetson Nano B01 mount plate ───────────────────────────────────────────── -// Positioned rear of deck, elevated on standoffs module jetson_mount_plate() { - jet_x = 60; // offset toward rear - jet_y = 0; - plate_z = DECK_THICKNESS + JETSON_STANDOFF_H; - - translate([jet_x, jet_y, 0]) { + translate([JETSON_X, 0, 0]) { difference() { union() { - // Mounting plate - translate([-JETSON_PLATE_L/2, -JETSON_PLATE_W/2, DECK_THICKNESS + JETSON_STANDOFF_H]) - cube([JETSON_PLATE_L, JETSON_PLATE_W, JETSON_PLATE_T]); - - // Four standoff columns - for (x = [-JETSON_HOLE_PITCH/2, JETSON_HOLE_PITCH/2]) - for (y = [-JETSON_HOLE_PITCH/2, JETSON_HOLE_PITCH/2]) - translate([x, y, DECK_THICKNESS]) - cylinder(d=6, h=JETSON_STANDOFF_H); + translate([-JETSON_PL/2, -JETSON_PW/2, DECK_THICK+JETSON_STOFF]) + cube([JETSON_PL, JETSON_PW, JETSON_PT]); + for (x=[-JETSON_PITCH/2, JETSON_PITCH/2]) + for (y=[-JETSON_PITCH/2, JETSON_PITCH/2]) + translate([x,y,DECK_THICK]) cylinder(d=6, h=JETSON_STOFF); } - - // B01 M3 hole pattern (58×58 mm) - for (x = [-JETSON_HOLE_PITCH/2, JETSON_HOLE_PITCH/2]) - for (y = [-JETSON_HOLE_PITCH/2, JETSON_HOLE_PITCH/2]) - translate([x, y, DECK_THICKNESS - 1]) - cylinder(d=JETSON_HOLE_D, h=JETSON_STANDOFF_H + JETSON_PLATE_T + 2); - - // Ventilation / cable routing slots in plate - for (i = [-1, 0, 1]) - translate([i*25 - 10, -JETSON_PLATE_W/2 + 10, - DECK_THICKNESS + JETSON_STANDOFF_H - 1]) - cube([20, JETSON_PLATE_W - 20, JETSON_PLATE_T + 2]); - - // Deck anchor holes (M3 × 4 corners of plate) - for (x = [-JETSON_PLATE_L/2 + 8, JETSON_PLATE_L/2 - 8]) - for (y = [-JETSON_PLATE_W/2 + 8, JETSON_PLATE_W/2 - 8]) - translate([x, y, -1]) - cylinder(d=M3_D, h=DECK_THICKNESS + 2); + for (x=[-JETSON_PITCH/2, JETSON_PITCH/2]) + for (y=[-JETSON_PITCH/2, JETSON_PITCH/2]) + translate([x,y,DECK_THICK-1]) + cylinder(d=JETSON_HOLE_D, h=JETSON_STOFF+JETSON_PT+2); + for (i=[-1,0,1]) + translate([i*22-8,-JETSON_PW/2+8,DECK_THICK+JETSON_STOFF-1]) + cube([16, JETSON_PW-16, JETSON_PT+2]); + for (x=[-JETSON_PL/2+8, JETSON_PL/2-8]) + for (y=[-JETSON_PW/2+8, JETSON_PW/2-8]) + translate([x,y,-1]) cylinder(d=M3, h=DECK_THICK+2); } } } -// ─── Bumper bracket ─────────────────────────────────────────────────────────── -// `front` = +1 (front) or -1 (rear) -module bumper_bracket(front = 1) { - y_pos = front * (FRAME_WIDTH/2); +// ============================================================================= +// BUMPER BRACKETS (front / rear) +// ============================================================================= - translate([0, y_pos, 0]) { +module bumper_bracket(front = 1) { + yp = front * PLATE_D/2; + translate([0, yp, 0]) { difference() { union() { - // Horizontal mounting rail plate translate([-BUMPER_L/2, front*(BUMPER_T/2), 0]) cube([BUMPER_L, BUMPER_T, BUMPER_H]); - - // Vertical gussets at ends - for (x = [-BUMPER_L/2 + 5, BUMPER_L/2 - BUMPER_T - 5]) - translate([x, front*(BUMPER_T), 0]) - cube([BUMPER_T, 20, BUMPER_H]); - - // Bumper tube saddle clamps (×3 evenly spaced) - for (x = [-BUMPER_L/3, 0, BUMPER_L/3]) - translate([x - BUMPER_TUBE_OD/2 - 2, - front*(BUMPER_T + 15), - BUMPER_H - BUMPER_TUBE_OD/2 - 5]) + for (x=[-BUMPER_L/2+4, BUMPER_L/2-BUMPER_T-4]) + translate([x, front*BUMPER_T, 0]) cube([BUMPER_T, 16, BUMPER_H]); + for (x=[-BUMPER_L/3, 0, BUMPER_L/3]) + translate([x-BUMPER_TUBE_OD/2-2, + front*(BUMPER_T+10), + BUMPER_H-BUMPER_TUBE_OD/2-4]) difference() { - cube([BUMPER_TUBE_OD + 4, BUMPER_TUBE_OD/2 + 4, BUMPER_TUBE_OD + 4]); - translate([BUMPER_TUBE_OD/2 + 2, -1, BUMPER_TUBE_OD/2 + 2]) - rotate([-90, 0, 0]) - cylinder(d=BUMPER_TUBE_OD, h=BUMPER_TUBE_OD/2 + 6); + cube([BUMPER_TUBE_OD+4, BUMPER_TUBE_OD/2+4, BUMPER_TUBE_OD+4]); + translate([BUMPER_TUBE_OD/2+2,-1,BUMPER_TUBE_OD/2+2]) + rotate([-90,0,0]) + cylinder(d=BUMPER_TUBE_OD, h=BUMPER_TUBE_OD/2+6); } } - - // Deck mounting holes (M5 × 6 along length) - for (x = [-BUMPER_L/2 + 20, -BUMPER_L/6, BUMPER_L/6, BUMPER_L/2 - 20]) - translate([x, y_pos * 0.001, -1]) - cylinder(d=M5_D, h=BUMPER_H + 2); + for (x=[-BUMPER_L/2+16, -BUMPER_L/6, BUMPER_L/6, BUMPER_L/2-16]) + translate([x, yp*0.001, -1]) cylinder(d=M5, h=BUMPER_H+2); } } } - -// ============================================================================= -// DIMENSIONS / ANNOTATIONS (2D cross-section reference) -// ============================================================================= -// Uncomment to render a side-view dimension guide: -// -// %translate([0, FRAME_WIDTH/2 + 30, 0]) { -// color("Blue") { -// cube([WHEELBASE, 1, 1]); // wheelbase span -// translate([0,0,0]) text(str("Wheelbase: ", WHEELBASE, "mm"), size=12); -// } -// } diff --git a/chassis/prototype_baseplate.scad b/chassis/prototype_baseplate.scad index 5ab165f..e822b7f 100644 --- a/chassis/prototype_baseplate.scad +++ b/chassis/prototype_baseplate.scad @@ -1,96 +1,104 @@ // ============================================================================= -// SaltyBot — Prototype Base Plate (Rev C — compact, stem-mount) -// Agent: sl-mechanical | 2026-02-28 +// SaltyBot — Prototype Base Plate (Rev D — correct 270 × 240 mm envelope) +// Agent: sl-mechanical | 2026-02-28 | Fixes issue #18 // // Laser-cut or CNC-routed flat plate (6 mm Al / 8 mm acrylic). -// Uses CALIPER-VERIFIED hub motor axle measurements (see PR #7 / #11). // -// ARCHITECTURE CHANGE (Rev C): -// Batteries are NO LONGER on the base plate. -// They stand vertically on a central stem via stem_battery_clamp.scad. -// The base plate is now compact — only axle dropouts + stem socket + FC mount. +// PLATE DIMENSIONS (per spec, issue #18) +// Width (axle direction, X): 270 mm +// Depth (front-to-rear, Y): 240 mm +// Wheels extend beyond plate edges — expected. // -// ── AXLE PROFILE (stepped D-cut, caliper-verified) ─────────────────────────── -// Zone │ Feature │ Ø / Width │ Length -// ───────┼──────────────────┼────────────────────┼────────── -// Base │ Round (near hub) │ Ø 16.11 mm │ 15.00 mm -// D-cut │ Round OD │ Ø 15.95 mm │ 43.35 mm -// │ Flat chord │ 13.00 mm │ -// Tip │ Shoulder/end │ — │ 3.00 mm -// Total │ Hub face → tip │ — │ 65.50 mm -// Bearing seat collar: Ø 37.8 mm -// Tire: 10 × 2.125" pneumatic (Ø 254 mm, 35 PSI) +// MOTOR AXLE GEOMETRY +// Plate edge = axle entry point (X = ±135 mm from plate centre) +// Fork slot depth = 50 mm inward → axle C/L seated at X = ±85 mm +// Axle-to-axle distance = 170 mm +// Tires (Ø254 mm, 54 mm wide) at X = ±(85 ± 27) = ±58 – 112 mm ← clear of ±135 edge +// +// VERIFIED AXLE PROFILE (caliper) +// Base section: Ø 16.11 mm, 15 mm long +// D-cut zone: Ø 15.95 mm, 13 mm flat chord, 43.35 mm long +// Tip: 3 mm +// Total: 65.5 mm from hub face +// Bearing seat collar: Ø 37.8 mm // ============================================================================= $fn = 128; +// ============================================================================= +// PLATE DIMENSIONS ← primary parameters for issue #18 fix +// ============================================================================= + +PLATE_W = 270.0; // mm width (axle / left-right direction) +PLATE_D = 240.0; // mm depth (front-to-rear) +PLATE_THICK = 6.0; // mm stock thickness + +// Derived +PLATE_X_HALF = PLATE_W / 2; // 135 mm — plate edge +FORK_DEPTH = 50.0; // mm fork slot depth (inward from edge) +AXLE_X = PLATE_X_HALF - FORK_DEPTH; // 85 mm — axle C/L from plate centre + // ============================================================================= // AXLE PARAMETERS — caliper-verified // ============================================================================= -AXLE_BASE_DIA = 16.11; -AXLE_BASE_LEN = 15.00; -AXLE_DCUT_DIA = 15.95; -AXLE_DCUT_FLAT = 13.00; -AXLE_DCUT_LEN = 43.35; -AXLE_TIP_LEN = 3.00; -AXLE_TOTAL = 65.50; +AXLE_BASE_DIA = 16.11; AXLE_BASE_LEN = 15.00; +AXLE_DCUT_DIA = 15.95; AXLE_DCUT_FLAT = 13.00; AXLE_DCUT_LEN = 43.35; +AXLE_TIP_LEN = 3.00; AXLE_TOTAL = 65.50; BEARING_SEAT_OD = 37.80; -TIRE_OD = 254.0; -AXLE_CL_HEIGHT = TIRE_OD / 2; // 127 mm above ground +TIRE_OD = 254.0; // 10" × 25.4 mm // ============================================================================= -// PLATE PARAMETERS +// DROPOUT CLAMP PARAMETERS // ============================================================================= -WHEELBASE = 600.0; // mm axle C/L to axle C/L -// Plate depth now driven only by structural + FC needs (no battery footprint). -PLATE_DEPTH = 130.0; // mm front-to-rear -PLATE_OVERHANG = 40.0; // mm plate past axle C/L each side -PLATE_THICK = 6.0; // mm +FORK_W = AXLE_BASE_DIA + 0.4; // 16.51 mm fork slot width -// Fork slot -FORK_W = AXLE_BASE_DIA + 0.4; // 16.51 mm -FORK_DEPTH = 50.0; +CLAMP_L = 80.0; // mm clamp length along axle axis + // outer end at AXLE_X + CLAMP_L/2 = 85+40 = 125 mm + // plate edge at 135 mm → 10 mm margin ✓ +CLAMP_H = 60.0; // mm clamp height (front-to-rear, in Y) +CLAMP_THICK = 8.0; // mm each clamp layer +CLAMP_BOLT_D = 5.3; // M5 clearance +CLAMP_BOLT_DX = 22.0; // mm bolt offset ±X from axle C/L + // inner bolt at AXLE_X - 22 = 63 mm ✓ + // outer bolt at AXLE_X + 22 = 107 mm < 135 mm ✓ +CLAMP_BOLT_DY = 22.0; // mm bolt offset ±Y from axle C/L +CLAMP_ALIGN_D = 4.1; // Ø4 alignment pin clearance -// Dropout clamp (two-piece sandwich) -CLAMP_L = 80.0; -CLAMP_H = 60.0; -CLAMP_THICK = 8.0; -CLAMP_BOLT_D = 5.3; // M5 -CLAMP_BOLT_DX = 22.0; -CLAMP_BOLT_DY = 22.0; -CLAMP_ALIGN_D = 4.1; // Ø4 pin +DCUT_CL = 0.3; // mm D-cut bore clearance (all-round) +DCUT_R = (AXLE_DCUT_DIA + 2*DCUT_CL) / 2; +DCUT_FC = AXLE_DCUT_FLAT + 2*DCUT_CL; -// D-cut bore clearance -DCUT_CL = 0.3; +// ============================================================================= +// STEM SOCKET +// ============================================================================= + +STEM_OD = 38.1; // mm 1.5" EMT conduit OD +STEM_BORE = STEM_OD + 0.5; +STEM_FLANGE_OD = 82.0; // mm flange ring OD +STEM_FLANGE_BC = 66.0; // mm M5 bolt circle diameter (r = 33 mm) +STEM_FLANGE_T = 6.0; // mm = PLATE_THICK + +// Verify flange bolts clear all other holes: +// Flange bolts at (±33, 0) and (0, ±33) +// Clamp bolts at (±63, ±22) and (±107, ±22) +// FC holes centred at (-40, 0) spanning X = ±55.25, Y = ±15.25 +// All separations are adequate ✓ + +// ============================================================================= +// FC MOUNT — MAMBA F722S 30.5 × 30.5 mm M3 +// ============================================================================= -// FC mount — MAMBA F722S 30.5 × 30.5 mm M3 FC_PITCH = 30.5; FC_HOLE_D = 3.2; -// FC is offset toward front of plate (away from stem) -FC_X_OFFSET = -40.0; // mm from plate centre (negative = front/motor side) - -// ============================================================================= -// STEM SOCKET PARAMETERS -// ============================================================================= - -STEM_OD = 38.1; // mm 1.5" EMT conduit OD -STEM_BORE = STEM_OD + 0.5; // 38.6 mm with clearance -// Flange ring (laser-cut, bolts above + below plate to grip tube): -STEM_FLANGE_OD = 82.0; // mm flange outer diameter -STEM_FLANGE_BC = 66.0; // mm bolt circle diameter (4× M5 at 90°) -STEM_FLANGE_T = 6.0; // mm = PLATE_THICK (flush-mount) -// Stem position: at plate centre (X=0, Y=0) +FC_X_OFFSET = -40.0; // mm front of plate (−X); FC at (−40, 0) within ±135 ✓ // ============================================================================= // UTILITIES // ============================================================================= -M3 = 3.2; M4 = 4.3; M5 = 5.3; -PLATE_X_HALF = WHEELBASE/2 + PLATE_OVERHANG; // ± 340 mm -DCUT_R = (AXLE_DCUT_DIA + 2*DCUT_CL) / 2; -DCUT_FC = AXLE_DCUT_FLAT + 2*DCUT_CL; +M3 = 3.2; M4 = 4.3; M5 = 5.3; // ============================================================================= // RENDER CONTROL @@ -100,144 +108,134 @@ DCUT_FC = AXLE_DCUT_FLAT + 2*DCUT_CL; // "plate_2d" DXF — base plate // "clamp_lower_2d" DXF — lower dropout clamp (× 2) // "clamp_upper_2d" DXF — upper dropout clamp (× 2) -// "stem_flange_2d" DXF — stem flange ring (× 2, one above + one below plate) +// "stem_flange_2d" DXF — stem flange ring (× 2) RENDER = "assembly"; -if (RENDER == "assembly") { - assembly(); -} else if (RENDER == "plate_2d") { - projection(cut=true) translate([0,0,-PLATE_THICK/2]) base_plate(); -} else if (RENDER == "clamp_lower_2d") { - projection(cut=true) translate([0,0,-CLAMP_THICK/2]) clamp_lower(); -} else if (RENDER == "clamp_upper_2d") { - projection(cut=true) translate([0,0,-CLAMP_THICK/2]) clamp_upper(); -} else if (RENDER == "stem_flange_2d") { - projection(cut=true) translate([0,0,-STEM_FLANGE_T/2]) stem_flange(); -} +if (RENDER == "assembly") assembly(); +else if (RENDER == "plate_2d") projection(cut=true) translate([0,0,-PLATE_THICK/2]) base_plate(); +else if (RENDER == "clamp_lower_2d") projection(cut=true) translate([0,0,-CLAMP_THICK/2]) clamp_lower(); +else if (RENDER == "clamp_upper_2d") projection(cut=true) translate([0,0,-CLAMP_THICK/2]) clamp_upper(); +else if (RENDER == "stem_flange_2d") projection(cut=true) translate([0,0,-STEM_FLANGE_T/2]) stem_flange(); // ============================================================================= // ASSEMBLY // ============================================================================= module assembly() { - color("Silver", 0.85) base_plate(); + color("Silver", 0.85) base_plate(); for (side = [-1, 1]) { - color("SteelBlue", 0.80) - translate([side * WHEELBASE/2, 0, PLATE_THICK]) + color("SteelBlue", 0.80) + translate([side * AXLE_X, 0, PLATE_THICK]) clamp_lower(); color("CornflowerBlue", 0.80) - translate([side * WHEELBASE/2, 0, PLATE_THICK + CLAMP_THICK]) + translate([side * AXLE_X, 0, PLATE_THICK + CLAMP_THICK]) clamp_upper(); } - // Stem flange rings (above and below plate) - color("DimGray", 0.70) - translate([0, 0, -STEM_FLANGE_T]) - stem_flange(); - color("DimGray", 0.70) - translate([0, 0, PLATE_THICK]) - stem_flange(); + // Stem flanges (above and below plate) + color("DimGray", 0.70) translate([0, 0, -STEM_FLANGE_T]) stem_flange(); + color("DimGray", 0.70) translate([0, 0, PLATE_THICK]) stem_flange(); - // Reference ghosts + // Ghosts — not exported %color("Orange", 0.25) translate([0, 0, PLATE_THICK + STEM_FLANGE_T]) - cylinder(d=STEM_OD, h=800); // vertical stem + cylinder(d=STEM_OD, h=900); + %for (side = [-1, 1]) - color("Tomato", 0.2) - translate([side * WHEELBASE/2, 0, 0]) - rotate([0, side*90, 0]) + color("Tomato", 0.20) + translate([side * PLATE_X_HALF, 0, 0]) + rotate([0, side * (-90), 0]) // axle projects inward from plate edge axle_ghost(); } // ============================================================================= -// BASE PLATE (Part A — compact) +// BASE PLATE (Part A — 270 × 240 mm) // ============================================================================= module base_plate() { - R = 12; // corner radius + R = 10; // corner radius difference() { - // ── Outer profile ── + // ── Outer profile (minkowski rounded corners) ────────────────────── linear_extrude(PLATE_THICK) minkowski() { - square([2*(PLATE_X_HALF - R), PLATE_DEPTH - 2*R], center=true); + square([PLATE_W - 2*R, PLATE_D - 2*R], center=true); circle(r=R); } - // ── Fork slots (open at ±X edges, semicircular tip) ────────────── + // ── Fork slots (open at ±X plate edges, semicircular tip) ────────── + // Slot width FORK_W, depth FORK_DEPTH inward from plate edge. + // Axle seats in the semicircle at X = ±(PLATE_X_HALF − FORK_DEPTH) = ±85 mm. for (side = [-1, 1]) { - translate([side*(PLATE_X_HALF - FORK_DEPTH), -FORK_W/2, -1]) - cube([FORK_DEPTH + 1, FORK_W, PLATE_THICK + 2]); - translate([side*(PLATE_X_HALF - FORK_DEPTH), 0, -1]) + // Rectangular slot body + translate([side * (PLATE_X_HALF - FORK_DEPTH), -FORK_W/2, -1]) + cube([side > 0 ? FORK_DEPTH + 1 : FORK_DEPTH + 1, + FORK_W, PLATE_THICK + 2]); + // Semicircular slot tip (axle seat) + translate([side * (PLATE_X_HALF - FORK_DEPTH), 0, -1]) cylinder(d=FORK_W, h=PLATE_THICK + 2); } - // ── Bearing seat relief (prevents Ø37.8 mm collar binding on edge) ─ + // ── Bearing seat relief (prevents Ø37.8 mm hub collar contacting edge) ─ for (side = [-1, 1]) - translate([side*PLATE_X_HALF - side*(BEARING_SEAT_OD/2 + 1), + translate([side * (PLATE_X_HALF - BEARING_SEAT_OD/2 - 1), -BEARING_SEAT_OD/2, -1]) cube([BEARING_SEAT_OD/2 + 2, BEARING_SEAT_OD, PLATE_THICK + 2]); - // ── Dropout clamp bolt through-holes ───────────────────────────── + // ── Dropout clamp bolt through-holes ─────────────────────────────── for (side = [-1, 1]) for (dx = [-CLAMP_BOLT_DX, CLAMP_BOLT_DX]) for (dy = [-CLAMP_BOLT_DY, CLAMP_BOLT_DY]) - translate([side*WHEELBASE/2 + dx, dy, -1]) + translate([side * AXLE_X + dx, dy, -1]) cylinder(d=CLAMP_BOLT_D, h=PLATE_THICK + 2); - // ── Alignment pin holes (Ø4) ────────────────────────────────────── + // ── Alignment pin holes (Ø4 × 2 per clamp) ───────────────────────── for (side = [-1, 1]) for (dy = [-CLAMP_BOLT_DY + 8, CLAMP_BOLT_DY - 8]) - translate([side*WHEELBASE/2, dy, -1]) + translate([side * AXLE_X, dy, -1]) cylinder(d=CLAMP_ALIGN_D, h=PLATE_THICK + 2); - // ── Stem socket bore ────────────────────────────────────────────── - translate([0, 0, -1]) - cylinder(d=STEM_BORE, h=PLATE_THICK + 2); + // ── Stem socket bore ───────────────────────────────────────────────── + translate([0, 0, -1]) cylinder(d=STEM_BORE, h=PLATE_THICK + 2); - // ── Stem flange bolt holes (4× M5, 90° pattern on STEM_FLANGE_BC) ─ + // ── Stem flange bolt holes (4× M5 at 90° on Ø66 BC) ──────────────── for (a = [0, 90, 180, 270]) rotate([0, 0, a]) translate([STEM_FLANGE_BC/2, 0, -1]) cylinder(d=M5, h=PLATE_THICK + 2); - // ── FC mount (MAMBA F722S 30.5 × 30.5 M3) ──────────────────────── + // ── FC mount holes (MAMBA F722S 30.5 × 30.5 M3) ──────────────────── for (x = [FC_X_OFFSET - FC_PITCH/2, FC_X_OFFSET + FC_PITCH/2]) for (y = [-FC_PITCH/2, FC_PITCH/2]) - translate([x, y, -1]) - cylinder(d=FC_HOLE_D, h=PLATE_THICK + 2); + translate([x, y, -1]) cylinder(d=FC_HOLE_D, h=PLATE_THICK + 2); - // ── Wiring / cable pass-through slots (2×, flanking stem) ───────── - for (dy = [-30, 30]) + // ── Wiring / cable pass-through slots (flanking stem) ─────────────── + // Placed at Y = ±55 mm (clear of stem flange bolts at ±33 mm and clamp at ±22 mm) + for (dy = [-55, 55]) hull() { - translate([15, dy, -1]) cylinder(d=14, h=PLATE_THICK + 2); - translate([-15, dy, -1]) cylinder(d=14, h=PLATE_THICK + 2); + translate([ 12, dy, -1]) cylinder(d=12, h=PLATE_THICK + 2); + translate([-12, dy, -1]) cylinder(d=12, h=PLATE_THICK + 2); } - // ── Lightening slots (between FC zone and dropout zones) ────────── - for (sx = [-1, 1]) { - // One slot each side of stem, in the structural corridor - lx = sx * (WHEELBASE/4); + // ── Lightening holes (2× in open areas between stem and clamp zones) ─ + // At X = 0, Y = ±80 mm (front/rear open corridor) + for (dy = [-80, 80]) hull() { - translate([lx, -(PLATE_DEPTH/2 - 22), -1]) cylinder(d=18, h=PLATE_THICK+2); - translate([lx, (PLATE_DEPTH/2 - 22), -1]) cylinder(d=18, h=PLATE_THICK+2); + translate([ 20, dy, -1]) cylinder(d=20, h=PLATE_THICK + 2); + translate([-20, dy, -1]) cylinder(d=20, h=PLATE_THICK + 2); } - } } } // ============================================================================= -// STEM FLANGE RING (laser-cut, qty 2 — one above, one below plate) +// STEM FLANGE RING (laser-cut, qty 2) // ============================================================================= module stem_flange() { difference() { cylinder(d=STEM_FLANGE_OD, h=STEM_FLANGE_T); - // Stem bore (tight fit — tube presses into flange) - translate([0, 0, -1]) - cylinder(d=STEM_BORE, h=STEM_FLANGE_T + 2); - // 4× M5 flange bolts + translate([0, 0, -1]) cylinder(d=STEM_BORE, h=STEM_FLANGE_T + 2); for (a = [0, 90, 180, 270]) rotate([0, 0, a]) translate([STEM_FLANGE_BC/2, 0, -1]) @@ -246,7 +244,7 @@ module stem_flange() { } // ============================================================================= -// DROPOUT CLAMP — LOWER (round bore, base-section diameter) +// DROPOUT CLAMP — LOWER (round bore, base section) // ============================================================================= module clamp_lower() { @@ -255,94 +253,78 @@ module clamp_lower() { translate([-CLAMP_L/2 + CLAMP_H/2, 0, 0]) cylinder(d=CLAMP_H, h=CLAMP_THICK); translate([ CLAMP_L/2 - CLAMP_H/2, 0, 0]) cylinder(d=CLAMP_H, h=CLAMP_THICK); } - // Round bore (base zone) - translate([0, 0, -1]) cylinder(d=AXLE_BASE_DIA + 0.4, h=CLAMP_THICK + 2); - // Slide-in slot (open toward wheel side) + translate([0,0,-1]) cylinder(d=AXLE_BASE_DIA + 0.4, h=CLAMP_THICK + 2); + // Open entry slot (axle slides in from plate edge) translate([-CLAMP_L/2 - 1, -FORK_W/2, -1]) cube([CLAMP_L/2 + 1, FORK_W, CLAMP_THICK + 2]); - // Clamp bolts for (dx = [-CLAMP_BOLT_DX, CLAMP_BOLT_DX]) for (dy = [-CLAMP_BOLT_DY, CLAMP_BOLT_DY]) translate([dx, dy, -1]) cylinder(d=CLAMP_BOLT_D, h=CLAMP_THICK + 2); - // Alignment pins for (dy = [-CLAMP_BOLT_DY + 8, CLAMP_BOLT_DY - 8]) translate([0, dy, -1]) cylinder(d=CLAMP_ALIGN_D, h=CLAMP_THICK + 2); } } // ============================================================================= -// DROPOUT CLAMP — UPPER (D-cut bore, anti-rotation) +// DROPOUT CLAMP — UPPER (D-cut bore, anti-rotation) // ============================================================================= module clamp_upper() { - dcut_r = DCUT_R; - dcut_d = sqrt(pow(dcut_r, 2) - pow(DCUT_FC/2, 2)); - + dcut_d = sqrt(pow(DCUT_R, 2) - pow(DCUT_FC/2, 2)); difference() { hull() { translate([-CLAMP_L/2 + CLAMP_H/2, 0, 0]) cylinder(d=CLAMP_H, h=CLAMP_THICK); translate([ CLAMP_L/2 - CLAMP_H/2, 0, 0]) cylinder(d=CLAMP_H, h=CLAMP_THICK); } - // D-cut bore - translate([0, 0, -1]) - linear_extrude(CLAMP_THICK + 2) - dcut_profile_2d(dcut_r, dcut_d); - // Slide-in slot + translate([0,0,-1]) + linear_extrude(CLAMP_THICK + 2) dcut_profile_2d(DCUT_R, dcut_d); translate([-CLAMP_L/2 - 1, -FORK_W/2, -1]) cube([CLAMP_L/2 + 1, FORK_W, CLAMP_THICK + 2]); - // Clamp bolts for (dx = [-CLAMP_BOLT_DX, CLAMP_BOLT_DX]) for (dy = [-CLAMP_BOLT_DY, CLAMP_BOLT_DY]) translate([dx, dy, -1]) cylinder(d=CLAMP_BOLT_D, h=CLAMP_THICK + 2); - // Alignment pins for (dy = [-CLAMP_BOLT_DY + 8, CLAMP_BOLT_DY - 8]) translate([0, dy, -1]) cylinder(d=CLAMP_ALIGN_D, h=CLAMP_THICK + 2); - // Orientation emboss + // Orientation marker (triangle pointing toward flat) translate([0, dcut_d + 1.5, CLAMP_THICK - 0.8]) - linear_extrude(1) - polygon([[0,0],[-3,-5],[3,-5]]); + linear_extrude(1) polygon([[0,0],[-3,-5],[3,-5]]); } } -// ── D-cut 2D profile helper ─────────────────────────────────────────────────── module dcut_profile_2d(r, flat_d) { intersection() { circle(r=r); - translate([-r - 1, -r - 1]) - square([2*(r+1), r + 1 + flat_d]); + translate([-r-1, -r-1]) square([2*(r+1), r+1+flat_d]); } } // ============================================================================= -// AXLE GHOST (visualisation only) +// AXLE GHOST (visualisation only) // ============================================================================= module axle_ghost() { cylinder(d=BEARING_SEAT_OD, h=12); - translate([0,0,12]) cylinder(d=AXLE_BASE_DIA, h=AXLE_BASE_LEN); + translate([0,0,12]) cylinder(d=AXLE_BASE_DIA, h=AXLE_BASE_LEN); translate([0,0,12+AXLE_BASE_LEN]) cylinder(d=AXLE_DCUT_DIA, h=AXLE_DCUT_LEN); } // ============================================================================= -// DXF EXPORT +// DXF EXPORT COMMANDS // ============================================================================= // -// Part 1 — Base plate: +// Base plate (270 × 240 mm, 6 mm Al or 8 mm acrylic): // openscad prototype_baseplate.scad -D 'RENDER="plate_2d"' -o baseplate.dxf // -// Part 2 — Dropout clamp, lower (× 2): +// Dropout clamp lower (× 2): // openscad prototype_baseplate.scad -D 'RENDER="clamp_lower_2d"' -o clamp_lower.dxf // -// Part 3 — Dropout clamp, upper (× 2): +// Dropout clamp upper (× 2): // openscad prototype_baseplate.scad -D 'RENDER="clamp_upper_2d"' -o clamp_upper.dxf // -// Part 4 — Stem flange ring (× 2, one each side of plate): +// Stem flange ring (× 2): // openscad prototype_baseplate.scad -D 'RENDER="stem_flange_2d"' -o stem_flange.dxf // -// Materials: -// Plate + flanges : 6 mm 5052-H32 aluminium (preferred) -// 8 mm clear acrylic (quick proto) -// Dropout clamps : 8 mm 6061-T6 aluminium -// Stem tube : 38.1 mm OD × 1.5 mm wall 6061-T6 (or 1.5" EMT) -// Cut stem to ~1050 mm — allows batteries from ~100 mm to ~950 mm height. +// PLATE BLANK: 280 × 250 mm (10 mm oversized each side for datum + clamping) +// CLAMP BLANKS: 90 × 70 mm each +// FLANGE BLANKS: Ø90 mm disc each // =============================================================================