Live patch: add support for creating connections between nodes
This commit is contained in:
parent
441f42383e
commit
202138304a
|
@ -6,12 +6,15 @@ class LogicNode {
|
|||
var inputs: Array<LogicNodeInput> = [];
|
||||
var outputs: Array<Array<LogicNode>> = [];
|
||||
|
||||
#if arm_debug
|
||||
#if (arm_debug || arm_patch)
|
||||
public var name = "";
|
||||
public function watch(b: Bool) { // Watch in debug console
|
||||
var nodes = armory.trait.internal.DebugConsole.watchNodes;
|
||||
b ? nodes.push(this) : nodes.remove(this);
|
||||
}
|
||||
|
||||
#if (arm_debug)
|
||||
public function watch(b: Bool) { // Watch in debug console
|
||||
var nodes = armory.trait.internal.DebugConsole.watchNodes;
|
||||
b ? nodes.push(this) : nodes.remove(this);
|
||||
}
|
||||
#end
|
||||
#end
|
||||
|
||||
public function new(tree: LogicTree) {
|
||||
|
|
|
@ -2,10 +2,23 @@ package armory.logicnode;
|
|||
|
||||
class LogicTree extends iron.Trait {
|
||||
|
||||
#if arm_patch
|
||||
public static var nodeTrees = new Map<String, LogicTree>();
|
||||
|
||||
/**
|
||||
[node name => logic node] for later node replacement for live patching.
|
||||
**/
|
||||
public var nodes: Map<String, LogicNode>;
|
||||
#end
|
||||
|
||||
public var loopBreak = false; // Trigger break from loop nodes
|
||||
|
||||
public function new() {
|
||||
super();
|
||||
|
||||
#if arm_patch
|
||||
nodes = new Map<String, LogicNode>();
|
||||
#end
|
||||
}
|
||||
|
||||
public function add() {}
|
||||
|
|
|
@ -1,8 +1,16 @@
|
|||
package armory.trait.internal;
|
||||
|
||||
import armory.logicnode.LogicNode.LogicNodeInput;
|
||||
import armory.logicnode.LogicTree;
|
||||
|
||||
|
||||
#if arm_patch @:expose("LivePatch") #end
|
||||
@:access(armory.logicnode.LogicNode)
|
||||
class LivePatch extends iron.Trait {
|
||||
|
||||
#if arm_patch
|
||||
#if !arm_patch
|
||||
public function new() { super(); }
|
||||
#else
|
||||
|
||||
static var patchId = 0;
|
||||
|
||||
|
@ -23,9 +31,19 @@ class LivePatch extends iron.Trait {
|
|||
});
|
||||
}
|
||||
|
||||
#else
|
||||
public static function patchCreateNodeLink(treeName: String, fromNodeName: String, toNodeName: String, fromIndex: Int, toIndex: Int) {
|
||||
var tree = LogicTree.nodeTrees[treeName];
|
||||
if (tree == null) return;
|
||||
|
||||
public function new() { super(); }
|
||||
var fromNode = tree.nodes[fromNodeName];
|
||||
var toNode = tree.nodes[toNodeName];
|
||||
if (fromNode == null || toNode == null) return;
|
||||
|
||||
// Don't add a connection twice
|
||||
if (!fromNode.outputs[fromIndex].contains(toNode)) {
|
||||
fromNode.outputs[fromIndex].push(toNode);
|
||||
}
|
||||
toNode.inputs[toIndex] = new LogicNodeInput(fromNode, fromIndex);
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import os
|
||||
import shutil
|
||||
from typing import Type
|
||||
from typing import Any, Type
|
||||
|
||||
import bpy
|
||||
|
||||
import arm.assets as assets
|
||||
import arm.assets
|
||||
import arm.node_utils
|
||||
from arm.exporter import ArmoryExporter
|
||||
import arm.log as log
|
||||
from arm.logicnode.arm_nodes import ArmLogicTreeNode
|
||||
import arm.make as make
|
||||
import arm.make_state as state
|
||||
import arm.utils
|
||||
|
@ -44,7 +46,7 @@ def patch_export():
|
|||
if state.proc_build is not None:
|
||||
return
|
||||
|
||||
assets.invalidate_enabled = False
|
||||
arm.assets.invalidate_enabled = False
|
||||
|
||||
with arm.utils.WorkingDir(arm.utils.get_fp()):
|
||||
asset_path = arm.utils.get_fp_build() + '/compiled/Assets/' + arm.utils.safestr(bpy.context.scene.name) + '.arm'
|
||||
|
@ -66,7 +68,7 @@ def patch_export():
|
|||
'--noproject'
|
||||
]
|
||||
|
||||
assets.invalidate_enabled = True
|
||||
arm.assets.invalidate_enabled = True
|
||||
state.proc_build = make.run_proc(cmd, patch_done)
|
||||
|
||||
|
||||
|
@ -99,7 +101,7 @@ def listen(rna_type: Type[bpy.types.bpy_struct], prop: str, event_id: str):
|
|||
)
|
||||
|
||||
|
||||
def send_event(event_id: str):
|
||||
def send_event(event_id: str, opt_data: Any = None):
|
||||
"""Send the result of the given event to Krom."""
|
||||
if hasattr(bpy.context, 'object') and bpy.context.object is not None:
|
||||
obj = bpy.context.object.name
|
||||
|
@ -143,6 +145,29 @@ def send_event(event_id: str):
|
|||
else:
|
||||
patch_export()
|
||||
|
||||
if event_id == 'ln_insert_link':
|
||||
node: ArmLogicTreeNode
|
||||
link: bpy.types.NodeLink
|
||||
node, link = opt_data
|
||||
|
||||
# This event is called twice for a connection but we only need
|
||||
# send it once
|
||||
if node == link.from_node:
|
||||
node_tree = node.get_tree()
|
||||
tree_name = arm.node_utils.get_export_tree_name(node_tree)
|
||||
|
||||
# [1:] is used here because make_logic already uses that for
|
||||
# node names if arm_debug is used
|
||||
from_node_name = arm.node_utils.get_export_node_name(node)[1:]
|
||||
to_node_name = arm.node_utils.get_export_node_name(link.to_node)[1:]
|
||||
|
||||
from_index = arm.node_utils.get_socket_index(node.outputs, link.from_socket)
|
||||
to_index = arm.node_utils.get_socket_index(link.to_node.inputs, link.to_socket)
|
||||
|
||||
js = f'LivePatch.patchCreateNodeLink("{tree_name}", "{from_node_name}", "{to_node_name}", "{from_index}", "{to_index}");'
|
||||
write_patch(js)
|
||||
|
||||
|
||||
|
||||
def on_operator(operator_id: str):
|
||||
"""As long as bpy.msgbus doesn't listen to changes made by
|
||||
|
@ -151,7 +176,7 @@ def on_operator(operator_id: str):
|
|||
(*) https://developer.blender.org/T72109
|
||||
"""
|
||||
# Don't re-export the scene for the following operators
|
||||
if operator_id in ("VIEW3D_OT_select", "OUTLINER_OT_item_activate", "OBJECT_OT_editmode_toggle"):
|
||||
if operator_id in ("VIEW3D_OT_select", "OUTLINER_OT_item_activate", "OBJECT_OT_editmode_toggle", "NODE_OT_select", "NODE_OT_translate_attach_remove_on_cancel"):
|
||||
return
|
||||
|
||||
if operator_id == "TRANSFORM_OT_translate":
|
||||
|
|
|
@ -7,7 +7,8 @@ import bpy.types
|
|||
from bpy.props import *
|
||||
from nodeitems_utils import NodeItem
|
||||
|
||||
# Pass NodeReplacment forward to individual node modules that import arm_nodes
|
||||
import arm # we cannot import arm.livepatch here or we have a circular import
|
||||
# Pass NodeReplacement forward to individual node modules that import arm_nodes
|
||||
from arm.logicnode.replacement import NodeReplacement
|
||||
import arm.node_utils
|
||||
|
||||
|
@ -48,6 +49,13 @@ class ArmLogicTreeNode(bpy.types.Node):
|
|||
def on_unregister(cls):
|
||||
pass
|
||||
|
||||
def get_tree(self):
|
||||
return self.id_data
|
||||
|
||||
def insert_link(self, link: bpy.types.NodeLink):
|
||||
"""Called on *both* nodes when a link between two nodes is created."""
|
||||
arm.live_patch.send_event('ln_insert_link', (self, link))
|
||||
|
||||
def get_replacement_node(self, node_tree: bpy.types.NodeTree):
|
||||
# needs to be overridden by individual node classes with arm_version>1
|
||||
"""(only called if the node's version is inferior to the node class's version)
|
||||
|
|
|
@ -5,6 +5,7 @@ import bpy
|
|||
|
||||
from arm.exporter import ArmoryExporter
|
||||
import arm.log
|
||||
import arm.node_utils
|
||||
import arm.utils
|
||||
|
||||
parsed_nodes = []
|
||||
|
@ -13,14 +14,16 @@ function_nodes = dict()
|
|||
function_node_outputs = dict()
|
||||
group_name = ''
|
||||
|
||||
def get_logic_trees():
|
||||
|
||||
def get_logic_trees() -> list['arm.nodes_logic.ArmLogicTree']:
|
||||
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
|
||||
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())
|
||||
|
@ -34,7 +37,7 @@ def build():
|
|||
for tree in trees:
|
||||
build_node_tree(tree)
|
||||
|
||||
def build_node_tree(node_group):
|
||||
def build_node_tree(node_group: 'arm.nodes_logic.ArmLogicTree'):
|
||||
global parsed_nodes
|
||||
global parsed_ids
|
||||
global function_nodes
|
||||
|
@ -48,12 +51,8 @@ def build_node_tree(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}"')
|
||||
|
||||
group_name = arm.node_utils.get_export_tree_name(node_group, do_warn=True)
|
||||
file = path + group_name + '.hx'
|
||||
|
||||
# Import referenced node group
|
||||
|
@ -65,6 +64,8 @@ def build_node_tree(node_group):
|
|||
if node_group.arm_cached and os.path.isfile(file):
|
||||
return
|
||||
|
||||
wrd = bpy.data.worlds['Arm']
|
||||
|
||||
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')
|
||||
|
@ -72,10 +73,12 @@ def build_node_tree(node_group):
|
|||
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:
|
||||
if wrd.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')
|
||||
if wrd.arm_live_patch:
|
||||
f.write(f'\t\tarmory.logicnode.LogicTree.nodeTrees["{group_name}"] = this;\n')
|
||||
f.write('\t\tnotifyOnAdd(add);\n')
|
||||
f.write('\t}\n\n')
|
||||
f.write('\toverride public function add() {\n')
|
||||
|
@ -116,7 +119,7 @@ def build_node(node: bpy.types.Node, f: TextIO) -> Optional[str]:
|
|||
return None
|
||||
|
||||
# Get node name
|
||||
name = '_' + arm.utils.safesrc(node.name)
|
||||
name = arm.node_utils.get_export_node_name(node)
|
||||
|
||||
# Link nodes using IDs
|
||||
if node.arm_logic_id != '':
|
||||
|
@ -143,11 +146,17 @@ def build_node(node: bpy.types.Node, f: TextIO) -> Optional[str]:
|
|||
# Index function output name by corresponding function name
|
||||
function_node_outputs[node.function_name] = name
|
||||
|
||||
wrd = bpy.data.worlds['Arm']
|
||||
|
||||
# Watch in debug console
|
||||
if node.arm_watch and bpy.data.worlds['Arm'].arm_debug_console:
|
||||
if node.arm_watch and wrd.arm_debug_console:
|
||||
f.write('\t\t' + name + '.name = "' + name[1:] + '";\n')
|
||||
f.write('\t\t' + name + '.watch(true);\n')
|
||||
|
||||
elif wrd.arm_live_patch:
|
||||
f.write('\t\t' + name + '.name = "' + name[1:] + '";\n')
|
||||
f.write(f'\t\tthis.nodes["{name[1:]}"] = {name};\n')
|
||||
|
||||
# Properties
|
||||
for i in range(0, 10):
|
||||
prop_name = 'property' + str(i) + '_get'
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
from typing import Type
|
||||
from typing import Type, Union
|
||||
|
||||
import bpy
|
||||
from bpy.types import NodeSocket, NodeInputs, NodeOutputs
|
||||
from nodeitems_utils import NodeItem
|
||||
|
||||
import arm.log
|
||||
import arm.utils
|
||||
|
||||
|
||||
def find_node_by_link(node_group, to_node, inp):
|
||||
for link in node_group.links:
|
||||
|
@ -46,6 +50,39 @@ def get_output_node(node_group, from_node, output_index):
|
|||
return link.to_node
|
||||
|
||||
|
||||
def get_socket_index(sockets: Union[NodeInputs, NodeOutputs], socket: NodeSocket) -> int:
|
||||
"""Find the socket index in the given node input or output
|
||||
collection, return -1 if not found.
|
||||
"""
|
||||
for i in range(0, len(sockets)):
|
||||
if sockets[i] == socket:
|
||||
return i
|
||||
return -1
|
||||
|
||||
|
||||
def get_export_tree_name(tree: bpy.types.NodeTree, do_warn=False) -> str:
|
||||
"""Return the name of the given node tree that's used in the
|
||||
exported Haxe code.
|
||||
|
||||
If `do_warn` is true, a warning is displayed if the export name
|
||||
differs from the actual tree name.
|
||||
"""
|
||||
export_name = arm.utils.safesrc(tree.name[0].upper() + tree.name[1:])
|
||||
|
||||
if export_name != tree.name:
|
||||
arm.log.warn('Logic node tree and generated trait names differ! Node'
|
||||
f' tree: "{tree.name}", trait: "{export_name}"')
|
||||
|
||||
return export_name
|
||||
|
||||
|
||||
def get_export_node_name(node: bpy.types.Node) -> str:
|
||||
"""Return the name of the given node that's used in the exported
|
||||
Haxe code.
|
||||
"""
|
||||
return '_' + arm.utils.safesrc(node.name)
|
||||
|
||||
|
||||
def nodetype_to_nodeitem(node_type: Type[bpy.types.Node]) -> NodeItem:
|
||||
"""Create a NodeItem from a given node class."""
|
||||
# Internal node types seem to have no bl_idname attribute
|
||||
|
|
Loading…
Reference in a new issue