Ignoring certain entities in the tool

I will try to explain in an accessible way). I created a tool that adds a circle to a point on a selected face and to the context of that face. Everything works fine. But for example, if I have a face in a group context, and then I draw construction lines in the model context above the face, and then I want to add a circle to the intersection of those lines, then the circle is not built in the face context, but in the model context (where these lines). How to properly modify the code so that the circle is added to the context of the face that is below these construction lines? Can we ignore and simultaneously use construction lines to show the intersection point?

require 'sketchup.rb'

def self.prompt_for_diameter_and_activate_tool
  diameter = UI.inputbox(['Diameter'], [100.mm], 'Enter Circle Diameter')
  Sketchup.active_model.select_tool(CircleTool.new(diameter[0].to_l)) unless diameter[0].nil?
end

class CircleTool
  def initialize(diameter)
    @diameter = diameter
  end
  
  def activate
    @input_point = Sketchup::InputPoint.new
  end
  
  def onMouseMove(flags, x, y, view)
    @input_point.pick(view, x, y, nil)
    view.invalidate
  end
  
  def onLButtonDown(flags, x, y, view)
    if @input_point.valid? && @input_point.face
      entities = correct_context(@input_point, view)
      normal = @input_point.face.normal
      transformation = @input_point.transformation
      transformed_normal = normal.transform(transformation)
      
      add_circle(@input_point.position, @diameter, transformed_normal, entities, transformation)
    end
  end
  
def draw(view)
  # Check if the input point is valid and if it is over a face
  if @input_point.valid? && @input_point.face
    # Draw the temporary input point using the built-in method
    @input_point.draw(view)
  end
end

  private
  
  def calculate_total_transformation(input_point)
    total_transformation = Geom::Transformation.new
    if input_point.instance_path
      input_point.instance_path.each do |instance|
        total_transformation *= instance.transformation if instance.respond_to?(:transformation)
      end
    end
    total_transformation
  end
  
  def correct_context(input_point, view)
    instance_path = input_point.instance_path
    
    if instance_path && !instance_path.empty?
      last_instance = instance_path.to_a.last
      if last_instance.is_a?(Sketchup::Group) || last_instance.is_a?(Sketchup::ComponentInstance)
        return last_instance.definition.entities
      else
        parent = last_instance.parent
        return parent.is_a?(Sketchup::Model) ? parent.active_entities : parent.entities
      end
    else
      return Sketchup.active_model.active_entities
    end
  end
  
  def add_circle(center, diameter, normal, entities, transformation)
    radius = diameter / 2
    num_segments = 24
    
    local_center = center.transform(transformation.inverse)
    local_normal = normal.transform(transformation.inverse)
    
    edges = entities.add_circle(local_center, local_normal, radius, num_segments)
    entities.add_face(edges) unless edges.empty?
  end
end

self.prompt_for_diameter_and_activate_tool
```Ruby

I Do not have to much time right now, but I guess this can be simplified like:

  def onLButtonDown(flags, x, y, view)
    if @input_point.valid? && @input_point.face
      entities = @input_point.face.parent.entities

Thank you for your attention to my question.

I seem to have already found a solution to my questions using the “PickHelper” methods. I will post the corrected code here. Perhaps he will agree with someone else as an example. Also, I would appreciate it if someone has time to look at the code and maybe give some recommendations and explain how this code can be further simplified. That would be a good learning lesson. Thank you all.

require 'sketchup.rb'

def self.prompt_for_diameter_and_activate_tool
  diameter = UI.inputbox(['Diameter'], [100.mm], 'Enter Circle Diameter')
  Sketchup.active_model.select_tool(CircleTool.new(diameter[0].to_l)) unless diameter[0].nil?
end

class CircleTool
  def initialize(diameter)
    @diameter = diameter
	end
  
  def activate
    @input_point = Sketchup::InputPoint.new
	end
  
  def onMouseMove(flags, x, y, view)
    @input_point.pick(view, x, y, nil)
    view.invalidate
	end
  
  def onLButtonDown(flags, x, y, view)
    pick_helper = view.pick_helper
    pick_helper.do_pick(x, y)
    
    picked_face = nil
    picked_face_path = nil
    
    # Iterate through the picked entities to find the deepest face ignoring construction lines and edges
    pick_helper.count.times do |index|
      path = pick_helper.path_at(index)
      next unless path
      face = path.last
      next unless face.is_a?(Sketchup::Face)
      picked_face = face
      picked_face_path = path
      break
		end
    
    if picked_face
      entities = correct_context(picked_face_path)
      normal = picked_face.normal
      transformation = calculate_transformation(picked_face_path)
      transformed_normal = normal.transform(transformation)
      
      add_circle(@input_point.position, @diameter, transformed_normal, entities, transformation)
		end
	end
  
  def draw(view)
    @input_point.draw(view) if @input_point.valid?
	end
	
  private
	
	def correct_context(instance_path)
		# Check if the face is directly in the model context
		if instance_path.nil? || instance_path.empty? || instance_path.size == 1
			return Sketchup.active_model.active_entities
			else
			# Otherwise, get the entities collection from the group or component instance
			return instance_path[-2].definition.entities
		end
	end
	
	def calculate_transformation(instance_path)
		total_transformation = Geom::Transformation.new
		instance_path.each do |instance|
			# Only apply transformation if the instance responds to the 'transformation' method
			if instance.respond_to?(:transformation)
				total_transformation *= instance.transformation
			end
		end
		total_transformation
	end
	
  
  def add_circle(center, diameter, normal, entities, transformation)
    radius = diameter / 2
    num_segments = 24
    
    local_center = center.transform(transformation.inverse)
    local_normal = normal.transform(transformation.inverse)
    
    edges = entities.add_circle(local_center, local_normal, radius, num_segments)
    entities.add_face(edges) unless edges.empty?
	end
end

self.prompt_for_diameter_and_activate_tool

Your last version works for me.

EDIT:

Don’t include the final, “Ruby”, as it displays in the text:

```Ruby
1 Like

