2
$\begingroup$

How to copy all outputs off one node to another as inputs? For instance, in the Compositing workspace, I have a Render Layer node (donor) with an immense amount of outputs and a File Output node (receiver), I would like to transfer all outputs of the donor node to the receiver node as inputs and connect them correspondingly. enter image description here

$\endgroup$

2 Answers 2

4
$\begingroup$

I've submitted a patch for Node Wrangler to improve Lazy Connect to do what you want (a long time ago) but unfortunately never had the time to finish it. However, the patch should still work: https://developer.blender.org/D6337


The following add-on is a standalone version of that which has almost the same functionality. Select the desired Render Layer node(s), Shift-select the File Output node and click "Create and Connect Slots" in the Properties Panel. In case more than one Render Layer is selected, all slots are getting a prefix based on the name of the actual Render Layer node:

enter image description here

bart.py

# ##### BEGIN GPL LICENSE BLOCK #####
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####

bl_info = {
    "name": "Bart",
    "description": "Dynamic File Output Slots",
    "author": "p2or",
    "version": (0, 1),
    "blender": (2, 90, 0),
    "location": "Compositor > File Output Node",
    "warning": "", # used for warning icon and text in addons panel
    "category": "Render"
}

import bpy

class RV_OT_OutputSockets(bpy.types.Operator):
    """Add Slots to File Output Node"""
    bl_idname = "rv.create_file_output_slots"
    bl_label = "Create and Connect Slots"
    bl_options = {'REGISTER', 'UNDO'}

    @classmethod
    def poll(cls, context):
        #context.area.ui_type == 'CompositorNodeTree'
        return context.space_data.type == 'NODE_EDITOR'

    def execute(self, context):
        print(context.area.ui_type)
        nodes_selected = context.selected_nodes
        inode, onode = ('R_LAYERS', 'OUTPUT_FILE')
        tree = context.scene.node_tree
        links = tree.links
        nodes = tree.nodes

        # Basic Error handling
        out_nodes = [o for o in nodes_selected if o.type == onode]
        if not out_nodes:
            self.report({'ERROR'}, "No Output Node in Selection")
            return {'CANCELLED'}

        candidates = [n for n in nodes_selected if not n.type == onode]
        if not candidates:
            self.report({'ERROR'}, "Nothing selected")
            return {'CANCELLED'}

        # Sort nodes by location
        if len(candidates) > 1:
            candidates = sorted(candidates, key=lambda n: n.location.y, reverse=True)
        
        # Lookup for nodes and their sockets
        node_sockets = {}
        for node in candidates:
            out_sockets = {}
            for o in node.outputs:
                if o.enabled:
                    out_sockets.setdefault(o.name, o)
            node_sockets.setdefault(node.name, {})["Outputs"] = out_sockets

            # Naming of sockets per type of node
            if node.type == inode:
                if len(context.scene.view_layers) > 1:
                    socket_prefix = node.layer
                elif len(bpy.data.scenes) > 1:
                    socket_prefix = "{}_{}".format(node.scene.name, node.layer)
                else:
                    socket_prefix = node.label if node.label else node.name
            elif node.type == 'IMAGE':
                if node.label:
                    socket_prefix = node.label
                elif node.image:
                    socket_prefix = path.splitext(node.image.name)[0]
                else:
                    socket_prefix = node.name
            else:
                socket_prefix = node.label if node.label else node.name
            
            # Handle spaces and dots in name strings
            socket_prefix = socket_prefix.replace(" ", "").replace('.', '-')
            
            # Add dynamic prefix to the dict 
            node_sockets[node.name]["OutSocketPrefix"] = socket_prefix        

        file_node = out_nodes[0]
        file_node.width = 350
        
        prefix_flag = False
        if len([o for o in nodes_selected if o.type == inode]) > 1:
            prefix_flag = True

        # Create slots and noodles
        for key, val in node_sockets.items():
            prefix = val.get("OutSocketPrefix")
            for name, socket in val.get("Outputs").items():
                if prefix_flag:
                    file_socket_name = "{}_{}".format(prefix, name)
                else:
                    file_socket_name = "{}".format(name)
                if file_socket_name not in file_node.layer_slots:
                    file_socket = file_node.layer_slots.new(file_socket_name)
                else:
                    file_socket = file_node.inputs[file_socket_name]
                links.new(socket, file_socket)

        return {'FINISHED'}

def create_slot_button(self, context):
    if context.active_node.type == 'OUTPUT_FILE' and \
            any(n for n in context.selected_nodes if n.type == 'R_LAYERS'):
        self.layout.operator(RV_OT_OutputSockets.bl_idname)
        self.layout.row()

def register():
    bpy.utils.register_class(RV_OT_OutputSockets)
    bpy.types.NODE_PT_active_node_generic.prepend(create_slot_button)

def unregister():
    bpy.utils.unregister_class(RV_OT_OutputSockets)
    bpy.types.NODE_PT_active_node_generic.remove(create_slot_button)

if __name__ == "__main__":
    register()

Related: Save all render passes to EXR image?

$\endgroup$
3
$\begingroup$

If you enable Node Wrangler add-on, you can Alt right-mouse-button drag from one node to the other, which makes it a little faster. Otherwise you can modify the Operator Simple template to do that automatically:

import bpy


def main(C):
    right = C.active_node
    left = next(n for n in C.selected_nodes if n != right)
    links = left.id_data.links
    for i in right.inputs:
        o = left.outputs[i.name]
        links.new(input=i, output=o)


class ConnectNodes(bpy.types.Operator):
    """Tooltip"""
    bl_idname = "object.connect_nodes"
    bl_label = "Connect Nodes"

    def execute(self, context):
        main(context)
        return {'FINISHED'}


def menu_func(self, context):
    self.layout.operator(ConnectNodes.bl_idname, text=ConnectNodes.bl_label)


# Register and add to the "object" menu (required to also use F3 search "Simple Object Operator" for quick access)
def register():
    bpy.utils.register_class(ConnectNodes)
    bpy.types.NODE_MT_view.append(menu_func)


def unregister():
    bpy.utils.unregister_class(ConnectNodes)
    bpy.types.NODE_MT_view.remove(menu_func)


if __name__ == "__main__":
    register()

First select the node with outputs, then the node with inputs (in code called left and right, respectively, as typically the nodes are arranged from left to right), then F3, search for Connect Nodes, Enter. The operator should connect the inputs to outputs as long as they have the same names. Once it finds an input, for which it can't find an equally named output, it will stop due to an error.

If you want to change that logic, look no further than the main function at the beginning of the code.

$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .