forked from LeenkxTeam/LNXSDK
add new node called Set Look at Rotation
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
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user