discuss@lists.openscad.org

OpenSCAD general discussion Mailing-list

View all threads

A simple approach to creating fillets

AM
Adrian Mariano
Wed, Feb 15, 2023 9:38 PM

I realized that I overlooked a way to do this with a module.  The problem
was I tried passing children() instead of passing the children
individually, which you can do in this case because you know how many there
are.  That gives this slightly streamlined code:

// Invoke with 2 children that are at unit scale and create a
// fillet joint between them, while applying specified transformations.
//
// r = radius of fillet (relative to scale of children)
// T0,R0,S0 = translation, rotation and scaling for object 0
// T1,R1,S1 = translation, rotation and scaling for object 1
// steps = number of steps in fillet
// chamfer = set true to get chamfer instead of rounding

module fillet_slice(r, a, T0,R0,S0, T1,R1,S1, chamfer=false)
{
offset = chamfer ? r * [a,1-a]
: r * [1-cos(90a), 1-sin(90a)];
scale0 = [for(s=S0) offset[0]+s];
scale1 = [for(s=S1) offset[1]+s];
intersection(){
translate(T0)rotate(R0)scale(scale0) children(0);
translate(T1)rotate(R1)scale(scale1) children(1);
}
}

module fillet_join(r, T0,R0,S0, T1,R1,S1, steps=10, chamfer=false)
{
assert($children==2);
da=1/steps;
for(a=[0:da:1-da/2]){
hull() {
fillet_slice(r,a,T0,R0,S0,T1,R1,S1,chamfer){
children(0);
children(1);
}
fillet_slice(r,a+da,T0,R0,S0,T1,R1,S1,chamfer){
children(0);
children(1);
}
}
}
translate(T0)rotate(R0)scale(S0) children(0);
translate(T1)rotate(R1)scale(S1) children(1);
}

fillet_join(3,[0,0,3], [0,90,-10], [22,22,100],
[0,6,6], [0,20,0], [15,20,28],chamfer=false)
{
cylinder(d=1,center=true,$fn=64);
cube(center=true);
}

On Wed, Feb 15, 2023 at 3:20 AM Jordan Brown openscad@jordan.maileater.net
wrote:

On 2/14/2023 6:31 PM, Daniel Harvey wrote:

It is a pity that OpenSCAD doesn't allow you to pass one module definition
as an argument to another, as Python does - and I was grasping for.

Excuse me for a moment while I get pedantic.  People often get confused
about what "module" means, and especially with the upcoming work in
PR#4478 https://github.com/openscad/openscad/pull/4478 it's important
to keep the concepts straight.

Modules are executable subprograms.  They might happen to always produce
the same output, but they may not.  "cube" is a module - sometimes it
produces 1x1x1 cubes, sometimes 10x10x10, and sometimes 9x4x1.

Geometric objects (still looking for a good phrase there) are the
results of executing modules.  Once you have executed cube(1), the result
is a 1x1 cube (in the current coordinate system) and nothing will ever
change that.

By way of analogy, consider this function:

function five() = 5;

This function will always return the number 5, but it is not the number 5

  • it is an executable subprogram that happens to always yield the same
    result.

Today's OpenSCAD does have one form for passing something in this area -
the "child" mechanism:

rotate(45) cube(10);

That looks like it's passing a geometric object to rotate, but really it's
not - it's passing something that's more like a parameter-less module.
This is most easily seen with modules that invoke their children more than
once:

module dup(dx) {
children();
translate([dx,0,0]) children();
}

If you invoke this with a simple child, it could be working on geometric
objects:

dup(5) cube(2);

does the obvious thing.

But what do these do?

dup(5) echo("hello");
dup(5) cube(rands(1,3,1)[0]);

The "passing a geometric object" model would say that the first would echo
"hello", while the second would generate a random-sized cube, and then
duplicate it... but that's not what happens.  Instead, each invocation of
"children()" causes the children to be evaluated, so what you get are two
echos of "hello" and two independently random cubes.

The same is true if you set $ variables in the parent module; the child
can receive them and take action based on them.

The children may not be evaluated at all...

module maybe(bool) {
if (bool) children();
}

maybe(false) echo("you never see this");


4478 includes several mechanisms offering more features for both kinds of
constructs, and it becomes even more important to keep them straight.

4478 lets you use geometric objects as data values:

c = {{ cube(rands(1,3,1)[0]); }}      // Maybe not final syntax.

and then because it's just a data value, you can put it in an array, pass
it as a parameter, return it from a function, and so on.

You can later add it to the model:

c;

Note that that geometric value gets evaluated once, so "c" represents
some one random-sized cube.  No matter how many times you add it to the
model, it's always the same size.  (In the current coordinate system, of
course.)

4478 also lets you use modules as data values (as you can with functions
today):

m = module () { cube(rands(1,3,1)[0]); };

Again, this "module reference" is a data value.  You can put it in an
array, pass it as a parameter, et cetera.  At some later point you can
invoke it and add the resulting geometry to the model:

m();

But it's a module.  Until you invoke it, it isn't geometry.  Each time
you invoke it, it gets evaluated and produces whatever geometry it
produces.  Here, each time you invoke it you get a random-sized cube, and
two invocations will yield different results.

I hope that makes the distinction a little clearer, and makes it clear why
I want people to understand the distinction.


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org

I realized that I overlooked a way to do this with a module. The problem was I tried passing children() instead of passing the children individually, which you can do in this case because you know how many there are. That gives this slightly streamlined code: // Invoke with 2 children that are at unit scale and create a // fillet joint between them, while applying specified transformations. // // r = radius of fillet (relative to scale of children) // T0,R0,S0 = translation, rotation and scaling for object 0 // T1,R1,S1 = translation, rotation and scaling for object 1 // steps = number of steps in fillet // chamfer = set true to get chamfer instead of rounding module fillet_slice(r, a, T0,R0,S0, T1,R1,S1, chamfer=false) { offset = chamfer ? r * [a,1-a] : r * [1-cos(90*a), 1-sin(90*a)]; scale0 = [for(s=S0) offset[0]+s]; scale1 = [for(s=S1) offset[1]+s]; intersection(){ translate(T0)rotate(R0)scale(scale0) children(0); translate(T1)rotate(R1)scale(scale1) children(1); } } module fillet_join(r, T0,R0,S0, T1,R1,S1, steps=10, chamfer=false) { assert($children==2); da=1/steps; for(a=[0:da:1-da/2]){ hull() { fillet_slice(r,a,T0,R0,S0,T1,R1,S1,chamfer){ children(0); children(1); } fillet_slice(r,a+da,T0,R0,S0,T1,R1,S1,chamfer){ children(0); children(1); } } } translate(T0)rotate(R0)scale(S0) children(0); translate(T1)rotate(R1)scale(S1) children(1); } fillet_join(3,[0,0,3], [0,90,-10], [22,22,100], [0,6,6], [0,20,0], [15,20,28],chamfer=false) { cylinder(d=1,center=true,$fn=64); cube(center=true); } On Wed, Feb 15, 2023 at 3:20 AM Jordan Brown <openscad@jordan.maileater.net> wrote: > On 2/14/2023 6:31 PM, Daniel Harvey wrote: > > It is a pity that OpenSCAD doesn't allow you to pass one module definition > as an argument to another, as Python does - and I was grasping for. > > > Excuse me for a moment while I get pedantic. People often get confused > about what "module" means, and especially with the upcoming work in > PR#4478 <https://github.com/openscad/openscad/pull/4478> it's important > to keep the concepts straight. > > Modules are executable subprograms. They might happen to always produce > the same output, but they may not. "cube" is a module - sometimes it > produces 1x1x1 cubes, sometimes 10x10x10, and sometimes 9x4x1. > > Geometric objects (still looking for a good phrase there) are the > *results* of executing modules. Once you have executed cube(1), the result > is a 1x1 cube (in the current coordinate system) and nothing will ever > change that. > > By way of analogy, consider this function: > > function five() = 5; > > This function will always return the number 5, but it is not the number 5 > - it is an executable subprogram that happens to always yield the same > result. > > ------------------------------ > > Today's OpenSCAD does have one form for passing something in this area - > the "child" mechanism: > > rotate(45) cube(10); > > That looks like it's passing a geometric object to rotate, but really it's > not - it's passing something that's more like a parameter-less module. > This is most easily seen with modules that invoke their children more than > once: > > module dup(dx) { > children(); > translate([dx,0,0]) children(); > } > > If you invoke this with a simple child, it could be working on geometric > objects: > > dup(5) cube(2); > > does the obvious thing. > > But what do these do? > > dup(5) echo("hello"); > dup(5) cube(rands(1,3,1)[0]); > > The "passing a geometric object" model would say that the first would echo > "hello", while the second would generate a random-sized cube, and then > duplicate it... but that's not what happens. Instead, each invocation of > "children()" causes the children to be evaluated, so what you get are two > echos of "hello" and two independently random cubes. > > The same is true if you set $ variables in the parent module; the child > can receive them and take action based on them. > > The children may not be evaluated at all... > > module maybe(bool) { > if (bool) children(); > } > > maybe(false) echo("you never see this"); > > > ------------------------------ > > 4478 includes several mechanisms offering more features for both kinds of > constructs, and it becomes even more important to keep them straight. > > 4478 lets you use geometric objects as data values: > > c = {{ cube(rands(1,3,1)[0]); }} // Maybe not final syntax. > > and then because it's just a data value, you can put it in an array, pass > it as a parameter, return it from a function, and so on. > > You can later add it to the model: > > c; > > Note that that geometric value gets evaluated *once*, so "c" represents > some one random-sized cube. No matter how many times you add it to the > model, it's always the same size. (In the current coordinate system, of > course.) > > 4478 also lets you use modules as data values (as you can with functions > today): > > m = module () { cube(rands(1,3,1)[0]); }; > > Again, this "module reference" is a data value. You can put it in an > array, pass it as a parameter, et cetera. At some later point you can > invoke it and add the resulting geometry to the model: > > m(); > > But it's a module. Until you invoke it, it isn't geometry. *Each* time > you invoke it, it gets evaluated and produces whatever geometry it > produces. Here, each time you invoke it you get a random-sized cube, and > two invocations will yield different results. > > > I hope that makes the distinction a little clearer, and makes it clear why > I want people to understand the distinction. > > > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email to discuss-leave@lists.openscad.org >
J
jon
Wed, Feb 15, 2023 9:50 PM

