[currently viewing: /raytrace/scene_export.py]
 
#!BPY
"""Registration info for Blender menus:
Name: '342 scene description (.txt)...'
Blender: 232
Group: 'Export'
Tooltip: 'Export for the 342 raytracer scene file'
"
""
#-----------------------------------------------------------------
# Export for the 342 raytracer scene file
#-----------------------------------------------------------------

####################################
# Global Variables
####################################

_safeOverwrite = True     # If false, will overwrite files without asking.

# Private, don't change these
_file = None
_fatalError = False

####################################
# Library dependancies
####################################
import sys
from math import *
from os.path import exists, join
 
try:
  import Blender
  from Blender import World, Camera, Object, NMesh, Lamp, Draw, BGL
  from Blender.Mathutils import *
except:
  print "Fatal Error! Unable to find Blender modules!"
  print "Are you running this script from within blender?"
  _fatalError = True


##########################################################
# Callbacks, needed before Main
##########################################################
def select_file(filename):

  if filename.find('.txt', -4) <= 0: filename += '.txt'
  
  if exists(filename) and _safeOverwrite:
    result = Draw.PupMenu("File Already Exists, Overwrite?%t|Yes%x1|No%x0")
    if(result != 1):
      return

  export_file(filename)

########################################
# Main
########################################
if(not _fatalError):

  if Blender.Get('version') < 232:
    print "Warning: this exporter may not function with versions of blender"
    print "older than 2.32. It if fails please download the latest version"
    print "from http://blender.org"

  if sys.hexversion < 0x020300F0:
    print "NOTICE: Python 2.2 or older, applying Document.writexml patch"
    Document.writexml = patch2_2_Document_writexml
    DocumentType.writexml = patch2_2_DocumentType_writexml

  Blender.Window.FileSelector(select_file,"Export scene file")

###########################################################
# Functions for writing output file
##########################################################
def export_file(filename):
  global _file
  _file = open(filename,"w")
  
  print ""
  print "*** Exporting scene file to:"
  print " ", filename
  writeFile(_file)

  print "*** Done"

########################################################
# Functions for writing scene structure
########################################################

def writeFile(file):

  # World details
  writeWorld()

  ##
  # There's no reason to have this as three loops
  # -- the data can be written in any order, but this is
  #    asthetically pleasing.
  # There may be easier ways to get 'just the lights',
  # and 'just the objects'.

  # Get the scene objects
  objects = Object.Get()
  
  # Camera
  for obj in objects:
    if obj.getType() == "Camera":
      writeCamera(obj)

  # Lights
  _file.write("\n")
  _file.write("# Outputting the lights in the scene\n")
  _file.write("# NOTE: Converted all blender light sources to point lights\n")
  _file.write("\n")
  for obj in objects:
    if obj.getType() == "Lamp":
      writeLamp(obj)

  #Meshes
  _file.write("\n\n")
  _file.write("# Outputting the meshes in the scene\n")
  _file.write("# NOTE: make sure all faces are triangles!!\n")
  for obj in objects:
    if obj.getType() == "Mesh":
      writeMesh(obj.getData(), obj.matrixWorld)

  # Done!
  _file.write("\nendview\n")


##
# Output a matrix in "non-transposed" format ....
def writeMatrix(matrix):
  _file.write("transformation\n")
  _file.write("  %.3f %.3f %.3f %.3f\n"
               % (matrix[0][0], matrix[1][0], matrix[2][0], matrix[3][0]))
  _file.write("  %.3f %.3f %.3f %.3f\n"
               % (matrix[0][1], matrix[1][1], matrix[2][1], matrix[3][1]))
  _file.write("  %.3f %.3f %.3f %.3f\n"
               % (matrix[0][2], matrix[1][2], matrix[2][2], matrix[3][2]))
  _file.write("  %.3f %.3f %.3f %.3f\n"
               % (matrix[0][3], matrix[1][3], matrix[2][3], matrix[3][3]))
##

#############################################################
# Functions for exporting Mesh Objects
#############################################################
def writeMesh(mesh, matrix):
  print "Exporting Mesh :  \"%s\" %i vertices, %i faces" %\
      (mesh.name,len(mesh.verts),len(mesh.faces))

  if(mesh.getMode() & NMesh.Modes['AUTOSMOOTH']):
    print "Warning: Autosmoothing is not supported."
    print "Export will behave as if this feature were off"

  if (len(mesh.materials) > 1):
    print ""
    print "Warning: Object has more than one material."
    print "Only one material property will be output!!"
    print ""
    _file.write("\n# Multiple Materials not exported.\n")

  _file.write("# Mesh:  %s" %  mesh.name)
  _file.write("\nmesh\n")
  if (len(mesh.materials) == 0):
    writeDefaultMaterial()
  else:
    writeMaterial(mesh.materials[0])
  writeVertexData(mesh, matrix)

##########################################################
# Functions for exporting vertex data
##########################################################
def writeVertexData(mesh, matrix):
  print "Vertex"
  getVertices(mesh, matrix)
  getIndices(mesh)

def getVertices(mesh, matrix):
  _file.write("vertices  %i\n" % (len(mesh.verts)) )
  for vert in mesh.verts:
    vertex = apply_transform(vert, matrix)
    _file.write("  %.3f %.3f %.3f\n" % (tuple(vertex)))

def getIndices(mesh):
  _file.write("faces     %i\n" % (len(mesh.faces)) )
  for face in mesh.faces:
    _file.write("  ")
    if (len(face.v) != 3):
      _file.write("error_in_blender_scene: non-triangle mesh found!!!\n  ")
    for vert in face.v:
      _file.write("%i " % vert.index)
    _file.write("\n")
  _file.write("\n")

############################################################
# Functions for exporting material data
############################################################

##
# Exporting default material
def writeDefaultMaterial():
  _file.write("ambient   0.500 0.500 0.500\n")
  _file.write("diffuse   0.640 0.640 0.640\n")
  _file.write("specular  0.500 0.500 0.500  50.00\n")
  _file.write("mirror    0.000 0.000 0.000\n")

##
# Exporting material
def writeMaterial(mat):
  _file.write("ambient   %.3f %.3f %.3f\n" % (mat.amb, mat.amb, mat.amb))
  _file.write("diffuse   %.3f %.3f %.3f\n" % (tuple(getDiffuse(mat))))
  _file.write("specular  %.3f %.3f %.3f  " % (tuple(getSpecular(mat))))
  _file.write("%.3f\n" % (mat.hard))
  _file.write("mirror    %.3f %.3f %.3f\n" % (tuple(getMirror(mat))))


def getColour(c):
  return "%.3f %.3f %.3f" % (c[0], c[1], c[2])

# the following get colour functions are a best guess
# as to how to convert blender's colour model

##
# Not dealing with emissive materials
def getEmissive(mat):
  eCol = mat.rgbCol
  for c in range(3):
    eCol[c] *= mat.emit
  return eCol

##
#  Diffuse == (diffuse_colour) * (diffuse_reflectivity)
#           * (1 - (mirror_reflectivity))
#
#  I guess ... the latter factor looks 'odd' to me!
#
def getDiffuse(mat):
  dCol = mat.rgbCol
  for c in range(3):
    dCol[c] *= mat.ref
    dCol[c] *= (1 -  mat.rayMirr)
  return dCol

##
#  Specular == (specular_colour) * (specular_reflectivity)
#
#  making sure it's in the range [0.0, 1.0]
#
def getSpecular(mat):
  sCol = mat.specCol
  for c in range(3):
    sCol[c] = min(sCol[c]*mat.spec,1.0)
  return sCol

##
#  Mirror == (mirror_colour) * (mirror_reflectivity)
#
#  I'm always assuming that the object is 'reflective'
#
def getMirror(mat):
  mCol = mat.mirCol
  for c in range(3):
    mCol[c] *= mat.rayMirr
  return mCol


#########################################################
# Functions for exporting details about the World
#########################################################
def writeWorld():
  print "Exporting the World"

  ## Some hard coded values for the view screen
  ## Rather dodgy, and may need to be fixed!!!
  ##
  _file.write("\n")
  _file.write("# default value for the size of the image to be created\n")
  _file.write("imagesize     320 \n")
  _file.write("\n")

  ## An attempt to get some information about the world
  ## Background == Horizon
  _file.write("# The background colour of the scene\n")
  _file.write("# -- using the blender 'horizon' value\n")
  hor = (World.Get())[0].getHor()
  _file.write("background    %.3f %.3f %.3f\n" % tuple(hor) )
  _file.write("\n")

  ## Get some information about the world
  _file.write("# The ambient lighting of the scene\n")
  amb = (World.Get())[0].getAmb()
  _file.write("ambient       %.3f %.3f %.3f\n" % tuple(amb) )
  _file.write("\n")

  
  
#########################################################
# Functions for exporting camera data
#########################################################
def writeCamera(obj):
  print "Exporting Camera : \"%s\"" % (obj.name)

  _file.write("# the default camera is located at the origin\n")
  _file.write("# the default viewplane has corners\n")
  _file.write("# --  top left:     (-16,  16, -lens) \n")
  _file.write("# --  bottom right: ( 16, -16, -lens) \n")
  _file.write("# ((Actually, Blender uses a 32*24 viewplane))\n")
  _file.write("# \n")
  _file.write("camera\n")

  ## The camera's 'lens'  -- distance to standard viewplane (32 * 24)
  cam = (Camera.Get())[0].getLens()
  _file.write("lens  %.3f\n" % (cam) )

  ## Output the camera transformation from the 'standard position'
  writeMatrix(obj.matrixWorld)
  _file.write("\n")


#########################################################
# Functions for exporting light data
#########################################################
def writeLamp(obj):
  light = obj.getData()
  print "Exporting Light : \"%s\"" % (obj.name)

#  if(light.type == Lamp.Types['Lamp']):
#    writePointLight(obj)
#  else:
#    print "Error: Light type not recognized"

  # output all blender light sources as point lights
  writePointLight(obj)

def writePointLight(obj):
  light = obj.getData()
  _file.write("light   ")
  _file.write("%.3f %.3f %.3f   "   % (obj.loc) )
  _file.write("%.3f %.3f %.3f\n"    % (tuple(light.col)) )

    
###############################################
# Basic utility functions
###############################################
def radians(deg):
  return deg * pi/180.0

def degs(rad):
  return rad * 180.0/pi

def euler2AxisAngle(rot):
  c = ( cos(rot[0]/2.0), cos(rot[1]/2.0), cos(rot[2]/2.0) )
  s = ( sin(rot[0]/2.0), sin(rot[1]/2.0), sin(rot[2]/2.0) )
  
  a = 2*acos( c[0]*c[1]*c[2] + s[0]*s[1]*s[2] )
  z = c[0]*c[1]*s[2] - s[0]*s[1]*c[2]
  y = c[0]*s[1]*c[2] + s[0]*c[1]*s[2]
  x = s[0]*c[1]*c[2] - c[0]*s[1]*s[2]

  if( abs(a) < 0.0001 ): a = 0
  if( abs(x) < 0.0001 ): x = 0
  if( abs(y) < 0.0001 ): y = 0
  if( abs(z) < 0.0001 ): z = 0
  
  len = sqrt( x*x + y*y + z*z )
  if( abs(len) > 0.0001 ):
    x /= len
    y /= len
    z /= len

  return ( x, y, z, a )

def euler2Vector(rot):
  vec = Vector([0,0,-1])
  x,y,z = rot
  mat = Euler([degs(x),degs(y),degs(z)]).toMatrix()
  res = VecMultMat(vec,mat.rotationPart()) 
  return res[0],res[1],res[2]


# =======================
# === Apply Transform ===
# =======================
def apply_transform(vertex, matrix):
    x, y, z = vertex
    xloc, yloc, zloc = matrix[3][0], matrix[3][1], matrix[3][2]
    xcomponent = x*matrix[0][0] + y*matrix[1][0] + z*matrix[2][0] + xloc
    ycomponent = x*matrix[0][1] + y*matrix[1][1] + z*matrix[2][1] + yloc
    zcomponent = x*matrix[0][2] + y*matrix[1][2] + z*matrix[2][2] + zloc
    vertex = [xcomponent, ycomponent, zcomponent]
    return vertex