Selecting a Dimension by its Bounds with a Tool

This is related to my previous post about editing dimensions for a wall panel.

I am currently working on a simple tool that will allow the user to select a dimension. As I mouse over the dimension I would like my tool to detect which dimension I am hovering over.

As you can see below I have some dimensions (stacked):

All four of these dimensions are contained within a sub-group that is within the main group/assembly of the wall. When I move my mouse over the dimensions because they are stacked I am only able to select the main wall dimension. My single line of code to detect which dimension I am hovering over is:

if dimi.bounds.contains?(@iptrans)

Where iptrans is the @ip.position of the cursor transformed into the wall group coordinates.

I think the problem is that using the bounds of the dimension will not work since the stacked dimensions (bounds) overlap and I will always get the larger of the two or even a random sort of behavior. I am wondering if there is a more reliable or precise way of having the tool detect the dimension I am hovering over.

I’m wondering if anyone else has encountered this problem/programming puzzle before and how did they deal with it?

I am thinking there may be some other reference point of the DimensionLinear class that I can utilize to aid in my selection other than just the bounds.

My first thought would be to look for the position of the dimension text and then set some minimum distance between this point and the cursor, however I don’t see any methods to get the actual text position in the model (however I’m assuming one could calculate this given the start and end points and the dimension offset).

My 1st thought is to double down on what I said in the other topic thread, that a PickHelper would be the way to drill down and pick a dimension.

But, I’ve myself never actually tried to pick a dimension object with a PickHelper. Have you tried it?

Here is a sample that picks dimensions that are inside a group.

class DimensionSelectTool
  def onMouseMove(_flags, x, y, view)
    pick_helper = view.pick_helper
    pick_helper.do_pick(x, y)
    cnt = pick_helper.all_picked.count
    dimension = nil
    if cnt > 0
      (0..cnt).each do |idx|
        next unless pick_helper.leaf_at(idx).is_a? Sketchup::DimensionLinear

        dimension = pick_helper.leaf_at(idx)                 
      end
    end
    Sketchup.active_model.selection.clear
    Sketchup.active_model.selection.add(dimension) if dimension
  end
end

tool = DimensionSelectTool.new
Sketchup.active_model.tools.push_tool tool

Select

3 Likes

Your code works quite well.

However dimensions are unlike groups in that to select them you need to carefully place your mouse directly on the lines or text.

For someone as fat fingered as myself this may be a bit challenging and that is why I was thinking of using some sort of proximity test to aid in the selection/highlighting, if this makes sense. You wouldn’t have to be so careful in order to select the dimension, just get reasonably close.

I gave up on the proximity sensing, I can make it work but just using the pick helper is so much easier and intuitive.

Given the way I have my plan dimensions the key is really just using the leat_at method, this seems to work quite reliably.

That is what aperture parameter is for. Set it to 60 and that should help.

Here is an example with a decreasing aperture if multiple dimensions are ‘in range’.

class DimensionSelectTool
  def onMouseMove(_flags, x, y, view)
    pick_helper = view.pick_helper
    pick_helper.do_pick(x, y)
    cnt = pick_helper.all_picked.count
    dimension = get_dimensions(pick_helper, x, y, 60)
    Sketchup.active_model.selection.clear
    Sketchup.active_model.selection.add(dimension) if dimension
  end

  def get_dimensions(pick_helper, x, y, aperture)
    dimensions = []
    pick_helper.do_pick(x, y, aperture)
    cnt = pick_helper.all_picked.count
    if cnt > 0
      (0...cnt).each do |idx|
        next unless pick_helper.leaf_at(idx).is_a? Sketchup::DimensionLinear

        dimensions.push pick_helper.leaf_at(idx)                 
      end
    end
    return get_dimensions(pick_helper, x,y,aperture - 5) if (dimensions.count > 1 && aperture > 0)

    return dimensions[0] if dimensions.count > 0

    return nil
  end
end
  
tool = DimensionSelectTool.new
Sketchup.active_model.tools.push_tool tool
2 Likes

Should the range should use the 3 dot ellipsis to exclude the last index ? ie: (0...cnt).each do |idx|

