discuss@lists.openscad.org

OpenSCAD general discussion Mailing-list

View all threads

The behaviour if intersection has changed.

A
arnholm@arnholm.org
Fri, Oct 2, 2020 4:22 PM

On 2020-10-02 18:13, nop head wrote:

Yes an intersection between two operands works like that but what
about an intersection with one operand?

That's a syntax error and should fail.

Carsten Arnholm

On 2020-10-02 18:13, nop head wrote: > Yes an intersection between two operands works like that but what > about an intersection with one operand? That's a syntax error and should fail. Carsten Arnholm
NH
nop head
Fri, Oct 2, 2020 4:24 PM

The problem is OpenSCAD previously had no distinction between an empty
geometry node and a missing operand. So in some cases it ignored an empty
geometry first operand and used the second operand as the first. Now that
has been fixed it has changed the behaviour of if(false) in an intersection.

I can live with it because it only changes two modules in my library and
the change is backwards compatible. Will it break more things in the wild
though? Does it make logical sense that if(false) creates empty
geometry rather than nothing at all.

It would be a syntax error if intersection was a binary operator but when
it is just a built-in module with children() the syntax is correct as in
general you can pass any number of children() to a module.

On Fri, 2 Oct 2020 at 17:13, arnholm@arnholm.org wrote:

On 2020-10-02 17:45, nop head wrote:

What has changed is if(false) now produces an empty() node equivalent
to empty geometry, whereas before it returned an empty group() node,
which must have been ignored by intersection().

what is the significance between a "group()" node that is empty and an
"empty()" node that is empty?

One may also ask what is the significance between a "group()" that
contains something and a "union()" that contains something?

Carsten Arholm


OpenSCAD mailing list
Discuss@lists.openscad.org
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org

The problem is OpenSCAD previously had no distinction between an empty geometry node and a missing operand. So in some cases it ignored an empty geometry first operand and used the second operand as the first. Now that has been fixed it has changed the behaviour of if(false) in an intersection. I can live with it because it only changes two modules in my library and the change is backwards compatible. Will it break more things in the wild though? Does it make logical sense that if(false) creates empty geometry rather than nothing at all. It would be a syntax error if intersection was a binary operator but when it is just a built-in module with children() the syntax is correct as in general you can pass any number of children() to a module. On Fri, 2 Oct 2020 at 17:13, <arnholm@arnholm.org> wrote: > On 2020-10-02 17:45, nop head wrote: > > What has changed is if(false) now produces an empty() node equivalent > > to empty geometry, whereas before it returned an empty group() node, > > which must have been ignored by intersection(). > > what is the significance between a "group()" node that is empty and an > "empty()" node that is empty? > > One may also ask what is the significance between a "group()" that > contains something and a "union()" that contains something? > > Carsten Arholm > > _______________________________________________ > OpenSCAD mailing list > Discuss@lists.openscad.org > http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org >
JB
Jordan Brown
Fri, Oct 2, 2020 5:27 PM

On 10/2/2020 9:22 AM, arnholm@arnholm.org wrote:

On 2020-10-02 18:13, nop head wrote:

Yes an intersection between two operands works like that but what
about an intersection with one operand?

That's a syntax error and should fail.

No.  Operators like intersection() should take all of their arguments
and process them, and should handle the degenerate cases of one and zero
arguments if they make any sense at all.

That's doubly true if things are changed so that a failing "if"
generates nothing, instead of generating an empty-geometry argument.

You might say that

difference() {
    cube();
}

is an error, but what about

difference() {
    cube();
    if (optionA) thingA();
    if (optionB) thinkB();
}

?

I have exactly that construct in a project I recently did, where I have
a module that models a piece of lumber and optionally subtracts off
bevels.  I could have done the same thing with intersection instead of
difference.

The use case is less obvious if failing "if" yields an empty object (as
it does today), but even then one might imagine cases that involve
programs that generate OpenSCAD source.

