Update Files

This commit is contained in:
2025-01-22 16:18:30 +01:00
parent ed4603cf95
commit a36294b518
16718 changed files with 2960346 additions and 0 deletions

View File

@ -0,0 +1,58 @@
from lnx.logicnode.lnx_nodes import *
class BitwiseMathNode(LnxLogicTreeNode):
"""Perform bitwise math on integer values."""
bl_idname = 'LNBitwiseMathNode'
bl_label = 'Bitwise Math'
lnx_version = 1
operators = {
'negation': '~',
'and': '&',
'or': '|',
'xor': '^',
'left_shift': '<<',
'right_shift': '>>',
'unsigned_right_shift': '>>>'
}
def set_mode(self, context):
if self.property0 == 'negation':
self.inputs[0].name = 'Operand'
self.inputs.remove(self.inputs[1])
else:
self.inputs[0].name = 'Operand 1'
if len(self.inputs) < 2:
self.add_input('LnxIntSocket', 'Operand 2')
property0: HaxeEnumProperty(
'property0',
items=[
('negation', 'Negation (~)', 'Performs bitwise negation on the input, so a 0-bit becomes a 1-bit and vice versa'),
None,
('and', 'And (&)', 'A bit in the result is 1 if both bits at the same digit in the operands are 1, else it is 0'),
('or', 'Or (|)', 'A bit in the result is 1 if at least one bit at the same digit in the operands is 1, else it is 0'),
('xor', 'Xor (^)', 'A bit in the result is 1 if exactly one bit at the same digit in the operands is 1, else it is 0'),
None,
('left_shift', 'Left Shift (<<)', 'Shifts the bits of operand 1 to the left by the amount of operand 2. The result is undefined if operand 2 is negative'),
('right_shift', 'Right Shift (>>)', 'Shifts the bits of operand 1 to the right by the amount of operand 2 and keeps the sign of operand 1 (the most significant bit does not change). The result is undefined if operand 2 is negative'),
('unsigned_right_shift', 'Unsigned Right Shift (>>>)', 'Shifts the bits of operand 1 to the right by the amount of operand 2, and the most significant bit is set to 0. The result is undefined if operand 2 is negative'),
],
name='Operation',
description='The operation to perform on the input(s)',
default='negation',
update=set_mode
)
def lnx_init(self, context):
self.add_input('LnxIntSocket', 'Operand 1')
self.add_output('LnxIntSocket', 'Result')
self.set_mode(context)
def draw_buttons(self, context, layout):
layout.prop(self, 'property0', text='')
def draw_label(self) -> str:
return f'{self.bl_label}: {self.operators[self.property0]}'

View File

@ -0,0 +1,17 @@
from lnx.logicnode.lnx_nodes import *
class ClampNode(LnxLogicTreeNode):
"""Keeps the value inside the given bound.
@seeNode Map Range
"""
bl_idname = 'LNClampNode'
bl_label = 'Clamp'
lnx_version = 1
def lnx_init(self, context):
self.add_input('LnxFloatSocket', 'Value')
self.add_input('LnxFloatSocket', 'Min')
self.add_input('LnxFloatSocket', 'Max')
self.add_output('LnxFloatSocket', 'Result')

View File

@ -0,0 +1,24 @@
from lnx.logicnode.lnx_nodes import *
class ColorMixNode(LnxLogicTreeNode):
"""Mix 2 colors:
@input Color: Fist color to mix.
@input Color: Second color to mix.
@input Mix: Mix factor from 0 to 1.
@output Color: Mixed color.
@see https://github.com/rvanwijnen/spectral.js 2023 Ronald van Wijnen.
"""
bl_idname = 'LNColorMixNode'
bl_label = 'Color Mix'
lnx_version = 1
def lnx_init(self, context):
self.add_input('LnxColorSocket', 'Color In', default_value=[1.0, 1.0, 1.0, 1.0])
self.add_input('LnxColorSocket', 'Color In', default_value=[1.0, 1.0, 1.0, 1.0])
self.add_input('LnxFloatSocket', 'Mix', default_value= 0.5)
self.add_output('LnxColorSocket', 'Color Out', is_var=True)

View File

@ -0,0 +1,20 @@
from lnx.logicnode.lnx_nodes import *
class CombineColorNode(LnxLogicTreeNode):
"""Combines the given HSVA() components to a color value.
If any input is `null`, the respective channel of the output color is set to `0.0`.
formula from // https://stackoverflow.com/a/17243070
"""
bl_idname = 'LNCombineColorHSVNode'
bl_label = 'Combine HSVA'
lnx_section = 'color'
lnx_version = 1
def lnx_init(self, context):
self.add_input('LnxFloatSocket', 'H', default_value=0.0)
self.add_input('LnxFloatSocket', 'S', default_value=0.0)
self.add_input('LnxFloatSocket', 'V', default_value=0.0)
self.add_input('LnxFloatSocket', 'A', default_value=1.0)
self.add_output('LnxColorSocket', 'Color', default_value=[1.0, 1.0, 1.0, 1.0])

View File

@ -0,0 +1,19 @@
from lnx.logicnode.lnx_nodes import *
class CombineColorNode(LnxLogicTreeNode):
"""Combines the given RGBA (red, green, blue, and alpha) components to a color value.
If any input is `null`, the respective channel of the output color is set to `0.0`.
"""
bl_idname = 'LNCombineColorNode'
bl_label = 'Combine RGBA'
lnx_section = 'color'
lnx_version = 1
def lnx_init(self, context):
self.add_input('LnxFloatSocket', 'R', default_value=0.0)
self.add_input('LnxFloatSocket', 'G', default_value=0.0)
self.add_input('LnxFloatSocket', 'B', default_value=0.0)
self.add_input('LnxFloatSocket', 'A', default_value=1.0)
self.add_output('LnxColorSocket', 'Color', default_value=[1.0, 1.0, 1.0, 1.0])

View File

@ -0,0 +1,67 @@
from lnx.logicnode.lnx_nodes import *
def remove_extra_inputs(self, context):
if not any(p == self.property0 for p in ['Or', 'And']):
while len(self.inputs) > self.min_inputs:
self.inputs.remove(self.inputs[-1])
if self.property0 == 'Between':
self.add_input('LnxDynamicSocket', 'Input 3')
class CompareNode(LnxLogicTreeNode):
"""Compares values."""
bl_idname = 'LNCompareNode'
bl_label = 'Compare'
lnx_version = 3
property0: HaxeEnumProperty(
'property0',
items = [('Equal', 'Equal', 'Equal'),
('Not Equal', 'Not Equal', 'Not Equal'),
('Almost Equal', 'Almost Equal', 'Almost Equal'),
('Greater', 'Greater', 'Greater'),
('Greater Equal', 'Greater Equal', 'Greater Equal'),
('Less', 'Less', 'Less'),
('Less Equal', 'Less Equal', 'Less Equal'),
('Between', 'Between', 'Input 1 Between Input 2 and Input 3 inclusive'),
('Or', 'Or', 'Or'),
('And', 'And', 'And')],
name='', default='Equal',
update=remove_extra_inputs)
min_inputs = 2
property1: HaxeFloatProperty('property1', name='Tolerance', description='Precision for float compare', default=0.0001)
def __init__(self):
super(CompareNode, self).__init__()
array_nodes[str(id(self))] = self
def lnx_init(self, context):
self.add_input('LnxDynamicSocket', 'Value')
self.add_input('LnxDynamicSocket', 'Value')
self.add_output('LnxBoolSocket', 'Bool')
def draw_buttons(self, context, layout):
layout.prop(self, 'property0')
if self.property0 == 'Almost Equal':
layout.prop(self, 'property1')
if any(p == self.property0 for p in ['Or', 'And']):
row = layout.row(align=True)
op = row.operator('lnx.node_add_input', text='New', icon='PLUS', emboss=True)
op.node_index = str(id(self))
op.socket_type = 'LnxDynamicSocket'
column = row.column(align=True)
op = column.operator('lnx.node_remove_input', text='', icon='X', emboss=True)
op.node_index = str(id(self))
if len(self.inputs) == self.min_inputs:
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:
return NodeReplacement(
'LNGateNode', self.lnx_version, 'LNGateNode', 2,
in_socket_mapping={0:0, 1:1, 2:2}, out_socket_mapping={0:0, 1:1}
)

