# STL generation for Ball-in-a-maze puzzle

As a minimal example, consider:

annuli = {Annulus[{0, 0}, {0.8, 1}, {1.3681023133518435,
6.255024218935966}],
Annulus[{0, 0}, {1.8, 2}, {2.9394676092071563,
8.524521215589012}],
Annulus[{0, 0}, {2.8, 3}, {5.390068220020829,
11.207832393335261}],
Annulus[{0, 0}, {3.8, 4}, {1.847790960182163,
7.781910416962884}],
Annulus[{0, 0}, {4.8, 5}, {5.369176063296676,
11.373108690157169}]}

regU = RegionUnion@(RegionProduct[annuli[[#]] ,
Line[{{0}, {0.8}}]] & /@ Range[Length@annuli])

Region[RegionUnion[
regU
, Cylinder[{{0, 0, 0.2}, {0, 0, -0.75}}, 5.8]
, Cylinder[{{0, 0, -0.75}, {0, 0, -1.5}}, 6.0]
]
, Boxed -> True
, BoxRatios -> Automatic
, Axes -> True
, PlotRange -> {{-7, 7}, {-7, 7}, {-2, 2}}
, ImageSize -> Medium
]


Using Show, a better rendering is generated for annuli on top.

Show[Region /@ (RegionProduct[annuli[[#]] , Line[{{0}, {0.8}}]] & /@
Range[Length@annuli])]


Question: I want to generate a STL file from this. How can I improve the resolution of the structure shown in the first graphic?

Any suggestions, tips, improvements will be gratefully received.

• Removing PlotRange seems to do it. Jun 10, 2022 at 16:56
• (2) use the CSGRegion functions (but unfortunately it looks like they don't handle the Product that you use to create the extruded annuli)... Jun 10, 2022 at 17:11
• @lericr, why would you use an external CAD system when Mathematica ships with it's own? Jun 10, 2022 at 20:09
• @user21 I guess I don't know what CAD system you're referring to. All I can say is that the functions that seem most amenable to generating printable object, i.e. the Region* functions, just don't perform well compared to other programs that are special-built for that purpose. The new CSGRegion* functions are much better. Jun 10, 2022 at 20:44
• @user21 Just saw your answer with a link to OpenCascadeLink. I wasn't aware of this. Where have you been for the last 10 years as I desperately looked for a Mathematica-only solution for my printing projects? Jun 10, 2022 at 20:48

Using OpenCascadeLink, which comes with Mathematica, will give you excellent results. We start by loading the package and writing a little helper function to convert the annuli to polygons in 3D space which we then rotate in 3D.

Needs["OpenCascadeLink"]
f[\[Theta]_, r_] := Sequence[r*Cos[\[Theta]], r*Sin[\[Theta]]]
makePoly[\[Theta]_, {rin_, rout_}, {h1_, h2_}] :=
Polygon[{{f[\[Theta], rin], h1}, {f[\[Theta], rout],
h1}, {f[\[Theta], rout], h2}, {f[\[Theta], rin], h2}}]


Create the polygon and visualize:

poly = makePoly @@@
Transpose[{annuli[[All, 3, 1]], annuli[[All, 2]],
ConstantArray[{0, 0.8}, Length[annuli]]}];

Graphics3D[poly]


Convert the polygon to OpenCascade shapes and rotate them by the given amount:

shapes = OpenCascadeShape /@ poly;
axis = {{0, 0, 0}, {0, 0, 1}};
sweeps =
Subtract @@@ annuli[[All, 3]][[All, {2, 1}]]}];


Create the two cylinders and make a union of all of it:

cyls = OpenCascadeShape /@ {Cylinder[{{0, 0, 0.2}, {0, 0, -0.75}},
5.8], Cylinder[{{0, 0, -0.75}, {0, 0, -1.5}}, 6.0]};


If you want to inspect before exporting use a wireframe:

OpenCascadeShapeSurfaceMeshToBoundaryMesh[union][
"Wireframe"["MeshElementStyle" -> FaceForm[Orange]]]


Ah, yes, and export:

OpenCascadeShapeExport["~/test.stl", union]


Have a look at the aforementioned tutorial and also this Helical Bevelgear example to get an idea of the scope of OpenCascadeLink.

You could convert it to BoundaryMeshRegion and do operations:

regU = (RegionProduct[BoundaryDiscretizeGraphics@annuli[[#]],
Line[{{0}, {0.8}}]] & /@ Range[Length@annuli]);

cylinder =
BoundaryDiscretizeGraphics /@ {Cylinder[{{0, 0, 0.2}, {0,
0, -0.75}}, 5.8],
Cylinder[{{0, 0, -0.75}, {0, 0, -1.5}}, 6.0]};

RegionUnion[Flatten[{cylinder, regUU}]]


Although CSGRegion is hard to export to stl upto the 13.0.1 version.

annuliCylinder[{x_, y_}, {r1_, r2_}, {θ1_, θ2_}, {h1_,
h2_}] :=
CSGRegion[
"Difference", {CSGRegion[
"Difference", {Cylinder[{{x, y, h1}, {x, y, h2}}, r2],
Cylinder[{{x, y, h1}, {x, y, h2}}, r1]}],
Parallelepiped[{x, y,
h1}, {{r2*Cos[θ1], r2*Sin[θ1],
h1}, {r2*Cos[θ2], r2*Sin[θ2], h1}, {x, y, 2 h2}}]}]
data = Append[#, {0, 1}] & /@
List @@@ {Annulus[{0, 0}, {0.8, 1}, {1.3681023133518435,
6.255024218935966}],
Annulus[{0, 0}, {1.8, 2}, {2.9394676092071563,
8.524521215589012}],
Annulus[{0, 0}, {2.8, 3}, {5.390068220020829,
11.207832393335261}],
Annulus[{0, 0}, {3.8, 4}, {1.847790960182163,
7.781910416962884}],
Annulus[{0, 0}, {4.8, 5}, {5.369176063296676,
11.373108690157169}]};
CSGRegion["Union", {annuliCylinder @@@ data,
Cylinder[{{0, 0, 0.2}, {0, 0, -0.75}}, 5.8],
Cylinder[{{0, 0, -0.75}, {0, 0, -1.5}}, 6.0]} // Flatten,
ImageSize -> Full]
`