Add Select node

Implements feature requests #2200 and #2201
This commit is contained in:
Moritz Brückner 2021-06-05 20:35:21 +02:00
parent 5eb8fc781f
commit 20f9f8a5f4
4 changed files with 198 additions and 9 deletions

View file

@ -0,0 +1,37 @@
package armory.logicnode;
class SelectNode extends LogicNode {
/** Execution mode. **/
public var property0: String;
var value: Dynamic = null;
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
// Get value according to the activated input (run() can only be called
// if the execution mode is from_input).
value = inputs[from + Std.int(inputs.length / 2)].get();
runOutput(0);
}
override function get(from: Int): Dynamic {
if (property0 == "from_index") {
var index = inputs[0].get() + 2;
// Return default value for invalid index
if (index < 2 || index >= inputs.length) {
return inputs[1].get();
}
return inputs[index].get();
}
// from_input
return value;
}
}

View file

@ -270,6 +270,30 @@ class ArmNodeRemoveInputOutputButton(bpy.types.Operator):
return{'FINISHED'}
class ArmNodeCallFuncButton(bpy.types.Operator):
"""Operator that calls a function on a specified
node (used for dynamic callbacks)."""
bl_idname = 'arm.node_call_func'
bl_label = 'Execute'
bl_options = {'UNDO', 'INTERNAL'}
node_index: StringProperty(name='Node Index', default='')
callback_name: StringProperty(name='Callback Name', default='')
def execute(self, context):
node = array_nodes[self.node_index]
if hasattr(node, self.callback_name):
getattr(node, self.callback_name)()
else:
return {'CANCELLED'}
# Reset to default again for subsequent calls of this operator
self.node_index = ''
self.callback_name = ''
return {'FINISHED'}
class ArmNodeSearch(bpy.types.Operator):
bl_idname = "arm.node_search"
bl_label = "Search..."
@ -460,12 +484,16 @@ def reset_globals():
category_items = OrderedDict()
bpy.utils.register_class(ArmNodeSearch)
bpy.utils.register_class(ArmNodeAddInputButton)
bpy.utils.register_class(ArmNodeAddInputValueButton)
bpy.utils.register_class(ArmNodeRemoveInputButton)
bpy.utils.register_class(ArmNodeRemoveInputValueButton)
bpy.utils.register_class(ArmNodeAddOutputButton)
bpy.utils.register_class(ArmNodeRemoveOutputButton)
bpy.utils.register_class(ArmNodeAddInputOutputButton)
bpy.utils.register_class(ArmNodeRemoveInputOutputButton)
REG_CLASSES = (
ArmNodeSearch,
ArmNodeAddInputButton,
ArmNodeAddInputValueButton,
ArmNodeRemoveInputButton,
ArmNodeRemoveInputValueButton,
ArmNodeAddOutputButton,
ArmNodeRemoveOutputButton,
ArmNodeAddInputOutputButton,
ArmNodeRemoveInputOutputButton,
ArmNodeCallFuncButton
)
register, unregister = bpy.utils.register_classes_factory(REG_CLASSES)

View file

@ -0,0 +1,122 @@
from bpy.types import NodeSocketInterfaceInt
from arm.logicnode.arm_nodes import *
class SelectNode(ArmLogicTreeNode):
"""Selects one of multiple values (of arbitrary types) based on some
input state. The exact behaviour of this node is specified by the
`Execution Mode` option (see below).
@output Out: [*Available if Execution Mode is set to From Input*]
Activated after the node was executed.
@output Value: The last selected value. This value is not reset
until the next execution of this node.
@option Execution Mode: Specifies the condition that determines
what value to choose.
- `From Index`: Select the value at the given index. If there is
no value at that index, the value plugged in to the
`Default` input is used instead (`null` if unconnected).
- `From Input`: This mode uses input pairs of one action socket
and one value socket. Depending on which action socket is
activated, the associated value socket (the value with the
same index as the activated action input) is forwarded to
the `Value` output.
@option New: Add a new value to the list of values.
@option X Button: Remove the value with the highest index."""
bl_idname = 'LNSelectNode'
bl_label = 'Select'
arm_version = 1
min_inputs = 2
def update_exec_mode(self, context):
self.set_mode()
property0: EnumProperty(
name='Execution Mode',
description="The node's behaviour.",
items=[
('from_index', 'From Index', 'Choose the value from the given index'),
('from_input', 'From Input', 'Choose the value with the same position as the active input')],
default='from_index',
update=update_exec_mode,
)
# The number of choices, NOT of individual inputs. This needs to be
# a property in order to be saved with each individual node
num_choices: IntProperty(default=1, min=0)
def __init__(self):
super().__init__()
array_nodes[str(id(self))] = self
def init(self, context):
super().init(context)
self.set_mode()
def set_mode(self):
self.inputs.clear()
self.outputs.clear()
if self.property0 == 'from_index':
self.add_input('NodeSocketInt', 'Index')
self.add_input('NodeSocketShader', 'Default')
self.num_choices = 0
# from_input
else:
# We could also start with index 1 here, but we need to use
# 0 for the "from_index" mode and it makes the code simpler
# if we stick to the same convention for both exec modes
self.add_input('ArmNodeSocketAction', 'Input 0')
self.add_input('NodeSocketShader', 'Value 0')
self.num_choices = 1
self.add_output('ArmNodeSocketAction', 'Out')
self.add_output('NodeSocketShader', 'Value')
def draw_buttons(self, context, layout):
layout.prop(self, 'property0', text='')
row = layout.row(align=True)
op = row.operator('arm.node_call_func', text='New', icon='PLUS', emboss=True)
op.node_index = str(id(self))
op.callback_name = 'add_input_func'
op = row.operator('arm.node_call_func', text='', icon='X', emboss=True)
op.node_index = str(id(self))
op.callback_name = 'remove_input_func'
def add_input_func(self):
if self.property0 == 'from_input':
self.add_input('ArmNodeSocketAction', f'Input {self.num_choices}')
# Move new action input up to the end of all other action inputs
self.inputs.move(from_index=len(self.inputs) - 1, to_index=self.num_choices)
self.add_input('NodeSocketShader', f'Value {self.num_choices}')
self.num_choices += 1
def remove_input_func(self):
if self.property0 == 'from_input':
if len(self.inputs) > self.min_inputs:
self.inputs.remove(self.inputs[self.num_choices - 1])
if len(self.inputs) > self.min_inputs:
self.inputs.remove(self.inputs[-1])
self.num_choices -= 1
def draw_label(self) -> str:
if self.num_choices == 0:
return self.bl_label
return f'{self.bl_label}: [{self.num_choices}]'

View file

@ -363,6 +363,7 @@ class ReplaceNodesOperator(bpy.types.Operator):
def register():
arm.logicnode.arm_nodes.register()
arm.logicnode.arm_sockets.register()
bpy.utils.register_class(ArmLogicTree)
@ -403,3 +404,4 @@ def unregister():
bpy.utils.register_class(ARM_MT_NodeAddOverride.overridden_menu)
arm.logicnode.arm_sockets.unregister()
arm.logicnode.arm_nodes.unregister()