Script – render all View Layers separately

Is it even possible to automate any of the stages in my workflow with a script? I have about 20 View Layers on one scene in my projects. I need to render each View Layer separately and save it to an EXR file (I need it on separate layers for photoshop).

My Steps:

  1. I create a “File Output” node with input with the same amount and names as the layer view.
  2. Plug all nodes from “Render Layers” into the appropriate place in the “File Output” node.

Any chance to do this with scripts? I thought maybe there was an add-on for this, but unfortunately I couldn’t find anything.

I’m posting the finished script here, maybe someone will find it useful :v::grinning:

import bpy
C = bpy.context
D = bpy.data

# print('-'*40)

# ensure compositor is using nodes
if not C.scene.use_nodes:
    C.scene.use_nodes = True

# get the names of all view layers in current scene
#v_layers = [v_layer.name for v_layer in C.scene.view_layers]

# or get the names of view layers that are not excluded from render
v_layers = [v_layer.name for v_layer in C.scene.view_layers if v_layer.use]

# ensure denoising data available in render pass
# requires cycles render engine
if not C.scene.render.engine == 'CYCLES':
    C.scene.render.engine = 'CYCLES'

x_spacing = 300
y_spacing = -500


for i, vl_name in enumerate(v_layers):
    # print('-'*40)
    # print(f'idx:{i} name:{vl_name}')
    if not D.scenes[C.scene.name].cycles.use_denoising:
        D.scenes[C.scene.name].cycles.use_denoising = True
    if not C.scene.view_layers[vl_name].cycles.use_denoising:
        C.scene.view_layers[vl_name].cycles.use_denoising = True
    if not C.scene.view_layers[vl_name].cycles.denoising_store_passes:
        C.scene.view_layers[vl_name].cycles.denoising_store_passes = True
    # add render layer node
    rl_node = C.scene.node_tree.nodes.get("rl_"+vl_name)
    if not rl_node:
        rl_node = C.scene.node_tree.nodes.new('CompositorNodeRLayers')
    rl_node.name = "rl_"+vl_name
    rl_node.label = "rl_"+vl_name
    rl_node.layer = vl_name
    rl_node.location[0] = 0 * x_spacing
    rl_node.location[1] = (i) * y_spacing
    # add de-noise node
    dn_node = C.scene.node_tree.nodes.get("dn_"+vl_name)
    if not dn_node:
        dn_node = C.scene.node_tree.nodes.new('CompositorNodeDenoise')
    dn_node.name = "dn_"+vl_name
    dn_node.label = "dn_"+vl_name
    dn_node.prefilter = 'FAST'
    dn_node.location[0] = 1 * x_spacing
    dn_node.location[1] = (i) * y_spacing
    # add mixrgb node
    mix_node = C.scene.node_tree.nodes.get("mix_"+vl_name)
    if not mix_node:
        mix_node = C.scene.node_tree.nodes.new('CompositorNodeMixRGB')
    mix_node.name = "mix_"+vl_name
    mix_node.label = "mix_"+vl_name
    mix_node.inputs['Fac'].default_value = 0.75
    mix_node.location[0] = 1.75 * x_spacing
    mix_node.location[1] = (i) * y_spacing
    # add file output node and slots
    op_node = C.scene.node_tree.nodes.get("File Output")
    if not op_node:
        op_node = C.scene.node_tree.nodes.new('CompositorNodeOutputFile')
        op_node.file_slots.clear()
    op_node.name = "File Output"
    op_node.label = "File Output"
    op_node.location[0] = 2.5 * x_spacing
    
    if len(op_node.file_slots) < i+1:
        op_node.file_slots.new(vl_name)
    op_node.file_slots[i].path = vl_name
    op_node.format.file_format = 'OPEN_EXR_MULTILAYER'
    op_node.format.color_depth = '16'
    C.scene.node_tree.links.new(op_node.inputs[i], mix_node.outputs[0])
    C.scene.node_tree.links.new(mix_node.inputs[1], rl_node.outputs['Image'])
    C.scene.node_tree.links.new(mix_node.inputs[2], dn_node.outputs[0])
    C.scene.node_tree.links.new(dn_node.inputs['Image'], rl_node.outputs['Image'])
    C.scene.node_tree.links.new(dn_node.inputs['Normal'], rl_node.outputs['Denoising Normal'])
    C.scene.node_tree.links.new(dn_node.inputs['Albedo'], rl_node.outputs['Denoising Albedo'])