View File

@ -0,0 +1,13 @@
from lnx.logicnode.lnx_nodes import *
class DegToRadNode(LnxLogicTreeNode):
"""Converts degrees to radians."""
bl_idname = 'LNDegToRadNode'
bl_label = 'Deg to Rad'
lnx_version = 1
lnx_section = 'angle'
def lnx_init(self, context):
self.add_input('LnxFloatSocket', 'Degrees')
self.add_output('LnxFloatSocket', 'Radians')

View File

@ -0,0 +1,24 @@
from lnx.logicnode.lnx_nodes import *
class DistanceScreenToWorldSpaceNode(LnxLogicTreeNode):
"""Gets the distance from given screen coordinates to World coordinates.
@input Screen X: screen x position.
@input Screen Y: screen y position.
@input At: World coordinates is a vector position.
@output Distance At: distance result.
"""
bl_idname = 'LNDistanceScreenToWorldSpaceNode'
bl_label = 'Distance Screen to World Space'
lnx_section = 'matrix'
lnx_version = 1
def lnx_init(self, context):
self.add_input('LnxIntSocket', 'Screen X')
self.add_input('LnxIntSocket', 'Screen Y')
self.add_input('LnxVectorSocket', 'At')
self.add_output('LnxFloatSocket', 'Distance at')

View File

@ -0,0 +1,20 @@
from lnx.logicnode.lnx_nodes import *
class FloatDeltaInterpolateNode(LnxLogicTreeNode):
"""Linearly interpolate to a new value with specified interpolation `Rate`.
@input From: Value to interpolate from.
@input To: Value to interpolate to.
@input Delta Time: Delta Time.
@input Rate: Rate of interpolation.
"""
bl_idname = 'LNFloatDeltaInterpolateNode'
bl_label = 'Float Delta Interpolate'
lnx_version = 1
def lnx_init(self, context):
self.add_input('LnxFloatSocket', 'From', default_value=0.0)
self.add_input('LnxFloatSocket', 'To', default_value=1.0)
self.add_input('LnxFloatSocket', 'Delta Time')
self.add_input('LnxFloatSocket', 'Rate')
self.add_output('LnxFloatSocket', 'Result')

View File

@ -0,0 +1,18 @@
from lnx.logicnode.lnx_nodes import *
class KeyInterpolateNode(LnxLogicTreeNode):
"""Linearly interpolate to 1.0 if input is true and interpolate to 0.0 if input is false.
@input Key State: Interpolate to 1.0 if true and 0.0 if false.
@input Init: Initial value in the range 0.0 to 1.0.
@input Rate: Rate of interpolation.
"""
bl_idname = 'LNKeyInterpolateNode'
bl_label = 'Key Interpolate Node'
lnx_version = 1
def lnx_init(self, context):
self.add_input('LnxBoolSocket', 'Key State')
self.add_input('LnxFloatSocket', 'Init', default_value=0.0)
self.add_input('LnxFloatSocket', 'Rate')
self.add_output('LnxFloatSocket', 'Result')

View File

@ -0,0 +1,19 @@
from lnx.logicnode.lnx_nodes import *
class MapRangeNode(LnxLogicTreeNode):
"""Converts the given value from a range to another range.
@seeNode Clamp
"""
bl_idname = 'LNMapRangeNode'
bl_label = 'Map Range'
lnx_version = 1
def lnx_init(self, context):
self.add_input('LnxFloatSocket', 'Value', default_value=1.0)
self.add_input('LnxFloatSocket', 'From Min')
self.add_input('LnxFloatSocket', 'From Max', default_value=1.0)
self.add_input('LnxFloatSocket', 'To Min')
self.add_input('LnxFloatSocket', 'To Max', default_value=1.0)
self.add_output('LnxFloatSocket', 'Result')

View File

