I want to create a script that reads multiple image files and creates image texture nodes for them in Cycles default ShaderNodeTree.
This script will be housed in it's own NodeTreeCategory called "Import".
The script is exactly like the NodeWrangler addon's Multiple Images import function. In NodeWrangler, executing this program opens up the File Browser, allows the user to select multiple images, and reads/imports them as texture nodes into the Active Node Tree.
Here is the working code:
#NOTE: Run this code first then use SHIFT-A, below, to add Custom Float node type.
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
# 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'
# 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 = 'Custom Float'
bl_icon = 'INFO'
def update_value(self, context):
self.outputs["Float"].default_value = self.some_value
self.update ()
some_value = bpy.props.FloatProperty(default=0.0, update = update_value)
def init(self, context):
self.outputs.new('NodeSocketFloat', "Float")
self.outputs["Float"].default_value = self.some_value
def update(self):
#Review linked outputs.
try:
out = self.outputs["Float"]
can_continue = True
except:
can_continue = False
if can_continue:
if out.is_linked:
# I am an ouput node that is linked, try to update my link.
for o in out.links:
if o.is_valid:
o.to_socket.node.inputs[o.to_socket.name].default_value = self.outputs["Float"].default_value #self.some_value
# Additional buttons displayed on the node.
def draw_buttons(self, context, layout):
layout.prop(self, "some_value",text = '')
# Optional: custom label
# Explicit user label overrides this, but here we can define a label dynamically.
def draw_label(self):
return "My Float"
### 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"),
]),
]
def register():
bpy.utils.register_class(MyCustomNode)
nodeitems_utils.register_node_categories("CUSTOM_NODES", node_categories)
def unregister():
nodeitems_utils.unregister_node_categories("CUSTOM_NODES")
bpy.utils.unregister_class(MyCustomNode)
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)
The part I want to modify is the MyCustomNode class. I plan on creating a class that looks like this:
class NWAddMultipleImages(Operator, ImportHelper):
"""Add multiple images at once"""
bl_idname = 'node.nw_add_multiple_images'
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'}
How do I create this class so that it shows up in the Import NodeCategory of the ShaderNodeTree?