Make toolbar icon using code

I wonder it is possible to make simple pixel toolbar icon using code or other way

All of my extensions have toolbar icon(png file) path /Plugins folder

And my Plugins folder seems too complicated

I don’t want to see png file in my Plugin folder

Is there a way to union rb file and png file?

On Mac you can also use a vector PDF, and SVG vector format on Windows, as well as PNG. But I’m pretty sure there’s no way to ‘draw’ them on the fly in Ruby.

But they still go in a subfolder of your extension’s folder - by default, though not by necessity, in …/Sketchup/Plugins/YourPluginName/Images.

2 Likes

Each one of your extensions MUST be in it’s OWN SUBFOLDER.
The extension’s files (.rb, .png, .svg, etc.,) do NOT belong in the “Plugins” folder itself.

Read about the SketchupExtension class.

Yes it can be done with code, however it still must be written to a diskfile because that is what the API uses to load into memory. So you won’t gain anything, and you will just slow down the SketchUp load cycle.

So, put your extension’s and their resource files in proper extension subfolders.

ONLY the extension’s registrar file goes in the “Plugins” folder.

1 Like

Can I put two extensions(makebox1.rb, makebox2.rb and two png files) into one subfolder?

image

YES. We do this all the time. * CORRECTED in next post.

Your PNG files should have a .png file extension.

Each of your .rb files should open your submodule for editing (using Ruby module blocks.)
Ie …


Your extension’s registrar file In the "Plugins" folder …
Must be same name as the extension subfolder but with a .rb file extension !

# encoding: UTF-8
# "Plugins/Agent47_BoxMaker.rb"
#
module Agent47
  module BoxMaker

    EXTENSION ||= SketchupExtension.new(
      "BoxMaker", "Agent47_BoxMaker/makebox_loader"
    ).instance_eval do
      # self is the instance object within this block:
      self.name= "Agent47's Nifty BoxMaker"
      self.description= "Allows the creation of various nifty boxes."
      self.version= '1.0.0'
      self.creator= "Agent47"
      self.copyright= "©2020 by author, All Rights Reserved."
      # Return the new object to get assigned to the constant EXTENSION
      self
    end

    Sketchup.register_extension(EXTENSION)

  end # close extension submodule
end # close author namespace module

* Changed the SketchupExtension constructor call’s path argument to omit the .rb file extension (as the documentation advises,) so that the loader file can be scrambled or RBE encrypted.


In your extension "Agent47_BoxMaker" subfolder …

# encoding: UTF-8
# "Plugins/Agent47_BoxMaker/makebox_loader.rb"
#
module Agent47
  module BoxMaker

    ['makebox1','makebox2','makebox_menu'].each do |rb_file|
      Sketchup.require( File.join(__dir__, rb_file) )
    end

  end # close extension submodule
end # close author namespace module

* NOTE: The global __dir__ method returns the full path to where the current evaluating file is located on disk. The File::join method concatenates parts of pathnames using the File::SEPARATOR character.

# encoding: UTF-8
# "Plugins/Agent47_BoxMaker/makebox1.rb"
#
module Agent47
  module BoxMaker

    def make_box_1
      # box making code
    end

  end # close extension submodule
end # close author namespace module
# encoding: UTF-8
# "Plugins/Agent47_BoxMaker/makebox2.rb"
module Agent47
  module BoxMaker

    def make_box_2
      # box making code
    end

  end # close extension submodule
end # close author namespace module
# encoding: UTF-8
# "Plugins/Agent47_BoxMaker/makebox_menu.rb"
#
module Agent47
  module BoxMaker

    extend self

    if !@loaded # Run only on startup
      @loaded = true

      # Define Commands, Menu Items and Toolbars here
      CMD ||= {}
      CMD[:one]= UI::Command.new("Make Box Type 1") { makebox1() }
      CMD[:two]= UI::Command.new("Make Box Type 2") { makebox2() }

      submenu = UI.menu("Plugins").add_submenu("Agent47")
      submenu.add_item(CMD[:one])
      submenu.add_item(CMD[:two])

    end # Run only on startup block

  end # close extension submodule
end # close author namespace module

So, an author can split their extension code up into as many files as they wish to help with code maintenance.

Sorry I misread what you said. I thought you meant 2 files for the same extension.
In Ruby (because it is a dynamic language) we can open an object multiple times, in multiple files, and modify the object. Modules and Classes are objects in Ruby, and can be reopened using the interpretive module and class blocks.

BUT NO each extension must have it’s own registrar file in the "Plugins" folder, and it’s OWN subfolder that is the same name as the registrar file.

The example I show above is for one extension that can draw two kinds of boxes.

To further clarify: this is not a matter of Ruby, it is because of how SketchUp manages extensions.

1 Like

As I understand it, to union two extension rb files [boxmaker1.rb , boxmaker2.rb] in one subfolder
It need
[one subfolder, one load rb file, main.rb or two rb files, susug file, two icons]
Is it right?

I have two more questions…

  1. To make a subfolder, Should it must be registered?

  2. How to make a susig file[pic 3]?

20200807_230419 20200807_230741 20200807_230832

I’m looking for some help, as I can’t get toolbar icones to display.
I have some example code which I copied from this forum to make a simple toolbar (See below) but it shows some generic graphic of a green box with a face which looks its going to be sick.
I have saved the icon files as png file, and to the correct pixel sizes as I understand the requirements are (16x16 and 24x24).
They are stored in a sub folder of the extension folder (see screenshot).
Can anyone advise why they are not displaying?
Thanks

