Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zicklag/2f6eeabd4f1802349b99793082b95c4a to your computer and use it in GitHub Desktop.
Save zicklag/2f6eeabd4f1802349b99793082b95c4a to your computer and use it in GitHub Desktop.
Patch for Function Nodes in Armory
diff --git a/Sources/armory/logicnode/CallFunctionNode.hx b/Sources/armory/logicnode/CallFunctionNode.hx
index d53fabae..b032dd95 100644
--- a/Sources/armory/logicnode/CallFunctionNode.hx
+++ b/Sources/armory/logicnode/CallFunctionNode.hx
@@ -15,8 +15,15 @@ class CallFunctionNode extends LogicNode {
if (object == null) return;
var funName:String = inputs[2].get();
+ var args:Array<Dynamic> = [];
+
+ for (i in 3...inputs.length) {
+ args.push(inputs[i].get());
+ }
- result = Reflect.callMethod(object, Reflect.field(object, funName), null);
+ var func = Reflect.field(object, funName);
+ if (func != null)
+ result = Reflect.callMethod(object, func, args);
runOutputs(0);
}
diff --git a/Sources/armory/logicnode/FunctionNode.hx b/Sources/armory/logicnode/FunctionNode.hx
new file mode 100644
index 00000000..65950224
--- /dev/null
+++ b/Sources/armory/logicnode/FunctionNode.hx
@@ -0,0 +1,22 @@
+package armory.logicnode;
+
+class FunctionNode extends LogicNode {
+
+ @:allow(armory.logicnode.LogicTree)
+ var args:Array<Dynamic> = [];
+ @:allow(armory.logicnode.LogicTree)
+ var result:Dynamic;
+
+ public function new(tree:LogicTree) {
+ super(tree);
+ }
+
+ @:allow(armory.logicnode.LogicTree)
+ override function run(from:Int) {
+ runOutput(0);
+ }
+
+ override function get(from:Int) {
+ return this.args[from - 1];
+ }
+}
diff --git a/Sources/armory/logicnode/FunctionOutputNode.hx b/Sources/armory/logicnode/FunctionOutputNode.hx
new file mode 100644
index 00000000..1a10722c
--- /dev/null
+++ b/Sources/armory/logicnode/FunctionOutputNode.hx
@@ -0,0 +1,16 @@
+package armory.logicnode;
+
+class FunctionOutputNode extends LogicNode {
+
+ @:allow(armory.logicnode.LogicTree)
+ var result:Dynamic;
+
+ public function new(tree:LogicTree) {
+ super(tree);
+ }
+
+ override function run(from:Int) {
+ this.result = inputs[1].get();
+ runOutput(0);
+ }
+}
diff --git a/blender/arm/logicnode/action_call_function.py b/blender/arm/logicnode/action_call_function.py
new file mode 100644
index 00000000..7eab4d65
--- /dev/null
+++ b/blender/arm/logicnode/action_call_function.py
@@ -0,0 +1,33 @@
+import bpy
+from bpy.props import *
+from bpy.types import Node, NodeSocket
+from arm.logicnode.arm_nodes import *
+
+class CallFunctionNode(Node, ArmLogicTreeNode):
+ '''Call Haxe function node'''
+ bl_idname = 'LNCallFunctionNode'
+ bl_label = 'Call Function'
+ bl_icon = 'GAME'
+ min_inputs = 3
+
+ def __init__(self):
+ array_nodes[str(id(self))] = self
+
+ def init(self, context):
+ self.inputs.new('ArmNodeSocketAction', 'In')
+ self.inputs.new('NodeSocketShader', 'Trait/Any')
+ self.inputs.new('NodeSocketString', 'Function')
+ self.outputs.new('ArmNodeSocketAction', 'Out')
+ self.outputs.new('NodeSocketShader', 'Result')
+
+ def draw_buttons(self, context, layout):
+ row = layout.row(align=True)
+ op = row.operator('arm.node_add_input', text='Add Arg', icon='PLUS', emboss=True)
+ op.node_index = str(id(self))
+ op.socket_type = 'NodeSocketShader'
+ op.name_format = "Arg {0}"
+ op.index_name_offset = -2
+ op2 = row.operator('arm.node_remove_input', text='', icon='X', emboss=True)
+ op2.node_index = str(id(self))
+
+add_node(CallFunctionNode, category='Action')
diff --git a/blender/arm/logicnode/arm_nodes.py b/blender/arm/logicnode/arm_nodes.py
index db257d68..eb43cc24 100644
--- a/blender/arm/logicnode/arm_nodes.py
+++ b/blender/arm/logicnode/arm_nodes.py
@@ -122,11 +122,13 @@ class ArmNodeAddInputButton(bpy.types.Operator):
bl_label = 'Add Input'
node_index = StringProperty(name='Node Index', default='')
socket_type = StringProperty(name='Socket Type', default='NodeSocketShader')
+ name_format = StringProperty(name='Name Format', default='Input {0}')
+ index_name_offset = IntProperty(name='Index Name Offset', default=0)
def execute(self, context):
global array_nodes
inps = array_nodes[self.node_index].inputs
- inps.new(self.socket_type, 'Input ' + str(len(inps)))
+ inps.new(self.socket_type, self.name_format.format(str(len(inps) + self.index_name_offset)))
return{'FINISHED'}
class ArmNodeAddInputValueButton(bpy.types.Operator):
@@ -178,11 +180,13 @@ class ArmNodeAddOutputButton(bpy.types.Operator):
bl_label = 'Add Output'
node_index = StringProperty(name='Node Index', default='')
socket_type = StringProperty(name='Socket Type', default='NodeSocketShader')
+ name_format = StringProperty(name='Name Format', default='Output {0}')
+ index_name_offset = IntProperty(name='Index Name Offset', default=0)
def execute(self, context):
global array_nodes
outs = array_nodes[self.node_index].outputs
- outs.new(self.socket_type, 'Output ' + str(len(outs)))
+ outs.new(self.socket_type, self.name_format.format(str(len(outs) + self.index_name_offset)))
return{'FINISHED'}
class ArmNodeRemoveOutputButton(bpy.types.Operator):
diff --git a/blender/arm/logicnode/logic_function.py b/blender/arm/logicnode/logic_function.py
new file mode 100644
index 00000000..6e5cb9ea
--- /dev/null
+++ b/blender/arm/logicnode/logic_function.py
@@ -0,0 +1,33 @@
+import bpy
+from bpy.props import *
+from bpy.types import Node, NodeSocket
+from arm.logicnode.arm_nodes import *
+
+class FunctionNode(Node, ArmLogicTreeNode):
+ '''Function node'''
+ bl_idname = 'LNFunctionNode'
+ bl_label = 'Function'
+ bl_icon = 'CURVE_PATH'
+ min_outputs = 1
+
+ def __init__(self):
+ array_nodes[str(id(self))] = self
+
+ def init(self, context):
+ self.outputs.new('ArmNodeSocketAction', 'Out')
+
+ function_name = StringProperty(name="Name")
+
+ def draw_buttons(self, context, layout):
+ row = layout.row(align=True)
+ row.prop(self, 'function_name')
+ row = layout.row(align=True)
+ op = row.operator('arm.node_add_output', text='Add Arg', icon='PLUS', emboss=True)
+ op.node_index = str(id(self))
+ op.socket_type = 'NodeSocketShader'
+ op.name_format = "Arg {0}"
+ op.index_name_offset = 0
+ op2 = row.operator('arm.node_remove_output', text='', icon='X', emboss=True)
+ op2.node_index = str(id(self))
+
+add_node(FunctionNode, category='Logic')
diff --git a/blender/arm/logicnode/logic_function_output.py b/blender/arm/logicnode/logic_function_output.py
new file mode 100644
index 00000000..dfc5fd90
--- /dev/null
+++ b/blender/arm/logicnode/logic_function_output.py
@@ -0,0 +1,22 @@
+import bpy
+from bpy.props import *
+from bpy.types import Node, NodeSocket
+from arm.logicnode.arm_nodes import *
+
+class FunctionOutputNode(Node, ArmLogicTreeNode):
+ '''Function output node'''
+ bl_idname = 'LNFunctionOutputNode'
+ bl_label = 'Function Output'
+ bl_icon = 'CURVE_PATH'
+
+ def init(self, context):
+ self.inputs.new('ArmNodeSocketAction', 'In')
+ self.inputs.new('NodeSocketShader', 'Value')
+
+ function_name = StringProperty(name="Name")
+
+ def draw_buttons(self, context, layout):
+ row = layout.row(align=True)
+ row.prop(self, 'function_name')
+
+add_node(FunctionOutputNode, category='Logic')
diff --git a/blender/arm/make_logic.py b/blender/arm/make_logic.py
index 833cba1b..72beb9b4 100755
--- a/blender/arm/make_logic.py
+++ b/blender/arm/make_logic.py
@@ -6,6 +6,8 @@ from arm.exporter import ArmoryExporter
parsed_nodes = []
parsed_ids = dict() # Sharing node data
+function_nodes = dict()
+function_node_outputs = dict()
group_name = ''
def get_logic_trees():
@@ -32,9 +34,13 @@ def build():
def build_node_tree(node_group):
global parsed_nodes
global parsed_ids
+ global function_nodes
+ global function_node_outputs
global group_name
parsed_nodes = []
parsed_ids = dict()
+ function_nodes = dict()
+ function_node_outputs = dict()
root_nodes = get_root_nodes(node_group)
pack_path = arm.utils.safestr(bpy.data.worlds['Arm'].arm_project_package)
@@ -54,15 +60,39 @@ def build_node_tree(node_group):
with open(file, 'w') as f:
f.write('package ' + pack_path + '.node;\n\n')
f.write('@:keep class ' + group_name + ' extends armory.logicnode.LogicTree {\n\n')
- f.write('\tpublic function new() { super();')
+ f.write('\tvar functionNodes:Map<String, armory.logicnode.FunctionNode>;\n\n')
+ f.write('\tvar functionOutputNodes:Map<String, armory.logicnode.FunctionOutputNode>;\n\n')
+ f.write('\tpublic function new() {\n')
+ f.write('\t\tsuper();\n')
if bpy.data.worlds['Arm'].arm_play_console:
- f.write(' name = "' + group_name + '";')
- f.write(' notifyOnAdd(add); }\n\n')
+ f.write('\t\tname = "' + group_name + '";\n')
+ f.write('\t\tthis.functionNodes = new Map();\n')
+ f.write('\t\tthis.functionOutputNodes = new Map();\n')
+ f.write('\t\tnotifyOnAdd(add);\n')
+ f.write('\t}\n\n')
f.write('\toverride public function add() {\n')
for node in root_nodes:
build_node(node, f)
f.write('\t}\n')
- f.write('}\n')
+
+ # Create node functions
+ for node_name in function_nodes:
+ node = function_nodes[node_name]
+ function_name = node.function_name
+ f.write('\n\tpublic function ' + function_name + '(')
+ for i in range(0, len(node.outputs) - 1):
+ if i != 0: f.write(', ')
+ f.write('arg' + str(i) + ':Dynamic')
+ f.write(') {\n')
+ f.write('\t\tvar functionNode = this.functionNodes["' + node_name + '"];\n')
+ f.write('\t\tfunctionNode.args = [];\n')
+ for i in range(0, len(node.outputs) - 1):
+ f.write('\t\tfunctionNode.args.push(arg' + str(i) + ');\n')
+ f.write('\t\tfunctionNode.run(0);\n')
+ if function_node_outputs.get(function_name) != None:
+ f.write('\t\treturn this.functionOutputNodes["' + function_node_outputs[function_name] + '"].result;\n')
+ f.write('\t}\n\n')
+ f.write('}')
node_group.is_cached = True
def build_node(node, f):
@@ -94,6 +124,15 @@ def build_node(node, f):
node_type = node.bl_idname[2:] # Discard 'LN'TimeNode prefix
f.write('\t\tvar ' + name + ' = new armory.logicnode.' + node_type + '(this);\n')
+ # Handle Function Nodes
+ if node_type == 'FunctionNode':
+ f.write('\t\tthis.functionNodes.set("' + name + '", ' + name + ');\n')
+ function_nodes[name] = node
+ elif node_type == 'FunctionOutputNode':
+ f.write('\t\tthis.functionOutputNodes.set("' + name + '", ' + name + ');\n')
+ # Index function output name by corresponding function name
+ function_node_outputs[node.function_name] = name
+
# Watch in debug console
if node.arm_watch and bpy.data.worlds['Arm'].arm_play_console:
f.write('\t\t' + name + '.name = "' + name[1:] + '";\n')
package armory.logicnode;
import iron.object.Object;
class CallFunctionNode extends LogicNode {
var result:Dynamic;
public function new(tree:LogicTree) {
super(tree);
}
override function run() {
var object:Object = inputs[1].get();
if (object == null) return;
var funName:String = inputs[2].get();
var args:Array<Dynamic> = [];
for (i in 3...inputs.length) {
args.push(inputs[i].get());
}
var func = Reflect.field(object, funName);
if (func != null)
result = Reflect.callMethod(object, func, args);
runOutputs(0);
}
override function get(from:Int):Dynamic {
return result;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment