#!BPY


"""
Name: 'SoftImage XSI (.xsi)...'
Blender: 236
Group: 'Export'
Tooltip: 'Export to a SoftImage XSI file'
"""

__author__ = ("Elira")
__url__ = ["Author's site, http://www.creative-realms.net/~elira/blender.html",
"SoftImage's site, www.softimage.com", "elysiun"]
__email__ = ["scripts"]
__version__ = "2005/11/01"


__bpydoc__ = """\
This script exports to the XSI format.

Usage:

Run this script from "File->Export" menu.

Note:<br>
- Updates by Mal Duffin, to assist with XSI to Shockwave 3D conversion.
"""

# $Id: xsi_export.py,v 1.4.6 2005/11/01 
#
#------------------------------------------------------------------------
# XSI exporter for blender 2.36 or above
#
# ***** BEGIN GPL LICENSE BLOCK *****
#
# 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 *****
#


#
# ---------------------------------------------------------------------------
# XSI Export V 1.4.1 by Elira (at) creative-realms (dot) net
# 
# Updates by Mal Duffin, to assist with XSI to Shockwave 3D conversion
# ---------------------------------------------------------------------------
# 0.0.0 - This header and having blender ID the file.
# 0.1.0 - Output the statis xsi header elements
# 0.2.0 - create a full shell output (no content just structure)
# 0.3.0 - output used materials from the full materials list
# 0.4.0 - output the object model minor data
# 0.5.0 - output the object shape data, storing a uv table
# 0.6.0 - output the triangle lists (uv references stored uv table)
# 0.7.0 - convert output to genuine file writes.
# 1.0.0 - Admit this script exists and wait for flames
# 1.1.0 - Correctly export mesh shapes
# 1.2.0 - Mesh positioning corrected, added back normals
# 1.3.0 - conditionally output uv co-ordinates
# 1.4.0 - export vertex paint colours.
# ---------------------------------------------------------------------------
# 1.4.1 - added basic normal export code, 
#         to get XSI to Shockwave 3D converter working ( Mal Duffin )
# 1.4.2 - invalid mesh checking
#         better normal exporting
#         general code clean up
# 1.4.3 - basic light exporting
#         fix for ambient light being ignored by Shockwave 3D converter
# 1.4.4 - basic camera exporting
# 1.4.5 - exports normals correctly
# 1.4.6 - exports multiple materials per object
# ---------------------------------------------------------------------------
# TO DO
# - Support texturing
#  - for both methods of texturing ( render method, and Game Engine method )
# ---------------------------------------------------------------------------
# add required modules

import Blender
from Blender import sys as bsys
from Blender import Mathutils
from Blender import Lamp
from Blender import Camera
import math



# ---------------------------------------------------------------------------
# globals to make things a lot lot easier
OBJ = []		# the object list
MAT = []		# the materials list 
UVC = []		# uv vert co-ords
UVI = []                # uv vert index
VCC = []		# vert colour co-ords
VCI = []                # vert colour index
FD  = []                # file handle
NORMALS = []			# normal list
mats = []
EXPORT_DIR = ''
WORLD = Blender.World.Get() 

# ---------------------------------------------------------------------------
# get_path returns the path portion o/wf the supplied filename.
# ---------------------------------------------------------------------------
def get_path(file):
  l=len(file)
  r=0
  for i in range(l, 0, -1):
    if r == 0:
      if file[i-1] == "/" or file[i-1] == "\\":
        r = i
  return file[:r]



# ---------------------------------------------------------------------------
# r2d - radians to degrees
# ---------------------------------------------------------------------------
def r2d(r):
  return round(r*180.0/math.pi,4)



# ---------------------------------------------------------------------------
# d2r - degrees to radians
# ---------------------------------------------------------------------------
def d2r(d):
  return (d*math.pi)/180.0



# ---------------------------------------------------------------------------
# get_filename returns the filename 
# ---------------------------------------------------------------------------
def get_filename(file):
  l=len(file)
  r=0
  for i in range(l, 0, -1):
    if r == 0:
      if file[i-1] == "/" or file[i-1] == "\\":
        r = i
  return file[r:]


# ---------------------------------------------------------------------------
# find materials returns all materials on an object.
# ---------------------------------------------------------------------------
def get_materials(obj):

  # any materials attached to the object itself
  mats = obj.getMaterials(0)

  if 'Mesh' != obj.getType():
    return mats

  # now drop down to the mesh level
  #mesh = Blender.NMesh.GetRaw(obj.data.name)
  mesh = obj.data

  if mesh.materials:
    for mat in mesh.materials:
      mats.append(mat)

  # return the materials list
  return mats



# ---------------------------------------------------------------------------
# apply_transform converts a vertex to co-ords
# ---------------------------------------------------------------------------
def apply_transform(vert, matrix):
  vc = Mathutils.CopyVec(vert)
  vc.resize4D()
  return Mathutils.VecMultMat(vc, matrix)



# ---------------------------------------------------------------------------
# do_header writes out the header data
# ---------------------------------------------------------------------------
def do_header():

  global FD

  # this says which xsi version
  FD.write("xsi 0300txt 0032\n\n")

  # static fileinfo block
  FD.write("SI_FileInfo  {\n")
  FD.write("	\"Blender Scene\",\n")
  FD.write("	\"Blender User\",\n")
  FD.write("	\"Now\",\n")
  FD.write("	\"xsi_export Blender Scene Exporter\",\n")
  FD.write("}\n\n")

  # static scene block
  FD.write("SI_Scene no_name {\n")
  FD.write("	\"FRAMES\",\n")
  FD.write("	0.000000,\n")
  FD.write("	100.000000,\n")
  FD.write("	30.000000,\n")
  FD.write("}\n\n")

  # static co-ordinate system block
  FD.write("SI_CoordinateSystem coord {\n")
  FD.write("	1,\n")
  FD.write("	0,\n")
  FD.write("	1,\n")
  FD.write("	0,\n")
  FD.write("	5,\n")
  FD.write("	2,\n")
  FD.write("}\n\n")

  # static angle block 
  FD.write("SI_Angle  {\n")
  FD.write("	0,\n")
  FD.write("}\n\n")

  # static ambience block 
  ambient = WORLD[0].getAmb()

  FD.write("SI_Ambience  {\n")
  FD.write("	%f,\n" % ambient[0])
  FD.write("	%f,\n" % ambient[1])
  FD.write("	%f,\n" % ambient[2])
  FD.write("}\n\n")



# ---------------------------------------------------------------------------
# do_materiallibrary writes out the materials subsection.
# ---------------------------------------------------------------------------
def do_materiallibrary():

  global OBJ, MAT, FD

  # set some flags first
  mnum = 0

  # run through every material, how many used?
  for mat in MAT:
    nmat = mat.getName()

    # first, is this material on any of the objects.
    f = 0
    for obj in OBJ:
      ml = get_materials(obj)
      for mli in ml:
        nmli = mli.getName()
        if nmli == nmat:
          f = 1
          mnum += 1
          break
      if f == 1:
        break

  bCreateDefault = 0
  # if none then exit
  if not mnum:
    bCreateDefault = 1
#    return
    
  # get to work create the materiallibrary wrapper and fill.
  FD.write("SI_MaterialLibrary  {\n")
  FD.write("	" + str(mnum) + ",\n")

  # run through every material, write the used ones
  for mat in MAT:
    nmat = mat.getName()

    # find out if on any object, if so we write.
    f = 0
    for obj in OBJ:
      ml = get_materials(obj)
      for mli in ml:
        nmli = mli.getName()
        if nmli == nmat:
          do_material(mat)
          f = 1
          break
      if f == 1:
        break

  if bCreateDefault == 1:
    do_material ( 0 )

  # clean up
  FD.write("}\n\n")


def removeSpacesFromName(name):
  name = name.replace ( " ", "_" )
  return name


