Losing face

While writing PC code to draw some “buckyballs”, I noticed some erratic behavior of faces that have specified duplicate points. Behavior seen on a PC under SU21(w/o Build) and SU22.
To test, open the Ruby console and run this self documenting example code, then wait while for SU to clean up - the red faces are deleted.

The methods .add_polygon and .add_faces_from_mesh seem to discard duplicate points, but the faces are treated differently. Some are erased by SU’s garbage collection and some are not.
Build .add_face seems to act the same as mesh faces do.

The entities method .add_face allows the face (p1, p2, p3, p1), I assume this is to support both open and closed polygon specification. I found no other combination of duplicate points that were allowed.

Why are some faces erased while others remain? And why no error message/return? Is there a defect in the way I code?

module BB_test
  def BB_test.bbtest      
    p1 = Geom::Point3d.new( 0.0, 0.0, 2.0)    
    p2 = Geom::Point3d.new(-1.0, 0.0, 1.0)
    p3 = Geom::Point3d.new( 0.0, 0.0, 0.0) 

    puts
    puts "Red faces will be erased"
    
    mesh1 = Geom::PolygonMesh.new 
    ret = mesh1.add_polygon(p1, p2, p3, p1)  
    puts
    puts "PolygonMesh"
    puts("add_polygon(p1,p2,p3,p1) returns: " + ret.inspect + " Face will be erased")  
    puts "              mesh1.count_points= " + mesh1.count_points.to_s
    (mG1 = Sketchup.active_model.entities.add_group).entities.add_faces_from_mesh(mesh1,0)      
    mG1.material = 'red'
    
    mesh2 = Geom::PolygonMesh.new 
    ret = mesh2.add_polygon(p1, p2, p2, p3)  
    puts("add_polygon(p1,p2,p2,p3) returns: " + ret.inspect + " Face will be erased")  
    puts "              mesh2.count_points= " + mesh2.count_points.to_s
    (mG2 = Sketchup.active_model.entities.add_group).entities.add_faces_from_mesh(mesh2,0)      
    mG2.material = 'red'

    mesh3 = Geom::PolygonMesh.new 
    ret = mesh3.add_polygon(p1, p1, p2, p3)  
    puts("add_polygon(p1,p1,p2,p3) returns: " + ret.inspect + " Face will remain")  
    puts "              mesh3.count_points= " + mesh3.count_points.to_s
    (mG3 = Sketchup.active_model.entities.add_group).entities.add_faces_from_mesh(mesh3,0)  
    mG3.material = 'magenta'
    
    mesh4 = Geom::PolygonMesh.new 
    ret = mesh4.add_polygon(p1, p2, p3)  
    puts("   add_polygon(p1,p2,p3) returns: " + ret.inspect + " Face will remain")  
    puts "              mesh4.count_points= " + mesh4.count_points.to_s
    (mG4 = Sketchup.active_model.entities.add_group).entities.add_faces_from_mesh(mesh4,0)  
    mG4.material = 'magenta'
    
    
    f = (fG1 = Sketchup.active_model.entities.add_group).entities.add_face(p1, p2, p3, p1)
    fG1.material = 'blue'
    puts
    puts "Group"
    puts("  add_face(p1,p2,p3,p1)  returns: " + f.class.to_s + " Face will remain")  
    puts "     f.outer_loop.vertices.count= " + f.outer_loop.vertices.count.to_s
    puts("  add_face(p1,p1,p2,p3)  returns- <ArgumentError: Duplicate points in array>")  
    puts("  add_face(p1,p2,p2,p3)  returns- <ArgumentError: Duplicate points in array>")  
    puts("  add_face(p1,p2,p3,p3)  returns- <ArgumentError: Duplicate points in array>")

    f = (fG2 = Sketchup.active_model.entities.add_group).entities.add_face(p1, p2, p3)
    fG2.material = 'blue'
    puts("  add_face(p1,p2,p3)     returns: " + f.class.to_s + " Face will remain")  
    puts "     f.outer_loop.vertices.count= " + f.outer_loop.vertices.count.to_s

    bf1 = (bG1 = Sketchup.active_model.entities.add_group).entities.build { |builder|
        face1 = builder.add_face([p1, p2, p3, p1])
        face1.material = 'red'
        puts  
        puts"Build"  
        puts("  builer.add_face([p1,p2,p3,p1])   returns: " + face1.class.to_s + " Face will be erased" )  
        puts "         face1.outer_loop.vertices.count= " + face1.outer_loop.vertices.count.to_s
      }
    bf2 = (bG2 = Sketchup.active_model.entities.add_group).entities.build { |builder|
        face2 = builder.add_face([p1, p2, p2, p3])
        face2.material = 'red'
        puts("  builder.add_face([p1,p2,p2,p3])  returns: " + face2.class.to_s + " Face will be erased" )
        puts "         face2.outer_loop.vertices.count= " + face2.outer_loop.vertices.count.to_s
      }
    bf3 = (bG3 = Sketchup.active_model.entities.add_group).entities.build { |builder|
        face3 = builder.add_face([p1, p1, p2, p3])
        face3.material = 'magenta'
        puts("  builder.add_face([p1,p1,p2,p3])  returns: " + face3.class.to_s + " Face will remain" )
        puts "         face3.outer_loop.vertices.count= " + face3.outer_loop.vertices.count.to_s
      }

    t = Geom::Transformation.new(Geom::Point3d.new(0, 0, 2.5))
    mG1.transform!(t*t*t*t*t*t*t*t*t)
    mG2.transform!(t*t*t*t*t*t*t*t)
    mG3.transform!(t*t*t*t*t*t*t)
    mG4.transform!(t*t*t*t*t*t)
    fG1.transform!(t*t*t*t*t)
    fG2.transform!(t*t*t*t)
    bG1.transform!(t*t*t)
    bG2.transform!(t*t)
    bG3.transform!(t)
  end    
end

I ran your code in 2022. All faces remained. (The red ones were not erased.)

My expectation was that the faces should not be erased, but …

Below I am inspecting generated faces by setting a reference to the selection set:
ss = Sketchup.active_model.selection
… and then entering the groups and selecting the face.


EntitiesBuilder#add_face()

I also expected that the vertex count would be correct afterward, especially for those made with EntitiesBuilder. But this is not the case. The outer loop vertex count afterward (ie, outside the builder block) is still 4 instead of 3.

For builder.add_face([p1, p2, p3, p1]) we get …

f = ss[0]
=> #<Sketchup::Face:0x0000022849784178>
v = f.outer_loop.vertices
=> [
  #<Sketchup::Vertex:0x000002284a363f48>, #<Sketchup::Vertex:0x000002284a363f20>,
  #<Sketchup::Vertex:0x000002284a363ef8>, #<Sketchup::Vertex:0x000002284a363f48>
]

The first and last vertices are duplicates.

This causes SketchUp not to allow selecting the face with a surface click (after entering the group.) I had to use a window crossing pick and then remove an edge from the selection set to have just the face selected.

I believe you’ve found a bug at least with the new EntitiesBuilder.

Since this is the new desired geometry building class, I think the Extensibility team will want to fix this.


Entities#add_faces_from_mesh()

I always found dealing with Geom::PolygonMesh to be complex and avoided it when possible.

However, this paradigm also produces a triangular face with 4 vertices. This is also not desirable.

v = f.outer_loop.vertices
=> [
  #<Sketchup::Vertex:0x000002284a388a78>, #<Sketchup::Vertex:0x000002284a388a78>,
  #<Sketchup::Vertex:0x000002284a388a50>, #<Sketchup::Vertex:0x000002284a388a28>
]
v[0] == v[1]
=> true
v[0].position == v[1].position
=> true

Notice how the first and second vertex in the loop array are the same object.

Weirdly, I do not have an issue manually selecting this face when it has 4 vertices.


Entities#add_face()

The vertex count of the generated faces (in the blue groups) is correct at 3.

As to why this method was written this way, who knows ? This was done by people no longer on the SketchUp team perhaps 20 years ago.

It is a nice Easter egg that it will ignore the trailing duplicate point.


Trick to force SketchUp to merge a duplicate vertex

When inside the group … and you have identified a duplicate vertex, v[0] in this case, if you transform the vertex using a zero length translational vector, SketchUp does merging:

m = Sketchup.active_model
ents = m.active_entities
zt = Geom::Transformation.translation([0,0,0])
ents.transform_entities( zt, v[0] )

If the group is not open for edit, then ents should be set to group.entities.

After the transform-merge, the face should only have 3 vertices.

2 Likes

EDIT: I commented out the builder part and reran in 2021 and again all (6) faces remained.


So the main (and obvious) solution is in uniquifying arrays of Geom::Point3d objects … before passing them to geometry creation methods.

I’ve added a TIP article on this subject …

2 Likes

re: I ran your code in 2022. All faces remained . (The red ones were not erased.)

That is curious and maybe it’s a clue, as I have tested on two machines, an ASUS laptop and an Intel desktop, both running WIN10.

I can reproduce the problem at will on both machines, under both SU21 and SU22.

Is there anything I can do to gather info for the debug team? Core dump? I am willing chat and or screen share.

This reminds me of a problem I had years ago when I successfully installed SU and NetBeans on two PC machines, but a third PC simply would not interface SU to NetBeans. Never got it to work and don’t recall any details

Thanks for the follow up and the fix with its explanation.

We should be careful not to mix this issue with SketchUp’s small faces issue. None of the problem faces were removed in my test on 22 or 21. Silent fails for face creation usually is due to face bounding edges being too short for SketchUp’s tolerance.

You can try with larger coordinates for the vertices.

As this does not produce a crash dump and because Trimble by policy does not release debug editions, all you can do is attach code to reproduce the issue(s) to the bug report you file in the API Issue tracker at GitHub.

Please file 2 separate reports. One for Entities#add_faces_from_mesh() and the other for EntitiesBuilder#add_face() methods. This way each issue gets it’s own internal Jira issue and can be fixed independently of the other.

I think @tt_su (Thomas) will want more to fix the new builder because it is the now preferred way to add bulk geometry. I’d caption the issue “EntitiesBuilder#add_face does not de-duplicate vertices” or similar.

There is the possibility that they will not do anything (at least any time soon) with PolygonMesh#add_polygon / Entities#add_faces_from_mesh() because it is really adding more points to the PolygonMesh that it should have. (I did notice in your snippet that you did not tell the mesh how many points it should expect to have when you called the constructor. Doing this is encouraged for speed and memory management.)
In other words the snippet is purposefully fooling the mesh class using poor practice which is easily avoided by uniquifying the point arrays before passing to the PolygonMesh#add_polygon method.
It will be low priority in the stack over more than 500 open issues as it can be avoided by coding defensively.


One thing I did not yet try was to save and reopen the model to see if SketchUp’s automatic Validity Check would merge the duplicate vertices. This would also be a separate issue, but a core issue not an API issue.

Also, I do have “Automatically fix problems when they are found” checked in the General panel of the Preferences dialog.

Here’s what I learned;

  1. Size doesn’t matter. Scaled 100 fold and erasure still occurs.
  2. Used Geom::PolygonMesh.new(3, 1), specifying pts & polys - erasure still occurs.
  3. “Automatically fix problems when they are found” has been checked all along.
    BUT “Notify me when problems are fixed.” was not checked, I checked it. Then retested with one mesh add_polygon(p1, p2, p3, p1) and one build add_face([p1, p2, p3, p1]). A “Problems Detected and Fixed” msg popped up. Clicking on “Show Details…” gives this clue:

"Results of Validity Check.

	CLoop (97095) for CFace (97094) is not closed
	Loop 0 of CFace (97094) is an invalid outer loop
	Entity CFace (97094) should be erased - done
	CLoop (97117) for CFace (97122) is not closed
	Loop 0 of CFace (97122) is an invalid outer loop
	Entity CFace (97122) should be erased - done"

Based on the formatting of the msg, it’s possible the same code, code library or msg library is activated for both mesh and build.
The bigger issue to me is why the faces are erased on my equipment and not yours.

Per your request I’ll file two bug reports. Thanks for taking the time to look into this.

module BB_test
  def BB_test.bbtest
    p1 = Geom::Point3d.new( 0.0, 0.0, 200.0)    
    p2 = Geom::Point3d.new(-100.0, 0.0, 100.0)
    p3 = Geom::Point3d.new( 0.0, 0.0, 0.0) 

    puts
    puts "SU Preference 'Notify me when problems are fixed.' must be checked to see SU fix message"
    puts "Red faces will be erased"

    mesh1 = Geom::PolygonMesh.new(3,1) 
    ret = mesh1.add_polygon(p1, p2, p3, p1)  
    puts
    puts "PolygonMesh"
    puts("add_polygon(p1,p2,p3,p1) returns: " + ret.inspect + " Face will be erased")  
    puts "              mesh1.count_points= " + mesh1.count_points.to_s
    (mG1 = Sketchup.active_model.entities.add_group).entities.add_faces_from_mesh(mesh1)  
		mG1.material = 'red'

    (bG1 = Sketchup.active_model.entities.add_group).entities.build { |builder|
      face1 = builder.add_face([p1, p2, p3, p1])
      bG1.material = 'red'
      puts  
      puts"Build"  
      puts("  builer.add_face([p1,p2,p3,p1])   returns: " + face1.class.to_s + " Face will be erased" )  
      puts "         face1.outer_loop.vertices.count= " + face1.outer_loop.vertices.count.to_s
    }
    t = Geom::Transformation.new(Geom::Point3d.new(0, 0, 250.0))
    mG1.transform!(t*t)
    bG1.transform!(t)    
  end    
end
1 Like

Follow up, I opened ‘EntitiesBuilder#add_face does not de-duplicate vertices, Pushpull on defective face causes SU to lockup. #812
Mentioned you by name in it, updated example code is there too.

This defect can lead to a fatal error: Adding face1.pushpull(50) to the BB_test.bbtest example above will lock up SU on my machine.

Which is …