[SOLVED] Is it possible to add custom node add menus only to one type of node tree?

I want to add a new sub menu for the add node menu that is only accessible in the geometry nodes editor but when I add it with bpy.types.NODE_MT_add.append() it gets added to all node editors. Is there a workaround for that?

1 Like
import bpy
from nodeitems_utils import NodeItem, register_node_categories, unregister_node_categories
from nodeitems_builtins import GeometryNodeCategory

class Selector(bpy.types.ShaderNodeCustomGroup):
    bl_name='Selector'
    bl_label='Selector Node' 
    pass

def register():
    bpy.utils.register_class(Selector)
    newcatlist = [GeometryNodeCategory("GN_NEW_CUSTOM1", "Custom Nodes1", items=[NodeItem("Selector")]),
                    GeometryNodeCategory("GN_NEW_CUSTOM2", "Custom Nodes2", items=[])]
    register_node_categories("CUSTOM_NODES", newcatlist)
    
register()

There is also a way to add new node items to existing categories but i don’t remember that.

My problem currently is that I need a custom draw function that creates a menu with operators to add nodes, because I need to add node groups and as far as I know that can only be done by adding a generic node group and then setting the property of the node group to the node tree that I want. Is there a way to use this method with a custom draw function or adapt it to work with bpy.types.NODE_MT_add.append()?

My guess is, this is the part you need to replace with the identifier of the node menu you register. register_node_categories("CUSTOM_NODES", newcatlist) would be CUSTOM_NODES.

Also, a warning… Node Groups SUCK. I am in the process of completely refactoring my addon because I didn’t know what I was getting into. They are really hard to figure out, in my experience.

“Custom_nodes” is just the identifier, anything (“test1234”) would work instead. I have tried replacing NodeItem with my menu but that just throws an error. I can definitely understand your pain with node groups, I just finished my addon and that is the only thing left that is annoying me.

Yes, I think the identifier is also the class name that you need to append the category onto.

Well, here’s nodeitem_utils … I think it’s undocumented so you should get it straight from the horse’s mouth, so to speak.

1 Like

From what I can see the identifier is only used to unregister the correct entries again, the functions to register menu_type look interesting, I will look into that tomorrow.

As JYoshi says, the identifier is just for the unregister part. All the rest of the logic (for showing/hiding menu items) is expected to be part of the Category’s poll function (and the NodeItems’ poll).
I personally don’t like this also… it should be register_node_categories(nodetree_type, identifier, catlist), with the filtering for each nodetree type being executed for each identifier, and not for each category (which is only a layout.menu command).

For the problem you’re facing, inheriting from the NodeItemCustom should be your solution. All you need is to write the poll and whatever draw function you want.

Here’s the runtime logic of NODE_MT_add.draw():

for cat in all_registered_categories:
    if cat.poll():
       layout.menu(cat_menu)

cat_menu (of type bpy.types.Menu) is automatically generated when a category is registered. Its draw call will loop over the category items and call the draw function of each item.

NodeItems’ draw is a call to bpy.ops.node.add, and is part of the NodeItem class.
But a NodeItemCustom allows you to explicitly setup your own poll and draw functions.

If you look how the nodeitems_builtins.node_group_items is build, it might give a hint on how to do whatever you want in your own NodeCategory.

@pvn31 shared a solution with me in discord:

import bpy

def draw_menu(self, context):
    print(context.area.ui_type)
    if context.area.ui_type == 'GeometryNodeTree':
        layout = self.layout
        layout.separator()
        layout.operator("node.duplicate_move", text="My new context menu item")

def register():
    bpy.types.NODE_MT_context_menu.append(draw_menu)

def unregister():
    bpy.types.NODE_MT_context_menu.remove(draw_menu)
    
register()

This checks the area.ui_type in the function that gets appended to NODE_MT_context_menu and not in the actual menu itself. This mimics the behavior of the poll method and is a nice workaround for my usecase (just one line of code). I will probably need to look into the NodeItemCustom class at some point, it seems to be much more powerful.
Thanks to everyone that helped solve this :smiley:

3 Likes

Thank you, you helped me, I didn’t knew at all how the nodeitem_utils worked and this script helped me reveal that there is has_node_categories function (needed to re-run the default template script for custom_nodes )

1 Like