# How to make hollow polyhedra?

I found a version of the Mathematica spikey in 3D printable format (STL) at the Shapeways site that was hollow. Here it is when viewed in MeshLab:

You can see there's a bit cut out of the end, and the whole thing is obviously hollow, with a thick shell.

When I try to make a spikey myself, with code like this:

spikey = PolyhedronData["MathematicaPolyhedron"]
Export["spikey.stl", spikey]


and look at the file in MeshLab, it's solid, but if I look inside, it's obviously just a surface, rather than a thick shell. You can see through to the inside of the opposite faces.

The file with a bite out of it was created by the "Wolfram Team", so I'm pretty sure they used Mathematica. But can this hollowing-out treatment be done using some Mathematica code, or does it require the assembled brain-power of the entire Wolfram organization, or some third-party software, to make polyhedra hollow?

The reason for hollowing out polyhedra is that 3D printers charge per cubic centimeter of material used, so hollow shapes are much cheaper than solid shapes. (This one starts at 45 Euros, so it's quite expensive even when hollow.)

So: How can I hollow out 3D shapes created in Mathematica? Or is it a case of expanding the surfaces to make flat cuboids?

Summary

I liked both answers, and in general the solutions work. However, this business of printing 3D polyhedra made in Mathematica proves to be more complicated, and takes me beyond the scope of my innocent initial question. JM.'s excellent thickened polygons look great, but some of the checking programs used by 3D printers consider the adjacent faces to be holes. Here's a picture comparing a cross-section slice of the Wolfram spikey (on the right) with a similar slice through the thickened polygon (on the left):

The Wolfram team's version (right) has no internal walls. The adjacent surfaces in the version built by the code on this page (left) are considered to be 'holes' by analysis programs such as NetFabb, even though it makes no visible difference to us. The construction of an interior wall (jVincent's approach) also looks good, and passes some checks (but not others); for printing purposes these shapes should ideally have a hole joining outside and inside surfaces.

I think a future version of Mathematica could benefit from something like Boolean operations for solid shapes, whereby simpler shapes could be merged together to form more complex shapes that are less problematic for subsequent use. Perhaps in version 9.0?! :)

-
Now, a 3D-printed hollow spikey could be a wonderful bounty :) – Dr. belisarius Aug 19 '12 at 20:35
@belisarius if Mathematica chooses the right six lottery numbers next week I will buy you one... – cormullion Aug 19 '12 at 21:10
Here's how to Import[] the thing in Mathematica: Import["http://www.shapeways.com/model/download/669186", "original-669186_v0.stl"] – J. M. Aug 20 '12 at 3:11
Commonly 3D printers fill internal spaces with a configurable sparse matrix of material, not solid PLA/ABS. Was that a factor in your cost calculations? – image_doctor Aug 20 '12 at 6:40
"I think a future version of Mathematica could benefit from something like Boolean operations for solid shapes" heck, we don't even have simple vector shape intersection yet; keep dreaming! (I'd love both of these myself.) – Mr.Wizard Aug 24 '12 at 7:55

Here's a procedure I use:

(* barycenter of a polygon *)
averagepoints[points_?MatrixQ] :=
Mean[If[TrueQ[First[points] == Last[points]], Most, Identity][points]]

