4
$\begingroup$

I read this first to get an idea on assigning materials.

How to assign a new material to an object in the scene from Python?

I wrote a script to replace one material with another. It seems to work on an active object but does not replace materials on selected objects if I loop through them. I do not know if it is because the rest of the objects are not active or there is another reason.

How do I correctly replace materials with others?

Code is posted below:

import bpy



def replace_material(object,old_material,new_material):
    """
    replace a material in blender.
    params:
        ob - object for which we are replacing the material
        old_material - The old material name as a string
        new_material - The new material name as a string
    """
    ob = object
    om = bpy.data.materials[old_material]
    nm = bpy.data.materials[new_material]

    print ("ob is ",ob)
    print ("om is", om)
    print ("nm is", nm)
    materialnum = len (ob.data.materials)
    print ("materialnum is", materialnum)

    for i in range(0,materialnum):
        # test if the old material is in the slot and replace it
        print ("running ", i)
        if ob.data.materials[i] == om:
            print ("match found", ob.data.materials[i])
            ob.data.materials[i] == nm



def main ():
    #get a list of objects selected
    selected = bpy.context.selected_objects
    active = bpy.context.active_object
    for obj in selected:
        replace_material(obj,'DEFAULT BASE','Default Base')



main()

Sample blend here: http://www.pasteall.org/blend/42037

$\endgroup$
7
  • $\begingroup$ This script doesn't deal with 'Object' material links, for the objects this fails on, can you check the link option is set to Data and not Object ? (to the right of the material name.) $\endgroup$
    – ideasman42
    Commented May 25, 2016 at 6:55
  • $\begingroup$ I checked. I used select linked to filter objects my material. Then I selected the objects and checked all of them the material is a "Data" material not "Object" I do not know the difference between the two. It appears by default all materials are "Data" $\endgroup$
    – Aiena
    Commented May 25, 2016 at 6:57
  • $\begingroup$ If you iterate the material slots rather than the data.materials you will bypass needing the Data/Object link test. slots = [s for s in obj.material_slots if s.material.name == old_material] will give you a list of the material slots with old material, which you can then set with s.material = bpy.data.materials.get(new_material). (nb get will return None if there is no material of that name, whereas materials[name] will throw an error) $\endgroup$
    – batFINGER
    Commented May 25, 2016 at 7:08
  • $\begingroup$ But since all materials are linked as "Data" why is the above failing ? $\endgroup$
    – Aiena
    Commented May 25, 2016 at 7:16
  • 1
    $\begingroup$ Not sure its worth keeping this question open, since issue was a typo $\endgroup$
    – ideasman42
    Commented May 25, 2016 at 9:56

2 Answers 2

7
$\begingroup$

Ok from the above I noticed there are possibly 2 approaches to the same problem.

1) is iterate through the list of available materials linked as data and replace the material. This will not work if materials are linked by Object.

Sample code below:

import bpy

def replace_material(object,old_material,new_material):
    """
    replace a material in blender.
    params:
        object - object for which we are replacing the material
        old_material - The old material name as a string
        new_material - The new material name as a string
    """
    ob = object
    om = bpy.data.materials[old_material]
    nm = bpy.data.materials[new_material]
    materialnum = len(ob.data.materials)

    for i in range(0,materialnum):
        # test if the old material is in the list of used materials and replace it
        print ("running ", i)
        if ob.data.materials[i] == om:
            print ("match found", ob.data.materials[i])
            ob.data.materials[i] = nm

2) Iterate over material slots and replace the material in the slot of the object. This as batFINGER pointed out is a solution wherein whether the material is linked to the Object or Data is irrelevant and hence probably a better solution.

import bpy

def replace_material(object,old_material,new_material):
    """
    replace a material in blender.
    params:
        object - object for which we are replacing the material
        old_material - The old material name as a string
        new_material - The new material name as a string
    """
    ob = object
    om = bpy.data.materials[old_material]
    nm = bpy.data.materials[new_material]
    # Iterate over the material slots and replace the material
    for s in ob.material_slots:
        if s.material.name == old_material:
            s.material = nm

Notes:

1) The above samples do not do any error handling the script will just fail if the materials do not already exists. I wanted this kind of behavior.

2) For brevity sake I have posted only definition of the functions themselves this function can be used for a single object or along with looping to work with several objects. An example of such is shown in the main() function in the question.

Usage: Suppose you have an object named "Bottle" 2 materials "Red paint" "Blue rubber" and you want to replace the material "Red Paint" with "Blue rubber"

then you would use:

obj = bpy.data.objects["Bottle"]
replace_material(obj,"Red paint","Blue rubber")
$\endgroup$
1
  • 1
    $\begingroup$ Could also do while oldmat in obj.material_slots: obj.material_slots[oldmat] = bpy.data.materials.get(newmat) $\endgroup$
    – batFINGER
    Commented May 25, 2016 at 16:14
2
$\begingroup$

The script had a typo, heres a working version.

import bpy


def replace_material(obj, obj_data, mat_src, mat_dst):
    """
    replace a material in blender.
    """
    print("ob is", obj)
    for i in range(len(obj_data.materials)):
        # test if the old material is in the slot and replace it
        if obj_data.materials[i] == mat_src:
            print("match found", ob.data.materials[i])
            obj_data.materials[i] = mat_dst

def main():
    # get a list of objects selected
    selected = bpy.context.selected_objects
    active = bpy.context.active_object

    mat_src = bpy.data.materials['DEFAULT BASE']
    mat_dst = bpy.data.materials['Default Base']

    for obj in selected:
        obj_data = obj.data
        if obj_data and hasattr(obj_data, "materials"):
            replace_material(obj, obj_data, mat_src, mat_dst)


main()
$\endgroup$
2
  • $\begingroup$ Huh? Isn't obj_data.materials[i] == mat_dst the same typo and should be obj_data.materials[i] = mat_dst? $\endgroup$
    – Samoth
    Commented May 25, 2016 at 10:10
  • $\begingroup$ Kind of in a Dilemma I wish I could accept both $\endgroup$
    – Aiena
    Commented May 25, 2016 at 11:04

You must log in to answer this question.

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