#!BPY
""" Released under the Blender Artistic Licence (BAL)
Name: 'Stereorender 1.3'
Blender: 235
Group: 'Render'
Tooltip: 'Render stills or animations in red-cyan stereo'
"""
__author__ = "pat"
__version__ = "1.3 - 19/01/05 -"
__email__ = ('Author, pat:psycho3d*de')
__url__ = ("Author's website, www.psycho3d.de")
__bpydoc__ ="""\
This script renders your scene as red-cyan anaglyph.

The script renders your scene twice with different camera locations
(controlled by the "deltaX" value), generates the final image and displays
it in the render window. This also works for animations, the final animation
will be played after rendering all frames.

Buttons:<br>
   Left, Right, Join: deactivate to skip a part of the rendering process<br>
   BW: tells Blender to save grey images, faster to generate final image<br>
   RGB: Blender saves colored pics, final image will be colored<br>
   deltaX: controls the distance between the two camera locations<br>
   Correct Cam: Creates an empty to apply location/rotation/animation to the cam. This avoids errors when the camera is rotated.

Hotkeys:<br>
   Q: [Q]uit
"""

import Blender
from Blender import Scene, Object, Get, Window, Text as txt
from Blender.BGL import *
from Blender.Draw import *
from string import zfill
from Blender.sys import sep, time

rc = Scene.getCurrent().getRenderingContext()
### gui defines ###
f_deltaX = Create(.06)
t_anim = Create(0)
i_startFrame = Create(rc.startFrame())
i_endFrame = Create(rc.endFrame())
t_gray = Create(1)
t_rgb = Create(0)
t_left = Create(1)
t_right = Create(1)
t_join = Create(1)
m_settings = Create(1)
sett_name = Create("untitled")
settings = ""
### gui defines ###
finalSize = []
del rc

def renderFrame(rc, framenr):
  rc.startFrame(framenr)
  rc.endFrame(framenr)
  rc.renderAnim()
### renderFrame


def read24bit(path):
  f = open(path, 'rb')
  header = f.read(18)
  data = f.read()
  f.close()

  sizeX = ord(header[12]) + ord(header[13]) * 256
  sizeY = ord(header[14]) + ord(header[15]) * 256
  tga = []
  t = 0
  size = sizeX * sizeY * 3
  while t < size:
    tga.append(ord(data[t + 2]))
    t += 3
  return tga, sizeX, sizeY
### read24bit


def read24bitCyan(path):
  f = open(path, 'rb')
  header = f.read(18)
  data = f.read()
  f.close()

  sizeX = ord(header[12]) + ord(header[13]) * 256
  sizeY = ord(header[14]) + ord(header[15]) * 256
  tga = []
  tga2 = []
  t = 0
  size = sizeX * sizeY * 3
  while t < size:
    tga.append(ord(data[t]))
    tga2.append(ord(data[t + 1]))
    t += 3
  return tga2, tga
### read24bitCyan


def read8bit(path):
  f = open(path, 'rb')
  header = f.read(18)
  data = f.read()
  f.close()

  sizeX = ord(header[12]) + ord(header[13]) * 256
  sizeY = ord(header[14]) + ord(header[15]) * 256
  tga = []
  t = 0
  size = sizeX * sizeY
  while t < size:
    tga.append(ord(data[t]))
    t += 1
  return tga, sizeX, sizeY
### read8bit


def write24bit(path, data, sizeX, sizeY):
  header = [0,0,2,0,0,0,0,0,0,0,0,0, \
    sizeX % 256, sizeX / 256, sizeY % 256, sizeY / 256, 24, 0]
  f = open(path, 'wb')
  t = 0
  while t < 18:
    f.write(chr(header[t]))
    t += 1
  t = 0
  d  = len(data)
  while t < d:
    f.write(chr(data[t][2]))
    f.write(chr(data[t][1]))
    f.write(chr(data[t][0]))
    t += 1
  f.close()
### write24bit


def repCam():
  sce = Scene.GetCurrent()
  cam = sce.getCurrentCamera()
  eul = cam.getEuler()
  camipo = cam.getIpo()
  if eul[1] != 0 or eul[2] != 0 or camipo != None:
    emp = Object.New("Empty")#, "camcontroller")
    sce.link(emp)
    cam.clearIpo()
    camloc = cam.getLocation()
    cam.setLocation(0,0,0)
    cam.setEuler(0,0,0)
    cam.clrParent()
    emp.makeParent([cam])
    emp.setLocation(camloc)
    emp.setEuler(eul)
    if camipo:
      emp.setIpo(camipo)
    PupMenu("Note:%t|The empty '"+emp.getName()+"' now controlls the cam, use it to apply rotation or animation")
  else:
    PupMenu("No need to correct the cam:%t|The camera is not rotated and has no Ipo")
