773 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			773 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| from math import radians, pi, cos, sin, sqrt
 | |
| from typing import Type
 | |
| 
 | |
| import bpy
 | |
| from bpy.props import *
 | |
| from bpy.types import NodeSocket
 | |
| import mathutils
 | |
| 
 | |
| import lnx.node_utils
 | |
| import lnx.utils
 | |
| 
 | |
| if lnx.is_reload(__name__):
 | |
|     lnx.node_utils = lnx.reload_module(lnx.node_utils)
 | |
|     lnx.utils = lnx.reload_module(lnx.utils)
 | |
| else:
 | |
|     lnx.enable_reload(__name__)
 | |
| 
 | |
| # See Blender sources: /source/blender/editors/space_node/drawnode.cc
 | |
| # Permalink for 3.2.2: https://github.com/blender/blender/blob/bcfdb14560e77891d674c2701a5071a7c07baba3/source/blender/editors/space_node/drawnode.cc#L1152-L1167
 | |
| socket_colors = {
 | |
|     'LnxNodeSocketAction': (0.8, 0.3, 0.3, 1),
 | |
|     'LnxNodeSocketAnimAction': (0.8, 0.8, 0.8, 1),
 | |
|     'LnxRotationSocket': (0.68, 0.22, 0.62, 1),
 | |
|     'LnxNodeSocketArray': (0.8, 0.4, 0.0, 1),
 | |
|     'LnxBoolSocket': (0.80, 0.65, 0.84, 1.0),
 | |
|     'LnxColorSocket': (0.78, 0.78, 0.16, 1.0),
 | |
|     'LnxDynamicSocket': (0.39, 0.78, 0.39, 1.0),
 | |
|     'LnxFloatSocket': (0.63, 0.63, 0.63, 1.0),
 | |
|     'LnxIntSocket': (0.059, 0.522, 0.149, 1),
 | |
|     'LnxNodeSocketObject': (0.15, 0.55, 0.75, 1),
 | |
|     'LnxStringSocket': (0.44, 0.70, 1.00, 1.0),
 | |
|     'LnxVectorSocket': (0.39, 0.39, 0.78, 1.0),
 | |
|     'LnxAnySocket': (0.9, 0.9, 0.9, 1),
 | |
|     'LnxNodeSocketAnimTree': (0.3, 0.1, 0.0, 1.0),
 | |
|     'LnxFactorSocket': (0.631, 0.631, 0.631, 1.0),
 | |
|     'LnxBlendSpaceSocket': (0.631, 0.631, 0.631, 1.0)
 | |
| }
 | |
| 
 | |
| 
 | |
| def _on_update_socket(self, context):
 | |
|     self.node.on_socket_val_update(context, self)
 | |
| 
 | |
| 
 | |
| class LnxCustomSocket(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 = 'LnxCustomSocket'
 | |
|     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.
 | |
|     lnx_socket_type = 'NONE'
 | |
|     # please also declare a property named "default_value_raw" of lnx_socket_type isn't "NONE"
 | |
| 
 | |
|     def get_default_value(self):
 | |
|         """Override this for values of unconnected input sockets."""
 | |
|         return None
 | |
| 
 | |
|     def on_node_update(self):
 | |
|         """Called when the update() method of the corresponding node is called."""
 | |
|         pass
 | |
| 
 | |
|     def copy_defaults(self, socket):
 | |
|         """Called when this socket default values are to be copied to the given socket"""
 | |
|         pass
 | |
| 
 | |
| 
 | |
| class LnxActionSocket(LnxCustomSocket):
 | |
|     bl_idname = 'LnxNodeSocketAction'
 | |
|     bl_label = 'Action Socket'
 | |
|     lnx_socket_type = 'NONE'
 | |
| 
 | |
|     def draw(self, context, layout, node, text):
 | |
|         layout.label(text=self.name)
 | |
| 
 | |
|     def draw_color(self, context, node):
 | |
|         return socket_colors[self.bl_idname]
 | |
| 
 | |
| 
 | |
| class LnxAnimActionSocket(LnxCustomSocket):
 | |
|     bl_idname = 'LnxNodeSocketAnimAction'
 | |
|     bl_label = 'Action Socket'
 | |
|     lnx_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, update=_on_update_socket)
 | |
| 
 | |
|     def __init__(self, *args, **kwargs):
 | |
|         super().__init__(*args, **kwargs)
 | |
