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 = 2 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: 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]) 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, '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) 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 arm_init(self, context): 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, '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('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} XYZ;Quaternion {0} W' else: op.name_format = 'Value {0}' if (self.property0 == "MultiplyFloats"): op.socket_type = 'ArmFloatSocket' else: 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)