armory/blender/arm/logicnode/arm_sockets.py
niacdoial 1d0a6d7955
Did the haxe part of the ongoing update.
Also fixed a ton of bugs in the python part.
NOTE: this requires a yet-to-be-done commit to Iron to work.
2021-08-17 19:29:35 +02:00

313 lines
11 KiB
Python

from math import pi, cos, sin, sqrt
import bpy
from bpy.props import PointerProperty, EnumProperty, FloatProperty, FloatVectorProperty
from bpy.types import NodeSocket
import mathutils
import arm.utils
class ArmCustomSocket(NodeSocket):
"""
A custom socket that can be used to define more socket types for
logic node packs. Do not use this type directly (it is not
registered)!
"""
bl_idname = 'ArmCustomSocket'
bl_label = 'Custom Socket'
# note: trying to use the `type` property will fail. All custom nodes will have "VALUE" as a type, because it is the default.
arm_socket_type = 'NONE'
# please also declare a property named "default_value_raw" of arm_socket_type isn't "NONE"
def get_default_value(self):
"""Override this for values of unconnected input sockets."""
return None
class ArmActionSocket(ArmCustomSocket):
bl_idname = 'ArmNodeSocketAction'
bl_label = 'Action Socket'
arm_socket_type = 'NONE'
def draw(self, context, layout, node, text):
layout.label(text=self.name)
def draw_color(self, context, node):
return 0.8, 0.3, 0.3, 1
class ArmAnimActionSocket(ArmCustomSocket):
bl_idname = 'ArmNodeSocketAnimAction'
bl_label = 'Action Socket'
arm_socket_type = 'STRING'
default_value_get: PointerProperty(name='Action', type=bpy.types.Action) # legacy version of the line after this one
default_value_raw: PointerProperty(name='Action', type=bpy.types.Action)
def __init__(self):
super().__init__()
if self.default_value_get is not None:
self.default_value_raw = self.default_value_get
self.default_value_get = None
def get_default_value(self):
if self.default_value_raw is None:
return ''
if self.default_value_raw.name not in bpy.data.actions:
return self.default_value_raw.name
name = arm.utils.asset_name(bpy.data.actions[self.default_value_raw.name])
return arm.utils.safestr(name)
def draw(self, context, layout, node, text):
if self.is_output:
layout.label(text=self.name)
elif self.is_linked:
layout.label(text=self.name)
else:
layout.prop_search(self, 'default_value_raw', bpy.data, 'actions', icon='NONE', text='')
def draw_color(self, context, node):
return 0.8, 0.8, 0.8, 1
class ArmRotationSocket(ArmCustomSocket):
bl_idname = 'ArmNodeSocketRotation'
bl_label = 'Rotation Socket'
arm_socket_type = 'ROTATION' # the internal representation is a quaternion, AKA a '4D vector' (using mathutils.Vector((x,y,z,w)))
def get_default_value(self):
if self.default_value_raw is None:
return Vector((0.0,0.0,0.0,1.0))
else:
return self.default_value_raw
def on_unit_update(self, context):
if self.default_value_unit == 'Rad':
fac = pi/180 # deg->rad conversion
else:
fac = 180/pi # rad->deg conversion
if self.default_value_mode == 'AxisAngle':
self.default_value_s3 *= fac
elif self.default_value_mode == 'EulerAngles':
self.default_value_s0 *= fac
self.default_value_s1 *= fac
self.default_value_s2 *= fac
self.do_update_raw(context)
def on_mode_update(self, context):
if self.default_value_mode == 'Quaternion':
summ = abs(self.default_value_s0)
summ+= abs(self.default_value_s1)
summ+= abs(self.default_value_s2)
summ+= abs(self.default_value_s3)
if summ<0.01:
self.default_value_s3 = 1.0
elif self.default_value_mode == 'AxisAngle':
summ = abs(self.default_value_s0)
summ+= abs(self.default_value_s1)
summ+= abs(self.default_value_s2)
if summ<1E-5:
self.default_value_s3 = 0.0
self.do_update_raw(context)
@staticmethod
def convert_to_quaternion(part1,part2,param1,param2,param3):
"""converts a representation of rotation into a quaternion.
``part1`` is a vector, ``part2`` is a scalar or None,
``param1`` is in ('Quaternion', 'EulerAngles', 'AxisAngle'),
``param2`` is in ('Rad','Deg') for both EulerAngles and AxisAngle,
``param3`` is a len-3 string like "XYZ", for EulerAngles """
if param1=='Quaternion':
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)
ql = sqrt(qx**2+qy**2+qz**2+qw**2)
if abs(ql)<1E-5:
qx, qy, qz, qw = 0.0,0.0,0.0,1.0
else:
qx /= ql
qy /= ql
qz /= ql
qw /= ql
return mathutils.Vector((qx,qy,qz,qw))
elif param1 == 'AxisAngle':
if param2 == 'Deg':
angle = part2 * pi/180
else:
angle = part2
cang, sang = cos(angle/2), sin(angle/2)
x,y,z = part1[0], part1[1], part1[2]
veclen = sqrt(x**2+y**2+z**2)
if veclen<1E-5:
return mathutils.Vector((0.0,0.0,0.0,1.0))
else:
return mathutils.Vector((
x/veclen * sang,
y/veclen * sang,
z/veclen * sang,
cang
))
else: # param1 == 'EulerAngles'
x,y,z = part1[0], part1[1], part1[2]
if param2 == 'Deg':
x *= pi/180
y *= pi/180
z *= pi/180
cx, sx = cos(x/2), sin(x/2)
cy, sy = cos(y/2), sin(y/2)
cz, sz = cos(z/2), sin(z/2)
qw, qx, qy, qz = 1.0,0.0,0.0,0.0
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]
qw = qw*qwi -qx*qxi -qy*qyi -qz*qzi
qx = qx*qwi +qw*qxi +qy*qzi -qz*qyi
qy = qy*qwi +qw*qyi +qz*qxi -qx*qzi
qz = qz*qwi +qw*qzi +qx*qyi -qy*qxi
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.default_value_unit,
self.default_value_order
)
def draw(self, context, layout, node, text):
if (self.is_output or self.is_linked):
layout.label(text=self.name)
else:
coll1 = layout.column(align=True)
coll1.label(text=self.name)
bx=coll1.box()
coll = bx.column(align=True)
coll.prop(self, 'default_value_mode')
if self.default_value_mode in ('EulerAngles', 'AxisAngle'):
coll.prop(self, 'default_value_unit')
if self.default_value_mode == 'EulerAngles':
coll.prop(self, 'default_value_order')
coll.prop(self, 'default_value_s0', text='X')
coll.prop(self, 'default_value_s1', text='Y')
coll.prop(self, 'default_value_s2', text='Z')
elif self.default_value_mode == 'Quaternion':
coll.prop(self, 'default_value_s0', text='X')
coll.prop(self, 'default_value_s1', text='Y')
coll.prop(self, 'default_value_s2', text='Z')
coll.prop(self, 'default_value_s3', text='W')
elif self.default_value_mode == 'AxisAngle':
coll.prop(self, 'default_value_s0', text='X')
coll.prop(self, 'default_value_s1', text='Y')
coll.prop(self, 'default_value_s2', text='Z')
coll.separator()
coll.prop(self, 'default_value_s3', text='Angle')
def draw_color(self, context, node):
return 0.68, 0.22, 0.62, 1
default_value_mode: EnumProperty(
items=[('EulerAngles', 'Euler Angles', 'Euler Angles'),
('AxisAngle', 'Axis/Angle', 'Axis/Angle'),
('Quaternion', 'Quaternion', 'Quaternion')],
name='', default='EulerAngles',
update=on_mode_update)
default_value_unit: EnumProperty(
items=[('Deg', 'Degrees', 'Degrees'),
('Rad', 'Radians', 'Radians')],
name='', default='Rad',
update=on_unit_update)
default_value_order: EnumProperty(
items=[('XYZ','XYZ','XYZ'),
('XZY','XZY (legacy Armory euler order)','XZY (legacy Armory euler order)'),
('YXZ','YXZ','YXZ'),
('YZX','YZX','YZX'),
('ZXY','ZXY','ZXY'),
('ZYX','ZYX','ZYX')],
name='', default='XYZ'
)
default_value_s0: FloatProperty(update=do_update_raw)
default_value_s1: FloatProperty(update=do_update_raw)
default_value_s2: FloatProperty(update=do_update_raw)
default_value_s3: FloatProperty(update=do_update_raw)
default_value_raw: FloatVectorProperty(size=4, default=(0,0,0,1))
class ArmArraySocket(ArmCustomSocket):
bl_idname = 'ArmNodeSocketArray'
bl_label = 'Array Socket'
arm_socket_type = 'NONE'
def draw(self, context, layout, node, text):
layout.label(text=self.name)
def draw_color(self, context, node):
return 0.8, 0.4, 0.0, 1
class ArmObjectSocket(ArmCustomSocket):
bl_idname = 'ArmNodeSocketObject'
bl_label = 'Object Socket'
arm_socket_type = 'OBJECT'
default_value_get: PointerProperty(name='Object', type=bpy.types.Object) # legacy version of the line after this one
default_value_raw: PointerProperty(name='Object', type=bpy.types.Object)
def __init__(self):
super().__init__()
if self.default_value_get is not None:
self.default_value_raw = self.default_value_get
self.default_value_get = None
def get_default_value(self):
if self.default_value_raw is None:
return ''
if self.default_value_raw.name not in bpy.data.objects:
return self.default_value_raw.name
return arm.utils.asset_name(bpy.data.objects[self.default_value_raw.name])
def draw(self, context, layout, node, text):
if self.is_output:
layout.label(text=self.name)
elif self.is_linked:
layout.label(text=self.name)
else:
row = layout.row(align=True)
row.prop_search(self, 'default_value_raw', context.scene, 'objects', icon='NONE', text=self.name)
def draw_color(self, context, node):
return 0.15, 0.55, 0.75, 1
def register():
bpy.utils.register_class(ArmActionSocket)
bpy.utils.register_class(ArmAnimActionSocket)
bpy.utils.register_class(ArmRotationSocket)
bpy.utils.register_class(ArmArraySocket)
bpy.utils.register_class(ArmObjectSocket)
def unregister():
bpy.utils.unregister_class(ArmObjectSocket)
bpy.utils.unregister_class(ArmArraySocket)
bpy.utils.unregister_class(ArmAnimActionSocket)
bpy.utils.unregister_class(ArmRotationSocket)
bpy.utils.unregister_class(ArmActionSocket)