I’m making a statistics plugin, and I’m trying to get the amount of unused materials in a model
I can get a number, but in large projects there is an account error, a number appears that is higher than it should be.
def self.get_unused_materials_count
model = Sketchup.active_model
used_materials = model.entities.grep(Sketchup::Face).map(&:material).compact.uniq +
model.definitions.grep(Sketchup::ComponentDefinition).flat_map { |d| d.entities.grep(Sketchup::Face).map(&:material) }.compact.uniq +
model.entities.grep(Sketchup::Group).flat_map { |g| g.entities.grep(Sketchup::Face).map(&:material) }.compact.uniq
all_materials = model.materials.to_a
unused_materials = all_materials - used_materials
unused_materials.size
end
for tags it’s the same thing
def self.get_unused_layers_count
model = Sketchup.active_model
all_layers = model.layers.to_a
used_layers = []
model.entities.each do |entity|
used_layers << entity.layer unless entity.layer.nil?
if entity.respond_to?(:definition)
entity.definition.entities.each do |sub_entity|
used_layers << sub_entity.layer unless sub_entity.layer.nil?
end
end
end
used_layers.uniq!
unused_layers = all_layers - used_layers
unused_layers.size
end
help? I just need to show the total materials in the file and how many are not being used, the same thing for the tags.
Cabarsa:
I’m making a statistics plugin, and I’m trying to get the amount of unused materials in a model
I can get a number, but in large projects there is an account error, a number appears that is higher than it should be.
(1) Realize that there are three kinds (owner types) of material objects:
Only the materials manager “owned” material objects are allowed to be assigned to geometric Drawingelements
.
See this API tracker issue:
opened 03:39PM - 16 Nov 20 UTC
Ruby API
SketchUp
documentation
#### SketchUp Ruby API Documentation Issue
### **Add Ruby API information or … warnings for internal vs manager materials**.
----
#### Backstory
It is **already known** that [`Sketchup::Materials#count`](https://ruby.sketchup.com/Sketchup/Materials.html#count-instance_method), [`Sketchup::Materials#length`](https://ruby.sketchup.com/Sketchup/Materials.html#length-instance_method) and [`Sketchup::Materials#size`](https://ruby.sketchup.com/Sketchup/Materials.html#size-instance_method) return the sum of both "internal" and "manager" materials, but that [`Enumerable#to_a`](https://ruby-doc.org/core-2.5.5/Enumerable.html#method-i-to_a) exports only "manager" materials. (Ie, ALL methods that are inherited by mixing in the [`Enumerable`](https://ruby-doc.org/core-2.5.5/Enumerable.html) module into the [`Sketchup::Materials`](https://ruby.sketchup.com/Sketchup/Materials.html) collection class... act **only** upon the subset of "manager" materials.)
See: [Issue : Sketchup.active_model.materials.size() != Sketchup.active_model.materials.to_a.size()](https://github.com/SketchUp/api-issue-tracker/issues/12)
This (limitation to "manager" materials) was done **purposefully** by the API authors because manipulating "internal" materials can and often does crash SketchUp. It was never intended for coders to **directly** manipulate [`Image`](https://ruby.sketchup.com/Sketchup/Image.html) materials and [`Layer`](https://ruby.sketchup.com/Sketchup/Layer.html) material functionality has not yet been fully developed and only partially exposed via the [`Sketchup::Layer#color=`](https://ruby.sketchup.com/Sketchup/Layer.html#color=-instance_method) setter method.
#### ImageRep class added in v2018
The [`Sketchup::ImageRep`](https://ruby.sketchup.com/Sketchup/ImageRep.html) class was added to enable getting raw image data from materials. A dedicated [`Sketchup::Image#image_rep`](https://ruby.sketchup.com/Sketchup/Image.html#image_rep-instance_method) method was added so coders could get the image object's material texture without rigmarole and enabling it to be used safely for _normal_ "manager" material creation.
This interface can be used to convert a "internal" hidden material into a normal "user" material. See:
* [Issue 307 : Add example of converting an Image to a Material](https://github.com/SketchUp/api-issue-tracker/issues/307) (documentation)
#### Getting the material type (it's owner) was added in v2019.2
Issue 12 resulted in the 2019.2 release adding a query method for code to determine what "owns" the material.
For these "hidden" material instances, [`Sketchup::Material#owner_type`](https://ruby.sketchup.com/Sketchup/Material.html#owner_type-instance_method) will return:
* **`Sketchup::Material::OWNER_IMAGE`**
* **`Sketchup::Material::OWNER_LAYER`**
For normal "user" materials that the user can see in the materials manager (and paint objects with,) this query will return:
* **`Sketchup::Material::OWNER_MANAGER`**
```ruby
def all_matl_types
matls = Sketchup.active_model.materials
i = 0
all = []
while (matls[i] rescue nil) # IndexError if i is out of range
all << matls[i]
i += 1
end
n = 0
for m in all
type = case m.owner_type
when Sketchup::Material::OWNER_MANAGER then "manager"
when Sketchup::Material::OWNER_LAYER then "layer"
when Sketchup::Material::OWNER_IMAGE then "image"
end
puts "#{m.inspect}\n name: #{m.name.inspect} (type:#{type})"
n += 1
end
puts "#{n} materials found."
nil
end
```
See: [Issue 180 : Access hidden materials like hidden definitions](https://github.com/SketchUp/api-issue-tracker/issues/180) (feature request)
----
#### The API dictionary should explain the difference between "internal" and "manager" materials.
(The related issues have been labeled "Bug" or "Enhancement", so the docs have **not** been improved with regard to this situation.)
The main problem here (that resulted in this issue report) is that the API documentation is sorely lacking in describing and warning coders about the pitfalls of "internal" material objects.
Documentation (and warning notes) are needed for:
* [`Sketchup::Materials`](https://ruby.sketchup.com/Sketchup/Materials.html) class Overview
Should have a note informing coders that all methods that are inherited by mixing in the [`Enumerable`](https://ruby-doc.org/core-2.5.5/Enumerable.html) module into this collection class... act **only** upon the subset of "manager" materials.
This occurs because the Enumerable iterators use the subclass' [`#each`](https://ruby.sketchup.com/Sketchup/Materials.html#each-instance_method) method ...
* [`Sketchup::Materials#each`](https://ruby.sketchup.com/Sketchup/Materials.html#each-instance_method) should tell coders that it acts only upon the subset of "manager" materials.
* [`Sketchup::Materials#[](index)`](https://ruby.sketchup.com/Sketchup/Materials.html#[]-instance_method) should show that an [`IndexError`](https://ruby-doc.org/core-2.7.1/IndexError.html) will occur if the [`Integer`](https://ruby-doc.org/core-2.7.1/Integer.html) argument passed is out of range (or changed so that **`nil`** is returned as it is when this happens on an [`Array`](https://ruby-doc.org/core-2.7.1/Array.html) object.)
####
But these 3 methods should warn coders that they return the sum of both "internal" and "manager" materials:
* [`Sketchup::Materials#count`](https://ruby.sketchup.com/Sketchup/Materials.html#count-instance_method)
Has `"Enumerable"` **misspelled** as `"Enumable"`. Although this method is inherited from the [`Enumerable`](https://ruby-doc.org/core-2.5.5/Enumerable.html) module since SketchUp 2014, it apparently is not limited to the "manager" materials. (Somehow it does not seem to use [`#each`](https://ruby.sketchup.com/Sketchup/Materials.html#each-instance_method) for it's iteration.)
* [`Sketchup::Materials#length`](https://ruby.sketchup.com/Sketchup/Materials.html#length-instance_method)
* [`Sketchup::Materials#size`](https://ruby.sketchup.com/Sketchup/Materials.html#size-instance_method)
... all 3 methods could inform coders that they can get true "manager" material counts via **[`.to_a.size`](https://ruby-doc.org/core-2.7.1/Array.html#method-i-size)**.
#####
* [`Sketchup::Material`](https://ruby.sketchup.com/Sketchup/Material.html) class Overview
... and specifically:
* [`Sketchup::Material#owner_type`](https://ruby.sketchup.com/Sketchup/Material.html#owner_type-instance_method) instance method.
Briefly, note (or point readers via a link to an explanation of) the difference between "internal" and "manager" materials. (It could be a link to the top of the page, where the explanation in the class Overview could do double-duty.)
#####
* [`Sketchup::Image`](https://ruby.sketchup.com/Sketchup/Image.html) class Overview
A warning not to use image textures directly as materials for geometry, ... and to use the [`#image_rep`](https://ruby.sketchup.com/Sketchup/Image.html#image_rep-instance_method) interface to create a new "manager" material for use with [`Sketchup::Drawingelement#material=`](https://ruby.sketchup.com/Sketchup/Drawingelement.html#material=-instance_method) method calls assigning materials to geometric objects. (See also Issue #307)
----
#### Materials class could be refined or expanded
I posted an example of a Ruby refinement to refine the [`Materials`](https://ruby.sketchup.com/Sketchup/Materials.html) collection class:
* ["Refined::Sketchup::Materials IDEAS" in Issue 180](https://github.com/SketchUp/api-issue-tracker/issues/180#issuecomment-727609597)
~
(2) Your counting code is only checking for materials assigned to Face
objects. However, both group and component instances as well as Edge
objects can have material assignments. There are also other Drawingelements
subclass objects that can have material assignments, such as dimensions and text callouts (which will display their material.)
Then there are others that accept a manual material setting which shows in the Model Info swatch, but does not display nor get returned from Ruby, such as a SectionPlane
object. (The latter may be a bug as Guidelines are similar but do not allow the Paint Bucket tool to paint them.)
Also, see this API tracker thread which I posted some useful Ruby refinements as ideas for future API methods:
opened 10:47PM - 25 Nov 18 UTC
enhancement
Ruby API
SketchUp
## Issue:
Currently, a model can contain Sketchup::Material objects that are 1.… hidden from the materials browser GUI and 2. hidden from the Sketchup::Materials collection (!).
When you import an image entity (image instance of a hidden ComponentDefinition containing a face with textured material) into an empty model, the material is not accessible:
```
Sketchup.active_model.materials.length
> 1
Sketchup.active_model.materials.to_a.length
> 0
Sketchup.active_model.materials.to_a
> []
Sketchup.active_model.materials[0]
> #<Sketchup::Material:0x000000286bddc0>
```
This is a contradiction! And like `to_a`, all other Enumerable methods on the Sketchup::Materials collection traverse only the visible materials. The only way to traverse all materials is:
```
(0...Sketchup.active_model.materials.length).map{ |i| Sketchup.active_model.materials[i] }
```
## Use case:
Traversing all image files that are embedded in a model in order to optimize their file size.
## Request
The inconsistency should be resolved because it can cause errors in extensions that use the Enumerable methods and `length` and assume they are consistent.
Apart from that, there should be a proper way to access hidden materials, as well as to distinguish them from visible materials, e.g. like component definition's `hidden?`method.
1 Like