Script to remove duplicated meshes and materials
NathanR2015 started this discussion in Model Export and Render

Hi, I noticed that if I import multiple zmbx files to the same Blender file, I often end up with multiple copies of the materials (e.g. mb😒olid.21, mb😒olid.21.001, mb😒olid.21.002) and the meshes ("20482.001", "20482.003"). So I have hacked together a little python script that will remap the meshes and materials, leaving the duplicate materials as orphan data that will auto-delete when the Blender file is closed.

To use this, just copy the script at the end of this post into the "Scripting" tab of Blender. Make sure you keep the correct line indentation! Then click "Run Script".

The script carries a few dangers:
If you make local modifications to the Mecabricks materials for one (but not all) of the imported zmbx files, then these changes could be lost in the material reshuffle
If you create custom meshes or materials with names that are similar to the names of Mecabricks meshes or materials., these could get replaced with Mecabricks objects.
*** Always backup your blender file before you run this script!!! ***
*** You use this script entirely at your own risk ***

(P.S. This is my first python script in several years, and my first ever attempt at a script for Blender. Any suggestions for improving it would be appreciated!)

Python script:

#Mecabricks multi-import cleanup
#by NathanR2015

#If multiple Mecabricks zmbx files are imported into a single scene, there may be 
#several duplicated materials and meshes. This script cleans up the blender file
#by remapping the materials and meshes. 
#For example: 
#  Materials mb:solid.21.001, mb:solid.21.002, all become mb:solid.21.
#  Meshes "20482.001", "20482.003" both get changed to "20482.001" if they share the 
#  same material (the mesh+material combination must be unique)
#WARNING! If you make local modifications to Mecabricks materials or meshes, then 
#run this script, your changes may be lost.  Also, materials or meshes with names
#matching the Mecabricks naming scheme may be misidentified and remapped.
#ALWAYS BACKUP YOUR FILE BEFORE RUNNING THIS SCRIPT!!!


# Loop over every object in the blender file, and remove any duplicated materials.
# e.g. mb:solid.21.001, mb:solid.21.002, all get replaced with mb:solid.21.
# The remaining materials are now unused and will auto-delete when the blender file is closed/reopend.
# Search for duplicate meshes, if one is found (same mesh, same material, reassign it

from collections import defaultdict
import bpy




# NOTE: This will only check mb materials
# WARNING! If you have modified a mecabricks material, the changes will be lost!
for obj in bpy.data.objects:
    for slot in obj.material_slots:

        #Ignore mb decorations, they need to be unique due to possible custom textures
        if slot.name == "mb:decoration": 
            continue
    
        # Ignore anything that is not a mecabricks material
        if not (slot.name.startswith(("mb:chrome", "mb:glitter", "mb:metal", "mb:milky", "mb:pearlescent", "mb:rubber", "mb:solid", "mb:speckle", "mb:transparent"))):
            continue
        
        #Split name by the "." to get base name and number of duplicate
        part = slot.name.rpartition('.')
        if not (part[2].isnumeric()):
            continue
        
        #Try to remap numbered material to a non-numbered material
        if part[0] in bpy.data.materials:
            slot.material = bpy.data.materials.get(part[0])
        else:
            #Problem - the non-numbered material doesn't exist, try to find a numbered material instead
            for num in range(1, int(part[2])):
                name = part[0] + "." + str(num).rjust(3, '0')
                if name in bpy.data.materials:
                    slot.material = bpy.data.materials.get(name)
                    break
                



#Construct a dictionary of mesh names to make script faster
#Key = base name, List = All numbered names, e.g '20482': ['20482.001', '20482.003']
brickIDs = defaultdict(list)
for mesh in bpy.data.meshes:
    #Ignore orphan meshes
    if mesh.users == 0:  
        continue

    #Ignore meshes with "uv" in name (decorated elements)
    part = mesh.name.rpartition('.')
    if not part[0].isnumeric(): 
        continue

    brickIDs[part[0]].append(mesh.name)
 

#Loop over each mesh, see if there is a lower number that it can be assigned to.
#Brick ID and assigned material must match.
#Note, this assumes that there is no mesh name without a number.
#This should always be the case as mesh "20482" is orphaned data.
for brickID in brickIDs:
    for mesh in brickIDs[brickID]:
        matname = bpy.data.meshes[mesh].materials.values()
        part = mesh.rpartition('.')
        
        #Loop over every mesh number up this point, if there is a match then reassign mesh
        for num in range(1, int(part[2])):
            testname = part[0] + "." + str(num).rjust(3, '0')
            if testname in bpy.data.meshes:
                if matname == bpy.data.meshes[testname].materials.values():
                    bpy.data.meshes[mesh].user_remap(bpy.data.meshes[testname].id_data)
                    break