|         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 = lnx.utils.asset_name(bpy.data.actions[self.default_value_raw.name])
 | |
|         return lnx.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:
 | |
|             row = layout.row(align=True)
 | |
|             layout.prop_search(self, 'default_value_raw', bpy.data, 'actions', icon='NONE', text=self.name)
 | |
| 
 | |
|     def draw_color(self, context, node):
 | |
|         return socket_colors[self.bl_idname]
 | |
| 
 | |
|     def copy_defaults(self, socket):
 | |
|         if socket.bl_idname == self.bl_idname:
 | |
|             socket.default_value_raw = self.default_value_raw
 | |
| 
 | |
| class LnxRotationSocket(LnxCustomSocket):
 | |
|     bl_idname = 'LnxRotationSocket'
 | |
|     bl_label = 'Rotation Socket'
 | |
|     lnx_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 mathutils.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(vec3_val, scalar_val, mode, unit, order):
 | |
|         '''Converts Euler or Axis-Angle representation to a Quaternion Vector'''
 | |
| 
 | |
|         if mode == 'Quaternion':
 | |
|             qx, qy, qz = vec3_val[0], vec3_val[1], vec3_val[2]
 | |
|             qw = scalar_val 
 | |
| 
 | |
|             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 mode == 'EulerAngles':
 | |
|             x, y, z = vec3_val.to_tuple()
 | |
| 
 | |
|             if unit == 'Deg':
 | |
|                 x, y, z = radians(x), radians(y), radians(z)
 | |
| 
 | |
|             angles_ordered = [0.0, 0.0, 0.0]
 | |
|             for i, axis in enumerate(order):
 | |
|                 if axis == 'X':
 | |
|                     angles_ordered[i] = x
 | |
|                 elif axis == 'Y':
 | |
|                     angles_ordered[i] = y
 | |
|                 elif axis == 'Z':
 | |
|                     angles_ordered[i] = z
 | |
|             eul = mathutils.Euler(angles_ordered, order)
 | |
|             quat = eul.to_quaternion()
 | |
|             return mathutils.Vector((quat.x, quat.y, quat.z, quat.w))
 | |
| 
 | |
|         elif mode == 'AxisAngle':
 | |
|             axis = vec3_val.normalized().to_tuple()
 | |
|             angle = scalar_val
 | |
|             if unit == 'Deg':
 | |
|                 angle = radians(angle)
 | |
|             quat = mathutils.Quaternion(axis, angle)
 | |
|             return mathutils.Vector((quat.x, quat.y, quat.z, quat.w))
 | |
| 
 | |
|         print(f"Warning: Invalid mode '{mode}' in convert_to_quaternion")
 | |
|         return mathutils.Vector((0.0, 0.0, 0.0, 1.0))
 | |
| 
 | |
|                 
 | |
| 
 | |
|     def do_update_raw(self, context):
 | |
|         if self.default_value_mode == 'Quaternion':
 | |
|             # Directly construct the quaternion vector from s0, s1, s2, s3 (x, y, z, w)
 | |
|             vec3_val = mathutils.Vector((
 | |
|                 self.default_value_s0, # X component or Euler X or Axis X
 | |
|                 self.default_value_s1, # Y component or Euler Y or Axis Y
 | |
|                 self.default_value_s2  # Z component or Euler Z or Axis Z
 | |
|             ))
 | |
|             scalar_val = self.default_value_s3 # W component or Axis Angle
 | |
| 
 | |
|             # Always call the unified conversion function
 | |
|             # The result will be in (x, y, z, w) order
 | |
|             self.default_value_raw = self.convert_to_quaternion(
 | |
|                 vec3_val,
 | |
|                 scalar_val,
 | |
|                 self.default_value_mode,
 | |
|                 self.default_value_unit,
 | |
|                 self.default_value_order
 | |
|             )
 | |
|         else:
 | |
|             # Handle EulerAngles and AxisAngle using the conversion helper
 | |
|             vec3_val = mathutils.Vector((
 | |
|                 self.default_value_s0,
 | |
|                 self.default_value_s1,
 | |
|                 self.default_value_s2
 | |
|             ))
 | |
|             # s3 is used for AxisAngle angle, irrelevant for Euler
 | |
|             scalar_val = self.default_value_s3
 | |
| 
 | |
