2025-01-22 16:18:30 +01:00
from lnx . logicnode . lnx_nodes import *
from lnx . logicnode . lnx_sockets import LnxRotationSocket as Rotation
class QuaternionMathNode ( LnxLogicTreeNode ) :
""" 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 '
lnx_section = ' quaternions '
lnx_version = 3
def ensure_input_socket ( self , socket_number , newclass , newname , default_value = None ) :
while len ( self . inputs ) < socket_number :
self . inputs . new ( ' LnxFloatSocket ' , ' 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 ] . lnx_socket_type != ' NONE '
) :
default_value = self . inputs [ socket_number ] . default_value_raw
self . inputs . remove ( self . inputs [ socket_number ] )
else :
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 ( ' LnxFloatSocket ' , ' 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 )
@staticmethod
def get_enum_id_value ( obj , prop_name , value ) :
return obj . bl_rna . properties [ prop_name ] . enum_items [ value ] . identifier
@staticmethod
def get_count_in ( operation_name ) :
return {
' Add ' : 0 ,
' Subtract ' : 0 ,
' DotProduct ' : 0 ,
' Multiply ' : 0 ,
' MultiplyFloats ' : 0 ,
' Module ' : 1 ,
' Normalize ' : 1 ,
' GetEuler ' : 1 ,
' FromTo ' : 2 ,
' FromMat ' : 2 ,
' FromRotationMat ' : 2 ,
' ToAxisAngle ' : 2 ,
' Lerp ' : 3 ,
' Slerp ' : 3 ,
' FromAxisAngle ' : 3 ,
' FromEuler ' : 3
} . get ( operation_name , 0 )
def get_enum ( self ) :
return self . get ( ' property0 ' , 0 )
def set_enum ( self , value ) :
# Checking the selection of another operation
select_current = self . get_enum_id_value ( self , ' property0 ' , value )
select_prev = self . property0
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 , ' LnxVectorSocket ' , ' Quaternion %d XYZ ' % i )
self . ensure_input_socket ( 2 * i + 1 , ' LnxFloatSocket ' , ' 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 , ' LnxVectorSocket ' , ' Quaternion XYZ ' )
self . ensure_input_socket ( 1 , ' LnxFloatSocket ' , ' Quaternion W ' , default_value = 1.0 )
for i in range ( max ( len ( self . inputs ) - 2 , 1 ) ) :
self . ensure_input_socket ( i + 2 , ' LnxFloatSocket ' , ' Value %d ' % i )
elif select_current in ( ' Module ' , ' Normalize ' ) :
self . ensure_input_socket ( 0 , ' LnxVectorSocket ' , ' Quaternion XYZ ' )
self . ensure_input_socket ( 1 , ' LnxFloatSocket ' , ' 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 )
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 '
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 ' ) ,
( ' Subtract ' , ' Subtract ' , ' Subtract ' ) ,
( ' DotProduct ' , ' Dot Product ' , ' Dot Product ' ) ,
( ' Multiply ' , ' Multiply ' , ' Multiply ' ) ,
( ' MultiplyFloats ' , ' Multiply (Floats) ' , ' Multiply (Floats) ' ) ,
( ' Module ' , ' Module ' , ' Module ' ) ,
( ' 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 )
2025-04-06 10:20:29 +00:00
def __init__ ( self , * args , * * kwargs ) :
super ( QuaternionMathNode , self ) . __init__ ( * args , * * kwargs )
2025-01-22 16:18:30 +01:00
array_nodes [ str ( id ( self ) ) ] = self
def lnx_init ( self , context ) :
self . add_input ( ' LnxVectorSocket ' , ' Quaternion 0 XYZ ' , default_value = [ 0.0 , 0.0 , 0.0 ] )
self . add_input ( ' LnxFloatSocket ' , ' Quaternion 0 W ' , default_value = 1 )
self . add_input ( ' LnxVectorSocket ' , ' Quaternion 1 XYZ ' , default_value = [ 0.0 , 0.0 , 0.0 ] )
self . add_input ( ' LnxFloatSocket ' , ' Quaternion 1 W ' , default_value = 1 )
self . add_output ( ' LnxVectorSocket ' , ' Result XYZ ' , default_value = [ 0.0 , 0.0 , 0.0 ] )
self . add_output ( ' LnxFloatSocket ' , ' Result W ' , default_value = 1 )
def draw_buttons ( self , context , layout ) :
layout . prop ( self , ' property0_proxy ' ) # Operation
# Buttons
if ( self . get_count_in ( self . property0 ) == 0 ) :
row = layout . row ( align = True )
column = row . column ( align = True )
op = column . operator ( ' lnx.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} XYZ;Quaternion {0} W '
else :
op . name_format = ' Value {0} '
if ( self . property0 == " MultiplyFloats " ) :
op . socket_type = ' LnxFloatSocket '
else :
op . socket_type = ' LnxVectorSocket;LnxFloatSocket '
column = row . column ( align = True )
op = column . operator ( ' lnx.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 ) == 4 :
column . enabled = False
def get_replacement_node ( self , node_tree : bpy . types . NodeTree ) :
if self . lnx_version not in ( 0 , 2 ) :
raise LookupError ( )
if self . lnx_version == 1 or self . lnx_version == 2 :
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 == ' LnxRotationSocket ' :
in2 . default_value_raw = Rotation . convert_to_quaternion (
in1 . default_value , 0 ,
' EulerAngles ' , ' Rad ' , ' XZY '
)
elif in1 . bl_idname in ( ' LnxFloatSocket ' , ' LnxVectorSocket ' ) :
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 == ' LnxVectorSocket ' :
# 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 , ' LnxVectorSocket ' , ' Quaternion %d XYZ ' % ( i_in_1 ) )
newself . ensure_input_socket ( i_in_2 + 1 , ' LnxFloatSocket ' , ' 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 == ' LnxFloatSocket ' :
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 )