1
$\begingroup$

I have hundreds of blend files containing assets. I moved the directory containing the textures. "Find missing files" works fine, but it would be quite some work to repeat this step for all files. Is there a way to perform this operation within a python script?

Iterating over all materials and replacing the path would theoretically work, but I would prefer this inbuilt feature, when possible.

Related to Find Missing Assets automatically? but as script and no autostart.

$\endgroup$
2
  • 1
    $\begingroup$ Will bpy.data.images[i].filepath help you? .source also tells you if the image is a file or not. $\endgroup$
    – TheLabCat
    Nov 29, 2022 at 12:11
  • 1
    $\begingroup$ @TheLabCat thanks, this will definitely come in handy $\endgroup$
    – Luke100000
    Nov 29, 2022 at 20:50

1 Answer 1

2
$\begingroup$

Based on @TheLabCat hints I wrote a python script searching and relinking missing files for all blend files. In case anyone else has a similar issue, feel free to copy and adapt.

Called as usual: blender --background --python ./theScript.py

import bpy
import glob
import logging
import os

logger = logging.getLogger(__name__)
logging.root.setLevel(logging.NOTSET)

# Dump to file
fh = logging.FileHandler("log.log")
fh.setLevel(logging.NOTSET)
logger.addHandler(fh)

# Blend files
blends = [
    "/YourRoot/**/*.blend",
]

# Directories (glob patterns) to look for textures
directories = [
    "/TexturesRoot/**/*.*",
]

files_lookup = {}
for dir in directories:
    for p in glob.glob(dir, recursive=True):
        files_lookup[os.path.basename(p)] = p

# And an additional set of exceptions (old file: new file)
replaces = {
    "old.png": "new.png",
}


def process(filepath):
    bpy.ops.wm.open_mainfile(filepath=filepath)
    os.chdir(os.path.dirname(filepath))

    dirty = False
    for image in bpy.data.images.values():
        if image.source == "FILE":
            # Make absolute
            if image.filepath[:2] == "//":
                path = os.path.abspath(image.filepath[2:])
            else:
                path = image.filepath

            # Check if file does not exist
            if not os.path.exists(path):
                name = os.path.basename(path)
                if image.filepath in replaces:
                    logger.info(
                        f"File {image.filepath} replaced with {replaces[image.filepath]}."
                    )
                    image.filepath = "//" + os.path.relpath(replaces[image.filepath])
                    dirty = True
                elif name in files_lookup:
                    image.filepath = "//" + os.path.relpath(files_lookup[name])
                    logger.info(f"File {image.filepath} found.")
                    dirty = True
                else:
                    logger.error(f"File {image.filepath} not found!")

    if dirty:
        bpy.ops.wm.save_as_mainfile(filepath=bpy.data.filepath)


if __name__ == "__main__":
    for dir in blends:
        for file in glob.glob(dir, recursive=True):
            process(file)

Not the prettiest solution but it did the job :)

$\endgroup$

You must log in to answer this question.

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