1//////////////////////////////////////////////////////////////////////
   2// LibFile: distributors.scad
   3//   Functions and modules to distribute children or copies of children onto
   4//   a line, a grid, or an arbitrary path.  The $idx mechanism means that
   5//   the "copies" of children can vary.  Also includes shortcuts for mirroring.  
   6// Includes:
   7//   include <BOSL2/std.scad>
   8// FileGroup: Basic Modeling
   9// FileSummary: Copy or distribute objects onto a line, grid, or path.  Mirror shortcuts. 
  10// FileFootnotes: STD=Included in std.scad
  11//////////////////////////////////////////////////////////////////////
  12
  13
  14//////////////////////////////////////////////////////////////////////
  15// Section: Translating copies of all the children
  16//////////////////////////////////////////////////////////////////////
  17
  18
  19// Module: move_copies()
  20//
  21// Description:
  22//   Translates copies of all children to each given translation offset.
  23//
  24// Usage:
  25//   move_copies(a) CHILDREN;
  26//
  27// Arguments:
  28//   a = Array of XYZ offset vectors. Default `[[0,0,0]]`
  29//
  30// Side Effects:
  31//   `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
  32//   `$idx` is set to the index number of each child being copied.
  33//
  34// Example:
  35//   #sphere(r=10);
  36//   move_copies([[-25,-25,0], [25,-25,0], [0,0,50], [0,25,0]]) sphere(r=10);
  37module move_copies(a=[[0,0,0]])
  38{
  39    req_children($children);
  40    assert(is_list(a));
  41    for ($idx = idx(a)) {
  42        $pos = a[$idx];
  43        assert(is_vector($pos),"move_copies offsets should be a 2d or 3d vector.");
  44        translate($pos) children();
  45    }
  46}
  47
  48
  49// Module: xcopies()
  50//
  51// Description:
  52//   Places out `n` copies of the children along a line on the X axis.
  53//
  54// Usage:
  55//   xcopies(spacing, [n], [sp]) CHILDREN;
  56//   xcopies(l, [n], [sp]) CHILDREN;
  57//   xcopies(LIST) CHILDREN;
  58//
  59// Arguments:
  60//   spacing = Given a scalar, specifies a uniform spacing between copies. Given a list of scalars, each one gives a specific position along the line. (Default: 1.0)
  61//   n = Number of copies to place. (Default: 2)
  62//   l = Length to place copies over.
  63//   sp = If given as a point, copies will be placed on a line to the right of starting position `sp`.  If given as a scalar, copies will be placed on a line to the right of starting position `[sp,0,0]`.  If not given, copies will be placed along a line that is centered at [0,0,0].
  64//
  65// Side Effects:
  66//   `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
  67//   `$idx` is set to the index number of each child being copied.
  68//
  69// Examples:
  70//   xcopies(20) sphere(3);
  71//   xcopies(20, n=3) sphere(3);
  72//   xcopies(spacing=15, l=50) sphere(3);
  73//   xcopies(n=4, l=30, sp=[0,10,0]) sphere(3);
  74// Example:
  75//   xcopies(10, n=3) {
  76//       cube(size=[1,3,1],center=true);
  77//       cube(size=[3,1,1],center=true);
  78//   }
  79// Example:
  80//   xcopies([1,2,3,5,7]) sphere(d=1);
  81module xcopies(spacing, n, l, sp)
  82{
  83    req_children($children);
  84    dir = RIGHT;
  85    sp = is_finite(sp)? (sp*dir) : sp;
  86    if (is_vector(spacing)) {
  87        translate(default(sp,[0,0,0])) {
  88            for (i = idx(spacing)) {
  89                $idx = i;
  90                $pos = spacing[i]*dir;
  91                translate($pos) children();
  92            }
  93        }
  94    } else {
  95        line_copies(
  96            l=u_mul(l,dir),
  97            spacing=u_mul(spacing,dir),
  98            n=n, p1=sp
  99        ) children();
 100    }
 101}
 102
 103
 104// Module: ycopies()
 105//
 106// Description:
 107//   Places `n` copies of the children along a line on the Y axis.
 108//
 109// Usage:
 110//   ycopies(spacing, [n], [sp]) CHILDREN;
 111//   ycopies(l, [n], [sp]) CHILDREN;
 112//   ycopies(LIST) CHILDREN;
 113//
 114// Arguments:
 115//   spacing = Given a scalar, specifies a uniform spacing between copies. Given a list of scalars, each one gives a specific position along the line. (Default: 1.0)
 116//   n = Number of copies to place on the line. (Default: 2)
 117//   l = Length to place copies over.
 118//   sp = If given as a point, copies will be place on a line back from starting position `sp`.  If given as a scalar, copies will be placed on a line back from starting position `[0,sp,0]`.  If not given, copies will be placed along a line that is centered at [0,0,0].
 119//
 120// Side Effects:
 121//   `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
 122//   `$idx` is set to the index number of each child being copied.
 123//
 124// Examples:
 125//   ycopies(20) sphere(3);
 126//   ycopies(20, n=3) sphere(3);
 127//   ycopies(spacing=15, l=50) sphere(3);
 128//   ycopies(n=4, l=30, sp=[10,0,0]) sphere(3);
 129// Example:
 130//   ycopies(10, n=3) {
 131//       cube(size=[1,3,1],center=true);
 132//       cube(size=[3,1,1],center=true);
 133//   }
 134// Example:
 135//   ycopies([1,2,3,5,7]) sphere(d=1);
 136module ycopies(spacing, n, l, sp)
 137{
 138    req_children($children);  
 139    dir = BACK;
 140    sp = is_finite(sp)? (sp*dir) : sp;
 141    if (is_vector(spacing)) {
 142        translate(default(sp,[0,0,0])) {
 143            for (i = idx(spacing)) {
 144                $idx = i;
 145                $pos = spacing[i]*dir;
 146                translate($pos) children();
 147            }
 148        }
 149    } else {
 150        line_copies(
 151            l=u_mul(l,dir),
 152            spacing=u_mul(spacing,dir),
 153            n=n, p1=sp
 154        ) children();
 155    }
 156}
 157
 158
 159// Module: zcopies()
 160//
 161// Description:
 162//   Places `n` copies of the children along a line on the Z axis.
 163//
 164// Usage:
 165//   zcopies(spacing, [n], [sp]) CHILDREN;
 166//   zcopies(l, [n], [sp]) CHILDREN;
 167//   zcopies(LIST) CHILDREN;
 168//
 169// Arguments:
 170//   spacing = Given a scalar, specifies a uniform spacing between copies. Given a list of scalars, each one gives a specific position along the line. (Default: 1.0)
 171//   n = Number of copies to place. (Default: 2)
 172//   l = Length to place copies over.
 173//   sp = If given as a point, copies will be placed on a line up from starting position `sp`.  If given as a scalar, copies will be placed on a line up from starting position `[0,0,sp]`.  If not given, copies will be placed on a line that is centered at [0,0,0].
 174//
 175// Side Effects:
 176//   `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
 177//   `$idx` is set to the index number of each child being copied.
 178//
 179// Examples:
 180//   zcopies(20) sphere(3);
 181//   zcopies(20, n=3) sphere(3);
 182//   zcopies(spacing=15, l=50) sphere(3);
 183//   zcopies(n=4, l=30, sp=[10,0,0]) sphere(3);
 184// Example:
 185//   zcopies(10, n=3) {
 186//       cube(size=[1,3,1],center=true);
 187//       cube(size=[3,1,1],center=true);
 188//   }
 189// Example: Cubic sphere packing
 190//   s = 20;
 191//   s2 = s * sin(45);
 192//   zcopies(s2,n=8) union()
 193//       grid_copies([s2,s2],n=8,stagger=($idx%2)? true : "alt")
 194//          sphere(d=s);
 195// Example: Hexagonal sphere packing
 196//   s = 20;
 197//   xyr = adj_ang_to_hyp(s/2,30);
 198//   h = hyp_adj_to_opp(s,xyr);
 199//   zcopies(h,n=8) union()
 200//       back(($idx%2)*xyr*cos(60))
 201//           grid_copies(s,n=[12,7],stagger=($idx%2)? "alt" : true)
 202//               sphere(d=s);
 203// Example:
 204//   zcopies([1,2,3,5,7]) sphere(d=1);
 205module zcopies(spacing, n, l, sp)
 206{
 207    req_children($children);  
 208    dir = UP;
 209    sp = is_finite(sp)? (sp*dir) : sp;
 210    if (is_vector(spacing)) {
 211        translate(default(sp,[0,0,0])) {
 212            for (i = idx(spacing)) {
 213                $idx = i;
 214                $pos = spacing[i]*dir;
 215                translate($pos) children();
 216            }
 217        }
 218    } else {
 219        line_copies(
 220            l=u_mul(l,dir),
 221            spacing=u_mul(spacing,dir),
 222            n=n, p1=sp
 223        ) children();
 224    }
 225}
 226
 227
 228
 229// Function&Module: line_copies()
 230//
 231// Usage: Place `n` copies at a given spacing along the line
 232//   line_copies(spacing, [n], [p1=]) CHILDREN;
 233// Usage: Place as many copies as will fit at a given spacing
 234//   line_copies(spacing, [l=], [p1=]) CHILDREN;
 235// Usage: Place `n` copies along the length of the line
 236//   line_copies([n=], [l=], [p1=]) CHILDREN;
 237// Usage: Place `n` copies along the line from `p1` to `p2`
 238//   line_copies([n=], [p1=], [p2=]) CHILDREN;
 239// Usage: Place copies at the given spacing, centered along the line from `p1` to `p2`
 240//   line_copies([spacing], [p1=], [p2=]) CHILDREN;
 241// Usage: As a function
 242//   pts = line_copies([spacing], [n], [p1=]);
 243//   pts = line_copies([spacing], [l=], [p1=]);
 244//   pts = line_copies([n=], [l=], [p1=]);
 245//   pts = line_copies([n=], [p1=], [p2=]);
 246//   pts = line_copies([spacing], [p1=], [p2=]);
 247// Description:
 248//   When called as a function, returns a list of points at evenly spaced positions along a line.
 249//   When called as a module, copies `children()` at one or more evenly spaced positions along a line.
 250//   By default, the line will be centered at the origin, unless the starting point `p1` is given.
 251//   The line will be pointed towards `RIGHT` (X+) unless otherwise given as a vector in `l`,
 252//   `spacing`, or `p1`/`p2`.  The psotion of the copies is specified in one of several ways:
 253//   .
 254//   If You Know...                   | Then Use Something Like...
 255//   -------------------------------- | --------------------------------
 256//   Spacing distance, Count          | `line_copies(spacing=10, n=5) ...` or `line_copies(10, n=5) ...`
 257//   Spacing vector, Count            | `line_copies(spacing=[10,5], n=5) ...` or `line_copies([10,5], n=5) ...`
 258//   Spacing distance, Line length    | `line_copies(spacing=10, l=50) ...` or `line_copies(10, l=50) ...`
 259//   Spacing distance, Line vector    | `line_copies(spacing=10, l=[50,30]) ...` or `line_copies(10, l=[50,30]) ...`
 260//   Spacing vector, Line length      | `line_copies(spacing=[10,5], l=50) ...` or `line_copies([10,5], l=50) ...`
 261//   Line length, Count               | `line_copies(l=50, n=5) ...`
 262//   Line vector, Count               | `line_copies(l=[50,40], n=5) ...`
 263//   Line endpoints, Count            | `line_copies(p1=[10,10], p2=[60,-10], n=5) ...`
 264//   Line endpoints, Spacing distance | `line_copies(p1=[10,10], p2=[60,-10], spacing=10) ...`
 265//
 266// Arguments:
 267//   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.
 268//   n = Number of copies to distribute along the line. (Default: 2)
 269//   ---
 270//   l = Either the scalar length of the line, or a vector giving both the direction and length of the line.
 271//   p1 = If given, specifies the starting point of the line.
 272//   p2 = If given with `p1`, specifies the ending point of line, and indirectly calculates the line length.
 273//
 274// Side Effects:
 275//   `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
 276//   `$idx` is set to the index number of each child being copied.
 277//
 278// Examples:
 279//   line_copies(10) sphere(d=1);
 280//   line_copies(10, n=5) sphere(d=1);
 281//   line_copies([10,5], n=5) sphere(d=1);
 282//   line_copies(spacing=10, n=6) sphere(d=1);
 283//   line_copies(spacing=[10,5], n=6) sphere(d=1);
 284//   line_copies(spacing=10, l=50) sphere(d=1);
 285//   line_copies(spacing=10, l=[50,30]) sphere(d=1);
 286//   line_copies(spacing=[10,5], l=50) sphere(d=1);
 287//   line_copies(l=50, n=4) sphere(d=1);
 288//   line_copies(l=[50,-30], n=4) sphere(d=1);
 289// Example(FlatSpin,VPD=133):
 290//   line_copies(p1=[0,0,0], p2=[5,5,20], n=6) cube(size=[3,2,1],center=true);
 291// Example(FlatSpin,VPD=133):
 292//   line_copies(p1=[0,0,0], p2=[5,5,20], spacing=6) cube(size=[3,2,1],center=true);
 293// Example: All children are copied to each position
 294//   line_copies(l=20, n=3) {
 295//       cube(size=[1,3,1],center=true);
 296//       cube(size=[3,1,1],center=true);
 297//   }
 298// Example(2D): The functional form of line_copies() returns a list of points.
 299//   pts = line_copies([10,5],n=5);
 300//   move_copies(pts) circle(d=2);
 301
 302module line_of(spacing, n, l, p1, p2) {
 303    deprecate("line_copies");
 304    line_copies(spacing, n, l, p1, p2);
 305}
 306
 307module line_copies(spacing, n, l, p1, p2)
 308{
 309    req_children($children);
 310    pts = line_copies(spacing=spacing, n=n, l=l, p1=p1, p2=p2);
 311    for (i=idx(pts)) {
 312        $idx = i;
 313        $pos = pts[i];
 314        translate($pos) children();
 315    }
 316}
 317
 318function line_copies(spacing, n, l, p1, p2) =
 319    assert(is_undef(spacing) || is_finite(spacing) || is_vector(spacing))
 320    assert(is_undef(n) || is_finite(n))
 321    assert(is_undef(l) || is_finite(l) || is_vector(l))
 322    assert(is_undef(p1) || is_vector(p1))
 323    assert(is_undef(p2) || is_vector(p2))
 324    let(
 325        ll = !is_undef(l)? scalar_vec3(l, 0) :
 326            (!is_undef(spacing) && !is_undef(n))? ((n-1) * scalar_vec3(spacing, 0)) :
 327            (!is_undef(p1) && !is_undef(p2))? point3d(p2-p1) :
 328            undef,
 329        cnt = !is_undef(n)? n :
 330            (!is_undef(spacing) && !is_undef(ll))? floor(norm(ll) / norm(scalar_vec3(spacing, 0)) + 1.000001) :
 331            2,
 332        spc = cnt<=1? [0,0,0] :
 333            is_undef(spacing)? (ll/(cnt-1)) :
 334            is_num(spacing) && !is_undef(ll)? (ll/(cnt-1)) :
 335            scalar_vec3(spacing, 0)
 336    )
 337    assert(!is_undef(cnt), "Need two of `spacing`, 'l', 'n', or `p1`/`p2` arguments in `line_copies()`.")
 338    let( spos = !is_undef(p1)? point3d(p1) : -(cnt-1)/2 * spc )
 339    [for (i=[0:1:cnt-1]) i * spc + spos];
 340
 341
 342
 343
 344
 345
 346// Module: grid_copies()
 347//
 348// Description:
 349//   Makes a square or hexagonal grid of copies of children, with an optional masking polygon or region.  
 350//
 351// Usage:
 352//   grid_copies(spacing, size=, [stagger=], [scale=], [inside=]) CHILDREN;
 353//   grid_copies(n=, size=, [stagger=], [scale=], [inside=]) CHILDREN;
 354//   grid_copies(spacing, [n], [stagger=], [scale=], [inside=]) CHILDREN;
 355//   grid_copies(n=, inside=, [stagger], [scale]) CHILDREN;
 356//
 357// Arguments:
 358//   spacing = Distance between copies in [X,Y] or scalar distance.
 359//   n = How many columns and rows of copies to make.  Can be given as `[COLS,ROWS]`, or just as a scalar that specifies both.  If staggered, count both staggered and unstaggered columns and rows.  Default: 2 (3 if staggered)
 360//   size = The [X,Y] size to spread the copies over.
 361//   ---
 362//   stagger = If true, make a staggered (hexagonal) grid.  If false, make square grid.  If `"alt"`, makes alternate staggered pattern.  Default: false
 363//   inside = If given a list of polygon points, or a region, only creates copies whose center would be inside the polygon or region.  Polygon can be concave and/or self crossing.
 364//   nonzero = If inside is set to a polygon with self-crossings then use the nonzero method for deciding if points are in the polygon.  Default: false
 365//
 366// Side Effects:
 367//   `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
 368//   `$col` is set to the integer column number for each child.
 369//   `$row` is set to the integer row number for each child.
 370//
 371// Examples:
 372//   grid_copies(size=50, spacing=10) cylinder(d=10, h=1);
 373//   grid_copies(size=50, spacing=[10,15]) cylinder(d=10, h=1);
 374//   grid_copies(spacing=10, n=[13,7], stagger=true) cylinder(d=6, h=5);
 375//   grid_copies(spacing=10, n=[13,7], stagger="alt") cylinder(d=6, h=5);
 376//   grid_copies(size=50, n=11, stagger=true) cylinder(d=5, h=1);
 377//
 378// Example:
 379//   poly = [[-25,-25], [25,25], [-25,25], [25,-25]];
 380//   grid_copies(spacing=5, stagger=true, inside=poly)
 381//      zrot(180/6) cylinder(d=5, h=1, $fn=6);
 382//   %polygon(poly);
 383//
 384// Example: Using `$row` and `$col`
 385//   grid_copies(spacing=8, n=8)
 386//       color(($row+$col)%2?"black":"red")
 387//           cube([8,8,0.01], center=false);
 388//
 389// Example:
 390//   // Makes a grid of hexagon pillars whose tops are all
 391//   // angled to reflect light at [0,0,50], if they were shiny.
 392//   hexregion = circle(r=50.01,$fn=6);
 393//   grid_copies(spacing=10, stagger=true, inside=hexregion) union() {
 394//       // Note: The union() is needed or else $pos will be
 395//       //   inexplicably unreadable.
 396//       ref_v = (unit([0,0,50]-point3d($pos)) + UP)/2;
 397//       half_of(v=-ref_v, cp=[0,0,5])
 398//           zrot(180/6)
 399//               cylinder(h=20, d=10/cos(180/6)+0.01, $fn=6);
 400//   }
 401
 402function grid_copies(spacing, n, size, stagger=false, inside=undef, nonzero) = no_function("grid_copies");
 403module grid2d(spacing, n, size, stagger=false, inside=undef, nonzero)
 404{
 405   deprecate("grid_copies");
 406   grid_copies(spacing, n, size, stagger, inside, nonzero) children();
 407}   
 408
 409module grid_copies(spacing, n, size, stagger=false, inside=undef, nonzero)
 410{
 411    req_children($children);    
 412    dummy = assert(in_list(stagger, [false, true, "alt"]));
 413    bounds = is_undef(inside)? undef :
 414        is_path(inside)? pointlist_bounds(inside) :
 415        assert(is_region(inside))
 416        pointlist_bounds(flatten(inside));
 417    nonzero = is_path(inside) ? default(nonzero,false)
 418            : assert(is_undef(nonzero), "nonzero only allowed if inside is a polygon")
 419              false;
 420    size = is_num(size)? [size, size] :
 421        is_vector(size)? assert(len(size)==2) size :
 422        bounds!=undef? [
 423            for (i=[0:1]) 2*max(abs(bounds[0][i]),bounds[1][i])
 424        ] : undef;
 425    spacing = is_num(spacing)? (
 426            stagger!=false? polar_to_xy(spacing,60) :
 427            [spacing,spacing]
 428        ) :
 429        is_vector(spacing)? assert(len(spacing)==2) spacing :
 430        size!=undef? (
 431            is_num(n)? v_div(size,(n-1)*[1,1]) :
 432            is_vector(n)? assert(len(n)==2) v_div(size,n-[1,1]) :
 433            v_div(size,(stagger==false? [1,1] : [2,2]))
 434        ) :
 435        undef;
 436    n = is_num(n)? [n,n] :
 437        is_vector(n)? assert(len(n)==2) n :
 438        size!=undef && spacing!=undef? v_floor(v_div(size,spacing))+[1,1] :
 439        [2,2];
 440    offset = v_mul(spacing, n-[1,1])/2;
 441    if (stagger == false) {
 442        for (row = [0:1:n.y-1]) {
 443            for (col = [0:1:n.x-1]) {
 444                pos = v_mul([col,row],spacing) - offset;
 445                if (
 446                    is_undef(inside) ||
 447                    (is_path(inside) && point_in_polygon(pos, inside, nonzero=nonzero)>=0) ||
 448                    (is_region(inside) && point_in_region(pos, inside)>=0)
 449                ) {
 450                    $col = col;
 451                    $row = row;
 452                    $pos = pos;
 453                    translate(pos) children();
 454                }
 455            }
 456        }
 457    } else {
 458        // stagger == true or stagger == "alt"
 459        staggermod = (stagger == "alt")? 1 : 0;
 460        cols1 = ceil(n.x/2);
 461        cols2 = n.x - cols1;
 462        for (row = [0:1:n.y-1]) {
 463            rowcols = ((row%2) == staggermod)? cols1 : cols2;
 464            if (rowcols > 0) {
 465                for (col = [0:1:rowcols-1]) {
 466                    rowdx = (row%2 != staggermod)? spacing.x : 0;
 467                    pos = v_mul([2*col,row],spacing) + [rowdx,0] - offset;
 468                    if (
 469                        is_undef(inside) ||
 470                        (is_path(inside) && point_in_polygon(pos, inside, nonzero=nonzero)>=0) ||
 471                        (is_region(inside) && point_in_region(pos, inside)>=0)
 472                    ) {
 473                        $col = col * 2 + ((row%2!=staggermod)? 1 : 0);
 474                        $row = row;
 475                        $pos = pos;
 476                        translate(pos) children();
 477                    }
 478                }
 479            }
 480        }
 481    }
 482}
 483
 484
 485//////////////////////////////////////////////////////////////////////
 486// Section: Rotating copies of all children
 487//////////////////////////////////////////////////////////////////////
 488
 489
 490// Module: rot_copies()
 491//
 492// Description:
 493//   Given a list of [X,Y,Z] rotation angles in `rots`, rotates copies of the children to each of those angles, regardless of axis of rotation.
 494//   Given a list of scalar angles in `rots`, rotates copies of the children to each of those angles around the axis of rotation.
 495//   If given a vector `v`, that becomes the axis of rotation.  Default axis of rotation is UP.
 496//   If given a count `n`, makes that many copies, rotated evenly around the axis.
 497//   If given an offset `delta`, translates each child by that amount before rotating them into place.  This makes rings.
 498//   If given a centerpoint `cp`, centers the ring around that centerpoint.
 499//   If `subrot` is true, each child will be rotated in place to keep the same size towards the center when making rings.
 500//   The first (unrotated) copy will be placed at the relative starting angle `sa`.
 501//
 502// Usage:
 503//   rot_copies(rots, [cp=], [sa=], [delta=], [subrot=]) CHILDREN;
 504//   rot_copies(rots, v, [cp=], [sa=], [delta=], [subrot=]) CHILDREN;
 505//   rot_copies(n=, [v=], [cp=], [sa=], [delta=], [subrot=]) CHILDREN;
 506//
 507// Arguments:
 508//   rots = A list of [X,Y,Z] rotation angles in degrees.  If `v` is given, this will be a list of scalar angles in degrees to rotate around `v`.
 509//   v = If given, this is the vector of the axis to rotate around.
 510//   cp = Centerpoint to rotate around.  Default: `[0,0,0]`
 511//   ---
 512//   n = Optional number of evenly distributed copies, rotated around the axis.
 513//   sa = Starting angle, in degrees.  For use with `n`.  Angle is in degrees counter-clockwise.  Default: 0
 514//   delta = [X,Y,Z] amount to move away from cp before rotating.  Makes rings of copies.  Default: `[0,0,0]`
 515//   subrot = If false, don't sub-rotate children as they are copied around the ring.  Only makes sense when used with `delta`.  Default: `true`
 516//
 517// Side Effects:
 518//   `$ang` is set to the rotation angle (or XYZ rotation triplet) of each child copy, and can be used to modify each child individually.
 519//   `$idx` is set to the index value of each child copy.
 520//   `$axis` is set to the axis to rotate around, if `rots` was given as a list of angles instead of a list of [X,Y,Z] rotation angles.
 521//
 522// Example:
 523//   #cylinder(h=20, r1=5, r2=0);
 524//   rot_copies([[45,0,0],[0,45,90],[90,-45,270]]) cylinder(h=20, r1=5, r2=0);
 525//
 526// Example:
 527//   rot_copies([45, 90, 135], v=DOWN+BACK)
 528//       yrot(90) cylinder(h=20, r1=5, r2=0);
 529//   color("red",0.333) yrot(90) cylinder(h=20, r1=5, r2=0);
 530//
 531// Example:
 532//   rot_copies(n=6, v=DOWN+BACK)
 533//       yrot(90) cylinder(h=20, r1=5, r2=0);
 534//   color("red",0.333) yrot(90) cylinder(h=20, r1=5, r2=0);
 535//
 536// Example:
 537//   rot_copies(n=6, v=DOWN+BACK, delta=[10,0,0])
 538//       yrot(90) cylinder(h=20, r1=5, r2=0);
 539//   color("red",0.333) yrot(90) cylinder(h=20, r1=5, r2=0);
 540//
 541// Example:
 542//   rot_copies(n=6, v=UP+FWD, delta=[10,0,0], sa=45)
 543//       yrot(90) cylinder(h=20, r1=5, r2=0);
 544//   color("red",0.333) yrot(90) cylinder(h=20, r1=5, r2=0);
 545//
 546// Example:
 547//   rot_copies(n=6, v=DOWN+BACK, delta=[20,0,0], subrot=false)
 548//       yrot(90) cylinder(h=20, r1=5, r2=0);
 549//   color("red",0.333) yrot(90) cylinder(h=20, r1=5, r2=0);
 550module rot_copies(rots=[], v=undef, cp=[0,0,0], n, sa=0, offset=0, delta=[0,0,0], subrot=true)
 551{
 552    req_children($children);  
 553    sang = sa + offset;
 554    angs = !is_undef(n)?
 555        (n<=0? [] : [for (i=[0:1:n-1]) i/n*360+sang]) :
 556        rots==[]? [] :
 557        assert(!is_string(rots), "Argument rots must be an angle, a list of angles, or a range of angles.")
 558        assert(!is_undef(rots[0]), "Argument rots must be an angle, a list of angles, or a range of angles.")
 559        [for (a=rots) a];
 560    for ($idx = idx(angs)) {
 561        $ang = angs[$idx];
 562        $axis = v;
 563        translate(cp) {
 564            rotate(a=$ang, v=v) {
 565                translate(delta) {
 566                    rot(a=(subrot? sang : $ang), v=v, reverse=true) {
 567                        translate(-cp) {
 568                            children();
 569                        }
 570                    }
 571                }
 572            }
 573        }
 574    }
 575}
 576
 577
 578// Module: xrot_copies()
 579//
 580// Usage:
 581//   xrot_copies(rots, [cp], [r=|d=], [sa=], [subrot=]) CHILDREN;
 582//   xrot_copies(n=, [cp=], [r=|d=], [sa=], [subrot=]) CHILDREN;
 583//
 584// Description:
 585//   Given an array of angles, rotates copies of the children to each of those angles around the X axis.
 586//   If given a count `n`, makes that many copies, rotated evenly around the X axis.
 587//   If given a radius `r` (or diameter `d`), distributes children around a ring of that size around the X axis.
 588//   If given a centerpoint `cp`, centers the rotation around that centerpoint.
 589//   If `subrot` is true, each child will be rotated in place to keep the same size towards the center when making rings.
 590//   The first (unrotated) copy will be placed at the relative starting angle `sa`.
 591//
 592// Arguments:
 593//   rots = Optional array of rotation angles, in degrees, to make copies at.
 594//   cp = Centerpoint to rotate around.
 595//   ---
 596//   n = Optional number of evenly distributed copies to be rotated around the ring.
 597//   sa = Starting angle, in degrees.  For use with `n`.  Angle is in degrees counter-clockwise from Y+, when facing the origin from X+.  First unrotated copy is placed at that angle.
 598//   r = If given, makes a ring of child copies around the X axis, at the given radius.  Default: 0
 599//   d = If given, makes a ring of child copies around the X axis, at the given diameter.
 600//   subrot = If false, don't sub-rotate children as they are copied around the ring.
 601//
 602// Side Effects:
 603//   `$idx` is set to the index value of each child copy.
 604//   `$ang` is set to the rotation angle of each child copy, and can be used to modify each child individually.
 605//   `$axis` is set to the axis vector rotated around.
 606//
 607// Example:
 608//   xrot_copies([180, 270, 315])
 609//       cylinder(h=20, r1=5, r2=0);
 610//   color("red",0.333) cylinder(h=20, r1=5, r2=0);
 611//
 612// Example:
 613//   xrot_copies(n=6)
 614//       cylinder(h=20, r1=5, r2=0);
 615//   color("red",0.333) cylinder(h=20, r1=5, r2=0);
 616//
 617// Example:
 618//   xrot_copies(n=6, r=10)
 619//       xrot(-90) cylinder(h=20, r1=5, r2=0);
 620//   color("red",0.333) xrot(-90) cylinder(h=20, r1=5, r2=0);
 621//
 622// Example:
 623//   xrot_copies(n=6, r=10, sa=45)
 624//       xrot(-90) cylinder(h=20, r1=5, r2=0);
 625//   color("red",0.333) xrot(-90) cylinder(h=20, r1=5, r2=0);
 626//
 627// Example:
 628//   xrot_copies(n=6, r=20, subrot=false)
 629//       xrot(-90) cylinder(h=20, r1=5, r2=0, center=true);
 630//   color("red",0.333) xrot(-90) cylinder(h=20, r1=5, r2=0, center=true);
 631module xrot_copies(rots=[], cp=[0,0,0], n, sa=0, r, d, subrot=true)
 632{
 633    req_children($children);  
 634    r = get_radius(r=r, d=d, dflt=0);
 635    rot_copies(rots=rots, v=RIGHT, cp=cp, n=n, sa=sa, delta=[0, r, 0], subrot=subrot) children();
 636}
 637
 638
 639// Module: yrot_copies()
 640//
 641// Usage:
 642//   yrot_copies(rots, [cp], [r=|d=], [sa=], [subrot=]) CHILDREN;
 643//   yrot_copies(n=, [cp=], [r=|d=], [sa=], [subrot=]) CHILDREN;
 644//
 645// Description:
 646//   Given an array of angles, rotates copies of the children to each of those angles around the Y axis.
 647//   If given a count `n`, makes that many copies, rotated evenly around the Y axis.
 648//   If given a radius `r` (or diameter `d`), distributes children around a ring of that size around the Y axis.
 649//   If given a centerpoint `cp`, centers the rotation around that centerpoint.
 650//   If `subrot` is true, each child will be rotated in place to keep the same size towards the center when making rings.
 651//   The first (unrotated) copy will be placed at the relative starting angle `sa`.
 652//
 653// Arguments:
 654//   rots = Optional array of rotation angles, in degrees, to make copies at.
 655//   cp = Centerpoint to rotate around.
 656//   ---
 657//   n = Optional number of evenly distributed copies to be rotated around the ring.
 658//   sa = Starting angle, in degrees.  For use with `n`.  Angle is in degrees counter-clockwise from X-, when facing the origin from Y+.
 659//   r = If given, makes a ring of child copies around the Y axis, at the given radius.  Default: 0
 660//   d = If given, makes a ring of child copies around the Y axis, at the given diameter.
 661//   subrot = If false, don't sub-rotate children as they are copied around the ring.
 662//
 663// Side Effects:
 664//   `$idx` is set to the index value of each child copy.
 665//   `$ang` is set to the rotation angle of each child copy, and can be used to modify each child individually.
 666//   `$axis` is set to the axis vector rotated around.
 667//
 668// Example:
 669//   yrot_copies([180, 270, 315])
 670//       cylinder(h=20, r1=5, r2=0);
 671//   color("red",0.333) cylinder(h=20, r1=5, r2=0);
 672//
 673// Example:
 674//   yrot_copies(n=6)
 675//       cylinder(h=20, r1=5, r2=0);
 676//   color("red",0.333) cylinder(h=20, r1=5, r2=0);
 677//
 678// Example:
 679//   yrot_copies(n=6, r=10)
 680//       yrot(-90) cylinder(h=20, r1=5, r2=0);
 681//   color("red",0.333) yrot(-90) cylinder(h=20, r1=5, r2=0);
 682//
 683// Example:
 684//   yrot_copies(n=6, r=10, sa=45)
 685//       yrot(-90) cylinder(h=20, r1=5, r2=0);
 686//   color("red",0.333) yrot(-90) cylinder(h=20, r1=5, r2=0);
 687//
 688// Example:
 689//   yrot_copies(n=6, r=20, subrot=false)
 690//       yrot(-90) cylinder(h=20, r1=5, r2=0, center=true);
 691//   color("red",0.333) yrot(-90) cylinder(h=20, r1=5, r2=0, center=true);
 692module yrot_copies(rots=[], cp=[0,0,0], n, sa=0, r, d, subrot=true)
 693{
 694    req_children($children);
 695    r = get_radius(r=r, d=d, dflt=0);
 696    rot_copies(rots=rots, v=BACK, cp=cp, n=n, sa=sa, delta=[-r, 0, 0], subrot=subrot) children();
 697}
 698
 699
 700// Module: zrot_copies()
 701//
 702// Usage:
 703//   zrot_copies(rots, [cp], [r=|d=], [sa=], [subrot=]) CHILDREN;
 704//   zrot_copies(n=, [cp=], [r=|d=], [sa=], [subrot=]) CHILDREN;
 705//
 706// Description:
 707//   Given an array of angles, rotates copies of the children to each of those angles around the Z axis.
 708//   If given a count `n`, makes that many copies, rotated evenly around the Z axis.
 709//   If given a radius `r` (or diameter `d`), distributes children around a ring of that size around the Z axis.
 710//   If given a centerpoint `cp`, centers the rotation around that centerpoint.
 711//   If `subrot` is true, each child will be rotated in place to keep the same size towards the center when making rings.
 712//   The first (unrotated) copy will be placed at the relative starting angle `sa`.
 713//
 714// Arguments:
 715//   rots = Optional array of rotation angles, in degrees, to make copies at.
 716//   cp = Centerpoint to rotate around.  Default: [0,0,0]
 717//   ---
 718//   n = Optional number of evenly distributed copies to be rotated around the ring.
 719//   sa = Starting angle, in degrees.  For use with `n`.  Angle is in degrees counter-clockwise from X+, when facing the origin from Z+.  Default: 0
 720//   r = If given, makes a ring of child copies around the Z axis, at the given radius.  Default: 0
 721//   d = If given, makes a ring of child copies around the Z axis, at the given diameter.
 722//   subrot = If false, don't sub-rotate children as they are copied around the ring.  Default: true
 723//
 724// Side Effects:
 725//   `$idx` is set to the index value of each child copy.
 726//   `$ang` is set to the rotation angle of each child copy, and can be used to modify each child individually.
 727//   `$axis` is set to the axis vector rotated around.
 728//
 729// Example:
 730//   zrot_copies([180, 270, 315])
 731//       yrot(90) cylinder(h=20, r1=5, r2=0);
 732//   color("red",0.333) yrot(90) cylinder(h=20, r1=5, r2=0);
 733//
 734// Example:
 735//   zrot_copies(n=6)
 736//       yrot(90) cylinder(h=20, r1=5, r2=0);
 737//   color("red",0.333) yrot(90) cylinder(h=20, r1=5, r2=0);
 738//
 739// Example:
 740//   zrot_copies(n=6, r=10)
 741//       yrot(90) cylinder(h=20, r1=5, r2=0);
 742//   color("red",0.333) yrot(90) cylinder(h=20, r1=5, r2=0);
 743//
 744// Example:
 745//   zrot_copies(n=6, r=20, sa=45)
 746//       yrot(90) cylinder(h=20, r1=5, r2=0, center=true);
 747//   color("red",0.333) yrot(90) cylinder(h=20, r1=5, r2=0, center=true);
 748//
 749// Example:
 750//   zrot_copies(n=6, r=20, subrot=false)
 751//       yrot(-90) cylinder(h=20, r1=5, r2=0, center=true);
 752//   color("red",0.333) yrot(-90) cylinder(h=20, r1=5, r2=0, center=true);
 753module zrot_copies(rots=[], cp=[0,0,0], n, sa=0, r, d, subrot=true)
 754{
 755    r = get_radius(r=r, d=d, dflt=0);
 756    rot_copies(rots=rots, v=UP, cp=cp, n=n, sa=sa, delta=[r, 0, 0], subrot=subrot) children();
 757}
 758
 759
 760// Module: arc_copies()
 761//
 762// Description:
 763//   Evenly distributes n duplicate children around an ovoid arc on the XY plane.
 764//
 765// Usage:
 766//   arc_copies(n, r|d=, [sa=], [ea=], [rot=]) CHILDREN;
 767//   arc_copies(n, rx=|dx=, ry=|dy=, [sa=], [ea=], [rot=]) CHILDREN;
 768//
 769// Arguments:
 770//   n = number of copies to distribute around the circle. (Default: 6)
 771//   r = radius of circle (Default: 1)
 772//   ---
 773//   rx = radius of ellipse on X axis. Used instead of r.
 774//   ry = radius of ellipse on Y axis. Used instead of r.
 775//   d = diameter of circle. (Default: 2)
 776//   dx = diameter of ellipse on X axis. Used instead of d.
 777//   dy = diameter of ellipse on Y axis. Used instead of d.
 778//   rot = whether to rotate the copied children.  (Default: true)
 779//   sa = starting angle. (Default: 0.0)
 780//   ea = ending angle. Will distribute copies CCW from sa to ea. (Default: 360.0)
 781//
 782// Side Effects:
 783//   `$ang` is set to the rotation angle of each child copy, and can be used to modify each child individually.
 784//   `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
 785//   `$idx` is set to the index value of each child copy.
 786//
 787// Example:
 788//   #cube(size=[10,3,3],center=true);
 789//   arc_copies(d=40, n=5) cube(size=[10,3,3],center=true);
 790//
 791// Example:
 792//   #cube(size=[10,3,3],center=true);
 793//   arc_copies(d=40, n=5, sa=45, ea=225) cube(size=[10,3,3],center=true);
 794//
 795// Example:
 796//   #cube(size=[10,3,3],center=true);
 797//   arc_copies(r=15, n=8, rot=false) cube(size=[10,3,3],center=true);
 798//
 799// Example:
 800//   #cube(size=[10,3,3],center=true);
 801//   arc_copies(rx=20, ry=10, n=8) cube(size=[10,3,3],center=true);
 802// Example(2D): Using `$idx` to alternate shapes
 803//   arc_copies(r=50, n=19, sa=0, ea=180)
 804//       if ($idx % 2 == 0) rect(6);
 805//       else circle(d=6);
 806
 807function arc_copies(n=6,r,rx,ry,d,dx,dy,sa=0,ea=360,rot=true) = no_function("arc_copies");
 808module arc_of(n=6,r,rx,ry,d,dx,dy,sa=0,ea=360,rot=true){
 809    deprecate("arc_copies");
 810    arc_copies(n,r,rx,ry,d,dx,dy,sa,ea,rot);
 811}    
 812module arc_copies(
 813    n=6,
 814    r=undef,
 815    rx=undef, ry=undef,
 816    d=undef, dx=undef, dy=undef,
 817    sa=0, ea=360,
 818    rot=true
 819) {
 820    req_children($children);  
 821    rx = get_radius(r1=rx, r=r, d1=dx, d=d, dflt=1);
 822    ry = get_radius(r1=ry, r=r, d1=dy, d=d, dflt=1);
 823    sa = posmod(sa, 360);
 824    ea = posmod(ea, 360);
 825    n = (abs(ea-sa)<0.01)?(n+1):n;
 826    delt = (((ea<=sa)?360.0:0)+ea-sa)/(n-1);
 827    for ($idx = [0:1:n-1]) {
 828        $ang = sa + ($idx * delt);
 829        $pos =[rx*cos($ang), ry*sin($ang), 0];
 830        translate($pos) {
 831            zrot(rot? atan2(ry*sin($ang), rx*cos($ang)) : 0) {
 832                children();
 833            }
 834        }
 835    }
 836}
 837
 838
 839
 840// Module: sphere_copies()
 841//
 842// Description:
 843//   Spreads children semi-evenly over the surface of a sphere or ellipsoid.
 844//
 845// Usage:
 846//   sphere_copies(n, r|d=, [cone_ang=], [scale=], [perp=]) CHILDREN;
 847//
 848// Arguments:
 849//   n = How many copies to evenly spread over the surface.
 850//   r = Radius of the sphere to distribute over
 851//   ---
 852//   d = Diameter of the sphere to distribute over
 853//   cone_ang = Angle of the cone, in degrees, to limit how much of the sphere gets covered.  For full sphere coverage, use 180.  Measured pre-scaling.  Default: 180
 854//   scale = The [X,Y,Z] scaling factors to reshape the sphere being covered.
 855//   perp = If true, rotate children to be perpendicular to the sphere surface.  Default: true
 856//
 857// Side Effects:
 858//   `$pos` is set to the relative post-scaled centerpoint of each child copy, and can be used to modify each child individually.
 859//   `$theta` is set to the theta angle of the child from the center of the sphere.
 860//   `$phi` is set to the pre-scaled phi angle of the child from the center of the sphere.
 861//   `$rad` is set to the pre-scaled radial distance of the child from the center of the sphere.
 862//   `$idx` is set to the index number of each child being copied.
 863//
 864// Example:
 865//   sphere_copies(n=250, d=100, cone_ang=45, scale=[3,3,1])
 866//       cylinder(d=10, h=10, center=false);
 867//
 868// Example:
 869//   sphere_copies(n=500, d=100, cone_ang=180)
 870//       color(unit(point3d(v_abs($pos))))
 871//           cylinder(d=8, h=10, center=false);
 872function sphere_copies(n=100, r=undef, d=undef, cone_ang=90, scale=[1,1,1], perp=true) = no_function("sphere_copies");
 873module ovoid_spread(n=100, r=undef, d=undef, cone_ang=90, scale=[1,1,1], perp=true)
 874{
 875  deprecate("sphere_copies");
 876  sphere_copies(n,r,d,cone_ang,scale,perp);
 877}  
 878
 879module sphere_copies(n=100, r=undef, d=undef, cone_ang=90, scale=[1,1,1], perp=true)
 880{
 881    req_children($children);  
 882    r = get_radius(r=r, d=d, dflt=50);
 883    cnt = ceil(n / (cone_ang/180));
 884
 885    // Calculate an array of [theta,phi] angles for `n` number of
 886    // points, almost evenly spaced across the surface of a sphere.
 887    // This approximation is based on the golden spiral method.
 888    theta_phis = [for (x=[0:1:n-1]) [180*(1+sqrt(5))*(x+0.5)%360, acos(1-2*(x+0.5)/cnt)]];
 889
 890    for ($idx = idx(theta_phis)) {
 891        tp = theta_phis[$idx];
 892        xyz = spherical_to_xyz(r, tp[0], tp[1]);
 893        $pos = v_mul(xyz,point3d(scale,1));
 894        $theta = tp[0];
 895        $phi = tp[1];
 896        $rad = r;
 897        translate($pos) {
 898            if (perp) {
 899                rot(from=UP, to=xyz) children();
 900            } else {
 901                children();
 902            }
 903        }
 904    }
 905}
 906
 907// Section: Placing copies of all children on a path
 908
 909
 910// Module: path_copies()
 911//
 912// Description:
 913//   Place copies all of the children at points along the path based on path length.  You can specify `dist` as
 914//   a scalar or distance list and the children will be placed at the specified distances from the start of the path.  Otherwise the children are
 915//   placed at uniformly spaced points along the path.  If you specify `n` but not `spacing` then `n` copies will be placed
 916//   with one at path[0] if `closed` is true, or spanning the entire path from start to end if `closed` is false.
 917//   If you specify `spacing` but not `n` then copies will spread out starting from one set at path[0] for `closed=true` or at the path center for open paths.
 918//   If you specify `sp` then the copies will start at distance `sp` from the start of the path.  
 919//
 920// Usage: Uniformly distribute copies 
 921//   path_copies(path, [n], [spacing], [sp], [rotate_children], [closed=]) CHILDREN;
 922// Usage: Place copies at specified locations
 923//   path_copies(path, dist=, [rotate_children=], [closed=]) CHILDREN;
 924//
 925// Arguments:
 926//   path = path or 1-region where children are placed
 927//   n = number of copies
 928//   spacing = space between copies
 929//   sp = if given, copies will start distance sp from the path start and spread beyond that point
 930//   rotate_children = if true, rotate children to line up with curve normal.  Default: true
 931//   ---
 932//   dist = Specify a list of distances to determine placement of children.  
 933//   closed = If true treat path as a closed curve.  Default: false
 934//
 935// Side Effects:
 936//   `$pos` is set to the center of each copy
 937//   `$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`.
 938//   `$dir` is set to the direction vector of the path at the point where the copy is placed.
 939//   `$normal` is set to the direction of the normal vector to the path direction that is coplanar with the path at this point
 940//
 941// Example(2D):
 942//   spiral = [for(theta=[0:360*8]) theta * [cos(theta), sin(theta)]]/100;
 943//   stroke(spiral,width=.25);
 944//   color("red") path_copies(spiral, n=100) circle(r=1);
 945// Example(2D):
 946//   circle = regular_ngon(n=64, or=10);
 947//   stroke(circle,width=1,closed=true);
 948//   color("green") path_copies(circle, n=7, closed=true) circle(r=1+$idx/3);
 949// Example(2D):
 950//   heptagon = regular_ngon(n=7, or=10);
 951//   stroke(heptagon, width=1, closed=true);
 952//   color("purple") path_copies(heptagon, n=9, closed=true) rect([0.5,3],anchor=FRONT);
 953// Example(2D): Direction at the corners is the average of the two adjacent edges
 954//   heptagon = regular_ngon(n=7, or=10);
 955//   stroke(heptagon, width=1, closed=true);
 956//   color("purple") path_copies(heptagon, n=7, closed=true) rect([0.5,3],anchor=FRONT);
 957// Example(2D):  Don't rotate the children
 958//   heptagon = regular_ngon(n=7, or=10);
 959//   stroke(heptagon, width=1, closed=true);
 960//   color("red") path_copies(heptagon, n=9, closed=true, rotate_children=false) rect([0.5,3],anchor=FRONT);
 961// Example(2D): Open path, specify `n`
 962//   sinwav = [for(theta=[0:360]) 5*[theta/180, sin(theta)]];
 963//   stroke(sinwav,width=.1);
 964//   color("red") path_copies(sinwav, n=5) rect([.2,1.5],anchor=FRONT);
 965// Example(2D): Open path, specify `n` and `spacing`
 966//   sinwav = [for(theta=[0:360]) 5*[theta/180, sin(theta)]];
 967//   stroke(sinwav,width=.1);
 968//   color("red") path_copies(sinwav, n=5, spacing=1) rect([.2,1.5],anchor=FRONT);
 969// Example(2D): Closed path, specify `n` and `spacing`, copies centered around circle[0]
 970//   circle = regular_ngon(n=64,or=10);
 971//   stroke(circle,width=.1,closed=true);
 972//   color("red") path_copies(circle, n=10, spacing=1, closed=true) rect([.2,1.5],anchor=FRONT);
 973// Example(2D): Open path, specify `spacing`
 974//   sinwav = [for(theta=[0:360]) 5*[theta/180, sin(theta)]];
 975//   stroke(sinwav,width=.1);
 976//   color("red") path_copies(sinwav, spacing=5) rect([.2,1.5],anchor=FRONT);
 977// Example(2D): Open path, specify `sp`
 978//   sinwav = [for(theta=[0:360]) 5*[theta/180, sin(theta)]];
 979//   stroke(sinwav,width=.1);
 980//   color("red") path_copies(sinwav, n=5, sp=18) rect([.2,1.5],anchor=FRONT);
 981// Example(2D): Open path, specify `dist`
 982//   sinwav = [for(theta=[0:360]) 5*[theta/180, sin(theta)]];
 983//   stroke(sinwav,width=.1);
 984//   color("red") path_copies(sinwav, dist=[1,4,9,16]) rect([.2,1.5],anchor=FRONT);
 985// Example(2D):
 986//   wedge = arc(angle=[0,100], r=10, $fn=64);
 987//   difference(){
 988//     polygon(concat([[0,0]],wedge));
 989//     path_copies(wedge,n=5,spacing=3) fwd(.1) rect([1,4],anchor=FRONT);
 990//   }
 991// Example(Spin,VPD=115): 3d example, with children rotated into the plane of the path
 992//   tilted_circle = lift_plane([[0,0,0], [5,0,5], [0,2,3]],regular_ngon(n=64, or=12));
 993//   path_sweep(regular_ngon(n=16,or=.1),tilted_circle);
 994//   path_copies(tilted_circle, n=15,closed=true) {
 995//      color("blue") cyl(h=3,r=.2, anchor=BOTTOM);      // z-aligned cylinder
 996//      color("red") xcyl(h=10,r=.2, anchor=FRONT+LEFT); // x-aligned cylinder
 997//   }
 998// Example(Spin,VPD=115): 3d example, with rotate_children set to false
 999//   tilted_circle = lift_plane([[0,0,0], [5,0,5], [0,2,3]], regular_ngon(n=64, or=12));