# ---------------------------------------------------------------------------
# do_material writes out this material.
# ---------------------------------------------------------------------------
def do_material(mat):

  global FD

  if mat == 0:
    name = "__default"
    cr = 1.0
    cg = 1.0
    cb = 1.0	
    ca = 1.0
    sp = 0.0
    sr = 0.0
    sg = 0.0
    sb = 0.0
    em = 0.0
    am = 1.0
    sm = 0
  else:
		

    # get the name first
    name = mat.getName()

   # face colour		r, g, b, a
  # power (spec decay)  fl
  # spec colour		r, g, b
  # emmisive colourm	r, g, b
  # shading model       int   constant, lambert, phong, blinn, shadow, vertex
  # ambient colour	r, g, b

  # get and print the base material block
    cr, cg, cb = mat.getRGBCol()
    ca = mat.getAlpha()

    sp = 0.0
    sr, sg, sb = mat.getSpecCol()
    em = mat.getEmit()
    am = mat.getAmb()
 
  # how do we render this material? start with constant (0)
    sm = 0
    fl = mat.getMode()
    if fl & Blender.Material.Modes['VCOL_PAINT']:
      sm = 5
  

  FD.write("	SI_Material " + removeSpacesFromName(name) + " {\n")
  FD.write("		%f,\n" % cr)
  FD.write("		%f,\n" % cg)
  FD.write("		%f,\n" % cb)
  FD.write("		%f,\n" % ca)
  FD.write("		%f,\n" % sp)
  FD.write("		%f,\n" % sr)
  FD.write("		%f,\n" % sg)
  FD.write("		%f,\n" % sb)
  FD.write("		%f,\n" % em)
  FD.write("		%f,\n" % em)
  FD.write("		%f,\n" % em)
  FD.write("		%d,\n" % sm)
  #FD.write("		%f,\n" % am)
  #FD.write("		%f,\n" % am)
  #FD.write("		%f,\n" % am)
  FD.write("		%f,\n" % cr)
  FD.write("		%f,\n" % cg)
  FD.write("		%f,\n" % cb)

  if mat != 0:
  # if this material has a texture, then add here
    mtex = mat.getTextures()
    for mt in mtex:
      if mt:
        do_texture(mt)

  FD.write("	}\n")



# ---------------------------------------------------------------------------
# do_texture writes out this texture if usable.
# ---------------------------------------------------------------------------
def do_texture(mtex):
  global FD


  # get our texture
  tex = mtex.tex
  tn = tex.getName()


  # what type of texture, we are limitd
  if tex.type != Blender.Texture.Types.IMAGE:
    return


  FD.write("		SI_Texture2D " + tn + " {\n")

  img = tex.getImage()
  iname = get_filename(img.getFilename())

  FD.write("			\"" + iname + "\",\n")

  # mapping type  ? uv  map wrapped is 4, how to detect?
  # start with a simple xy mapping ie 0
  FD.write("			4,\n")

  print img.getSize ()

  # image width, and height
  ix, iy = img.getSize()
  FD.write("			%d,\n" % ix)
  FD.write("			%d,\n" % iy)
  # u crop min/max, v crop min/max
  mincu, mincv, maxcu, maxcv = tex.crop
  FD.write("			%d,\n" % ( mincu * ix ) )
  FD.write("			%d,\n" % ( maxcu * ix - 1 ) )
  FD.write("			%d,\n" % ( mincv * iy ) )
  FD.write("			%d,\n" % ( maxcv * iy - 1) )
  # uv swap
  uvs =0
  if (tex.flags & Blender.Texture.Flags.FLIPBLEND):
    uvs = 1
  FD.write("			%d,\n" % uvs )
  # u/v repeat
  iru = img.getXRep()
  FD.write("			%d,\n" % iru )
  irv = img.getYRep()
  FD.write("			%d,\n" % irv )
  # u/v alt - 0, 0
  FD.write("			0,\n" ) 
  FD.write("			0,\n" )
  # u/v scale - 1,1
  FD.write("			1.000000,\n" )
  FD.write("			1.000000,\n" )
  # u/v offset - 0,0
  FD.write("			0.000000,\n" )
  FD.write("			0.000000,\n" )
  # proj mat 4x4 1 0 0 0, 0 1 0 0, 0 0 1 0, 0 0 0 1  is default
  FD.write("			1.000000,\n" )
  FD.write("			0.000000,\n" )
  FD.write("			0.000000,\n" )
  FD.write("			0.000000,\n" )

  FD.write("			0.000000,\n" )
  FD.write("			1.000000,\n" )
  FD.write("			0.000000,\n" )
  FD.write("			0.000000,\n" )

  FD.write("			0.000000,\n" )
  FD.write("			0.000000,\n" )
  FD.write("			1.000000,\n" )
  FD.write("			0.000000,\n" )

  FD.write("			0.000000,\n" )
  FD.write("			0.000000,\n" )
  FD.write("			0.000000,\n" )
  FD.write("			1.000000,\n" )

  # blending type - 3
  FD.write("			3,\n" )
  # blending - 1
  FD.write("			1.000000,\n" )
  # ambient - 0
  FD.write("			0.000000,\n" )
  # diffuse - 1
  FD.write("			1.000000,\n" )
  # speculara - 0
  FD.write("			0.000000,\n" )
  # transparent - 0
  FD.write("			0.000000,\n" )
  # reflective - 0
  FD.write("			0.000000,\n" )
  # roughness - 0
  FD.write("			0.000000,\n" )
  
  # close off this texture
  FD.write("		}\n")