|             self.default_value_raw = self.convert_to_quaternion(
 | |
|                 vec3_val,           # Vector part (Euler angles X,Y,Z or Axis X,Y,Z)
 | |
|                 scalar_val,         # Scalar part (Axis angle)
 | |
|                 self.default_value_mode, # Mode ('EulerAngles' or 'AxisAngle')
 | |
|                 self.default_value_unit, # Unit ('Rad' or 'Deg')
 | |
|                 self.default_value_order # Order ('XYZ', 'ZYX', etc. - used only for Euler)
 | |
|             )
 | |
| 
 | |
| 
 | |
|     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 socket_colors[self.bl_idname]
 | |
| 
 | |
|     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 Leenkx euler order)','XZY (legacy Leenkx euler order)'),
 | |
|                ('YXZ','YXZ','YXZ'),
 | |
|                ('YZX','YZX','YZX'),
 | |
|                ('ZXY','ZXY','ZXY'),
 | |
|                ('ZYX','ZYX','ZYX')],
 | |
|         name='', default='XYZ',
 | |
|         update=do_update_raw
 | |
|     )
 | |
| 
 | |
| 
 | |
|     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(
 | |
|         name='Value',
 | |
|         description='Raw quaternion obtained for the default value of a LnxRotationSocket socket',
 | |
|         size=4, default=(0,0,0,1),
 | |
|         update = _on_update_socket
 | |
|     )
 | |
| 
 | |
|     def copy_defaults(self, socket):
 | |
|         if socket.bl_idname == self.bl_idname:
 | |
|             socket.default_value_mode = self.default_value_mode
 | |
|             socket.default_value_unit = self.default_value_unit
 | |
|             socket.default_value_order = self.default_value_order
 | |
|             socket.default_value_s0 = self.default_value_s0
 | |
|             socket.default_value_s1 = self.default_value_s1
 | |
|             socket.default_value_s2 = self.default_value_s2
 | |
|             socket.default_value_s3 = self.default_value_s3
 | |
|             socket.default_value_raw = self.default_value_raw
 | |
| 
 | |
| 
 | |
| class LnxArraySocket(LnxCustomSocket):
 | |
|     bl_idname = 'LnxNodeSocketArray'
 | |
|     bl_label = 'Array Socket'
 | |
|     lnx_socket_type = 'NONE'
 | |
| 
 | |
|     def draw(self, context, layout, node, text):
 | |
|         layout.label(text=self.name)
 | |
| 
 | |
|     def draw_color(self, context, node):
 | |
|         return socket_colors[self.bl_idname]
 | |
| 
 | |
| 
 | |
| class LnxBoolSocket(LnxCustomSocket):
 | |
|     bl_idname = 'LnxBoolSocket'
 | |
|     bl_label = 'Boolean Socket'
 | |
|     lnx_socket_type = 'BOOLEAN'
 | |
| 
 | |
|     default_value_raw: BoolProperty(
 | |
|         name='Value',
 | |
|         description='Input value used for unconnected socket',
 | |
|         update=_on_update_socket
 | |
|     )
 | |
| 
 | |
|     def draw(self, context, layout, node, text):
 | |
|         draw_socket_layout(self, layout)
 | |
| 
 | |
|     def draw_color(self, context, node):
 | |
|         return socket_colors[self.bl_idname]
 | |
| 
 | |
|     def get_default_value(self):
 | |
|         return self.default_value_raw
 | |
| 
 | |
|     def copy_defaults(self, socket):
 | |
|         if socket.bl_idname == self.bl_idname:
 | |
|             socket.default_value_raw = self.default_value_raw
 | |
| 
 | |
| 
 | |
| class LnxColorSocket(LnxCustomSocket):
 | |
|     bl_idname = 'LnxColorSocket'
 | |
|     bl_label = 'Color Socket'
 | |
|     lnx_socket_type = 'RGBA'
 | |
| 
 | |
|     default_value_raw: FloatVectorProperty(
 | |
|         name='Value',
 | |
|         size=4,
 | |
|         subtype='COLOR',
 | |
|         min=0.0,
 | |
|         max=1.0,
 | |
|         default=[0.0, 0.0, 0.0, 1.0],
 | |
|         description='Input value used for unconnected socket',
 | |
|         update=_on_update_socket
 | |
|     )
 | |
| 
 | |
|     def draw(self, context, layout, node, text):
 | |
|         draw_socket_layout_split(self, layout)
 | |
| 
 | |