@ -0,0 +1,138 @@
from lnx.logicnode.lnx_nodes import *
class MathNode(LnxLogicTreeNode):
"""Mathematical operations on values."""
bl_idname = 'LNMathNode'
bl_label = 'Math'
lnx_version = 3
@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,
'Multiply': 0,
'Divide': 0,
'Sine': 1,
'Cosine': 1,
'Abs': 1,
'Tangent': 1,
'Arcsine': 1,
'Arccosine': 1,
'Arctangent': 1,
'Logarithm': 1,
'Round': 2,
'Floor': 1,
'Ceil': 1,
'Square Root': 1,
'Fract': 1,
'Exponent': 1,
'Max': 2,
'Min': 2,
'Power': 2,
'Arctan2': 2,
'Modulo': 2,
'Less Than': 2,
'Greater Than': 2,
'Ping-Pong': 2
}.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_prev != select_current:
# Many arguments: Add, Subtract, Multiply, Divide
if (self.get_count_in(select_current) == 0):
while (len(self.inputs) < 2):
self.add_input('LnxFloatSocket', 'Value ' + str(len(self.inputs)))
# 2 arguments: Max, Min, Power, Arctan2, Modulo, Less Than, Greater Than, Ping-Pong
if (self.get_count_in(select_current) == 2):
while (len(self.inputs) > 2):
self.inputs.remove(self.inputs.values()[-1])
while (len(self.inputs) < 2):
self.add_input('LnxFloatSocket', 'Value ' + str(len(self.inputs)))
# 1 argument: Sine, Cosine, Abs, Tangent, Arcsine, Arccosine, Arctangent, Logarithm, Round, Floor, Ceil, Square Root, Fract, Exponent
if (self.get_count_in(select_current) == 1):
while (len(self.inputs) > 1):
self.inputs.remove(self.inputs.values()[-1])
self['property0'] = value
if (self.property0 == 'Round'):
self.inputs[1].name = 'Precision'
elif (self.property0 == 'Ping-Pong'):
self.inputs[1].name = 'Scale'
elif (len(self.inputs) > 1): self.inputs[1].name = 'Value 1'
property0: HaxeEnumProperty(
'property0',
items = [('Add', 'Add', 'Add'),
('Multiply', 'Multiply', 'Multiply'),
('Sine', 'Sine', 'Sine'),
('Cosine', 'Cosine', 'Cosine'),
('Max', 'Maximum', 'Max'),
('Min', 'Minimum', 'Min'),
('Abs', 'Absolute', 'Abs'),
('Subtract', 'Subtract', 'Subtract'),
('Divide', 'Divide', 'Divide'),
('Tangent', 'Tangent', 'Tangent'),
('Arcsine', 'Arcsine', 'Arcsine'),
('Arccosine', 'Arccosine', 'Arccosine'),
('Arctangent', 'Arctangent', 'Arctangent'),
('Power', 'Power', 'Power'),
('Logarithm', 'Logarithm', 'Logarithm'),
('Round', 'Round', 'Round (Value 1 precision of decimal places)'),
('Less Than', 'Less Than', 'Less Than'),
('Greater Than', 'Greater Than', 'Greater Than'),
('Modulo', 'Modulo', 'Modulo'),
('Arctan2', 'Arctan2', 'Arctan2'),
('Floor', 'Floor', 'Floor'),
('Ceil', 'Ceil', 'Ceil'),
('Fract', 'Fract', 'Fract'),
('Square Root', 'Square Root', 'Square Root'),
('Exponent', 'Exponent', 'Exponent'),
('Ping-Pong', 'Ping-Pong', 'The output value is moved between 0.0 and the Scale based on the input value')],
name='', default='Add', set=set_enum, get=get_enum)
property1: HaxeBoolProperty('property1', name='Clamp', default=False)
def __init__(self):
array_nodes[str(id(self))] = self
def lnx_init(self, context):
self.add_input('LnxFloatSocket', 'Value 0', default_value=0.0)
self.add_input('LnxFloatSocket', 'Value 1', default_value=0.0)
self.add_output('LnxFloatSocket', 'Result')
def draw_buttons(self, context, layout):
layout.prop(self, 'property1')
layout.prop(self, 'property0')
# Many arguments: Add, Subtract, Multiply, Divide
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))
op.socket_type = 'LnxFloatSocket'
op.name_format = 'Value {0}'
column = row.column(align=True)
op = column.operator('lnx.node_remove_input', text='', icon='X', emboss=True)
op.node_index = str(id(self))
if len(self.inputs) == 2:
column.enabled = False
def draw_label(self) -> str:
return f'{self.bl_label}: {self.property0}'
def get_replacement_node(self, node_tree: bpy.types.NodeTree):
if self.lnx_version not in (0, 2):
raise LookupError()
return NodeReplacement.Identity(self)

View File

@ -0,0 +1,80 @@
from lnx.logicnode.lnx_nodes import *
import re
class MathExpressionNode(LnxLogicTreeNode):
"""Mathematical operations on values."""
bl_idname = 'LNMathExpressionNode'
bl_label = 'Math Expression'
lnx_version = 2
num_params: IntProperty(default=2, min=0)
@staticmethod
def get_variable_name(index):
return chr( range(ord('a'), ord('z')+1)[index] )
def set_exp(self, value):
self['property2'] = value
# TODO: Check expression for errors
self['exp_error'] = False
def get_exp(self):
return self.get('property2', 'a + b')
property0: HaxeBoolProperty('property0', name='Clamp Result', default=False)
property1: HaxeIntProperty('property1', name='Number of Params', default=2)
property2: HaxeStringProperty('property2', name='', description='Math Expression: +, -, *, /, ^, %, (, ), log(a, b), ln(a), abs(a), max(a,b), min(a,b), sin(a), cos(a), tan(a), cot(a), asin(a), acos(a), atan(a), atan2(a,b), pi(), e()', set=set_exp, get=get_exp)
def __init__(self):
super(MathExpressionNode, self).__init__()
self.register_id()
def lnx_init(self, context):
# OUTPUTS:
self.add_output('LnxFloatSocket', 'Result')
# two default parameters at start
self.add_input('LnxFloatSocket', self.get_variable_name(0), default_value=0.0)
self.add_input('LnxFloatSocket', self.get_variable_name(1), default_value=0.0)
def add_sockets(self):
if self.num_params < 26:
self.add_input('LnxFloatSocket', self.get_variable_name(self.num_params), default_value=0.0)
self.num_params += 1
self['property1'] = self.num_params
def remove_sockets(self):
if self.num_params > 0:
self.inputs.remove(self.inputs.values()[-1])
self.num_params -= 1
self['property1'] = self.num_params
def draw_buttons(self, context, layout):
# Clamp Property
layout.prop(self, 'property0')
# Expression Property
row = layout.row(align=True)
column = row.column(align=True)
# TODO:
#column.alert = self['exp_error']
column.prop(self, 'property2', icon='FORCE_HARMONIC')
# Button ADD parameter
row = layout.row(align=True)
column = row.column(align=True)
op = column.operator('lnx.node_call_func', text='Add Param', icon='PLUS', emboss=True)
op.node_index = self.get_id_str()
op.callback_name = 'add_sockets'
if self.num_params == 26:
column.enabled = False
# Button REMOVE parameter
column = row.column(align=True)
op = column.operator('lnx.node_call_func', text='', icon='X', emboss=True)
op.node_index = self.get_id_str()
op.callback_name = 'remove_sockets'
if self.num_params == 0:
column.enabled = False

View File

@ -0,0 +1,70 @@
from lnx.logicnode.lnx_nodes import *
import re
class MathTermNode(LnxLogicTreeNode):
"""Formula for symbolic Math"""
bl_idname = 'LNMathTermNode'
bl_label = 'Math Term'
lnx_version = 0
num_params: IntProperty(default=2, min=0)
property0: HaxeBoolProperty('property0', name='Resolve params', description='Resolve input param values/subterms for output term/transformations', default=False)
def __init__(self):
super(MathTermNode, self).__init__()
self.register_id()
def lnx_init(self, context):
# OUTPUTS:
self.add_output('LnxDynamicSocket', 'Math Term')
self.add_output('LnxDynamicSocket', 'Simplifyed')
self.add_output('LnxDynamicSocket', 'Derivate')
self.add_output('LnxFloatSocket', 'Result')
self.add_output('LnxStringSocket', 'Error')
self.add_output('LnxIntSocket', 'ErrorPos')
# INPUTS:
# HOW to setup a Tooltip here and how to put it above the param-add/remove-buttons into layout ?
self.add_input('LnxStringSocket', 'Math Term', default_value='a+b')
# two default parameters at start
self.add_input('LnxStringSocket', 'Param 0', default_value='a')
self.add_input('LnxDynamicSocket', 'Value / Term 0')
self.add_input('LnxStringSocket', 'Param 1', default_value='b')
self.add_input('LnxDynamicSocket', 'Value / Term 1')
def add_sockets(self):
self.add_input('LnxStringSocket', 'Name ' + str(self.num_params))
#self.add_input('LnxFloatSocket', 'Value ' + str(self.num_params))
self.add_input('LnxDynamicSocket', 'Value / Term ' + str(self.num_params))
self.num_params += 1
def remove_sockets(self):
if self.num_params > 0:
self.inputs.remove(self.inputs.values()[-1])
self.inputs.remove(self.inputs.values()[-1])
self.num_params -= 1
def draw_buttons(self, context, layout):
# Bind values to params Property
layout.prop(self, 'property0')
# Button ADD parameter
row = layout.row(align=True)
column = row.column(align=True)
op = column.operator('lnx.node_call_func', text='Add Param', icon='PLUS', emboss=True)
op.node_index = self.get_id_str()
op.callback_name = 'add_sockets'
# Button REMOVE parameter
column = row.column(align=True)
op = column.operator('lnx.node_call_func', text='', icon='X', emboss=True)
op.node_index = self.get_id_str()
op.callback_name = 'remove_sockets'
if self.num_params == 0:
column.enabled = False