On 10/2/2020 9:22 AM, arnholm@arnholm.org wrote: > On 2020-10-02 18:13, nop head wrote: >> Yes an intersection between two operands works like that but what >> about an intersection with one operand? > > That's a syntax error and should fail. No.  Operators like intersection() should take all of their arguments and process them, and should handle the degenerate cases of one and zero arguments if they make any sense at all. That's doubly true if things are changed so that a failing "if" generates nothing, instead of generating an empty-geometry argument. You might say that difference() { cube(); } is an error, but what about difference() { cube(); if (optionA) thingA(); if (optionB) thinkB(); } ? I have exactly that construct in a project I recently did, where I have a module that models a piece of lumber and optionally subtracts off bevels.  I could have done the same thing with intersection instead of difference. The use case is less obvious if failing "if" yields an empty object (as it does today), but even then one might imagine cases that involve programs that generate OpenSCAD source.
A
adrianv
Fri, Oct 2, 2020 8:59 PM

Well, there may not be an intersection explicitly with one operand in math,
but there is certainly an intersection with n operands where n may happen to
be one.  This is arguably the exact semantics of the intersection module in
OpenSCAD.

http://forum.openscad.org/file/t2477/int.png

It would be an odd choice to do as as suggested elsewhere and make it an
error to pass one argument to intersection().

It seems to me that if it's possible to make "if (false) {....}" return
nothing that this makes more sense than having it return empty geometry,
both for compatibility reasons and also because it's more useful.

I also find it more intuitively consistent with the behavior in lists where

[a,b,if(false) c]

produces [a,b] and not [a,b,<some null representation>].

nophead wrote

Yes an intersection between two operands works like that but what about an
intersection with one operand? There is no such thing in maths but the
OpenSCAD has had the convention of being a NOP in that case and so is
union
and difference.

Well, there may not be an intersection explicitly with one operand in math, but there is certainly an intersection with n operands where n may happen to be one. This is arguably the exact semantics of the intersection module in OpenSCAD. <http://forum.openscad.org/file/t2477/int.png> It would be an odd choice to do as as suggested elsewhere and make it an error to pass one argument to intersection(). It seems to me that if it's possible to make "if (false) {....}" return nothing that this makes more sense than having it return empty geometry, both for compatibility reasons and also because it's more useful. I also find it more intuitively consistent with the behavior in lists where [a,b,if(false) c] produces [a,b] and not [a,b,<some null representation>]. nophead wrote > Yes an intersection between two operands works like that but what about an > intersection with one operand? There is no such thing in maths but the > OpenSCAD has had the convention of being a NOP in that case and so is > union > and difference. -- Sent from: http://forum.openscad.org/
JB
Jordan Brown
Fri, Oct 2, 2020 9:15 PM

First, Torsten:  was the change to "if", that it would generate an empty
object instead of no object, or vice versa, or was it a change to what
intersection did with an empty object?

On 10/2/2020 9:24 AM, nop head wrote:

The problem is OpenSCAD previously had no distinction between an empty
geometry node and a missing operand.

Not true, in the general case.  An empty geometry node is a child and
has an index.  Some operators might treat an empty-geometry-node child
specially, but that's that particular operator.

difference() also seems to ignore empty groups; if you have a failing
"if" as the first argument then that first argument is ignored.  Not
that I think that's a useful semantic.

difference() {
    if (false) cube();
    sphere();
}

yields a sphere.

Offhand, for the other builtin modules you can't tell whether it
processes or ignores an empty group, since the effect is the same.

But a general module will count an empty child as a child.

So in some cases it ignored an empty geometry first operand and used
the second operand as the first. Now that has been fixed it has
changed the behaviour of if(false) in an intersection.

Also the interaction between intersection() and "for" that yields no
objects, or more complex empty constructs:

intersection() {
    cube();
    for(i=[1,2,3]) if (false) sphere();
}

yields a cube in 2019.05, and nothing in 2020.10.02.

Does it make logical sense that if(false) creates empty
geometry rather than nothing at all.

My impression - from outside the black box - is that every module
invocation, including builtins like "if", "for", and "echo", yields
exactly one geometry object.  That's a simple rule, although it can
yield some unobvious results.

There could be a different rule, so that "if" might yield either one or
zero objects, but with that rule then I would also expect that "for"
would yield any number (zero or more) objects.

if (something) {
    cube();
    sphere();
}

currently yields one object, but if a module invocation doesn't always
yield exactly one object, I'd say it should yield two objects. 

Similarly, a module that adds two objects should yield two objects.  The
only time that objects would be combined is when they are processed by a
module (like union) that combines them.

(This seems related to the "lazy union" question, but I got the
impression that that only affected when the actual rendering happened,
not how many children there are.)

Here's a related tidbit.  In both 2019.05 and 2020.10.02, this yields a
cube:

intersection() {
    cube();
    echo("hello");
}

