// 2026 hexapod body: top + bottom plate, rebuilt from the Fusion 360 model // fusion360/body-bottom/bottom v59.f3d (the `top` and `bottom` solids in // "bottom v59.stp", which are newer than the top.dxf/bottom.dxf sketches). // // Plate geometry is written in the original's top-view coordinates // (u, v) = (Fusion x, -Fusion z), so every number can be checked against // the Fusion file. fusion_to_robot() turns that into the robot frame of // robot/Config.h (+X forward, +Y left, +Z up): X = v, Y = -u. // Z = 0 is the top face of the bottom plate. // // Deviations from the original (its bugs): // - Left-front pivot boss at u = -50 instead of -50.894. // - All six coxa servos present (the original has none at right front). // - Hex nut wells share one orientation (the original's are rotated randomly). use <../main-pcb-2026-11-13/main-pcb.scad> include /* [Plates] */ PLATE_T = 3.0; // original: 3 mm, both plates CAVITY_H = 27.0; // inner gap; sized so the servo case ends 2 mm above the bottom plate /* [Standoffs: split halfway, screw from the top into a captive nut] */ STANDOFF_D = 15.0; SCREW_D = 3.1; SCREW_GRIP = 4.0; // solid length of each half next to the split COUNTERBORE_D = 7.0; NUT_AF = 5.5; // M3 nut across flats STANDOFFS_UV = [[35, 30], [-35, 30], [35, -30], [-35, -30]]; /* [Coxa servos] */ // Shaft axis per leg, in (u, v), with the direction the case extends from the shaft. // Indices match robot/Config.h legs 0..5. COXA_UV = [ [ 50, 100, 230], // 0 right front [ 65, 0, 180], // 1 right mid [ 50, -100, 130], // 2 right rear [-50, -100, 50], // 3 left rear [-65, 0, 0], // 4 left mid [-50, 100, -50], // 5 left front (original boss at u = -50.894) ]; ARM_R = 19.5; // rounded arm end, centred on the coxa axis PIVOT_BOSS_D = 14.0; // under the bottom plate, carries the coxa bracket PIVOT_BOSS_H = 2.0; /* [Teensy, PCB and switch] */ TEENSY_CUT = [23.78, 66.96]; // (u, v) size, centred PCB_BOSSES_UV = [[17.89, 39.48], [-17.89, 39.48], [17.89, -39.48], [-17.89, -39.48]]; PCB_BOSS_D = 5.0; PCB_BOSS_H = 1.0; // PCB top face ends up 1 mm under the top plate PCB_SCREW_D = 2.5; SWITCH_SLOT = [[-4, 57], [4, 73]]; SWITCH_HOLES = [[0, 55.5], [0, 74.5]]; SWITCH_HOLE_D = 2.0; ENGRAVE_D = 1.0; /* [Battery bay on the bottom plate] */ BAY_RAIL_U = [26.06, 29.06]; // inner/outer face of each rail, mirrored in u BAY_V = [-74.21, 31.79]; // open end at -v (rear), closed end at +v BAY_STOP = [[-16, 28.79], [16, 31.79]]; BAY_H = 13.5; /* [Rendering] */ SHOW_TOP = true; SHOW_BOTTOM = true; SHOW_PCB = true; SHOW_SERVOS = true; EXPLODE = 0; // mm; lifts the top half PCB_USB_FRONT = false; // owner: Teensy USB faces away from the switch (rear) /* [Hidden] */ $fa = 2; $fs = 0.3; STANDOFF_SPLIT = CAVITY_H / 2; TOP_Z = CAVITY_H + PLATE_T; module fusion_to_robot() rotate([0, 0, -90]) children(); // linear_extrude with a convexity, so the preview shows holes correctly. module lx(h) linear_extrude(h, convexity = 10) children(); // ---------- outline ------------------------------------------------------- function bez2(p, t) = (1-t)*(1-t)*p[0] + 2*(1-t)*t*p[1] + t*t*p[2]; function rev(l) = [for (i = [len(l)-1:-1:0]) l[i]]; NOTCH = [[-34.12, 111.31], [0, 71.93], [34.12, 111.31]]; // front/back notch WAIST = [[60.03, 83.28], [49.86, 74.48], [50.13, 64.77]]; // corner arm -> side FILLET_C = [65, 35]; FILLET_R = 15.5; N = 24; // Right half, front to back; the arm circles fill in the chords at the arm ends. Q1 = concat( [for (i = [N/2:N]) bez2(NOTCH, i/N)], [for (i = [0:N]) bez2(WAIST, i/N)], [[49.5, 35]], [for (a = [180:5:270]) FILLET_C + FILLET_R * [cos(a), sin(a)]], [[65, 0]]); RIGHT = concat(Q1, [for (p = rev(Q1)) [p[0], -p[1]]]); OUTLINE = concat(RIGHT, [for (p = rev(RIGHT)) [-p[0], p[1]]]); module outline_2d() { polygon(OUTLINE); for (c = COXA_UV) translate([c[0], c[1]]) circle(r = ARM_R); } module at_coxa(c) translate([c[0], c[1]]) rotate([0, 0, c[2]]) children(); // ---------- top plate ----------------------------------------------------- module top_plate_2d() { difference() { outline_2d(); for (c = COXA_UV) at_coxa(c) servo_pocket_2d(); square(TEENSY_CUT, center = true); for (s = STANDOFFS_UV) translate(s) circle(d = SCREW_D); for (b = PCB_BOSSES_UV) translate(b) circle(d = PCB_SCREW_D); translate(SWITCH_SLOT[0]) square(SWITCH_SLOT[1] - SWITCH_SLOT[0]); for (h = SWITCH_HOLES) translate(h) circle(d = SWITCH_HOLE_D); } } module switch_text() { for (t = [["ON", 49.3], ["OFF", 80.6]]) translate([0, t[1]]) text(t[0], size = 5.8, halign = "center", valign = "center", font = "Liberation Sans"); } module top_half() render(convexity = 10) { difference() { union() { translate([0, 0, CAVITY_H]) lx(PLATE_T) top_plate_2d(); for (s = STANDOFFS_UV) translate([s[0], s[1], STANDOFF_SPLIT]) cylinder(d = STANDOFF_D, h = CAVITY_H - STANDOFF_SPLIT); for (b = PCB_BOSSES_UV) translate([b[0], b[1], CAVITY_H - PCB_BOSS_H]) cylinder(d = PCB_BOSS_D, h = PCB_BOSS_H); } for (s = STANDOFFS_UV) translate([s[0], s[1], 0]) { translate([0, 0, STANDOFF_SPLIT - 1]) cylinder(d = SCREW_D, h = SCREW_GRIP + 2); translate([0, 0, STANDOFF_SPLIT + SCREW_GRIP]) cylinder(d = COUNTERBORE_D, h = TOP_Z); } for (b = PCB_BOSSES_UV) translate([b[0], b[1], 0]) { translate([0, 0, CAVITY_H - PCB_BOSS_H - 1]) cylinder(d = PCB_SCREW_D, h = PLATE_T + 2); translate([0, 0, TOP_Z - 1]) cylinder(d = PCB_BOSS_D, h = 2); } translate([0, 0, TOP_Z - ENGRAVE_D]) lx(ENGRAVE_D + 1) switch_text(); } } // ---------- bottom plate -------------------------------------------------- module bay_2d() { for (m = [1, -1]) scale([m, 1]) translate([BAY_RAIL_U[0], BAY_V[0]]) square([BAY_RAIL_U[1] - BAY_RAIL_U[0], BAY_V[1] - BAY_V[0]]); translate(BAY_STOP[0]) square(BAY_STOP[1] - BAY_STOP[0]); } module bottom_half() render(convexity = 10) { difference() { union() { translate([0, 0, -PLATE_T]) lx(PLATE_T) outline_2d(); for (s = STANDOFFS_UV) translate([s[0], s[1], 0]) cylinder(d = STANDOFF_D, h = STANDOFF_SPLIT); lx(BAY_H) bay_2d(); for (c = COXA_UV) translate([c[0], c[1], -PLATE_T - PIVOT_BOSS_H]) cylinder(d = PIVOT_BOSS_D, h = PIVOT_BOSS_H); } for (s = STANDOFFS_UV) translate([s[0], s[1], 0]) { translate([0, 0, STANDOFF_SPLIT - SCREW_GRIP - 1]) cylinder(d = SCREW_D, h = SCREW_GRIP + 2); translate([0, 0, -PLATE_T - 1]) cylinder(d = NUT_AF / cos(30), h = STANDOFF_SPLIT - SCREW_GRIP + PLATE_T + 1, $fn = 6); } } } // ---------- PCB ----------------------------------------------------------- // Long axis along the body (robot X), holes on the PCB bosses, top face // 1 mm under the top plate, Teensy up through the cut-out. PCB_T = 1.6; PCB_HOLE_CENTER = [22.89, 44.52]; // HOLE_GRID_CENTER in main-pcb.scad module pcb_in_body() { translate([0, 0, CAVITY_H - PCB_BOSS_H - PCB_T]) rotate([0, 0, PCB_USB_FRONT ? -90 : 90]) translate([-PCB_HOLE_CENTER[0], -PCB_HOLE_CENTER[1], 0]) main_pcb(); } // ---------- assembly ------------------------------------------------------ module body_servos_uv() for (c = COXA_UV) translate([0, 0, TOP_Z]) at_coxa(c) hs485hb(); module body_assembly() { fusion_to_robot() { if (SHOW_BOTTOM) color("#3a3a3a") bottom_half(); if (SHOW_TOP) translate([0, 0, EXPLODE]) color("#555555") top_half(); if (SHOW_SERVOS) translate([0, 0, EXPLODE]) body_servos_uv(); } if (SHOW_PCB) translate([0, 0, EXPLODE]) pcb_in_body(); } body_assembly();