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 on a ledge, bay modules with a grid of bolt holes in the floor that take drive clamps or anything else you design for the grid, and a slim lid with a relief pattern across its whole top that opens into a grille over the fan and pulls air up through the stack. Modules locate on each other with a lip and pocket and bolt together with M3 screws into captive nuts. test_print renders a coupon set to check every fit in about an hour: the rear left corner of the board module, of a bay module and of the lid, the matching end of the rear shield and one foot.
The image shows the solid OpenSCAD renders at part assembly · test_print false · test_size 36.8 · bay_modules 2 · hang_hdd true · hang_rot true · explode 0 · show_hardware true · board 170 · board_clear 0.5 · ledge_w 2 · ledge_h 6.5 · relief true · relief_h 20 · board_h 64 · front_clear 2 · front_relief true · button_d 16 · button_x 88 · button_z 36 · button_ring true · jack_d 9 · jack_pos rear · jack_x 150 · jack_z 42 · vents true · vent_style horizontal · vent_span 112 · vent_z 8 · vent_z_board 28 · vent_w 1.6 · vent_rows 3 · vent_dash 12 · vent_h 12 · vent_pitch 6 · hdd_w 101.6 · hdd_l 147 · hdd_h 26.1 · hdd_gap 2 · tray_t 2.5 · tray_col 1 · hang_col 0 · fan_size 92 · fan_t 14 · fan_gap 4 · lid_pattern ripple · lid_relief 1 · lid_ch 2.5 · io_x 0 · io_w 170 · io_h 20 · io_h_right 15.5 · io_split 85 · 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 · foot_h 8 · foot_style bolt · clip_d 3 · lift 15 · side_open false · color_case Silver.
Stackable Mini-ITX NAS case
mick/itx-nas-stack@1.21.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 on a ledge, bay modules with a grid of bolt holes in the floor that take drive clamps or anything else you design for the grid, and a slim lid with a relief pattern across its whole top that opens into a grille over the fan and pulls air up through the stack. Modules locate on each other with a lip and pocket and bolt together with M3 screws into captive nuts. test_print renders a coupon set to check every fit in about an hour: the rear left corner of the board module, of a bay module and of the lid, the matching end of the rear shield and one foot.
What you use it for
A slim, clean NAS around a 170 × 170 board with a pico PSU: DC barrel jack in the rear shield just above the ports (or in a side wall), a centred 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. The port opening in the shield can step down halfway, for boards whose ports are lower on one side. The board rests loose on a narrow ledge along all four walls, so it fits any 170 mm board regardless of its hole pattern; the wall around it rises past the PCB so it cannot shift, and the shield closes the rear behind it. Just above the board the left and right walls are thinned from the inside to 1.2 mm in a band 20 mm tall, from the rear shield to the front, so connectors and their latch tabs along the board edges get about 2 mm of extra room. The front wall is thinned the same way on both sides of the button, up to 3 mm under the module top, symmetric, which also gives the pico PSU room where it overhangs the front edge. Each bay module is 40 mm tall around a 26 mm drive and has a grid of 72 through holes on an 18 mm pitch. Under the floor every column of holes runs in a narrow channel between two ribs, just wider than an M3 nut, so a nut under a hole cannot turn and needs no spanner. Two identical clamps hold a 3.5" drive, one over each short end, without screws in the drive: they lie flat on the floor, in any grid column, bolted from above with M3 × 10 into nuts in the channels, so the rest of the bay is free for a second drive, an SSD, a fan or whatever fits 18 mm steps. Turned over they hang under the floor of the lowest bay: flat against the ribs, bolted from inside the bay with M3 × 12 and a nut under the flange, a second drive hanging upside down in them, into the free air above a low CPU cooler in the board module; turned sideways it stays clear of the pico PSU in the front corner and its connector lands next to the board's SATA ports. The lid is 27 mm tall around a 92 × 14 mm slim fan (Noctua NF-A9x14 or similar); a 25 mm fan adds 11 mm. Feet with a recess for a rubber pad lift the base module for air under the floor; they bolt on from inside the board module, or clip on from below when the case is already assembled. A rear cutout in every bay floor passes the SATA cables down to the board.
Design notes
Print everything without supports. Every overhang is 45° or less and every bridge is about 12 mm or less: the boss pyramids, the nut slots, the pocket groove, the floor cells, the vent dashes and the roofs of the wall reliefs are all shaped for that. The vent dashes are kept to 12 mm because PETG bridges droop and string beyond that, and a slicer set to its default 30° threshold will still offer to fill all of this, and that support is hard to get out. The modules nearly fill the bed, so print them with a brim or mouse ears on the corners. All x positions are measured from the left as seen from the rear, the I/O side. The board module has a solid base: nothing stacks under it, so its underside is closed apart from the vent slots and four sockets for the feet, for full contact with the bed and no bridges. A bay floor sits on a rib grid that leaves room for the lip of the module below. Across the width it has two ribs per 18 mm instead of one: a narrow channel along each column of holes and a wider cell between the columns, so the floor bridges across 6 to 9 mm instead of 16, and there is more rib on the bed. The channels are nut_af plus 0.5 wide, so a nut fits with its flats against the ribs but cannot turn. Nothing is self-tapping: every bolt goes through clearance holes into a nut, locked in a channel, trapped in a side slot of a corner boss, a foot or a fan boss, or loose under the hanging clamps. Each drive clamp is a 30 mm sleeve over one end of the drive: a deck under it, a wall along each side with a lip over the top edge, and a stop in each corner of the end face, all 2.5 mm thick, with 0.2 mm play around the drive. Stops and lips are 10 mm wide measured from the outside of the wall, so they cover 7.3 mm of the drive and the rest of the end face stays open for the SATA plugs. Nothing flexes: slide a clamp onto each end, set the drive down, push the clamps against it and bolt them. The flange holes are slots with 18 mm of travel, so there is always a grid hole under each, wherever the drive sits. The clamps print standing on their end face, without supports. The wall reliefs keep the board module stiff in four ways: the side walls stay full thickness below and above their band, so each works as a channel whose flanges carry the stack; the reliefs stop short of the two front inner corners, so those stay solid columns that take the bolt loads down to the base, and at the rear the band stops just behind the groove of the rear shield, so the shield stays guided over its full height; every relief closes with a 45° roof instead of a flat step, so there is no overhang and no sharp inside corner to crack from; and the side vents of the board module sit above the band, so the thin skin is never perforated. In the front the wall stays full thickness around the button, a solid rib down the middle between the two thin fields, and at the corners and along the top and bottom. The footprint is board + clearance + two walls, 177 mm by default, plus front_clear in depth for the pico PSU that overhangs the front edge. Every millimetre of front_clear counts against the 180 mm bed. The board module is 64 mm tall by default, which takes a straight pico PSU of 47 mm over the board with 4 mm to spare under the ribs of the module above. Every bay and the lid have a pocket ring under them and every module a lip ring on top; the lip is the inner half of the wall so the outside stays flush, and a chamfer on the top edge draws a seam line at every joint; the lid carries a deeper chamfer so it reads as a cap. Air enters through a band of short horizontal dashes on each side of every module and leaves through the lid; the drive sits 5 mm under the next floor, whose rib zone adds another 4 mm of air over it. The lid is plate, four fan bosses, the fan, and fan_gap of air under it, nothing more. The lid top is one pattern: ripples spreading from the fan, a honeycomb, or diagonal lines, as raised tiles with a recessed groove network everywhere, and the same pattern cut through over the fan only, because a fan only pulls air through the stack when the rest of the lid is closed. Without a fan (fan_size 0) the lid is 7.5 mm tall and the pattern goes through over the whole top instead, inside a solid border and clear of the corner bolts, so warm air rises straight out: eight spokes tie the ripple rings and cross ties every 24 mm the diagonal stripes, so the grille stays one stiff piece. The fan hangs from four bosses under the top plate; each boss takes an M3 nut in a side slot right under the plate, open away from the fan, and the fan bolts on from the inside through its corner holes before the lid goes on, so the top carries no screw heads. The bolt ends in a shallow blind hole in the plate that stays closed under the relief. The coupons are cut on a rib line and include a nut channel, so nothing in them bridges further than in the real parts. Four bosses hang in the top corners of every module, each with a side slot that traps an M3 nut under 2.5 mm of material. The feet follow the case corner, plug into the sockets under the board module and bolt through its floor. The plug on a foot has 0.5 mm of play each side and stops 1 mm short of the socket roof, with chamfered top edges, so a first layer that spreads or a roof that sags does not stop it. With foot_style clip a foot carries a split pin instead: pushed up through the floor hole from below, its two halves spring together and a barb catches above the floor, so the feet go on a case that is already assembled, without tools. The barb has a 45 degree underside, so a firm pull takes a foot off again, and the pin has a flat top with a 45 degree lead-in, because a fine point this small melts into a blob; the pin is solid in the foot and the plug, with no gap around it, and splits into its two halves only above the plug. Bolts: M3 × 12 between modules, for the feet and for the hanging clamps, M3 × 10 for clamps on a floor, M3 × 25 for the lid (M3 × 10 for the open lid without a fan), M3 × 20 for a 14 mm fan (fan_t plus 6 for other fans); the drive needs no screws. Tolerances are tuned for PETG on an A1 mini: 0.2 mm on the lip, 0.5 mm around the board, 0.3 mm in holes, 0.2 mm on sliding nuts, 0.7 mm across the nut channels. On its side the case cools far better: the drives stand upright, so warm air rises along both faces of each drive and out through the side vents on top; a drive that ran at 48 °C in the standing stack ran at 40 °C on its side. Two rails along the depth carry it 15 mm off the desk, each on two short feet with a notch between, so cool air also comes in through the side vents underneath. The rail under the board module plugs into the two lower foot sockets and clips in with the same split pins as the clip feet, so it goes on without opening the case. The top bay is closed by a side lid with a 10 by 10 waffle of square pockets the size of the foot sockets; its corner pockets take the plugs of the second rail and the bolts into the corner bosses: M3 x 12 for the top two, M3 x 20 through the rail for the bottom two.
Parameters
test_print swaps whatever part is chosen for the coupon set, test_size is the corner it cuts. on_side shows the case lying on its side on its rails; side_lid, rail_base and rail_lid are the parts for that, and rail_test is a short piece of rail_base to try the click in the foot sockets first. part picks the assembly or one printable part in print orientation; hdd_clamp is one of the two clamps that hold a drive; print two per drive. hang_hdd shows a drive hanging under the lowest bay, hang_rot turns it sideways. ledge_w and ledge_h size the ledge; relief switches the thinned band above the board and relief_h sets its height. board_h sets the height of the board module: everything on the board, PSU included, must stay 3 mm under it. front_clear adds depth in front of the board; front_relief runs the relief in the front wall up to 3 mm under the top on both sides of the button, off keeps the front at the band height. button_d/x/z place the switch, button_ring recesses a ring around it. jack_pos puts the DC jack in the rear shield or a side wall, jack_d sizes its hole, jack_x/jack_z place it. vent_style, vent_span, vent_rows, vent_dash, vent_z, vent_z_board, vent_w and vent_pitch shape the intake bands; on the board module the band is lifted clear of the relief band when they would overlap. hdd_w/l/h size the drive and clamps, hdd_gap the air above it, tray_t the clamp deck; together they set the bay height. tray_col and hang_col pick the grid column of the drives in the assembly. fan_size picks the fan (0 for no fan: the pattern then goes through over the whole top), fan_t its thickness, fan_gap the air under it; together they set the lid height. lid_pattern picks the relief, lid_relief its depth, lid_ch the lid chamfer. io_x and io_w bound the opening in the rear shield; io_h is its height left of io_split and io_h_right its height right of it. 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 and sets the nut slots and channels, foot_h the height of the feet. foot_style picks bolted or clip-on feet, clip_d the diameter of the clip pin: thinner if it will not go through the floor hole, thicker if the feet come off too easily. lift sets how far the rails lift a case on its side, side_open lets the pockets of the side lid go right through.
Use with your agent
{
"mcpServers": {
"remixcad": {
"type": "http",
"url": "https://remix-cad.vercel.app/api/mcp"
}
}
}get_model("mick/itx-nas-stack@1.21.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, on_side, board_module, bay_module, hdd_clamp, lid, rear_shield, foot, side_lid, rail_base, rail_lid, rail_test | ||
| test_print | false | |||
| test_size | 36.8 | 18.8 to 54.8 step 18 | ||
| bay_modules | 2 | 0 to 4 step 1 | ||
| hang_hdd | true | |||
| hang_rot | true | |||
| explode | 0 | 0 to 80 step 5 | ||
| show_hardware | true | |||
| board | 170 | 120 to 172 step 1 | ||
| board_clear | 0.5 | 0.3 to 1.5 step 0.1 | ||
| ledge_w | 2 | 1.5 to 4 step 0.1 | ||
| ledge_h | 6.5 | 4 to 10 step 0.5 | ||
| relief | true | |||
| relief_h | 20 | 8 to 40 step 1 | ||
| board_h | 64 | 40 to 90 step 1 | ||
| front_clear | 2 | 0 to 10 step 0.5 | ||
| front_relief | true | |||
| button_d | 16 | 0 to 22 step 0.5 | ||
| button_x | 88 | 10 to 167 step 0.5 | ||
| button_z | 36 | 15 to 80 step 1 | ||
| button_ring | true | |||
| jack_d | 9 | 0 to 16 step 0.5 | ||
| jack_pos | "rear" | rear, right, left | ||
| jack_x | 150 | 10 to 160 step 1 | ||
| jack_z | 42 | 12 to 80 step 1 | ||
| vents | true | |||
| vent_style | "horizontal" | horizontal, vertical | ||
| vent_span | 112 | 30 to 150 step 2 | ||
| vent_z | 8 | 6 to 20 step 1 | ||
| vent_z_board | 28 | 6 to 50 step 1 | ||
| vent_w | 1.6 | 1.2 to 4 step 0.2 | ||
| vent_rows | 3 | 1 to 6 step 1 | ||
| vent_dash | 12 | 8 to 40 step 1 | ||
| vent_h | 12 | 6 to 30 step 1 | ||
| 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_gap | 2 | 2 to 10 step 0.5 | ||
| tray_t | 2.5 | 2.5 to 4 step 0.1 | ||
| tray_col | 1 | 0 to 2 step 1 | ||
| hang_col | 0 | 0 to 2 step 1 | ||
| fan_size | 92 | 0, 40, 60, 80, 92, 120 | ||
| fan_t | 14 | 10 to 30 step 1 | ||
| fan_gap | 4 | 2 to 15 step 1 | ||
| lid_pattern | "ripple" | ripple, hex, diagonal | ||
| lid_relief | 1 | 0 to 1.5 step 0.2 | ||
| lid_ch | 2.5 | 0 to 4 step 0.5 | ||
| 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 | ||
| io_h_right | 15.5 | 5 to 60 step 0.5 | ||
| io_split | 85 | 0 to 170 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 | ||
| foot_h | 8 | 5 to 15 step 0.5 | ||
| foot_style | "bolt" | bolt, clip | ||
| clip_d | 3 | 2.6 to 3.4 step 0.1 | ||
| lift | 15 | 8 to 30 step 1 | ||
| side_open | false | |||
| 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.21.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 on a ledge, bay modules carry drive trays on a
// grid of bolt holes, 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 shield, trays
// and feet flat. Modules bolt together with M3 screws into nuts trapped in corner
// bosses. Nothing is self-tapping. Everything prints without supports.
//
// Coordinates: y = 0 is the rear (I/O) wall, x = 0 the left wall AS SEEN FROM THE
// REAR. Every x position in the parameters follows that view.
/* [Parts] */
// The whole stack, or one part in print orientation. hdd_clamp is one of the two
// clamps that hold a drive, on a bay floor or hanging under it: print two per drive.
part = "assembly"; // [assembly, on_side, board_module, bay_module, hdd_clamp, lid, rear_shield, foot, side_lid, rail_base, rail_lid, rail_test]
// Print a coupon set instead: the rear left corner of the board module, of a bay
// module and of the lid (upside down, as it prints), the matching strip of rear
// shield and one foot. Stack them, drop a nut in, bolt through, slide the shield in:
// every fit is tested in about an hour.
test_print = false;
// Size of the corner the coupons are cut from. On a rib line (18.8, 36.8, 54.8) so
// the cut floor edge sits on a rib instead of hanging in the air.
test_size = 36.8; // [18.8:18:54.8]
// Bay modules shown in the assembly.
bay_modules = 2; // [0:1:4]
// Show a drive hanging under the lowest bay, above the board.
hang_hdd = true;
// Turn the hanging drive sideways, connector toward the right wall: clears the pico
// PSU in the front right corner and lands next to the SATA ports.
hang_rot = true;
// Pull the stack apart in the assembly view.
explode = 0; // [0:5:80]
// 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]
// The board rests loose on a narrow ledge along all four walls: width, and height
// above the floor. The wall around it holds it in sideways, the rear shield behind it.
ledge_w = 2; // [1.5:0.1:4]
ledge_h = 6.5; // [4:0.5:10]
// Thin the walls from the inside to 1.2 mm in a band just above the board, relief_h
// tall, so connectors and their latch tabs along the board edges get extra room. It
// runs along the side walls from the rear shield to 8 mm short of the front corners,
// and across the front on both sides of the button; full thickness stays below and
// above it. The band closes with a 45 degree roof.
relief = true;
relief_h = 20; // [8:1:40]
// Height of the board module. Everything on the board, the PSU included, must stay
// 3 mm under it: the ribs of the module above come down to this height. A straight
// pico PSU of 47 mm over the board needs 64. With a hanging drive the cooler gets
// board_h minus 46 mm.
board_h = 64; // [40:1:90]
/* [Front, rear and sides] */
// Extra depth in front of the board for the pico PSU that overhangs the front edge.
// Every millimetre adds to the 177 mm footprint; an A1 mini bed stops at 180.
front_clear = 2; // [0:0.5:10]
// Run the relief in the front wall up to 3 mm under the module top, on both sides
// of the button alike, which also clears the pico PSU. Off keeps the front at the
// band height.
front_relief = true;
// 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. 88 is centred.
button_x = 88; // [10:0.5:167]
button_z = 36; // [15:1:80]
// Shallow ring recessed around the button.
button_ring = true;
// Panel mount DC jack hole (the hole gets 0.3 mm play on top), 0 for none.
jack_d = 9; // [0:0.5:16]
// Where the DC jack sits: in the rear shield above the ports, or in a side wall.
jack_pos = "rear"; // [rear, right, left]
// Jack position: from the left as seen from the rear (rear), or from the rear (sides).
jack_x = 150; // [10:1:160]
// Jack height from the module bottom.
jack_z = 42; // [12:1:80]
// Intake band on each side of every module.
vents = true;
// Horizontal dashes in rows, or short vertical slots.
vent_style = "horizontal"; // [horizontal, vertical]
// Width of the band along the side, centred.
vent_span = 112; // [30:2:150]
// Bottom of the band above the module bottom: bay modules, and the board module. On
// the board module the band is lifted clear of the relief band when they overlap.
vent_z = 8; // [6:1:20]
vent_z_board = 28; // [6:1:50]
// Slot width (vertical) or slot height (horizontal).
vent_w = 1.6; // [1.2:0.2:4]
// Horizontal: rows of dashes and dash length. Each dash is a bridge; PETG keeps
// 12 mm clean, longer ones droop and string.
vent_rows = 3; // [1:1:6]
vent_dash = 12; // [8:1:40]
// Vertical: slot height and pitch.
vent_h = 12; // [6:1:30]
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]
// Air above the disk, under the next floor. Its rib zone adds another 4 mm.
hdd_gap = 2; // [2:0.5:10]
// Thickness of the drive tray.
tray_t = 2.5; // [2.5:0.1:4]
// Grid column (18 mm steps from the left) of the tray on the bay floor, and of the
// hanging tray when it is not turned sideways.
tray_col = 1; // [0:1:2]
hang_col = 0; // [0:1:2]
/* [Lid] */
// Fan in the lid. 92 fits the common slim 92 x 14 fans. 0 for no fan: the pattern
// then goes through over the whole top, so warm air rises straight out.
fan_size = 92; // [0, 40, 60, 80, 92, 120]
// Thickness of the fan: 14 or 15 for slim fans, 25 for standard ones. It bolts on
// with M3 bolts of fan_t plus 6 mm into nuts in the lid.
fan_t = 14; // [10:1:30]
// Free air under the fan, on top of the hdd_gap over the drive in the bay below.
fan_gap = 4; // [2:1:15]
// Relief pattern over the whole lid; it opens up over the fan, or over the whole top
// without one.
lid_pattern = "ripple"; // [ripple, hex, diagonal]
// Depth of the relief. 0 for a flat lid.
lid_relief = 1; // [0:0.2:1.5]
// Chamfer on the top edge of the lid.
lid_ch = 2.5; // [0:0.5:4]
/* [Rear shield] */
// The port opening in the rear shield: from the left as seen from the rear, and its width.
io_x = 0; // [0:0.5:170]
io_w = 170; // [10:0.5:170]
// Height of the opening from the shield bottom, left and right of io_split (from the
// left as seen from the rear), so it can step down where the ports sit lower.
io_h = 20; // [5:0.5:60]
io_h_right = 15.5; // [5:0.5:60]
io_split = 85; // [0:0.5:170]
/* [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 for the sliding nut slots. The nut channels under
// a bay floor are 0.5 mm wider, still too narrow for the nut to turn.
nut_af = 5.7; // [5.4:0.1:6.2]
// Height of the feet.
foot_h = 8; // [5:0.5:15]
// How the feet hold. bolt: an M3 x 12 from inside the board module into a nut in the
// foot, fitted before the board goes in. clip: a split pin with a barb that you push
// through the floor hole from below and that snaps in above the floor, so the feet
// go on an assembled case without tools.
foot_style = "bolt"; // [bolt, clip]
// Diameter of the clip pin. The floor hole is 3.4 mm; printed holes come out a little
// small, so 3 slides in and the barb still catches. Thinner if it will not go in,
// thicker if the feet come off too easily.
clip_d = 3; // [2.6:0.1:3.4]
// The case can also lie on its side, the drives upright. Two rails along the depth
// then carry it, one under the board module and one on the side lid, and lift it
// this far, so air comes in through the side vents underneath.
lift = 15; // [8:1:30]
// Let the pockets in the side lid go right through, as a vent.
side_open = false;
/* [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); // width
Sy = S + front_clear; // depth
skirt_t = wall - lip_t - lip_c;
rib_in = wall + lip_c + 0.1;
rib_t = 1.6;
grid_p = 18;
stop_len = 6;
stop_end = wall + stop_len;
groove_y = 1.0; // rear shield groove starts this far from the rear face
groove_d = 1.0;
shield_t = 1.6;
bs = 9; // corner boss size
boss_h = 12;
nut_h = 2.8;
nut_ac = nut_af / cos(30); // across corners
nut_top = 2.5; // material above a trapped nut
chan_w = nut_af + 0.5; // nut channel under a bay floor: the nut fits, cannot turn
bolt_d = 3.4;
bc = wall + bs / 2; // bolt centre from the outer faces
z0 = lip_h + floor_t;
board_z = z0 + ledge_h;
// top of the rear strip, where the shield sits
rear_top = board_z - 0.5;
// wall reliefs in the board module: skin left, bottom, top of the straight part of
// the band, top of its 45 degree roof, full thickness kept into the front corners,
// where it starts at the rear (1.5 mm in front of the shield groove), and the top of
// the straight part of the full height front relief, whose roof ends 3 mm under the
// module top and which keeps clear of the corner bosses
relief_t = 1.2;
rel_z0 = board_z + pcb_t;
rel_z1 = rel_z0 + relief_h;
rel_top = rel_z1 + wall - relief_t;
rel_corner = 8;
rel_rear = groove_y + shield_t + 0.2 + 1.5;
rel_front_z1 = board_h - 3 - (wall - relief_t);
rel_front_corner = bs + 1;
// side vents of the board module, lifted clear of the relief band
vz_board = relief ? max(vent_z_board, rel_top + 2) : vent_z_board;
cell_in = rib_in + rib_t; // corner cell starts here
cell_out = grid_p - rib_t / 2; // and ends at the first rib
// the square plug on top of a foot: 0.5 mm play each side in its socket, and 1 mm
// short of the socket depth, room for a first layer that spreads and a socket roof
// that sags a little
plug_c = 0.5;
plug = cell_out - cell_in - 2 * plug_c;
plug_h = lip_h - 1;
// feet under the tray: 3 mm lifts it clear of the drive screw heads on a floor, and
// keeps them below the bay when the tray hangs under it, feet on the ribs
tray_pad = 3;
tray_pad_d = 9;
bay_h = z0 + tray_pad + tray_t + hdd_h + hdd_gap;
lid_wall = rib_in + rib_t;
// fan bosses: material under the nut, the boss height with the nut slot right under
// the plate, its diameter, and the blind end of the bolt hole in the plate, kept
// closed under the relief grooves
fan_nut_below = 3.5;
fan_boss_h = fan_nut_below + nut_h;
fan_boss_d = 11;
fan_blind = max(0, top_t - lid_relief - 0.6);
lid_h = top_t + (fan_size > 0 ? fan_boss_h + fan_t + fan_gap : 5);
// counterbore for the lid bolt heads: 9 deep in a fan lid (M3 x 25), and in the thin
// lid without a fan deep enough for the head with 4 mm of column under it (M3 x 10)
lid_bore = fan_size > 0 ? 9 : lid_h - 4;
fan_hole = lookup(fan_size, [[40, 32], [60, 50], [80, 71.5], [92, 82.5], [120, 105]]);
hex_af = 6; // honeycomb cell across flats
hex_w = 1.8; // honeycomb wall
rip_p = 6; // ripple pitch
rip_w = 2.5; // ripple groove width
dia_p = 8; // diagonal pitch
dia_w = 2; // diagonal groove width
vent_gap = 4; // between dashes
vent_rp = vent_w + 2.4; // row pitch
tray_span = ceil((hdd_w + 8) / grid_p) * grid_p; // flange hole spacing, a grid multiple
tray_fx = (hdd_w - tray_span) / 2; // flange hole x, left of the drive edge
// the clamp slots give 18 mm of travel along the drive, so a drive can sit anywhere
// along the grid: centred in the bay depth, and sideways with room for the SATA
// plugs at the right wall
tray_x = grid_p * tray_col + 9 - tray_fx; // drive left edge in the bay
hang_x = grid_p * hang_col + 9 - tray_fx; // same for the hanging drive, straight
hang_rx = S - wall - 19; // sideways: drive connector end
hang_ry = grid_p + 9 - tray_fx; // sideways: drive rear edge
tray_y = (Sy - hdd_l) / 2; // drive rear edge in the bay
foot_s = 18; // foot pad size
// clip pin of the feet: the slot between its two halves, how far the barb stands
// out, and how deep its root is sunk into the solid foot
clip_slot = 1.2;
clip_barb = 0.3;
clip_free = 3;
// a case on its side: the side lid that closes the top bay, the plate thickness of
// the rails, how far a rail reaches under the case edge, and how far up the end face
// its plate reaches, just past the foot sockets
side_h = lip_h + 6;
foot_l = 30; // length of each foot of a rail
side_py = (Sy - 2 * cell_in - 10 * (cell_out - cell_in)) / 9 + (cell_out - cell_in); // pocket pitch on the side lid
side_t = 6;
side_shelf = 6;
side_top = cell_out + 1;
// ---- 2D outlines ----
module outer2d() {
offset(r = corner_r) offset(delta = -corner_r) square([S, Sy]);
}
module inset2d(k) {
offset(r = -k) outer2d();
}
module ring2d(a, b) {
difference() { inset2d(a); inset2d(b); }
}
module annulus2d(r1, r2) {
difference() { circle(r = r2); circle(r = r1); }
}
// Rib grid under a bay floor. Across the width two ribs per 18 mm, one either side of
// each column of grid holes, forming a nut channel chan_w wide with a wider cell
// between the columns; along the depth one rib per 18 mm, between the rows of holes.
// Every floor bridge is 9 mm or less.
module ribs2d() {
for (k = [0 : ceil(S / grid_p)], s = [-1, 1])
translate([grid_p * k + 9 + s * (chan_w + rib_t) / 2 - rib_t / 2, 0]) square([rib_t, Sy]);
for (p = [grid_p : grid_p : Sy - grid_p / 2]) 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 ? Sy : 0]) mirror([m, 0]) mirror([0, n])
square([stop_end + 0.3, stop_end + 0.3]);
}
}
// The four corners, as a transform: children are drawn in the rear left corner,
// with the side wall along x = 0 and the rear wall along y = 0.
module corners() {
for (m = [0, 1], n = [0, 1])
translate([m ? S : 0, n ? Sy : 0, 0]) mirror([m, 0, 0]) mirror([0, n, 0]) children();
}
// The grid positions of a bay floor, in the nut channels between the rows of ribs.
module grid_at() {
for (i = [0 : 8], j = [1 : 9]) {
x = grid_p * i + 9;
y = grid_p * j + 9;
if (x < S - wall - 6 && y < Sy - wall - 6) translate([x, y, 0]) children();
}
}
// ---- Lid patterns, all centred on the origin ----
// Honeycomb of pointy top hexagons covering a w by w area.
module hex_field2d(w) {
p = hex_af + hex_w;
n = ceil(w / p / 2) + 1;
for (j = [-n : n], i = [-n : n])
translate([(i + (j % 2 == 0 ? 0 : 0.5)) * p, j * p * cos(30)])
rotate(90) circle(d = hex_af / cos(30), $fn = 6);
}
// Diagonal stripes of width w covering the lid.
module stripes2d(w) {
n = ceil(Sy / dia_p);
rotate(45) for (k = [-n : n]) translate([k * dia_p - w / 2, -Sy]) square([w, 2 * Sy]);
}
// The recessed groove network of the relief.
module grooves2d() {
if (lid_pattern == "hex")
difference() { square(2 * Sy, center = true); hex_field2d(Sy); }
else if (lid_pattern == "ripple")
for (r = [rip_p : rip_p : Sy]) annulus2d(r, r + rip_w);
else
stripes2d(dia_w);
}
// The same pattern cut through over the fan.
module fan_open2d() {
d = fan_size - 4;
if (lid_pattern == "hex")
intersection() { circle(d = d); hex_field2d(fan_size); }
else if (lid_pattern == "ripple")
difference() {
intersection() {
circle(d = d);
for (r = [rip_p : rip_p : d / 2]) annulus2d(r - 0.5, r + rip_w + 0.5);
}
for (a = [0, 90]) rotate(a) square([fan_size, 2.5], center = true);
}
else
intersection() { circle(d = d); stripes2d(dia_p - 3); }
}
// Without a fan the pattern goes through over the whole top, inside a 2 mm border and
// clear of the corner columns. Eight spokes tie the ripple rings and cross ties every
// 24 mm the diagonal stripes, so the grille stays one stiff piece; the honeycomb holds
// together on its own.
module lid_open2d() {
difference() {
intersection() {
inset2d(lid_wall + 2);
translate([S / 2, Sy / 2]) {
if (lid_pattern == "hex")
hex_field2d(Sy + 20);
else if (lid_pattern == "ripple")
difference() {
for (r = [rip_p : rip_p : Sy]) annulus2d(r - 0.5, r + rip_w + 0.5);
for (a = [0 : 45 : 135]) rotate(a) square([2 * Sy, 2.5], center = true);
}
else
difference() {
stripes2d(dia_p - 3);
rotate(-45) for (k = [-ceil(Sy / 24) : ceil(Sy / 24)])
translate([k * 24 - 1.25, -Sy]) square([2.5, 2 * Sy]);
}
}
}
corners() square(wall + bs + 2);
}
}
// ---- Shared shell ----
module body(h, ch) {
hull() {
linear_extrude(h - ch) outer2d();
translate([0, 0, h - 0.01]) linear_extrude(0.01) inset2d(ch);
}
}
// With solid_base the underside stays closed: no pocket ring, no rib cells. For the
// board module, which has nothing under it but its feet.
module shell(h, wt, has_floor, has_lip, cap, ch, solid_base = false) {
cav_z = has_floor ? z0 : -1;
cav_h = cap > 0 ? h - cap - cav_z : h + lip_h + 2;
union() {
difference() {
body(h, ch);
// cavity
translate([0, 0, cav_z]) linear_extrude(cav_h) inset2d(wt);
if (!solid_base) {
// 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(cell_in); ribs2d(); }
}
}
if (!solid_base) 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. An M3 nut slides in from the side and is trapped under
// nut_top of material; the boss tapers 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 - boss_h - bs - 1]) cylinder(d = bolt_d, h = boss_h + bs + 2);
translate([bc - nut_af / 2, bc - nut_ac / 2, h - nut_top - nut_h]) cube([nut_af, 20, nut_h]);
}
}
// Bolt holes through the floor corners: down into the bosses of the module below,
// or into the feet under the base.
module floor_bolt_holes() {
corners() translate([bc, bc, -1]) cylinder(d = bolt_d, h = z0 + 2);
}
// Vent slots through the floor, from z up.
module floor_slots(ys, w, z = lip_h) {
for (y = ys)
translate([wall + 10, y, z]) cube([S - 2 * wall - 20, w, z0 + 1 - z]);
}
// Intake band on both side walls: rows of horizontal dashes, or vertical slots.
module side_vents(h, z, with_jack) {
if (vent_style == "vertical") {
vh = min(vent_h, h - z - 6);
for (m = [0, 1], y = [Sy / 2 - vent_span / 2 : vent_pitch : Sy / 2 + vent_span / 2 + 0.01])
if (!(with_jack && jack_d > 0 && jack_pos != "rear" && ((jack_pos == "right") == (m == 1)) && abs(y - jack_x) < jack_d / 2 + 5))
translate([m ? S - wall - 1 : -1, y - vent_w / 2, z]) cube([wall + 2, vent_w, vh]);
} else {
dp = vent_dash + vent_gap;
n = max(1, floor((vent_span + vent_gap) / dp));
y0 = Sy / 2 - (n * dp - vent_gap) / 2;
rows = min(vent_rows, floor((h - z - 6) / vent_rp));
for (m = [0, 1], r = [0 : rows - 1], k = [0 : n - 1])
if (!(with_jack && jack_d > 0 && jack_pos != "rear" && ((jack_pos == "right") == (m == 1)) && abs(y0 + k * dp + vent_dash / 2 - jack_x) < jack_d / 2 + vent_dash / 2 + 3))
translate([m ? S - wall - 1 : -1, y0 + k * dp, z + r * vent_rp]) cube([wall + 2, vent_dash, vent_w]);
}
}
// ---- Board module ----
// A narrow ledge along all four walls at ledge_h: the board rests on its edges,
// whatever its hole pattern. Notched at the corners for the feet bolt heads.
module ledges() {
b0 = wall + board_clear;
difference() {
translate([0, 0, z0 - 0.01]) linear_extrude(ledge_h + 0.01)
difference() {
translate([wall - 0.01, wall - 0.01]) square([S - 2 * wall + 0.02, Sy - 2 * wall + 0.02]);
translate([b0 + ledge_w, b0 + ledge_w]) square([board - 2 * ledge_w, board - 2 * ledge_w]);
}
corners() translate([bc, bc, z0 - 1]) cylinder(d = 7.5, h = ledge_h + 2);
}
}
// One stretch of a wall relief, from a to b along a wall, drawn against the left
// wall (inner face at x = wall, outer face at x = 0): the wall thinned to relief_t
// between z0 and z1, closed by a 45 degree roof so it prints without overhang.
module relief_cut(a, b, z0, z1) {
d = wall - relief_t;
hull() {
translate([relief_t, a, z0]) cube([d + 0.01, b - a, z1 - z0]);
translate([wall - 0.01, a, z0]) cube([0.02, b - a, z1 - z0 + d]);
}
}
// [a, b] as one stretch, or as two around a solid panel from p0 to p1.
function split(a, b, on, p0, p1) = on ? [[a, p0], [p1, b]] : [[a, b]];
// The relief band in the left and right walls, from just in front of the shield
// groove to rel_corner short of the front corners, solid around a side jack. In the
// front wall the same on both sides of the button: band height, or with front_relief
// up to 3 mm under the top, clear of the corner bosses.
module relief_cuts() {
if (relief)
for (m = [0, 1]) {
jk = jack_d > 0 && ((jack_pos == "right" && m == 1) || (jack_pos == "left" && m == 0));
translate([m ? S : 0, 0, 0]) mirror([m, 0, 0])
for (s = split(rel_rear, Sy - wall - rel_corner, jk, jack_x - jack_d / 2 - 6, jack_x + jack_d / 2 + 6))
if (s[1] > s[0]) relief_cut(s[0], s[1], rel_z0, rel_z1);
}
fc = wall + (front_relief ? rel_front_corner : rel_corner);
fz = front_relief ? rel_front_z1 : rel_z1;
translate([0, Sy, 0]) rotate(-90)
for (s = split(fc, S - fc, button_d > 0, button_x - button_d / 2 - 8, button_x + button_d / 2 + 8))
if (s[1] > s[0]) relief_cut(s[0], s[1], rel_z0, fz);
}
// The board module stands on its feet only, so its base is solid: the vent slots go
// straight through and the feet plug into a socket in each corner.
module board_module() {
difference() {
union() {
shell(board_h, wall, true, true, 0, edge_ch, true);
ledges();
}
floor_slots([wall + 12 : 9 : Sy - wall - 12], 3, -1);
floor_bolt_holes();
// sockets for the feet
corners() translate([cell_in, cell_in, -1]) cube([cell_out - cell_in, cell_out - cell_in, lip_h + 1]);
// 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, groove_y, rear_top])
cube([groove_d + 0.01, shield_t + 0.2, board_h + lip_h + 2]);
if (relief || front_relief) relief_cuts();
if (button_d > 0) {
translate([button_x, Sy - wall - 1, button_z]) rotate([-90, 0, 0])
cylinder(d = button_d + 0.3, h = wall + 2);
if (button_ring)
translate([button_x, Sy - 1, button_z]) rotate([-90, 0, 0])
cylinder(d = button_d + 12, h = 2);
}
if (jack_d > 0 && jack_pos != "rear")
translate([jack_pos == "right" ? S - wall - 1 : -1, jack_x, jack_z]) rotate([0, 90, 0])
cylinder(d = jack_d + 0.3, h = wall + 2);
if (vents) side_vents(board_h, vz_board, true);
}
}
// ---- Bay module ----
// A grid of M3 clearance holes through the floor on the 18 mm pitch. Under the floor
// each column of holes runs in a nut channel between two ribs, so a nut under a hole
// sits with its flats against the ribs and cannot turn. Anything with holes on the
// same pitch bolts down anywhere on the floor, or hangs under it.
module bay_module() {
difference() {
shell(bay_h, wall, true, true, 0, edge_ch);
// two vent slots per grid row, between the rows of holes
floor_slots([32 : grid_p : Sy - wall - 8], 2);
floor_slots([38 : grid_p : Sy - wall - 8], 2);
grid_at() translate([0, 0, -1]) cylinder(d = bolt_d, h = z0 + 2);
floor_bolt_holes();
// cable pass-through behind the drive
translate([S / 2 - 55, wall - 0.01, -1]) cube([110, tray_y - wall, z0 + 2]);
if (vents) side_vents(bay_h, vent_z, false);
}
}
// ---- Drive clamps ----
// Two identical clamps hold a drive, one over each short end. No screws go into the
// drive. Each clamp is a short sleeve: a deck under the drive, a wall along each side
// with a lip over the top edge, and at the end only a small block in each corner,
// so the end face stays open for the SATA plugs. Slide a clamp onto each end, set the
// drive down, push the clamps together and bolt them: the bolts run in slots with
// 18 mm of travel, so there is always a grid hole under each slot, and once tight
// the drive is held on all six faces. Flat on a floor they bolt from above into the
// nuts in the channels; turned over against the ribs under a floor they bolt from
// inside the bay with a nut under the flange, the drive hanging upside down in them.
// Drawn at the connector end: drive end face at y = 0, the drive running toward +y,
// its left edge at x = 0. It prints standing on its end face.
clamp_c = 0.2; // play around the drive
clamp_t = 2.5; // wall, lip and end wall thickness
clamp_l = 30; // how far a clamp reaches along the drive
// corner stops and the lips over the top: 10 mm wide in all, measured from the
// outside of the side wall, so they cover 7.3 mm of the drive
clamp_post = 10 - clamp_t - clamp_c;
clamp_lip = clamp_post;
clamp_e = -clamp_c - clamp_t; // outer faces, sides and end
clamp_zt = tray_t + hdd_h + clamp_c; // underside of the lips
clamp_zh = clamp_zt + clamp_t; // top of the clamp
// Children drawn against the drive's left edge, mirrored to the right edge.
module drive_sides() {
for (m = [0, 1]) translate([m ? hdd_w : 0, 0, 0]) mirror([m, 0, 0]) children();
}
module hdd_clamp() {
e = clamp_e;
sy = e + 2.5 + bolt_d / 2; // first slot centre
difference() {
union() {
// deck under the drive, and the bolt flanges either side
translate([e, e, 0]) cube([hdd_w - 2 * e, clamp_l - e, tray_t]);
translate([tray_fx - 6, e, 0]) cube([tray_span + 12, clamp_l - e, tray_t]);
drive_sides() {
// side wall with a lip over the top edge
translate([e, e, 0]) cube([clamp_t, clamp_l - e, clamp_zh]);
translate([-clamp_c - 0.01, e, clamp_zt]) cube([clamp_lip + clamp_c, clamp_l - e, clamp_t]);
// a stop in the corner, in front of the end face, as thick as the walls and
// the same width from the deck to the top: stops the drive sliding
translate([e, e, 0]) cube([clamp_post - e, clamp_t, clamp_zh]);
}
}
// slots in the flanges, 18 mm of travel
for (x = [tray_fx, tray_fx + tray_span])
hull() for (y = [sy, sy + grid_p]) translate([x, y, -1]) cylinder(d = bolt_d, h = tray_t + 2);
}
}
// Both clamps on a drive, in the drive's frame: connector end at y = 0.
module drive_clamps() {
hdd_clamp();
translate([hdd_w, hdd_l, 0]) rotate(180) hdd_clamp();
}
// ---- Foot ----
// Follows the case corner, plugs into a socket under the board module and bolts
// through its floor. A recess underneath takes a 10 mm stick-on rubber pad. Drawn in
// the rear left corner, pad down.
module foot_pad2d(k) {
offset(r = max(corner_r - k, 0.01)) offset(delta = -corner_r) translate([0.4, 0.4]) square(foot_s);
}
module foot() {
difference() {
union() {
hull() {
linear_extrude(0.01) foot_pad2d(1);
translate([0, 0, 1]) linear_extrude(foot_h - 1) foot_pad2d(0);
}
// the plug, its top edges chamfered as a lead-in
translate([cell_in + plug_c, cell_in + plug_c, foot_h - 0.01]) hull() {
cube([plug, plug, plug_h - 0.6]);
translate([0.6, 0.6, 0]) cube([plug - 1.2, plug - 1.2, plug_h]);
}
}
if (foot_style != "clip") {
translate([bc, bc, -1]) cylinder(d = bolt_d, h = foot_h + lip_h + 2);
translate([bc - nut_af / 2, bc - nut_ac / 2, foot_h - nut_top - nut_h]) cube([nut_af, 20, nut_h]);
}
translate([bc, bc, -1]) cylinder(d = 10.4, h = 1.6);
}
if (foot_style == "clip") translate([bc, bc, foot_h - clip_free - 0.01]) clip_pin();
}
// A split pin with a barb, solid in the foot and the plug and split above the plug.
// Pushed up through the floor hole its two halves spring together, and above the
// floor the barb spreads out again and catches. The barb's underside is a 45 degree
// cone, so a firm pull takes the foot off. The top is flat with a 45 degree lead-in:
// a fine point this small melts into a blob when it prints. Drawn from its root,
// clip_free deep in the foot.
module clip_pin() {
r = clip_d / 2;
rb = r + clip_barb;
zp = clip_free + plug_h; // top of the plug, where the halves start
zc = clip_free + z0 + 0.15; // just above the floor top
zt = zc + clip_barb + 0.6; // top of the barb
tip = r - 0.3; // radius of the flat top
difference() {
rotate_extrude($fn = 32)
polygon([[0, 0], [r, 0], [r, zc], [rb, zc + clip_barb], [rb, zt], [tip, zt + rb - tip], [0, zt + rb - tip]]);
translate([-rb - 1, -clip_slot / 2, zp]) cube([2 * rb + 2, clip_slot, zt + 3]);
}
}
// ---- On its side ----
// On its side the drives stand upright: warm air rises along both faces of each
// drive and leaves through the side vents on top, which cools far better than the
// stack standing up. The x = 0 wall goes down. Two rails along the full depth carry
// the case and lift it, so cool air comes in through the side vents underneath.
// The top bay is closed by a side lid with a waffle of square pockets on its face;
// its corner pockets are the same sockets as under the board module, so both rails
// plug in the same way. The rail under the board module clips in with
// split pins, so it goes on without opening the case; the rail on the side lid is
// held by the two lower lid bolts.
// The side lid: a 6 mm plate over the top bay, a pocket underneath for the lip of
// the bay, and on its face a waffle of square pockets, 10 by 10, all the size of the
// foot sockets under the board module and on the same border, so the four corner
// pockets are the sockets the rail plugs into. With side_open the pockets go right
// through, apart from the corners, which carry the bolts.
module side_lid() {
blk = cell_out + 2.5; // solid corner block over the bosses
cs = cell_out - cell_in; // pocket size, the size of a foot socket
n = 10;
px = (S - 2 * cell_in - n * cs) / (n - 1) + cs; // pitch across
py = (Sy - 2 * cell_in - n * cs) / (n - 1) + cs; // pitch along the depth
difference() {
union() {
difference() {
body(side_h, lid_ch);
translate([0, 0, -1]) linear_extrude(lip_h + 1) ring2d(skirt_t, rib_in);
translate([0, 0, -1]) linear_extrude(lip_h + 1)
difference() { inset2d(lid_wall); corners() square(blk); }
}
linear_extrude(lip_h) stops2d();
}
for (i = [0 : n - 1], j = [0 : n - 1]) {
corner = (i == 0 || i == n - 1) && (j == 0 || j == n - 1);
d = (side_open && !corner) ? side_h - lip_h + 1 : side_h - lip_h - 1.2;
translate([cell_in + i * px, cell_in + j * py, side_h - d]) cube([cs, cs, d + 1]);
}
// the bolts into the corner bosses of the top bay
corners() translate([bc, bc, -1]) cylinder(d = bolt_d, h = side_h + 2);
}
}
// One rail, drawn against an end face at z = 0, the case on the +z side and the
// x = 0 wall at the bottom. A slim L: a plate against the end face that reaches just
// past the sockets, and a leg below the case with a shelf the case edge rests on.
// Chamfers on every outside edge, grid lines on its face, and a notch from below in
// the middle so the case stands on four short feet, firm on any desk. Both
// rails plug into the two lower sockets. clip: split pins that snap in above the
// floor of the board module; otherwise counterbored holes for the lid bolts.
module side_rail(clip) {
c = 2; // outside chamfer
pts = [[-lift, -side_t + c], [-lift, side_shelf - 3], [-lift + 3, side_shelf], [0, side_shelf],
[0, 0], [side_top, 0], [side_top, -side_t + 3], [side_top - 3, -side_t], [-lift + c, -side_t]];
ends = [[-side_shelf - 1, 0], [side_t - 3, 0], [side_t + 0.01, 3], [side_t + 0.01, Sy - 3],
[side_t - 3, Sy], [-side_shelf - 1, Sy]];
difference() {
union() {
intersection() {
translate([0, Sy, 0]) rotate([90, 0, 0]) linear_extrude(Sy) polygon(pts);
translate([-lift - 1, 0, 0]) rotate([0, 90, 0]) linear_extrude(lift + side_top + 2) polygon(ends);
}
for (n = [0, 1]) translate([0, n ? Sy : 0, 0]) mirror([0, n, 0])
translate([cell_in + plug_c, cell_in + plug_c, -0.01]) hull() {
cube([plug, plug, plug_h - 0.6]);
translate([0.6, 0.6, 0]) cube([plug - 1.2, plug - 1.2, plug_h]);
}
}
// a notch from below in the middle, so the case stands on four feet
translate([0, 0, -side_t - 1]) linear_extrude(side_t + side_shelf + 2)
polygon([[-lift - 1, foot_l - 1], [-lift - 1, Sy - foot_l + 1],
[-4, Sy - foot_l - lift + 4], [-4, foot_l + lift - 4]]);
// on the outside the grid of the side lid carries on: a line where the case
// begins and a cross line in line with every rib between the lid pockets
translate([-0.8, -1, -side_t - 1]) cube([1.6, Sy + 2, 2]);
for (k = [0 : 8]) translate([-lift + 3, cell_in + k * side_py + (cell_out - cell_in) + (side_py - (cell_out - cell_in)) / 2 - 0.8, -side_t - 1])
cube([lift + side_top - 6, 1.6, 2]);
if (!clip)
for (y = [bc, Sy - bc]) {
translate([bc, y, -side_t - 1]) cylinder(d = bolt_d, h = side_t + plug_h + 2);
translate([bc, y, -side_t - 1]) cylinder(d = 6.5, h = 3.5 + 1);
}
}
if (clip)
for (y = [bc, Sy - bc]) translate([bc, y, -clip_free - 0.01]) clip_pin();
}
// ---- Lid ----
// One relief pattern over the whole top, raised tiles with a recessed groove network,
// and the same pattern cut through over the fan; without a fan it goes through over
// the whole top. The fan hangs from four bosses under
// the plate. Each boss takes an M3 nut in a side slot right under the plate, open away
// from the fan; the fan bolts on from the inside through its corner holes, so the
// top carries no screw heads.
// The four fan hole positions; children are drawn with the nut slot opening toward +x,
// away from the fan.
module fan_bosses() {
for (sx = [-1, 1], sy = [-1, 1])
translate([S / 2 + sx * fan_hole / 2, Sy / 2 + sy * fan_hole / 2, 0])
mirror([sx < 0 ? 1 : 0, 0, 0]) children();
}
module lid() {
fb = lid_h - top_t - fan_boss_h; // bottom of the fan bosses
difference() {
union() {
shell(lid_h, lid_wall, false, false, top_t, lid_ch);
// 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]);
if (fan_size > 0)
fan_bosses() translate([0, 0, fb]) cylinder(d = fan_boss_d, h = fan_boss_h + 0.01);
}
corners() {
translate([bc, bc, -1]) cylinder(d = bolt_d, h = lid_h + 2);
translate([bc, bc, lid_h - lid_bore]) cylinder(d = 6.5, h = lid_bore + 1);
}
// the recessed grooves of the relief, inside the chamfer border; without a fan the
// top is cut through and there is no relief left to make
if (lid_relief > 0 && fan_size > 0)
translate([0, 0, lid_h - lid_relief]) linear_extrude(lid_relief + 1)
offset(r = 0.3) offset(r = -0.3)
intersection() {
inset2d(lid_ch + 2);
translate([S / 2, Sy / 2]) grooves2d();
}
if (fan_size == 0)
translate([0, 0, lid_h - top_t - 1]) linear_extrude(top_t + 2) lid_open2d();
if (fan_size > 0) {
// the pattern opens up over the fan
translate([S / 2, Sy / 2, lid_h - top_t - 1]) linear_extrude(top_t + 2) fan_open2d();
fan_bosses() {
// bolt hole, ending blind in the plate
translate([0, 0, fb - 1]) cylinder(d = bolt_d, h = fan_boss_h + 1 + fan_blind);
// side slot for the nut, right under the plate
translate([-nut_ac / 2, -nut_af / 2, lid_h - top_t - nut_h]) cube([fan_boss_d, nut_af, nut_h]);
}
}
}
}
// ---- Rear shield ----
// Modelled flat: x along the case width, y up the case, z inward. The port opening
// is io_h tall left of io_split and io_h_right tall right of it.
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;
xa = io_x;
xb = min(io_x + io_w, board);
xs = min(max(io_split, xa), xb);
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]);
}
if (xs > xa)
translate([bx + xa, -1, -1]) cube([xs - xa + 0.01, io_h + 1, shield_t + 2]);
if (xb > xs)
translate([bx + xs, -1, -1]) cube([xb - xs, io_h_right + 1, shield_t + 2]);
if (jack_d > 0 && jack_pos == "rear")
translate([bx + jack_x, jack_z - rear_top, -1]) cylinder(d = jack_d + 0.3, h = 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(x, y, z, rot) {
color("Silver", 0.5)
translate([x, y, z]) cube(rot ? [hdd_l, hdd_w, hdd_h] : [hdd_w, hdd_l, hdd_h]);
}
module ghost_fan() {
color("DimGray", 0.5)
translate([S / 2 - fan_size / 2, Sy / 2 - fan_size / 2, lid_h - top_t - fan_boss_h - fan_t])
difference() { cube([fan_size, fan_size, fan_t]); translate([fan_size / 2, fan_size / 2, -1]) cylinder(d = fan_size - 4, h = fan_t + 2); }
}
// ---- Test coupons ----
// The rear left corner of the board module, of a bay module and of the lid, cut
// test_size square on a rib line, the matching end of the rear shield and one foot.
module corner_cut(s) {
intersection() {
children();
translate([-1, -1, -1]) cube([s + 1, s + 1, 300]);
}
}
module test_layout() {
corner_cut(test_size) board_module();
translate([test_size + 12, 0, 0]) corner_cut(test_size) bay_module();
translate([2 * test_size + 24, 0, 0]) corner_cut(test_size) rear_shield();
translate([2 * test_size + 24, board_h + 10, 0]) foot();
translate([0, test_size + 12, 0]) translate([0, test_size, lid_h]) rotate([180, 0, 0])
corner_cut(test_size) lid();
}
// ---- Layout ----
module bays(ex) {
if (bay_modules > 0)
for (i = [0 : bay_modules - 1])
translate([0, 0, board_h + i * bay_h + (i + 1) * ex]) {
color(color_case) bay_module();
// the tray feet down on the floor, the drive on top
color(color_case) translate([tray_x, tray_y, z0]) drive_clamps();
if (show_hardware) ghost_hdd(tray_x, tray_y, z0 + tray_t, false);
if (i == 0 && hang_hdd) {
// feet up against the ribs, the drive hanging below
hz = -tray_t;
if (hang_rot) {
color(color_case) translate([hang_rx, hang_ry, 0]) rotate(90) mirror([0, 0, 1]) drive_clamps();
if (show_hardware) ghost_hdd(hang_rx - hdd_l, hang_ry, hz - hdd_h, true);
} else {
color(color_case) translate([hang_x, tray_y, 0]) mirror([0, 0, 1]) drive_clamps();
if (show_hardware) ghost_hdd(hang_x, tray_y, hz - hdd_h, false);
}
}
}
}
module assembly() {
color(color_case) board_module();
color(color_case) corners() translate([0, 0, -foot_h - explode / 2]) foot();
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();
bays(explode);
translate([0, 0, board_h + bay_modules * bay_h + (bay_modules + 1) * explode]) {
color(color_case) lid();
if (show_hardware && fan_size > 0) ghost_fan();
}
}
// The case on its side as it stands on the desk, on its two rails.
module on_side() {
top = board_h + bay_modules * bay_h;
translate([0, 0, lift]) multmatrix([[0, 0, 1, 0], [0, -1, 0, Sy], [1, 0, 0, 0], [0, 0, 0, 1]]) {
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();
bays(0);
color(color_case) translate([0, 0, top]) side_lid();
color(color_case) side_rail(true);
color(color_case) translate([0, 0, top + side_h]) mirror([0, 0, 1]) side_rail(false);
}
}
if (test_print) color(color_case) test_layout();
else if (part == "board_module") color(color_case) board_module();
else if (part == "bay_module") color(color_case) bay_module();
else if (part == "hdd_clamp") color(color_case) translate([6 - tray_fx, clamp_zh, -clamp_e]) rotate([90, 0, 0]) hdd_clamp();
else if (part == "lid") color(color_case) translate([0, Sy, lid_h]) rotate([180, 0, 0]) lid();
else if (part == "rear_shield") color(color_case) rear_shield();
else if (part == "foot") color(color_case) foot();
else if (part == "on_side") on_side();
else if (part == "side_lid") color(color_case) translate([0, Sy, side_h]) rotate([180, 0, 0]) side_lid();
else if (part == "rail_base") color(color_case) translate([lift, 0, side_t]) side_rail(true);
else if (part == "rail_test") color(color_case) intersection() {
translate([lift, 0, side_t]) side_rail(true);
translate([-1, -1, -1]) cube([lift + side_top + 2, 33, 60]);
}
else if (part == "rail_lid") color(color_case) translate([lift, 0, side_t]) side_rail(false);
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.
