0

Is there a simple way to get a label on the panel containing the number of materials in the project? Especially updating yourself

Edit:

In accordance with the suggestions, I add a fundamental detail: I'm trying to visualize in the panel a label that indicates the number of bpy.data.materials I don't know what the correct practice is, so I didn't sketch a script, as I don't have the idea of what the right path is. Basically the way that comes to mind is a script like this:

mat_list=[]
for m in bpy.data.materials:
    if m.users!=0:
        mat_list.append(m)
mat_count= len(mat_list)

I don't know how to place this, if I use a function and how to update it without weighing down the script, I don't know if we should use a pointerproperty. I have serious doubts about it

Edit: 2 (My solution) In relation to the inspiration given to me by brockmann, I have reached this compromise that seems simple and works, I only hope that it does not take away too many resources from the CPU.

layout.label(text= "Material in use: {}".format(len([m for m in bpy.data.materials if m.users!=0])))

This:

##`if m.users!=0`

because I am interested only in materials that are actually in use on objects, bpy.data.materials also collects materials not in use so it would give me a wrong count. For now I think I have solved, I hope I have found a correct way.

Noob Cat
  • 1,222
  • 3
  • 20
  • 61
  • 1
    What have you tried and what failed. What python scripting knowledge do you have, how complex can the answer be? – Leander Sep 02 '19 at 12:35
  • @Leander I edited the question, as it is more a question of correct practice, of ways I think there are many, but currently I have no idea how to do it. I'm sorry if I seemed unclear, I hope to be with this edit – Noob Cat Sep 02 '19 at 23:24

1 Answers1

2

len(bpy.data.materials) returns the number (int) of all materials in the file. To draw this number onto a custom panel, means that you have to pass it to the text argument of the label() call (string).

In fact, you can either use str.format() or the old C-style formatting syntax for integers "%i" % number to pass the variable. A nice collection of examples here: https://pyformat.info/

# Number of all Materials in the .blend
layout.label(text="Materials: {}".format(len(bpy.data.materials))

Following example displays the number materials per file (Data.materials) as well as all materials in the current scene (custom object iteration). Layout code is ripped from How to create a custom UI?

enter image description here

import bpy

from bpy.props import (BoolProperty,
                       IntProperty,
                       PointerProperty
                       )

from bpy.types import (Panel,
                       PropertyGroup
                       )


# ------------------------------------------------------------------------
#    Scene Properties
# ------------------------------------------------------------------------

class MyProperties(PropertyGroup):

    my_bool: BoolProperty(
        name="Enable or Disable",
        description="A bool property",
        default = False
        )

    my_int: IntProperty(
        name = "Int Value",
        description="A integer property",
        default = 23,
        min = 10,
        max = 100
        )

# ------------------------------------------------------------------------
#    Panel in Object Mode
# ------------------------------------------------------------------------

class OBJECT_PT_CustomPanel(Panel):
    bl_label = "My Panel"
    bl_idname = "OBJECT_PT_custom_panel"
    bl_space_type = "VIEW_3D"   
    bl_region_type = "UI"
    bl_category = "Tools"
    bl_context = "objectmode"   

    def scene_materials(self, context):
        mat_list_per_object = []
        for ob in context.scene.objects:
            if ob.type in ("MESH", "CURVE", "SURFACE", "FONT", "GPENCIL"):
                if ob.data.materials:
                    mat_list_per_object.append(ob.data.materials.items())
        # Flatten the list and return a set (unique)
        return set([i[0] for i in sum(mat_list_per_object, [])]) 

    @classmethod
    def poll(self,context):
        return context.object is not None

    def draw(self, context):
        layout = self.layout
        scene = context.scene
        mytool = scene.my_tool

        # Custom Properties
        #layout.prop(mytool, "my_bool")
        #layout.prop(mytool, "my_int")

        # Number of Materials in File (bpy.data)
        layout.label(text="Number of Materials in this File: {}".format(
                len(bpy.data.materials))
            )

        # Number of materials (unique) per scene in context
        layout.label(text='Unique Materials in "{}": {}'.format(
                context.scene.name, 
                len(self.scene_materials(context)))
            )

        # Draw a list of materials per scene
        layout.separator()
        layout.label(text="List of Materials:")

        for i in self.scene_materials(context):
            layout.label(text=i)

        layout.separator()

# ------------------------------------------------------------------------
#    Registration
# ------------------------------------------------------------------------

classes = (
    MyProperties,
    OBJECT_PT_CustomPanel
)

def register():
    from bpy.utils import register_class
    for cls in classes:
        register_class(cls)

    bpy.types.Scene.my_tool = PointerProperty(type=MyProperties)

def unregister():
    from bpy.utils import unregister_class
    for cls in reversed(classes):
        unregister_class(cls)
    del bpy.types.Scene.my_tool


if __name__ == "__main__":
    register()
brockmann
  • 12,613
  • 4
  • 50
  • 93
  • Thanks for the inspiration you gave me, I found a solution that seems to work and mentioned it in the Edit 2 – Noob Cat Sep 03 '19 at 13:24
  • 1
    Huh, problem is that bpy.data.materials returns all materials in the file (as stated in my answer), doesn't necessary makes sense to display that for the user... Next, you can get rid of the =! operator: len([m for m in bpy.data.materials if m.users]) Note: would be nice you had mentioned that you want to check for a user in your question @Pastrokkio – brockmann Sep 03 '19 at 13:38
  • Right! excellent observation, Thank you for your time – Noob Cat Sep 03 '19 at 13:43
  • 1
    Even faster is using a generator: sum(1 for _ in (m for m in bpy.data.materials if m.users)) for counting instead of len([m for m in bpy.data.materials if m.users]) @Pastrokkio – brockmann Sep 03 '19 at 13:46
  • I'm happy with this help, it's great, sometimes I worry about the interface, because it updates often, I don't want to overload it unnecessarily. We hope I'm choosing the right way. Ty brockmann – Noob Cat Sep 03 '19 at 14:00
  • 1
    Not sure what you're talking about... Choosing the right way, the interface updates often? There are always hundreds of ways implementing something, you just need t make sure it's working in any case the user expects. Also I don't think there is any speed issue when iterating over a few hundreds of materials, but that is also something you can test beforehand @Pastrokkio – brockmann Sep 03 '19 at 14:20