Merge pull request #995 from katharostech/node-functions

Add Functions and Function Calls to Logic Nodes
This commit is contained in:
Lubos Lenco 2018-11-11 10:18:23 +01:00 committed by GitHub
commit 6c859bdb4d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 184 additions and 27 deletions

View file

@ -2,7 +2,7 @@ package armory.logicnode;
import iron.object.Object;
class CallHaxeNode extends LogicNode {
class CallFunctionNode extends LogicNode {
var result:Dynamic;
@ -15,8 +15,15 @@ class CallHaxeNode 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);
runOutput(0);
}

View file

@ -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];
}
}

View file

@ -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);
}
}

View file

@ -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')

View file

@ -109,11 +109,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):
@ -165,11 +167,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):

View file

@ -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')

View file

@ -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')

View file

@ -1,19 +0,0 @@
import bpy
from bpy.props import *
from bpy.types import Node, NodeSocket
from arm.logicnode.arm_nodes import *
class CallHaxeNode(Node, ArmLogicTreeNode):
'''Call Haxe function node'''
bl_idname = 'LNCallHaxeNode'
bl_label = 'Call Haxe'
bl_icon = 'GAME'
def init(self, context):
self.inputs.new('ArmNodeSocketAction', 'In')
self.inputs.new('ArmNodeSocketObject', 'Object')
self.inputs.new('NodeSocketString', 'Function')
self.outputs.new('ArmNodeSocketAction', 'Out')
self.outputs.new('NodeSocketShader', 'Result')
add_node(CallHaxeNode, category='Native')

View file

@ -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')