0
$\begingroup$

new to Python, so struggle a little. With some help from here I managed to create little code to count objects in my scene. It works great in console, now I want to create a panel which will automatically update object count. Here's the code for object counter:

from collections import defaultdict
counter = defaultdict(int)
for obj in bpy.data.collections['Stands'].all_objects:
    dot_split = obj.name.split('.')
    counter[dot_split[0].split('.')[0].strip()] += 1
    
for name, count in counter.items():
    print(f"{name:35} | {count:1d}")

I want to try and implement this code into ui_panel_simple template.



class HelloWorldPanel(bpy.types.Panel):
    """Creates a Panel in the Object properties window"""
    bl_label = "Hello World Panel"
    bl_idname = "OBJECT_PT_hello"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "object"

    def draw(self, context):
        layout = self.layout

        obj = context.object

        row = layout.row()
        row.label(text="Hello world!", icon='WORLD_DATA')

def register():
    bpy.utils.register_class(HelloWorldPanel)


def unregister():
    bpy.utils.unregister_class(HelloWorldPanel)


if __name__ == "__main__":
    register()

Here is what I tried to do:

```import bpy


class HelloWorldPanel(bpy.types.Panel):
    """Creates a Panel in the Object properties window"""
    bl_label = "Hello World Panel"
    bl_idname = "OBJECT_PT_hello"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "scene"

    
    
    def draw(self,context):
        layout = self.layout

        def obj(self):
            from collections import defaultdict
            counter = defaultdict(int) 
        
            for obj in bpy.data.collections['Stands'].all_objects:
                dot_split = obj.name.split('.')
                counter[dot_split[0].split('.')[0].strip()] += 1
            
            for name, count in counter.items():
                 return f"{name:35} | {count:1d}"         

        col = layout.column()
        col.label(text = "1" + obj)

def register():
    bpy.utils.register_class(HelloWorldPanel)

def unregister():
    bpy.utils.unregister_class(HelloWorldPanel)


if __name__ == "__main__":
    register()

Obviously, it doesn't work. I have been trying for 3 days now and I am at a loss...

Here is the latest message from console:

location: :-1 Python: Traceback (most recent call last): File "C:\Users\fedor\Regional group stand.blend\ob_count.py", line 29, in draw TypeError: can only concatenate str (not "function") to str

$\endgroup$
5
  • $\begingroup$ Hello ! you're concatenating the function to 1, not the result of the function. You need to call it. BTW you don't need self as an argument here. def obj(): should be enough. Try replacing the label line with col.label(text = "1" + obj()) or col.label(text = "1" + str(obj())) to make sure you're dealing with string types. Not a blender related question TBH, I suggest you familiarize yourself with python basics a bit more before diving into the Blender API. Cheers :) $\endgroup$
    – Gorgious
    Dec 20, 2022 at 10:46
  • $\begingroup$ Great ressource if you want to learn more about nested functions youtube.com/watch?v=jXugs4B3lwU $\endgroup$
    – Gorgious
    Dec 20, 2022 at 10:47
  • $\begingroup$ @Gorgious, I edited as you said, here is what the panel shows dropmefiles.com/cNxKh $\endgroup$
    – Ted Ryabov
    Dec 20, 2022 at 10:50
  • $\begingroup$ Hello, please read my first comment again. You need to call the function. use open and closed parentheses col.label(text = "1" + str(obj())) $\endgroup$
    – Gorgious
    Dec 20, 2022 at 10:52
  • $\begingroup$ @Gorgious, yeah, sorry, i misspelled the line... so, It works! However, it only shows only one line of text in panel... dropmefiles.com/sU8ka How to make it look like this dropmefiles.com/S9gwe $\endgroup$
    – Ted Ryabov
    Dec 20, 2022 at 10:57

2 Answers 2

1
$\begingroup$

I don't think you need a nested function for what you're trying to achieve. However if you really want to use this workflow, you need to explore generators and the yield keyword.

You can use for instance :

# Don't import modules in a draw loop, it's called dozens of time per second
from collections import defaultdict

...

    def draw(self,context):
        layout = self.layout

        counter = defaultdict(int) 
        if "Stands" not in bpy.data.collections:
            return
        
        for obj in bpy.data.collections['Stands'].all_objects:
            dot_split = obj.name.split('.')
            counter[dot_split[0].split('.')[0].strip()] += 1
            
        for name, count in counter.items():
             layout.label(text=f"{name:35} | {count:1d}")

You'll notice though that performance will be horrible if you happen to have hundreds of objects in your scene, because this code is called many times per second. In that case you'll need to find a way to do it more efficiently, or to cache the result somehow.

$\endgroup$
2
  • $\begingroup$ do you mean like this? dropmefiles.com/fZwdI $\endgroup$
    – Ted Ryabov
    Dec 20, 2022 at 11:13
  • $\begingroup$ No, you should put it at the start of your script just after import bpy $\endgroup$
    – Gorgious
    Dec 20, 2022 at 11:40
0
$\begingroup$

With @Gorgious help I finalized the code an this is what I have:

from collections import defaultdict


class ObjectCounterPanel(bpy.types.Panel):
    """Creates a Panel in the Object properties window"""
    bl_label = "Object Counter"
    bl_idname = "OBJECT_PT_Counter"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = 'Object Counter'

    def draw(self,context):
        layout = self.layout
        
        split = layout.split(factor=0.8)
        boxname_1 = split.split()
        boxname_2 = split.split()
        boxname_1.label(text = "Object Name")
        boxname_2.label(text = "Qty")        
        
        counter = defaultdict(int) 
        if "Stands" not in bpy.data.collections:
            return
        
        for obj in bpy.data.collections['Stands'].all_objects:
            dot_split = obj.name.split('.')
            counter[dot_split[0].split('.')[0].strip()] += 1
        

        split = layout.split(factor=0.8)
        splitbox_1 = split.box()
        splitbox_2 = split.box()

                   
        for name in counter.keys():
            splitbox_1.label(text = f"{name}")
        
    
        for count in counter.values():             
            splitbox_2.label(text = f"{count}")


def register():
    bpy.utils.register_class(ObjectCounterPanel)

def unregister():
    bpy.utils.unregister_class(ObjectCounterPanel)


if __name__ == "__main__":
    register()

enter image description here

$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .