 # Vertices of a Face (Ordered)

This is a continuation of my last technical question where I am trying to find the end face of a followme command.

Now that I have managed to grab the face I am trying to get an ordered list of the vertices of that face.

When I use the vertices method on the face it seems to give the vertices in no particular order.

The documentation is not really clear on whether it provides the vertices in any order or not.

Perhaps if I use the outer_loop method:

I can then get an ordered list of the vertices?

``````     # add a face
face = ents.add_face(cir)

# collect the end position of each edge
verts = []

# these will be in a useable order
face.outer_loop.vertices.each{|v| verts << v.position}
``````

john

3 Likes

That does appear to get them into an ordered array but now I’ve got to find the correct starting point of the array so that transform_by_vectors method applies the correct vector to the correct point:

In the image below the right side of the footing has been properly modified by the transform whereas the left side is jumbled up because the incorrect vectors are being applied to the specific points. Both the points and vectors are ordered in this case but they are not synchronized, or more correctly clocked to the same starting point. The other thing I didn’t think about is that the order may be also reversed even though it is ordered.

It sure would be nice if I had an easier way to modify these faces for an angled cut but unfortunately I can’t rely on a boolean operation in this instance since the two end of the footings may actually contact or overlap each other and then the cut operation would inadvertently cut the other end and vice versa.

Somehow I manage to get myself into some real messy topological problems. I guess this is the plight of a plugin developer.

I often use `verts.push(verts.shift)` until the one I want is first…

I reverse it if I need the opposite winding order…

ymmv…

john

1 Like

So now I need to figure out which point in the array to search for and then once I find it I need to reorder the array so that the order is maintained but the starting point is the correct point that I have identified.

actually, I have occasionally used face.bounds and it’s corners() for some things like this in the past…

john

1 Like

Would something like this work?

``````       ftg_start_vertices.each do |vertex|
if vertex == testpt1
break
else
ftg_start_vertices.shift(ftg_start_vertices.pop)
end
end
``````

Or would the fact that I am altering the array of which I am iterating through cause a problem?

Definitely. Try …

``````      ftg_start_vertices.clone.each do |vertex|
# block code
end
``````
1 Like

I changed the snippet to one that works…

``````ary.push(ary.shift)
``````

I shouldn’t rely on my memory…

beginning end
adding, returns array .unshift .push
returns removed element .shift .pop

john

1 Like

Amazing, it actually seems to be working, this is what I have so far:

``````ftg_start_vertices.clone.each do |vertex|
if vertex == testpt1
break
else
ftg_start_vertices.push(ftg_start_vertices.shift)
end
end
``````

Now I just need to devise a method to check the order and then reverse it if needed. To do this I will probably need to ascertain another test point to compare against.

Here is an idea …

``````# Returns a new array (regardless)
def reorder_array(ary, testpt)
i = ary.index(testpt)
return [] if i.nil? # not found
i > 0 ? ary.values_at(i..-1, 0..i-1) : ary.clone
end
``````

You are attempting a multistep process of creating and modifying geometry where some of the steps use complex methods (like follow me or booleans). The problem is that those methods don’t return sufficiently detailed info about the geometry they create so that you cannot easily select only a subset for the next step. To find the end face of a follow me, you have 2 options:

• create a bounding box to select the end face at the last vertex of the edge array.

• Code your own “follow me” and return the end face.

• Code your own “follow me” which handles the case where the edge array is a closed loop. No need to alter the end face to miter it.

Often we code to create geometry, then modify in the same steps that a user would. But modifying geometry is risky. Sometimes it’s better to query the geometry, then delete it and recreate it in modified form. Users, of course, don’t like to do that because it’s often easier for them to modify and they can adjust their steps based on the feedback they get. But for coders, deletion and recreation is sometimes the best strategy.

It also looks like Ruby has a rotate method:

https://ruby-doc.org/core-2.5.0/Array.html#method-i-rotate-21

2 Likes

I just saw that, was gonna edit the example … but you got the idea.

I have no idea how to create a follow me method nor do I have the programming chops to pull it off.

My best bet is to try and use the built in methods in the API and then cobble together something that can modify the resulting geometry in a fairly predictable and stable fashion.

1 Like

if you create a face plane >> rotate it >> project the points to the plane???

john

1 Like

loops = faces.loops # all of the face’s loops
oloop = face.outer_loop # outermost perimeter
iloops = loops - [oloop] #other inner loops [aka holes]

oloop.vertices are arranged ccw around the face.normal, unless the face is flat and it is exactly at Z=0 and then face.normal==Z_AXIS.reverse just like manually drawing the face. So trap for that.

each of the iloop’s loop.vertices are arranged cw around the face.normal [see above about exception]

face loops are always ordered one after the other… even if its starting vertex might seem random…

2 Likes

Seems to be working. My final code after some corrections:

``````        testpt1 = Geom::Point3d.new(xvalue, @Ftgx1, @Ftgy1)
testpt1.transform!(tr2s)
testpt1.transform!(tr1_ftg)

testpt2 = Geom::Point3d.new(xvalue, @Ftgx2, @Ftgy2)
testpt2.transform!(tr2s)
testpt2.transform!(tr1_ftg)

startloop = new_face2.outer_loop
ftg_start_vertices = startloop.vertices

ftg_start_vertices.clone.each do |vertex|
if vertex.position == testpt1
break
else
ftg_start_vertices.push(ftg_start_vertices.shift)
end
end

if ftg_start_vertices.position != testpt2
ftg_start_vertices.push(ftg_start_vertices.shift)
ftg_start_vertices.reverse!
end

entities2.transform_by_vectors(ftg_start_vertices, ftg_start_vecs)
``````

I would use the rotate method but the push-shift method is more intuitive for me and when I come back to this code at a later date I will be able to follow it better.

1 Like

This chunk of code combines the previous discussion where I am finding the end face of an extrusion plus the code to order the vertices of this face:

``````        testpt1e = Geom::Point3d.new(xvalue, @Ftgx1, @Ftgy1)
testpt1e.transform!(tr2e)
testpt1e.transform!(tr1e_ftg)

testpt2e = Geom::Point3d.new(xvalue, @Ftgx2, @Ftgy2)
testpt2e.transform!(tr2e)
testpt2e.transform!(tr1e_ftg)

dirvec = @pts_aligned[-1] - @pts_aligned[-2]
group2faces = entities2.grep(Sketchup::Face)
endfacelist = group2faces.find_all {|face| face.normal.samedirection?(dirvec) && face.area.round(3) == face_area && face.to_s != new_face2.to_s }
endface = endfacelist[-1]
endloop = endface.outer_loop
ftg_end_vertices = endloop.vertices

ftg_end_vertices.clone.each do |vertex|
if vertex.position == testpt1e
break
else
ftg_end_vertices.push(ftg_end_vertices.shift)
end
end

if ftg_end_vertices.position != testpt2e
ftg_end_vertices.push(ftg_end_vertices.shift)
ftg_end_vertices.reverse!
end

entities2.transform_by_vectors(ftg_end_vertices, ftg_end_vecs)``````

The final result when both ends are given different miters:

Thank-you to everyone who contributed and help me work my way through this, for a while there I was beginning to think that there might not be a solution.

I will say that transform_by_vectors method is a very hand tool to have in one’s toolbox however my problem has been figuring out the vertices of the face so they properly sync with the vectors. This bit of code discovered today will find its way into many more places, I am sure.

1 Like