Making High Quality 3D Prints

Math 250 - Math with Mathematica
Christopher Hanusa

12.1 Overview

In this tutorial, we export 3D printable models from Mathematica, learn how to make models with higher resolution, and work directly with mesh-based data structures.

12.2 Export-Import

First, we’ll see how easy it is to export a model for 3D printing.

Exporting is easy.

It is very easy to export from Mathematica into many different file types.  Use the command Export!

?Export

Here is a 3D zigzag example we’ve seen before:

pointlist={{0,0,0},{1,1,0},{2,0,0},{3,1,0},{4,0,0},{5,1,0},{6,0,0},{7,1,0},{8,0,0}};
zigzag=Graphics3D[Tube[pointlist,.2]]

Because we’ve named the object above, we can just include it in this command here.

Export[NotebookDirectory[]<>"zigzag.png",zigzag]

The extension of the file that you use tells Mathematica which file format you want to export to.  Here we’ve exported a PNG file which is an image file and can easily be imported into a be directly inserted .

The NotebookDirectory[] code chooses the folder that our current file is saved in.  The <> (“less-than”-“greater-than”) appends the name of your file to the text string of the current directory.  This helps to keep your files organized.

And then you can see what you’ve exported using Import.

Import[NotebookDirectory[]<>"zigzag.png"]

This imported object is an image.  This means we can’t rotate it or add to it anymore, but we could do some image processing if we wanted to.

Exporting is not easy.

When you want to create a 3D-printable model, you will want to export it to one of the 3D model types, such as STL.

Export[NotebookDirectory[]<>"tubetest.stl",zigzag]

Import[NotebookDirectory[]<>"tubetest.stl"]

Spin it around - Do you notice anything?

The 3D rendering in Mathematica is not the same as what is exported to the STL file by default.  There are some commands that export well and others that do not export at all!  So as you learn what works and what doesn’t, you will want to export your model to an STL file and re-import it to check that the exported file has the structure you want.

For example, to fix our zigzag structure, we’ll need to add spheres of the same radius as our tube.  Use Map to create them all at once!  Let’s add colors to be able to see the different parts.