actually, you need
```ruby
your ruby code
```
The back-ticks need to be on a separate line from the code, and the first one needs the keyword ruby to tell the formatter what language is involved. Without it you will just get a verbatim text block without markdown procesing, which is better than no back-ticks, but won’t get the ruby syntax highlighting.

Edit: hmm… just tried and found that the ruby next to the first backticks isn’t necessary. I don’t think it used to be that way. Perhaps they changed a forum setting?

Yes you’re right. I edited my post without “Ruby” and the code displays correctly.

I only use the triple backticks before and after the ruby code and that seems to work.

Perhaps, because Discourse is built using Ruby.

I thought I have seen differences in the past.

I come to you again with a request for help). Now I’m playing with this code and ran into scaling again) How do I correctly add a circle at scale 1 to a pre-scaled object? Is there some general method where we could convert the scaling to a factor of 1?
You can see in the video how it works.

require 'sketchup.rb'

def self.prompt_for_diameter_and_activate_tool
  diameter = UI.inputbox(['Diameter'], [100.mm], 'Enter Circle Diameter')
  Sketchup.active_model.select_tool(CircleTool.new(diameter[0].to_l)) unless diameter[0].nil?
end

class CircleTool
  def initialize(diameter)
    @diameter = diameter
		@transformation = IDENTITY
	end
	
  def activate
    @input_point = Sketchup::InputPoint.new
	end
	
  def onMouseMove(flags, x, y, view)
    @input_point.pick(view, x, y, nil)
    view.invalidate
	end
	
  def onLButtonDown(flags, x, y, view)
    pick_helper = view.pick_helper
    pick_helper.do_pick(x, y)
    
    picked_face = nil
    picked_face_path = nil
    
    # Iterate through the picked entities to find the deepest face ignoring construction lines and edges
    pick_helper.count.times do |index|
      path = pick_helper.path_at(index)
      next unless path
      face = path.last
      next unless face.is_a?(Sketchup::Face)
      picked_face = face
      picked_face_path = path
      break
		end
    
    if picked_face
      entities = correct_context(picked_face_path)
      normal = picked_face.normal
			# Using the transformation_at method
      @transformation = pick_helper.transformation_at(pick_helper.count.times.find { |i| pick_helper.path_at(i) == picked_face_path })
      transformed_normal = normal.transform(@transformation)
      
      add_circle(@input_point.position, @diameter, transformed_normal, entities, @transformation)
		end
	end
	
  def draw(view)
    @input_point.draw(view) if @input_point.valid?
	end
	
  private
	
  def correct_context(path)
    if path.nil? || path.empty? || path.size == 1
      Sketchup.active_model.active_entities
			else
      path[-2].definition.entities
		end
	end
	
  def add_circle(center, diameter, normal, entities, transformation)
    radius = diameter / 2
    num_segments = 24
		
    local_center = center.transform(transformation.inverse)
    local_normal = normal.transform(transformation.inverse)
		
    edges = entities.add_circle(local_center, local_normal, radius, num_segments)
    entities.add_face(edges) unless edges.empty?
	end
end

self.prompt_for_diameter_and_activate_tool