but in 2019.05 the CSG tree has an empty group() for the echo(), and in
2020.10.02 it does not.

That kind of demonstrates the danger in trying to be clever.

module myecho(s) {
    echo(s);
}

intersection() {
    cube();
    myecho("hello");
}

is superficially similar, but in 2019.05 yields a cube (because echo
yields an empty object, and myecho yields an empty object, and
intersection ignores the empty object), and in 2020.10.02 yields nothing
(because echo yields nothing, and myecho yields an empty object, and the
intersection of an empty object with anything is the empty object.).  It
seems surprising that wrapping the echo() in a module changes the behavior.

Anyhow, again, it seems like there are two possible consistent rules:

  • Every module invocation yields exactly one object, or
  • Module invocations yield as many objects are they are defined to
    yield - zero or one for "if", zero or more for "for", zero for
    "echo", zero or more for a general module invocation (as defined for
    that particular module).

Both are consistent rules.  The "zero or more" rule is arguably more
useful, because it eliminates the need for "intersection_for", and
because it would allow for generating multiple children with a for(),
but it's not compatible with existing practice.

Net, I'd probably stick with the "exactly one" rule.

My next question is:

What should intersection() (and perhaps others) do with children that
are empty geometry?

I'd say that empty geometry, like the empty set, is a real thing and
should interact "naturally" with the various operators.

intersection() {
    cube();
    intersection() {
        translate([-10,-10,-10]) cube();
        translate([10,10,10]) cube();
    }
}

Note that the second intersection yields nothing, because the cubes do
not overlap.

This yields a cube in 2019.05 and nothing in 2020.10.02.  I'd say that
2019.05 is clearly wrong and 2020.10.02 is clearly right.

My final question is:

What should cube(0) yield?

In all versions, the answer is and in my opinion should be "a zero-sized
cube"... which is still another kind of "nothing".  Presumably

union() cube(0);

will yield an empty object indistinguishable from

union();
First, Torsten:  was the change to "if", that it would generate an empty object instead of no object, or vice versa, or was it a change to what intersection did with an empty object? On 10/2/2020 9:24 AM, nop head wrote: > The problem is OpenSCAD previously had no distinction between an empty > geometry node and a missing operand. Not true, in the general case.  An empty geometry node is a child and has an index.  Some operators might treat an empty-geometry-node child specially, but that's that particular operator. difference() also seems to ignore empty groups; if you have a failing "if" as the first argument then that first argument is ignored.  Not that I think that's a useful semantic. difference() { if (false) cube(); sphere(); } yields a sphere. Offhand, for the other builtin modules you can't tell whether it processes or ignores an empty group, since the effect is the same. But a general module will count an empty child as a child. > So in some cases it ignored an empty geometry first operand and used > the second operand as the first. Now that has been fixed it has > changed the behaviour of if(false) in an intersection. Also the interaction between intersection() and "for" that yields no objects, or more complex empty constructs: intersection() { cube(); for(i=[1,2,3]) if (false) sphere(); } yields a cube in 2019.05, and nothing in 2020.10.02. > Does it make logical sense that if(false) creates empty > geometry rather than nothing at all. My impression - from outside the black box - is that every module invocation, including builtins like "if", "for", and "echo", yields exactly one geometry object.  That's a simple rule, although it can yield some unobvious results. There could be a different rule, so that "if" might yield either one or zero objects, but with that rule then I would also expect that "for" would yield any number (zero or more) objects. if (something) { cube(); sphere(); } currently yields one object, but if a module invocation doesn't always yield exactly one object, I'd say it should yield *two* objects.  Similarly, a module that adds two objects should yield two objects.  The only time that objects would be combined is when they are processed by a module (like union) that combines them. (This seems related to the "lazy union" question, but I got the impression that that only affected when the actual rendering happened, not how many children there are.) Here's a related tidbit.  In both 2019.05 and 2020.10.02, this yields a cube: intersection() { cube(); echo("hello"); } but in 2019.05 the CSG tree has an empty group() for the echo(), and in 2020.10.02 it does not. That kind of demonstrates the danger in trying to be clever. module myecho(s) { echo(s); } intersection() { cube(); myecho("hello"); } is superficially similar, but in 2019.05 yields a cube (because echo yields an empty object, and myecho yields an empty object, and intersection ignores the empty object), and in 2020.10.02 yields nothing (because echo yields nothing, and myecho yields an empty object, and the intersection of an empty object with anything is the empty object.).  It seems surprising that wrapping the echo() in a module changes the behavior. Anyhow, again, it seems like there are two possible consistent rules: * Every module invocation yields exactly one object, or * Module invocations yield as many objects are they are defined to yield - zero or one for "if", zero or more for "for", zero for "echo", zero or more for a general module invocation (as defined for that particular module). Both are consistent rules.  The "zero or more" rule is arguably more useful, because it eliminates the need for "intersection_for", and because it would allow for generating multiple children with a for(), but it's not compatible with existing practice. Net, I'd probably stick with the "exactly one" rule. My next question is: What should intersection() (and perhaps others) do with children that are empty geometry? I'd say that empty geometry, like the empty set, is a real thing and should interact "naturally" with the various operators. intersection() { cube(); intersection() { translate([-10,-10,-10]) cube(); translate([10,10,10]) cube(); } } Note that the second intersection yields nothing, because the cubes do not overlap. This yields a cube in 2019.05 and nothing in 2020.10.02.  I'd say that 2019.05 is clearly wrong and 2020.10.02 is clearly right. My final question is: What should cube(0) yield? In all versions, the answer is and in my opinion should be "a zero-sized cube"... which is still another kind of "nothing".  Presumably union() cube(0); will yield an empty object indistinguishable from union();
JB
Jordan Brown
Fri, Oct 2, 2020 9:19 PM

