// Desk case for the Samsung 20L203 2x20 VFD and a WEMOS D1 R1 (UNO-sized ESP8266). // // Three parts, all printable without supports: // body sides, top and back wall (DC jack + USB cut-outs). Print upside down, top on the bed. // bezel 15 degree front panel with the window and the display bosses. Print face down. // base floor with the board standoffs. Print flat. // // Assembly: // 1. Melt M3 heat-set inserts into: 4 display bosses + 2 feet on the bezel, // 2 rear corner bosses in the body, 4 board standoffs on the base. // 2. Bolt the display to the bezel from behind (4x M3x8). // 3. Screw the D1 board onto the base (4x M3x6). // 4. Slide the bezel (with display) up into the body's side grooves from below. // 5. Wire up, put the body on the base and screw it together from below (4x M3x8). // // Render one part: openscad -D 'part="body"' -o body.stl vfd-case.scad // Parts: "assembly" (preview with dummy display + board), "body", "bezel", "base", // "check_*" (collision checks, see the end of this file). part = "assembly"; section_x = 130; // cut position for part = "section" /* [Case] */ tilt = 15; // display tilt back from upright, degrees wall = 3; // side, top and back wall thickness lip = 1.5; // how far the bezel sits behind the front edge of the body bezel_t = 3; // bezel thickness groove = 1.5; // how deep the bezel edges sit in the side walls clr = 0.3; // fit clearance for sliding / mating parts base_t = 3.5; // base plate thickness depth = 112; // outer depth (front bottom edge to back) top_margin = 4; // bezel above the display's top edge (along the panel) /* [Display module, datasheet 4.10] */ disp_w = 190; disp_h = 64; disp_hole_inset = 4; // hole centres from the PCB edges (182 x 56 spacing) glass_x0 = 3.0; // glass left edge from PCB left edge (datasheet front view) glass_y0 = 7.85; // glass bottom edge from PCB bottom edge (datasheet front view) glass_w = 175.2; glass_h = 48.3; glass_depth = 12.0; // glass front face to PCB front face glass_gap = 0.5; // air gap between bezel and glass (datasheet asks for one) pcb_t = 1.6; disp_back = 11; // components behind the display PCB disp_raise = 14; // PCB bottom edge, measured along the panel from the bottom disp_rotated = true; // display turned 180 deg from the datasheet's front view // (jumper header on the left seen from the front); the // glass is off-centre, so this moves the bosses 8.8 mm /* [Window] */ win_w = 156; win_h = 36; win_r = 2; win_chamfer = 1.5; /* [Heat-set inserts, M3] */ insert_d = 4.0; // hole for the insert insert_depth = 6; boss_d = 7; // display bosses (larger would touch the glass) screw_d = 3.4; // M3 clearance head_d = 6.2; // M3 head counterbore in the base head_depth = 2; /* [TPU feet, held by the base screws (use M3x10 instead of M3x8)] */ foot_d = 14; // fits inside the base outline at the rear corners foot_h = 4.5; // below the base foot_spigot_h = 1.8; // locates in the base's screw counterbore foot_spigot_d = head_d - 0.2; foot_head_d = 6.6; // pocket for the screw head (from below) foot_head_depth = 3.5; // M3 socket head is 3 mm tall /* [WEMOS D1 R1 (Arduino UNO footprint)] */ board_l = 68.58; // along the case depth, connector edge at the back board_w = 53.34; board_gap = 0.5; // board edge to back wall standoff_h = 6; standoff_d = 7; // UNO hole positions: (distance from the connector edge, distance from the jack side) board_holes = [[13.97, 2.54], [15.24, 50.8], [66.04, 7.62], [66.04, 35.56]]; jack_pos = 7.6; // DC jack centre from the board's jack-side edge jack_cut = [12, 13]; // width, height (from 0.5 below the PCB top) usb_pos = 38.1; // micro-USB centre from the jack-side edge -- measure yours! usb_cut = [12, 8]; // width, height (centred 1.6 above the PCB top) board_comp_h = 10; // tallest part on the board (dummy for the collision check) $fn = 48; // ---------- derived ---------- // glass position from the PCB's bottom-left corner as mounted (front view) gl_x = disp_rotated ? disp_w - glass_x0 - glass_w : glass_x0; gl_y = disp_rotated ? disp_h - glass_y0 - glass_h : glass_y0; gx_c = gl_x + glass_w / 2; // glass centre from PCB left inner_w = 2 * (max(gx_c, disp_w - gx_c) + 1.5); // window centred, 1.5 mm side clearance outer_w = inner_w + 2 * wall; disp_x0 = inner_w / 2 - gx_c; // PCB left edge, x z_top_in = (disp_raise + disp_h + top_margin) * cos(tilt); // top wall underside height = z_top_in + wall; n_bezel_in = lip + bezel_t; // bezel inner face (local N) n_glass = n_bezel_in + glass_gap; n_pcb = n_glass + glass_depth; win_s = disp_raise + gl_y + glass_h / 2; // window centre along the panel board_x0 = inner_w / 2 - board_w / 2; board_conn_y = depth - wall - board_gap; // y of the board's connector edge pcb_top_z = standoff_h + pcb_t; foot_n = n_bezel_in + 6; // insert axis in the bezel feet foot_y = foot_n / cos(tilt); foot_xs = [inner_w * 0.25, inner_w * 0.75]; rear_boss = 9; // rear corner boss block size rear_screws = [[rear_boss / 2, depth - wall - rear_boss / 2], [inner_w - rear_boss / 2, depth - wall - rear_boss / 2]]; base_screws = concat([for (x = foot_xs) [x, foot_y]], rear_screws); echo(str("Outer size: ", outer_w, " x ", depth, " x ", height + base_t, " mm (W x D x H)")); // Panel-local frame: X = across, S = up the panel from the bottom front edge, // N = into the case from the body's front plane. The matrix is its own inverse. M = [[1, 0, 0, 0], [0, sin(tilt), cos(tilt), 0], [0, cos(tilt), -sin(tilt), 0], [0, 0, 0, 1]]; module panel() multmatrix(M) children(); module rrect(w, h, r) { offset(r) square([w - 2 * r, h - 2 * r], center = true); } // 2D side profile (y, z) extruded along x. module side_extrude(w) rotate([90, 0, 90]) linear_extrude(w) children(); // ---------- body ---------- module body() { difference() { union() { translate([-wall, 0, 0]) side_extrude(outer_w) polygon([[0, 0], [depth, 0], [depth, height], [height * tan(tilt), height]]); } // cavity, open at the front and bottom translate([0, -50, -1]) cube([inner_w, depth - wall + 50, z_top_in + 1]); // bezel grooves in the side walls intersection() { panel() translate([-groove - clr, -50, lip]) cube([inner_w + 2 * (groove + clr), 300, bezel_t + clr]); translate([-50, -50, -1]) cube([inner_w + 100, depth + 100, z_top_in + 1]); } // DC jack and USB for (c = [[jack_pos, jack_cut, pcb_top_z - 0.5 + jack_cut[1] / 2], [usb_pos, usb_cut, pcb_top_z + 1.6]]) translate([board_x0 + c[0], depth - wall / 2, c[2]]) rotate([90, 0, 0]) linear_extrude(wall + 2, center = true) rrect(c[1][0], c[1][1], 1.5); // top vents over the back half for (i = [0 : 3]) translate([inner_w / 2, depth - wall - 14 - i * 8, height - wall / 2]) linear_extrude(wall + 2, center = true) rrect(inner_w - 50, 3, 1.4); } // rear corner bosses for the base screws, chamfered up into the walls for (p = rear_screws) { x0 = p[0] < inner_w / 2 ? 0 : inner_w - rear_boss; xw = p[0] < inner_w / 2 ? 0 : inner_w - 0.01; difference() { hull() { translate([x0, depth - wall - rear_boss, 0]) cube([rear_boss, rear_boss, 10]); translate([xw, depth - wall - 0.01, 0]) cube([0.01, 0.01, 10 + rear_boss]); translate([x0, depth - wall - 0.01, 0]) cube([rear_boss, 0.01, 10 + rear_boss]); translate([xw, depth - wall - rear_boss, 0]) cube([0.01, rear_boss, 10 + rear_boss]); } translate([p[0], p[1], -1]) cylinder(d = insert_d, h = insert_depth + 1); } } } // ---------- bezel ---------- display_holes = [for (hx = [disp_hole_inset, disp_w - disp_hole_inset]) for (hy = [disp_hole_inset, disp_h - disp_hole_inset]) [disp_x0 + hx, disp_raise + hy]]; module bezel() { difference() { intersection() { panel() union() { // plate translate([-groove + clr, -50, lip]) cube([inner_w + 2 * (groove - clr), 300, bezel_t]); // display bosses, from the bezel to the PCB front face for (h = display_holes) translate([h[0], h[1], n_bezel_in - 0.01]) cylinder(d = boss_d, h = n_pcb - n_bezel_in + 0.01); // feet that take the front base screws for (x = foot_xs) translate([x - 6, -10, n_bezel_in - 0.01]) cube([12, 22, 12]); } // flat bottom on the base, flat top against the top wall translate([-50, -50, 0]) cube([inner_w + 100, depth + 100, z_top_in - clr]); } // window, chamfered on the front panel() translate([inner_w / 2, win_s, 0]) { translate([0, 0, lip - 1]) linear_extrude(bezel_t + 2) rrect(win_w, win_h, win_r); hull() { translate([0, 0, lip - 0.01]) linear_extrude(0.01) rrect(win_w + 2 * win_chamfer, win_h + 2 * win_chamfer, win_r + win_chamfer); translate([0, 0, lip + win_chamfer]) linear_extrude(0.01) rrect(win_w, win_h, win_r); } } // inserts in the display bosses panel() for (h = display_holes) translate([h[0], h[1], n_pcb - insert_depth]) cylinder(d = insert_d, h = insert_depth + 1); // inserts in the feet (vertical) for (x = foot_xs) translate([x, foot_y, -1]) cylinder(d = insert_d, h = insert_depth + 1); } } // ---------- base ---------- module base() { difference() { union() { translate([-wall, 0, -base_t]) cube([outer_w, depth, base_t]); // locating rim inside the side and back walls (stops short of the bezel and bosses) rim_y0 = 20; for (x = [clr, inner_w - clr - 1.6]) translate([x, rim_y0, -0.01]) cube([1.6, depth - wall - rear_boss - clr - rim_y0, 2]); translate([rear_boss + clr, depth - wall - clr - 1.6, -0.01]) cube([inner_w - 2 * (rear_boss + clr), 1.6, 2]); // board standoffs for (h = board_holes) translate(board_xy(h)) cylinder(d = standoff_d, h = standoff_h); // cable-tie anchor for the MAX3232 / display cable translate([inner_w / 2, board_conn_y - board_l - 4.5, 0]) tie_anchor(); } // screws into the body / bezel inserts, counterbored from below for (p = base_screws) translate([p[0], p[1], -base_t - 1]) { cylinder(d = screw_d, h = base_t + 2); cylinder(d = head_d, h = head_depth + 1); } // board standoff inserts for (h = board_holes) translate(board_xy(h)) translate([0, 0, standoff_h - insert_depth + 0.5]) cylinder(d = insert_d, h = insert_depth); // vents under the display for (i = [0 : 2]) translate([inner_w / 2, 20 + i * 7, -base_t / 2]) linear_extrude(base_t + 2, center = true) rrect(inner_w - 70, 3, 1.4); } } function board_xy(h) = [board_x0 + h[1], board_conn_y - h[0], 0]; // One foot, top face at z = 0 (against the base's underside), centred on a screw. module foot() { difference() { union() { translate([0, 0, -foot_h]) cylinder(d = foot_d, h = foot_h); translate([0, 0, -0.01]) cylinder(d = foot_spigot_d, h = foot_spigot_h + 0.01); } translate([0, 0, -foot_h - 1]) cylinder(d = screw_d, h = foot_h + foot_spigot_h + 2); translate([0, 0, -foot_h - 1]) cylinder(d = foot_head_d, h = foot_head_depth + 1); // soften the bottom edge translate([0, 0, -foot_h - 0.01]) difference() { cylinder(d = foot_d + 1, h = 1); cylinder(d1 = foot_d - 2, d2 = foot_d + 0.01, h = 1.01); } } } module feet_in_place() { for (p = base_screws) translate([p[0], p[1], -base_t]) foot(); } module tie_anchor() { difference() { translate([-5, -3, 0]) cube([10, 6, 5]); translate([-2.5, -4, -0.01]) cube([5, 8, 3.2]); } } // ---------- dummies for the preview and the collision checks ---------- module display_dummy() { panel() { translate([disp_x0 + gl_x, disp_raise + gl_y, n_glass + 0.01]) cube([glass_w, glass_h, glass_depth - 0.02]); translate([disp_x0, disp_raise, n_pcb + 0.01]) difference() { cube([disp_w, disp_h, pcb_t + disp_back]); for (hx = [disp_hole_inset, disp_w - disp_hole_inset]) for (hy = [disp_hole_inset, disp_h - disp_hole_inset]) translate([hx, hy, -1]) cylinder(d = 3.5, h = pcb_t + 2); } } } module board_dummy() { translate([board_x0, board_conn_y - board_l, standoff_h + 0.01]) difference() { union() { cube([board_w, board_l, pcb_t]); translate([2, 2, pcb_t]) cube([board_w - 4, board_l - 4, board_comp_h]); // DC jack, sticking out 1 mm past the board edge translate([jack_pos - 4.5, board_l - 14, pcb_t]) cube([9, 15, 11]); } for (h = board_holes) translate([h[1], board_l - h[0], -1]) cylinder(d = 3.2, h = pcb_t + 2); } } // ---------- output ---------- if (part == "assembly") { color("SlateGray", 0.5) body(); color("DimGray") bezel(); color("Gainsboro") base(); color("Black") feet_in_place(); color("SeaGreen") display_dummy(); color("SteelBlue") board_dummy(); } else if (part == "section") { // assembly cut at x = section_x, for checking the inside intersection() { union() { color("SlateGray") body(); color("DimGray") bezel(); color("Gainsboro") base(); color("SeaGreen") display_dummy(); color("SteelBlue") board_dummy(); } translate([section_x, -50, -50]) cube([500, 500, 500]); } } else if (part == "body") { translate([0, depth, height]) rotate([180, 0, 0]) body(); // top on the bed } else if (part == "bezel") { translate([0, 0, -lip]) multmatrix(M) bezel(); // front face on the bed } else if (part == "feet") { for (i = [0 : len(base_screws) - 1]) // 4 feet, flat side down translate([i * (foot_d + 5), 0, foot_h]) foot(); } else if (part == "base") { translate([0, 0, base_t]) base(); } // Collision checks: each must render as an empty object. else if (part == "check_body_bezel") intersection() { body(); bezel(); } else if (part == "check_body_display") intersection() { body(); display_dummy(); } else if (part == "check_bezel_display") intersection() { bezel(); display_dummy(); } else if (part == "check_body_base") intersection() { body(); base(); } else if (part == "check_bezel_base") intersection() { bezel(); base(); } else if (part == "check_board") intersection() { union() { body(); bezel(); base(); display_dummy(); } board_dummy(); } else if (part == "check_display_base") intersection() { base(); display_dummy(); } else if (part == "check_feet_base") intersection() { base(); feet_in_place(); } // The bezel slides in from below along the panel: sweep it (with the display) down and check. else if (part == "check_insertion") intersection() { body(); for (k = [5 : 5 : 80]) translate(-k * [0, sin(tilt), cos(tilt)]) { bezel(); display_dummy(); } }