A fully parametric OpenSCAD model of a 10-inch rack shelf with an integrated front panel, mechanically compatible with the Lab Rax 3D-printed rack system by Michael Klements (The DIY Life): 236.525 mm mounting raster, 1U = 44.45 mm, three M6 holes per U per side. Designed to print on ~250 mm beds (Bambu Lab A1, 256 × 256 mm) without supports.
Highlights
- Panel and shelf are generated as a single printable monolith, already oriented for printing (panel face down, shelf growing upward).
- A declarative cutout system: every hole in the panel or the shelf deck is one line in a list — circles, rectangles, slots, hexagons, grids, linear arrays, PC-fan presets, Raspberry Pi preset.
- Mounting holes are horizontal slots for sideways adjustment, and they are skipped automatically wherever the shelf, its walls, ribs or gussets cover them from behind.
- Extensive
assert()validation with readable error messages: parts that exceed the build plate, the panel outline, or the rack geometry fail loudly at render time. - A standalone button generator (
button.scad) for panel cutouts — including backlit transparent power buttons.
Requires OpenSCAD 2021.01 or newer.
- Quick start
- File layout
- Coordinate systems
- Main parameters (Customizer)
- The declarative cutout system
- The shelf and its walls
- Automatic mounting-hole management
- Printability
- The button generator
- Examples
- Calibration
- System constants
- Open
main.scadin OpenSCAD. - Open Window → Customizer — all parameters (part, panel, shelf, walls, ribs, quality) are exposed as sliders and dropdowns.
- Define your cutouts in the
panel_cutoutsandshelf_holeslists inmain.scad(see the cutout system). - Press
F6to render,F7to export STL. The model is already in print orientation — no rotation needed in the slicer.
Ready-made configurations live in examples/.
main.scad entry point: Customizer parameters, part switch, assembly
config.scad Lab Rax system constants + globals (hole_clearance, EPS)
button.scad standalone button generator (round/rect face, flange, pin)
lib/rack_std.scad u_height(), mount_hole_positions(), panel outline, validation
lib/cutouts.scad declarative cutout system: cutout(spec) dispatcher + validation
lib/helpers.scad rounded_rect(), slot2d(), hex2d(), hole_negative(), chamfers
panel.scad module panel(u, cutouts, ...)
shelf.scad module shelf(width, depth, wall_type, ..., holes)
examples/ self-contained example configurations
Library files do not include their own dependencies — main.scad
(and each example) defines the include order. If you build your own
top-level file, copy the include block from main.scad.
All dimensions are millimeters. Two 2D coordinate systems are used for cutouts, both viewed from the front of the rack:
| List | Origin [0,0] |
X axis | Y axis |
|---|---|---|---|
panel_cutouts |
panel center | right | up |
shelf_holes |
deck center | right | toward the rack's back |
The 3D model itself is built in print orientation: panel plane = XY, front face at z = 0 (bed side), rack depth along +Z.
| Parameter | Default | Description |
|---|---|---|
part |
assembly |
assembly / panel / shelf — what to render. The printed part is the monolithic assembly; the single parts are for inspection. |
orientation |
print |
print = as printed (export this); upright = in-use orientation for previewing. |
| Parameter | Default | Description |
|---|---|---|
panel_u |
2 | Panel height in rack units (1–5). |
panel_thickness |
3 | Panel thickness [mm]. |
corner_radius |
3 | Outline corner radius [mm]. |
edge_chamfer |
0.6 | 45° chamfer on the outer edges of both faces [mm]. |
mount_slot_length |
10 | Overall length of the M6 mounting slots [mm]. 10 mm gives ±1.85 mm of sideways adjustment; 6.3 degenerates to a plain round hole. |
panel_top_trim |
0 | Shortens the panel from the top [mm]. 15.875 removes exactly one hole row; the bottom edge and the coordinate system stay put. Holes that no longer fit are skipped automatically. |
| Parameter | Default | Description |
|---|---|---|
shelf_enable |
true | Generate the shelf at all. |
shelf_width |
180 | Deck width [mm]. Limited by the panel width (250), not by the 222 mm post clearance — see hole management. |
shelf_depth |
100 | Deck depth [mm] (= print height − panel thickness). |
shelf_thickness |
3 | Deck thickness [mm]. |
shelf_x_offset |
0 | Sideways shift of the shelf relative to the panel center [mm]. |
shelf_z_offset |
6 | Height of the deck's bottom plane above the panel's bottom edge [mm]. |
| Parameter | Default | Description |
|---|---|---|
wall_type |
slant |
none / slant / rect (see walls). |
wall_height |
15 | Wall height at the panel, measured above the deck's top surface [mm]. |
wall_end_height |
0 | slant only: wall height at the deck's far end [mm]. 0 = slope all the way down to the deck. |
wall_thickness |
0 | Wall thickness [mm]; 0 = same as deck thickness. |
wall_left / wall_right / wall_back |
true | Which edges get walls. |
| Parameter | Default | Description |
|---|---|---|
ribs_enable |
false | Stiffening ribs under the deck (vertical fins running along the depth). |
ribs_count |
3 | Number of ribs, evenly spaced across the deck width. |
ribs_height |
8 | Rib height below the deck [mm]; clipped to the panel's bottom edge. |
gusset_enable |
false | Triangular panel-to-deck gussets at the deck's side edges. |
gusset_size |
10 | Gusset leg length [mm]; clipped to shelf_z_offset. |
| Parameter | Default | Description |
|---|---|---|
preview_quality |
24 | $fn used in the F5 preview — keep it low for smooth model rotation. |
render_quality |
128 | $fn used for F6 render / STL export — high for smooth arcs on the print. |
Switching is automatic via OpenSCAD's $preview variable; you never
have to change it by hand.
| Parameter | Default | Description |
|---|---|---|
hole_clearance |
0.3 | Added to the diameter of every screw hole (M6 slots, fan screws, presets). Tune after a test print. |
EPS |
0.01 | Epsilon used to oversize negative volumes (avoids z-fighting / non-manifold booleans). |
A cutout is a vector: [type, [x, y], parameters...]. Optional trailing
parameters can simply be omitted. Adding or removing a hole is a
one-line edit.
| Spec | Description |
|---|---|
["circle", [x,y], d, chamfer, chamfer_side] |
Round hole of diameter d. |
["rect", [x,y], [w,h], r, chamfer, rot, chamfer_side] |
Rectangle; r = corner radius, rot = rotation in degrees. |
["slot", [x,y], l, d, rot, chamfer, chamfer_side] |
Slot / stadium; l = overall length including the end arcs, d = width. |
["hex", [x,y], af, rot, chamfer, chamfer_side] |
Regular hexagon; af = across-flats (wrench size), e.g. for captive nuts. |
["grid", [x,y], child, nx, ny, [px,py]] |
nx × ny grid of any child cutout; [x,y] = grid center, [px,py] = pitch. |
["linear", [x,y], child, n, pitch, rot] |
Linear array of n child cutouts, centered at [x,y]. |
["fan", [x,y], size, chamfer, chamfer_side] |
PC fan preset: size ∈ {40, 80, 120}. Opening + four Ø4.3 screw holes (spacings: 40→32 mm/Ø38, 80→71.5 mm/Ø76.5, 120→105 mm/Ø116.5). |
["rpi5", [x,y], rot] |
Raspberry Pi 5/4/3B+ preset: 58 × 49 mm pattern, M2.5 holes (deck). |
["grid_m3" | "grid_m4", [x,y], nx, ny, [px,py]] |
Generic M3 / M4 screw grids. |
Details:
chamfer— width of a 45° countersink chamfer in mm.chamfer_side—"front"(default),"back"or"both". "Front" means the panel's visible face, or the deck's top surface.rot— rotation of the cutout around its own center, degrees.childingrid/linearis a complete cutout spec (normally with position[0,0]); patterns may be nested arbitrarily (e.g. a grid of slots, a linear array of hexes).- Every screw-hole preset already includes the global
hole_clearance.
Validation: a cutout extending outside the panel/deck outline stops
the render with a descriptive assert. A cutout that may collide
with a mounting slot produces an echo warning (a conservative
circumscribed-circle test — expect occasional false positives; check
visually).
Worked example — a 2U panel with two 80 mm fans:
panel_cutouts = [
["fan", [-45, 0], 80, 1, "front"],
["fan", [ 45, 0], 80, 1, "front"],
["grid", [ 0, 0], ["hex", [0,0], 6], 1, 5, [0, 8]], // hex column between fans
];And mounting a Mini-ITX board on the deck:
shelf_holes = [
// 4× M3 standoff holes, 157.48 × 154.94 mm pattern (2×2 grid)
["grid", [0, 0], ["circle", [0,0], 3.4], 2, 2, [157.48, 154.94]],
];The shelf is a monolith with the panel: a deck perpendicular to the
panel, extending into the rack. shelf_z_offset positions the deck's
bottom plane relative to the panel's bottom edge.
Wall types (wall_type):
none— bare deck.rect— vertical walls of constant heightwall_height.slant— vertical side walls whose top edge slopes along the depth:wall_heightat the panel, descending towall_end_heightat the deck's far end (0 = all the way down).
The back wall is always vertical (90° to the deck). In slant
mode its height equals wall_end_height (so it lines up with the
sloped side walls; with 0 it is omitted), in rect mode it is
wall_height. Walls taller than the panel are clipped to the panel's
top edge (with an INFO message).
The shelf width is limited by the panel width (250 mm), not by the 222 mm clearance between the rack posts — this allows wide boards to sit directly on the deck. The trade-off: a wide shelf, its walls, ribs or gussets can end up right behind some of the panel's mounting slots.
The model resolves this automatically: shelf_panel_keepouts()
computes the exact shelf-to-panel contact zones, and panel() skips
every mounting slot that collides with one (with a 0.5 mm material
margin), emitting an INFO message per skipped hole. Slots above and
below an obstacle are still cut. Combined with panel_top_trim this
lets you design panels with exactly the rows of mounting holes you can
actually reach.
If you call panel() directly (as the examples do), pass the keepouts
yourself:
panel(u = 2, cutouts = my_cutouts,
mount_keepouts = shelf_panel_keepouts(u = 2, width = 240,
thickness = 3, z_offset = 6,
wall_type = "slant",
wall_height = 15));Print orientation: panel face down on the bed, shelf growing upward — the exported STL is already oriented this way.
- The deck, side walls (
rectandslant, any slope), ribs and gussets print without supports: every layer is contained within the one below it. - The back wall is an overhang (a horizontal cantilever at the top
of the print) — the model emits a warning. Either disable it
(
wall_back = false), or print with a small local support. - Hole chamfers on the front face print as 45° rings directly on the bed — no bridging problems.
- Asserts guard the build volume: any part larger than 256 mm in X/Y/Z aborts the render with a clear message.
Suggested settings: PETG or PLA, 3–4 perimeters, no supports (except the back wall case above).
button.scad is a standalone generator for panel buttons (e.g. a power
button extender above a motherboard's tactile switch). The button goes
into the panel cutout from the back:
| Parameter | Default | Description |
|---|---|---|
button_shape |
circle |
circle or rect face. |
hole_d / hole_w, hole_h, hole_r |
12 / 14, 6.5, 1 | Dimensions of the panel cutout — the same numbers you used in panel_cutouts; the clearance is subtracted automatically. |
clearance |
0.25 | Face-to-cutout play per side [mm]. |
face_proud |
1.5 | How far the face protrudes in front of the panel [mm]. |
face_round |
0.8 | Rounding radius of the face edge [mm] (0 = sharp). |
panel_t |
3 | Panel thickness the body passes through [mm]. |
flange_w / flange_t |
1.5 / 1.5 | Flange (retaining collar) width per side / thickness [mm]. |
pin_d / pin_l |
4 / 2 | Central pin diameter / length behind the flange [mm]. Measure pin_l on the assembled unit: distance from the panel's back to the switch, minus ~0.5 mm so the button does not hold the switch pressed. |
Print face-down (as generated): the face comes out smooth with a clean rounded edge. The flange is a plain overhang — use small supports under it. For a backlit power button print in transparent filament with 100% infill; the board's LED will glow through the whole button.
Each file in examples/ is self-contained — open, render,
export:
| File | Contents |
|---|---|
blank_panel_1u.scad |
1U blank panel — fit-test print for hole_clearance tuning. |
keystone_panel_1u.scad |
1U keystone patch panel: 6 snap-in ports (RJ45/USB/HDMI couplers), printed 2 mm thick so the keystone latch engages. |
cable_panel_1u.scad |
1U cable pass-through: wide chamfered slot + zip-tie anchor slots for strain relief. |
vent_panel_2u.scad |
2U vent panel: 2× 80 mm fan + hex-grid column. |
rpi5_shelf_2u.scad |
1U Raspberry Pi 5 shelf: slant walls, gussets, rpi5 preset, vent slots. |
pi_cluster_2u.scad |
2U dual Raspberry Pi 5 cluster shelf: boards side by side, ports facing front, per-board port windows in the panel, rotated rpi5 pattern. |
ssd_shelf_2u.scad |
2U shelf for two 2.5" drives: SFF bottom-mount M3 pattern, vent slots under the drives and in the panel. |
tray_3u.scad |
Deep 3U tray: rect side walls, ribs, gussets, M3 grid, cable slot in the panel. |
mini_itx_3u.scad |
3U Mini-ITX shelf: M3 standoff pattern, ATX I/O window in the panel, ventilation. Demonstrates deriving cutout positions from a few measured variables. |
The examples show the recommended pattern for real builds: put your measured dimensions in named variables and derive the cutout positions from them, so a single correction shifts everything consistently.
- Print
examples/blank_panel_1u.scad. - Check the fit in your rack: M6 screws through the slots into the post inserts.
- Too tight / too loose? Adjust
hole_clearanceinconfig.scad(affects every screw hole in the project) and reprint.
Defined in config.scad. Do not change — they encode Lab Rax
compatibility:
| Constant | Value | Meaning |
|---|---|---|
RACK_U |
44.45 mm | Height of one rack unit. |
MOUNT_HOLE_SPACING_X |
236.525 mm | Horizontal center-to-center mounting spacing. |
RACK_CLEARANCE_W |
222 mm | Clear width between the rack posts (informational). |
MOUNT_HOLE_D |
6 mm | M6 mounting screws (clearance added on top). |
HOLE_PATTERN_1U |
6.35 / 22.225 / 38.1 mm | Hole positions within 1U, from the U's bottom edge. |
PANEL_W |
250 mm | Total panel width including ears (project decision, fits a 256 mm bed). |
Licensed under Creative Commons Attribution 4.0 International (CC BY 4.0) — see LICENSE. You are free to share and adapt this work, including commercially, as long as you give appropriate credit.
Compatible with the Lab Rax rack system by Michael Klements (The DIY Life). This model is an independent parametric implementation; verify all dimensions against your own rack before committing to long prints.









