Bedside clip-on shelf tray for bed. Customizable
by Mach3 ·
Fully customizable. Change 'bed wall thickness' param to fit your bed wall. `total shelf length` 195mm is printed fine on A1 mini if positioned 45deg on the print bed (brims are recommended for better adhesion).
Interactive Preview & Customizer
3D preview with browser-based OpenSCAD rendering on parameter changes.
Total horizontal shelf length/depth
Width of the shelf along the bed wall (extrusion length)
Thickness of the bed headboard/wall
General wall thickness for structural strength
Thickness of the shelf plate and top connector
Depth of the hook clamp on both sides (making both walls the same height)
Chamfer size for the bottom tips of the clamp walls (flat-bottomed chamfer)
Toggle to enable chamfers in the inner corners of the hook slot
Thickness of the support brace
Horizontal extension of the brace under the shelf
Vertical extension of the brace on the middle wall
Chamfer size for the support brace angles
Toggle to enable the recessed tray (raised lip). Set to false for a flat top shelf.
Toggle to make the shelf outer corners chamfered (45 deg) or square (90 deg)
Chamfer size for the outer shelf corners
Width of the raised lip around the shelf
Depth of the recess (tray pocket)
Bevel/chamfer width for the sloped inner edge of the lip
Chamfer size for the shelf outer edge profile (flat-front chamfer)
Screenshots
Prompt
Create clip-on shelf tray for bed.
OpenSCAD Code
// Parametric Bed Shelf Design - v21
// Optimized for printing on its side without supports on a Bambu Lab A1 Mini (180x180x180mm)
/* [Main Dimensions] */
// Total horizontal shelf length/depth
total_shelf_length = 195; // [100:1:280]
// Width of the shelf along the bed wall (extrusion length)
shelf_width = 175; // [50:1:280]
// Thickness of the bed headboard/wall
bed_wall_thickness = 37; // [5:1:100]
// General wall thickness for structural strength
wall_thickness = 6; // [1:1:15]
// Thickness of the shelf plate and top connector
shelf_thickness = 8; // [1:1:15]
/* [Hook/Clamp Dimensions] */
// Depth of the hook clamp on both sides (making both walls the same height)
hook_depth = 65;
// Chamfer size for the bottom tips of the clamp walls (flat-bottomed chamfer)
wall_chamfer = 2;
// Toggle to enable chamfers in the inner corners of the hook slot
enable_hook_chamfer = false;
/* [Support Brace] */
// Thickness of the support brace
brace_thickness = 6;
// Horizontal extension of the brace under the shelf
brace_depth = 60;
// Vertical extension of the brace on the middle wall
brace_height = 45;
// Chamfer size for the support brace angles
brace_chamfer = 2;
/* [Tray/Recess Options] */
// Toggle to enable the recessed tray (raised lip). Set to false for a flat top shelf.
enable_tray = true;
// Toggle to make the shelf outer corners chamfered (45 deg) or square (90 deg)
chamfer_shelf_corners = true;
// Chamfer size for the outer shelf corners
corner_radius = 12;
// Width of the raised lip around the shelf
lip_width = 4;
// Depth of the recess (tray pocket)
recess_depth = 2;
// Bevel/chamfer width for the sloped inner edge of the lip
recess_bevel_width = 2;
// Chamfer size for the shelf outer edge profile (flat-front chamfer)
fillet_radius = 2;
/* [Calculated Parameters] */
hook_total_width = bed_wall_thickness + 2 * wall_thickness;
shelf_projection = total_shelf_length - hook_total_width;
module profile2D() {
// 1. Shelf plate with chamfered outer edge (flat-front chamfer)
polygon([
[0, 0],
[-shelf_projection + fillet_radius, 0],
[-shelf_projection, -fillet_radius],
[-shelf_projection, -shelf_thickness + fillet_radius],
[-shelf_projection + fillet_radius, -shelf_thickness],
[0, -shelf_thickness]
]);
// 2. Middle wall (extending up to Y = 0, with flat-bottomed chamfer)
polygon([
[0, 0],
[wall_thickness, 0],
[wall_thickness, -hook_depth + wall_chamfer],
[wall_thickness - wall_chamfer, -hook_depth],
[wall_chamfer, -hook_depth],
[0, -hook_depth + wall_chamfer]
]);
// 3. Top connector (thickness controlled by shelf_thickness)
translate([wall_thickness, -shelf_thickness])
square([bed_wall_thickness, shelf_thickness]);
// 4. Inner flap (with flat-bottomed chamfer and top-right chamfer)
difference() {
polygon([
[hook_total_width - wall_thickness, 0],
[hook_total_width, 0],
[hook_total_width, -hook_depth + wall_chamfer],
[hook_total_width - wall_chamfer, -hook_depth],
[hook_total_width - wall_thickness + wall_chamfer, -hook_depth],
[hook_total_width - wall_thickness, -hook_depth + wall_chamfer]
]);
// Subtract top-right corner chamfer (3mm)
translate([hook_total_width, 0])
polygon([
[0, 0],
[-3, 0],
[0, -3]
]);
}
// 5. Support brace (gusset) - clean diagonal bar with chamfered inner corners
theta = atan((brace_height - shelf_thickness) / brace_depth);
// Chamfer points
V1_shelf = [-brace_depth + brace_chamfer, -shelf_thickness];
V1_diag = [-brace_depth + brace_chamfer * cos(theta), -shelf_thickness - brace_chamfer * sin(theta)];
V2_diag = [-brace_chamfer * cos(theta), -brace_height + brace_chamfer * sin(theta)];
V2_wall = [0, -brace_height + brace_chamfer];
V3 = [0, -brace_height - brace_thickness / cos(theta)];
V4 = [-brace_depth - brace_thickness / sin(theta), -shelf_thickness];
polygon([V1_shelf, V1_diag, V2_diag, V2_wall, V3, V4]);
// 6. Corner chamfer inside the support brace triangle (shelf bottom to middle wall)
polygon([
[0, -shelf_thickness],
[-brace_chamfer, -shelf_thickness],
[0, -shelf_thickness - brace_chamfer]
]);
// 7. Hook inner corner chamfers (optional)
if (enable_hook_chamfer) {
polygon([
[wall_thickness, -shelf_thickness],
[wall_thickness + brace_chamfer, -shelf_thickness],
[wall_thickness, -shelf_thickness - brace_chamfer]
]);
polygon([
[hook_total_width - wall_thickness, -shelf_thickness],
[hook_total_width - wall_thickness - brace_chamfer, -shelf_thickness],
[hook_total_width - wall_thickness, -shelf_thickness - brace_chamfer]
]);
}
}
module footprint2D() {
union() {
// Shelf footprint (chamfered or square)
if (chamfer_shelf_corners) {
polygon([
[0, 0],
[0, shelf_width],
[-shelf_projection + corner_radius, shelf_width],
[-shelf_projection, shelf_width - corner_radius],
[-shelf_projection, corner_radius],
[-shelf_projection + corner_radius, 0]
]);
} else {
translate([-shelf_projection, 0])
square([shelf_projection, shelf_width]);
}
// Hook footprint (straight rectangle)
translate([0, 0])
square([hook_total_width, shelf_width]);
}
}
module recess2D(offset_val = 0) {
x_min = -shelf_projection + lip_width;
x_max = hook_total_width; // Extended to the very back of the inner flap (no back lip)
z_min = lip_width;
z_max = shelf_width - lip_width;
c_size = corner_radius - lip_width;
// Shift coordinates by offset_val
x_min_offset = x_min + offset_val;
x_max_offset = x_max; // Open on bed side, no shift
z_min_offset = z_min + offset_val;
z_max_offset = z_max - offset_val;
// Scale the chamfer size based on offset geometry:
// c'_size = c_size - offset_val * (sqrt(2) - 1)
c_size_offset = c_size - offset_val * (sqrt(2) - 1);
if (chamfer_shelf_corners && c_size_offset > 0) {
polygon([
[x_max_offset, z_min_offset],
[x_max_offset, z_max_offset],
[x_min_offset + c_size_offset, z_max_offset],
[x_min_offset, z_max_offset - c_size_offset],
[x_min_offset, z_min_offset + c_size_offset],
[x_min_offset + c_size_offset, z_min_offset]
]);
} else {
polygon([
[x_max_offset, z_min_offset],
[x_max_offset, z_max_offset],
[x_min_offset, z_max_offset],
[x_min_offset, z_min_offset]
]);
}
}
module beveled_recess3D() {
// We create a beveled 3D shape by taking the convex hull of two offset 2D slices at different heights.
hull() {
// Top slice (larger, slightly above Y=0 to avoid z-fighting)
rotate([90, 0, 0])
translate([0, 0, -0.1])
linear_extrude(height = 0.1) {
recess2D(0);
}
// Bottom slice (smaller, offset inwards by recess_bevel_width at Y = -recess_depth)
rotate([90, 0, 0])
translate([0, 0, recess_depth])
linear_extrude(height = 0.1) {
recess2D(recess_bevel_width);
}
}
}
module bed_shelf() {
difference() {
// Solid body: intersection of profile and footprint
intersection() {
// Extrude profile in Z
linear_extrude(height = shelf_width) {
profile2D();
}
// Extrude footprint in Y and rotate to align
rotate([90, 0, 0])
linear_extrude(height = 2 * hook_depth, center = true) {
footprint2D();
}
}
// Recessed tray subtraction (with sloped inner walls)
if (enable_tray) {
beveled_recess3D();
}
}
}
// Center the shelf in XY and rotate it by 55 degrees around Z
// to maximize print area usage on the Bambu Lab A1 Mini (180x180x180mm).
// 55 degrees is the mathematically optimal angle for this asymmetric geometry,
// minimizing the bounding box and leaving equal clearance on both sides.
rotate([0, 0, 55])
translate([-(-shelf_projection + hook_total_width)/2, hook_depth/2, 0])
bed_shelf();