Intersection with only one argument returns that argument. Previously a
second argument guarded by a false if was ignored but with the latest
snapshot it seems to be counted as and empty argument giving an empty
result.
intersection() {
cube();
if(false)
sphere();
}
I.e. this gives a cube with the last release but the latest snapshot gives
nothing. Again this has broken my library as I made use of it. For example
this code:
module shape()
intersection() {
hull() {
circle(boss_r);
translate([boss_r + extension - eps, 0])
square([eps, 2 * boss_r], center = true);
}
if(corner_r)
translate([boss_r + extension - corner_r, 0])
rotate(-45)
quadrant(w = 100, r = corner_r - eps, center =
true);
}
Now has to be written like this:
module shape() {
module _shape()
hull() {
circle(boss_r);
translate([boss_r + extension - eps, 0])
square([eps, 2 * boss_r], center = true);
}
if(corner_r)
intersection() {
_shape();
translate([boss_r + extension - corner_r, 0])
rotate(-45)
quadrant(w = 100, r = corner_r - eps, center =
true);
}
else
_shape();
}
Again is this an intentional change? Should a false if be treated as
nothing at all or an empty object?
I do this a lot with difference but that still works because no second
argument is the same as an empty second argument with difference.
On 02.10.20 12:37, nop head wrote:
Intersection with only one argument returns that argument.
Again is this an intentional change? Should a false if be
treated as nothing at all or an empty object?
So that's probably due to the fix for
https://github.com/openscad/openscad/issues/3312
I'm not sure there's a way to fix the bug and also keep the
behavior with if(false).
ciao,
Torsten.
I think cube(0) and a non-overlapping intersection or any other csg
operation that results in an empty object is different from a false
if, which is no object at all.
On Fri, 2 Oct 2020 at 14:08, Torsten Paul Torsten.Paul@gmx.de wrote:
On 02.10.20 12:37, nop head wrote:
Intersection with only one argument returns that argument.
Again is this an intentional change? Should a false if be
treated as nothing at all or an empty object?
So that's probably due to the fix for
https://github.com/openscad/openscad/issues/3312
I'm not sure there's a way to fix the bug and also keep the
behavior with if(false).
ciao,
Torsten.
OpenSCAD mailing list
Discuss@lists.openscad.org
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
On 02.10.20 15:17, nop head wrote:
I think cube(0) and a non-overlapping intersection or any
other csg operation that results in an empty object is
different from a false if, which is no object at all.
Right, it sounds sensible that if(false) would remove the
object completely. I guess we have to see if that would
affect anything where the if is used as first part of a
difference().
ciao,
Torsten.
Is intersection not a logical AND operation? So what should you expect
if you say 1 AND 0 ?
On 10/2/20 6:07 AM, Torsten Paul wrote:
On 02.10.20 12:37, nop head wrote:
Intersection with only one argument returns that argument.
Again is this an intentional change? Should a false if be
treated as nothing at all or an empty object?
So that's probably due to the fix for
https://github.com/openscad/openscad/issues/3312
I'm not sure there's a way to fix the bug and also keep the
behavior with if(false).
ciao,
Torsten.
OpenSCAD mailing list
Discuss@lists.openscad.org
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
If the second operand is empty geometry I expect an empty result.
If the second object is missing I expect the first object, as it has always
done, just as difference() with one child. Intersection still does that.
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().
On Fri, 2 Oct 2020 at 16:33, William W Martin wwm@cox.net wrote:
Is intersection not a logical AND operation? So what should you expect
if you say 1 AND 0 ?
On 10/2/20 6:07 AM, Torsten Paul wrote:
On 02.10.20 12:37, nop head wrote:
Intersection with only one argument returns that argument.
Again is this an intentional change? Should a false if be
treated as nothing at all or an empty object?
So that's probably due to the fix for
https://github.com/openscad/openscad/issues/3312
I'm not sure there's a way to fix the bug and also keep the
behavior with if(false).
ciao,
Torsten.
OpenSCAD mailing list
Discuss@lists.openscad.org
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
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.
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
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
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.
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
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);
}
}
As explicit unions can always be added, but unioned objects can't be easily
unioned, it is definitely a more flexible solution. Breaking compatibility
is the only good reason to ever disable it
On Sun, 4 Oct 2020, 14:19 Jordan Brown, openscad@jordan.maileater.net
wrote:
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);
}
}
OpenSCAD mailing list
Discuss@lists.openscad.org
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
Here's the forum post about lazy unions:
http://forum.openscad.org/quot-Lazy-union-quot-is-now-experimental-in-snapshots-td27991.html
I have to admit I don't quite understand the workings of the change because
it seems like a half-way change where sometimes objects are passed
separately and sometimes not. As you note, if you write your own module a
union gets applied. But if you use difference() or intersection() applied
to a for() you get the behavior of individual objects without a union. (So
intersection_for is already unnecessary with this change.)
Try this one with and without lazy union:
difference() {
for(i=[-1,1], j=[-1,1], k=[-1,1])
translate([i,j,k])
sphere(r=.1,$fn=32);
translate([1,1,1])sphere(r=.15,$fn=32);
}
--
Sent from: http://forum.openscad.org/
On 04.10.2020 19:41, adrianv wrote:
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);
There is really no need for a union in this case, so "lazy union" looks
like a fancy name for no implicit union. The difference between 9.5
seconds and 0.2 seconds is an unnecessary union vs. no union. You are
essentially creating an array of spheres and computing the convex hull
from them directly instead of first unioning them and then computing the
hull, much like
https://gist.github.com/arnholm/eda3f740da4f101421a8141b2446a4df
Carsten Arnholm
On 10/4/2020 11:51 AM, adrianv wrote:
I have to admit I don't quite understand the workings of the change because
it seems like a half-way change where sometimes objects are passed
separately and sometimes not.
Ugh. And a particular module (say, "for") sometimes yields multiple
objects and sometimes does not. It appears to yield multiple objects
when its parent is "intersection", but a single object when its parent
is a user-defined module.
I like either answer better than an (apparently) inconsistent answer.
Based on the quoted forum post it sounded like this was an initial step with
the intent of eliminating the implicit union everywhere, so an inconsistent
intermediate state with a consistent destination.
JordanBrown wrote
On 10/4/2020 11:51 AM, adrianv wrote:
I have to admit I don't quite understand the workings of the change
because
it seems like a half-way change where sometimes objects are passed
separately and sometimes not.
Ugh. And a particular module (say, "for") sometimes yields multiple
objects and sometimes does not. It appears to yield multiple objects
when its parent is "intersection", but a single object when its parent
is a user-defined module.
I like either answer better than an (apparently) inconsistent answer.
OpenSCAD mailing list
Discuss@.openscad
--
Sent from: http://forum.openscad.org/
A place where lazy union breaks my code is with an intersection again. If
you have a for loop as the second argument it changes the result because
each interaction of the for makes a new argument.
color(gold)
linear_extrude(size.z)
intersection() {
square([size.x, size.y], center = true);
for(end = [-1, 1])
translate([end * size.x / 2, 0])
ring(or = r, ir = r / 2);
}
Easy to fix by adding explicit union() but not easy to spot without
regression tests.
On Sun, 4 Oct 2020 at 22:06, adrianv avm4@cornell.edu wrote:
Based on the quoted forum post it sounded like this was an initial step
with
the intent of eliminating the implicit union everywhere, so an inconsistent
intermediate state with a consistent destination.
JordanBrown wrote
On 10/4/2020 11:51 AM, adrianv wrote:
I have to admit I don't quite understand the workings of the change
because
it seems like a half-way change where sometimes objects are passed
separately and sometimes not.
Ugh. And a particular module (say, "for") sometimes yields multiple
objects and sometimes does not. It appears to yield multiple objects
when its parent is "intersection", but a single object when its parent
is a user-defined module.
I like either answer better than an (apparently) inconsistent answer.
OpenSCAD mailing list
Discuss@.openscad
--
Sent from: http://forum.openscad.org/
OpenSCAD mailing list
Discuss@lists.openscad.org
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
On 05.10.20 11:09, nop head wrote:
A place where lazy union breaks my code is with an
intersection again. If you have a for loop as the second
argument it changes the result because each interaction
of the for makes a new argument.
I think that relates to the question how to handle
$children. I would assume this needs to be calculated
in a way so the behavior does not change depending on
the number of child objects returned.
ciao,
Torsten.
Lazy union also breaks difference when the first argument is a for loop.
Anything after the first iteration gets subtracted, so another case where
explicit union is needed. As far as I can tell only two unions() needed
adding to my library to fix it.
Speed doesn't seem much different, two seconds slower for a 20 minute
build, which is just noise. Some things get faster and some slower.
On Mon, 5 Oct 2020 at 13:13, Torsten Paul Torsten.Paul@gmx.de wrote:
On 05.10.20 11:09, nop head wrote:
A place where lazy union breaks my code is with an
intersection again. If you have a for loop as the second
argument it changes the result because each interaction
of the for makes a new argument.
I think that relates to the question how to handle
$children. I would assume this needs to be calculated
in a way so the behavior does not change depending on
the number of child objects returned.
ciao,
Torsten.
OpenSCAD mailing list
Discuss@lists.openscad.org
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
On 05.10.20 15:20, nop head wrote:
Lazy union also breaks difference when the first argument
is a for loop. Anything after the first iteration gets
subtracted, so another case where explicit union is needed.
As far as I can tell only two unions() needed adding to my
library to fix it.
My preference would be to have it unchanged in behavior, but
maybe with deprecation warnings where needed.
As already mentioned, I think the only way to handle those
cases is to keep the implicit union / child() selection based
on static "slots". So if the first "slot" of the union is
a for(), it will still do the union, or if that "slot" is
accessed via child() then that will return the list of objects
that for() generated.
Speed doesn't seem much different, two seconds slower
for a 20 minute build, which is just noise. Some things
get faster and some slower.
If I remember correctly, it's not yet available in all places
so it might not reach the full potential. At this point it's
probably affecting mostly cases where the top level union is
the big slow operation.
ciao,
Torsten.
My tests are all preview mode, so there is no top level union. Union is
free in OpenCSG anyway. It might speed up STL generation but I don't do
that inside the library.
Also a lot of my code is optimised to avoid unions inside hulls, etc. For
the cube example above I would make a nested module that placed the sphere
at a specified x,y,x and then call it 8 times to avoid a for loop.
On Mon, 5 Oct 2020 at 14:53, Torsten Paul Torsten.Paul@gmx.de wrote:
On 05.10.20 15:20, nop head wrote:
Lazy union also breaks difference when the first argument
is a for loop. Anything after the first iteration gets
subtracted, so another case where explicit union is needed.
As far as I can tell only two unions() needed adding to my
library to fix it.
My preference would be to have it unchanged in behavior, but
maybe with deprecation warnings where needed.
As already mentioned, I think the only way to handle those
cases is to keep the implicit union / child() selection based
on static "slots". So if the first "slot" of the union is
a for(), it will still do the union, or if that "slot" is
accessed via child() then that will return the list of objects
that for() generated.
Speed doesn't seem much different, two seconds slower
for a 20 minute build, which is just noise. Some things
get faster and some slower.
If I remember correctly, it's not yet available in all places
so it might not reach the full potential. At this point it's
probably affecting mostly cases where the top level union is
the big slow operation.
ciao,
Torsten.
OpenSCAD mailing list
Discuss@lists.openscad.org
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
On Mon, Oct 05, 2020 at 03:53:09PM +0200, Torsten Paul wrote:
On 05.10.20 15:20, nop head wrote:
Lazy union also breaks difference when the first argument
is a for loop. Anything after the first iteration gets
subtracted, so another case where explicit union is needed.
As far as I can tell only two unions() needed adding to my
library to fix it.
My preference would be to have it unchanged in behavior, but
maybe with deprecation warnings where needed.
As already mentioned, I think the only way to handle those
cases is to keep the implicit union / child() selection based
on static "slots". So if the first "slot" of the union is
a for(), it will still do the union, or if that "slot" is
accessed via child() then that will return the list of objects
that for() generated.
This IMHO would be complicated: Complicated to do in the code,
complicated to explain to the users.
In Unix the filosophy is that you implement what's an easy-to-use
building block and then allow the users to expand using the building
blocks provided.
So when you get to chose what "printf" does, you can say: most people
will want the trailing \n, so we'll tack it on for them. Not the right
choice: supplying the \n in the format string is simple and easy, but
the other way around is not: If your printf adds the \n at the end
it is NOT easy to make a printf-without-\n out of that.
So IMHO, a "for" primitive in openscad should NOT do an implicit union
and return one object. It's easy to union the objects with an explicit
union () in front of the for. But not the other way around.
And doing it this way can even be funny to use.
Consider such a puzzle.
https://cdn.shopify.com/s/files/1/0181/8685/products/Crystal_Puzzle.jpg?v=1571438517
(now in this specific puzzle, I'm not sure if the actual puzzle is
equivalent to what I'm suggesting).
If I create a module that returns block number i in the position in
the final puzzle, in the order that the puzzle is assembled.....
(but without the cutouts for to-be-assembled blocks).
for (i=[0:1:NBLOCKS])
difference ()
for (j=[i:1:NBLOCKS])
block(j);
This code is simplified when for does not implicitly union the objects
that for returns. In the current situation it has to be something like:
for (i=[0:1:NBLOCKS])
difference () {
block (i); // this one is the one we're building
for (j=[i+1:1:NBLOCKS]) // but there have to be cutouts for all the future blocks.
block(j);
}
with the for-endpoint-smaller-than-start issue we've discussed
before..... (but for clarity writing it the second way might be a
good idea anyway. I'm torn between the two: I'm a proponent of less
code is good.)
Roger.
--
** 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 05.10.20 17:08, Rogier Wolff wrote:
As already mentioned, I think the only way to handle those
cases is to keep the implicit union / child() selection based
on static "slots". So if the first "slot" of the union is
a for(), it will still do the union, or if that "slot" is
accessed via child() then that will return the list of objects
that for() generated.
This IMHO would be complicated: Complicated to do in the code,
complicated to explain to the users.
So far this is the only solution that actually allows
computation of the needed values. That it's currently
a bit awkward to return data structures is a totally
different topic.
https://github.com/openscad/openscad/issues/3143
I'm open to other suggestions which do solve the issue.
ciao,
Torsten.
On 10/5/2020 6:53 AM, Torsten Paul wrote:
As already mentioned, I think the only way to handle those cases is to
keep the implicit union / child() selection based on static "slots".
So if the first "slot" of the union is a for(), it will still do the
union, or if that "slot" is accessed via child() then that will return
the list of objects that for() generated.
I think what you're describing would make "implicit union" be an
implementation detail, something that is invisible to the program. Right?
Yes - I think there should be two mostly-separate questions:
(1) When does the geometry engine actually do the unions? I'm not sure
because I don't know beans about the internals, but I don't think this
needs to interact with the user program at all. I suspect that it
could be done while walking the geometry tree to render the final result.
I'm assuming here that the program generates the CSG tree that we
see in Design/Display CSG tree, and then one of the two geometry
engines walks the tree to build the final result. Is that a
reasonably close model?
Note that this behavior could even extend up through explicit
"union" invocations. If there's some reason why union(A,B,C,D) is
better than union(union(A,B),union(C,D)) the geometry engine should
be free to rearrange the expression.
(2) When a module - builtin or otherwise - creates multiple objects,
does its parent see one object, or several? (And the degenerate case of
zero.) This is all on the front end.
(1) should be totally invisible to the program, totally compatible.
(2) is not at all compatible.
On 10/5/2020 8:08 AM, Rogier Wolff wrote:
So when you get to chose what "printf" does, you can say: most people
will want the trailing \n, so we'll tack it on for them. Not the right
choice: supplying the \n in the format string is simple and easy, but
the other way around is not: If your printf adds the \n at the end it
is NOT easy to make a printf-without-\n out of that.
If it wasn't for the huge number of bugs where programs forget to
include a \n, or include one where it's not appropriate, I'd agree with
you. But people get this wrong all the time. I think it would have
been better to have a function that prints a line (with line
termination, whatever that is) and an otherwise similar function that
prints a partial line without termination. That's what Java did with
print() and println(). I have to bet that Java programs have many fewer
of that class of bugs than C programs, because Java programmers have to
make an explicit and obvious choice. I think Java got it slightly
wrong, by having "print" be the less common form; I think BASIC and sh
got it right by having the printing statement (print, echo) default to
printing a line, and optionally not terminate it.
I think the best strategy is to have the usual case be easy and obvious,
and the advanced case be possible.
On Mon, Oct 05, 2020 at 04:48:26PM +0000, Jordan Brown wrote:
On 10/5/2020 8:08 AM, Rogier Wolff wrote:
So when you get to chose what "printf" does, you can say: most people
will want the trailing \n, so we'll tack it on for them. Not the right
choice: supplying the \n in the format string is simple and easy, but
the other way around is not: If your printf adds the \n at the end it
is NOT easy to make a printf-without-\n out of that.
If it wasn't for the huge number of bugs where programs forget to
include a \n, or include one where it's not appropriate, I'd agree with
you. But people get this wrong all the time. I think it would have
been better to have a function that prints a line (with line
termination, whatever that is) and an otherwise similar function that
prints a partial line without termination. That's what Java did with
print() and println(). I have to bet that Java programs have many fewer
of that class of bugs than C programs, because Java programmers have to
make an explicit and obvious choice. I think Java got it slightly
wrong, by having "print" be the less common form; I think BASIC and sh
got it right by having the printing statement (print, echo) default to
printing a line, and optionally not terminate it.
I think the best strategy is to have the usual case be easy and obvious,
and the advanced case be possible.
In my example of a library function, you can easily provide two variants.
In the case of how to define a language construct you have the restriction
that you have to make ONE choice. That translates back to the library
function example with a restriction: "You are only allowed to provide ONE
function".
In the case of print/println you should then go for "print" as you can
easily define
println (somestring)
{
print (somestring);
print(NEWLINE);
}
in almost all languages and you can (must under the restriction that
the library is only allowed one function) leave that to the user.
With this: "what would we want?" discussion I don't think I can
influence openscad to change to do things right: That would lose
compatibility with older programs, and that's not acceptable. But the
realization: "we should've done it differently is" important: it might
help "design" future modules/operations.
Roger.
--
** 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.
With regards to speed improvements, the one big case I am aware of is calling
hull() on a for loop, the example I posted above. What are other cases
where unnecessary union gets forced, leading to unnecessarily long run
times?
I noticed nophead said he avoids the problem of for() loops in hull() by
unwrapping for() loops. I'll note that this is (1) not always possible and
(2) ugly and annoying to code. But it seems a necessary workaround (when
possible) in the current stable version because of the performance hit. I
have a code that generates polyhedra and I'd rather not unroll the loops for
all of the polyhedra: it would make the code huge, incomprehensible and
unmaintainable. And I'd have to write code to do the unrolling and write
out the unrolled scad code. Of course, another strategy is to avoid
constructing objects with hull(), which I think is a method nophead also
used: he has described the speed up he achieved by recoding his models to
perform all the difficult operations in 2d and avoid 3d operations. One
could argue this is sort of like my approach to making rounded solids: avoid
minkowski and compute the rounded solids directly so I can use polyhedron.
It would be nice if we didn't have to work around limitations this way.
It seems like there are a couple strategies that could be deployed to avoid
breaking compatibility. One would be to introduce a new for loop, call it
split_for() that performs for() without the implicit union. If this was
combined with split_children() to pass the children without a union I think
it would provide the full flexibility to pass objects separately or the old
way. Or I suppose an option flag like for(i=[0:N], union=false) could also
do the job.
Another strategy (that might be impractical to implement---I don't know)
would be to have a second mechanism to access children that accesses the
children without the union. This seems harder to understand and use.
tp3 wrote
On 05.10.20 15:20, nop head wrote:
Lazy union also breaks difference when the first argument
is a for loop. Anything after the first iteration gets
subtracted, so another case where explicit union is needed.
As far as I can tell only two unions() needed adding to my
library to fix it.
My preference would be to have it unchanged in behavior, but
maybe with deprecation warnings where needed.
As already mentioned, I think the only way to handle those
cases is to keep the implicit union / child() selection based
on static "slots". So if the first "slot" of the union is
a for(), it will still do the union, or if that "slot" is
accessed via child() then that will return the list of objects
that for() generated.
Speed doesn't seem much different, two seconds slower
for a 20 minute build, which is just noise. Some things
get faster and some slower.
If I remember correctly, it's not yet available in all places
so it might not reach the full potential. At this point it's
probably affecting mostly cases where the top level union is
the big slow operation.
--
Sent from: http://forum.openscad.org/
There is a fundamental problem with children and for loops that Torsten
linked to above though. If the child is a for loop that isn't unioned then
$children can't be evaluated until the loop has run but with dynamic scoped
variables the loop count might depend on variables in the parent. So you
get a chicken and egg problem.
On Mon, 5 Oct 2020 at 21:36, adrianv avm4@cornell.edu wrote:
With regards to speed improvements, the one big case I am aware of is
calling
hull() on a for loop, the example I posted above. What are other cases
where unnecessary union gets forced, leading to unnecessarily long run
times?
I noticed nophead said he avoids the problem of for() loops in hull() by
unwrapping for() loops. I'll note that this is (1) not always possible and
(2) ugly and annoying to code. But it seems a necessary workaround (when
possible) in the current stable version because of the performance hit. I
have a code that generates polyhedra and I'd rather not unroll the loops
for
all of the polyhedra: it would make the code huge, incomprehensible and
unmaintainable. And I'd have to write code to do the unrolling and write
out the unrolled scad code. Of course, another strategy is to avoid
constructing objects with hull(), which I think is a method nophead also
used: he has described the speed up he achieved by recoding his models to
perform all the difficult operations in 2d and avoid 3d operations. One
could argue this is sort of like my approach to making rounded solids:
avoid
minkowski and compute the rounded solids directly so I can use polyhedron.
It would be nice if we didn't have to work around limitations this way.
It seems like there are a couple strategies that could be deployed to avoid
breaking compatibility. One would be to introduce a new for loop, call it
split_for() that performs for() without the implicit union. If this was
combined with split_children() to pass the children without a union I think
it would provide the full flexibility to pass objects separately or the old
way. Or I suppose an option flag like for(i=[0:N], union=false) could
also
do the job.
Another strategy (that might be impractical to implement---I don't know)
would be to have a second mechanism to access children that accesses the
children without the union. This seems harder to understand and use.
tp3 wrote
On 05.10.20 15:20, nop head wrote:
Lazy union also breaks difference when the first argument
is a for loop. Anything after the first iteration gets
subtracted, so another case where explicit union is needed.
As far as I can tell only two unions() needed adding to my
library to fix it.
My preference would be to have it unchanged in behavior, but
maybe with deprecation warnings where needed.
As already mentioned, I think the only way to handle those
cases is to keep the implicit union / child() selection based
on static "slots". So if the first "slot" of the union is
a for(), it will still do the union, or if that "slot" is
accessed via child() then that will return the list of objects
that for() generated.
Speed doesn't seem much different, two seconds slower
for a 20 minute build, which is just noise. Some things
get faster and some slower.
If I remember correctly, it's not yet available in all places
so it might not reach the full potential. At this point it's
probably affecting mostly cases where the top level union is
the big slow operation.
--
Sent from: http://forum.openscad.org/
OpenSCAD mailing list
Discuss@lists.openscad.org
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
On 10/5/2020 11:47 AM, Rogier Wolff wrote:
In the case of how to define a language construct you have the
restriction that you have to make ONE choice. That translates back to
the library function example with a restriction: "You are only allowed
to provide ONE function".
You could do as some other languages (BASIC and sh come to mind) and
have "don't terminate the line" be an option on the print operation.
That is, it could be
print(..., line={true|false})
where the default is true. Or something like that.
In BASIC, a PRINT statement normally terminates the line. When
terminated with a semicolon, it ends without terminating the line.
In UNIX shells, "echo" normally terminates the line. In
Berkeley-derived shells, "echo -n" does not terminate the line; in
AT&T-derived shells a "\c" at the end prevents it from terminating
the line.
With this: "what would we want?" discussion I don't think I can
influence openscad to change to do things right: That would lose
compatibility with older programs, and that's not acceptable. But the
realization: "we should've done it differently is" important: it might
help "design" future modules/operations.
Yes. And in particular a hypothetical printf-like module. (Which I
will try to throw together, but it'll be stretching my C++ skills.)
On 10/5/2020 2:04 PM, nop head wrote:
There is a fundamental problem with children and for loops that
Torsten linked to above though. If the child is a for loop that isn't
unioned then $children can't be evaluated until the loop has run but
with dynamic scoped variables the loop count might depend on variables
in the parent. So you get a chicken and egg problem.
That's a good point, and a problem with any scheme where a module can
return other than one child - that children aren't executed until they
are referred to. The same problem would apply to an "failing if yields
nothing" scheme.
I don't think - and as usual I'm speaking as somebody who only has a
mental model of how this stuff works, not actual experience with the
guts - that this necessarily affects a lazy union mechanism. A lazy
union mechanism could walk the geometry tree accumulating lists of
objects that need to eventually be unioned, and do the actual union only
when needed for that particular context.
Making mistakes and fixing them is part of programming. But I would
prefer a syntax that is easy to read over one that is not. I really dislike
Print(a)
Print(b)
Print(c)
PrintLn()
vs
PrintLn(a, b, c, d)
or
Print(a, b, c, d, \n)
If one cannot find and fix a missing \n, then one probably should find a
hobby (or career) other than programming
IMHO, of course. Goes without saying
On 10/5/2020 12:48 PM, Jordan Brown wrote:
On 10/5/2020 8:08 AM, Rogier Wolff wrote:
So when you get to chose what "printf" does, you can say: most people
will want the trailing \n, so we'll tack it on for them. Not the
right choice: supplying the \n in the format string is simple and
easy, but the other way around is not: If your printf adds the \n at
the end it is NOT easy to make a printf-without-\n out of that.
If it wasn't for the huge number of bugs where programs forget to
include a \n, or include one where it's not appropriate, I'd agree
with you. But people get this wrong all the time. I think it would
have been better to have a function that prints a line (with line
termination, whatever that is) and an otherwise similar function that
prints a partial line without termination. That's what Java did with
print() and println(). I have to bet that Java programs have many
fewer of that class of bugs than C programs, because Java programmers
have to make an explicit and obvious choice. I think Java got it
slightly wrong, by having "print" be the less common form; I think
BASIC and sh got it right by having the printing statement (print,
echo) default to printing a line, and optionally not terminate it.
I think the best strategy is to have the usual case be easy and
obvious, and the advanced case be possible.
OpenSCAD mailing list
Discuss@lists.openscad.org
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org