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:
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();
+ }
+}