As previously mentioned, I did make an attempt to generate 3D shapes using spirographs. But only using circles, I really didn’t feel like trying to sort out getting the other shapes plotted/spinning on a specifc 2D plane in a 3D space. At this point, expect I never will.

Even though I pretty much failed at producing any interesting 3D shapes, I thought I’d record my efforts and failure. Mainly because even in failure we all learn something. And, you may be able to take it where I couldn’t.

A surface of rotation was out of the question. I did not have an equation mapping and x and y values to a z value. I am using an equation to generate x, y and z values from a series of angles. I had no obvious way to tell the plot engine what z value to use for any given x and y values.

Surface Plots

matplotlib does have a 3D method for generating surfaces from 3D plots. Specifically Axes3D.plot_surface. Not sure how it works, didn’t really dig into it.

Still had the basic problem of mapping x and y values to a z value. But I decided to give it a try.

X, Y = np.meshgrid(pxs[-1], pys[-1])
Z, Z2 = np.meshgrid(pzs[-1], pzs[-1])

cmp = get_clrmap(rcm)
mx_lim = max(max(pxs[-1]), max(pys[-1]), max(pzs[-1]))
ax.set_xlim(-mx_lim, mx_lim)
ax.set_ylim(-mx_lim, mx_lim)
ax.set_zlim(-mx_lim, mx_lim)

surf = ax.plot_surface(X, Y, Z, alpha=1, cmap=cmp)

This ended up producing a plot that looked like a cylinder/tube with a number of crazy shape inner tubes down its length/height. Looks like the 3D curve was squashed down to a 2D shape and replicated for each value in one dimension of one of the above arrays. Here’s an example. (Again these are quite large files, so I did not embed the videos in this post.)

So, I used fewer values to get something somewhat less tubular.

surf = ax.plot_surface(X[:5][:200], Y[:5][:200], Z[:5][:200], alpha=1, cmap=cmp)

And, here’s an example of the change.

Tri-Surface Plots

Looking a little further into the documentation I got to Axes3D.plot_trisurf. This method generates a surface using Delaunay triangulation. Well Delaunay is, I believe, the default. I didn’t try anything else.

# mess with plot_trisurf
cmp = get_clrmap(rcm)
ax.plot_trisurf(pxs[-1], pys[-1], pzs[-1], linewidth=ln_w, cmap=cmp)

Here’s a couple of examples. Would be smoother if I used more plotting points. But, as it was, the animations took quite some time to save to file.

Pretty much solid blocks.

Masking

A little more research led me to the use of masking. Figured I could use that to give me shapes without anything in the middle. And to perhaps remove some of the spiker bits on the perimeter of most of the generated tri-surfaces. That took a bit more code.

# trisurf: min/max radius for generating mask
min_radius = 0.5
max_radius = 2.0

...

# mess with plot_trisurf
cmp = get_clrmap(rcm)

# Create the Triangulation; no triangles so Delaunay triangulation created.
triang = mtri.Triangulation(pxs[-1], pys[-1])

# Mask off unwanted triangles.
print(f"masking with min radius {min_radius} and max radius {max_radius}")
xmid = pxs[-1][triang.triangles].mean(axis=1)
ymid = pys[-1][triang.triangles].mean(axis=1)
# bit of a guess here
mask1 = xmid**2 + ymid**2 < min_radius**2
mask2 = xmid**2 + ymid**2 > max_radius**2
mask = np.logical_or(mask1, mask2)
triang.set_mask(mask)

# Plot the surface.
ax.plot_trisurf(triang, pzs[-1], cmap=cmp)

And, an exmaple of two.

Done I’m Sure

Well, like I said, pretty much a failure. No interesting and viable 3D objects or surfaces that might have been worth 3D printing. But, did learn a bit more about the capabilities of matplotlib and numpy. And, having a short post isn’t always bad. (Though that statement does hide the time required to write/debug/refactor the code and generate/save images.)

This may really be it for spirograph posts. I had thought about one more covering the code for my do-all-be-all spirograph module. But, it is currently very much a pigsty. I would be truly embarassed to show it to anyone. And, I am not sure I currently have the time to refactor it into something a touch more presentable.

It’s time for us to get out and do some serious yard work. Also, I am working on a react/nextjs course, which is beginning to take more time each week. Many weeks left to go. And, I should probably get back to working on my attempt to learn something about data science and machine learning.

But, if I ever do meaningfully refactor the module, I will likely add another spirograph post.

Until next time, be happy, be coding.

Resources