Weird behavior of Sketchup::InputPoint#pick() method

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

I tried to run this, but Iā€™m not sure what Iā€™m supposed to do to reproduce what youā€™re seeing.

Can you add some screenshots or a video capture to illustrate?

this should be

view.model.entities.transform_entities(tr, @vertex)

Then I suggest to modify the draw method to visualise better: e.g.:

  def draw(view)
    @ip.draw(view)
    view.draw_points(@ip1.position, 10, 1, "red")
  end

ipvertex

So when you pick @ip1 at vertex it will be ā€œassociatedā€ to that vertex and will move together with it.

I got it, but cant explain why.

1 Like

Little off topic: Dont forget this :wink:

Sorry, mod is an instance method defined in the Object class in my code base, I just forget to replace it with Sketchup.active_model

And yes, your video shows what Iā€™m confusing.

As I mentioned above you can use:
the View #model method to retrieve the model for the current view.

DO NOT modify Ruby Core classes or SketchUp API classes !
SketchUp Ruby is a shared environment and doing this this will effect ALL other extensions.

If you must ā€¦ use a class refinement that only affects your code.

FYI, the native Move tool does this.


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 @vertex before 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.

  def activate
    @ip = Sketchup::InputPoint.new
    @ip1 = Sketchup::InputPoint.new
    @mode = 0
    @edge = nil
    @vertex = nil
  end

It should actually be ā€¦

view.model.active_entities.transform_entities(tr, @vertex)

This might be simplified to:

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

2 Likes

Thanks for your advice.

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.

Thanks anyway.

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.

No, they are not identical. Because we allow user to pick @ip1 at any point but not only on the vertex of an edge.

In fact, for demonstration reasons, it is better to draw @ip1 before it was picked, so that users can clearly see its movement trajectory.

Here is a little quote from the official Ruby documentation ā€¦

From: File: assignment.rdoc [Ruby 2.7.2]

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.

See the following page for an example of enabling warnings and causing a warning with access to an uninitialized variable:
Enable ruby warnings | Ruby Tricks, Idiomatic Ruby, Refactorings and Best Practices


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.

Okay I changed it back.

The changed test code ...
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.

Did you use the code in the collapsed ā€œThe changed test code ā€¦ā€ section of my previous post?

Again the red square stays where the point is picked with the code I posted.

Yes, the video I posted was using ā€˜The changed test codeā€™.

Then must be a Mac bug.

1 Like