Dynamically changing color of entity created with ruby is very slow

I create a piston-like object with the ruby API (i.e. two cylinders that slide inside of each other).
I do this by first creating a Circle (circle = Entities::add_circle), then adding a face to it the resulting edges (face = Entities::add_face(circle)) and the pushpulling it (face.pushpull(length)).
This will create a simple cylinder, which I want to color in dynamically (i.e. around once per frame).
Unfortunately, this slows the simulation, I have running, down a lot (about 10 fps drop per cylinder). Inside the simulation there are a few much more complex bottle structures, which work much quicker.
Investigating the problem, I found out, that the bottle structures are loaded via an .skp file and calling entity.definition.entities reveals, that the only have two entities that are being colored in. On the other hand, the cylinders I create with the Ruby API have almost 100 entities, which all have to be colored. I assume that this is where the performance is going away.
Is there a way to “squash” all the entities of my object into one contiguous face that can be colored? Might it bring performance improvements, if I would create an .skp file for the cylinders?

EDIT:
This is how I create the cylinders

def create_cylinder(name, diameter)
  definition = Sketchup.active_model.definitions.add(name)
  circle_edgearray = definition.entities.add_circle(@center,
                                                    @up_vector,
                                                    diameter, 12)
  face = definition.entities.add_face(circle_edgearray)
  face.pushpull(-2 * @length / 3, false)
  definition
end

and then, in order to render it correctly, I create a component instance

def create_entity
  return @entity if @entity
  translation = Geom::Transformation.translation(@center)
  rotation_angle = Geometry.rotation_angle_between(Geometry::Z_AXIS,
                                                   @vector)
  rotation_axis = Geometry.perpendicular_rotation_axis(Geometry::Z_AXIS,
                                                       @vector)
  rotation = Geom::Transformation.rotation(@center,
                                           rotation_axis,
                                           rotation_angle)

  transformation = rotation * translation
  entity = Sketchup.active_model.active_entities.add_instance(@definition,
                                                              transformation).make_unique
  entity
end

changing the color is done like this:

def change_color(color)
   @entity.definition.entities.each do |ent|
    if ent.material != color
      ent.material = color
      ent.material.alpha = 1.0
    end
  end
end

A skp file of the bottle model is attached.

5-small-single-bottle(23cm).skp (26.7 KB)

an example .skp and example code, would make answering easier…

I find it fastest to assign a material once and then switch the materials color or texture…

john

The cylinders need to be grouped or made into a component (same thing as a group is a special component instance.) Then you just paint the cylinder group, and the paint displays on any child faces whose material is set to nil. This is called the “default material”, as child face material display is “defaulting” to the parent’s material setting.

Painting individual faces or edges will override the parent’s material setting.

Updated the question. I feel like I am changing the color instead of assigning a new material (probably don’t have to reset the alpha, to be fair, but that shouldn’t matter too much).

Okay, so I found a way to do it. I am now lazy-initializing the material of the subentities to that of the parent entity and then change the color of this one material. This seems to work. Is this how you meant it?

Thanks!

No. Your just making SketchUp do extra work.

As I said you leave the definition’s child faces set to nil, then change the component instance’s material:

def change_color(color)
  @entity.material = color
  @entity.material.alpha = 1.0
end

But if you are using textured materials and you wish to preserve UV coordinates, then painting individual faces will make sense.


Also the following …

 if ent.material != color

… means nothing as it will always evaluate as true (ie, the same as saying “if true”.)
This is because the Sketchup::Material has no “!=” instance method defined, so it just inherits Object#!=(), which checks object id.
Also, this conditional statement implies that two objects of different classes are being compared, which usually results in a Type Mismatch error (Ruby’s TypeError exception being raised.)
[The reason you see no error is because Object#!= checks object identity not class.]

Also, for code in the forum, see:

[How to] Colourize code on the forum? (pinned topic)

I would encourage you to follow Ruby’s 2 space indent rule, as it will make your code more readable, and maintainable.

def create_entity
  return @entity if @entity
  translation = Geom::Transformation.translation(@center)
  rotation_angle = Geometry.rotation_angle_between(
    Geometry::Z_AXIS,
    @vector
  )
  rotation_axis = Geometry.perpendicular_rotation_axis(
    Geometry::Z_AXIS,
    @vector
  )
  rotation = Geom::Transformation.rotation(
    @center,
    rotation_axis,
    rotation_angle
  )
  transformation = rotation * translation
  entity = Sketchup.active_model.active_entities.add_instance(
    @definition,
    transformation
  ).make_unique
  entity
end

This also gives space for comments after method arguments.

what I was suggesting is adding the color to the model and applying it in the definition…

# add the material to the model
mat = Sketchup.active_model.materials.add('bottle')
mat.color = 'green'
mat.alpha = 0.65

# add it in the definition before the pushpull
face.reverse!
face.material = mat
face.pushpull(distance)
# all the new faces inherit the material

refer to the material in the change method…

def change_bottle_color(mat, color)
  mat.color = color
end

then a test after placing in the model…

colors = Sketchup::Color.names
view   = Sketchup.active_model.active_view

# get the material by name
if Sketchup.active_model.materials['bottle']
  Sketchup.active_model.start_operation('changecolors')
    colors.each do |color|
      change_bottle_color(mat, color)
      view.refresh
      sleep 0.2
    end
  Sketchup.active_model.commit_operation
end

switch_mats

EDIT: code change following @DanRathbun’s advice…

john

2 Likes

Not efficient, just do:

if mat = Sketchup.active_model.materials['bottle']
  # ... operation ...
end

… otherwise this is good example.

1 Like

Okay, so I implemented it the way @DanRathbun suggested it. For some reason, I thought that this simple solution wouldn’t be performant enough, but it works fine now.
The solution by @john_drivenupthewall seemed interesting as well, but I couldn’t really make it work. I need a different material for each bottle and cylinder and storing them by a unique ID during the creation of the cylinder feels unhandy.
I now have 60fps most of the time, though. So I’m fine with that.
Thank you!

1 Like

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.