274 lines
14 KiB
OpenSCAD
274 lines
14 KiB
OpenSCAD
// =============================================================================
|
|
// Woven planter + drip tray with bayonet (twist) lock
|
|
// Parametric.
|
|
// Reverse-engineered from "Planter - 100mm" and "Drip Tray - 100mm" STLs (https://www.printables.com/model/1507840-weave-planter).
|
|
//
|
|
// Outside: diagonal basket weave (two strip directions crossing over/under, 45 deg chamfered strip edges, dark diamond gaps between strips).
|
|
// Lock: 4 lugs on the tray rim pass between 4 stops on the underside of the planter. Push the planter down onto the tray with the lugs in the gaps, then twist ~45 deg so every lug sits over a stop.
|
|
//
|
|
// Both parts print as modelled (flat side down, no supports):
|
|
// - weave strips have 45 deg chamfers, ends of the weave taper 45 deg
|
|
// - lugs and stops have 45 deg flanks
|
|
// - the only bridge is the 5 mm slot ceiling inside the planter floor
|
|
// =============================================================================
|
|
|
|
/* [Render] */
|
|
part = "both"; // [both, planter, tray, assembled]
|
|
|
|
/* [Main dimensions] */
|
|
outer_d = 93.6; // max outer diameter, mm (rim bead / tray foot)
|
|
planter_h = 79.92; // planter height, mm
|
|
tray_h = 26.4; // tray height incl. lock rim, mm
|
|
|
|
/* [Walls & floor] */
|
|
wall_t = 3.2; // planter wall thickness at the raised weave
|
|
floor_center = 2.45; // planter floor thickness at the centre
|
|
floor_edge = 8.0; // planter floor height at the wall (cone slopes ~7 deg)
|
|
tray_floor_center = 2.4; // tray floor thickness at the centre
|
|
tray_floor_edge = 8.0; // tray floor height at the rim (water runs to the centre)
|
|
|
|
/* [Drainage] */
|
|
drain_d = 4.0; // drain hole diameter (keep >= 3.5 mm for PETG)
|
|
hole_pitch = 15; // radial spacing between hole rings
|
|
holes_per_ring = 8; // holes on every ring (+1 in the centre)
|
|
hole_margin = 4; // keep holes this far from the plug edge
|
|
|
|
/* [Weave] */
|
|
texture = true; // weave on/off (off = smooth walls)
|
|
weave_pitch = 10.2; // strip pitch (centre to centre), mm; snapped so it fits the circumference
|
|
relief = 0.9; // weave depth, mm
|
|
weave_zphase = 1.4; // vertical start phase of the weave, mm
|
|
weave_rot = 8.5; // rotate the weave pattern around the part, degrees
|
|
|
|
/* [Bayonet lock] */
|
|
lug_count = 4; // number of lugs on the tray / stops on the planter
|
|
lug_w = 24; // angular width of each lug and each stop, degrees
|
|
lug_depth = 2.2; // how far the lugs reach inwards, mm
|
|
lug_z0 = 1.8; // height of the lug start above the tray shoulder, mm
|
|
rim_t = 2.2; // tray rim wall thickness
|
|
rim_h = 6.38; // tray rim height above the shoulder
|
|
fit_c = 0.25; // radial clearance rim <-> planter wall (raise to 0.3 if tight)
|
|
lug_c = 0.3; // radial clearance lug tip <-> planter plug
|
|
lock_c = 0.3; // vertical clearance lug <-> stop (smaller = tighter lock)
|
|
lug_angle = 0; // tray lug rotation in the assembled view (0 = locked, 45 = unlocked)
|
|
|
|
// ----------------------------------------------------------------- derived ---
|
|
bead_r = 1.2;
|
|
bead_h = 2.4;
|
|
neck_h = 1.2;
|
|
|
|
R = outer_d / 2; // bead radius
|
|
R_t = R - 0.4; // top of the weave strips
|
|
Rc = R_t - relief; // base (gap) level of the weave
|
|
R_in = R_t - wall_t; // planter inner wall radius
|
|
|
|
wall_r = texture ? Rc - 0.3 : R_t; // core wall radius (weave skin is added on top)
|
|
neck_r = Rc - 0.1; // neck under the bead / above the foot
|
|
|
|
C = 2 * PI * R_t;
|
|
N = max(6, round(C / (sqrt(2) * weave_pitch))); // diamonds per row around the part
|
|
cellx = C / N; // horizontal distance between neighbouring diamonds
|
|
fn_core = 10 * N; // facets of the round parts (rim, bead, lock)
|
|
|
|
// bayonet geometry
|
|
flank_max = (rim_h - lug_z0 - 0.15) / 2;
|
|
lug_d = min(lug_depth, flank_max); // lug reach, 45 deg flanks
|
|
lug_tf = rim_h - lug_z0 - 2 * lug_d; // flat tip height
|
|
R_rim_o = R_in - fit_c; // tray rim outer radius
|
|
R_rim_i = R_rim_o - rim_t; // tray rim inner radius
|
|
tip_r = R_rim_i - lug_d; // lug tip radius
|
|
plug_r = tip_r - lug_c; // planter floor plug radius
|
|
blk_r = R_rim_i - 0.25; // stop outer radius
|
|
blk_z = lug_z0 + (R_rim_i - blk_r) - lock_c; // stop vertical land before the 45 deg top
|
|
pocket_h = rim_h + 0.3; // slot ceiling inside the planter
|
|
z_s = tray_h - rim_h; // tray shoulder height
|
|
fe_p = max(floor_edge, pocket_h + 1.2);
|
|
planter_skin_z1 = planter_h - bead_h - neck_h;
|
|
tray_skin_z0 = bead_h + neck_h;
|
|
|
|
assert(lug_tf >= 0.1, "rim_h too small for the lug: raise rim_h or lower lug_z0");
|
|
assert(blk_z + (blk_r - plug_r) < pocket_h - 0.5, "slot too shallow for the stops: raise rim_h");
|
|
assert(z_s > tray_floor_edge + 6, "tray_h too small: raise tray_h or lower tray_floor_edge");
|
|
assert(planter_h > 25, "planter_h too small");
|
|
assert(plug_r > 15, "outer_d too small for the lock geometry");
|
|
|
|
// ------------------------------------------------------------------- weave ---
|
|
// Plain diagonal weave: two strip families run at +/-45 deg and pass over / under each other in a checker. (u, v) are lattice coordinates, one unit = one strip pitch; u runs up-right, v runs down-right along the wall.
|
|
strip_half = 0.30; // half width of a strip, in pitches
|
|
strip_edge = 0.05; // width of the chamfered strip edge, in pitches
|
|
ramp_floor = 0.25; // height an under-going strip ramps down to (fraction of relief)
|
|
weave_detail = 4; // mesh facets across each strip top: 1 coarse ... 4 very smooth (3 renders faster)
|
|
|
|
function tri(z) = abs(z - 2 * floor(z / 2) - 1);
|
|
function Tb(f) = max(0, min(1, (strip_half - abs(f - 0.5)) / strip_edge)); // across a strip
|
|
function Wv(q) = ramp_floor + (1 - ramp_floor) *
|
|
max(0, min(1, (tri(q - 0.5) - strip_half) / (1 - 2 * strip_half))); // along a strip
|
|
function weave_h(u, v) = // surface height 0..1
|
|
let (I = floor(u), J = floor(v),
|
|
a = min(Tb(u - I), Wv(v + I)),
|
|
b = min(Tb(v - J), Wv(u + J + 1)))
|
|
max(a, b);
|
|
|
|
// The surface mesh is built on the lattice lines where the weave changes slope (strip edges and ramp ends). Every edge of every strip is therefore a straight mesh edge, and the flat tops, ramps and gaps need only a handful of facets. Extra lines across the flat strip tops and the gaps keep them round (one big facet would chord across the curved wall) and give smoother shading. weave_detail sets how many facets span each strip top: 1 = coarse, 3 = smooth, 4 = very smooth (default).
|
|
function gap_lines(k0, k1) = [for (k = [k0 : k1]) 0.5 + strip_half + (1 - 2 * strip_half) * k / weave_detail];
|
|
low_g = [for (f = gap_lines(1, weave_detail - 1)) if (f >= 1 - 1e-9) f - 1]; // gap, next cell
|
|
high_g = [for (f = gap_lines(1, weave_detail - 1)) if (f < 1 - 1e-9) f]; // gap, this cell
|
|
plat = [for (k = [0 : weave_detail])
|
|
0.5 - strip_half + strip_edge + (2 * strip_half - 2 * strip_edge) * k / weave_detail];
|
|
Fl = concat(low_g, [0.5 - strip_half], plat, [0.5 + strip_half], high_g); // mesh lines per pitch
|
|
nl = len(Fl);
|
|
function mline(a) = floor(a / nl) + Fl[((a % nl) + nl) % nl]; // lattice coordinate of mesh line a
|
|
function g_y(a, dl) = (mline(a) - mline(a + dl)) * cellx / 2 + weave_zphase; // height on the wall
|
|
function g_h(a, dl) = weave_h(mline(a), mline(a + dl));
|
|
// P = [lines around, rows, first row, last row, y min, y max]
|
|
function g_vi(a, dl, P) = (((a % P[0]) + P[0]) % P[0]) * P[1] + (dl - P[2]);
|
|
function g_in(a, dl, P) = dl >= P[2] && dl <= P[3] && g_y(a, dl) >= P[4] && g_y(a, dl) <= P[5];
|
|
function g_q(a, dl, P) = g_in(a, dl, P) && g_in(a + 1, dl - 1, P) && g_in(a + 1, dl, P) && g_in(a, dl + 1, P);
|
|
function g_pt(a, dl, z0, inner) =
|
|
let (u = mline(a), v = mline(a + dl), th = 180 * (u + v) / N,
|
|
r = inner ? Rc - 0.8 : Rc + relief * weave_h(u, v))
|
|
[r * cos(th), r * sin(th), z0 + (u - v) * cellx / 2 + weave_zphase];
|
|
function g_faces(a, dl, P, off) =
|
|
let (p00 = g_vi(a, dl, P), p10 = g_vi(a + 1, dl - 1, P),
|
|
p11 = g_vi(a + 1, dl, P), p01 = g_vi(a, dl + 1, P),
|
|
i00 = p00 + off, i10 = p10 + off, i11 = p11 + off, i01 = p01 + off,
|
|
d1 = abs(g_h(a, dl) - g_h(a + 1, dl)),
|
|
d2 = abs(g_h(a + 1, dl - 1) - g_h(a, dl + 1)))
|
|
concat(
|
|
// outer surface: split the quad along the diagonal that bends the least
|
|
d1 <= d2 ? [[p00, p10, p11], [p00, p11, p01]] : [[p00, p10, p01], [p10, p11, p01]],
|
|
// inner wall (hidden inside the core)
|
|
[[i00, i01, i11], [i00, i11, i10]],
|
|
// side walls where the patch ends (these lie outside the part and get trimmed away)
|
|
g_q(a, dl - 1, P) ? [] : [[p10, p00, i00], [p10, i00, i10]],
|
|
g_q(a + 1, dl - 1, P) ? [] : [[p11, p10, i10], [p11, i10, i11]],
|
|
g_q(a, dl + 1, P) ? [] : [[p01, p11, i11], [p01, i11, i01]],
|
|
g_q(a - 1, dl + 1, P) ? [] : [[p00, p01, i01], [p00, i01, i00]]);
|
|
|
|
// Ring-shaped skin whose outer surface is the weave between z0 and z1, with the 45 deg run-out at both ends (trimmed by the envelope below).
|
|
module weave_skin(z0, z1) {
|
|
L = z1 - z0;
|
|
M = nl * N;
|
|
ylo = -0.6 * cellx;
|
|
yhi = L + 0.6 * cellx;
|
|
dlo = floor((weave_zphase - yhi) * 2 * nl / cellx) - 3;
|
|
dhi = ceil((weave_zphase - ylo) * 2 * nl / cellx) + 3;
|
|
Dn = dhi - dlo + 1;
|
|
P = [M, Dn, dlo, dhi, ylo, yhi];
|
|
off = M * Dn;
|
|
pts = concat([for (a = [0 : M - 1], dl = [dlo : dhi]) g_pt(a, dl, z0, false)],
|
|
[for (a = [0 : M - 1], dl = [dlo : dhi]) g_pt(a, dl, z0, true)]);
|
|
fcs = [for (a = [0 : M - 1], dl = [dlo : dhi]) if (g_q(a, dl, P)) each g_faces(a, dl, P, off)];
|
|
Rbig = R_t + 0.6;
|
|
k = Rbig - Rc; // 45 deg run-out
|
|
assert(L > 2 * k + 1, "weave zone too short");
|
|
intersection() {
|
|
rotate([0, 0, weave_rot]) polyhedron(points = pts, faces = fcs, convexity = 10);
|
|
translate([0, 0, z0]) rotate_extrude($fn = fn_core)
|
|
polygon([[Rc - 1, 0], [Rc, 0], [Rbig, k], [Rbig, L - k], [Rc, L], [Rc - 1, L]]);
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------- planter ---
|
|
module planter_profile() {
|
|
difference() {
|
|
union() {
|
|
polygon([[0, 0], [wall_r, 0], [wall_r, planter_skin_z1 - 0.2],
|
|
[neck_r, planter_skin_z1 - 0.2], [neck_r, planter_h],
|
|
[R_in, planter_h], [R_in, fe_p], [0, floor_center]]);
|
|
// rim bead: round on top, 45 deg underneath (printable)
|
|
cx = R - bead_r;
|
|
cz = planter_h - bead_r;
|
|
tx = cx + bead_r * cos(45);
|
|
tz = cz - bead_r * sin(45);
|
|
zP = tz - (tx - neck_r);
|
|
hull() {
|
|
translate([cx, cz]) circle(r = bead_r, $fn = 48);
|
|
polygon([[R_in, planter_h], [R_in, zP], [neck_r, zP]]);
|
|
}
|
|
}
|
|
// annular slot on the underside that receives the tray rim
|
|
polygon([[plug_r, -1], [R_in, -1], [R_in, pocket_h], [plug_r, pocket_h]]);
|
|
}
|
|
}
|
|
|
|
// stops on the plug: the tray lugs sit on top of these once twisted
|
|
module lock_stops() {
|
|
zt = blk_z + (blk_r - plug_r);
|
|
for (k = [0 : lug_count - 1])
|
|
rotate([0, 0, k * 360 / lug_count - lug_w / 2])
|
|
rotate_extrude(angle = lug_w, $fn = fn_core)
|
|
polygon([[plug_r - 0.6, 0], [blk_r, 0], [blk_r, blk_z],
|
|
[plug_r, zt], [plug_r - 0.6, zt]]);
|
|
}
|
|
|
|
module drain_holes() {
|
|
n_rings = floor((plug_r - hole_margin - drain_d / 2) / hole_pitch);
|
|
translate([0, 0, -1]) cylinder(d = drain_d, h = fe_p + 2, $fn = 32);
|
|
for (i = [1 : n_rings], k = [0 : holes_per_ring - 1])
|
|
rotate([0, 0, k * 360 / holes_per_ring])
|
|
translate([i * hole_pitch, 0, -1])
|
|
cylinder(d = drain_d, h = fe_p + 2, $fn = 32);
|
|
}
|
|
|
|
module planter() {
|
|
union() {
|
|
difference() {
|
|
union() {
|
|
rotate_extrude($fn = fn_core) planter_profile();
|
|
lock_stops();
|
|
}
|
|
drain_holes();
|
|
}
|
|
if (texture) weave_skin(0, planter_skin_z1);
|
|
}
|
|
}
|
|
|
|
// -------------------------------------------------------------------- tray ---
|
|
module tray_profile() {
|
|
union() {
|
|
polygon([[0, 0], [neck_r, 0], [neck_r, tray_skin_z0 + 0.2],
|
|
[wall_r, tray_skin_z0 + 0.2], [wall_r, z_s],
|
|
[R_rim_o, z_s], [R_rim_o, z_s + rim_h], [R_rim_i, z_s + rim_h],
|
|
[R_rim_i, tray_floor_edge], [0, tray_floor_center]]);
|
|
// rounded foot bead
|
|
r = 0.7;
|
|
hull() {
|
|
translate([R - r, r]) circle(r = r, $fn = 32);
|
|
translate([R - r, bead_h - r]) circle(r = r, $fn = 32);
|
|
square([R - r, bead_h]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// bayonet lugs on the inside of the rim: 45 deg flanks, flat tip
|
|
module lock_lugs() {
|
|
z_a = z_s + lug_z0;
|
|
for (k = [0 : lug_count - 1])
|
|
rotate([0, 0, k * 360 / lug_count + lug_angle - lug_w / 2])
|
|
rotate_extrude(angle = lug_w, $fn = fn_core)
|
|
polygon([[R_rim_i + 0.5, z_a], [R_rim_i, z_a],
|
|
[tip_r, z_a + lug_d], [tip_r, z_a + lug_d + lug_tf],
|
|
[R_rim_i, z_a + 2 * lug_d + lug_tf],
|
|
[R_rim_i + 0.5, z_a + 2 * lug_d + lug_tf]]);
|
|
}
|
|
|
|
module tray() {
|
|
union() {
|
|
rotate_extrude($fn = fn_core) tray_profile();
|
|
lock_lugs();
|
|
if (texture) weave_skin(tray_skin_z0, z_s);
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------ output ---
|
|
if (part == "planter") planter();
|
|
else if (part == "tray") tray();
|
|
else if (part == "assembled") {
|
|
tray();
|
|
translate([0, 0, z_s]) planter();
|
|
} else {
|
|
planter();
|
|
translate([outer_d + 10, 0, 0]) tray();
|
|
}
|