1000//   path_sweep(regular_ngon(n=16,or=.1),tilted_circle);
1001//   path_copies(tilted_circle, n=25,rotate_children=false,closed=true) {
1002//      color("blue") cyl(h=3,r=.2, anchor=BOTTOM);       // z-aligned cylinder
1003//      color("red") xcyl(h=10,r=.2, anchor=FRONT+LEFT);  // x-aligned cylinder
1004//   }
1005function path_copies(path, n, spacing, sp=undef, rotate_children=true, dist, closed) = no_function("path_copies");
1006
1007module path_spread(path, n, spacing, sp=undef, rotate_children=true, dist, closed){
1008  deprecate("path_copes");
1009  path_copies(path,n,spacing,sp,dist,rotate_children,dist, closed) children();
1010}  
1011
1012module path_copies(path, n, spacing, sp=undef, dist, rotate_children=true, dist, closed)
1013{
1014    req_children($children);  
1015    is_1reg = is_1region(path);
1016    path = is_1reg ? path[0] : path;
1017    closed = default(closed, is_1reg);
1018    length = path_length(path,closed);
1019    distind = is_def(dist) ? sortidx(dist) : undef;
1020    distances =
1021        is_def(dist) ? assert(is_undef(n) && is_undef(spacing) && is_undef(sp), "Can't use n, spacing or undef with dist")
1022                       select(dist,distind)
1023      : is_def(sp)? (   // Start point given
1024            is_def(n) && is_def(spacing)? count(n,sp,spacing) :
1025            is_def(n)? lerpn(sp, length, n) :
1026            list([sp:spacing:length])
1027        )
1028      : is_def(n) && is_undef(spacing)? lerpn(0,length,n,!closed) // N alone given
1029      : (      // No start point and spacing is given, N maybe given
1030        let(
1031            n = is_def(n)? n : floor(length/spacing)+(closed?0:1),
1032            ptlist = count(n,0,spacing),
1033            listcenter = mean(ptlist)
1034        ) closed?
1035            sort([for(entry=ptlist) posmod(entry-listcenter,length)]) :
1036            [for(entry=ptlist) entry + length/2-listcenter ]
1037    );
1038    distOK = min(distances)>=0 && max(distances)<=length;
1039    dummy = assert(distOK,"Cannot fit all of the copies");
1040    cutlist = path_cut_points(path, distances, closed, direction=true);
1041    planar = len(path[0])==2;
1042    for(i=[0:1:len(cutlist)-1]) {
1043        $pos = cutlist[i][0];
1044        $idx = is_def(dist) ? distind[i] : i;
1045        $dir = rotate_children ? (planar?[1,0]:[1,0,0]) : cutlist[i][2];
1046        $normal = rotate_children? (planar?[0,1]:[0,0,1]) : cutlist[i][3];
1047        translate($pos) {
1048            if (rotate_children) {
1049                if(planar) {
1050                    rot(from=[0,1],to=cutlist[i][3]) children();
1051                } else {
1052                    frame_map(x=cutlist[i][2], z=cutlist[i][3])
1053                        children();
1054                }
1055            } else {
1056                children();
1057            }
1058        }
1059    }
1060}
1061
1062
1063
1064//////////////////////////////////////////////////////////////////////
1065// Section: Making a copy of all children with reflection
1066//////////////////////////////////////////////////////////////////////
1067
1068
1069// Module: xflip_copy()
1070//
1071// Description:
1072//   Makes a copy of the children, mirrored across the X axis.
1073//
1074// Usage:
1075//   xflip_copy([offset], [x]) CHILDREN;
1076//
1077// Arguments:
1078//   offset = Distance to offset children right, before copying.
1079//   x = The X coordinate of the mirroring plane.  Default: 0
1080//
1081// Side Effects:
1082//   `$orig` is true for the original instance of children.  False for the copy.
1083//   `$idx` is set to the index value of each copy.
1084//
1085// Example:
1086//   xflip_copy() yrot(90) cylinder(h=20, r1=4, r2=0);
1087//   color("blue",0.25) cube([0.01,15,15], center=true);
1088//
1089// Example:
1090//   xflip_copy(offset=5) yrot(90) cylinder(h=20, r1=4, r2=0);
1091//   color("blue",0.25) cube([0.01,15,15], center=true);
1092//
1093// Example:
1094//   xflip_copy(x=-5) yrot(90) cylinder(h=20, r1=4, r2=0);
1095//   color("blue",0.25) left(5) cube([0.01,15,15], center=true);
1096module xflip_copy(offset=0, x=0)
1097{
1098    req_children($children);  
1099    mirror_copy(v=[1,0,0], offset=offset, cp=[x,0,0]) children();
1100}
1101
1102
1103// Module: yflip_copy()
1104//
1105// Description:
1106//   Makes a copy of the children, mirrored across the Y axis.
1107//
1108// Usage:
1109//   yflip_copy([offset], [y]) CHILDREN;
1110//
1111// Arguments:
1112//   offset = Distance to offset children back, before copying.
1113//   y = The Y coordinate of the mirroring plane.  Default: 0
1114//
1115// Side Effects:
1116//   `$orig` is true for the original instance of children.  False for the copy.
1117//   `$idx` is set to the index value of each copy.
1118//
1119// Example:
1120//   yflip_copy() xrot(-90) cylinder(h=20, r1=4, r2=0);
1121//   color("blue",0.25) cube([15,0.01,15], center=true);
1122//
1123// Example:
1124//   yflip_copy(offset=5) xrot(-90) cylinder(h=20, r1=4, r2=0);
1125//   color("blue",0.25) cube([15,0.01,15], center=true);
1126//
1127// Example:
1128//   yflip_copy(y=-5) xrot(-90) cylinder(h=20, r1=4, r2=0);
1129//   color("blue",0.25) fwd(5) cube([15,0.01,15], center=true);
1130module yflip_copy(offset=0, y=0)
1131{
1132    req_children($children);
1133    mirror_copy(v=[0,1,0], offset=offset, cp=[0,y,0]) children();
1134}
1135
1136
1137// Module: zflip_copy()
1138//
1139// Description:
1140//   Makes a copy of the children, mirrored across the Z axis.
1141//
1142// Usage:
1143//   zflip_copy([offset], [z]) CHILDREN;
1144//
1145// Arguments:
1146//   offset = Distance to offset children up, before copying.
1147//   z = The Z coordinate of the mirroring plane.  Default: 0
1148//
1149// Side Effects:
1150//   `$orig` is true for the original instance of children.  False for the copy.
1151//   `$idx` is set to the index value of each copy.
1152//
1153// Example:
1154//   zflip_copy() cylinder(h=20, r1=4, r2=0);
1155//   color("blue",0.25) cube([15,15,0.01], center=true);
1156//
1157// Example:
1158//   zflip_copy(offset=5) cylinder(h=20, r1=4, r2=0);
1159//   color("blue",0.25) cube([15,15,0.01], center=true);
1160//
1161// Example:
1162//   zflip_copy(z=-5) cylinder(h=20, r1=4, r2=0);
1163//   color("blue",0.25) down(5) cube([15,15,0.01], center=true);
1164module zflip_copy(offset=0, z=0)
1165{
1166    req_children($children);  
1167    mirror_copy(v=[0,0,1], offset=offset, cp=[0,0,z]) children();
1168}
1169
1170
1171// Module: mirror_copy()
1172//
1173// Description:
1174//   Makes a copy of the children, mirrored across the given plane.
1175//
1176// Usage:
1177//   mirror_copy(v, [cp], [offset]) CHILDREN;
1178//
1179// Arguments:
1180//   v = The normal vector of the plane to mirror across.
1181//   offset = distance to offset away from the plane.
1182//   cp = A point that lies on the mirroring plane.
1183//
1184// Side Effects:
1185//   `$orig` is true for the original instance of children.  False for the copy.
1186//   `$idx` is set to the index value of each copy.
1187//
1188// Example:
1189//   mirror_copy([1,-1,0]) zrot(-45) yrot(90) cylinder(d1=10, d2=0, h=20);
1190//   color("blue",0.25) zrot(-45) cube([0.01,15,15], center=true);
1191//
1192// Example:
1193//   mirror_copy([1,1,0], offset=5) rot(a=90,v=[-1,1,0]) cylinder(d1=10, d2=0, h=20);
1194//   color("blue",0.25) zrot(45) cube([0.01,15,15], center=true);
1195//
1196// Example:
1197//   mirror_copy(UP+BACK, cp=[0,-5,-5]) rot(from=UP, to=BACK+UP) cylinder(d1=10, d2=0, h=20);
1198//   color("blue",0.25) translate([0,-5,-5]) rot(from=UP, to=BACK+UP) cube([15,15,0.01], center=true);
1199module mirror_copy(v=[0,0,1], offset=0, cp)
1200{
1201    req_children($children);  
1202    cp = is_vector(v,4)? plane_normal(v) * v[3] :
1203        is_vector(cp)? cp :
1204        is_num(cp)? cp*unit(v) :
1205        [0,0,0];
1206    nv = is_vector(v,4)? plane_normal(v) : unit(v);
1207    off = nv*offset;
1208    if (cp == [0,0,0]) {
1209        translate(off) {
1210            $orig = true;
1211            $idx = 0;
1212            children();
1213        }
1214        mirror(nv) translate(off) {
1215            $orig = false;
1216            $idx = 1;
1217            children();
1218        }
1219    } else {
1220        translate(off) children();
1221        translate(cp) mirror(nv) translate(-cp) translate(off) children();
1222    }
1223}
1224
1225
1226
1227////////////////////
1228// Section: Distributing children individually along a line
1229///////////////////
1230
1231// Module: xdistribute()
1232//
1233// Description:
1234//   Spreads out the children individually along the X axis. 
1235//   Every child is placed at a different position, in order.
1236//   This is useful for laying out groups of disparate objects
1237//   where you only really care about the spacing between them.
1238//
1239// Usage:
1240//   xdistribute(spacing, [sizes]) CHILDREN;
1241//   xdistribute(l=, [sizes=]) CHILDREN;
1242//
1243// Arguments:
1244//   spacing = spacing between each child. (Default: 10.0)
1245//   sizes = Array containing how much space each child will need.
1246//   l = Length to distribute copies along.
1247//
1248// Side Effects:
1249//   `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
1250//   `$idx` is set to the index number of each child being copied.
1251//
1252// Example:
1253//   xdistribute(sizes=[100, 10, 30], spacing=40) {
1254//       sphere(r=50);
1255//       cube([10,20,30], center=true);
1256//       cylinder(d=30, h=50, center=true);
1257//   }
1258module xdistribute(spacing=10, sizes=undef, l=undef)
1259{
1260    req_children($children);  
1261    dir = RIGHT;
1262    gaps = ($children < 2)? [0] :
1263        !is_undef(sizes)? [for (i=[0:1:$children-2]) sizes[i]/2 + sizes[i+1]/2] :
1264        [for (i=[0:1:$children-2]) 0];
1265    spc = !is_undef(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10);
1266    gaps2 = [for (gap = gaps) gap+spc];
1267    spos = dir * -sum(gaps2)/2;
1268    spacings = cumsum([0, each gaps2]);
1269    for (i=[0:1:$children-1]) {
1270        $pos = spos + spacings[i] * dir;
1271        $idx = i;
1272        translate($pos) children(i);
1273    }
1274}
1275
1276
1277// Module: ydistribute()
1278//
1279// Description:
1280//   Spreads out the children individually along the Y axis. 
1281//   Every child is placed at a different position, in order.
1282//   This is useful for laying out groups of disparate objects
1283//   where you only really care about the spacing between them.
1284//
1285// Usage:
1286//   ydistribute(spacing, [sizes]) CHILDREN;
1287//   ydistribute(l=, [sizes=]) CHILDREN;
1288//
1289// Arguments:
1290//   spacing = spacing between each child. (Default: 10.0)
1291//   sizes = Array containing how much space each child will need.
1292//   l = Length to distribute copies along.
1293//
1294// Side Effects:
1295//   `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
1296//   `$idx` is set to the index number of each child being copied.
1297//
1298// Example:
1299//   ydistribute(sizes=[30, 20, 100], spacing=40) {
1300//       cylinder(d=30, h=50, center=true);
1301//       cube([10,20,30], center=true);
1302//       sphere(r=50);
1303//   }
1304module ydistribute(spacing=10, sizes=undef, l=undef)
1305{
1306    req_children($children);  
1307    dir = BACK;
1308    gaps = ($children < 2)? [0] :
1309        !is_undef(sizes)? [for (i=[0:1:$children-2]) sizes[i]/2 + sizes[i+1]/2] :
1310        [for (i=[0:1:$children-2]) 0];
1311    spc = !is_undef(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10);
1312    gaps2 = [for (gap = gaps) gap+spc];
1313    spos = dir * -sum(gaps2)/2;
1314    spacings = cumsum([0, each gaps2]);
1315    for (i=[0:1:$children-1]) {
1316        $pos = spos + spacings[i] * dir;
1317        $idx = i;
1318        translate($pos) children(i);
1319    }
1320}
1321
1322
1323// Module: zdistribute()
1324//
1325// Description:
1326//   Spreads out each individual child along the Z axis.
1327//   Every child is placed at a different position, in order.
1328//   This is useful for laying out groups of disparate objects
1329//   where you only really care about the spacing between them.
1330//
1331// Usage:
1332//   zdistribute(spacing, [sizes]) CHILDREN;
1333//   zdistribute(l=, [sizes=]) CHILDREN;
1334//
1335// Arguments:
1336//   spacing = spacing between each child. (Default: 10.0)
1337//   sizes = Array containing how much space each child will need.
1338//   l = Length to distribute copies along.
1339//
1340// Side Effects:
1341//   `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
1342//   `$idx` is set to the index number of each child being copied.
1343//
1344// Example:
1345//   zdistribute(sizes=[30, 20, 100], spacing=40) {
1346//       cylinder(d=30, h=50, center=true);
1347//       cube([10,20,30], center=true);
1348//       sphere(r=50);
1349//   }
1350module zdistribute(spacing=10, sizes=undef, l=undef)
1351{
1352    req_children($children);  
1353    dir = UP;
1354    gaps = ($children < 2)? [0] :
1355        !is_undef(sizes)? [for (i=[0:1:$children-2]) sizes[i]/2 + sizes[i+1]/2] :
1356        [for (i=[0:1:$children-2]) 0];
1357    spc = !is_undef(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10);
1358    gaps2 = [for (gap = gaps) gap+spc];
1359    spos = dir * -sum(gaps2)/2;
1360    spacings = cumsum([0, each gaps2]);
1361    for (i=[0:1:$children-1]) {
1362        $pos = spos + spacings[i] * dir;
1363        $idx = i;
1364        translate($pos) children(i);
1365    }
1366}
1367
1368
1369
1370// Module: distribute()
1371//
1372// Description:
1373//   Spreads out the children individually along the direction `dir`.
1374//   Every child is placed at a different position, in order.
1375//   This is useful for laying out groups of disparate objects
1376//   where you only really care about the spacing between them.
1377//
1378// Usage:
1379//   distribute(spacing, sizes, dir) CHILDREN;
1380//   distribute(l=, [sizes=], [dir=]) CHILDREN;
1381//
1382// Arguments:
1383//   spacing = Spacing to add between each child. (Default: 10.0)
1384//   sizes = Array containing how much space each child will need.
1385//   dir = Vector direction to distribute copies along.  Default: RIGHT
1386//   l = Length to distribute copies along.
1387//
1388// Side Effects:
1389//   `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
1390//   `$idx` is set to the index number of each child being copied.
1391//
1392// Example:
1393//   distribute(sizes=[100, 30, 50], dir=UP) {
1394//       sphere(r=50);
1395//       cube([10,20,30], center=true);
1396//       cylinder(d=30, h=50, center=true);
1397//   }
1398module distribute(spacing=undef, sizes=undef, dir=RIGHT, l=undef)
1399{
1400    req_children($children);  
1401    gaps = ($children < 2)? [0] :
1402        !is_undef(sizes)? [for (i=[0:1:$children-2]) sizes[i]/2 + sizes[i+1]/2] :
1403        [for (i=[0:1:$children-2]) 0];
1404    spc = !is_undef(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10);
1405    gaps2 = [for (gap = gaps) gap+spc];
1406    spos = dir * -sum(gaps2)/2;
1407    spacings = cumsum([0, each gaps2]);
1408    for (i=[0:1:$children-1]) {
1409        $pos = spos + spacings[i] * dir;
1410        $idx = i;
1411        translate($pos) children(i);
1412    }
1413}
1414
1415
1416
1417
1418
1419// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap