Merge pull request #2283 from MoritzBrueckner/live-patch

Live patch: refactor and logic nodes support
This commit is contained in:
Lubos Lenco 2021-07-26 09:00:31 +02:00 committed by GitHub
commit ce75516c59
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
366 changed files with 3120 additions and 2099 deletions

View file

@ -1,13 +1,11 @@
package armory.logicnode;
import armory.trait.physics.PhysicsConstraint;
#if arm_physics
import armory.trait.physics.bullet.PhysicsConstraint.ConstraintType;
import armory.trait.physics.bullet.PhysicsConstraint.ConstraintAxis;
#end
import iron.object.Object;
import armory.trait.physics.RigidBody;
import armory.logicnode.PhysicsConstraintNode;
#if arm_physics
import armory.trait.physics.PhysicsConstraint;
import armory.trait.physics.bullet.PhysicsConstraint.ConstraintType;
#end
class AddPhysicsConstraintNode extends LogicNode {
@ -21,115 +19,96 @@ class AddPhysicsConstraintNode extends LogicNode {
}
override function run(from: Int) {
var pivotObject:Object = inputs[1].get();
var pivotObject: Object = inputs[1].get();
rb1 = inputs[2].get();
rb2 = inputs[3].get();
var disableCollisions: Bool = inputs[4].get();
var breakable: Bool = inputs[5].get();
var breakingThreshold: Float = inputs[6].get();
var type: ConstraintType = 0;
if (pivotObject == null || rb1 == null || rb2 == null) return;
#if arm_physics
var disableCollisions: Bool = inputs[4].get();
var breakable: Bool = inputs[5].get();
var breakingThreshold: Float = inputs[6].get();
var type: ConstraintType = 0;
var con: PhysicsConstraint = pivotObject.getTrait(PhysicsConstraint);
if(con == null)
{
switch(property0)
{
case 'Fixed':
type = Fixed;
case 'Point':
type = Point;
case 'Hinge':
type = Hinge;
case 'Slider':
type = Slider;
case 'Piston':
type = Piston;
case 'Generic Spring':
type = Generic;
if (con == null) {
switch (property0) {
case "Fixed": type = Fixed;
case "Point": type = Point;
case "Hinge": type = Hinge;
case "Slider": type = Slider;
case "Piston": type = Piston;
case "Generic Spring": type = Generic;
}
if(! breakable) breakingThreshold = 0.0;
if (!breakable) breakingThreshold = 0.0;
if (type != Generic) {
if(type != Generic) {
con = new PhysicsConstraint(rb1, rb2, type, disableCollisions, breakingThreshold);
switch (type)
{
switch (type) {
case Hinge:
var setLimit:Bool = inputs[7].get();
var low:Float = inputs[8].get();
var up:Float = inputs[9].get();
var setLimit: Bool = inputs[7].get();
var low: Float = inputs[8].get();
var up: Float = inputs[9].get();
con.setHingeConstraintLimits(setLimit, low, up);
case Slider:
var setLimit:Bool = inputs[7].get();
var low:Float = inputs[8].get();
var up:Float = inputs[9].get();
var setLimit: Bool = inputs[7].get();
var low: Float = inputs[8].get();
var up: Float = inputs[9].get();
con.setSliderConstraintLimits(setLimit, low, up);
case Piston:
var setLinLimit:Bool = inputs[7].get();
var linLow:Float = inputs[8].get();
var linUp:Float = inputs[9].get();
var setAngLimit:Bool = inputs[10].get();
var angLow:Float = inputs[11].get();
var angUp:Float = inputs[12].get();
var setLinLimit: Bool = inputs[7].get();
var linLow: Float = inputs[8].get();
var linUp: Float = inputs[9].get();
var setAngLimit: Bool = inputs[10].get();
var angLow: Float = inputs[11].get();
var angUp: Float = inputs[12].get();
con.setPistonConstraintLimits(setLinLimit, linLow, linUp, setAngLimit, angLow, angUp);
default:
default:
}
}
else
{
else {
var spring: Bool = false;
var prop: PhysicsConstraintNode;
for(inp in 7...inputs.length)
{
for (inp in 7...inputs.length) {
prop = inputs[inp].get();
if(prop == null) continue;
if(prop.isSpring)
{
if (prop == null) continue;
if (prop.isSpring) {
spring = true;
break;
}
}
if(spring) {
if (spring) {
con = new PhysicsConstraint(rb1, rb2, GenericSpring, disableCollisions, breakingThreshold);
}
}
else {
con = new PhysicsConstraint(rb1, rb2, Generic, disableCollisions, breakingThreshold);
}
for(inp in 7...inputs.length)
{
for (inp in 7...inputs.length) {
prop = inputs[inp].get();
if(prop == null) continue;
(inp + ': ');
if (prop == null) continue;
if(prop.isSpring)
{
if (prop.isSpring) {
con.setSpringParams(prop.isSpring, prop.value1, prop.value2, prop.axis, prop.isAngular);
}
else
{
else {
con.setGenericConstraintLimits(true, prop.value1, prop.value2, prop.axis, prop.isAngular);
}
}
}
pivotObject.addTrait(con);
}
#end
runOutput(0);
}
}

View file

@ -1,15 +1,17 @@
package armory.logicnode;
import iron.object.Object;
#if arm_physics
import armory.trait.physics.RigidBody;
import armory.trait.physics.bullet.RigidBody.Shape;
#end
import iron.object.Object;
import armory.trait.physics.RigidBody;
class AddRigidBodyNode extends LogicNode {
public var property0: String;//Shape
public var property1: String;//Advanced
public var property0: String; //Shape
public var property1: Bool; //Advanced
public var object: Object;
public function new(tree: LogicTree) {
@ -18,6 +20,10 @@ class AddRigidBodyNode extends LogicNode {
override function run(from: Int) {
object = inputs[1].get();
if (object == null) return;
#if arm_physics
var mass: Float = inputs[2].get();
var active: Bool = inputs[3].get();
var animated: Bool = inputs[4].get();
@ -38,8 +44,7 @@ class AddRigidBodyNode extends LogicNode {
var shape: Shape = 1;
if(property1 == 'true')
{
if (property1) {
margin = inputs[9].get();
marginLen = inputs[10].get();
linDamp = inputs[11].get();
@ -49,50 +54,34 @@ class AddRigidBodyNode extends LogicNode {
angVelThreshold = inputs[15].get();
group = inputs[16].get();
mask = inputs[17].get();
}
if (object == null) return;
#if arm_physics
var rb: RigidBody = object.getTrait(RigidBody);
if((group < 0) || (group > 32)) group = 1; //Limiting max groups to 32
if((mask < 0) || (mask > 32)) mask = 1; //Limiting max masks to 32
if(rb == null)
{
switch (property0){
case 'Box':
shape = Box;
case 'Sphere':
shape = Sphere;
case 'Capsule':
shape = Capsule;
case 'Cone':
shape = Cone;
case 'Cylinder':
shape = Cylinder;
case 'Convex Hull':
shape = ConvexHull;
case 'Mesh':
shape = Mesh;
if ((group < 0) || (group > 32)) group = 1; //Limiting max groups to 32
if ((mask < 0) || (mask > 32)) mask = 1; //Limiting max masks to 32
if (rb == null) {
switch (property0) {
case "Box": shape = Box;
case "Sphere": shape = Sphere;
case "Capsule": shape = Capsule;
case "Cone": shape = Cone;
case "Cylinder": shape = Cylinder;
case "Convex Hull": shape = ConvexHull;
case "Mesh": shape = Mesh;
}
rb = new RigidBody(shape, mass, friction, bounciness, group, mask);
rb.animated = animated;
rb.staticObj = ! active;
rb.staticObj = !active;
rb.isTriggerObject(trigger);
if(property1 == 'true')
{
if (property1) {
rb.linearDamping = linDamp;
rb.angularDamping = angDamp;
if(margin) rb.collisionMargin = marginLen;
if(useDeactiv) {
if (margin) rb.collisionMargin = marginLen;
if (useDeactiv) {
rb.setUpDeactivation(true, linearVelThreshold, angVelThreshold, 0.0);
}
}
object.addTrait(rb);

View file

@ -1,31 +1,136 @@
package armory.logicnode;
#if arm_patch @:keep @:keepSub #end
class LogicNode {
var tree: LogicTree;
var inputs: Array<LogicNodeInput> = [];
var outputs: Array<Array<LogicNode>> = [];
var inputs: Array<LogicNodeLink> = [];
var outputs: Array<Array<LogicNodeLink>> = [];
#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) {
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 outLen...fromIndex + 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.
@param from impulse index
@ -38,47 +143,45 @@ class LogicNode {
**/
function runOutput(i: Int) {
if (i >= outputs.length) return;
for (output in outputs[i]) {
// Check which input activated the node
for (j in 0...output.inputs.length) {
// Check if the node is connected to the current node
if (output.inputs[j].node == this) {
// Check if the input socekt is linked to current output socket
if (output.inputs[j].from == i) {
output.run(j);
break;
}
}
}
for (outLink in outputs[i]) {
outLink.toNode.run(outLink.toIndex);
}
}
@:allow(armory.logicnode.LogicNodeInput)
@:allow(armory.logicnode.LogicNodeLink)
function get(from: Int): Dynamic { return this; }
@:allow(armory.logicnode.LogicNodeInput)
@:allow(armory.logicnode.LogicNodeLink)
function set(value: Dynamic) {}
}
class LogicNodeInput {
@:allow(armory.logicnode.LogicNode)
@:allow(armory.logicnode.LogicTree)
class LogicNodeLink {
@:allow(armory.logicnode.LogicNode)
var node: LogicNode;
@:allow(armory.logicnode.LogicNode)
var from: Int; // Socket index
var fromNode: LogicNode;
var toNode: LogicNode;
var fromIndex: Int;
var toIndex: Int;
public function new(node: LogicNode, from: Int) {
this.node = node;
this.from = from;
#if arm_patch
var fromType: String;
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)
function get(): Dynamic {
return node.get(from);
inline function get(): Dynamic {
return fromNode.get(fromIndex);
}
@:allow(armory.logicnode.LogicNode)
function set(value: Dynamic) {
node.set(value);
inline function set(value: Dynamic) {
fromNode.set(value);
}
}

View file

@ -2,10 +2,26 @@ package armory.logicnode;
class LogicTree extends iron.Trait {
#if arm_patch
/**
Stores all trait instances of the tree via its name.
**/
public static var nodeTrees = new Map<String, Array<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() {}

View file

@ -3,7 +3,7 @@ package armory.logicnode;
class MathNode extends LogicNode {
public var property0: String; // Operation
public var property1: String; // Clamp
public var property1: Bool; // Clamp
public function new(tree: LogicTree) {
super(tree);
@ -80,8 +80,8 @@ class MathNode extends LogicNode {
}
}
// Clamp
if (property1 == "true") r = r < 0.0 ? 0.0 : (r > 1.0 ? 1.0 : r);
if (property1) r = r < 0.0 ? 0.0 : (r > 1.0 ? 1.0 : r);
return r;
}
}
}

View file

@ -6,7 +6,7 @@ class MixNode extends LogicNode {
public var property0: String; // Type
public var property1: String; // Ease
public var property2: String; // Clamp
public var property2: Bool; // Clamp
var ease: Float->Float = null;
@ -50,7 +50,9 @@ class MixNode extends LogicNode {
var v2: Float = inputs[2].get();
var f = v1 + (v2 - v1) * ease(k);
if (property2 == "true") f = f < 0 ? 0 : f > 1 ? 1 : f;
// Clamp
if (property2) f = f < 0 ? 0 : f > 1 ? 1 : f;
return f;
}
}

View file

@ -21,7 +21,7 @@ class ObjectNode extends LogicNode {
override function set(value: Dynamic) {
if (inputs.length > 0) inputs[0].set(value);
else {
objectName = value.name;
objectName = value != null ? value.name : "";
this.value = value;
}
}

View file

@ -3,50 +3,39 @@ package armory.logicnode;
#if arm_physics
import armory.trait.physics.bullet.PhysicsConstraint.ConstraintAxis;
#end
import iron.object.Object;
class PhysicsConstraintNode extends LogicNode {
public var property0: String;//Linear or Angular
public var property1: String;//Axis
public var property2: String;//Is a spring
public var value1: Float;//Lower limit or Spring Stiffness
public var value2: Float;//Upper limit or Spring Damping
public var property0: String; //Linear or Angular
public var property1: String; //Axis
public var property2: Bool; //Is a spring
#if arm_physics
public var value1: Float; //Lower limit or Spring Stiffness
public var value2: Float; //Upper limit or Spring Damping
public var isAngular: Bool;
public var axis: ConstraintAxis;
public var isSpring: Bool;
#end
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): PhysicsConstraintNode {
#if arm_physics
value1 = inputs[0].get();
value2 = inputs[1].get();
if(property0 == 'Linear') {
isAngular = false;
}
else{
isAngular = true;
}
isAngular = property0 != "Linear";
isSpring = property2;
if(property2 == 'true'){
isSpring = true;
switch (property1) {
case "X": axis = X;
case "Y": axis = Y;
case "Z": axis = Z;
}
else {
isSpring = false;
}
switch (property1){
case 'X':
axis = X;
case 'Y':
axis = Y;
case 'Z':
axis = Z;
}
#end
return this;
}
}

View file

@ -11,10 +11,10 @@ class QuaternionNode extends LogicNode {
super(tree);
if (x != null) {
addInput(new FloatNode(tree, x), 0);
addInput(new FloatNode(tree, y), 0);
addInput(new FloatNode(tree, z), 0);
addInput(new FloatNode(tree, w), 0);
LogicNode.addLink(new FloatNode(tree, x), this, 0, 0);
LogicNode.addLink(new FloatNode(tree, y), this, 0, 1);
LogicNode.addLink(new FloatNode(tree, z), this, 0, 2);
LogicNode.addLink(new FloatNode(tree, w), this, 0, 3);
}
}
@ -27,15 +27,15 @@ class QuaternionNode extends LogicNode {
switch (from){
case 0:
return value;
case 1:
var value1 = new Vec4();
case 1:
var value1 = new Vec4();
value1.x = value.x;
value1.y = value.y;
value1.z = value.z;
value1.w = 0; // use 0 to avoid this vector being translated.
return value1;
case 2:
return value.w;
return value.w;
default:
return null;
}

View file

@ -14,8 +14,9 @@ class SetParentNode extends LogicNode {
var parent: Object;
var isUnparent = false;
if (Std.isOfType(inputs[2].node, ObjectNode)) {
var parentNode = cast(inputs[2].node, ObjectNode);
if (Std.isOfType(inputs[2].fromNode, ObjectNode)) {
var parentNode = cast(inputs[2].fromNode, ObjectNode);
isUnparent = parentNode.objectName == "";
}
if (isUnparent) parent = iron.Scene.active.root;
@ -24,7 +25,7 @@ class SetParentNode extends LogicNode {
if (object == null || parent == null || object.parent == parent) return;
object.parent.removeChild(object, isUnparent); // keepTransform
#if arm_physics
var rigidBody = object.getTrait(RigidBody);
if (rigidBody != null) rigidBody.setActivationState(0);

View file

@ -7,7 +7,7 @@ class VectorMixNode extends LogicNode {
public var property0: String; // Type
public var property1: String; // Ease
public var property2: String; // Clamp
public var property2: Bool; // Clamp
var v = new Vec4();
@ -57,7 +57,7 @@ class VectorMixNode extends LogicNode {
v.y = v1.y + (v2.y - v1.y) * f;
v.z = v1.z + (v2.z - v1.z) * f;
if (property2 == "true") v.clamp(0, 1);
if (property2) v.clamp(0, 1);
return v;
}
}

View file

@ -10,9 +10,9 @@ class VectorNode extends LogicNode {
super(tree);
if (x != null) {
addInput(new FloatNode(tree, x), 0);
addInput(new FloatNode(tree, y), 0);
addInput(new FloatNode(tree, z), 0);
LogicNode.addLink(new FloatNode(tree, x), this, 0, 0);
LogicNode.addLink(new FloatNode(tree, y), this, 0, 1);
LogicNode.addLink(new FloatNode(tree, z), this, 0, 2);
}
}

View file

@ -11,6 +11,7 @@ class Bridge {
public static var Input = iron.system.Input;
public static var Object = iron.object.Object;
public static var Data = iron.data.Data;
public static var Vec4 = iron.math.Vec4;
public static function log(s: String) { trace(s); };
}

View file

@ -1,8 +1,17 @@
package armory.trait.internal;
import armory.logicnode.LogicNode;
import armory.logicnode.LogicTree;
#if arm_patch @:expose("LivePatch") #end
@:access(armory.logicnode.LogicNode)
@:access(armory.logicnode.LogicNodeLink)
class LivePatch extends iron.Trait {
#if arm_patch
#if !arm_patch
public function new() { super(); }
#else
static var patchId = 0;
@ -23,9 +32,164 @@ class LivePatch extends iron.Trait {
});
}
#else
public static function patchCreateNodeLink(treeName: String, fromNodeName: String, toNodeName: String, fromIndex: Int, toIndex: Int) {
if (!LogicTree.nodeTrees.exists(treeName)) return;
var trees = LogicTree.nodeTrees[treeName];
public function new() { super(); }
for (tree in trees) {
var fromNode = tree.nodes[fromNodeName];
var toNode = tree.nodes[toNodeName];
if (fromNode == null || toNode == null) return;
LogicNode.addLink(fromNode, toNode, fromIndex, toIndex);
}
}
public static function patchSetNodeLinks(treeName: String, nodeName: String, inputDatas: Array<Dynamic>, outputDatas: Array<Array<Dynamic>>) {
if (!LogicTree.nodeTrees.exists(treeName)) return;
var trees = LogicTree.nodeTrees[treeName];
for (tree in trees) {
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);
}
}
}
}
public static function patchUpdateNodeProp(treeName: String, nodeName: String, propName: String, value: Dynamic) {
if (!LogicTree.nodeTrees.exists(treeName)) return;
var trees = LogicTree.nodeTrees[treeName];
for (tree in trees) {
var node = tree.nodes[nodeName];
if (node == null) return;
Reflect.setField(node, propName, value);
}
}
public static function patchUpdateNodeInputVal(treeName: String, nodeName: String, socketIndex: Int, value: Dynamic) {
if (!LogicTree.nodeTrees.exists(treeName)) return;
var trees = LogicTree.nodeTrees[treeName];
for (tree in trees) {
var node = tree.nodes[nodeName];
if (node == null) return;
node.inputs[socketIndex].set(value);
}
}
public static function patchNodeDelete(treeName: String, nodeName: String) {
if (!LogicTree.nodeTrees.exists(treeName)) return;
var trees = LogicTree.nodeTrees[treeName];
for (tree in trees) {
var node = tree.nodes[nodeName];
if (node == null) return;
node.clearOutputs();
node.clearInputs();
tree.nodes.remove(nodeName);
}
}
public static function patchNodeCreate(treeName: String, nodeName: String, nodeType: String, propDatas: Array<Array<Dynamic>>, inputDatas: Array<Array<Dynamic>>, outputDatas: Array<Array<Dynamic>>) {
if (!LogicTree.nodeTrees.exists(treeName)) return;
var trees = LogicTree.nodeTrees[treeName];
for (tree in trees) {
// No further constructor parameters required here, all variable nodes
// use optional further parameters and all values are set later in this
// function.
var newNode: LogicNode = Type.createInstance(Type.resolveClass(nodeType), [tree]);
newNode.name = nodeName;
tree.nodes[nodeName] = newNode;
for (propData in propDatas) {
Reflect.setField(newNode, propData[0], propData[1]);
}
var i = 0;
for (inputData in inputDatas) {
LogicNode.addLink(LogicNode.createSocketDefaultNode(newNode.tree, inputData[0], inputData[1]), newNode, 0, i++);
}
i = 0;
for (outputData in outputDatas) {
LogicNode.addLink(newNode, LogicNode.createSocketDefaultNode(newNode.tree, outputData[0], outputData[1]), i++, 0);
}
}
}
public static function patchNodeCopy(treeName: String, nodeName: String, newNodeName: String, copyProps: Array<String>, inputDatas: Array<Array<Dynamic>>, outputDatas: Array<Array<Dynamic>>) {
if (!LogicTree.nodeTrees.exists(treeName)) return;
var trees = LogicTree.nodeTrees[treeName];
for (tree in trees) {
var node = tree.nodes[nodeName];
if (node == null) return;
// No further constructor parameters required here, all variable nodes
// use optional further parameters and all values are set later in this
// function.
var newNode: LogicNode = Type.createInstance(Type.getClass(node), [tree]);
newNode.name = newNodeName;
tree.nodes[newNodeName] = newNode;
for (propName in copyProps) {
Reflect.setField(newNode, propName, Reflect.field(node, propName));
}
var i = 0;
for (inputData in inputDatas) {
LogicNode.addLink(LogicNode.createSocketDefaultNode(newNode.tree, inputData[0], inputData[1]), newNode, 0, i++);
}
i = 0;
for (outputData in outputDatas) {
LogicNode.addLink(newNode, LogicNode.createSocketDefaultNode(newNode.tree, outputData[0], outputData[1]), i++, 0);
}
}
}
#end
}

View file

@ -1531,24 +1531,18 @@ Make sure the mesh only has tris/quads.""")
# Less bias for bigger maps
out_light['shadows_bias'] *= 1 / (out_light['shadowmap_size'] / 1024)
elif objtype == 'POINT':
out_light['strength'] *= 2.6
if bpy.app.version >= (2, 80, 72):
out_light['strength'] *= 0.01
out_light['strength'] *= 0.01
out_light['fov'] = 1.5708 # pi/2
out_light['shadowmap_cube'] = True
if light_ref.shadow_soft_size > 0.1:
out_light['light_size'] = light_ref.shadow_soft_size * 10
elif objtype == 'SPOT':
out_light['strength'] *= 2.6
if bpy.app.version >= (2, 80, 72):
out_light['strength'] *= 0.01
out_light['strength'] *= 0.01
out_light['spot_size'] = math.cos(light_ref.spot_size / 2)
# Cycles defaults to 0.15
out_light['spot_blend'] = light_ref.spot_blend / 10
elif objtype == 'AREA':
out_light['strength'] *= 80.0 / (light_ref.size * light_ref.size_y)
if bpy.app.version >= (2, 80, 72):
out_light['strength'] *= 0.01
out_light['strength'] *= 0.01
out_light['size'] = light_ref.size
out_light['size_y'] = light_ref.size_y
@ -2460,7 +2454,7 @@ Make sure the mesh only has tris/quads.""")
else:
self.material_to_object_dict[mat] = [bobject]
self.material_to_arm_object_dict[mat] = [o]
# Add UniformsManager trait
if type is NodeType.MESH:
uniformManager = {}
@ -2678,7 +2672,7 @@ Make sure the mesh only has tris/quads.""")
}
self.output['traits'].append(out_trait)
if wrd.arm_live_patch:
if arm.utils.is_livepatch_enabled():
if 'traits' not in self.output:
self.output['traits'] = []
out_trait = {'type': 'Script', 'class_name': 'armory.trait.internal.LivePatch'}

View file

@ -7,6 +7,7 @@ import bpy
from bpy.app.handlers import persistent
import arm.api
import arm.live_patch as live_patch
import arm.logicnode.arm_nodes as arm_nodes
import arm.nodes_logic
import arm.make as make
@ -14,6 +15,7 @@ import arm.make_state as state
import arm.props as props
import arm.utils
@persistent
def on_depsgraph_update_post(self):
if state.proc_build != None:
@ -41,12 +43,10 @@ def on_depsgraph_update_post(self):
# Send last operator to Krom
wrd = bpy.data.worlds['Arm']
if state.proc_play != None and \
state.target == 'krom' and \
wrd.arm_live_patch:
if state.proc_play is not None and state.target == 'krom' and wrd.arm_live_patch:
ops = bpy.context.window_manager.operators
if len(ops) > 0 and ops[-1] != None:
send_operator(ops[-1])
if len(ops) > 0 and ops[-1] is not None:
live_patch.on_operator(ops[-1].bl_idname)
# Hacky solution to update armory props after operator executions
last_operator = bpy.context.active_operator
@ -125,6 +125,7 @@ def poll_threads() -> float:
appended_py_paths = []
context_screen = None
@persistent
def on_load_post(context):
global appended_py_paths

376
blender/arm/live_patch.py Normal file
View file

@ -0,0 +1,376 @@
import os
import shutil
from typing import Any, Type
import bpy
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
# Current patch id
patch_id = 0
# Any object can act as a message bus owner
msgbus_owner = object()
# Whether live patch is currently active
__running = False
def start():
"""Start the live patch session."""
log.debug("Live patch session started")
listen(bpy.types.Object, "location", "obj_location")
listen(bpy.types.Object, "rotation_euler", "obj_rotation")
listen(bpy.types.Object, "scale", "obj_scale")
# 'energy' is defined in sub classes only, also workaround for
# https://developer.blender.org/T88408
for light_type in (bpy.types.AreaLight, bpy.types.PointLight, bpy.types.SpotLight, bpy.types.SunLight):
listen(light_type, "color", "light_color")
listen(light_type, "energy", "light_energy")
global __running
__running = True
def stop():
"""Stop the live patch session."""
global __running
if __running:
__running = False
log.debug("Live patch session stopped")
bpy.msgbus.clear_by_owner(msgbus_owner)
def patch_export():
"""Re-export the current scene and update the game accordingly."""
if not __running or state.proc_build is not None:
return
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'
ArmoryExporter.export_scene(bpy.context, asset_path, scene=bpy.context.scene)
dir_std_shaders_dst = os.path.join(arm.utils.build_dir(), 'compiled', 'Shaders', 'std')
if not os.path.isdir(dir_std_shaders_dst):
dir_std_shaders_src = os.path.join(arm.utils.get_sdk_path(), 'armory', 'Shaders', 'std')
shutil.copytree(dir_std_shaders_src, dir_std_shaders_dst)
node_path = arm.utils.get_node_path()
khamake_path = arm.utils.get_khamake_path()
cmd = [
node_path, khamake_path, 'krom',
'--shaderversion', '330',
'--parallelAssetConversion', '4',
'--to', arm.utils.build_dir() + '/debug',
'--nohaxe',
'--noproject'
]
arm.assets.invalidate_enabled = True
state.proc_build = make.run_proc(cmd, patch_done)
def patch_done():
"""Signal Iron to reload the running scene after a re-export."""
js = 'iron.Scene.patch();'
write_patch(js)
state.proc_build = None
def write_patch(js: str):
"""Write the given javascript code to 'krom.patch'."""
global patch_id
with open(arm.utils.get_fp_build() + '/debug/krom/krom.patch', 'w') as f:
patch_id += 1
f.write(str(patch_id) + '\n')
f.write(js)
def listen(rna_type: Type[bpy.types.bpy_struct], prop: str, event_id: str):
"""Subscribe to '<rna_type>.<prop>'. The event_id can be choosen
freely but must match with the id used in send_event().
"""
bpy.msgbus.subscribe_rna(
key=(rna_type, prop),
owner=msgbus_owner,
args=(event_id, ),
notify=send_event
# options={"PERSISTENT"}
)
def send_event(event_id: str, opt_data: Any = None):
"""Send the result of the given event to Krom."""
if not __running:
return
if hasattr(bpy.context, 'object') and bpy.context.object is not None:
obj = bpy.context.object.name
if bpy.context.object.mode == "OBJECT":
if event_id == "obj_location":
vec = bpy.context.object.location
js = f'var o = iron.Scene.active.getChild("{obj}"); o.transform.loc.set({vec[0]}, {vec[1]}, {vec[2]}); o.transform.dirty = true;'
write_patch(js)
elif event_id == 'obj_scale':
vec = bpy.context.object.scale
js = f'var o = iron.Scene.active.getChild("{obj}"); o.transform.scale.set({vec[0]}, {vec[1]}, {vec[2]}); o.transform.dirty = true;'
write_patch(js)
elif event_id == 'obj_rotation':
vec = bpy.context.object.rotation_euler.to_quaternion()
js = f'var o = iron.Scene.active.getChild("{obj}"); o.transform.rot.set({vec[1]}, {vec[2]}, {vec[3]}, {vec[0]}); o.transform.dirty = true;'
write_patch(js)
elif event_id == 'light_color':
light: bpy.types.Light = bpy.context.object.data
vec = light.color
js = f'var lRaw = iron.Scene.active.getLight("{light.name}").data.raw; lRaw.color[0]={vec[0]}; lRaw.color[1]={vec[1]}; lRaw.color[2]={vec[2]};'
write_patch(js)
elif event_id == 'light_energy':
light: bpy.types.Light = bpy.context.object.data
# Align strength to Armory, see exporter.export_light()
# TODO: Use exporter.export_light() and simply reload all raw light data in Iron?
strength_fac = 1.0
if light.type == 'SUN':
strength_fac = 0.325
elif light.type in ('POINT', 'SPOT', 'AREA'):
strength_fac = 0.01
js = f'var lRaw = iron.Scene.active.getLight("{light.name}").data.raw; lRaw.strength={light.energy * strength_fac};'
write_patch(js)
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:
tree_name = arm.node_utils.get_export_tree_name(node.get_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)
elif event_id == 'ln_update_prop':
node: ArmLogicTreeNode
prop_name: str
node, prop_name = 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:]
value = arm.node_utils.haxe_format_prop_value(node, prop_name)
if prop_name.endswith('_get'):
# Hack because some nodes use a different Python property
# name than they use in Haxe
prop_name = prop_name[:-4]
js = f'LivePatch.patchUpdateNodeProp("{tree_name}", "{node_name}", "{prop_name}", {value});'
write_patch(js)
elif event_id == 'ln_socket_val':
node: ArmLogicTreeNode
socket: bpy.types.NodeSocket
node, socket = opt_data
socket_index = arm.node_utils.get_socket_index(node.inputs, socket)
if socket_index != -1:
tree_name = arm.node_utils.get_export_tree_name(node.get_tree())
node_name = arm.node_utils.get_export_node_name(node)[1:]
value = socket.get_default_value()
inp_type = socket.arm_socket_type
if inp_type in ('VECTOR', 'RGB'):
value = f'new iron.Vec4({arm.node_utils.haxe_format_socket_val(value, array_outer_brackets=False)}, 1.0)'
elif inp_type == 'RGBA':
value = f'new iron.Vec4({arm.node_utils.haxe_format_socket_val(value, array_outer_brackets=False)})'
elif inp_type == 'OBJECT':
value = f'iron.Scene.active.getChild("{value}")' if value != '' else 'null'
else:
value = arm.node_utils.haxe_format_socket_val(value)
js = f'LivePatch.patchUpdateNodeInputVal("{tree_name}", "{node_name}", {socket_index}, {value});'
write_patch(js)
elif event_id == 'ln_create':
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:]
node_type = 'armory.logicnode.' + node.bl_idname[2:]
prop_names = list(arm.node_utils.get_haxe_property_names(node))
prop_py_names, prop_hx_names = zip(*prop_names) if len(prop_names) > 0 else ([], [])
prop_values = (getattr(node, prop_name) for prop_name in prop_py_names)
prop_datas = arm.node_utils.haxe_format_socket_val(list(zip(prop_hx_names, prop_values)))
inp_data = [(inp.arm_socket_type, inp.get_default_value()) for inp in node.inputs]
inp_data = arm.node_utils.haxe_format_socket_val(inp_data)
out_data = [(out.arm_socket_type, out.get_default_value()) for out in node.outputs]
out_data = arm.node_utils.haxe_format_socket_val(out_data)
js = f'LivePatch.patchNodeCreate("{tree_name}", "{node_name}", "{node_type}", {prop_datas}, {inp_data}, {out_data});'
write_patch(js)
elif event_id == 'ln_delete':
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:]
js = f'LivePatch.patchNodeDelete("{tree_name}", "{node_name}");'
write_patch(js)
elif event_id == 'ln_copy':
newnode: ArmLogicTreeNode
node: ArmLogicTreeNode
newnode, node = opt_data
# Use newnode to get the tree, node has no id_data at this moment
tree_name = arm.node_utils.get_export_tree_name(newnode.get_tree())
newnode_name = arm.node_utils.get_export_node_name(newnode)[1:]
node_name = arm.node_utils.get_export_node_name(node)[1:]
props_list = '[' + ','.join(f'"{p}"' for _, p in arm.node_utils.get_haxe_property_names(node)) + ']'
inp_data = [(inp.arm_socket_type, inp.get_default_value()) for inp in newnode.inputs]
inp_data = arm.node_utils.haxe_format_socket_val(inp_data)
out_data = [(out.arm_socket_type, out.get_default_value()) for out in newnode.outputs]
out_data = arm.node_utils.haxe_format_socket_val(out_data)
js = f'LivePatch.patchNodeCopy("{tree_name}", "{node_name}", "{newnode_name}", {props_list}, {inp_data}, {out_data});'
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):
"""As long as bpy.msgbus doesn't listen to changes made by
operators (*), additionally notify the callback manually.
(*) https://developer.blender.org/T72109
"""
if not __running:
return
if operator_id in IGNORE_OPERATORS:
return
if operator_id == 'TRANSFORM_OT_translate':
send_event('obj_location')
elif operator_id in ('TRANSFORM_OT_rotate', 'TRANSFORM_OT_trackball'):
send_event('obj_rotation')
elif operator_id == 'TRANSFORM_OT_resize':
send_event('obj_scale')
# Rebuild
else:
patch_export()
# Don't re-export the scene for the following operators
IGNORE_OPERATORS = (
'ARM_OT_node_add_input',
'ARM_OT_node_add_input_output',
'ARM_OT_node_add_input_value',
'ARM_OT_node_add_output',
'ARM_OT_node_call_func',
'ARM_OT_node_remove_input',
'ARM_OT_node_remove_input_output',
'ARM_OT_node_remove_input_value',
'ARM_OT_node_remove_output',
'ARM_OT_node_search',
'NODE_OT_delete',
'NODE_OT_duplicate_move',
'NODE_OT_hide_toggle',
'NODE_OT_link',
'NODE_OT_move_detach_links',
'NODE_OT_select',
'NODE_OT_translate_attach',
'NODE_OT_translate_attach_remove_on_cancel',
'OBJECT_OT_editmode_toggle',
'OUTLINER_OT_item_activate',
'UI_OT_button_string_clear',
'UI_OT_eyedropper_id',
'VIEW3D_OT_select',
'VIEW3D_OT_select_box',
)

View file

@ -6,8 +6,7 @@ class AnimActionNode(ArmLogicTreeNode):
bl_label = 'Action'
arm_version = 1
def init(self, context):
super(AnimActionNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketAnimAction', 'Action')
self.add_output('ArmNodeSocketAnimAction', 'Action', is_var=True)

View file

@ -6,12 +6,11 @@ class BlendActionNode(ArmLogicTreeNode):
bl_label = 'Blend Action'
arm_version = 1
def init(self, context):
super(BlendActionNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('ArmNodeSocketObject', 'Object')
self.add_input('ArmNodeSocketAnimAction', 'Action 1')
self.add_input('ArmNodeSocketAnimAction', 'Action 2')
self.add_input('NodeSocketFloat', 'Factor', default_value = 0.5)
self.add_input('ArmFloatSocket', 'Factor', default_value = 0.5)
self.add_output('ArmNodeSocketAction', 'Out')

View file

@ -7,11 +7,10 @@ class BoneFKNode(ArmLogicTreeNode):
arm_version = 1
arm_section = 'armature'
def init(self, context):
super(BoneFKNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('ArmNodeSocketObject', 'Object')
self.add_input('NodeSocketString', 'Bone')
self.add_input('NodeSocketShader', 'Transform')
self.add_input('ArmStringSocket', 'Bone')
self.add_input('ArmDynamicSocket', 'Transform')
self.add_output('ArmNodeSocketAction', 'Out')

View file

@ -26,19 +26,17 @@ class BoneIKNode(ArmLogicTreeNode):
arm_version = 2
arm_section = 'armature'
def init(self, context):
super(BoneIKNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('ArmNodeSocketObject', 'Object')
self.add_input('NodeSocketString', 'Bone')
self.add_input('NodeSocketVector', 'Goal Position')
self.add_input('NodeSocketBool', 'Enable Pole')
self.add_input('NodeSocketVector', 'Pole Position')
self.add_input('NodeSocketInt', 'Chain Length')
self.add_input('NodeSocketInt', 'Max Iterations', 10)
self.add_input('NodeSocketFloat', 'Precision', 0.01)
self.add_input('NodeSocketFloat', 'Roll Angle')
self.add_input('ArmStringSocket', 'Bone')
self.add_input('ArmVectorSocket', 'Goal Position')
self.add_input('ArmBoolSocket', 'Enable Pole')
self.add_input('ArmVectorSocket', 'Pole Position')
self.add_input('ArmIntSocket', 'Chain Length')
self.add_input('ArmIntSocket', 'Max Iterations', 10)
self.add_input('ArmFloatSocket', 'Precision', 0.01)
self.add_input('ArmFloatSocket', 'Roll Angle')
self.add_output('ArmNodeSocketAction', 'Out')

View file

@ -6,10 +6,9 @@ class AnimationStateNode(ArmLogicTreeNode):
bl_label = 'Get Action State'
arm_version = 1
def init(self, context):
super(AnimationStateNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketObject', 'Object')
self.add_output('NodeSocketString', 'Action')
self.add_output('NodeSocketInt', 'Frame')
self.add_output('NodeSocketBool', 'Is Paused')
self.add_output('ArmStringSocket', 'Action')
self.add_output('ArmIntSocket', 'Frame')
self.add_output('ArmBoolSocket', 'Is Paused')

View file

@ -7,8 +7,7 @@ class GetBoneFkIkOnlyNode(ArmLogicTreeNode):
arm_version = 1
arm_section = 'armature'
def init(self, context):
super(GetBoneFkIkOnlyNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketObject', 'Object')
self.add_input('NodeSocketString', 'Bone')
self.add_output('NodeSocketBool', 'FK or IK only')
self.add_input('ArmStringSocket', 'Bone')
self.add_output('ArmBoolSocket', 'FK or IK only')

View file

@ -7,8 +7,7 @@ class GetBoneTransformNode(ArmLogicTreeNode):
arm_version = 1
arm_section = 'armature'
def init(self, context):
super(GetBoneTransformNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketObject', 'Object')
self.add_input('NodeSocketString', 'Bone')
self.add_output('NodeSocketShader', 'Transform')
self.add_input('ArmStringSocket', 'Bone')
self.add_output('ArmDynamicSocket', 'Transform')

View file

@ -7,10 +7,9 @@ class GetTilesheetStateNode(ArmLogicTreeNode):
arm_version = 1
arm_section = 'tilesheet'
def init(self, context):
super(GetTilesheetStateNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketObject', 'Object')
self.add_output('NodeSocketString', 'Name')
self.add_output('NodeSocketInt', 'Frame')
self.add_output('NodeSocketBool', 'Is Paused')
self.add_output('ArmStringSocket', 'Name')
self.add_output('ArmIntSocket', 'Frame')
self.add_output('ArmBoolSocket', 'Is Paused')

View file

@ -6,9 +6,8 @@ class OnActionMarkerNode(ArmLogicTreeNode):
bl_label = 'On Action Marker'
arm_version = 1
def init(self, context):
super(OnActionMarkerNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketObject', 'Object')
self.add_input('NodeSocketString', 'Marker')
self.add_input('ArmStringSocket', 'Marker')
self.add_output('ArmNodeSocketAction', 'Out')

View file

@ -6,15 +6,14 @@ class PlayActionFromNode(ArmLogicTreeNode):
bl_label = 'Play Action From'
arm_version = 2
def init(self, context):
super(PlayActionFromNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('ArmNodeSocketObject', 'Object')
self.add_input('ArmNodeSocketAnimAction', 'Action')
self.add_input('NodeSocketInt', 'Start Frame')
self.add_input('NodeSocketFloat', 'Blend', default_value = 0.25)
self.add_input('NodeSocketFloat', 'Speed', default_value = 1.0)
self.add_input('NodeSocketBool', 'Loop', default_value = False)
self.add_input('ArmIntSocket', 'Start Frame')
self.add_input('ArmFloatSocket', 'Blend', default_value = 0.25)
self.add_input('ArmFloatSocket', 'Speed', default_value = 1.0)
self.add_input('ArmBoolSocket', 'Loop', default_value = False)
self.add_output('ArmNodeSocketAction', 'Out')
self.add_output('ArmNodeSocketAction', 'Done')
@ -22,7 +21,7 @@ class PlayActionFromNode(ArmLogicTreeNode):
def get_replacement_node(self, node_tree: bpy.types.NodeTree):
if self.arm_version not in (0, 1):
raise LookupError()
return NodeReplacement(
'LNPlayActionFromNode', self.arm_version, 'LNPlayActionFromNode', 2,
in_socket_mapping={0:0, 1:1, 2:2, 3:3, 4:4}, out_socket_mapping={0:0, 1:1}

View file

@ -7,11 +7,10 @@ class PlayTilesheetNode(ArmLogicTreeNode):
arm_version = 1
arm_section = 'tilesheet'
def init(self, context):
super(PlayTilesheetNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('ArmNodeSocketObject', 'Object')
self.add_input('NodeSocketString', 'Name')
self.add_input('ArmStringSocket', 'Name')
self.add_output('ArmNodeSocketAction', 'Out')
self.add_output('ArmNodeSocketAction', 'Done')

View file

@ -6,10 +6,9 @@ class SetActionPausedNode(ArmLogicTreeNode):
bl_label = 'Set Action Paused'
arm_version = 1
def init(self, context):
super(SetActionPausedNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('ArmNodeSocketObject', 'Object')
self.add_input('NodeSocketBool', 'Paused')
self.add_input('ArmBoolSocket', 'Paused')
self.add_output('ArmNodeSocketAction', 'Out')

View file

@ -6,10 +6,9 @@ class SetActionSpeedNode(ArmLogicTreeNode):
bl_label = 'Set Action Speed'
arm_version = 1
def init(self, context):
super(SetActionSpeedNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('ArmNodeSocketObject', 'Object')
self.add_input('NodeSocketFloat', 'Speed', default_value=1.0)
self.add_input('ArmFloatSocket', 'Speed', default_value=1.0)
self.add_output('ArmNodeSocketAction', 'Out')

View file

@ -7,11 +7,10 @@ class SetBoneFkIkOnlyNode(ArmLogicTreeNode):
arm_version = 1
arm_section = 'armature'
def init(self, context):
super(SetBoneFkIkOnlyNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('ArmNodeSocketObject', 'Object')
self.add_input('NodeSocketString', 'Bone')
self.add_input('NodeSocketBool', 'FK or IK only')
self.add_input('ArmStringSocket', 'Bone')
self.add_input('ArmBoolSocket', 'FK or IK only')
self.add_output('ArmNodeSocketAction', 'Out')

View file

@ -7,11 +7,10 @@ class SetParentBoneNode(ArmLogicTreeNode):
arm_version = 1
arm_section = 'armature'
def init(self, context):
super(SetParentBoneNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('ArmNodeSocketObject', 'Object')
self.add_input('ArmNodeSocketObject', 'Parent', default_value='Parent')
self.add_input('NodeSocketString', 'Bone', default_value='Bone')
self.add_input('ArmStringSocket', 'Bone', default_value='Bone')
self.add_output('ArmNodeSocketAction', 'Out')

View file

@ -6,10 +6,9 @@ class SetParticleSpeedNode(ArmLogicTreeNode):
bl_label = 'Set Particle Speed'
arm_version = 1
def init(self, context):
super(SetParticleSpeedNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('ArmNodeSocketObject', 'Object')
self.add_input('NodeSocketFloat', 'Speed', default_value=1.0)
self.add_input('ArmFloatSocket', 'Speed', default_value=1.0)
self.add_output('ArmNodeSocketAction', 'Out')

View file

@ -7,10 +7,9 @@ class SetTilesheetPausedNode(ArmLogicTreeNode):
arm_section = 'tilesheet'
arm_version = 1
def init(self, context):
super(SetTilesheetPausedNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('ArmNodeSocketObject', 'Object')
self.add_input('NodeSocketBool', 'Paused')
self.add_input('ArmBoolSocket', 'Paused')
self.add_output('ArmNodeSocketAction', 'Out')

View file

@ -1,13 +1,16 @@
import itertools
from collections import OrderedDict
from typing import Any, Generator, List, Optional, Type, Dict
from typing import Any, Generator, List, Optional, Type
from typing import OrderedDict as ODict # Prevent naming conflicts
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 custom property types and NodeReplacement forward to individual
# node modules that import arm_nodes
from arm.logicnode.arm_props import *
from arm.logicnode.replacement import NodeReplacement
import arm.node_utils
@ -21,6 +24,10 @@ category_items: ODict[str, List['ArmNodeCategory']] = OrderedDict()
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):
arm_category = PKG_AS_CATEGORY
@ -34,6 +41,14 @@ class ArmLogicTreeNode(bpy.types.Node):
else:
self.arm_version = 1
if not hasattr(self, 'arm_init'):
# Show warning for older node packages
arm.log.warn(f'Node {self.bl_idname} has no arm_init function and might not work correctly!')
else:
self.arm_init(context)
arm.live_patch.send_event('ln_create', self)
@classmethod
def poll(cls, ntree):
return ntree.bl_idname == 'ArmLogicTreeType'
@ -48,6 +63,68 @@ class ArmLogicTreeNode(bpy.types.Node):
def on_unregister(cls):
pass
def get_tree(self):
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):
"""Called before the node is deleted."""
arm.live_patch.send_event('ln_delete', self)
def copy(self, node):
"""Called if the node was copied. `self` holds the copied node,
`node` the original one.
"""
arm.live_patch.send_event('ln_copy', (self, node))
def on_prop_update(self, context: bpy.types.Context, prop_name: str):
"""Called if a property created with a function from the
arm_props module is changed. If the property has a custom update
function, it is called before `on_prop_update()`.
"""
arm.live_patch.send_event('ln_update_prop', (self, prop_name))
def on_socket_val_update(self, context: bpy.types.Context, socket: bpy.types.NodeSocket):
arm.live_patch.send_event('ln_socket_val', (self, socket))
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))
pass
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)
@ -115,7 +192,7 @@ class ArmNodeAddInputButton(bpy.types.Operator):
bl_options = {'UNDO', 'INTERNAL'}
node_index: StringProperty(name='Node Index', default='')
socket_type: StringProperty(name='Socket Type', default='NodeSocketShader')
socket_type: StringProperty(name='Socket Type', default='ArmDynamicSocket')
name_format: StringProperty(name='Name Format', default='Input {0}')
index_name_offset: IntProperty(name='Index Name Offset', default=0)
@ -126,7 +203,7 @@ class ArmNodeAddInputButton(bpy.types.Operator):
# Reset to default again for subsequent calls of this operator
self.node_index = ''
self.socket_type = 'NodeSocketShader'
self.socket_type = 'ArmDynamicSocket'
self.name_format = 'Input {0}'
self.index_name_offset = 0
@ -138,7 +215,7 @@ class ArmNodeAddInputValueButton(bpy.types.Operator):
bl_label = 'Add Input'
bl_options = {'UNDO', 'INTERNAL'}
node_index: StringProperty(name='Node Index', default='')
socket_type: StringProperty(name='Socket Type', default='NodeSocketShader')
socket_type: StringProperty(name='Socket Type', default='ArmDynamicSocket')
def execute(self, context):
global array_nodes
@ -185,7 +262,7 @@ class ArmNodeAddOutputButton(bpy.types.Operator):
bl_options = {'UNDO', 'INTERNAL'}
node_index: StringProperty(name='Node Index', default='')
socket_type: StringProperty(name='Socket Type', default='NodeSocketShader')
socket_type: StringProperty(name='Socket Type', default='ArmDynamicSocket')
name_format: StringProperty(name='Name Format', default='Output {0}')
index_name_offset: IntProperty(name='Index Name Offset', default=0)
@ -196,7 +273,7 @@ class ArmNodeAddOutputButton(bpy.types.Operator):
# Reset to default again for subsequent calls of this operator
self.node_index = ''
self.socket_type = 'NodeSocketShader'
self.socket_type = 'ArmDynamicSocket'
self.name_format = 'Output {0}'
self.index_name_offset = 0
@ -225,8 +302,8 @@ class ArmNodeAddInputOutputButton(bpy.types.Operator):
bl_options = {'UNDO', 'INTERNAL'}
node_index: StringProperty(name='Node Index', default='')
in_socket_type: StringProperty(name='In Socket Type', default='NodeSocketShader')
out_socket_type: StringProperty(name='Out Socket Type', default='NodeSocketShader')
in_socket_type: StringProperty(name='In Socket Type', default='ArmDynamicSocket')
out_socket_type: StringProperty(name='Out Socket Type', default='ArmDynamicSocket')
in_name_format: StringProperty(name='In Name Format', default='Input {0}')
out_name_format: StringProperty(name='Out Name Format', default='Output {0}')
in_index_name_offset: IntProperty(name='Index Name Offset', default=0)
@ -241,8 +318,8 @@ class ArmNodeAddInputOutputButton(bpy.types.Operator):
# Reset to default again for subsequent calls of this operator
self.node_index = ''
self.in_socket_type = 'NodeSocketShader'
self.out_socket_type = 'NodeSocketShader'
self.in_socket_type = 'ArmDynamicSocket'
self.out_socket_type = 'ArmDynamicSocket'
self.in_name_format = 'Input {0}'
self.out_name_format = 'Output {0}'
self.in_index_name_offset = 0

View file

@ -0,0 +1,267 @@
"""Custom bpy property creators for logic nodes. Please be aware that
the code in this file is usually run once at registration and not for
each individual node instance when it is created.
The functions for creating typed properties wrap the private __haxe_prop
function to allow for IDE autocompletion.
Some default parameters in the signature of functions in this module are
mutable (common Python pitfall, be aware of this!), but because they
don't get accessed later it doesn't matter here and we keep it this way
for parity with the Blender API.
"""
from typing import Any, Callable, Sequence, Union
import bpy
from bpy.props import *
def __haxe_prop(prop_type: Callable, prop_name: str, *args, **kwargs) -> Any:
"""Declares a logic node property as a property that will be
used ingame for a logic node."""
update_callback: Callable = kwargs.get('update', None)
if update_callback is None:
def wrapper(self: bpy.types.Node, context: bpy.types.Context):
self.on_prop_update(context, prop_name)
kwargs['update'] = wrapper
else:
def wrapper(self: bpy.types.Node, context: bpy.types.Context):
update_callback(self, context)
self.on_prop_update(context, prop_name)
kwargs['update'] = wrapper
# Tags are not allowed on classes other than bpy.types.ID or
# bpy.types.Bone, remove them here to prevent registration errors
if 'tags' in kwargs:
del kwargs['tags']
return prop_type(*args, **kwargs)
def HaxeBoolProperty(
prop_name: str,
*, # force passing further arguments as keywords, see PEP 3102
name: str = "",
description: str = "",
default=False,
options: set = {'ANIMATABLE'},
override: set = set(),
tags: set = set(),
subtype: str = 'NONE',
update=None,
get=None,
set=None
) -> 'bpy.types.BoolProperty':
"""Declares a new BoolProperty that has a Haxe counterpart with the
given prop_name (Python and Haxe names must be identical for now).
"""
return __haxe_prop(BoolProperty, **locals())
def HaxeBoolVectorProperty(
prop_name: str,
*,
name: str = "",
description: str = "",
default: list = (False, False, False),
options: set = {'ANIMATABLE'},
override: set = set(),
tags: set = set(),
subtype: str = 'NONE',
size: int = 3,
update=None,
get=None,
set=None
) -> list['bpy.types.BoolProperty']:
"""Declares a new BoolVectorProperty that has a Haxe counterpart
with the given prop_name (Python and Haxe names must be identical
for now).
"""
return __haxe_prop(BoolVectorProperty, **locals())
def HaxeCollectionProperty(
prop_name: str,
*,
type=None,
name: str = "",
description: str = "",
options: set = {'ANIMATABLE'},
override: set = set(),
tags: set = set()
) -> 'bpy.types.CollectionProperty':
"""Declares a new CollectionProperty that has a Haxe counterpart
with the given prop_name (Python and Haxe names must be identical
for now).
"""
return __haxe_prop(CollectionProperty, **locals())
def HaxeEnumProperty(
prop_name: str,
*,
items: Sequence,
name: str = "",
description: str = "",
default: Union[str, set[str]] = None,
options: set = {'ANIMATABLE'},
override: set = set(),
tags: set = set(),
update=None,
get=None,
set=None
) -> 'bpy.types.EnumProperty':
"""Declares a new EnumProperty that has a Haxe counterpart with the
given prop_name (Python and Haxe names must be identical for now).
"""
return __haxe_prop(EnumProperty, **locals())
def HaxeFloatProperty(
prop_name: str,
*,
name: str = "",
description: str = "",
default=0.0,
min: float = -3.402823e+38,
max: float = 3.402823e+38,
soft_min: float = -3.402823e+38,
soft_max: float = 3.402823e+38,
step: int = 3,
precision: int = 2,
options: set = {'ANIMATABLE'},
override: set = set(),
tags: set = set(),
subtype: str = 'NONE',
unit: str = 'NONE',
update=None,
get=None,
set=None
) -> 'bpy.types.FloatProperty':
"""Declares a new FloatProperty that has a Haxe counterpart with the
given prop_name (Python and Haxe names must be identical for now).
"""
return __haxe_prop(FloatProperty, **locals())
def HaxeFloatVectorProperty(
prop_name: str,
*,
name: str = "",
description: str = "",
default: list = (0.0, 0.0, 0.0),
min: float = 'sys.float_info.min',
max: float = 'sys.float_info.max',
soft_min: float = 'sys.float_info.min',
soft_max: float = 'sys.float_info.max',
step: int = 3,
precision: int = 2,
options: set = {'ANIMATABLE'},
override: set = set(),
tags: set = set(),
subtype: str = 'NONE',
unit: str = 'NONE',
size: int = 3,
update=None,
get=None,
set=None
) -> list['bpy.types.FloatProperty']:
"""Declares a new FloatVectorProperty that has a Haxe counterpart
with the given prop_name (Python and Haxe names must be identical
for now).
"""
return __haxe_prop(FloatVectorProperty, **locals())
def HaxeIntProperty(
prop_name: str,
*,
name: str = "",
description: str = "",
default=0,
min: int = -2**31,
max: int = 2**31 - 1,
soft_min: int = -2**31,
soft_max: int = 2**31 - 1,
step: int = 1,
options: set = {'ANIMATABLE'},
override: set = set(),
tags: set = set(),
subtype: str = 'NONE',
update=None,
get=None,
set=None
) -> 'bpy.types.IntProperty':
"""Declares a new IntProperty that has a Haxe counterpart with the
given prop_name (Python and Haxe names must be identical for now).
"""
return __haxe_prop(IntProperty, **locals())
def HaxeIntVectorProperty(
prop_name: str,
*,
name: str = "",
description: str = "",
default: list = (0, 0, 0),
min: int = -2**31,
max: int = 2**31 - 1,
soft_min: int = -2**31,
soft_max: int = 2**31 - 1,
step: int = 1,
options: set = {'ANIMATABLE'},
override: set = set(),
tags: set = set(),
subtype: str = 'NONE',
size: int = 3,
update=None,
get=None,
set=None
) -> list['bpy.types.IntProperty']:
"""Declares a new IntVectorProperty that has a Haxe counterpart with
the given prop_name (Python and Haxe names must be identical for now).
"""
return __haxe_prop(IntVectorProperty, **locals())
def HaxePointerProperty(
prop_name: str,
*,
type=None,
name: str = "",
description: str = "",
options: set = {'ANIMATABLE'},
override: set = set(),
tags: set = set(),
poll=None,
update=None
) -> 'bpy.types.PointerProperty':
"""Declares a new PointerProperty that has a Haxe counterpart with
the given prop_name (Python and Haxe names must be identical for now).
"""
return __haxe_prop(PointerProperty, **locals())
def RemoveHaxeProperty(cls, attr: str):
RemoveProperty(cls, attr)
def HaxeStringProperty(
prop_name: str,
*,
name: str = "",
description: str = "",
default: str = "",
maxlen: int = 0,
options: set = {'ANIMATABLE'},
override: set = set(),
tags: set = set(),
subtype: str = 'NONE',
update=None,
get=None,
set=None
) -> 'bpy.types.StringProperty':
"""Declares a new StringProperty that has a Haxe counterpart with
the given prop_name (Python and Haxe names must be identical for now).
"""
return __haxe_prop(StringProperty, **locals())

View file

@ -1,10 +1,14 @@
import bpy
from bpy.props import PointerProperty
from bpy.props import *
from bpy.types import NodeSocket
import arm.utils
def _on_update_socket(self, context):
self.node.on_socket_val_update(context, self)
class ArmCustomSocket(NodeSocket):
"""
A custom socket that can be used to define more socket types for
@ -41,7 +45,7 @@ class ArmAnimActionSocket(ArmCustomSocket):
arm_socket_type = 'STRING'
default_value_get: PointerProperty(name='Action', type=bpy.types.Action) # legacy version of the line after this one
default_value_raw: PointerProperty(name='Action', type=bpy.types.Action)
default_value_raw: PointerProperty(name='Action', type=bpy.types.Action, update=_on_update_socket)
def __init__(self):
super().__init__()
@ -81,13 +85,114 @@ class ArmArraySocket(ArmCustomSocket):
return 0.8, 0.4, 0.0, 1
class ArmBoolSocket(ArmCustomSocket):
bl_idname = 'ArmBoolSocket'
bl_label = 'Boolean Socket'
arm_socket_type = 'BOOLEAN'
default_value_raw: BoolProperty(
name='Value',
description='Input value used for unconnected socket',
update=_on_update_socket
)
def draw(self, context, layout, node, text):
draw_socket_layout(self, layout)
def draw_color(self, context, node):
return 0.8, 0.651, 0.839, 1
def get_default_value(self):
return self.default_value_raw
class ArmColorSocket(ArmCustomSocket):
bl_idname = 'ArmColorSocket'
bl_label = 'Color Socket'
arm_socket_type = 'RGBA'
default_value_raw: FloatVectorProperty(
name='Value',
size=4,
subtype='COLOR',
min=0.0,
max=1.0,
description='Input value used for unconnected socket',
update=_on_update_socket
)
def draw(self, context, layout, node, text):
draw_socket_layout(self, layout)
def draw_color(self, context, node):
return 0.78, 0.78, 0.161, 1
def get_default_value(self):
return self.default_value_raw
class ArmDynamicSocket(ArmCustomSocket):
bl_idname = 'ArmDynamicSocket'
bl_label = 'Dynamic Socket'
arm_socket_type = 'NONE'
def draw(self, context, layout, node, text):
layout.label(text=self.name)
def draw_color(self, context, node):
return 0.388, 0.78, 0.388, 1
class ArmFloatSocket(ArmCustomSocket):
bl_idname = 'ArmFloatSocket'
bl_label = 'Float Socket'
arm_socket_type = 'VALUE'
default_value_raw: FloatProperty(
name='Value',
description='Input value used for unconnected socket',
precision=3,
update=_on_update_socket
)
def draw(self, context, layout, node, text):
draw_socket_layout(self, layout)
def draw_color(self, context, node):
return 0.631, 0.631, 0.631, 1
def get_default_value(self):
return self.default_value_raw
class ArmIntSocket(ArmCustomSocket):
bl_idname = 'ArmIntSocket'
bl_label = 'Integer Socket'
arm_socket_type = 'INT'
default_value_raw: IntProperty(
name='Value',
description='Input value used for unconnected socket',
update=_on_update_socket
)
def draw(self, context, layout, node, text):
draw_socket_layout(self, layout)
def draw_color(self, context, node):
return 0.059, 0.522, 0.149, 1
def get_default_value(self):
return self.default_value_raw
class ArmObjectSocket(ArmCustomSocket):
bl_idname = 'ArmNodeSocketObject'
bl_label = 'Object Socket'
arm_socket_type = 'OBJECT'
default_value_get: PointerProperty(name='Object', type=bpy.types.Object) # legacy version of the line after this one
default_value_raw: PointerProperty(name='Object', type=bpy.types.Object)
default_value_raw: PointerProperty(name='Object', type=bpy.types.Object, update=_on_update_socket)
def __init__(self):
super().__init__()
@ -115,15 +220,82 @@ class ArmObjectSocket(ArmCustomSocket):
return 0.15, 0.55, 0.75, 1
def register():
bpy.utils.register_class(ArmActionSocket)
bpy.utils.register_class(ArmAnimActionSocket)
bpy.utils.register_class(ArmArraySocket)
bpy.utils.register_class(ArmObjectSocket)
class ArmStringSocket(ArmCustomSocket):
bl_idname = 'ArmStringSocket'
bl_label = 'String Socket'
arm_socket_type = 'STRING'
default_value_raw: StringProperty(
name='Value',
description='Input value used for unconnected socket',
update=_on_update_socket
)
def draw(self, context, layout, node, text):
draw_socket_layout_split(self, layout)
def draw_color(self, context, node):
return 0.439, 0.698, 1, 1
def get_default_value(self):
return self.default_value_raw
def unregister():
bpy.utils.unregister_class(ArmObjectSocket)
bpy.utils.unregister_class(ArmArraySocket)
bpy.utils.unregister_class(ArmAnimActionSocket)
bpy.utils.unregister_class(ArmActionSocket)
class ArmVectorSocket(ArmCustomSocket):
bl_idname = 'ArmVectorSocket'
bl_label = 'Vector Socket'
arm_socket_type = 'VECTOR'
default_value_raw: FloatVectorProperty(
name='Value',
size=3,
description='Input value used for unconnected socket',
update=_on_update_socket
)
def draw(self, context, layout, node, text):
if not self.is_output and not self.is_linked:
col = layout.column(align=True)
col.prop(self, 'default_value_raw', text='')
else:
layout.label(text=self.name)
def draw_color(self, context, node):
return 0.388, 0.388, 0.78, 1
def get_default_value(self):
return self.default_value_raw
def draw_socket_layout(socket: bpy.types.NodeSocket, layout: bpy.types.UILayout, prop_name='default_value_raw'):
if not socket.is_output and not socket.is_linked:
layout.prop(socket, prop_name, text=socket.name)
else:
layout.label(text=socket.name)
def draw_socket_layout_split(socket: bpy.types.NodeSocket, layout: bpy.types.UILayout, prop_name='default_value_raw'):
if not socket.is_output and not socket.is_linked:
# Blender layouts use 0.4 splits
layout = layout.split(factor=0.4, align=True)
layout.label(text=socket.name)
if not socket.is_output and not socket.is_linked:
layout.prop(socket, prop_name, text='')
REG_CLASSES = (
ArmActionSocket,
ArmAnimActionSocket,
ArmArraySocket,
ArmBoolSocket,
ArmColorSocket,
ArmDynamicSocket,
ArmFloatSocket,
ArmIntSocket,
ArmObjectSocket,
ArmStringSocket,
ArmVectorSocket,
)
register, unregister = bpy.utils.register_classes_factory(REG_CLASSES)

View file

@ -10,17 +10,16 @@ class ArrayNode(ArmLogicTreeNode):
def __init__(self):
array_nodes[str(id(self))] = self
def init(self, context):
super(ArrayNode, self).init(context)
def arm_init(self, context):
self.add_output('ArmNodeSocketArray', 'Array', is_var=True)
self.add_output('NodeSocketInt', 'Length')
self.add_output('ArmIntSocket', 'Length')
def draw_buttons(self, context, layout):
row = layout.row(align=True)
op = row.operator('arm.node_add_input', text='New', icon='PLUS', emboss=True)
op.node_index = str(id(self))
op.socket_type = 'NodeSocketShader'
op.socket_type = 'ArmDynamicSocket'
op2 = row.operator('arm.node_remove_input', text='', icon='X', emboss=True)
op2.node_index = str(id(self))

View file

@ -15,13 +15,12 @@ class ArrayAddNode(ArmLogicTreeNode):
super(ArrayAddNode, self).__init__()
array_nodes[str(id(self))] = self
def init(self, context):
super(ArrayAddNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('ArmNodeSocketArray', 'Array')
self.add_input('NodeSocketBool', 'Modify Original', default_value=True)
self.add_input('NodeSocketBool', 'Unique Values')
self.add_input('NodeSocketShader', 'Value')
self.add_input('ArmBoolSocket', 'Modify Original', default_value=True)
self.add_input('ArmBoolSocket', 'Unique Values')
self.add_input('ArmDynamicSocket', 'Value')
self.add_output('ArmNodeSocketAction', 'Out')
self.add_output('ArmNodeSocketArray', 'Array')
@ -31,6 +30,6 @@ class ArrayAddNode(ArmLogicTreeNode):
op = row.operator('arm.node_add_input_value', text='Add Input', icon='PLUS', emboss=True)
op.node_index = str(id(self))
op.socket_type = 'NodeSocketShader'
op.socket_type = 'ArmDynamicSocket'
op2 = row.operator('arm.node_remove_input_value', text='', icon='X', emboss=True)
op2.node_index = str(id(self))

View file

@ -11,17 +11,16 @@ class BooleanArrayNode(ArmLogicTreeNode):
super(BooleanArrayNode, self).__init__()
array_nodes[str(id(self))] = self
def init(self, context):
super(BooleanArrayNode, self).init(context)
def arm_init(self, context):
self.add_output('ArmNodeSocketArray', 'Array', is_var=True)
self.add_output('NodeSocketInt', 'Length')
self.add_output('ArmIntSocket', 'Length')
def draw_buttons(self, context, layout):
row = layout.row(align=True)
op = row.operator('arm.node_add_input', text='New', icon='PLUS', emboss=True)
op.node_index = str(id(self))
op.socket_type = 'NodeSocketBool'
op.socket_type = 'ArmBoolSocket'
op2 = row.operator('arm.node_remove_input', text='', icon='X', emboss=True)
op2.node_index = str(id(self))

View file

@ -11,17 +11,16 @@ class ColorArrayNode(ArmLogicTreeNode):
super(ColorArrayNode, self).__init__()
array_nodes[str(id(self))] = self
def init(self, context):
super(ColorArrayNode, self).init(context)
def arm_init(self, context):
self.add_output('ArmNodeSocketArray', 'Array', is_var=True)
self.add_output('NodeSocketInt', 'Length')
self.add_output('ArmIntSocket', 'Length')
def draw_buttons(self, context, layout):
row = layout.row(align=True)
op = row.operator('arm.node_add_input', text='New', icon='PLUS', emboss=True)
op.node_index = str(id(self))
op.socket_type = 'NodeSocketColor'
op.socket_type = 'ArmColorSocket'
op2 = row.operator('arm.node_remove_input', text='', icon='X', emboss=True)
op2.node_index = str(id(self))

View file

@ -6,9 +6,8 @@ class ArrayContainsNode(ArmLogicTreeNode):
bl_label = 'Array Contains'
arm_version = 1
def init(self, context):
super(ArrayContainsNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketArray', 'Array')
self.add_input('NodeSocketShader', 'Value')
self.add_input('ArmDynamicSocket', 'Value')
self.add_output('NodeSocketBool', 'Contains')
self.add_output('ArmBoolSocket', 'Contains')

View file

@ -11,17 +11,16 @@ class FloatArrayNode(ArmLogicTreeNode):
super(FloatArrayNode, self).__init__()
array_nodes[str(id(self))] = self
def init(self, context):
super(FloatArrayNode, self).init(context)
def arm_init(self, context):
self.add_output('ArmNodeSocketArray', 'Array', is_var=True)
self.add_output('NodeSocketInt', 'Length')
self.add_output('ArmIntSocket', 'Length')
def draw_buttons(self, context, layout):
row = layout.row(align=True)
op = row.operator('arm.node_add_input', text='New', icon='PLUS', emboss=True)
op.node_index = str(id(self))
op.socket_type = 'NodeSocketFloat'
op.socket_type = 'ArmFloatSocket'
op2 = row.operator('arm.node_remove_input', text='', icon='X', emboss=True)
op2.node_index = str(id(self))

View file

@ -6,9 +6,8 @@ class ArrayGetNode(ArmLogicTreeNode):
bl_label = 'Array Get'
arm_version = 1
def init(self, context):
super(ArrayGetNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketArray', 'Array')
self.add_input('NodeSocketInt', 'Index')
self.add_input('ArmIntSocket', 'Index')
self.add_output('NodeSocketShader', 'Value')
self.add_output('ArmDynamicSocket', 'Value')

View file

@ -11,17 +11,16 @@ class IntegerArrayNode(ArmLogicTreeNode):
super(IntegerArrayNode, self).__init__()
array_nodes[str(id(self))] = self
def init(self, context):
super(IntegerArrayNode, self).init(context)
def arm_init(self, context):
self.add_output('ArmNodeSocketArray', 'Array')
self.add_output('NodeSocketInt', 'Length')
self.add_output('ArmIntSocket', 'Length')
def draw_buttons(self, context, layout):
row = layout.row(align=True)
op = row.operator('arm.node_add_input', text='New', icon='PLUS', emboss=True)
op.node_index = str(id(self))
op.socket_type = 'NodeSocketInt'
op.socket_type = 'ArmIntSocket'
op2 = row.operator('arm.node_remove_input', text='', icon='X', emboss=True)
op2.node_index = str(id(self))

View file

@ -6,8 +6,7 @@ class ArrayLengthNode(ArmLogicTreeNode):
bl_label = 'Array Length'
arm_version = 1
def init(self, context):
super(ArrayLengthNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketArray', 'Array')
self.add_output('NodeSocketInt', 'Length')
self.add_output('ArmIntSocket', 'Length')

View file

@ -7,12 +7,11 @@ class ArrayLoopNode(ArmLogicTreeNode):
bl_label = 'Array Loop'
arm_version = 1
def init(self, context):
super(ArrayLoopNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('ArmNodeSocketArray', 'Array')
self.add_output('ArmNodeSocketAction', 'Loop')
self.add_output('NodeSocketShader', 'Value')
self.add_output('NodeSocketInt', 'Index')
self.add_output('ArmDynamicSocket', 'Value')
self.add_output('ArmIntSocket', 'Index')
self.add_output('ArmNodeSocketAction', 'Done')

View file

@ -11,10 +11,9 @@ class ObjectArrayNode(ArmLogicTreeNode):
super(ObjectArrayNode, self).__init__()
array_nodes[str(id(self))] = self
def init(self, context):
super(ObjectArrayNode, self).init(context)
def arm_init(self, context):
self.add_output('ArmNodeSocketArray', 'Array', is_var=True)
self.add_output('NodeSocketInt', 'Length')
self.add_output('ArmIntSocket', 'Length')
def draw_buttons(self, context, layout):
row = layout.row(align=True)

View file

@ -8,8 +8,7 @@ class ArrayPopNode(ArmLogicTreeNode):
bl_label = 'Array Pop'
arm_version = 1
def init(self, context):
super(ArrayPopNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketArray', 'Array')
self.add_output('NodeSocketShader', 'Value')
self.add_output('ArmDynamicSocket', 'Value')

View file

@ -8,11 +8,10 @@ class ArrayRemoveIndexNode(ArmLogicTreeNode):
bl_label = 'Array Remove by Index'
arm_version = 1
def init(self, context):
super(ArrayRemoveIndexNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('ArmNodeSocketArray', 'Array')
self.add_input('NodeSocketInt', 'Index')
self.add_input('ArmIntSocket', 'Index')
self.add_output('ArmNodeSocketAction', 'Out')
self.add_output('NodeSocketShader', 'Value')
self.add_output('ArmDynamicSocket', 'Value')

View file

@ -11,20 +11,19 @@ class ArrayRemoveValueNode(ArmLogicTreeNode):
# def __init__(self):
# array_nodes[str(id(self))] = self
def init(self, context):
super(ArrayRemoveValueNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('ArmNodeSocketArray', 'Array')
self.add_input('NodeSocketShader', 'Value')
self.add_input('ArmDynamicSocket', 'Value')
self.add_output('ArmNodeSocketAction', 'Out')
self.add_output('NodeSocketShader', 'Value')
self.add_output('ArmDynamicSocket', 'Value')
# def draw_buttons(self, context, layout):
# row = layout.row(align=True)
# op = row.operator('arm.node_add_input_value', text='New', icon='PLUS', emboss=True)
# op.node_index = str(id(self))
# op.socket_type = 'NodeSocketShader'
# op.socket_type = 'ArmDynamicSocket'
# op2 = row.operator('arm.node_remove_input_value', text='', icon='X', emboss=True)
# op2.node_index = str(id(self))

View file

@ -6,11 +6,10 @@ class ArraySetNode(ArmLogicTreeNode):
bl_label = 'Array Set'
arm_version = 1
def init(self, context):
super(ArraySetNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('ArmNodeSocketArray', 'Array')
self.add_input('NodeSocketInt', 'Index')
self.add_input('NodeSocketShader', 'Value')
self.add_input('ArmIntSocket', 'Index')
self.add_input('ArmDynamicSocket', 'Value')
self.add_output('ArmNodeSocketAction', 'Out')

View file

@ -8,8 +8,7 @@ class ArrayShiftNode(ArmLogicTreeNode):
bl_label = 'Array Shift'
arm_version = 1
def init(self, context):
super(ArrayShiftNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketArray', 'Array')
self.add_output('NodeSocketShader', 'Value')
self.add_output('ArmDynamicSocket', 'Value')

View file

@ -8,10 +8,9 @@ class ArraySliceNode(ArmLogicTreeNode):
bl_label = 'Array Slice'
arm_version = 1
def init(self, context):
super(ArraySliceNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketArray', 'Array')
self.add_input('NodeSocketInt', 'Index')
self.add_input('NodeSocketInt', 'End')
self.add_input('ArmIntSocket', 'Index')
self.add_input('ArmIntSocket', 'End')
self.add_output('ArmNodeSocketArray', 'Array')

View file

@ -8,11 +8,10 @@ class ArraySpliceNode(ArmLogicTreeNode):
bl_label = 'Array Splice'
arm_version = 1
def init(self, context):
super(ArraySpliceNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('ArmNodeSocketArray', 'Array')
self.add_input('NodeSocketInt', 'Index')
self.add_input('NodeSocketInt', 'Length')
self.add_input('ArmIntSocket', 'Index')
self.add_input('ArmIntSocket', 'Length')
self.add_output('ArmNodeSocketAction', 'Out')

View file

@ -11,17 +11,16 @@ class StringArrayNode(ArmLogicTreeNode):
super(StringArrayNode, self).__init__()
array_nodes[str(id(self))] = self
def init(self, context):
super(StringArrayNode, self).init(context)
def arm_init(self, context):
self.add_output('ArmNodeSocketArray', 'Array', is_var=True)
self.add_output('NodeSocketInt', 'Length')
self.add_output('ArmIntSocket', 'Length')
def draw_buttons(self, context, layout):
row = layout.row(align=True)
op = row.operator('arm.node_add_input', text='New', icon='PLUS', emboss=True)
op.node_index = str(id(self))
op.socket_type = 'NodeSocketString'
op.socket_type = 'ArmStringSocket'
op2 = row.operator('arm.node_remove_input', text='', icon='X', emboss=True)
op2.node_index = str(id(self))

View file

@ -11,17 +11,16 @@ class VectorArrayNode(ArmLogicTreeNode):
super(VectorArrayNode, self).__init__()
array_nodes[str(id(self))] = self
def init(self, context):
super(VectorArrayNode, self).init(context)
def arm_init(self, context):
self.add_output('ArmNodeSocketArray', 'Array', is_var=True)
self.add_output('NodeSocketInt', 'Length')
self.add_output('ArmIntSocket', 'Length')
def draw_buttons(self, context, layout):
row = layout.row(align=True)
op = row.operator('arm.node_add_input', text='New', icon='PLUS', emboss=True)
op.node_index = str(id(self))
op.socket_type = 'NodeSocketVector'
op.socket_type = 'ArmVectorSocket'
op2 = row.operator('arm.node_remove_input', text='', icon='X', emboss=True)
op2.node_index = str(id(self))

View file

@ -8,6 +8,5 @@ class ActiveCameraNode(ArmLogicTreeNode):
bl_label = 'Get Camera Active'
arm_version = 1
def init(self, context):
super(ActiveCameraNode, self).init(context)
def arm_init(self, context):
self.add_output('ArmNodeSocketObject', 'Camera')

View file

@ -8,8 +8,7 @@ class GetCameraFovNode(ArmLogicTreeNode):
bl_label = 'Get Camera FOV'
arm_version = 1
def init(self, context):
super(GetCameraFovNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketObject', 'Object')
self.add_output('NodeSocketFloat', 'FOV')
self.add_output('ArmFloatSocket', 'FOV')

View file

@ -8,8 +8,7 @@ class SetCameraNode(ArmLogicTreeNode):
bl_label = 'Set Camera Active'
arm_version = 1
def init(self, context):
super(SetCameraNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('ArmNodeSocketObject', 'Camera')

View file

@ -8,10 +8,9 @@ class SetCameraFovNode(ArmLogicTreeNode):
bl_label = 'Set Camera FOV'
arm_version = 1
def init(self, context):
super(SetCameraFovNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('ArmNodeSocketObject', 'Camera')
self.add_input('NodeSocketFloat', 'FOV', default_value=0.9)
self.add_input('ArmFloatSocket', 'FOV', default_value=0.9)
self.add_output('ArmNodeSocketAction', 'Out')

View file

@ -6,8 +6,7 @@ class CanvasGetCheckboxNode(ArmLogicTreeNode):
bl_label = 'Get Canvas Checkbox'
arm_version = 1
def init(self, context):
super(CanvasGetCheckboxNode, self).init(context)
self.add_input('NodeSocketString', 'Element')
def arm_init(self, context):
self.add_input('ArmStringSocket', 'Element')
self.add_output('NodeSocketBool', 'Is Checked')
self.add_output('ArmBoolSocket', 'Is Checked')

View file

@ -6,8 +6,7 @@ class CanvasGetInputTextNode(ArmLogicTreeNode):
bl_label = 'Get Canvas Input Text'
arm_version = 1
def init(self, context):
super(CanvasGetInputTextNode, self).init(context)
self.add_input('NodeSocketString', 'Element')
def arm_init(self, context):
self.add_input('ArmStringSocket', 'Element')
self.add_output('NodeSocketString', 'Text')
self.add_output('ArmStringSocket', 'Text')

View file

@ -6,11 +6,10 @@ class CanvasGetLocationNode(ArmLogicTreeNode):
bl_label = 'Get Canvas Location'
arm_version = 1
def init(self, context):
super(CanvasGetLocationNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('NodeSocketString', 'Element')
self.add_input('ArmStringSocket', 'Element')
self.add_output('ArmNodeSocketAction', 'Out')
self.add_output('NodeSocketInt', 'X')
self.add_output('NodeSocketInt', 'Y')
self.add_output('ArmIntSocket', 'X')
self.add_output('ArmIntSocket', 'Y')

View file

@ -6,8 +6,7 @@ class CanvasGetPositionNode(ArmLogicTreeNode):
bl_label = 'Get Canvas Position'
arm_version = 1
def init(self, context):
super(CanvasGetPositionNode, self).init(context)
self.add_input('NodeSocketString', 'Element')
def arm_init(self, context):
self.add_input('ArmStringSocket', 'Element')
self.add_output('NodeSocketInt', 'Position')
self.add_output('ArmIntSocket', 'Position')

View file

@ -6,11 +6,10 @@ class CanvasGetPBNode(ArmLogicTreeNode):
bl_label = 'Get Canvas Progress Bar'
arm_version = 1
def init(self, context):
super(CanvasGetPBNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('NodeSocketString', 'Element')
self.add_input('ArmStringSocket', 'Element')
self.add_output('ArmNodeSocketAction', 'Out')
self.add_output('NodeSocketInt', 'At')
self.add_output('NodeSocketInt', 'Max')
self.add_output('ArmIntSocket', 'At')
self.add_output('ArmIntSocket', 'Max')

View file

@ -6,10 +6,9 @@ class CanvasGetRotationNode(ArmLogicTreeNode):
bl_label = 'Get Canvas Rotation'
arm_version = 1
def init(self, context):
super(CanvasGetRotationNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('NodeSocketString', 'Element')
self.add_input('ArmStringSocket', 'Element')
self.add_output('ArmNodeSocketAction', 'Out')
self.add_output('NodeSocketFloat', 'Rad')
self.add_output('ArmFloatSocket', 'Rad')

View file

@ -6,11 +6,10 @@ class CanvasGetScaleNode(ArmLogicTreeNode):
bl_label = 'Get Canvas Scale'
arm_version = 1
def init(self, context):
super(CanvasGetScaleNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('NodeSocketString', 'Element')
self.add_input('ArmStringSocket', 'Element')
self.add_output('ArmNodeSocketAction', 'Out')
self.add_output('NodeSocketInt', 'Height')
self.add_output('NodeSocketInt', 'Width')
self.add_output('ArmIntSocket', 'Height')
self.add_output('ArmIntSocket', 'Width')

View file

@ -6,8 +6,7 @@ class CanvasGetSliderNode(ArmLogicTreeNode):
bl_label = 'Get Canvas Slider'
arm_version = 1
def init(self, context):
super(CanvasGetSliderNode, self).init(context)
self.add_input('NodeSocketString', 'Element')
def arm_init(self, context):
self.add_input('ArmStringSocket', 'Element')
self.add_output('NodeSocketFloat', 'Float')
self.add_output('ArmFloatSocket', 'Float')

View file

@ -9,8 +9,7 @@ class CanvasGetVisibleNode(ArmLogicTreeNode):
bl_label = 'Get Canvas Visible'
arm_version = 1
def init(self, context):
super(CanvasGetVisibleNode, self).init(context)
self.inputs.new('NodeSocketString', 'Element')
def arm_init(self, context):
self.inputs.new('ArmStringSocket', 'Element')
self.outputs.new('NodeSocketBool', 'Is Visible')
self.outputs.new('ArmBoolSocket', 'Is Visible')

View file

@ -6,24 +6,26 @@ class OnCanvasElementNode(ArmLogicTreeNode):
bl_label = 'On Canvas Element'
arm_version = 1
property0: EnumProperty(
property0: HaxeEnumProperty(
'property0',
items=[('click', 'Click', 'Listen to mouse clicks'),
('hover', 'Hover', 'Listen to mouse hover')],
name='Listen to', default='click')
property1: EnumProperty(
property1: HaxeEnumProperty(
'property1',
items=[('started', 'Started', 'Started'),
('down', 'Down', 'Down'),
('released', 'Released', 'Released')],
name='Status', default='started')
property2: EnumProperty(
property2: HaxeEnumProperty(
'property2',
items=[('left', 'Left', 'Left mouse button'),
('middle', 'Middle', 'Middle mouse button'),
('right', 'Right', 'Right mouse button')],
name='Mouse Button', default='left')
def init(self, context):
super(OnCanvasElementNode, self).init(context)
self.add_input('NodeSocketString', 'Element')
def arm_init(self, context):
self.add_input('ArmStringSocket', 'Element')
self.add_output('ArmNodeSocketAction', 'Out')

View file

@ -6,10 +6,9 @@ class CanvasSetAssetNode(ArmLogicTreeNode):
bl_label = 'Set Canvas Asset'
arm_version = 1
def init(self, context):
super(CanvasSetAssetNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('NodeSocketString', 'Element')
self.add_input('NodeSocketString', 'Asset')
self.add_input('ArmStringSocket', 'Element')
self.add_input('ArmStringSocket', 'Asset')
self.add_output('ArmNodeSocketAction', 'Out')

View file

@ -6,10 +6,9 @@ class CanvasSetCheckBoxNode(ArmLogicTreeNode):
bl_label = 'Set Canvas Checkbox'
arm_version = 1
def init(self, context):
super(CanvasSetCheckBoxNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('NodeSocketString', 'Element')
self.add_input('NodeSocketBool', 'Check')
self.add_input('ArmStringSocket', 'Element')
self.add_input('ArmBoolSocket', 'Check')
self.add_output('ArmNodeSocketAction', 'Out')

View file

@ -6,11 +6,10 @@ class CanvasSetLocationNode(ArmLogicTreeNode):
bl_label = 'Set Canvas Location'
arm_version = 1
def init(self, context):
super(CanvasSetLocationNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('NodeSocketString', 'Element')
self.add_input('NodeSocketFloat', 'X')
self.add_input('NodeSocketFloat', 'Y')
self.add_input('ArmStringSocket', 'Element')
self.add_input('ArmFloatSocket', 'X')
self.add_input('ArmFloatSocket', 'Y')
self.add_output('ArmNodeSocketAction', 'Out')

View file

@ -6,11 +6,10 @@ class CanvasSetPBNode(ArmLogicTreeNode):
bl_label = 'Set Canvas Progress Bar'
arm_version = 1
def init(self, context):
super(CanvasSetPBNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('NodeSocketString', 'Element')
self.add_input('NodeSocketInt', 'At')
self.add_input('NodeSocketInt', 'Max')
self.add_input('ArmStringSocket', 'Element')
self.add_input('ArmIntSocket', 'At')
self.add_input('ArmIntSocket', 'Max')
self.add_output('ArmNodeSocketAction', 'Out')

View file

@ -6,10 +6,9 @@ class CanvasSetRotationNode(ArmLogicTreeNode):
bl_label = 'Set Canvas Rotation'
arm_version = 1
def init(self, context):
super(CanvasSetRotationNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('NodeSocketString', 'Element')
self.add_input('NodeSocketFloat', 'Rad')
self.add_input('ArmStringSocket', 'Element')
self.add_input('ArmFloatSocket', 'Rad')
self.add_output('ArmNodeSocketAction', 'Out')

View file

@ -6,11 +6,10 @@ class CanvasSetScaleNode(ArmLogicTreeNode):
bl_label = 'Set Canvas Scale'
arm_version = 1
def init(self, context):
super(CanvasSetScaleNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('NodeSocketString', 'Element')
self.add_input('NodeSocketInt', 'Height')
self.add_input('NodeSocketInt', 'Width')
self.add_input('ArmStringSocket', 'Element')
self.add_input('ArmIntSocket', 'Height')
self.add_input('ArmIntSocket', 'Width')
self.add_output('ArmNodeSocketAction', 'Out')

View file

@ -6,10 +6,9 @@ class CanvasSetSliderNode(ArmLogicTreeNode):
bl_label = 'Set Canvas Slider'
arm_version = 1
def init(self, context):
super(CanvasSetSliderNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('NodeSocketString', 'Element')
self.add_input('NodeSocketFloat', 'Float')
self.add_input('ArmStringSocket', 'Element')
self.add_input('ArmFloatSocket', 'Float')
self.add_output('ArmNodeSocketAction', 'Out')

View file

@ -6,10 +6,9 @@ class CanvasSetTextNode(ArmLogicTreeNode):
bl_label = 'Set Canvas Text'
arm_version = 1
def init(self, context):
super(CanvasSetTextNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('NodeSocketString', 'Element')
self.add_input('NodeSocketString', 'Text')
self.add_input('ArmStringSocket', 'Element')
self.add_input('ArmStringSocket', 'Text')
self.add_output('ArmNodeSocketAction', 'Out')

View file

@ -6,13 +6,12 @@ class CanvasSetTextColorNode(ArmLogicTreeNode):
bl_label = 'Set Canvas Text Color'
arm_version = 1
def init(self, context):
super(CanvasSetTextColorNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('NodeSocketString', 'Element')
self.add_input('NodeSocketFloat', 'R')
self.add_input('NodeSocketFloat', 'G')
self.add_input('NodeSocketFloat', 'B')
self.add_input('NodeSocketFloat', 'A')
self.add_input('ArmStringSocket', 'Element')
self.add_input('ArmFloatSocket', 'R')
self.add_input('ArmFloatSocket', 'G')
self.add_input('ArmFloatSocket', 'B')
self.add_input('ArmFloatSocket', 'A')
self.add_output('ArmNodeSocketAction', 'Out')

View file

@ -6,10 +6,9 @@ class CanvasSetVisibleNode(ArmLogicTreeNode):
bl_label = 'Set Canvas Visible'
arm_version = 1
def init(self, context):
super(CanvasSetVisibleNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('NodeSocketString', 'Element')
self.add_input('NodeSocketBool', 'Visible')
self.add_input('ArmStringSocket', 'Element')
self.add_input('ArmBoolSocket', 'Visible')
self.add_output('ArmNodeSocketAction', 'Out')

View file

@ -14,9 +14,8 @@ class GetMouseLockNode(ArmLogicTreeNode):
arm_category = 'Input'
arm_section = 'mouse'
def init(self, context):
super(GetMouseLockNode, self).init(context)
self.outputs.new('NodeSocketBool', 'Is Locked')
def arm_init(self, context):
self.outputs.new('ArmBoolSocket', 'Is Locked')
def get_replacement_node(self, node_tree: bpy.types.NodeTree):
if self.arm_version not in (0, 1):

View file

@ -14,9 +14,8 @@ class GetMouseVisibleNode(ArmLogicTreeNode):
arm_section = 'mouse'
arm_version = 2
def init(self, context):
super(GetMouseVisibleNode, self).init(context)
self.outputs.new('NodeSocketBool', 'Is Visible')
def arm_init(self, context):
self.outputs.new('ArmBoolSocket', 'Is Visible')
def get_replacement_node(self, node_tree: bpy.types.NodeTree):
if self.arm_version not in (0, 1):

View file

@ -11,11 +11,10 @@ class MouseCoordsNode(ArmLogicTreeNode):
arm_section = 'mouse'
arm_version = 2
def init(self, context):
super(MouseCoordsNode, self).init(context)
self.add_output('NodeSocketVector', 'Coords')
self.add_output('NodeSocketVector', 'Movement')
self.add_output('NodeSocketInt', 'Wheel')
def arm_init(self, context):
self.add_output('ArmVectorSocket', 'Coords')
self.add_output('ArmVectorSocket', 'Movement')
self.add_output('ArmIntSocket', 'Wheel')
def get_replacement_node(self, node_tree: bpy.types.NodeTree):
if self.arm_version not in (0, 1):

View file

@ -11,7 +11,8 @@ class OnGamepadNode(ArmLogicTreeNode):
arm_section = 'gamepad'
arm_version = 2
property0: EnumProperty(
property0: HaxeEnumProperty(
'property0',
items = [('Down', 'Down', 'Down'),
('Started', 'Started', 'Started'),
('Released', 'Released', 'Released')],
@ -19,7 +20,8 @@ class OnGamepadNode(ArmLogicTreeNode):
# ('Moved Right', 'Moved Right', 'Moved Right'),],
name='', default='Started')
property1: EnumProperty(
property1: HaxeEnumProperty(
'property1',
items = [('cross', 'cross / a', 'cross / a'),
('circle', 'circle / b', 'circle / b'),
('square', 'square / x', 'square / x'),
@ -40,10 +42,9 @@ class OnGamepadNode(ArmLogicTreeNode):
('touchpad', 'touchpad', 'touchpad'),],
name='', default='cross')
def init(self, context):
super(OnGamepadNode, self).init(context)
def arm_init(self, context):
self.add_output('ArmNodeSocketAction', 'Out')
self.add_input('NodeSocketInt', 'Gamepad')
self.add_input('ArmIntSocket', 'Gamepad')
def draw_buttons(self, context, layout):
layout.prop(self, 'property0')

View file

@ -11,13 +11,15 @@ class OnKeyboardNode(ArmLogicTreeNode):
arm_section = 'keyboard'
arm_version = 2
property0: EnumProperty(
property0: HaxeEnumProperty(
'property0',
items = [('Down', 'Down', 'Down'),
('Started', 'Started', 'Started'),
('Released', 'Released', 'Released')],
name='', default='Started')
property1: EnumProperty(
property1: HaxeEnumProperty(
'property1',
items = [('a', 'a', 'a'),
('b', 'b', 'b'),
('c', 'c', 'c'),
@ -72,8 +74,7 @@ class OnKeyboardNode(ArmLogicTreeNode):
('down', 'down', 'down'),],
name='', default='space')
def init(self, context):
super(OnKeyboardNode, self).init(context)
def arm_init(self, context):
self.add_output('ArmNodeSocketAction', 'Out')
def draw_buttons(self, context, layout):

View file

@ -11,20 +11,21 @@ class OnMouseNode(ArmLogicTreeNode):
arm_section = 'mouse'
arm_version = 2
property0: EnumProperty(
property0: HaxeEnumProperty(
'property0',
items = [('Down', 'Down', 'Down'),
('Started', 'Started', 'Started'),
('Released', 'Released', 'Released'),
('Moved', 'Moved', 'Moved')],
name='', default='Down')
property1: EnumProperty(
property1: HaxeEnumProperty(
'property1',
items = [('left', 'left', 'left'),
('right', 'right', 'right'),
('middle', 'middle', 'middle')],
name='', default='left')
def init(self, context):
super(OnMouseNode, self).init(context)
def arm_init(self, context):
self.add_output('ArmNodeSocketAction', 'Out')
def draw_buttons(self, context, layout):

View file

@ -11,15 +11,15 @@ class OnSurfaceNode(ArmLogicTreeNode):
arm_section = 'surface'
arm_version = 2
property0: EnumProperty(
property0: HaxeEnumProperty(
'property0',
items = [('Touched', 'Touched', 'Touched'),
('Started', 'Started', 'Started'),
('Released', 'Released', 'Released'),
('Moved', 'Moved', 'Moved')],
name='', default='Touched')
def init(self, context):
super(OnSurfaceNode, self).init(context)
def arm_init(self, context):
self.add_output('ArmNodeSocketAction', 'Out')
def draw_buttons(self, context, layout):

View file

@ -11,15 +11,15 @@ class OnVirtualButtonNode(ArmLogicTreeNode):
arm_section = 'virtual'
arm_version = 2
property0: EnumProperty(
property0: HaxeEnumProperty(
'property0',
items = [('Down', 'Down', 'Down'),
('Started', 'Started', 'Started'),
('Released', 'Released', 'Released')],
name='', default='Started')
property1: StringProperty(name='', default='button')
property1: HaxeStringProperty('property1', name='', default='button')
def init(self, context):
super(OnVirtualButtonNode, self).init(context)
def arm_init(self, context):
self.add_output('ArmNodeSocketAction', 'Out')
def draw_buttons(self, context, layout):

View file

@ -10,8 +10,7 @@ class PauseActionNode(ArmLogicTreeNode):
arm_category = 'Animation'
arm_version = 2
def init(self, context):
super(PauseActionNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('ArmNodeSocketObject', 'Object')
self.add_output('ArmNodeSocketAction', 'Out')

View file

@ -11,8 +11,7 @@ class PauseTilesheetNode(ArmLogicTreeNode):
arm_section = 'tilesheet'
arm_version = 2
def init(self, context):
super(PauseTilesheetNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('ArmNodeSocketObject', 'Object')
self.add_output('ArmNodeSocketAction', 'Out')

View file

@ -10,8 +10,7 @@ class PauseTraitNode(ArmLogicTreeNode):
arm_category = 'Trait'
arm_version = 2
def init(self, context):
super(PauseTraitNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('NodeSocketShader', 'Trait')
self.add_input('ArmDynamicSocket', 'Trait')
self.add_output('ArmNodeSocketAction', 'Out')

View file

@ -10,11 +10,10 @@ class PlayActionNode(ArmLogicTreeNode):
arm_category = 'Animation'
arm_version = 2
def init(self, context):
super(PlayActionNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('ArmNodeSocketObject', 'Object')
self.add_input('ArmNodeSocketAnimAction', 'Action')
self.add_input('NodeSocketFloat', 'Blend', default_value=0.2)
self.add_input('ArmFloatSocket', 'Blend', default_value=0.2)
self.add_output('ArmNodeSocketAction', 'Out')
self.add_output('ArmNodeSocketAction', 'Done')

View file

@ -10,8 +10,7 @@ class ResumeActionNode(ArmLogicTreeNode):
arm_category = 'Animation'
arm_version = 2
def init(self, context):
super(ResumeActionNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('ArmNodeSocketObject', 'Object')
self.add_output('ArmNodeSocketAction', 'Out')

View file

@ -10,8 +10,7 @@ class ResumeTilesheetNode(ArmLogicTreeNode):
arm_category = 'Animation'
arm_version = 2
def init(self, context):
super(ResumeTilesheetNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('ArmNodeSocketObject', 'Object')
self.add_output('ArmNodeSocketAction', 'Out')

View file

@ -10,8 +10,7 @@ class ResumeTraitNode(ArmLogicTreeNode):
arm_category = 'Trait'
arm_version = 2
def init(self, context):
super(ResumeTraitNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('NodeSocketShader', 'Trait')
self.add_input('ArmDynamicSocket', 'Trait')
self.add_output('ArmNodeSocketAction', 'Out')

View file

@ -11,12 +11,11 @@ class RotateObjectAroundAxisNode(ArmLogicTreeNode):
arm_section = 'rotation'
arm_version = 2
def init(self, context):
super(RotateObjectAroundAxisNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('ArmNodeSocketObject', 'Object')
self.add_input('NodeSocketVector', 'Axis', default_value=[0, 0, 1])
self.add_input('NodeSocketFloat', 'Angle')
self.add_input('ArmVectorSocket', 'Axis', default_value=[0, 0, 1])
self.add_input('ArmFloatSocket', 'Angle')
self.add_output('ArmNodeSocketAction', 'Out')
def get_replacement_node(self, node_tree: bpy.types.NodeTree):

View file

@ -11,9 +11,8 @@ class ScaleObjectNode(ArmLogicTreeNode):
arm_section = 'scale'
arm_version = 2
def init(self, context):
super(ScaleObjectNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('ArmNodeSocketObject', 'Object')
self.add_input('NodeSocketVector', 'Scale')
self.add_input('ArmVectorSocket', 'Scale')
self.add_output('ArmNodeSocketAction', 'Out')

View file

@ -11,10 +11,9 @@ class SetMouseLockNode(ArmLogicTreeNode):
arm_section = 'mouse'
arm_version = 2
def init(self, context):
super(SetMouseLockNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('NodeSocketBool', 'Lock')
self.add_input('ArmBoolSocket', 'Lock')
self.add_output('ArmNodeSocketAction', 'Out')
def get_replacement_node(self, node_tree: bpy.types.NodeTree):

View file

@ -11,10 +11,9 @@ class ShowMouseNode(ArmLogicTreeNode):
arm_section = 'mouse'
arm_version = 2
def init(self, context):
super(ShowMouseNode, self).init(context)
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('NodeSocketBool', 'Show')
self.add_input('ArmBoolSocket', 'Show')
self.add_output('ArmNodeSocketAction', 'Out')
def get_replacement_node(self, node_tree: bpy.types.NodeTree):

Some files were not shown because too many files have changed in this diff Show more