Rendering these values. This takes a few seconds.
Download STL
Read this model as text

A hinged travel case for a tiny whoop, its charger and a row of batteries, designed for PETG. The base holds the whoop on the left in a low locating ring shaped around its ducts, and the charger on the right, pressed down by two spring clamps. The lid holds the batteries flat against its plate 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 · whoop_wheelbase 80 · duct_d 50 · whoop_h 40 · charger_l 90 · charger_w 45 · charger_min_h 20 · battery_count 8 · battery_l 62 · battery_w 13.5 · battery_t 9 · color_case SteelBlue.

MIT

Tiny whoop case with charger clamps and battery clips

mick/whoop-case@1.0.0

Remix this
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.

Sign in to report

0reused0 remixes · 0 imports0downloads3.6.3latest

Parameters

from source
60120, steps of 0.5
3070, steps of 0.5
2060, steps of 1
40140, steps of 1
2080, steps of 1
840, steps of 1

Showing the render for these values.

Description

What is this

A hinged travel case for a tiny whoop, its charger and a row of batteries, designed for PETG. The base holds the whoop on the left in a low locating ring shaped around its ducts, and the charger on the right, pressed down by two spring clamps. The lid holds the batteries flat against its plate 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 batteries in one box. Set the whoop by its diagonal wheelbase, duct diameter and height, the charger by its footprint and the lowest height the clamps must hold, and the batteries by count and size; the case grows around them. Chargers lower than the whoop are held by the clamps: each is a flat strip that bolts onto a post beside the charger and presses a pad onto it with about 1.5 mm of preload. A charger up to about 3 mm taller than charger_min_h lifts the strips and is held as well. Lift a clamp by its tab to take the charger out.

Design notes

Everything prints without supports in PETG. The clamps print flat with the strip on the bed, so they bend along their layers instead of across them, which is what lets a PETG spring last. 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. The latch ridge and hook, the battery clip bumps and the lip on the latch tongue are all 45 degree faces. Nothing is self-tapping: each clamp bolts on with an M3 x 8 into a nut trapped in a side slot of its post, open toward the charger so the charger keeps the nut in. Each hinge is base, lid, base on an M3 x 30 with a nut. The latch tongue flexes over about 12 mm of free length and the battery arms about 10 mm; both bend across layers, which PETG takes at these small deflections. A lip on the inner half of the base rim locates the lid. With the defaults the case is about 177 x 118 mm and fits a 180 mm bed; a bigger whoop or charger can push it past that. test_print renders a coupon set: one hinge and the latch in both halves, one battery clip, one post and one clamp.

Parameters

part picks the assembly or one part in print orientation; clamp lays out both clamps. test_print swaps it for the coupon set. 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_min_h the height of the lowest charger the clamps hold. battery_count, battery_l (with the plug), battery_w and battery_t set the battery clips in the lid. 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.0.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
Parameters of Tiny whoop case with charger clamps and battery clips, with the ranges the source declares
NameDefaultRangeUnitDescription
part"assembly"assembly, base, lid, clamp
test_printfalse
whoop_wheelbase8060 to 120 step 0.5
duct_d5030 to 70 step 0.5
whoop_h4020 to 60 step 1
charger_l9040 to 140 step 1
charger_w4520 to 80 step 1
charger_min_h208 to 40 step 1
battery_count80 to 12 step 1
battery_l6240 to 80 step 1
battery_w13.58 to 20 step 0.5
battery_t95 to 14 step 0.5
color_case"SteelBlue"SteelBlue, White, DimGray, Black, FireBrick, Goldenrod, ForestGreen, MediumPurple

Lineage

0 models depend on this one

Only what this model came from and what came from it.

Remix, the code changed Import, used unchanged and pinned The version you are on Withdrawn, still recorded

License

Whoever downloads this takes these obligations along
  • mick/whoop-case@1.0.0 is MIT

Source

