Render Splitter and reCompositor - Possible Blender Bug with Render Borders in Compositor

Hello everybody!

I have written a script that splits up a render into small blocks, each in their own scene, and then composites those blocks into the final image at full resolution. I originally wrote this script so that it makes a separate .blend file for each block. This, however, was proving to be very time and space consuming, so I wanted to find a way to make it work with a single .blend file.

I have it almost completely but am now running into an issue.

If you render a scene with a render border, and this scene is being used by a RenderLayer compositing node, the render border gets overridden by the border of the scene the compositor is in.

import bpy
import os

factor = 2
blocks = factor**2
blockno = 0
unit = 1/factor
blist = list(range(blocks))
startFrame = bpy.context.scene.frame_start
endFrame = bpy.context.scene.frame_end
xres = bpy.context.scene.render.resolution_x 
yres = bpy.context.scene.render.resolution_y
pres = bpy.context.scene.render.resolution_percentage


currentSceneName = bpy.context.scene.name
csn = currentSceneName

blendlocation = bpy.data.filepath
blendlocation  = os.path.splitext(blendlocation)[0]
filename = bpy.path.basename(bpy.data.filepath)
filename = os.path.splitext(filename)[0]
newSceneName = csn + " Block 001"

for block in range(blocks):
    if block == blockno:
        blockstr = str(block +1)
        bpy.ops.scene.new(type='LINK_OBJECTS')
        newSceneName = csn + " Block " + blockstr.zfill(3)
        bpy.context.scene.name = newSceneName
        bpy.context.scene.render.filepath = blendlocation + "_BLOCKED\\" + filename + "_BLOCK_" + blockstr.zfill(3) +"_"
        min_x = block/factor%1
        max_x = block/factor%1 + unit
        min_y = int(block/factor)*unit
        max_y = int(block/factor)*unit + unit
        bpy.data.scenes[newSceneName].render.use_border = True      
        bpy.data.scenes[newSceneName].render.border_min_x = min_x
        bpy.data.scenes[newSceneName].render.border_max_x = max_x   
        bpy.data.scenes[newSceneName].render.border_min_y = min_y
        bpy.data.scenes[newSceneName].render.border_max_y = max_y
        blockno = blockno + 1

blockno=1        
bpy.ops.scene.new(type='NEW')
bpy.context.scene.render.filepath = blendlocation + "_BLOCKED\\" + filename + "_BLOCK_COMP_"
bpy.context.scene.frame_start = startFrame
bpy.context.scene.frame_end = endFrame
bpy.context.scene.render.resolution_x = xres
bpy.context.scene.render.resolution_y = yres
bpy.context.scene.render.resolution_percentage = pres
bpy.context.scene.name = csn +" Compositor"
#bpy.context.scene.render.layers["RenderLayer"].use = False
bpy.data.scenes[csn +" Compositor"].render.use_border = False
bpy.data.scenes[csn +" Compositor"].render.use_compositing = True
bpy.context.scene.view_settings.view_transform = 'Default'
bpy.context.scene.view_settings.exposure = 0.0
bpy.context.scene.view_settings.gamma = 1.0
bpy.context.scene.view_settings.look = 'None'
bpy.context.scene.view_settings.view_transform = 'Default'
bpy.context.scene.use_nodes = True
tree = bpy.context.scene.node_tree

# clear default nodes
for node in tree.nodes:
    tree.nodes.remove(node)

#tree.nodes.new(type='CompositorNodeRLayers')
#bpy.data.scenes['Test Compositor'].node_tree.nodes['Render Layers'].scene.name = "Test"
blocno= 0

for scene in bpy.data.scenes:
    if "Block" in scene.name:
            if "001" in scene.name:
                blockstr = str(blockno)
                blockstr = blockstr.zfill(3)
                ref= blockno-1
                rlNodes=['rlNodes_'+str(i) for i in blist]
                rlNodes[ref] = tree.nodes.new(type='CompositorNodeRLayers')
                rlNodes[ref].scene = bpy.data.scenes[scene.name]
                rlNodes[ref].location = 0,(-400)
                rlNodes[ref].width = 250
                aoNodes=['aoNodes_'+str(i) for i in blist]
                aoNodes[ref] = tree.nodes.new(type='CompositorNodeAlphaOver')
                aoNodes[ref].location = 400,(-400)
                aoNodes[ref].inputs[1].default_value = (0,0,0,0)
                opNodes = tree.nodes.new(type='CompositorNodeOutputFile')
                opNodes.file_slots.remove(opNodes.inputs[0])
                opNodes.file_slots.new(filename + "_BLOCK_" + blockstr.zfill(3) +"_")
                opNodes.base_path = blendlocation + "_BLOCKED\\"
                opNodes.label = filename + "_BLOCK_" + blockstr.zfill(3) +"_"
                opNodes.location = 800,(-400*blockno)
                compNodes = tree.nodes.new('CompositorNodeComposite')  
                compNodes.location = 800,-200
                viewerNodes = tree.nodes.new('CompositorNodeViewer') 
                viewerNodes.location = 800, 0
                links = tree.links
                links.new(rlNodes[ref].outputs[0], opNodes.inputs[0])
                links.new(rlNodes[ref].outputs[0], aoNodes[ref].inputs[2])
                links.new(aoNodes[ref].outputs[0], compNodes.inputs[0])
                links.new(aoNodes[ref].outputs[0], viewerNodes.inputs[0])
                blockno = blockno + 1
            else:
                blockstr = str(blockno)
                blockstr = blockstr.zfill(3)
                ref= blockno-1
                old = ref-1
                rlNodes[ref] = tree.nodes.new(type='CompositorNodeRLayers')
                rlNodes[ref].scene = bpy.data.scenes[scene.name]
                rlNodes[ref].location = 0,(-400*blockno)
                rlNodes[ref].width = 250
                opNodes = tree.nodes.new(type='CompositorNodeOutputFile')
                opNodes.file_slots.remove(opNodes.inputs[0])
                opNodes.file_slots.new(filename + "_BLOCK_" + blockstr.zfill(3) +"_")
                opNodes.base_path = blendlocation + "_BLOCKED\\"
                opNodes.label = filename + "_BLOCK_" + blockstr.zfill(3) +"_"
                opNodes.location = 800,(-400*blockno)
                aoNodes[ref] = tree.nodes.new(type='CompositorNodeAlphaOver')
                aoNodes[ref].location = 400,(-400*blockno)
                aoNodes[ref].inputs[1].default_value = (0,0,0,0)
                links.new(rlNodes[ref].outputs[0], opNodes.inputs[0])
                links.new(rlNodes[ref].outputs[0], aoNodes[ref].inputs[2])
                links.new(aoNodes[ref].outputs[0], aoNodes[old].inputs[1])
                blockno = blockno + 1

Try it out for youself. Run this script and then press render in the “Compositor” Scene. You will see that the render ignores the borders of those individual scenes themselves. If you open up the individual “Block” scenes, you can clearly see the render border. Likewise, if you press “Render active scene” on any of the RenderLayer nodes in the Compositor scene, they render with the border. However if you press Render or Animation on the Compositor scene itself, the borders become overridden.

Is this a bug in blender or is there something I am missing?

Full disclosure: I have minimal programming experience and I know that my code isn’t the most elegant and can be more efficiently optimized. Everything I know is from playing around with the blender API for my own applications. Any help would be greatly appreciated.

Thank you in advance!

  • Joe

*edit: fixed a part of the code where the fileouput names between the scenes and the compositor were different. they should be the same now