I sometimes fall into the trap of thinking writing and the hobbies I write about as a one-way street: I do something interesting, and then I write about it. Rarely do I consider the other way around: I write something that inspires me in real life. This is partly structural: rarely do I write when I don’t have a subject to write about (much to the chagrin of the search engine optimization bots I find lurking in my site’s spam folder, hawking “algorithmic content generation services”... yeesh, no thanks). It’s also partly driven by my personal preferences: for me, programming is a series of fun logic puzzles, each of which when solved results in a little endorphin rush and feeling of pride and accomplishment. Writing, on the other hand, offers me all the fun of typing and retyping the same sentence over and over again (The early 90’s edutainment game Mario Teaches Typing is a fairly accurate representation of my writing process) until I’m “happy” with it, and then moving on to the next sentence to repeat the process. But writing has its benefits--in this case, all that introspection ended up inspiring me to develop a new feature now available in the latest version of rayshader.

In my last post I introduced the new 3D plotting features of rayshader: building beautiful 3D raytraced maps directly from an elevation matrix. It was more than just making the maps 3D, however–I wanted to make them look like a solid object. To accomplish this, I added in a base and a shadow:

Figure 1: Transforming a map from a thin surface into a solid object with the rayshader package in R. The map is Hobart, Tasmania (if Hobart, Tasmania was re-imagined as a moonscape with a river of unicorn tears).

When I described them in the blog post, I wrote the following:

I didn’t want the map to look like a carefully crinkled sheet of paper; I wanted a 3D representation that looked like a paper weight, one that you could imagine setting on your work desk and occasionally picking up and examining when you needed a mid-day moment of introspection (i.e. when you are fidgeting).

Writing that got me thinking: how cool would it be actually have one of these maps for real? I wrote that bit about introspection and realized–actually, yes please, I would indeed like a fidget map, thank you (me) very much. The first question was now: how the hell do I do that? Also, how do I convert my 3D on-screen maps to a 3D printable format? Also–how do 3D printers work, and what exactly is that 3D printable format I need?

I feel ya, buddy.

A few google searches later, and I had a rough draft of my answers: The 3D printable format I was looking for was a stereolithography (STL) file, which thankfully had an export function already in the rgl package. However, I quickly found that the output of this function applied to rayshader was not in a “production 3D printer-ready” format: The default orientation was 90 degrees off , and the size was determined by the size of the underlying matrix–an 800x800 matrix would be bigger and more expensive to print than a 400x400 matrix–and wasn’t able to be specified by the user via that function. And if the user turned on the water layer, shadow layer, or any other aesthetic option in plot_3d it would include those in the print as well.

I originally fixed the issues with the free "meshlab" software, but it wasn’t exactly a solution that I would wish upon end users. It's the main problem I've found when seeing other people explain how to 3D print topographic maps online: the toolchain they describe usually involved software that was way overkill for the intended usage: "Step 1) Download, install, and learn to use QGIS/Blender. Step 2) Now... wait, where did you go?" So, I wrote my own function and included it in rayshader, save_3dprint() (here montereybay and volcano are just N*M matrices of elevation values):

library(rayshader) #Printing the included `montereybay` dataset: montereybay %>% sphere_shade() %>% plot_3d(montereybay,zscale=50) save_3dprint("montereybay_3d.stl", maxwidth = 150, unit = "mm") ## Dimensions of model are: 150.0 mm x 150.0 mm x 38.2 mm

#Printing `volcano`: volcano %>% sphere_shade() %>% plot_3d(volcano,zscale=3) save_3dprint("volcano_3d.stl", maxwidth = 4, unit = "in") ## Dimensions of model are: 4.00 inches x 2.79 inches x 1.88 inches

save_3dprint() takes the on-screen 3D matrix, strips off all the aesthetic options (water, lines, shadow), turns it into an STL file, applies transformations to the polygons to correct the orientation and scale the matrix to the size the user wants, and then writes out the print-ready STL file. Notice that it only takes four lines of code to go from source data to finalized 3D printable file--dead simple. It also displays the dimensions of the print when you’re done, so you can sanity check the size of the model before finalizing it. Then it's ready to be sent off to the printers.