spheres=Map[Sphere[#,.2]&,pointlist]
segments=Tube[pointlist,.2]
newZig=Graphics3D[{Blue,segments,Red,spheres}]

Export[NotebookDirectory[]<>"newZig.stl",newZig]

Import[NotebookDirectory[]<>"newZig.stl"]

Exporting in color

The STL format does not support color, so we’ll want to use another format (like X3D or WRL) if we want to have a 3D model with some color.

colorZig=Graphics3D[{Red,segments,Blue, spheres}]

Export[NotebookDirectory[]<>"colorZig.x3d",colorZig]

Export[NotebookDirectory[]<>"colorZig.wrl",colorZig]

Unfortunately Mathematica won’t import these formats.

Import[NotebookDirectory[]<>"colorZig.x3d"]
Import[NotebookDirectory[]<>"colorZig.wrl"]

To verify that the exporting has worked correctly, we need to use an external viewer to see the models. An online service is Sketchfab.  You can create an account for education using your QC email. Alternatively, you can upload your models directly to the 3D printing service Shapeways and view them there.

12.3 Creating smoother curves and surfaces

When you use one of the plotting commands (like Plot3D, ParametricPlot3D, or RegionPlot3D), use the PlotPoints option to increase the resolution of your model.  It changes the number of points that Mathematica uses to approximate your model.  A larger value increases the resolution, but don’t make it too large or it will increase your computation time and result in a very large file.

Smoother curves

curve=ParametricPlot3D[{Sin[t],Sin[2t],Sin[3t]},{t,0,2Pi},PlotStyle->Tube[.1],PlotRange->All]

Import[Export[NotebookDirectory[]<>"Curve.stl",curve]]

Make the tube smoother by adding in PlotPoints options into both the ParametricPlot3D function (for the number of points chosen along the curve) and the PlotStyle option (for the number of points chosen on each circular cross-section).  Notice how much longer it takes to process!

curveHD=ParametricPlot3D[{Sin[t],Sin[2t],Sin[3t]},{t,0,2Pi},PlotStyle->Tube[.1,PlotPoints->50],PlotRange->All,PlotPoints->100]

Import[Export[NotebookDirectory[]<>"Curve.HD.stl",curveHD]]

Smoother surfaces

To improve the resolution of our torus, use a PlotPoints option.

torus=ParametricPlot3D[{(3+Cos[v]) Cos[u],(3+Cos[v]) Sin[u],Sin[v]},{u,0,2Pi},{v,0,2Pi},PlotStyle->Green,Mesh->None,PlotPoints->30]

Import[Export[NotebookDirectory[]<>"Torus.stl",torus]]

btorus=ParametricPlot3D[{(3+Cos[v]) Cos[u],(3+Cos[v]) Sin[u],Sin[v]},{u,0,2Pi},{v,0,2Pi},PlotStyle->Green,Mesh->None,PlotPoints->{80,80}]

Import[Export[NotebookDirectory[]<>"Torus.HD.stl",btorus]]

Smoother three-dimensional objects

Here is a block that has a bumpy surface.

block=RegionPlot3D[Sin[x]+Sin[y]>z,{x,0-Pi/2,6Pi-Pi/2},{y,0-Pi/2,6Pi-Pi/2},{z,-3,3},BoxRatios->Automatic]

Import[Export[NotebookDirectory[]<>"Bumpy.stl",block]]

That’s not nearly as smooth as we would like it, because Mathematica is not sampling many points in each direction.  We can specify the number of points sampled in each direction using the PlotPoints option.

block2=RegionPlot3D[Sin[x]+Sin[y]>z,{x,0-Pi/2,6Pi-Pi/2},{y,0-Pi/2,6Pi-Pi/2},{z,-3,3},PlotPoints->{100,100,100},BoxRatios->Automatic]

Import[Export[NotebookDirectory[]<>"Bumpy.HD.stl",block2]]

12.4 Meshes

An Imperfect World

Mathematica uses mesh-based geometric regions to make three-dimensional objects 3D printable. It is another example of a theoretical object being represented in a human-usable format, similar to how complex numerical expressions can be converted to a decimal or a two-dimensional scene can be represented as a collection of pixels:

Sqrt[Sqrt[3]+5]

N[(Sqrt[5+(Sqrt[3])])]

heptagon=Graphics[{RegularPolygon[7]}]

Row@{Rasterize[heptagon,ImageSize->50],Rasterize[heptagon,ImageSize->100],Rasterize[heptagon,ImageSize->200]}

Boundary Representation vs. Cell Representation

The process of replacing an ideal object by an approximation is called discretization (or triangulation).  This replaces the smooth object by a discrete object made up of cells: points, line segments, triangles, tetrahedra, and higher-dimensional simplices. There are two main ways to describe a discretized object: either by the solid cells that comprise it or by its boundary cells.  Compare the following two discretizations of the circle:

{DiscretizeRegion[Disk[]],BoundaryDiscretizeRegion[Disk[]]}

In the first example, the circle is approximated by a collection of two-dimensional triangles. In the second example, the circle is approximated as the interior of a collection of one-dimensional line segments.

Similarly, a sphere can be represented as a collection of three-dimensional tetrahedra or as the interior of a collection of two-dimensional triangles.

{DiscretizeRegion[Ball[]],BoundaryDiscretizeRegion[Ball[]]}

You can’t see the difference here but we can understand it better by accessing the information directly using MeshPrimitives:

MeshPrimitives[DiscretizeRegion[Ball[]],3][[1;;5]]
Graphics3D[{%,Opacity[.1],Sphere[]}]

MeshPrimitives[BoundaryDiscretizeRegion[Ball[]],2][[{100,300,500,700,900}]]
Graphics3D[{%,Opacity[.1],Sphere[]}]

The commands

When you are discretizing a shape that is defined as a region, use DiscretizeRegion or BoundaryDiscretizeRegion.  When you are discretizing a shape that is defined as a graphics object, use DiscretizeGraphics or BoundaryDiscretizeGraphics.

Mesh Notes

The Mathematica developers have been making a lot of progress!

Since boundary representations are one lower dimension than cell representations, they are easier to deal with and have more functionality in Mathematica.  However, not all shapes have a nice boundary representation, in which case you’ll have to work with the shape’s cell representation.

When you use Export, I think Mathematica tries its best to discretize each piece of your model, then combines everything together.  This process sometimes goes awry, so it may make sense to discretize your objects first and then export. For example, Mathematica is able to discretize the zigzag from above without the need to include the spheres.

DiscretizeGraphics[Tube[pointlist,.2]]

When you export and import to check the model, you see it is the same and has not lost any parts.

Import[Export[NotebookDirectory[]<>"temp.stl",%]]

12.5 Creating a smoother mesh

To improve the quality of your discretized region, use the option MaxCellMeasure.

{BoundaryDiscretizeGraphics[Ball[],MaxCellMeasure->.01],
BoundaryDiscretizeGraphics[Ball[],MaxCellMeasure->.001],
BoundaryDiscretizeGraphics[Ball[],MaxCellMeasure->.0001]}

{BoundaryDiscretizeGraphics[Graphics3D[Tube[pointlist,.2]],ImageSize->400],
BoundaryDiscretizeGraphics[Graphics3D[Tube[pointlist,.2]],MaxCellMeasure->.0001,ImageSize->400]}

12.6 Creating meshes from images and text

You can discretize images:

Drag and drop your black and white image here.

flower=;

Use ImageMesh to discretize.  (ColorNegate is used to convert white pixels to black and vice versa)

image=ImageMesh[ColorNegate[flower]]

You can also discretize words in whatever fonts your computer has installed.

word=Text[Style["word",Bold,FontFamily->"Bauhaus 93",FontSize->50]]

text=BoundaryDiscretizeGraphics[word,_Text]

12.7 Accessing parts of a mesh

Use MeshCoordinates and MeshPrimitives to get information out of a mesh-based region.  MeshCoordinates will extract the coordinates of the mesh

coords=MeshCoordinates[ball];

first10=Take[coords,10]

Graphics3D[Map[Point,coords]]

Use MeshPrimitives will extract other parts of the object, like its edges or polygonal faces.  The second input is the dimension of the parts you want to extract.

edges=MeshPrimitives[ball,1];  (* edges have dimension 1 *)

first10=Take[edges,10]

Graphics3D[edges]

faces=MeshPrimitives[ball,2];  (* faces have dimension 2 *)

first10=Take[faces,10]

Graphics3D[faces]

12.8 Region properties

mesh=PolyhedronData["SnubCube","MeshRegion"]

There are two useful commands to situate your object in 3D space. RegionBounds lets you know the size of the object by giving the lower and upper bounds of the x, y, and z variables for all points in the object. RegionCentroid gives the center of mass of the object.

Here we see that the size of this object is 2.29 units wide and is centered at (0,0,0):

RegionBounds[mesh]
Map[#[[2]]-#[[1]]&,%]

Chop[RegionCentroid[mesh]]

12.9 Changing the size of the Region

There is an easy way to change the size of a model when it is a mesh-based region---use RegionResize.  If you only use one parameter as the second input, it will scale the object to have first coordinate equal to that input.  If you specify a list of parameters, it will scale the object to fit into the given box, not respecting the ratios between side lengths.

If we want our model to be 100 units wide, we would write:

newmesh=RegionResize[mesh, 100]

RegionBounds[newmesh]

If we want to skew it differently in each direction, we can do this:

skewmesh=RegionResize[mesh, {10,1,2}]

RegionBounds[skewmesh]

When you are finalizing your model, you will have a 3D rendering of your object that you created using coordinates with dimensions determined by mathematical units. Once you have exported your model and re-imported your model to make sure that the form is correct, you will need to scale your model to be in millimeters before you upload to Shapeways.  For instance, the model that was 100 units will be 100 mm (approximately 4 inches) when printed by the 3D printer.  As you change the size of your model, the price of your 3D print is impacted.  So you will want to play around with the sizing and do a cost-benefit analysis to determine the final size of your model.

12.10 Translations and Rotations

If you want to perform a transformation on a region, use TransformedRegion and the various Transform commands.

For example, we can apply various transformations to the basic cube:

cube=BoundaryDiscretizeGraphics[Cuboid[]];
Graphics3D[{Green,cube},Axes->True,Boxed->True,AxesLabel->{"x","y","z"}, ImageSize->150,PlotRange->All]

To rotate the object by a given angle θ around an axis of rotation (with direction v and passing through point p), input these values into RotationTransform.

rotated=TransformedRegion[cube,RotationTransform[Pi/4,{0,0,1},{1/2,1/2,0}]];
Graphics3D[{{Opacity[.4],Green,cube},{Red,rotated}},Axes->True,Boxed->True,AxesLabel->{"x","y","z"}, ImageSize->300,PlotRange->All]

To translate the object along a vector v, input that vector into TranslationTransform.

translated=TransformedRegion[cube,TranslationTransform[{1,1,0}]];
Graphics3D[{{Opacity[.4],Green,cube},{Red,translated}},Axes->True,Boxed->True,AxesLabel->{"x","y","z"}, ImageSize->300,PlotRange->All]

Combining a few of these techniques is useful to reset the position of an object.

Example 12.10.1. Create a region shaped like Germany that is centered at the origin and 2 inches across.

germany=BoundaryDiscretizeGraphics[CountryData["Germany","Shape"]];
RegionBounds[germany]
Show[germany,Axes->True]

imagereset=RegionResize[TransformedRegion[germany,TranslationTransform[-RegionCentroid[germany]]],2*2.54];
Show[imagereset,Axes->True]

12.11 Region boolean operations

RegionIntersection, RegionUnion, and RegionDifference are used to intersect, merge, or subtract two regions.  These commands work well in 2D. RegionIntersection and RegionDifference do not often work for 3D cell representations.  (But starting in version 12.1 of Mathematica, they do work more often for 3D boundary representations!)

2D Examples

ovalmesh=DiscretizeGraphics[Graphics[Disk[{0,0},{6,4}]]];
trianglemesh=DiscretizeGraphics[Graphics[Polygon[{{-2,-7},{-2,7},{4,0}}]]];
squaremesh=DiscretizeGraphics[Graphics[Polygon[{{0,0},{0,1},{1,1},{1,0}}]]];
Graphics[{Opacity[.3],Green,ovalmesh,Blue,trianglemesh,Red,squaremesh}]

The union of the oval and the triangle:

RegionUnion[ovalmesh,trianglemesh]

The intersection of the oval and the triangle:

RegionIntersection[ovalmesh,trianglemesh]

The oval with the square removed:

RegionDifference[ovalmesh,squaremesh]

3D Boundary Mesh Examples

ball=BoundaryDiscretizeGraphics[Graphics3D[Ball[{0,0,0},1]]];
cube=BoundaryDiscretizeGraphics[Graphics3D[Cuboid[{0.1,0.1,0.1},{1,1,1}]]];
tetrahedron=BoundaryDiscretizeGraphics[PolyhedronData["Tetrahedron","Polyhedron"]];
Graphics3D[{Opacity[.3],Green,ball,Blue,cube,Red,tetrahedron}]

Here you can see that you can make the union of two 3D meshes, but the intersection and the difference don’t  work

{RegionUnion[ball,cube],RegionUnion[tetrahedron,cube]}
{RegionIntersection[ball,cube],RegionDifference[ball,cube],RegionDifference[tetrahedron,cube]}

3D Region Mesh Examples

ball=DiscretizeGraphics[Graphics3D[Sphere[{0,0,0},1]]];
cube=DiscretizeGraphics[Graphics3D[Cuboid[{0,0,0},{1,1,1}]]];
tetrahedron=DiscretizeGraphics[PolyhedronData["Tetrahedron","Polyhedron"]];

Here you can see that you can make the union of two 3D meshes, but the intersection and the difference don’t  work

{RegionUnion[ball,cube],RegionUnion[tetrahedron,cube]}
{RegionIntersection[ball,cube],RegionDifference[ball,cube],RegionDifference[tetrahedron,cube]}