How to efficiently color a polymesh

I am trying to color each polygon of a Polymesh with a different color based on a specific criteria. It seems that the time required grows very quickly with the number of polygons. For example with around 500 polygons and colors/materials it takes more than 16 seconds and I usually deal with meshes of a few thousands polygons.

My understanding is that for each different color I have to define a new material and then assign this material to the face. This is an extract from the code that I am using at the moment.

# polymesh is a Polymesh defined elsewhere
# number_of_cells is the number of polygons of polymesh

colors = []
start_time = Time.now
number_of_cells.times{colors << [rand(0..255), rand(0..255), rand(0..255)]}
puts "After Random: #{(Time.now-start_time).to_s}"

start_time = Time.now
mod = Sketchup.active_model
ent = mod.entities
mod.start_operation("view_mesh", true)
mesh_view = ent.add_group
mesh_view.layer = mod.layers.add 'color_mesh'
mesh_view.entities.fill_from_mesh(poly_mesh, true, 0)
puts "After Polymesh: #{(Time.now-start_time).to_s}"

start_time = Time.now
mesh_view.entities.grep(Sketchup::Face).zip(colors).each do |f, c|
  f.material = c#mat
  f.back_material = c#mat
end
mod.commit_operation
puts "After Color: #{(Time.now-start_time).to_s}"

Am I doing something wrong? If so, is there a faster way to do this?

Thanks

When looking at the puts, which part takes most of the time with large polygon counts?

Also, did you try replacing the .zip with a simple counter ? To me, it seems like an unnecessary step.

c =0
mesh_view.entities.grep(Sketchup::Face).each do |f|
   f.material = colors[c]#mat
   f.back_material = colors[c]#mat
   c+=1
end

The actual color assignment takes most of the time. The first two operations take a negligible amount of time. To give you an idea, with 234 polygons:

After Random: 0.000213
After Polymesh: 0.007534
After Color: 2.070201

There is no much difference with the counter. Actually, in average, .zip seems a bit faster.

Are you sure about that? It’s the grepping AND color assignment that you’re timing here.
You could time them separately to find out. That way you could also find out if the .zip really makes a difference.

Something like:

start_time = Time.now
ents = mesh_view.entities.grep(Sketchup::Face)
puts "After Grep: #{(Time.now-start_time).to_s}"

c=0
start_time = Time.now
ents.each do |f|
   f.material = colors[c]#mat
   f.back_material = colors[c]#mat
   c+=1
end
puts "After Colors: #{(Time.now-start_time).to_s}"

Fair enough.

This is the output with 505 cells and zip.

After Random: 0.00039
After Polymesh: 0.011746
After Grep: 0.000937
After Color: 19.267925

This is the output with the counter:

After Random: 0.000485
After Polymesh: 0.012937
After Grep: 0.000795
After Color: 19.08803

Seems like to gain some speed you have to declare a unique named material yourself instead of letting SketchUp do that for you.

In the top part of your code add:
mats = mod.materials

In the material assignment loop do:

c=0
start_time = Time.now
ents.each do |f|
name = 'mat' + c.to_s
m = mats.add(name)
m.color = colors[c]   
f.material = m
f.back_material = m
c+=1
end

edit: add a check to see if the material name already exits. If you run this code twice, it’s just as slow as before. My guess is: due to having to check all the time of a material name already exists, add a number, check again etc

edit 2: run code once, for 500 entities is 0.04s Run code again, its 18s

edit 3: fix for code

Thanks.

It does definitely helps up to 500 faces. Below a few experiments. After each test I was careful to undo and remove all the created materials.

Faces	Time
76	0.064855
95	0.092226
179	0.280488
234	0.455392
505	2.455658
716	5.595834
1080	16.096122
1640	123.873587

Once you get to around 2000 it seems to get stuck and SU memory use goes to more than 1GB for a simple model. I wonder whether there is a limit in the number of materials SU can handle.

Latest version of the code:

start_time = Time.now
ci = 0
faces.each do |f|
  name = 'mat' + colors[ci][0].to_s.rjust(3,"0") + colors[ci][1].to_s.rjust(3,"0") + colors[ci][2].to_s.rjust(3,"0")
  if mats[name] == nil
    mat = mats.add(name) 
    mat.color = colors[ci]
  else
    mat = mats[name]
  end
  f.material = mat 
  f.back_material = mat 
  ci += 1
