Remapping Materials in Blender after Simplygon Processing

Disclaimer: The code in this post is written on version 10.1.11000.0 of Simplygon and Blender 3.5.1. In future versions of Blender or the Simplygon plug-in behaviours might look differently.

Introduction

Simplygon is using Blender glTF exporter/importer as an intermediate format when processing objects. The glTF importer creates new materials even if identical materials already exist in the scene, resulting in a duplication of materials when reduction processes brought back into Blender. To overcome this limitation and ensure material continuity, we can employ a Python script that remaps materials from the processed objects to their original counterparts. This script helps streamline the material workflow and maintain consistency in Blender projects. Let's dig into the script

Finding the Original Objects

Before remapping the materials, we need to identify the original objects that were processed by Simplygon. A regular expression-based pattern matching technique is utilized to extract the base name of the objects. By specifying a postfix pattern such as _LOD1, _LOD2, etc., the script identifies the base name from the processed objects' names. This base name corresponds to the original objects in the scene.

def find_original_object_names(post_fix):
    pattern = re.compile(fr"(.*){re.escape(post_fix)}\d+")
    original_object_names = []
    
    # Iterate through all objects
    for obj in bpy.data.objects:
        if obj.type == 'MESH':
            match = pattern.match(obj.name)
            if match:
                original_object_names.append(match.group(1))
    
    return original_object_names

Updating Material Slots

Once the original objects are identified, the script proceeds to update the material slots of the processed objects to match the materials of their respective original objects. It iterates through all the objects in the scene and checks if their names follow the pattern of the base name followed by the specified postfix. If a match is found, the material slots of the processed object are cleared, and the materials from the corresponding original object are copied over.

def update_material_slots(original_object_name, post_fix):
    original_obj = bpy.data.objects.get(original_object_name)
    if original_obj is None:
        print(f"Original object '{original_object_name}' not found.")
        return

    for obj in bpy.data.objects:
        if obj.type == 'MESH' and obj.name.startswith(original_object_name + post_fix):
            obj.data.materials.clear()

            for slot in original_obj.data.materials:
                obj.data.materials.append(slot)

Clearing Unused Materials

To further streamline the material setup, the script includes a step to clear unused materials. Since the glTF importer generates new materials even if identical materials already exist, it's a good idea to remove the redundant materials to avoid clutter. The script identifies the used materials by collecting the material names from the objects in the scene. Any material that has a postfix of .001 and is not associated with any object is considered unused and is subsequently removed from the materials collection.

def clear_unused_materials():
    objects = bpy.data.objects
    used_materials = set()
    
    for obj in objects:
        if obj.type == 'MESH':
            material_slots = obj.data.materials
            
            for slot in material_slots:
                if slot is not None:
                    used_materials.add(slot.name)
    
    materials = bpy.data.materials
    
    for material in materials:
        if material.name not in used_materials and material.name.endswith(".001"):
            print("Deleting {}".format(material.name))
            materials.remove(material)

Putting It All Together

To utilize the script and remap the materials, follow these steps:

Specify the postfix used in the processed objects' names. This is typically .001_LOD:

post_fix = ".001_LOD"

Find the original objects from the postfix:

original_object_names = find_original_object_names(post_fix)

Iterate through the original objects and update the material slots:

for original_object_name in original_object_names:
    update_material_slots(original_object_name, post_fix)

Clear the unused materials:

clear_unused_materials()

By incorporating this script into your Blender workflow, you can streamline the material management process and focus on the creative aspects of your 3D projects.

I hope you find this updated blog post helpful for remapping materials in Blender after Simplygon processing.

# Copyright (c) Microsoft Corporation. 
# Licensed under the MIT license.

import bpy
import re

# Returns the list of objects that was the base for the Simplygon process.
def find_original_object_names(post_fix):
    pattern = re.compile(fr"(.*){re.escape(post_fix)}\d+")  # Pattern to match the base name
    
    original_object_names = []
    
    # Iterate through all objects
    for obj in bpy.data.objects:
        if obj.type == 'MESH':
            match = pattern.match(obj.name)
            if match:
                original_object_names.append(match.group(1))
    
    return original_object_names

# Updates the material for all processed objects back to the original material.
def update_material_slots(original_object_name, post_fix):
    # Find the object object
    original_obj = bpy.data.objects.get(original_object_name)
    if original_obj is None:
        print(f"Original object '{original_object_name}' not found.")
        return

    # Iterate through all objects
    for obj in bpy.data.objects:
        if obj.type == 'MESH' and obj.name.startswith(original_object_name + post_fix):
            # Clear existing material slots in the derived object            
            obj.data.materials.clear()

            # Copy materials from the base object to the derived object
            for slot in original_obj.data.materials:
                obj.data.materials.append(slot)

    print("Materials updated successfully for {}{}".format(original_object_name, post_fix))


# Deletes all materials in the scene that aren't used and are likely generated when importing the Simplygon processed object.
def clear_unused_materials():
    # Get all the objects in the scene
    objects = bpy.data.objects
    
    # Create a set to store all used material names
    used_materials = set()
    
    # Iterate through each object in the scene
    for obj in objects:
        if obj.type == 'MESH':
            # Get the material slots of the object
            material_slots = obj.data.materials
            
            # Add the material names to the used_materials set
            for slot in material_slots:
                if slot is not None:
                    used_materials.add(slot.name)
    
    # Get all the materials in the scene
    materials = bpy.data.materials
    
    # Iterate through each material and remove unused ones
    for material in materials:
        if material.name not in used_materials and material.name.endswith(".001"):
            print("Deleting {}".format(material.name))
            materials.remove(material)
    
    print("Unused materials cleared.")

# The normal postfix after a Simplygon processed object have been imported. 
# If you run several processes in the same scene you might have to modify this.
post_fix = ".001_LOD"
# Find the original objects from the post fix
original_object_names = find_original_object_names(post_fix)
# Go through all the original objects and update the material slots.
for original_object_name in original_object_names:
    update_material_slots(original_object_name, post_fix)
# Clears all the generated materials. Some caution here if you have materials in the scene that isn't used
# and are postfixed ".001". That could lead to accidental deletion of materials you want to keep for some reason. 
clear_unused_materials()
⇐ Back to all posts

Request 30-days free evaluation license

*
*
*
*
Industry
*

Request 30-days free evaluation license

*
*
*
*
Industry
*