View File

@ -0,0 +1,22 @@
from lnx.logicnode.lnx_nodes import *
class MatrixMathNode(LnxLogicTreeNode):
"""Multiplies matrices."""
bl_idname = 'LNMatrixMathNode'
bl_label = 'Matrix Math'
lnx_section = 'matrix'
lnx_version = 1
property0: HaxeEnumProperty(
'property0',
items = [('Multiply', 'Multiply', 'Multiply')],
name='', default='Multiply')
def lnx_init(self, context):
self.add_input('LnxDynamicSocket', 'Matrix 1')
self.add_input('LnxDynamicSocket', 'Matrix 2')
self.add_output('LnxDynamicSocket', 'Result')
def draw_buttons(self, context, layout):
layout.prop(self, 'property0')

View File

@ -0,0 +1,43 @@
from lnx.logicnode.lnx_nodes import *
class MixNode(LnxLogicTreeNode):
"""Interpolates between the two given values."""
bl_idname = 'LNMixNode'
bl_label = 'Mix'
lnx_version = 1
property0: HaxeEnumProperty(
'property0',
items = [('Linear', 'Linear', 'Linear'),
('Sine', 'Sine', 'Sine'),
('Quad', 'Quad', 'Quad'),
('Cubic', 'Cubic', 'Cubic'),
('Quart', 'Quart', 'Quart'),
('Quint', 'Quint', 'Quint'),
('Expo', 'Expo', 'Expo'),
('Circ', 'Circ', 'Circ'),
('Back', 'Back', 'Back'),
('Bounce', 'Bounce', 'Bounce'),
('Elastic', 'Elastic', 'Elastic'),
],
name='', default='Linear')
property1: HaxeEnumProperty(
'property1',
items = [('In', 'In', 'In'),
('Out', 'Out', 'Out'),
('InOut', 'InOut', 'InOut'),
],
name='', default='Out')
property2: HaxeBoolProperty('property2', name='Clamp', default=False)
def lnx_init(self, context):
self.add_input('LnxFloatSocket', 'Factor', default_value=0.0)
self.add_input('LnxFloatSocket', 'Value 1', default_value=0.0)
self.add_input('LnxFloatSocket', 'Value 2', default_value=1.0)
self.add_output('LnxFloatSocket', 'Result')
def draw_buttons(self, context, layout):
layout.prop(self, 'property2')
layout.prop(self, 'property0')
layout.prop(self, 'property1')

View File

@ -0,0 +1,46 @@
from lnx.logicnode.lnx_nodes import *
class VectorMixNode(LnxLogicTreeNode):
"""Interpolates between the two given vectors."""
bl_idname = 'LNVectorMixNode'
bl_label = 'Mix Vector'
lnx_section = 'vector'
lnx_version = 1
property0: HaxeEnumProperty(
'property0',
items = [('Linear', 'Linear', 'Linear'),
('Sine', 'Sine', 'Sine'),
('Quad', 'Quad', 'Quad'),
('Cubic', 'Cubic', 'Cubic'),
('Quart', 'Quart', 'Quart'),
('Quint', 'Quint', 'Quint'),
('Expo', 'Expo', 'Expo'),
('Circ', 'Circ', 'Circ'),
('Back', 'Back', 'Back'),
('Bounce', 'Bounce', 'Bounce'),
('Elastic', 'Elastic', 'Elastic'),
],
name='', default='Linear')
property1: HaxeEnumProperty(
'property1',
items = [('In', 'In', 'In'),
('Out', 'Out', 'Out'),
('InOut', 'InOut', 'InOut'),
],
name='', default='Out')
property2: HaxeBoolProperty('property2', name='Clamp', default=False)
def lnx_init(self, context):
self.add_input('LnxFloatSocket', 'Factor', default_value=0.0)
self.add_input('LnxVectorSocket', 'Vector 1', default_value=[0.0, 0.0, 0.0])
self.add_input('LnxVectorSocket', 'Vector 2', default_value=[1.0, 1.0, 1.0])
self.add_output('LnxVectorSocket', 'Result')
def draw_buttons(self, context, layout):
layout.prop(self, 'property2')
layout.prop(self, 'property0')
if self.property0 != 'Linear':
layout.prop(self, 'property1')

View File

@ -0,0 +1,369 @@
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)

View File

@ -0,0 +1,13 @@
from lnx.logicnode.lnx_nodes import *
class RadToDegNode(LnxLogicTreeNode):
"""Converts radians to degrees."""
bl_idname = 'LNRadToDegNode'
bl_label = 'Rad to Deg'
lnx_version = 1
lnx_section = 'angle'
def lnx_init(self, context):
self.add_input('LnxFloatSocket', 'Radians')
self.add_output('LnxFloatSocket', 'Degrees')

View File

