forked from LeenkxTeam/LNXSDK
add new Set Look At Rotation Node for making logic nodes great again
This commit is contained in:
@ -0,0 +1,367 @@
|
||||
from lnx.logicnode.lnx_nodes import *
|
||||
import bpy
|
||||
|
||||
class SetLookAtRotationNode(LnxLogicTreeNode):
|
||||
"""Returns a rotation that makes an object look at a target object or location"""
|
||||
bl_idname = 'LNSetLookAtRotationNode'
|
||||
bl_label = 'Set Look At Rotation'
|
||||
lnx_section = 'rotation'
|
||||
lnx_version = 1
|
||||
|
||||
use_vector: bpy.props.BoolProperty(
|
||||
name='Use Vector for Target Location',
|
||||
description='Use a vector location instead of a target object',
|
||||
default=False,
|
||||
update=lambda self, context: self.update_sockets(context)
|
||||
)
|
||||
|
||||
use_source_vector: bpy.props.BoolProperty(
|
||||
name='Use Vector for Source',
|
||||
description='Use a vector location instead of a source object',
|
||||
default=False,
|
||||
update=lambda self, context: self.update_sockets(context)
|
||||
)
|
||||
|
||||
disable_rotation_on_align_axis: bpy.props.BoolProperty(
|
||||
name='Disable Rotation on Aligning Axis',
|
||||
description='Zero out the rotation on the aligning axis after look at is applied',
|
||||
default=False,
|
||||
update=lambda self, context: self.update_sockets(context)
|
||||
)
|
||||
|
||||
damping: bpy.props.FloatProperty(
|
||||
name='Damping',
|
||||
description='Amount of damping for rotation (0.0 = instant, 1.0 = no movement)',
|
||||
default=0.0,
|
||||
min=0.0,
|
||||
max=1.0,
|
||||
step=10,
|
||||
precision=2,
|
||||
update=lambda self, context: self.update_damping_socket(context)
|
||||
)
|
||||
|
||||
# Store object references as custom properties
|
||||
source_object_name: bpy.props.StringProperty(default="")
|
||||
target_object_name: bpy.props.StringProperty(default="")
|
||||
|
||||
property0: HaxeEnumProperty(
|
||||
'property0',
|
||||
items = [('X', ' X', 'X'),
|
||||
('-X', '-X', '-X'),
|
||||
('Y', ' Y', 'Y'),
|
||||
('-Y', '-Y', '-Y'),
|
||||
('Z', ' Z', 'Z'),
|
||||
('-Z', '-Z', '-Z')],
|
||||
name='With', default='Z')
|
||||
|
||||
property1: HaxeEnumProperty(
|
||||
'property1',
|
||||
items = [('true', 'True', 'True'),
|
||||
('false', 'False', 'False')],
|
||||
name='Use Vector for Target Location', default='false')
|
||||
|
||||
property2: HaxeEnumProperty(
|
||||
'property2',
|
||||
items = [('true', 'True', 'True'),
|
||||
('false', 'False', 'False')],
|
||||
name='Use Vector for Source', default='false')
|
||||
|
||||
property3: bpy.props.StringProperty(name='Damping', default='0.0')
|
||||
|
||||
property4: HaxeEnumProperty(
|
||||
'property4',
|
||||
items = [('true', 'True', 'True'),
|
||||
('false', 'False', 'False')],
|
||||
name='Disable Rotation on Aligning Axis', default='false')
|
||||
|
||||
def lnx_init(self, context):
|
||||
# Add inputs in standard order
|
||||
self.inputs.new('LnxNodeSocketAction', 'In')
|
||||
|
||||
# Add the initial source input
|
||||
self.inputs.new('LnxNodeSocketObject', 'Source Object')
|
||||
|
||||
# Add the initial target input
|
||||
self.inputs.new('LnxNodeSocketObject', 'Target Object')
|
||||
|
||||
# Add damping input socket with default value
|
||||
damping_socket = self.inputs.new('LnxFloatSocket', 'Damping')
|
||||
damping_socket.default_value_raw = 0.0
|
||||
|
||||
# Add outputs
|
||||
self.add_output('LnxNodeSocketAction', 'Out')
|
||||
# Add rotation output socket
|
||||
self.add_output('LnxRotationSocket', 'Rotation')
|
||||
|
||||
def draw_buttons(self, context, layout):
|
||||
# 1. Axis Selector
|
||||
layout.prop(self, 'property0')
|
||||
|
||||
# 2. 'Use Vector for Source' checkbox
|
||||
layout.prop(self, 'use_source_vector')
|
||||
|
||||
# 3. 'Use Vector for Target Location' checkbox
|
||||
layout.prop(self, 'use_vector')
|
||||
|
||||
# 4. 'Disable Rotation on Aligning Axis' checkbox
|
||||
layout.prop(self, 'disable_rotation_on_align_axis')
|
||||
|
||||
# Note: Damping is now handled by the input socket
|
||||
|
||||
def update_sockets(self, context):
|
||||
# Update the Haxe properties to match the Python properties
|
||||
self.property1 = 'true' if self.use_vector else 'false'
|
||||
self.property2 = 'true' if self.use_source_vector else 'false'
|
||||
self.property3 = str(self.damping) # Keep for backward compatibility
|
||||
self.property4 = 'true' if self.disable_rotation_on_align_axis else 'false'
|
||||
|
||||
# Store current object references before changing sockets
|
||||
self.save_object_references()
|
||||
|
||||
# Helper to find a socket by name
|
||||
def find_input_socket(name):
|
||||
for i, s in enumerate(self.inputs):
|
||||
if s.name == name:
|
||||
return i, s
|
||||
return -1, None
|
||||
|
||||
# Ensure we have the 'In' socket at index 0
|
||||
in_idx, in_socket = find_input_socket('In')
|
||||
if in_idx == -1:
|
||||
# If 'In' socket is missing, add it at index 0
|
||||
self.inputs.new('LnxNodeSocketAction', 'In')
|
||||
|
||||
# Fixed indices for our sockets
|
||||
SOURCE_INDEX = 1
|
||||
TARGET_INDEX = 2
|
||||
DAMPING_INDEX = 3
|
||||
|
||||
# Get current socket information
|
||||
source_names = ['Source Object', 'Source Location']
|
||||
target_names = ['Target Object', 'Target Location']
|
||||
|
||||
# Find current source and target sockets
|
||||
source_idx = -1
|
||||
source_socket = None
|
||||
for name in source_names:
|
||||
idx, socket = find_input_socket(name)
|
||||
if idx != -1:
|
||||
source_idx = idx
|
||||
source_socket = socket
|
||||
break
|
||||
|
||||
target_idx = -1
|
||||
target_socket = None
|
||||
for name in target_names:
|
||||
idx, socket = find_input_socket(name)
|
||||
if idx != -1:
|
||||
target_idx = idx
|
||||
target_socket = socket
|
||||
break
|
||||
|
||||
# Expected types based on current settings
|
||||
expected_source_name = 'Source Location' if self.use_source_vector else 'Source Object'
|
||||
expected_source_type = 'LnxVectorSocket' if self.use_source_vector else 'LnxNodeSocketObject'
|
||||
|
||||
expected_target_name = 'Target Location' if self.use_vector else 'Target Object'
|
||||
expected_target_type = 'LnxVectorSocket' if self.use_vector else 'LnxNodeSocketObject'
|
||||
|
||||
# Ensure we have exactly 4 sockets (In, Source, Target, Damping) in the correct order
|
||||
while len(self.inputs) > 4:
|
||||
# Remove any extra sockets
|
||||
self.inputs.remove(self.inputs[-1])
|
||||
|
||||
# Make sure we have exactly 4 sockets
|
||||
while len(self.inputs) < 4:
|
||||
if len(self.inputs) == 0:
|
||||
self.inputs.new('LnxNodeSocketAction', 'In')
|
||||
elif len(self.inputs) == 1:
|
||||
self.inputs.new(expected_source_type, expected_source_name)
|
||||
elif len(self.inputs) == 2:
|
||||
self.inputs.new(expected_target_type, expected_target_name)
|
||||
elif len(self.inputs) == 3:
|
||||
damping_socket = self.inputs.new('LnxFloatSocket', 'Damping')
|
||||
damping_socket.default_value_raw = self.damping
|
||||
|
||||
# Now update the source socket if needed
|
||||
if source_socket and source_socket.name != expected_source_name:
|
||||
# Store links before removing
|
||||
links = []
|
||||
for link in source_socket.links:
|
||||
links.append(link.from_socket)
|
||||
|
||||
# Get the index where this socket should be
|
||||
correct_idx = SOURCE_INDEX
|
||||
|
||||
# Create the new socket at the correct position
|
||||
self.inputs.remove(source_socket)
|
||||
new_socket = self.inputs.new(expected_source_type, expected_source_name)
|
||||
|
||||
# Move the new socket to the correct position
|
||||
if not new_socket.is_linked: # Only move if not linked
|
||||
# Move the socket to the correct index
|
||||
if correct_idx < len(self.inputs) - 1:
|
||||
self.inputs.move(len(self.inputs) - 1, correct_idx)
|
||||
|
||||
# Restore links
|
||||
if links and hasattr(context, 'space_data') and context.space_data and hasattr(context.space_data, 'edit_tree'):
|
||||
for link_socket in links:
|
||||
context.space_data.edit_tree.links.new(link_socket, new_socket)
|
||||
|
||||
# Update the target socket if needed
|
||||
if target_socket and target_socket.name != expected_target_name:
|
||||
# Store links before removing
|
||||
links = []
|
||||
for link in target_socket.links:
|
||||
links.append(link.from_socket)
|
||||
|
||||
# Get the index where this socket should be
|
||||
correct_idx = TARGET_INDEX
|
||||
|
||||
# Create the new socket at the correct position
|
||||
self.inputs.remove(target_socket)
|
||||
new_socket = self.inputs.new(expected_target_type, expected_target_name)
|
||||
|
||||
# Move the new socket to the correct position
|
||||
if not new_socket.is_linked: # Only move if not linked
|
||||
# Move the socket to the correct index
|
||||
if correct_idx < len(self.inputs) - 1:
|
||||
self.inputs.move(len(self.inputs) - 1, correct_idx)
|
||||
|
||||
# Restore links
|
||||
if links and hasattr(context, 'space_data') and context.space_data and hasattr(context.space_data, 'edit_tree'):
|
||||
for link_socket in links:
|
||||
context.space_data.edit_tree.links.new(link_socket, new_socket)
|
||||
|
||||
# Make a final check to ensure the sockets are in the correct order
|
||||
# This is a safety measure to ensure consistent UI
|
||||
in_idx, in_socket = find_input_socket('In')
|
||||
source_idx = -1
|
||||
for name in source_names:
|
||||
idx, _ = find_input_socket(name)
|
||||
if idx != -1:
|
||||
source_idx = idx
|
||||
break
|
||||
|
||||
target_idx = -1
|
||||
for name in target_names:
|
||||
idx, _ = find_input_socket(name)
|
||||
if idx != -1:
|
||||
target_idx = idx
|
||||
break
|
||||
|
||||
damping_idx, damping_socket = find_input_socket('Damping')
|
||||
|
||||
# If the order is wrong, fix it by recreating the sockets in the correct order
|
||||
if not (in_idx == 0 and source_idx == 1 and target_idx == 2 and damping_idx == 3):
|
||||
# Store all links
|
||||
all_links = {}
|
||||
|
||||
for i, socket in enumerate(self.inputs):
|
||||
# Store links
|
||||
links = []
|
||||
for link in socket.links:
|
||||
links.append(link.from_socket)
|
||||
all_links[socket.name] = links
|
||||
|
||||
# Clear all inputs
|
||||
while len(self.inputs) > 0:
|
||||
self.inputs.remove(self.inputs[0])
|
||||
|
||||
# Recreate in the correct order
|
||||
in_socket = self.inputs.new('LnxNodeSocketAction', 'In')
|
||||
source_socket = self.inputs.new(expected_source_type, expected_source_name)
|
||||
target_socket = self.inputs.new(expected_target_type, expected_target_name)
|
||||
damping_socket = self.inputs.new('LnxFloatSocket', 'Damping')
|
||||
damping_socket.default_value_raw = self.damping
|
||||
|
||||
# Restore links
|
||||
if hasattr(context, 'space_data') and context.space_data and hasattr(context.space_data, 'edit_tree'):
|
||||
# Restore In links
|
||||
if 'In' in all_links:
|
||||
for link_socket in all_links['In']:
|
||||
context.space_data.edit_tree.links.new(link_socket, in_socket)
|
||||
|
||||
# Restore Source links
|
||||
source_links = []
|
||||
for name in source_names:
|
||||
if name in all_links:
|
||||
source_links.extend(all_links[name])
|
||||
for link_socket in source_links:
|
||||
context.space_data.edit_tree.links.new(link_socket, source_socket)
|
||||
|
||||
# Restore Target links
|
||||
target_links = []
|
||||
for name in target_names:
|
||||
if name in all_links:
|
||||
target_links.extend(all_links[name])
|
||||
for link_socket in target_links:
|
||||
context.space_data.edit_tree.links.new(link_socket, target_socket)
|
||||
|
||||
# Restore Damping links
|
||||
if 'Damping' in all_links:
|
||||
for link_socket in all_links['Damping']:
|
||||
context.space_data.edit_tree.links.new(link_socket, damping_socket)
|
||||
|
||||
# Restore object references after socket changes
|
||||
self.restore_object_references()
|
||||
|
||||
def update_damping_socket(self, context):
|
||||
"""Update the damping socket default value when the slider changes"""
|
||||
for socket in self.inputs:
|
||||
if socket.name == 'Damping' and hasattr(socket, 'default_value_raw'):
|
||||
socket.default_value_raw = self.damping
|
||||
break
|
||||
|
||||
def save_object_references(self):
|
||||
"""Save object references to custom properties"""
|
||||
# Find source and target object sockets
|
||||
for socket in self.inputs:
|
||||
if socket.name == 'Source Object' and socket.is_linked:
|
||||
for link in socket.links:
|
||||
if hasattr(link.from_node, 'item') and link.from_node.item:
|
||||
self.source_object_name = link.from_node.item.name
|
||||
|
||||
if socket.name == 'Target Object' and socket.is_linked:
|
||||
for link in socket.links:
|
||||
if hasattr(link.from_node, 'item') and link.from_node.item:
|
||||
self.target_object_name = link.from_node.item.name
|
||||
|
||||
def restore_object_references(self):
|
||||
"""Restore object references from custom properties"""
|
||||
# Only restore if we're not using vector inputs
|
||||
if not self.use_source_vector:
|
||||
# Find source object socket
|
||||
for socket in self.inputs:
|
||||
if socket.name == 'Source Object' and not socket.is_linked:
|
||||
# Try to find the object in the scene
|
||||
if self.source_object_name and self.source_object_name in bpy.context.scene.objects:
|
||||
# Find the appropriate object node in the node tree
|
||||
if hasattr(self, 'id_data') and hasattr(self.id_data, 'nodes'):
|
||||
for node in self.id_data.nodes:
|
||||
if (node.bl_idname == 'LNObjectNode' and
|
||||
hasattr(node, 'item') and
|
||||
node.item and
|
||||
node.item.name == self.source_object_name):
|
||||
# Create a link between the nodes
|
||||
if hasattr(self.id_data, 'links'):
|
||||
self.id_data.links.new(node.outputs[0], socket)
|
||||
break
|
||||
|
||||
if not self.use_vector:
|
||||
# Find target object socket
|
||||
for socket in self.inputs:
|
||||
if socket.name == 'Target Object' and not socket.is_linked:
|
||||
# Try to find the object in the scene
|
||||
if self.target_object_name and self.target_object_name in bpy.context.scene.objects:
|
||||
# Find the appropriate object node in the node tree
|
||||
if hasattr(self, 'id_data') and hasattr(self.id_data, 'nodes'):
|
||||
for node in self.id_data.nodes:
|
||||
if (node.bl_idname == 'LNObjectNode' and
|
||||
hasattr(node, 'item') and
|
||||
node.item and
|
||||
node.item.name == self.target_object_name):
|
||||
# Create a link between the nodes
|
||||
if hasattr(self.id_data, 'links'):
|
||||
self.id_data.links.new(node.outputs[0], socket)
|
||||
break
|
Reference in New Issue
Block a user