"Points are no planar" error message when creating a face

I’m trying to create an extension with “grooves” added, but I can’t seem to find the right solution. The plugin should work like this: I select a face, then select an edge and fill in the options in the dialog box (groove width, groove depth, groove offset). The length of the groove is equal to the length of the selected edge. I faced two problems: 1 - How to correctly define the offset vector of the groove? (The groove must always be built on the selected face and move to the opposite edge of this face regardless of the placement of the face relative to the axes). 2 - The task of constructing a face for a groove. The below code I am trying to use is outputting a message “Points are no planar”. Please help me to fix these problems.

Ruby
def create_groove
  model = Sketchup.active_model
  model.start_operation("Create Groove", true)

  group = @selected_face.parent.entities.add_group
  group_entities = group.entities

  edge_vector = @selected_edge.line[1].normalize
  offset_vector = @selected_face.normal * edge_vector
  offset_vector.length = @groove_offset

  # Застосування зміщення для створення початкових точок канавки
  offset_start_point = @selected_edge.start.position.offset(offset_vector)
  offset_end_point = @selected_edge.end.position.offset(offset_vector)

  # Визначення додаткових точок для формування ширини канавки
  point3 = offset_end_point.offset(edge_vector, @groove_width)
  point4 = offset_start_point.offset(edge_vector, @groove_width)

  # Створення лиця безпосередньо з точок
  begin
    new_face = group_entities.add_face(offset_start_point, offset_end_point, point3, point4)
    new_face.pushpull(-@groove_depth, true) if new_face.valid?
  rescue ArgumentError => e
    UI.messagebox("Error creating groove: #{e.message}")
  ensure
    model.commit_operation
  end
end
Ruby

Ruby is an English coding language as well as this forum.
If you code comments were in English we might be able to read them.

For example, what are point3 and point4 used for?

How do you know that the offset_vector is pointing inward toward the face and not outward away from the face?

Sorry. My native language is Ukrainian.

def create_groove
  model = Sketchup.active_model
  model.start_operation("Create Groove", true)

  group = @selected_face.parent.entities.add_group
  group_entities = group.entities

  edge_vector = @selected_edge.line[1].normalize
  offset_vector = @selected_face.normal * edge_vector
  offset_vector.length = @groove_offset

  # Applying offset to create starting points for the groove
  offset_start_point = @selected_edge.start.position.offset(offset_vector)
  offset_end_point = @selected_edge.end.position.offset(offset_vector)

  # Defining additional points to form the width of the groove
  point3 = offset_end_point.offset(edge_vector, @groove_width)
  point4 = offset_start_point.offset(edge_vector, @groove_width)

  # Creating the face directly from points
  begin
    new_face = group_entities.add_face(offset_start_point, offset_end_point, point3, point4)
    new_face.pushpull(-@groove_depth, true) if new_face.valid?
  rescue ArgumentError => e
    UI.messagebox("Error creating groove: #{e.message}")
  ensure
    model.commit_operation
  end
end

You should not create your group until the last millisecond before using it.


The main problem is likely that you are trying to draw the groove in a new subgroup whose origin will be the parent entities origin. So, will the offset points be where you really want them?

What if you just add the groove to the face’s parent entities?

This is what I want to get right. I asked this question in the original thread post.

My asking is meant to prompt you to test a point using the offset vector to see if it lies upon the face’s geometry.

See:

If is does not, then you reverse the vector.

Thanks for the recommendations. I tried to change the code of the create_groove function. Unfortunately I get the non-coplanar points error again. Here’s what I changed:

def create_groove
  model = Sketchup.active_model
  model.start_operation("Create Groove", true)

  edge_vector = @selected_edge.line[1].normalize
  offset_vector = @selected_face.normal * edge_vector
  offset_vector.length = @groove_offset

  # Checking if an offset point lies on a face
  test_point = @selected_edge.start.position.offset(offset_vector)
  if @selected_face.classify_point(test_point) != Sketchup::Face::PointInside
    offset_vector.reverse!
  end

  # Apply offset to create groove start points
  offset_start_point = @selected_edge.start.position.offset(offset_vector)
  offset_end_point = @selected_edge.end.position.offset(offset_vector)

  # Determination of additional points for forming the width of the groove
  edge_vector.length = @groove_width
  point3 = offset_end_point.offset(edge_vector)
  point4 = offset_start_point.offset(edge_vector)

  # Creating a face directly from points on the parent entities of the selected face
  begin
    new_face = @selected_face.parent.entities.add_face(offset_start_point, offset_end_point, point3, point4)
    new_face.pushpull(-@groove_depth, true) if new_face.valid?
  rescue ArgumentError => e
    UI.messagebox("Error creating groove: #{e.message}")
  ensure
    model.commit_operation
  end
end

Okay, the error message is wrong, The points are coplanar, but point3 was outside the selected face.

Bascially, you were using the wrong vector, i.e., the selected edge’s vector which runs along the edge.

I refactored and renamed point3 and point4 and used the offset_vector.

WAS:

    # Determination of additional points for forming the width of the groove
    edge_vector.length = @groove_width  # <<<<-------<< WRONG VECTOR
    point3 = offset_end_point.offset(edge_vector)
    point4 = offset_start_point.offset(edge_vector)

Changed TO:

    # Determination of additional points for forming the width of the groove
    offset_vector.length = @groove_width
    width_start_point = offset_start_point.offset(offset_vector)
    width_end_point   = offset_end_point.offset(offset_vector)

Since the code was done with using offsset_vector’s length to create the two offset points,
we can reuse it but change it’s length again to create the points across the groove (by the width.)

Also, the “copy” argument should not be used in the pushpull method call.

Here is the test module, use: GrooveTest.go to fire the command after selecting the face and edge:

module GrooveTest

  extend self

  @selected_edge = nil
  @selected_face = nil
  @groove_offset = 1.inch
  @groove_width  = 0.25.inch
  @groove_depth  = 0.1.inch

  def go
    model = Sketchup.active_model
    sel = model.selection
    if sel.empty?
      @selected_edge = nil
      @selected_face = nil
    else
      @selected_face = sel.grep(Sketchup::Face).first
      @selected_edge = sel.grep(Sketchup::Edge).first
    end
    if @selected_face.nil? || @selected_edge.nil?
      UI.beep
      return UI.messagebox("A face and an edge must both be selected!")
    end
    points = get_groove_points()
    create_groove(points)
  end

  def get_groove_points
    edge_vector = @selected_edge.line[1].normalize
    offset_vector = @selected_face.normal * edge_vector
    offset_vector.length = @groove_offset

    # Checking if an offset point lies on a face
    test_point = @selected_edge.start.position.offset(offset_vector)
    if @selected_face.classify_point(test_point) == Sketchup::Face::PointOutside
      offset_vector.reverse!
    end

    # Apply offset to create groove start points
    offset_start_point = @selected_edge.start.position.offset(offset_vector)
    offset_end_point = @selected_edge.end.position.offset(offset_vector)

    # Determination of additional points for forming the width of the groove
    offset_vector.length = @groove_width
    width_start_point = offset_start_point.offset(offset_vector)
    width_end_point   = offset_end_point.offset(offset_vector)

    points = [
      offset_start_point, offset_end_point, width_end_point, width_start_point
    ]
    #puts points.inspect
    return points
  end

  def create_groove(points)
    model = Sketchup.active_model
    # Creating a face directly from points on the parent entities of the selected face
    begin
      model.start_operation("Create Groove", true)
      # Working entitites:
      ents = @selected_face.parent.entities
      # Groove face:
      groove_face = ents.add_face(points)
      #puts groove_face.inspect
      groove_face.pushpull(-@groove_depth) if groove_face.valid?
    rescue => e
      UI.messagebox("#{e.class.name} Error creating groove: #{e.message}")
    else
      model.selection.clear
    ensure
      model.commit_operation
    end
  end

end # GrooveTest module
1 Like

Thank you very much. It works. After seeing your code, fixes and explanations, I now understand where I went wrong. Thank you for the lesson and attention to my questions.

3 Likes