"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

I would like to ask you to solve another problem that I am facing and I don’t know how to solve it. The tool code works well on unscaled objects. But if I scale the group, the face creation points are calculated incorrectly. How can this be solved for scalable objects?

class GrooveTool
	
	def initialize
		@selected_face = nil
		@selected_edge = nil
		@transformations = []
		@step = 1
		update_status_text
	end
	
	def activate
		reset_tool
	end
	
	def deactivate(view)
		view.invalidate
	end
	
	def onMouseMove(flags, x, y, view)
		ph = view.pick_helper
		ph.do_pick(x, y)
		path = ph.path_at(0)
		
		if path
			last_entity = path.last
			if @step == 1 && last_entity.is_a?(Sketchup::Face)
				@selected_face = last_entity
				@transformations = compute_transformation(path)
				view.invalidate
				elsif @step == 2 && last_entity.is_a?(Sketchup::Edge)
				@selected_edge = last_entity
				@transformations = compute_transformation(path)
				view.invalidate
			end
		end
	end
	
	def onLButtonDown(flags, x, y, view)
		if @step == 1 && @selected_face
			@step = 2
			update_status_text
			elsif @step == 2 && @selected_edge
			prompt_for_groove_parameters
			reset_tool
		end
		view.invalidate
	end
	
	def draw(view)
		draw_highlight(view)
	end
	
	private
	
	def reset_tool
		@selected_face = nil
		@selected_edge = nil
		@transformations = []
		@step = 1
		update_status_text
	end
	
	def update_status_text
		Sketchup.status_text = case @step
			when 1
			"Оберіть площину"
			when 2
			"Оберіть край на площині"
		end
	end
	
	def prompt_for_groove_parameters
		prompts = ["Groove width (mm)", "Groove depth (mm)", "Groove offset (mm)"]
		defaults = ["10", "5", "50"]
		input = UI.inputbox(prompts, defaults, "Setting groove")
		
		if input
			@groove_width, @groove_depth, @groove_offset = input.map { |value| value.to_f / 25.4 }
			create_groove
		end
	end
	
	def create_groove
		return UI.messagebox("Необхідно виділити як площину, так і край!") if @selected_face.nil? || @selected_edge.nil?
		
		model = Sketchup.active_model
		points = get_groove_points()
		
		begin
			model.start_operation("Create Groove", true)
			groove_face = @selected_face.parent.entities.add_face(points)
			groove_face.pushpull(-@groove_depth) if groove_face && groove_face.valid?
			rescue => e
			UI.messagebox("Error creating groove: #{e.message}")
			ensure
			model.commit_operation
		end
	end
	
	def get_groove_points
		edge_vector = @selected_edge.line[1].normalize
		offset_vector = @selected_face.normal * edge_vector
		
		if @groove_offset > 0
			offset_vector.length = @groove_offset
			
			test_point = @selected_edge.start.position.offset(offset_vector)
			if @selected_face.classify_point(test_point) == Sketchup::Face::PointOutside
				offset_vector.reverse!
			end
			
			offset_start_point = @selected_edge.start.position.offset(offset_vector)
			offset_end_point = @selected_edge.end.position.offset(offset_vector)
			
			offset_vector.length = @groove_width
			width_start_point = offset_start_point.offset(offset_vector)
			width_end_point = offset_end_point.offset(offset_vector)
			else
			offset_start_point = @selected_edge.start.position
			offset_end_point = @selected_edge.end.position
			
			width_vector = @selected_face.normal * edge_vector
			width_vector.length = @groove_width
			
			test_width_point = offset_start_point.offset(width_vector)
			if @selected_face.classify_point(test_width_point) == Sketchup::Face::PointOutside
				width_vector.reverse!
			end
			
			width_start_point = offset_start_point.offset(width_vector)
			width_end_point = offset_end_point.offset(width_vector)
		end
		
		points = [
			offset_start_point, offset_end_point, width_end_point, width_start_point
		]
		
		return points
	end
	
	def compute_transformation(path)
		transformation = Geom::Transformation.new
		path.each do |entity|
			if entity.respond_to?(:transformation)
				transformation *= entity.transformation
			end
		end
		transformation
	end
	
	def draw_highlight(view)
		if @selected_face
			points = @selected_face.outer_loop.vertices.map(&:position)
			transformed_points = points.map { |point| point.transform(@transformations) } if @transformations.is_a?(Geom::Transformation)
			view.drawing_color = 'yellow'
			view.draw(GL_POLYGON, transformed_points)
		end
		
		if @selected_edge
			points = @selected_edge.vertices.map(&:position)
			transformed_points = points.map { |point| point.transform(@transformations) } if @transformations.is_a?(Geom::Transformation)
			view.line_width = 5
			view.drawing_color = 'blue'
			view.draw(GL_LINES, transformed_points)
		end
	end
	
end

Sketchup.active_model.select_tool(GrooveTool.new)

Please take this to a new topic and this one is already solved.

Thank you. I created a new topic Incorrect calculation of point distance after scaling