I am enjoying this discussion, but have not followed the details.  The
code, below, takes objects from their initial position and uses the
modules to position and rotate them prior to filleting.  Is it the case
that one cannot position and rotate the objects ahead of time and then
have the modules still function properly?

Jon

On 2/15/2023 4:38 PM, Adrian Mariano wrote:

I realized that I overlooked a way to do this with a module.  The
problem was I tried passing children() instead of passing the children
individually, which you can do in this case because you know how many
there are.  That gives this slightly streamlined code:

// Invoke with 2 children that are at unit scale and create a
// fillet joint between them, while applying specified transformations.
//
// r = radius of fillet (relative to scale of children)
// T0,R0,S0 = translation, rotation and scaling for object 0
// T1,R1,S1 = translation, rotation and scaling for object 1
// steps = number of steps in fillet
// chamfer = set true to get chamfer instead of rounding

module fillet_slice(r, a, T0,R0,S0, T1,R1,S1, chamfer=false)
{
    offset = chamfer ? r * [a,1-a]
                           : r * [1-cos(90a), 1-sin(90a)];
    scale0 = [for(s=S0) offset[0]+s];
    scale1 = [for(s=S1) offset[1]+s];
    intersection(){
      translate(T0)rotate(R0)scale(scale0) children(0);
      translate(T1)rotate(R1)scale(scale1) children(1);
    }
}

module fillet_join(r, T0,R0,S0, T1,R1,S1, steps=10, chamfer=false)
{
  assert($children==2);
  da=1/steps;
  for(a=[0:da:1-da/2]){
      hull() {
          fillet_slice(r,a,T0,R0,S0,T1,R1,S1,chamfer){
             children(0);
             children(1);
          }
          fillet_slice(r,a+da,T0,R0,S0,T1,R1,S1,chamfer){
             children(0);
             children(1);
          }
      }
  }
  translate(T0)rotate(R0)scale(S0) children(0);
  translate(T1)rotate(R1)scale(S1) children(1);
}

fillet_join(3,[0,0,3], [0,90,-10], [22,22,100],
              [0,6,6], [0,20,0], [15,20,28],chamfer=false)
{
  cylinder(d=1,center=true,$fn=64);
  cube(center=true);
}

I am enjoying this discussion, but have not followed the details.  The code, below, takes objects from their initial position and uses the modules to position and rotate them prior to filleting.  Is it the case that one cannot position and rotate the objects ahead of time and then have the modules still function properly? Jon On 2/15/2023 4:38 PM, Adrian Mariano wrote: > I realized that I overlooked a way to do this with a module.  The > problem was I tried passing children() instead of passing the children > individually, which you can do in this case because you know how many > there are.  That gives this slightly streamlined code: > > // Invoke with 2 children that are at unit scale and create a > // fillet joint between them, while applying specified transformations. > // > // r = radius of fillet (relative to scale of children) > // T0,R0,S0 = translation, rotation and scaling for object 0 > // T1,R1,S1 = translation, rotation and scaling for object 1 > // steps = number of steps in fillet > // chamfer = set true to get chamfer instead of rounding > > module fillet_slice(r, a, T0,R0,S0, T1,R1,S1, chamfer=false) > { >     offset = chamfer ? r * [a,1-a] >                            : r * [1-cos(90*a), 1-sin(90*a)]; >     scale0 = [for(s=S0) offset[0]+s]; >     scale1 = [for(s=S1) offset[1]+s]; >     intersection(){ >       translate(T0)rotate(R0)scale(scale0) children(0); >       translate(T1)rotate(R1)scale(scale1) children(1); >     } > } > > module fillet_join(r, T0,R0,S0, T1,R1,S1, steps=10, chamfer=false) > { >   assert($children==2); >   da=1/steps; >   for(a=[0:da:1-da/2]){ >       hull() { >           fillet_slice(r,a,T0,R0,S0,T1,R1,S1,chamfer){ >              children(0); >              children(1); >           } >           fillet_slice(r,a+da,T0,R0,S0,T1,R1,S1,chamfer){ >              children(0); >              children(1); >           } >       } >   } >   translate(T0)rotate(R0)scale(S0) children(0); >   translate(T1)rotate(R1)scale(S1) children(1); > } > > > fillet_join(3,[0,0,3], [0,90,-10], [22,22,100], >               [0,6,6], [0,20,0], [15,20,28],chamfer=false) > { >   cylinder(d=1,center=true,$fn=64); >   cube(center=true); > } >
J
jon
Wed, Feb 15, 2023 9:53 PM

Jordan:

Very helpful.  Thank you for taking the time to write this.

Jon

On 2/15/2023 3:19 AM, Jordan Brown wrote:

On 2/14/2023 6:31 PM, Daniel Harvey wrote:

It is a pity that OpenSCAD doesn't allow you to pass one module
definition as an argument to another, as Python does - and I was
grasping for.

Excuse me for a moment while I get pedantic.  People often get
confused about what "module" means, and especially with the upcoming
work in PR#4478 https://github.com/openscad/openscad/pull/4478 it's
important to keep the concepts straight.

Modules are executable subprograms.  They might happen to always
produce the same output, but they may not.  "cube" is a module -
sometimes it produces 1x1x1 cubes, sometimes 10x10x10, and sometimes
9x4x1.

Geometric objects (still looking for a good phrase there) are the
results of executing modules.  Once you have executed cube(1), the
result is a 1x1 cube (in the current coordinate system) and nothing
will ever change that.

By way of analogy, consider this function:

 function five() = 5;

This function will always return the number 5, but it is not the
number 5 - it is an executable subprogram that happens to always yield
the same result.


Today's OpenSCAD does have one form for passing something in this area

  • the "child" mechanism:

    rotate(45) cube(10);

That looks like it's passing a geometric object to rotate, but really
it's not - it's passing something that's more like a parameter-less
module.  This is most easily seen with modules that invoke their
children more than once:

 module dup(dx) {
      children();
      translate([dx,0,0]) children();
 }

If you invoke this with a simple child, it could be working on
geometric objects:

 dup(5) cube(2);

does the obvious thing.

But what do these do?

 dup(5) echo("hello");
 dup(5) cube(rands(1,3,1)[0]);

The "passing a geometric object" model would say that the first would
echo "hello", while the second would generate a random-sized cube, and
then duplicate it... but that's not what happens.  Instead, each
invocation of "children()" causes the children to be evaluated, so
what you get are two echos of "hello" and two independently random cubes.

The same is true if you set $ variables in the parent module; the
child can receive them and take action based on them.

The children may not be evaluated at all...

 module maybe(bool) {
      if (bool) children();
 }

 maybe(false) echo("you never see this");

4478 includes several mechanisms offering more features for both kinds
of constructs, and it becomes even more important to keep them straight.

4478 lets you use geometric objects as data values:

 c = {{ cube(rands(1,3,1)[0]); }}      // Maybe not final syntax.

and then because it's just a data value, you can put it in an array,
pass it as a parameter, return it from a function, and so on.

You can later add it to the model:

 c;

Note that that geometric value gets evaluated once, so "c"
represents some one random-sized cube.  No matter how many times you
add it to the model, it's always the same size.  (In the current
coordinate system, of course.)

4478 also lets you use modules as data values (as you can with
functions today):

 m = module () { cube(rands(1,3,1)[0]); };

Again, this "module reference" is a data value.  You can put it in an
array, pass it as a parameter, et cetera.  At some later point you can
invoke it and add the resulting geometry to the model:

 m();

But it's a module.  Until you invoke it, it isn't geometry. Each
time you invoke it, it gets evaluated and produces whatever geometry
it produces.  Here, each time you invoke it you get a random-sized
cube, and two invocations will yield different results.

I hope that makes the distinction a little clearer, and makes it clear
why I want people to understand the distinction.


OpenSCAD mailing list
To unsubscribe send an email todiscuss-leave@lists.openscad.org

