From cc362357362122428fe6fdf5bf80d7228ea8eb10 Mon Sep 17 00:00:00 2001 From: Revar Desmera Date: Fri, 19 Apr 2019 17:02:17 -0700 Subject: [PATCH] Updated copyright years. Split math.scad up. Enabled attach for lots of shapes. Removed backwards compatability. --- LICENSE | 2 +- arrays.scad | 486 +++++++++++++++ beziers.scad | 2 +- compat.scad | 86 +-- constants.scad | 2 +- coords.scad | 368 +++++++++++ debug.scad | 2 +- geometry.scad | 306 ++++++++++ involute_gears.scad | 6 +- joiners.scad | 63 +- linear_bearings.scad | 7 +- masks.scad | 173 +++--- math.scad | 1393 +----------------------------------------- matrices.scad | 352 +++++++++++ metric_screws.scad | 11 +- nema_steppers.scad | 158 ++--- paths.scad | 10 +- phillips_drive.scad | 5 +- primitives.scad | 2 +- quaternions.scad | 2 +- shapes.scad | 44 +- sliders.scad | 8 +- std.scad | 9 +- threading.scad | 24 +- torx_drive.scad | 5 +- transforms.scad | 96 +-- triangulation.scad | 2 +- vectors.scad | 112 ++++ wiring.scad | 2 +- 29 files changed, 1995 insertions(+), 1743 deletions(-) create mode 100644 arrays.scad create mode 100644 coords.scad create mode 100644 geometry.scad create mode 100644 matrices.scad create mode 100644 vectors.scad diff --git a/LICENSE b/LICENSE index e0c9ed0..d66591f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 2-Clause License -Copyright (c) 2017, Revar Desmera +Copyright (c) 2017-2019, Revar Desmera All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/arrays.scad b/arrays.scad new file mode 100644 index 0000000..2cbd370 --- /dev/null +++ b/arrays.scad @@ -0,0 +1,486 @@ +////////////////////////////////////////////////////////////////////// +// LibFile: arrays.scad +// List and Array manipulation functions. +// To use, add the following lines to the beginning of your file: +// ``` +// use +// ``` +////////////////////////////////////////////////////////////////////// + +/* +BSD 2-Clause License + +Copyright (c) 2017-2019, Revar Desmera +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +// Section: Terminology +// - **List**: An ordered collection of zero or more items. ie: ["a", "b", "c"] +// - **Vector**: A list of numbers. ie: [4, 5, 6] +// - **Array**: A nested list of lists, or list of lists of lists, or deeper. ie: [[2,3], [4,5], [6,7]] +// - **Dimension**: The depth of nesting of lists in an array. A List is 1D. A list of lists is 2D. etc. + + +// Section: List Operations + + +// Function: replist() +// Usage: +// replist(val, n) +// Description: +// Generates a list or array of `n` copies of the given `list`. +// If the count `n` is given as a list of counts, then this creates a +// multi-dimensional array, filled with `val`. +// Arguments: +// val = The value to repeat to make the list or array. +// n = The number of copies to make of `val`. +// Example: +// replist(1, 4); // Returns [1,1,1,1] +// replist(8, [2,3]); // Returns [[8,8,8], [8,8,8]] +// replist(0, [2,2,3]); // Returns [[[0,0,0],[0,0,0]], [[0,0,0],[0,0,0]]] +// replist([1,2,3],3); // Returns [[1,2,3], [1,2,3], [1,2,3]] +function replist(val, n, i=0) = + is_num(n)? [for(j=[1:n]) val] : + (i>=len(n))? val : + [for (j=[1:n[i]]) replist(val, n, i+1)]; + + +// Function: in_list() +// Description: Returns true if value `x` is in list `l`. +// Arguments: +// x = The value to search for. +// l = The list to search. +// idx = If given, searches the given subindexes for matches for `x`. +// Example: +// in_list("bar", ["foo", "bar", "baz"]); // Returns true. +// in_list("bee", ["foo", "bar", "baz"]); // Returns false. +// in_list("bar", [[2,"foo"], [4,"bar"], [3,"baz"]], idx=1); // Returns true. +function in_list(x,l,idx=undef) = search([x], l, num_returns_per_match=1, index_col_num=idx) != [[]]; + + +// Function: slice() +// Description: +// Returns a slice of a list. The first item is index 0. +// Negative indexes are counted back from the end. The last item is -1. +// Arguments: +// arr = The array/list to get the slice of. +// st = The index of the first item to return. +// end = The index after the last item to return, unless negative, in which case the last item to return. +// Example: +// slice([3,4,5,6,7,8,9], 3, 5); // Returns [6,7] +// slice([3,4,5,6,7,8,9], 2, -1); // Returns [5,6,7,8,9] +// slice([3,4,5,6,7,8,9], 1, 1); // Returns [] +// slice([3,4,5,6,7,8,9], 6, -1); // Returns [9] +// slice([3,4,5,6,7,8,9], 2, -2); // Returns [5,6,7,8] +function slice(arr,st,end) = let( + s=st<0?(len(arr)+st):st, + e=end<0?(len(arr)+end+1):end + ) (s==e)? [] : [for (i=[s:e-1]) if (e>s) arr[i]]; + + +// Function: select() +// Description: +// Returns a portion of a list, wrapping around past the beginning, if endlength)? list_trim(v,length) : list_pad(v,length,fill); + + +// Function: enumerate() +// Description: +// Returns a list, with each item of the given list `l` numbered in a sublist. +// Something like: `[[0,l[0]], [1,l[1]], [2,l[2]], ...]` +// Arguments: +// l = List to enumerate. +// idx = If given, enumerates just the given subindex items of `l`. +// Example: +// enumerate(["a","b","c"]); // Returns: [[0,"a"], [1,"b"], [2,"c"]] +// enumerate([[88,"a"],[76,"b"],[21,"c"]], idx=1); // Returns: [[0,"a"], [1,"b"], [2,"c"]] +// enumerate([["cat","a",12],["dog","b",10],["log","c",14]], idx=[1:2]); // Returns: [[0,"a",12], [1,"b",10], [2,"c",14]] +function enumerate(l,idx=undef) = + (l==[])? [] : + (idx==undef)? + [for (i=[0:len(l)-1]) [i,l[i]]] : + [for (i=[0:len(l)-1]) concat([i], [for (j=idx) l[i][j]])]; + + +// Function: sort() +// Usage: +// sort(arr, [idx]) +// Description: +// Sorts the given list using `compare_vals()`. Results are undefined if list elements are not of similar type. +// Arguments: +// arr = The list to sort. +// idx = If given, the index, range, or list of indices of sublist items to compare. +// Example: +// l = [45,2,16,37,8,3,9,23,89,12,34]; +// sorted = sort(l); // Returns [2,3,8,9,12,16,23,34,37,45,89] +function sort(arr, idx=undef) = + (len(arr)<=1) ? arr : + let( + pivot = arr[floor(len(arr)/2)], + pivotval = idx==undef? pivot : [for (i=idx) pivot[i]], + compare = [ + for (entry = arr) let( + val = idx==undef? entry : [for (i=idx) entry[i]], + cmp = compare_vals(val, pivotval) + ) cmp + ], + lesser = [ for (i = [0:len(arr)-1]) if (compare[i] < 0) arr[i] ], + equal = [ for (i = [0:len(arr)-1]) if (compare[i] ==0) arr[i] ], + greater = [ for (i = [0:len(arr)-1]) if (compare[i] > 0) arr[i] ] + ) + concat(sort(lesser,idx), equal, sort(greater,idx)); + + +// Function: sortidx() +// Description: +// Given a list, calculates the sort order of the list, and returns +// a list of indexes into the original list in that sorted order. +// If you iterate the returned list in order, and use the list items +// to index into the original list, you will be iterating the original +// values in sorted order. +// Example: +// lst = ["d","b","e","c"]; +// idxs = sortidx(lst); // Returns: [1,3,0,2] +// ordered = [for (i=idxs) lst[i]]; // Returns: ["b", "c", "d", "e"] +// Example: +// lst = [ +// ["foo", 88, [0,0,1], false], +// ["bar", 90, [0,1,0], true], +// ["baz", 89, [1,0,0], false], +// ["qux", 23, [1,1,1], true] +// ]; +// idxs1 = sortidx(lst, idx=1); // Returns: [3,0,2,1] +// idxs2 = sortidx(lst, idx=0); // Returns: [1,2,0,3] +// idxs3 = sortidx(lst, idx=[1,3]); // Returns: [3,0,2,1] +function sortidx(l, idx=undef) = + (l==[])? [] : + let( + ll=enumerate(l,idx=idx), + sidx = [1:len(ll[0])-1] + ) + array_subindex(sort(ll, idx=sidx), 0); + + +// Function: unique() +// Usage: +// unique(arr); +// Description: +// Returns a sorted list with all repeated items removed. +// Arguments: +// arr = The list to uniquify. +function unique(arr) = + len(arr)<=1? arr : let( + sorted = sort(arr) + ) [ + for (i=[0:len(sorted)-1]) + if (i==0 || (sorted[i] != sorted[i-1])) + sorted[i] + ]; + + + +// Section: Array Manipulation + +// Function: array_subindex() +// Description: +// For each array item, return the indexed subitem. +// Returns a list of the values of each vector at the specfied +// index list or range. If the index list or range has +// only one entry the output list is flattened. +// Arguments: +// v = The given list of lists. +// idx = The index, list of indices, or range of indices to fetch. +// Example: +// v = [[[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]]; +// array_subindex(v,2); // Returns [3, 7, 11, 15] +// array_subindex(v,[2,1]); // Returns [[3, 2], [7, 6], [11, 10], [15, 14]] +// array_subindex(v,[1:3]); // Returns [[2, 3, 4], [6, 7, 8], [10, 11, 12], [14, 15, 16]] +function array_subindex(v, idx) = [ + for(val=v) let(value=[for(i=idx) val[i]]) + len(value)==1 ? value[0] : value +]; + + +// Function: array_zip() +// Usage: +// array_zip(v1, v2, v3, [fit], [fill]); +// array_zip(vecs, [fit], [fill]); +// Description: +// Zips together corresponding items from two or more lists. +// Returns a list of lists, where each sublist contains corresponding +// items from each of the input lists. `[[A1, B1, C1], [A2, B2, C2], ...]` +// Arguments: +// vecs = A list of two or more lists to zipper together. +// fit = If `fit=="short"`, the zips together up to the length of the shortest list in vecs. If `fit=="long"`, then pads all lists to the length of the longest, using the value in `fill`. If `fit==false`, then requires all lists to be the same length. Default: false. +// fill = The default value to fill in with if one or more lists if short. Default: undef +// Example: +// v1 = [1,2,3,4]; +// v2 = [5,6,7]; +// v3 = [8,9,10,11]; +// array_zip(v1,v3); // returns [[1,8], [2,9], [3,10], [4,11]] +// array_zip([v1,v3]); // returns [[1,8], [2,9], [3,10], [4,11]] +// array_zip([v1,v2], fit="short"); // returns [[1,5], [2,6], [3,7]] +// array_zip([v1,v2], fit="long"); // returns [[1,5], [2,6], [3,7], [4,undef]] +// array_zip([v1,v2], fit="long, fill=0); // returns [[1,5], [2,6], [3,7], [4,0]] +// array_zip([v1,v2,v3], fit="long"); // returns [[1,5,8], [2,6,9], [3,7,10], [4,undef,11]] +// Example: +// v1 = [[1,2,3], [4,5,6], [7,8,9]]; +// v2 = [[20,19,18], [17,16,15], [14,13,12]]; +// array_zip(v1,v2); // Returns [[1,2,3,20,19,18], [4,5,6,17,16,15], [7,8,9,14,13,12]] +function array_zip(vecs, v2, v3, fit=false, fill=undef) = + (v3!=undef)? array_zip([vecs,v2,v3], fit=fit, fill=fill) : + (v2!=undef)? array_zip([vecs,v2], fit=fit, fill=fill) : + let( + dummy1 = assert_in_list("fit", fit, [false, "short", "long"]), + minlen = list_shortest(vecs), + maxlen = list_longest(vecs), + dummy2 = (fit==false)? assert(minlen==maxlen, "Input vectors must have the same length") : 0 + ) (fit == "long")? + [for(i=[0:maxlen-1]) [for(v=vecs) for(x=(i len(dimlist))? 0 : dimlist[depth-1] + ); + + + +// vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/beziers.scad b/beziers.scad index ad80907..014d55f 100644 --- a/beziers.scad +++ b/beziers.scad @@ -12,7 +12,7 @@ /* BSD 2-Clause License -Copyright (c) 2017, Revar Desmera +Copyright (c) 2017-2019, Revar Desmera All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/compat.scad b/compat.scad index 24174a5..9aead82 100644 --- a/compat.scad +++ b/compat.scad @@ -11,7 +11,7 @@ /* BSD 2-Clause License -Copyright (c) 2017, Revar Desmera +Copyright (c) 2017-2019, Revar Desmera All rights reserved. Redistribution and use in source and binary forms, with or without @@ -47,32 +47,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Arguments: // v = Value to pass through if not `undef`. // dflt = Value to return if `v` *is* `undef`. -function default(v,dflt=undef) = v==undef? dflt : v; - - -// Function: is_def() -// Description: Returns true if given value is not `undef`. -function is_def(v) = (version_num() > 20190100)? !is_undef(v) : (v != undef); - - -// Function: is_str() -// Description: Given a value, returns true if it is a string. -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))); - - -// Function: is_boolean() -// Description: Given a value, returns true if it is a boolean. -function is_boolean(v) = (version_num() > 20190100)? is_bool(v) : (!is_str(v) && (str(v) == "true" || str(v) == "false")); - - -// Function: is_scalar() -// Description: Given a value, returns true if it is a scalar number. -function is_scalar(v) = (version_num() > 20190100)? is_num(v) : (!is_boolean(v) && is_def(v+0)); - - -// Function: is_array() -// Description: Given a value, returns true if it is an array/list/vector. -function is_array(v) = (version_num() > 20190100)? is_list(v) : (v==[] || (is_def(v[0]) && !is_str(v) )); +function default(v,dflt=undef) = is_undef(v)? dflt : v; // Function: get_radius() @@ -88,23 +63,18 @@ function is_array(v) = (version_num() > 20190100)? is_list(v) : (v==[] || (is_de // d = Most general diameter. // dflt = Value to return if all other values given are `undef`. function get_radius(r1=undef, r=undef, d1=undef, d=undef, dflt=undef) = ( - is_def(r1)? r1 : - is_def(d1)? d1/2 : - is_def(r)? r : - is_def(d)? d/2 : + !is_undef(r1)? r1 : + !is_undef(d1)? d1/2 : + !is_undef(r)? r : + !is_undef(d)? d/2 : dflt ); -// Function: Len() -// Description: -// Given an array, returns number of items in array. Otherwise returns `undef`. -function Len(v) = is_array(v)? len(v) : undef; - // Function: remove_undefs() // Description: Removes all `undef`s from a list. -function remove_undefs(v) = [for (x = v) if (is_def(x)) x]; +function remove_undefs(v) = [for (x = v) if (!is_undef(x)) x]; // Function: first_defined() @@ -132,16 +102,12 @@ function any_defined(v) = len(remove_undefs(v))>0; // v = Value to return vector from. // dflt = Default value to set empty vector parts from. function scalar_vec3(v, dflt=undef) = - !is_def(v)? undef : - is_array(v)? [for (i=[0:2]) default(v[i], default(dflt, 0))] : - is_def(dflt)? [v,dflt,dflt] : [v,v,v]; + is_undef(v)? undef : + is_list(v)? [for (i=[0:2]) default(v[i], default(dflt, 0))] : + !is_undef(dflt)? [v,dflt,dflt] : [v,v,v]; -// Function: f_echo() -// Description: If possible, echo a message from a function. -function f_echo(msg) = (version_num() > 20190100)? echo(msg) : 0; - // Section: Modules @@ -162,9 +128,9 @@ module assert_in_list(argname, val, l, idx=undef) { if (!succ) { msg = str( "In argument '", argname, "', ", - (is_str(val)? str("\"", val, "\"") : val), + (is_string(val)? str("\"", val, "\"") : val), " must be one of ", - (is_def(idx)? [for (v=l) v[idx]] : l) + (!is_undef(idx)? [for (v=l) v[idx]] : l) ); assertion(succ, msg); } @@ -175,9 +141,9 @@ function assert_in_list(argname, val, l, idx=undef) = succ? 0 : let( msg = str( "In argument '", argname, "', ", - (is_str(val)? str("\"", val, "\"") : val), + (is_string(val)? str("\"", val, "\"") : val), " must be one of ", - (is_def(idx)? [for (v=l) v[idx]] : l) + (!is_undef(idx)? [for (v=l) v[idx]] : l) ) ) assertion(succ, msg); @@ -193,17 +159,13 @@ function assert_in_list(argname, val, l, idx=undef) = // succ = If this is `false`, trigger the assertion. // msg = The message to emit if `succ` is `false`. module assertion(succ, msg) { - if (version_num() > 20190100) { - // assert() will echo the variable name, and `succ` looks confusing there. So we store it in FAILED. - FAILED = succ; - assert(FAILED, msg); - } else if (!succ) { - echo_error(msg); - } + // assert() will echo the variable name, and `succ` looks confusing there. So we store it in FAILED. + FAILED = succ; + assert(FAILED, msg); } function assertion(succ, msg) = - (version_num() > 20190100)? let(FAILED=succ) assert(FAILED, msg) : 0; + let(FAILED=succ) assert(FAILED, msg); // Module: echo_error() @@ -220,7 +182,7 @@ module echo_error(msg, pfx="ERROR") { } function echo_error(msg, pfx="ERROR") = - f_echo(str("

", pfx, ": ", msg, "

")); + echo(str("

", pfx, ": ", msg, "

")); // Module: echo_warning() @@ -237,7 +199,7 @@ module echo_warning(msg, pfx="WARNING") { } function echo_warning(msg, pfx="WARNING") = - f_echo(str("

", pfx, ": ", msg, "

")); + echo(str("

", pfx, ": ", msg, "

