0
$\begingroup$

I have a folder with 750 texture folders, within each texture folder there are four files: jpg, png, exr, and x2. I can import these textures manually by going to the shader tab and pressing Ctrl+Shift+T with Node Wrangler enabled and then select all the files within a given texture folder. My goal with this post is to automate this so that I don't have to manually do this for all 750 texture folders.

Here is the code I have so far:

import bpy
import os

FOLDER_PATH = "/Users/torrinleonard/Desktop/Texture_Importer/Textures_Folder"
MATERIAL_OBJECT = "Material_Object"  # The name of an object used to only add materials to the scene and nothing else.


for texture_folder in os.listdir(FOLDER_PATH):
    if texture_folder != '.DS_Store':  # Make sure Blender is not importing MacOs .DS_Store files
        # Get folder path for each texture
        texture_path = os.path.join(FOLDER_PATH, texture_folder)

        # Create new material slot in material list and create new material:
        material_object = bpy.data.objects[MATERIAL_OBJECT]  # Set active object to variable
        material = bpy.data.materials.new(name=texture_folder)  # Set new material to variable
        material_object.data.materials.append(material)  # Add material to the object

        bpy.context.area.type = 'NODE_EDITOR'
        bpy.context.area.ui_type = 'ShaderNodeTree'

        # Prepare node setup of material:
        material.use_nodes = True
        material.node_tree.nodes.get("Principled BSDF").select = True  # Set nodes in material node tree to active

        textures = os.listdir(texture_path)
        first_jpg = ""
        files = []

        for texture in textures:
            if texture != '.DS_Store':  # Make sure Blender is not importing MacOs .DS_Store files
                if texture.endswith(".jpg"):
                    first_jpg = texture
                files.append(
                    {
                        "name": texture,
                        "name": texture
                    }
                )
        
        bpy.ops.node.nw_add_textures_for_principled(
            filepath=f"//Textures_Folder/metal_plate/{first_jpg}",
            directory=os.path.join(FOLDER_PATH, texture_folder),
            files=files
        )

It's pretty self explanatory but here is the recap: We have an object in our scene, "Material_Object", for each texture located in our Texture folder, we add a new material to that object's material list, and create a new material. This creates a Principled BSDF node in the Shader Editor.

When running this script inside Blender, the materials are created, the names are set, but for some reason the files are not applied to the Principled BSDF node as if I were to do it manually:

enter image description here

Why is this happening?

I believe it is due to the way Blender is selecting the Principled BSDF node. In the console it prints it successfully Selected that node:

enter image description here

However going to that materials node group we can see it is highlighted in orange, not white, which is the selection type we need to activate Ctrl+Shift+T, or in this case, bpy.ops.node.nw_add_textures_for_principled with Python. REMEMBER: This was selected with the above function material.node_tree.nodes.get("Principled BSDF").select = True:

enter image description here

To contrast that, here is what the selection looks like when you do it manually:

enter image description here

Below I've highlighted the code for nw_add_textures_for_principled in Node Wranglers, it appears to be looking for nodes.active:

enter image description here

I have spent hours trying to find out how to get this hidden 'active' state on a node with python, but with no luck. I've poured through the documentation and node.select is the only thing close to what I can find, but that's not selecting the Principled BSDF like you would do it manually to import textures with Ctrl+Shift+T.

My question: How do I apply this hidden 'active' state to a node so that I can import images to a materials Principled BSDF node using nw_add_textures_for_principled from Node Wrangler?

Is this the only way to bulk import textures? Am I missing some much better solution?

I'm using Blender 3.1.2 on MacOS 11.6.7 Big Sur to code/test this and Blender 3.2 on Windows 10 to execute it if that matters

$\endgroup$
9
  • 1
    $\begingroup$ material.node_tree.nodes.active = material.node_tree.nodes.get("Principled BSDF") ? docs.blender.org/api/current/… active is a property of the node tree's nodes construct, it's not tied to each and every node (Every node can be selected, but only one node at most can be active per tree at any given time) $\endgroup$
    – Gorgious
    Commented Jun 21, 2022 at 13:57
  • $\begingroup$ @Gorgious, that seems to be selecting the BSDF node correctly, however for some reason it changes the context away from the NODE_EDITOR/ShaderNodeTree: RuntimeError: Operator bpy.ops.node.nw_add_textures_for_principled.poll() failed, context is incorrect $\endgroup$ Commented Jun 21, 2022 at 14:13
  • $\begingroup$ @Torrinworx The operator needs to be executed in the shader editor, which your script does not do. You will either have to supply a custom context or alternatively write your own script to connect the textures with the Principled BSDF. You can take a look at Node Wrangler's implemention in 3.2\scripts\addons\node_wrangler.py NWAddPrincipledSetup. $\endgroup$ Commented Jun 21, 2022 at 14:18
  • $\begingroup$ The strange thing is, when I add print(f"AREA TYPE: {bpy.context.area.ui_type}") before the node wrangler line, the console prints the area as AREA TYPE: ShaderNodeTree $\endgroup$ Commented Jun 21, 2022 at 14:18
  • 1
    $\begingroup$ Also as a suggestion you could try and locate the part of the addon which creates the setup in the node wrangler addon and copy / paste it inside of your script, you'll even be able to tweak it to your liking and won't need to rely on operator overrides. $\endgroup$
    – Gorgious
    Commented Jun 21, 2022 at 15:28

