# Set image texture node label based upon characters in filename/filepath string

I want to import a set of 4 images. Upon import, I want to rename the image texture node that is created for each imported image to represent the type of image that is being loaded, i.e. (Diffuse, Normal, Spec, Roughness). How do I pass the filepath/filename of the image I am importing as a string, tell the script to look for certain characters in the passed string (COL, NRM, SPEC, ROUGH) and rename based upon the returned characters?

I would assume I can complete the rename function by using if-else statements. Any help or examples you could provide would be greatly appreciated.

The script below creates a new node category, places a custom node inside of it to import pbr textures, and allows you to import multiple import multiple images at one time into the node editor. I am looking to expand this script to include the functionality I have mentioned above.

Here is the working code:

import bpy
from bpy.types import NodeTree, Node, NodeSocket
import bpy, blf, bgl
from bpy.types import Operator, Panel, Menu
from bpy.props import FloatProperty, EnumProperty, BoolProperty, IntProperty, StringProperty, FloatVectorProperty, CollectionProperty
from bpy_extras.io_utils import ImportHelper
from mathutils import Vector
from math import cos, sin, pi, hypot
from os import path
from glob import glob
from copy import copy

def node_mid_pt(node, axis):
if axis == 'x':
d = node.location.x + (node.dimensions.x / 2)
elif axis == 'y':
d = node.location.y - (node.dimensions.y / 2)
else:
d = 0
return d

space = context.space_data
tree = space.node_tree
nodes = tree.nodes
active = nodes.active
context_active = context.active_node
# check if we are working on regular node tree or node group is currently edited.
# if group is edited - active node of space_tree is the group
# if context.active_node != space active node - it means that the group is being edited.
# in such case we set "nodes" to be nodes of this group, "links" to be links of this group
# if context.active_node == space.active_node it means that we are not currently editing group
is_main_tree = True
if active:
is_main_tree = context_active == active
if not is_main_tree:  # if group is currently edited
tree = active.node_tree
nodes = tree.nodes

# Implementation of custom nodes from Python
# Derived from the NodeTree base type, similar to Menu, Operator, Panel, etc.
class MyCustomTree(NodeTree):
bl_idname = "CustomTreeType"
bl_label = "Custom Node Tree"
bl_icon = "NODE"

# Defines a poll function to enable filtering for various node tree types.
class MyCustomTreeNode:
@classmethod
def poll(cls, ntree):
b = False
# Make your node appear in different node trees by adding their bl_idname type here.
if ntree.bl_idname == 'ShaderNodeTree': b = True
return b

# Derived from the Node base type.
class MyCustomNode(Node, MyCustomTreeNode):
'''A custom node'''
bl_idname = 'CustomNodeType'
bl_label = 'Import PBR Textures'
bl_icon = 'NODETREE'

# Additional buttons displayed on the node.
def draw_buttons(self, context, layout):

# Optional: custom label
# Explicit user label overrides this, but here we can define a label dynamically.
def draw_label(self):
return "Import PBR Textures"

bl_icon = 'NODETREE'
bl_label = 'Open Selected Images'
bl_options = {'REGISTER', 'UNDO'}
directory = StringProperty(subtype="DIR_PATH")
files = CollectionProperty(type=bpy.types.OperatorFileListElement, options={'HIDDEN', 'SKIP_SAVE'})

def execute(self, context):
nodes_list = [node for node in nodes]
if nodes_list:
nodes_list.sort(key=lambda k: k.location.x)
xloc = nodes_list[0].location.x - 220  # place new nodes at far left
yloc = 0
for node in nodes:
node.select = False
yloc += node_mid_pt(node, 'y')
yloc = yloc/len(nodes)
else:
xloc = 0
yloc = 0

elif context.space_data.node_tree.type == 'COMPOSITING':
node_type = "CompositorNodeImage"
else:
self.report({'ERROR'}, "Unsupported Node Tree type!")
return {'CANCELLED'}

new_nodes = []
for f in self.files:
fname = f.name