# ---------------------------------------------------------------------------
# do_model_transform dumps out the transform data
# ---------------------------------------------------------------------------
def do_model_transform(obj):

  global FD

  # now output
  FD.write("		SI_Transform SRT-" + removeSpacesFromName( obj.getName() ) + " {\n" )

  

  # write out the object size? (scaling)
  FD.write("			%f,\n" % obj.SizeX )
  FD.write("			%f,\n" % obj.SizeY )
  FD.write("			%f,\n" % obj.SizeZ )
  
  # write out the object rotation
  FD.write("			%f,\n" % r2d(obj.RotX) )
  FD.write("			%f,\n" % r2d(obj.RotY) )
  FD.write("			%f,\n" % r2d(obj.RotZ) )

  # this is the position of the object's axis
  FD.write("			%f,\n" % obj.LocX )
  FD.write("			%f,\n" % obj.LocY )
  FD.write("			%f,\n" % obj.LocZ )
  FD.write("		}\n\n")



# ---------------------------------------------------------------------------
# do_model_visibility marks if the model is visible or not???
# ---------------------------------------------------------------------------
def do_model_visibility(obj):

  global FD

  # for now this is a static block
  FD.write("		SI_Visibility  {\n" )
  FD.write("			1,\n" )
  FD.write("		}\n\n" )



# ---------------------------------------------------------------------------
# do_model_material sets the global material for the model
# ---------------------------------------------------------------------------
def do_model_material(obj):

  global FD

  # do we have one?
  ml = get_materials(obj)


  n = 0
  for mli in ml:
    if mli:
      n+=1
      if n == 1:
        mat=mli


  # if no materials just go back
  if n == 0:
    return

  # for now we grab the first material on the list.

  for mat in ml:
    FD.write("		SI_GlobalMaterial  {\n" )
    FD.write("			\"" + removeSpacesFromName(mat.getName()) + "\",\n" )
    FD.write("			\"NODE\",\n" )
    FD.write("		}\n\n" )



def meshHasUV ( mesh ):
  if mesh.hasFaceUV():
    return TRUE
#  materials = mesh.materials
#    if len(materials) > 0:
	  
  return FALSE

# ---------------------------------------------------------------------------
# do_collect_uv, makes an easy to use list out of the uv data
# todo, remove duplicates and compress the list size, xsi supports this.
# ---------------------------------------------------------------------------
def do_collect_uv(mesh):

  global UVC, UVI

  # reset the uv details first.
  UVI = []
  UVC = []

  #print "Textures..."
  #mtex = mat.getTextures()
  #for mt in mtex:
  #  print mt


  # if no uv data then return
  if not mesh.hasFaceUV():
    return

  # run through all the faces
  j = 0
  for f in mesh.faces:
    for i in range(len(f)):
      UVI.append(j)
      UVC.append(f.uv[i])
      j+=1
    UVI.append(-1)



# ---------------------------------------------------------------------------
# do_collect_colour, makes an easy to use list out of the colour data
# todo, remove duplicates and compress the list size, xsi supports this.
# ---------------------------------------------------------------------------
def do_collect_colour(mesh):

  global VCC, VCI

  # reset the uv details first.
  VCC = []
  VCI = []

  # if no uv data then return
  if not mesh.hasVertexColours():
    return

  # run through all the faces
  j = 0
  for f in mesh.faces:
    for i in range(len(f)):
      VCI.append(j)
      VCC.append(f.col[i])
      j+=1
    VCI.append(-1)