On 10/2/2020 2:15 PM, Jordan Brown wrote:

First, Torsten:  was the change to "if", that it would generate an
empty object instead of no object, or vice versa, or was it a change
to what intersection did with an empty object?

Sorry, ignore that.  While writing that message I did some experiments
and went back to correct the message, but I failed to get this bit right
at the top of the message.

The answer is that it's a change to what intersection() does with an
empty object.

On 10/2/2020 2:15 PM, Jordan Brown wrote: > First, Torsten:  was the change to "if", that it would generate an > empty object instead of no object, or vice versa, or was it a change > to what intersection did with an empty object? Sorry, ignore that.  While writing that message I did some experiments and went back to correct the message, but I failed to get this bit right at the top of the message. The answer is that it's a change to what intersection() does with an empty object.
RW
Rogier Wolff
Sun, Oct 4, 2020 8:35 AM

On Fri, Oct 02, 2020 at 05:13:26PM +0100, nop head wrote:

Yes an intersection between two operands works like that but what about an
intersection with one operand? There is no such thing in maths but the
OpenSCAD has had the convention of being a NOP in that case and so is union
and difference.

I disagree with "there is no such thing... "

Lots of things are extensible.

x * y * z = x * y * z * 1

this means that x = x * 1

So multiplying a list of numbers gives just the first number if the
list has just one member.

Adding has the same mathematical property with the number being 0 this
time. Adding a list of numbers will give just the one number when the
list has length one.

It is precisely the same for the boolean AND and OR operations.

And it remains the same for the CSG AND and OR operations
(intersection resp union)

The odd one out is difference: Not all arguments to a function like
difference (numbers-math: Subtraction, division etc) are symmetric.

It is PLUS the first object, MINUS all the rest. But again there is an
item you can add to the list-of-all-the-rest that has no effect. This
time the empty object.

So again: for consistency difference ([something]) = difference
([something], <empty>) And this holds when [something] is A, B or when
[something] is only A. So again for consistency, difference (objectA)
should equal objectA.

It is not some arbitrary choice, it makes the system "work".

All this doesn't help with the orignal question: does: "if (false)
objectB" evaluate to an object of nothing (a noop for union, hull , so
noboby cares there), or nothing at all (no object).

I think there is no good mathematical background to argue either way.
But looking at the target use I can imagine people making
a cube with an optional hole with:

difference () {
cube (20, center=true);
if (needhole) cylinder (d=7, h=22, center=true);
}

Here nobody cares if the if evaluates to an empty object or no object
at all, because the empty object is the NULL (does nothing) argument
to the "difference" fucntion (in the second or any further position).

Similarly, an object might be optionally restricted by a second object:

intersection () {
cube (20, center=true);
if (needs_to_fit_in_hole) cylinder (r=10, h=22, center=true);
}

so evaluating to "<empty object>" now gives something entirely
different because it is no longer the NULL argument to the operation
being done.  A workaround would be:

bigenough=50;
intersection () {
cube (20, center=true);
if (needs_to_fit_in_hole) cylinder (r=11, h=22, center=true);
else                      cube (bigenough,center=true);
}

Imho, this clutters the code and the "evaluates to nothing" situation
is preferred: It allows conditional objects in situations where
the empty object is not the NULL operation.

Roger.

On Fri, 2 Oct 2020 at 17:11, William W Martin wwm@cox.net wrote:

On 10/2/20 8:47 AM, Torsten Paul wrote:

On 02.10.20 17:33, William W Martin wrote:

Is intersection not a logical AND operation? So what
should you expect if you say 1 AND 0 ?

That's not the point, the question is if this:

intersection() {
cube();
if (false) sphere();
}

is equal to

intersection() {
cube();
}

=> result expectation is a cube

or

intersection() {
cube();
empty-geometry
}

=> result expectation is empty geometry

ciao,
Torsten.


OpenSCAD mailing list
Discuss@lists.openscad.org
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org

All I am pointing out is that the "old" result has been wrong all along,
in spite of how many are used to it working that way. Something AND
nothing, no matter how you name "nothing", should always be nothing. Try
doing a VEN diagram...

regards,

Bill


OpenSCAD mailing list
Discuss@lists.openscad.org
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org

--
** R.E.Wolff@BitWizard.nl ** https://www.BitWizard.nl/ ** +31-15-2049110 **
**    Delftechpark 11 2628 XJ  Delft, The Netherlands.  KVK: 27239233    **
f equals m times a. When your f is steady, and your m is going down
your a is going up.  -- Chris Hadfield about flying up the space shuttle.

On Fri, Oct 02, 2020 at 05:13:26PM +0100, nop head wrote: > Yes an intersection between two operands works like that but what about an > intersection with one operand? There is no such thing in maths but the > OpenSCAD has had the convention of being a NOP in that case and so is union > and difference. I disagree with "there is no such thing... " Lots of things are extensible. x * y * z = x * y * z * 1 this means that x = x * 1 So multiplying a list of numbers gives just the first number if the list has just one member. Adding has the same mathematical property with the number being 0 this time. Adding a list of numbers will give just the one number when the list has length one. It is precisely the same for the boolean AND and OR operations. And it remains the same for the CSG AND and OR operations (intersection resp union) The odd one out is difference: Not all arguments to a function like difference (numbers-math: Subtraction, division etc) are symmetric. It is PLUS the first object, MINUS all the rest. But again there is an item you can add to the list-of-all-the-rest that has no effect. This time the empty object. So again: for consistency difference ([something]) = difference ([something], <empty>) And this holds when [something] is A, B or when [something] is only A. So again for consistency, difference (objectA) should equal objectA. It is not some arbitrary choice, it makes the system "work". All this doesn't help with the orignal question: does: "if (false) objectB" evaluate to an object of nothing (a noop for union, hull , so noboby cares there), or nothing at all (no object). I think there is no good mathematical background to argue either way. But looking at the target use I can imagine people making a cube with an optional hole with: difference () { cube (20, center=true); if (needhole) cylinder (d=7, h=22, center=true); } Here nobody cares if the if evaluates to an empty object or no object at all, because the empty object is the NULL (does nothing) argument to the "difference" fucntion (in the second or any further position). Similarly, an object might be optionally restricted by a second object: intersection () { cube (20, center=true); if (needs_to_fit_in_hole) cylinder (r=10, h=22, center=true); } so evaluating to "<empty object>" now gives something entirely different because it is no longer the NULL argument to the operation being done. A workaround would be: bigenough=50; intersection () { cube (20, center=true); if (needs_to_fit_in_hole) cylinder (r=11, h=22, center=true); else cube (bigenough,center=true); } Imho, this clutters the code and the "evaluates to nothing" situation is preferred: It allows conditional objects in situations where the empty object is not the NULL operation. Roger. > > On Fri, 2 Oct 2020 at 17:11, William W Martin <wwm@cox.net> wrote: > > > > > On 10/2/20 8:47 AM, Torsten Paul wrote: > > > On 02.10.20 17:33, William W Martin wrote: > > >> Is intersection not a logical AND operation? So what > > >> should you expect if you say 1 AND 0 ? > > > That's not the point, the question is if this: > > > > > > intersection() { > > > cube(); > > > if (false) sphere(); > > > } > > > > > > is equal to > > > > > > intersection() { > > > cube(); > > > } > > > > > > => result expectation is a cube > > > > > > or > > > > > > intersection() { > > > cube(); > > > *empty-geometry* > > > } > > > > > > => result expectation is empty geometry > > > > > > ciao, > > > Torsten. > > > > > > _______________________________________________ > > > OpenSCAD mailing list > > > Discuss@lists.openscad.org > > > http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org > > > > > All I am pointing out is that the "old" result has been wrong all along, > > in spite of how many are used to it working that way. Something AND > > nothing, no matter how you name "nothing", should always be nothing. Try > > doing a VEN diagram... > > > > regards, > > > > Bill > > > > > > _______________________________________________ > > OpenSCAD mailing list > > Discuss@lists.openscad.org > > http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org > > > _______________________________________________ > OpenSCAD mailing list > Discuss@lists.openscad.org > http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org -- ** R.E.Wolff@BitWizard.nl ** https://www.BitWizard.nl/ ** +31-15-2049110 ** ** Delftechpark 11 2628 XJ Delft, The Netherlands. KVK: 27239233 ** f equals m times a. When your f is steady, and your m is going down your a is going up. -- Chris Hadfield about flying up the space shuttle.
JB
Jordan Brown
Sun, Oct 4, 2020 5:13 PM

