This question is in reference to this post by @barry_milliken_droid addressing the quirky challenge that the API does not have a method to copy a group.
Here is a slightly modified version of Barry’s code that successfully “copies” all root-level groups into a new container group and moves it slightly to show that it worked.
array_of_ents_to_copy = Sketchup.active_model.entities.to_a
if array_of_ents_to_copy.length == 0
UI.messagebox "nothing to copy"
return
end
original_groups_temp_container = Sketchup.active_model.entities.add_group array_of_ents_to_copy
temp_transformation = original_groups_temp_container.transformation
temp_definition = original_groups_temp_container.definition
container_ent = Sketchup.active_model.entities.add_instance temp_definition, temp_transformation
container_ent.name = "CONTAINER_ENT"
vector = Geom::Vector3d.new(80, 40, 0)
tr = Geom::Transformation.translation(vector)
Sketchup.active_model.entities.transform_entities(tr, container_ent)
#container_ent.explode # don't explode if you want to keep everything in one group
original_groups_temp_container.explode # explode to return original groups to normal
The problem comes when I then try to loop the new container’s contents. I want to get rid of anything that is not a comp or group, not on a visible layer, or named certain things. When I run .each() on the new container’s entities, and try to erase! certain qualifying objects, I get a bunch of Deleted Entity warnings.
Here is the loop…
container_ent.entities.each{ |this_ent|
print "inspecting #{this_ent}\n"
# prune unwanted ents
if (!this_ent.is_a?(Sketchup::Group) && !this_ent.is_a?(Sketchup::ComponentInstance))
if this_ent.erase!
print "erased #{this_ent} - because it was not a comp or group\n"
else
print "erase failed for #{this_ent} \n"
end
elsif this_ent.layer.visible? != true
if this_ent.erase!
print "erased #{this_ent.name} - because its layer was not visible\n"
else
print "erase failed for #{this_ent}\n"
end
elsif this_ent.name.include?("_CIRCLE")
if this_ent.erase!
print "erased #{this_ent.name} - because it was a cutting circle\n"
else
print "erase failed for #{this_ent}\n"
end
elsif this_ent.name.include?("_CUT")
if this_ent.erase!
print "erased #{this_ent.name} - because it was a _CUT result\n"
else
print "erase failed for #{this_ent}\n"
end
else
print "keeping #{this_ent.name}\n"
end
}
And here is the console output…
inspecting #<Sketchup::ComponentInstance:0x007fdfeaa8dd48>
keeping
inspecting #<Sketchup::Group:0x007fdfeaa8dd20>
erase failed for #<Deleted Entity:0x7fdfeaa8dd20>
inspecting #<Sketchup::Group:0x007fdfeaa8dcf8>
erase failed for #<Deleted Entity:0x7fdfeaa8dcf8>
inspecting #<Sketchup::Group:0x007fdfeaa8dca8>
erase failed for #<Deleted Entity:0x7fdfeaa8dca8>
inspecting #<Sketchup::Group:0x007fdfeaa8dc80>
keeping PROP_GROUP
inspecting #<Sketchup::Group:0x007fdfeaa8dc58>
erase failed for #<Deleted Entity:0x7fdfeaa8dc58>
inspecting #<Sketchup::Group:0x007fdfeaa8dbe0>
keeping EXIST_GROUP
inspecting #<Sketchup::Group:0x007fdfeaa8dbb8>
keeping DEMO_GROUP
inspecting #<Sketchup::SectionPlane:0x007fdfeaa8db90>
erase failed for #<Deleted Entity:0x7fdfeaa8db90>
inspecting #<Sketchup::SectionPlane:0x007fdfeaa8db40>
erase failed for #<Deleted Entity:0x7fdfeaa8db40>
inspecting #<Sketchup::SectionPlane:0x007fdfeaa8db18>
erase failed for #<Deleted Entity:0x7fdfeaa8db18>
inspecting #<Sketchup::SectionPlane:0x007fdfeaa8dac8>
erase failed for #<Deleted Entity:0x7fdfeaa8dac8>
#<Sketchup::Entities:0x007fdfe8d0a550>
Somehow, between starting the loop and inspecting the entities, they have changed/been deleted? It must have something to do with the handles being changed?
Don’t delete from the collection you are iterating over. Doing so changes what elements are contained in the collection, shifts the remaining ones to new indices, and risks iterating over the same element multiple times while missing others.
instead of entities.each, use entities.to_a.each. The array created from to_a retains elements even while they are deleted in model, meaning it can be safely iterated over.
Btw, if you follow the Ruby style guide (2 spaces for indentation among other things), your code is easier to read and give feedback on.
That’s odd. Perhaps some entities were already deleted before the loop even started. What happens if you run p container_ent.entities.to_a? Any sign of deleted entities there?
You should never iterate an .entities collection [or a .selection] and delete items as you go because that changes what you are iterating !
The .entities.to_a etc avoids that issue.
BUT it’s quite possible to iterate a ‘to_a’ collection and still find some items are already deleted !
For example, if your collection consists of a few edges and a face supported by those edges, then when you delete an edge the face is also deleted at that step, so later on in the iteration you get to the face and it’s already gone !
Hence the tip about adding in next if this_ent.deleted? at the start of the block…
This then skips the entity in question if it’s already been deleted.
Avoiding the error message !!
OK, I get that Sketchup’s cleaning directive will erase things if any partner geometry is erased but I am getting this on EVERY entity. Look at this erase criteria…
elsif this_ent.name.include?("_CIRCLE")
This is a pretty specific criteria for a group or comp with a specific name. How could this have been erased when is no partner geometry at the active context that could have already been deleted?
As far as I can tell
next if this_ent.deleted?
Just avoids the error message associated with trying to delete something that isn’t there. Still doesn’t address why it’s not there in there first place, which is the whole conundrum here.
It is usually safer to queue up references to drawing elements that you will delete in an array, and then pass that array to the Sketchup::Entities#erase_entities() method to erase them in one operation. It is faster and should be easier on the undo stack.
This method has been part of the API since v 6.0 …
Also for topic threads that were locked when that autolock feature was active, you can PM one of the admins like Aaron to unlock it. Admins are listed on the forum’s About page (accessible via the hamburger menu, top right on the toolbar.)
So I am not sure if we figured out why some ents were deleted but I suspect it had to do with using the “old” copy method rather than group.copy()
Here is the updated code that works as desired.
array_of_ents_to_copy = Sketchup.active_model.entities.to_a
if array_of_ents_to_copy.length == 0
UI.messagebox "nothing to copy"
return
end
original_groups_temp_container = Sketchup.active_model.entities.add_group array_of_ents_to_copy
container_ent = original_groups_temp_container.copy
#temp_transformation = original_groups_temp_container.transformation
#temp_definition = original_groups_temp_container.definition
#container_ent = Sketchup.active_model.entities.add_instance temp_definition, temp_transformation
container_ent.name = "CONTAINER_ENT"
vector = Geom::Vector3d.new(80, 40, 0)
tr = Geom::Transformation.translation(vector)
Sketchup.active_model.entities.transform_entities(tr, container_ent)
#container_ent.explode # don't explode if you want to keep everything in one group
original_groups_temp_container.explode # explode to return original groups to normal
ents_to_erase = []
container_ent.entities.to_a.each{ |this_ent|
print "inspecting #{this_ent}\n"
next if this_ent.deleted?
# prune unwanted ents
if (!this_ent.is_a?(Sketchup::Group) && !this_ent.is_a?(Sketchup::ComponentInstance))
ents_to_erase << this_ent
print "added #{this_ent} to ents_to_erase - because it was not a comp or group\n"
elsif this_ent.layer.visible? != true
ents_to_erase << this_ent
print "added #{this_ent} to ents_to_erase - because its layer was not visible\n"
elsif this_ent.name.include?("_CIRCLE")
ents_to_erase << this_ent
print "added #{this_ent} to ents_to_erase - because it was a cutting circle\n"
elsif this_ent.name.include?("_CUT")
ents_to_erase << this_ent
print "added #{this_ent} to ents_to_erase - because it was a _CUT result\n"
else
print "keeping #{this_ent.name}\n"
end
}
Sketchup.active_model.active_entities.erase_entities ents_to_erase