# ---------------------------------------------------------------------------
# do_mesh_shape outputs the shape data
# ---------------------------------------------------------------------------
def do_mesh_shape(obj):

  global UVC, UVI, VCC, VCI, FD, NORMALS

  # Grab the mesh itself
  mesh = Blender.NMesh.GetRaw(obj.data.name)

  # get the world matrix
  matrix = obj.getMatrix('worldspace')

  # we need to decide about vertex and uv details first.
  do_collect_uv(mesh)
  do_collect_colour(mesh)

  # output the shell
  elements=2
  if len(UVC):
    elements+=1
  if len(VCC):
    elements+=1
  FD.write("			SI_Shape SHP-" + removeSpacesFromName ( obj.getName() ) + "-ORG {\n" )
  FD.write("				%d,\n" % elements )
  FD.write("				\"ORDERED\",\n\n" )

  # vertices first
  FD.write("				%d,\n" % len(mesh.verts) )
  FD.write("				\"POSITION\",\n" )
  for v in mesh.verts:
    FD.write("				%f,%f,%f,\n" % (v.co[0], v.co[1], v.co[2]) )
  FD.write("\n")


  print "  MESH NAME = " + mesh.name

  NORMALS = []
  for f in mesh.faces:
    NORMALS.append ( f.no )
  for v in mesh.verts:
    aTemp = [v.no[0], v.no[1], v.no[2]]
    NORMALS.append ( aTemp )


  FD.write("				%d,\n" % len(NORMALS) )
  FD.write("				\"NORMAL\",\n" )							
				
  for n in NORMALS:
	FD.write("				%f,%f,%f,\n" % ( n[0], n[1], n[2] ) )

  # if vertex colour data then process
  if mesh.hasVertexColours():

    # put out the co-ord header
    FD.write("				%d,\n" % len(VCC) )
    FD.write("				\"COLOR\",\n" )

    # now output them
    for vc in VCC:
      FD.write("				%f,%f,%f,%f,\n" % (vc.r/255.0, vc.g/255.0, vc.b/255.0, vc.a/255.0) )



  # if uv data then process
  if mesh.hasFaceUV():
    # put out the co-ord header
    FD.write("				%d,\n" % len(UVC) )
    FD.write("				\"TEX_COORD_UV\",\n" )

    # now output them
    for uv in UVC:
      FD.write("				%f,%f\n" % (uv[0], uv[1]) )

  # close off
  FD.write("			}\n" )
 


# ---------------------------------------------------------------------------
# do_mesh_faces outputs the faces data
# ---------------------------------------------------------------------------
def do_mesh_faces(obj):

  global FD, UVI, VCI, mats

  # do we have a texture?
  ml = get_materials(obj)
  n = 0
  for mli in ml:
    if mli:
      n+=1
      if n == 1:
        mat=mli

  # Grab the mesh itself
  # mesh = Blender.NMesh.GetRaw(obj.data.name)
  
  # mesh = Blender.NMesh.GetRawFromObject(obj.name)

  mesh = obj.data



  tris = []
  normalX = []
  mats = []
  for f in mesh.faces:
    tris.extend ( triangulate_face(f) )
    aVal = triangulate_normals(mesh,f)

    for v in aVal:
      normalX.append ( v )


  triangles = len(tris)

  if n == 0:
    FD.write("			SI_TriangleList " + removeSpacesFromName(obj.getName()) + " {\n")
    FD.write("				%d,\n" % triangles)

    ostring="				\"NORMAL"
    if len(VCI):
      ostring += "|COLOR"
    if len(UVC):
      ostring += "|TEX_COORD_UV"
    ostring += "\",\n"
    FD.write(ostring)

    FD.write("				\"\",\n\n")

    for t in tris:
      FD.write("				%d,%d,%d,\n" % (t[0], t[2], t[1]))

    FD.write("\n")

    for n in normalX:
      FD.write("				%d,%d,%d,\n" % ( n[0], n[1], n[2] ) )

  # finally close this triangle list off
    FD.write("			}\n\n")



  print "total materials"
  print ml

  for mIndex in range (0,len(ml)):
    mat = ml[mIndex]
    print "checking materials"
    print mat

    aTriCount = 0
    for tIndex in range ( 0, len ( tris ) ):
      aMat = mats[tIndex]
      if aMat == mIndex:
        aTriCount = aTriCount + 1	

  #
  # output the shell
    FD.write("			SI_TriangleList " + removeSpacesFromName(obj.getName()) + " {\n")
  #  FD.write("				%d,\n" % triangles)
    FD.write("				%d,\n" % aTriCount)

    ostring="				\"NORMAL"
    if len(VCI):
      ostring += "|COLOR"
    if len(UVC):
      ostring += "|TEX_COORD_UV"
    ostring += "\",\n"
    FD.write(ostring)


    FD.write("				\"" + removeSpacesFromName ( mat.getName() ) + "\",\n\n")

