Blender 4.0 introduced new collapsible panels within nodes, but I can’t find any documentation about how to actually create them for custom nodes though. Does anyone know how to do this? Thanks so much!
Do I understand correctly that you are asking about creating a node interface panel?
It looks like it, yes! Part of my confusion might be because there’s no clear name for what they’re actually supposed to be called so my Google-fu is failing.
Hi, I’m running with the same issue. Would you be able to share what you found please?
I ended up reaching out to the dev who wrote the feature, and he said that as of right now panels in custom nodes are not supported for architectural reasons. So at the moment we’re just out of luck, unfortunately. You can use them in node groups, though, both in Python and in the user interface.
You have any idea if this is yet available?
After some digging in blender source folder. the node.py file does have an operator which seems able to move items to a panel. Ive been trying to use it, but without luck. Getting the correct context here is the big issue
The Class im talking about is called “NODE_OT_interface_item_new”
class NODE_OT_interface_item_new(NodeInterfaceOperator, Operator):
'''Add a new item to the interface'''
bl_idname = "node.interface_item_new"
bl_label = "New Item"
bl_options = {'REGISTER', 'UNDO'}
item_type: EnumProperty(
name="Item Type",
description="Type of the item to create",
items=(
('INPUT', "Input", ""),
('OUTPUT', "Output", ""),
('PANEL', "Panel", ""),
),
default='INPUT',
)
# Returns a valid socket type for the given tree or None.
@staticmethod
def find_valid_socket_type(tree):
socket_type = 'NodeSocketFloat'
# Socket type validation function is only available for custom
# node trees. Assume that 'NodeSocketFloat' is valid for
# built-in node tree types.
if not hasattr(tree, "valid_socket_type") or tree.valid_socket_type(socket_type):
return socket_type
# Custom nodes may not support float sockets, search all
# registered socket subclasses.
types_to_check = [bpy.types.NodeSocket]
while types_to_check:
t = types_to_check.pop()
idname = getattr(t, "bl_idname", "")
if tree.valid_socket_type(idname):
return idname
# Test all subclasses
types_to_check.extend(t.__subclasses__())
def execute(self, context):
snode = context.space_data
tree = snode.edit_tree
interface = tree.interface
# Remember active item and position to determine target position.
active_item = interface.active
active_pos = active_item.position if active_item else -1
if self.item_type == 'INPUT':
item = interface.new_socket("Socket", socket_type=self.find_valid_socket_type(tree), in_out='INPUT')
elif self.item_type == 'OUTPUT':
item = interface.new_socket("Socket", socket_type=self.find_valid_socket_type(tree), in_out='OUTPUT')
elif self.item_type == 'PANEL':
item = interface.new_panel("Panel")
else:
return {'CANCELLED'}
if active_item:
# Insert into active panel if possible, otherwise insert after active item.
if active_item.item_type == 'PANEL' and item.item_type != 'PANEL':
interface.move_to_parent(item, active_item, len(active_item.interface_items))
else:
interface.move_to_parent(item, active_item.parent, active_pos + 1)
interface.active = item
return {'FINISHED'}
I haven’t thought about this in a long time. I haven’t seen anything to indicate it is possible though, unfortunately. I’d love to see this happen for custom Python nodes.
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'
Also in this post, i got different method;
# 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
)