bl_info = {
    "name": "Tile Render",
    "blender": (2, 79, 0),
    "category": "Render",
    "location": "Properties > Render > Tile Render",
    "author": "Daniel Roberts (BlenderTimer)",
    "warning": "",
    "version": (1, 0),
    "description": "Render an image as separate tiles!",
}

import bpy
addon_keymaps = []
currentTileX = 0
currentTileY = 0
currentTile = 1
tilesRendered = 0

class Settings(bpy.types.PropertyGroup):
    tilesX = bpy.props.IntProperty(name="X", description="Amount of horizontal tiles", default=3, min=1, max=100)
    tilesY = bpy.props.IntProperty(name="Y", description="Amount of vertical tiles", default=3, min=1, max=100)

class WM_OT_SetTileForward(bpy.types.Operator):
    """Select next tile to render"""
    bl_idname = "render_tile.set_forward"
    bl_label = "Next Tile"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        scene = context.scene
        tilerender = scene.tile_render
        
        area = bpy.context.area.type
        bpy.context.area.type = 'VIEW_3D'
        bpy.context.area.spaces[0].region_3d.view_perspective = 'CAMERA'
        bpy.ops.view3d.view_center_camera()
        global currentTileX
        global currentTileY
        global currentTile
        currentTileX += 1
        currentTile += 1
        if currentTileX > (tilerender.tilesX - 1):
            if currentTileY < (tilerender.tilesY - 1):
                currentTileY += 1
                currentTileX = 0
            else:
                currentTileY = 0
                currentTileX = 0
                currentTile = 1
        bpy.context.scene.render.use_border = True
        bpy.context.scene.render.border_min_x = (1 / tilerender.tilesX) * (currentTileX)
        bpy.context.scene.render.border_min_y = ((1 / tilerender.tilesY) * (currentTileY+1) * -1) + 1
        bpy.context.scene.render.border_max_x = (1 / tilerender.tilesX) * (currentTileX+1)
        bpy.context.scene.render.border_max_y = ((1 / tilerender.tilesY) * (currentTileY)) * -1 + 1
        bpy.context.area.type = area
        
        return {'FINISHED'}

class WM_OT_SetTileBackward(bpy.types.Operator):
    """Select previous tile to render"""
    bl_idname = "render_tile.set_backward"
    bl_label = "Previous Tile"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        scene = context.scene
        tilerender = scene.tile_render
        
        area = bpy.context.area.type
        bpy.context.area.type = 'VIEW_3D'
        bpy.context.area.spaces[0].region_3d.view_perspective = 'CAMERA'
        bpy.ops.view3d.view_center_camera()
        global currentTileX
        global currentTileY
        global currentTile
        currentTileX -= 1
        currentTile -= 1
        if currentTileX < 0:
            if currentTileY > 0:
                currentTileY -= 1
                currentTileX = tilerender.tilesX - 1
            else:
                currentTileY = tilerender.tilesY - 1
                currentTileX = tilerender.tilesX - 1
                currentTile = tilerender.tilesX * tilerender.tilesY
        bpy.context.scene.render.use_border = True
        bpy.context.scene.render.border_min_x = (1 / tilerender.tilesX) * (currentTileX)
        bpy.context.scene.render.border_min_y = ((1 / tilerender.tilesY) * (currentTileY+1) * -1) + 1
        bpy.context.scene.render.border_max_x = (1 / tilerender.tilesX) * (currentTileX+1)
        bpy.context.scene.render.border_max_y = ((1 / tilerender.tilesY) * (currentTileY)) * -1 + 1
        bpy.context.area.type = area
        
        return {'FINISHED'}