Jordan: Very helpful.  Thank you for taking the time to write this. Jon On 2/15/2023 3:19 AM, Jordan Brown wrote: > On 2/14/2023 6:31 PM, Daniel Harvey wrote: >> It is a pity that OpenSCAD doesn't allow you to pass one module >> definition as an argument to another, as Python does - and I was >> grasping for. > > Excuse me for a moment while I get pedantic.  People often get > confused about what "module" means, and especially with the upcoming > work in PR#4478 <https://github.com/openscad/openscad/pull/4478> it's > important to keep the concepts straight. > > Modules are executable subprograms.  They might happen to always > produce the same output, but they may not.  "cube" is a module - > sometimes it produces 1x1x1 cubes, sometimes 10x10x10, and sometimes > 9x4x1. > > Geometric objects (still looking for a good phrase there) are the > *results* of executing modules.  Once you have executed cube(1), the > result is a 1x1 cube (in the current coordinate system) and nothing > will ever change that. > > By way of analogy, consider this function: > > function five() = 5; > > This function will always return the number 5, but it is not the > number 5 - it is an executable subprogram that happens to always yield > the same result. > > ------------------------------------------------------------------------ > > Today's OpenSCAD does have one form for passing something in this area > - the "child" mechanism: > > rotate(45) cube(10); > > That looks like it's passing a geometric object to rotate, but really > it's not - it's passing something that's more like a parameter-less > module.  This is most easily seen with modules that invoke their > children more than once: > > module dup(dx) { > children(); > translate([dx,0,0]) children(); > } > > If you invoke this with a simple child, it could be working on > geometric objects: > > dup(5) cube(2); > > does the obvious thing. > > But what do these do? > > dup(5) echo("hello"); > dup(5) cube(rands(1,3,1)[0]); > > The "passing a geometric object" model would say that the first would > echo "hello", while the second would generate a random-sized cube, and > then duplicate it... but that's not what happens.  Instead, each > invocation of "children()" causes the children to be evaluated, so > what you get are two echos of "hello" and two independently random cubes. > > The same is true if you set $ variables in the parent module; the > child can receive them and take action based on them. > > The children may not be evaluated at all... > > module maybe(bool) { > if (bool) children(); > } > > maybe(false) echo("you never see this"); > > > ------------------------------------------------------------------------ > > 4478 includes several mechanisms offering more features for both kinds > of constructs, and it becomes even more important to keep them straight. > > 4478 lets you use geometric objects as data values: > > c = {{ cube(rands(1,3,1)[0]); }} // Maybe not final syntax. > > and then because it's just a data value, you can put it in an array, > pass it as a parameter, return it from a function, and so on. > > You can later add it to the model: > > c; > > Note that that geometric value gets evaluated *once*, so "c" > represents some one random-sized cube.  No matter how many times you > add it to the model, it's always the same size.  (In the current > coordinate system, of course.) > > 4478 also lets you use modules as data values (as you can with > functions today): > > m = module () { cube(rands(1,3,1)[0]); }; > > Again, this "module reference" is a data value.  You can put it in an > array, pass it as a parameter, et cetera.  At some later point you can > invoke it and add the resulting geometry to the model: > > m(); > > But it's a module.  Until you invoke it, it isn't geometry. *Each* > time you invoke it, it gets evaluated and produces whatever geometry > it produces.  Here, each time you invoke it you get a random-sized > cube, and two invocations will yield different results. > > > I hope that makes the distinction a little clearer, and makes it clear > why I want people to understand the distinction. > > > > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email todiscuss-leave@lists.openscad.org
DH
Daniel Harvey
Wed, Feb 15, 2023 10:40 PM

Jordan,
thanks for taking the time to explain all of that. I think what I was
hoping for would be the possibility to pass something like:

module fillet(object_module1, pars1, object_module2, pars2, ...) {
intersection() {
object_module1(pars1);
object_module2(pars2);
}
}

in the way one can pass a method/function in python, that you can then call
from within another function/method. In other words, in this context, to be
able to pass the modules and parameters separately, rather than the
geometry they create (as you can now via children), or the combined module
and parameters as you are saying will be possible in 4478.
Either way, syntax aside, I was happy to find a clean / simple way to
achieve my goal.

On Wed, Feb 15, 2023 at 1:19 AM Jordan Brown openscad@jordan.maileater.net
wrote:

On 2/14/2023 6:31 PM, Daniel Harvey wrote:

It is a pity that OpenSCAD doesn't allow you to pass one module definition
as an argument to another, as Python does - and I was grasping for.

Excuse me for a moment while I get pedantic.  People often get confused
about what "module" means, and especially with the upcoming work in
PR#4478 https://github.com/openscad/openscad/pull/4478 it's important
to keep the concepts straight.

Modules are executable subprograms.  They might happen to always produce
the same output, but they may not.  "cube" is a module - sometimes it
produces 1x1x1 cubes, sometimes 10x10x10, and sometimes 9x4x1.

Geometric objects (still looking for a good phrase there) are the
results of executing modules.  Once you have executed cube(1), the result
is a 1x1 cube (in the current coordinate system) and nothing will ever
change that.

By way of analogy, consider this function:

function five() = 5;

This function will always return the number 5, but it is not the number 5

  • it is an executable subprogram that happens to always yield the same
    result.

Today's OpenSCAD does have one form for passing something in this area -
the "child" mechanism:

rotate(45) cube(10);

That looks like it's passing a geometric object to rotate, but really it's
not - it's passing something that's more like a parameter-less module.
This is most easily seen with modules that invoke their children more than
once:

module dup(dx) {
children();
translate([dx,0,0]) children();
}

If you invoke this with a simple child, it could be working on geometric
objects:

dup(5) cube(2);

does the obvious thing.

But what do these do?

dup(5) echo("hello");
dup(5) cube(rands(1,3,1)[0]);

The "passing a geometric object" model would say that the first would echo
"hello", while the second would generate a random-sized cube, and then
duplicate it... but that's not what happens.  Instead, each invocation of
"children()" causes the children to be evaluated, so what you get are two
echos of "hello" and two independently random cubes.

The same is true if you set $ variables in the parent module; the child
can receive them and take action based on them.

The children may not be evaluated at all...

module maybe(bool) {
if (bool) children();
}

maybe(false) echo("you never see this");


4478 includes several mechanisms offering more features for both kinds of
constructs, and it becomes even more important to keep them straight.

4478 lets you use geometric objects as data values:

c = {{ cube(rands(1,3,1)[0]); }}      // Maybe not final syntax.

and then because it's just a data value, you can put it in an array, pass
it as a parameter, return it from a function, and so on.

You can later add it to the model:

c;

Note that that geometric value gets evaluated once, so "c" represents
some one random-sized cube.  No matter how many times you add it to the
model, it's always the same size.  (In the current coordinate system, of
course.)

4478 also lets you use modules as data values (as you can with functions
today):

m = module () { cube(rands(1,3,1)[0]); };

Again, this "module reference" is a data value.  You can put it in an
array, pass it as a parameter, et cetera.  At some later point you can
invoke it and add the resulting geometry to the model:

m();

But it's a module.  Until you invoke it, it isn't geometry.  Each time
you invoke it, it gets evaluated and produces whatever geometry it
produces.  Here, each time you invoke it you get a random-sized cube, and
two invocations will yield different results.

I hope that makes the distinction a little clearer, and makes it clear why
I want people to understand the distinction.

Jordan, thanks for taking the time to explain all of that. I think what I was hoping for would be the possibility to pass something like: module fillet(object_module1, pars1, object_module2, pars2, ...) { intersection() { object_module1(pars1); object_module2(pars2); } } in the way one can pass a method/function in python, that you can then call from within another function/method. In other words, in this context, to be able to pass the modules and parameters separately, rather than the geometry they create (as you can now via children), or the combined module and parameters as you are saying will be possible in 4478. Either way, syntax aside, I was happy to find a clean / simple way to achieve my goal. On Wed, Feb 15, 2023 at 1:19 AM Jordan Brown <openscad@jordan.maileater.net> wrote: > On 2/14/2023 6:31 PM, Daniel Harvey wrote: > > It is a pity that OpenSCAD doesn't allow you to pass one module definition > as an argument to another, as Python does - and I was grasping for. > > > Excuse me for a moment while I get pedantic. People often get confused > about what "module" means, and especially with the upcoming work in > PR#4478 <https://github.com/openscad/openscad/pull/4478> it's important > to keep the concepts straight. > > Modules are executable subprograms. They might happen to always produce > the same output, but they may not. "cube" is a module - sometimes it > produces 1x1x1 cubes, sometimes 10x10x10, and sometimes 9x4x1. > > Geometric objects (still looking for a good phrase there) are the > *results* of executing modules. Once you have executed cube(1), the result > is a 1x1 cube (in the current coordinate system) and nothing will ever > change that. > > By way of analogy, consider this function: > > function five() = 5; > > This function will always return the number 5, but it is not the number 5 > - it is an executable subprogram that happens to always yield the same > result. > > ------------------------------ > > Today's OpenSCAD does have one form for passing something in this area - > the "child" mechanism: > > rotate(45) cube(10); > > That looks like it's passing a geometric object to rotate, but really it's > not - it's passing something that's more like a parameter-less module. > This is most easily seen with modules that invoke their children more than > once: > > module dup(dx) { > children(); > translate([dx,0,0]) children(); > } > > If you invoke this with a simple child, it could be working on geometric > objects: > > dup(5) cube(2); > > does the obvious thing. > > But what do these do? > > dup(5) echo("hello"); > dup(5) cube(rands(1,3,1)[0]); > > The "passing a geometric object" model would say that the first would echo > "hello", while the second would generate a random-sized cube, and then > duplicate it... but that's not what happens. Instead, each invocation of > "children()" causes the children to be evaluated, so what you get are two > echos of "hello" and two independently random cubes. > > The same is true if you set $ variables in the parent module; the child > can receive them and take action based on them. > > The children may not be evaluated at all... > > module maybe(bool) { > if (bool) children(); > } > > maybe(false) echo("you never see this"); > > > ------------------------------ > > 4478 includes several mechanisms offering more features for both kinds of > constructs, and it becomes even more important to keep them straight. > > 4478 lets you use geometric objects as data values: > > c = {{ cube(rands(1,3,1)[0]); }} // Maybe not final syntax. > > and then because it's just a data value, you can put it in an array, pass > it as a parameter, return it from a function, and so on. > > You can later add it to the model: > > c; > > Note that that geometric value gets evaluated *once*, so "c" represents > some one random-sized cube. No matter how many times you add it to the > model, it's always the same size. (In the current coordinate system, of > course.) > > 4478 also lets you use modules as data values (as you can with functions > today): > > m = module () { cube(rands(1,3,1)[0]); }; > > Again, this "module reference" is a data value. You can put it in an > array, pass it as a parameter, et cetera. At some later point you can > invoke it and add the resulting geometry to the model: > > m(); > > But it's a module. Until you invoke it, it isn't geometry. *Each* time > you invoke it, it gets evaluated and produces whatever geometry it > produces. Here, each time you invoke it you get a random-sized cube, and > two invocations will yield different results. > > > I hope that makes the distinction a little clearer, and makes it clear why > I want people to understand the distinction. > > >
DH
Daniel Harvey
Thu, Feb 16, 2023 12:11 AM

