feat/blind-cap #1

Merged
fmartingr merged 7 commits from feat/blind-cap into main 2026-04-26 10:34:11 +02:00
24 changed files with 34066 additions and 0 deletions

View file

@ -4,6 +4,8 @@ A collection of parametric OpenSCAD models for 3D printing.
| Preview | Name | Description |
|---------|------|-------------|
| <img src="blind-cap/preview.png" width="200"> | [Blind Cap](blind-cap/) | A blank cover plate for a flush-mounted electrical wall box, with internal screw bosses on the vertical centerline. |
| <img src="filament-spool-cover/preview.png" width="200"> | [Filament Spool Cover](filament-spool-cover/) | A circular dust cover that clips onto the outer rim of a filament spool. |
| <img src="gridfinity-bin-label/preview.png" width="200"> | [Gridfinity Bin Label](gridfinity-bin-label/) | A swappable Cullenect-style bin label with two text slots and two screw-head icon slots. |
| <img src="shower-phone-holder/preview.png" width="200"> | [Shower Phone Holder](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. |
| <img src="simple-hook/preview.png" width="200"> | [Parametrizable Wall Hook](simple-hook/) | A simple wall hook with a rectangular base plate and a cylindrical peg extending outward. |

61
blind-cap/README.md Normal file
View file

@ -0,0 +1,61 @@
# Blind Cap
![Preview](preview.png)
A blank cover plate (tapa ciega) for a flush-mounted electrical wall box. Square plate with rounded corners, a hollow back cavity, and two internal screw bosses on the vertical centerline. Designed for 3D printing — the front face sits on the build plate so the cavity opens upward and prints without supports.
## Parameters
### Customizable Parameters
| Parameter | Default | Description |
|-----------|---------|-------------|
| `outer_size` | 85 mm | Outer width and height of the cap (square) |
| `corner_radius` | 5 mm | Radius of the rounded outer corners |
| `total_depth` | 10 mm | Total depth of the cap (front face to back rim) |
| `front_thickness` | 2.5 mm | Thickness of the front face |
| `wall_thickness` | 2 mm | Thickness of the side walls |
| `screw_hole_spacing` | 60 mm | Center-to-center distance between the two screw holes |
| `screw_hole_diameter` | 3.5 mm | Through-hole diameter for the screw shank |
| `screw_countersink_diameter` | 7 mm | Countersink diameter on the front face |
| `screw_countersink_depth` | 1.2 mm | Countersink depth on the front face |
| `boss_diameter` | 9 mm | Diameter of the internal screw bosses |
| `front_edge_round` | 0.8 mm | Slight rounding applied to the visible front edge |
| `back_edge_round` | 1 mm | Slight rounding applied to the rear outer edge |
### Keystone Jack Cradle
Optional internal cradle that snap-locks a standard keystone module (RJ45, HDMI, USB, etc.) centered in the front face. Every dimension is configurable, so the cradle can be tuned to whatever keystone you have on hand.
How it works:
- A separate front-face cutout (sized to the *visible* portion of the keystone face) frames the module from outside the cap.
- A larger cradle slot behind the cutout captures the keystone body. The cap's front face material around the smaller cutout stops the face flange and hides any gap.
- A latch-catch shelf protrudes inward from one long-axis wall at the back of the cradle. The cantilever latch hook compresses past this shelf during insertion, then springs out behind it and locks the keystone in place. (The shelf and slot proportions follow WALLY's `keystone_solid` receiver from thenewhobbyist/OpenSCAD.)
- The cradle protrudes from the back of the front face into the cap's cavity and on into the wall box behind it. The external front face of the cap is unchanged.
To install: push the keystone face-first into the cavity-side opening of the slot until the face flange seats against the back of the front face.
If your keystone doesn't fit, increase `keystone_slot_width` / `keystone_slot_length` / `keystone_clearance`. If the latch ends up on the wrong side, flip `keystone_latch_side`. If the latch doesn't lock, increase `keystone_latch_shelf_protrusion` (or its thickness).
| Parameter | Default | Description |
|-----------|---------|-------------|
| `keystone_enabled` | `true` | Add the keystone cradle behind the front-face cutout |
| `keystone_face_width` | 14.5 mm | Front-face cutout width (visible face — short axis) |
| `keystone_face_length` | 16.0 mm | Front-face cutout length (visible face — long latch-fulcrum axis) |
| `keystone_face_offset_y` | 2.5 mm | Offset of the front-face cutout along Y, away from the latch shelf, so the cradle keeps material above the cutout for the latch to flex into |
| `keystone_slot_width` | 15.6 mm | Cradle slot width (body cross-section, with hooks) |
| `keystone_slot_length` | 23.5 mm | Cradle slot length (body cross-section, with hooks) |
| `keystone_cradle_depth` | 10 mm | How far the cradle extends behind the front face |
| `keystone_latch_shelf_protrusion` | 1.5 mm | How far the latch shelf protrudes into the slot |
| `keystone_latch_shelf_thickness` | 3 mm | Thickness of the latch shelf in z |
| `keystone_latch_side` | -1 | Which long-axis end has the shelf (-1 = -Y, 1 = +Y) |
| `keystone_rotation` | 0 | Rotation of the keystone about the cap's Z axis, in 90° steps (0/90/180/270) |
| `keystone_position_x` | 0 mm | Translate the keystone along X relative to the cap centre |
| `keystone_position_y` | 0 mm | Translate the keystone along Y relative to the cap centre |
| `keystone_clearance` | 0.2 mm | Print-tolerance clearance on each side of every cutout |
| `keystone_cradle_wall` | 1.6 mm | Wall thickness around the slot |
### Defaults
The default values target a common 85×85 mm Spanish flush-box cover with a 60 mm vertical screw spacing. Adjust `outer_size` and `screw_hole_spacing` to match your specific wall box.

360
blind-cap/cap.scad Normal file
View file

@ -0,0 +1,360 @@
// ============================================
// 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.
// ============================================
// 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 = 5.5; // [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
// ============================================
// Snap-in keystone modules (RJ45, HDMI, USB, etc.) lock into a panel
// using a flexible cantilever latch on one long-axis end of the body.
// We give it something real to lock against by building a cradle on
// the back of the front face: a rectangular slot that captures the
// keystone body, with a latch-catch shelf and angled insertion ramp
// at the back of the slot the latch hook compresses past the ramp
// during insertion, then springs out behind the shelf and locks the
// module in place. The shelf and ramp shapes are taken from the WALLY
// wall-plate customizer's `keystone_solid` receiver (thenewhobbyist/
// OpenSCAD).
//
// The cap's front face has a separate, smaller cutout sized to the
// visible portion of the keystone face. The keystone is loaded from
// behind: push it face-first into the cavity-side opening of the
// cradle until the face flange stops against the back of the front
// face. From the outside, only the keystone face is visible through
// the cutout the wider slot behind it is hidden.
//
// Every slot/shelf dimension is configurable so you can tune it to
// the specific keystone you have. If your keystone is too tight to
// insert, increase keystone_slot_width / keystone_slot_length /
// keystone_clearance. If the latch doesn't engage, adjust
// keystone_latch_shelf_protrusion / keystone_latch_shelf_thickness.
// Add a centered keystone cradle behind the front-face cutout
keystone_enabled = true; // [false, true]
// Front-face cutout sized to the visible portion of the keystone face.
// Make this match what you actually want to see through the cap.
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 Y, *relative to the cradle
// centre*. Most keystones have the visible face sitting on the lower
// portion of the body with the latch occupying the upper portion, so
// the cutout needs to sit a few mm off-centre toward the non-latch
// side. Negative values shift the cutout toward -Y; positive toward +Y.
// The default biases the cutout away from the latch shelf.
keystone_face_offset_y = 1; // [-6:0.1:6]
// Cradle slot sized to the keystone body cross-section (with hooks).
// Larger than the front-face cutout so the face flange seats against
// the back of the front face and hides any gap.
keystone_slot_width = 15.6; // [10:0.1:30]
keystone_slot_length = 23.5; // [10:0.1:35]
// How far the cradle extends behind the front face (into the cavity
// and beyond into the wall box). The latch shelf sits at this depth.
keystone_cradle_depth = 10; // [4:0.5:25]
// Latch-catch shelf protrudes inward from one long-axis wall at the
// back of the slot. The cantilever latch hook locks behind it.
keystone_latch_shelf_protrusion = 1.5; // [0:0.1:6]
keystone_latch_shelf_thickness = 3; // [1:0.1:6]
// Which long-axis end has the latch-catch shelf, so it lines up with
// the cantilever-latch side of *your* keystone. Change this if the
// shelf ends up on the wrong side after a print.
// 1 = shelf on +Y end of the slot
// -1 = shelf on -Y end of the slot
keystone_latch_side = -1; // [-1, 1]
// Rotation of the keystone (cradle + cutout) about the cap's Z axis,
// in 90° steps. 0° puts the long axis along Y; 90° puts it along X.
keystone_rotation = 270; // [0, 90, 180, 270]
// Translate the whole keystone (cradle + cutout) away from the cap's
// centre. Useful when the keystone shouldn't sit dead-centre.
keystone_position_x = 15; // [-40:0.5:40]
keystone_position_y = 0; // [-40:0.5:40]
// Print-tolerance clearance added on each side of every cutout
keystone_clearance = 0.2; // [0:0.05:1]
// Cradle wall thickness around the slot
keystone_cradle_wall = 1.6; // [1:0.1:4]
// ============================================
// 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. Bounding box dimensions take rotation
// into account: at 90°/270° the cradle's local X and Y are swapped in
// the cap frame, which changes which screw boss it might hit.
KS_OUTER_X = keystone_slot_width + 2 * keystone_cradle_wall;
KS_OUTER_Y = keystone_slot_length + 2 * keystone_cradle_wall;
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;
// Cradle bounding box in cap coords after rotation + translation.
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_enabled || keystone_face_width <= keystone_slot_width,
"Keystone front-face cutout (width) must be ≤ the slot width");
assert(!keystone_enabled || keystone_face_length <= keystone_slot_length,
"Keystone front-face cutout (length) must be ≤ the slot length");
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 cradle (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 cradle (with rotation+offset) extends beyond the cap's inner cavity along Y");
assert(!keystone_enabled || !KS_HITS_TOP,
"Keystone cradle (with rotation+offset) would collide with the top screw boss");
assert(!keystone_enabled || !KS_HITS_BOT,
"Keystone cradle (with rotation+offset) would collide with the bottom screw boss");
assert(!keystone_enabled
|| keystone_latch_shelf_protrusion < keystone_slot_length / 2,
"Latch shelf protrusion must leave room for the body in the slot");
// ============================================
// 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 cradle additive material on the back of the front face,
// built from the WALLY keystone_solid receiver geometry. Rotated
// and translated according to keystone_rotation / position_*.
if (keystone_enabled)
translate([keystone_position_x, keystone_position_y, 0])
rotate([0, 0, keystone_rotation])
keystone_cradle();
}
// 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. Smaller than the slot behind it, so the face
// flange seats against the back of the front face material around
// this cutout (no gaps visible from the outside). Offset along Y
// by keystone_face_offset_y so the cutout sits below/above slot
// centre to leave room for the latch on its side of the cradle.
// Rotated and translated together with the cradle so they stay
// aligned regardless of orientation/position.
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);
}
}
// Keystone cradle. Slot dimensions and the latch-catch shelf are all
// configurable so the cradle can be tuned to the keystone in hand. The
// shelf + insertion ramp pattern follows WALLY's `keystone_solid`
// receiver (thenewhobbyist/OpenSCAD), but we drop WALLY's face-stop
// shelf in this design the cap's front face material around the
// smaller `keystone_face_*` cutout already stops the face flange.
module keystone_cradle() {
sw = keystone_slot_width + 2 * keystone_clearance;
sl = keystone_slot_length + 2 * keystone_clearance;
cd = keystone_cradle_depth;
ow = sw + 2 * keystone_cradle_wall;
ol = sl + 2 * keystone_cradle_wall;
ls_p = keystone_latch_shelf_protrusion;
ls_t = keystone_latch_shelf_thickness;
ls_z = cd - ls_t;
// Shelf y-edge (the slot-facing edge of the shelf, where the body
// ends and the shelf begins). For latch_side = +1 this is +sl/2;
// for latch_side = -1 it's -sl/2.
ls_y_outer = keystone_latch_side * sl / 2;
// Inner edge of the shelf (further into the slot).
ls_y_inner = ls_y_outer - keystone_latch_side * ls_p;
// Position cradle so its slot front (low z) butts against the back
// of the cap's front face.
translate([0, 0, front_thickness]) {
difference() {
translate([-ow / 2, -ol / 2, 0])
cube([ow, ol, cd]);
// Slot through the cradle (open at both z ends; the front
// is closed by the cap's front face material around the
// smaller front-face cutout).
translate([-sw / 2, -sl / 2, -1])
cube([sw, sl, cd + 2]);
}
// Latch-catch shelf solid material protruding into the slot
// from the latch-side wall, at the back (deep z) end of the
// cradle. Transplanted from WALLY's `keystone_solid` 17×4×4
// latch shelf. The keystone's own latch hook has a leading-edge
// slope, so we don't need a printed ramp here.
if (ls_p > 0 && ls_t > 0)
translate([-sw / 2, min(ls_y_outer, ls_y_inner), ls_z])
cube([sw, ls_p, ls_t]);
}
}
// 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);
}

