-
Notifications
You must be signed in to change notification settings - Fork 316
/
make_logic.py
executable file
·292 lines (261 loc) · 11.2 KB
/
make_logic.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
import os
import bpy
import arm.utils
import arm.log
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():
ar = []
for node_group in bpy.data.node_groups:
if node_group.bl_idname == 'ArmLogicTreeType':
node_group.use_fake_user = True # Keep fake references for now
ar.append(node_group)
return ar
# Generating node sources
def build():
os.chdir(arm.utils.get_fp())
trees = get_logic_trees()
if len(trees) > 0:
# Make sure package dir exists
nodes_path = 'Sources/' + arm.utils.safestr(bpy.data.worlds['Arm'].arm_project_package).replace(".", "/") + "/node"
if not os.path.exists(nodes_path):
os.makedirs(nodes_path)
# Export node scripts
for tree in trees:
build_node_tree(tree)
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)
path = 'Sources/' + pack_path.replace('.', '/') + '/node/'
group_name = arm.utils.safesrc(node_group.name[0].upper() + node_group.name[1:])
if group_name != node_group.name:
arm.log.warn('Logic node tree and generated trait names differ! Node'
f' tree: "{node_group.name}", trait: "{group_name}"')
file = path + group_name + '.hx'
# Import referenced node group
for node in node_group.nodes:
if node.bl_idname == 'LNCallGroupNode':
prop = getattr(node, 'property0')
ArmoryExporter.import_traits.append(prop)
if node_group.arm_cached and os.path.isfile(file):
return
with open(file, 'w', encoding="utf-8") as f:
f.write('package ' + pack_path + '.node;\n\n')
f.write('@:keep class ' + group_name + ' extends armory.logicnode.LogicTree {\n\n')
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_debug_console:
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')
# 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.arm_cached = True
def build_node(node: bpy.types.Node, f):
global parsed_nodes
global parsed_ids
if node.type == 'REROUTE':
if len(node.inputs) > 0 and len(node.inputs[0].links) > 0:
return build_node(node.inputs[0].links[0].from_node, f)
else:
return None
# Get node name
name = '_' + arm.utils.safesrc(node.name)
# Link nodes using IDs
if node.arm_logic_id != '':
if node.arm_logic_id in parsed_ids:
return parsed_ids[node.arm_logic_id]
parsed_ids[node.arm_logic_id] = name
# Check if node already exists
if name in parsed_nodes:
return name
parsed_nodes.append(name)
# Create node
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_debug_console:
f.write('\t\t' + name + '.name = "' + name[1:] + '";\n')
f.write('\t\t' + name + '.watch(true);\n')
# Properties
for i in range(0, 10):
prop_name = 'property' + str(i) + '_get'
prop_found = hasattr(node, prop_name)
if not prop_found:
prop_name = 'property' + str(i)
prop_found = hasattr(node, prop_name)
if prop_found:
prop = getattr(node, prop_name)
if isinstance(prop, str):
prop = '"' + str(prop) + '"'
elif isinstance(prop, bool):
prop = str(prop).lower()
elif hasattr(prop, 'name'): # PointerProperty
prop = '"' + str(prop.name) + '"'
else:
if prop is None:
prop = 'null'
else:
prop = str(prop)
f.write('\t\t' + name + '.property' + str(i) + ' = ' + prop + ';\n')
# Create inputs
for inp in node.inputs:
# Is linked - find node
if inp.is_linked:
n = inp.links[0].from_node
socket = inp.links[0].from_socket
if (inp.bl_idname == 'ArmNodeSocketAction' and socket.bl_idname != 'ArmNodeSocketAction') or \
(socket.bl_idname == 'ArmNodeSocketAction' and inp.bl_idname != 'ArmNodeSocketAction'):
print('Armory Error: Sockets do not match in logic node tree "{0}" - node "{1}" - socket "{2}"'.format(group_name, node.name, inp.name))
inp_name = build_node(n, f)
for i in range(0, len(n.outputs)):
if n.outputs[i] == socket:
inp_from = i
break
# Not linked - create node with default values
else:
inp_name = build_default_node(inp)
inp_from = 0
# The input is linked to a reroute, but the reroute is unlinked
if inp_name == None:
inp_name = build_default_node(inp)
inp_from = 0
# Add input
f.write('\t\t' + name + '.addInput(' + inp_name + ', ' + str(inp_from) + ');\n')
# Create outputs
for out in node.outputs:
if out.is_linked:
out_name = ''
for node in collect_nodes_from_output(out, f):
out_name += '[' if len(out_name) == 0 else ', '
out_name += node
out_name += ']'
# Not linked - create node with default values
else:
out_name = '[' + build_default_node(out) + ']'
# Add outputs
f.write('\t\t' + name + '.addOutputs(' + out_name + ');\n')
return name
# Expects an output socket
# It first checks all outgoing links for non-reroute nodes and adds them to a list
# Then it recursively checks all the discoverey reroute nodes
# Returns all non reroute nodes which are directly or indirectly connected to this output.
def collect_nodes_from_output(out, f):
outputs = []
reroutes = []
# skipped if there are no links
for l in out.links:
n = l.to_node
if n.type == 'REROUTE':
# collect all rerouts and process them later
reroutes.append(n)
else:
# immediatly add the current node
outputs.append(build_node(n, f))
for reroute in reroutes:
for o in reroute.outputs:
outputs = outputs + collect_nodes_from_output(o, f)
return outputs
def get_root_nodes(node_group):
roots = []
for node in node_group.nodes:
if node.bl_idname == 'NodeUndefined':
arm.log.warn('Undefined logic nodes in ' + node_group.name)
return []
if node.type == 'FRAME':
continue
linked = False
for out in node.outputs:
if out.is_linked:
linked = True
break
if not linked: # Assume node with no connected outputs as roots
roots.append(node)
return roots
def build_default_node(inp: bpy.types.NodeSocket):
"""Creates a new node to give a not connected input socket a value"""
is_custom_socket = isinstance(inp, arm.logicnode.arm_sockets.ArmCustomSocket)
if is_custom_socket:
# ArmCustomSockets need to implement get_default_value()
default_value = inp.get_default_value()
if isinstance(default_value, str):
default_value = '"{:s}"'.format(default_value.replace('"', '\\"') )
inp_type = inp.arm_socket_type # any custom socket's `type` is "VALUE". might as well have valuable type information for custom nodes as well.
else:
if hasattr(inp, 'default_value'):
default_value = inp.default_value
else:
default_value = None
if isinstance(default_value, str):
default_value = '"{:s}"'.format(default_value.replace('"', '\\"') )
inp_type = inp.type
# Don't write 'None' into the Haxe code
if default_value is None:
default_value = 'null'
if inp_type == 'VECTOR':
return f'new armory.logicnode.VectorNode(this, {default_value[0]}, {default_value[1]}, {default_value[2]})'
elif inp_type == 'RGBA':
return f'new armory.logicnode.ColorNode(this, {default_value[0]}, {default_value[1]}, {default_value[2]}, {default_value[3]})'
elif inp_type == 'RGB':
return f'new armory.logicnode.ColorNode(this, {default_value[0]}, {default_value[1]}, {default_value[2]})'
elif inp_type == 'VALUE':
return f'new armory.logicnode.FloatNode(this, {default_value})'
elif inp_type == 'INT':
return f'new armory.logicnode.IntegerNode(this, {default_value})'
elif inp_type == 'BOOLEAN':
return f'new armory.logicnode.BooleanNode(this, {str(default_value).lower()})'
elif inp_type == 'STRING':
return f'new armory.logicnode.StringNode(this, {default_value})'
elif inp_type == 'NONE':
return 'new armory.logicnode.NullNode(this)'
elif inp_type == 'OBJECT':
return f'new armory.logicnode.ObjectNode(this, {default_value})'
elif is_custom_socket:
return f'new armory.logicnode.DynamicNode(this, {default_value})'
else:
return 'new armory.logicnode.NullNode(this)'