[code] SketchupExtension and rbs rubies
-
[ 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()
andinclude()
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 thepath
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 callSketchup.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
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"
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:# 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 methodslarge_icon()
andsmall_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
Removedfalse
argument from [ruby:13edfjou]find[/ruby:13edfjou] method call (line 40.) -
-
@dan rathbun said:
A scrambled rbs file CAN take an absolute path. But I don't know if it can take a relative path.
When I hacked UI::Command to intercept the data plugins sent I found that Sandbox Tools used a relative path. Though I don't remember what it was relative to.
-
@thomthom said:
@dan rathbun said:
A scrambled rbs file CAN take an absolute path. But I don't know if it can take a relative path.
When I hacked UI::Command to intercept the data plugins sent I found that Sandbox Tools used a relative path. Though I don't remember what it was relative to.
The problem is... that example does not equate, because the command builder script for Sandbox is unscrambled. It's named
%(#004000)[Sandbox/sandboxmenus.rb]
-
Fixed: example above for "author_widget_ext.rb"
ABSDIR
was usingFile.expand_path
incorrectly.Added in the search of
$LOAD_PATH
array so that the ext registration script can be in the Plugins folder (or any of the$LOAD_PATH
folders,) and the target plugin dir can be below ANY of the$LOAD_PATH
folders, (possibly even a different one than the "author_widget_ext.rb" script.) -
Fixed: example above for "author_widget_ext.rb"
Line 19:
Sketchup.register_extension( @@plugin, true )
Moved down to line 51, so ALL constants are defined BEFORE extension loading.
Thanks to Keld Sørensen (ksor) for finding this bug.
-
Dan, I tried to implement your extension script, but around line 25:
def @@plugin.path() @path end
The @path variable seems to be undefined. Do you recall where that should be set? (I'm probably missing something?!)
-
No you're not missing something.. I am. Doh! It's a booboo.
The
@path
variable is out of scope.But it's not THAT important. From inside you plugin, you could always just:
ext_path = @@plugin.instance_eval("@path")
Thanks for keeping me honest !! I'll fix it now.
Fixed: example above for "author_widget_ext.rb", line 25
@blruuska said:
Do you recall where that should be set?
It is set by the
SketchupExtension
class constructornew()
method's second argument, which is required. (Ie they will not let you create the object with empty args, and set them afterward. If@path
does not get set, then it'sload
method will raise an exception. So they wanted to force you to set it in the constructor call. And, BTW there is no setter method defined either.) -
Dan,
How to escape of the "false" at line 40?
ROOT = $LOAD_PATH.find(false) do |abspath|
SketchUp 8 show undefined method `call' for false:FalseClass
-
@greenskp said:
Dan,
How to escape of the "false" at line 40?
ROOT = $LOAD_PATH.find(false) do |abspath|
SketchUp 8 show undefined method `call' for false:FalseClass
Just eliminate the argument for
find
. If no match is found, the method will returnnil
, which evaluates asfalse
.So use:
ROOT = $LOAD_PATH.find do |abspath|
Sorry this is a very old topic thread. It may have errors running under newer Ruby versions.
-
WARNING: This is an OLD topic thread. The
SketchupExtension
class has been revised (at least) twice since this topic was written.
Advertisement