Added node replacement routines for previous commits

(and fixed a couple bugs along the way)
This commit is contained in:
niacdoial 2021-08-12 20:58:35 +02:00
parent 9b1393ea41
commit bd67667a6e
14 changed files with 750 additions and 177 deletions

View file

@ -121,7 +121,14 @@ class ArmNodeAddInputButton(bpy.types.Operator):
def execute(self, context): def execute(self, context):
global array_nodes global array_nodes
inps = array_nodes[self.node_index].inputs inps = array_nodes[self.node_index].inputs
inps.new(self.socket_type, self.name_format.format(str(len(inps) + self.index_name_offset)))
socket_types = self.socket_type.split(';')
name_formats = self.name_format.split(';')
assert len(socket_types)==len(name_formats)
format_index = (len(inps) + self.index_name_offset) //len(socket_types)
for socket_type, name_format in zip(socket_types, name_formats):
inps.new(socket_type, name_format.format(str(format_index)))
# Reset to default again for subsequent calls of this operator # Reset to default again for subsequent calls of this operator
self.node_index = '' self.node_index = ''
@ -132,31 +139,34 @@ class ArmNodeAddInputButton(bpy.types.Operator):
return{'FINISHED'} return{'FINISHED'}
class ArmNodeAddInputValueButton(bpy.types.Operator): class ArmNodeAddInputValueButton(bpy.types.Operator):
"""Add new input""" """Add new input"""
bl_idname = 'arm.node_add_input_value' bl_idname = 'arm.node_add_input_value'
bl_label = 'Add Input' bl_label = 'Add Input'
node_index: StringProperty(name='Node Index', default='') node_index: StringProperty(name='Node Index', default='')
socket_type: StringProperty(name='Socket Type', default='NodeSocketShader') socket_type: StringProperty(name='Socket Type', default='NodeSocketShader')
def execute(self, context): def execute(self, context):
global array_nodes global array_nodes
inps = array_nodes[self.node_index].inputs inps = array_nodes[self.node_index].inputs
inps.new(self.socket_type, 'Value') inps.new(self.socket_type, 'Value')
return{'FINISHED'} return{'FINISHED'}
class ArmNodeRemoveInputButton(bpy.types.Operator): class ArmNodeRemoveInputButton(bpy.types.Operator):
"""Remove last input""" """Remove last input"""
bl_idname = 'arm.node_remove_input' bl_idname = 'arm.node_remove_input'
bl_label = 'Remove Input' bl_label = 'Remove Input'
node_index: StringProperty(name='Node Index', default='') node_index: StringProperty(name='Node Index', default='')
count: IntProperty(name='Number of inputs to remove', default=1, min=1)
min_inputs: IntProperty(name='Number of inputs to keep', default=0, min=0)
def execute(self, context): def execute(self, context):
global array_nodes global array_nodes
node = array_nodes[self.node_index] node = array_nodes[self.node_index]
inps = node.inputs inps = node.inputs
min_inps = 0 if not hasattr(node, 'min_inputs') else node.min_inputs min_inps = self.min_inputs if not hasattr(node, 'min_inputs') else node.min_inputs
if len(inps) > min_inps: if len(inps) >= min_inps + self.count:
inps.remove(inps.values()[-1]) for _ in range(self.count):
inps.remove(inps.values()[-1])
return{'FINISHED'} return{'FINISHED'}
class ArmNodeRemoveInputValueButton(bpy.types.Operator): class ArmNodeRemoveInputValueButton(bpy.types.Operator):
@ -164,13 +174,14 @@ class ArmNodeRemoveInputValueButton(bpy.types.Operator):
bl_idname = 'arm.node_remove_input_value' bl_idname = 'arm.node_remove_input_value'
bl_label = 'Remove Input' bl_label = 'Remove Input'
node_index: StringProperty(name='Node Index', default='') node_index: StringProperty(name='Node Index', default='')
target_name: StringProperty(name='Name of socket to remove', default='Value')
def execute(self, context): def execute(self, context):
global array_nodes global array_nodes
node = array_nodes[self.node_index] node = array_nodes[self.node_index]
inps = node.inputs inps = node.inputs
min_inps = 0 if not hasattr(node, 'min_inputs') else node.min_inputs min_inps = 0 if not hasattr(node, 'min_inputs') else node.min_inputs
if len(inps) > min_inps and inps[-1].name == 'Value': if len(inps) > min_inps and inps[-1].name == self.target_name:
inps.remove(inps.values()[-1]) inps.remove(inps.values()[-1])
return{'FINISHED'} return{'FINISHED'}
@ -187,7 +198,14 @@ class ArmNodeAddOutputButton(bpy.types.Operator):
def execute(self, context): def execute(self, context):
global array_nodes global array_nodes
outs = array_nodes[self.node_index].outputs outs = array_nodes[self.node_index].outputs
outs.new(self.socket_type, self.name_format.format(str(len(outs) + self.index_name_offset)))
socket_types = self.socket_type.split(';')
name_formats = self.name_format.split(';')
assert len(socket_types)==len(name_formats)
format_index = (len(outs) + self.index_name_offset) //len(socket_types)
for socket_type, name_format in zip(socket_types, name_formats):
outs.new(socket_type, name_format.format(str(format_index)))
# Reset to default again for subsequent calls of this operator # Reset to default again for subsequent calls of this operator
self.node_index = '' self.node_index = ''
@ -202,14 +220,16 @@ class ArmNodeRemoveOutputButton(bpy.types.Operator):
bl_idname = 'arm.node_remove_output' bl_idname = 'arm.node_remove_output'
bl_label = 'Remove Output' bl_label = 'Remove Output'
node_index: StringProperty(name='Node Index', default='') node_index: StringProperty(name='Node Index', default='')
count: IntProperty(name='Number of outputs to remove', default=1, min=1)
def execute(self, context): def execute(self, context):
global array_nodes global array_nodes
node = array_nodes[self.node_index] node = array_nodes[self.node_index]
outs = node.outputs outs = node.outputs
min_outs = 0 if not hasattr(node, 'min_outputs') else node.min_outputs min_outs = 0 if not hasattr(node, 'min_outputs') else node.min_outputs
if len(outs) > min_outs: if len(outs) >= min_outs + self.count:
outs.remove(outs.values()[-1]) for _ in range(self.count):
outs.remove(outs.values()[-1])
return{'FINISHED'} return{'FINISHED'}
class ArmNodeAddInputOutputButton(bpy.types.Operator): class ArmNodeAddInputOutputButton(bpy.types.Operator):
@ -229,8 +249,21 @@ class ArmNodeAddInputOutputButton(bpy.types.Operator):
node = array_nodes[self.node_index] node = array_nodes[self.node_index]
inps = node.inputs inps = node.inputs
outs = node.outputs outs = node.outputs
inps.new(self.in_socket_type, self.in_name_format.format(str(len(inps) + self.in_index_name_offset)))
outs.new(self.out_socket_type, self.out_name_format.format(str(len(outs)))) in_socket_types = self.in_socket_type.split(';')
in_name_formats = self.in_name_format.split(';')
assert len(in_socket_types)==len(in_name_formats)
out_socket_types = self.out_socket_type.split(';')
out_name_formats = self.out_name_format.split(';')
assert len(out_socket_types)==len(out_name_formats)
in_format_index = (len(outs) + self.index_name_offset) // len(in_socket_types)
out_format_index = len(outs) // len(out_socket_types)
for socket_type, name_format in zip(in_socket_types, in_name_formats):
inps.new(socket_type, name_format.format(str(in_format_index)))
for socket_type, name_format in zip(out_socket_types, out_name_formats):
outs.new(socket_type, name_format.format(str(out_format_index)))
# Reset to default again for subsequent calls of this operator # Reset to default again for subsequent calls of this operator
self.node_index = '' self.node_index = ''
@ -247,7 +280,9 @@ class ArmNodeRemoveInputOutputButton(bpy.types.Operator):
bl_idname = 'arm.node_remove_input_output' bl_idname = 'arm.node_remove_input_output'
bl_label = 'Remove Input Output' bl_label = 'Remove Input Output'
node_index: StringProperty(name='Node Index', default='') node_index: StringProperty(name='Node Index', default='')
in_count: IntProperty(name='Number of inputs to remove', default=1, min=1)
out_count: IntProperty(name='Number of inputs to remove', default=1, min=1)
def execute(self, context): def execute(self, context):
global array_nodes global array_nodes
node = array_nodes[self.node_index] node = array_nodes[self.node_index]
@ -255,10 +290,12 @@ class ArmNodeRemoveInputOutputButton(bpy.types.Operator):
outs = node.outputs outs = node.outputs
min_inps = 0 if not hasattr(node, 'min_inputs') else node.min_inputs min_inps = 0 if not hasattr(node, 'min_inputs') else node.min_inputs
min_outs = 0 if not hasattr(node, 'min_outputs') else node.min_outputs min_outs = 0 if not hasattr(node, 'min_outputs') else node.min_outputs
if len(inps) > min_inps: if len(inps) >= min_inps + self.in_count:
inps.remove(inps.values()[-1]) for _ in range(self.in_count):
if len(outs) > min_outs: inps.remove(inps.values()[-1])
outs.remove(outs.values()[-1]) if len(outs) >= min_outs + self.out_count:
for _ in range(self.out_count):
outs.remove(outs.values()[-1])
return{'FINISHED'} return{'FINISHED'}
@ -428,7 +465,10 @@ def deprecated(*alternatives: str, message=""):
def wrapper(cls: ArmLogicTreeNode) -> ArmLogicTreeNode: def wrapper(cls: ArmLogicTreeNode) -> ArmLogicTreeNode:
cls.bl_label += ' (Deprecated)' cls.bl_label += ' (Deprecated)'
cls.bl_description = f'Deprecated. {cls.bl_description}' if hasattr(cls, 'bl_description'):
cls.bl_description = f'Deprecated. {cls.bl_description}'
else:
cls.bl_description = 'Deprecated.'
cls.bl_icon = 'ERROR' cls.bl_icon = 'ERROR'
cls.arm_is_obsolete = True cls.arm_is_obsolete = True

