Generate clickable images and assign functions to it

Dear Community,

I’m trying to program a tool that selecting an edge it can show me set of commands I can select from clicking on an icon generated next to the vertex of the edge. Exactly like the icons in the image below (I take it from a video of 3skeng Steelwork: https://www.youtube.com/watch?v=iNfPVz_YUXA):

image

I can’t figure out how to generate an image that can be clicked.

Actually I write down a code that generate a circle and if I click inside the area of the circle a message box appear:

class MyTool
  def initialize
    @circle_center = nil
    @circle_radius = 10
    @circle_generated = false
  end

  def activate
    puts "Tool attivato."
  end

  def deactivate(view)
    puts "Tool disattivato."
  end

  def onMouseMove(flags, x, y, view)
    puts "Mouse in movimento."
    view.invalidate if @circle_generated
  end

  def onLButtonDown(flags, x, y, view)
    puts "Clic sinistro del mouse."
    if @circle_generated
      pick_circle(view, x, y)
    else
      pick_entity(view, x, y)
    end
  end

  def onMouseEnter(view)
    puts "Mouse entrato."
  end

  def pick_entity(view, x, y)
    puts "Ricerca dell'entità selezionata."
    ph = view.pick_helper
    ph.do_pick(x, y)
    edge = ph.best_picked
    if edge && edge.is_a?(Sketchup::Edge)
      puts "Linea selezionata."
      @circle_center = edge.vertices.first.position
      @circle_generated = true
      view.invalidate
    end
  end

  def pick_circle(view, x, y)
    puts "Clic sul cerchio."
    if @circle_generated && point_in_circle?(x, y, view)
      UI.messagebox("Cerchio selezionato!")
    else
      puts "Clic non all'interno del cerchio."
    end
  end

  def draw(view)
    puts "Disegno."
    draw_circle(view) if @circle_generated
  end

  def draw_circle(view)
    return unless @circle_center
    center = view.screen_coords(@circle_center)
    radius = @circle_radius
    num_segments = 24
    angle = 0
    step = 2 * Math::PI / num_segments
    points = []
    num_segments.times do
      x = center.x + radius * Math.cos(angle)
      y = center.y - radius * Math.sin(angle)
      angle += step
      points << [x, y]
    end
    view.drawing_color = 'red'
    view.draw2d(GL_LINE_LOOP, points)
  end

  def point_in_circle?(x, y, view)
    return false unless @circle_generated && @circle_center
    center = view.screen_coords(@circle_center)
    distance_squared = (x - center.x) ** 2 + (y - center.y) ** 2
    distance_squared <= @circle_radius ** 2
  end
end

# Creazione di un'istanza del tool
my_tool = MyTool.new

# Attivazione del tool
Sketchup.active_model.select_tool(my_tool)

I wonder how I can display an icon inside the circle area and assign functions to it when clicked.

Is there anyone who can help me?

Thanks in advance!!

You will need to know the screen coordinates for each image button, same as you do for the circle.

Your tool will need to load button texture images as needed using:

And when you are in “image button” mode, you load and place images in screen space with:

When the mouse moves over a button image, you can display a tooltip for the command using:

The tooltip will be shown whenever the view is invalidated and the mouse stops moving if your draw method has a statement like:

    view.tooltip= @tooltip

Initially @tooltip would be nil, but when the buttons are shown, the onMouseMove callback would set a tooltip string if within the 2D bounds of a button.

Thank you @DanRathbun!! I will do a test and come back to you soon!!

Hello @DanRathbun, here I’m again!
I tested your suggestions but unfortunately I couldn’t get the image to appear!

I updated my code trying to follow your advice, but obviously I’m doing something wrong because this error comes out:

Error: #<ArgumentError: Incorrect number of points: must be multiple of 4>
in draw2d' in draw_image’
in `draw’

Questo il mio codice:

require 'sketchup.rb'

class MyTool
  def initialize
    @circle_center = nil
    @circle_radius = 10
    @circle_generated = false
    @image_filepath = "C:/Users/AP/Desktop/Big24.png"
    @tooltip = nil
  end

  def activate
    puts "Tool activated."
    view = Sketchup.active_model.active_view
    load_image(view)
  end

  def deactivate(view)
    puts "Tool deactivated."
  end

  def onMouseMove(flags, x, y, view)
    view.invalidate if @circle_generated
  end

  def onLButtonDown(flags, x, y, view)
    if @circle_generated
      pick_circle(view, x, y)
    else
      pick_entity(view, x, y)
    end
  end

  def onMouseEnter(view)
    puts "Mouse entered."
  end

  def pick_entity(view, x, y)
    ph = view.pick_helper
    ph.do_pick(x, y)
    edge = ph.best_picked
    if edge && edge.is_a?(Sketchup::Edge)
      @circle_center = edge.start.position
      @circle_generated = true
      view.invalidate
    end
  end

  def pick_circle(view, x, y)
    if @circle_generated && point_in_circle?(x, y, view)
      UI.messagebox("Circle selected!")
    end
  end

  def draw(view)
    draw_circle(view) if @circle_generated
    draw_image(view) if @image_loaded
  end

  def draw_circle(view)
    return unless @circle_center
    center = view.screen_coords(@circle_center)
    radius = @circle_radius
    num_segments = 24
    angle = 0
    step = 2 * Math::PI / num_segments
    points = []
    num_segments.times do
      x = center.x + radius * Math.cos(angle)
      y = center.y - radius * Math.sin(angle)
      angle += step
      points << [x, y]
    end
    view.drawing_color = 'red'
    view.draw2d(GL_LINE_LOOP, points)
  end

  def point_in_circle?(x, y, view)
    return false unless @circle_generated && @circle_center
    center = view.screen_coords(@circle_center)
    distance_squared = (x - center.x) ** 2 + (y - center.y) ** 2
    distance_squared <= @circle_radius ** 2
  end

  def load_image(view)
    @image_rep = Sketchup::ImageRep.new(@image_filepath)
    @texture_id = view.load_texture(@image_rep)
    @image_loaded = true
  rescue => e
    puts "Error: unable to load image as texture."
    puts e.message
    @image_loaded = false
  end

  def draw_image(view)
    return unless @texture_id
    return unless @circle_center && @circle_center.is_a?(Geom::Point3d)

    center = view.screen_coords(@circle_center)
    width = 24
    height = 24

    # Calculate image vertices based on center and dimensions
    left = center.x - width / 2
    right = center.x + width / 2
    top = center.y + height / 2
    bottom = center.y - height / 2

    puts "Left: #{left}, Right: #{right}, Top: #{top}, Bottom: #{bottom}" # Added for debugging

    # Print SketchUp viewport dimensions for comparison
    puts "Viewport Width: #{view.vpwidth}, Viewport Height: #{view.vpheight}"

    options = {
      :texture => @texture_id,
      :uvs => [0, 1, 0, 0, 1, 0, 1, 1]
    }

    # Draw the image
    view.draw2d(GL_QUADS, [left, top, 0], [left, bottom, 0], [right, bottom, 0], [right, top, 0], options)
  end
end

# Create an instance of the tool
my_tool = MyTool.new

# Activate the tool
Sketchup.active_model.select_tool(my_tool)

Do you have any other suggestions?
Thank you

The number of UV coordinates must match the number of vertices for your quad face.

Whoops, incorrect (UVs are between {0,0} and {1,1})

If the button image is 24 x 24 then perhaps the UVs would be:

    options = {
      :texture => @texture_id,
      :uvs => [
        [0, 0, 0],
        [0, 24, 0],
        [24, 24, 0],
        [24, 0, 0]
      ]
    }

With texture UV coordinates the origin is at the top left, with the x_axis pointing toward the right and the y_axis pointing downwards.

Hello @DanRathbun, thank you for your help. I was also giving the input to draw2d method in a wrong way.

But the problem is not solved yet unfortunatelly: a 24x24 gray square is drawned, not exactly the image I want to load.

Here after the update method:

  def draw_image(view)
    return unless @texture_id
    return unless @circle_center && @circle_center.is_a?(Geom::Point3d)

    center = view.screen_coords(@circle_center)
    width = 24
    height = 24

    # Calculate image vertices based on center and dimensions
    left = center.x - width / 2
    right = center.x + width / 2
    top = center.y + height / 2
    bottom = center.y - height / 2

    # Print image vertices for debugging
    puts "Left: #{left}, Right: #{right}, Top: #{top}, Bottom: #{bottom}"

    # Print SketchUp viewport dimensions for comparison
    puts "Viewport Width: #{view.vpwidth}, Viewport Height: #{view.vpheight}"

    points = [
      [left, top, 0],
      [left, bottom, 0],
      [right, bottom, 0],
      [right, top, 0]
    ]

    options = {
      :texture => @texture_id,
      :uvs => [
        [0, 0, 0],
        [0, 24, 0],
        [24, 24, 0],
        [24, 0, 0]
      ]
    }

    # Draw the image
    view.draw2d(GL_QUADS, points, options)
  end

Attached the image I would like to upload:

Big24

Could be a matter of texture?

Thanks again!

I saw the same thing, I even tried a image that came with SketchUp’s DC extension.

It may be necessary to apply display scaling if you (or your users) have a high DPI display. See

Note that the View.vpwidth and View.vpheight methods return physical pixels sizes, not logical.

I made a booboo, when I said above …

The UV coordinates are really scaling vectors between [0,0] and [1,1]


Things I did …

debugging

Added a @@debug class variable and @debug instance variable.
Added a debug=() class setter method.

deactivate()

Added a release texture statement
Added a view.invalidate call to clear anything that the tool has drawn

load_image()

Added a little debug information

draw_image()

Scaled the height and width by UI.scale_factor
Rearranged the points and uvs in clockwise order
Restored the uvs to (clockwise from top left corner):

    uvs = [
      [0, 0, 0],
      [1, 0, 0],
      [1, 1, 0],
      [0, 1, 0]
    ]

image_buttons_tool.zip (1.9 KB)

Thank you @DanRathbun!! Great!! Now it is working!!

1 Like