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, *args, **kwargs):
 | 
						|
        super(QuaternionMathNode, self).__init__(*args, **kwargs)
 | 
						|
        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) |