What is causing this script to hang?


#1

Basically, given a group with several subgroups within it, I’d like to loop through and union all of them…

def self.union_entities(group_in)

    # convert entity list into array
    temp_union_array = group_in.entities.to_a

    # remove the first member from of array and define as new variable
    union_result = temp_union_array.shift

    # run union on each item in remaining array
    temp_union_array.each_with_index{|item,index|
        #print "#{index}: #{item}\n"
        next unless item.valid?
        union_result = union_result.union(item)
    }

 end # end union_entities

#2

You need to test the items for manifoldness before subjecting them to a union (or any solid boolean operation.)
Otherwise nil is returned from the operation and it mucks up your loop.

So add a

&& item.manifold?

… to the validity test line.


#3

Whoaa… I have never even heard of manifoldness! OK great, I will look into it. Thanks Dan!


#4

… And I thot I was being clever and making up new slang. :roll_eyes:


Oh, and you should probably also test the initial shifted group before starting the union loop.


#5

I’m using that word all the time from now on!


#6

OK… Still hanging.

Could this have something to do with the re-assignment of the variable union result?

When I change this line

union_result = union_result.union(item)

to this…

whatev = union_result.union(item)

at least it does not hang. I get a “reference to deleted Group” but at least it does not hang.

That whole re-using a variable in the same line that you are defining it feels wrong.


#7

What do you mean by “hang” ?

Are the groups very heavy geometrically ?

Perhaps post a test model.


#8

OK, not necessarily hang but take a longer time then you might think. Here is a test model. The TEMP_VOIDS group is the one that I am looping through to union them all together (in preparation for a subsequent subtract operation).

temp4.skp (112.8 KB)

The union process takes about 12 seconds. Compared to the rest of the script this is certainly the bottleneck!

If you want to know, I am unioning everything because if I simply explode everything in here, you sometimes end up with an invalid solid because of conditions where two “voids” overlap.


#9

No, I take that back… HANG! Its definitely taking too long. The result is visible in the display window in about 12 seconds but the “working” cursor is present for about another 40 seconds… doesn’t seem right.


#10

ruby “each_with_index” and the union may reorder the set, so you get duplicates or excessive reallocation.

Check the item id to see if it is already in union_result before adding it?


#11

OK, not sure how I would check if a certain item id was already in union but meanwhile, I tried .each instead of .each_with_index with no luck. I’m not sure how re-ordering would work at all, Isn’t .each just a loop through every entity? It wouldn’t do the same entity twice would it?


#12

The first rule of collection iteration is, … do not modify the collection whilst iterating it.

If you do, the loop gets confused, skips members or iterates them more than once, especially if the “mods” result in the size of the collection changing (ie, members being deleted or becoming invalid.)

This code from an earlier post was done correctly.

You made an array copy of the entities collection using #to_a, and used the array copy to iterate whilst actually modifying the entities collection.


#13

OK so if the original code was correct, I’m still not sure what is causing this operation to take so long. Did you guys experience a similar lag on the test model I uploaded?


#14

I haven’t had time and this “browsing” machine is only capable of SU2016 at the most (so your v18 file won’t load for me.)

Generally speaking the more complex the geometry, the more time a boolean / solid operation can take.

Julia Eneroth has a 3rd party boolean tools you might try.
http://extensions.sketchup.com/en/content/eneroth-solid-tools


#15

Here is a SU2014 version in case you want to give it a try.

temp5.skp (839.9 KB)

Honestly, its 33 box shaped objects… it should not take that long IMO.


#16

No, it only takes about 2.6 seconds here, using the code in your first post and your temp4.skp file.

Edit: This could just be down to luck in which order things get done. You are still modifying the collection while iterating it.


#17

That triggers a memory that a few version ago there was a reported large difference in time taken for operations inside a group and outside at the top model level. ( I don’t remember which was longer, but I think I recall that the results were opposite what we’d expect.)


#18

I don’t think it is making a clone of all the objects, just an array of references to the SketchUp groups.
I tried to work out if Entities#to_a actually gives you a clone of the entities in an array or an array of references to the entities. If you open test4.skp, select the “TEMP VOIDS” group and paste this in to the Ruby console:

g = Sketchup.active_model.selection[0]
g_copy = g.entities.to_a
g_copy[0].name ="Erase me"

You should see the name of one of the groups being changed in the outliner. Follow with this to remove that group

g_copy[0].erase!

All done from the array given by Entites#to_a. So I think you are still iterating through a collection while modifying that collection. It did work when I tried it, but maybe that was just to do with some random order that the collection was iterated through. Try using this while loop instead:

    def self.union_entities(group_in)
      temp_union_array = group_in.entities.to_a
      union_result = temp_union_array.shift
      while temp_union_array.size > 0 do
        union_result = union_result.union(temp_union_array.shift)
      end #while
    end #union_entities

#20

That also hung for about 45 seconds (in SU2018).

Regarding your comment about cloning, that seems to be the meat of the problem. When we run:

g_copy = g.entities.to_a

I think you are correct that we are not cloning the group because nothing changes in the Outliner. We are merely creating a ruby object that references the group and its entities. Therefore, any iterating, whether its on the group itself or the array reference to it, would seem to be breaking Dan’s first rule of iterating:

Does that make sense to you too?

If so, I’ll have to perhaps work on making a copy of each entity or the whole group and work on that I would imagine. (scratches head).


#21

Yes it makes sense. That’s why I used the while loop. I’m expecting the array to be smaller each time.

You’re still getting much slower execution times though. Maybe there’s something else that’s different. Are you running something else at the same time? Running out of RAM, etc?