On 10/4/2020 1:35 AM, Rogier Wolff wrote:

intersection () {
cube (20, center=true);
if (needs_to_fit_in_hole) cylinder (r=10, h=22, center=true);
}

I'd do this as something like:

module intersection_if(flags) {
    assert(len(flags) == $children);
    intersection_for(i = [ for (j=[0:$children-1]) if (flags[j]) j]) {
        children(i);
    }
}

intersection_if([true,true,true]) {
    cube(15,center=true);
    sphere(10);
    cylinder(h=15,d=17,center=true);
}

so the argument is a vector of flags saying whether or not to include
the particular child in the intersection.


I kind of think that the "modules return zero or more objects" model
would be better overall, but it's not what we have now and would be a
significant compatibility issue.  I suspect that it would need to be a mode.

On 10/4/2020 1:35 AM, Rogier Wolff wrote: > intersection () { > cube (20, center=true); > if (needs_to_fit_in_hole) cylinder (r=10, h=22, center=true); > } I'd do this as something like: module intersection_if(flags) { assert(len(flags) == $children); intersection_for(i = [ for (j=[0:$children-1]) if (flags[j]) j]) { children(i); } } intersection_if([true,true,true]) { cube(15,center=true); sphere(10); cylinder(h=15,d=17,center=true); } so the argument is a vector of flags saying whether or not to include the particular child in the intersection. --- I kind of think that the "modules return zero or more objects" model would be better overall, but it's not what we have now and would be a significant compatibility issue.  I suspect that it would need to be a mode.
A
adrianv
Sun, Oct 4, 2020 5:41 PM

Isn't the new lazy union mode already doing the "modules return multiple
objects" behavior?  Or is there some subtle distinction here?  It at least
seems like a step in that direction, and I seem to recall there being talk
about having for() return its objects separately, and hence not needing
intersection_for.

For anybody who hasn't paid attention, I found that if I make a rounded cube
as the hull of 8 spheres it takes 9.5 seconds to preview without lazy union
and 0.2 seconds to preview with lazy union.

hull()
for(i=[-1,1], j=[-1,1], k=[-1,1])
translate([i,j,k])
sphere(r=.1,$fn=32);

This advantage is so huge I can't imagine wanting to turn it off.  If it
somehow broke my code I'd fix the code.  (Consider the case where you have
50 cubes.)

JordanBrown wrote

On 10/4/2020 1:35 AM, Rogier Wolff wrote:

intersection () {
cube (20, center=true);
if (needs_to_fit_in_hole) cylinder (r=10, h=22, center=true);
}

I'd do this as something like:

 module intersection_if(flags) {
     assert(len(flags) == $children);
     intersection_for(i = [ for (j=[0:$children-1]) if (flags[j]) j]) {
         children(i);
     }
 }

 intersection_if([true,true,true]) {
     cube(15,center=true);
     sphere(10);
     cylinder(h=15,d=17,center=true);
 }

so the argument is a vector of flags saying whether or not to include
the particular child in the intersection.


I kind of think that the "modules return zero or more objects" model
would be better overall, but it's not what we have now and would be a
significant compatibility issue.  I suspect that it would need to be a
mode.