node = nodes.new(node_type)
new_nodes.append(node)
node.label = fname
node.hide = True
node.width_hidden = 100
node.location.x = xloc
node.location.y = yloc
yloc -= 40

node.image = img

# shift new nodes up to center of tree
list_size = new_nodes[0].location.y - new_nodes[-1].location.y
for node in new_nodes:
node.select = True
node.location.y += (list_size/2)
return {'FINISHED'}

### Node Categories ###
import nodeitems_utils
from nodeitems_utils import NodeCategory, NodeItem

# our own base class with an appropriate poll function,
# so the categories only show up in our target tree type
class MyNodeCategory(NodeCategory):
@classmethod
def poll(cls, context):
b = False
# Make your node appear in different node trees by adding their bl_idname type here.
if context.space_data.tree_type == 'ShaderNodeTree': b = True
return b

# all categories in a list
node_categories = [
# identifier, label, items list
MyNodeCategory("SOMENODES", "Import", items=[
NodeItem("CustomNodeType"),
]),
]

#col = self.layout.column(align=True)
#col.separator()

def register():
bpy.utils.register_class(MyCustomNode)
nodeitems_utils.register_node_categories("CUSTOM_NODES", node_categories)

bpy.utils.register_module(__name__)

def unregister():
nodeitems_utils.unregister_node_categories("CUSTOM_NODES")
bpy.utils.unregister_class(MyCustomNode)

bpy.utils.unregister_module(__name__)

def pre_frame_change(scene):
if scene.render.engine == 'CYCLES':
# Scan materials to see if I have a custom node within any of the trees.
for m in bpy.data.materials:
if m.node_tree != None:
for n in m.node_tree.nodes:
if n.bl_idname == 'CustomNodeType':
print(n.bl_idname)
# One of our custom nodes, let's update it.
# When we set the value that will trigger an update inside the node.
# Even if we change it to the same value it was.
v = n.some_value
n.some_value = v

if __name__ == "__main__":
register()
bpy.app.handlers.frame_change_pre.append(pre_frame_change)

• Okay, I'm taking a look at this really fast... Why do you need to create a node to import image texture nodes? Can't you just import from the Import PBR Textures operator you created in the toolshelf? Also, you are importing things like bgl and blf, which you don't need. – Nathan Craddock Jun 9 '16 at 15:08

So in order to scan a filename and return a string based on what is found, you need to create a function similar to this

def get_label_from_filename(filename):
# Here we anticipate what kind of map names users might use. For example COL, COLOR, DIF, DIFFUSE, etc all mean the same thing
# To add another category (say reflection) then do this 'reflection':["ref", "reflect", "reflection"]
# The list of synonyms can be as large as you want!
map_names = {'diffuse':["col", "color", "colour", "dif", "diffuse"]}

# Strip the extension and lowercase
filename = path.splitext(filename)[0].lower()

original = filename

# We will iterate over the dictionary and then over each of the
for key, synonyms in map_names.items():
for s in synonyms:
if s in filename:
filename = key.upper() # You could fill map_names with the uppercase version, but I find typing in lowercase easier

# If the filename remained the same, let the user know the image wasn't named correctly
if filename == original:
filename = "UNRECOGNIZED TYPE"

return filename


Then call the function right after you load the image in your operator. Then you can set node.label = get_label_from_filename().

I have commented the code, but here are some more detailed explanations of the more confusing parts.

map_names = {'diffuse':["col", "color", "colour", "dif", "diffuse"]}

This is a dictionary. It holds key-value pairs. You can add more to this by creating a new key, and pairing it with a list. The reason we are using a dictionary is because it allows us to have a single variable to reference when iterating through it later. You could create separate variables for each list, but that would be a bit more cumbersome to code.

for key, synonyms in map_names.items():

We assign the variable key and the variable synonyms to the key-value pairs found in map_names. We are required to call .items() when iterating over the dictionary. The next layer in the loop iterates over the synonym list for each key. Then if the synonym is found in the filename, we set the filename to the key in uppercase.

Now, this isn't the only way to do this. And this system is very user-dependent. If the user is too lazy to name the textures, this method will not work.