end
mod.commit_operation
puts "Number of faces: #{ci}"
puts "After Color: #{(Time.now-start_time).to_s}"

SketchUp does have its limitations. 2000+ materials in a file is quite something and makes using the materialpanel also a bit hard. Maybe have a look at it in another way?

Instead of using 2000 materials, each with its own color you could also use just one material with a high resolution texture with a color gradient on it. You could then apply random texture mapping coordinates to each face so each face uses a random but very small (small enough not to recognize the gradient) part of the texture. Could be a lot faster because you don’t run into problems with SketchUp building some sort of a materials array.

Never coded uv coordinates in SketchUp though so it could be a nice challenge. I’m curious though what kind of objects you are color painting and why you need 2000+ colors. Could you maybe post a picture to satisfy our curiosity :slight_smile:

Thanks Max.

I was using random colors just to test the code. In the actual plugin I am generating contour plots and the local color depends on the specific value of a field variable.

I am working on a plugin to perform various simulations (wind, radiation, comfort…) on masterplans. I see you are an architect so I assume you understand what kind of analysis.

For example, the first picture shows the results of a wind analysis for a resort visualized in SU.

For this visualisations I already use textures. They are rendered by an external program and then scaled and placed correctly in SketchUp.

This is quite easy when you have one single flat face like that in the picture, but if you have multiple surfaces with multiple orientations (see second picture) it becomes more complicated as I should keep track in the code of every single surface, its orientation and the associated mesh and I was trying to avoid this.

Interesting! Using a projected material is a no-go I guess due to different conditions on the individual facades of a building…?

adding materials does get slower…

 #Gem.install('color-generator')
require 'color-generator'
@generator = ColorGenerator.new saturation: 0.3, lightness: 0.75
@colors = {}

def hash_colors
  color = @generator.create_rgb
  hex = "#%02X%02X%02X" % color
  @colors[hex] = color
end

def add_mats(hex,color)
  this_mat = Sketchup.active_model.materials.add(hex)
  this_mat.color = Sketchup::Color.new(color[0], color[1], color[2], 255)
  this_mat.alpha = 1.0
end 
 
t1 = Time.now
2000.times do break if @colors.length == 200
						hash_colors 
						end
p Time.now - t1
t2 = Time.now
@colors.map{|k,v| add_mats(k,v)}
Time.now - t2
#=> 0.001461 for the color generator
#=> 3.96155  for adding materials

john

1 Like

Rojj,

Very interesting. I used the code shown below to add 3,000 materials to the model, then added them to the faces of a model with over 3,000 faces. Total time was about 3 seconds, without anything ‘interfering’. If I had the native ‘Materials’ window open, it took 544 seconds. If you run it a second time, it shows that testing for the existence of 3,000 materials also takes a little time…

Therein maybe an issue. I can’t test anything past SU 2014 for the time being, but many of the native SketchUp windows do not respect operations. The main surface does, so within an operation that adds materials and changes the faces’ materials, the model won’t change, but the native ‘Materials’ window will refresh every time a material is added to the model. Obviously, this should change. This issue is also part of the reason for the ‘Transaction’ callbacks being added to SU.

Also, you may have plug-ins installed that don’t correctly work with this kind of situation and a MaterialsObserver.

My plug-in has a Materials web dialog, and it actually works much better in this situation than the native window. Then again, I’ve got a timer in my MaterialsObserver to wait until events quit firing to update the UI. Screen grab below.

am = Sketchup.active_model
am_mats = am.materials

FMT_C_M = "mat_%03d_%03d_%03d"
len  = 3000
mats = []
mat  = nil
name = ''
c    = []
ctr  = 0

am.start_operation("test", true)
t_start = Time.now

# create colors, add to materials
0.upto(len) { |i|
  c = [ 128, (i/256).to_i, i % 256 ]
  name = FMT_C_M % c
  mats << ( am_mats[name] || (
    mat = am_mats.add(name)
    mat.color = c
    mat ) )
}
t_mid = Time.now

# get faces and change material
faces = am.entities.grep(Sketchup::Face)
faces.each { |f|
  f.material = mats[ctr]
  f.back_material = mats[ctr]
  ctr += 1
  break if (ctr > len)
}
am.commit_operation
t1 = t_mid    - t_start
t2 = Time.now - t_mid
puts "Faces & Materials Count = #{len}\n" \
     "         Materials Time = %5.3f\n"  \
     "             Faces Time = %5.3f\n"  \
     "-------------------------------" % [t1, t2]

