How to loop through components, delete non-solid components

I’m trying to write a script that will delete all non-solid components in my model.

I found the manifold? method http://ruby.sketchup.com/Sketchup/ComponentInstance.html#manifold%3F-instance_method which will check for solid.

But I can’t quite figure out how to loop through the active model definitions, or if I need to loop through all the instances?

I ended up trying to use a selection instead, so I’m selecting all the components I want to process, then running the script, but it’s giving me problems. It’s almost as if the selection is being updated in real time as I erase the entities, instead of the loop going through all the original selection? So it’s causing the loop to end prematurely.

model = Sketchup.active_model
selection = model.selection
selection.each {|ent|
  if !ent.manifold?
    ent.erase!
  end
}

I understand that this will through an error if my selection includes something other than a component. That’s ok. I just can’t get it to actually erase all the non-solids. It seems to erase one, then the script stops.

It depends on what you are trying to do. Do you want to erase component instances or erase their definitions?

This is called a “fence post error”: never delete members from an enumeration while iterating over that enumeration. Doing so can cause some members to be skipped or can terminate the loop early. Instead, make an array to access the contents and then iterate over that array, e.g.

selection = SketchUp.active_model.selection.to_a

It’s safe to do the operation on an array because it is a simple linear data structure that doesn’t care if its elements refer to deleted or invalid entries.

2 Likes

Very cool. Yeah, I ended up trying that out, and it worked. I didn’t know about .to_a, I used a much less elegant solution, lol

model = Sketchup.active_model
selection = model.selection
deletethese = []
selection.each {|ent|
  if !ent.manifold?
    deletethese << ent
  end
}
deletethese.each { |ent|
  ent.erase!
}

This worked. Took several minutes to process, but did the job.

Now if I wanted to do this on the entire model (without having to select the entities first), the manifold method only works on instances, but maybe I don’t understand the distinction between definition and instance in this scenario. Are definitions basically all component definitions embedded in the model, even if the component doesn’t have an instance in the workspace? So how would you check each definition for manifold, and erase it, when the manifold method only works on instances?

To iterate the model recursively, you could look at the source code for Eneroth Material Area Counter. If you know you want to do this with everything in the model, iterating over the model Definitions context may be enough.

Regarding identifying solids, the native solid tools don’t recognize anything as a solid if it contains a nested instance. The check in Eneroth Solid Tools may be better as it ignores nested geometry, but teh algorithm is far from perfect and sometimes fails on some geometry.

Could be useful though.

EDIT: When thinking of it, it’s the solid operations, not the solidity checks, that sometimes fails.

1 Like

For reasons I don’t know, there is no manifold? method on a ComponentDefinition. So, you would need to iterate over the DefinitionList of the model (model.definitions), and if the ComponentDefinition has any instances (definition.count_used_instances > 0), test one of them for manifold?. If it is manifold, you can get rid of both the ComponentDefinition and all its ComponentInstances using DefinitionList.remove.

Just for some context to why I’m doing this, I imported a truss model from Mitek, and the 3D trusses are surrounded by a bunch of single surface components representing the boundaries of the floor system. All of the planes are on the same layer as the 3D truss, so there’s no way for me to easily isolate them.

So I figured since they are all non-solid, I could use that as a method to grab them and delete them.

Oh that’s a good point. So maybe I should just keep it simple and just use the selection method. It’s easy enough for my needs.

On another note, is there any benefit to creating a selection from all the entities I collected in the array, and erase them in one step? Instead of looping through my array and erasing them one-by-one? It seems to take a long time, I’m wondering if selecting all of them, then erasing all at once would be faster?

How would you code this? I don’t know of an operation that bulk-erases an array of Entities…

I was thinking I could just create a selection set of the entities in the array? Then erase that? Or would that not work?

I’m not sure what you mean by “a selection set”, but unless that object provides a bulk erase, that idea isn’t going to work.

I just mean add each object to the model selection https://ruby.sketchup.com/Sketchup/Selection.html

Then I can just tap delete on my keyboard at the very least

Yes, that should work. Of course, it is a non-Ruby, purely manual technique, so you trade the time to select all the targets for the speed of deleting them once selected. Depending on how they are arranged spatially, the drag box select techniques may be able to select them quickly.

Edit: I may have missed the point. I think maybe you meant to add them to the selection using Ruby and then use the GUI delete key to blow them all away. That would work.

Bingo. Idk if it would be faster for ruby to just select all the entities, so I can delete them in one step vs having the script loop through each entity deleting them one at a time. I can play around with it to see.

I think you can use a send action like …

Sketchup.send_action("cut:")

… and it’ll be all Ruby.

1 Like

A further thought: if you really just want them out of the way, you could assign a Layer to them and make that Layer non-visible. That would avoid permanently removing them from the model in case you ever want them back!

Yes, that should work and would make it all Ruby.

Edit: Another alternative would be

Sketchup.send_action('editDelete:')

which will skip copying them to the clipboard.

@Matt since you asked …

def erase_nonsolids(groups = false)
  model = Sketchup.active_model
  dlist = model.definitions
  dlist.each do |cdef|
    next if cdef.image?
    if cdef.count_used_instances > 0 && cdef.instances[0].manifold?
      next if ( groups ? !cdef.group? : cdef.group? )
      ents = cdef.entities
      next if ents.grep(Sketchup::Group).size > 0
      next if ents.grep(Sketchup::ComponentInstance).size > 0
      cdef.instances.to_a {|i| i.erase! }
    end
  end
end

However this will also erase non-solid instances that might be nested within unused definitions.

1 Like

I knew there must be an action for delete but couldn’t remember it.
(I do have it listed in my send action list.)

So what you’re saying is I need more error checking :laughing: :laughing: :laughing: I like to live dangerously!

Yeah, I definitely wouldn’t have been able to come up with that myself, thank you. I find it really interesting to read through and understand the logic behind it all.

Note that @DanRathbun’s code erases the instances while leaving the definition intact. That is the equivalent of what would happen if you used the GUI and didn’t follow up with a purge unused.