jbod

jbod.git
git clone git://git.lenczewski.org/jbod.git
Log | Files | Refs | README

commit e74ab2fd2102c83a3a4a1fabe5c5cbe1b6effe3d
parent c0784d9da6780be5b9b33208c5647669b46146df
Author: MikoĊ‚aj Lenczewski <mikolaj@lenczewski.org>
Date:   Thu,  9 Jul 2026 19:58:44 +0100

Add enclosure parts, and add visualisation helpers

Diffstat:
MREADME | 6+++---
Aopenscad/constants.scad | 178+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mopenscad/enclosure.scad | 175++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Aopenscad/faceplate.scad | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aopenscad/fasteners.scad | 2++
Aopenscad/rearplate.scad | 5+++++
Aopenscad/separator.scad | 8++++++++
Aopenscad/shell.scad | 35+++++++++++++++++++++++++++++++++++
Aopenscad/viz.scad | 92+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
9 files changed, 485 insertions(+), 86 deletions(-)

diff --git a/README b/README @@ -43,9 +43,9 @@ Some notes, in no particular order: + Noctua 80mm chomax swap pwm fans (w/h/l 80/80/25 mm) - https://www.noctua.at/en/products/nf-a8-pwm-chromax-black-swap#hero -- The chassis has dimensions w/h/l 480/177/800 mm - + Front panel dimensions (w/h/l x/y/z mm) - + Enclosure dimensions (w/h/l x/y/z mm) +- The chassis has dimensions w/h/l 482/177/800 mm + + Front panel dimensions (w/h/d 482/177/2.5 mm) + + Enclosure dimensions (w/h/l 434/177/800 mm) - The chassis supports redundant 1+1 CRPS 1000W+ PSUs + Open Compute M-CRPS form factor standard PSUs (w/h/l 75/40/185+11 mm) diff --git a/openscad/constants.scad b/openscad/constants.scad @@ -0,0 +1,178 @@ +// see enclosure.scad for more details on the constants + +enclosure_rack_units = 4; + +rack_unit_dims = [ + [undef, undef, undef, undef], // "0U" + [44.45, 43.65, 31.75, 5.95], // 1U + [88.90, 88.10, 44.45, 21,83], // 2U + [133.35, 132.55, 57.15, 37.70], // 3U + [177.80, 177.00, 101.60, 37.70], // 4U + [222.25, 221.45, 146.06, 37.70], // 5U + [266.70, 265.90, 190.50, 37.70], // 6U +]; + +// enclosure parameters +// =========================================================================== + +disk_blocks = 2; +fan_groups = 3; + +// fastener constants +// =========================================================================== +// + +flush_stud_height = 0; +flush_stud_shoulder_height = 0; +flush_stud_inner_radius = 0; +flush_stud_outer_radius = 0; + +flush_nut_height = 0; +flush_nut_shoulder_height = 0; +flush_nut_inner_radius = 0; +flush_nut_outer_radius = 0; + +standoff_height = 0; +standoff_inner_radius = 0; +standoff_outer_radius = 0; + +// chassis constants +// =========================================================================== +// + +faceplate_width = 482; +faceplate_height = rack_unit_dims[enclosure_rack_units][0]; +faceplate_thickness = 2; + +handle_width = 0; +handle_height = 0; +handle_thickness = 0; + +chassis_width = 434; +chassis_height = rack_unit_dims[enclosure_rack_units][0]; +chassis_depth = 800; +chassis_thickness = 1.5; + +cover_width = chassis_width; +cover_depth = chassis_depth; +cover_thickness = 1.5; + +rearplate_width = chassis_width; +rearplate_height = rack_unit_dims[enclosure_rack_units][0]; +rearplate_thickness = chassis_thickness; + +// chamber separator +separator_height = chassis_height; +separator_depth = chassis_depth; +separator_thickness = 1.5; + +// disk retainer +retainer_width = chassis_width; +retainer_depth = chassis_depth; +retainer_thickness = 1.5; + +// disk (-group/-row/-block) constants +// =========================================================================== +// - disks will be placed in caddies +// - disks are treated logically in groups, with one backplane per group +// - groups are further combined into rows, and rows into blocks + +//disk_width = 26.11; +//disk_height = 146.05; +//disk_depth = 101.60; + +disk_width = 27; // with caddy +disk_height = 157; // with caddy +disk_depth = 112; // with caddy + +disk_conn_width = 6; +disk_conn_height = 4; +disk_conn_depth = 42.37; + +disk_conn_off_width = 0; +disk_conn_off_height = -1; +disk_conn_off_depth = 4.665; + +disk_pad_width = 1; +disk_pad_height = 1; +disk_pad_depth = 2.5; + +disks_per_group = 4; +disk_group_pad_width = 0; +disk_group_pad_depth = 0; + +function disk_group_width(disks = disks_per_group) = + (2 * disk_group_pad_width) + (disks * (disk_width + (2 * disk_pad_width))); +function disk_group_depth(disks = disks_per_group) = + (2 * disk_group_pad_depth) + (disk_depth + (2 * disk_pad_depth)); + +disk_groups_per_row = 3; +disk_row_pad_width = 0; +disk_row_pad_depth = 0; + +function disk_row_width(groups = disk_groups_per_row, disks = disks_per_group) = + (2 * disk_row_pad_width) + (groups * disk_group_width(disks)); +function disk_row_depth(groups = disk_groups_per_row, disks = disks_per_group) = + (2 * disk_row_pad_depth) + disk_group_depth(disks); + +disk_rows_per_block = 3; +disk_block_pad_width = 0; +disk_block_pad_depth = 0; + +function disk_block_width(rows = disk_rows_per_block, + groups = disk_groups_per_row, + disks = disks_per_group) = + (2 * disk_block_pad_width) + (rows * disk_row_width(groups, disks)); + +function disk_block_depth(rows = disk_rows_per_block, + groups = disk_groups_per_row, + disks = disks_per_group) = + (2 * disk_block_pad_width) + (rows * disk_row_depth(groups, disks)); + +// miscellaneous constants +// =========================================================================== +// + +fan_width = 141; +fan_height = 141; +fan_depth = 30; + +fan_pad_width = 1.0; +fan_pad_height = 20; // account for pcb + cable routing area +fan_pad_depth = 1.0; + +fan_group_pad_width = 0; +fan_group_pad_height = 0; +fan_group_pad_depth = 0; + +function fan_group_width(fans) = + (2 * fan_group_pad_width) + (fans * (fan_width + (2 * fan_pad_width))); +function fan_group_height() = + (2 * fan_group_pad_height) + (fan_height + (2 * fan_pad_height)); +function fan_group_depth() = + (2 * fan_group_pad_depth) + (fan_depth + (2 * fan_pad_depth)); + +fan_wall_thickness = 1.5; +fan_wall_width = chassis_width; +fan_wall_depth = fan_wall_thickness + fan_group_depth(); + +psu_width = 73.5; +psu_height = 40; +psu_depth = 185; + +psu_pad_width = 1.0; +psu_pad_height = 1.0; +psu_pad_depth = 0.0; + +psus_per_group = 2; +psu_group_width = psu_width; +function psu_group_height(psus = psus_per_group) = + psus * (psu_height + (2 * psu_pad_height)); + +io_width = 80; +io_height = 40; +io_depth = 80; + +io_pad_width = 1.0; +io_pad_height = 1.0; +io_pad_depth = 0.0; diff --git a/openscad/enclosure.scad b/openscad/enclosure.scad @@ -1,6 +1,7 @@ // 19-inch rack chassis details // =========================================================================== -// width: 482.6mm +/- 0.4mm +// faceplate width: 482.6mm +/- 0.4mm +// chassis width: 430mm +/- 1mm // // heights: +/- 0.4mm // units | height (mm) | h1 (mm) | h2 (mm) | h3 (mm) @@ -36,115 +37,123 @@ // - dust filter // - rack handles // - castellated faceplate 4U mounting holes +// rearplate: +// - two-chamber design +// chamber separator wall: +// - w/d (mm): 483/800 // body (bottom + sides + rear): // - w/h/d (mm): 483/178/800 // - external rack rails -// - 3x3 140mm fans, w/h/d (mm): 150/150/30 -// - 6x12 3.5" disk slots, w/h/d (mm): 105/30/150 -// spacer: +// - 1x3 + 2x2 140mm fans, w/h/d (mm): 141/141/30 +// - 6x12 3.5" disk caddy slots, w/h/d (mm): 112/27/157 +// disk retainer wall: // - w/d (mm): 483/800 // lid: // - tool-free (ptfe rollers and groove) +// +// fastners: +// - m5 flush stud +// - m5 flush nut $fn = 64; -width = 482.6; -depth = 800; - -unit_dims = [ - [undef, undef, undef, undef], // "0U" - [44.45, 43.65, 31.75, 5.95], // 1U - [88.90, 88.10, 44.45, 21,83], // 2U - [133.35, 132.55, 57.15, 37.70], // 3U - [177.80, 177.00, 101.60, 37.70], // 4U - [222.25, 221.45, 146.06, 37.70], // 5U - [266.70, 265.90, 190.50, 37.70], // 6U +include <constants.scad> + +use <fasteners.scad> +use <faceplate.scad> +use <rearplate.scad> +use <separator.scad> +use <shell.scad> + +use <viz.scad> + +// =========================================================================== + +viz_disks = true; +viz_fans = true; +viz_psus = true; +viz_pcbs = true; +viz_io = true; + +// =========================================================================== + +cover_offset = [0, 0, chassis_height]; + +separator_offset = [ + disk_row_width(), + faceplate_thickness + fan_wall_depth, + 0, ]; -mounting_hole_dims = [ - [14.7, 6.8], - [13.5, 6.8, 10.3], +faceplate_offset = [ + -(faceplate_width - chassis_width) / 2, // center on shell + -(faceplate_thickness / 2), // account for faceplate thickness + 0, ]; -module logo() { - // TODO -} +rearplate_offset = [0, 0, 0]; -module front_panel_mounting_hole(castellated = true) { - d = 6.8; r = d / 2; +disk_offset = [ + chassis_thickness, + chassis_thickness + fan_wall_depth, + chassis_thickness / 2 + disk_pad_height + 20, +]; - if (castellated) { - w = 14.7; translate([0,-r,0]) %square([w,d]); // template +fan_offset = [ + chassis_thickness, + chassis_thickness + fan_wall_thickness, + chassis_thickness / 2 + fan_pad_height, +]; - r0 = w - r; +psu_offset = [ + chassis_width - chassis_thickness - psu_width, + chassis_depth - chassis_thickness - psu_depth, + chassis_thickness / 2 + psu_pad_height, +]; - union() { - translate([0,-r,0]) square([w-r, d]); - translate([r0,0,0]) circle(r); - } - } else { - w = 13.5; translate([0,-r,0]) %square([w,d]); // template +io_offset = [ + chassis_width - chassis_thickness - io_width, + chassis_depth - chassis_thickness - io_depth, + chassis_thickness / 2 + psu_group_height(), +]; - o = 13.5 - 10.3; // initial offset from side - r0 = r; // 1st hole centre - r1 = 10.3 - r; // 2nd hole centre +module viz_guts(disks = true, fans = true, psus = true, io = true, pcbs = true) { +} - union() { - translate([o,0,0]) { - translate([r0,0,0]) circle(r); - translate([r0,-r,0]) square([r, d]); - translate([r1,0,0]) circle(r); - }; - } +shell(); + +//translate(cover_offset) cover(); +translate(faceplate_offset) rotate([90,0,0]) faceplate(); +translate(rearplate_offset) rotate([90,0,0]) rearplate(); + +if (viz_disks) { + translate(disk_offset) + for (i = [0:disk_blocks-1]) { + translate([0, i * (fan_wall_depth + disk_block_depth()), 0]) + disk_block(); + disk_separator(); } } -module front_panel_mesh(porosity = 0.85) { - // TODO +if (viz_fans) { + translate(fan_offset) + for (i = [0:fan_groups-1]) { + translate([0, i * (fan_wall_depth + disk_block_depth()), 0]) + if (i == 0) + fan_group(fans=3); + else + fan_group(fans=2); + } } -module front_panel(units = 4, mounting_holes = true, castellated = false, mesh = true, porosity = 0.85, thickness = 1.5) { - h0 = unit_dims[units][0]; // unit height (including padding) - h1 = unit_dims[units][1]; // front panel total height - h2 = unit_dims[units][2]; // distance between mounting hole centres - h3 = unit_dims[units][3]; // distance of mounting hole from panel bottom - - linear_extrude(height=thickness, center=true, convexity=10) difference() { - square([width, h1]); // blank front panel - - if (mounting_holes) { - translate([0,h3,0]) - front_panel_mounting_hole(castellated=castellated); - translate([0,h3+h2,0]) - front_panel_mounting_hole(castellated=castellated); - - translate([width,h3,0]) scale([-1,1,1]) - front_panel_mounting_hole(castellated=castellated); - translate([width,h3+h2,0]) scale([-1,1,1]) - front_panel_mounting_hole(castellated=castellated); - } - - if (mesh) { - front_panel_mesh(porosity=porosity); - } - - logo(); // embossed logo - }; +if (viz_psus) { + translate(psu_offset) psu_group(); } -module body() { - linear_extrude(height=1, center=true, convexity=10) square(10); -} +if (viz_pcbs) { -module spacer() { - linear_extrude(height=1, center=true, convexity=10) square(10); } -module lid() { - linear_extrude(height=1, center=true, convexity=10) square(10); +if (viz_io) { + translate(io_offset) expander_io_group(); } - -front_panel(); -// body(); -// spacer(); -// lid(); diff --git a/openscad/faceplate.scad b/openscad/faceplate.scad @@ -0,0 +1,70 @@ +include <constants.scad> + +width = faceplate_width; +units = enclosure_rack_units; + +module logo() { + // TODO: embossed text? +} + +module faceplate_mesh(porosity = 0.85) { + // TODO: circle cutouts? shaped cutouts? simple hole with mesh? +} + +module faceplate_mounting_hole(castellated = true) { + d = 6.8; r = d / 2; + + if (castellated) { + w = 14.7; translate([0,-r,0]) %square([w,d]); // template + + r0 = w - r; + + union() { + translate([0,-r,0]) square([w-r, d]); + translate([r0,0,0]) circle(r); + } + } else { + w = 13.5; translate([0,-r,0]) %square([w,d]); // template + + o = 13.5 - 10.3; // initial offset from side + r0 = r; // 1st hole centre + r1 = 10.3 - r; // 2nd hole centre + + union() { + translate([o,0,0]) { + translate([r0,0,0]) circle(r); + translate([r0,-r,0]) square([r, d]); + translate([r1,0,0]) circle(r); + }; + } + } +} + +module faceplate(mounting_holes = true, castellated = false, mesh = true, porosity = 0.85) { + h0 = rack_unit_dims[units][0]; // unit height (including padding) + h1 = rack_unit_dims[units][1]; // front panel total height + h2 = rack_unit_dims[units][2]; // distance between mounting hole centres + h3 = rack_unit_dims[units][3]; // distance of mounting hole from panel bottom + + linear_extrude(height=faceplate_thickness, center=true) difference() { + square([width, h1]); // blank front panel + + if (mounting_holes) { + translate([0,h3,0]) + faceplate_mounting_hole(castellated=castellated); + translate([0,h3+h2,0]) + faceplate_mounting_hole(castellated=castellated); + + translate([width,h3,0]) scale([-1,1,1]) + faceplate_mounting_hole(castellated=castellated); + translate([width,h3+h2,0]) scale([-1,1,1]) + faceplate_mounting_hole(castellated=castellated); + } + + if (mesh) { + faceplate_mesh(porosity=porosity); + } + + logo(); // embossed logo + }; +} diff --git a/openscad/fasteners.scad b/openscad/fasteners.scad @@ -0,0 +1,2 @@ +include <constants.scad> + diff --git a/openscad/rearplate.scad b/openscad/rearplate.scad @@ -0,0 +1,5 @@ +include <constants.scad> + +module rearplate() { + +} diff --git a/openscad/separator.scad b/openscad/separator.scad @@ -0,0 +1,8 @@ +include <constants.scad> + +module disk_separator() { + union() { + cube([separator_thickness, separator_depth, separator_height]); + } + +} diff --git a/openscad/shell.scad b/openscad/shell.scad @@ -0,0 +1,35 @@ +include <constants.scad> + +width = chassis_width; +height = chassis_height; +depth = chassis_depth; + +module shell_wall_cover_roller_slots() { + d = 10.0; r = d / 2; + //translate([0,0,0]) circle(r); +} + +module shell_wall_rack_rail_slots() { + d = 10.0; r = d / 2; + //translate([0,0,0]) circle(r); +} + +module shell_wall() { + linear_extrude(height=chassis_thickness) difference() { + square([height, depth]); + shell_wall_rack_rail_slots(); + shell_wall_cover_roller_slots(); + } +} + +module shell() { + rotate([0,-90,0]) shell_wall(); + + linear_extrude(height=chassis_thickness) square([width, depth]); + + translate([width,0,0]) rotate([0,-90,0]) shell_wall(); +} + +module cover() { + linear_extrude(height=chassis_thickness) square([width, depth]); +} diff --git a/openscad/viz.scad b/openscad/viz.scad @@ -0,0 +1,92 @@ +include <constants.scad> + +module disk() { + disk_size = [ + disk_width, disk_depth, disk_height, + ]; + + conn_size = [ + disk_conn_width, disk_conn_depth, disk_conn_height, + ]; + + conn_off = [ + disk_conn_off_width, disk_conn_off_depth, disk_conn_off_height, + ]; + + color("red", 1.0) + difference() { + cube(disk_size); + translate(conn_off - [1, 0, 0]) cube(conn_size + [2, 0, 0]); + } + + translate(conn_off) color("black", 1.0) cube(conn_size); +} + +module disk_group(disks = disks_per_group) { + disk_offset = [disk_width + (2 * disk_pad_width), 0, 0]; + disk_pad = [disk_pad_width, 0, 0]; + + for (i = [0:disks - 1]) { + translate((i * disk_offset) + disk_pad) disk(); + } +} + +module disk_row(groups = disk_groups_per_row, disks = disks_per_group) { + group_offset = [disk_group_width(disks), 0, 0]; + group_pad = [disk_group_pad_width, disk_group_pad_depth, 0]; + + for (i = [0:groups-1]) { + translate((i * group_offset) + group_pad) disk_group(disks); + } +} + +module disk_block(rows = disk_rows_per_block, + groups = disk_groups_per_row, + disks = disks_per_group) +{ + row_offset = [0, disk_depth + (2 * disk_pad_depth), 0]; + row_pad = [0, disk_pad_depth, 0]; + + for (i = [0:rows - 1]) { + translate((i * row_offset) + row_pad) disk_row(groups, disks); + } +} + +module fan() { + color("grey", 1.0) cube([fan_width, fan_depth, fan_height]); +} + +module fan_group(fans = 3) { + fan_offset = [fan_width + (2 * fan_pad_width), 0, 0]; + fan_pad = [fan_pad_width, 0, 0]; + + for (i = [0:fans - 1]) { + translate((i * fan_offset) + fan_pad) fan(); + } +} + +module psu() { + color("blue", 1.0) cube([psu_width, psu_depth, psu_height]); +} + +module psu_group(psus = 2) { + psu_offset = [0, 0, psu_height + (2 * psu_pad_height)]; + psu_pad = [0, 0, psu_pad_height]; + + for (i = [0:psus - 1]) { + translate((i * psu_offset) + psu_pad) psu(); + } +} + +module expander_io() { + color("green", 1.0) cube([io_width, io_depth, io_height]); +} + +module expander_io_group(ios = 2) { + expander_io_offset = [0, 0, io_height + (2 * io_pad_height)]; + expander_io_pad = [0, 0, io_pad_height]; + + for (i = [0:ios - 1]) { + translate((i * expander_io_offset) + expander_io_pad) expander_io(); + } +}