OpenSCAD mailing list

Discuss@.openscad

Isn't the new lazy union mode already doing the "modules return multiple objects" behavior? Or is there some subtle distinction here? It at least seems like a step in that direction, and I seem to recall there being talk about having for() return its objects separately, and hence not needing intersection_for. For anybody who hasn't paid attention, I found that if I make a rounded cube as the hull of 8 spheres it takes 9.5 seconds to preview without lazy union and 0.2 seconds to preview with lazy union. hull() for(i=[-1,1], j=[-1,1], k=[-1,1]) translate([i,j,k]) sphere(r=.1,$fn=32); This advantage is so huge I can't imagine wanting to turn it off. If it somehow broke my code I'd fix the code. (Consider the case where you have 50 cubes.) JordanBrown wrote > On 10/4/2020 1:35 AM, Rogier Wolff wrote: >> intersection () { >> cube (20, center=true); >> if (needs_to_fit_in_hole) cylinder (r=10, h=22, center=true); >> } > > I'd do this as something like: > > module intersection_if(flags) { > assert(len(flags) == $children); > intersection_for(i = [ for (j=[0:$children-1]) if (flags[j]) j]) { > children(i); > } > } > > intersection_if([true,true,true]) { > cube(15,center=true); > sphere(10); > cylinder(h=15,d=17,center=true); > } > > so the argument is a vector of flags saying whether or not to include > the particular child in the intersection. > > --- > > I kind of think that the "modules return zero or more objects" model > would be better overall, but it's not what we have now and would be a > significant compatibility issue.  I suspect that it would need to be a > mode. > > > > _______________________________________________ > OpenSCAD mailing list > Discuss@.openscad > http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org -- Sent from: http://forum.openscad.org/
JB
Jordan Brown
Sun, Oct 4, 2020 6:19 PM

On 10/4/2020 10:41 AM, adrianv wrote:

Isn't the new lazy union mode already doing the "modules return multiple
objects" behavior?  Or is there some subtle distinction here?

I don't know exactly what lazy-union does, but for programmatic purposes
it does not appear to implement "modules return multiple objects".  It
produces a different CSG tree, but doesn't seem to affect the number of
objects visible to the program.

module howmany() {
    echo($children);
}

howmany() {
    for(i=[1:10])
        translate([i*10,0,0]) cube();
}

echoes "1" whether or not lazy unions are enabled.

Similarly:

module two() {
    cube();
    translate([2,0,0]) cube();
}

howmany() two();

always echoes "1".

[...] I seem to recall there being talk about having for() return its objects separately, and hence not needing intersection_for.

That would be the "modules return multiple objects" behavior - or, as I
would say it, the "modules return zero or more objects" behavior. 
Eliminating intersection_for would be one of the advantages.  Similarly,
if you had a spread() module that spaced its children out, you could say:

spread() {
    for(i=[1:10]) cube();
}

However, it would lead to a need for more explicit unions:

module spherecube() {
    sphere(10);
    cube(15, center=true);
}

spread() spherecube();

would not do what you would expect; you would need

module spherecube() {
    union() {
        sphere(10);
        cube(15, center=true);
    }
}
On 10/4/2020 10:41 AM, adrianv wrote: > Isn't the new lazy union mode already doing the "modules return multiple > objects" behavior? Or is there some subtle distinction here? I don't know exactly what lazy-union does, but for programmatic purposes it does not appear to implement "modules return multiple objects".  It produces a different CSG tree, but doesn't seem to affect the number of objects visible to the program. module howmany() { echo($children); } howmany() { for(i=[1:10]) translate([i*10,0,0]) cube(); } echoes "1" whether or not lazy unions are enabled. Similarly: module two() { cube(); translate([2,0,0]) cube(); } howmany() two(); always echoes "1". > [...] I seem to recall there being talk about having for() return its objects separately, and hence not needing intersection_for. That would be the "modules return multiple objects" behavior - or, as I would say it, the "modules return zero or more objects" behavior.  Eliminating intersection_for would be one of the advantages.  Similarly, if you had a spread() module that spaced its children out, you could say: spread() { for(i=[1:10]) cube(); } However, it would lead to a need for more explicit unions: module spherecube() { sphere(10); cube(15, center=true); } spread() spherecube(); would not do what you would expect; you would need module spherecube() {     union() {         sphere(10);         cube(15, center=true);     } }