Hey folks
This is the blender artist topic of Noodler, a free node utility plugin featuring so far:
- Quick Frames
- Fast Links/Reroutes
- Favorites
- Fast Color Assign
- Downstream/Upstream Select
- Reroute Chamfer
- Advanced Search
- Purge Unused
Available for free on Gumroad
And for a $ donation to the dev fund on BM
Known Issues:
- when we set values on python API it sends an update signal, unfortunately moving a node/reroute from a modal operator like these from noodler will also send update signal, slowing down the scene unecessarily. Ive made a thread on the devtalk about this here.
Sadly this appear to be a limitation to all blender plugins.
Old Post
Hello,
i made a little addon that let you create nodal shortcuts easily.
It’s a behavior similar to what we can see in text editors such as SublimeText3
- CTRL+F2 to create/remove shortcuts on the active node
- F2 to loop over your shortcuts
- If you don’t like F2/CTRL F2 You can change the keymap by changing
km.keymap_items.new()
function if needed inregister()
(or simply edit your keymaps) i chose these keys as most programmers are already familiar with them - Altho i made it for geonode it It seems to work on any kind of NodeTree types
geonode_shortcut.py (7.9 KB)
bl_info = {
"name" : "Node Shortcuts",
"author" : "BD3D",
"description" : "Create/remove Shortcut to active node with [CTRL+F2], loop Shortcuts with [F2]",
"blender" : (2, 92, 0),
"version" : (5, 0),
"wiki_url" : "",
"support" : "COMMUNITY",
"category" : "Node",
}
import bpy
# dP"Yb 88""Yb 888888 88""Yb db 888888 dP"Yb 88""Yb .dP"Y8
# dP Yb 88__dP 88__ 88__dP dPYb 88 dP Yb 88__dP `Ybo."
# Yb dP 88""" 88"" 88"Yb dP__Yb 88 Yb dP 88"Yb o.`Y8b
# YbodP 88 888888 88 Yb dP""""Yb 88 YbodP 88 Yb 8bodP'
def get_active_node():
return bpy.context.space_data.node_tree.nodes.active
def set_node_active(node):
bpy.context.space_data.node_tree.nodes.active = node
def deselect_all():
node_group = bpy.context.space_data.node_tree
for nodes in node_group.nodes:
nodes.select = False
return
def get_selection():
node_list = []
node_group = bpy.context.space_data.node_tree
for n in node_group.nodes:
if n.select:
node_list.append(n)
return node_list
def set_selection(node_list):
node_group = bpy.context.space_data.node_tree
for n in node_list:
if n.name in node_group.nodes:
n.select = True
return
def popup_menu(msgs,title,icon):
def draw(self, context):
layout = self.layout
for msg in msgs:
layout.label(text=msg)
return
bpy.context.window_manager.popup_menu(draw, title=title, icon=icon)
return None
class SHORT_OT_add_remove_shortcuts(bpy.types.Operator):
"""add or remove items from list"""
bl_idname = "short.add_remove_shortcuts"
bl_label = "Node Shortcuts : add/remove shortcuts from/to list"
bl_description = "Add or remove shortcuts"
def execute(self, context):
node_group = context.space_data.node_tree
active = get_active_node()
n = node_group.shortcuts.get(active.name)
if active is None:
return {"FINISHED"}
#if shortcut is none, add, else remove
if n is None:
n = node_group.shortcuts.add()
n.name = active.name
popup_menu([f"Shorcut Elements: {len(node_group.shortcuts)}"],"Added a Shortcut","SOLO_ON")
else:
n.trash()
popup_menu([f"Shorcut Elements: {len(node_group.shortcuts)}"],"Removed a Shortcut","TRASH")
return {"FINISHED"}
class SHORT_OT_loop(bpy.types.Operator):
"""add or remove items from list"""
bl_idname = "short.loop"
bl_label = "Loop over shortcut list"
bl_description = "Add or remove shortcuts"
def execute(self, context):
node_group = context.space_data.node_tree
if len(node_group.shortcuts)==0:
return {"FINISHED"}
active = get_active_node()
selection = get_selection()
deselect_all()
#if not already in loop then set to first
node_group.shortcuts_idx +=1
if node_group.shortcuts_idx>=len(node_group.shortcuts):
node_group.shortcuts_idx = 0
n = node_group.nodes.get(node_group.shortcuts[node_group.shortcuts_idx].name)
if n is not None:
n.select = True
set_node_active(n)
bpy.ops.node.view_selected()
#Restore selection
set_selection(selection)
return {"FINISHED"}
class SHORT_OT_clear_shortcuts(bpy.types.Operator):
"""add or remove items from list"""
bl_idname = "short.clear"
bl_label = "Clear all shortcut list"
bl_description = "Clear all shortcut list"
def execute(self, context):
node_group = context.space_data.node_tree
node_group.shortcuts.clear()
return {"FINISHED"}
# dP""b8 88 88 88 dP 88 88 .dP"Y8 888888
# dP `" 88 88 88 dP 88 88 `Ybo." 88
# Yb "88 Y8 8P 88 dP 88 .o 88 o.`Y8b 88
# YboodP `YbodP' 88 dP 88ood8 88 8bodP' 88
class SHORT_PT_side_panel(bpy.types.Panel):
bl_idname = "SHORT_PT_side_panel"
bl_label = "Shortcuts"
bl_category = "View"
bl_space_type = "NODE_EDITOR"
bl_region_type = "UI"
def draw(self, context):
layout = self.layout
active = get_active_node()
node_group = context.space_data.node_tree
layout.template_list("SHORT_UL_shortcuts_listgui", "", node_group, "shortcuts", node_group, "shortcuts_idx")
if active is not None:
if active.name in node_group.shortcuts:
layout.operator("short.add_remove_shortcuts", text="Remove Shortcut", icon="TRASH")
else: layout.operator("short.add_remove_shortcuts", text="Add Shortcut", icon="SOLO_ON")
layout.operator("short.loop", text="Loop", icon="TRACKING_FORWARDS_SINGLE")
layout.operator(SHORT_OT_clear_shortcuts.bl_idname, text="Clear List", icon="TRASH")
return None
class SHORT_shortcuts(bpy.types.PropertyGroup):
name : bpy.props.StringProperty()
def trash(self):
node_group = self.id_data
for i,n in enumerate(node_group.shortcuts):
if n == self:
node_group.shortcuts.remove(i)
break
return None
class SHORT_UL_shortcuts_listgui(bpy.types.UIList):
"""selection area"""
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
node_group = context.space_data.node_tree
#layout.prop_search(item, "name", node_group, "nodes", icon="NODE", text="")
if item.name in node_group.nodes:
active = get_active_node()
if active is not None and active.name==item.name:
layout.prop(item, "name", text="", emboss=False, icon="SOLO_ON")
else: layout.prop(item, "name", text="", emboss=False, icon="SOLO_OFF")
else:
row= layout.row()
row.active = False
row.prop(item, "name", text="", emboss=False, icon="SOLO_OFF")
return None
# 88""Yb 888888 dP""b8 88 .dP"Y8 888888 888888 88""Yb
# 88__dP 88__ dP `" 88 `Ybo." 88 88__ 88__dP
# 88"Yb 88"" Yb "88 88 o.`Y8b 88 88"" 88"Yb
# 88 Yb 888888 YboodP 88 8bodP' 88 888888 88 Yb
classes = [
SHORT_shortcuts,
SHORT_UL_shortcuts_listgui,
SHORT_PT_side_panel,
SHORT_OT_add_remove_shortcuts,
SHORT_OT_loop,
SHORT_OT_clear_shortcuts,
]
addon_keymaps = []
def register():
#Classes
for cls in classes:
bpy.utils.register_class(cls)
#Properties
bpy.types.NodeTree.shortcuts = bpy.props.CollectionProperty(type=SHORT_shortcuts)
bpy.types.NodeTree.shortcuts_idx = bpy.props.IntProperty()
#Add the hotkey
wm = bpy.context.window_manager
kc = wm.keyconfigs.addon
if kc: #vvv CHANGE SHORTCUT HERE vvv
km = wm.keyconfigs.addon.keymaps.new(name='Node Editor', space_type='NODE_EDITOR')
kmi = km.keymap_items.new(SHORT_OT_add_remove_shortcuts.bl_idname, type='F2', value='PRESS', ctrl=True) #CTRL F2 to set/unset shortcut
addon_keymaps.append((km, kmi))
km = wm.keyconfigs.addon.keymaps.new(name='Node Editor', space_type='NODE_EDITOR')
kmi = km.keymap_items.new(SHORT_OT_loop.bl_idname, type='F2', value='PRESS') #F2 to loop over shortcuts
addon_keymaps.append((km, kmi))
return
def unregister():
#Remove the hotkey
for km, kmi in addon_keymaps:
km.keymap_items.remove(kmi)
addon_keymaps.clear()
#Properties
del bpy.types.NodeTree.shortcuts
del bpy.types.NodeTree.shortcuts_idx
#Classes
for cls in reversed(classes):
bpy.utils.unregister_class(cls)
return