Modular 3D-printable honeycomb rack for cooling and mounting SBCs with a 92mm Noctua fan.
A stackable, parametric enclosure system designed in OpenSCAD. Each unit houses one SBC with board-specific back panels and connector cutouts. Features hexagonal shell design with voronoi ventilation patterns, dovetail rail system, and silent Noctua cooling.
- Rock 5B+
- Raspberry Pi 5 + Pironman 5 Max
cad/ OpenSCAD parametric design
config.scad Master configuration (dimensions, presets, tolerances)
body.scad Main entry point for rendering
sections/body/ Body modules (face, fan, back, feet)
components/ Reusable parts (rails, snap-fits, dovetails)
lib/ Helpers (shapes, honeycomb, screws, brackets)
assets/ STL/SVG assets (fan model, voronoi pattern)
SBC_Model_Framework/ Git submodule with precise SBC 3D models
website/ React + Three.js STL viewer and download portal
scripts/ STL generation pipeline
Visit the live website to preview 3D models and download STL files.
# Install website dependencies
npm install
# Fetch the OpenSCAD WebAssembly runtime the configurator engraves with (~3MB, once)
./scripts/fetch-openscad-wasm.sh
# Dev server
npm run dev
# Generate STL files (requires OpenSCAD nightly)
chmod +x scripts/generate-stl.sh
./scripts/generate-stl.sh --localPer unit, as counted from the CAD's hole patterns by cad/hardware-export.scad:
- Fan: Noctua NF-A9 PWM (92mm) — ×1, fixed with the 4 screws that come with it
- M4×50 + M4 nut: ×2, joining the front sections to the back
- M3×10: ×2, back panel
- M2.5 heat-set inserts + M2.5 screws: ×4 for Rock 5B+, ×7 for Pi 5 + Pironman 5 Max
- SMA antenna post + nut: ×2, only on units with WiFi antennas
- Filament: PLA, PETG (preferred)
Units joined into a rack need no extra fasteners — the intercase dovetail is entirely printed. The configurator totals the hardware for a whole rack.
Each unit is one SBC enclosure. The body is split into printable sections that snap together:
- Face - Front panel with voronoi ventilation pattern
- Dust Filter - Removable filter compartment, optionally engraved (see below)
- Fan Section - 92mm Noctua mount with airflow channel
- Back Top - Upper shell half with dovetail rails and diamond stacking keys for vertical/horizontal alignment
- Back Bottom - Lower shell half with SBC mounting supports, screw pillars, and board-specific standoffs
- Feet - Under units in a staggered column: an inverted triangle (default, open or closed) that continues the case's lower faces to the floor, an inverted pyramid that tapers the same way along the depth (prints upside down, with supports), an X flat on both the case and the floor (optionally on printed TPU pads), a half-cell that fills the gap between the neighbours and carries the face's vent pattern, or a tree trunk. Both slide into the back bottom's groove on a dovetail rail
The back panels are generated per-board configuration, so each unit gets precise mounting holes and connector openings for its specific SBC. Units stack on top of each other.
Each unit's dust filter can carry two engraved lines — a hostname, a rack position — cut into the flat band between its two hexagons, which is the one outward-facing surface on the assembled case that prints as a top face.
A label is per-unit, so it cannot be pre-built: CI publishes only the blank filter. The
configurator instead runs real OpenSCAD, compiled to WebAssembly, in a Web Worker over
this repository's own cad/ sources, with the label passed as -D. That is the point
of carrying a 3MB runtime rather than doing the text in JavaScript: the mesh comes out of
the same Manifold engine and the same sources as every other published part, so an
engraved filter is the published one plus the text, not a lookalike. The browser's output
has been verified bit-identical to a native OpenSCAD render, vertex for vertex.
The runtime is fetched only when a label is actually set, so an unlabelled configuration downloads nothing extra.
The limit on a label is a width in millimetres, not a character count — dustLabelCutter()
derives it from the hexagon band and publishes it as manifest.labelLimit. "NODE-01-RACK-A-XY"
and "NODE-01-RACK-ABCD" are both 17 characters and span 70.8mm and 74.1mm. Past the limit
the glyphs run off the flat and quietly stop cutting, so the field measures the real text
in the real font and the CAD asserts it again during the render.
Pushing to main triggers GitHub Actions which:
- Generates all STL files with OpenSCAD
- Builds the React website
- Deploys to GitHub Pages
CC BY-NC-SA 4.0 - David Lepaux
You are free to share and adapt this work for non-commercial purposes, with attribution and under the same license.
For commercial licensing inquiries, reach out at david.lepaux.com.
The site also serves two unmodified third-party components under their own terms: the
OpenSCAD WebAssembly build (GPL-2.0 with the CGAL linking exception) and Liberation Sans
Bold (SIL OFL 1.1). Notices and licence texts are in public/licences/.
