Looking for a face selection extension

Is there an extension that would allow me to select the face of a deeply nested object, then select the face of another object which is deeply nested in another group─ basically selecting a bunch of faces ignoring the nesting?

Context:

Select specific faces on these objects to measure the total area of those selected faces using entity info.

block

It is really a simple concept. Here is an example for you …

1 Like

The example only selects 1 face at a time, but you can use it as a starting point and modify it so it keeps adding the selected faces to the selection set.

Basically comment out line 43 in the onLButtonUp callback method, where the selection is cleared for each pick.

I could’ve got fancy and done a test upon the flags argument for the COPY_MODIFIER_MASK and skipped the selection clear. (Maybe something for a version 2?)

For normal CTRL(Option) + click to add items to a selection, replace line 43 with:

          unless (flags & COPY_MODIFIER_MASK) == COPY_MODIFIER_MASK
            @selset.clear
          end

For fun homework, … you can get even fancier and mimic SketchUp’s Select Tool for all the modifier key combinations.
Ie SHIFT+CTRL+click to remove the face from the selection.
SHIFT+click to toggle the face’s selection state.

The mask for SHIFT is: CONSTRAIN_MODIFIER_MASK


Click to expand and cheat on Homework ...
      def onLButtonUp(flags, x, y, view)
        ph  = view.pick_helper
        num = ph.do_pick(x,y)
        #puts "items picked = #{num}"
        return unless num > 0
        # Note: Selection #add, #remove, and #toggle are method aliases.
        if pressed_shift_and_control(flags) # remove
          #puts "SHIFT + CTRL is pressed"
          @selset.remove(ph.picked_face) if @selset.contains?(ph.picked_face)
        elsif pressed_shift(flags) # toggle
          #puts "SHIFT is pressed"
          @selset.toggle(ph.picked_face)
        elsif pressed_control(flags) # add
          #puts "CTRL is pressed"
          @selset.add(ph.picked_face) unless @selset.contains?(ph.picked_face)
        else # clear and add
          #puts "No modifiers pressed"
          @selset.clear
          @selset.add(ph.picked_face)
        end
      end

      def pressed_control(flags)
        (flags & COPY_MODIFIER_MASK) == COPY_MODIFIER_MASK
      end

      def pressed_shift(flags)
        (flags & CONSTRAIN_MODIFIER_MASK) == CONSTRAIN_MODIFIER_MASK
      end

      def pressed_shift_and_control(flags)
        pressed_shift(flags) && pressed_control(flags)
      end

:bulb:

4 Likes

What a boss…

This is the coolest software community ever! Thanks!


I can barely make ruby do math for me, but I will attempt to look at what you are saying before looking at the cheat codes!

Actually… Yea I need them cheat codes… I can barely tell ruby to put “Chocolate Rain” in the console.

block

Will be attempting to at least copy and paste what code you have given, and then try to rezip into an RBZ. (I actually don’t really know how to do either of those things. But here we go!)

Thanks, @DanRathbun!

D:

I have no idea what I am doing, but whatever it is clearly doesn’t work. I tried to copy and paste things from here, and edit the info as suggested in the comments. It loaded but it doesn’t do anything:

FaceSniffer_main.rbz (1.9 KB)
FaceSniffer_main.zip (1.9 KB)


Failure.

If you look for an area calculaton, you might be interested in FredoTools::ReporLabelArea, which is part of FredoTools.

You can pick faces at any level and get the sum of areas

2 Likes

Oh. Sorry I thought you were further along given some of the questions you had asked me privately.

I’ll add another post to the Example thread with the changes, but you did paste in the extra methods okay.

However, … somehow you deleted the class method activate, so …

      def self.activate
        Sketchup.active_model.select_tool(self.new)
      end

… needs to be pasted back into the tool class.

Note that this class method is separate and distinct from the class’ instance callback method of the same name.

You need to keep it in the same file and folder structure as I showed in the example rbz.

That is, that the registrar file must have the same name as the extension subfolder, and all the extension’s files (except the registrar) go in the subfolder.

The zipped RBZ usually has “_” and the version appended to the end of the name before the “.rbz”.

After installing the “Plugins” directory look like …

