[Example] A simple FaceSniffer tool extension

A very simple face area sniffer tool example per request by Braden York.

NOTE: Updated version in next post with extra features.


Examples_FaceSniffer_1.0.0.rbz (1.7 KB)

The extension registrar script for the "Plugins" folder:
# encoding: UTF-8

# File: Plugins/Examples_FaceSniffer.rb
#
# An example that adds an "FaceSniffer" tool item to the bottom of the
# "Tools" menu.
#
# Copyright 2022, released under the MIT License
#
# * v 1.0.0 - Dan Rathbun
#
# Permission to use, copy, modify, and distribute this software for any purpose
# and without fee is hereby granted, provided that the above copyright notice
# appear in all copies.
#
# However if you modify it, PLEASE change the top level namespace module name
# in both files to YOUR OWN unique top level namespace module, and then add
# your copyright notice to the list above.
#
# THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
# WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
#
#-----------------------------------------------------------------------------

module Examples
  module FaceSniffer

    EXTENSION = SketchupExtension.new(
      'Face Sniffer',
      'Examples_FaceSniffer/FaceSniffer_main.rb'
    )
    EXTENSION.instance_eval {
      self.description= 'Adds a Tools > Face Sniffer command.'
      self.version=     '1.0.0'
      self.copyright=   "©2022 under MIT License"
      self.creator=     'Dan Rathbun'
    }
    Sketchup.register_extension(EXTENSION, true)

  end # extension submodule
end # top level namespace module

And the main file in the "Examples_FaceSniffer" folder:
# encoding: UTF-8

# This is the main file that implements the sniffer tool.
#
# If you modify this example extension, you should change the
# top level namespace module name to your unique namespace.
# Also then add your copyright notice to the comments at the
# top of the extension registrar file.

module Examples # <-- the top level namespace module
  module FaceSniffer # extension submodule

    class FaceSnifferTool

      CURSOR ||= UI.create_cursor(
        Sketchup.find_support_file('cursor_select.svg', 'Images'),
        0, 0
      )

      def initialize
        @selset = Sketchup.active_model.selection
      end

      def activate
        Sketchup.set_status_text(
          'Select faces to see their area in Entity Info'
        )
      end

      def deactivate(view)
        @selset.clear
        Sketchup.set_status_text("")
      end

      def onSetCursor
        UI.set_cursor(CURSOR)
      end

      def onLButtonUp(flags, x, y, view)
        ph  = view.pick_helper
        num = ph.do_pick(x,y)
        if num > 0
          @selset.clear
          @selset.add(ph.picked_face)
        end
      end

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

    end # tool class

    if !defined?(@loaded)
      @loaded = true
      CMD = UI::Command.new('Face Sniffer') {
        FaceSnifferTool.activate
      }
      CMD.status_bar_text = "Pick Nested Faces to see Area"
      UI.menu('Tools').add_item(CMD)
    end

  end # extension submodule
end # namespace module

~

5 Likes

A little more complex edition of the same extension.

Examples_FaceSniffer_2.0.0.rbz (2.7 KB)

  • Still will select nested faces
  • Display correct cumulative area for scaled faces in the VCB
    (Ignore the area in Model Info as it will be incorrect for scaled faces.)
  • Displays area in correct model units format
  • Reacts to changes in units area format instantly
  • Mimics native Select Tool for adding, removing, toggling to selection
    • Uses native select cursors
  • ESC key will now reset tool
The extension registrar script for the "Plugins" folder:
# encoding: UTF-8

# File: Plugins/Examples_FaceSniffer.rb
#
# An example that adds an "FaceSniffer" tool item to the bottom of the
# "Tools" menu. This tool shows the cumulative area of selected faces
# in the Value Control Box. It allows picking nested faces. 
#
# Copyright 2022, released under the MIT License
#
# * v 2.0.0 - Dan Rathbun
#
# Permission to use, copy, modify, and distribute this software for any purpose
# and without fee is hereby granted, provided that the above copyright notice
# appear in all copies.
#
# However if you modify it, PLEASE change the top level namespace module name
# in both files to YOUR OWN unique top level namespace module, and then add
# your copyright notice to the list above.
#
# THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
# WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
#
#-----------------------------------------------------------------------------

module Examples
  module FaceSniffer

    EXTENSION = SketchupExtension.new(
      'Face Aera Sniffer',
      'Examples_FaceSniffer/FaceSniffer_main.rb'
    )
    EXTENSION.instance_eval {
      self.description= 'Adds a Tools > Face Area Sniffer command.'
      self.version=     '2.0.0'
      self.copyright=   "©2022 under MIT License"
      self.creator=     'Dan Rathbun'
    }
    Sketchup.register_extension(EXTENSION, true)

  end # extension submodule
end # top level namespace module

And the main file in the "Examples_FaceSniffer" folder:
# encoding: UTF-8

# This is the main file that implements the sniffer tool.
#
# If you modify this example extension, you should change the
# top level namespace module name to your unique namespace.
# Also then add your copyright notice to the comments at the
# top of the extension registrar file.

module Examples # <-- the top level namespace module
  module FaceSniffer # extension submodule

    class FaceSnifferTool

      #{# CONSTANTS : Native Cursor IDs
      #
        IDC_SELECT_ONE ||= 633 # Select one
        IDC_SELECT_ADD ||= 634 # Select +
        IDC_SELECT_REM ||= 636 # Select -
        IDC_SELECT_TOG ||= 635 # Select +/-
      #
      #}#

      def initialize
        @area  = 0
        @model = Sketchup.active_model
        @model.options['UnitsOptions'].add_observer(self)
        @selset = @model.selection
      end

      def activate
        reset()
        update_area()
      end

      def clear_ui
        Sketchup.set_status_text("")
        Sketchup.set_status_text('', SB_VCB_LABEL)
        Sketchup.set_status_text('', SB_VCB_VALUE)
      end

      def deactivate(view)
        @selset.clear
        clear_ui()
        @model.options['UnitsOptions'].remove_observer(self)
      end

      def onCancel(reason, view)
        if reason == 0
          @selset.clear
          @area = 0
          reset()
        end
      end

      def onKeyDown(key, repeat, flags, view)
        # Set the tool's cursor ID appropriately for the select mode:
        if key == VK_SHIFT
          @shift = true
          if @ctrl
            #puts "SHIFT now pressed + CTRL"
            @cursor = IDC_SELECT_REM # remove
          else
            #puts "SHIFT is pressed"
            @cursor = IDC_SELECT_TOG # toggle
          end
          onSetCursor()
        elsif key == VK_CONTROL # add
          @ctrl = true
          if @shift
            #puts "CTRL now pressed + SHIFT"
            @cursor = IDC_SELECT_REM # remove
          else
            #puts "CTRL is pressed"
            @cursor = IDC_SELECT_ADD # add
          end
          onSetCursor()
        end
        false
      end

      def onKeyUp(key, repeat, flags, view)
        # Set the tool's cursor ID appropriately for the select mode:
        if key == VK_SHIFT
          @shift = false
          if @ctrl # was remove mode
            #puts "SHIFT released, CTRL is pressed"
            @cursor = IDC_SELECT_ADD # add
          else # was toggle mode
            #puts "SHIFT was released"
            @cursor = IDC_SELECT_ONE # normal
          end
          onSetCursor()
        elsif key == VK_CONTROL
          @ctrl = false
          if @shift # was remove mode
            #puts "CTRL released, SHIFT is pressed"
            @cursor = IDC_SELECT_TOG # toggle
          else # was add mode
            #puts "CTRL was released"
            @cursor = IDC_SELECT_ONE # normal
          end
          onSetCursor()
        end
        # otherwise do nothing
      end

      def onLButtonUp(flags, x, y, view)
        ph  = view.pick_helper
        num = ph.do_pick(x,y)
        #puts "items picked = #{num}"
        return unless num > 0
        face  = ph.picked_face
        index = ph.count.times.find { |i| ph.leaf_at(i) == face }
        trans = index ? ph.transformation_at(index) : IDENTITY
        area = face.area(trans)
        # Note: Selection #add, #remove, and #toggle are method aliases.
        if pressed_shift_and_control(flags) # remove
          #puts "SHIFT + CTRL is pressed"
          if @selset.contains?(face)
            @selset.remove(face)
            @area -= area
          end
        elsif pressed_shift(flags) # toggle
          #puts "SHIFT is pressed"
          @selset.contains?(face) ? @area -= area : @area += area
          @selset.toggle(face)
        elsif pressed_control(flags) # add
          #puts "CTRL is pressed"
          unless @selset.contains?(face)
            @selset.add(face)
            @area += area
          end
        else # clear and add
          #puts "No modifiers pressed"
          @selset.clear
          @selset.add(face)
          @area = area
        end
        update_area()
      end

      def onSetCursor
        UI.set_cursor(@cursor)
      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

      def reset(view = Sketchup.active_model.active_view)
        @cursor = IDC_SELECT_ONE
        @shift = false
        @ctrl  = false
        Sketchup.set_status_text('Select faces to see their area in VCB')
        Sketchup.set_status_text('Area:', SB_VCB_LABEL)
        update_area()
        view.invalidate
      end

      def resume(view)
        reset()
      end

      def suspend(view)
        clear_ui()
      end

      def update_area
        Sketchup.set_status_text(Sketchup.format_area(@area), SB_VCB_VALUE)
      end

      ### OBSERVER CALLBACK

      def onOptionsProviderChanged(provider, name)
        update_area()
      end

      ### CLASS METHODS

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

    end # tool class

    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

  end # extension submodule
end # namespace module
2 Likes