Don't perform a union in the following cases:
for loops: A for loop will create a list of separate object passed on to the parent node (or the root)children(). A children() statement will pass on the list of children to the parent nodeSee also https://github.com/openscad/openscad/wiki/OEP2:-Implicit-Unions.
The code lives in https://github.com/openscad/openscad/tree/issue350
This feature is currently experimental. To enabled it, _lazy unions_ must be enabled in Preferences or the cmd-line (--enable=lazy-union).
Exporting top-level object to STL/OFF/AMF/DXF/SVG will attempt to keep the objects separate as far as the used file format supports it.
TODO:
for() and children())intersection() for()hull() for()minkowski() for()difference() for()children()intersection_for: To combine this with the experimental feature, we need to defer builtin initialization until after preferences has been read => no need to combine it with experimental for now, deprecate intersection_for after we move out of the experimental stageThis has been discussed a bit, and is interesting for a number of reasons:
Further work past this issue:
Want to back this issue? Post a bounty on it! We accept bounties via Bountysource.
Some quick experiments using the design below, show very promising results:
num=20;
for (i=[0:num-1])
rotate([0,0,i*360/num])
translate([40,0,0])
rotate([0,i*90,0])
scale([0.7,1,1])
rotate_extrude() translate([10,0,0]) circle(r=2);
..and with new implementation (May 7th 2014): 2 seconds @ 50 MB
Some printer software (MakerWare I think) supports multiple materials only through loading separate STL files for each material. To facilitate this workflow, we could add an option to STL export to export each top-level object as a separate STL, indexed or named according to the color() of each object.
Eliminating the top level union would be a great feature by itself as far as i am concerned. It would be nice to see just this feature in the development releases soon and add features on top of this later.
Aside from the separate stl's per top level object, one feature we may want is collision detection between top level objects, for instance if the chains are mis-aligned and touch, i'd like to know. If this is a costly calculation (i can't imagine it is) it could be made optional.
I agree that we should get the minimal feature set into master - it's still a bit of work to make this robust though..
We don't have any code for doing collision detection yet, but that's an interesting feature request.
Collision detection is still an extra feature, since intersecting objects are sometimes OK since slicing software might handle it well (e.g. Slic3r does).
Some thoughts about syntax:
ununion(){ ... omit explicit union of these elements (childs)...);
ununion in F5 is treated as a normal union
ununion in F6 starts the compile for every child and just appends the
result to the list of nerf-polygons
ununion() can only be used on 'root' level
ununion can be used more than ones in a file
ununion can be used inside if/else statements
ununion can not be used inside of for-loops, but a for-loop can be used as
'argument' of ununion
no testing for non-two manifold?
Sincerely,
TakeItAndRun
On 8 May 2013 10:22, Marius Kintel [email protected] wrote:
I agree that we should get the minimal feature set into master - it's
still a bit of work to make this robust though..We don't have any code for doing collision detection yet, but that's an
interesting feature request.
Collision detection is still an extra feature, since intersecting objects
are sometimes OK since slicing software might handle it well (e.g. Slic3r
does).—
Reply to this email directly or view it on GitHubhttps://github.com/openscad/openscad/issues/350#issuecomment-17592506
.
[email protected] [email protected]
P.S. Falls meine E-Mail kürzer ausfällt als Dir angenehm ist:
Ich probiere gerade aus kurze Antworten statt gar keine Antworten zu
schreiben.
Wenn Du gerne mehr lesen möchtest, dann lass es mich bitte wissen.
P.S. In case my e-mail is shorter than you enjoy:
I am currently trying short replies instead of no replies at all.
Please let me know, if you like to read more.
Enjoy!
I imagine that this ununion() module is so as not to break backwards compatibility and have to explicitly declare not unioning the objects. Can the group() module be cleverly used for this purpose in place of ununion() its a horrible name for a module an "unun" was a bit clumsy to type correctly.
I think group should do that anywhere in the tree. For example I group
logically separate things so they can be translated together, but they are
separate objects so an explicit union is just a waste of processing.
On 8 May 2013 13:25, Giles Bathgate [email protected] wrote:
I imagine that this ununion() module is so as not to break backwards
compatibility and have to explicitly declare not unioning the objects. Can
the group() module be cleverly used for this purpose in place of ununion()
its a horrible name for a module an "unun" was a bit clumsy to type
correctly.—
Reply to this email directly or view it on GitHubhttps://github.com/openscad/openscad/issues/350#issuecomment-17602135
.
Sorry typo:
I think group should do that anywhere in the tree. For example I group
logically separate things so they can be translated together, but they are
separate objects so an *implicit *union is just a waste of processing
.
On 8 May 2013 13:33, nop head nop.[email protected] wrote:
I think group should do that anywhere in the tree. For example I group
logically separate things so they can be translated together, but they are
separate objects so an explicit union is just a waste of processing.On 8 May 2013 13:25, Giles Bathgate [email protected] wrote:
I imagine that this ununion() module is so as not to break backwards
compatibility and have to explicitly declare not unioning the objects. Can
the group() module be cleverly used for this purpose in place of ununion()
its a horrible name for a module an "unun" was a bit clumsy to type
correctly.—
Reply to this email directly or view it on GitHubhttps://github.com/openscad/openscad/issues/350#issuecomment-17602135
.
@nophead group() is currently just an alias for union(), so you might think you are saving processing, but actually its not doing anything for you until group is implemented properly. The proposed hack is to not union things implicitly at the top level, but @TakeItAndRun seems to think we should have an ununion() module?! I was just pointing out that group() might be a more appropriate choice, since group() seems to be, by everyone's understanding, not unioned. break_appart() , ungroup() or deunion() would also be ok, but ununununion() wouldn't.
Another point, there is an implicit union() inside for loops, leading to the need for intersection_for() {} in place of: intersection() for() { } which is more generic. I showed Marius how I had implemented this in RapCAD, and he said he would consider it. This might be a good time to consider it.
Regards,
Giles
Giles ideas match mine exactly. intersection_for() is a hack made to avoid having to refactor internal workings and will be deprecatable once group() is implemented properly.
On 7 May 2013 17:49, Marius Kintel [email protected] wrote:
Current OpenSCAD: 10m38s @ 385 MB
Total rendering time: 0 hours, 2 minutes, 30 seconds
Hmmm either I do something wrong, or you would benefit from a system
upgrade.
Using SSD here... not sure how much that helps
Torsten
On 2013-05-08, at 16:32 , torwag wrote:
On 7 May 2013 17:49, Marius Kintel [email protected] wrote:
Current OpenSCAD: 10m38s @ 385 MB
Total rendering time: 0 hours, 2 minutes, 30 seconds
Hmmm either I do something wrong, or you would benefit from a system
upgrade.Hm, my computer does feel slow, but it shouldn't be that bad (2.6 GHz i7) - perhaps I need a reinstall…
Hopefully, the speed improvement ratio will stay similar though :)
-Marius
just as a reference:
Intel(R) Xeon(R) CPU E3-1225 V2 @ 3.20GHz
32GB of RAM
256GB SSD
NVIDIA Quadro 600
Arch Linux
Torsten
On 8 May 2013 16:38, Marius Kintel [email protected] wrote:
On 2013-05-08, at 16:32 , torwag wrote:
On 7 May 2013 17:49, Marius Kintel [email protected] wrote:
Current OpenSCAD: 10m38s @ 385 MB
Total rendering time: 0 hours, 2 minutes, 30 seconds
Hmmm either I do something wrong, or you would benefit from a system
upgrade.Hm, my computer does feel slow, but it shouldn't be that bad (2.6 GHz i7)
- perhaps I need a reinstall…
Hopefully, the speed improvement ratio will stay similar though :)-Marius
—
Reply to this email directly or view it on GitHubhttps://github.com/openscad/openscad/issues/350#issuecomment-17610022
.
Off topic:
Hi torwag,
would you be so nice and run the attached program and let me know how long
this will take on your machine.(F5 and/or F6)
Thanks,
TakeItAndRun
On 8 May 2013 16:53, torwag [email protected] wrote:
just as a reference:
Intel(R) Xeon(R) CPU E3-1225 V2 @ 3.20GHz
32GB of RAM
256GB SSD
NVIDIA Quadro 600Arch Linux
Torsten
On 8 May 2013 16:38, Marius Kintel [email protected] wrote:
On 2013-05-08, at 16:32 , torwag wrote:
On 7 May 2013 17:49, Marius Kintel [email protected] wrote:
Current OpenSCAD: 10m38s @ 385 MB
Total rendering time: 0 hours, 2 minutes, 30 seconds
Hmmm either I do something wrong, or you would benefit from a system
upgrade.Hm, my computer does feel slow, but it shouldn't be that bad (2.6 GHz
i7)
- perhaps I need a reinstall…
Hopefully, the speed improvement ratio will stay similar though :)-Marius
—
Reply to this email directly or view it on GitHub<
https://github.com/openscad/openscad/issues/350#issuecomment-17610022>
.—
Reply to this email directly or view it on GitHubhttps://github.com/openscad/openscad/issues/350#issuecomment-17610978
.
[email protected] [email protected]
P.S. Falls meine E-Mail kürzer ausfällt als Dir angenehm ist:
Ich probiere gerade aus kurze Antworten statt gar keine Antworten zu
schreiben.
Wenn Du gerne mehr lesen möchtest, dann lass es mich bitte wissen.
P.S. In case my e-mail is shorter than you enjoy:
I am currently trying short replies instead of no replies at all.
Please let me know, if you like to read more.
Enjoy!
Silly me, I intended to append the code!
Sorry,
TakeItAndRun
On 8 May 2013 17:53, Peter Falke [email protected]:
Off topic:
Hi torwag,
would you be so nice and run the attached program and let me know how long
this will take on your machine.(F5 and/or F6)Thanks,
TakeItAndRun
On 8 May 2013 16:53, torwag [email protected] wrote:
just as a reference:
Intel(R) Xeon(R) CPU E3-1225 V2 @ 3.20GHz
32GB of RAM
256GB SSD
NVIDIA Quadro 600Arch Linux
Torsten
On 8 May 2013 16:38, Marius Kintel [email protected] wrote:
On 2013-05-08, at 16:32 , torwag wrote:
On 7 May 2013 17:49, Marius Kintel [email protected] wrote:
Current OpenSCAD: 10m38s @ 385 MB
Total rendering time: 0 hours, 2 minutes, 30 seconds
Hmmm either I do something wrong, or you would benefit from a system
upgrade.Hm, my computer does feel slow, but it shouldn't be that bad (2.6 GHz
i7)
- perhaps I need a reinstall…
Hopefully, the speed improvement ratio will stay similar though :)-Marius
—
Reply to this email directly or view it on GitHub<
https://github.com/openscad/openscad/issues/350#issuecomment-17610022>
.—
Reply to this email directly or view it on GitHubhttps://github.com/openscad/openscad/issues/350#issuecomment-17610978
.
[email protected] [email protected]
P.S. Falls meine E-Mail kürzer ausfällt als Dir angenehm ist:
Ich probiere gerade aus kurze Antworten statt gar keine Antworten zu
schreiben.
Wenn Du gerne mehr lesen möchtest, dann lass es mich bitte wissen.P.S. In case my e-mail is shorter than you enjoy:
I am currently trying short replies instead of no replies at all.
Please let me know, if you like to read more.Enjoy!
[email protected] [email protected]
P.S. Falls meine E-Mail kürzer ausfällt als Dir angenehm ist:
Ich probiere gerade aus kurze Antworten statt gar keine Antworten zu
schreiben.
Wenn Du gerne mehr lesen möchtest, dann lass es mich bitte wissen.
P.S. In case my e-mail is shorter than you enjoy:
I am currently trying short replies instead of no replies at all.
Please let me know, if you like to read more.
Enjoy!
Collision detection would be useful (currently I use #of volumes after F6 to check this). I assume detecting a collision is the same computational burdon as a union (though if a shortcut is available, that would be great). Perhaps put in an F7 render where it also detects collisions, and takes longer. Would be especially good if it could color the colliding objects.
I agree - collision detection would be cool. Since we only need detection, not evaluation, perhaps the clipper library could be utilized for this.
You could perform an intersection between the projections of the objects onto the ground, but that would detect many false positives. Not sure how you would use Clipper for that :)
ah, if course - clipper is 2D only..
This should work (I used it a decade ago and it was pretty solid IIRC): http://sourceforge.net/projects/coldet/
Hi,
sorry just read your email. I tried my best but couldn't still find any
kind of code to test in your mails.
Send me the code and I am happy to give it a try
Greetings
Torsten
On 8 May 2013 18:08, TakeItAndRun [email protected] wrote:
Silly me, I intended to append the code!
Sorry,
TakeItAndRun
On 8 May 2013 17:53, Peter Falke [email protected]:
Off topic:
Hi torwag,
would you be so nice and run the attached program and let me know how
long
this will take on your machine.(F5 and/or F6)Thanks,
TakeItAndRun
On 8 May 2013 16:53, torwag [email protected] wrote:
just as a reference:
Intel(R) Xeon(R) CPU E3-1225 V2 @ 3.20GHz
32GB of RAM
256GB SSD
NVIDIA Quadro 600Arch Linux
Torsten
On 8 May 2013 16:38, Marius Kintel [email protected] wrote:
On 2013-05-08, at 16:32 , torwag wrote:
On 7 May 2013 17:49, Marius Kintel [email protected]
wrote:Current OpenSCAD: 10m38s @ 385 MB
Total rendering time: 0 hours, 2 minutes, 30 seconds
Hmmm either I do something wrong, or you would benefit from a
system
upgrade.Hm, my computer does feel slow, but it shouldn't be that bad (2.6 GHz
i7)
- perhaps I need a reinstall…
Hopefully, the speed improvement ratio will stay similar though :)-Marius
—
Reply to this email directly or view it on GitHub<
https://github.com/openscad/openscad/issues/350#issuecomment-17610022>
.—
Reply to this email directly or view it on GitHub<
https://github.com/openscad/openscad/issues/350#issuecomment-17610978>
.
[email protected] [email protected]
P.S. Falls meine E-Mail kürzer ausfällt als Dir angenehm ist:
Ich probiere gerade aus kurze Antworten statt gar keine Antworten zu
schreiben.
Wenn Du gerne mehr lesen möchtest, dann lass es mich bitte wissen.P.S. In case my e-mail is shorter than you enjoy:
I am currently trying short replies instead of no replies at all.
Please let me know, if you like to read more.Enjoy!
[email protected] [email protected]
P.S. Falls meine E-Mail kürzer ausfällt als Dir angenehm ist:
Ich probiere gerade aus kurze Antworten statt gar keine Antworten zu
schreiben.
Wenn Du gerne mehr lesen möchtest, dann lass es mich bitte wissen.P.S. In case my e-mail is shorter than you enjoy:
I am currently trying short replies instead of no replies at all.
Please let me know, if you like to read more.Enjoy!
—
Reply to this email directly or view it on GitHubhttps://github.com/openscad/openscad/issues/350#issuecomment-17615991
.
+1. This would be very useful for verification if part still fit together well after changes made to a project, kind of unit-tests. In some situations one might to occupy some space with a solid and render an animation to make sure nothing comes in the way. As for detection itself I think it should be "optionally optional". One may want to turn it on or off globally and for each solid separately.
As of their last release, MakerWare now also does a proper top-level union before slicing, just like Slic3r. I think this is becoming a popular approach.
This would be an excellent feature to have, especially since I very frequently have nemurous top-level objects that are non-intersecting but would be failing bounding-box checks (eg. packed triangles).
In a thread I on the mailing list I presented a case for not implicitly unioning not olny _top level_ objects, but also any node. In the particular case it was solids created by a for loop Ă la hull() for() {...}. When the objects generated by for did not overlap, the code was roaring fast. When they did overlap, it became inacceptable, because the implicit union on the for-objects took so long.
So deferring unioning in the tree as long as finally necessary for the next higher node seems to be very relevant with respect to performance, not only at the top level.
It seems like implicit union only on anonymous blocks (such as the top block) might be a reasonable behavior.
I tend to think a lot of the issue is that the way that the engine does unions is just slow. How hard would it be to have some kind of looseunion() that uses a faster - possibly less exact - algorithm to produce a simple cgal object? Ideally using the alternative explicitly on the top level would then make the implicit top level union into a case that can be recognized and handled quickly.
Another example using hull() for():
This takes forever (too lazy to wait) in 2014.03, both in F5 and F6 mode, but renders in 2-3 seconds in this branch. F6 is as fast as F5.
points=600;
random_angles=rands(0,360,points,42);
hull()
for(i=[0:3:points])
rotate([random_angles[i],random_angles[i+1],random_angles[i+2]]) translate([10,0,0]) cube();
What's the status on this? How can we help to move it forward?
The missing piece is deciding how to deal with backwards incompatibilities.
One alternative is to implement the OEP 4 counter proposal.
See https://github.com/openscad/openscad/wiki/OEP2:-Implicit-Unions
I'm likely to postpone the final integration until we have a clear vision of other related language changes.
Another alternative is to enable this as an experimental feature and be vocal about it being likely to break in the future.
I've read through the OEP4 proposal a few times and just finally understand what it's trying to do.
It seems the OEP4 unblocks users while preserving backward compatibility. It also adds new abilities for user defined library functions, which is awesome. So it seems like a win all around and should be implemented regardless of what happens with OEP2.
OEP2 is a harder issue that i'm not sure of the right way to go. On the one hand, it provides faster renders and less confusing code (especially for beginners). On the other hand, it will confuse existing users and break previous scripts. Offering a settings option doesn't do much to help since it will fracture new scripts into scripts that assume implicit union and those that do not. That doesn't seem like a great way to go. One way to solve this would be to provide a way to convert an implicit union script into one that doesn't require implicit unions... it would basically just add a union operation around every module. That's a bit janky, but at least would provide some ability for legacy scripts to be usable.
One could have the view that outputting overlapping STLs is a "hack", and AMF is the "proper" way to do multi-volumes.
In the case of AMF, the standard requires volumes to be non-overlapping.[1] So for AMF export, it would have to call back into the geometry functions before exporting to resolve the geometry. Exporting to AMF could then create an unexpected long delay, which currently is not cancellable. The AMF output might not match the output in other file formats.
That all seems rather messy. I think it would be much cleaner to have all the geometry resolved before export functions are called.
If we had a faster CSG, do we still need lazy unions?
[1] https://en.wikipedia.org/wiki/Additive_Manufacturing_File_Format#Structure
Lazy union is indeed a workaround for faster CSG. I foresee that CSG will still be a bottleneck, even if it's significantly faster, so we might eventually have to revisit this.
Not sure it's the place or time for that, but what about a more simple "render by color()" and "export STL by color()", provided the scad file was using separate colors (e.g. color("red")…) for each part?
@francoisbecker I think the color() module only works for rendering using OpenCSG, and you cannot export a model to STL until it has been rendered in CGAL mode.
(For multi material support see: https://github.com/openscad/openscad/wiki/Ideas-for-Development-Tasks#multi-material-support)
@GilesBathgate Maybe I was not explicit enough: render all tree under a specific color using OpenCSG separately (and subsequent separate STL export with file naming according to the colour). Thanks for the link.
@francoisbecker What I am trying to get at is that the CGAL geometry doesn't contain color information. As I understand, (prior to this lazy union development) the entire geometry is compiled down into a single CGAL union node. There is simply not "separate things" to export as STL (yet). OpenCSG geometry is implicit and cannot be exported to STL.
@francoisbecker While exporting colors as separate files is interesting, you still need to guarantee that the exported STL files don't intersect, as AMF (and I assume slicers) require these volumes to be non-overlapping for 3D printing purposes. Having tools in OpenSCAD to help ensuring this would be cool.
Some notes about this here: https://github.com/openscad/openscad/wiki/Multi-material-support
I may have misunderstood your request :)
@kintel Checking whether the AABB overlaps is quite trivial, but for multi material probably not very useful (I assume people want to have two parts touching/interlocking) so I think you would have to use CGAL::Nef_polyhedron_3::intersect() operation and check to see if the result was the empty set via CGAL::Nef_polyhedron_3::is_empty()
@GilesBathgate exactly - checking might not be sufficient. We may want an operator which takes two volumes and returns two non-overlapping ones.
@kintel An operator that takes two overlapping volumes an returns two non-overlapping ones is just the difference operator, with the exception that you want to keep hold of the volume that was subtracted:
module volume1() {
difference() {
cube(10);
cube(5); //<-- keep a copy of this somewhere volume2
}
}
volume1(); //<--This is the other part.
@GilesBathgate yes, in essence. I guess it could be expanded into any number of volumes and done in user space.
@kintel Well yes you could do it in userspace, but I provided the openscad example to aid explanation (perhaps that was a bit misleading.)
I meant that this new operation would be in C++:
void GetNonOverlappingVolumes(
CGAL::Nef_polyhedron_3 inputVolume1,
CGAL::Nef_polyhedron_3 inputVolume2,
CGAL::Nef_polyhedron_3& outputVolume1,
CGAL::Nef_polyhedron_3& outputVolume2)
{
outputVolume1 = inputVolume1.difference(inputVolume2);
outputVolume2 = inputVolume2;
}
I was thinking about the backwards compatibility issue, and I was reminded of how javascript handles strict mode by placing a string on its own before any statements
'use strict';
And how this can be used at the top of the file to cover the whole script, or at the top of individual functions for just the scope of that function.
A string by itself would not be not a valid statement in OpenSCAD, but what if it was implemented as a special $ variable that could be set at the top of any new script, or within specific modules, etc. You could maybe also have OpenSCAD editor automatically put this line at the top of new blank scripts with a short comment.
$implicit_union = false; // Recommeded for new scripts, see http://wiki/url for details
or
$lazy_union = true; // Recommeded for new scripts, see http://wiki/url for details
We've been debating such feature selection. The challenge is that it gets messy really fast, when we keep adding new features.
The best two solutions so far, IMO, is either a version directive like '#openscad
Any chance this could be merged?
See the TODO list for the missing pieces.
The main missing thing is evaluating forward compatibility with the current OpenSCAD2 proposal and either modifying this feature, modifying the OpenSCAD2 proposal, or document any incompatibility.
Ok, do you think adding lazy unions for modules as well would be a lot of work?
I think it's just a matter of enabling support for modules, but it needs testing as well as evaluating potential backwards compatibility issues.
I wanted to work on this a bit but I had trouble getting it to work at all.
After finally getting a debug build up (so I could debug it properly) I've found that the Preferences::setupFeaturesPage() seems to override the -enable=lazy-union CLI parameter that I set...
I'm thinking that CLI parameters should probably override the serialized? settings.
I also don't see any features GUI at all, am I correct in assuming that's just not implemented?
NVM: The features tab is showing up now.. not sure why it did not earlier :P
Some notes:
qmake CONFIG+=experimentalThis doesn't seem to work in recursive modules. Here's an example where it doesn't seem to pass all the children to further invocations:
module arrange() {
echo($children);
if ($children <= 1) {
children();
} else {
children(0);
translate([10, 0, 0]) {
arrange() children([1 : $children - 1]);
}
}
}
arrange() {
cube(5);
cube(5);
cube(5);
}
Note the echos result in
ECHO: 3
ECHO: 1
but we expect to see a call of the module with $children being two.
Ah, I see this is actually out of scope and the first "not in this issue" bullet point. Sorry about that. Consider this a vote for the module behavior being implemented as well.
The best two solutions so far, IMO, is either a version directive like '#openscad "
I'm not sure if there's a better issue to discuss this idea in, but if you do end up going this route, I'd point out that OpenJSCAD is already using //!OpenSCAD as a directive at the top of a file to interpret code as being written for (the current version of) OpenSCAD.
I'd really like to experiment with this, but I'm having issues building it - I get issues very similar to https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=844881 Any ideas?
I hate to see us leave this much performance in a branch just because we're not sure how it mixes with the (probably far) future directions of OpenSCAD. I feel like having this performance boost in play might change the whole narrative, for the better, about how to use OpenSCAD and what sort of applications it's capable of.
What if, instead of waiting until we can commit to a global change of behavior, we put this behind a system variable like $union. When $union is true (the default) OpenSCAD would behave as it always has. A user could then pass $union=false to modules where it's safe to do so and skip the implicit unions.
This would get us some real-world experience to better refine our vision for the OpenSCAD-2. Older versions of OpenSCAD would just ignore $union, and only users who know to set $union=true would be affected by any potential problems. If it all turns out to be mostly a non-issue (or we make it a non-issue by handling any unforeseen issues at export time), we can change the default to $union=false down the road so you get the performance by default.
@lostapathy I cherry-picked the fix for that issue from master and pushed it.
A good first step towards working on this would be to bring the branch in sync with master.
Using a $union variable and maintain backwards compatibility is a decent experiment. Getting it merged to master would be nice and allow us to more quickly iterate and seek external input.
How would $union interact with modules written to expect implicit unions, though?
I built this branch and compared it to the 2017.08.03 (git cecf3b98) build I use regularly. Rendering (F6) the chain ring example with this branch (feature on) ran in essentially 0s compared to ~30s on my "stable" build.
I ran a brick benchmark that contains ~600 identical non-overlapping boxes. This branch (feature on) ran in ~1m20s compared to the ~60s on my "stable" build. Some of the additional time is likely attributable to the additional GCAL cache logging. Is this a valid test case for this feature branch?
@deckar01 This branch should only give significant speed-up if your object live on the top level, not if they're further down in the hierarchy. It should, however, not cause significant slowdown.
for loops: A for loop will create a list of separate object passed on to the parent node (or the root)
I assumed this meant that the for loops in my modules would avoid the implicit union. After reading through the thread again it seems that group is intended to expand this functionality past the top level. Is there an issue for implementing group? I can't seem to find one and it does not seem to be in the scope of this issue.
@deckar01 The primary challenge finalizing this is to figure out how to manage compatibility with existing designs. It shouldn't be hard to implement a non-union group. It might even be there as a switch already; I cannot remember the details.
What do you think about a flag on each op that previously used to imply a union, and then add a default for that flag in the parser? This way anything created by the default parser will imply a union, and parsers that recognize they are reading new code can disable that flag?
for(x=[...]) { // union = true
}
for(x=[...], union=false) { // manually disabled
}
use 2.0 // borrow a notation from Perl. Only affects this scope
use <lib/something.scad> // will have its own scope, unaffected
for (x=[...]) { // union=false
}
The other advantage this way is that we could start using union=false immediately even before people reach a consensus about what use 2.0 should imply.
Just updated this branch to be current with master. It was only something like 2575 commits behind :stuck_out_tongue:
Seems to be working well with the current changes to just children and for loops.
I found that ListNode wasn't handling background/highlight modifiers and fixed that.
I suspect we want to eventually make all other potentially grouping nodes to use ListNode (or at least to not imply union):
Also still need to add 3mf support since that was added well after this was last worked on.
(assign?)
Perhaps it is time to drop deprecated things.
I agree Michael, not worth updating even if it would be simple to do so. I would be fine with transitioning our currently deprecated modules and module parameters to be fully obsolete, not supported anymore.
Unrelated to that, there was some discussion on IRC of what difference it would make if we change change user-defined modules to act like ListNodes so I'm leaving my thoughts on that here.
ListNodes basically just append children to their parent node rather than grouping them in implicit union. And in the case of user-defined modules I'm thinking about children from the body of the module definition, not children from module call/instantiation, which the builtin children() module would be used to access.
I have identified 3 cases where this change in behavior would affect the output:
1) If the module is called directly from the top level, or all its parent nodes up to root are also ListNodes, without any CSG operation in-between.
2) If the module is called as the first child of a difference operation.
difference(){ union(){A(); B();} C(); } != difference() { A(); B(); C(); }
3) if the module is any child of an intersection.
intersection() { A(); union(){B(); C();} } != intersection() { A(); B(); C(); }
These could cause problems in terms of backwards compatibility, since I have a hunch that its fairly common for existing libraries or modules to rely on that implicit union to output a single object.
Of course having the ability to define modules that for ex. output arrangements of disjoint geometries that don't require union, could also be very useful to speed up compile times.
I'm picturing a case where a user wants to use the new features in their code, but also depends on an older library that wasn't written for it. One solution could be to default to implicit union, but have some flag to enable/disable it? Could be a special variable. If this were done we'd also want to consider whether it should apply to just user-defined modules, or all the other potential list-able builtins too.
Branch updated again to add echo assert let and if.
Also updated the highlight/background modifier handling again since I realized it was missing them in CSG dumps when applied to ListNodes, and fixed some other weird edge cases.
I enabled the lazy-union for all tests(change not committed) just to see what issues this might cause, and besides most dumptests failing (expected, because CSG dump has less group nodes now), there are just a handful of Test failures:
offcgalpngtest_issue1105b and offcgalpngtest_issue1215b tests output blank images, so something wrong with .off export or import, I guess the format doesn't really support independent objects. I wonder if meshes in OFF can overlap or maybe we need to force union for .off files.

