Add sym_battery to the shared label decoration library: an upright cell with a solid terminal nub and "+"/"-" marks in the hollow body. Register it in symbol_shape and add it to the customizer dropdowns of both the bin label and the box label. Switch the gridfinity-box-label customizer defaults to a centered "PILAS" text flanked by two battery symbols, replacing the M2-M5 screw preset. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018WMwnWKAqyrQveSoyi6o93
428 lines
15 KiB
OpenSCAD
428 lines
15 KiB
OpenSCAD
// Shared label primitives used by gridfinity-bin-label and gridfinity-box-label.
|
||
// Provides:
|
||
// - symbol_shape(name, d, stroke): 2D screw/bit/washer/nut symbols
|
||
// - label_text(...) and label_symbol(...): positioned, extruded helpers
|
||
// - text_x(...) and symbol_x(...): horizontal alignment math
|
||
// - RoundedCube(size, radius): rounded cuboid base helper
|
||
|
||
// 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]);
|
||
}
|
||
|
||
// 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]
|
||
]);
|
||
}
|
||
}
|
||
|
||
// Drive-bit symbols: just the drive shape, without a surrounding circle. Useful
|
||
// when the symbol shares the label face with text and the empty circle would
|
||
// waste space. The shapes are scaled up to roughly fill the d×d bounding box.
|
||
module sym_bit_phillips(d, stroke) {
|
||
bs = stroke * 1.4;
|
||
union() {
|
||
square([bs, d * 0.95], center = true);
|
||
square([d * 0.95, bs], center = true);
|
||
}
|
||
}
|
||
|
||
module sym_bit_slotted(d, stroke) {
|
||
square([d * 0.95, stroke * 1.4], center = true);
|
||
}
|
||
|
||
module sym_bit_hex(d, stroke) {
|
||
rotate(30) circle(d = d * 0.95, $fn = 6);
|
||
}
|
||
|
||
module sym_bit_torx(d, stroke) {
|
||
bs = stroke * 1.4;
|
||
for (a = [0:60:300])
|
||
rotate(a) square([bs, d * 0.95], center = true);
|
||
}
|
||
|
||
module sym_bit_square(d, stroke) {
|
||
rotate(45) square(d * 0.65, center = true);
|
||
}
|
||
|
||
module sym_bit_pozidriv(d, stroke) {
|
||
bs = stroke * 1.4;
|
||
union() {
|
||
square([bs, d * 0.95], center = true);
|
||
square([d * 0.95, bs], center = true);
|
||
rotate(45) square([bs * 0.7, d * 0.7], center = true);
|
||
rotate(45) square([d * 0.7, bs * 0.7], center = true);
|
||
}
|
||
}
|
||
|
||
module sym_washer(d, stroke) {
|
||
difference() {
|
||
circle(d = d, $fn = 64);
|
||
circle(d = d - stroke * 2.4, $fn = 64);
|
||
}
|
||
}
|
||
|
||
module sym_nut(d, stroke) {
|
||
// Top-down nut: hex outline with a large central thread hole.
|
||
// Bigger hole than sym_hex_flat to read as a nut, not a bolt head.
|
||
difference() {
|
||
rotate(30) circle(d = d, $fn = 6);
|
||
circle(d = d * 0.5, $fn = 64);
|
||
}
|
||
}
|
||
|
||
module sym_printer(d, stroke) {
|
||
// Stylized paper-printer icon: a narrow paper input rectangle on top, a
|
||
// rounded-top body in the middle with an indicator dot on the right, and
|
||
// an output tray on the bottom. Each piece is drawn as a hollow outline
|
||
// and the three are unioned before the inner area is subtracted, so where
|
||
// the shapes overlap the internal lines disappear and the silhouette
|
||
// reads as a single printer. Centered in a d×d bounding box.
|
||
s = stroke * 0.85;
|
||
cr = d * 0.09;
|
||
cr_in = cr - s;
|
||
|
||
paper_w = d * 0.42; paper_h = d * 0.32;
|
||
paper_x = (d - paper_w) / 2;
|
||
paper_y = d * 0.62;
|
||
|
||
body_w = d * 0.86; body_h = d * 0.45;
|
||
body_x = (d - body_w) / 2;
|
||
body_y = d * 0.32;
|
||
|
||
tray_w = d * 0.62; tray_h = d * 0.4;
|
||
tray_x = (d - tray_w) / 2;
|
||
tray_y = 0;
|
||
|
||
translate([-d / 2, -d / 2]) {
|
||
difference() {
|
||
// Outer silhouette: union of paper, body (rounded top), tray
|
||
union() {
|
||
translate([paper_x, paper_y]) square([paper_w, paper_h]);
|
||
translate([body_x, body_y]) square([body_w, body_h - cr]);
|
||
translate([body_x, body_y + body_h - cr])
|
||
hull() {
|
||
translate([cr, 0]) circle(r = cr);
|
||
translate([body_w - cr, 0]) circle(r = cr);
|
||
square([body_w, 0.001]);
|
||
}
|
||
translate([tray_x, tray_y]) square([tray_w, tray_h]);
|
||
}
|
||
// Inner cutout: each piece inset by s, also unioned so any
|
||
// overlapping wall segments between pieces dissolve.
|
||
union() {
|
||
translate([paper_x + s, paper_y + s])
|
||
square([paper_w - 2 * s, paper_h - 2 * s]);
|
||
translate([body_x + s, body_y + s])
|
||
square([body_w - 2 * s, body_h - cr - s]);
|
||
translate([body_x, body_y + body_h - cr])
|
||
hull() {
|
||
translate([cr, 0]) circle(r = cr_in);
|
||
translate([body_w - cr, 0]) circle(r = cr_in);
|
||
translate([s, 0]) square([body_w - 2 * s, 0.001]);
|
||
}
|
||
translate([tray_x + s, tray_y + s])
|
||
square([tray_w - 2 * s, tray_h - 2 * s]);
|
||
}
|
||
}
|
||
// Indicator dot inside the body (sits in the hollow interior).
|
||
translate([body_x + body_w - d * 0.13, body_y + body_h * 0.62])
|
||
circle(d = s * 1.6);
|
||
}
|
||
}
|
||
|
||
module sym_magnet(d, stroke) {
|
||
// Horseshoe magnet: solid U-shape silhouette with horizontal pole-tip
|
||
// slits across each leg near the open mouth. Centered in a d×d bounding
|
||
// box; the open mouth points down (-y).
|
||
outer_w = d * 0.88;
|
||
arm_w = d * 0.26;
|
||
arc_r = outer_w / 2;
|
||
arc_r_in = arc_r - arm_w;
|
||
arm_h = d - arc_r;
|
||
inner_w = outer_w - 2 * arm_w;
|
||
leg_x_l = (d - outer_w) / 2;
|
||
leg_x_r = leg_x_l + outer_w - arm_w;
|
||
tip_y = d * 0.16;
|
||
slit_h = stroke * 0.7;
|
||
|
||
translate([-d / 2, -d / 2]) {
|
||
difference() {
|
||
union() {
|
||
translate([leg_x_l, 0]) square([arm_w, arm_h]);
|
||
translate([leg_x_r, 0]) square([arm_w, arm_h]);
|
||
translate([d / 2, arm_h])
|
||
difference() {
|
||
intersection() {
|
||
circle(r = arc_r);
|
||
translate([-arc_r, 0]) square([outer_w, arc_r]);
|
||
}
|
||
translate([0, -0.01])
|
||
intersection() {
|
||
circle(r = arc_r_in);
|
||
translate([-arc_r_in, 0])
|
||
square([inner_w, arc_r_in + 0.02]);
|
||
}
|
||
}
|
||
}
|
||
translate([leg_x_l - 0.01, tip_y])
|
||
square([arm_w + 0.02, slit_h]);
|
||
translate([leg_x_r - 0.01, tip_y])
|
||
square([arm_w + 0.02, slit_h]);
|
||
}
|
||
}
|
||
}
|
||
|
||
module sym_battery(d, stroke) {
|
||
// Side-on battery cell: a hollow rounded body with a solid positive
|
||
// terminal nub on top. A "+" mark and a "-" mark sit in the hollow
|
||
// interior so the icon reads as a battery, not a plain box. Centered in
|
||
// a d x d bounding box; the terminal points up (+y).
|
||
s = stroke * 0.85;
|
||
cr = d * 0.09;
|
||
|
||
cap_w = d * 0.26; cap_h = d * 0.1;
|
||
body_w = d * 0.62; body_h = d - cap_h;
|
||
body_x = (d - body_w) / 2;
|
||
cap_x = (d - cap_w) / 2;
|
||
|
||
mark_len = body_w * 0.42;
|
||
mark_t = s;
|
||
|
||
translate([-d / 2, -d / 2]) {
|
||
// Solid terminal nub.
|
||
translate([cap_x, body_h]) square([cap_w, cap_h]);
|
||
// Hollow body outline.
|
||
difference() {
|
||
translate([body_x + cr, cr])
|
||
offset(r = cr) square([body_w - 2 * cr, body_h - 2 * cr]);
|
||
translate([body_x + s + cr, s + cr])
|
||
offset(r = cr) square([body_w - 2 * s - 2 * cr,
|
||
body_h - 2 * s - 2 * cr]);
|
||
}
|
||
// Polarity marks inside the hollow interior.
|
||
translate([d / 2, body_h * 0.68]) {
|
||
square([mark_len, mark_t], center = true);
|
||
square([mark_t, mark_len], center = true);
|
||
}
|
||
translate([d / 2, body_h * 0.3])
|
||
square([mark_len, mark_t], center = true);
|
||
}
|
||
}
|
||
|
||
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 == "bit_phillips") sym_bit_phillips(d, stroke);
|
||
else if (name == "bit_slotted") sym_bit_slotted(d, stroke);
|
||
else if (name == "bit_hex") sym_bit_hex(d, stroke);
|
||
else if (name == "bit_torx") sym_bit_torx(d, stroke);
|
||
else if (name == "bit_square") sym_bit_square(d, stroke);
|
||
else if (name == "bit_pozidriv") sym_bit_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);
|
||
else if (name == "washer") sym_washer(d, stroke);
|
||
else if (name == "nut") sym_nut(d, stroke);
|
||
else if (name == "printer") sym_printer(d, stroke);
|
||
else if (name == "magnet") sym_magnet(d, stroke);
|
||
else if (name == "battery") sym_battery(d, stroke);
|
||
}
|
||
|
||
// Horizontal anchor for a text baseline within a labelX-wide rectangle.
|
||
// Match the original bin-label positioning: "left" aligns to x=0, "right" to x=labelX.
|
||
function text_x(align, dx, labelX) =
|
||
(align == "left") ? 0 + dx :
|
||
(align == "center") ? (labelX / 2) + dx :
|
||
(align == "right") ? labelX + dx : 0;
|
||
|
||
// Horizontal anchor for a symbol's center, keeping its bounding box inside the label.
|
||
function symbol_x(align, size, dx, labelX) =
|
||
(align == "left") ? (size / 2) + dx :
|
||
(align == "center") ? (labelX / 2) + dx :
|
||
(align == "right") ? labelX - (size / 2) + dx : 0;
|
||
|
||
// Split a string on "\n" into a list of lines.
|
||
function split_lines(s, i = 0, current = "", acc = []) =
|
||
(i >= len(s)) ? concat(acc, [current]) :
|
||
(s[i] == "\n") ? split_lines(s, i + 1, "", concat(acc, [current])) :
|
||
split_lines(s, i + 1, str(current, s[i]), acc);
|
||
|
||
// Render text at (x, y, z), extruded by extrude_h. Embedded "\n" splits the
|
||
// string into multiple lines stacked vertically and centered as a block on y.
|
||
// line_spacing is the distance between line centers in mm; if 0 it defaults
|
||
// to size * 1.2.
|
||
module label_text(text_str, x, y, z, size, font, style, align, extrude_h,
|
||
line_spacing = 0) {
|
||
if (text_str != "") {
|
||
lines = split_lines(text_str);
|
||
num = len(lines);
|
||
spacing = (line_spacing > 0) ? line_spacing : size * 1.2;
|
||
y_top = y + (num - 1) * spacing / 2;
|
||
translate([0, 0, z])
|
||
linear_extrude(extrude_h)
|
||
for (i = [0 : num - 1])
|
||
if (lines[i] != "")
|
||
translate([x, y_top - i * spacing])
|
||
text(lines[i], size,
|
||
font = str(font, ":", style),
|
||
halign = align, valign = "center");
|
||
}
|
||
}
|
||
|
||
// Render a single symbol at (x, y, z), extruded by extrude_h. Skips when name == "none".
|
||
module label_symbol(name, x, y, z, size, stroke, extrude_h) {
|
||
if (name != "none")
|
||
translate([x, y, z])
|
||
linear_extrude(extrude_h)
|
||
symbol_shape(name, size, stroke);
|
||
}
|