Open a Local collection of component definitions in ruby

I am working on an extension which will come with a set of related component definitions.

In the standard SU UI the component tray has a function to “Open… a local collection” where you select a folder containing the component skp files and this creates a separate collection with that folder name.

But in the ruby API, I see only methods to load one component at a time to the general pile of component definitions… Does the API not know about collections?
If not, this kind of extension isn’t really feasible.

Barry,

My understanding is that the ‘collection’ in the component browser is only a registry setting pointing to the folder that contains the collection of components.

I simply edited the registry because using SketchUp.read_default(‘UserLibrariesComponentBrowser1’, ‘Name1’), results in an error.

In contrast to that, when you load a component using ruby, it loads the component into the model. Same as clicking the component in the in the collection.

To see a list of components in the model, click the ‘In Model’ (House) button.

I obviously can’t ask extension customers to edit their registries.
And I need to load my definitions into a collection so they are not mixed in with all the other component definitions users may have loaded. Collections are an indispensible tool of the SU UI to give some organization to larges numbers of component definitions.

If the API doesn’t yet support them, such extensions are not really useable.

I’m not aware of a way to load a collection into the tray using Ruby code.

You could create your own WebDialog with a list of your components, loading them as the user selects them.

It might be possible to edit the registry using Win32 api calls, but don’t ask me how to do it.

I need to support both Win and Mac.

I’ll have to wait until they add an API method to load a local collection. There are a large number of potential discipline specific extensions that could use this. But each set of components must be a separate collection.

Otherwise I’d have to instruct all users on how to load a collection from a subfolder under ./Plugins/Myextension/components

Not too feasible.

On the Windows editions, this is basically true. But are likely kept in defaults (plist) files on the Mac editions.

This method call always evaluates the string read from the registry. (And there is no way to even trap the error message! It is a long-standing implementation, and not likely to change.) So, the only values that can be read using this method, are values written by the Sketchup::write_default method.

And it may not matter anyway. Many of the registry values of the SketchUp Windows editions are only read at application startup. And then making changes whilst SketchUp is running is problematic (and dangerous,) because SketchUp will overwrite the registry values when it shuts down.

So using the image from the RegEdit above, say that you added a new collection and bumped up the count to 10. When SketchUp shuts down it would overwrite the count attribute you bumped and set it back to 9. Which basically would cause your new collection attributes to be ignored upon the following restart.

I am unsure if calling a UI.refresh_inspectors() will cause SketchUp to reread the collection registry attributes. (I doubt it.)

FYI, I have formerly filed API requests for registration methods for all collection folder types. (ie, component, material, style, and templates.)

What I envisioned was class methods like:

Sketchup::DefinitionList::register_collection( "Collection Name", path_to_my_component_folder )

(… but had actually suggested then, that they be class methods of the Sketchup::ExtensionsManager class.)

Correct there is no API exposure to collection objects of component folders, nor templates, nor materials, nor styles, etc.

In the aforementioned requests I made note that:

I’ve been thinking that something more robust is needed. Code needs to check if a path is registered, and if not add it. Or if it is, perhaps the path needs to be changed.
~
To that end, I think the collections functionality can be added as Sketchup::OptionsProvider collections, controlled by a new singleton Sketchup::OptionsManager instance attached to the application (accessed via a Sketchup::options module method.)

So (example) code could get a list of template directory paths via:

paths = Sketchup::options["TemplateProvider"].values

So in reality, when (and if) implemented the registration methods might become instance methods of 4 specific options providers, like:

comp_opts = Sketchup::options["ComponentsProvider"]
comp_opts.register_collection( "Collection Name", path_to_my_component_folder )

… who knows ? Collections might not be accessed via an ::options() method, perhaps it is named ::collections() instead ?

(I had been promoting ::options() as I have also requested exposure of other application level user options, such as “Click Style” and others found in the Preferences dialog.)

But I don’t care if options and collections managers are accessed via separate method calls and managers.

1 Like

A very useful feature request.

It’s straightforward to get a list of all SKP files in a folder.
You can easily get the path to any folder [from FILE] in your ruby code.
Then filter the folders contents for SKPs and assemble an array of ‘names’…
So you end up with say
compo_names = ['A', 'B', 'C', ...]
Then you iterate that array:

compo_names.each{|e|
  next if model.definitions[e] ### already loaded
  defn = model.definitions.load(File.join(path_to_folder, e+".skp"))
  defn.name = e
}

The collection is then loaded.

TIG: you didn’t quite get the point.
I know how to do what you say.
The problem is they are added to the model (as unused definitions) and combined in the list with all many other definitions the model may contain.
I need a way to organize related definitions in separate “panes” of the Components tray.
That’s what the current user command “load local collection” does.
Simplistic example: If I’m designing a house I want windows in one pane, doors in another, appliances in another, plumbing fixtures… Having them all in one alpha list is unusable.

Once components are added into the model they go into the model-tab.
Before that you can have a zillion collections and their contents, but until you select one of them it is not entered into the model’s definitions list.
Also once in that list it is never in a sub-collection - just the model-collection…

So if you want to make a collection available you need to tell users to add it to their collections list.
Otherwise, add the collection’s contents to the model as I describe, sidestepping the need for that - as they are pre-loaded into the model by your code…

it’s trivial on a mac to add your plugin subfolders to the plist using a system call…

but you need to restart SU to see them in the Component Browser…

that can also be achieved via ruby…

john

I a similar way you could use the newer Ruby methods [requite 'registry.rb'] to change the PC’s Registry entries for:
HKEY_CURRENT_USER\SOFTWARE\SketchUp\SketchUp 2016\File Locations
and change the
ComponentBrowser2
entry to include the desired path…
Remembering to include \ separators etc.
Typically it’d default to
C:\Program Files\SketchUp\SketchUp 2016\Components\

But of course there might be permission issues etc, so a one off manual addition of the folder path into the user’s set up would seem trivial by comparison…

The correct solution is for a function to be added to the API which does what the user command “Open local Collection” does as per Dan Rathbun above. The components in such collections are NOT added to the model until the user adds them, but appear in a separate folder withing the Components tray.

I write complex applications which need to work for MAC or WIN when the user downloads the extension from the extension warehouse and Sketchup tells her that “the extension is ready for use”.

Its not practical not should it be necessary to have some supplemental set of instructions to the user to open each collection manually by traversing a 12 level folder tree like:

but which will vary by user and OS. This would lead to a support nightmare.

1 Like

Reviving this thread because at the time (Aug of 2016) the 2017 release was still in development.

The following November SketchUp 2017 was released and now has a user appdata "Components" directory folder. So since v2017 user component collections are more accessible.

comp_path = File.join(
  File.dirname(Sketchup.find_support_file("Plugins")), "Components"
)
# >> C:/Users/Dan/AppData/Roaming/SketchUp/SketchUp 2017/SketchUp/Components
File.exist?(comp_path)
# >> true

When your extension loads, you can check for the existence of your plugin’s named sub-folder in the comp_path directory, and if false then copy your component folders over to a plugin specific comp_path subfolder.

plugin_component_lib = File.join(comp_path,my_plugin_dirname)
unless Kernel.test( ?d, plugin_component_lib )
  Dir::mkdir( plugin_component_lib )
  copy_component_folders( plugin_component_lib )
end

You may wish to load FileUtils from the Ruby library to help with the copying tasks …
http://ruby-doc.org/stdlib-2.2.4/libdoc/fileutils/rdoc/index.html

After doing this, the Components browser may need to be refreshed either via UI.refresh_inspectors() or having the user switch folders and back to the root again. (Worse case would be SketchUp needs restarting, but that is usually the best action after installing major extensions anyway.)

Quick and dirty example …

def copy_plugin_components(
  plugin_distrib_dirname = "Components",
  plugin_library_dirname = File.basename(__dir__)
)
  user_components_path = File.join(
    File.dirname(Sketchup.find_support_file("Plugins")),
    "Components"
  )
  unless Kernel.test( ?d, user_components_path )
    fail("User Components Directory Not Found")
  end
  require 'fileutils' unless defined?(FileUtils)

  src  = File.join( __dir__, plugin_distrib_dirname )
  dest = File.join( user_components_path, plugin_library_dirname )
  Dir::mkdir(dest) unless Kernel.test(?d,dest)
  Dir::entries(dest) # force Windows to refresh environment

  begin
    FileUtils.cp_r(src+'/.',dest)
  rescue => err
    puts "Error copying extension components from:\n  #{src}\nto:\n  #{dest}"
    puts err.inspect
  end

end

Thanks,
I’m in a crunch before a 10 day trip, so I will take a look when I get back

1 Like