@ -0,0 +1,121 @@
from lnx.logicnode.lnx_nodes import *
from mathutils import Vector
class RotationMathNode(LnxLogicTreeNode):
"""Mathematical operations on rotations."""
bl_idname = 'LNRotationMathNode'
bl_label = 'Rotation Math'
bl_description = 'Mathematical operations that can be performed on rotations, no matter their internal representation'
lnx_section = 'quaternions'
lnx_version = 1
@staticmethod
def get_count_in(operation_name):
return {
'Inverse': 1,
'Normalize': 1,
'Compose': 2,
'Amplify': 2,
'FromTo': 2,
#'FromRotationMat': 2,
'Lerp': 3,
'Slerp': 3,
}.get(operation_name, 0)
def ensure_input_socket(self, socket_number, newclass, newname):
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
self.inputs.remove(self.inputs[socket_number])
else:
source_socket = None
self.inputs.new(newclass, newname)
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)
def on_property_update(self, context):
# Checking the selection of another operation
# Rotation as argument 0:
if self.property0 in ('Inverse','Normalize','Amplify'):
self.ensure_input_socket(0, "LnxRotationSocket", "Rotation")
self.ensure_input_socket(1, "LnxFloatSocket", "Amplification factor")
elif self.property0 in ('Slerp','Lerp','Compose'):
self.ensure_input_socket(0, "LnxRotationSocket", "From")
self.ensure_input_socket(1, "LnxRotationSocket", "To")
if self.property0 == 'Compose':
self.inputs[0].name = 'Outer rotation'
self.inputs[1].name = 'Inner rotation'
else:
self.ensure_input_socket(2, "LnxFloatSocket", "Interpolation factor")
elif self.property0 == 'FromTo':
self.ensure_input_socket(0, "LnxVectorSocket", "From")
self.ensure_input_socket(1, "LnxVectorSocket", "To")
# Rotation as argument 1:
if self.property0 in ('Compose','Lerp','Slerp'):
if self.inputs[1].bl_idname != "LnxRotationSocket":
self.replace_input_socket(1, "LnxRotationSocket", "Rotation 2")
if self.property0 == 'Compose':
self.inputs[1].name = "Inner quaternion"
# Float as argument 1:
if self.property0 == 'Amplify':
if self.inputs[1].bl_idname != 'LnxFloatSocket':
self.replace_input_socket(1, "LnxFloatSocket", "Amplification factor")
# Vector as argument 1:
#if self.property0 == 'FromRotationMat':
# # WHAT??
# pass
while len(self.inputs) > self.get_count_in(self.property0):
self.inputs.remove(self.inputs[len(self.inputs)-1])
property0: HaxeEnumProperty(
'property0',
items = [('Compose', 'Compose (multiply)', 'compose (multiply) two rotations. Note that order of the composition matters.'),
('Amplify', 'Amplify (multiply by float)', 'Amplify or diminish the effect of a rotation'),
#('Normalize', 'Normalize', 'Normalize'),
('Inverse', 'Get Inverse', 'from r, get the rotation r2 so that " r×r2=r2×r= <no rotation>" '),
('Lerp', 'Lerp', 'Linearly interpolation'),
('Slerp', 'Slerp', 'Spherical linear interpolation'),
('FromTo', 'From To', 'From direction To direction'),
#('FromRotationMat', 'From Rotation Mat', 'From Rotation Mat')
],
name='', default='Compose', update=on_property_update)
#def __init__(self):
# array_nodes[str(id(self))] = self
def lnx_init(self, context):
self.add_input('LnxRotationSocket', 'Outer rotation', default_value=(0.0, 0.0, 0.0, 1.0) )
self.add_input('LnxRotationSocket', 'Inner rotation', default_value=(0.0, 0.0, 0.0, 1.0) )
self.add_output('LnxRotationSocket', 'Result')
def draw_buttons(self, context, layout):
layout.prop(self, 'property0') # Operation

View File

@ -0,0 +1,61 @@
from lnx.logicnode.lnx_nodes import *
class ScreenToWorldSpaceNode(LnxLogicTreeNode):
"""Transforms the given screen coordinates into World coordinates.
@input Screen X: screen x position.
@input Screen Y: screen y position.
@input Distance at: distance from camera to the result vector position.
Try between 0 and 1.
@output Screen At: result vector position.
@output Screen Word: origin position of the ray emitted from camera.
@output Screen Direction: ray direction.
"""
bl_idname = 'LNScreenToWorldSpaceNode'
bl_label = 'Screen to World Space'
lnx_section = 'matrix'
lnx_version = 2
max_outputs = 9
property0: HaxeBoolProperty('property0', name='Separator Out', default=False)
def lnx_init(self, context):
self.add_input('LnxIntSocket', 'Screen X')
self.add_input('LnxIntSocket', 'Screen Y')
self.add_input('LnxFloatSocket', 'Distance at', default_value = 0.1)
self.add_output('LnxVectorSocket', 'At')
self.add_output('LnxVectorSocket', 'Origin')
self.add_output('LnxVectorSocket', 'Direction')
def draw_buttons(self, context, layout):
layout.prop(self, 'property0') # Separator Out
if self.property0:
if len(self.outputs) < self.max_outputs:
self.outputs.remove(self.outputs.values()[-1]) # Direction vector
self.add_output('LnxFloatSocket', 'X') # Origin X
self.add_output('LnxFloatSocket', 'Y') # Origin Y
self.add_output('LnxFloatSocket', 'Z') # Origin Z
self.add_output('LnxVectorSocket', 'Direction') # Vector
self.add_output('LnxFloatSocket', 'X') # Direction X
self.add_output('LnxFloatSocket', 'Y') # Direction Y
self.add_output('LnxFloatSocket', 'Z') # Direction Z
else:
if len(self.outputs) == self.max_outputs:
self.outputs.remove(self.outputs.values()[-1]) # Z
self.outputs.remove(self.outputs.values()[-1]) # Y
self.outputs.remove(self.outputs.values()[-1]) # X
self.outputs.remove(self.outputs.values()[-1]) # Direction
self.outputs.remove(self.outputs.values()[-1]) # Z
self.outputs.remove(self.outputs.values()[-1]) # Y
self.outputs.remove(self.outputs.values()[-1]) # X
self.add_output('LnxVectorSocket', 'Direction')
def get_replacement_node(self, node_tree: bpy.types.NodeTree):
if self.lnx_version not in (0, 1):
raise LookupError()
return NodeReplacement.Identity(self)

View File

@ -0,0 +1,20 @@
from lnx.logicnode.lnx_nodes import *
class SeparateColorHSVNode(LnxLogicTreeNode):
"""Splits the given color into its HSVA components (hue, saturation, value, and alpha).
If the input color is `null`, the outputs are each set to `0.0`.
formula from: https://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c
"""
bl_idname = 'LNSeparateColorHSVNode'
bl_label = 'Separate HSVA'
lnx_section = 'color'
lnx_version = 1
def lnx_init(self, context):
self.add_input('LnxColorSocket', 'Color', default_value=[1.0, 1.0, 1.0, 1.0])
self.add_output('LnxFloatSocket', 'H')
self.add_output('LnxFloatSocket', 'S')
self.add_output('LnxFloatSocket', 'V')
self.add_output('LnxFloatSocket', 'A')

View File

@ -0,0 +1,25 @@
from lnx.logicnode.lnx_nodes import *
class SeparateColorNode(LnxLogicTreeNode):
"""Splits the given color into its RGBA components (red, green, blue, and alpha).
If the input color is `null`, the outputs are each set to `0.0`.
"""
bl_idname = 'LNSeparateColorNode'
bl_label = 'Separate RGBA'
lnx_section = 'color'
lnx_version = 2
def lnx_init(self, context):
self.add_input('LnxColorSocket', 'Color', default_value=[1.0, 1.0, 1.0, 1.0])
self.add_output('LnxFloatSocket', 'R')
self.add_output('LnxFloatSocket', 'G')
self.add_output('LnxFloatSocket', 'B')
self.add_output('LnxFloatSocket', 'A')
def get_replacement_node(self, node_tree: bpy.types.NodeTree):
if self.lnx_version not in (0, 1):
raise LookupError()
return NodeReplacement.Identity(self)

View File

@ -0,0 +1,15 @@
from lnx.logicnode.lnx_nodes import *
class SeparateVectorNode(LnxLogicTreeNode):
"""Splits the given vector into X, Y and Z."""
bl_idname = 'LNSeparateVectorNode'
bl_label = 'Separate XYZ'
lnx_section = 'vector'
lnx_version = 1
def lnx_init(self, context):
self.add_input('LnxVectorSocket', 'Vector')
self.add_output('LnxFloatSocket', 'X')
self.add_output('LnxFloatSocket', 'Y')
self.add_output('LnxFloatSocket', 'Z')

View File