View file

@ -112,12 +112,16 @@ class ArmRotationSocket(ArmCustomSocket):
self.default_value_s3 = 0.0 self.default_value_s3 = 0.0
self.do_update_raw(context) self.do_update_raw(context)
def do_update_raw(self, context): @staticmethod
if self.default_value_mode == 'Quat': def convert_to_quaternion(part1,part2,param1,param2,param3):
qx = self.default_value_s0 """converts a representation of rotation into a quaternion.
qy = self.default_value_s1 ``part1`` is a vector, ``part2`` is a scalar or None,
qz = self.default_value_s2 ``param1`` is in ('Quat', 'EulerAngles', 'AxisAngle'),
qw = self.default_value_s3 ``param2`` is in ('Rad','Deg') for both EulerAngles and AxisAngle,
``param3`` is a len-3 string like "XYZ", for EulerAngles """
if param1 == 'Quat':
qx, qy, qz = part1[0], part1[1], part1[2]
qw = part2
# need to normalize the quaternion for a rotation (having it be 0 is not an option) # need to normalize the quaternion for a rotation (having it be 0 is not an option)
ql = sqrt(qx**2+qy**2+qz**2+qw**2) ql = sqrt(qx**2+qy**2+qz**2+qw**2)
if abs(ql)<1E-5: if abs(ql)<1E-5:
@ -127,49 +131,61 @@ class ArmRotationSocket(ArmCustomSocket):
qy /= ql qy /= ql
qz /= ql qz /= ql
qw /= ql qw /= ql
self.default_value_raw = mathutils.Vector((qx,qy,qz,qw)) return mathutils.Vector((qx,qy,qz,qw))
elif self.default_value_mode == 'AxisAngle': elif param1 == 'AxisAngle':
if self.default_value_unit == 'Deg': if param2 == 'Deg':
angle = self.default_value_s3 * pi/180 angle = part2 * pi/180
else: else:
angle = self.default_value_s3 angle = part2
cang, sang = cos(angle/2), sin(angle/2) cang, sang = cos(angle/2), sin(angle/2)
x = self.default_value_s0 x,y,z = part1[0], part1[1], part1[2]
y = self.default_value_s1
z = self.default_value_s2
veclen = sqrt(x**2+y**2+z**2) veclen = sqrt(x**2+y**2+z**2)
if veclen<1E-5: if veclen<1E-5:
self.default_value_raw = mathutils.Vector((0.0,0.0,0.0,1.0)) return mathutils.Vector((0.0,0.0,0.0,1.0))
else: else:
self.default_value_raw = mathutils.Vector(( return mathutils.Vector((
x/veclen * sang, x/veclen * sang,
y/veclen * sang, y/veclen * sang,
z/veclen * sang, z/veclen * sang,
cang cang
)) ))
else: else: # param1 == 'EulerAngles'
if self.default_value_unit == 'Deg': x,y,z = part1[0], part1[1], part1[2]
x = self.default_value_s0 * pi/180 if param2 == 'Deg':
y = self.default_value_s1 * pi/180 x *= pi/180
z = self.default_value_s2 * pi/180 y *= pi/180
else: z *= pi/180
x = self.default_value_s0
y = self.default_value_s1
z = self.default_value_s2
cx, sx = cos(x/2), sin(x/2) cx, sx = cos(x/2), sin(x/2)
cy, sy = cos(y/2), sin(y/2) cy, sy = cos(y/2), sin(y/2)
cz, sz = cos(z/2), sin(z/2) cz, sz = cos(z/2), sin(z/2)
qw, qx, qy, qz = 1.0,0.0,0.0,0.0 qw, qx, qy, qz = 1.0,0.0,0.0,0.0
for direction in self.default_value_order[::-1]: for direction in param3[::-1]:
qwi, qxi,qyi,qzi = {'X': (cx,sx,0,0), 'Y': (cy,0,sy,0), 'Z': (cz,0,0,sz)}[direction] qwi, qxi,qyi,qzi = {'X': (cx,sx,0,0), 'Y': (cy,0,sy,0), 'Z': (cz,0,0,sz)}[direction]
qw = qw*qwi -qx*qxi -qy*qyi -qz*qzi qw = qw*qwi -qx*qxi -qy*qyi -qz*qzi
qx = qx*qwi +qw*qxi +qy*qzi -qz*qyi qx = qx*qwi +qw*qxi +qy*qzi -qz*qyi
qy = qy*qwi +qw*qyi +qz*qxi -qx*qzi qy = qy*qwi +qw*qyi +qz*qxi -qx*qzi
qz = qz*qwi +qw*qzi +qx*qyi -qy*qxi qz = qz*qwi +qw*qzi +qx*qyi -qy*qxi
self.default_value_raw = mathutils.Vector((qx,qy,qz,qw)) return mathutils.Vector((qx,qy,qz,qw))
def do_update_raw(self, context):
part1 = mathutils.Vector((
self.default_value_s0,
self.default_value_s1,
self.default_value_s2, 1
))
part2 = self.default_value_s3
self.default_value_raw = self.convert_to_quaternion(
part1,
self.default_value_s3,
self.default_value_mode,
self.defualt_value_unit,
self.default_value_order
)
def draw(self, context, layout, node, text): def draw(self, context, layout, node, text):

View file

@ -4,44 +4,46 @@ class QuaternionMathNode(ArmLogicTreeNode):
"""Mathematical operations on quaternions.""" """Mathematical operations on quaternions."""
bl_idname = 'LNQuaternionMathNode' bl_idname = 'LNQuaternionMathNode'
bl_label = 'Quaternion Math' bl_label = 'Quaternion Math'
bl_description = 'Mathematical operations that can be performed on rotations, when represented as quaternions specifically'
arm_section = 'quaternions' arm_section = 'quaternions'
arm_version = 1 arm_version = 2
def get_bool(self):
return self.get('property1', False)
def set_bool(self, value): def ensure_input_socket(self, socket_number, newclass, newname, default_value=None):
self['property1'] = value while len(self.inputs) < socket_number:
if value: self.inputs.new('NodeSocketFloat', 'BOGUS')
if ((self.property0 == 'Module') or (self.property0 == 'DotProduct') or (self.property0 == 'ToAxisAngle')) and (len(self.outputs) > 1): if len(self.inputs) > socket_number:
self.outputs.remove(self.outputs.values()[-1]) # Module/DotProduct/ToAxisAngle if len(self.inputs[socket_number].links) == 1:
self.add_output('NodeSocketFloat', 'X') # Result X source_socket = self.inputs[socket_number].links[0].from_socket
self.add_output('NodeSocketFloat', 'Y') # Result Y else:
self.add_output('NodeSocketFloat', 'Z') # Result Z source_socket = None
self.add_output('NodeSocketFloat', 'W') # Result W if self.inputs[socket_number].bl_idname == newclass:
if (self.property0 == 'Module'): default_value = self.inputs[socket_number].default_value
self.add_output('NodeSocketFloat', 'Module') # Module self.inputs.remove(self.inputs[socket_number])
if (self.property0 == 'DotProduct'):
self.add_output('NodeSocketFloat', 'Scalar') # DotProduct
if (self.property0 == 'ToAxisAngle'):
self.add_output('NodeSocketFloat', 'To Axis Angle') # ToAxisAngle
else: else:
if ((self.property0 == 'Module') or (self.property0 == 'DotProduct') or (self.property0 == 'ToAxisAngle')) and (len(self.outputs) > 1): source_socket = None
self.outputs.remove(self.outputs.values()[-1]) # Module/DotProduct/ToAxisAngle
# Remove X, Y, Z, W
for i in range(4): self.inputs.new(newclass, newname)
if len(self.outputs) > 1: if default_value != None:
self.outputs.remove(self.outputs.values()[-1]) self.inputs[-1].default_value = default_value
else: self.inputs.move(len(self.inputs)-1, socket_number)
break if source_socket is not None:
if (self.property0 == 'Module'): self.id_data.links.new(source_socket, self.inputs[socket_number])
self.add_output('NodeSocketFloat', 'Module') # Module
if (self.property0 == 'DotProduct'): def ensure_output_socket(self, socket_number, newclass, newname):
self.add_output('NodeSocketFloat', 'Scalar') # DotProduct sink_sockets = []
if (self.property0 == 'ToAxisAngle'): while len(self.outputs) < socket_number:
self.add_output('NodeSocketFloat', 'To Axis Angle') # ToAxisAngle self.outputs.new('NodeSocketFloat', '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])
property1: BoolProperty(name='Separator Out', default=False, set=set_bool, get=get_bool) 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 @staticmethod
def get_enum_id_value(obj, prop_name, value): def get_enum_id_value(obj, prop_name, value):
@ -75,62 +77,40 @@ class QuaternionMathNode(ArmLogicTreeNode):
# Checking the selection of another operation # Checking the selection of another operation
select_current = self.get_enum_id_value(self, 'property0', value) select_current = self.get_enum_id_value(self, 'property0', value)
select_prev = self.property0 select_prev = self.property0
if select_prev != select_current:
# Remove
count = 0
if ((select_prev == 'Add') or (select_prev == 'Subtract') or (select_prev == 'Multiply') or (select_prev == 'DotProduct')) and ((select_current == 'Add') or (select_current == 'Subtract') or (select_current == 'Multiply') or (select_current == 'DotProduct')) or (((select_current == 'Lerp') or (select_current == 'Slerp')) and ((select_prev == 'Lerp') or (select_prev == 'Slerp'))):
count = len(self.inputs)
while (len(self.inputs) > count):
self.inputs.remove(self.inputs.values()[-1])
if (select_prev == 'DotProduct') or (select_prev == 'ToAxisAngle') or (select_prev == 'Module'):
self.outputs.remove(self.outputs.values()[-1])
# Many arguments: Add, Subtract, DotProduct, Multiply, MultiplyFloat
if (self.get_count_in(select_current) == 0):
if (select_current == "MultiplyFloats"):
self.add_input('NodeSocketVector', 'Quaternion ' + str(len(self.inputs)))
self.add_input('NodeSocketFloat', 'Value ' + str(len(self.inputs)))
else:
while (len(self.inputs) < 2):
self.add_input('NodeSocketVector', 'Quaternion ' + str(len(self.inputs)))
if (select_current == 'DotProduct'):
self.add_output('NodeSocketFloat', 'Scalar')
# 3 arguments: Lerp, Slerp, FromAxisAngle, FromEuler
if (self.get_count_in(select_current) == 3):
if (select_current == 'Lerp') or (select_current == 'Slerp'):
while (len(self.inputs) < 3):
self.add_input('NodeSocketVector', 'From')
self.add_input('NodeSocketVector', 'To')
self.add_input('NodeSocketFloat', 'T')
if (select_current == 'FromAxisAngle'):
self.add_input('NodeSocketVector', 'Quaternion')
self.add_input('NodeSocketVector', 'Axis')
self.add_input('NodeSocketFloat', 'Angle')
if (select_current == 'FromEuler'):
self.add_input('NodeSocketFloat', 'X')
self.add_input('NodeSocketFloat', 'Y')
self.add_input('NodeSocketFloat', 'Z')
# 2 arguments: FromTo, FromMat, FromRotationMat, ToAxisAngle
if (self.get_count_in(select_current) == 2):
if (select_current == 'FromTo'):
self.add_input('NodeSocketVector', 'Vector ' + str(len(self.inputs)))
self.add_input('NodeSocketVector', 'Vector ' + str(len(self.inputs)))
if (select_current == 'FromMat') or (select_current == 'FromRotationMat'):
self.add_input('NodeSocketVector', 'Quaternion')
self.add_input('NodeSocketShader', 'Matrix')
if (select_current == 'ToAxisAngle'):
self.add_input('NodeSocketVector', 'Quaternion')
self.add_input('NodeSocketVector', 'Axis')
self.add_output('NodeSocketFloat', 'Angle')
# 1 argument: Module, Normalize, GetEuler
if (self.get_count_in(select_current) == 1): if select_current in ('Add','Subtract','Multiply','DotProduct') \
self.add_input('NodeSocketVector', 'Quaternion') and select_prev in ('Add','Subtract','Multiply','DotProduct'):
if (select_current == 'Module'): pass # same as select_current==select_prev for the sockets
self.add_output('NodeSocketFloat', 'Module') 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, 'NodeSocketVector', 'Quaternion %d XYZ'%i)
self.ensure_input_socket(2*i+1, 'NodeSocketFloat', '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, 'NodeSocketVector', 'Quaternion XYZ')
self.ensure_input_socket(1, 'NodeSocketFloat', 'Quaternion W', default_value=1.0)
for i in range( max(len(self.inputs)-2 ,1) ):
self.ensure_input_socket(i+2, 'NodeSocketFloat', 'Value %d'%i)
elif select_current in ('Module', 'Normalize'):
self.ensure_input_socket(0, 'NodeSocketVector', 'Quaternion XYZ')
self.ensure_input_socket(1, 'NodeSocketFloat', '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'] = value
self['property0_proxy'] = value
property0: EnumProperty( property0: EnumProperty(
items = [('Add', 'Add', 'Add'), items = [('Add', 'Add', 'Add'),
@ -139,30 +119,35 @@ class QuaternionMathNode(ArmLogicTreeNode):
('Multiply', 'Multiply', 'Multiply'), ('Multiply', 'Multiply', 'Multiply'),
('MultiplyFloats', 'Multiply (Floats)', 'Multiply (Floats)'), ('MultiplyFloats', 'Multiply (Floats)', 'Multiply (Floats)'),
('Module', 'Module', 'Module'), ('Module', 'Module', 'Module'),
('Normalize', 'Normalize', 'Normalize'), ('Normalize', 'Normalize', 'Normalize'), #],
('Lerp', 'Lerp', 'Linearly interpolate'), # NOTE: the unused parts need to exist to be read from an old version from the node.
('Slerp', 'Slerp', 'Spherical linear interpolation'), # this is so dumb…
('FromTo', 'From To', 'From To'), ('Lerp', 'DO NOT USE',''),
('FromMat', 'From Mat', 'From Mat'), ('Slerp', 'DO NOT USE',''),
('FromRotationMat', 'From Rotation Mat', 'From Rotation Mat'), ('FromTo', 'DO NOT USE',''),
('ToAxisAngle', 'To Axis Angle', 'To Axis Angle'), ('FromMat', 'DO NOT USE',''),
('FromAxisAngle', 'From Axis Angle', 'From Axis Angle'), ('FromRotationMat', 'DO NOT USE',''),
('FromEuler', 'From Euler', 'From Euler'), ('ToAxisAngle', 'DO NOT USE',''),
('GetEuler', 'To Euler', 'To Euler')], ('FromAxisAngle', 'DO NOT USE',''),
name='', default='Add', set=set_enum, get=get_enum) ('FromEuler', 'DO NOT USE',''),
('GetEuler', 'DO NOT USE','')],
name='', default='Add') #, set=set_enum, get=get_enum)
def __init__(self): def __init__(self):
super(QuaternionMathNode, self).__init__()
array_nodes[str(id(self))] = self array_nodes[str(id(self))] = self
def init(self, context): def init(self, context):
super(QuaternionMathNode, self).init(context) super(QuaternionMathNode, self).init(context)
self.add_input('NodeSocketVector', 'Quaternion 0', default_value=[0.0, 0.0, 0.0]) self.add_input('NodeSocketVector', 'Quaternion 0 XYZ', default_value=[0.0, 0.0, 0.0])
self.add_input('NodeSocketVector', 'Quaternion 1', default_value=[0.0, 0.0, 0.0]) self.add_input('NodeSocketFloat', 'Quaternion 0 W', default_value=1)
self.add_output('NodeSocketVector', 'Result') self.add_input('NodeSocketVector', 'Quaternion 1 XYZ', default_value=[0.0, 0.0, 0.0])
self.add_input('NodeSocketFloat', 'Quaternion 1 W', default_value=1)
self.add_output('NodeSocketVector', 'Result XYZ', default_value=[0.0, 0.0, 0.0])
self.add_output('NodeSocketFloat', 'Result W', default_value=1)
def draw_buttons(self, context, layout): def draw_buttons(self, context, layout):
layout.prop(self, 'property1') # Separator Out layout.prop(self, 'property0_proxy') # Operation
layout.prop(self, 'property0') # Operation
# Buttons # Buttons
if (self.get_count_in(self.property0) == 0): if (self.get_count_in(self.property0) == 0):
row = layout.row(align=True) row = layout.row(align=True)
@ -170,15 +155,201 @@ class QuaternionMathNode(ArmLogicTreeNode):
op = column.operator('arm.node_add_input', text='Add Value', icon='PLUS', emboss=True) op = column.operator('arm.node_add_input', text='Add Value', icon='PLUS', emboss=True)
op.node_index = str(id(self)) op.node_index = str(id(self))
if (self.property0 == 'Add') or (self.property0 == 'Subtract') or (self.property0 == 'Multiply') or (self.property0 == 'DotProduct'): if (self.property0 == 'Add') or (self.property0 == 'Subtract') or (self.property0 == 'Multiply') or (self.property0 == 'DotProduct'):
op.name_format = 'Quaternion {0}' op.name_format = 'Quaternion {0} XYZ;Quaternion {0} W'
else: else:
op.name_format = 'Value {0}' op.name_format = 'Value {0}'
if (self.property0 == "MultiplyFloats"): if (self.property0 == "MultiplyFloats"):
op.socket_type = 'NodeSocketFloat' op.socket_type = 'NodeSocketFloat'
else: else:
op.socket_type = 'NodeSocketVector' op.socket_type = 'NodeSocketVector;NodeSocketFloat'
column = row.column(align=True) column = row.column(align=True)
op = column.operator('arm.node_remove_input', text='', icon='X', emboss=True) op = column.operator('arm.node_remove_input', text='', icon='X', emboss=True)
op.node_index = str(id(self)) 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: if len(self.inputs) == 2:
column.enabled = False 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 == 'ToEuler':
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_node, 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_node, 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_node, 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_node, 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 in1.bl_idname in ('NodeSocketFloat', 'NodeSocketVector'):
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 == 'NodeSocketVector':
# quaternion input: now two sockets, not one.
convnode = node_tree.nodes.new('LNSeparateRotationNode')
convnode.property0 = 'Quaternion'
ret.append(convnode)
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 +=1
i_in_1 +=2
elif in1.bl_idname == 'NodeSocketfloat':
for link in in1.links:
node_tree.links.new(link.from_socket, newself.inputs[i_in_2])
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:
node_tree.links.new(newself.outputs[0], 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)
return ret
# note: keep property1, so that it is actually readable for node conversion.
property1: BoolProperty(name='DEPRECATED', default=False)
# this is the version of property0 that is shown in the interface,
# even though the real property0 is the one used elsewhere.
# NOTE FOR FUTURE MAINTAINERS: the value of this proxy property does **not** matter, only the value of property0 does.
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)

