// Handheld remote: bottom shell + lid, rebuilt from the Fusion 360 model // fusion360/remote/remote v40.f3d (the `bottom` and `top` solids in // "remote v40.stp"; remote v4 is an early flat stick plate). // // Written in the original's top-view coordinates (u, v) = (Fusion x, // -Fusion z), so every number can be checked against the Fusion file. // Z = 0 on the rim of the bottom shell (the lid's top face sits flush). // Text on the lid reads upright with +v up. // // Deviations from the original (its bugs): // - Lid moved by (-0.406, -0.21, +0.365) mm: it was off-centre and sat // 0.365 mm into the bottom's ledge, its posts overlapping the bosses. // - The display bezel's stepped facets (an imported mesh) are a clean // rectangular window plus its 0.45 mm lip; small clip notches omitted. // - Body12 (an "ON-OFF" bracket line that was never cut into the lid) left out. /* [Shell] */ CORNERS = [[-66.5, -58.883], [66.5, -58.883], [66.5, 41.117], [-66.5, 41.117]]; R_OUT = 37.5; // outside of the bottom shell R_WALL = 32.5; // inside of the walls (5 mm) R_LEDGE = 35.5; // rebate the lid drops into R_LID = 35.0; // lid outline (0.5 mm clearance in the rebate) FLOOR_Z = [-45, -42]; LEDGE_Z = -5; LID_Z = [-5, 0]; /* [Fixing] */ BOSSES = [[-30, 40], [30, 40], [-80.387, -72.711], [82.73, -73]]; BOSS_D = 10; BOSS_TOP = -35; BOSS_HOLE_D = 6; // through the floor, 10 mm deep (magnets?) POST_HOLE_D = 3; // blind, from the lid post's end /* [Sticks, switch, display] */ STICKS = [[-66.5, 41.117], [66.5, 41.117]]; STICK_HOLE_D = 40; STICK_SCREW = 16.25; // 4 x d2 (M2.5 self-tapping) on the joystick's 32.5 mm square (original: 33) SWITCH_SLOT = [[-4, 33.975], [4, 49.975]]; // on/off slide switch, mounted on top (same as the robot's) SWITCH_HOLES = [[0, 32.475], [0, 51.475]]; PILL = [[0, 5], [0, 15]]; // centres of the round ends WINDOW = [[-37.94, -66.964], [37.96, -16.064]]; WINDOW_LIP = [0.45, -1.05, -0.45]; // inset, z range DISPLAY_POCKET = [[-49.0, -73.0], [52.258, -10.0]]; DISPLAY_POCKET_Z = -2; // pocket from the lid's underside up to here DISPLAY_POSTS = [[-44.939, -65.772], [49.261, -65.772], [-44.939, -17.272], [49.261, -17.272]]; TEXT = [["ON", [0.7, 26.29]], ["OFF", [0.15, 57.61]]]; ENGRAVE_D = 1; /* [Rendering] */ SHOW_BOTTOM = true; SHOW_LID = true; SHOW_DISPLAY = true; SHOW_STICKS = true; SHOW_BATTERY = true; EXPLODE = 0; /* [Hidden] */ $fa = 2; $fs = 0.3; BIG = 300; module lx(h) linear_extrude(h, convexity = 10) children(); module rounded_2d(r) hull() for (c = CORNERS) translate(c) circle(r = r); module stadium_2d(a, b, r) hull() { translate(a) circle(r = r); translate(b) circle(r = r); } // ---------- bottom shell -------------------------------------------------- module remote_bottom() render(convexity = 10) difference() { union() { difference() { translate([0, 0, FLOOR_Z[0]]) lx(-FLOOR_Z[0]) rounded_2d(R_OUT); translate([0, 0, FLOOR_Z[1]]) lx(BIG) rounded_2d(R_WALL); translate([0, 0, LEDGE_Z]) lx(BIG) rounded_2d(R_LEDGE); } for (b = BOSSES) translate([b[0], b[1], FLOOR_Z[1] - 1]) cylinder(d = BOSS_D, h = BOSS_TOP - FLOOR_Z[1] + 1); // blocks rising from the floor translate([-10.5, 29, FLOOR_Z[1] - 1]) cube([21, 9, -17 - FLOOR_Z[1] + 1]); translate([-4, 44, FLOOR_Z[1] - 1]) cube([8, 24, -17 - FLOOR_Z[1] + 1]); for (m = [-1, 1]) translate([m * 27.9, -15, FLOOR_Z[1] - 1]) cylinder(d = 5, h = 4); translate([88, -10, FLOOR_Z[1] - 1]) cube([12, 14, -28 - FLOOR_Z[1] + 1]); // port board block // post up through the lid's slot, with a rounded lanyard loop on top translate([0, 0, FLOOR_Z[1] - 1]) lx(-FLOOR_Z[1] + 1) stadium_2d(PILL[0], PILL[1], 4.75); difference() { intersection() { hull() for (p = PILL) translate([p[0], p[1], 2.75]) sphere(r = 4.75); translate([-10, 0, 0]) cube([20, 30, 10]); } hull() for (p = PILL) translate([-10, p[1], 2.75]) rotate([0, 90, 0]) cylinder(r = 2.75, h = 20); } } for (b = BOSSES) translate([b[0], b[1], FLOOR_Z[0] - 1]) cylinder(d = BOSS_HOLE_D, h = BOSS_TOP - FLOOR_Z[0] + 2); for (m = [-1, 1]) translate([m * 6.562, 35, FLOOR_Z[1]]) cylinder(d = 3, h = 30); for (m = [-1, 1]) translate([m * 27.9, -15, FLOOR_Z[1]]) cylinder(d = 3, h = 10); for (m = [-1, 1]) translate([0, 0, FLOOR_Z[0] - 1]) lx(5) stadium_2d([m * 23.9, -17], [m * 23.9, -13], 1); translate([98, -10, -35]) cube([10, 14, 15]); // side port opening for (v = [-7.5, 1.5]) translate([91, v, -37]) cylinder(d = 3, h = 10); } // ---------- lid ----------------------------------------------------------- module lid_plate_2d() difference() { rounded_2d(R_LID); for (s = STICKS) translate(s) { circle(d = STICK_HOLE_D); for (du = [-1, 1], dv = [-1, 1]) translate([du, dv] * STICK_SCREW) circle(d = 2); } stadium_2d(PILL[0], PILL[1], 5); translate(SWITCH_SLOT[0]) square(SWITCH_SLOT[1] - SWITCH_SLOT[0]); for (h = SWITCH_HOLES) translate(h) circle(d = 2); translate(WINDOW[0]) offset(r = 0.5) offset(delta = -0.5) square(WINDOW[1] - WINDOW[0]); } module remote_lid() render(convexity = 10) difference() { union() { translate([0, 0, LID_Z[0]]) lx(LID_Z[1] - LID_Z[0]) lid_plate_2d(); for (b = BOSSES) translate([b[0], b[1], BOSS_TOP]) cylinder(d = BOSS_D, h = LID_Z[0] - BOSS_TOP + 1); // window lip, left over from the original's display bezel translate([0, 0, WINDOW_LIP[1]]) lx(WINDOW_LIP[2] - WINDOW_LIP[1]) difference() { translate(WINDOW[0]) square(WINDOW[1] - WINDOW[0]); translate(WINDOW[0] + [1, 1] * WINDOW_LIP[0]) square(WINDOW[1] - WINDOW[0] - [2, 2] * WINDOW_LIP[0]); } } translate([0, 0, LID_Z[0] - 1]) lx(DISPLAY_POCKET_Z - LID_Z[0] + 1) difference() { translate(DISPLAY_POCKET[0]) square(DISPLAY_POCKET[1] - DISPLAY_POCKET[0]); for (p = DISPLAY_POSTS) translate(p) circle(d = 6); } for (p = DISPLAY_POSTS) translate([p[0], p[1], LID_Z[0] - 1]) cylinder(d = 2.5, h = 4); for (b = BOSSES) translate([b[0], b[1], BOSS_TOP - 1]) cylinder(d = POST_HOLE_D, h = LID_Z[0] - BOSS_TOP + 1); for (t = TEXT) translate([t[1][0], t[1][1], LID_Z[1] - ENGRAVE_D]) lx(ENGRAVE_D + 1) text(t[0], size = 5.8, halign = "center", valign = "center", font = "Liberation Sans"); } // ---------- reference parts ----------------------------------------------- // Nextion NX4832T035 (3.5" Basic): 100.5 x 54.94 mm board, M3 holes 3.15 / // 3.22 mm from its edges = the lid's 94.2 x 48.5 post grid; LCD module // approximate, its visible area (73.44 x 48.96) under the window. module nextion_3_5() { centre = (DISPLAY_POSTS[0] + DISPLAY_POSTS[3]) / 2; translate([centre[0], centre[1], DISPLAY_POCKET_Z - 4.5 - 1.6]) { color("#1e5a2a") difference() { translate([-100.5/2, -54.94/2, 0]) cube([100.5, 54.94, 1.6]); for (p = DISPLAY_POSTS) translate([p[0] - centre[0], p[1] - centre[1], -1]) cylinder(d = 3.2, h = 4); } } win = (WINDOW[0] + WINDOW[1]) / 2; color("#202020") translate([win[0] - 84/2, win[1] - 54/2, DISPLAY_POCKET_Z - 4.5]) cube([84, 54, 4.5]); color("#3050a0") translate([win[0] - 73.44/2, win[1] - 48.96/2, DISPLAY_POCKET_Z - 0.2]) cube([73.44, 48.96, 0.2]); } // JH-D400X-R4 4-axis joystick (X/Y +-25 deg, twist +-45 deg, button), // from its dimension drawing. Mounted with its dust cover on the lid's top // face; the body hangs through the d40 opening. Local frame: stick axis on // Z, Z = 0 on the dust cover's underside. Pots on +X and +Y. JOY_COVER_D = 53.5; JOY_COVER_T = 3; // not dimensioned; drawing proportions JOY_BOX = 40.4; // control box, square JOY_BOX_H = 25.5; JOY_BELOW = 42; // box bottom to dust cover top JOY_ABOVE = 52.5; // dust cover top to button top JOY_POT_D = 16.5; JOY_POT_L = (49.6 - 40.4); module jh_d400x() { top = JOY_COVER_T; color("#202020") { difference() { cylinder(d = JOY_COVER_D, h = JOY_COVER_T); for (du = [-1, 1], dv = [-1, 1]) translate([du * 16.25, dv * 16.25, -1]) cylinder(d = 2.7, h = 5); } translate([0, 0, top]) cylinder(d1 = 39.5, d2 = 30, h = 3); // dust ring / boot translate([0, 0, top - JOY_BELOW]) translate([-JOY_BOX/2, -JOY_BOX/2, 0]) cube([JOY_BOX, JOY_BOX, JOY_BOX_H]); translate([0, 0, top - JOY_BELOW + JOY_BOX_H]) cylinder(d = 34, h = JOY_BELOW - JOY_BOX_H - JOY_COVER_T + 0.01); for (a = [0, 90]) rotate([0, 0, a]) translate([JOY_BOX/2, 0, top - JOY_BELOW + JOY_BOX_H/2]) rotate([0, 90, 0]) cylinder(d = JOY_POT_D, h = JOY_POT_L); // handle: neck, knurled body, top with the button translate([0, 0, top + 6]) cylinder(d = 6, h = 12); translate([0, 0, top + 16]) cylinder(d = 22, h = 8); translate([0, 0, top + 24]) cylinder(d = 35, h = 22); translate([0, 0, top + 46]) cylinder(d1 = 35, d2 = 26, h = 5); } color("#c0c0c0") translate([0, 0, top + 51]) cylinder(d = 8, h = JOY_ABOVE - 51); } // 18650 UPS / boost board (5/9/12 V, Type-C charging), "SMD battery box" // version: 89 x 22 mm board, holder on top, 22.4 mm overall, d3 holes. // Placed on the bottom's two d5 standoffs at (+-27.9, -15); the board's real // hole spacing isn't confirmed. The floor slots beside them take a strap. UPS_CENTRE = [0, -15]; UPS_Z = -39; // standoff tops module ups_18650() translate([UPS_CENTRE[0], UPS_CENTRE[1], UPS_Z]) { color("#202020") difference() { translate([-89/2, -22/2, 0]) cube([89, 22, 1.6]); for (m = [-1, 1]) translate([m * 27.9, 0, -1]) cylinder(d = 3, h = 4); } color("#151515") translate([-77/2, -21/2, 1.6]) difference() { cube([77, 21, 22.4 - 1.6]); translate([3, 1.5, 3]) cube([71, 18, 30]); } color("#4060c0") translate([-65/2, 0, 1.6 + 3 + 18.5/2]) rotate([0, 90, 0]) cylinder(d = 18.5, h = 65); color("#b0b0b0") translate([89/2 - 7, -4.5, 1.6]) cube([7.5, 9, 3.2]); // Type-C } // Small USB-C breakout (owner: glued in the holder at the side port), on the // port block (u 88..99, v -10..4, top Z -28) with the receptacle facing the // opening (v -10..4, Z -35..-20). Board size approximate. module usb_c_breakout() translate([88, -10, -28]) { color("#1a3a8a") cube([11, 14, 1.6]); color("#c0c0c0") translate([11 - 7.3, 7 - 8.9/2, 1.6]) cube([7.5, 8.9, 3.2]); } // IMU (owner: on the big standoff in the middle), assumed a GY-521 / MPU6050 // like the robot's: 21.2 x 15.6 mm board on the 21 x 9 mm block (top Z -17), // its hole end over the block's two d3 holes at v 35, board towards the centre. module imu_gy521() translate([0, 35 + 3.5 - 15.6, -17]) { color("#1f4fa0") translate([-21.2/2, 0, 0]) cube([21.2, 15.6, 1.6]); color("#222") translate([-2, 7, 1.6]) cube([4, 4, 1]); } // Teensy 4.0 (owner; no mounting holes), assumed on the hole-less 8 x 24 mm // block (u -4..4, v 44..68, top Z -17). Across the block by default; lengthwise // it would end 0.1 mm from the shell wall. TEENSY_LENGTHWISE = false; module teensy_4_0() translate([0, 56, -17]) rotate([0, 0, TEENSY_LENGTHWISE ? 90 : 0]) { color("#1e5a2a") translate([-35.6/2, -17.8/2, 0]) cube([35.6, 17.8, 1.6]); color("#222") translate([-3, -5, 1.6]) cube([10, 10, 1.2]); // i.MX RT1062 color("#c0c0c0") translate([-35.6/2 - 1, -7.5/2, 1.6]) cube([5.7, 7.5, 2.9]); // micro-USB } module remote_assembly() { if (SHOW_BOTTOM) color("#3a3a3a") remote_bottom(); if (SHOW_LID) translate([0, 0, EXPLODE]) color("#5a5a5a") remote_lid(); if (SHOW_DISPLAY) translate([0, 0, EXPLODE]) nextion_3_5(); if (SHOW_BATTERY) ups_18650(); if (SHOW_BATTERY) usb_c_breakout(); if (SHOW_BATTERY) imu_gy521(); if (SHOW_BATTERY) teensy_4_0(); // pots towards the nearest side wall and the near edge, where there's room if (SHOW_STICKS) for (s = STICKS) translate([s[0], s[1], LID_Z[1] + EXPLODE]) mirror([s[0] < 0 ? 1 : 0, 0, 0]) jh_d400x(); } remote_assembly();