main.scad is the entrypoint
main.scad
// Tiny whoop case: the whoop, its charger and a row of batteries in one hinged box.
// Printed in PETG. The base holds the whoop on the left in a locating ring and the
// charger on the right, pressed down by two flat-printed spring clamps that bolt onto
// posts. The lid holds the batteries in snap clips. Two pin hinges with an M3 bolt
// each at the rear, a snap latch at the front. Nothing is self-tapping and every
// part prints without supports.
//
// Coordinates: x across (whoop left, charger right), y = 0 at the rear (hinges),
// z = 0 under the base. The lid is modelled closed and flipped for printing.

/* [Part] */
// The whole case with the lid open, or one part in print orientation. Print the
// clamp part once: it lays out both clamps.
part = "assembly"; // [assembly, base, lid, clamp]
// Print a coupon set instead: one hinge and the latch in base and lid halves, one
// battery clip, one clamp post and one clamp. Checks every fit in about an hour.
test_print = 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] */
// Footprint of the charger. It lies lengthwise next to the whoop.
charger_l = 90; // [40:1:140]
charger_w = 45; // [20:1:80]
// Height of the lowest charger the clamps hold down. Chargers up to about 3 mm
// taller push the clamps up and are held as well.
charger_min_h = 20; // [8:1:40]

/* [Batteries] */
// Batteries in the lid, side by side. 0 for an empty lid.
battery_count = 8; // [0:1:12]
// Length with the plug, width and thickness of one battery.
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 bay and spring clamps
ch_clr = 0.5;
rib_h = 4;
post_x = 10;
post_y = 9;
post_gap = 1;
anchor_t = 4;
strip_t = 1.6;
strip_w = 8;
finger_len = 4;
preload = 1.5;
pad_l = 6;
tab_l = 6;
bolt_d = 3.4;
nut_af = 5.7;
nut_ac = nut_af / cos(30);
nut_h = 2.6;
nut_top = 1.2;
// battery clips
bat_clr = 0.25;
arm_t = 1.4;
arm_w = 6;
arm_gap = 0.6;
bump = 0.7;
// hinges: knuckle radius, axis behind the rear face, segment width and gap
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;
right_need = 2 + ch_clr + ring_t + charger_w + post_gap + post_x;
bat_pitch = battery_w + 2 * (bat_clr + arm_t) + arm_gap;
Sx_in = max(drone_need + right_need, battery_count * bat_pitch + 8);
Sy_in = max(drone_need, charger_l + 2 * (ch_clr + ring_t) + 2, battery_l + 12);
Sx = Sx_in + 2 * wall;
Sy = Sy_in + 2 * wall;
fz = floor_t;
cav_h = max(whoop_h + 2, charger_min_h + 10.5);
base_h = floor_t + cav_h;
zc = base_h;
lid_cav = max(battery_t + 2.5, 9);
lid_h = top_t + lid_cav;
pb = zc + lid_h - top_t;
// whoop centre
dx = wall + (Sx_in - right_need) / 2;
dy = Sy / 2;
// charger footprint and clamp posts
xr = Sx - wall;
cx1 = xr - post_x - post_gap;
cx0 = cx1 - charger_w;
cy0 = Sy / 2 - charger_l / 2;
cy1 = Sy / 2 + charger_l / 2;
post_cx = xr - post_x / 2;
post_ys = [cy0 + 12, cy1 - 12];
pad_x = cx1 - 0.55 * charger_w;
reach = (xr - post_x) - pad_x;
strip_bot = fz + charger_min_h - preload + finger_len;
post_top = strip_bot - (anchor_t - strip_t);
// 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();
    }
}

// Low ribs on the whoop side and both ends of the charger; the posts close the
// fourth side.
module charger_ribs() {
  x0 = cx0 - ch_clr - ring_t;
  translate([0, 0, fz - 0.01]) linear_extrude(rib_h + 0.01) difference() {
    translate([x0, cy0 - ch_clr - ring_t]) square([xr + 0.01 - x0, charger_l + 2 * (ch_clr + ring_t)]);
    translate([cx0 - ch_clr, cy0 - ch_clr]) square([xr + 1 - (cx0 - ch_clr), charger_l + 2 * ch_clr]);
  }
}