Adrian - I like where you've taken this. I rearranged the code a bit and
made it into a single module. I also allowed r to be a list, because I like
how the fillet looks when it has an elliptical section - allows you to make
one object feel "primary" and have the other object flow into it, rather
than the fillet just evening them both equally.

// Invoke with 2 children that are at unit scale and create a
// fillet joint between them, while applying specified transformations.
//
// r = radius of fillet (relative to scale of children, can be a list to
create elliptical fillet)
// T0,R0,S0 = translation, rotation and scaling for object 0
// T1,R1,S1 = translation, rotation and scaling for object 1
// steps = number of steps in fillet
// chamfer = set true to get chamfer instead of rounding
module fillet_join(r, T0,R0,S0, T1,R1,S1, steps=10, chamfer=false) {
T = [T0,T1];
R = [R0,R1];
S = [S0,S1];
module _position(i, dS=0) {
translate(T[i]) rotate(R[i]) scale(S[i]+dS*[1,1,1]) children();
}
function scaleR(fs, i) = fs[i] * (is_list(r) ? r[i] : r);

assert($children==2);
da=1/steps;
for(ai=[da:da:1]){
    hull() {
        for (a=[ai-da,ai]) {
            fsc = chamfer ? [a,1-a] : [1-cos(90*a), 1-sin(90*a)];
            intersection(){
                _position(0,scaleR(fsc,0)) children(0);
                _position(1,scaleR(fsc,1)) children(1);
            }
        }
    }
}
_position(0) children(0);
_position(1) children(1);

}

fillet_join([2,5],[0,0,3], [0,90,-10], [22,22,100],
[0,6,6], [0,20,0], [15,20,28],chamfer=false) {
cylinder(d=1,center=true,$fn=64);
cube(center=true);
}

On Wed, Feb 15, 2023 at 2:39 PM Adrian Mariano avm4@cornell.edu wrote:

I realized that I overlooked a way to do this with a module.  The problem
was I tried passing children() instead of passing the children
individually, which you can do in this case because you know how many there
are.  That gives this slightly streamlined code:

// Invoke with 2 children that are at unit scale and create a
// fillet joint between them, while applying specified transformations.
//
// r = radius of fillet (relative to scale of children)
// T0,R0,S0 = translation, rotation and scaling for object 0
// T1,R1,S1 = translation, rotation and scaling for object 1
// steps = number of steps in fillet
// chamfer = set true to get chamfer instead of rounding

module fillet_slice(r, a, T0,R0,S0, T1,R1,S1, chamfer=false)
{
offset = chamfer ? r * [a,1-a]
: r * [1-cos(90a), 1-sin(90a)];
scale0 = [for(s=S0) offset[0]+s];
scale1 = [for(s=S1) offset[1]+s];
intersection(){
translate(T0)rotate(R0)scale(scale0) children(0);
translate(T1)rotate(R1)scale(scale1) children(1);
}
}

module fillet_join(r, T0,R0,S0, T1,R1,S1, steps=10, chamfer=false)
{
assert($children==2);
da=1/steps;
for(a=[0:da:1-da/2]){
hull() {
fillet_slice(r,a,T0,R0,S0,T1,R1,S1,chamfer){
children(0);
children(1);
}
fillet_slice(r,a+da,T0,R0,S0,T1,R1,S1,chamfer){
children(0);
children(1);
}
}
}
translate(T0)rotate(R0)scale(S0) children(0);
translate(T1)rotate(R1)scale(S1) children(1);
}

fillet_join(3,[0,0,3], [0,90,-10], [22,22,100],
[0,6,6], [0,20,0], [15,20,28],chamfer=false)
{
cylinder(d=1,center=true,$fn=64);
cube(center=true);
}

On Wed, Feb 15, 2023 at 3:20 AM Jordan Brown <
openscad@jordan.maileater.net> wrote:

On 2/14/2023 6:31 PM, Daniel Harvey wrote:

It is a pity that OpenSCAD doesn't allow you to pass one module
definition as an argument to another, as Python does - and I was grasping
for.

Excuse me for a moment while I get pedantic.  People often get confused
about what "module" means, and especially with the upcoming work in
PR#4478 https://github.com/openscad/openscad/pull/4478 it's important
to keep the concepts straight.

Modules are executable subprograms.  They might happen to always produce
the same output, but they may not.  "cube" is a module - sometimes it
produces 1x1x1 cubes, sometimes 10x10x10, and sometimes 9x4x1.

Geometric objects (still looking for a good phrase there) are the
results of executing modules.  Once you have executed cube(1), the result
is a 1x1 cube (in the current coordinate system) and nothing will ever
change that.

By way of analogy, consider this function:

function five() = 5;

This function will always return the number 5, but it is not the number 5

  • it is an executable subprogram that happens to always yield the same
    result.

Today's OpenSCAD does have one form for passing something in this area -
the "child" mechanism:

rotate(45) cube(10);

That looks like it's passing a geometric object to rotate, but really
it's not - it's passing something that's more like a parameter-less
module.  This is most easily seen with modules that invoke their children
more than once:

module dup(dx) {
children();
translate([dx,0,0]) children();
}

If you invoke this with a simple child, it could be working on geometric
objects:

dup(5) cube(2);

does the obvious thing.

But what do these do?

dup(5) echo("hello");
dup(5) cube(rands(1,3,1)[0]);

The "passing a geometric object" model would say that the first would
echo "hello", while the second would generate a random-sized cube, and then
duplicate it... but that's not what happens.  Instead, each invocation of
"children()" causes the children to be evaluated, so what you get are two
echos of "hello" and two independently random cubes.

The same is true if you set $ variables in the parent module; the child
can receive them and take action based on them.

The children may not be evaluated at all...

module maybe(bool) {
if (bool) children();
}

maybe(false) echo("you never see this");


4478 includes several mechanisms offering more features for both kinds of
constructs, and it becomes even more important to keep them straight.

4478 lets you use geometric objects as data values:

c = {{ cube(rands(1,3,1)[0]); }}      // Maybe not final syntax.

and then because it's just a data value, you can put it in an array, pass
it as a parameter, return it from a function, and so on.

You can later add it to the model:

c;

Note that that geometric value gets evaluated once, so "c" represents
some one random-sized cube.  No matter how many times you add it to the
model, it's always the same size.  (In the current coordinate system, of
course.)

4478 also lets you use modules as data values (as you can with functions
today):

m = module () { cube(rands(1,3,1)[0]); };

Again, this "module reference" is a data value.  You can put it in an
array, pass it as a parameter, et cetera.  At some later point you can
invoke it and add the resulting geometry to the model:

m();

But it's a module.  Until you invoke it, it isn't geometry.  Each time
you invoke it, it gets evaluated and produces whatever geometry it
produces.  Here, each time you invoke it you get a random-sized cube, and
two invocations will yield different results.

I hope that makes the distinction a little clearer, and makes it clear
why I want people to understand the distinction.


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org

