3d-printing/blind-cap/cap.scad
Felipe M. 2767709809
Replace hand-built keystone cradle with marcuswu RJ45 receiver
Drops all the custom slot/latch-shelf/face-stop/base-notch parameters
and modules in favour of the imported rj45Receiver() component. The
receiver sits flat behind the front face with its slot opening at
cap z=0 (after a 180° flip around Y) and its body extending into the
cavity / wall box. Validation now uses rj45_width × rj45_height as the
in-plane footprint. Also bumps screw_countersink_diameter from 5.5 mm
to 6.6 mm.
2026-04-27 08:25:01 +02:00

272 lines
11 KiB
OpenSCAD
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ============================================
// Blind Cap (Tapa Ciega)
// ============================================
// A blank cover plate for a flush-mounted electrical wall box.
// Square plate with rounded corners, hollow back, and two
// internal screw bosses on the vertical centerline.
use <../components/rj45_keystone.scad>;
// ============================================
// CUSTOMIZABLE PARAMETERS
// ============================================
// Outer width and height of the cap (square)
outer_size = 80; // [50:1:120]
// Radius of the rounded outer corners
corner_radius = 5; // [1:0.5:15]
// Total depth of the cap (front face to back rim)
total_depth = 3; // [5:0.5:20]
// Thickness of the front face
front_thickness = 2; // [1:0.1:5]
// Thickness of the side walls (the rim around the cavity)
wall_thickness = 1; // [1:0.1:5]
// Center-to-center distance between the two screw holes
screw_hole_spacing = 60; // [30:1:100]
// Through-hole diameter for the mounting screw shank
screw_hole_diameter = 3; // [2:0.1:6]
// Countersink diameter on the front face for the screw head
screw_countersink_diameter = 6.6; // [4:0.1:12]
// Countersink depth on the front face
screw_countersink_depth = 1.2; // [0:0.1:3]
// Diameter of the internal screw bosses
boss_diameter = 9; // [5:0.5:15]
// Front-face edge rounding (slight chamfer on the visible front edge)
front_edge_round = 1.5; // [0:0.1:3]
// Back-rim edge rounding (slight chamfer on the rear outer edge)
back_edge_round = 0; // [0:0.1:3]
// ============================================
// KEYSTONE JACK CRADLE
// ============================================
// The cradle behind the front-face cutout is the marcuswu RJ45
// keystone receiver, imported from ../components/rj45_keystone.scad.
// Its slot, latch shelf, fulcrum/clip bars, and insertion ramp are
// all defined by that module — we just position it behind the front
// face and cut a hole in the front face for the keystone face to
// show through.
//
// The receiver's Y axis (insertion direction) is rotated to align
// with the cap's Z axis (depth). The keystone slides in face-first
// from the cavity side and clips into the receiver's built-in
// latch-catch.
// Add the keystone receiver behind the front-face cutout
keystone_enabled = true; // [false, true]
// Front-face cutout — sized to the visible portion of the keystone face.
keystone_face_width = 14.5; // [10:0.1:25]
keystone_face_length = 16.0; // [10:0.1:25]
// Offset of the front-face cutout along the receiver's long axis (Y in
// cradle local). The receiver's slot is taller than the visible face,
// so the cutout typically needs to sit a few mm off-centre to align
// with where the face actually shows.
keystone_face_offset_y = 1.2; // [-6:0.1:6]
// Print-tolerance clearance added on each side of the front-face cutout
keystone_clearance = 0.2; // [0:0.05:1]
// Rotation of the keystone (receiver + front cutout) about the cap's
// Z axis, in 90° steps. 0° puts the receiver's long axis along Y;
// 90° puts it along X.
keystone_rotation = 90; // [0, 90, 180, 270]
// Translate the whole keystone (receiver + cutout) away from the cap's
// centre.
keystone_position_x = 15; // [-40:0.5:40]
keystone_position_y = 0; // [-40:0.5:40]
// ============================================
// RENDERING QUALITY
// ============================================
$fn = 64;
// ============================================
// PARAMETER VALIDATION
// ============================================
assert(outer_size > 2 * corner_radius, "Outer size must be larger than twice the corner radius");
assert(total_depth > front_thickness, "Total depth must be greater than front thickness");
assert(boss_diameter > screw_hole_diameter, "Boss diameter must exceed screw hole diameter");
assert(screw_countersink_diameter >= screw_hole_diameter, "Countersink must be at least as wide as the screw hole");
assert(screw_hole_spacing + boss_diameter < outer_size - 2 * wall_thickness, "Screw bosses must fit inside the cavity");
assert(front_edge_round < front_thickness, "Front edge round must be smaller than front thickness");
assert(back_edge_round < total_depth - front_thickness, "Back edge round must be smaller than the cavity depth");
assert(front_edge_round + back_edge_round < total_depth, "Combined edge rounds must not exceed total depth");
// Keystone-specific validation. The receiver sits flat behind the
// front face, so its X × Y footprint is rj45_width × rj45_height
// (with rj45_depth becoming the cap-Z protrusion). At 90°/270° the
// X and Y swap in the cap frame, which changes which screw boss the
// receiver might hit.
KS_OUTER_X = rj45_width();
KS_OUTER_Y = rj45_height();
KS_ROT_SWAP = (keystone_rotation == 90 || keystone_rotation == 270);
KS_EFF_X = KS_ROT_SWAP ? KS_OUTER_Y : KS_OUTER_X;
KS_EFF_Y = KS_ROT_SWAP ? KS_OUTER_X : KS_OUTER_Y;
KS_X_MIN = keystone_position_x - KS_EFF_X / 2;
KS_X_MAX = keystone_position_x + KS_EFF_X / 2;
KS_Y_MIN = keystone_position_y - KS_EFF_Y / 2;
KS_Y_MAX = keystone_position_y + KS_EFF_Y / 2;
// Helper: do AABBs [a1,a2] and [b1,b2] overlap on one axis?
function _ks_overlap(a1, a2, b1, b2) = !(a2 < b1 || a1 > b2);
// Each screw boss's AABB in cap coords (square inscribing the circle).
KS_BOSS_R = boss_diameter / 2;
KS_TOP_Y = screw_hole_spacing / 2;
KS_BOT_Y = -screw_hole_spacing / 2;
KS_HITS_TOP = _ks_overlap(KS_X_MIN, KS_X_MAX, -KS_BOSS_R, KS_BOSS_R)
&& _ks_overlap(KS_Y_MIN, KS_Y_MAX, KS_TOP_Y - KS_BOSS_R, KS_TOP_Y + KS_BOSS_R);
KS_HITS_BOT = _ks_overlap(KS_X_MIN, KS_X_MAX, -KS_BOSS_R, KS_BOSS_R)
&& _ks_overlap(KS_Y_MIN, KS_Y_MAX, KS_BOT_Y - KS_BOSS_R, KS_BOT_Y + KS_BOSS_R);
assert(keystone_rotation == 0 || keystone_rotation == 90
|| keystone_rotation == 180 || keystone_rotation == 270,
"keystone_rotation must be 0, 90, 180, or 270");
assert(!keystone_enabled
|| (KS_X_MIN >= -(outer_size / 2 - wall_thickness)
&& KS_X_MAX <= (outer_size / 2 - wall_thickness)),
"Keystone receiver (with rotation+offset) extends beyond the cap's inner cavity along X");
assert(!keystone_enabled
|| (KS_Y_MIN >= -(outer_size / 2 - wall_thickness)
&& KS_Y_MAX <= (outer_size / 2 - wall_thickness)),
"Keystone receiver (with rotation+offset) extends beyond the cap's inner cavity along Y");
assert(!keystone_enabled || !KS_HITS_TOP,
"Keystone receiver (with rotation+offset) would collide with the top screw boss");
assert(!keystone_enabled || !KS_HITS_BOT,
"Keystone receiver (with rotation+offset) would collide with the bottom screw boss");
// ============================================
// MAIN ASSEMBLY
// ============================================
difference() {
union() {
// Outer shell with the back cavity already removed
difference() {
outer_shell();
// Hollow out the back cavity
translate([0, 0, front_thickness])
rounded_prism(
size = outer_size - 2 * wall_thickness,
radius = max(corner_radius - wall_thickness, 0.5),
height = total_depth - front_thickness + 1
);
}
// Internal screw bosses (rise from the inside of the front face)
for (y = [-screw_hole_spacing / 2, screw_hole_spacing / 2])
translate([0, y, front_thickness])
cylinder(h = total_depth - front_thickness, d = boss_diameter);
// Keystone receiver — imported from components/rj45_keystone.scad.
// Sits flat behind the front face: receiver Z=0 (its slot
// opening) is at cap z=0, so the keystone is inserted face-
// first through the front-face cutout into the receiver. The
// body extends in cap +Z into the cavity / wall box. Centred
// in cap X/Y at (keystone_position_x, keystone_position_y) and
// rotated about cap Z by keystone_rotation.
if (keystone_enabled)
translate([keystone_position_x, keystone_position_y, 0])
rotate([0, 0, keystone_rotation])
translate([rj45_width() / 2, (-rj45_height() / 2)+2, rj45_depth()])
rotate([0, 180, 0])
rj45Receiver();
}
// Through-holes for the mounting screws
for (y = [-screw_hole_spacing / 2, screw_hole_spacing / 2])
translate([0, y, -1])
cylinder(h = total_depth + 2, d = screw_hole_diameter);
// Countersinks on the front face for the screw heads
if (screw_countersink_depth > 0)
for (y = [-screw_hole_spacing / 2, screw_hole_spacing / 2])
translate([0, y, -0.01])
cylinder(
h = screw_countersink_depth + 0.01,
d1 = screw_countersink_diameter,
d2 = screw_hole_diameter
);
// Keystone front-face cutout — sized to the visible portion of the
// keystone face only. Punches through the cap's front face so the
// keystone face inside the receiver is visible from outside. The
// face flange behind the cutout seats against the back of the
// front-face material. Rotated and translated together with the
// receiver so they stay aligned.
if (keystone_enabled) {
cw = keystone_face_width + 2 * keystone_clearance;
cl = keystone_face_length + 2 * keystone_clearance;
translate([keystone_position_x, keystone_position_y, 0])
rotate([0, 0, keystone_rotation])
translate([-cw / 2, -cl / 2 + keystone_face_offset_y, -1])
cube([cw, cl, front_thickness + 2]);
}
}
// ============================================
// COMPONENT MODULES
// ============================================
// Outer shell with softly tapered front and back outer edges.
// Front face sits at z = 0 (build plate), back rim at z = total_depth.
module outer_shell() {
if (front_edge_round > 0 || back_edge_round > 0) {
hull() {
// Front face (inset) — only when front edge is rounded
if (front_edge_round > 0)
translate([0, 0, 0])
rounded_prism(
size = outer_size - 2 * front_edge_round,
radius = max(corner_radius - front_edge_round, 0.1),
height = 0.01
);
// Full-size middle section
translate([0, 0, front_edge_round])
rounded_prism(
size = outer_size,
radius = corner_radius,
height = total_depth - front_edge_round - back_edge_round
);
// Back rim (inset) — only when back edge is rounded
if (back_edge_round > 0)
translate([0, 0, total_depth - 0.01])
rounded_prism(
size = outer_size - 2 * back_edge_round,
radius = max(corner_radius - back_edge_round, 0.1),
height = 0.01
);
}
} else {
rounded_prism(outer_size, corner_radius, total_depth);
}
}
// Square prism with rounded corners, centered on the X/Y origin.
module rounded_prism(size, radius, height) {
hull()
for (x = [-size / 2 + radius, size / 2 - radius])
for (y = [-size / 2 + radius, size / 2 - radius])
translate([x, y, 0])
cylinder(h = height, r = radius);
}