3d-printing/shower-phone-holder/holder.scad
Felipe M. 8489c57949
Update print notes to reflect flexible print orientation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 16:45:26 +01:00

234 lines
8.3 KiB
OpenSCAD

// Shower Phone Holder
// Hangs from an aluminum shower rail via two hooks at the top,
// with a pocket/tray at the bottom to hold a phone.
//
// Print suggestions:
// Print orientation: flat on one side.
// Hook will need support on the side that is not flat on the bed.
// Depending on the back plate design you may need supports as well.
//
// Coordinate system:
// Origin (0,0,0) = bottom-left corner of back plate, rear face.
// X = left to right (width)
// Y = bottom to top (height)
// Z = back to front — hooks go -Z, pocket goes +Z
/* [Phone] */
phone_width = 165; // mm (landscape)
phone_height = 80; // mm (landscape)
phone_thickness = 12; // mm (with case)
/* [Rail] */
rail_width = 25; // mm (measure your rail!)
rail_thickness = 10; // mm (depth of the rail)
/* [Tolerances] */
rail_tolerance = 1.0; // mm clearance around rail
phone_tolerance = 2.0; // mm clearance around phone
/* [Structure] */
wall = 3; // mm general wall thickness
back_thickness = 3; // mm back plate thickness
/* [Hook] */
hook_width = 25; // mm width of each hook
hook_drop = 15; // mm how far back leg drops behind rail
/* [Pocket] */
pocket_depth = 25; // mm how far pocket extends forward
pocket_lip = 15; // mm front wall height
pocket_floor = 3; // mm floor thickness
/* [Drain Holes] */
drain_radius_top = 3; // mm radius at pocket interior (wide end)
drain_radius_bottom = 1.5; // mm radius at pocket exterior (narrow end)
drain_count = 5; // number of drain holes across pocket width
/* [Corners] */
corner_radius = 5; // mm radius for rounded corners
corner_fn = 40; // segments per rounded arc
/* [Back Plate Style] */
back_style = "open"; // "solid", "hex", or "open" (open = single hole with diagonal brace)
hex_radius = 5; // mm circumradius of each hexagon (hex style only)
hex_wall = 2; // mm wall thickness between adjacent hexagons (hex style only)
hex_margin = 5; // mm inset from plate edges / features
/* [Open Style] */
open_border_left = 10; // mm left border width (open style only)
open_border_right = 10; // mm right border width (open style only)
open_diagonal_width = 10; // mm width of diagonal brace (open style only)
// --- Derived dimensions ---
plate_width = phone_width + phone_tolerance + 2 * wall;
hook_rise = rail_thickness + rail_tolerance + wall; // height hooks need above phone area
plate_height = pocket_lip + phone_height + hook_rise;
module round_convex(r) {
offset(r = r, $fn = corner_fn)
offset(delta = -r)
children();
}
module center_opening(w, h, brace_w) {
diag_len = sqrt(w * w + h * h);
diag_angle = atan2(h, w);
difference() {
square([w, h]);
// Diagonal brace from bottom-left to top-right (solid)
translate([w / 2, h / 2])
rotate([0, 0, diag_angle])
translate([-diag_len / 2, -brace_w / 2])
square([diag_len, brace_w]);
}
}
module hex_grid(width, height) {
pitch_x = hex_radius * sqrt(3) + hex_wall;
pitch_y = pitch_x * sqrt(3) / 2;
cols = floor(width / pitch_x);
rows = floor(height / pitch_y);
for (row = [0 : rows]) {
x_offset = (row % 2 == 0) ? 0 : pitch_x / 2;
for (col = [0 : cols]) {
x = col * pitch_x + x_offset;
y = row * pitch_y;
if (x >= 0 && x <= width && y >= 0 && y <= height)
translate([x, y, -1])
linear_extrude(back_thickness + 2)
rotate([0, 0, 30])
circle(r = hex_radius, $fn = 6);
}
}
}
module back_plate() {
difference() {
linear_extrude(back_thickness)
intersection() {
square([plate_width, plate_height]);
round_convex(corner_radius)
square([plate_width, plate_height + corner_radius]);
}
if (back_style == "hex") {
safe_x = hex_margin;
safe_y = pocket_lip + hex_margin;
safe_w = plate_width - 2 * hex_margin;
hex_pitch_y = (hex_radius * sqrt(3) + hex_wall) * sqrt(3) / 2;
safe_h = plate_height - pocket_lip - 2 * hex_margin - hex_pitch_y;
translate([safe_x, safe_y, 0])
hex_grid(safe_w, safe_h);
}
if (back_style == "open") {
safe_x = open_border_left;
safe_y = pocket_lip + hex_margin;
safe_w = plate_width - open_border_left - open_border_right;
hex_pitch_y = (hex_radius * sqrt(3) + hex_wall) * sqrt(3) / 2;
safe_h = plate_height - pocket_lip - 2 * hex_margin - hex_pitch_y;
translate([safe_x, safe_y, -1])
linear_extrude(back_thickness + 2)
center_opening(safe_w, safe_h, open_diagonal_width);
}
}
}
// Single hook — L-shaped profile extruded along hook width.
// Outer convex corners are rounded; the inner elbow stays sharp (concave).
module hook() {
rail_slot_width = rail_width + rail_tolerance;
bridge_depth = rail_slot_width + wall;
// Clamp radius so it fits within the wall thickness
hook_r = min(corner_radius, wall / 2 - 0.1);
// L-profile in a depth (X) / height (Y) coordinate frame.
// Negative X = toward plate; positive X = away from plate.
// The profile extends past the plate face by hook_r so the
// junction is seamless (extra material hides inside the plate union).
profile = [
[-hook_r, 0], // plate junction (extended)
[-hook_r, wall], // plate junction top
[bridge_depth, wall], // outer top of bridge
[bridge_depth, -hook_drop], // outer bottom of leg
[bridge_depth - wall, -hook_drop], // inner bottom of leg
[bridge_depth - wall, 0] // inner elbow (concave, stays sharp)
];
// Extrude along X (hook_width), then rotate so:
// polygon X (depth) → -Z, polygon Y (height) → Y, extrude Z → X
rotate([0, 90, 0])
linear_extrude(hook_width)
round_convex(hook_r)
polygon(profile);
}
module drain_holes() {
inner_width = plate_width - 2 * wall;
spacing = inner_width / (drain_count + 1);
inner_depth = pocket_depth - wall;
z_row1 = inner_depth / 3;
z_row2 = inner_depth * 2 / 3;
// Front row
for (i = [1 : drain_count]) {
translate([wall + i * spacing, pocket_floor + 1, z_row1])
rotate([90, 0, 0])
cylinder(h = pocket_floor + 2, r1 = drain_radius_top, r2 = drain_radius_bottom, $fn = 24);
}
// Back row — offset by half spacing for staggered coverage
for (i = [0 : drain_count]) {
x = wall + i * spacing + spacing / 2;
if (x > wall + drain_radius_top && x < plate_width - wall - drain_radius_top)
translate([x, pocket_floor + 1, z_row2])
rotate([90, 0, 0])
cylinder(h = pocket_floor + 2, r1 = drain_radius_top, r2 = drain_radius_bottom, $fn = 24);
}
}
module pocket() {
difference() {
// Outer solid — bottom corners rounded, top edge straight
linear_extrude(pocket_depth)
intersection() {
square([plate_width, pocket_lip]);
round_convex(corner_radius)
square([plate_width, pocket_lip + corner_radius]);
}
// Inner cutout — bottom corners rounded, open top and back (back plate provides the back wall)
translate([wall, pocket_floor, -1])
linear_extrude(pocket_depth - wall + 1)
intersection() {
square([plate_width - 2 * wall, pocket_lip - pocket_floor + 1]);
round_convex(corner_radius)
square([plate_width - 2 * wall, pocket_lip - pocket_floor + 1 + corner_radius]);
}
// Conical drainage holes through pocket floor
drain_holes();
}
}
module holder() {
// Back plate at origin
back_plate();
// Pocket on the front face of the plate, at the bottom
translate([0, 0, back_thickness])
pocket();
// Two hooks at the top of the plate, flush with sides
hook_y = plate_height - wall; // align hook bridge with plate top
translate([0, hook_y, 0])
hook();
translate([plate_width - hook_width, hook_y, 0])
hook();
}
holder();