|     def draw_color(self, context, node):
 | |
|         return socket_colors[self.bl_idname]
 | |
| 
 | |
|     def get_default_value(self):
 | |
|         return self.default_value_raw
 | |
| 
 | |
|     def copy_defaults(self, socket):
 | |
|         if socket.bl_idname == self.bl_idname:
 | |
|             socket.default_value_raw = self.default_value_raw
 | |
| 
 | |
| 
 | |
| class LnxDynamicSocket(LnxCustomSocket):
 | |
|     bl_idname = 'LnxDynamicSocket'
 | |
|     bl_label = 'Dynamic Socket'
 | |
|     lnx_socket_type = 'NONE'
 | |
| 
 | |
|     def draw(self, context, layout, node, text):
 | |
|         layout.label(text=self.name)
 | |
| 
 | |
|     def draw_color(self, context, node):
 | |
|         return socket_colors[self.bl_idname]
 | |
| 
 | |
| 
 | |
| class LnxAnySocket(LnxCustomSocket):
 | |
|     bl_idname = 'LnxAnySocket'
 | |
|     bl_label = 'Any Socket'
 | |
|     lnx_socket_type = 'NONE'
 | |
| 
 | |
|     # Callback function when socket label is changed
 | |
|     def on_disp_label_update(self, context):
 | |
|         node = self.node
 | |
|         if node.bl_idname == 'LNGroupInputsNode' or node.bl_idname == 'LNGroupOutputsNode':
 | |
|             if not node.invalid_link:
 | |
|                 node.socket_name_update(self)
 | |
|                 self.on_node_update()
 | |
|                 self.name = self.display_label
 | |
| 
 | |
|     display_label: StringProperty(
 | |
|         name='display_label',
 | |
|         description='Property to store socket display name',
 | |
|         update=on_disp_label_update)
 | |
| 
 | |
|     display_color: FloatVectorProperty(
 | |
|         name='Color',
 | |
|         size=4,
 | |
|         subtype='COLOR',
 | |
|         min=0.0,
 | |
|         max=1.0,
 | |
|         default=socket_colors['LnxAnySocket']
 | |
|     )
 | |
| 
 | |
|     def draw(self, context, layout, node, text):
 | |
|         layout.label(text=self.display_label)
 | |
| 
 | |
|     def draw_color(self, context, node):
 | |
|         return self.display_color
 | |
| 
 | |
|     def on_node_update(self):
 | |
|         # Cache name and color of connected socket
 | |
|         if self.is_output:
 | |
|             c_node, c_socket = lnx.node_utils.output_get_connected_node(self)
 | |
|         else:
 | |
|             c_node, c_socket = lnx.node_utils.input_get_connected_node(self)
 | |
| 
 | |
|         if c_node is None:
 | |
|             self.display_color = socket_colors[self.__class__.bl_idname]
 | |
|         else:
 | |
|             if self.display_label == '':
 | |
|                 self.display_label = c_socket.name
 | |
|             self.display_color = c_socket.draw_color(bpy.context, c_node)
 | |
| 
 | |
| 
 | |
| class LnxFloatSocket(LnxCustomSocket):
 | |
|     bl_idname = 'LnxFloatSocket'
 | |
|     bl_label = 'Float Socket'
 | |
|     lnx_socket_type = 'VALUE'
 | |
| 
 | |
|     default_value_raw: FloatProperty(
 | |
|         name='Value',
 | |
|         description='Input value used for unconnected socket',
 | |
|         precision=3,
 | |
|         update=_on_update_socket
 | |
|     )
 | |
| 
 | |
|     def draw(self, context, layout, node, text):
 | |
|         draw_socket_layout(self, layout)
 | |
| 
 | |
|     def draw_color(self, context, node):
 | |
|         return socket_colors[self.bl_idname]
 | |
| 
 | |
|     def get_default_value(self):
 | |
|         return self.default_value_raw
 | |
| 
 | |
|     def copy_defaults(self, socket):
 | |
|         if socket.bl_idname == self.bl_idname:
 | |
|             socket.default_value_raw = self.default_value_raw
 | |
| 
 | |
| class LnxIntSocket(LnxCustomSocket):
 | |
|     bl_idname = 'LnxIntSocket'
 | |
|     bl_label = 'Integer Socket'
 | |
|     lnx_socket_type = 'INT'
 | |
| 
 | |
