Plugin folder structure - Is it a good habit to extend search path?

Hi,

My plugin folder is now organized based on the SU guidelines for extensions.

I have noticed that some authors extend the search path for Ruby with something like

basedir = File.dirname(__FILE__)
$:.push(File.join(basedir, 'plugin_folder_name'))

This allows to require files without specifying the path.

I was wondering whether this is actually a good idea. If two authors create a file with the same name, then only the first file would be loaded from require and this is not necessarily the right one. Am I understanding it correctly?

I was thinking to a way to make the plugin independent from the main plugin file in the Plugin folder and I thought that the following approach should work. I first define three constants:

PLUGIN_NAME      = File.basename( __FILE__, ".*").freeze
PLUGIN_PATH_ROOT = File.dirname( __FILE__ ).freeze
PLUGIN_PATH      = File.join( PLUGIN_PATH_ROOT, PLUGIN_NAME ).freeze

I would then replace all the requires in my code with

require File.join(PLUGIN_PATH, 'file_required')

In this way, if I want to change the plugin name (and I need to do that in the future) I can simply change the main rb file and the corresponding folder name

Is this the recommended approach?

Corollary question about require: would it make sense to put all the requires in the loader file within the respective plugin folder?

Thanks

You understand correctly. There is no good reason to add paths to Rubyā€™s search path.

2 Likes

you can achieves the same with

require_relative   'file_required'

IMHO, too many plugins overload Ruby with occasional use methodsā€¦

To ensure none get loaded before the user actually wants to use them,
I tend to use the menu file to load the ā€˜loadersā€™ needed for my pluginsā€¦
it will add the menu items, toolbars and occasionally relevant observers, then only when called, they load the required logicā€¦

command1 = UI::Command.new("Add This Now"){
                          load( File.join(File.dirname(__FILE__), "Add_This_Now_logic.rb"))}

This also ensures my logic has precedenceā€¦

john

1 Like

Yes, this is how most of us do it. Also some of this path creation has been built into the SketchupExtension class (in later versions.)
Read ā€œTools/extensions.rbā€

1 Like

I am trying to use this technique with my plugin but the problem is that after the menu item runs once it will not fire again.
Here is a snippet of the Medeek_Load.rb file :

module Medeek_Engineering_Inc_Extensions

	module MedeekTrussPluginModuleLoader

	# Show the Ruby Console at startup so we can
	# see any programming errors we may make.
	# SKETCHUP_CONSOLE.show

	# First we pull in the standard API hooks.


	this_dir=File.dirname(__FILE__)
	# Fix for ruby 2.0
	if this_dir.respond_to?(:force_encoding)
		this_dir=this_dir.dup.force_encoding("UTF-8")
	end
	PATH=this_dir
	entries=Dir.entries(this_dir)
	ext=".rbs"


	# Add a toolbar item to launch our plugin.

	toolbar = UI::Toolbar.new "Medeek Truss"

cmd = UI::Command.new("Draw Roof Truss") {

		Sketchup.load(File.join(this_dir,"MEDEEK_ROOF_TRUSS.rbs"))
}
	cmd.small_icon = "images/mdkplg_tool_icon16_2.png"
	cmd.large_icon = "images/mdkplg_tool_icon24_2.png"
	cmd.tooltip = "Medeek Truss"
	cmd.status_bar_text = "Draw Roof Truss"
	cmd.menu_text = "Roof Truss"
	toolbar = toolbar.add_item cmd
	# toolbar.show

	cmd2 = UI::Command.new("Draw Floor Truss") {

	Sketchup.load(File.join(this_dir,"MEDEEK_FLOOR_TRUSS.rbs"))
}
	cmd2.small_icon = "images/mdkplg_tool_icon16_3.png"
	cmd2.large_icon = "images/mdkplg_tool_icon24_3.png"
	cmd2.tooltip = "Medeek Truss"
	cmd2.status_bar_text = "Draw Floor Truss"
	cmd2.menu_text = "Floor Truss"
	toolbar = toolbar.add_item cmd2
	# toolbar.show

	cmd3 = UI::Command.new("Draw Truss Set") {

	Sketchup.load(File.join(this_dir,"MEDEEK_TRUSS_SET.rbs"))
}
	cmd3.small_icon = "images/mdkplg_tool_icon16_4.png"
	cmd3.large_icon = "images/mdkplg_tool_icon24_4.png"
	cmd3.tooltip = "Medeek Truss"
	cmd3.status_bar_text = "Draw Truss Set"
	cmd3.menu_text = "Truss Set"
	toolbar = toolbar.add_item cmd3
	toolbar.show

	cmd4 = UI::Command.new("Draw Roof Rafters") {

	Sketchup.load(File.join(this_dir,"MEDEEK_ROOF_RAFTERS.rbs"))
}
	cmd4.small_icon = "images/mdkplg_tool_icon16_5.png"
	cmd4.large_icon = "images/mdkplg_tool_icon24_5.png"
	cmd4.tooltip = "Medeek Truss"
	cmd4.status_bar_text = "Draw Roof Rafters"
	cmd4.menu_text = "Roof Rafters"
	toolbar = toolbar.add_item cmd4
	toolbar.show


	# Add a menu item to launch our plugin.

	submenu = UI.menu("Plugins").add_submenu("Medeek Truss")
	submenu.add_item("Roof Truss") { Sketchup.load(File.join(this_dir,"MEDEEK_ROOF_TRUSS.rbs")) }
	submenu.add_item("Floor Truss") { Sketchup.load(File.join(this_dir,"MEDEEK_FLOOR_TRUSS.rbs")) }
	submenu.add_item("Truss Set") { Sketchup.load(File.join(this_dir,"MEDEEK_TRUSS_SET.rbs")) }
	submenu.add_item("Roof Rafters") { Sketchup.load(File.join(this_dir,"MEDEEK_ROOF_RAFTERS.rbs")) }
	


	end
end

It depends if you have a call as the last line in the file, or as part of the commandā€¦
in this example I call JcB::TransL8.get_text from the command2ā€¦

      if @dev_mode

        transL8_menu = UI.menu('Help',(TRANSL8["Add Translation to Plugin"]))

        command2 = UI::Command.new((TRANSL8["Add Translation to Plugin"])){
                                load( File.join(File.dirname(__FILE__), 'transL8_process.rb'))
                                JcB::TransL8.get_text 
}

        transL8_menu.add_item(command2)

      end

once you have a command you can reuse it for both menu and toolbarā€¦
for a .rbs you may need to use Sketchup.requireā€¦
for your toolbar it is better to use restore for people who want it ā€˜offā€™ā€¦

john

For the first item I call up the file ā€œMEDEEK_ROOF_TRUSS.rbsā€, the code in this file in very condensed form looks like:

require 'sketchup.rb'
require 'extensions.rb'
require 'langhandler.rb'

module Medeek_Engineering_Inc_Extensions

	module MedeekTrussPlugin


##############################
#
# Class Methods of Plugin
#
##############################

class MedeekMethods
  	class << self

include Math

def some method name

{
ā€¦
}

def another method name

{
ā€¦
}

end # << self
end # MedeekMethods Class

###########################
#
# Main Entry into Program
#
###########################

MedeekMethods.roof_truss_family_menu


end # MedeekTrussPlugin

end # Medeek Module

I guess Iā€™m not fully understanding how to make this work. I will study your example above and see if I can implement it.

My original code was all contained within one file and now it has become rather lengthy and unmanageable. The idea is to break it into four separate files and only that portion of the code that pertains is loaded when a user clicks on a menu item or toolbar icon.

I am very new to Ruby programming so I apologize for my ignorance.

not a problemā€¦

each file still needs a command, with plain .rb files this can be either the last line, or in the menu cmdā€¦

with .rbs that may need to be in the menu cmdā€¦

if it is there, you can use require rather than loadā€¦

for your example itā€™s something likeā€¦
Medeek_Engineering_Inc_Extensions::MedeekTrussPlugin::MedeekMethods.roof_truss_family_menu

john

1 Like

This revised code seems to work:

module Medeek_Engineering_Inc_Extensions

module MedeekTrussPluginModuleLoader


require 'sketchup.rb'
require 'extensions.rb'
require 'langhandler.rb'

# Show the Ruby Console at startup so we can
# see any programming errors we may make.
# SKETCHUP_CONSOLE.show




this_dir=File.dirname(__FILE__)
# Fix for ruby 2.0
if this_dir.respond_to?(:force_encoding)
	this_dir=this_dir.dup.force_encoding("UTF-8")
end
PATH=this_dir
entries=Dir.entries(this_dir)
ext=".rbs"


# Add a toolbar item to launch our plugin.

