├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── scripts └── make_all_docs.sh ├── LICENSE ├── acme_screws.scad ├── .gitignore ├── tests ├── test_convex_hull.scad └── test_math.scad ├── examples ├── orientations.scad └── bezier_patches.scad ├── phillips_drive.scad ├── wiring.scad ├── torx_drive.scad ├── linear_bearings.scad ├── WRITING_DOCS.md ├── README.md ├── debug.scad ├── sliders.scad ├── convex_hull.scad ├── triangulation.scad ├── quaternions.scad ├── compat.scad ├── constants.scad ├── paths.scad ├── joiners.scad ├── nema_steppers.scad └── metric_screws.scad /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: revarbat 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **Code To Reproduce Bug** 14 | ``` 15 | // Code goes here. 16 | ``` 17 | 18 | **Expected behavior** 19 | A clear and concise description of what you expected to happen. 20 | 21 | **Screenshots** 22 | If applicable, add screenshots to help explain your problem. 23 | 24 | **Additional context** 25 | Add any other context about the problem here. 26 | - OpenSCAD Version: 27 | - Other libraries used: 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: revarbat 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Example Code** 20 | ``` 21 | // Code goes here. 22 | ``` 23 | 24 | **Additional context** 25 | Add any other context or screenshots about the feature request here. 26 | -------------------------------------------------------------------------------- /scripts/make_all_docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [[ $# > 0 ]]; then 4 | PREVIEW_LIBS="$@" 5 | else 6 | PREVIEW_LIBS="constants compat transforms shapes masks paths beziers math metric_screws threading involute_gears sliders joiners linear_bearings nema_steppers wiring triangulation quaternions phillips_drive torx_drive debug" 7 | fi 8 | 9 | dir="$(basename $PWD)" 10 | if [ "$dir" = "BOSL" ]; then 11 | cd BOSL.wiki 12 | elif [ "$dir" != "BOSL.wiki" ]; then 13 | echo "Must run this script from the BOSL or BOSL/BOSL.wiki directories." 14 | exit 1 15 | fi 16 | 17 | rm -f tmpscad*.scad 18 | for lib in $PREVIEW_LIBS; do 19 | lib="$(basename $lib .scad)" 20 | mkdir -p images/$lib 21 | rm -f images/$lib/*.png images/$lib/*.gif 22 | echo ../scripts/docs_gen.py ../$lib.scad -o $lib.scad.md -c -i -I images/$lib/ 23 | ../scripts/docs_gen.py ../$lib.scad -o $lib.scad.md -c -i -I images/$lib/ || exit 1 24 | open -a Typora $lib.scad.md 25 | done 26 | 27 | 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2017, Revar Desmera 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /acme_screws.scad: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////// 2 | // ACME Trapezoidal-threaded Screw Rods and Nuts 3 | ////////////////////////////////////////////////////////////////////// 4 | 5 | /* 6 | BSD 2-Clause License 7 | 8 | Copyright (c) 2017, Revar Desmera 9 | All rights reserved. 10 | 11 | Redistribution and use in source and binary forms, with or without 12 | modification, are permitted provided that the following conditions are met: 13 | 14 | * Redistributions of source code must retain the above copyright notice, this 15 | list of conditions and the following disclaimer. 16 | 17 | * Redistributions in binary form must reproduce the above copyright notice, 18 | this list of conditions and the following disclaimer in the documentation 19 | and/or other materials provided with the distribution. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | include 34 | 35 | 36 | 37 | // vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | 103 | # Mac stuff 104 | .DS_Store 105 | 106 | # Vim temp files 107 | .*.swp 108 | 109 | BOSL.wiki 110 | foo.scad 111 | ref 112 | 113 | 114 | -------------------------------------------------------------------------------- /tests/test_convex_hull.scad: -------------------------------------------------------------------------------- 1 | include 2 | include 3 | 4 | 5 | testpoints_on_sphere = [ for(p = 6 | [ 7 | [1,PHI,0], [-1,PHI,0], [1,-PHI,0], [-1,-PHI,0], 8 | [0,1,PHI], [0,-1,PHI], [0,1,-PHI], [0,-1,-PHI], 9 | [PHI,0,1], [-PHI,0,1], [PHI,0,-1], [-PHI,0,-1] 10 | ]) 11 | normalize(p) 12 | ]; 13 | 14 | testpoints_circular = [ for(a = [0:15:360-EPSILON]) [cos(a),sin(a)] ]; 15 | 16 | testpoints_coplanar = let(u = normalize([1,3,7]), v = normalize([-2,1,-2])) [ for(i = [1:10]) rands(-1,1,1)[0] * u + rands(-1,1,1)[0] * v ]; 17 | 18 | testpoints_collinear_2d = let(u = normalize([5,3])) [ for(i = [1:20]) rands(-1,1,1)[0] * u ]; 19 | testpoints_collinear_3d = let(u = normalize([5,3,-5])) [ for(i = [1:20]) rands(-1,1,1)[0] * u ]; 20 | 21 | testpoints2d = 20 * [for (i = [1:10]) concat(rands(-1,1,2))]; 22 | testpoints3d = 20 * [for (i = [1:50]) concat(rands(-1,1,3))]; 23 | 24 | // All points are on the sphere, no point should be red 25 | translate([-50,0]) visualize_hull(20*testpoints_on_sphere); 26 | 27 | // 2D points 28 | translate([50,0]) visualize_hull(testpoints2d); 29 | 30 | // All points on a circle, no point should be red 31 | translate([0,50]) visualize_hull(20*testpoints_circular); 32 | 33 | // All points 3d but collinear 34 | translate([0,-50]) visualize_hull(20*testpoints_coplanar); 35 | 36 | // Collinear 37 | translate([50,50]) visualize_hull(20*testpoints_collinear_2d); 38 | 39 | // Collinear 40 | translate([-50,50]) visualize_hull(20*testpoints_collinear_3d); 41 | 42 | // 3D points 43 | visualize_hull(testpoints3d); 44 | 45 | 46 | module visualize_hull(points) { 47 | hull = convex_hull(points); 48 | 49 | %if (len(hull) > 0 && is_list(hull[0]) && len(hull[0]) > 0) 50 | polyhedron(points=points, faces = hull); 51 | else 52 | polyhedron(points=points, faces = [hull]); 53 | 54 | for (i = [0:len(points)-1]) { 55 | p = points[i]; 56 | $fn = 16; 57 | translate(p) { 58 | if (hull_contains_index(hull,i)) { 59 | color("blue") sphere(1); 60 | } else { 61 | color("red") sphere(1); 62 | } 63 | } 64 | } 65 | 66 | function hull_contains_index(hull, index) = 67 | search(index,hull,1,0) || 68 | search(index,hull,1,1) || 69 | search(index,hull,1,2); 70 | } 71 | 72 | 73 | // vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 74 | -------------------------------------------------------------------------------- /examples/orientations.scad: -------------------------------------------------------------------------------- 1 | use 2 | use 3 | include 4 | 5 | // Shows all the orientations on cubes in their correct rotations. 6 | 7 | orientations = [ 8 | ORIENT_X, ORIENT_Y, ORIENT_Z, 9 | ORIENT_XNEG, ORIENT_YNEG, ORIENT_ZNEG, 10 | 11 | ORIENT_X_90, ORIENT_Y_90, ORIENT_Z_90, 12 | ORIENT_XNEG_90, ORIENT_YNEG_90, ORIENT_ZNEG_90, 13 | 14 | ORIENT_X_180, ORIENT_Y_180, ORIENT_Z_180, 15 | ORIENT_XNEG_180, ORIENT_YNEG_180, ORIENT_ZNEG_180, 16 | 17 | ORIENT_X_270, ORIENT_Y_270, ORIENT_Z_270, 18 | ORIENT_XNEG_270, ORIENT_YNEG_270, ORIENT_ZNEG_270 19 | ]; 20 | 21 | 22 | axisdiam = 0.5; 23 | axislen = 12; 24 | axislbllen = 15; 25 | axiscolors = ["red", "forestgreen", "dodgerblue"]; 26 | 27 | module text3d(text, h=0.01, size=3) { 28 | linear_extrude(height=h, convexity=10) { 29 | text(text=text, size=size, valign="center", halign="center"); 30 | } 31 | } 32 | 33 | module dottedline(l, d) for(y = [0:d*3:l]) up(y) sphere(d=d); 34 | 35 | module orient_cubes() { 36 | color(axiscolors[0]) { 37 | yrot( 90) cylinder(h=axislen, d=axisdiam, center=false); 38 | right(axislbllen) rot([90,0,0]) text3d(text="X+"); 39 | yrot(-90) dottedline(l=axislen, d=axisdiam); 40 | left(axislbllen) rot([90,0,180]) text3d(text="X-"); 41 | } 42 | color(axiscolors[1]) { 43 | xrot(-90) cylinder(h=axislen, d=axisdiam, center=false); 44 | back(axislbllen) rot([90,0,90]) text3d(text="Y+"); 45 | xrot( 90) dottedline(l=axislen, d=axisdiam); 46 | fwd(axislbllen) rot([90,0,-90]) text3d(text="Y-"); 47 | } 48 | color(axiscolors[2]) { 49 | cylinder(h=axislen, d=axisdiam, center=false); 50 | up(axislbllen) rot([0,-90,90+$vpr[2]]) text3d(text="Z+"); 51 | xrot(180) dottedline(l=axislen, d=axisdiam); 52 | down(axislbllen) rot([0,90,-90+$vpr[2]]) text3d(text="Z-"); 53 | } 54 | 55 | 56 | for (ang = [0:90:270]) { 57 | translate(cylindrical_to_xyz(40, ang+90, 0)) { 58 | color("lightgray") cube(20, center=true); 59 | } 60 | } 61 | 62 | for (axis=[0:2], neg=[0:1], ang = [0:90:270]) { 63 | idx = axis + 3*neg + 6*ang/90; 64 | translate(cylindrical_to_xyz(40, ang+90, 0)) { 65 | rotate(orientations[idx]) { 66 | up(10) { 67 | ydistribute(8) { 68 | color("black") text3d(text=str(ang, "º"), size=5); 69 | color(axiscolors[axis]) text3d(text=str(["X","Y","Z"][axis], ["+","-"][neg]), size=5); 70 | } 71 | } 72 | } 73 | } 74 | } 75 | } 76 | 77 | 78 | //rotate(a=180, v=[1,1,0]) 79 | orient_cubes(); 80 | 81 | 82 | 83 | // vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 84 | -------------------------------------------------------------------------------- /phillips_drive.scad: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////// 2 | // LibFile: phillips_drive.scad 3 | // Phillips driver bits 4 | // To use, add these lines to the top of your file: 5 | // ``` 6 | // include 7 | // use 8 | // ``` 9 | ////////////////////////////////////////////////////////////////////// 10 | 11 | /* 12 | BSD 2-Clause License 13 | 14 | Copyright (c) 2017, Revar Desmera 15 | All rights reserved. 16 | 17 | Redistribution and use in source and binary forms, with or without 18 | modification, are permitted provided that the following conditions are met: 19 | 20 | * Redistributions of source code must retain the above copyright notice, this 21 | list of conditions and the following disclaimer. 22 | 23 | * Redistributions in binary form must reproduce the above copyright notice, 24 | this list of conditions and the following disclaimer in the documentation 25 | and/or other materials provided with the distribution. 26 | 27 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 28 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 29 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 30 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 31 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 32 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 33 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 34 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 35 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 36 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 37 | */ 38 | 39 | 40 | use 41 | use 42 | include 43 | include 44 | 45 | 46 | // Section: Modules 47 | 48 | 49 | // Module: phillips_drive() 50 | // Description: Creates a model of a phillips driver bit of a given named size. 51 | // Arguments: 52 | // size = The size of the bit. "#1", "#2", or "#3" 53 | // shaft = The diameter of the drive bit's shaft. 54 | // l = The length of the drive bit. 55 | // Example: 56 | // xdistribute(10) { 57 | // phillips_drive(size="#1", shaft=4, l=20); 58 | // phillips_drive(size="#2", shaft=6, l=20); 59 | // phillips_drive(size="#3", shaft=6, l=20); 60 | // } 61 | module phillips_drive(size="#2", shaft=6, l=20, orient=ORIENT_Z, align=V_UP) { 62 | // These are my best guess reverse-engineered measurements of 63 | // the tip diameters of various phillips screwdriver sizes. 64 | ang = 11; 65 | rads = [["#1", 1.25], ["#2", 1.77], ["#3", 2.65]]; 66 | radidx = search([size], rads)[0]; 67 | r = radidx == []? 0 : rads[radidx][1]; 68 | h = (r/2)/tan(ang); 69 | cr = r/2; 70 | orient_and_align([shaft, shaft, l], orient, align) { 71 | down(l/2) { 72 | difference() { 73 | intersection() { 74 | union() { 75 | clip = (shaft-1.2*r)/2/tan(26.5); 76 | zrot(360/8/2) cylinder(h=clip, d1=1.2*r/cos(360/8/2), d2=shaft/cos(360/8/2), center=false, $fn=8); 77 | up(clip-0.01) cylinder(h=l-clip, d=shaft, center=false, $fn=24); 78 | } 79 | cylinder(d=shaft, h=l, center=false, $fn=24); 80 | } 81 | zrot(45) 82 | zring(n=4) { 83 | yrot(ang) { 84 | zrot(-45) { 85 | off = (r/2-cr*(sqrt(2)-1))/sqrt(2); 86 | translate([off, off, 0]) { 87 | linear_extrude(height=l*2, convexity=4) { 88 | difference() { 89 | union() { 90 | square([shaft, shaft], center=false); 91 | back(cr) zrot(1.125) square([shaft, shaft], center=false); 92 | right(cr) zrot(-1.125) square([shaft, shaft], center=false); 93 | } 94 | difference() { 95 | square([cr*2, cr*2], center=true); 96 | translate([cr,cr,0]) circle(r=cr, $fn=8); 97 | } 98 | } 99 | } 100 | } 101 | } 102 | } 103 | } 104 | } 105 | } 106 | } 107 | } 108 | 109 | 110 | // vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 111 | -------------------------------------------------------------------------------- /examples/bezier_patches.scad: -------------------------------------------------------------------------------- 1 | include 2 | use 3 | use 4 | use 5 | 6 | 7 | function CR_corner(size, orient=[0,0,0], trans=[0,0,0]) = 8 | let ( 9 | r = 0.4, 10 | k = r/2, 11 | // I know this patch is not yet correct for continuous 12 | // rounding, but it's a first approximation proof of concept. 13 | // Currently this is a degree 4 triangular patch. 14 | patch = [ 15 | [[0,1,1], [0,r,1], [0,0,1], [r,0,1], [1,0,1]], 16 | [[0,1,r], [0,k,k], [k,0,k], [1,0,r]], 17 | [[0,1,0], [k,k,0], [1,0,0]], 18 | [[r,1,0], [1,r,0]], 19 | [[1,1,0]] 20 | ] 21 | ) [for (row=patch) 22 | translate_points(v=trans, 23 | rotate_points3d(v=orient, 24 | scale_points(v=size, row) 25 | ) 26 | ) 27 | ]; 28 | 29 | 30 | function CR_edge(size, orient=[0,0,0], trans=[0,0,0]) = 31 | let ( 32 | r = 0.4, 33 | a = -1/2, 34 | b = -1/4, 35 | c = 1/4, 36 | d = 1/2, 37 | // I know this patch is not yet correct for continuous 38 | // rounding, but it's a first approximation proof of concept. 39 | // Currently this is a degree 4 rectangular patch. 40 | patch = [ 41 | [[1,0,a], [1,0,b], [1,0,0], [1,0,c], [1,0,d]], 42 | [[r,0,a], [r,0,b], [r,0,0], [r,0,c], [r,0,d]], 43 | [[0,0,a], [0,0,b], [0,0,0], [0,0,c], [0,0,d]], 44 | [[0,r,a], [0,r,b], [0,r,0], [0,r,c], [0,r,d]], 45 | [[0,1,a], [0,1,b], [0,1,0], [0,1,c], [0,1,d]] 46 | ] 47 | ) [for (row=patch) 48 | translate_points(v=trans, 49 | rotate_points3d(v=orient, 50 | scale_points(v=size, row) 51 | ) 52 | ) 53 | ]; 54 | 55 | 56 | module CR_cube(size=[100,100,100], r=10, splinesteps=8, cheat=false) 57 | { 58 | s = size-2*[r,r,r]; 59 | h = size/2; 60 | corners = [ 61 | CR_corner([r,r,r], orient=ORIENT_Z, trans=[-size.x/2, -size.y/2, -size.z/2]), 62 | CR_corner([r,r,r], orient=ORIENT_Z_90, trans=[ size.x/2, -size.y/2, -size.z/2]), 63 | CR_corner([r,r,r], orient=ORIENT_Z_180, trans=[ size.x/2, size.y/2, -size.z/2]), 64 | CR_corner([r,r,r], orient=ORIENT_Z_270, trans=[-size.x/2, size.y/2, -size.z/2]), 65 | 66 | CR_corner([r,r,r], orient=ORIENT_ZNEG, trans=[ size.x/2, -size.y/2, size.z/2]), 67 | CR_corner([r,r,r], orient=ORIENT_ZNEG_90, trans=[-size.x/2, -size.y/2, size.z/2]), 68 | CR_corner([r,r,r], orient=ORIENT_ZNEG_180, trans=[-size.x/2, size.y/2, size.z/2]), 69 | CR_corner([r,r,r], orient=ORIENT_ZNEG_270, trans=[ size.x/2, size.y/2, size.z/2]) 70 | ]; 71 | edges = [ 72 | CR_edge([r, r, s.x], orient=ORIENT_X, trans=[ 0, -h.y, -h.z]), 73 | CR_edge([r, r, s.x], orient=ORIENT_X_90, trans=[ 0, h.y, -h.z]), 74 | CR_edge([r, r, s.x], orient=ORIENT_X_180, trans=[ 0, h.y, h.z]), 75 | CR_edge([r, r, s.x], orient=ORIENT_X_270, trans=[ 0, -h.y, h.z]), 76 | 77 | CR_edge([r, r, s.y], orient=ORIENT_Y, trans=[ h.x, 0, -h.z]), 78 | CR_edge([r, r, s.y], orient=ORIENT_Y_90, trans=[-h.x, 0, -h.z]), 79 | CR_edge([r, r, s.y], orient=ORIENT_Y_180, trans=[-h.x, 0, h.z]), 80 | CR_edge([r, r, s.y], orient=ORIENT_Y_270, trans=[ h.x, 0, h.z]), 81 | 82 | CR_edge([r, r, s.z], orient=ORIENT_Z, trans=[-h.x, -h.y, 0]), 83 | CR_edge([r, r, s.z], orient=ORIENT_Z_90, trans=[ h.x, -h.y, 0]), 84 | CR_edge([r, r, s.z], orient=ORIENT_Z_180, trans=[ h.x, h.y, 0]), 85 | CR_edge([r, r, s.z], orient=ORIENT_Z_270, trans=[-h.x, h.y, 0]) 86 | ]; 87 | faces = [ 88 | // Yes, these are degree 1 bezier patches. That means just the four corner points. 89 | // Since these are flat, it doesn't matter what degree they are, and this will reduce calculation overhead. 90 | bezier_patch_flat([s.y, s.z], N=1, orient=ORIENT_X, trans=[ h.x, 0, 0]), 91 | bezier_patch_flat([s.y, s.z], N=1, orient=ORIENT_XNEG, trans=[-h.x, 0, 0]), 92 | 93 | bezier_patch_flat([s.x, s.z], N=1, orient=ORIENT_Y, trans=[ 0, h.y, 0]), 94 | bezier_patch_flat([s.x, s.z], N=1, orient=ORIENT_YNEG, trans=[ 0, -h.y, 0]), 95 | 96 | bezier_patch_flat([s.x, s.y], N=1, orient=ORIENT_Z, trans=[ 0, 0, h.z]), 97 | bezier_patch_flat([s.x, s.y], N=1, orient=ORIENT_ZNEG, trans=[ 0, 0, -h.z]) 98 | ]; 99 | // Generating all the patches above took about 0.05 secs. 100 | 101 | if (cheat) { 102 | // Hulling just the corners takes less than a second. 103 | hull() bezier_polyhedron(tris=corners, splinesteps=splinesteps); 104 | } else { 105 | // Generating the polyhedron fully from bezier patches takes 3 seconds on my laptop. 106 | bezier_polyhedron(patches=concat(edges, faces), tris=corners, splinesteps=splinesteps); 107 | } 108 | } 109 | 110 | 111 | CR_cube(size=[100,100,100], r=20, splinesteps=9, cheat=false); 112 | cube(1); 113 | 114 | 115 | 116 | // vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 117 | -------------------------------------------------------------------------------- /wiring.scad: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////// 2 | // LibFile: wiring.scad 3 | // Rendering for wiring bundles 4 | // To use, include the following line at the top of your file: 5 | // ``` 6 | // use 7 | // ``` 8 | ////////////////////////////////////////////////////////////////////// 9 | 10 | /* 11 | BSD 2-Clause License 12 | 13 | Copyright (c) 2017, Revar Desmera 14 | All rights reserved. 15 | 16 | Redistribution and use in source and binary forms, with or without 17 | modification, are permitted provided that the following conditions are met: 18 | 19 | * Redistributions of source code must retain the above copyright notice, this 20 | list of conditions and the following disclaimer. 21 | 22 | * Redistributions in binary form must reproduce the above copyright notice, 23 | this list of conditions and the following disclaimer in the documentation 24 | and/or other materials provided with the distribution. 25 | 26 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 27 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 28 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 29 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 30 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 31 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 32 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 33 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 34 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 35 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 36 | */ 37 | 38 | 39 | include 40 | include 41 | include 42 | 43 | 44 | // Section: Functions 45 | 46 | 47 | // Function: hex_offset_ring() 48 | // Description: 49 | // Returns a hexagonal ring of points, with a spacing of `d`. 50 | // If `lev=0`, returns a single point at `[0,0]`. All greater 51 | // levels return 6 times `lev` points. 52 | // Usage: 53 | // hex_offset_ring(d, lev) 54 | // Arguments: 55 | // d = Base unit diameter to build rings upon. 56 | // lev = How many rings to produce. 57 | // Example: 58 | // hex_offset_ring(d=1, lev=3); // Returns a hex ring of 18 points. 59 | function hex_offset_ring(d, lev=0) = 60 | (lev == 0)? [[0,0]] : [ 61 | for ( 62 | sideang = [0:60:359.999], 63 | sidenum = [1:lev] 64 | ) [ 65 | lev*d*cos(sideang)+sidenum*d*cos(sideang+120), 66 | lev*d*sin(sideang)+sidenum*d*sin(sideang+120) 67 | ] 68 | ]; 69 | 70 | 71 | // Function: hex_offsets() 72 | // Description: 73 | // Returns the centerpoints for the optimal hexagonal packing 74 | // of at least `n` circular items, of diameter `d`. Will return 75 | // enough points to fill out the last ring, even if that is more 76 | // than `n` points. 77 | // Usage: 78 | // hex_offsets(n, d) 79 | // Arguments: 80 | // n = Number of items to bundle. 81 | // d = How far to space each point away from others. 82 | function hex_offsets(n, d, lev=0, arr=[]) = 83 | (len(arr) >= n)? arr : 84 | hex_offsets( 85 | n=n, 86 | d=d, 87 | lev=lev+1, 88 | arr=concat(arr, hex_offset_ring(d, lev=lev)) 89 | ); 90 | 91 | 92 | 93 | // Section: Modules 94 | 95 | 96 | // Module: wiring() 97 | // Description: 98 | // Returns a 3D object representing a bundle of wires that follow a given path, 99 | // with the corners filleted to a given radius. There are 17 base wire colors. 100 | // If you have more than 17 wires, colors will get re-used. 101 | // Usage: 102 | // wiring(path, wires, [wirediam], [fillet], [wirenum], [bezsteps]); 103 | // Arguments: 104 | // path = The 3D polyline path that the wire bundle should follow. 105 | // wires = The number of wires in the wiring bundle. 106 | // wirediam = The diameter of each wire in the bundle. 107 | // fillet = The radius that the path corners will be filleted to. 108 | // wirenum = The first wire's offset into the color table. 109 | // bezsteps = The corner fillets in the path will be converted into this number of segments. 110 | // Example: 111 | // wiring([[50,0,-50], [50,50,-50], [0,50,-50], [0,0,-50], [0,0,0]], fillet=10, wires=13); 112 | module wiring(path, wires, wirediam=2, fillet=10, wirenum=0, bezsteps=12) { 113 | colors = [ 114 | [0.2, 0.2, 0.2], [1.0, 0.2, 0.2], [0.0, 0.8, 0.0], [1.0, 1.0, 0.2], 115 | [0.3, 0.3, 1.0], [1.0, 1.0, 1.0], [0.7, 0.5, 0.0], [0.5, 0.5, 0.5], 116 | [0.2, 0.9, 0.9], [0.8, 0.0, 0.8], [0.0, 0.6, 0.6], [1.0, 0.7, 0.7], 117 | [1.0, 0.5, 1.0], [0.5, 0.6, 0.0], [1.0, 0.7, 0.0], [0.7, 1.0, 0.5], 118 | [0.6, 0.6, 1.0], 119 | ]; 120 | offsets = hex_offsets(wires, wirediam); 121 | bezpath = fillet_path(path, fillet); 122 | poly = simplify3d_path(path3d(bezier_polyline(bezpath, bezsteps))); 123 | n = max(segs(wirediam), 8); 124 | r = wirediam/2; 125 | for (i = [0:wires-1]) { 126 | extpath = [for (j = [0:n-1]) let(a=j*360/n) [r*cos(a)+offsets[i][0], r*sin(a)+offsets[i][1]]]; 127 | color(colors[(i+wirenum)%len(colors)]) { 128 | extrude_2dpath_along_3dpath(extpath, poly); 129 | } 130 | } 131 | } 132 | 133 | 134 | 135 | // vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 136 | -------------------------------------------------------------------------------- /torx_drive.scad: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////// 2 | // LibFile: torx_drive.scad 3 | // Torx driver bits 4 | // To use, add these lines to the top of your file: 5 | // ``` 6 | // include 7 | // use 8 | // ``` 9 | ////////////////////////////////////////////////////////////////////// 10 | 11 | /* 12 | BSD 2-Clause License 13 | 14 | Copyright (c) 2017, Revar Desmera 15 | All rights reserved. 16 | 17 | Redistribution and use in source and binary forms, with or without 18 | modification, are permitted provided that the following conditions are met: 19 | 20 | * Redistributions of source code must retain the above copyright notice, this 21 | list of conditions and the following disclaimer. 22 | 23 | * Redistributions in binary form must reproduce the above copyright notice, 24 | this list of conditions and the following disclaimer in the documentation 25 | and/or other materials provided with the distribution. 26 | 27 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 28 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 29 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 30 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 31 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 32 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 33 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 34 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 35 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 36 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 37 | */ 38 | 39 | 40 | use 41 | use 42 | include 43 | include 44 | 45 | 46 | // Section: Functions 47 | 48 | 49 | // Function: torx_outer_diam() 50 | // Description: Get the typical outer diameter of Torx profile. 51 | // Arguments: 52 | // size = Torx size. 53 | function torx_outer_diam(size) = lookup(size, [ 54 | [ 6, 1.75], 55 | [ 8, 2.40], 56 | [ 10, 2.80], 57 | [ 15, 3.35], 58 | [ 20, 3.95], 59 | [ 25, 4.50], 60 | [ 30, 5.60], 61 | [ 40, 6.75], 62 | [ 45, 7.93], 63 | [ 50, 8.95], 64 | [ 55, 11.35], 65 | [ 60, 13.45], 66 | [ 70, 15.70], 67 | [ 80, 17.75], 68 | [ 90, 20.20], 69 | [100, 22.40] 70 | ]); 71 | 72 | 73 | // Function: torx_inner_diam() 74 | // Description: Get typical inner diameter of Torx profile. 75 | // Arguments: 76 | // size = Torx size. 77 | function torx_inner_diam(size) = lookup(size, [ 78 | [ 6, 1.27], 79 | [ 8, 1.75], 80 | [ 10, 2.05], 81 | [ 15, 2.40], 82 | [ 20, 2.85], 83 | [ 25, 3.25], 84 | [ 30, 4.05], 85 | [ 40, 4.85], 86 | [ 45, 5.64], 87 | [ 50, 6.45], 88 | [ 55, 8.05], 89 | [ 60, 9.60], 90 | [ 70, 11.20], 91 | [ 80, 12.80], 92 | [ 90, 14.40], 93 | [100, 16.00] 94 | ]); 95 | 96 | 97 | // Function: torx_depth() 98 | // Description: Gets typical drive hole depth. 99 | // Arguments: 100 | // size = Torx size. 101 | function torx_depth(size) = lookup(size, [ 102 | [ 6, 1.82], 103 | [ 8, 3.05], 104 | [ 10, 3.56], 105 | [ 15, 3.81], 106 | [ 20, 4.07], 107 | [ 25, 4.45], 108 | [ 30, 4.95], 109 | [ 40, 5.59], 110 | [ 45, 6.22], 111 | [ 50, 6.48], 112 | [ 55, 6.73], 113 | [ 60, 8.17], 114 | [ 70, 8.96], 115 | [ 80, 9.90], 116 | [ 90, 10.56], 117 | [100, 11.35] 118 | ]); 119 | 120 | 121 | // Function: torx_tip_radius() 122 | // Description: Gets minor rounding radius of Torx profile. 123 | // Arguments: 124 | // size = Torx size. 125 | function torx_tip_radius(size) = lookup(size, [ 126 | [ 6, 0.132], 127 | [ 8, 0.190], 128 | [ 10, 0.229], 129 | [ 15, 0.267], 130 | [ 20, 0.305], 131 | [ 25, 0.375], 132 | [ 30, 0.451], 133 | [ 40, 0.546], 134 | [ 45, 0.574], 135 | [ 50, 0.775], 136 | [ 55, 0.867], 137 | [ 60, 1.067], 138 | [ 70, 1.194], 139 | [ 80, 1.526], 140 | [ 90, 1.530], 141 | [100, 1.720] 142 | ]); 143 | 144 | 145 | // Function: torx_rounding_radius() 146 | // Description: Gets major rounding radius of Torx profile. 147 | // Arguments: 148 | // size = Torx size. 149 | function torx_rounding_radius(size) = lookup(size, [ 150 | [ 6, 0.383], 151 | [ 8, 0.510], 152 | [ 10, 0.598], 153 | [ 15, 0.716], 154 | [ 20, 0.859], 155 | [ 25, 0.920], 156 | [ 30, 1.194], 157 | [ 40, 1.428], 158 | [ 45, 1.796], 159 | [ 50, 1.816], 160 | [ 55, 2.667], 161 | [ 60, 2.883], 162 | [ 70, 3.477], 163 | [ 80, 3.627], 164 | [ 90, 4.468], 165 | [100, 4.925] 166 | ]); 167 | 168 | 169 | // Section: Modules 170 | 171 | 172 | // Module: torx_drive2d() 173 | // Description: Creates a torx bit 2D profile. 174 | // Arguments: 175 | // size = Torx size. 176 | // Example(2D): 177 | // torx_drive2d(size=30, $fa=1, $fs=1); 178 | module torx_drive2d(size) { 179 | od = torx_outer_diam(size); 180 | id = torx_inner_diam(size); 181 | tip = torx_tip_radius(size); 182 | rounding = torx_rounding_radius(size); 183 | base = od - 2*tip; 184 | $fn = quantup(segs(od/2),12); 185 | difference() { 186 | union() { 187 | circle(d=base); 188 | zring(n=2) { 189 | hull() { 190 | zring(n=3) { 191 | translate([base/2,0,0]) { 192 | circle(r=tip, $fn=$fn/2); 193 | } 194 | } 195 | } 196 | } 197 | } 198 | zring(n=6) { 199 | zrot(180/6) { 200 | translate([id/2+rounding,0,0]) { 201 | circle(r=rounding); 202 | } 203 | } 204 | } 205 | } 206 | } 207 | 208 | 209 | 210 | // Module: torx_drive() 211 | // Description: Creates a torx bit tip. 212 | // Arguments: 213 | // size = Torx size. 214 | // l = Length of bit. 215 | // center = If true, centers bit vertically. 216 | // Examples: 217 | // torx_drive(size=30, l=10, $fa=1, $fs=1); 218 | module torx_drive(size, l=5, center=undef, orient=ORIENT_Z, align=V_UP) { 219 | od = torx_outer_diam(size); 220 | orient_and_align([od, od, l], orient, align, center) { 221 | linear_extrude(height=l, convexity=4, center=true) { 222 | torx_drive2d(size); 223 | } 224 | } 225 | } 226 | 227 | 228 | 229 | // vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 230 | 231 | -------------------------------------------------------------------------------- /linear_bearings.scad: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////// 2 | // LibFile: linear_bearings.scad 3 | // Linear Bearing clips/holders. 4 | // To use, add these lines to the top of your file: 5 | // ``` 6 | // include 7 | // use 8 | // ``` 9 | ////////////////////////////////////////////////////////////////////// 10 | 11 | /* 12 | BSD 2-Clause License 13 | 14 | Copyright (c) 2017, Revar Desmera 15 | All rights reserved. 16 | 17 | Redistribution and use in source and binary forms, with or without 18 | modification, are permitted provided that the following conditions are met: 19 | 20 | * Redistributions of source code must retain the above copyright notice, this 21 | list of conditions and the following disclaimer. 22 | 23 | * Redistributions in binary form must reproduce the above copyright notice, 24 | this list of conditions and the following disclaimer in the documentation 25 | and/or other materials provided with the distribution. 26 | 27 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 28 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 29 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 30 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 31 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 32 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 33 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 34 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 35 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 36 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 37 | */ 38 | 39 | 40 | include 41 | include 42 | 43 | 44 | // Section: Functions 45 | 46 | 47 | // Function: get_lmXuu_bearing_diam() 48 | // Description: Get outside diameter, in mm, of a standard lmXuu bearing. 49 | // Arguments: 50 | // size = Inner size of lmXuu bearing, in mm. 51 | function get_lmXuu_bearing_diam(size) = lookup(size, [ 52 | [ 4.0, 8.0], 53 | [ 5.0, 10.0], 54 | [ 6.0, 12.0], 55 | [ 8.0, 15.0], 56 | [ 10.0, 19.0], 57 | [ 12.0, 21.0], 58 | [ 13.0, 23.0], 59 | [ 16.0, 28.0], 60 | [ 20.0, 32.0], 61 | [ 25.0, 40.0], 62 | [ 30.0, 45.0], 63 | [ 35.0, 52.0], 64 | [ 40.0, 60.0], 65 | [ 50.0, 80.0], 66 | [ 60.0, 90.0], 67 | [ 80.0, 120.0], 68 | [100.0, 150.0] 69 | ]); 70 | 71 | 72 | // Function: get_lmXuu_bearing_length() 73 | // Description: Get length, in mm, of a standard lmXuu bearing. 74 | // Arguments: 75 | // size = Inner size of lmXuu bearing, in mm. 76 | function get_lmXuu_bearing_length(size) = lookup(size, [ 77 | [ 4.0, 12.0], 78 | [ 5.0, 15.0], 79 | [ 6.0, 19.0], 80 | [ 8.0, 24.0], 81 | [ 10.0, 29.0], 82 | [ 12.0, 30.0], 83 | [ 13.0, 32.0], 84 | [ 16.0, 37.0], 85 | [ 20.0, 42.0], 86 | [ 25.0, 59.0], 87 | [ 30.0, 64.0], 88 | [ 35.0, 70.0], 89 | [ 40.0, 80.0], 90 | [ 50.0, 100.0], 91 | [ 60.0, 110.0], 92 | [ 80.0, 140.0], 93 | [100.0, 175.0] 94 | ]); 95 | 96 | 97 | // Module: linear_bearing_housing() 98 | // Description: 99 | // Creates a model of a clamp to hold a generic linear bearing cartridge. 100 | // Arguments: 101 | // d = Diameter of linear bearing. (Default: 15) 102 | // l = Length of linear bearing. (Default: 24) 103 | // tab = Clamp tab height. (Default: 7) 104 | // tabwall = Clamp Tab thickness. (Default: 5) 105 | // wall = Wall thickness of clamp housing. (Default: 3) 106 | // gap = Gap in clamp. (Default: 5) 107 | // screwsize = Size of screw to use to tighten clamp. (Default: 3) 108 | // orient = Orientation of the housing. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_X`. 109 | // align = Alignment of the housing by the axis-negative (size1) end. Use the `V_` constants from `constants.scad`. Default: `V_UP` 110 | // Example: 111 | // linear_bearing_housing(d=19, l=29, wall=2, tab=6, screwsize=2.5); 112 | module linear_bearing_housing(d=15, l=24, tab=7, gap=5, wall=3, tabwall=5, screwsize=3, orient=ORIENT_X, align=V_UP) 113 | { 114 | od = d+2*wall; 115 | ogap = gap+2*tabwall; 116 | tabh = tab/2+od/2*sqrt(2)-ogap/2; 117 | orient_and_align([l, od, od], orient, align, orig_orient=ORIENT_X) { 118 | difference() { 119 | union() { 120 | zrot(90) teardrop(r=od/2,h=l); 121 | up(tabh) cube(size=[l,ogap,tab+0.05], center=true); 122 | down(od/4) cube(size=[l,od,od/2], center=true); 123 | } 124 | zrot(90) teardrop(r=d/2,h=l+0.05); 125 | up((d*sqrt(2)+tab)/2) 126 | cube(size=[l+0.05,gap,d+tab], center=true); 127 | up(tabh) { 128 | fwd(ogap/2-2+0.01) 129 | xrot(90) screw(screwsize=screwsize*1.06, screwlen=ogap, headsize=screwsize*2, headlen=10); 130 | back(ogap/2+0.01) 131 | xrot(90) metric_nut(size=screwsize, hole=false); 132 | } 133 | } 134 | } 135 | } 136 | 137 | 138 | // Module: lmXuu_housing() 139 | // Description: 140 | // Creates a model of a clamp to hold a standard sized lmXuu linear bearing cartridge. 141 | // Arguments: 142 | // size = Standard lmXuu inner size. 143 | // tab = Clamp tab height. Default: 7 144 | // tabwall = Clamp Tab thickness. Default: 5 145 | // wall = Wall thickness of clamp housing. Default: 3 146 | // gap = Gap in clamp. Default: 5 147 | // screwsize = Size of screw to use to tighten clamp. Default: 3 148 | // orient = Orientation of the housing. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_X`. 149 | // align = Alignment of the housing by the axis-negative (size1) end. Use the `V_` constants from `constants.scad`. Default: `V_UP` 150 | // Example: 151 | // lmXuu_housing(size=10, wall=2, tab=6, screwsize=2.5); 152 | module lmXuu_housing(size=8, tab=7, gap=5, wall=3, tabwall=5, screwsize=3, orient=ORIENT_X, align=V_UP) 153 | { 154 | d = get_lmXuu_bearing_diam(size); 155 | l = get_lmXuu_bearing_length(size); 156 | linear_bearing_housing(d=d,l=l,tab=tab,gap=gap,wall=wall,tabwall=tabwall,screwsize=screwsize, orient=orient, align=align); 157 | } 158 | 159 | 160 | // vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 161 | -------------------------------------------------------------------------------- /WRITING_DOCS.md: -------------------------------------------------------------------------------- 1 | # Formatting Comments for Docs 2 | 3 | Documentation and example images are generated automatically from source code comments by the `scripts/docs_gen.py` script. Not all comments are added to the wiki. Just those comment blocks starting with certain keywords: 4 | 5 | - `// LibFile: NAME` 6 | - `// Section: NAME` 7 | - `// Module: NAME` 8 | - `// Function: NAME` 9 | - `// Constant: NAME` 10 | 11 | ## LibFile: 12 | 13 | LibFile blocks can be followed by multiple lines that can be added as markdown text after the header. Indentation is important, as it denotes the end of block. 14 | 15 | ``` 16 | // LibFile: foo.scad 17 | // You can have several lines of markdown formatted text here. 18 | // You just need to make sure that each line is indented, with 19 | // at least three spaces after the comment marker. You can 20 | // denote a paragraph break with a comment line with three 21 | // trailing spaces. 22 | // 23 | // The end of the block is denoted by a line without a comment. 24 | ``` 25 | 26 | ## Section: 27 | 28 | Section blocks can be followed by multiple lines that can be added as markdown text after the header. Indentation is important, as it denotes the end of block. 29 | 30 | Sections can also include Figures; images generated from code that is not shown in a code block. 31 | 32 | ``` 33 | // Section: Foobar 34 | // You can have several lines of markdown formatted text here. 35 | // You just need to make sure that each line is indented with 36 | // at least three spaces after the comment marker. You can 37 | // denote a paragraph break with a comment line with three 38 | // trailing spaces. 39 | // 40 | // The end of the block is denoted by a line without a comment, 41 | // or a line that is unindented after the comment. 42 | // Figure: Figure description 43 | // cylinder(h=100, d1=75, d2=50); 44 | // up(100) cylinder(h=100, d1=50, d2=75); 45 | // Figure(Spin): Animated figure that spins to show all faces. 46 | // cube([10,100,50], center=true); 47 | // cube([100,10,30], center=true); 48 | ``` 49 | 50 | ## CommonCode: 51 | 52 | CommonCode blocks can be used to denote code that can be shared between all of the Figure and Example blocks in the file, without being shown itself. Indentation is important. Less than three spaces indent denotes the end of the block 53 | 54 | ``` 55 | // CommonCode: 56 | // module text3d(text, h=0.01, size=3) { 57 | // linear_extrude(height=h, convexity=10) { 58 | // text(text=text, size=size, valign="center", halign="center"); 59 | // } 60 | // } 61 | ``` 62 | 63 | ## Module:/Function:/Constant: 64 | 65 | Module, Function, and Constant docs blocks all have a similar specific format. Most sub-blocks are optional, except the Module/Function/Constant line, and the Description block. 66 | 67 | Valid sub-blocks are: 68 | 69 | - `Status: DEPRECATED, use blah instead.` - Optional, used to denote deprecation. 70 | - `Usage: Optional Usage Title` - Optional. Multiple allowed. Followed by an indented block of usage patterns. Optional arguments should be in braces like `[opt]`. Alternate args should be separated by a vertical bar like `r|d`. 71 | - `Description:` - Can be single-line or a multi-line block of the description. 72 | - `Arguments:` - Denotes start of an indented block of argument descriptions. Each line has the argument name, a space, an equals, another space, then the description for the argument all on one line. Like `arg = The argument description`. If you really need to explain an argument in longer form, explain it in the Description. 73 | - `Side Effects:` - Denotes the start of a block describing the side effects, such as `$special_var`s that are set. 74 | - `Example:` - Denotes the beginning of a multi-line example code block. 75 | - `Examples:` - Denotes the beginning of a block of examples, where each line will be shows as a separate example with a separate image if needed. 76 | 77 | Modules blocks will generate images for each example block. Function and Constant blocks will only generate images for example blocks if they have `2D` or `3D` tags. Example blocks can have tags added by putting then inside parentheses before the colon. Ie: `Examples(BigFlatSpin):`. 78 | 79 | The full set of optional example tags are: 80 | 81 | - `2D`: Orient camera in a top-down view for showing 2D objects. 82 | - `3D`: Orient camera in an oblique view for showing 3D objects. Used to force an Example sub-block to generate an image in Function and Constant blocks. 83 | - `Spin`: Animate camera orbit around the `[0,1,1]` axis to display all sides of an object. 84 | - `FlatSpin` : Animate camera orbit around the Z axis, above the XY plane. 85 | - `FR`: Force full rendering from OpenSCAD, instead of the normal preview. 86 | - `Small`: Make the image small sized. 87 | - `Med`: Make the image medium sized. 88 | - `Big`: Make the image big sized. 89 | 90 | Indentation is important, as it denotes the end of sub-block. 91 | 92 | ``` 93 | // Module: foo() 94 | // Status: DEPRECATED, use BLAH instead. 95 | // Usage: Optional Usage Description 96 | // foo(foo, bar, [qux]); 97 | // foo(bar, baz, [qux]); 98 | // Usage: Another Optional Usage Description 99 | // foo(foo, flee, flie, [qux]) 100 | // Description: Short description. 101 | // Description: 102 | // A longer, multi-line description. 103 | // All description blocks are added together. 104 | // You _can_ use *markdown* notation as well. 105 | // You can end multi-line blocks by un-indenting the 106 | // next line, or by using a blank comment line like this: 107 | // 108 | // Arguments: 109 | // foo = This is the description of the foo argument. All on one line. 110 | // bar = This is the description of the bar argument. All on one line. 111 | // baz = This is the description of the baz argument. All on one line. 112 | // qux = This is the description of the qux argument. All on one line. 113 | // flee = This is the description of the flee argument. All on one line. 114 | // flie = This is the description of the flie argument. All on one line. 115 | // Side Effects: 116 | // `$floo` gets set to the floo value. 117 | // Examples: Each line below gets its own example block and image. 118 | // foo(foo="a", bar="b"); 119 | // foo(foo="b", baz="c"); 120 | // Example: Multi-line example. 121 | // lst = [ 122 | // "multi-line examples", 123 | // "are shown in one block", 124 | // "with a single image.", 125 | // ]; 126 | // foo(lst, 23, "blah"); 127 | // Example(2D): Example to show as 2D top-down rendering. 128 | // foo(foo="b", baz="c", qux=true); 129 | // Example(Spin): Example orbiting the [0,1,1] axis. 130 | // foo(foo="b", baz="c", qux="full"); 131 | // Example(FlatSpin): Example orbiting the Z axis from above. 132 | // foo(foo="b", baz="c", qux="full2"); 133 | ``` 134 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BOSL 2 | The Belfry OpenScad Library - A library of tools, shapes, and helpers to make OpenScad easier to use. 3 | 4 | This library is a set of useful tools, shapes and manipulators that I developed while working on various 5 | projects, including large ones like the Snappy-Reprap printed 3D printer. 6 | 7 | 8 | ## Installation 9 | 1. Download the .zip or .tar.gz release file for this library. 10 | 2. Unpack it. It should create a `BOSL-v1.0` directory with the library files within it. 11 | 3. Rename the directory to `BOSL`. 12 | 4. Move the `BOSL` directory into the apropriate OpenSCAD library directory for your platform: 13 | - Windows: `My Documents\OpenSCAD\libraries\` 14 | - Linux: `$HOME/.local/share/OpenSCAD/libraries/` 15 | - Mac OS X: `$HOME/Documents/OpenSCAD/libraries/` 16 | 5. Restart OpenSCAD. 17 | 18 | 19 | ## Terminology 20 | For purposes of the BOSL library, the following terms apply: 21 | - **Left**: Towards X- 22 | - **Right**: Towards X+ 23 | - **Front**/**Forward**: Towards Y- 24 | - **Back**/**Behind**: Towards Y+ 25 | - **Bottom**/**Down**/**Below**: Towards Z- 26 | - **Top**/**Up**/**Above**: Towards Z+ 27 | 28 | - **Axis-Positive**: Towards the positive end of the axis the object is oriented on. IE: X+, Y+, or Z+. 29 | - **Axis-Negative**: Towards the negative end of the axis the object is oriented on. IE: X-, Y-, or Z-. 30 | 31 | ## Common Arguments: 32 | 33 | Args | What it is 34 | ------- | ---------------------------------------- 35 | fillet | Radius of rounding for interior or exterior edges. 36 | chamfer | Size of chamfers/bevels for interior or exterior edges. 37 | orient | Axis a part should be oriented along. Given as an XYZ triplet of rotation angles. It is recommended that you use the `ORIENT_` constants from `constants.scad`. Default is usually `ORIENT_Z` for vertical orientation. 38 | align | Side of the origin that the part should be on. Given as a vector away from the origin. It is recommended that you use the `V_` constants from `constants.scad`. Default is usually `V_ZERO` for centered. 39 | 40 | 41 | ## Examples 42 | A lot of the features of this library are to allow shorter, easier-to-read, intent-based coding. For example: 43 | 44 | [`BOSL/transforms.scad`](https://github.com/revarbat/BOSL/wiki/transforms.scad) Examples | Raw OpenSCAD Equivalent 45 | ------------------------------- | ------------------------------- 46 | `up(5)` | `translate([0,0,5])` 47 | `xrot(30,cp=[0,10,20])` | `translate([0,10,20]) rotate([30,0,0]) translate([0,-10,-20])` 48 | `xspread(20,n=3)` | `for (dx=[-20,0,20]) translate([dx,0,0])` 49 | `zring(n=6,r=20)` | `for (zr=[0:5]) rotate([0,0,zr*60]) translate([20,0,0])` 50 | `skew_xy(xa=30,ya=45)` | `multmatrix([[1,0,tan(30),0],[0,1,tan(45),0],[0,0,1,0],[0,0,0,1]])` 51 | 52 | [`BOSL/shapes.scad`](https://github.com/revarbat/BOSL/wiki/shapes.scad) Examples | Raw OpenSCAD Equivalent 53 | ---------------------------------- | ------------------------------- 54 | `upcube([10,20,30]);` | `translate([0,0,15]) cube([10,20,30], center=true);` 55 | `cuboid([20,20,30], fillet=5, edges=EDGES_Z_ALL);` | `minkowski() {cube([10,10,20], center=true); sphere(r=5, $fn=32);}` 56 | `prismoid([30,40],[20,30],h=10);` | `hull() {translate([0,0,0.005]) cube([30,40,0.01], center=true); translate([0,0,9.995]) cube([20,30,0.01],center=true);}` 57 | `xcyl(l=20,d=4);` | `rotate([0,90,0]) cylinder(h=20, d=4, center=true);` 58 | `cyl(l=100, d=40, fillet=5);` | `translate([0,0,50]) minkowski() {cylinder(h=90, d=30, center=true); sphere(r=5);}` 59 | 60 | [`BOSL/masks.scad`](https://github.com/revarbat/BOSL/wiki/masks.scad) Examples | Raw Openscad Equivalent 61 | ----------------------------------- | ------------------------------- 62 | `chamfer_mask_z(l=20,chamfer=5);` | `rotate(45) cube([5*sqrt(2), 5*sqrt(2), 20], center=true);` 63 | `fillet_mask_z(l=20,fillet=5);` | `difference() {cube([10,10,20], center=true); for(dx=[-5,5],dy=[-5,5]) translate([dx,dy,0]) cylinder(h=20.1, r=5, center=true);}` 64 | `fillet_hole_mask(r=30,fillet=5);` | `difference() {cube([70,70,10], center=true); translate([0,0,-5]) rotate_extrude(convexity=4) translate([30,0,0]) circle(r=5);}` 65 | 66 | 67 | ## The Library Files 68 | The library files are as follows: 69 | 70 | ### Commonly Used 71 | - [`transforms.scad`](https://github.com/revarbat/BOSL/wiki/transforms.scad): The most commonly used transformations, manipulations, and shortcuts are in this file. 72 | - [`shapes.scad`](https://github.com/revarbat/BOSL/wiki/shapes.scad): Common useful shapes and structured objects. 73 | - [`masks.scad`](https://github.com/revarbat/BOSL/wiki/masks.scad): Shapes that are useful for masking with `difference()` and `intersect()`. 74 | - [`threading.scad`](https://github.com/revarbat/BOSL/wiki/threading.scad): Modules to make triangular and trapezoidal threaded rods and nuts. 75 | - [`paths.scad`](https://github.com/revarbat/BOSL/wiki/paths.scad): Functions and modules to work with arbitrary 3D paths. 76 | - [`beziers.scad`](https://github.com/revarbat/BOSL/wiki/beziers.scad): Functions and modules to work with bezier curves. 77 | 78 | ### Standard Parts 79 | - [`involute_gears.scad`](https://github.com/revarbat/BOSL/wiki/involute_gears.scad): Modules and functions to make involute gears and racks. 80 | - [`joiners.scad`](https://github.com/revarbat/BOSL/wiki/joiners.scad): Modules to make joiner shapes for connecting separately printed objects. 81 | - [`sliders.scad`](https://github.com/revarbat/BOSL/wiki/sliders.scad): Modules for creating simple sliders and rails. 82 | - [`metric_screws.scad`](https://github.com/revarbat/BOSL/wiki/metric_screws.scad): Functions and modules to make metric screws, nuts, and screwholes. 83 | - [`linear_bearings.scad`](https://github.com/revarbat/BOSL/wiki/linear_bearings.scad): Modules to make mounts for LMxUU style linear bearings. 84 | - [`nema_steppers.scad`](https://github.com/revarbat/BOSL/wiki/nema_steppers.scad): Modules to make mounting holes for NEMA motors. 85 | - [`phillips_drive.scad`](https://github.com/revarbat/BOSL/wiki/phillips_drive.scad): Modules to create Phillips screwdriver tips. 86 | - [`torx_drive.scad`](https://github.com/revarbat/BOSL/wiki/torx_drive.scad): Functions and Modules to create Torx bit drive holes. 87 | - [`wiring.scad`](https://github.com/revarbat/BOSL/wiki/wiring.scad): Modules to render routed bundles of wires. 88 | 89 | ### Miscellaneous 90 | - [`math.scad`](https://github.com/revarbat/BOSL/wiki/math.scad): Useful helper functions. 91 | - [`constants.scad`](https://github.com/revarbat/BOSL/wiki/constants.scad): Useful constants for vectors, edges, etc. 92 | - [`quaternions.scad`](https://github.com/revarbat/BOSL/wiki/quaternions.scad): Functions to work with quaternion rotations. 93 | - [`debug.scad`](https://github.com/revarbat/BOSL/wiki/debug.scad): Modules to help debug creation of beziers, `polygons()`s and `polyhedron()`s 94 | 95 | ## Documentation 96 | The full library docs can be found at https://github.com/revarbat/BOSL/wiki 97 | 98 | -------------------------------------------------------------------------------- /debug.scad: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////// 2 | // LibFile: debug.scad 3 | // Helpers to make debugging OpenScad code easier. 4 | // To use, add the following lines to the beginning of your file: 5 | // ``` 6 | // include 7 | // use 8 | // ``` 9 | ////////////////////////////////////////////////////////////////////// 10 | 11 | /* 12 | BSD 2-Clause License 13 | 14 | Copyright (c) 2017, Revar Desmera 15 | All rights reserved. 16 | 17 | Redistribution and use in source and binary forms, with or without 18 | modification, are permitted provided that the following conditions are met: 19 | 20 | * Redistributions of source code must retain the above copyright notice, this 21 | list of conditions and the following disclaimer. 22 | 23 | * Redistributions in binary form must reproduce the above copyright notice, 24 | this list of conditions and the following disclaimer in the documentation 25 | and/or other materials provided with the distribution. 26 | 27 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 28 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 29 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 30 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 31 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 32 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 33 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 34 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 35 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 36 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 37 | */ 38 | 39 | include 40 | include 41 | include 42 | include 43 | 44 | 45 | // Section: Debugging Polyhedrons 46 | 47 | 48 | // Module: debug_vertices() 49 | // Description: 50 | // Draws all the vertices in an array, at their 3D position, numbered by their 51 | // position in the vertex array. Also draws any children of this module with 52 | // transparency. 53 | // Arguments: 54 | // vertices = Array of point vertices. 55 | // size = The size of the text used to label the vertices. 56 | // disabled = If true, don't draw numbers, and draw children without transparency. Default = false. 57 | // Example: 58 | // verts = [for (z=[-10,10], y=[-10,10], x=[-10,10]) [x,y,z]]; 59 | // faces = [[0,1,2], [1,3,2], [0,4,5], [0,5,1], [1,5,7], [1,7,3], [3,7,6], [3,6,2], [2,6,4], [2,4,0], [4,6,7], [4,7,5]]; 60 | // debug_vertices(vertices=verts, size=2) { 61 | // polyhedron(points=verts, faces=faces); 62 | // } 63 | module debug_vertices(vertices, size=1, disabled=false) { 64 | if (!disabled) { 65 | echo(vertices=vertices); 66 | color("blue") { 67 | for (i = [0:len(vertices)-1]) { 68 | v = vertices[i]; 69 | translate(v) { 70 | up(size/8) zrot($vpr[2]) xrot(90) { 71 | linear_extrude(height=size/10, center=true, convexity=10) { 72 | text(text=str(i), size=size, halign="center"); 73 | } 74 | } 75 | sphere(size/10); 76 | } 77 | } 78 | } 79 | } 80 | if ($children > 0) { 81 | if (!disabled) { 82 | color([0.2, 1.0, 0, 0.5]) children(); 83 | } else { 84 | children(); 85 | } 86 | } 87 | } 88 | 89 | 90 | 91 | // Module: debug_faces() 92 | // Description: 93 | // Draws all the vertices at their 3D position, numbered in blue by their 94 | // position in the vertex array. Each face will have their face number drawn 95 | // in red, aligned with the center of face. All children of this module are drawn 96 | // with transparency. 97 | // Arguments: 98 | // vertices = Array of point vertices. 99 | // faces = Array of faces by vertex numbers. 100 | // size = The size of the text used to label the faces and vertices. 101 | // disabled = If true, don't draw numbers, and draw children without transparency. Default = false. 102 | // Example: 103 | // verts = [for (z=[-10,10], y=[-10,10], x=[-10,10]) [x,y,z]]; 104 | // faces = [[0,1,2], [1,3,2], [0,4,5], [0,5,1], [1,5,7], [1,7,3], [3,7,6], [3,6,2], [2,6,4], [2,4,0], [4,6,7], [4,7,5]]; 105 | // debug_faces(vertices=verts, faces=faces, size=2) { 106 | // polyhedron(points=verts, faces=faces); 107 | // } 108 | module debug_faces(vertices, faces, size=1, disabled=false) { 109 | if (!disabled) { 110 | vlen = len(vertices); 111 | color("red") { 112 | for (i = [0:len(faces)-1]) { 113 | face = faces[i]; 114 | if (face[0] < 0 || face[1] < 0 || face[2] < 0 || face[0] >= vlen || face[1] >= vlen || face[2] >= vlen) { 115 | echo("BAD FACE: ", vlen=vlen, face=face); 116 | } else { 117 | v0 = vertices[face[0]]; 118 | v1 = vertices[face[1]]; 119 | v2 = vertices[face[2]]; 120 | c = (v0 + v1 + v2) / 3; 121 | dv0 = normalize(v1 - v0); 122 | dv1 = normalize(v2 - v0); 123 | nrm0 = normalize(cross(dv0, dv1)); 124 | nrm1 = [0, 0, 1]; 125 | axis = normalize(cross(nrm0, nrm1)); 126 | ang = vector_angle(nrm0, nrm1); 127 | theta = atan2(nrm0[1], nrm0[0]); 128 | translate(c) { 129 | rotate(a=180-ang, v=axis) { 130 | zrot(theta-90) 131 | linear_extrude(height=size/10, center=true, convexity=10) { 132 | union() { 133 | text(text=str(i), size=size, halign="center"); 134 | text(text=str("_"), size=size, halign="center"); 135 | } 136 | } 137 | } 138 | } 139 | } 140 | } 141 | } 142 | } 143 | debug_vertices(vertices, size=size, disabled=disabled) { 144 | children(); 145 | } 146 | if (!disabled) { 147 | echo(faces=faces); 148 | } 149 | } 150 | 151 | 152 | 153 | // Module: debug_polyhedron() 154 | // Description: 155 | // A drop-in module to replace `polyhedron()` and help debug vertices and faces. 156 | // Draws all the vertices at their 3D position, numbered in blue by their 157 | // position in the vertex array. Each face will have their face number drawn 158 | // in red, aligned with the center of face. All given faces are drawn with 159 | // transparency. All children of this module are drawn with transparency. 160 | // Works best with Thrown-Together preview mode, to see reversed faces. 161 | // Arguments: 162 | // vertices = Array of point vertices. 163 | // faces = Array of faces by vertex numbers. 164 | // txtsize = The size of the text used to label the faces and vertices. 165 | // disabled = If true, act exactly like `polyhedron()`. Default = false. 166 | // Example: 167 | // verts = [for (z=[-10,10], a=[0:120:359.9]) [10*cos(a),10*sin(a),z]]; 168 | // faces = [[0,1,2], [5,4,3], [0,3,4], [0,4,1], [1,4,5], [1,5,2], [2,5,3], [2,3,0]]; 169 | // debug_polyhedron(points=verts, faces=faces, txtsize=1); 170 | module debug_polyhedron(points, faces, convexity=10, txtsize=1, disabled=false) { 171 | debug_faces(vertices=points, faces=faces, size=txtsize, disabled=disabled) { 172 | polyhedron(points=points, faces=faces, convexity=convexity); 173 | } 174 | } 175 | 176 | 177 | 178 | // vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 179 | -------------------------------------------------------------------------------- /sliders.scad: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////// 2 | // LibFile: sliders.scad 3 | // Simple V-groove based sliders and rails. 4 | // To use, add these lines to the beginning of your file: 5 | // ``` 6 | // include 7 | // use 8 | // ``` 9 | ////////////////////////////////////////////////////////////////////// 10 | 11 | /* 12 | BSD 2-Clause License 13 | 14 | Copyright (c) 2017, Revar Desmera 15 | All rights reserved. 16 | 17 | Redistribution and use in source and binary forms, with or without 18 | modification, are permitted provided that the following conditions are met: 19 | 20 | * Redistributions of source code must retain the above copyright notice, this 21 | list of conditions and the following disclaimer. 22 | 23 | * Redistributions in binary form must reproduce the above copyright notice, 24 | this list of conditions and the following disclaimer in the documentation 25 | and/or other materials provided with the distribution. 26 | 27 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 28 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 29 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 30 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 31 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 32 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 33 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 34 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 35 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 36 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 37 | */ 38 | 39 | use 40 | use 41 | include 42 | include 43 | 44 | 45 | // Section: Modules 46 | 47 | 48 | // Module: slider() 49 | // Description: 50 | // Creates a slider to match a V-groove rail. 51 | // Usage: 52 | // slider(l, w, h, [base], [wall], [ang], [slop], [orient], [align]) 53 | // Arguments: 54 | // l = Length (long axis) of slider. 55 | // w = Width of slider. 56 | // h = Height of slider. 57 | // chamfer = Size of chamfer 58 | // base = Height of slider base. 59 | // wall = Width of wall behind each side of the slider. 60 | // ang = Overhang angle for slider, to facilitate supportless printig. 61 | // slop = Printer-specific slop value to make parts fit exactly. 62 | // orient = Orientation of the slider. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Y`. 63 | // align = Alignment of the slider. Use the `V_` constants from `constants.scad`. Default: `V_UP`. 64 | // Example: 65 | // slider(l=30, base=10, wall=4, slop=0.2, orient=ORIENT_Y); 66 | module slider(l=30, w=10, h=10, chamfer=2, base=10, wall=5, ang=30, slop=PRINTER_SLOP, orient=ORIENT_Y, align=V_UP) 67 | { 68 | full_width = w + 2*wall; 69 | full_height = h + base; 70 | 71 | orient_and_align([full_width, l, h+2*base], orient, align, orig_orient=ORIENT_Y) { 72 | down(base+h/2) { 73 | // Base 74 | cuboid([full_width, l, base-slop], chamfer=chamfer, edges=EDGE_TOP_FR+EDGE_TOP_BK+EDGES_Z_ALL, align=V_UP); 75 | 76 | // Wall 77 | xflip_copy(offset=w/2+slop) { 78 | cuboid([wall, l, full_height], chamfer=chamfer, edges=EDGE_TOP_RT+EDGE_FR_RT+EDGE_BK_RT, align=V_UP+V_RIGHT); 79 | } 80 | 81 | // Sliders 82 | up(base+h/2) { 83 | xflip_copy(offset=w/2+slop+0.02) { 84 | bev_h = h/2*tan(ang); 85 | prismoid([l, h], [l-w, 0], h=bev_h+0.01, orient=ORIENT_XNEG, align=V_LEFT); 86 | } 87 | } 88 | } 89 | } 90 | } 91 | 92 | 93 | 94 | // Module: rail() 95 | // Description: 96 | // Creates a V-groove rail. 97 | // Usage: 98 | // rail(l, w, h, [chamfer], [ang], [orient], [align]) 99 | // Arguments: 100 | // l = Length (long axis) of slider. 101 | // w = Width of slider. 102 | // h = Height of slider. 103 | // chamfer = Size of chamfer at end of rail. 104 | // ang = Overhang angle for slider, to facilitate supportless printig. 105 | // orient = Orientation of the rail. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Y`. 106 | // align = Alignment of the rail. Use the `V_` constants from `constants.scad`. Default: `V_UP`. 107 | // Example: 108 | // rail(l=100, w=10, h=10); 109 | module rail(l=30, w=10, h=10, chamfer=1.0, ang=30, orient=ORIENT_Y, align=V_UP) 110 | { 111 | attack_ang = 30; 112 | attack_len = 2; 113 | 114 | fudge = 1.177; 115 | chamf = sqrt(2) * chamfer; 116 | cosa = cos(ang*fudge); 117 | sina = sin(ang*fudge); 118 | 119 | z1 = h/2; 120 | z2 = z1 - chamf * cosa; 121 | z3 = z1 - attack_len * sin(attack_ang); 122 | z4 = 0; 123 | 124 | x1 = w/2; 125 | x2 = x1 - chamf * sina; 126 | x3 = x1 - chamf; 127 | x4 = x1 - attack_len * sin(attack_ang); 128 | x5 = x2 - attack_len * sin(attack_ang); 129 | x6 = x1 - z1 * sina; 130 | x7 = x4 - z1 * sina; 131 | 132 | y1 = l/2; 133 | y2 = y1 - attack_len * cos(attack_ang); 134 | 135 | orient_and_align([w, l, h], orient, align, orig_orient=ORIENT_Y) { 136 | polyhedron( 137 | convexity=4, 138 | points=[ 139 | [-x5, -y1, z3], 140 | [ x5, -y1, z3], 141 | [ x7, -y1, z4], 142 | [ x4, -y1, -z1-0.05], 143 | [-x4, -y1, -z1-0.05], 144 | [-x7, -y1, z4], 145 | 146 | [-x3, -y2, z1], 147 | [ x3, -y2, z1], 148 | [ x2, -y2, z2], 149 | [ x6, -y2, z4], 150 | [ x1, -y2, -z1-0.05], 151 | [-x1, -y2, -z1-0.05], 152 | [-x6, -y2, z4], 153 | [-x2, -y2, z2], 154 | 155 | [ x5, y1, z3], 156 | [-x5, y1, z3], 157 | [-x7, y1, z4], 158 | [-x4, y1, -z1-0.05], 159 | [ x4, y1, -z1-0.05], 160 | [ x7, y1, z4], 161 | 162 | [ x3, y2, z1], 163 | [-x3, y2, z1], 164 | [-x2, y2, z2], 165 | [-x6, y2, z4], 166 | [-x1, y2, -z1-0.05], 167 | [ x1, y2, -z1-0.05], 168 | [ x6, y2, z4], 169 | [ x2, y2, z2], 170 | ], 171 | faces=[ 172 | [0, 1, 2], 173 | [0, 2, 5], 174 | [2, 3, 4], 175 | [2, 4, 5], 176 | 177 | [0, 13, 6], 178 | [0, 6, 7], 179 | [0, 7, 1], 180 | [1, 7, 8], 181 | [1, 8, 9], 182 | [1, 9, 2], 183 | [2, 9, 10], 184 | [2, 10, 3], 185 | [3, 10, 11], 186 | [3, 11, 4], 187 | [4, 11, 12], 188 | [4, 12, 5], 189 | [5, 12, 13], 190 | [5, 13, 0], 191 | 192 | [14, 15, 16], 193 | [14, 16, 19], 194 | [16, 17, 18], 195 | [16, 18, 19], 196 | 197 | [14, 27, 20], 198 | [14, 20, 21], 199 | [14, 21, 15], 200 | [15, 21, 22], 201 | [15, 22, 23], 202 | [15, 23, 16], 203 | [16, 23, 24], 204 | [16, 24, 17], 205 | [17, 24, 25], 206 | [17, 25, 18], 207 | [18, 25, 26], 208 | [18, 26, 19], 209 | [19, 26, 27], 210 | [19, 27, 14], 211 | 212 | [6, 21, 20], 213 | [6, 20, 7], 214 | [7, 20, 27], 215 | [7, 27, 8], 216 | [8, 27, 26], 217 | [8, 26, 9], 218 | [9, 26, 25], 219 | [9, 25, 10], 220 | [10, 25, 24], 221 | [10, 24, 11], 222 | [11, 24, 23], 223 | [11, 23, 12], 224 | [12, 23, 22], 225 | [12, 22, 13], 226 | [13, 22, 21], 227 | [13, 21, 6], 228 | ] 229 | ); 230 | } 231 | } 232 | 233 | 234 | 235 | // vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 236 | -------------------------------------------------------------------------------- /convex_hull.scad: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////// 2 | // LibFile: convex_hull.scad 3 | // Functions to create 2D and 3D convex hulls. 4 | // To use, add the following line to the beginning of your file: 5 | // ``` 6 | // include 7 | // ``` 8 | // Derived from Linde's Hull: 9 | // - https://github.com/openscad/scad-utils 10 | ////////////////////////////////////////////////////////////////////// 11 | 12 | include 13 | 14 | 15 | 16 | // Section: Generalized Hull 17 | 18 | // Function: convex_hull() 19 | // Usage: 20 | // convex_hull(points) 21 | // Description: 22 | // When given a list of 3D points, returns a list of faces for 23 | // the minimal convex hull polyhedron of those points. Each face 24 | // is a list of indexes into `points`. 25 | // When given a list of 2D points, or 3D points that are all 26 | // coplanar, returns a list of indices into `points` for the path 27 | // that forms the minimal convex hull polygon of those points. 28 | // Arguments: 29 | // points = The list of points to find the minimal convex hull of. 30 | function convex_hull(points) = 31 | !(len(points) > 0) ? [] : 32 | len(points[0]) == 2 ? convex_hull2d(points) : 33 | len(points[0]) == 3 ? convex_hull3d(points) : []; 34 | 35 | 36 | 37 | // Section: 2D Hull 38 | 39 | // Function: convex_hull2d() 40 | // Usage: 41 | // convex_hull2d(points) 42 | // Description: 43 | // Takes a list of arbitrary 2D points, and finds the minimal convex 44 | // hull polygon to enclose them. Returns a path as a list of indices 45 | // into `points`. 46 | function convex_hull2d(points) = 47 | (len(points) < 3)? [] : let( 48 | a=0, b=1, 49 | c = _find_first_noncollinear([a,b], points, 2) 50 | ) (c == len(points))? _convex_hull_collinear(points) : let( 51 | remaining = [ for (i = [2:len(points)-1]) if (i != c) i ], 52 | ccw = triangle_area2d(points[a], points[b], points[c]) > 0, 53 | polygon = ccw? [a,b,c] : [a,c,b] 54 | ) _convex_hull_iterative_2d(points, polygon, remaining); 55 | 56 | 57 | // Adds the remaining points one by one to the convex hull 58 | function _convex_hull_iterative_2d(points, polygon, remaining, _i=0) = 59 | (_i >= len(remaining))? polygon : let ( 60 | // pick a point 61 | i = remaining[_i], 62 | // find the segments that are in conflict with the point (point not inside) 63 | conflicts = _find_conflicting_segments(points, polygon, points[i]) 64 | // no conflicts, skip point and move on 65 | ) (len(conflicts) == 0)? _convex_hull_iterative_2d(points, polygon, remaining, _i+1) : let( 66 | // find the first conflicting segment and the first not conflicting 67 | // conflict will be sorted, if not wrapping around, do it the easy way 68 | polygon = _remove_conflicts_and_insert_point(polygon, conflicts, i) 69 | ) _convex_hull_iterative_2d(points, polygon, remaining, _i+1); 70 | 71 | 72 | function _find_first_noncollinear(line, points, i) = 73 | (i>=len(points) || !collinear_indexed(points, line[0], line[1], i))? i : 74 | _find_first_noncollinear(line, points, i+1); 75 | 76 | 77 | function _find_conflicting_segments(points, polygon, point) = [ 78 | for (i = [0:len(polygon)-1]) let( 79 | j = (i+1) % len(polygon), 80 | p1 = points[polygon[i]], 81 | p2 = points[polygon[j]], 82 | area = triangle_area2d(p1, p2, point) 83 | ) if (area < 0) i 84 | ]; 85 | 86 | 87 | // remove the conflicting segments from the polygon 88 | function _remove_conflicts_and_insert_point(polygon, conflicts, point) = 89 | (conflicts[0] == 0)? let( 90 | nonconflicting = [ for(i = [0:len(polygon)-1]) if (!in_list(i, conflicts)) i ], 91 | new_indices = concat(nonconflicting, (nonconflicting[len(nonconflicting)-1]+1) % len(polygon)), 92 | polygon = concat([ for (i = new_indices) polygon[i] ], point) 93 | ) polygon : let( 94 | before_conflicts = [ for(i = [0:min(conflicts)]) polygon[i] ], 95 | after_conflicts = (max(conflicts) >= (len(polygon)-1))? [] : [ for(i = [max(conflicts)+1:len(polygon)-1]) polygon[i] ], 96 | polygon = concat(before_conflicts, point, after_conflicts) 97 | ) polygon; 98 | 99 | 100 | 101 | // Section: 3D Hull 102 | 103 | // Function: convex_hull3d() 104 | // Usage: 105 | // convex_hull3d(points) 106 | // Description: 107 | // Takes a list of arbitrary 3D points, and finds the minimal convex 108 | // hull polyhedron to enclose them. Returns a list of faces, where 109 | // each face is a list of indexes into the given `points` list. 110 | // If all points passed to it are coplanar, then the return is the 111 | // list of indices of points forming the minimal convex hull polygon. 112 | function convex_hull3d(points) = 113 | (len(points) < 3)? list_range(len(points)) : let ( 114 | // start with a single triangle 115 | a=0, b=1, c=2, 116 | plane = plane3pt_indexed(points, a, b, c), 117 | d = _find_first_noncoplanar(plane, points, 3) 118 | ) (d == len(points))? /* all coplanar*/ let ( 119 | pts2d = [ for (p = points) xyz_to_planar(p, points[a], points[b], points[c]) ], 120 | hull2d = convex_hull2d(pts2d) 121 | ) hull2d : let( 122 | remaining = [for (i = [3:len(points)-1]) if (i != d) i], 123 | // Build an initial tetrahedron. 124 | // Swap b, c if d is in front of triangle t. 125 | ifop = in_front_of_plane(plane, points[d]), 126 | bc = ifop? [c,b] : [b,c], 127 | b = bc[0], 128 | c = bc[1], 129 | triangles = [ 130 | [a,b,c], 131 | [d,b,a], 132 | [c,d,a], 133 | [b,d,c] 134 | ], 135 | // calculate the plane equations 136 | planes = [ for (t = triangles) plane3pt_indexed(points, t[0], t[1], t[2]) ] 137 | ) _convex_hull_iterative(points, triangles, planes, remaining); 138 | 139 | 140 | // Adds the remaining points one by one to the convex hull 141 | function _convex_hull_iterative(points, triangles, planes, remaining, _i=0) = 142 | _i >= len(remaining) ? triangles : 143 | let ( 144 | // pick a point 145 | i = remaining[_i], 146 | // find the triangles that are in conflict with the point (point not inside) 147 | conflicts = _find_conflicts(points[i], planes), 148 | // for all triangles that are in conflict, collect their halfedges 149 | halfedges = [ 150 | for(c = conflicts, i = [0:2]) let( 151 | j = (i+1)%3 152 | ) [triangles[c][i], triangles[c][j]] 153 | ], 154 | // find the outer perimeter of the set of conflicting triangles 155 | horizon = _remove_internal_edges(halfedges), 156 | // generate a new triangle for each horizon halfedge together with the picked point i 157 | new_triangles = [ for (h = horizon) concat(h,i) ], 158 | // calculate the corresponding plane equations 159 | new_planes = [ for (t = new_triangles) plane3pt_indexed(points, t[0], t[1], t[2]) ] 160 | ) _convex_hull_iterative( 161 | points, 162 | // remove the conflicting triangles and add the new ones 163 | concat(list_remove(triangles, conflicts), new_triangles), 164 | concat(list_remove(planes, conflicts), new_planes), 165 | remaining, 166 | _i+1 167 | ); 168 | 169 | 170 | function _convex_hull_collinear(points) = 171 | let( 172 | a = points[0], 173 | n = points[1] - a, 174 | points1d = [ for(p = points) (p-a)*n ], 175 | min_i = min_index(points1d), 176 | max_i = max_index(points1d) 177 | ) [min_i, max_i]; 178 | 179 | 180 | 181 | function _remove_internal_edges(halfedges) = [ 182 | for (h = halfedges) 183 | if (!in_list(reverse(h), halfedges)) 184 | h 185 | ]; 186 | 187 | 188 | function _find_conflicts(point, planes) = [ 189 | for (i = [0:len(planes)-1]) 190 | if (in_front_of_plane(planes[i], point)) 191 | i 192 | ]; 193 | 194 | 195 | function _find_first_noncoplanar(plane, points, i) = 196 | (i >= len(points) || !coplanar(plane, points[i]))? i : 197 | _find_first_noncoplanar(plane, points, i+1); 198 | 199 | 200 | // vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 201 | -------------------------------------------------------------------------------- /triangulation.scad: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////// 2 | // LibFile: triangulation.scad 3 | // Functions to triangulate polyhedron faces. 4 | // To use, add the following lines to the beginning of your file: 5 | // ``` 6 | // use 7 | // ``` 8 | ////////////////////////////////////////////////////////////////////// 9 | 10 | /* 11 | BSD 2-Clause License 12 | 13 | Copyright (c) 2017, Revar Desmera 14 | All rights reserved. 15 | 16 | Redistribution and use in source and binary forms, with or without 17 | modification, are permitted provided that the following conditions are met: 18 | 19 | * Redistributions of source code must retain the above copyright notice, this 20 | list of conditions and the following disclaimer. 21 | 22 | * Redistributions in binary form must reproduce the above copyright notice, 23 | this list of conditions and the following disclaimer in the documentation 24 | and/or other materials provided with the distribution. 25 | 26 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 27 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 28 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 29 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 30 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 31 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 32 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 33 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 34 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 35 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 36 | */ 37 | 38 | 39 | use 40 | 41 | 42 | // Section: Functions 43 | 44 | 45 | // Function: face_normal() 46 | // Description: 47 | // Given an array of vertices (`points`), and a list of indexes into the 48 | // vertex array (`face`), returns the normal vector of the face. 49 | // Arguments: 50 | // points = Array of vertices for the polyhedron. 51 | // face = The face, given as a list of indices into the vertex array `points`. 52 | function face_normal(points, face) = 53 | let(count=len(face)) 54 | normalize( 55 | sum( 56 | [ 57 | for(i=[0:count-1]) cross( 58 | points[face[(i+1)%count]]-points[face[0]], 59 | points[face[(i+2)%count]]-points[face[(i+1)%count]] 60 | ) 61 | ] 62 | ) 63 | ) 64 | ; 65 | 66 | 67 | // Function: find_convex_vertex() 68 | // Description: 69 | // Returns the index of a convex point on the given face. 70 | // Arguments: 71 | // points = Array of vertices for the polyhedron. 72 | // face = The face, given as a list of indices into the vertex array `points`. 73 | // facenorm = The normal vector of the face. 74 | function find_convex_vertex(points, face, facenorm, i=0) = 75 | let(count=len(face), 76 | p0=points[face[i]], 77 | p1=points[face[(i+1)%count]], 78 | p2=points[face[(i+2)%count]] 79 | ) 80 | (len(face)>i)? 81 | (cross(p1-p0, p2-p1)*facenorm>0)? (i+1)%count : find_convex_vertex(points, face, facenorm, i+1) 82 | : //This should never happen since there is at least 1 convex vertex. 83 | undef 84 | ; 85 | 86 | 87 | // Function: point_in_ear() 88 | // Description: Determine if a point is in a clipable convex ear. 89 | // Arguments: 90 | // points = Array of vertices for the polyhedron. 91 | // face = The face, given as a list of indices into the vertex array `points`. 92 | function point_in_ear(points, face, tests, i=0) = 93 | (iprev[0])? [test, i] : prev 99 | : 100 | [_check_point_in_ear(points[face[i]], tests), i] 101 | ; 102 | 103 | 104 | // Internal non-exposed function. 105 | function _check_point_in_ear(point, tests) = 106 | let( 107 | result=[ 108 | (point*tests[0][0])-tests[0][1], 109 | (point*tests[1][0])-tests[1][1], 110 | (point*tests[2][0])-tests[2][1] 111 | ] 112 | ) 113 | (result[0]>0 && result[1]>0 && result[2]>0)? result[0] : -1 114 | ; 115 | 116 | 117 | // Function: normalize_vertex_perimeter() 118 | // Description: Removes the last item in an array if it is the same as the first item. 119 | // Arguments: 120 | // v = The array to normalize. 121 | function normalize_vertex_perimeter(v) = 122 | (len(v) < 2)? v : 123 | (v[len(v)-1] != v[0])? v : 124 | [for (i=[0:len(v)-2]) v[i]] 125 | ; 126 | 127 | 128 | // Function: is_only_noncolinear_vertex() 129 | // Description: 130 | // Given a face in a polyhedron, and a vertex in that face, returns true 131 | // if that vertex is the only non-colinear vertex in the face. 132 | // Arguments: 133 | // points = Array of vertices for the polyhedron. 134 | // facelist = The face, given as a list of indices into the vertex array `points`. 135 | // vertex = The index into `facelist`, of the vertex to test. 136 | function is_only_noncolinear_vertex(points, facelist, vertex) = 137 | let( 138 | face=select(facelist, vertex+1, vertex-1), 139 | count=len(face) 140 | ) 141 | 0==sum( 142 | [ 143 | for(i=[0:count-1]) norm( 144 | cross( 145 | points[face[(i+1)%count]]-points[face[0]], 146 | points[face[(i+2)%count]]-points[face[(i+1)%count]] 147 | ) 148 | ) 149 | ] 150 | ) 151 | ; 152 | 153 | 154 | // Function: triangulate_face() 155 | // Description: 156 | // Given a face in a polyhedron, subdivides the face into triangular faces. 157 | // Returns an array of faces, where each face is a list of three vertex indices. 158 | // Arguments: 159 | // points = Array of vertices for the polyhedron. 160 | // face = The face, given as a list of indices into the vertex array `points`. 161 | function triangulate_face(points, face) = 162 | let(count=len(face)) 163 | (3==count)? 164 | [face] 165 | : 166 | let( 167 | facenorm=face_normal(points, face), 168 | cv=find_convex_vertex(points, face, facenorm), 169 | pv=(count+cv-1)%count, 170 | nv=(cv+1)%count, 171 | p0=points[face[pv]], 172 | p1=points[face[cv]], 173 | p2=points[face[nv]], 174 | tests=[ 175 | [cross(facenorm, p0-p2), cross(facenorm, p0-p2)*p0], 176 | [cross(facenorm, p1-p0), cross(facenorm, p1-p0)*p1], 177 | [cross(facenorm, p2-p1), cross(facenorm, p2-p1)*p2] 178 | ], 179 | ear_test=point_in_ear(points, face, tests), 180 | clipable_ear=(ear_test[0]<0), 181 | diagonal_point=ear_test[1] 182 | ) 183 | (clipable_ear)? // There is no point inside the ear. 184 | is_only_noncolinear_vertex(points, face, cv)? 185 | // In the point&line degeneracy clip to somewhere in the middle of the line. 186 | flatten([ 187 | triangulate_face(points, select(face, cv, (cv+2)%count)), 188 | triangulate_face(points, select(face, (cv+2)%count, cv)) 189 | ]) 190 | : 191 | // Otherwise the ear is safe to clip. 192 | flatten([ 193 | [select(face, pv, nv)], 194 | triangulate_face(points, select(face, nv, pv)) 195 | ]) 196 | : // If there is a point inside the ear, make a diagonal and clip along that. 197 | flatten([ 198 | triangulate_face(points, select(face, cv, diagonal_point)), 199 | triangulate_face(points, select(face, diagonal_point, cv)) 200 | ]) 201 | ; 202 | 203 | 204 | // Function: triangulate_faces() 205 | // Description: 206 | // Subdivides all faces for the given polyhedron that have more than three vertices. 207 | // Returns an array of faces where each face is a list of three vertex array indices. 208 | // Arguments: 209 | // points = Array of vertices for the polyhedron. 210 | // faces = Array of faces for the polyhedron. Each face is a list of 3 or more indices into the `points` array. 211 | function triangulate_faces(points, faces) = 212 | [ 213 | for (i=[0 : len(faces)-1]) 214 | let(facet = normalize_vertex_perimeter(faces[i])) 215 | for (face = triangulate_face(points, facet)) 216 | if (face[0]!=face[1] && face[1]!=face[2] && face[2]!=face[0]) face 217 | ] 218 | ; 219 | 220 | 221 | // vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 222 | -------------------------------------------------------------------------------- /quaternions.scad: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////// 2 | // LibFile: quaternions.scad 3 | // Support for Quaternions. 4 | // To use, add the following line to the beginning of your file: 5 | // ``` 6 | // use 7 | // ``` 8 | /////////////////////////////////////////// 9 | 10 | /* 11 | BSD 2-Clause License 12 | 13 | Copyright (c) 2017, Revar Desmera 14 | All rights reserved. 15 | 16 | Redistribution and use in source and binary forms, with or without 17 | modification, are permitted provided that the following conditions are met: 18 | 19 | * Redistributions of source code must retain the above copyright notice, this 20 | list of conditions and the following disclaimer. 21 | 22 | * Redistributions in binary form must reproduce the above copyright notice, 23 | this list of conditions and the following disclaimer in the documentation 24 | and/or other materials provided with the distribution. 25 | 26 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 27 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 28 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 29 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 30 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 31 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 32 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 33 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 34 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 35 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 36 | */ 37 | 38 | 39 | use 40 | 41 | 42 | // Section: Quaternions 43 | // Quaternions are fast methods of storing and calculating arbitrary rotations. 44 | // Quaternions contain information on both axis of rotation, and rotation angle. 45 | // You can chain multiple rotation together by multiplying quaternions together. 46 | // They don't suffer from the gimbal-lock issues that [X,Y,Z] rotation angles do. 47 | // Quaternions are stored internally as a 4-value vector: 48 | // `[X, Y, Z, W] = W + Xi + Yj + Zk` 49 | 50 | 51 | // Internal 52 | function _Quat(a,s,w) = [a[0]*s, a[1]*s, a[2]*s, w]; 53 | 54 | 55 | // Function: Quat() 56 | // Usage: 57 | // Quat(ax, ang); 58 | // Description: Create a new Quaternion from axis and angle of rotation. 59 | // Arguments: 60 | // ax = Vector of axis of rotation. 61 | // ang = Number of degrees to rotate around the axis counter-clockwise, when facing the origin. 62 | function Quat(ax=[0,0,1], ang=0) = _Quat(ax/norm(ax), sin(ang/2), cos(ang/2)); 63 | 64 | 65 | // Function: QuatX() 66 | // Usage: 67 | // QuatX(a); 68 | // Description: Create a new Quaternion for rotating around the X axis [1,0,0]. 69 | // Arguments: 70 | // a = Number of degrees to rotate around the axis counter-clockwise, when facing the origin. 71 | function QuatX(a=0) = Quat([1,0,0],a); 72 | 73 | 74 | // Function: QuatY() 75 | // Usage: 76 | // QuatY(a); 77 | // Description: Create a new Quaternion for rotating around the Y axis [0,1,0]. 78 | // Arguments: 79 | // a = Number of degrees to rotate around the axis counter-clockwise, when facing the origin. 80 | function QuatY(a=0) = Quat([0,1,0],a); 81 | 82 | // Function: QuatZ() 83 | // Usage: 84 | // QuatZ(a); 85 | // Description: Create a new Quaternion for rotating around the Z axis [0,0,1]. 86 | // Arguments: 87 | // a = Number of degrees to rotate around the axis counter-clockwise, when facing the origin. 88 | function QuatZ(a=0) = Quat([0,0,1],a); 89 | 90 | 91 | // Function: QuatXYZ() 92 | // Usage: 93 | // QuatXYZ([X,Y,Z]) 94 | // Description: 95 | // Creates a quaternion from standard [X,Y,Z] rotation angles in degrees. 96 | // Arguments: 97 | // a = The triplet of rotation angles, [X,Y,Z] 98 | function QuatXYZ(a=[0,0,0]) = 99 | let( 100 | qx = QuatX(a[0]), 101 | qy = QuatY(a[1]), 102 | qz = QuatZ(a[2]) 103 | ) 104 | Q_Mul(qz, Q_Mul(qy, qx)); 105 | 106 | 107 | // Function: Q_Ident() 108 | // Description: Returns the "Identity" zero-rotation Quaternion. 109 | function Q_Ident() = [0, 0, 0, 1]; 110 | 111 | 112 | // Function: Q_Add_S() 113 | // Usage: 114 | // Q_Add_S(q, s) 115 | // Description: Adds a scalar value `s` to the W part of a quaternion `q`. 116 | function Q_Add_S(q, s) = q+[0,0,0,s]; 117 | 118 | 119 | // Function: Q_Sub_S() 120 | // Usage: 121 | // Q_Sub_S(q, s) 122 | // Description: Subtracts a scalar value `s` from the W part of a quaternion `q`. 123 | function Q_Sub_S(q, s) = q-[0,0,0,s]; 124 | 125 | 126 | // Function: Q_Mul_S() 127 | // Usage: 128 | // Q_Mul_S(q, s) 129 | // Description: Multiplies each part of a quaternion `q` by a scalar value `s`. 130 | function Q_Mul_S(q, s) = q*s; 131 | 132 | 133 | // Function: Q_Div_S() 134 | // Usage: 135 | // Q_Div_S(q, s) 136 | // Description: Divides each part of a quaternion `q` by a scalar value `s`. 137 | function Q_Div_S(q, s) = q/s; 138 | 139 | 140 | // Function: Q_Add() 141 | // Usage: 142 | // Q_Add(a, b) 143 | // Description: Adds each part of two quaternions together. 144 | function Q_Add(a, b) = a+b; 145 | 146 | 147 | // Function: Q_Sub() 148 | // Usage: 149 | // Q_Sub(a, b) 150 | // Description: Subtracts each part of quaternion `b` from quaternion `a`. 151 | function Q_Sub(a, b) = a-b; 152 | 153 | 154 | // Function: Q_Mul() 155 | // Usage: 156 | // Q_Mul(a, b) 157 | // Description: Multiplies quaternion `a` by quaternion `b`. 158 | function Q_Mul(a, b) = [ 159 | a[3]*b.x + a.x*b[3] + a.y*b.z - a.z*b.y, 160 | a[3]*b.y - a.x*b.z + a.y*b[3] + a.z*b.x, 161 | a[3]*b.z + a.x*b.y - a.y*b.x + a.z*b[3], 162 | a[3]*b[3] - a.x*b.x - a.y*b.y - a.z*b.z, 163 | ]; 164 | 165 | 166 | // Function: Q_Dot() 167 | // Usage: 168 | // Q_Dot(a, b) 169 | // Description: Calculates the dot product between quaternions `a` and `b`. 170 | function Q_Dot(a, b) = a[0]*b[0] + a[1]*b[1] + a[2]*b[2] + a[3]*b[3]; 171 | 172 | 173 | // Function: Q_Neg() 174 | // Usage: 175 | // Q_Neg(q) 176 | // Description: Returns the negative of quaternion `q`. 177 | function Q_Neg(q) = -q; 178 | 179 | 180 | // Function: Q_Conj() 181 | // Usage: 182 | // Q_Conj(q) 183 | // Description: Returns the conjugate of quaternion `q`. 184 | function Q_Conj(q) = [-q.x, -q.y, -q.z, q[3]]; 185 | 186 | 187 | // Function: Q_Norm() 188 | // Usage: 189 | // Q_Norm(q) 190 | // Description: Returns the `norm()` "length" of quaternion `q`. 191 | function Q_Norm(q) = norm(q); 192 | 193 | 194 | // Function: Q_Normalize() 195 | // Usage: 196 | // Q_Normalize(q) 197 | // Description: Normalizes quaternion `q`, so that norm([W,X,Y,Z]) == 1. 198 | function Q_Normalize(q) = q/norm(q); 199 | 200 | 201 | // Function: Q_Dist() 202 | // Usage: 203 | // Q_Dist(q1, q2) 204 | // Description: Returns the "distance" between two quaternions. 205 | function Q_Dist(q1, q2) = norm(q2-q1); 206 | 207 | 208 | // Function: Q_Slerp() 209 | // Usage: 210 | // Q_Slerp(q1, q2, u); 211 | // Description: 212 | // Returns a quaternion that is a spherical interpolation between two quaternions. 213 | // Arguments: 214 | // q1 = The first quaternion. (u=0) 215 | // q2 = The second quaternion. (u=1) 216 | // u = The proportional value, from 0 to 1, of what part of the interpolation to return. 217 | // Example(3D): 218 | // a = QuatY(15); 219 | // b = QuatY(75); 220 | // color("blue",0.25) Qrot(a) cylinder(d=1, h=80); 221 | // color("red",0.25) Qrot(b) cylinder(d=1, h=80); 222 | // Qrot(Q_Slerp(a, b, 0.6)) cylinder(d=1, h=80); 223 | function Q_Slerp(q1, q2, u) = let( 224 | dot = Q_Dot(q1, q2), 225 | qq2 = dot<0? Q_Neg(q2) : q2, 226 | dott = dot<0? -dot : dot, 227 | theta = u * acos(constrain(dott,-1,1)) 228 | ) (dott>0.9995)? 229 | Q_Normalize(q1 + ((qq2-q1) * u)) : 230 | (q1*cos(theta) + (Q_Normalize(qq2 - (q1 * dott)) * sin(theta))); 231 | 232 | 233 | // Function: Q_Matrix3() 234 | // Usage: 235 | // Q_Matrix3(q); 236 | // Description: 237 | // Returns the 3x3 rotation matrix for the given normalized quaternion q. 238 | function Q_Matrix3(q) = [ 239 | [1-2*q[1]*q[1]-2*q[2]*q[2], 2*q[0]*q[1]-2*q[2]*q[3], 2*q[0]*q[2]+2*q[1]*q[3]], 240 | [ 2*q[0]*q[1]+2*q[2]*q[3], 1-2*q[0]*q[0]-2*q[2]*q[2], 2*q[1]*q[2]-2*q[0]*q[3]], 241 | [ 2*q[0]*q[2]-2*q[1]*q[3], 2*q[1]*q[2]+2*q[0]*q[3], 1-2*q[0]*q[0]-2*q[1]*q[1]] 242 | ]; 243 | 244 | 245 | // Function: Q_Matrix4() 246 | // Usage: 247 | // Q_Matrix4(q); 248 | // Description: 249 | // Returns the 4x4 rotation matrix for the given normalized quaternion q. 250 | function Q_Matrix4(q) = [ 251 | [1-2*q[1]*q[1]-2*q[2]*q[2], 2*q[0]*q[1]-2*q[2]*q[3], 2*q[0]*q[2]+2*q[1]*q[3], 0], 252 | [ 2*q[0]*q[1]+2*q[2]*q[3], 1-2*q[0]*q[0]-2*q[2]*q[2], 2*q[1]*q[2]-2*q[0]*q[3], 0], 253 | [ 2*q[0]*q[2]-2*q[1]*q[3], 2*q[1]*q[2]+2*q[0]*q[3], 1-2*q[0]*q[0]-2*q[1]*q[1], 0], 254 | [ 0, 0, 0, 1] 255 | ]; 256 | 257 | 258 | // Function: Q_Axis() 259 | // Usage: 260 | // Q_Axis(q) 261 | // Description: 262 | // Returns the axis of rotation of a normalized quaternion `q`. 263 | function Q_Axis(q) = let(d = sqrt(1-(q[3]*q[3]))) (d==0)? [0,0,1] : [q[0]/d, q[1]/d, q[2]/d]; 264 | 265 | 266 | // Function: Q_Angle() 267 | // Usage: 268 | // Q_Angle(q) 269 | // Description: 270 | // Returns the angle of rotation (in degrees) of a normalized quaternion `q`. 271 | function Q_Angle(q) = 2 * acos(q[3]); 272 | 273 | 274 | // Function: Q_Rot_Vector() 275 | // Usage: 276 | // Q_Rot_Vector(v,q); 277 | // Description: 278 | // Returns the vector `v` after rotating it by the quaternion `q`. 279 | function Q_Rot_Vector(v,q) = Q_Mul(Q_Mul(q,concat(v,0)),Q_Conj(q)); 280 | 281 | 282 | // Module: Qrot() 283 | // Usage: 284 | // Qrot(q) ... 285 | // Description: 286 | // Rotate all children by the rotation stored in quaternion `q`. 287 | // Example(FlatSpin): 288 | // q = QuatXYZ([45,35,10]); 289 | // color("red",0.25) cylinder(d=1,h=80); 290 | // Qrot(q) cylinder(d=1,h=80); 291 | module Qrot(q) { 292 | multmatrix(Q_Matrix4(q)) { 293 | children(); 294 | } 295 | } 296 | 297 | 298 | // vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 299 | -------------------------------------------------------------------------------- /compat.scad: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////// 2 | // LibFile: compat.scad 3 | // Backwards Compatability library 4 | // To use, include this line at the top of your file: 5 | // ``` 6 | // use 7 | // ``` 8 | ////////////////////////////////////////////////////////////////////// 9 | 10 | 11 | /* 12 | BSD 2-Clause License 13 | 14 | Copyright (c) 2017, Revar Desmera 15 | All rights reserved. 16 | 17 | Redistribution and use in source and binary forms, with or without 18 | modification, are permitted provided that the following conditions are met: 19 | 20 | * Redistributions of source code must retain the above copyright notice, this 21 | list of conditions and the following disclaimer. 22 | 23 | * Redistributions in binary form must reproduce the above copyright notice, 24 | this list of conditions and the following disclaimer in the documentation 25 | and/or other materials provided with the distribution. 26 | 27 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 28 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 29 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 30 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 31 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 32 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 33 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 34 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 35 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 36 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 37 | */ 38 | 39 | 40 | // Section: Functions 41 | 42 | 43 | // Function: default() 44 | // Description: 45 | // Returns the value given as `v` if it is not `undef`. 46 | // Otherwise, returns the value of `dflt`. 47 | // Arguments: 48 | // v = Value to pass through if not `undef`. 49 | // dflt = Value to return if `v` *is* `undef`. 50 | function default(v,dflt=undef) = v==undef? dflt : v; 51 | 52 | 53 | // Function: is_def() 54 | // Description: Returns true if given value is not `undef`. 55 | function is_def(v) = (version_num() > 20190100)? !is_undef(v) : (v != undef); 56 | 57 | 58 | // Function: is_str() 59 | // Description: Given a value, returns true if it is a string. 60 | function is_str(v) = (version_num() > 20190100)? is_string(v) : (v=="" || (is_def(v) && is_def(v[0]) && (len(str(v,v)) == len(v)*2))); 61 | 62 | 63 | // Function: is_boolean() 64 | // Description: Given a value, returns true if it is a boolean. 65 | function is_boolean(v) = (version_num() > 20190100)? is_bool(v) : (!is_str(v) && (str(v) == "true" || str(v) == "false")); 66 | 67 | 68 | // Function: is_scalar() 69 | // Description: Given a value, returns true if it is a scalar number. 70 | function is_scalar(v) = (version_num() > 20190100)? is_num(v) : (!is_boolean(v) && is_def(v+0)); 71 | 72 | 73 | // Function: is_array() 74 | // Description: Given a value, returns true if it is an array/list/vector. 75 | function is_array(v) = (version_num() > 20190100)? is_list(v) : (v==[] || (is_def(v[0]) && !is_str(v) )); 76 | 77 | 78 | // Function: get_radius() 79 | // Description: 80 | // Given various radii and diameters, returns the most specific radius. 81 | // If a diameter is most specific, returns half its value, giving the radius. 82 | // If no radii or diameters are defined, returns the value of dflt. 83 | // Value specificity order is r1, d1, r, d, then dflt 84 | // Arguments: 85 | // r1 = Most specific radius. 86 | // d1 = Most specific Diameter. 87 | // r = Most general radius. 88 | // d = Most general diameter. 89 | // dflt = Value to return if all other values given are `undef`. 90 | function get_radius(r1=undef, r=undef, d1=undef, d=undef, dflt=undef) = ( 91 | is_def(r1)? r1 : 92 | is_def(d1)? d1/2 : 93 | is_def(r)? r : 94 | is_def(d)? d/2 : 95 | dflt 96 | ); 97 | 98 | 99 | // Function: Len() 100 | // Description: 101 | // Given an array, returns number of items in array. Otherwise returns `undef`. 102 | function Len(v) = is_array(v)? len(v) : undef; 103 | 104 | 105 | // Function: remove_undefs() 106 | // Description: Removes all `undef`s from a list. 107 | function remove_undefs(v) = [for (x = v) if (is_def(x)) x]; 108 | 109 | 110 | // Function: first_defined() 111 | // Description: 112 | // Returns the first item in the list that is not `undef`. 113 | // If all items are `undef`, or list is empty, returns `undef`. 114 | function first_defined(v) = remove_undefs(v)[0]; 115 | 116 | 117 | // Function: any_defined() 118 | // Description: 119 | // Returns true if any item in the given array is not `undef`. 120 | function any_defined(v) = len(remove_undefs(v))>0; 121 | 122 | 123 | // Function: scalar_vec3() 124 | // Usage: 125 | // scalar_vec3(v, [dflt]); 126 | // Description: 127 | // If `v` is a scalar, and `dflt==undef`, returns `[v, v, v]`. 128 | // If `v` is a scalar, and `dflt!=undef`, returns `[v, dflt, dflt]`. 129 | // If `v` is a vector, returns the first 3 items, with any missing values replaced by `dflt`. 130 | // If `v` is `undef`, returns `undef`. 131 | // Arguments: 132 | // v = Value to return vector from. 133 | // dflt = Default value to set empty vector parts from. 134 | function scalar_vec3(v, dflt=undef) = 135 | !is_def(v)? undef : 136 | is_array(v)? [for (i=[0:2]) default(v[i], default(dflt, 0))] : 137 | is_def(dflt)? [v,dflt,dflt] : [v,v,v]; 138 | 139 | 140 | 141 | // Function: f_echo() 142 | // Description: If possible, echo a message from a function. 143 | function f_echo(msg) = (version_num() > 20190100)? echo(msg) : 0; 144 | 145 | 146 | // Section: Modules 147 | 148 | 149 | // Module: assert_in_list() 150 | // Usage: 151 | // assert_in_list(argname, val, l, [idx]); 152 | // Description: 153 | // Emulates the newer OpenSCAD `assert()` with an `in_list()` test. 154 | // You can also use this as a function call from a function. 155 | // Arguments: 156 | // argname = The name of the argument value being tested. 157 | // val = The value to test if it exists in the list. 158 | // l = The list to look for `val` in. 159 | // idx = If given, and `l` is a list of lists, look for `val` in the given index of each sublist. 160 | module assert_in_list(argname, val, l, idx=undef) { 161 | succ = search([val], l, num_returns_per_match=1, index_col_num=idx) != [[]]; 162 | if (!succ) { 163 | msg = str( 164 | "In argument '", argname, "', ", 165 | (is_str(val)? str("\"", val, "\"") : val), 166 | " must be one of ", 167 | (is_def(idx)? [for (v=l) v[idx]] : l) 168 | ); 169 | assertion(succ, msg); 170 | } 171 | } 172 | 173 | function assert_in_list(argname, val, l, idx=undef) = 174 | let(succ = search([val], l, num_returns_per_match=1, index_col_num=idx) != [[]]) 175 | succ? 0 : let( 176 | msg = str( 177 | "In argument '", argname, "', ", 178 | (is_str(val)? str("\"", val, "\"") : val), 179 | " must be one of ", 180 | (is_def(idx)? [for (v=l) v[idx]] : l) 181 | ) 182 | ) assertion(succ, msg); 183 | 184 | 185 | // Module: assertion() 186 | // Usage: 187 | // assertion(succ, msg); 188 | // Description: 189 | // Backwards compatible assert() semi-replacement. 190 | // If `succ` is false, then print an error with `msg`. 191 | // You can also use this as a function call from a function. 192 | // Arguments: 193 | // succ = If this is `false`, trigger the assertion. 194 | // msg = The message to emit if `succ` is `false`. 195 | module assertion(succ, msg) { 196 | if (version_num() > 20190100) { 197 | // assert() will echo the variable name, and `succ` looks confusing there. So we store it in FAILED. 198 | FAILED = succ; 199 | assert(FAILED, msg); 200 | } else if (!succ) { 201 | echo_error(msg); 202 | } 203 | } 204 | 205 | function assertion(succ, msg) = 206 | (version_num() > 20190100)? let(FAILED=succ) assert(FAILED, msg) : 0; 207 | 208 | 209 | // Module: echo_error() 210 | // Usage: 211 | // echo_error(msg, [pfx]); 212 | // Description: 213 | // Emulates printing of an error message. The text will be shaded red. 214 | // You can also use this as a function call from a function. 215 | // Arguments: 216 | // msg = The message to print. 217 | // pfx = The prefix to print before `msg`. Default: `ERROR` 218 | module echo_error(msg, pfx="ERROR") { 219 | echo(str("

", pfx, ": ", msg, "

")); 220 | } 221 | 222 | function echo_error(msg, pfx="ERROR") = 223 | f_echo(str("

", pfx, ": ", msg, "

")); 224 | 225 | 226 | // Module: echo_warning() 227 | // Usage: 228 | // echo_warning(msg, [pfx]); 229 | // Description: 230 | // Emulates printing of a warning message. The text will be shaded yellow. 231 | // You can also use this as a function call from a function. 232 | // Arguments: 233 | // msg = The message to print. 234 | // pfx = The prefix to print before `msg`. Default: `WARNING` 235 | module echo_warning(msg, pfx="WARNING") { 236 | echo(str("

", pfx, ": ", msg, "

")); 237 | } 238 | 239 | function echo_warning(msg, pfx="WARNING") = 240 | f_echo(str("

", pfx, ": ", msg, "

")); 241 | 242 | 243 | // Module: deprecate() 244 | // Usage: 245 | // deprecate(name, [suggest]); 246 | // Description: 247 | // Show module deprecation warnings. 248 | // You can also use this as a function call from a function. 249 | // Arguments: 250 | // name = The name of the module that is deprecated. 251 | // suggest = If given, the module to recommend using instead. 252 | module deprecate(name, suggest=undef) { 253 | echo_warning(pfx="DEPRECATED", 254 | str( 255 | "`", name, "` is deprecated and should not be used.", 256 | !is_def(suggest)? "" : str( 257 | " You should use `", suggest, "` instead." 258 | ) 259 | ) 260 | ); 261 | } 262 | 263 | function deprecate(name, suggest=undef) = 264 | echo_warning(pfx="DEPRECATED", 265 | str( 266 | "`", name, "` is deprecated and should not be used.", 267 | !is_def(suggest)? "" : str( 268 | " You should use `", suggest, "` instead." 269 | ) 270 | ) 271 | ); 272 | 273 | 274 | // Module: deprecate_argument() 275 | // Usage: 276 | // deprecate(name, arg, [suggest]); 277 | // Description: 278 | // Show argument deprecation warnings. 279 | // You can also use this as a function call from a function. 280 | // Arguments: 281 | // name = The name of the module/function the deprecated argument is used in. 282 | // arg = The name of the deprecated argument. 283 | // suggest = If given, the argument to recommend using instead. 284 | module deprecate_argument(name, arg, suggest=undef) { 285 | echo_warning(pfx="DEPRECATED ARG", str( 286 | "In `", name, "`, ", 287 | "the argument `", arg, "` ", 288 | "is deprecated and should not be used.", 289 | !is_def(suggest)? "" : str( 290 | " You should use `", suggest, "` instead." 291 | ) 292 | )); 293 | } 294 | 295 | function deprecate_argument(name, arg, suggest=undef) = 296 | echo_warning(pfx="DEPRECATED ARG", str( 297 | "In `", name, "`, ", 298 | "the argument `", arg, "` ", 299 | "is deprecated and should not be used.", 300 | !is_def(suggest)? "" : str( 301 | " You should use `", suggest, "` instead." 302 | ) 303 | )); 304 | 305 | 306 | 307 | // vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 308 | -------------------------------------------------------------------------------- /constants.scad: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////// 2 | // LibFile: constants.scad 3 | // Useful Constants. 4 | // To use this, add the following line to the top of your file. 5 | // ``` 6 | // include 7 | // ``` 8 | ////////////////////////////////////////////////////////////////////// 9 | 10 | /* 11 | BSD 2-Clause License 12 | 13 | Copyright (c) 2017, Revar Desmera 14 | All rights reserved. 15 | 16 | Redistribution and use in source and binary forms, with or without 17 | modification, are permitted provided that the following conditions are met: 18 | 19 | * Redistributions of source code must retain the above copyright notice, this 20 | list of conditions and the following disclaimer. 21 | 22 | * Redistributions in binary form must reproduce the above copyright notice, 23 | this list of conditions and the following disclaimer in the documentation 24 | and/or other materials provided with the distribution. 25 | 26 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 27 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 28 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 29 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 30 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 31 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 32 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 33 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 34 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 35 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 36 | */ 37 | 38 | 39 | // Section: General Constants 40 | 41 | PRINTER_SLOP = 0.20; // The printer specific amount of slop in mm to print with to make parts fit exactly. You may need to override this value for your printer. 42 | 43 | 44 | 45 | // Section: Directional Vectors 46 | // Vectors useful for `rotate()`, `mirror()`, and `align` arguments for `cuboid()`, `cyl()`, etc. 47 | 48 | // Constant: V_LEFT 49 | // Description: Vector pointing left. [-1,0,0] 50 | // Example(3D): Usage with `align` 51 | // cuboid(20, align=V_LEFT); 52 | V_LEFT = [-1, 0, 0]; 53 | 54 | // Constant: V_RIGHT 55 | // Description: Vector pointing right. [1,0,0] 56 | // Example(3D): Usage with `align` 57 | // cuboid(20, align=V_RIGHT); 58 | V_RIGHT = [ 1, 0, 0]; 59 | 60 | // Constant: V_FWD 61 | // Description: Vector pointing forward. [0,-1,0] 62 | // Example(3D): Usage with `align` 63 | // cuboid(20, align=V_FWD); 64 | V_FWD = [ 0, -1, 0]; 65 | 66 | // Constant: V_BACK 67 | // Description: Vector pointing back. [0,1,0] 68 | // Example(3D): Usage with `align` 69 | // cuboid(20, align=V_BACK); 70 | V_BACK = [ 0, 1, 0]; 71 | 72 | // Constant: V_DOWN 73 | // Description: Vector pointing down. [0,0,-1] 74 | // Example(3D): Usage with `align` 75 | // cuboid(20, align=V_DOWN); 76 | V_DOWN = [ 0, 0, -1]; 77 | 78 | // Constant: V_UP 79 | // Description: Vector pointing up. [0,0,1] 80 | // Example(3D): Usage with `align` 81 | // cuboid(20, align=V_UP); 82 | V_UP = [ 0, 0, 1]; 83 | 84 | // Constant: V_ALLPOS 85 | // Description: Vector pointing right, back, and up. [1,1,1] 86 | // Example(3D): Usage with `align` 87 | // cuboid(20, align=V_ALLPOS); 88 | V_ALLPOS = [ 1, 1, 1]; // Vector pointing X+,Y+,Z+. 89 | 90 | // Constant: V_ALLNEG 91 | // Description: Vector pointing left, forwards, and down. [-1,-1,-1] 92 | // Example(3D): Usage with `align` 93 | // cuboid(20, align=V_ALLNEG); 94 | V_ALLNEG = [-1, -1, -1]; // Vector pointing X-,Y-,Z-. 95 | 96 | // Constant: V_ZERO 97 | // Description: Zero vector. Centered. [0,0,0] 98 | // Example(3D): Usage with `align` 99 | // cuboid(20, align=V_ZERO); 100 | V_ZERO = [ 0, 0, 0]; // Centered zero vector. 101 | 102 | 103 | // Section: Vector Aliases 104 | // Useful aliases for use with `align`. 105 | 106 | V_CENTER = V_ZERO; // Centered, alias to `V_ZERO`. 107 | V_ABOVE = V_UP; // Vector pointing up, alias to `V_UP`. 108 | V_BELOW = V_DOWN; // Vector pointing down, alias to `V_DOWN`. 109 | V_BEFORE = V_FWD; // Vector pointing forward, alias to `V_FWD`. 110 | V_BEHIND = V_BACK; // Vector pointing back, alias to `V_BACK`. 111 | 112 | V_TOP = V_UP; // Vector pointing up, alias to `V_UP`. 113 | V_BOTTOM = V_DOWN; // Vector pointing down, alias to `V_DOWN`. 114 | V_FRONT = V_FWD; // Vector pointing forward, alias to `V_FWD`. 115 | V_REAR = V_BACK; // Vector pointing back, alias to `V_BACK`. 116 | 117 | 118 | 119 | // Section: Pre-Orientation Alignments 120 | // Constants for pre-orientation alignments. 121 | 122 | 123 | // Constant: ALIGN_POS 124 | // Description: Align the axis-positive end to the origin. 125 | // Example(3D): orient=ORIENT_X 126 | // cyl(d1=10, d2=5, h=20, orient=ORIENT_X, align=ALIGN_POS); 127 | // Example(3D): orient=ORIENT_Y 128 | // cyl(d1=10, d2=5, h=20, orient=ORIENT_Y, align=ALIGN_POS); 129 | // Example(3D): orient=ORIENT_Z 130 | // cyl(d1=10, d2=5, h=20, orient=ORIENT_Z, align=ALIGN_POS); 131 | // Example(3D): orient=ORIENT_XNEG 132 | // cyl(d1=10, d2=5, h=20, orient=ORIENT_XNEG, align=ALIGN_POS); 133 | // Example(3D): orient=ORIENT_YNEG 134 | // cyl(d1=10, d2=5, h=20, orient=ORIENT_YNEG, align=ALIGN_POS); 135 | // Example(3D): orient=ORIENT_ZNEG 136 | // cyl(d1=10, d2=5, h=20, orient=ORIENT_ZNEG, align=ALIGN_POS); 137 | ALIGN_POS = 1; 138 | 139 | 140 | ALIGN_CENTER = 0; // Align centered. 141 | 142 | // Constant: ALIGN_NEG 143 | // Description: Align the axis-negative end to the origin. 144 | // Example(3D): orient=ORIENT_X 145 | // cyl(d1=10, d2=5, h=20, orient=ORIENT_X, align=ALIGN_NEG); 146 | // Example(3D): orient=ORIENT_Y 147 | // cyl(d1=10, d2=5, h=20, orient=ORIENT_Y, align=ALIGN_NEG); 148 | // Example(3D): orient=ORIENT_Z 149 | // cyl(d1=10, d2=5, h=20, orient=ORIENT_Z, align=ALIGN_NEG); 150 | // Example(3D): orient=ORIENT_XNEG 151 | // cyl(d1=10, d2=5, h=20, orient=ORIENT_XNEG, align=ALIGN_NEG); 152 | // Example(3D): orient=ORIENT_YNEG 153 | // cyl(d1=10, d2=5, h=20, orient=ORIENT_YNEG, align=ALIGN_NEG); 154 | // Example(3D): orient=ORIENT_ZNEG 155 | // cyl(d1=10, d2=5, h=20, orient=ORIENT_ZNEG, align=ALIGN_NEG); 156 | ALIGN_NEG = -1; 157 | 158 | 159 | // CommonCode: 160 | // orientations = [ 161 | // ORIENT_X, ORIENT_Y, ORIENT_Z, 162 | // ORIENT_XNEG, ORIENT_YNEG, ORIENT_ZNEG, 163 | // ORIENT_X_90, ORIENT_Y_90, ORIENT_Z_90, 164 | // ORIENT_XNEG_90, ORIENT_YNEG_90, ORIENT_ZNEG_90, 165 | // ORIENT_X_180, ORIENT_Y_180, ORIENT_Z_180, 166 | // ORIENT_XNEG_180, ORIENT_YNEG_180, ORIENT_ZNEG_180, 167 | // ORIENT_X_270, ORIENT_Y_270, ORIENT_Z_270, 168 | // ORIENT_XNEG_270, ORIENT_YNEG_270, ORIENT_ZNEG_270 169 | // ]; 170 | // axiscolors = ["red", "forestgreen", "dodgerblue"]; 171 | // module text3d(text, h=0.01, size=3) { 172 | // linear_extrude(height=h, convexity=10) { 173 | // text(text=text, size=size, valign="center", halign="center"); 174 | // } 175 | // } 176 | // module orient_cube(ang) { 177 | // color("lightgray") cube(20, center=true); 178 | // color(axiscolors.x) up ((20-1)/2+0.01) back ((20-1)/2+0.01) cube([18,1,1], center=true); 179 | // color(axiscolors.y) up ((20-1)/2+0.01) right((20-1)/2+0.01) cube([1,18,1], center=true); 180 | // color(axiscolors.z) back((20-1)/2+0.01) right((20-1)/2+0.01) cube([1,1,18], center=true); 181 | // for (axis=[0:2], neg=[0:1]) { 182 | // idx = axis + 3*neg + 6*ang/90; 183 | // rotate(orientations[idx]) { 184 | // up(10) { 185 | // fwd(4) color("black") text3d(text=str(ang), size=4); 186 | // back(4) color(axiscolors[axis]) text3d(text=str(["X","Y","Z"][axis], ["+","NEG"][neg]), size=4); 187 | // } 188 | // } 189 | // } 190 | // } 191 | 192 | 193 | // Section: Standard Orientations 194 | // Orientations for `cyl()`, `prismoid()`, etc. They take the form of standard [X,Y,Z] 195 | // rotation angles for rotating a vertical shape into the given orientations. 196 | // Figure(Spin): Standard Orientations 197 | // orient_cube(0); 198 | 199 | ORIENT_X = [ 90, 0, 90]; // Orient along the X axis. 200 | ORIENT_Y = [ 90, 0, 180]; // Orient along the Y axis. 201 | ORIENT_Z = [ 0, 0, 0]; // Orient along the Z axis. 202 | ORIENT_XNEG = [ 90, 0, -90]; // Orient reversed along the X axis. 203 | ORIENT_YNEG = [ 90, 0, 0]; // Orient reversed along the Y axis. 204 | ORIENT_ZNEG = [ 0, 180, 0]; // Orient reversed along the Z axis. 205 | 206 | 207 | // Section: Orientations Rotated 90º 208 | // Orientations for `cyl()`, `prismoid()`, etc. They take the form of standard [X,Y,Z] 209 | // rotation angles for rotating a vertical shape into the given orientations. 210 | // Figure(Spin): Orientations Rotated 90º 211 | // orient_cube(90); 212 | 213 | ORIENT_X_90 = [ 90, -90, 90]; // Orient along the X axis, then rotate 90 degrees counter-clockwise on that axis, as seen when facing the origin from that axis orientation. 214 | ORIENT_Y_90 = [ 90, -90, 180]; // Orient along the Y axis, then rotate 90 degrees counter-clockwise on that axis, as seen when facing the origin from that axis orientation. 215 | ORIENT_Z_90 = [ 0, 0, 90]; // Orient along the Z axis, then rotate 90 degrees counter-clockwise on that axis, as seen when facing the origin from that axis orientation. 216 | ORIENT_XNEG_90 = [ 0, -90, 0]; // Orient reversed along the X axis, then rotate 90 degrees counter-clockwise on that axis, as seen when facing the origin from that axis orientation. 217 | ORIENT_YNEG_90 = [ 90, -90, 0]; // Orient reversed along the Y axis, then rotate 90 degrees counter-clockwise on that axis, as seen when facing the origin from that axis orientation. 218 | ORIENT_ZNEG_90 = [ 0, 180, -90]; // Orient reversed along the Z axis, then rotate 90 degrees counter-clockwise on that axis, as seen when facing the origin from that axis orientation. 219 | 220 | 221 | // Section: Orientations Rotated 180º 222 | // Orientations for `cyl()`, `prismoid()`, etc. They take the form of standard [X,Y,Z] 223 | // rotation angles for rotating a vertical shape into the given orientations. 224 | // Figure(Spin): Orientations Rotated 180º 225 | // orient_cube(180); 226 | 227 | ORIENT_X_180 = [-90, 0, -90]; // Orient along the X axis, then rotate 180 degrees counter-clockwise on that axis, as seen when facing the origin from that axis orientation. 228 | ORIENT_Y_180 = [-90, 0, 0]; // Orient along the Y axis, then rotate 180 degrees counter-clockwise on that axis, as seen when facing the origin from that axis orientation. 229 | ORIENT_Z_180 = [ 0, 0, 180]; // Orient along the Z axis, then rotate 180 degrees counter-clockwise on that axis, as seen when facing the origin from that axis orientation. 230 | ORIENT_XNEG_180 = [-90, 0, 90]; // Orient reversed along the X axis, then rotate 180 degrees counter-clockwise on that axis, as seen when facing the origin from that axis orientation. 231 | ORIENT_YNEG_180 = [-90, 0, 180]; // Orient reversed along the Y axis, then rotate 180 degrees counter-clockwise on that axis, as seen when facing the origin from that axis orientation. 232 | ORIENT_ZNEG_180 = [ 0, 180, 180]; // Orient reversed along the Z axis, then rotate 180 degrees counter-clockwise on that axis, as seen when facing the origin from that axis orientation. 233 | 234 | 235 | // Section: Orientations Rotated 270º 236 | // Orientations for `cyl()`, `prismoid()`, etc. They take the form of standard [X,Y,Z] 237 | // rotation angles for rotating a vertical shape into the given orientations. 238 | // Figure(Spin): Orientations Rotated 270º 239 | // orient_cube(270); 240 | 241 | ORIENT_X_270 = [ 90, 90, 90]; // Orient along the X axis, then rotate 270 degrees counter-clockwise on that axis, as seen when facing the origin from that axis orientation. 242 | ORIENT_Y_270 = [ 90, 90, 180]; // Orient along the Y axis, then rotate 270 degrees counter-clockwise on that axis, as seen when facing the origin from that axis orientation. 243 | ORIENT_Z_270 = [ 0, 0, -90]; // Orient along the Z axis, then rotate 270 degrees counter-clockwise on that axis, as seen when facing the origin from that axis orientation. 244 | ORIENT_XNEG_270 = [ 90, 90, -90]; // Orient reversed along the X axis, then rotate 270 degrees counter-clockwise on that axis, as seen when facing the origin from that axis orientation. 245 | ORIENT_YNEG_270 = [ 90, 90, 0]; // Orient reversed along the Y axis, then rotate 270 degrees counter-clockwise on that axis, as seen when facing the origin from that axis orientation. 246 | ORIENT_ZNEG_270 = [ 0, 180, 90]; // Orient reversed along the Z axis, then rotate 270 degrees counter-clockwise on that axis, as seen when facing the origin from that axis orientation. 247 | 248 | 249 | // Section: Individual Edges 250 | // Constants for specifying edges for `cuboid()`, etc. 251 | 252 | EDGE_TOP_BK = [[1,0,0,0], [0,0,0,0], [0,0,0,0]]; // Top Back edge. 253 | EDGE_TOP_FR = [[0,1,0,0], [0,0,0,0], [0,0,0,0]]; // Top Front edge. 254 | EDGE_BOT_FR = [[0,0,1,0], [0,0,0,0], [0,0,0,0]]; // Bottom Front Edge. 255 | EDGE_BOT_BK = [[0,0,0,1], [0,0,0,0], [0,0,0,0]]; // Bottom Back Edge. 256 | 257 | EDGE_TOP_RT = [[0,0,0,0], [1,0,0,0], [0,0,0,0]]; // Top Right edge. 258 | EDGE_TOP_LF = [[0,0,0,0], [0,1,0,0], [0,0,0,0]]; // Top Left edge. 259 | EDGE_BOT_LF = [[0,0,0,0], [0,0,1,0], [0,0,0,0]]; // Bottom Left edge. 260 | EDGE_BOT_RT = [[0,0,0,0], [0,0,0,1], [0,0,0,0]]; // Bottom Right edge. 261 | 262 | EDGE_BK_RT = [[0,0,0,0], [0,0,0,0], [1,0,0,0]]; // Back Right edge. 263 | EDGE_BK_LF = [[0,0,0,0], [0,0,0,0], [0,1,0,0]]; // Back Left edge. 264 | EDGE_FR_LF = [[0,0,0,0], [0,0,0,0], [0,0,1,0]]; // Front Left edge. 265 | EDGE_FR_RT = [[0,0,0,0], [0,0,0,0], [0,0,0,1]]; // Front Right edge. 266 | 267 | // Section: Sets of Edges 268 | // Constants for specifying edges for `cuboid()`, etc. 269 | 270 | EDGES_X_TOP = [[1,1,0,0], [0,0,0,0], [0,0,0,0]]; // Both X-aligned Top edges. 271 | EDGES_X_BOT = [[0,0,1,1], [0,0,0,0], [0,0,0,0]]; // Both X-aligned Bottom edges. 272 | EDGES_X_FR = [[0,1,1,0], [0,0,0,0], [0,0,0,0]]; // Both X-aligned Front edges. 273 | EDGES_X_BK = [[1,0,0,1], [0,0,0,0], [0,0,0,0]]; // Both X-aligned Back edges. 274 | EDGES_X_ALL = [[1,1,1,1], [0,0,0,0], [0,0,0,0]]; // All four X-aligned edges. 275 | 276 | EDGES_Y_TOP = [[0,0,0,0], [1,1,0,0], [0,0,0,0]]; // Both Y-aligned Top edges. 277 | EDGES_Y_BOT = [[0,0,0,0], [0,0,1,1], [0,0,0,0]]; // Both Y-aligned Bottom edges. 278 | EDGES_Y_LF = [[0,0,0,0], [0,1,1,0], [0,0,0,0]]; // Both Y-aligned Left edges. 279 | EDGES_Y_RT = [[0,0,0,0], [1,0,0,1], [0,0,0,0]]; // Both Y-aligned Right edges. 280 | EDGES_Y_ALL = [[0,0,0,0], [1,1,1,1], [0,0,0,0]]; // All four Y-aligned edges. 281 | 282 | EDGES_Z_BK = [[0,0,0,0], [0,0,0,0], [1,1,0,0]]; // Both Z-aligned Back edges. 283 | EDGES_Z_FR = [[0,0,0,0], [0,0,0,0], [0,0,1,1]]; // Both Z-aligned Front edges. 284 | EDGES_Z_LF = [[0,0,0,0], [0,0,0,0], [0,1,1,0]]; // Both Z-aligned Left edges. 285 | EDGES_Z_RT = [[0,0,0,0], [0,0,0,0], [1,0,0,1]]; // Both Z-aligned Right edges. 286 | EDGES_Z_ALL = [[0,0,0,0], [0,0,0,0], [1,1,1,1]]; // All four Z-aligned edges. 287 | 288 | EDGES_LEFT = [[0,0,0,0], [0,1,1,0], [0,1,1,0]]; // All four Left edges. 289 | EDGES_RIGHT = [[0,0,0,0], [1,0,0,1], [1,0,0,1]]; // All four Right edges. 290 | EDGES_FRONT = [[0,1,1,0], [0,0,0,0], [0,0,1,1]]; // All four Front edges. 291 | EDGES_BACK = [[1,0,0,1], [0,0,0,0], [1,1,0,0]]; // All four Back edges. 292 | EDGES_BOTTOM = [[0,0,1,1], [0,0,1,1], [0,0,0,0]]; // All four Bottom edges. 293 | EDGES_TOP = [[1,1,0,0], [1,1,0,0], [0,0,0,0]]; // All four Top edges. 294 | 295 | EDGES_NONE = [[0,0,0,0], [0,0,0,0], [0,0,0,0]]; // No edges. 296 | EDGES_ALL = [[1,1,1,1], [1,1,1,1], [1,1,1,1]]; // All edges. 297 | 298 | 299 | // Section: Edge Helpers 300 | 301 | EDGE_OFFSETS = [ // Array of XYZ offsets to the center of each edge. 302 | [[0, 1, 1], [ 0,-1, 1], [ 0,-1,-1], [0, 1,-1]], 303 | [[1, 0, 1], [-1, 0, 1], [-1, 0,-1], [1, 0,-1]], 304 | [[1, 1, 0], [-1, 1, 0], [-1,-1, 0], [1,-1, 0]] 305 | ]; 306 | 307 | 308 | // Function: corner_edge_count() 309 | // Description: Counts how many given edges intersect at a specific corner. 310 | // Arguments: 311 | // edges = Standard edges array. 312 | // v = Vector pointing to the corner to count edge intersections at. 313 | function corner_edge_count(edges, v) = 314 | (v[2]<=0)? ( 315 | (v[1]<=0)? ( 316 | (v[0]<=0)? ( 317 | edges[0][2] + edges[1][2] + edges[2][2] 318 | ) : ( 319 | edges[0][2] + edges[1][3] + edges[2][3] 320 | ) 321 | ) : ( 322 | (v[0]<=0)? ( 323 | edges[0][3] + edges[1][2] + edges[2][1] 324 | ) : ( 325 | edges[0][3] + edges[1][3] + edges[2][0] 326 | ) 327 | ) 328 | ) : ( 329 | (v[1]<=0)? ( 330 | (v[0]<=0)? ( 331 | edges[0][1] + edges[1][1] + edges[2][2] 332 | ) : ( 333 | edges[0][1] + edges[1][0] + edges[2][3] 334 | ) 335 | ) : ( 336 | (v[0]<=0)? ( 337 | edges[0][0] + edges[1][1] + edges[2][1] 338 | ) : ( 339 | edges[0][0] + edges[1][0] + edges[2][0] 340 | ) 341 | ) 342 | ); 343 | 344 | 345 | 346 | // vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 347 | -------------------------------------------------------------------------------- /paths.scad: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////// 2 | // LibFile: paths.scad 3 | // Polylines, polygons and paths. 4 | // To use, add the following lines to the beginning of your file: 5 | // ``` 6 | // include 7 | // use 8 | // ``` 9 | ////////////////////////////////////////////////////////////////////// 10 | 11 | /* 12 | BSD 2-Clause License 13 | 14 | Copyright (c) 2017, Revar Desmera 15 | All rights reserved. 16 | 17 | Redistribution and use in source and binary forms, with or without 18 | modification, are permitted provided that the following conditions are met: 19 | 20 | * Redistributions of source code must retain the above copyright notice, this 21 | list of conditions and the following disclaimer. 22 | 23 | * Redistributions in binary form must reproduce the above copyright notice, 24 | this list of conditions and the following disclaimer in the documentation 25 | and/or other materials provided with the distribution. 26 | 27 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 28 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 29 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 30 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 31 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 32 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 33 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 34 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 35 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 36 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 37 | */ 38 | 39 | 40 | include 41 | use 42 | use 43 | use 44 | use 45 | 46 | 47 | // Section: Functions 48 | 49 | 50 | // Function: simplify2d_path() 51 | // Description: 52 | // Takes a 2D polyline and removes unnecessary collinear points. 53 | // Usage: 54 | // simplify2d_path(path, [eps]) 55 | // Arguments: 56 | // path = A list of 2D path points. 57 | // eps = Largest angle delta between segments to count as colinear. Default: 1e-6 58 | function simplify2d_path(path, eps=1e-6) = simplify_path(path, eps=eps); 59 | 60 | 61 | // Function: simplify3d_path() 62 | // Description: 63 | // Takes a 3D polyline and removes unnecessary collinear points. 64 | // Usage: 65 | // simplify3d_path(path, [eps]) 66 | // Arguments: 67 | // path = A list of 3D path points. 68 | // eps = Largest angle delta between segments to count as colinear. Default: 1e-6 69 | function simplify3d_path(path, eps=1e-6) = simplify_path(path, eps=eps); 70 | 71 | 72 | // Function: path_length() 73 | // Usage: 74 | // path3d_length(path) 75 | // Description: 76 | // Returns the length of the path. 77 | // Arguments: 78 | // path = The list of points of the path to measure. 79 | // Example: 80 | // path = [[0,0], [5,35], [60,-25], [80,0]]; 81 | // echo(path_length(path)); 82 | function path_length(path) = 83 | len(path)<2? 0 : 84 | sum([for (i = [0:len(path)-2]) norm(path[i+1]-path[i])]); 85 | 86 | 87 | // Function: path2d_regular_ngon() 88 | // Description: 89 | // Returns a 2D open counter-clockwise path of the vertices of a regular polygon of `n` sides. 90 | // Usage: 91 | // path2d_regular_ngon(n, r|d, [cp], [scale]); 92 | // Arguments: 93 | // n = Number of polygon sides. 94 | // r = Radius of regular polygon. 95 | // d = Radius of regular polygon. 96 | // cp = Centerpoint of regular polygon. Default: `[0,0]` 97 | // scale = [X,Y] scaling factors for each axis. Default: `[1,1]` 98 | // Example(2D): 99 | // trace_polyline(path2d_regular_ngon(n=12, r=50), N=1, showpts=true); 100 | function path2d_regular_ngon(n=6, r=undef, d=undef, cp=[0,0], scale=[1,1]) = 101 | let( 102 | rr=get_radius(r=r, d=d, dflt=100) 103 | ) [ 104 | for (i=[0:n-1]) 105 | rr * [cos(i*360/n)*scale.x, sin(i*360/n)*scale.y] + cp 106 | ]; 107 | 108 | 109 | // Function: path3d_spiral() 110 | // Description: 111 | // Returns a 3D spiral path. 112 | // Usage: 113 | // path3d_spiral(turns, h, n, r|d, [cp], [scale]); 114 | // Arguments: 115 | // h = Height of spiral. 116 | // turns = Number of turns in spiral. 117 | // n = Number of spiral sides. 118 | // r = Radius of spiral. 119 | // d = Radius of spiral. 120 | // cp = Centerpoint of spiral. Default: `[0,0]` 121 | // scale = [X,Y] scaling factors for each axis. Default: `[1,1]` 122 | // Example(3D): 123 | // trace_polyline(path3d_spiral(turns=2.5, h=100, n=24, r=50), N=1, showpts=true); 124 | function path3d_spiral(turns=3, h=100, n=12, r=undef, d=undef, cp=[0,0], scale=[1,1]) = let( 125 | rr=get_radius(r=r, d=d, dflt=100), 126 | cnt=floor(turns*n), 127 | dz=h/cnt 128 | ) [ 129 | for (i=[0:cnt]) [ 130 | rr * cos(i*360/n) * scale.x + cp.x, 131 | rr * sin(i*360/n) * scale.y + cp.y, 132 | i*dz 133 | ] 134 | ]; 135 | 136 | 137 | // Function: points_along_path3d() 138 | // Usage: 139 | // points_along_path3d(polyline, path); 140 | // Description: 141 | // Calculates the vertices needed to create a `polyhedron()` of the 142 | // extrusion of `polyline` along `path`. The closed 2D path shold be 143 | // centered on the XY plane. The 2D path is extruded perpendicularly 144 | // along the 3D path. Produces a list of 3D vertices. Vertex count 145 | // is `len(polyline)*len(path)`. Gives all the reoriented vertices 146 | // for `polyline` at the first point in `path`, then for the second, 147 | // and so on. 148 | // Arguments: 149 | // polyline = A closed list of 2D path points. 150 | // path = A list of 3D path points. 151 | function points_along_path3d( 152 | polyline, // The 2D polyline to drag along the 3D path. 153 | path, // The 3D polyline path to follow. 154 | q=Q_Ident(), // Used in recursion 155 | n=0 // Used in recursion 156 | ) = let( 157 | end = len(path)-1, 158 | v1 = (n == 0)? [0, 0, 1] : normalize(path[n]-path[n-1]), 159 | v2 = (n == end)? normalize(path[n]-path[n-1]) : normalize(path[n+1]-path[n]), 160 | crs = cross(v1, v2), 161 | axis = norm(crs) <= 0.001? [0, 0, 1] : crs, 162 | ang = vector_angle(v1, v2), 163 | hang = ang * (n==0? 1.0 : 0.5), 164 | hrot = Quat(axis, hang), 165 | arot = Quat(axis, ang), 166 | roth = Q_Mul(hrot, q), 167 | rotm = Q_Mul(arot, q) 168 | ) concat( 169 | [for (i = [0:len(polyline)-1]) Q_Rot_Vector(point3d(polyline[i]),roth) + path[n]], 170 | (n == end)? [] : points_along_path3d(polyline, path, rotm, n+1) 171 | ); 172 | 173 | 174 | 175 | // Section: 2D Modules 176 | 177 | 178 | // Module: modulated_circle() 179 | // Description: 180 | // Creates a 2D polygon circle, modulated by one or more superimposed sine waves. 181 | // Arguments: 182 | // r = radius of the base circle. 183 | // sines = array of [amplitude, frequency] pairs, where the frequency is the number of times the cycle repeats around the circle. 184 | // Example(2D): 185 | // modulated_circle(r=40, sines=[[3, 11], [1, 31]], $fn=6); 186 | module modulated_circle(r=40, sines=[10]) 187 | { 188 | freqs = len(sines)>0? [for (i=sines) i[1]] : [5]; 189 | points = [ 190 | for (a = [0 : (360/segs(r)/max(freqs)) : 360]) 191 | let(nr=r+sum_of_sines(a,sines)) [nr*cos(a), nr*sin(a)] 192 | ]; 193 | polygon(points); 194 | } 195 | 196 | 197 | // Section: 3D Modules 198 | 199 | 200 | // Module: extrude_from_to() 201 | // Description: 202 | // Extrudes a 2D shape between the points pt1 and pt2. Takes as children a set of 2D shapes to extrude. 203 | // Arguments: 204 | // pt1 = starting point of extrusion. 205 | // pt2 = ending point of extrusion. 206 | // convexity = max number of times a line could intersect a wall of the 2D shape being extruded. 207 | // twist = number of degrees to twist the 2D shape over the entire extrusion length. 208 | // scale = scale multiplier for end of extrusion compared the start. 209 | // slices = Number of slices along the extrusion to break the extrusion into. Useful for refining `twist` extrusions. 210 | // Example(FlatSpin): 211 | // extrude_from_to([0,0,0], [10,20,30], convexity=4, twist=360, scale=3.0, slices=40) { 212 | // xspread(3) circle(3, $fn=32); 213 | // } 214 | module extrude_from_to(pt1, pt2, convexity=undef, twist=undef, scale=undef, slices=undef) { 215 | rtp = xyz_to_spherical(pt2-pt1); 216 | translate(pt1) { 217 | rotate([0, rtp[2], rtp[1]]) { 218 | linear_extrude(height=rtp[0], convexity=convexity, center=false, slices=slices, twist=twist, scale=scale) { 219 | children(); 220 | } 221 | } 222 | } 223 | } 224 | 225 | 226 | 227 | // Module: extrude_2d_hollow() 228 | // Description: 229 | // Similar to linear_extrude(), except the result is a hollow shell. 230 | // Arguments: 231 | // wall = thickness of shell wall. 232 | // height = height of extrusion. 233 | // twist = degrees of twist, from bottom to top. 234 | // slices = how many slices to use when making extrusion. 235 | // Example: 236 | // extrude_2d_hollow(wall=2, height=100, twist=90, slices=50) 237 | // circle(r=40, $fn=6); 238 | module extrude_2d_hollow(wall=2, height=50, twist=90, slices=60, center=undef, orient=ORIENT_Z, align=V_UP) 239 | { 240 | orient_and_align([0,0,height], orient, align, center) { 241 | linear_extrude(height=height, twist=twist, slices=slices, center=true) { 242 | difference() { 243 | children(); 244 | offset(r=-wall) { 245 | children(); 246 | } 247 | } 248 | } 249 | } 250 | } 251 | 252 | 253 | // Module: extrude_2dpath_along_spiral() 254 | // Description: 255 | // Takes a closed 2D polyline path, centered on the XY plane, and 256 | // extrudes it along a 3D spiral path of a given radius, height and twist. 257 | // Arguments: 258 | // polyline = Array of points of a polyline path, to be extruded. 259 | // h = height of the spiral to extrude along. 260 | // r = radius of the spiral to extrude along. 261 | // twist = number of degrees of rotation to spiral up along height. 262 | // Example: 263 | // poly = [[-10,0], [-3,-5], [3,-5], [10,0], [0,-30]]; 264 | // extrude_2dpath_along_spiral(poly, h=200, r=50, twist=1080, $fn=36); 265 | module extrude_2dpath_along_spiral(polyline, h, r, twist=360, center=undef, orient=ORIENT_Z, align=V_CENTER) { 266 | pline_count = len(polyline); 267 | steps = ceil(segs(r)*(twist/360)); 268 | 269 | poly_points = [ 270 | for ( 271 | p = [0:steps] 272 | ) let ( 273 | a = twist * (p/steps), 274 | dx = r*cos(a), 275 | dy = r*sin(a), 276 | dz = h * (p/steps), 277 | pts = matrix4_apply( 278 | polyline, [ 279 | matrix4_xrot(90), 280 | matrix4_zrot(a), 281 | matrix4_translate([dx, dy, dz]) 282 | ] 283 | ) 284 | ) for (pt = pts) pt 285 | ]; 286 | 287 | poly_faces = concat( 288 | [[for (b = [0:pline_count-1]) b]], 289 | [ 290 | for ( 291 | p = [0:steps-1], 292 | b = [0:pline_count-1], 293 | i = [0:1] 294 | ) let ( 295 | b2 = (b == pline_count-1)? 0 : b+1, 296 | p0 = p * pline_count + b, 297 | p1 = p * pline_count + b2, 298 | p2 = (p+1) * pline_count + b2, 299 | p3 = (p+1) * pline_count + b, 300 | pt = (i==0)? [p0, p2, p1] : [p0, p3, p2] 301 | ) pt 302 | ], 303 | [[for (b = [pline_count-1:-1:0]) b+(steps)*pline_count]] 304 | ); 305 | 306 | tri_faces = triangulate_faces(poly_points, poly_faces); 307 | orient_and_align([r,r,h], orient, align, center) { 308 | polyhedron(points=poly_points, faces=tri_faces, convexity=10); 309 | } 310 | } 311 | 312 | 313 | // Module: extrude_2dpath_along_3dpath() 314 | // Description: 315 | // Takes a closed 2D path `polyline`, centered on the XY plane, and extrudes it perpendicularly along a 3D path `path`, forming a solid. 316 | // Arguments: 317 | // polyline = Array of points of a polyline path, to be extruded. 318 | // path = Array of points of a polyline path, to extrude along. 319 | // ang = Angle in degrees to rotate 2D polyline before extrusion. 320 | // convexity = max number of surfaces any single ray could pass through. 321 | // Example(FlatSpin): 322 | // shape = [[0,-10], [5,-3], [5,3], [0,10], [30,0]]; 323 | // path = concat( 324 | // [for (a=[30:30:180]) [50*cos(a)+50, 50*sin(a), 20*sin(a)]], 325 | // [for (a=[330:-30:180]) [50*cos(a)-50, 50*sin(a), 20*sin(a)]] 326 | // ); 327 | // extrude_2dpath_along_3dpath(shape, path, ang=140); 328 | module extrude_2dpath_along_3dpath(polyline, path, ang=0, convexity=10) { 329 | pline_count = len(polyline); 330 | path_count = len(path); 331 | 332 | polyline = rotate_points2d(path2d(polyline), ang); 333 | poly_points = points_along_path3d(polyline, path); 334 | 335 | poly_faces = concat( 336 | [[for (b = [0:pline_count-1]) b]], 337 | [ 338 | for ( 339 | p = [0:path_count-2], 340 | b = [0:pline_count-1], 341 | i = [0:1] 342 | ) let ( 343 | b2 = (b == pline_count-1)? 0 : b+1, 344 | p0 = p * pline_count + b, 345 | p1 = p * pline_count + b2, 346 | p2 = (p+1) * pline_count + b2, 347 | p3 = (p+1) * pline_count + b, 348 | pt = (i==0)? [p0, p2, p1] : [p0, p3, p2] 349 | ) pt 350 | ], 351 | [[for (b = [pline_count-1:-1:0]) b+(path_count-1)*pline_count]] 352 | ); 353 | 354 | tri_faces = triangulate_faces(poly_points, poly_faces); 355 | polyhedron(points=poly_points, faces=tri_faces, convexity=convexity); 356 | } 357 | 358 | 359 | 360 | // Module: extrude_2d_shapes_along_3dpath() 361 | // Description: 362 | // Extrudes 2D children along a 3D polyline path. This may be slow. 363 | // Arguments: 364 | // path = array of points for the bezier path to extrude along. 365 | // convexity = maximum number of walls a ran can pass through. 366 | // clipsize = increase if artifacts are left. Default: 1000 367 | // Example(FlatSpin): 368 | // path = [ [0, 0, 0], [33, 33, 33], [66, 33, 40], [100, 0, 0], [150,0,0] ]; 369 | // extrude_2d_shapes_along_3dpath(path) circle(r=10, $fn=6); 370 | module extrude_2d_shapes_along_3dpath(path, convexity=10, clipsize=100) { 371 | function polyquats(path, q=Q_Ident(), v=[0,0,1], i=0) = let( 372 | v2 = path[i+1] - path[i], 373 | ang = vector_angle(v,v2), 374 | axis = ang>0.001? normalize(cross(v,v2)) : [0,0,1], 375 | newq = Q_Mul(Quat(axis, ang), q), 376 | dist = norm(v2) 377 | ) i < (len(path)-2)? 378 | concat([[dist, newq, ang]], polyquats(path, newq, v2, i+1)) : 379 | [[dist, newq, ang]]; 380 | 381 | epsilon = 0.0001; // Make segments ever so slightly too long so they overlap. 382 | ptcount = len(path); 383 | pquats = polyquats(path); 384 | for (i = [0 : ptcount-2]) { 385 | pt1 = path[i]; 386 | pt2 = path[i+1]; 387 | dist = pquats[i][0]; 388 | q = pquats[i][1]; 389 | difference() { 390 | translate(pt1) { 391 | Qrot(q) { 392 | down(clipsize/2/2) { 393 | linear_extrude(height=dist+clipsize/2, convexity=convexity) { 394 | children(); 395 | } 396 | } 397 | } 398 | } 399 | translate(pt1) { 400 | hq = (i > 0)? Q_Slerp(q, pquats[i-1][1], 0.5) : q; 401 | Qrot(hq) down(clipsize/2+epsilon) cube(clipsize, center=true); 402 | } 403 | translate(pt2) { 404 | hq = (i < ptcount-2)? Q_Slerp(q, pquats[i+1][1], 0.5) : q; 405 | Qrot(hq) up(clipsize/2+epsilon) cube(clipsize, center=true); 406 | } 407 | } 408 | } 409 | } 410 | 411 | 412 | // Module: trace_polyline() 413 | // Description: 414 | // Renders lines between each point of a polyline path. 415 | // Can also optionally show the individual vertex points. 416 | // Arguments: 417 | // pline = The array of points in the polyline. 418 | // showpts = If true, draw vertices and control points. 419 | // N = Mark the first and every Nth vertex after in a different color and shape. 420 | // size = Diameter of the lines drawn. 421 | // color = Color to draw the lines (but not vertices) in. 422 | // Example(FlatSpin): 423 | // polyline = [for (a=[0:30:210]) 10*[cos(a), sin(a), sin(a)]]; 424 | // trace_polyline(polyline, showpts=true, size=0.5, color="lightgreen"); 425 | module trace_polyline(pline, N=1, showpts=false, size=1, color="yellow") { 426 | if (showpts) { 427 | for (i = [0:len(pline)-1]) { 428 | translate(pline[i]) { 429 | if (i%N == 0) { 430 | color("blue") sphere(d=size*2.5, $fn=8); 431 | } else { 432 | color("red") { 433 | cylinder(d=size/2, h=size*3, center=true, $fn=8); 434 | xrot(90) cylinder(d=size/2, h=size*3, center=true, $fn=8); 435 | yrot(90) cylinder(d=size/2, h=size*3, center=true, $fn=8); 436 | } 437 | } 438 | } 439 | } 440 | } 441 | for (i = [0:len(pline)-2]) { 442 | if (N!=3 || (i%N) != 1) { 443 | color(color) extrude_from_to(pline[i], pline[i+1]) circle(d=size/2); 444 | } 445 | } 446 | } 447 | 448 | 449 | // Module: debug_polygon() 450 | // Description: A drop-in replacement for `polygon()` that renders and labels the path points. 451 | // Arguments: 452 | // points = The array of 2D polygon vertices. 453 | // paths = The path connections between the vertices. 454 | // convexity = The max number of walls a ray can pass through the given polygon paths. 455 | // Example(2D): 456 | // debug_polygon( 457 | // points=concat( 458 | // path2d_regular_ngon(r=10, n=8), 459 | // path2d_regular_ngon(r=8, n=8) 460 | // ), 461 | // paths=[ 462 | // [for (i=[0:7]) i], 463 | // [for (i=[15:-1:8]) i] 464 | // ] 465 | // ); 466 | module debug_polygon(points, paths=undef, convexity=2, size=1) 467 | { 468 | pths = (!is_def(paths))? [for (i=[0:len(points)-1]) i] : is_scalar(paths[0])? [paths] : paths; 469 | echo(points=points); 470 | echo(paths=paths); 471 | linear_extrude(height=0.01, convexity=convexity, center=true) { 472 | polygon(points=points, paths=paths, convexity=convexity); 473 | } 474 | for (i = [0:len(points)-1]) { 475 | color("red") { 476 | up(0.2) { 477 | translate(points[i]) { 478 | linear_extrude(height=0.1, convexity=10, center=true) { 479 | text(text=str(i), size=size, halign="center", valign="center"); 480 | } 481 | } 482 | } 483 | } 484 | } 485 | for (j = [0:len(paths)-1]) { 486 | path = paths[j]; 487 | translate(points[path[0]]) { 488 | color("cyan") up(0.1) cylinder(d=size*1.5, h=0.01, center=false, $fn=12); 489 | } 490 | translate(points[path[len(path)-1]]) { 491 | color("pink") up(0.11) cylinder(d=size*1.5, h=0.01, center=false, $fn=4); 492 | } 493 | for (i = [0:len(path)-1]) { 494 | midpt = (points[path[i]] + points[path[(i+1)%len(path)]])/2; 495 | color("blue") { 496 | up(0.2) { 497 | translate(midpt) { 498 | linear_extrude(height=0.1, convexity=10, center=true) { 499 | text(text=str(chr(65+j),i), size=size/2, halign="center", valign="center"); 500 | } 501 | } 502 | } 503 | } 504 | } 505 | } 506 | } 507 | 508 | 509 | 510 | // vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 511 | -------------------------------------------------------------------------------- /joiners.scad: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////// 2 | // LibFile: joiners.scad 3 | // Snap-together joiners. 4 | // To use, add the following lines to the beginning of your file: 5 | // ``` 6 | // include 7 | // use 8 | // ``` 9 | ////////////////////////////////////////////////////////////////////// 10 | 11 | /* 12 | BSD 2-Clause License 13 | 14 | Copyright (c) 2017, Revar Desmera 15 | All rights reserved. 16 | 17 | Redistribution and use in source and binary forms, with or without 18 | modification, are permitted provided that the following conditions are met: 19 | 20 | * Redistributions of source code must retain the above copyright notice, this 21 | list of conditions and the following disclaimer. 22 | 23 | * Redistributions in binary form must reproduce the above copyright notice, 24 | this list of conditions and the following disclaimer in the documentation 25 | and/or other materials provided with the distribution. 26 | 27 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 28 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 29 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 30 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 31 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 32 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 33 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 34 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 35 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 36 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 37 | */ 38 | 39 | 40 | use 41 | use 42 | include 43 | include 44 | 45 | 46 | // Section: Half Joiners 47 | 48 | 49 | // Module: half_joiner_clear() 50 | // Description: 51 | // Creates a mask to clear an area so that a half_joiner can be placed there. 52 | // Usage: 53 | // half_joiner_clear(h, w, [a], [clearance], [overlap], [orient], [align]) 54 | // Arguments: 55 | // h = Height of the joiner to clear space for. 56 | // w = Width of the joiner to clear space for. 57 | // a = Overhang angle of the joiner. 58 | // clearance = Extra width to clear. 59 | // overlap = Extra depth to clear. 60 | // orient = Orientation of the shape. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Y`. 61 | // align = Alignment of the shape by the axis-negative (size1) end. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. 62 | // Example: 63 | // half_joiner_clear(orient=ORIENT_X); 64 | module half_joiner_clear(h=20, w=10, a=30, clearance=0, overlap=0.01, orient=ORIENT_Y, align=V_CENTER) 65 | { 66 | dmnd_height = h*1.0; 67 | dmnd_width = dmnd_height*tan(a); 68 | guide_size = w/3; 69 | guide_width = 2*(dmnd_height/2-guide_size)*tan(a); 70 | 71 | orient_and_align([w, guide_width, h], orient, align, orig_orient=ORIENT_Y) { 72 | yspread(overlap, n=overlap>0? 2 : 1) { 73 | difference() { 74 | // Diamonds. 75 | scale([w+clearance, dmnd_width/2, dmnd_height/2]) { 76 | xrot(45) cube(size=[1,sqrt(2),sqrt(2)], center=true); 77 | } 78 | // Blunt point of tab. 79 | yspread(guide_width+4) { 80 | cube(size=[(w+clearance)*1.05, 4, h*0.99], center=true); 81 | } 82 | } 83 | } 84 | if (overlap>0) cube([w+clearance, overlap+0.001, h], center=true); 85 | } 86 | } 87 | 88 | 89 | 90 | // Module: half_joiner() 91 | // Usage: 92 | // half_joiner(h, w, l, [a], [screwsize], [guides], [slop], [orient], [align]) 93 | // Description: 94 | // Creates a half_joiner object that can be attached to half_joiner2 object. 95 | // Arguments: 96 | // h = Height of the half_joiner. 97 | // w = Width of the half_joiner. 98 | // l = Length of the backing to the half_joiner. 99 | // a = Overhang angle of the half_joiner. 100 | // screwsize = Diameter of screwhole. 101 | // guides = If true, create sliding alignment guides. 102 | // slop = Printer specific slop value to make parts fit more closely. 103 | // orient = Orientation of the shape. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Y`. 104 | // align = Alignment of the shape by the axis-negative (size1) end. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. 105 | // Example: 106 | // half_joiner(screwsize=3, orient=ORIENT_X); 107 | module half_joiner(h=20, w=10, l=10, a=30, screwsize=undef, guides=true, slop=PRINTER_SLOP, orient=ORIENT_Y, align=V_CENTER) 108 | { 109 | dmnd_height = h*1.0; 110 | dmnd_width = dmnd_height*tan(a); 111 | guide_size = w/3; 112 | guide_width = 2*(dmnd_height/2-guide_size)*tan(a); 113 | 114 | if ($children > 0) { 115 | difference() { 116 | children(); 117 | half_joiner_clear(h=h, w=w, a=a, clearance=0.1, overlap=0.01, orient=orient, align=align); 118 | } 119 | } 120 | orient_and_align([w, 2*l, h], orient, align, orig_orient=ORIENT_Y) { 121 | difference() { 122 | union() { 123 | // Make base. 124 | difference() { 125 | // Solid backing base. 126 | fwd(l/2) cube(size=[w, l, h], center=true); 127 | 128 | // Clear diamond for tab 129 | grid3d(xa=[-(w*2/3), (w*2/3)]) { 130 | half_joiner_clear(h=h+0.01, w=w, clearance=slop*2, a=a); 131 | } 132 | } 133 | 134 | difference() { 135 | // Make tab 136 | scale([w/3-slop*2, dmnd_width/2, dmnd_height/2]) xrot(45) 137 | cube(size=[1,sqrt(2),sqrt(2)], center=true); 138 | 139 | // Blunt point of tab. 140 | back(guide_width/2+2) 141 | cube(size=[w*0.99,4,guide_size*2], center=true); 142 | } 143 | 144 | 145 | // Guide ridges. 146 | if (guides == true) { 147 | xspread(w/3-slop*2) { 148 | // Guide ridge. 149 | fwd(0.05/2) { 150 | scale([0.75, 1, 2]) yrot(45) 151 | cube(size=[guide_size/sqrt(2), guide_width+0.05, guide_size/sqrt(2)], center=true); 152 | } 153 | 154 | // Snap ridge. 155 | scale([0.25, 0.5, 1]) zrot(45) 156 | cube(size=[guide_size/sqrt(2), guide_size/sqrt(2), dmnd_width], center=true); 157 | } 158 | } 159 | } 160 | 161 | // Make screwholes, if needed. 162 | if (screwsize != undef) { 163 | yrot(90) cylinder(r=screwsize*1.1/2, h=w+1, center=true, $fn=12); 164 | } 165 | } 166 | } 167 | } 168 | //half_joiner(screwsize=3, orient=ORIENT_Z, align=V_UP); 169 | 170 | 171 | 172 | // Module: half_joiner2() 173 | // Usage: 174 | // half_joiner2(h, w, l, [a], [screwsize], [guides], [orient], [align]) 175 | // Description: 176 | // Creates a half_joiner2 object that can be attached to half_joiner object. 177 | // Arguments: 178 | // h = Height of the half_joiner. 179 | // w = Width of the half_joiner. 180 | // l = Length of the backing to the half_joiner. 181 | // a = Overhang angle of the half_joiner. 182 | // screwsize = Diameter of screwhole. 183 | // guides = If true, create sliding alignment guides. 184 | // orient = Orientation of the shape. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Y`. 185 | // align = Alignment of the shape by the axis-negative (size1) end. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. 186 | // Example: 187 | // half_joiner2(screwsize=3, orient=ORIENT_X); 188 | module half_joiner2(h=20, w=10, l=10, a=30, screwsize=undef, guides=true, orient=ORIENT_Y, align=V_CENTER) 189 | { 190 | dmnd_height = h*1.0; 191 | dmnd_width = dmnd_height*tan(a); 192 | guide_size = w/3; 193 | guide_width = 2*(dmnd_height/2-guide_size)*tan(a); 194 | 195 | if ($children > 0) { 196 | difference() { 197 | children(); 198 | half_joiner_clear(h=h, w=w, a=a, clearance=0.1, overlap=0.01, orient=orient, align=align); 199 | } 200 | } 201 | 202 | orient_and_align([w, 2*l, h], orient, align, orig_orient=ORIENT_Y) { 203 | difference() { 204 | union () { 205 | fwd(l/2) cube(size=[w, l, h], center=true); 206 | cube([w, guide_width, h], center=true); 207 | } 208 | 209 | // Subtract mated half_joiner. 210 | zrot(180) half_joiner(h=h+0.01, w=w+0.01, l=guide_width+0.01, a=a, screwsize=undef, guides=guides, slop=0.0); 211 | 212 | // Make screwholes, if needed. 213 | if (screwsize != undef) { 214 | xcyl(r=screwsize*1.1/2, l=w+1, $fn=12); 215 | } 216 | } 217 | } 218 | } 219 | 220 | 221 | 222 | // Section: Full Joiners 223 | 224 | 225 | // Module: joiner_clear() 226 | // Description: 227 | // Creates a mask to clear an area so that a joiner can be placed there. 228 | // Usage: 229 | // joiner_clear(h, w, [a], [clearance], [overlap], [orient], [align]) 230 | // Arguments: 231 | // h = Height of the joiner to clear space for. 232 | // w = Width of the joiner to clear space for. 233 | // a = Overhang angle of the joiner. 234 | // clearance = Extra width to clear. 235 | // overlap = Extra depth to clear. 236 | // orient = Orientation of the shape. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Y`. 237 | // align = Alignment of the shape by the axis-negative (size1) end. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. 238 | // Example: 239 | // joiner_clear(orient=ORIENT_X); 240 | module joiner_clear(h=40, w=10, a=30, clearance=0, overlap=0.01, orient=ORIENT_Y, align=V_CENTER) 241 | { 242 | dmnd_height = h*0.5; 243 | dmnd_width = dmnd_height*tan(a); 244 | guide_size = w/3; 245 | guide_width = 2*(dmnd_height/2-guide_size)*tan(a); 246 | 247 | orient_and_align([w, guide_width, h], orient, align, orig_orient=ORIENT_Y) { 248 | up(h/4) half_joiner_clear(h=h/2.0-0.01, w=w, a=a, overlap=overlap, clearance=clearance); 249 | down(h/4) half_joiner_clear(h=h/2.0-0.01, w=w, a=a, overlap=overlap, clearance=-0.01); 250 | } 251 | } 252 | 253 | 254 | 255 | // Module: joiner() 256 | // Usage: 257 | // joiner(h, w, l, [a], [screwsize], [guides], [slop], [orient], [align]) 258 | // Description: 259 | // Creates a joiner object that can be attached to another joiner object. 260 | // Arguments: 261 | // h = Height of the joiner. 262 | // w = Width of the joiner. 263 | // l = Length of the backing to the joiner. 264 | // a = Overhang angle of the joiner. 265 | // screwsize = Diameter of screwhole. 266 | // guides = If true, create sliding alignment guides. 267 | // slop = Printer specific slop value to make parts fit more closely. 268 | // orient = Orientation of the shape. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Y`. 269 | // align = Alignment of the shape by the axis-negative (size1) end. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. 270 | // Examples: 271 | // joiner(screwsize=3, orient=ORIENT_X); 272 | // joiner(w=10, l=10, h=40, orient=ORIENT_X) cuboid([10, 10*2, 40], align=V_LEFT); 273 | module joiner(h=40, w=10, l=10, a=30, screwsize=undef, guides=true, slop=PRINTER_SLOP, orient=ORIENT_Y, align=V_CENTER) 274 | { 275 | if ($children > 0) { 276 | difference() { 277 | children(); 278 | joiner_clear(h=h, w=w, a=a, clearance=0.1, orient=orient, align=align); 279 | } 280 | } 281 | orient_and_align([w, 2*l, h], orient, align, orig_orient=ORIENT_Y) { 282 | up(h/4) half_joiner(h=h/2, w=w, l=l, a=a, screwsize=screwsize, guides=guides, slop=slop); 283 | down(h/4) half_joiner2(h=h/2, w=w, l=l, a=a, screwsize=screwsize, guides=guides); 284 | } 285 | } 286 | 287 | 288 | 289 | // Section: Full Joiners Pairs/Sets 290 | 291 | 292 | // Module: joiner_pair_clear() 293 | // Description: 294 | // Creates a mask to clear an area so that a pair of joiners can be placed there. 295 | // Usage: 296 | // joiner_pair_clear(spacing, [n], [h], [w], [a], [clearance], [overlap], [orient], [align]) 297 | // Arguments: 298 | // spacing = Spacing between joiner centers. 299 | // h = Height of the joiner to clear space for. 300 | // w = Width of the joiner to clear space for. 301 | // a = Overhang angle of the joiner. 302 | // n = Number of joiners (2 by default) to clear for. 303 | // clearance = Extra width to clear. 304 | // overlap = Extra depth to clear. 305 | // orient = Orientation of the shape. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Y`. 306 | // align = Alignment of the shape by the axis-negative (size1) end. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. 307 | // Examples: 308 | // joiner_pair_clear(spacing=50, n=2); 309 | // joiner_pair_clear(spacing=50, n=3); 310 | module joiner_pair_clear(spacing=100, h=40, w=10, a=30, n=2, clearance=0, overlap=0.01, orient=ORIENT_Y, align=V_CENTER) 311 | { 312 | dmnd_height = h*0.5; 313 | dmnd_width = dmnd_height*tan(a); 314 | guide_size = w/3; 315 | guide_width = 2*(dmnd_height/2-guide_size)*tan(a); 316 | 317 | orient_and_align([spacing+w, guide_width, h], orient, align, orig_orient=ORIENT_Y) { 318 | xspread(spacing, n=n) { 319 | joiner_clear(h=h, w=w, a=a, clearance=clearance, overlap=overlap); 320 | } 321 | } 322 | } 323 | 324 | 325 | 326 | // Module: joiner_pair() 327 | // Usage: 328 | // joiner_pair(h, w, l, [a], [screwsize], [guides], [slop], [orient], [align]) 329 | // Description: 330 | // Creates a joiner_pair object that can be attached to other joiner_pairs . 331 | // Arguments: 332 | // spacing = Spacing between joiner centers. 333 | // h = Height of the joiners. 334 | // w = Width of the joiners. 335 | // l = Length of the backing to the joiners. 336 | // a = Overhang angle of the joiners. 337 | // n = Number of joiners in a row. Default: 2 338 | // alternate = If true (default), each joiner alternates it's orientation. If alternate is "alt", do opposite alternating orientations. 339 | // screwsize = Diameter of screwhole. 340 | // guides = If true, create sliding alignment guides. 341 | // slop = Printer specific slop value to make parts fit more closely. 342 | // orient = Orientation of the shape. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Y`. 343 | // align = Alignment of the shape by the axis-negative (size1) end. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. 344 | // Examples: 345 | // joiner_pair(spacing=50, l=10, orient=ORIENT_X) cuboid([10, 50+10-0.1, 40], align=V_LEFT); 346 | // joiner_pair(spacing=50, l=10, n=2, orient=ORIENT_X); 347 | // joiner_pair(spacing=50, l=10, n=3, alternate=false, orient=ORIENT_X); 348 | // joiner_pair(spacing=50, l=10, n=3, alternate=true, orient=ORIENT_X); 349 | // joiner_pair(spacing=50, l=10, n=3, alternate="alt", orient=ORIENT_X); 350 | module joiner_pair(spacing=100, h=40, w=10, l=10, a=30, n=2, alternate=true, screwsize=undef, guides=true, slop=PRINTER_SLOP, orient=ORIENT_Y, align=V_CENTER) 351 | { 352 | if ($children > 0) { 353 | difference() { 354 | children(); 355 | joiner_pair_clear(spacing=spacing, h=h, w=w, a=a, clearance=0.1, orient=orient, align=align); 356 | } 357 | } 358 | orient_and_align([spacing+w, 2*l, h], orient, align, orig_orient=ORIENT_Y) { 359 | left((n-1)*spacing/2) { 360 | for (i=[0:n-1]) { 361 | right(i*spacing) { 362 | yrot(180 + (alternate? (i*180+(alternate=="alt"?180:0))%360 : 0)) { 363 | joiner(h=h, w=w, l=l, a=a, screwsize=screwsize, guides=guides, slop=slop); 364 | } 365 | } 366 | } 367 | } 368 | } 369 | } 370 | 371 | 372 | 373 | // Section: Full Joiners Quads/Sets 374 | 375 | 376 | // Module: joiner_quad_clear() 377 | // Description: 378 | // Creates a mask to clear an area so that a pair of joiners can be placed there. 379 | // Usage: 380 | // joiner_quad_clear(spacing, [n], [h], [w], [a], [clearance], [overlap], [orient], [align]) 381 | // Arguments: 382 | // spacing1 = Spacing between joiner centers. 383 | // spacing2 = Spacing between back-to-back pairs/sets of joiners. 384 | // h = Height of the joiner to clear space for. 385 | // w = Width of the joiner to clear space for. 386 | // a = Overhang angle of the joiner. 387 | // n = Number of joiners in a row. Default: 2 388 | // clearance = Extra width to clear. 389 | // overlap = Extra depth to clear. 390 | // orient = Orientation of the shape. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Y`. 391 | // align = Alignment of the shape by the axis-negative (size1) end. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. 392 | // Examples: 393 | // joiner_quad_clear(spacing1=50, spacing2=50, n=2); 394 | // joiner_quad_clear(spacing1=50, spacing2=50, n=3); 395 | module joiner_quad_clear(xspacing=undef, yspacing=undef, spacing1=undef, spacing2=undef, n=2, h=40, w=10, a=30, clearance=0, overlap=0.01, orient=ORIENT_Y, align=V_CENTER) 396 | { 397 | spacing1 = first_defined([spacing1, xspacing, 100]); 398 | spacing2 = first_defined([spacing2, yspacing, 50]); 399 | orient_and_align([w+spacing1, spacing2, h], orient, align, orig_orient=ORIENT_Y) { 400 | zrot_copies(n=2) { 401 | back(spacing2/2) { 402 | joiner_pair_clear(spacing=spacing1, n=n, h=h, w=w, a=a, clearance=clearance, overlap=overlap); 403 | } 404 | } 405 | } 406 | } 407 | 408 | 409 | 410 | // Module: joiner_quad() 411 | // Usage: 412 | // joiner_quad(h, w, l, [a], [screwsize], [guides], [slop], [orient], [align]) 413 | // Description: 414 | // Creates a joiner_quad object that can be attached to other joiner_pairs . 415 | // Arguments: 416 | // spacing = Spacing between joiner centers. 417 | // h = Height of the joiners. 418 | // w = Width of the joiners. 419 | // l = Length of the backing to the joiners. 420 | // a = Overhang angle of the joiners. 421 | // n = Number of joiners in a row. Default: 2 422 | // alternate = If true (default), each joiner alternates it's orientation. If alternate is "alt", do opposite alternating orientations. 423 | // screwsize = Diameter of screwhole. 424 | // guides = If true, create sliding alignment guides. 425 | // slop = Printer specific slop value to make parts fit more closely. 426 | // orient = Orientation of the shape. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Y`. 427 | // align = Alignment of the shape by the axis-negative (size1) end. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. 428 | // Examples: 429 | // joiner_quad(spacing1=50, spacing2=50, l=10, orient=ORIENT_X) cuboid([50, 50+10-0.1, 40]); 430 | // joiner_quad(spacing1=50, spacing2=50, l=10, n=2, orient=ORIENT_X); 431 | // joiner_quad(spacing1=50, spacing2=50, l=10, n=3, alternate=false, orient=ORIENT_X); 432 | // joiner_quad(spacing1=50, spacing2=50, l=10, n=3, alternate=true, orient=ORIENT_X); 433 | // joiner_quad(spacing1=50, spacing2=50, l=10, n=3, alternate="alt", orient=ORIENT_X); 434 | module joiner_quad(spacing1=undef, spacing2=undef, xspacing=undef, yspacing=undef, h=40, w=10, l=10, a=30, n=2, alternate=true, screwsize=undef, guides=true, slop=PRINTER_SLOP, orient=ORIENT_Y, align=V_CENTER) 435 | { 436 | spacing1 = first_defined([spacing1, xspacing, 100]); 437 | spacing2 = first_defined([spacing2, yspacing, 50]); 438 | if ($children > 0) { 439 | difference() { 440 | children(); 441 | joiner_quad_clear(spacing1=spacing1, spacing2=spacing2, h=h, w=w, a=a, clearance=0.1, orient=orient, align=align); 442 | } 443 | } 444 | orient_and_align([w+spacing1, spacing2, h], orient, align, orig_orient=ORIENT_Y) { 445 | zrot_copies(n=2) { 446 | back(spacing2/2) { 447 | joiner_pair(spacing=spacing1, n=n, h=h, w=w, l=l, a=a, screwsize=screwsize, guides=guides, slop=slop); 448 | } 449 | } 450 | } 451 | } 452 | 453 | 454 | 455 | // vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 456 | -------------------------------------------------------------------------------- /nema_steppers.scad: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////// 2 | // LibFile: nema_steppers.scad 3 | // Masks and models for NEMA stepper motors. 4 | // To use, add these lines to the top of your file: 5 | // ``` 6 | // include 7 | // use 8 | // ``` 9 | ////////////////////////////////////////////////////////////////////// 10 | 11 | /* 12 | BSD 2-Clause License 13 | 14 | Copyright (c) 2017, Revar Desmera 15 | All rights reserved. 16 | 17 | Redistribution and use in source and binary forms, with or without 18 | modification, are permitted provided that the following conditions are met: 19 | 20 | * Redistributions of source code must retain the above copyright notice, this 21 | list of conditions and the following disclaimer. 22 | 23 | * Redistributions in binary form must reproduce the above copyright notice, 24 | this list of conditions and the following disclaimer in the documentation 25 | and/or other materials provided with the distribution. 26 | 27 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 28 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 29 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 30 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 31 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 32 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 33 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 34 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 35 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 36 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 37 | */ 38 | 39 | include 40 | use 41 | use 42 | use 43 | use 44 | 45 | 46 | // Section: Functions 47 | 48 | 49 | // Function: nema_motor_width() 50 | // Description: Gets width of NEMA motor of given standard size. 51 | // Arguments: 52 | // size = The standard NEMA motor size. 53 | function nema_motor_width(size) = lookup(size, [ 54 | [11.0, 28.2], 55 | [14.0, 35.2], 56 | [17.0, 42.3], 57 | [23.0, 57.0], 58 | [34.0, 86.0], 59 | ]); 60 | 61 | 62 | // Function: nema_motor_plinth_height() 63 | // Description: Gets plinth height of NEMA motor of given standard size. 64 | // Arguments: 65 | // size = The standard NEMA motor size. 66 | function nema_motor_plinth_height(size) = lookup(size, [ 67 | [11.0, 1.5], 68 | [14.0, 2.0], 69 | [17.0, 2.0], 70 | [23.0, 1.6], 71 | [34.0, 2.03], 72 | ]); 73 | 74 | 75 | // Function: nema_motor_plinth_diam() 76 | // Description: Gets plinth diameter of NEMA motor of given standard size. 77 | // Arguments: 78 | // size = The standard NEMA motor size. 79 | function nema_motor_plinth_diam(size) = lookup(size, [ 80 | [11.0, 22.0], 81 | [14.0, 22.0], 82 | [17.0, 22.0], 83 | [23.0, 38.1], 84 | [34.0, 73.0], 85 | ]); 86 | 87 | 88 | // Function: nema_motor_screw_spacing() 89 | // Description: Gets screw spacing of NEMA motor of given standard size. 90 | // Arguments: 91 | // size = The standard NEMA motor size. 92 | function nema_motor_screw_spacing(size) = lookup(size, [ 93 | [11.0, 23.11], 94 | [14.0, 26.0], 95 | [17.0, 30.99], 96 | [23.0, 47.14], 97 | [34.0, 69.6], 98 | ]); 99 | 100 | 101 | // Function: nema_motor_screw_size() 102 | // Description: Gets mount screw size of NEMA motor of given standard size. 103 | // Arguments: 104 | // size = The standard NEMA motor size. 105 | function nema_motor_screw_size(size) = lookup(size, [ 106 | [11.0, 2.6], 107 | [14.0, 3.0], 108 | [17.0, 3.0], 109 | [23.0, 5.1], 110 | [34.0, 5.5], 111 | ]); 112 | 113 | 114 | // Function: nema_motor_screw_depth() 115 | // Description: Gets mount screwhole depth of NEMA motor of given standard size. 116 | // Arguments: 117 | // size = The standard NEMA motor size. 118 | function nema_motor_screw_depth(size) = lookup(size, [ 119 | [11.0, 3.0], 120 | [14.0, 4.5], 121 | [17.0, 4.5], 122 | [23.0, 4.8], 123 | [34.0, 9.0], 124 | ]); 125 | 126 | 127 | // Section: Motor Models 128 | 129 | 130 | // Module: nema11_stepper() 131 | // Description: Creates a model of a NEMA 11 stepper motor. 132 | // Arguments: 133 | // h = Length of motor body. Default: 24mm 134 | // shaft = Shaft diameter. Default: 5mm 135 | // shaft_len = Length of shaft protruding out the top of the stepper motor. Default: 20mm 136 | // orient = Orientation of the stepper. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. 137 | // align = Alignment of the stepper. Use the `V_` constants from `constants.scad`. Default: `V_DOWN`. 138 | // Example: 139 | // nema11_stepper(); 140 | module nema11_stepper(h=24, shaft=5, shaft_len=20, orient=ORIENT_Z, align=V_DOWN) 141 | { 142 | size = 11; 143 | motor_width = nema_motor_width(size); 144 | plinth_height = nema_motor_plinth_height(size); 145 | plinth_diam = nema_motor_plinth_diam(size); 146 | screw_spacing = nema_motor_screw_spacing(size); 147 | screw_size = nema_motor_screw_size(size); 148 | screw_depth = nema_motor_screw_depth(size); 149 | 150 | orient_and_align([motor_width, motor_width, h], orient, align, orig_align=V_DOWN) { 151 | difference() { 152 | color([0.4, 0.4, 0.4]) 153 | cuboid(size=[motor_width, motor_width, h], chamfer=2, edges=EDGES_Z_ALL, align=V_DOWN); 154 | color("silver") 155 | xspread(screw_spacing) 156 | yspread(screw_spacing) 157 | cyl(r=screw_size/2, h=screw_depth*2, $fn=max(12,segs(screw_size/2))); 158 | } 159 | color([0.6, 0.6, 0.6]) { 160 | difference() { 161 | cylinder(h=plinth_height, d=plinth_diam); 162 | cyl(h=plinth_height*3, d=shaft+0.75); 163 | } 164 | } 165 | color("silver") 166 | cylinder(h=shaft_len, d=shaft, $fn=max(12,segs(shaft/2))); 167 | } 168 | } 169 | 170 | 171 | 172 | // Module: nema14_stepper() 173 | // Description: Creates a model of a NEMA 14 stepper motor. 174 | // Arguments: 175 | // h = Length of motor body. Default: 24mm 176 | // shaft = Shaft diameter. Default: 5mm 177 | // shaft_len = Length of shaft protruding out the top of the stepper motor. Default: 24mm 178 | // orient = Orientation of the stepper. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. 179 | // align = Alignment of the stepper. Use the `V_` constants from `constants.scad`. Default: `V_DOWN`. 180 | // Example: 181 | // nema14_stepper(); 182 | module nema14_stepper(h=24, shaft=5, shaft_len=24, orient=ORIENT_Z, align=V_DOWN) 183 | { 184 | size = 14; 185 | motor_width = nema_motor_width(size); 186 | plinth_height = nema_motor_plinth_height(size); 187 | plinth_diam = nema_motor_plinth_diam(size); 188 | screw_spacing = nema_motor_screw_spacing(size); 189 | screw_size = nema_motor_screw_size(size); 190 | screw_depth = nema_motor_screw_depth(size); 191 | 192 | orient_and_align([motor_width, motor_width, h], orient, align, orig_align=V_DOWN) { 193 | difference() { 194 | color([0.4, 0.4, 0.4]) 195 | cuboid(size=[motor_width, motor_width, h], chamfer=2, edges=EDGES_Z_ALL, align=V_DOWN); 196 | color("silver") 197 | xspread(screw_spacing) 198 | yspread(screw_spacing) 199 | cyl(d=screw_size, h=screw_depth*2, $fn=max(12,segs(screw_size/2))); 200 | } 201 | color([0.6, 0.6, 0.6]) { 202 | difference() { 203 | cylinder(h=plinth_height, d=plinth_diam); 204 | cyl(h=plinth_height*3, d=shaft+0.75); 205 | } 206 | } 207 | color("silver") 208 | cyl(h=shaft_len, d=shaft, align=V_UP, $fn=max(12,segs(shaft/2))); 209 | } 210 | } 211 | 212 | 213 | 214 | // Module: nema17_stepper() 215 | // Description: Creates a model of a NEMA 17 stepper motor. 216 | // Arguments: 217 | // h = Length of motor body. Default: 34mm 218 | // shaft = Shaft diameter. Default: 5mm 219 | // shaft_len = Length of shaft protruding out the top of the stepper motor. Default: 20mm 220 | // orient = Orientation of the stepper. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. 221 | // align = Alignment of the stepper. Use the `V_` constants from `constants.scad`. Default: `V_DOWN`. 222 | // Example: 223 | // nema17_stepper(); 224 | module nema17_stepper(h=34, shaft=5, shaft_len=20, orient=ORIENT_Z, align=V_DOWN) 225 | { 226 | size = 17; 227 | motor_width = nema_motor_width(size); 228 | plinth_height = nema_motor_plinth_height(size); 229 | plinth_diam = nema_motor_plinth_diam(size); 230 | screw_spacing = nema_motor_screw_spacing(size); 231 | screw_size = nema_motor_screw_size(size); 232 | screw_depth = nema_motor_screw_depth(size); 233 | 234 | orient_and_align([motor_width, motor_width, h], orient, align, orig_align=V_DOWN) { 235 | difference() { 236 | color([0.4, 0.4, 0.4]) 237 | cuboid([motor_width, motor_width, h], chamfer=2, edges=EDGES_Z_ALL, align=V_DOWN); 238 | color("silver") 239 | xspread(screw_spacing) 240 | yspread(screw_spacing) 241 | cyl(d=screw_size, h=screw_depth*2, $fn=max(12,segs(screw_size/2))); 242 | } 243 | color([0.6, 0.6, 0.6]) { 244 | difference() { 245 | cylinder(h=plinth_height, d=plinth_diam); 246 | cyl(h=plinth_height*3, d=shaft+0.75); 247 | } 248 | } 249 | color([0.9, 0.9, 0.9]) { 250 | down(h-motor_width/12) { 251 | fwd(motor_width/2+motor_width/24/2-0.1) { 252 | difference() { 253 | cube(size=[motor_width/8, motor_width/24, motor_width/8], center=true); 254 | cyl(d=motor_width/8-2, h=motor_width/6, orient=ORIENT_Y, $fn=12); 255 | } 256 | } 257 | } 258 | } 259 | color("silver") { 260 | difference() { 261 | cylinder(h=shaft_len, d=shaft, $fn=max(12,segs(shaft/2))); 262 | up(shaft_len/2+1) { 263 | right(shaft-0.75) { 264 | cube([shaft, shaft, shaft_len], center=true); 265 | } 266 | } 267 | } 268 | } 269 | } 270 | } 271 | 272 | 273 | 274 | // Module: nema23_stepper() 275 | // Description: Creates a model of a NEMA 23 stepper motor. 276 | // Arguments: 277 | // h = Length of motor body. Default: 50mm 278 | // shaft = Shaft diameter. Default: 6.35mm 279 | // shaft_len = Length of shaft protruding out the top of the stepper motor. Default: 25mm 280 | // orient = Orientation of the stepper. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. 281 | // align = Alignment of the stepper. Use the `V_` constants from `constants.scad`. Default: `V_DOWN`. 282 | // Example: 283 | // nema23_stepper(); 284 | module nema23_stepper(h=50, shaft=6.35, shaft_len=25, orient=ORIENT_Z, align=V_DOWN) 285 | { 286 | size = 23; 287 | motor_width = nema_motor_width(size); 288 | plinth_height = nema_motor_plinth_height(size); 289 | plinth_diam = nema_motor_plinth_diam(size); 290 | screw_spacing = nema_motor_screw_spacing(size); 291 | screw_size = nema_motor_screw_size(size); 292 | screw_depth = nema_motor_screw_depth(size); 293 | 294 | screw_inset = motor_width - screw_spacing + 1; 295 | orient_and_align([motor_width, motor_width, h], orient, align, orig_align=V_DOWN) { 296 | difference() { 297 | union() { 298 | color([0.4, 0.4, 0.4]) 299 | cuboid([motor_width, motor_width, h], chamfer=2, edges=EDGES_Z_ALL, align=V_DOWN); 300 | color([0.4, 0.4, 0.4]) 301 | cylinder(h=plinth_height, d=plinth_diam); 302 | color("silver") 303 | cylinder(h=shaft_len, d=shaft, $fn=max(12,segs(shaft/2))); 304 | } 305 | color([0.4, 0.4, 0.4]) { 306 | xspread(screw_spacing) { 307 | yspread(screw_spacing) { 308 | cyl(d=screw_size, h=screw_depth*3, $fn=max(12,segs(screw_size/2))); 309 | down(screw_depth) cuboid([screw_inset, screw_inset, h], align=V_DOWN); 310 | } 311 | } 312 | } 313 | } 314 | } 315 | } 316 | 317 | 318 | 319 | // Module: nema34_stepper() 320 | // Description: Creates a model of a NEMA 34 stepper motor. 321 | // Arguments: 322 | // h = Length of motor body. Default: 75mm 323 | // shaft = Shaft diameter. Default: 12.7mm 324 | // shaft_len = Length of shaft protruding out the top of the stepper motor. Default: 32mm 325 | // orient = Orientation of the stepper. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. 326 | // align = Alignment of the stepper. Use the `V_` constants from `constants.scad`. Default: `V_DOWN`. 327 | // Example: 328 | // nema34_stepper(); 329 | module nema34_stepper(h=75, shaft=12.7, shaft_len=32, orient=ORIENT_Z, align=V_DOWN) 330 | { 331 | size = 34; 332 | motor_width = nema_motor_width(size); 333 | plinth_height = nema_motor_plinth_height(size); 334 | plinth_diam = nema_motor_plinth_diam(size); 335 | screw_spacing = nema_motor_screw_spacing(size); 336 | screw_size = nema_motor_screw_size(size); 337 | screw_depth = nema_motor_screw_depth(size); 338 | 339 | screw_inset = motor_width - screw_spacing + 1; 340 | orient_and_align([motor_width, motor_width, h], orient, align, orig_align=V_DOWN) { 341 | difference() { 342 | union() { 343 | color([0.4, 0.4, 0.4]) 344 | cuboid(size=[motor_width, motor_width, h], chamfer=2, edges=EDGES_Z_ALL, align=V_DOWN); 345 | color([0.4, 0.4, 0.4]) 346 | cylinder(h=plinth_height, d=plinth_diam); 347 | color("silver") 348 | cylinder(h=shaft_len, d=shaft, $fn=max(24,segs(shaft/2))); 349 | } 350 | color([0.4, 0.4, 0.4]) { 351 | xspread(screw_spacing) { 352 | yspread(screw_spacing) { 353 | cylinder(d=screw_size, h=screw_depth*3, center=true, $fn=max(12,segs(screw_size/2))); 354 | down(screw_depth) downcube([screw_inset, screw_inset, h]); 355 | } 356 | } 357 | } 358 | } 359 | } 360 | } 361 | 362 | 363 | 364 | // Section: Masking Modules 365 | 366 | 367 | 368 | // Module: nema_mount_holes() 369 | // Description: Creates a mask to use when making standard NEMA stepper motor mounts. 370 | // Arguments: 371 | // size = The standard NEMA motor size to make a mount for. 372 | // depth = The thickness of the mounting hole mask. Default: 5 373 | // l = The length of the slots, for making an adjustable motor mount. Default: 5 374 | // slop = The printer-specific slop value to make parts fit just right. Default: `PRINTER_SLOP` 375 | // orient = Orientation of the stepper. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. 376 | // align = Alignment of the stepper. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. 377 | // Example: 378 | // nema_mount_holes(size=14, depth=5, l=5); 379 | // Example: 380 | // nema_mount_holes(size=17, depth=5, l=5); 381 | // Example: 382 | // nema_mount_holes(size=17, depth=5, l=0); 383 | module nema_mount_holes(size=17, depth=5, l=5, slop=PRINTER_SLOP, orient=ORIENT_Z, align=V_CENTER) 384 | { 385 | motor_width = nema_motor_width(size); 386 | plinth_diam = nema_motor_plinth_diam(size)+slop; 387 | screw_spacing = nema_motor_screw_spacing(size); 388 | screw_size = nema_motor_screw_size(size)+slop; 389 | 390 | orient_and_align([motor_width, motor_width, l], orient, align) { 391 | union() { 392 | xspread(screw_spacing) { 393 | yspread(screw_spacing) { 394 | if (l>0) { 395 | union() { 396 | yspread(l) cyl(h=depth, d=screw_size, $fn=max(8,segs(screw_size/2))); 397 | cube([screw_size, l, depth], center=true); 398 | } 399 | } else { 400 | cyl(h=depth, d=screw_size, $fn=max(8,segs(screw_size/2))); 401 | } 402 | } 403 | } 404 | } 405 | if (l>0) { 406 | union () { 407 | yspread(l) cyl(h=depth, d=plinth_diam); 408 | cube([plinth_diam, l, depth], center=true); 409 | } 410 | } else { 411 | cyl(h=depth, d=plinth_diam); 412 | } 413 | } 414 | } 415 | 416 | 417 | 418 | // Module: nema11_mount_holes() 419 | // Description: Creates a mask to use when making NEMA 11 stepper motor mounts. 420 | // Arguments: 421 | // depth = The thickness of the mounting hole mask. Default: 5 422 | // l = The length of the slots, for making an adjustable motor mount. Default: 5 423 | // slop = The printer-specific slop value to make parts fit just right. Default: `PRINTER_SLOP` 424 | // orient = Orientation of the stepper. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. 425 | // align = Alignment of the stepper. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. 426 | // Example: 427 | // nema11_mount_holes(depth=5, l=5); 428 | // Example: 429 | // nema11_mount_holes(depth=5, l=0); 430 | module nema11_mount_holes(depth=5, l=5, slop=PRINTER_SLOP, orient=ORIENT_Z, align=V_CENTER) 431 | { 432 | nema_mount_holes(size=11, depth=depth, l=l, slop=slop, orient=orient, align=align); 433 | } 434 | 435 | 436 | 437 | // Module: nema14_mount_holes() 438 | // Description: Creates a mask to use when making NEMA 14 stepper motor mounts. 439 | // Arguments: 440 | // depth = The thickness of the mounting hole mask. Default: 5 441 | // l = The length of the slots, for making an adjustable motor mount. Default: 5 442 | // slop = The printer-specific slop value to make parts fit just right. Default: `PRINTER_SLOP` 443 | // orient = Orientation of the stepper. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. 444 | // align = Alignment of the stepper. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. 445 | // Example: 446 | // nema14_mount_holes(depth=5, l=5); 447 | // Example: 448 | // nema14_mount_holes(depth=5, l=0); 449 | module nema14_mount_holes(depth=5, l=5, slop=PRINTER_SLOP, orient=ORIENT_Z, align=V_CENTER) 450 | { 451 | nema_mount_holes(size=14, depth=depth, l=l, slop=slop, orient=orient, align=align); 452 | } 453 | 454 | 455 | 456 | // Module: nema17_mount_holes() 457 | // Description: Creates a mask to use when making NEMA 17 stepper motor mounts. 458 | // Arguments: 459 | // depth = The thickness of the mounting hole mask. Default: 5 460 | // l = The length of the slots, for making an adjustable motor mount. Default: 5 461 | // slop = The printer-specific slop value to make parts fit just right. Default: `PRINTER_SLOP` 462 | // orient = Orientation of the stepper. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. 463 | // align = Alignment of the stepper. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. 464 | // Example: 465 | // nema17_mount_holes(depth=5, l=5); 466 | // Example: 467 | // nema17_mount_holes(depth=5, l=0); 468 | module nema17_mount_holes(depth=5, l=5, slop=PRINTER_SLOP, orient=ORIENT_Z, align=V_CENTER) 469 | { 470 | nema_mount_holes(size=17, depth=depth, l=l, slop=slop, orient=orient, align=align); 471 | } 472 | 473 | 474 | 475 | // Module: nema23_mount_holes() 476 | // Description: Creates a mask to use when making NEMA 23 stepper motor mounts. 477 | // Arguments: 478 | // depth = The thickness of the mounting hole mask. Default: 5 479 | // l = The length of the slots, for making an adjustable motor mount. Default: 5 480 | // slop = The printer-specific slop value to make parts fit just right. Default: `PRINTER_SLOP` 481 | // orient = Orientation of the stepper. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. 482 | // align = Alignment of the stepper. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. 483 | // Example: 484 | // nema23_mount_holes(depth=5, l=5); 485 | // Example: 486 | // nema23_mount_holes(depth=5, l=0); 487 | module nema23_mount_holes(depth=5, l=5, slop=PRINTER_SLOP, orient=ORIENT_Z, align=V_CENTER) 488 | { 489 | nema_mount_holes(size=23, depth=depth, l=l, slop=slop, orient=orient, align=align); 490 | } 491 | 492 | 493 | 494 | // Module: nema34_mount_holes() 495 | // Description: Creates a mask to use when making NEMA 34 stepper motor mounts. 496 | // Arguments: 497 | // depth = The thickness of the mounting hole mask. Default: 5 498 | // l = The length of the slots, for making an adjustable motor mount. Default: 5 499 | // slop = The printer-specific slop value to make parts fit just right. Default: `PRINTER_SLOP` 500 | // orient = Orientation of the stepper. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. 501 | // align = Alignment of the stepper. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. 502 | // Example: 503 | // nema34_mount_holes(depth=5, l=5); 504 | // Example: 505 | // nema34_mount_holes(depth=5, l=0); 506 | module nema34_mount_holes(depth=5, l=5, slop=PRINTER_SLOP, orient=ORIENT_Z, align=V_CENTER) 507 | { 508 | nema_mount_holes(size=34, depth=depth, l=l, slop=slop, orient=orient, align=align); 509 | } 510 | 511 | 512 | 513 | // Module: nema34_mount_holes() 514 | // Description: Creates a mask to use when making NEMA 34 stepper motor mounts. 515 | // Arguments: 516 | // depth = The thickness of the mounting hole mask. Default: 5 517 | // l = The length of the slots, for making an adjustable motor mount. Default: 5 518 | // slop = The printer-specific slop value to make parts fit just right. Default: `PRINTER_SLOP` 519 | // orient = Orientation of the stepper. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. 520 | // align = Alignment of the stepper. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. 521 | // Example: 522 | // nema34_mount_holes(depth=5, l=5); 523 | // Example: 524 | // nema34_mount_holes(depth=5, l=0); 525 | module nema34_mount_holes(depth=5, l=5, slop=PRINTER_SLOP, orient=ORIENT_Z, align=V_CENTER) 526 | { 527 | nema_mount_holes(size=34, depth=depth, l=l, slop=slop, orient=orient, align=align); 528 | } 529 | 530 | 531 | 532 | // vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 533 | -------------------------------------------------------------------------------- /tests/test_math.scad: -------------------------------------------------------------------------------- 1 | include 2 | include 3 | 4 | eps = 1e-9; 5 | 6 | // Simple Calculations 7 | 8 | module test_quant() { 9 | assert(quant(-4,3) == -3); 10 | assert(quant(-3,3) == -3); 11 | assert(quant(-2,3) == -3); 12 | assert(quant(-1,3) == 0); 13 | assert(quant(0,3) == 0); 14 | assert(quant(1,3) == 0); 15 | assert(quant(2,3) == 3); 16 | assert(quant(3,3) == 3); 17 | assert(quant(4,3) == 3); 18 | assert(quant(7,3) == 6); 19 | } 20 | test_quant(); 21 | 22 | 23 | module test_quantdn() { 24 | assert(quantdn(-4,3) == -6); 25 | assert(quantdn(-3,3) == -3); 26 | assert(quantdn(-2,3) == -3); 27 | assert(quantdn(-1,3) == -3); 28 | assert(quantdn(0,3) == 0); 29 | assert(quantdn(1,3) == 0); 30 | assert(quantdn(2,3) == 0); 31 | assert(quantdn(3,3) == 3); 32 | assert(quantdn(4,3) == 3); 33 | assert(quantdn(7,3) == 6); 34 | } 35 | test_quantdn(); 36 | 37 | 38 | module test_quantup() { 39 | assert(quantup(-4,3) == -3); 40 | assert(quantup(-3,3) == -3); 41 | assert(quantup(-2,3) == 0); 42 | assert(quantup(-1,3) == 0); 43 | assert(quantup(0,3) == 0); 44 | assert(quantup(1,3) == 3); 45 | assert(quantup(2,3) == 3); 46 | assert(quantup(3,3) == 3); 47 | assert(quantup(4,3) == 6); 48 | assert(quantup(7,3) == 9); 49 | } 50 | test_quantup(); 51 | 52 | 53 | module test_constrain() { 54 | assert(constrain(-2,-1,1) == -1); 55 | assert(constrain(-1.75,-1,1) == -1); 56 | assert(constrain(-1,-1,1) == -1); 57 | assert(constrain(-0.75,-1,1) == -0.75); 58 | assert(constrain(0,-1,1) == 0); 59 | assert(constrain(0.75,-1,1) == 0.75); 60 | assert(constrain(1,-1,1) == 1); 61 | assert(constrain(1.75,-1,1) == 1); 62 | assert(constrain(2,-1,1) == 1); 63 | } 64 | test_constrain(); 65 | 66 | 67 | module test_posmod() { 68 | assert(posmod(-5,3) == 1); 69 | assert(posmod(-4,3) == 2); 70 | assert(posmod(-3,3) == 0); 71 | assert(posmod(-2,3) == 1); 72 | assert(posmod(-1,3) == 2); 73 | assert(posmod(0,3) == 0); 74 | assert(posmod(1,3) == 1); 75 | assert(posmod(2,3) == 2); 76 | assert(posmod(3,3) == 0); 77 | } 78 | test_posmod(); 79 | 80 | 81 | module test_modrange() { 82 | assert(modrange(-5,5,3) == [1,2]); 83 | assert(modrange(-1,4,3) == [2,0,1]); 84 | assert(modrange(1,8,10,step=2) == [1,3,5,7]); 85 | assert(modrange(5,12,10,step=2) == [5,7,9,1]); 86 | } 87 | test_modrange(); 88 | 89 | 90 | module test_segs() { 91 | assert(segs(50,$fn=8) == 8); 92 | assert(segs(50,$fa=2,$fs=2) == 158); 93 | } 94 | test_segs(); 95 | 96 | 97 | module test_lerp() { 98 | assert(lerp(-20,20,0) == -20); 99 | assert(lerp(-20,20,0.25) == -10); 100 | assert(lerp(-20,20,0.5) == 0); 101 | assert(lerp(-20,20,0.75) == 10); 102 | assert(lerp(-20,20,1) == 20); 103 | assert(lerp([10,10],[30,-10],0.5) == [20,0]); 104 | } 105 | test_lerp(); 106 | 107 | 108 | module test_hypot() { 109 | assert(hypot(20,30) == norm([20,30])); 110 | } 111 | test_hypot(); 112 | 113 | 114 | module test_sinh() { 115 | assert(abs(sinh(-2)+3.6268604078) < eps); 116 | assert(abs(sinh(-1)+1.1752011936) < eps); 117 | assert(abs(sinh(0)) < eps); 118 | assert(abs(sinh(1)-1.1752011936) < eps); 119 | assert(abs(sinh(2)-3.6268604078) < eps); 120 | } 121 | test_sinh(); 122 | 123 | 124 | module test_cosh() { 125 | assert(abs(cosh(-2)-3.7621956911) < eps); 126 | assert(abs(cosh(-1)-1.5430806348) < eps); 127 | assert(abs(cosh(0)-1) < eps); 128 | assert(abs(cosh(1)-1.5430806348) < eps); 129 | assert(abs(cosh(2)-3.7621956911) < eps); 130 | } 131 | test_cosh(); 132 | 133 | 134 | module test_tanh() { 135 | assert(abs(tanh(-2)+0.9640275801) < eps); 136 | assert(abs(tanh(-1)+0.761594156) < eps); 137 | assert(abs(tanh(0)) < eps); 138 | assert(abs(tanh(1)-0.761594156) < eps); 139 | assert(abs(tanh(2)-0.9640275801) < eps); 140 | } 141 | test_tanh(); 142 | 143 | 144 | module test_asinh() { 145 | assert(abs(asinh(sinh(-2))+2) < eps); 146 | assert(abs(asinh(sinh(-1))+1) < eps); 147 | assert(abs(asinh(sinh(0))) < eps); 148 | assert(abs(asinh(sinh(1))-1) < eps); 149 | assert(abs(asinh(sinh(2))-2) < eps); 150 | } 151 | test_asinh(); 152 | 153 | 154 | module test_acosh() { 155 | assert(abs(acosh(cosh(-2))-2) < eps); 156 | assert(abs(acosh(cosh(-1))-1) < eps); 157 | assert(abs(acosh(cosh(0))) < eps); 158 | assert(abs(acosh(cosh(1))-1) < eps); 159 | assert(abs(acosh(cosh(2))-2) < eps); 160 | } 161 | test_acosh(); 162 | 163 | 164 | module test_atanh() { 165 | assert(abs(atanh(tanh(-2))+2) < eps); 166 | assert(abs(atanh(tanh(-1))+1) < eps); 167 | assert(abs(atanh(tanh(0))) < eps); 168 | assert(abs(atanh(tanh(1))-1) < eps); 169 | assert(abs(atanh(tanh(2))-2) < eps); 170 | } 171 | test_atanh(); 172 | 173 | 174 | module test_sum() { 175 | assert(sum([1,2,3]) == 6); 176 | assert(sum([-2,-1,0,1,2]) == 0); 177 | assert(sum([[1,2,3], [3,4,5], [5,6,7]]) == [9,12,15]); 178 | } 179 | test_sum(); 180 | 181 | 182 | module test_sum_of_squares() { 183 | assert(sum_of_squares([1,2,3]) == 14); 184 | assert(sum_of_squares([1,2,4]) == 21); 185 | assert(sum_of_squares([-3,-2,-1]) == 14); 186 | } 187 | test_sum_of_squares(); 188 | 189 | 190 | module test_sum_of_sines() { 191 | assert(sum_of_sines(0, [[3,4,0],[2,2,0]]) == 0); 192 | assert(sum_of_sines(45, [[3,4,0],[2,2,0]]) == 2); 193 | assert(sum_of_sines(90, [[3,4,0],[2,2,0]]) == 0); 194 | assert(sum_of_sines(135, [[3,4,0],[2,2,0]]) == -2); 195 | assert(sum_of_sines(180, [[3,4,0],[2,2,0]]) == 0); 196 | } 197 | test_sum_of_sines(); 198 | 199 | 200 | module test_mean() { 201 | assert(mean([2,3,4]) == 3); 202 | assert(mean([[1,2,3], [3,4,5], [5,6,7]]) == [3,4,5]); 203 | } 204 | test_mean(); 205 | 206 | 207 | // Logic 208 | 209 | 210 | module test_compare_vals() { 211 | assert(compare_vals(-10,0) == -1); 212 | assert(compare_vals(10,0) == 1); 213 | assert(compare_vals(10,10) == 0); 214 | 215 | assert(compare_vals("abc","abcd") == -1); 216 | assert(compare_vals("abcd","abc") == 1); 217 | assert(compare_vals("abcd","abcd") == 0); 218 | 219 | assert(compare_vals(false,false) == 0); 220 | assert(compare_vals(true,false) == 1); 221 | assert(compare_vals(false,true) == -1); 222 | assert(compare_vals(true,true) == 0); 223 | 224 | assert(compare_vals([2,3,4], [2,3,4,5]) == -1); 225 | assert(compare_vals([2,3,4,5], [2,3,4,5]) == 0); 226 | assert(compare_vals([2,3,4,5], [2,3,4]) == 1); 227 | assert(compare_vals([2,3,4,5], [2,3,5,5]) == -1); 228 | assert(compare_vals([[2,3,4,5]], [[2,3,5,5]]) == -1); 229 | 230 | assert(compare_vals([[2,3,4],[3,4,5]], [[2,3,4], [3,4,5]]) == 0); 231 | assert(compare_vals([[2,3,4],[3,4,5]], [[2,3,4,5], [3,4,5]]) == -1); 232 | assert(compare_vals([[2,3,4],[3,4,5]], [[2,3,4], [3,4,5,6]]) == -1); 233 | assert(compare_vals([[2,3,4,5],[3,4,5]], [[2,3,4], [3,4,5]]) == 1); 234 | assert(compare_vals([[2,3,4],[3,4,5,6]], [[2,3,4], [3,4,5]]) == 1); 235 | assert(compare_vals([[2,3,4],[3,5,5]], [[2,3,4], [3,4,5]]) == 1); 236 | assert(compare_vals([[2,3,4],[3,4,5]], [[2,3,4], [3,5,5]]) == -1); 237 | } 238 | test_compare_vals(); 239 | 240 | 241 | module test_compare_lists() { 242 | assert(compare_lists([2,3,4], [2,3,4,5]) == -1); 243 | assert(compare_lists([2,3,4,5], [2,3,4,5]) == 0); 244 | assert(compare_lists([2,3,4,5], [2,3,4]) == 1); 245 | assert(compare_lists([2,3,4,5], [2,3,5,5]) == -1); 246 | 247 | assert(compare_lists([[2,3,4],[3,4,5]], [[2,3,4], [3,4,5]]) == 0); 248 | assert(compare_lists([[2,3,4],[3,4,5]], [[2,3,4,5], [3,4,5]]) == -1); 249 | assert(compare_lists([[2,3,4],[3,4,5]], [[2,3,4], [3,4,5,6]]) == -1); 250 | assert(compare_lists([[2,3,4,5],[3,4,5]], [[2,3,4], [3,4,5]]) == 1); 251 | assert(compare_lists([[2,3,4],[3,4,5,6]], [[2,3,4], [3,4,5]]) == 1); 252 | assert(compare_lists([[2,3,4],[3,5,5]], [[2,3,4], [3,4,5]]) == 1); 253 | assert(compare_lists([[2,3,4],[3,4,5]], [[2,3,4], [3,5,5]]) == -1); 254 | 255 | assert(compare_lists("cat", "bat") == 1); 256 | assert(compare_lists(["cat"], ["bat"]) == 1); 257 | } 258 | test_compare_lists(); 259 | 260 | 261 | module test_any() { 262 | assert(any([0,false,undef]) == false); 263 | assert(any([1,false,undef]) == true); 264 | assert(any([1,5,true]) == true); 265 | assert(any([[0,0], [0,0]]) == false); 266 | assert(any([[0,0], [1,0]]) == true); 267 | } 268 | test_any(); 269 | 270 | 271 | module test_all() { 272 | assert(all([0,false,undef]) == false); 273 | assert(all([1,false,undef]) == false); 274 | assert(all([1,5,true]) == true); 275 | assert(all([[0,0], [0,0]]) == false); 276 | assert(all([[0,0], [1,0]]) == false); 277 | assert(all([[1,1], [1,1]]) == true); 278 | } 279 | test_all(); 280 | 281 | 282 | module test_count_true() { 283 | assert(count_true([0,false,undef]) == 0); 284 | assert(count_true([1,false,undef]) == 1); 285 | assert(count_true([1,5,false]) == 2); 286 | assert(count_true([1,5,true]) == 3); 287 | assert(count_true([[0,0], [0,0]]) == 0); 288 | assert(count_true([[0,0], [1,0]]) == 1); 289 | assert(count_true([[1,1], [1,1]]) == 4); 290 | assert(count_true([[1,1], [1,1]], nmax=3) == 3); 291 | } 292 | test_count_true(); 293 | 294 | 295 | 296 | // List/Array Ops 297 | 298 | module test_cdr() { 299 | assert(cdr([]) == []); 300 | assert(cdr([88]) == []); 301 | assert(cdr([1,2,3]) == [2,3]); 302 | assert(cdr(["a","b","c"]) == ["b","c"]); 303 | } 304 | test_cdr(); 305 | 306 | 307 | module test_replist() { 308 | assert(replist(1, 4) == [1,1,1,1]); 309 | assert(replist(8, [2,3]) == [[8,8,8], [8,8,8]]); 310 | assert(replist(0, [2,2,3]) == [[[0,0,0],[0,0,0]], [[0,0,0],[0,0,0]]]); 311 | assert(replist([1,2,3],3) == [[1,2,3], [1,2,3], [1,2,3]]); 312 | } 313 | test_replist(); 314 | 315 | 316 | module test_in_list() { 317 | assert(in_list("bar", ["foo", "bar", "baz"])); 318 | assert(!in_list("bee", ["foo", "bar", "baz"])); 319 | assert(in_list("bar", [[2,"foo"], [4,"bar"], [3,"baz"]], idx=1)); 320 | } 321 | test_in_list(); 322 | 323 | 324 | module test_slice() { 325 | assert(slice([3,4,5,6,7,8,9], 3, 5) == [6,7]); 326 | assert(slice([3,4,5,6,7,8,9], 2, -1) == [5,6,7,8,9]); 327 | assert(slice([3,4,5,6,7,8,9], 1, 1) == []); 328 | assert(slice([3,4,5,6,7,8,9], 6, -1) == [9]); 329 | assert(slice([3,4,5,6,7,8,9], 2, -2) == [5,6,7,8]); 330 | } 331 | test_slice(); 332 | 333 | 334 | module test_select() { 335 | l = [3,4,5,6,7,8,9]; 336 | assert(select(l, 5, 6) == [8,9]); 337 | assert(select(l, 5, 8) == [8,9,3,4]); 338 | assert(select(l, 5, 2) == [8,9,3,4,5]); 339 | assert(select(l, -3, -1) == [7,8,9]); 340 | assert(select(l, 3, 3) == [6]); 341 | assert(select(l, 4) == 7); 342 | assert(select(l, -2) == 8); 343 | assert(select(l, [1:3]) == [4,5,6]); 344 | assert(select(l, [1,3]) == [4,6]); 345 | } 346 | test_select(); 347 | 348 | 349 | module test_reverse() { 350 | assert(reverse([3,4,5,6]) == [6,5,4,3]); 351 | } 352 | test_reverse(); 353 | 354 | 355 | module test_array_subindex() { 356 | v = [[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]]; 357 | assert(array_subindex(v,2) == [3, 7, 11, 15]); 358 | assert(array_subindex(v,[2,1]) == [[3, 2], [7, 6], [11, 10], [15, 14]]); 359 | assert(array_subindex(v,[1:3]) == [[2, 3, 4], [6, 7, 8], [10, 11, 12], [14, 15, 16]]); 360 | } 361 | test_array_subindex(); 362 | 363 | 364 | module test_list_range() { 365 | assert(list_range(4) == [0,1,2,3]); 366 | assert(list_range(n=4, step=2) == [0,2,4,6]); 367 | assert(list_range(n=4, s=3, step=3) == [3,6,9,12]); 368 | assert(list_range(n=4, s=3, e=9, step=3) == [3,6,9]); 369 | assert(list_range(e=3) == [0,1,2,3]); 370 | assert(list_range(e=6, step=2) == [0,2,4,6]); 371 | assert(list_range(s=3, e=5) == [3,4,5]); 372 | assert(list_range(s=3, e=8, step=2) == [3,5,7]); 373 | assert(list_range(s=4, e=8, step=2) == [4,6,8]); 374 | assert(list_range(n=4, s=[3,4], step=[2,3]) == [[3,4], [5,7], [7,10], [9,13]]); 375 | } 376 | test_list_range(); 377 | 378 | 379 | module test_array_shortest() { 380 | assert(array_shortest(["foobar", "bazquxx", "abcd"]) == 4); 381 | } 382 | test_array_shortest(); 383 | 384 | 385 | module test_array_longest() { 386 | assert(array_longest(["foobar", "bazquxx", "abcd"]) == 7); 387 | } 388 | test_array_longest(); 389 | 390 | 391 | module test_array_pad() { 392 | assert(array_pad([4,5,6], 5, 8) == [4,5,6,8,8]); 393 | assert(array_pad([4,5,6,7,8], 5, 8) == [4,5,6,7,8]); 394 | assert(array_pad([4,5,6,7,8,9], 5, 8) == [4,5,6,7,8,9]); 395 | } 396 | test_array_pad(); 397 | 398 | 399 | module test_array_trim() { 400 | assert(array_trim([4,5,6], 5) == [4,5,6]); 401 | assert(array_trim([4,5,6,7,8], 5) == [4,5,6,7,8]); 402 | assert(array_trim([3,4,5,6,7,8,9], 5) == [3,4,5,6,7]); 403 | } 404 | test_array_trim(); 405 | 406 | 407 | module test_array_fit() { 408 | assert(array_fit([4,5,6], 5, 8) == [4,5,6,8,8]); 409 | assert(array_fit([4,5,6,7,8], 5, 8) == [4,5,6,7,8]); 410 | assert(array_fit([3,4,5,6,7,8,9], 5, 8) == [3,4,5,6,7]); 411 | } 412 | test_array_fit(); 413 | 414 | 415 | module test_enumerate() { 416 | assert(enumerate(["a","b","c"]) == [[0,"a"], [1,"b"], [2,"c"]]); 417 | assert(enumerate([[88,"a"],[76,"b"],[21,"c"]], idx=1) == [[0,"a"], [1,"b"], [2,"c"]]); 418 | assert(enumerate([["cat","a",12],["dog","b",10],["log","c",14]], idx=[1:2]) == [[0,"a",12], [1,"b",10], [2,"c",14]]); 419 | } 420 | test_enumerate(); 421 | 422 | 423 | module test_array_zip() { 424 | v1 = [1,2,3,4]; 425 | v2 = [5,6,7]; 426 | v3 = [8,9,10,11]; 427 | assert(array_zip(v1,v3) == [[1,8],[2,9],[3,10],[4,11]]); 428 | assert(array_zip([v1,v3]) == [[1,8],[2,9],[3,10],[4,11]]); 429 | assert(array_zip([v1,v2],fit="short") == [[1,5],[2,6],[3,7]]); 430 | assert(array_zip([v1,v2],fit="long") == [[1,5],[2,6],[3,7],[4,undef]]); 431 | assert(array_zip([v1,v2],fit="long", fill=0) == [[1,5],[2,6],[3,7],[4,0]]); 432 | assert(array_zip([v1,v2,v3],fit="long") == [[1,5,8],[2,6,9],[3,7,10],[4,undef,11]]); 433 | } 434 | test_array_zip(); 435 | 436 | 437 | module test_array_group() { 438 | v = [1,2,3,4,5,6]; 439 | assert(array_group(v,2) == [[1,2], [3,4], [5,6]]); 440 | assert(array_group(v,3) == [[1,2,3], [4,5,6]]); 441 | assert(array_group(v,4,0) == [[1,2,3,4], [5,6,0,0]]); 442 | } 443 | test_array_group(); 444 | 445 | 446 | module test_flatten() { 447 | assert(flatten([[1,2,3], [4,5,[6,7,8]]]) == [1,2,3,4,5,[6,7,8]]); 448 | } 449 | test_flatten(); 450 | 451 | 452 | module test_sort() { 453 | assert(sort([7,3,9,4,3,1,8]) == [1,3,3,4,7,8,9]); 454 | assert(sort(["cat", "oat", "sat", "bat", "vat", "rat", "pat", "mat", "fat", "hat", "eat"]) == ["bat", "cat", "eat", "fat", "hat", "mat", "oat", "pat", "rat", "sat", "vat"]); 455 | assert(sort(enumerate([[2,3,4],[1,2,3],[2,4,3]]),idx=1)==[[1,[1,2,3]], [0,[2,3,4]], [2,[2,4,3]]]); 456 | } 457 | test_sort(); 458 | 459 | 460 | module test_sortidx() { 461 | lst1 = ["d","b","e","c"]; 462 | assert(sortidx(lst1) == [1,3,0,2]); 463 | lst2 = [ 464 | ["foo", 88, [0,0,1], false], 465 | ["bar", 90, [0,1,0], true], 466 | ["baz", 89, [1,0,0], false], 467 | ["qux", 23, [1,1,1], true] 468 | ]; 469 | assert(sortidx(lst2, idx=1) == [3,0,2,1]); 470 | assert(sortidx(lst2, idx=0) == [1,2,0,3]); 471 | assert(sortidx(lst2, idx=[1,3]) == [3,0,2,1]); 472 | lst3 = [[-4, 0, 0], [0, 0, -4], [0, -4, 0], [-4, 0, 0], [0, -4, 0], [0, 0, 4], [0, 0, -4], [0, 4, 0], [4, 0, 0], [0, 0, 4], [0, 4, 0], [4, 0, 0]]; 473 | assert(sortidx(lst3)==[0,3,2,4,1,6,5,9,7,10,8,11]); 474 | } 475 | test_sortidx(); 476 | 477 | 478 | module test_unique() { 479 | assert(unique([]) == []); 480 | assert(unique([8]) == [8]); 481 | assert(unique([7,3,9,4,3,1,8]) == [1,3,4,7,8,9]); 482 | } 483 | test_unique(); 484 | 485 | 486 | module test_array_dim() { 487 | assert(array_dim([[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]]) == [2,2,3]); 488 | assert(array_dim([[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]], 0) == 2); 489 | assert(array_dim([[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]], 2) == 3); 490 | assert(array_dim([[[1,2,3],[4,5,6]],[[7,8,9]]]) == [2,undef,3]); 491 | } 492 | test_array_dim(); 493 | 494 | 495 | module test_vmul() { 496 | assert(vmul([3,4,5], [8,7,6]) == [24,28,30]); 497 | assert(vmul([1,2,3], [4,5,6]) == [4,10,18]); 498 | } 499 | test_vmul(); 500 | 501 | 502 | module test_vdiv() { 503 | assert(vdiv([24,28,30], [8,7,6]) == [3, 4, 5]); 504 | } 505 | test_vdiv(); 506 | 507 | 508 | module test_vabs() { 509 | assert(vabs([2,4,8]) == [2,4,8]); 510 | assert(vabs([-2,-4,-8]) == [2,4,8]); 511 | assert(vabs([-2,4,8]) == [2,4,8]); 512 | assert(vabs([2,-4,8]) == [2,4,8]); 513 | assert(vabs([2,4,-8]) == [2,4,8]); 514 | } 515 | test_vabs(); 516 | 517 | 518 | module test_normalize() { 519 | assert(normalize([10,0,0]) == [1,0,0]); 520 | assert(normalize([0,10,0]) == [0,1,0]); 521 | assert(normalize([0,0,10]) == [0,0,1]); 522 | assert(abs(norm(normalize([10,10,10]))-1) < eps); 523 | assert(abs(norm(normalize([-10,-10,-10]))-1) < eps); 524 | assert(abs(norm(normalize([-10,0,0]))-1) < eps); 525 | assert(abs(norm(normalize([0,-10,0]))-1) < eps); 526 | assert(abs(norm(normalize([0,0,-10]))-1) < eps); 527 | } 528 | test_normalize(); 529 | 530 | 531 | module test_vector_angle() { 532 | vecs = [[10,0,0], [-10,0,0], [0,10,0], [0,-10,0], [0,0,10], [0,0,-10]]; 533 | for (a=vecs, b=vecs) { 534 | if(a==b) { 535 | assert(vector_angle(a,b)==0); 536 | } else if(a==-b) { 537 | assert(vector_angle(a,b)==180); 538 | } else { 539 | assert(vector_angle(a,b)==90); 540 | } 541 | } 542 | assert(abs(vector_angle([10,10,0],[10,0,0])-45) < eps); 543 | } 544 | test_vector_angle(); 545 | 546 | 547 | module test_vector_axis() { 548 | assert(norm(vector_axis([10,0,0],[10,10,0]) - [0,0,1]) < eps); 549 | assert(norm(vector_axis([10,0,0],[0,10,0]) - [0,0,1]) < eps); 550 | assert(norm(vector_axis([0,10,0],[10,0,0]) - [0,0,-1]) < eps); 551 | assert(norm(vector_axis([0,0,10],[10,0,0]) - [0,1,0]) < eps); 552 | assert(norm(vector_axis([10,0,0],[0,0,10]) - [0,-1,0]) < eps); 553 | assert(norm(vector_axis([10,0,10],[0,-10,0]) - [sin(45),0,-sin(45)]) < eps); 554 | } 555 | test_vector_axis(); 556 | 557 | 558 | module test_point2d() { 559 | assert(point2d([1,2,3])==[1,2]); 560 | assert(point2d([2,3])==[2,3]); 561 | assert(point2d([1])==[1,0]); 562 | } 563 | test_point2d(); 564 | 565 | 566 | module test_path2d() { 567 | assert(path2d([[1], [1,2], [1,2,3], [1,2,3,4], [1,2,3,4,5]])==[[1,0],[1,2],[1,2],[1,2],[1,2]]); 568 | } 569 | test_path2d(); 570 | 571 | 572 | module test_point3d() { 573 | assert(point3d([1,2,3,4,5])==[1,2,3]); 574 | assert(point3d([1,2,3,4])==[1,2,3]); 575 | assert(point3d([1,2,3])==[1,2,3]); 576 | assert(point3d([2,3])==[2,3,0]); 577 | assert(point3d([1])==[1,0,0]); 578 | } 579 | test_point3d(); 580 | 581 | 582 | module test_path3d() { 583 | assert(path3d([[1], [1,2], [1,2,3], [1,2,3,4], [1,2,3,4,5]])==[[1,0,0],[1,2,0],[1,2,3],[1,2,3],[1,2,3]]); 584 | } 585 | test_path3d(); 586 | 587 | 588 | module test_translate_points() { 589 | pts = [[0,0,1], [0,1,0], [1,0,0], [0,0,-1], [0,-1,0], [-1,0,0]]; 590 | assert(translate_points(pts, v=[1,2,3]) == [[1,2,4], [1,3,3], [2,2,3], [1,2,2], [1,1,3], [0,2,3]]); 591 | assert(translate_points(pts, v=[-1,-2,-3]) == [[-1,-2,-2], [-1,-1,-3], [0,-2,-3], [-1,-2,-4], [-1,-3,-3], [-2,-2,-3]]); 592 | } 593 | test_translate_points(); 594 | 595 | 596 | module test_scale_points() { 597 | pts = [[0,0,1], [0,1,0], [1,0,0], [0,0,-1], [0,-1,0], [-1,0,0]]; 598 | assert(scale_points(pts, v=[2,3,4]) == [[0,0,4], [0,3,0], [2,0,0], [0,0,-4], [0,-3,0], [-2,0,0]]); 599 | assert(scale_points(pts, v=[-2,-3,-4]) == [[0,0,-4], [0,-3,0], [-2,0,0], [0,0,4], [0,3,0], [2,0,0]]); 600 | assert(scale_points(pts, v=[1,1,1]) == [[0,0,1], [0,1,0], [1,0,0], [0,0,-1], [0,-1,0], [-1,0,0]]); 601 | assert(scale_points(pts, v=[-1,-1,-1]) == [[0,0,-1], [0,-1,0], [-1,0,0], [0,0,1], [0,1,0], [1,0,0]]); 602 | } 603 | test_scale_points(); 604 | 605 | 606 | module test_rotate_points2d() { 607 | pts = [[0,1], [1,0], [0,-1], [-1,0]]; 608 | s = sin(45); 609 | assert(rotate_points2d(pts,45) == [[-s,s],[s,s],[s,-s],[-s,-s]]); 610 | assert(rotate_points2d(pts,90) == [[-1,0],[0,1],[1,0],[0,-1]]); 611 | assert(rotate_points2d(pts,90,cp=[1,0]) == [[0,-1],[1,0],[2,-1],[1,-2]]); 612 | } 613 | test_rotate_points2d(); 614 | 615 | 616 | module test_rotate_points3d() { 617 | pts = [[0,0,1], [0,1,0], [1,0,0], [0,0,-1], [0,-1,0], [-1,0,0]]; 618 | assert(rotate_points3d(pts, [90,0,0]) == [[0,-1,0], [0,0,1], [1,0,0], [0,1,0], [0,0,-1], [-1,0,0]]); 619 | assert(rotate_points3d(pts, [0,90,0]) == [[1,0,0], [0,1,0], [0,0,-1], [-1,0,0], [0,-1,0], [0,0,1]]); 620 | assert(rotate_points3d(pts, [0,0,90]) == [[0,0,1], [-1,0,0], [0,1,0], [0,0,-1], [1,0,0], [0,-1,0]]); 621 | assert(rotate_points3d(pts, [0,0,90],cp=[2,0,0]) == [[2,-2,1], [1,-2,0], [2,-1,0], [2,-2,-1], [3,-2,0], [2,-3,0]]); 622 | assert(rotate_points3d(pts, 90, axis=V_UP) == [[0,0,1], [-1,0,0], [0,1,0], [0,0,-1], [1,0,0], [0,-1,0]]); 623 | assert(rotate_points3d(pts, 90, axis=V_DOWN) == [[0,0,1], [1,0,0], [0,-1,0], [0,0,-1], [-1,0,0], [0,1,0]]); 624 | assert(rotate_points3d(pts, 90, axis=V_RIGHT) == [[0,-1,0], [0,0,1], [1,0,0], [0,1,0], [0,0,-1], [-1,0,0]]); 625 | assert(rotate_points3d(pts, from=V_UP, to=V_BACK) == [[0,1,0], [0,0,-1], [1,0,0], [0,-1,0], [0,0,1], [-1,0,0]]); 626 | assert(rotate_points3d(pts, 90, from=V_UP, to=V_BACK), [[0,1,0], [-1,0,0], [0,0,-1], [0,-1,0], [1,0,0], [0,0,1]]); 627 | assert(rotate_points3d(pts, from=V_UP, to=V_UP*2) == [[0,0,1], [0,1,0], [1,0,0], [0,0,-1], [0,-1,0], [-1,0,0]]); 628 | assert(rotate_points3d(pts, from=V_UP, to=V_DOWN*2) == [[0,0,-1], [0,1,0], [-1,0,0], [0,0,1], [0,-1,0], [1,0,0]]); 629 | } 630 | test_rotate_points3d(); 631 | 632 | 633 | module test_simplify_path() 634 | { 635 | path = [[-20,10],[-10,0],[-5,0],[0,0],[5,0],[10,0], [10,10]]; 636 | assert(simplify_path(path) == [[-20,10],[-10,0],[10,0], [10,10]]); 637 | } 638 | test_simplify_path(); 639 | 640 | 641 | module test_simplify_path_indexed() 642 | { 643 | points = [[-20,10],[-10,0],[-5,0],[0,0],[5,0],[10,0], [10,10]]; 644 | path = list_range(len(points)); 645 | assert(simplify_path_indexed(points, path) == [0,1,5,6]); 646 | } 647 | test_simplify_path_indexed(); 648 | 649 | 650 | // vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 651 | -------------------------------------------------------------------------------- /metric_screws.scad: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////// 2 | // LibFile: metric_screws.scad 3 | // Screws, Bolts, and Nuts. 4 | // To use, include the following lines at the top of your file: 5 | // ``` 6 | // include 7 | // use 8 | // ``` 9 | ////////////////////////////////////////////////////////////////////// 10 | 11 | /* 12 | BSD 2-Clause License 13 | 14 | Copyright (c) 2017, Revar Desmera 15 | All rights reserved. 16 | 17 | Redistribution and use in source and binary forms, with or without 18 | modification, are permitted provided that the following conditions are met: 19 | 20 | * Redistributions of source code must retain the above copyright notice, this 21 | list of conditions and the following disclaimer. 22 | 23 | * Redistributions in binary form must reproduce the above copyright notice, 24 | this list of conditions and the following disclaimer in the documentation 25 | and/or other materials provided with the distribution. 26 | 27 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 28 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 29 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 30 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 31 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 32 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 33 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 34 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 35 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 36 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 37 | */ 38 | 39 | 40 | include 41 | use 42 | use 43 | use 44 | use 45 | use 46 | use 47 | 48 | 49 | // Section: Functions 50 | 51 | 52 | // Function: get_metric_bolt_head_size() 53 | // Description: Returns the diameter of a typical metric bolt's head, based on the bolt `size`. 54 | function get_metric_bolt_head_size(size) = lookup(size, [ 55 | [ 3.0, 5.5], 56 | [ 4.0, 7.0], 57 | [ 5.0, 8.0], 58 | [ 6.0, 10.0], 59 | [ 7.0, 11.0], 60 | [ 8.0, 13.0], 61 | [10.0, 17.0], 62 | [12.0, 19.0], 63 | [14.0, 22.0], 64 | [16.0, 24.0], 65 | [18.0, 27.0], 66 | [20.0, 30.0], 67 | [24.0, 36.0], 68 | [30.0, 46.0], 69 | [36.0, 55.0], 70 | [42.0, 65.0], 71 | [48.0, 75.0], 72 | [56.0, 85.0], 73 | [64.0, 95.0] 74 | ]); 75 | 76 | 77 | // Function: get_metric_bolt_head_height() 78 | // Description: Returns the height of a typical metric bolt's head, based on the bolt `size`. 79 | function get_metric_bolt_head_height(size) = lookup(size, [ 80 | [ 1.6, 1.23], 81 | [ 2.0, 1.53], 82 | [ 2.5, 1.83], 83 | [ 3.0, 2.13], 84 | [ 4.0, 2.93], 85 | [ 5.0, 3.65], 86 | [ 6.0, 4.15], 87 | [ 8.0, 5.45], 88 | [10.0, 6.58], 89 | [12.0, 7.68], 90 | [14.0, 8.98], 91 | [16.0, 10.18], 92 | [20.0, 12.72], 93 | [24.0, 15.35], 94 | [30.0, 19.12], 95 | [36.0, 22.92], 96 | [42.0, 26.42], 97 | [48.0, 30.42], 98 | [56.0, 35.50], 99 | [64.0, 40.50] 100 | ]); 101 | 102 | 103 | // Function: get_metric_socket_cap_diam() 104 | // Description: Returns the diameter of a typical metric socket cap bolt's head, based on the bolt `size`. 105 | function get_metric_socket_cap_diam(size) = lookup(size, [ 106 | [ 1.6, 3.0], 107 | [ 2.0, 3.8], 108 | [ 2.5, 4.5], 109 | [ 3.0, 5.5], 110 | [ 4.0, 7.0], 111 | [ 5.0, 8.5], 112 | [ 6.0, 10.0], 113 | [ 8.0, 13.0], 114 | [10.0, 16.0], 115 | [12.0, 18.0], 116 | [14.0, 21.0], 117 | [16.0, 24.0], 118 | [18.0, 27.0], 119 | [20.0, 30.0], 120 | [22.0, 33.0], 121 | [24.0, 36.0], 122 | [27.0, 40.0], 123 | [30.0, 45.0], 124 | [33.0, 50.0], 125 | [36.0, 54.0], 126 | [42.0, 63.0], 127 | [48.0, 72.0], 128 | [56.0, 84.0], 129 | [64.0, 96.0] 130 | ]); 131 | 132 | 133 | // Function: get_metric_socket_cap_height() 134 | // Description: Returns the height of a typical metric socket cap bolt's head, based on the bolt `size`. 135 | function get_metric_socket_cap_height(size) = lookup(size, [ 136 | [ 1.6, 1.7], 137 | [ 2.0, 2.0], 138 | [ 2.5, 2.5], 139 | [ 3.0, 3.0], 140 | [ 4.0, 4.0], 141 | [ 5.0, 5.0], 142 | [ 6.0, 6.0], 143 | [ 8.0, 8.0], 144 | [10.0, 10.0], 145 | [12.0, 12.0], 146 | [14.0, 14.0], 147 | [16.0, 16.0], 148 | [18.0, 18.0], 149 | [20.0, 20.0], 150 | [22.0, 22.0], 151 | [24.0, 24.0], 152 | [27.0, 27.0], 153 | [30.0, 30.0], 154 | [33.0, 33.0], 155 | [36.0, 36.0], 156 | [42.0, 42.0], 157 | [48.0, 48.0], 158 | [56.0, 56.0], 159 | [64.0, 64.0] 160 | ]); 161 | 162 | 163 | // Function: get_metric_socket_cap_socket_size() 164 | // Description: Returns the diameter of a typical metric socket cap bolt's hex drive socket, based on the bolt `size`. 165 | function get_metric_socket_cap_socket_size(size) = lookup(size, [ 166 | [ 1.6, 1.5], 167 | [ 2.0, 1.5], 168 | [ 2.5, 2.0], 169 | [ 3.0, 2.5], 170 | [ 4.0, 3.0], 171 | [ 5.0, 4.0], 172 | [ 6.0, 5.0], 173 | [ 8.0, 6.0], 174 | [10.0, 8.0], 175 | [12.0, 10.0], 176 | [14.0, 12.0], 177 | [16.0, 14.0], 178 | [18.0, 14.0], 179 | [20.0, 17.0], 180 | [22.0, 17.0], 181 | [24.0, 19.0], 182 | [27.0, 19.0], 183 | [30.0, 22.0], 184 | [33.0, 24.0], 185 | [36.0, 27.0], 186 | [42.0, 32.0], 187 | [48.0, 36.0], 188 | [56.0, 41.0], 189 | [64.0, 46.0] 190 | ]); 191 | 192 | 193 | // Function: get_metric_socket_cap_socket_depth() 194 | // Description: Returns the depth of a typical metric socket cap bolt's hex drive socket, based on the bolt `size`. 195 | function get_metric_socket_cap_socket_depth(size) = lookup(size, [ 196 | [ 1.6, 0.7], 197 | [ 2.0, 1.0], 198 | [ 2.5, 1.1], 199 | [ 3.0, 1.3], 200 | [ 4.0, 2.0], 201 | [ 5.0, 2.5], 202 | [ 6.0, 3.0], 203 | [ 8.0, 4.0], 204 | [10.0, 5.0], 205 | [12.0, 6.0], 206 | [14.0, 7.0], 207 | [16.0, 8.0], 208 | [18.0, 9.0], 209 | [20.0, 10.0], 210 | [22.0, 11.0], 211 | [24.0, 12.0], 212 | [27.0, 13.5], 213 | [30.0, 15.5], 214 | [33.0, 18.0], 215 | [36.0, 19.0], 216 | [42.0, 24.0], 217 | [48.0, 28.0], 218 | [56.0, 34.0], 219 | [64.0, 38.0] 220 | ]); 221 | 222 | 223 | // Function: get_metric_iso_coarse_thread_pitch() 224 | // Description: Returns the ISO metric standard coarse threading pitch for a given bolt `size`. 225 | function get_metric_iso_coarse_thread_pitch(size) = lookup(size, [ 226 | [ 1.6, 0.35], 227 | [ 2.0, 0.40], 228 | [ 2.5, 0.45], 229 | [ 3.0, 0.50], 230 | [ 4.0, 0.70], 231 | [ 5.0, 0.80], 232 | [ 6.0, 1.00], 233 | [ 7.0, 1.00], 234 | [ 8.0, 1.25], 235 | [10.0, 1.50], 236 | [12.0, 1.75], 237 | [14.0, 2.00], 238 | [16.0, 2.00], 239 | [18.0, 2.50], 240 | [20.0, 2.50], 241 | [22.0, 2.50], 242 | [24.0, 3.00], 243 | [27.0, 3.00], 244 | [30.0, 3.50], 245 | [33.0, 3.50], 246 | [36.0, 4.00], 247 | [39.0, 4.00], 248 | [42.0, 4.50], 249 | [45.0, 4.50], 250 | [48.0, 5.00], 251 | [56.0, 5.50], 252 | [64.0, 6.00] 253 | ]); 254 | 255 | 256 | // Function: get_metric_iso_fine_thread_pitch() 257 | // Description: Returns the ISO metric standard fine threading pitch for a given bolt `size`. 258 | function get_metric_iso_fine_thread_pitch(size) = lookup(size, [ 259 | [ 1.6, 0.35], 260 | [ 2.0, 0.40], 261 | [ 2.5, 0.45], 262 | [ 3.0, 0.50], 263 | [ 4.0, 0.70], 264 | [ 5.0, 0.80], 265 | [ 6.0, 1.00], 266 | [ 7.0, 1.00], 267 | [ 8.0, 1.00], 268 | [10.0, 1.25], 269 | [12.0, 1.50], 270 | [14.0, 1.50], 271 | [16.0, 2.00], 272 | [18.0, 2.50], 273 | [20.0, 2.50], 274 | [22.0, 2.50], 275 | [24.0, 3.00], 276 | [27.0, 3.00], 277 | [30.0, 3.50], 278 | [33.0, 3.50], 279 | [36.0, 4.00], 280 | [39.0, 4.00], 281 | [42.0, 4.50], 282 | [45.0, 4.50], 283 | [48.0, 5.00], 284 | [56.0, 5.50], 285 | [64.0, 6.00] 286 | ]); 287 | 288 | 289 | // Function: get_metric_iso_superfine_thread_pitch() 290 | // Description: Returns the ISO metric standard superfine threading pitch for a given bolt `size`. 291 | function get_metric_iso_superfine_thread_pitch(size) = lookup(size, [ 292 | [ 1.6, 0.35], 293 | [ 2.0, 0.40], 294 | [ 2.5, 0.45], 295 | [ 3.0, 0.50], 296 | [ 4.0, 0.70], 297 | [ 5.0, 0.80], 298 | [ 6.0, 1.00], 299 | [ 7.0, 1.00], 300 | [ 8.0, 1.00], 301 | [10.0, 1.00], 302 | [12.0, 1.25], 303 | [14.0, 1.50], 304 | [16.0, 2.00], 305 | [18.0, 2.50], 306 | [20.0, 2.50], 307 | [22.0, 2.50], 308 | [24.0, 3.00], 309 | [27.0, 3.00], 310 | [30.0, 3.50], 311 | [33.0, 3.50], 312 | [36.0, 4.00], 313 | [39.0, 4.00], 314 | [42.0, 4.50], 315 | [45.0, 4.50], 316 | [48.0, 5.00], 317 | [56.0, 5.50], 318 | [64.0, 6.00] 319 | ]); 320 | 321 | 322 | // Function: get_metric_jis_thread_pitch() 323 | // Description: Returns the JIS metric standard threading pitch for a given bolt `size`. 324 | function get_metric_jis_thread_pitch(size) = lookup(size, [ 325 | [ 2.0, 0.40], 326 | [ 2.5, 0.45], 327 | [ 3.0, 0.50], 328 | [ 4.0, 0.70], 329 | [ 5.0, 0.80], 330 | [ 6.0, 1.00], 331 | [ 7.0, 1.00], 332 | [ 8.0, 1.25], 333 | [10.0, 1.25], 334 | [12.0, 1.25], 335 | [14.0, 1.50], 336 | [16.0, 1.50], 337 | [18.0, 1.50], 338 | [20.0, 1.50] 339 | ]); 340 | 341 | 342 | // Function: get_metric_nut_size() 343 | // Description: Returns the typical metric nut flat-to-flat diameter for a given bolt `size`. 344 | function get_metric_nut_size(size) = lookup(size, [ 345 | [ 2.0, 4.0], 346 | [ 2.5, 5.0], 347 | [ 3.0, 5.5], 348 | [ 4.0, 7.0], 349 | [ 5.0, 8.0], 350 | [ 6.0, 10.0], 351 | [ 7.0, 11.0], 352 | [ 8.0, 13.0], 353 | [10.0, 17.0], 354 | [12.0, 19.0], 355 | [14.0, 22.0], 356 | [16.0, 24.0], 357 | [18.0, 27.0], 358 | [20.0, 30.0], 359 | [22.0, 32.0], 360 | [24.0, 36.0], 361 | [27.0, 41.0], 362 | [30.0, 46.0], 363 | [33.0, 50.0], 364 | [36.0, 55.0], 365 | [39.0, 60.0], 366 | [42.0, 65.0], 367 | [45.0, 70.0], 368 | [48.0, 75.0], 369 | [52.0, 80.0], 370 | [56.0, 85.0], 371 | [60.0, 90.0], 372 | [64.0, 95.0], 373 | [68.0, 100.0], 374 | [72.0, 105.0] 375 | ]); 376 | 377 | 378 | // Function: get_metric_nut_thickness() 379 | // Description: Returns the typical metric nut thickness for a given bolt `size`. 380 | function get_metric_nut_thickness(size) = lookup(size, [ 381 | [ 1.6, 1.3], 382 | [ 2.0, 1.6], 383 | [ 2.5, 2.0], 384 | [ 3.0, 2.4], 385 | [ 4.0, 3.2], 386 | [ 5.0, 4.0], 387 | [ 6.0, 5.0], 388 | [ 7.0, 5.5], 389 | [ 8.0, 6.5], 390 | [10.0, 8.0], 391 | [12.0, 10.0], 392 | [14.0, 11.0], 393 | [16.0, 13.0], 394 | [18.0, 15.0], 395 | [20.0, 16.0], 396 | [24.0, 21.5], 397 | [30.0, 25.6], 398 | [36.0, 31.0], 399 | [42.0, 34.0], 400 | [48.0, 38.0], 401 | [56.0, 45.0], 402 | [64.0, 51.0] 403 | ]); 404 | 405 | 406 | 407 | // Section: Modules 408 | 409 | 410 | // Module: screw() 411 | // Description: 412 | // Makes a very simple screw model, useful for making screwholes. 413 | // Usage: 414 | // screw(screwsize, screwlen, headsize, headlen, [countersunk], [orient], [align]) 415 | // Arguments: 416 | // screwsize = diameter of threaded part of screw. 417 | // screwlen = length of threaded part of screw. 418 | // headsize = diameter of the screw head. 419 | // headlen = length of the screw head. 420 | // countersunk = If true, center from cap's top instead of it's bottom. 421 | // orient = Orientation of the screw. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. 422 | // align = Alignment of the screw. Use the `V_` constants from `constants.scad` or `"sunken"`, or `"base"`. Default: `"base"`. 423 | // Examples: 424 | // screw(screwsize=3,screwlen=10,headsize=6,headlen=3,countersunk=true); 425 | // screw(screwsize=3,screwlen=10,headsize=6,headlen=3, align="base"); 426 | module screw( 427 | screwsize=3, 428 | screwlen=10, 429 | headsize=6, 430 | headlen=3, 431 | pitch=undef, 432 | countersunk=false, 433 | orient=ORIENT_Z, 434 | align="base" 435 | ) { 436 | sides = max(12, segs(screwsize/2)); 437 | algn = countersunk? ALIGN_NEG : align; 438 | alignments = [ 439 | ["base", [0,0,-headlen/2+screwlen/2]], 440 | ["sunken", [0,0,(headlen+screwlen)/2-0.01]] 441 | ]; 442 | orient_and_align([headsize, headsize, headlen+screwlen], orient, algn, alignments=alignments) { 443 | down(headlen/2-screwlen/2) { 444 | down(screwlen/2) { 445 | if (pitch == undef) { 446 | cylinder(r=screwsize/2, h=screwlen+0.05, center=true, $fn=sides); 447 | } else { 448 | threaded_rod(d=screwsize, l=screwlen+0.05, pitch=pitch, $fn=sides); 449 | } 450 | } 451 | up(headlen/2) cylinder(r=headsize/2, h=headlen, center=true, $fn=sides*2); 452 | } 453 | } 454 | } 455 | 456 | 457 | // Module: metric_bolt() 458 | // Description: 459 | // Makes a standard metric screw model. 460 | // Arguments: 461 | // size = Diameter of threaded part of screw. 462 | // headtype = One of `"hex"`, `"pan"`, `"button"`, `"round"`, `"countersunk"`, `"oval"`, `"socket`". Default: `"socket"` 463 | // l = Length of screw, except for the head. 464 | // shank = Length of unthreaded portion of the shaft. 465 | // pitch = If given, render threads of the given pitch. If 0, then no threads. Overrides coarse argument. 466 | // details = If true model should be rendered with extra details. (Default: false) 467 | // coarse = If true, make coarse threads instead of fine threads. Default = true 468 | // flange = Radius of flange beyond the head. Default = 0 (no flange) 469 | // phillips = If given, the size of the phillips drive hole to add. (ie: "#1", "#2", or "#3") 470 | // torx = If given, the size of the torx drive hole to add. (ie: 10, 20, 30, etc.) 471 | // orient = Orientation of the bolt. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. 472 | // align = Alignment of the bolt. Use the `V_` constants from `constants.scad` or `"sunken"`, `"base"`, or `"shank"`. Default: `"base"`. 473 | // Example: Bolt Head Types 474 | // ydistribute(40) { 475 | // xdistribute(30) { 476 | // // Front Row, Left to Right 477 | // metric_bolt(headtype="pan", size=10, l=15, details=true, phillips="#2"); 478 | // metric_bolt(headtype="button", size=10, l=15, details=true, phillips="#2"); 479 | // metric_bolt(headtype="round", size=10, l=15, details=true, phillips="#2"); 480 | // } 481 | // xdistribute(30) { 482 | // // Back Row, Left to Right 483 | // metric_bolt(headtype="socket", size=10, l=15, details=true); 484 | // metric_bolt(headtype="hex", size=10, l=15, details=true, phillips="#2"); 485 | // metric_bolt(headtype="countersunk", size=10, l=15, details=true, phillips="#2"); 486 | // metric_bolt(headtype="oval", size=10, l=15, details=true, phillips="#2"); 487 | // } 488 | // } 489 | // Example: Details 490 | // metric_bolt(size=10, l=15, details=true, $fn=32); 491 | // Example: No Details Except Threads 492 | // metric_bolt(size=10, l=15); 493 | // Example: No Details, No Threads 494 | // metric_bolt(size=10, l=15, pitch=0); 495 | // Example: Fine Threads 496 | // metric_bolt(size=10, l=15, coarse=false); 497 | // Example: Flange 498 | // metric_bolt(size=10, l=15, flange=5); 499 | // Example: Shank 500 | // metric_bolt(size=10, l=25, shank=10); 501 | // Example: Hex Head with Phillips 502 | // metric_bolt(headtype="hex", size=10, l=15, phillips="#2"); 503 | // Example: Hex Head with Torx 504 | // metric_bolt(headtype="hex", size=10, l=15, torx=50); 505 | module metric_bolt( 506 | headtype="socket", 507 | size=3, 508 | l=12, 509 | shank=0, 510 | pitch=undef, 511 | details=false, 512 | coarse=true, 513 | phillips=undef, 514 | torx=undef, 515 | flange=0, 516 | orient=ORIENT_Z, 517 | align="base" 518 | ) { 519 | D = headtype != "hex"? 520 | get_metric_socket_cap_diam(size) : 521 | get_metric_bolt_head_size(size); 522 | H = headtype == "socket"? 523 | get_metric_socket_cap_height(size) : 524 | get_metric_bolt_head_height(size); 525 | P = coarse? 526 | (pitch==undef? get_metric_iso_coarse_thread_pitch(size) : pitch) : 527 | (pitch==undef? get_metric_iso_fine_thread_pitch(size) : pitch); 528 | tlen = l - min(l, shank); 529 | sides = max(12, segs(size/2)); 530 | tcirc = D/cos(30); 531 | bevtop = (tcirc-D)/2; 532 | bevbot = P/2; 533 | 534 | //algn = (headtype == "countersunk" || headtype == "oval")? (D-size)/2 : 0; 535 | headlen = ( 536 | (headtype == "pan" || headtype == "round" || headtype == "button")? H*0.75 : 537 | (headtype == "countersunk")? (D-size)/2 : 538 | (headtype == "oval")? ((D-size)/2 + D/2/3) : 539 | H 540 | ); 541 | base = l/2 - headlen/2; 542 | sunklen = ( 543 | (headtype == "oval")? (D-size)/2 : 544 | headlen-0.001 545 | ); 546 | 547 | alignments = [ 548 | ["sunken", [0,0,base+sunklen]], 549 | ["base", [0,0,base]], 550 | ["shank", [0,0,base-shank]] 551 | ]; 552 | 553 | color("silver") 554 | orient_and_align([D+flange, D+flange, headlen+l], orient, align, alignments=alignments) { 555 | up(base) { 556 | difference() { 557 | union() { 558 | // Head 559 | if (headtype == "hex") { 560 | difference() { 561 | cylinder(d=tcirc, h=H, center=false, $fn=6); 562 | 563 | // Bevel hex nut top 564 | if (details) { 565 | up(H-bevtop) { 566 | difference() { 567 | upcube([tcirc+1, tcirc+1, bevtop+0.5]); 568 | down(0.01) cylinder(d1=tcirc, d2=tcirc-bevtop*2, h=bevtop+0.02, center=false); 569 | } 570 | } 571 | } 572 | } 573 | } else if (headtype == "socket") { 574 | sockw = get_metric_socket_cap_socket_size(size); 575 | sockd = get_metric_socket_cap_socket_depth(size); 576 | difference() { 577 | cylinder(d=D, h=H, center=false); 578 | up(H-sockd) cylinder(h=sockd+0.1, d=sockw/cos(30), center=false, $fn=6); 579 | if (details) { 580 | kcnt = 36; 581 | zring(n=kcnt, r=D/2) up(H/3) upcube([PI*D/kcnt/2, PI*D/kcnt/2, H]); 582 | } 583 | } 584 | } else if (headtype == "pan") { 585 | cyl(l=H*0.75, d=D, fillet2=H*0.75/2, align=V_UP); 586 | } else if (headtype == "round") { 587 | top_half() zscale(H*0.75/D*2) sphere(d=D); 588 | } else if (headtype == "button") { 589 | up(H*0.75/3) top_half() zscale(H*0.75*2/3/D*2) sphere(d=D); 590 | cylinder(d=D, h=H*0.75/3+0.01, center=false); 591 | } else if (headtype == "countersunk") { 592 | cylinder(h=(D-size)/2, d1=size, d2=D, center=false); 593 | } else if (headtype == "oval") { 594 | up((D-size)/2) top_half() zscale(0.333) sphere(d=D); 595 | cylinder(h=(D-size)/2, d1=size, d2=D, center=false); 596 | } 597 | 598 | // Flange 599 | if (flange>0) { 600 | up(headtype == "countersunk" || headtype == "oval"? (D-size)/2 : 0) { 601 | cylinder(d=D+flange, h=H/8, center=false); 602 | up(H/8) cylinder(d1=D+flange, d2=D, h=H/8, center=false); 603 | } 604 | } 605 | 606 | // Unthreaded Shank 607 | if (tlen < l) { 608 | down(l-tlen) cylinder(d=size, h=l-tlen+0.05, center=false, $fn=sides); 609 | } 610 | 611 | // Threads 612 | down(l) { 613 | difference() { 614 | up(tlen/2+0.05) { 615 | if (tlen > 0) { 616 | if (P > 0) { 617 | threaded_rod(d=size, l=tlen+0.05, pitch=P, $fn=sides); 618 | } else { 619 | cylinder(d=size, h=tlen+0.05, $fn=sides, center=true); 620 | } 621 | } 622 | } 623 | 624 | // Bevel bottom end of threads 625 | if (details) { 626 | difference() { 627 | down(0.5) upcube([size+1, size+1, bevbot+0.5]); 628 | cylinder(d1=size-bevbot*2, d2=size, h=bevbot+0.01, center=false); 629 | } 630 | } 631 | } 632 | } 633 | } 634 | 635 | // Phillips drive hole 636 | if (headtype != "socket" && phillips != undef) { 637 | down(headtype != "hex"? H/6 : 0) { 638 | phillips_drive(size=phillips, shaft=D); 639 | } 640 | } 641 | 642 | // Torx drive hole 643 | if (headtype != "socket" && torx != undef) { 644 | up(1) torx_drive(size=torx, l=H+0.1, center=false); 645 | } 646 | } 647 | } 648 | } 649 | } 650 | 651 | 652 | // Module: metric_nut() 653 | // Description: 654 | // Makes a model of a standard nut for a standard metric screw. 655 | // Arguments: 656 | // size = standard metric screw size in mm. (Default: 3) 657 | // hole = include the hole in the nut. (Default: true) 658 | // pitch = pitch of threads in the hole. No threads if not given. 659 | // flange = radius of flange beyond the head. Default = 0 (no flange) 660 | // details = true if model should be rendered with extra details. (Default: false) 661 | // orient = Orientation of the nut. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. 662 | // align = Alignment of the nut. Use the `V_` constants from `constants.scad`. Default: `V_UP`. 663 | // center = If true, centers the nut at the origin. If false, sits on top of XY plane. Overrides `align` if given. 664 | // Example: No details, No Hole. Useful for a mask. 665 | // metric_nut(size=10, hole=false); 666 | // Example: Hole, with No Threads 667 | // metric_nut(size=10, hole=true); 668 | // Example: Threads 669 | // metric_nut(size=10, hole=true, pitch=1.5); 670 | // Example: Details 671 | // metric_nut(size=10, hole=true, pitch=1.5, details=true); 672 | // Example: Centered 673 | // metric_nut(size=10, hole=true, pitch=1.5, details=true, center=true); 674 | // Example: Flange 675 | // metric_nut(size=10, hole=true, pitch=1.5, flange=3, details=true); 676 | module metric_nut( 677 | size=3, 678 | hole=true, 679 | pitch=undef, 680 | details=false, 681 | flange=0, 682 | center=undef, 683 | orient=ORIENT_Z, 684 | align=V_UP 685 | ) { 686 | H = get_metric_nut_thickness(size); 687 | D = get_metric_nut_size(size); 688 | boltfn = max(12, segs(size/2)); 689 | nutfn = max(12, segs(D/2)); 690 | dcirc = D/cos(30); 691 | bevtop = (dcirc - D)/2; 692 | 693 | color("silver") 694 | orient_and_align([dcirc+flange, dcirc+flange, H], orient, align, center) { 695 | difference() { 696 | union() { 697 | difference() { 698 | cylinder(d=dcirc, h=H, center=true, $fn=6); 699 | if (details) { 700 | up(H/2-bevtop) { 701 | difference() { 702 | upcube([dcirc+1, dcirc+1, bevtop+0.5]); 703 | down(0.01) cylinder(d1=dcirc, d2=dcirc-bevtop*2, h=bevtop+0.02, center=false, $fn=nutfn); 704 | } 705 | } 706 | if (flange == 0) { 707 | down(H/2) { 708 | difference() { 709 | down(0.5) upcube([dcirc+1, dcirc+1, bevtop+0.5]); 710 | down(0.01) cylinder(d1=dcirc-bevtop*2, d2=dcirc, h=bevtop+0.02, center=false, $fn=nutfn); 711 | } 712 | } 713 | } 714 | } 715 | } 716 | if (flange>0) { 717 | down(H/2) { 718 | cylinder(d=D+flange, h=H/8, center=false); 719 | up(H/8) cylinder(d1=D+flange, d2=D, h=H/8, center=false); 720 | } 721 | } 722 | } 723 | if (hole == true) { 724 | if (pitch == undef) { 725 | cylinder(r=size/2, h=H+0.5, center=true, $fn=boltfn); 726 | } else { 727 | threaded_rod(d=size, l=H+0.5, pitch=pitch, $fn=boltfn); 728 | } 729 | } 730 | } 731 | } 732 | } 733 | 734 | 735 | // vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap 736 | --------------------------------------------------------------------------------