Yeah i didnāt know either until Secrop send me a small sample script, thereās not that much to it, you pretty much create a nodegroup and then hook it up to some ui with some python. donāt think thereās any tutorials on it, best just to jump in the deep end and mess with some sample code. Hereās a relatively simple one that makes a mix node for cycles with unlimited inputs.
bl_info = {
"name": "PicSwitch",
"author": (
"LazyDodo"
),
"version": (0, 0, 1,0),
"blender": (2, 7, 8),
"location": "Nodes > Add nodes",
"description": "Pic Switch Cycles Node",
"warning": "",
"wiki_url": "",
"category": "Node"}
import bpy,nodeitems_utils
from nodeitems_utils import NodeCategory, NodeItem
class ShaderNode_picswitch(bpy.types.NodeCustomGroup):
bl_name='ShaderNode_picswitch'
bl_label='pic_switch'
bl_icon='NONE'
def treename(self):
return self.name + '_node_tree_'+str(self.inputCount)+str(self.clamp)
def mypropUpdate(self, context):
self.getNodetree(self.treename())
return None
inputCount = bpy.props.IntProperty(name = "Inputs" , default=3, min=2, soft_max=15, update=mypropUpdate)
clamp = bpy.props.BoolProperty(name="Clamp", update=mypropUpdate)
def init(self, context):
self.inputCount=3
self.getNodetree(self.treename())
def draw_buttons(self, context, layout):
layout.label("Node settings")
row = layout.row(align=True)
row.alignment = 'EXPAND'
row.prop(self, "inputCount")
row = layout.row(align=True)
row.alignment = 'EXPAND'
row.prop(self, "clamp")
def value_set(self, obj, path, value):
if '.' in path:
path_prop, path_attr = path.rsplit('.', 1)
prop = obj.path_resolve(path_prop)
else:
prop = obj
path_attr = path
setattr(prop, path_attr, value)
def createNodetree(self, name, count) :
self.Tmpnode_tree = bpy.data.node_groups.new(name, 'ShaderNodeTree')
#Nodes
self.addNode('NodeGroupInput', { 'name':'GroupInput' })
self.addNode('NodeGroupOutput', { 'name':'GroupOutput' })
self.addNode('ShaderNodeMath', { 'name':'LimitMin' ,'inputs[1].default_value':0 ,'operation':'MAXIMUM' })
self.addNode('ShaderNodeMath', { 'name':'LimitMax' ,'inputs[1].default_value':count-1 ,'operation':'MINIMUM' })
self.addNode('ShaderNodeMath', { 'name':'SubFactor' ,'inputs[1].default_value':1.000001 ,'operation':'MODULO' })
if (self.clamp):
print("clamp on!")
self.addNode('ShaderNodeMath', { 'name':'Clamp1' ,'inputs[1].default_value':0.5 ,'operation':'SUBTRACT' })
self.addNode('ShaderNodeMath', { 'name':'Clamp2' ,'operation':'ROUND' })
else:
print("clamp off!")
for i in range(1,count):
nodeName = 'mix'+str(i)+str(i+1)
self.addNode('ShaderNodeMixRGB', { 'name':nodeName })
for i in range(1,count-1):
self.addNode('ShaderNodeMath', { 'name':'Switch'+str(i) ,'inputs[1].default_value':i ,'operation':'GREATER_THAN' })
self.addNode('ShaderNodeMixRGB', { 'name':'SwitchMix'+str(i) })
#Sockets
self.addSocket(False, 'NodeSocketFloat', 'Factor')
for i in range(1,count+1):
self.addSocket(False, 'NodeSocketColor', 'Input'+str(i))
self.addSocket(True, 'NodeSocketColor', 'Color')
#Links
if (self.clamp):
self.innerLink('nodes["GroupInput"].outputs[0]', 'nodes["LimitMin"].inputs[0]')
self.innerLink('nodes["LimitMin"].outputs[0]', 'nodes["LimitMax"].inputs[0]')
self.innerLink('nodes["LimitMax"].outputs[0]', 'nodes["Clamp1"].inputs[0]')
self.innerLink('nodes["Clamp1"].outputs[0]', 'nodes["Clamp2"].inputs[0]')
self.innerLink('nodes["Clamp2"].outputs[0]', 'nodes["SubFactor"].inputs[0]')
else:
self.innerLink('nodes["GroupInput"].outputs[0]', 'nodes["LimitMin"].inputs[0]')
self.innerLink('nodes["LimitMin"].outputs[0]', 'nodes["LimitMax"].inputs[0]')
self.innerLink('nodes["LimitMax"].outputs[0]', 'nodes["SubFactor"].inputs[0]')
for i in range(1,count):
nodeName = 'mix'+str(i)+str(i+1)
self.innerLink('nodes["SubFactor"].outputs[0]', 'nodes["' + nodeName +'"].inputs[0]')
self.innerLink('nodes["GroupInput"].outputs['+ str(i) +']', 'nodes["' + nodeName +'"].inputs[1]')
self.innerLink('nodes["GroupInput"].outputs['+ str(i+1) +']', 'nodes["' + nodeName +'"].inputs[2]')
LastNode = 'mix12'
for i in range(1,count-1):
self.innerLink('nodes["LimitMax"].outputs[0]', 'nodes["Switch'+str(i)+'"].inputs[0]')
nodeName = 'mix'+str(i+1)+str(i+2)
self.innerLink('nodes["Switch' +str(i)+'"].outputs[0]', 'nodes["SwitchMix' + str(i) +'"].inputs[0]')
self.innerLink('nodes["' + LastNode +'"].outputs[0]', 'nodes["SwitchMix' + str(i) +'"].inputs[1]')
self.innerLink('nodes["' + nodeName +'"].outputs[0]', 'nodes["SwitchMix' + str(i) +'"].inputs[2]')
LastNode = 'SwitchMix' + str(i)
self.innerLink('nodes["' + LastNode +'"].outputs[0]', 'nodes["GroupOutput"].inputs[0]')
self.node_tree = self.Tmpnode_tree
def getNodetree(self, name):
if bpy.data.node_groups.find(name)==-1:
self.createNodetree(name,self.inputCount)
else:
self.node_tree=bpy.data.node_groups[name]
def addSocket(self, is_output, sockettype, name):
#for now duplicated socket names are not allowed
if is_output==True:
if self.Tmpnode_tree.nodes['GroupOutput'].inputs.find(name)==-1:
socket=self.Tmpnode_tree.outputs.new(sockettype, name)
elif is_output==False:
if self.Tmpnode_tree.nodes['GroupInput'].outputs.find(name)==-1:
socket=self.Tmpnode_tree.inputs.new(sockettype, name)
return socket
def addNode(self, nodetype, attrs):
node=self.Tmpnode_tree.nodes.new(nodetype)
for attr in attrs:
self.value_set(node, attr, attrs[attr])
return node
def getNode(self, nodename):
if self.Tmpnode_tree.nodes.find(nodename)>-1:
return self.Tmpnode_tree.nodes[nodename]
return None
def innerLink(self, socketin, socketout):
SI=self.Tmpnode_tree.path_resolve(socketin)
SO=self.Tmpnode_tree.path_resolve(socketout)
self.Tmpnode_tree.links.new(SI, SO)
def free(self):
if self.Tmpnode_tree.users==1:
bpy.data.node_groups.remove(self.Tmpnode_tree, do_unlink=True)
class ExtraNodesCategory(NodeCategory):
@classmethod
def poll(cls, context):
return (context.space_data.tree_type == 'ShaderNodeTree' and
context.scene.render.use_shading_nodes)
node_categories = [
ExtraNodesCategory("SH_PIC_SWITCH", "PicSwitch", items=[
NodeItem("ShaderNode_picswitch")
]),
]
def register():
bpy.utils.register_class(ShaderNode_picswitch)
nodeitems_utils.register_node_categories("SH_PIC_SWITCH", node_categories)
def unregister():
nodeitems_utils.unregister_node_categories("SH_PIC_SWITCH")
bpy.utils.unregister_class(ShaderNode_picswitch)
if __name__ == "__main__":
register()