Instructor content
-
@bentleykfrog said:
Issues I'm unsure of:
- I removed the @@tool_filepath setting as I'm not sure what to do with it. If the scripts are intended to be truly portable, then how does an .rbs file require() other files? ThomThom's vertex tools method suggests that their code will already have a variable showing the scripts location, with which you could set the instructor folder location from, and use the location folder attribute of lins_setup().
Well, he actually says constant. And this is true, because the
SketchupExtension
classload()
(which usesSketchup::require()
,) cannot for some reason directly load rbs files. So we must use a non-scrambled loader script to in turn load the rbs. I do the same thing ThomThom does.. however, there is no way to know what the author will choose to name his file path constant. Nor.. should a library (or mixin,) module force an author to use any specific name. Therefor.. an internal variable is needed, and I chose to make it a class variable, and named it@@tool_filepath
. ThomThom and any other author, simply assigns@@tool_filepath
to point to the constant name they defined in their loader script. Perhaps because my example showed a literal string assignment, you assumed that was necessary? It's not, for example ThomThom could declare in his Tool class:
@@tool_filepath = VertexToolsPath
The way I had it.. it would use
__FILE__
as a default for non-scrambled files, and insist on a explicit assignment of the@@tool_filepath
variable for rbs files.
By instead, adding the required argumentscript_folder
to thelins_setup()
method, you have also defeated the defaultinitialize()
method that is mixed in (it has no arguments in thelins_setup()
call.)
I think it will work better for newbie's the previous way.@bentleykfrog said:
- Sketchup loaded from a server location? (ie. portable versions of Sketchup?)
In normal circumstances, this is a violation of the Sketchup "Terms of Use." After all cloud computing is one of Google's main business models. One website that did this was told to remove Sketchup from their servers.
Now if you were in a educational environment, and had permission from Google it may be different. But it's not that hard to keep client application installs up to date. The 'bear' is keeping all the Plugins for all those client workstations up to date. So really using a shared Plugins folder on a network drive is what most admins will want to do. And as I showed (above) it can easily be done by pussing a network path into the$LOAD_PATH
array and using therequire_all()
method.
The tricky part comes in with TIG's case, where each student has their own personal "User Plugins" folder on a network drive. These personal folders cannot be shared with write access (because it would defeat the purpose of having secure user accounts.)
So everything should work fine for the student whilst they are working on a plugin... because they will have access to their own instructor content folders. When they finish and 'publish' the plugin for others to use, the plugin would get installed (by the teacher, or an IT person,) into the 'Network Shared Plugins' path, where all the students could have access to it and it's help content.@bentleykfrog said:
- Attaching an AppObserver for onQuit to delete the temp file? (I wouldn't have a clue about this: I'm concerned that it might overwrite other plugins' onQuit method, and I'm guessing that onQuit won't be called when there's a bugsplat, so maybe its better to have consistent temp folder directories as in this update? and rely on the system/users preference for temp folder clearing?)
True about the BugSplat! If you've done away with the temp names that have timecodes embedded, then the erase on first access method will work fine (in case the help content was updated after the last use.)
TheonQuit()
would not interfere as a special observer instance would have been used, BUT... because of their quirky nature, if you can avoid an observer, do so. ALSO I have been trying to use an AppObserver and it'sonQuit()
callback, (in another project,) only to discover that Sketchup does not wait for the callback to return. I'm trying to save settings and window locations.. but Sketchup is closing all it's owned and child windows, and it's own application window before any of the code in my callback can do it's job. Annoying!@bentleykfrog said:
Thanks to Dan for all the tips/advice/&code, you've been more than helpful.
no problem.. glad I could help.
-
A few more observations / issues:
File.exists?
is deprecated.
Use the callFile.exist?
(singular) for future compatability.The
$LOAD_PATH
members can be manipulated. Making an assumption about where the the first member points could cause problems. (Refering to thelins_get_default_instructor_folder()
method.)The
lins_return_relative_path()
method still references the deprecatedPLATFORM
constant, and assumes all PCs will running a MSVS compiled Ruby. The mingW compiled Ruby is becoming more popular and the Windows Ruby One-Click Installer uses only the mingW compiler now.
To test for Windows, Line 553 should be, either:
if( not RUBY_PLATFORM =~ /darwin/i )
or
if RUBY_PLATFORM =~ /(mswin|mingw)/i
The second has the advantage that, if in the future say a Sketchup Linux edition is released, you code will not break.Instructions:
The inline instructions have grown to be very confusing for newbies.
I would suggest reverting to the simple default instructions, and add a note for advanced use see load_instructor_advanced.txt, and move all the advanced instructions into the separate text file.And.. when this is all done (seeing as how you still have a beta version number,) and you decide on the first release, it would be best to create a new topic with a [ code ] prefix for version 1.0 (so Jim's indexer script can find it.) Then we will remove our beta version(s) this topic, and insert a link to the code release topic.
-
Dan, your advice is invaluable. There's still a couple of things I'm concerned about:
@dan rathbun said:
The way I had it.. it would use
__FILE__
as a default for non-scrambled files, and insist on a explicit assignment of the@@tool_filepath
variable for rbs files.
By instead, adding the required argumentscript_folder
to thelins_setup()
method, you have also defeated the defaultinitialize()
method that is mixed in (it has no arguments in thelins_setup()
call.)
I think it will work better for newbie's the previous way.The issue I had with the previous version was the way that load_instructor.rb constructed the default path to the /instructor/ folder. The previous version used
__FILE__
from /mixin/load_instructor.rb and then navigated back to the folder that contained the /mixin/ folder. From that path it used /instructor/, so it meant that you would have issues if you had more than one ruby script using Load Instructor. So there needs to be a string attribute that identifies the name of the folder that contains /instructor/ to avoid this conflict between plugins. If the LoadInstructor mixin is going to be stored in a file that is separate from your script then calling__FILE__
will only be able to identify /load_instructor.rb and wont be able to identify the filename of the script that the LoadInstructor mixin was loaded into (unless there's a method for getting around this).Regarding the @@tool_filepath issue, you should still be able to declare these paths in the second and third attributes of lins_setup()? like:
@@tool_filepath = VertexToolsPath def initialize instructor_folder = File.join( @@tool_filepath ,'your_scripts_folder/instructor') temp_folder = (RUBY_PLATFORM =~ /darwin/i) ? '' ; File.join(ENV["TEMP"], 'your_scripts_folder/instructor') lins_setup('',instructor_folder,temp_folder) end
And if you're storing all the files beneath any of the load path's, you shouldn't need to worry about the second and third attributes, or @@tool_filepath for that matter, like:
def initialize lins_setup('your_scripts_folder') end
@dan rathbun said:
The
$LOAD_PATH
members can be manipulated. Making an assumption about where the the first member points could cause problems. (Refering to thelins_get_default_instructor_folder()
method.)Hmmm, it should be best then if I threw an error if the /instructor/ folder can't be found, like (using your @@tool_filepath check):
def lins_get_default_instructor_folder(script_folder) instructor_folder = '' $LOAD_PATH.each {|load_path| check_path = File.join(load_path, script_folder, 'instructor') if check_path != '(eval)' && File.exist?(check_path) instructor_folder = File.join(load_path, script_folder, 'instructor') break end } if instructor_folder.empty? file_path = File.join(script_folder, 'instructor') raise(ScriptError,"#{self.class.name}; Instructor folder error. Could not find #{file_path} beneath any of the $LOAD_PATH's.") else return instructor_folder end #if end #def
I've adopted all your other suggestions to the script, I just have to sort out the former issues before I'll produce the first release.
-
Niall... I am sorry. I've got my head wrapped around another project, and I was not seeing the obvious here.
YOU cannot use
__FILE__
at all, in your mixin code. Because it's a special Ruby keyword that gets evaluated when the mixin is parsed.I was thinking wrongly that it's evaluation would be defered if it was used inside a method (the "setup" method.)
The tool author CAN use
__FILE__
if his script is unscrambled, otherwise if he scrambles his tool code, he must use a rbs loader where he should set a constant (like ThomThom's example.)The tool author only cares where YOUR code is when he uses the
require()
statement which should find it via the$LOAD_PATH
array. Otherwise where the mixin file resides (it's path,) has no bearing on anything else. (Once it's loaded.. it is refered to asMixin::LoadInstructor
and it's path is irrelevant.)So really yout mixin NEEDS to use the
@@tool_filepath
class variable in both cases (an rb or rbs.)And the tool author needs to always set it to the path of his tool script file, either:
@@tool_filepath = __FILE__
# for unscrambled
or
@@tool_filepath = SOME_CONSTANT
# (or a literal,) for scrambled rbs.
If the author is smart, they will wrap their loader and declare their path constant, in the same tool class namespace their rbs code in wrapped in. -
@bentleykfrog said:
Dan, your advice is invaluable. There's still a couple of things I'm concerned about:
Regarding the @@tool_filepath issue, you should still be able to declare these paths in the second and third attributes of lins_setup()? like:
@@tool_filepath
would contain the "your_scripts_folder" so to get just the path part use:
File.dirname(@@tool_filepath)
which removes the actual tool script filename, leaving the relative path from one of the$LOAD_PATH
members. You can then expand it to get the tool script's absolute folder path.So your thinking.. what's the difference in making the author use a variable or making them use a method argument ??
Well IMHO the variable is cleaner, set once and forget. And that variable can be referenced by other methods written by the tool author.Here's an example of a rbs loader script that loads a scrambled tool script named testtool.rbs:
module Niall class TestTool require('mixin/load_instructor.rb') include( Mixin;;LoadInstructor ) @@tool_filepath = File.dirname(__FILE__)+'/testtool.rbs' Sketchup.require( @@tool_filepath ) end # class end # module
In this example, the author does the 'mixing' in the loader, sets
@@tool_filepath
and uses it in his call to load the rbs. -
@dan rathbun said:
Here's an example of a rbs loader script that loads a scrambled tool script named testtool.rbs:
module Niall > class TestTool > require('mixin/load_instructor.rb') > include( Mixin;;LoadInstructor ) > @@tool_filepath = File.dirname(__FILE__)+'/testtool.rbs' > Sketchup.require( @@tool_filepath ) > end # class > end # module
In this example, the author does the 'mixing' in the loader, sets
@@tool_filepath
and uses it in his call to load the rbs.Thats cool then, I don't really see an issue passing @@tool_filepath as an argument to lins_setup(). With default values and your script in the $LOAD_PATH's, lins_setup('my_scripts_files') won't require @@tool_filepath. You'll just need it for requiring the mixin possibly? With custom values you'll need to pass absolute values as the second and third arguments so you'll need to use File.join( @@tool_filepath, 'my_scripts_files/instructor-boogaloo'), so that's quite easy to do, and I've documented it under Advanced Use.
Looks like I'm ready to release this code, I'll start a new topic.
-
@bentleykfrog said:
Thats cool then, I don't really see an issue passing @@tool_filepath as an argument to lins_setup(). With default values and your script in the $LOAD_PATH's, lins_setup('my_scripts_files') won't require @@tool_filepath.
You're missing the point of the
@@tool_filepath
variable.
In the simple default mode, YOU set thescript_folder
argument oflins_setup()
to have a default value so the method can be called with no arguments, like so:
def lins_setup( instructor_folder=false, temp_folder=false, script_folder=File.dirname(@@tool_filepath) )
The absolute path to help content would be:
instructor_folder = 'instructor' if instructor_folder==false File.expand_path("#{script_folder}/#{instructor_folder}")
I show the
script_folder
argument on the end, as it's least likely authors will override that argument's default, and most likely wish to specify a custom help content folder name. (Specifying a custom Temp dir would fall in the middle.)@bentleykfrog said:
You'll just need it [
@@tool_filepath
] for requiring the mixin possibly?No.. the plugin script's path has nothing to do with what path the user decides to use for their mixin library. The mixin lbrary folder should be in a subfolder of one of the
$LOAD_PATH
members, so Ruby will have no problem finding it.@bentleykfrog said:
With custom values you'll need to pass absolute values as the second and third arguments so you'll need to use
File.join( @@tool_filepath, 'my_scripts_files/instructor-boogaloo')
, so that's quite easy to do, and I've documented it under Advanced Use.Actually as I said (above,) it's:
File.dirname(@@tool_filepath)
as@@tool_filepath
will be a relative path that contains the tool script filename on the end. -
@dan rathbun said:
@bentleykfrog said:
Thats cool then, I don't really see an issue passing @@tool_filepath as an argument to lins_setup(). With default values and your script in the $LOAD_PATH's, lins_setup('my_scripts_files') won't require @@tool_filepath.
You're missing the point of the
@@tool_filepath
variable.
In the simple default mode, YOU set thescript_folder
argument oflins_setup()
to have a default value so the method can be called with no arguments, like so:
def lins_setup( instructor_folder=false, temp_folder=false, script_folder=File.dirname(@@tool_filepath) )
I'm still trying to wrap my head around this. In the code's current state, using the default mode, the script_folder argument just specifies a relative path from any of the
$LOAD_PATH
's to the 'instructor' folder's parent. So absolute paths from different volumes won't work with this, unfortunately. The default mode assumes that you'll have access to the$LOAD_PATH
's, or use a script that adds accessible folder to the$LOAD_PATH
. Some plugin loader's don't do this@dan rathbun said:
@bentleykfrog said:
You'll just need it [
@@tool_filepath
] for requiring the mixin possibly?No.. the plugin script's path has nothing to do with what path the user decides to use for their mixin library. The mixin lbrary folder should be in a subfolder of one of the
$LOAD_PATH
members, so Ruby will have no problem finding it.Ahh, I see. In my case, using Alex's Plugin Loader, when a new script is loaded
$LOAD_PATH
's aren't updated to show the file's folder location. So I'm thinking of instances where a Sketchup user doesn't have access to the$LOAD_PATH
's but does have a plugin loader script installed. So in this case, you would need to bundle upload_instructor.rb
with your plugin's files. Then, we need to pass an absolute path torequire()
representing the location ofload_instructor.rb
. I was thinking that you would useFile.dirname( @@tool_filepath
for this, or in my Advanced Use example for .rb files I useFile.dirname( __FILE__ )
.@dan rathbun said:
@bentleykfrog said:
With custom values you'll need to pass absolute values as the second and third arguments so you'll need to use
File.join( @@tool_filepath, 'my_scripts_files/instructor-boogaloo')
, so that's quite easy to do, and I've documented it under Advanced Use.Actually as I said (above,) it's:
File.dirname(@@tool_filepath)
as@@tool_filepath
will be a relative path that contains the tool script filename on the end.You're right, I forgot about that
-
@bentleykfrog said:
I'm still trying to wrap my head around this. In the code's current state, using the default mode, the
script_folder
argument just specifies a relative path from any of the$LOAD_PATH
's to the 'instructor' folder's parent.Exactly as it should be, and what
File.dirname(@@tool_filepath)
would do, which is why I suggest it as the default value for yourscript_folder
argument.@bentleykfrog said:
So absolute paths from different volumes won't work with this, unfortunately. The default mode assumes that you'll have access to the
$LOAD_PATH
's, or use a script that adds accessible folder to the$LOAD_PATH
. Some plugin loader's don't do thisThey are not supposed to if they are not needed.
Kernel.require()
can use absolute paths, but it's better, memory wise, if you will be loading multiple scripts from a custom plugins folder (or library folder, using the standard Ruby library folders as an example,) to push that path onto$LOAD_PATH
, so that when scripts are loaded, only the relative part, gets pushed into the$"
(aka$LOADED_FEATURES
) array. Pay no mind to the erroroneous way thatSketchup.require()
pushes absolute paths onto$"
. It's a bug.. and I have entered a report on this.@bentleykfrog said:
Ahh, I see. In my case, using Alex's Plugin Loader, when a new script is loaded
$LOAD_PATH
's aren't updated to show the file's folder location.They are not supposed to. If the script can be found by using one of the members in the
$LOAD_PATH
array, then there is already an appropriate load path in the array.It is not customary to push every specific plugin folder into
$LOAD_PATH
if there is already a path in the array, for one of the plugin folder's ancestor directories. (You would run the risk of confusing therequire()
method, resulting in scripts or library modules getting loaded more than once. That can reset class and module variables causing problems.)@bentleykfrog said:
So I'm thinking of instances where a Sketchup user doesn't have access to the
$LOAD_PATH
's but does have a plugin loader script installed.I don't follow you. The
$LOAD_PATH
array is a Ruby global. It cannot be garbage collected. A user will always have access to it. However, a script might make a mistake and wipe the members out, resulting in an empty$LOAD_PATH
array. There is also the possibility that a user may feel, sometime after startup, that they have loaded all that needs to be loaded from say, the Tools path, and could "pop" that path off the array. But it's more likely they would insert other custom paths at the beginning of the array (if say, they are using a common plugins folder some where else, which can have a drive letter at the beginning.) Therequire()
method does not mind absolute paths on other drives.@bentleykfrog said:
So in this case, you would need to bundle up
load_instructor.rb
with your plugin's files. Then, we need to pass an absolute path torequire()
representing the location ofload_instructor.rb
.Again.. No. You are confounding the issues of finding and loading a library file, with the issues of finding and loading a script and it's help content. Apples and Oranges.
@bentleykfrog said:
I was thinking that you would use
File.dirname( @@tool_filepath )
for this, or in my Advanced Use example for .rb files I useFile.dirname( __FILE__ )
.Oh.. you are refering to the example in Section 3.4
That should not be necessary. Unless the author needs to customize the mixin module for his/her own use. In which case they would need to change the module name, or the methods would overwrite a previously loaded edition from another place (required by another tool plugin.)
But.. this defeats the general idea of a library. I cannot think of any good reason why that example (3.4) would ever be needed, and it just confuses the issue.
ALL users and authors, should just simply follow the instructions in 2.1 and 2.2, and they will then never have any problems requiring or including the load instructor mixin module.
So really,.. remove the example in Section 3.4 and consider the issue of finding and loading 'mixin/instructor.rb' done. It's not a problem.
-
Just a quick reply before I have to run some errands.
@bentleykfrog said:
Ahh, I see. In my case, using Alex's Plugin Loader, when a new script is loaded
$LOAD_PATH
's aren't updated to show the file's folder location.I should have elaborated on what I meant by
$LOAD_PATH
. In this case, I meant the folders that are represented in the$LOAD_PATH
array. As a case example, take my University. There's only read access granted to anything below C:/Program Files & C:/Program Files (x86). They don't provide a script that will load some read/writeable directories to the$LOAD_PATH
array. I'm asserting that if there was a similar setup, but had Alex's Plugin Loader underneath one of the$LOAD_PATH
's in C:/Program Files & C:/Program Files (x86), you would need to bundle load_instructor.rb with your script, and provide an absolute path to it using require(). This is probably quite rare though, but its one of the reasons I provide the Advanced Use of the Load Instructor.got to go, will respond to your post in more detail when I get some more free time.
-niall
-
@bentleykfrog said:
As a case example, take my University. There's only read access granted to anything below C:/Program Files & C:/Program Files (x86).
OK I now understand where your coming from. Yes this is one of the issues that caused this topic to begin with.
@bentleykfrog said:
They don't provide a script that will load some read/writeable directories to the
$LOAD_PATH
array.They should! Someone needs to convince them to put a simple script that pushes the user's home directory + "/Plugins" onto the
$LOAD_PATH
array. It's a one-liner.SO am I correct in that each time you run Sketchup at University, you must open the Console and type that one-liner manually? Or perhaps:
require_all( "ENV['HOMEPATH']/Plugins" )
which will push that path onto$LOAD_PATH
for you?@bentleykfrog said:
I'm asserting that if there was a similar setup, but had Alex's Plugin Loader underneath one of the
$LOAD_PATH
's in C:/Program Files & C:/Program Files (x86), you would need to bundle load_instructor.rb with your script, and provide an absolute path to it usingrequire()
.Bundle perhaps... but even in a specific user's homepath, the library files do not belong beneath any of the script subfolders.
The mixin needs to still be installed into a "mixin" folder, or a "lib/mixin" folder.
Otherwise what is going happen is users will wind up with more that one copy of your mixin script (perhaps of differing versions,) installed in multiple places. They will insist they have the latest version, but an older version below some plugin's subfolder wil be loading after the newest version, overwriting methods.
This will be a headache for you as a library author, because they will come whining to you (in the forums.)
Save yourself the trouble... always insist it be only installed once, in a mixin library folder only. (So again Section 3.4 is a bad idea.)Also.. bundling libraries with plugins, generically, is bound to lead to revisioning conflicts. The plugin version is likely to remain stable (once all the kinks are ironed out,) but the libraries will continue to be updated.
It is better to have your users follow a link and download a library file (of the latest version,) from the library download page, and install it separately. (I would never allow any plugin author to bundle any of my library files. And I'm sure ThomThom, TIG and other gurus around here wouldn't either. Users need to go to the source and always get the latest library version.)@bentleykfrog said:
This is probably quite rare though, but its one of the reasons I provide the Advanced Use of the Load Instructor.
Well, the issue of having shared scripts, down in the Program Application path, was a stupid one. And I'm sure it looks really dumb to the guy who originally made the decision, now that he has many more years of experience 'under his belt.'
It is not that rare in a network environment, (both Alex and TIG have this problem at their University.)
It will become more and more of a problem, as XP support is withdrawn, and more machines are migrated to the "linux-like" Windows 6+.
The weird thing is that the Mac edition automatically checks the user library path, but Google did not do it on the PC. (Perhaps because %APPDATA% is by default a hidden / system path.) The answer is simple.. and I've set up my machine this way this past week.
I have created user folders under "My Documents/Google Sketchup" and beneath that a "Common", "Sketchup 6", "Sketchup 7" and "Sketchup 8" folders.
Your mixin library script has the honor of being the first to be installed into my "Common/Plugins/Mixin" folder. (Now the laborious task of copying all those files from the Program App paths to my new Folders.) -
Here's a version cleaned up and made into a Mixin module.
Added Class Variables declarations:
# CLASS VARIABLES @@relative_instructor_path = '' @@temp_instructor_folder = false @@tool_filepath = '' # must be redefined in the Tool class for rbs files
Added method definitions:
public def relative_instructor_path return @@relative_instructor_path end def temp_instructor_folder return @@temp_instructor_folder end private # initialize() # # Likely to be redefined in the Tool class definition. # We define it here just in case the Tool author does not. # def initialize help_setup() end # help_setup( instructor_folder=false, temp_folder=false ) # # Sets up and inits some instance variables used for # the tool's Instructor help system. # # ** Must be called by the Tool's initialize() method. ** # def help_setup(instructor_folder=false, temp_folder=false) # if @@tool_filepath.empty? if __FILE__ != '(eval)' && File.exist?(File.expand_path(__FILE__)) @@tool_filepath = __FILE__ else raise(ScriptError,"#{self.class.name}; @@tool_filepath class variable error. @@tool_filepath must be declared for rbs files!") end else unless File.exist?(File.expand_path(@@tool_filepath)) raise(ScriptError,"#{self.class.name}; @@tool_filepath class variable error. File '#{@@tool_filepath}' does not exist!") end end # if instructor_folder @instructor_folder = instructor_folder else @instructor_folder = getDefaultInstructorFolder() end if temp_folder @temp_folder = temp_folder elsif RUBY_PLATFORM =~ /darwin/i # on a Mac @temp_folder = @instructor_folder else @temp_folder = getDefaultTempFolder() end # @@relative_instructor_path = returnRelativePath() # end #def help_setup()
and:
# getInstructorContentDirectory() # # Callback for tool to give Sketchup the relative path # to the tool's help directory. # def getInstructorContentDirectory unless @@temp_instructor_folder # copy help files copyInstructor() @@temp_instructor_folder = returnTempInstructorFolderName() end return @@relative_instructor_path end #def
I did not make any changes to the "engine" methods, except:
getDefaultInstructorFolder()
changed all__FILE__
to@@tool_filepath
returnTempInstructorFolderName()
inserted a 'short circuit' as the first line, for Macs
return @temp_folder if @temp_folder == @instructor_folder
File Removed - Get the latest version in Niall's [ code ] topic:
[ code ] Load InstructorI also wonder if there should be an AppObserver instance that deletes the temp/instructor file at the end of the session ??
Advertisement