224 lines
9.9 KiB
OpenSCAD
224 lines
9.9 KiB
OpenSCAD
//
|
|
// NopSCADlib Copyright Chris Palmer 2019
|
|
// nop.head@gmail.com
|
|
// hydraraptor.blogspot.com
|
|
//
|
|
// This file is part of NopSCADlib.
|
|
//
|
|
// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
|
|
// GNU General Public License as published by the Free Software Foundation, either version 3 of
|
|
// the License, or (at your option) any later version.
|
|
//
|
|
// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
|
// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
// See the GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License along with NopSCADlib.
|
|
// If not, see <https://www.gnu.org/licenses/>.
|
|
//
|
|
|
|
//
|
|
//! Utilities for making threads with sweep. They can be used to model screws, nuts, studding, leadscrews, etc, and also to make printed threads.
|
|
//!
|
|
//! The ends can be tapered, flat or chamfered by setting the ```top``` and ```bot``` parameters to -1 for tapered, 0 for a flat cut and positive to
|
|
//! specify a chamfer angle.
|
|
//!
|
|
//! Threads are by default solid, so the male version is wrapped around a cylinder and the female inside a tube. This can be suppressed to just get the helix, for
|
|
//! example to make a printed pot with a screw top lid.
|
|
//!
|
|
//! A left hand thread can be made by using mirror([0,1]).
|
|
//!
|
|
//! Threads with a typical 60 degree angle appear too bright with OpenSCAD's primitive lighting model as they face towards the lights more than the top and sides of
|
|
//! a cylinder. To get around this a colour can be passed to thread that is used to colour the cylinder and then toned down to colour the helix.
|
|
//!
|
|
//! Making the ends requires a CGAL intersection, which make threads relatively slow. For this reason they are generally disabled when using the GUI but can
|
|
//! be enabled by setting ```$show_threads``` to ```true```. When the tests are run, by default, threads are enabled only for things that feature them like screws.
|
|
//! This behaviour can be changed by setting a ```NOPSCADLIB_SHOW_THREADS``` environment variable to ```false``` to disable all threads and ```true``` to enable all threads.
|
|
//! The same variable also affects the generation of assembly diagrams.
|
|
//!
|
|
//! Threads obey the $fn, $fa, $fs variables.
|
|
//
|
|
include <../utils/core/core.scad>
|
|
use <sweep.scad>
|
|
use <maths.scad>
|
|
use <tube.scad>
|
|
|
|
thread_colour_factor = 0.8; // 60 degree threads appear too bright due to the angle facing the light sources
|
|
|
|
function thread_profile(h, crest, angle, overlap = 0.1) = //! Create thread profile path
|
|
let(base = crest + 2 * (h + overlap) * tan(angle / 2))
|
|
[[-base / 2, -overlap, 0], [-crest / 2, h, 0], [crest / 2, h, 0], [base / 2, -overlap, 0]];
|
|
|
|
module thread(dia, pitch, length, profile, center = true, top = -1, bot = -1, starts = 1, solid = true, female = false, colour = undef) { //! Create male or female thread, ends can be tapered, chamfered or square
|
|
//
|
|
// Apply colour if defined
|
|
//
|
|
module colour(factor) if(is_undef(colour)) children(); else color(colour * factor) children();
|
|
//
|
|
// Compress the profile to compensate for it being tilted by the helix angle
|
|
//
|
|
scale = cos(atan(pitch / (PI * dia)));
|
|
sprofile = [for(p = profile) [p.x * scale, p.y, p.z]];
|
|
//
|
|
// Extract some properties from the profile, perhaps they should be stored in it.
|
|
//
|
|
h = max([for(p = sprofile) p.y]);
|
|
xs = [for(p = sprofile) p.x];
|
|
maxx = max(xs);
|
|
minx = min(xs);
|
|
crest_xs = [for(p = sprofile) if(p.y == h) p.x];
|
|
crest_xmax = max(crest_xs);
|
|
crest_xmin = min(crest_xs);
|
|
//
|
|
// If the ends don't taper we need an extra half turn past the ends to be cropped horizontally.
|
|
//
|
|
extra_top = top < 0 ? 0 : -minx / pitch;
|
|
extra_bot = bot < 0 ? 0 : maxx / pitch;
|
|
turns = length / pitch + extra_top + extra_bot;
|
|
//
|
|
// Generate the helix path, possibly with tapered ends
|
|
//
|
|
dir = female ? 1 : -1;
|
|
r = dia / 2;
|
|
sides = r2sides4n(r);
|
|
step_angle = 360 / sides;
|
|
segs = ceil(turns * sides);
|
|
leadin = min(ceil(sides / starts), floor(turns * sides / 2));
|
|
final = floor(turns * sides) - leadin;
|
|
path = [for(i = [0 : segs],
|
|
R = i < leadin && bot < 0 ? r + dir * (h - h * i / leadin)
|
|
: i > final && top < 0 ? r + dir * h * (i - final) / leadin : r,
|
|
a = i * step_angle - 360 * extra_bot)
|
|
[R * cos(a), R * sin(a), a * pitch / 360]];
|
|
//
|
|
// Generate the skin vertices
|
|
//
|
|
facets = len(profile);
|
|
twist = helical_twist_per_segment(r, pitch, sides);
|
|
//
|
|
// For female threads we need to invert the profile
|
|
//
|
|
iprofile = female ? reverse([for(p = sprofile) [p.x, -p.y, 0]]) : sprofile;
|
|
//
|
|
// If the bottom is tapered then the twist will be greater, so pre-twist the profile to get the straight bit at the correct angle
|
|
//
|
|
rprofile = bot < 0 ? transform_points(iprofile, rotate(-dir * (helical_twist_per_segment(r - h, pitch, sides) - twist) * sides / PI))
|
|
: iprofile;
|
|
points = skin_points(rprofile, path, false, twist * segs);
|
|
//
|
|
// To form the ends correctly we need to use intersection but it is very slow with the full thread so we just
|
|
// intersect the start and the end and sweep the rest outside of the intersection.
|
|
//
|
|
top_chamfer_h = (top > 0 ? h * tan(top) : 0);
|
|
bot_chamfer_h = (bot > 0 ? h * tan(bot) : 0);
|
|
top_overlap = max( maxx, top_chamfer_h - crest_xmin) / pitch;
|
|
bot_overlap = max(-minx, bot_chamfer_h + crest_xmax) / pitch;
|
|
start = ceil(sides * (bot_overlap + extra_bot));
|
|
end = segs - ceil(sides * (top_overlap + extra_top));
|
|
|
|
start_skin_faces = skin_faces(points, start + 1, facets, false);
|
|
middle_skin_faces = skin_faces(points, end - start + 1, facets, false, start);
|
|
end_skin_faces = skin_faces(points, segs - end + 1, facets, false, end);
|
|
|
|
start_faces = concat([cap(facets) ], start_skin_faces, [cap(facets, start)]);
|
|
middle_faces = concat([cap(facets, start, false)], middle_skin_faces, [cap(facets, end)]);
|
|
end_faces = concat([cap(facets, end, false)], end_skin_faces, [cap(facets, segs)]);
|
|
|
|
overlap = - profile[0].y;
|
|
translate_z((center ? -length / 2 : 0)) {
|
|
ends_faces = concat(start_faces, end_faces);
|
|
for(i = [0 : starts - 1])
|
|
colour(thread_colour_factor)
|
|
rotate(360 * i / starts + (female ? 180 / starts : 0)) {
|
|
render() intersection() {
|
|
polyhedron(points, ends_faces);
|
|
|
|
shorten = !is_undef(colour);
|
|
len = shorten ? length - 2 * eps : length;
|
|
offset = shorten ? eps : 0;
|
|
rotate_extrude()
|
|
if(female) {
|
|
difference() {
|
|
translate([0, offset])
|
|
square([r + h + overlap, len]);
|
|
|
|
if(top_chamfer_h)
|
|
polygon([[0, length], [r, length], [r - h, length - top_chamfer_h], [0, length - top_chamfer_h]]);
|
|
|
|
if(bot_chamfer_h)
|
|
polygon([[0, 0], [r, 0], [r - h, bot_chamfer_h], [0, bot_chamfer_h]]);
|
|
}
|
|
}
|
|
else
|
|
difference() {
|
|
hull() {
|
|
translate([0, offset])
|
|
square([r, len]);
|
|
|
|
translate([0, bot_chamfer_h])
|
|
square([r + h + overlap, len - top_chamfer_h - bot_chamfer_h]);
|
|
}
|
|
if(!solid)
|
|
square([r - overlap, length]);
|
|
}
|
|
}
|
|
|
|
polyhedron(points, middle_faces);
|
|
}
|
|
|
|
if(solid)
|
|
colour(1)
|
|
rotate(90)
|
|
if(female)
|
|
tube(or = r + (top < 0 || bot < 0 ? h : 0) + 2 * overlap, ir = r, h = length, center = false);
|
|
else
|
|
cylinder(d = dia, h = length);
|
|
}
|
|
}
|
|
|
|
module male_metric_thread(d, pitch, length, center = true, top = -1, bot = -1, solid = true, colour = undef) { //! Create male thread with metric profile
|
|
H = pitch * sqrt(3) / 2;
|
|
h = 5 * H / 8;
|
|
minor_d = d - 2 * h;
|
|
thread(minor_d, pitch, length, thread_profile(h, pitch / 8, 60), center, top, bot, solid = solid, colour = colour);
|
|
}
|
|
|
|
module female_metric_thread(d, pitch, length, center = true, top = -1, bot = -1, colour = undef) { //! Create female thread with metric profile
|
|
H = pitch * sqrt(3) / 2;
|
|
h = 5 * H / 8;
|
|
thread(d, pitch, length, thread_profile(h, pitch / 4, 60), center, top, bot, solid = false, female = true, colour = colour);
|
|
}
|
|
|
|
function metric_coarse_pitch(d) //! Convert metric diameter to pitch
|
|
= d == 1.6 ? 0.35 // M1.6
|
|
: [0.4, // M2
|
|
0.45,// M2.5
|
|
0.5, // M3
|
|
0.6, // M3.5
|
|
0.7, // M4
|
|
0,
|
|
0.8, // M5
|
|
0,
|
|
1.0, // M6
|
|
0,
|
|
0,
|
|
0,
|
|
1.25, // M8
|
|
0,
|
|
0,
|
|
0,
|
|
1.5, // M10
|
|
0,
|
|
0,
|
|
0,
|
|
1.75, // M12
|
|
0,
|
|
0,
|
|
0,
|
|
0, // M14
|
|
0,
|
|
0,
|
|
0,
|
|
2.0, // M16
|
|
][d * 2 - 4];
|