1 Answer 1

2
$\begingroup$

I had a similar issue here, and finally got it resolved. The important but unintuitive thing is to set:

areas[0].spaces.active.node_tree = material.node_tree

This is the script that should work for you. Before you run it: make sure you test it first with a folder with only a few textures, maybe 2 or 3, because it takes a while for blender python to process.

import bpy
import os

FOLDER_PATH = "/Users/torrinleonard/Desktop/Texture_Importer/Textures_Folder/"
MATERIAL_OBJECT = "Material_Object"

#-----------------------------------------------------------------------------
#
# Starting in 3.2 context overrides are deprecated in favor of temp_override
# https://docs.blender.org/api/3.2/bpy.types.Context.html#bpy.types.Context.temp_override
#
# They are scheduled to be removed in 3.3
#
def use_temp_override():
    ''' Determine whether Blender is 3.2 or newer and requires
        the temp_override function, or is older and requires
        the context override dictionary
    '''
    version = bpy.app.version
    major = version[0]
    minor = version[1]
    if major < 3 or (major == 3 and minor < 2):
        return False
    return True

for texture_folder in os.listdir(FOLDER_PATH):
    if texture_folder == '.DS_Store':  # Make sure Blender is not importing MacOs .DS_Store files
        continue

    texture_path = os.path.join(FOLDER_PATH, texture_folder)
    object = material_object = bpy.data.objects[MATERIAL_OBJECT]
    material = bpy.data.materials.new(name=texture_folder)
    object.data.materials.append(material)

    object.active_material_index = len(object.material_slots) - 1

    material.use_nodes = True
    tree = material.node_tree
    nodes = tree.nodes

    material.node_tree.nodes['Material Output'].select = False
    material.node_tree.nodes['Principled BSDF'].select = True

    material.node_tree.nodes.active = material.node_tree.nodes.get("Principled BSDF")

    bpy.context.area.type = 'NODE_EDITOR'
    bpy.context.area.ui_type = 'ShaderNodeTree'
    
    textures = os.listdir(texture_path)
    files = []

    for texture in textures:
        if texture == '.DS_Store':
            continue

        files.append(
            {
                "name": texture,
                "name": texture
            }
        )
        
        filepath = texture_path + '/'
        directory = texture_path + '/'
        relative_path = True

        win = bpy.context.window
        scr = win.screen
        areas  = [area for area in scr.areas if area.type == 'NODE_EDITOR']
        areas[0].spaces.active.node_tree = material.node_tree
        regions = [region for region in areas[0].regions if region.type == 'WINDOW']

        if use_temp_override():
            with bpy.context.temp_override(window=win, area=areas[0], region=regions[0], screen=scr):
                bpy.ops.node.nw_add_textures_for_principled(
                    filepath=filepath,
                    directory=directory,
                    files=files,
                    relative_path=relative_path
                )
        else:
            override = {
                'window': win,
                'screen': scr,
                'area': areas[0],
                'region': regions[0],
            }

            bpy.ops.node.nw_add_textures_for_principled(
                override,
                filepath=filepath,
                directory=directory,
                files=files,
                relative_path=relative_path
            )
$\endgroup$
3
  • $\begingroup$ Could you maybe add just a few words explaining shortly what makes things work please? Just to make this answer reach more people :) $\endgroup$
    – Lauloque
    Commented Sep 17, 2022 at 15:59
  • 1
    $\begingroup$ @L0Lock sorry i didn't give more details. the thing is the important piece is this unintuitive thing you have to set areas[0].spaces.active.node_tree = material.node_tree. i always run into this same problem wondering why things don't work because of this missing line. $\endgroup$
    – Harry McKenzie
    Commented Jan 30 at 4:58
  • $\begingroup$ projects.blender.org/blender/blender/issues/99484 $\endgroup$
    – Harry McKenzie
    Commented Jan 31 at 11:57

You must log in to answer this question.

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