Merge pull request #2305 from niacdoial/newrotation
Added a rotation node-socket
This commit is contained in:
commit
85edde6d24
|
@ -1,10 +1,13 @@
|
|||
package armory.logicnode;
|
||||
|
||||
import iron.object.Object;
|
||||
import iron.math.Vec3;
|
||||
import iron.math.Quat;
|
||||
import iron.math.Vec4;
|
||||
|
||||
class GetRotationNode extends LogicNode {
|
||||
|
||||
public var property0: String;
|
||||
|
||||
public function new(tree: LogicTree) {
|
||||
super(tree);
|
||||
}
|
||||
|
@ -16,34 +19,16 @@ class GetRotationNode extends LogicNode {
|
|||
return null;
|
||||
}
|
||||
|
||||
var rot = object.transform.rot;
|
||||
|
||||
switch (from) {
|
||||
case 0:
|
||||
// euler angles
|
||||
return object.transform.rot.getEuler();
|
||||
case 1:
|
||||
// vector
|
||||
var sqrtW = Math.sqrt(1 - (rot.w * rot.w));
|
||||
if (sqrtW == 0) {
|
||||
return new Vec3(0, 0, 1);
|
||||
}
|
||||
return new Vec3(rot.x / sqrtW, rot.y / sqrtW, rot.z / sqrtW);
|
||||
case 2:
|
||||
// angle radians
|
||||
var angle = 2 * Math.acos(rot.w);
|
||||
return angle;
|
||||
case 3:
|
||||
// angle degrees
|
||||
var angle = 2 * Math.acos(rot.w);
|
||||
return angle * (180 / Math.PI);
|
||||
case 4:
|
||||
//quaternion xyz
|
||||
return new Vec3(rot.x, rot.y, rot.z);
|
||||
case 5:
|
||||
//quaternion w
|
||||
return rot.w;
|
||||
}
|
||||
switch(property0){
|
||||
case "Local":
|
||||
return object.transform.rot;
|
||||
case "Global":{
|
||||
var useless1 = new Vec4();
|
||||
var ret = new Quat();
|
||||
object.transform.world.decompose(useless1, ret, useless1);
|
||||
return ret;
|
||||
}}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,6 +37,6 @@ class LookAtNode extends LogicNode {
|
|||
v2.setFrom(vto).sub(vfrom).normalize();
|
||||
|
||||
q.fromTo(v1, v2);
|
||||
return q.getEuler();
|
||||
return q;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ class QuaternionMathNode extends LogicNode {
|
|||
|
||||
override function get(from: Int): Dynamic {
|
||||
switch (property0) {
|
||||
// 1 argument: Module, Normalize, GetEuler
|
||||
// 1 argument: Module, Normalize
|
||||
case "Module": {
|
||||
var q: Quat = inputs[0].get();
|
||||
if (q == null) return null;
|
||||
|
@ -32,159 +32,106 @@ class QuaternionMathNode extends LogicNode {
|
|||
res_q.setFrom(q);
|
||||
res_q = res_q.normalize();
|
||||
}
|
||||
case "GetEuler": {
|
||||
var q: Quat = inputs[0].get();
|
||||
if (q == null) return null;
|
||||
res_q.setFrom(q);
|
||||
res_v = res_q.getEuler();
|
||||
}
|
||||
// 2 arguments: FromTo, FromMat, FromRotationMat, ToAxisAngle
|
||||
case "FromTo": {
|
||||
var v1: Vec4 = inputs[0].get();
|
||||
var v2: Vec4 = inputs[1].get();
|
||||
if ((v1 == null) || (v2 == null)) return null;
|
||||
res_q.fromTo(v1, v2);
|
||||
}
|
||||
case "FromMat": {
|
||||
var q: Quat = inputs[0].get();
|
||||
var m: Mat4 = inputs[1].get();
|
||||
if ((q == null) || (m == null)) return null;
|
||||
res_q.setFrom(q);
|
||||
res_q = res_q.fromMat(m);
|
||||
}
|
||||
case "FromRotationMat": {
|
||||
var q: Quat = inputs[0].get();
|
||||
var m: Mat4 = inputs[1].get();
|
||||
if ((q == null) || (m == null)) return null;
|
||||
res_q.setFrom(q);
|
||||
res_q = res_q.fromRotationMat(m);
|
||||
}
|
||||
case "ToAxisAngle": {
|
||||
var q: Quat = inputs[0].get();
|
||||
var v: Vec4 = inputs[1].get();
|
||||
if ((q == null) || (v == null)) return null;
|
||||
res_q.setFrom(q);
|
||||
res_f = res_q.toAxisAngle(v);
|
||||
}
|
||||
// # 3 arguments: Lerp, Slerp, FromAxisAngle, FromEuler
|
||||
case "Lerp": {
|
||||
var from: Quat = inputs[0].get();
|
||||
var to: Quat = inputs[1].get();
|
||||
var f: Float = inputs[2].get();
|
||||
if ((from == null) || (to == null)) return null;
|
||||
res_q = res_q.lerp(from, to, f);
|
||||
}
|
||||
case "Slerp": {
|
||||
var from: Quat = inputs[0].get();
|
||||
var to: Quat = inputs[1].get();
|
||||
var f: Float = inputs[2].get();
|
||||
if ((from == null) || (to == null)) return null;
|
||||
res_q = res_q.slerp(from, to, f);
|
||||
}
|
||||
case "FromAxisAngle": {
|
||||
var q: Quat = inputs[0].get();
|
||||
var axis: Vec4 = inputs[1].get();
|
||||
var angle: Float = inputs[2].get();
|
||||
if ((q == null) || (axis == null)) return null;
|
||||
res_q.setFrom(q);
|
||||
res_q = res_q.fromAxisAngle(axis, angle);
|
||||
}
|
||||
case "FromEuler": {
|
||||
var x: Float = inputs[0].get();
|
||||
var y: Float = inputs[1].get();
|
||||
var z: Float = inputs[2].get();
|
||||
res_q = res_q.fromEuler(x, y, z);
|
||||
}
|
||||
// Many arguments: Add, Subtract, DotProduct, Multiply
|
||||
case "Add": {
|
||||
var q: Quat = inputs[0].get();
|
||||
if (q == null) return null;
|
||||
res_q.setFrom(q);
|
||||
var q2 = new Quat();
|
||||
res_v = inputs[0].get();
|
||||
res_f = inputs[1].get();
|
||||
if (res_v == null || res_f == null) return null;
|
||||
|
||||
res_q.set(res_v.x, res_v.y, res_v.z, res_f);
|
||||
var i = 1;
|
||||
while (i < inputs.length) {
|
||||
q2 = inputs[i].get();
|
||||
if (q2 == null) return null;
|
||||
res_q.add(q2);
|
||||
while (2*i+1 < inputs.length) {
|
||||
res_v = inputs[2*i].get();
|
||||
res_f = inputs[2*i+1].get();
|
||||
if (res_v == null || res_f == null) return null;
|
||||
res_q.x += res_v.x;
|
||||
res_q.y += res_v.y;
|
||||
res_q.z += res_v.z;
|
||||
res_q.w += res_f;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
case "Subtract": {
|
||||
var q: Quat = inputs[0].get();
|
||||
if (q == null) return null;
|
||||
res_q.setFrom(q);
|
||||
var q2 = new Quat();
|
||||
res_v = inputs[0].get();
|
||||
res_f = inputs[1].get();
|
||||
if (res_v == null || res_f == null) return null;
|
||||
|
||||
res_q.set(res_v.x, res_v.y, res_v.z, res_f);
|
||||
var i = 1;
|
||||
while (i < inputs.length) {
|
||||
q2 = inputs[i].get();
|
||||
if (q2 == null) return null;
|
||||
res_q.sub(q2);
|
||||
while (2*i+1 < inputs.length) {
|
||||
res_v = inputs[2*i].get();
|
||||
res_f = inputs[2*i+1].get();
|
||||
if (res_v == null || res_f == null) return null;
|
||||
res_q.x -= res_v.x;
|
||||
res_q.y -= res_v.y;
|
||||
res_q.z -= res_v.z;
|
||||
res_q.w -= res_f;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
case "Multiply": {
|
||||
var q: Quat = inputs[0].get();
|
||||
if (q == null) return null;
|
||||
res_q.setFrom(q);
|
||||
var q2 = new Quat();
|
||||
res_v = inputs[0].get();
|
||||
res_f = inputs[1].get();
|
||||
if (res_v == null || res_f == null) return null;
|
||||
|
||||
res_q.set(res_v.x, res_v.y, res_v.z, res_f);
|
||||
var i = 1;
|
||||
while (i < inputs.length) {
|
||||
q2 = inputs[i].get();
|
||||
if (q2 == null) return null;
|
||||
res_q.mult(q2);
|
||||
while (2*i+1 < inputs.length) {
|
||||
res_v = inputs[2*i].get();
|
||||
res_f = inputs[2*i+1].get();
|
||||
if (res_v == null || res_f == null) return null;
|
||||
var temp_q = new Quat(res_v.x, res_v.y, res_v.z, res_f);
|
||||
res_q.mult(temp_q);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
case "MultiplyFloats": {
|
||||
var q: Quat = inputs[0].get();
|
||||
if (q == null) return null;
|
||||
res_q.setFrom(q);
|
||||
res_v = inputs[0].get();
|
||||
res_f = inputs[1].get();
|
||||
if (res_v == null || res_f == null) return null;
|
||||
|
||||
res_q.set(res_v.x, res_v.y, res_v.z, res_f);
|
||||
var f: Float = 1.0;
|
||||
var i = 1;
|
||||
var i = 2;
|
||||
while (i < inputs.length) {
|
||||
f = inputs[i].get();
|
||||
res_q.scale(f);
|
||||
f *= inputs[i].get();
|
||||
if (f == null) return null;
|
||||
i++;
|
||||
}
|
||||
res_q.scale(f);
|
||||
}
|
||||
case "DotProduct": {
|
||||
var q: Quat = inputs[0].get();
|
||||
if (q == null) return null;
|
||||
res_q.setFrom(q);
|
||||
var q2 = new Quat();
|
||||
case "DotProduct": { // what this does with more than 2 terms is not *remotely* intuitive. Heck, you could consider it a footgun!
|
||||
|
||||
res_v = inputs[0].get();
|
||||
var temp_f = inputs[1].get();
|
||||
if (res_v == null || temp_f == null) return null;
|
||||
|
||||
res_q.set(res_v.x, res_v.y, res_v.z, temp_f);
|
||||
var i = 1;
|
||||
while (i < inputs.length) {
|
||||
q2 = inputs[i].get();
|
||||
if (q2 == null) return null;
|
||||
res_f = res_q.dot(q2);
|
||||
while (2*i+1 < inputs.length) {
|
||||
res_v = inputs[2*i].get();
|
||||
temp_f = inputs[2*i+1].get();
|
||||
if (res_v == null || temp_f == null) return null;
|
||||
var temp_q = new Quat(res_v.x, res_v.y, res_v.z, temp_f);
|
||||
res_f = res_q.dot(temp_q);
|
||||
res_q.set(res_f, res_f, res_f, res_f);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Return and check separator
|
||||
switch (from) {
|
||||
case 0: {
|
||||
if (property0 == 'GetEuler')
|
||||
return res_v;
|
||||
else
|
||||
return res_q;
|
||||
return res_q;
|
||||
}
|
||||
case 1:
|
||||
if (property1) {
|
||||
return res_q.x;
|
||||
} else {
|
||||
if (property0 == "DotProduct" || property0 == "Module") {
|
||||
return res_f;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
case 2:
|
||||
if (property1) return res_q.y;
|
||||
case 3:
|
||||
if (property1) return res_q.z;
|
||||
case 4:
|
||||
if (property1) return res_q.w;
|
||||
case 5:
|
||||
if (property1) return res_f;
|
||||
default: {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -2,13 +2,11 @@ package armory.logicnode;
|
|||
|
||||
import iron.object.Object;
|
||||
import iron.math.Quat;
|
||||
import iron.math.Vec4;
|
||||
import armory.trait.physics.RigidBody;
|
||||
|
||||
class RotateObjectNode extends LogicNode {
|
||||
|
||||
public var property0 = "Euler Angles";
|
||||
var q = new Quat();
|
||||
public var property0 = "Local";
|
||||
|
||||
public function new(tree: LogicTree) {
|
||||
super(tree);
|
||||
|
@ -16,32 +14,19 @@ class RotateObjectNode extends LogicNode {
|
|||
|
||||
override function run(from: Int) {
|
||||
var object: Object = inputs[1].get();
|
||||
var vec: Vec4 = inputs[2].get();
|
||||
var q: Quat = inputs[2].get();
|
||||
|
||||
// note: here, the next line is disabled because old versions of the node don't have a third input.
|
||||
// when those old versions will be considered remove, feel free to uncomment that, and replace the other `inputs[3].get()` by `w` in this file.
|
||||
//var w: Float = inputs[3].get();
|
||||
if (object == null || q == null) return;
|
||||
|
||||
if (object == null || vec == null) return;
|
||||
|
||||
switch (property0) {
|
||||
case "Euler Angles":
|
||||
q.fromEuler(vec.x, vec.y, vec.z);
|
||||
case "Angle Axies (Degrees)" | "Angle Axies (Radians)":
|
||||
var angle: Float = inputs[3].get();
|
||||
if (property0 == "Angle Axies (Degrees)") {
|
||||
angle = angle * (Math.PI / 180);
|
||||
}
|
||||
var angleSin = Math.sin(angle / 2);
|
||||
vec = vec.normalize();
|
||||
var angleCos = Math.cos(angle / 2);
|
||||
q = new Quat(vec.x * angleSin, vec.y * angleSin, vec.z * angleSin, angleCos);
|
||||
case "Quaternion":
|
||||
q = new Quat(vec.x, vec.y, vec.z, inputs[3].get());
|
||||
q.normalize();
|
||||
q.normalize();
|
||||
switch (property0){
|
||||
case "Local":
|
||||
object.transform.rot.mult(q);
|
||||
case "Global":
|
||||
object.transform.rot.multquats(q, object.transform.rot);
|
||||
// that function call (Quat.multquats) is weird: it both modifies the object, and returns `this`
|
||||
}
|
||||
|
||||
object.transform.rot.mult(q);
|
||||
|
||||
object.transform.buildMatrix();
|
||||
|
||||
#if arm_physics
|
||||
|
|
96
Sources/armory/logicnode/RotationMathNode.hx
Normal file
96
Sources/armory/logicnode/RotationMathNode.hx
Normal file
|
@ -0,0 +1,96 @@
|
|||
package armory.logicnode;
|
||||
|
||||
import iron.math.Quat;
|
||||
import iron.math.Vec4;
|
||||
import iron.math.Mat4;
|
||||
import kha.FastFloat;
|
||||
|
||||
class RotationMathNode extends LogicNode {
|
||||
|
||||
public var property0: String; // Operation
|
||||
var res_q = new Quat();
|
||||
var res_v = new Vec4();
|
||||
var res_f: FastFloat = 0.0;
|
||||
|
||||
public function new(tree: LogicTree) {
|
||||
super(tree);
|
||||
}
|
||||
|
||||
override function get(from: Int): Dynamic {
|
||||
//var q: Quat = inputs[0].get();
|
||||
//if (q==null) return null;
|
||||
|
||||
//var res_q: Quat = new Quat();
|
||||
switch (property0) {
|
||||
// 1 argument: Normalize, Inverse
|
||||
case "Normalize": {
|
||||
var q: Quat = inputs[0].get();
|
||||
if (q==null) return null;
|
||||
res_q.setFrom(q);
|
||||
res_q = res_q.normalize();
|
||||
}
|
||||
case "Inverse": {
|
||||
var q: Quat = inputs[0].get();
|
||||
if (q==null) return null;
|
||||
var modl = q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w;
|
||||
modl = -1/modl;
|
||||
res_q.w = -q.w*modl;
|
||||
res_q.x = q.x*modl;
|
||||
res_q.y = q.y*modl;
|
||||
res_q.z = q.z*modl;
|
||||
}
|
||||
// 2 arguments: Compose, Amplify, FromTo, FromRotationMat,
|
||||
case "FromTo": {
|
||||
var v1: Vec4 = inputs[0].get();
|
||||
var v2: Vec4 = inputs[1].get();
|
||||
if ((v1 == null) || (v2 == null)) return null;
|
||||
res_q.fromTo(v1, v2);
|
||||
}
|
||||
case "Compose": {
|
||||
var v1: Quat = inputs[0].get();
|
||||
var v2: Quat = inputs[1].get();
|
||||
if ((v1 == null) || (v2 == null)) return null;
|
||||
res_q.multquats(v1,v2);
|
||||
}
|
||||
case "Amplify": {
|
||||
var v1: Quat = inputs[0].get();
|
||||
var v2: Float = inputs[1].get();
|
||||
if ((v1 == null) || (v2 == null)) return null;
|
||||
res_q.setFrom(v1);
|
||||
var fac2 = Math.sqrt(1- res_q.w*res_q.w);
|
||||
if (fac2 > 0.001) {
|
||||
var fac1 = v2*Math.acos(res_q.w);
|
||||
res_q.w = Math.cos(fac1);
|
||||
fac1 = Math.sin(fac1)/fac2;
|
||||
res_q.x *= fac1;
|
||||
res_q.y *= fac1;
|
||||
res_q.z *= fac1;
|
||||
}
|
||||
}
|
||||
//case "FromRotationMat": {
|
||||
// var m: Mat4 = inputs[1].get();
|
||||
// if (m == null) return null;
|
||||
|
||||
// res_q = res_q.fromMat(m);
|
||||
//}
|
||||
// # 3 arguments: Lerp, Slerp, FromAxisAngle, FromEuler
|
||||
case "Lerp": {
|
||||
//var from = q;
|
||||
var from: Quat = inputs[0].get();
|
||||
var to: Quat = inputs[1].get();
|
||||
var f: Float = inputs[2].get();
|
||||
if ((from == null) || (f == null) || (to == null)) return null;
|
||||
res_q = res_q.lerp(from, to, f);
|
||||
}
|
||||
case "Slerp": {
|
||||
//var from = q;
|
||||
var from:Quat = inputs[0].get();
|
||||
var to: Quat = inputs[1].get();
|
||||
var f: Float = inputs[2].get();
|
||||
if ((from == null) || (f == null) || (to == null)) return null;
|
||||
res_q = res_q.slerp(from, to, f);
|
||||
}
|
||||
}
|
||||
return res_q;
|
||||
}
|
||||
}
|
120
Sources/armory/logicnode/RotationNode.hx
Normal file
120
Sources/armory/logicnode/RotationNode.hx
Normal file
|
@ -0,0 +1,120 @@
|
|||
package armory.logicnode;
|
||||
|
||||
import iron.math.Vec4;
|
||||
import iron.math.Quat;
|
||||
import kha.FastFloat;
|
||||
|
||||
class RotationNode extends LogicNode {
|
||||
|
||||
static inline var toRAD: FastFloat = 0.017453292519943295; // 180/pi
|
||||
|
||||
public var property0: String; // type of input (EulerAngles, AxisAngle, Quaternion)
|
||||
public var property1: String; // angle unit (Deg, Rad)
|
||||
public var property2: String; // euler order (XYZ, XZY, etc…)
|
||||
|
||||
public var value: Quat;
|
||||
//var input0_cache: Vec4 = new Vec4();
|
||||
//var input1_cache: Float = 0;
|
||||
var input_length: Int = 0;
|
||||
|
||||
public function new(tree: LogicTree, x: Null<Float> = null,
|
||||
y: Null<Float> = null,
|
||||
z: Null<Float> = null,
|
||||
w: Null<Float> = null
|
||||
) {
|
||||
super(tree);
|
||||
this.value = new Quat();
|
||||
if (x!=null) this.value.set(x,y,z,w);
|
||||
for (input in inputs) {
|
||||
if (input !=null)
|
||||
this.input_length +=1;
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
override function get(from: Int): Dynamic {
|
||||
//var inp0 = inputs[0].get();
|
||||
//var inp
|
||||
//if (inputs[0].get())
|
||||
if (inputs.length == 0){
|
||||
return this.value;
|
||||
}
|
||||
|
||||
switch (property0){
|
||||
case "Quaternion": {
|
||||
if (inputs[0]!=null && inputs[1]!=null) {
|
||||
var vect: Vec4 = inputs[0].get();
|
||||
value.x = vect.x;
|
||||
value.y = vect.y;
|
||||
value.z = vect.z;
|
||||
value.w = inputs[1].get();
|
||||
}
|
||||
}
|
||||
case "AxisAngle": {
|
||||
if (inputs[0]!=null && inputs[1]!=null){
|
||||
var vec: Vec4 = inputs[0].get();
|
||||
var angle: FastFloat = inputs[1].get();
|
||||
if (property1=="Deg")
|
||||
angle *= toRAD;
|
||||
value.fromAxisAngle(vec, angle);
|
||||
}
|
||||
}
|
||||
case "EulerAngles": {
|
||||
if (inputs[0] != null){
|
||||
var vec: Vec4 = new Vec4().setFrom(inputs[0].get());
|
||||
if (property1=="Deg"){
|
||||
vec.x *= toRAD;
|
||||
vec.y *= toRAD;
|
||||
vec.z *= toRAD;
|
||||
}
|
||||
this.value.fromEulerOrdered(vec, property2);
|
||||
}
|
||||
}
|
||||
default: {
|
||||
return property0;
|
||||
}
|
||||
}
|
||||
return this.value;
|
||||
}
|
||||
|
||||
override function set(value: Dynamic) {
|
||||
switch (property0){
|
||||
case "Quaternion": {
|
||||
if (input_length>1) {
|
||||
var vect = new Vec4();
|
||||
vect.x = value.x;
|
||||
vect.y = value.y;
|
||||
vect.z = value.z;
|
||||
inputs[0].set(vect);
|
||||
inputs[1].set(value.w);
|
||||
}
|
||||
}
|
||||
case "AxisAngle": {
|
||||
if (input_length>1){
|
||||
var vec = new Vec4();
|
||||
var angle = this.value.toAxisAngle(vec);
|
||||
if (property1=="Deg")
|
||||
angle /= toRAD;
|
||||
inputs[0].set(vec);
|
||||
inputs[1].set(angle);
|
||||
|
||||
}
|
||||
}
|
||||
case "EulerAngles": {
|
||||
if (input_length>0){
|
||||
var vec:Vec4 = value.toEulerOrdered(property2);
|
||||
if (property1=="Deg"){
|
||||
vec.x /= toRAD;
|
||||
vec.y /= toRAD;
|
||||
vec.z /= toRAD;
|
||||
}
|
||||
inputs[0].set(vec);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (input_length > 0){
|
||||
// NYI
|
||||
}else this.value=value;
|
||||
}
|
||||
}
|
60
Sources/armory/logicnode/SeparateRotationNode.hx
Normal file
60
Sources/armory/logicnode/SeparateRotationNode.hx
Normal file
|
@ -0,0 +1,60 @@
|
|||
package armory.logicnode;
|
||||
|
||||
import kha.FastFloat;
|
||||
import iron.math.Quat;
|
||||
import iron.math.Vec4;
|
||||
|
||||
class SeparateRotationNode extends LogicNode {
|
||||
|
||||
public var property0 = "EulerAngles"; // EulerAngles, AxisAngle, or Quat
|
||||
public var property1 = "Rad"; // Rad or Deg
|
||||
public var property2 = "XYZ";
|
||||
|
||||
static inline var toDEG:FastFloat = 57.29577951308232; // 180/pi
|
||||
|
||||
var input_cache = new Quat();
|
||||
var euler_cache = new Vec4();
|
||||
var aa_axis_cache = new Vec4();
|
||||
var aa_angle_cache: Float = 0;
|
||||
|
||||
|
||||
public function new(tree: LogicTree) {
|
||||
super(tree);
|
||||
}
|
||||
|
||||
override function get(from: Int): Dynamic {
|
||||
var q: Quat = inputs[0].get();
|
||||
if (q == null) return null;
|
||||
q.normalize();
|
||||
|
||||
switch (property0) {
|
||||
case "EulerAngles":
|
||||
if (q!=this.input_cache)
|
||||
euler_cache = q.toEulerOrdered(property2);
|
||||
if (from>0)
|
||||
return null;
|
||||
|
||||
switch (property1){
|
||||
case "Rad": return euler_cache;
|
||||
case "Deg": return new Vec4(euler_cache.x*toDEG, euler_cache.y*toDEG, euler_cache.z*toDEG);
|
||||
}
|
||||
|
||||
case "AxisAngle":
|
||||
if (q!=this.input_cache)
|
||||
aa_angle_cache = q.toAxisAngle(aa_axis_cache);
|
||||
switch (from){
|
||||
case 0: return aa_axis_cache;
|
||||
case 1: switch(property1){
|
||||
case "Rad": return aa_angle_cache;
|
||||
case "Deg": return toDEG*aa_angle_cache;
|
||||
}
|
||||
}
|
||||
case "Quaternion":
|
||||
switch(from){
|
||||
case 0: return new Vec4(q.x,q.y,q.z);
|
||||
case 1: return q.w;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -20,7 +20,7 @@ class SeparateTransformNode extends LogicNode {
|
|||
matrix.decompose(loc, rot, scale);
|
||||
|
||||
if (from == 0) return loc;
|
||||
else if (from == 1) return rot.getEuler();
|
||||
else if (from == 1) return rot;
|
||||
else return scale;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
|
||||
package armory.logicnode;
|
||||
|
||||
import iron.object.Object;
|
||||
import iron.math.Quat;
|
||||
import iron.math.Vec4;
|
||||
import armory.trait.physics.RigidBody;
|
||||
|
||||
class SetRotationNode extends LogicNode {
|
||||
|
||||
public var property0: String;
|
||||
public var property0: String; // UNUSED
|
||||
|
||||
public function new(tree: LogicTree) {
|
||||
super(tree);
|
||||
|
@ -16,27 +16,13 @@ class SetRotationNode extends LogicNode {
|
|||
override function run(from: Int) {
|
||||
var object: Object = inputs[1].get();
|
||||
if (object == null) return;
|
||||
var vec: Vec4 = inputs[2].get();
|
||||
if (vec == null) return;
|
||||
var w: Float = inputs[3].get();
|
||||
var q: Quat = inputs[2].get();
|
||||
if (q == null) return;
|
||||
|
||||
switch (property0) {
|
||||
case "Euler Angles":
|
||||
object.transform.rot.fromEuler(vec.x, vec.y, vec.z);
|
||||
case "Angle Axies (Degrees)" | "Angle Axies (Radians)":
|
||||
var angle: Float = w;
|
||||
if (property0 == "Angle Axies (Degrees)") {
|
||||
angle = angle * (Math.PI / 180);
|
||||
}
|
||||
var angleSin = Math.sin(angle / 2);
|
||||
vec = vec.normalize();
|
||||
var angleCos = Math.cos(angle / 2);
|
||||
object.transform.rot = new Quat(vec.x * angleSin, vec.y * angleSin, vec.z * angleSin, angleCos);
|
||||
case "Quaternion":
|
||||
object.transform.rot = new Quat(vec.x, vec.y, vec.z, w);
|
||||
object.transform.rot.normalize();
|
||||
}
|
||||
q.normalize();
|
||||
object.transform.rot = q;
|
||||
object.transform.buildMatrix();
|
||||
|
||||
#if arm_physics
|
||||
var rigidBody = object.getTrait(RigidBody);
|
||||
if (rigidBody != null) {
|
||||
|
|
|
@ -7,9 +7,9 @@ import iron.math.Quat;
|
|||
class TransformNode extends LogicNode {
|
||||
|
||||
var value: Mat4 = Mat4.identity();
|
||||
static var q = new Quat();
|
||||
static var v1 = new Vec4();
|
||||
static var v2 = new Vec4();
|
||||
var q = new Quat();
|
||||
var v1 = new Vec4();
|
||||
var v2 = new Vec4();
|
||||
|
||||
public function new(tree: LogicTree) {
|
||||
super(tree);
|
||||
|
@ -17,18 +17,21 @@ class TransformNode extends LogicNode {
|
|||
|
||||
override function get(from: Int): Dynamic {
|
||||
var loc: Vec4 = inputs[0].get();
|
||||
var rot: Vec4 = inputs[1].get();
|
||||
var rot: Quat = new Quat().setFrom(inputs[1].get());
|
||||
rot.normalize();
|
||||
var scale: Vec4 = inputs[2].get();
|
||||
if (loc == null && rot == null && scale == null) return this.value;
|
||||
if (loc == null || rot == null || scale == null) return null;
|
||||
q.fromEuler(rot.x, rot.y, rot.z);
|
||||
value.compose(loc, q, scale);
|
||||
return value;
|
||||
this.value.compose(loc, rot, scale);
|
||||
return this.value;
|
||||
}
|
||||
|
||||
override function set(value: Dynamic) {
|
||||
cast(value, Mat4).decompose(v1, q, v2);
|
||||
inputs[0].set(v1);
|
||||
inputs[1].set(q.getEuler());
|
||||
inputs[2].set(v2);
|
||||
if (inputs.length>0){
|
||||
cast(value, Mat4).decompose(v1, q, v2);
|
||||
inputs[0].set(v1);
|
||||
inputs[1].set(q);
|
||||
inputs[2].set(v2);
|
||||
}else this.value = value;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ from typing import OrderedDict as ODict # Prevent naming conflicts
|
|||
import bpy.types
|
||||
from bpy.props import *
|
||||
from nodeitems_utils import NodeItem
|
||||
from arm.logicnode.arm_sockets import ArmCustomSocket
|
||||
|
||||
import arm # we cannot import arm.livepatch here or we have a circular import
|
||||
# Pass custom property types and NodeReplacement forward to individual
|
||||
|
@ -169,7 +170,13 @@ class ArmLogicTreeNode(bpy.types.Node):
|
|||
socket = self.inputs.new(socket_type, socket_name)
|
||||
|
||||
if default_value is not None:
|
||||
socket.default_value = default_value
|
||||
if isinstance(socket, ArmCustomSocket):
|
||||
if socket.arm_socket_type != 'NONE':
|
||||
socket.default_value_raw = default_value
|
||||
else:
|
||||
raise ValueError('specified a default value for an input node that doesn\'t accept one')
|
||||
else: # should not happen anymore?
|
||||
socket.default_value = default_value
|
||||
|
||||
if is_var and not socket.display_shape.endswith('_DOT'):
|
||||
socket.display_shape += '_DOT'
|
||||
|
@ -185,8 +192,12 @@ class ArmLogicTreeNode(bpy.types.Node):
|
|||
"""
|
||||
socket = self.outputs.new(socket_type, socket_name)
|
||||
|
||||
# FIXME: …a default_value on an output socket? Why is that a thing?
|
||||
if default_value is not None:
|
||||
socket.default_value = default_value
|
||||
if socket.arm_socket_type != 'NONE':
|
||||
socket.default_value_raw = default_value
|
||||
else:
|
||||
raise ValueError('specified a default value for an input node that doesn\'t accept one')
|
||||
|
||||
if is_var and not socket.display_shape.endswith('_DOT'):
|
||||
socket.display_shape += '_DOT'
|
||||
|
@ -208,7 +219,14 @@ class ArmNodeAddInputButton(bpy.types.Operator):
|
|||
def execute(self, context):
|
||||
global array_nodes
|
||||
inps = array_nodes[self.node_index].inputs
|
||||
inps.new(self.socket_type, self.name_format.format(str(len(inps) + self.index_name_offset)))
|
||||
|
||||
socket_types = self.socket_type.split(';')
|
||||
name_formats = self.name_format.split(';')
|
||||
assert len(socket_types)==len(name_formats)
|
||||
|
||||
format_index = (len(inps) + self.index_name_offset) //len(socket_types)
|
||||
for socket_type, name_format in zip(socket_types, name_formats):
|
||||
inps.new(socket_type, name_format.format(str(format_index)))
|
||||
|
||||
# Reset to default again for subsequent calls of this operator
|
||||
self.node_index = ''
|
||||
|
@ -238,14 +256,17 @@ class ArmNodeRemoveInputButton(bpy.types.Operator):
|
|||
bl_label = 'Remove Input'
|
||||
bl_options = {'UNDO', 'INTERNAL'}
|
||||
node_index: StringProperty(name='Node Index', default='')
|
||||
count: IntProperty(name='Number of inputs to remove', default=1, min=1)
|
||||
min_inputs: IntProperty(name='Number of inputs to keep', default=0, min=0)
|
||||
|
||||
def execute(self, context):
|
||||
global array_nodes
|
||||
node = array_nodes[self.node_index]
|
||||
inps = node.inputs
|
||||
min_inps = 0 if not hasattr(node, 'min_inputs') else node.min_inputs
|
||||
if len(inps) > min_inps:
|
||||
inps.remove(inps.values()[-1])
|
||||
min_inps = self.min_inputs if not hasattr(node, 'min_inputs') else node.min_inputs
|
||||
if len(inps) >= min_inps + self.count:
|
||||
for _ in range(self.count):
|
||||
inps.remove(inps.values()[-1])
|
||||
return{'FINISHED'}
|
||||
|
||||
class ArmNodeRemoveInputValueButton(bpy.types.Operator):
|
||||
|
@ -254,13 +275,14 @@ class ArmNodeRemoveInputValueButton(bpy.types.Operator):
|
|||
bl_label = 'Remove Input'
|
||||
bl_options = {'UNDO', 'INTERNAL'}
|
||||
node_index: StringProperty(name='Node Index', default='')
|
||||
target_name: StringProperty(name='Name of socket to remove', default='Value')
|
||||
|
||||
def execute(self, context):
|
||||
global array_nodes
|
||||
node = array_nodes[self.node_index]
|
||||
inps = node.inputs
|
||||
min_inps = 0 if not hasattr(node, 'min_inputs') else node.min_inputs
|
||||
if len(inps) > min_inps and inps[-1].name == 'Value':
|
||||
if len(inps) > min_inps and inps[-1].name == self.target_name:
|
||||
inps.remove(inps.values()[-1])
|
||||
return{'FINISHED'}
|
||||
|
||||
|
@ -278,7 +300,14 @@ class ArmNodeAddOutputButton(bpy.types.Operator):
|
|||
def execute(self, context):
|
||||
global array_nodes
|
||||
outs = array_nodes[self.node_index].outputs
|
||||
outs.new(self.socket_type, self.name_format.format(str(len(outs) + self.index_name_offset)))
|
||||
|
||||
socket_types = self.socket_type.split(';')
|
||||
name_formats = self.name_format.split(';')
|
||||
assert len(socket_types)==len(name_formats)
|
||||
|
||||
format_index = (len(outs) + self.index_name_offset) //len(socket_types)
|
||||
for socket_type, name_format in zip(socket_types, name_formats):
|
||||
outs.new(socket_type, name_format.format(str(format_index)))
|
||||
|
||||
# Reset to default again for subsequent calls of this operator
|
||||
self.node_index = ''
|
||||
|
@ -294,14 +323,16 @@ class ArmNodeRemoveOutputButton(bpy.types.Operator):
|
|||
bl_label = 'Remove Output'
|
||||
bl_options = {'UNDO', 'INTERNAL'}
|
||||
node_index: StringProperty(name='Node Index', default='')
|
||||
count: IntProperty(name='Number of outputs to remove', default=1, min=1)
|
||||
|
||||
def execute(self, context):
|
||||
global array_nodes
|
||||
node = array_nodes[self.node_index]
|
||||
outs = node.outputs
|
||||
min_outs = 0 if not hasattr(node, 'min_outputs') else node.min_outputs
|
||||
if len(outs) > min_outs:
|
||||
outs.remove(outs.values()[-1])
|
||||
if len(outs) >= min_outs + self.count:
|
||||
for _ in range(self.count):
|
||||
outs.remove(outs.values()[-1])
|
||||
return{'FINISHED'}
|
||||
|
||||
class ArmNodeAddInputOutputButton(bpy.types.Operator):
|
||||
|
@ -322,8 +353,21 @@ class ArmNodeAddInputOutputButton(bpy.types.Operator):
|
|||
node = array_nodes[self.node_index]
|
||||
inps = node.inputs
|
||||
outs = node.outputs
|
||||
inps.new(self.in_socket_type, self.in_name_format.format(str(len(inps) + self.in_index_name_offset)))
|
||||
outs.new(self.out_socket_type, self.out_name_format.format(str(len(outs))))
|
||||
|
||||
in_socket_types = self.in_socket_type.split(';')
|
||||
in_name_formats = self.in_name_format.split(';')
|
||||
assert len(in_socket_types)==len(in_name_formats)
|
||||
|
||||
out_socket_types = self.out_socket_type.split(';')
|
||||
out_name_formats = self.out_name_format.split(';')
|
||||
assert len(out_socket_types)==len(out_name_formats)
|
||||
|
||||
in_format_index = (len(outs) + self.index_name_offset) // len(in_socket_types)
|
||||
out_format_index = len(outs) // len(out_socket_types)
|
||||
for socket_type, name_format in zip(in_socket_types, in_name_formats):
|
||||
inps.new(socket_type, name_format.format(str(in_format_index)))
|
||||
for socket_type, name_format in zip(out_socket_types, out_name_formats):
|
||||
outs.new(socket_type, name_format.format(str(out_format_index)))
|
||||
|
||||
# Reset to default again for subsequent calls of this operator
|
||||
self.node_index = ''
|
||||
|
@ -341,7 +385,9 @@ class ArmNodeRemoveInputOutputButton(bpy.types.Operator):
|
|||
bl_label = 'Remove Input Output'
|
||||
bl_options = {'UNDO', 'INTERNAL'}
|
||||
node_index: StringProperty(name='Node Index', default='')
|
||||
|
||||
in_count: IntProperty(name='Number of inputs to remove', default=1, min=1)
|
||||
out_count: IntProperty(name='Number of inputs to remove', default=1, min=1)
|
||||
|
||||
def execute(self, context):
|
||||
global array_nodes
|
||||
node = array_nodes[self.node_index]
|
||||
|
@ -349,10 +395,12 @@ class ArmNodeRemoveInputOutputButton(bpy.types.Operator):
|
|||
outs = node.outputs
|
||||
min_inps = 0 if not hasattr(node, 'min_inputs') else node.min_inputs
|
||||
min_outs = 0 if not hasattr(node, 'min_outputs') else node.min_outputs
|
||||
if len(inps) > min_inps:
|
||||
inps.remove(inps.values()[-1])
|
||||
if len(outs) > min_outs:
|
||||
outs.remove(outs.values()[-1])
|
||||
if len(inps) >= min_inps + self.in_count:
|
||||
for _ in range(self.in_count):
|
||||
inps.remove(inps.values()[-1])
|
||||
if len(outs) >= min_outs + self.out_count:
|
||||
for _ in range(self.out_count):
|
||||
outs.remove(outs.values()[-1])
|
||||
return{'FINISHED'}
|
||||
|
||||
|
||||
|
@ -547,7 +595,10 @@ def deprecated(*alternatives: str, message=""):
|
|||
|
||||
def wrapper(cls: ArmLogicTreeNode) -> ArmLogicTreeNode:
|
||||
cls.bl_label += ' (Deprecated)'
|
||||
cls.bl_description = f'Deprecated. {cls.bl_description}'
|
||||
if hasattr(cls, 'bl_description'):
|
||||
cls.bl_description = f'Deprecated. {cls.bl_description}'
|
||||
else:
|
||||
cls.bl_description = 'Deprecated.'
|
||||
cls.bl_icon = 'ERROR'
|
||||
cls.arm_is_obsolete = True
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
from math import pi, cos, sin, sqrt
|
||||
import bpy
|
||||
from bpy.props import *
|
||||
from bpy.types import NodeSocket
|
||||
import mathutils
|
||||
|
||||
import arm.utils
|
||||
|
||||
|
@ -77,6 +79,190 @@ class ArmAnimActionSocket(ArmCustomSocket):
|
|||
def draw_color(self, context, node):
|
||||
return 0.8, 0.8, 0.8, 1
|
||||
|
||||
|
||||
class ArmRotationSocket(ArmCustomSocket):
|
||||
bl_idname = 'ArmRotationSocket'
|
||||
bl_label = 'Rotation Socket'
|
||||
arm_socket_type = 'ROTATION' # the internal representation is a quaternion, AKA a '4D vector' (using mathutils.Vector((x,y,z,w)))
|
||||
|
||||
def get_default_value(self):
|
||||
if self.default_value_raw is None:
|
||||
return mathutils.Vector((0.0,0.0,0.0,1.0))
|
||||
else:
|
||||
return self.default_value_raw
|
||||
|
||||
def on_unit_update(self, context):
|
||||
if self.default_value_unit == 'Rad':
|
||||
fac = pi/180 # deg->rad conversion
|
||||
else:
|
||||
fac = 180/pi # rad->deg conversion
|
||||
if self.default_value_mode == 'AxisAngle':
|
||||
self.default_value_s3 *= fac
|
||||
elif self.default_value_mode == 'EulerAngles':
|
||||
self.default_value_s0 *= fac
|
||||
self.default_value_s1 *= fac
|
||||
self.default_value_s2 *= fac
|
||||
self.do_update_raw(context)
|
||||
|
||||
|
||||
def on_mode_update(self, context):
|
||||
if self.default_value_mode == 'Quaternion':
|
||||
summ = abs(self.default_value_s0)
|
||||
summ+= abs(self.default_value_s1)
|
||||
summ+= abs(self.default_value_s2)
|
||||
summ+= abs(self.default_value_s3)
|
||||
if summ<0.01:
|
||||
self.default_value_s3 = 1.0
|
||||
elif self.default_value_mode == 'AxisAngle':
|
||||
summ = abs(self.default_value_s0)
|
||||
summ+= abs(self.default_value_s1)
|
||||
summ+= abs(self.default_value_s2)
|
||||
if summ<1E-5:
|
||||
self.default_value_s3 = 0.0
|
||||
self.do_update_raw(context)
|
||||
|
||||
@staticmethod
|
||||
def convert_to_quaternion(part1,part2,param1,param2,param3):
|
||||
"""converts a representation of rotation into a quaternion.
|
||||
``part1`` is a vector, ``part2`` is a scalar or None,
|
||||
``param1`` is in ('Quaternion', 'EulerAngles', 'AxisAngle'),
|
||||
``param2`` is in ('Rad','Deg') for both EulerAngles and AxisAngle,
|
||||
``param3`` is a len-3 string like "XYZ", for EulerAngles """
|
||||
if param1=='Quaternion':
|
||||
qx, qy, qz = part1[0], part1[1], part1[2]
|
||||
qw = part2
|
||||
# need to normalize the quaternion for a rotation (having it be 0 is not an option)
|
||||
ql = sqrt(qx**2+qy**2+qz**2+qw**2)
|
||||
if abs(ql)<1E-5:
|
||||
qx, qy, qz, qw = 0.0,0.0,0.0,1.0
|
||||
else:
|
||||
qx /= ql
|
||||
qy /= ql
|
||||
qz /= ql
|
||||
qw /= ql
|
||||
return mathutils.Vector((qx,qy,qz,qw))
|
||||
|
||||
elif param1 == 'AxisAngle':
|
||||
if param2 == 'Deg':
|
||||
angle = part2 * pi/180
|
||||
else:
|
||||
angle = part2
|
||||
cang, sang = cos(angle/2), sin(angle/2)
|
||||
x,y,z = part1[0], part1[1], part1[2]
|
||||
veclen = sqrt(x**2+y**2+z**2)
|
||||
if veclen<1E-5:
|
||||
return mathutils.Vector((0.0,0.0,0.0,1.0))
|
||||
else:
|
||||
return mathutils.Vector((
|
||||
x/veclen * sang,
|
||||
y/veclen * sang,
|
||||
z/veclen * sang,
|
||||
cang
|
||||
))
|
||||
else: # param1 == 'EulerAngles'
|
||||
x,y,z = part1[0], part1[1], part1[2]
|
||||
if param2 == 'Deg':
|
||||
x *= pi/180
|
||||
y *= pi/180
|
||||
z *= pi/180
|
||||
cx, sx = cos(x/2), sin(x/2)
|
||||
cy, sy = cos(y/2), sin(y/2)
|
||||
cz, sz = cos(z/2), sin(z/2)
|
||||
|
||||
qw, qx, qy, qz = 1.0,0.0,0.0,0.0
|
||||
for direction in param3[::-1]:
|
||||
qwi, qxi,qyi,qzi = {'X': (cx,sx,0,0), 'Y': (cy,0,sy,0), 'Z': (cz,0,0,sz)}[direction]
|
||||
|
||||
qw = qw*qwi -qx*qxi -qy*qyi -qz*qzi
|
||||
qx = qx*qwi +qw*qxi +qy*qzi -qz*qyi
|
||||
qy = qy*qwi +qw*qyi +qz*qxi -qx*qzi
|
||||
qz = qz*qwi +qw*qzi +qx*qyi -qy*qxi
|
||||
return mathutils.Vector((qx,qy,qz,qw))
|
||||
|
||||
|
||||
def do_update_raw(self, context):
|
||||
part1 = mathutils.Vector((
|
||||
self.default_value_s0,
|
||||
self.default_value_s1,
|
||||
self.default_value_s2, 1
|
||||
))
|
||||
part2 = self.default_value_s3
|
||||
|
||||
self.default_value_raw = self.convert_to_quaternion(
|
||||
part1,
|
||||
self.default_value_s3,
|
||||
self.default_value_mode,
|
||||
self.default_value_unit,
|
||||
self.default_value_order
|
||||
)
|
||||
|
||||
|
||||
def draw(self, context, layout, node, text):
|
||||
if (self.is_output or self.is_linked):
|
||||
layout.label(text=self.name)
|
||||
else:
|
||||
coll1 = layout.column(align=True)
|
||||
coll1.label(text=self.name)
|
||||
bx=coll1.box()
|
||||
coll = bx.column(align=True)
|
||||
coll.prop(self, 'default_value_mode')
|
||||
if self.default_value_mode in ('EulerAngles', 'AxisAngle'):
|
||||
coll.prop(self, 'default_value_unit')
|
||||
|
||||
if self.default_value_mode == 'EulerAngles':
|
||||
coll.prop(self, 'default_value_order')
|
||||
coll.prop(self, 'default_value_s0', text='X')
|
||||
coll.prop(self, 'default_value_s1', text='Y')
|
||||
coll.prop(self, 'default_value_s2', text='Z')
|
||||
elif self.default_value_mode == 'Quaternion':
|
||||
coll.prop(self, 'default_value_s0', text='X')
|
||||
coll.prop(self, 'default_value_s1', text='Y')
|
||||
coll.prop(self, 'default_value_s2', text='Z')
|
||||
coll.prop(self, 'default_value_s3', text='W')
|
||||
elif self.default_value_mode == 'AxisAngle':
|
||||
coll.prop(self, 'default_value_s0', text='X')
|
||||
coll.prop(self, 'default_value_s1', text='Y')
|
||||
coll.prop(self, 'default_value_s2', text='Z')
|
||||
coll.separator()
|
||||
coll.prop(self, 'default_value_s3', text='Angle')
|
||||
|
||||
def draw_color(self, context, node):
|
||||
return 0.68, 0.22, 0.62, 1
|
||||
|
||||
default_value_mode: EnumProperty(
|
||||
items=[('EulerAngles', 'Euler Angles', 'Euler Angles'),
|
||||
('AxisAngle', 'Axis/Angle', 'Axis/Angle'),
|
||||
('Quaternion', 'Quaternion', 'Quaternion')],
|
||||
name='', default='EulerAngles',
|
||||
update=on_mode_update)
|
||||
|
||||
default_value_unit: EnumProperty(
|
||||
items=[('Deg', 'Degrees', 'Degrees'),
|
||||
('Rad', 'Radians', 'Radians')],
|
||||
name='', default='Rad',
|
||||
update=on_unit_update)
|
||||
default_value_order: EnumProperty(
|
||||
items=[('XYZ','XYZ','XYZ'),
|
||||
('XZY','XZY (legacy Armory euler order)','XZY (legacy Armory euler order)'),
|
||||
('YXZ','YXZ','YXZ'),
|
||||
('YZX','YZX','YZX'),
|
||||
('ZXY','ZXY','ZXY'),
|
||||
('ZYX','ZYX','ZYX')],
|
||||
name='', default='XYZ'
|
||||
)
|
||||
|
||||
default_value_s0: FloatProperty(update=do_update_raw)
|
||||
default_value_s1: FloatProperty(update=do_update_raw)
|
||||
default_value_s2: FloatProperty(update=do_update_raw)
|
||||
default_value_s3: FloatProperty(update=do_update_raw)
|
||||
|
||||
default_value_raw: FloatVectorProperty(
|
||||
name='Value',
|
||||
description='Raw quaternion obtained for the default value of a ArmRotationSocket socket',
|
||||
size=4, default=(0,0,0,1),
|
||||
update = _on_update_socket
|
||||
)
|
||||
|
||||
|
||||
class ArmArraySocket(ArmCustomSocket):
|
||||
bl_idname = 'ArmNodeSocketArray'
|
||||
|
@ -226,6 +412,7 @@ class ArmObjectSocket(ArmCustomSocket):
|
|||
return 0.15, 0.55, 0.75, 1
|
||||
|
||||
|
||||
|
||||
class ArmStringSocket(ArmCustomSocket):
|
||||
bl_idname = 'ArmStringSocket'
|
||||
bl_label = 'String Socket'
|
||||
|
@ -292,10 +479,10 @@ def draw_socket_layout_split(socket: bpy.types.NodeSocket, layout: bpy.types.UIL
|
|||
if not socket.is_output and not socket.is_linked:
|
||||
layout.prop(socket, prop_name, text='')
|
||||
|
||||
|
||||
REG_CLASSES = (
|
||||
ArmActionSocket,
|
||||
ArmAnimActionSocket,
|
||||
ArmRotationSocket,
|
||||
ArmArraySocket,
|
||||
ArmBoolSocket,
|
||||
ArmColorSocket,
|
||||
|
|
75
blender/arm/logicnode/deprecated/LN_quaternion.py
Normal file
75
blender/arm/logicnode/deprecated/LN_quaternion.py
Normal file
|
@ -0,0 +1,75 @@
|
|||
from arm.logicnode.arm_nodes import *
|
||||
from mathutils import Vector
|
||||
|
||||
@deprecated(message='Do not use quaternion sockets')
|
||||
class QuaternionNode(ArmLogicTreeNode):
|
||||
"""TO DO."""
|
||||
bl_idname = 'LNQuaternionNode'
|
||||
bl_label = 'Quaternion'
|
||||
bl_description = 'Create a quaternion variable (transported through a vector socket)'
|
||||
arm_section = 'quaternions'
|
||||
arm_version = 2 # deprecate
|
||||
|
||||
def arm_init(self, context):
|
||||
self.add_input('ArmFloatSocket', 'X')
|
||||
self.add_input('ArmFloatSocket', 'Y')
|
||||
self.add_input('ArmFloatSocket', 'Z')
|
||||
self.add_input('ArmFloatSocket', 'W', default_value=1.0)
|
||||
|
||||
self.add_output('ArmVectorSocket', 'Quaternion')
|
||||
self.add_output('ArmVectorSocket', 'XYZ')
|
||||
self.add_output('ArmVectorSocket', 'W')
|
||||
|
||||
|
||||
def get_replacement_node(self, node_tree: bpy.types.NodeTree):
|
||||
if self.arm_version not in (0, 1):
|
||||
raise LookupError()
|
||||
|
||||
# transition from version 1 to version 2[deprecated]
|
||||
|
||||
|
||||
newnodes = []
|
||||
|
||||
rawlinks = self.outputs[0].links
|
||||
xyzlinks = self.outputs[1].links
|
||||
wlinks = self.outputs[2].links
|
||||
if len(rawlinks)>0 or len(xyzlinks)>0:
|
||||
xyzcomb = node_tree.nodes.new('LNVectorNode')
|
||||
newnodes.append(xyzcomb)
|
||||
|
||||
xyzcomb.inputs[0].default_value = self.inputs[0].default_value
|
||||
xyzcomb.inputs[1].default_value = self.inputs[1].default_value
|
||||
xyzcomb.inputs[2].default_value = self.inputs[2].default_value
|
||||
for link in self.inputs[0].links:
|
||||
node_tree.links.new(link.from_socket, xyzcomb.inputs[0])
|
||||
for link in self.inputs[1].links:
|
||||
node_tree.links.new(link.from_socket, xyzcomb.inputs[1])
|
||||
for link in self.inputs[2].links:
|
||||
node_tree.links.new(link.from_socket, xyzcomb.inputs[2])
|
||||
|
||||
for link in xyzlinks:
|
||||
node_tree.links.new(xyzcomb.outputs[0], link.to_socket)
|
||||
if len(rawlinks)>0:
|
||||
rotnode = node_tree.nodes.new('LNRotationNode')
|
||||
newnodes.append(rotnode)
|
||||
rotnode.property0 = 'Quaternion'
|
||||
rotnode.inputs[0].default_value = Vector(
|
||||
(self.inputs[0].default_value,
|
||||
self.inputs[1].default_value,
|
||||
self.inputs[2].default_value))
|
||||
rotnode.inputs[1].default_value = self.inputs[3].default_value
|
||||
node_tree.links.new(xyzcomb.outputs[0], rotnode.inputs[0])
|
||||
for link in self.inputs[3].links: # 0 or 1
|
||||
node_tree.links.new(link.from_socket, rotnode.inputs[1])
|
||||
for link in rawlinks:
|
||||
node_tree.links.new(rotnode.outputs[0], link.to_socket)
|
||||
|
||||
if len(self.inputs[3].links)>0:
|
||||
fromval = self.inputs[3].links[0].from_socket
|
||||
for link in self.outputs[2].links:
|
||||
node_tree.links.new(fromval, link.to_socket)
|
||||
else:
|
||||
for link in self.outputs[2].links:
|
||||
link.to_socket.default_value = self.inputs[3].default_value
|
||||
|
||||
return newnodes
|
45
blender/arm/logicnode/deprecated/LN_separate_quaternion.py
Normal file
45
blender/arm/logicnode/deprecated/LN_separate_quaternion.py
Normal file
|
@ -0,0 +1,45 @@
|
|||
from arm.logicnode.arm_nodes import *
|
||||
|
||||
@deprecated(message='Do not use quaternion sockets')
|
||||
class SeparateQuaternionNode(ArmLogicTreeNode):
|
||||
"""Splits the given quaternion into X, Y, Z and W."""
|
||||
bl_idname = 'LNSeparateQuaternionNode'
|
||||
bl_label = "Separate Quaternion (do not use: quaternions sockets have been phased out entirely)"
|
||||
bl_description = "Separate a quaternion object (transported through a vector socket) into its four compoents."
|
||||
arm_section = 'quaternions'
|
||||
arm_version = 2 # deprecate
|
||||
|
||||
|
||||
def arm_init(self, context):
|
||||
self.add_input('ArmVectorSocket', 'Quaternion')
|
||||
|
||||
self.add_output('ArmFloatSocket', 'X')
|
||||
self.add_output('ArmFloatSocket', 'Y')
|
||||
self.add_output('ArmFloatSocket', 'Z')
|
||||
self.add_output('ArmFloatSocket', 'W')
|
||||
|
||||
|
||||
def get_replacement_node(self, node_tree: bpy.types.NodeTree):
|
||||
if self.arm_version not in (0, 1):
|
||||
raise LookupError()
|
||||
|
||||
# transition from version 1 to version 2[deprecated]
|
||||
newself = node_tree.nodes.new('LNSeparateRotationNode')
|
||||
separator = node_tree.nodes.new('LNSeparateVectorNode')
|
||||
|
||||
newself.property0 = 'Quaternion'
|
||||
newself.property1 = 'Rad' # bogus
|
||||
newself.property2 = 'XYZ' # bogus
|
||||
|
||||
for link in self.inputs[0].links:
|
||||
node_tree.links.new(link.from_socket, newself.inputs[0])
|
||||
node_tree.links.new(newself.outputs[0], separator.inputs[0])
|
||||
for link in self.outputs[0].links:
|
||||
node_tree.links.new(separator.outputs[0], link.to_socket)
|
||||
for link in self.outputs[1].links:
|
||||
node_tree.links.new(separator.outputs[1], link.to_socket)
|
||||
for link in self.outputs[2].links:
|
||||
node_tree.links.new(separator.outputs[2], link.to_socket)
|
||||
for link in self.outputs[3].links:
|
||||
node_tree.links.new(newself.outputs[1], link.to_socket)
|
||||
return [newself, separator]
|
|
@ -1,47 +1,52 @@
|
|||
from arm.logicnode.arm_nodes import *
|
||||
from arm.logicnode.arm_sockets import ArmRotationSocket as Rotation
|
||||
|
||||
class QuaternionMathNode(ArmLogicTreeNode):
|
||||
"""Mathematical operations on quaternions."""
|
||||
bl_idname = 'LNQuaternionMathNode'
|
||||
bl_label = 'Quaternion Math'
|
||||
bl_description = 'Mathematical operations that can be performed on rotations, when represented as quaternions specifically'
|
||||
arm_section = 'quaternions'
|
||||
arm_version = 1
|
||||
arm_version = 2
|
||||
|
||||
def get_bool(self):
|
||||
return self.get('property1', False)
|
||||
|
||||
def set_bool(self, value):
|
||||
self['property1'] = value
|
||||
if value:
|
||||
if ((self.property0 == 'Module') or (self.property0 == 'DotProduct') or (self.property0 == 'ToAxisAngle')) and (len(self.outputs) > 1):
|
||||
self.outputs.remove(self.outputs.values()[-1]) # Module/DotProduct/ToAxisAngle
|
||||
self.add_output('ArmFloatSocket', 'X') # Result X
|
||||
self.add_output('ArmFloatSocket', 'Y') # Result Y
|
||||
self.add_output('ArmFloatSocket', 'Z') # Result Z
|
||||
self.add_output('ArmFloatSocket', 'W') # Result W
|
||||
if (self.property0 == 'Module'):
|
||||
self.add_output('ArmFloatSocket', 'Module') # Module
|
||||
if (self.property0 == 'DotProduct'):
|
||||
self.add_output('ArmFloatSocket', 'Scalar') # DotProduct
|
||||
if (self.property0 == 'ToAxisAngle'):
|
||||
self.add_output('ArmFloatSocket', 'To Axis Angle') # ToAxisAngle
|
||||
def ensure_input_socket(self, socket_number, newclass, newname, default_value=None):
|
||||
while len(self.inputs) < socket_number:
|
||||
self.inputs.new('ArmFloatSocket', 'BOGUS')
|
||||
if len(self.inputs) > socket_number:
|
||||
if len(self.inputs[socket_number].links) == 1:
|
||||
source_socket = self.inputs[socket_number].links[0].from_socket
|
||||
else:
|
||||
source_socket = None
|
||||
if (
|
||||
self.inputs[socket_number].bl_idname == newclass \
|
||||
and self.inputs[socket_number].arm_socket_type != 'NONE'
|
||||
):
|
||||
default_value = self.inputs[socket_number].default_value_raw
|
||||
self.inputs.remove(self.inputs[socket_number])
|
||||
else:
|
||||
if ((self.property0 == 'Module') or (self.property0 == 'DotProduct') or (self.property0 == 'ToAxisAngle')) and (len(self.outputs) > 1):
|
||||
self.outputs.remove(self.outputs.values()[-1]) # Module/DotProduct/ToAxisAngle
|
||||
# Remove X, Y, Z, W
|
||||
for i in range(4):
|
||||
if len(self.outputs) > 1:
|
||||
self.outputs.remove(self.outputs.values()[-1])
|
||||
else:
|
||||
break
|
||||
if (self.property0 == 'Module'):
|
||||
self.add_output('ArmFloatSocket', 'Module') # Module
|
||||
if (self.property0 == 'DotProduct'):
|
||||
self.add_output('ArmFloatSocket', 'Scalar') # DotProduct
|
||||
if (self.property0 == 'ToAxisAngle'):
|
||||
self.add_output('ArmFloatSocket', 'To Axis Angle') # ToAxisAngle
|
||||
source_socket = None
|
||||
|
||||
|
||||
self.inputs.new(newclass, newname)
|
||||
if default_value != None:
|
||||
self.inputs[-1].default_value_raw = default_value
|
||||
self.inputs.move(len(self.inputs)-1, socket_number)
|
||||
if source_socket is not None:
|
||||
self.id_data.links.new(source_socket, self.inputs[socket_number])
|
||||
|
||||
def ensure_output_socket(self, socket_number, newclass, newname):
|
||||
sink_sockets = []
|
||||
while len(self.outputs) < socket_number:
|
||||
self.outputs.new('ArmFloatSocket', 'BOGUS')
|
||||
if len(self.outputs) > socket_number:
|
||||
for link in self.inputs[socket_number].links:
|
||||
sink_sockets.append(link.to_socket)
|
||||
self.inputs.remove(self.inputs[socket_number])
|
||||
|
||||
property1: HaxeBoolProperty('property1', name='Separator Out', default=False, set=set_bool, get=get_bool)
|
||||
self.inputs.new(newclass, newname)
|
||||
self.inputs.move(len(self.inputs)-1, socket_number)
|
||||
for socket in sink_sockets:
|
||||
self.id_data.links.new(self.inputs[socket_number], socket)
|
||||
|
||||
@staticmethod
|
||||
def get_enum_id_value(obj, prop_name, value):
|
||||
|
@ -75,63 +80,43 @@ class QuaternionMathNode(ArmLogicTreeNode):
|
|||
# Checking the selection of another operation
|
||||
select_current = self.get_enum_id_value(self, 'property0', value)
|
||||
select_prev = self.property0
|
||||
if select_prev != select_current:
|
||||
# Remove
|
||||
count = 0
|
||||
if ((select_prev == 'Add') or (select_prev == 'Subtract') or (select_prev == 'Multiply') or (select_prev == 'DotProduct')) and ((select_current == 'Add') or (select_current == 'Subtract') or (select_current == 'Multiply') or (select_current == 'DotProduct')) or (((select_current == 'Lerp') or (select_current == 'Slerp')) and ((select_prev == 'Lerp') or (select_prev == 'Slerp'))):
|
||||
count = len(self.inputs)
|
||||
while (len(self.inputs) > count):
|
||||
self.inputs.remove(self.inputs.values()[-1])
|
||||
if (select_prev == 'DotProduct') or (select_prev == 'ToAxisAngle') or (select_prev == 'Module'):
|
||||
self.outputs.remove(self.outputs.values()[-1])
|
||||
|
||||
# Many arguments: Add, Subtract, DotProduct, Multiply, MultiplyFloat
|
||||
if (self.get_count_in(select_current) == 0):
|
||||
if (select_current == "MultiplyFloats"):
|
||||
self.add_input('ArmVectorSocket', 'Quaternion ' + str(len(self.inputs)))
|
||||
self.add_input('ArmFloatSocket', 'Value ' + str(len(self.inputs)))
|
||||
else:
|
||||
while (len(self.inputs) < 2):
|
||||
self.add_input('ArmVectorSocket', 'Quaternion ' + str(len(self.inputs)))
|
||||
if (select_current == 'DotProduct'):
|
||||
self.add_output('ArmFloatSocket', 'Scalar')
|
||||
if select_current in ('Add','Subtract','Multiply','DotProduct') \
|
||||
and select_prev in ('Add','Subtract','Multiply','DotProduct'):
|
||||
pass # same as select_current==select_prev for the sockets
|
||||
elif select_prev != select_current:
|
||||
if select_current in ('Add','Subtract','Multiply','DotProduct'):
|
||||
for i in range( max(len(self.inputs)//2 ,2) ):
|
||||
self.ensure_input_socket(2*i, 'ArmVectorSocket', 'Quaternion %d XYZ'%i)
|
||||
self.ensure_input_socket(2*i+1, 'ArmFloatSocket', 'Quaternion %d W'%i, default_value=1.0)
|
||||
if len(self.inputs)%1:
|
||||
self.inputs.remove(self.inputs[len(self.inputs)-1])
|
||||
elif select_current == 'MultiplyFloats':
|
||||
self.ensure_input_socket(0, 'ArmVectorSocket', 'Quaternion XYZ')
|
||||
self.ensure_input_socket(1, 'ArmFloatSocket', 'Quaternion W', default_value=1.0)
|
||||
for i in range( max(len(self.inputs)-2 ,1) ):
|
||||
self.ensure_input_socket(i+2, 'ArmFloatSocket', 'Value %d'%i)
|
||||
elif select_current in ('Module', 'Normalize'):
|
||||
self.ensure_input_socket(0, 'ArmVectorSocket', 'Quaternion XYZ')
|
||||
self.ensure_input_socket(1, 'ArmFloatSocket', 'Quaternion W', default_value=1.0)
|
||||
while len(self.inputs)>2:
|
||||
self.inputs.remove(self.inputs[2])
|
||||
else:
|
||||
raise ValueError('Internal code of LNQuaternionMathNode failed to deal correctly with math operation "%s". Please report this to the developers.' %select_current)
|
||||
|
||||
# 3 arguments: Lerp, Slerp, FromAxisAngle, FromEuler
|
||||
if (self.get_count_in(select_current) == 3):
|
||||
if (select_current == 'Lerp') or (select_current == 'Slerp'):
|
||||
while (len(self.inputs) < 3):
|
||||
self.add_input('ArmVectorSocket', 'From')
|
||||
self.add_input('ArmVectorSocket', 'To')
|
||||
self.add_input('ArmFloatSocket', 'T')
|
||||
if (select_current == 'FromAxisAngle'):
|
||||
self.add_input('ArmVectorSocket', 'Quaternion')
|
||||
self.add_input('ArmVectorSocket', 'Axis')
|
||||
self.add_input('ArmFloatSocket', 'Angle')
|
||||
if (select_current == 'FromEuler'):
|
||||
self.add_input('ArmFloatSocket', 'X')
|
||||
self.add_input('ArmFloatSocket', 'Y')
|
||||
self.add_input('ArmFloatSocket', 'Z')
|
||||
if select_current in ('Add','Subtract','Multiply','MultiplyFloats','Normalize'):
|
||||
self.outputs[0].name = 'XYZ Out'
|
||||
self.outputs[1].name = 'W Out'
|
||||
else:
|
||||
self.outputs[0].name = '[unused]'
|
||||
self.outputs[1].name = 'Value Out'
|
||||
|
||||
# 2 arguments: FromTo, FromMat, FromRotationMat, ToAxisAngle
|
||||
if (self.get_count_in(select_current) == 2):
|
||||
if (select_current == 'FromTo'):
|
||||
self.add_input('ArmVectorSocket', 'Vector ' + str(len(self.inputs)))
|
||||
self.add_input('ArmVectorSocket', 'Vector ' + str(len(self.inputs)))
|
||||
if (select_current == 'FromMat') or (select_current == 'FromRotationMat'):
|
||||
self.add_input('ArmVectorSocket', 'Quaternion')
|
||||
self.add_input('ArmDynamicSocket', 'Matrix')
|
||||
if (select_current == 'ToAxisAngle'):
|
||||
self.add_input('ArmVectorSocket', 'Quaternion')
|
||||
self.add_input('ArmVectorSocket', 'Axis')
|
||||
self.add_output('ArmFloatSocket', 'Angle')
|
||||
|
||||
# 1 argument: Module, Normalize, GetEuler
|
||||
if (self.get_count_in(select_current) == 1):
|
||||
self.add_input('ArmVectorSocket', 'Quaternion')
|
||||
if (select_current == 'Module'):
|
||||
self.add_output('ArmFloatSocket', 'Module')
|
||||
self['property0'] = value
|
||||
self['property0_proxy'] = value
|
||||
|
||||
|
||||
# this property swaperoo is kinda janky-looking, but necessary.
|
||||
# Read more on LN_rotate_object.py
|
||||
property0: HaxeEnumProperty(
|
||||
'property0',
|
||||
items = [('Add', 'Add', 'Add'),
|
||||
|
@ -140,29 +125,44 @@ class QuaternionMathNode(ArmLogicTreeNode):
|
|||
('Multiply', 'Multiply', 'Multiply'),
|
||||
('MultiplyFloats', 'Multiply (Floats)', 'Multiply (Floats)'),
|
||||
('Module', 'Module', 'Module'),
|
||||
('Normalize', 'Normalize', 'Normalize'),
|
||||
('Lerp', 'Lerp', 'Linearly interpolate'),
|
||||
('Slerp', 'Slerp', 'Spherical linear interpolation'),
|
||||
('FromTo', 'From To', 'From To'),
|
||||
('FromMat', 'From Mat', 'From Mat'),
|
||||
('FromRotationMat', 'From Rotation Mat', 'From Rotation Mat'),
|
||||
('ToAxisAngle', 'To Axis Angle', 'To Axis Angle'),
|
||||
('FromAxisAngle', 'From Axis Angle', 'From Axis Angle'),
|
||||
('FromEuler', 'From Euler', 'From Euler'),
|
||||
('GetEuler', 'To Euler', 'To Euler')],
|
||||
('Normalize', 'Normalize', 'Normalize'), #],
|
||||
# NOTE: the unused parts need to exist to be read from an old version from the node.
|
||||
# this is so dumb…
|
||||
('Lerp', 'DO NOT USE',''),
|
||||
('Slerp', 'DO NOT USE',''),
|
||||
('FromTo', 'DO NOT USE',''),
|
||||
('FromMat', 'DO NOT USE',''),
|
||||
('FromRotationMat', 'DO NOT USE',''),
|
||||
('ToAxisAngle', 'DO NOT USE',''),
|
||||
('FromAxisAngle', 'DO NOT USE',''),
|
||||
('FromEuler', 'DO NOT USE',''),
|
||||
('GetEuler', 'DO NOT USE','')],
|
||||
name='', default='Add') #, set=set_enum, get=get_enum)
|
||||
property0_proxy: EnumProperty(
|
||||
items = [('Add', 'Add', 'Add'),
|
||||
('Subtract', 'Subtract', 'Subtract'),
|
||||
('DotProduct', 'Dot Product', 'Dot Product'),
|
||||
('Multiply', 'Multiply', 'Multiply'),
|
||||
('MultiplyFloats', 'Multiply (Floats)', 'Multiply (Floats)'),
|
||||
('Module', 'Module', 'Module'),
|
||||
('Normalize', 'Normalize', 'Normalize')],
|
||||
name='', default='Add', set=set_enum, get=get_enum)
|
||||
|
||||
|
||||
def __init__(self):
|
||||
super(QuaternionMathNode, self).__init__()
|
||||
array_nodes[str(id(self))] = self
|
||||
|
||||
def arm_init(self, context):
|
||||
self.add_input('ArmVectorSocket', 'Quaternion 0', default_value=[0.0, 0.0, 0.0])
|
||||
self.add_input('ArmVectorSocket', 'Quaternion 1', default_value=[0.0, 0.0, 0.0])
|
||||
self.add_output('ArmVectorSocket', 'Result')
|
||||
self.add_input('ArmVectorSocket', 'Quaternion 0 XYZ', default_value=[0.0, 0.0, 0.0])
|
||||
self.add_input('ArmFloatSocket', 'Quaternion 0 W', default_value=1)
|
||||
self.add_input('ArmVectorSocket', 'Quaternion 1 XYZ', default_value=[0.0, 0.0, 0.0])
|
||||
self.add_input('ArmFloatSocket', 'Quaternion 1 W', default_value=1)
|
||||
self.add_output('ArmVectorSocket', 'Result XYZ', default_value=[0.0, 0.0, 0.0])
|
||||
self.add_output('ArmFloatSocket', 'Result W', default_value=1)
|
||||
|
||||
def draw_buttons(self, context, layout):
|
||||
layout.prop(self, 'property1') # Separator Out
|
||||
layout.prop(self, 'property0') # Operation
|
||||
layout.prop(self, 'property0_proxy') # Operation
|
||||
# Buttons
|
||||
if (self.get_count_in(self.property0) == 0):
|
||||
row = layout.row(align=True)
|
||||
|
@ -170,15 +170,210 @@ class QuaternionMathNode(ArmLogicTreeNode):
|
|||
op = column.operator('arm.node_add_input', text='Add Value', icon='PLUS', emboss=True)
|
||||
op.node_index = str(id(self))
|
||||
if (self.property0 == 'Add') or (self.property0 == 'Subtract') or (self.property0 == 'Multiply') or (self.property0 == 'DotProduct'):
|
||||
op.name_format = 'Quaternion {0}'
|
||||
op.name_format = 'Quaternion {0} XYZ;Quaternion {0} W'
|
||||
else:
|
||||
op.name_format = 'Value {0}'
|
||||
if (self.property0 == "MultiplyFloats"):
|
||||
op.socket_type = 'ArmFloatSocket'
|
||||
else:
|
||||
op.socket_type = 'ArmVectorSocket'
|
||||
op.socket_type = 'ArmVectorSocket;ArmFloatSocket'
|
||||
|
||||
column = row.column(align=True)
|
||||
op = column.operator('arm.node_remove_input', text='', icon='X', emboss=True)
|
||||
op.node_index = str(id(self))
|
||||
if self.property0 != "MultiplyFloats":
|
||||
op.count = 2
|
||||
op.min_inputs = 4
|
||||
else:
|
||||
op.min_inputs = 2
|
||||
if len(self.inputs) == 2:
|
||||
column.enabled = False
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def get_replacement_node(self, node_tree: bpy.types.NodeTree):
|
||||
if self.arm_version not in (0, 1):
|
||||
raise LookupError()
|
||||
|
||||
|
||||
ret=[]
|
||||
if self.property0 == 'GetEuler':
|
||||
newself = node_tree.nodes.new('LNSeparateRotationNode')
|
||||
ret.append(newself)
|
||||
newself.property0='EulerAngles'
|
||||
newself.property2='XZY'
|
||||
newself.property1='Rad'
|
||||
|
||||
for link in self.inputs[0].links: # 0 or 1
|
||||
node_tree.links.new(link.from_socket, newself.inputs[0])
|
||||
elif self.property0 == 'FromEuler':
|
||||
newself = node_tree.nodes.new('LNRotationNode')
|
||||
ret.append(newself)
|
||||
preconv = node_tree.nodes.new('LNVectorNode')
|
||||
ret.append(preconv)
|
||||
newself.property0='EulerAngles'
|
||||
newself.property2='XZY'
|
||||
newself.property1='Rad'
|
||||
node_tree.links.new(preconv.outputs[0], newself.inputs[0])
|
||||
|
||||
preconv.inputs[0].default_value = self.inputs[0].default_value
|
||||
for link in self.inputs[0].links: # 0 or 1
|
||||
node_tree.links.new(link.from_socket, preconv.inputs[0])
|
||||
preconv.inputs[1].default_value = self.inputs[1].default_value
|
||||
for link in self.inputs[1].links: # 0 or 1
|
||||
node_tree.links.new(link.from_socket, preconv.inputs[1])
|
||||
preconv.inputs[2].default_value = self.inputs[2].default_value
|
||||
for link in self.inputs[2].links: # 0 or 1
|
||||
node_tree.links.new(link.from_socket, preconv.inputs[2])
|
||||
elif self.property0 == 'ToAxisAngle':
|
||||
newself = node_tree.nodes.new('LNSeparateRotationNode')
|
||||
ret.append(newself)
|
||||
newself.property0='AxisAngle'
|
||||
newself.property1='Rad'
|
||||
|
||||
for link in self.inputs[0].links: # 0 or 1
|
||||
node_tree.links.new(link.from_socket, newself.inputs[0])
|
||||
elif self.property0 == 'FromAxisAngle':
|
||||
newself = node_tree.nodes.new('LNRotationNode')
|
||||
ret.append(newself)
|
||||
newself.property0='AxisAngle'
|
||||
newself.property1='Rad'
|
||||
|
||||
newself.inputs[0].default_value = self.inputs[1].default_value
|
||||
for link in self.inputs[1].links: # 0 or 1
|
||||
node_tree.links.new(link.from_socket, newself.inputs[0])
|
||||
newself.inputs[1].default_value = self.inputs[2].default_value
|
||||
for link in self.inputs[2].links: # 0 or 1
|
||||
node_tree.links.new(link.from_socket, newself.inputs[1])
|
||||
elif self.property0 in ('FromMat','FromRotationMat'):
|
||||
newself = node_tree.nodes.new('LNSeparateTransformNode')
|
||||
ret.append(newself)
|
||||
for link in self.inputs[1].links: # 0 or 1
|
||||
node_tree.links.new(link.from_socket, newself.inputs[0])
|
||||
|
||||
elif self.property0 in ('Lerp','Slerp','FromTo'):
|
||||
newself = node_tree.nodes.new('LNRotationMathNode')
|
||||
ret.append(newself)
|
||||
newself.property0 = self.property0
|
||||
|
||||
for in1, in2 in zip(self.inputs, newself.inputs):
|
||||
if in2.bl_idname == 'ArmRotationSocket':
|
||||
in2.default_value_raw = Rotation.convert_to_quaternion(
|
||||
in1.default_value,0,
|
||||
'EulerAngles','Rad','XZY'
|
||||
)
|
||||
elif in1.bl_idname in ('ArmFloatSocket', 'ArmVectorSocket'):
|
||||
in2.default_value = in1.default_value
|
||||
for link in in1.links:
|
||||
node_tree.links.new(link.from_socket, in2)
|
||||
|
||||
else:
|
||||
newself = node_tree.nodes.new('LNQuaternionMathNode')
|
||||
ret.append(newself)
|
||||
newself.property0 = self.property0
|
||||
|
||||
# convert the inputs… this is going to be hard lmao.
|
||||
i_in_1 = 0
|
||||
i_in_2 = 0
|
||||
while i_in_1 < len(self.inputs):
|
||||
in1 = self.inputs[i_in_1]
|
||||
if in1.bl_idname == 'ArmVectorSocket':
|
||||
# quaternion input: now two sockets, not one.
|
||||
convnode = node_tree.nodes.new('LNSeparateRotationNode')
|
||||
convnode.property0 = 'Quaternion'
|
||||
ret.append(convnode)
|
||||
if i_in_2 >= len(newself.inputs):
|
||||
newself.ensure_input_socket(i_in_2, 'ArmVectorSocket', 'Quaternion %d XYZ'%(i_in_1))
|
||||
newself.ensure_input_socket(i_in_2+1, 'ArmFloatSocket', 'Quaternion %d W'%(i_in_1), 1.0)
|
||||
node_tree.links.new(convnode.outputs[0], newself.inputs[i_in_2])
|
||||
node_tree.links.new(convnode.outputs[1], newself.inputs[i_in_2+1])
|
||||
for link in in1.links:
|
||||
node_tree.links.new(link.from_socket, convnode.inputs[0])
|
||||
i_in_2 +=2
|
||||
i_in_1 +=1
|
||||
elif in1.bl_idname == 'ArmFloatSocket':
|
||||
for link in in1.links:
|
||||
node_tree.links.new(link.from_socket, newself.inputs[i_in_2])
|
||||
i_in_1 +=1
|
||||
i_in_2 +=1
|
||||
else:
|
||||
raise ValueError('get_replacement_node() for is not LNQuaternionMathNode V1->V2 is not prepared to deal with an input socket of type %s. This is a bug to report to the developers' %in1.bl_idname)
|
||||
|
||||
# #### now that the input has been dealt with, let's deal with the output.
|
||||
|
||||
if self.property0 in ('FromEuler','FromMat','FromRotationMat','FromAxisAngle','Lerp','Slerp','FromTo'):
|
||||
# the new self returns a rotation
|
||||
for link in self.outputs[0].links:
|
||||
out_sock_i = int( self.property0.endswith('Mat') )
|
||||
node_tree.links.new(newself.outputs[out_sock_i], link.to_socket)
|
||||
elif self.property0 in ('DotProduct','Module'):
|
||||
# new self returns a float
|
||||
for link in self.outputs[1 + 4*int(self.property1)].links:
|
||||
node_tree.links.new(newself.outputs[1], link.to_socket)
|
||||
elif self.property0 in ('GetEuler', 'ToAxisAngle'):
|
||||
# new self returns misc.
|
||||
for link in self.outputs[0].links:
|
||||
node_tree.links.new(newself.outputs[0], link.to_socket)
|
||||
if self.property0 == 'ToAxisAngle':
|
||||
for link in self.outputs[1 + 4*int(self.property1)].links:
|
||||
node_tree.links.new(newself.outputs[1], link.to_socket)
|
||||
if self.property1:
|
||||
xlinks = self.outputs[1].links
|
||||
ylinks = self.outputs[2].links
|
||||
zlinks = self.outputs[3].links
|
||||
if len(xlinks)>0 or len(ylinks)>0 or len(zlinks)>0:
|
||||
conv = node_tree.nodes.new('LNSeparateVectorNode')
|
||||
ret.append(conv)
|
||||
node_tree.links.new(newself.outputs[0], conv.inputs[0])
|
||||
for link in xlinks:
|
||||
node_tree.links.new(conv.outputs[0], link.to_socket)
|
||||
for link in ylinks:
|
||||
node_tree.links.new(conv.outputs[1], link.to_socket)
|
||||
for link in zlinks:
|
||||
node_tree.links.new(conv.outputs[2], link.to_socket)
|
||||
else:
|
||||
# new self returns a proper quaternion XYZ/W
|
||||
outlinks = self.outputs[0].links
|
||||
if len(outlinks)>0:
|
||||
conv = node_tree.nodes.new('LNRotationNode')
|
||||
conv.property0='Quaternion'
|
||||
ret.append(conv)
|
||||
node_tree.links.new(newself.outputs[0], conv.inputs[0])
|
||||
node_tree.links.new(newself.outputs[1], conv.inputs[1])
|
||||
for link in outlinks:
|
||||
node_tree.links.new(conv.outputs[0], link.to_socket)
|
||||
if self.property1:
|
||||
for link in self.outputs[4].links: # for W
|
||||
node_tree.links.new(newself.outputs[1], link.to_socket)
|
||||
xlinks = self.outputs[1].links
|
||||
ylinks = self.outputs[2].links
|
||||
zlinks = self.outputs[3].links
|
||||
if len(xlinks)>0 or len(ylinks)>0 or len(zlinks)>0:
|
||||
conv = node_tree.nodes.new('LNSeparateVectorNode')
|
||||
ret.append(conv)
|
||||
node_tree.links.new(newself.outputs[0], conv.inputs[0])
|
||||
for link in xlinks:
|
||||
node_tree.links.new(conv.outputs[0], link.to_socket)
|
||||
for link in ylinks:
|
||||
node_tree.links.new(conv.outputs[1], link.to_socket)
|
||||
for link in zlinks:
|
||||
node_tree.links.new(conv.outputs[2], link.to_socket)
|
||||
|
||||
for node in ret: # update the labels on the node's displays
|
||||
if node.bl_idname == 'LNSeparateRotationNode':
|
||||
node.on_property_update(None)
|
||||
elif node.bl_idname == 'LNRotationNode':
|
||||
node.on_property_update(None)
|
||||
elif node.bl_idname == 'LNRotationMathNode':
|
||||
node.on_update_operation(None)
|
||||
elif node.bl_idname == 'LNQuaternionMathNode':
|
||||
node.set_enum(node.get_enum())
|
||||
return ret
|
||||
|
||||
|
||||
# note: keep property1, so that it is actually readable for node conversion.
|
||||
property1: BoolProperty(name='DEPRECATED', default=False)
|
||||
|
|
121
blender/arm/logicnode/math/LN_rotation_math.py
Normal file
121
blender/arm/logicnode/math/LN_rotation_math.py
Normal file
|
@ -0,0 +1,121 @@
|
|||
from arm.logicnode.arm_nodes import *
|
||||
from mathutils import Vector
|
||||
|
||||
class RotationMathNode(ArmLogicTreeNode):
|
||||
"""Mathematical operations on rotations."""
|
||||
bl_idname = 'LNRotationMathNode'
|
||||
bl_label = 'Rotation Math'
|
||||
bl_description = 'Mathematical operations that can be performed on rotations, no matter their internal representation'
|
||||
arm_section = 'quaternions'
|
||||
arm_version = 1
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_count_in(operation_name):
|
||||
return {
|
||||
'Inverse': 1,
|
||||
'Normalize': 1,
|
||||
'Compose': 2,
|
||||
'Amplify': 2,
|
||||
'FromTo': 2,
|
||||
#'FromRotationMat': 2,
|
||||
'Lerp': 3,
|
||||
'Slerp': 3,
|
||||
}.get(operation_name, 0)
|
||||
|
||||
def ensure_input_socket(self, socket_number, newclass, newname):
|
||||
while len(self.inputs) < socket_number:
|
||||
self.inputs.new('ArmFloatSocket', 'BOGUS')
|
||||
if len(self.inputs) > socket_number:
|
||||
if len(self.inputs[socket_number].links) == 1:
|
||||
source_socket = self.inputs[socket_number].links[0].from_socket
|
||||
else:
|
||||
source_socket = None
|
||||
self.inputs.remove(self.inputs[socket_number])
|
||||
else:
|
||||
source_socket = None
|
||||
|
||||
|
||||
self.inputs.new(newclass, newname)
|
||||
self.inputs.move(len(self.inputs)-1, socket_number)
|
||||
if source_socket is not None:
|
||||
self.id_data.links.new(source_socket, self.inputs[socket_number])
|
||||
|
||||
def ensure_output_socket(self, socket_number, newclass, newname):
|
||||
sink_sockets = []
|
||||
while len(self.outputs) < socket_number:
|
||||
self.outputs.new('ArmFloatSocket', 'BOGUS')
|
||||
if len(self.outputs) > socket_number:
|
||||
for link in self.inputs[socket_number].links:
|
||||
sink_sockets.append(link.to_socket)
|
||||
self.inputs.remove(self.inputs[socket_number])
|
||||
|
||||
self.inputs.new(newclass, newname)
|
||||
self.inputs.move(len(self.inputs)-1, socket_number)
|
||||
for socket in sink_sockets:
|
||||
self.id_data.links.new(self.inputs[socket_number], socket)
|
||||
|
||||
def on_property_update(self, context):
|
||||
# Checking the selection of another operation
|
||||
|
||||
|
||||
# Rotation as argument 0:
|
||||
if self.property0 in ('Inverse','Normalize','Amplify'):
|
||||
self.ensure_input_socket(0, "ArmRotationSocket", "Rotation")
|
||||
self.ensure_input_socket(1, "ArmFloatSocket", "Amplification factor")
|
||||
elif self.property0 in ('Slerp','Lerp','Compose'):
|
||||
self.ensure_input_socket(0, "ArmRotationSocket", "From")
|
||||
self.ensure_input_socket(1, "ArmRotationSocket", "To")
|
||||
|
||||
if self.property0 == 'Compose':
|
||||
self.inputs[0].name = 'Outer rotation'
|
||||
self.inputs[1].name = 'Inner rotation'
|
||||
else:
|
||||
self.ensure_input_socket(2, "ArmFloatSocket", "Interpolation factor")
|
||||
|
||||
elif self.property0 == 'FromTo':
|
||||
self.ensure_input_socket(0, "ArmVectorSocket", "From")
|
||||
self.ensure_input_socket(1, "ArmVectorSocket", "To")
|
||||
|
||||
# Rotation as argument 1:
|
||||
if self.property0 in ('Compose','Lerp','Slerp'):
|
||||
if self.inputs[1].bl_idname != "ArmRotationSocket":
|
||||
self.replace_input_socket(1, "ArmRotationSocket", "Rotation 2")
|
||||
if self.property0 == 'Compose':
|
||||
self.inputs[1].name = "Inner quaternion"
|
||||
# Float as argument 1:
|
||||
if self.property0 == 'Amplify':
|
||||
if self.inputs[1].bl_idname != 'ArmFloatSocket':
|
||||
self.replace_input_socket(1, "ArmFloatSocket", "Amplification factor")
|
||||
# Vector as argument 1:
|
||||
#if self.property0 == 'FromRotationMat':
|
||||
# # WHAT??
|
||||
# pass
|
||||
|
||||
while len(self.inputs) > self.get_count_in(self.property0):
|
||||
self.inputs.remove(self.inputs[len(self.inputs)-1])
|
||||
|
||||
|
||||
property0: HaxeEnumProperty(
|
||||
'property0',
|
||||
items = [('Compose', 'Compose (multiply)', 'compose (multiply) two rotations. Note that order of the composition matters.'),
|
||||
('Amplify', 'Amplify (multiply by float)', 'Amplify or diminish the effect of a rotation'),
|
||||
#('Normalize', 'Normalize', 'Normalize'),
|
||||
('Inverse', 'Get Inverse', 'from r, get the rotation r2 so that " r×r2=r2×r= <no rotation>" '),
|
||||
('Lerp', 'Lerp', 'Linearly interpolation'),
|
||||
('Slerp', 'Slerp', 'Spherical linear interpolation'),
|
||||
('FromTo', 'From To', 'From direction To direction'),
|
||||
#('FromRotationMat', 'From Rotation Mat', 'From Rotation Mat')
|
||||
],
|
||||
name='', default='Compose', update=on_property_update)
|
||||
|
||||
#def __init__(self):
|
||||
# array_nodes[str(id(self))] = self
|
||||
|
||||
def arm_init(self, context):
|
||||
self.add_input('ArmRotationSocket', 'Outer rotation', default_value=(0.0, 0.0, 0.0, 1.0) )
|
||||
self.add_input('ArmRotationSocket', 'Inner rotation', default_value=(0.0, 0.0, 0.0, 1.0) )
|
||||
self.add_output('ArmRotationSocket', 'Result')
|
||||
|
||||
def draw_buttons(self, context, layout):
|
||||
layout.prop(self, 'property0') # Operation
|
|
@ -1,16 +0,0 @@
|
|||
from arm.logicnode.arm_nodes import *
|
||||
|
||||
class SeparateQuaternionNode(ArmLogicTreeNode):
|
||||
"""Splits the given quaternion into X, Y, Z and W."""
|
||||
bl_idname = 'LNSeparateQuaternionNode'
|
||||
bl_label = "Separate Quaternion"
|
||||
arm_section = 'quaternions'
|
||||
arm_version = 1
|
||||
|
||||
def arm_init(self, context):
|
||||
self.add_input('ArmVectorSocket', 'Quaternion')
|
||||
|
||||
self.add_output('ArmFloatSocket', 'X')
|
||||
self.add_output('ArmFloatSocket', 'Y')
|
||||
self.add_output('ArmFloatSocket', 'Z')
|
||||
self.add_output('ArmFloatSocket', 'W')
|
|
@ -5,14 +5,87 @@ class GetRotationNode(ArmLogicTreeNode):
|
|||
bl_idname = 'LNGetRotationNode'
|
||||
bl_label = 'Get Object Rotation'
|
||||
arm_section = 'rotation'
|
||||
arm_version = 1
|
||||
arm_version = 2
|
||||
|
||||
def arm_init(self, context):
|
||||
self.add_input('ArmNodeSocketObject', 'Object')
|
||||
self.add_output('ArmRotationSocket', 'Rotation')
|
||||
|
||||
|
||||
self.add_output('ArmVectorSocket', 'Euler Angles')
|
||||
self.add_output('ArmVectorSocket', 'Vector')
|
||||
self.add_output('ArmFloatSocket', 'Angle (Radians)')
|
||||
self.add_output('ArmFloatSocket', 'Angle (Degrees)')
|
||||
self.add_output('ArmVectorSocket', 'Quaternion XYZ')
|
||||
self.add_output('ArmFloatSocket', 'Quaternion W')
|
||||
def draw_buttons(self, context, layout):
|
||||
layout.prop(self, 'property0')
|
||||
|
||||
property0: HaxeEnumProperty(
|
||||
'property0',
|
||||
items = [('Local', 'Local', 'Local'),
|
||||
('Global', 'Global', 'Global')],
|
||||
name='', default='Local')
|
||||
|
||||
|
||||
|
||||
def get_replacement_node(self, node_tree: bpy.types.NodeTree):
|
||||
if self.arm_version not in (0, 1):
|
||||
raise LookupError()
|
||||
|
||||
|
||||
# transition from version 1 to version 2: make rotations their own sockets
|
||||
# this transition is a mess, I know.
|
||||
|
||||
newself = self.id_data.nodes.new('LNGetRotationNode')
|
||||
newself.property0 = 'Local'
|
||||
newnodes = [newself]
|
||||
|
||||
if len(self.outputs[0].links)>0:
|
||||
# euler (radians) needed
|
||||
converter = self.id_data.nodes.new('LNSeparateRotationNode')
|
||||
converter.property0 = "EulerAngles"
|
||||
converter.property1 = "Rad"
|
||||
converter.property2 = "XZY"
|
||||
newnodes.append(converter)
|
||||
self.id_data.links.new(newself.outputs[0], converter.inputs[0])
|
||||
for link in self.outputs[0].links:
|
||||
self.id_data.links.new(converter.outputs[0], link.to_socket)
|
||||
|
||||
if len(self.outputs[4].links)>0 or len(self.outputs[5].links)>0:
|
||||
# quaternion needed
|
||||
converter = self.id_data.nodes.new('LNSeparateRotationNode')
|
||||
converter.property0 = "Quaternion"
|
||||
newnodes.append(converter)
|
||||
self.id_data.links.new(newself.outputs[0], converter.inputs[0])
|
||||
for link in self.outputs[4].links:
|
||||
self.id_data.links.new(converter.outputs[0], link.to_socket)
|
||||
for link in self.outputs[5].links:
|
||||
self.id_data.links.new(converter.outputs[1], link.to_socket)
|
||||
|
||||
if len(self.outputs[1].links)>0 or len(self.outputs[2].links)>0 or len(self.outputs[3].links)>0:
|
||||
# axis/angle needed
|
||||
converter = self.id_data.nodes.new('LNSeparateRotationNode')
|
||||
converter.property0 = "AxisAngle"
|
||||
converter.property1 = "Rad"
|
||||
newnodes.append(converter)
|
||||
self.id_data.links.new(newself.outputs[0], converter.inputs[0])
|
||||
for link in self.outputs[1].links:
|
||||
self.id_data.links.new(converter.outputs[0], link.to_socket)
|
||||
|
||||
if len(self.outputs[3].links)==0 and len(self.outputs[2].links)==0:
|
||||
pass
|
||||
elif len(self.outputs[3].links)==0:
|
||||
for link in self.outputs[2].links:
|
||||
self.id_data.links.new(converter.outputs[1], link.to_socket)
|
||||
elif len(self.outputs[2].links)==0:
|
||||
converter.property1 = 'Deg'
|
||||
for link in self.outputs[3].links:
|
||||
self.id_data.links.new(converter.outputs[1], link.to_socket)
|
||||
else:
|
||||
for link in self.outputs[2].links:
|
||||
self.id_data.links.new(converter.outputs[1], link.to_socket)
|
||||
converter = self.id_data.nodes.new('LNSeparateRotationNode')
|
||||
converter.property0 = "AxisAngle"
|
||||
converter.property1 = "Deg"
|
||||
converter.property2 = "XYZ" # bogus
|
||||
newnodes.append(converter)
|
||||
self.id_data.links.new(newself.outputs[0], converter.inputs[0])
|
||||
for link in self.outputs[3].links:
|
||||
self.id_data.links.new(converter.outputs[1], link.to_socket)
|
||||
|
||||
return newnodes
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
from arm.logicnode.arm_nodes import *
|
||||
|
||||
class LookAtNode(ArmLogicTreeNode):
|
||||
"""Converts the two given coordinates to a quaternion rotation."""
|
||||
"""Returns *a* rotation that makes something look away from X,Y or Z, and instead look in the 'from->to' direction"""
|
||||
bl_idname = 'LNLookAtNode'
|
||||
bl_label = 'Look At'
|
||||
arm_section = 'rotation'
|
||||
arm_version = 1
|
||||
arm_version = 2
|
||||
|
||||
property0: HaxeEnumProperty(
|
||||
'property0',
|
||||
|
@ -21,7 +21,27 @@ class LookAtNode(ArmLogicTreeNode):
|
|||
self.add_input('ArmVectorSocket', 'From Location')
|
||||
self.add_input('ArmVectorSocket', 'To Location')
|
||||
|
||||
self.add_output('ArmVectorSocket', 'Rotation')
|
||||
self.add_output('ArmRotationSocket', 'Rotation')
|
||||
|
||||
|
||||
def draw_buttons(self, context, layout):
|
||||
layout.prop(self, 'property0')
|
||||
|
||||
|
||||
def get_replacement_node(self, node_tree: bpy.types.NodeTree):
|
||||
if self.arm_version not in (0, 1):
|
||||
raise LookupError()
|
||||
|
||||
|
||||
# transition from version 1 to version 2: make rotations their own sockets
|
||||
# this transition is a mess, I know.
|
||||
newself = self.id_data.nodes.new('LNLookAtNode')
|
||||
converter = self.id_data.nodes.new('LNSeparateRotationNode')
|
||||
self.id_data.links.new(newself.outputs[0], converter.inputs[0])
|
||||
converter.property0 = 'EulerAngles'
|
||||
converter.property1 = 'Rad'
|
||||
converter.property2 = 'XZY'
|
||||
for link in self.outputs[0].links:
|
||||
self.id_data.links.new(converter.outputs[0], link.to_socket)
|
||||
|
||||
return [newself, converter]
|
||||
|
|
|
@ -1,49 +1,105 @@
|
|||
from arm.logicnode.arm_nodes import *
|
||||
from arm.logicnode.arm_sockets import ArmRotationSocket as Rotation
|
||||
|
||||
class RotateObjectNode(ArmLogicTreeNode):
|
||||
"""Rotates the given object."""
|
||||
bl_idname = 'LNRotateObjectNode'
|
||||
bl_label = 'Rotate Object'
|
||||
arm_section = 'rotation'
|
||||
arm_version = 1
|
||||
arm_version = 2
|
||||
|
||||
def arm_init(self, context):
|
||||
self.add_input('ArmNodeSocketAction', 'In')
|
||||
self.add_input('ArmNodeSocketObject', 'Object')
|
||||
self.add_input('ArmVectorSocket', 'Euler Angles')
|
||||
self.add_input('ArmFloatSocket', 'Angle / W')
|
||||
self.add_input('ArmRotationSocket', 'Rotation')
|
||||
|
||||
self.add_output('ArmNodeSocketAction', 'Out')
|
||||
|
||||
def on_property_update(self, context):
|
||||
""" called by the EnumProperty, used to update the node socket labels"""
|
||||
if self.property0 == "Quaternion":
|
||||
self.inputs[2].name = "Quaternion XYZ"
|
||||
self.inputs[3].name = "Quaternion W"
|
||||
elif self.property0 == "Euler Angles":
|
||||
self.inputs[2].name = "Euler Angles"
|
||||
self.inputs[3].name = "[unused for Euler input]"
|
||||
elif self.property0.startswith("Angle Axies"):
|
||||
self.inputs[2].name = "Axis"
|
||||
self.inputs[3].name = "Angle"
|
||||
else:
|
||||
raise ValueError('No nodesocket labels for current input mode: check self-consistancy of LN_rotate_object.py')
|
||||
|
||||
def draw_buttons(self, context, layout):
|
||||
# this block is here to ensure backwards compatibility and warn the user.
|
||||
# delete it (only keep the "else" part) when the 'old version' of the node will be considered removed.
|
||||
# (note: please also update the corresponding haxe file when doing so)
|
||||
if len(self.inputs) < 4:
|
||||
row = layout.row(align=True)
|
||||
row.label(text="Node has been updated with armory 2020.09. Please consider deleting and recreating it.")
|
||||
else:
|
||||
layout.prop(self, 'property0')
|
||||
layout.prop(self, 'property0_proxy')
|
||||
|
||||
|
||||
# this property swaperoo is kinda janky-looking, but listen out:
|
||||
# - when you reload an old file, the properties of loaded nodes can be mangled if the node class drops the property or the specific value within the property.
|
||||
# -> to fix this, 'property0' needs to contain the old values so that node replacement can be done decently.
|
||||
# - "but", I hear you ask, "why not make property0 a simple blender property, and create a property0v2 HaxeProperty to be bound to the haxe-time property0?"
|
||||
# -> well, at the time of writing, a HaxeProperty's prop_name is only used for livepatching, not at initial setup, so a freshly-compiled game would get completely borked properties.
|
||||
# solution: have a property0 HaxeProperty contain every possible value, and have a property0_proxy Property be in the UI.
|
||||
|
||||
# NOTE FOR FUTURE MAINTAINERS: the value of the proxy property does **not** matter, only the value of property0 does. When eventually editing this class, you can safely drop the values in the proxy property, and *only* the proxy property.
|
||||
|
||||
def on_proxyproperty_update(self, context=None):
|
||||
self.property0 = self.property0_proxy
|
||||
|
||||
property0_proxy: EnumProperty(
|
||||
items = [('Local', 'Local F.O.R.', 'Frame of reference oriented with the object'),
|
||||
('Global', 'Global/Parent F.O.R.',
|
||||
'Frame of reference oriented with the object\'s parent or the world')],
|
||||
name='', default='Local',
|
||||
update = on_proxyproperty_update
|
||||
)
|
||||
property0: HaxeEnumProperty(
|
||||
'property0',
|
||||
items=[('Euler Angles', 'Euler Angles', 'Euler Angles'),
|
||||
('Angle Axies (Radians)', 'Angle Axies (Radians)', 'Angle Axies (Radians)'),
|
||||
('Angle Axies (Degrees)', 'Angle Axies (Degrees)', 'Angle Axies (Degrees)'),
|
||||
('Quaternion', 'Quaternion', 'Quaternion')],
|
||||
name='', default='Euler Angles',
|
||||
update=on_property_update)
|
||||
items=[('Euler Angles', 'NODE REPLACEMENT ONLY', ''),
|
||||
('Angle Axies (Radians)', 'NODE REPLACEMENT ONLY', ''),
|
||||
('Angle Axies (Degrees)', 'NODE REPLACEMENT ONLY', ''),
|
||||
('Quaternion', 'NODE REPLACEMENT ONLY', ''),
|
||||
('Local', 'Local F.O.R.', 'Frame of reference oriented with the object'),
|
||||
('Global', 'Global/Parent F.O.R.',
|
||||
'Frame of reference oriented with the object\'s parent or the world')
|
||||
],
|
||||
name='', default='Local')
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def get_replacement_node(self, node_tree: bpy.types.NodeTree):
|
||||
if self.arm_version not in (0, 1):
|
||||
raise LookupError()
|
||||
|
||||
|
||||
# transition from version 1 to version 2: make rotations their own sockets
|
||||
# this transition is a mess, I know.
|
||||
|
||||
newself = self.id_data.nodes.new('LNRotateObjectNode')
|
||||
inputnode = self.id_data.nodes.new('LNRotationNode')
|
||||
self.id_data.links.new(inputnode.outputs[0], newself.inputs[2])
|
||||
newself.inputs[1].default_value_raw = self.inputs[1].default_value_raw
|
||||
inputnode.inputs[0].default_value = self.inputs[2].default_value
|
||||
inputnode.inputs[1].default_value = self.inputs[3].default_value
|
||||
|
||||
if len(self.inputs[0].links) >0:
|
||||
self.id_data.links.new(self.inputs[0].links[0].from_socket, newself.inputs[0])
|
||||
if len(self.inputs[1].links) >0:
|
||||
self.id_data.links.new(self.inputs[1].links[0].from_socket, newself.inputs[1])
|
||||
if len(self.inputs[2].links) >0:
|
||||
self.id_data.links.new(self.inputs[2].links[0].from_socket, inputnode.inputs[0])
|
||||
if len(self.inputs[3].links) >0:
|
||||
self.id_data.links.new(self.inputs[3].links[0].from_socket, inputnode.inputs[1])
|
||||
|
||||
# first, convert the default value
|
||||
if self.property0 == 'Quaternion':
|
||||
inputnode.property0 = 'Quaternion'
|
||||
elif self.property0 == 'Euler Angles':
|
||||
inputnode.property0 = 'EulerAngles'
|
||||
inputnode.property1 = 'Rad'
|
||||
inputnode.property2 = 'XZY' # legacy order
|
||||
else: # starts with "Angle Axies"
|
||||
inputnode.property0 = 'AxisAngle'
|
||||
if 'Degrees' in self.property0:
|
||||
inputnode.property1 = 'Deg'
|
||||
else:
|
||||
inputnode.property1 = 'Rad'
|
||||
quat = Rotation.convert_to_quaternion(
|
||||
self.inputs[2].default_value,
|
||||
self.inputs[3].default_value,
|
||||
inputnode.property0,
|
||||
inputnode.property1,
|
||||
inputnode.property2
|
||||
)
|
||||
newself.inputs[2].default_value_raw = quat
|
||||
return [newself, inputnode]
|
||||
|
|
60
blender/arm/logicnode/transform/LN_separate_rotation.py
Normal file
60
blender/arm/logicnode/transform/LN_separate_rotation.py
Normal file
|
@ -0,0 +1,60 @@
|
|||
from arm.logicnode.arm_nodes import *
|
||||
|
||||
class SeparateRotationNode(ArmLogicTreeNode):
|
||||
"""Decompose a rotation into one of its mathematical representations"""
|
||||
bl_idname = 'LNSeparateRotationNode'
|
||||
bl_label = 'Separate Rotation'
|
||||
arm_section = 'rotation'
|
||||
arm_version = 1
|
||||
|
||||
def arm_init(self, context):
|
||||
self.add_input('ArmRotationSocket', 'Angle')
|
||||
|
||||
self.add_output('ArmVectorSocket', 'Euler Angles / Vector XYZ')
|
||||
self.add_output('ArmFloatSocket', 'Angle / W')
|
||||
|
||||
|
||||
def on_property_update(self, context):
|
||||
"""called by the EnumProperty, used to update the node socket labels"""
|
||||
if self.property0 == 'Quaternion':
|
||||
self.outputs[0].name = "Quaternion XYZ"
|
||||
self.outputs[1].name = "Quaternion W"
|
||||
elif self.property0 == 'EulerAngles':
|
||||
self.outputs[0].name = "Euler Angles"
|
||||
self.outputs[1].name = "[unused for Euler output]"
|
||||
elif self.property0 == 'AxisAngle':
|
||||
self.outputs[0].name = "Axis"
|
||||
self.outputs[1].name = "Angle"
|
||||
else:
|
||||
raise ValueError('No nodesocket labels for current input mode: check self-consistancy of LN_separate_rotation.py')
|
||||
|
||||
def draw_buttons(self, context, layout):
|
||||
coll = layout.column(align=True)
|
||||
coll.prop(self, 'property0')
|
||||
if self.property0 in ('EulerAngles','AxisAngle'):
|
||||
coll.prop(self, 'property1')
|
||||
if self.property0=='EulerAngles':
|
||||
coll.prop(self, 'property2')
|
||||
|
||||
property0: HaxeEnumProperty(
|
||||
'property0',
|
||||
items = [('EulerAngles', 'Euler Angles', 'Euler Angles'),
|
||||
('AxisAngle', 'Axis/Angle', 'Axis/Angle'),
|
||||
('Quaternion', 'Quaternion', 'Quaternion')],
|
||||
name='', default='EulerAngles',
|
||||
update=on_property_update)
|
||||
|
||||
property1: HaxeEnumProperty(
|
||||
'property1',
|
||||
items=[('Deg', 'Degrees', 'Degrees'),
|
||||
('Rad', 'Radians', 'Radians')],
|
||||
name='', default='Rad')
|
||||
property2: HaxeEnumProperty(
|
||||
'property2',
|
||||
items=[('XYZ','XYZ','XYZ'),
|
||||
('XZY','XZY (legacy Armory euler order)','XZY (legacy Armory euler order)'),
|
||||
('YXZ','YXZ','YXZ'),
|
||||
('YZX','YZX','YZX'),
|
||||
('ZXY','ZXY','ZXY'),
|
||||
('ZYX','ZYX','ZYX')],
|
||||
name='', default='XYZ')
|
|
@ -4,11 +4,42 @@ class SeparateTransformNode(ArmLogicTreeNode):
|
|||
"""Separates the transform of the given object."""
|
||||
bl_idname = 'LNSeparateTransformNode'
|
||||
bl_label = 'Separate Transform'
|
||||
arm_version = 1
|
||||
arm_version = 2
|
||||
|
||||
def arm_init(self, context):
|
||||
self.add_input('ArmDynamicSocket', 'Transform')
|
||||
|
||||
self.add_output('ArmVectorSocket', 'Location')
|
||||
self.add_output('ArmVectorSocket', 'Rotation')
|
||||
self.add_output('ArmRotationSocket', 'Rotation')
|
||||
self.add_output('ArmVectorSocket', 'Scale')
|
||||
|
||||
|
||||
|
||||
def get_replacement_node(self, node_tree: bpy.types.NodeTree):
|
||||
if self.arm_version not in (0, 1):
|
||||
raise LookupError()
|
||||
|
||||
|
||||
# transition from version 1 to version 2: make rotations their own sockets
|
||||
# this transition is a mess, I know.
|
||||
newself = self.id_data.nodes.new('LNSeparateTransformNode')
|
||||
for link in self.outputs[0].links:
|
||||
self.id_data.links.new(newself.outputs[0], link.to_socket)
|
||||
for link in self.outputs[2].links:
|
||||
self.id_data.links.new(newself.outputs[2], link.to_socket)
|
||||
for link in self.inputs[0].links:
|
||||
self.id_data.links.new(link.from_socket, newself.inputs[0])
|
||||
|
||||
ret = [newself]
|
||||
rot_links = self.outputs[1].links
|
||||
if len(rot_links) >0:
|
||||
converter = self.id_data.nodes.new('LNSeparateRotationNode')
|
||||
ret.append(converter)
|
||||
self.id_data.links.new(newself.outputs[1], converter.inputs[0])
|
||||
converter.property0 = 'EulerAngles'
|
||||
converter.property1 = 'Rad'
|
||||
converter.property2 = 'XZY'
|
||||
for link in rot_links:
|
||||
self.id_data.links.new(converter.outputs[0], link.to_socket)
|
||||
|
||||
return ret
|
||||
|
|
|
@ -1,42 +1,76 @@
|
|||
from arm.logicnode.arm_nodes import *
|
||||
from arm.logicnode.arm_sockets import ArmRotationSocket as Rotation
|
||||
|
||||
class SetRotationNode(ArmLogicTreeNode):
|
||||
"""Sets the rotation of the given object."""
|
||||
bl_idname = 'LNSetRotationNode'
|
||||
bl_label = 'Set Object Rotation'
|
||||
arm_section = 'rotation'
|
||||
arm_version = 1
|
||||
arm_version = 2
|
||||
|
||||
|
||||
def arm_init(self, context):
|
||||
self.add_input('ArmNodeSocketAction', 'In')
|
||||
self.add_input('ArmNodeSocketObject', 'Object')
|
||||
self.add_input('ArmVectorSocket', 'Euler Angles / Vector XYZ')
|
||||
self.add_input('ArmFloatSocket', 'Angle / W')
|
||||
self.add_input('ArmRotationSocket', 'Rotation')
|
||||
|
||||
self.add_output('ArmNodeSocketAction', 'Out')
|
||||
|
||||
def on_property_update(self, context):
|
||||
"""called by the EnumProperty, used to update the node socket labels"""
|
||||
if self.property0 == "Quaternion":
|
||||
self.inputs[2].name = "Quaternion XYZ"
|
||||
self.inputs[3].name = "Quaternion W"
|
||||
elif self.property0 == "Euler Angles":
|
||||
self.inputs[2].name = "Euler Angles"
|
||||
self.inputs[3].name = "[unused for Euler input]"
|
||||
elif self.property0.startswith("Angle Axies"):
|
||||
self.inputs[2].name = "Axis"
|
||||
self.inputs[3].name = "Angle"
|
||||
|
||||
def get_replacement_node(self, node_tree: bpy.types.NodeTree):
|
||||
if self.arm_version not in (0, 1):
|
||||
raise LookupError()
|
||||
|
||||
|
||||
# transition from version 1 to version 2: make rotations their own sockets
|
||||
# this transition is a mess, I know.
|
||||
|
||||
|
||||
newself = self.id_data.nodes.new('LNRotateObjectNode')
|
||||
inputnode = self.id_data.nodes.new('LNRotationNode')
|
||||
self.id_data.links.new(inputnode.outputs[0], newself.inputs[2])
|
||||
newself.inputs[1].default_value_raw = self.inputs[1].default_value_raw
|
||||
inputnode.inputs[0].default_value = self.inputs[2].default_value
|
||||
inputnode.inputs[1].default_value = self.inputs[3].default_value
|
||||
|
||||
if len(self.inputs[0].links) >0:
|
||||
self.id_data.links.new(self.inputs[0].links[0].from_socket, newself.inputs[0])
|
||||
if len(self.inputs[1].links) >0:
|
||||
self.id_data.links.new(self.inputs[1].links[0].from_socket, newself.inputs[1])
|
||||
if len(self.inputs[2].links) >0:
|
||||
self.id_data.links.new(self.inputs[2].links[0].from_socket, inputnode.inputs[0])
|
||||
if len(self.inputs[3].links) >0:
|
||||
self.id_data.links.new(self.inputs[3].links[0].from_socket, inputnode.inputs[1])
|
||||
|
||||
# first, convert the default value
|
||||
if self.property0 == 'Quaternion':
|
||||
inputnode.property0 = 'Quaternion'
|
||||
elif self.property0 == 'Euler Angles':
|
||||
inputnode.property0 = 'EulerAngles'
|
||||
inputnode.property1 = 'Rad'
|
||||
inputnode.property2 = 'XZY' # legacy order
|
||||
elif self.property0.startswith("Angle Axies "):
|
||||
inputnode.property0 = 'AxisAngle'
|
||||
if 'Degrees' in self.property0:
|
||||
inputnode.property1 = 'Deg'
|
||||
else:
|
||||
inputnode.property1 = 'Rad'
|
||||
else:
|
||||
raise ValueError('No nodesocket labels for current input mode: check self-consistancy of action_set_rotation.py')
|
||||
raise ValueError('nonsensical value {:s} for property0 in SetObjectRotationNode/V1. please report this to the devs.'.format(self.property0))
|
||||
quat = Rotation.convert_to_quaternion(
|
||||
self.inputs[2].default_value,
|
||||
self.inputs[3].default_value,
|
||||
inputnode.property0,
|
||||
inputnode.property1,
|
||||
inputnode.property2
|
||||
)
|
||||
newself.inputs[2].default_value_raw = quat
|
||||
return [newself, inputnode]
|
||||
|
||||
def draw_buttons(self, context, layout):
|
||||
layout.prop(self, 'property0')
|
||||
|
||||
property0: HaxeEnumProperty(
|
||||
'property0',
|
||||
items = [('Euler Angles', 'Euler Angles', 'Euler Angles'),
|
||||
('Angle Axies (Radians)', 'Angle Axies (Radians)', 'Angle Axies (Radians)'),
|
||||
('Angle Axies (Degrees)', 'Angle Axies (Degrees)', 'Angle Axies (Degrees)'),
|
||||
('Quaternion', 'Quaternion', 'Quaternion')],
|
||||
name='', default='Euler Angles',
|
||||
update=on_property_update)
|
||||
# note: this is unused, but kept here so that the 'property0' field can be read during node replacement
|
||||
property0: EnumProperty(
|
||||
items = [('Euler Angles', '',''),
|
||||
('Angle Axies (Radians)', '', ''),
|
||||
('Angle Axies (Degrees)', '', ''),
|
||||
('Quaternion', '', '')],
|
||||
name='', default='Euler Angles')
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
from arm.logicnode.arm_nodes import *
|
||||
|
||||
class TransformNode(ArmLogicTreeNode):
|
||||
"""Stores the location, rotation and scale values as a transform."""
|
||||
bl_idname = 'LNTransformNode'
|
||||
bl_label = 'Transform'
|
||||
arm_version = 1
|
||||
|
||||
def arm_init(self, context):
|
||||
self.add_input('ArmVectorSocket', 'Location')
|
||||
self.add_input('ArmVectorSocket', 'Rotation')
|
||||
self.add_input('ArmVectorSocket', 'Scale', default_value=[1.0, 1.0, 1.0])
|
||||
|
||||
self.add_output('ArmDynamicSocket', 'Transform')
|
|
@ -9,7 +9,7 @@ class VectorToObjectOrientationNode(ArmLogicTreeNode):
|
|||
"""
|
||||
bl_idname = 'LNVectorToObjectOrientationNode'
|
||||
bl_label = 'Vector to Object Orientation'
|
||||
arm_section = 'location'
|
||||
arm_section = 'rotation'
|
||||
arm_version = 1
|
||||
|
||||
def arm_init(self, context):
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
from arm.logicnode.arm_nodes import *
|
||||
|
||||
class QuaternionNode(ArmLogicTreeNode):
|
||||
"""TO DO."""
|
||||
bl_idname = 'LNQuaternionNode'
|
||||
bl_label = 'Quaternion'
|
||||
arm_section = 'quaternions'
|
||||
arm_version = 1
|
||||
|
||||
def arm_init(self, context):
|
||||
self.add_input('ArmFloatSocket', 'X')
|
||||
self.add_input('ArmFloatSocket', 'Y')
|
||||
self.add_input('ArmFloatSocket', 'Z')
|
||||
self.add_input('ArmFloatSocket', 'W', default_value=1.0)
|
||||
|
||||
self.add_output('ArmVectorSocket', 'Quaternion')
|
||||
self.add_output('ArmVectorSocket', 'XYZ')
|
||||
self.add_output('ArmFloatSocket', 'W')
|
61
blender/arm/logicnode/variable/LN_rotation.py
Normal file
61
blender/arm/logicnode/variable/LN_rotation.py
Normal file
|
@ -0,0 +1,61 @@
|
|||
from arm.logicnode.arm_nodes import *
|
||||
|
||||
class RotationNode(ArmLogicTreeNode):
|
||||
"""A rotation, created from one of its possible mathematical representations"""
|
||||
bl_idname = 'LNRotationNode'
|
||||
bl_label = 'Rotation'
|
||||
bl_description = 'Create a Rotation object, describing the difference between two orientations (internally represented as a quaternion for efficiency)'
|
||||
#arm_section = 'rotation'
|
||||
arm_version = 1
|
||||
|
||||
def arm_init(self, context):
|
||||
self.add_input('ArmVectorSocket', 'Euler Angles / Vector XYZ')
|
||||
self.add_input('ArmFloatSocket', 'Angle / W')
|
||||
|
||||
self.add_output('ArmRotationSocket', 'Out', is_var=True)
|
||||
|
||||
def on_property_update(self, context):
|
||||
"""called by the EnumProperty, used to update the node socket labels"""
|
||||
if self.property0 == "Quaternion":
|
||||
self.inputs[0].name = "Quaternion XYZ"
|
||||
self.inputs[1].name = "Quaternion W"
|
||||
elif self.property0 == "EulerAngles":
|
||||
self.inputs[0].name = "Euler Angles"
|
||||
self.inputs[1].name = "[unused for Euler input]"
|
||||
elif self.property0 == "AxisAngle":
|
||||
self.inputs[0].name = "Axis"
|
||||
self.inputs[1].name = "Angle"
|
||||
else:
|
||||
raise ValueError('No nodesocket labels for current input mode: check self-consistancy of LN_rotation.py')
|
||||
|
||||
def draw_buttons(self, context, layout):
|
||||
coll = layout.column(align=True)
|
||||
coll.prop(self, 'property0')
|
||||
if self.property0 in ('EulerAngles','AxisAngle'):
|
||||
coll.prop(self, 'property1')
|
||||
if self.property0=='EulerAngles':
|
||||
coll.prop(self, 'property2')
|
||||
|
||||
property0: HaxeEnumProperty(
|
||||
'property0',
|
||||
items = [('EulerAngles', 'Euler Angles', 'Euler Angles'),
|
||||
('AxisAngle', 'Axis/Angle', 'Axis/Angle'),
|
||||
('Quaternion', 'Quaternion', 'Quaternion')],
|
||||
name='', default='EulerAngles',
|
||||
update=on_property_update)
|
||||
|
||||
property1: HaxeEnumProperty(
|
||||
'property1',
|
||||
items=[('Deg', 'Degrees', 'Degrees'),
|
||||
('Rad', 'Radians', 'Radians')],
|
||||
name='', default='Rad')
|
||||
property2: HaxeEnumProperty(
|
||||
'property2',
|
||||
items=[('XYZ','XYZ','XYZ'),
|
||||
('XZY','XZY (legacy Armory euler order)','XZY (legacy Armory euler order)'),
|
||||
('YXZ','YXZ','YXZ'),
|
||||
('YZX','YZX','YZX'),
|
||||
('ZXY','ZXY','ZXY'),
|
||||
('ZYX','ZYX','ZYX')],
|
||||
name='', default='XYZ'
|
||||
)
|
44
blender/arm/logicnode/variable/LN_transform.py
Normal file
44
blender/arm/logicnode/variable/LN_transform.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
from arm.logicnode.arm_nodes import *
|
||||
|
||||
class TransformNode(ArmLogicTreeNode):
|
||||
"""Stores the location, rotation and scale values as a transform."""
|
||||
bl_idname = 'LNTransformNode'
|
||||
bl_label = 'Transform'
|
||||
arm_version = 2
|
||||
|
||||
def arm_init(self, context):
|
||||
self.add_input('ArmVectorSocket', 'Location')
|
||||
self.add_input('ArmRotationSocket', 'Rotation')
|
||||
self.add_input('ArmVectorSocket', 'Scale', default_value=[1.0, 1.0, 1.0])
|
||||
self.add_output('ArmDynamicSocket', 'Transform', is_var=True)
|
||||
|
||||
|
||||
def get_replacement_node(self, node_tree: bpy.types.NodeTree):
|
||||
if self.arm_version not in (0, 1):
|
||||
raise LookupError()
|
||||
|
||||
|
||||
# transition from version 1 to version 2: make rotations their own sockets
|
||||
# this transition is a mess, I know.
|
||||
newself = self.id_data.nodes.new('LNTransformNode')
|
||||
ret = [newself]
|
||||
|
||||
for link in self.inputs[0].links:
|
||||
self.id_data.links.new(link.from_socket, newself.inputs[0])
|
||||
for link in self.inputs[2].links:
|
||||
self.id_data.links.new(link.from_socket, newself.inputs[2])
|
||||
for link in self.outputs[0].links:
|
||||
self.id_data.links.new(newself.outputs[0], link.to_socket)
|
||||
|
||||
links_rot = self.inputs[1].links
|
||||
if len(links_rot) > 0:
|
||||
converter = self.id_data.nodes.new('LNRotationNode')
|
||||
self.id_data.links.new(converter.outputs[0], newself.inputs[1])
|
||||
converter.property0 = 'EulerAngles'
|
||||
converter.property1 = 'Rad'
|
||||
converter.property2 = 'XZY'
|
||||
ret.append(converter)
|
||||
for link in links_rot:
|
||||
self.id_data.links.new(link.from_socket, converter.inputs[0])
|
||||
|
||||
return ret
|
|
@ -308,6 +308,8 @@ def build_default_node(inp: bpy.types.NodeSocket):
|
|||
|
||||
if inp_type == 'VECTOR':
|
||||
return f'new armory.logicnode.VectorNode(this, {default_value})'
|
||||
elif inp_type == 'ROTATION': # a rotation is internally represented as a quaternion.
|
||||
return f'new armory.logicnode.RotationNode(this, {default_value})'
|
||||
elif inp_type in ('RGB', 'RGBA'):
|
||||
return f'new armory.logicnode.ColorNode(this, {default_value})'
|
||||
elif inp_type == 'VALUE':
|
||||
|
|
Loading…
Reference in a new issue