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

IMG-1676.jpg
IMG-1679.jpg

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

IMG-1684.jpg

Measure Tokens With Calipers

IMG-1682.jpg
IMG-1677.jpg
IMG-1678.jpg

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

Screenshot 2022-01-17 120519.png

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

Screenshot 2022-01-17 122003.png

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

Screenshot 2022-01-17 123739.png
Screenshot 2022-01-17 124416.png

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

Screenshot 2022-01-17 130921.png
Screenshot 2022-01-17 130858.png
Screenshot 2022-01-17 131011.png

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

Screenshot 2022-01-17 132451.png
Screenshot 2022-01-17 132510.png

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

Screenshot 2022-01-17 133639.png
Screenshot 2022-01-17 133448.png

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

Screenshot 2022-01-17 135815.png

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

Screenshot 2022-01-17 140430.png
Screenshot 2022-01-17 140525.png
Screenshot 2022-01-17 140645.png

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.