Skip to content

Commit

Permalink
Merge pull request #2970 from MoritzBrueckner/add-node-groups
Browse files Browse the repository at this point in the history
Make node groups available from "Add node" menu
  • Loading branch information
luboslenco authored Nov 20, 2023
2 parents 0d14c70 + dcd56ac commit e32751d
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 8 deletions.
16 changes: 14 additions & 2 deletions blender/arm/logicnode/arm_node_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# SPDX-License-Identifier: GPL3
# License-Filename: LICENSE
from functools import reduce
from typing import List, Set, Dict
from typing import Iterator, List, Set, Dict

import bpy
from bpy.props import *
Expand Down Expand Up @@ -62,7 +62,7 @@ def upstream_trees(self) -> List['ArmGroupTree']:
raise RecursionError(f'Looks like group tree "{self}" has links to itself from other groups')
return trees

def can_be_linked(self):
def can_be_linked(self) -> bool:
"""Try to avoid creating loops of group trees with each other"""
# upstream trees of tested treed should nad share trees with downstream trees of current tree
tested_tree_upstream_trees = {t.name for t in self.upstream_trees()}
Expand All @@ -73,6 +73,18 @@ def can_be_linked(self):
def update(self):
pass

@classmethod
def get_linkable_group_trees(cls) -> Iterator['ArmGroupTree']:
return filter(lambda tree: isinstance(tree, ArmGroupTree) and tree.can_be_linked(), bpy.data.node_groups)

@classmethod
def has_linkable_group_trees(cls) -> bool:
try:
_ = next(cls.get_linkable_group_trees())
except StopIteration:
return False
return True


class ArmEditGroupTree(bpy.types.Operator):
"""Go into subtree to edit"""
Expand Down
66 changes: 60 additions & 6 deletions blender/arm/nodes_logic.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from typing import Any, Callable
import webbrowser

import bl_operators
import bpy
import blf
from bpy.props import BoolProperty, StringProperty
from bpy.props import BoolProperty, CollectionProperty, StringProperty

import arm.logicnode.arm_nodes as arm_nodes
import arm.logicnode.replacement
Expand All @@ -25,6 +26,9 @@
else:
arm.enable_reload(__name__)

INTERNAL_GROUPS_MENU_ID = 'ARM_INTERNAL_GROUPS'
internal_groups_menu_class: bpy.types.Menu

registered_nodes = []
registered_categories = []

Expand Down Expand Up @@ -69,6 +73,10 @@ def draw(self, context):
safe_category_name = arm.utils.safesrc(category.name.lower())
layout.menu(f'ARM_MT_{safe_category_name}_menu', text=category.name, icon=category.icon)

if arm.logicnode.arm_node_group.ArmGroupTree.has_linkable_group_trees():
layout.separator()
layout.menu(f'ARM_MT_{INTERNAL_GROUPS_MENU_ID}_menu', text=internal_groups_menu_class.bl_label, icon='OUTLINER_OB_GROUP_INSTANCE')

else:
ARM_MT_NodeAddOverride.overridden_draw(self, context)

Expand All @@ -81,9 +89,26 @@ class ARM_OT_AddNodeOverride(bpy.types.Operator):

type: StringProperty(name="NodeItem type")
use_transform: BoolProperty(name="Use Transform")
settings: CollectionProperty(
name="Settings",
description="Settings to be applied on the newly created node",
type=bl_operators.node.NodeSetting,
options={'SKIP_SAVE'},
)

def invoke(self, context, event):
bpy.ops.node.add_node('INVOKE_DEFAULT', type=self.type, use_transform=self.use_transform)
# Passing collection properties as operator parameters only
# works via raw sequences of dicts:
# https://blender.stackexchange.com/a/298977/58208
# https://github.com/blender/blender/blob/cf1e1ed46b7ec80edb0f43cb514d3601a1696ec1/source/blender/python/intern/bpy_rna.c#L2033-L2043
setting_dicts = []
for setting in self.settings.values():
setting_dicts.append({
"name": setting.name,
"value": setting.value
})

bpy.ops.node.add_node('INVOKE_DEFAULT', type=self.type, use_transform=self.use_transform, settings=setting_dicts)
return {'FINISHED'}

@classmethod
Expand Down Expand Up @@ -122,7 +147,7 @@ def draw_category_menu(self, context):


def register_nodes():
global registered_nodes
global registered_nodes, internal_groups_menu_class

# Re-register all nodes for now..
if len(registered_nodes) > 0 or len(registered_categories) > 0:
Expand All @@ -145,6 +170,7 @@ def register_nodes():
for category in category_section:
category.sort_nodes()
safe_category_name = arm.utils.safesrc(category.name.lower())
assert(safe_category_name != INTERNAL_GROUPS_MENU_ID) # see below
menu_class = type(f'ARM_MT_{safe_category_name}Menu', (bpy.types.Menu, ), {
'bl_space_type': 'NODE_EDITOR',
'bl_idname': f'ARM_MT_{safe_category_name}_menu',
Expand All @@ -156,20 +182,48 @@ def register_nodes():

bpy.utils.register_class(menu_class)

# Generate and register group menu
def draw_nodegroups_menu(self, context):
layout = self.layout

tree: arm.logicnode.arm_node_group.ArmGroupTree
for tree in arm.logicnode.arm_node_group.ArmGroupTree.get_linkable_group_trees():
op = layout.operator('arm.add_node_override', text=tree.name)
op.type = 'LNCallGroupNode'
op.use_transform = True
item = op.settings.add()
item.name = "group_tree"
item.value = f'bpy.data.node_groups["{tree.name}"]'

# Don't name categories like the content of the INTERNAL_GROUPS_MENU_ID variable!
menu_class = type(f'ARM_MT_{INTERNAL_GROUPS_MENU_ID}Menu', (bpy.types.Menu,), {
'bl_space_type': 'NODE_EDITOR',
'bl_idname': f'ARM_MT_{INTERNAL_GROUPS_MENU_ID}_menu',
'bl_label': 'Node Groups',
'bl_description': 'List of node groups that can be added to the current tree',
'draw': draw_nodegroups_menu
})
internal_groups_menu_class = menu_class
bpy.utils.register_class(menu_class)


def unregister_nodes():
global registered_nodes, registered_categories
global registered_nodes, registered_categories, internal_groups_menu_class

for n in registered_nodes:
if issubclass(n, arm_nodes.ArmLogicTreeNode):
n.on_unregister()
bpy.utils.unregister_class(n)
registered_nodes = []

for c in registered_categories:
bpy.utils.unregister_class(c)

registered_nodes = []
registered_categories = []

if internal_groups_menu_class is not None:
bpy.utils.unregister_class(internal_groups_menu_class)
internal_groups_menu_class = None


class ARM_PT_LogicNodePanel(bpy.types.Panel):
bl_label = 'Armory Logic Node'
Expand Down

0 comments on commit e32751d

Please sign in to comment.