forked from LeenkxTeam/LNXSDK
369 lines
18 KiB
Python
369 lines
18 KiB
Python
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)
|
|
|
|
|
|
def __init__(self):
|
|
super(QuaternionMathNode, self).__init__()
|
|
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) |