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