diff --git a/.gitignore b/.gitignore
index e4cb333..8545f0b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,7 @@ tests/bom/
*.log
*.html
bounds.json
+options.json
times.txt
*_diff.png
*.echo
diff --git a/docs/metric_threads.png b/docs/metric_threads.png
new file mode 100644
index 0000000..4d6d55f
Binary files /dev/null and b/docs/metric_threads.png differ
diff --git a/libtest.png b/libtest.png
index d840cac..b716ade 100644
Binary files a/libtest.png and b/libtest.png differ
diff --git a/readme.md b/readme.md
index 950cf27..b40f0f5 100644
--- a/readme.md
+++ b/readme.md
@@ -32,8 +32,8 @@ See [usage](docs/usage.md) for requirements, installation instructions and a usa
Fans | Rod | Handle | Rounded_polygon | |
Fuseholder | Screws | Pcb_mount | Sector | |
Geared_steppers | Sealing_strip | Psu_shroud | Sweep | |
- Green_terminals | Sheets | Ribbon_clamp | Tube | |
- Hot_ends | Spades | Screw_knob | | |
+ Green_terminals | Sheets | Ribbon_clamp | Thread | |
+ Hot_ends | Spades | Screw_knob | Tube | |
Hygrometer | Spools | Socket_box | | |
Iecs | Springs | Ssr_shroud | | |
Inserts | Ssrs | Strap_handle | | |
@@ -1152,7 +1152,9 @@ Nuts for leadscrews.
| ```leadnut_hole_dia(type)``` | The diameter of the screw holes |
| ```leadnut_hole_pitch(type)``` | The radia pitch of the screw holes |
| ```leadnut_holes(type)``` | The number of screw holes |
+| ```leadnut_lead(type)``` | Screw lead |
| ```leadnut_od(type)``` | Outer diameter of the shank |
+| ```leadnut_pitch(type)``` | Screw pitch |
| ```leadnut_screw(type)``` | The type of the fixing screws |
### Functions
@@ -2281,14 +2283,20 @@ Steel rods and studding with chamfered ends.
### Modules
| Module | Description |
|:--- |:--- |
-| ```rod(d , l)``` | Draw a smooth rod with specified length and diameter |
-| ```studding(d , l)``` | Draw a threaded rod with specified length and diameter |
+| ```leadscrew(d , l, lead, starts, center = true)``` | Draw a leadscrew with specified diameter, length, lead and number of starts |
+| ```rod(d , l, center = true)``` | Draw a smooth rod with specified diameter and length |
+| ```studding(d , l, center = true)``` | Draw a threaded rod with specified diameter and length |
![rod](tests/png/rod.png)
### Vitamins
| Qty | Module call | BOM entry |
| ---:|:--- |:---|
+| 1 | ```leadscrew(10, 80, 8, 4)``` | Leadscrew 10 x 80mm, 8mm lead, 4 starts |
+| 1 | ```leadscrew(12, 80, 12, 4)``` | Leadscrew 12 x 80mm, 12mm lead, 4 starts |
+| 1 | ```leadscrew(16, 80, 16, 4)``` | Leadscrew 16 x 80mm, 16mm lead, 4 starts |
+| 1 | ```leadscrew(6, 80, 2, 1)``` | Leadscrew 6 x 80mm, 2mm lead, 1 starts |
+| 1 | ```leadscrew(8, 80, 8, 4)``` | Leadscrew 8 x 80mm, 8mm lead, 4 starts |
| 1 | ```rod(10, 80)``` | Smooth rod 10mm x 80mm |
| 1 | ```rod(12, 80)``` | Smooth rod 12mm x 80mm |
| 1 | ```rod(16, 80)``` | Smooth rod 16mm x 80mm |
@@ -2358,17 +2366,17 @@ Machine screws and wood screws with various head styles.
| Qty | Module call | BOM entry |
| ---:|:--- |:---|
| 1 | ```screw(No632_pan_screw, 30)``` | Screw 6-32 pan x 30mm |
-| 1 | ```screw(M2_cap_screw, 25)``` | Screw M2 cap x 25mm |
-| 1 | ```screw(M2_cs_cap_screw, 25)``` | Screw M2 cs cap x 25mm |
-| 1 | ```screw(M2p5_cap_screw, 25)``` | Screw M2.5 cap x 25mm |
-| 1 | ```screw(M2p5_pan_screw, 30)``` | Screw M2.5 pan x 30mm |
-| 1 | ```screw(M3_cap_screw, 25)``` | Screw M3 cap x 25mm |
-| 1 | ```screw(M3_cs_cap_screw, 25)``` | Screw M3 cs cap x 25mm |
-| 1 | ```screw(M3_dome_screw, 25)``` | Screw M3 dome x 25mm |
+| 1 | ```screw(M2_cap_screw, 10)``` | Screw M2 cap x 10mm |
+| 1 | ```screw(M2_cs_cap_screw, 10)``` | Screw M2 cs cap x 10mm |
+| 1 | ```screw(M2p5_cap_screw, 10)``` | Screw M2.5 cap x 10mm |
+| 1 | ```screw(M2p5_pan_screw, 10)``` | Screw M2.5 pan x 10mm |
+| 1 | ```screw(M3_cap_screw, 10)``` | Screw M3 cap x 10mm |
+| 1 | ```screw(M3_cs_cap_screw, 10)``` | Screw M3 cs cap x 10mm |
+| 1 | ```screw(M3_dome_screw, 10)``` | Screw M3 dome x 10mm |
| 1 | ```screw(M3_grub_screw, 6)``` | Screw M3 grub x 6mm |
-| 1 | ```screw(M3_hex_screw, 30)``` | Screw M3 hex x 30mm |
-| 1 | ```screw(M3_low_cap_screw, 25)``` | Screw M3 low cap x 25mm |
-| 1 | ```screw(M3_pan_screw, 30)``` | Screw M3 pan x 30mm |
+| 1 | ```screw(M3_hex_screw, 10)``` | Screw M3 hex x 10mm |
+| 1 | ```screw(M3_low_cap_screw, 10)``` | Screw M3 low cap x 10mm |
+| 1 | ```screw(M3_pan_screw, 10)``` | Screw M3 pan x 10mm |
| 1 | ```screw(M4_cap_screw, 25)``` | Screw M4 cap x 25mm |
| 1 | ```screw(M4_cs_cap_screw, 25)``` | Screw M4 cs cap x 25mm |
| 1 | ```screw(M4_dome_screw, 25)``` | Screw M4 dome x 25mm |
@@ -2383,8 +2391,8 @@ Machine screws and wood screws with various head styles.
| 1 | ```screw(M6_pan_screw, 30)``` | Screw M6 pan x 30mm |
| 1 | ```screw(M8_cap_screw, 35)``` | Screw M8 cap x 35mm |
| 1 | ```screw(M8_hex_screw, 30)``` | Screw M8 hex x 30mm |
-| 1 | ```screw(No2_screw, 30)``` | Screw No2 pan wood x 30mm |
-| 1 | ```screw(No4_screw, 30)``` | Screw No4 pan wood x 30mm |
+| 1 | ```screw(No2_screw, 10)``` | Screw No2 pan wood x 10mm |
+| 1 | ```screw(No4_screw, 10)``` | Screw No4 pan wood x 10mm |
| 1 | ```screw(No6_cs_screw, 30)``` | Screw No6 cs wood x 30mm |
| 1 | ```screw(No6_screw, 30)``` | Screw No6 pan wood x 30mm |
@@ -2672,7 +2680,7 @@ NEMA stepper motor model.
| ```NEMA_length(type)``` | Body length |
| ```NEMA_radius(type)``` | End cap radius |
| ```NEMA_shaft_dia(type)``` | Shaft diameter |
-| ```NEMA_shaft_length(type)``` | Shaft length above the face |
+| ```NEMA_shaft_length(type)``` | Shaft length above the face, if a list then a leadscrew: length, lead, starts |
| ```NEMA_width(type)``` | Width of the square face |
### Functions
@@ -2684,7 +2692,7 @@ NEMA stepper motor model.
### Modules
| Module | Description |
|:--- |:--- |
-| ```NEMA(type)``` | Draw specified NEMA stepper motor |
+| ```NEMA(type, shaft_angle = 0)``` | Draw specified NEMA stepper motor |
| ```NEMA_outline(type)``` | 2D outline |
| ```NEMA_screw_positions(type, n = 4)``` | Positions children at the screw holes |
| ```NEMA_screws(type, screw, n = 4, screw_length = 8, earth = undef)``` | Place screws and optional earth tag |
@@ -4431,6 +4439,8 @@ Maths utilities for manipulating vectors and matrices.
### Functions
| Function | Description |
|:--- |:--- |
+| ```angle_between(v1, v2)``` | Return the angle between two vectors |
+| ```euler(R)``` | Convert a rotation matrix to a Euler rotation vector. |
| ```identity(n, x = 1)``` | Construct an arbitrary size identity matrix |
| ```reverse(v)``` | Reverse a vector |
| ```rotate(a, v)``` | Generate a 4x4 rotation matrix, ```a``` can be a vector of three angles or a single angle around ```z```, or around axis ```v``` |
@@ -4441,6 +4451,7 @@ Maths utilities for manipulating vectors and matrices.
| ```transpose(m)``` | Transpose an arbitrary size matrix |
| ```unit(v)``` | Convert ```v``` to a unit vector |
| ```vec3(v)``` | Return a 3 vector with the first three elements of ```v``` |
+| ```vec4(v)``` | Return a 4 vector with the first three elements of ```v``` |
![maths](tests/png/maths.png)
@@ -4607,9 +4618,12 @@ An additional twist around the path can be specified. If the path is closed this
| ```after(path1, path2)``` | Translate ```path2``` so its start meets the end of ```path1``` and then concatenate |
| ```arc_points(r, a = [90, 0, 180], al = 90)``` | Generate the points of a circular arc |
| ```before(path1, path2)``` | Translate ```path1``` so its end meets the start of ```path2``` and then concatenate |
-| ```circle_points(r = 1, z = 0)``` | Generate the points of a circle, setting z makes a single turn spiral |
+| ```cap(facets, segment = 0, end)``` | Create the mesh for an end cap |
+| ```circle_points(r = 1, z = 0, dir = -1)``` | Generate the points of a circle, setting z makes a single turn spiral |
+| ```helical_twist_per_segment(r, pitch, sides)``` | Calculate the twist around Z that rotate_from_to() introduces |
| ```path_length(path, i = 0, length = 0)``` | Calculated the length along a path |
| ```rectangle_points(w, h)``` | Generate the points of a rectangle |
+| ```skin_faces(points, npoints, facets, loop, offset = 0)``` | Create the mesh for the swept volume without end caps |
| ```sweep(path, profile, loop = false, twist = 0)``` | Generate the point list and face list of the swept volume |
### Modules
@@ -4620,6 +4634,50 @@ An additional twist around the path can be specified. If the path is closed this
![sweep](tests/png/sweep.png)
+Top
+
+---
+
+## Thread
+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.
+
+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 ```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.
+
+
+[utils/thread.scad](utils/thread.scad) Implementation.
+
+[tests/thread.scad](tests/thread.scad) Code for this example.
+
+### Functions
+| Function | Description |
+|:--- |:--- |
+| ```metric_coarse_pitch(d)``` | Convert metric diameter to pitch |
+| ```thread_profile(h, crest, angle, overlap = 0.1)``` | Create thread profile path |
+
+### Modules
+| Module | Description |
+|:--- |:--- |
+| ```female_metric_thread(d, pitch, length, center = true, top = -1, bot = -1, colour = undef)``` | Create female thread with metric profile |
+| ```male_metric_thread(d, pitch, length, center = true, top = -1, bot = -1, solid = true, colour = undef)``` | Create male thread with metric profile |
+| ```thread(dia, pitch, length, profile, center = true, top = -1, bot = -1, starts = 1, solid = true, female = false, colour = undef)``` | Create male or femail thread, ends can be tapered, chamfered or square |
+
+![thread](tests/png/thread.png)
+
+
Top
---
diff --git a/scripts/openscad.py b/scripts/openscad.py
index 86ac8eb..7ab2333 100644
--- a/scripts/openscad.py
+++ b/scripts/openscad.py
@@ -24,7 +24,7 @@ from __future__ import print_function
import subprocess, sys
-def _run(args, silent):
+def run_list(args, silent = False):
cmd = ["openscad"] + args
if not silent:
for arg in cmd:
@@ -39,7 +39,7 @@ def _run(args, silent):
sys.exit(rc)
def run(*args):
- _run(list(args), False)
+ run_list(list(args), False)
def run_silent(*args):
- _run(list(args), True);
+ run_list(list(args), True);
diff --git a/scripts/options.py b/scripts/options.py
new file mode 100644
index 0000000..4d2bbee
--- /dev/null
+++ b/scripts/options.py
@@ -0,0 +1,49 @@
+#
+# NopSCADlib Copyright Chris Palmer 2020
+# 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 .
+#
+
+# Set command line options from enviroment variables and check if they have changed
+
+import json, os, deps
+
+def check_options(dir = '.'):
+ global options, options_mtime
+ options = { "show_threads": str(os.getenv("SHOW_THREADS")) }
+ options_fname = dir + '/options.json'
+ try:
+ with open(options_fname) as json_file:
+ last_options = json.load(json_file)
+ except:
+ last_options = {}
+ if last_options != options:
+ with open(options_fname, 'w') as outfile:
+ json.dump(options, outfile, indent = 4)
+ options_mtime = deps.mtime(options_fname)
+
+def have_changed(changed, target):
+ if not changed and deps.mtime(target) < options_mtime:
+ return "command line options changed"
+ return changed
+
+def list():
+ result = []
+ for name in options.keys():
+ value = options[name]
+ if value != 'None':
+ result.append('-D$' + name + '=' + value)
+ return result
diff --git a/scripts/tests.py b/scripts/tests.py
index d8f3a8a..4bd45b0 100755
--- a/scripts/tests.py
+++ b/scripts/tests.py
@@ -27,6 +27,7 @@ import openscad
import subprocess
import bom
import times
+import options
import time
import json
import shutil
@@ -96,6 +97,7 @@ def tests(tests):
index = {}
bodies = {}
times.read_times()
+ options.check_options(deps_dir)
#
# Make cover pic if does not exist as very slow. Delete it to force an update.
#
@@ -190,11 +192,12 @@ def tests(tests):
oldest = png_name if mtime(png_name) < mtime(bom_name) else bom_name
changed = check_deps(oldest, dname)
changed = times.check_have_time(changed, scad_name)
+ changed = options.have_changed(changed, oldest)
if changed:
print(changed)
t = time.time()
tmp_name = 'tmp.png'
- openscad.run("-D", "$bom=2", colour_scheme, "--projection=p", "--imgsize=%d,%d" % (w, h), "--camera=0,0,0,70,0,315,500", "--autocenter", "--viewall", "-d", dname, "-o", tmp_name, scad_name);
+ openscad.run_list(options.list() + ["-D$bom=2", colour_scheme, "--projection=p", "--imgsize=%d,%d" % (w, h), "--camera=0,0,0,70,0,315,500", "--autocenter", "--viewall", "-d", dname, "-o", tmp_name, scad_name]);
times.add_time(scad_name, t)
do_cmd(["magick", tmp_name, "-trim", "-resize", "1000x600", "-bordercolor", background, "-border", "10", tmp_name])
update_image(tmp_name, png_name)
diff --git a/scripts/views.py b/scripts/views.py
index ec0f1aa..8c456ab 100755
--- a/scripts/views.py
+++ b/scripts/views.py
@@ -28,6 +28,7 @@ import openscad
from tests import do_cmd, update_image, colour_scheme, background
import time
import times
+import options
from deps import *
import os
import json
@@ -102,6 +103,7 @@ def views(target, do_assemblies = None):
os.makedirs(deps_dir)
times.read_times(target_dir)
+ options.check_options(deps_dir)
bounds_fname = top_dir + 'stls/bounds.json'
with open(bounds_fname) as json_file:
bounds_map = json.load(json_file)
@@ -163,11 +165,12 @@ def views(target, do_assemblies = None):
png_name = png_name.replace('_assembly', '_assembled')
changed = check_deps(png_name, dname)
changed = times.check_have_time(changed, png_name)
+ changed = options.have_changed(changed, png_name)
tmp_name = 'tmp.png'
if changed:
print(changed)
t = time.time()
- openscad.run("-D$show_threads=1", "-D$pose=1", "-D$explode=%d" % explode, colour_scheme, "--projection=p", "--imgsize=4096,4096", "--autocenter", "--viewall", "-d", dname, "-o", tmp_name, png_maker_name);
+ openscad.run_list(options.list() + ["-D$pose=1", "-D$explode=%d" % explode, colour_scheme, "--projection=p", "--imgsize=4096,4096", "--autocenter", "--viewall", "-d", dname, "-o", tmp_name, png_maker_name]);
times.add_time(png_name, t)
do_cmd(["magick", tmp_name, "-trim", "-resize", "1004x1004", "-bordercolor", background, "-border", "10", tmp_name])
update_image(tmp_name, png_name)
diff --git a/tests/inserts.scad b/tests/inserts.scad
index d2fa113..0813c90 100644
--- a/tests/inserts.scad
+++ b/tests/inserts.scad
@@ -37,4 +37,5 @@ module inserts() {
}
if($preview)
- inserts();
+ let($show_threads = true)
+ inserts();
diff --git a/tests/leadnuts.scad b/tests/leadnuts.scad
index 1b5251d..d337bf0 100644
--- a/tests/leadnuts.scad
+++ b/tests/leadnuts.scad
@@ -27,4 +27,5 @@ module leadnuts()
leadnut(leadnuts[$i]);
if($preview)
- leadnuts();
+ let($show_threads = true)
+ leadnuts();
diff --git a/tests/maths.scad b/tests/maths.scad
index 0f36dda..4d732fc 100644
--- a/tests/maths.scad
+++ b/tests/maths.scad
@@ -56,7 +56,7 @@ module maths() {
//
z = [0, 0, 1];
v = cross(u, z);
- a = acos(u * z);
+ a = angle_between(u, z);
l = 20;
@@ -64,6 +64,11 @@ module maths() {
translate_z(l)
vflip()
arrow(l);
+
+ //
+ // Test Euler
+ //
+ assert(euler(rotate(r)) == r, "euler() failed");
}
rotate(45)
diff --git a/tests/nuts.scad b/tests/nuts.scad
index 4004702..2498bf5 100644
--- a/tests/nuts.scad
+++ b/tests/nuts.scad
@@ -48,4 +48,5 @@ module nuts() {
}
if($preview)
- nuts();
+ let($show_threads = true)
+ nuts();
diff --git a/tests/opengrab.scad b/tests/opengrab.scad
index 146e023..0fd59dc 100644
--- a/tests/opengrab.scad
+++ b/tests/opengrab.scad
@@ -29,4 +29,5 @@ module opengrab_test() {
}
if($preview)
- opengrab_test();
+ let($show_threads = true)
+ opengrab_test();
diff --git a/tests/png/belts.png b/tests/png/belts.png
index bf0c199..c814959 100644
Binary files a/tests/png/belts.png and b/tests/png/belts.png differ
diff --git a/tests/png/blowers.png b/tests/png/blowers.png
index 2a21bb0..1dce171 100644
Binary files a/tests/png/blowers.png and b/tests/png/blowers.png differ
diff --git a/tests/png/bom.png b/tests/png/bom.png
index d60fa5f..5dc51af 100644
Binary files a/tests/png/bom.png and b/tests/png/bom.png differ
diff --git a/tests/png/box.png b/tests/png/box.png
index 0261546..87dfccc 100644
Binary files a/tests/png/box.png and b/tests/png/box.png differ
diff --git a/tests/png/butt_box.png b/tests/png/butt_box.png
index a667dff..81d369b 100644
Binary files a/tests/png/butt_box.png and b/tests/png/butt_box.png differ
diff --git a/tests/png/components.png b/tests/png/components.png
index ab632f7..f604cd5 100644
Binary files a/tests/png/components.png and b/tests/png/components.png differ
diff --git a/tests/png/corner_block.png b/tests/png/corner_block.png
index 7a2ac60..3597b2d 100644
Binary files a/tests/png/corner_block.png and b/tests/png/corner_block.png differ
diff --git a/tests/png/d_connectors.png b/tests/png/d_connectors.png
index 9f9a617..b2d6ba2 100644
Binary files a/tests/png/d_connectors.png and b/tests/png/d_connectors.png differ
diff --git a/tests/png/displays.png b/tests/png/displays.png
index 66627f8..17ee39b 100644
Binary files a/tests/png/displays.png and b/tests/png/displays.png differ
diff --git a/tests/png/door_hinge.png b/tests/png/door_hinge.png
index a5a5167..b22ceb7 100644
Binary files a/tests/png/door_hinge.png and b/tests/png/door_hinge.png differ
diff --git a/tests/png/fan_guard.png b/tests/png/fan_guard.png
index f618137..843ef3f 100644
Binary files a/tests/png/fan_guard.png and b/tests/png/fan_guard.png differ
diff --git a/tests/png/fans.png b/tests/png/fans.png
index 8558630..f684b66 100644
Binary files a/tests/png/fans.png and b/tests/png/fans.png differ
diff --git a/tests/png/fixing_block.png b/tests/png/fixing_block.png
index 75b88ae..8f18708 100644
Binary files a/tests/png/fixing_block.png and b/tests/png/fixing_block.png differ
diff --git a/tests/png/flat_hinge.png b/tests/png/flat_hinge.png
index fa0cdac..b76b3a8 100644
Binary files a/tests/png/flat_hinge.png and b/tests/png/flat_hinge.png differ
diff --git a/tests/png/foot.png b/tests/png/foot.png
index 4d64ba7..6921db4 100644
Binary files a/tests/png/foot.png and b/tests/png/foot.png differ
diff --git a/tests/png/handle.png b/tests/png/handle.png
index 2e7005a..966579c 100644
Binary files a/tests/png/handle.png and b/tests/png/handle.png differ
diff --git a/tests/png/iecs.png b/tests/png/iecs.png
index 7484329..7ac6c35 100644
Binary files a/tests/png/iecs.png and b/tests/png/iecs.png differ
diff --git a/tests/png/inserts.png b/tests/png/inserts.png
index aaa52b1..1994cbc 100644
Binary files a/tests/png/inserts.png and b/tests/png/inserts.png differ
diff --git a/tests/png/leadnuts.png b/tests/png/leadnuts.png
index 9e1f58d..d7273da 100644
Binary files a/tests/png/leadnuts.png and b/tests/png/leadnuts.png differ
diff --git a/tests/png/modules.png b/tests/png/modules.png
index de5b326..e138538 100644
Binary files a/tests/png/modules.png and b/tests/png/modules.png differ
diff --git a/tests/png/nuts.png b/tests/png/nuts.png
index 622ba4e..e6881dc 100644
Binary files a/tests/png/nuts.png and b/tests/png/nuts.png differ
diff --git a/tests/png/opengrab.png b/tests/png/opengrab.png
index 6b648f3..44cb89b 100644
Binary files a/tests/png/opengrab.png and b/tests/png/opengrab.png differ
diff --git a/tests/png/pcb_mount.png b/tests/png/pcb_mount.png
index a4f662b..dbf2ece 100644
Binary files a/tests/png/pcb_mount.png and b/tests/png/pcb_mount.png differ
diff --git a/tests/png/pcbs.png b/tests/png/pcbs.png
index b5e688c..09e406e 100644
Binary files a/tests/png/pcbs.png and b/tests/png/pcbs.png differ
diff --git a/tests/png/pillars.png b/tests/png/pillars.png
index 055f34f..ed14e6d 100644
Binary files a/tests/png/pillars.png and b/tests/png/pillars.png differ
diff --git a/tests/png/psu_shroud.png b/tests/png/psu_shroud.png
index 1531207..4da8e30 100644
Binary files a/tests/png/psu_shroud.png and b/tests/png/psu_shroud.png differ
diff --git a/tests/png/pulleys.png b/tests/png/pulleys.png
index eabe5ac..c87d1cd 100644
Binary files a/tests/png/pulleys.png and b/tests/png/pulleys.png differ
diff --git a/tests/png/rails.png b/tests/png/rails.png
index 717c724..926a562 100644
Binary files a/tests/png/rails.png and b/tests/png/rails.png differ
diff --git a/tests/png/ribbon_clamp.png b/tests/png/ribbon_clamp.png
index 7f2d18d..c37511b 100644
Binary files a/tests/png/ribbon_clamp.png and b/tests/png/ribbon_clamp.png differ
diff --git a/tests/png/ring_terminals.png b/tests/png/ring_terminals.png
index 0b1984f..9414eba 100644
Binary files a/tests/png/ring_terminals.png and b/tests/png/ring_terminals.png differ
diff --git a/tests/png/rod.png b/tests/png/rod.png
index bd416ed..43f898f 100644
Binary files a/tests/png/rod.png and b/tests/png/rod.png differ
diff --git a/tests/png/screw_knob.png b/tests/png/screw_knob.png
index 6628c22..a2921b0 100644
Binary files a/tests/png/screw_knob.png and b/tests/png/screw_knob.png differ
diff --git a/tests/png/screws.png b/tests/png/screws.png
index 1eac829..3e728a5 100644
Binary files a/tests/png/screws.png and b/tests/png/screws.png differ
diff --git a/tests/png/socket_box.png b/tests/png/socket_box.png
index 91ba658..74834c4 100644
Binary files a/tests/png/socket_box.png and b/tests/png/socket_box.png differ
diff --git a/tests/png/spools.png b/tests/png/spools.png
index 29f5ed9..9d9847f 100644
Binary files a/tests/png/spools.png and b/tests/png/spools.png differ
diff --git a/tests/png/ssr_shroud.png b/tests/png/ssr_shroud.png
index f8ed7a0..2ef78e1 100644
Binary files a/tests/png/ssr_shroud.png and b/tests/png/ssr_shroud.png differ
diff --git a/tests/png/ssrs.png b/tests/png/ssrs.png
index 13cc01c..a82a771 100644
Binary files a/tests/png/ssrs.png and b/tests/png/ssrs.png differ
diff --git a/tests/png/stepper_motors.png b/tests/png/stepper_motors.png
index 2a166e3..3e2a165 100644
Binary files a/tests/png/stepper_motors.png and b/tests/png/stepper_motors.png differ
diff --git a/tests/png/thread.png b/tests/png/thread.png
new file mode 100644
index 0000000..a9e3068
Binary files /dev/null and b/tests/png/thread.png differ
diff --git a/tests/png/toggles.png b/tests/png/toggles.png
index b27bf82..3805947 100644
Binary files a/tests/png/toggles.png and b/tests/png/toggles.png differ
diff --git a/tests/png/veroboard.png b/tests/png/veroboard.png
index e681872..569b09d 100644
Binary files a/tests/png/veroboard.png and b/tests/png/veroboard.png differ
diff --git a/tests/png/washers.png b/tests/png/washers.png
index 1d32d48..b1977ff 100644
Binary files a/tests/png/washers.png and b/tests/png/washers.png differ
diff --git a/tests/rod.scad b/tests/rod.scad
index 3bc9734..97f4693 100644
--- a/tests/rod.scad
+++ b/tests/rod.scad
@@ -24,12 +24,21 @@ include <../vitamins/linear_bearings.scad>
use <../vitamins/rod.scad>
module rods()
- layout([for(b = linear_bearings) 2 * bearing_radius(b)]) {
+ layout([for(b = linear_bearings) 2 * bearing_radius(b)]) let(d = bearing_rod_dia(linear_bearings[$i])){
- rod(bearing_rod_dia(linear_bearings[$i]), 80);
+ rod(d, 80);
translate([0, 30])
- studding(bearing_rod_dia(linear_bearings[$i]), 80);
+ studding(d, 80);
+
+ if(d >= 6)
+ translate([0, 60]) {
+ starts = d > 6 ? 4 : 1;
+ pitch = d > 14 ? 4
+ : d > 10 ? 3 : 2;
+ let($show_threads = true)
+ leadscrew(d, 80, starts * pitch, starts);
+ }
}
if($preview)
diff --git a/tests/screws.scad b/tests/screws.scad
index 744e8fb..d826c83 100644
--- a/tests/screws.scad
+++ b/tests/screws.scad
@@ -25,9 +25,10 @@ for(y = [0 : len(screw_lists) -1])
for(x = [0 : len(screw_lists[y]) -1]) {
screw = screw_lists[y][x];
if(screw) {
- length = screw_max_thread(screw)
- ? screw_longer_than(screw_max_thread(screw) + 5)
- : screw_head_type(screw) == hs_grub ? 6 : 30;
+ length = screw_head_type(screw) == hs_grub ? 6
+ : screw_radius(screw) <= 1.5 ? 10
+ : screw_max_thread(screw) ? screw_longer_than(screw_max_thread(screw) + 5)
+ : 30;
translate([x * 20, y * 20])
screw(screw, length);
}
diff --git a/tests/sweep.scad b/tests/sweep.scad
index 900199f..6c36ec7 100644
--- a/tests/sweep.scad
+++ b/tests/sweep.scad
@@ -44,7 +44,7 @@ knot = [ for(i=[0:.2:359])
(19*cos(3*i) + 40)*sin(2*i),
19*sin(3*i) ] ];
-sweep(knot, L_points, loop = true, twist = 0);
+sweep(knot, L_points, loop = true);
p = transform_points([[0,0,0], [20,0,5], [10,30,4], [0,0,0], [0,0,20]], scale(10));
n = 100;
diff --git a/tests/thread.scad b/tests/thread.scad
new file mode 100644
index 0000000..97d4f70
--- /dev/null
+++ b/tests/thread.scad
@@ -0,0 +1,52 @@
+//
+// NopSCADlib Copyright Chris Palmer 2020
+// 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 .
+//
+include <../core.scad>
+use <../utils/thread.scad>
+
+pitch = 2;
+starts = 4;
+
+profile = thread_profile(pitch / 2, pitch * 0.366, 30);
+
+module threads()
+ for(female = [false, true]) translate([0, female ? -20 : 0]) {
+ length = female ? 8 : 40;
+ dia = female ? 8 : 8 - pitch;
+ colour = female ? brass : silver;
+
+ thread(dia, starts * pitch, length, profile, starts = starts, top = 45, bot = 45, female = female, colour = colour);
+
+ color(colour)
+ translate([20, 0])
+ thread(dia, starts * pitch, length, profile, starts = starts, top = 0, bot = 0, female = female);
+
+ translate([40, 0])
+ thread(dia, starts * pitch, length, profile, starts = starts, top = -1, bot = -1, female = female, colour = colour);
+
+ color(colour)
+ translate([60, 0])
+ thread(dia, 2 * pitch, length, profile, starts = 2, top = -1, bot = -1, female = female);
+
+ color(colour)
+ translate([80, 0])
+ thread(dia, pitch, length, profile, starts = 1, top = -1, bot = -1, female = female);
+}
+
+let($show_threads = true)
+ threads();
diff --git a/tests/toggles.scad b/tests/toggles.scad
index f87199d..3bc7cdb 100644
--- a/tests/toggles.scad
+++ b/tests/toggles.scad
@@ -27,4 +27,5 @@ module toggles()
toggle(toggles[$i], 3);
if($preview)
- toggles();
+ let($show_threads = true)
+ toggles();
diff --git a/utils/maths.scad b/utils/maths.scad
index 8cf8000..176cda2 100644
--- a/utils/maths.scad
+++ b/utils/maths.scad
@@ -39,9 +39,9 @@ function rotate(a, v) = //! Generate a 4x4 rotation matrix, ```a``` can be a vec
sy = sin(av[1]),
sz = sin(av[2]))
[
- [ cy * cz, cz * sx * sy - cx * sz, cx * cz * sy + sx * sz, 0],
- [ cy * sz, cx * cz + sx * sy * sz,-cz * sx + cx * sy * sz, 0],
- [-sy, cy * sx, cx * cy, 0],
+ [ cy * cz, sx * sy * cz - cx * sz, cx * sy * cz + sx * sz, 0],
+ [ cy * sz, sx * sy * sz + cx * cz, cx * sy * sz - sx * cz, 0],
+ [-sy, sx * cy, cx * cy, 0],
[ 0, 0, 0, 1]
]
: let(s = sin(a),
@@ -65,6 +65,7 @@ function scale(v) = let(s = is_list(v) ? v : [v, v, v]) //! Generate a 4x4 matr
];
function vec3(v) = [v.x, v.y, v.z]; //! Return a 3 vector with the first three elements of ```v```
+function vec4(v) = [v.x, v.y, v.z, 1]; //! Return a 4 vector with the first three elements of ```v```
function transform(v, m) = vec3(m * [v.x, v.y, v.z, 1]); //! Apply 4x4 transform to a 3 vector by extending it and cropping it again
function transform_points(path, m) = [for(p = path) transform(p, m)]; //! Apply transform to a path
function unit(v) = let(n = norm(v)) n ? v / n : v; //! Convert ```v``` to a unit vector
@@ -74,3 +75,11 @@ function transpose(m) = [ for(j = [0 : len(m[0]) - 1]) [ for(i = [0 : len(m) - 1
function identity(n, x = 1) = [for(i = [0 : n - 1]) [for(j = [0 : n - 1]) i == j ? x : 0] ]; //! Construct an arbitrary size identity matrix
function reverse(v) = let(n = len(v) - 1) n < 0 ? [] : [for(i = [0 : n]) v[n - i]]; //! Reverse a vector
+
+function angle_between(v1, v2) = acos(v1 * v2 / (norm(v1) * norm(v2))); //! Return the angle between two vectors
+
+// https://www.gregslabaugh.net/publications/euler.pdf
+function euler(R) = let(ay = asin(-R[2][0]), cy = cos(ay)) //! Convert a rotation matrix to a Euler rotation vector.
+ cy ? [ atan2(R[2][1] / cy, R[2][2] / cy), ay, atan2(R[1][0] / cy, R[0][0] / cy) ]
+ : R[2][0] < 0 ? [atan2( R[0][1], R[0][2]), 180, 0]
+ : [atan2(-R[0][1], -R[0][2]), -180, 0];
diff --git a/utils/sweep.scad b/utils/sweep.scad
index e51b668..ea30fcd 100644
--- a/utils/sweep.scad
+++ b/utils/sweep.scad
@@ -62,6 +62,7 @@ function rotate_from_to(a, b) =
function calculate_twist(A, B) = let(D = transpose3(B) * A) atan2(D[1][0], D[0][0]);
//
// Compute a 4x3 matrix to orientate a frame of the sweep given the position and a 3x3 rotation matrix.
+// Note that the rotation matrix is transposed to allow post multiplication.
//
function orientate(p, r) =
let(x = r[0], y = r[1], z = r[2])
@@ -79,12 +80,21 @@ function rot3_z(a) =
[ [ c, -s, 0],
[ s, c, 0],
[ 0, 0, 1] ];
-
//
// Calculate the unit tangent at a vertex given the indices before and after. One of these can be the same as i in the case
-// of the start and end of a non closed path.
+// of the start and end of a non closed path. Note that the edges are converted to unit vectors so that their relative lengths
+// don't affect the direction of the tangent.
//
-function tangent(path, before, i, after) = unit(unit(path[after] - path[i]) - unit(path[before] - path[i]));
+function tangent(path, before, i, after) = unit(unit(path[i] - path[before]) + unit(path[after] - path[i]));
+//
+// Calculate the twist per segment caused by rotate_from_to() instead of a simple Euler rotation around Z.
+//
+function helical_twist_per_segment(r, pitch, sides) = //! Calculate the twist around Z that rotate_from_to() introduces
+ let(step_angle = 360 / sides,
+ lt = 2 * r * sin(step_angle), // length of tangent between two facets
+ slope = atan(2 * pitch / sides / lt) // slope of tangents
+ ) step_angle * sin(slope); // angle tangent should rotate around z projected onto axis rotate_from_to() uses
+
//
// Generate all the surface points of the swept volume.
//
@@ -111,24 +121,28 @@ function skin_points(profile, path, loop, twist = 0) =
each profile4 * orientate(path[i], rotations[i] * rot3_z(za))
];
-function cap(facets, segment = 0) = [for(i = [0 : facets - 1]) segment ? facets * segment + i : facets - 1 - i];
+function cap(facets, segment = 0, end) = //! Create the mesh for an end cap
+ let(reverse = is_undef(end) ? segment : end)
+ [for(i = [0 : facets - 1]) facets * segment + (reverse ? i : facets - 1 - i)];
function quad(p, a, b, c, d) = norm(p[a] - p[c]) > norm(p[b] - p[d]) ? [[b, c, d], [b, d, a]] : [[a, b, c], [a, c, d]];
-function skin_faces(points, segs, facets, loop) = [for(i = [0 : facets - 1], s = [0 : segs - (loop ? 1 : 2)])
- each quad(points,
- s * facets + i,
- s * facets + (i + 1) % facets,
- ((s + 1) % segs) * facets + (i + 1) % facets,
- ((s + 1) % segs) * facets + i)];
+function skin_faces(points, npoints, facets, loop, offset = 0) = //! Create the mesh for the swept volume without end caps
+ [for(i = [0 : facets - 1], s = [0 : npoints - (loop ? 1 : 2)])
+ let(j = s + offset, k = loop ? (j + 1) % npoints : j + 1)
+ each quad(points,
+ j * facets + i,
+ j * facets + (i + 1) % facets,
+ k * facets + (i + 1) % facets,
+ k * facets + i)];
function sweep(path, profile, loop = false, twist = 0) = //! Generate the point list and face list of the swept volume
let(
- segments = len(path),
+ npoints = len(path),
facets = len(profile),
points = skin_points(profile, path, loop, twist),
- skin_faces = skin_faces(points, segments, facets, loop),
- faces = loop ? skin_faces : concat([cap(facets)], skin_faces, [cap(facets, segments - 1)])
+ skin_faces = skin_faces(points, npoints, facets, loop),
+ faces = loop ? skin_faces : concat([cap(facets)], skin_faces, [cap(facets, npoints - 1)])
) [points, faces];
module sweep(path, profile, loop = false, twist = 0) { //! Draw a polyhedron that is the swept volume
@@ -141,9 +155,9 @@ function path_length(path, i = 0, length = 0) = //! Calculated the length along
i >= len(path) - 1 ? length
: path_length(path, i + 1, length + norm(path[i + 1] - path[i]));
-function circle_points(r = 1, z = 0) = //! Generate the points of a circle, setting z makes a single turn spiral
+function circle_points(r = 1, z = 0, dir = -1) = //! Generate the points of a circle, setting z makes a single turn spiral
let(sides = r2sides(r))
- [for(i = [0 : sides - 1]) let(a = i * 360 / sides) [r * sin(a), r * cos(a), z * a / 360]];
+ [for(i = [0 : sides - 1]) let(a = dir * i * 360 / sides) [r * cos(a), r * sin(a), z * i / sides]];
function rectangle_points(w, h) = [[-w/2, -h/2, 0], [-w/2, h/2, 0], [w/2, h/2, 0], [w/2, -h/2, 0]]; //! Generate the points of a rectangle
diff --git a/utils/thread.scad b/utils/thread.scad
index 25b2eaa..ce26774 100644
--- a/utils/thread.scad
+++ b/utils/thread.scad
@@ -18,69 +18,169 @@
//
//
-//! A utilities for making threads with sweep.
+//! 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.
+//!
+//! 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 ```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 <../core.scad>
use
use
+use
-function thread_profile(h, crest, angle) = //! Create thread profile path
- let(base = crest + 2 * h * tan(angle / 2))
- [[-base / 2, 0, 0], [-crest / 2, h, 0], [crest / 2, h, 0], [base / 2, 0, 0]];
+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 femail 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]);
+ maxx = max([for(p = sprofile) p.x]);
+ minx = min([for(p = sprofile) p.x]);
+ crest_xmax = max([for(p = sprofile) if(p.x != maxx) p.x]);
+ crest_xmin = min([for(p = sprofile) if(p.x != minx) p.x]);
+ //
+ // 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 = ceil(sides / starts);
+ 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);
+
+ len = length - 2 * eps;
+ rotate_extrude()
+ if(female) {
+ difference() {
+ translate([0, eps])
+ 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, eps])
+ 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);
+ }
-module male_thread(pitch, minor_d, length, profile, taper_top = true, center = true, solid = true) { //! Create male thread
- turns = length / pitch + (taper_top ? 0 : 1);
- r = minor_d / 2;
- sides = r2sides(r);
- h = max([for(p = profile) p.y]);
- final = (turns - 1) * sides;
- path = [for(i = [0 : sides * turns],
- R = i < sides ? r - h + h * i / sides
- : i > final && taper_top ? r - h * (i - final) / sides : r,
- a = i * 360 / sides)
- [R * sin(-a), R * cos(-a), pitch * a / 360]];
- t = atan(pitch / sides / (r * cos(225 / sides)));
- translate_z(center ? -length / 2 : 0) {
- render() intersection() {
- sweep(path, profile, twist = t * sides * turns);
- cylinder(d = minor_d + 5, h = length);
- }
if(solid)
- rotate(90)
- cylinder(d = minor_d + eps, h = length);
+ 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 female_thread(pitch, outer_d, length, profile, taper_top = true, center = true) { //! Create female thread
- turns = length / pitch + (taper_top ? 0 : 1);
- r = outer_d / 2;
- sides = r2sides(r);
- h = max([for(p = profile) p.y]);
- final = (turns - 1) * sides;
- path = [for(i = [0 : sides * turns],
- R = i < sides ? r + h - h * i / sides
- : i > final && taper_top ? r + h * (i - final) / sides : r,
- a = i * 360 / sides)
- [R * sin(-a), R * cos(-a), pitch * a / 360]];
- t = atan(pitch / sides / (r * cos(225 / sides)));
- translate_z(center ? -length / 2 : 0) {
- render() intersection() {
- sweep(path, reverse([for(p = profile) [p.x, -p.y, 0]]), twist = t * sides * turns);
- cylinder(d = outer_d + 5, 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 male_metric_thread(d, pitch, length, taper_top = true, center = true) { //! Create male thread with metric profile
- h = sqrt(3) / 2 * pitch;
- minor_d = d - 5 * h / 4;
- male_thread(pitch, minor_d, length, thread_profile((d - minor_d) / 2, pitch / 8, 60), taper_top, center);
-}
-
-module female_metric_thread(d, pitch, length, taper_top = true, center = true) { //! Create male thread with metric profile
- h = sqrt(3) / 2 * pitch;
- outer_d = d + 5 * h / 4;
- male_thread(pitch, outer_d, length, thread_profile((outer_d - d) / 2, pitch / 8, 60), taper_top, center);
+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
@@ -106,9 +206,12 @@ function metric_coarse_pitch(d) //! Convert metric diameter to pitch
0,
0,
1.75, // M12
+ 0,
+ 0,
+ 0,
+ 0, // M14
+ 0,
+ 0,
+ 0,
+ 2.0, // M16
][d * 2 - 4];
-
-male_metric_thread(3, 0.5, 25);
-
-translate([10, 0])
-male_metric_thread(8, 1.25, 30);
diff --git a/vitamins/d_connector.scad b/vitamins/d_connector.scad
index bae6e17..37edb2e 100644
--- a/vitamins/d_connector.scad
+++ b/vitamins/d_connector.scad
@@ -52,21 +52,24 @@ module d_pillar() { //! Draw a pillar for a D-connector
height = 4.5;
screw = 2.5;
screw_length = 8;
+ pitch = metric_coarse_pitch(screw);
translate_z(-screw_length)
if(show_threads)
- color(d_pillar_color * 0.7)
- male_metric_thread(screw, metric_coarse_pitch(screw), screw_length, false, false);
+ male_metric_thread(screw, pitch, screw_length, false, top = 0, colour = d_pillar_color);
else
color(d_pillar_color)
cylinder(d = screw, h = screw_length + 1);
- color(d_pillar_color)
+ color(d_pillar_color) {
linear_extrude(height = height)
difference() {
circle(r = rad, $fn = 6);
circle(d = screw);
}
+ }
+ if(show_threads)
+ female_metric_thread(screw, pitch, height, false, colour = d_pillar_color);
}
module d_plug(type, socket = false, pcb = false, idc = false) { //! Draw specified D plug, which can be IDC, PCB or plain solder bucket
diff --git a/vitamins/insert.scad b/vitamins/insert.scad
index a22e65b..0cceb63 100644
--- a/vitamins/insert.scad
+++ b/vitamins/insert.scad
@@ -22,6 +22,7 @@
//
include <../core.scad>
use <../utils/quadrant.scad>
+use <../utils/thread.scad>
function insert_length(type) = type[1]; //! Length
function insert_outer_d(type) = type[2]; //! Outer diameter at the top
@@ -45,17 +46,18 @@ module insert(type) { //! Draw specified insert
vitamin(str("insert(", type[0], "): Heatfit insert M", insert_screw_diameter(type)));
$fn = 64;
- explode(20, offset =[0, 0, -5]) color(brass) translate_z(eps) {
- vflip(){
- r1 = insert_screw_diameter(type) / 2;
- r2 = insert_barrel_d(type) / 2;
- r3 = insert_ring3_d(type) / 2;
- r4 = insert_ring2_d(type) / 2;
- r5 = insert_outer_d(type) / 2;
- h1 = ring1_h;
- h2 = ring1_h + gap;
- h3 = ring1_h + gap + ring2_h;
- h4 = ring1_h + gap + ring2_h + gap;
+ thread_d = insert_screw_diameter(type);
+ explode(20, offset =[0, 0, -5]) translate_z(eps) vflip() {
+ r1 = thread_d / 2;
+ r2 = insert_barrel_d(type) / 2;
+ r3 = insert_ring3_d(type) / 2;
+ r4 = insert_ring2_d(type) / 2;
+ r5 = insert_outer_d(type) / 2;
+ h1 = ring1_h;
+ h2 = ring1_h + gap;
+ h3 = ring1_h + gap + ring2_h;
+ h4 = ring1_h + gap + ring2_h + gap;
+ color(brass)
rotate_extrude()
polygon([
[r1, 0],
@@ -72,7 +74,9 @@ module insert(type) { //! Draw specified insert
[r5, h1],
[r5, 0],
]);
- }
+
+ if(show_threads)
+ female_metric_thread(thread_d, metric_coarse_pitch(thread_d), length, center = false, colour = brass);
}
}
diff --git a/vitamins/leadnut.scad b/vitamins/leadnut.scad
index 56b570b..3804fd2 100644
--- a/vitamins/leadnut.scad
+++ b/vitamins/leadnut.scad
@@ -21,7 +21,8 @@
//! Nuts for leadscrews.
//
include <../core.scad>
-include <../utils/tube.scad>
+use <../utils/tube.scad>
+use <../utils/thread.scad>
function leadnut_bore(type) = type[2]; //! Thread size
function leadnut_od(type) = type[3]; //! Outer diameter of the shank
@@ -33,6 +34,8 @@ function leadnut_holes(type) = type[8]; //! The number of screw hole
function leadnut_hole_dia(type) = type[9]; //! The diameter of the screw holes
function leadnut_hole_pitch(type) = type[10]; //! The radia pitch of the screw holes
function leadnut_screw(type) = type[11]; //! The type of the fixing screws
+function leadnut_pitch(type) = type[12]; //! Screw pitch
+function leadnut_lead(type) = type[13]; //! Screw lead
function leadnut_shank(type) = leadnut_height(type) - leadnut_flange_t(type) - leadnut_flange_offset(type); //! The length of the shank below the flange
@@ -47,11 +50,18 @@ module leadnut_screw_positions(type) { //! Position children at the screw holes
module leadnut(type) { //! Draw specified leadnut
vitamin(str("leadnut(", type[0], "): ", type[1]));
- bore_r = (leadnut_bore(type) + 0.5) / 2;
+ bore_d = leadnut_bore(type);
+ bore_r = bore_d / 2;
+ h = leadnut_height(type);
+ pitch = leadnut_pitch(type);
+ lead = leadnut_lead(type);
color("dimgrey") vflip()
translate_z(-leadnut_flange_offset(type) - leadnut_flange_t(type)) {
- tube(or = leadnut_od(type) / 2, ir = bore_r, h = leadnut_height(type), center = false);
+ tube(or = leadnut_od(type) / 2, ir = bore_r, h = h, center = false);
+
+ if(show_threads)
+ thread(bore_d, lead, h, thread_profile(pitch / 2, pitch * 0.366, 30), false, starts = lead / pitch, female = true, solid = false);
translate_z(leadnut_flange_offset(type))
linear_extrude(height = leadnut_flange_t(type))
diff --git a/vitamins/leadnuts.scad b/vitamins/leadnuts.scad
index e119cc2..2a13596 100644
--- a/vitamins/leadnuts.scad
+++ b/vitamins/leadnuts.scad
@@ -17,8 +17,8 @@
// If not, see .
//
-LSN8x2 = ["LSN8x2", "Leadscrew nut 8 x 2", 8, 10.2, 15, 22, 3.5, 1.5, 4, 3.5, 8, M3_cap_screw];
-LSN8x8 = ["LSN8x8", "Leadscrew nut 8 x 8 RobotDigg",8, 12.75,19, 25.4, 4.1, 0, 3, 3.5, 19.05/2, M3_cap_screw];
+LSN8x2 = ["LSN8x2", "Leadscrew nut 8 x 2", 8, 10.2, 15, 22, 3.5, 1.5, 4, 3.5, 8, M3_cap_screw, 2, 2];
+LSN8x8 = ["LSN8x8", "Leadscrew nut 8 x 8 RobotDigg",8, 12.75,19, 25.4, 4.1, 0, 3, 3.5, 19.05/2, M3_cap_screw, 2, 8];
leadnuts = [LSN8x2, LSN8x8];
diff --git a/vitamins/nut.scad b/vitamins/nut.scad
index 435465b..39a6bb2 100644
--- a/vitamins/nut.scad
+++ b/vitamins/nut.scad
@@ -26,6 +26,8 @@ include <../core.scad>
use
use
use <../utils/rounded_cylinder.scad>
+use <../utils/thread.scad>
+use <../utils/tube.scad>
brass_colour = brass;
function nut_size(type) = type[1]; //! Diameter of the corresponding screw
@@ -37,7 +39,8 @@ function nut_trap_depth(type) = type[6]; //! Depth of nut trap
function nut_flat_radius(type) = nut_radius(type) * cos(30); //! Radius across the flats
module nut(type, nyloc = false, brass = false, nylon = false) { //! Draw specified nut
- hole_rad = nut_size(type) / 2;
+ thread_d = nut_size(type);
+ hole_rad = thread_d / 2;
outer_rad = nut_radius(type);
thickness = nut_thickness(type);
nyloc_thickness = nut_thickness(type, true);
@@ -45,18 +48,29 @@ module nut(type, nyloc = false, brass = false, nylon = false) { //! Draw specifi
vitamin(str("nut(", type[0], arg(nyloc, false, "nyloc"), arg(brass, false, "brass"), arg(nylon, false, "nylon"),
"): Nut M", nut_size(type), " x ", thickness, "mm ", desc));
- explode(nyloc ? 10 : 0)
- color(brass ? brass_colour : nylon ? grey30: grey70) {
+ colour = brass ? brass_colour : nylon ? grey30: grey70;
+ explode(nyloc ? 10 : 0) {
+ color(colour) {
linear_extrude(height = thickness)
difference() {
circle(outer_rad, $fn = 6);
circle(hole_rad);
}
+
if(nyloc)
translate_z(-eps)
rounded_cylinder(r = outer_rad * cos(30) , h = nyloc_thickness, r2 = (nyloc_thickness - thickness) / 2, ir = hole_rad);
}
+
+ if(show_threads)
+ female_metric_thread(thread_d, metric_coarse_pitch(thread_d), thickness, center = false, colour = colour);
+
+ if(nyloc)
+ translate_z(thickness)
+ color("royalblue")
+ tube(or = thread_d / 2 + eps, ir = (thread_d * 0.8) / 2, h = (nyloc_thickness - thickness) * 0.8, center = false);
+ }
if($children)
translate_z(nut_thickness(type, nyloc))
children();
@@ -73,7 +87,8 @@ module nut_and_washer(type, nyloc) { //! Draw nut with corresponding washer
}
module wingnut(type) { //! Draw a wingnut
- hole_rad = nut_size(type) / 2;
+ thread_d = nut_size(type);
+ hole_rad = thread_d / 2;
bottom_rad = nut_radius(type);
top_rad = type[4] / 2;
thickness = nut_thickness(type);
@@ -87,25 +102,31 @@ module wingnut(type) { //! Draw a wingnut
vitamin(str("wingnut(", type[0], "): Wingnut M", nut_size(type)));
- explode(10) color(grey70) {
- rotate_extrude()
- polygon([
- [hole_rad, 0],
- [bottom_rad, 0],
- [top_rad,, thickness],
- [hole_rad, thickness]
- ]);
- for(rot = [0, 180])
- rotate([90, 0, rot]) linear_extrude(height = wing_thickness, center = true)
- hull() {
- translate([wing_span / 2 - wing_width / 2, wing_height - wing_width / 2])
- circle(wing_width / 2);
- polygon([
- [bottom_rad * cos(top_angle) - eps, 0],
- [wing_span / 2 - wing_width / 2, wing_height - wing_width / 2],
- [top_rad * cos(top_angle) - eps, thickness],
- ]);
- }
+ colour = silver;
+ explode(10) {
+ color(colour) {
+ rotate_extrude()
+ polygon([
+ [hole_rad, 0],
+ [bottom_rad, 0],
+ [top_rad,, thickness],
+ [hole_rad, thickness]
+ ]);
+ for(rot = [0, 180])
+ rotate([90, 0, rot]) linear_extrude(height = wing_thickness, center = true)
+ hull() {
+ translate([wing_span / 2 - wing_width / 2, wing_height - wing_width / 2])
+ circle(wing_width / 2);
+ polygon([
+ [bottom_rad * cos(top_angle) - eps, 0],
+ [wing_span / 2 - wing_width / 2, wing_height - wing_width / 2],
+ [top_rad * cos(top_angle) - eps, thickness],
+ ]);
+ }
+ }
+
+ if(show_threads)
+ female_metric_thread(thread_d, metric_coarse_pitch(thread_d), thickness, center = false, colour = colour);
}
}
function nut_trap_radius(nut, horizontal = false) = nut_radius(nut) + (horizontal ? layer_height / 4 : 0); //! Radius across the corners of a nut trap
diff --git a/vitamins/opengrab.scad b/vitamins/opengrab.scad
index 54957ef..ce8b932 100644
--- a/vitamins/opengrab.scad
+++ b/vitamins/opengrab.scad
@@ -23,6 +23,7 @@
//! A permanent magnet that can be magnatized and de-magnatized electronically.
//
include <../core.scad>
+use <../utils/thread.scad>
pitch = 33.8;
width = 40;
@@ -64,15 +65,20 @@ module opengrab() { //! Draw OpenGrab module
translate_z(depth - pillar - pcb / 2)
cube([width, width, pcb], center = true);
- color(brass)
- translate_z(1)
- opengrab_hole_positions()
+
+ translate_z(1)
+ opengrab_hole_positions() {
+ color(brass)
linear_extrude(height = depth - 1)
difference() {
circle(d = 4.7 / cos(30), $fn = 6);
circle(r = 3/2);
}
+
+ if(show_threads)
+ female_metric_thread(3, metric_coarse_pitch(3), depth - 1, center = false, colour = brass);
+ }
}
module opengrab_target() { //! Draw OpenGrab target
diff --git a/vitamins/pillar.scad b/vitamins/pillar.scad
index 9ac3e00..d865ebc 100644
--- a/vitamins/pillar.scad
+++ b/vitamins/pillar.scad
@@ -44,26 +44,26 @@ module pillar(type) { //! Draw specified pillar
thread_d = pillar_thread(type);
bot_thread_l = pillar_bot_thread(type);
top_thread_l = pillar_top_thread(type);
- thread_colour = pillar_i_colour(type) * (show_threads ? 0.7 : 1);
+ thread_colour = pillar_i_colour(type);
+ pitch = metric_coarse_pitch(thread_d);
vitamin(str("pillar(", type[0], "): Pillar ", pillar_name(type), " ", sex, " M", thread_d, "x", height));
- color(thread_colour) {
- if(bot_thread_l > 0)
- translate_z(-bot_thread_l + eps)
- if(show_threads)
- male_metric_thread(thread_d, metric_coarse_pitch(thread_d), bot_thread_l, false, false);
- else
+ if(bot_thread_l > 0)
+ translate_z(-bot_thread_l + eps)
+ if(show_threads)
+ male_metric_thread(thread_d, pitch, bot_thread_l, false, top = 0, colour = thread_colour);
+ else
+ color(thread_colour)
cylinder(h = bot_thread_l, d = thread_d);
- if(top_thread_l > 0)
- translate_z(height + top_thread_l - eps)
- if(show_threads)
- vflip()
- male_metric_thread(thread_d, metric_coarse_pitch(thread_d), top_thread_l, false, false);
- else
+ if(top_thread_l > 0)
+ translate_z(height - eps)
+ if(show_threads)
+ male_metric_thread(thread_d, pitch, top_thread_l, false, bot = 0, colour = thread_colour);
+ else
+ color(thread_colour)
cylinder(h = top_thread_l, d = thread_d);
- }
color(pillar_i_colour(type)) {
linear_extrude(height = height)
@@ -79,6 +79,16 @@ module pillar(type) { //! Draw specified pillar
cylinder(h = top - bot, d = thread_d + eps);
}
+ if(show_threads) {
+ if(top_thread_l < 0)
+ translate_z(height)
+ vflip()
+ female_metric_thread(thread_d, pitch, -top_thread_l, false, colour = thread_colour);
+
+ if(bot_thread_l < 0)
+ female_metric_thread(thread_d, pitch, -bot_thread_l, false, colour = thread_colour);
+ }
+
if(pillar_od(type) > pillar_id(type))
color(pillar_o_colour(type)) linear_extrude(height = height)
difference() {
diff --git a/vitamins/rod.scad b/vitamins/rod.scad
index f9e0a5e..747e9cc 100644
--- a/vitamins/rod.scad
+++ b/vitamins/rod.scad
@@ -25,31 +25,53 @@ use <../utils/thread.scad>
rod_colour = grey80;
studding_colour = grey70;
+leadscrew_colour = grey70;
-module rod(d , l) { //! Draw a smooth rod with specified length and diameter
+module rod(d , l, center = true) { //! Draw a smooth rod with specified diameter and length
vitamin(str("rod(", d, ", ", l, "): Smooth rod ", d, "mm x ", l, "mm"));
chamfer = d / 10;
color(rod_colour)
- hull() {
- cylinder(d = d, h = l - 2 * chamfer, center = true);
-
- cylinder(d = d - 2 * chamfer, h = l, center = true);
- }
-}
-
-module studding(d , l) { //! Draw a threaded rod with specified length and diameter
- vitamin(str("studding(", d, ", ", l,"): Threaded rod M", d, " x ", l, "mm"));
-
- chamfer = d / 20;
- pitch = metric_coarse_pitch(d);
- color(studding_colour)
- if(show_threads && pitch)
- male_metric_thread(d, pitch, l);
- else
+ translate_z(center ? 0 : l / 2)
hull() {
cylinder(d = d, h = l - 2 * chamfer, center = true);
cylinder(d = d - 2 * chamfer, h = l, center = true);
}
}
+
+module studding(d , l, center = true) { //! Draw a threaded rod with specified diameter and length
+ vitamin(str("studding(", d, ", ", l,"): Threaded rod M", d, " x ", l, "mm"));
+
+ chamfer = d / 20;
+ pitch = metric_coarse_pitch(d);
+
+ translate_z(center ? 0 : l / 2)
+ if(show_threads && pitch)
+ male_metric_thread(d, pitch, l, colour = rod_colour);
+ else
+ color(studding_colour)
+ hull() {
+ cylinder(d = d, h = l - 2 * chamfer, center = true);
+
+ cylinder(d = d - 2 * chamfer, h = l, center = true);
+ }
+}
+
+module leadscrew(d , l, lead, starts, center = true) { //! Draw a leadscrew with specified diameter, length, lead and number of starts
+ vitamin(str("leadscrew(", d, ", ", l, ", ", lead, ", ", starts, "): Leadscrew ", d, " x ", l, "mm, ", lead, "mm lead, ", starts, " starts"));
+
+ pitch = lead / starts;
+ chamfer = pitch / 2;
+
+ translate_z(center ? 0 : l / 2)
+ if(show_threads && pitch)
+ thread(d - pitch, lead, l, thread_profile(pitch / 2, pitch * 0.366, 30), top = 45, bot = 45, starts = starts, center = center, colour = rod_colour);
+ else
+ color(leadscrew_colour)
+ hull() {
+ cylinder(d = d, h = l - 2 * chamfer, center = true);
+
+ cylinder(d = d - 2 * chamfer, h = l, center = true);
+ }
+}
diff --git a/vitamins/screw.scad b/vitamins/screw.scad
index 1aeea76..ab623ed 100644
--- a/vitamins/screw.scad
+++ b/vitamins/screw.scad
@@ -73,34 +73,33 @@ module screw(type, length, hob_point = 0, nylon = false) { //! Draw specified sc
thread = max_thread ? length >= max_thread + 5 ? max_thread
: length
: length;
- shank = length - thread;
+ d = 2 * screw_radius(type);
+ pitch = metric_coarse_pitch(d);
colour = nylon || head_type == hs_grub ? grey40 : grey80;
- module shaft(headless = 0) {
+ module shaft(socket = 0, headless = false) {
point = screw_nut(type) ? 0 : 3 * rad;
- d = 2 * screw_radius(type);
- pitch = metric_coarse_pitch(d);
- l = length - shank;
+ shank = length - thread - socket;
+
if(show_threads && !point && pitch)
- translate_z(-l - shank)
- color(colour * 0.7)
- male_metric_thread(d, pitch, l, !!headless, false);
+ translate_z(-length)
+ male_metric_thread(d, pitch, thread - (shank > 0 || headless ? 0 : socket), false, top = headless ? -1 : 0, solid = !headless, colour = colour);
else
color(colour * 0.9)
rotate_extrude() {
translate([0, -length + point])
- square([rad, length - headless - point]);
+ square([rad, length - socket - point]);
if(point)
polygon([
- [0, -length], [0, point - length], [rad - 0.1, point - length]
+ [0.4, -length], [0, point - length], [rad, point - length]
]);
}
- if(shank - headless > 0)
- color(colour)
- translate_z(-shank)
- cylinder(r = rad + eps, h = shank - headless);
+ if(shank > 0)
+ color(colour)
+ translate_z(-shank - socket)
+ cylinder(r = rad + eps, h = shank);
}
explode(length + 10) {
@@ -121,23 +120,20 @@ module screw(type, length, hob_point = 0, nylon = false) { //! Draw specified sc
}
if(head_type == hs_grub) {
color(colour) {
- if(!show_threads) {
- translate_z(-socket_depth)
- linear_extrude(height = socket_depth)
- difference() {
- circle(r = rad);
+ r = show_threads ? rad - pitch / 2 : rad;
+ translate_z(-socket_depth)
+ linear_extrude(height = socket_depth)
+ difference() {
+ circle(r);
- circle(socket_rad, $fn = 6);
- }
+ circle(socket_rad, $fn = 6);
+ }
- shaft(socket_depth);
- }
- else
- render() difference() {
- shaft(socket_depth);
+ shaft(socket_depth, true);
- cylinder(r = socket_rad, $fn = 6, h = 2 * socket_depth, center = true);
- }
+ if(show_threads)
+ translate_z(-length)
+ cylinder(r = r, h = length - socket_depth);
}
}
if(head_type == hs_hex) {
diff --git a/vitamins/stepper_motor.scad b/vitamins/stepper_motor.scad
index 616e547..83950e5 100644
--- a/vitamins/stepper_motor.scad
+++ b/vitamins/stepper_motor.scad
@@ -26,6 +26,7 @@ include
use
include
use <../utils/tube.scad>
+use
function NEMA_width(type) = type[1]; //! Width of the square face
function NEMA_length(type) = type[2]; //! Body length
@@ -34,7 +35,7 @@ function NEMA_body_radius(type) = type[4]; //! Body radius
function NEMA_boss_radius(type) = type[5]; //! Boss around the spindle radius
function NEMA_boss_height(type) = type[6]; //! Boss height
function NEMA_shaft_dia(type) = type[7]; //! Shaft diameter
-function NEMA_shaft_length(type)= type[8]; //! Shaft length above the face
+function NEMA_shaft_length(type)= type[8]; //! Shaft length above the face, if a list then a leadscrew: length, lead, starts
function NEMA_hole_pitch(type) = type[9]; //! Screw hole pitch
function NEMA_holes(type) = [-NEMA_hole_pitch(type) / 2, NEMA_hole_pitch(type) / 2]; //! Screw positions for for loop
function NEMA_big_hole(type) = NEMA_boss_radius(type) + 0.2; //! Clearance hole for the big boss
@@ -50,7 +51,7 @@ module NEMA_outline(type) //! 2D outline
circle(NEMA_radius(type));
}
-module NEMA(type) { //! Draw specified NEMA stepper motor
+module NEMA(type, shaft_angle = 0) { //! Draw specified NEMA stepper motor
side = NEMA_width(type);
length = NEMA_length(type);
body_rad = NEMA_body_radius(type);
@@ -88,9 +89,15 @@ module NEMA(type) { //! Draw specified NEMA stepper motor
}
}
- color(NEMA_shaft_length(type) > 50 ? "silver" : stepper_cap_colour)
- translate_z(-5)
- cylinder(r = shaft_rad, h = NEMA_shaft_length(type) + 5); // shaft
+ shaft = NEMA_shaft_length(type);
+ translate_z(-5)
+ rotate(shaft_angle)
+ if(!is_list(shaft))
+ color(stepper_cap_colour)
+ cylinder(r = shaft_rad, h = shaft + 5); // shaft
+ else
+ not_on_bom()
+ leadscrew(shaft_rad * 2, shaft.x + 5, shaft.y, shaft.z, center = false)
translate([0, side / 2, -length + cap / 2])
rotate([90, 0, 0])
diff --git a/vitamins/stepper_motors.scad b/vitamins/stepper_motors.scad
index 1a233b1..c27e3fd 100644
--- a/vitamins/stepper_motors.scad
+++ b/vitamins/stepper_motors.scad
@@ -22,14 +22,14 @@
//
// corner body boss boss shaft
-// side, length, radius, radius, radius, depth, shaft, length, holes
-NEMA17 = ["NEMA17", 42.3, 47, 53.6/2, 25, 11, 2, 5, 24, 31 ];
-NEMA17M = ["NEMA17M", 42.3, 40, 53.6/2, 25, 11, 2, 5, 20, 31 ];
-NEMA17M8= ["NEMA17M8", 42.3, 40, 53.6/2, 25, 11, 2, 8, 280, 31 ];
-NEMA17S = ["NEMA17S", 42.3, 34, 53.6/2, 25, 11, 2, 5, 24, 31 ];
-NEMA16 = ["NEMA16", 39.5, 19.2, 50.6/2, 50.6/2, 11, 2, 5, 12, 31 ];
-NEMA14 = ["NEMA14", 35.2, 36, 46.4/2, 21, 11, 2, 5, 21, 26 ];
-NEMA23 = ["NEMA23", 56.4, 51.2, 75.7/2, 35, 38.1/2, 1.6, 6.35, 24, 47.1 ];
+// side, length, radius, radius, radius, depth, shaft, length, holes
+NEMA17 = ["NEMA17", 42.3, 47, 53.6/2, 25, 11, 2, 5, 24, 31 ];
+NEMA17M = ["NEMA17M", 42.3, 40, 53.6/2, 25, 11, 2, 5, 20, 31 ];
+NEMA17M8= ["NEMA17M8", 42.3, 40, 53.6/2, 25, 11, 2, 8, [280, 8, 4], 31 ];
+NEMA17S = ["NEMA17S", 42.3, 34, 53.6/2, 25, 11, 2, 5, 24, 31 ];
+NEMA16 = ["NEMA16", 39.5, 19.2, 50.6/2, 50.6/2, 11, 2, 5, 12, 31 ];
+NEMA14 = ["NEMA14", 35.2, 36, 46.4/2, 21, 11, 2, 5, 21, 26 ];
+NEMA23 = ["NEMA23", 56.4, 51.2, 75.7/2, 35, 38.1/2, 1.6, 6.35, 24, 47.1 ];
stepper_motors = [NEMA14, NEMA16, NEMA17S, NEMA17M, NEMA17, NEMA23];
diff --git a/vitamins/toggle.scad b/vitamins/toggle.scad
index f531f9c..f4aa6dd 100644
--- a/vitamins/toggle.scad
+++ b/vitamins/toggle.scad
@@ -23,6 +23,8 @@
include <../core.scad>
use
use
+use <../utils/thread.scad>
+use <../utils/tube.scad>
function toggle_part(type) = type[1]; //! Part description
function toggle_width(type) = type[2]; //! Body width
@@ -80,7 +82,29 @@ module toggle(type, thickness) { //! Draw specified toggle switch with the nuts
translate_z(-h1 / 2 - t)
cube([toggle_width(type), toggle_height(type), h1], center = true);
- color("silver") {
+ if(show_threads) {
+ d = toggle_od(type);
+ pitch = inch(1/40);
+ h = 5 * pitch * sqrt(3) / 16;
+
+ male_metric_thread(d, pitch, thread, center = false, solid = false, colour = silver);
+
+ color(silver)
+ tube(or = d / 2 - h, ir = toggle_id(type) / 2, h = thread, center = false);
+ }
+ else
+ color(silver)
+ rotate_extrude()
+ difference() {
+ hull() {
+ square([toggle_od(type) / 2, thread - chamfer]);
+
+ square([toggle_od(type) / 2 - chamfer, thread]);
+ }
+ square([toggle_id(type) / 2, thread + 1]);
+ }
+
+ color(silver) {
if(toggle_collar_t(type))
cylinder(d = toggle_collar_d(type), h = toggle_collar_t(type));
@@ -90,15 +114,6 @@ module toggle(type, thickness) { //! Draw specified toggle switch with the nuts
translate_z(-h2 / 2)
cube([toggle_width(type) + 2 * eps, toggle_height(type) - 2 * inset, h2], center = true);
- rotate_extrude()
- difference() {
- hull() {
- square([toggle_od(type) / 2, thread - chamfer]);
-
- square([toggle_od(type) / 2 - chamfer, thread]);
- }
- square([toggle_id(type) / 2, thread + 1]);
- }
translate_z(toggle_pivot(type)) {
angle = toggle_angle(type);
diff --git a/vitamins/washer.scad b/vitamins/washer.scad
index fe2bf07..733a5e3 100644
--- a/vitamins/washer.scad
+++ b/vitamins/washer.scad
@@ -103,12 +103,14 @@ module spring_washer(type) { //! Draw spring version of washer
vitamin(str("spring_washer(", type[0], "_washer): Washer spring M", hole, " x ", thickness, "mm"));
ir = washer_id(type) / 2;
or = diameter / 2;
- path = circle_points((ir + or) / 2, exploded() ? thickness / 2 : 0);
+ pitch = exploded() ? thickness / 2 : 0;
+ path = circle_points((ir + or) / 2, pitch);
profile = rectangle_points(thickness, or - ir);
+ len = len(path);
color(hard_washer_colour)
translate_z(thickness / 2)
- rotate(180)
- sweep(path, profile);
+ rotate(-90)
+ sweep(path, profile, twist = -helical_twist_per_segment(ir, pitch, len) * (len - 1));
if($children)
translate_z(thickness)