So I’m trying to write code that creates a custom set of Nodes for the ShaderEditor.
Code:
import bpy
def create_custom_node_group(self, context):
node_tree = bpy.data.node_groups.new('custom group', 'ShaderNodeTree')
node_tree.inputs.new('NodeSocketColor', 'Color')
node_tree.outputs.new('NodeSocketColor', 'Output')
group_in = node_tree.nodes.new('NodeGroupInput')
group_in.location = (-200, 0)
group_out = node_tree.nodes.new('NodeGroupOutput')
group_out.location = (200, 0)
mix_node = node_tree.nodes.new(type= 'ShaderNodeMixRGB')
mix_node.location = (0, 0)
mix_node.inputs[0].default_vaule = 0.6
mix_node.inputs[7].default_vaule = (0, 1, 0, 1)
link = node_tree.links.new
link(group_in.outputs[0], mix_node.inputs[6])
link(mix_node.outputs[0], group_out.inputs[0])
return node_tree
class NODE_OT_test(bpy.types.Operator):
bl_label = "Test"
bl_idname = "node.test_operator"
def execute(self, context):
group = create_custom_node_group(context)
context.active_object.active_material.use_nodes = True
node = context.active_object.active_material.node_tree.nodes.new('ShaderNodeGroup')
node.node_tree = bpy.data.node_groups[node_name]
node.use_custom_color = True
node.color = (0.5, 0.4, 0.7)
return {'FINISHED'}
The thing is, when I try to run the script, I get the following error:
AttributeError: 'ShaderNodeTree' object has no attribute 'inputs'
I would love to get help.
chen-pan
(chen pan)
December 29, 2023, 11:46am
2
if bl<4.0
def create_custom_node_group(self, context):
node_tree = bpy.data.node_groups.new('custom group', 'ShaderNodeTree')
......
group_in = node_tree.nodes.new('NodeGroupInput')
group_out = node_tree.nodes.new('NodeGroupOutput')
group_in.location = (-200, 0)
group_out.location = (200, 0)
input_socket = group_in.inputs.new('NodeSocketColor', 'Color')
output_socket = group_out.outputs.new('NodeSocketColor', 'Output')
node_tree.links.new(group_in.outputs["Color"], mix_node.inputs[1])
node_tree.links.new(mix_node.outputs[0], group_out.inputs["Output"])
return {'FINISHED'}
Hi GoldenDragon,
in Blender 4.0 the inputs and outputs got removed from the NodeTree
Now you have to add them to the NodeTree interface. Here’s a modified version of your function:
Code
def create_custom_node_group():
# Create new node group
group = bpy.data.node_groups.new('custom group', 'ShaderNodeTree')
# Define interface
color_input = group.interface.new_socket(
name="Color",
description="Color input",
in_out='INPUT',
socket_type='NodeSocketColor'
)
color_input.default_value = (1.0, 1.0, 1.0, 1.0)
color_output = group.interface.new_socket(
name="Color",
description="Color output",
in_out='OUTPUT',
socket_type='NodeSocketColor'
)
color_output.default_value = (0.0, 0.0, 0.0, 1.0)
# Create nodes
nodes = group.nodes
group_in = nodes.new(type='NodeGroupInput')
group_in.location = (-200, 0)
group_out = nodes.new(type='NodeGroupOutput')
group_out.location = (200, 0)
mix_node = nodes.new(type='ShaderNodeMix')
mix_node.location = (0, 0)
mix_node.data_type = 'RGBA'
mix_node.inputs[0].default_value = 0.6
mix_node.inputs[7].default_value = (0.0, 1.0, 0.0, 1.0)
# Create links
links = group.links
links.new(group_in.outputs[0], mix_node.inputs[6])
links.new(mix_node.outputs[2], group_out.inputs[0])
return group
rombout
(rombout)
February 20, 2025, 1:30am
4
Do you know how to set the subtype? So say I create a nodesocketfloat. By default the subtype is not set, this causes issues when I try to connect say factor. This only runs from min 0 to max 1.0. but now min is inf and max is inf
I’ve veen searching Google for some hours, but it seems documentation and api still lacks the info
The socket objects have a property called “.subtype”. For example:
fac_input = group.interface.new_socket(
name="Fac",
description="Factor",
in_out='INPUT',
socket_type='NodeSocketFloat'
)
fac_input.subtype = 'FACTOR'
fac_input.default_value = 0.5
fac_input.min_value = 0.0
fac_input.max_value = 1.0
You could also activate the “Python Tooltips” in the preferences for some help:
This way, you’ll get an information about which property/operator to call in python:
Andrej
(Andrej)
February 20, 2025, 5:38am
6
I don’t know about subtype specifically but if you can recreate this somehow from UI, then you can export your node graph using https://extensions.blender.org/add-ons/node-to-python/ to Python and then investigate the code for solutions.
rombout
(rombout)
February 21, 2025, 2:21pm
7
I know that dev part, ive used it for years. But seeing the example code, it would not matter since its calling an operator. The API, most times, is not clear how to actually use the code.
After 2-4 hours of googling i did find exactly what you showed here, also thanks for that. Could be useful for others.
Now im still looking for api to add it to sub-panels. But i believe this has not been exposed to the API yet. I did find some code in the node.py in bl_operators folder. But it uses a specific context.
rombout
(rombout)
February 21, 2025, 2:22pm
8
Ow that looks handy! Thanks for showing that!
@Zebrahead showed the solution to my seearch
Try it like this (using the group and fac_input from above):
# Create a new panel
panel = group.interface.new_panel(name="My panel")
# Move socket to the panel
group.interface.move_to_parent(
item=fac_input,
parent=panel,
to_position=0
)
# Or assign a parent when creating a new socket
col_input = group.interface.new_socket(
name="Color",
in_out='INPUT',
socket_type='NodeSocketColor',
parent=panel
)
Hope that helps!
rombout
(rombout)
February 25, 2025, 3:54pm
10
Great! Ill try this
I also found an Addon called NodesToPython, which showed me another approach
import bpy, mathutils
# Generate unique scene name
base_name = "LG_View Layer"
end_name = base_name
if bpy.data.scenes.get(end_name) != None:
i = 1
end_name = base_name + f".{i:03d}"
while bpy.data.scenes.get(end_name) != None:
end_name = base_name + f".{i:03d}"
i += 1
scene = bpy.context.window.scene.copy()
scene.name = end_name
scene.use_fake_user = True
bpy.context.window.scene = scene
#initialize LG_View Layer node group
def lg_view_layer_node_group():
lg_view_layer = scene.node_tree
#start with a clean node tree
for node in lg_view_layer.nodes:
lg_view_layer.nodes.remove(node)
lg_view_layer.color_tag = 'NONE'
lg_view_layer.description = ""
lg_view_layer.default_group_node_width = 140
# # Create NodeGroup
# nt = context.scene.node_tree
# viewlayer_node = nt.nodes.active
# viewlayer_name = viewlayer_node.layer
# create group node
group_node = lg_view_layer.nodes.new(type='CompositorNodeGroup')
group_node.location = [0,0] #(viewlayer_node.location[0] + 300, viewlayer_node.location[1])
group_nt = bpy.data.node_groups.new(name='LG_' + base_name, type='CompositorNodeTree')
group_node.node_tree = group_nt
lg_view_layer = group_nt
#lg_view_layer interface
#Socket Outout
outout_socket = lg_view_layer.interface.new_socket(name = "Outout", in_out='OUTPUT', socket_type = 'NodeSocketColor')
outout_socket.default_value = (0.0, 0.0, 0.0, 1.0)
outout_socket.attribute_domain = 'POINT'
#Panel Area
area_panel = lg_view_layer.interface.new_panel("Area")
area_panel.description = "Point"
#Socket Factor
factor_socket = lg_view_layer.interface.new_socket(name = "Factor", in_out='INPUT', socket_type = 'NodeSocketFloat', parent = area_panel)
factor_socket.default_value = 0.0
factor_socket.min_value = 0.0
factor_socket.max_value = 1.0
factor_socket.subtype = 'FACTOR'
factor_socket.attribute_domain = 'POINT'
#Socket Color
color_socket = lg_view_layer.interface.new_socket(name = "Color", in_out='INPUT', socket_type = 'NodeSocketColor', parent = area_panel)
color_socket.default_value = (0.0, 0.0, 0.0, 0.0)
color_socket.attribute_domain = 'POINT'
#Socket Area Lights
area_lights_socket = lg_view_layer.interface.new_socket(name = "Area Lights", in_out='INPUT', socket_type = 'NodeSocketColor', parent = area_panel)
area_lights_socket.default_value = (0.0, 0.0, 0.0, 1.0)
area_lights_socket.attribute_domain = 'POINT'