Adrian - I like where you've taken this. I rearranged the code a bit and made it into a single module. I also allowed r to be a list, because I like how the fillet looks when it has an elliptical section - allows you to make one object feel "primary" and have the other object flow into it, rather than the fillet just evening them both equally. // Invoke with 2 children that are at unit scale and create a // fillet joint between them, while applying specified transformations. // // r = radius of fillet (relative to scale of children, can be a list to create elliptical fillet) // T0,R0,S0 = translation, rotation and scaling for object 0 // T1,R1,S1 = translation, rotation and scaling for object 1 // steps = number of steps in fillet // chamfer = set true to get chamfer instead of rounding module fillet_join(r, T0,R0,S0, T1,R1,S1, steps=10, chamfer=false) { T = [T0,T1]; R = [R0,R1]; S = [S0,S1]; module _position(i, dS=0) { translate(T[i]) rotate(R[i]) scale(S[i]+dS*[1,1,1]) children(); } function scaleR(fs, i) = fs[i] * (is_list(r) ? r[i] : r); assert($children==2); da=1/steps; for(ai=[da:da:1]){ hull() { for (a=[ai-da,ai]) { fsc = chamfer ? [a,1-a] : [1-cos(90*a), 1-sin(90*a)]; intersection(){ _position(0,scaleR(fsc,0)) children(0); _position(1,scaleR(fsc,1)) children(1); } } } } _position(0) children(0); _position(1) children(1); } fillet_join([2,5],[0,0,3], [0,90,-10], [22,22,100], [0,6,6], [0,20,0], [15,20,28],chamfer=false) { cylinder(d=1,center=true,$fn=64); cube(center=true); } On Wed, Feb 15, 2023 at 2:39 PM Adrian Mariano <avm4@cornell.edu> wrote: > I realized that I overlooked a way to do this with a module. The problem > was I tried passing children() instead of passing the children > individually, which you can do in this case because you know how many there > are. That gives this slightly streamlined code: > > // Invoke with 2 children that are at unit scale and create a > // fillet joint between them, while applying specified transformations. > // > // r = radius of fillet (relative to scale of children) > // T0,R0,S0 = translation, rotation and scaling for object 0 > // T1,R1,S1 = translation, rotation and scaling for object 1 > // steps = number of steps in fillet > // chamfer = set true to get chamfer instead of rounding > > module fillet_slice(r, a, T0,R0,S0, T1,R1,S1, chamfer=false) > { > offset = chamfer ? r * [a,1-a] > : r * [1-cos(90*a), 1-sin(90*a)]; > scale0 = [for(s=S0) offset[0]+s]; > scale1 = [for(s=S1) offset[1]+s]; > intersection(){ > translate(T0)rotate(R0)scale(scale0) children(0); > translate(T1)rotate(R1)scale(scale1) children(1); > } > } > > module fillet_join(r, T0,R0,S0, T1,R1,S1, steps=10, chamfer=false) > { > assert($children==2); > da=1/steps; > for(a=[0:da:1-da/2]){ > hull() { > fillet_slice(r,a,T0,R0,S0,T1,R1,S1,chamfer){ > children(0); > children(1); > } > fillet_slice(r,a+da,T0,R0,S0,T1,R1,S1,chamfer){ > children(0); > children(1); > } > } > } > translate(T0)rotate(R0)scale(S0) children(0); > translate(T1)rotate(R1)scale(S1) children(1); > } > > > fillet_join(3,[0,0,3], [0,90,-10], [22,22,100], > [0,6,6], [0,20,0], [15,20,28],chamfer=false) > { > cylinder(d=1,center=true,$fn=64); > cube(center=true); > } > > > > On Wed, Feb 15, 2023 at 3:20 AM Jordan Brown < > openscad@jordan.maileater.net> wrote: > >> On 2/14/2023 6:31 PM, Daniel Harvey wrote: >> >> It is a pity that OpenSCAD doesn't allow you to pass one module >> definition as an argument to another, as Python does - and I was grasping >> for. >> >> >> Excuse me for a moment while I get pedantic. People often get confused >> about what "module" means, and especially with the upcoming work in >> PR#4478 <https://github.com/openscad/openscad/pull/4478> it's important >> to keep the concepts straight. >> >> Modules are executable subprograms. They might happen to always produce >> the same output, but they may not. "cube" is a module - sometimes it >> produces 1x1x1 cubes, sometimes 10x10x10, and sometimes 9x4x1. >> >> Geometric objects (still looking for a good phrase there) are the >> *results* of executing modules. Once you have executed cube(1), the result >> is a 1x1 cube (in the current coordinate system) and nothing will ever >> change that. >> >> By way of analogy, consider this function: >> >> function five() = 5; >> >> This function will always return the number 5, but it is not the number 5 >> - it is an executable subprogram that happens to always yield the same >> result. >> >> ------------------------------ >> >> Today's OpenSCAD does have one form for passing something in this area - >> the "child" mechanism: >> >> rotate(45) cube(10); >> >> That looks like it's passing a geometric object to rotate, but really >> it's not - it's passing something that's more like a parameter-less >> module. This is most easily seen with modules that invoke their children >> more than once: >> >> module dup(dx) { >> children(); >> translate([dx,0,0]) children(); >> } >> >> If you invoke this with a simple child, it could be working on geometric >> objects: >> >> dup(5) cube(2); >> >> does the obvious thing. >> >> But what do these do? >> >> dup(5) echo("hello"); >> dup(5) cube(rands(1,3,1)[0]); >> >> The "passing a geometric object" model would say that the first would >> echo "hello", while the second would generate a random-sized cube, and then >> duplicate it... but that's not what happens. Instead, each invocation of >> "children()" causes the children to be evaluated, so what you get are two >> echos of "hello" and two independently random cubes. >> >> The same is true if you set $ variables in the parent module; the child >> can receive them and take action based on them. >> >> The children may not be evaluated at all... >> >> module maybe(bool) { >> if (bool) children(); >> } >> >> maybe(false) echo("you never see this"); >> >> >> ------------------------------ >> >> 4478 includes several mechanisms offering more features for both kinds of >> constructs, and it becomes even more important to keep them straight. >> >> 4478 lets you use geometric objects as data values: >> >> c = {{ cube(rands(1,3,1)[0]); }} // Maybe not final syntax. >> >> and then because it's just a data value, you can put it in an array, pass >> it as a parameter, return it from a function, and so on. >> >> You can later add it to the model: >> >> c; >> >> Note that that geometric value gets evaluated *once*, so "c" represents >> some one random-sized cube. No matter how many times you add it to the >> model, it's always the same size. (In the current coordinate system, of >> course.) >> >> 4478 also lets you use modules as data values (as you can with functions >> today): >> >> m = module () { cube(rands(1,3,1)[0]); }; >> >> Again, this "module reference" is a data value. You can put it in an >> array, pass it as a parameter, et cetera. At some later point you can >> invoke it and add the resulting geometry to the model: >> >> m(); >> >> But it's a module. Until you invoke it, it isn't geometry. *Each* time >> you invoke it, it gets evaluated and produces whatever geometry it >> produces. Here, each time you invoke it you get a random-sized cube, and >> two invocations will yield different results. >> >> >> I hope that makes the distinction a little clearer, and makes it clear >> why I want people to understand the distinction. >> >> >> _______________________________________________ >> OpenSCAD mailing list >> To unsubscribe send an email to discuss-leave@lists.openscad.org >> > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email to discuss-leave@lists.openscad.org >
AM
Adrian Mariano
Thu, Feb 16, 2023 1:01 AM

Daniel, here's a slightly tweaked and better documented version.  Also
chamfers only need one step, which obviously is faster.  I'm not sure if
it's possible for the loop to fail to run the "1" case, since it's floating
point with an equality test.  It could be changed to loop through
integers.

// Module: fillet_join()
// Usage:
//  fillet_join(r, T0, R0, S0, T1, R1, S1, [steps], [chamfer])
// Description:
//  Invoke with 2 children that are at unit scale and centered at the
//  origin.  You supply translation, rotation and scaling parameters
//  for each of the children.  The fillet_join module creates a fillet at
//  the intersection of the two objects in their transformed position.
//  The children must be locally convex in the region where they intersect.
//  If your objects are not unit sized then the fillets may end up
//  scaled, possibly differently in different directions.  You can give the
//  radius as a list of two values to scale the fillet differently on the
//  two objects.
// Arguments:
//  r = radius of fillet, relative to scaled of children.  Can be a list
of 2 values.
//  T0, R0, S0 = translation, rotation and scaling for object 0
//  T1,R1,S1 = translation, rotation and scaling for object 1
//  steps = number of steps in fillet.  Default: 10
//  chamfer = set true to get chamfer instead of rounding.  Default: False
module fillet_join(r, T0,R0,S0, T1,R1,S1, steps=10, chamfer=false) {
T = [T0,T1];
R = [R0,R1];
S = [S0,S1];
r = is_num(r) ? [r,r] : r;

module _position(i, dS=0) translate(T[i]) rotate(R[i])

scale(S[i]+dS*[1,1,1]) children();

assert(is_list(r) && len(r)==2,"radius must be a number of length 2

list");
assert($children==2);
da=1/steps;
for(ai=[da:da:1]){
hull() {
for (a=[ai-da,ai]) {
fsc = chamfer ? [a,1-a] : [1-cos(90a), 1-sin(90a)];
intersection_for(i=[0,1]) _position(i,fsc[i] * r[i])
children(i);
}
}
}
for(i=[0,1]) _position(i) children(i);
}

fillet_join([2,5],[0,0,3], [0,90,-10], [22,22,100],
[0,6,6], [0,20,0], [15,20,28],chamfer=false) {
cylinder(d=1,center=true,$fn=64);
cube(center=true);
}

On Wed, Feb 15, 2023 at 7:12 PM Daniel Harvey dwaharvey@gmail.com wrote:

Adrian - I like where you've taken this. I rearranged the code a bit and
made it into a single module. I also allowed r to be a list, because I like
how the fillet looks when it has an elliptical section - allows you to make
one object feel "primary" and have the other object flow into it, rather
than the fillet just evening them both equally.

// Invoke with 2 children that are at unit scale and create a
// fillet joint between them, while applying specified transformations.
//
// r = radius of fillet (relative to scale of children, can be a list to
create elliptical fillet)
// T0,R0,S0 = translation, rotation and scaling for object 0
// T1,R1,S1 = translation, rotation and scaling for object 1
// steps = number of steps in fillet
// chamfer = set true to get chamfer instead of rounding
module fillet_join(r, T0,R0,S0, T1,R1,S1, steps=10, chamfer=false) {
T = [T0,T1];
R = [R0,R1];
S = [S0,S1];
module _position(i, dS=0) {
translate(T[i]) rotate(R[i]) scale(S[i]+dS*[1,1,1]) children();
}
function scaleR(fs, i) = fs[i] * (is_list(r) ? r[i] : r);

 assert($children==2);
 da=1/steps;
 for(ai=[da:da:1]){
     hull() {
         for (a=[ai-da,ai]) {
             fsc = chamfer ? [a,1-a] : [1-cos(90*a), 1-sin(90*a)];
             intersection(){
                 _position(0,scaleR(fsc,0)) children(0);
                 _position(1,scaleR(fsc,1)) children(1);
             }
         }
     }
 }
 _position(0) children(0);
 _position(1) children(1);

}

fillet_join([2,5],[0,0,3], [0,90,-10], [22,22,100],
[0,6,6], [0,20,0], [15,20,28],chamfer=false) {
cylinder(d=1,center=true,$fn=64);
cube(center=true);
}

On Wed, Feb 15, 2023 at 2:39 PM Adrian Mariano avm4@cornell.edu wrote:

I realized that I overlooked a way to do this with a module.  The problem
was I tried passing children() instead of passing the children
individually, which you can do in this case because you know how many there
are.  That gives this slightly streamlined code:

// Invoke with 2 children that are at unit scale and create a
// fillet joint between them, while applying specified transformations.
//
// r = radius of fillet (relative to scale of children)
// T0,R0,S0 = translation, rotation and scaling for object 0
// T1,R1,S1 = translation, rotation and scaling for object 1
// steps = number of steps in fillet
// chamfer = set true to get chamfer instead of rounding

