Live patch: handle deletion of node links
This commit is contained in:
parent
256d27e289
commit
aa21402221
|
@ -4,8 +4,8 @@ package armory.logicnode;
|
||||||
class LogicNode {
|
class LogicNode {
|
||||||
|
|
||||||
var tree: LogicTree;
|
var tree: LogicTree;
|
||||||
var inputs: Array<LogicNodeInput> = [];
|
var inputs: Array<LogicNodeLink> = [];
|
||||||
var outputs: Array<Array<LogicNode>> = [];
|
var outputs: Array<Array<LogicNodeLink>> = [];
|
||||||
|
|
||||||
#if (arm_debug || arm_patch)
|
#if (arm_debug || arm_patch)
|
||||||
public var name = "";
|
public var name = "";
|
||||||
|
@ -22,14 +22,115 @@ class LogicNode {
|
||||||
this.tree = tree;
|
this.tree = tree;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function addInput(node: LogicNode, from: Int) {
|
/**
|
||||||
inputs.push(new LogicNodeInput(node, from));
|
Resize the inputs array to a given size to minimize dynamic
|
||||||
|
reallocation and over-allocation later.
|
||||||
|
**/
|
||||||
|
inline function preallocInputs(amount: Int) {
|
||||||
|
this.inputs.resize(amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function addOutputs(nodes: Array<LogicNode>) {
|
/**
|
||||||
outputs.push(nodes);
|
Resize the outputs array to a given size to minimize dynamic
|
||||||
|
reallocation and over-allocation later.
|
||||||
|
**/
|
||||||
|
inline function preallocOutputs(amount: Int) {
|
||||||
|
this.outputs.resize(amount);
|
||||||
|
for (i in 0...outputs.length) {
|
||||||
|
outputs[i] = [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Add a link between to nodes to the tree.
|
||||||
|
**/
|
||||||
|
public static function addLink(fromNode: LogicNode, toNode: LogicNode, fromIndex: Int, toIndex: Int): LogicNodeLink {
|
||||||
|
var link = new LogicNodeLink(fromNode, toNode, fromIndex, toIndex);
|
||||||
|
|
||||||
|
if (toNode.inputs.length <= toIndex) {
|
||||||
|
toNode.inputs.resize(toIndex + 1);
|
||||||
|
}
|
||||||
|
toNode.inputs[toIndex] = link;
|
||||||
|
|
||||||
|
var fromNodeOuts = fromNode.outputs;
|
||||||
|
var outLen = fromNodeOuts.length;
|
||||||
|
if (outLen <= fromIndex) {
|
||||||
|
fromNodeOuts.resize(fromIndex + 1);
|
||||||
|
|
||||||
|
// Initialize with empty arrays
|
||||||
|
for (i in 0...(fromIndex - (outLen - 1))) {
|
||||||
|
fromNodeOuts[i] = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fromNodeOuts[fromIndex].push(link);
|
||||||
|
|
||||||
|
return link;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if arm_patch
|
||||||
|
/**
|
||||||
|
Removes a link from the tree.
|
||||||
|
**/
|
||||||
|
static function removeLink(link: LogicNodeLink) {
|
||||||
|
link.fromNode.outputs[link.fromIndex].remove(link);
|
||||||
|
|
||||||
|
// Reuse the same link and connect a default input node to it.
|
||||||
|
// That's why this function is only available in arm_patch mode, we need
|
||||||
|
// access to the link's type and value.
|
||||||
|
link.fromNode = LogicNode.createSocketDefaultNode(link.toNode.tree, link.toType, link.toValue);
|
||||||
|
link.fromIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Removes all inputs and their links from this node.
|
||||||
|
Warning: this function changes the amount of node inputs to 0!
|
||||||
|
**/
|
||||||
|
function clearInputs() {
|
||||||
|
for (link in inputs) {
|
||||||
|
link.fromNode.outputs[link.fromIndex].remove(link);
|
||||||
|
}
|
||||||
|
inputs.resize(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Removes all outputs and their links from this node.
|
||||||
|
Warning: this function changes the amount of node inputs to 0!
|
||||||
|
**/
|
||||||
|
function clearOutputs() {
|
||||||
|
for (links in outputs) {
|
||||||
|
for (link in links) {
|
||||||
|
var defaultNode = LogicNode.createSocketDefaultNode(tree, link.toType, link.toValue);
|
||||||
|
link.fromNode = defaultNode;
|
||||||
|
link.fromIndex = 0;
|
||||||
|
defaultNode.outputs[0] = [link];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outputs.resize(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Creates a default node for a socket so that get() and set() can be
|
||||||
|
used without null checks.
|
||||||
|
Loosely equivalent to `make_logic.build_default_node()` in Python.
|
||||||
|
**/
|
||||||
|
static inline function createSocketDefaultNode(tree: LogicTree, socketType: String, value: Dynamic): LogicNode {
|
||||||
|
// Make sure to not add these nodes to the LogicTree.nodes array as they
|
||||||
|
// won't be garbage collected then if unlinked later.
|
||||||
|
return switch (socketType) {
|
||||||
|
case "VECTOR": new armory.logicnode.VectorNode(tree, value[0], value[1], value[2]);
|
||||||
|
case "RGBA": new armory.logicnode.ColorNode(tree, value[0], value[1], value[2], value[3]);
|
||||||
|
case "RGB": new armory.logicnode.ColorNode(tree, value[0], value[1], value[2]);
|
||||||
|
case "VALUE": new armory.logicnode.FloatNode(tree, value);
|
||||||
|
case "INT": new armory.logicnode.IntegerNode(tree, value);
|
||||||
|
case "BOOLEAN": new armory.logicnode.BooleanNode(tree, value);
|
||||||
|
case "STRING": new armory.logicnode.StringNode(tree, value);
|
||||||
|
case "NONE": new armory.logicnode.NullNode(tree);
|
||||||
|
case "OBJECT": new armory.logicnode.ObjectNode(tree, value);
|
||||||
|
default: new armory.logicnode.DynamicNode(tree, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#end
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Called when this node is activated.
|
Called when this node is activated.
|
||||||
@param from impulse index
|
@param from impulse index
|
||||||
|
@ -42,42 +143,45 @@ class LogicNode {
|
||||||
**/
|
**/
|
||||||
function runOutput(i: Int) {
|
function runOutput(i: Int) {
|
||||||
if (i >= outputs.length) return;
|
if (i >= outputs.length) return;
|
||||||
for (o in outputs[i]) {
|
for (outLink in outputs[i]) {
|
||||||
// Check which input activated the node
|
outLink.toNode.run(outLink.toIndex);
|
||||||
for (j in 0...o.inputs.length) {
|
|
||||||
if (o.inputs[j].node == this) {
|
|
||||||
o.run(j);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@:allow(armory.logicnode.LogicNodeInput)
|
@:allow(armory.logicnode.LogicNodeLink)
|
||||||
function get(from: Int): Dynamic { return this; }
|
function get(from: Int): Dynamic { return this; }
|
||||||
|
|
||||||
@:allow(armory.logicnode.LogicNodeInput)
|
@:allow(armory.logicnode.LogicNodeLink)
|
||||||
function set(value: Dynamic) {}
|
function set(value: Dynamic) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
class LogicNodeInput {
|
@:allow(armory.logicnode.LogicNode)
|
||||||
|
@:allow(armory.logicnode.LogicTree)
|
||||||
|
class LogicNodeLink {
|
||||||
|
|
||||||
@:allow(armory.logicnode.LogicNode)
|
var fromNode: LogicNode;
|
||||||
var node: LogicNode;
|
var toNode: LogicNode;
|
||||||
var from: Int; // Socket index
|
var fromIndex: Int;
|
||||||
|
var toIndex: Int;
|
||||||
|
|
||||||
public function new(node: LogicNode, from: Int) {
|
#if arm_patch
|
||||||
this.node = node;
|
var fromType: String;
|
||||||
this.from = from;
|
var toType: String;
|
||||||
|
var toValue: Dynamic;
|
||||||
|
#end
|
||||||
|
|
||||||
|
inline function new(fromNode: LogicNode, toNode: LogicNode, fromIndex: Int, toIndex: Int) {
|
||||||
|
this.fromNode = fromNode;
|
||||||
|
this.toNode = toNode;
|
||||||
|
this.fromIndex = fromIndex;
|
||||||
|
this.toIndex = toIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
@:allow(armory.logicnode.LogicNode)
|
inline function get(): Dynamic {
|
||||||
function get(): Dynamic {
|
return fromNode.get(fromIndex);
|
||||||
return node.get(from);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@:allow(armory.logicnode.LogicNode)
|
inline function set(value: Dynamic) {
|
||||||
function set(value: Dynamic) {
|
fromNode.set(value);
|
||||||
node.set(value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,10 +11,10 @@ class QuaternionNode extends LogicNode {
|
||||||
super(tree);
|
super(tree);
|
||||||
|
|
||||||
if (x != null) {
|
if (x != null) {
|
||||||
addInput(new FloatNode(tree, x), 0);
|
LogicNode.addLink(new FloatNode(tree, x), this, 0, 0);
|
||||||
addInput(new FloatNode(tree, y), 0);
|
LogicNode.addLink(new FloatNode(tree, y), this, 0, 1);
|
||||||
addInput(new FloatNode(tree, z), 0);
|
LogicNode.addLink(new FloatNode(tree, z), this, 0, 2);
|
||||||
addInput(new FloatNode(tree, w), 0);
|
LogicNode.addLink(new FloatNode(tree, w), this, 0, 3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,15 +27,15 @@ class QuaternionNode extends LogicNode {
|
||||||
switch (from){
|
switch (from){
|
||||||
case 0:
|
case 0:
|
||||||
return value;
|
return value;
|
||||||
case 1:
|
case 1:
|
||||||
var value1 = new Vec4();
|
var value1 = new Vec4();
|
||||||
value1.x = value.x;
|
value1.x = value.x;
|
||||||
value1.y = value.y;
|
value1.y = value.y;
|
||||||
value1.z = value.z;
|
value1.z = value.z;
|
||||||
value1.w = 0; // use 0 to avoid this vector being translated.
|
value1.w = 0; // use 0 to avoid this vector being translated.
|
||||||
return value1;
|
return value1;
|
||||||
case 2:
|
case 2:
|
||||||
return value.w;
|
return value.w;
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,8 +14,8 @@ class SetParentNode extends LogicNode {
|
||||||
|
|
||||||
var parent: Object;
|
var parent: Object;
|
||||||
var isUnparent = false;
|
var isUnparent = false;
|
||||||
if (Std.is(inputs[2].node, ObjectNode)) {
|
if (Std.is(inputs[2].fromNode, ObjectNode)) {
|
||||||
var parentNode = cast(inputs[2].node, ObjectNode);
|
var parentNode = cast(inputs[2].fromNode, ObjectNode);
|
||||||
isUnparent = parentNode.objectName == "";
|
isUnparent = parentNode.objectName == "";
|
||||||
}
|
}
|
||||||
if (isUnparent) parent = iron.Scene.active.root;
|
if (isUnparent) parent = iron.Scene.active.root;
|
||||||
|
@ -24,7 +24,7 @@ class SetParentNode extends LogicNode {
|
||||||
if (object == null || parent == null || object.parent == parent) return;
|
if (object == null || parent == null || object.parent == parent) return;
|
||||||
|
|
||||||
object.parent.removeChild(object, isUnparent); // keepTransform
|
object.parent.removeChild(object, isUnparent); // keepTransform
|
||||||
|
|
||||||
#if arm_physics
|
#if arm_physics
|
||||||
var rigidBody = object.getTrait(RigidBody);
|
var rigidBody = object.getTrait(RigidBody);
|
||||||
if (rigidBody != null) rigidBody.setActivationState(0);
|
if (rigidBody != null) rigidBody.setActivationState(0);
|
||||||
|
|
|
@ -10,9 +10,9 @@ class VectorNode extends LogicNode {
|
||||||
super(tree);
|
super(tree);
|
||||||
|
|
||||||
if (x != null) {
|
if (x != null) {
|
||||||
addInput(new FloatNode(tree, x), 0);
|
LogicNode.addLink(new FloatNode(tree, x), this, 0, 0);
|
||||||
addInput(new FloatNode(tree, y), 0);
|
LogicNode.addLink(new FloatNode(tree, y), this, 0, 1);
|
||||||
addInput(new FloatNode(tree, z), 0);
|
LogicNode.addLink(new FloatNode(tree, z), this, 0, 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import armory.logicnode.LogicTree;
|
||||||
|
|
||||||
#if arm_patch @:expose("LivePatch") #end
|
#if arm_patch @:expose("LivePatch") #end
|
||||||
@:access(armory.logicnode.LogicNode)
|
@:access(armory.logicnode.LogicNode)
|
||||||
@:access(armory.logicnode.LogicNodeInput)
|
@:access(armory.logicnode.LogicNodeLink)
|
||||||
class LivePatch extends iron.Trait {
|
class LivePatch extends iron.Trait {
|
||||||
|
|
||||||
#if !arm_patch
|
#if !arm_patch
|
||||||
|
@ -40,11 +40,54 @@ class LivePatch extends iron.Trait {
|
||||||
var toNode = tree.nodes[toNodeName];
|
var toNode = tree.nodes[toNodeName];
|
||||||
if (fromNode == null || toNode == null) return;
|
if (fromNode == null || toNode == null) return;
|
||||||
|
|
||||||
// Don't add a connection twice
|
LogicNode.addLink(fromNode, toNode, fromIndex, toIndex);
|
||||||
if (!fromNode.outputs[fromIndex].contains(toNode)) {
|
}
|
||||||
fromNode.outputs[fromIndex].push(toNode);
|
|
||||||
|
public static function patchSetNodeLinks(treeName: String, nodeName: String, inputDatas: Array<Dynamic>, outputDatas: Array<Array<Dynamic>>) {
|
||||||
|
var tree = LogicTree.nodeTrees[treeName];
|
||||||
|
if (tree == null) return;
|
||||||
|
|
||||||
|
var node = tree.nodes[nodeName];
|
||||||
|
if (node == null) return;
|
||||||
|
|
||||||
|
node.clearInputs();
|
||||||
|
node.clearOutputs();
|
||||||
|
|
||||||
|
for (inputData in inputDatas) {
|
||||||
|
var fromNode: LogicNode;
|
||||||
|
var fromIndex: Int;
|
||||||
|
|
||||||
|
if (inputData.isLinked) {
|
||||||
|
fromNode = tree.nodes[inputData.fromNode];
|
||||||
|
if (fromNode == null) continue;
|
||||||
|
fromIndex = inputData.fromIndex;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
fromNode = LogicNode.createSocketDefaultNode(node.tree, inputData.socketType, inputData.socketValue);
|
||||||
|
fromIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogicNode.addLink(fromNode, node, fromIndex, inputData.toIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (outputData in outputDatas) {
|
||||||
|
for (linkData in outputData) {
|
||||||
|
var toNode: LogicNode;
|
||||||
|
var toIndex: Int;
|
||||||
|
|
||||||
|
if (linkData.isLinked) {
|
||||||
|
toNode = tree.nodes[linkData.toNode];
|
||||||
|
if (toNode == null) continue;
|
||||||
|
toIndex = linkData.toIndex;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
toNode = LogicNode.createSocketDefaultNode(node.tree, linkData.socketType, linkData.socketValue);
|
||||||
|
toIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogicNode.addLink(node, toNode, linkData.fromIndex, toIndex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
toNode.inputs[toIndex] = new LogicNodeInput(fromNode, fromIndex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function patchUpdateNodeProp(treeName: String, nodeName: String, propName: String, value: Dynamic) {
|
public static function patchUpdateNodeProp(treeName: String, nodeName: String, propName: String, value: Dynamic) {
|
||||||
|
@ -123,11 +166,12 @@ class LivePatch extends iron.Trait {
|
||||||
|
|
||||||
var i = 0;
|
var i = 0;
|
||||||
for (inputData in inputDatas) {
|
for (inputData in inputDatas) {
|
||||||
newNode.addInput(createSocketDefaultNode(newNode.tree, inputData[0], inputData[1]), i++);
|
LogicNode.addLink(LogicNode.createSocketDefaultNode(newNode.tree, inputData[0], inputData[1]), newNode, 0, i++);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
i = 0;
|
||||||
for (outputData in outputDatas) {
|
for (outputData in outputDatas) {
|
||||||
newNode.addOutputs([createSocketDefaultNode(newNode.tree, outputData[0], outputData[1])]);
|
LogicNode.addLink(newNode, LogicNode.createSocketDefaultNode(newNode.tree, outputData[0], outputData[1]), i++, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,27 +195,14 @@ class LivePatch extends iron.Trait {
|
||||||
|
|
||||||
var i = 0;
|
var i = 0;
|
||||||
for (inputData in inputDatas) {
|
for (inputData in inputDatas) {
|
||||||
newNode.addInput(createSocketDefaultNode(newNode.tree, inputData[0], inputData[1]), i++);
|
LogicNode.addLink(LogicNode.createSocketDefaultNode(newNode.tree, inputData[0], inputData[1]), newNode, 0, i++);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
i = 0;
|
||||||
for (outputData in outputDatas) {
|
for (outputData in outputDatas) {
|
||||||
newNode.addOutputs([createSocketDefaultNode(newNode.tree, outputData[0], outputData[1])]);
|
LogicNode.addLink(newNode, LogicNode.createSocketDefaultNode(newNode.tree, outputData[0], outputData[1]), i++, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline function createSocketDefaultNode(tree: LogicTree, socketType: String, value: Dynamic): LogicNode {
|
|
||||||
return switch (socketType) {
|
|
||||||
case "VECTOR": new armory.logicnode.VectorNode(tree, value[0], value[1], value[2]);
|
|
||||||
case "RGBA": new armory.logicnode.ColorNode(tree, value[0], value[1], value[2], value[3]);
|
|
||||||
case "RGB": new armory.logicnode.ColorNode(tree, value[0], value[1], value[2]);
|
|
||||||
case "VALUE": new armory.logicnode.FloatNode(tree, value);
|
|
||||||
case "INT": new armory.logicnode.IntegerNode(tree, value);
|
|
||||||
case "BOOLEAN": new armory.logicnode.BooleanNode(tree, value);
|
|
||||||
case "STRING": new armory.logicnode.StringNode(tree, value);
|
|
||||||
case "NONE": new armory.logicnode.NullNode(tree);
|
|
||||||
case "OBJECT": new armory.logicnode.ObjectNode(tree, value);
|
|
||||||
default: new armory.logicnode.DynamicNode(tree, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#end
|
#end
|
||||||
}
|
}
|
||||||
|
|
|
@ -269,6 +269,53 @@ def send_event(event_id: str, opt_data: Any = None):
|
||||||
js = f'LivePatch.patchNodeCopy("{tree_name}", "{node_name}", "{newnode_name}", {props_list}, {inp_data}, {out_data});'
|
js = f'LivePatch.patchNodeCopy("{tree_name}", "{node_name}", "{newnode_name}", {props_list}, {inp_data}, {out_data});'
|
||||||
write_patch(js)
|
write_patch(js)
|
||||||
|
|
||||||
|
elif event_id == 'ln_update_sockets':
|
||||||
|
node: ArmLogicTreeNode = opt_data
|
||||||
|
|
||||||
|
tree_name = arm.node_utils.get_export_tree_name(node.get_tree())
|
||||||
|
node_name = arm.node_utils.get_export_node_name(node)[1:]
|
||||||
|
|
||||||
|
inp_data = '['
|
||||||
|
for idx, inp in enumerate(node.inputs):
|
||||||
|
inp_data += '{'
|
||||||
|
# is_linked can be true even if there are no links if the
|
||||||
|
# user starts dragging a connection away before releasing
|
||||||
|
# the mouse
|
||||||
|
if inp.is_linked and len(inp.links) > 0:
|
||||||
|
inp_data += 'isLinked: true,'
|
||||||
|
inp_data += f'fromNode: "{arm.node_utils.get_export_node_name(inp.links[0].from_node)[1:]}",'
|
||||||
|
inp_data += f'fromIndex: {arm.node_utils.get_socket_index(inp.links[0].from_node.outputs, inp.links[0].from_socket)},'
|
||||||
|
else:
|
||||||
|
inp_data += 'isLinked: false,'
|
||||||
|
inp_data += f'socketType: "{inp.arm_socket_type}",'
|
||||||
|
inp_data += f'socketValue: {arm.node_utils.haxe_format_socket_val(inp.get_default_value())},'
|
||||||
|
|
||||||
|
inp_data += f'toIndex: {idx}'
|
||||||
|
inp_data += '},'
|
||||||
|
inp_data += ']'
|
||||||
|
|
||||||
|
out_data = '['
|
||||||
|
for idx, out in enumerate(node.outputs):
|
||||||
|
out_data += '['
|
||||||
|
for link in out.links:
|
||||||
|
out_data += '{'
|
||||||
|
if out.is_linked:
|
||||||
|
out_data += 'isLinked: true,'
|
||||||
|
out_data += f'toNode: "{arm.node_utils.get_export_node_name(link.to_node)[1:]}",'
|
||||||
|
out_data += f'toIndex: {arm.node_utils.get_socket_index(link.to_node.inputs, link.to_socket)},'
|
||||||
|
else:
|
||||||
|
out_data += 'isLinked: false,'
|
||||||
|
out_data += f'socketType: "{out.arm_socket_type}",'
|
||||||
|
out_data += f'socketValue: {arm.node_utils.haxe_format_socket_val(out.get_default_value())},'
|
||||||
|
|
||||||
|
out_data += f'fromIndex: {idx}'
|
||||||
|
out_data += '},'
|
||||||
|
out_data += '],'
|
||||||
|
out_data += ']'
|
||||||
|
|
||||||
|
js = f'LivePatch.patchSetNodeLinks("{tree_name}", "{node_name}", {inp_data}, {out_data});'
|
||||||
|
write_patch(js)
|
||||||
|
|
||||||
|
|
||||||
def on_operator(operator_id: str):
|
def on_operator(operator_id: str):
|
||||||
"""As long as bpy.msgbus doesn't listen to changes made by
|
"""As long as bpy.msgbus doesn't listen to changes made by
|
||||||
|
|
|
@ -24,6 +24,10 @@ category_items: ODict[str, List['ArmNodeCategory']] = OrderedDict()
|
||||||
|
|
||||||
array_nodes = dict()
|
array_nodes = dict()
|
||||||
|
|
||||||
|
# See ArmLogicTreeNode.update()
|
||||||
|
# format: [tree pointer => (num inputs, num input links, num outputs, num output links)]
|
||||||
|
last_node_state: dict[int, tuple[int, int, int, int]] = {}
|
||||||
|
|
||||||
|
|
||||||
class ArmLogicTreeNode(bpy.types.Node):
|
class ArmLogicTreeNode(bpy.types.Node):
|
||||||
arm_category = PKG_AS_CATEGORY
|
arm_category = PKG_AS_CATEGORY
|
||||||
|
@ -62,6 +66,40 @@ class ArmLogicTreeNode(bpy.types.Node):
|
||||||
def get_tree(self):
|
def get_tree(self):
|
||||||
return self.id_data
|
return self.id_data
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
"""Called if the node was updated in some way, for example
|
||||||
|
if socket connections change. This callback is not called if
|
||||||
|
socket values were changed.
|
||||||
|
"""
|
||||||
|
def num_connected(sockets):
|
||||||
|
return sum([socket.is_linked for socket in sockets])
|
||||||
|
|
||||||
|
# If a link between sockets is removed, there is currently no
|
||||||
|
# _reliable_ way in the Blender API to check which connection
|
||||||
|
# was removed (*).
|
||||||
|
#
|
||||||
|
# So instead we just check _if_ the number of links or sockets
|
||||||
|
# has changed (the update function is called before and after
|
||||||
|
# each link removal). Because we listen for those updates in
|
||||||
|
# general, we automatically also listen to link creation events,
|
||||||
|
# which is more stable than using the dedicated callback for
|
||||||
|
# that (`insert_link()`), because adding links can remove other
|
||||||
|
# links and we would need to react to that as well.
|
||||||
|
#
|
||||||
|
# (*) https://devtalk.blender.org/t/how-to-detect-which-link-was-deleted-by-user-in-node-editor
|
||||||
|
|
||||||
|
self_id = self.as_pointer()
|
||||||
|
|
||||||
|
current_state = (len(self.inputs), num_connected(self.inputs), len(self.outputs), num_connected(self.outputs))
|
||||||
|
if self_id not in last_node_state:
|
||||||
|
# Lazily initialize the last_node_state dict to also store
|
||||||
|
# state for nodes that already exist in the tree
|
||||||
|
last_node_state[self_id] = current_state
|
||||||
|
|
||||||
|
if last_node_state[self_id] != current_state:
|
||||||
|
arm.live_patch.send_event('ln_update_sockets', self)
|
||||||
|
last_node_state[self_id] = current_state
|
||||||
|
|
||||||
def free(self):
|
def free(self):
|
||||||
"""Called before the node is deleted."""
|
"""Called before the node is deleted."""
|
||||||
arm.live_patch.send_event('ln_delete', self)
|
arm.live_patch.send_event('ln_delete', self)
|
||||||
|
@ -84,7 +122,8 @@ class ArmLogicTreeNode(bpy.types.Node):
|
||||||
|
|
||||||
def insert_link(self, link: bpy.types.NodeLink):
|
def insert_link(self, link: bpy.types.NodeLink):
|
||||||
"""Called on *both* nodes when a link between two nodes is created."""
|
"""Called on *both* nodes when a link between two nodes is created."""
|
||||||
arm.live_patch.send_event('ln_insert_link', (self, link))
|
# arm.live_patch.send_event('ln_insert_link', (self, link))
|
||||||
|
pass
|
||||||
|
|
||||||
def get_replacement_node(self, node_tree: bpy.types.NodeTree):
|
def get_replacement_node(self, node_tree: bpy.types.NodeTree):
|
||||||
# needs to be overridden by individual node classes with arm_version>1
|
# needs to be overridden by individual node classes with arm_version>1
|
||||||
|
|
|
@ -68,6 +68,7 @@ def build_node_tree(node_group: 'arm.nodes_logic.ArmLogicTree'):
|
||||||
|
|
||||||
with open(file, 'w', encoding="utf-8") as f:
|
with open(file, 'w', encoding="utf-8") as f:
|
||||||
f.write('package ' + pack_path + '.node;\n\n')
|
f.write('package ' + pack_path + '.node;\n\n')
|
||||||
|
f.write('@:access(armory.logicnode.LogicNode)')
|
||||||
f.write('@:keep class ' + group_name + ' extends armory.logicnode.LogicTree {\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 functionNodes:Map<String, armory.logicnode.FunctionNode>;\n\n')
|
||||||
f.write('\tvar functionOutputNodes:Map<String, armory.logicnode.FunctionOutputNode>;\n\n')
|
f.write('\tvar functionOutputNodes:Map<String, armory.logicnode.FunctionOutputNode>;\n\n')
|
||||||
|
@ -162,8 +163,12 @@ def build_node(node: bpy.types.Node, f: TextIO) -> Optional[str]:
|
||||||
prop = arm.node_utils.haxe_format_prop_value(node, prop_name)
|
prop = arm.node_utils.haxe_format_prop_value(node, prop_name)
|
||||||
f.write('\t\t' + name + '.' + prop_name + ' = ' + prop + ';\n')
|
f.write('\t\t' + name + '.' + prop_name + ' = ' + prop + ';\n')
|
||||||
|
|
||||||
|
# Avoid unnecessary input/output array resizes
|
||||||
|
f.write(f'\t\t{name}.preallocInputs({len(node.inputs)});\n')
|
||||||
|
f.write(f'\t\t{name}.preallocOutputs({len(node.outputs)});\n')
|
||||||
|
|
||||||
# Create inputs
|
# Create inputs
|
||||||
for inp in node.inputs:
|
for idx, inp in enumerate(node.inputs):
|
||||||
# True if the input is connected to a unlinked reroute
|
# True if the input is connected to a unlinked reroute
|
||||||
# somewhere down the reroute line
|
# somewhere down the reroute line
|
||||||
unconnected = False
|
unconnected = False
|
||||||
|
@ -191,34 +196,40 @@ def build_node(node: bpy.types.Node, f: TextIO) -> Optional[str]:
|
||||||
for i in range(0, len(n.outputs)):
|
for i in range(0, len(n.outputs)):
|
||||||
if n.outputs[i] == socket:
|
if n.outputs[i] == socket:
|
||||||
inp_from = i
|
inp_from = i
|
||||||
|
from_type = socket.arm_socket_type
|
||||||
break
|
break
|
||||||
|
|
||||||
# Not linked -> create node with default values
|
# Not linked -> create node with default values
|
||||||
else:
|
else:
|
||||||
inp_name = build_default_node(inp)
|
inp_name = build_default_node(inp)
|
||||||
inp_from = 0
|
inp_from = 0
|
||||||
|
from_type = inp.arm_socket_type
|
||||||
|
|
||||||
# The input is linked to a reroute, but the reroute is unlinked
|
# The input is linked to a reroute, but the reroute is unlinked
|
||||||
if unconnected:
|
if unconnected:
|
||||||
inp_name = build_default_node(inp)
|
inp_name = build_default_node(inp)
|
||||||
inp_from = 0
|
inp_from = 0
|
||||||
|
from_type = inp.arm_socket_type
|
||||||
|
|
||||||
# Add input
|
# Add input
|
||||||
f.write('\t\t' + name + '.addInput(' + inp_name + ', ' + str(inp_from) + ');\n')
|
f.write(f'\t\t{"var __link = " if wrd.arm_live_patch else ""}armory.logicnode.LogicNode.addLink({inp_name}, {name}, {inp_from}, {idx});\n')
|
||||||
|
if wrd.arm_live_patch:
|
||||||
|
to_type = inp.arm_socket_type
|
||||||
|
f.write(f'\t\t__link.fromType = "{from_type}";')
|
||||||
|
f.write(f'\t\t__link.toType = "{to_type}";')
|
||||||
|
f.write(f'\t\t__link.toValue = {arm.node_utils.haxe_format_socket_val(inp.get_default_value())};')
|
||||||
|
|
||||||
# Create outputs
|
# Create outputs
|
||||||
for out in node.outputs:
|
for idx, out in enumerate(node.outputs):
|
||||||
if out.is_linked:
|
# Linked outputs are already handled after iterating over inputs
|
||||||
out_name = ''
|
# above, so only unconnected outputs are handled here
|
||||||
for node in collect_nodes_from_output(out, f):
|
if not out.is_linked:
|
||||||
out_name += '[' if len(out_name) == 0 else ', '
|
f.write(f'\t\t{"var __link = " if wrd.arm_live_patch else ""}armory.logicnode.LogicNode.addLink({name}, {build_default_node(out)}, {idx}, 0);\n')
|
||||||
out_name += node
|
if wrd.arm_live_patch:
|
||||||
out_name += ']'
|
out_type = out.arm_socket_type
|
||||||
# Not linked - create node with default values
|
f.write(f'\t\t__link.fromType = "{out_type}";')
|
||||||
else:
|
f.write(f'\t\t__link.toType = "{out_type}";')
|
||||||
out_name = '[' + build_default_node(out) + ']'
|
f.write(f'\t\t__link.toValue = {arm.node_utils.haxe_format_socket_val(out.get_default_value())};')
|
||||||
# Add outputs
|
|
||||||
f.write('\t\t' + name + '.addOutputs(' + out_name + ');\n')
|
|
||||||
|
|
||||||
return name
|
return name
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue