All connected confounds selection.add

Hi. I am attempting to create a tool in which the user selects some geometry. I want to include a method in which all faces that are connected with soft edges get selected at once when one of the faces is selected. I have a method which does that using all_connected. I can collect the faces and store them in @entities for example. However I also want to add the selection visually so that the user can see which elements are selected. What I am finding is that when I implement a method that tries to get all_connected from the input more than one face or edge will not be selectable from a set of connected geometry. The elements get saved to my @entities collection but the visual selection only shows the last selection. The visual selection does work for elements that are not connected. This happens irregardless of whether the there are soft edges or not.

Here is a simplified version which demonstrates the problem:

      def onLButtonDown(flags, x, y, view)
        @pickhelper.do_pick(x, y)
        input = @pickhelper.best_picked
        get_entities(input)    
        view.invalidate        
      end

      def get_entities(input)           
        if test(input)
          puts "test " + test(input).to_s
        end
          @entities << input
          puts @entities.to_s
          @model.selection.add(input)
      end                         
       
      def test(input)
        x = input.all_connected
        true
      end

The selection#add method is an alias for remove and toggle. So you must only do it once per element.

          @model.selection.add(input) unless @model.selection.contains?(input)

Also, are you clearing the selection in the tool’s activate method?

I added the unless condition as you recommend. As for clearing the selection I am doing that in the reset tool.
I have a shift deselect code also, and all together it works well as long as I do not implement any method that that uses .all_connected.
I contrived a workaround that is so far so good. I wrote my own method to find all connected faces.

This is the input collection method currently, which is working

            def get_entities(value, view)           
                Sketchup.status_text = 'select items to skew, press enter to complete selection...1'
                if @picked == true  and value_unlocked?(value)        
                    unless @entities.include?(value)
                        if value.class == Sketchup::Face 
                            if is_surface?(value)
                                puts "is_surface? " + is_surface?(value).to_s
                                value = get_surface(value)
                                value.each{|e| @model.selection.add(e)}
                                value.each{|e| @entities << e}
                            end 
                        end
                        @entities << value
                        @model.selection.add(value)
                    else 
                        if @shift_is_pressed == true
                            puts "shift is pressed"
                            # deselect when shift is pressed..
                            # but not if it isn't already selected...
                            if @entities.include?(value)
                                if is_surface?(value)
                                    value = get_surface(value)
                                    value.each{|e| @model.selection.remove(e)} 
                                else
                                @model.selection.remove(value) unless value == nil
                                @entities.delete(value) unless value == nil 
                                end                      
                            else
                                if is_surface?(value)
                                    puts "is_surface? " + is_surface?(value).to_s
                                    value = get_surface(value)
                                    value.each{|e| @model.selection.add(e)}
                                    value.each{|e| @entities << e}
                                else
                                @model.selection.add(value)
                                @entities << value  
                                end
                            end
                        end
                    end
                elsif @picked == false 
                    unless @resume == true
                        Sketchup.status_text = 'Nothing got selected, try again or press enter to complete selection'
                    end
                elsif @picked == true and value_unlocked?(value) == false
                    unless @resume == true
                        @model.selection.add(value)
                        Sketchup.status_text = 'selection is locked. please make a different selection.'
                    end
                end  
                @picked = false
                @resume = false  
                
                puts "@entities " + @entities.to_s                          
            end

I will try to state the issue I was having more succinctly. Say there are several assemblies of face and edges in the model. Each is connected internally but they are not connected to each other. Say I do a pick, using the tool as summarized in my first post. So when the mouse button is pressed and the cursor is over a face or an edge, the tool will save that element to an array and should highlight the element in the model. When the pick is made the tool will also define x = input.all_connected. To no purpose in the example, but just to show that it happened. On the first pick all is good, the selection is highlighted and the element is saved. On the next pick, if the pick is on the same assembly of faces, the previous selection will be deselected and only the current selection will be highlighted. If the pick is on another assembly of faces, ( and or edges ), the first pick will remain highlighted, as well as the second pick. However when I comment out the test method, which runs .all_connected, I can pick from the same assembly of elements and the previous highlighting will not be deselected. In either case the array of saved elements is appended with each subsequent pick. To my inexperienced eye this seems like an out of scope situation. What ever happens in the test method should not be influencing what happens outside of it.

No one can reproduce this without a full working example tool. You’ve only posted pieces of the puzzle.

:jigsaw:

Here is the bug report in the API Tracker on GitHub, logged in October 2022.

1 Like

Here is a stripped down version in which the problem still occurs:

require 'sketchup.rb'

# Create the Tool bar command
toolbar = UI::Toolbar.new "SkewTool"
cmd = UI::Command.new("Toolbar Button ") {
Sketchup.active_model.select_tool Skew::SkewTool
.new
}
icon = File.join(__dir__, 'images', 'skew_icon.png')
cmd.small_icon = icon
cmd.large_icon = icon 
cmd.tooltip = "SkewTool"
toolbar = toolbar.add_item cmd 
toolbar.show

# Create the Menu command
cmd = UI::Command.new("Menu Item") {
Sketchup.active_model.select_tool Skew::SkewTool
.new
}
cmd.menu_text = "Skew"
cmd.set_validation_proc {MF_ENABLED}
UI.menu("Plugins").add_item(cmd)

