Merge pull request #735 from adrianVmariano/master

ellipse update
This commit is contained in:
Revar Desmera 2021-11-18 19:41:23 -08:00 committed by GitHub
commit 7cdc880a43
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 99 additions and 92 deletions

View file

@ -620,7 +620,9 @@ function _sort_general(arr, idx=undef, indexed=false) =
(len(arr)<=1) ? arr :
! indexed && is_undef(idx)
? _lexical_sort(arr)
: let( arrind = _indexed_sort(enumerate(arr,idx)) )
: let( labeled = is_undef(idx) ? [for(i=idx(arr)) [i,arr[i]]]
: [for(i=idx(arr)) [i, for(j=idx) arr[i][j]]],
arrind = _indexed_sort(labeled))
indexed
? arrind
: [for(i=arrind) arr[i]];
@ -797,7 +799,7 @@ function group_data(groups, values) =
assert(is_list(values))
assert(len(groups)==len(values),
"The groups and values arguments should be lists of matching length.")
let( sorted = _group_sort_by_index(zip(groups,values),0) )
let( sorted = _group_sort_by_index([for(i=idx(groups))[groups[i],values[i]]],0) )
// retrieve values and insert []
[
for (i = idx(sorted))

View file

@ -1047,7 +1047,7 @@ function _is_point_above_plane(plane, point) =
// r = radius of circle
// ---
// d = diameter of circle
// line = two points defining the unbounded line
// line = two points defining the line
// bounded = false for unbounded line, true for a segment, or a vector [false,true] or [true,false] to specify a ray with the first or second end unbounded. Default: false
// eps = epsilon used for identifying the case with one solution. Default: 1e-9
function circle_line_intersection(c,r,d,line,bounded=false,eps=EPSILON) =

View file

@ -822,7 +822,7 @@ function list_remove_values(list,values=[],all=false) =
// rng = idx(list, [s=], [e=], [step=]);
// for(i=idx(list, [s=], [e=], [step=])) ...
// Topics: List Handling, Iteration
// See Also: enumerate(), pair(), triplet(), combinations(), permutations()
// See Also: pair(), triplet(), combinations(), permutations()
// Description:
// Returns the range of indexes for the given list.
// Arguments:
@ -843,39 +843,13 @@ function idx(list, s=0, e=-1, step=1) =
) [_s : step : _e];
// Function: enumerate()
// Usage:
// arr = enumerate(l, [idx]);
// for (x = enumerate(l, [idx])) ... // x[0] is the index number, x[1] is the item.
// Topics: List Handling, Iteration
// See Also: idx(), pair(), triplet(), combinations(), permutations()
// Description:
// Returns a list, with each item of the given list `l` numbered in a sublist.
// Something like: `[[0,l[0]], [1,l[1]], [2,l[2]], ...]`
// Arguments:
// l = List to enumerate.
// idx = If given, enumerates just the given columns items of `l`.
// Example:
// enumerate(["a","b","c"]); // Returns: [[0,"a"], [1,"b"], [2,"c"]]
// enumerate([[88,"a"],[76,"b"],[21,"c"]], idx=1); // Returns: [[0,"a"], [1,"b"], [2,"c"]]
// enumerate([["cat","a",12],["dog","b",10],["log","c",14]], idx=[1:2]); // Returns: [[0,"a",12], [1,"b",10], [2,"c",14]]
// Example(2D):
// colors = ["red", "green", "blue"];
// for (p=enumerate(colors)) right(20*p[0]) color(p[1]) circle(d=10);
function enumerate(l,idx=undef) =
assert(is_list(l)||is_string(list), "Invalid input." )
assert( _valid_idx(idx,0,len(l)), "Invalid index/indices." )
(idx==undef)
? [for (i=[0:1:len(l)-1]) [i,l[i]]]
: [for (i=[0:1:len(l)-1]) [ i, for (j=idx) l[i][j]] ];
// Function: pair()
// Usage:
// p = pair(list, [wrap]);
// for (p = pair(list, [wrap])) ... // On each iteration, p contains a list of two adjacent items.
// Topics: List Handling, Iteration
// See Also: idx(), enumerate(), triplet(), combinations(), permutations()
// See Also: idx(), triplet(), combinations(), permutations()
// Description:
// Takes a list, and returns a list of adjacent pairs from it, optionally wrapping back to the front.
// Arguments:
@ -907,7 +881,7 @@ function pair(list, wrap=false) =
// list = triplet(list, [wrap]);
// for (t = triplet(list, [wrap])) ...
// Topics: List Handling, Iteration
// See Also: idx(), enumerate(), pair(), combinations(), permutations()
// See Also: idx(), pair(), combinations(), permutations()
// Description:
// Takes a list, and returns a list of adjacent triplets from it, optionally wrapping back to the front.
// If you set `wrap` to true then the first triplet is the one centered on the first list element, so it includes
@ -945,7 +919,7 @@ function triplet(list, wrap=false) =
// Usage:
// list = combinations(l, [n]);
// Topics: List Handling, Iteration
// See Also: idx(), enumerate(), pair(), triplet(), permutations()
// See Also: idx(), pair(), triplet(), permutations()
// Description:
// Returns a list of all of the (unordered) combinations of `n` items out of the given list `l`.
// For the list `[1,2,3,4]`, with `n=2`, this will return `[[1,2], [1,3], [1,4], [2,3], [2,4], [3,4]]`.
@ -966,11 +940,12 @@ function combinations(l,n=2,_s=0) =
: [for (i=[_s:1:len(l)-n], p=combinations(l,n=n-1,_s=i+1)) concat([l[i]], p)];
// Function: permutations()
// Usage:
// list = permutations(l, [n]);
// Topics: List Handling, Iteration
// See Also: idx(), enumerate(), pair(), triplet(), combinations()
// See Also: idx(), pair(), triplet(), combinations()
// Description:
// Returns a list of all of the (ordered) permutation `n` items out of the given list `l`.
// For the list `[1,2,3]`, with `n=2`, this will return `[[1,2],[1,3],[2,1],[2,3],[3,1],[3,2]]`
@ -989,8 +964,6 @@ function permutations(l,n=2) =
// Section: Changing list structure
@ -1050,33 +1023,6 @@ function full_flatten(l) =
// Function: zip()
// Usage:
// pairs = zip(a,b);
// triples = zip(a,b,c);
// quads = zip([LIST1,LIST2,LIST3,LIST4]);
// Topics: List Handling, Iteration
// Description:
// Zips together two or more lists into a single list. For example, if you have two
// lists [3,4,5], and [8,7,6], and zip them together, you get [ [3,8],[4,7],[5,6] ].
// The list returned will be as long as the shortest list passed to zip().
// Arguments:
// a = The first list, or a list of lists if b and c are not given.
// b = The second list, if given.
// c = The third list, if given.
// Example:
// a = [9,8,7,6]; b = [1,2,3];
// for (p=zip(a,b)) echo(p);
// // ECHO: [9,1]
// // ECHO: [8,2]
// // ECHO: [7,3]
function zip(a,b,c) =
b!=undef? zip([a,b,if (c!=undef) c]) :
let(n = min_length(a))
[for (i=[0:1:n-1]) [for (x=a) x[i]]];
// Section: Set Manipulation
// Function: set_union()

