Initial
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
stl/
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
SCAD = wargame_box.scad
|
||||||
|
PARTS = box lid insert
|
||||||
|
STLS = $(addprefix stl/,$(addsuffix .stl,$(PARTS)))
|
||||||
|
|
||||||
|
all: $(STLS)
|
||||||
|
|
||||||
|
stl/%.stl: $(SCAD)
|
||||||
|
@mkdir -p stl
|
||||||
|
openscad -o $@ -D 'part="$*"' $(SCAD)
|
||||||
|
|
||||||
|
docs/assembly.png: $(SCAD)
|
||||||
|
@mkdir -p docs
|
||||||
|
openscad -o $@ --render --imgsize=900,800 \
|
||||||
|
--camera=110,75,60,60,0,30,700 $(SCAD)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf stl
|
||||||
|
|
||||||
|
.PHONY: all clean
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
# Wargame Accessory Box & Dice Tray
|
||||||
|
|
||||||
|
A parametric OpenSCAD box for carrying tabletop wargaming accessories.
|
||||||
|
Three printable parts:
|
||||||
|
|
||||||
|
| Part | Purpose |
|
||||||
|
|----------|--------------------------------------------------------------------|
|
||||||
|
| `box` | Open tray. With the insert removed it doubles as a dice tray. |
|
||||||
|
| `lid` | Fully detachable friction-fit lid (no hinge to get in the way). |
|
||||||
|
| `insert` | Removable compartment tray for tokens, measuring tapes, etc. |
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
Open `wargame_box.scad` in OpenSCAD and set the three `total_*` values to
|
||||||
|
the free space in your backpack — they are **outer** dimensions and
|
||||||
|
`total_z` already includes the closed lid, so the printed result fits the
|
||||||
|
measured space exactly.
|
||||||
|
|
||||||
|
Pick what to render with the `part` variable (or the customizer):
|
||||||
|
`assembly` (exploded preview), `box`, `lid`, `insert`, `print`.
|
||||||
|
|
||||||
|
Or export everything from the command line:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make # exports stl/box.stl, stl/lid.stl, stl/insert.stl
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuring the insert layout
|
||||||
|
|
||||||
|
Compartments are defined by the `layout` list, one entry per row
|
||||||
|
(front to back):
|
||||||
|
|
||||||
|
```
|
||||||
|
layout = [
|
||||||
|
[1.0, [1, 1, 1, 1]], // front row: 4 equal token bins
|
||||||
|
[1.0, [2, 1, 1]], // middle row: 1 double-width + 2 small
|
||||||
|
[0.8, [1]] // back row: full-width slot (tapes etc.)
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
* The first number is the **row depth weight**.
|
||||||
|
* The list is the **column width weights** within that row.
|
||||||
|
|
||||||
|
Weights are relative, so the layout always fills the insert exactly no
|
||||||
|
matter what box size you choose. A row weight of `2` is twice as deep as
|
||||||
|
a row of weight `1`; a column weight of `2` is twice as wide as `1`.
|
||||||
|
|
||||||
|
To print a second insert variant (e.g. for a different game system), just
|
||||||
|
change `layout`, re-export, and swap inserts as needed. Two half-height
|
||||||
|
stacked inserts are possible too: set `insert_h_override` to about half
|
||||||
|
of the automatic height and print two with different layouts.
|
||||||
|
|
||||||
|
## How the parts work together
|
||||||
|
|
||||||
|
* The **lid** is a flat plate with a lip that plugs into the box opening.
|
||||||
|
`lid_clearance` (default 0.2 mm per side) controls the friction fit —
|
||||||
|
print a test fit and adjust for your printer/material. Shallow thumb
|
||||||
|
grooves on the box's front and back walls let you pry the lid off; they
|
||||||
|
do **not** pierce the wall, so the dice tray stays fully closed.
|
||||||
|
* The **insert** sits below the lid's lip, so the lip also keeps loose
|
||||||
|
tokens from jumping compartments in the bag. Finger notches on its
|
||||||
|
short walls let you lift it straight out; then the empty box is your
|
||||||
|
dice tray. `tray_chamfer` puts a 45° bevel around the floor edges so
|
||||||
|
dice don't lodge in the corners.
|
||||||
|
|
||||||
|
## Printing notes
|
||||||
|
|
||||||
|
* No supports needed for any part. Print all parts flat side down
|
||||||
|
(the lid prints plate-down, lip up).
|
||||||
|
* PLA or PETG, 2–3 perimeters, ~10 % infill (the parts are mostly walls).
|
||||||
|
* If the lid is too tight/loose, tune `lid_clearance` in steps of 0.05 mm.
|
||||||
|
Same for the insert with `insert_clearance` (looser is fine there —
|
||||||
|
it just needs to drop in and lift out easily).
|
||||||
|
* A piece of felt glued into the box floor makes dice rolling quieter;
|
||||||
|
if you plan this, add the felt thickness to nothing — the inner depth
|
||||||
|
is generous — but you may want `tray_chamfer = 0` for a flat floor.
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
@@ -0,0 +1,276 @@
|
|||||||
|
// ============================================================
|
||||||
|
// Wargame Accessory Box & Dice Tray
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
// Three printable parts:
|
||||||
|
// * box - open tray, doubles as a dice tray
|
||||||
|
// * lid - fully detachable friction-fit lid
|
||||||
|
// * insert - removable compartment tray (configurable layout)
|
||||||
|
//
|
||||||
|
// Set `part` below (or via customizer / -D on the CLI) to
|
||||||
|
// choose what to render. "assembly" shows an exploded preview,
|
||||||
|
// "print" lays all parts flat for a quick look at a full set.
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
/* [What to render] */
|
||||||
|
part = "assembly"; // [assembly, box, lid, insert, print]
|
||||||
|
|
||||||
|
/* [Outer dimensions (must fit the backpack)] */
|
||||||
|
// Total outer width (X), including walls
|
||||||
|
total_x = 220;
|
||||||
|
// Total outer depth (Y), including walls
|
||||||
|
total_y = 150;
|
||||||
|
// Total outer height (Z), INCLUDING the closed lid
|
||||||
|
total_z = 80;
|
||||||
|
|
||||||
|
/* [Shell] */
|
||||||
|
// Box / lid wall thickness
|
||||||
|
wall = 2.4;
|
||||||
|
// Box floor thickness
|
||||||
|
floor_t = 2.4;
|
||||||
|
// Outer corner radius (0 = sharp corners)
|
||||||
|
corner_r = 6;
|
||||||
|
|
||||||
|
/* [Dice tray] */
|
||||||
|
// 45-degree chamfer along the inner floor edges so dice
|
||||||
|
// don't get stuck in the corners (0 = off)
|
||||||
|
tray_chamfer = 6;
|
||||||
|
|
||||||
|
/* [Lid] */
|
||||||
|
// Thickness of the flat lid plate
|
||||||
|
lid_top = 2.4;
|
||||||
|
// Height of the lip that plugs into the box opening
|
||||||
|
lip_h = 10;
|
||||||
|
// Lip wall thickness
|
||||||
|
lip_t = 2.0;
|
||||||
|
// Gap per side between lip and box wall.
|
||||||
|
// Tune for your printer: smaller = tighter friction fit.
|
||||||
|
lid_clearance = 0.20;
|
||||||
|
// Thumb groove on the box's outer walls to pry the lid off
|
||||||
|
// (does NOT pierce the wall, dice tray stays closed)
|
||||||
|
thumb_grooves = true;
|
||||||
|
|
||||||
|
/* [Insert] */
|
||||||
|
// Gap per side between insert and box wall
|
||||||
|
insert_clearance = 0.40;
|
||||||
|
// Insert outer wall thickness
|
||||||
|
insert_wall = 1.6;
|
||||||
|
// Wall thickness between compartments
|
||||||
|
divider_t = 1.2;
|
||||||
|
// Insert floor thickness
|
||||||
|
insert_floor = 1.6;
|
||||||
|
// Insert height; 0 = automatic (fills the box up to the lid lip)
|
||||||
|
insert_h_override = 0;
|
||||||
|
// Finger notches on the insert's short walls for lifting it out
|
||||||
|
finger_notches = true;
|
||||||
|
// Finger notch diameter
|
||||||
|
notch_d = 22;
|
||||||
|
|
||||||
|
/* [Hidden] */
|
||||||
|
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
// Compartment layout
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
// One entry per row (front to back). Each entry is
|
||||||
|
// [row_depth_weight, [column_width_weights]]
|
||||||
|
// Weights are relative, so the layout always fills the insert
|
||||||
|
// exactly no matter what the box dimensions are.
|
||||||
|
//
|
||||||
|
// Default: a wide slot for measuring tapes across the back,
|
||||||
|
// and two rows of token compartments in front.
|
||||||
|
layout = [
|
||||||
|
[1.0, [1, 1, 1, 1]], // front row: 4 small token bins
|
||||||
|
[1.0, [2, 1, 1]], // middle row: 1 medium + 2 small
|
||||||
|
[0.8, [1]] // back row: full-width slot (tapes etc.)
|
||||||
|
];
|
||||||
|
|
||||||
|
$fn = $preview ? 32 : 64;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
// Derived dimensions
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
box_h = total_z - lid_top; // box body height
|
||||||
|
inner_x = total_x - 2 * wall; // cavity size
|
||||||
|
inner_y = total_y - 2 * wall;
|
||||||
|
inner_h = box_h - floor_t; // cavity depth
|
||||||
|
inner_r = max(corner_r - wall, 0.1); // cavity corner radius
|
||||||
|
|
||||||
|
ins_x = inner_x - 2 * insert_clearance; // insert outer size
|
||||||
|
ins_y = inner_y - 2 * insert_clearance;
|
||||||
|
ins_h = insert_h_override > 0
|
||||||
|
? insert_h_override
|
||||||
|
: inner_h - lip_h - 0.6; // leave room for the lid lip
|
||||||
|
|
||||||
|
lip_x = inner_x - 2 * lid_clearance; // lid lip outer size
|
||||||
|
lip_y = inner_y - 2 * lid_clearance;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
// Helpers
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
function sum(v, i = 0) = i >= len(v) ? 0 : v[i] + sum(v, i + 1);
|
||||||
|
// sum of the first n elements
|
||||||
|
function psum(v, n) = n <= 0 ? 0 : v[n - 1] + psum(v, n - 1);
|
||||||
|
|
||||||
|
function row_weights() = [for (r = layout) r[0]];
|
||||||
|
|
||||||
|
// rounded-corner rectangle, centered at origin corner (0,0)
|
||||||
|
module rrect(x, y, r) {
|
||||||
|
if (r > 0)
|
||||||
|
translate([r, r])
|
||||||
|
offset(r) square([x - 2 * r, y - 2 * r]);
|
||||||
|
else
|
||||||
|
square([x, y]);
|
||||||
|
}
|
||||||
|
|
||||||
|
module rbox(x, y, z, r) {
|
||||||
|
linear_extrude(z) rrect(x, y, r);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
// Box (dice tray)
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
module box() {
|
||||||
|
// thumb grooves are shallow vertical scoops on the outer
|
||||||
|
// front/back wall, so the dice tray wall stays closed
|
||||||
|
groove_depth = min(1.4, wall - 0.8);
|
||||||
|
difference() {
|
||||||
|
union() {
|
||||||
|
difference() {
|
||||||
|
rbox(total_x, total_y, box_h, corner_r);
|
||||||
|
translate([wall, wall, floor_t])
|
||||||
|
rbox(inner_x, inner_y, inner_h + 1, inner_r);
|
||||||
|
}
|
||||||
|
if (tray_chamfer > 0) tray_chamfers();
|
||||||
|
}
|
||||||
|
if (thumb_grooves)
|
||||||
|
for (m = [0, 1])
|
||||||
|
translate([total_x / 2,
|
||||||
|
m == 0 ? -(14 - groove_depth)
|
||||||
|
: total_y + (14 - groove_depth),
|
||||||
|
box_h - 16])
|
||||||
|
cylinder(h = 20, d = 28, $fn = 48);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module tray_chamfers() {
|
||||||
|
c = min(tray_chamfer, inner_h - 1);
|
||||||
|
// triangular prisms against each inner wall
|
||||||
|
module prism(length)
|
||||||
|
rotate([90, 0, 90])
|
||||||
|
linear_extrude(length)
|
||||||
|
polygon([[0, 0], [c, 0], [0, c]]);
|
||||||
|
|
||||||
|
intersection() {
|
||||||
|
translate([wall, wall, floor_t])
|
||||||
|
rbox(inner_x, inner_y, inner_h, inner_r);
|
||||||
|
translate([wall, wall, floor_t]) {
|
||||||
|
// along y = 0 wall
|
||||||
|
prism(inner_x);
|
||||||
|
// along y = inner_y wall
|
||||||
|
translate([inner_x, inner_y, 0]) rotate([0, 0, 180]) prism(inner_x);
|
||||||
|
// along x = 0 wall
|
||||||
|
translate([0, inner_y, 0]) rotate([0, 0, -90]) prism(inner_y);
|
||||||
|
// along x = inner_x wall
|
||||||
|
translate([inner_x, 0, 0]) rotate([0, 0, 90]) prism(inner_y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
// Lid
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
module lid() {
|
||||||
|
taper = 1.2; // 45-degree lead-in at the lip's top edge
|
||||||
|
difference() {
|
||||||
|
union() {
|
||||||
|
// flat plate, same footprint as the box
|
||||||
|
rbox(total_x, total_y, lid_top, corner_r);
|
||||||
|
// friction-fit lip; starts at z = 0 so it overlaps
|
||||||
|
// the plate and unions into a single manifold solid
|
||||||
|
translate([wall + lid_clearance, wall + lid_clearance, 0]) {
|
||||||
|
rbox(lip_x, lip_y, lid_top + lip_h - taper, inner_r);
|
||||||
|
// tapered tip enters the box first -> easy closing
|
||||||
|
translate([lip_x / 2, lip_y / 2, lid_top + lip_h - taper])
|
||||||
|
linear_extrude(taper,
|
||||||
|
scale = [(lip_x - 2 * taper) / lip_x,
|
||||||
|
(lip_y - 2 * taper) / lip_y])
|
||||||
|
translate([-lip_x / 2, -lip_y / 2])
|
||||||
|
rrect(lip_x, lip_y, inner_r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// hollow the lip above the plate
|
||||||
|
translate([wall + lid_clearance + lip_t,
|
||||||
|
wall + lid_clearance + lip_t, lid_top])
|
||||||
|
rbox(lip_x - 2 * lip_t, lip_y - 2 * lip_t,
|
||||||
|
lip_h + 1, max(inner_r - lip_t, 0.1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
// Insert
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
module insert() {
|
||||||
|
iw_x = ins_x - 2 * insert_wall; // usable interior
|
||||||
|
iw_y = ins_y - 2 * insert_wall;
|
||||||
|
n_rows = len(layout);
|
||||||
|
rw = row_weights();
|
||||||
|
avail_y = iw_y - (n_rows - 1) * divider_t;
|
||||||
|
|
||||||
|
difference() {
|
||||||
|
rbox(ins_x, ins_y, ins_h, inner_r);
|
||||||
|
|
||||||
|
// compartment cavities
|
||||||
|
for (ri = [0 : n_rows - 1]) {
|
||||||
|
row_y0 = insert_wall
|
||||||
|
+ avail_y * psum(rw, ri) / sum(rw)
|
||||||
|
+ ri * divider_t;
|
||||||
|
row_dy = avail_y * rw[ri] / sum(rw);
|
||||||
|
|
||||||
|
cols = layout[ri][1];
|
||||||
|
n_cols = len(cols);
|
||||||
|
avail_x = iw_x - (n_cols - 1) * divider_t;
|
||||||
|
|
||||||
|
for (ci = [0 : n_cols - 1]) {
|
||||||
|
col_x0 = insert_wall
|
||||||
|
+ avail_x * psum(cols, ci) / sum(cols)
|
||||||
|
+ ci * divider_t;
|
||||||
|
col_dx = avail_x * cols[ci] / sum(cols);
|
||||||
|
|
||||||
|
translate([col_x0, row_y0, insert_floor])
|
||||||
|
cube([col_dx, row_dy, ins_h]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// finger notches on the short (left/right) walls
|
||||||
|
if (finger_notches)
|
||||||
|
for (x = [0, ins_x])
|
||||||
|
translate([x, ins_y / 2, ins_h])
|
||||||
|
rotate([0, 90, 0])
|
||||||
|
cylinder(h = 2 * (insert_wall + 6),
|
||||||
|
d = notch_d, center = true, $fn = 48);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
// Output
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
module assembly() {
|
||||||
|
color("SteelBlue") box();
|
||||||
|
color("Orange")
|
||||||
|
translate([wall + insert_clearance,
|
||||||
|
wall + insert_clearance, floor_t])
|
||||||
|
insert();
|
||||||
|
// lid floats above, flipped into closed orientation
|
||||||
|
color("LightGreen", 0.7)
|
||||||
|
translate([0, total_y, box_h + ins_h + 25])
|
||||||
|
rotate([180, 0, 0]) translate([0, 0, -lid_top]) lid();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (part == "box") box();
|
||||||
|
else if (part == "lid") lid();
|
||||||
|
else if (part == "insert") insert();
|
||||||
|
else if (part == "print") {
|
||||||
|
box();
|
||||||
|
translate([total_x + 15, 0, 0]) lid();
|
||||||
|
translate([0, total_y + 15, 0]) insert();
|
||||||
|
}
|
||||||
|
else assembly();
|
||||||
Reference in New Issue
Block a user