245 lines
12 KiB
OpenSCAD
245 lines
12 KiB
OpenSCAD
// 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();
|