add proper error checking to [xyz]_copies and line_copies

also added doc section to distributors.scad about $ variables
This commit is contained in:
Adrian Mariano 2023-01-31 17:45:58 -05:00
parent 3426fa2df6
commit 0df14ad185

View file

@ -11,6 +11,52 @@
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// Section: Adaptive Children Using `$` Variables
// The distributor methods create multiple copies of their children and place them in various ways. While there are many occasions where
// a model demands multiple identical copies of an object, this framework is more powerful than
// might be immediately obvious because of `$` variables. The distributors set `$` variables that the children can use to change their
// behavior from one child to the next within a single distributor invocation. This means the copies need not be identical.
// The {{xcopies()}} module sets `$idx` to the index number
// of the copy. The first example shows how we can use that to produce **different** geometry at each index:
// Example(2D):
// xcopies(n=10, spacing=10)
// text(str($idx));
// Example(2D): Here the children are sometimes squares and sometimes circles as determined by the conditional statement.
// xcopies(n=4, spacing=10)
// if($idx%2==0) circle(r=3,$fn=16);
// else rect(6);
// Continues:
// Suppose we would like to color odd and even index copies differently. This example shows two important gotchas. First of all, the `if` statement
// in the previous example works for creating geometry, but don't be tempted to use an `if` statement to set variables like `if (condition) { c="red";}`
// because the variable is set only in the scope of the if statement and isn't available later on. Instead you must use the ternary operator as shown in the example.
// Example(2D):
// xcopies(n=6, spacing=10){
// let(c = $idx % 2 == 0 ? "red" : "green")
// color(c) rect(6);
// }
// Continues:
// The second complication is that in OpenSCAD version 2021.01 and earlier, assignments in children were executed before their
// parent. This means that `$idx` isn't available in assignments, so you will get a warning about an unknown variable.
// Two workarounds exist, neither of which are needed in newer versions of OpenSCAD. The workarounds solve the problem because
// **modules** execute after their parent, so the `$` variables **are** available in modules. In the example above, `let()` is a module
// that sets variables available to its children. Note that multiple assignments in `let()` are separated by commas, not semicolons.
// The other workaround is to wrap your child in a `union()`. The next 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()`.
// Example(2D):
// xcopies(n=5,spacing=10)
// union()
// {
// shiftback = $idx%2==0 ? back(5) : IDENT;
// spin = zrot(180*$idx/4);
// multmatrix(shiftback*spin) stroke([[-4,0],[4,0]],endcap2="arrow2",width=1/2);
// }
// Continues:
// In these examples we used `$idx`, but the various distributors offer a variety of `$` variables that you can use in your
// children. Check the "Side Effects" section for each module to learn what variables that module provides.
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// Section: Translating copies of all the children // Section: Translating copies of all the children
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
@ -96,7 +142,7 @@ function move_copies(a=[[0,0,0]],p=_NO_ARG) =
// 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) // 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)
// n = Number of copies to place. (Default: 2) // n = Number of copies to place. (Default: 2)
// l = Length to place copies over. // l = Length to place copies over.
// 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]. // 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].
// p = Either a point, pointlist, VNF or Bezier patch to be translated when used as a function. // p = Either a point, pointlist, VNF or Bezier patch to be translated when used as a function.
// //
// Side Effects: // Side Effects:
@ -119,6 +165,8 @@ function move_copies(a=[[0,0,0]],p=_NO_ARG) =
// xcopies([1,2,3,5,7]) sphere(d=1); // xcopies([1,2,3,5,7]) sphere(d=1);
module xcopies(spacing, n, l, sp) module xcopies(spacing, n, l, sp)
{ {
assert(is_undef(n) || num_defined([l,spacing])==1, "When n is given must give exactly one of spacing or l")
assert(is_def(n) || num_defined([l,spacing])>=1, "When n is not given must give at least one of spacing or l")
req_children($children); req_children($children);
dir = RIGHT; dir = RIGHT;
sp = is_finite(sp)? (sp*dir) : sp; sp = is_finite(sp)? (sp*dir) : sp;
@ -141,6 +189,8 @@ module xcopies(spacing, n, l, sp)
function xcopies(spacing, n, l, sp, p=_NO_ARG) = function xcopies(spacing, n, l, sp, p=_NO_ARG) =
assert(is_undef(n) || num_defined([l,spacing])==1, "When n is given must give exactly one of spacing or l")
assert(is_def(n) || num_defined([l,spacing])>=1, "When n is not given must give at least one of spacing or l")
let( let(
dir = RIGHT, dir = RIGHT,
sp = is_finite(sp)? (sp*dir) : sp, sp = is_finite(sp)? (sp*dir) : sp,
@ -201,6 +251,8 @@ function xcopies(spacing, n, l, sp, p=_NO_ARG) =
// ycopies([1,2,3,5,7]) sphere(d=1); // ycopies([1,2,3,5,7]) sphere(d=1);
module ycopies(spacing, n, l, sp) module ycopies(spacing, n, l, sp)
{ {
assert(is_undef(n) || num_defined([l,spacing])==1, "When n is given must give exactly one of spacing or l")
assert(is_def(n) || num_defined([l,spacing])>=1, "When n is not given must give at least one of spacing or l")
req_children($children); req_children($children);
dir = BACK; dir = BACK;
sp = is_finite(sp)? (sp*dir) : sp; sp = is_finite(sp)? (sp*dir) : sp;
@ -223,6 +275,8 @@ module ycopies(spacing, n, l, sp)
function ycopies(spacing, n, l, sp, p=_NO_ARG) = function ycopies(spacing, n, l, sp, p=_NO_ARG) =
assert(is_undef(n) || num_defined([l,spacing])==1, "When n is given must give exactly one of spacing or l")
assert(is_def(n) || num_defined([l,spacing])>=1, "When n is not given must give at least one of spacing or l")
let( let(
dir = BACK, dir = BACK,
sp = is_finite(sp)? (sp*dir) : sp, sp = is_finite(sp)? (sp*dir) : sp,
@ -297,6 +351,8 @@ function ycopies(spacing, n, l, sp, p=_NO_ARG) =
// zcopies([1,2,3,5,7]) sphere(d=1); // zcopies([1,2,3,5,7]) sphere(d=1);
module zcopies(spacing, n, l, sp) module zcopies(spacing, n, l, sp)
{ {
assert(is_undef(n) || num_defined([l,spacing])==1, "When n is given must give exactly one of spacing or l")
assert(is_def(n) || num_defined([l,spacing])>=1, "When n is not given must give at least one of spacing or l")
req_children($children); req_children($children);
dir = UP; dir = UP;
sp = is_finite(sp)? (sp*dir) : sp; sp = is_finite(sp)? (sp*dir) : sp;
@ -319,6 +375,8 @@ module zcopies(spacing, n, l, sp)
function zcopies(spacing, n, l, sp, p=_NO_ARG) = function zcopies(spacing, n, l, sp, p=_NO_ARG) =
assert(is_undef(n) || num_defined([l,spacing])==1, "When n is given must give exactly one of spacing or l")
assert(is_def(n) || num_defined([l,spacing])>=1, "When n is not given must give at least one of spacing or l")
let( let(
dir = UP, dir = UP,
sp = is_finite(sp)? (sp*dir) : sp, sp = is_finite(sp)? (sp*dir) : sp,
@ -441,18 +499,23 @@ function line_copies(spacing, n, l, p1, p2, p=_NO_ARG) =
assert(is_undef(l) || is_finite(l) || is_vector(l)) assert(is_undef(l) || is_finite(l) || is_vector(l))
assert(is_undef(p1) || is_vector(p1)) assert(is_undef(p1) || is_vector(p1))
assert(is_undef(p2) || is_vector(p2)) assert(is_undef(p2) || is_vector(p2))
assert(is_undef(p2) || is_def(p1), "If p2 is given must also give p1")
assert(is_undef(p2) || is_undef(l), "Cannot give both p2 and l")
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")
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")
let( let(
ll = !is_undef(l)? scalar_vec3(l, 0) : ll = is_def(l)? scalar_vec3(l, 0)
(!is_undef(spacing) && !is_undef(n))? ((n-1) * scalar_vec3(spacing, 0)) : : is_def(spacing) && is_def(n)? (n-1) * scalar_vec3(spacing, 0)
(!is_undef(p1) && !is_undef(p2))? point3d(p2-p1) : : is_def(p1) && is_def(p2)? point3d(p2-p1)
undef, : undef,
cnt = !is_undef(n)? n : cnt = is_def(n)? n
(!is_undef(spacing) && !is_undef(ll))? floor(norm(ll) / norm(scalar_vec3(spacing, 0)) + 1.000001) : : is_def(spacing) && is_def(ll) ? floor(norm(ll) / norm(scalar_vec3(spacing, 0)) + 1.000001)
2, : 2,
spc = cnt<=1? [0,0,0] : spc = cnt<=1? [0,0,0]
is_undef(spacing)? (ll/(cnt-1)) : : is_undef(spacing) && is_def(ll)? ll/(cnt-1)
is_num(spacing) && !is_undef(ll)? (ll/(cnt-1)) : : is_num(spacing) && is_def(ll)? (ll/(cnt-1))
scalar_vec3(spacing, 0) : scalar_vec3(spacing, 0),
afd=echo(spc=spc)
) )
assert(!is_undef(cnt), "Need two of `spacing`, 'l', 'n', or `p1`/`p2` arguments in `line_copies()`.") assert(!is_undef(cnt), "Need two of `spacing`, 'l', 'n', or `p1`/`p2` arguments in `line_copies()`.")
let( spos = !is_undef(p1)? point3d(p1) : -(cnt-1)/2 * spc ) let( spos = !is_undef(p1)? point3d(p1) : -(cnt-1)/2 * spc )