3
$\begingroup$

Ok, I have several models that because various joining and un joining, have material slots that are there, but not assigned to any faces. Im exporting and would like these gone but there are too many to delete by hand:

enter image description here

Is there a script/way to get materials not assigned to a face to be eliminated?

$\endgroup$

5 Answers 5

3
$\begingroup$

Another method

  • Gets all the face indices for each material in slots

How to get indices of the faces to which a material has been applied, using Python?

  • If not assigned a slot, or slot is empty assign to material "None". Make sure there is no material named "None" in file

  • If more than one slot has same material it merges them

  • Will end up with a dictionary of materials by name to corresponding list of face indices, and those assigned to None.

  • Note if a material index is found on a face that is outside the range of slots it is also assigned to "None".

  • Make sure the slots list is the same length.

  • Reassign slots and faces materials to dictionary values.

Test script. As pointed out by @Ratt always test on a copy, or don't save until happy with result. Simplifies material slot list of context object to those used, and an empty slot for outliers.

from collections import defaultdict
import bpy
context = bpy.context
ob = context.object

me = ob.data
slots = ob.material_slots
def getmname(idx):
    if idx < len(slots):
        name = slots[idx].name
        name = name if name else "None"
    else:
        name = "None"
    return name
    
matdic = defaultdict(list)

for f in me.polygons:
    matdic[getmname(f.material_index)].append(f.index)
    

while len(slots) > len(matdic):
    ob.active_material_index = 1
    bpy.ops.object.material_slot_remove()
    
while len(slots) < len(matdic):
    bpy.ops.object.material_slot_add()
for i, (name, faces) in enumerate(matdic.items()):
    slots[i].material = bpy.data.materials.get(name)
    for f in faces:
        me.polygons[f].material_index = i

See also https://blender.stackexchange.com/a/208390/15543

$\endgroup$
2
  • $\begingroup$ This works great, but it will only work on objects of type mesh, bearing in mind that there are also objects of type curves, this script will not perform on them, as there are actually no object.data.polygons (Sorry, but I was looking for a solution, which I can't find) $\endgroup$
    – Noob Cat
    Commented Oct 4, 2020 at 14:45
  • 1
    $\begingroup$ Imagine for a curve it would be a matter of looking at for index, s in enumerated(curve_object.data.splines) for the case of curves. $\endgroup$
    – batFINGER
    Commented Oct 6, 2020 at 20:11
6
$\begingroup$

For future landers. This works on selected objects and its using new APIs so 2.80+ only.

import bpy

objs = [obj for obj in bpy.context.selected_objects if obj.type == 'MESH']
for obj in objs:
        bpy.context.view_layer.objects.active = obj
        bpy.ops.object.material_slot_remove_unused()
$\endgroup$
2
  • $\begingroup$ See blender.stackexchange.com/a/208390/15543 Can call this operator via code once to remove unused slots on all selected objects. $\endgroup$
    – batFINGER
    Commented Feb 3, 2021 at 19:22
  • $\begingroup$ Is it normal that it says {'CENCELLED'} once for each selected object? For me, the solution worked completely perfectly, but the CANCELLED confuses me every time $\endgroup$ Commented May 29, 2023 at 12:30
1
$\begingroup$

Here's a method per request that doesn't involve using an operator. It will remove all unused material slots from all mesh objects in the file.

import bpy

for obj in bpy.data.objects:
    material_indices = set()
    if not obj.material_slots or not obj.type == "MESH":
        continue
    for f in obj.data.polygons:
        material_indices.add(f.material_index)
    for i in range(len(obj.material_slots) - 1, -1, -1):
        if i not in material_indices:
            obj.data.materials.pop(index=i)

Thanks to this thread over on Blenderartists.

$\endgroup$
1
$\begingroup$

Is there a script/way to get materials not assigned to a face to be eliminated?

I guess you're open to non-scripting solutions, which are simpler for anybody else finding this page in the future. (I'm sure that your problem is long solved.) Click on the materials specials dropdown and select "Removed Unused Slots".

enter image description here

This feature hasn't always been around, not natively, and may not have existed in 2019. It was implemented from the "Materials Extras" addon, distributed with Blender, which I believe has existed for a long, long time.

$\endgroup$
0
$\begingroup$

I would recommend testing this on a copy of your file first but based on my limited testing the following script should work for Blender 2.7x. Note this has to check every polygon of every mesh based on your description and may take some time. Script removes material slots which are not assigned to any polygons of a mesh. If a specific material is not assigned to any polygon of any mesh it will become orphan data and removed automatically on file save.

import bpy

objs = [obj for obj in bpy.data.objects if obj.type == 'MESH']

for obj in objs:
    mats = obj.material_slots
    used_slots = []
    for poly in obj.data.polygons:
        if poly.material_index not in used_slots:
            used_slots.append(poly.material_index)
    for i, mat in reversed(list(enumerate((mats)))):
        if i not in used_slots:
            obj.active_material_index = i
            bpy.ops.object.material_slot_remove()
$\endgroup$

You must log in to answer this question.

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