| 
									
										
										
										
											2025-01-22 16:18:30 +01:00
										 |  |  | import bpy | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import lnx.utils | 
					
						
							|  |  |  | import lnx.node_utils | 
					
						
							|  |  |  | from lnx.logicnode.lnx_nodes import * | 
					
						
							|  |  |  | import lnx.logicnode.miscellaneous.LN_call_group as LN_call_group | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class GroupOutputsNode(LnxLogicTreeNode): | 
					
						
							|  |  |  |     """Output for a node group.""" | 
					
						
							|  |  |  |     bl_idname = 'LNGroupOutputsNode' | 
					
						
							|  |  |  |     bl_label = 'Group Output Node' | 
					
						
							|  |  |  |     lnx_section = 'group' | 
					
						
							|  |  |  |     lnx_version = 3 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-06 10:23:47 +00:00
										 |  |  |     def __init__(self, *args, **kwargs): | 
					
						
							|  |  |  |         super(GroupOutputsNode, self).__init__(*args, **kwargs) | 
					
						
							| 
									
										
										
										
											2025-01-22 16:18:30 +01:00
										 |  |  |         self.register_id() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Active socket selected | 
					
						
							|  |  |  |     active_input: IntProperty(name='active_input', description='', default=0) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Flag to store invalid links | 
					
						
							|  |  |  |     invalid_link: BoolProperty(name='invalid_link', description='', default=False) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Override copy prevention in certain situations such as copying entire group | 
					
						
							|  |  |  |     copy_override: BoolProperty(name='copy override', description='', default=False) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def init(self, context): | 
					
						
							| 
									
										
										
										
											2025-09-28 12:44:04 -07:00
										 |  |  |         if bpy.context.space_data is not None: | 
					
						
							|  |  |  |             tree = bpy.context.space_data.edit_tree | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             return  | 
					
						
							| 
									
										
										
										
											2025-01-22 16:18:30 +01:00
										 |  |  |         node_count = 0 | 
					
						
							|  |  |  |         for node in tree.nodes: | 
					
						
							|  |  |  |             if node.bl_idname == 'LNGroupOutputsNode': | 
					
						
							|  |  |  |                 node_count += 1 | 
					
						
							|  |  |  |         if node_count > 1: | 
					
						
							|  |  |  |             lnx.log.warn("Only one group output node per node tree is allowed") | 
					
						
							|  |  |  |             self.mute = True | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             super().init(context) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Prevent copying of group node | 
					
						
							|  |  |  |     def copy(self, node): | 
					
						
							|  |  |  |         if not self.copy_override: | 
					
						
							|  |  |  |             self.mute = True | 
					
						
							|  |  |  |             self.inputs.clear() | 
					
						
							|  |  |  |         self.copy_override = False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def lnx_init(self, context): | 
					
						
							|  |  |  |         if not self.mute: | 
					
						
							|  |  |  |             self.add_socket() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Called when link is created | 
					
						
							|  |  |  |     def insert_link(self, link): | 
					
						
							|  |  |  |         to_socket = link.to_socket | 
					
						
							|  |  |  |         from_node = link.from_node | 
					
						
							|  |  |  |         from_socket = None | 
					
						
							|  |  |  |         # Recursively search for other socket in case of reroutes | 
					
						
							|  |  |  |         if from_node.type == 'REROUTE': | 
					
						
							|  |  |  |             _, from_socket = lnx.node_utils.input_get_connected_node(to_socket) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             from_socket = link.from_socket | 
					
						
							|  |  |  |         if from_socket is not None: | 
					
						
							|  |  |  |             index = self.get_socket_index(to_socket) | 
					
						
							|  |  |  |             # If socket connected to LnxAnySocket, link is invalid | 
					
						
							|  |  |  |             if from_socket.bl_idname == 'LnxAnySocket': | 
					
						
							|  |  |  |                 self.invalid_link = True | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 call_group_nodes = self.get_call_group_nodes() | 
					
						
							|  |  |  |                 for node in call_group_nodes: | 
					
						
							|  |  |  |                     # Change socket type according to the new link | 
					
						
							|  |  |  |                     node.change_output_socket(from_socket.bl_idname, index, link.to_socket.display_label) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Use update method to remove invalid links | 
					
						
							|  |  |  |     def update(self): | 
					
						
							|  |  |  |         super().update() | 
					
						
							|  |  |  |         if self.invalid_link: | 
					
						
							|  |  |  |             self.remove_invalid_links() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Called when name of the socket is changed | 
					
						
							|  |  |  |     def socket_name_update(self, socket): | 
					
						
							|  |  |  |         index = self.get_socket_index(socket) | 
					
						
							|  |  |  |         # Update socket names of the related call group nodes | 
					
						
							|  |  |  |         call_node_groups = self.get_call_group_nodes() | 
					
						
							|  |  |  |         for node in call_node_groups: | 
					
						
							|  |  |  |             out_socket = node.outputs[index] | 
					
						
							|  |  |  |             if out_socket.bl_idname == 'LnxAnySocket': | 
					
						
							|  |  |  |                 out_socket.display_label = socket.display_label | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 out_socket.name = socket.display_label | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Recursively search and remove invalid links | 
					
						
							|  |  |  |     def remove_invalid_links(self): | 
					
						
							|  |  |  |         for input in self.inputs: | 
					
						
							|  |  |  |             for link in input.links: | 
					
						
							|  |  |  |                 if link.from_socket.bl_idname == 'LnxAnySocket': | 
					
						
							|  |  |  |                     tree = self.get_tree() | 
					
						
							|  |  |  |                     tree.links.remove(link) | 
					
						
							|  |  |  |                     break | 
					
						
							|  |  |  |         self.invalid_link = False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Function to move socket up and handle the same in related call group nodes | 
					
						
							|  |  |  |     def move_socket_up(self): | 
					
						
							|  |  |  |         if self.active_input > 0: | 
					
						
							|  |  |  |             self.inputs.move(self.active_input, self.active_input - 1) | 
					
						
							|  |  |  |             call_node_groups = self.get_call_group_nodes() | 
					
						
							|  |  |  |             for nodes in call_node_groups: | 
					
						
							|  |  |  |                 nodes.outputs.move(self.active_input, self.active_input - 1) | 
					
						
							|  |  |  |             self.active_input = self.active_input - 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Function to move socket down and handle the same in related call group nodes | 
					
						
							|  |  |  |     def move_socket_down(self): | 
					
						
							|  |  |  |         if self.active_input < len(self.inputs) - 1: | 
					
						
							|  |  |  |             self.inputs.move(self.active_input, self.active_input + 1) | 
					
						
							|  |  |  |             call_node_groups = self.get_call_group_nodes() | 
					
						
							|  |  |  |             for nodes in call_node_groups: | 
					
						
							|  |  |  |                 nodes.outputs.move(self.active_input, self.active_input + 1) | 
					
						
							|  |  |  |             self.active_input = self.active_input + 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Function to recursively get related call group nodes | 
					
						
							|  |  |  |     def get_call_group_nodes(self): | 
					
						
							|  |  |  |         call_group_nodes = [] | 
					
						
							|  |  |  |         # Return empty list if node is muted | 
					
						
							|  |  |  |         if self.mute: | 
					
						
							|  |  |  |             return call_group_nodes | 
					
						
							|  |  |  |         for tree in bpy.data.node_groups: | 
					
						
							|  |  |  |             if tree.bl_idname == "LnxLogicTreeType" or tree.bl_idname == "LnxGroupTree": | 
					
						
							|  |  |  |                 for node in tree.nodes: | 
					
						
							|  |  |  |                     if node.bl_idname == 'LNCallGroupNode': | 
					
						
							|  |  |  |                         if node.group_tree == self.get_tree(): | 
					
						
							|  |  |  |                             call_group_nodes.append(node) | 
					
						
							|  |  |  |         return call_group_nodes | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Function to add a socket and handle the same in the related call group nodes | 
					
						
							|  |  |  |     def add_socket(self): | 
					
						
							|  |  |  |         self.add_input('LnxAnySocket','',) | 
					
						
							|  |  |  |         call_group_nodes = self.get_call_group_nodes() | 
					
						
							|  |  |  |         for node in call_group_nodes: | 
					
						
							|  |  |  |             node.add_output('LnxAnySocket','') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Function to remove a socket and handle the same in the related call group nodes | 
					
						
							|  |  |  |     def remove_socket(self): | 
					
						
							|  |  |  |         self.inputs.remove(self.inputs[-1]) | 
					
						
							|  |  |  |         call_group_nodes = self.get_call_group_nodes() | 
					
						
							|  |  |  |         for node in call_group_nodes: | 
					
						
							|  |  |  |             node.outputs.remove(node.outputs[-1]) | 
					
						
							|  |  |  |         if self.active_input > len(self.inputs) - 1: | 
					
						
							|  |  |  |             self.active_input = self.active_input - 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Function to add a socket at certain index and  | 
					
						
							|  |  |  |     # handle the same in the related call group nodes | 
					
						
							|  |  |  |     def add_socket_ext(self): | 
					
						
							|  |  |  |         index = self.active_input + 1 | 
					
						
							|  |  |  |         self.insert_input('LnxAnySocket', index, '') | 
					
						
							|  |  |  |         call_group_nodes = self.get_call_group_nodes() | 
					
						
							|  |  |  |         for node in call_group_nodes: | 
					
						
							|  |  |  |             node.insert_output('LnxAnySocket', index, '') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Function to remove a socket at certain index and  | 
					
						
							|  |  |  |     # handle the same in the related call group nodes | 
					
						
							|  |  |  |     def remove_socket_ext(self): | 
					
						
							|  |  |  |         self.inputs.remove(self.inputs[self.active_input]) | 
					
						
							|  |  |  |         call_group_nodes = self.get_call_group_nodes() | 
					
						
							|  |  |  |         for node in call_group_nodes: | 
					
						
							|  |  |  |             node.outputs.remove(node.outputs[self.active_input]) | 
					
						
							|  |  |  |         if self.active_input > len(self.inputs) - 1: | 
					
						
							|  |  |  |             self.active_input = len(self.inputs) - 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Handle deletion of group input node | 
					
						
							|  |  |  |     def free(self): | 
					
						
							|  |  |  |         call_group_nodes = self.get_call_group_nodes() | 
					
						
							|  |  |  |         for node in call_group_nodes: | 
					
						
							|  |  |  |             node.outputs.clear() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Draw node UI | 
					
						
							|  |  |  |     def draw_buttons(self, context, layout): | 
					
						
							|  |  |  |         if self.mute: | 
					
						
							|  |  |  |             layout.enabled = False | 
					
						
							|  |  |  |         row = layout.row(align=True) | 
					
						
							|  |  |  |         op = row.operator('lnx.node_call_func', text='New', icon='PLUS', emboss=True) | 
					
						
							|  |  |  |         op.node_index = self.get_id_str() | 
					
						
							|  |  |  |         op.callback_name = 'add_socket' | 
					
						
							|  |  |  |         if len(self.inputs) > 1: | 
					
						
							|  |  |  |             op2 = row.operator('lnx.node_call_func', text='', icon='X', emboss=True) | 
					
						
							|  |  |  |             op2.node_index = self.get_id_str() | 
					
						
							|  |  |  |             op2.callback_name = 'remove_socket' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Draw side panel UI | 
					
						
							|  |  |  |     def draw_buttons_ext(self, context, layout): | 
					
						
							|  |  |  |         if self.mute: | 
					
						
							|  |  |  |             layout.enabled = False | 
					
						
							|  |  |  |         node = context.active_node | 
					
						
							|  |  |  |         split = layout.row() | 
					
						
							|  |  |  |         split.template_list('LNX_UL_InterfaceSockets', 'IN', node, 'inputs', node, 'active_input') | 
					
						
							|  |  |  |         ops_col = split.column() | 
					
						
							|  |  |  |         add_remove_col = ops_col.column(align=True) | 
					
						
							|  |  |  |         props = add_remove_col.operator('lnx.node_call_func', icon='ADD', text="") | 
					
						
							|  |  |  |         props.node_index = self.get_id_str() | 
					
						
							|  |  |  |         props.callback_name = 'add_socket_ext' | 
					
						
							|  |  |  |         if len(self.inputs) > 1: | 
					
						
							|  |  |  |             props = add_remove_col.operator('lnx.node_call_func', icon='REMOVE', text="") | 
					
						
							|  |  |  |             props.node_index = self.get_id_str() | 
					
						
							|  |  |  |             props.callback_name = 'remove_socket_ext' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         ops_col.separator() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         up_down_col = ops_col.column(align=True) | 
					
						
							|  |  |  |         props = up_down_col.operator('lnx.node_call_func', icon='TRIA_UP', text="") | 
					
						
							|  |  |  |         props.node_index = self.get_id_str() | 
					
						
							|  |  |  |         props.callback_name = 'move_socket_up' | 
					
						
							|  |  |  |         props = up_down_col.operator('lnx.node_call_func', icon='TRIA_DOWN', text="") | 
					
						
							|  |  |  |         props.node_index = self.get_id_str() | 
					
						
							|  |  |  |         props.callback_name = 'move_socket_down' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def get_replacement_node(self, node_tree: bpy.types.NodeTree): | 
					
						
							|  |  |  |         if self.lnx_version not in (0, 1, 2): | 
					
						
							|  |  |  |             raise LookupError() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return node_tree.nodes.new('LNGroupOutputsNode') |