# Outerloop of a group with several faces inside and holes

How can I obtain the outer loop from a group or component that contains more than one coplanar face like this?

coplanar faces.skp (218.5 KB)

With this code:

``````instance = Sketchup.active_model.selection.first
totalArea = 0
facesInInstance = instance.definition.entities.grep(Sketchup::Face)

facesInInstance.each do |unitarea|
totalArea += unitarea.area
end
puts facesInInstance.count
puts Sketchup.format_area(totalArea)
``````

I get the sum of all the faces, but if I obtain the outer loop of each face and remove all duplicates, I still end up with the inner loop. Any ideas?

1 Like

Just â€śbrainstormingâ€ť here â€¦

If we assume that the cumulative length of the outer perimeter edges will always be a larger value than any of the interior sum lengths of inner loops, then a comparison can be made to get the outer perimeter.

After collecting all edges and filtering out duplicates â€¦

Step 2 would be to divide the array of all edges into a nested array of loops. In this case a loop is a collection of edges that are connected via shared vertices.

Step 3 would be Interating the loops array to find the longest loop.

Probably not foolproof, as you might have a inner loop that meanders and has more edges and so longer length than the outer perimeter.

1 Like

Another idea â€¦

Having done step 2 resulting in a nested array of loops, step 3 might be to determine which loop edges do not share an internal face. These would be the inner loops to be skipped leaving the outer perimeter loop.

The idea would be to geometrically sniff out what is â€śinsideâ€ť a loop as opposed to â€śoutsideâ€ť a loop.
A space inside an inner loop is a space shared by the edges that bound that space. The trick is to somehow determine this. Perhaps firing rays from the midpoint of the loop edges to see if they hit one of the other edges?

Thanks @DanRathbun . I think first idea should work. Iâ€™ll try tomorrow.

An alternative is to fill the holes with edge.find_faces and then gather all of the edges that are only connected to a single face.

``````model = Sketchup.active_model
ents = model.active_entities
sel = model.selection

model.start_operation("dummy", true)

grp = ents.grep(Sketchup::Group)[0] # grab the first group in the entities
edges = grp.entities.grep(Sketchup::Edge)

# fill the holes and return any edges that continue to have only one face
outer_loop = edges.select { | edge |
edge.find_faces if edge.faces.size == 1
edge.faces.size == 1 # return true for the outer_loop edges
}

model.abort_operation

sel.clear
puts outer_loop

``````

Good solution, I had not thought about the option of doing a process and interrupting it.

This will be the final result (Although I should also consider the option that the chosen loop is not the appropriate one for what you comment later)

``````module Rtches
module AreaAndOuterPerimeter
extend self
# Find loops
def find_loops(edges)
loops = []
workingEdges = edges.dup # Array copy to work with

until workingEdges.empty?
loop_edges = []
edge = workingEdges.shift
loop_edges << edge

# connectedEdges
current_vertex = edge.start
loop_completed = false

while !loop_completed
connected_edge = workingEdges.find do |e|
e.start == current_vertex || e.end == current_vertex
end

if connected_edge
loop_edges << connected_edge
workingEdges.delete(connected_edge)
current_vertex = (connected_edge.start == current_vertex) ? connected_edge.end : connected_edge.start
loop_completed = true if current_vertex == loop_edges.first.start
else
# No edges connected
break
end
end

# loop to array of loops
loops << loop_edges unless loop_edges.empty?
end

loops
end

instance = Sketchup.active_model.selection.first
allEdges = instance.definition.entities.grep(Sketchup::Edge)
facesInInstance = instance.definition.entities.grep(Sketchup::Face)

totalArea = 0
facesInInstance.each do |unitarea|
totalArea += unitarea.area
end

puts "Total Area "  + Sketchup.format_area(totalArea)

conssideredEdges = allEdges.select{|numberOfFaces| numberOfFaces.faces.count == 1}

# Finding loops
loops = find_loops(conssideredEdges)

#loops perimeters
outer_perimeter = 0
loops.each do |l|
perimeter = 0
l.each do |le|
perimeter += le.length
end
perimeter >= outer_perimeter ? outer_perimeter = perimeter : outer_perimeter
end
puts "Outer Loop Length " + Sketchup.format_length(outer_perimeter)

end
end
``````

Now, it is also best to pass the parentâ€™s transformation (or any cumulative transformation of the instance path) into the `#area` method so that instance scaling is applied to the face area values.

Indeed, youâ€™re absolutely right. In fact, in the rest of the extensions I have considered it.

Thank you @DanRathbun

1 Like

Althougth @sWilliams solution is very tempting and easy to implement I think that aborting the process could be problematic somtimes so, I finally will implement the option with both loops. Thnaks to both @DanRathbun and @sWilliams