class WM_OT_SetTileFirst(bpy.types.Operator):
    """Select first tile to render"""
    bl_idname = "render_tile.set_first"
    bl_label = "First Tile"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        scene = context.scene
        tilerender = scene.tile_render
        
        area = bpy.context.area.type
        bpy.context.area.type = 'VIEW_3D'
        bpy.context.area.spaces[0].region_3d.view_perspective = 'CAMERA'
        bpy.ops.view3d.view_center_camera()
        global currentTileX
        global currentTileY
        global currentTile
        currentTileX = 0
        currentTileY = 0
        bpy.context.scene.render.use_border = True
        bpy.context.scene.render.border_min_x = (1 / tilerender.tilesX) * (currentTileX)
        bpy.context.scene.render.border_min_y = ((1 / tilerender.tilesY) * (currentTileY+1) * -1) + 1
        bpy.context.scene.render.border_max_x = (1 / tilerender.tilesX) * (currentTileX+1)
        bpy.context.scene.render.border_max_y = ((1 / tilerender.tilesY) * (currentTileY)) * -1 + 1
        currentTile = 1
        bpy.context.area.type = area
        
        return {'FINISHED'}

class WM_OT_SetTileLast(bpy.types.Operator):
    """Select last tile to render"""
    bl_idname = "render_tile.set_last"
    bl_label = "Last Tile"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        scene = context.scene
        tilerender = scene.tile_render
        
        area = bpy.context.area.type
        bpy.context.area.type = 'VIEW_3D'
        bpy.context.area.spaces[0].region_3d.view_perspective = 'CAMERA'
        bpy.ops.view3d.view_center_camera()
        global currentTileX
        global currentTileY
        global currentTile
        currentTileX = tilerender.tilesX - 1
        currentTileY = tilerender.tilesY - 1
        bpy.context.scene.render.use_border = True
        bpy.context.scene.render.border_min_x = (1 / tilerender.tilesX) * (currentTileX)
        bpy.context.scene.render.border_min_y = ((1 / tilerender.tilesY) * (currentTileY+1) * -1) + 1
        bpy.context.scene.render.border_max_x = (1 / tilerender.tilesX) * (currentTileX+1)
        bpy.context.scene.render.border_max_y = ((1 / tilerender.tilesY) * (currentTileY)) * -1 + 1
        currentTile = tilerender.tilesX * tilerender.tilesY
        bpy.context.area.type = area
        
        return {'FINISHED'}

class WM_OT_Render(bpy.types.Operator):
    """Render next tile and save to output location"""
    bl_idname = "render_tile.render"
    bl_label = "Render Next Tile"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        scene = context.scene
        tilerender = scene.tile_render
        global currentTileX
        global currentTileY
        global currentTile
        global tilesRendered
        if not tilesRendered == (tilerender.tilesX * tilerender.tilesY):
            if not tilesRendered == 0:
                bpy.ops.render_tile.set_forward()
            else:
                bpy.ops.render_tile.set_first()
            if tilesRendered == (tilerender.tilesX * tilerender.tilesY):
                bpy.ops.render_tile.reset()
            rfilepath = bpy.context.scene.render.filepath.find(".")
            newfilepath = bpy.context.scene.render.filepath
            if not rfilepath == -1:
                newfilepath = bpy.context.scene.render.filepath[:rfilepath]
            if "[" and "]" in newfilepath:
                newfilepath = newfilepath[:-6]
            if len(str(currentTile)) == 1:
                bpy.context.scene.render.filepath = newfilepath + "[000" + str(currentTile) + "]"
            elif len(str(currentTile)) == 2:
                bpy.context.scene.render.filepath = newfilepath + "[00" + str(currentTile) + "]"
            elif len(str(currentTile)) == 3:
                bpy.context.scene.render.filepath = newfilepath + "[0" + str(currentTile) + "]"
            elif len(str(currentTile)) == 4:
                bpy.context.scene.render.filepath = newfilepath + "[" + str(currentTile) + "]"
            bpy.ops.render.render('INVOKE_DEFAULT', write_still=True)
            tilesRendered += 1
        
        return {'FINISHED'}