|     default_value_raw: IntProperty(
 | |
|         name='Value',
 | |
|         description='Input value used for unconnected socket',
 | |
|         update=_on_update_socket
 | |
|     )
 | |
| 
 | |
|     def draw(self, context, layout, node, text):
 | |
|         draw_socket_layout(self, layout)
 | |
| 
 | |
|     def draw_color(self, context, node):
 | |
|         return socket_colors[self.bl_idname]
 | |
| 
 | |
|     def get_default_value(self):
 | |
|         return self.default_value_raw
 | |
| 
 | |
|     def copy_defaults(self, socket):
 | |
|         if socket.bl_idname == self.bl_idname:
 | |
|             socket.default_value_raw = self.default_value_raw
 | |
| 
 | |
| class LnxObjectSocket(LnxCustomSocket):
 | |
|     bl_idname = 'LnxNodeSocketObject'
 | |
|     bl_label = 'Object Socket'
 | |
|     lnx_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, update=_on_update_socket)
 | |
| 
 | |
|     def __init__(self, *args, **kwargs):
 | |
|         super().__init__(*args, **kwargs)
 | |
|         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 lnx.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 socket_colors[self.bl_idname]
 | |
| 
 | |
|     def copy_defaults(self, socket):
 | |
|         if socket.bl_idname == self.bl_idname:
 | |
|             socket.default_value_raw = self.default_value_raw
 | |
| 
 | |
| class LnxStringSocket(LnxCustomSocket):
 | |
|     bl_idname = 'LnxStringSocket'
 | |
|     bl_label = 'String Socket'
 | |
|     lnx_socket_type = 'STRING'
 | |
| 
 | |
|     default_value_raw: StringProperty(
 | |
|         name='Value',
 | |
|         description='Input value used for unconnected socket',
 | |
|         update=_on_update_socket
 | |
|     )
 | |
| 
 | |
|     def draw(self, context, layout, node, text):
 | |
|         draw_socket_layout_split(self, layout)
 | |
| 
 | |
|     def draw_color(self, context, node):
 | |
|         return socket_colors[self.bl_idname]
 | |
| 
 | |
|     def get_default_value(self):
 | |
|         return self.default_value_raw
 | |
| 
 | |
|     def copy_defaults(self, socket):
 | |
|         if socket.bl_idname == self.bl_idname:
 | |
|             socket.default_value_raw = self.default_value_raw
 | |
| 
 | |
| class LnxVectorSocket(LnxCustomSocket):
 | |
|     bl_idname = 'LnxVectorSocket'
 | |
|     bl_label = 'Vector Socket'
 | |
|     lnx_socket_type = 'VECTOR'
 | |
| 
 | |
|     default_value_raw: FloatVectorProperty(
 | |
|         name='Value',
 | |
|         size=3,
 | |
|         precision=3,
 | |
|         description='Input value used for unconnected socket',
 | |
|         update=_on_update_socket
 | |
|     )
 | |
| 
 | |
|     def draw(self, context, layout, node, text):
 | |
|         if not self.is_output and not self.is_linked:
 | |
|             col = layout.column(align=True)
 | |
|             col.label(text=self.name + ":")
 | |
|             col.prop(self, 'default_value_raw', text='')
 | |
|         else:
 | |
|             layout.label(text=self.name)
 | |
| 
 | |
|     def draw_color(self, context, node):
 | |
|         return socket_colors[self.bl_idname]
 | |
| 
 | |
|     def get_default_value(self):
 | |
|         return self.default_value_raw
 | |
| 
 | |
|     def copy_defaults(self, socket):
 | |
|         if socket.bl_idname == self.bl_idname:
 | |
|             socket.default_value_raw = self.default_value_raw
 | |
| 
 | |
| class LnxAnimTreeSocket(LnxCustomSocket):
 | |
|     bl_idname = 'LnxNodeSocketAnimTree'
 | |
|     bl_label = 'Animation Tree Socket'
 | |
|     lnx_socket_type = 'NONE'
 | |
| 
 | |
|     def draw(self, context, layout, node, text):
 | |
|         layout.label(text=self.name)
 | |
| 
 | |
|     def draw_color(self, context, node):
 | |
|         return socket_colors[self.bl_idname]
 | |
| 
 | |
| class LnxFactorSocket(LnxCustomSocket):
 | |
|     bl_idname = 'LnxFactorSocket'
 | |
|     bl_label = 'Factor Socket'
 | |
