Guide to 3D Printing Board Game Token Containers
by ryanchakajodda in Workshop > 3D Printing
1275 Views, 6 Favorites, 0 Comments
Guide to 3D Printing Board Game Token Containers
I created a token holder for one of my favorite board games, Above and Below. I've documented the steps I used to create the token holder using OpenSCAD to create the model. These steps are a tutorial to teach how to use OpenSCAD.
Supplies
- Calipers (any will work, I used the Neiko Digital Calipers)
- OpenSCAD Software
- 3D Printer (any printer will work, but I used the Prusa Mini)
- Board game to make tokens for (I used Above and Below)
Measure Tokens With Calipers
For the first step you will need to measure the tokens that you are building a container for. In this case I am building a token holder for the quest items from the game Above and Below. In this game there are eight quest tokens which are the same size.
The diameter of the a single token measured with the calipers is: 24.59mm.
To create a container for tokens, we need to know the height of a stack of tokens. I measured the height of the fish, since they are the most numerous, which is: 23.5mm.
Design the Container
For this project I saw a donut shaped design that I liked to for this game, and decided to model that. However, you can make any shape you want.
Create New OpenSCAD Project
Create a new project file in OpenSCAD, give the file a logical name like "above_and_below". Enter the measurements that we captured earlier as variables. I rounded the measurements up, this way the tokens will fit into the container easily.
A few notes about OpenSCAD:
- Variables in OpenSCAD are created by giving them a name, followed by an equal sign and a value. Variables are great in 3D modeling because you can change one value to quickly update your model if you need to make it a little smaller or bigger.
- Comments in OpenSCAD are lines which start with //. These lines are not executable code, but helpful reminders for humans.
This is what my file starts with:
// token sizes goods_diameter=25; max_goods_stack=24;
Create Cylinder to Hold the Tokens
Lets start by modeling a cylinder (think a soup can) which is the size needed to hold a single stack of tokens. To create a cylinder in OpenSCAD you use the cylinder function. A basic cylinder needs two parameters, the height and the diameter.
Here is the code to make a cylinder for one stack of tokens:
// token sizes goods_diameter=25; max_goods_stack=24; cylinder( h=max_goods_stack, d=goods_diameter );
Note that "h" is the height parameter, and "d" is the diameter parameter.
Create Cylinders for All Tokens
For this game we have eight token types, so we need to create a cylinder for each. Rather than copying and pasting the code for each cylinder we will use a for loop.
In OpenSCAD a for loop allows you to repeat a piece of code multiple times. This is what the code looks like:
// token sizes goods_diameter=25; max_goods_stack=24; for(i =[0:7]) { translate([goods_diameter*i,0,0]) cylinder( h=max_goods_stack, d=goods_diameter ); }
On the first line, we create the for loop. We create a variable "i" for a range of values: 0, 1, 2, 3, 4, 5, 6, 7.
On the next line we are using translate to move where our cylinder is drawn. In OpenSCAD translate takes an array of values (creating an array is what the [ and ] are for) where the first value is X, the second value is Y, and the last value is Z. So like this:
translate([ x, y, z ])
Tip: If you are ever unsure if you need to move an object in the x, y or z dimension, look for the green, blue, red drawing in the render view (you can see this in the lower left of the image above).
Okay, so in this case we have the translate code:
translate([goods_diameter*i,0,0])
So what this is saying is move the cylinder goods_diameter multiplied by "i" millimeters in the x dimension. This multiplication will happen eight times, with the values 0,1,2...7. Note, there is NOT a ";" at the end of the translate line, this is done so that the next object drawn is translated. If you add a ";" to the end the translate will not happen.
Arrange Cylinders in a Circle
Our goal is to create a storage container where the tokens are arranged in a circle. Right now our code is drawing them in a straight line. So we need to update our loop so the cylinders are spaced in a circle. To do this, we will rotate our drawing space, then draw our cylinders in the loop.
We will use the rotate function in OpenSCAD for this. The rotate function takes an array of three values (like translate does):
rotate([ x, y, z])
Each value corresponds to how much rotation you want on that axis. For example, if you wanted to create the rotational movement of turning a dinner plate on a table 90 degrees you would use:
rotate([ x, y, 90])
In our case, we want to space eight cylinders equally. This means that for each cylinder we need to move it 360 divided by 8, and we want to this along the z axis.
Here is the code, see if you can figure out what it does before I explain below.
// token sizes goods_diameter=25; max_goods_stack=24; // how far to rotate each container goods_angle_spread = 360 / 8; // the distance a single container is from the middle goods_x_from_center = 30; for(i =[0:7]) { rotate([0,0,goods_angle_spread*i]) translate([goods_x_from_center,0,0]) cylinder( h=max_goods_stack, d=goods_diameter ); }
I've created a new variable "goods_angle_spread" which holds the 360 / 8 value. This will be how much we will rotate our drawing space for each cylinder. Making this into math makes it easy to increase the number of tokens if you copy this code later for another model.
goods_angle_spread = 360 / 8;
I've created another variable "goods_x_from_center" which is how far from the middle of our drawing space do we move the shape.
goods_x_from_center = 30;
I've added rotation to our loop so our drawing space is rotated based on the loop value "i". Think of this rotation as if you are turning a dinner plate on a table, your next cylinder will be drawn in this new location. The value of "goods_angle_spread" is 45, so:
- In the first loop iteration "i" is 0 so, 0 * 45 = 0, so no rotation is done in the first iteration.
- On the next iteration "i" is 1, which results in a rotation of 45, since 1 * 45 = 45.
- On the next iteration, "i" is 2, so the result is 90 (since 2 * 45 = 90, and so on.
This is the code for doing the rotation:
rotate([0,0,goods_angle_spread*i])
Finally I updated translate to move the cylinder "goods_x_from_center" value on the x axis. In the first image you can see I picked a value for this of 30 which was too small, because the cylinders were touching. So I updated the code for the second image to be 39. Updated code:
// token sizes goods_diameter=25; max_goods_stack=24; // how far to rotate each container goods_angle_spread = 360 / 8; // the distance a single container is from the middle goods_x_from_center = 39; for(i =[0:7]) { rotate([0,0,goods_angle_spread*i]) translate([goods_x_from_center,0,0]) cylinder( h=max_goods_stack, d=goods_diameter ); }
Create the Donut
At this point we have created cylinders which represent where our goods will be stored. Now we need to create the donut structure for holding all of them. For this will draw two cylinders, one large one (pink above), and a smaller one (yellow above) which will be removed from the center of the pink cylinder. To do this, we will use the difference function in OpenSCAD. The opposite of difference is "union", which merges objects together. I like to use union to see what I am drawing before I use difference.
Here is the first version of the code using union:
// token sizes goods_diameter=25; max_goods_stack=24; // token sizes goods_diameter=25; max_goods_stack=24; // how far to rotate each container goods_angle_spread = 360 / 8; // the distance a single container is from the middle goods_x_from_center = 39; // donut dimensions goods_container_height=max_goods_stack+5; goods_container_main_diameter=103; goods_middle_cutout_diameter=55; union() { // outer edge of donut color("pink") cylinder( h=goods_container_height, d=goods_container_main_diameter ); // middle of donut translate([0,0,-goods_container_height]) cylinder( h=goods_container_height*3, d=goods_middle_cutout_diameter ); }
I've created some new variables to represent the height of the donut, and how large it is. Note, with this line I am making the height of the donut taller than the max token stack to make sure the tokens will sit nicely in the container.
goods_container_height=max_goods_stack+5;
With this line, I am changing the color to pink, since we are making a donut:
color("pink")
I made the middle of the donut three times as tall, and shifted it down so that when we remove the middle, our deletion object will extend past the object we are deleting. This makes the preview look nicer, and avoids creating an object which has edges which can't be printed. If you look at the images above you will see one which is deleted, but not fully hollow- that is one where I made the deletion shape the same height.
This is the code using difference, instead of union:
// token sizes goods_diameter=25; max_goods_stack=24; // how far to rotate each container goods_angle_spread = 360 / 8; // the distance a single container is from the middle goods_x_from_center = 39; // donut dimensions goods_container_height=max_goods_stack+5; goods_container_main_diameter=103; goods_middle_cutout_diameter=55; difference() { // outer edge of donut color("pink") cylinder( h=goods_container_height, d=goods_container_main_diameter ); // middle of donut translate([0,0,-goods_container_height]) cylinder( h=goods_container_height*3, d=goods_middle_cutout_diameter ); }
Put Holes in the Donut
Now, we need to cut holes in the donut so we can store our tokens in the donut - putting all our work so far together. For this step, I am using the module feature in OpenSCAD so we can group our code into logical blocks to make it easier to read. A module allows you to define code that you want to execute in another part of code.
For example, this code below is very easy to understand. The "donut()" line does all the work to create a donut. The "containers()" line does all the work to print the eight container holders. Note, the translate line is used to move the containers up in the z index, so that when we cut the containers out of the donut, there is still a bottom in the donut below the containers.
difference() { donut(); translate([0,0,thickness]) containers(); }
Here is the full code now, including the module definitions.
// min thickness of our model thickness=1.5; // token sizes goods_diameter=25; max_goods_stack=24; // how far to rotate each container goods_angle_spread = 360 / 8; // the distance a single container is from the middle goods_x_from_center = 39; // donut dimensions goods_container_height=max_goods_stack+5; goods_container_main_diameter=103; goods_middle_cutout_diameter=55; // make the donut module donut() { difference() { // outer edge of donut color("pink") cylinder( h=goods_container_height, d=goods_container_main_diameter ); // middle of donut translate([0,0,-goods_container_height]) cylinder( h=goods_container_height*3, d=goods_middle_cutout_diameter ); } } // make the containers module containers() { for(i =[0:7]) { rotate([0,0,goods_angle_spread*i]) translate([goods_x_from_center,0,0]) cylinder( h=max_goods_stack*2, d=goods_diameter ); } } // cut the containers out of the donut difference() { donut(); translate([0,0,thickness]) containers(); }
Create Lid for the Container
Following what we have learned above, to create the lid we will use the OpenSCAD difference function again. We will create three cylinders. One will be the body of the lid (pictured in blue), the next will be a shape to remove the center of the donut (pictured in pink), and last a shape to remove the inner body of the lid to make the lip (pictured in orange). Note, to make the lid easy to print without supports I am building it upside down.
In this code, I've made a new module called "token_holder_lid" which will do the work of making the lid.
// the token holder lid module token_holder_lid() { difference() { // the body of the lid color("blue") cylinder( h=lid_lip_height, d=lid_diameter ); union() { // cut a hole in the middle of the lid translate([0,0,-goods_container_height]) color("pink") cylinder( h=goods_container_height*4, d=goods_middle_cutout_diameter ); // cut out the inside of the lid color("orange") translate([0,0,thickness]) cylinder( h=goods_container_height*2, d=inner_lid_diameter ); } } }
Here is the full listing of the code so far, you can see that I've created a new module which makes the main container. I've commented it out so that I can take pictures of the lid.
// min thickness of our model thickness=1.5; // token sizes goods_diameter=25; max_goods_stack=24; // how far to rotate each container goods_angle_spread = 360 / 8; // the distance a single container is from the middle goods_x_from_center = 39; // donut dimensions goods_container_height=max_goods_stack+5; goods_container_main_diameter=103; goods_middle_cutout_diameter=55; // lid dimensions lid_diameter=goods_container_main_diameter+thickness+2; inner_lid_diameter=lid_diameter-thickness-thickness; lid_lip_height=5+thickness; // make the donut module donut() { difference() { // outer edge of donut color("pink") cylinder( h=goods_container_height, d=goods_container_main_diameter ); // middle of donut translate([0,0,-goods_container_height]) cylinder( h=goods_container_height*3, d=goods_middle_cutout_diameter ); } } // make the containers module containers() { for(i =[0:7]) { rotate([0,0,goods_angle_spread*i]) translate([goods_x_from_center,0,0]) cylinder( h=max_goods_stack*2, d=goods_diameter ); } } // the main token holder module main_container() { // cut the containers out of the donut difference() { donut(); translate([0,0,thickness]) containers(); } } // the token holder lid module token_holder_lid() { difference() { // the body of the lid color("blue") cylinder( h=lid_lip_height, d=lid_diameter ); union() { // cut a hole in the middle of the lid translate([0,0,-goods_container_height]) color("pink") cylinder( h=goods_container_height*4, d=goods_middle_cutout_diameter ); // cut out the inside of the lid color("orange") translate([0,0,thickness]) cylinder( h=goods_container_height*2, d=inner_lid_diameter ); } } } token_holder_lid(); //main_container();
Finishing Touches
In this step I updated the values for the token holder a bit to make sure it won't break when being handled (call me paranoid), and I added a little more wiggle room for holding the tokens. You may need to change sizes to make the lid fight tighter, or tokens fit better for the game you pick for your tokens. By using variables these changes are easy.
I increased the diameter of the tokens:
goods_diameter=25+2;
I made the token storage a little deeper by making the height of the container taller:
goods_container_height=max_goods_stack+7;
I made the base of the container base a little thicker by moving the containers up two times the thickness:
module main_container() { // cut the containers out of the donut difference() { donut(); translate([0,0,thickness*2]) containers(); } }
I added this line to make the cylinders have more faces, so they are smoother:
$fn=90;
Final listing of the code:
// this smoothens the cylinders $fn=90; // min thickness of our model thickness=1.5; // token sizes goods_diameter=25+2; max_goods_stack=24; // how far to rotate each container goods_angle_spread = 360 / 8; // the distance a single container is from the middle goods_x_from_center = 39; // donut dimensions goods_container_height=max_goods_stack+7; goods_container_main_diameter=103; goods_middle_cutout_diameter=55; // lid dimensions lid_diameter=goods_container_main_diameter+thickness+2; inner_lid_diameter=lid_diameter-thickness-thickness; lid_lip_height=5+thickness; // make the donut module donut() { difference() { // outer edge of donut color("pink") cylinder( h=goods_container_height, d=goods_container_main_diameter ); // middle of donut translate([0,0,-goods_container_height]) cylinder( h=goods_container_height*3, d=goods_middle_cutout_diameter ); } } // make the containers module containers() { for(i =[0:7]) { rotate([0,0,goods_angle_spread*i]) translate([goods_x_from_center,0,0]) cylinder( h=max_goods_stack*2, d=goods_diameter ); } } // the main token holder module main_container() { // cut the containers out of the donut difference() { donut(); translate([0,0,thickness*2]) containers(); } } // the token holder lid module token_holder_lid() { difference() { // the body of the lid color("blue") cylinder( h=lid_lip_height, d=lid_diameter ); union() { // cut a hole in the middle of the lid translate([0,0,-goods_container_height]) color("pink") cylinder( h=goods_container_height*4, d=goods_middle_cutout_diameter ); // cut out the inside of the lid color("orange") translate([0,0,thickness]) cylinder( h=goods_container_height*2, d=inner_lid_diameter ); } } } // uncomment to print the lid //token_holder_lid(); // uncomment to print the container main_container();
Export and Print
In this step you will export the models and print them. To export from OpenSCAD you need to render your print first, then export the files. Since we have two models, we will need to render and export twice.
1) Comment out the lid module, and uncomment the main container module:
// uncomment to print the lid //token_holder_lid(); // uncomment to print the container main_container();
2) Save your project.
3) Press the render button (see picture above).
4) From the File Menu choose "Export > Export as 3MF..." (use whatever format you like, but I recommend 3MF for these reasons).
5) Uncomment the lid module, and comment the main container module:
// uncomment to print the lid token_holder_lid(); // uncomment to print the container //main_container();
6) Save your project.
7) Press the render button.
8) From the File Menu choose "Export > Export as 3MF..."
9) Load the models into your slicer program, and print.