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)