@ -0,0 +1,67 @@
from lnx.logicnode.lnx_nodes import *
class TweenFloatNode(LnxLogicTreeNode):
"""Tween a float value.
@input Start: Start tweening
@input Stop: Stop a tweening. tweening can be re-started via the `Start`input
@input From: Tween start value
@input To: Tween final value
@input Duration: Duartion of the tween in seconds
@output Out: Executed immidiately after `Start` or `Stop` is called
@output Tick: Executed at every time step in the tween duration
@output Done: Executed when tween is successfully completed. Not executed if tweening is stopped mid-way
@output Value: Current tween value
"""
bl_idname = 'LNTweenFloatNode'
bl_label = 'Tween Float'
lnx_version = 1
property0: HaxeEnumProperty(
'property0',
items = [('Linear', 'Linear', 'Linear'),
('SineIn', 'SineIn', 'SineIn'),
('SineOut', 'SineOut', 'SineOut'),
('SineInOut', 'SineInOut', 'SineInOut'),
('QuadIn', 'QuadIn', 'QuadIn'),
('QuadOut', 'QuadOut', 'QuadOut'),
('QuadInOut', 'QuadInOut', 'QuadInOut'),
('CubicIn', 'CubicIn', 'CubicIn'),
('CubicOut', 'CubicOut', 'CubicOut'),
('CubicInOut', 'CubicInOut', 'CubicInOut'),
('QuartIn', 'QuartIn', 'QuartIn'),
('QuartOut', 'QuartOut', 'QuartOut'),
('QuartInOut', 'QuartInOut', 'QuartInOut'),
('QuintIn', 'QuintIn', 'QuintIn'),
('QuintOut', 'QuintOut', 'QuintOut'),
('QuintInOut', 'QuintInOut', 'QuintInOut'),
('ExpoIn', 'ExpoIn', 'ExpoIn'),
('ExpoOut', 'ExpoOut', 'ExpoOut'),
('ExpoInOut', 'ExpoInOut', 'ExpoInOut'),
('CircIn', 'CircIn', 'CircIn'),
('CircOut', 'CircOut', 'CircOut'),
('CircInOut', 'CircInOut', 'CircInOut'),
('BackIn', 'BackIn', 'BackIn'),
('BackOut', 'BackOut', 'BackOut'),
('BackInOut', 'BackInOut', 'BackInOut')],
name='', default='Linear')
def lnx_init(self, context):
self.add_input('LnxNodeSocketAction', 'Start')
self.add_input('LnxNodeSocketAction', 'Stop')
self.add_input('LnxFloatSocket', 'From', default_value=0.0)
self.add_input('LnxFloatSocket', 'To', default_value=0.0)
self.add_input('LnxFloatSocket', 'Duration', default_value=1.0)
self.add_output('LnxNodeSocketAction', 'Out')
self.add_output('LnxNodeSocketAction', 'Tick')
self.add_output('LnxNodeSocketAction', 'Done')
self.add_output('LnxFloatSocket', 'Value')
def draw_buttons(self, context, layout):
layout.prop(self, 'property0')
def draw_label(self) -> str:
return f'{self.bl_label}: {self.property0}'

View File

@ -0,0 +1,67 @@
from lnx.logicnode.lnx_nodes import *
class TweenFloatNode(LnxLogicTreeNode):
"""Tween rotation.
@input Start: Start tweening
@input Stop: Stop a tweening. tweening can be re-started via the `Start`input
@input From: Tween start value
@input To: Tween final value
@input Duration: Duartion of the tween in seconds
@output Out: Executed immidiately after `Start` or `Stop` is called
@output Tick: Executed at every time step in the tween duration
@output Done: Executed when tween is successfully completed. Not executed if tweening is stopped mid-way
@output Value: Current tween value
"""
bl_idname = 'LNTweenRotationNode'
bl_label = 'Tween Rotation'
lnx_version = 1
property0: HaxeEnumProperty(
'property0',
items = [('Linear', 'Linear', 'Linear'),
('SineIn', 'SineIn', 'SineIn'),
('SineOut', 'SineOut', 'SineOut'),
('SineInOut', 'SineInOut', 'SineInOut'),
('QuadIn', 'QuadIn', 'QuadIn'),
('QuadOut', 'QuadOut', 'QuadOut'),
('QuadInOut', 'QuadInOut', 'QuadInOut'),
('CubicIn', 'CubicIn', 'CubicIn'),
('CubicOut', 'CubicOut', 'CubicOut'),
('CubicInOut', 'CubicInOut', 'CubicInOut'),
('QuartIn', 'QuartIn', 'QuartIn'),
('QuartOut', 'QuartOut', 'QuartOut'),
('QuartInOut', 'QuartInOut', 'QuartInOut'),
('QuintIn', 'QuintIn', 'QuintIn'),
('QuintOut', 'QuintOut', 'QuintOut'),
('QuintInOut', 'QuintInOut', 'QuintInOut'),
('ExpoIn', 'ExpoIn', 'ExpoIn'),
('ExpoOut', 'ExpoOut', 'ExpoOut'),
('ExpoInOut', 'ExpoInOut', 'ExpoInOut'),
('CircIn', 'CircIn', 'CircIn'),
('CircOut', 'CircOut', 'CircOut'),
('CircInOut', 'CircInOut', 'CircInOut'),
('BackIn', 'BackIn', 'BackIn'),
('BackOut', 'BackOut', 'BackOut'),
('BackInOut', 'BackInOut', 'BackInOut')],
name='', default='Linear')
def lnx_init(self, context):
self.add_input('LnxNodeSocketAction', 'Start')
self.add_input('LnxNodeSocketAction', 'Stop')
self.add_input('LnxRotationSocket', 'From')
self.add_input('LnxRotationSocket', 'To')
self.add_input('LnxFloatSocket', 'Duration', default_value=1.0)
self.add_output('LnxNodeSocketAction', 'Out')
self.add_output('LnxNodeSocketAction', 'Tick')
self.add_output('LnxNodeSocketAction', 'Done')
self.add_output('LnxRotationSocket', 'Value')
def draw_buttons(self, context, layout):
layout.prop(self, 'property0')
def draw_label(self) -> str:
return f'{self.bl_label}: {self.property0}'

View File

