0
$\begingroup$

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


def get_nodes_links(context):
    space = context.space_data
    tree = space.node_tree
    nodes = tree.nodes
    links = tree.links
    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
        links = tree.links

    return nodes, links



# 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):
        layout.operator("node.nw_add_multiple_images", text="Select Images")

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


class NWAddMultipleImages(Operator, ImportHelper):
    """Add multiple images at once"""
    bl_idname = "node.nw_add_multiple_images"
    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, links = get_nodes_links(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

        if context.space_data.node_tree.type == 'SHADER':
            node_type = "ShaderNodeTexImage"
        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

            img = bpy.data.images.load(self.directory+fname)
            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"),
        #NodeItem("node.nw_add_multiple_images"),
        ]),
    ]


#def multipleimages_menu_func(self, context):
    #col = self.layout.column(align=True)
    #col.operator(NWAddMultipleImages.bl_idname, text="Import PBR")
    #col.separator()

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

    #bpy.types.NODE_MT_category_SH_NEW_TEXTURE.prepend(multipleimages_menu_func)
    #bpy.types.NODE_PT_category_SH_NEW_TEXTURE.prepend(multipleimages_menu_func)
    #bpy.types.NODE_MT_category_CMP_INPUT.prepend(multipleimages_menu_func)
    #bpy.types.NODE_PT_category_CMP_INPUT.prepend(multipleimages_menu_func)

    bpy.utils.register_module(__name__)

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

    #bpy.types.NODE_MT_category_SH_NEW_TEXTURE.remove(multipleimages_menu_func)
    #bpy.types.NODE_PT_category_SH_NEW_TEXTURE.remove(multipleimages_menu_func)
    #bpy.types.NODE_MT_category_CMP_INPUT.remove(multipleimages_menu_func)
    #bpy.types.NODE_PT_category_CMP_INPUT.remove(multipleimages_menu_func)

    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)
$\endgroup$
  • $\begingroup$ 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. $\endgroup$ – Nathan Craddock Jun 9 '16 at 15:08
1
$\begingroup$

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.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.