### repCam


def loadSettings(name):
  global t_left, t_join, t_right, t_rgb, i_startFrame, i_endFrame, f_deltaX
  name = "[%s]" % name
  t = txt.Get("stereorenderini")
  lines = t.asLines()
  line = 0
  while line < t.nlines:
    if lines[line] == name:
      data = lines[line+1]
    line += 1
  data = data.split()
  print data
  t_left.val = int(data[0])
  t_join.val = int(data[1])
  t_right.val = int(data[2])
  t_rgb.val = int(data[3])
  i_startFrame.val = int(data[4])
  i_endFrame.val = int(data[5])
  f_deltaX.val = float(data[6])
  bevent(9) #set BW button
  Redraw()
### loadSettings


def saveSettings(name):
  name = "[%s]" % name
  data = [t_left.val]
  data.append(t_join.val)
  data.append(t_right.val)
  data.append(t_rgb.val)
  data.append(i_startFrame.val)
  data.append(i_endFrame.val)
  data.append(f_deltaX.val)
  datastr = ""
  for st in data:
    datastr += str(st) + " "
  t = txt.Get("stereorenderini")
  lines = t.asLines()
  line = 0
  overwrite = 0
  while line < t.nlines:
    if lines[line] == name:
      overwrite = line + 1
    line += 1
  if not overwrite == 0:
    lines[overwrite] = datastr
    t.clear()
    for lin in lines:
      if not lin=="":
        t.write(lin + "\n")
  else:
    t.write(name + "\n")
    t.write(datastr + "\n")
### saveSettings
  

def checkSettings():
  global settings
  settings = "Select Settings%t|** Add New **"
  try:
    t = txt.Get("stereorenderini")
  except NameError:
    t = txt.New("stereorenderini")
  else:
    lines = t.asLines()
    nline = 0
    while nline < t.nlines:
      name = lines[nline]
      settings += "|" + name[1 : len(name)-1]
      nline += 2
    Redraw()
    return nline/2
### checkSettings

  
def joinImages(rc, framenr, oldPath):
  leftImage = oldPath + "_left_" + zfill(str(framenr), 4) + ".tga"
  rightImage = oldPath + "_right_" + zfill(str(framenr), 4) + ".tga"
  if t_gray.val:
    redChannel, sizeX, sizeY = read8bit(leftImage)
    cyanChannel, sizeX, sizeY = read8bit(rightImage)
    combined = map(None, redChannel, cyanChannel, cyanChannel)
  else:
    redChannel, sizeX, sizeY = read24bit(leftImage)
    greenChannel, blueChannel = read24bitCyan(rightImage)
    combined = map(None, redChannel, greenChannel, blueChannel)
  write24bit(oldPath + zfill(str(framenr), 4) + ".tga", combined, sizeX, sizeY)
  global finalSize
  finalSize = [sizeX, sizeY]
  print "Joined Images to: %s" % (oldPath + zfill(str(framenr), 4) + ".tga")
### joinImages


def renderScene(start, end):
  global t_join
  ### start ###
  sce = Scene.getCurrent()
  rc = sce.getRenderingContext()

  rc.enableExtensions(1)
  rc.setImageType(14)
  if t_gray.val:
    rc.enableGrayscale()
  else:
    rc.enableRGBColor()

  oldStart = rc.startFrame()
  oldEnd = rc.endFrame()
  origPath = rc.getRenderPath()
  oldPath = rc.getRenderPath()
  oldPath = oldPath.replace("/",sep)
  oldPath = oldPath.replace("\\",sep)
  oldPath = oldPath.replace(sep*2,sep)
  #render to file's directory if nothing is set
  if oldPath == "":
    oldPath = Get("filename")[:Get("filename").rindex(sep)+1]

  cam = sce.getCurrentCamera()

  #render the current scene
  joinFailed = 0
  frnr = start
  while frnr <= end:
    t = time()
    loc = cam.getLocation()
    if t_left.val:
      rc.setRenderPath(oldPath + "_left_")
      cam.setLocation(loc[0] - f_deltaX.val/2, loc[1], loc[2])
      renderFrame(rc, frnr)
    if t_right.val:
      rc.setRenderPath(oldPath + "_right_")
      cam.setLocation(loc[0] + f_deltaX.val/2, loc[1], loc[2])
      renderFrame(rc, frnr)
    cam.setLocation(loc[0], loc[1], loc[2])

    if t_join.val:
      try:
        joinImages(rc, frnr, oldPath)
      except:
        PupMenu("Error%t|Trying to join: pictures missing")
        t_join.val = 0
        Redraw()
    print "----- Stereo Render Time: %fs -----" % (time() - t)

    #next frame
    frnr += 1

  Scene.Render.CloseRenderWindow()
  rc.enableRGBColor()
  rc.startFrame(oldStart)
  rc.endFrame(oldEnd)
  rc.setRenderPath(oldPath)
  #show result
  if t_join.val:
    if start != end:
      rc.play() #play final animation
    else:
      tempscene = Scene.New("stereorendertempscene")
      tempscene.makeCurrent()
      tempscene.link(Blender.Object.New("Camera"))
      rc2 = tempscene.getRenderingContext()
      rc2.enableBackbuf(1)
      rc2.setBackbufPath(oldPath + zfill(str(frnr-1), 4) + ".tga")
      rc2.setRenderPath(oldPath)
      rc2.enableExtensions(1)
      rc2.setImageType(14)
      rc2.enableRGBColor()
      rc2.setRenderWinSize(100)
      rc2.imageSizeX(finalSize[0])
      rc2.imageSizeY(finalSize[1])
      rc2.render() #show final image
      sce.makeCurrent()
      Scene.Unlink(tempscene)
  ### end ###
  rc.setRenderPath(origPath)
