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// Section: Adaptive Children Using `$` Variables
14// The distributor methods create multiple copies of their children and place them in various ways. While many models
15// require multiple identical copies of an object, this framework is more powerful than
16// might be immediately obvious because of `$` variables. The distributors set `$` variables that the children can use to change their
17// behavior from one child to the next within a single distributor invocation. This means the copies need not be identical.
18// The {{xcopies()}} module sets `$idx` to the index number of the copy, and in the examples below we use `$idx`, but the various
19// distributors offer a variety of `$` variables that you can use in your children. Check the "Side Effects" section for each module
20// to learn what variables that module provides.
21// .
22// Two gotchas may lead to models that don't behave as expected. While `if` statements work to control modules, you cannot
23// use them to make variable assignments in your child object. If you write a statement like
24// ```
25// if (condition) { c="red";}
26// else {c="green";}
27// ```
28// then the `c` variable is set only in the scope of the `if` and `else` clauses and is not available later on when you actually
29// try to use it. Instead you must use the ternary operator and write:
30// ```
31// c = condition ? "red" : "green";
32// ```
33// The second complication is
34// that in OpenSCAD version 2021.01 and earlier, assignments in children were executed before their parent. This means
35// that `$` variables like `$idx` are not available in assignments because the parent hasn't run to set them, so if you use them
36// you will get a warning about an unknown variable.
37// Two workarounds exist, neither of which are needed in newer versions of OpenSCAD. The workarounds solve the problem because
38// **modules** execute after their parent, so the `$` variables **are** available in modules. You can put your assignments
39// in a `let()` module, or you can wrap your child in a `union()`. Both methods appear below.
40// Figure(2D,NoScales): This example shows how we can use `$idx` to produce **different** geometry at each index.
41// xcopies(n=10, spacing=10)
42// text(str($idx));
43// Continues:
44// ```
45// xcopies(n=10, spacing=10)
46// text(str($idx));
47// ```
48// Figure(2D,NoScales): Here the children are sometimes squares and sometimes circles as determined by the conditional `if` module. This use of `if` is OK because no variables are assigned.
49// xcopies(n=4, spacing=10)
50// if($idx%2==0) circle(r=3,$fn=16);
51// else rect(6);
52// Continues:
53// ```
54// xcopies(n=4, spacing=10)
55// if($idx%2==0) circle(r=3,$fn=16);
56// else rect(6);
57// ```
58// Figure(2D,NoScales): Suppose we would like to color odd and even index copies differently. In this example we compute the color for a given child from `$idx` using the ternary operator. The `let()` module is a module that sets variables and makes them available to its children. Note that multiple assignments in `let()` are separated by commas, not semicolons.
59// xcopies(n=6, spacing=10){
60// let(c = $idx % 2 == 0 ? "red" : "green")
61// color(c) rect(6);
62// }
63// Continues:
64// ```
65// xcopies(n=6, spacing=10){
66// let(c = $idx % 2 == 0 ? "red" : "green")
67// color(c) rect(6);
68// }
69// ```
70// Figure(2D,NoScales): This example shows how you can change the position of children adaptively. If you want to avoid repeating your code for each case, this requires storing a transformation matrix in a variable and then applying it using `multmatrix()`. We wrap our code in `union()` to ensure that it works in OpenSCAD 2021.01.
71// xcopies(n=5,spacing=10)
72// union()
73// {
74// shiftback = $idx%2==0 ? back(10) : IDENT;
75// spin = zrot(180*$idx/4);
76// multmatrix(shiftback*spin) stroke([[-4,0],[4,0]],endcap2="arrow2",width=3/4, color="red");
77// }
78// Continues:
79// ```
80// xcopies(n=5,spacing=10)
81// union()
82// {
83// shiftback = $idx%2==0 ? back(10) : IDENT;
84// spin = zrot(180*$idx/4);
85// multmatrix(shiftback*spin) stroke([[-4,0],[4,0]],endcap2="arrow2",width=3/4,color="red");
86// }
87// ```
88
89
90//////////////////////////////////////////////////////////////////////
91// Section: Translating copies of all the children
92//////////////////////////////////////////////////////////////////////
93
94// Function&Module: move_copies()
95// Synopsis: Translates copies of all children.
96// SynTags: MatList, Trans
97// Topics: Transformations, Distributors, Translation, Copiers
98// See Also: xcopies(), ycopies(), zcopies(), line_copies(), grid_copies(), rot_copies(), xrot_copies(), yrot_copies(), zrot_copies(), arc_copies(), sphere_copies()
99//
100// Usage:
101// move_copies(a) CHILDREN;
102// Usage: As a function to translate points, VNF, or Bezier patches
103// copies = move_copies(a, p=);
104// Usage: Get Translation Matrices
105// mats = move_copies(a);
106// Description:
107// When called as a module, translates copies of all children to each given translation offset.
108// When called as a function, with no `p=` argument, returns a list of transformation matrices, one for each copy.
109// When called as a function, *with* a `p=` argument, returns a list of transformed copies of `p=`.
110//
111// Arguments:
112// a = Array of XYZ offset vectors. Default `[[0,0,0]]`
113// ---
114// p = Either a point, pointlist, VNF or Bezier patch to be translated when used as a function.
115//
116// Side Effects:
117// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
118// `$idx` is set to the index number of each child being copied.
119//
120//
121// Example:
122// #sphere(r=10);
123// move_copies([[-25,-25,0], [25,-25,0], [0,0,50], [0,25,0]]) sphere(r=10);
124module move_copies(a=[[0,0,0]])
125{
126 req_children($children);
127 assert(is_list(a));
128 for ($idx = idx(a)) {
129 $pos = a[$idx];
130 assert(is_vector($pos),"move_copies offsets should be a 2d or 3d vector.");
131 translate($pos) children();
132 }
133}
134
135function move_copies(a=[[0,0,0]],p=_NO_ARG) =
136 assert(is_list(a))
137 let(
138 mats = [
139 for (pos = a)
140 assert(is_vector(pos),"move_copies offsets should be a 2d or 3d vector.")
141 translate(pos)
142 ]
143 )
144 p==_NO_ARG? mats : [for (m = mats) apply(m, p)];
145
146
147// Function&Module: xcopies()
148// Synopsis: Places copies of children along the X axis.
149// SynTags: MatList, Trans
150// Topics: Transformations, Distributors, Translation, Copiers
151// See Also: move_copies(), ycopies(), zcopies(), line_copies(), grid_copies(), rot_copies(), xrot_copies(), yrot_copies(), zrot_copies(), arc_copies(), sphere_copies()
152//
153// Usage:
154// xcopies(spacing, [n], [sp=]) CHILDREN;
155// xcopies(l=, [n=], [sp=]) CHILDREN;
156// xcopies(LIST) CHILDREN;
157// Usage: As a function to translate points, VNF, or Bezier patches
158// copies = xcopies(spacing, [n], [sp=], p=);
159// copies = xcopies(l=, [n=], [sp=], p=);
160// copies = xcopies(LIST, p=);
161// Usage: Get Translation Matrices
162// mats = xcopies(spacing, [n], [sp=]);
163// mats = xcopies(l=, [n=], [sp=]);
164// mats = xcopies(LIST);
165// Description:
166// When called as a module, places `n` copies of the children along a line on the X axis.
167// When called as a function, *without* a `p=` argument, returns a list of transformation matrices, one for each copy.
168// When called as a function, *with* a `p=` argument, returns a list of transformed copies of `p=`.
169//
170// Arguments:
171// 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)
172// n = Number of copies to place. (Default: 2)
173// ---
174// l = If given, the length to place copies over.
175// 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 segment to the right of starting position `[sp,0,0]`. If not given, copies will be placed along a line segment that is centered at [0,0,0].
176// p = Either a point, pointlist, VNF or Bezier patch to be translated when used as a function.
177//
178// Side Effects:
179// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
180// `$idx` is set to the index number of each child being copied.
181//
182// Examples:
183// xcopies(20) sphere(3);
184// xcopies(20, n=3) sphere(3);
185// xcopies(spacing=15, l=50) sphere(3);
186// xcopies(n=4, l=30, sp=[0,10,0]) sphere(3);
187// Example:
188// xcopies(10, n=3) {
189// cube(size=[1,3,1],center=true);
190// cube(size=[3,1,1],center=true);
191// }
192// Example:
193// xcopies([1,2,3,5,7]) sphere(d=1);
194module xcopies(spacing, n, l, sp)
195{
196 assert(is_undef(n) || num_defined([l,spacing])==1, "When n is given must give exactly one of spacing or l")
197 assert(is_def(n) || num_defined([l,spacing])>=1, "When n is not given must give at least one of spacing or l")
198 req_children($children);
199 dir = RIGHT;
200 sp = is_finite(sp)? (sp*dir) : sp;
201 if (is_vector(spacing)) {
202 translate(default(sp,[0,0,0])) {
203 for (i = idx(spacing)) {
204 $idx = i;
205 $pos = spacing[i]*dir;
206 translate($pos) children();
207 }
208 }
209 } else {
210 line_copies(
211 l=u_mul(l,dir),
212 spacing=u_mul(spacing,dir),
213 n=n, p1=sp
214 ) children();
215 }
216}
217
218
219function xcopies(spacing, n, l, sp, p=_NO_ARG) =
220 assert(is_undef(n) || num_defined([l,spacing])==1, "When n is given must give exactly one of spacing or l")
221 assert(is_def(n) || num_defined([l,spacing])>=1, "When n is not given must give at least one of spacing or l")
222 let(
223 dir = RIGHT,
224 sp = is_finite(sp)? (sp*dir) : sp,
225 mats = is_vector(spacing)
226 ? let(sp = default(sp,[0,0,0])) [for (n = spacing) translate(sp + n*dir)]
227 : line_copies(l=u_mul(l,dir), spacing=u_mul(spacing,dir), n=n, p1=sp)
228 )
229 p==_NO_ARG? mats : [for (m = mats) apply(m, p)];
230
231
232// Function&Module: ycopies()
233// Synopsis: Places copies of children along the Y axis.
234// SynTags: MatList, Trans
235// Topics: Transformations, Distributors, Translation, Copiers
236// See Also: move_copies(), xcopies(), zcopies(), line_copies(), grid_copies(), rot_copies(), xrot_copies(), yrot_copies(), zrot_copies(), arc_copies(), sphere_copies()
237//
238// Usage:
239// ycopies(spacing, [n], [sp=]) CHILDREN;
240// ycopies(l=, [n=], [sp=]) CHILDREN;
241// ycopies(LIST) CHILDREN;
242// Usage: As a function to translate points, VNF, or Bezier patches
243// copies = ycopies(spacing, [n], [sp=], p=);
244// copies = ycopies(l=, [n=], [sp=], p=);
245// copies = ycopies(LIST, p=);
246// Usage: Get Translation Matrices
247// mats = ycopies(spacing, [n], [sp=]);
248// mats = ycopies(l=, [n=], [sp=]);
249// mats = ycopies(LIST);
250// Description:
251// When called as a module, places `n` copies of the children along a line on the Y axis.
252// When called as a function, *without* a `p=` argument, returns a list of transformation matrices, one for each copy.
253// When called as a function, *with* a `p=` argument, returns a list of transformed copies of `p=`.
254//
255// Arguments:
256// 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)
257// n = Number of copies to place on the line. (Default: 2)
258// ---
259// l = If given, the length to place copies over.
260// 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].
261// p = Either a point, pointlist, VNF or Bezier patch to be translated when used as a function.
262//
263// Side Effects:
264// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
265// `$idx` is set to the index number of each child being copied.
266//
267// Examples:
268// ycopies(20) sphere(3);
269// ycopies(20, n=3) sphere(3);
270// ycopies(spacing=15, l=50) sphere(3);
271// ycopies(n=4, l=30, sp=[10,0,0]) sphere(3);
272// Example:
273// ycopies(10, n=3) {
274// cube(size=[1,3,1],center=true);
275// cube(size=[3,1,1],center=true);
276// }
277// Example:
278// ycopies([1,2,3,5,7]) sphere(d=1);
279module ycopies(spacing, n, l, sp)
280{
281 assert(is_undef(n) || num_defined([l,spacing])==1, "When n is given must give exactly one of spacing or l")
282 assert(is_def(n) || num_defined([l,spacing])>=1, "When n is not given must give at least one of spacing or l")
283 req_children($children);
284 dir = BACK;
285 sp = is_finite(sp)? (sp*dir) : sp;
286 if (is_vector(spacing)) {
287 translate(default(sp,[0,0,0])) {
288 for (i = idx(spacing)) {
289 $idx = i;
290 $pos = spacing[i]*dir;
291 translate($pos) children();
292 }
293 }
294 } else {
295 line_copies(
296 l=u_mul(l,dir),
297 spacing=u_mul(spacing,dir),
298 n=n, p1=sp
299 ) children();
300 }
301}
302
303
304function ycopies(spacing, n, l, sp, p=_NO_ARG) =
305 assert(is_undef(n) || num_defined([l,spacing])==1, "When n is given must give exactly one of spacing or l")
306 assert(is_def(n) || num_defined([l,spacing])>=1, "When n is not given must give at least one of spacing or l")
307 let(
308 dir = BACK,
309 sp = is_finite(sp)? (sp*dir) : sp,
310 mats = is_vector(spacing)
311 ? let(sp = default(sp,[0,0,0])) [for (n = spacing) translate(sp + n*dir)]
312 : line_copies(l=u_mul(l,dir), spacing=u_mul(spacing,dir), n=n, p1=sp)
313 )
314 p==_NO_ARG? mats : [for (m = mats) apply(m, p)];
315
316
317// Function&Module: zcopies()
318// Synopsis: Places copies of children along the Z axis.
319// SynTags: MatList, Trans
320// Topics: Transformations, Distributors, Translation, Copiers
321// See Also: move_copies(), xcopies(), ycopies(), line_copies(), grid_copies(), rot_copies(), xrot_copies(), yrot_copies(), zrot_copies(), arc_copies(), sphere_copies()
322//
323// Usage:
324// zcopies(spacing, [n], [sp=]) CHILDREN;
325// zcopies(l=, [n=], [sp=]) CHILDREN;
326// zcopies(LIST) CHILDREN;
327// Usage: As a function to translate points, VNF, or Bezier patches
328// copies = zcopies(spacing, [n], [sp=], p=);
329// copies = zcopies(l=, [n=], [sp=], p=);
330// copies = zcopies(LIST, p=);
331// Usage: Get Translation Matrices
332// mats = zcopies(spacing, [n], [sp=]);
333// mats = zcopies(l=, [n=], [sp=]);
334// mats = zcopies(LIST);
335// Description:
336// When called as a module, places `n` copies of the children along a line on the Z axis.
337// When called as a function, *without* a `p=` argument, returns a list of transformation matrices, one for each copy.
338// When called as a function, *with* a `p=` argument, returns a list of transformed copies of `p=`.
339//
340// Arguments:
341// 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)
342// n = Number of copies to place. (Default: 2)
343// ---
344// l = If given, the length to place copies over.
345// 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].
346// p = Either a point, pointlist, VNF or Bezier patch to be translated when used as a function.
347//
348// Side Effects:
349// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
350// `$idx` is set to the index number of each child being copied.
351//
352// Examples:
353// zcopies(20) sphere(3);
354// zcopies(20, n=3) sphere(3);
355// zcopies(spacing=15, l=50) sphere(3);
356// zcopies(n=4, l=30, sp=[10,0,0]) sphere(3);
357// Example:
358// zcopies(10, n=3) {
359// cube(size=[1,3,1],center=true);
360// cube(size=[3,1,1],center=true);
361// }
362// Example: Cubic sphere packing
363// s = 20;
364// s2 = s * sin(45);
365// zcopies(s2,n=8)
366// grid_copies([s2,s2],n=8,stagger=($idx%2)? true : "alt")
367// sphere(d=s);
368// Example: Hexagonal sphere packing
369// s = 20;
370// xyr = adj_ang_to_hyp(s/2,30);
371// h = hyp_adj_to_opp(s,xyr);
372// zcopies(h,n=8)
373// back(($idx%2)*xyr*cos(60))
374// grid_copies(s,n=[12,7],stagger=($idx%2)? "alt" : true)
375// sphere(d=s);
376// Example:
377// zcopies([1,2,3,5,7]) sphere(d=1);
378module zcopies(spacing, n, l, sp)
379{
380 assert(is_undef(n) || num_defined([l,spacing])==1, "When n is given must give exactly one of spacing or l")
381 assert(is_def(n) || num_defined([l,spacing])>=1, "When n is not given must give at least one of spacing or l")
382 req_children($children);
383 dir = UP;
384 sp = is_finite(sp)? (sp*dir) : sp;
385 if (is_vector(spacing)) {
386 translate(default(sp,[0,0,0])) {
387 for (i = idx(spacing)) {
388 $idx = i;
389 $pos = spacing[i]*dir;
390 translate($pos) children();
391 }
392 }
393 } else {
394 line_copies(
395 l=u_mul(l,dir),
396 spacing=u_mul(spacing,dir),
397 n=n, p1=sp
398 ) children();
399 }
400}
401
402
403function zcopies(spacing, n, l, sp, p=_NO_ARG) =
404 assert(is_undef(n) || num_defined([l,spacing])==1, "When n is given must give exactly one of spacing or l")
405 assert(is_def(n) || num_defined([l,spacing])>=1, "When n is not given must give at least one of spacing or l")
406 let(
407 dir = UP,
408 sp = is_finite(sp)? (sp*dir) : sp,
409 mats = is_vector(spacing)
410 ? let(sp = default(sp,[0,0,0])) [for (n = spacing) translate(sp + n*dir)]
411 : line_copies(l=u_mul(l,dir), spacing=u_mul(spacing,dir), n=n, p1=sp)
412 )
413 p==_NO_ARG? mats : [for (m = mats) apply(m, p)];
414
415
416
417// Function&Module: line_copies()
418// Synopsis: Places copies of children along an arbitrary line.
419// SynTags: MatList, Trans
420// Topics: Transformations, Distributors, Translation, Copiers
421// See Also: move_copies(), xcopies(), ycopies(), zcopies(), rot_copies(), xrot_copies(), yrot_copies(), zrot_copies(), arc_copies(), sphere_copies()
422//
423// Usage: Place `n` copies at a given spacing along the line
424// line_copies(spacing, [n], [p1=]) CHILDREN;
425// Usage: Place as many copies as will fit at a given spacing
426// line_copies(spacing, [l=], [p1=]) CHILDREN;
427// Usage: Place `n` copies along the length of the line
428// line_copies([n=], [l=], [p1=]) CHILDREN;
429// Usage: Place `n` copies along the line from `p1` to `p2`
430// line_copies([n=], [p1=], [p2=]) CHILDREN;
431// Usage: Place copies at the given spacing, centered along the line from `p1` to `p2`
432// line_copies([spacing], [p1=], [p2=]) CHILDREN;
433// Usage: As a function to translate points, VNF, or Bezier patches
434// copies = line_copies([spacing], [n], [p1=], p=);
435// copies = line_copies([spacing], [l=], [p1=], p=);
436// copies = line_copies([n=], [l=], [p1=], p=);
437// copies = line_copies([n=], [p1=], [p2=], p=);
438// copies = line_copies([spacing], [p1=], [p2=], p=);
439// Usage: Get Translation Matrices
440// mats = line_copies([spacing], [n], [p1=]);
441// mats = line_copies([spacing], [l=], [p1=]);
442// mats = line_copies([n=], [l=], [p1=]);
443// mats = line_copies([n=], [p1=], [p2=]);
444// mats = line_copies([spacing], [p1=], [p2=]);
445// Description:
446// When called as a function, *without* a `p=` argument, returns a list of transformation matrices, one for each copy.
447// When called as a function, *with* a `p=` argument, returns a list of transformed copies of `p=`.
448// When called as a module, copies `children()` at one or more evenly spaced positions along a line.
449// By default, the line will be centered at the origin, unless the starting point `p1` is given.
450// The line will be pointed towards `RIGHT` (X+) unless otherwise given as a vector in `l`,
451// `spacing`, or `p1`/`p2`. The psotion of the copies is specified in one of several ways:
452// .
453// If You Know... | Then Use Something Like...
454// -------------------------------- | --------------------------------
455// Spacing distance, Count | `line_copies(spacing=10, n=5) ...` or `line_copies(10, n=5) ...`
456// Spacing vector, Count | `line_copies(spacing=[10,5], n=5) ...` or `line_copies([10,5], n=5) ...`
457// Spacing distance, Line length | `line_copies(spacing=10, l=50) ...` or `line_copies(10, l=50) ...`
458// Spacing distance, Line vector | `line_copies(spacing=10, l=[50,30]) ...` or `line_copies(10, l=[50,30]) ...`
459// Spacing vector, Line length | `line_copies(spacing=[10,5], l=50) ...` or `line_copies([10,5], l=50) ...`
460// Line length, Count | `line_copies(l=50, n=5) ...`
461// Line vector, Count | `line_copies(l=[50,40], n=5) ...`
462// Line endpoints, Count | `line_copies(p1=[10,10], p2=[60,-10], n=5) ...`
463// Line endpoints, Spacing distance | `line_copies(p1=[10,10], p2=[60,-10], spacing=10) ...`
464//
465// Arguments:
466// 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.
467// n = Number of copies to distribute along the line. (Default: 2)
468// ---
469// l = Either the scalar length of the line, or a vector giving both the direction and length of the line.
470// p1 = If given, specifies the starting point of the line.
471// p2 = If given with `p1`, specifies the ending point of line, and indirectly calculates the line length.
472// p = Either a point, pointlist, VNF or Bezier patch to be translated when used as a function.
473//
474// Side Effects:
475// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
476// `$idx` is set to the index number of each child being copied.
477//
478// Examples:
479// line_copies(10) sphere(d=1.5);
480// line_copies(10, n=5) sphere(d=3);
481// line_copies([10,5], n=5) sphere(d=3);
482// line_copies(spacing=10, n=6) sphere(d=3);
483// line_copies(spacing=[10,5], n=6) sphere(d=3);
484// line_copies(spacing=10, l=50) sphere(d=3);
485// line_copies(spacing=10, l=[50,30]) sphere(d=3);
486// line_copies(spacing=[10,5], l=50) sphere(d=3);
487// line_copies(l=50, n=4) sphere(d=3);
488// line_copies(l=[50,-30], n=4) sphere(d=3);
489// Example(FlatSpin,VPD=133):
490// line_copies(p1=[0,0,0], p2=[5,5,20], n=6) cuboid([3,2,1]);
491// Example(FlatSpin,VPD=133):
492// line_copies(p1=[0,0,0], p2=[5,5,20], spacing=6) cuboid([3,2,1]);
493// Example: All children are copied to each position
494// line_copies(l=20, n=3) {
495// cube(size=[1,3,1],center=true);
496// cube(size=[3,1,1],center=true);
497// }
498// Example(2D): The functional form of line_copies() returns a list of transform matrices.
499// mats = line_copies([10,5],n=5);
500// for (m = mats) multmatrix(m) circle(d=3);
501// Example(2D): The functional form of line_copies() returns a list of points if given a point.
502// pts = line_copies([10,5],n=5,p=[0,0,0]);
503// move_copies(pts) circle(d=3);
504
505module line_of(spacing, n, l, p1, p2) {
506 deprecate("line_copies");
507 line_copies(spacing, n, l, p1, p2) children();
508}
509
510module line_copies(spacing, n, l, p1, p2)
511{
512 req_children($children);
513 pts = line_copies(spacing=spacing, n=n, l=l, p1=p1, p2=p2, p=[0,0,0]);
514 for (i=idx(pts)) {
515 $idx = i;
516 $pos = pts[i];
517 translate($pos) children();
518 }
519}
520
521function line_copies(spacing, n, l, p1, p2, p=_NO_ARG) =
522 assert(is_undef(spacing) || is_finite(spacing) || is_vector(spacing))
523 assert(is_undef(n) || is_finite(n))
524 assert(is_undef(l) || is_finite(l) || is_vector(l))
525 assert(is_undef(p1) || is_vector(p1))
526 assert(is_undef(p2) || is_vector(p2))
527 assert(is_undef(p2) || is_def(p1), "If p2 is given must also give p1")
528 assert(is_undef(p2) || is_undef(l), "Cannot give both p2 and l")
529 assert(is_undef(n) || num_defined([l,spacing,p2])==1,"If n is given then must give exactly one of 'l', 'spacing', or the 'p1'/'p2' pair")
530 assert(is_def(n) || num_defined([l,spacing,p2])>=1,"If n is given then must give at least one of 'l', 'spacing', or the 'p1'/'p2' pair")
531 let(
532 ll = is_def(l)? scalar_vec3(l, 0)
533 : is_def(spacing) && is_def(n)? (n-1) * scalar_vec3(spacing, 0)
534 : is_def(p1) && is_def(p2)? point3d(p2-p1)
535 : undef,
536 cnt = is_def(n)? n
537 : is_def(spacing) && is_def(ll) ? floor(norm(ll) / norm(scalar_vec3(spacing, 0)) + 1.000001)
538 : 2,
539 spc = cnt<=1? [0,0,0]
540 : is_undef(spacing) && is_def(ll)? ll/(cnt-1)
541 : is_num(spacing) && is_def(ll)? (ll/(cnt-1))
542 : scalar_vec3(spacing, 0)
543 )
544 assert(!is_undef(cnt), "Need two of `spacing`, 'l', 'n', or `p1`/`p2` arguments in `line_copies()`.")
545 let( spos = !is_undef(p1)? point3d(p1) : -(cnt-1)/2 * spc )
546 [for (i=[0:1:cnt-1]) translate(i * spc + spos, p=p)];
547
548
549
550// Function&Module: grid_copies()
551// Synopsis: Places copies of children in an [X,Y] grid.
552// SynTags: MatList, Trans
553// Topics: Transformations, Distributors, Translation, Copiers
554// See Also: move_copies(), xcopies(), ycopies(), zcopies(), line_copies(), rot_copies(), xrot_copies(), yrot_copies(), zrot_copies(), arc_copies(), sphere_copies()
555//
556// Usage:
557// grid_copies(spacing, size=, [stagger=], [scale=], [inside=]) CHILDREN;
558// grid_copies(n=, size=, [stagger=], [scale=], [inside=]) CHILDREN;
559// grid_copies(spacing, [n], [stagger=], [scale=], [inside=]) CHILDREN;
560// grid_copies(n=, inside=, [stagger], [scale]) CHILDREN;
561// Usage: As a function to translate points, VNF, or Bezier patches
562// copies = grid_copies(spacing, size=, [stagger=], [scale=], [inside=], p=);
563// copies = grid_copies(n=, size=, [stagger=], [scale=], [inside=], p=);
564// copies = grid_copies(spacing, [n], [stagger=], [scale=], [inside=], p=);
565// copies = grid_copies(n=, inside=, [stagger], [scale], p=);
566// Usage: Get Translation Matrices
567// mats = grid_copies(spacing, size=, [stagger=], [scale=], [inside=]);
568// mats = grid_copies(n=, size=, [stagger=], [scale=], [inside=]);
569// mats = grid_copies(spacing, [n], [stagger=], [scale=], [inside=]);
570// mats = grid_copies(n=, inside=, [stagger], [scale]);
571// Description:
572// When called as a module, makes a square or hexagonal grid of copies of children, with an optional masking polygon or region.
573// When called as a function, *without* a `p=` argument, returns a list of transformation matrices, one for each copy.
574// When called as a function, *with* a `p=` argument, returns a list of transformed copies of `p=`.
575//
576// Arguments:
577// spacing = Distance between copies in [X,Y] or scalar distance.
578// 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)
579// size = The [X,Y] size to spread the copies over.
580// ---
581// stagger = If true, make a staggered (hexagonal) grid. If false, make square grid. If `"alt"`, makes alternate staggered pattern. Default: false
582// 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.
583// 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
584// p = Either a point, pointlist, VNF or Bezier patch to be translated when used as a function.
585//
586// Side Effects:
587// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
588// `$col` is set to the integer column number for each child.
589// `$row` is set to the integer row number for each child.
590// `$idx` is set to a unique index for each child, progressing across rows first, from the bottom
591//
592// Examples:
593// grid_copies(size=50, spacing=10) cylinder(d=10, h=1);
594// grid_copies(size=50, spacing=[10,15]) cylinder(d=10, h=1);
595// grid_copies(spacing=10, n=[13,7], stagger=true) cylinder(d=6, h=5);
596// grid_copies(spacing=10, n=[13,7], stagger="alt") cylinder(d=6, h=5);
597// grid_copies(size=50, n=11, stagger=true) cylinder(d=5, h=1);
598//
599// Example:
600// poly = [[-25,-25], [25,25], [-25,25], [25,-25]];
601// grid_copies(spacing=5, stagger=true, inside=poly)
602// zrot(180/6) cylinder(d=5, h=1, $fn=6);
603// %polygon(poly);
604//
605// Example: Using `$row` and `$col`
606// grid_copies(spacing=8, n=8)
607// color(($row+$col)%2?"black":"red")
608// cube([8,8,0.01], center=false);
609//
610// Example: Makes a grid of hexagon pillars whose tops are all angled to reflect light at [0,0,50], if they were shiny.
611// hexregion = circle(r=50.01,$fn=6);
612// grid_copies(spacing=10, stagger=true, inside=hexregion)
613// union() { // Needed for OpenSCAD 2021.01 as noted above
614// ref_v = (unit([0,0,50]-point3d($pos)) + UP)/2;
615// half_of(v=-ref_v, cp=[0,0,5])
616// zrot(180/6)
617// cylinder(h=20, d=10/cos(180/6)+0.01, $fn=6);
618// }
619
620module grid2d(spacing, n, size, stagger=false, inside=undef, nonzero)
621{
622 deprecate("grid_copies");
623 grid_copies(spacing, n, size, stagger, inside, nonzero) children();
624}
625
626module grid_copies(spacing, n, size, stagger=false, inside=undef, nonzero)
627{
628 req_children($children);
629 dummy = assert(in_list(stagger, [false, true, "alt"]));
630 bounds = is_undef(inside)? undef :
631 is_path(inside)? pointlist_bounds(inside) :
632 assert(is_region(inside))
633 pointlist_bounds(flatten(inside));
634 nonzero = is_path(inside) ? default(nonzero,false)
635 : assert(is_undef(nonzero), "nonzero only allowed if inside is a polygon")
636 false;
637 size = is_num(size)? [size, size] :
638 is_vector(size)? assert(len(size)==2) size :
639 bounds!=undef? [
640 for (i=[0:1]) 2*max(abs(bounds[0][i]),bounds[1][i])
641 ] : undef;
642 spacing = is_num(spacing)? (
643 stagger!=false? polar_to_xy(spacing,60) :
644 [spacing,spacing]
645 ) :
646 is_vector(spacing)? assert(len(spacing)==2) spacing :
647 size!=undef? (
648 is_num(n)? v_div(size,(n-1)*[1,1]) :
649 is_vector(n)? assert(len(n)==2) v_div(size,n-[1,1]) :
650 v_div(size,(stagger==false? [1,1] : [2,2]))
651 ) :
652 undef;
653 n = is_num(n)? [n,n] :
654 is_vector(n)? assert(len(n)==2) n :
655 size!=undef && spacing!=undef? v_floor(v_div(size,spacing))+[1,1] :
656 [2,2];
657 offset = v_mul(spacing, n-[1,1])/2;
658
659 poslist =
660 stagger==false ?
661 [for (row = [0:1:n.y-1], col = [0:1:n.x-1])
662 let(
663 pos = v_mul([col,row],spacing) - offset
664 )
665 if (
666 is_undef(inside) ||
667 (is_path(inside) && point_in_polygon(pos, inside, nonzero=nonzero)>=0) ||
668 (is_region(inside) && point_in_region(pos, inside)>=0)
669 )
670 [pos,row,col]
671 ]
672 :
673 let( // stagger == true or stagger == "alt"
674 staggermod = (stagger == "alt")? 1 : 0,
675 cols1 = ceil(n.x/2),
676 cols2 = n.x - cols1
677 )
678 [for (row = [0:1:n.y-1])
679 let(
680 rowcols = ((row%2) == staggermod)? cols1 : cols2
681 )
682 if (rowcols > 0)
683 for (col = [0:1:rowcols-1])
684 let(
685 rowdx = (row%2 != staggermod)? spacing.x : 0,
686 pos = v_mul([2*col,row],spacing) + [rowdx,0] - offset
687 )
688 if (
689 is_undef(inside) ||
690 (is_path(inside) && point_in_polygon(pos, inside, nonzero=nonzero)>=0) ||
691 (is_region(inside) && point_in_region(pos, inside)>=0)
692 )
693 [pos, row, col * 2 + ((row%2!=staggermod)? 1 : 0)]
694 ];
695 for(i=idx(poslist)){
696 $idx=i;
697 $pos=poslist[i][0];
698 $row=poslist[i][1];
699 $col=poslist[i][2];
700 translate(poslist[i][0])children();
701 }
702}
703
704
705function grid_copies(spacing, n, size, stagger=false, inside=undef, nonzero, p=_NO_ARG) =
706 let(
707 dummy = assert(in_list(stagger, [false, true, "alt"])),
708 bounds = is_undef(inside)? undef :
709 is_path(inside)? pointlist_bounds(inside) :
710 assert(is_region(inside))
711 pointlist_bounds(flatten(inside)),
712 nonzero = is_path(inside) ? default(nonzero,false)
713 : assert(is_undef(nonzero), "nonzero only allowed if inside is a polygon")
714 false,
715 size = is_num(size)? [size, size] :
716 is_vector(size)? assert(len(size)==2) size :
717 bounds!=undef? [
718 for (i=[0:1]) 2*max(abs(bounds[0][i]),bounds[1][i])
719 ] : undef,
720 spacing = is_num(spacing)? (
721 stagger!=false? polar_to_xy(spacing,60) :
722 [spacing,spacing]
723 ) :
724 is_vector(spacing)? assert(len(spacing)==2) spacing :
725 size!=undef? (
726 is_num(n)? v_div(size,(n-1)*[1,1]) :
727 is_vector(n)? assert(len(n)==2) v_div(size,n-[1,1]) :
728 v_div(size,(stagger==false? [1,1] : [2,2]))
729 ) :
730 undef,
731 n = is_num(n)? [n,n] :
732 is_vector(n)? assert(len(n)==2) n :
733 size!=undef && spacing!=undef? v_floor(v_div(size,spacing))+[1,1] :
734 [2,2],
735 offset = v_mul(spacing, n-[1,1])/2,
736 mats = stagger == false
737 ? [
738 for (row = [0:1:n.y-1], col = [0:1:n.x-1])
739 let( pos = v_mul([col,row],spacing) - offset )
740 if (
741 is_undef(inside) ||
742 (is_path(inside) && point_in_polygon(pos, inside, nonzero=nonzero)>=0) ||
743 (is_region(inside) && point_in_region(pos, inside)>=0)
744 )
745 translate(pos)
746 ]
747 : // stagger == true or stagger == "alt"
748 let(
749 staggermod = (stagger == "alt")? 1 : 0,
750 cols1 = ceil(n.x/2),
751 cols2 = n.x - cols1
752 )
753 [
754 for (row = [0:1:n.y-1])
755 let( rowcols = ((row%2) == staggermod)? cols1 : cols2 )
756 if (rowcols > 0)
757 for (col = [0:1:rowcols-1])
758 let(
759 rowdx = (row%2 != staggermod)? spacing.x : 0,
760 pos = v_mul([2*col,row],spacing) + [rowdx,0] - offset
761 )
762 if (
763 is_undef(inside) ||
764 (is_path(inside) && point_in_polygon(pos, inside, nonzero=nonzero)>=0) ||
765 (is_region(inside) && point_in_region(pos, inside)>=0)
766 )
767 translate(pos)
768 ]
769 )
770 p==_NO_ARG? mats : [for (m = mats) apply(m, p)];
771
772
773//////////////////////////////////////////////////////////////////////
774// Section: Rotating copies of all children
775//////////////////////////////////////////////////////////////////////
776
777// Function&Module: rot_copies()
778// Synopsis: Rotates copies of children.
779// SynTags: MatList, Trans
780// Topics: Transformations, Distributors, Rotation, Copiers
781// See Also: rot_copies(), xrot_copies(), yrot_copies(), zrot_copies(), arc_copies(), sphere_copies(), move_copies(), xcopies(), ycopies(), zcopies(), line_copies(), grid_copies()
782//
783// Usage:
784// rot_copies(rots, [cp=], [sa=], [delta=], [subrot=]) CHILDREN;
785// rot_copies(rots, v, [cp=], [sa=], [delta=], [subrot=]) CHILDREN;
786// rot_copies(n=, [v=], [cp=], [sa=], [delta=], [subrot=]) CHILDREN;
787// Usage: As a function to translate points, VNF, or Bezier patches
788// copies = rot_copies(rots, [cp=], [sa=], [delta=], [subrot=], p=);
789// copies = rot_copies(rots, v, [cp=], [sa=], [delta=], [subrot=], p=);
790// copies = rot_copies(n=, [v=], [cp=], [sa=], [delta=], [subrot=], p=);
791// Usage: Get Translation Matrices
792// mats = rot_copies(rots, [cp=], [sa=], [delta=], [subrot=]);
793// mats = rot_copies(rots, v, [cp=], [sa=], [delta=], [subrot=]);
794// mats = rot_copies(n=, [v=], [cp=], [sa=], [delta=], [subrot=]);
795// Description:
796// When called as a module:
797// - 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.
798// - Given a list of scalar angles in `rots`, rotates copies of the children to each of those angles around the axis of rotation.
799// - If given a vector `v`, that becomes the axis of rotation. Default axis of rotation is UP.
800// - If given a count `n`, makes that many copies, rotated evenly around the axis.
801// - If given an offset `delta`, translates each child by that amount before rotating them into place. This makes rings.
802// - If given a centerpoint `cp`, centers the ring around that centerpoint.
803// - If `subrot` is true, each child will be rotated in place to keep the same size towards the center when making rings.
804// - The first (unrotated) copy will be placed at the relative starting angle `sa`.
805// .
806// When called as a function, *without* a `p=` argument, returns a list of transformation matrices, one for each copy.
807// When called as a function, *with* a `p=` argument, returns a list of transformed copies of `p=`.
808//
809// Arguments:
810// 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`.
811// v = If given, this is the vector of the axis to rotate around.
812// cp = Centerpoint to rotate around. Default: `[0,0,0]`
813// ---
814// n = Optional number of evenly distributed copies, rotated around the axis.
815// sa = Starting angle, in degrees. For use with `n`. Angle is in degrees counter-clockwise. Default: 0
816// delta = [X,Y,Z] amount to move away from cp before rotating. Makes rings of copies. Default: `[0,0,0]`
817// subrot = If false, don't sub-rotate children as they are copied around the ring. Only makes sense when used with `delta`. Default: `true`
818// p = Either a point, pointlist, VNF or Bezier patch to be translated when used as a function.
819//
820// Side Effects:
821// `$ang` is set to the rotation angle (or XYZ rotation triplet) of each child copy, and can be used to modify each child individually.
822// `$idx` is set to the index value of each child copy.
823// `$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.
824//
825//
826// Example:
827// #cylinder(h=20, r1=5, r2=0);
828// rot_copies([[45,0,0],[0,45,90],[90,-45,270]]) cylinder(h=20, r1=5, r2=0);
829//
830// Example:
831// rot_copies([45, 90, 135], v=DOWN+BACK)
832// yrot(90) cylinder(h=20, r1=5, r2=0);
833// color("red",0.333) yrot(90) cylinder(h=20, r1=5, r2=0);
834//
835// Example:
836// rot_copies(n=6, v=DOWN+BACK)
837// yrot(90) cylinder(h=20, r1=5, r2=0);
838// color("red",0.333) yrot(90) cylinder(h=20, r1=5, r2=0);
839//
840// Example:
841// rot_copies(n=6, v=DOWN+BACK, delta=[10,0,0])
842// yrot(90) cylinder(h=20, r1=5, r2=0);
843// color("red",0.333) yrot(90) cylinder(h=20, r1=5, r2=0);
844//
845// Example:
846// rot_copies(n=6, v=UP+FWD, delta=[10,0,0], sa=45)
847// yrot(90) cylinder(h=20, r1=5, r2=0);
848// color("red",0.333) yrot(90) cylinder(h=20, r1=5, r2=0);
849//
850// Example:
851// rot_copies(n=6, v=DOWN+BACK, delta=[20,0,0], subrot=false)
852// yrot(90) cylinder(h=20, r1=5, r2=0);
853// color("red",0.333) yrot(90) cylinder(h=20, r1=5, r2=0);
854module rot_copies(rots=[], v, cp=[0,0,0], n, sa=0, offset=0, delta=[0,0,0], subrot=true)
855{
856 req_children($children);
857 sang = sa + offset;
858 angs = !is_undef(n)?
859 (n<=0? [] : [for (i=[0:1:n-1]) i/n*360+sang]) :
860 rots==[]? [] :
861 assert(!is_string(rots), "Argument rots must be an angle, a list of angles, or a range of angles.")
862 assert(!is_undef(rots[0]), "Argument rots must be an angle, a list of angles, or a range of angles.")
863 [for (a=rots) a];
864 for ($idx = idx(angs)) {
865 $ang = angs[$idx];
866 $axis = v;
867 translate(cp) {
868 rotate(a=$ang, v=v) {
869 translate(delta) {
870 rot(a=(subrot? sang : $ang), v=v, reverse=true) {
871 translate(-cp) {
872 children();
873 }
874 }
875 }
876 }
877 }
878 }
879}
880
881
882function rot_copies(rots=[], v, cp=[0,0,0], n, sa=0, offset=0, delta=[0,0,0], subrot=true, p=_NO_ARG) =
883 let(
884 sang = sa + offset,
885 angs = !is_undef(n)?
886 (n<=0? [] : [for (i=[0:1:n-1]) i/n*360+sang]) :
887 rots==[]? [] :
888 assert(!is_string(rots), "Argument rots must be an angle, a list of angles, or a range of angles.")
889 assert(!is_undef(rots[0]), "Argument rots must be an angle, a list of angles, or a range of angles.")
890 [for (a=rots) a],
891 mats = [
892 for (ang = angs)
893 translate(cp) *
894 rot(a=ang, v=v) *
895 translate(delta) *
896 rot(a=(subrot? sang : ang), v=v, reverse=true) *
897 translate(-cp)
898 ]
899 )
900 p==_NO_ARG? mats : [for (m = mats) apply(m, p)];
901
902
903// Function&Module: xrot_copies()
904// Synopsis: Rotates copies of children around the X axis.
905// SynTags: MatList, Trans
906// Topics: Transformations, Distributors, Rotation, Copiers
907// See Also: rot_copies(), xrot_copies(), yrot_copies(), zrot_copies(), arc_copies(), sphere_copies(), move_copies(), xcopies(), ycopies(), zcopies(), line_copies(), grid_copies()
908//
909// Usage:
910// xrot_copies(rots, [cp], [r=|d=], [sa=], [subrot=]) CHILDREN;
911// xrot_copies(n=, [cp=], [r=|d=], [sa=], [subrot=]) CHILDREN;
912// Usage: As a function to translate points, VNF, or Bezier patches
913// copies = xrot_copies(rots, [cp], [r=|d=], [sa=], [subrot=], p=);
914// copies = xrot_copies(n=, [cp=], [r=|d=], [sa=], [subrot=], p=);
915// Usage: Get Translation Matrices
916// mats = xrot_copies(rots, [cp], [r=|d=], [sa=], [subrot=]);
917// mats = xrot_copies(n=, [cp=], [r=|d=], [sa=], [subrot=]);
918// Description:
919// When called as a module:
920// - Given an array of angles, rotates copies of the children to each of those angles around the X axis.
921// - If given a count `n`, makes that many copies, rotated evenly around the X axis.
922// - If given a radius `r` (or diameter `d`), distributes children around a ring of that size around the X axis.
923// - If given a centerpoint `cp`, centers the rotation around that centerpoint.
924// - If `subrot` is true, each child will be rotated in place to keep the same size towards the center when making rings.
925// - The first (unrotated) copy will be placed at the relative starting angle `sa`.
926// .
927// When called as a function, *without* a `p=` argument, returns a list of transformation matrices, one for each copy.
928// When called as a function, *with* a `p=` argument, returns a list of transformed copies of `p=`.
929//
930// Arguments:
931// rots = Optional array of rotation angles, in degrees, to make copies at.
932// cp = Centerpoint to rotate around.
933// ---
934// n = Optional number of evenly distributed copies to be rotated around the ring.
935// 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.
936// r = If given, makes a ring of child copies around the X axis, at the given radius. Default: 0
937// d = If given, makes a ring of child copies around the X axis, at the given diameter.
938// subrot = If false, don't sub-rotate children as they are copied around the ring.
939// p = Either a point, pointlist, VNF or Bezier patch to be translated when used as a function.
940//
941// Side Effects:
942// `$idx` is set to the index value of each child copy.
943// `$ang` is set to the rotation angle of each child copy, and can be used to modify each child individually.
944// `$axis` is set to the axis vector rotated around.
945//
946//
947// Example:
948// xrot_copies([180, 270, 315])
949// cylinder(h=20, r1=5, r2=0);
950// color("red",0.333) cylinder(h=20, r1=5, r2=0);
951//
952// Example:
953// xrot_copies(n=6)
954// cylinder(h=20, r1=5, r2=0);
955// color("red",0.333) cylinder(h=20, r1=5, r2=0);
956//
957// Example:
958// xrot_copies(n=6, r=10)
959// xrot(-90) cylinder(h=20, r1=5, r2=0);
960// color("red",0.333) xrot(-90) cylinder(h=20, r1=5, r2=0);
961//
962// Example:
963// xrot_copies(n=6, r=10, sa=45)
964// xrot(-90) cylinder(h=20, r1=5, r2=0);
965// color("red",0.333) xrot(-90) cylinder(h=20, r1=5, r2=0);
966//
967// Example:
968// xrot_copies(n=6, r=20, subrot=false)
969// xrot(-90) cylinder(h=20, r1=5, r2=0, center=true);
970// color("red",0.333) xrot(-90) cylinder(h=20, r1=5, r2=0, center=true);
971module xrot_copies(rots=[], cp=[0,0,0], n, sa=0, r, d, subrot=true)
972{
973 req_children($children);
974 r = get_radius(r=r, d=d, dflt=0);
975 rot_copies(rots=rots, v=RIGHT, cp=cp, n=n, sa=sa, delta=[0, r, 0], subrot=subrot) children();
976}
977
978
979function xrot_copies(rots=[], cp=[0,0,0], n, sa=0, r, d, subrot=true, p=_NO_ARG) =
980 let( r = get_radius(r=r, d=d, dflt=0) )
981 rot_copies(rots=rots, v=RIGHT, cp=cp, n=n, sa=sa, delta=[0, r, 0], subrot=subrot, p=p);
982
983
984// Function&Module: yrot_copies()
985// Synopsis: Rotates copies of children around the Y axis.
986// SynTags: MatList, Trans
987// Topics: Transformations, Distributors, Rotation, Copiers
988// See Also: rot_copies(), xrot_copies(), yrot_copies(), zrot_copies(), arc_copies(), sphere_copies(), move_copies(), xcopies(), ycopies(), zcopies(), line_copies(), grid_copies()
989//
990// Usage:
991// yrot_copies(rots, [cp], [r=|d=], [sa=], [subrot=]) CHILDREN;
992// yrot_copies(n=, [cp=], [r=|d=], [sa=], [subrot=]) CHILDREN;
993// Usage: As a function to translate points, VNF, or Bezier patches
994// copies = yrot_copies(rots, [cp], [r=|d=], [sa=], [subrot=], p=);
995// copies = yrot_copies(n=, [cp=], [r=|d=], [sa=], [subrot=], p=);
996// Usage: Get Translation Matrices
997// mats = yrot_copies(rots, [cp], [r=|d=], [sa=], [subrot=]);
998// mats = yrot_copies(n=, [cp=], [r=|d=], [sa=], [subrot=]);
999// Description:
1000// When called as a module:
1001// - Given an array of angles, rotates copies of the children to each of those angles around the Y axis.
1002// - If given a count `n`, makes that many copies, rotated evenly around the Y axis.
1003// - If given a radius `r` (or diameter `d`), distributes children around a ring of that size around the Y axis.
1004// - If given a centerpoint `cp`, centers the rotation around that centerpoint.
1005// - If `subrot` is true, each child will be rotated in place to keep the same size towards the center when making rings.
1006// - The first (unrotated) copy will be placed at the relative starting angle `sa`.
1007// .
1008// When called as a function, *without* a `p=` argument, returns a list of transformation matrices, one for each copy.
1009// When called as a function, *with* a `p=` argument, returns a list of transformed copies of `p=`.
1010//
1011// Arguments:
1012// rots = Optional array of rotation angles, in degrees, to make copies at.
1013// cp = Centerpoint to rotate around.
1014// ---
1015// n = Optional number of evenly distributed copies to be rotated around the ring.
1016// sa = Starting angle, in degrees. For use with `n`. Angle is in degrees counter-clockwise from X-, when facing the origin from Y+.
1017// r = If given, makes a ring of child copies around the Y axis, at the given radius. Default: 0
1018// d = If given, makes a ring of child copies around the Y axis, at the given diameter.
1019// subrot = If false, don't sub-rotate children as they are copied around the ring.
1020// p = Either a point, pointlist, VNF or Bezier patch to be translated when used as a function.
1021//
1022// Side Effects:
1023// `$idx` is set to the index value of each child copy.
1024// `$ang` is set to the rotation angle of each child copy, and can be used to modify each child individually.
1025// `$axis` is set to the axis vector rotated around.
1026//
1027//
1028// Example:
1029// yrot_copies([180, 270, 315])
1030// cylinder(h=20, r1=5, r2=0);
1031// color("red",0.333) cylinder(h=20, r1=5, r2=0);
1032//
1033// Example:
1034// yrot_copies(n=6)
1035// cylinder(h=20, r1=5, r2=0);
1036// color("red",0.333) cylinder(h=20, r1=5, r2=0);
1037//
1038// Example:
1039// yrot_copies(n=6, r=10)
1040// yrot(-90) cylinder(h=20, r1=5, r2=0);
1041// color("red",0.333) yrot(-90) cylinder(h=20, r1=5, r2=0);
1042//
1043// Example:
1044// yrot_copies(n=6, r=10, sa=45)
1045// yrot(-90) cylinder(h=20, r1=5, r2=0);
1046// color("red",0.333) yrot(-90) cylinder(h=20, r1=5, r2=0);
1047//
1048// Example:
1049// yrot_copies(n=6, r=20, subrot=false)
1050// yrot(-90) cylinder(h=20, r1=5, r2=0, center=true);
1051// color("red",0.333) yrot(-90) cylinder(h=20, r1=5, r2=0, center=true);
1052module yrot_copies(rots=[], cp=[0,0,0], n, sa=0, r, d, subrot=true)
1053{
1054 req_children($children);
1055 r = get_radius(r=r, d=d, dflt=0);
1056 rot_copies(rots=rots, v=BACK, cp=cp, n=n, sa=sa, delta=[-r, 0, 0], subrot=subrot) children();
1057}
1058
1059
1060function yrot_copies(rots=[], cp=[0,0,0], n, sa=0, r, d, subrot=true, p=_NO_ARG) =
1061 let( r = get_radius(r=r, d=d, dflt=0) )
1062 rot_copies(rots=rots, v=BACK, cp=cp, n=n, sa=sa, delta=[-r, 0, 0], subrot=subrot, p=p);
1063
1064
1065// Function&Module: zrot_copies()
1066// Synopsis: Rotates copies of children around the Z axis.
1067// SynTags: MatList, Trans
1068// Topics: Transformations, Distributors, Rotation, Copiers
1069// See Also: rot_copies(), xrot_copies(), yrot_copies(), zrot_copies(), arc_copies(), sphere_copies(), move_copies(), xcopies(), ycopies(), zcopies(), line_copies(), grid_copies()
1070//
1071// Usage:
1072// zrot_copies(rots, [cp], [r=|d=], [sa=], [subrot=]) CHILDREN;
1073// zrot_copies(n=, [cp=], [r=|d=], [sa=], [subrot=]) CHILDREN;
1074// Usage: As a function to translate points, VNF, or Bezier patches
1075// copies = zrot_copies(rots, [cp], [r=|d=], [sa=], [subrot=], p=);
1076// copies = zrot_copies(n=, [cp=], [r=|d=], [sa=], [subrot=], p=);
1077// Usage: Get Translation Matrices
1078// mats = zrot_copies(rots, [cp], [r=|d=], [sa=], [subrot=]);
1079// mats = zrot_copies(n=, [cp=], [r=|d=], [sa=], [subrot=]);
1080//
1081// Description:
1082// When called as a module:
1083// - Given an array of angles, rotates copies of the children to each of those angles around the Z axis.
1084// - If given a count `n`, makes that many copies, rotated evenly around the Z axis.
1085// - If given a radius `r` (or diameter `d`), distributes children around a ring of that size around the Z axis.
1086// - If given a centerpoint `cp`, centers the rotation around that centerpoint.
1087// - If `subrot` is true, each child will be rotated in place to keep the same size towards the center when making rings.
1088// - The first (unrotated) copy will be placed at the relative starting angle `sa`.
1089// .
1090// When called as a function, *without* a `p=` argument, returns a list of transformation matrices, one for each copy.
1091// When called as a function, *with* a `p=` argument, returns a list of transformed copies of `p=`.
1092//
1093// Arguments:
1094// rots = Optional array of rotation angles, in degrees, to make copies at.
1095// cp = Centerpoint to rotate around. Default: [0,0,0]
1096// ---
1097// n = Optional number of evenly distributed copies to be rotated around the ring.
1098// 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
1099// r = If given, makes a ring of child copies around the Z axis, at the given radius. Default: 0
1100// d = If given, makes a ring of child copies around the Z axis, at the given diameter.
1101// subrot = If false, don't sub-rotate children as they are copied around the ring. Default: true
1102// p = Either a point, pointlist, VNF or Bezier patch to be translated when used as a function.
1103//
1104// Side Effects:
1105// `$idx` is set to the index value of each child copy.
1106// `$ang` is set to the rotation angle of each child copy, and can be used to modify each child individually.
1107// `$axis` is set to the axis vector rotated around.
1108//
1109//
1110// Example:
1111// zrot_copies([180, 270, 315])
1112// yrot(90) cylinder(h=20, r1=5, r2=0);
1113// color("red",0.333) yrot(90) cylinder(h=20, r1=5, r2=0);
1114//
1115// Example:
1116// zrot_copies(n=6)
1117// yrot(90) cylinder(h=20, r1=5, r2=0);
1118// color("red",0.333) yrot(90) cylinder(h=20, r1=5, r2=0);
1119//
1120// Example:
1121// zrot_copies(n=6, r=10)
1122// yrot(90) cylinder(h=20, r1=5, r2=0);
1123// color("red",0.333) yrot(90) cylinder(h=20, r1=5, r2=0);
1124//
1125// Example:
1126// zrot_copies(n=6, r=20, sa=45)
1127// yrot(90) cylinder(h=20, r1=5, r2=0, center=true);
1128// color("red",0.333) yrot(90) cylinder(h=20, r1=5, r2=0, center=true);
1129//
1130// Example:
1131// zrot_copies(n=6, r=20, subrot=false)
1132// yrot(-90) cylinder(h=20, r1=5, r2=0, center=true);
1133// color("red",0.333) yrot(-90) cylinder(h=20, r1=5, r2=0, center=true);
1134module zrot_copies(rots=[], cp=[0,0,0], n, sa=0, r, d, subrot=true)
1135{
1136 r = get_radius(r=r, d=d, dflt=0);
1137 rot_copies(rots=rots, v=UP, cp=cp, n=n, sa=sa, delta=[r, 0, 0], subrot=subrot) children();
1138}
1139
1140
1141function zrot_copies(rots=[], cp=[0,0,0], n, sa=0, r, d, subrot=true, p=_NO_ARG) =
1142 let( r = get_radius(r=r, d=d, dflt=0) )
1143 rot_copies(rots=rots, v=UP, cp=cp, n=n, sa=sa, delta=[r, 0, 0], subrot=subrot, p=p);
1144
1145
1146// Function&Module: arc_copies()
1147// Synopsis: Distributes duplicates of children along an arc.
1148// SynTags: MatList, Trans
1149// Topics: Transformations, Distributors, Rotation, Copiers
1150// See Also: rot_copies(), xrot_copies(), yrot_copies(), zrot_copies(), sphere_copies(), move_copies(), xcopies(), ycopies(), zcopies(), line_copies(), grid_copies()
1151//
1152// Usage:
1153// arc_copies(n, r|d=, [sa=], [ea=], [rot=]) CHILDREN;
1154// arc_copies(n, rx=|dx=, ry=|dy=, [sa=], [ea=], [rot=]) CHILDREN;
1155// Usage: As a function to translate points, VNF, or Bezier patches
1156// copies = arc_copies(n, r|d=, [sa=], [ea=], [rot=], p=);
1157// copies = arc_copies(n, rx=|dx=, ry=|dy=, [sa=], [ea=], [rot=], p=);
1158// Usage: Get Translation Matrices
1159// mats = arc_copies(n, r|d=, [sa=], [ea=], [rot=]);
1160// mats = arc_copies(n, rx=|dx=, ry=|dy=, [sa=], [ea=], [rot=]);
1161//
1162//
1163// Description:
1164// When called as a module, evenly distributes n duplicate children around an ovoid arc on the XY plane.
1165// When called as a function, *without* a `p=` argument, returns a list of transformation matrices, one for each copy.
1166// When called as a function, *with* a `p=` argument, returns a list of transformed copies of `p=`.
1167//
1168// Arguments:
1169// n = number of copies to distribute around the circle. (Default: 6)
1170// r = radius of circle (Default: 1)
1171// ---
1172// rx = radius of ellipse on X axis. Used instead of r.
1173// ry = radius of ellipse on Y axis. Used instead of r.
1174// d = diameter of circle. (Default: 2)
1175// dx = diameter of ellipse on X axis. Used instead of d.
1176// dy = diameter of ellipse on Y axis. Used instead of d.
1177// rot = whether to rotate the copied children. (Default: true)
1178// sa = starting angle. (Default: 0.0)
1179// ea = ending angle. Will distribute copies CCW from sa to ea. (Default: 360.0)
1180// p = Either a point, pointlist, VNF or Bezier patch to be translated when used as a function.
1181//
1182// Side Effects:
1183// `$ang` is set to the rotation angle of each child copy, and can be used to modify each child individually.
1184// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
1185// `$idx` is set to the index value of each child copy.
1186//
1187//
1188// Example:
1189// #cube(size=[10,3,3],center=true);
1190// arc_copies(d=40, n=5) cube(size=[10,3,3],center=true);
1191//
1192// Example:
1193// #cube(size=[10,3,3],center=true);
1194// arc_copies(d=40, n=5, sa=45, ea=225) cube(size=[10,3,3],center=true);
1195//
1196// Example:
1197// #cube(size=[10,3,3],center=true);
1198// arc_copies(r=15, n=8, rot=false) cube(size=[10,3,3],center=true);
1199//
1200// Example:
1201// #cube(size=[10,3,3],center=true);
1202// arc_copies(rx=20, ry=10, n=8) cube(size=[10,3,3],center=true);
1203// Example(2D): Using `$idx` to alternate shapes
1204// arc_copies(r=50, n=19, sa=0, ea=180)
1205// if ($idx % 2 == 0) rect(6);
1206// else circle(d=6);
1207
1208module arc_of(n=6,r,rx,ry,d,dx,dy,sa=0,ea=360,rot=true){
1209 deprecate("arc_copies");
1210 arc_copies(n,r,rx,ry,d,dx,dy,sa,ea,rot) children();
1211}
1212
1213
1214module arc_copies(
1215 n=6,
1216 r=undef,
1217 rx=undef, ry=undef,
1218 d=undef, dx=undef, dy=undef,
1219 sa=0, ea=360,
1220 rot=true
1221) {
1222 req_children($children);
1223 rx = get_radius(r1=rx, r=r, d1=dx, d=d, dflt=1);
1224 ry = get_radius(r1=ry, r=r, d1=dy, d=d, dflt=1);
1225 sa = posmod(sa, 360);
1226 ea = posmod(ea, 360);
1227 n = (abs(ea-sa)<0.01)?(n+1):n;
1228 delt = (((ea<=sa)?360.0:0)+ea-sa)/(n-1);
1229 for ($idx = [0:1:n-1]) {
1230 $ang = sa + ($idx * delt);
1231 $pos =[rx*cos($ang), ry*sin($ang), 0];
1232 translate($pos) {
1233 zrot(rot? atan2(ry*sin($ang), rx*cos($ang)) : 0) {
1234 children();
1235 }
1236 }
1237 }
1238}
1239
1240
1241function arc_copies(
1242 n=6,
1243 r=undef,
1244 rx=undef, ry=undef,
1245 d=undef, dx=undef, dy=undef,
1246 sa=0, ea=360,
1247 rot=true,
1248 p=_NO_ARG
1249) =
1250 let(
1251 rx = get_radius(r1=rx, r=r, d1=dx, d=d, dflt=1),
1252 ry = get_radius(r1=ry, r=r, d1=dy, d=d, dflt=1),
1253 sa = posmod(sa, 360),
1254 ea = posmod(ea, 360),
1255 n = (abs(ea-sa)<0.01)?(n+1):n,
1256 delt = (((ea<=sa)?360.0:0)+ea-sa)/(n-1),
1257 mats = [
1258 for (i = [0:1:n-1])
1259 let(
1260 ang = sa + (i * delt),
1261 pos =[rx*cos(ang), ry*sin(ang), 0],
1262 ang2 = rot? atan2(ry*sin(ang), rx*cos(ang)) : 0
1263 )
1264 translate(pos) * zrot(ang2)
1265 ]
1266 )
1267 p==_NO_ARG? mats : [for (m = mats) apply(m, p)];
1268
1269
1270
1271// Function&Module: sphere_copies()
1272// Synopsis: Distributes copies of children over the surface of a sphere.
1273// SynTags: MatList, Trans
1274// Topics: Transformations, Distributors, Rotation, Copiers
1275// See Also: rot_copies(), xrot_copies(), yrot_copies(), zrot_copies(), arc_copies(), move_copies(), xcopies(), ycopies(), zcopies(), line_copies(), grid_copies()
1276//
1277// Usage:
1278// sphere_copies(n, r|d=, [cone_ang=], [scale=], [perp=]) CHILDREN;
1279// Usage: As a function to translate points, VNF, or Bezier patches
1280// copies = sphere_copies(n, r|d=, [cone_ang=], [scale=], [perp=], p=);
1281// Usage: Get Translation Matrices
1282// mats = sphere_copies(n, r|d=, [cone_ang=], [scale=], [perp=]);
1283//
1284// Description:
1285// When called as a module, spreads children semi-evenly over the surface of a sphere or ellipsoid.
1286// When called as a function, *without* a `p=` argument, returns a list of transformation matrices, one for each copy.
1287// When called as a function, *with* a `p=` argument, returns a list of transformed copies of `p=`.
1288//
1289// Arguments:
1290// n = How many copies to evenly spread over the surface.
1291// r = Radius of the sphere to distribute over
1292// ---
1293// d = Diameter of the sphere to distribute over
1294// 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
1295// scale = The [X,Y,Z] scaling factors to reshape the sphere being covered.
1296// perp = If true, rotate children to be perpendicular to the sphere surface. Default: true
1297// p = Either a point, pointlist, VNF or Bezier patch to be translated when used as a function.
1298//
1299// Side Effects:
1300// `$pos` is set to the relative post-scaled centerpoint of each child copy, and can be used to modify each child individually.
1301// `$theta` is set to the theta angle of the child from the center of the sphere.
1302// `$phi` is set to the pre-scaled phi angle of the child from the center of the sphere.
1303// `$rad` is set to the pre-scaled radial distance of the child from the center of the sphere.
1304// `$idx` is set to the index number of each child being copied.
1305//
1306//
1307// Example:
1308// sphere_copies(n=250, d=100, cone_ang=45, scale=[3,3,1])
1309// cylinder(d=10, h=10, center=false);
1310//
1311// Example:
1312// sphere_copies(n=500, d=100, cone_ang=180)
1313// color(unit(point3d(v_abs($pos))))
1314// cylinder(d=8, h=10, center=false);
1315
1316module ovoid_spread(n=100, r=undef, d=undef, cone_ang=90, scale=[1,1,1], perp=true)
1317{
1318 deprecate("sphere_copies");
1319 sphere_copies(n,r,d,cone_ang,scale,perp) children();
1320}
1321
1322
1323module sphere_copies(n=100, r=undef, d=undef, cone_ang=90, scale=[1,1,1], perp=true)
1324{
1325 req_children($children);
1326 r = get_radius(r=r, d=d, dflt=50);
1327 cnt = ceil(n / (cone_ang/180));
1328
1329 // Calculate an array of [theta,phi] angles for `n` number of
1330 // points, almost evenly spaced across the surface of a sphere.
1331 // This approximation is based on the golden spiral method.
1332 theta_phis = [for (x=[0:1:n-1]) [180*(1+sqrt(5))*(x+0.5)%360, acos(1-2*(x+0.5)/cnt)]];
1333
1334 for ($idx = idx(theta_phis)) {
1335 tp = theta_phis[$idx];
1336 xyz = spherical_to_xyz(r, tp[0], tp[1]);
1337 $pos = v_mul(xyz,point3d(scale,1));
1338 $theta = tp[0];
1339 $phi = tp[1];
1340 $rad = r;
1341 translate($pos) {
1342 if (perp) {
1343 rot(from=UP, to=xyz) children();
1344 } else {
1345 children();
1346 }
1347 }
1348 }
1349}
1350
1351
1352function sphere_copies(n=100, r=undef, d=undef, cone_ang=90, scale=[1,1,1], perp=true, p=_NO_ARG) =
1353 let(
1354 r = get_radius(r=r, d=d, dflt=50),
1355 cnt = ceil(n / (cone_ang/180)),
1356
1357 // Calculate an array of [theta,phi] angles for `n` number of
1358 // points, almost evenly spaced across the surface of a sphere.
1359 // This approximation is based on the golden spiral method.
1360 theta_phis = [for (x=[0:1:n-1]) [180*(1+sqrt(5))*(x+0.5)%360, acos(1-2*(x+0.5)/cnt)]],
1361
1362 mats = [
1363 for (tp = theta_phis)
1364 let(
1365 xyz = spherical_to_xyz(r, tp[0], tp[1]),
1366 pos = v_mul(xyz,point3d(scale,1))
1367 )
1368 translate(pos) *
1369 (perp? rot(from=UP, to=xyz) : ident(4))
1370 ]
1371 )
1372 p==_NO_ARG? mats : [for (m = mats) apply(m, p)];
1373
1374
1375
1376// Section: Placing copies of all children on a path
1377
1378
1379// Function&Module: path_copies()
1380// Synopsis: Uniformly distributes copies of children along a path.
1381// SynTags: MatList, Trans
1382// Topics: Transformations, Distributors, Copiers
1383// See Also: line_copies(), move_copies(), xcopies(), ycopies(), zcopies(), grid_copies(), xflip_copy(), yflip_copy(), zflip_copy(), mirror_copy()
1384//
1385// Usage: Uniformly distribute copies
1386// path_copies(path, [n], [spacing], [sp], [rotate_children], [closed=]) CHILDREN;
1387// Usage: Place copies at specified locations
1388// path_copies(path, dist=, [rotate_children=], [closed=]) CHILDREN;
1389// Usage: As a function to translate points, VNF, or Bezier patches
1390// copies = path_copies(path, [n], [spacing], [sp], [rotate_children], [closed=], p=);
1391// copies = path_copies(path, dist=, [rotate_children=], [closed=], p=);
1392// Usage: Get Translation Matrices
1393// mats = path_copies(path, [n], [spacing], [sp], [rotate_children], [closed=]);
1394// mats = path_copies(path, dist=, [rotate_children=], [closed=]);
1395//
1396// Description:
1397// When called as a module:
1398// - Place copies all of the children at points along the path based on path length. You can specify `dist` as
1399// - 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
1400// - placed at uniformly spaced points along the path. If you specify `n` but not `spacing` then `n` copies will be placed
1401// - with one at path[0] if `closed` is true, or spanning the entire path from start to end if `closed` is false.
1402// - 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.
1403// - If you specify `sp` then the copies will start at distance `sp` from the start of the path.
1404// .
1405// When called as a function, *without* a `p=` argument, returns a list of transformation matrices, one for each copy.
1406// When called as a function, *with* a `p=` argument, returns a list of transformed copies of `p=`.
1407//
1408// Arguments:
1409// path = path or 1-region where children are placed
1410// n = number of copies
1411// spacing = space between copies
1412// sp = if given, copies will start distance sp from the path start and spread beyond that point
1413// rotate_children = if true, rotate children to line up with curve normal. Default: true
1414// ---
1415// dist = Specify a list of distances to determine placement of children.
1416// closed = If true treat path as a closed curve. Default: false
1417// p = Either a point, pointlist, VNF or Bezier patch to be translated when used as a function.
1418//
1419// Side Effects:
1420// `$pos` is set to the center of each copy
1421// `$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`.
1422// `$dir` is set to the direction vector of the path at the point where the copy is placed.
1423// `$normal` is set to the direction of the normal vector to the path direction that is coplanar with the path at this point
1424//
1425//
1426// Example(2D):
1427// spiral = [for(theta=[0:360*8]) theta * [cos(theta), sin(theta)]]/100;
1428// stroke(spiral,width=.25);
1429// color("red") path_copies(spiral, n=100) circle(r=1);
1430// Example(2D):
1431// circle = regular_ngon(n=64, or=10);
1432// stroke(circle,width=1,closed=true);
1433// color("green") path_copies(circle, n=7, closed=true) circle(r=1+$idx/3);
1434// Example(2D):
1435// heptagon = regular_ngon(n=7, or=10);
1436// stroke(heptagon, width=1, closed=true);
1437// color("purple") path_copies(heptagon, n=9, closed=true) rect([0.5,3],anchor=FRONT);
1438// Example(2D): Direction at the corners is the average of the two adjacent edges
1439// heptagon = regular_ngon(n=7, or=10);
1440// stroke(heptagon, width=1, closed=true);
1441// color("purple") path_copies(heptagon, n=7, closed=true) rect([0.5,3],anchor=FRONT);
1442// Example(2D): Don't rotate the children
1443// heptagon = regular_ngon(n=7, or=10);
1444// stroke(heptagon, width=1, closed=true);
1445// color("red") path_copies(heptagon, n=9, closed=true, rotate_children=false) rect([0.5,3],anchor=FRONT);
1446// Example(2D): Open path, specify `n`
1447// sinwav = [for(theta=[0:360]) 5*[theta/180, sin(theta)]];
1448// stroke(sinwav,width=.1);
1449// color("red") path_copies(sinwav, n=5) rect([.2,1.5],anchor=FRONT);
1450// Example(2D): Open path, specify `n` and `spacing`
1451// sinwav = [for(theta=[0:360]) 5*[theta/180, sin(theta)]];
1452// stroke(sinwav,width=.1);
1453// color("red") path_copies(sinwav, n=5, spacing=1) rect([.2,1.5],anchor=FRONT);
1454// Example(2D): Closed path, specify `n` and `spacing`, copies centered around circle[0]
1455// circle = regular_ngon(n=64,or=10);
1456// stroke(circle,width=.1,closed=true);
1457// color("red") path_copies(circle, n=10, spacing=1, closed=true) rect([.2,1.5],anchor=FRONT);
1458// Example(2D): Open path, specify `spacing`
1459// sinwav = [for(theta=[0:360]) 5*[theta/180, sin(theta)]];
1460// stroke(sinwav,width=.1);
1461// color("red") path_copies(sinwav, spacing=5) rect([.2,1.5],anchor=FRONT);
1462// Example(2D): Open path, specify `sp`
1463// sinwav = [for(theta=[0:360]) 5*[theta/180, sin(theta)]];
1464// stroke(sinwav,width=.1);
1465// color("red") path_copies(sinwav, n=5, sp=18) rect([.2,1.5],anchor=FRONT);
1466// Example(2D): Open path, specify `dist`
1467// sinwav = [for(theta=[0:360]) 5*[theta/180, sin(theta)]];
1468// stroke(sinwav,width=.1);
1469// color("red") path_copies(sinwav, dist=[1,4,9,16]) rect([.2,1.5],anchor=FRONT);
1470// Example(2D):
1471// wedge = arc(angle=[0,100], r=10, $fn=64);
1472// difference(){
1473// polygon(concat([[0,0]],wedge));
1474// path_copies(wedge,n=5,spacing=3) fwd(.1) rect([1,4],anchor=FRONT);
1475// }
1476// Example(Spin,VPD=115): 3d example, with children rotated into the plane of the path
1477// tilted_circle = lift_plane([[0,0,0], [5,0,5], [0,2,3]],regular_ngon(n=64, or=12));
1478// path_sweep(regular_ngon(n=16,or=.1),tilted_circle);
1479// path_copies(tilted_circle, n=15,closed=true) {
1480// color("blue") cyl(h=3,r=.2, anchor=BOTTOM); // z-aligned cylinder
1481// color("red") xcyl(h=10,r=.2, anchor=FRONT+LEFT); // x-aligned cylinder
1482// }
1483// Example(Spin,VPD=115): 3d example, with rotate_children set to false
1484// tilted_circle = lift_plane([[0,0,0], [5,0,5], [0,2,3]], regular_ngon(n=64, or=12));
1485// path_sweep(regular_ngon(n=16,or=.1),tilted_circle);
1486// path_copies(tilted_circle, n=25,rotate_children=false,closed=true) {
1487// color("blue") cyl(h=3,r=.2, anchor=BOTTOM); // z-aligned cylinder
1488// color("red") xcyl(h=10,r=.2, anchor=FRONT+LEFT); // x-aligned cylinder
1489// }
1490
1491module path_spread(path, n, spacing, sp=undef, rotate_children=true, dist, closed){
1492 deprecate("path_copes");
1493 path_copies(path,n,spacing,sp,dist,rotate_children,dist, closed) children();
1494}
1495
1496
1497module path_copies(path, n, spacing, sp=undef, dist, rotate_children=true, dist, closed)
1498{
1499 req_children($children);
1500 is_1reg = is_1region(path);
1501 path = is_1reg ? path[0] : path;
1502 closed = default(closed, is_1reg);
1503 length = path_length(path,closed);
1504 distind = is_def(dist) ? sortidx(dist) : undef;
1505 distances =
1506 is_def(dist) ? assert(is_undef(n) && is_undef(spacing) && is_undef(sp), "Can't use n, spacing or undef with dist")
1507 select(dist,distind)
1508 : is_def(sp)? ( // Start point given
1509 is_def(n) && is_def(spacing)? count(n,sp,spacing) :
1510 is_def(n)? lerpn(sp, length, n) :
1511 list([sp:spacing:length])
1512 )
1513 : is_def(n) && is_undef(spacing)? lerpn(0,length,n,!closed) // N alone given
1514 : ( // No start point and spacing is given, N maybe given
1515 let(
1516 n = is_def(n)? n : floor(length/spacing)+(closed?0:1),
1517 ptlist = count(n,0,spacing),
1518 listcenter = mean(ptlist)
1519 ) closed?
1520 sort([for(entry=ptlist) posmod(entry-listcenter,length)]) :
1521 [for(entry=ptlist) entry + length/2-listcenter ]
1522 );
1523 distOK = min(distances)>=0 && max(distances)<=length;
1524 dummy = assert(distOK,"Cannot fit all of the copies");
1525 cutlist = path_cut_points(path, distances, closed, direction=true);
1526 planar = len(path[0])==2;
1527 for(i=[0:1:len(cutlist)-1]) {
1528 $pos = cutlist[i][0];
1529 $idx = is_def(dist) ? distind[i] : i;
1530 $dir = !rotate_children ? (planar?[1,0]:[1,0,0]) : cutlist[i][2];
1531 $normal = !rotate_children? (planar?[0,1]:[0,0,1]) : cutlist[i][3];
1532 translate($pos) {
1533 if (rotate_children) {
1534 if(planar) {
1535 rot(from=[0,1],to=cutlist[i][3]) children();
1536 } else {
1537 frame_map(x=cutlist[i][2], z=cutlist[i][3])
1538 children();
1539 }
1540 } else {
1541 children();
1542 }
1543 }
1544 }
1545}
1546
1547
1548function path_copies(path, n, spacing, sp=undef, dist, rotate_children=true, dist, closed, p=_NO_ARG) =
1549 let(
1550 is_1reg = is_1region(path),
1551 path = is_1reg ? path[0] : path,
1552 closed = default(closed, is_1reg),
1553 length = path_length(path,closed),
1554 distind = is_def(dist) ? sortidx(dist) : undef,
1555 distances =
1556 is_def(dist) ? assert(is_undef(n) && is_undef(spacing) && is_undef(sp), "Can't use n, spacing or undef with dist")
1557 select(dist,distind)
1558 : is_def(sp)? ( // Start point given
1559 is_def(n) && is_def(spacing)? count(n,sp,spacing) :
1560 is_def(n)? lerpn(sp, length, n) :
1561 list([sp:spacing:length])
1562 )
1563 : is_def(n) && is_undef(spacing)? lerpn(0,length,n,!closed) // N alone given
1564 : ( // No start point and spacing is given, N maybe given
1565 let(
1566 n = is_def(n)? n : floor(length/spacing)+(closed?0:1),
1567 ptlist = count(n,0,spacing),
1568 listcenter = mean(ptlist)
1569 ) closed?
1570 sort([for(entry=ptlist) posmod(entry-listcenter,length)]) :
1571 [for(entry=ptlist) entry + length/2-listcenter ]
1572 ),
1573 distOK = min(distances)>=0 && max(distances)<=length,
1574 dummy = assert(distOK,"Cannot fit all of the copies"),
1575 cutlist = path_cut_points(path, distances, closed, direction=true),
1576 planar = len(path[0])==2,
1577 mats = [
1578 for(i=[0:1:len(cutlist)-1])
1579 translate(cutlist[i][0]) * (
1580 !rotate_children? ident(4) :
1581 planar? rot(from=[0,1],to=cutlist[i][3]) :
1582 frame_map(x=cutlist[i][2], z=cutlist[i][3])
1583 )
1584 ]
1585 )
1586 p==_NO_ARG? mats : [for (m = mats) apply(m, p)];
1587
1588
1589
1590//////////////////////////////////////////////////////////////////////
1591// Section: Making a copy of all children with reflection
1592//////////////////////////////////////////////////////////////////////
1593
1594// Function&Module: xflip_copy()
1595// Synopsis: Makes a copy of children mirrored across the X axis.
1596// SynTags: MatList, Trans
1597// Topics: Transformations, Distributors, Translation, Copiers
1598// See Also: yflip_copy(), zflip_copy(), mirror_copy(), path_copies(), move_copies(), xcopies(), ycopies(), zcopies(), line_copies(), grid_copies()
1599//
1600// Usage:
1601// xflip_copy([offset], [x]) CHILDREN;
1602// Usage: As a function to translate points, VNF, or Bezier patches
1603// copies = xflip_copy([offset], [x], p=);
1604// Usage: Get Translation Matrices
1605// mats = xflip_copy([offset], [x]);
1606//
1607// Description:
1608// When called as a module, makes a copy of the children, mirrored across the X axis.
1609// When called as a function, *without* a `p=` argument, returns a list of transformation matrices, one for each copy.
1610// When called as a function, *with* a `p=` argument, returns a list of transformed copies of `p=`.
1611//
1612// Arguments:
1613// offset = Distance to offset children right, before copying.
1614// x = The X coordinate of the mirroring plane. Default: 0
1615// ---
1616// p = Either a point, pointlist, VNF or Bezier patch to be translated when used as a function.
1617//
1618// Side Effects:
1619// `$orig` is true for the original instance of children. False for the copy.
1620// `$idx` is set to the index value of each copy.
1621//
1622//
1623// Example:
1624// xflip_copy() yrot(90) cylinder(h=20, r1=4, r2=0);
1625// color("blue",0.25) cube([0.01,15,15], center=true);
1626//
1627// Example:
1628// xflip_copy(offset=5) yrot(90) cylinder(h=20, r1=4, r2=0);
1629// color("blue",0.25) cube([0.01,15,15], center=true);
1630//
1631// Example:
1632// xflip_copy(x=-5) yrot(90) cylinder(h=20, r1=4, r2=0);
1633// color("blue",0.25) left(5) cube([0.01,15,15], center=true);
1634module xflip_copy(offset=0, x=0)
1635{
1636 req_children($children);
1637 mirror_copy(v=[1,0,0], offset=offset, cp=[x,0,0]) children();
1638}
1639
1640
1641function xflip_copy(offset=0, x=0, p=_NO_ARG) =
1642 mirror_copy(v=[1,0,0], offset=offset, cp=[x,0,0], p=p);
1643
1644
1645// Function&Module: yflip_copy()
1646// Synopsis: Makes a copy of children mirrored across the Y axis.
1647// SynTags: MatList, Trans
1648// Topics: Transformations, Distributors, Translation, Copiers
1649// See Also: xflip_copy(), zflip_copy(), mirror_copy(), path_copies(), move_copies(), xcopies(), ycopies(), zcopies(), line_copies(), grid_copies()
1650//
1651// Usage:
1652// yflip_copy([offset], [y]) CHILDREN;
1653// Usage: As a function to translate points, VNF, or Bezier patches
1654// copies = yflip_copy([offset], [y], p=);
1655// Usage: Get Translation Matrices
1656// mats = yflip_copy([offset], [y]);
1657//
1658// Description:
1659// When called as a module, makes a copy of the children, mirrored across the Y axis.
1660// When called as a function, *without* a `p=` argument, returns a list of transformation matrices, one for each copy.
1661// When called as a function, *with* a `p=` argument, returns a list of transformed copies of `p=`.
1662//
1663// Arguments:
1664// offset = Distance to offset children back, before copying.
1665// y = The Y coordinate of the mirroring plane. Default: 0
1666// ---
1667// p = Either a point, pointlist, VNF or Bezier patch to be translated when used as a function.
1668//
1669// Side Effects:
1670// `$orig` is true for the original instance of children. False for the copy.
1671// `$idx` is set to the index value of each copy.
1672//
1673//
1674// Example:
1675// yflip_copy() xrot(-90) cylinder(h=20, r1=4, r2=0);
1676// color("blue",0.25) cube([15,0.01,15], center=true);
1677//
1678// Example:
1679// yflip_copy(offset=5) xrot(-90) cylinder(h=20, r1=4, r2=0);
1680// color("blue",0.25) cube([15,0.01,15], center=true);
1681//
1682// Example:
1683// yflip_copy(y=-5) xrot(-90) cylinder(h=20, r1=4, r2=0);
1684// color("blue",0.25) fwd(5) cube([15,0.01,15], center=true);
1685module yflip_copy(offset=0, y=0)
1686{
1687 req_children($children);
1688 mirror_copy(v=[0,1,0], offset=offset, cp=[0,y,0]) children();
1689}
1690
1691
1692function yflip_copy(offset=0, y=0, p=_NO_ARG) =
1693 mirror_copy(v=[0,1,0], offset=offset, cp=[0,y,0], p=p);
1694
1695
1696// Function&Module: zflip_copy()
1697// Synopsis: Makes a copy of children mirrored across the Z axis.
1698// SynTags: MatList, Trans
1699// Topics: Transformations, Distributors, Translation, Copiers
1700// See Also: xflip_copy(), yflip_copy(), mirror_copy(), path_copies(), move_copies(), xcopies(), ycopies(), zcopies(), line_copies(), grid_copies()
1701//
1702// Usage:
1703// zflip_copy([offset], [z]) CHILDREN;
1704// Usage: As a function to translate points, VNF, or Bezier patches
1705// copies = zflip_copy([offset], [z], p=);
1706// Usage: Get Translation Matrices
1707// mats = zflip_copy([offset], [z]);
1708//
1709// Description:
1710// When called as a module, makes a copy of the children, mirrored across the Z axis.
1711// When called as a function, *without* a `p=` argument, returns a list of transformation matrices, one for each copy.
1712// When called as a function, *with* a `p=` argument, returns a list of transformed copies of `p=`.
1713//
1714// Arguments:
1715// offset = Distance to offset children up, before copying.
1716// z = The Z coordinate of the mirroring plane. Default: 0
1717// ---
1718// p = Either a point, pointlist, VNF or Bezier patch to be translated when used as a function.
1719//
1720// Side Effects:
1721// `$orig` is true for the original instance of children. False for the copy.
1722// `$idx` is set to the index value of each copy.
1723//
1724//
1725// Example:
1726// zflip_copy() cylinder(h=20, r1=4, r2=0);
1727// color("blue",0.25) cube([15,15,0.01], center=true);
1728//
1729// Example:
1730// zflip_copy(offset=5) cylinder(h=20, r1=4, r2=0);
1731// color("blue",0.25) cube([15,15,0.01], center=true);
1732//
1733// Example:
1734// zflip_copy(z=-5) cylinder(h=20, r1=4, r2=0);
1735// color("blue",0.25) down(5) cube([15,15,0.01], center=true);
1736module zflip_copy(offset=0, z=0)
1737{
1738 req_children($children);
1739 mirror_copy(v=[0,0,1], offset=offset, cp=[0,0,z]) children();
1740}
1741
1742
1743function zflip_copy(offset=0, z=0, p=_NO_ARG) =
1744 mirror_copy(v=[0,0,1], offset=offset, cp=[0,0,z], p=p);
1745
1746
1747// Function&Module: mirror_copy()
1748// Synopsis: Makes a copy of children mirrored across a given plane.
1749// SynTags: MatList, Trans
1750// Topics: Transformations, Distributors, Translation, Copiers
1751// See Also: xflip_copy(), yflip_copy(), zflip_copy(), path_copies(), move_copies(), xcopies(), ycopies(), zcopies(), line_copies(), grid_copies()
1752//
1753// Usage:
1754// mirror_copy(v, [cp], [offset]) CHILDREN;
1755// Usage: As a function to translate points, VNF, or Bezier patches
1756// copies = mirror_copy(v, [cp], [offset], p=);
1757// Usage: Get Translation Matrices
1758// mats = mirror_copy(v, [cp], [offset]);
1759//
1760// Description:
1761// When called as a module, makes a copy of the children, mirrored across the given plane.
1762// When called as a function, *without* a `p=` argument, returns a list of transformation matrices, one for each copy.
1763// When called as a function, *with* a `p=` argument, returns a list of transformed copies of `p=`.
1764//
1765// Arguments:
1766// v = The normal vector of the plane to mirror across.
1767// offset = distance to offset away from the plane.
1768// cp = A point that lies on the mirroring plane.
1769// ---
1770// p = Either a point, pointlist, VNF or Bezier patch to be translated when used as a function.
1771//
1772// Side Effects:
1773// `$orig` is true for the original instance of children. False for the copy.
1774// `$idx` is set to the index value of each copy.
1775//
1776//
1777// Example:
1778// mirror_copy([1,-1,0]) zrot(-45) yrot(90) cylinder(d1=10, d2=0, h=20);
1779// color("blue",0.25) zrot(-45) cube([0.01,15,15], center=true);
1780//
1781// Example:
1782// mirror_copy([1,1,0], offset=5) rot(a=90,v=[-1,1,0]) cylinder(d1=10, d2=0, h=20);
1783// color("blue",0.25) zrot(45) cube([0.01,15,15], center=true);
1784//
1785// Example:
1786// mirror_copy(UP+BACK, cp=[0,-5,-5]) rot(from=UP, to=BACK+UP) cylinder(d1=10, d2=0, h=20);
1787// color("blue",0.25) translate([0,-5,-5]) rot(from=UP, to=BACK+UP) cube([15,15,0.01], center=true);
1788module mirror_copy(v=[0,0,1], offset=0, cp)
1789{
1790 req_children($children);
1791 cp = is_vector(v,4)? plane_normal(v) * v[3] :
1792 is_vector(cp)? cp :
1793 is_num(cp)? cp*unit(v) :
1794 [0,0,0];
1795 nv = is_vector(v,4)? plane_normal(v) : unit(v);
1796 off = nv*offset;
1797 if (cp == [0,0,0]) {
1798 translate(off) {
1799 $orig = true;
1800 $idx = 0;
1801 children();
1802 }
1803 mirror(nv) translate(off) {
1804 $orig = false;
1805 $idx = 1;
1806 children();
1807 }
1808 } else {
1809 translate(off) children();
1810 translate(cp) mirror(nv) translate(-cp) translate(off) children();
1811 }
1812}
1813
1814
1815function mirror_copy(v=[0,0,1], offset=0, cp, p=_NO_ARG) =
1816 let(
1817 cp = is_vector(v,4)? plane_normal(v) * v[3] :
1818 is_vector(cp)? cp :
1819 is_num(cp)? cp*unit(v) :
1820 [0,0,0],
1821 nv = is_vector(v,4)? plane_normal(v) : unit(v),
1822 off = nv*offset,
1823 mats = [
1824 translate(off),
1825 translate(cp) *
1826 mirror(nv) *
1827 translate(-cp) *
1828 translate(off)
1829 ]
1830 )
1831 p==_NO_ARG? mats : [for (m = mats) apply(m, p)];
1832
1833
1834
1835////////////////////
1836// Section: Distributing children individually along a line
1837///////////////////
1838// Module: xdistribute()
1839// Synopsis: Distributes each child, individually, out along the X axis.
1840// SynTags: Trans
1841// See Also: xdistribute(), ydistribute(), zdistribute(), distribute()
1842//
1843// Usage:
1844// xdistribute(spacing, [sizes]) CHILDREN;
1845// xdistribute(l=, [sizes=]) CHILDREN;
1846//
1847//
1848// Description:
1849// Spreads out the children individually along the X axis.
1850// Every child is placed at a different position, in order.
1851// This is useful for laying out groups of disparate objects
1852// where you only really care about the spacing between them.
1853//
1854// Arguments:
1855// spacing = spacing between each child. (Default: 10.0)
1856// sizes = Array containing how much space each child will need.
1857// l = Length to distribute copies along.
1858//
1859// Side Effects:
1860// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
1861// `$idx` is set to the index number of each child being copied.
1862//
1863// Example:
1864// xdistribute(sizes=[100, 10, 30], spacing=40) {
1865// sphere(r=50);
1866// cube([10,20,30], center=true);
1867// cylinder(d=30, h=50, center=true);
1868// }
1869module xdistribute(spacing=10, sizes=undef, l=undef)
1870{
1871 req_children($children);
1872 dir = RIGHT;
1873 gaps = ($children < 2)? [0] :
1874 !is_undef(sizes)? [for (i=[0:1:$children-2]) sizes[i]/2 + sizes[i+1]/2] :
1875 [for (i=[0:1:$children-2]) 0];
1876 spc = !is_undef(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10);
1877 gaps2 = [for (gap = gaps) gap+spc];
1878 spos = dir * -sum(gaps2)/2;
1879 spacings = cumsum([0, each gaps2]);
1880 for (i=[0:1:$children-1]) {
1881 $pos = spos + spacings[i] * dir;
1882 $idx = i;
1883 translate($pos) children(i);
1884 }
1885}
1886
1887
1888// Module: ydistribute()
1889// Synopsis: Distributes each child, individually, out along the Y axis.
1890// SynTags: Trans
1891// See Also: xdistribute(), ydistribute(), zdistribute(), distribute()
1892//
1893// Usage:
1894// ydistribute(spacing, [sizes]) CHILDREN;
1895// ydistribute(l=, [sizes=]) CHILDREN;
1896//
1897// Description:
1898// Spreads out the children individually along the Y axis.
1899// Every child is placed at a different position, in order.
1900// This is useful for laying out groups of disparate objects
1901// where you only really care about the spacing between them.
1902//
1903// Arguments:
1904// spacing = spacing between each child. (Default: 10.0)
1905// sizes = Array containing how much space each child will need.
1906// l = Length to distribute copies along.
1907//
1908// Side Effects:
1909// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
1910// `$idx` is set to the index number of each child being copied.
1911//
1912// Example:
1913// ydistribute(sizes=[30, 20, 100], spacing=40) {
1914// cylinder(d=30, h=50, center=true);
1915// cube([10,20,30], center=true);
1916// sphere(r=50);
1917// }
1918module ydistribute(spacing=10, sizes=undef, l=undef)
1919{
1920 req_children($children);
1921 dir = BACK;
1922 gaps = ($children < 2)? [0] :
1923 !is_undef(sizes)? [for (i=[0:1:$children-2]) sizes[i]/2 + sizes[i+1]/2] :
1924 [for (i=[0:1:$children-2]) 0];
1925 spc = !is_undef(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10);
1926 gaps2 = [for (gap = gaps) gap+spc];
1927 spos = dir * -sum(gaps2)/2;
1928 spacings = cumsum([0, each gaps2]);
1929 for (i=[0:1:$children-1]) {
1930 $pos = spos + spacings[i] * dir;
1931 $idx = i;
1932 translate($pos) children(i);
1933 }
1934}
1935
1936
1937// Module: zdistribute()
1938// Synopsis: Distributes each child, individually, out along the Z axis.
1939// SynTags: Trans
1940// See Also: xdistribute(), ydistribute(), zdistribute(), distribute()
1941//
1942// Usage:
1943// zdistribute(spacing, [sizes]) CHILDREN;
1944// zdistribute(l=, [sizes=]) CHILDREN;
1945//
1946// Description:
1947// Spreads out each individual child along the Z axis.
1948// Every child is placed at a different position, in order.
1949// This is useful for laying out groups of disparate objects
1950// where you only really care about the spacing between them.
1951//
1952// Arguments:
1953// spacing = spacing between each child. (Default: 10.0)
1954// sizes = Array containing how much space each child will need.
1955// l = Length to distribute copies along.
1956//
1957// Side Effects:
1958// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
1959// `$idx` is set to the index number of each child being copied.
1960//
1961// Example:
1962// zdistribute(sizes=[30, 20, 100], spacing=40) {
1963// cylinder(d=30, h=50, center=true);
1964// cube([10,20,30], center=true);
1965// sphere(r=50);
1966// }
1967module zdistribute(spacing=10, sizes=undef, l=undef)
1968{
1969 req_children($children);
1970 dir = UP;
1971 gaps = ($children < 2)? [0] :
1972 !is_undef(sizes)? [for (i=[0:1:$children-2]) sizes[i]/2 + sizes[i+1]/2] :
1973 [for (i=[0:1:$children-2]) 0];
1974 spc = !is_undef(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10);
1975 gaps2 = [for (gap = gaps) gap+spc];
1976 spos = dir * -sum(gaps2)/2;
1977 spacings = cumsum([0, each gaps2]);
1978 for (i=[0:1:$children-1]) {
1979 $pos = spos + spacings[i] * dir;
1980 $idx = i;
1981 translate($pos) children(i);
1982 }
1983}
1984
1985
1986
1987// Module: distribute()
1988// Synopsis: Distributes each child, individually, out along an arbitrary line.
1989// SynTags: Trans
1990// See Also: xdistribute(), ydistribute(), zdistribute(), distribute()
1991//
1992// Usage:
1993// distribute(spacing, sizes, dir) CHILDREN;
1994// distribute(l=, [sizes=], [dir=]) CHILDREN;
1995//
1996// Description:
1997// Spreads out the children individually along the direction `dir`.
1998// Every child is placed at a different position, in order.
1999// This is useful for laying out groups of disparate objects
2000// where you only really care about the spacing between them.
2001//
2002// Arguments:
2003// spacing = Spacing to add between each child. (Default: 10.0)
2004// sizes = Array containing how much space each child will need.
2005// dir = Vector direction to distribute copies along. Default: RIGHT
2006// l = Length to distribute copies along.
2007//
2008// Side Effects:
2009// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
2010// `$idx` is set to the index number of each child being copied.
2011//
2012// Example:
2013// distribute(sizes=[100, 30, 50], dir=UP) {
2014// sphere(r=50);
2015// cube([10,20,30], center=true);
2016// cylinder(d=30, h=50, center=true);
2017// }
2018module distribute(spacing=undef, sizes=undef, dir=RIGHT, l=undef)
2019{
2020 req_children($children);
2021 gaps = ($children < 2)? [0] :
2022 !is_undef(sizes)? [for (i=[0:1:$children-2]) sizes[i]/2 + sizes[i+1]/2] :
2023 [for (i=[0:1:$children-2]) 0];
2024 spc = !is_undef(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10);
2025 gaps2 = [for (gap = gaps) gap+spc];
2026 spos = dir * -sum(gaps2)/2;
2027 spacings = cumsum([0, each gaps2]);
2028 for (i=[0:1:$children-1]) {
2029 $pos = spos + spacings[i] * dir;
2030 $idx = i;
2031 translate($pos) children(i);
2032 }
2033}
2034
2035
2036
2037// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap