New approach to screw threads in OpenSCAD

This is an intellectual exercise with a practical end-result. There are better approaches to this problem, but the approach I describe here is interesting.

A screw thread from a stack of discs

Imagine a stack of 50 coins, stacked so that each coin is slightly offset from the one below it, with the offset moving around in a circle with each new coin. Here is what it looks like for fifty coins 20 mm in diameter and 0.5 mm thick, offset by 2 mm from a center of rotation, and stacked with a rotation offset of 30 degrees per coin (click to enlarge any image in this article):

Obviously, this is a spiral stack of coins. But it also approximates screw threads. The thread profile looks like a sinewave rather than the trapezoidal profile of machine screw threads.

Indeed, others have observed this, and used the freeware parametric CAD program OpenSCAD to make screw threads by extruding an offset circle while twisting it, as in this model on Thingiverse, for example.

From a distance, it looks OK...

...but the problem with that approach is that you end up with a thread profile having serrated surfaces, because the edges of any given facet get stretched and twisted way out of plane. Here is that Thingiverse model viewed from the side, using 20-sided circles:

For the purpose of 3D printing, as long as those serrations are much smaller than the resolution of the printer, the end result comes out smooth enough, at the expense of having way too much unnecessary detail in the model. Even so, that's the fastest way to generate threads that I know of.

But what if I could generate smooth threads with this stacked-circle technique?

Smooth threads using stacked discs

To make a smoother version of this wave thread, I wrote an OpenSCAD script to generate a polyhedron made of a spiral stack of polygon circles, with all surfaces created by connecting the same vertices of each circle. Then, because each polygon isn't rotated but just translated in a straight line by some offset on each layer (with the offset changing angular direction each layer), all of the polygons are parallel. That means the final polyhedron is made of flat rectangular faces. This is what it looks like with 20-sided circles:

That's much smoother. No serrations at all. It generates pretty fast, although not as fast as OpenSCAD's native "extrude with twist" operation. Notice how the vertical "seams" connecting the circles' vertices trace a spiral path along the screw shaft.

However, this isn't a true sinewave profile, because the sinewave is in the spiral path of the seams, not the cross-section straight up the middle of the screw shaft. Here's a deep-thread-depth stacked-circle screw shaft cut in half lengthwise, so you can see the actual profile:

Notice that the inside curves are rounder than the outside curves? This is actually better than a true sinewave for a 3D-printed screw thread, because the space between the threads is slightly wider than the actual threads, allowing the threads to mate reasonably well with standard machine threads. If I want a true sinewave thread profile, then I need to stack some sort of non-circular shape to get it.

ISO thread profile using stacked discs

The next obvious question is: why restrict myself to circles? By using other shapes, I should be able to create other thread profiles.

I decided to see if I could create ISO metric screw threads using a some flat cross-sectional shape that would produce the desired thread profile when stacked with a rotation offset.

This drawing describes the thread profile as an x-y plot with pitch as the x axis and radius as the y axis:

If the screw shaft is printed vertically, the 30° face angle may not work well, depending on your printer. The traditional "45 degree rule" for 3D printing says to avoid overhangs less than 45° from horizontal. In my case, using PrusaSlicer 2.1 with my Prusa i3 MK3S, the slicer doesn't identify any part of a standard ISO screw thread as an "overhang perimeter" so it should be OK as is for most modern printers. At less than 30°, however, the slicer does start identifying overhang perimeters, but angles shallower than the ISO standard aren't needed for fitting threads to machine parts. The primary exception that comes to mind would be a worm drive thread, which should have a nearly-flat thread face angle. Even then, only one face of the worm gear thread is under load, so it can be printed face up with a 45° angle on the unloaded underside face of the thread. However, the approach to thread generation described here doesn't work well for horizontal thread face angles, as I explain near the end of this article.

If the 3D printer doesn't work reliably for printing overhang slopes shallower than 45° (or if you're printing really large threads) it may be a good practice to increase the thread face angle from 30° to 45°. Fortunately, the ISO standard defines the parameter \(H\) (and therefore thread depth) in this figure as a function of angle \(\theta\) and thread pitch \(P\):

$$H = \frac{P}{2\tan\theta}\tag{1}$$

When \(\theta=30^\circ\), the threads are ISO threads. Regardless of the angle, the proportions shown in the figure still hold: The screw diameter is always the outer thread edge surface, which always has thickness \(P/8\), the inner diameter surface always has thickness \(P/4\), and the thread depth is always \(5H/8\).

That means I can define a dimensionless profile that defines a peak-to-peak pitch interval, with pitch ranging from 0 to 1, and thread depth ranging from -1 (inner diameter) to +1 (outer diameter).

function ISO_ext_thread_profile() = [
    [0, 1],          // middle of outer edge
    [1/16, 1],       // top of outer edge
    [0.5-1/8, -1],   // bottom of inner edge
    [0.5+1/8, -1],   // top of inner edge
    [1-1/16, 1],     // bottom of next higher outer edge
    [1, 1]           // middle of next higher outer edge

Graphically, it looks like this:

From pitch=0 to pitch=1 is one complete rotation; therefore, the horizontal axis not only represents thread pitch, but also rotation angle from 0° to 360°. The thread generator scales the depth to the desired value, and uses the profile to generate a stack of flat polygons to create a screw thread.

Let's say that we want to make a metric threaded rod 4 mm in diameter. Looking up the coarse thread pitch for 4 mm diameter gives a pitch of 0.7 mm. For a thread with a 30° face angle, the thread depth \(5H/8\) using equation (1) for \(H\) is about 0.38.

And here it is, the cross-section of an ISO 4-mm screw:

It looks almost like a circle. If you look closely, however, you'll see it's made from four curves:

  • There's a small circle arc on the right, intersecting x=2, with an arc radius equal to the screw radius (2 mm for a 4 mm diameter screw).
  • There's another circle arc on the left, intersecting x=−1.62 (0.38 from x=−2), with an arc radius equal to the screw's inner radius at the thread depth.
  • A non-constant radius curve at the top connects the left and right arcs.
  • Another non-constant radius curve at the bottom connects the left and right arcs.

Those two non-constant radius curves form the sloped faces of the threads. As these polygons stack vertically, they rotate by one angular step for each layer. The angular step size is equal to the angular step size used to generate the polygon shape, so that for each rotational increment, the polygon vertices are always vertically aligned. Because this is an irregular shape, some polygon edges won't be parallel on each rotational step, so we won't end up with nice clean rectangular facets everywhere. We get rectangular facets where the stacked polygon edges are parallel (like the inner and outer radius surfaces), and we'll also get triangles at the transitions between the different surfaces. But that's OK.

Here's how that shape stacks up into a screw thread, using 32 angular steps per full rotation:

It worked!

Using this algorithm, I can easily create new thread profiles. Here are an ISO thread, a sinewave thread, a sinewave double thread, and a triangular thread:

About that sinewave thread: The stacked discs used to make a true sinewave profile have a cardioid shape. In the picture above, it's roughly like a circle squashed a bit on one side, but at the extreme where the inner radius is zero, it's a cardoid with a cusp. Here are what the cardioids look like for a thread depth of 1/4, 1/2, and 1 times the radius of the rod, with the center of rotation shown as a black dot:

To make the original stacked-circle thread described at the beginning of this article, the profile is based on the generalized polar-coordinate equation of a circle offset from the origin by the thread depth, resulting in the distorted sinewave profile shape seen earlier.

Final touches for 3D printing

I need to add some final touches:

  • Lead-in taper
  • Radius adjustment for nonstandard thread face angles
  • ISO hexagon shapes for nuts and screw heads
  • Facet reduction

Lead-in taper

A machine screw thread generally has a taper at the beginning. For about a quarter-turn the thread transitions between minimum and maximum diameter.

For nuts, constructing a lead-in is simple: just bevel a cone out of each side of the nut, so that the cone diameter at the surface of the nut equals the thread outer diameter. Then the thread has a nice lead-in for a full turn on each end.

For screws, the thread depth and outer radius of each stacked disc must shrink slightly in the layers near the tip of the screw shaft until the depth shrinks to zero. Now, with this stacked-disc approach, the resulting tapered thread gets distorted because the thread profile along the screw shaft isn't being shrunk all at once; the profile has a different depth scaling on one end of the pitch interval compared to the other. For an ISO thread, this results in the flat outer edge of the thread transitioning to a rounded edge as the stacked discs get smaller:

Nevertheless, that result is satisfactory for the purpose of a lead-in.

Radius adjustment for nonstandard angles

As I said earlier, it shouldn't be necessary to change the ISO thread face angle for 3D printing. There may be reasons to do this, though; perhaps you have a large threads with big overhangs, or you're using a printing material that tends to sag. In that case, a thread face angle of 45° results in a smaller thread depth than one gets from using the ISO standard 30°. For a 3D printed plastic screw to fit into a metal ISO threaded hole, it is important to retain the the inner diameter, which means the outer diameter must be adjusted smaller to fit the thread face angle.

The adjustment is simple: Just use equation (1) to calculate the thread depth \(5H/8\) for 30° and 45°, and subtract the difference from the overall outer diameter of the screw. That way the inner diameter is retained and the screw can fit into metal holes.

Internal threads (threaded holes) are probably the most common use-case for 3D printing. A threaded hole is constructed by subtracting a threaded rod from a solid object. In this case, the outer diameter is critical for a metal ISO screw to fit into the hole. In this case, the inner diameter must adjust outward by the difference in depth between 30° and 45° threads, while keeping the original outer diameter fixed.

ISO hexagon shapes for nuts and screw heads

After completing the challenge of constructing a threaded rod, it's easy to put a screw head on it, or subtract the rod from a solid to make a threaded hole. So why not use ISO standards for screw heads and nuts? The website Engineer's Edge has some useful tables for the dimensions of standard metric hex nuts and ISO 4014 hex screw heads that be programmed as a lookup table. OpenSCAD's lookup() function returns an interpolated value if the lookup key isn't present, so we can get a good hex nut or screw head size for any arbitrary shaft diameter.

It turns out that the hexagon diameter, as a function of thread diameter, is the same for both screw hex heads and nuts. The heights are different, though: for a given diameter, a nut is a bit taller than a screw head. Also, both nuts and screws have beveled edges, typically 30°; or less, but I'll use 45° for easier printing (and I think it looks better too).

Here's how it turned out for an M4 screw and nut, showing a comparison between 30° and 45° thread face angles, on an ISO head and nut:

It may be hard to see, but you may notice in the second picture (using 45° threads) that the outer diameter is smaller on the screw, and the inner diameter is larger on the nut. That screw and that nut are intended to mate with a corresponding metal nut and screw, respectively, for a snug fit. Due to the diametric differences, they would fit loosely to one another.

Facet reduction

Ideally, the thread geometry should have these desired properties:

  • regular mesh facets (triangles and quadrilaterals) with no unusually large aspect ratios and facet size differences
  • minimal number of polygons necessary to reproduce the thread profile
  • thread geometry compatible with a model needing, say, a threaded hole; that is, the threads shouldn't account for the bulk of the model's facets

One thread pitch interval is equivalent to one rotation. The models shown so far have the same number of vertices along one pitch interval as around the circumference of the screw shaft. For the ISO thread, this results in many more facets than needed on the load-bearing thread surfaces, as well as along the inner shaft surface.

The problem can be mitigated somewhat by retaining the resolution of each stacked disc, but rotating each new disc by multiple resolution steps, and raising the height of each new disc by the same multiple of the original pitch steps.

For example, say we have a polygon disc made from 64 segments. Instead of rotating each successive disc by 1/64 of a circle and raising it by pitch/64 on each iteration, we can rotate each disc 4/64 (1/16) of a circle and raise it by pitch/16. All 64 vertices of each stacked disc still line up so they can be connected as usual.

And with 1/4 of the discs, we also reduced the number of facets to 1/4 of what was originally needed!

Here's a comparison between a facet-reduced sinewave thread and the original sinewave thread. The discs in both images have 64 segments, but the facet-reduced one on the left uses only 16 steps per pitch interval instead of 64:

The facet-reduced model looks more coarse, which is expected, and the polygons aren't terribly elongated. Given that this is a screw shaft with a diameter of 4 mm, the resolution is more than sufficient for 3D printing.

Let's see what happens with the ISO thread, same parameters as the figure above with only the thread profile changed:

Here, the angular transitions in the thread profile are messier, but at this scale it's still adequate for 3D printing.

Limitations of this approach

Making threads by stacking 2-dimensional polygons is an interesting intellectual exercise, as I stated in the beginning. However, this approach has some drawbacks:

Still too many facets

While I managed to create a thread that eliminates the serrated edges of a twisted extrusion, and I reduced the facet count, there are still more facets than needed to describe the shape. Less steps along the pitch interval compared to the circumference did reduce the facet count, but even so, there are still redundant facets on the ISO thread faces. In the previous figure, those surfaces that appear as quadrilaterals on the thread faces are actually made from multiple coplanar facets due to the spacing interval being smaller than the size of the thread face. The spacing interval is limited in size by the smallest feature, in this case the outer edge of the thread. And even at this limit, the slope transitions in the thread profile get messy-looking.

Except for a sinewave thread that continually changes slope, for most threads with flat faces, we don't need so many samples along the pitch of the thread. An ISO thread has a trapezoid profile; it needs only four numbers to define it, but we have more samples than needed because of the equal spacing of the pitch steps, which can be no bigger than the smallest segment of the thread profile. Equal spacing is required because one pitch interval equals one rotation and each pitch step corresponds to a rotation step, and the rotation steps must all be equal size.

Zero degree thread faces aren't possible

Typically a screw thread experiences a load on one face, and none on the other face. A thread profile optimized for 3D printing (not needing ISO features) would have a 0° angle on the load-bearing face and a 45°; (or greater) angle on the the unloaded face. That would be the strongest thread for 3D printing.

A zero-degree face, however, would require a thread profile with a discontinuity in it. The best one could do is approximate it (say 0.1°), but then the profile would need to be tightly sampled to resolve that rapid change from maximum to minimum thread radius. And tight samples are unnecessary everywhere else except for that narrow transition. Resolving that narrow transition means having far too many unnecessary facets elsewhere.

Best appearance has too many stretched facets

The threads look best when using the same number of steps for circumference and pitch. But then the facets have a high aspect ratio because the thread circumference is much larger than the thread pitch. The facets appear more uniform if you make the pitch size approximate the circumference, but then you don't have functional threads, you just have an interesting undulating shape.


I enjoyed this exercise. In the end I have some OpenSCAD code that I can use to generate a screw or nut or threaded hole in a part, easily and quickly, even though the geometry isn't optimal.

The thread generation algorithm described here, with OpenSCAD source code, is available for download in my Nuts&bolt baby dexterity toy on Thingiverse.

For complex models with multiple threaded elements, however, I lean toward the alternate approach of extruding a thread profile along a spiral path, which makes more efficient use of polygons and allows for arbitrary thread profiles that include horizontal thread faces, or even concave faces. There's a helpful article "Generating Nice Threads in OpenSCAD about that subject, including a GitHub repository for PET bottle threads.


Popular posts from this blog

Syncing Office 365 Outlook to Google calendar using Power Automate

Whose hands are biggest? You may be surprised.