// ============================================================ // miniRobo EP2 — MG90S Pan-Tilt Camera Head // Three printed parts driven by two MG90S metal-gear servos. // Units: millimeters. minirobo.io // // Set `part` to export one piece at a time for printing: // "base" | "yoke" | "tilt" | "all" // The MG90S shares the SG90 footprint, so these pockets fit // either — but build with the metal-gear MG90S. // ============================================================ part = "all"; // "base" | "yoke" | "tilt" | "all" $fn = 48; // ---------- Fit / hardware ---------- fit_tol = 0.4; // clearance around servo bodies (0.2 tight, 0.5 loose) wall = 2.4; screw_m2 = 2.2; // M2 clearance hole horn_screw = 1.8; // self-tapper pilot into servo horn // ---------- MG90S nominal dimensions ---------- mg90s_body_l = 22.8; mg90s_body_w = 12.2; mg90s_body_h = 22.5; mg90s_tab_l = 32.0; mg90s_tab_t = 2.5; mg90s_tab_z = 16.0; mg90s_hole_sp = 28.0; mg90s_shaft_dx= 6.0; mg90s_shaft_d = 5.0; horn_r = 7.0; horn_screws = 4; // ============================================================ // SHARED MODULES // ============================================================ // Negative volume of an MG90S standing with its shaft pointing +Z. module mg90s_negative() { tol = fit_tol; translate([-(mg90s_body_l+tol)/2, -(mg90s_body_w+tol)/2, 0]) cube([mg90s_body_l+tol, mg90s_body_w+tol, mg90s_body_h+1]); translate([-(mg90s_tab_l+tol)/2, -(mg90s_body_w+tol)/2, mg90s_tab_z]) cube([mg90s_tab_l+tol, mg90s_body_w+tol, mg90s_tab_t+tol]); for (x = [-mg90s_hole_sp/2, mg90s_hole_sp/2]) translate([x, 0, -1]) cylinder(d = screw_m2, h = mg90s_body_h + 6); translate([mg90s_shaft_dx, 0, mg90s_body_h - 1]) cylinder(d = 9, h = 12); } // Round-horn mounting boss: center bore + ring of pilot holes. module horn_mount(boss_d = 18, boss_t = wall + 1) { difference() { cylinder(d = boss_d, h = boss_t); translate([0,0,-1]) cylinder(d = mg90s_shaft_d + 1.5, h = boss_t + 2); for (i = [0 : horn_screws - 1]) rotate([0,0, i * 360 / horn_screws]) translate([horn_r, 0, -1]) cylinder(d = horn_screw, h = boss_t + 2); } } // ============================================================ // PART 1 — BASE PLATE (holds pan servo upright, shaft up) // ============================================================ module base_plate() { base_l = 46; base_w = 34; base_h = mg90s_tab_z + mg90s_tab_t + 2; chassis_hole_sp = 38; difference() { hull() for (x = [-1,1], y = [-1,1]) translate([x*(base_l/2 - 4), y*(base_w/2 - 4), 0]) cylinder(r = 4, h = base_h); translate([0, 0, base_h - mg90s_body_h]) mg90s_negative(); for (x = [-chassis_hole_sp/2, chassis_hole_sp/2]) translate([x, 0, -1]) { cylinder(d = screw_m2, h = base_h + 2); translate([0,0, base_h - 1.6]) cylinder(d1 = screw_m2, d2 = 4.5, h = 1.8); } translate([0,0,-1]) cube([mg90s_body_l+fit_tol, mg90s_body_w+fit_tol+8, 2.2], center = true); } } // ============================================================ // PART 2 — PAN YOKE (clips to pan horn, carries tilt servo) // ============================================================ module pan_yoke() { plate_d = 26; arm_h = mg90s_body_h + 6; arm_w = mg90s_body_w + 2*wall + fit_tol; wall_gap = mg90s_body_w + fit_tol; union() { difference() { cylinder(d = plate_d, h = wall); translate([0,0,-0.1]) horn_mount(); } for (s = [-1, 1]) translate([s * (wall_gap/2 + wall/2), 0, 0]) difference() { translate([-wall/2, -arm_w/2 + wall, 0]) cube([wall, mg90s_body_l + 2*wall, arm_h]); translate([0, mg90s_hole_sp/2, arm_h - mg90s_body_l/2]) rotate([0,90,0]) cylinder(d = screw_m2, h = wall*3, center = true); translate([0, -mg90s_hole_sp/2 + mg90s_body_l, arm_h - mg90s_body_l/2]) rotate([0,90,0]) cylinder(d = screw_m2, h = wall*3, center = true); } } } // ============================================================ // PART 3 — TILT BRACKET (clips to tilt horn, holds camera) // ============================================================ module tilt_bracket() { plate_l = 30; plate_w = 26; cam_hole_sp = 18; union() { horn_mount(boss_d = 16); translate([0, 0, wall]) difference() { translate([-plate_l/2, -plate_w/2, 0]) cube([plate_l, plate_w, wall]); for (x = [-cam_hole_sp/2, cam_hole_sp/2], y = [-cam_hole_sp/2, cam_hole_sp/2]) translate([x, y, -1]) cylinder(d = screw_m2, h = wall + 2); cylinder(d = 8, h = wall + 2, center = true); } } } // ============================================================ // RENDER SELECTOR // ============================================================ if (part == "base") base_plate(); else if (part == "yoke") pan_yoke(); else if (part == "tilt") tilt_bracket(); else { base_plate(); translate([60, 0, 0]) pan_yoke(); translate([110, 0, 0]) tilt_bracket(); }