Count sub-components with the definition "TOTO"

Hello everyone. :grinning:

In recent days I manage to code alone things more and more complex.

I am surprised to block on a problem that seems simple.

To count the entities present in a component, I can do like this:

Sketchup.active_model.selection.grep(Sketchup::ComponentInstance).each do |s|
lg = s.definition.entities.count
puts lg	
end	

Now I would like to count only components with the definition "TOTO".

How can I do ?

All help would be really appreciated.

Thank you

where do we look for definitions?

defs = Sketchup.active_model.definitions
# defs is our array of definitions so find the one you want
denf = defs['TOTO'] 
# find it's instances
insts = defn.instances
# count them
insts.count

### in one-liner 
toto_instances = Sketchup.active_model.definitions['TOTO'].instances.count

but, definition’s can be deeply nested inside other definitions…

for those, you will need a method that drills down looking for children…

you have example code from before…

john

To collect any selected instances with an exact name match use:

nom = "TOTO" # or any name
trouve = []
Sketchup.active_model.selection.grep(Sketchup::ComponentInstance).each{|e|
  trouve << e if e.definition.name == nom
}
puts trouve.length

To just match it partially use:
if e.definition.name =~ /#{nom}/ ### anywhere in name
if e.definition.name =~ /^#{nom}/ ### at start of name
if e.definition.name =~ /#{nom}$/ ### at end of name

Thank you TIG and John for your 2 examples.

My goal was to count the sub-components “TOTO” in a selection of components.

I managed to adapt the example of TIG like this:

nom = "TOTO"
trouve = []
Sketchup.active_model.selection.grep(Sketchup::ComponentInstance).each{|s|
s.definition.entities.grep(Sketchup::ComponentInstance).each{|e|
trouve << e if e.definition.name == nom
}}
puts trouve.length

Now, I would like to count a list of components present in every furniture with the definition “IKEA MB”, “IKEA MH”.

For example, “Handles” and “Feet”.

If the quantity is greater than 1, for the “Handles” definition in furniture “IKEA MB”, the excess handles are removed only if they are hidden.

If “IKEA MH”, has 2 feet, they are removed under the same conditions.

I have coded the method below which must be filled with error because it does not work:

  ["IKEA MB","IKEA MH"].each do |liste1|
	match1 = /#{liste1}/
	trouve = []
    mod = Sketchup.active_model
	mod.definitions.each do |d| next unless d.name =~ match1
	d.entities.grep(Sketchup::ComponentInstance).each do |e|
  ["Handles","Feet"].each do |liste2|
	match2 = /#{liste2}/	
	trouve << e if e.definition.name =~ match2
	e.definition.instances.each do |i|
 if trouve.length > 1
	i.erase! if i.visible?
    end
   end
  end
end

end
end

Thank you in advance for your help.

David

I always was sure that “e.definition.instances” returns an array of instances of this definition (not instances stored inside of this definition).
Here is my attempt for the second task (not tested, just a code to express an idea):

# Declare some variables outside of iterators
mod=Sketchup.active_model
defs=mod.definitions
approp_names=["IKEA MB","IKEA MH"]
# Select defintions, which have appropriate names
approp_defs=defs.to_a.select{|d| approp_names.include?(d.name)}
names2check=["Handles","Feet"]
names2check.each{|n2c|
    approp_defs.each{|a_d|
        # Select visible component instances with a matching name
        ents2erase=a_d.entities.to_a.select{|e| e.respond_to?("definition")}.select{|c| (c.definition.name==n2c and c.visible?)}
        next if ents2erase.length<2
        # Leave only one of such component instances
        ents2erase.pop
        # Erase others
        a_d.entities.erase_entities(ents2erase)
    }
}
[UPD] Added a check of ents2erase length to skip unnecessary steps.

definition.entities.grep(Sketchup::ComponentInstance) returns all component instances directly inside a component.

To get references to all instances of one certain definition, definition_a, within another definition, definition_b, you could use:

definition_a.instances & defnition_b.entities.grep(Sketchup::ComponentInstance)
1 Like

Hello, kirill200777