module fillet_slice(r, a, T0,R0,S0, T1,R1,S1, chamfer=false)
{
offset = chamfer ? r * [a,1-a]
: r * [1-cos(90a), 1-sin(90a)];
scale0 = [for(s=S0) offset[0]+s];
scale1 = [for(s=S1) offset[1]+s];
intersection(){
translate(T0)rotate(R0)scale(scale0) children(0);
translate(T1)rotate(R1)scale(scale1) children(1);
}
}

module fillet_join(r, T0,R0,S0, T1,R1,S1, steps=10, chamfer=false)
{
assert($children==2);
da=1/steps;
for(a=[0:da:1-da/2]){
hull() {
fillet_slice(r,a,T0,R0,S0,T1,R1,S1,chamfer){
children(0);
children(1);
}
fillet_slice(r,a+da,T0,R0,S0,T1,R1,S1,chamfer){
children(0);
children(1);
}
}
}
translate(T0)rotate(R0)scale(S0) children(0);
translate(T1)rotate(R1)scale(S1) children(1);
}

fillet_join(3,[0,0,3], [0,90,-10], [22,22,100],
[0,6,6], [0,20,0], [15,20,28],chamfer=false)
{
cylinder(d=1,center=true,$fn=64);
cube(center=true);
}

On Wed, Feb 15, 2023 at 3:20 AM Jordan Brown <
openscad@jordan.maileater.net> wrote:

On 2/14/2023 6:31 PM, Daniel Harvey wrote:

It is a pity that OpenSCAD doesn't allow you to pass one module
definition as an argument to another, as Python does - and I was grasping
for.

Excuse me for a moment while I get pedantic.  People often get confused
about what "module" means, and especially with the upcoming work in
PR#4478 https://github.com/openscad/openscad/pull/4478 it's important
to keep the concepts straight.

Modules are executable subprograms.  They might happen to always produce
the same output, but they may not.  "cube" is a module - sometimes it
produces 1x1x1 cubes, sometimes 10x10x10, and sometimes 9x4x1.

Geometric objects (still looking for a good phrase there) are the
results of executing modules.  Once you have executed cube(1), the result
is a 1x1 cube (in the current coordinate system) and nothing will ever
change that.

By way of analogy, consider this function:

function five() = 5;

This function will always return the number 5, but it is not the number
5 - it is an executable subprogram that happens to always yield the same
result.


Today's OpenSCAD does have one form for passing something in this area -
the "child" mechanism:

rotate(45) cube(10);

That looks like it's passing a geometric object to rotate, but really
it's not - it's passing something that's more like a parameter-less
module.  This is most easily seen with modules that invoke their children
more than once:

module dup(dx) {
children();
translate([dx,0,0]) children();
}

If you invoke this with a simple child, it could be working on geometric
objects:

dup(5) cube(2);

does the obvious thing.

But what do these do?

dup(5) echo("hello");
dup(5) cube(rands(1,3,1)[0]);

The "passing a geometric object" model would say that the first would
echo "hello", while the second would generate a random-sized cube, and then
duplicate it... but that's not what happens.  Instead, each invocation of
"children()" causes the children to be evaluated, so what you get are two
echos of "hello" and two independently random cubes.

The same is true if you set $ variables in the parent module; the child
can receive them and take action based on them.

The children may not be evaluated at all...

module maybe(bool) {
if (bool) children();
}

maybe(false) echo("you never see this");


4478 includes several mechanisms offering more features for both kinds
of constructs, and it becomes even more important to keep them straight.

4478 lets you use geometric objects as data values:

c = {{ cube(rands(1,3,1)[0]); }}      // Maybe not final syntax.

and then because it's just a data value, you can put it in an array,
pass it as a parameter, return it from a function, and so on.

You can later add it to the model:

c;

Note that that geometric value gets evaluated once, so "c" represents
some one random-sized cube.  No matter how many times you add it to the
model, it's always the same size.  (In the current coordinate system, of
course.)

4478 also lets you use modules as data values (as you can with functions
today):

m = module () { cube(rands(1,3,1)[0]); };

Again, this "module reference" is a data value.  You can put it in an
array, pass it as a parameter, et cetera.  At some later point you can
invoke it and add the resulting geometry to the model:

m();

But it's a module.  Until you invoke it, it isn't geometry.  Each time
you invoke it, it gets evaluated and produces whatever geometry it
produces.  Here, each time you invoke it you get a random-sized cube, and
two invocations will yield different results.

I hope that makes the distinction a little clearer, and makes it clear
why I want people to understand the distinction.


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org

Daniel, here's a slightly tweaked and better documented version. Also chamfers only need one step, which obviously is faster. I'm not sure if it's possible for the loop to fail to run the "1" case, since it's floating point with an equality test. It could be changed to loop through integers. // Module: fillet_join() // Usage: // fillet_join(r, T0, R0, S0, T1, R1, S1, [steps], [chamfer]) // Description: // Invoke with 2 children that are at unit scale and centered at the // origin. You supply translation, rotation and scaling parameters // for each of the children. The fillet_join module creates a fillet at // the intersection of the two objects in their transformed position. // The children must be locally convex in the region where they intersect. // If your objects are not unit sized then the fillets may end up // scaled, possibly differently in different directions. You can give the // radius as a list of two values to scale the fillet differently on the // two objects. // Arguments: // r = radius of fillet, relative to scaled of children. Can be a list of 2 values. // T0, R0, S0 = translation, rotation and scaling for object 0 // T1,R1,S1 = translation, rotation and scaling for object 1 // steps = number of steps in fillet. Default: 10 // chamfer = set true to get chamfer instead of rounding. Default: False module fillet_join(r, T0,R0,S0, T1,R1,S1, steps=10, chamfer=false) { T = [T0,T1]; R = [R0,R1]; S = [S0,S1]; r = is_num(r) ? [r,r] : r; module _position(i, dS=0) translate(T[i]) rotate(R[i]) scale(S[i]+dS*[1,1,1]) children(); assert(is_list(r) && len(r)==2,"radius must be a number of length 2 list"); assert($children==2); da=1/steps; for(ai=[da:da:1]){ hull() { for (a=[ai-da,ai]) { fsc = chamfer ? [a,1-a] : [1-cos(90*a), 1-sin(90*a)]; intersection_for(i=[0,1]) _position(i,fsc[i] * r[i]) children(i); } } } for(i=[0,1]) _position(i) children(i); } fillet_join([2,5],[0,0,3], [0,90,-10], [22,22,100], [0,6,6], [0,20,0], [15,20,28],chamfer=false) { cylinder(d=1,center=true,$fn=64); cube(center=true); } On Wed, Feb 15, 2023 at 7:12 PM Daniel Harvey <dwaharvey@gmail.com> wrote: > Adrian - I like where you've taken this. I rearranged the code a bit and > made it into a single module. I also allowed r to be a list, because I like > how the fillet looks when it has an elliptical section - allows you to make > one object feel "primary" and have the other object flow into it, rather > than the fillet just evening them both equally. > > // Invoke with 2 children that are at unit scale and create a > // fillet joint between them, while applying specified transformations. > // > // r = radius of fillet (relative to scale of children, can be a list to > create elliptical fillet) > // T0,R0,S0 = translation, rotation and scaling for object 0 > // T1,R1,S1 = translation, rotation and scaling for object 1 > // steps = number of steps in fillet > // chamfer = set true to get chamfer instead of rounding > module fillet_join(r, T0,R0,S0, T1,R1,S1, steps=10, chamfer=false) { > T = [T0,T1]; > R = [R0,R1]; > S = [S0,S1]; > module _position(i, dS=0) { > translate(T[i]) rotate(R[i]) scale(S[i]+dS*[1,1,1]) children(); > } > function scaleR(fs, i) = fs[i] * (is_list(r) ? r[i] : r); > > assert($children==2); > da=1/steps; > for(ai=[da:da:1]){ > hull() { > for (a=[ai-da,ai]) { > fsc = chamfer ? [a,1-a] : [1-cos(90*a), 1-sin(90*a)]; > intersection(){ > _position(0,scaleR(fsc,0)) children(0); > _position(1,scaleR(fsc,1)) children(1); > } > } > } > } > _position(0) children(0); > _position(1) children(1); > } > > fillet_join([2,5],[0,0,3], [0,90,-10], [22,22,100], > [0,6,6], [0,20,0], [15,20,28],chamfer=false) { > cylinder(d=1,center=true,$fn=64); > cube(center=true); > } > > On Wed, Feb 15, 2023 at 2:39 PM Adrian Mariano <avm4@cornell.edu> wrote: > >> I realized that I overlooked a way to do this with a module. The problem >> was I tried passing children() instead of passing the children >> individually, which you can do in this case because you know how many there >> are. That gives this slightly streamlined code: >> >> // Invoke with 2 children that are at unit scale and create a >> // fillet joint between them, while applying specified transformations. >> // >> // r = radius of fillet (relative to scale of children) >> // T0,R0,S0 = translation, rotation and scaling for object 0 >> // T1,R1,S1 = translation, rotation and scaling for object 1 >> // steps = number of steps in fillet >> // chamfer = set true to get chamfer instead of rounding >> >> module fillet_slice(r, a, T0,R0,S0, T1,R1,S1, chamfer=false) >> { >> offset = chamfer ? r * [a,1-a] >> : r * [1-cos(90*a), 1-sin(90*a)]; >> scale0 = [for(s=S0) offset[0]+s]; >> scale1 = [for(s=S1) offset[1]+s]; >> intersection(){ >> translate(T0)rotate(R0)scale(scale0) children(0); >> translate(T1)rotate(R1)scale(scale1) children(1); >> } >> } >> >> module fillet_join(r, T0,R0,S0, T1,R1,S1, steps=10, chamfer=false) >> { >> assert($children==2); >> da=1/steps; >> for(a=[0:da:1-da/2]){ >> hull() { >> fillet_slice(r,a,T0,R0,S0,T1,R1,S1,chamfer){ >> children(0); >> children(1); >> } >> fillet_slice(r,a+da,T0,R0,S0,T1,R1,S1,chamfer){ >> children(0); >> children(1); >> } >> } >> } >> translate(T0)rotate(R0)scale(S0) children(0); >> translate(T1)rotate(R1)scale(S1) children(1); >> } >> >> >> fillet_join(3,[0,0,3], [0,90,-10], [22,22,100], >> [0,6,6], [0,20,0], [15,20,28],chamfer=false) >> { >> cylinder(d=1,center=true,$fn=64); >> cube(center=true); >> } >> >> >> >> On Wed, Feb 15, 2023 at 3:20 AM Jordan Brown < >> openscad@jordan.maileater.net> wrote: >> >>> On 2/14/2023 6:31 PM, Daniel Harvey wrote: >>> >>> It is a pity that OpenSCAD doesn't allow you to pass one module >>> definition as an argument to another, as Python does - and I was grasping >>> for. >>> >>> >>> Excuse me for a moment while I get pedantic. People often get confused >>> about what "module" means, and especially with the upcoming work in >>> PR#4478 <https://github.com/openscad/openscad/pull/4478> it's important >>> to keep the concepts straight. >>> >>> Modules are executable subprograms. They might happen to always produce >>> the same output, but they may not. "cube" is a module - sometimes it >>> produces 1x1x1 cubes, sometimes 10x10x10, and sometimes 9x4x1. >>> >>> Geometric objects (still looking for a good phrase there) are the >>> *results* of executing modules. Once you have executed cube(1), the result >>> is a 1x1 cube (in the current coordinate system) and nothing will ever >>> change that. >>> >>> By way of analogy, consider this function: >>> >>> function five() = 5; >>> >>> This function will always return the number 5, but it is not the number >>> 5 - it is an executable subprogram that happens to always yield the same >>> result. >>> >>> ------------------------------ >>> >>> Today's OpenSCAD does have one form for passing something in this area - >>> the "child" mechanism: >>> >>> rotate(45) cube(10); >>> >>> That looks like it's passing a geometric object to rotate, but really >>> it's not - it's passing something that's more like a parameter-less >>> module. This is most easily seen with modules that invoke their children >>> more than once: >>> >>> module dup(dx) { >>> children(); >>> translate([dx,0,0]) children(); >>> } >>> >>> If you invoke this with a simple child, it could be working on geometric >>> objects: >>> >>> dup(5) cube(2); >>> >>> does the obvious thing. >>> >>> But what do these do? >>> >>> dup(5) echo("hello"); >>> dup(5) cube(rands(1,3,1)[0]); >>> >>> The "passing a geometric object" model would say that the first would >>> echo "hello", while the second would generate a random-sized cube, and then >>> duplicate it... but that's not what happens. Instead, each invocation of >>> "children()" causes the children to be evaluated, so what you get are two >>> echos of "hello" and two independently random cubes. >>> >>> The same is true if you set $ variables in the parent module; the child >>> can receive them and take action based on them. >>> >>> The children may not be evaluated at all... >>> >>> module maybe(bool) { >>> if (bool) children(); >>> } >>> >>> maybe(false) echo("you never see this"); >>> >>> >>> ------------------------------ >>> >>> 4478 includes several mechanisms offering more features for both kinds >>> of constructs, and it becomes even more important to keep them straight. >>> >>> 4478 lets you use geometric objects as data values: >>> >>> c = {{ cube(rands(1,3,1)[0]); }} // Maybe not final syntax. >>> >>> and then because it's just a data value, you can put it in an array, >>> pass it as a parameter, return it from a function, and so on. >>> >>> You can later add it to the model: >>> >>> c; >>> >>> Note that that geometric value gets evaluated *once*, so "c" represents >>> some one random-sized cube. No matter how many times you add it to the >>> model, it's always the same size. (In the current coordinate system, of >>> course.) >>> >>> 4478 also lets you use modules as data values (as you can with functions >>> today): >>> >>> m = module () { cube(rands(1,3,1)[0]); }; >>> >>> Again, this "module reference" is a data value. You can put it in an >>> array, pass it as a parameter, et cetera. At some later point you can >>> invoke it and add the resulting geometry to the model: >>> >>> m(); >>> >>> But it's a module. Until you invoke it, it isn't geometry. *Each* time >>> you invoke it, it gets evaluated and produces whatever geometry it >>> produces. Here, each time you invoke it you get a random-sized cube, and >>> two invocations will yield different results. >>> >>> >>> I hope that makes the distinction a little clearer, and makes it clear >>> why I want people to understand the distinction. >>> >>> >>> _______________________________________________ >>> OpenSCAD mailing list >>> To unsubscribe send an email to discuss-leave@lists.openscad.org >>> >> _______________________________________________ >> OpenSCAD mailing list >> To unsubscribe send an email to discuss-leave@lists.openscad.org >> > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email to discuss-leave@lists.openscad.org >
JB
Jordan Brown
Thu, Feb 16, 2023 5:31 AM

