Creation of Materials is slow?

I’ve been working on an extension that creates several named Materials from standard named Colors listed in the Ruby API documents. The gist of the code is:

color_index = 0
Sketchup.active_model.start_operation('Color Room Faces', true)
begin
  @rooms.each_with_index do |room, index|
    material_name = "Room #{index + 1}"
    material = Sketchup.active_model.materials[material_name]
    if material.nil?
      color_name = ROOM_COLORS[color_index]
      color = Sketchup::Color.new(color_name)
      material = Sketchup.active_model.materials.add(material_name)
      material.color = color
    end
    room.color_faces(material)
    color_index += 1
  end
  Sketchup.active_model.commit_operation
rescue Exception => e
  # The main point of this rescue is to abort the operation.
  # Otherwise the model may be left in a partially colored
  # state.
  Sketchup.active_model.abort_operation
  # no more we can do - reraise the Exception so default
  # handler can report it.
  raise
end

@rooms is an Array of Room objects generated elsewhere in the code. I have checked that is it as intended
ROOM_COLORS is an Array of String color names from the Ruby API document page for Color.
The rescue is there as a precaution. It has never tripped yet.

The first time this snippet is run, it is very slow (as much as 4 or 5 seconds), which seems crazy given that there are typically only about 10 Materials involved. Thereafter, when the named Materials already exist in model, it is perceptibly instantaneous, which to my thinking proves that the creation and adding of the Materials is the choke point.

I could find nothing about this on the GitHub Ruby API Issues tracker. Is there a reason why the first time is so slow? Is there a known workaound?

I don’t know if it is faster, but you can assign a color to faces directly using Sketchup::Color name strings.

It is not necessary to be checking the materials collection first, the C-side does it for you and if there is already a material of that color then it is used rather than creating a duplicate.

For speed sake, I always create references before (outside) the loop. (You snippet is repeatedly getting the model reference and then the materials collection twice in each loop.)

I did that first, and yes, it was much faster, But, a user requirement was to assign specific names to the Materials visible in the Materials Window, and so far as I can tell that is impossible with Colors.

The main limitation of that is knowing how many to create before starting the loop, but I think that is manageable. I’ll try it and see what happens.

Good catch about repeatedly getting the model and materials collection. I’ll give that a try and see if it improves things.

I moved the setting of model and materials variables outside the operation and now it runs essentially instantly again. So, at least for the moment, problem solved.

But I wonder why that would make such a big difference, as there are only 5-10 Rooms in the Array. It isn’t a matter of accessing them millions or even hundreds of times in the loop. Some peculiar interaction with model#operation? I was running it with SketchUp launched in debug mode. Could that cause such an effect, even when the debugger is not attached?

It might be a MacOS thing where there could be multiple models.

On Windows, when I repeatedly do:

id = Sketchup.active_model.materials.__id__

… I get the same integer ID each time.

However, it may be more about the block closure where it holds a snapshot of the objects in the local calling scope. Maybe there is a difference in Ruby 3.x from past Ruby versions?

I don’t know as I never use the debugger. You could try it without the debug parameter and compare.