#    FD.write("				\"\",\n\n")


    for tIndex in range ( 0, len ( tris ) ):
      aMat = mats[tIndex]
      if mIndex == aMat:
        t = tris[tIndex]
        FD.write("				%d,%d,%d,\n" % (t[0], t[2], t[1]))

    FD.write("\n")



#    for n in normalX:
    for tIndex in range ( 0, len ( tris ) ):
      aMat = mats[tIndex]
      if mIndex == aMat:
        n = normalX[tIndex]
        FD.write("				%d,%d,%d,\n" % ( n[0], n[1], n[2] ) )



  # if we have it, put out the colour vertex list
  #  ostring = "				"
  #  for i in range(len(VCI)):
    # if a -1 its end of line, write.
  #    if VCI[i] == -1:
  #      ostring = ostring + "\n"
  #      FD.write(ostring)
  #      ostring="				"
  #    else:
  #      ostring = ostring + "%d," % VCI[i]

  # The final set is to work out the uv list, its one set per face
  #  ostring = "				"
  #  for i in range(len(UVI)):
  #    # if a -1 its end of line, write.
  #    if UVI[i] == -1:
  #      ostring = ostring + "\n"
  #      FD.write(ostring)
  #      ostring="				"
  #    else:
  #      ostring = ostring + "%d," % UVI[i]

  # finally close this triangle list off
    FD.write("			}\n\n")


def getNormalInfo(mesh, faceInfo):
  global NORMALS
  aNL = []
  for fi in faceInfo:
    aN = []

    aFace = mesh.faces[fi[0]]

    print aFace

    if (aFace.smooth):
      aN.append ( NORMALS.index ( aFace.v.no[0] ) )
      aN.append ( NORMALS.index ( aFace.v.no[1] ) )
      aN.append ( NORMALS.index ( aFace.v.no[2] ) )
    else:
      aN.append ( NORMALS.index ( aFace.no ) )
      aN.append ( NORMALS.index ( aFace.no ) )
      aN.append ( NORMALS.index ( aFace.no ) )

#      aN.append ( NORMALS.index ( mesh.faces[fi[0]].no ) )
#      aN.append ( NORMALS.index ( mesh.faces[fi[0]].no ) )
#      aN.append ( NORMALS.index ( mesh.faces[fi[0]].no ) )
    
    aNL.append ( aN )
  return aNL
 


# copy of code to triangulate mesh
##################################
def triangulate_face(f):    
  if len(f.v) <= 3:
    #newFaces = [ [f.v[0].index, f.v[1].index, f.v[2].index] ]
    newFaces = [ [f.v[0].index, f.v[2].index, f.v[1].index] ]
    mats.append ( f.materialIndex )
  else:
    #newFaces = [ [f.v[0].index, f.v[1].index, f.v[2].index] ]
    #newFaces.append ( [f.v[3].index, f.v[0].index, f.v[2].index] )
    newFaces = [ [f.v[0].index, f.v[2].index, f.v[1].index] ]
    newFaces.append ( [f.v[3].index, f.v[2].index, f.v[0].index] )
    mats.append ( f.materialIndex )
    mats.append ( f.materialIndex )

  return newFaces

# copy of code to triangulate mesh
##################################
def triangulate_normals(mesh, f): 
 
  if len(f.v) <= 3:
    if f.smooth:	
      n1 = get_normal_index ( mesh, [f.v[0].no[0], f.v[0].no[1], f.v[0].no[2]] )
      n2 = get_normal_index ( mesh, [f.v[1].no[0], f.v[1].no[1], f.v[1].no[2]] )
      n3 = get_normal_index ( mesh, [f.v[2].no[0], f.v[2].no[1], f.v[2].no[2]] )
      newNormals = [[ n1, n2, n3 ]]
    else:
      n1 = get_normal_index ( mesh, [f.no[0], f.no[1], f.no[2]] )
      newNormals = [[ n1, n1, n1 ]]	  
  else:
    if f.smooth:
      n1 = get_normal_index ( mesh, [f.v[0].no[0], f.v[0].no[1], f.v[0].no[2]] )
      n2 = get_normal_index ( mesh, [f.v[1].no[0], f.v[1].no[1], f.v[1].no[2]] )
      n3 = get_normal_index ( mesh, [f.v[2].no[0], f.v[2].no[1], f.v[2].no[2]] )
      n4 = get_normal_index ( mesh, [f.v[3].no[0], f.v[3].no[1], f.v[3].no[2]] )
      newNormals = [ [ n1, n2, n3 ] ]
      newNormals.append ( [ n4, n1, n3 ] )