On 2/15/2023 2:40 PM, Daniel Harvey wrote:

module fillet(object_module1, pars1, object_module2, pars2, ...) {
    intersection() {
         object_module1(pars1);
         object_module2(pars2);
    }
}

It happens that that exact case you can do today:

module fillet() {
    intersection() {
        children(0);
        children(1);
    }
}

fillet() {
    object_module1(pars1);
    object_module2(pars2);
}

The "children(0)" causes the first module invocation,
"object_module1(pars1)", to be evaluated.  The "children(1)" causes the
second module invocation to be evaluated.  It is as you requested:  you
supply a module and its parameters, and another module and its
parameters, and the fillet module evaluates them and intersects them.

In fact, you can even do that particular case for an arbitrary number
of children...

module fillet() {
    intersection_for(i=[0:$children-1]) {
        children(i);
    }
}

fillet() {
    object_module1(pars1);
    object_module2(pars2);
    [...]
}

Unfortunately, there are quite a few other cases that you can't do for
arbitrary numbers of children.  In particular, if module A has an
unknown number of children, there is no way to pass them as separate
children
to module B.  That's exactly why intersection_for() was
created as a builtin, because otherwise there's no way to intersect an
unknown number of objects.

The children() mechanism is a bit ... novel?  exotic?  weird? ... but it
is very powerful and well worth spending the effort to master.

Some of the features in PR#4478 will make it possible to use more
traditional programming constructs, but I don't think they will end up
being as concise as the child syntax allows.

On 2/15/2023 2:40 PM, Daniel Harvey wrote: > module fillet(object_module1, pars1, object_module2, pars2, ...) { >     intersection() { >          object_module1(pars1); >          object_module2(pars2); >     } > } It happens that that exact case you *can* do today: module fillet() {     intersection() { children(0); children(1); } } fillet() { object_module1(pars1); object_module2(pars2); } The "children(0)" causes the first module invocation, "object_module1(pars1)", to be evaluated.  The "children(1)" causes the second module invocation to be evaluated.  It is as you requested:  you supply a module and its parameters, and another module and its parameters, and the fillet module evaluates them and intersects them. In fact, you can even do that *particular* case for an arbitrary number of children... module fillet() {     intersection_for(i=[0:$children-1]) { children(i); } } fillet() { object_module1(pars1); object_module2(pars2); [...] } Unfortunately, there are quite a few other cases that you *can't* do for arbitrary numbers of children.  In particular, if module A has an unknown number of children, there is no way to pass them *as separate children* to module B.  That's exactly why intersection_for() was created as a builtin, because otherwise there's no way to intersect an unknown number of objects. The children() mechanism is a bit ... novel?  exotic?  weird? ... but it is very powerful and well worth spending the effort to master. Some of the features in PR#4478 will make it possible to use more traditional programming constructs, but I don't think they will end up being as concise as the child syntax allows.
DH
Daniel Harvey
Thu, Feb 16, 2023 3:53 PM

I garee this sort of gets the ball over the line, but not in a way that
permits good organization of the code in my opinion.

Take the use case here - the aim is that one wants to repeatedly create the
same object but with parameters that vary around a particular point in
parameter space.
The use of children() forces you to create the parameter space you want to
explore in the outside module, and the fillet module becomes effectively
trivial - it is just an intersection_for loop and offers nothing else.
Whereas the aim here for me was to create a module to do fillets
repeatedly, and be able to use it from the outside without knowledge or
repetition of the way the fillet is achieved.
At present one is forced to have the logic about how to make it outside the
fillet module (wtih fillet() being trivial), or use the slightly awkward
scale/rotate/translate approach.

On Wed, Feb 15, 2023 at 10:31 PM Jordan Brown openscad@jordan.maileater.net
wrote:

On 2/15/2023 2:40 PM, Daniel Harvey wrote:

module fillet(object_module1, pars1, object_module2, pars2, ...) {
intersection() {
object_module1(pars1);
object_module2(pars2);
}
}

It happens that that exact case you can do today:

module fillet() {
intersection() {
children(0);
children(1);
}
}

fillet() {
object_module1(pars1);
object_module2(pars2);
}

The "children(0)" causes the first module invocation,
"object_module1(pars1)", to be evaluated.  The "children(1)" causes the
second module invocation to be evaluated.  It is as you requested:  you
supply a module and its parameters, and another module and its parameters,
and the fillet module evaluates them and intersects them.

In fact, you can even do that particular case for an arbitrary number of
children...

module fillet() {
intersection_for(i=[0:$children-1]) {
children(i);
}
}

fillet() {
object_module1(pars1);
object_module2(pars2);
[...]
}