View file

@ -179,7 +179,7 @@ function rect(size=1, center, rounding=0, chamfer=0, anchor, spin=0) =
// circle(r|d=, ...) { attachables }
// Usage: As a Function
// path = circle(r|d=, ...);
// See Also: ellipse()
// See Also: ellipse(), circle_2tangents(), circle_3points()
// Description:
// When called as the builtin module, creates a 2D polygon that approximates a circle of the given size.
// When called as a function, returns a 2D list of points (path) for a polygon that approximates a circle of the given size.
@ -224,13 +224,14 @@ module circle(r, d, anchor=CENTER, spin=0) {
// Description:
// When called as a module, creates a 2D polygon that approximates a circle or ellipse of the given size.
// When called as a function, returns a 2D list of points (path) for a polygon that approximates a circle or ellipse of the given size.
// Note that the point list or shape is the same as the one you would get by scaling the output of {{circle()}}, but with this module your
// attachments to the ellipse will
// By default the point list or shape is the same as the one you would get by scaling the output of {{circle()}}, but with this module your
// attachments to the ellipse will retain their dimensions, whereas scaling a circle with attachments will also scale the attachments.
// If you set unifom to true then you will get a polygon with congruent sides whose vertices lie on the ellipse.
// Arguments:
// r = Radius of the circle or pair of semiaxes of ellipse
// ---
// d = Diameter of the circle or a pair giving the full X and Y axis lengths.
// realign = If true, rotates the polygon that approximates the circle/ellipse by half of one size.
// realign = If false starts the approximate ellipse with a point on the X+ axis. If true the midpoint of a side is on the X+ axis and the first point of the polygon is below the X+ axis. This can result in a very different polygon when $fn is small. Default: false
// circum = If true, the polygon that approximates the circle will be upsized slightly to circumscribe the theoretical circle. If false, it inscribes the theoretical circle. Default: false
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER`
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0`
@ -244,6 +245,54 @@ module circle(r, d, anchor=CENTER, spin=0) {
// ellipse(d=50, anchor=FRONT, spin=45);
// Example(NORENDER): Called as Function
// path = ellipse(d=50, anchor=FRONT, spin=45);
// Example(2D,NoAxes): Uniformly sampled hexagon at the top, regular non-uniform one at the bottom
// r=[10,3];
// ydistribute(7){
// union(){
// stroke([ellipse(r=r, $fn=100)],width=0.05,color="blue");
// stroke([ellipse(r=r, $fn=6)],width=0.1,color="red");
// }
// union(){
// stroke([ellipse(r=r, $fn=100)],width=0.05,color="blue");
// stroke([ellipse(r=r, $fn=6,uniform=true)],width=0.1,color="red");
// }
// }
// Example(2D): The realigned hexagons are even more different
// r=[10,3];
// ydistribute(7){
// union(){
// stroke([ellipse(r=r, $fn=100)],width=0.05,color="blue");
// stroke([ellipse(r=r, $fn=6,realign=true)],width=0.1,color="red");
// }
// union(){
// stroke([ellipse(r=r, $fn=100)],width=0.05,color="blue");
// stroke([ellipse(r=r, $fn=6,realign=true,uniform=true)],width=0.1,color="red");
// }
// }
// Example(2D): For odd $fn the result may not look very elliptical:
// r=[10,3];
// ydistribute(7){
// union(){
// stroke([ellipse(r=r, $fn=100)],width=0.05,color="blue");
// stroke([ellipse(r=r, $fn=5,realign=false)],width=0.1,color="red");
// }
// union(){
// stroke([ellipse(r=r, $fn=100)],width=0.05,color="blue");
// stroke([ellipse(r=r, $fn=5,realign=false,uniform=true)],width=0.1,color="red");
// }
// }
// Example(2D): The same ellipse, turned 90 deg, gives a very different result:
// r=[3,10];
// xdistribute(7){
// union(){
// stroke([ellipse(r=r, $fn=100)],width=0.1,color="blue");
// stroke([ellipse(r=r, $fn=5,realign=false)],width=0.2,color="red");
// }
// union(){
// stroke([ellipse(r=r, $fn=100)],width=0.1,color="blue");
// stroke([ellipse(r=r, $fn=5,realign=false,uniform=true)],width=0.2,color="red");
// }
// }
module ellipse(r, d, realign=false, circum=false, uniform=false, anchor=CENTER, spin=0)
{
r = force_list(get_radius(r=r, d=d, dflt=1),2);
@ -298,13 +347,41 @@ function _ellipse_refine(a,b,N, _theta=[]) =
function _ellipse_refine_realign(a,b,N, _theta=[],i=0) =
len(_theta)==0?
_ellipse_refine_realign(a,b,N, count(N-1,180/N,360/N))
:
let(
pts = [for(t=_theta) [a*cos(t),b*sin(t)],
[a*cos(_theta[0]), -b*sin(_theta[0])]],
lenlist= path_segment_lengths(pts,closed=true),
meanlen = mean(lenlist),
error = lenlist/meanlen
)
all_equal(error,EPSILON) ? pts
:
let(
dtheta = [each deltas(_theta),
360-last(_theta)-_theta[0],
2*_theta[0]],
newdtheta = [for(i=idx(dtheta)) dtheta[i]/error[i]],
normdtheta = newdtheta / sum(newdtheta) * 360,
adjusted = cumsum([last(normdtheta)/2, each list_head(normdtheta, -3)])
)
_ellipse_refine_realign(a,b,N,adjusted, i+1);
function ellipse(r, d, realign=false, circum=false, uniform=false, anchor=CENTER, spin=0) =
let(
r = force_list(get_radius(r=r, d=d, dflt=1),2),
sides = segs(max(r))
)
uniform ? assert(!circum, "Circum option not allowed when \"uniform\" is true")
reorient(anchor,spin,two_d=true,r=[r.x,r.y],p=_ellipse_refine(r.x,r.y,sides))
reorient(anchor,spin,two_d=true,r=[r.x,r.y],
p=realign ? reverse(_ellipse_refine_realign(r.x,r.y,sides))
: reverse_polygon(_ellipse_refine(r.x,r.y,sides)))
:
let(
offset = realign? 180/sides : 0,
@ -336,7 +413,7 @@ function ellipse(r, d, realign=false, circum=false, uniform=false, anchor=CENTER
// id = Inside diameter, at center of sides.
// side = Length of each side.
// rounding = Radius of rounding for the tips of the polygon. Default: 0 (no rounding)
// realign = If false, a tip is aligned with the Y+ axis. If true, the midpoint of a side is aligned with the Y+ axis. Default: false
// realign = If false, vertex 0 will lie on the X+ axis. If true then the midpoint of the last edge will lie on the X+ axis, and vertex 0 will be below the X axis. Default: false
// align_tip = If given as a 2D vector, rotates the whole shape so that the first vertex points in that direction. This occurs before spin.
// align_side = If given as a 2D vector, rotates the whole shape so that the normal of side0 points in that direction. This occurs before spin.
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER`
@ -471,7 +548,7 @@ module regular_ngon(n=6, r, d, or, od, ir, id, side, rounding=0, realign=false,
// id = Inside diameter, at center of sides.
// side = Length of each side.
// rounding = Radius of rounding for the tips of the polygon. Default: 0 (no rounding)
// realign = If false, a tip is aligned with the Y+ axis. If true, the midpoint of a side is aligned with the Y+ axis. Default: false
// realign = If false, vertex 0 will lie on the X+ axis. If true then the midpoint of the last edge will lie on the X+ axis, and vertex 0 will be below the X axis. Default: false
// align_tip = If given as a 2D vector, rotates the whole shape so that the first vertex points in that direction. This occurs before spin.
// align_side = If given as a 2D vector, rotates the whole shape so that the normal of side0 points in that direction. This occurs before spin.
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER`
@ -535,7 +612,7 @@ module pentagon(r, d, or, od, ir, id, side, rounding=0, realign=false, align_tip
// id = Inside diameter, at center of sides.
// side = Length of each side.
// rounding = Radius of rounding for the tips of the polygon. Default: 0 (no rounding)
// realign = If false, a tip is aligned with the Y+ axis. If true, the midpoint of a side is aligned with the Y+ axis. Default: false
// realign = If false, vertex 0 will lie on the X+ axis. If true then the midpoint of the last edge will lie on the X+ axis, and vertex 0 will be below the X axis. Default: false
// align_tip = If given as a 2D vector, rotates the whole shape so that the first vertex points in that direction. This occurs before spin.
// align_side = If given as a 2D vector, rotates the whole shape so that the normal of side0 points in that direction. This occurs before spin.
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER`
@ -598,7 +675,7 @@ module hexagon(r, d, or, od, ir, id, side, rounding=0, realign=false, align_tip,
// id = Inside diameter, at center of sides.
// side = Length of each side.
// rounding = Radius of rounding for the tips of the polygon. Default: 0 (no rounding)
// realign = If false, a tip is aligned with the Y+ axis. If true, the midpoint of a side is aligned with the Y+ axis. Default: false
// realign = If false, vertex 0 will lie on the X+ axis. If true then the midpoint of the last edge will lie on the X+ axis, and vertex 0 will be below the X axis. Default: false
// align_tip = If given as a 2D vector, rotates the whole shape so that the first vertex points in that direction. This occurs before spin.
// align_side = If given as a 2D vector, rotates the whole shape so that the normal of side0 points in that direction. This occurs before spin.
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER`
@ -808,7 +885,7 @@ module trapezoid(h, w1, w2, angle, shift=0, chamfer=0, rounding=0, anchor=CENTER
// d/od = The diameter to the tips of the star.
// id = The diameter to the inner corners of the star.
// step = Calculates the radius of the inner star corners by virtually drawing a straight line `step` tips around the star. 2 <= step < n/2
// realign = If false, a tip is aligned with the Y+ axis. If true, an inner corner is aligned with the Y+ axis. Default: false
// realign = If false, vertex 0 will lie on the X+ axis. If true then the midpoint of the last edge will lie on the X+ axis, and vertex 0 will be below the X axis. Default: false
// align_tip = If given as a 2D vector, rotates the whole shape so that the first star tip points in that direction. This occurs before spin.
// align_pit = If given as a 2D vector, rotates the whole shape so that the first inner corner is pointed towards that direction. This occurs before spin.
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER`

View file

@ -8,7 +8,7 @@ module test_sort() {
assert(sort([[8,0],[3,1],[9,2],[4,3],[3,4],[1,5],[8,6]],idx=1) == [[8,0],[3,1],[9,2],[4,3],[3,4],[1,5],[8,6]]);
assert(sort(["cat", "oat", "sat", "bat", "vat", "rat", "pat", "mat", "fat", "hat", "eat"])
== ["bat", "cat", "eat", "fat", "hat", "mat", "oat", "pat", "rat", "sat", "vat"]);
assert(sort(enumerate([[2,3,4],[1,2,3],[2,4,3]]),idx=1)==[[1,[1,2,3]], [0,[2,3,4]], [2,[2,4,3]]]);
assert(sort([[0,[2,3,4]],[1,[1,2,3]],[2,[2,4,3]]],idx=1)==[[1,[1,2,3]], [0,[2,3,4]], [2,[2,4,3]]]);
assert(sort([0,"1",[1,0],2,"a",[1]])== [0,2,"1","a",[1],[1,0]]);
assert(sort([["oat",0], ["cat",1], ["bat",3], ["bat",2], ["fat",3]])== [["bat",2],["bat",3],["cat",1],["fat",3],["oat",0]]);
}

View file

@ -254,14 +254,6 @@ module test_idx() {
test_idx();
module test_enumerate() {
assert(enumerate(["a","b","c"]) == [[0,"a"], [1,"b"], [2,"c"]]);
assert(enumerate([[88,"a"],[76,"b"],[21,"c"]], idx=1) == [[0,"a"], [1,"b"], [2,"c"]]);
assert(enumerate([["cat","a",12],["dog","b",10],["log","c",14]], idx=[1:2]) == [[0,"a",12], [1,"b",10], [2,"c",14]]);
}
test_enumerate();
module test_shuffle() {
nums1 = count(100);
nums2 = shuffle(nums1,33);
@ -375,16 +367,6 @@ module test_repeat_entries() {
test_repeat_entries();
module test_zip() {
v1 = [1,2,3,4];
v2 = [5,6,7];
v3 = [8,9,10,11];
assert(zip(v1,v3) == [[1,8],[2,9],[3,10],[4,11]]);
assert(zip([v1,v3]) == [[1,8],[2,9],[3,10],[4,11]]);
}
test_zip();
module test_list_to_matrix() {
v = [1,2,3,4,5,6];
assert(list_to_matrix(v,2) == [[1,2], [3,4], [5,6]]);