Read this model as text
A stackable case for a Mini-ITX NAS board, sized so every part prints on a 180 mm bed (Bambu A1 mini). The stack is a base module that carries the board, one identical drive module per flat 3.5" disk, and a lid with a fan grille that pulls air up through the whole stack. Modules locate on each other with a lip and pocket and bolt together with M3 screws into captive nuts, so adding a disk later is printing one more drive module and dropping it in.
The image shows the solid OpenSCAD renders at part assembly · hdd_modules 2 · explode 0 · show_hardware true · board 170 · board_clear 0.5 · standoff_h 6.5 · standoff_hole 2.6 · hole_rear_a 6.35 · hole_rear_b 163.83 · hole_side_a 10.16 · hole_side_b 165.1 · board_h 60 · button_d 16 · button_x 30 · button_z 36 · jack_d 12 · jack_side right · jack_y 140 · jack_z 30 · vents true · vent_w 2 · vent_pitch 6 · hdd_w 101.6 · hdd_l 147 · hdd_h 26.1 · hdd_rail_h 2 · hdd_gap 3 · hdd_cable 22 · hdd_clear 0.4 · fan_size 80 · fan_t 25 · io_x 0 · io_w 170 · io_h 20 · wall 3 · floor_t 2.5 · top_t 2.5 · corner_r 2 · edge_ch 0.8 · lip_h 4 · lip_t 1.5 · lip_c 0.2 · nut_af 5.7 · color_case Silver.
Stackable Mini-ITX NAS case
mick/itx-nas-stack@1.1.0
Report
For a legal or policy problem: a license violation, personal data, somebody else’s work republished as their own. A model that prints badly is a comment, not a report.
Parameters
from sourceDescription
What is this
A stackable case for a Mini-ITX NAS board, sized so every part prints on a 180 mm bed (Bambu A1 mini). The stack is a base module that carries the board, one identical drive module per flat 3.5" disk, and a lid with a fan grille that pulls air up through the whole stack. Modules locate on each other with a lip and pocket and bolt together with M3 screws into captive nuts, so adding a disk later is printing one more drive module and dropping it in.
What you use it for
A slim, clean NAS around a 170 × 170 board with a pico PSU: DC barrel jack in a side wall, a 16 mm vandal switch in the front, the rear I/O open above the board with a flat rear shield that slides down into grooves. Each drive module takes one 3.5" disk lying on two rails, connector to the rear, no screws: four corner guides keep it in place, the rails lift it off the floor and the floor is slotted so air passes under and over it on its way to the lid. A rear cutout in every floor passes the SATA cables down to the board.
Design notes
The footprint is board + clearance + two walls, 177 mm by default, which is why the walls are three millimetres and the case is not wider. Every module has a pocket ring under its floor and a lip ring on top; the lip is the inner half of the wall so the outside stays flush, and a small chamfer on the top edge draws a seam line at every joint. The floor sits on a rib grid so the pocket is a two millimetre bridge and nothing else is unsupported. Four bosses hang in the top corners of every module, each with a hex pocket for an M3 nut; the module above bolts down through its floor corners with M3 × 12 to 16, the lid through full-height columns with M3 × 35, everything from the inside. The bosses taper at 45° so they print without support and sit above the ports in the rear corners. The board module has no rear wall above the board, because the ports on these NAS boards run edge to edge; its rear lip is carried by the rear shield instead, and stops in every pocket block the stack from sliding rearward. The lid has a thicker wall so its pocket has an inner face without a floor; it prints upside down, the modules print upright, the shield prints flat.
Parameters
part picks the assembly or one printable part. hole_rear_a/b and hole_side_a/b are the four standoff positions measured from the rear and left edges of the board; the defaults are the Mini-ITX spec, check them on your board. board_h sets how tall the board module is. button_d/x/z and jack_d/side/y/z place the switch and DC jack. hdd_* size the drive pocket, hdd_rail_h lifts the disk, hdd_cable is the room behind it for the plugs. fan_size picks the lid grille (0 for a plain lid). io_x/io_w/io_h cut one opening in the rear shield. wall, lip_h, lip_t, lip_c and edge_ch tune the fit and the seam between modules; nut_af is the nut size across flats.
Use with your agent
{
"mcpServers": {
"remixcad": {
"type": "http",
"url": "https://remix-cad.vercel.app/api/mcp"
}
}
}get_model("mick/itx-nas-stack@1.1.0")Returns the manifest, the source, the import closure and the license obligations. Full MCP setup.
Parameter table
Parsed from the Customizer annotations in main.scad| Name | Default | Range | Unit | Description |
|---|---|---|---|---|
| part | "assembly" | assembly, board_module, hdd_module, lid, rear_shield | ||
| hdd_modules | 2 | 0 to 4 step 1 | ||
| explode | 0 | 0 to 40 step 5 | ||
| show_hardware | true | |||
| board | 170 | 120 to 172 step 1 | ||
| board_clear | 0.5 | 0.3 to 1.5 step 0.1 | ||
| standoff_h | 6.5 | 4 to 10 step 0.5 | ||
| standoff_hole | 2.6 | 2.4 to 4.5 step 0.1 | ||
| hole_rear_a | 6.35 | 2 to 40 step 0.01 | ||
| hole_rear_b | 163.83 | 130 to 168 step 0.01 | ||
| hole_side_a | 10.16 | 2 to 40 step 0.01 | ||
| hole_side_b | 165.1 | 130 to 168 step 0.01 | ||
| board_h | 60 | 40 to 90 step 1 | ||
| button_d | 16 | 0 to 22 step 0.5 | ||
| button_x | 30 | 10 to 160 step 1 | ||
| button_z | 36 | 15 to 80 step 1 | ||
| jack_d | 12 | 0 to 16 step 0.5 | ||
| jack_side | "right" | right, left | ||
| jack_y | 140 | 15 to 160 step 1 | ||
| jack_z | 30 | 12 to 80 step 1 | ||
| vents | true | |||
| vent_w | 2 | 1.5 to 4 step 0.5 | ||
| vent_pitch | 6 | 4 to 12 step 0.5 | ||
| hdd_w | 101.6 | 69 to 102 step 0.1 | ||
| hdd_l | 147 | 100 to 150 step 0.1 | ||
| hdd_h | 26.1 | 7 to 30 step 0.1 | ||
| hdd_rail_h | 2 | 0 to 4 step 0.5 | ||
| hdd_gap | 3 | 2 to 10 step 0.5 | ||
| hdd_cable | 22 | 12 to 30 step 1 | ||
| hdd_clear | 0.4 | 0 to 1 step 0.1 | ||
| fan_size | 80 | 0, 40, 60, 80, 92, 120 | ||
| fan_t | 25 | 10 to 30 step 1 | ||
| io_x | 0 | 0 to 170 step 0.5 | ||
| io_w | 170 | 10 to 170 step 0.5 | ||
| io_h | 20 | 5 to 60 step 0.5 | ||
| wall | 3 | 2.5 to 4 step 0.1 | ||
| floor_t | 2.5 | 2 to 4 step 0.1 | ||
| top_t | 2.5 | 2 to 4 step 0.1 | ||
| corner_r | 2 | 0 to 3 step 0.5 | ||
| edge_ch | 0.8 | 0 to 1.5 step 0.1 | ||
| lip_h | 4 | 3 to 6 step 0.5 | ||
| lip_t | 1.5 | 1.2 to 2 step 0.1 | ||
| lip_c | 0.2 | 0.1 to 0.4 step 0.05 | ||
| nut_af | 5.7 | 5.4 to 6.2 step 0.1 | ||
| color_case | "Silver" | Silver, Gainsboro, White, DimGray, Black, SlateGray, FireBrick, SteelBlue, ForestGreen, Goldenrod |
Lineage
0 models depend on this oneOnly what this model came from and what came from it.
License
Whoever downloads this takes these obligations along- mick/itx-nas-stack@1.1.0 is MIT
Source
main.scad is the entrypointmain.scad
// Stackable Mini-ITX NAS case for a small printer (180 mm bed).
// A base module carries the board, each drive module carries one flat 3.5" disk,
// and a fan lid closes the stack and pulls air up through it. Modules print
// upright as one piece, the lid prints upside down, the rear shield flat.
// Modules bolt together with M3 screws into captive nuts in corner bosses.
/* [Parts] */
// The whole stack, or one part in print orientation.
part = "assembly"; // [assembly, board_module, hdd_module, lid, rear_shield]
// Drive modules shown in the assembly.
hdd_modules = 2; // [0:1:4]
// Pull the stack apart in the assembly view.
explode = 0; // [0:5:40]
// Show the board and the drives as ghosts in the assembly.
show_hardware = true;
/* [Board] */
// Board size, 170 for Mini-ITX.
board = 170; // [120:1:172]
// Gap between the board edge and the wall, each side.
board_clear = 0.5; // [0.3:0.1:1.5]
// Height of the printed standoffs under the board.
standoff_h = 6.5; // [4:0.5:10]
// Screw hole in the standoffs, 2.6 for a self tapping M3.
standoff_hole = 2.6; // [2.4:0.1:4.5]
// Mounting holes, measured from the rear (I/O) edge of the board.
hole_rear_a = 6.35; // [2:0.01:40]
hole_rear_b = 163.83; // [130:0.01:168]
// Mounting holes, measured from the left edge of the board, seen from the front.
hole_side_a = 10.16; // [2:0.01:40]
hole_side_b = 165.1; // [130:0.01:168]
// Height of the board module. Everything on the board must fit under it.
board_h = 60; // [40:1:90]
/* [Front and sides] */
// Thread diameter of the power button, 16 for the common vandal switch. 0 for none.
button_d = 16; // [0:0.5:22]
// Button position from the left, and height from the module bottom.
button_x = 30; // [10:1:160]
button_z = 36; // [15:1:80]
// Panel mount DC jack hole, 0 for none.
jack_d = 12; // [0:0.5:16]
// Which side wall the jack sits in.
jack_side = "right"; // [right, left]
// Jack position from the rear, and height from the module bottom.
jack_y = 140; // [15:1:160]
jack_z = 30; // [12:1:80]
// Vertical vent slots in the side walls.
vents = true;
vent_w = 2; // [1.5:0.5:4]
vent_pitch = 6; // [4:0.5:12]
/* [Drives] */
// Size of the disk, 3.5" by default.
hdd_w = 101.6; // [69:0.1:102]
hdd_l = 147; // [100:0.1:150]
hdd_h = 26.1; // [7:0.1:30]
// Rails that lift the disk off the floor so air passes under it. 0 lays it flat.
hdd_rail_h = 2; // [0:0.5:4]
// Room above the disk.
hdd_gap = 3; // [2:0.5:10]
// Room behind the disk for the SATA plugs.
hdd_cable = 22; // [12:1:30]
// Play around the disk in its guides.
hdd_clear = 0.4; // [0:0.1:1]
/* [Lid] */
// Fan in the lid, 0 for a plain lid.
fan_size = 80; // [0, 40, 60, 80, 92, 120]
// Thickness of the fan.
fan_t = 25; // [10:1:30]
/* [Rear shield] */
// One opening in the rear shield: from the left edge of the board, its width, and its height from the shield bottom.
io_x = 0; // [0:0.5:170]
io_w = 170; // [10:0.5:170]
io_h = 20; // [5:0.5:60]
/* [Case] */
wall = 3; // [2.5:0.1:4]
floor_t = 2.5; // [2:0.1:4]
top_t = 2.5; // [2:0.1:4]
// Rounding of the vertical outer corners.
corner_r = 2; // [0:0.5:3]
// Chamfer on the top outer edge of every module, the seam line in the stack.
edge_ch = 0.8; // [0:0.1:1.5]
// Height and thickness of the stacking lip, and its play in the pocket.
lip_h = 4; // [3:0.5:6]
lip_t = 1.5; // [1.2:0.1:2]
lip_c = 0.2; // [0.1:0.05:0.4]
// M3 nut across flats, plus play.
nut_af = 5.7; // [5.4:0.1:6.2]
/* [Colour] */
// Display only, the STL carries no colour.
color_case = "Silver"; // [Silver, Gainsboro, White, DimGray, Black, SlateGray, FireBrick, SteelBlue, ForestGreen, Goldenrod]
/* [Hidden] */
$fn = 48;
pcb_t = 1.6;
S = board + 2 * (board_clear + wall);
skirt_t = wall - lip_t - lip_c;
rib_in = wall + lip_c + 0.1;
rib_t = 1.6;
rib_pitch = 18;
stop_len = 6;
stop_end = wall + stop_len;
groove_d = 1.0;
shield_t = 1.6;
bs = 9; // boss size
boss_h = 12;
nut_h = 2.8;
bolt_d = 3.4;
bc = wall + bs / 2; // bolt centre from the outer face
z0 = lip_h + floor_t;
board_z = z0 + standoff_h;
rear_top = board_z - 0.5;
hdd_mod_h = z0 + hdd_rail_h + hdd_h + hdd_gap;
lid_wall = rib_in + rib_t;
lid_h = top_t + (fan_size > 0 ? fan_t + 2 : 5);
fan_hole = lookup(fan_size, [[40, 32], [60, 50], [80, 71.5], [92, 82.5], [120, 105]]);
// ---- 2D outlines ----
module outer2d() {
offset(r = corner_r) offset(delta = -corner_r) square([S, S]);
}
module inset2d(k) {
offset(r = -k) outer2d();
}
module ring2d(a, b) {
difference() { inset2d(a); inset2d(b); }
}
// Rib grid under the floor.
module ribs2d() {
for (p = [rib_pitch : rib_pitch : S - rib_pitch / 2]) {
translate([p - rib_t / 2, 0]) square([rib_t, S]);
translate([0, p - rib_t / 2]) square([S, rib_t]);
}
}
// Blocks in the rear corners of the pocket: they stop the stack sliding rearward.
module stops2d() {
for (m = [0, 1]) translate([m ? S : 0, 0]) mirror([m, 0])
translate([skirt_t - 0.05, skirt_t - 0.05]) {
square([rib_in - skirt_t + 0.6, stop_end - skirt_t + 0.05]);
square([stop_end - skirt_t + 0.05, rib_in - skirt_t + 0.6]);
}
}
// Lip ring on top of a module, kept clear of the corners.
module lip2d(wt) {
difference() {
ring2d(wt - lip_t, wt);
for (m = [0, 1], n = [0, 1]) translate([m ? S : 0, n ? S : 0]) mirror([m, 0]) mirror([0, n])
square([stop_end + 0.3, stop_end + 0.3]);
}
}
// The four corners, as a transform.
module corners() {
for (m = [0, 1], n = [0, 1])
translate([m ? S : 0, n ? S : 0, 0]) mirror([m, 0, 0]) mirror([0, n, 0]) children();
}
// ---- Shared shell ----
// Walls with a chamfered top edge, a floor on a rib grid with the pocket ring
// under it, corner stops, the lip and the nut bosses in the top corners.
module body(h) {
hull() {
linear_extrude(h - edge_ch) outer2d();
translate([0, 0, h - 0.01]) linear_extrude(0.01) inset2d(edge_ch);
}
}
module shell(h, wt, has_floor, has_lip, cap) {
cav_z = has_floor ? z0 : -1;
cav_h = cap > 0 ? h - cap - cav_z : h + lip_h + 2;
union() {
difference() {
body(h);
// cavity
translate([0, 0, cav_z]) linear_extrude(cav_h) inset2d(wt);
// pocket ring for the lip of the module below
translate([0, 0, -1]) linear_extrude(lip_h + 1) ring2d(skirt_t, rib_in);
// hollow under the floor, leaving the rib grid
if (has_floor)
translate([0, 0, -1]) linear_extrude(lip_h + 1)
difference() { inset2d(rib_in + rib_t); ribs2d(); }
}
linear_extrude(lip_h) stops2d();
if (has_lip) {
translate([0, 0, h]) linear_extrude(lip_h - 0.3) lip2d(wt);
corner_bosses(h);
}
}
}
// A boss in each top corner with a captive nut, tapering at 45 degrees from the
// side wall so it prints without support.
module corner_bosses(h) {
corners() difference() {
hull() {
translate([wall - 0.01, wall - 0.01, h - boss_h]) cube([bs, bs, boss_h]);
translate([wall - 0.01, wall - 0.01, h - boss_h - bs]) cube([0.02, bs, 0.02]);
}
translate([bc, bc, h - nut_h]) cylinder(d = nut_af / cos(30), h = nut_h + 1, $fn = 6);
translate([bc, bc, h - boss_h - bs - 1]) cylinder(d = bolt_d, h = boss_h + bs + 2);
}
}
// Bolt holes through the floor corners, down into the bosses of the module below.
module floor_bolt_holes() {
corners() translate([bc, bc, -1]) cylinder(d = bolt_d, h = z0 + 2);
}
module floor_slots(y_from, y_to) {
for (y = [y_from : 9 : y_to])
translate([wall + 10, y, lip_h]) cube([S - 2 * wall - 20, 3, floor_t + 1]);
}
module side_vents(h, with_jack) {
for (m = [0, 1], y = [wall + 18 : vent_pitch : S - wall - 18])
if (!(with_jack && jack_d > 0 && ((jack_side == "right") == (m == 1)) && abs(y - jack_y) < jack_d / 2 + 5))
translate([m ? S - wall - 1 : -1, y, 8]) cube([wall + 2, vent_w, h - 16]);
}
// ---- Board module ----
module standoffs() {
for (hx = [hole_side_a, hole_side_b], hy = [hole_rear_a, hole_rear_b])
translate([wall + board_clear + hx, wall + board_clear + hy, z0 - 0.01])
difference() {
cylinder(d = 7, h = standoff_h + 0.01);
translate([0, 0, 1]) cylinder(d = standoff_hole, h = standoff_h + 1);
}
}
module board_module() {
difference() {
union() {
difference() {
shell(board_h, wall, true, true, 0);
floor_slots(wall + 12, S - wall - 12);
}
standoffs();
}
floor_bolt_holes();
// rear open above the board, the ports run edge to edge
translate([wall - 0.01, -1, rear_top])
cube([S - 2 * wall + 0.02, wall + 1.01, board_h + lip_h + 2]);
// grooves the rear shield slides down into
for (m = [0, 1])
translate([m ? S - wall - 0.01 : wall - groove_d, 1.0, rear_top])
cube([groove_d + 0.01, shield_t + 0.2, board_h + lip_h + 2]);
if (button_d > 0)
translate([button_x, S - wall - 1, button_z]) rotate([-90, 0, 0])
cylinder(d = button_d + 0.3, h = wall + 2);
if (jack_d > 0)
translate([jack_side == "right" ? S - wall - 1 : -1, jack_y, jack_z]) rotate([0, 90, 0])
cylinder(d = jack_d + 0.3, h = wall + 2);
if (vents) side_vents(board_h, true);
}
}
// ---- Drive module ----
module hdd_module() {
hx0 = (S - hdd_w) / 2 - hdd_clear;
hy0 = wall + hdd_cable - hdd_clear;
gt = 1.5; gl = 12; gh = hdd_rail_h + 4; rw = 8;
difference() {
union() {
difference() {
shell(hdd_mod_h, wall, true, true, 0);
floor_slots(wall + hdd_cable + 6, S - wall - 10);
}
// two rails under the long edges of the disk
if (hdd_rail_h > 0)
translate([hx0 + hdd_clear, hy0 + hdd_clear, z0 - 0.01]) {
cube([rw, hdd_l, hdd_rail_h + 0.01]);
translate([hdd_w - rw, 0, 0]) cube([rw, hdd_l, hdd_rail_h + 0.01]);
}
// four corner guides around the disk
for (sx = [0, 1], sy = [0, 1])
translate([sx ? hx0 + hdd_w + 2 * hdd_clear : hx0,
sy ? hy0 + hdd_l + 2 * hdd_clear : hy0, z0 - 0.01])
mirror([sx, 0, 0]) mirror([0, sy, 0]) translate([-gt, -gt, 0]) {
cube([gl + gt, gt, gh]);
cube([gt, gl + gt, gh]);
}
}
floor_bolt_holes();
// cable pass-through behind the disk
translate([S / 2 - 55, wall - 0.01, -1]) cube([110, hdd_cable - 3, z0 + 2]);
if (vents) side_vents(hdd_mod_h, false);
}
}
// ---- Lid ----
module fan_cut2d() {
difference() {
circle(d = fan_size - 2);
circle(r = 5);
for (r = [8 : 5 : fan_size / 2])
difference() { circle(r = r + 2); circle(r = r); }
for (a = [0, 45, 90, 135]) rotate(a) square([fan_size, 2.5], center = true);
}
for (sx = [-1, 1], sy = [-1, 1])
translate([sx * fan_hole / 2, sy * fan_hole / 2]) circle(d = 4.3);
}
module lid() {
difference() {
union() {
shell(lid_h, lid_wall, false, false, top_t);
// full height columns in the corners for the long lid bolts
corners() translate([rib_in, rib_in, 0]) cube([wall + bs - rib_in, wall + bs - rib_in, lid_h - 0.5]);
}
corners() {
translate([bc, bc, -1]) cylinder(d = bolt_d, h = lid_h + 2);
translate([bc, bc, lid_h - 4]) cylinder(d = 6.5, h = 5);
}
if (fan_size > 0)
translate([S / 2, S / 2, lid_h - top_t - 1]) linear_extrude(top_t + 2) fan_cut2d();
}
}
// ---- Rear shield ----
// Modelled flat: x along the case width, y up the case, z inward.
module rear_shield() {
sw = S - 2 * wall + 2 * groove_d - 0.3;
sh = board_h - rear_top;
bx = groove_d + board_clear - 0.15;
lip_in = stop_len + groove_d + 0.15;
difference() {
union() {
cube([sw, sh, shield_t]);
// the rear lip of the board module lives on the shield
translate([lip_in, sh - 0.01, 0.3])
cube([sw - 2 * lip_in, lip_h - 0.3 + 0.01, shield_t - 0.3]);
}
translate([bx + io_x, -1, -1]) cube([io_w, io_h + 1, shield_t + 2]);
}
}
// ---- Ghost hardware ----
module ghost_board() {
color("DarkGreen", 0.5)
translate([wall + board_clear, wall + board_clear, board_z]) cube([board, board, pcb_t]);
}
module ghost_hdd() {
color("Silver", 0.5)
translate([(S - hdd_w) / 2, wall + hdd_cable, z0 + hdd_rail_h]) cube([hdd_w, hdd_l, hdd_h]);
}
// ---- Layout ----
module assembly() {
color(color_case) board_module();
color(color_case)
translate([wall - groove_d + 0.15, 1.1 + shield_t, rear_top]) rotate([90, 0, 0]) rear_shield();
if (show_hardware) ghost_board();
if (hdd_modules > 0)
for (i = [0 : hdd_modules - 1])
translate([0, 0, board_h + i * hdd_mod_h + (i + 1) * explode]) {
color(color_case) hdd_module();
if (show_hardware) ghost_hdd();
}
translate([0, 0, board_h + hdd_modules * hdd_mod_h + (hdd_modules + 1) * explode])
color(color_case) lid();
}
if (part == "board_module") color(color_case) board_module();
else if (part == "hdd_module") color(color_case) hdd_module();
else if (part == "lid") color(color_case) translate([0, S, lid_h]) rotate([180, 0, 0]) lid();
else if (part == "rear_shield") color(color_case) rear_shield();
else assembly();
Comments
Context, not a score. Comments never move a model up or down the results.No comments yet
Printed it? A note about your material and the clearance you landed on saves the next person a failed print.