Unfortunately, there are quite a few other cases that you can't do for
arbitrary numbers of children.  In particular, if module A has an unknown
number of children, there is no way to pass them as separate children to
module B.  That's exactly why intersection_for() was created as a builtin,
because otherwise there's no way to intersect an unknown number of objects.

The children() mechanism is a bit ... novel?  exotic?  weird? ... but it
is very powerful and well worth spending the effort to master.

Some of the features in PR#4478 will make it possible to use more
traditional programming constructs, but I don't think they will end up
being as concise as the child syntax allows.

I garee this sort of gets the ball over the line, but not in a way that permits good organization of the code in my opinion. Take the use case here - the aim is that one wants to repeatedly create the same object but with parameters that vary around a particular point in parameter space. The use of children() forces you to create the parameter space you want to explore in the outside module, and the fillet module becomes effectively trivial - it is just an intersection_for loop and offers nothing else. Whereas the aim here for me was to create a module to do fillets repeatedly, and be able to use it from the outside without knowledge or repetition of the way the fillet is achieved. At present one is forced to have the logic about how to make it outside the fillet module (wtih fillet() being trivial), or use the slightly awkward scale/rotate/translate approach. On Wed, Feb 15, 2023 at 10:31 PM Jordan Brown <openscad@jordan.maileater.net> wrote: > On 2/15/2023 2:40 PM, Daniel Harvey wrote: > > module fillet(object_module1, pars1, object_module2, pars2, ...) { > intersection() { > object_module1(pars1); > object_module2(pars2); > } > } > > > It happens that that exact case you *can* do today: > > module fillet() { > intersection() { > children(0); > children(1); > } > } > > fillet() { > object_module1(pars1); > object_module2(pars2); > } > > The "children(0)" causes the first module invocation, > "object_module1(pars1)", to be evaluated. The "children(1)" causes the > second module invocation to be evaluated. It is as you requested: you > supply a module and its parameters, and another module and its parameters, > and the fillet module evaluates them and intersects them. > > In fact, you can even do that *particular* case for an arbitrary number of > children... > > module fillet() { > intersection_for(i=[0:$children-1]) { > children(i); > } > } > > fillet() { > object_module1(pars1); > object_module2(pars2); > [...] > } > > Unfortunately, there are quite a few other cases that you *can't* do for > arbitrary numbers of children. In particular, if module A has an unknown > number of children, there is no way to pass them *as separate children* to > module B. That's exactly why intersection_for() was created as a builtin, > because otherwise there's no way to intersect an unknown number of objects. > > The children() mechanism is a bit ... novel? exotic? weird? ... but it > is very powerful and well worth spending the effort to master. > > Some of the features in PR#4478 will make it possible to use more > traditional programming constructs, but I don't think they will end up > being as concise as the child syntax allows. > >
SP
Sanjeev Prabhakar
Thu, Feb 16, 2023 4:50 PM

I would suggest someone to find a solution for the intersections as shown
below.

I have tried this but it's not so good.
[image: Screenshot 2023-02-16 at 10.10.47 PM.png]

also please see if the below is possible with the method proposed here

[image: Screenshot 2023-02-16 at 10.18.19 PM.png]

On Thu, 16 Feb 2023 at 21:24, Daniel Harvey dwaharvey@gmail.com wrote:

I garee this sort of gets the ball over the line, but not in a way that
permits good organization of the code in my opinion.

Take the use case here - the aim is that one wants to repeatedly create
the same object but with parameters that vary around a particular point in
parameter space.
The use of children() forces you to create the parameter space you want to
explore in the outside module, and the fillet module becomes effectively
trivial - it is just an intersection_for loop and offers nothing else.
Whereas the aim here for me was to create a module to do fillets
repeatedly, and be able to use it from the outside without knowledge or
repetition of the way the fillet is achieved.
At present one is forced to have the logic about how to make it outside
the fillet module (wtih fillet() being trivial), or use the slightly
awkward scale/rotate/translate approach.

On Wed, Feb 15, 2023 at 10:31 PM Jordan Brown <
openscad@jordan.maileater.net> wrote:

On 2/15/2023 2:40 PM, Daniel Harvey wrote:

module fillet(object_module1, pars1, object_module2, pars2, ...) {
intersection() {
object_module1(pars1);
object_module2(pars2);
}
}

It happens that that exact case you can do today:

module fillet() {
intersection() {
children(0);
children(1);
}
}

fillet() {
object_module1(pars1);
object_module2(pars2);
}

The "children(0)" causes the first module invocation,
"object_module1(pars1)", to be evaluated.  The "children(1)" causes the
second module invocation to be evaluated.  It is as you requested:  you
supply a module and its parameters, and another module and its parameters,
and the fillet module evaluates them and intersects them.

In fact, you can even do that particular case for an arbitrary number
of children...

module fillet() {
intersection_for(i=[0:$children-1]) {
children(i);
}
}

fillet() {
object_module1(pars1);
object_module2(pars2);
[...]
}

Unfortunately, there are quite a few other cases that you can't do for
arbitrary numbers of children.  In particular, if module A has an unknown
number of children, there is no way to pass them as separate children to
module B.  That's exactly why intersection_for() was created as a builtin,
because otherwise there's no way to intersect an unknown number of objects.

The children() mechanism is a bit ... novel?  exotic?  weird? ... but it
is very powerful and well worth spending the effort to master.

Some of the features in PR#4478 will make it possible to use more
traditional programming constructs, but I don't think they will end up
being as concise as the child syntax allows.


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org

I would suggest someone to find a solution for the intersections as shown below. I have tried this but it's not so good. [image: Screenshot 2023-02-16 at 10.10.47 PM.png] also please see if the below is possible with the method proposed here [image: Screenshot 2023-02-16 at 10.18.19 PM.png] On Thu, 16 Feb 2023 at 21:24, Daniel Harvey <dwaharvey@gmail.com> wrote: > I garee this sort of gets the ball over the line, but not in a way that > permits good organization of the code in my opinion. > > Take the use case here - the aim is that one wants to repeatedly create > the same object but with parameters that vary around a particular point in > parameter space. > The use of children() forces you to create the parameter space you want to > explore in the outside module, and the fillet module becomes effectively > trivial - it is just an intersection_for loop and offers nothing else. > Whereas the aim here for me was to create a module to do fillets > repeatedly, and be able to use it from the outside without knowledge or > repetition of the way the fillet is achieved. > At present one is forced to have the logic about how to make it outside > the fillet module (wtih fillet() being trivial), or use the slightly > awkward scale/rotate/translate approach. > > > > On Wed, Feb 15, 2023 at 10:31 PM Jordan Brown < > openscad@jordan.maileater.net> wrote: > >> On 2/15/2023 2:40 PM, Daniel Harvey wrote: >> >> module fillet(object_module1, pars1, object_module2, pars2, ...) { >> intersection() { >> object_module1(pars1); >> object_module2(pars2); >> } >> } >> >> >> It happens that that exact case you *can* do today: >> >> module fillet() { >> intersection() { >> children(0); >> children(1); >> } >> } >> >> fillet() { >> object_module1(pars1); >> object_module2(pars2); >> } >> >> The "children(0)" causes the first module invocation, >> "object_module1(pars1)", to be evaluated. The "children(1)" causes the >> second module invocation to be evaluated. It is as you requested: you >> supply a module and its parameters, and another module and its parameters, >> and the fillet module evaluates them and intersects them. >> >> In fact, you can even do that *particular* case for an arbitrary number >> of children... >> >> module fillet() { >> intersection_for(i=[0:$children-1]) { >> children(i); >> } >> } >> >> fillet() { >> object_module1(pars1); >> object_module2(pars2); >> [...] >> } >> >> Unfortunately, there are quite a few other cases that you *can't* do for >> arbitrary numbers of children. In particular, if module A has an unknown >> number of children, there is no way to pass them *as separate children* to >> module B. That's exactly why intersection_for() was created as a builtin, >> because otherwise there's no way to intersect an unknown number of objects. >> >> The children() mechanism is a bit ... novel? exotic? weird? ... but it >> is very powerful and well worth spending the effort to master. >> >> Some of the features in PR#4478 will make it possible to use more >> traditional programming constructs, but I don't think they will end up >> being as concise as the child syntax allows. >> >> _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email to discuss-leave@lists.openscad.org >
JB
Jordan Brown
Thu, Feb 16, 2023 5:55 PM

Hmm.  One issue is that we're using different meanings for
"parameters".  I don't think of an object's translation and rotation, or
even scale, as parameters.  They are transformations externally applied
to the object.  I generally find it offensive to specify a position or
rotation as part of the definition of an object - just as in the real
world, an object comes from the factory, and then I position it where
I want it.

And mostly that mental model works well, but not for this algorithm.

For this algorithm, the transformation, rotation, and scale have to be
done in the middle of the algorithm, neither applied to the objects
before the algorithm nor applied to the result afterward.

The reason for that is that the algorithm depends on scaling the
objects, for which they must be at the origin, and then intersecting the
objects, which must be with them at their final positions.

I agree that it's ugly, though perhaps for different reasons than you
think it is.

It makes my head hurt.

Hmm.  One issue is that we're using different meanings for "parameters".  I don't think of an object's translation and rotation, or even scale, as parameters.  They are transformations externally applied to the object.  I generally find it offensive to specify a position or rotation as part of the definition of an object - just as in the real world, an object comes from the factory, and *then* I position it where I want it. And mostly that mental model works well, but not for this algorithm. For this algorithm, the transformation, rotation, and scale have to be done in the middle of the algorithm, neither applied to the objects before the algorithm nor applied to the result afterward. The reason for that is that the algorithm depends on scaling the objects, for which they must be at the origin, and then intersecting the objects, which must be with them at their final positions. I agree that it's ugly, though perhaps for different reasons than you think it is. It makes my head hurt.