Create a face entity with outer and inner loops

Hi,

I am working on a plug-in for the SketchUp Rubi API able to read a text file containing a some geometrical information and create a SketchUp model with that.

The surfaces in my model are flat and rectilinear (no curved shapes) but often they consist in an outer loop plus some inner loops. My question is how can I create a new face in model made of outer + inner loops as the one in the image below

image

Best regards, Carlos

First create a face using the outer loop then add and erase faces using the inner loops.

Hi sdmitch

thank you for your answer, It is what I was looking for :slight_smile:

ents = Sketchup.active_model.entities
# points to be used in the model
pt1 = [-100, -100, 0]
pt2 = [ 100, -100, 0]
pt3 = [ 100,  100, 0]
pt4 = [-100,  100, 0]
pt5 = [-50, -50, 0]
pt6 = [ 50, -50, 0]
pt7 = [ 50,  50, 0]
pt8 = [-50,  50, 0]
# add outer loop
outerLoop = ents.add_face pt1, pt2, pt3, pt4 
# add inner loop
innerLoop = ents.add_face pt5, pt6, pt7, pt8
# remove inner loop
ents.erase_entities ents[-1] 

image

If performance is an issue you can create a mesh with a hole in one command. Add the connection line between the outer and inner loop twice (in reversed direction) and the result will be a face with a hole or multiple holes.
Use the entitities.fill_from_mesh command to increase more speed.

1 Like

More secure :

ents.add_face(pt5, pt6, pt7, pt8).erase!

well, performance might be an issue, models could have many faces. Hopefully most of them have no holes though.

I tried something like the thing you are suggesting, to be added to my above script

pm = Geom::PolygonMesh.new
pm.add_point(pt1) # 1
pm.add_point(pt2) # 2
pm.add_point(pt3) # 3
pm.add_point(pt4) # 4
pm.add_point(pt5) # 5
pm.add_point(pt6) # 6
pm.add_point(pt7) # 7
pm.add_point(pt8) # 8
#pm.add_polygon(1, 2, 3, 4, 1, 5, 6, 7, 8, 5) # innerLoop same orientation than outer
pm.add_polygon(1, 2, 3, 4, 1, 5, 8, 7, 6, 5) # innerLoop reverse orientation
ents.fill_from_mesh(pm)

But for some reason no surface is appearing in the model. How should this be done?

Thanks for your reply

Thanks, that works fine. and I agree It seems safer.

In line with the Initial question. What if after creating the face with a hole I want a second face to cover the hole (see right panel in figure below). I tried simply

 ents = Sketchup.active_model.entities
# points to be used in the model
pt1 = [-100, -100, 0]
pt2 = [ 100, -100, 0]
pt3 = [ 100,  100, 0]
pt4 = [-100,  100, 0]
pt5 = [-50, -50, 0]
pt6 = [ 50, -50, 0]
pt7 = [ 50,  50, 0]
pt8 = [-50,  50, 0]
# add outer loop
outerLoop = ents.add_face(pt1, pt2, pt3, pt4) 
# add inner loop
innerLoop = ents.add_face(pt5, pt6, pt7, pt8).erase!
# add new surface covering the Hole
ents.add_face(pt5, pt6, pt7, pt8)

But It is not working as I expected. Somehow the second surface that covers the Hole is missing (see left figure below).

image

I can make it like this:
…
# add outer loop
outerLoop = ents.add_face(pt1, pt2, pt3, pt4)
# add inner loop
innerLoop = ents.add_face(pt5, pt6, pt7, pt8)

but that requires me to now upfront that a hole in a surface with holes will be filled up. But this is not the case since I am reading geometrical information from a text file, where the surfaces covering the holes are independent of the surface with holes.

I could read all the text file and then look for Surfaces that cover a hole in any of the surfaces with holes of the geometry, but I would be happy to avoid this search. Do you have any idea to solve this problem?

Thanks, Carlos

You need to close the polygon with creating a reversed connection line.
The inner loop should be reversed with the outer loop

pm = Geom::PolygonMesh.new
pm.add_point([ 0, 0, 0]) # 1
pm.add_point([ 10, 0, 0]) # 2
pm.add_point([ 10, -10, 0]) # 3
pm.add_point([ 0, -10, 0]) # 4
pm.add_point([ 3, -3, 0]) # 5
pm.add_point([ 6, -3, 0]) # 6
pm.add_point([ 6, -6, 0]) # 7
pm.add_point([ 3, -6, 0]) # 8
pm.add_polygon(1, 2, 3, 4, 1, 5, 8, 7, 6, 5, 1)
Sketchup.active_model.entities.fill_from_mesh(pm)

image

1 Like

I think it is a “timing” problem which will probably not exist when you are reading the text file. If you group the outer_loop and inner_loop or the cover, the problem is solved.

ents = Sketchup.active_model.entities
# points to be used in the model
pt1 = [-100, -100, 0]
pt2 = [ 100, -100, 0]
pt3 = [ 100,  100, 0]
pt4 = [-100,  100, 0]
pt5 = [-50, -50, 0]
pt6 = [ 50, -50, 0]
pt7 = [ 50,  50, 0]
pt8 = [-50,  50, 0]
grp = ents.add_group; gents = grp.entities
# add outer loop
outerLoop = ents.add_face(pt1, pt2, pt3, pt4) 
# add inner loop
innerLoop = ents.add_face(pt5, pt6, pt7, pt8).erase!
# add new surface covering the Hole
gents.add_face(pt5, pt6, pt7, pt8)
grp.explode

