diff --git a/source/__init__.py b/source/__init__.py index 86d480b..06708b1 100644 --- a/source/__init__.py +++ b/source/__init__.py @@ -32,10 +32,6 @@ def register(): name="Source Socket!", default=0, description="An internal property used to store the source socket in a Lazy Connect operation") - bpy.types.NodeTreeInterfaceSocket.NWViewerSocket = BoolProperty( - name="NW Socket", - default=False, - description="An internal property used to determine if a socket is generated by the addon") operators.register() interface.register() @@ -51,5 +47,4 @@ def unregister(): del bpy.types.Scene.NWBusyDrawing del bpy.types.Scene.NWLazySource del bpy.types.Scene.NWLazyTarget - del bpy.types.Scene.NWSourceSocket - del bpy.types.NodeTreeInterfaceSocket.NWViewerSocket + del bpy.types.Scene.NWSourceSocket \ No newline at end of file diff --git a/source/operators.py b/source/operators.py index 6b541ba..f8784e3 100644 --- a/source/operators.py +++ b/source/operators.py @@ -27,11 +27,11 @@ from .interface import NWConnectionListInputs, NWConnectionListOutputs from .utils.constants import blend_types, geo_combine_operations, operations, navs, get_texture_node_types, rl_outputs from .utils.draw import draw_callback_nodeoutline from .utils.paths import match_files_to_socket_names, split_into_components -from .utils.nodes import (node_mid_pt, autolink, node_at_pos, get_nodes_links, is_viewer_socket, is_viewer_link, +from .utils.nodes import (node_mid_pt, autolink, node_at_pos, get_nodes_links, get_group_output_node, get_output_location, force_update, get_internal_socket, nw_check, nw_check_not_empty, nw_check_selected, nw_check_active, nw_check_space_type, nw_check_node_type, nw_check_visible_outputs, nw_check_viewer_node, NWBase, - get_first_enabled_output, is_visible_socket, viewer_socket_name) + get_first_enabled_output, is_visible_socket) class NWLazyMix(Operator, NWBase): """Add a Mix RGB/Shader node by interactively drawing lines between nodes""" @@ -486,323 +486,6 @@ class NWAddAttrNode(Operator, NWBase): return {'FINISHED'} -class NWPreviewNode(Operator, NWBase): - bl_idname = "node.nw_preview_node" - bl_label = "Preview Node" - bl_description = "Connect active node to the Node Group output or the Material Output" - bl_options = {'REGISTER', 'UNDO'} - - # If false, the operator is not executed if the current node group happens to be a geometry nodes group. - # This is needed because geometry nodes has its own viewer node that uses the same shortcut as in the compositor. - run_in_geometry_nodes: BoolProperty(default=True) - - def __init__(self): - self.shader_output_type = "" - self.shader_output_ident = "" - - @classmethod - def poll(cls, context): - """Already implemented natively for compositing nodes.""" - return (nw_check(cls, context) and nw_check_not_empty(cls, context) - and nw_check_space_type(cls, context, {'ShaderNodeTree', 'GeometryNodeTree'})) - - @staticmethod - def get_output_sockets(node_tree): - return [item for item in node_tree.interface.items_tree - if item.item_type == 'SOCKET' and item.in_out in {'OUTPUT', 'BOTH'}] - - def init_shader_variables(self, space, shader_type): - if shader_type == 'OBJECT': - if space.id in bpy.data.lights.values(): - self.shader_output_type = "OUTPUT_LIGHT" - self.shader_output_ident = "ShaderNodeOutputLight" - else: - self.shader_output_type = "OUTPUT_MATERIAL" - self.shader_output_ident = "ShaderNodeOutputMaterial" - - elif shader_type == 'WORLD': - self.shader_output_type = "OUTPUT_WORLD" - self.shader_output_ident = "ShaderNodeOutputWorld" - - def ensure_viewer_socket(self, node_tree, socket_type, connect_socket=None): - """Check if a viewer output already exists in a node group, otherwise create it""" - viewer_socket = None - output_sockets = self.get_output_sockets(node_tree) - if len(output_sockets): - for i, socket in enumerate(output_sockets): - if is_viewer_socket(socket) and socket.socket_type == socket_type: - # If viewer output is already used but leads to the same socket we can still use it - is_used = self.has_socket_other_users(socket) - if is_used: - if connect_socket is None: - continue - groupout = get_group_output_node(node_tree) - groupout_input = groupout.inputs[i] - links = groupout_input.links - if connect_socket not in [link.from_socket for link in links]: - continue - viewer_socket = socket - break - - if viewer_socket is None: - # Create viewer socket - viewer_socket = node_tree.interface.new_socket( - viewer_socket_name, in_out='OUTPUT', socket_type=socket_type) - viewer_socket.NWViewerSocket = True - return viewer_socket - - @staticmethod - def ensure_group_output(node_tree): - """Check if a group output node exists, otherwise create it""" - groupout = get_group_output_node(node_tree) - if groupout is None: - groupout = node_tree.nodes.new('NodeGroupOutput') - loc_x, loc_y = get_output_location(node_tree) - groupout.location.x = loc_x - groupout.location.y = loc_y - groupout.select = False - # So that we don't keep on adding new group outputs - groupout.is_active_output = True - return groupout - - @classmethod - def search_sockets(cls, node, sockets, index=None): - """Recursively scan nodes for viewer sockets and store them in a list""" - for i, input_socket in enumerate(node.inputs): - if index and i != index: - continue - if len(input_socket.links): - link = input_socket.links[0] - next_node = link.from_node - external_socket = link.from_socket - if hasattr(next_node, "node_tree"): - for socket_index, socket in enumerate(next_node.node_tree.interface.items_tree): - if socket.identifier == external_socket.identifier: - break - if is_viewer_socket(socket) and socket not in sockets: - sockets.append(socket) - # continue search inside of node group but restrict socket to where we came from - groupout = get_group_output_node(next_node.node_tree) - cls.search_sockets(groupout, sockets, index=socket_index) - - @classmethod - def scan_nodes(cls, tree, sockets): - """Recursively get all viewer sockets in a material tree""" - for node in tree.nodes: - if hasattr(node, "node_tree"): - if node.node_tree is None: - continue - for socket in cls.get_output_sockets(node.node_tree): - if is_viewer_socket(socket) and (socket not in sockets): - sockets.append(socket) - cls.scan_nodes(node.node_tree, sockets) - - @staticmethod - def remove_socket(tree, socket): - interface = tree.interface - interface.remove(socket) - interface.active_index = min(interface.active_index, len(interface.items_tree) - 1) - - def link_leads_to_used_socket(self, link): - """Return True if link leads to a socket that is already used in this node""" - socket = get_internal_socket(link.to_socket) - return socket and self.is_socket_used_active_tree(socket) - - def is_socket_used_active_tree(self, socket): - """Ensure used sockets in active node tree is calculated and check given socket""" - if not hasattr(self, "used_viewer_sockets_active_mat"): - self.used_viewer_sockets_active_mat = [] - - node_tree = bpy.context.space_data.node_tree - output_node = None - if node_tree.type == 'GEOMETRY': - output_node = get_group_output_node(node_tree) - elif node_tree.type == 'SHADER': - output_node = get_group_output_node(node_tree, - output_node_type=self.shader_output_type) - - if output_node is not None: - self.search_sockets(output_node, self.used_viewer_sockets_active_mat) - return socket in self.used_viewer_sockets_active_mat - - def has_socket_other_users(self, socket): - """List the other users for this socket (other materials or GN groups)""" - if not hasattr(self, "other_viewer_sockets_users"): - self.other_viewer_sockets_users = [] - if socket.socket_type == 'NodeSocketShader': - for mat in bpy.data.materials: - if mat.node_tree == bpy.context.space_data.node_tree or not hasattr(mat.node_tree, "nodes"): - continue - # Get viewer node - output_node = get_group_output_node(mat.node_tree, - output_node_type=self.shader_output_type) - if output_node is not None: - self.search_sockets(output_node, self.other_viewer_sockets_users) - elif socket.socket_type == 'NodeSocketGeometry': - for obj in bpy.data.objects: - for mod in obj.modifiers: - if mod.type != 'NODES' or mod.node_group == bpy.context.space_data.node_tree: - continue - # Get viewer node - output_node = get_group_output_node(mod.node_group) - if output_node is not None: - self.search_sockets(output_node, self.other_viewer_sockets_users) - return socket in self.other_viewer_sockets_users - - def get_output_index(self, node, output_node, is_base_node_tree, socket_type, check_type=False): - """Get the next available output socket in the active node""" - out_i = None - valid_outputs = [] - for i, out in enumerate(node.outputs): - if is_visible_socket(out) and (not check_type or out.type == socket_type): - valid_outputs.append(i) - if valid_outputs: - out_i = valid_outputs[0] # Start index of node's outputs - for i, valid_i in enumerate(valid_outputs): - for out_link in node.outputs[valid_i].links: - if is_viewer_link(out_link, output_node): - if is_base_node_tree or self.link_leads_to_used_socket(out_link): - if i < len(valid_outputs) - 1: - out_i = valid_outputs[i + 1] - else: - out_i = valid_outputs[0] - return out_i - - def create_links(self, path, node, active_node_socket_id, socket_type): - """Create links at each step in the node group path.""" - path = list(reversed(path)) - # Starting from the level of the active node - for path_index, path_element in enumerate(path[:-1]): - # Ensure there is a viewer node and it has an input - tree = path_element.node_tree - viewer_socket = self.ensure_viewer_socket( - tree, socket_type, - connect_socket = node.outputs[active_node_socket_id] - if path_index == 0 else None) - if viewer_socket in self.delete_sockets: - self.delete_sockets.remove(viewer_socket) - - # Connect the current to its viewer - link_start = node.outputs[active_node_socket_id] - link_end = self.ensure_group_output(tree).inputs[viewer_socket.identifier] - connect_sockets(link_start, link_end) - - # Go up in the node group hierarchy - next_tree = path[path_index + 1].node_tree - node = next(n for n in next_tree.nodes - if n.type == 'GROUP' - and n.node_tree == tree) - tree = next_tree - active_node_socket_id = viewer_socket.identifier - return node.outputs[active_node_socket_id] - - def cleanup(self): - # Delete sockets - for socket in self.delete_sockets: - if not self.has_socket_other_users(socket): - tree = socket.id_data - self.remove_socket(tree, socket) - - def invoke(self, context, event): - space = context.space_data - # Ignore operator when running in wrong context. - if self.run_in_geometry_nodes != (space.tree_type == "GeometryNodeTree"): - return {'PASS_THROUGH'} - - mlocx = event.mouse_region_x - mlocy = event.mouse_region_y - select_node = bpy.ops.node.select(location=(mlocx, mlocy), extend=False) - if 'FINISHED' not in select_node: # only run if mouse click is on a node - return {'CANCELLED'} - - base_node_tree = space.node_tree - active_tree = context.space_data.edit_tree - path = context.space_data.path - nodes = active_tree.nodes - active = nodes.active - - if not active and not any(is_visible_socket(out) for out in active.outputs): - return {'CANCELLED'} - - # Scan through all nodes in tree including nodes inside of groups to find viewer sockets - self.delete_sockets = [] - self.scan_nodes(base_node_tree, self.delete_sockets) - - if not active.outputs: - self.cleanup() - return {'CANCELLED'} - - # For geometry node trees, we just connect to the group output - if space.tree_type == "GeometryNodeTree": - socket_type = 'NodeSocketGeometry' - - # Find (or create if needed) the output of this node tree - output_node = self.ensure_group_output(base_node_tree) - - active_node_socket_index = self.get_output_index( - active, output_node, base_node_tree == active_tree, 'GEOMETRY', check_type=True - ) - # If there is no 'GEOMETRY' output type - We can't preview the node - if active_node_socket_index is None: - return {'CANCELLED'} - - # Find an input socket of the output of type geometry - output_node_socket_index = None - for i, inp in enumerate(output_node.inputs): - if inp.type == 'GEOMETRY': - output_node_socket_index = i - break - if output_node_socket_index is None: - # Create geometry socket - geometry_out_socket = base_node_tree.interface.new_socket( - 'Geometry', in_out='OUTPUT', socket_type=socket_type - ) - output_node_socket_index = geometry_out_socket.index - - # For shader node trees, we connect to a material output - elif space.tree_type == "ShaderNodeTree": - socket_type = 'NodeSocketShader' - self.init_shader_variables(space, space.shader_type) - - # Get or create material_output node - output_node = get_group_output_node(base_node_tree, - output_node_type=self.shader_output_type) - if not output_node: - output_node = base_node_tree.nodes.new(self.shader_output_ident) - output_node.location = get_output_location(base_node_tree) - output_node.select = False - - active_node_socket_index = self.get_output_index( - active, output_node, base_node_tree == active_tree, 'SHADER' - ) - - # Cancel if no socket was found. This can happen for group input - # nodes with only a virtual socket output. - if active_node_socket_index is None: - return {'CANCELLED'} - - if active.outputs[active_node_socket_index].name == "Volume": - output_node_socket_index = 1 - else: - output_node_socket_index = 0 - - # If there are no nested node groups, the link starts at the active node - node_output = active.outputs[active_node_socket_index] - if len(path) > 1: - # Recursively connect inside nested node groups and get the one from base level - node_output = self.create_links(path, active, active_node_socket_index, socket_type) - output_node_input = output_node.inputs[output_node_socket_index] - - # Connect at base level - connect_sockets(node_output, output_node_input) - - self.cleanup() - nodes.active = active - active.select = True - force_update(context) - return {'FINISHED'} - - class NWFrameSelected(Operator, NWBase): bl_idname = "node.nw_frame_selected" bl_label = "Frame Selected" @@ -2759,7 +2442,6 @@ classes = ( NWSwapLinks, NWResetBG, NWAddAttrNode, - NWPreviewNode, NWFrameSelected, NWReloadImages, NWMergeNodes, diff --git a/source/preferences.py b/source/preferences.py index f80da00..10c25e9 100644 --- a/source/preferences.py +++ b/source/preferences.py @@ -333,11 +333,6 @@ kmi_defs = ( (operators.NWFrameSelected.bl_idname, 'P', 'PRESS', False, True, False, None, "Frame selected nodes"), # Swap Links (operators.NWSwapLinks.bl_idname, 'S', 'PRESS', False, False, True, None, "Swap Links"), - # Preview Node - (operators.NWPreviewNode.bl_idname, 'LEFTMOUSE', 'PRESS', True, True, - False, (('run_in_geometry_nodes', False),), "Preview node output"), - (operators.NWPreviewNode.bl_idname, 'LEFTMOUSE', 'PRESS', False, True, - True, (('run_in_geometry_nodes', True),), "Preview node output"), # Reload Images (operators.NWReloadImages.bl_idname, 'R', 'PRESS', False, False, True, None, "Reload images"), # Lazy Mix diff --git a/source/utils/nodes.py b/source/utils/nodes.py index b39f648..665c100 100644 --- a/source/utils/nodes.py +++ b/source/utils/nodes.py @@ -143,14 +143,6 @@ def get_nodes_links(context): return tree.nodes, tree.links -viewer_socket_name = "tmp_viewer" - - -def is_viewer_socket(socket): - # checks if a internal socket is a valid viewer socket - return socket.name == viewer_socket_name and socket.NWViewerSocket - - def get_internal_socket(socket): # get the internal socket from a socket inside or outside the group node = socket.node @@ -169,16 +161,6 @@ def get_internal_socket(socket): return iterator[0] -def is_viewer_link(link, output_node): - if link.to_node == output_node and link.to_socket == output_node.inputs[0]: - return True - if link.to_node.type == 'GROUP_OUTPUT': - socket = get_internal_socket(link.to_socket) - if is_viewer_socket(socket): - return True - return False - - def get_group_output_node(tree, output_node_type='GROUP_OUTPUT'): for node in tree.nodes: if node.type == output_node_type and node.is_active_output: