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?
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.
If I replace the surface with a group created manually it works as expected (or at least as I expect ). See this second file test_forum(group).skp (94.0 KB)
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 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
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.
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.
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.
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)