Replace the corner-cylinder hull in back_plate() with a hull of rotate_extruded posts whose top-right 2D corner is filleted, so the back plate's front-face perimeter rounds smoothly into the side walls while the back face stays flat against the pegboard. Adds the back_front_fillet_r parameter (default 1.0 mm).
291 lines
12 KiB
OpenSCAD
291 lines
12 KiB
OpenSCAD
// ============================================
|
|
// Skadis Coffee Tamper Holder
|
|
// ============================================
|
|
// IKEA Skadis pegboard holder for a coffee tamper that ships in a
|
|
// tamper cup (e.g. Normcore 58mm tamper inside its 64mm cup).
|
|
//
|
|
// The cup drops open-end-up into the holder's pocket; the tamper rests
|
|
// on top of the cup with its tamping face protected.
|
|
//
|
|
// Mounts to the Skadis pegboard via the T-Clip system from
|
|
// https://www.printables.com/model/256896-skadis-t-clip-system
|
|
// (clips are printed separately). The slot + recess geometry comes from
|
|
// the shared lib/skadis-t-clip helper.
|
|
//
|
|
// Coordinate system:
|
|
// Origin (0, 0, 0) = back-bottom-center of the back plate
|
|
// (rear face touches the pegboard).
|
|
// X = left/right (width)
|
|
// Y = pegboard outward (depth, +Y away from board)
|
|
// Z = up (height)
|
|
|
|
use <../lib/skadis-t-clip/skadis-t-clip.scad>;
|
|
|
|
// ============================================
|
|
// CUSTOMIZABLE PARAMETERS
|
|
// ============================================
|
|
|
|
/* [Tamper Cup] */
|
|
// Outer diameter of the tamper cup (mm)
|
|
cup_diameter = 64; // [40:0.5:120]
|
|
// Pocket depth — how far the cup sits in the holder (mm)
|
|
cup_pocket_depth = 10; // [5:1:60]
|
|
// Radial clearance around the cup, per side (mm)
|
|
cup_tolerance = 0.4; // [0:0.1:2]
|
|
// Pocket floor thickness; 0 = open bottom (mm)
|
|
cup_floor = 4; // [0:0.5:8]
|
|
// Drainage hole through the floor; 0 = solid floor (mm)
|
|
cup_drain_diameter = 50; // [0:1:60]
|
|
// Front-cut window across the pocket so the cup is graspable; 0 = closed cylinder (mm chord)
|
|
cup_front_cut_width = 30; // [0:1:120]
|
|
|
|
/* [Holder Body] */
|
|
// Wall thickness around the cup pocket (mm)
|
|
pocket_wall = 4; // [1.5:0.5:6]
|
|
// Overlap between the cup pocket and the back plate, ensuring a solid union (mm)
|
|
merge_overlap = 3; // [1:0.1:6]
|
|
// Fillet radius applied to the cup pocket's outer top + bottom edges and
|
|
// to the inside-bottom corner where the cavity wall meets the floor (mm)
|
|
cup_fillet_r = 1.5; // [0:0.1:5]
|
|
|
|
/* [Skadis Mount] */
|
|
// Number of T-Clip mounts (1 = compact, 2 prevents rotation)
|
|
mount_count = 1; // [1, 2]
|
|
// Vertical pitch between slots (Skadis standard = 40mm)
|
|
mount_pitch = 40; // [40:1:120]
|
|
// Back-plate thickness (mm). The recess depth is subtracted from this,
|
|
// so the material remaining at the recess bottom = back_thickness - clip_recess_depth.
|
|
back_thickness = 5.4; // [2.5:0.1:8]
|
|
// Margin around the slot recess on the back plate (mm)
|
|
back_margin = 6; // [3:0.5:15]
|
|
// Outer corner radius of the back plate (mm)
|
|
back_corner_r = 4; // [0:0.5:10]
|
|
// Fillet on the back plate's front-face perimeter edges (mm) — aesthetic only
|
|
back_front_fillet_r = 1.0; // [0:0.1:5]
|
|
|
|
/* [Skadis Slot] */
|
|
// Skadis nominal slot is 5 x 15mm. Adjust if your printed T-Clip
|
|
// stem needs a different fit.
|
|
skadis_slot_width = 5.0; // [4:0.1:8]
|
|
skadis_slot_height = 15.0; // [10:0.1:25]
|
|
// Cylindrical recess on the front face that seats the T-Clip's cap.
|
|
clip_recess_diameter = 22.2; // [15:0.1:35]
|
|
clip_recess_depth = 2.9; // [0.1:0.1:5]
|
|
|
|
/* [Resolution] */
|
|
$fn = 96;
|
|
|
|
// ============================================
|
|
// DERIVED DIMENSIONS
|
|
// ============================================
|
|
cup_inner = cup_diameter + 2 * cup_tolerance;
|
|
cup_outer = cup_inner + 2 * pocket_wall;
|
|
|
|
// Back plate is only as wide as needed for the slot recess + margins
|
|
back_w = clip_recess_diameter + 2 * back_margin;
|
|
back_h = cup_pocket_depth + 2 * back_margin + skadis_slot_height
|
|
+ (mount_count - 1) * mount_pitch;
|
|
|
|
// Cup pocket sits in front of the back plate, with merge_overlap mm of
|
|
// the cylinder pushed into the plate so the union is a solid volume.
|
|
cup_pocket_y = back_thickness + cup_outer / 2 - merge_overlap;
|
|
|
|
// Bottom slot Z; subsequent rows space upward by mount_pitch
|
|
bottom_mount_z = cup_pocket_depth + back_margin + skadis_slot_height / 2;
|
|
|
|
// ============================================
|
|
// PARAMETER VALIDATION
|
|
// ============================================
|
|
assert(cup_diameter > 0, "cup_diameter must be positive");
|
|
assert(cup_pocket_depth > 0, "cup_pocket_depth must be positive");
|
|
assert(cup_floor >= 0, "cup_floor must be non-negative");
|
|
assert(cup_drain_diameter >= 0, "cup_drain_diameter must be non-negative");
|
|
assert(cup_drain_diameter < cup_inner,
|
|
"cup_drain_diameter must be smaller than the cup interior");
|
|
assert(pocket_wall >= 1.5, "pocket_wall must be at least 1.5mm");
|
|
assert(merge_overlap > 0 && merge_overlap < cup_outer / 2,
|
|
"merge_overlap must be between 0 and cup_outer/2");
|
|
assert(mount_count == 1 || mount_count == 2, "mount_count must be 1 or 2");
|
|
assert(back_thickness >= 2.5, "back_thickness must be at least 2.5mm for strength");
|
|
assert(back_front_fillet_r >= 0, "back_front_fillet_r must be non-negative");
|
|
assert(back_front_fillet_r < back_thickness,
|
|
"back_front_fillet_r must be smaller than back_thickness");
|
|
assert(back_corner_r == 0 || back_front_fillet_r < back_corner_r,
|
|
"back_front_fillet_r must be smaller than back_corner_r when back_corner_r > 0");
|
|
assert(skadis_slot_height >= skadis_slot_width,
|
|
"skadis_slot_height must be at least skadis_slot_width");
|
|
assert(clip_recess_diameter > skadis_slot_height,
|
|
"clip_recess_diameter must exceed skadis_slot_height so the recess surrounds the slot");
|
|
assert(clip_recess_depth < back_thickness,
|
|
"clip_recess_depth must be less than back_thickness");
|
|
assert(cup_front_cut_width <= cup_outer,
|
|
"cup_front_cut_width cannot exceed cup outer diameter");
|
|
assert(cup_fillet_r >= 0, "cup_fillet_r must be non-negative");
|
|
assert(cup_fillet_r < pocket_wall,
|
|
"cup_fillet_r must be smaller than pocket_wall");
|
|
assert(2 * cup_fillet_r < cup_pocket_depth,
|
|
"2 * cup_fillet_r must be smaller than cup_pocket_depth");
|
|
assert(cup_fillet_r <= cup_pocket_depth - cup_floor,
|
|
"cup_fillet_r must not exceed (cup_pocket_depth - cup_floor)");
|
|
assert(cup_drain_diameter == 0 || 2 * cup_fillet_r <= cup_floor,
|
|
"2 * cup_fillet_r must not exceed cup_floor when there is a drain hole (so the drain-top and drain-bottom fillets fit on the drain wall)");
|
|
assert(cup_drain_diameter == 0
|
|
|| 2 * cup_fillet_r < (cup_inner - cup_drain_diameter) / 2,
|
|
"2 * cup_fillet_r must be smaller than (R_inner - R_drain) so the inside-bottom and drain-top fillets fit on the floor top");
|
|
|
|
// ============================================
|
|
// MAIN ASSEMBLY
|
|
// ============================================
|
|
holder();
|
|
|
|
module holder() {
|
|
difference() {
|
|
union() {
|
|
back_plate();
|
|
cup_pocket();
|
|
}
|
|
cup_front_cutout();
|
|
clip_cutouts();
|
|
}
|
|
}
|
|
|
|
// ============================================
|
|
// COMPONENT MODULES
|
|
// ============================================
|
|
|
|
// Narrow vertical plate that carries the Skadis mount. Rests flat
|
|
// against the pegboard. The front-face perimeter is filleted by
|
|
// back_front_fillet_r for a softer look; the back face stays flat.
|
|
module back_plate() {
|
|
if (back_corner_r > 0) {
|
|
hull()
|
|
for (xc = [-back_w / 2 + back_corner_r,
|
|
back_w / 2 - back_corner_r])
|
|
for (zc = [back_corner_r, back_h - back_corner_r])
|
|
translate([xc, 0, zc])
|
|
rotate([-90, 0, 0])
|
|
rotate_extrude($fn = 96)
|
|
polygon(_back_plate_post_profile(
|
|
R = back_corner_r,
|
|
H = back_thickness,
|
|
F = back_front_fillet_r));
|
|
} else {
|
|
translate([-back_w / 2, 0, 0])
|
|
cube([back_w, back_thickness, back_h]);
|
|
}
|
|
}
|
|
|
|
// Radial-axial profile of a back-plate corner post: a rectangle (R wide
|
|
// X H tall) with the top-right corner rounded by F. After rotate_extrude
|
|
// (axis along Z) and rotate([-90,0,0]) (Z -> Y), the top-right of this
|
|
// 2D profile becomes the post's front edge — so hulling four of these
|
|
// posts gives the back plate a filleted front-face perimeter.
|
|
function _back_plate_post_profile(R, H, F, n_arc = 16) =
|
|
(F > 0) ? concat(
|
|
[[0, 0], [R, 0], [R, H - F]],
|
|
[for (i = [0 : n_arc])
|
|
let (a = 90 * i / n_arc)
|
|
[(R - F) + F * cos(a), (H - F) + F * sin(a)]
|
|
],
|
|
[[0, H]]
|
|
) : [[0, 0], [R, 0], [R, H], [0, H]];
|
|
|
|
// Cup pocket: outer cylinder, cavity, floor, and drain hole built as a
|
|
// single rotate_extrude of a 2D radial profile. Five fillets give the
|
|
// piece a smoother appearance: bottom-outer + top-outer (outer rim
|
|
// joints), inside-bottom (cavity wall meeting the floor), drain-top
|
|
// (floor meeting the drain hole, visible from inside the cup), and
|
|
// drain-bottom (drain hole meeting the bottom face). The cavity rim
|
|
// (top-inside) stays sharp so the cup seats cleanly.
|
|
module cup_pocket() {
|
|
translate([0, cup_pocket_y, 0])
|
|
rotate_extrude($fn = 96)
|
|
polygon(_cup_pocket_profile(
|
|
R_outer = cup_outer / 2,
|
|
R_inner = cup_inner / 2,
|
|
R_drain = (cup_drain_diameter > 0) ? cup_drain_diameter / 2 : 0,
|
|
depth = cup_pocket_depth,
|
|
floor = cup_floor,
|
|
f = cup_fillet_r));
|
|
}
|
|
|
|
// Radial-axial 2D profile of the cup pocket's solid material. X is the
|
|
// radial direction; Y is the vertical direction (Y=0 at the bottom face,
|
|
// Y=depth at the rim). CCW traversal: drain-bottom fillet (if drain) ->
|
|
// bottom face -> bottom-outer fillet -> outer wall -> top-outer fillet
|
|
// -> top face -> cavity rim -> cavity wall -> inside-bottom fillet ->
|
|
// floor top -> drain-top fillet (if drain) -> drain wall -> close.
|
|
function _cup_pocket_profile(R_outer, R_inner, R_drain, depth, floor, f,
|
|
n_arc = 16) =
|
|
let (
|
|
// Drain-bottom (convex, plate corner): rounds the bottom face
|
|
// meeting the drain hole. Skipped when R_drain == 0.
|
|
drain_bottom = (R_drain > 0) ? [
|
|
for (i = [0 : n_arc])
|
|
let (a = 180 + 90 * i / n_arc)
|
|
[(R_drain + f) + f * cos(a), f + f * sin(a)]
|
|
] : [[R_drain, 0]],
|
|
// Bottom-outer (convex, plate corner): rounds the outer rim at Y=0.
|
|
arc_B = [
|
|
for (i = [0 : n_arc])
|
|
let (a = 270 + 90 * i / n_arc)
|
|
[(R_outer - f) + f * cos(a), f + f * sin(a)]
|
|
],
|
|
// Top-outer (convex, plate corner): rounds the outer rim at Y=depth.
|
|
arc_C = [
|
|
for (i = [0 : n_arc])
|
|
let (a = 0 + 90 * i / n_arc)
|
|
[(R_outer - f) + f * cos(a), (depth - f) + f * sin(a)]
|
|
],
|
|
// Inside-bottom (concave, cavity corner): rounds the cavity wall
|
|
// meeting the floor for a smooth interior.
|
|
arc_E = [
|
|
for (i = [0 : n_arc])
|
|
let (a = 0 - 90 * i / n_arc)
|
|
[(R_inner - f) + f * cos(a), (floor + f) + f * sin(a)]
|
|
],
|
|
// Drain-top (convex, plate corner): rounds the floor meeting the
|
|
// drain hole, visible from inside the cup. Skipped when there is
|
|
// no drain hole (R_drain == 0); the fallback is the sharp F vertex.
|
|
drain_top = (R_drain > 0) ? [
|
|
for (i = [0 : n_arc])
|
|
let (a = 90 + 90 * i / n_arc)
|
|
[(R_drain + f) + f * cos(a), (floor - f) + f * sin(a)]
|
|
] : [[R_drain, floor]]
|
|
)
|
|
concat(
|
|
drain_bottom,
|
|
arc_B,
|
|
arc_C,
|
|
[[R_inner, depth]],
|
|
arc_E,
|
|
drain_top
|
|
);
|
|
|
|
// Front window in the pocket wall so the cup can be grabbed from the front.
|
|
module cup_front_cutout() {
|
|
if (cup_front_cut_width > 0) {
|
|
rim_top = cup_pocket_depth + 1; // overshoot the rim
|
|
rim_bot = min(cup_floor + 3, cup_pocket_depth - 4);
|
|
notch_h = max(rim_top - rim_bot, 1);
|
|
translate([-cup_front_cut_width / 2,
|
|
cup_pocket_y,
|
|
rim_bot])
|
|
cube([cup_front_cut_width,
|
|
cup_outer / 2 + 2,
|
|
notch_h]);
|
|
}
|
|
}
|
|
|
|
// One T-Clip cutout per mount, stacked at mount_pitch. Geometry comes
|
|
// from lib/skadis-t-clip — see that library for the parameter contract.
|
|
module clip_cutouts() {
|
|
for (i = [0 : mount_count - 1])
|
|
translate([0, 0, bottom_mount_z + i * mount_pitch])
|
|
skadis_t_clip_cutout(
|
|
plate_thickness = back_thickness,
|
|
slot_w = skadis_slot_width,
|
|
slot_h = skadis_slot_height,
|
|
recess_d = clip_recess_diameter,
|
|
recess_depth = clip_recess_depth);
|
|
}
|