View file

@ -4,6 +4,7 @@ class QuaternionMathNode(ArmLogicTreeNode):
"""Mathematical operations on rotations.""" """Mathematical operations on rotations."""
bl_idname = 'LNRotationMathNode' bl_idname = 'LNRotationMathNode'
bl_label = 'Rotation Math' bl_label = 'Rotation Math'
bl_description = 'Mathematical operations that can be performed on rotations, no matter their internal representation'
arm_section = 'quaternions' arm_section = 'quaternions'
arm_version = 1 arm_version = 1
@ -97,7 +98,7 @@ class QuaternionMathNode(ArmLogicTreeNode):
property0: EnumProperty( property0: EnumProperty(
items = [('Compose', 'Compose (multiply)', 'compose (multiply) two rotations. Note that order of the composition matters.'), 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'), ('Amplify', 'Amplify (multiply by float)', 'Amplify or diminish the effect of a rotation'),
('Normalize', 'Normalize', 'Normalize'), #('Normalize', 'Normalize', 'Normalize'),
('Inverse', 'Get Inverse', 'from r, get the rotation r2 so that " r×r2=r2×r= <no rotation>" '), ('Inverse', 'Get Inverse', 'from r, get the rotation r2 so that " r×r2=r2×r= <no rotation>" '),
('Lerp', 'Lerp', 'Linearly interpolation'), ('Lerp', 'Lerp', 'Linearly interpolation'),
('Slerp', 'Slerp', 'Spherical linear interpolation'), ('Slerp', 'Slerp', 'Spherical linear interpolation'),

View file

@ -1,11 +1,14 @@
from arm.logicnode.arm_nodes import * from arm.logicnode.arm_nodes import *
@deprecated(message='Do not use quaternion sockets')
class SeparateQuaternionNode(ArmLogicTreeNode): class SeparateQuaternionNode(ArmLogicTreeNode):
"""Splits the given quaternion into X, Y, Z and W.""" """Splits the given quaternion into X, Y, Z and W."""
bl_idname = 'LNSeparateQuaternionNode' bl_idname = 'LNSeparateQuaternionNode'
bl_label = "Separate Quaternion" bl_label = "Separate Quaternion (do not use: quaternions sockets have been phased out entirely)"
bl_description = "Separate a quaternion object (transported through a vector socket) into its four compoents."
arm_section = 'quaternions' arm_section = 'quaternions'
arm_version = 1 arm_version = 2 # deprecate
def init(self, context): def init(self, context):
super(SeparateQuaternionNode, self).init(context) super(SeparateQuaternionNode, self).init(context)
@ -15,3 +18,29 @@ class SeparateQuaternionNode(ArmLogicTreeNode):
self.add_output('NodeSocketFloat', 'Y') self.add_output('NodeSocketFloat', 'Y')
self.add_output('NodeSocketFloat', 'Z') self.add_output('NodeSocketFloat', 'Z')
self.add_output('NodeSocketFloat', 'W') self.add_output('NodeSocketFloat', 'W')
def get_replacement_node(self, node_tree: bpy.types.NodeTree):
if self.arm_version not in (0, 1):
raise LookupError()
# transition from version 1 to version 2[deprecated]
newself = node_tree.nodes.new('LNSeparateRotationNode')
separator = node_tree.nodes.new('LNSeparateVectorNode')
newself.property0 = 'Quaternion'
newself.property1 = 'Rad' # bogus
newself.property2 = 'XYZ' # bogus
for link in self.inputs[0].links:
node_tree.links.new(link.from_socket, newself.inputs[0])
node_tree.links.new(newself.outputs[0], separator.inputs[0])
for link in self.outputs[0].links:
node_tree.links.new(separator.outputs[0], link.to_socket)
for link in self.outputs[1].links:
node_tree.links.new(separator.outputs[1], link.to_socket)
for link in self.outputs[2].links:
node_tree.links.new(separator.outputs[2], link.to_socket)
for link in self.outputs[3].links:
node_tree.links.new(newself.outputs[1], link.to_socket)
return [newself, separator]

View file

@ -5,7 +5,7 @@ class GetRotationNode(ArmLogicTreeNode):
bl_idname = 'LNGetRotationNode' bl_idname = 'LNGetRotationNode'
bl_label = 'Get Object Rotation' bl_label = 'Get Object Rotation'
arm_section = 'rotation' arm_section = 'rotation'
arm_version = 1 arm_version = 2
def init(self, context): def init(self, context):
super(GetRotationNode, self).init(context) super(GetRotationNode, self).init(context)
@ -19,3 +19,72 @@ class GetRotationNode(ArmLogicTreeNode):
items = [('Local', 'Local', 'Local'), items = [('Local', 'Local', 'Local'),
('Global', 'Global', 'Global')], ('Global', 'Global', 'Global')],
name='', default='Local') name='', default='Local')
def get_replacement_node(self, node_tree: bpy.types.NodeTree):
if self.arm_version not in (0, 1):
raise LookupError()
# transition from version 1 to version 2: make rotations their own sockets
# this transition is a mess, I know.
newself = self.id_data.nodes.new('LNGetRotationNode')
newself.property0 = 'Local'
newnodes = [newself]
if len(self.outputs[0].links)>0:
# euler (radians) needed
converter = self.id_data.nodes.new('LNSeparateRotationNode')
converter.property0 = "EulerAngles"
converter.property1 = "Rad"
converter.property2 = "XZY"
newnodes.append(converter)
self.id_data.links.new(newself.outputs[0], converter.inputs[0])
for link in self.outputs[0].links:
self.id_data.links.new(converter.outputs[0], link.to_socket)
if len(self.outputs[4].links)>0 or len(self.outputs[5].links)>0:
# quaternion needed
converter = self.id_data.nodes.new('LNSeparateRotationNode')
converter.property0 = "Quaternion"
newnodes.append(converter)
self.id_data.links.new(newself.outputs[0], converter.inputs[0])
for link in self.outputs[4].links:
self.id_data.links.new(converter.outputs[0], link.to_socket)
for link in self.outputs[5].links:
self.id_data.links.new(converter.outputs[1], link.to_socket)
if len(self.outputs[1].links)>0 or len(self.outputs[2].links)>0 or len(self.outputs[3].links)>0:
# axis/angle needed
converter = self.id_data.nodes.new('LNSeparateRotationNode')
converter.property0 = "AxisAngle"
converter.property1 = "Rad"
newnodes.append(converter)
self.id_data.links.new(newself.outputs[0], converter.inputs[0])
for link in self.outputs[1].links:
self.id_data.links.new(converter.outputs[0], link.to_socket)
if len(self.outputs[3].links)==0 and len(self.outputs[2].links)==0:
pass
elif len(self.outputs[3].links)==0:
for link in self.outputs[2].links:
self.id_data.links.new(converter.outputs[1], link.to_socket)
elif len(self.outputs[2].links)==0:
converter.property1 = 'Deg'
for link in self.outputs[3].links:
self.id_data.links.new(converter.outputs[1], link.to_socket)
else:
for link in self.outputs[2].links:
self.id_data.links.new(converter.outputs[1], link.to_socket)
converter = self.id_data.nodes.new('LNSeparateRotationNode')
converter.property0 = "AxisAngle"
converter.property1 = "Deg"
converter.property2 = "XYZ" # bogus
newnodes.append(converter)
self.id_data.links.new(newself.outputs[0], converter.inputs[0])
for link in self.outputs[3].links:
self.id_data.links.new(converter.outputs[1], link.to_socket)
return newnodes

View file

@ -5,7 +5,7 @@ class LookAtNode(ArmLogicTreeNode):
bl_idname = 'LNLookAtNode' bl_idname = 'LNLookAtNode'
bl_label = 'Look At' bl_label = 'Look At'
arm_section = 'rotation' arm_section = 'rotation'
arm_version = 1 arm_version = 2
property0: EnumProperty( property0: EnumProperty(
items = [('X', ' X', 'X'), items = [('X', ' X', 'X'),
@ -25,3 +25,23 @@ class LookAtNode(ArmLogicTreeNode):
def draw_buttons(self, context, layout): def draw_buttons(self, context, layout):
layout.prop(self, 'property0') layout.prop(self, 'property0')
def get_replacement_node(self, node_tree: bpy.types.NodeTree):
if self.arm_version not in (0, 1):
raise LookupError()
# transition from version 1 to version 2: make rotations their own sockets
# this transition is a mess, I know.
newself = self.id_data.nodes.new('LNLookAtNode')
converter = self.id_data.nodes.new('LNSeparateRotationNode')
self.id_data.links.new(newself.outputs[0], converter.inputs[0])
converter.property0 = 'EulerAngles'
converter.property1 = 'Rad'
converter.property2 = 'XZY'
for link in self.outputs[0].links:
self.id_data.links.new(converter.outputs[0], link.to_socket)
return [newself, converter]

View file

@ -1,11 +1,12 @@
from arm.logicnode.arm_nodes import * from arm.logicnode.arm_nodes import *
from arm.logicnode.arm_sockets import ArmRotationSocket as Rotation
class RotateObjectNode(ArmLogicTreeNode): class RotateObjectNode(ArmLogicTreeNode):
"""Rotates the given object.""" """Rotates the given object."""
bl_idname = 'LNRotateObjectNode' bl_idname = 'LNRotateObjectNode'
bl_label = 'Rotate Object' bl_label = 'Rotate Object'
arm_section = 'rotation' arm_section = 'rotation'
arm_version = 1 arm_version = 2
def init(self, context): def init(self, context):
super().init(context) super().init(context)
@ -20,5 +21,59 @@ class RotateObjectNode(ArmLogicTreeNode):
property0: EnumProperty( property0: EnumProperty(
items = [('Local', 'Local F.O.R.', 'Frame of reference oriented with the object'), items = [('Local', 'Local F.O.R.', 'Frame of reference oriented with the object'),
('Global', 'Global/Parent F.O.R.', 'Frame of reference oriented with the object\'s parent or the world')], ('Global', 'Global/Parent F.O.R.',
'Frame of reference oriented with the object\'s parent or the world')],
name='', default='Local') name='', default='Local')
def get_replacement_node(self, node_tree: bpy.types.NodeTree):
if self.arm_version not in (0, 1):
raise LookupError()
# transition from version 1 to version 2: make rotations their own sockets
# this transition is a mess, I know.
newself = self.id_data.nodes.new('LNRotateObjectNode')
inputnode = self.id_data.nodes.new('LNRotationNode')
self.id_data.links.new(inputnode.outputs[0], newself.inputs[2])
newself.inputs[1].default_value_raw = self.inputs[1].default_value_raw
inputnode.inputs[0].default_value = self.inputs[2].default_value
inputnode.inputs[1].default_value = self.inputs[3].default_value
if len(self.inputs[0].links) >0:
self.id_data.links.new(self.inputs[0].links[0].from_socket, newself.inputs[0])
if len(self.inputs[1].links) >0:
self.id_data.links.new(self.inputs[1].links[0].from_socket, newself.inputs[1])
if len(self.inputs[2].links) >0:
self.id_data.links.new(self.inputs[2].links[0].from_socket, inputnode.inputs[0])
if len(self.inputs[3].links) >0:
self.id_data.links.new(self.inputs[3].links[0].from_socket, inputnode.inputs[1])
# first, convert the default value
if self.property0 == 'Quaternion':
inputnode.property0 = 'Quaternion'
elif self.property0 == 'Euler Angles':
inputnode.property0 = 'EulerAngles'
inputnode.property1 = 'Rad'
inputnode.property2 = 'XZY' # legacy order
else: # starts with "Angle Axies"
inputnode.property0 = 'AxisAngle'
if 'Degrees' in self.property0:
inputnode.property1 = 'Deg'
else:
inputnode.property1 = 'Rad'
quat = Rotation.convert_to_quaternion(
self.inputs[2].default_value,
self.inputs[3].default_value,
inputnode.property0,
inputnode.property1,
inputnode.property2
)
newself.inputs[2].default_value_raw = quat
return [newself, inputnode]

View file

@ -40,7 +40,7 @@ class SeparateRotationNode(ArmLogicTreeNode):
property0: EnumProperty( property0: EnumProperty(
items = [('EulerAngles', 'Euler Angles', 'Euler Angles'), items = [('EulerAngles', 'Euler Angles', 'Euler Angles'),
('AxisAngle', 'Axis/Angle', 'Axis/Angle'), ('AxisAngle', 'Axis/Angle', 'Axis/Angle'),
('Quat', 'Quaternion', 'Quaternion')], ('Quaternion', 'Quaternion', 'Quaternion')],
name='', default='EulerAngles', name='', default='EulerAngles',
update=on_property_update) update=on_property_update)

View file

@ -4,7 +4,7 @@ class SeparateTransformNode(ArmLogicTreeNode):
"""Separates the transform of the given object.""" """Separates the transform of the given object."""
bl_idname = 'LNSeparateTransformNode' bl_idname = 'LNSeparateTransformNode'
bl_label = 'Separate Transform' bl_label = 'Separate Transform'
arm_version = 1 arm_version = 2
def init(self, context): def init(self, context):
super(SeparateTransformNode, self).init(context) super(SeparateTransformNode, self).init(context)
@ -13,3 +13,34 @@ class SeparateTransformNode(ArmLogicTreeNode):
self.add_output('NodeSocketVector', 'Location') self.add_output('NodeSocketVector', 'Location')
self.add_output('ArmNodeSocketRotation', 'Rotation') self.add_output('ArmNodeSocketRotation', 'Rotation')
self.add_output('NodeSocketVector', 'Scale') self.add_output('NodeSocketVector', 'Scale')
def get_replacement_node(self, node_tree: bpy.types.NodeTree):
if self.arm_version not in (0, 1):
raise LookupError()
# transition from version 1 to version 2: make rotations their own sockets
# this transition is a mess, I know.
newself = self.id_data.nodes.new('LNSeparateTransformNode')
for link in self.outputs[0].links:
self.id_data.links.new(newself.outputs[0], link.to_socket)
for link in self.outputs[2].links:
self.id_data.links.new(newself.outputs[2], link.to_socket)
for link in self.inputs[0].links:
self.id_data.links.new(link.from_socket, newself.inputs[0])
ret = [newself]
rot_links = self.outputs[1].links
if len(rot_links) >0:
converter = self.id_data.nodes.new('LNSeparateRotationNode')
ret.append(converter)
self.id_data.links.new(newself.outputs[1], converter.inputs[0])
converter.property0 = 'EulerAngles'
converter.property1 = 'Rad'
converter.property2 = 'XZY'
for link in rot_links:
self.id_data.links.new(converter.outputs[0], link.to_socket)
return ret

View file

@ -1,11 +1,12 @@
from arm.logicnode.arm_nodes import * from arm.logicnode.arm_nodes import *
from arm.logicnode.arm_sockets import ArmRotationSocket as Rotation
class SetRotationNode(ArmLogicTreeNode): class SetRotationNode(ArmLogicTreeNode):
"""Sets the rotation of the given object.""" """Sets the rotation of the given object."""
bl_idname = 'LNSetRotationNode' bl_idname = 'LNSetRotationNode'
bl_label = 'Set Object Rotation' bl_label = 'Set Object Rotation'
arm_section = 'rotation' arm_section = 'rotation'
arm_version = 1 arm_version = 2
def init(self, context): def init(self, context):
@ -16,10 +17,61 @@ class SetRotationNode(ArmLogicTreeNode):
self.add_output('ArmNodeSocketAction', 'Out') self.add_output('ArmNodeSocketAction', 'Out')
# def draw_buttons(self, context, layout):
# layout.prop(self, 'property0')
# property0: EnumProperty( def get_replacement_node(self, node_tree: bpy.types.NodeTree):
# items = [('Local', 'Local', 'Local'), if self.arm_version not in (0, 1):
# ('Global', 'Global', 'Global')], raise LookupError()
# name='', default='Local')
# transition from version 1 to version 2: make rotations their own sockets
# this transition is a mess, I know.
newself = self.id_data.nodes.new('LNRotateObjectNode')
inputnode = self.id_data.nodes.new('LNRotationNode')
self.id_data.links.new(inputnode.outputs[0], newself.inputs[2])
newself.inputs[1].default_value_raw = self.inputs[1].default_value_raw
inputnode.inputs[0].default_value = self.inputs[2].default_value
inputnode.inputs[1].default_value = self.inputs[3].default_value
if len(self.inputs[0].links) >0:
self.id_data.links.new(self.inputs[0].links[0].from_socket, newself.inputs[0])
if len(self.inputs[1].links) >0:
self.id_data.links.new(self.inputs[1].links[0].from_socket, newself.inputs[1])
if len(self.inputs[2].links) >0:
self.id_data.links.new(self.inputs[2].links[0].from_socket, inputnode.inputs[0])
if len(self.inputs[3].links) >0:
self.id_data.links.new(self.inputs[3].links[0].from_socket, inputnode.inputs[1])
# first, convert the default value
if self.property0 == 'Quaternion':
inputnode.property0 = 'Quaternion'
elif self.property0 == 'Euler Angles':
inputnode.property0 = 'EulerAngles'
inputnode.property1 = 'Rad'
inputnode.property2 = 'XZY' # legacy order
elif self.property0.startswith("Angle Axies "):
inputnode.property0 = 'AxisAngle'
if 'Degrees' in self.property0:
inputnode.property1 = 'Deg'
else:
inputnode.property1 = 'Rad'
else:
raise ValueError('nonsensical value {:s} for property0 in SetObjectRotationNode/V1. please report this to the devs.'.format(self.property0))
quat = Rotation.convert_to_quaternion(
self.inputs[2].default_value,
self.inputs[3].default_value,
inputnode.property0,
inputnode.property1,
inputnode.property2
)
newself.inputs[2].default_value_raw = quat
return [newself, inputnode]
# note: this is unused, but kept here so that the 'property0' field can be read during node replacement
property0: EnumProperty(
items = [('Euler Angles', '',''),
('Angle Axies (Radians)', '', ''),
('Angle Axies (Degrees)', '', ''),
('Quaternion', '', '')],
name='', default='Euler Angles')

View file

@ -4,7 +4,7 @@ class TransformNode(ArmLogicTreeNode):
"""Stores the location, rotation and scale values as a transform.""" """Stores the location, rotation and scale values as a transform."""
bl_idname = 'LNTransformNode' bl_idname = 'LNTransformNode'
bl_label = 'Transform' bl_label = 'Transform'
arm_version = 1 arm_version = 2
def init(self, context): def init(self, context):
super(TransformNode, self).init(context) super(TransformNode, self).init(context)
@ -13,3 +13,34 @@ class TransformNode(ArmLogicTreeNode):
self.add_input('NodeSocketVector', 'Scale', default_value=[1.0, 1.0, 1.0]) self.add_input('NodeSocketVector', 'Scale', default_value=[1.0, 1.0, 1.0])
self.add_output('NodeSocketShader', 'Transform', is_var=True) self.add_output('NodeSocketShader', 'Transform', is_var=True)
def get_replacement_node(self, node_tree: bpy.types.NodeTree):
if self.arm_version not in (0, 1):
raise LookupError()
# transition from version 1 to version 2: make rotations their own sockets
# this transition is a mess, I know.
newself = self.id_data.nodes.new('LNTransformNode')
ret = [newself]
for link in self.inputs[0].links:
self.id_data.links.new(link.from_socket, newself.inputs[0])
for link in self.inputs[2].links:
self.id_data.links.new(link.from_socket, newself.inputs[2])
for link in self.outputs[0].links:
self.id_data.links.new(newself.outputs[0], link.to_socket)
links_rot = self.inputs[1].links
if len(links_rot) > 0:
converter = self.id_data.nodes.new('LNRotationNode')
self.id_data.links.new(converter.outputs[0], newself.inputs[1])
converter.property0 = 'EulerAngles'
converter.property1 = 'Rad'
converter.property2 = 'XZY'
ret.append(converter)
for link in links_rot:
self.id_data.links.new(link.from_socket, converter.inputs[0])
return ret

View file

@ -1,11 +1,14 @@
from arm.logicnode.arm_nodes import * from arm.logicnode.arm_nodes import *
from mathutils import Vector
@deprecated(message='Do not use quaternion sockets')
class QuaternionNode(ArmLogicTreeNode): class QuaternionNode(ArmLogicTreeNode):
"""TO DO.""" """TO DO."""
bl_idname = 'LNQuaternionNode' bl_idname = 'LNQuaternionNode'
bl_label = 'Quaternion' bl_label = 'Quaternion'
bl_label = 'Create a quaternion variable (transported through a vector socket)'
arm_section = 'quaternions' arm_section = 'quaternions'
arm_version = 1 arm_version = 2 # deprecate
def init(self, context): def init(self, context):
super(QuaternionNode, self).init(context) super(QuaternionNode, self).init(context)
@ -17,3 +20,57 @@ class QuaternionNode(ArmLogicTreeNode):
self.add_output('NodeSocketVector', 'Quaternion') self.add_output('NodeSocketVector', 'Quaternion')
self.add_output('NodeSocketVector', 'XYZ') self.add_output('NodeSocketVector', 'XYZ')
self.add_output('NodeSocketFloat', 'W') self.add_output('NodeSocketFloat', 'W')
def get_replacement_node(self, node_tree: bpy.types.NodeTree):
if self.arm_version not in (0, 1):
raise LookupError()
# transition from version 1 to version 2[deprecated]
newnodes = []
rawlinks = self.outputs[0].links
xyzlinks = self.outputs[1].links
wlinks = self.outputs[2].links
if len(rawlinks)>0 or len(xyzlinks)>0:
xyzcomb = node_tree.nodes.new('LNVectorNode')
newnodes.append(xyzcomb)
xyzcomb.inputs[0].default_value = self.inputs[0].default_value
xyzcomb.inputs[1].default_value = self.inputs[1].default_value
xyzcomb.inputs[2].default_value = self.inputs[2].default_value
for link in self.inputs[0].links:
node_tree.links.new(link.from_socket, xyzcomb.inputs[0])
for link in self.inputs[1].links:
node_tree.links.new(link.from_socket, xyzcomb.inputs[1])
for link in self.inputs[2].links:
node_tree.links.new(link.from_socket, xyzcomb.inputs[2])
for link in xyzlinks:
node_tree.links.new(xyzcomb.outputs[0], link.to_socket)
if len(rawlinks)>0:
rotnode = node_tree.nodes.new('LNRotationNode')
newnodes.append(rotnode)
rotnode.property0 = 'Quaternion'
rotnode.inputs[0].default_value = Vector(
(self.inputs[0].default_value,
self.inputs[1].default_value,
self.inputs[2].default_value))
rotnode.inputs[1].default_value = self.inputs[3].default_value
node_tree.links.new(xyzcomb.outputs[0], rotnode.inputs[0])
for link in self.inputs[3].links: # 0 or 1
node_tree.links.new(link.from_socket, rotnode.inputs[1])
for link in rawlinks:
node_tree.links.new(rotnode.outputs[0], link.to_socket)
if len(self.inputs[3].links)>0:
fromval = self.inputs[3].links[0].from_socket
for link in self.outputs[2].links:
node_tree.links.new(fromval, link.to_socket)
else:
for link in self.outputs[2].links:
link.to_socket.default_value = self.inputs[3].default_value
return newnodes

View file

@ -4,6 +4,7 @@ class RotationNode(ArmLogicTreeNode):
"""A rotation, created from one of its possible mathematical representations""" """A rotation, created from one of its possible mathematical representations"""
bl_idname = 'LNRotationNode' bl_idname = 'LNRotationNode'
bl_label = 'Rotation' bl_label = 'Rotation'
bl_description = 'Create a Rotation object, describing the difference between two orientations (internally represented as a quaternion for efficiency)'
#arm_section = 'rotation' #arm_section = 'rotation'
arm_version = 1 arm_version = 1