Set object material in Python

Frustrated with how slow the material drop down is (sometimes hangs forever), I’m trying to create a quick dialog that lets me assign a material to an object with a listbox and no thumbnail previews.

I’m struggling to find the material index. I thought it would be the index of the material in the list, but the result seems to be pretty random.

I tried sc.doc.Materials.find() but it seems to always return -1. And sc.doc.RenderMaterials has no find methods.

Surprisingly, there was no rhinoscriptsyntax.GetMaterialIndex() either.

I would appreciate any pointers!

import rhinoscriptsyntax as rs
import Rhino as rc
import scriptcontext as sc
import Rhino.Geometry as rg
    
def AssignMaterialFast():
        ids = rs.GetObjects("Select object to change material", preselect=True)
        
        rmats = [rm for rm in sc.doc.RenderMaterials]
        materialNames = []
        for mat in rmats:
            materialNames.append(mat.Name)
        
        materialNames.sort()
        result = rs.ListBox(materialNames, "Select Material", "Assign Material")
        if result is None: return
        
        for i, mat in enumerate(rmats):
            if mat.Name == result:
                index = i
                break
        
        attr = rc.DocObjects.ObjectAttributes()
        attr.MaterialIndex = index
        attr.MaterialSource = rc.DocObjects.ObjectMaterialSource.MaterialFromObject
        
        for id in ids:
            sc.doc.Objects.ModifyAttributes(id, attr, True)
        sc.doc.Views.Redraw()

    if __name__ == "__main__":
        AssignMaterialFast()

Realized I can do it in a less fancy way:

def AssignMaterialFast():
ids = rs.GetObjects(“Select object to change material”, preselect=True)
if ids is None:
return

rmats = [rm for rm in sc.doc.RenderMaterials]
materialNames = []
for mat in rmats:
    materialNames.append(mat.Name)

materialNames.sort()
result = rs.ListBox(materialNames, "Select Material", "Assign Material")
if result is None: return

rs.SelectObjects(ids)
cmdString = "-_RenderAssignMaterialToObjects " + result + " "
rs.Command(cmdString, False)
1 Like

Thanks for posting your approach. I really struggled to get a usable solution to assign materials to blocks. Somehow I think I need to run the command twice to assign the material properly. Once with a no and then with a yes added to the command.

import rhinoscriptsyntax as rs
import scriptcontext as sc
import re


ids = rs.GetObjects("Select objects to change material", preselect=True)

rmats = [rm for rm in sc.doc.RenderMaterials]
materialNames = []
for mat in rmats:
    if not bool(re.search(r"\s", mat.Name)) and len(mat.Name) <= 10:
        materialNames.append(mat.Name)

materialNames.sort(reverse=True)
result = rs.ListBox(materialNames, "", "Materials")

rs.SelectObjects(ids)
cmdString = "-_RenderAssignMaterialToObjects " + result + " " "n"
rs.Command(cmdString, False)
cmdString = "-_RenderAssignMaterialToObjects " + result + " " "y"
rs.Command(cmdString, False)
rs.UnselectAllObjects()

My materials include several imported materials from clients files. They are all named Custom (*) and I eliminated these and materials with very long names from the list…