Read this model as text
A hinged travel case for a tiny whoop, its charger and a row of 1S batteries, designed for PETG, with a smooth outside. Seen from the front with the lid open behind, the base is filled up to a deck with two pockets: one shaped around the ducts of the whoop, one for the charger in the front right corner. Flat-printed zigzag springs with a T clip on one end slide into sockets in the pocket walls: two press the charger into its corner, four hold a smaller whoop in the whoop pocket. A window in the front wall takes the charging cable and closes with a slider that runs flush in a dovetail channel and clicks open and shut. The latch sits flush in a recess. The lid holds the batteries standing on their side in snap clips. Two pin hinges at the rear, each on one M3 bolt.
The image shows the solid OpenSCAD renders at part assembly · test_print false · show_insert true · show_contents false · whoop_wheelbase 80 · duct_d 50 · whoop_h 40 · insert_wheelbase 65 · insert_duct_d 36 · charger_l 90 · charger_w 45 · charger_h 20 · port_x 0 · port_z 10 · battery_count 8 · battery_l 62 · battery_w 13.5 · battery_t 9 · color_case SteelBlue.
Tiny whoop case with clip-in zigzag springs, flush slider and latch
mick/whoop-case@1.3.0
Report
For a legal or policy problem: a license violation, personal data, somebody else’s work republished as their own. A model that prints badly is a comment, not a report.
Parameters
from sourceDescription
What is this
A hinged travel case for a tiny whoop, its charger and a row of 1S batteries, designed for PETG, with a smooth outside. Seen from the front with the lid open behind, the base is filled up to a deck with two pockets: one shaped around the ducts of the whoop, one for the charger in the front right corner. Flat-printed zigzag springs with a T clip on one end slide into sockets in the pocket walls: two press the charger into its corner, four hold a smaller whoop in the whoop pocket. A window in the front wall takes the charging cable and closes with a slider that runs flush in a dovetail channel and clicks open and shut. The latch sits flush in a recess. The lid holds the batteries standing on their side in snap clips. Two pin hinges at the rear, each on one M3 bolt.
What you use it for
Carrying a whoop with its charger and spare 1S batteries in one box. The default whoop, a Meteor75 Pro II (80 mm wheelbase), drops straight into its pocket. For a smaller whoop, print the four whoop springs set to its wheelbase and duct diameter and clip them into the four sockets: they centre it along the diagonals and hold it. The charger springs push the charger forward and outward, so its port meets the window; they take a charger from 2 mm smaller to about 2 mm larger than set. Push the whoop or the charger straight down: the chamfer on each spring pad pushes the spring back.
Design notes
Everything prints without supports and the floor stays closed. The springs print flat: each strip of the zigzag is a little wall that bends sideways, along its layers, which is what lets PETG take the travel again and again. The T head drops into a vertical T slot in the deck from above, so a spring can be swapped without tools. The deck fills the base up to the pockets; slice it with sparse infill. The outside has a 6 mm corner radius, a 45 degree chamfer under the base and on top of the lid, and a small chamfer either side of the parting line. The slider channel is a dovetail with 45 degree sides, cut straight up to the rim, so the slider goes in from the top and the closed lid keeps it there; the slider prints front face down with its edges flaring at 45 degrees. A tongue cut into it bends along its layers and carries a bump that clicks into a V groove at both ends of its travel. The latch tongue is the outer skin of the lid front, freed by two slits and a relief behind it; its hook rides over a 45 degree lead-in on the wall top and catches in a pocket backed by a boss inside the front wall, and a scoop under the tongue gives a fingernail room to open it. Battery arms hang from the lid plate and bend across their layers, which PETG takes at their small deflection. Each hinge is base, lid, base on an M3 x 30 with a nut. With the defaults the case is about 176 x 116 mm and fits a 180 mm bed. test_print renders a coupon set: one hinge and the latch in both halves, the slider channel with the slider, a charger spring socket with its spring, and one battery clip.
Parameters
part picks the assembly or one part in print orientation; charger_springs lays out both charger springs, whoop_springs all four whoop springs. test_print swaps it for the coupon set. show_insert shows the whoop springs in the assembly, show_contents the whoop, charger and batteries as ghosts. whoop_wheelbase (motor to motor across the diagonal), duct_d (outer diameter of one duct) and whoop_h (total height with camera) set the whoop pocket and the case. insert_wheelbase and insert_duct_d set the smaller whoop the four whoop springs hold. charger_l, charger_w and charger_h set the charger pocket. port_x moves the window from the middle of the charger toward the whoop, port_z sets the height of its centre above the floor. battery_count, battery_l (with the plug), battery_w (the height as it stands) and battery_t set the battery clips. color_case is display only.
Use with your agent
{
"mcpServers": {
"remixcad": {
"type": "http",
"url": "https://remix-cad.vercel.app/api/mcp"
}
}
}get_model("mick/whoop-case@1.3.0")Returns the manifest, the source, the import closure and the license obligations. Full MCP setup.
Parameter table
Parsed from the Customizer annotations in main.scad| Name | Default | Range | Unit | Description |
|---|---|---|---|---|
| part | "assembly" | assembly, base, lid, slider, charger_springs, whoop_springs | ||
| test_print | false | |||
| show_insert | true | |||
| show_contents | false | |||
| whoop_wheelbase | 80 | 60 to 120 step 0.5 | ||
| duct_d | 50 | 30 to 70 step 0.5 | ||
| whoop_h | 40 | 20 to 60 step 1 | ||
| insert_wheelbase | 65 | 50 to 110 step 0.5 | ||
| insert_duct_d | 36 | 25 to 65 step 0.5 | ||
| charger_l | 90 | 40 to 140 step 1 | ||
| charger_w | 45 | 20 to 80 step 1 | ||
| charger_h | 20 | 8 to 40 step 1 | ||
| port_x | 0 | -30 to 30 step 0.5 | ||
| port_z | 10 | 9 to 30 step 0.5 | ||
| battery_count | 8 | 0 to 12 step 1 | ||
| battery_l | 62 | 40 to 80 step 1 | ||
| battery_w | 13.5 | 8 to 20 step 0.5 | ||
| battery_t | 9 | 5 to 14 step 0.5 | ||
| color_case | "SteelBlue" | SteelBlue, White, DimGray, Black, FireBrick, Goldenrod, ForestGreen, MediumPurple |
Lineage
0 models depend on this oneOnly what this model came from and what came from it.
License
Whoever downloads this takes these obligations along- mick/whoop-case@1.3.0 is MIT
Source
main.scad is the entrypointmain.scad
// Tiny whoop case: the whoop, its charger and a row of 1S batteries in one hinged box,
// printed in PETG, with a smooth outside. The base is filled up to a deck with a
// pocket shaped around the ducts of the whoop and a pocket for the charger in the
// front right corner (seen from the front with the lid open behind). Flat-printed
// zigzag springs with a T clip slide into sockets in the pocket walls: two press the
// charger into its corner, four hold a smaller whoop. A flush slider closes the
// window for the charging cable, a flush snap latch closes the lid. Every part
// prints without supports.
//
// Coordinates: y = 0 at the rear (hinges), y = Sy at the front (latch). Seen from
// the front, the viewer's right is low x. The lid is modelled closed and flipped
// for printing.
/* [Part] */
// The whole case with the lid open, or one part in print orientation.
part = "assembly"; // [assembly, base, lid, slider, charger_springs, whoop_springs]
// Print a coupon set instead: one hinge and the latch in base and lid halves, the
// slider channel with the slider, a charger spring socket with its spring, and one
// battery clip.
test_print = false;
// Show the four whoop springs, and the smaller whoop as ghost, in the assembly.
show_insert = true;
// Show the whoop, the charger and the batteries as ghosts in the assembly.
show_contents = false;
/* [Whoop] */
// The whoop the pocket fits without springs. Motor to motor across the diagonal:
// 80 for a Meteor75 Pro II.
whoop_wheelbase = 80; // [60:0.5:120]
// Outer diameter of one duct.
duct_d = 50; // [30:0.5:70]
// Total height of the whoop, camera included.
whoop_h = 40; // [20:1:60]
/* [Whoop springs] */
// A smaller whoop held in the pocket by the four whoop springs: wheelbase and duct
// diameter.
insert_wheelbase = 65; // [50:0.5:110]
insert_duct_d = 36; // [25:0.5:65]
/* [Charger] */
// Charger size, port end against the front wall. The springs take a charger from
// 2 mm smaller to about 2 mm larger, in both directions.
charger_l = 90; // [40:1:140]
charger_w = 45; // [20:1:80]
charger_h = 20; // [8:1:40]
// Charging window: offset of its centre from the middle of the charger toward the
// whoop, and height of its centre above the floor.
port_x = 0; // [-30:0.5:30]
port_z = 10; // [9:0.5:30]
/* [Batteries] */
// 1S batteries in the lid, standing on their side. 0 for an empty lid.
battery_count = 8; // [0:1:12]
// Length with the plug, width (its height as it stands) and thickness.
battery_l = 62; // [40:1:80]
battery_w = 13.5; // [8:0.5:20]
battery_t = 9; // [5:0.5:14]
/* [Colour] */
// Display only, the STL carries no colour.
color_case = "SteelBlue"; // [SteelBlue, White, DimGray, Black, FireBrick, Goldenrod, ForestGreen, MediumPurple]
/* [Hidden] */
$fn = 48;
wall = 3.2;
floor_t = 2;
top_t = 2;
corner_r = 6;
bot_ch = 1.5;
top_ch = 2;
seam_ch = 0.6;
// deck that fills the base around the pockets, clearance around the whoop, room
// between the charger and its pocket walls for the springs
deck_h = 14;
c_clr = 0.5;
c_gap = 9;
// lip on the base rim, recess in the lid
lip_h = 2;
lip_t = 1.2;
lip_c = 0.25;
// clip-in springs: height, strip, pitch, socket lip, head depth and width, neck
// width, pad thickness, preload
sp_h = 6;
sp_st = 0.8;
sp_p = 2.5;
sp_lip = 1.6;
sp_hd = 2;
sp_hw = 8;
sp_nw = 3;
sp_pad = 2;
sp_pre = 2;
sp_slot = sp_lip + sp_hd + 0.3;
// window and slider
sl_w = 30;
sl_h = 16;
sl_t = 1.6;
sl_travel = 13;
det = 0.5;
// battery clips
bat_clr = 0.25;
arm_t = 1.4;
arm_w = 6;
arm_gap = 1.6;
bump = 0.7;
// hinges: knuckle radius, axis behind the rear face, segment width and gap
bolt_d = 3.4;
rk = 4;
hk = rk + 0.5;
seg_w = 8;
seg_gap = 0.4;
hinge_in = 22;
// latch
latch_w = 20;
latch_drop = 10;
tongue_t = 1.6;
sq = whoop_wheelbase / sqrt(2);
whoop_span = sq + duct_d;
drone_need = whoop_span + 2 * c_clr + 2.4;
charger_zone = charger_w + c_gap + sp_slot + 1.2;
bat_pitch = battery_t + 2 * (bat_clr + arm_t) + arm_gap;
Sx_in = max(charger_zone + drone_need, battery_count * bat_pitch + 8);
Sy_in = max(drone_need, charger_l + c_gap + sp_slot + 1.2, battery_l + 12);
Sx = Sx_in + 2 * wall;
Sy = Sy_in + 2 * wall;
fz = floor_t;
cav_h = max(whoop_h + 2, charger_h + 4, deck_h + 4);
base_h = fz + cav_h;
zc = base_h;
lid_cav = max(battery_w + 2.5, 9);
lid_h = top_t + lid_cav;
pb = zc + lid_h - top_t;
// charger pocket: the charger sits against the outer and the front wall
yf = Sy - wall;
cx_in = wall + charger_w + c_gap;
cy_r = yf - charger_l - c_gap;
c_len = c_gap + sp_pre;
// whoop pocket and its four spring sockets on the diagonals
dx = wall + charger_zone + (Sx_in - charger_zone) / 2;
dy = Sy / 2;
r_out = whoop_wheelbase / 2 + duct_d / 2 + c_clr;
w_len = r_out - (insert_wheelbase / 2 + insert_duct_d / 2) + sp_pre;
w_ok = w_len >= 8;
// window and slider
p_lim = max(0, charger_w / 2 - 9);
px = max(corner_r + sl_w / 2 + sl_t + 1.5, wall + charger_w / 2 + max(-p_lim, min(port_x, p_lim)));
pz = fz + max(9, min(port_z, cav_h - 22));
sl_z0 = pz - sl_h / 2;
// ---- 2D helpers ----
module outer2d() {
offset(r = corner_r) offset(delta = -corner_r) square([Sx, Sy]);
}
module inset2d(k) {
offset(r = -k) outer2d();
}
module ring2d(a, b) {
difference() { inset2d(a); inset2d(b); }
}
// Horizontal hole that prints without support: the tip points up in 2D y.
module teardrop2d(r) {
circle(r = r);
rotate(45) square(r);
}
// Whoop outline: four ducts on a square of side s, and the frame between them.
module whoop2d(s, d) {
for (sx = [-1, 1], sy = [-1, 1]) translate([sx * s / 2, sy * s / 2]) circle(d = d);
square(s, center = true);
}
// ---- Clip-in spring ----
// Printed flat. A zigzag of thin strips: each is a little wall that bends sideways,
// along its layers. A neck on the middle of the first strip carries a T head that
// drops into a socket from above; a stub on the middle of the last strip carries
// the pad, whose top front edge is chamfered so the whoop or charger pushes it back
// on the way down. Local frame: the pocket face at x = 0, the spring along +x,
// len is the relaxed length from the pocket face to the pad face.
module serp_spring(len, w, pw) {
x0 = 1;
avail = len - x0 - 1 - sp_pad;
n = max(2, floor((avail - sp_st) / sp_p) + 1);
p = (avail - sp_st) / (n - 1);
xf = len;
difference() {
linear_extrude(sp_h) {
translate([-sp_lip - sp_hd - 0.15, -sp_hw / 2]) square([sp_hd, sp_hw]);
translate([-sp_lip - 0.16, -sp_nw / 2]) square([sp_lip + 0.16 + x0 + 0.01, sp_nw]);
for (i = [0 : n - 1]) translate([x0 + i * p, -w / 2]) square([sp_st, w]);
for (i = [0 : n - 2]) translate([x0 + i * p, i % 2 == 0 ? w / 2 - sp_st : -w / 2]) square([p + sp_st, sp_st]);
translate([x0 + (n - 1) * p, -sp_nw / 2]) square([sp_st + 1.01, sp_nw]);
translate([xf - sp_pad, -pw / 2]) square([sp_pad, pw]);
}
translate([0, pw / 2 + 1, 0]) rotate([90, 0, 0]) linear_extrude(pw + 2)
polygon([[xf + 0.01, sp_h - 1.6], [xf + 0.01, sp_h + 0.01], [xf - 1.6, sp_h + 0.01]]);
}
}
// Vertical T slot behind a pocket face, open at the top, same local frame.
module spring_socket() {
translate([0, 0, -0.01]) linear_extrude(deck_h + 0.03) {
translate([-sp_lip - 0.01, -(sp_nw / 2 + 0.25)]) square([sp_lip + 0.03, sp_nw + 0.5]);
translate([-sp_slot, -(sp_hw / 2 + 0.2)]) square([sp_slot - sp_lip + 0.01, sp_hw + 0.4]);
}
}
// Where the charger springs sit: one in the middle of the rear face of the pocket,
// pushing the charger forward, one in the middle of its inner face, pushing it
// against the outer wall.
module at_charger_springs() {
translate([wall + charger_w / 2, cy_r, fz]) rotate(90) children();
translate([cx_in, yf - charger_l / 2, fz]) rotate(180) children();
}
// The four whoop springs, one on each diagonal at the outer point of a duct, all
// pushing toward the centre.
module at_whoop_springs() {
for (sx = [-1, 1], sy = [-1, 1])
translate([dx + sx * r_out / sqrt(2), dy + sy * r_out / sqrt(2), fz]) rotate(atan2(-sy, -sx)) children();
}
// ---- Hinge ----
// Knuckle profile in (y, z) about the hinge axis, which sits hk behind the rear face
// at rim height. Its underside runs into the wall at 45 degrees. The lid mirrors it
// about the rim, so both halves print without support.
module knuckle_profile() {
difference() {
hull() {
translate([-hk, 0]) circle(r = rk);
translate([-0.01, -hk - 1.42 * rk]) square([1, hk + 1.42 * rk - 2]);
}
translate([-hk, 0]) teardrop2d(bolt_d / 2);
}
}
module knuckle2d(upper) {
translate([0, zc]) {
if (upper) mirror([0, 1]) knuckle_profile();
else knuckle_profile();
}
}
// Two hinges, each base-lid-base on one M3 x 30 bolt with a nut.
module knuckles(upper) {
for (gx = [hinge_in, Sx - hinge_in], k = upper ? [0] : [-1, 1])
translate([gx + k * (seg_w + seg_gap) - seg_w / 2, 0, 0])
rotate([90, 0, 90]) linear_extrude(seg_w) knuckle2d(upper);
}
// ---- Base ----
module base_shell() {
difference() {
union() {
hull() {
linear_extrude(0.01) inset2d(bot_ch);
translate([0, 0, bot_ch]) linear_extrude(base_h - bot_ch - seam_ch) outer2d();
translate([0, 0, base_h - 0.01]) linear_extrude(0.01) inset2d(seam_ch);
}
// lip on the inner half of the rim, it locates the lid
translate([0, 0, base_h - 0.01]) linear_extrude(lip_h + 0.01) ring2d(wall - lip_t, wall);
}
translate([0, 0, floor_t]) linear_extrude(base_h + lip_h) inset2d(wall);
}
}
// The deck fills the base up to deck_h, around the whoop pocket and the charger
// pocket. Slice it with sparse infill.
module deck() {
translate([0, 0, fz - 0.01]) linear_extrude(deck_h + 0.01) difference() {
inset2d(wall - 0.01);
translate([dx, dy]) offset(r = c_clr) whoop2d(sq, duct_d);
translate([wall - 1, cy_r]) offset(r = 1) offset(delta = -1) square([cx_in - wall + 1, yf - cy_r + 1]);
}
}
// Window for the charging cable. The top corners are chamfered at 45 degrees, so
// the top is an 8 mm bridge.
module port_cut() {
translate([0, Sy + 1, 0]) rotate([90, 0, 0]) linear_extrude(wall + 2)
translate([px, pz]) polygon([[-7, -4.5], [7, -4.5], [7, 1.5], [4, 4.5], [-4, 4.5], [-7, 1.5]]);
}
// Dovetail channel in the outer face of the front wall, from under the window up
// through the rim. The slider sits flush in it and goes in from the top.
module slider_channel() {
a = sl_w / 2 + 0.2;
b = sl_w / 2 + sl_t + 0.2;
translate([px, 0, sl_z0]) linear_extrude(zc + lip_h + 1 - sl_z0)
polygon([[-a, Sy + 0.01], [a, Sy + 0.01], [b, Sy - sl_t], [b, Sy - sl_t - 0.3], [-b, Sy - sl_t - 0.3], [-b, Sy - sl_t]]);
}
// V grooves in the channel floor where the slider bump clicks in: closed and open.
module detents() {
for (zg = [sl_z0 + 5, sl_z0 + 5 + sl_travel])
translate([px + 8.5, 0, 0]) rotate([90, 0, 90]) linear_extrude(5)
polygon([[Sy - sl_t - 0.29, zg - det], [Sy - sl_t - 0.3 - det, zg], [Sy - sl_t - 0.29, zg + det]]);
}
// Boss inside the front wall behind the latch pocket, with a 45 degree underside.
module latch_boss() {
hull() {
translate([Sx / 2 - 14, yf - 3, zc - latch_drop - 3]) cube([28, 3.01, latch_drop + 3]);
translate([Sx / 2 - 14, yf - 0.01, zc - latch_drop - 6]) cube([28, 0.02, 0.01]);
}
}
// Recess for the flush tongue, a 45 degree lead-in over the wall top, the pocket the
// hook catches in, a scoop for a fingernail under the tongue, and a notch in the
// rim lip where the tongue passes.
module latch_cut() {
zt = zc - latch_drop;
translate([Sx / 2 - latch_w / 2 - 0.5, 0, 0]) rotate([90, 0, 90]) linear_extrude(latch_w + 1) {
translate([Sy - 1.9, zt - 0.5]) square([3, latch_drop + 2]);
polygon([[Sy - 1.89, zc - 1], [Sy - 1.89, zc + 1], [Sy - 2.9, zc + 1], [Sy - 2.9, zc]]);
polygon([[Sy - 1.89, zt - 0.2], [Sy - 2.9, zt + 0.81], [Sy - 2.9, zt + 1.59], [Sy - 1.89, zt + 2.6]]);
polygon([[Sy - 1.9, zt - 0.49], [Sy + 1, zt - 0.49], [Sy + 1, zt - 3.4]]);
}
translate([Sx / 2 - latch_w / 2 - 0.6, Sy - wall - 0.2, zc - 0.01]) cube([latch_w + 1.2, wall + 1.2, lip_h + 1]);
}
module base() {
difference() {
union() {
base_shell();
knuckles(false);
deck();
latch_boss();
}
at_charger_springs() spring_socket();
at_whoop_springs() spring_socket();
port_cut();
slider_channel();
detents();
latch_cut();
}
}
// ---- Slider ----
// Prints front face down; its edges flare at 45 degrees into the dovetail. A tongue
// cut into it beside the window bends along its layers and carries a bump on the
// back that clicks into the grooves. Local frame: x across, y up the wall, z from
// the front face to the back face.
module slider_flat() {
tx0 = sl_w / 2 + 9;
difference() {
hull() {
cube([sl_w, sl_h, 0.01]);
translate([-sl_t, 0, sl_t - 0.01]) cube([sl_w + 2 * sl_t, sl_h, 0.01]);
}
translate([tx0 - 0.7, 3.3, -1]) cube([5.4, 9.7, sl_t + 2]);
}
translate([tx0, 4, 0]) cube([4, 9.01, sl_t]);
translate([tx0, 0, sl_t - 0.01]) rotate([90, 0, 90]) linear_extrude(4) polygon([[4, 0], [5, det], [6, 0]]);
}
module slider_placed() {
multmatrix([[1, 0, 0, px - sl_w / 2], [0, 0, -1, Sy], [0, 1, 0, sl_z0], [0, 0, 0, 1]]) slider_flat();
}
// ---- Lid ----
module lid_shell() {
difference() {
translate([0, 0, zc]) hull() {
linear_extrude(0.01) inset2d(seam_ch);
translate([0, 0, seam_ch]) linear_extrude(lid_h - seam_ch - top_ch) outer2d();
translate([0, 0, lid_h - 0.01]) linear_extrude(0.01) inset2d(top_ch);
}
translate([0, 0, zc - 1]) linear_extrude(lid_cav + 1) inset2d(wall);
// recess for the lip of the base
translate([0, 0, zc - 1]) linear_extrude(lip_h + 0.3 + 1) ring2d(wall - lip_t - lip_c, wall + 0.01);
}
}
// The tongue is the outer skin of the lid front, carried on down past the rim into
// the recess of the base. Its hook rides over the lead-in and catches in the pocket.
module latch_tongue() {
zt = zc - latch_drop;
translate([Sx / 2 - latch_w / 2, 0, 0]) rotate([90, 0, 90]) linear_extrude(latch_w) {
translate([Sy - tongue_t, zt]) square([tongue_t, latch_drop + 4]);
polygon([[Sy - tongue_t + 0.01, zt], [Sy - 2.7, zt + 1.1], [Sy - 2.7, zt + 1.3], [Sy - tongue_t + 0.01, zt + 2.4]]);
}
}
// Two slits and a relief behind the tongue free its upper 4 mm from the lid wall,
// so it flexes over latch_drop plus 4 mm.
module tongue_relief() {
for (s = [-1, 1]) translate([Sx / 2 + s * (latch_w / 2 + 0.3) - 0.3, Sy - wall - 1, zc - 1]) cube([0.6, wall + 2, 5]);
translate([Sx / 2 - latch_w / 2 - 0.01, Sy - wall - 1, zc - 1]) cube([latch_w + 0.02, wall + 1 - tongue_t, 5]);
}
function bat_x(i) = Sx / 2 + (i - (battery_count - 1) / 2) * bat_pitch;
// 1S batteries stand on their side, thickness across x, plug end along y. Two pairs
// of arms per battery, each with a 45 degree bump that snaps over its free edge.
// arm_gap between neighbouring arms leaves room for an arm to give while the next
// slot is full.
module bat_clips() {
if (battery_count > 0)
for (i = [0 : battery_count - 1], yc = [Sy / 2 - 0.28 * battery_l, Sy / 2 + 0.28 * battery_l], s = [-1, 1]) {
f = bat_x(i) + s * (battery_t / 2 + bat_clr);
zb = pb - battery_w - 0.15;
translate([0, yc + arm_w / 2, 0]) rotate([90, 0, 0]) linear_extrude(arm_w) {
translate([s < 0 ? f - arm_t : f, pb - battery_w - 1.6]) square([arm_t, battery_w + 1.6 + 0.01]);
polygon([[f + s * 0.01, zb], [f - s * bump, zb - bump], [f + s * 0.01, zb - 2 * bump]]);
}
}
}
module lid() {
difference() {
union() {
lid_shell();
knuckles(true);
bat_clips();
latch_tongue();
}
tongue_relief();
}
}
module lid_print() {
translate([0, Sy, zc + lid_h]) rotate([180, 0, 0]) lid();
}
// ---- Spring sets, laid out flat for printing ----
module charger_springs_flat() {
for (k = [0, 1]) translate([sp_slot, 8 + k * 18, 0]) serp_spring(c_len, 14, 12);
}
module whoop_springs_flat() {
if (w_ok) for (k = [0 : 3]) translate([sp_slot, 7 + k * 16, 0]) serp_spring(w_len, 12, 10);
else echo("whoop springs: the smaller whoop is too close in size to the pocket, no room for springs");
}
// ---- Ghost contents ----
module ghost_whoop(wb, dd) {
s = wb / sqrt(2);
color("Gainsboro", 0.5) translate([dx, dy, fz]) {
for (sx = [-1, 1], sy = [-1, 1]) translate([sx * s / 2, sy * s / 2, 0])
difference() { cylinder(d = dd, h = 16); translate([0, 0, -1]) cylinder(d = dd - 2, h = 18); }
translate([-12, -16, 0]) cube([24, 32, whoop_h]);
}
}
module ghost_charger() {
color("OrangeRed", 0.5) translate([wall, yf - charger_l, fz]) cube([charger_w, charger_l, charger_h]);
}
module ghost_batteries() {
if (battery_count > 0)
color("DimGray", 0.6) for (i = [0 : battery_count - 1])
translate([bat_x(i) - battery_t / 2, Sy / 2 - battery_l / 2, pb - battery_w]) cube([battery_t, battery_l, battery_w]);
}
// ---- Test coupons ----
module coupon(lo, hi) {
translate(-lo) intersection() { children(); translate(lo) cube(hi - lo); }
}
module test_layout() {
g = 46;
// hinge, base and lid half
coupon([hinge_in - 16, -hk - rk - 1, zc - 14], [hinge_in + 16, 6, zc + lip_h + 1]) base();
translate([0, 22, 0]) coupon([hinge_in - 16, Sy - 6, 0], [hinge_in + 16, Sy + hk + rk + 1, lid_h + 12]) lid_print();
// latch, base and lid half
translate([g, 0, 0]) coupon([Sx / 2 - 16, Sy - 7, zc - latch_drop - 8], [Sx / 2 + 16, Sy + 0.1, zc + lip_h + 1]) base();
translate([g, 22, 0]) coupon([Sx / 2 - 16, -0.1, 0], [Sx / 2 + 16, 7, lid_h + latch_drop + 1]) lid_print();
// slider channel with the window, and the slider
translate([2 * g, 0, 0]) coupon([px - 20, Sy - 5, sl_z0 - 3], [px + 20, Sy + 0.1, zc + 0.5]) base();
translate([2 * g + 6, 12, 0]) slider_flat();
// the inner charger spring socket with a piece of deck, and one charger spring
translate([3 * g, 0, 0])
coupon([cx_in - 8, yf - charger_l / 2 - 9, 0], [cx_in + sp_slot + 3, yf - charger_l / 2 + 9, fz + deck_h]) base();
translate([3 * g + sp_slot, 30, 0]) serp_spring(c_len, 14, 12);
// one battery clip
if (battery_count > 0) {
yc = Sy / 2 - 0.28 * battery_l;
bx = bat_x(0);
translate([4 * g, 0, 0])
coupon([bx - bat_pitch / 2 - 1, Sy - yc - arm_w / 2 - 4, 0], [bx + bat_pitch / 2 + 1, Sy - yc + arm_w / 2 + 4, lid_h + 1]) lid_print();
}
}
// ---- Layout ----
module assembly() {
color(color_case) base();
color(color_case) slider_placed();
color(color_case) at_charger_springs() serp_spring(c_len - sp_pre, 14, 12);
if (show_insert && w_ok) color(color_case) at_whoop_springs() serp_spring(w_len - sp_pre, 12, 10);
if (show_contents) {
if (show_insert && w_ok) ghost_whoop(insert_wheelbase, insert_duct_d);
else ghost_whoop(whoop_wheelbase, duct_d);
ghost_charger();
}
// lid swung open flat behind the base
translate([0, -hk, zc]) rotate([180, 0, 0]) translate([0, hk, -zc]) {
color(color_case) lid();
if (show_contents) ghost_batteries();
}
}
if (test_print) color(color_case) test_layout();
else if (part == "base") color(color_case) base();
else if (part == "lid") color(color_case) lid_print();
else if (part == "slider") color(color_case) slider_flat();
else if (part == "charger_springs") color(color_case) charger_springs_flat();
else if (part == "whoop_springs") color(color_case) whoop_springs_flat();
else assembly();
Comments
Context, not a score. Comments never move a model up or down the results.No comments yet
Printed it? A note about your material and the clearance you landed on saves the next person a failed print.
