My sister recently commisioned me to make a 3D printed model of a mathematical shape. This shape is from Alex McDonough's research paper: "A Combinatorial Mapping For The Higher-Dimensional Matrix-Tree Theorem".
Identifying the Challenge
In the past when I have been asked to print 3D parts, the requests have come from engineers and technologists with some experience using 3D CAD software such as Autodesk Inventor, Solidworks, FreeCAD, etc. These requests are usually made with them providing me an STL file of their model. I can simply import this STL file into my slicer program, generate the code for the 3D printer, and start printing the part.
My sister who studies math, comes from a very different educational background, and as a result does not have experience using CAD software to provide me with an STL file. This led to challenges when she gave me the input information for the model she wanted me to fabricate. The model is composed of three parallelepipeds, one large one in the back, a smaller one in the front, and a small cube on top.
Alex McDonough made the animation below in Blender. It is showing how the shape can be used to tile space.
My sister gave me the figure from above with all of the points labeled with their x, y, z coordinates as shown in the image below.
While this seems simple enough, I quickly realized that I had never created a model in 3D CAD from coordinates alone. Drawings in 3D CAD software are typically made using dimensions rather than coordinates. I knew that it wouldn't be very difficult or time consuming to derive the dimensions of the shape from the input data, but I knew that there must be a way to generate the STL file from the coordinates alone through code. If mathematicians can incorporate STL file generation algorithmically through code in their workflow, it would allow for them to much more easily get figures from their work 3D printed. Sure enough, after a quick google search, I found a library called numpy-stl.
Generating the STL File
To start off I installed numpy-stl globally on my computer using the following command.
pip install numpy-stl
Cube Example
In the numpy-stl documentation there is an example called "Creating Mesh objects from a list of vertices and faces". This example was a perfect starting point for generating a STL file from a list of coordinates. The full example is shown below. It generates a STL file for a cube.
import numpy as np
from stl import mesh
# Define the 8 vertices of the cube
vertices = np.array([\
[-1, -1, -1],
[+1, -1, -1],
[+1, +1, -1],
[-1, +1, -1],
[-1, -1, +1],
[+1, -1, +1],
[+1, +1, +1],
[-1, +1, +1]])
# Define the 12 triangles composing the cube
faces = np.array([\
[0,3,1],
[1,3,2],
[0,4,7],
[0,7,3],
[4,5,6],
[4,6,7],
[5,1,2],
[5,2,6],
[2,3,6],
[3,7,6],
[0,1,5],
[0,5,4]])
# Create the mesh
cube = mesh.Mesh(np.zeros(faces.shape[0], dtype=mesh.Mesh.dtype))
for i, f in enumerate(faces):
for j in range(3):
cube.vectors[i][j] = vertices[f[j],:]
# Write the mesh to file "cube.stl"
cube.save('cube.stl')
I saved this example code into a file called generate_cube.py. Running the file generates the STL file "cube.stl". In the gif below, I import the STL file into my slicer, and as you can see, it appears as expected.
Generating the Shape
Now that I have confirmed the example worked, I can start trying to generate the figure from the paper. First I imported all of the necessary Python libraries.
import numpy as np
from stl import mesh
Then, I created a numpy array of all of the vertices that make up the shape. I started at the top corner of the shape and moved down through all of the points in the shape, trying to keep adjacent points in order as much as possible. I also commented the index of each coordinate in the array on the same line. This will make it easier to access their indices when defining the faces.
vertices = np.array([
[0,0,1], #0
[0,1,1], #1
[1,1,1], #2
[1,0,1], #3
[0,0,0], #4
[0,1,0], #5
[1,1,0], #6
[1,0,0], #7
[3,3,0], #8
[3,2,0], #9
[0,1,-3], #10
[3,3,-3], #11
[4,2,0], #12
[0,0,-2], #13
[1,0,-2], #14
[0,0,-3], #15
[3,2,-3], #16
[3,2,-2], #17
[4,2,-2], #18
])
Next, I created a numpy array of faces. STL files require all faces to be defined as triangles using the right hand rule to define the normal. This simply means that triangles should be defined with their coordinates ordered counter-clockwise to make sure that the face of the triangle is pointing outward. Here are the steps for defining a triangle face in the model:
- Identify the points of the triangle and arrange them in a counter-clockwise order.
- Translate the points into indices in the
vertices
array (defined above). - Add this list of vertex indices to the
faces
array.
Now, let's follow these steps to define the first face which is highlighted in blue in the image below:
- Looking at the image, I can see that the points are (0,0,1), (1,1,1), and (0,1,1) in counter-clockwise order.
- In the
vertices
array, I can see that (0,0,1) is at index 0, (1,1,1) is at index 2, and (0,1,1) is at index 1. - From steps one and two I can conclude that the first face I need to add to the
faces
array is[0,2,1]
.
Below is the faces array after repeating this process for every triangle making up the shape.
faces = np.array([
[0,2,1],
[0,3,2],
[0,1,5],
[0,5,4],
[5,1,2],
[5,2,6],
[3,6,2],
[3,7,6],
[0,4,7],
[0,7,3],
[6,8,5],
[6,9,8],
[5,8,11],
[5,11,10],
[6,7,12],
[6,12,9],
[4,13,14],
[4,14,7],
[4,10,15],
[4,5,10],
[8,16,11],
[8,9,16],
[9,12,18],
[9,18,17],
[7,18,12],
[7,14,18],
[13,17,18],
[13,18,14],
[13,15,16],
[13,16,17],
[15,10,11],
[15,11,16],
])
Now that I have a list of faces and vertices I can start generating the vectors that compose the shape. To start off, I can initialize a variable called shape
to be a numpy-stl Mesh
object by using the following line.
shape = mesh.Mesh(np.zeros(faces.shape[0], dtype=mesh.Mesh.dtype))
Then, the vectors for the shape can be generated with the following nested for
loops.
for i, f in enumerate(faces):
for j in range(3):
shape.vectors[i][j] = vertices[f[j], :]
Lastly, the shape can be saved using the Mesh.save
function. This function also updates the normal vectors by default.
shape.save("parallelepipeds.stl")
I can now import this STL file into my slicer software and generate the code for my 3D printer to make the figure.
Below is the full Python script that I used for generating the shape.
import numpy as np
from stl import mesh
vertices = np.array([
[0,0,1], #0
[0,1,1], #1
[1,1,1], #2
[1,0,1], #3
[0,0,0], #4
[0,1,0], #5
[1,1,0], #6
[1,0,0], #7
[3,3,0], #8
[3,2,0], #9
[0,1,-3], #10
[3,3,-3], #11
[4,2,0], #12
[0,0,-2], #13
[1,0,-2], #14
[0,0,-3], #15
[3,2,-3], #16
[3,2,-2], #17
[4,2,-2], #18
])
faces = np.array([
[0,2,1],
[0,3,2],
[0,1,5],
[0,5,4],
[5,1,2],
[5,2,6],
[3,6,2],
[3,7,6],
[0,4,7],
[0,7,3],
[6,8,5],
[6,9,8],
[5,8,11],
[5,11,10],
[6,7,12],
[6,12,9],
[4,13,14],
[4,14,7],
[4,10,15],
[4,5,10],
[8,16,11],
[8,9,16],
[9,12,18],
[9,18,17],
[7,18,12],
[7,14,18],
[13,17,18],
[13,18,14],
[13,15,16],
[13,16,17],
[15,10,11],
[15,11,16],
])
shape = mesh.Mesh(np.zeros(faces.shape[0], dtype=mesh.Mesh.dtype))
for i, f in enumerate(faces):
for j in range(3):
shape.vectors[i][j] = vertices[f[j], :]
shape.save("parallelepipeds.stl")
Creating the Shape
In this section I'm going to describe the process I used to print, touch-up, and paint the shape.
The slicer software that I use is called Simplify3D. Slicer software is used for "slicing" the model into the layers to be printed by a 3D printer. This slicer software outputs a gcode file which is read by the 3D printer to perform the actions to print the model. My process for printing parts is to save my gcode files to an SD card and then insert the SD card into my printers SD card slot. Then I can simply select a file to print, and my printer will start creating the part layer by layer.
After printing the figure, I brushed on a coating called XTC-3D to smooth out the imperfections and layer lines on the print. After this coating was dry, I sanded and filed the part smooth.
To paint the model, I started off by covering it with several coats of white primer. Then I isolated each of the individual parallelepipeds, and applied several coats of their designated color. Lastly, I applied a matte finish to the model. Below is an image of the final model.
Conclusion
I hope that people from many different backgrounds can learn something new from this article. I know that mathematicians put a lot of work into writing papers, and may want a 3D print to remember their work by. I hope that this article was effective in explaining the process from a fabricator's point of view.
Comments (0)
Page 1 of 0
You need to be logged in to comment