Skip to content
Snippets Groups Projects
node_efficiency_tools.py 142 KiB
Newer Older
  • Learn to ignore specific revisions
  •             for node in selected:
                    for input in node.inputs:
                        if input.links:
                            src = input.links[0].from_node
                            node.label = src.label
                            break
            elif option == 'FROM_SOCKET':
                selected = [n for n in nodes if n.select]
                for node in selected:
                    for input in node.inputs:
                        if input.links:
                            src = input.links[0].from_socket
                            node.label = src.name
                            break
    
            return {'FINISHED'}
    
    
    
    class NWClearLabel(Operator, NWBase):
        bl_idname = "node.nw_clear_label"
    
        bl_label = "Clear Label"
        bl_options = {'REGISTER', 'UNDO'}
    
        option = BoolProperty()
    
        def execute(self, context):
            nodes, links = get_nodes_links(context)
            for node in [n for n in nodes if n.select]:
                node.label = ''
    
            return {'FINISHED'}
    
        def invoke(self, context, event):
            if self.option:
                return self.execute(context)
            else:
                return context.window_manager.invoke_confirm(self, event)
    
    
    
    class NWModifyLabels(Operator, NWBase):
        """Modify Labels of all selected nodes."""
        bl_idname = "node.nw_modify_labels"
        bl_label = "Modify Labels"
        bl_options = {'REGISTER', 'UNDO'}
    
        prepend = StringProperty(
            name="Add to Beginning"
        )
        append = StringProperty(
            name="Add to End"
        )
        replace_from = StringProperty(
            name="Text to Replace"
        )
        replace_to = StringProperty(
            name="Replace with"
        )
    
        def execute(self, context):
            nodes, links = get_nodes_links(context)
            for node in [n for n in nodes if n.select]:
                node.label = self.prepend + node.label.replace(self.replace_from, self.replace_to) + self.append
    
            return {'FINISHED'}
    
        def invoke(self, context, event):
            self.prepend = ""
            self.append = ""
            self.remove = ""
            return context.window_manager.invoke_props_dialog(self)
    
    
    class NWAddTextureSetup(Operator, NWBase):
        bl_idname = "node.nw_add_texture"
    
        bl_label = "Texture Setup"
        bl_description = "Add Texture Node Setup to Selected Shaders"
        bl_options = {'REGISTER', 'UNDO'}
    
        @classmethod
        def poll(cls, context):
            space = context.space_data
            valid = False
            if space.type == 'NODE_EDITOR':
                if space.tree_type == 'ShaderNodeTree' and space.node_tree is not None:
                    valid = True
            return valid
    
        def execute(self, context):
            nodes, links = get_nodes_links(context)
            active = nodes.active
    
            shader_types = [x[1] for x in shaders_shader_nodes_props if x[1] not in {'MIX_SHADER', 'ADD_SHADER'}]
            texture_types = [x[1] for x in shaders_texture_nodes_props]
    
            valid = False
            if active:
                if active.select:
    
                    if active.type in shader_types or active.type in texture_types:
    
                        if not active.inputs[0].is_linked:
                            valid = True
            if valid:
                locx = active.location.x
                locy = active.location.y
    
    
                xoffset = [500.0, 700.0]
                isshader = True
                if active.type not in shader_types:
                    xoffset = [290.0, 500.0]
                    isshader = False
    
                coordout = 2
                image_type = 'ShaderNodeTexImage'
    
                if (active.type in texture_types and active.type != 'TEX_IMAGE') or (active.type == 'BACKGROUND'):
                    coordout = 0  # image texture uses UVs, procedural textures and Background shader use Generated
                    if active.type == 'BACKGROUND':
                        image_type = 'ShaderNodeTexEnvironment'
    
                if isshader:
                    tex = nodes.new(image_type)
                    tex.location = [locx - 200.0, locy + 28.0]
    
    
                map = nodes.new('ShaderNodeMapping')
    
                map.location = [locx - xoffset[0], locy + 80.0]
                map.width = 240
    
                coord = nodes.new('ShaderNodeTexCoord')
    
                coord.location = [locx - xoffset[1], locy + 40.0]
    
                active.select = False
    
    
                if isshader:
                    nodes.active = tex
                    links.new(tex.outputs[0], active.inputs[0])
                    links.new(map.outputs[0], tex.inputs[0])
                    links.new(coord.outputs[coordout], map.inputs[0])
    
                else:
                    nodes.active = map
                    links.new(map.outputs[0], active.inputs[0])
                    links.new(coord.outputs[coordout], map.inputs[0])
    
    class NWAddReroutes(Operator, NWBase):
        """Add Reroute Nodes and link them to outputs of selected nodes"""
        bl_idname = "node.nw_add_reroutes"
    
        bl_label = "Add Reroutes"
        bl_description = "Add Reroutes to Outputs"
        bl_options = {'REGISTER', 'UNDO'}
    
        option = EnumProperty(
    
            name="option",
            items=[
                ('ALL', 'to all', 'Add to all outputs'),
                ('LOOSE', 'to loose', 'Add only to loose outputs'),
                ('LINKED', 'to linked', 'Add only to linked outputs'),
            ]
        )
    
    
        def execute(self, context):
            tree_type = context.space_data.node_tree.type
            option = self.option
            nodes, links = get_nodes_links(context)
            # output valid when option is 'all' or when 'loose' output has no links
            valid = False
            post_select = []  # nodes to be selected after execution
            # create reroutes and recreate links
            for node in [n for n in nodes if n.select]:
                if node.outputs:
                    x = node.location.x
                    y = node.location.y
                    width = node.width
                    # unhide 'REROUTE' nodes to avoid issues with location.y
                    if node.type == 'REROUTE':
                        node.hide = False
                    # When node is hidden - width_hidden not usable.
                    # Hack needed to calculate real width
                    if node.hide:
                        bpy.ops.node.select_all(action='DESELECT')
                        helper = nodes.new('NodeReroute')
                        helper.select = True
                        node.select = True
                        # resize node and helper to zero. Then check locations to calculate width
                        bpy.ops.transform.resize(value=(0.0, 0.0, 0.0))
                        width = 2.0 * (helper.location.x - node.location.x)
                        # restore node location
                        node.location = x, y
                        # delete helper
                        node.select = False
                        # only helper is selected now
                        bpy.ops.node.delete()
                    x = node.location.x + width + 20.0
                    if node.type != 'REROUTE':
                        y -= 35.0
                    y_offset = -22.0
                    loc = x, y
                reroutes_count = 0  # will be used when aligning reroutes added to hidden nodes
                for out_i, output in enumerate(node.outputs):
                    pass_used = False  # initial value to be analyzed if 'R_LAYERS'
                    # if node is not 'R_LAYERS' - "pass_used" not needed, so set it to True
                    if node.type != 'R_LAYERS':
                        pass_used = True
                    else:  # if 'R_LAYERS' check if output represent used render pass
                        node_scene = node.scene
                        node_layer = node.layer
                        # If output - "Alpha" is analyzed - assume it's used. Not represented in passes.
                        if output.name == 'Alpha':
                            pass_used = True
                        else:
                            # check entries in global 'rl_outputs' variable
                            for render_pass, out_name, exr_name, in_internal, in_cycles in rl_outputs:
                                if output.name == out_name:
                                    pass_used = getattr(node_scene.render.layers[node_layer], render_pass)
                                    break
                    if pass_used:
                        valid = ((option == 'ALL') or
                                 (option == 'LOOSE' and not output.links) or
                                 (option == 'LINKED' and output.links))
                        # Add reroutes only if valid, but offset location in all cases.
                        if valid:
                            n = nodes.new('NodeReroute')
                            nodes.active = n
                            for link in output.links:
                                links.new(n.outputs[0], link.to_socket)
                            links.new(output, n.inputs[0])
                            n.location = loc
                            post_select.append(n)
                        reroutes_count += 1
                        y += y_offset
                        loc = x, y
                # disselect the node so that after execution of script only newly created nodes are selected
                node.select = False
                # nicer reroutes distribution along y when node.hide
                if node.hide:
                    y_translate = reroutes_count * y_offset / 2.0 - y_offset - 35.0
                    for reroute in [r for r in nodes if r.select]:
                        reroute.location.y -= y_translate
                for node in post_select:
                    node.select = True
    
            return {'FINISHED'}
    
    
    
    class NWLinkActiveToSelected(Operator, NWBase):
        """Link active node to selected nodes basing on various criteria"""
        bl_idname = "node.nw_link_active_to_selected"
    
        bl_label = "Link Active Node to Selected"
        bl_options = {'REGISTER', 'UNDO'}
    
        replace = BoolProperty()
        use_node_name = BoolProperty()
        use_outputs_names = BoolProperty()
    
        @classmethod
        def poll(cls, context):
            space = context.space_data
            valid = False
            if space.type == 'NODE_EDITOR':
                if space.node_tree is not None and context.active_node is not None:
                    if context.active_node.select:
                        valid = True
            return valid
    
        def execute(self, context):
            nodes, links = get_nodes_links(context)
            replace = self.replace
            use_node_name = self.use_node_name
            use_outputs_names = self.use_outputs_names
            active = nodes.active
            selected = [node for node in nodes if node.select and node != active]
            outputs = []  # Only usable outputs of active nodes will be stored here.
            for out in active.outputs:
                if active.type != 'R_LAYERS':
                    outputs.append(out)
                else:
                    # 'R_LAYERS' node type needs special handling.
                    # outputs of 'R_LAYERS' are callable even if not seen in UI.
                    # Only outputs that represent used passes should be taken into account
                    # Check if pass represented by output is used.
                    # global 'rl_outputs' list will be used for that
                    for render_pass, out_name, exr_name, in_internal, in_cycles in rl_outputs:
                        pass_used = False  # initial value. Will be set to True if pass is used
                        if out.name == 'Alpha':
                            # Alpha output is always present. Doesn't have representation in render pass. Assume it's used.
                            pass_used = True
                        elif out.name == out_name:
                            # example 'render_pass' entry: 'use_pass_uv' Check if True in scene render layers
                            pass_used = getattr(active.scene.render.layers[active.layer], render_pass)
                            break
                    if pass_used:
                        outputs.append(out)
            doit = True  # Will be changed to False when links successfully added to previous output.
            for out in outputs:
                if doit:
                    for node in selected:
                        dst_name = node.name  # Will be compared with src_name if needed.
                        # When node has label - use it as dst_name
                        if node.label:
                            dst_name = node.label
                        valid = True  # Initial value. Will be changed to False if names don't match.
                        src_name = dst_name  # If names not used - this asignment will keep valid = True.
                        if use_node_name:
                            # Set src_name to source node name or label
                            src_name = active.name
                            if active.label:
                                src_name = active.label
                        elif use_outputs_names:
                            src_name = (out.name, )
                            for render_pass, out_name, exr_name, in_internal, in_cycles in rl_outputs:
                                if out.name in {out_name, exr_name}:
                                    src_name = (out_name, exr_name)
                        if dst_name not in src_name:
                            valid = False
                        if valid:
                            for input in node.inputs:
                                if input.type == out.type or node.type == 'REROUTE':
                                    if replace or not input.is_linked:
                                        links.new(out, input)
                                        if not use_node_name and not use_outputs_names:
                                            doit = False
                                        break
    
            return {'FINISHED'}
    
    
    
    class NWAlignNodes(Operator, NWBase):
        bl_idname = "node.nw_align_nodes"
    
        bl_label = "Align nodes"
        bl_options = {'REGISTER', 'UNDO'}
    
        # option: 'Vertically', 'Horizontally'
        option = EnumProperty(
    
            name="option",
            description="Direction",
            items=(
                ('AXIS_X', "Align Vertically", 'Align Vertically'),
                ('AXIS_Y', "Aligh Horizontally", 'Aligh Horizontally'),
            )
        )
    
    
        def execute(self, context):
            nodes, links = get_nodes_links(context)
            selected = []  # entry = [index, loc.x, loc.y, width, height]
            frames_reselect = []  # entry = frame node. will be used to reselect all selected frames
            active = nodes.active
            for i, node in enumerate(nodes):
    
                total_w = 0.0  # total width of all nodes. Will be calculated later.
                total_h = 0.0  # total height of all nodes. Will be calculated later
    
                if node.select:
                    if node.type == 'FRAME':
                        node.select = False
                        frames_reselect.append(i)
                    else:
                        locx = node.location.x
                        locy = node.location.y
    
                        width = node.dimensions[0]
                        height = node.dimensions[1]
                        total_w += width  # add nodes[i] width to total width of all nodes
                        total_h += height  # add nodes[i] height to total height of all nodes
                        # calculate relative locations
    
                        parent = node.parent
                        while parent is not None:
                            locx += parent.location.x
                            locy += parent.location.y
                            parent = parent.parent
    
                        selected.append([i, locx, locy, width, height])
    
            count = len(selected)
            if count > 1:  # aligning makes sense only if at least 2 nodes are selected
                selected_sorted_x = sorted(selected, key=lambda k: (k[1], -k[2]))
                selected_sorted_y = sorted(selected, key=lambda k: (-k[2], k[1]))
                min_x = selected_sorted_x[0][1]  # min loc.x
                min_x_loc_y = selected_sorted_x[0][2]  # loc y of node with min loc x
                min_x_w = selected_sorted_x[0][3]  # width of node with max loc x
                max_x = selected_sorted_x[count - 1][1]  # max loc.x
                max_x_loc_y = selected_sorted_x[count - 1][2]  # loc y of node with max loc.x
                max_x_w = selected_sorted_x[count - 1][3]  # width of node with max loc.x
                min_y = selected_sorted_y[0][2]  # min loc.y
                min_y_loc_x = selected_sorted_y[0][1]  # loc.x of node with min loc.y
                min_y_h = selected_sorted_y[0][4]  # height of node with min loc.y
                min_y_w = selected_sorted_y[0][3]  # width of node with min loc.y
                max_y = selected_sorted_y[count - 1][2]  # max loc.y
                max_y_loc_x = selected_sorted_y[count - 1][1]  # loc x of node with max loc.y
                max_y_w = selected_sorted_y[count - 1][3]  # width of node with max loc.y
                max_y_h = selected_sorted_y[count - 1][4]  # height of node with max loc.y
    
    
                if self.option == 'AXIS_Y':  # Horizontally. Equivelent of s -> x -> 0 with even spacing.
    
                    loc_x = min_x
                    #loc_y = (max_x_loc_y + min_x_loc_y) / 2.0
                    loc_y = (max_y - max_y_h / 2.0 + min_y - min_y_h / 2.0) / 2.0
                    offset_x = (max_x - min_x - total_w + max_x_w) / (count - 1)
                    for i, x, y, w, h in selected_sorted_x:
                        nodes[i].location.x = loc_x
                        nodes[i].location.y = loc_y + h / 2.0
                        parent = nodes[i].parent
                        while parent is not None:
                            nodes[i].location.x -= parent.location.x
                            nodes[i].location.y -= parent.location.y
                            parent = parent.parent
                        loc_x += offset_x + w
                else:  # if self.option == 'AXIS_Y'
                    loc_x = (max_x + max_x_w / 2.0 + min_x + min_x_w / 2.0) / 2.0
                    loc_y = min_y
                    offset_y = (max_y - min_y + total_h - min_y_h) / (count - 1)
                    for i, x, y, w, h in selected_sorted_y:
                        nodes[i].location.x = loc_x - w / 2.0
                        nodes[i].location.y = loc_y
                        parent = nodes[i].parent
                        while parent is not None:
                            nodes[i].location.x -= parent.location.x
                            nodes[i].location.y -= parent.location.y
                            parent = parent.parent
                        loc_y += offset_y - h
    
                # reselect selected frames
                for i in frames_reselect:
                    nodes[i].select = True
                # restore active node
                nodes.active = active
    
            return {'FINISHED'}
    
    
    
    class NWSelectParentChildren(Operator, NWBase):
        bl_idname = "node.nw_select_parent_child"
    
        bl_label = "Select Parent or Children"
        bl_options = {'REGISTER', 'UNDO'}
    
        option = EnumProperty(
    
            name="option",
            items=(
                ('PARENT', 'Select Parent', 'Select Parent Frame'),
                ('CHILD', 'Select Children', 'Select members of selected frame'),
            )
        )
    
    
        def execute(self, context):
            nodes, links = get_nodes_links(context)
            option = self.option
            selected = [node for node in nodes if node.select]
            if option == 'PARENT':
                for sel in selected:
                    parent = sel.parent
                    if parent:
                        parent.select = True
            else:  # option == 'CHILD'
                for sel in selected:
                    children = [node for node in nodes if node.parent == sel]
                    for kid in children:
                        kid.select = True
    
            return {'FINISHED'}
    
    
    
    class NWDetachOutputs(Operator, NWBase):
        """Detach outputs of selected node leaving inluts liked"""
        bl_idname = "node.nw_detach_outputs"
    
        bl_label = "Detach Outputs"
    
    Bartek Skorupa's avatar
    Bartek Skorupa committed
        bl_options = {'REGISTER', 'UNDO'}
    
    Bartek Skorupa's avatar
    Bartek Skorupa committed
        def execute(self, context):
            nodes, links = get_nodes_links(context)
            selected = context.selected_nodes
            bpy.ops.node.duplicate_move_keep_inputs()
            new_nodes = context.selected_nodes
            bpy.ops.node.select_all(action="DESELECT")
            for node in selected:
                node.select = True
            bpy.ops.node.delete_reconnect()
            for new_node in new_nodes:
                new_node.select = True
    
            bpy.ops.transform.translate('INVOKE_DEFAULT')
    
    Bartek Skorupa's avatar
    Bartek Skorupa committed
            return {'FINISHED'}
    
    
    class NWLinkToOutputNode(Operator, NWBase):
        """Link to Composite node or Material Output node"""
        bl_idname = "node.nw_link_out"
        bl_label = "Connect to Output"
    
    Bartek Skorupa's avatar
    Bartek Skorupa committed
        bl_options = {'REGISTER', 'UNDO'}
    
    Bartek Skorupa's avatar
    Bartek Skorupa committed
        @classmethod
        def poll(cls, context):
    
            return (space.type == 'NODE_EDITOR' and space.node_tree is not None and context.active_node is not None)
    
    Bartek Skorupa's avatar
    Bartek Skorupa committed
        def execute(self, context):
            nodes, links = get_nodes_links(context)
            active = nodes.active
            output_node = None
    
            tree_type = context.space_data.tree_type
            output_types_shaders = [x[1] for x in shaders_output_nodes_props]
            output_types_compo = ['COMPOSITE']
            output_types = output_types_shaders + output_types_compo
    
    Bartek Skorupa's avatar
    Bartek Skorupa committed
            for node in nodes:
    
                if node.type in output_types:
    
    Bartek Skorupa's avatar
    Bartek Skorupa committed
                    output_node = node
                    break
            if not output_node:
                bpy.ops.node.select_all(action="DESELECT")
    
                if tree_type == 'ShaderNodeTree':
    
    Bartek Skorupa's avatar
    Bartek Skorupa committed
                    output_node = nodes.new('ShaderNodeOutputMaterial')
    
                elif tree_type == 'CompositorNodeTree':
    
    Bartek Skorupa's avatar
    Bartek Skorupa committed
                    output_node = nodes.new('CompositorNodeComposite')
    
                output_node.location.x = active.location.x + active.dimensions.x + 80
                output_node.location.y = active.location.y
    
    Bartek Skorupa's avatar
    Bartek Skorupa committed
            if (output_node and active.outputs):
                output_index = 0
                for i, output in enumerate(active.outputs):
                    if output.type == output_node.inputs[0].type:
                        output_index = i
                        break
    
    
                out_input_index = 0
                if tree_type == 'ShaderNodeTree':
                    if active.outputs[output_index].type != 'SHADER':  # connect to displacement if not a shader
                        out_input_index = 2
                links.new(active.outputs[output_index], output_node.inputs[out_input_index])
    
            hack_force_update(context, nodes)  # viewport render does not update
    
    Bartek Skorupa's avatar
    Bartek Skorupa committed
    
            return {'FINISHED'}
    
    
    
    class NWMakeLink(Operator, NWBase):
        """Make a link from one socket to another"""
        bl_idname = 'node.nw_make_link'
        bl_label = 'Make Link'
        bl_options = {'REGISTER', 'UNDO'}
        from_socket = IntProperty()
        to_socket = IntProperty()
    
        @classmethod
        def poll(cls, context):
            snode = context.space_data
            return (snode.type == 'NODE_EDITOR' and snode.node_tree is not None)
    
        def execute(self, context):
            nodes, links = get_nodes_links(context)
    
            n1 = nodes[context.scene.NWLazySource]
            n2 = nodes[context.scene.NWLazyTarget]
    
            links.new(n1.outputs[self.from_socket], n2.inputs[self.to_socket])
    
            hack_force_update(context, nodes)
    
            return {'FINISHED'}
    
    
    class NWCallInputsMenu(Operator, NWBase):
        """Link from this output"""
        bl_idname = 'node.nw_call_inputs_menu'
        bl_label = 'Make Link'
        bl_options = {'REGISTER', 'UNDO'}
        from_socket = IntProperty()
    
        @classmethod
        def poll(cls, context):
            snode = context.space_data
            return (snode.type == 'NODE_EDITOR' and snode.node_tree is not None)
    
        def execute(self, context):
            nodes, links = get_nodes_links(context)
    
            context.scene.NWSourceSocket = self.from_socket
    
            n1 = nodes[context.scene.NWLazySource]
            n2 = nodes[context.scene.NWLazyTarget]
            if len(n2.inputs) > 1:
                bpy.ops.wm.call_menu("INVOKE_DEFAULT", name=NWConnectionListInputs.bl_idname)
            elif len(n2.inputs) == 1:
                links.new(n1.outputs[self.from_socket], n2.inputs[0])
            return {'FINISHED'}
    
    
    
    def drawlayout(context, layout, mode='non-panel'):
        tree_type = context.space_data.tree_type
    
        col = layout.column(align=True)
        col.menu(NWMergeNodesMenu.bl_idname)
        col.separator()
    
        col = layout.column(align=True)
        col.menu(NWSwitchNodeTypeMenu.bl_idname, text="Switch Node Type")
        col.separator()
    
        if tree_type == 'ShaderNodeTree':
            col = layout.column(align=True)
            col.operator(NWAddTextureSetup.bl_idname, text="Add Texture Setup", icon='NODE_SEL')
            col.separator()
    
        col = layout.column(align=True)
        col.operator(NWDetachOutputs.bl_idname, icon='UNLINKED')
        col.operator(NWSwapOutputs.bl_idname)
        col.menu(NWAddReroutesMenu.bl_idname, text="Add Reroutes", icon='LAYER_USED')
        col.separator()
    
        col = layout.column(align=True)
        col.menu(NWLinkActiveToSelectedMenu.bl_idname, text="Link Active To Selected", icon='LINKED')
        col.operator(NWLinkToOutputNode.bl_idname, icon='DRIVER')
        col.separator()
    
        col = layout.column(align=True)
        if mode == 'panel':
            row = col.row(align=True)
            row.operator(NWClearLabel.bl_idname).option = True
            row.operator(NWModifyLabels.bl_idname)
        else:
            col.operator(NWClearLabel.bl_idname).option = True
            col.operator(NWModifyLabels.bl_idname)
        col.menu(NWBatchChangeNodesMenu.bl_idname, text="Batch Change")
        col.separator()
        col.menu(NWCopyToSelectedMenu.bl_idname, text="Copy to Selected")
        col.separator()
    
        col = layout.column(align=True)
        if tree_type == 'CompositorNodeTree':
            col.operator(NWResetBG.bl_idname, icon='ZOOM_PREVIOUS')
        col.operator(NWReloadImages.bl_idname, icon='FILE_REFRESH')
        col.separator()
    
        col = layout.column(align=True)
        col.operator(NWFrameSelected.bl_idname, icon='STICKY_UVS_LOC')
        col.separator()
    
        col = layout.column(align=True)
        col.operator(NWDeleteUnused.bl_idname, icon='CANCEL')
        col.separator()
    
    
    class NodeWranglerPanel(Panel, NWBase):
        bl_idname = "NODE_PT_nw_node_wrangler"
    
        bl_space_type = 'NODE_EDITOR'
        bl_region_type = 'UI'
    
        bl_label = "Node Wrangler"
    
        prepend = StringProperty(
            name='prepend',
        )
        append = StringProperty()
        remove = StringProperty()
    
    
        def draw(self, context):
    
            self.layout.label(text="(Quick access: Ctrl+Space)")
            drawlayout(context, self.layout, mode='panel')
    
    #
    #  M E N U S
    #
    class NodeWranglerMenu(Menu, NWBase):
        bl_idname = "NODE_MT_nw_node_wrangler_menu"
        bl_label = "Node Wrangler"
    
    
        def draw(self, context):
    
            drawlayout(context, self.layout)
    
    
    class NWMergeNodesMenu(Menu, NWBase):
        bl_idname = "NODE_MT_nw_merge_nodes_menu"
    
        bl_label = "Merge Selected Nodes"
    
        def draw(self, context):
            type = context.space_data.tree_type
            layout = self.layout
            if type == 'ShaderNodeTree':
    
                layout.menu(NWMergeShadersMenu.bl_idname, text="Use Shaders")
            layout.menu(NWMergeMixMenu.bl_idname, text="Use Mix Nodes")
            layout.menu(NWMergeMathMenu.bl_idname, text="Use Math Nodes")
    
    class NWMergeShadersMenu(Menu, NWBase):
        bl_idname = "NODE_MT_nw_merge_shaders_menu"
    
        bl_label = "Merge Selected Nodes using Shaders"
    
        def draw(self, context):
            layout = self.layout
    
            for type in ('MIX', 'ADD'):
                props = layout.operator(NWMergeNodes.bl_idname, text=type)
    
                props.mode = type
                props.merge_type = 'SHADER'
    
    
    
    class NWMergeMixMenu(Menu, NWBase):
        bl_idname = "NODE_MT_nw_merge_mix_menu"
    
        bl_label = "Merge Selected Nodes using Mix"
    
        def draw(self, context):
            layout = self.layout
            for type, name, description in blend_types:
    
                props = layout.operator(NWMergeNodes.bl_idname, text=name)
    
                props.mode = type
                props.merge_type = 'MIX'
    
    
    
    class NWConnectionListOutputs(Menu, NWBase):
        bl_idname = "NODE_MT_nw_connection_list_out"
        bl_label = "From:"
    
        def draw(self, context):
            layout = self.layout
            nodes, links = get_nodes_links(context)
    
            n1 = nodes[context.scene.NWLazySource]
    
            if n1.type == "R_LAYERS":
                index=0
                for o in n1.outputs:
                    if o.enabled:  # Check which passes the render layer has enabled
                        layout.operator(NWCallInputsMenu.bl_idname, text=o.name, icon="RADIOBUT_OFF").from_socket=index
                    index+=1
            else:
                index=0
                for o in n1.outputs:
                    layout.operator(NWCallInputsMenu.bl_idname, text=o.name, icon="RADIOBUT_OFF").from_socket=index
                    index+=1
    
    
    class NWConnectionListInputs(Menu, NWBase):
        bl_idname = "NODE_MT_nw_connection_list_in"
        bl_label = "To:"
    
        def draw(self, context):
            layout = self.layout
            nodes, links = get_nodes_links(context)
    
            n2 = nodes[context.scene.NWLazyTarget]
    
            #print (self.from_socket)
    
            index = 0
            for i in n2.inputs:
                op = layout.operator(NWMakeLink.bl_idname, text=i.name, icon="FORWARD")
                op.from_socket = context.scene.NWSourceSocket
                op.to_socket = index
                index+=1
    
    
    
    class NWMergeMathMenu(Menu, NWBase):
        bl_idname = "NODE_MT_nw_merge_math_menu"
    
        bl_label = "Merge Selected Nodes using Math"
    
        def draw(self, context):
            layout = self.layout
            for type, name, description in operations:
    
                props = layout.operator(NWMergeNodes.bl_idname, text=name)
    
                props.mode = type
                props.merge_type = 'MATH'
    
    
    
    class NWBatchChangeNodesMenu(Menu, NWBase):
        bl_idname = "NODE_MT_nw_batch_change_nodes_menu"
    
        bl_label = "Batch Change Selected Nodes"
    
        def draw(self, context):
            layout = self.layout
    
            layout.menu(NWBatchChangeBlendTypeMenu.bl_idname)
            layout.menu(NWBatchChangeOperationMenu.bl_idname)
    
    class NWBatchChangeBlendTypeMenu(Menu, NWBase):
        bl_idname = "NODE_MT_nw_batch_change_blend_type_menu"
    
        bl_label = "Batch Change Blend Type"
    
        def draw(self, context):
            layout = self.layout
            for type, name, description in blend_types:
    
                props = layout.operator(NWBatchChangeNodes.bl_idname, text=name)
    
                props.blend_type = type
                props.operation = 'CURRENT'
    
    
    
    class NWBatchChangeOperationMenu(Menu, NWBase):
        bl_idname = "NODE_MT_nw_batch_change_operation_menu"
    
        bl_label = "Batch Change Math Operation"
    
        def draw(self, context):
            layout = self.layout
            for type, name, description in operations:
    
                props = layout.operator(NWBatchChangeNodes.bl_idname, text=name)
    
                props.blend_type = 'CURRENT'
                props.operation = type
    
    
    
    class NWCopyToSelectedMenu(Menu, NWBase):
        bl_idname = "NODE_MT_nw_copy_node_properties_menu"
    
        bl_label = "Copy to Selected"
    
        def draw(self, context):
            layout = self.layout
    
            layout.operator(NWCopySettings.bl_idname, text="Settings from Active")
            layout.menu(NWCopyLabelMenu.bl_idname)
    
    class NWCopyLabelMenu(Menu, NWBase):
        bl_idname = "NODE_MT_nw_copy_label_menu"
    
        bl_label = "Copy Label"
    
        def draw(self, context):
            layout = self.layout
    
            layout.operator(NWCopyLabel.bl_idname, text="from Active Node's Label").option = 'FROM_ACTIVE'
            layout.operator(NWCopyLabel.bl_idname, text="from Linked Node's Label").option = 'FROM_NODE'
            layout.operator(NWCopyLabel.bl_idname, text="from Linked Output's Name").option = 'FROM_SOCKET'
    
    class NWAddReroutesMenu(Menu, NWBase):
        bl_idname = "NODE_MT_nw_add_reroutes_menu"
    
        bl_label = "Add Reroutes"
        bl_description = "Add Reroute Nodes to Selected Nodes' Outputs"
    
        def draw(self, context):
            layout = self.layout
    
            layout.operator(NWAddReroutes.bl_idname, text="to All Outputs").option = 'ALL'
            layout.operator(NWAddReroutes.bl_idname, text="to Loose Outputs").option = 'LOOSE'
            layout.operator(NWAddReroutes.bl_idname, text="to Linked Outputs").option = 'LINKED'
    
    class NWLinkActiveToSelectedMenu(Menu, NWBase):
        bl_idname = "NODE_MT_nw_link_active_to_selected_menu"
    
        bl_label = "Link Active to Selected"
    
        def draw(self, context):
            layout = self.layout
    
            layout.menu(NWLinkStandardMenu.bl_idname)
            layout.menu(NWLinkUseNodeNameMenu.bl_idname)
            layout.menu(NWLinkUseOutputsNamesMenu.bl_idname)
    
    class NWLinkStandardMenu(Menu, NWBase):
        bl_idname = "NODE_MT_nw_link_standard_menu"
    
        bl_label = "To All Selected"
    
        def draw(self, context):
            layout = self.layout
    
            props = layout.operator(NWLinkActiveToSelected.bl_idname, text="Don't Replace Links")
    
            props.replace = False
            props.use_node_name = False
            props.use_outputs_names = False
    
            props = layout.operator(NWLinkActiveToSelected.bl_idname, text="Replace Links")
    
            props.replace = True
            props.use_node_name = False
            props.use_outputs_names = False
    
    
    
    class NWLinkUseNodeNameMenu(Menu, NWBase):
        bl_idname = "NODE_MT_nw_link_use_node_name_menu"
    
        bl_label = "Use Node Name/Label"
    
        def draw(self, context):
            layout = self.layout
    
            props = layout.operator(NWLinkActiveToSelected.bl_idname, text="Don't Replace Links")
    
            props.replace = False
            props.use_node_name = True
            props.use_outputs_names = False
    
            props = layout.operator(NWLinkActiveToSelected.bl_idname, text="Replace Links")
    
            props.replace = True
            props.use_node_name = True
            props.use_outputs_names = False
    
    class NWLinkUseOutputsNamesMenu(Menu, NWBase):
        bl_idname = "NODE_MT_nw_link_use_outputs_names_menu"
    
        bl_label = "Use Outputs Names"
    
        def draw(self, context):
            layout = self.layout
    
            props = layout.operator(NWLinkActiveToSelected.bl_idname, text="Don't Replace Links")
    
            props.replace = False
            props.use_node_name = False
            props.use_outputs_names = True
    
            props = layout.operator(NWLinkActiveToSelected.bl_idname, text="Replace Links")
    
            props.replace = True
            props.use_node_name = False
            props.use_outputs_names = True
    
    class NWNodeAlignMenu(Menu, NWBase):
        bl_idname = "NODE_MT_nw_node_align_menu"
    
        bl_label = "Align Nodes"
    
        def draw(self, context):
            layout = self.layout
    
            layout.operator(NWAlignNodes.bl_idname, text="Horizontally").option = 'AXIS_X'
            layout.operator(NWAlignNodes.bl_idname, text="Vertically").option = 'AXIS_Y'
    
    # TODO, add to toolbar panel
    class NWUVMenu(bpy.types.Menu):
        bl_idname = "NODE_MT_nw_node_uvs_menu"
        bl_label = "UV Maps"
    
        @classmethod
        def poll(cls, context):
            if context.area.spaces[0].node_tree:
                if context.area.spaces[0].node_tree.type == 'SHADER':
                    return True
                else:
                    return False
            else:
                return False
    
        def draw(self, context):
            l = self.layout
            nodes, links = get_nodes_links(context)
            mat = context.object.active_material
    
            objs = []
            for obj in bpy.data.objects:
                for slot in obj.material_slots:
                    if slot.material == mat:
                        objs.append(obj)
            uvs = []
            for obj in objs:
                if obj.data.uv_layers:
                    for uv in obj.data.uv_layers:
                        uvs.append(uv.name)
            uvs = list(set(uvs))  # get a unique list
    
            if uvs:
                for uv in uvs:
                    l.operator(NWAddAttrNode.bl_idname, text=uv).attr_name = uv
            else:
                l.label("No UV layers on objects with this material")
    
    
    class NWVertColMenu(bpy.types.Menu):
        bl_idname = "NODE_MT_nw_node_vertex_color_menu"
        bl_label = "Vertex Colors"
    
        @classmethod
        def poll(cls, context):
            if context.area.spaces[0].node_tree:
                if context.area.spaces[0].node_tree.type == 'SHADER':
                    return True
                else:
                    return False
            else:
                return False
    
        def draw(self, context):
            l = self.layout
            nodes, links = get_nodes_links(context)
            mat = context.object.active_material
    
            objs = []
            for obj in bpy.data.objects:
                for slot in obj.material_slots:
                    if slot.material == mat:
                        objs.append(obj)
            vcols = []
            for obj in objs:
                if obj.data.vertex_colors:
                    for vcol in obj.data.vertex_colors:
                        vcols.append(vcol.name)
            vcols = list(set(vcols))  # get a unique list
    
            if vcols:
                for vcol in vcols:
                    l.operator(NWAddAttrNode.bl_idname, text=vcol).attr_name = vcol
            else:
                l.label("No Vertex Color layers on objects with this material")
    
    
    class NWSwitchNodeTypeMenu(Menu, NWBase):
        bl_idname = "NODE_MT_nw_switch_node_type_menu"
        bl_label = "Switch Type to..."
    
        def draw(self, context):
            layout = self.layout
            tree = context.space_data.node_tree
            if tree.type == 'SHADER':
                layout.menu(NWSwitchShadersInputSubmenu.bl_idname)
                layout.menu(NWSwitchShadersOutputSubmenu.bl_idname)
                layout.menu(NWSwitchShadersShaderSubmenu.bl_idname)
                layout.menu(NWSwitchShadersTextureSubmenu.bl_idname)
                layout.menu(NWSwitchShadersColorSubmenu.bl_idname)
                layout.menu(NWSwitchShadersVectorSubmenu.bl_idname)
                layout.menu(NWSwitchShadersConverterSubmenu.bl_idname)
                layout.menu(NWSwitchShadersLayoutSubmenu.bl_idname)
            if tree.type == 'COMPOSITING':
                layout.menu(NWSwitchCompoInputSubmenu.bl_idname)
                layout.menu(NWSwitchCompoOutputSubmenu.bl_idname)
                layout.menu(NWSwitchCompoColorSubmenu.bl_idname)
                layout.menu(NWSwitchCompoConverterSubmenu.bl_idname)
                layout.menu(NWSwitchCompoFilterSubmenu.bl_idname)
                layout.menu(NWSwitchCompoVectorSubmenu.bl_idname)
                layout.menu(NWSwitchCompoMatteSubmenu.bl_idname)
                layout.menu(NWSwitchCompoDistortSubmenu.bl_idname)
                layout.menu(NWSwitchCompoLayoutSubmenu.bl_idname)
    
    
    class NWSwitchShadersInputSubmenu(Menu, NWBase):
        bl_idname = "NODE_MT_nw_switch_shaders_input_submenu"
        bl_label = "Input"
    
        def draw(self, context):
            layout = self.layout