2
$\begingroup$

I've made an addon that goes through all selected objects materials and sorts them in some way. My addon adds a button to the materials context menu to do just that.

enter image description here

Below is the simplified version of the addon.

What I've found is that it doesn't work - changing active object with bpy.context.view_layer.objects.active = o doesn't work (logs to prove it are below) and because of that bpy.ops.object.material_slot_move doesn't work properly too.

I think the solution would be to either

  1. To find a non bpy.ops way to rearrange material slots. Then there will be no need to change active object.
  2. To find a way to change active object from inside materials context menu (current it's seems impossible).
import bpy

bl_info = {
    "name": "simple addon",
    "author": "Andrej",
    "version": (0, 0, 1),
    "blender": (3, 0, 0),
    "location": "Material Properties > Material Specials",
    "doc_url": "",
    "tracker_url": "",      
    "category": "Material"
}


class SimpleAddon(bpy.types.Operator):
    bl_idname = 'object.simple_addon'
    bl_label = 'Simple addon'
    bl_description = "Simple addon"
    bl_options = {"REGISTER", "UNDO"}
  
    def execute(self, context):
        selected_objects = bpy.context.selected_objects
    
        for o in selected_objects:
            bpy.context.view_layer.objects.active = o
            print(f'Changing active object to {str(o)}. Result: {str(bpy.context.object)}')
            bpy.ops.object.material_slot_move(direction='UP')
            
        return {'FINISHED'}


def simple_addon_menu(self, context):
    layout = self.layout
    layout.operator_context = 'INVOKE_REGION_WIN'
    layout.separator()
    layout.operator(SimpleAddon.bl_idname, icon='NODE_COMPOSITING', text="Simple addon")
    layout.separator()
     
def register():
    bpy.utils.register_class(SimpleAddon)
    bpy.types.MATERIAL_MT_context_menu.prepend(simple_addon_menu)
 
def unregister():   
    bpy.utils.unregister_class(SimpleAddon)
    bpy.types.MATERIAL_MT_context_menu.remove(simple_addon_menu)
 
if __name__ == "__main__" :
    register()

Logs result:

Changing active object to <bpy_struct, Object("x0_yoke") at 0x0000024E8E231608>. Result: <bpy_struct, Object("x0_wings") at 0x0000024E8E231D08>
Changing active object to <bpy_struct, Object("x0_seats") at 0x0000024E8E232408>. Result: <bpy_struct, Object("x0_wings") at 0x0000024E8E231D08>
Changing active object to <bpy_struct, Object("x0_fuselage") at 0x0000024E8E233908>. Result: <bpy_struct, Object("x0_wings") at 0x0000024E8E231D08>
Changing active object to <bpy_struct, Object("x0_wings") at 0x0000024E8E231D08>. Result: <bpy_struct, Object("x0_wings") at 0x0000024E8E231D08>
$\endgroup$

2 Answers 2

4
$\begingroup$

One non-bpy.ops way to arrange material slots is to

  • somehow identify the object
  • make a list of the materials in the slots.
  • organize the list the way you want it.
  • Assign the materials into the slots using the list.

The basic thing is that if object is your object and material is your material, then object.material_slots[slot_number].material = material puts the named material into the slot.

I don't know how you want to ID the object, probably by selecting it from bpy.data.objects but once you do, you can create a list of materials from the slots via code like this pseudocode:

material_list = []
for slot in object.material_slots:
    material_list.append(slot.material) 

once you've reorganized material_list, then you put it back with code like this pseudcode:

for index, slot in enumerate(object.material_slots):
    slot.material = material_list[index]

There are other ways that take into account the order of the material. You could even go so far as to create a UIList instead of material_list and use its sort and filter functions to filter for you.

But this one is very few lines of code and since you rarely have very many materials on an object, it shouldn't have a noticable performance impact.

$\endgroup$
2
  • $\begingroup$ It will work but how do you preserve the active material for the object? Example of my experience in blender: 1) You have object with materials m3, m1, m2, m4 and m2 (2nd slot is active material); 2) You change the order of materials and now the order is m1, m2, m3, m4. Now m3 is the active material; 3) You try to change it to m2 by o.active_material = m2 and now the order of materials is m1, m2, m2, m4. $\endgroup$ Jan 31, 2022 at 15:45
  • $\begingroup$ To cover the case when the material list doesn't include, say, an empty material, break out of the reassignment for loop. for index, slot in enumerate(object.material_slots): if index >= len(material_list): break; slot.material = material_list[index] $\endgroup$
    – Ajay
    Jun 26, 2022 at 22:32
0
$\begingroup$

I think I've made a solution based on Marty Fouts answer. It works without ops but it adds more material_index juggling which could be even less perfomant than ops in some cases. And another thing you have to keep in mind is that you also need to reassign indices for object's faces to keep them valid.

Example of sortings materials in alphabetical order without ops:

selected_objects = [o for o in bpy.context.selected_objects if hasattr(o.data, 'materials')]

for o in selected_objects:
    # sort all materials in alphabetical order
    active_material = o.active_material
    current_materials = [ms.material for ms in o.material_slots]
    sorted_materials = sorted(current_materials, key=lambda m: m.lower())

    material_index_translation = dict()
    for m in current_materials:
        material_index_translation[current_materials.index(m)] = sorted_materials.index(m)

    for i, m in enumerate(sorted_materials):
        o.active_material_index = i
        o.active_material = m

    # preserve correct material for the object's faces
    for face in o.data.polygons:
        face.material_index = material_index_translation[face.material_index]

    o.active_material_index = sorted_materials.index(active_material)
$\endgroup$

You must log in to answer this question.

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