diff --git a/arrays.scad b/arrays.scad
index 74d2ea90..728cc47 100644
--- a/arrays.scad
+++ b/arrays.scad
@@ -53,6 +53,150 @@ function _same_type(a,b, depth) =
&& []==[for(i=idx(a)) if( ! _same_type(a[i],b[i],depth-1) ) 0] );
+
+
+// Function: list_shortest()
+// Usage:
+// llen = list_shortest(array);
+// Topics: List Handling
+// See Also: list_longest()
+// Description:
+// Returns the length of the shortest sublist in a list of lists.
+// Arguments:
+// array = A list of lists.
+// Example:
+// slen = list_shortest([[3,4,5],[6,7,8,9]]); // Returns: 3
+function list_shortest(array) =
+ assert(is_list(array), "Invalid input." )
+ min([for (v = array) len(v)]);
+
+
+// Function: list_longest()
+// Usage:
+// llen = list_longest(array);
+// Topics: List Handling
+// See Also: list_shortest()
+// Description:
+// Returns the length of the longest sublist in a list of lists.
+// Arguments:
+// array = A list of lists.
+// Example:
+// llen = list_longest([[3,4,5],[6,7,8,9]]); // Returns: 4
+function list_longest(array) =
+ assert(is_list(array), "Invalid input." )
+ max([for (v = array) len(v)]);
+
+
+
+// Function: in_list()
+// Usage:
+// bool = in_list(val, list, [idx]);
+// Topics: List Handling
+// Description:
+// Returns true if value `val` is in list `list`. When `val==NAN` the answer will be false for any list.
+// Arguments:
+// val = The simple value to search for.
+// list = The list to search.
+// idx = If given, searches the given columns for matches for `val`.
+// Example:
+// a = in_list("bar", ["foo", "bar", "baz"]); // Returns true.
+// b = in_list("bee", ["foo", "bar", "baz"]); // Returns false.
+// c = in_list("bar", [[2,"foo"], [4,"bar"], [3,"baz"]], idx=1); // Returns true.
+function in_list(val,list,idx) =
+ assert( is_list(list) && (is_undef(idx) || is_finite(idx)),
+ "Invalid input." )
+ let( s = search([val], list, num_returns_per_match=1, index_col_num=idx)[0] )
+ s==[] || s==[[]] ? false
+ : is_undef(idx) ? val==list[s]
+ : val==list[s][idx];
+
+
+// Function: find_first_match()
+// Topics: List Handling
+// See Also: in_list()
+// Usage:
+// idx = find_first_match(val, list, [start=], [eps=]);
+// indices = find_first_match(val, list, all=true, [start=], [eps=]);
+// Description:
+// Finds the first item in `list` that matches `val`, returning the index.
+// Arguments:
+// 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 (
+ (!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 :
+ (
+ (!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: list_increasing()
+// Usage:
+// bool = list_increasing(list);
+// Topics: List Handling
+// See Also: max_index(), min_index(), list_decreasing()
+// Description:
+// Returns true if the list is (non-strictly) increasing
+// Example:
+// a = list_increasing([1,2,3,4]); // Returns: true
+// b = list_increasing([1,3,2,4]); // Returns: false
+// c = list_increasing([4,3,2,1]); // Returns: false
+function list_increasing(list) =
+ assert(is_list(list)||is_string(list))
+ len([for (p=pair(list)) if(p.x>p.y) true])==0;
+
+
+// Function: list_decreasing()
+// Usage:
+// bool = list_decreasing(list);
+// Topics: List Handling
+// See Also: max_index(), min_index(), list_increasing()
+// Description:
+// Returns true if the list is (non-strictly) decreasing
+// Example:
+// a = list_decreasing([1,2,3,4]); // Returns: false
+// b = list_decreasing([4,2,3,1]); // Returns: false
+// c = list_decreasing([4,3,2,1]); // Returns: true
+function list_decreasing(list) =
+ assert(is_list(list)||is_string(list))
+ len([for (p=pair(list)) if(p.x
=len(array) , "Improper index list." )
+ is_string(array)? str_join(bselect( [for (x=array) x], index)) :
+ [for(i=[0:len(array)-1]) if (index[i]) array[i]];
+
+
+
+
+
+// Section: List Construction
+
+
// Function: list()
// Topics: List Handling, Type Conversion
// Usage:
@@ -246,155 +416,6 @@ function force_list(value, n=1, fill) =
is_undef(fill)? [for (i=[1:1:n]) value] : [value, for (i=[2:1:n]) fill];
-// Function: add_scalar()
-// Usage:
-// v = add_scalar(v, s);
-// Topics: List Handling
-// Description:
-// Given a list and a scalar, returns the list with the scalar added to each item in it.
-// If given a list of arrays, recursively adds the scalar to the each array.
-// Arguments:
-// v = The initial array.
-// s = A scalar value to add to every item in the array.
-// Example:
-// a = add_scalar([1,2,3],3); // Returns: [4,5,6]
-// b = add_scalar([[1,2,3],[3,4,5]],3); // Returns: [[4,5,6],[6,7,8]]
-function add_scalar(v,s) =
- is_finite(s) ? [for (x=v) is_list(x)? add_scalar(x,s) : is_finite(x) ? x+s: x] : v;
-
-
-// Function: in_list()
-// Usage:
-// bool = in_list(val, list, [idx]);
-// Topics: List Handling
-// Description:
-// Returns true if value `val` is in list `list`. When `val==NAN` the answer will be false for any list.
-// Arguments:
-// val = The simple value to search for.
-// list = The list to search.
-// idx = If given, searches the given subindex for matches for `val`.
-// Example:
-// a = in_list("bar", ["foo", "bar", "baz"]); // Returns true.
-// b = in_list("bee", ["foo", "bar", "baz"]); // Returns false.
-// c = in_list("bar", [[2,"foo"], [4,"bar"], [3,"baz"]], idx=1); // Returns true.
-function in_list(val,list,idx) =
- assert( is_list(list) && (is_undef(idx) || is_finite(idx)),
- "Invalid input." )
- let( s = search([val], list, num_returns_per_match=1, index_col_num=idx)[0] )
- s==[] || s==[[]] ? false
- : is_undef(idx) ? val==list[s]
- : val==list[s][idx];
-
-
-// Function: find_first_match()
-// Topics: List Handling
-// See Also: in_list()
-// Usage:
-// idx = find_first_match(val, list, [start=], [eps=]);
-// indices = find_first_match(val, list, all=true, [start=], [eps=]);
-// Description:
-// Finds the first item in `list` that matches `val`, returning the index.
-// Arguments:
-// 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 (
- (!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 :
- (
- (!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()
-// Usage:
-// idx = min_index(vals);
-// idxlist = min_index(vals, all=true);
-// Topics: List Handling
-// See Also: max_index(), list_increasing(), list_decreasing()
-// Description:
-// Returns the index of the first occurrence of the minimum value in the given list.
-// If `all` is true then returns a list of all indices where the minimum value occurs.
-// Arguments:
-// vals = vector of values
-// all = set to true to return indices of all occurences of the minimum. Default: false
-// Example:
-// a = min_index([5,3,9,6,2,7,8,2,1]); // Returns: 8
-// b = min_index([5,3,9,6,2,7,8,2,7],all=true); // Returns: [4,7]
-function min_index(vals, all=false) =
- assert( is_vector(vals) && len(vals)>0 , "Invalid or empty list of numbers.")
- all ? search(min(vals),vals,0) : search(min(vals), vals)[0];
-
-
-// Function: max_index()
-// Usage:
-// idx = max_index(vals);
-// idxlist = max_index(vals, all=true);
-// Topics: List Handling
-// See Also: min_index(), list_increasing(), list_decreasing()
-// Description:
-// Returns the index of the first occurrence of the maximum value in the given list.
-// If `all` is true then returns a list of all indices where the maximum value occurs.
-// Arguments:
-// vals = vector of values
-// all = set to true to return indices of all occurences of the maximum. Default: false
-// Example:
-// max_index([5,3,9,6,2,7,8,9,1]); // Returns: 2
-// max_index([5,3,9,6,2,7,8,9,1],all=true); // Returns: [2,7]
-function max_index(vals, all=false) =
- assert( is_vector(vals) && len(vals)>0 , "Invalid or empty list of numbers.")
- all ? search(max(vals),vals,0) : search(max(vals), vals)[0];
-
-
-// Function: list_increasing()
-// Usage:
-// bool = list_increasing(list);
-// Topics: List Handling
-// See Also: max_index(), min_index(), list_decreasing()
-// Description:
-// Returns true if the list is (non-strictly) increasing
-// Example:
-// a = list_increasing([1,2,3,4]); // Returns: true
-// b = list_increasing([1,3,2,4]); // Returns: false
-// c = list_increasing([4,3,2,1]); // Returns: false
-function list_increasing(list) =
- assert(is_list(list)||is_string(list))
- len([for (p=pair(list)) if(p.x>p.y) true])==0;
-
-
-// Function: list_decreasing()
-// Usage:
-// bool = list_decreasing(list);
-// Topics: List Handling
-// See Also: max_index(), min_index(), list_increasing()
-// Description:
-// Returns true if the list is (non-strictly) decreasing
-// Example:
-// a = list_decreasing([1,2,3,4]); // Returns: false
-// b = list_decreasing([4,2,3,1]); // Returns: false
-// c = list_decreasing([4,3,2,1]); // Returns: true
-function list_decreasing(list) =
- assert(is_list(list)||is_string(list))
- len([for (p=pair(list)) if(p.xlen(valuelist)), str("List `valuelist` too short; its length should be ",len(trueind)) )
+ assert( !(len(trueind)=len(array) , "Improper index list." )
- is_string(array)? str_join(bselect( [for (x=array) x], index)) :
- [for(i=[0:len(array)-1]) if (index[i]) array[i]];
-
-
-// Function: list_bset()
-// Usage:
-// arr = list_bset(indexset, valuelist, [dflt]);
-// Topics: List Handling
-// See Also: bselect()
-// Description:
-// Opposite of `bselect()`. Returns a list the same length as `indexlist`, where each item will
-// either be 0 if the corresponding item in `indexset` is false, or the next sequential value
-// from `valuelist` if the item is true. The number of `true` values in `indexset` must be equal
-// to the length of `valuelist`.
-// Arguments:
-// indexset = A list of boolean values.
-// valuelist = The list of values to set into the returned list.
-// dflt = Default value to store when the indexset item is false.
-// Example:
-// a = list_bset([false,true,false,true,false], [3,4]); // Returns: [0,3,0,4,0]
-// b = list_bset([false,true,false,true,false], [3,4], dflt=1); // Returns: [1,3,1,4,1]
-function list_bset(indexset, valuelist, dflt=0) =
- assert(is_list(indexset), "The index set is not a list." )
- assert(is_list(valuelist), "The `valuelist` is not a list." )
- let( trueind = search([true], indexset,0)[0] )
- assert( !(len(trueind)>len(valuelist)), str("List `valuelist` too short; its length should be ",len(trueind)) )
- assert( !(len(trueind)= -eps &&
max(ys) >= -eps &&
@@ -1615,7 +1615,7 @@ function _find_anchor(anchor, geom) =
)
assert(len(hits)>0, "Anchor vector does not intersect with the shape. Attachment failed.")
let(
- furthest = max_index(subindex(hits,0)),
+ furthest = max_index(columns(hits,0)),
dist = hits[furthest][0],
pos = hits[furthest][2],
hitnorms = [for (hit = hits) if (approx(hit[0],dist,eps=eps)) hit[1]],
@@ -1640,7 +1640,7 @@ function _find_anchor(anchor, geom) =
) vnf==EMPTY_VNF? [anchor, [0,0,0], unit(anchor), 0] :
let(
rpts = apply(rot(from=anchor, to=RIGHT) * move(point3d(-cp)), vnf[0]),
- maxx = max(subindex(rpts,0)),
+ maxx = max(columns(rpts,0)),
idxs = [for (i = idx(rpts)) if (approx(rpts[i].x, maxx)) i],
mm = pointlist_bounds(select(rpts,idxs)),
avgy = (mm[0].y+mm[1].y)/2,
@@ -1681,7 +1681,7 @@ function _find_anchor(anchor, geom) =
)
if(!is_undef(isect) && !approx(isect,t[0])) [norm(isect), isect, n2]
],
- maxidx = max_index(subindex(isects,0)),
+ maxidx = max_index(columns(isects,0)),
isect = isects[maxidx],
pos = point2d(cp) + isect[1],
vec = unit(isect[2],[0,1])
@@ -1691,7 +1691,7 @@ function _find_anchor(anchor, geom) =
path = geom[1],
anchor = point2d(anchor),
rpath = rot(from=anchor, to=RIGHT, p=move(point2d(-cp), p=path)),
- maxx = max(subindex(rpath,0)),
+ maxx = max(columns(rpath,0)),
idxs = [for (i = idx(rpath)) if (approx(rpath[i].x, maxx)) i],
miny = min([for (i=idxs) rpath[i].y]),
maxy = max([for (i=idxs) rpath[i].y]),
@@ -1717,7 +1717,7 @@ function _find_anchor(anchor, geom) =
if(!is_undef(isect) && !approx(isect,t[0]))
[norm(isect), isect, n2]
],
- maxidx = max_index(subindex(isects,0)),
+ maxidx = max_index(columns(isects,0)),
isect = isects[maxidx],
pos = point3d(cp) + point3d(isect[1]) + unit([0,0,anchor.z],CENTER)*l/2,
xyvec = unit(isect[2],[0,1]),
@@ -1730,7 +1730,7 @@ function _find_anchor(anchor, geom) =
anchor = point3d(anchor),
xyanch = point2d(anchor),
rpath = rot(from=xyanch, to=RIGHT, p=move(point2d(-cp), p=path)),
- maxx = max(subindex(rpath,0)),
+ maxx = max(columns(rpath,0)),
idxs = [for (i = idx(rpath)) if (approx(rpath[i].x, maxx)) i],
ys = [for (i=idxs) rpath[i].y],
avgy = (min(ys)+max(ys))/2,
diff --git a/beziers.scad b/beziers.scad
index d71d37b..4cc5ee8 100644
--- a/beziers.scad
+++ b/beziers.scad
@@ -1043,9 +1043,9 @@ function bezier_patch_points(patch, u, v) =
assert(is_num(u) || !is_undef(u[0]))
assert(is_num(v) || !is_undef(v[0]))
let(
- vbezes = [for (i = idx(patch[0])) bezier_points(subindex(patch,i), is_num(u)? [u] : u)]
+ vbezes = [for (i = idx(patch[0])) bezier_points(columns(patch,i), is_num(u)? [u] : u)]
)
- [for (i = idx(vbezes[0])) bezier_points(subindex(vbezes,i), is_num(v)? [v] : v)];
+ [for (i = idx(vbezes[0])) bezier_points(columns(vbezes,i), is_num(v)? [v] : v)];
// Function: bezier_triangle_point()
@@ -1357,7 +1357,7 @@ function bezier_patch_degenerate(patch, splinesteps=16, reverse=false, return_ed
all(row_degen) && all(col_degen) ? // fully degenerate case
[EMPTY_VNF, repeat([patch[0][0]],4)] :
all(row_degen) ? // degenerate to a line (top to bottom)
- let(pts = bezier_points(subindex(patch,0), samplepts))
+ let(pts = bezier_points(columns(patch,0), samplepts))
[EMPTY_VNF, [pts,pts,[pts[0]],[last(pts)]]] :
all(col_degen) ? // degenerate to a line (left to right)
let(pts = bezier_points(patch[0], samplepts))
@@ -1366,7 +1366,7 @@ function bezier_patch_degenerate(patch, splinesteps=16, reverse=false, return_ed
let(pts = bezier_patch_points(patch, samplepts, samplepts))
[
vnf_vertex_array(pts, reverse=!reverse),
- [subindex(pts,0), subindex(pts,len(pts)-1), pts[0], last(pts)]
+ [columns(pts,0), columns(pts,len(pts)-1), pts[0], last(pts)]
] :
top_degen && bot_degen ?
let(
@@ -1375,17 +1375,17 @@ function bezier_patch_degenerate(patch, splinesteps=16, reverse=false, return_ed
if (splinesteps%2==0) splinesteps+1,
each reverse(list([3:2:splinesteps]))
],
- bpatch = [for(i=[0:1:len(patch[0])-1]) bezier_points(subindex(patch,i), samplepts)],
+ bpatch = [for(i=[0:1:len(patch[0])-1]) bezier_points(columns(patch,i), samplepts)],
pts = [
[bpatch[0][0]],
- for(j=[0:splinesteps-2]) bezier_points(subindex(bpatch,j+1), lerpn(0,1,rowcount[j])),
+ for(j=[0:splinesteps-2]) bezier_points(columns(bpatch,j+1), lerpn(0,1,rowcount[j])),
[last(bpatch[0])]
],
vnf = vnf_tri_array(pts, reverse=!reverse)
) [
vnf,
[
- subindex(pts,0),
+ columns(pts,0),
[for(row=pts) last(row)],
pts[0],
last(pts),
@@ -1404,16 +1404,16 @@ function bezier_patch_degenerate(patch, splinesteps=16, reverse=false, return_ed
full_degen = len(patch)>=4 && all(select(row_degen,1,ceil(len(patch)/2-1))),
rowmax = full_degen ? count(splinesteps+1) :
[for(j=[0:splinesteps]) j<=splinesteps/2 ? 2*j : splinesteps],
- bpatch = [for(i=[0:1:len(patch[0])-1]) bezier_points(subindex(patch,i), samplepts)],
+ bpatch = [for(i=[0:1:len(patch[0])-1]) bezier_points(columns(patch,i), samplepts)],
pts = [
[bpatch[0][0]],
- for(j=[1:splinesteps]) bezier_points(subindex(bpatch,j), lerpn(0,1,rowmax[j]+1))
+ for(j=[1:splinesteps]) bezier_points(columns(bpatch,j), lerpn(0,1,rowmax[j]+1))
],
vnf = vnf_tri_array(pts, reverse=!reverse)
) [
vnf,
[
- subindex(pts,0),
+ columns(pts,0),
[for(row=pts) last(row)],
pts[0],
last(pts),
diff --git a/bottlecaps.scad b/bottlecaps.scad
index 7d88ac0..2451b7e 100644
--- a/bottlecaps.scad
+++ b/bottlecaps.scad
@@ -1121,7 +1121,7 @@ module sp_neck(diam,type,wall,id,style="L",bead=false, anchor, spin, orient)
isect400 = [for(seg=pair(beadpts)) let(segisect = line_intersection([[T/2,0],[T/2,1]] , seg, LINE, SEGMENT)) if (is_def(segisect)) segisect.y];
- extra_bot = type==400 && bead ? -min(subindex(beadpts,1))+max(isect400) : 0;
+ extra_bot = type==400 && bead ? -min(columns(beadpts,1))+max(isect400) : 0;
bead_shift = type==400 ? H+max(isect400) : entry[5]+W/2; // entry[5] is L
attachable(anchor,spin,orient,r=bead ? beadmax : T/2, l=H+extra_bot){
diff --git a/geometry.scad b/geometry.scad
index c328c87..5a4a76c 100644
--- a/geometry.scad
+++ b/geometry.scad
@@ -1878,26 +1878,7 @@ function polygon_shift(poly, i) =
// move_copies(concat(circ,pent)) circle(r=.1,$fn=32);
// color("red") move_copies([pent[0],circ[0]]) circle(r=.1,$fn=32);
// color("blue") translate(reindexed[0])circle(r=.1,$fn=32);
-function old_reindex_polygon(reference, poly, return_error=false) =
- assert(is_path(reference) && is_path(poly,dim=len(reference[0])),
- "Invalid polygon(s) or incompatible dimensions. " )
- assert(len(reference)==len(poly), "The polygons must have the same length.")
- let(
- dim = len(reference[0]),
- N = len(reference),
- fixpoly = dim != 2? poly :
- is_polygon_clockwise(reference)
- ? clockwise_polygon(poly)
- : ccw_polygon(poly),
- I = [for(i=reference) 1],
- val = [ for(k=[0:N-1])
- [for(i=[0:N-1])
- (reference[i]*poly[(i+k)%N]) ] ]*I,
- optimal_poly = polygon_shift(fixpoly, max_index(val))
- )
- return_error? [optimal_poly, min(poly*(I*poly)-2*val)] :
- optimal_poly;
-function reindex_polygon(reference, poly, return_error=false) =
+ function reindex_polygon(reference, poly, return_error=false) =
assert(is_path(reference) && is_path(poly,dim=len(reference[0])),
"Invalid polygon(s) or incompatible dimensions. " )
assert(len(reference)==len(poly), "The polygons must have the same length.")
@@ -1919,68 +1900,70 @@ function reindex_polygon(reference, poly, return_error=false) =
optimal_poly;
-
// Function: align_polygon()
// Usage:
-// newpoly = align_polygon(reference, poly, angles, [cp]);
+// newpoly = align_polygon(reference, poly, [angles], [cp], [tran], [return_ind]);
// Topics: Geometry, Polygons
// Description:
-// Tries the list or range of angles to find a rotation of the specified 2D polygon that best aligns
-// with the reference 2D polygon. For each angle, the polygon is reindexed, which is a costly operation
-// so if run time is a problem, use a smaller sampling of angles. Returns the rotated and reindexed
-// polygon.
+// Find the best alignment of a specified 2D polygon with a reference 2D polygon over a set of
+// transformations. You can specify a list or range of angles and a centerpoint or you can
+// give a list of arbitrary 2d transformation matrices. For each transformation or angle, the polygon is
+// reindexed, which is a costly operation so if run time is a problem, use a smaller sampling of angles or
+// transformations. By default returns the rotated and reindexed polygon. You can also request that
+// the best angle or the index into the transformation list be returned. When you specify an angle
// Arguments:
// reference = reference polygon
// poly = polygon to rotate into alignment with the reference
// angles = list or range of angles to test
// cp = centerpoint for rotations
-// Example(2D): The original hexagon in yellow is not well aligned with the pentagon. Turning it so the faces line up gives an optimal alignment, shown in red.
-// $fn=32;
-// pentagon = subdivide_path(pentagon(side=2),60);
-// hexagon = subdivide_path(hexagon(side=2.7),60);
-// color("red") move_copies(scale(1.4,p=align_polygon(pentagon,hexagon,[0:10:359]))) circle(r=.1);
-// move_copies(concat(pentagon,hexagon))circle(r=.1);
-function old_align_polygon(reference, poly, angles, cp) =
+// ---
+// tran = list of 2D transformation matrices to optimize over
+// return_ind = if true, return the best angle (if you specified angles) or the index into tran otherwise of best alignment
+// Example(2D): Rotating the poorly aligned light gray triangle by 105 degrees produces the best alignment, shown in blue:
+// ellipse = yscale(3,circle(r=10, $fn=32));
+// tri = move([-50/3,-9],
+// subdivide_path([[0,0], [50,0], [0,27]], 32));
+// aligned = align_polygon(ellipse,tri, [0:5:180]);
+// color("white")stroke(tri,width=.5,closed=true);
+// stroke(ellipse, width=.5, closed=true);
+// color("blue")stroke(aligned,width=.5,closed=true);
+// Example(2D,NoAxes): Translating a triangle (light gray) to the best alignment (blue)
+// ellipse = yscale(2,circle(r=10, $fn=32));
+// tri = subdivide_path([[0,0], [27,0], [-7,50]], 32);
+// T = [for(x=[-10:0], y=[-30:-15]) move([x,y])];
+// aligned = align_polygon(ellipse,tri, trans=T);
+// color("white")stroke(tri,width=.5,closed=true);
+// stroke(ellipse, width=.5, closed=true);
+// color("blue")stroke(aligned,width=.5,closed=true);
+function align_polygon(reference, poly, angles, cp, trans, return_ind=false) =
+ assert(is_undef(trans) || (is_undef(angles) && is_undef(cp)), "Cannot give both angles/cp and trans as input")
+ let(
+ trans = is_def(trans) ? trans :
+ assert( (is_vector(angles) && len(angles)>0) || valid_range(angles),
+ "The `angle` parameter must be a range or a non void list of numbers.")
+ [for(angle=angles) zrot(angle,cp=cp)]
+ )
assert(is_path(reference,dim=2) && is_path(poly,dim=2),
"Invalid polygon(s). " )
assert(len(reference)==len(poly), "The polygons must have the same length.")
- assert( (is_vector(angles) && len(angles)>0) || valid_range(angles),
- "The `angle` parameter must be a range or a non void list of numbers.")
let( // alignments is a vector of entries of the form: [polygon, error]
alignments = [
- for(angle=angles)
- reindex_polygon(
- reference,
- zrot(angle,p=poly,cp=cp),
- return_error=true
- )
+ for(T=trans)
+ reindex_polygon(
+ reference,
+ apply(T,poly),
+ return_error=true
+ )
],
- best = min_index(subindex(alignments,1))
- ) alignments[best][0];
-
-
-function align_polygon(reference, poly, angles, cp) =
- assert(is_path(reference,dim=2) && is_path(poly,dim=2),
- "Invalid polygon(s). " )
- assert(len(reference)==len(poly), "The polygons must have the same length.")
- assert( (is_vector(angles) && len(angles)>0) || valid_range(angles),
- "The `angle` parameter must be a range or a non void list of numbers.")
- let( // alignments is a vector of entries of the form: [polygon, error]
- alignments = [
- for(angle=angles)
- reindex_polygon(
- reference,
- zrot(angle,p=poly,cp=cp),
- return_error=true
- )
- ],
- scores = subindex(alignments,1),
+ scores = columns(alignments,1),
minscore = min(scores),
minind = [for(i=idx(scores)) if (scores[i]1) select(ptind[i],k)]],
- risect = [for(i=[0:1]) concat(subindex(intersections,i), cornerpts[i])],
+ risect = [for(i=[0:1]) concat(columns(intersections,i), cornerpts[i])],
counts = [count(len(region1)), count(len(region2))],
pathind = [for(i=[0:1]) search(counts[i], risect[i], 0)]
)
@@ -381,82 +381,6 @@ function region_parts(region) =
// Section: Region Extrusion and VNFs
-function _path_path_closest_vertices(path1,path2) =
- let(
- dists = [for (i=idx(path1)) let(j=closest_point(path1[i],path2)) [j,norm(path2[j]-path1[i])]],
- i1 = min_index(subindex(dists,1)),
- i2 = dists[i1][0]
- ) [dists[i1][1], i1, i2];
-
-
-function _join_paths_at_vertices(path1,path2,v1,v2) =
- let(
- repeat_start = !approx(path1[v1],path2[v2]),
- path1 = clockwise_polygon(polygon_shift(path1,v1)),
- path2 = ccw_polygon(polygon_shift(path2,v2))
- )
- [
- each path1,
- if (repeat_start) path1[0],
- each path2,
- if (repeat_start) path2[0],
- ];
-
-
-// Given a region that is connected and has its outer border in region[0],
-// produces a polygon with the same points that has overlapping connected paths
-// to join internal holes to the outer border. Output is a single path.
-function _cleave_connected_region(region) =
- len(region)==0? [] :
- len(region)<=1? clockwise_polygon(region[0]) :
- let(
- dists = [
- for (i=[1:1:len(region)-1])
- _path_path_closest_vertices(region[0],region[i])
- ],
- idxi = min_index(subindex(dists,0)),
- newoline = _join_paths_at_vertices(
- region[0], region[idxi+1],
- dists[idxi][1], dists[idxi][2]
- )
- ) len(region)==2? clockwise_polygon(newoline) :
- let(
- orgn = [
- newoline,
- for (i=idx(region))
- if (i>0 && i!=idxi+1)
- region[i]
- ]
- )
- assert(len(orgn)depth && _r2>depth, "Screw profile deeper than rod radius");
@@ -1087,7 +1087,7 @@ module generic_threaded_nut(
bevel1 = first_defined([bevel1,bevel,false]);
bevel2 = first_defined([bevel2,bevel,false]);
dummy1 = assert(is_num(pitch) && pitch>0);
- depth = -pitch*min(subindex(profile,1));
+ depth = -pitch*min(columns(profile,1));
attachable(anchor,spin,orient, size=[od/cos(30),od,h]) {
difference() {
cyl(d=od/cos(30), h=h, center=true, $fn=6,chamfer1=bevel1?depth:undef,chamfer2=bevel2?depth:undef);
diff --git a/vectors.scad b/vectors.scad
index e75216a..ac9c382 100644
--- a/vectors.scad
+++ b/vectors.scad
@@ -263,11 +263,50 @@ function vector_axis(v1,v2=undef,v3=undef) =
+// Function: min_index()
+// Usage:
+// idx = min_index(vals);
+// idxlist = min_index(vals, all=true);
+// Topics: List Handling
+// See Also: max_index(), list_increasing(), list_decreasing()
+// Description:
+// Returns the index of the first occurrence of the minimum value in the given list.
+// If `all` is true then returns a list of all indices where the minimum value occurs.
+// Arguments:
+// vals = vector of values
+// all = set to true to return indices of all occurences of the minimum. Default: false
+// Example:
+// a = min_index([5,3,9,6,2,7,8,2,1]); // Returns: 8
+// b = min_index([5,3,9,6,2,7,8,2,7],all=true); // Returns: [4,7]
+function min_index(vals, all=false) =
+ assert( is_vector(vals) && len(vals)>0 , "Invalid or empty list of numbers.")
+ all ? search(min(vals),vals,0) : search(min(vals), vals)[0];
+
+
+// Function: max_index()
+// Usage:
+// idx = max_index(vals);
+// idxlist = max_index(vals, all=true);
+// Topics: List Handling
+// See Also: min_index(), list_increasing(), list_decreasing()
+// Description:
+// Returns the index of the first occurrence of the maximum value in the given list.
+// If `all` is true then returns a list of all indices where the maximum value occurs.
+// Arguments:
+// vals = vector of values
+// all = set to true to return indices of all occurences of the maximum. Default: false
+// Example:
+// max_index([5,3,9,6,2,7,8,9,1]); // Returns: 2
+// max_index([5,3,9,6,2,7,8,9,1],all=true); // Returns: [2,7]
+function max_index(vals, all=false) =
+ assert( is_vector(vals) && len(vals)>0 , "Invalid or empty list of numbers.")
+ all ? search(max(vals),vals,0) : search(max(vals), vals)[0];
+
+
// Section: Vector Searching
-
// Function: closest_point()
// Usage:
// index = closest_point(pt, points);
@@ -504,8 +543,8 @@ function vector_nearest(query, k, target) =
"More results are requested than the number of points.")
tgpts
? let( tree = _bt_tree(target, count(len(target))) )
- subindex(_bt_nearest( query, k, target, tree),0)
- : subindex(_bt_nearest( query, k, target[0], target[1]),0);
+ columns(_bt_nearest( query, k, target, tree),0)
+ : columns(_bt_nearest( query, k, target[0], target[1]),0);
//Ball tree nearest
diff --git a/vnf.scad b/vnf.scad
index 24fc232..9ba0e0d 100644
--- a/vnf.scad
+++ b/vnf.scad
@@ -287,9 +287,9 @@ function vnf_tri_array(points, row_wrap=false, reverse=false, vnf=EMPTY_VNF) =
// and eliminates any faces with fewer than 3 vertices.
// (Unreferenced vertices of the input VNFs are not dropped.)
// Arguments:
-// vnfs - a list of the VNFs to merge in one VNF.
-// cleanup - when true, consolidates the duplicate vertices of the merge. Default: false
-// eps - the tolerance in finding duplicates when cleanup=true. Default: EPSILON
+// vnfs = a list of the VNFs to merge in one VNF.
+// cleanup = when true, consolidates the duplicate vertices of the merge. Default: false
+// eps = the tolerance in finding duplicates when cleanup=true. Default: EPSILON
function vnf_merge(vnfs, cleanup=false, eps=EPSILON) =
is_vnf(vnfs) ? vnf_merge([vnfs], cleanup, eps) :
assert( is_vnf_list(vnfs) , "Improper vnf or vnf list")
@@ -350,6 +350,95 @@ function vnf_from_polygons(polygons) =
+
+function _path_path_closest_vertices(path1,path2) =
+ let(
+ dists = [for (i=idx(path1)) let(j=closest_point(path1[i],path2)) [j,norm(path2[j]-path1[i])]],
+ i1 = min_index(columns(dists,1)),
+ i2 = dists[i1][0]
+ ) [dists[i1][1], i1, i2];
+
+
+function _join_paths_at_vertices(path1,path2,v1,v2) =
+ let(
+ repeat_start = !approx(path1[v1],path2[v2]),
+ path1 = clockwise_polygon(polygon_shift(path1,v1)),
+ path2 = ccw_polygon(polygon_shift(path2,v2))
+ )
+ [
+ each path1,
+ if (repeat_start) path1[0],
+ each path2,
+ if (repeat_start) path2[0],
+ ];
+
+
+// Given a region that is connected and has its outer border in region[0],
+// produces a polygon with the same points that has overlapping connected paths
+// to join internal holes to the outer border. Output is a single path.
+function _cleave_connected_region(region) =
+ len(region)==0? [] :
+ len(region)<=1? clockwise_polygon(region[0]) :
+ let(
+ dists = [
+ for (i=[1:1:len(region)-1])
+ _path_path_closest_vertices(region[0],region[i])
+ ],
+ idxi = min_index(columns(dists,0)),
+ newoline = _join_paths_at_vertices(
+ region[0], region[idxi+1],
+ dists[idxi][1], dists[idxi][2]
+ )
+ ) len(region)==2? clockwise_polygon(newoline) :
+ let(
+ orgn = [
+ newoline,
+ for (i=idx(region))
+ if (i>0 && i!=idxi+1)
+ region[i]
+ ]
+ )
+ assert(len(orgn) pivot) li ]
+ )
+ concat( _link_indicator(lesser ,imin,pivot-1),
+ search(pivot,l,1) ? 1 : 0 ,
+ _link_indicator(greater,pivot+1,imax) ) ;
+
// Function: vnf_triangulate()
// Usage:
// vnf2 = vnf_triangulate(vnf);
// Description:
-// Triangulates faces in the VNF that have more than 3 vertices.
+// Triangulates faces in the VNF that have more than 3 vertices.
+// Example:
+// include
+// vnf = zrot(33,regular_polyhedron_info("vnf", "dodecahedron", side=12));
+// vnf_polyhedron(vnf);
+// triangulated = vnf_triangulate(vnf);
+// color("red")vnf_wireframe(triangulated,width=.3);
function vnf_triangulate(vnf) =
let(
- vnf = is_vnf_list(vnf)? vnf_merge(vnf) : vnf,
verts = vnf[0],
faces = [for (face=vnf[1]) each len(face)==3 ? [face] :
polygon_triangulate(verts, face)]
@@ -446,7 +568,7 @@ function vnf_slice(vnf,dir,cuts) =
function _split_polygon_at_x(poly, x) =
let(
- xs = subindex(poly,0)
+ xs = columns(poly,0)
) (min(xs) >= x || max(xs) <= x)? [poly] :
let(
poly2 = [
@@ -709,7 +831,7 @@ function vnf_halfspace(plane, vnf, closed=true) =
let(
M = project_plane(plane),
faceregion = [for(path=newpaths) path2d(apply(M,select(newvert,path)))],
- facevnf = region_faces(faceregion,transform=rot_inverse(M),reverse=true)
+ facevnf = vnf_from_region(faceregion,transform=rot_inverse(M),reverse=true)
)
vnf_merge([[newvert, faces_edges_vertices[0]], facevnf]);