How to test if a face will be valid


#1

Hi,
How to test if a face will be valid ?
for exemple:
Sketchup.active_model.entities.add_face([85633.68524760043, -156720.70957342573, 16041.781177255041], [93548.54067123163, -164726.37168133756, 9652.44358096269], [93889.10645925412, -165070.6724738833, 9377.661310890353])

This code will not add face because the triangle is to long. Is it a easyway to test if the triangle will be valid before to create it ?

Best regards


#2
face = Sketchup.active_model.entities.add_face([85633.68524760043, -156720.70957342573, 16041.781177255041], [93548.54067123163, -164726.37168133756, 9652.44358096269], [93889.10645925412, -165070.6724738833, 9377.661310890353])
puts face
# if it is nil then it failed to add the face, 
# otherwise it succeeded and it will be a Sketchup::Face

#3

You need to do

puts face.inspect

since when face is nil, puts will emit a blank line that is very easy to miss.

But in any case that misses the center of the OP’s question, which is to somehow test the points before invoking add_face. And in the end that may be the wrong idea. It would likely take only a minimally smaller amount of time to run some test vs to try add_face and detect when it fails.


#4

Forgive this perhaps dumb question from a person who hasn’t (yet) started to explore using Ruby with SketchUp:

Does puts face.inspect, embedded in an error test, have any side effects?

Is there not a more direct way to test for a nil value?

Edit 2 minutes later: Never mind! @DanRathbun answered this below.


#5

… or …

Yes below. The Object class defines the #nil? boolean method, which all Ruby classes inherit.

unless face.nil?
  # use the face
else
  # bail out
end

… and … if inside a method …

return if face.nil?

#6

Yea, I am wondering why the coordinates need to be 1.5 to 2.6 miles from the origin ?


#7

The effect is to send output to the Ruby Console window. When a lot of output goes there, it can seriously slow down a Ruby.


#8

The #inspect method merely formats a developer friendly string representation of the object. Printing nil to the console prints an empty line but printing nil.inspect prints “nil” (without quotes) which is easier to see.