CODE:
module Taxsola
module Extension_Example
unless file_loaded?(FILE)

		#Menu definitions
		@Menu = UI.menu("Plugins")
			@Submenu = @Menu.add_submenu("Extension_Example")
			@Submenu.add_item("Script_1"){self.script_1}



		#Toolbar definitions
		@toolbar = UI::Toolbar.new "Extension_Example"
		cmd = UI::Command.new("Extension_Example") {self.script_1}
		cmd.small_icon = "Cabinets_Icon (custom).png"
		cmd.large_icon = "Cabinets_Icon (custom)(1).png"
		cmd.tooltip = "Script 1"
		cmd.status_bar_text = "Script 1."
		cmd.menu_text = "Script 1"
		@toolbar = @toolbar.add_item cmd
		@toolbar.show



		#Script 1
		def self.script_1
			UI.messagebox("Hello World", type = MB_OK)
		end





		file_loaded(__FILE__)
	end

	@toolbar.show

end #end of extension space

end #end of Taxsola’s space

Please post code correctly in the forum.

… with TAB characters replaced with 2 space characters.

Because SketchUp’s Ruby cannot find them.

The location on disk of the current interpreted file is got with the global __dir__ method.

You have the icons in a subfolder named "Example Images".

You can build a full path to the files using File.join method.

    cmd.small_icon = File.join(__dir__,"Example Images","Cabinets_small.png")
    cmd.large_icon = File.join(__dir__,"Example Images","Cabinets_large.png")

[I suggest shorter descriptive icon names like: “Cabinets_small.png” and “Cabinets_large.png”]

You might also be able to use the block form of Dir::chdir method (which restores the previous current directory when the block ends.)

    Dir.chdir(File.join(__dir__,"Example Images")) do
      cmd.small_icon = "Cabinets_small.png"
      cmd.large_icon = "Cabinets_large.png"
    end

Thanks again, but unfortunately it’s still not working - the green sick face is still showing and not my own custom icons.

I copied and pasted the 2 lines of code as you suggested (replaced what was previously there)…
Capture2

I have reproduced the issue with your latest version of the code. Looking into what might be the cause.


Again

Ruby uses 2 spaces indentation. And please do not paste images of code, post code text correctly.


Several other issues:

(1) In Ruby, variable and method names, by convention are all lower case. Class and module names are CamelCase. Contants are all UPPER_CASE.

(2) Currently menu object references are non-persistent past the loading of a single extension. So there is no point in using instance @menu or @submenu references. Just use local menu and submenu references and set them nil when no longer needed.

(3) Your method definitions should to go before any code that calls them.
You have the script_1 method definition after the definition for the UI::Command and menu item blocks that call it.

(4) Your method definitions should be before and outside the conditional unless file_loaded? block.

In other words, your User Interface code is conditionally run once at startup (when the extension loads.)

But the other parts of your code can be redefined during the development by reloading the code. So you can make changes to methods, reload, test, make edits, … repeat … etc. without having to restart SketchUp. (One of the advantages of a dynamic programming language.)

This is done by using the global #load rather than #require. (The #load method needs a full absolute path and must have the .rb file extension on the filename. It’s not like the #require method which goes searching for various types of files in the paths of the $LOAD_PATH array.

(5) Your instance variables look strange in VS Code. Do you have a Ruby lexer plugin installed in VS Code ?

The cause: I named the images subfolder "Extension Images" instead of "Example Images" so the paths were invalid.

I corrected the name of the subfolder to "Example Images" and the toolbar buttons displayed correctly.

This works for me using known good png icon files:

# encoding: UTF-8
#
# File: "main.rb"

module Taxsola
  module Extension_Example

    # Script 1
    def self.script_1
      UI.messagebox("Hello World", type = MB_OK)
    end

    unless file_loaded?(__FILE__)

      # UI::Command definitions:
      cmd = UI::Command.new("Script 1") { self.script_1 }
      small_path = File.join(__dir__,"Example Images","Cabinets_Icon (custom).png")
      puts "small_path File.exist? #{File.exist?(small_path)}"
      cmd.small_icon = small_path
      large_path = File.join(__dir__,"Example Images","Cabinets_Icon (custom)(1).png")
      puts "large_path File.exist? #{File.exist?(small_path)}"
      cmd.large_icon = large_path
      cmd.tooltip = "Script 1"
      cmd.status_bar_text = "Script 1."
      cmd.menu_text = "Script 1"

      # Menu definitions:
      menu = UI.menu("Plugins")
      submenu = menu.add_submenu("Extension_Example")
      submenu.add_item( cmd )

      # Toolbar definitions:
      @toolbar = UI::Toolbar.new "Extension_Example"
      @toolbar = @toolbar.add_item( cmd )
      @toolbar.show

      file_loaded(__FILE__)
    end

    # Clean up:
    local_variables.each { |var| eval "#{var} = nil" }

  end #end of extension space
end #end of Taxsola’s space

Notice how I moved the definition of the UI::Command up before the menu definition, and used the cmd reference for both the menu and the toolbar.

If the paths to the icons shows up true for existing, then replace icons with known good icons and retest.

If the toolbar works (with known good icon files,) then you’ve created corrupt png files (somehow.)

Thanks Dan
It’s displaying correctly now!

What do ya’ think the problem was ?

I’m not sure yet what the issue was as it’s quite a lot of new information to take in and get my head around it all.
But I just copied and pasted the code and it worked, although I did change the name of the png files to something simpler as you suggested. It’s a good basis for me to learn from so now I can start adding new code and modifying it.
The original png files were fine though (not corrupted) so that can at least be ruled that out.

Then it had to be a typo in the path strings.

Why do you use “extend self” in this last file ?

It can be in any of the files that define objects for a submodule.

Basically it uses the submodule itself as a mixin module for itself. This makes the defined instance methods available throughout the module without needing the self. qualification.

REF:

I also suggest reading the chapter on mixin modules in the ol’ Pick Axe “Programming Ruby” book. See the book lists in the Ruby Resources lists.