Read this model as text
A hinged travel case for a tiny whoop, its charger and a row of 1S batteries, designed for PETG. Seen from the front with the lid open behind, like a tool case, the whoop sits on the left in a low locating ring shaped around its ducts and the charger on the right, pressed into the front right corner by two springs. A window in the front wall in front of the charger takes its charging cable. The lid holds the batteries standing on their side in snap clips. Two pin hinges at the rear, each on one M3 bolt, and a snap latch at the front.
The image shows the solid OpenSCAD renders at part assembly · test_print false · show_contents false · whoop_wheelbase 80 · duct_d 50 · whoop_h 40 · charger_l 90 · charger_w 45 · charger_h 20 · port_x 0 · port_z 8 · battery_count 8 · battery_l 62 · battery_w 13.5 · battery_t 9 · color_case SteelBlue.
Tiny whoop case with sprung charger bay and 1S battery clips
mick/whoop-case@1.1.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. Seen from the front with the lid open behind, like a tool case, the whoop sits on the left in a low locating ring shaped around its ducts and the charger on the right, pressed into the front right corner by two springs. A window in the front wall in front of the charger takes its charging cable. The lid holds the batteries standing on their side in snap clips. Two pin hinges at the rear, each on one M3 bolt, and a snap latch at the front.
What you use it for
Carrying a 65, 75 or 85 mm whoop with its charger and spare 1S batteries in one box, with the charger in a fixed spot so it can charge in the case. Set the whoop by its diagonal wheelbase, duct diameter and height, the charger by its footprint and height, the port by its position, and the batteries by count and size; the case grows around them. The springs take a charger from 1.5 mm smaller to about 3.5 mm larger than set, in length and in width, so a variant fits a few chargers. Push the charger straight down: its bottom edge rides down a ramp on each spring pad, pushes the springs back and they snap over its top edge. Pull a spring back with a finger to take it out.
Design notes
Everything prints without supports and nothing needs hardware but the two hinge bolts. The charger springs are upright leaves that hang from the wall at one end and bend sideways. Printed upright, a sideways bend loads them along their layers, which is what lets a PETG spring take 5 mm of travel again and again. Each leaf stands on the bed in a slot through the floor, 0.6 mm clear all round, so it is joined to the case only at its root. The pad at the free end has a 45 degree ramp on top that runs into the catch lip, and the lip underside is 45 degrees as well. The battery arms hang from the lid plate and bend across their layers, which PETG takes at their small deflection; there is 1.6 mm between neighbouring arms, so an arm has room to give even when the next slot is full. The charging window has a 45 degree roof instead of a flat top. The base prints upright, the lid upside down on its top plate. Hinge knuckles run into the walls at 45 degrees and their bolt holes are teardrops; each hinge is base, lid, base on an M3 x 30 with a nut. A lip on the inner half of the base rim locates the lid. With the defaults the case is about 175 x 118 mm and fits a 180 mm bed. test_print renders a coupon set: one hinge and the latch in both halves, one charger spring with its slot and wall, and one battery clip.
Parameters
part picks the assembly or one part in print orientation, test_print swaps it for the coupon set, show_contents shows the whoop, charger and batteries as ghosts. whoop_wheelbase is motor to motor across the diagonal, duct_d the outer diameter of one duct, whoop_h the total height with the camera. charger_l and charger_w are the charger footprint, charger_h its height, where the springs catch over its top edge. port_x moves the charging 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 when the battery stands on its side) and battery_t (its thickness) 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.1.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 | ||
| test_print | false | |||
| 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 | ||
| 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 | 8 | 3 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.1.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. Seen from the front with the lid open behind, the whoop sits on
// the left in a locating ring and the charger on the right, pressed into the front
// right corner by two springs, with a window in the front wall for its charging
// cable. The lid holds the batteries standing on their side in snap clips. Two pin
// hinges with an M3 bolt each at the rear, a snap latch at the front. 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 charger corner is at low x, high y.
// 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]
// Print a coupon set instead: one hinge and the latch in base and lid halves, one
// charger spring and one battery clip. Checks every fit in about an hour.
test_print = false;
// Show the whoop, the charger and the batteries as ghosts in the assembly.
show_contents = false;
/* [Whoop] */
// Motor to motor across the diagonal. A 75 mm class whoop is about 80.
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]
/* [Charger] */
// Charger footprint, port end against the front wall. The springs take a charger
// from 1.5 mm smaller to about 3.5 mm larger, in both directions.
charger_l = 90; // [40:1:140]
charger_w = 45; // [20:1:80]
// Height of the charger: the springs catch over its top edge.
charger_h = 20; // [8:1:40]
// Charging window in the front wall: 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 = 8; // [3: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 = 2.6;
floor_t = 2;
top_t = 2;
corner_r = 4;
// whoop locating ring
w_clr = 1;
ring_t = 1.2;
ring_h = 3;
// lip on the base rim, recess in the lid
lip_h = 2;
lip_t = 1.2;
lip_c = 0.25;
// charger springs: leaf thickness, travel room behind it, preload into the charger,
// pad depth from the leaf face, pad width, slot clearance
s_t = 1.6;
travel = 5;
preload = 1.5;
pad_d = 3;
pad_w = 6;
slot_c = 0.6;
// 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 = 8;
tongue_t = 1.6;
ridge_d = 1;
latch_gap = 0.3;
sq = whoop_wheelbase / sqrt(2);
whoop_span = sq + duct_d;
drone_need = whoop_span + 2 * (w_clr + ring_t) + 2;
spring_room = (pad_d - preload) + s_t + travel + slot_c + 2;
left_need = charger_w + spring_room;
bat_pitch = battery_t + 2 * (bat_clr + arm_t) + arm_gap;
Sx_in = max(left_need + drone_need, battery_count * bat_pitch + 8);
Sy_in = max(drone_need, charger_l + spring_room, battery_l + 12);
Sx = Sx_in + 2 * wall;
Sy = Sy_in + 2 * wall;
fz = floor_t;
cav_h = max(whoop_h + 2, charger_h + 6);
base_h = floor_t + 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: outer side against the wall at low x, front face against the front wall
xi = wall + charger_w;
yf = Sy - wall;
yr = yf - charger_l;
// spring along the rear face (from the side wall) and along the inner side (from
// the front wall); free lengths
la = min(0.6 * charger_w + 3, charger_w - 2);
lb = min(max(25, 0.55 * charger_l), charger_l - 6);
// underside of the catch lip on the spring pads
zl0 = fz + charger_h + 0.2;
// whoop centre, in the zone right of the charger springs
dx = wall + left_need + (Sx_in - left_need) / 2;
dy = Sy / 2;
// latch ridge
zr = zc - latch_drop + 2.4;
// ---- 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);
}
module whoop2d() {
for (sx = [-1, 1], sy = [-1, 1]) translate([sx * sq / 2, sy * sq / 2]) circle(d = duct_d);
square(sq, center = true);
}
// ---- 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(0.5);
translate([0, 0, 0.5]) linear_extrude(base_h - 0.5) outer2d();
}
// 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);
}
}
// Low ring around the ducts and the frame: the whoop drops in and cannot slide.
module whoop_ring() {
translate([dx, dy, fz - 0.01]) linear_extrude(ring_h + 0.01)
difference() {
offset(r = w_clr + ring_t) whoop2d();
offset(r = w_clr) whoop2d();
}
}
// Ridge on the front wall that the latch hook catches; both faces at 45 degrees.
module latch_ridge() {
translate([Sx / 2 - (latch_w + 4) / 2, 0, 0]) rotate([90, 0, 90]) linear_extrude(latch_w + 4)
polygon([[Sy - 0.5, zr - 1.5], [Sy + ridge_d, zr], [Sy + ridge_d, zr + 0.6], [Sy - 0.5, zr + 2.1]]);
}
// ---- Charger springs ----
// An upright leaf that hangs from a wall at its root (u = 0) and bends sideways.
// Printed upright, a sideways bend loads it along its layers. It stands on the bed
// in a slot through the floor, so it is joined to the case only at the root. The
// pad at the free end reaches preload into the charger; its top is a 45 degree ramp
// the charger edge pushes back on the way down, ending in a lip that catches over
// the charger. Local frame: u along the leaf, v toward the charger, z up.
module spring(len) {
translate([-0.5, -s_t, 0]) cube([len + 0.5, s_t, zl0 + 2]);
translate([len - pad_w, 0, 0]) rotate([90, 0, 90]) linear_extrude(pad_w)
polygon([[-0.01, 0], [pad_d, 0], [pad_d, zl0], [pad_d + 1, zl0 + 1], [-0.01, zl0 + pad_d + 2.01]]);
}
module spring_slot(len) {
translate([0, -s_t - travel - slot_c, -1])
cube([len + slot_c, s_t + travel + slot_c + pad_d + slot_c, fz + 1.01]);
}
// Where the two springs sit: one along the rear face of the charger, hanging from
// the side wall and pushing it forward; one along its inner side, hanging from the
// front wall and pushing it against the side wall.
module spring_a() {
translate([wall, yr - (pad_d - preload), 0]) children();
}
module spring_b() {
translate([xi + (pad_d - preload), yf, 0]) mirror([1, 0, 0]) rotate(-90) children();
}
// Window for the charging cable, with a 45 degree roof instead of a flat top.
module port_cut() {
lim = max(0, charger_w / 2 - 8);
px = wall + charger_w / 2 + max(-lim, min(port_x, lim));
pz = fz + min(port_z, cav_h - 12);
translate([0, Sy + 1, 0]) rotate([90, 0, 0]) linear_extrude(wall + 2)
translate([px, pz]) polygon([[-7, -4.5], [7, -4.5], [7, 2.5], [0, 9.5], [-7, 2.5]]);
}
module base() {
difference() {
union() {
base_shell();
knuckles(false);
whoop_ring();
latch_ridge();
}
spring_a() spring_slot(la);
spring_b() spring_slot(lb);
port_cut();
}
spring_a() spring(la);
spring_b() spring(lb);
}
// ---- Lid ----
module lid_shell() {
difference() {
translate([0, 0, zc]) hull() {
linear_extrude(lid_h - 0.5) outer2d();
translate([0, 0, lid_h - 0.01]) linear_extrude(0.01) inset2d(0.5);
}
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);
}
}
// Snap tongue on the front. Its hook catches under the ridge of the base; the lip
// at its end is for a fingernail.
module latch_tongue() {
yt = Sy + ridge_d + latch_gap;
zt = zc - latch_drop - 3;
translate([Sx / 2 - latch_w / 2, 0, 0]) rotate([90, 0, 90]) linear_extrude(latch_w) {
translate([Sy - 0.5, zc + 4]) square([yt + tongue_t - (Sy - 0.5), lid_h - 4]);
translate([yt, zt]) square([tongue_t, zc + 4.01 - zt]);
polygon([[yt + 0.01, zr + 0.2], [Sy + latch_gap, zr - 0.8], [Sy + latch_gap, zr - 1.4], [yt + 0.01, zr - 2.4]]);
polygon([[yt + tongue_t - 0.01, zt], [yt + tongue_t + 1.5, zt], [yt + tongue_t - 0.01, zt + 1.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() {
lid_shell();
knuckles(true);
bat_clips();
latch_tongue();
}
module lid_print() {
translate([0, Sy, zc + lid_h]) rotate([180, 0, 0]) lid();
}
// ---- Ghost contents ----
module ghost_whoop() {
color("Gainsboro", 0.5) translate([dx, dy, fz]) {
for (sx = [-1, 1], sy = [-1, 1]) translate([sx * sq / 2, sy * sq / 2, 0])
difference() { cylinder(d = duct_d, h = 16); translate([0, 0, -1]) cylinder(d = duct_d - 2, h = 18); }
translate([-12, -16, 0]) cube([24, 32, whoop_h]);
}
}
module ghost_charger() {
color("OrangeRed", 0.5) translate([wall, yr, 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 = 42;
// 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 - 6, zc - 14], [Sx / 2 + 16, Sy + 5, zc + lip_h + 1]) base();
translate([g, 22, 0]) coupon([Sx / 2 - 16, -6, 0], [Sx / 2 + 16, 6, lid_h + latch_drop + 5]) lid_print();
// the spring on the inner side of the charger, with its floor slot and wall root
translate([2 * g, 0, 0])
coupon([xi - 4, yf - lb - 4, 0], [xi + (pad_d - preload) + s_t + travel + 1.5, Sy + 0.1, zl0 + pad_d + 3]) base();
// one battery clip
if (battery_count > 0) {
yc = Sy / 2 - 0.28 * battery_l;
bx = bat_x(0);
translate([2 * g + 24, 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();
if (show_contents) { ghost_whoop(); 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 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.