class WM_OT_Reset(bpy.types.Operator):
    """Reset"""
    bl_idname = "render_tile.reset"
    bl_label = "Reset"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        scene = context.scene
        tilerender = scene.tile_render
        global currentTileX
        global currentTileY
        global currentTile
        global tilesRendered
        bpy.ops.render_tile.set_first()
        tilesRendered = 0
        rfilepath = bpy.context.scene.render.filepath.find(".")
        newfilepath = bpy.context.scene.render.filepath
        if not rfilepath == -1:
            newfilepath = bpy.context.scene.render.filepath[:rfilepath]
        if "[" and "]" in newfilepath:
            newfilepath = newfilepath[:-6]
        bpy.context.scene.render.filepath = newfilepath
        
        return {'FINISHED'}

class PROPERTIES_PT_RenderTilePanel(bpy.types.Panel):
    bl_idname = "properties.tile_render_panel"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_label = "Tile Render"
    bl_context = 'render'
    
    def draw(self, context):
        layout = self.layout
        scene = context.scene
        tilerender = scene.tile_render
        col1 = layout.column(align=True)
        col1.prop(tilerender, "tilesX")
        col1.prop(tilerender, "tilesY")
        
        col2 = layout.column(align=True)
        row1 = col2.row(align=True)
        row1.operator(WM_OT_SetTileBackward.bl_idname, icon="BACK")
        row1.operator(WM_OT_SetTileForward.bl_idname, icon="FORWARD")
        row2 = col2.row(align=True)
        row2.operator(WM_OT_SetTileFirst.bl_idname, icon="TRIA_LEFT_BAR")
        row2.operator(WM_OT_SetTileLast.bl_idname, icon="TRIA_RIGHT_BAR")
        
        col3 = layout.column(align=True)
        col3.operator(WM_OT_Render.bl_idname, icon="RENDER_STILL")
        col3.operator(WM_OT_Reset.bl_idname, icon="FILE_REFRESH")
        
        row3 = layout.row(align=False)
        row3.label("Tiles Rendered: " + str(tilesRendered) + "/" + str(tilerender.tilesX * tilerender.tilesY))
        row3.label("Current Tile: " + str(currentTile) + "/" + str(tilerender.tilesX * tilerender.tilesY))

def register():
    #Draw and register
    bpy.utils.register_class(WM_OT_SetTileBackward)
    bpy.utils.register_class(WM_OT_SetTileForward)
    bpy.utils.register_class(WM_OT_SetTileFirst)
    bpy.utils.register_class(WM_OT_SetTileLast)
    bpy.utils.register_class(WM_OT_Render)
    bpy.utils.register_class(WM_OT_Reset)
    bpy.utils.register_class(PROPERTIES_PT_RenderTilePanel)
    bpy.utils.register_class(Settings)
    bpy.types.Scene.tile_render = bpy.props.PointerProperty(type=Settings)
    #Keymaps
    wm = bpy.context.window_manager
    kc = wm.keyconfigs.addon
    if kc:
        km = wm.keyconfigs.addon.keymaps.new(name='Image', space_type='IMAGE_EDITOR')
        kmi = km.keymap_items.new(WM_OT_SetTileForward.bl_idname, 'F9', 'PRESS', ctrl=False, shift=False)
        kmi = km.keymap_items.new(WM_OT_SetTileBackward.bl_idname, 'F9', 'PRESS', ctrl=False, shift=True)
        kmi = km.keymap_items.new(WM_OT_Render.bl_idname, 'F10', 'PRESS', ctrl=False, shift=False)
        addon_keymaps.append((km, kmi))

def unregister():
    #Keymaps
    for km, kmi in addon_keymaps:
        km.keymap_items.remove(kmi)
    addon_keymaps.clear()
    #Erase and unregister
    bpy.utils.unregister_class(WM_OT_SetTileForward)
    bpy.utils.unregister_class(WM_OT_SetTileBackward)
    bpy.utils.unregister_class(WM_OT_SetTileFirst)
    bpy.utils.unregister_class(WM_OT_SetTileLast)
    bpy.utils.unregister_class(WM_OT_Render)
    bpy.utils.unregister_class(WM_OT_Reset)
    bpy.utils.unregister_class(Settings)
    bpy.utils.unregister_class(PROPERTIES_PT_RenderTilePanel)
    del bpy.types.Scene.tile_render

if __name__ == "__main__":
    register()