Updated copyright years. Split math.scad up. Enabled attach for lots of shapes. Removed backwards compatability.

This commit is contained in:
Revar Desmera 2019-04-19 17:02:17 -07:00
parent 019aae4347
commit cc36235736
29 changed files with 1995 additions and 1743 deletions

View file

@ -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

486
arrays.scad Normal file
View file

@ -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 <BOSL2/std.scad>
// ```
//////////////////////////////////////////////////////////////////////
/*
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 end<start.
// The first item is index 0. Negative indexes are counted back from the end.
// The last item is -1. If only the `start` index is given, returns just the value
// at that position.
// Usage:
// select(list,start)
// select(list,start,end)
// Arguments:
// list = The list to get the portion of.
// start = The index of the first item.
// end = The index of the last item.
// Example:
// l = [3,4,5,6,7,8,9];
// select(l, 5, 6); // Returns [8,9]
// select(l, 5, 8); // Returns [8,9,3,4]
// select(l, 5, 2); // Returns [8,9,3,4,5]
// select(l, -3, -1); // Returns [7,8,9]
// select(l, 3, 3); // Returns [6]
// select(l, 4); // Returns 7
// select(l, -2); // Returns 8
// select(l, [1:3]); // Returns [4,5,6]
// select(l, [1,3]); // Returns [4,6]
function select(list, start, end=undef) =
let(l=len(list))
(list==[])? [] :
end==undef? (
is_num(start)?
let(s=(start%l+l)%l) list[s] :
[for (i=start) list[(i%l+l)%l]]
) : (
let(s=(start%l+l)%l, e=(end%l+l)%l)
(s<=e)?
[for (i = [s:e]) list[i]] :
concat([for (i = [s:l-1]) list[i]], [for (i = [0:e]) list[i]])
);
// Function: list_range()
// Usage:
// list_range(n, [s], [e], [step])
// list_range(e, [step])
// list_range(s, e, [step])
// Description:
// Returns a list, counting up from starting value `s`, by `step` increments,
// until either `n` values are in the list, or it reaches the end value `e`.
// Arguments:
// n = Desired number of values in returned list, if given.
// s = Starting value. Default: 0
// e = Ending value to stop at, if given.
// step = Amount to increment each value. Default: 1
// Example:
// list_range(4); // Returns [0,1,2,3]
// list_range(n=4, step=2); // Returns [0,2,4,6]
// list_range(n=4, s=3, step=3); // Returns [3,6,9,12]
// list_range(n=4, s=3, e=9, step=3); // Returns [3,6,9]
// list_range(e=3); // Returns [0,1,2,3]
// list_range(e=6, step=2); // Returns [0,2,4,6]
// list_range(s=3, e=5); // Returns [3,4,5]
// list_range(s=3, e=8, step=2); // Returns [3,5,7]
// list_range(s=4, e=8, step=2); // Returns [4,6,8]
// list_range(n=4, s=[3,4], step=[2,3]); // Returns [[3,4], [5,7], [7,10], [9,13]]
function list_range(n=undef, s=0, e=undef, step=1) =
(n!=undef && e!=undef)? [for (i=[0:n-1]) let(v=s+step*i) if (v<=e) v] :
(n!=undef)? [for (i=[0:n-1]) let(v=s+step*i) v] :
(e!=undef)? [for (v=[s:step:e]) v] :
assert(e!=undef||n!=undef, "Must supply one of `n` or `e`.");
// Function: reverse()
// Description: Reverses a list/array.
// Arguments:
// list = The list to reverse.
// Example:
// reverse([3,4,5,6]); // Returns [6,5,4,3]
function reverse(list) = [ for (i = [len(list)-1 : -1 : 0]) list[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]
];
// Function: list_insert()
// Usage:
// list_insert(list, pos, elements);
// Description:
// Insert `elements` into `list` before position `pos`.
function list_insert(list, pos, elements) =
concat(
slice(list,0,pos),
elements,
(pos<len(list)? slide(list,pos,-1) : [])
);
// Function: list_shortest()
// Description:
// Returns the length of the shortest sublist in a list of lists.
// Arguments:
// vecs = A list of lists.
function list_shortest(vecs) =
min([for (v = vecs) len(v)]);
// Function: list_longest()
// Description:
// Returns the length of the longest sublist in a list of lists.
// Arguments:
// vecs = A list of lists.
function list_longest(vecs) =
max([for (v = vecs) len(v)]);
// Function: list_pad()
// Description:
// If the list `v` is shorter than `minlen` length, pad it to length with the value given in `fill`.
// Arguments:
// v = A list.
// minlen = The minimum length to pad the list to.
// fill = The value to pad the list with.
function list_pad(v, minlen, fill=undef) =
let(l=len(v)) [for (i=[0:max(l,minlen)-1]) i<l? v[i] : fill];
// Function: list_trim()
// Description:
// If the list `v` is longer than `maxlen` length, truncates it to be `maxlen` items long.
// Arguments:
// v = A list.
// minlen = The minimum length to pad the list to.
function list_trim(v, maxlen) =
maxlen<1? [] : [for (i=[0:min(len(v),maxlen)-1]) v[i]];
// Function: list_fit()
// Description:
// If the list `v` is longer than `length` items long, truncates it to be exactly `length` items long.
// If the list `v` is shorter than `length` items long, pad it to length with the value given in `fill`.
// Arguments:
// v = A list.
// minlen = The minimum length to pad the list to.
// fill = The value to pad the list with.
function list_fit(v, length, fill) =
let(l=len(v)) (l==length)? v : (l>length)? 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(v)? v[i] : (fill==undef)? [fill] : fill)) x] ] :
[for(i=[0:minlen-1]) [for(v=vecs) for(x=v[i]) x] ];
// Function: array_group()
// Description:
// Takes a flat array of values, and groups items in sets of `cnt` length.
// The opposite of this is `flatten()`.
// Arguments:
// v = The list of items to group.
// cnt = The number of items to put in each grouping.
// dflt = The default value to fill in with is the list is not a multiple of `cnt` items long.
// Example:
// v = [1,2,3,4,5,6];
// array_group(v,2) returns [[1,2], [3,4], [5,6]]
// array_group(v,3) returns [[1,2,3], [4,5,6]]
// array_group(v,4,0) returns [[1,2,3,4], [5,6,0,0]]
function array_group(v, cnt=2, dflt=0) = [for (i = [0:cnt:len(v)-1]) [for (j = [0:cnt-1]) default(v[i+j], dflt)]];
// Function: flatten()
// Description: Takes a list of lists and flattens it by one level.
// Arguments:
// l = List to flatten.
// Example:
// flatten([[1,2,3], [4,5,[6,7,8]]]) returns [1,2,3,4,5,[6,7,8]]
function flatten(l) = [for (a = l) for (b = a) b];
// 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]
);
// vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View file

@ -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

View file

@ -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("<p style=\"background-color: #ffb0b0\"><b>", pfx, ":</b> ", msg, "</p>"));
echo(str("<p style=\"background-color: #ffb0b0\"><b>", pfx, ":</b> ", msg, "</p>"));
// Module: echo_warning()
@ -237,7 +199,7 @@ module echo_warning(msg, pfx="WARNING") {
}
function echo_warning(msg, pfx="WARNING") =
f_echo(str("<p style=\"background-color: #ffffb0\"><b>", pfx, ":</b> ", msg, "</p>"));
echo(str("<p style=\"background-color: #ffffb0\"><b>", pfx, ":</b> ", msg, "</p>"));
// Module: deprecate()
@ -253,7 +215,7 @@ module deprecate(name, suggest=undef) {
echo_warning(pfx="DEPRECATED",
str(
"`<code>", name, "</code>` is deprecated and should not be used.",
!is_def(suggest)? "" : str(
is_undef(suggest)? "" : str(
" You should use `<code>", suggest, "</code>` instead."
)
)
@ -264,7 +226,7 @@ function deprecate(name, suggest=undef) =
echo_warning(pfx="DEPRECATED",
str(
"`<code>", name, "</code>` is deprecated and should not be used.",
!is_def(suggest)? "" : str(
is_undef(suggest)? "" : str(
" You should use `<code>", suggest, "</code>` instead."
)
)
@ -286,7 +248,7 @@ module deprecate_argument(name, arg, suggest=undef) {
"In `<code>", name, "</code>`, ",
"the argument `<code>", arg, "</code>` ",
"is deprecated and should not be used.",
!is_def(suggest)? "" : str(
is_undef(suggest)? "" : str(
" You should use `<code>", suggest, "</code>` instead."
)
));
@ -297,7 +259,7 @@ function deprecate_argument(name, arg, suggest=undef) =
"In `<code>", name, "</code>`, ",
"the argument `<code>", arg, "</code>` ",
"is deprecated and should not be used.",
!is_def(suggest)? "" : str(
is_undef(suggest)? "" : str(
" You should use `<code>", suggest, "</code>` instead."
)
));

View file

@ -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

368
coords.scad Normal file
View file

@ -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 <BOSL2/std.scad>
// ```
//////////////////////////////////////////////////////////////////////
/*
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

View file

@ -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

306
geometry.scad Normal file
View file

@ -0,0 +1,306 @@
//////////////////////////////////////////////////////////////////////
// LibFile: geometry.scad
// Geometry helpers.
// To use, add the following lines to the beginning of your file:
// ```
// use <BOSL2/std.scad>
// ```
//////////////////////////////////////////////////////////////////////
/*
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

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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();
}

View file

@ -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();
}
}

1393
math.scad

File diff suppressed because it is too large Load diff

352
matrices.scad Normal file
View file

@ -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 <BOSL2/std.scad>
// ```
//////////////////////////////////////////////////////////////////////
/*
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

View file

@ -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();
}
}

View file

@ -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();
}

View file

@ -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) {

View file

@ -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();
}
}

View file

@ -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

View file

@ -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

View file

@ -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() {

View file

@ -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();
}
}

View file

@ -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 <BOSL2/constants.scad>
include <BOSL2/compat.scad>
include <BOSL2/math.scad>
include <BOSL2/arrays.scad>
include <BOSL2/vector.scad>
include <BOSL2/matrices.scad>
include <BOSL2/coords.scad>
include <BOSL2/geometry.scad>
include <BOSL2/transforms.scad>
include <BOSL2/primitives.scad>
include <BOSL2/shapes.scad>
include <BOSL2/masks.scad>
include <BOSL2/primitives.scad>
// vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View file

@ -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();
}

View file

@ -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();
}
}

View file

@ -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];

View file

@ -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

112
vectors.scad Normal file
View file

@ -0,0 +1,112 @@
//////////////////////////////////////////////////////////////////////
// LibFile: vectors.scad
// Vector math functions.
// To use, add the following lines to the beginning of your file:
// ```
// use <BOSL2/std.scad>
// ```
//////////////////////////////////////////////////////////////////////
/*
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

View file

@ -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