Is it possible to create a dropdown shader socket using Python?
Whatâs a dropdown shader socket?
I need an âenumâ socket type
That doesnât seem to exist.
Adding a custom interface for a nodegroup is only possible by extending a ShaderNodeCustomGroup class.
With it, you have access to the draw_buttons()
call, and you can draw enum properties to the nodeâs layout.
sounds good, would you please show me an example?
This is just a small example⌠a special care is needed for dealing with generating/instancing/copying/freeing the private node_tree⌠but that depends from case to case.
import bpy
from bpy.types import ShaderNodeCustomGroup
from nodeitems_utils import NodeItem, register_node_categories, unregister_node_categories
from nodeitems_builtins import ShaderNodeCategory
class ShaderNodeColorDropDown(ShaderNodeCustomGroup):
bl_name='ShaderNodeColorDropDown'
bl_label='Color DropDown'
bl_icon='NONE'
cl_items=(('0', 'Black', 'BLACK'),
('1', 'White', 'WHITE'),
('2', 'Red', 'RED'),
('3', 'Green', 'GREEN'),
('4', 'Blue', 'BLUE'))
def color_update(self, context):
out = self.node_tree.nodes['RGB'].outputs[0]
if self.Colors == '0':
out.default_value = (0.0,0.0,0.0,1.0)
elif self.Colors == '1':
out.default_value = (1.0,1.0,1.0,1.0)
elif self.Colors == '2':
out.default_value = (1.0,0.0,0.0,1.0)
elif self.Colors == '3':
out.default_value = (0.0,1.0,0.0,1.0)
elif self.Colors == '4':
out.default_value = (0.0,0.0,1.0,1.0)
Colors: bpy.props.EnumProperty(default = '1', items = cl_items, name = "Colors_Enum", update = color_update)
def init(self, context):
#create the private node_group... just for illustration purposes!
ntname = '.' + self.bl_name + 'nodetree' #blender hides Nodegroups with name '.*'
self.node_tree = bpy.data.node_groups.new(ntname, 'ShaderNodeTree')
self.node_tree.nodes.new('NodeGroupInput')
o = self.node_tree.nodes.new('NodeGroupOutput')
s = self.node_tree.nodes.new('ShaderNodeRGB')
self.node_tree.outputs.new('NodeSocketColor', "Color")
self.node_tree.links.new(s.outputs[0], o.inputs[0])
s.outputs[0].default_value = (1.0,1.0,1.0,1.0)
def draw_buttons(self, context, layout):
layout.prop(self, 'Colors', text='')
thisMenu = [ShaderNodeCategory("MyNodes", "My Nodes", items=[NodeItem("ShaderNodeColorDropDown")])]
def register():
bpy.utils.register_class(ShaderNodeColorDropDown)
register_node_categories("MY_NODES", thisMenu)
def unregister():
#unregister_node_categories(thisMenu)
bpy.utils.unregister_class(ShaderNodeColorDropDown)
if __name__ == "__main__":
register()
This is very nice, but actually it dosnât work in a group node:
import bpy
from bpy.types import ShaderNodeCustomGroup
from nodeitems_utils import NodeItem, register_node_categories, unregister_node_categories
from nodeitems_builtins import ShaderNodeCategory
class ShaderNodeColorDropDown(ShaderNodeCustomGroup):
bl_name='ShaderNodeColorDropDown'
bl_label='Color DropDown'
bl_icon='NONE'
cl_items=(('0', 'Black', 'BLACK'),
('1', 'White', 'WHITE'),
('2', 'Red', 'RED'),
('3', 'Green', 'GREEN'),
('4', 'Blue', 'BLUE'))
def color_update(self, context):
out = self.node_tree.nodes['RGB'].outputs[0]
if self.Colors == '0':
out.default_value = (0.0,0.0,0.0,1.0)
elif self.Colors == '1':
out.default_value = (1.0,1.0,1.0,1.0)
elif self.Colors == '2':
out.default_value = (1.0,0.0,0.0,1.0)
elif self.Colors == '3':
out.default_value = (0.0,1.0,0.0,1.0)
elif self.Colors == '4':
out.default_value = (0.0,0.0,1.0,1.0)
Colors: bpy.props.EnumProperty(default = '1', items = cl_items, name = "Colors_Enum", update = color_update)
def init(self, context):
#create the private node_group... just for illustration purposes!
ntname = '.' + self.bl_name + 'nodetree' #blender hides Nodegroups with name '.*'
self.node_tree = bpy.data.node_groups.new(ntname, 'ShaderNodeTree')
self.node_tree.nodes.new('NodeGroupInput')
o = self.node_tree.nodes.new('NodeGroupOutput')
s = self.node_tree.nodes.new('ShaderNodeRGB')
self.node_tree.outputs.new('NodeSocketColor', "Color")
self.node_tree.links.new(s.outputs[0], o.inputs[0])
s.outputs[0].default_value = (1.0,1.0,1.0,1.0)
def draw_buttons(self, context, layout):
layout.prop(self, 'Colors', text='')
thisMenu = [ShaderNodeCategory("MyNodes", "My Nodes", items=[NodeItem("ShaderNodeColorDropDown")])]
def register():
bpy.utils.register_class(ShaderNodeColorDropDown)
register_node_categories("MY_NODES", thisMenu)
def unregister():
#unregister_node_categories(thisMenu)
bpy.utils.unregister_class(ShaderNodeColorDropDown)
if __name__ == "__main__":
register()
bpy.ops.mesh.primitive_monkey_add()
obj = bpy.context.object
mat = bpy.data.materials.new(name = 'Material')
mat.use_nodes = True
obj.active_material = mat
node_group = bpy.data.node_groups.new('node_group', 'ShaderNodeTree')
# works:
node_group.inputs.new('NodeSocketIntFactor','test 1')
# not working:
# node_group.inputs.new('ShaderNodeColorDropDown','test 2')
node_group_container = mat.node_tree.nodes.new('ShaderNodeGroup')
node_group_container.location = (-500,0)
node_group_container.width = 300
node_group_container.node_tree = node_group
A ShaderNodeCustomGroup is the same as a ShaderNodeGroup. They are just a visual interface for something (a node_group!).
They both reference a ShaderNodeTree that has a GroupInput and a GroupOutput (node_groups).
Both follow the same âdraw functionâ:
- [icon, label]
- [outputsockets from self.node_tree.outputs]
- [layout*]
#has draw_buttons()
- [inputsockets from self.node_tree.inputs]
The ShaderNodeGroup has just one layout.prop
in the draw_buttons() and itâs just a dynamic enum of all nodegroups compatible with the current NodeTreeType stored in bpy.data.node_groups
. Once chosen, it will become the self.node_tree
of that instance.
In a ShaderNodeCustomGroup you have the same thing⌠well, at least a node_tree property (that might be initially empty), but thereâs no graphic interface to change it. The draw_buttons()
is not showing anything. You are in charge!
You can still add a dynamic enum for existent nodegroups as in the ShaderNodeGroup if you like (see here).
And you can still add more options, expose Colorramp, CurveMaps, etc, directly from some Datablock with those elements, in your nodeâs GUI.
Itâs up to you.
(and of course: you can change the node_tree as you like!! )
Thank you so much! it helps me a lot!
I will discover it moreâŚ
I just checked you code and I couldnât realize how we can add custom options to a ShaderNodeGroup, would you please show me an example?
My goal is to have an EDITABLE group node with CUSTOM OPTIONS.
you need to describe exactly what kind of âCUSTOM OPTIONSâ do you really want for âEDITABLEâ node_groups, and what would those options change in the referenced node_treeâŚ
Because, if your plan is to add some control to the vanilla ShaderNodeGroup, it may be better to add that functionality to the NodeEditor instead.
So does this have an input socket?
ShaderNodeGroup:
We can visually edit node tree inside it (Editable). But we donât have enum socket (Custom option).
ShaderNodeCustomGroup
Is not editable, but we can have enum socket.
My goal is to have enum socket in ShaderNodeGroup.
Just to clarify, because using the wrong terminology is a step to chaosâŚ
Thereâs no such thing as an Enum Socket (at least in ShaderNodeTrees)!
A Socket is an input/output that can be connected to other sockets.
The DropDown is not a Socket, its a Layout.Prop that references an Enum Property.
If you were using a CustomNodeTree, you can create a Socket that draws as a DropDown, so long your NodeTree logic has an understanding of the values being passed to/from the socket. But in ShaderNodeTree thatâs impossible, because the Engines (Cycles and Eevee) wonât understand what the new socket represents, and they will fail to compile the shader.
In this view, adding Enum Properties to a ShaderNodeGroup needs to be translated to something that the engine can understand. In my example, changing the âColorsâ Enum changes the color of the RGB node inside the â.ShaderNodeColorDropDownnodetreeâ. And this works because the engines understand what a NodeSocketColor represents.
In the code you posted:
node_group.inputs.new('ShaderNodeColorDropDown','test 2')
youâre trying to add a ShaderNodeCustomGroup to a NodeTreeâs inputs⌠that doesnât work.
A ShaderNodeCustomGroup is not a Socket. Itâs a Node (with a Node_Tree inside, just like the ShaderNodeGroup)!
But if you change the ShaderNodeCustomGroup init()
, and move this logic inside the init call (replacing the code that is there):
node_group = bpy.data.node_groups.new('node_group', 'ShaderNodeTree')
node_group.inputs.new('NodeSocketIntFactor','test 1')
and then set the node_group
as the Nodeâs node_tree:
self.node_tree = node_group
it will work!
Now you can add this node into the material:
mat.node_tree.nodes.new("ShaderNodeColorDropDown")
thought the node will have just one input, and the Colors Enum will fail to reference the now inexistent RGB node inside this new Node_Tree.
And by the way⌠The Tab key can only be used with the NodeGroup node. Itâs still possible to edit the NodeTree if you load that NodeTree into a ShaderNodeGroup, but youâll loose your own layout (note that node_trees with a name started with a period are hidden, so renaming them will let you use the NodeTree in a normal NodeGroup.
By default, you shouldnât edit NodeTrees from NodeCustomGroups⌠To make this happening, you can create an Operator to push the NodeTree into the editor, and override the Tab key bindings⌠(not really desirable, but I use a similar approach in my newest version of the LoopNode, where the Tab pushes the step_nodetree into the Editor as if it was the nodetree inside).
So, summarising this:
- A Node_Tree is a Graph of interconnected nodes.
- A Node_Tree with GroupInput and GroupOutput is âconsideredâ a NodeGroup (but this is not the node itself, as itâs still a node_tree!)
- A Node has InputSockets, OutputSockets, a Layout and an Ext_Layout (side menu).
- A NodeGroup(the node!) is a Node that reads the inputs and outputs of a node_tree and draws it into its own interface. Itâs Layout is just a DynamicEnumProp to choose an existent Node_Tree.
- A NodeCustomGroup(also a node) does the same for the inputs and outputs of a Node_Tree, but letâs you have your own Layouts, plus listen to Editor changes, etc.
Thank You!
Letâs skip the Tab editing, and talk about the layout capabilities. How many controls we have for layout?
Ohh Yeah!! ⌠the layout is the same as any other class that has a drawing call:
https://docs.blender.org/api/current/bpy.types.UILayout.html
I just have another question that you may know why âNodeSocketBoolâ dosnât work as expected?
Cycles and Eevee donât use booleans, so they donât understand the NodeSocketBool.
Instead they use the NodeSocketFloat where (0 is False and 1 is True).
If you use a NodeSocketBool, you need to do your own translation to something the render engine can understand, in the same similar way as the EnumProperty.
For example, add a ValueNode to the NodeTree, and in the change the its defaiult value when the socket value changes (socket_value_update()
).
The problem is that you have to have your own logic if you connect anything to the socket, since that socket wonât be use by the shader compiler. That means you need to recursively check the source of that link until you get the original value.