========================================

2 replies · Page 1 of 1

Thanks for this! Definitely keeping it in mind shall I ever want to import several MB files after another. 👍

Sorry to bump such an old thread, but here is a revised version of the script that is designed to work with the current Mecabricks Blender addon, and it's updated naming scheme for meshes and materials.

Previous safety warnings still stand, this script could severely mess up your Blender file if you have made local modifications to mecabricks materials, or if you have custom materials that appear to be duplicates (e.g. if you had two different materials named sand.001 and sand.002, one of these will get erased)

Always make a backup before you run!

#Mecabricks multi-import cleanup
#by NathanR2015

# If multiple Mecabricks zmbx files are imported into a single scene, there may be 
# several duplicated materials and meshes. This script cleans up the blender file
# by remapping the materials and meshes. 
# For example: 
#   Materials mb:o:64:191.001, mb:o:64:191.002, all become mb:o:64:191.
#   Meshes "mb:o:1756:21.001", "mb:o:1756:21.001" both get changed to "mb:o:1756:21.001"
#   if they share the same material (the mesh+material combination must be unique)

# WARNING! If you make local modifications to Mecabricks materials or meshes, then 
# run this script, your changes may be lost.  Also, materials or meshes with names
# matching the Mecabricks naming scheme may be misidentified and remapped.

# ALWAYS BACKUP YOUR FILE BEFORE RUNNING THIS SCRIPT!!!

from collections import defaultdict
import bpy

# Loop over every object in the blender file, and remove any duplicated materials.
# The remaining materials are now unused and will auto-delete when the blender file is closed/reopend.
for obj in bpy.data.objects:
    for slot in obj.material_slots:
        
        #Not much safety, but should ensure that only mecabricks materials are remapped
        if not (slot.name.startswith("mb:")):
            continue
        
        #Split name by the "." to get base name and number of duplicate
        part = slot.name.rpartition('.')
        if not (part[2].isnumeric()):
            continue
        
        #Try to remap numbered material to a non-numbered material
        if part[0] in bpy.data.materials:
            slot.material = bpy.data.materials.get(part[0])
        else:
            #Problem - the non-numbered material doesn't exist, try to find a numbered material instead
            for num in range(1, int(part[2])):
                name = part[0] + "." + str(num).rjust(3, '0')
                if name in bpy.data.materials:
                    slot.material = bpy.data.materials.get(name)
                break


#Construct a dictionary of mesh names to make script faster
#Key = base name, List = All numbered names, e.g '20482': ['20482.001', '20482.003']
brickIDs = defaultdict(list)
for mesh in bpy.data.meshes:
    #Ignore orphan meshes
    if mesh.users == 0:  
        continue

    #Ignore meshes with "uv" in name (decorated elements)
    part = mesh.name.rpartition('.')
    if not part[0].isnumeric(): 
        continue

    brickIDs[part[0]].append(mesh.name)
 

#Loop over each mesh, see if there is a lower number that it can be assigned to.
#Brick ID and assigned material must match.
#Note, this assumes that there is no mesh name without a number.
#This should always be the case as mesh "20482" is orphaned data.

for brickID in brickIDs:
    for mesh in brickIDs[brickID]:
        matname = bpy.data.meshes[mesh].materials.values()
        part = mesh.rpartition('.')
        
        #Loop over every mesh number up this point, if there is a match then reassign mesh
        for num in range(1, int(part[2])):
            testname = part[0] + "." + str(num).rjust(3, '0')
            if testname in bpy.data.meshes:
                if matname == bpy.data.meshes[testname].materials.values():
                    bpy.data.meshes[mesh].user_remap(bpy.data.meshes[testname].id_data)
                    break
Advertising
2 participants
Avatar of NGCHunter2

LEGO, the LEGO logo, the Minifigure, and the Brick and Knob configurations are trademarks of the LEGO Group of Companies. ©2024 The LEGO Group.

Mecabricks, the Mecabricks logo and all content not covered by The LEGO Group's copyright is, unless otherwise stated, ©2011-2024 Mecabricks.