Merge pull request 'Transforms_and_Rotations' (#42) from Onek8/LNXSDK:Transforms_and_Rotations into main
Reviewed-on: #42
This commit is contained in:
commit
5628493493
@ -399,17 +399,33 @@ class Quat {
|
|||||||
@return This quaternion.
|
@return This quaternion.
|
||||||
**/
|
**/
|
||||||
public inline function fromEulerOrdered(e: Vec4, order: String): Quat {
|
public inline function fromEulerOrdered(e: Vec4, order: String): Quat {
|
||||||
var c1 = Math.cos(e.x / 2);
|
|
||||||
var c2 = Math.cos(e.y / 2);
|
|
||||||
var c3 = Math.cos(e.z / 2);
|
|
||||||
var s1 = Math.sin(e.x / 2);
|
|
||||||
var s2 = Math.sin(e.y / 2);
|
|
||||||
var s3 = Math.sin(e.z / 2);
|
|
||||||
|
|
||||||
|
var mappedAngles = new Vec4();
|
||||||
|
switch (order) {
|
||||||
|
case "XYZ":
|
||||||
|
mappedAngles.set(e.x, e.y, e.z);
|
||||||
|
case "XZY":
|
||||||
|
mappedAngles.set(e.x, e.z, e.y);
|
||||||
|
case "YXZ":
|
||||||
|
mappedAngles.set(e.y, e.x, e.z);
|
||||||
|
case "YZX":
|
||||||
|
mappedAngles.set(e.y, e.z, e.x);
|
||||||
|
case "ZXY":
|
||||||
|
mappedAngles.set(e.z, e.x, e.y);
|
||||||
|
case "ZYX":
|
||||||
|
mappedAngles.set(e.z, e.y, e.x);
|
||||||
|
}
|
||||||
|
var c1 = Math.cos(mappedAngles.x / 2);
|
||||||
|
var c2 = Math.cos(mappedAngles.y / 2);
|
||||||
|
var c3 = Math.cos(mappedAngles.z / 2);
|
||||||
|
var s1 = Math.sin(mappedAngles.x / 2);
|
||||||
|
var s2 = Math.sin(mappedAngles.y / 2);
|
||||||
|
var s3 = Math.sin(mappedAngles.z / 2);
|
||||||
var qx = new Quat(s1, 0, 0, c1);
|
var qx = new Quat(s1, 0, 0, c1);
|
||||||
var qy = new Quat(0, s2, 0, c2);
|
var qy = new Quat(0, s2, 0, c2);
|
||||||
var qz = new Quat(0, 0, s3, c3);
|
var qz = new Quat(0, 0, s3, c3);
|
||||||
|
|
||||||
|
// Original multiplication sequence (implements reverse of 'order')
|
||||||
if (order.charAt(2) == 'X')
|
if (order.charAt(2) == 'X')
|
||||||
this.setFrom(qx);
|
this.setFrom(qx);
|
||||||
else if (order.charAt(2) == 'Y')
|
else if (order.charAt(2) == 'Y')
|
||||||
@ -429,7 +445,7 @@ class Quat {
|
|||||||
else
|
else
|
||||||
this.mult(qz);
|
this.mult(qz);
|
||||||
|
|
||||||
// TO DO quick fix doesnt make sense.
|
// TO DO quick fix somethings wrong..
|
||||||
this.x = -this.x;
|
this.x = -this.x;
|
||||||
this.y = -this.y;
|
this.y = -this.y;
|
||||||
this.z = -this.z;
|
this.z = -this.z;
|
||||||
|
@ -37,6 +37,7 @@ class RotationNode extends LogicNode {
|
|||||||
value.y = vect.y;
|
value.y = vect.y;
|
||||||
value.z = vect.z;
|
value.z = vect.z;
|
||||||
value.w = inputs[1].get();
|
value.w = inputs[1].get();
|
||||||
|
value.normalize();
|
||||||
|
|
||||||
case "AxisAngle":
|
case "AxisAngle":
|
||||||
var vec: Vec4 = inputs[0].get();
|
var vec: Vec4 = inputs[0].get();
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from math import pi, cos, sin, sqrt
|
from math import radians, pi, cos, sin, sqrt
|
||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
@ -158,16 +158,13 @@ class LnxRotationSocket(LnxCustomSocket):
|
|||||||
self.do_update_raw(context)
|
self.do_update_raw(context)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def convert_to_quaternion(part1,part2,param1,param2,param3):
|
def convert_to_quaternion(vec3_val, scalar_val, mode, unit, order):
|
||||||
"""converts a representation of rotation into a quaternion.
|
'''Converts Euler or Axis-Angle representation to a Quaternion Vector'''
|
||||||
``part1`` is a vector, ``part2`` is a scalar or None,
|
|
||||||
``param1`` is in ('Quaternion', 'EulerAngles', 'AxisAngle'),
|
if mode == 'Quaternion':
|
||||||
``param2`` is in ('Rad','Deg') for both EulerAngles and AxisAngle,
|
qx, qy, qz = vec3_val[0], vec3_val[1], vec3_val[2]
|
||||||
``param3`` is a len-3 string like "XYZ", for EulerAngles """
|
qw = scalar_val
|
||||||
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)
|
ql = sqrt(qx**2 + qy**2 + qz**2 + qw**2)
|
||||||
if abs(ql) < 1E-5:
|
if abs(ql) < 1E-5:
|
||||||
qx, qy, qz, qw = 0.0, 0.0, 0.0, 1.0
|
qx, qy, qz, qw = 0.0, 0.0, 0.0, 1.0
|
||||||
@ -178,50 +175,73 @@ class LnxRotationSocket(LnxCustomSocket):
|
|||||||
qw /= ql
|
qw /= ql
|
||||||
return mathutils.Vector((qx, qy, qz, qw))
|
return mathutils.Vector((qx, qy, qz, qw))
|
||||||
|
|
||||||
elif param1 == 'AxisAngle':
|
elif mode == 'EulerAngles':
|
||||||
if param2 == 'Deg':
|
x, y, z = vec3_val.to_tuple()
|
||||||
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
|
|
||||||
|
|
||||||
euler = mathutils.Euler((x, y, z), param3)
|
if unit == 'Deg':
|
||||||
quat = euler.to_quaternion()
|
x, y, z = radians(x), radians(y), radians(z)
|
||||||
|
|
||||||
|
angles_ordered = [0.0, 0.0, 0.0]
|
||||||
|
for i, axis in enumerate(order):
|
||||||
|
if axis == 'X':
|
||||||
|
angles_ordered[i] = x
|
||||||
|
elif axis == 'Y':
|
||||||
|
angles_ordered[i] = y
|
||||||
|
elif axis == 'Z':
|
||||||
|
angles_ordered[i] = z
|
||||||
|
eul = mathutils.Euler(angles_ordered, order)
|
||||||
|
quat = eul.to_quaternion()
|
||||||
return mathutils.Vector((quat.x, quat.y, quat.z, quat.w))
|
return mathutils.Vector((quat.x, quat.y, quat.z, quat.w))
|
||||||
|
|
||||||
|
elif mode == 'AxisAngle':
|
||||||
|
axis = vec3_val.normalized().to_tuple()
|
||||||
|
angle = scalar_val
|
||||||
|
if unit == 'Deg':
|
||||||
|
angle = radians(angle)
|
||||||
|
quat = mathutils.Quaternion(axis, angle)
|
||||||
|
return mathutils.Vector((quat.x, quat.y, quat.z, quat.w))
|
||||||
|
|
||||||
|
print(f"Warning: Invalid mode '{mode}' in convert_to_quaternion")
|
||||||
|
return mathutils.Vector((0.0, 0.0, 0.0, 1.0))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def do_update_raw(self, context):
|
def do_update_raw(self, context):
|
||||||
part1 = mathutils.Vector((
|
if self.default_value_mode == 'Quaternion':
|
||||||
self.default_value_s0,
|
# Directly construct the quaternion vector from s0, s1, s2, s3 (x, y, z, w)
|
||||||
self.default_value_s1,
|
vec3_val = mathutils.Vector((
|
||||||
self.default_value_s2, 1
|
self.default_value_s0, # X component or Euler X or Axis X
|
||||||
|
self.default_value_s1, # Y component or Euler Y or Axis Y
|
||||||
|
self.default_value_s2 # Z component or Euler Z or Axis Z
|
||||||
))
|
))
|
||||||
part2 = self.default_value_s3
|
scalar_val = self.default_value_s3 # W component or Axis Angle
|
||||||
|
|
||||||
|
# Always call the unified conversion function
|
||||||
|
# The result will be in (x, y, z, w) order
|
||||||
self.default_value_raw = self.convert_to_quaternion(
|
self.default_value_raw = self.convert_to_quaternion(
|
||||||
part1,
|
vec3_val,
|
||||||
self.default_value_s3,
|
scalar_val,
|
||||||
self.default_value_mode,
|
self.default_value_mode,
|
||||||
self.default_value_unit,
|
self.default_value_unit,
|
||||||
self.default_value_order
|
self.default_value_order
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
# Handle EulerAngles and AxisAngle using the conversion helper
|
||||||
|
vec3_val = mathutils.Vector((
|
||||||
|
self.default_value_s0,
|
||||||
|
self.default_value_s1,
|
||||||
|
self.default_value_s2
|
||||||
|
))
|
||||||
|
# s3 is used for AxisAngle angle, irrelevant for Euler
|
||||||
|
scalar_val = self.default_value_s3
|
||||||
|
|
||||||
|
self.default_value_raw = self.convert_to_quaternion(
|
||||||
|
vec3_val, # Vector part (Euler angles X,Y,Z or Axis X,Y,Z)
|
||||||
|
scalar_val, # Scalar part (Axis angle)
|
||||||
|
self.default_value_mode, # Mode ('EulerAngles' or 'AxisAngle')
|
||||||
|
self.default_value_unit, # Unit ('Rad' or 'Deg')
|
||||||
|
self.default_value_order # Order ('XYZ', 'ZYX', etc. - used only for Euler)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def draw(self, context, layout, node, text):
|
def draw(self, context, layout, node, text):
|
||||||
@ -275,9 +295,11 @@ class LnxRotationSocket(LnxCustomSocket):
|
|||||||
('YZX','YZX','YZX'),
|
('YZX','YZX','YZX'),
|
||||||
('ZXY','ZXY','ZXY'),
|
('ZXY','ZXY','ZXY'),
|
||||||
('ZYX','ZYX','ZYX')],
|
('ZYX','ZYX','ZYX')],
|
||||||
name='', default='XYZ'
|
name='', default='XYZ',
|
||||||
|
update=do_update_raw
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
default_value_s0: FloatProperty(update=do_update_raw)
|
default_value_s0: FloatProperty(update=do_update_raw)
|
||||||
default_value_s1: FloatProperty(update=do_update_raw)
|
default_value_s1: FloatProperty(update=do_update_raw)
|
||||||
default_value_s2: FloatProperty(update=do_update_raw)
|
default_value_s2: FloatProperty(update=do_update_raw)
|
||||||
|
@ -344,6 +344,8 @@ class LNX_PT_NodeDevelopment(bpy.types.Panel):
|
|||||||
|
|
||||||
layout.separator()
|
layout.separator()
|
||||||
layout.operator('lnx.node_replace_all')
|
layout.operator('lnx.node_replace_all')
|
||||||
|
layout.operator('lnx.recalculate_rotations')
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _draw_row(col: bpy.types.UILayout, text: str, val: Any):
|
def _draw_row(col: bpy.types.UILayout, text: str, val: Any):
|
||||||
@ -366,6 +368,40 @@ class LNX_OT_ReplaceNodesOperator(bpy.types.Operator):
|
|||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
return context.space_data is not None and context.space_data.type == 'NODE_EDITOR'
|
return context.space_data is not None and context.space_data.type == 'NODE_EDITOR'
|
||||||
|
|
||||||
|
class LNX_OT_RecalculateRotations(bpy.types.Operator):
|
||||||
|
"""Recalculates internal rotation values for all rotation sockets in the tree"""
|
||||||
|
bl_idname = "lnx.recalculate_rotations"
|
||||||
|
bl_label = "Recalculate Rotations"
|
||||||
|
bl_description = "Forces recalculation of internal quaternion values for all LnxRotationSockets in the active tree using their current settings. Useful for fixing old files."
|
||||||
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return (context.space_data is not None and
|
||||||
|
context.space_data.type == 'NODE_EDITOR' and
|
||||||
|
context.space_data.tree_type == 'LnxLogicTreeType' and
|
||||||
|
context.space_data.edit_tree is not None)
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
tree = context.space_data.edit_tree
|
||||||
|
if not tree:
|
||||||
|
self.report({'WARNING'}, "No active Logic Node tree found")
|
||||||
|
return {'CANCELLED'}
|
||||||
|
recalculated_count = 0
|
||||||
|
for node in tree.nodes:
|
||||||
|
for socket in list(node.inputs) + list(node.outputs):
|
||||||
|
if hasattr(socket, 'do_update_raw') and callable(socket.do_update_raw):
|
||||||
|
try:
|
||||||
|
socket.do_update_raw(context)
|
||||||
|
recalculated_count += 1
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error recalculating socket '{socket.name}' on node '{node.name}': {e}")
|
||||||
|
|
||||||
|
self.report({'INFO'}, f"Recalculated {recalculated_count} rotation sockets in tree '{tree.name}'")
|
||||||
|
tree.update_tag()
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
class LNX_UL_InterfaceSockets(bpy.types.UIList):
|
class LNX_UL_InterfaceSockets(bpy.types.UIList):
|
||||||
"""UI List of input and output sockets"""
|
"""UI List of input and output sockets"""
|
||||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
|
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
|
||||||
@ -421,6 +457,7 @@ __REG_CLASSES = (
|
|||||||
LnxOpenNodePythonSource,
|
LnxOpenNodePythonSource,
|
||||||
LnxOpenNodeWikiEntry,
|
LnxOpenNodeWikiEntry,
|
||||||
LNX_OT_ReplaceNodesOperator,
|
LNX_OT_ReplaceNodesOperator,
|
||||||
|
LNX_OT_RecalculateRotations,
|
||||||
LNX_MT_NodeAddOverride,
|
LNX_MT_NodeAddOverride,
|
||||||
LNX_OT_AddNodeOverride,
|
LNX_OT_AddNodeOverride,
|
||||||
LNX_UL_InterfaceSockets,
|
LNX_UL_InterfaceSockets,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user