Loops
-
Hello to all,
I have a problem and wonder if someone can help me. A small example will illustrate the problem:
` def helpme
ents = Sketchup.active_model.entities
a0 = [0, 0, 0]
a1 = [5, 0, 0]
a2 = [5, 3, 0]
a3 = [0, 3, 0]
face = ents.add_face(a0, a1, a2, a3)
for i in 0..3
puts face.vertices[i].position
enddosomething(face)
end`
There is a difference in the order of the four points shown in the output and the order of points, used in the definition of the face. The outcommented method needs to do something based on the original sequence. A solution or at least a hint would be appreciated.
-
You provided the points in anti-clockwise order as seen from above, which means the normal vector to the face would point upward (+z). Entities#add_face automatically forces the normal of a face drawn at z=0 to point downward, which causes your vertices to be traced in the opposite (clockwise) order. You can reverse the face after drawing it to correct for this, but there is no option by which you can turn off this "feature".
-
Try making the
z=1
for all of those 4 points.
You should then find that the vertices' points will now report in the same order.When you make a face the clockwise/counter-clockwise ordering of the points dictates the face.normal...
When you get theface.outer_loop.vertices
these are returned counter-clockwise around theface.normal
[inner loops go the other way!].
When you draw a flat face atz=0
it ALWAYS faces downwards - even when you order the points counter-clockwise and you'd expect it the look upwards.
So in that unique case the points were given as if the face were to look up, BUT just as with a manual face creation at z=0, the face is made with its normal reversed.So when you return the points of that
z=0
face they'll appear in reverse order compared to the original points.
A simple trap to always end up with a face withface.normal==Z_AXIS
- even when it's atz=0
- is to check thus:
face.reverse! if face.vertices[0].z==0 && face.normal.parallel?(Z_AXIS) && face.normal==Z_AXIS.reverse
-
@slbaumgartner said:
You provided the points in anti-clockwise order as seen from above, which means the normal vector to the face would point upward (+z). Entities#add_face automatically forces the normal of a face drawn at z=0 to point downward, which causes your vertices to be traced in the opposite (clockwise) order. You can reverse the face after drawing it to correct for this, but there is no option by which you can turn off this "feature".
Thanks for your quick response! It helps a lot but still it is not the total solution. A clockwise definition with four points can take place in four different way's, depending on which point is the first one. I tried the four possibilities in my little method. The response is still a bit confusing!!! I need the exact order.
-
Still not happy with your answers.
Take a look at the next method, based on my old one.` def helpme(a0, a1, a2, a3)
ents = Sketchup.active_model.entities
face = ents.add_face(a0, a1, a2, a3)
for i in 0..3
puts face.vertices[i].position
enddosomething
end`
There are eight possibilities to define the rectangle but the output of the method will give seven times the same answer and exactly once a different one. I need all eight different answers. The dependency of clockwise/ccw is not really the problem because the method 'dosomething' already copes with this item.
-
Aren't you making this harder than it needs to be?
You already have the 4 original points.
Those can be put into an array [called 'points
'].
You can reverse the face if it's at z=0 and the loop is c/cw...
You then have an array of the face.vertices [called 'verts
']...
You then extract an array of the vertices' points [called 'vpoints
'].
You iterate the 'points
' and compare them with the elements of 'vpoints
'.
When you have a match you know the index of the 'vpoints
' match then rearrange 'verts
' into the same order as 'points
'.
e.g. something like this...
` points=[a0,a1,a2,a3]fix face.normal if z==0
verts=[va,vb,vc,vd]
pverts=[va.position,vb.position,vc.position,vd.position]
indx=0
points.each_with_index{|p,i|
if p==pverts[i]
indx=i
break
end
}
verts_sorted=verts[indx..-1]
verts.sorted=verts_sorted+verts[0..indx-1] if indx>0` -
Thanks TIG
I think the solution indeed lies in matching - what you called - points and vpoints. I'm going to give it a try. It is a lot of work because my method 'dosomething' really exists of a couple of tranformations that required already all my knowledge of maths!!! -
@ahjkuipers said:
Still not happy with your answers.
Take a look at the next method, based on my old one.` def helpme(a0, a1, a2, a3)
ents = Sketchup.active_model.entities
face = ents.add_face(a0, a1, a2, a3)
for i in 0..3
puts face.vertices[i].position
enddosomething
end`
There are eight possibilities to define the rectangle but the output of the method will give seven times the same answer and exactly once a different one. I need all eight different answers. The dependency of clockwise/ccw is not really the problem because the method 'dosomething' already copes with this item.
There are eight possible ways to list the points of the rectangle, but since Entities#add_face will force the normal to be downward when z==0, there are actually only four possible returns in that case. All eight can only occur when the Face is not at z==0 so you can get both upward and downward normals.
However, Entities#add_face does a lot of work behind the scenes. In addition to forcing a downward normal at z==0, it checks for duplication and intersection of Entities and edits both existing Entities and your new Face to account for them. If you run helpme two times in a row without deleting the Face between, add_face will detect that the two Faces are geometrically the same (despite the original point ordering) and will return the pre-existing Face. You can add 'puts face' in your code to confirm this - same object ID! That is probably why you are seeing the same results seven times (did you delete the Face before the eighth?). I tried it while deleting the Face between cases, and I got four distinct results as expected at z==0.
You don't say anything about how dosomething works or why the Vertex ordering matters, so the best advice we can offer is something like TIG provides above, which remembers the original ordering of the points and uses this to sort the Vertices. It is possible there is a different, better way to accomplish dosomething's purpose.
-
Thank for your help. I think your answers bring the solution within sight. My apologies for the rather stupid 'dosomething' method. At least I should have written 'dosomething(face, ..)'. And there exactly lies the bottleneck. Even if all the entities are erased at the very beginning of 'helpme' the problem still occurs in the method 'dosomething(face, ..)'. But I can smell the scent of a solution.
-
Finally: the solution
The face I used was part of another face. And in that case it seems that the control over the order in which the vertices of the smaller face appear is taken over by the other face. Your answers helped me to discover that phenomenon. Is this assumption correct?
-
@ahjkuipers said:
Finally: the solution
The face I used was part of another face. And in that case it seems that the control over the order in which the vertices of the smaller face appear is taken over by the other face. Your answers helped me to discover that phenomenon. Is this assumption correct?
There are situations in which add_face will reverse the Vertex ordering of a Face to keep it consistent with other Faces that it touches or intersects. That is probably what you are seeing.
My experience has been that the operation of add_face is so loaded with undocumented special cases (and some bugs) that there are only two safe ways to deal with it: either make no assumptions at all and search or test the Entities collection after it completes, or very carefully try all possible test cases to make sure that you know what it does for all of them. When user input is involved, it is very hard to be certain there are no remaining bugs...
-
There is no guaranty in the API that the order of the vertices of the face is the same as the input points. As slbaumgartner mentions, there is a lot that goes on when you call add_face - SketchUp invokes it's merge operation that may create additional entities as it merges vertices and splits edges etc.
What are you doing to the vertices afterward that require the same order?
-
The troubles appear in the following simplified case.
def example ents = Sketchup.active_model.entities ents.clear! # body pt0 = [0, 0, 0] pt1 = [10, 0, 0] pt2 = [10, 6, 0] pt3 = [0, 6, 0] ents.add_face(pt0, pt1, pt2, pt3).pushpull(-8) # protrusion 1 pt0 = [2, 2, 8] pt1 = [4, 2, 8] pt2 = [4, 4, 8] pt3 = [2, 4, 8] ents.add_face(pt0, pt1, pt2, pt3).pushpull(6) center = [3, 2, 10] ents.add_circle(center, [0, 1, 0], 0.5) # protrusion 2 pt0 = [7, 2, 8] pt1 = [9, 2, 8] pt2 = [9, 4, 8] pt3 = [7, 4, 8] ents.add_face(pt0, pt1, pt2, pt3).pushpull(6) center = [9, 3, 10] ents.add_circle(center, [1, 0, 0], 0.5) end
I succeeded in writing a method that is able to place the spikes at all six (maybe more) sides of the body, no matter how the body is transformed in 3D. The problem is how to determine the face on which the circles (drilling holes) are placed. In the mean time I have found a solution that exists of an extra boolean parameter in the parameterlist of the method. But that's not a satisfactory solution: the choice of the parametervalue is a matter of trial and error (not such an attempt with just two possibilities). The method that generates the spikes now is something as 'dosomething(face, extrusion, semaphoor = true)'. I was hoping that all your answers lead me to the conclusion that the trial and error parameter 'semaphoor' is redundant.
-
def example ents = Sketchup.active_model.entities ents.clear! # body pt0 = [0, 0, 0] pt1 = [10, 0, 0] pt2 = [10, 6, 0] pt3 = [0, 6, 0] ents.add_face(pt0, pt1, pt2, pt3).pushpull(-8) # protrusion 1 pt0 = [2, 2, 8] pt1 = [4, 2, 8] pt2 = [4, 4, 8] pt3 = [2, 4, 8] ents.add_face(pt0, pt1, pt2, pt3).pushpull(6) center = [3, 2, 10] fas=ents.grep(Sketchup;;Face) cir=ents.add_circle(center, [0, 1, 0], 0.5) ents.add_face(cir) fa=(ents.grep(Sketchup;;Face)-fas)[0] fa.material='red' if fa fa.pushpull(-2) if fa # protrusion 2 pt0 = [7, 2, 8] pt1 = [9, 2, 8] pt2 = [9, 4, 8] pt3 = [7, 4, 8] ents.add_face(pt0, pt1, pt2, pt3).pushpull(6) center = [9, 3, 10] fas=ents.grep(Sketchup;;Face) cir=ents.add_circle(center, [1, 0, 0], 0.5) ents.add_face(cir) fa=(ents.grep(Sketchup;;Face)-fas)[0] fa.material='green' if fa fa.pushpull(-2) if fa end
This works.
get all of the faces in 'ents' before adding the new face based on the circular edges.
Then 'fa' is the new face...
I colored them red & green so you can see which is which... -
Problem solved!!!
It's all about vectors. One of the arguments of the method that is resposable for creating the protrusions must be a vector or the order of the four given points of the rectangle must choosen in a way that the vector can be derived as the difference between the first two points of de rectangle.` def findface(pt)
arr = Sketchup.active_model.entities.grep(Sketchup::Face)
arr.each {|f| return f if f.classify_point(pt) == Sketchup::Face::PointInside}
return nil
enddef body
ents = Sketchup.active_model.entities
ents.clear!
f = ents.add_face([0, 0, 0], [60, 0, 0], [60, 40, 0], [0, 40, 0])
f.pushpull(-30)
enddef protr(array, extrusion)
ents = Sketchup.active_model.entities
base = ents.add_face(array)
arcvector = array[1].vector_to(array[0])
basenormal = base.normal
depthvector = basenormal.cross(arcvector).reverse
width = array[1].distance(array[0])
depth = array[2].distance(array[1])
basenormal.length = extrusion - width/2
basecenter = Geom::Point3d.new
basecenter.x = (array[0].x + array[1].x)/2
basecenter.y = (array[0].y + array[1].y)/2
basecenter.z = (array[0].z + array[1].z)/2
center = basecenter + basenormal
base.pushpull(extrusion + 1)
ents.add_arc(center, arcvector, depthvector, width/2, 0, Math::PI)
basenormal.length = extrusion + 0.5
findface(basecenter + basenormal).pushpull(-depth)
ents.add_circle(center, depthvector, 1)
findface(center).pushpull(-depth)
enddef test
body
protr [[22.0, 8.0, 30.0], [22.0, 20.0, 30.0], [10.0, 20.0, 30.0], [10.0, 8.0, 30.0]], 14.0
protr [[35.0, 8.0, 30.0], [47.0, 8.0, 30.0], [47.0, 20.0, 30.0], [35.0, 20.0, 30.0]], 14.0
end`
Advertisement