diff --git a/distributors.scad b/distributors.scad index 387b88b..4932463 100644 --- a/distributors.scad +++ b/distributors.scad @@ -1020,6 +1020,134 @@ module ovoid_spread(r=undef, d=undef, n=100, cone_ang=90, scale=[1,1,1], perp=tr +// Module: path_spread() +// +// Description: +// Uniformly spreads out copies of children along a path. Copies are located based on path length. If you specify `n` but not spacing then `n` copies will be placed +// with one at path[0] of `closed` is true, or spanning the entire path from start to end if `closed` is false. +// If you specify `spacing` but not `n` then copies will spread out starting from one at path[0] for `closed=true` or at the path center for open paths. +// If you specify `sp` then the copies will start at `sp`. +// +// Usage: +// path_spread(path), [n], [spacing], [sp], [rotate_children], [closed]) ... +// +// Arguments: +// path = the path where children are placed +// n = number of copies +// spacing = space between copies +// sp = if given, copies will start distance sp from the path start and spread beyond that point +// +// Side Effects: +// `$pos` is set to the center of each copy +// `$idx` is set to the index number of each copy. In the case of closed paths the first copy is at `path[0]` unless you give `sp`. +// `$dir` is set to the direction vector of the path at the point where the copy is placed. +// `$normal` is set to the direction of the normal vector to the path direction that is coplanar with the path at this point +// +// Example(2D): +// spiral = [for(theta=[0:360*8]) theta * [cos(theta), sin(theta)]]/100; +// stroke(spiral,width=.25); +// color("red") path_spread(spiral, n=100) circle(r=1); +// Example(2D): +// circle = regular_ngon(n=64, or=10); +// stroke(circle,width=1,closed=true); +// color("green") path_spread(circle, n=7, closed=true) circle(r=1+$idx/3); +// Example(2D): +// heptagon = regular_ngon(n=7, or=10); +// stroke(heptagon, width=1, closed=true); +// color("purple") path_spread(heptagon, n=9, closed=true) rect([0.5,3],anchor=FRONT); +// Example(2D): Direction at the corners is the average of the two adjacent edges +// heptagon = regular_ngon(n=7, or=10); +// stroke(heptagon, width=1, closed=true); +// color("purple") path_spread(heptagon, n=7, closed=true) rect([0.5,3],anchor=FRONT); +// Example(2D): Don't rotate the children +// heptagon = regular_ngon(n=7, or=10); +// stroke(heptagon, width=1, closed=true); +// color("red") path_spread(heptagon, n=9, closed=true, rotate_children=false) rect([0.5,3],anchor=FRONT); +// Example(2D): Open path, specify `n` +// sinwav = [for(theta=[0:360]) 5*[theta/180, sin(theta)]]; +// stroke(sinwav,width=.1); +// color("red") path_spread(sinwav, n=5) rect([.2,1.5],anchor=FRONT); +// Example(2D): Open path, specify `n` and `spacing` +// sinwav = [for(theta=[0:360]) 5*[theta/180, sin(theta)]]; +// stroke(sinwav,width=.1); +// color("red") path_spread(sinwav, n=5, spacing=1) rect([.2,1.5],anchor=FRONT); +// Example(2D): Closed path, specify `n` and `spacing`, copies centered around circle[0] +// circle = regular_ngon(n=64,or=10); +// stroke(circle,width=.1,closed=true); +// color("red") path_spread(circle, n=10, spacing=1, closed=true) rect([.2,1.5],anchor=FRONT); +// Example(2D): Open path, specify `spacing` +// sinwav = [for(theta=[0:360]) 5*[theta/180, sin(theta)]]; +// stroke(sinwav,width=.1); +// color("red") path_spread(sinwav, spacing=5) rect([.2,1.5],anchor=FRONT); +// Example(2D): Open path, specify `sp` +// sinwav = [for(theta=[0:360]) 5*[theta/180, sin(theta)]]; +// stroke(sinwav,width=.1); +// color("red") path_spread(sinwav, n=5, sp=18) rect([.2,1.5],anchor=FRONT); +// Example(2D): +// wedge = arc(angle=[0,100], r=10, $fn=64); +// difference(){ +// polygon(concat([[0,0]],wedge)); +// path_spread(wedge,n=5,spacing=3) fwd(.1) rect([1,4],anchor=FRONT); +// } +// Example(Spin,VPD=115): 3d example, with children rotated into the plane of the path +// tilted_circle = lift_plane([[0,0,0], [5,0,5], [0,2,3]],regular_ngon(n=64, or=12)); +// path_sweep(regular_ngon(n=16,or=.1),tilted_circle); +// path_spread(tilted_circle, n=15,closed=true) { +// color("blue") cyl(h=3,r=.2, anchor=BOTTOM); // z-aligned cylinder +// color("red") xcyl(h=10,r=.2, anchor=FRONT+LEFT); // x-aligned cylinder +// } +// Example(Spin,VPD=115): 3d example, with rotate_children set to false +// tilted_circle = lift_plane([[0,0,0], [5,0,5], [0,2,3]], regular_ngon(n=64, or=12)); +// path_sweep(regular_ngon(n=16,or=.1),tilted_circle); +// path_spread(tilted_circle, n=25,rotate_children=false,closed=true) { +// color("blue") cyl(h=3,r=.2, anchor=BOTTOM); // z-aligned cylinder +// color("red") xcyl(h=10,r=.2, anchor=FRONT+LEFT); // x-aligned cylinder +// } +module path_spread(path, n, spacing, sp=undef, rotate_children=true, closed=false) +{ + length = path_length(path,closed); + distances = + is_def(sp)? ( // Start point given + is_def(n) && is_def(spacing)? count(n,sp,spacing) : + is_def(n)? lerpn(sp, length, n) : + list([sp:spacing:length]) + ) + : is_def(n) && is_undef(spacing)? lerpn(0,length,n,!closed) // N alone given + : ( // No start point and spacing is given, N maybe given + let( + n = is_def(n)? n : floor(length/spacing)+(closed?0:1), + ptlist = count(n,0,spacing), + listcenter = mean(ptlist) + ) closed? + sort([for(entry=ptlist) posmod(entry-listcenter,length)]) : + [for(entry=ptlist) entry + length/2-listcenter ] + ); + distOK = is_def(n) || (min(distances)>=0 && max(distances)<=length); + assert(distOK,"Cannot fit all of the copies"); + cutlist = path_cut_points(path, distances, closed, direction=true); + planar = len(path[0])==2; + if (true) for(i=[0:1:len(cutlist)-1]) { + $pos = cutlist[i][0]; + $idx = i; + $dir = rotate_children ? (planar?[1,0]:[1,0,0]) : cutlist[i][2]; + $normal = rotate_children? (planar?[0,1]:[0,0,1]) : cutlist[i][3]; + translate($pos) { + if (rotate_children) { + if(planar) { + rot(from=[0,1],to=cutlist[i][3]) children(); + } else { + frame_map(x=cutlist[i][2], z=cutlist[i][3]) + children(); + } + } else { + children(); + } + } + } +} + + + ////////////////////////////////////////////////////////////////////// // Section: Reflectional Distributors ////////////////////////////////////////////////////////////////////// diff --git a/paths.scad b/paths.scad index 828c7ca..7529230 100644 --- a/paths.scad +++ b/paths.scad @@ -1458,133 +1458,6 @@ module path_extrude(path, convexity=10, clipsize=100) { } -// Module: path_spread() -// -// Description: -// Uniformly spreads out copies of children along a path. Copies are located based on path length. If you specify `n` but not spacing then `n` copies will be placed -// with one at path[0] of `closed` is true, or spanning the entire path from start to end if `closed` is false. -// If you specify `spacing` but not `n` then copies will spread out starting from one at path[0] for `closed=true` or at the path center for open paths. -// If you specify `sp` then the copies will start at `sp`. -// -// Usage: -// path_spread(path), [n], [spacing], [sp], [rotate_children], [closed]) ... -// -// Arguments: -// path = the path where children are placed -// n = number of copies -// spacing = space between copies -// sp = if given, copies will start distance sp from the path start and spread beyond that point -// -// Side Effects: -// `$pos` is set to the center of each copy -// `$idx` is set to the index number of each copy. In the case of closed paths the first copy is at `path[0]` unless you give `sp`. -// `$dir` is set to the direction vector of the path at the point where the copy is placed. -// `$normal` is set to the direction of the normal vector to the path direction that is coplanar with the path at this point -// -// Example(2D): -// spiral = [for(theta=[0:360*8]) theta * [cos(theta), sin(theta)]]/100; -// stroke(spiral,width=.25); -// color("red") path_spread(spiral, n=100) circle(r=1); -// Example(2D): -// circle = regular_ngon(n=64, or=10); -// stroke(circle,width=1,closed=true); -// color("green") path_spread(circle, n=7, closed=true) circle(r=1+$idx/3); -// Example(2D): -// heptagon = regular_ngon(n=7, or=10); -// stroke(heptagon, width=1, closed=true); -// color("purple") path_spread(heptagon, n=9, closed=true) rect([0.5,3],anchor=FRONT); -// Example(2D): Direction at the corners is the average of the two adjacent edges -// heptagon = regular_ngon(n=7, or=10); -// stroke(heptagon, width=1, closed=true); -// color("purple") path_spread(heptagon, n=7, closed=true) rect([0.5,3],anchor=FRONT); -// Example(2D): Don't rotate the children -// heptagon = regular_ngon(n=7, or=10); -// stroke(heptagon, width=1, closed=true); -// color("red") path_spread(heptagon, n=9, closed=true, rotate_children=false) rect([0.5,3],anchor=FRONT); -// Example(2D): Open path, specify `n` -// sinwav = [for(theta=[0:360]) 5*[theta/180, sin(theta)]]; -// stroke(sinwav,width=.1); -// color("red") path_spread(sinwav, n=5) rect([.2,1.5],anchor=FRONT); -// Example(2D): Open path, specify `n` and `spacing` -// sinwav = [for(theta=[0:360]) 5*[theta/180, sin(theta)]]; -// stroke(sinwav,width=.1); -// color("red") path_spread(sinwav, n=5, spacing=1) rect([.2,1.5],anchor=FRONT); -// Example(2D): Closed path, specify `n` and `spacing`, copies centered around circle[0] -// circle = regular_ngon(n=64,or=10); -// stroke(circle,width=.1,closed=true); -// color("red") path_spread(circle, n=10, spacing=1, closed=true) rect([.2,1.5],anchor=FRONT); -// Example(2D): Open path, specify `spacing` -// sinwav = [for(theta=[0:360]) 5*[theta/180, sin(theta)]]; -// stroke(sinwav,width=.1); -// color("red") path_spread(sinwav, spacing=5) rect([.2,1.5],anchor=FRONT); -// Example(2D): Open path, specify `sp` -// sinwav = [for(theta=[0:360]) 5*[theta/180, sin(theta)]]; -// stroke(sinwav,width=.1); -// color("red") path_spread(sinwav, n=5, sp=18) rect([.2,1.5],anchor=FRONT); -// Example(2D): -// wedge = arc(angle=[0,100], r=10, $fn=64); -// difference(){ -// polygon(concat([[0,0]],wedge)); -// path_spread(wedge,n=5,spacing=3) fwd(.1) rect([1,4],anchor=FRONT); -// } -// Example(Spin,VPD=115): 3d example, with children rotated into the plane of the path -// tilted_circle = lift_plane([[0,0,0], [5,0,5], [0,2,3]],regular_ngon(n=64, or=12)); -// path_sweep(regular_ngon(n=16,or=.1),tilted_circle); -// path_spread(tilted_circle, n=15,closed=true) { -// color("blue") cyl(h=3,r=.2, anchor=BOTTOM); // z-aligned cylinder -// color("red") xcyl(h=10,r=.2, anchor=FRONT+LEFT); // x-aligned cylinder -// } -// Example(Spin,VPD=115): 3d example, with rotate_children set to false -// tilted_circle = lift_plane([[0,0,0], [5,0,5], [0,2,3]], regular_ngon(n=64, or=12)); -// path_sweep(regular_ngon(n=16,or=.1),tilted_circle); -// path_spread(tilted_circle, n=25,rotate_children=false,closed=true) { -// color("blue") cyl(h=3,r=.2, anchor=BOTTOM); // z-aligned cylinder -// color("red") xcyl(h=10,r=.2, anchor=FRONT+LEFT); // x-aligned cylinder -// } -module path_spread(path, n, spacing, sp=undef, rotate_children=true, closed=false) -{ - length = path_length(path,closed); - distances = - is_def(sp)? ( // Start point given - is_def(n) && is_def(spacing)? count(n,sp,spacing) : - is_def(n)? lerpn(sp, length, n) : - list([sp:spacing:length]) - ) - : is_def(n) && is_undef(spacing)? lerpn(0,length,n,!closed) // N alone given - : ( // No start point and spacing is given, N maybe given - let( - n = is_def(n)? n : floor(length/spacing)+(closed?0:1), - ptlist = count(n,0,spacing), - listcenter = mean(ptlist) - ) closed? - sort([for(entry=ptlist) posmod(entry-listcenter,length)]) : - [for(entry=ptlist) entry + length/2-listcenter ] - ); - distOK = is_def(n) || (min(distances)>=0 && max(distances)<=length); - assert(distOK,"Cannot fit all of the copies"); - cutlist = path_cut_points(path, distances, closed, direction=true); - planar = len(path[0])==2; - if (true) for(i=[0:1:len(cutlist)-1]) { - $pos = cutlist[i][0]; - $idx = i; - $dir = rotate_children ? (planar?[1,0]:[1,0,0]) : cutlist[i][2]; - $normal = rotate_children? (planar?[0,1]:[0,0,1]) : cutlist[i][3]; - translate($pos) { - if (rotate_children) { - if(planar) { - rot(from=[0,1],to=cutlist[i][3]) children(); - } else { - frame_map(x=cutlist[i][2], z=cutlist[i][3]) - children(); - } - } else { - children(); - } - } - } -} - - function _cut_interp(pathcut, path, data) = [for(entry=pathcut) let(