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 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
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?
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.