FWIW,

Greg

1 Like

That’s right.

Also at the moment I am using one single mesh to represent all the points were the calculation is performed . I am planning to extend the code to avoid this issue, but it is not a priority at the moment.

Thanks Greg.

I have tried to run your code with my mesh of 1080 faces. It hangs and at least after 15 minutes it has not finished yet. I have also looked for MaterialsObserver and removed the only plugin that was defining one. No difference.

I have rerun my plugin and visualised the counter in the status bar. You can clearly see that around 1300 materials the speed drops suddenly and it takes almost 2 seconds to add a new color. I would have expected a more gradual slow down actually.

Rojj,

Odd. I wish I knew what was so different with our models / hardware, etc. When your system hangs, and mine completes in 30 seconds, something is very odd.

My model has a little over 6k faces, and no materials.

I ran it with the loop counter at 6k, and got the following –

Faces & Materials Count = 6000
         Materials Time = 11.326
             Faces Time =  0.124
--------------------------------

I verified that there were 6k materials, then I changed the red value to 64, and ran it again. Got the following –

Faces & Materials Count = 6000
         Materials Time = 29.671
             Faces Time =  0.140
--------------------------------

Verified that the model has 12k materials. Then, I ran it again. Got the following –

Faces & Materials Count = 6000
         Materials Time = 14.742
             Faces Time =  0.078
--------------------------------

Of course, materials aren’t added this time, but it took 14.7 seconds to determine that the 6k materials already existed.

I’ve attached my model in case you want to test with it (1 MB). Regardless, it seems like your application requires ‘painting’ in such a way that using discrete materials for each color may not be possible.

Greg

Cubes Test 6k_14.skp (1.1 MB)

@MSP_Greg your file runs much slower on my mac…

Faces & Materials Count = 3000
Materials Time = 475.458
Faces Time = 0.256

and if I then try to open material window SU hangs…

john

John,

Thanks for checking. The 2nd post results I listed were with the Materials window closed.

I did some more Windows tests (listed below). I didn’t run ‘Large Thumbnails’, but ‘Small’ and ‘Medium’ had little effect on the timing, it’s only when I selected ‘List View’ that the time jumped to the level that your test was.

Odd question, do you know if you had previously opened the Materials Window in the session you did the testing in? Also, what the view setting was?

As an aside, the Web Dialog I showed a screen grab of is the Materials WD in my plug-in. I just opened it with 6k materials, and it loaded. I’ll have to check my code, but I’m really sure I did not do any length checks on the execute_script string that loads the html table. Hence, on Windows, 6k names and color info went in one statement. I guess if there’s a length limit, that didn’t hit it.

Thanks,

Greg

Material Window Closed
Faces & Materials Count = 3000
Materials Time = 4.649
Faces Time = 0.047

Material Window Open - set to Small Thumbnails
Faces & Materials Count = 3000
Materials Time = 6.365
Faces Time = 0.312

Material Window Open - set to Medium Thumbnails
Faces & Materials Count = 3000
Materials Time = 6.458
Faces Time = 0.312

Material Window Open - set to List View
Faces & Materials Count = 3000
Materials Time = 559.321
Faces Time = 0.328

Has anyone tested on OSX?

I ran into similar issue a couple of years ago when I created a script to color each face based on it’s normal. On larger meshes I noticed it took an incredible amount of time. And any attempt to interact with the material browser failed.

But even simply creating material seemed to trigger a rebuild of the material browser’s list. I haven’t actually dug into the code to see what goes on though. But there is an issue filed. I’ll bump it with reference to this issue.

I only ever use mac…

@Rojj uses both, but I’m not sure which he was testing against…

many ‘bulk’ actions appear to slow down a mac,i.e. adding Materials, Groups, Layers, Geometry…

john

I am currently using OSX and SU15Pro.

@MSP_Greg I am currently testing your model and your code. 15 minutes and it has not finished yet. SketchUp memory use is up to 2.47GB and rising…

EDIT: Final results

Faces & Materials Count = 3000
Materials Time = 1078.697
Faces Time = 0.037

Final memory use: 3GB