|     lnx_socket_type = 'FACTOR'
 | |
| 
 | |
|     default_value_raw: FloatProperty(
 | |
|         name='Factor',
 | |
|         description='Input value used for unconnected socket in the range [0 , 1]',
 | |
|         precision=3,
 | |
|         min = 0.0,
 | |
|         max = 1.0,
 | |
|         update=_on_update_socket
 | |
|     )
 | |
| 
 | |
|     def draw(self, context, layout, node, text):
 | |
|         draw_socket_layout(self, layout)
 | |
| 
 | |
|     def draw_color(self, context, node):
 | |
|         return socket_colors[self.bl_idname]
 | |
| 
 | |
|     def get_default_value(self):
 | |
|         return self.default_value_raw
 | |
| 
 | |
| class LnxBlendSpaceSocket(LnxCustomSocket):
 | |
|     bl_idname = 'LnxBlendSpaceSocket'
 | |
|     bl_label = 'Blend Space Socket'
 | |
|     lnx_socket_type = 'FACTOR'
 | |
| 
 | |
|     default_value_raw: FloatProperty(
 | |
|         name='Factor',
 | |
|         description='Input value used for unconnected socket in the range [0 , 1]',
 | |
|         precision=3,
 | |
|         min = 0.0,
 | |
|         max = 1.0,
 | |
|         update=_on_update_socket
 | |
|     )
 | |
| 
 | |
|     def draw(self, context, layout, node, text):
 | |
|         draw_socket_layout(self, layout)
 | |
| 
 | |
|     def draw_color(self, context, node):
 | |
|         return socket_colors[self.bl_idname]
 | |
| 
 | |
|     def get_default_value(self):
 | |
|         return self.default_value_raw
 | |
| 
 | |
|     def set_default_value(self, value):
 | |
|         self.default_value_raw = value
 | |
| 
 | |
| def draw_socket_layout(socket: bpy.types.NodeSocket, layout: bpy.types.UILayout, prop_name='default_value_raw'):
 | |
|     if not socket.is_output and not socket.is_linked:
 | |
|         layout.prop(socket, prop_name, text=socket.name)
 | |
|     else:
 | |
|         layout.label(text=socket.name)
 | |
| 
 | |
| 
 | |
| def draw_socket_layout_split(socket: bpy.types.NodeSocket, layout: bpy.types.UILayout, prop_name='default_value_raw'):
 | |
|     if not socket.is_output and not socket.is_linked:
 | |
|         # Blender layouts use 0.4 splits
 | |
|         layout = layout.split(factor=0.4, align=True)
 | |
| 
 | |
|     layout.label(text=socket.name)
 | |
| 
 | |
|     if not socket.is_output and not socket.is_linked:
 | |
|         layout.prop(socket, prop_name, text='')
 | |
| 
 | |
| 
 | |
| if bpy.app.version < (4, 1, 0):
 | |
|     def _make_socket_interface(interface_name: str, bl_idname: str) -> Type[bpy.types.NodeSocketInterface]:
 | |
|         """Create a socket interface class that is used by Blender for node
 | |
|         groups. We currently don't use real node groups, but without these
 | |
|         classes Blender will (incorrectly) draw the socket borders in light grey.
 | |
|         """
 | |
|         def draw(self, context, layout):
 | |
|             pass
 | |
| 
 | |
|         def draw_color(self, context):
 | |
|             # This would be used if we were using "real" node groups
 | |
|             return 0, 0, 0, 1
 | |
| 
 | |
|         cls = type(
 | |
|             interface_name,
 | |
|             (bpy.types.NodeSocketInterface, ), {
 | |
|                 'bl_socket_idname': bl_idname,
 | |
|                 'draw': draw,
 | |
|                 'draw_color': draw_color,
 | |
|             }
 | |
|         )
 | |
|         return cls
 | |
| else:
 | |
|     def _make_socket_interface(interface_name: str, bl_idname: str) -> Type[bpy.types.NodeTreeInterfaceSocket]:
 | |
|         """Create a socket interface class that is used by Blender for node
 | |
|         groups. We currently don't use real node groups, but without these
 | |
|         classes Blender will (incorrectly) draw the socket borders in light grey.
 | |
|         """
 | |
|         def draw(self, context, layout):
 | |
|             pass
 | |
| 
 | |
|         def draw_color(self, context):
 | |