15962
blind-cap/cap.stl Normal file

File diff suppressed because it is too large Load diff

BIN
blind-cap/preview.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View file

@ -0,0 +1,76 @@
# Gridfinity Bin Label
![Preview](preview.png)
A swappable bin label compatible with Cullenect-style label sockets, sized for Gridfinity bins. Each label has two text fields and two icon fields that can be combined freely to identify the bin contents (screw size, head type, thread pitch, etc.). Adapted from the [Cullenect Labels](https://github.com/CullenJWebb/Cullenect-Labels) source.
## Output
`Select_Output` chooses what the SCAD generates:
| Value | Output |
|-------|--------|
| `0` | The label itself (default) |
| `10` | A socket test fit (for verifying socket dimensions on a printed receiver) |
| `11` | The negative volume of the socket (for embedding into another model) |
## Label Geometry
| Parameter | Default | Description |
|-----------|---------|-------------|
| `label_width` | `1` | Width in Gridfinity units (`1` = 36 mm × 11 mm) |
| `backward_compatible` | `true` | Generate V1-style latches for 1U labels (compatible with the original Cullenect socket) |
| `label_deboss` | `false` | If `true`, text and symbols are subtracted from the label face instead of raised |
| `gridfinity` | `true` | Use Gridfinity sizing. Set to `false` to drive size from `labelXmm`/`labelYmm`/`labelZmm` |
| `labelXmm` | 36 mm | Custom label width when `gridfinity` is `false` |
| `labelYmm` | 11 mm | Custom label height when `gridfinity` is `false` |
| `labelZmm` | 1.2 mm | Custom label thickness when `gridfinity` is `false` |
## Text 1 / Text 2
Each text slot is independent. `Text2` defaults to empty so single-line labels just use `Text1`.
| Parameter | Default | Description |
|-----------|---------|-------------|
| `Text{N}` | `"M5*8"` / `""` | Label text |
| `Text{N}_Align` | `center` / `right` | Horizontal alignment within the label |
| `Text{N}_Font_Size` | 4 mm | Font size |
| `Text{N}_Font` | `Open Sans` | Font family |
| `Text{N}_Font_Style` | `Bold` | Font weight/style |
| `Text{N}_XY` | `[0,0]` / `[-2,0]` | Fine X/Y offset on top of the alignment anchor |
## Symbol 1 / Symbol 2
Two independent symbol slots that render alongside the text (not in place of it). Useful for showing screw head type, drive type, or material on the same label as the size text. Set `Symbol{N}` to `none` to omit a slot.
| Parameter | Default | Description |
|-----------|---------|-------------|
| `Symbol{N}` | `side_pan` / `hex_rounded` | Symbol shape (see catalogue below) |
| `Symbol{N}_Align` | `left` / `right` | Horizontal alignment within the label |
| `Symbol{N}_Size` | 7 mm | Symbol diameter (bounding box) |
| `Symbol{N}_Stroke` | 1 mm | Stroke thickness for symbol details (cross arms, slot, star arms, etc.) |
| `Symbol{N}_XY` | `[1,0]` / `[-2,0]` | Fine X/Y offset on top of the alignment anchor |
### Symbol Catalogue
Top-down screw head views (showing what the head looks like from above):
| Symbol | Value | Description |
|:---:|---|---|
| <img src="symbols/hex_rounded.png" width="48"> | `hex_rounded` | Hex socket cap screw (round head with hex socket) |
| <img src="symbols/hex_flat.png" width="48"> | `hex_flat` | Hex bolt head (flat hexagonal head) |
| <img src="symbols/phillips.png" width="48"> | `phillips` | Phillips cross drive |
| <img src="symbols/slotted.png" width="48"> | `slotted` | Slotted (single-slot) drive |
| <img src="symbols/torx.png" width="48"> | `torx` | Torx (six-pointed star) drive |
| <img src="symbols/square.png" width="48"> | `square` | Square (Robertson) drive |
| <img src="symbols/pozidriv.png" width="48"> | `pozidriv` | Pozidriv (Phillips + diagonal cross) drive |
Side-profile screw silhouettes (showing the screw from the side, head + threaded shaft):
| Symbol | Value | Description |
|:---:|---|---|
| <img src="symbols/side_cap.png" width="48"> | `side_cap` | Hex socket cap screw — cylindrical head |
| <img src="symbols/side_flat.png" width="48"> | `side_flat` | Countersunk flat head |
| <img src="symbols/side_pan.png" width="48"> | `side_pan` | Pan head (rounded dome with skirt) |
| <img src="symbols/side_button.png" width="48"> | `side_button` | Button head (low rounded dome) |
| <img src="symbols/side_hex.png" width="48"> | `side_hex` | Hex bolt with chamfered head |

View file

@ -0,0 +1,509 @@
// Original source: https://github.com/CullenJWebb/Cullenect-Labels
/* [General Settings] */
//I want to generate...
Select_Output=0; // [0:Label, 10:Socket test fit, 11:Socket Negative Volume]
//model = "" // ["Cullenect label","Socket test fit","Socket Negative Volume"]
// Width in gridfinity units
label_width = 1; // .1
// Generate V1 Latches for 1U labels?
backward_compatible = true;
// Deboss Text?
label_deboss = false;
/* [Label Text 1] */
// Label Text
Text1 = "M5*8";
// Text Alignment
Text1_Align = "center"; // ["left","center","right"]
// Font Size
Text1_Font_Size = 4; // .1
// Font Family
Text1_Font = "Open Sans"; // [Open Sans, Open Sans Condensed, Ubuntu, Montserrat]
// Font Style
Text1_Font_Style = "Bold"; // [Regular,Black,Bold,ExtraBol,ExtraLight,Light,Medium,SemiBold,Thin,Italic,Black Italic,Bold Italic,ExtraBold Italic,ExtraLight Italic,Light Italic,Medium Italic,SemiBold Italic,Thin Italic]
// Adjust X and Y Position
Text1_XY = [0,0]; // .1
/* [Label Text 2] */
// Label Text
Text2 = "";
// Text Alignment
Text2_Align = "right"; // ["left","center","right"]
// Font Size
Text2_Font_Size = 4; // .1
// Font Family
Text2_Font = "Open Sans"; // [Open Sans, Open Sans Condensed, Ubuntu, Montserrat]
// Font Style
Text2_Font_Style = "Bold"; // [Regular,Black,Bold,ExtraBol,ExtraLight,Light,Medium,SemiBold,Thin,Italic,Black Italic,Bold Italic,ExtraBold Italic,ExtraLight Italic,Light Italic,Medium Italic,SemiBold Italic,Thin Italic]
// Adjust X and Y Position
Text2_XY = [-2,0]; // .1
/* [Symbol 1] */
// Symbol shape (set to "none" to omit)
Symbol1 = "side_pan"; // [none, hex_rounded, hex_flat, phillips, slotted, torx, square, pozidriv, side_cap, side_flat, side_pan, side_button, side_hex]
// Symbol Alignment
Symbol1_Align = "left"; // ["left","center","right"]
// Symbol diameter in mm
Symbol1_Size = 7; // .1
// Stroke thickness for symbol details (cross, slot, star arms)
Symbol1_Stroke = 1.0; // .1
// Adjust X and Y Position
Symbol1_XY = [1,0]; // .1
/* [Symbol 2] */
// Symbol shape (set to "none" to omit)
Symbol2 = "hex_rounded"; // [none, hex_rounded, hex_flat, phillips, slotted, torx, square, pozidriv, side_cap, side_flat, side_pan, side_button, side_hex]
// Symbol Alignment
Symbol2_Align = "right"; // ["left","center","right"]
// Symbol diameter in mm
Symbol2_Size = 7; // .1
// Stroke thickness for symbol details (cross, slot, star arms)
Symbol2_Stroke = 1.0; // .1
// Adjust X and Y Position
Symbol2_XY = [-2,0]; // .1
/* [Advanced] */
// Increase or decrease resolution of certain details
$fs = 0.01; // .01
// Use gridfinity U
gridfinity = true;
// Width of label in mm
labelXmm = 36.0; // .1
// Height of label in mm
labelYmm = 11.0; // .1
// Thickness of label in mm
labelZmm = 1.2; // .1
/* [Hidden] */
gridfinityX = 42; // Grid size for gridfinity units.
labelX = (gridfinity) ? (label_width * gridfinityX) - 6 : labelXmm;
labelY = (gridfinity) ? 11 : labelYmm;
labelZ = (gridfinity) ? 1.2 : labelZmm;
latchX = 0.2; // Width of socket on label walls
latchZ = 0.6; // Z-height of wall socket
// Tool for rounded cubes
module RoundedCube(size, radius) {
offsetfix = radius * 2;
width = size[0] - offsetfix;
height = size[1] - offsetfix;
depth = size[2];
translate([radius, radius, 0]) linear_extrude(height = depth) offset(r = radius) square([width, height]);
}
// V1 Label with new wall sockets
// Will only generate V1 label XY with V2 label Z
module Cullenect_Label_V1() {
// Variables
labelX_v1 = 36;
labelY_v1 = 11;
$midX1 = 4.695;
$midX2 = 10.18;
$midlatchX = 1.95;
$bottomX1 = 5.395;
$bottomX2 = 11.18;
$bottomlatchX = 0.95;
// Base below socket
// Base #1
color("Silver")
RoundedCube([$bottomX1, labelY_v1, 0.2], 0.5);
// Base #2
translate([$bottomX1 + $bottomlatchX,0,0])
color("Silver")
RoundedCube([$bottomX2, labelY_v1, 0.2], 0.5);
// Base #3
translate([$bottomX1 + $bottomlatchX + $bottomX2 + $bottomlatchX,0,0])
color("Silver")
RoundedCube([$bottomX2, labelY_v1, 0.2], 0.5);
// Base #4
translate([$bottomX1 + $bottomlatchX + $bottomX2 + $bottomlatchX + $bottomlatchX + $bottomX2,0,0])
color("Silver")
RoundedCube([$bottomX1, labelY_v1, 0.2], 0.5);
// Middle along socket
// Middle #1
translate([latchX,latchX,0.2])
color("Gray")
RoundedCube([$midX1, labelY_v1 - (latchX * 2), 0.6], 0.5);
// Middle #2
translate([latchX + $midX1 + $midlatchX,latchX,0.2])
color("Gray")
RoundedCube([$midX2, labelY_v1 - (latchX * 2), 0.6], 0.5);
// Middle #3
translate([latchX + $midX1 + $midlatchX + $midX2 + $midlatchX,latchX,0.2])
color("Gray")
RoundedCube([$midX2, labelY_v1 - (latchX * 2), 0.6], 0.5);
// Middle #4
translate([latchX + $midX1 + $midlatchX + $midX2 + $midlatchX + $midX2 + $midlatchX,latchX,0.2])
color("Gray")
RoundedCube([$midX1, labelY_v1 - (latchX * 2), 0.6], 0.5);
// Top above socket
translate([0,0,0.8])
color("Silver")
RoundedCube([labelX_v1, labelY_v1, 0.4], 0.5);
}
// V2 Label without backward compatibility
module Cullenect_Label_V2() {
// Label base below socket
color("Silver")
RoundedCube([labelX, labelY, 0.2], 0.5);
// Label middle within socket
translate([latchX,latchX,0])
color("Gray")
RoundedCube([labelX - (latchX * 2), labelY - (latchX * 2), labelZ - 0.2], 0.5);
// Label top above socket
translate([0,0,0.2 + latchZ])
color("Silver")
RoundedCube([labelX, labelY, (labelZ - 0.2) - latchZ], 0.5);
}
// Module to generate the correct label type
module Cullenect_Label() {
if (backward_compatible && gridfinity && (label_width == 1)){
Cullenect_Label_V1();
} else {
Cullenect_Label_V2();
}
}
// Calculate Text1 Position and font
Text1_posX = (Text1_Align == "left") ? 0 + Text1_XY.x :
(Text1_Align == "center") ? (labelX / 2) + Text1_XY.x :
(Text1_Align == "right") ? labelX + Text1_XY.x : 0; // Fallback to 0
Text1_posY = (labelY / 2) + Text1_XY.y;
Text1_posZ = labelZ - 0.2;
Text1_pos = [Text1_posX, Text1_posY, Text1_posZ];
// Calculate Text2 Position and font
Text2_posX = (Text2_Align == "left") ? 0 + Text2_XY.x :
(Text2_Align == "center") ? (labelX / 2) + Text2_XY.x :
(Text2_Align == "right") ? labelX + Text2_XY.x : 0; // Fallback to 0
Text2_posY = (labelY / 2) + Text2_XY.y;
Text2_posZ = labelZ - 0.2;
Text2_pos = [Text2_posX, Text2_posY, Text2_posZ];
// Generate Label Text #1
module label_text1() {
translate(Text1_pos)
linear_extrude(0.4)
text(Text1, Text1_Font_Size, font = str(Text1_Font, ":", Text1_Font_Style), halign = Text1_Align, valign = "center");
}
// Generate Label Text #2
module label_text2() {
translate(Text2_pos)
linear_extrude(0.4)
text(Text2, Text2_Font_Size, font = str(Text2_Font, ":", Text2_Font_Style), halign = Text2_Align, valign = "center");
}
// Symbol positions same alignment options as text, with the symbol's bounding box kept inside the label
Symbol_Z = labelZ - 0.2;
function symbol_x(align, size, dx) =
(align == "left") ? (size / 2) + dx :
(align == "center") ? (labelX / 2) + dx :
(align == "right") ? labelX - (size / 2) + dx : 0;
Symbol1_pos = [
symbol_x(Symbol1_Align, Symbol1_Size, Symbol1_XY.x),
(labelY / 2) + Symbol1_XY.y,
Symbol_Z
];
Symbol2_pos = [
symbol_x(Symbol2_Align, Symbol2_Size, Symbol2_XY.x),
(labelY / 2) + Symbol2_XY.y,
Symbol_Z
];
// 2D screw-head symbols, centered at origin, fitting within a circle of diameter d
module sym_hex_rounded(d, stroke) {
// Hex socket cap screw (rounded outer head with hex socket cutout)
difference() {
circle(d = d);
rotate(30) circle(d = d * 0.55, $fn = 6);
}
}
module sym_hex_flat(d, stroke) {
// Hex head bolt (flat hexagonal head with central thread hole)
difference() {
rotate(30) circle(d = d, $fn = 6);
circle(d = d * 0.3);
}
}
module sym_phillips(d, stroke) {
difference() {
circle(d = d);
union() {
square([stroke, d * 0.75], center = true);
square([d * 0.75, stroke], center = true);
}
}
}
module sym_slotted(d, stroke) {
difference() {
circle(d = d);
square([d * 0.85, stroke], center = true);
}
}
module sym_torx(d, stroke) {
difference() {
circle(d = d);
for (a = [0:60:300])
rotate(a) square([stroke, d * 0.75], center = true);
}
}
module sym_square(d, stroke) {
difference() {
circle(d = d);
rotate(45) square(d * 0.45, center = true);
}
}
module sym_pozidriv(d, stroke) {
// Phillips cross plus a smaller diagonal cross
difference() {
circle(d = d);
union() {
square([stroke, d * 0.75], center = true);
square([d * 0.75, stroke], center = true);
rotate(45) square([stroke * 0.7, d * 0.55], center = true);
rotate(45) square([d * 0.55, stroke * 0.7], center = true);
}
}
}
// Side-profile screw silhouettes (head + threaded shaft), centered in a d×d bounding box
module sym_side_cap(d, stroke) {
// Hex socket cap screw: cylindrical head with a slim socket marker
sw = d * 0.28; sh = d * 0.6;
hw = d * 0.55; hh = d * 0.4;
translate([0, -d / 2]) {
translate([-sw / 2, 0]) square([sw, sh]);
difference() {
translate([-hw / 2, sh]) square([hw, hh]);
translate([-hw * 0.2, sh + hh * 0.55])
square([hw * 0.4, stroke * 0.7]);
}
}
}
module sym_side_flat(d, stroke) {
// Countersunk flat head: trapezoid that flares from shaft to flat top
sw = d * 0.28; sh = d * 0.6;
htop = d * 0.7; hbot = d * 0.32; hh = d * 0.4;
translate([0, -d / 2]) {
translate([-sw / 2, 0]) square([sw, sh]);
translate([0, sh])
polygon([
[-hbot / 2, 0], [hbot / 2, 0],
[htop / 2, hh], [-htop / 2, hh]
]);
translate([-htop * 0.4, sh + hh - stroke * 0.9])
square([htop * 0.8, stroke * 0.6]);
}
}
module sym_side_pan(d, stroke) {
// Pan head: rounded dome with broader skirt
sw = d * 0.28; sh = d * 0.55;
hw = d * 0.7; hh = d * 0.45;
r = hh * 0.55;
translate([0, -d / 2]) {
translate([-sw / 2, 0]) square([sw, sh]);
translate([0, sh])
hull() {
translate([-hw / 2 + r, hh - r]) circle(r = r);
translate([hw / 2 - r, hh - r]) circle(r = r);
translate([-hw / 2, 0]) square([hw, 0.01]);
}
}
}
module sym_side_button(d, stroke) {
// Button head: low rounded dome
sw = d * 0.28; sh = d * 0.7;
hw = d * 0.65; hh = d * 0.3;
r = hh * 0.95;
translate([0, -d / 2]) {
translate([-sw / 2, 0]) square([sw, sh]);
translate([0, sh])
hull() {
translate([-hw / 2 + r, 0]) circle(r = r);
translate([hw / 2 - r, 0]) circle(r = r);
translate([-hw / 2, 0]) square([hw, 0.01]);
}
}
}
module sym_side_hex(d, stroke) {
// Hex bolt: head profile shows flat top with chamfered corners
sw = d * 0.28; sh = d * 0.55;
hw = d * 0.7; hh = d * 0.45;
cf = hh * 0.3;
translate([0, -d / 2]) {
translate([-sw / 2, 0]) square([sw, sh]);
translate([0, sh])
polygon([
[-hw / 2, 0], [hw / 2, 0],
[hw / 2, hh - cf], [hw / 2 - cf, hh],
[-hw / 2 + cf, hh], [-hw / 2, hh - cf]
]);
}
}
module symbol_shape(name, d, stroke) {
if (name == "hex_rounded") sym_hex_rounded(d, stroke);
else if (name == "hex_flat") sym_hex_flat(d, stroke);
else if (name == "phillips") sym_phillips(d, stroke);
else if (name == "slotted") sym_slotted(d, stroke);
else if (name == "torx") sym_torx(d, stroke);
else if (name == "square") sym_square(d, stroke);
else if (name == "pozidriv") sym_pozidriv(d, stroke);
else if (name == "side_cap") sym_side_cap(d, stroke);
else if (name == "side_flat") sym_side_flat(d, stroke);
else if (name == "side_pan") sym_side_pan(d, stroke);
else if (name == "side_button") sym_side_button(d, stroke);
else if (name == "side_hex") sym_side_hex(d, stroke);
}
// Independent symbols render alongside (not in place of) the text layers
module label_symbol1() {
if (Symbol1 != "none")
translate(Symbol1_pos)
linear_extrude(0.4)
symbol_shape(Symbol1, Symbol1_Size, Symbol1_Stroke);
}
module label_symbol2() {
if (Symbol2 != "none")
translate(Symbol2_pos)
linear_extrude(0.4)
symbol_shape(Symbol2, Symbol2_Size, Symbol2_Stroke);
}
// Combined text + symbol overlay
module label_decorations() {
label_text1();
label_text2();
label_symbol1();
label_symbol2();
}
// Join or difference the label and decorations
module cullenect_label_text(){
if (label_deboss) {
difference() {
Cullenect_Label();
color("Gray") label_decorations();
}
} else {
union() {
Cullenect_Label();
color("Gray") label_decorations();
}
}
}
*cullenect_label_text();
// Socket and Socket Negative Variables
socket_offset = 0.3;
socket_walls = 2;
socketX = labelX + socket_offset;
socketY = labelY + socket_offset;
ribZ = 0.4;
// Generate socket
module cullenect_socket(){
union(){
difference(){
translate([-socket_walls,-socket_walls,-1])
color("Silver")
RoundedCube([labelX + (socket_walls * 2), socketY + (socket_walls * 2), labelZ + 1], 0.2);
color("Gray")
RoundedCube([socketX, socketY, labelZ + 1], 0.5);
}
translate([0,0,0.2])
color("Silver")
cube([socketX, latchX, ribZ]);
translate([0, socketY - latchX,0.2])
color("Silver")
cube([socketX, latchX, ribZ]);
}
}
// Generate negative volume of socket
module cullenect_socket_negative(){
difference(){
color("Gray")
RoundedCube([socketX, socketY, labelZ], 0.5);
cullenect_socket();
}
}
// Vertical socket variables
vsocketY = labelZ - latchZ - 0.2; // define starting pos and depth
vsocketZ = socketY + 1; // Vertical height with 45 degree ceiling
// Generate vertical socket
// Unlike the h-socket and label this starts with the negative volume due to easier rounded edges
// Rounded edges are needed for the vertical printing of the ribbing
module cullenect_vertical_socket() {
difference(){
// Create base
union(){
cube([socketX, vsocketY / 2, vsocketZ]);// front of socket, no rounding
RoundedCube([socketX, vsocketY, vsocketZ], 0.1);// front of socket, rounded inside around rib
translate([0,vsocketY,0])
cube([socketX,latchZ,vsocketZ]); // Middle of socket, to be cut away later by rounded cube for rib
translate([-0.2,vsocketY + latchZ,0])
RoundedCube([socketX + 0.4, vsocketY, vsocketZ], 0.1);
}
// Remove rounded ribs
translate([-1,vsocketY,0])
RoundedCube([latchX + 1, latchZ, vsocketZ], 0.1);
translate([socketX - latchX,vsocketY,0])
RoundedCube([latchX + 1, latchZ, vsocketZ], 0.1);
// remove 45 degree top
translate([-2,labelZ / 2,vsocketY])
RoundedCube([latchX + 1, latchZ, vsocketZ], 0.1);
}
}
*cullenect_vertical_socket();
// Vertical Socket still under development
// Generate Selected Model...
module selected_model() {
if (Select_Output == 10) {cullenect_socket();}
else if (Select_Output == 11) {cullenect_socket_negative();}
else {cullenect_label_text();}
}
selected_model();

16074
gridfinity-bin-label/label.stl Normal file

File diff suppressed because it is too large Load diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View file

@ -0,0 +1,12 @@
// Renders a single symbol from ../label.scad as a flat top-down preview.
// Used to generate the symbol thumbnails referenced in the README.
// Usage: openscad -D 'symbol="hex_rounded"' --camera=0,0,100,0,0,0 --projection=ortho \
// --imgsize=240,240 -o hex_rounded.png render.scad
use <../label.scad>;
symbol = "hex_rounded";
size = 20;
stroke = 3;
linear_extrude(1)
symbol_shape(symbol, size, stroke);

File diff suppressed because it is too large Load diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB