Close

Generating Nice Threads In OpenSCAD

helgehelge wrote 05/26/2018 at 23:52 • 10 min read • Like

[Hello future reader. This article refers to OpenSCAD 2018.05.05, please check if it still applies later on]

3D printing internal and external threads is interesting for many reasons.

Unlike gears that invariably wear rapidly and cannot measure up compared to cast or subtractively machined parts, they are pretty functional and allow the common maker to interface with custom and standardized bolts, pipes, valves, soda bottles and canisters - and that's on top of the liberty to create your own threaded mating components within the confines of your design.

The generalized approach presented here supports arbitrary thread geometries and numbers of starts.

Shown below are single start inside and outside threads as well as a swivel nut with a "faster" 3-start thread, all modelled to match commercial water bottles - the small one is "PCO-1881" known from 1.5L soda bottles, the larger one is "48-41" for 3-5L jugs:

Only right-hand threads are supported but left-handed threads are easy to get by applying mirror([0,1,0]) to the output. That's the OpenSCAD way :)

While the core idea of a straight thread - the helical profile wrapped onto a cylinder - is rather straight forward, generating nice geometry that does not disintegrate along the way from model rendering thru .stl export to the slicer tool deserves a little bit of attention.

What do I mean by "nice geometry"?

in short, what we don't want is unnecessary structure, gaps, holes and mismatch of resolution of the threads and the rest of the geometry.

What I'm going to walk you through here is a way to create helical thread forms just like the ones you'd strip when overtightening a bolt:

http://www.tribology-abc.com/calculators/e3_6f.htm

Since OpenSCAD is am open source work in progress, users do not necessarily work with a version that has the latest features, documentation may not be up to date and forum posts claiming "it cannot be done" are repeatedly being invalidated by software revisions, adding to the confusion.


Let's go over what doesn't work, then look at my proposed approach. If you just want the answers, please scroll down :)

linear_extrude()

As an example: without an operator that creates dedicated helical structures people are using linear_extrude() with twist to get something helical:

The problem here is that like fanning a deck of cards you'll end up with tucked-in faces and serrated outsides - as seen on Thingiverse:

And here's another one with a bit of discussion from 2014:

https://www.thingiverse.com/thing:368073/#comments

rotate_extrude()

Unfortunately as of now rotate_extrude() which should be closer to creating helical objects has learned partial rotations (not supported in 2015 builds for Windows) but cannot create helical extrusions. There's also no special treatment for the ends.

One could create a 180° arc and mirror it but as soon as these arcs are tilted to follow the helical pattern, the end faces where two arcs butt to form a full turn rotate in opposite directions. You can partition the arcs to your heart's content, this problem will never vanish:

hull()

The obvious way to get the end face of an arc segment and the starting face of the following segment to stay aligned is to start with them by definition and then wrap the space in between with hulls. I gave up at the point where some geometry seemed to be "simplified" into a brick. This behavior may change with upcoming revisions but there are better methods to begin with.

Ok, then how to?

Prerequisites:

Now we're basically down to creating geometry from a list of vertices and triangles, having full control over how the surface mesh turns out. Luckily there are two sets of libraries to help us out.

Starting with a recent development snapshot for your platform of choice:

OpenSCAD http://www.openscad.org/downloads.html

and additionally

scad-utils https://github.com/openscad/scad-utils

list-comprehension https://github.com/openscad/list-comprehension-demos

under Windows, the libraries have to be cloned/copied into your <user>\Documents\OpenSCAD\libraries  folder.

As of this writing there's also a pending modification to skin.scad to make the fancy toroidal geometry part of the example work, see

https://github.com/openscad/list-comprehension-demos/issues/8

but this is disconnected from the initial effort of creating nice threads.

Download:

For now I put up thread_profile.scad on github. If you've created new thread profiles I'd be happy to add them to the library. Just drop me a message.

https://github.com/MisterHW/IoP-satellite

The Approach:

With that out of the way, what is being shown is the generation of a helically arranged 1-dimensional array of vertical polygon() sections wrapped in a skin() made of triangles, the latter being an extension to polyhedron(). This thread helix can then be merged with the rest of the geometry via the boolean union() operator.

A radial function is introduced to create tapered ends, the Higbee cut. This is done to let the the threads start / end with a fully formed profile, avoiding galling.

Adding a cylinder and lead-in / lead-out taper this then becomes:


How to create new thread profiles:

First you'll need exact thread dimensions, e.g. obtained with a caliper or by gluing a sacraficial neck and cap piece together, cutting it in half and sticking the sanded cross section in a flatbed scanner (along with horizontal and vertical reference objects for scale) or even an official dimenional drawing like this one:

http://imajeenyus.com/mechanical/20120508_bottle_top_threads/28mm-ISBT-PCO-1881-Finish-3784253-17.pdf

If for a neck finish the cap profile is missing, its complementary profile has to be inferred, including some proper clearances. There's also a bit of leeway for shaping the non-load-bearing side of the thread profiles.

Let's go over the code.

As per https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/The_OpenSCAD_Language#Special_variables_2 which don't seem to work across files it seems function declarations are generalized way to export constants and lists from libraries, so I'm using the function construct to create parameter sets for individual thread profiles to be fed to the generating module.

// PCO-1881 soda bottle neck thread
function pco1881_neck_bore() = 21.74;
function pco1881_neck_thread_dia()     = 24.94;
function pco1881_neck_thread_pitch()   = 2.7;
function pco1881_neck_thread_profile() = [
    [0,0],
    [0,1.42],
    [1.15,1.22],
    [1.15,0.22] ];

Note the thread profile is defined so that x == 0 is at the minor (major) diameter of the outside (inside) thread because for additive thread forms that's the outside (inside) cylindrical reference surface.

https://www.fastenal.com/content/feds/pdf/Article%20-%20Screw%20Threads%20Design.pdf

The Higbee cut is an additional operation that removes partially formed thread which would otherwise create a ramp of knife edge that could cut into the mating thread at an angle, destroying both sides. It also defines a nice ramp that makes the pieces click as you rotate a loose lid that "falls back down" one turn.

The lead-in and lead-out tapering function is more of an added bonus than a necessity though.

// radial scaling function for tapered lead-in and lead-out
function lilo_taper(x,N,tapered_fraction) =     min( min( 1, (1.0/tapered_fraction)*(x/N) ), (1/tapered_fraction)*(1-x/N) )
;

All that's left to do now is to put it all together using transform() which applies the (non-commutative) product of transformation matrices to the vertical thread section polygon passed as section_profile. A for loop is used within square brackets to generate an array of slices along a helical arc.

[ for (i=[0:steps]) <...> ]

Note that the original polygon specified with 2D cooridnates is implicitly converted to 3D coordinates by applying the matrix transform. The only not-so-clean thing here is to taper the thread profile to 1% of its nominal height instead of 0% to avoid separate handling (irregular geometry at the ends or zero-area faces, both of which are annoying / bad).

// helical thread with higbee cut at start and end
// to be attached to a cylindrical surface with matching $fn
module straight_thread(section_profile, pitch = 4, turns = 3, r=10, higbee_arc=45, fn=120)
{
    $fn = fn;
    steps = turns*$fn;
    thing =  [ for (i=[0:steps])
        transform(
            rotation([0, 0, 360*i/$fn - 90])*
            translation([0, r, pitch*i/$fn])*
            rotation([90,0,0])*
            rotation([0,90,0])*
            scaling([0.01+0.99*
            lilo_taper(i/turns,steps/turns,(higbee_arc/360)/turns),1,1]),
            section_profile
            )
        ];
    skin(thing);
}

Finally adjacent sections have to be linked with shells of triangles. As the end product is topologically the same as a block or sphere, the ends have to be closed (it's not a toroid) which is also handled by skin(). The latter just adds start and end faces and feeds the whole thing to polyhedron().

The axes of rotation and the order in which they are applied has been determined so that the section is tilted upright and positioned in a way that matches how cylinder() creates faces which becomes obvious at small facet counts $fn

Finally the outsides generated by sections having [0,a],[0,b] vertical lines are not quadrilaterals but pairs of triangles which *should* be co-planar to the faces of cylindrical walls. Depending on the number of facets and overall make-up of the geometry this seems to be a hit-and-miss thing with CGAL. One way to avoid the butt joint problem and infinitesimal gaps is to let the parts interfere. My preferred way of doing that is to make the minor(major) diameter of the shaft (bore) a tad larger (smaller) to interfere with the thread form.

Usage:
union(){
straight_thread(
    section_profile = pco1881_nut_thread_profile(),
    higbee_arc = 20,
    r     = pco1881_nut_thread_dia()/2,
    turns = nut_turns,
    pitch = pco1881_nut_thread_pitch(),
    fn    = $fn
); 
// ... the rest of your geometry here
}

and that's all folks.

Other approaches:

turns out "tornillo" is the spanish word for screw/bolt. If you're looking for ISO metric threads (threadAngle = 30) or ACME (threadAngle = 29), this might be for you:

https://github.com/KaliNuska/Curso_2015-2017/blob/master/TIC/Imagen%20Vectorial.md

Part of the purpose to my effort to create nice threads in a generalized way is to become more familiar with OpenSCAD and the ways it wants to be used. If you've done something similar I apologize for not giving credit where due. Please drop a few lines in the comments.

Like

Discussions