0
$\begingroup$

I got this really big scene that shares some image textures in some parts of the map. Is there any way I can just merge all of them into 1 master image? Like, if I had a material called "wood" and there was other 2 called "wood.001" and "wood.002", I want to make "wood" act as a master image.

This is how it looks in my blender scene:

This scene is really, really big, it has over 3400 textures, so I'd thank if the solution was a script or an addon. I know you can do this manually but it's honestly a pain.

$\endgroup$

2 Answers 2

0
$\begingroup$

You can easily create a list that stores all the common names or create a getter function, that for any form like my-image-name.png.XXX, returns the base name string my-image.png, and then iterate through all Image Textures and replace the names with the base name. Then optionally you can purge all the unused images to reduce the file size.

enter image description here

Here's the script:

import bpy

def get_base_name(name):
    p = name.rsplit('.', 1)
    return p[0] if len(p) == 2 and p[1].isdigit() and len(p[1]) == 3 else name

def get_base_names_list():
    base_names_set = set()
    for k in bpy.data.images.keys():
        base_names_set.add(get_base_name(k))
    return list(base_names_set)

def purge_unsed_data():
    bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=True, do_recursive=False)
    bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=True, do_recursive=True)
    bpy.ops.outliner.orphans_purge(do_local_ids=False, do_linked_ids=True, do_recursive=False)
    bpy.ops.outliner.orphans_purge(do_local_ids=False, do_linked_ids=True, do_recursive=True)
    bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=False, do_recursive=False)
    bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=False, do_recursive=True)

def replace_all_images_with_basenames():
    for mat in bpy.data.materials:
        if not mat.use_nodes:
            continue
        for node in mat.node_tree.nodes:
            if node.type == 'TEX_IMAGE' and node.image:
                replace = get_base_name(node.image.name)
                if replace == node.image.name:
                    continue
                print("replace", node.image.name, "with", replace)
                if replace in bpy.data.images:
                    node.image =  bpy.data.images[replace]
                else:
                    node.image.name = replace

base_names = get_base_names_list()
print("base names list:", base_names)

replace_all_images_with_basenames()
purge_unsed_data()

Notice that the images with same name as the base name don't get changed, only those with .XXX format

enter image description here

$\endgroup$
7
  • $\begingroup$ Hey, thanks for taking your time to help me out. I got this error in Blender when I executed it. If you got the time and obviously if you want to, I'd like to ask you if you could give it a check for me. Python: Traceback (most recent call last): File "C:\Users\Usuario\Documents\theyhaveapartyallright.blend\fakepngtorealpng.py.001", line 33, in <module> File "C:\Users\Usuario\Documents\theyhaveapartyallright.blend\fakepngtorealpng.py.001", line 28, in replace_all_images_with_basenames KeyError: 'bpy_prop_collection[key]: key "s_met_flm01_tt_d.png" not found' $\endgroup$
    – HESOYAM
    Sep 2, 2023 at 17:08
  • $\begingroup$ @HESOYAM ah i know why. because you have s_met_flm01_tt_d.png.001 without any s_met_flm01_tt_d.png. You incorrectly claimed in your question that you have the form wood, wood.001, wood.002, etc. when there actually is no wood for this unique case, but the code assumed there was also wood. i'll update the code, to make wood.001 the new wood. $\endgroup$ Sep 2, 2023 at 17:44
  • $\begingroup$ Great, thanks. Yeah it was my fault, had to clarify better. The image textures are listed as "wood.png.001" (for example) $\endgroup$
    – HESOYAM
    Sep 2, 2023 at 17:53
  • $\begingroup$ yeah any name will work be it wood.png.001 or wood.001. it was just problem that you didnt have the base name wood or wood.png while the code assumed that this master form existed. so now if it doesnt find any, it will just convert the first find, into the master form. $\endgroup$ Sep 2, 2023 at 17:55
  • 1
    $\begingroup$ Nevermind, I managed to fix it. Thanks for the help, you saved my project, literally! I had issues uploading to other platforms so it was impossible to continue. I really thank you! $\endgroup$
    – HESOYAM
    Sep 2, 2023 at 19:20
0
$\begingroup$

You can re-use the same image pretty much everywhere in Blender. You just have to select the same image data block wherever you are using the image. So for example if you use it in an image node in the shader editor, you click on the image icon left to the image name and choose the data block you want:

enter image description here

It works similarly in all other parts of the UI, where you can have images.

If you have thousands of images and fixing all of them manually is not practical, you could use Python to do that quicker. If the images in question were in material nodes, a Python script could be something like this:

import bpy

for m in bpy.data.materials:
    if m.node_tree is None:
        continue
    for n in m.node_tree.nodes:  
        if n.type != 'TEX_IMAGE':
            continue
        if "." in n.image.name[-4:] and n.image.name[-3:].isnumeric():
            try:
                n.image = bpy.data.images[n.image.name[:-4]]
            except: 
                print(f'could not set image "{n.image.name[:-4]}"')

You can paste this into new text block in the Text Editor and hit Run. This assumes all images with the same name are the same image. if that is not the case, this is obviously not going to work well and you would need to come up with another solution.

It is also possible to replace images in other parts of Blender file with Python, but you would need to adjust the script for your specific situation then.

For replacing materials you could run this:

import bpy

for o in bpy.context.scene.objects:
    for slot in o.material_slots:
        if slot.material is None:
            continue
        if "." in slot.material.name[-4:] and slot.material.name[-3:].isnumeric():
            if slot.material.name[:-4] in bpy.data.materials:
                slot.material = bpy.data.materials[slot.material.name[:-4]]
$\endgroup$
6
  • $\begingroup$ Hello, your script is the one that should work the best for me since it covers my case, I use material nodes and the materials that share name contain the same content. However, this is most definitely my fault and I'd like some assistance if you got the time. I execute the script and blender doesn't pop up any error, but the materials stay the same. Is there anything I'm doing wrong? This is how it looks in my scene after executing: prnt.sc/v7tGYPnP6SjG $\endgroup$
    – HESOYAM
    Sep 2, 2023 at 15:44
  • $\begingroup$ It doesn't touch materials, only images in the materials. $\endgroup$ Sep 2, 2023 at 16:25
  • $\begingroup$ All right, thanks for the clarification and for taking your time to answer. Appreciate it $\endgroup$
    – HESOYAM
    Sep 2, 2023 at 16:48
  • $\begingroup$ @HESOYAM See the updated answer. $\endgroup$ Sep 2, 2023 at 18:10
  • 1
    $\begingroup$ Nevermind, I managed to fix it. Thanks for the help, you saved my project, literally! I had issues uploading to other platforms so it was impossible to continue. I really thank you $\endgroup$
    – HESOYAM
    Sep 2, 2023 at 19:20

You must log in to answer this question.

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