I have created a custom tool that allows users to select an edge and a point. When the user moves the mouse, one endpoint of the edge will move along with the mouse. The selected point is marked as @ip1. If @ip1 is not located on the edge, its position will not change. However, if @ip1 is located at an endpoint of the edge, it will change when the mouse is moved. Why does this happen?
class TestTool
def activate
@ip = Sketchup::InputPoint.new
@ip1 = Sketchup::InputPoint.new
@mode = 0
end
def onLButtonDown(flags, x, y, view)
if @mode == 0
ph = view.pick_helper
ph.do_pick(x, y)
@edge = ph.picked_edge
@mode = 1 if @edge
return
end
@ip1.pick(view, x, y) # Here is the only place to change @ip1
vertices = @edge.vertices.sort do |a, b|
a.position.distance(@ip1.position) <=> b.position.distance(@ip1.position)
end
@vertex = vertices.first
end
def onMouseMove(flags, x, y, view)
@ip.pick(view, x, y)
if @vertex
pt = @vertex.position
vector = pt.vector_to(@ip.position)
tr = Geom::Transformation.translation(vector)
Sketchup.active_model.entities.transform_entities(tr, @vertex)
end
puts "@ip1: #{@ip1.position}"
view.invalidate
end
def draw(view)
@ip.draw(view)
view.draw_points(@ip1.position, 10, 1, "red")
end
end
tool = TestTool.new
Sketchup.active_model.select_tool tool
One thing your tool does not yet do is set itās cursor. When you do not do this the userās system cursor is used and may not be appropriate.
def onSetCursor()
UI.set_cursor(633) # 633 is Select cursor
end
With recent Ruby versions warnings will be output if an object has not defined @instance variables before accessing them. In older versions of Ruby nil would be returned when accessing uninitialized @variables. But this is no longer the case.
You are accessing @edge and @vertexbefore they are initialized.
You should initialize them in activate() ⦠or in initialize() as well.
It is always good to do this for ābest practicesā and clarity.
@ip1.pick(view, x, y) # Here is the only place to change @ip1
@vertex = @edge.vertices.find { |vertex| vertex.position == @ip1.position }
This is drawing the red square at the ORIGIN when @ip1 has not yet picked a point.
This should draw only in tool @mode > 0 or @ip1,valid?
def draw(view)
@ip.draw(view)
view.draw_points(@ip1.position, 10, 1, "red") if @ip1.valid?
end
The same for puts the @ip1 position to the console in onMouseMove callback so as not to flood the console with ORIGIN coordinates as the mouse is moved before any point is picked.
Also the @ip tooltip should be displayed ā¦
def onMouseMove(flags, x, y, view)
@ip.pick(view, x, y)
view.tooltip= @ip.tooltip
if @vertex
pt = @vertex.position
vector = pt.vector_to(@ip.position)
tr = Geom::Transformation.translation(vector)
view.model.active_entities.transform_entities(tr, @vertex)
end
puts "@ip1: #{@ip1.position}" if @ip1.valid?
view.invalidate
end
You should also have a deactivate callback to clear the view when the tool is done.
def deactivate(view)
view.invalidate
end
Regarding an undo operation, the onLButtonDown callback should be like ā¦
def onLButtonDown(flags, x, y, view)
if @mode == 0
ph = view.pick_helper
ph.do_pick(x, y)
@edge = ph.picked_edge
@mode = 1 if @edge
return
elsif @mode == 1
@ip1.pick(view, x, y) # Here is the only place to change @ip1
@vertex = @edge.vertices.find { |vertex|
vertex.position == @ip1.position
}
if @vertex
@mode = 2
view.model.start_operation('Move Vertex')
end
elsif @mode == 2
# Stop moving the vertex and reset the tool:
view.model.commit_operation
deactivate(view)
activate()
end
end
⦠and an onCancel callback so the user can reset the tool via ESC key ā¦
def onCancel(reason, view)
# Restore the vertex and reset the tool:
view.model.abort_operation if @mode == 2
deactivate(view)
activate()
end
Here is the complete modifications of the Tool class.
I know that the code for this tool is incomplete and not rigorous, and I am not trying to create a MOVE tool. As the name suggests, this is just a temporary test tool created to demonstrate a somewhat strange behavior of Sketchup::InputPoint#pick method.
I searched on Google and didnāt see any articles describing such behavior, and I tried in Ruby Console with version 3.1.2 and also didnāt see any warnings.
But I agree itās the best practice to initialize them before use.
An uninitialized instance variable has a value of nil. If you run Ruby with warnings enabled, you will get a warning when accessing an uninitialized instance variable.
I made a booboo when saying that initialized @vars would no longer be nil.
This does not make sense. An invalid (pre-pick) inputpoint has no trajectory. I was surprised the #position method returned a point for (0,0,0) as this is undocumented. Anyway, the red square just sits at the origin for the current active entities context.
class TestTool
RED ||= Sketchup::Color.new("Red")
def activate
@ip = Sketchup::InputPoint.new
@ip1 = Sketchup::InputPoint.new
@mode = 0
@edge = nil
@vertex = nil
Sketchup.status_text= "Pick a edge."
end
def deactivate(view)
view.invalidate
end
def draw(view)
view.tooltip= @ip.tooltip
@ip.draw(view)
if @mode > 0
view.drawing_color= RED
view.draw2d(GL_LINES, @edge.vertices.map(&:position) )
view.draw_points(@ip1.position, 10, 1, RED) if @ip1.valid?
end
end
def onCancel(reason, view)
# Restore the vertex and reset the tool:
view.model.abort_operation if @mode == 2
deactivate(view)
activate()
end
def onLButtonDown(flags, x, y, view)
if @mode == 0
ph = view.pick_helper
ph.do_pick(x, y)
@edge = ph.picked_edge
if @edge
@mode = 1
onMouseMove(flags, x, y, view)
end
elsif @mode == 1
@ip1.pick(view, x, y) # Here is the only place to change @ip1
vertices = @edge.vertices.sort do |a, b|
a.position.distance(@ip1.position) <=> b.position.distance(@ip1.position)
end
@vertex = vertices.first
# @vertex = @edge.vertices.find { |vertex|
# vertex.position == @ip1.position
# }
if @vertex
@mode = 2
view.model.start_operation('Move Vertex')
onMouseMove(flags, x, y, view)
end
elsif @mode == 2
# Stop moving the vertex and reset the tool:
view.model.commit_operation
deactivate(view)
activate()
end
end
def onMouseMove(flags, x, y, view)
@ip.pick(view, x, y)
if @vertex
pt = @vertex.position
vector = pt.vector_to(@ip.position)
tr = Geom::Transformation.translation(vector)
view.model.active_entities.transform_entities(tr, @vertex)
end
if @mode == 0
Sketchup.status_text= "#{@ip.position.inspect}: Pick a edge."
elsif @mode == 1
if @ip1.valid?
puts "@ip1: #{@ip1.position}"
else
Sketchup.status_text= "#{@ip.position.inspect}: Pick a point."
end
elsif @mode == 2
Sketchup.status_text= "#{@ip.position.inspect}: Drag the vertex."
end
view.invalidate
end
def onSetCursor()
UI.set_cursor(633) # 633 is Select cursor
end
end
Sketchup.active_model.select_tool TestTool.new
I do not see this happening. The closest vertex to @ip1 is chosen correctly.
What I do notice is the after picking @ip1 the mouse needs to move a bit before the tool shows the next @mode. Which shows the vertex being dragged.
So to fix this, I added a internal call to onMouseMove(flags, x, y, view) from within the onLButtonDown(flags, x, y, view) method when @vertex is good.
Sorry too late to reply.
I used your code and the @ip1 still changed when moving. In the video, you could see that the red point moves and status_text on the left bottom also changed.