")); // Module: deprecate() @@ -253,7 +215,7 @@ module deprecate(name, suggest=undef) { echo_warning(pfx="DEPRECATED", str( "`", name, "` is deprecated and should not be used.", - !is_def(suggest)? "" : str( + is_undef(suggest)? "" : str( " You should use `", suggest, "` instead." ) ) @@ -264,7 +226,7 @@ function deprecate(name, suggest=undef) = echo_warning(pfx="DEPRECATED", str( "`", name, "` is deprecated and should not be used.", - !is_def(suggest)? "" : str( + is_undef(suggest)? "" : str( " You should use `", suggest, "` instead." ) ) @@ -286,7 +248,7 @@ module deprecate_argument(name, arg, suggest=undef) { "In `", name, "`, ", "the argument `", arg, "` ", "is deprecated and should not be used.", - !is_def(suggest)? "" : str( + is_undef(suggest)? "" : str( " You should use `", suggest, "` instead." ) )); @@ -297,7 +259,7 @@ function deprecate_argument(name, arg, suggest=undef) = "In `", name, "`, ", "the argument `", arg, "` ", "is deprecated and should not be used.", - !is_def(suggest)? "" : str( + is_undef(suggest)? "" : str( " You should use `", suggest, "` instead." ) )); diff --git a/constants.scad b/constants.scad index 6ca81dc..189a19a 100644 --- a/constants.scad +++ b/constants.scad @@ -10,7 +10,7 @@ /* BSD 2-Clause License -Copyright (c) 2017, Revar Desmera +Copyright (c) 2017-2019, Revar Desmera All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/coords.scad b/coords.scad new file mode 100644 index 0000000..53b103c --- /dev/null +++ b/coords.scad @@ -0,0 +1,368 @@ +////////////////////////////////////////////////////////////////////// +// LibFile: coords.scad +// Coordinate transformations and coordinate system conversions. +// To use, add the following lines to the beginning of your file: +// ``` +// use +// ``` +////////////////////////////////////////////////////////////////////// + +/* +BSD 2-Clause License + +Copyright (c) 2017-2019, Revar Desmera +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +// Section: Coordinate Manipulation + +// Function: point2d() +// Description: +// Returns a 2D vector/point from a 2D or 3D vector. +// If given a 3D point, removes the Z coordinate. +// Arguments: +// p = The coordinates to force into a 2D vector/point. +function point2d(p) = [for (i=[0:1]) (p[i]==undef)? 0 : p[i]]; + + +// Function: path2d() +// Description: +// Returns a list of 2D vectors/points from a list of 2D or 3D vectors/points. +// If given a 3D point list, removes the Z coordinates from each point. +// Arguments: +// points = A list of 2D or 3D points/vectors. +function path2d(points) = [for (point = points) point2d(point)]; + + +// Function: point3d() +// Description: +// Returns a 3D vector/point from a 2D or 3D vector. +// Arguments: +// p = The coordinates to force into a 3D vector/point. +function point3d(p) = [for (i=[0:2]) (p[i]==undef)? 0 : p[i]]; + + +// Function: path3d() +// Description: +// Returns a list of 3D vectors/points from a list of 2D or 3D vectors/points. +// Arguments: +// points = A list of 2D or 3D points/vectors. +function path3d(points) = [for (point = points) point3d(point)]; + + +// Function: translate_points() +// Usage: +// translate_points(pts, v); +// Description: +// Moves each point in an array by a given amount. +// Arguments: +// pts = List of points to translate. +// v = Amount to translate points by. +function translate_points(pts, v=[0,0,0]) = [for (pt = pts) pt+v]; + + +// Function: scale_points() +// Usage: +// scale_points(pts, v, [cp]); +// Description: +// Scales each point in an array by a given amount, around a given centerpoint. +// Arguments: +// pts = List of points to scale. +// v = A vector with a scaling factor for each axis. +// cp = Centerpoint to scale around. +function scale_points(pts, v=[0,0,0], cp=[0,0,0]) = [for (pt = pts) [for (i = [0:len(pt)-1]) (pt[i]-cp[i])*v[i]+cp[i]]]; + + +// Function: rotate_points2d() +// Usage: +// rotate_points2d(pts, ang, [cp]); +// Description: +// Rotates each 2D point in an array by a given amount, around an optional centerpoint. +// Arguments: +// pts = List of 3D points to rotate. +// ang = Angle to rotate by. +// cp = 2D Centerpoint to rotate around. Default: `[0,0]` +function rotate_points2d(pts, ang, cp=[0,0]) = let( + m = matrix3_zrot(ang) + ) [for (pt = pts) m*point3d(pt-cp)+cp]; + + +// Function: rotate_points3d() +// Usage: +// rotate_points3d(pts, a, [cp], [reverse]); +// rotate_points3d(pts, a, v, [cp], [reverse]); +// rotate_points3d(pts, from, to, [a], [cp], [reverse]); +// Description: +// Rotates each 3D point in an array by a given amount, around a given centerpoint. +// Arguments: +// pts = List of points to rotate. +// a = Rotation angle(s) in degrees. +// v = If given, axis vector to rotate around. +// cp = Centerpoint to rotate around. +// from = If given, the vector to rotate something from. Used with `to`. +// to = If given, the vector to rotate something to. Used with `from`. +// reverse = If true, performs an exactly reversed rotation. +function rotate_points3d(pts, a=0, v=undef, cp=[0,0,0], from=undef, to=undef, reverse=false) = + assert(is_undef(from)==is_undef(to), "`from` and `to` must be given together.") + let( + mrot = reverse? ( + !is_undef(from)? ( + let ( + from = from / norm(from), + to = to / norm(from), + ang = vector_angle(from, to), + v = vector_axis(from, to) + ) + matrix4_rot_by_axis(from, -a) * matrix4_rot_by_axis(v, -ang) + ) : !is_undef(v)? ( + matrix4_rot_by_axis(v, -a) + ) : is_num(a)? ( + matrix4_zrot(-a) + ) : ( + matrix4_xrot(-a.x) * matrix4_yrot(-a.y) * matrix4_zrot(-a.z) + ) + ) : ( + !is_undef(from)? ( + let ( + from = from / norm(from), + to = to / norm(from), + ang = vector_angle(from, to), + v = vector_axis(from, to) + ) + matrix4_rot_by_axis(v, ang) * matrix4_rot_by_axis(from, a) + ) : !is_undef(v)? ( + matrix4_rot_by_axis(v, a) + ) : is_num(a)? ( + matrix4_zrot(a) + ) : ( + matrix4_zrot(a.z) * matrix4_yrot(a.y) * matrix4_xrot(a.x) + ) + ), + m = matrix4_translate(cp) * mrot * matrix4_translate(-cp) + ) [for (pt = pts) point3d(m*concat(point3d(pt),[1]))]; + + + +// Section: Coordinate Systems + +// Function: polar_to_xy() +// Usage: +// polar_to_xy(r, theta); +// polar_to_xy([r, theta]); +// Description: +// Convert polar coordinates to 2D cartesian coordinates. +// Returns [X,Y] cartesian coordinates. +// Arguments: +// r = distance from the origin. +// theta = angle in degrees, counter-clockwise of X+. +// Examples: +// xy = polar_to_xy(20,30); +// xy = polar_to_xy([40,60]); +function polar_to_xy(r,theta=undef) = let( + rad = theta==undef? r[0] : r, + t = theta==undef? r[1] : theta + ) rad*[cos(t), sin(t)]; + + +// Function: xy_to_polar() +// Usage: +// xy_to_polar(x,y); +// xy_to_polar([X,Y]); +// Description: +// Convert 2D cartesian coordinates to polar coordinates. +// Returns [radius, theta] where theta is the angle counter-clockwise of X+. +// Arguments: +// x = X coordinate. +// y = Y coordinate. +// Examples: +// plr = xy_to_polar(20,30); +// plr = xy_to_polar([40,60]); +function xy_to_polar(x,y=undef) = let( + xx = y==undef? x[0] : x, + yy = y==undef? x[1] : y + ) [norm([xx,yy]), atan2(yy,xx)]; + + +// Function: xyz_to_planar() +// Usage: +// xyz_to_planar(point, a, b, c); +// Description: +// Given three points defining a plane, returns the projected planar +// [X,Y] coordinates of the closest point to a 3D `point`. The origin +// of the planar coordinate system [0,0] will be at point `a`, and the +// Y+ axis direction will be towards point `b`. This coordinate system +// can be useful in taking a set of nearly coplanar points, and converting +// them to a pure XY set of coordinates for manipulation, before convering +// them back to the original 3D plane. +function xyz_to_planar(point, a, b, c) = let( + u = normalize(b-a), + v = normalize(c-a), + n = normalize(cross(u,v)), + w = normalize(cross(n,u)), + relpoint = point-a +) [relpoint * w, relpoint * u]; + + +// Function: planar_to_xyz() +// Usage: +// planar_to_xyz(point, a, b, c); +// Description: +// Given three points defining a plane, converts a planar [X,Y] +// coordinate to the actual corresponding 3D point on the plane. +// The origin of the planar coordinate system [0,0] will be at point +// `a`, and the Y+ axis direction will be towards point `b`. +function planar_to_xyz(point, a, b, c) = let( + u = normalize(b-a), + v = normalize(c-a), + n = normalize(cross(u,v)), + w = normalize(cross(n,u)) +) a + point.x * w + point.y * u; + + +// Function: cylindrical_to_xyz() +// Usage: +// cylindrical_to_xyz(r, theta, z) +// cylindrical_to_xyz([r, theta, z]) +// Description: +// Convert cylindrical coordinates to 3D cartesian coordinates. Returns [X,Y,Z] cartesian coordinates. +// Arguments: +// r = distance from the Z axis. +// theta = angle in degrees, counter-clockwise of X+ on the XY plane. +// z = Height above XY plane. +// Examples: +// xyz = cylindrical_to_xyz(20,30,40); +// xyz = cylindrical_to_xyz([40,60,50]); +function cylindrical_to_xyz(r,theta=undef,z=undef) = let( + rad = theta==undef? r[0] : r, + t = theta==undef? r[1] : theta, + zed = theta==undef? r[2] : z + ) [rad*cos(t), rad*sin(t), zed]; + + +// Function: xyz_to_cylindrical() +// Usage: +// xyz_to_cylindrical(x,y,z) +// xyz_to_cylindrical([X,Y,Z]) +// Description: +// Convert 3D cartesian coordinates to cylindrical coordinates. +// Returns [radius,theta,Z]. Theta is the angle counter-clockwise +// of X+ on the XY plane. Z is height above the XY plane. +// Arguments: +// x = X coordinate. +// y = Y coordinate. +// z = Z coordinate. +// Examples: +// cyl = xyz_to_cylindrical(20,30,40); +// cyl = xyz_to_cylindrical([40,50,70]); +function xyz_to_cylindrical(x,y=undef,z=undef) = let( + p = is_num(x)? [x, default(y,0), default(z,0)] : point3d(x) + ) [norm([p.x,p.y]), atan2(p.y,p.x), p.z]; + + +// Function: spherical_to_xyz() +// Usage: +// spherical_to_xyz(r, theta, phi); +// spherical_to_xyz([r, theta, phi]); +// Description: +// Convert spherical coordinates to 3D cartesian coordinates. +// Returns [X,Y,Z] cartesian coordinates. +// Arguments: +// r = distance from origin. +// theta = angle in degrees, counter-clockwise of X+ on the XY plane. +// phi = angle in degrees from the vertical Z+ axis. +// Examples: +// xyz = spherical_to_xyz(20,30,40); +// xyz = spherical_to_xyz([40,60,50]); +function spherical_to_xyz(r,theta=undef,phi=undef) = let( + rad = theta==undef? r[0] : r, + t = theta==undef? r[1] : theta, + p = theta==undef? r[2] : phi + ) rad*[sin(p)*cos(t), sin(p)*sin(t), cos(p)]; + + +// Function: xyz_to_spherical() +// Usage: +// xyz_to_spherical(x,y,z) +// xyz_to_spherical([X,Y,Z]) +// Description: +// Convert 3D cartesian coordinates to spherical coordinates. +// Returns [r,theta,phi], where phi is the angle from the Z+ pole, +// and theta is degrees counter-clockwise of X+ on the XY plane. +// Arguments: +// x = X coordinate. +// y = Y coordinate. +// z = Z coordinate. +// Examples: +// sph = xyz_to_spherical(20,30,40); +// sph = xyz_to_spherical([40,50,70]); +function xyz_to_spherical(x,y=undef,z=undef) = let( + p = is_num(x)? [x, default(y,0), default(z,0)] : point3d(x) + ) [norm(p), atan2(p.y,p.x), atan2(norm([p.x,p.y]),p.z)]; + + +// Function: altaz_to_xyz() +// Usage: +// altaz_to_xyz(alt, az, r); +// altaz_to_xyz([alt, az, r]); +// Description: +// Convert altitude/azimuth/range coordinates to 3D cartesian coordinates. +// Returns [X,Y,Z] cartesian coordinates. +// Arguments: +// alt = altitude angle in degrees above the XY plane. +// az = azimuth angle in degrees clockwise of Y+ on the XY plane. +// r = distance from origin. +// Examples: +// xyz = altaz_to_xyz(20,30,40); +// xyz = altaz_to_xyz([40,60,50]); +function altaz_to_xyz(alt,az=undef,r=undef) = let( + p = az==undef? alt[0] : alt, + t = 90 - (az==undef? alt[1] : az), + rad = az==undef? alt[2] : r + ) rad*[cos(p)*cos(t), cos(p)*sin(t), sin(p)]; + + +// Function: xyz_to_altaz() +// Usage: +// xyz_to_altaz(x,y,z); +// xyz_to_altaz([X,Y,Z]); +// Description: +// Convert 3D cartesian coordinates to altitude/azimuth/range coordinates. +// Returns [altitude,azimuth,range], where altitude is angle above the +// XY plane, azimuth is degrees clockwise of Y+ on the XY plane, and +// range is the distance from the origin. +// Arguments: +// x = X coordinate. +// y = Y coordinate. +// z = Z coordinate. +// Examples: +// aa = xyz_to_altaz(20,30,40); +// aa = xyz_to_altaz([40,50,70]); +function xyz_to_altaz(x,y=undef,z=undef) = let( + p = is_num(x)? [x, default(y,0), default(z,0)] : point3d(x) + ) [atan2(p.z,norm([p.x,p.y])), atan2(p.x,p.y), norm(p)]; + + + +// vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/debug.scad b/debug.scad index 827a081..5755fcb 100644 --- a/debug.scad +++ b/debug.scad @@ -13,7 +13,7 @@ /* BSD 2-Clause License -Copyright (c) 2017, Revar Desmera +Copyright (c) 2017-2019, Revar Desmera All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/geometry.scad b/geometry.scad new file mode 100644 index 0000000..f920996 --- /dev/null +++ b/geometry.scad @@ -0,0 +1,306 @@ +////////////////////////////////////////////////////////////////////// +// LibFile: geometry.scad +// Geometry helpers. +// To use, add the following lines to the beginning of your file: +// ``` +// use +// ``` +////////////////////////////////////////////////////////////////////// + +/* +BSD 2-Clause License + +Copyright (c) 2017-2019, Revar Desmera +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + + +// Section: Lines and Triangles + +// Function: point_on_segment() +// Usage: +// point_on_segment(point, edge); +// Description: +// Determine if the point is on the line segment between two points. +// Returns true if yes, and false if not. +// Arguments: +// point = The point to check colinearity of. +// edge = Array of two points forming the line segment to test against. +function point_on_segment(point, edge) = + point==edge[0] || point==edge[1] || // The point is an endpoint + sign(edge[0].x-point.x)==sign(point.x-edge[1].x) // point is in between the + && sign(edge[0].y-point.y)==sign(point.y-edge[1].y) // edge endpoints + && point_left_of_segment(point, edge)==0; // and on the line defined by edge + + +// Function: point_left_of_segment() +// Usage: +// point_left_of_segment(point, edge); +// Description: +// Return >0 if point is left of the line defined by edge. +// Return =0 if point is on the line. +// Return <0 if point is right of the line. +// Arguments: +// point = The point to check position of. +// edge = Array of two points forming the line segment to test against. +function point_left_of_segment(point, edge) = + (edge[1].x-edge[0].x) * (point.y-edge[0].y) - (point.x-edge[0].x) * (edge[1].y-edge[0].y); + + +// Internal non-exposed function. +function _point_above_below_segment(point, edge) = + edge[0].y <= point.y? ( + (edge[1].y > point.y && point_left_of_segment(point, edge) > 0)? 1 : 0 + ) : ( + (edge[1].y <= point.y && point_left_of_segment(point, edge) < 0)? -1 : 0 + ); + + +// Function: right_of_line2d() +// Usage: +// right_of_line2d(line, pt) +// Description: +// Returns true if the given point is to the left of the given line. +// Arguments: +// line = A list of two points. +// pt = The point to test. +function right_of_line2d(line, pt) = + triangle_area2d(line[0], line[1], pt) < 0; + + +// Function: collinear() +// Usage: +// collinear(a, b, c, [eps]); +// Description: +// Returns true if three points are co-linear. +// Arguments: +// a = First point. +// b = Second point. +// c = Third point. +// eps = Acceptable max angle variance. Default: EPSILON (1e-9) degrees. +function collinear(a, b, c, eps=EPSILON) = + abs(vector_angle(b-a,c-a)) < eps; + + +// Function: collinear_indexed() +// Usage: +// collinear_indexed(points, a, b, c, [eps]); +// Description: +// Returns true if three points are co-linear. +// Arguments: +// points = A list of points. +// a = Index in `points` of first point. +// b = Index in `points` of second point. +// c = Index in `points` of third point. +// eps = Acceptable max angle variance. Default: EPSILON (1e-9) degrees. +function collinear_indexed(points, a, b, c, eps=EPSILON) = + let( + p1=points[a], + p2=points[b], + p3=points[c] + ) abs(vector_angle(p2-p1,p3-p1)) < eps; + + +// Function: triangle_area2d() +// Usage: +// triangle_area2d(a,b,c); +// Description: +// Returns the area of a triangle formed between three vertices. +// Result will be negative if the points are in clockwise order. +// Examples: +// triangle_area2d([0,0], [5,10], [10,0]); // Returns -50 +// triangle_area2d([10,0], [5,10], [0,0]); // Returns 50 +function triangle_area2d(a,b,c) = + ( + a.x * (b.y - c.y) + + b.x * (c.y - a.y) + + c.x * (a.y - b.y) + ) / 2; + + +// Section: Planes + +// Function: plane3pt() +// Usage: +// plane3pt(p1, p2, p3); +// Description: +// Generates the cartesian equation of a plane from three non-colinear points on the plane. +// Returns [A,B,C,D] where Ax+By+Cz+D=0 is the equation of a plane. +// Arguments: +// p1 = The first point on the plane. +// p2 = The second point on the plane. +// p3 = The third point on the plane. +function plane3pt(p1, p2, p3) = + let(normal = normalize(cross(p3-p1, p2-p1))) concat(normal, [normal*p1]); + + +// Function: plane3pt_indexed() +// Usage: +// plane3pt_indexed(points, i1, i2, i3); +// Description: +// Given a list of points, and the indexes of three of those points, +// generates the cartesian equation of a plane that those points all +// lie on. Requires that the three indexed points be non-collinear. +// Returns [A,B,C,D] where Ax+By+Cz+D=0 is the equation of a plane. +// Arguments: +// points = A list of points. +// i1 = The index into `points` of the first point on the plane. +// i2 = The index into `points` of the second point on the plane. +// i3 = The index into `points` of the third point on the plane. +function plane3pt_indexed(points, i1, i2, i3) = + let( + p1 = points[i1], + p2 = points[i2], + p3 = points[i3], + normal = normalize(cross(p3-p1, p2-p1)) + ) concat(normal, [normal*p1]); + + +// Function: distance_from_plane() +// Usage: +// distance_from_plane(plane, point) +// Description: +// Given a plane as [A,B,C,D] where the cartesian equation for that plane +// is Ax+By+Cz+D=0, determines how far from that plane the given point is. +// The returned distance will be positive if the point is in front of the +// plane; on the same side of the plane as the normal of that plane points +// towards. If the point is behind the plane, then the distance returned +// will be negative. The normal of the plane is the same as [A,B,C]. +// Arguments: +// plane = The [A,B,C,D] values for the equation of the plane. +// point = The point to test. +function distance_from_plane(plane, point) = + [plane.x, plane.y, plane.z] * point - plane[3]; + + +// Function: coplanar() +// Usage: +// coplanar(plane, point); +// Description: +// Given a plane as [A,B,C,D] where the cartesian equation for that plane +// is Ax+By+Cz+D=0, determines if the given point is on that plane. +// Returns true if the point is on that plane. +// Arguments: +// plane = The [A,B,C,D] values for the equation of the plane. +// point = The point to test. +function coplanar(plane, point) = + abs(distance_from_plane(plane, point)) <= EPSILON; + + +// Function: in_front_of_plane() +// Usage: +// in_front_of_plane(plane, point); +// Description: +// Given a plane as [A,B,C,D] where the cartesian equation for that plane +// is Ax+By+Cz+D=0, determines if the given point is on the side of that +// plane that the normal points towards. The normal of the plane is the +// same as [A,B,C]. +// Arguments: +// plane = The [A,B,C,D] values for the equation of the plane. +// point = The point to test. +function in_front_of_plane(plane, point) = + distance_from_plane(plane, point) > EPSILON; + + + +// Section: Paths and Polygons + + +// Function: simplify_path() +// Description: +// Takes a path and removes unnecessary collinear points. +// Usage: +// simplify_path(path, [eps]) +// Arguments: +// path = A list of 2D path points. +// eps = Largest angle variance allowed. Default: EPSILON (1-e9) degrees. +function simplify_path(path, eps=EPSILON, _a=0, _b=2, _acc=[]) = + (_b >= len(path))? concat([path[0]], _acc, [path[len(path)-1]]) : + simplify_path( + path, eps, + (collinear_indexed(path, _a, _b-1, _b, eps=eps)? _a : _b-1), + _b+1, + (collinear_indexed(path, _a, _b-1, _b, eps=eps)? _acc : concat(_acc, [path[_b-1]])) + ); + + +// Function: simplify_path_indexed() +// Description: +// Takes a list of points, and a path as a list of indexes into `points`, +// and removes all path points that are unecessarily collinear. +// Usage: +// simplify_path_indexed(path, eps) +// Arguments: +// points = A list of points. +// path = A list of indexes into `points` that forms a path. +// eps = Largest angle variance allowed. Default: EPSILON (1-e9) degrees. +function simplify_path_indexed(points, path, eps=EPSILON, _a=0, _b=2, _acc=[]) = + (_b >= len(path))? concat([path[0]], _acc, [path[len(path)-1]]) : + simplify_path_indexed( + points, path, eps, + (collinear_indexed(points, path[_a], path[_b-1], path[_b], eps=eps)? _a : _b-1), + _b+1, + (collinear_indexed(points, path[_a], path[_b-1], path[_b], eps=eps)? _acc : concat(_acc, [path[_b-1]])) + ); + + +// Function: point_in_polygon() +// Usage: +// point_in_polygon(point, path) +// Description: +// This function tests whether the given point is inside, outside or on the boundary of +// the specified polygon using the Winding Number method. (http://geomalgorithms.com/a03-_inclusion.html) +// The polygon is given as a list of points, not including the repeated end point. +// Returns -1 if the point is outside the polyon. +// Returns 0 if the point is on the boundary. +// Returns 1 if the point lies in the interior. +// The polygon does not need to be simple: it can have self-intersections. +// But the polygon cannot have holes (it must be simply connected). +// Rounding error may give mixed results for points on or near the boundary. +// Arguments: +// point = The point to check position of. +// path = The list of 2D path points forming the perimeter of the polygon. +function point_in_polygon(point, path) = + // Does the point lie on any edges? If so return 0. + sum([for(i=[0:len(path)-1]) point_on_segment(point, select(path, i, i+1))?1:0])>0 ? 0 : + // Otherwise compute winding number and return 1 for interior, -1 for exterior + sum([for(i=[0:len(path)-1]) _point_above_below_segment(point, select(path, i, i+1))]) != 0 ? 1 : -1; + + +// Function: pointlist_bounds() +// Usage: +// pointlist_bounds(pts); +// Description: +// Finds the bounds containing all the points in pts. +// Returns [[minx, miny, minz], [maxx, maxy, maxz]] +// Arguments: +// pts = List of points. +function pointlist_bounds(pts) = [ + [for (a=[0:2]) min([ for (x=pts) point3d(x)[a] ]) ], + [for (a=[0:2]) max([ for (x=pts) point3d(x)[a] ]) ] +]; + + +// vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/involute_gears.scad b/involute_gears.scad index b9dfdfd..4215e37 100644 --- a/involute_gears.scad +++ b/involute_gears.scad @@ -311,7 +311,7 @@ module gear( c = outer_radius(mm_per_tooth, number_of_teeth, clearance, interior); r = root_radius(mm_per_tooth, number_of_teeth, clearance, interior); p2 = p - (thickness*tan(bevelang)); - orient_and_align([p, p, thickness], orient, align) { + orient_and_align([p, p, thickness], orient, align, chain=true) { difference() { linear_extrude(height=thickness, center=true, convexity=10, twist=twist, scale=p2/p, slices=slices) { gear2d( @@ -338,6 +338,7 @@ module gear( } } } + children(); } } @@ -373,7 +374,7 @@ module rack( d = dedendum(mm_per_tooth, clearance); xa = a * sin(pressure_angle); xd = d * sin(pressure_angle); - orient_and_align([(number_of_teeth-1)*mm_per_tooth, height, thickness], orient, align, orig_orient=ORIENT_X) { + orient_and_align([(number_of_teeth-1)*mm_per_tooth, height, thickness], orient, align, orig_orient=ORIENT_X, chain=true) { left((number_of_teeth-1)*mm_per_tooth/2) { linear_extrude(height = thickness, center = true, convexity = 10) { for (i = [0:number_of_teeth-1] ) { @@ -394,6 +395,7 @@ module rack( } } } + children(); } } diff --git a/joiners.scad b/joiners.scad index 938a265..4862de6 100644 --- a/joiners.scad +++ b/joiners.scad @@ -11,7 +11,7 @@ /* BSD 2-Clause License -Copyright (c) 2017, Revar Desmera +Copyright (c) 2017-2019, Revar Desmera All rights reserved. Redistribution and use in source and binary forms, with or without @@ -63,20 +63,23 @@ module half_joiner_clear(h=20, w=10, a=30, clearance=0, overlap=0.01, orient=ORI guide_size = w/3; guide_width = 2*(dmnd_height/2-guide_size)*tan(a); - orient_and_align([w, guide_width, h], orient, align, orig_orient=ORIENT_Y) { - yspread(overlap, n=overlap>0? 2 : 1) { - difference() { - // Diamonds. - scale([w+clearance, dmnd_width/2, dmnd_height/2]) { - xrot(45) cube(size=[1,sqrt(2),sqrt(2)], center=true); - } - // Blunt point of tab. - yspread(guide_width+4) { - cube(size=[(w+clearance)*1.05, 4, h*0.99], center=true); + orient_and_align([w, guide_width, h], orient, align, orig_orient=ORIENT_Y, chain=true) { + union() { + yspread(overlap, n=overlap>0? 2 : 1) { + difference() { + // Diamonds. + scale([w+clearance, dmnd_width/2, dmnd_height/2]) { + xrot(45) cube(size=[1,sqrt(2),sqrt(2)], center=true); + } + // Blunt point of tab. + yspread(guide_width+4) { + cube(size=[(w+clearance)*1.05, 4, h*0.99], center=true); + } } } + if (overlap>0) cube([w+clearance, overlap+0.001, h], center=true); } - if (overlap>0) cube([w+clearance, overlap+0.001, h], center=true); + children(); } } @@ -113,7 +116,7 @@ module half_joiner(h=20, w=10, l=10, a=30, screwsize=undef, guides=true, slop=PR } } render(convexity=12) - orient_and_align([w, 2*l, h], orient, align, orig_orient=ORIENT_Y) { + orient_and_align([w, 2*l, h], orient, align, orig_orient=ORIENT_Y, chain=true) { difference() { union() { // Make base. @@ -159,6 +162,7 @@ module half_joiner(h=20, w=10, l=10, a=30, screwsize=undef, guides=true, slop=PR yrot(90) cylinder(r=screwsize*1.1/2, h=w+1, center=true, $fn=12); } } + children(); } } //half_joiner(screwsize=3, orient=ORIENT_Z, align=UP); @@ -196,7 +200,7 @@ module half_joiner2(h=20, w=10, l=10, a=30, screwsize=undef, guides=true, orient } render(convexity=12) - orient_and_align([w, 2*l, h], orient, align, orig_orient=ORIENT_Y) { + orient_and_align([w, 2*l, h], orient, align, orig_orient=ORIENT_Y, chain=true) { difference() { union () { fwd(l/2) cube(size=[w, l, h], center=true); @@ -211,6 +215,7 @@ module half_joiner2(h=20, w=10, l=10, a=30, screwsize=undef, guides=true, orient xcyl(r=screwsize*1.1/2, l=w+1, $fn=12); } } + children(); } } @@ -241,9 +246,12 @@ module joiner_clear(h=40, w=10, a=30, clearance=0, overlap=0.01, orient=ORIENT_Y guide_size = w/3; guide_width = 2*(dmnd_height/2-guide_size)*tan(a); - orient_and_align([w, guide_width, h], orient, align, orig_orient=ORIENT_Y) { - up(h/4) half_joiner_clear(h=h/2.0-0.01, w=w, a=a, overlap=overlap, clearance=clearance); - down(h/4) half_joiner_clear(h=h/2.0-0.01, w=w, a=a, overlap=overlap, clearance=-0.01); + orient_and_align([w, guide_width, h], orient, align, orig_orient=ORIENT_Y, chain=true) { + union() { + up(h/4) half_joiner_clear(h=h/2.0-0.01, w=w, a=a, overlap=overlap, clearance=clearance); + down(h/4) half_joiner_clear(h=h/2.0-0.01, w=w, a=a, overlap=overlap, clearance=-0.01); + } + children(); } } @@ -275,9 +283,12 @@ module joiner(h=40, w=10, l=10, a=30, screwsize=undef, guides=true, slop=PRINTER joiner_clear(h=h, w=w, a=a, clearance=0.1, orient=orient, align=align); } } - orient_and_align([w, 2*l, h], orient, align, orig_orient=ORIENT_Y) { - up(h/4) half_joiner(h=h/2, w=w, l=l, a=a, screwsize=screwsize, guides=guides, slop=slop); - down(h/4) half_joiner2(h=h/2, w=w, l=l, a=a, screwsize=screwsize, guides=guides); + orient_and_align([w, 2*l, h], orient, align, orig_orient=ORIENT_Y, chain=true) { + union() { + up(h/4) half_joiner(h=h/2, w=w, l=l, a=a, screwsize=screwsize, guides=guides, slop=slop); + down(h/4) half_joiner2(h=h/2, w=w, l=l, a=a, screwsize=screwsize, guides=guides); + } + children(); } } @@ -311,10 +322,11 @@ module joiner_pair_clear(spacing=100, h=40, w=10, a=30, n=2, clearance=0, overla guide_size = w/3; guide_width = 2*(dmnd_height/2-guide_size)*tan(a); - orient_and_align([spacing+w, guide_width, h], orient, align, orig_orient=ORIENT_Y) { + orient_and_align([spacing+w, guide_width, h], orient, align, orig_orient=ORIENT_Y, chain=true) { xspread(spacing, n=n) { joiner_clear(h=h, w=w, a=a, clearance=clearance, overlap=overlap); } + children(); } } @@ -352,7 +364,7 @@ module joiner_pair(spacing=100, h=40, w=10, l=10, a=30, n=2, alternate=true, scr joiner_pair_clear(spacing=spacing, h=h, w=w, a=a, clearance=0.1, orient=orient, align=align); } } - orient_and_align([spacing+w, 2*l, h], orient, align, orig_orient=ORIENT_Y) { + orient_and_align([spacing+w, 2*l, h], orient, align, orig_orient=ORIENT_Y, chain=true) { left((n-1)*spacing/2) { for (i=[0:n-1]) { right(i*spacing) { @@ -362,6 +374,7 @@ module joiner_pair(spacing=100, h=40, w=10, l=10, a=30, n=2, alternate=true, scr } } } + children(); } } @@ -393,12 +406,13 @@ module joiner_quad_clear(xspacing=undef, yspacing=undef, spacing1=undef, spacing { spacing1 = first_defined([spacing1, xspacing, 100]); spacing2 = first_defined([spacing2, yspacing, 50]); - orient_and_align([w+spacing1, spacing2, h], orient, align, orig_orient=ORIENT_Y) { + orient_and_align([w+spacing1, spacing2, h], orient, align, orig_orient=ORIENT_Y, chain=true) { zrot_copies(n=2) { back(spacing2/2) { joiner_pair_clear(spacing=spacing1, n=n, h=h, w=w, a=a, clearance=clearance, overlap=overlap); } } + children(); } } @@ -438,12 +452,13 @@ module joiner_quad(spacing1=undef, spacing2=undef, xspacing=undef, yspacing=unde joiner_quad_clear(spacing1=spacing1, spacing2=spacing2, h=h, w=w, a=a, clearance=0.1, orient=orient, align=align); } } - orient_and_align([w+spacing1, spacing2, h], orient, align, orig_orient=ORIENT_Y) { + orient_and_align([w+spacing1, spacing2, h], orient, align, orig_orient=ORIENT_Y, chain=true) { zrot_copies(n=2) { back(spacing2/2) { joiner_pair(spacing=spacing1, n=n, h=h, w=w, l=l, a=a, screwsize=screwsize, guides=guides, slop=slop); } } + children(); } } diff --git a/linear_bearings.scad b/linear_bearings.scad index f0b31af..350a420 100644 --- a/linear_bearings.scad +++ b/linear_bearings.scad @@ -12,7 +12,7 @@ /* BSD 2-Clause License -Copyright (c) 2017, Revar Desmera +Copyright (c) 2017-2019, Revar Desmera All rights reserved. Redistribution and use in source and binary forms, with or without @@ -111,7 +111,7 @@ module linear_bearing_housing(d=15, l=24, tab=7, gap=5, wall=3, tabwall=5, screw od = d+2*wall; ogap = gap+2*tabwall; tabh = tab/2+od/2*sqrt(2)-ogap/2; - orient_and_align([l, od, od], orient, align, orig_orient=ORIENT_X) { + orient_and_align([l, od, od], orient, align, orig_orient=ORIENT_X, chain=true) { difference() { union() { zrot(90) teardrop(r=od/2,h=l); @@ -128,6 +128,7 @@ module linear_bearing_housing(d=15, l=24, tab=7, gap=5, wall=3, tabwall=5, screw xrot(90) metric_nut(size=screwsize, hole=false); } } + children(); } } @@ -150,7 +151,7 @@ module lmXuu_housing(size=8, tab=7, gap=5, wall=3, tabwall=5, screwsize=3, orien { d = get_lmXuu_bearing_diam(size); l = get_lmXuu_bearing_length(size); - linear_bearing_housing(d=d,l=l,tab=tab,gap=gap,wall=wall,tabwall=tabwall,screwsize=screwsize, orient=orient, align=align); + linear_bearing_housing(d=d,l=l,tab=tab,gap=gap,wall=wall,tabwall=tabwall,screwsize=screwsize, orient=orient, align=align) children(); } diff --git a/masks.scad b/masks.scad index 854f7d2..4690972 100644 --- a/masks.scad +++ b/masks.scad @@ -10,7 +10,7 @@ /* BSD 2-Clause License -Copyright (c) 2017, Revar Desmera +Copyright (c) 2017-2019, Revar Desmera All rights reserved. Redistribution and use in source and binary forms, with or without @@ -41,8 +41,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Module: angle_pie_mask() // Usage: -// angle_pie_mask(r|d, l, ang, [orient], [align], [center]); -// angle_pie_mask(r1|d1, r2|d2, l, ang, [orient], [align], [center]); +// angle_pie_mask(r|d, l, ang, [orient], [align]); +// angle_pie_mask(r1|d1, r2|d2, l, ang, [orient], [align]); // Description: // Creates a pie wedge shape that can be used to mask other shapes. // Arguments: @@ -56,7 +56,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // d2 = Upper diameter of cone that wedge is created from. (optional) // orient = Orientation of the pie slice. Use the ORIENT_ constants from constants.h. Default: ORIENT_Z. // align = Alignment of the pie slice. Use the constants from constants.h. Default: CENTER. -// center = If true, centers vertically. If false, lift up to sit on top of the XY plane. Overrides `align`. // Example(FR): // angle_pie_mask(ang=30, d=100, l=20); module angle_pie_mask( @@ -64,13 +63,14 @@ module angle_pie_mask( r=undef, r1=undef, r2=undef, d=undef, d1=undef, d2=undef, orient=ORIENT_Z, align=CENTER, - h=undef, center=undef + h=undef ) { l = first_defined([l, h, 1]); r1 = get_radius(r1, r, d1, d, 10); r2 = get_radius(r2, r, d2, d, 10); - orient_and_align([2*r1, 2*r1, l], orient, align, center=center) { + orient_and_align([2*r1, 2*r1, l], orient, align, chain=true) { pie_slice(ang=ang, l=l+0.1, r1=r1, r2=r2, align=CENTER); + children(); } } @@ -152,7 +152,7 @@ module cylinder_mask( cylinder_mask(l=l, r1=sc*r1, r2=sc*r2, chamfer1=cham1, chamfer2=cham2, chamfang1=ang1, chamfang2=ang2, fillet1=fil1, fillet2=fil2, orient=orient, from_end=from_end); } } else { - orient_and_align([2*r1, 2*r1, l], orient, align) { + orient_and_align([2*r1, 2*r1, l], orient, align, chain=true) { difference() { union() { chlen1 = cham1 / (from_end? 1 : tan(ang1)); @@ -168,6 +168,7 @@ module cylinder_mask( } cyl(r1=sc*r1, r2=sc*r2, l=l, chamfer1=cham1, chamfer2=cham2, chamfang1=ang1, chamfang2=ang2, from_end=from_end, fillet1=fil1, fillet2=fil2); } + children(); } } } @@ -179,7 +180,7 @@ module cylinder_mask( // Module: chamfer_mask() // Usage: -// chamfer_mask(l, chamfer, [orient], [align], [center]); +// chamfer_mask(l, chamfer, [orient], [align]); // Description: // Creates a shape that can be used to chamfer a 90 degree edge. // Difference it from the object to be chamfered. The center of @@ -189,15 +190,15 @@ module cylinder_mask( // chamfer = Size of chamfer // orient = Orientation of the mask. Use the `ORIENT_` constants from `constants.h`. Default: vertical. // align = Alignment of the mask. Use the constants from `constants.h`. Default: centered. -// center = If true, centers vertically. If false, lift up to sit on top of the XY plane. Overrides `align`. // Example: // difference() { // cube(50); // #chamfer_mask(l=50, chamfer=10, orient=ORIENT_X, align=RIGHT); // } -module chamfer_mask(l=1, chamfer=1, orient=ORIENT_Z, align=CENTER, center=undef) { - orient_and_align([chamfer, chamfer, l], orient, align, center=center) { +module chamfer_mask(l=1, chamfer=1, orient=ORIENT_Z, align=CENTER) { + orient_and_align([chamfer, chamfer, l], orient, align, chain=true) { cylinder(d=chamfer*2, h=l+0.1, center=true, $fn=4); + children(); } } @@ -219,7 +220,7 @@ module chamfer_mask(l=1, chamfer=1, orient=ORIENT_Z, align=CENTER, center=undef) // #chamfer_mask_x(l=80, chamfer=20); // } module chamfer_mask_x(l=1.0, chamfer=1.0, align=CENTER) { - chamfer_mask(l=l, chamfer=chamfer, orient=ORIENT_X, align=align); + chamfer_mask(l=l, chamfer=chamfer, orient=ORIENT_X, align=align) children(); } @@ -240,7 +241,7 @@ module chamfer_mask_x(l=1.0, chamfer=1.0, align=CENTER) { // right(80) #chamfer_mask_y(l=80, chamfer=20); // } module chamfer_mask_y(l=1.0, chamfer=1.0, align=CENTER) { - chamfer_mask(l=l, chamfer=chamfer, orient=ORIENT_Y, align=align); + chamfer_mask(l=l, chamfer=chamfer, orient=ORIENT_Y, align=align) children(); } @@ -261,7 +262,7 @@ module chamfer_mask_y(l=1.0, chamfer=1.0, align=CENTER) { // #chamfer_mask_z(l=80, chamfer=20); // } module chamfer_mask_z(l=1.0, chamfer=1.0, align=CENTER) { - chamfer_mask(l=l, chamfer=chamfer, orient=ORIENT_Z, align=align); + chamfer_mask(l=l, chamfer=chamfer, orient=ORIENT_Z, align=align) children(); } @@ -325,7 +326,7 @@ module chamfer(chamfer=1, size=[1,1,1], edges=EDGES_ALL) module chamfer_cylinder_mask(r=1.0, d=undef, chamfer=0.25, ang=45, from_end=false, orient=ORIENT_Z) { r = get_radius(r=r, d=d, dflt=1); - rot(orient) cylinder_mask(l=chamfer*3, r=r, chamfer2=chamfer, chamfang2=ang, from_end=from_end, ends_only=true, align=DOWN); + rot(orient) cylinder_mask(l=chamfer*3, r=r, chamfer2=chamfer, chamfang2=ang, from_end=from_end, ends_only=true, align=DOWN) children(); } @@ -343,6 +344,8 @@ module chamfer_cylinder_mask(r=1.0, d=undef, chamfer=0.25, ang=45, from_end=fals // ang = Angle of chamfer in degrees from vertical. (Default: 45) // from_end = If true, chamfer size is measured from end of hole. If false, chamfer is measured outset from the radius of the hole. (Default: false) // overage = The extra thickness of the mask. Default: `0.1`. +// orient = Orientation of the mask. Use the `ORIENT_` constants from `constants.h`. Default: `ORIENT_Z`. +// align = Alignment of the mask. Use the constants from `constants.h`. Default: `CENTER`. // Example: // difference() { // cube(100, center=true); @@ -351,18 +354,18 @@ module chamfer_cylinder_mask(r=1.0, d=undef, chamfer=0.25, ang=45, from_end=fals // } // Example: // chamfer_hole_mask(d=100, chamfer=25, ang=30, overage=10); -module chamfer_hole_mask(r=undef, d=undef, chamfer=0.25, ang=45, from_end=false, overage=0.1) +module chamfer_hole_mask(r=undef, d=undef, chamfer=0.25, ang=45, from_end=false, overage=0.1, orient=ORIENT_Z, align=CENTER) { r = get_radius(r=r, d=d, dflt=1); h = chamfer * (from_end? 1 : tan(90-ang)); r2 = r + chamfer * (from_end? tan(ang) : 1); $fn = segs(r); - difference() { + orient_and_align([2*r, 2*r, h*2], orient, align, size2=[2*r2, 2*r2], chain=true) { union() { cylinder(r=r2, h=overage, center=false); down(h) cylinder(r1=r, r2=r2, h=h, center=false); } - cylinder(r=r-overage, h=h*2.1+overage, center=true); + children(); } } @@ -372,7 +375,7 @@ module chamfer_hole_mask(r=undef, d=undef, chamfer=0.25, ang=45, from_end=false, // Module: fillet_mask() // Usage: -// fillet_mask(l|h, r, [orient], [align], [center]) +// fillet_mask(l|h, r, [orient], [align]) // Description: // Creates a shape that can be used to fillet a vertical 90 degree edge. // Difference it from the object to be filletted. The center of the mask @@ -382,30 +385,30 @@ module chamfer_hole_mask(r=undef, d=undef, chamfer=0.25, ang=45, from_end=false, // r = Radius of the fillet. // orient = Orientation of the mask. Use the `ORIENT_` constants from `constants.h`. Default: vertical. // align = Alignment of the mask. Use the constants from `constants.h`. Default: centered. -// center = If true, centers vertically. If false, lift up to sit on top of the XY plane. Overrides `align`. // Example: // difference() { // cube(size=100, center=false); // #fillet_mask(l=100, r=25, orient=ORIENT_Z, align=UP); // } -module fillet_mask(l=undef, r=1.0, orient=ORIENT_Z, align=CENTER, h=undef, center=undef) +module fillet_mask(l=undef, r=1.0, orient=ORIENT_Z, align=CENTER, h=undef) { l = first_defined([l, h, 1]); sides = quantup(segs(r),4); - orient_and_align([2*r, 2*r, l], orient, align, center=center) { + orient_and_align([2*r, 2*r, l], orient, align, chain=true) { linear_extrude(height=l+0.1, convexity=4, center=true) { difference() { square(2*r, center=true); xspread(2*r) yspread(2*r) circle(r=r, $fn=sides); } } + children(); } } // Module: fillet_mask_x() // Usage: -// fillet_mask_x(l, r, [align], [center]) +// fillet_mask_x(l, r, [align]) // Description: // Creates a shape that can be used to fillet a 90 degree edge oriented // along the X axis. Difference it from the object to be filletted. @@ -415,18 +418,17 @@ module fillet_mask(l=undef, r=1.0, orient=ORIENT_Z, align=CENTER, h=undef, cente // l = Length of mask. // r = Radius of the fillet. // align = Alignment of the mask. Use the constants from `constants.h`. Default: centered. -// center = If true, centers vertically. If false, lift up to sit on top of the XY plane. Overrides `align`. // Example: // difference() { // cube(size=100, center=false); // #fillet_mask_x(l=100, r=25, align=RIGHT); // } -module fillet_mask_x(l=1.0, r=1.0, align=CENTER) fillet_mask(l=l, r=r, orient=ORIENT_X, align=align); +module fillet_mask_x(l=1.0, r=1.0, align=CENTER) fillet_mask(l=l, r=r, orient=ORIENT_X, align=align) children(); // Module: fillet_mask_y() // Usage: -// fillet_mask_y(l, r, [align], [center]) +// fillet_mask_y(l, r, [align]) // Description: // Creates a shape that can be used to fillet a 90 degree edge oriented // along the Y axis. Difference it from the object to be filletted. @@ -436,18 +438,17 @@ module fillet_mask_x(l=1.0, r=1.0, align=CENTER) fillet_mask(l=l, r=r, orient=OR // l = Length of mask. // r = Radius of the fillet. // align = Alignment of the mask. Use the constants from `constants.h`. Default: centered. -// center = If true, centers vertically. If false, lift up to sit on top of the XY plane. Overrides `align`. // Example: // difference() { // cube(size=100, center=false); // right(100) #fillet_mask_y(l=100, r=25, align=BACK); // } -module fillet_mask_y(l=1.0, r=1.0, align=CENTER) fillet_mask(l=l, r=r, orient=ORIENT_Y, align=align); +module fillet_mask_y(l=1.0, r=1.0, align=CENTER) fillet_mask(l=l, r=r, orient=ORIENT_Y, align=align) children(); // Module: fillet_mask_z() // Usage: -// fillet_mask_z(l, r, [align], [center]) +// fillet_mask_z(l, r, [align]) // Description: // Creates a shape that can be used to fillet a 90 degree edge oriented // along the Z axis. Difference it from the object to be filletted. @@ -457,13 +458,12 @@ module fillet_mask_y(l=1.0, r=1.0, align=CENTER) fillet_mask(l=l, r=r, orient=OR // l = Length of mask. // r = Radius of the fillet. // align = Alignment of the mask. Use the constants from `constants.h`. Default: centered. -// center = If true, centers vertically. If false, lift up to sit on top of the XY plane. Overrides `align`. // Example: // difference() { // cube(size=100, center=false); // #fillet_mask_z(l=100, r=25, align=UP); // } -module fillet_mask_z(l=1.0, r=1.0, align=CENTER) fillet_mask(l=l, r=r, orient=ORIENT_Z, align=align); +module fillet_mask_z(l=1.0, r=1.0, align=CENTER) fillet_mask(l=l, r=r, orient=ORIENT_Z, align=align) children(); // Module: fillet() @@ -505,7 +505,7 @@ module fillet(fillet=1, size=[1,1,1], edges=EDGES_ALL) // Module: fillet_angled_edge_mask() // Usage: -// fillet_angled_edge_mask(h, r, [ang], [center]); +// fillet_angled_edge_mask(h, r, [ang], [orient], [align]); // Description: // Creates a vertical mask that can be used to fillet the edge where two // face meet, at any arbitrary angle. Difference it from the object to @@ -515,35 +515,39 @@ module fillet(fillet=1, size=[1,1,1], edges=EDGES_ALL) // h = height of vertical mask. // r = radius of the fillet. // ang = angle that the planes meet at. -// center = If true, vertically center mask. +// orient = Orientation of the mask. Use the `ORIENT_` constants from `constants.h`. Default: `ORIENT_Z`. +// align = Alignment of the mask. Use the constants from `constants.h`. Default: `CENTER`. // Example: // difference() { // angle_pie_mask(ang=70, h=50, d=100); // #fillet_angled_edge_mask(h=51, r=20.0, ang=70, $fn=32); // } -module fillet_angled_edge_mask(h=1.0, r=1.0, ang=90, center=true) +module fillet_angled_edge_mask(h=1.0, r=1.0, ang=90, orient=ORIENT_Z, align=CENTER) { sweep = 180-ang; n = ceil(segs(r)*sweep/360); x = r*sin(90-(ang/2))/sin(ang/2); - linear_extrude(height=h, convexity=4, center=center) { - polygon( - points=concat( - [for (i = [0:n]) let (a=90+ang+i*sweep/n) [r*cos(a)+x, r*sin(a)+r]], - [for (i = [0:n]) let (a=90+i*sweep/n) [r*cos(a)+x, r*sin(a)-r]], - [ - [min(-1, r*cos(270-ang)+x-1), r*sin(270-ang)-r], - [min(-1, r*cos(90+ang)+x-1), r*sin(90+ang)+r], - ] - ) - ); + orient_and_align([2*x,2*r,h], orient, align, chain=true) { + linear_extrude(height=h, convexity=4, center=true) { + polygon( + points=concat( + [for (i = [0:n]) let (a=90+ang+i*sweep/n) [r*cos(a)+x, r*sin(a)+r]], + [for (i = [0:n]) let (a=90+i*sweep/n) [r*cos(a)+x, r*sin(a)-r]], + [ + [min(-1, r*cos(270-ang)+x-1), r*sin(270-ang)-r], + [min(-1, r*cos(90+ang)+x-1), r*sin(90+ang)+r], + ] + ) + ); + } + children(); } } // Module: fillet_angled_corner_mask() // Usage: -// fillet_angled_corner_mask(fillet, ang); +// fillet_angled_corner_mask(fillet, ang, [orient], [align]); // Description: // Creates a shape that can be used to fillet the corner of an angle. // Difference it from the object to be filletted. The center of the mask @@ -551,6 +555,8 @@ module fillet_angled_edge_mask(h=1.0, r=1.0, ang=90, center=true) // Arguments: // fillet = radius of the fillet. // ang = angle between planes that you need to fillet the corner of. +// orient = Orientation of the mask. Use the `ORIENT_` constants from `constants.h`. Default: `ORIENT_Z`. +// align = Alignment of the mask. Use the constants from `constants.h`. Default: `CENTER`. // Example: // ang=60; // difference() { @@ -561,36 +567,41 @@ module fillet_angled_edge_mask(h=1.0, r=1.0, ang=90, center=true) // } // fillet_angled_edge_mask(h=51, r=20, ang=ang); // } -module fillet_angled_corner_mask(fillet=1.0, ang=90) +module fillet_angled_corner_mask(fillet=1.0, ang=90, orient=ORIENT_Z, align=CENTER) { dx = fillet / tan(ang/2); + dx2 = dx / cos(ang/2) + 1; fn = quantup(segs(fillet), 4); - difference() { - down(fillet) cylinder(r=dx/cos(ang/2)+1, h=fillet+1, center=false); - yflip_copy() { - translate([dx, fillet, -fillet]) { - hull() { - sphere(r=fillet, $fn=fn); - down(fillet*3) sphere(r=fillet, $fn=fn); - zrot_copies([0,ang]) { - right(fillet*3) sphere(r=fillet, $fn=fn); + orient_and_align([2*dx2, 2*dx2, fillet*2], orient, align, chain=true) { + difference() { + down(fillet) cylinder(r=dx2, h=fillet+1, center=false); + yflip_copy() { + translate([dx, fillet, -fillet]) { + hull() { + sphere(r=fillet, $fn=fn); + down(fillet*3) sphere(r=fillet, $fn=fn); + zrot_copies([0,ang]) { + right(fillet*3) sphere(r=fillet, $fn=fn); + } } } } } + children(); } } // Module: fillet_corner_mask() // Usage: -// fillet_corner_mask(r); +// fillet_corner_mask(r, [align]); // Description: // Creates a shape that you can use to round 90 degree corners on a fillet. // Difference it from the object to be filletted. The center of the mask // object should align exactly with the corner to be filletted. // Arguments: // r = radius of corner fillet. +// align = Alignment of the mask. Use the constants from `constants.h`. Default: `CENTER`. // Example: // fillet_corner_mask(r=20.0); // Example: @@ -601,20 +612,23 @@ module fillet_angled_corner_mask(fillet=1.0, ang=90) // translate([15, 25, 0]) fillet_mask_z(l=81, r=15); // translate([15, 25, 40]) #fillet_corner_mask(r=15); // } -module fillet_corner_mask(r=1.0) +module fillet_corner_mask(r=1.0, align=CENTER) { - difference() { - cube(size=r*2, center=true); - grid3d(n=[2,2,2], spacing=r*2-0.05) { - sphere(r=r); + orient_and_align([2*r, 2*r, 2*r], ORIENT_Z, align, chain=true) { + difference() { + cube(size=r*2, center=true); + grid3d(n=[2,2,2], spacing=r*2-0.05) { + sphere(r=r); + } } + children(); } } // Module: fillet_cylinder_mask() // Usage: -// fillet_cylinder_mask(r, fillet, [xtilt], [ytilt]); +// fillet_cylinder_mask(r, fillet); // Description: // Create a mask that can be used to round the end of a cylinder. // Difference it from the cylinder to be filletted. The center of the @@ -623,8 +637,6 @@ module fillet_corner_mask(r=1.0) // Arguments: // r = radius of cylinder to fillet. (Default: 1.0) // fillet = radius of the edge filleting. (Default: 0.25) -// xtilt = angle of tilt of end of cylinder in the X direction. (Default: 0) -// ytilt = angle of tilt of end of cylinder in the Y direction. (Default: 0) // Example: // difference() { // cylinder(r=50, h=50, center=false); @@ -633,22 +645,18 @@ module fillet_corner_mask(r=1.0) // Example: // difference() { // cylinder(r=50, h=100, center=false); -// up(75) fillet_cylinder_mask(r=50, fillet=10, xtilt=30); +// up(75) fillet_cylinder_mask(r=50, fillet=10); // } -module fillet_cylinder_mask(r=1.0, fillet=0.25, xtilt=0, ytilt=0) +module fillet_cylinder_mask(r=1.0, fillet=0.25) { - skew_xz(za=xtilt) { - skew_yz(za=ytilt) { - cylinder_mask(l=fillet*3, r=r, fillet2=fillet, overage=fillet+2*r*sin(max(xtilt,ytilt)), ends_only=true, align=DOWN); - } - } + cylinder_mask(l=fillet*3, r=r, fillet2=fillet, overage=fillet, ends_only=true, align=DOWN) children(); } // Module: fillet_hole_mask() // Usage: -// fillet_hole_mask(r|d, fillet, [xtilt], [ytilt]); +// fillet_hole_mask(r|d, fillet); // Description: // Create a mask that can be used to round the edge of a circular hole. // Difference it from the hole to be filletted. The center of the @@ -658,9 +666,9 @@ module fillet_cylinder_mask(r=1.0, fillet=0.25, xtilt=0, ytilt=0) // r = Radius of hole to fillet. // d = Diameter of hole to fillet. // fillet = Radius of the filleting. (Default: 0.25) -// xtilt = Angle of tilt of end of cylinder in the X direction. (Default: 0) -// ytilt = Angle of tilt of end of cylinder in the Y direction. (Default: 0) // overage = The extra thickness of the mask. Default: `0.1`. +// orient = Orientation of the mask. Use the `ORIENT_` constants from `constants.h`. Default: `ORIENT_Z`. +// align = Alignment of the mask. Use the constants from `constants.h`. Default: `CENTER`. // Example: // difference() { // cube([150,150,100], center=true); @@ -669,18 +677,17 @@ module fillet_cylinder_mask(r=1.0, fillet=0.25, xtilt=0, ytilt=0) // } // Example: // fillet_hole_mask(r=40, fillet=20, $fa=2, $fs=2); -module fillet_hole_mask(r=undef, d=undef, fillet=0.25, overage=0.1, xtilt=0, ytilt=0) +module fillet_hole_mask(r=undef, d=undef, fillet=0.25, overage=0.1, orient=ORIENT_Z, align=CENTER) { r = get_radius(r=r, d=d, dflt=1); - skew_xz(za=xtilt) { - skew_yz(za=ytilt) { - rotate_extrude(convexity=4) { - difference() { - right(r-overage) fwd(fillet) square(fillet+overage, center=false); - right(r+fillet) fwd(fillet) circle(r=fillet); - } + orient_and_align([2*(r+fillet), 2*(r+fillet), fillet*2], orient, align, chain=true) { + rotate_extrude(convexity=4) { + difference() { + right(r-overage) fwd(fillet) square(fillet+overage, center=false); + right(r+fillet) fwd(fillet) circle(r=fillet); } } + children(); } } diff --git a/math.scad b/math.scad index 26a8518..aa1655f 100644 --- a/math.scad +++ b/math.scad @@ -10,7 +10,7 @@ /* BSD 2-Clause License -Copyright (c) 2017, Revar Desmera +Copyright (c) 2017-2019, Revar Desmera All rights reserved. Redistribution and use in source and binary forms, with or without @@ -346,7 +346,7 @@ function any(l, i=0, succ=false) = (i>=len(l) || succ)? succ : any( l, i=i+1, succ=( - is_array(l[i])? any(l[i]) : + is_list(l[i])? any(l[i]) : !(!l[i]) ) ); @@ -369,7 +369,7 @@ function all(l, i=0, fail=false) = (i>=len(l) || fail)? (!fail) : all( l, i=i+1, fail=( - is_array(l[i])? !all(l[i]) : + is_list(l[i])? !all(l[i]) : !l[i] ) ); @@ -399,1395 +399,10 @@ function count_true(l, nmax=undef, i=0, cnt=0) = (i>=len(l) || (nmax!=undef && cnt>=nmax))? cnt : count_true( l=l, nmax=nmax, i=i+1, cnt=cnt+( - is_array(l[i])? count_true(l[i], nmax=nmax-cnt) : + is_list(l[i])? count_true(l[i], nmax=nmax-cnt) : (l[i]? 1 : 0) ) ); - -// Section: List/Array Operations - - -// Function: replist() -// Usage: -// replist(val, n) -// Description: -// Generates a list or array of `n` copies of the given `list`. -// If the count `n` is given as a list of counts, then this creates a -// multi-dimensional array, filled with `val`. -// Arguments: -// val = The value to repeat to make the list or array. -// n = The number of copies to make of `val`. -// Example: -// replist(1, 4); // Returns [1,1,1,1] -// replist(8, [2,3]); // Returns [[8,8,8], [8,8,8]] -// replist(0, [2,2,3]); // Returns [[[0,0,0],[0,0,0]], [[0,0,0],[0,0,0]]] -// replist([1,2,3],3); // Returns [[1,2,3], [1,2,3], [1,2,3]] -function replist(val, n, i=0) = - is_scalar(n)? [for(j=[1:n]) val] : - (i>=len(n))? val : - [for (j=[1:n[i]]) replist(val, n, i+1)]; - - -// Function: in_list() -// Description: Returns true if value `x` is in list `l`. -// Arguments: -// x = The value to search for. -// l = The list to search. -// idx = If given, searches the given subindexes for matches for `x`. -// Example: -// in_list("bar", ["foo", "bar", "baz"]); // Returns true. -// in_list("bee", ["foo", "bar", "baz"]); // Returns false. -// in_list("bar", [[2,"foo"], [4,"bar"], [3,"baz"]], idx=1); // Returns true. -function in_list(x,l,idx=undef) = search([x], l, num_returns_per_match=1, index_col_num=idx) != [[]]; - - -// Function: slice() -// Description: -// Returns a slice of a list. The first item is index 0. -// Negative indexes are counted back from the end. The last item is -1. -// Arguments: -// arr = The array/list to get the slice of. -// st = The index of the first item to return. -// end = The index after the last item to return, unless negative, in which case the last item to return. -// Example: -// slice([3,4,5,6,7,8,9], 3, 5); // Returns [6,7] -// slice([3,4,5,6,7,8,9], 2, -1); // Returns [5,6,7,8,9] -// slice([3,4,5,6,7,8,9], 1, 1); // Returns [] -// slice([3,4,5,6,7,8,9], 6, -1); // Returns [9] -// slice([3,4,5,6,7,8,9], 2, -2); // Returns [5,6,7,8] -function slice(arr,st,end) = let( - s=st<0?(len(arr)+st):st, - e=end<0?(len(arr)+end+1):end - ) (s==e)? [] : [for (i=[s:e-1]) if (e>s) arr[i]]; - - -// Function: select() -// Description: -// Returns a portion of a list, wrapping around past the beginning, if endlength)? array_trim(v,length) : array_pad(v,length,fill); - - -// Function: enumerate() -// Description: -// Returns a list, with each item of the given list `l` numbered in a sublist. -// Something like: `[[0,l[0]], [1,l[1]], [2,l[2]], ...]` -// Arguments: -// l = List to enumerate. -// idx = If given, enumerates just the given subindex items of `l`. -// Example: -// enumerate(["a","b","c"]); // Returns: [[0,"a"], [1,"b"], [2,"c"]] -// enumerate([[88,"a"],[76,"b"],[21,"c"]], idx=1); // Returns: [[0,"a"], [1,"b"], [2,"c"]] -// enumerate([["cat","a",12],["dog","b",10],["log","c",14]], idx=[1:2]); // Returns: [[0,"a",12], [1,"b",10], [2,"c",14]] -function enumerate(l,idx=undef) = - (l==[])? [] : - (idx==undef)? - [for (i=[0:len(l)-1]) [i,l[i]]] : - [for (i=[0:len(l)-1]) concat([i], [for (j=idx) l[i][j]])]; - - -// Function: array_zip() -// Usage: -// array_zip(v1, v2, v3, [fit], [fill]); -// array_zip(vecs, [fit], [fill]); -// Description: -// Zips together corresponding items from two or more lists. -// Returns a list of lists, where each sublist contains corresponding -// items from each of the input lists. `[[A1, B1, C1], [A2, B2, C2], ...]` -// Arguments: -// vecs = A list of two or more lists to zipper together. -// fit = If `fit=="short"`, the zips together up to the length of the shortest list in vecs. If `fit=="long"`, then pads all lists to the length of the longest, using the value in `fill`. If `fit==false`, then requires all lists to be the same length. Default: false. -// fill = The default value to fill in with if one or more lists if short. Default: undef -// Example: -// v1 = [1,2,3,4]; -// v2 = [5,6,7]; -// v3 = [8,9,10,11]; -// array_zip(v1,v3); // returns [[1,8], [2,9], [3,10], [4,11]] -// array_zip([v1,v3]); // returns [[1,8], [2,9], [3,10], [4,11]] -// array_zip([v1,v2], fit="short"); // returns [[1,5], [2,6], [3,7]] -// array_zip([v1,v2], fit="long"); // returns [[1,5], [2,6], [3,7], [4,undef]] -// array_zip([v1,v2], fit="long, fill=0); // returns [[1,5], [2,6], [3,7], [4,0]] -// array_zip([v1,v2,v3], fit="long"); // returns [[1,5,8], [2,6,9], [3,7,10], [4,undef,11]] -// Example: -// v1 = [[1,2,3], [4,5,6], [7,8,9]]; -// v2 = [[20,19,18], [17,16,15], [14,13,12]]; -// array_zip(v1,v2); // Returns [[1,2,3,20,19,18], [4,5,6,17,16,15], [7,8,9,14,13,12]] -function array_zip(vecs, v2, v3, fit=false, fill=undef) = - (v3!=undef)? array_zip([vecs,v2,v3], fit=fit, fill=fill) : - (v2!=undef)? array_zip([vecs,v2], fit=fit, fill=fill) : - let( - dummy1 = assert_in_list("fit", fit, [false, "short", "long"]), - minlen = array_shortest(vecs), - maxlen = array_longest(vecs), - dummy2 = (fit==false)? assertion(minlen==maxlen, "Input vectors must have the same length") : 0 - ) (fit == "long")? - [for(i=[0:maxlen-1]) [for(v=vecs) for(x=(i 0) arr[i] ] - ) - concat(sort(lesser,idx), equal, sort(greater,idx)); - - -// Function: sortidx() -// Description: -// Given a list, calculates the sort order of the list, and returns -// a list of indexes into the original list in that sorted order. -// If you iterate the returned list in order, and use the list items -// to index into the original list, you will be iterating the original -// values in sorted order. -// Example: -// lst = ["d","b","e","c"]; -// idxs = sortidx(lst); // Returns: [1,3,0,2] -// ordered = [for (i=idxs) lst[i]]; // Returns: ["b", "c", "d", "e"] -// Example: -// lst = [ -// ["foo", 88, [0,0,1], false], -// ["bar", 90, [0,1,0], true], -// ["baz", 89, [1,0,0], false], -// ["qux", 23, [1,1,1], true] -// ]; -// idxs1 = sortidx(lst, idx=1); // Returns: [3,0,2,1] -// idxs2 = sortidx(lst, idx=0); // Returns: [1,2,0,3] -// idxs3 = sortidx(lst, idx=[1,3]); // Returns: [3,0,2,1] -function sortidx(l, idx=undef) = - (l==[])? [] : - let( - ll=enumerate(l,idx=idx), - sidx = [1:len(ll[0])-1] - ) - array_subindex(sort(ll, idx=sidx), 0); - - -// Function: unique() -// Usage: -// unique(arr); -// Description: -// Returns a sorted list with all repeated items removed. -// Arguments: -// arr = The list to uniquify. -function unique(arr) = - len(arr)<=1? arr : let( - sorted = sort(arr) - ) [ - for (i=[0:len(sorted)-1]) - if (i==0 || (sorted[i] != sorted[i-1])) - sorted[i] - ]; - - - -// Function: list_remove() -// Usage: -// list_remove(list, elements) -// Description: -// Remove all items from `list` whose indexes are in `elements`. -// Arguments: -// list = The list to remove items from. -// elements = The list of indexes of items to remove. -function list_remove(list, elements) = [ - for (i = [0:len(list)-1]) if (!search(i, elements)) list[i] -]; - - - -// Internal. Not exposed. -function _array_dim_recurse(v) = - !is_list(v[0])? ( - sum( [for(entry=v) is_list(entry) ? 1 : 0]) == 0 ? [] : [undef] - ) : let( - firstlen = len(v[0]), - first = sum( [for(entry = v) len(entry) == firstlen ? 0 : 1] ) == 0 ? firstlen : undef, - leveldown = flatten(v) - ) is_list(leveldown[0])? ( - concat([first],_array_dim_recurse(leveldown)) - ) : [first]; - - -// Function: array_dim() -// Usage: -// array_dim(v, [depth]) -// Description: -// Returns the size of a multi-dimensional array. Returns a list of -// dimension lengths. The length of `v` is the dimension `0`. The -// length of the items in `v` is dimension `1`. The length of the -// items in the items in `v` is dimension `2`, etc. For each dimension, -// if the length of items at that depth is inconsistent, `undef` will -// be returned. If no items of that dimension depth exist, `0` is -// returned. Otherwise, the consistent length of items in that -// dimensional depth is returned. -// Arguments: -// v = Array to get dimensions of. -// depth = Dimension to get size of. If not given, returns a list of dimension lengths. -// Examples: -// array_dim([[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]]); // Returns [2,2,3] -// array_dim([[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]], 0); // Returns 2 -// array_dim([[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]], 2); // Returns 3 -// array_dim([[[1,2,3],[4,5,6]],[[7,8,9]]]); // Returns [2,undef,3] -function array_dim(v, depth=undef) = - (depth == undef)? ( - concat([len(v)], _array_dim_recurse(v)) - ) : (depth == 0)? ( - len(v) - ) : ( - let(dimlist = _array_dim_recurse(v)) - (depth > len(dimlist))? 0 : dimlist[depth-1] - ); - - - -// Section: Vector Manipulation - -// Function: vmul() -// Description: -// Element-wise vector multiplication. Multiplies each element of vector `v1` by -// the corresponding element of vector `v2`. Returns a vector of the products. -// Arguments: -// v1 = The first vector. -// v2 = The second vector. -// Example: -// vmul([3,4,5], [8,7,6]); // Returns [24, 28, 30] -function vmul(v1, v2) = [for (i = [0:len(v1)-1]) v1[i]*v2[i]]; - - -// Function: vdiv() -// Description: -// Element-wise vector division. Divides each element of vector `v1` by -// the corresponding element of vector `v2`. Returns a vector of the quotients. -// Arguments: -// v1 = The first vector. -// v2 = The second vector. -// Example: -// vdiv([24,28,30], [8,7,6]); // Returns [3, 4, 5] -function vdiv(v1, v2) = [for (i = [0:len(v1)-1]) v1[i]/v2[i]]; - - -// Function: vabs() -// Description: Returns a vector of the absolute value of each element of vector `v`. -// Arguments: -// v = The vector to get the absolute values of. -function vabs(v) = [for (x=v) abs(x)]; - - -// Function: normalize() -// Description: -// Returns unit length normalized version of vector v. -// Arguments: -// v = The vector to normalize. -function normalize(v) = v/norm(v); - - -// Function: vector_angle() -// Usage: -// vector_angle(v1,v2); -// Description: -// Returns angle in degrees between two vectors of similar dimensions. -// Arguments: -// v1 = First vector. -// v2 = Second vector. -// NOTE: constrain() corrects crazy FP rounding errors that exceed acos()'s domain. -function vector_angle(v1,v2) = acos(constrain((v1*v2)/(norm(v1)*norm(v2)), -1, 1)); - - -// Function: vector_axis() -// Usage: -// vector_xis(v1,v2); -// Description: -// Returns the vector perpendicular to both of the given vectors. -// Arguments: -// v1 = First vector. -// v2 = Second vector. -function vector_axis(v1,v2) = - let( - eps = 1e-6, - v1 = point3d(v1/norm(v1)), - v2 = point3d(v2/norm(v2)), - v3 = (norm(v1-v2) > eps && norm(v1+v2) > eps)? v2 : - (norm(vabs(v2)-UP) > eps)? UP : - RIGHT - ) normalize(cross(v1,v3)); - - -// Section: Coordinates Manipulation - -// Function: point2d() -// Description: -// Returns a 2D vector/point from a 2D or 3D vector. -// If given a 3D point, removes the Z coordinate. -// Arguments: -// p = The coordinates to force into a 2D vector/point. -function point2d(p) = [for (i=[0:1]) (p[i]==undef)? 0 : p[i]]; - - -// Function: path2d() -// Description: -// Returns a list of 2D vectors/points from a list of 2D or 3D vectors/points. -// If given a 3D point list, removes the Z coordinates from each point. -// Arguments: -// points = A list of 2D or 3D points/vectors. -function path2d(points) = [for (point = points) point2d(point)]; - - -// Function: point3d() -// Description: -// Returns a 3D vector/point from a 2D or 3D vector. -// Arguments: -// p = The coordinates to force into a 3D vector/point. -function point3d(p) = [for (i=[0:2]) (p[i]==undef)? 0 : p[i]]; - - -// Function: path3d() -// Description: -// Returns a list of 3D vectors/points from a list of 2D or 3D vectors/points. -// Arguments: -// points = A list of 2D or 3D points/vectors. -function path3d(points) = [for (point = points) point3d(point)]; - - -// Function: translate_points() -// Usage: -// translate_points(pts, v); -// Description: -// Moves each point in an array by a given amount. -// Arguments: -// pts = List of points to translate. -// v = Amount to translate points by. -function translate_points(pts, v=[0,0,0]) = [for (pt = pts) pt+v]; - - -// Function: scale_points() -// Usage: -// scale_points(pts, v, [cp]); -// Description: -// Scales each point in an array by a given amount, around a given centerpoint. -// Arguments: -// pts = List of points to scale. -// v = A vector with a scaling factor for each axis. -// cp = Centerpoint to scale around. -function scale_points(pts, v=[0,0,0], cp=[0,0,0]) = [for (pt = pts) [for (i = [0:len(pt)-1]) (pt[i]-cp[i])*v[i]+cp[i]]]; - - -// Function: rotate_points2d() -// Usage: -// rotate_points2d(pts, ang, [cp]); -// Description: -// Rotates each 2D point in an array by a given amount, around an optional centerpoint. -// Arguments: -// pts = List of 3D points to rotate. -// ang = Angle to rotate by. -// cp = 2D Centerpoint to rotate around. Default: `[0,0]` -function rotate_points2d(pts, ang, cp=[0,0]) = let( - m = matrix3_zrot(ang) - ) [for (pt = pts) m*point3d(pt-cp)+cp]; - - -// Function: rotate_points3d() -// Usage: -// rotate_points3d(pts, a, [cp], [reverse]); -// rotate_points3d(pts, a, v, [cp], [reverse]); -// rotate_points3d(pts, from, to, [a], [cp], [reverse]); -// Description: -// Rotates each 3D point in an array by a given amount, around a given centerpoint. -// Arguments: -// pts = List of points to rotate. -// a = Rotation angle(s) in degrees. -// v = If given, axis vector to rotate around. -// cp = Centerpoint to rotate around. -// from = If given, the vector to rotate something from. Used with `to`. -// to = If given, the vector to rotate something to. Used with `from`. -// reverse = If true, performs an exactly reversed rotation. -function rotate_points3d(pts, a=0, v=undef, cp=[0,0,0], from=undef, to=undef, reverse=false) = - let( - dummy = assertion(is_def(from)==is_def(to), "`from` and `to` must be given together."), - mrot = reverse? ( - is_def(from)? ( - let ( - from = from / norm(from), - to = to / norm(from), - ang = vector_angle(from, to), - v = vector_axis(from, to) - ) - matrix4_rot_by_axis(from, -a) * matrix4_rot_by_axis(v, -ang) - ) : is_def(v)? ( - matrix4_rot_by_axis(v, -a) - ) : is_scalar(a)? ( - matrix4_zrot(-a) - ) : ( - matrix4_xrot(-a.x) * matrix4_yrot(-a.y) * matrix4_zrot(-a.z) - ) - ) : ( - is_def(from)? ( - let ( - from = from / norm(from), - to = to / norm(from), - ang = vector_angle(from, to), - v = vector_axis(from, to) - ) - matrix4_rot_by_axis(v, ang) * matrix4_rot_by_axis(from, a) - ) : is_def(v)? ( - matrix4_rot_by_axis(v, a) - ) : is_scalar(a)? ( - matrix4_zrot(a) - ) : ( - matrix4_zrot(a.z) * matrix4_yrot(a.y) * matrix4_xrot(a.x) - ) - ), - m = matrix4_translate(cp) * mrot * matrix4_translate(-cp) - ) [for (pt = pts) point3d(m*concat(point3d(pt),[1]))]; - - - -// Section: Coordinate Systems - -// Function: polar_to_xy() -// Usage: -// polar_to_xy(r, theta); -// polar_to_xy([r, theta]); -// Description: -// Convert polar coordinates to 2D cartesian coordinates. -// Returns [X,Y] cartesian coordinates. -// Arguments: -// r = distance from the origin. -// theta = angle in degrees, counter-clockwise of X+. -// Examples: -// xy = polar_to_xy(20,30); -// xy = polar_to_xy([40,60]); -function polar_to_xy(r,theta=undef) = let( - rad = theta==undef? r[0] : r, - t = theta==undef? r[1] : theta - ) rad*[cos(t), sin(t)]; - - -// Function: xy_to_polar() -// Usage: -// xy_to_polar(x,y); -// xy_to_polar([X,Y]); -// Description: -// Convert 2D cartesian coordinates to polar coordinates. -// Returns [radius, theta] where theta is the angle counter-clockwise of X+. -// Arguments: -// x = X coordinate. -// y = Y coordinate. -// Examples: -// plr = xy_to_polar(20,30); -// plr = xy_to_polar([40,60]); -function xy_to_polar(x,y=undef) = let( - xx = y==undef? x[0] : x, - yy = y==undef? x[1] : y - ) [norm([xx,yy]), atan2(yy,xx)]; - - -// Function: xyz_to_planar() -// Usage: -// xyz_to_planar(point, a, b, c); -// Description: -// Given three points defining a plane, returns the projected planar -// [X,Y] coordinates of the closest point to a 3D `point`. The origin -// of the planar coordinate system [0,0] will be at point `a`, and the -// Y+ axis direction will be towards point `b`. This coordinate system -// can be useful in taking a set of nearly coplanar points, and converting -// them to a pure XY set of coordinates for manipulation, before convering -// them back to the original 3D plane. -function xyz_to_planar(point, a, b, c) = let( - u = normalize(b-a), - v = normalize(c-a), - n = normalize(cross(u,v)), - w = normalize(cross(n,u)), - relpoint = point-a -) [relpoint * w, relpoint * u]; - - -// Function: planar_to_xyz() -// Usage: -// planar_to_xyz(point, a, b, c); -// Description: -// Given three points defining a plane, converts a planar [X,Y] -// coordinate to the actual corresponding 3D point on the plane. -// The origin of the planar coordinate system [0,0] will be at point -// `a`, and the Y+ axis direction will be towards point `b`. -function planar_to_xyz(point, a, b, c) = let( - u = normalize(b-a), - v = normalize(c-a), - n = normalize(cross(u,v)), - w = normalize(cross(n,u)) -) a + point.x * w + point.y * u; - - -// Function: cylindrical_to_xyz() -// Usage: -// cylindrical_to_xyz(r, theta, z) -// cylindrical_to_xyz([r, theta, z]) -// Description: -// Convert cylindrical coordinates to 3D cartesian coordinates. Returns [X,Y,Z] cartesian coordinates. -// Arguments: -// r = distance from the Z axis. -// theta = angle in degrees, counter-clockwise of X+ on the XY plane. -// z = Height above XY plane. -// Examples: -// xyz = cylindrical_to_xyz(20,30,40); -// xyz = cylindrical_to_xyz([40,60,50]); -function cylindrical_to_xyz(r,theta=undef,z=undef) = let( - rad = theta==undef? r[0] : r, - t = theta==undef? r[1] : theta, - zed = theta==undef? r[2] : z - ) [rad*cos(t), rad*sin(t), zed]; - - -// Function: xyz_to_cylindrical() -// Usage: -// xyz_to_cylindrical(x,y,z) -// xyz_to_cylindrical([X,Y,Z]) -// Description: -// Convert 3D cartesian coordinates to cylindrical coordinates. -// Returns [radius,theta,Z]. Theta is the angle counter-clockwise -// of X+ on the XY plane. Z is height above the XY plane. -// Arguments: -// x = X coordinate. -// y = Y coordinate. -// z = Z coordinate. -// Examples: -// cyl = xyz_to_cylindrical(20,30,40); -// cyl = xyz_to_cylindrical([40,50,70]); -function xyz_to_cylindrical(x,y=undef,z=undef) = let( - p = is_scalar(x)? [x, default(y,0), default(z,0)] : point3d(x) - ) [norm([p.x,p.y]), atan2(p.y,p.x), p.z]; - - -// Function: spherical_to_xyz() -// Usage: -// spherical_to_xyz(r, theta, phi); -// spherical_to_xyz([r, theta, phi]); -// Description: -// Convert spherical coordinates to 3D cartesian coordinates. -// Returns [X,Y,Z] cartesian coordinates. -// Arguments: -// r = distance from origin. -// theta = angle in degrees, counter-clockwise of X+ on the XY plane. -// phi = angle in degrees from the vertical Z+ axis. -// Examples: -// xyz = spherical_to_xyz(20,30,40); -// xyz = spherical_to_xyz([40,60,50]); -function spherical_to_xyz(r,theta=undef,phi=undef) = let( - rad = theta==undef? r[0] : r, - t = theta==undef? r[1] : theta, - p = theta==undef? r[2] : phi - ) rad*[sin(p)*cos(t), sin(p)*sin(t), cos(p)]; - - -// Function: xyz_to_spherical() -// Usage: -// xyz_to_spherical(x,y,z) -// xyz_to_spherical([X,Y,Z]) -// Description: -// Convert 3D cartesian coordinates to spherical coordinates. -// Returns [r,theta,phi], where phi is the angle from the Z+ pole, -// and theta is degrees counter-clockwise of X+ on the XY plane. -// Arguments: -// x = X coordinate. -// y = Y coordinate. -// z = Z coordinate. -// Examples: -// sph = xyz_to_spherical(20,30,40); -// sph = xyz_to_spherical([40,50,70]); -function xyz_to_spherical(x,y=undef,z=undef) = let( - p = is_scalar(x)? [x, default(y,0), default(z,0)] : point3d(x) - ) [norm(p), atan2(p.y,p.x), atan2(norm([p.x,p.y]),p.z)]; - - -// Function: altaz_to_xyz() -// Usage: -// altaz_to_xyz(alt, az, r); -// altaz_to_xyz([alt, az, r]); -// Description: -// Convert altitude/azimuth/range coordinates to 3D cartesian coordinates. -// Returns [X,Y,Z] cartesian coordinates. -// Arguments: -// alt = altitude angle in degrees above the XY plane. -// az = azimuth angle in degrees clockwise of Y+ on the XY plane. -// r = distance from origin. -// Examples: -// xyz = altaz_to_xyz(20,30,40); -// xyz = altaz_to_xyz([40,60,50]); -function altaz_to_xyz(alt,az=undef,r=undef) = let( - p = az==undef? alt[0] : alt, - t = 90 - (az==undef? alt[1] : az), - rad = az==undef? alt[2] : r - ) rad*[cos(p)*cos(t), cos(p)*sin(t), sin(p)]; - - -// Function: xyz_to_altaz() -// Usage: -// xyz_to_altaz(x,y,z); -// xyz_to_altaz([X,Y,Z]); -// Description: -// Convert 3D cartesian coordinates to altitude/azimuth/range coordinates. -// Returns [altitude,azimuth,range], where altitude is angle above the -// XY plane, azimuth is degrees clockwise of Y+ on the XY plane, and -// range is the distance from the origin. -// Arguments: -// x = X coordinate. -// y = Y coordinate. -// z = Z coordinate. -// Examples: -// aa = xyz_to_altaz(20,30,40); -// aa = xyz_to_altaz([40,50,70]); -function xyz_to_altaz(x,y=undef,z=undef) = let( - p = is_scalar(x)? [x, default(y,0), default(z,0)] : point3d(x) - ) [atan2(p.z,norm([p.x,p.y])), atan2(p.x,p.y), norm(p)]; - - -// Section: Matrix Manipulation - -// Function: ident() -// Description: Create an `n` by `n` identity matrix. -// Arguments: -// n = The size of the identity matrix square, `n` by `n`. -function ident(n) = [for (i = [0:n-1]) [for (j = [0:n-1]) (i==j)?1:0]]; - - - -// Function: matrix_transpose() -// Description: Returns the transposition of the given matrix. -// Example: -// m = [ -// [11,12,13,14], -// [21,22,23,24], -// [31,32,33,34], -// [41,42,43,44] -// ]; -// tm = matrix_transpose(m); -// // Returns: -// // [ -// // [11,21,31,41], -// // [12,22,32,42], -// // [13,23,33,43], -// // [14,24,34,44] -// // ] -function matrix_transpose(m) = [for (i=[0:len(m[0])-1]) [for (j=[0:len(m)-1]) m[j][i]]]; - - - -// Function: mat3_to_mat4() -// Description: Takes a 3x3 matrix and returns its 4x4 equivalent. -function mat3_to_mat4(m) = concat( - [for (r = [0:2]) - concat( - [for (c = [0:2]) m[r][c]], - [0] - ) - ], - [[0, 0, 0, 1]] -); - - - -// Function: matrix3_translate() -// Description: -// Returns the 3x3 matrix to perform a 2D translation. -// Arguments: -// v = 2D Offset to translate by. [X,Y] -function matrix3_translate(v) = [ - [1, 0, v.x], - [0, 1, v.y], - [0 ,0, 1] -]; - - -// Function: matrix4_translate() -// Description: -// Returns the 4x4 matrix to perform a 3D translation. -// Arguments: -// v = 3D offset to translate by. [X,Y,Z] -function matrix4_translate(v) = [ - [1, 0, 0, v.x], - [0, 1, 0, v.y], - [0, 0, 1, v.z], - [0 ,0, 0, 1] -]; - - -// Function: matrix3_scale() -// Description: -// Returns the 3x3 matrix to perform a 2D scaling transformation. -// Arguments: -// v = 2D vector of scaling factors. [X,Y] -function matrix3_scale(v) = [ - [v.x, 0, 0], - [ 0, v.y, 0], - [ 0, 0, 1] -]; - - -// Function: matrix4_scale() -// Description: -// Returns the 4x4 matrix to perform a 3D scaling transformation. -// Arguments: -// v = 3D vector of scaling factors. [X,Y,Z] -function matrix4_scale(v) = [ - [v.x, 0, 0, 0], - [ 0, v.y, 0, 0], - [ 0, 0, v.z, 0], - [ 0, 0, 0, 1] -]; - - -// Function: matrix3_zrot() -// Description: -// Returns the 3x3 matrix to perform a rotation of a 2D vector around the Z axis. -// Arguments: -// ang = Number of degrees to rotate. -function matrix3_zrot(ang) = [ - [cos(ang), -sin(ang), 0], - [sin(ang), cos(ang), 0], - [ 0, 0, 1] -]; - - -// Function: matrix4_xrot() -// Description: -// Returns the 4x4 matrix to perform a rotation of a 3D vector around the X axis. -// Arguments: -// ang = number of degrees to rotate. -function matrix4_xrot(ang) = [ - [1, 0, 0, 0], - [0, cos(ang), -sin(ang), 0], - [0, sin(ang), cos(ang), 0], - [0, 0, 0, 1] -]; - - -// Function: matrix4_yrot() -// Description: -// Returns the 4x4 matrix to perform a rotation of a 3D vector around the Y axis. -// Arguments: -// ang = Number of degrees to rotate. -function matrix4_yrot(ang) = [ - [ cos(ang), 0, sin(ang), 0], - [ 0, 1, 0, 0], - [-sin(ang), 0, cos(ang), 0], - [ 0, 0, 0, 1] -]; - - -// Function: matrix4_zrot() -// Usage: -// matrix4_zrot(ang) -// Description: -// Returns the 4x4 matrix to perform a rotation of a 3D vector around the Z axis. -// Arguments: -// ang = number of degrees to rotate. -function matrix4_zrot(ang) = [ - [cos(ang), -sin(ang), 0, 0], - [sin(ang), cos(ang), 0, 0], - [ 0, 0, 1, 0], - [ 0, 0, 0, 1] -]; - - -// Function: matrix4_rot_by_axis() -// Usage: -// matrix4_rot_by_axis(u, ang); -// Description: -// Returns the 4x4 matrix to perform a rotation of a 3D vector around an axis. -// Arguments: -// u = 3D axis vector to rotate around. -// ang = number of degrees to rotate. -function matrix4_rot_by_axis(u, ang) = let( - u = normalize(u), - c = cos(ang), - c2 = 1-c, - s = sin(ang) -) [ - [u[0]*u[0]*c2+c , u[0]*u[1]*c2-u[2]*s, u[0]*u[2]*c2+u[1]*s, 0], - [u[1]*u[0]*c2+u[2]*s, u[1]*u[1]*c2+c , u[1]*u[2]*c2-u[0]*s, 0], - [u[2]*u[0]*c2-u[1]*s, u[2]*u[1]*c2+u[0]*s, u[2]*u[2]*c2+c , 0], - [ 0, 0, 0, 1] -]; - - -// Function: matrix3_skew() -// Usage: -// matrix3_skew(xa, ya) -// Description: -// Returns the 3x3 matrix to skew a 2D vector along the XY plane. -// Arguments: -// xa = Skew angle, in degrees, in the direction of the X axis. -// ya = Skew angle, in degrees, in the direction of the Y axis. -function matrix3_skew(xa, ya) = [ - [1, tan(xa), 0], - [tan(ya), 1, 0], - [0, 0, 1] -]; - - - -// Function: matrix4_skew_xy() -// Usage: -// matrix4_skew_xy(xa, ya) -// Description: -// Returns the 4x4 matrix to perform a skew transformation along the XY plane.. -// Arguments: -// xa = Skew angle, in degrees, in the direction of the X axis. -// ya = Skew angle, in degrees, in the direction of the Y axis. -function matrix4_skew_xy(xa, ya) = [ - [1, 0, tan(xa), 0], - [0, 1, tan(ya), 0], - [0, 0, 1, 0], - [0, 0, 0, 1] -]; - - - -// Function: matrix4_skew_xz() -// Usage: -// matrix4_skew_xz(xa, za) -// Description: -// Returns the 4x4 matrix to perform a skew transformation along the XZ plane. -// Arguments: -// xa = Skew angle, in degrees, in the direction of the X axis. -// za = Skew angle, in degrees, in the direction of the Z axis. -function matrix4_skew_xz(xa, za) = [ - [1, tan(xa), 0, 0], - [0, 1, 0, 0], - [0, tan(za), 1, 0], - [0, 0, 0, 1] -]; - - -// Function: matrix4_skew_yz() -// Usage: -// matrix4_skew_yz(ya, za) -// Description: -// Returns the 4x4 matrix to perform a skew transformation along the YZ plane. -// Arguments: -// ya = Skew angle, in degrees, in the direction of the Y axis. -// za = Skew angle, in degrees, in the direction of the Z axis. -function matrix4_skew_yz(ya, za) = [ - [ 1, 0, 0, 0], - [tan(ya), 1, 0, 0], - [tan(za), 0, 1, 0], - [ 0, 0, 0, 1] -]; - - -// Function: matrix3_mult() -// Usage: -// matrix3_mult(matrices) -// Description: -// Returns a 3x3 transformation matrix which results from applying each matrix in `matrices` in order. -// Arguments: -// matrices = A list of 3x3 matrices. -// m = Optional starting matrix to apply everything to. -function matrix3_mult(matrices, m=ident(3), i=0) = - (i>=len(matrices))? m : - let (newmat = is_def(m)? matrices[i] * m : matrices[i]) - matrix3_mult(matrices, m=newmat, i=i+1); - - -// Function: matrix4_mult() -// Usage: -// matrix4_mult(matrices) -// Description: -// Returns a 4x4 transformation matrix which results from applying each matrix in `matrices` in order. -// Arguments: -// matrices = A list of 4x4 matrices. -// m = Optional starting matrix to apply everything to. -function matrix4_mult(matrices, m=ident(4), i=0) = - (i>=len(matrices))? m : - let (newmat = is_def(m)? matrices[i] * m : matrices[i]) - matrix4_mult(matrices, m=newmat, i=i+1); - - -// Function: matrix3_apply() -// Usage: -// matrix3_apply(pts, matrices) -// Description: -// Given a list of transformation matrices, applies them in order to the points in the point list. -// Arguments: -// pts = A list of 2D points to transform. -// matrices = A list of 3x3 matrices to apply, in order. -// Example: -// npts = matrix3_apply( -// pts = [for (x=[0:3]) [5*x,0]], -// matrices =[ -// matrix3_scale([3,1]), -// matrix3_rot(90), -// matrix3_translate([5,5]) -// ] -// ); // Returns [[5,5], [5,20], [5,35], [5,50]] -function matrix3_apply(pts, matrices) = let(m = matrix3_mult(matrices)) [for (p = pts) point2d(m * concat(point2d(p),[1]))]; - - -// Function: matrix4_apply() -// Usage: -// matrix4_apply(pts, matrices) -// Description: -// Given a list of transformation matrices, applies them in order to the points in the point list. -// Arguments: -// pts = A list of 3D points to transform. -// matrices = A list of 4x4 matrices to apply, in order. -// Example: -// npts = matrix4_apply( -// pts = [for (x=[0:3]) [5*x,0,0]], -// matrices =[ -// matrix4_scale([2,1,1]), -// matrix4_zrot(90), -// matrix4_translate([5,5,10]) -// ] -// ); // Returns [[5,5,10], [5,15,10], [5,25,10], [5,35,10]] - -function matrix4_apply(pts, matrices) = let(m = matrix4_mult(matrices)) [for (p = pts) point3d(m * concat(point3d(p),[1]))]; - - -// Section: Geometry - -// Function: point_on_segment() -// Usage: -// point_on_segment(point, edge); -// Description: -// Determine if the point is on the line segment between two points. -// Returns true if yes, and false if not. -// Arguments: -// point = The point to check colinearity of. -// edge = Array of two points forming the line segment to test against. -function point_on_segment(point, edge) = - point==edge[0] || point==edge[1] || // The point is an endpoint - sign(edge[0].x-point.x)==sign(point.x-edge[1].x) // point is in between the - && sign(edge[0].y-point.y)==sign(point.y-edge[1].y) // edge endpoints - && point_left_of_segment(point, edge)==0; // and on the line defined by edge - - -// Function: point_left_of_segment() -// Usage: -// point_left_of_segment(point, edge); -// Description: -// Return >0 if point is left of the line defined by edge. -// Return =0 if point is on the line. -// Return <0 if point is right of the line. -// Arguments: -// point = The point to check position of. -// edge = Array of two points forming the line segment to test against. -function point_left_of_segment(point, edge) = - (edge[1].x-edge[0].x) * (point.y-edge[0].y) - (point.x-edge[0].x) * (edge[1].y-edge[0].y); - - -// Internal non-exposed function. -function _point_above_below_segment(point, edge) = - edge[0].y <= point.y? ( - (edge[1].y > point.y && point_left_of_segment(point, edge) > 0)? 1 : 0 - ) : ( - (edge[1].y <= point.y && point_left_of_segment(point, edge) < 0)? -1 : 0 - ); - - -// Function: point_in_polygon() -// Usage: -// point_in_polygon(point, path) -// Description: -// This function tests whether the given point is inside, outside or on the boundary of -// the specified polygon using the Winding Number method. (http://geomalgorithms.com/a03-_inclusion.html) -// The polygon is given as a list of points, not including the repeated end point. -// Returns -1 if the point is outside the polyon. -// Returns 0 if the point is on the boundary. -// Returns 1 if the point lies in the interior. -// The polygon does not need to be simple: it can have self-intersections. -// But the polygon cannot have holes (it must be simply connected). -// Rounding error may give mixed results for points on or near the boundary. -// Arguments: -// point = The point to check position of. -// path = The list of 2D path points forming the perimeter of the polygon. -function point_in_polygon(point, path) = - // Does the point lie on any edges? If so return 0. - sum([for(i=[0:len(path)-1]) point_on_segment(point, select(path, i, i+1))?1:0])>0 ? 0 : - // Otherwise compute winding number and return 1 for interior, -1 for exterior - sum([for(i=[0:len(path)-1]) _point_above_below_segment(point, select(path, i, i+1))]) != 0 ? 1 : -1; - - -// Function: pointlist_bounds() -// Usage: -// pointlist_bounds(pts); -// Description: -// Finds the bounds containing all the points in pts. -// Returns [[minx, miny, minz], [maxx, maxy, maxz]] -// Arguments: -// pts = List of points. -function pointlist_bounds(pts) = [ - [for (a=[0:2]) min([ for (x=pts) point3d(x)[a] ]) ], - [for (a=[0:2]) max([ for (x=pts) point3d(x)[a] ]) ] -]; - - -// Function: triangle_area2d() -// Usage: -// triangle_area2d(a,b,c); -// Description: -// Returns the area of a triangle formed between three vertices. -// Result will be negative if the points are in clockwise order. -// Examples: -// triangle_area2d([0,0], [5,10], [10,0]); // Returns -50 -// triangle_area2d([10,0], [5,10], [0,0]); // Returns 50 -function triangle_area2d(a,b,c) = - ( - a.x * (b.y - c.y) + - b.x * (c.y - a.y) + - c.x * (a.y - b.y) - ) / 2; - - -// Function: right_of_line2d() -// Usage: -// right_of_line2d(line, pt) -// Description: -// Returns true if the given point is to the left of the given line. -// Arguments: -// line = A list of two points. -// pt = The point to test. -function right_of_line2d(line, pt) = - triangle_area2d(line[0], line[1], pt) < 0; - - -// Function: collinear() -// Usage: -// collinear(a, b, c, [eps]); -// Description: -// Returns true if three points are co-linear. -// Arguments: -// a = First point. -// b = Second point. -// c = Third point. -// eps = Acceptable max angle variance. Default: EPSILON (1e-9) degrees. -function collinear(a, b, c, eps=EPSILON) = - abs(vector_angle(b-a,c-a)) < eps; - - -// Function: collinear_indexed() -// Usage: -// collinear_indexed(points, a, b, c, [eps]); -// Description: -// Returns true if three points are co-linear. -// Arguments: -// points = A list of points. -// a = Index in `points` of first point. -// b = Index in `points` of second point. -// c = Index in `points` of third point. -// eps = Acceptable max angle variance. Default: EPSILON (1e-9) degrees. -function collinear_indexed(points, a, b, c, eps=EPSILON) = - let( - p1=points[a], - p2=points[b], - p3=points[c] - ) abs(vector_angle(p2-p1,p3-p1)) < eps; - - -// Function: plane3pt() -// Usage: -// plane3pt(p1, p2, p3); -// Description: -// Generates the cartesian equation of a plane from three non-colinear points on the plane. -// Returns [A,B,C,D] where Ax+By+Cz+D=0 is the equation of a plane. -// Arguments: -// p1 = The first point on the plane. -// p2 = The second point on the plane. -// p3 = The third point on the plane. -function plane3pt(p1, p2, p3) = - let(normal = normalize(cross(p3-p1, p2-p1))) concat(normal, [normal*p1]); - - -// Function: plane3pt_indexed() -// Usage: -// plane3pt_indexed(points, i1, i2, i3); -// Description: -// Given a list of points, and the indexes of three of those points, -// generates the cartesian equation of a plane that those points all -// lie on. Requires that the three indexed points be non-collinear. -// Returns [A,B,C,D] where Ax+By+Cz+D=0 is the equation of a plane. -// Arguments: -// points = A list of points. -// i1 = The index into `points` of the first point on the plane. -// i2 = The index into `points` of the second point on the plane. -// i3 = The index into `points` of the third point on the plane. -function plane3pt_indexed(points, i1, i2, i3) = - let( - p1 = points[i1], - p2 = points[i2], - p3 = points[i3], - normal = normalize(cross(p3-p1, p2-p1)) - ) concat(normal, [normal*p1]); - - -// Function: distance_from_plane() -// Usage: -// distance_from_plane(plane, point) -// Description: -// Given a plane as [A,B,C,D] where the cartesian equation for that plane -// is Ax+By+Cz+D=0, determines how far from that plane the given point is. -// The returned distance will be positive if the point is in front of the -// plane; on the same side of the plane as the normal of that plane points -// towards. If the point is behind the plane, then the distance returned -// will be negative. The normal of the plane is the same as [A,B,C]. -// Arguments: -// plane = The [A,B,C,D] values for the equation of the plane. -// point = The point to test. -function distance_from_plane(plane, point) = - [plane.x, plane.y, plane.z] * point - plane[3]; - - -// Function: coplanar() -// Usage: -// coplanar(plane, point); -// Description: -// Given a plane as [A,B,C,D] where the cartesian equation for that plane -// is Ax+By+Cz+D=0, determines if the given point is on that plane. -// Returns true if the point is on that plane. -// Arguments: -// plane = The [A,B,C,D] values for the equation of the plane. -// point = The point to test. -function coplanar(plane, point) = - abs(distance_from_plane(plane, point)) <= EPSILON; - - -// Function: in_front_of_plane() -// Usage: -// in_front_of_plane(plane, point); -// Description: -// Given a plane as [A,B,C,D] where the cartesian equation for that plane -// is Ax+By+Cz+D=0, determines if the given point is on the side of that -// plane that the normal points towards. The normal of the plane is the -// same as [A,B,C]. -// Arguments: -// plane = The [A,B,C,D] values for the equation of the plane. -// point = The point to test. -function in_front_of_plane(plane, point) = - distance_from_plane(plane, point) > EPSILON; - - -// Function: simplify_path() -// Description: -// Takes a path and removes unnecessary collinear points. -// Usage: -// simplify_path(path, [eps]) -// Arguments: -// path = A list of 2D path points. -// eps = Largest angle variance allowed. Default: EPSILON (1-e9) degrees. -function simplify_path(path, eps=EPSILON, _a=0, _b=2, _acc=[]) = - (_b >= len(path))? concat([path[0]], _acc, [path[len(path)-1]]) : - simplify_path( - path, eps, - (collinear_indexed(path, _a, _b-1, _b, eps=eps)? _a : _b-1), - _b+1, - (collinear_indexed(path, _a, _b-1, _b, eps=eps)? _acc : concat(_acc, [path[_b-1]])) - ); - - -// Function: simplify_path_indexed() -// Description: -// Takes a list of points, and a path as a list of indexes into `points`, -// and removes all path points that are unecessarily collinear. -// Usage: -// simplify_path_indexed(path, eps) -// Arguments: -// points = A list of points. -// path = A list of indexes into `points` that forms a path. -// eps = Largest angle variance allowed. Default: EPSILON (1-e9) degrees. -function simplify_path_indexed(points, path, eps=EPSILON, _a=0, _b=2, _acc=[]) = - (_b >= len(path))? concat([path[0]], _acc, [path[len(path)-1]]) : - simplify_path_indexed( - points, path, eps, - (collinear_indexed(points, path[_a], path[_b-1], path[_b], eps=eps)? _a : _b-1), - _b+1, - (collinear_indexed(points, path[_a], path[_b-1], path[_b], eps=eps)? _acc : concat(_acc, [path[_b-1]])) - ); - - // vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/matrices.scad b/matrices.scad new file mode 100644 index 0000000..b9f6b30 --- /dev/null +++ b/matrices.scad @@ -0,0 +1,352 @@ +////////////////////////////////////////////////////////////////////// +// LibFile: matrices.scad +// Matrix math and affine transformation matrices. +// To use, add the following lines to the beginning of your file: +// ``` +// use +// ``` +////////////////////////////////////////////////////////////////////// + +/* +BSD 2-Clause License + +Copyright (c) 2017-2019, Revar Desmera +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + + +// Section: Matrix Manipulation + +// Function: ident() +// Description: Create an `n` by `n` identity matrix. +// Arguments: +// n = The size of the identity matrix square, `n` by `n`. +function ident(n) = [for (i = [0:n-1]) [for (j = [0:n-1]) (i==j)?1:0]]; + + +// Function: matrix_transpose() +// Description: Returns the transposition of the given matrix. +// Example: +// m = [ +// [11,12,13,14], +// [21,22,23,24], +// [31,32,33,34], +// [41,42,43,44] +// ]; +// tm = matrix_transpose(m); +// // Returns: +// // [ +// // [11,21,31,41], +// // [12,22,32,42], +// // [13,23,33,43], +// // [14,24,34,44] +// // ] +function matrix_transpose(m) = [for (i=[0:len(m[0])-1]) [for (j=[0:len(m)-1]) m[j][i]]]; + + + +// Function: mat3_to_mat4() +// Description: Takes a 3x3 matrix and returns its 4x4 affine equivalent. +function mat3_to_mat4(m) = concat( + [for (r = [0:2]) + concat( + [for (c = [0:2]) m[r][c]], + [0] + ) + ], + [[0, 0, 0, 1]] +); + + + +// Section: Affine Transformation 3x3 Matrices + + +// Function: matrix3_translate() +// Description: +// Returns the 3x3 matrix to perform a 2D translation. +// Arguments: +// v = 2D Offset to translate by. [X,Y] +function matrix3_translate(v) = [ + [1, 0, v.x], + [0, 1, v.y], + [0 ,0, 1] +]; + + +// Function: matrix3_scale() +// Description: +// Returns the 3x3 matrix to perform a 2D scaling transformation. +// Arguments: +// v = 2D vector of scaling factors. [X,Y] +function matrix3_scale(v) = [ + [v.x, 0, 0], + [ 0, v.y, 0], + [ 0, 0, 1] +]; + + +// Function: matrix3_zrot() +// Description: +// Returns the 3x3 matrix to perform a rotation of a 2D vector around the Z axis. +// Arguments: +// ang = Number of degrees to rotate. +function matrix3_zrot(ang) = [ + [cos(ang), -sin(ang), 0], + [sin(ang), cos(ang), 0], + [ 0, 0, 1] +]; + + +// Function: matrix3_skew() +// Usage: +// matrix3_skew(xa, ya) +// Description: +// Returns the 3x3 matrix to skew a 2D vector along the XY plane. +// Arguments: +// xa = Skew angle, in degrees, in the direction of the X axis. +// ya = Skew angle, in degrees, in the direction of the Y axis. +function matrix3_skew(xa, ya) = [ + [1, tan(xa), 0], + [tan(ya), 1, 0], + [0, 0, 1] +]; + + +// Function: matrix3_mult() +// Usage: +// matrix3_mult(matrices) +// Description: +// Returns a 3x3 transformation matrix which results from applying each matrix in `matrices` in order. +// Arguments: +// matrices = A list of 3x3 matrices. +// m = Optional starting matrix to apply everything to. +function matrix3_mult(matrices, m=ident(3), i=0) = + (i>=len(matrices))? m : + let (newmat = is_undef(m)? matrices[i] : matrices[i] * m) + matrix3_mult(matrices, m=newmat, i=i+1); + + +// Function: matrix3_apply() +// Usage: +// matrix3_apply(pts, matrices) +// Description: +// Given a list of transformation matrices, applies them in order to the points in the point list. +// Arguments: +// pts = A list of 2D points to transform. +// matrices = A list of 3x3 matrices to apply, in order. +// Example: +// npts = matrix3_apply( +// pts = [for (x=[0:3]) [5*x,0]], +// matrices =[ +// matrix3_scale([3,1]), +// matrix3_rot(90), +// matrix3_translate([5,5]) +// ] +// ); // Returns [[5,5], [5,20], [5,35], [5,50]] +function matrix3_apply(pts, matrices) = + let(m = matrix3_mult(matrices)) + [for (p = pts) point2d(m * concat(point2d(p),[1]))]; + + + +// Section: Affine Transformation 4x4 Matrices + + +// Function: matrix4_translate() +// Description: +// Returns the 4x4 matrix to perform a 3D translation. +// Arguments: +// v = 3D offset to translate by. [X,Y,Z] +function matrix4_translate(v) = [ + [1, 0, 0, v.x], + [0, 1, 0, v.y], + [0, 0, 1, v.z], + [0 ,0, 0, 1] +]; + + +// Function: matrix4_scale() +// Description: +// Returns the 4x4 matrix to perform a 3D scaling transformation. +// Arguments: +// v = 3D vector of scaling factors. [X,Y,Z] +function matrix4_scale(v) = [ + [v.x, 0, 0, 0], + [ 0, v.y, 0, 0], + [ 0, 0, v.z, 0], + [ 0, 0, 0, 1] +]; + + +// Function: matrix4_xrot() +// Description: +// Returns the 4x4 matrix to perform a rotation of a 3D vector around the X axis. +// Arguments: +// ang = number of degrees to rotate. +function matrix4_xrot(ang) = [ + [1, 0, 0, 0], + [0, cos(ang), -sin(ang), 0], + [0, sin(ang), cos(ang), 0], + [0, 0, 0, 1] +]; + + +// Function: matrix4_yrot() +// Description: +// Returns the 4x4 matrix to perform a rotation of a 3D vector around the Y axis. +// Arguments: +// ang = Number of degrees to rotate. +function matrix4_yrot(ang) = [ + [ cos(ang), 0, sin(ang), 0], + [ 0, 1, 0, 0], + [-sin(ang), 0, cos(ang), 0], + [ 0, 0, 0, 1] +]; + + +// Function: matrix4_zrot() +// Usage: +// matrix4_zrot(ang) +// Description: +// Returns the 4x4 matrix to perform a rotation of a 3D vector around the Z axis. +// Arguments: +// ang = number of degrees to rotate. +function matrix4_zrot(ang) = [ + [cos(ang), -sin(ang), 0, 0], + [sin(ang), cos(ang), 0, 0], + [ 0, 0, 1, 0], + [ 0, 0, 0, 1] +]; + + +// Function: matrix4_rot_by_axis() +// Usage: +// matrix4_rot_by_axis(u, ang); +// Description: +// Returns the 4x4 matrix to perform a rotation of a 3D vector around an axis. +// Arguments: +// u = 3D axis vector to rotate around. +// ang = number of degrees to rotate. +function matrix4_rot_by_axis(u, ang) = let( + u = normalize(u), + c = cos(ang), + c2 = 1-c, + s = sin(ang) +) [ + [u[0]*u[0]*c2+c , u[0]*u[1]*c2-u[2]*s, u[0]*u[2]*c2+u[1]*s, 0], + [u[1]*u[0]*c2+u[2]*s, u[1]*u[1]*c2+c , u[1]*u[2]*c2-u[0]*s, 0], + [u[2]*u[0]*c2-u[1]*s, u[2]*u[1]*c2+u[0]*s, u[2]*u[2]*c2+c , 0], + [ 0, 0, 0, 1] +]; + + +// Function: matrix4_skew_xy() +// Usage: +// matrix4_skew_xy(xa, ya) +// Description: +// Returns the 4x4 matrix to perform a skew transformation along the XY plane.. +// Arguments: +// xa = Skew angle, in degrees, in the direction of the X axis. +// ya = Skew angle, in degrees, in the direction of the Y axis. +function matrix4_skew_xy(xa, ya) = [ + [1, 0, tan(xa), 0], + [0, 1, tan(ya), 0], + [0, 0, 1, 0], + [0, 0, 0, 1] +]; + + +// Function: matrix4_skew_xz() +// Usage: +// matrix4_skew_xz(xa, za) +// Description: +// Returns the 4x4 matrix to perform a skew transformation along the XZ plane. +// Arguments: +// xa = Skew angle, in degrees, in the direction of the X axis. +// za = Skew angle, in degrees, in the direction of the Z axis. +function matrix4_skew_xz(xa, za) = [ + [1, tan(xa), 0, 0], + [0, 1, 0, 0], + [0, tan(za), 1, 0], + [0, 0, 0, 1] +]; + + +// Function: matrix4_skew_yz() +// Usage: +// matrix4_skew_yz(ya, za) +// Description: +// Returns the 4x4 matrix to perform a skew transformation along the YZ plane. +// Arguments: +// ya = Skew angle, in degrees, in the direction of the Y axis. +// za = Skew angle, in degrees, in the direction of the Z axis. +function matrix4_skew_yz(ya, za) = [ + [ 1, 0, 0, 0], + [tan(ya), 1, 0, 0], + [tan(za), 0, 1, 0], + [ 0, 0, 0, 1] +]; + + +// Function: matrix4_mult() +// Usage: +// matrix4_mult(matrices) +// Description: +// Returns a 4x4 transformation matrix which results from applying each matrix in `matrices` in order. +// Arguments: +// matrices = A list of 4x4 matrices. +// m = Optional starting matrix to apply everything to. +function matrix4_mult(matrices, m=ident(4), i=0) = + (i>=len(matrices))? m : + let (newmat = is_undef(m)? matrices[i] : matrices[i] * m) + matrix4_mult(matrices, m=newmat, i=i+1); + + +// Function: matrix4_apply() +// Usage: +// matrix4_apply(pts, matrices) +// Description: +// Given a list of transformation matrices, applies them in order to the points in the point list. +// Arguments: +// pts = A list of 3D points to transform. +// matrices = A list of 4x4 matrices to apply, in order. +// Example: +// npts = matrix4_apply( +// pts = [for (x=[0:3]) [5*x,0,0]], +// matrices =[ +// matrix4_scale([2,1,1]), +// matrix4_zrot(90), +// matrix4_translate([5,5,10]) +// ] +// ); // Returns [[5,5,10], [5,15,10], [5,25,10], [5,35,10]] + +function matrix4_apply(pts, matrices) = + let(m = matrix4_mult(matrices)) + [for (p = pts) point3d(m * concat(point3d(p),[1]))]; + + + +// vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/metric_screws.scad b/metric_screws.scad index 8b80539..fe81f25 100644 --- a/metric_screws.scad +++ b/metric_screws.scad @@ -14,7 +14,7 @@ /* BSD 2-Clause License -Copyright (c) 2017, Revar Desmera +Copyright (c) 2017-2019, Revar Desmera All rights reserved. Redistribution and use in source and binary forms, with or without @@ -418,7 +418,7 @@ module screw( ["base", [0,0,-headlen/2+screwlen/2]], ["sunken", [0,0,(headlen+screwlen)/2-0.01]] ]; - orient_and_align([headsize, headsize, headlen+screwlen], orient, algn, alignments=alignments) { + orient_and_align([headsize, headsize, headlen+screwlen], orient, algn, alignments=alignments, chain=true) { down(headlen/2-screwlen/2) { down(screwlen/2) { if (pitch == undef) { @@ -429,6 +429,7 @@ module screw( } up(headlen/2) cylinder(r=headsize/2, h=headlen, center=true, $fn=sides*2); } + children(); } } @@ -530,7 +531,7 @@ module metric_bolt( ]; color("silver") - orient_and_align([D+flange, D+flange, headlen+l], orient, align, alignments=alignments) { + orient_and_align([D+flange, D+flange, headlen+l], orient, align, alignments=alignments, chain=true) { up(base) { difference() { union() { @@ -624,6 +625,7 @@ module metric_bolt( } } } + children(); } } @@ -670,7 +672,7 @@ module metric_nut( bevtop = (dcirc - D)/2; color("silver") - orient_and_align([dcirc+flange, dcirc+flange, H], orient, align, center) { + orient_and_align([dcirc+flange, dcirc+flange, H], orient, align, center, chain=true) { difference() { union() { difference() { @@ -707,6 +709,7 @@ module metric_nut( } } } + children(); } } diff --git a/nema_steppers.scad b/nema_steppers.scad index 1822a63..5e7ec65 100644 --- a/nema_steppers.scad +++ b/nema_steppers.scad @@ -11,7 +11,7 @@ /* BSD 2-Clause License -Copyright (c) 2017, Revar Desmera +Copyright (c) 2017-2019, Revar Desmera All rights reserved. Redistribution and use in source and binary forms, with or without @@ -142,23 +142,25 @@ module nema11_stepper(h=24, shaft=5, shaft_len=20, orient=ORIENT_Z, align=DOWN) screw_size = nema_motor_screw_size(size); screw_depth = nema_motor_screw_depth(size); - orient_and_align([motor_width, motor_width, h], orient, align, orig_align=DOWN) { - difference() { - color([0.4, 0.4, 0.4]) - cuboid(size=[motor_width, motor_width, h], chamfer=2, edges=EDGES_Z_ALL, align=DOWN); - color("silver") - xspread(screw_spacing) - yspread(screw_spacing) - cyl(r=screw_size/2, h=screw_depth*2, $fn=max(12,segs(screw_size/2))); - } - color([0.6, 0.6, 0.6]) { + orient_and_align([motor_width, motor_width, h], orient, align, orig_align=DOWN, chain=true) { + union() { difference() { - cylinder(h=plinth_height, d=plinth_diam); - cyl(h=plinth_height*3, d=shaft+0.75); + color([0.4, 0.4, 0.4]) + cuboid(size=[motor_width, motor_width, h], chamfer=2, edges=EDGES_Z_ALL, align=DOWN); + color("silver") + xspread(screw_spacing) + yspread(screw_spacing) + cyl(r=screw_size/2, h=screw_depth*2, $fn=max(12,segs(screw_size/2))); } + color([0.6, 0.6, 0.6]) { + difference() { + cylinder(h=plinth_height, d=plinth_diam); + cyl(h=plinth_height*3, d=shaft+0.75); + } + } + color("silver") cylinder(h=shaft_len, d=shaft, $fn=max(12,segs(shaft/2))); } - color("silver") - cylinder(h=shaft_len, d=shaft, $fn=max(12,segs(shaft/2))); + children(); } } @@ -184,23 +186,25 @@ module nema14_stepper(h=24, shaft=5, shaft_len=24, orient=ORIENT_Z, align=DOWN) screw_size = nema_motor_screw_size(size); screw_depth = nema_motor_screw_depth(size); - orient_and_align([motor_width, motor_width, h], orient, align, orig_align=DOWN) { - difference() { - color([0.4, 0.4, 0.4]) - cuboid(size=[motor_width, motor_width, h], chamfer=2, edges=EDGES_Z_ALL, align=DOWN); - color("silver") - xspread(screw_spacing) - yspread(screw_spacing) - cyl(d=screw_size, h=screw_depth*2, $fn=max(12,segs(screw_size/2))); - } - color([0.6, 0.6, 0.6]) { + orient_and_align([motor_width, motor_width, h], orient, align, orig_align=DOWN, chain=true) { + union() { difference() { - cylinder(h=plinth_height, d=plinth_diam); - cyl(h=plinth_height*3, d=shaft+0.75); + color([0.4, 0.4, 0.4]) + cuboid(size=[motor_width, motor_width, h], chamfer=2, edges=EDGES_Z_ALL, align=DOWN); + color("silver") + xspread(screw_spacing) + yspread(screw_spacing) + cyl(d=screw_size, h=screw_depth*2, $fn=max(12,segs(screw_size/2))); } + color([0.6, 0.6, 0.6]) { + difference() { + cylinder(h=plinth_height, d=plinth_diam); + cyl(h=plinth_height*3, d=shaft+0.75); + } + } + color("silver") cyl(h=shaft_len, d=shaft, align=UP, $fn=max(12,segs(shaft/2))); } - color("silver") - cyl(h=shaft_len, d=shaft, align=UP, $fn=max(12,segs(shaft/2))); + children(); } } @@ -226,41 +230,44 @@ module nema17_stepper(h=34, shaft=5, shaft_len=20, orient=ORIENT_Z, align=DOWN) screw_size = nema_motor_screw_size(size); screw_depth = nema_motor_screw_depth(size); - orient_and_align([motor_width, motor_width, h], orient, align, orig_align=DOWN) { - difference() { - color([0.4, 0.4, 0.4]) - cuboid([motor_width, motor_width, h], chamfer=2, edges=EDGES_Z_ALL, align=DOWN); - color("silver") - xspread(screw_spacing) - yspread(screw_spacing) - cyl(d=screw_size, h=screw_depth*2, $fn=max(12,segs(screw_size/2))); - } - color([0.6, 0.6, 0.6]) { + orient_and_align([motor_width, motor_width, h], orient, align, orig_align=DOWN, chain=true) { + union() { difference() { - cylinder(h=plinth_height, d=plinth_diam); - cyl(h=plinth_height*3, d=shaft+0.75); + color([0.4, 0.4, 0.4]) + cuboid([motor_width, motor_width, h], chamfer=2, edges=EDGES_Z_ALL, align=DOWN); + color("silver") + xspread(screw_spacing) + yspread(screw_spacing) + cyl(d=screw_size, h=screw_depth*2, $fn=max(12,segs(screw_size/2))); } - } - color([0.9, 0.9, 0.9]) { - down(h-motor_width/12) { - fwd(motor_width/2+motor_width/24/2-0.1) { - difference() { - cube(size=[motor_width/8, motor_width/24, motor_width/8], center=true); - cyl(d=motor_width/8-2, h=motor_width/6, orient=ORIENT_Y, $fn=12); - } - } - } - } - color("silver") { - difference() { - cylinder(h=shaft_len, d=shaft, $fn=max(12,segs(shaft/2))); - up(shaft_len/2+1) { - right(shaft-0.75) { - cube([shaft, shaft, shaft_len], center=true); + color([0.6, 0.6, 0.6]) { + difference() { + cylinder(h=plinth_height, d=plinth_diam); + cyl(h=plinth_height*3, d=shaft+0.75); + } + } + color([0.9, 0.9, 0.9]) { + down(h-motor_width/12) { + fwd(motor_width/2+motor_width/24/2-0.1) { + difference() { + cube(size=[motor_width/8, motor_width/24, motor_width/8], center=true); + cyl(d=motor_width/8-2, h=motor_width/6, orient=ORIENT_Y, $fn=12); + } + } + } + } + color("silver") { + difference() { + cylinder(h=shaft_len, d=shaft, $fn=max(12,segs(shaft/2))); + up(shaft_len/2+1) { + right(shaft-0.75) { + cube([shaft, shaft, shaft_len], center=true); + } } } } } + children(); } } @@ -287,7 +294,7 @@ module nema23_stepper(h=50, shaft=6.35, shaft_len=25, orient=ORIENT_Z, align=DOW screw_depth = nema_motor_screw_depth(size); screw_inset = motor_width - screw_spacing + 1; - orient_and_align([motor_width, motor_width, h], orient, align, orig_align=DOWN) { + orient_and_align([motor_width, motor_width, h], orient, align, orig_align=DOWN, chain=true) { difference() { union() { color([0.4, 0.4, 0.4]) @@ -306,6 +313,7 @@ module nema23_stepper(h=50, shaft=6.35, shaft_len=25, orient=ORIENT_Z, align=DOW } } } + children(); } } @@ -332,7 +340,7 @@ module nema34_stepper(h=75, shaft=12.7, shaft_len=32, orient=ORIENT_Z, align=DOW screw_depth = nema_motor_screw_depth(size); screw_inset = motor_width - screw_spacing + 1; - orient_and_align([motor_width, motor_width, h], orient, align, orig_align=DOWN) { + orient_and_align([motor_width, motor_width, h], orient, align, orig_align=DOWN, chain=true) { difference() { union() { color([0.4, 0.4, 0.4]) @@ -351,6 +359,7 @@ module nema34_stepper(h=75, shaft=12.7, shaft_len=32, orient=ORIENT_Z, align=DOW } } } + children(); } } @@ -382,7 +391,7 @@ module nema_mount_holes(size=17, depth=5, l=5, slop=PRINTER_SLOP, orient=ORIENT_ screw_spacing = nema_motor_screw_spacing(size); screw_size = nema_motor_screw_size(size)+slop; - orient_and_align([motor_width, motor_width, l], orient, align) { + orient_and_align([motor_width, motor_width, l], orient, align, chain=true) { union() { xspread(screw_spacing) { yspread(screw_spacing) { @@ -396,15 +405,16 @@ module nema_mount_holes(size=17, depth=5, l=5, slop=PRINTER_SLOP, orient=ORIENT_ } } } - } - if (l>0) { - union () { - yspread(l) cyl(h=depth, d=plinth_diam); - cube([plinth_diam, l, depth], center=true); + if (l>0) { + union () { + yspread(l) cyl(h=depth, d=plinth_diam); + cube([plinth_diam, l, depth], center=true); + } + } else { + cyl(h=depth, d=plinth_diam); } - } else { - cyl(h=depth, d=plinth_diam); } + children(); } } @@ -424,7 +434,7 @@ module nema_mount_holes(size=17, depth=5, l=5, slop=PRINTER_SLOP, orient=ORIENT_ // nema11_mount_holes(depth=5, l=0); module nema11_mount_holes(depth=5, l=5, slop=PRINTER_SLOP, orient=ORIENT_Z, align=CENTER) { - nema_mount_holes(size=11, depth=depth, l=l, slop=slop, orient=orient, align=align); + nema_mount_holes(size=11, depth=depth, l=l, slop=slop, orient=orient, align=align) children(); } @@ -443,7 +453,7 @@ module nema11_mount_holes(depth=5, l=5, slop=PRINTER_SLOP, orient=ORIENT_Z, alig // nema14_mount_holes(depth=5, l=0); module nema14_mount_holes(depth=5, l=5, slop=PRINTER_SLOP, orient=ORIENT_Z, align=CENTER) { - nema_mount_holes(size=14, depth=depth, l=l, slop=slop, orient=orient, align=align); + nema_mount_holes(size=14, depth=depth, l=l, slop=slop, orient=orient, align=align) children(); } @@ -462,7 +472,7 @@ module nema14_mount_holes(depth=5, l=5, slop=PRINTER_SLOP, orient=ORIENT_Z, alig // nema17_mount_holes(depth=5, l=0); module nema17_mount_holes(depth=5, l=5, slop=PRINTER_SLOP, orient=ORIENT_Z, align=CENTER) { - nema_mount_holes(size=17, depth=depth, l=l, slop=slop, orient=orient, align=align); + nema_mount_holes(size=17, depth=depth, l=l, slop=slop, orient=orient, align=align) children(); } @@ -481,7 +491,7 @@ module nema17_mount_holes(depth=5, l=5, slop=PRINTER_SLOP, orient=ORIENT_Z, alig // nema23_mount_holes(depth=5, l=0); module nema23_mount_holes(depth=5, l=5, slop=PRINTER_SLOP, orient=ORIENT_Z, align=CENTER) { - nema_mount_holes(size=23, depth=depth, l=l, slop=slop, orient=orient, align=align); + nema_mount_holes(size=23, depth=depth, l=l, slop=slop, orient=orient, align=align) children(); } @@ -500,7 +510,7 @@ module nema23_mount_holes(depth=5, l=5, slop=PRINTER_SLOP, orient=ORIENT_Z, alig // nema34_mount_holes(depth=5, l=0); module nema34_mount_holes(depth=5, l=5, slop=PRINTER_SLOP, orient=ORIENT_Z, align=CENTER) { - nema_mount_holes(size=34, depth=depth, l=l, slop=slop, orient=orient, align=align); + nema_mount_holes(size=34, depth=depth, l=l, slop=slop, orient=orient, align=align) children(); } @@ -519,7 +529,7 @@ module nema34_mount_holes(depth=5, l=5, slop=PRINTER_SLOP, orient=ORIENT_Z, alig // nema34_mount_holes(depth=5, l=0); module nema34_mount_holes(depth=5, l=5, slop=PRINTER_SLOP, orient=ORIENT_Z, align=CENTER) { - nema_mount_holes(size=34, depth=depth, l=l, slop=slop, orient=orient, align=align); + nema_mount_holes(size=34, depth=depth, l=l, slop=slop, orient=orient, align=align) children(); } diff --git a/paths.scad b/paths.scad index e612d06..a196ff5 100644 --- a/paths.scad +++ b/paths.scad @@ -13,7 +13,7 @@ /* BSD 2-Clause License -Copyright (c) 2017, Revar Desmera +Copyright (c) 2017-2019, Revar Desmera All rights reserved. Redistribution and use in source and binary forms, with or without @@ -233,7 +233,7 @@ module extrude_from_to(pt1, pt2, convexity=undef, twist=undef, scale=undef, slic // circle(r=40, $fn=6); module extrude_2d_hollow(wall=2, height=50, twist=90, slices=60, center=undef, orient=ORIENT_Z, align=UP) { - orient_and_align([0,0,height], orient, align, center) { + orient_and_align([0,0,height], orient, align, center, chain=true) { linear_extrude(height=height, twist=twist, slices=slices, center=true) { difference() { children(); @@ -242,6 +242,7 @@ module extrude_2d_hollow(wall=2, height=50, twist=90, slices=60, center=undef, o } } } + children(); } } @@ -300,8 +301,9 @@ module extrude_2dpath_along_spiral(polyline, h, r, twist=360, center=undef, orie ); tri_faces = triangulate_faces(poly_points, poly_faces); - orient_and_align([r,r,h], orient, align, center) { + orient_and_align([r,r,h], orient, align, center, chain=true) { polyhedron(points=poly_points, faces=tri_faces, convexity=10); + children(); } } @@ -461,7 +463,7 @@ module trace_polyline(pline, N=1, showpts=false, size=1, color="yellow") { // ); module debug_polygon(points, paths=undef, convexity=2, size=1) { - pths = (!is_def(paths))? [for (i=[0:len(points)-1]) i] : is_scalar(paths[0])? [paths] : paths; + pths = is_undef(paths)? [for (i=[0:len(points)-1]) i] : is_num(paths[0])? [paths] : paths; echo(points=points); echo(paths=paths); linear_extrude(height=0.01, convexity=convexity, center=true) { diff --git a/phillips_drive.scad b/phillips_drive.scad index c8893d5..b23e7fd 100644 --- a/phillips_drive.scad +++ b/phillips_drive.scad @@ -11,7 +11,7 @@ /* BSD 2-Clause License -Copyright (c) 2017, Revar Desmera +Copyright (c) 2017-2019, Revar Desmera All rights reserved. Redistribution and use in source and binary forms, with or without @@ -61,7 +61,7 @@ module phillips_drive(size="#2", shaft=6, l=20, orient=ORIENT_Z, align=UP) { r = radidx == []? 0 : rads[radidx][1]; h = (r/2)/tan(ang); cr = r/2; - orient_and_align([shaft, shaft, l], orient, align) { + orient_and_align([shaft, shaft, l], orient, align, chain=true) { down(l/2) { difference() { intersection() { @@ -97,6 +97,7 @@ module phillips_drive(size="#2", shaft=6, l=20, orient=ORIENT_Z, align=UP) { } } } + children(); } } diff --git a/primitives.scad b/primitives.scad index 6f02796..e7a3383 100644 --- a/primitives.scad +++ b/primitives.scad @@ -11,7 +11,7 @@ /* BSD 2-Clause License -Copyright (c) 2017, Revar Desmera +Copyright (c) 2017-2019, Revar Desmera All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/quaternions.scad b/quaternions.scad index 2f31cae..be7c779 100644 --- a/quaternions.scad +++ b/quaternions.scad @@ -11,7 +11,7 @@ /* BSD 2-Clause License -Copyright (c) 2017, Revar Desmera +Copyright (c) 2017-2019, Revar Desmera All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/shapes.scad b/shapes.scad index 94e0514..8d80c6a 100644 --- a/shapes.scad +++ b/shapes.scad @@ -10,7 +10,7 @@ /* BSD 2-Clause License -Copyright (c) 2017, Revar Desmera +Copyright (c) 2017-2019, Revar Desmera All rights reserved. Redistribution and use in source and binary forms, with or without @@ -87,8 +87,8 @@ module cuboid( center=undef ) { size = scalar_vec3(size); - if (is_def(p1)) { - if (is_def(p2)) { + if (!is_undef(p1)) { + if (!is_undef(p2)) { translate([for (v=array_zip([p1,p2],0)) min(v)]) { cuboid(size=vabs(p2-p1), chamfer=chamfer, fillet=fillet, edges=edges, trimcorners=trimcorners, align=ALLPOS) children(); } @@ -98,8 +98,8 @@ module cuboid( } } } else { - if (chamfer != undef) assertion(chamfer <= min(size)/2, "chamfer must be smaller than half the cube width, length, or height."); - if (fillet != undef) assertion(fillet <= min(size)/2, "fillet must be smaller than half the cube width, length, or height."); + if (chamfer != undef) assert(chamfer <= min(size)/2, "chamfer must be smaller than half the cube width, length, or height."); + if (fillet != undef) assert(fillet <= min(size)/2, "fillet must be smaller than half the cube width, length, or height."); majrots = [[0,90,0], [90,0,0], [0,0,0]]; orient_and_align(size, ORIENT_Z, align, center=center, noncentered=ALLPOS, chain=true) { if (chamfer != undef) { @@ -608,30 +608,30 @@ module cyl( fil1 = first_defined([fillet1, fillet]); fil2 = first_defined([fillet2, fillet]); if (chamfer != undef) { - assertion(chamfer <= r1, "chamfer is larger than the r1 radius of the cylinder."); - assertion(chamfer <= r2, "chamfer is larger than the r2 radius of the cylinder."); - assertion(chamfer <= l/2, "chamfer is larger than half the length of the cylinder."); + assert(chamfer <= r1, "chamfer is larger than the r1 radius of the cylinder."); + assert(chamfer <= r2, "chamfer is larger than the r2 radius of the cylinder."); + assert(chamfer <= l/2, "chamfer is larger than half the length of the cylinder."); } if (cham1 != undef) { - assertion(cham1 <= r1, "chamfer1 is larger than the r1 radius of the cylinder."); - assertion(cham1 <= l/2, "chamfer1 is larger than half the length of the cylinder."); + assert(cham1 <= r1, "chamfer1 is larger than the r1 radius of the cylinder."); + assert(cham1 <= l/2, "chamfer1 is larger than half the length of the cylinder."); } if (cham2 != undef) { - assertion(cham2 <= r2, "chamfer2 is larger than the r2 radius of the cylinder."); - assertion(cham2 <= l/2, "chamfer2 is larger than half the length of the cylinder."); + assert(cham2 <= r2, "chamfer2 is larger than the r2 radius of the cylinder."); + assert(cham2 <= l/2, "chamfer2 is larger than half the length of the cylinder."); } if (fillet != undef) { - assertion(fillet <= r1, "fillet is larger than the r1 radius of the cylinder."); - assertion(fillet <= r2, "fillet is larger than the r2 radius of the cylinder."); - assertion(fillet <= l/2, "fillet is larger than half the length of the cylinder."); + assert(fillet <= r1, "fillet is larger than the r1 radius of the cylinder."); + assert(fillet <= r2, "fillet is larger than the r2 radius of the cylinder."); + assert(fillet <= l/2, "fillet is larger than half the length of the cylinder."); } if (fil1 != undef) { - assertion(fil1 <= r1, "fillet1 is larger than the r1 radius of the cylinder."); - assertion(fil1 <= l/2, "fillet1 is larger than half the length of the cylinder."); + assert(fil1 <= r1, "fillet1 is larger than the r1 radius of the cylinder."); + assert(fil1 <= l/2, "fillet1 is larger than half the length of the cylinder."); } if (fil2 != undef) { - assertion(fil2 <= r2, "fillet2 is larger than the r1 radius of the cylinder."); - assertion(fil2 <= l/2, "fillet2 is larger than half the length of the cylinder."); + assert(fil2 <= r2, "fillet2 is larger than the r1 radius of the cylinder."); + assert(fil2 <= l/2, "fillet2 is larger than half the length of the cylinder."); } dy1 = first_defined([cham1, fil1, 0]); @@ -924,8 +924,8 @@ module tube( r2 = first_defined([or2, od2/2, r2, d2/2, or, od/2, r, d/2, ir2+wall, id2/2+wall, ir+wall, id/2+wall]); ir1 = first_defined([ir1, id1/2, ir, id/2, r1-wall, d1/2-wall, r-wall, d/2-wall]); ir2 = first_defined([ir2, id2/2, ir, id/2, r2-wall, d2/2-wall, r-wall, d/2-wall]); - assertion(ir1 <= r1, "Inner radius is larger than outer radius."); - assertion(ir2 <= r2, "Inner radius is larger than outer radius."); + assert(ir1 <= r1, "Inner radius is larger than outer radius."); + assert(ir2 <= r2, "Inner radius is larger than outer radius."); sides = segs(max(r1,r2)); size = [r1*2,r1*2,h]; size2 = [r2*2,r2*2,h]; @@ -1113,7 +1113,7 @@ module teardrop2d(r=1, d=undef, ang=45, cap_h=undef) cord = 2 * r * cos(ang); cord_h = r * sin(ang); tip_y = (cord/2)/tan(ang); - cap_h = min((is_def(cap_h)? cap_h : tip_y+cord_h), tip_y+cord_h); + cap_h = min((!is_undef(cap_h)? cap_h : tip_y+cord_h), tip_y+cord_h); cap_w = cord * (1 - (cap_h - cord_h)/tip_y); difference() { hull() { diff --git a/sliders.scad b/sliders.scad index 228148f..080607a 100644 --- a/sliders.scad +++ b/sliders.scad @@ -11,7 +11,7 @@ /* BSD 2-Clause License -Copyright (c) 2017, Revar Desmera +Copyright (c) 2017-2019, Revar Desmera All rights reserved. Redistribution and use in source and binary forms, with or without @@ -63,7 +63,7 @@ module slider(l=30, w=10, h=10, base=10, wall=5, ang=30, slop=PRINTER_SLOP, orie full_width = w + 2*wall; full_height = h + base; - orient_and_align([full_width, l, h+2*base], orient, align, orig_orient=ORIENT_Y) { + orient_and_align([full_width, l, h+2*base], orient, align, orig_orient=ORIENT_Y, chain=true) { down(base+h/2) { // Base cuboid([full_width, l, base-slop], chamfer=2, edges=EDGE_TOP_FR+EDGE_TOP_BK+EDGES_Z_ALL, align=UP); @@ -81,6 +81,7 @@ module slider(l=30, w=10, h=10, base=10, wall=5, ang=30, slop=PRINTER_SLOP, orie } } } + children(); } } @@ -127,7 +128,7 @@ module rail(l=30, w=10, h=10, chamfer=1.0, ang=30, orient=ORIENT_Y, align=UP) y1 = l/2; y2 = y1 - attack_len * cos(attack_ang); - orient_and_align([w, l, h], orient, align, orig_orient=ORIENT_Y) { + orient_and_align([w, l, h], orient, align, orig_orient=ORIENT_Y, chain=true) { polyhedron( convexity=4, points=[ @@ -222,6 +223,7 @@ module rail(l=30, w=10, h=10, chamfer=1.0, ang=30, orient=ORIENT_Y, align=UP) [13, 21, 6], ] ); + children(); } } diff --git a/std.scad b/std.scad index 822458b..79f22e7 100644 --- a/std.scad +++ b/std.scad @@ -10,7 +10,7 @@ /* BSD 2-Clause License -Copyright (c) 2017, Revar Desmera +Copyright (c) 2017-2019, Revar Desmera All rights reserved. Redistribution and use in source and binary forms, with or without @@ -39,10 +39,15 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. include include include +include +include +include +include +include include +include include include -include // vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/threading.scad b/threading.scad index f3e4082..b2560d2 100644 --- a/threading.scad +++ b/threading.scad @@ -11,7 +11,7 @@ /* BSD 2-Clause License -Copyright (c) 2017, Revar Desmera +Copyright (c) 2017-2019, Revar Desmera All rights reserved. Redistribution and use in source and binary forms, with or without @@ -218,12 +218,13 @@ module trapezoidal_threaded_rod( ) otri ] ); - orient_and_align([d,d,l], orient, align, center) { + orient_and_align([d,d,l], orient, align, center, chain=true) { difference() { polyhedron(points=poly_points, faces=poly_faces, convexity=threads*starts*2); zspread(l+4*pitch*starts) cube([d+1, d+1, 4*pitch*starts], center=true); if (bevel) cylinder_mask(d=d, l=l+0.01, chamfer=depth); } + children(); } } @@ -269,7 +270,7 @@ module trapezoidal_threaded_nut( align=CENTER ) { depth = min((thread_depth==undef? pitch/2 : thread_depth), pitch/2/tan(thread_angle)); - orient_and_align([od/cos(30),od,h], orient, align) { + orient_and_align([od/cos(30),od,h], orient, align, chain=true) { difference() { cylinder(d=od/cos(30), h=h, center=true, $fn=6); zspread(slop, n=slop>0?2:1) { @@ -291,6 +292,7 @@ module trapezoidal_threaded_nut( } } } + children(); } } @@ -320,7 +322,7 @@ module threaded_rod(d=10, l=100, pitch=2, left_handed=false, bevel=false, orient bevel=bevel, orient=orient, align=align - ); + ) children(); } @@ -354,7 +356,7 @@ module threaded_nut( left_handed=left_handed, bevel=bevel, slop=slop, orient=orient, align=align - ); + ) children(); } @@ -392,7 +394,7 @@ module metric_trapezoidal_threaded_rod( bevel=bevel, orient=orient, align=align - ); + ) children(); } @@ -433,7 +435,7 @@ module metric_trapezoidal_threaded_nut( slop=slop, orient=orient, align=align - ); + ) children(); } @@ -476,7 +478,7 @@ module acme_threaded_rod( bevel=bevel, orient=orient, align=align - ); + ) children(); } @@ -521,7 +523,7 @@ module acme_threaded_nut( slop=slop, orient=orient, align=align - ); + ) children(); } @@ -558,7 +560,7 @@ module square_threaded_rod( starts=starts, orient=orient, align=align - ); + ) children(); } @@ -599,7 +601,7 @@ module square_threaded_nut( slop=slop, orient=orient, align=align - ); + ) children(); } diff --git a/torx_drive.scad b/torx_drive.scad index daa3f6f..409db03 100644 --- a/torx_drive.scad +++ b/torx_drive.scad @@ -11,7 +11,7 @@ /* BSD 2-Clause License -Copyright (c) 2017, Revar Desmera +Copyright (c) 2017-2019, Revar Desmera All rights reserved. Redistribution and use in source and binary forms, with or without @@ -212,10 +212,11 @@ module torx_drive2d(size) { // torx_drive(size=30, l=10, $fa=1, $fs=1); module torx_drive(size, l=5, center=undef, orient=ORIENT_Z, align=UP) { od = torx_outer_diam(size); - orient_and_align([od, od, l], orient, align, center) { + orient_and_align([od, od, l], orient, align, center, chain=true) { linear_extrude(height=l, convexity=4, center=true) { torx_drive2d(size); } + children(); } } diff --git a/transforms.scad b/transforms.scad index ed421d5..1eeae4e 100644 --- a/transforms.scad +++ b/transforms.scad @@ -10,7 +10,7 @@ /* BSD 2-Clause License -Copyright (c) 2017, Revar Desmera +Copyright (c) 2017-2019, Revar Desmera All rights reserved. Redistribution and use in source and binary forms, with or without @@ -267,10 +267,10 @@ module up(z=0) translate([0,0,z]) children(); // rot(from=UP, to=LEFT+BACK) cube([2,4,9]); module rot(a=0, v=undef, cp=undef, from=undef, to=undef, reverse=false) { - if (is_def(cp)) { + if (!is_undef(cp)) { translate(cp) rot(a=a, v=v, from=from, to=to, reverse=reverse) translate(-cp) children(); - } else if (is_def(from)) { - assertion(is_def(to), "`from` and `to` should be used together."); + } else if (!is_undef(from)) { + assert(!is_undef(to), "`from` and `to` should be used together."); axis = vector_axis(from, to); ang = vector_angle(from, to); if (ang < 0.0001 && a == 0) { @@ -283,9 +283,9 @@ module rot(a=0, v=undef, cp=undef, from=undef, to=undef, reverse=false) } else if (a == 0) { children(); // May be slightly faster? } else if (reverse) { - if (is_def(v)) { + if (!is_undef(v)) { rotate(a=-a, v=v) children(); - } else if (is_scalar(a)) { + } else if (is_num(a)) { rotate(-a) children(); } else { rotate([-a[0],0,0]) rotate([0,-a[1],0]) rotate([0,0,-a[2]]) children(); @@ -315,7 +315,7 @@ module xrot(a=0, cp=undef) { if (a==0) { children(); // May be slightly faster? - } else if (is_def(cp)) { + } else if (!is_undef(cp)) { translate(cp) rotate([a, 0, 0]) translate(-cp) children(); } else { rotate([a, 0, 0]) children(); @@ -342,7 +342,7 @@ module yrot(a=0, cp=undef) { if (a==0) { children(); // May be slightly faster? - } else if (is_def(cp)) { + } else if (!is_undef(cp)) { translate(cp) rotate([0, a, 0]) translate(-cp) children(); } else { rotate([0, a, 0]) children(); @@ -369,7 +369,7 @@ module zrot(a=0, cp=undef) { if (a==0) { children(); // May be slightly faster? - } else if (is_def(cp)) { + } else if (!is_undef(cp)) { translate(cp) rotate(a) translate(-cp) children(); } else { rotate(a) children(); @@ -637,23 +637,23 @@ module place_copies(a=[[0,0,0]]) module spread(p1=undef, p2=undef, spacing=undef, l=undef, n=undef) { ll = ( - is_def(l)? scalar_vec3(l, 0) : - (is_def(spacing) && is_def(n))? (n * scalar_vec3(spacing, 0)) : - (is_def(p1) && is_def(p2))? point3d(p2-p1) : + !is_undef(l)? scalar_vec3(l, 0) : + (!is_undef(spacing) && !is_undef(n))? (n * scalar_vec3(spacing, 0)) : + (!is_undef(p1) && !is_undef(p2))? point3d(p2-p1) : undef ); cnt = ( - is_def(n)? n : - (is_def(spacing) && is_def(ll))? floor(norm(ll) / norm(scalar_vec3(spacing, 0)) + 1.000001) : + !is_undef(n)? n : + (!is_undef(spacing) && !is_undef(ll))? floor(norm(ll) / norm(scalar_vec3(spacing, 0)) + 1.000001) : 2 ); spc = ( - !is_def(spacing)? (ll/(cnt-1)) : - is_scalar(spacing) && is_def(ll)? (ll/(cnt-1)) : + is_undef(spacing)? (ll/(cnt-1)) : + is_num(spacing) && !is_undef(ll)? (ll/(cnt-1)) : scalar_vec3(spacing, 0) ); - assertion(is_def(cnt), "Need two of `spacing`, 'l', 'n', or `p1`/`p2` arguments in `spread()`."); - spos = is_def(p1)? point3d(p1) : -(cnt-1)/2 * spc; + assert(!is_undef(cnt), "Need two of `spacing`, 'l', 'n', or `p1`/`p2` arguments in `spread()`."); + spos = !is_undef(p1)? point3d(p1) : -(cnt-1)/2 * spc; for (i=[0 : cnt-1]) { pos = i * spc + spos; $pos = pos; @@ -800,9 +800,9 @@ module zspread(spacing=undef, n=undef, l=undef, sp=undef) module distribute(spacing=undef, sizes=undef, dir=RIGHT, l=undef) { gaps = ($children < 2)? [0] : - is_def(sizes)? [for (i=[0:$children-2]) sizes[i]/2 + sizes[i+1]/2] : + !is_undef(sizes)? [for (i=[0:$children-2]) sizes[i]/2 + sizes[i+1]/2] : [for (i=[0:$children-2]) 0]; - spc = is_def(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10); + spc = !is_undef(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10); gaps2 = [for (gap = gaps) gap+spc]; spos = dir * -sum(gaps2)/2; for (i=[0:$children-1]) { @@ -845,9 +845,9 @@ module xdistribute(spacing=10, sizes=undef, l=undef) { dir = RIGHT; gaps = ($children < 2)? [0] : - is_def(sizes)? [for (i=[0:$children-2]) sizes[i]/2 + sizes[i+1]/2] : + !is_undef(sizes)? [for (i=[0:$children-2]) sizes[i]/2 + sizes[i+1]/2] : [for (i=[0:$children-2]) 0]; - spc = is_def(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10); + spc = !is_undef(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10); gaps2 = [for (gap = gaps) gap+spc]; spos = dir * -sum(gaps2)/2; for (i=[0:$children-1]) { @@ -890,9 +890,9 @@ module ydistribute(spacing=10, sizes=undef, l=undef) { dir = BACK; gaps = ($children < 2)? [0] : - is_def(sizes)? [for (i=[0:$children-2]) sizes[i]/2 + sizes[i+1]/2] : + !is_undef(sizes)? [for (i=[0:$children-2]) sizes[i]/2 + sizes[i+1]/2] : [for (i=[0:$children-2]) 0]; - spc = is_def(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10); + spc = !is_undef(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10); gaps2 = [for (gap = gaps) gap+spc]; spos = dir * -sum(gaps2)/2; for (i=[0:$children-1]) { @@ -935,9 +935,9 @@ module zdistribute(spacing=10, sizes=undef, l=undef) { dir = UP; gaps = ($children < 2)? [0] : - is_def(sizes)? [for (i=[0:$children-2]) sizes[i]/2 + sizes[i+1]/2] : + !is_undef(sizes)? [for (i=[0:$children-2]) sizes[i]/2 + sizes[i+1]/2] : [for (i=[0:$children-2]) 0]; - spc = is_def(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10); + spc = !is_undef(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10); gaps2 = [for (gap = gaps) gap+spc]; spos = dir * -sum(gaps2)/2; for (i=[0:$children-1]) { @@ -1006,9 +1006,9 @@ module grid2d(size=undef, spacing=undef, cols=undef, rows=undef, stagger=false, { assert_in_list("stagger", stagger, [false, true, "alt"]); scl = vmul(scalar_vec3(scale, 1), (stagger!=false? [0.5, sin(60), 0] : [1,1,0])); - if (is_def(size)) { + if (!is_undef(size)) { siz = scalar_vec3(size); - if (is_def(spacing)) { + if (!is_undef(spacing)) { spc = vmul(scalar_vec3(spacing), scl); maxcols = ceil(siz[0]/spc[0]); maxrows = ceil(siz[1]/spc[1]); @@ -1018,11 +1018,11 @@ module grid2d(size=undef, spacing=undef, cols=undef, rows=undef, stagger=false, grid2d(spacing=spc, cols=cols, rows=rows, stagger=stagger, scale=scale, in_poly=in_poly, orient=orient, align=align) children(); } } else { - spc = is_array(spacing)? spacing : vmul(scalar_vec3(spacing), scl); - bounds = is_def(in_poly)? pointlist_bounds(in_poly) : undef; - bnds = is_def(bounds)? [for (a=[0:1]) 2*max(vabs([ for (i=[0,1]) bounds[i][a] ]))+1 ] : undef; - mcols = is_def(cols)? cols : (is_def(spc) && is_def(bnds))? quantup(ceil(bnds[0]/spc[0])-1, 4)+1 : undef; - mrows = is_def(rows)? rows : (is_def(spc) && is_def(bnds))? quantup(ceil(bnds[1]/spc[1])-1, 4)+1 : undef; + spc = is_list(spacing)? spacing : vmul(scalar_vec3(spacing), scl); + bounds = !is_undef(in_poly)? pointlist_bounds(in_poly) : undef; + bnds = !is_undef(bounds)? [for (a=[0:1]) 2*max(vabs([ for (i=[0,1]) bounds[i][a] ]))+1 ] : undef; + mcols = !is_undef(cols)? cols : (!is_undef(spc) && !is_undef(bnds))? quantup(ceil(bnds[0]/spc[0])-1, 4)+1 : undef; + mrows = !is_undef(rows)? rows : (!is_undef(spc) && !is_undef(bnds))? quantup(ceil(bnds[1]/spc[1])-1, 4)+1 : undef; siz = vmul(spc, [mcols-1, mrows-1, 0]); staggermod = (stagger == "alt")? 1 : 0; if (stagger == false) { @@ -1030,7 +1030,7 @@ module grid2d(size=undef, spacing=undef, cols=undef, rows=undef, stagger=false, for (row = [0:mrows-1]) { for (col = [0:mcols-1]) { pos = [col*spc[0], row*spc[1]] - point2d(siz/2); - if (!is_def(in_poly) || point_in_polygon(pos, in_poly)>=0) { + if (is_undef(in_poly) || point_in_polygon(pos, in_poly)>=0) { $col = col; $row = row; $pos = pos; @@ -1050,7 +1050,7 @@ module grid2d(size=undef, spacing=undef, cols=undef, rows=undef, stagger=false, for (col = [0:rowcols-1]) { rowdx = (row%2 != staggermod)? spc[0] : 0; pos = [2*col*spc[0]+rowdx, row*spc[1]] - point2d(siz/2); - if (!is_def(in_poly) || point_in_polygon(pos, in_poly)>=0) { + if (is_undef(in_poly) || point_in_polygon(pos, in_poly)>=0) { $col = col * 2 + ((row%2!=staggermod)? 1 : 0); $row = row; $pos = pos; @@ -1101,7 +1101,7 @@ module grid3d(xa=[0], ya=[0], za=[0], n=undef, spacing=undef) { n = scalar_vec3(n, 1); spacing = scalar_vec3(spacing, undef); - if (is_def(n) && is_def(spacing)) { + if (!is_undef(n) && !is_undef(spacing)) { for (xi = [0:n.x-1]) { for (yi = [0:n.y-1]) { for (zi = [0:n.z-1]) { @@ -1182,7 +1182,7 @@ module rot_copies(rots=[], v=undef, cp=[0,0,0], count=undef, n=undef, sa=0, offs { cnt = first_defined([count, n]); sang = sa + offset; - angs = is_def(cnt)? (cnt<=0? [] : [for (i=[0:cnt-1]) i/cnt*360+sang]) : rots; + angs = !is_undef(cnt)? (cnt<=0? [] : [for (i=[0:cnt-1]) i/cnt*360+sang]) : rots; if (cp != [0,0,0]) { translate(cp) rot_copies(rots=rots, v=v, n=cnt, sa=sang, delta=delta, subrot=subrot) children(); } else if (subrot) { @@ -1756,7 +1756,7 @@ module zflip_copy(offset=0, cp=[0,0,0]) // half_of([1,1], planar=true) circle(d=50); module half_of(v=UP, cp=[0,0,0], s=100, planar=false) { - cp = is_scalar(cp)? cp*normalize(v) : cp; + cp = is_num(cp)? cp*normalize(v) : cp; if (cp != [0,0,0]) { translate(cp) half_of(v=v, s=s, planar=planar) translate(-cp) children(); } else if (planar) { @@ -1801,7 +1801,7 @@ module half_of(v=UP, cp=[0,0,0], s=100, planar=false) module top_half(s=100, cp=[0,0,0], planar=false) { dir = planar? BACK : UP; - cp = is_scalar(cp)? cp*dir : cp; + cp = is_num(cp)? cp*dir : cp; translate(cp) difference() { translate(-cp) children(); translate(-dir*s/2) { @@ -1838,7 +1838,7 @@ module top_half(s=100, cp=[0,0,0], planar=false) module bottom_half(s=100, cp=[0,0,0], planar=false) { dir = planar? FWD : DOWN; - cp = is_scalar(cp)? cp*dir : cp; + cp = is_num(cp)? cp*dir : cp; translate(cp) difference() { translate(-cp) children(); translate(-dir*s/2) { @@ -1875,7 +1875,7 @@ module bottom_half(s=100, cp=[0,0,0], planar=false) module left_half(s=100, cp=[0,0,0], planar=false) { dir = LEFT; - cp = is_scalar(cp)? cp*dir : cp; + cp = is_num(cp)? cp*dir : cp; translate(cp) difference() { translate(-cp) children(); translate(-dir*s/2) { @@ -1912,7 +1912,7 @@ module left_half(s=100, cp=[0,0,0], planar=false) module right_half(s=100, cp=[0,0,0], planar=false) { dir = RIGHT; - cp = is_scalar(cp)? cp*dir : cp; + cp = is_num(cp)? cp*dir : cp; translate(cp) difference() { translate(-cp) children(); translate(-dir*s/2) { @@ -1949,7 +1949,7 @@ module right_half(s=100, cp=[0,0,0], planar=false) module front_half(s=100, cp=[0,0,0], planar=false) { dir = FWD; - cp = is_scalar(cp)? cp*dir : cp; + cp = is_num(cp)? cp*dir : cp; translate(cp) difference() { translate(-cp) children(); translate(-dir*s/2) { @@ -1986,7 +1986,7 @@ module front_half(s=100, cp=[0,0,0], planar=false) module back_half(s=100, cp=[0,0,0], planar=false) { dir = BACK; - cp = is_scalar(cp)? cp*dir : cp; + cp = is_num(cp)? cp*dir : cp; translate(cp) difference() { translate(-cp) children(); translate(-dir*s/2) { @@ -2156,7 +2156,7 @@ module round2d(r, or, ir) // shell2d(8,or=16,ir=8,round=16,fill=8) {square([40,100], center=true); square([100,40], center=true);} module shell2d(thickness, or=0, ir=0, fill=0, round=0) { - thickness = is_scalar(thickness)? ( + thickness = is_num(thickness)? ( thickness<0? [thickness,0] : [0,thickness] ) : (thickness[0]>thickness[1])? ( [thickness[1],thickness[0]] @@ -2224,7 +2224,7 @@ module orient_and_align( ) { size2 = point2d(default(size2, size)); shift = point2d(shift); - align = is_def(center)? (center? CENTER : noncentered) : align; + align = !is_undef(center)? (center? CENTER : noncentered) : align; m = matrix4_mult(concat( (orig_align==CENTER)? [] : [ // If original alignment is not centered, center it. @@ -2316,7 +2316,7 @@ function find_connector(align, h, size, size2=undef, shift=[0,0], extra_conns=[] shift = point3d(shift), size = point3d(point2d(size)), size2 = (size2!=undef)? point3d(point2d(size2)) : size, - found = !is_str(align)? [] : search([align], extra_conns, num_returns_per_match=1)[0] + found = !is_string(align)? [] : search([align], extra_conns, num_returns_per_match=1)[0] ) (found!=[])? extra_conns[found] : let( top = [-size2/2+shift, shift, size2/2+shift], bot = [-size/2, CENTER, size/2], @@ -2356,7 +2356,7 @@ function find_connector(align, h, size, size2=undef, shift=[0,0], extra_conns=[] // } module attach(name, to=undef, overlap=undef, norot=false) { - assertion($parent_size != undef, "No object to attach to!"); + assert($parent_size != undef, "No object to attach to!"); overlap = (overlap!=undef)? overlap : $overlap; conn = find_connector(name, $parent_size.z, point2d($parent_size), size2=$parent_size2, shift=$parent_shift, extra_conns=$parent_conns); pos = conn[1]; diff --git a/triangulation.scad b/triangulation.scad index f824852..9418712 100644 --- a/triangulation.scad +++ b/triangulation.scad @@ -11,7 +11,7 @@ /* BSD 2-Clause License -Copyright (c) 2017, Revar Desmera +Copyright (c) 2017-2019, Revar Desmera All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/vectors.scad b/vectors.scad new file mode 100644 index 0000000..39b1625 --- /dev/null +++ b/vectors.scad @@ -0,0 +1,112 @@ +////////////////////////////////////////////////////////////////////// +// LibFile: vectors.scad +// Vector math functions. +// To use, add the following lines to the beginning of your file: +// ``` +// use +// ``` +////////////////////////////////////////////////////////////////////// + +/* +BSD 2-Clause License + +Copyright (c) 2017-2019, Revar Desmera +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + + +// Section: Vector Manipulation + +// Function: vmul() +// Description: +// Element-wise vector multiplication. Multiplies each element of vector `v1` by +// the corresponding element of vector `v2`. Returns a vector of the products. +// Arguments: +// v1 = The first vector. +// v2 = The second vector. +// Example: +// vmul([3,4,5], [8,7,6]); // Returns [24, 28, 30] +function vmul(v1, v2) = [for (i = [0:len(v1)-1]) v1[i]*v2[i]]; + + +// Function: vdiv() +// Description: +// Element-wise vector division. Divides each element of vector `v1` by +// the corresponding element of vector `v2`. Returns a vector of the quotients. +// Arguments: +// v1 = The first vector. +// v2 = The second vector. +// Example: +// vdiv([24,28,30], [8,7,6]); // Returns [3, 4, 5] +function vdiv(v1, v2) = [for (i = [0:len(v1)-1]) v1[i]/v2[i]]; + + +// Function: vabs() +// Description: Returns a vector of the absolute value of each element of vector `v`. +// Arguments: +// v = The vector to get the absolute values of. +function vabs(v) = [for (x=v) abs(x)]; + + +// Function: normalize() +// Description: +// Returns unit length normalized version of vector v. +// Arguments: +// v = The vector to normalize. +function normalize(v) = v/norm(v); + + +// Function: vector_angle() +// Usage: +// vector_angle(v1,v2); +// Description: +// Returns angle in degrees between two vectors of similar dimensions. +// Arguments: +// v1 = First vector. +// v2 = Second vector. +// NOTE: constrain() corrects crazy FP rounding errors that exceed acos()'s domain. +function vector_angle(v1,v2) = acos(constrain((v1*v2)/(norm(v1)*norm(v2)), -1, 1)); + + +// Function: vector_axis() +// Usage: +// vector_xis(v1,v2); +// Description: +// Returns the vector perpendicular to both of the given vectors. +// Arguments: +// v1 = First vector. +// v2 = Second vector. +function vector_axis(v1,v2) = + let( + eps = 1e-6, + v1 = point3d(v1/norm(v1)), + v2 = point3d(v2/norm(v2)), + v3 = (norm(v1-v2) > eps && norm(v1+v2) > eps)? v2 : + (norm(vabs(v2)-UP) > eps)? UP : + RIGHT + ) normalize(cross(v1,v3)); + + +// vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/wiring.scad b/wiring.scad index 3519d66..977db1b 100644 --- a/wiring.scad +++ b/wiring.scad @@ -13,7 +13,7 @@ /* BSD 2-Clause License -Copyright (c) 2017, Revar Desmera +Copyright (c) 2017-2019, Revar Desmera All rights reserved. Redistribution and use in source and binary forms, with or without