(* Newell's algorithm for face normals *)
newellNormals[pts_?MatrixQ] := Module[{tp = Transpose[pts]}, Normalize[MapThread[Dot,
{RotateLeft[ListConvolve[{{-1, 1}}, tp, {-1, -1}]],
RotateRight[ListConvolve[{{1, 1}}, tp, {-1, -1}]]}]]]

thickenaux[points_, outer_, thick_] := Module[{center = averagepoints[points],
outerpoints = Map[(center - thick nrm + outer (# - center)) &, points];
Map[Partition[#, 2, 1, 1] &, {points, outerpoints}]];

ThickenPolygons[shape_, outer_: 0.8, thick_: 0.04] :=
shape /. Polygon[p_?MatrixQ] :> thickenaux[p, outer, thick]


The procedure is more or less a modification of the old (undocumented) package function OutlinePolygons[] in GraphicsShapes.

Let's try it out on a simpler case:

Graphics3D[{FaceForm[Cyan, Red], ThickenPolygons[
Delete[Cases[Normal[PolyhedronData["Tetrahedron"]], _Polygon, Infinity], 3]]},
Boxed -> False, Lighting -> "Neutral"]


Note that both the interior and exterior are colored with Cyan; this is due to the code ensuring that all the polygons generated are oriented properly.

I don't know how to cut a hole in the manner shown in the Shapeways model, so I'll go for a spikey with a simpler cutaway:

Needs["PolyhedronOperations"];
spikeyCut = Cases[Normal[Stellate[
MapAt[Delete[#, {1, 4}] &, PolyhedronData["Icosahedron"], {1, 2}],
1 + 2 Sqrt[7 - 3 Sqrt[5]]]], _Polygon, Infinity];

Graphics3D[{Directive[EdgeForm[], ColorData["Legacy", "SpringGreen"]],
ThickenPolygons[spikeyCut]}, Boxed -> False]


I set the defaults in ThickenPolygons[] to work for tetrahedra; for other polyhedra or for parametrically-defined surfaces, you might need to play around with the values for the parameters outer and thick.

Here's an alternate version, which might be more suitable for polyhedra than the previous version:

thickenaux[points_, outer_, fac_] :=
Module[{center = averagepoints[points], nrm = newellNormals[points], n, outerpoints,
n = Length[points] - Boole[TrueQ[First[points] == Last[points]]];
thick = fac (1 - outer) Mean[Norm[# - center] & /@ points];
outerpoints = Map[(center - thick nrm + outer (# - center)) &, points];
Map[Partition[#, 2, 1, {1, 1}] &, {points, outerpoints}]];

ThickenPolygons[shape_, outer_: 0.8, fac_: Sqrt[2]/4] :=
shape /. Polygon[p_?MatrixQ] :> thickenaux[p, outer, fac]


If you run this version of ThickenPolygons[] on spikeyCut and carefully inspect the coordinates, you'll see that the points in the inner walls match up perfectly; there are neither gaps nor unintentional polygon intersections.

The proper value of fac to use will depend on the polyhedron being considered. For the Platonic solids ($\phi$ denotes the golden ratio),

\begin{array}{c|c} \text{polyhedron}&\class{code}{\text{fac}}\\\hline \text{cube}&\frac1{\sqrt 2}\\ \text{dodecahedron}&\frac{1+\phi}{2}\\ \text{icosahedron}&\frac{1+\phi}{2}\\ \text{octahedron}&\frac1{\sqrt 2}\\ \text{tetrahedron}&\frac1{2\sqrt 2} \end{array}

The default value used for fac works for the tetrahedron and the spikey.

-
Thanks - great approach. I'll need to investigate this by uploading some models using this approach. However, it looks like the hole is important for successful printing - for now, your cutaway is a good stop-gap. (Sorry for pun there.) – cormullion Aug 20 '12 at 13:57

I'm not sure if this will print, but to get a thick wall, you could create a new graphics complex with offset and reversed faces. The surface normal is defined by the traversal direction, so reversing the order flips the normal.

base = Normal[PolyhedronData["MathematicaPolyhedron", "Faces"]];
spikey = Join[base , base /. Polygon[a_List] :> Polygon[Reverse[a] 0.9]];
Export["spikey.stl", Graphics3D[spikey]]


If you need to do some special editing around the base or top, it might be easier to do it manually, but you could also play around with doing it through mathematica prior to exporting it. The method used here (radial expansion) won't work in all cases, but you could properly make a face to wall rule that would work more generally.

-
Excellent approach! It appears to work - at least, the models are accepted by ShapeWay's checking programs. However, I'm coming to the conclusion that it's not just hollowness which counts - there has to be a hole "joining" the inside and outside walls. – cormullion Aug 20 '12 at 13:55

The easiest way I have found to do this is to create the solid model in Mathematica, export it as an STL file, then hollow it out in OpenSCAD. The way you do that is with code like this:

difference() {
import("spikey.stl");
scale([.9,.9,.9]) import("spikey.stl");
}
`

You could then cut a hole in the shell with another difference command.

-
Thanks - this may be a good solution if Mathematica 9 lets me down... – cormullion Sep 3 '12 at 7:00