234 lines
8.3 KiB
OpenSCAD
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();
|