It would be helpful if you could post a sample of the text file.

Hi Guy, For some reason this is not working for me, even copy pasting in the ruby console…
I am using SketcUp 2018 pro, Do you have any idea why?. It would be great to make my faces with holes this way.

are you adding the first into your arrays at the end…

ary = [1, 2, 3]
ary.push(ary[0])
# => [1, 2, 3, 1]

it’s a mistake I always make with :add_polygon…

john

Hi Jonh,

actually I got the problem now. It works when I eliminate the person at the origo!. My bad , sorry.
I will investigate this way of creating faces with holes Thanks!

Dear Smitch,

by a timing problem you mean that If I first add_face for all the holes and then I create the surface with the holes using add_face for outerLoop + add_face.erase! It will work?.

here is an easy example of the text file I am reading to create my geometry.

BlockBegin Corner section
Pt 1	0.0	    0.0	   0.0
Pt 2	0.0	    0.0	   20.0
Pt 3	10.0	0.0	   10.0
Pt 4	10.0	0.0	   15.0
Pt 5	20.0	0.0	   10.0
Pt 6	20.0	0.0	   15.0
Pt 7	30.0	0.0	   10.0
Pt 8	30.0	0.0	   15.0
Pt 9	40.0	0.0	   10.0
Pt 10	40.0	0.0	   15.0
Pt 11	50.0	0.0	   0.0
Pt 12	50.0	0.0	   20.0
BlockEnd Corner section
BlockBegin Surface section
LSurf 1 3 
1 11 12 2
3 5 6 4
7 9 10 8
LSurf 2 1  
3 5 6 4
LSurf 3 1  
7 9 10 8
BlockEnd Surface section

First Block is a Corners List and the second block defines the Surfaces in the model. The first line of a SurfaceBlock, gives you the surfaceNumber and the number of Loops in the surface (i.e LSurf 1 3) Surface 1 has 3 Loops so the block will have 3 more lines one for outerLoop and other 2 for innerLoops. In my example Surfaces 2 and 3 are the holes that are refilled.

In general a model has many Surfaces and the numbering is completely arbitrary, meaning that given a surface with holes I have no way to know if its holes are filled up or not by a later(or previous) Surface in the file, Unless a Investigate that. This means that I cannot group the faces as you suggest because I dont know which Surfaces form the face with filled holes.

Hi Carlos,

I don’t get what you’re trying to achieve here :

In this code you are trying to recreate the face you’ve just deleted in the same container… Why so ?

# add inner loop
innerLoop = ents.add_face(pt5, pt6, pt7, pt8).erase!
# add new surface covering the Hole
ents.add_face(pt5, pt6, pt7, pt8)

Anyway if you really want to recreate it, you just need to force the geometry to merge which is not always the case when scripting.

Assuming all your geometry is inside the same group just use the Sketchup::Edge.find_faces method like so :
ents.to_a.grep(Sketchup::Edge).each{|e| e.find_faces}

This have your geometry correctly merged and avoid having two separate faces with an outter edge shell.

You only need to create a temporary group and explode it after adding surface 2 and again for surface 3 as I demonstrated in my last code example.

It seems that the temporary group is still needed even when reading input from file as demonstrated by the following code and Gif.

mod = Sketchup.active_model
ent = mod.active_entities
sel = mod.selection
SKETCHUP_CONSOLE.clear
data_input=File.open('c:/users/public/sketchup/test/test.txt')
while  !data_input.eof?
  case data_input.readline.strip
  when "BlockBegin Corner section"
    corner = []
    data = data_input.readline.strip
    begin
      typ,num,x,y,z=data.split(" ")
      corner[num.to_i] = [x.to_f,y.to_f,z.to_f]
      data = data_input.readline.strip
    end until data.split(" ")[0]=="BlockEnd"
  when "BlockBegin Surface section"
    begin
      sur,num,cnt = data_input.readline.strip.split(" ")
      if sur == "LSurf"
        if cnt.to_i > 1
          for i in 0...cnt.to_i
            p1,p2,p3,p4 = data_input.readline.strip.split(" ")
            fac = ent.add_face(corner[p1.to_i],corner[p2.to_i],corner[p3.to_i],corner[p4.to_i])
            fac.erase! if i > 0
          end
        else
          p1,p2,p3,p4 = data_input.readline.strip.split(" "); grp = ent.add_group
          fac = grp.entities.add_face(corner[p1.to_i],corner[p2.to_i],corner[p3.to_i],corner[p4.to_i])
          grp.explode;
        end
      end
    end while sur=="LSurf"
  end
end
data_input.close

Be carefull, in that case it will works (geometry included), but when exploding temp subgroup if edges are crossing each other with parent group it won’t merge the geometry and create faces. Forcing geometry merging will be required.

Hi milmandre,

thanks for your comments. The point of creating a surface with a hole and then covering the hole with another surface is that each surface might have different properties (say color, material,…). For example If you think in the facade of a building, the surface with a hole could be made of a brick material and the surface covering the hole could be made of glass, that’s why I need both of them and not just their union.

Yea, fill_from_mesh require the target Entities collection to be empty.

(Though, it should have raises an error instead of simply nothing…)

add_faces_from_mesh can add to existing entities collections, but it’s not as fast as fill_from_mesh.

2 Likes