Entities not deleted within group

Hi,

I am implementing a simple function to perform an intersection. I want to ‘clip’ a surface with a ‘tool’ object.

I have managed to perform the intersection correctly, but I can’t figure out why the ‘clipped’ portion of the surface does not get deleted. I thought that the reference to the surface was lost after the intersection, but that does not seem to be the case as you can see from the output.

require 'sketchup'

def self.cut_surfaces(tools, surfaces)
  puts 'Before intersection'
  puts surfaces.entities
  orig_surfaces = surfaces.entities.select{|e| e.is_a?(Sketchup::Face)}
  puts orig_surfaces

  tools.each do |t|
    arrays  = t.entities.intersect_with false, t.transformation, surfaces.entities, surfaces.transformation, false, surfaces
  end
  puts 'After intersection'
  surfaces.entities.select{|e| e.is_a?(Sketchup::Face)}.each{|e| p e.to_s}

  del = surfaces.entities.select{|e| (e.is_a?(Sketchup::Face) && (!orig_surfaces.include?(e)))}
  puts 'Surfaces to delete'
  puts del.to_s
  puts 'Current list of surfaces'
  puts surfaces.to_s
  puts surfaces.entities.to_s
  surfaces.entities.select{|e| e.is_a?(Sketchup::Face)}.each{|e| p e.to_s}
  
  surfaces.entities.erase_entities(*del) if del != []
  return true
end
mod = Sketchup.active_model # Open model
ent = mod.entities # All entities in model
 
tools = ent.select{|e| e.layer.name == 'fixed_surroundings'}
surfaces = ent.select{|e| e.layer.name == 'area_of_interest'}
  
cut_surfaces(tools, surfaces[0])

If you run the code on this file you will notice that the small area in the middle is not erased as I would expect.

I am of course doing something wrong, but I really cannot understand what.

Is there a better way to achieve the same goal? Clip a surface with a solid?

Thanks

I believe the problem is that Entities#intersect_with creates only Edges, it does not create new Faces or split pre-existing ones. You have to follow up with Edge#find_faces to create the new Faces. Insert a call to this method between the intersect_with and the select to get the revised list of faces:

arrays.each {|edge| edge.find_faces}

By the way, entities.grep(Sketchup::Face) will be faster than entities.select{|e| e.is_a?Sketchup::Face) particularly if the entities collection is large.

Thanks. Unfortunately it does not solve the problem. The face is actually created even without find_faces.

Good tip about grep. I had forgotten about this command.

That is very strange! Entities#intersect_with does not create Faces!

Could you perchance post the model you are using? Without the same surfaces and tools it is difficult to diagnose what else might be going wrong.

Sure. I sent the link in my original post test_forum.skp (92.5 KB)

Anyway, I just discovered another detail.

My original surface was obtained from a file by using

definition = mod.definitions.load(area_of_interest)
mod.active_entities.add_instance( definition, Geom::Transformation.new).explode

area_of_interest = ‘location of file’

If I replace the surface with a group created manually it works as expected (or at least as I expect :slight_smile: ). See this second file test_forum(group).skp (94.0 KB)

intersect_with will split existing faces - like the Intersect With context menu do:

Example:

model = Sketchup.active_model
model.start_operation("Intersect")

face1 = model.entities.add_face(
  [0,0,0],
  [9,0,0],
  [9,9,0],
  [0,9,0]
)

group = model.entities.add_group
face2 = group.entities.add_face(
  [1,1,0],
  [9,1,0],
  [9,9,0],
  [1,9,0]
)
tr = Geom::Transformation.new([2, 3, 0])
group.transform!(tr)

recurse = false
hidden = true
model.entities.intersect_with(
  recurse,
  IDENTITY, # Transformation for the calling Sketchup::Entities
  model.entities, # parent (target) Sketchup::Entities
  IDENTITY, # parent (target) transform
  hidden,
  model.entities.to_a # Entities to intersect the calling Sketchup::Entities with
)

group.erase!

model.commit_operation

This is a very strange thing going on here… After erasing the entities there is a new Face reference… I’ll run this through the debugger and see what is going on here.

Here is my output:

Before intersection
#<Sketchup::Entities:0x0000000c299170>
[#<Sketchup::Face:0x0000000c298838>]

After intersection
[#<Sketchup::Face:0x0000000c298838>, #<Sketchup::Face:0x0000000c293748>]

Surfaces to delete
[#<Sketchup::Face:0x0000000c293748>]

Current list of surfaces
#<Sketchup::Group:0x0000000c4204f8>
[#<Sketchup::Face:0x0000000c298838>, #<Sketchup::Face:0x0000000c293748>]
delete...
nil

After delete
[#<Sketchup::Face:0x0000000c292348>, #<Sketchup::Face:0x0000000c292168>]

From a slightly modified version:

require 'sketchup'

def self.cut_surfaces(tools, surfaces)
  puts ""
  puts 'Before intersection'
  puts surfaces.entities
  #orig_surfaces = surfaces.entities.select{|e| e.is_a?(Sketchup::Face)}
  orig_surfaces = surfaces.entities.grep(Sketchup::Face)
  p orig_surfaces

  tools.each do |t|
    arrays  = t.entities.intersect_with false, t.transformation, surfaces.entities, surfaces.transformation, false, surfaces
  end
  puts ""
  puts 'After intersection'
  p surfaces.entities.grep(Sketchup::Face)
  #surfaces.entities.select{|e| e.is_a?(Sketchup::Face)}.each{|e| p e.to_s}

  #del = surfaces.entities.select{|e| (e.is_a?(Sketchup::Face) && (!orig_surfaces.include?(e)))}
  del = surfaces.entities.grep(Sketchup::Face) - orig_surfaces
  puts ""
  puts 'Surfaces to delete'
  p del
  del.each { |face|
    face.material = 'red'
    face.back_material = 'red'
  }
  puts ""
  puts 'Current list of surfaces'
  p surfaces
  #p surfaces.entities
  #surfaces.entities.select{|e| e.is_a?(Sketchup::Face)}.each{|e| p e.to_s}
  p surfaces.entities.grep(Sketchup::Face)

  #surfaces.entities.erase_entities(*del) if del != []
  unless del.empty?
    puts "delete..."
    p surfaces.entities.erase_entities(del)
  end

  puts ""
  puts 'After delete'
  p surfaces.entities.grep(Sketchup::Face)
  return true
end
mod = Sketchup.active_model # Open model
ent = mod.entities # All entities in model

tools = ent.select{|e| e.layer.name == 'fixed_surroundings'}
surfaces = ent.select{|e| e.layer.name == 'area_of_interest'}

cut_surfaces(tools, surfaces[0])

That is very strange… this manually created group - via Ruby API or UI?

Ok - I figured out what is happening.

That group you have, there is a copy of that in another definition in the model - one that is unused but not purged.
When calling erase_entities Sketchup tries to make the group unique - but it does that by making a new copy if the group you are interested in. That means the faces you keep a reference to isn’t the actual faces in the new created group. This is something the docs needs to be improved upon.

The way to deal with this is to call Group.make_unique before you do anything to it - then use the group reference it returns.

Here is a modified version that works:

require 'sketchup'

def self.cut_surfaces(tools, surfaces)
  puts ""
  p surfaces
  surfaces = surfaces.make_unique
  p surfaces

  puts ""
  puts 'Before intersection'
  puts surfaces.entities
  #orig_surfaces = surfaces.entities.select{|e| e.is_a?(Sketchup::Face)}
  orig_surfaces = surfaces.entities.grep(Sketchup::Face)
  p orig_surfaces

  tools.each do |t|
    arrays  = t.entities.intersect_with false, t.transformation, surfaces.entities, surfaces.transformation, false, surfaces
  end
  puts ""
  puts 'After intersection'
  p surfaces.entities.grep(Sketchup::Face)
  #surfaces.entities.select{|e| e.is_a?(Sketchup::Face)}.each{|e| p e.to_s}

  #del = surfaces.entities.select{|e| (e.is_a?(Sketchup::Face) && (!orig_surfaces.include?(e)))}
  del = surfaces.entities.grep(Sketchup::Face) - orig_surfaces
  puts ""
  puts 'Surfaces to delete'
  p del
  del.each { |face|
    face.material = 'red'
    face.back_material = 'red'
  }
  puts ""
  puts 'Current list of surfaces'
  p surfaces
  #p surfaces.entities
  #surfaces.entities.select{|e| e.is_a?(Sketchup::Face)}.each{|e| p e.to_s}
  p surfaces.entities.grep(Sketchup::Face)

  #surfaces.entities.erase_entities(*del) if del != []
  unless del.empty?
    puts "delete..."
    p surfaces.entities.erase_entities(del)
  end

  puts ""
  puts 'After delete'
  p surfaces.entities.grep(Sketchup::Face)
  return true
end
mod = Sketchup.active_model # Open model
ent = mod.entities # All entities in model

tools = ent.select{|e| e.layer.name == 'fixed_surroundings'}
surfaces = ent.select{|e| e.layer.name == 'area_of_interest'}

mod.start_operation("Intersect and Remove", true)
cut_surfaces(tools, surfaces[0])
mod.commit_operation
1 Like

I stand corrected. Is this always the case? I’d swear that in previous testing I found cases where Faces were not created…

Aha! I had noticed that extra Group definition but had no idea what to make of it. I also noticed that the Entities after the erase were different from those before the erase, and again didn’t know what to make of it. This seems like another case in which the shared underpinnings of Groups and Components cause some strange behavior.

To me, the bottom line is that it is hazardous to hold a reference to an Entity across any Ruby API operation that might modify the model. Internally, SketchUp feels free to play loose with the objects behind the Ruby API references.

Manually created.

However I have tried to explode the original group (the one not working) and make the group again. It works fine even in this case.

Thanks guys for looking into this. I will try as soon as I get back home.

I think this is very important. The case I have shared here is a very simple one. In reality I could have a very big surface and a large number of buildings (fixed-surroundings) that I use to clip it. My algorithm is based on the fact that the reference to the original surface will not change. If this is not always the case then I will have to rethink my algorithm.

For a different module of the plugin I have used the rbclipper gem to perform the intersection and then rebuild the surfaces.

What do you think?

I’m not familiar with rbclipper, so I can’t comment on how that would work out.

Thanks. My question was more about the persistence of references to objects during a Ruby session.

It’s this whole concept of groups that complicates things on the back end. Technically they are components, just made unique on demand for optimizations. But the handling of how they are made unique complicates things.

To be honest - I had to tinker a bit to get the example I wanted. intersect_with is the most confusing API methods we have. Even when looking at the underlying source code and internal comments I’m confused. It’s on my list to do a detailed article on this one - and clean up the documentation.

Did you see the essay I posted over on sketchUcation a while ago?

http://sketchucation.com/forums/viewtopic.php?f=180&t=56110

It now seems my statement there that Faces are not created is not entirely true, but I’ve not heard other technical corrections.

Steve

I’ve not seen that before.
I need to dig closer into this to give an assertive description. This method still confuses me as well - I keep having to look back at the source.

hello sir,
i tried the code you suggested to get the intersection between two faces and i managed to do that.

model = Sketchup.active_model

group = model.entities.add_group
f = group.entities.add_face([1.6439.37, 0.8839.37, 0.6139.37], [1.6439.37, 3.4639.37, 0.6139.37], [0.7139.37, 3.4639.37, 1.2539.37], [0.7139.37, 0.8839.37, 1.2539.37])

                recurse = false 
                hidden = true
                model.entities.intersect_with(
                  recurse,
                  IDENTITY, # Transformation for the calling Sketchup::Entities
                  model.entities, # parent (target) Sketchup::Entities
                  IDENTITY, # parent (target) transform
                  hidden,
                  model.entities.to_a # Entities to intersect the calling Sketchup::Entities with
                )
               group.erase!

but when i add group.erase! it erases the entire right face (the one created) and leaves only the intersection line.
how can i delete the two small rectangles created after the intersection to get a clean roof form ? as in your example the 2 faces are planar, in my model they r not.
i joined the model i’m testing on.
thnx in advance.
roof intersection.skp (258.8 KB)