// Two posts against the right wall carry the clamps. Each traps an M3 nut in a slot
// open toward the charger, under nut_top of material; the clamp bolts on from above
// with an M3 x 8. The charger covers the slot, so the nut cannot fall out.
module posts() {
  for (py = post_ys) difference() {
    translate([xr - post_x, py - post_y / 2, fz - 0.01]) cube([post_x + 0.5, post_y, post_top - fz + 0.01]);
    translate([post_cx, py, max(fz + 0.4, post_top - 8)]) cylinder(d = bolt_d, h = 9);
    translate([xr - post_x - 1, py - nut_af / 2, post_top - nut_top - nut_h])
      cube([post_x / 2 + 1 + nut_ac / 2 + 0.2, nut_af, nut_h]);
  }
}

// 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]]);
}

module base() {
  base_shell();
  knuckles(false);
  whoop_ring();
  charger_ribs();
  posts();
  latch_ridge();
}

// ---- Charger clamp ----
// A flat strip printed on the bed, so it bends along its layers. The anchor bolts on
// a post, the strip spans over the charger and a pad under its end presses the
// charger down with preload. A taller charger lifts the strip. The tab past the pad
// lifts it by hand. Drawn as fitted: bolt at the origin, strip along +u.

module clamp() {
  pu = 5 + reach;
  difference() {
    union() {
      translate([-5, -strip_w / 2, 0]) cube([10, strip_w, anchor_t]);
      translate([4.99, -strip_w / 2, anchor_t - strip_t]) cube([reach + pad_l / 2 + tab_l + 0.01, strip_w, strip_t]);
      // fillet where the strip leaves the anchor
      hull() {
        translate([4.99, -strip_w / 2, 1]) cube([0.01, strip_w, anchor_t - 1]);
        translate([5, -strip_w / 2, anchor_t - strip_t]) cube([2.5, strip_w, strip_t]);
      }
      // pad, chamfered at its foot
      hull() {
        translate([pu - pad_l / 2, -strip_w / 2, anchor_t - strip_t - finger_len + 0.8])
          cube([pad_l, strip_w, finger_len - 0.8 + 0.01]);
        translate([pu - pad_l / 2 + 0.8, -strip_w / 2, anchor_t - strip_t - finger_len])
          cube([pad_l - 1.6, strip_w, 0.01]);
      }
    }
    translate([0, 0, -1]) cylinder(d = bolt_d, h = anchor_t + 2);
  }
}

module clamps_placed() {
  for (py = post_ys) translate([post_cx, py, post_top]) rotate(180) clamp();
}

module clamp_print() {
  translate([0, 0, anchor_t]) rotate([180, 0, 0]) clamp();
}

// ---- 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. It flexes over latch_drop plus 4 mm of free length.
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;

// Batteries lie flat against the lid plate. Each is held by two pairs of arms with a
// 45 degree bump that snaps over its free face. Neighbouring arms stand apart, so
// every arm flexes on its own.
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_w / 2 + bat_clr);
      zb = pb - battery_t - 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_t - 1.6]) square([arm_t, battery_t + 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 hardware ----

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([cx0, cy0, fz]) cube([charger_w, charger_l, charger_min_h]);
}

module ghost_batteries() {
  if (battery_count > 0)
    color("DimGray", 0.6) for (i = [0 : battery_count - 1])
      translate([bat_x(i) - battery_w / 2, Sy / 2 - battery_l / 2, pb - battery_t]) cube([battery_w, battery_l, battery_t]);
}

// ---- 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();
  // clamp post with its nut slot
  translate([2 * g, 0, 0]) coupon([xr - post_x - 4, post_ys[0] - 8, 0], [Sx + 0.1, post_ys[0] + 8, post_top + 1]) base();
  // one battery clip
  if (battery_count > 0) {
    yc = Sy / 2 - 0.28 * battery_l;
    bx = bat_x(0);
    translate([2 * g, 22, 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();
  }
  // one clamp
  translate([3 * g, 4, 0]) clamp_print();
}

// ---- Layout ----

module assembly() {
  color(color_case) base();
  color(color_case) clamps_placed();
  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();
    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 == "clamp") color(color_case) { clamp_print(); translate([0, strip_w + 6, 0]) clamp_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.

Sign in to comment