1
$\begingroup$

I want to rename the materials of my scene from a long database of names I have. Just the name. I have created a simple version here with items, but I have similar list with more than 1800 items. So I want to be able to do search in that EnumProperty. I am beginner in Blender python API, I know I should be close but could not do it so far. Can you please help me create search capability in EnumProperty and change the name of the material with the one selected in EnumPropert?

import bpy
import re
import json

    
items = (("Metal", "Metal", ""),
("Plastic", "Plastic", ""),
("Glass", "Glass", ""),
("Shadow", "Shadow", ""),
)

PROPS = [
    ('material', bpy.props.PointerProperty(type=bpy.types.Material, name='Material')),
    ('my_enum', bpy.props.EnumProperty(items = items, name='New Name', default=None)),
]
# == UTILS
def rename_object(obj, params):
    (material, new_material) = params
        
    obj.name = '{}'.format(new_material)

# == OPERATORS
class MYCAT_OT_search_popup(bpy.types.Operator):
    bl_idname = "object.search_popup"
    bl_label = "Material Renamer"
    bl_property = "my_enum"

    my_enum: bpy.props.EnumProperty(items = items, name='New Name', default=None)

    def execute(self, context):
        self.report({'INFO'}, "You've selected: %s" % self.my_enum)
        return {'FINISHED'}

    def invoke(self, context, event):
        wm = context.window_manager
        wm.invoke_search_popup(self)
        #
        return {'FINISHED'}

    
class ObjectRenamerOperator(bpy.types.Operator):
    
    bl_idname = 'opr.object_renamer_operator'
    bl_label = 'Material Renamer'
    
    
    def execute(self, context):
        params = (
            context.scene.material,
            context.scene.my_enum,
        )
        
        for obj in bpy.context.selected_objects:
            rename_object(obj, params)
            
        return {'FINISHED'}

# == PANELS
class ObjectRenamerPanel(bpy.types.Panel):
    
    bl_idname = 'VIEW3D_PT_object_renamer'
    bl_label = 'Material Renamer'
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    
    
    def draw(self, context):
        col = self.layout.column()
        row = col.row()
        row.prop(context.scene, "material")
        row = col.row()
        row.prop(context.scene, "my_enum")
        
        #row.prop_search(context.scene, "my_enum", context.scene, "objects")
            
        col.operator('opr.object_renamer_operator', text='Rename')

# == MAIN ROUTINE
CLASSES = [
    MYCAT_OT_search_popup,
    ObjectRenamerOperator,
    ObjectRenamerPanel,
    
]

def register():
    for (prop_name, prop_value) in PROPS:
        setattr(bpy.types.Scene, prop_name, prop_value)
    
    for klass in CLASSES:
        bpy.utils.register_class(klass)

def unregister():
    for (prop_name, _) in PROPS:
        delattr(bpy.types.Scene, prop_name)

    for klass in CLASSES:
        bpy.utils.unregister_class(klass)
        

if __name__ == '__main__':
    register()
    

I did it, it was still %99 but I made a few changes by looking at your previous suggestions and it works now. Thanks a lot! A small question below also..

import bpy
import re
import json

    
items = (("Metal", "Metal", ""),
("Plastic", "Plastic", ""),
("Glass", "Glass", ""),
("Shadow", "Shadow", ""),
)

PROPS = [
    ('material', bpy.props.PointerProperty(type=bpy.types.Material, name='Material')),
    ("my_enum", bpy.props.EnumProperty(items = items, name='New Name', default=None)),
]
# == UTILS
def rename_object(obj, params):
    (material, new_material) = params
    obj.name = '{}'.format(new_material)

# == OPERATORS
class MYCAT_OT_search_popup(bpy.types.Operator):
    bl_idname = "object.search_popup"
    bl_label = "Material Renamer"
    bl_property = "my_enum"

    my_enum: bpy.props.EnumProperty(items = items, name='New Name', default=None)
    
    @classmethod
    def poll(cls, context):
        return context.scene.material  # This prevents executing the operator if we didn't select a material

    def execute(self, context):
        context.scene.my_enum = self.my_enum
        return {'FINISHED'}

    def invoke(self, context, event):
        wm = context.window_manager
        wm.invoke_search_popup(self)
        #
        return {'FINISHED'}

    
