Batch create materials from directory of files

Hello, World!

This is my first attempt at Ruby for SketchUp. I have extensive experience creating similar tools in Rhino and 3dsMax and decent experience with SketchUp. Just zero knowledge of Ruby.

I would like to loop through a folder, pulling out each file with _d suffix and create a material from each.
This bit worked once but running it again loops forever. Do I need to check if a material already exists before creating a new one with the same name?

path = 'D:/SketchupMaterial/*_d.png'

model = Sketchup.active_model
materials = model.materials

Dir.glob(path) do  |file|
	bName = File.basename(file, "_d.png")

	material = materials.add(bName)
	material.texture = file 

#	puts "working on: #{bName}"

Thank you in advance.

Yes, otherwise SketchUp will append “#n” where n is an integer which creates a unique name.

From the docs

Add a new Material. When called with no arguments, this will generate a new unique name for the new Material. If a name is given, it will check to see if there is already a material with that name. If there is already a material with the given name, then a new unique name is generated using the given name as a base.

So insert a conditional bailout statement inside the loop, just before the call to #add

next if materials[bName]

In Ruby, there is a convention that getters return nil when a property is unset or a member of a collection is not found, and then only false and nil evaluate conditionally as a falsity. So everything other object, true, an empty String, empty arrays etc all evaluate as a truth because they are objects and are not either false or nil.

1 Like

Thank you for the reply. It helps me to understand how to break a loop easily enough. That boolean evaluation is much like python. And I’ll familiarize myself more with the docs.

Having made this change, I see the same behavior in SketchUp and now suspect that the problem may be something else.

When it appears to be looping continuously, what I see in the material tray is each material thumbnail appear until the last one. Then they all are removed and the process starts again. Almost like it is refreshing the thumbnails over and over.

I can close the palette before running the snippet and all is fine.

I am executing this code snippet from the Ruby Console. Is there a better way I should run this?

Another issue may be the high resolution of the images. They are all 4k to 6k images from Arroway.

next only returns to the top of the loop and starts the next iteration (which may be subject to a conditional expression if within an until or while loop.)

To break out of a loop … use the break statement (optional parts in brackets.)

break [ expression to return ] [ conditional modifier expression ]

Oy! That is good way to slow SketchUp to a crawl.

There is an option for large textures.
Window > Preferences > OpenGL > Use maximum texture size

Well this depends. If these materials are something every one of your models will need, then usually is is better to add them to an empty model, and save that as a template. Then use this template when staring new models. It’s also handy to set up some standard scene pages (views) and styles for those scenes, to help in modeling and / or output.
(For example my template has a specific “Thumbnail” scene with it’s style, and a Print scene with it’s own black and white style, in addition to a “Work” scene that is reverse video, black background scene,m that I use for just modeling in monochrome mode. These 3 template scenes have their “Include in animation” flag unchecked.)

But if you really want to run a snippet like this “on demand” you could get Aerilius’ Toolbar Editor plugin and create a button and use your snippet for the button’s code.

If you’d rather do it simpler, with a Extensions menu item then you can do something as simple as …

module JonahHawk
  module AddMyMaterials

    extend self

    def add_materials
      path = 'D:/SketchupMaterial/*_d.png'

      model = Sketchup.active_model
      materials = model.materials
      before = model.materials.size

      puts "\nAdding Jonah Hawk Materials:"

      Dir.glob(path) do |file|
        bName = File.basename(file, "_d.png")
        next if materials[bName]
        puts "  Adding material: #{bName}"
        material = materials.add(bName)
        material.texture = file 

      added = model.materials.size - before
      msg = "Materials added: #{added}"
      puts msg
      return added

    # Note: Undefined instance references evaluate as nil
    if !@loaded
      # Only add menu item once if reloading during development'Extensions').add_item("Add Jonah's Materials") {
      @loaded = true

  end # module AddMyMaterials
end # module JonahHawk

Drop the file in your user “Plugins” folder, or package it up as a valid extension.
See for the explanation of a extension registrar file.
In the case of an extension, the extension code file(s) go in a subfolder of their own.

An alternative to a “proper” extension can be a subfolder of snippets (like above) which has a loader file that has a loop similar to your Dir.glob loop, but globs rb files and passes them to the global require method.

Notice, that in Ruby, it is not the name of the file that creates module or class names, as with Python. In Ruby we are free to create whatever and however many, different modules and or classes within the same file. Some coders do, as they prefer to edit all the code in one file. I myself do not. I will usually at least have each nested class in it’s own file.

Also, differing from Python, Ruby module and class definitions can span multiple files. Ruby’s module and class scripting blocks are actually read by the interpreter as “open for edit and create if needed”. (ie, behind the scenes, the interpreter takes the text of the block and passes it either to a constructor method, or an object evaluation method.)
This is how extension authors publish code in multiple files. Each one of an authors files for ALL of their extension must be inside their top level namespace module. Each author should then separate each of their plugins from one another using a submodule.

But it is convention (because SketchUp’s “Plugins” folder is a shared file space,) to at least qualify the name of the extension registrar script file using your top level author/company namespace module name, followed by an underscore, and then the plugin name (which is likely to be the nested plugin module name.)
So taking the posted example, the registrar script would be: "JonahHawk_AddMyMaterials.rb".
This registrar script would name the loader file (the one I posted above) which should be in a subfolder of the same name as the registrar script, ie subfolder: "JonahHawk_AddMyMaterials".
The actually name inside your plugin subfolders can be whatever convention you desire. I usually always have a file named “loader.rb”. Others have the loader named “main.rb” etc.

Lastly, you may see use of global variables in examples. This is unacceptable for any published extensions, and dangerous in any case as clashes could occur.

See the resource lists I created …

1 Like

Could this code be modified to REPLACE any texture in the model with a filename match? What if I want it to do that instead?

Say I have a folder of the exact same materials as JPG files but maybe altered in some new way, and they all have the exact same name as the materials in my model and they each match in size 1-to-1. How would you modify the above code to replace each and every one of those textures in the model with the JPGs in the folder specified?

That sounds like a relatively simple homework assignment for you.

(Ie, I am not interested in relearning what was talked about above after 2 years and I have other things on my todo list right now.)

I would give a hint to look at the Material#texture= method that assigns or reassigns a material object’s texture.