[ code ] SketchupExtension and rbs rubies
What is SketchupExtension ?
- It's not AN extension (or plugin,) it's a Ruby class named SketchupExtension that is basically a custom hash. It registers plugins with the Sketchup application, so it can display them in a list. The list is on the "Extensions" panel, of the "Preferences" dialog (accessed from the "Window" menu.). The list has check boxes so the user can decide whether the extension / plugin will (or will not be,) loaded when Sketchup starts.
- Examples in the Sketchup API Blog (by Scott Lininger.)
- The class Reference for SketchupExtension in the API Documentation.
This example concentrates on the quirks regarding scrambled rbs scripts with the SketchupExtension class. (Although the issues here can also be applied to non-scrambled Ruby scripts, as well.)
WARNING: This is an OLD topic thread. The SketchupExtension class has been revised (at least) twice since this topic was written.
1) Manage your "filespace"
Why mix your files up with everyone else's ??
If everyone puts all their files into the Plugins folder, we will have filename clashes, and the Plugins folder will become unmanagable. We will not know what files belong to which plugin.
I encourage rubyists, to use an "author" subfolder, of the Plugins folder (which would be their top level "filespace",) and put ALL their plugins, in sub-folders of their "author" subfolder. (The actual name of the "author" folder can be a company name, or your SCF screen name, or whatever you like. Just do not use some other entity's trademark name like "CocaCola".)
WARNING: Since the ExtensionWarehouse was implemented, ALL plugins sub-dirirectories must be directly in one of the "plugin" directories, AND be prefixed with the author's namespace, followed by an underscore character. (See the ExtensionWarehouse Developer's Documentation.)
A specific plugin subdir, can have it's own subdirs, if you wish, such as "images".
Keep naming conventions simple, by sync'ing directory names and module (plugin) names:
- Use the same name for your toplevel author namespace, AND your toplevel folder (directory.)
- Use the same names for your submodule names, AND your subfolder names. This makes it easier to remember what the pathstrings and namespace stings will be, when you code require(), load() and include() arguments.
2) Manage your Ruby namespaces
You should be using a toplevel "Author" or "Company" module to wrap ALL of your plugins, to protect your code from other author's code, and their code from yours.
Each one of your separate plugins should be defined within a separate submodule of your toplevel module, so the code for each plugin runs within it's own namespace, and does not clash with your other plugins.
3) Using SketchupExtension class and scrambled rbs plugins.
In the examples below... the identifer Author would be replaced by your own toplevel module identifier, and the folder name "author" would be replaced by the name of YOUR author folder.
There are a few quirks you need to be aware of with scrambled rubies.
1) If your using the SketchupExtension class (see file Tools/extensions.rb,) the file refered to in the path attribute must be an unscrambled script. Convention is that it is named with a "_loader.rb" suffix. So if the name of your plugin is "widget" then the registration script in the Plugins folder would be "author_widget_ext.rb", which sets the path to "author/widget/widget_loader.rb" (in your plugin subfolder, of your author subfolder.)
The script for "author/widget/widget_loader.rb" would be a one-liner, that simply says:
Sketchup.require("author/widget/widget")
which will (if it isn't loaded,) will call Sketchup.load("author/widget/widget.rbs")
- SketchupExtension registration scripts:
- IF they will be in the "Plugins" folder, prefix their filename, with "Author_" (where Author is YOUR toplevel module name, aka YOUR namespace.) This helps prevent name clashes, and we can tell at a glance in a directory listing, who authored the file.)
- Put the PluginName into the filename, after any prefix, and before the suffix. (Where PluginName is the name of the plugin, or it's submodule name. This helps prevent filename clashes, and we can tell at a glance in a directory listing, what plugin it registers.)
- Suffix them with "_ext.rb" so we can tell at a glance, that they are SketchupExtension registration scripts.
2) Special Ruby __vars__The special Ruby keywords (actually built-in functions,) __FILE__ and __LINE__ do NOT work inside scrambled rbs scripts (as of v8.0M2.)
I've reported it in the last beta round, and we've complained about it all thru the ver 7 lifecycle as well. Hoping it will be fixed in the next release.
[Fixed with SketchUp 2014M0, using Ruby 2.0]
Workaround:
Keep a reference to your SketchupExtension instance object, by doing this for your extension registration script (this file goes in the main Plugins folder.)
author_widget_ext.rb
- Code: Select all
require('extensions.rb')
module Author # Proprietary TopLevel Namespace: No Tresspassing!
module Widget # Namespace for THIS plugin
# Create an entry in the Extension list that loads
# a script called: "author/widget/widget_loader.rb"
@@plugin = SketchupExtension.new( "Author's Widget", "author/widget/widget_loader.rb" )
@@plugin.creator = 'Author'
@@plugin.copyright = "(c)2011 by Author"
@@plugin.version = '1.0.0'
@@plugin.description = "A Widget that does fancy things."
unless @@plugin.class.method_defined?(:path)
#
# Define a singleton method for @@plugin to access the @path attribute.
# (... because the API did not do it in extensions.rb !!)
#
def @@plugin.path()
instance_variable_get(:@path)
end
end # unless
# Create local path and filename constants:
#
PATH = @@plugin.path()
LOADER_SUFFIX = '_loader'
LOADER = File.basename(PATH)
RBSFILE = LOADER.split("_")[0] << '.rbs'
# RELDIR is relative to Plugins dir, OR the dir from
# the $LOAD_PATH array that require used to find it.
RELDIR = File.dirname(PATH)
# search the load paths for a valid RELDIR
ROOT = $LOAD_PATH.find do |abspath|
Kernel.test(?d, File.join(abspath,RELDIR) )
end
if ROOT
ABSDIR = File.join( ROOT, RELDIR )
else
# assume the target dir is directly below, the dir that THIS file is in.
ABSDIR = File.join( File.dirname(__FILE__), RELDIR )
end
# Register this extension with the Sketchup::ExtensionManager
Sketchup.register_extension( @@plugin, true )
end # module Author::Widget
end # module Author
"author/widget/widget_loader.rb"
- Code: Select all
Sketchup.require("author/widget/widget")
Now all of the code in the "author/widget/widget.rbs" file should also be namespace wrapped within module Author::Widget like so:
- Code: Select all
# widget.rbs
require('sketchup.rb')
# We can use a namespace qualification, saving an indent,
# because the outer module already has been defined.
#
# Since the inner Widget namespace also already exists,
# the following module definition block just adds to the
# module, or redefines things that are already defined.
# (Such as overriding methods, constants or variables.)
#
module Author::Widget
# The constants PATH, ABSDIR, RELDIR, RBSFILE, etc., can be
# accessed here because they are local to this namespace.
# The same is true for the module var @@plugin, which is the
# reference to your SketchupExtension plugin instance.
def self.plugin()
@@plugin
end
class WIDGETmaker
# your Tool class definition
end #class
# The rest of your scrambled code goes here.
# More methods, etc.
# Can have nested submodules and class definitions, etc.
imgdir = ABSDIR # or ABSDIR + "/images" if in a subdir
### Do only once block
#
unless file_loaded?( RBSFILE )
# create toolbar
@@toolbar = UI::Toolbar.new("Author Widget Toolbar")
def self.toolbar()
@@toolbar
end
# Button 1
cmd = UI::Command.new("Widget") { Sketchup.active_model.select_tool WIDGETmaker.new }
cmd.large_icon = File.join(imgdir, "WIDGET_LG.png")
cmd.small_icon = File.join(imgdir, "WIDGET_SM.png")
cmd.status_bar_text = "Use Author's nifty widget."
cmd.tooltip = "Widget"
@@toolbar.add_item cmd
file_loaded( RBSFILE )
end # unless
end #mod Author::Widget
3) Paths for toolbar icons:
The UI::Command instance methods large_icon() and small_icon() can (in unscrambled rb files,) take either an absolute path, or a relative path (that is relative to the dir that the ruby script is in, NOT relative to the Plugins folder.)
A scrambled rbs file CAN take an absolute path. But I don't know if it can take a relative path.
Edited: 08-AUG-2014
Removed false argument from find method call (line 40.)