Typically inspect is not used in the actual extension, just when testing. In the the actual extension the value’s truthiness is tested directly (or the #nil? method is used as Dan shows).


#9

Actually it is puts that is mainly used for debugging or error logging during development and then removed or commented out for the production version. By default, puts invokes the to_s method on any object that isn’t already a string, and for nil to_s generates nothing, not even a space. Explicitly using inspect causes visible output.


#10

OK. Is there any negative effects if anything is output to the Ruby Console (other than I//O overhead) if the Rubly Console isn’t open?


#11

Not that I know of - though of course formatting the string for output is part of the I/O overhead as well as sending the string to the Ruby Console (which itself involves another round of formatting).


#12

Actually neither of p, puts or inspect are typically used in production, but for debugging. At least in a SketchUp extension context.


#13

There are two that come to mind.

pts=[[85633.68524760043, -156720.70957342573, 16041.781177255041],
 [93548.54067123163, -164726.37168133756, 9652.44358096269],
 [93889.10645925412, -165070.6724738833, 9377.661310890353]]
ang=pts[0].vector_to(pts[1]).angle_between(pts[0].vector_to(pts[2]))
if ang>some_minimum and ang<some_maximum
 fac=Sketchup.active_model.entities.add_face(pts)
end

or

dst = pts[2].distance_to_line(pts[0],pts[0].vector_to(pts[1]));
if dst>some_minimum
 fac=Sketchup.active_model.entities.add_face(pts)
end

#14

Thanks for your answer,

My script grep the faces of the model and projected them on a plane into a new group.
I scaleup on all point, create the projected face and scaledown the result.
My script work on all model since 2 years but this week we found a bug on a specific model.

The entire ruby quite too long so I simplified it and add manually the face in the script with the value of the model that cause trouble.

@model = Sketchup.active_model
blueVector = Geom::Vector3d.new 0,1,1

initial_points = [[856.3368524760043,-1567.2070957342573,160.41777885028986],
[935.4854067123163,-1647.2637168133756,96.5243973571191],
[938.8910645925412,-1650.7067247388331,93.77667705045559]] # normally this points are grep from the model

face = @model.entities.add_face initial_points

projection_plan = [Geom::Point3d.new(856.4238828783741,-1621.8729329954144,115.20644543635315), Geom::Vector3d.new(-0.0225928, -0.637181, 0.770383)] # normally the projection plan is compute from the model.
scalecoef = 100.0
group = face.parent.entities.add_group
group.name = "projected_face A"
scaleUp = Geom::Transformation.scaling scalecoef
scaleDown =  Geom::Transformation.scaling (1.0 / scalecoef )
holes_loop = []


newpts = []
if face
	holes_loop += (face.loops - [face.outer_loop])
	pts = face.outer_loop.vertices
	pts.each{|vertex| 
		pt = vertex
		newpoint = Geom.intersect_line_plane([pt,blueVector], projection_plan)
		newpts.push(newpoint.transform scaleUp) if newpoint
	}
	newpts.uniq!
	if newpts.length > 2

		puts newpts.inspect
		puts "grou befor face:#{group.valid?}"
			new_face = group.entities.add_face newpts
		puts "group after face:#{group.valid?}"

	end
end
Error: #<ArgumentError: Points are not planar>
<main>:35:in `add_face'
<main>:35:in `<main>'
SketchUp:1:in `eval'

The result in the console is "Points are not planar’ because point are quite aligned.
When I execute my real script this error is not display but the puts “group after face:#{group.valid?}” return a false, the group have been erase and that cause trouble later in the script.

I can check if the point are colinear, … but I looking for a method to know if an array of points is good enouth to create a face without create an error.

Thanks for all your answers


#15

Thanks sdmitch, any advice for get the some_minimum ?


#16

I thinks I need to compute the compactness of the points.
Is anyone know a method that already exist in ruby and the minimum value of Sketchup ?


#17

Not really sure but in this case ang was 1.0128153852026937e-05 radians and dist was 3.473275mm. So something greater than that. It is certainly possible that the distance between the points and/or the distance of the points to the model origin will effect the minimum


#18

Ok thanks ! i will take 2 inches, waiting to compute properly the compactness.

Maybe 2 additional questions:
-> why script return an error where it should return a nil as describe by TIG
-> Why in my entire script it doesn’t return an error but delete silently the group ? ( is it due to @model.start_operation, true and @model.commit ?)


#19

Three points usually add a face - as they’ll at least be coplanar !
But if not the result is nil.
But if there are more not three points, and some are not coplanar then it will throw an error.
Therefore using a begin…rescue…end block is needed to trap for those error cases, and take the various actions that you need in each case…


#20

While TIG was suggesting the begin rescue end blocks, I was doing just that to your code. I found that the initial add_face was not the problem but the new_face was.

@model = Sketchup.active_model
blueVector = Geom::Vector3d.new 0,1,1

initial_points = [[856.3368524760043,-1567.2070957342573,160.41777885028986],
  [935.4854067123163,-1647.2637168133756,96.5243973571191],
[938.8910645925412,-1650.7067247388331,93.77667705045559]] # normally this points are grep from the model

begin #<-- trap error

  face = @model.entities.add_face initial_points

  projection_plan = [Geom::Point3d.new(856.4238828783741,-1621.8729329954144,115.20644543635315), Geom::Vector3d.new(-0.0225928, -0.637181, 0.770383)] # normally the projection plan is compute from the model.
  scalecoef = 100.0
  group = face.parent.entities.add_group
  group.name = "projected_face A"
  scaleUp = Geom::Transformation.scaling scalecoef
  scaleDown =  Geom::Transformation.scaling (1.0 / scalecoef )
  holes_loop = []


  newpts = []
  if face
    holes_loop += (face.loops - [face.outer_loop])
    pts = face.outer_loop.vertices
    pts.each{|vertex|
      pt = vertex
      newpoint = Geom.intersect_line_plane([pt,blueVector], projection_plan)
      newpts.push(newpoint.transform scaleUp) if newpoint
    }
    newpts.uniq!
    if newpts.length > 2

      puts newpts.inspect
      puts "group before face:#{group.valid?}"
      begin
        new_face = group.entities.add_face newpts
        puts "group after face:#{group.valid?}"
      rescue
        puts "error creating new_face"
      end

    end
  end
rescue
  puts "error creating initial face"
end