#      newNormals = [[ n1, n3, n2 ]]
#      newNormals.append ( [ n4, n3, n1 ] )
    else:
      n1 = get_normal_index ( mesh, [f.no[0], f.no[1], f.no[2]] )
      newNormals = [[ n1, n1, n1 ]]
      newNormals.append ( [ n1, n1, n1 ] )

  return newNormals



##################################
def get_normal_index(mesh,normal):
  global NORMALS

  indx=NORMALS.index(normal)
  return indx


# ---------------------------------------------------------------------------
# do_model_mesh outputs the shape/triangelist wrapper block
# ---------------------------------------------------------------------------
def do_model_mesh(obj):

  global FD

  # output the shell
  FD.write("		SI_Mesh MSH-" + removeSpacesFromName(obj.getName()) + " {\n")

  # todo, add calc normals and calc uv here
  # these can be used in both the following sections.

  # next the shape
  do_mesh_shape(obj)

  # finally the trangle list
  do_mesh_faces(obj)

  # finally close this mesh off
  FD.write("		}\n\n")



# ---------------------------------------------------------------------------
# do_model actually outputs a mesh model
# ---------------------------------------------------------------------------
def do_model(obj):

  global FD

  # we only want meshes for now.
  if 'Mesh' != obj.getType():
    return

  # check if the mesh is valid
  if validMesh(obj) <> 0:
	  print "INVALID MESH " + obj.getName ()
	  return


  print "Exporting model " + obj.getName ()

  # start model
  FD.write("	SI_Model MDL-" + removeSpacesFromName(obj.getName()) + " {\n")

  # do transform
  do_model_transform(obj)

  # do visibility
  do_model_visibility(obj)

  # do global material
  do_model_material(obj)

  # do the mesh
  do_model_mesh(obj)

  # close this model
  FD.write("	}\n")

#
# check for invalid mesh ( faces that have < 3 vertices )
#

def validMesh (obj):
  mesh = Blender.NMesh.GetRaw(obj.data.name)
  for f in mesh.faces:
    if len(f.v) < 3:
      print "MESH HAS FACES WITH < 3 VERTICES"
      return 1
  if len (mesh.faces) == 0:
    print "MESH HAS NO FACES"
    return 1
	
  return 0

# ---------------------------------------------------------------------------
# do_models is the process which allows us to write out a bunch of models
# ---------------------------------------------------------------------------
def do_models():

  global OBJ, MAT, FD

  #create the full scene wrapper object
  FD.write("SI_Model MDL-SceneRoot {\n")
  FD.write("	SI_Transform SRT-SceneRoot {\n" )
  FD.write("		1.000000,\n")
  FD.write("		1.000000,\n")
  FD.write("		1.000000,\n")
  FD.write("		-90.000000,\n")
  FD.write("		0.000000,\n")
  FD.write("		0.000000,\n")
  FD.write("		0.000000,\n")
  FD.write("		0.000000,\n")
  FD.write("		0.000000,\n")
  FD.write("	}\n\n")

  # now process the actual selected meshes themselves
  for obj in OBJ:
    do_model(obj)
 
  for obj in OBJ:
    do_light(obj)

  for obj in OBJ:
    do_camera(obj)

  do_light_ambient ()

  # finally close off the model list
  FD.write("}\n")


