Something went wrong. Try again.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296// Streamplace Cube — three-piece parametric enclosure shell (v2 prototype)//// Three pieces, designed for three filament colors:// 1. Bottom shell — floor + 3 walls (left, right, back) + front channel ribs;// back wall carries the USB-C power port.// 2. Top shell — ceiling + 4 walls; snaps onto the bottom shell on three sides// and locks the front panel in place by capping its top edge.// 3. Front panel — flat slab that slides down into the bottom shell's grooves// and carries the user-facing ports (HDMI in/out, USB-A, USB-C data).//// Render via the included Dockerfile / Makefile:// make stl -> out/cube_bottom.stl + out/cube_top.stl + out/cube_front.stl// make preview -> out/preview.png (exploded view, uses host openscad)
part = "preview"; // "bottom" | "top" | "front" | "preview"
// ---- main dimensions ----W = 115; // width (X)D = 115; // depth (Y) — front face is -Y, back face is +YH = 115; // height (Z)wall = 2.0;
// Lid (top shell) height — half of v2's 45 mm. Bottom shell grows to fill the// rest of H. The front panel sits between two equal frames of bottom-shell// color: `lid_h` of frame above (= top shell) and `lid_h` of frame below// (= bottom-shell front-wall section under the panel).lid_h = 22.5;split_z = H - lid_h; // bottom shell height (= 92.5)
// Corner-rounding radius for the cube's four vertical edges. Outer envelope// and interior cavity both get the same radius so wall thickness stays uniform.corner_r = 4.0;
// ---- snap lip (3-sided: left, right, back; no front) ----lip_h = 6.0;lip_thick = 1.6;lip_clear = 0.3;lip_inset = wall + lip_clear;snap_d = 1.6;snap_z = 3.5;dimple_d = 2.2;
// ---- seam ledge (3-sided) ----ledge_h = 1.5;
// ---- front panel parameters ----panel_thick = 2.5; // panel Y thicknesspanel_recess = 1.0; // panel front face recessed from cube front face by thisslide_clear = 0.3; // sliding-fit clearance (per side)front_frame_h = lid_h; // bottom-shell front-wall height under the panel
// ---- front channel rib parameters ----rib_x_extra = 3.0; // rib extends inward from inner wall face by this in Xrib_y = panel_recess + panel_thick + slide_clear/2 + 1.5; // rib Y depthslot_x_depth = 1.5; // slot X depth (cut into rib from interior face inward)
// ---- ports ----port_clear = 0.5;
// back wall: just powerback_ports = [ ["usb_c_power", 9.0, 3.5, (W - 9.0 - port_clear) / 2, // x left — centered on back wall 18], // z above floor];
// front panel: data ports (panel-local coords; x=0 is panel left edge, z=0 is bottom).// Vertically centered: each port's z is `panel_h/2 - port_h/2`, with// panel_h derived below.function port_z(h) = (split_z - front_frame_h)/2 - h/2;panel_ports = [ ["hdmi_in", 14.0, 6.5, 20, port_z(6.5)], ["hdmi_out", 14.0, 6.5, 39, port_z(6.5)], ["usb_a", 13.0, 6.0, 58, port_z(6.0)], ["usb_c", 9.0, 3.5, 76, port_z(3.5)],];
// ---- preview / explode ----preview_gap_z = 14; // top shell raised this much above the seampreview_gap_y = 18; // front panel pulled this far forward in -Y
$fn = 56;
// ============================================================// derived dimensions// ============================================================
// Slot in the LEFT rib (absolute coords)slot_x_min_L = wall + rib_x_extra - slot_x_depth;slot_x_max_L = wall + rib_x_extra + 0.1; // breaks through inner faceslot_y_min = panel_recess - slide_clear/2;slot_y_max = panel_recess + panel_thick + slide_clear/2;slot_z_min = front_frame_h; // slot bottom = panel bottomslot_z_max = split_z + 1; // extends above rib top
// Panel X extent — left edge at -X end of left slot (with clearance), mirroredpanel_x_min = slot_x_min_L + slide_clear;panel_x_max = W - slot_x_min_L - slide_clear;
// Panel Y / Z extentspanel_y_min = panel_recess;panel_y_max = panel_recess + panel_thick;panel_z_min = front_frame_h; // panel sits on top of the bottom-shell front wallpanel_z_max = split_z; // top of panel meets the top shell exactly
// Snap positions (3 sides, no front)bump_positions = [ [W/2, D - lip_inset, split_z + snap_z], // back (+Y) [lip_inset, D/2, split_z + snap_z], // left (-X) [W - lip_inset, D/2, split_z + snap_z], // right (+X)];dimple_positions = [ [W/2, D - wall, split_z + snap_z], [wall, D/2, split_z + snap_z], [W - wall, D/2, split_z + snap_z],];
// ============================================================// helpers// ============================================================
module hollow_box(z0, z1, t = wall) { difference() { translate([0, 0, z0]) cube([W, D, z1 - z0]); translate([t, t, z0 - 0.01]) cube([W - 2*t, D - 2*t, (z1 - z0) + 0.02]); }}
// 2D rounded rectangle, W × D footprint, vertical edges rounded with `corner_r`module rounded_outer_2d() { offset(r = corner_r) offset(r = -corner_r) square([W, D]);}
// 2D footprint of the interior cavity — same rounding so wall thickness stays// `wall` everywhere (including the diagonal at each corner).module rounded_inner_2d() { offset(r = corner_r) offset(r = -corner_r) translate([wall, wall]) square([W - 2*wall, D - 2*wall]);}
// 3-sided seam ledge — full 4-sided ring with the front strip cut outmodule seam_ledge_3sided() { difference() { difference() { translate([0, 0, split_z - ledge_h]) cube([W, D, ledge_h]); translate([lip_inset + lip_thick, lip_inset + lip_thick, split_z - ledge_h - 0.01]) cube([W - 2*(lip_inset + lip_thick), D - 2*(lip_inset + lip_thick), ledge_h + 0.02]); } translate([wall + 0.01, -0.5, split_z - ledge_h - 0.5]) cube([W - 2*wall - 0.02, lip_inset + lip_thick + 0.5, ledge_h + 1]); }}
// 3-sided lip ring (no front)module lip_ring_3sided() { difference() { difference() { translate([lip_inset, lip_inset, split_z]) cube([W - 2*lip_inset, D - 2*lip_inset, lip_h]); translate([lip_inset + lip_thick, lip_inset + lip_thick, split_z - 0.01]) cube([W - 2*(lip_inset + lip_thick), D - 2*(lip_inset + lip_thick), lip_h + 0.02]); } translate([wall, -0.5, split_z - 0.01]) cube([W - 2*wall, lip_inset + lip_thick + 0.5, lip_h + 0.02]); }}
// Front-left channel rib: block at the inner front-left corner with a vertical// slot cut through its inner face. Panel's left edge slides down into the slot.// Rib extends the full height of the bottom-shell side walls; the slot only// exists above the front-wall section (z >= front_frame_h).module left_front_rib() { difference() { translate([wall, 0, wall]) cube([rib_x_extra, rib_y, split_z - wall]); translate([slot_x_min_L, slot_y_min, slot_z_min]) cube([slot_x_max_L - slot_x_min_L, slot_y_max - slot_y_min, slot_z_max - slot_z_min + 0.01]); }}
module right_front_rib() { translate([W, 0, 0]) mirror([1, 0, 0]) left_front_rib();}
module back_port_cuts() { for (p = back_ports) { pw = p[1] + port_clear; ph = p[2] + port_clear; translate([p[3], D - wall - 0.5, p[4]]) cube([pw, wall + 1, ph]); }}
// ============================================================// shells// ============================================================
module bottom_shell() { union() { // Solid outer envelope (z=[0, split_z]) with rounded vertical edges, // minus the interior cavity, the front opening above the frame wall, // and the back-wall power-port cutout. difference() { linear_extrude(split_z) rounded_outer_2d(); // Hollow interior (above floor) translate([0, 0, wall]) linear_extrude(split_z - wall + 0.01) rounded_inner_2d(); // Front opening — only above the frame wall (z >= front_frame_h) translate([wall, -0.5, front_frame_h]) cube([W - 2*wall, wall + 1, split_z - front_frame_h + 0.01]); back_port_cuts(); }
// Interior features (seam ledge / lip / ribs) — clipped to the rounded // envelope so they don't square off the corners where they would otherwise // protrude past the outer face. intersection() { union() { seam_ledge_3sided(); lip_ring_3sided(); left_front_rib(); right_front_rib(); } linear_extrude(split_z + lip_h + 1) rounded_outer_2d(); }
// Snap bumps (positioned safely inside the envelope, no clip needed) for (p = bump_positions) translate(p) sphere(d = snap_d); }}
module top_shell() { difference() { // Rounded outer envelope from z=split_z to z=H translate([0, 0, split_z]) linear_extrude(H - split_z) rounded_outer_2d(); // Hollow interior, stopping `wall` below H to leave a ceiling translate([0, 0, split_z]) linear_extrude(H - split_z - wall + 0.01) rounded_inner_2d(); // Snap dimples (recessed into the inner wall surface) for (p = dimple_positions) translate(p) sphere(d = dimple_d); }}
module front_panel() { difference() { translate([panel_x_min, panel_y_min, panel_z_min]) cube([panel_x_max - panel_x_min, panel_y_max - panel_y_min, panel_z_max - panel_z_min]); for (p = panel_ports) { pw = p[1] + port_clear; ph = p[2] + port_clear; translate([panel_x_min + p[3], panel_y_min - 0.5, panel_z_min + p[4]]) cube([pw, panel_y_max - panel_y_min + 1, ph]); } }}
// ============================================================// dispatch// ============================================================
if (part == "bottom") { bottom_shell();} else if (part == "top") { top_shell();} else if (part == "front") { front_panel();} else { // preview — explode along +Z (top) and -Y (front panel) color("hotpink") bottom_shell(); color("ghostwhite") translate([0, 0, preview_gap_z]) top_shell(); color("dimgray") translate([0, -preview_gap_y, 0]) front_panel();}