@ -0,0 +1,67 @@
from lnx.logicnode.lnx_nodes import *
class TweenTransformNode(LnxLogicTreeNode):
"""Tween Transform.
@input Start: Start tweening
@input Stop: Stop a tweening. tweening can be re-started via the `Start`input
@input From: Tween start value
@input To: Tween final value
@input Duration: Duartion of the tween in seconds
@output Out: Executed immidiately after `Start` or `Stop` is called
@output Tick: Executed at every time step in the tween duration
@output Done: Executed when tween is successfully completed. Not executed if tweening is stopped mid-way
@output Value: Current tween value
"""
bl_idname = 'LNTweenTransformNode'
bl_label = 'Tween Transform'
lnx_version = 1
property0: HaxeEnumProperty(
'property0',
items = [('Linear', 'Linear', 'Linear'),
('SineIn', 'SineIn', 'SineIn'),
('SineOut', 'SineOut', 'SineOut'),
('SineInOut', 'SineInOut', 'SineInOut'),
('QuadIn', 'QuadIn', 'QuadIn'),
('QuadOut', 'QuadOut', 'QuadOut'),
('QuadInOut', 'QuadInOut', 'QuadInOut'),
('CubicIn', 'CubicIn', 'CubicIn'),
('CubicOut', 'CubicOut', 'CubicOut'),
('CubicInOut', 'CubicInOut', 'CubicInOut'),
('QuartIn', 'QuartIn', 'QuartIn'),
('QuartOut', 'QuartOut', 'QuartOut'),
('QuartInOut', 'QuartInOut', 'QuartInOut'),
('QuintIn', 'QuintIn', 'QuintIn'),
('QuintOut', 'QuintOut', 'QuintOut'),
('QuintInOut', 'QuintInOut', 'QuintInOut'),
('ExpoIn', 'ExpoIn', 'ExpoIn'),
('ExpoOut', 'ExpoOut', 'ExpoOut'),
('ExpoInOut', 'ExpoInOut', 'ExpoInOut'),
('CircIn', 'CircIn', 'CircIn'),
('CircOut', 'CircOut', 'CircOut'),
('CircInOut', 'CircInOut', 'CircInOut'),
('BackIn', 'BackIn', 'BackIn'),
('BackOut', 'BackOut', 'BackOut'),
('BackInOut', 'BackInOut', 'BackInOut')],
name='', default='Linear')
def lnx_init(self, context):
self.add_input('LnxNodeSocketAction', 'Start')
self.add_input('LnxNodeSocketAction', 'Stop')
self.add_input('LnxDynamicSocket', 'From')
self.add_input('LnxDynamicSocket', 'To')
self.add_input('LnxFloatSocket', 'Duration', default_value=1.0)
self.add_output('LnxNodeSocketAction', 'Out')
self.add_output('LnxNodeSocketAction', 'Tick')
self.add_output('LnxNodeSocketAction', 'Done')
self.add_output('LnxDynamicSocket', 'Value')
def draw_buttons(self, context, layout):
layout.prop(self, 'property0')
def draw_label(self) -> str:
return f'{self.bl_label}: {self.property0}'

View File

@ -0,0 +1,67 @@
from lnx.logicnode.lnx_nodes import *
class TweenVectorNode(LnxLogicTreeNode):
"""Tween a vector value.
@input Start: Start tweening
@input Stop: Stop a tweening. tweening can be re-started via the `Start`input
@input From: Tween start value
@input To: Tween final value
@input Duration: Duartion of the tween in seconds
@output Out: Executed immidiately after `Start` or `Stop` is called
@output Tick: Executed at every time step in the tween duration
@output Done: Executed when tween is successfully completed. Not executed if tweening is stopped mid-way
@output Value: Current tween value
"""
bl_idname = 'LNTweenVectorNode'
bl_label = 'Tween Vector'
lnx_version = 1
property0: HaxeEnumProperty(
'property0',
items = [('Linear', 'Linear', 'Linear'),
('SineIn', 'SineIn', 'SineIn'),
('SineOut', 'SineOut', 'SineOut'),
('SineInOut', 'SineInOut', 'SineInOut'),
('QuadIn', 'QuadIn', 'QuadIn'),
('QuadOut', 'QuadOut', 'QuadOut'),
('QuadInOut', 'QuadInOut', 'QuadInOut'),
('CubicIn', 'CubicIn', 'CubicIn'),
('CubicOut', 'CubicOut', 'CubicOut'),
('CubicInOut', 'CubicInOut', 'CubicInOut'),
('QuartIn', 'QuartIn', 'QuartIn'),
('QuartOut', 'QuartOut', 'QuartOut'),
('QuartInOut', 'QuartInOut', 'QuartInOut'),
('QuintIn', 'QuintIn', 'QuintIn'),
('QuintOut', 'QuintOut', 'QuintOut'),
('QuintInOut', 'QuintInOut', 'QuintInOut'),
('ExpoIn', 'ExpoIn', 'ExpoIn'),
('ExpoOut', 'ExpoOut', 'ExpoOut'),
('ExpoInOut', 'ExpoInOut', 'ExpoInOut'),
('CircIn', 'CircIn', 'CircIn'),
('CircOut', 'CircOut', 'CircOut'),
('CircInOut', 'CircInOut', 'CircInOut'),
('BackIn', 'BackIn', 'BackIn'),
('BackOut', 'BackOut', 'BackOut'),
('BackInOut', 'BackInOut', 'BackInOut')],
name='', default='Linear')
def lnx_init(self, context):
self.add_input('LnxNodeSocketAction', 'Start')
self.add_input('LnxNodeSocketAction', 'Stop')
self.add_input('LnxVectorSocket', 'From')
self.add_input('LnxVectorSocket', 'To')
self.add_input('LnxFloatSocket', 'Duration', default_value=1.0)
self.add_output('LnxNodeSocketAction', 'Out')
self.add_output('LnxNodeSocketAction', 'Tick')
self.add_output('LnxNodeSocketAction', 'Done')
self.add_output('LnxVectorSocket', 'Value')
def draw_buttons(self, context, layout):
layout.prop(self, 'property0')
def draw_label(self) -> str:
return f'{self.bl_label}: {self.property0}'

View File

@ -0,0 +1,41 @@
from lnx.logicnode.lnx_nodes import *
class VectorClampToSizeNode(LnxLogicTreeNode):
"""Clamp the vector's value inside the given range and return the result as a new vector.
@option Clamping Mode: Whether to clamp the length of the vector
or the value of each individual component.
"""
bl_idname = 'LNVectorClampToSizeNode'
bl_label = 'Vector Clamp'
lnx_section = 'vector'
lnx_version = 2
property0: HaxeEnumProperty(
'property0',
name='Clamping Mode', default='length',
description='Whether to clamp the length of the vector or the value of each individual component',
items=[
('length', 'Length', 'Clamp the length (magnitude) of the vector'),
('components', 'Components', 'Clamp the individual components of the vector'),
]
)
def draw_buttons(self, context, layout):
col = layout.column()
col.label(text="Clamping Mode:")
col.prop(self, 'property0', expand=True)
def lnx_init(self, context):
self.add_input('LnxVectorSocket', 'Vector In', default_value=[0.0, 0.0, 0.0])
self.add_input('LnxFloatSocket', 'Min')
self.add_input('LnxFloatSocket', 'Max')
self.add_output('LnxVectorSocket', 'Vector Out')
def get_replacement_node(self, node_tree: bpy.types.NodeTree):
if self.lnx_version not in (0, 1):
raise LookupError()
return NodeReplacement.Identity(self)

View File