toolbar = UI::Toolbar.new "Medeek Truss"
 	
 	cmd = UI::Command.new("Draw Roof Truss") {

	Sketchup.load(File.join(this_dir,"MEDEEK_ROOF_TRUSS.rbs"))
	Medeek_Engineering_Inc_Extensions::MedeekTrussPlugin::MedeekMethods.roof_truss_family_menu
 	}
		cmd.small_icon = "images/mdkplg_tool_icon16_2.png"
 		cmd.large_icon = "images/mdkplg_tool_icon24_2.png"
 		cmd.tooltip = "Medeek Truss"
		cmd.status_bar_text = "Draw Roof Truss"
		cmd.menu_text = "Roof Truss"
 		toolbar = toolbar.add_item cmd
 		# toolbar.show

cmd2 = UI::Command.new("Draw Floor Truss") {

   		Sketchup.load(File.join(this_dir,"MEDEEK_FLOOR_TRUSS.rbs"))
	Medeek_Engineering_Inc_Extensions::MedeekTrussPlugin::FloorTruss::MedeekMethods.floor_truss_family_menu
 	}
		cmd2.small_icon = "images/mdkplg_tool_icon16_3.png"
 		cmd2.large_icon = "images/mdkplg_tool_icon24_3.png"
 		cmd2.tooltip = "Medeek Truss"
		cmd2.status_bar_text = "Draw Floor Truss"
		cmd2.menu_text = "Floor Truss"
 		toolbar = toolbar.add_item cmd2
 		# toolbar.show

cmd3 = UI::Command.new("Draw Truss Set") {

   		Sketchup.load(File.join(this_dir,"MEDEEK_TRUSS_SET.rbs"))
	Medeek_Engineering_Inc_Extensions::MedeekTrussPlugin::TrussSet::MedeekMethods.truss_set_family_menu
 	}
		cmd3.small_icon = "images/mdkplg_tool_icon16_4.png"
 		cmd3.large_icon = "images/mdkplg_tool_icon24_4.png"
 		cmd3.tooltip = "Medeek Truss"
		cmd3.status_bar_text = "Draw Truss Set"
		cmd3.menu_text = "Truss Set"
 		toolbar = toolbar.add_item cmd3
 		# toolbar.show

cmd4 = UI::Command.new("Draw Roof Rafters") {

   		Sketchup.load(File.join(this_dir,"MEDEEK_ROOF_RAFTERS.rbs"))
	Medeek_Engineering_Inc_Extensions::MedeekTrussPlugin::RoofRafters::MedeekMethods.roof_rafters_family_menu
 	}
		cmd4.small_icon = "images/mdkplg_tool_icon16_5.png"
 		cmd4.large_icon = "images/mdkplg_tool_icon24_5.png"
 		cmd4.tooltip = "Medeek Truss"
		cmd4.status_bar_text = "Draw Roof Rafters"
		cmd4.menu_text = "Roof Rafters"
 		toolbar = toolbar.add_item cmd4
 		toolbar.show


# Add a menu item to launch our plugin.

submenu = UI.menu("Plugins").add_submenu("Medeek Truss")
submenu.add_item(cmd)
submenu.add_item(cmd2)
submenu.add_item(cmd3)
submenu.add_item(cmd4)



end

end

Thank-you for the assistance. A simple fix but it had me stumped for a few hours.

1 Like

I guess I had not paid much attention to this topic at the time, but Iā€™ll weigh in (7.5 years later.)


(1) Avoid the use of Sketchup::load as there is an open issue to alter it to make it work like Kernel#load.
Currently it is just alias for Sketchup::require which should be used explicitly.

Also the file extensions should not be used with these methods calls.


(2) The best way to conditionally load files that define objects, is to test whether those objects (modules or classes) have been defined yet.

So ā€¦

require 'sketchup' unless defined?(LanguageHandler)
require 'extensions' unless defined?(SketchupExtension)

ā€¦ or more toward your example ā€¦

module Medeek_Engineering_Inc_Extensions
  module MedeekTrussPluginModuleLoader

    # ... other code ...

    # Add a toolbar item to launch our plugin.

    toolbar = UI::Toolbar.new('Medeek Truss')
      
    cmd = UI::Command.new('Draw Roof Truss') {
      unless defined?(::Medeek_Engineering_Inc_Extensions::MedeekTrussPlugin)
        Sketchup.require(File.join(__dir__,'MEDEEK_ROOF_TRUSS'))
      end
      ::Medeek_Engineering_Inc_Extensions::MedeekTrussPlugin::MedeekMethods.roof_truss_family_menu
    }

    # ... etc., ... more commands ...

    # ... populating the submenu and the toolbar, etc., ...

  end # submodule
end # top level namespace