[TIP] Uniquifying arrays of Point3d and Vector3d objects

Uniquifying arrays of Geom::Point3d or Geom::Vector3d objects

In Ruby each Geom::Point3d or Geom::Vector3d instance object created has it’s own signature and is distinct, even if it’s coordinates match another instance.

But, in many coding scenarios it is desirable or necessary to uniquify arrays of Geom::Point3d or Geom::Vector3d objects … before passing them to geometry creation methods. (Uniquifying means that the duplicates that have matching coordinates need to be pared down to a single item in the array.)

Ruby’s Array class has the #uniq and #uniq! methods. But beware!

These methods use Object#hash and Object#eql? for comparison.
However, the SketchUp Ruby API has not overridden these methods for the Geom::Point3d or Geom::Vector3d classes. So they will return differing values and will not help to find instance objects that have the same coordinates.

What we need is to compare point or vector objects within SketchUp’s merging tolerance (~ 0.001").
Fortunately the API’s Length class does this in comparisons, and the Geom::Point3d#to_a and Geom::Vector3d#to_a methods return an array of [Length, Length, Length]. (How convenient!)

So for example, to leverage Ruby’s uniq’ing methods on an array of points, use …

pt_ary.uniq!(&:to_a)

… which will remove any duplicates based upon comparing the point’s x,y,z Length arrays.

The shorthand above is the same as:

pt_ary.uniq! { |pt| pt.to_a }

Note that this will not convert the members of pt_ary to Length arrays. It only uses them for comparison.

Also, the first duplicate(s) found are kept, the others discarded.

If we want a new uniq’ed array, then we would not use the bang form of the method, instead:

uniq_ary = pt_ary.uniq(&:to_a)

The same applies to arrays of 3D vector objects.


In summation, if a point array is to be passed into a face creation method, it can be uniquified as:

model.entities.add_face(pts.uniq(&:to_a))

Or …

model.entities.build { |builder|
  builder.add_face(pts.uniq(&:to_a))
}

:nerd_face:

3 Likes

SketchUp will allow a face to be created that is what I used to call a ‘sliced donut’. It could be described as two concentric polygons with one line connecting a vertex of each.

Using squares as the polygons, the face has ten vertices, but the array has only eight members when uniq is used…

L = 200.0
S = 100.0
Z = 0.0
pts = [[S,S,Z], [S,-S,Z], [-S,-S,Z], [-L,-L,Z], [L,-L,Z], [L,L,Z], [-L,L,Z], [-L,-L,Z], [-S,-S,Z], [-S,S,Z]]
Sketchup.active_model.entities.add_face pts
pts.length         #-> 10
pts.uniq.length    #->  8

That face is an edge case bug. The shared vertices are not being merged.
Later manual moving of one of those problem vertices shows the issue.

This is not the usual way to create a donut-like face in SketchUp with the API (especially using the new builder class.)

The article is meant as a general tip that can prevent other problem face generation (recently discussed here and in the API issue tracker,) where duplicate points do create problem faces.

But, I’ve reworded it as “it can be uniquified as:” so as not to offend those coders who are fond of problem faces.

Also, the subject is specifically about processing arrays of Geom::Point3d objects having Length coordinates, not arrays of arrays having 3 Float values.