|             # This would be used if we were using "real" node groups
 | |
|             return 0, 0, 0, 1
 | |
| 
 | |
|         cls = type(
 | |
|             interface_name,
 | |
|             (bpy.types.NodeTreeInterfaceSocket, ), {
 | |
|                 'bl_socket_idname': bl_idname,
 | |
|                 'draw': draw,
 | |
|                 'draw_color': draw_color,
 | |
|             }
 | |
|         )
 | |
|         
 | |
|         return cls    
 | |
| 
 | |
| LnxActionSocketInterface = _make_socket_interface('LnxActionSocketInterface', 'LnxNodeSocketAction')
 | |
| LnxAnimSocketInterface = _make_socket_interface('LnxAnimSocketInterface', 'LnxNodeSocketAnimAction')
 | |
| LnxRotationSocketInterface = _make_socket_interface('LnxRotationSocketInterface', 'LnxRotationSocket')
 | |
| LnxArraySocketInterface = _make_socket_interface('LnxArraySocketInterface', 'LnxNodeSocketArray')
 | |
| LnxBoolSocketInterface = _make_socket_interface('LnxBoolSocketInterface', 'LnxBoolSocket')
 | |
| LnxColorSocketInterface = _make_socket_interface('LnxColorSocketInterface', 'LnxColorSocket')
 | |
| LnxDynamicSocketInterface = _make_socket_interface('LnxDynamicSocketInterface', 'LnxDynamicSocket')
 | |
| LnxFloatSocketInterface = _make_socket_interface('LnxFloatSocketInterface', 'LnxFloatSocket')
 | |
| LnxIntSocketInterface = _make_socket_interface('LnxIntSocketInterface', 'LnxIntSocket')
 | |
| LnxObjectSocketInterface = _make_socket_interface('LnxObjectSocketInterface', 'LnxNodeSocketObject')
 | |
| LnxStringSocketInterface = _make_socket_interface('LnxStringSocketInterface', 'LnxStringSocket')
 | |
| LnxVectorSocketInterface = _make_socket_interface('LnxVectorSocketInterface', 'LnxVectorSocket')
 | |
| LnxAnySocketInterface = _make_socket_interface('LnxAnySocketInterface', 'LnxAnySocket')
 | |
| LnxAnimTreeSocketInterface = _make_socket_interface('LnxAnimTreeSocketInterface', 'LnxNodeSocketAnimTree')
 | |
| LnxFactorSocketInterface = _make_socket_interface('LnxFactorSocketInterface', 'LnxFactorSocket')
 | |
| LnxBlendSpaceSocketInterface = _make_socket_interface('LnxBlendSpaceSocketInterface', 'LnxBlendSpaceSocket')
 | |
| 
 | |
| __REG_CLASSES = (
 | |
|     LnxActionSocketInterface,
 | |
|     LnxAnimSocketInterface,
 | |
|     LnxRotationSocketInterface,
 | |
|     LnxArraySocketInterface,
 | |
|     LnxBoolSocketInterface,
 | |
|     LnxColorSocketInterface,
 | |
|     LnxDynamicSocketInterface,
 | |
|     LnxFloatSocketInterface,
 | |
|     LnxIntSocketInterface,
 | |
|     LnxObjectSocketInterface,
 | |
|     LnxStringSocketInterface,
 | |
|     LnxVectorSocketInterface,
 | |
|     LnxAnySocketInterface,
 | |
|     LnxAnimTreeSocketInterface,
 | |
|     LnxFactorSocketInterface,
 | |
|     LnxBlendSpaceSocketInterface,
 | |
| 
 | |
|     LnxActionSocket,
 | |
|     LnxAnimActionSocket,
 | |
|     LnxRotationSocket,
 | |
|     LnxArraySocket,
 | |
|     LnxBoolSocket,
 | |
|     LnxColorSocket,
 | |
|     LnxDynamicSocket,
 | |
|     LnxFloatSocket,
 | |
|     LnxIntSocket,
 | |
|     LnxObjectSocket,
 | |
|     LnxStringSocket,
 | |
|     LnxVectorSocket,
 | |
|     LnxAnySocket,
 | |
|     LnxAnimTreeSocket,
 | |
|     LnxFactorSocket,
 | |
|     LnxBlendSpaceSocket,
 | |
| )
 | |
| register, unregister = bpy.utils.register_classes_factory(__REG_CLASSES)
 |