I’m new to Ruby and my English is bad.
I have the following code:
model = Sketchup.active_model
definition=model.definitions
selection = model.selection
entities = model.active_entities
mat="Green"
valor="portas"
dcs=[]
selection.each{|e| e.typename=="ComponentInstance"
dcs << e if e.get_attribute("dynamic_attributes","materialoptions")== valor
}
dcs.each{|m|m.material=mat}
It applies a material in the selection that has a dynamic attribute “materialoptions” and “portas”, however I want to apply the material to all nested instances or not that have the attribute and that are within the selection.
How do I get it.
Thanks in advance for your help.
Do not use entity.typename == "some string"
It is very slow and deprecated for most use.
Use entities.grep(SomeClass) instead. It is a very fast filter.
Or for a single object, use entity.is_a?(CertainClass)
Component Instances do not “own” entities. Only their definition has an entities collection.
If the instance is not unique, then changing the nested instances will change them in ALL of the instances of that definition.
Try not to create a new string literal object in every loop. Define them before and outside the looping construct.
model = Sketchup.active_model
selection = model.selection
mat = "Green"
valor = "portas" # "doors"
dict = "dynamic_attributes"
key = "materialoptions"
dcs = selection.grep(Sketchup::ComponentInstance).find_all { |dc|
dc.get_attribute(dict,key) == valor
}
defs = dcs.map { |dc| dc.definition }.uniq!
defs.each { |cdef|
cdef.entities.grep(Sketchup::ComponentInstance).each { |inst|
inst.material = mat
}
}
Many of the SketchUp API collection classes have the Ruby core Enumerable module mixed into them.
Hello!
Sorry for the delay in answering DanRathbun.
I was out and only now can get on my pc.
Thanks for answering and for the tips, they will be very useful for me to learn Ruby.
Your code is very elegant, but it returns an error and I can not understand why.
Follows an image.
The method each is an iterator method for collection type objects (arrays, hashes, sets, … etc., …)
In Ruby when a method is called upon an object that does not have such a method, then a NoMethodError exception is raised. The error message tells you that the each method was called upon the singleton object nil (it’s a singleton because it is the one and only instance of the NilClass.)
Ruby has a tradition of returning nil from many method calls that fail, especially search methods.
So it is normal to check after such method calls that you have a valid object (or array of returned objects.)
There is only two places where each is called. So you can test by outputting the returns to the console …
dcs = selection.grep(Sketchup::ComponentInstance).find_all { |dc|
dc.get_attribute(dict,key) == valor
}
puts dcs.inspect # <---<<< corrected by Steve below ;)
… see what you get ?
If all this code was within a method definition, it is normal to add a “bailout” statement …
… and we can also check the DC definition because if the DC instance is using the default valor then the instance’s dictionary will not have the "materialoptions" attribute …
module Tenquin
extend self
def change_mat( key, valor, mat )
model = Sketchup.active_model
selection = model.selection
return "Empty Selection!" if selection.empty?
dict = "dynamic_attributes"
dcs = selection.grep(Sketchup::ComponentInstance).find_all { |dc|
dc.get_attribute(dict,key) == valor ||
dc.definition.get_attribute(dict,key) == valor
}
return "No Dynamic Component Instances found !" if dcs.empty?
puts " Instances found : #{dcs.count}"
defs = dcs.map { |dc| dc.definition }.uniq!
return "Dynamic Component search resulted in nil." if defs.nil?
return "No Dynamic Component Definitions found !" if defs.empty?
puts " Definitions found : #{defs.count}"
defs.each { |cdef|
change = cdef.entities.grep(Sketchup::ComponentInstance)
next if change.nil? || change.empty?
change.each { |inst| inst.material = mat }
}
end # change_mat
end # module Tenquin
I need some time to better understand how things work in ruby.
My knowledge boils down to editing a simple code, pasting in console, and seeing what happens to the model.
return "Dynamic Component search resulted in nil." if dcs.nil?
return "No Dynamic Component Definitions found !" if dcs.empty?
… should have used defs in the conditional expression, instead of dcs, and read as …
return "Dynamic Component search resulted in nil." if defs.nil?
return "No Dynamic Component Definitions found !" if defs.empty?
I have made the correction in the code above.
Here is a slightly expanded edition … it outputs the number of changed instances and returns an array of those changed instances or nil if none were changed.
module Tenquin
extend self
def change_mat( key, valor, mat )
model = Sketchup.active_model
selection = model.selection
return "Empty Selection!" if selection.empty?
dict = "dynamic_attributes"
dcs = selection.grep(Sketchup::ComponentInstance).find_all { |dc|
dc.get_attribute(dict,key) == valor ||
dc.definition.get_attribute(dict,key) == valor
}
return "No Dynamic Component Instances found !" if dcs.empty?
puts "Instances found : #{dcs.count}"
defs = dcs.map { |dc| dc.definition }.uniq!
return "DC Definitions search resulted in nil." if defs.nil?
return "No Dynamic Component Definitions found !" if defs.empty?
puts "Definitions found : #{defs.count}"
changed = []
num = 0
defs.each { |cdef|
change = cdef.entities.grep(Sketchup::ComponentInstance)
next if change.nil? || change.empty?
change.each { |inst| inst.material = mat }
num += change.size
changed.push(*change)
}
puts "Total DC Instances material changed : #{num}"
if num > 0
puts "Returning an array of references to each changed instance."
return changed
else
return nil
end
end # change_mat
end # module Tenquin
I checked your code and saw that it returns the number of instances if the component has not nested … If it is nested it returns as below.
‘’'ruby
mat = "Green"
valor = "portas" # "doors"
dict = "dynamic_attributes"
key = "materialoptions"
Tenquin.change_mat( key, valor, mat )
No Dynamic Component Instances found !
‘’
I can not get where I want to: Apply material in the instances it contains…
‘’'ruby
value = "portas"
dict = "dynamic_attributes"
key = "materialoptions"
‘’
… at any nesting level …
I did a re-reading of the first code I posted, but only applies material to instances that are at a nesting level, I’d like to apply at any level and without nesting.
Maybe you’re leading me the right way, but I still can not adapt your code to do what I want.
NOTE: I do not know if I’m putting the codes in the correct way, but I followed what you said …
‘’'ruby
## 1 nível de aninhamento
model = Sketchup.active_model
definition=model.definitions
selection = model.selection
entities = model.active_entities
mat="Green"
valor="portas"
dict = "dynamic_attributes"
key = "materialoptions"
dcs = []
selection.grep(Sketchup::ComponentInstance).each{|s|
s.definition.entities.grep(Sketchup::ComponentInstance).each{|e|
dcs << e if e.definition.get_attribute(dict,key)== valor
}}
dcs.each{|m|m.material=mat}
That needs to be three backticks before ruby, not single quotes. Backtick is at the upper left of the keyboard (at least on a US keyboard) along with tilde ‘~’.
It was really an example of how to write a module, and how to inspect collections at certain times in the code.
Build on it and learn.
Well this is complicated. You are dealing with Dynamic Components which are complex. If they have their own material attributes they may be overriding what you are trying to do.
Perhaps explain in more basic terms what and why you want to do this ?
I work with woodworking and would like to develop a tool that applies material to all instances of nested components or not, which contains the attributes and keys previously mentioned.
It would be a kind of filter, in all selected cabinets that have pieces with dynamic attributes “materialoptions” and keys “doors” apply the green material, for example.
At first I’m trying to make the code, but then I want to put a collection of materials in a webdialog that when triggered executes the code applying the material as described.
To apply a material to all existing instances in a selection, you can follow this example:
def add_mat(ents)
ents.grep(Sketchup::ComponentInstance).each do |e|
e.material = "Green"
add_mat(e.definition.entities)
end
end
mod = Sketchup.active_model
sel = mod.selection
add_mat(sel)
To add a dynamic material attribute to all nested components, you can follow this example:
def add_attribut_mat(ents)
ents.grep(Sketchup::ComponentInstance).each do |e|
e.set_attribute 'dynamic_attributes','material','0'
e.set_attribute 'dynamic_attributes','_material_formula','"Green"'
$dc_observers.get_latest_class.redraw_with_undo(e)
add_attribut_mat(e.definition.entities)
end
end
mod = Sketchup.active_model
sel = mod.selection
add_attribut_mat(sel)
Edit
Corrected example!
To test the codes, you must select your components and then copy and paste into the Ruby console.
This is silly code. You have already filtered just component instances from the entstwice !
You need not do it three times. Once is enough.
And you did the same thing in the example add_attribut_mat().
You should not create literal string objects in each loop of an iterative block.
Define them first outside the block and then each loop will use the same object …
def add_attribut_mat(ents)
dict = 'dynamic_attributes'
attb = 'material'
form = '_material_formula'
matl = '"Green"'
indx = '0'
ents.grep(Sketchup::ComponentInstance).each do |e|
e.set_attribute( dict, attb, indx )
e.set_attribute( dict, form, matl )
$dc_observers.get_latest_class.redraw_with_undo(e)
add_attribut_mat(e.definition.entities)
end
end
When you create a literal String, the Ruby interpreter calls String::new("Your literal string") each pass through the loop.
Ruby does not actually have real variables like Pascal or BASIC.
Ruby is a 100% object oriented programming language.
Ruby has 2 things,… objects and references that point at objects.
So we do not “store” strings “in” variables.
In Ruby the interpretive = is the assignment operator.
It assigns a reference name to point at an object.
So, running the code …
attb = 'material'
… creates a new String class instance object "material" …
… and then, assigns the reference name attb to point to it.
Anyway, … creating the same objects over and over again,
in each pass through your loops will slow down your code.
Oh, and …
dict = 'dynamic_attributes'
… should be a constant at the top of your module as you’ll likely be using it many places …
It’s not a pretty code, but it’s close to what I want.
Apply the material at up to three nesting levels, but one at a time.
I’m in the right way? How would you do it?
#Meu código
def add_attribut_mat(ents)
dict = "dynamic_attributes"
key = 'materialoptions'
valor = 'portas'
attb = 'material'
form = '_material_formula'
matl = '"Green"'
indx = '0'
model = Sketchup.active_model
selection = model.selection
return "Empty Selection!" if selection.empty?
ents = []
selection.grep(Sketchup::ComponentInstance).each{|a|
ents << a if a.definition.get_attribute(dict,key)== valor
}
selection.grep(Sketchup::ComponentInstance).each{|b|
b.definition.entities.grep(Sketchup::ComponentInstance).each{|c|
ents << c if c.definition.get_attribute(dict,key)== valor
}}
selection.grep(Sketchup::ComponentInstance).each{|d|
d.definition.entities.grep(Sketchup::ComponentInstance).each{|e|
e.definition.entities.grep(Sketchup::ComponentInstance).each{|f|
ents << f if f.definition.get_attribute(dict,key)== valor
}}}
ents.each do |e|
e.set_attribute( dict, attb, indx )
e.set_attribute( dict, form, matl )
$dc_observers.get_latest_class.redraw_with_undo(e)
add_attribut_mat(e.definition.entities)
end
end
mod = Sketchup.active_model
sel = mod.selection
add_attribut_mat(sel)