### renderScene


def gui():
  global t_gray, t_rgb, f_deltaX, i_startFrame, i_endFrame, t_left, t_right, t_join, m_settings, sett_name
  col = Window.Theme.Get()[0].get("buts").back
  glClearColor(col[0], col[1], col[2], col[3])
  glClear(GL_COLOR_BUFFER_BIT)
  col = Window.Theme.Get()[0].get("buts").text_hi
  glColor3f(col[0], col[1], col[2])
  PushButton("Exit", 1, 10, 10, 100, 20, "This one should explain itself :)")
  PushButton("Correct Cam", 5, 120, 40, 100, 20, "Create an empty to carry the camera, for correct images in animation or with rotated cam")
  f_deltaX = Number("deltaX: ", 2, 10, 40, 100, 20, f_deltaX.val, 0.001, 1.0, "The distance between the \"eyes\", the best value depends on the distance to the foreground objects")
  i_startFrame = Number("Sta: ", 2, 10, 70, 105, 20, i_startFrame.val, 1, 18000, "the start frame of the animation")
  i_endFrame = Number("End: ", 2, 115, 70, 105, 20, i_endFrame.val, 1, 18000, "the end frame of the animation")
  t_gray = Toggle("BW", 8, 10, 95, 105, 20, t_gray.val, "save as grayscale, faster (result will be RGB)")
  t_rgb = Toggle("RGB", 9, 115, 95, 105, 20, t_rgb.val, "save as RGB, colored pictures but slower")
  t_left = Toggle("Left", 2, 10, 120, 70, 20, t_left.val, "render left eye picture")
  t_join = Toggle("Join", 2, 80, 120, 70, 20, t_join.val, "join images to red-cyan stereo image")
  t_right = Toggle("Right", 2, 150, 120, 70, 20, t_right.val, "render right eye picture")
  PushButton("RENDER", 6, 10, 150, 100, 30, "render frame \"Sta:\"")
  PushButton("ANIM", 7, 120, 150, 100, 30, "render animation - WARNING: save your file, you can't stop animation rendering")
  PushButton("Load", 10, 10, 215, 100, 20, "Load settings")
  PushButton("Save", 11, 120, 215, 100, 20, "Save settings")
  sett_name = String("", 2, 145, 190, 75, 20, sett_name.val, 20, "Name of setting for ** Add New **")
  m_settings = Menu(settings, 3, 10, 190, 130, 20, m_settings.val, "The settings to load or save")
  glRasterPos2d(180, 10)
  Text("by pat")

def event(evt, val):
  if (evt == QKEY and not val): Exit()

def bevent(evt):
  if (evt == 1): Exit()
  elif evt == 5: repCam()
  elif (evt == 6):
    saveSettings("** last render **")
    checkSettings()
    renderScene(i_startFrame.val, i_startFrame.val)
  elif (evt == 7):
    saveSettings("** last render **")
    checkSettings()
    renderScene(i_startFrame.val, i_endFrame.val)
  elif (evt == 8):
    t_rgb.val = not t_gray.val
    Redraw()
  elif (evt == 9):
    t_gray.val = not t_rgb.val
    Redraw()
  elif (evt == 10): #load
    if m_settings.val != 1:
      name = settings.split("|")[m_settings.val]
      loadSettings(name)
  elif (evt == 11): #save
    if m_settings.val == 1:
      if sett_name.val != "":
        saveSettings(sett_name.val)
        #select new setting in menu
        m_settings.val = checkSettings()
    else:
      name = settings.split("|")[m_settings.val]
      saveSettings(name)
      checkSettings()

Register(gui, event, bevent)
checkSettings()