Plugins
  |
  +--- BradenYork_FaceSniffer.rb 
  |
  +--- BradenYork_FaceSniffer
  |     |
  |     +--- FaceSniffer_main.rb
  |
  +--- SomeAuthor_SomeOtherPlugin.rb
  |
  +--- SomeAuthor_SomeOtherPlugin
  |     |
  |     +--- (SomeOtherPlugin's files) ...
  |
  ... etc.

To zip them, in Windows File Explorer, you display the project folder with the registrar file and it’s same named subfolder, then select both the file and the folder.

You right-click the selection and choose “Send to > Compressed (zipped) folder”. The zip should have the same name as the folder and the registrar file.

Lastly you rename the zip archive by appending “_2.0.0” (or whatever the specific version is,) and change the file type to “.rbz”. (Windows will ask you to confirm the file type change.)

So, you should end up with an archive named something like: "BradenYork_FaceSniffer_2.0.0.rbz"


Re the files, you did properly change the top namespace module name, but the registrar file and the folder need to have the “Examples” prefix changed to your namespace name as well.

But you forgot to change the creator (or add your name) and insert a new version and copyright for yourself below mine in the preamble comments of the registrar file. (It’s a MIT License kinda thang.)

You could also have bumped the version to a new major version as this changes behavior of the extension from v1.0.0.

2 Likes

Yea, no… I’m a Ruby super noob. I just wanted to know where to start learning and practicing and you answered my questions beautifully! Along with this one.

I can’t believe I didn’t know about that. I already have fredo tools and it’s been staring me in the face…

block

Hello Braden, I had (have?) these things muddled up as well so I made a couple of clips that may be helpful.

When you download an .rbz and install using the installation manager a file will be placed in your Plugins folder and a new subfolder with another file will also be created.

So, in the case of installing, “Examples_FaceSniffer_1.0.0.rbz”, you’ll end up with, “Examples_FaceSniffer.rb” in your Plugins folder, and a new Plugins folder subfolder named, “Examples_FaceSniffer”, with a file named, “FaceSniffer_main.rb” inside of it.

Your “registrar.rb” isn’t pointing to a “FaceSniffer_main.rb” that is inside of your folder.


(Edit: oops! Quick redo on the vid.)

Here I have, “Examples_FaceSniffer.rb” and “FaceSniffer_main.rb” opened in Notepad++, with Dan’s first modification at line 43 (#commented out line, code snip added below).

I goofed up the other suggestions so went with a KISS here. But I’ll be trying to get the SketchUp Selections to work tomorrow.

Oh … I forgot about this, but Entity Info lies to you about face areas if you are not within the same context and the context that the face is in has been scaled. Ie, if the context is not open for edit.

So the extension needs to calculate the scaled areas and display them elsewhere (the VCB or the status bar, etc.)

1 Like

@DanRathbun - Thank you for posting the examples. They are very helpful and appreciated.

Braden,
Here I selected onLButtonUp code (which I have open in Notepad++), deleted it, and then pasted Dan’s code in its place. I highlighted and did show/hide “-/+” just to show a handy way to check what has been selected. After saving the “FaceSniffer_main.rb” (to its location in the Examples_FaceSniffer folder) it will be reloaded when a new .skp opened.

It’s alive!:

2 Likes

Added FaceSniffer Toolbar.

Not confident that the code is correctly done but it does work in the example here and in FaceSniffer 2.0 ( [Example] A simple FaceSniffer tool extension - Developers / Ruby API - SketchUp Community )

Face Area Sniffer Toolbar Code Snip
    command = UI::Command.new('Face Area Sniffer') { FaceSnifferTool.activate }
	command.tooltip = "Find Face Areas"
	command.status_bar_text = "Select components to display face areas."
	command.small_icon = command.large_icon = "C:/Users/REPLACE_WITH_YOUR_USER_FOLDER_NAME/AppData/Roaming/SketchUp/SketchUp 2022/SketchUp/Plugins/Examples_FaceSniffer/FAS_icon.png"

	toolbar = UI::Toolbar.new("Face Area Sniffer")
	toolbar.add_item(command)
	toolbar.restore

The code is ‘jammed in’ under the tools menu code. This seems aberrant and crude, so any tips or examples on the correct way to go about this would be welcome.

Code Snip, shown inserted for context
if !defined?(@loaded)
      @loaded = true
      CMD = UI::Command.new('Face Area Sniffer') { FaceSnifferTool.activate }
      CMD.status_bar_text = "Pick Nested Faces to see Area"
      UI.menu('Tools').add_item(CMD)
    end
	#Begin inserted code
	command = UI::Command.new('Face Area Sniffer') { FaceSnifferTool.activate }
	command.tooltip = "Find Face Areas"
	command.status_bar_text = "Select components to display face areas."
	command.small_icon = command.large_icon = "C:/Users/YOUR_USER_FOLDER/AppData/Roaming/SketchUp/SketchUp 2022/SketchUp/Plugins/Examples_FaceSniffer/FAS_icon.png"

	toolbar = UI::Toolbar.new("Face Area Sniffer")
	toolbar.add_item(command)
	toolbar.restore
    #End inserted code

  end # extension submodule
end # namespace module

Here is the test icon I used:

FAS_icon

EDIT: Incorrect formatting didn’t show first line of code snip above:

command = UI::Command.new(‘Face Area Sniffer’) { FaceSnifferTool.activate }

1 Like

Well, yall…

I have to say that I am very impressed by your willingness to help and teach me in this matter!
Though I am afraid that I am rather dull in the art of code, so please forgive my slowness.

Check

Check


Success!

Braden_FaceSniffer.zip (1.9 KB)
Braden_FaceSniffer.zip (1.9 KB)

1 Like

Yea, …

  1. ALWAYS put UI object creation code within a reload guard (conditional block,) so that during development and reloading there are not extra menu items, toolbars and buttons created.
    However, you don’t see anything extra if you only loaded it once.

  2. You should reuse and/or modify the CMD object already defined and use it for both the menu item and the toolbar button.

  3. Don’t use hardcoded paths to resources as users can install extensions in other places.
    If it’s in the same location as the evaluated file, then …

     command.small_icon = command.large_icon = File.join( __dir__, 'FAS_icon.png' )
    

    … or …

     command.small_icon = command.large_icon = "#{__dir__}/FAS_icon.png"
    

    REF:

  4. Try to use single quoted strings unless doing interpolation or using escaped characters.
    (Ruby doesn’t then need to scan for these features. And yes, I myself sometimes forget.)

  5. In your status text it’s not components that are selected, it’s nested faces. And those nested in groups can also be selected. Using the plural “areas” sounds weird. It’s a cumulated area that is being calculated and displayed.

  6. (Not critical but,) you don’t need a persistent reference to the toolbar. It’s an application object so SketchUp will keep a reference to it for manual manipulation (positioning, closing, opening, etc.)
    I used a local constant for the command object just so some other code could get a reference easily from outside. (I usually define a CMD hash for extension that have more the an one command.)
    Anyway, You can always cleanup unneeded references by setting them to nil when done with them.

  7. From time to time the #restore method has problems on the initial load and hasn’t displayed the toolbar, so I like to use a conditional as shown below.


So, the UI load guard block could look more like …

    if !defined?(@loaded)
      @loaded = true
      CMD = UI::Command.new('Face Area Sniffer') { FaceSnifferTool.activate }
      CMD.tooltip = 'Sum Area of Faces'
      CMD.status_bar_text = 'Select faces to display cumulative area.'
      CMD.small_icon = CMD.large_icon = File.join( __dir__, 'FAS_icon.png' )

      UI.menu('Tools').add_item(CMD)

      UI::Toolbar.new('Face Area Sniffer').instance_eval {
        self.add_item(CMD)
        if self.get_last_state == TB_NEVER_SHOWN
          self.show
        else
          self.restore
        end
      }
    end
1 Like

Thank you for this. I tested as I went along and then made one change (the addition of an “Icons” folder).

File.join to new subdirectory
command.small_icon = command.large_icon = File.join(__dir__, 'Icons/FAS_icon.png')
#Option 2, tested also works.
command.small_icon = command.large_icon = "#{__dir__}/Icons/FAS_icon.png"

I made a mistake when retyping the code, which was the omitted, “command”: CMD.small_icon = large_icon...)

The icon did not load. Instead of entering, “command”, I went with, “CMD”.

The functional result of following your example:

Summary
    if !defined?(@loaded)
      @loaded = true
      CMD = UI::Command.new('Face Area Sniffer') { FaceSnifferTool.activate }
      CMD.status_bar_text = "Pick Nested Faces to see Area"
	  CMD.tooltip = 'Sum Area of Faces'
      CMD.small_icon = CMD.large_icon = File.join(__dir__, 'Icons/FAS_icon.png')
	  
	  UI.menu('Tools').add_item(CMD)
	  
	  UI::Toolbar.new('Face Area Sniffer').instance_eval {
	    self.add_item(CMD)
		if self.get_last_state == TB_NEVER_SHOWN
		  self.show
		else
		  self.restore
		end  
	  }	  
		  
	end

This may be what I need to do with the extension monstrosity I created in my Notepad++ laboratory.

Agreed. My face areas (mostly tongue and mouth) don’t always cooperate with my brain and finger areas.

Homunculus Map

Thanks again for the help Dan.

Laff :laughing: … I made the mistake first … fixed now in the above snippet.

1 Like