2 Likes

How come I never noticed that aperture parameter before? Jeez, I’m trying to reinvent the wheel here.

The decreasing aperture algorithm is pretty slick, I must say.

2 Likes

I checked out your new tool in your other thread. That looks like a perfect use case for the measurements bar. Not a single click needed.

VCB Text

And the code for your convenience.
class DimensionSelectTool
  def onMouseMove(_flags, x, y, view)
    pick_helper = view.pick_helper
    pick_helper.do_pick(x, y)
    cnt = pick_helper.all_picked.count
    @dimension = get_dimensions(pick_helper, x, y, 60)
    Sketchup.active_model.selection.clear
    Sketchup.vcb_value = ''
    if @dimension   
      Sketchup.active_model.selection.add(@dimension) 
      Sketchup.vcb_value = @dimension.text      
    end
  end

  def get_dimensions(pick_helper, x, y, aperture)
    dimensions = []
    pick_helper.do_pick(x, y, aperture)
    cnt = pick_helper.all_picked.count
    if cnt > 0
      (0...cnt).each do |idx|
        next unless pick_helper.leaf_at(idx).is_a? Sketchup::DimensionLinear

        dimensions.push pick_helper.leaf_at(idx)                 
      end
    end
    return get_dimensions(pick_helper, x,y,aperture - 5) if (dimensions.count > 1 && aperture > 0)

    return dimensions[0] if dimensions.count > 0

    return nil
  end

  def enableVCB?
    return true 
  end

  def onUserText(text, view)
    @distance = text.to_l
    @dimension.text = @distance.to_s
  rescue ArgumentError
    view.tooltip = 'Invalid length'
  end
end
  
tool = DimensionSelectTool.new
Sketchup.active_model.tools.push_tool tool

3 Likes

I’m going to have to muck around with this one. Very nice.

1 Like

Maybe the following, which seems to work, isn’t using reentrant code, and may create a few less objects to be gc’d in a loop that runs frequently…

# frozen_string_literal: true

# Base class for tool entity selection.  Initialize with the entity class to be
# selected.  Calls into the subclass via `post_select`.
#
class EntitySelectToolBase
  APERTURE_START = 60
  APERTURE_STEP  = 5

  def initialize(entity_class)
    @entity_class = entity_class
  end

  def onMouseMove(_flags, x, y, view)
    pick_helper = view.pick_helper
    aperture = APERTURE_START
    begin
      pick_helper.do_pick x, y, aperture
      entities_picked = pick_helper.all_picked.grep @entity_class
      break if entities_picked.empty?

      if entities_picked.count > 1 && aperture >= APERTURE_STEP
        aperture -= APERTURE_STEP
      end
    end until aperture < APERTURE_STEP || entities_picked.length == 1
    post_select entities_picked.first
  end
end

class DimensionSelectTool < EntitySelectToolBase
  def post_select(entity)
    Sketchup.active_model.selection.clear
    if entity
      Sketchup.active_model.selection.add entity
      Sketchup.vcb_value = entity.text
    else
      Sketchup.vcb_value = ''
    end
  end

  def enableVCB?
    return true 
  end

  def onUserText(text, view)
    @distance = text.to_l
    @dimension.text = @distance.to_s
  rescue ArgumentError
    view.tooltip = 'Invalid length'
  end
end
  
tool = DimensionSelectTool.new Sketchup::DimensionLinear
Sketchup.active_model.tools.push_tool tool
4 Likes

I updated the code above to use a base class. Also, yesterday I had my ruby console obstructing some of the SU GUI, and the updated code removes the ‘fluttering’ in the VCB, along with clearing the selection in possibly a better manner.

Thanks to @Neil_Burkholder for his code.

Thanks @greg. I just did a quick and dirty proof of concept. I figured @medeek has enough experience to take it from there. :slight_smile: Your code is a lot cleaner.

I assumed as much. GUI apps don’t have a lot of ‘hot’ code paths, but onMouseMove might qualify.

So, I wanted to show possible optimizations. JFYI, I try to avoid reentrant code unless it’s really necessary, as rewinding it can involve a lot of resources…

3 Likes