class ObjectRenamerOperator(bpy.types.Operator):
    
    bl_idname = 'opr.object_renamer_operator'
    bl_label = 'Material Renamer'
    
    
    def execute(self, context):
        params = (
            context.scene.material,
            context.scene.my_enum,
        )
        
        rename_object(context.scene.material, params)
        return {'FINISHED'}

# == PANELS
class ObjectRenamerPanel(bpy.types.Panel):
    
    bl_idname = 'VIEW3D_PT_object_renamer'
    bl_label = 'Material Renamer'
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    
    
    def draw(self, context):
        col = self.layout.column()
        row = col.row()
        row.prop(context.scene, "material")
        row = col.row()
        row.prop(context.scene, "my_enum")


        col.operator('object.search_popup', text='Search Name From Database')
        col.operator('opr.object_renamer_operator', text='Rename')

# == MAIN ROUTINE
CLASSES = [
    MYCAT_OT_search_popup,
    ObjectRenamerOperator,
    ObjectRenamerPanel,
    
]

def register():
    for (prop_name, prop_value) in PROPS:
        setattr(bpy.types.Scene, prop_name, prop_value)
    
    for klass in CLASSES:
        bpy.utils.register_class(klass)

def unregister():
    for (prop_name, _) in PROPS:
        delattr(bpy.types.Scene, prop_name)

    for klass in CLASSES:
        bpy.utils.unregister_class(klass)
        

if __name__ == '__main__':
    register()

Another question if you know tho, when I select the material to rename in my Panel, can i make it shown in 3D View as it happens when you choose a material in View Layer and it shows you the material in the scene? Like below..

Simple Scene

$\endgroup$
3
  • $\begingroup$ Regarding your additional question, I don't think you understand how it works, it's not selecting the material when you click on it in the outliner, it's selecting the object this particular instance of the material is attached to. Do you want to select all objects that use the material when you select it in the panel ? $\endgroup$
    – Gorgious
    Dec 23, 2021 at 22:21
  • $\begingroup$ Hey, yes that works for me, do you know how to add it in my code? $\endgroup$
    – Yilmazz
    Jan 5, 2022 at 6:36
  • $\begingroup$ Replace Scene.material definition (l13) with ('material', bpy.props.PointerProperty(type=bpy.types.Material, name='Material', update=lambda self, context: [o.select_set(self.material.name in o.material_slots) for o in context.scene.objects])) (or ideally use an update method to do the same thing, that will shut up the warnings in the console) $\endgroup$
    – Gorgious
    Jan 5, 2022 at 8:06

1 Answer 1

1
$\begingroup$

Good news ! You have done 99% of the work :)

You just need to display the right search operator in your panel, and call your operator after having selected the right item in the search popup.

class MYCAT_OT_search_popup(bpy.types.Operator):
    bl_idname = "object.search_popup"
    bl_label = "Material Renamer"
    bl_property = "my_enum"

    my_enum: bpy.props.EnumProperty(items = items, name='New Name', default=None)

    def execute(self, context):
        context.scene.my_enum = self.my_enum  # Change the scene prop
        bpy.ops.opr.object_renamer_operator()  # Call the other operator afterwards
        return {'FINISHED'}

    def invoke(self, context, event):
        wm = context.window_manager            
        return wm.invoke_search_popup(self)

And your panel draw method :

def draw(self, context):
    col = self.layout.column()
    row = col.row()
    row.prop(context.scene, "material")
    row = col.row()
    row.prop(context.scene, "my_enum")
        
    # col.operator('opr.object_renamer_operator', text='Rename')  # Standard operator
    col.operator('object.search_popup', text='Rename')  # Display the search popup operator

I can't help but notice that your script renames the objects, not the actual material. Since the material is shared between objects, you don't even have to access any object in the scene. Also, you can get rid of the overhead of using a my_enum property on bpy.types.Scene, here's a refactored suggestion :

