// ============================================ // 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); }