@ -0,0 +1,152 @@
from lnx.logicnode.lnx_nodes import *
class VectorMathNode(LnxLogicTreeNode):
"""Mathematical operations on vectors."""
bl_idname = 'LNVectorMathNode'
bl_label = 'Vector Math'
lnx_section = 'vector'
lnx_version = 1
def get_bool(self):
return self.get('property1', False)
def set_bool(self, value):
self['property1'] = value
if value:
if self.property0 in ('Length', 'Distance', 'Dot Product'):
self.outputs.remove(self.outputs.values()[-1]) # Distance/Length/Scalar
self.add_output('LnxFloatSocket', 'X') # Result X
self.add_output('LnxFloatSocket', 'Y') # Result Y
self.add_output('LnxFloatSocket', 'Z') # Result Z
if self.property0 == 'Length':
self.add_output('LnxFloatSocket', 'Length') # Length
if self.property0 == 'Distance':
self.add_output('LnxFloatSocket', 'Distance') # Distance
if self.property0 == 'Dot Product':
self.add_output('LnxFloatSocket', 'Scalar') # Scalar
else:
if self.property0 in ('Length', 'Distance', 'Dot Product') and len(self.outputs) > 1:
self.outputs.remove(self.outputs.values()[-1]) # Distance/Length/Scalar
# Remove X, Y, Z
for i in range(3):
if len(self.outputs) > 1:
self.outputs.remove(self.outputs.values()[-1])
else:
break
if self.property0 == 'Length':
self.add_output('LnxFloatSocket', 'Length') # Length
if self.property0 == 'Distance':
self.add_output('LnxFloatSocket', 'Distance') # Distance
if self.property0 == 'Dot Product':
self.add_output('LnxFloatSocket', 'Scalar') # Scalar
property1: HaxeBoolProperty('property1', name='Separator Out', default=False, set=set_bool, get=get_bool)
@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,
'Average': 0,
'Dot Product': 0,
'Cross Product': 0,
'Multiply': 0,
'MultiplyFloats': 0,
'Distance': 2,
'Reflect': 2,
'Normalize': 1,
'Length': 1
}.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_prev != select_current:
if select_prev in ('Distance', 'Length', 'Dot Product'):
self.outputs.remove(self.outputs.values()[-1])
# Many arguments: Add, Subtract, Average, Dot Product, Cross Product, Multiply, MultiplyFloats
if self.get_count_in(select_current) == 0:
if select_current == "MultiplyFloats" or select_prev == "MultiplyFloats":
while (len(self.inputs) > 1):
self.inputs.remove(self.inputs.values()[-1])
if select_current == "MultiplyFloats":
self.add_input('LnxFloatSocket', 'Value ' + str(len(self.inputs)))
else:
while (len(self.inputs) < 2):
self.add_input('LnxVectorSocket', 'Value ' + str(len(self.inputs)))
if select_current == 'Dot Product':
self.add_output('LnxFloatSocket', 'Scalar')
# 2 arguments: Distance, Reflect
if self.get_count_in(select_current) == 2:
count = 2
if select_prev == "MultiplyFloats":
count = 1
while len(self.inputs) > count:
self.inputs.remove(self.inputs.values()[-1])
while len(self.inputs) < 2:
self.add_input('LnxVectorSocket', 'Value ' + str(len(self.inputs)))
if select_current == 'Distance':
self.add_output('LnxFloatSocket', 'Distance')
# 1 argument: Normalize, Length
if self.get_count_in(select_current) == 1:
while len(self.inputs) > 1:
self.inputs.remove(self.inputs.values()[-1])
if select_current == 'Length':
self.add_output('LnxFloatSocket', 'Length')
self['property0'] = value
property0: HaxeEnumProperty(
'property0',
items=[('Add', 'Add', 'Add'),
('Dot Product', 'Dot Product', 'Dot Product'),
('Multiply', 'Multiply', 'Multiply'),
('MultiplyFloats', 'Multiply (Floats)', 'Multiply (Floats)'),
('Normalize', 'Normalize', 'Normalize'),
('Subtract', 'Subtract', 'Subtract'),
('Average', 'Average', 'Average'),
('Cross Product', 'Cross Product', 'Cross Product'),
('Length', 'Length', 'Length'),
('Distance', 'Distance', 'Distance'),
('Reflect', 'Reflect', 'Reflect')],
name='', default='Add', set=set_enum, get=get_enum)
def __init__(self):
array_nodes[str(id(self))] = self
def lnx_init(self, context):
self.add_input('LnxVectorSocket', 'Value 0', default_value=[0.0, 0.0, 0.0])
self.add_input('LnxVectorSocket', 'Value 1', default_value=[0.0, 0.0, 0.0])
self.add_output('LnxVectorSocket', 'Result')
def draw_buttons(self, context, layout):
layout.prop(self, 'property1') # Separator Out
layout.prop(self, 'property0') # 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))
op.name_format = 'Value {0}'
if self.property0 == "MultiplyFloats":
op.socket_type = 'LnxFloatSocket'
else:
op.socket_type = 'LnxVectorSocket'
column = row.column(align=True)
op = column.operator('lnx.node_remove_input', text='', icon='X', emboss=True)
op.node_index = str(id(self))
if len(self.inputs) == 2:
column.enabled = False
def draw_label(self) -> str:
return f'{self.bl_label}: {self.property0}'

View File

@ -0,0 +1,15 @@
from lnx.logicnode.lnx_nodes import *
class VectorMoveTowardsNode(LnxLogicTreeNode):
"""Add a constant value to the given vector until it reach the target vector."""
bl_idname = 'LNVectorMoveTowardsNode'
bl_label = 'Vector Move Towards'
lnx_section = 'vector'
lnx_version = 1
def lnx_init(self, context):
self.add_input('LnxVectorSocket', 'Vector 1', default_value=[0.0, 0.0, 0.0])
self.add_input('LnxVectorSocket', 'Vector 2', default_value=[1.0, 1.0, 1.0])
self.add_input('LnxFloatSocket', 'Delta', default_value=0.1)
self.add_output('LnxVectorSocket', 'Result')

View File

@ -0,0 +1,35 @@
from lnx.logicnode.lnx_nodes import *
class WorldToScreenSpaceNode(LnxLogicTreeNode):
"""Transforms the given world coordinates into screen coordinates,
using the active camera or a selected camera."""
bl_idname = 'LNWorldToScreenSpaceNode'
bl_label = 'World to Screen Space'
lnx_section = 'matrix'
lnx_version = 2
def remove_extra_inputs(self, context):
while len(self.inputs) > 1:
self.inputs.remove(self.inputs[-1])
if self.property0 == 'Selected Camera':
self.add_input('LnxNodeSocketObject', 'Camera')
property0: HaxeEnumProperty(
'property0',
items = [('Active Camera', 'Active Camera', 'Active Camera'),
('Selected Camera', 'Selected Camera', 'Selected Camera')],
name='', default='Active Camera', update=remove_extra_inputs)
def lnx_init(self, context):
self.add_input('LnxVectorSocket', 'World')
self.add_output('LnxVectorSocket', 'Screen')
def draw_buttons(self, context, layout):
layout.prop(self, 'property0')
def get_replacement_node(self, node_tree: bpy.types.NodeTree):
if self.lnx_version not in (0, 1):
raise LookupError()
return NodeReplacement.Identity(self)

View File

@ -0,0 +1,7 @@
from lnx.logicnode.lnx_nodes import add_node_section
add_node_section(name='default', category='Math')
add_node_section(name='angle', category='Math')
add_node_section(name='matrix', category='Math')
add_node_section(name='color', category='Math')
add_node_section(name='vector', category='Math')