#!BPY
"""
Name: 'Really Big Render'
Blender: 248
Group: 'Render'
Tooltip: 'Render tiles that will make up an image that is way too big for blender to normally render.'
"""

__author__ = ["macouno"]
__url__ = ("http://www.alienhelpdesk.com")
__version__ = "2.1"
__bpydoc__ = """\

Really Big Render!

This script is written for people that want to render really really big images with blender.
Images that either blender doesn't want to render, or your system can't normally handle.

The script will render to 2, 3, 4, or 5 times your orignal rendersize. You can select the multiplier you want from a popup window.
So say your rendersettings are 800 x 600 and you select 3X multiplication, then the script will render nine images at 800 x 600.
If you combine these images in your favoured image editor the end result will be a single image of 3200 x 1800 pixels

Make sure that Blender can render at your current render settings! If it can't then it won't render the tiles either!

The tiles are rendered left to right, top to bottom and numbered as such, so 0_0 will be the left top, and 1_1 the right bottom (for a 4 tile render).

New in version 2: It can now use the PIL library to combine your tiles and create the final image... if your system can handle it and you have the PIL library for python installed.

New in version 2.1: Added a method for dealing with cameras that have ipo curves assigned (removing the ipo for render, reapplying afterwards). Thanks to Kai Kostack for pointing it out and putting me on the path towards the solution.

"""

# ***** BEGIN GPL LICENSE BLOCK *****
#
# Script copyright (C) macouno 2008
#
# 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
# ***** END GPL LICENCE BLOCK *****
# --------------------------------------------------------------------------

import Blender
from Blender import *
import math


## PRESET THE GLOBAL STATE VARIABLE
STATE = {}

# Preset the start values for lens shift
STATE['tileNr'] = [0, 2, 3, 4, 5]
STATE['tileMin'] = [0, -0.5, -1.0, -1.5, -2.0]

try:
	import PIL
	from PIL import Image
	STATE['PIL'] = 1
except:
	STATE['PIL'] = 0


## GET THE SCENE SETTINGS FOR REFERENCE AND RESET
def GetSettings(fileName):
	
	imageExtensions = ['.tga','.rgb','.tga','3','.jpg','5','6','7','8','9','10','11','12','13','.tga','.avi','.avi','.png','.avi','19','.bmp','.hdr','.tiff','.exr','24','.tga','.cin','27','.exr','.dds']
	
	try:
		STATE['SCN'] = Scene.GetCurrent()
		STATE['CAM'] = Camera.Get(STATE['SCN'].getCurrentCamera().name)
		STATE['CNTX'] = STATE['SCN'].render
		STATE['PATH'] = STATE['CNTX'].renderPath
		STATE['FILENAME'] = fileName
		STATE['EXTENSION'] = imageExtensions[STATE['CNTX'].imageType]
		STATE['DEPTH'] = STATE['CNTX'].imagePlanes
		STATE['ASPX'] = float(STATE['CNTX'].sizeX) / float(STATE['CNTX'].sizeY)
		STATE['ASPY'] = float(STATE['CNTX'].sizeY) / float(STATE['CNTX'].sizeX)
		
		STATE['IPO'] = 0;
		if STATE['CAM'].ipo:
			STATE['IPO'] = STATE['CAM'].ipo
			STATE['CAM'].ipo = None
		
		STATE['LENS'] = STATE['CAM'].lens
		STATE['SCALE'] = STATE['CAM'].scale
		STATE['SHIFTX'] = STATE['CAM'].shiftX
		STATE['SHIFTY'] = STATE['CAM'].shiftY
		return 1
	except:
		Draw.PupMenu('Error! The script could not find a camera in your scene.')
		return 0


## SET GLOBAL SCENE SETTINGS FOR RENDERING
def SetSettings(tileNr):
	STATE['CAM'].scale = (STATE['SCALE'] / tileNr)	
	STATE['CAM'].lens = (STATE['LENS'] * tileNr)


## PUT THE SCENE BACK IN IT'S ORIGINAL STATE
def ResetSettings():
	if STATE['IPO']:
		STATE['CAM'].ipo = STATE['IPO']
	STATE['CNTX'].renderPath = STATE['PATH']
	STATE['CAM'].lens = STATE['LENS']
	STATE['CAM'].scale = STATE['SCALE']
	STATE['CAM'].shiftX = STATE['SHIFTX']
	STATE['CAM'].shiftY = STATE['SHIFTY']
	STATE['SCN'].update()


## RENDER A TILE
def RenderTile(xShift, yShift, fileName):

	if STATE['ASPX'] > 1:
		yShift /= STATE['ASPX']
	else:
		xShift /= STATE['ASPY']
	
	STATE['CAM'].shiftX = xShift
	STATE['CAM'].shiftY = yShift
	STATE['SCN'].update()
	STATE['CNTX'].renderPath = str(STATE['FILENAME'])
	print 'rendering', str(STATE['FILENAME'] + fileName)
	STATE['CNTX'].render()
	STATE['CNTX'].saveRenderedImage(fileName, 0)


## PREPARE EACH TILE FOR RENDER
def MakeTiles(tileSel, composite):

	# Find the total nr of tiles to render
	tileSquare = STATE['tileNr'][tileSel]
	tileStart = STATE['tileMin'][tileSel]
	
	# Now that we have all data, set everything up
	SetSettings(tileSquare)
	tiles = [];
	
	# Loop through the columns of tiles
	for y in range(tileSquare):
		
		yShift = ((-tileStart) - float(y))
	
		# Loop through the rows of tiles
		for x in range(tileSquare):
		
			xShift = (float(x) + tileStart)
			
			# Make the name for the tile
			fileName = '_tile_' + str(str(y) + '_' + str(x)) + STATE['EXTENSION']
			
			# Render the current tile
			RenderTile(xShift, yShift, fileName)
			
			fileName = STATE['FILENAME']  + fileName
			
			if(composite == 1):
				tiles.append([fileName , x, y])
			
	if(composite == 1):
		MakeComposite(tileSel, tileSquare, tiles)


## MAKE A COMPOSITE OF THE RESULTING TILES
def MakeComposite(tileSel, tileSquare, tiles):
		
	print 'creating composite'
	
	# Get the image sizes
	tileX = STATE['CNTX'].sizeX
	tileY = STATE['CNTX'].sizeY
	
	# Find the size of the final composite
	sizeX = tileX * tileSquare
	sizeY = tileY * tileSquare
	
	# Hold your breath whilst the PIL library creates an image at the required size and depth
	if(STATE['DEPTH'] == 8):
		im = Image.new('L', (sizeX, sizeY))
	elif(STATE['DEPTH'] == 24):
		im = Image.new('RGB', (sizeX, sizeY))
	else:
		im = Image.new('RGBA', (sizeX, sizeY), (0, 0, 0, 0))	
	
	# Loop through the columns of tiles
	for  i, t in enumerate(tiles):
	
		offX = t[1] * tileX
		offY = t[2] * tileY
		
		tile = Image.open(t[0])
		
		## Paste in the tile at the proper offset
		im.paste(tile, (offX,offY))

	fileName = STATE['FILENAME'] + '_composite' + STATE['EXTENSION']
	im.save(fileName)
	print 'saved composite' + str(STATE['FILENAME'] + '_composite' + STATE['EXTENSION'])


## PREPARE THE SCRIPT TO RUN
def PrepScript(fileName):

	## Draw popup selector
	tileSel = Draw.PupMenu("How much bigger?%t|2X    4 tiles (2x2)|3X    9 tiles (3x3)|4X    16 tiles (4x4)|5X    25 tiles (5x5)|cancel")
	if tileSel and tileSel < 5:
	
		if STATE['PIL']:
			composite = Draw.PupMenu("Create a composite?%t|Yes|No")
		else:
			print 'Unable to import PIL, will render the tiles but can not create a composite image.'
			composite = 2

		if GetSettings(fileName):
		
			Window.WaitCursor(1)
		
			MakeTiles(tileSel, composite)
		
			ResetSettings()

			Window.RedrawAll()

			Window.WaitCursor(0)


## SET WHAT FILE TO RENDER TO BEFORE DOING ANYTHING ELSE
def SetFile(): Window.FileSelector(PrepScript, "Render to", sys.makename(ext=''))


## START
SetFile()