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
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
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.
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/
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:
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();
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 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.
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.
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
--
Sent from: http://forum.openscad.org/
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);
}
}