csgpngtest_2d-3d and cgalpngtest_2d-3d. F6 Render now shows 2D + 3D in the same view. Need added check to fail in that case.
Other bug noticed from manually testing:
! on ListNodes needs fixing. Testing on for loops gives blank Preview when there should be geometry, and crashes F6 Render. Results for both tests look same so only pasting one imageDIscussion of remaining modules to possibly convert to lazy union:
render - I think will be straightforward to implement, maybe I will try this next.translate, rotate, scale, and mirror all reduce to multmatrix so just need to implement once for all those. Not yet sure how much needs to change for this. As @kintel says in the initial comment, this should be done lazily on CGAL Nef Polyhedrons, but will need to make it compatible with how we cache. We can compose nested transformations internally but I think we will want to keep the CSG dump output looking the same.color - implementation will probably be similar to other matrix transformations since it can be store in State variable which is passed along through node visitor.resize - should be do-able without union but will require some extra code to get individual bounding boxes, and extend those instead.offset - does it matter if we lazy union this?hull and minkowski - not applicableprojection - Should we project 3D to 2D and keep all geometries separate? Or project individually, then union the 2D objects?linear_extrude and rotate_extrude - these feel like lowest priority to me. Do we even want them to lazy union at all? 2D union is fast, and I'm not sure there's much reason to allow for separate extrusion results.Last I looked at this, I ended up with a feature set that touched way too many parts, and still had the looming question of backwards compatibility. If you see any way of iterating our way to success, that would make this easier to merge.
Alternatively, if we end up having to (even temporarily) change behavior, tagging that as experimental would buy us time to figure out/refactor this in master rather than a long-living pull request.
Best way to solve back-compat is with a opt-in directive that affects only the current source file. To borrow design from Perl,
use feature 'lazy_union';
or since 'feature' already means something in this codebase that can't be toggled on the fly (or can it?)
use options implicit_union=false;
Then if people decide it should be the default:
use 2020.xx; // opt-in to default options as of version 2020.xx
Just realized a weird case that I thought would be a cool example, but doesn't work that way.
Giving a for loop as the child in a module call, results in $children==1 rather than one for each iteration.
Not sure if this can be fixed without significant changes to how ModuleInstantiation and LocalScope work though.
// Note: Enable "View -> Animate"
// "particle" module with short name
module p() { square(); }
module explode() {
echo($children);
for(j=[0:1:$children-1]) {
// angle is multiple of golden ratio to get "sunflower" pattern
a = j*1.61803398875*360;
translate(j*$t*[sin(a),cos(a)]) children(j);
}
}
// single child, does not move
explode() for(i=[0:1:200]) p();
// vs. specifying each child (works as intended, with or without lazy union)
/*
explode() {
p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();
p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();
p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();
p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();
p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();
p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();
p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();
p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();
p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();
p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();p();
}
//*/
@thehans Here's the solution I came up with for that, though I don't know if it applies cleanly with this branch:
https://github.com/openscad/openscad/pull/2796/commits/f6cd34d8f172bcf663528261a63ff8c8157af64b
Last I looked at this, I ended up with a feature set that touched way too many parts, and still had the looming question of backwards compatibility. If you see any way of iterating our way to success, that would make this easier to merge.
OK. I fixed the 2d-3d mixed object issue, and the root modifier on ListNodes, and added support for multiple geometry export to 3MF. I think the branch is actually in a reasonable state to merge as an experimental feature. We can wait to implement lazy-union for other module types as separate PRs, if that's easier than waiting for one huge merge to be ready. It would be nice to push this forward and start getting feedback from nightly users.
Alternatively, if we end up having to (even temporarily) change behavior, tagging that as experimental would buy us time to figure out/refactor this in master rather than a long-living pull request.
Just to be clear, the current test setup on this branch passes 100%. The other test failures I mentioned before were only when I temporarily applied the lazy-union feature to every regular test in the suite. Additionally, these remaining failures are only when exporting non-manifold/intersecting geometry for DXF and OFF. The OFF export succeeds in writing a file, and I can open them in meshlab, its just the import again into OpenSCAD which goes through CGAL functions and seems to silently fail for being non-manifold.
Some possible solutions:
1) Document in a release statement(eg. on the lists, and maybe in wiki somewhere) when we merge this, for users to be aware if they are exporting intersecting objects that they will need to call union themselves. (Personally I think this option would be fine, since users who choose to enable an experimental feature should be expected to be aware of possible consequences)
2) Possibly add some check during export if any objects intersect and print a warning? Not sure if we can check this more efficiently than just performing a union.
3) Force union on exports even when lazy-union feature is enabled - this is the least desirable option in my opinion, as it defeats the purpose of these changes. And would make for less flexible usage, such as the example of purposely exporting DXF with intersecting objects for laser engraving, where they represent tool paths rather than 'solids".
@thehans Sounds good. I'll review this and push towards merging. Thanks for taking this on and reviving this ancient branch! Would you mind opening a PR, so we can discuss further details there?
Shouldn't export to STL always do an implicit union if objects overlap?
On Tue, 19 Nov 2019 at 00:19, Marius Kintel notifications@github.com
wrote:
@thehans https://github.com/thehans Sounds good. I'll review this and
push towards merging. Thanks for taking this on and reviving this ancient
branch! Would you mind opening a PR, so we can discuss further details
there?—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/openscad/openscad/issues/350?email_source=notifications&email_token=AAEKHBJLFUI75FUX2C6W72LQUMWHBA5CNFSM4AFCTFNKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEEMMK5A#issuecomment-555271540,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/AAEKHBPO4TQBIDC62HJ2CF3QUMWHBANCNFSM4AFCTFNA
.
One use case for preserving overlapping solids in a single STL is multi-material slicers. The layout of the parts is preserved during a single import, but the components can be configured independently with different colors/materials.
If they share vertices the result will not be a valid STL and probably won't import properly. Use another format if you want to do that.
Otherwise this will create a whole new wave of broken STLs on Thingiverse where people forgot to union the top level.
This is already being done by other applications. The majority of complex models I find on thingiverse contain overlapping solids, slicers handle them fine, but OpenSCAD does not. I need to check the file contents, but I assumed they were declaring multiple solids in the STL file.
Not all slicers handle them fine. Skeinforge definitely doesn't.
With multiple materials they shouldn't overlap but if they touch that is enough to break STL. If they are separated by a micron they would not need to be unioned.
@nophead Yeah, slicers do vary. My checkbox above "Document the behavior of each file format for some slicer engines" was meant as an activity to build some knowledge around this.
We should probably break such discussion up into separate tickets once we re-establish a reasonable list of missing pieces related to this.
I do like the idea from nrdvana's suggestion re: backwards compatibility.
Something that requires a scad file to declare when it relies on a new, experimental, or optional feature, rather than(or in addition to) GUI based preferences which will differ across users. This makes it clear what features need to be supported, and the examples given would at least "fail fast" as a parser error on older releases, rather than output some possibly incorrect geometry while acting as if everything is fine.
I'm a bit hesitant to merge after thinking about this. Should we implement such a check before we end up with ever more mix of scripts in the wild which aren't clear about which features or OpenSCAD version they were written for?
I think merging is fine. We already have the experimental system where all features are turned off by default, and that has served us well so far.
..but starting on a more well-defined way of managing this would definitely be welcome
We may need that kind of definition for actual release features that might a compatibility mode. I think for experimental stuff it's the wrong solution. This is intended to change, potentially in incompatible ways while it's experimental, so putting it into designs will get messy.
OK, I went ahead and merged. I will open a separate issue for discussing solutions to compatibility issues going forward.
Found another highlight & background modifier issue from testing this complex assembly: https://github.com/CarlosGS/Cyclone-PCB-Factory
I ended up simplifying the issue down to this previewChildren example which is supposed to make it so children only show during preview, not render:
module previewChildren() { %children(); }
$fn=20;
previewChildren()
minkowski() {
translate([-5,-5]) cube();
translate([5,5]) cylinder();
}
translate([-5,5]) sphere(2);
I thought I had solved the issue of modifiers on ListNodes by passing these flags via the State &state parameter of NodeVisitor::visit(...). But this ends up applying to all children recursively, when we actually need to apply it to just the immediate children of a ListNode.
So during preview, the above example ends up showing the minkowski result as a background obejct, but also the individual minkowski components backgrounded by themselves. F6 Render output is fine though.
State already had these highlight and background flags defined but I noticed they weren't being used in code anywhere. Maybe because it doesn't work!
I'm not sure a good way around this because child nodes are basically stored as a list of pointers to const AbstractNode.
AbstractNode class doesn't store modifier flags and even if we added that, its still const.
Maybe we can apply the modifiers directly to the ModuleInstantiation objects of the ListNode children instead?
CSG dumps of above example:
(without lazy union)
group() {
% group() {
minkowski(convexity = 0) {
multmatrix([[1, 0, 0, -5], [0, 1, 0, -5], [0, 0, 1, 0], [0, 0, 0, 1]]) {
cube(size = [1, 1, 1], center = false);
}
multmatrix([[1, 0, 0, 5], [0, 1, 0, 5], [0, 0, 1, 0], [0, 0, 0, 1]]) {
cylinder($fn = 20, $fa = 12, $fs = 2, h = 1, r1 = 1, r2 = 1, center = false);
}
}
}
}
multmatrix([[1, 0, 0, -5], [0, 1, 0, 5], [0, 0, 1, 0], [0, 0, 0, 1]]) {
sphere($fn = 20, $fa = 12, $fs = 2, r = 2);
}
(with lazy union)
group() {
% minkowski(convexity = 0) {
% multmatrix([[1, 0, 0, -5], [0, 1, 0, -5], [0, 0, 1, 0], [0, 0, 0, 1]]) {
% cube(size = [1, 1, 1], center = false);
}
% multmatrix([[1, 0, 0, 5], [0, 1, 0, 5], [0, 0, 1, 0], [0, 0, 0, 1]]) {
% cylinder($fn = 20, $fa = 12, $fs = 2, h = 1, r1 = 1, r2 = 1, center = false);
}
}
}
multmatrix([[1, 0, 0, -5], [0, 1, 0, 5], [0, 0, 1, 0], [0, 0, 0, 1]]) {
sphere($fn = 20, $fa = 12, $fs = 2, r = 2);
}
There is also a problem with modifiers and render.
It is also strangely only with some color-schemes.
Change the scheme to Tomorrow Night.
Disable Lazy Union.
translate([-5,0]) cube(3);
difference() {
translate([5,0]) cube(3);
%cube(2);
}
Remove the %.
F6;
Enable Lazy Union, Flush Cache, F6;
Reinsert the %, F6;
I've got no idea what's going on.
Hey people!
If it helps motivate anyone, I could use this feature right about now. I would like to import my mesh into an application that only takes convex hulls. I can make that happen with a bunch of cubes, but the auto-union keeps the mesh concave.
Just so you know. I am rooting for you to succeed!
@hansonry The feature is already available as experimental (must be enabled in preferences) if you use a "nightly" build or basically any dev snapshot after Nov 18, 2019 when the code was merged.
Give it a try and see if it works for your use-case.
I have been playing with it a bit for the purpose of laser cutting and engraving, and I agree that it would be nice if translate() etc., custom module()s' top-level scope and possibly also group() would be lazy as well. For example it would be very useful to define a bunch of overlapping 2D shapes for engraving within a module, then replicate that module in a for loop and be able to export an SVG or DXF with those outlines.
I just started to use OpenSCAD a few days ago, and realised, I'd need this feature badly. I like the composability of modules and such a lot.
So, just wondering, is there a roadmap to promote this from and experimental to a core feature?
Most helpful comment
I'd really like to experiment with this, but I'm having issues building it - I get issues very similar to https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=844881 Any ideas?
I hate to see us leave this much performance in a branch just because we're not sure how it mixes with the (probably far) future directions of OpenSCAD. I feel like having this performance boost in play might change the whole narrative, for the better, about how to use OpenSCAD and what sort of applications it's capable of.
What if, instead of waiting until we can commit to a global change of behavior, we put this behind a system variable like $union. When $union is true (the default) OpenSCAD would behave as it always has. A user could then pass $union=false to modules where it's safe to do so and skip the implicit unions.
This would get us some real-world experience to better refine our vision for the OpenSCAD-2. Older versions of OpenSCAD would just ignore $union, and only users who know to set $union=true would be affected by any potential problems. If it all turns out to be mostly a non-issue (or we make it a non-issue by handling any unforeseen issues at export time), we can change the default to $union=false down the road so you get the performance by default.