3D Printing for IoT Developers

3D printing promises to do to physical objects what the Internet did for information: make almost everything accessible from almost anywhere with a device that almost anybody can use. As with all such promises, it’s useful, but the reality is not quite as good as the hype. I’d like to explain what a relatively cheap 3D printer can do for you, what it can’t do, and how you can best make use of this new technology.

Uses for 3D printers

3D printers typically print out inert pieces of plastic. That doesn’t sound very exciting, but there are some use cases:

  • IoT containers. Internet of Things devices are typically fragile electronic circuits used in environments that were never designed for them (for example, haybarns). With a 3D printer, you can custom print a container for your IoT devices. For my haybarn tutorial, which uses NodeMCU connected to a DHT11, you can use this container or this one.

  • Displaying 3D data in a solid form. For example, this object can be used by mathematics teachers to explain the difference between a local maximum, a local minimum, and a saddle point:

    Displaying 3D data in a solid form`

  • Moving parts. You can create moving parts and there are two ways to do it:

    • You can print multiple parts and fit them together. (For example, this IBM TJBot.)
    • You can also create a print with a hole, and inside the hole have an object that is too large to pop out but still small enough to have freedom of movement. (For example, this baby toy.)

If you can’t find what you want on Thingiverse or 3DShare, you can always design it yourself. My favorite design platform is OpenJSCAD.

Using OpenJSCAD to design your own thing to print

There are several 3D design programs out there, but they require you to be coordinated enough to put shapes where you intend to put them. I’m a programmer, and if I want to be accurate, I write code. The system I found easiest to use is OpenJSCAD. It lets you write in JavaScript on the web browser and then download an STL file that you can print.

Basic shapes

The basic OpenJSCAD entity is the shape. The simplest possible program creates and returns a single 3D object, (for example, a rectangular prism).

function main () {

The cube function creates a cube or rectangular prism. The size parameter can be either a single number (for a cube, which has three equal sides) or an array of three numbers (for a rectangular prism, whose sides vary in length).

    return cube({size: [10, 5, 1]});

Transforming and combining shapes

We can transform shapes in various ways. Shapes can be translated (moved), rotated, and scaled. You can also create a union of multiple shapes, calculate their intersection (the points they have in common) or find the difference between one shape and another (the points that are part of only the first shape). These combinations allow for more complex shapes.

For example, you can use this program to create a box for a NodeMCU development board.

This program needs three configuration parameters: the size of the device (as documented here), the margin of space to let us put the NodeMCU device comfortably into our box, and the width of the box walls.

const devSize = [49, 24.5, 13];
const margin = 3;
const wallWidth = 10;

function main () {

In each dimension, the size of the hole for the device is the size of the physical device plus twice the margin.

const holeSize = devSize.map(l => l+2*margin);

On the other hand, the size of the overall box is different between dimentions. In the x and y dimensions, there are two walls, one to each size of the device. In the z dimension, there is only one: the floor. We need a hole to insert our device into.

const boxSize = [

When we use the cube function, we create a rectangular prism with a corner at (0,0,0). This is fine for the overall box; it could be anywhere. However, the hole’s corner needs to be one wallWidth away from zero in each dimension. That way, the hole is centered and all the walls are the same width.

const box = cube({size: boxSize});
const hole = cube({size:holeSize}).
  translate([wallWidth, wallWidth, wallWidth]);

The complete box is the overall box minus the hole that is left open for the device.

const completeBox = difference(box, hole);

OpenJSCAD is JavaScript with a few specialized libraries. It runs the main function and expects that function to return the shape created.

    return completeBox;

A more useful example

The box we created in the previous program is not very usable. It doesn’t have a hole for the microUSB connector that NodeMCU requires as a power supply.

microUSB connector needs a hole`

According to Wikipedia, this microUSB connector is 6.8 x 1.8 mm. However, using that figure is problematic:

  • Cheap 3d printers are not 100 percent accurate. To make sure the hole is big enough, it needs to be designed to be a bit bigger.
  • MicroUSB connectors usually have plastic handles that are bigger than the actual connector. That handle also needs to fit inside the hole.

In makes more sense to make the hole 15mm wide. However, if we just do that, we have a different problem: It is hard to plug the micro-USB connector when the NodeMCU is already at the bottom (because it’s hard to get it in exactly the right spot). If we pass the cable through the hole, plug it in, and attempt to put the NodeMCU in place, we have a different problem because it doesn’t fit.

microUSB with 15mm hole`

Depending on how much dust is in the environment, we could solve this problem by having the slot for the micro-USB connector go all the way to the top of the box, as in this program. Also, most NodeMCU IoT devices need access to the NodeMCU pins. The easiest way to do this is to flip the NodeMCU device so the pins point up.

Pins point up`

Shape restrictions

3D printing works by building the object one layer at a time. In addition to being time consuming, this process puts limits on the shapes that can be printed. It is a problem to print layers that are unsupported, even if the entire object would support them when the print job is done. So shapes like the following one aren’t printable (at the current orientation, it is printable if rotated).

Shape that is not printable`

A related problem is near horizontal angles. When you attempt to print such an angle, at some point during the process, you’ll have a very thin layer supporting a relatively large weight of material. Angles of more than 45 degrees for vertical, such as this one, usually don’t work.

Angles of more than 45 degrees for vertical do not work`

One exception to this general rule is bridges: short horizontal pieces that connect between two vertical pieces. Those work, if they are short enough, because they are supported from both sides while they are being printed.

Bridges are printable if short enough`

Most 3D print programs also have the ability to add supports to an object to make an otherwise unprintable shape printable (see the following example from FlashForge’s FlashPrint). However, if you use those supports, you need to manually remove them afterwards. It is much better if you can design your shape so that it does not need supports in the first place.

Add supports to an object to make it printable`

Printing a NodeMCU box with a lid in 3D

Armed with this knowledge of what shapes are supported, we can design a better NodeMCU box, one that has a lid on top of it. Use this program.

The lid has to be printed as a separate piece for two reasons:

  • It is necessary to be able to open the lid to insert the NodeMCU.
  • The width of the lid has to be at least 24.5 mm (nearly an inch), the width of a NodeMCU. That is too long to bridge.

Lid has to be printed as a separate piece`

The lid has two slots for wires connected to the NodeMCU. Those wires have a female end to connect to a NodeMCU pin and a male end to connect to a breadboard.

Lid has two slots for wires connected to the NodeMCU`

Wires have a female end to connect to a NodeMCU pin and a male end to connect to a breadboard`

Another feature is the matching indentations on the lid and the walls of the main box. These indentations allow the lid to be placed directly on top of the box, but they do not allow it to slide much. The indentations are triangular in shape because consumer grade 3D printers are not accurate. If a rectangular indentation does not match exactly, either the lid would have too much room to slide or it would not fit at all. With triangular indentations, an inaccurate match would still allow for the lid to be placed correctly, maybe with a bit of force.

There is a rectangular section missing from the lid indentation. Because it is rectangular, a bit of force is required to shove the lid in or to pry it out. But because it is so small, the force required is very small and it still helps keep the lid in place.

Rectangular section missing from the lid indentation`

Here is how the program creates those indentations:

Rectangular section missing from the lid indentation`

The first step is to create a rectangular prism and rotate it 45 degrees around the X axis. The Y and Z dimensions are slotSize/Math.sqrt(2), so that the diagonal will be exacly slotSize.

const slotPrecursor = cube({size: [boxSize[0],
  slotSize/Math.sqrt(2), slotSize/Math.sqrt(2)]}).

Rectangular prism rotated`

When OpenJSCAD scales a shape, it scales around the [0,0,0] coordinates. We want to increase the height, and the easiest way to do it is to first move the prism so it will be centered around the X axis.


Rectangular prism rotated`

To make the angle at the top of the indentation smaller than 90 degrees, we scale it on the Z axis.

scale([1, 1, slotHeightFactor]);

To make a triangular indendation instead of a rectangular one, we create a cube that is only the space above the Z=0 plane and intersect with it. This removes the bottom half of the triangle.

const zAbove0 = cube({size: [200, 200, 200]}).

Rectangular prism rotated`

This slot is the right size and orientation for the side indentations.

const slot = intersection(slotPrecursor,
  zAbove0).translate([0, (wallWidth-slotSize)/2 ,0]);

The indentation on the back needs to be rotated by 90 degrees around the Z axis and scaled to the size of the box on the Y (instead of X) axis.

const backSlot = slot.rotateZ(90).

The front indentation is similar to the back one, but it needs a small bit taken out for the USB connector.

const frontSlot = difference(

Small bit taken out for the USB connector`

With all the indentations available, the program uses the union function to put all the indentations together.

const slots = union(slot,
  slot.translate([0, boxSize[1]-wallWidth, 0]),

Union function puts all the indentations together`

Now that we have the indentations, we can add them to the box to get the complete box.

const completeBox = union(noSlotBox,

The complete box`

And also subtract them from the lid (in addition to the slots for wires) to get the complete lid.

    const top = cube({size: [boxSize[0], boxSize[1],

    const wireHole = cube({size: wireHoleSize}).

    const wireHoles = union(wireHole.translate([0,wallWidth,0]),

    const completeTop = difference(top,

The 3D printing material (PLA in most cases) is liquid when extruded by the printhead, and as it cools down, it is a bit sticky. As it is deposited, it sticks to the layer below it or the printbed, in the case of the bottom layer. However, it also sticks to the printhead. If the item you are printing is too small, the bottom layer might stick to the printhead with more force than it sticks to the printbed and be dragged away from where it is supposed to be. To avoid this, you can use a weak glue on the printbed before you start the print job and use a butter knife to separate the printed object after it is done.


It’s difficult to learn the skills to use a 3D printer by reading about them, rather than creating your own objects. But hopefully, I made you curious enough to invest in an inexpensive 3D printer and provided you with enough information to get started.