fix: base plate dimensions 270x240mm per spec (issue #18)

Both chassis files corrected. Previous dimensions were wrong:
  prototype_baseplate.scad: was 680x130mm → now 270x240mm
  chassis_frame.scad:       was 640x220mm → now 270x240mm

Geometry recalculated for 270x240mm envelope:

  PLATE_W = 270mm (axle direction)
  PLATE_D = 240mm (front-to-rear)
  PLATE_X_HALF = 135mm (plate edge / axle entry)
  FORK_SLOT_D = 50mm → AXLE_X = 85mm from plate centre
  Axle-to-axle = 170mm (2 × 85mm)

  Wheels extend beyond plate edges as expected:
  Tire at X = ±(85 ± 27) = ±58–112mm, plate edge at ±135mm ✓

Mount positions recalculated for smaller plate:
  Dropout clamp bolt holes: X = ±(85±22) = ±63 and ±107mm ✓ (all within ±135mm)
  Stem flange bolts: r=33mm BC, at (±33,0) and (0,±33) ✓
  FC mount: X = -40mm offset, holes at ±15mm ✓
  Wiring slots: Y = ±55mm (clear of stem flange bolts at ±33mm)
  Lightening ovals: Y = ±80mm in open front/rear corridors
  Bumper length: 300mm (was 660mm, now PLATE_D + 60)
  Longitudinal ribs: 270mm long (was 600mm)

chassis_frame.scad also updated:
  AXLE_HEIGHT corrected: 310mm → 127mm (TIRE_OD/2)
  Axle dimensions: 14mm → 16.11mm, flat 10mm → 13.00mm
  AXLE_X for fork bracket positioning
  Motor fork bracket logic cleaned up (no more negative cube dimensions)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
sl-mechanical 2026-02-28 17:30:54 -05:00
parent fb3c8c863a
commit 6ea4b56471
2 changed files with 414 additions and 433 deletions

View File

@ -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);
// }
// }

View File

@ -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
// =============================================================================