mirror of
https://github.com/BelfrySCAD/BOSL2.git
synced 2024-12-29 16:29:40 +00:00
Merge branch 'master' into pr/483
This commit is contained in:
commit
bda2661855
33 changed files with 583 additions and 334 deletions
32
.github/openscad_docsgen.json
vendored
Normal file
32
.github/openscad_docsgen.json
vendored
Normal file
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"problemMatcher": [
|
||||
{
|
||||
"owner": "docsgen_script",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^!! (WARNING|ERROR) at ([^:]+):(\\d+): Failed OpenSCAD script:$",
|
||||
"severity": 1,
|
||||
"file": 2,
|
||||
"line": 3
|
||||
},
|
||||
{
|
||||
"regexp": "^ (.*)$",
|
||||
"message": 1,
|
||||
"loop": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"owner": "docsgen",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^!! (WARNING|ERROR) at ([^:]+):(\\d+): (.*[^:])$",
|
||||
"severity": 1,
|
||||
"file": 2,
|
||||
"line": 3,
|
||||
"message": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
1
.github/workflows/docsgen.yml
vendored
1
.github/workflows/docsgen.yml
vendored
|
@ -36,6 +36,7 @@ jobs:
|
|||
run: |
|
||||
cd $GITHUB_WORKSPACE
|
||||
export OPENSCADPATH=$(dirname $GITHUB_WORKSPACE)
|
||||
echo "::add-matcher::.github/openscad_docsgen.json"
|
||||
openscad-docsgen -m -i -t -c *.scad
|
||||
cd BOSL2.wiki
|
||||
git config user.name github-actions
|
||||
|
|
1
.github/workflows/main.yml
vendored
1
.github/workflows/main.yml
vendored
|
@ -56,6 +56,7 @@ jobs:
|
|||
- name: Generating Docs
|
||||
run: |
|
||||
cd $GITHUB_WORKSPACE
|
||||
echo "::add-matcher::.github/openscad_docsgen.json"
|
||||
export OPENSCADPATH=$(dirname $GITHUB_WORKSPACE)
|
||||
openscad-docsgen -Tm *.scad
|
||||
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -109,4 +109,5 @@ ENV/
|
|||
foo.scad
|
||||
BOSL2.wiki
|
||||
/ref/
|
||||
docsgen_report.json
|
||||
|
||||
|
|
166
arrays.scad
166
arrays.scad
|
@ -53,13 +53,12 @@ function _same_type(a,b, depth) =
|
|||
|
||||
|
||||
// Function: select()
|
||||
// Topics: List Handling
|
||||
// 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.
|
||||
// Topics: List Handling
|
||||
// See Also: slice(), subindex(), last()
|
||||
// Usage:
|
||||
// item = select(list,start);
|
||||
// list = select(list,start,end);
|
||||
|
@ -67,6 +66,7 @@ function _same_type(a,b, depth) =
|
|||
// list = The list to get the portion of.
|
||||
// start = The index of the first item.
|
||||
// end = The index of the last item.
|
||||
// See Also: slice(), subindex(), last()
|
||||
// Example:
|
||||
// l = [3,4,5,6,7,8,9];
|
||||
// a = select(l, 5, 6); // Returns [8,9]
|
||||
|
@ -97,33 +97,36 @@ function select(list, start, end) =
|
|||
|
||||
|
||||
// Function: slice()
|
||||
// Usage:
|
||||
// list = slice(list,start,end);
|
||||
// Topics: List Handling
|
||||
// See Also: select(), subindex(), last()
|
||||
// Usage:
|
||||
// list = slice(list,s,e);
|
||||
// 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.
|
||||
// Returns a slice of a list, from the first position `s` up to and including the last position `e`.
|
||||
// The first item in the list is at index 0. Negative indexes are counted back from the end.
|
||||
// An index of -1 refers to the last list item.
|
||||
// Arguments:
|
||||
// list = The array/list to get the slice of.
|
||||
// start = 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.
|
||||
// list = The list to get the slice of.
|
||||
// s = The index of the first item to return.
|
||||
// e = The index of the last item to return.
|
||||
// See Also: select(), subindex(), last()
|
||||
// Example:
|
||||
// a = slice([3,4,5,6,7,8,9], 3, 5); // Returns [6,7]
|
||||
// a = slice([3,4,5,6,7,8,9], 3, 5); // Returns [6,7,8]
|
||||
// b = slice([3,4,5,6,7,8,9], 2, -1); // Returns [5,6,7,8,9]
|
||||
// c = slice([3,4,5,6,7,8,9], 1, 1); // Returns []
|
||||
// d = slice([3,4,5,6,7,8,9], 6, -1); // Returns [9]
|
||||
// c = slice([3,4,5,6,7,8,9], 1, 1); // Returns [4]
|
||||
// d = slice([3,4,5,6,7,8,9], 5); // Returns [8,9]
|
||||
// e = slice([3,4,5,6,7,8,9], 2, -2); // Returns [5,6,7,8]
|
||||
function slice(list,start,end) =
|
||||
assert( is_list(list), "Invalid list" )
|
||||
assert( is_finite(start) && is_finite(end), "Invalid number(s)" )
|
||||
let( l = len(list) )
|
||||
l==0
|
||||
? []
|
||||
: let(
|
||||
s = start<0? (l+start) : start,
|
||||
e = end<0? (l+end+1) : end
|
||||
) [for (i=[s:1:e-1]) if (e>s) list[i]];
|
||||
// f = slice([3,4,5,6,7,8,9], 4, 3; // Returns []
|
||||
function slice(list,s=0,e=-1) =
|
||||
assert(is_list(list))
|
||||
assert(is_int(s))
|
||||
assert(is_int(e))
|
||||
!list? [] :
|
||||
let(
|
||||
l = len(list),
|
||||
s = constrain(s + (s<0? l : 0), 0, l-1),
|
||||
e = constrain(e + (e<0? l : 0), 0, l-1)
|
||||
)
|
||||
[if (e>=s) for (i=[s:1:e]) list[i]];
|
||||
|
||||
|
||||
// Function: last()
|
||||
|
@ -156,6 +159,24 @@ function delete_last(list) =
|
|||
list==[] ? [] : slice(list,0,-2);
|
||||
|
||||
|
||||
// Function: list()
|
||||
// Topics: List Handling, Type Conversion
|
||||
// Usage:
|
||||
// list = list(l)
|
||||
// Description:
|
||||
// Expands a range into a full list. If given a list, returns it verbatim.
|
||||
// If given a string, explodes it into a list of single letters.
|
||||
// Arguments:
|
||||
// l = The value to expand.
|
||||
// See Also: scalar_vec3(), force_list(), range(), rangex()
|
||||
// Example:
|
||||
// l1 = list([3:2:9]); // Returns: [3,5,7,9]
|
||||
// l2 = list([3,4,5]); // Returns: [3,4,5]
|
||||
// l3 = list("Foo"); // Returns: ["F","o","o"]
|
||||
// l4 = list(23); // Returns: [23]
|
||||
function list(l) = is_list(l)? l : [for (x=l) x];
|
||||
|
||||
|
||||
// Function: force_list()
|
||||
// Usage:
|
||||
// list = force_list(value, <n>, <fill>);
|
||||
|
@ -230,20 +251,28 @@ function in_list(val,list,idx) =
|
|||
// Description:
|
||||
// Finds the first item in `list` that matches `val`, returning the index.
|
||||
// Arguments:
|
||||
// val = The value to search for.
|
||||
// val = The value to search for. If given a function literal of signature `function (x)`, uses that function to check list items. Returns true for a match.
|
||||
// list = The list to search through.
|
||||
// ---
|
||||
// start = The index to start searching from.
|
||||
// all = If true, returns a list of all matching item indices.
|
||||
// eps = The maximum allowed floating point rounding error for numeric comparisons.
|
||||
function find_first_match(val, list, start=0, all=false, eps=EPSILON) =
|
||||
all? [for (i=[start:1:len(list)-1]) if(val==list[i] || approx(val, list[i], eps=eps)) i] :
|
||||
all? [
|
||||
for (i=[start:1:len(list)-1])
|
||||
if (
|
||||
(!is_func(val) && approx(val, list[i], eps=eps)) ||
|
||||
(is_func(val) && val(list[i]))
|
||||
) i
|
||||
] :
|
||||
__find_first_match(val, list, eps=eps, i=start);
|
||||
|
||||
function __find_first_match(val, list, eps, i=0) =
|
||||
i >= len(list)? undef :
|
||||
approx(val, list[i], eps=eps)? i :
|
||||
__find_first_match(val, list, eps=eps, i=i+1);
|
||||
(
|
||||
(!is_func(val) && approx(val, list[i], eps=eps)) ||
|
||||
(is_func(val) && val(list[i]))
|
||||
)? i : __find_first_match(val, list, eps=eps, i=i+1);
|
||||
|
||||
|
||||
// Function: min_index()
|
||||
|
@ -326,7 +355,7 @@ function list_decreasing(list) =
|
|||
// Usage:
|
||||
// list = repeat(val, n);
|
||||
// Topics: List Handling
|
||||
// See Also: list_range()
|
||||
// See Also: range(), rangex()
|
||||
// Description:
|
||||
// Generates a list or array of `n` copies of the given value `val`.
|
||||
// If the count `n` is given as a list of counts, then this creates a
|
||||
|
@ -346,47 +375,90 @@ function repeat(val, n, i=0) =
|
|||
[for (j=[1:1:n[i]]) repeat(val, n, i+1)];
|
||||
|
||||
|
||||
// Function: list_range()
|
||||
// Function: range()
|
||||
// Usage:
|
||||
// list = list_range(n=, <s=>, <e=>);
|
||||
// list = list_range(n=, <s=>, <step=>);
|
||||
// list = list_range(e=, <step=>);
|
||||
// list = list_range(s=, e=, <step=>);
|
||||
// list = range(n, <s=>, <e=>);
|
||||
// list = range(n, <s=>, <step=>);
|
||||
// list = range(e=, <step=>);
|
||||
// list = range(s=, e=, <step=>);
|
||||
// Topics: List Handling
|
||||
// See Also: repeat()
|
||||
// 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`.
|
||||
// If both `n` and `e` are given, returns `n` values evenly spread from `s`
|
||||
// to `e`, and `step` is ignored.
|
||||
// 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
|
||||
// See Also: repeat(), rangex()
|
||||
// Example:
|
||||
// a = list_range(4); // Returns [0,1,2,3]
|
||||
// b = list_range(n=4, step=2); // Returns [0,2,4,6]
|
||||
// c = list_range(n=4, s=3, step=3); // Returns [3,6,9,12]
|
||||
// d = list_range(n=5, s=0, e=10); // Returns [0, 2.5, 5, 7.5, 10]
|
||||
// e = list_range(e=3); // Returns [0,1,2,3]
|
||||
// f = list_range(e=7, step=2); // Returns [0,2,4,6]
|
||||
// g = list_range(s=3, e=5); // Returns [3,4,5]
|
||||
// h = list_range(s=3, e=8, step=2); // Returns [3,5,7]
|
||||
// i = list_range(s=4, e=8.3, step=2); // Returns [4,6,8]
|
||||
// j = list_range(n=4, s=[3,4], step=[2,3]); // Returns [[3,4], [5,7], [7,10], [9,13]]
|
||||
function list_range(n, s=0, e, step) =
|
||||
// a = range(4); // Returns [0,1,2,3]
|
||||
// b = range(n=4, step=2); // Returns [0,2,4,6]
|
||||
// c = range(n=4, s=3, step=3); // Returns [3,6,9,12]
|
||||
// d = range(n=5, s=0, e=10); // Returns [0, 2.5, 5, 7.5, 10]
|
||||
// e = range(e=3); // Returns [0,1,2,3]
|
||||
// f = range(e=7, step=2); // Returns [0,2,4,6]
|
||||
// g = range(s=3, e=5); // Returns [3,4,5]
|
||||
// h = range(s=3, e=8, step=2); // Returns [3,5,7]
|
||||
// i = range(s=4, e=8.3, step=2); // Returns [4,6,8]
|
||||
function range(n, s=0, e, step) =
|
||||
assert( is_undef(n) || is_finite(n), "Parameter `n` must be a number.")
|
||||
assert( is_undef(n) || is_undef(e) || is_undef(step), "At most 2 of n, e, and step can be given.")
|
||||
let( step = (n!=undef && e!=undef)? (e-s)/(n-1) : default(step,1) )
|
||||
is_undef(e)
|
||||
? assert( is_consistent([s, step]), "Incompatible data.")
|
||||
[for (i=[0:1:n-1]) s+step*i ]
|
||||
[for (i=[0:1:n-1]) s+step*i]
|
||||
: assert( is_vector([s,step,e]), "Start `s`, step `step` and end `e` must be numbers.")
|
||||
[for (v=[s:step:e]) v] ;
|
||||
|
||||
|
||||
// Function: rangex()
|
||||
// Usage:
|
||||
// list = rangex(n, <s=>, <e=>);
|
||||
// list = rangex(n, <s=>, <step=>);
|
||||
// list = rangex(e=, <step=>);
|
||||
// list = rangex(s=, e=, <step=>);
|
||||
// Topics: List Handling
|
||||
// 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 value just before the end value `e`.
|
||||
// If both `n` and `e` are given, returns `n` values evenly spread from `s` to the value
|
||||
// just before `e`, and `step` is ignored.
|
||||
// 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
|
||||
// See Also: repeat(), range()
|
||||
// Example:
|
||||
// a = rangex(4); // Returns [0,1,2,3]
|
||||
// b = rangex(5,e=1); // Returns [0, 0.2, 0.4, 0.6, 0.8]
|
||||
// c = rangex(n=4, step=2); // Returns [0,2,4,6]
|
||||
// d = rangex(n=4, step=0.25); // Returns [0, 0.25, 0.5, 0.75]
|
||||
// e = rangex(n=4, s=3, step=3); // Returns [3,6,9,12]
|
||||
// f = rangex(n=5, s=0, e=10); // Returns [0, 2, 4, 6, 8]
|
||||
// g = rangex(e=3); // Returns [0,1,2]
|
||||
// h = rangex(e=7, step=2); // Returns [0,2,4,6]
|
||||
// i = rangex(s=3, e=5); // Returns [3,4]
|
||||
// j = rangex(s=3, e=8, step=2); // Returns [3,5,7]
|
||||
// k = rangex(s=2, e=8, step=2); // Returns [2,4,6]
|
||||
// l = rangex(s=2, e=8.1, step=2); // Returns [2,4,6,8]
|
||||
function rangex(n, s=0, e, step) =
|
||||
assert( is_undef(n) || is_finite(n), "Parameter `n` must be a number.")
|
||||
assert( is_undef(n) || is_undef(e) || is_undef(step), "At most 2 of n, e, and step can be given.")
|
||||
let( step = (n!=undef && e!=undef)? (e-s)/n : default(step,1) )
|
||||
is_undef(e)
|
||||
? assert( is_consistent([s, step]), "Incompatible data.")
|
||||
[for (i=[0:1:n-1]) s+step*i]
|
||||
: assert( is_vector([s,step,e]), "Start `s`, step `step` and end `e` must be numbers.")
|
||||
let(steps=floor((e-s)/step+0.5))
|
||||
[for (i=[0:1:steps-1]) s+step*i];
|
||||
|
||||
|
||||
|
||||
// Section: List Manipulation
|
||||
|
||||
|
|
|
@ -442,7 +442,7 @@ function find_anchor(anchor, geom) =
|
|||
let(
|
||||
cp = select(geom,-3),
|
||||
offset = anchor==CENTER? CENTER : select(geom,-2),
|
||||
anchors = select(geom,-1),
|
||||
anchors = last(geom),
|
||||
type = geom[0]
|
||||
)
|
||||
is_string(anchor)? (
|
||||
|
|
25
beziers.scad
25
beziers.scad
|
@ -413,16 +413,17 @@ function bezier_curvature(curve, u) =
|
|||
// Topics: Bezier Segments
|
||||
// See Also: bezier_curvature(), bezier_tangent(), bezier_derivative(), bezier_points()
|
||||
// Description:
|
||||
// Takes a list of bezier curve control points and generates n points along the bezier path.
|
||||
// Points start at the first control point and are sampled every `1/n`th
|
||||
// of the way along the bezier parameter, ending *before* the final control point by default.
|
||||
// The distance between the points will *not* be equidistant. If you wish to add the
|
||||
// endpoint you can set `endpoint` to true. The degree of the bezier curve is one
|
||||
// less than the number of points in `curve`.
|
||||
// Takes a list of bezier control points and generates n points along the bezier curve they define.
|
||||
// Points start at the first control point and are sampled uniformly along the bezier parameter.
|
||||
// The endpoints of the output will be *exactly* equal to the first and last bezier control points
|
||||
// when endpoint is true. If endpoint is false the sampling stops one step before the final point
|
||||
// of the bezier curve, but you still get n, more tightly spaced, points.
|
||||
// The distance between the points will *not* be equidistant.
|
||||
// The degree of the bezier curve is one less than the number of points in `curve`.
|
||||
// Arguments:
|
||||
// curve = The list of endpoints and control points for this bezier segment.
|
||||
// n = The number of points to generate along the bezier curve.
|
||||
// endpoint = if true then add the endpoint (an extra point, giving n+1 points output). Default: False
|
||||
// endpoint = if false then exclude the endpoint. Default: True
|
||||
// Example(2D): Quadratic (Degree 2) Bezier.
|
||||
// bez = [[0,0], [30,30], [80,0]];
|
||||
// move_copies(bezier_curve(bez, 8)) sphere(r=1.5, $fn=12);
|
||||
|
@ -435,9 +436,11 @@ function bezier_curvature(curve, u) =
|
|||
// bez = [[0,0], [5,15], [40,20], [60,-15], [80,0]];
|
||||
// move_copies(bezier_curve(bez, 8)) sphere(r=1.5, $fn=12);
|
||||
// trace_bezier(bez, N=len(bez)-1);
|
||||
function bezier_curve(curve,n,endpoint) = [each bezier_points(curve, [0:1/n:(n-0.5)/n]),
|
||||
if (endpoint) curve[len(curve)-1]
|
||||
];
|
||||
function bezier_curve(curve,n,endpoint=true) =
|
||||
[
|
||||
each bezier_points(curve, rangex(endpoint?n-1:n,0,1)),
|
||||
if (endpoint) last(curve)
|
||||
];
|
||||
|
||||
// Function: bezier_segment_closest_point()
|
||||
// Usage:
|
||||
|
@ -769,7 +772,7 @@ function path_to_bezier(path, closed=false, tangents, uniform=false, size, relsi
|
|||
)
|
||||
assert(min(sizevect)>0, "Size and relsize must be greater than zero")
|
||||
[
|
||||
for(i=[0:lastpt-1])
|
||||
for(i=[0:1:lastpt-1])
|
||||
let(
|
||||
first = path[i],
|
||||
second = select(path,i+1),
|
||||
|
|
13
common.scad
13
common.scad
|
@ -184,6 +184,19 @@ function valid_range(x) =
|
|||
: ( x[1]<0 && x[0]>=x[2] ) );
|
||||
|
||||
|
||||
// Function: is_func()
|
||||
// Usage:
|
||||
// bool = is_func(x);
|
||||
// Description:
|
||||
// Returns true if OpenSCAD supports function literals, and the given item is one.
|
||||
// Arguments:
|
||||
// x = The value to check against.
|
||||
// Example:
|
||||
// f = function (a) a==2;
|
||||
// bool = is_func(f); // Returns: true
|
||||
function is_func(x) = version_num()>20210000 && is_function(x);
|
||||
|
||||
|
||||
// Function: is_consistent()
|
||||
// Usage:
|
||||
// bool = is_consistent(list, <pattern>);
|
||||
|
|
|
@ -377,7 +377,7 @@ module show_anchors(s=10, std=true, custom=true) {
|
|||
}
|
||||
}
|
||||
if (custom) {
|
||||
for (anchor=select($parent_geom,-1)) {
|
||||
for (anchor=last($parent_geom)) {
|
||||
attach(anchor[0]) {
|
||||
if(two_d) {
|
||||
anchor_arrow2d(s, color="cyan");
|
||||
|
|
|
@ -40,24 +40,30 @@ module move_copies(a=[[0,0,0]])
|
|||
}
|
||||
|
||||
|
||||
// Module: line_of()
|
||||
// Function&Module: line_of()
|
||||
//
|
||||
// Usage: Spread `n` copies by a given spacing
|
||||
// line_of(spacing, [n], [p1]) ...
|
||||
// line_of(spacing, <n>, <p1=>) ...
|
||||
// Usage: Spread copies every given spacing along the line
|
||||
// line_of(spacing, l, [p1]) ...
|
||||
// line_of(spacing, <l=>, <p1=>) ...
|
||||
// Usage: Spread `n` copies along the length of the line
|
||||
// line_of(l, [n], [p1]) ...
|
||||
// line_of(<n=>, <l=>, <p1=>) ...
|
||||
// Usage: Spread `n` copies along the line from `p1` to `p2`
|
||||
// line_of(p1, p2, [n]) ...
|
||||
// line_of(<n=>, <p1=>, <p2=>) ...
|
||||
// Usage: Spread copies every given spacing, centered along the line from `p1` to `p2`
|
||||
// line_of(p1, p2, spacing) ...
|
||||
//
|
||||
// line_of(<spacing>, <p1=>, <p2=>) ...
|
||||
// Usage: As a function
|
||||
// pts = line_of(<spacing>, <n>, <p1=>);
|
||||
// pts = line_of(<spacing>, <l=>, <p1=>);
|
||||
// pts = line_of(<n=>, <l=>, <p1=>);
|
||||
// pts = line_of(<n=>, <p1=>, <p2=>);
|
||||
// pts = line_of(<spacing>, <p1=>, <p2=>);
|
||||
// Description:
|
||||
// Copies `children()` at one or more evenly spread positions along a line. By default, the line
|
||||
// will be centered at the origin, unless the starting point `p1` is given. The line will be
|
||||
// pointed towards `RIGHT` (X+) unless otherwise given as a vector in `l`, `spacing`, or `p1`/`p2`.
|
||||
// The spread is specified in one of several ways:
|
||||
// When called as a function, returns a list of points at evenly spread positions along a line.
|
||||
// When called as a module, copies `children()` at one or more evenly spread positions along a line.
|
||||
// By default, the line will be centered at the origin, unless the starting point `p1` is given.
|
||||
// The line will be pointed towards `RIGHT` (X+) unless otherwise given as a vector in `l`,
|
||||
// `spacing`, or `p1`/`p2`. The spread is specified in one of several ways:
|
||||
// .
|
||||
// If You Know... | Then Use Something Like...
|
||||
// -------------------------------- | --------------------------------
|
||||
|
@ -74,6 +80,7 @@ module move_copies(a=[[0,0,0]])
|
|||
// Arguments:
|
||||
// spacing = Either the scalar spacing distance along the X+ direction, or the vector giving both the direction and spacing distance between each set of copies.
|
||||
// n = Number of copies to distribute along the line. (Default: 2)
|
||||
// ---
|
||||
// l = Either the scalar length of the line, or a vector giving both the direction and length of the line.
|
||||
// p1 = If given, specifies the starting point of the line.
|
||||
// p2 = If given with `p1`, specifies the ending point of line, and indirectly calculates the line length.
|
||||
|
@ -102,40 +109,42 @@ module move_copies(a=[[0,0,0]])
|
|||
// cube(size=[1,3,1],center=true);
|
||||
// cube(size=[3,1,1],center=true);
|
||||
// }
|
||||
// Example(2D):
|
||||
// pts = line_of([10,5],n=5);
|
||||
// move_copies(pts) circle(d=2);
|
||||
module line_of(spacing, n, l, p1, p2)
|
||||
{
|
||||
assert(is_undef(spacing) || is_finite(spacing) || is_vector(spacing));
|
||||
assert(is_undef(n) || is_finite(n));
|
||||
assert(is_undef(l) || is_finite(l) || is_vector(l));
|
||||
assert(is_undef(p1) || is_vector(p1));
|
||||
assert(is_undef(p2) || is_vector(p2));
|
||||
ll = (
|
||||
!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_undef(n)? n :
|
||||
(!is_undef(spacing) && !is_undef(ll))? floor(norm(ll) / norm(scalar_vec3(spacing, 0)) + 1.000001) :
|
||||
2
|
||||
);
|
||||
spc = (
|
||||
cnt<=1? [0,0,0] :
|
||||
is_undef(spacing)? (ll/(cnt-1)) :
|
||||
is_num(spacing) && !is_undef(ll)? (ll/(cnt-1)) :
|
||||
scalar_vec3(spacing, 0)
|
||||
);
|
||||
assert(!is_undef(cnt), "Need two of `spacing`, 'l', 'n', or `p1`/`p2` arguments in `line_of()`.");
|
||||
spos = !is_undef(p1)? point3d(p1) : -(cnt-1)/2 * spc;
|
||||
for (i=[0:1:cnt-1]) {
|
||||
pos = i * spc + spos;
|
||||
$pos = pos;
|
||||
pts = line_of(spacing=spacing, n=n, l=l, p1=p1, p2=p2);
|
||||
for (i=idx(pts)) {
|
||||
$idx = i;
|
||||
translate(pos) children();
|
||||
$pos = pts[i];
|
||||
translate($pos) children();
|
||||
}
|
||||
}
|
||||
|
||||
function line_of(spacing, n, l, p1, p2) =
|
||||
assert(is_undef(spacing) || is_finite(spacing) || is_vector(spacing))
|
||||
assert(is_undef(n) || is_finite(n))
|
||||
assert(is_undef(l) || is_finite(l) || is_vector(l))
|
||||
assert(is_undef(p1) || is_vector(p1))
|
||||
assert(is_undef(p2) || is_vector(p2))
|
||||
let(
|
||||
ll = !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_undef(n)? n :
|
||||
(!is_undef(spacing) && !is_undef(ll))? floor(norm(ll) / norm(scalar_vec3(spacing, 0)) + 1.000001) :
|
||||
2,
|
||||
spc = cnt<=1? [0,0,0] :
|
||||
is_undef(spacing)? (ll/(cnt-1)) :
|
||||
is_num(spacing) && !is_undef(ll)? (ll/(cnt-1)) :
|
||||
scalar_vec3(spacing, 0)
|
||||
)
|
||||
assert(!is_undef(cnt), "Need two of `spacing`, 'l', 'n', or `p1`/`p2` arguments in `line_of()`.")
|
||||
let( spos = !is_undef(p1)? point3d(p1) : -(cnt-1)/2 * spc )
|
||||
[for (i=[0:1:cnt-1]) i * spc + spos];
|
||||
|
||||
|
||||
// Module: xcopies()
|
||||
//
|
||||
|
|
10
gears.scad
10
gears.scad
|
@ -960,11 +960,11 @@ function bevel_gear(
|
|||
each apply(xflip() * zrot(360*tooth/teeth) * m, path3d(profile))
|
||||
]
|
||||
],
|
||||
thickness = abs(verts1[0][0].z - select(verts1,-1)[0].z),
|
||||
thickness = abs(verts1[0][0].z - last(verts1)[0].z),
|
||||
vertices = [for (x=verts1) down(thickness/2, p=reverse(x))],
|
||||
sides_vnf = vnf_vertex_array(vertices, caps=false, col_wrap=true, reverse=true),
|
||||
top_verts = select(vertices,-1),
|
||||
bot_verts = select(vertices,0),
|
||||
top_verts = last(vertices),
|
||||
bot_verts = vertices[0],
|
||||
gear_pts = len(top_verts),
|
||||
face_pts = gear_pts / teeth,
|
||||
top_faces =[
|
||||
|
@ -1431,8 +1431,8 @@ function worm_gear(
|
|||
)
|
||||
]
|
||||
],
|
||||
top_verts = select(profiles,-1),
|
||||
bot_verts = select(profiles,0),
|
||||
top_verts = last(profiles),
|
||||
bot_verts = profiles[0],
|
||||
face_pts = len(tooth_profile),
|
||||
gear_pts = face_pts * teeth,
|
||||
top_faces =[
|
||||
|
|
|
@ -158,7 +158,7 @@ function _hull_collinear(points) =
|
|||
// %polyhedron(points=pts, faces=faces);
|
||||
function hull3d_faces(points) =
|
||||
assert(is_path(points,3),"Invalid input to hull3d_faces")
|
||||
len(points) < 3 ? list_range(len(points))
|
||||
len(points) < 3 ? range(len(points))
|
||||
: let ( // start with a single non-collinear triangle
|
||||
tri = noncollinear_triple(points, error=false)
|
||||
)
|
||||
|
|
|
@ -968,7 +968,7 @@ module rabbit_clip(type, length, width, snap, thickness, depth, compression=0.1
|
|||
);
|
||||
assert(fullpath[4].y < fullpath[3].y, "Pin is too wide for its length");
|
||||
|
||||
snapmargin = -snap + select(sidepath,-1).x;// - compression;
|
||||
snapmargin = -snap + last(sidepath).x;// - compression;
|
||||
if (is_pin){
|
||||
if (snapmargin<0) echo("WARNING: The snap is too large for the clip to squeeze to fit its socket")
|
||||
echo(snapmargin=snapmargin);
|
||||
|
|
180
math.scad
180
math.scad
|
@ -174,6 +174,31 @@ function lerp(a,b,u) =
|
|||
[for (v = u) (1-v)*a + v*b ];
|
||||
|
||||
|
||||
// Function: lerpn()
|
||||
// Usage:
|
||||
// x = lerpn(a, b, n);
|
||||
// x = lerpn(a, b, n, <endpoint>);
|
||||
// Description:
|
||||
// Returns exactly `n` values, linearly interpolated between `a` and `b`.
|
||||
// If `endpoint` is true, then the last value will exactly equal `b`.
|
||||
// If `endpoint` is false, then the last value will about `a+(b-a)*(1-1/n)`.
|
||||
// Arguments:
|
||||
// a = First value or vector.
|
||||
// b = Second value or vector.
|
||||
// n = The number of values to return.
|
||||
// endpoint = If true, the last value will be exactly `b`. If false, the last value will be one step less.
|
||||
// Examples:
|
||||
// l = lerpn(-4,4,9); // Returns: [-4,-3,-2,-1,0,1,2,3,4]
|
||||
// l = lerpn(-4,4,8,false); // Returns: [-4,-3,-2,-1,0,1,2,3]
|
||||
// l = lerpn(0,1,6); // Returns: [0, 0.2, 0.4, 0.6, 0.8, 1]
|
||||
// l = lerpn(0,1,5,false); // Returns: [0, 0.2, 0.4, 0.6, 0.8]
|
||||
function lerpn(a,b,n,endpoint=true) =
|
||||
assert(same_shape(a,b), "Bad or inconsistent inputs to lerp")
|
||||
assert(is_int(n))
|
||||
assert(is_bool(endpoint))
|
||||
let( d = n - (endpoint? 1 : 0) )
|
||||
[for (i=[0:1:n-1]) let(u=i/d) (1-u)*a + u*b];
|
||||
|
||||
|
||||
// Section: Undef Safe Math
|
||||
|
||||
|
@ -434,32 +459,6 @@ function modang(x) =
|
|||
let(xx = posmod(x,360)) xx<180? xx : xx-360;
|
||||
|
||||
|
||||
// Function: modrange()
|
||||
// Usage:
|
||||
// modrange(x, y, m, <step>)
|
||||
// Description:
|
||||
// Returns a normalized list of numbers from `x` to `y`, by `step`, modulo `m`. Wraps if `x` > `y`.
|
||||
// Arguments:
|
||||
// x = The start value to constrain.
|
||||
// y = The end value to constrain.
|
||||
// m = Modulo value.
|
||||
// step = Step by this amount.
|
||||
// Examples:
|
||||
// modrange(90,270,360, step=45); // Returns: [90,135,180,225,270]
|
||||
// modrange(270,90,360, step=45); // Returns: [270,315,0,45,90]
|
||||
// modrange(90,270,360, step=-45); // Returns: [90,45,0,315,270]
|
||||
// modrange(270,90,360, step=-45); // Returns: [270,225,180,135,90]
|
||||
function modrange(x, y, m, step=1) =
|
||||
assert( is_finite(x+y+step+m) && !approx(m,0), "Input must be finite numbers and the module value cannot be zero." )
|
||||
let(
|
||||
a = posmod(x, m),
|
||||
b = posmod(y, m),
|
||||
c = step>0? (a>b? b+m : b)
|
||||
: (a<b? b-m : b)
|
||||
) [for (i=[a:step:c]) (i%m+m)%m ];
|
||||
|
||||
|
||||
|
||||
// Section: Random Number Generation
|
||||
|
||||
// Function: rand_int()
|
||||
|
@ -495,7 +494,7 @@ function rand_int(minval, maxval, N, seed=undef) =
|
|||
function gaussian_rands(mean, stddev, N=1, seed=undef) =
|
||||
assert( is_finite(mean+stddev+N) && (is_undef(seed) || is_finite(seed) ), "Input must be finite numbers.")
|
||||
let(nums = is_undef(seed)? rands(0,1,N*2) : rands(0,1,N*2,seed))
|
||||
[for (i = list_range(N)) mean + stddev*sqrt(-2*ln(nums[i*2]))*cos(360*nums[i*2+1])];
|
||||
[for (i = range(N)) mean + stddev*sqrt(-2*ln(nums[i*2]))*cos(360*nums[i*2+1])];
|
||||
|
||||
|
||||
// Function: log_rands()
|
||||
|
@ -613,7 +612,7 @@ function _cumsum(v,_i=0,_acc=[]) =
|
|||
v, _i+1,
|
||||
concat(
|
||||
_acc,
|
||||
[_i==0 ? v[_i] : select(_acc,-1)+v[_i]]
|
||||
[_i==0 ? v[_i] : last(_acc) + v[_i]]
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -906,7 +905,7 @@ function _back_substitute(R, b, x=[]) =
|
|||
: let(
|
||||
newvalue = len(x)==0
|
||||
? b[ind]/R[ind][ind]
|
||||
: (b[ind]-select(R[ind],ind+1,-1) * x)/R[ind][ind]
|
||||
: (b[ind]-list_tail(R[ind],ind+1) * x)/R[ind][ind]
|
||||
)
|
||||
_back_substitute(R, b, concat([newvalue],x));
|
||||
|
||||
|
@ -1217,72 +1216,85 @@ function compare_vals(a, b) =
|
|||
// a = First list to compare.
|
||||
// b = Second list to compare.
|
||||
function compare_lists(a, b) =
|
||||
a==b? 0
|
||||
: let(
|
||||
cmps = [ for(i=[0:1:min(len(a),len(b))-1])
|
||||
let( cmp = compare_vals(a[i],b[i]) )
|
||||
if(cmp!=0) cmp
|
||||
]
|
||||
)
|
||||
cmps==[]? (len(a)-len(b)) : cmps[0];
|
||||
a==b? 0 :
|
||||
let(
|
||||
cmps = [
|
||||
for (i = [0:1:min(len(a),len(b))-1])
|
||||
let( cmp = compare_vals(a[i],b[i]) )
|
||||
if (cmp!=0) cmp
|
||||
]
|
||||
)
|
||||
cmps==[]? (len(a)-len(b)) : cmps[0];
|
||||
|
||||
|
||||
// Function: any()
|
||||
// Usage:
|
||||
// b = any(l);
|
||||
// b = any(l,func);
|
||||
// Description:
|
||||
// Returns true if any item in list `l` evaluates as true.
|
||||
// If `l` is a lists of lists, `any()` is applied recursively to each sublist.
|
||||
// Arguments:
|
||||
// l = The list to test for true items.
|
||||
// func = An optional function literal of signature (x), returning bool, to test each list item with.
|
||||
// Example:
|
||||
// any([0,false,undef]); // Returns false.
|
||||
// any([1,false,undef]); // Returns true.
|
||||
// any([1,5,true]); // Returns true.
|
||||
// any([[0,0], [0,0]]); // Returns false.
|
||||
// any([[0,0], [0,0]]); // Returns true.
|
||||
// any([[0,0], [1,0]]); // Returns true.
|
||||
function any(l) =
|
||||
function any(l, func) =
|
||||
assert(is_list(l), "The input is not a list." )
|
||||
_any(l);
|
||||
assert(func==undef || is_func(func))
|
||||
is_func(func)
|
||||
? _any_func(l, func)
|
||||
: _any_bool(l);
|
||||
|
||||
function _any(l, i=0, succ=false) =
|
||||
(i>=len(l) || succ)? succ :
|
||||
_any(
|
||||
l, i+1,
|
||||
succ = is_list(l[i]) ? _any(l[i]) : !(!l[i])
|
||||
);
|
||||
function _any_func(l, func, i=0, out=false) =
|
||||
i >= len(l) || out? out :
|
||||
_any_func(l, func, i=i+1, out=out || func(l[i]));
|
||||
|
||||
function _any_bool(l, i=0, out=false) =
|
||||
i >= len(l) || out? out :
|
||||
_any_bool(l, i=i+1, out=out || l[i]);
|
||||
|
||||
|
||||
// Function: all()
|
||||
// Usage:
|
||||
// b = all(l);
|
||||
// b = all(l,func);
|
||||
// Description:
|
||||
// Returns true if all items in list `l` evaluate as true.
|
||||
// If `l` is a lists of lists, `all()` is applied recursively to each sublist.
|
||||
// Returns true if all items in list `l` evaluate as true. If `func` is given a function liteal
|
||||
// of signature (x), returning bool, then that function literal is evaluated for each list item.
|
||||
// Arguments:
|
||||
// l = The list to test for true items.
|
||||
// func = An optional function literal of signature (x), returning bool, to test each list item with.
|
||||
// Example:
|
||||
// all([0,false,undef]); // Returns false.
|
||||
// all([1,false,undef]); // Returns false.
|
||||
// all([1,5,true]); // Returns true.
|
||||
// all([[0,0], [0,0]]); // Returns false.
|
||||
// all([[0,0], [1,0]]); // Returns false.
|
||||
// all([[0,0], [0,0]]); // Returns true.
|
||||
// all([[0,0], [1,0]]); // Returns true.
|
||||
// all([[1,1], [1,1]]); // Returns true.
|
||||
function all(l) =
|
||||
assert( is_list(l), "The input is not a list." )
|
||||
_all(l);
|
||||
function all(l, func) =
|
||||
assert(is_list(l), "The input is not a list.")
|
||||
assert(func==undef || is_func(func))
|
||||
is_func(func)
|
||||
? _all_func(l, func)
|
||||
: _all_bool(l);
|
||||
|
||||
function _all(l, i=0, fail=false) =
|
||||
(i>=len(l) || fail)? !fail :
|
||||
_all(
|
||||
l, i+1,
|
||||
fail = is_list(l[i]) ? !_all(l[i]) : !l[i]
|
||||
) ;
|
||||
function _all_func(l, func, i=0, out=true) =
|
||||
i >= len(l) || !out? out :
|
||||
_all_func(l, func, i=i+1, out=out && func(l[i]));
|
||||
|
||||
function _all_bool(l, i=0, out=true) =
|
||||
i >= len(l) || !out? out :
|
||||
_all_bool(l, i=i+1, out=out && l[i]);
|
||||
|
||||
|
||||
// Function: count_true()
|
||||
// Usage:
|
||||
// n = count_true(l)
|
||||
// n = count_true(l,<nmax=>)
|
||||
// n = count_true(l,func,<nmax=>)
|
||||
// Description:
|
||||
// Returns the number of items in `l` that evaluate as true.
|
||||
// If `l` is a lists of lists, this is applied recursively to each
|
||||
|
@ -1290,24 +1302,38 @@ function _all(l, i=0, fail=false) =
|
|||
// in all recursive sublists.
|
||||
// Arguments:
|
||||
// l = The list to test for true items.
|
||||
// nmax = If given, stop counting if `nmax` items evaluate as true.
|
||||
// func = An optional function literal of signature (x), returning bool, to test each list item with.
|
||||
// ---
|
||||
// nmax = Max number of true items to count. Default: `undef` (no limit)
|
||||
// Example:
|
||||
// count_true([0,false,undef]); // Returns 0.
|
||||
// count_true([1,false,undef]); // Returns 1.
|
||||
// count_true([1,5,false]); // Returns 2.
|
||||
// count_true([1,5,true]); // Returns 3.
|
||||
// count_true([[0,0], [0,0]]); // Returns 0.
|
||||
// count_true([[0,0], [1,0]]); // Returns 1.
|
||||
// count_true([[1,1], [1,1]]); // Returns 4.
|
||||
// count_true([[1,1], [1,1]], nmax=3); // Returns 3.
|
||||
function _count_true_rec(l, nmax, _cnt=0, _i=0) =
|
||||
_i>=len(l) || (is_num(nmax) && _cnt>=nmax)? _cnt :
|
||||
_count_true_rec(l, nmax, _cnt=_cnt+(l[_i]?1:0), _i=_i+1);
|
||||
// count_true([[0,0], [0,0]]); // Returns 2.
|
||||
// count_true([[0,0], [1,0]]); // Returns 2.
|
||||
// count_true([[1,1], [1,1]]); // Returns 2.
|
||||
// count_true([[1,1], [1,1]], nmax=1); // Returns 1.
|
||||
function count_true(l, func, nmax) =
|
||||
assert(is_list(l))
|
||||
assert(func==undef || is_func(func))
|
||||
is_func(func)
|
||||
? _count_true_func(l, func, nmax)
|
||||
: _count_true_bool(l, nmax);
|
||||
|
||||
function count_true(l, nmax) =
|
||||
is_undef(nmax)? len([for (x=l) if(x) 1]) :
|
||||
!is_list(l) ? ( l? 1: 0) :
|
||||
_count_true_rec(l, nmax);
|
||||
function _count_true_func(l, func, nmax, i=0, out=0) =
|
||||
i >= len(l) || (nmax!=undef && out>=nmax) ? out :
|
||||
_count_true_func(
|
||||
l, func, nmax, i = i + 1,
|
||||
out = out + (func(l[i])? 1:0)
|
||||
);
|
||||
|
||||
function _count_true_bool(l, nmax, i=0, out=0) =
|
||||
i >= len(l) || (nmax!=undef && out>=nmax) ? out :
|
||||
_count_true_bool(
|
||||
l, nmax, i = i + 1,
|
||||
out = out + (l[i]? 1:0)
|
||||
);
|
||||
|
||||
|
||||
|
||||
|
@ -1574,6 +1600,7 @@ function c_ident(n) = [for (i = [0:1:n-1]) [for (j = [0:1:n-1]) (i==j)?[1,0]:[0,
|
|||
function c_norm(z) = norm_fro(z);
|
||||
|
||||
|
||||
|
||||
// Section: Polynomials
|
||||
|
||||
// Function: quadratic_roots()
|
||||
|
@ -1625,6 +1652,7 @@ function polynomial(p,z,k,total) =
|
|||
: k==len(p) ? total
|
||||
: polynomial(p,z,k+1, is_num(z) ? total*z+p[k] : c_mul(total,z)+[p[k],0]);
|
||||
|
||||
|
||||
// Function: poly_mult()
|
||||
// Usage:
|
||||
// x = polymult(p,q)
|
||||
|
@ -1636,7 +1664,7 @@ function poly_mult(p,q) =
|
|||
is_undef(q) ?
|
||||
len(p)==2
|
||||
? poly_mult(p[0],p[1])
|
||||
: poly_mult(p[0], poly_mult(select(p,1,-1)))
|
||||
: poly_mult(p[0], poly_mult(list_tail(p)))
|
||||
:
|
||||
assert( is_vector(p) && is_vector(q),"Invalid arguments to poly_mult")
|
||||
p*p==0 || q*q==0
|
||||
|
@ -1679,7 +1707,7 @@ function _poly_div(n,d,q) =
|
|||
/// or give epsilon for approximate zeros.
|
||||
function _poly_trim(p,eps=0) =
|
||||
let( nz = [for(i=[0:1:len(p)-1]) if ( !approx(p[i],0,eps)) i])
|
||||
len(nz)==0 ? [0] : select(p,nz[0],-1);
|
||||
len(nz)==0 ? [0] : list_tail(p,nz[0]);
|
||||
|
||||
|
||||
// Function: poly_add()
|
||||
|
@ -1720,7 +1748,7 @@ function poly_roots(p,tol=1e-14,error_bound=false) =
|
|||
let( p = _poly_trim(p,eps=0) )
|
||||
assert( p!=[0], "Input polynomial cannot be zero." )
|
||||
p[len(p)-1] == 0 ? // Strip trailing zero coefficients
|
||||
let( solutions = poly_roots(select(p,0,-2),tol=tol, error_bound=error_bound))
|
||||
let( solutions = poly_roots(list_head(p),tol=tol, error_bound=error_bound))
|
||||
(error_bound ? [ [[0,0], each solutions[0]], [0, each solutions[1]]]
|
||||
: [[0,0], each solutions]) :
|
||||
len(p)==1 ? (error_bound ? [[],[]] : []) : // Nonzero constant case has no solutions
|
||||
|
|
|
@ -169,9 +169,9 @@ module modular_hose(size, type, clearance=0, waist_len, anchor=BOTTOM, spin=0,or
|
|||
type=="segment"? concat(back(midlength,p=smallend),yflip(p=bigend))
|
||||
: type=="small" || type=="ball" ?
|
||||
concat(back(midlength,p=smallend),
|
||||
[[select(smallend,-1).x,0],[ smallend[0].x,0]])
|
||||
[[last(smallend).x,0],[ smallend[0].x,0]])
|
||||
: concat( back(midlength,p=bigend),
|
||||
[[select(bigend,-1).x,0],[ bigend[0].x,0]]);
|
||||
[[last(bigend).x,0],[ bigend[0].x,0]]);
|
||||
bounds = pointlist_bounds(shape);
|
||||
center = mean(bounds);
|
||||
attachable(anchor,spin,orient,l=bounds[1].y-bounds[0].y, r=bounds[1].x)
|
||||
|
|
68
paths.scad
68
paths.scad
|
@ -183,7 +183,7 @@ function path_length(path,closed=false) =
|
|||
function path_segment_lengths(path, closed=false) =
|
||||
[
|
||||
for (i=[0:1:len(path)-2]) norm(path[i+1]-path[i]),
|
||||
if (closed) norm(path[0]-select(path,-1))
|
||||
if (closed) norm(path[0]-last(path))
|
||||
];
|
||||
|
||||
|
||||
|
@ -482,13 +482,13 @@ function path_chamfer_and_rounding(path, closed, chamfer, rounding) =
|
|||
p2 = select(path,i),
|
||||
crn1 = select(corner_paths,i-1),
|
||||
crn2 = corner_paths[i],
|
||||
l1 = norm(select(crn1,-1)-p1),
|
||||
l1 = norm(last(crn1)-p1),
|
||||
l2 = norm(crn2[0]-p2),
|
||||
needed = l1 + l2,
|
||||
seglen = norm(p2-p1),
|
||||
check = assert(seglen >= needed, str("Path segment ",i," is too short to fulfill rounding/chamfering for the adjacent corners."))
|
||||
) each crn2,
|
||||
if (!closed) select(path,-1)
|
||||
if (!closed) last(path)
|
||||
]
|
||||
) deduplicate(out);
|
||||
|
||||
|
@ -783,16 +783,16 @@ function _path_fast_defragment(fragments, eps=EPSILON, _done=[]) =
|
|||
len(fragments)==0? _done :
|
||||
let(
|
||||
path = fragments[0],
|
||||
endpt = select(path,-1),
|
||||
endpt = last(path),
|
||||
extenders = [
|
||||
for (i = [1:1:len(fragments)-1]) let(
|
||||
test1 = approx(endpt,fragments[i][0],eps=eps),
|
||||
test2 = approx(endpt,select(fragments[i],-1),eps=eps)
|
||||
test2 = approx(endpt,last(fragments[i]),eps=eps)
|
||||
) if (test1 || test2) (test1? i : -1)
|
||||
]
|
||||
) len(extenders) == 1 && extenders[0] >= 0? _path_fast_defragment(
|
||||
fragments=[
|
||||
concat(select(path,0,-2),fragments[extenders[0]]),
|
||||
concat(list_head(path),fragments[extenders[0]]),
|
||||
for (i = [1:1:len(fragments)-1])
|
||||
if (i != extenders[0]) fragments[i]
|
||||
],
|
||||
|
@ -814,7 +814,7 @@ function _extreme_angle_fragment(seg, fragments, rightmost=true, eps=EPSILON) =
|
|||
for (i = idx(fragments)) let(
|
||||
fragment = fragments[i],
|
||||
fwdmatch = approx(seg[1], fragment[0], eps=eps),
|
||||
bakmatch = approx(seg[1], select(fragment,-1), eps=eps)
|
||||
bakmatch = approx(seg[1], last(fragment), eps=eps)
|
||||
) [
|
||||
fwdmatch,
|
||||
bakmatch,
|
||||
|
@ -872,13 +872,13 @@ function assemble_a_path_from_fragments(fragments, rightmost=true, startfrag=0,
|
|||
// Found fragment is already closed
|
||||
[foundfrag, concat([path], remainder)]
|
||||
) : let(
|
||||
fragend = select(foundfrag,-1),
|
||||
fragend = last(foundfrag),
|
||||
hits = [for (i = idx(path,e=-2)) if(approx(path[i],fragend,eps=eps)) i]
|
||||
) hits? (
|
||||
let(
|
||||
// Found fragment intersects with initial path
|
||||
hitidx = select(hits,-1),
|
||||
newpath = slice(path,0,hitidx+1),
|
||||
hitidx = last(hits),
|
||||
newpath = list_head(path,hitidx),
|
||||
newfrags = concat(len(newpath)>1? [newpath] : [], remainder),
|
||||
outpath = concat(slice(path,hitidx,-2), foundfrag)
|
||||
)
|
||||
|
@ -1238,18 +1238,20 @@ module path_extrude(path, convexity=10, clipsize=100) {
|
|||
module path_spread(path, n, spacing, sp=undef, rotate_children=true, closed=false)
|
||||
{
|
||||
length = path_length(path,closed);
|
||||
distances = is_def(sp)? (
|
||||
is_def(n) && is_def(spacing)? list_range(s=sp, step=spacing, n=n) :
|
||||
is_def(n)? list_range(s=sp, e=length, n=n) :
|
||||
list_range(s=sp, step=spacing, e=length)
|
||||
) : is_def(n) && is_undef(spacing)? (
|
||||
closed?
|
||||
let(range=list_range(s=0,e=length, n=n+1)) slice(range,0,-2) :
|
||||
list_range(s=0, e=length, n=n)
|
||||
) : (
|
||||
distances =
|
||||
is_def(sp)? ( // Start point given
|
||||
is_def(n) && is_def(spacing)? range(s=sp, step=spacing, n=n) :
|
||||
is_def(n)? range(s=sp, e=length, n=n) :
|
||||
range(s=sp, step=spacing, e=length)
|
||||
)
|
||||
: is_def(n) && is_undef(spacing)? ( // N alone given
|
||||
closed ? rangex(s=0, e=length, n=n)
|
||||
: range(s=0, e=length, n=n)
|
||||
)
|
||||
: ( // No start point and spacing is given, N maybe given
|
||||
let(
|
||||
n = is_def(n)? n : floor(length/spacing)+(closed?0:1),
|
||||
ptlist = list_range(s=0,step=spacing,n=n),
|
||||
ptlist = range(s=0,step=spacing,n=n),
|
||||
listcenter = mean(ptlist)
|
||||
) closed?
|
||||
sort([for(entry=ptlist) posmod(entry-listcenter,length)]) :
|
||||
|
@ -1333,7 +1335,7 @@ function path_cut_points(path, dists, closed=false, direction=false) =
|
|||
function _path_cut_points(path, dists, closed=false, pind=0, dtotal=0, dind=0, result=[]) =
|
||||
dind == len(dists) ? result :
|
||||
let(
|
||||
lastpt = len(result)==0? [] : select(result,-1)[0], // location of last cut point
|
||||
lastpt = len(result)==0? [] : last(result)[0], // location of last cut point
|
||||
dpartial = len(result)==0? 0 : norm(lastpt-select(path,pind)), // remaining length in segment
|
||||
nextpoint = dists[dind] < dpartial+dtotal // Do we have enough length left on the current segment?
|
||||
? [lerp(lastpt,select(path,pind),(dists[dind]-dtotal)/dpartial),pind]
|
||||
|
@ -1419,7 +1421,7 @@ function _path_cuts_dir(path, cuts, closed=false, eps=1e-2) =
|
|||
function path_cut(path,cutdist,closed) =
|
||||
is_num(cutdist) ? path_cut(path,[cutdist],closed) :
|
||||
assert(is_vector(cutdist))
|
||||
assert(select(cutdist,-1)<path_length(path,closed=closed),"Cut distances must be smaller than the path length")
|
||||
assert(last(cutdist)<path_length(path,closed=closed),"Cut distances must be smaller than the path length")
|
||||
assert(cutdist[0]>0, "Cut distances must be strictly positive")
|
||||
let(
|
||||
cutlist = path_cut_points(path,cutdist,closed=closed),
|
||||
|
@ -1433,7 +1435,7 @@ function path_cut(path,cutdist,closed) =
|
|||
cutlist[i][0]==cutlist[i+1][0] ? []
|
||||
:
|
||||
[ if (!approx(cutlist[i][0], select(path,cutlist[i][1]))) cutlist[i][0],
|
||||
each slice(path,cutlist[i][1], cutlist[i+1][1]),
|
||||
each slice(path, cutlist[i][1], cutlist[i+1][1]-1),
|
||||
if (!approx(cutlist[i+1][0], select(path,cutlist[i+1][1]-1))) cutlist[i+1][0],
|
||||
],
|
||||
[
|
||||
|
@ -1553,7 +1555,7 @@ function subdivide_path(path, N, refine, closed=true, exact=true, method="length
|
|||
lerp(path[i],select(path,i+1), j/(add[i]+1))
|
||||
]
|
||||
],
|
||||
closed? [] : [select(path,-1)]
|
||||
closed? [] : [last(path)]
|
||||
);
|
||||
|
||||
|
||||
|
@ -1575,7 +1577,7 @@ function path_length_fractions(path, closed=false) =
|
|||
norm(select(path,i+1)-path[i])
|
||||
],
|
||||
partial_len = cumsum(lengths),
|
||||
total_len = select(partial_len,-1)
|
||||
total_len = last(partial_len)
|
||||
) partial_len / total_len;
|
||||
|
||||
|
||||
|
@ -1600,13 +1602,17 @@ function resample_path(path, N, spacing, closed=false) =
|
|||
assert(num_defined([N,spacing])==1,"Must define exactly one of N and spacing")
|
||||
assert(is_bool(closed))
|
||||
let(
|
||||
length = path_length(path,closed),
|
||||
N = is_def(N) ? N : round(length/spacing) + (closed?0:1),
|
||||
spacing = length/(closed?N:N-1), // Note: worried about round-off error, so don't include
|
||||
distlist = list_range(closed?N:N-1,step=spacing), // last point when closed=false
|
||||
cuts = path_cut_points(path, distlist, closed=closed)
|
||||
length = path_length(path,closed),
|
||||
// In the open path case decrease N by 1 so that we don't try to get
|
||||
// path_cut to return the endpoint (which might fail due to rounding)
|
||||
// Add last point later
|
||||
N = is_def(N) ? N-(closed?0:1) : round(length/spacing),
|
||||
distlist = rangex(N,e=length),
|
||||
cuts = path_cut_points(path, distlist, closed=closed)
|
||||
)
|
||||
concat(subindex(cuts,0),closed?[]:[select(path,-1)]); // Then add last point here
|
||||
[ each subindex(cuts,0),
|
||||
if (!closed) last(path) // Then add last point here
|
||||
];
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -223,7 +223,7 @@ function Q_Cumulative(v, _i=0, _acc=[]) =
|
|||
v, _i+1,
|
||||
concat(
|
||||
_acc,
|
||||
[_i==0 ? v[_i] : Q_Mul(v[_i], select(_acc,-1))]
|
||||
[_i==0 ? v[_i] : Q_Mul(v[_i], last(_acc))]
|
||||
)
|
||||
);
|
||||
|
||||
|
|
|
@ -758,7 +758,7 @@ function offset(
|
|||
// Note if !closed the last corner doesn't matter, so exclude it
|
||||
parallelcheck =
|
||||
(len(sharpcorners)==2 && !closed) ||
|
||||
all_defined(select(sharpcorners,closed?0:1,-1))
|
||||
all_defined(closed? sharpcorners : list_tail(sharpcorners))
|
||||
)
|
||||
assert(parallelcheck, "Path contains sequential parallel segments (either 180 deg turn or 0 deg turn")
|
||||
let(
|
||||
|
|
|
@ -194,7 +194,7 @@ include <structs.scad>
|
|||
// square = [[0,0],[1,0],[1,1],[0,1]];
|
||||
// spiral = flatten(repeat(concat(square,reverse(square)),5)); // Squares repeat 10 times, forward and backward
|
||||
// squareind = [for(i=[0:9]) each [i,i,i,i]]; // Index of the square for each point
|
||||
// z = list_range(40)*.2+squareind;
|
||||
// z = range(40)*.2+squareind;
|
||||
// path3d = hstack(spiral,z); // 3D spiral
|
||||
// rounding = squareind/20;
|
||||
// // Setting k=1 means curvature won't be continuous, but curves are as round as possible
|
||||
|
@ -346,7 +346,7 @@ function _bezcorner(points, parm) =
|
|||
] : _smooth_bez_fill(points,parm),
|
||||
N = max(3,$fn>0 ?$fn : ceil(bezier_segment_length(P)/$fs))
|
||||
)
|
||||
bezier_curve(P,N,endpoint=true);
|
||||
bezier_curve(P,N+1,endpoint=true);
|
||||
|
||||
function _chamfcorner(points, parm) =
|
||||
let(
|
||||
|
@ -423,13 +423,12 @@ function _rounding_offsets(edgespec,z_dir=1) =
|
|||
) :
|
||||
edgetype == "circle"? radius==0? [] : [for(i=[1:N]) [radius*(cos(i*90/N)-1), z_dir*abs(radius)*sin(i*90/N)]] :
|
||||
/* smooth */ joint==0 ? [] :
|
||||
select(
|
||||
_bezcorner([[0,0],[0,z_dir*abs(joint)],[-joint,z_dir*abs(joint)]], k, $fn=N+2),
|
||||
1, -1
|
||||
list_tail(
|
||||
_bezcorner([[0,0],[0,z_dir*abs(joint)],[-joint,z_dir*abs(joint)]], k, $fn=N+2)
|
||||
)
|
||||
)
|
||||
|
||||
quant(extra > 0? concat(offsets, [select(offsets,-1)+[0,z_dir*extra]]) : offsets, 1/1024);
|
||||
quant(extra > 0? concat(offsets, [last(offsets)+[0,z_dir*extra]]) : offsets, 1/1024);
|
||||
|
||||
|
||||
|
||||
|
@ -670,7 +669,7 @@ function _path_join(paths,joint,k=0.5,i=0,result=[],relocate=true,closed=false)
|
|||
new_result = [each select(result,loop?nextcut[1]:0,len(revresult)-1-firstcut[1]),
|
||||
each bezpath,
|
||||
nextcut[0],
|
||||
if (!loop) each select(nextpath,nextcut[1],-1)
|
||||
if (!loop) each list_tail(nextpath,nextcut[1])
|
||||
]
|
||||
)
|
||||
i==len(paths)-(closed?1:2)
|
||||
|
@ -900,7 +899,7 @@ function _make_offset_polyhedron(path,offsets, offset_type, flip_faces, quality,
|
|||
vertexcount=0, vertices=[], faces=[] )=
|
||||
offsetind==len(offsets)? (
|
||||
let(
|
||||
bottom = list_range(n=len(path),s=vertexcount),
|
||||
bottom = range(n=len(path),s=vertexcount),
|
||||
oriented_bottom = !flip_faces? bottom : reverse(bottom)
|
||||
) [vertices, concat(faces,[oriented_bottom])]
|
||||
) : (
|
||||
|
@ -980,8 +979,8 @@ function offset_sweep(
|
|||
: 0,
|
||||
|
||||
// "Extra" height enlarges the result beyond the requested height, so subtract it
|
||||
bottom_height = len(offsets_bot)==0 ? 0 : abs(select(offsets_bot,-1)[1]) - struct_val(bottom,"extra"),
|
||||
top_height = len(offsets_top)==0 ? 0 : abs(select(offsets_top,-1)[1]) - struct_val(top,"extra"),
|
||||
bottom_height = len(offsets_bot)==0 ? 0 : abs(last(offsets_bot)[1]) - struct_val(bottom,"extra"),
|
||||
top_height = len(offsets_top)==0 ? 0 : abs(last(offsets_top)[1]) - struct_val(top,"extra"),
|
||||
|
||||
height = one_defined([l,h,height], "l,h,height", dflt=u_add(bottom_height,top_height)),
|
||||
middle = height-bottom_height-top_height
|
||||
|
@ -1129,7 +1128,7 @@ function os_mask(mask, out=false, extra,check_valid, quality, offset_maxstep, of
|
|||
let(
|
||||
points = ([for(pt=polygon_shift(mask,origin_index[0])) [xfactor*max(pt.x,0),-max(pt.y,0)]])
|
||||
)
|
||||
os_profile(deduplicate(move(-points[1],p=select(points,1,-1))), extra,check_valid,quality,offset_maxstep,offset);
|
||||
os_profile(deduplicate(move(-points[1],p=list_tail(points))), extra,check_valid,quality,offset_maxstep,offset);
|
||||
|
||||
|
||||
// Module: convex_offset_extrude()
|
||||
|
@ -1248,8 +1247,8 @@ module convex_offset_extrude(
|
|||
offsets_top = _rounding_offsets(top, 1);
|
||||
|
||||
// "Extra" height enlarges the result beyond the requested height, so subtract it
|
||||
bottom_height = len(offsets_bot)==0 ? 0 : abs(select(offsets_bot,-1)[1]) - struct_val(bottom,"extra");
|
||||
top_height = len(offsets_top)==0 ? 0 : abs(select(offsets_top,-1)[1]) - struct_val(top,"extra");
|
||||
bottom_height = len(offsets_bot)==0 ? 0 : abs(last(offsets_bot)[1]) - struct_val(bottom,"extra");
|
||||
top_height = len(offsets_top)==0 ? 0 : abs(last(offsets_top)[1]) - struct_val(top,"extra");
|
||||
|
||||
height = one_defined([l,h,height], "l,h,height", dflt=u_add(bottom_height,top_height));
|
||||
assert(height>=0, "Height must be nonnegative");
|
||||
|
@ -1583,11 +1582,11 @@ function _stroke_end(width,left, right, spec) =
|
|||
cutright = cut[1],
|
||||
// Create updated paths taking into account clipping for end rotation
|
||||
newright = intright?
|
||||
concat([pathclip[0]],select(right,pathclip[1],-1)) :
|
||||
concat([pathextend],select(right,1,-1)),
|
||||
concat([pathclip[0]],list_tail(right,pathclip[1])) :
|
||||
concat([pathextend],list_tail(right)),
|
||||
newleft = !intright?
|
||||
concat([pathclip[0]],select(left,pathclip[1],-1)) :
|
||||
concat([pathextend],select(left,1,-1)),
|
||||
concat([pathclip[0]],list_tail(left,pathclip[1])) :
|
||||
concat([pathextend],list_tail(left)),
|
||||
// calculate corner angles, which are different when the cut is negative (outside corner)
|
||||
leftangle = cutleft>=0?
|
||||
vector_angle([newleft[1],newleft[0],newright[0]])/2 :
|
||||
|
@ -1953,11 +1952,11 @@ function rounded_prism(bottom, top, joint_bot=0, joint_top=0, joint_sides=0, k_b
|
|||
function bezier_patch_degenerate(patch, splinesteps=16, reverse=false) =
|
||||
assert(is_num(splinesteps), "splinesteps must be a number")
|
||||
let(
|
||||
top_degen = patch[0][0] == select(patch[0],-1),
|
||||
bot_degen = select(patch,-1)[0] == select(select(patch,-1),-1),
|
||||
left_degen = patch[0][0] == select(patch,-1)[0],
|
||||
right_degen = select(patch[0],-1) == select(select(patch,-1),-1),
|
||||
samplepts = list_range(splinesteps+1)/splinesteps
|
||||
top_degen = patch[0][0] == last(patch[0]),
|
||||
bot_degen = last(patch)[0] == last(last(patch)),
|
||||
left_degen = patch[0][0] == last(patch)[0],
|
||||
right_degen = last(patch[0]) == last(last(patch)),
|
||||
samplepts = range(splinesteps+1)/splinesteps
|
||||
)
|
||||
top_degen && bot_degen && left_degen && right_degen ? // fully degenerate case
|
||||
[repeat([patch[0][0]],4), EMPTY_VNF] :
|
||||
|
@ -1965,19 +1964,19 @@ function bezier_patch_degenerate(patch, splinesteps=16, reverse=false) =
|
|||
let(
|
||||
pts = bezier_points(subindex(patch,0), samplepts)
|
||||
)
|
||||
[[pts,pts,[pts[0]],[select(pts,-1)]], EMPTY_VNF] :
|
||||
[[pts,pts,[pts[0]],[last(pts)]], EMPTY_VNF] :
|
||||
left_degen && right_degen ? // double degenerate case (sides)
|
||||
let(
|
||||
pts = bezier_points(patch[0], samplepts)
|
||||
)
|
||||
[[[pts[0]], [select(pts,-1)], pts, pts], EMPTY_VNF] :
|
||||
[[[pts[0]], [last(pts)], pts, pts], EMPTY_VNF] :
|
||||
!top_degen && !bot_degen ? // non-degenerate case
|
||||
let(
|
||||
k=echo("non-degenerate case"),
|
||||
pts = bezier_patch_points(patch, samplepts, samplepts)
|
||||
)
|
||||
[
|
||||
[subindex(pts,0), subindex(pts,len(pts)-1), pts[0], select(pts,-1)],
|
||||
[subindex(pts,0), subindex(pts,len(pts)-1), pts[0], last(pts)],
|
||||
vnf_vertex_array(pts, reverse=reverse)
|
||||
] :
|
||||
bot_degen ? // only bottom is degenerate
|
||||
|
@ -1990,22 +1989,22 @@ function bezier_patch_degenerate(patch, splinesteps=16, reverse=false) =
|
|||
] :
|
||||
// at this point top_degen is true // only top is degenerate
|
||||
let(
|
||||
full_degen = patch[1][0] == select(patch[1],-1),
|
||||
rowmax = full_degen ? list_range(splinesteps+1) :
|
||||
full_degen = patch[1][0] == last(patch[1]),
|
||||
rowmax = full_degen ? range(splinesteps+1) :
|
||||
[for(j=[0:splinesteps]) j<=splinesteps/2 ? 2*j : splinesteps],
|
||||
vbb=echo("single degenerate case"),
|
||||
bpatch = [for(i=[0:1:len(patch[0])-1]) bezier_points(subindex(patch,i), samplepts)],
|
||||
pts = [
|
||||
[bpatch[0][0]],
|
||||
for(j=[1:splinesteps]) bezier_points(subindex(bpatch,j), list_range(rowmax[j]+1)/rowmax[j])
|
||||
for(j=[1:splinesteps]) bezier_points(subindex(bpatch,j), range(rowmax[j]+1)/rowmax[j])
|
||||
],
|
||||
vnf = vnf_tri_array(pts, reverse=reverse)
|
||||
) [
|
||||
[
|
||||
subindex(pts,0),
|
||||
[for(row=pts) select(row,-1)],
|
||||
[for(row=pts) last(row)],
|
||||
pts[0],
|
||||
select(pts,-1),
|
||||
last(pts),
|
||||
],
|
||||
vnf
|
||||
];
|
||||
|
|
|
@ -1085,7 +1085,7 @@ function _ISO_thread_tolerance(diameter, pitch, internal=false, tolerance=undef)
|
|||
],
|
||||
|
||||
rangepts = [0.99, 1.4, 2.8, 5.6, 11.2, 22.4, 45, 90, 180, 300],
|
||||
d_ind = floor(lookup(diameter,hstack(rangepts,list_range(len(rangepts))))),
|
||||
d_ind = floor(lookup(diameter,hstack(rangepts,range(len(rangepts))))),
|
||||
avgd = sqrt(rangepts[d_ind]* rangepts[d_ind+1]),
|
||||
|
||||
T_d2_6 = 90*pow(P, 0.4)*pow(avgd,0.1),
|
||||
|
|
|
@ -1339,7 +1339,7 @@ module spheroid(r, style="aligned", d, circum=false, anchor=CENTER, spin=0, orie
|
|||
path = [
|
||||
let(a = merids[0]) [0, sin(a)],
|
||||
for (a=merids) [cos(a), sin(a)],
|
||||
let(a = select(merids,-1)) [0, sin(a)]
|
||||
let(a = last(merids)) [0, sin(a)]
|
||||
];
|
||||
scale(r) rotate(180) rotate_extrude(convexity=2,$fn=sides) polygon(path);
|
||||
} else {
|
||||
|
@ -1441,7 +1441,7 @@ function spheroid(r, style="aligned", d, circum=false, anchor=CENTER, spin=0, or
|
|||
1,
|
||||
],
|
||||
offs = cumsum(meridians),
|
||||
pc = select(offs,-1)-1,
|
||||
pc = last(offs)-1,
|
||||
os = octa_steps * 2
|
||||
) [
|
||||
for (i=[0:1:3]) [0, 1+(i+1)%4, 1+i],
|
||||
|
|
|
@ -209,10 +209,10 @@ module stroke(
|
|||
assert(is_undef(endcap_angle2)||is_num(endcap_angle2));
|
||||
assert(is_undef(joint_angle)||is_num(joint_angle));
|
||||
|
||||
endcap_shape1 = _shape_path(endcap1, select(width,0), endcap_width1, endcap_length1, endcap_extent1);
|
||||
endcap_shape2 = _shape_path(endcap2, select(width,-1), endcap_width2, endcap_length2, endcap_extent2);
|
||||
endcap_shape1 = _shape_path(endcap1, width[0], endcap_width1, endcap_length1, endcap_extent1);
|
||||
endcap_shape2 = _shape_path(endcap2, last(width), endcap_width2, endcap_length2, endcap_extent2);
|
||||
|
||||
trim1 = select(width,0) * first_defined([
|
||||
trim1 = width[0] * first_defined([
|
||||
trim1, trim,
|
||||
(endcap1=="arrow")? endcap_length1-0.01 :
|
||||
(endcap1=="arrow2")? endcap_length1*3/4 :
|
||||
|
@ -220,7 +220,7 @@ module stroke(
|
|||
]);
|
||||
assert(is_num(trim1));
|
||||
|
||||
trim2 = select(width,-1) * first_defined([
|
||||
trim2 = last(width) * first_defined([
|
||||
trim2, trim,
|
||||
(endcap2=="arrow")? endcap_length2-0.01 :
|
||||
(endcap2=="arrow2")? endcap_length2*3/4 :
|
||||
|
@ -244,8 +244,8 @@ module stroke(
|
|||
[lerp(width[epos[0]], width[(epos[0]+1)%len(width)], epos[1])]
|
||||
);
|
||||
|
||||
start_vec = select(path,0) - select(path,1);
|
||||
end_vec = select(path,-1) - select(path,-2);
|
||||
start_vec = path[0] - path[1];
|
||||
end_vec = last(path) - select(path,-2);
|
||||
|
||||
if (len(path[0]) == 2) {
|
||||
// Straight segments
|
||||
|
@ -301,7 +301,7 @@ module stroke(
|
|||
}
|
||||
|
||||
// Endcap2
|
||||
translate(select(path,-1)) {
|
||||
translate(last(path)) {
|
||||
mat = is_undef(endcap_angle2)? rot(from=BACK,to=end_vec) :
|
||||
zrot(endcap_angle2);
|
||||
multmatrix(mat) polygon(endcap_shape2);
|
||||
|
@ -402,9 +402,9 @@ module stroke(
|
|||
}
|
||||
|
||||
// Endcap2
|
||||
translate(select(path,-1)) {
|
||||
multmatrix(select(rotmats,-1)) {
|
||||
$fn = select(sides,-1);
|
||||
translate(last(path)) {
|
||||
multmatrix(last(rotmats)) {
|
||||
$fn = last(sides);
|
||||
if (is_undef(endcap_angle2)) {
|
||||
rotate_extrude(convexity=convexity) {
|
||||
right_half(planar=true) {
|
||||
|
@ -413,7 +413,7 @@ module stroke(
|
|||
}
|
||||
} else {
|
||||
rotate([90,0,endcap_angle2]) {
|
||||
linear_extrude(height=max(select(widths,-1),0.001), center=true, convexity=convexity) {
|
||||
linear_extrude(height=max(last(widths),0.001), center=true, convexity=convexity) {
|
||||
polygon(endcap_shape2);
|
||||
}
|
||||
}
|
||||
|
@ -533,7 +533,7 @@ module dashed_stroke(path, dashpat=[3,3], width=1, closed=false) {
|
|||
function arc(N, r, angle, d, cp, points, width, thickness, start, wedge=false, long=false, cw=false, ccw=false, endpoint=true) =
|
||||
assert(is_bool(endpoint))
|
||||
!endpoint ? assert(!wedge, "endpoint cannot be false if wedge is true")
|
||||
slice(arc(N,r,angle,d,cp,points,width,thickness,start,wedge,long,cw,ccw,true),0,-2) :
|
||||
list_head(arc(N+1,r,angle,d,cp,points,width,thickness,start,wedge,long,cw,ccw,true)) :
|
||||
assert(is_undef(N) || is_integer(N), "Number of points must be an integer")
|
||||
// First try for 2D arc specified by width and thickness
|
||||
is_def(width) && is_def(thickness)? (
|
||||
|
@ -791,7 +791,7 @@ function _turtle_command(command, parm, parm2, state, index) =
|
|||
chvec = !in_list(command,needvec) || is_vector(parm,2),
|
||||
chnum = !in_list(command,neednum) || is_num(parm),
|
||||
vec_or_num = !in_list(command,needeither) || (is_num(parm) || is_vector(parm,2)),
|
||||
lastpt = select(state[path],-1)
|
||||
lastpt = last(state[path])
|
||||
)
|
||||
assert(chvec,str("\"",command,"\" requires a vector parameter at index ",index))
|
||||
assert(chnum,str("\"",command,"\" requires a numeric parameter at index ",index))
|
||||
|
@ -1875,7 +1875,7 @@ function reuleaux_polygon(N=3, r, d, anchor=CENTER, spin=0) =
|
|||
sa = ca + 180 + (90/N),
|
||||
ea = ca + 180 - (90/N),
|
||||
cp = polar_to_xy(r, ca)
|
||||
) each arc(N=ssegs, r=slen, cp=cp, angle=[sa,ea], endpoint=false)
|
||||
) each arc(N=ssegs-1, r=slen, cp=cp, angle=[sa,ea], endpoint=false)
|
||||
],
|
||||
anchors = [
|
||||
for (i = [0:1:N-1]) let(
|
||||
|
@ -2339,8 +2339,8 @@ function mask2d_ogee(pattern, excess, anchor=CENTER, spin=0) =
|
|||
0
|
||||
)
|
||||
])),
|
||||
tot_x = select(x,-1),
|
||||
tot_y = select(y,-1),
|
||||
tot_x = last(x),
|
||||
tot_y = last(y),
|
||||
data = [
|
||||
for (i=idx(pattern,step=2)) let(
|
||||
type = pattern[i],
|
||||
|
|
26
skin.scad
26
skin.scad
|
@ -444,8 +444,8 @@ function skin(profiles, slices, refine=1, method="direct", sampling, caps, close
|
|||
// Define this to be 1 if a profile is used on either side by a resampling method, zero otherwise.
|
||||
profile_resampled = [for(i=idx(profiles))
|
||||
1-(
|
||||
i==0 ? method_type[0] * (closed? select(method_type,-1) : 1) :
|
||||
i==len(profiles)-1 ? select(method_type,-1) * (closed ? select(method_type,-2) : 1) :
|
||||
i==0 ? method_type[0] * (closed? last(method_type) : 1) :
|
||||
i==len(profiles)-1 ? last(method_type) * (closed ? select(method_type,-2) : 1) :
|
||||
method_type[i] * method_type[i-1])],
|
||||
parts = search(1,[1,for(i=[0:1:len(profile_resampled)-2]) profile_resampled[i]!=profile_resampled[i+1] ? 1 : 0],0),
|
||||
plen = [for(i=idx(parts)) (i== len(parts)-1? len(refined_len) : parts[i+1]) - parts[i]],
|
||||
|
@ -534,8 +534,8 @@ function _skin_core(profiles, caps) =
|
|||
prof1 = profiles[0]
|
||||
) [[for (i=idx(prof1)) plens[0]-1-i]],
|
||||
secondcap = !caps[1] ? [] : let(
|
||||
prof2 = select(profiles,-1),
|
||||
eoff = sum(select(plens,0,-2))
|
||||
prof2 = last(profiles),
|
||||
eoff = sum(list_head(plens))
|
||||
) [[for (i=idx(prof2)) eoff+i]]
|
||||
) [vertices, concat(sidefaces,firstcap,secondcap)];
|
||||
|
||||
|
@ -812,7 +812,7 @@ function _skin_tangent_match(poly1, poly2) =
|
|||
small = swap ? poly2 : poly1,
|
||||
curve_offset = centroid(small)-centroid(big),
|
||||
cutpts = [for(i=[0:len(small)-1]) _find_one_tangent(big, select(small,i,i+1),curve_offset=curve_offset)],
|
||||
shift = select(cutpts,-1)+1,
|
||||
shift = last(cutpts)+1,
|
||||
newbig = polygon_shift(big, shift),
|
||||
repeat_counts = [for(i=[0:len(small)-1]) posmod(cutpts[i]-select(cutpts,i-1),len(big))],
|
||||
newsmall = repeat_entries(small,repeat_counts)
|
||||
|
@ -894,7 +894,7 @@ function associate_vertices(polygons, split, curpoly=0) =
|
|||
str("Split ",cursplit," at polygon ",curpoly," has invalid vertices. Must be in [0:",polylen-1,"]"))
|
||||
len(cursplit)==0 ? associate_vertices(polygons,split,curpoly+1) :
|
||||
let(
|
||||
splitindex = sort(concat(list_range(polylen), cursplit)),
|
||||
splitindex = sort(concat(range(polylen), cursplit)),
|
||||
newpoly = [for(i=[0:len(polygons)-1]) i<=curpoly ? select(polygons[i],splitindex) : polygons[i]]
|
||||
)
|
||||
associate_vertices(newpoly, split, curpoly+1);
|
||||
|
@ -970,10 +970,10 @@ function sweep(shape, transforms, closed=false, caps) =
|
|||
rtrans = reverse(transforms),
|
||||
vnfs = [
|
||||
for (rgn=regions) each [
|
||||
for (path=select(rgn,0,-1))
|
||||
for (path=rgn)
|
||||
sweep(path, transforms, closed=closed, caps=false),
|
||||
if (fullcaps[0]) region_faces(rgn, transform=transforms[0], reverse=true),
|
||||
if (fullcaps[1]) region_faces(rgn, transform=select(transforms,-1)),
|
||||
if (fullcaps[1]) region_faces(rgn, transform=last(transforms)),
|
||||
],
|
||||
],
|
||||
vnf = vnf_merge(vnfs)
|
||||
|
@ -1311,7 +1311,7 @@ function path_sweep(shape, path, method="incremental", normal, closed=false, twi
|
|||
assert(is_integer(symmetry) && symmetry>0, "symmetry must be a positive integer")
|
||||
// let(shape = check_and_fix_path(shape,valid_dim=2,closed=true,name="shape"))
|
||||
assert(is_path(path), "input path is not a path")
|
||||
assert(!closed || !approx(path[0],select(path,-1)), "Closed path includes start point at the end")
|
||||
assert(!closed || !approx(path[0],last(path)), "Closed path includes start point at the end")
|
||||
let(
|
||||
path = path3d(path),
|
||||
caps = is_def(caps) ? caps :
|
||||
|
@ -1350,13 +1350,13 @@ function path_sweep(shape, path, method="incremental", normal, closed=false, twi
|
|||
// X axis. Similarly, in the closed==false case the desired and actual transformations can only differ in the twist,
|
||||
// so we can need to calculate the twist angle so we can apply a correction, which we distribute uniformly over the whole path.
|
||||
reference_rot = closed ? rotations[0] :
|
||||
is_undef(last_normal) ? select(rotations,-1) :
|
||||
is_undef(last_normal) ? last(rotations) :
|
||||
let(
|
||||
last_tangent = select(tangents,-1),
|
||||
last_tangent = last(tangents),
|
||||
lastynormal = last_normal - (last_normal * last_tangent) * last_tangent
|
||||
)
|
||||
affine3d_frame_map(y=lastynormal, z=last_tangent),
|
||||
mismatch = transpose(select(rotations,-1)) * reference_rot,
|
||||
mismatch = transpose(last(rotations)) * reference_rot,
|
||||
correction_twist = atan2(mismatch[1][0], mismatch[0][0]),
|
||||
// Spread out this extra twist over the whole sweep so that it doesn't occur
|
||||
// abruptly as an artifact at the last step.
|
||||
|
@ -1492,7 +1492,7 @@ function _ofs_vmap(ofs,closed=false) =
|
|||
)
|
||||
[
|
||||
for(entry=ofs[1]) _ofs_face_edge(entry,firstlen),
|
||||
if (!closed) _ofs_face_edge(select(ofs[1],-1),firstlen,second=true)
|
||||
if (!closed) _ofs_face_edge(last(ofs[1]),firstlen,second=true)
|
||||
];
|
||||
|
||||
|
||||
|
|
|
@ -335,7 +335,7 @@ function _str_find_last(str,pattern,sindex) =
|
|||
(sindex >=0 ? sindex : undef);
|
||||
|
||||
function _str_find_all(str,pattern) =
|
||||
pattern == "" ? list_range(len(str)) :
|
||||
pattern == "" ? range(len(str)) :
|
||||
[for(i=[0:1:len(str)-len(pattern)]) if (_str_cmp(str,i,pattern)) i];
|
||||
|
||||
|
||||
|
|
|
@ -27,6 +27,20 @@ module test_select() {
|
|||
}
|
||||
test_select();
|
||||
|
||||
|
||||
module test_slice() {
|
||||
l = [3,4,5,6,7,8,9];
|
||||
assert(slice(l, 5, 6) == [8,9]);
|
||||
assert(slice(l, 5, 8) == [8,9]);
|
||||
assert(slice(l, 5, 2) == []);
|
||||
assert(slice(l, -3, -1) == [7,8,9]);
|
||||
assert(slice(l, 3, 3) == [6]);
|
||||
assert(slice(l, 4) == [7,8,9]);
|
||||
assert(slice(l, -2) == [8,9]);
|
||||
}
|
||||
test_slice();
|
||||
|
||||
|
||||
module test_last() {
|
||||
list = [1,2,3,4];
|
||||
assert(last(list)==4);
|
||||
|
@ -110,19 +124,36 @@ module test_repeat() {
|
|||
test_repeat();
|
||||
|
||||
|
||||
module test_list_range() {
|
||||
assert(list_range(4) == [0,1,2,3]);
|
||||
assert(list_range(n=4, step=2) == [0,2,4,6]);
|
||||
assert(list_range(n=4, s=3, step=3) == [3,6,9,12]);
|
||||
assert(list_range(e=3) == [0,1,2,3]);
|
||||
assert(list_range(e=6, step=2) == [0,2,4,6]);
|
||||
assert(list_range(s=3, e=5) == [3,4,5]);
|
||||
assert(list_range(s=3, e=8, step=2) == [3,5,7]);
|
||||
assert(list_range(s=4, e=8, step=2) == [4,6,8]);
|
||||
assert(list_range(e=4, n=3) == [0,2,4]);
|
||||
assert(list_range(n=4, s=[3,4], step=[2,3]) == [[3,4], [5,7], [7,10], [9,13]]);
|
||||
module test_range() {
|
||||
assert(range(4) == [0,1,2,3]);
|
||||
assert(range(n=4, step=2) == [0,2,4,6]);
|
||||
assert(range(n=4, s=3, step=3) == [3,6,9,12]);
|
||||
assert(range(e=3) == [0,1,2,3]);
|
||||
assert(range(e=6, step=2) == [0,2,4,6]);
|
||||
assert(range(s=3, e=5) == [3,4,5]);
|
||||
assert(range(s=3, e=8, step=2) == [3,5,7]);
|
||||
assert(range(s=4, e=8, step=2) == [4,6,8]);
|
||||
assert(range(e=4, n=3) == [0,2,4]);
|
||||
assert(range(n=4, s=[3,4], step=[2,3]) == [[3,4], [5,7], [7,10], [9,13]]);
|
||||
}
|
||||
test_list_range();
|
||||
test_range();
|
||||
|
||||
|
||||
module test_rangex() {
|
||||
assert_equal(rangex(4), [0,1,2,3]);
|
||||
assert_approx(rangex(5,e=1), [0, 0.2, 0.4, 0.6, 0.8]);
|
||||
assert_equal(rangex(n=4, step=2), [0,2,4,6]);
|
||||
assert_approx(rangex(n=4, step=0.25), [0,0.25,0.5,0.75]);
|
||||
assert_equal(rangex(n=4, s=3, step=3), [3,6,9,12]);
|
||||
assert_equal(rangex(e=3), [0,1,2]);
|
||||
assert_equal(rangex(e=6, step=2), [0,2,4]);
|
||||
assert_equal(rangex(s=3, e=5), [3,4]);
|
||||
assert_equal(rangex(s=3, e=8, step=2), [3,5,7]);
|
||||
assert_equal(rangex(s=4, e=8, step=2), [4,6]);
|
||||
assert_equal(rangex(e=6, n=3), [0,2,4]);
|
||||
assert_equal(rangex(n=4, s=[3,4], step=[2,3]), [[3,4], [5,7], [7,10], [9,13]]);
|
||||
}
|
||||
test_rangex();
|
||||
|
||||
|
||||
module test_reverse() {
|
||||
|
@ -282,7 +313,7 @@ test_enumerate();
|
|||
|
||||
|
||||
module test_shuffle() {
|
||||
nums1 = [for (i=list_range(100)) i];
|
||||
nums1 = [for (i=range(100)) i];
|
||||
nums2 = shuffle(nums1,33);
|
||||
nums3 = shuffle(nums2,99);
|
||||
assert(sort(nums2)==nums1);
|
||||
|
|
|
@ -481,7 +481,7 @@ module test_circle_3points() {
|
|||
radii = rands(10,100,count,seed_value=390);
|
||||
angles = rands(0,360,count,seed_value=699);
|
||||
// 2D tests.
|
||||
for(i = list_range(count)) {
|
||||
for(i = range(count)) {
|
||||
cp = select(coords,i,i+1);
|
||||
r = radii[i];
|
||||
angs = sort(select(angles,i,i+2));
|
||||
|
@ -506,7 +506,7 @@ module test_circle_3points() {
|
|||
assert(approx(res[2], UP));
|
||||
}
|
||||
}
|
||||
for(i = list_range(count)) {
|
||||
for(i = range(count)) {
|
||||
cp = select(coords,i,i+1);
|
||||
r = radii[i];
|
||||
angs = sort(select(angles,i,i+2));
|
||||
|
@ -532,7 +532,7 @@ module test_circle_3points() {
|
|||
}
|
||||
}
|
||||
// 3D tests.
|
||||
for(i = list_range(count)) {
|
||||
for(i = range(count)) {
|
||||
cp = select(coords,i,i+2);
|
||||
r = radii[i];
|
||||
nrm = unit(select(coords,i+10,i+12));
|
||||
|
@ -559,7 +559,7 @@ module test_circle_3points() {
|
|||
assert(approx(res[2], n));
|
||||
}
|
||||
}
|
||||
for(i = list_range(count)) {
|
||||
for(i = range(count)) {
|
||||
cp = select(coords,i,i+2);
|
||||
r = radii[i];
|
||||
nrm = unit(select(coords,i+10,i+12));
|
||||
|
@ -987,8 +987,8 @@ module test_pointlist_bounds() {
|
|||
|
||||
|
||||
module test_closest_point() {
|
||||
ptlist = [for (i=list_range(100)) rands(-100,100,2,seed_value=8463)];
|
||||
testpts = [for (i=list_range(100)) rands(-100,100,2,seed_value=6834)];
|
||||
ptlist = [for (i=range(100)) rands(-100,100,2,seed_value=8463)];
|
||||
testpts = [for (i=range(100)) rands(-100,100,2,seed_value=6834)];
|
||||
for (pt = testpts) {
|
||||
pidx = closest_point(pt,ptlist);
|
||||
dists = [for (p=ptlist) norm(pt-p)];
|
||||
|
@ -1000,8 +1000,8 @@ module test_closest_point() {
|
|||
|
||||
|
||||
module test_furthest_point() {
|
||||
ptlist = [for (i=list_range(100)) rands(-100,100,2,seed_value=8463)];
|
||||
testpts = [for (i=list_range(100)) rands(-100,100,2,seed_value=6834)];
|
||||
ptlist = [for (i=range(100)) rands(-100,100,2,seed_value=8463)];
|
||||
testpts = [for (i=range(100)) rands(-100,100,2,seed_value=6834)];
|
||||
for (pt = testpts) {
|
||||
pidx = furthest_point(pt,ptlist);
|
||||
dists = [for (p=ptlist) norm(pt-p)];
|
||||
|
|
|
@ -299,15 +299,6 @@ module test_modang() {
|
|||
test_modang();
|
||||
|
||||
|
||||
module test_modrange() {
|
||||
assert_equal(modrange(-5,5,3), [1,2]);
|
||||
assert_equal(modrange(-1,4,3), [2,0,1]);
|
||||
assert_equal(modrange(1,8,10,step=2), [1,3,5,7]);
|
||||
assert_equal(modrange(5,12,10,step=2), [5,7,9,1]);
|
||||
}
|
||||
test_modrange();
|
||||
|
||||
|
||||
module test_sqr() {
|
||||
assert_equal(sqr(-3), 9);
|
||||
assert_equal(sqr(0), 0);
|
||||
|
@ -738,11 +729,14 @@ module test_any() {
|
|||
assert_equal(any([0,false,undef]), false);
|
||||
assert_equal(any([1,false,undef]), true);
|
||||
assert_equal(any([1,5,true]), true);
|
||||
assert_equal(any([[0,0], [0,0]]), false);
|
||||
assert_equal(any([[0,0], [0,0]]), true);
|
||||
assert_equal(any([[0,0], [1,0]]), true);
|
||||
assert_equal(any([[false,false],[[false,[false],[[[true]]]],false],[false,false]]), true);
|
||||
assert_equal(any([[false,false],[[false,[false],[[[false]]]],false],[false,false]]), false);
|
||||
assert_equal(any([[false,false],[[false,[false],[[[false]]]],false],[false,false]]), true);
|
||||
assert_equal(any([]), false);
|
||||
assert_equal(any([1,3,5,7,9], function (a) a%2==0),false);
|
||||
assert_equal(any([1,3,6,7,9], function (a) a%2==0),true);
|
||||
assert_equal(any([1,3,5,7,9], function (a) a%2!=0),true);
|
||||
}
|
||||
test_any();
|
||||
|
||||
|
@ -751,12 +745,15 @@ module test_all() {
|
|||
assert_equal(all([0,false,undef]), false);
|
||||
assert_equal(all([1,false,undef]), false);
|
||||
assert_equal(all([1,5,true]), true);
|
||||
assert_equal(all([[0,0], [0,0]]), false);
|
||||
assert_equal(all([[0,0], [1,0]]), false);
|
||||
assert_equal(all([[0,0], [0,0]]), true);
|
||||
assert_equal(all([[0,0], [1,0]]), true);
|
||||
assert_equal(all([[1,1], [1,1]]), true);
|
||||
assert_equal(all([[true,true],[[true,[true],[[[true]]]],true],[true,true]]), true);
|
||||
assert_equal(all([[true,true],[[true,[true],[[[false]]]],true],[true,true]]), false);
|
||||
assert_equal(all([[true,true],[[true,[true],[[[true]]]],true],[true,true]]), true);
|
||||
assert_equal(all([[true,true],[[true,[true],[[[false]]]],true],[true,true]]), true);
|
||||
assert_equal(all([]), true);
|
||||
assert_equal(all([1,3,5,7,9], function (a) a%2==0),false);
|
||||
assert_equal(all([1,3,6,8,9], function (a) a%2==0),false);
|
||||
assert_equal(all([1,3,5,7,9], function (a) a%2!=0),true);
|
||||
}
|
||||
test_all();
|
||||
|
||||
|
@ -770,6 +767,9 @@ module test_count_true() {
|
|||
assert_equal(count_true([[0,0], [1,0]]), 2);
|
||||
assert_equal(count_true([[1,1], [1,1]]), 2);
|
||||
assert_equal(count_true([1,1,1,1,1], nmax=3), 3);
|
||||
assert_equal(count_true([1,3,5,7,9], function (a) a%2==0),0);
|
||||
assert_equal(count_true([1,3,6,8,9], function (a) a%2==0),2);
|
||||
assert_equal(count_true([1,3,5,7,9], function (a) a%2!=0),5);
|
||||
}
|
||||
test_count_true();
|
||||
|
||||
|
@ -789,6 +789,7 @@ module test_factorial() {
|
|||
}
|
||||
test_factorial();
|
||||
|
||||
|
||||
module test_binomial() {
|
||||
assert_equal(binomial(1), [1,1]);
|
||||
assert_equal(binomial(2), [1,2,1]);
|
||||
|
@ -797,6 +798,7 @@ module test_binomial() {
|
|||
}
|
||||
test_binomial();
|
||||
|
||||
|
||||
module test_binomial_coefficient() {
|
||||
assert_equal(binomial_coefficient(2,1), 2);
|
||||
assert_equal(binomial_coefficient(3,2), 3);
|
||||
|
@ -815,8 +817,8 @@ module test_gcd() {
|
|||
assert_equal(gcd(39, 101),1);
|
||||
assert_equal(gcd(15,-25), 5);
|
||||
assert_equal(gcd(-15,25), 5);
|
||||
assert_equal(gcd(5,0),5);
|
||||
assert_equal(gcd(0,5),5);
|
||||
assert_equal(gcd(5,0), 5);
|
||||
assert_equal(gcd(0,5), 5);
|
||||
}
|
||||
test_gcd();
|
||||
|
||||
|
@ -830,6 +832,15 @@ module test_lcm() {
|
|||
test_lcm();
|
||||
|
||||
|
||||
|
||||
module test_complex(){
|
||||
assert_equal( complex(ident(4)), c_ident(4));
|
||||
assert_equal( complex(3), [3,0]);
|
||||
assert_equal( complex([1,2]), [[1,0],[2,0]]);
|
||||
assert_equal( complex([[1,2],[3,4]]), [[ [1,0],[2,0] ], [ [3,0],[4,0]]]);
|
||||
}
|
||||
test_complex();
|
||||
|
||||
module test_c_mul() {
|
||||
assert_equal(c_mul([4,5],[9,-4]), [56,29]);
|
||||
assert_equal(c_mul([-7,2],[24,3]), [-174, 27]);
|
||||
|
@ -1122,7 +1133,7 @@ module test_real_roots(){
|
|||
// Wilkinson polynomial is a nasty test:
|
||||
assert_approx(
|
||||
sort(real_roots(poly_mult([[1,-1],[1,-2],[1,-3],[1,-4],[1,-5],[1,-6],[1,-7],[1,-8],[1,-9],[1,-10]]))),
|
||||
list_range(n=10,s=1));
|
||||
range(n=10,s=1));
|
||||
assert_equal(real_roots([3]), []);
|
||||
assert_equal(real_roots(poly_mult([[1,-2,5],[12,-24,24],[-2, -12, -20],[1,-10,50]])),[]);
|
||||
assert_equal(real_roots(poly_mult([[1,-2,5],[12,-24,24],[-2, -12, -20],[1,-10,50],[1,0,0]])),[0,0]);
|
||||
|
|
42
tests/test_rounding.scad
Normal file
42
tests/test_rounding.scad
Normal file
|
@ -0,0 +1,42 @@
|
|||
include <../std.scad>
|
||||
include <../rounding.scad>
|
||||
|
||||
module test_round_corners() {
|
||||
|
||||
test1 = turtle(["move", 10, "move", 10, "left", 30, "move", 17, "left", 155, "move", 10, "right", 90, "move", 10,"left", 90, "move", 30]);
|
||||
test2 = turtle(["move", 20, "left", 30, "move", 17, "left", 155, "move", 10, "right", 90, "move", 10,"left", 90, "move", 30]);
|
||||
|
||||
assert_approx(round_corners(test2, cut=.5, $fn=8,closed=true),
|
||||
[[-0.606288551887,1.51404651037],[0.280235443937,0.414087063263],[1.63092692777,0],[16.2021229436,0],[19.8705904774,0.482962913145],[23.2890580113,1.89893852818],[34.1829092195,8.18850645576],[34.2505074296,8.28809878531],[34.2143124553,8.40289457772],[34.1018154298,8.44570309758],[25.9629982589,7.73364886061],[25.0818786883,8.01146479408],[24.6552785953,8.83095594797],[23.994133744,16.387876178],[23.5675336511,17.2073673319],[22.6864140805,17.4851832654],[-5.00680751277,15.0623403194],[-5.5709968443,14.7138107731],[-5.62744081644,14.0530562616]]);
|
||||
|
||||
assert_approx(round_corners(test2, cut=.5, $fn=8,closed=false),
|
||||
[[0,0],[16.2021229436,0],[19.8705904774,0.482962913145],[23.2890580113,1.89893852818],[34.1829092195,8.18850645576],[34.2505074296,8.28809878531],[34.2143124553,8.40289457772],[34.1018154298,8.44570309758],[25.9629982589,7.73364886061],[25.0818786883,8.01146479408],[24.6552785953,8.83095594797],[23.994133744,16.387876178],[23.5675336511,17.2073673319],[22.6864140805,17.4851832654],[-5.99691348681,14.975717271]]);
|
||||
|
||||
assert_approx(round_corners(test2, radius=[9,1.5,1.5,3], $fn=8,closed=false),[[0,0],[17.5884572681,0],[19.917828674,0.306667563398],[22.0884572681,1.20577136594],[28.8628496345,5.11696862225],[29.5970120924,6.19860893897],[29.2039100968,7.44536918473],[27.9821160204,7.91029877507],[26.2547769306,7.75917618664],[25.1598619019,8.1044015691],[24.6297512693,9.12273461966],[24.1503946842,14.6018054592],[23.090173419,16.6384715603],[20.9003433617,17.3289223252],[-5.99691348681,14.975717271]]);
|
||||
|
||||
assert_approx(round_corners(test2, radius=[0,9,1.5,1.5,3,0], $fn=8,closed=false),[[0,0],[17.5884572681,0],[19.917828674,0.306667563398],[22.0884572681,1.20577136594],[28.8628496345,5.11696862225],[29.5970120924,6.19860893897],[29.2039100968,7.44536918473],[27.9821160204,7.91029877507],[26.2547769306,7.75917618664],[25.1598619019,8.1044015691],[24.6297512693,9.12273461966],[24.1503946842,14.6018054592],[23.090173419,16.6384715603],[20.9003433617,17.3289223252],[-5.99691348681,14.975717271]]);
|
||||
|
||||
assert_approx(round_corners(test2, radius=[4,9,1.5,1.5,3,5], $fn=8,closed=true), [[-1.00632035384,2.51302092923],[0.46513599873,0.687303493898],[2.70701955022,0],[17.5884572681,0],[19.917828674,0.306667563398],[22.0884572681,1.20577136594],[28.8628496345,5.11696862225],[29.5970120924,6.19860893897],[29.2039100968,7.44536918473],[27.9821160204,7.91029877507],[26.2547769306,7.75917618664],[25.1598619019,8.1044015691],[24.6297512693,9.12273461966],[24.1503946842,14.6018054592],[23.090173419,16.6384715603],[20.9003433617,17.3289223252],[0.712818162855,15.5627427257],[-3.11056954856,13.2008342139],[-3.49307800348,8.72304539674]]);
|
||||
|
||||
assert_approx(round_corners(test1, radius=[4,0,9,1.5,1.5,3,5], $fn=8, closed=true),[[-1.00632035384,2.51302092923],[0.46513599873,0.687303493898],[2.70701955022,0],[10,0],[17.5884572681,0],[19.917828674,0.306667563398],[22.0884572681,1.20577136594],[28.8628496345,5.11696862225],[29.5970120924,6.19860893897],[29.2039100968,7.44536918473],[27.9821160204,7.91029877507],[26.2547769306,7.75917618664],[25.1598619019,8.1044015691],[24.6297512693,9.12273461966],[24.1503946842,14.6018054592],[23.090173419,16.6384715603],[20.9003433617,17.3289223252],[0.712818162855,15.5627427257],[-3.11056954856,13.2008342139],[-3.49307800348,8.72304539674]]);
|
||||
|
||||
assert_approx(round_corners(test1, joint=3, $fn=8, closed=true),[[-1.11523430308,2.78500492805],[0.515477620423,0.76169028093],[3,0],[7,0],[13,0],[17,0],[19.8977774789,0.381499642545],[22.5980762114,1.5],[32.124355653,7],[32.4498754498,7.47958777023],[32.2755782213,8.03238795439],[31.7338477701,8.23853277176],[27.7490689777,7.88990980077],[25.5592389204,8.58036056568],[24.4990176552,10.6170266668],[24.1503946842,14.6018054592],[23.090173419,16.6384715603],[20.9003433617,17.3289223252],[-3.00832939254,15.2371844993],[-4.71130594915,14.1851659419],[-4.88167918373,12.190712343]]);
|
||||
|
||||
assert_approx(round_corners(test1, joint=3, $fn=8, method="chamfer", closed=true), [[-1.11523430308,2.78500492805],[3,0],[7,0],[13,0],[17,0],[22.5980762114,1.5],[32.124355653,7],[31.7338477701,8.23853277176],[27.7490689777,7.88990980077],[24.4990176552,10.6170266668],[24.1503946842,14.6018054592],[20.9003433617,17.3289223252],[-3.00832939254,15.2371844993],[-4.88167918373,12.190712343]]);
|
||||
|
||||
assert_approx(round_corners(test1, joint=3, $fn=4, method="smooth", closed=true),[[-1.11523430308,2.78500492805],[-0.506080589514,1.46865494252],[0.353393568173,0.522188424009],[1.55153656203,0.0761524785012],[3,0],[7,0],[8.5,0],[10,0],[11.5,0],[13,0],[17,0],[18.4890098964,0.041015625],[19.9246392896,0.28125],[21.2880480021,0.791015625],[22.5980762114,1.5],[32.124355653,7],[33.2706335159,7.70183488048],[33.674933057,8.1697248947],[33.0753795745,8.32110126636],[31.7338477701,8.23853277176],[27.7490689777,7.88990980077],[26.3293465324,7.8480447775],[25.2718192958,8.2378271955],[24.7043208711,9.21160321051],[24.4990176552,10.6170266668],[24.1503946842,14.6018054592],[23.9450914683,16.0072289155],[23.3775930436,16.9810049305],[22.320065807,17.3707873485],[20.9003433617,17.3289223252],[-3.00832939254,15.2371844993],[-4.39040765537,15.0374479012],[-5.22744753731,14.5025539523],[-5.32708255097,13.514211823],[-4.88167918373,12.190712343]]);
|
||||
|
||||
assert_approx(round_corners(test1, joint=3, $fn=4, method="smooth", k=.1, closed=true), [[-1.11523430308,2.78500492805],[-0.374134800869,0.998685360916],[0.164916998481,0.243687931204],[1.06619720521,0.0239336361004],[3,0],[7,0],[8.95,0],[10,0],[11.05,0],[13,0],[17,0],[18.9465459674,0.012890625],[19.9648316685,0.13125],[20.9058726414,0.537890625],[22.5980762114,1.5],[32.124355653,7],[33.7650948284,7.95986239101],[34.2335990876,8.34587161753],[33.6284170693,8.39334886112],[31.7338477701,8.23853277176],[27.7490689777,7.88990980077],[25.829925477,7.74788623096],[24.9991076092,7.91282206324],[24.6924075141,8.70237713407],[24.4990176552,10.6170266668],[24.1503946842,14.6018054592],[23.9570048253,16.5164549919],[23.6503047302,17.3060100627],[22.8194868624,17.470945895],[20.9003433617,17.3289223252],[-3.00832939254,15.2371844993],[-4.91564186446,15.0455441488],[-5.63782937704,14.7549077223],[-5.57131429138,13.9792788941],[-4.88167918373,12.190712343]]);
|
||||
|
||||
assert_approx(round_corners(test1, joint=[3,0,3,5,2,2,4], $fn=4, method="smooth", k=[.8,0,.7,.5,0,.4,1], closed=true),[[-1.11523430308,2.78500492805],[-0.605039930997,1.82113212873],[0.494750995442,0.731063793612],[1.91554107964,0.115316610302],[3,0],[10,0],[17,0],[18.2602418609,0.055078125],[19.9045431002,0.35625],[21.4791356824,0.917578125],[22.5980762114,1.5],[30.3923048454,6],[32.3027679503,7.1697248008],[32.9766005188,7.94954149117],[31.9773447146,8.20183544393],[29.7414583739,8.06422128626],[26.7528742796,7.80275405802],[25.3902084366,7.69137858706],[24.8741147528,7.76386137763],[24.713114411,8.25952793415],[24.5861733979,9.62083196871],[24.0632389414,15.5980001573],[23.9283556903,16.6198201409],[23.5934897955,17.2383006602],[22.9262565325,17.4606811745],[21.8965380598,17.4160780679],[-2.01213469444,15.324340242],[-2.97951536307,15.0445310318],[-4.28698915458,13.9242432294],[-4.69675267167,12.2519315552],[-4.50993441604,11.262377367]]);
|
||||
|
||||
assert_approx(round_corners(test2, cut=.6, $fn=4, method="smooth", k=[.8,.7,.5,0,.4,1], closed=true), [[-0.758025389489,1.89296943206],[-0.411245984888,1.23782454268],[0.336282532724,0.496904475915],[1.30199436026,0.0783807655462],[2.03910170463,0],[15.1195326672,0],[17.1697224117,0.0896023299387],[19.8447085729,0.579555495773],[22.4062911263,1.49273668813],[24.2266086926,2.44023366641],[33.3031485364,7.68057638855],[33.9293399672,8.06397643682],[34.1502016939,8.3195765203],[33.8226761739,8.40227076906],[33.0898209499,8.35716505304],[31.5228787369,8.220075373],[26.897837498,7.84205448929],[25.1461574492,8.08806923839],[24.5997041686,9.77041731835],[24.1688520829,14.390836426],[24.116478533,14.9894688406],[23.9403981733,16.3233811597],[23.5032548901,17.1307628876],[22.6322299704,17.4210646163],[21.2880067431,17.3628384763],[-4.80585474409,15.0799214086],[-5.0950068559,14.9962858485],[-5.48581351579,14.6614294736],[-5.6082926909,14.161572005],[-5.55245232227,13.8657921816]]);
|
||||
|
||||
assert_approx(round_corners([[0,0],[10,0],[10,10]], cut=1, method="chamfer",closed=false), [[0,0],[8.58578643763,0],[10,1.41421356237],[10,10]]);
|
||||
|
||||
}
|
||||
test_round_corners();
|
||||
|
||||
|
||||
|
||||
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap
|
|
@ -40,7 +40,7 @@ test_turtle();
|
|||
|
||||
module test_arc() {
|
||||
assert_approx(arc(N=8, d=100, angle=135, cp=[10,10]), [[60,10],[57.1941665154,26.5139530978],[49.0915741234,41.1744900929],[36.6016038258,52.3362099614],[21.1260466978,58.7463956091],[4.40177619483,59.6856104947],[-11.6941869559,55.0484433951],[-25.3553390593,45.3553390593]]);
|
||||
assert_approx(arc(N=8, d=100, angle=135, cp=[10,10],endpoint=false), [[60,10],[57.1941665154,26.5139530978],[49.0915741234,41.1744900929],[36.6016038258,52.3362099614],[21.1260466978,58.7463956091],[4.40177619483,59.6856104947],[-11.6941869559,55.0484433951]]);
|
||||
assert_approx(arc(N=8, d=100, angle=135, cp=[10,10],endpoint=false), [[60,10],[57.8470167866,24.5142338627],[51.5734806151,37.778511651],[41.7196642082,48.6505226681],[29.1341716183,56.1939766256],[14.9008570165,59.7592363336],[0.245483899194,59.0392640202],[-13.5698368413,54.0960632174]]);
|
||||
assert_approx(arc(N=8, d=100, angle=[45,225], cp=[10,10]), [[45.3553390593,45.3553390593],[26.5139530978,57.1941665154],[4.40177619483,59.6856104947],[-16.6016038258,52.3362099614],[-32.3362099614,36.6016038258],[-39.6856104947,15.5982238052],[-37.1941665154,-6.51395309776],[-25.3553390593,-25.3553390593]]);
|
||||
assert_approx(arc(N=8, d=100, start=45, angle=135, cp=[10,10]), [[45.3553390593,45.3553390593],[31.6941869559,55.0484433951],[15.5982238052,59.6856104947],[-1.12604669782,58.7463956091],[-16.6016038258,52.3362099614],[-29.0915741234,41.1744900929],[-37.1941665154,26.5139530978],[-40,10]]);
|
||||
assert_approx(arc(N=8, d=100, start=45, angle=-90, cp=[10,10]), [[45.3553390593,45.3553390593],[52.3362099614,36.6016038258],[57.1941665154,26.5139530978],[59.6856104947,15.5982238052],[59.6856104947,4.40177619483],[57.1941665154,-6.51395309776],[52.3362099614,-16.6016038258],[45.3553390593,-25.3553390593]]);
|
||||
|
|
|
@ -228,13 +228,13 @@ module trapezoidal_threaded_rod(
|
|||
zrot(i*360/starts, p=vnf_vertex_array(thread_verts, reverse=left_handed)),
|
||||
for (i=[0:1:starts-1]) let(
|
||||
rmat = zrot(i*360/starts),
|
||||
pts = deduplicate(select(thread_verts[0], 0, len(prof3d)+1)),
|
||||
pts = deduplicate(list_head(thread_verts[0], len(prof3d)+1)),
|
||||
faces = [for (i=idx(pts,e=-2)) [0, i+1, i]],
|
||||
rfaces = left_handed? [for (x=faces) reverse(x)] : faces
|
||||
) [apply(rmat,pts), rfaces],
|
||||
for (i=[0:1:starts-1]) let(
|
||||
rmat = zrot(i*360/starts),
|
||||
pts = deduplicate(select(last(thread_verts), -len(prof3d)-2, -1)),
|
||||
pts = deduplicate(list_tail(last(thread_verts), -len(prof3d)-2)),
|
||||
faces = [for (i=idx(pts,e=-2)) [len(pts)-1, i, i+1]],
|
||||
rfaces = left_handed? [for (x=faces) reverse(x)] : faces
|
||||
) [apply(rmat,pts), rfaces]
|
||||
|
|
|
@ -479,7 +479,7 @@ function _tupdate(state, tran, pretran) =
|
|||
[
|
||||
concat(state[0],tran),
|
||||
concat(state[1],pretran),
|
||||
each select(state,2,-1)
|
||||
each list_tail(state,2)
|
||||
];
|
||||
|
||||
function _turtle3d_command(command, parm, parm2, state, index) =
|
||||
|
@ -504,8 +504,8 @@ function _turtle3d_command(command, parm, parm2, state, index) =
|
|||
chnum = (!in_list(command,neednum) || is_num(parm))
|
||||
&& (!in_list(command,numornothing) || (is_undef(parm) || is_num(parm))),
|
||||
chtran = !in_list(command,needtran) || is_matrix(parm,4,4),
|
||||
lastT = select(state[trlist],-1),
|
||||
lastPre = select(state[prelist],-1),
|
||||
lastT = last(state[trlist]),
|
||||
lastPre = last(state[prelist]),
|
||||
lastpt = apply(lastT,[0,0,0])
|
||||
)
|
||||
assert(chvec,str("\"",command,"\" requires a 3d vector parameter at index ",index))
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
BOSL_VERSION = [2,0,595];
|
||||
BOSL_VERSION = [2,0,600];
|
||||
|
||||
|
||||
// Section: BOSL Library Version Functions
|
||||
|
|
16
vnf.scad
16
vnf.scad
|
@ -316,7 +316,7 @@ function vnf_vertex_array(
|
|||
)
|
||||
]
|
||||
)
|
||||
rows<=1 || cols<=1 ? vnf :
|
||||
rows<=1 || cols<=1 ? vnf :
|
||||
vnf_merge(cleanup=true, [
|
||||
vnf, [
|
||||
verts,
|
||||
|
@ -396,7 +396,7 @@ module vnf_polyhedron(vnf, convexity=2, extent=true, cp=[0,0,0], anchor="origin"
|
|||
// Usage:
|
||||
// vnf_wireframe(vnf, <r|d>);
|
||||
// Description:
|
||||
// Given a VNF, creates a wire frame ball-and-stick model of the polyhedron with a cylinder for each edge and a sphere at each vertex.
|
||||
// Given a VNF, creates a wire frame ball-and-stick model of the polyhedron with a cylinder for each edge and a sphere at each vertex.
|
||||
// Arguments:
|
||||
// vnf = A vnf structure
|
||||
// r|d = radius or diameter of the cylinders forming the wire frame. Default: r=1
|
||||
|
@ -404,12 +404,12 @@ module vnf_polyhedron(vnf, convexity=2, extent=true, cp=[0,0,0], anchor="origin"
|
|||
// $fn=32;
|
||||
// ball = sphere(r=20, $fn=6);
|
||||
// vnf_wireframe(ball,d=1);
|
||||
// Example:
|
||||
// Example:
|
||||
// include<BOSL2/polyhedra.scad>
|
||||
// $fn=32;
|
||||
// cube_oct = regular_polyhedron_info("vnf", name="cuboctahedron", or=20);
|
||||
// vnf_wireframe(cube_oct);
|
||||
// Example: The spheres at the vertex are imperfect at aligning with the cylinders, so especially at low $fn things look prety ugly. This is normal.
|
||||
// Example: The spheres at the vertex are imperfect at aligning with the cylinders, so especially at low $fn things look prety ugly. This is normal.
|
||||
// include<BOSL2/polyhedra.scad>
|
||||
// $fn=8;
|
||||
// octahedron = regular_polyhedron_info("vnf", name="octahedron", or=20);
|
||||
|
@ -423,7 +423,7 @@ module vnf_wireframe(vnf, r, d)
|
|||
]);
|
||||
for (e=edges) extrude_from_to(vertex[e[0]],vertex[e[1]]) circle(r=r);
|
||||
move_copies(vertex) sphere(r=r);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Function: vnf_volume()
|
||||
|
@ -451,7 +451,7 @@ function vnf_volume(vnf) =
|
|||
// no holes; otherwise the results are undefined.
|
||||
|
||||
// Divide the solid up into tetrahedra with the origin as one vertex. The centroid of a tetrahedron is the average of its vertices.
|
||||
// The centroid of the total is the volume weighted average.
|
||||
// The centroid of the total is the volume weighted average.
|
||||
function vnf_centroid(vnf) =
|
||||
let(
|
||||
verts = vnf[0],
|
||||
|
@ -626,7 +626,7 @@ function vnf_bend(vnf,r,d,axis="Z") =
|
|||
// bad edges and vertices, overlaid on a transparent gray polyhedron of the VNF.
|
||||
// .
|
||||
// Currently checks for these problems:
|
||||
// Type | Color | Code | Message
|
||||
// Type | Color | Code | Message
|
||||
// ------- | -------- | ------------ | ---------------------------------
|
||||
// WARNING | Yellow | BIG_FACE | Face has more than 3 vertices, and may confuse CGAL.
|
||||
// WARNING | Brown | NULL_FACE | Face has zero area.
|
||||
|
@ -695,7 +695,7 @@ function vnf_bend(vnf,r,d,axis="Z") =
|
|||
// move([75,35,30],p=vnf_triangulate(linear_sweep(square(100,center=true), height=100)))
|
||||
// ]);
|
||||
// vnf_validate(vnf,size=2,check_isects=true);
|
||||
// Example: HOLE_EDGE Errors; Edges Adjacent to Holes.
|
||||
// Example: HOLE_EDGE Errors; Edges Adjacent to Holes.
|
||||
// vnf = skin([
|
||||
// path3d(regular_ngon(n=4, d=100),0),
|
||||
// path3d(regular_ngon(n=5, d=100),100)
|
||||
|
|
Loading…
Reference in a new issue