forked from LeenkxTeam/LNXSDK
		
	merge upstream
This commit is contained in:
		
							
								
								
									
										190
									
								
								leenkx/Sources/leenkx/logicnode/SetLookAtRotationNode.hx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										190
									
								
								leenkx/Sources/leenkx/logicnode/SetLookAtRotationNode.hx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,190 @@
 | 
			
		||||
package leenkx.logicnode;
 | 
			
		||||
 | 
			
		||||
import iron.math.Vec4;
 | 
			
		||||
import iron.math.Quat;
 | 
			
		||||
import iron.math.Mat4;
 | 
			
		||||
import iron.object.Object;
 | 
			
		||||
 | 
			
		||||
class SetLookAtRotationNode extends LogicNode {
 | 
			
		||||
 | 
			
		||||
	public var property0: String; // Axis to align
 | 
			
		||||
	public var property1: String; // Use vector for target (true/false)
 | 
			
		||||
	public var property2: String; // Use vector for source (true/false)
 | 
			
		||||
	public var property3: String; // Damping value (backward compatibility, now input socket)
 | 
			
		||||
	public var property4: String; // Disable rotation on aligning axis (true/false)
 | 
			
		||||
	
 | 
			
		||||
	// Store the calculated rotation for output
 | 
			
		||||
	var calculatedRotation: Quat = null;
 | 
			
		||||
	// Store the previous rotation for smooth interpolation
 | 
			
		||||
	var previousRotation: Quat = null;
 | 
			
		||||
 | 
			
		||||
	public function new(tree: LogicTree) {
 | 
			
		||||
		super(tree);
 | 
			
		||||
		previousRotation = new Quat();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	override function run(from: Int): Void {
 | 
			
		||||
		// Determine if we're using a vector or an object as source
 | 
			
		||||
		var useSourceVector: Bool = property2 == "true";
 | 
			
		||||
		var objectToUse: Object = null;
 | 
			
		||||
		var objectLoc: Vec4 = null;
 | 
			
		||||
 | 
			
		||||
		if (useSourceVector) {
 | 
			
		||||
			// Use tree.object as the object to rotate
 | 
			
		||||
			objectToUse = tree.object;
 | 
			
		||||
			if (objectToUse == null) {
 | 
			
		||||
				runOutput(0);
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Get the source location directly
 | 
			
		||||
			objectLoc = inputs[1].get();
 | 
			
		||||
			if (objectLoc == null) {
 | 
			
		||||
				runOutput(0);
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			// Get the source object (or fallback to tree.object)
 | 
			
		||||
			objectToUse = (inputs.length > 1 && inputs[1] != null) ? inputs[1].get() : tree.object;
 | 
			
		||||
			if (objectToUse == null) {
 | 
			
		||||
				runOutput(0);
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Get source object's position
 | 
			
		||||
			objectLoc = objectToUse.transform.loc;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Determine if we're using a vector or an object as target
 | 
			
		||||
		var useTargetVector: Bool = property1 == "true";
 | 
			
		||||
		var targetLoc: Vec4 = null;
 | 
			
		||||
 | 
			
		||||
		if (useTargetVector) {
 | 
			
		||||
			// Get the target location directly
 | 
			
		||||
			targetLoc = inputs[2].get();
 | 
			
		||||
			if (targetLoc == null) {
 | 
			
		||||
				runOutput(0);
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			// Get the target object
 | 
			
		||||
			var targetObject: Object = inputs[2].get();
 | 
			
		||||
			if (targetObject == null) {
 | 
			
		||||
				runOutput(0);
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
			// Get target object's position
 | 
			
		||||
			targetLoc = targetObject.transform.loc;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		// Calculate direction to target
 | 
			
		||||
		var direction = new Vec4(
 | 
			
		||||
			targetLoc.x - objectLoc.x,
 | 
			
		||||
			targetLoc.y - objectLoc.y,
 | 
			
		||||
			targetLoc.z - objectLoc.z
 | 
			
		||||
		);
 | 
			
		||||
		direction.normalize();
 | 
			
		||||
		
 | 
			
		||||
		// Calculate target rotation based on selected axis
 | 
			
		||||
		calculatedRotation = new Quat();
 | 
			
		||||
		switch (property0) {
 | 
			
		||||
			case "X":
 | 
			
		||||
				calculatedRotation.fromTo(new Vec4(1, 0, 0), direction);
 | 
			
		||||
			case "-X":
 | 
			
		||||
				calculatedRotation.fromTo(new Vec4(-1, 0, 0), direction);
 | 
			
		||||
			case "Y":
 | 
			
		||||
				calculatedRotation.fromTo(new Vec4(0, 1, 0), direction);
 | 
			
		||||
			case "-Y":
 | 
			
		||||
				calculatedRotation.fromTo(new Vec4(0, -1, 0), direction);
 | 
			
		||||
			case "Z":
 | 
			
		||||
				calculatedRotation.fromTo(new Vec4(0, 0, 1), direction);
 | 
			
		||||
			case "-Z":
 | 
			
		||||
				calculatedRotation.fromTo(new Vec4(0, 0, -1), direction);
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		// If disable rotation on aligning axis is enabled, constrain the target rotation
 | 
			
		||||
		if (property4 == "true") {
 | 
			
		||||
			// Apply constraint to the target rotation BEFORE damping to avoid jiggling
 | 
			
		||||
			var eulerAngles = calculatedRotation.toEulerOrdered("XYZ");
 | 
			
		||||
			
 | 
			
		||||
			// Set the rotation around the selected axis to 0
 | 
			
		||||
			switch (property0) {
 | 
			
		||||
				case "X", "-X":
 | 
			
		||||
					eulerAngles.x = 0.0;
 | 
			
		||||
				case "Y", "-Y":
 | 
			
		||||
					eulerAngles.y = 0.0;
 | 
			
		||||
				case "Z", "-Z":
 | 
			
		||||
					eulerAngles.z = 0.0;
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
			// Convert back to quaternion
 | 
			
		||||
			calculatedRotation.fromEulerOrdered(eulerAngles, "XYZ");
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		// Apply rotation with damping
 | 
			
		||||
		var dampingValue: Float = 0.0;
 | 
			
		||||
		
 | 
			
		||||
		// Try to get damping from input socket first (index 3), fallback to property
 | 
			
		||||
		if (inputs.length > 3 && inputs[3] != null) {
 | 
			
		||||
			var dampingInput: Dynamic = inputs[3].get();
 | 
			
		||||
			if (dampingInput != null) {
 | 
			
		||||
				dampingValue = dampingInput;
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			// Fallback to property for backward compatibility
 | 
			
		||||
			dampingValue = Std.parseFloat(property3);
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		if (dampingValue > 0.0) {
 | 
			
		||||
			// Create a fixed interpolation rate that never reaches exactly 1.0
 | 
			
		||||
			// Higher damping = slower rotation (smaller step)
 | 
			
		||||
			var step = Math.max(0.001, (1.0 - dampingValue) * 0.2); // 0.001 to 0.2 range
 | 
			
		||||
			
 | 
			
		||||
			// Get current rotation as quaternion
 | 
			
		||||
			var currentRot = new Quat().setFrom(objectToUse.transform.rot);
 | 
			
		||||
			
 | 
			
		||||
			// Calculate the difference between current and target rotation
 | 
			
		||||
			var diffQuat = new Quat();
 | 
			
		||||
			// q1 * inverse(q2) gives the rotation from q2 to q1
 | 
			
		||||
			var invCurrent = new Quat().setFrom(currentRot);
 | 
			
		||||
			invCurrent.x = -invCurrent.x;
 | 
			
		||||
			invCurrent.y = -invCurrent.y;
 | 
			
		||||
			invCurrent.z = -invCurrent.z;
 | 
			
		||||
			diffQuat.multquats(calculatedRotation, invCurrent);
 | 
			
		||||
			
 | 
			
		||||
			// Convert to axis-angle representation
 | 
			
		||||
			var axis = new Vec4();
 | 
			
		||||
			var angle = diffQuat.toAxisAngle(axis);
 | 
			
		||||
			
 | 
			
		||||
			// Apply only a portion of this rotation (step)
 | 
			
		||||
			var partialAngle = angle * step;
 | 
			
		||||
			
 | 
			
		||||
			// Create partial rotation quaternion
 | 
			
		||||
			var partialRot = new Quat().fromAxisAngle(axis, partialAngle);
 | 
			
		||||
			
 | 
			
		||||
			// Apply this partial rotation to current
 | 
			
		||||
			var newRot = new Quat();
 | 
			
		||||
			newRot.multquats(partialRot, currentRot);
 | 
			
		||||
			
 | 
			
		||||
			// Apply the new rotation
 | 
			
		||||
			objectToUse.transform.rot.setFrom(newRot);
 | 
			
		||||
		} else {
 | 
			
		||||
			// No damping, apply instant rotation
 | 
			
		||||
			objectToUse.transform.rot.setFrom(calculatedRotation);
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		objectToUse.transform.buildMatrix();
 | 
			
		||||
		
 | 
			
		||||
		runOutput(0);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Getter method for output sockets
 | 
			
		||||
	override function get(from: Int): Dynamic {
 | 
			
		||||
		// Output index 1 is the rotation socket (global rotation)
 | 
			
		||||
		if (from == 1) {
 | 
			
		||||
			return calculatedRotation;
 | 
			
		||||
		}
 | 
			
		||||
		return null;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -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