I tested your method and tried to modify it in several ways, but I can not make it work.
With the method “_ ** defs.to_a.select {| d | ** _”, I have the impression that nothing is selected, is this normal?

Thank you eneroth3, I will try to use your advice to readapt my method.

Thank you

David

Your code does about the same thing as mine but is longer and harder to read. Also it doesn’t assign found at all, not even as an empty Array, if definition_a has no instances.

Wait what?

No. It will evaluate as an Array of ComponentInstance objects.

Of course you wouldn’t want nil returned on ComponentDefinition.instances. Nil isn’t the same as an empty set., just as nil isn’t the same as false or 0. There are different kinds of nothingness.

That’s why the two Arrays are intersected to find their overlap.

This is still an unnecessary long and complex method that handles empty sets separately as edge cases, declare variables and use two different loops.

Eneroth 3 and Dan,

I have analyzed your methods and I am unable to know who is right. :grin:

In your 2 examples, you use definition_ :

definition_a is the list of components to observe?

defintion_b is the list of sub-components to be identified in definition_a?

If yes, should I proceed like this:

	["IKEA-MB","IKEA-MH"].each do |liste1|
	a = /#{liste1}/
	mod = Sketchup.active_model
    sel = mod.selection
    mod.definitions.each do |d| sel.add d.instances if d.name=~a

I still lack experience in ruby, to use your code snippet in my methods.

More details would be really appreciated.
_

Another question that has nothing to do with your examples.

I learned that it is possible to browse a table and execute a method on any element of the list.

For the moment I still lack technical words to be explicit so here is an example:

	a = ["Handles","Feet"]
	mod = Sketchup.active_model
    sel = mod.selection
    mod.definitions.each do |d| sel.add d.instances if d.name==a[0]
	end

This code example selects “Handles”.
If I change the value to “1” instead of “0”, “Feet” will be selected.

This can be useful if I want to apply methods to “Handles” without affecting “Feet”.

For example, count the “Handles” entities separately from “Feet”, and apply the deletion only if one of them is> 1.

But I have a problem with this solution:

This method only applies if the definitions are strictly == “IKEA-MB”.

How to use this method for all made unique definitions like “IKEA-MB”, “IKEA-MB # 1”, “IKEA-MB # 2”?

Note :

With the method “each”, “a == [0]”, no longer works.

How can I get around the problem?

Thank you in advance for your help.

David

@eneroth3 is correct.

My eyesight failed me again. I mistook a single & for a double ampersand (&&), so I read the entire statement as a compound boolean expression.

@dynamiqueagencement David, nevermind anything I wrote. (Deleting all my posts.)

1 Like

Dan I find it a shame because it is always interesting to have 2 different approaches!

Am I getting closer to the solution?

  ["Handles"].each do |liste2|
	b = /#{liste2}/
    qut = []	
    mod = Sketchup.active_model
	defi = mod.definitions.each do |d| 
    d.instances & d.entities.grep(Sketchup::ComponentInstance).each do |e|
	  qut << e unless e.visible?
	lg = qut.length if e.definition.name =~ b
 if lg.to_i>1
    e.erase! unless e.visible?
    end
  end

end
end

Your help would be really welcome!

Hello David,
I’ve tested my own example, seems to work fine in case if I understood the task correctly.
Method “_ ** defs.to_a.select {| d | ** _” does not “select” entities visually in SketchUp model (i.e. it does not put entities into an active selection in an active model) it only returns an array with entities, which match selection condition (again if I understood your question correctly). In case if “approp_defs” array appears to be empty, then it means that active model does not contain any definitions with names specified in “approp_names” array.
So my example searches for definitions with appropriate names, then iterates through all appropriate definitions and searches for specified instances nested inside of found definitions and erases all visible instances except one. Maybe it’s not what you actually expected, it is how I understood the task.

I’m frankly quite confused what you are trying to achieve. I know it has to do with counting component instances but do you want to:

a) count the number of instances of a certain definition within another certain definition,
b) count the number of instances of a certain definition within the selection,
c) count the number of instances of a certain definition within another certain definition, within the selection or
d) something else.

Also note that there can be several instance counts of the same definition. Imagine you create a definition named “Bed” and inside it create another definition called “Pillow” and then copy the component “Bed” so you have to instances of it. Technically there is still only one instance of the component named “Pillow”, the one that resides in the “Bed” definition. pillow_definition.instances.size returns 1. Still you can visually see 2 pillows in the model and when selecting the pillow Entity Info will report 2 instance. This is because there are 2 instance paths, one going to the first Bed instance and one going through the second Bed instance, both leading to the same single Pillow instance. If you want to count instances, which count are you interested in?

As Kirrill mentioned the select method has nothing to do with adding entities to the selection (highlight them and let the user interacts with them). The select method is a Ruby core method that exists in all Ruby environments, even outside of SketchUp. The select method is filter out only the elements in a collection that matches a certain condition.

Instead of writing this:

matches = []
my_array.each do |element|
  matches << element if some_condition
end
matches

you can write this:

my_array.select { |e| some_condition }

It means the same but the latter is easier top read which makes the code easier to work with easier to dind bugs in and easier to add new features to.

Regarding code readability if would help drastically if you payed more attention to indentation. Indent with two spaces, only increase indentation when a new code block is opened (on if, unless, def, do, { etc) and only decrease indentation when a code block is closed (end and }). If a line neither opens or closes code blocks it should line up with the line above.

In this code I struggle to read that the reference to e in “e.erase! unless e.visible?” is the e defined in “grep(Sketchup::ComponentDefinition).each do |e|” becase it looks like the code block opened by do is already closed at erase!.

Without being able to parse the code I can’t say if it works.

Hi kirill200777,

Indeed your code works but it does not do what wishes.

Thank you for the explanation regarding the selection.

Can I use the 50/50 of Who Wants to Be a Millionaire??

More seriously, I think answer A is what I want to do.

Here are more details in your example:

Bed = 2 Pillows.

Bed # 1 = 3 Pillows.

Bed # 2 = 4 Pillows.

Condition A: 2 pillows are allowed for each bed.

Action B: The method removes, “1 Pillow to Bed # 1”, “2 Pillow to Bed # 2” and do nothing to “Bed”.

Condition C: The pillows are removed only if they are hidden.

_

I tested your changes in my base code, and I get the following error messages in the ruby console:

Error: #<SyntaxError: <main>:6: syntax error, unexpected &, expecting keyword_end
         qut &lg;&lg; e unless e.visible?

Thank you

David

The difficulty is to count the sub-components for each parent component.

Here is a method I wrote that:

1- List the instances in the model.
2- Extract all entities with their definitions.
3- Counts the entities.

mod = Sketchup.active_model.definitions.each do |d|
	inst = d.instances.each do |i|
	       d.entities.grep(Sketchup::ComponentInstance).each do |e| 
     puts "#{i.definition.name} - #{e.definition.name} Qut: #{e.definition.instances.length}"
    end
  end 
end	

I do not understand why, instances and entities are listed multiple times by the ruby console?

It’s been too long since I’m stuck with this challenge, way too complex for my current level in Ruby.

I have no choice but to work around the problem in another way.

This challenge will have at least allowed me to progress a little more in Ruby.

Thank you to everyone who tried to help me.

See you.

David

I took the liberty to fix the indentation to make the code easier to read. I also purged unused variables being assigned.

Sketchup.active_model.definitions.each do |d|
  d.instances.each do |i|
    d.entities.grep(Sketchup::ComponentInstance).each do |e| 
      puts "#{i.definition.name} - #{e.definition.name} Qut: #{e.definition.instances.length}"
    end
  end 
end	

First the code iterates over all definitions in the model. Within that loop, once for each definition there is, it iterates over all the instances. Within that loop it prints definition specific information to the console.

As you are only printing data related to the definitions, not every individual instance, you can delete “d.instances.each do |i|” and its corresponding “end”. Also you need to change “i.definition.name” to “d.name” as you then no longer has i as a reference.

After doing this the the information will only be printed once for each definition.

Counting instances can be misleading, as instances might be used by definitions that themselves’ have no instances in the model.

So there are two API methods to get component counts:

1 Like

These two methods account for all instances in SketchUp and not in a component or group.