import bpy
import re
import json

    
items = (("Metal", "Metal", ""),
("Plastic", "Plastic", ""),
("Glass", "Glass", ""),
("Shadow", "Shadow", ""),
)

PROPS = [
    ('material', bpy.props.PointerProperty(type=bpy.types.Material, name='Material')),
]

# == OPERATORS
class MYCAT_OT_search_popup(bpy.types.Operator):
    bl_idname = "object.search_popup"
    bl_label = "Material Renamer"
    bl_property = "my_enum"

    my_enum: bpy.props.EnumProperty(items = items, name='New Name', default=None)
    
    @classmethod
    def poll(cls, context):
        return context.scene.material  # This prevents executing the operator if we didn't select a material

    def execute(self, context):
        material = context.scene.material
        material.name = self.my_enum
        return {'FINISHED'}

    def invoke(self, context, event):
        wm = context.window_manager
        wm.invoke_search_popup(self)
        return {'FINISHED'}


# == PANELS
class ObjectRenamerPanel(bpy.types.Panel):
    
    bl_idname = 'VIEW3D_PT_object_renamer'
    bl_label = 'Material Renamer'
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    
    
    def draw(self, context):
        col = self.layout.column()
        row = col.row()
        row.prop(context.scene, "material")

        col.operator('object.search_popup', text='Rename')  #Display the search popup operator

# == MAIN ROUTINE
CLASSES = [
    MYCAT_OT_search_popup,
    ObjectRenamerPanel,
    
]

def register():
    for (prop_name, prop_value) in PROPS:
        setattr(bpy.types.Scene, prop_name, prop_value)
    
    for klass in CLASSES:
        bpy.utils.register_class(klass)

def unregister():
    for (prop_name, _) in PROPS:
        delattr(bpy.types.Scene, prop_name)

    for klass in CLASSES:
        bpy.utils.unregister_class(klass)
        

if __name__ == '__main__':
    register()

enter image description here

Note : When using context in a draw or execute method, it is advised to use the one passed as attribute, not bpy.context otherwise you won't be able to override your operator at a later point.

Also Note : Two different materials can't share the same name, since they are accessed by name in a data container acting like a dictionary (unique distinct keys). So if you rename two materials with the same name, the new one will be renamed with a suffix eg. Metal.001.

$\endgroup$
5
  • $\begingroup$ Hello, Thanks a lot, really close to what I want, but not exactly. So I want a second row that shows the items EnumProperty with a search capability, because I want to make sure I selected true item and then push "Rename" to finish the process. Also you are right about the changing material name, after this search capability, I will focus on that. $\endgroup$
    – Yilmazz
    Dec 21, 2021 at 11:04
  • $\begingroup$ Oh so scratch the second part and simply use context.scene.my_enum = self.my_enum in the execute method, but do not write bpy.ops.opr.object_renamer_operator()afterwards $\endgroup$
    – Gorgious
    Dec 21, 2021 at 14:26
  • $\begingroup$ Hey, if you get the time, can you share the full new code, I am stil at that 99 percent and could not do 100. I believe I am doing the changes you said, but not able to make it work. Maybe I am too lost in blender python API.. $\endgroup$
    – Yilmazz
    Dec 21, 2021 at 15:26
  • $\begingroup$ Sure. Starting with what you had in your question, replace self.report({'INFO'}, "You've selected: %s" % self.my_enum) by context.scene.my_enum = self.my_enum and add col.operator('object.search_popup', text='Choose Name') before col.operator('opr.object_renamer_operator', text='Rename') Tell me if you still can't make it work I'll update the full script $\endgroup$
    – Gorgious
    Dec 21, 2021 at 16:24
  • $\begingroup$ Hey, I did it, it worked and I shared the code up in the question if anyone needs in future. I also asked another question, if you wanna check it out. Thanks a lot! $\endgroup$
    – Yilmazz
    Dec 22, 2021 at 12:33

You must log in to answer this question.

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