On 9/25/2026 12:49 AM, Cory Cross via Discuss wrote:
On 9/24/26 6:45 PM, Jordan Brown via Discuss wrote:
Preserving LU at the top level only would of course be incompatible
with its existing behavior.
If you did as I said (export options) and it was possible (caveat what
you pointed out), it would be backward-compatible. All existing
non-LU-code would render and export the exact same STL.
It would be backward-incompatible with current Lazy Union behavior.
If you're going to be having the model encode this amount of metadata,
I'm thinking you're also going to want to encode modifiers as well,
which means having non-unioned objects taking up the same volume. I
guess you're right, it makes more sense to have an explicit label
("group(part=...)") rather than magically have the top level do this.
Yep, my thinking exactly.
And I had thought that for multi-color/multi-material you would want
union to give the overlap to one side or the other, so that each point
in the shape has exactly one color/material/whatever. But then I learned
about slicers interleaving overlapping colors/materials to form stronger
joints, and between that and the modifier use case I changed over to a
"parts can overlap" model.
Maybe however LU is implemented has some reusable code, or maybe this
is so trivial it doesn't matter.
I'm not sure yet. I've started some implementation, but haven't gotten
to the geometry end of it yet.
Either you'll need to ban STL export or union things for export;
you'll still need to address the point you made about render vs export.
I figured that when had a multi-part model and did Export-As-STL, it
would in some fashion export multiple files. (Maybe it would prompt N
times, or maybe it would prompt for a base name and append part names to
it.)
On 9/25/2026 2:23 AM, Guenther Sohler via Discuss wrote:
I think the easiest fix for OpenSCAD would be the formerly proposed
group("name_of_part") approach.
My current thinking is a new marker part(name); see OEP9
https://github.com/openscad/openscad/wiki/OEP9:-Generating-Multiple-%E2%80%9CParts%E2%80%9D-in-One-Run.
group() has a lot of history of being a synonym for union(), and making
it have an additional meaning bothers me. Also I think the original
intent (never completed) was that group() meant "put these children into
a list without unioning them", and that would be overloading it further.
Unless OpenSCAD some time in the future makes solids 1st class object,
I do want to do that, ref PR#4478. However, I don't think it's the
right answer here. I think it's important that you be able to have a
deeply nested "part", that you aren't required to have all of the parts
be directly visible at the top level.
For instance... my house model has a wall() module that builds a wall.
You can give it arguments saying where to put windows, and it cuts holes
in the walls for the windows. It adds dividers (what are apparently
called mullions, muntins, and transoms
https://en.wikipedia.org/wiki/Transom_(architecture)#/media/File:Casement_window_parts.svg)
to those windows. You can supply children that add interior and
exterior trim pieces. I want to be able to make the wall, dividers, and
trim pieces different parts, since they can in real life be different
colors.
This need is especially important for BOSL2, where deeply nesting
components, positioning them relative to one another, is a fundamental
piece of the design. You want to be able to say something like:
part("red") cuboid(10)
attach(TOP, BOTTOM)
part("blue")
cuboid(10);
to put a blue cube on top of a red cube.
On 9/24/26 5:00 PM, Jordan Brown wrote:
On 9/24/2026 11:24 AM, Jamie Larkby-Lahet via Discuss wrote:
another way of looking at it would be that the implicit unions in
various parts of openscad are the issue, no?
I did pre-emptively answer that:
Q. Isn't the LU behavior better?
A. For reasons too complicated to get into right now (having to
do with children()), I don't think so. But it doesn't matter,
because LU behavior is not base OpenSCAD's behavior, and breaking
models in subtle ways is highly undesirable.
if every union was explicit,
Sure. But there are many years, thousands of users, and thousands of
models that say that it isn't.
my thought was a new operator like your proposed part("name"), but that
is immune to implicit unions. so when it counts, we can ask for
explicit-union style behavior, in a backwards compatible way (actually I
was thinking of named sets of geometry, which has the potential for
producing hierarchical structures, which might be good or bad. but
naming the contents of the set individually as you propose also works
and keeps the namespace flat).
children() can list every item in the curly braces as before, including
parts, but we could also add list_parts() and $num_parts that allow
processing of parts specially. and list_parts("name") would allow
operating on specific parts, if present. interestingly this also
provides a limited form of object references.
for:
A() B(){
part("A") cube();
part("B") cube();
cube();
sphere();
}
A and B would see 2 child parts, but A would see 3 children and B would
see 4. another way to think of it is that the cube() and sphere() are
implicitly assigned an identical private, internal part name, and this
part does respect implicit unions.
Most transformations would operate on parts individually even when a
group of them are passed though, and they would be individually
difference()'d when several are passed in on the first line of difference().
I would argue that explicit unions, as they may exist in legacy code,
esp for difference()s, should also not apply to parts. instead you
would need a join() operator, which could also potentially select
specific part names for the joining to apply to.
It looks like your proposal implicitly unions parts with the same name.
Hierarchical naming of sets of parts would be a way around that, if it
is deemed desirable.
translate(...) { cube(); sphere(); } either implicitly unions, or
doesn't. And existing practice is that it doesn't.
interesting, because:
intersection() {
translate([0,0,0]){
for(x=[-1,1])
translate([x,0,0]*10)
sphere(15);
}
cube(25, center=true);
}
with lazy union on behaves identically to lazy union off.
same goes for:
intersection() {
a(){
for(x=[-1,1])
translate([x,0,0]*10)
sphere(15);
}
cube(25, center=true);
}
module a() {
children();
}
On 9/25/2026 6:58 PM, Jamie LL wrote:
my thought was a new operator like your proposed part("name"), but
that is immune to implicit unions. so when it counts, we can ask for
explicit-union style behavior, in a backwards compatible way (actually
I was thinking of named sets of geometry, which has the potential for
producing hierarchical structures, which might be good or bad. but
naming the contents of the set individually as you propose also works
and keeps the namespace flat).
part(), as proposed, is in a sense immune to implicit union. Actually,
it's mostly immune to explicit union too. Not completely, because if
you union two pieces that have the same part name, they do get unioned.
To a first approximation, you could say that all pieces that are inside
part("A") are in their own isolated universe, interacting with one
another but not with pieces in other parts. (And it is tempting to
define it exactly that way, since it would provide a simple and uniform
definition across all operations. But I'm not sure it's the most useful
definition.)
I think you want that, because I think that if you have ten separated
pieces that are all to be green, you want them to end up in one part in
the resulting mesh file (typically 3MF) so that you only need to
associate that one part with the green filament.
But we are mostly agreeing. Lazy Union in its current form is not the
answer. We need something new. Exactly what... that's a good
question. part() is my stab, but I don't claim that its implications
are fully thought-out.
we could also add list_parts() and $num_parts that allow processing of
parts specially.
I believe you'll find that there is still a chicken-and-egg problem.
You need to evaluate the child before you can determine whether it
contains parts, or how many of them... and we don't have a way to
evaluate a child without adding the result to the model. If we had a
way to evaluate a child and temporarily store the result, so that we
could examine its structure, things would be different... but we don't.
It looks like your proposal implicitly unions parts with the same name.
Yes. (Give or take some uncertainty as to how parts interact with
intersection, difference, hull, minkowski, projection, et cetera.)
Hierarchical naming of sets of parts would be a way around that, if
it is deemed desirable.
Mostly, don't call part() with the same name if you don't intend the
results to be (eventually) unioned. How you come up with unique names
is your business. The current generation of the proposal says that
part() with no name will construct a unique name, so if you don't need
to identify the part and you want it to be separated, you can use that
and not have to come up with unique names on your own.
translate(...) { cube(); sphere(); } either implicitly unions, or
doesn't. And existing practice is that it doesn't.
Oh, oops. My error. "doesn't" should be "does".
translate() implicitly unions, with or without lazy union. There's only
a limited set of operators that don't implicitly union, even with LU
enabled: children(), echo(), assert(), let(), for(), if().
What I was saying was that getting rid of implicit union is
intrinsically incompatible with standard OpenSCAD. You can come up with
schemes that explicitly don't union (like part()), but then you haven't
gotten rid of implicit union.
same goes for:
intersection() {
a(){
for(x=[-1,1])
translate([x,0,0]*10)
sphere(15);
}
cube(25, center=true);
}
module a() {
children();
}
The results of the module call are wrapped in a group(). I don't know
for sure whether they are unioned or not (I think they are), but they
end up as a single child for the intersection. Check the CSG tree:
intersection() {
group() {
multmatrix([[1, 0, 0, -10], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) {
sphere($fn = 0, $fe = 0, $fa = 12, $fs = 2, r = 15);
}
multmatrix([[1, 0, 0, 10], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) {
sphere($fn = 0, $fe = 0, $fa = 12, $fs = 2, r = 15);
}
}
cube(size = [25, 25, 25], center = true);
}
But note that LU kept the for() from generating its own group().
As best I can tell, the semantics of Lazy Union in its current form can
be described as:
Some, but not all, operations will not implicitly union their
children. Those un-unioned children will be considered to be
separate children when processed by some, but not all, parents.
Un-unioned children that survive to the top level will be exported
as separate 3MF parts, or as potentially overlapping and invalid STL
polyhedra.
Strange women lyin' in ponds distributin' swords is no basis for a
system of government. "Some, but not all", especially repeated, is no
basis for a solid and understandable design.