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. The base is one shell for every combination; two drop-in trays go inside. The whoop tray has a pocket shaped around the ducts of one whoop, so a new whoop only needs a new tray. The charger tray holds a BetaFPV charger in the front right corner (seen from the front with the lid open behind) with two click jaws: each slides in a dovetail groove in the tray and ratchets against a row of teeth, so you push it against the charger until it clicks and it stays there, and its lip hooks over the top edge of the charger. No nuts or bolts in the charger tray. A slider sits flush in the front wall over the charging window: pull it up to open, push it down to close; it clicks in both positions. 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_contents false · whoop_wheelbase 80 · duct_d 50 · whoop_h 40 · charger_h 19 · port_from_side 23 · port_z 9.5 · battery_class LAVA 480-680 · battery_count 8 · color_case SteelBlue.
Tiny whoop case with drop-in trays, click jaws and a port slider
mick/whoop-case@2.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, with a smooth outside. The base is one shell for every combination; two drop-in trays go inside. The whoop tray has a pocket shaped around the ducts of one whoop, so a new whoop only needs a new tray. The charger tray holds a BetaFPV charger in the front right corner (seen from the front with the lid open behind) with two click jaws: each slides in a dovetail groove in the tray and ratchets against a row of teeth, so you push it against the charger until it clicks and it stays there, and its lip hooks over the top edge of the charger. No nuts or bolts in the charger tray. A slider sits flush in the front wall over the charging window: pull it up to open, push it down to close; it clicks in both positions. 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 up to the size of a Meteor75 Pro II (80 mm wheelbase, 50 mm ducts) with a BetaFPV charger and spare 1S batteries in one box. The whoop tray takes the wheelbase and duct diameter of the whoop it is for; the shell stays the same. The charger tray fits the BetaFPV 6-port charger (79 x 46 x 19 mm) and the BetaFPV BT2.0 charger board (75 x 42 x 22 mm): the side jaw covers widths from 41.5 to 46.5 mm, the rear jaw lengths from 70.5 to 80.5 mm, in 1 mm clicks. Set charger_h to the real height of the charger so the jaw lips catch its top edge. To fit or take out the charger, lift the post at the back of a jaw and slide the jaw back. The battery clips take the LAVA family: 480, 550, 580 and 680 mAh (15 to 15.5 mm wide, up to 7 mm thick, up to 80 mm long) or 280 and 320 mAh (10 mm wide).
Design notes
Everything prints without supports. The trays print upright and have no floor under the pockets, so the whoop and the charger stand on the floor of the shell; the rest of each tray is solid, so slice it with sparse infill. The jaw grooves are dovetails with 45 degree lips, and the teeth on their floor are a sawtooth that rises toward the charger. A jaw prints on its side: its slider matches the groove, its lower slab is a pawl arm freed by a thin gap, rooted at the front, with a tooth under its rear end, and a post rises from that end through a slot in the jaw to above its top. Printed on its side, the pawl bends along its layers. Lifting the post lifts the tooth out of the teeth. The slider prints back face down, its edges narrowing at 45 degrees into the dovetail of its channel, which runs straight up to the rim so the slider goes in from the top and the closed lid keeps it there. A tongue cut into it, bending along its layers, has a hole at its tip that clicks over a bump in the channel at the open and the closed position; two grooves across its top give the thumb a grip. The window has chamfered top corners, so its top is an 8 mm bridge. 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 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 and catches in a pocket backed by a boss inside the front wall, with a scoop under it for a fingernail. Battery arms hang from the lid plate and bend across their layers, which PETG takes at their small deflection. The only hardware is the two hinge pins: each hinge is base, lid, base on an M3 x 30 with a nut. The case is about 179 x 117 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 strip of charger tray with its groove and the side jaw, and one battery clip.
Parameters
part picks the assembly or one part in print orientation; jaws lays out the side and the rear jaw. test_print swaps it for the coupon set, show_contents shows the whoop, charger and batteries as ghosts. whoop_wheelbase (motor to motor across the diagonal) and duct_d (outer diameter of one duct) set the pocket of the whoop tray, up to 80 and 50; whoop_h (total height with camera) sets the height of the case. charger_h is the height of the charger, where the jaw lips catch. port_from_side is the distance from the outer side of the charger to the middle of its port, port_z the height of the middle of the port above the floor. battery_class picks the LAVA size family, battery_count the number of 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@2.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, whoop_tray, charger_tray, jaws, slider | ||
| test_print | false | |||
| show_contents | false | |||
| whoop_wheelbase | 80 | 55 to 80 step 0.5 | ||
| duct_d | 50 | 28 to 50 step 0.5 | ||
| whoop_h | 40 | 20 to 60 step 1 | ||
| charger_h | 19 | 14 to 24 step 0.5 | ||
| port_from_side | 23 | 10 to 40 step 0.5 | ||
| port_z | 9.5 | 6 to 20 step 0.5 | ||
| battery_class | "LAVA 480-680" | LAVA 480-680, LAVA 280-320 | ||
| battery_count | 8 | 0 to 12 step 1 | ||
| 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@2.1.0 is MIT
Source
main.scad is the entrypointmain.scad
// Tiny whoop case: a whoop, its BetaFPV charger and a row of 1S batteries in one
// hinged box, printed in PETG, with a smooth outside. One shell for everything and
// two drop-in trays: a whoop tray with a pocket shaped around the ducts of one
// whoop, and a charger tray with two click jaws that ratchet against the charger.
// A flush slider opens and closes the charging window. The lid holds the batteries
// on their side in snap clips. 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 sits in the corner 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, whoop_tray, charger_tray, jaws, slider]
// Print a coupon set instead: one hinge and the latch in base and lid halves, the
// slider channel with the slider, a strip of charger tray with the side jaw, and
// one battery clip.
test_print = false;
// Show the whoop, the charger and the batteries as ghosts in the assembly.
show_contents = false;
/* [Whoop] */
// The whoop the whoop tray is for. Motor to motor across the diagonal: 80 for a
// Meteor75 Pro II, the largest the case takes.
whoop_wheelbase = 80; // [55:0.5:80]
// Outer diameter of one duct, up to 50.
duct_d = 50; // [28:0.5:50]
// Total height of the whoop, camera included: sets the height of the case.
whoop_h = 40; // [20:1:60]
/* [Charger] */
// Height of the charger: the jaw lips catch its top edge. 19 for the BetaFPV 6-port
// charger, 22 for the BT2.0 charger board.
charger_h = 19; // [14:0.5:24]
// Charging port: distance from the outer side of the charger to the middle of the
// port, and height of the middle of the port above the floor.
port_from_side = 23; // [10:0.5:40]
port_z = 9.5; // [6:0.5:20]
/* [Batteries] */
// LAVA size family: 480 to 680 mAh (15 mm wide), or 280 and 320 mAh (10 mm wide).
battery_class = "LAVA 480-680"; // [LAVA 480-680, LAVA 280-320]
// Batteries in the lid, standing on their side. 0 for an empty lid.
battery_count = 8; // [0:1:12]
/* [Colour] */
// Display only, the STL carries no colour.
color_case = "SteelBlue"; // [SteelBlue, White, DimGray, Black, FireBrick, Goldenrod, ForestGreen, MediumPurple]
/* [Hidden] */
$fn = 64;
wall = 3.2;
floor_t = 2;
top_t = 2;
corner_r = 6;
bot_ch = 1.5;
top_ch = 2;
seam_ch = 0.6;
// trays: clearance in the shell, whoop tray height, charger tray height, clearance
// around the whoop, the largest whoop the shell takes
t_clr = 0.3;
deck_h = 14;
ct_h = 12;
c_clr = 0.5;
max_wb = 80;
max_dd = 50;
// charger: the largest footprint the jaws open to, the ghost charger (BetaFPV
// 6-port), the length of the side and the rear jaw (their travel is 9 less)
ch_w = 46.5;
ch_l = 80.5;
gh_w = 46;
gh_l = 79;
l_side = 14;
l_rear = 19;
jaw_h = charger_h - ct_h + 3.6;
// port slider: width, height, thickness, travel, tongue centre from its left edge
sl_w = 26;
sl_h = 16;
sl_t = 1.6;
sl_travel = 13;
tg_x = sl_w / 2 + 9.5;
// lip on the base rim, recess in the lid
lip_h = 2;
lip_t = 1.2;
lip_c = 0.25;
// battery clips
big_bat = battery_class == "LAVA 480-680";
battery_w = big_bat ? 15.5 : 10.5;
battery_t = big_bat ? 7 : 6.5;
battery_l = big_bat ? 80 : 64;
bat_clr = 0.25;
arm_t = 1.4;
arm_w = 6;
arm_gap = 1.6;
bump = 0.7;
clip_y = 17;
// hinges: pin hole, 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_zone = max_wb / sqrt(2) + max_dd + 2 * c_clr + 2.4;
// charger zone across x: charger, side jaw groove, wall behind it
x_pw = wall + ch_w;
x_cz = x_pw + l_side + 0.5 + 1.5;
bat_pitch = battery_t + 2 * (bat_clr + arm_t) + arm_gap;
Sx_in = max(x_cz - wall + 2 * t_clr + whoop_zone, battery_count * bat_pitch + 8);
Sy_in = max(whoop_zone + 2 * t_clr, ch_l + l_rear + 0.5 + 1.5 + t_clr, 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, 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 along y: front face on the front wall, rear jaw behind it
yf = Sy - wall;
y_pr = yf - ch_l;
y_ji = yf - 40;
x_jr = wall + ch_w / 2;
// whoop centre
dx = (x_cz + Sx - wall) / 2;
dy = Sy / 2;
// charging window and slider
px = max(corner_r + sl_w / 2 + sl_t + 1.5, min(wall + port_from_side, x_cz - sl_w / 2 - 3));
pz = fz + max(9.5, min(port_z, cav_h - 23));
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);
}
// The charging window: 14 x 9 with chamfered top corners, so the top is an 8 mm
// bridge.
module win2d() {
polygon([[-7, -4.5], [7, -4.5], [7, 1.5], [4, 4.5], [-4, 4.5], [-7, 1.5]]);
}
// ---- 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 shell ----
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);
}
}
module port_cut() {
translate([0, Sy + 1, 0]) rotate([90, 0, 0]) linear_extrude(wall + 2) translate([px, pz]) win2d();
}
// Dovetail channel in the outer face of the front wall, from under the window up
// through the rim: narrow at the face, wide behind. The slider sits flush in it and
// goes in from the top; the closed lid keeps it in.
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]]);
}
// Two bumps on the channel floor: the hole in the slider tongue clicks over the
// lower one when closed and over the upper one when open.
module slider_bumps() {
intersection() {
for (zb = [sl_z0 + 4, sl_z0 + 4 + sl_travel]) translate([px - sl_w / 2 + tg_x, Sy - sl_t - 0.6, zb]) sphere(r = 0.9, $fn = 24);
translate([px, Sy - sl_t - 0.31, sl_z0]) cube([sl_w, 2, sl_h + sl_travel + 4]);
}
}
// 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);
latch_boss();
}
port_cut();
slider_channel();
latch_cut();
}
slider_bumps();
}
// ---- Whoop tray ----
// Fills the whoop side of the shell up to deck_h, with a pocket around the ducts of
// one whoop. No floor under the pocket: the whoop stands on the floor of the shell.
module whoop_tray() {
translate([0, 0, fz]) linear_extrude(deck_h) difference() {
intersection() {
inset2d(wall + t_clr);
translate([x_cz + t_clr, 0]) square([Sx, Sy]);
}
translate([dx, dy]) offset(r = c_clr) whoop2d(sq, duct_d);
}
}
// ---- Charger tray and click jaws ----
// The tray fills the charger side up to ct_h, with a pocket in the front right corner
// open to the front and the outer wall, so the charger meets both. Behind the pocket
// run two dovetail grooves, one for each jaw, with a row of sawtooth teeth on their
// floor that rise toward the charger. Local frame of a groove or a jaw: u toward the
// charger, w across, z from the top of the tray; the pocket face at u = 0.
module jaw_groove(lc) {
translate([-lc, 0, 0]) rotate([90, 0, 90]) linear_extrude(lc + 1)
polygon([[-4.0, 0.01], [4.0, 0.01], [5.5, -1.5], [5.5, -5.3], [-5.5, -5.3], [-5.5, -1.5]]);
}
module jaw_teeth(lc) {
translate([0, 5.5, 0]) rotate([90, 0, 0]) linear_extrude(11)
for (i = [0 : floor(lc) - 1]) polygon([[-lc + i, -5.31], [-lc + i + 1, -4.5], [-lc + i + 1, -5.31]]);
}
module at_side_groove() {
translate([x_pw, y_ji, fz + ct_h]) rotate(180) children();
}
module at_rear_groove() {
translate([x_jr, y_pr, fz + ct_h]) rotate(90) children();
}
module charger_tray() {
difference() {
translate([0, 0, fz]) linear_extrude(ct_h) difference() {
intersection() {
inset2d(wall + t_clr);
square([x_cz - t_clr, Sy]);
}
translate([-1, y_pr]) square([x_pw + 1, Sy]);
}
at_side_groove() jaw_groove(l_side + 0.5);
at_rear_groove() jaw_groove(l_rear + 0.5);
}
at_side_groove() jaw_teeth(l_side + 0.5);
at_rear_groove() jaw_teeth(l_rear + 0.5);
}
// A click jaw, length len. The body rides on the tray, the slider below it runs in
// the dovetail. The lower slab of the slider is a pawl arm, freed by a thin gap and
// rooted at the front, with a tooth under its rear end that drops into the teeth of
// the groove: the jaw slides toward the charger and clicks, but not back. A post on
// the rear end of the arm rises through a slot to above the jaw: lift it to slide
// the jaw back. The lip on the face has a 45 degree underside and hooks over the
// charger.
module jaw(len) {
zl = charger_h - ct_h + 0.3;
zb = -4.2;
difference() {
union() {
translate([-len, -5.3, 0]) cube([len, 10.6, jaw_h]);
translate([-len, 0, 0]) rotate([90, 0, 90]) linear_extrude(len)
polygon([[-3.8, 0.01], [3.8, 0.01], [5.3, -1.5], [5.3, zb], [-5.3, zb], [-5.3, -1.5]]);
translate([0, 5.3, 0]) rotate([90, 0, 0]) linear_extrude(10.6) {
polygon([[-0.01, zl], [1.2, zl + 1.2], [1.2, zl + 1.6], [-0.01, zl + 2.8]]);
polygon([[-len + 0.3, zb + 0.01], [-len + 0.3, zb - 0.8], [-len + 1.3, zb + 0.01]]);
}
}
// the gap over the pawl arm, open at the rear
translate([-len - 1, -6, zb + 1.2]) cube([len - 1.5, 12, 0.7]);
// the slot the release post rises through, open at the rear
translate([-len - 1, -1.9, zb + 1.2]) cube([3.3, 3.8, jaw_h + 6]);
}
// the release post, standing on the rear end of the pawl arm
translate([-len + 0.3, -1.5, zb + 1.19]) cube([1.6, 3, jaw_h + 2 - zb - 1.19]);
}
// The jaws as fitted against the ghost charger: the side jaw pushes toward the outer
// wall, the rear jaw toward the front wall.
module jaws_placed() {
translate([wall + gh_w, y_ji, fz + ct_h]) rotate(180) jaw(l_side);
translate([x_jr, yf - gh_l, fz + ct_h]) rotate(90) jaw(l_rear);
}
// Printed on its side, so the pawl arm bends along its layers.
module jaw_flat(len) {
translate([len, 0, 5.3]) rotate([90, 0, 0]) jaw(len);
}
module jaws_flat() {
jaw_flat(l_side);
translate([0, -(jaw_h + 14), 0]) jaw_flat(l_rear);
}
// ---- Port slider ----
// Prints back face down; its edges narrow at 45 degrees toward the front, into the
// dovetail. A tongue cut into it beside the window bends along its layers; the hole
// at its tip clicks over the bumps in the channel. Two grooves across the top of the
// front give the thumb a grip. Local frame: x across, y up the wall, z from the back
// face to the front.
module slider_flat() {
difference() {
hull() {
translate([-sl_t, 0, 0]) cube([sl_w + 2 * sl_t, sl_h, 0.01]);
translate([0, 0, sl_t - 0.01]) cube([sl_w, sl_h, 0.01]);
}
difference() {
translate([tg_x - 2.1, 1.4, -1]) cube([4.2, 10.6, sl_t + 2]);
translate([tg_x - 1.5, 2, -2]) cube([3, 11, sl_t + 4]);
}
translate([tg_x, 4, -1]) cylinder(d = 1.8, h = sl_t + 2, $fn = 24);
for (gy = [14.2, 15.4]) translate([-sl_t - 1, gy - 0.4, sl_t - 0.6]) cube([sl_w + 2 * sl_t + 2, 0.8, 1]);
}
}
module slider_placed() {
multmatrix([[1, 0, 0, px - sl_w / 2], [0, 0, 1, Sy - sl_t], [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, clip_y either side of the middle, so short and long batteries
// of the family both sit across them; each arm has a 45 degree bump that snaps over
// the 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 - clip_y, Sy / 2 + clip_y], 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();
}
// ---- 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, yf - gh_l, fz]) cube([gh_w, gh_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 - 17, Sy - 5, max(0, sl_z0 - 3)], [px + 17, Sy + 0.1, zc + 0.5]) base();
translate([2 * g + 4, 12, 0]) slider_flat();
// a strip of charger tray with the side groove and its teeth, and the side jaw
translate([3 * g, 0, 0]) coupon([x_pw - 2, y_ji - 9, fz], [x_cz - t_clr + 0.1, y_ji + 9, fz + ct_h]) charger_tray();
translate([3 * g, jaw_h + 26, 0]) jaw_flat(l_side);
// one battery clip
if (battery_count > 0) {
yc = Sy / 2 - clip_y;
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) whoop_tray();
color(color_case) charger_tray();
color(color_case) jaws_placed();
color(color_case) slider_placed();
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 if (part == "whoop_tray") color(color_case) translate([0, 0, -fz]) whoop_tray();
else if (part == "charger_tray") color(color_case) translate([0, 0, -fz]) charger_tray();
else if (part == "jaws") color(color_case) jaws_flat();
else if (part == "slider") color(color_case) slider_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.