# ---------------------------------------------------------------------------
# do_light actually outputs a light model
# ---------------------------------------------------------------------------
def do_light(obj):

  global FD

  # we only want lights for now.
  if 'Lamp' != obj.getType():
    return

  print "Exporting light " + obj.getName ()

  aLampType = 1

  lmpName=Lamp.Get(obj.data.getName())
  lmpType=lmpName.getType()

  if lmpType == Lamp.Types.Lamp:
    aLampType = 0
  elif lmpType == Lamp.Types.Spot:
    aLampType = 0
  elif lmpType == Lamp.Types.Sun:
    aLampType = 1
  else:
    aLampType = 0

  # start model
  FD.write("	SI_Light " + removeSpacesFromName(obj.getName()) + " {\n")

  # do type
  FD.write("		%d,\n" % aLampType)

  lampName=Lamp.Get(obj.data.getName())
  colour = lampName.col

  # do color
  FD.write("		%f,\n" % colour[0] )
  FD.write("		%f,\n" % colour[1] )
  FD.write("		%f,\n" % colour[2] )

  # do position

  FD.write("		%f,\n" % obj.LocX )
  FD.write("		%f,\n" % obj.LocY )
  FD.write("		%f,\n" % obj.LocZ )


  # close this model
  FD.write("	}\n")


# ---------------------------------------------------------------------------
# do_light actually outputs a light model
# ---------------------------------------------------------------------------
def do_camera(obj):

  global FD

  # we only want cameras for now.
  if 'Camera' != obj.getType():
    return

  print "Exporting camera " + obj.getName ()



  # start model
  FD.write("	SI_Camera " + removeSpacesFromName(obj.getName()) + " {\n")


  cameraName=Camera.Get(obj.data.getName())
  
  # colour = cameraName.col

  # do position

  FD.write("		%f,\n" % obj.LocX )
  FD.write("		%f,\n" % obj.LocY )
  FD.write("		%f,\n" % obj.LocZ )

  # looking at

  FD.write("		%f,\n" % 0.0 )
  FD.write("		%f,\n" % 0.0 )
  FD.write("		%f,\n" % 0.0 )

  # roll
  FD.write("		%f,\n" % 0.0 )

  aLens = cameraName.getLens()

  # field of view
  FD.write("		%f,\n" % aLens )

  # near plane
  FD.write("		%f,\n" % 1.0 )

  # far plane
  FD.write("		%f,\n" % 10000000.0 )


  # close this model
  FD.write("	}\n")



# ---------------------------------------------------------------------------
# write out the ambient light ( for Shockwave 3D converter )
# ---------------------------------------------------------------------------

def do_light_ambient():
  ambient = WORLD[0].getAmb()
  if ambient == [0.0,0.0,0.0]:
    ambient = [0.5,0.5,0.5]

  FD.write("	SI_Light ambient_sw3d {\n")

  FD.write("		9,\n")
  FD.write("		%f,\n" % ambient[0])
  FD.write("		%f,\n" % ambient[1])
  FD.write("		%f,\n" % ambient[2])
  FD.write("		0.00000000,\n")
  FD.write("		0.00000000,\n")
  FD.write("		0.00000000,\n")

  FD.write("	}\n")



# ---------------------------------------------------------------------------
# export_xsi is the wrapper function to process the loading of an xsi model.
# ---------------------------------------------------------------------------
def export_xsi(filename):

  global OBJ, MAT, FD, EXPORT_DIR

  # safety check
  if filename.find('.xsi', -4) <= 0:
    print "XSI not found"
    filename += '.xsi'


  export_dir = bsys.dirname(filename)
  if export_dir != EXPORT_DIR:
    EXPORT_DIR = export_dir   

  # open our output  
  FD = open(filename, 'w')

  # get the selected objects, otherwise get them all
  #OBJ = Blender.Object.GetSelected()
  #if not OBJ:
  
  OBJ = Blender.Object.Get()

  # we need some objects, if none specified stop
  if not OBJ:
    return

  # if any exist, grab the materials
  MAT = Blender.Material.Get()

  # output the header data
  do_header()

  # output the materials used by the selected objects.
  do_materiallibrary()

  # we punch out the models, that is, the meshes themselves
  do_models()


  # finally close our file
  FD.close()



# ---------------------------------------------------------------------------
# Lets trigger it off now
# Blender.Window.FileSelector(export_xsi, 'Export SoftImage XSI')

fname = bsys.makename(ext=".xsi")
if EXPORT_DIR <> '':
  fname = bsys.join(EXPORT_DIR, bsys.basename(fname))

Blender.Window.FileSelector(export_xsi, "Export SoftImage XSI", fname)