I use this to scale the definition of components, I think that is what you want? You would probably want to make sure its unique before or it will affect any other instances…

def scale_defintition(dc) # Same result as right click menu Scale Definition
  
  tr = dc.transformation
  x_scale = X_AXIS.transform( tr ).length
  y_scale = Y_AXIS.transform( tr ).length
  z_scale = Z_AXIS.transform( tr ).length

  tr_definition = Geom::Transformation.scaling( x_scale, y_scale, z_scale )
  tr_instance = tr_definition.inverse

  definition = dc.definition
  entities = definition.entities
  entities.transform_entities( tr_definition, entities.to_a )

  for instance in definition.instances
    tr_i = instance.transformation
    instance.transform!( tr_i * tr_instance * tr_i.inverse )
  end

end

rwamoore, thank you. i will look into it.
The only solution I found so far is to use the input_point.instance_path method. I remembered that I had already seen this somewhere Solving Drill.rb Problem - #4 by majid866

  def onMouseMove(flags, x, y, view)
    @input_point.pick(view, x, y, nil)

    # Check if the picked input point has a valid instance path
    if @input_point.instance_path.valid?
      # Potentially change the active editing context based on the instance_path
      view.model.active_path = @input_point.instance_path
    else
      # Reset the active_path to nil if no valid instance path is picked
      view.model.active_path = nil
    end

    view.invalidate # Refresh the view to reflect any changes
  end
Full modified code
require 'sketchup.rb'

def self.prompt_for_diameter_and_activate_tool
  diameter = UI.inputbox(['Diameter'], [100.mm], 'Enter Circle Diameter')
  Sketchup.active_model.select_tool(CircleTool.new(diameter[0].to_l)) unless diameter[0].nil?
end

class CircleTool
  def initialize(diameter)
    @diameter = diameter
    @transformation = IDENTITY
  end

  def activate
    @input_point = Sketchup::InputPoint.new
  end

  def onMouseMove(flags, x, y, view)
    @input_point.pick(view, x, y, nil)

    # Check if the picked input point has a valid instance path
    if @input_point.instance_path.valid?
      # Potentially change the active editing context based on the instance_path
      view.model.active_path = @input_point.instance_path
    else
      # Reset the active_path to nil if no valid instance path is picked
      view.model.active_path = nil
    end

    view.invalidate # Refresh the view to reflect any changes
  end

  def onLButtonDown(flags, x, y, view)
    pick_helper = view.pick_helper
    pick_helper.do_pick(x, y)

    picked_face = nil
    picked_face_path = nil

    # Iterate through the picked entities to find the deepest face ignoring construction lines and edges
    pick_helper.count.times do |index|
      path = pick_helper.path_at(index)
      next unless path
      face = path.last
      next unless face.is_a?(Sketchup::Face)
      picked_face = face
      picked_face_path = path
      break
    end

    if picked_face
      entities = correct_context(picked_face_path)
      normal = picked_face.normal
      # Using the transformation_at method
      @transformation = pick_helper.transformation_at(pick_helper.count.times.find { |i| pick_helper.path_at(i) == picked_face_path })
      transformed_normal = normal.transform(@transformation)

      add_circle(@input_point.position, @diameter, transformed_normal, entities, @transformation)
    end
  end

  def draw(view)
    @input_point.draw(view) if @input_point.valid?
  end

  private

  def correct_context(path)
    if path.nil? || path.empty? || path.size == 1
      Sketchup.active_model.active_entities
    else
      path[-2].definition.entities
    end
  end

  def add_circle(center, diameter, normal, entities, transformation)
    radius = diameter / 2
    num_segments = 24

    local_center = center.transform(transformation.inverse)
    local_normal = normal.transform(transformation.inverse)

    edges = entities.add_circle(local_center, local_normal, radius, num_segments)
    entities.add_face(edges) unless edges.empty?
  end
end

self.prompt_for_diameter_and_activate_tool

But I don’t really like this blinking when changing the context). Let’s wait, maybe there will be more options.

Modify code in your post #9

  def add_circle(center, diameter, normal, entities, transformation)
    radius = diameter / 2
    num_segments = 24
    
#Remove these lines
    #local_center = center.transform(transformation.inverse)
    #local_normal = normal.transform(transformation.inverse)
    
#Modifiy:
    edges = entities.add_circle(center, normal, radius, num_segments)
# Make transformation of edges, after created
    entities.transform_entities(transformation.inverse, edges)
    entities.add_face(edges) unless edges.empty?
  end
2 Likes

Dezmo, thank you very much. I thought of many complex approaches without paying attention to the simple things. Experience and training. Thank you.

1 Like