#module WaltsExtensions
  module Skew
    class SkewTool

      def activate      
        view = Sketchup.active_model.active_view
        @model = view.model
        @mouse_ip = Sketchup::InputPoint.new
        @picked_ip = Sketchup::InputPoint.new
        @pickhelper = view.pick_helper
        @picked = false
        @resume = false
        @entities = []
        @error_handler = false
        intital_state(nil, view)    
      end

      def intital_state(value, view)
        Sketchup.status_text = 'select items to skew, press enter to complete selection...0' 
        @model.selection.clear
      end

      def deactivate(view)
        puts "deactivate"
        view.invalidate
        @model.selection.clear 
      end
    
      def resume(view)
        puts "resume"
        @resume = true     
        view.invalidate 
      end
    
      def suspend(view)
        puts "suspend"
        view.invalidate 
      end
    
      def onCancel(reason, view) 
        puts "onCancel " + reason.to_s     
        reset_tool(view)  
      end
    
      def onMouseMove(flags, x, y, view)
        if picked_first_point?
          @mouse_ip.pick(view, x, y, @picked_ip)
        else
          @mouse_ip.pick(view, x, y)
        end
        view.tooltip = @mouse_ip.tooltip if @mouse_ip.valid?
        view.invalidate
      end

##########################################################################################################

      def onLButtonDown(flags, x, y, view)
        @pickhelper.do_pick(x, y)
        input = @pickhelper.best_picked
        get_entities(input)    
        view.invalidate        
      end

      def get_entities(input)           
        if test(input)
          puts "test " + test(input).to_s
        end
          @entities << input
          puts @entities.to_s
          @model.selection.add(input)
      end                         
       
      def test(input)
        x = input.all_connected
        true
      end

##########################################################################################################
      def picked_first_point?
        @picked_ip.valid?
      end
 
      def error_handler(error, methname, view)
        @error_handler = true
        line = error.backtrace_locations.first.lineno
        puts "Nifty Tool Error: #{error.class} in #{methname}, at #{line}"
        puts error.inspect
        view.model.abort_operation   
        reset_tool(view)
      end

      def reset_tool(view)
        @model = view.model
        @mouse_ip = Sketchup::InputPoint.new
        @picked_ip = Sketchup::InputPoint.new
        @pickhelper = view.pick_helper
        @resume = false
        @entities = []   
        @model.selection.clear
        if @error_handler == true 
          Sketchup.status_text = 'an error occured...tool is reset'  
        end
        @error_handler = false
      end
      

    end # SkewTool   
  end # module Skew 
#end #WaltsExtensions

I had not remembered that this was a bug that has already been reported.

It is a lesson that the tracker should be searched when weirdness happens.

Yes. We must not assume that, because it is out of our memories, it has been fixed…!

2 Likes

This is how I did my own version of .all_connected . But I am sure there is a slicker way (hint, hint)

      def get_connected_faces(face) 
        # get all the connected faces that have soft edges
        mod = Sketchup.active_model
        ents = mod.entities
        search_set = ents.find_all{|e| e.class == Sketchup::Face}
        search_set.delete_if{|f| f == face}
        connected = [face]
        # get the first batch of adjacent soft faces
        adjacent_next = adjacent_next([face], search_set)
        # collect the newfound adjacent soft faces
        adjacent_next.each{|face| connected << face}
        # purge the search set
        for f in adjacent_next
            search_set.delete_if{|face| face == f}
        end
        # count the newfound adjacent soft faces
        find_count = adjacent_next.length
        # collect again until no new candidates are found
        count = 0
        until (find_count == 0) or (count == search_set.length)
            found_set = adjacent_next
            adjacent_next = adjacent_next(found_set, search_set)
            adjacent_next.each{|face| connected << face}
            # purge the search set
            for f in adjacent_next
                search_set.delete_if{|face| face == f}
            end
            find_count = adjacent_next.length
            count += 1
        end
        connected
      end

      def adjacent_next(found_set, search_set)
        adjacent_next = []
        for face in found_set
            edges = face.edges
            contiguous_faces = []
            for edge in edges
                contiguous_faces << search_set.find_all{|face| face.edges.include?(edge)} 
            end
            contiguous_faces = contiguous_faces.flatten.uniq
            contiguous_faces.delete_if{|f| f == face}
            contiguous_faces.each{|e|adjacent_next << e}
        end
        adjacent_next = adjacent_next.uniq
      end

yeah, ignore the comments about soft faces…

As Fredo said in the official bug report, it is a display bug only. The selection set is actually correct.

So, a simple workaround after using face.all_connected might be …

sel_ary = selection.to_a
selection.clear
selection.add(sel_ary)

Basically, the idea is to reset the selection display.

This recommendation works. Thanks

I noticed that in 2024 there doesn’t seem to be any issue with .all_connected conflicting with the selection highlighting …

Oh I had not yet found time to test it.

Did you test on both graphic engines ?

The bug is still there in SU2024, with New Graphic engine (Windows 10).

1 Like

darn. Oh well.