#!BPY

""" Registration info for Blender menus:
Name: 'AC3D (.ac)...'
Blender: 233
Group: 'Export'
Submenu: 'All meshes...' all
Submenu: 'Only selected...' sel
Submenu: 'Configure +' config
Tip: 'Export to AC3D (.ac) format.'
"""

__author__ = "Willian P. Germano"
__url__ = ("blender", "elysiun", "AC3D's homepage, http://www.ac3d.org",
	"PLib 3d gaming lib, http://plib.sf.net")
__version__ = "2.34 09/20/04"

__bpydoc__ = """\
This script exports Blender meshes to AC3D's .ac file format.

AC3D is a simple commercial 3d modeller also built with OpenGL.
The .ac file format is an easy to parse text format well supported,
for example, by the PLib 3d gaming library (AC3D v3.x).

Supported:<br>
    UV-textured meshes with hierarchy (grouping) information.

Missing:<br>
    Support for AC3D 4's crease tag (simple, will be added as option for
the next version of this exporter).

Known issues:<br>
    Models textured with more than one image do not work -- for the
moment you can separate them in Blender such that each mesh only has one
image assigned (also see notes below);<br>
    The exporter is slow for large meshes -- faster code was written for the
TuxKart (http://tuxkart.sf.net, wiki at http://netpanzer.berlios.de/tuxkart)
game exporter and will be integrated on a future version of this exporter.

Notes:<br>
    There is a version of this script, by Ingo Ruhnke, that accepts meshes with
more than one texture image assigned, check TuxKart's wiki.
"""

# $Id: ac3d_export.py,v 1.9 2004/11/09 14:07:25 ianwill Exp $
#
# --------------------------------------------------------------------------
# AC3DExport version 2.34
# Program versions: Blender 2.34 and AC3Db files (means version 0xb)
# new: minor cosmetic tweaks, exporter itself didn't change
# --------------------------------------------------------------------------
# ***** BEGIN GPL LICENSE BLOCK *****
#
# Copyright (C) 2004: Willian P. Germano, wgermano _at_ ig.com.br
#
# 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

ARG = __script__['arg'] # user selected argument

HELPME = 0 # help window

SKIP_DATA = 1
MIRCOL_AS_AMB = 0
MIRCOL_AS_EMIS = 0
ADD_DEFAULT_MAT = 1

# Looking for a saved key in Blender.Registry dict:
rd = Blender.Registry.GetKey('AC3DExport')
if rd:
  SKIP_DATA = rd['SKIP_DATA']
  MIRCOL_AS_AMB = rd['MIRCOL_AS_AMB']
  MIRCOL_AS_EMIS = rd['MIRCOL_AS_EMIS']
  ADD_DEFAULT_MAT = rd['ADD_DEFAULT_MAT']

def update_RegistryInfo():
  d = {}
  d['SKIP_DATA'] = SKIP_DATA
  d['MIRCOL_AS_AMB'] = MIRCOL_AS_AMB
  d['MIRCOL_AS_EMIS'] = MIRCOL_AS_EMIS
  d['ADD_DEFAULT_MAT'] = ADD_DEFAULT_MAT
  Blender.Registry.SetKey('AC3DExport', d)

# The default material to be used when necessary (see ADD_DEFAULT_MAT)
DEFAULT_MAT = \
'MATERIAL "DefaultWhite" rgb 1 1 1  amb 1 1 1  emis 0 0 0  spec 0.5 0.5 0.5 shi 64  trans 0'

# This transformation aligns Blender and AC3D coordinate systems:
acmatrix = [[1,0,0,0],[0,0,-1,0],[0,1,0,0],[0,0,0,1]]

def Round(f):
    r = round(f,6) # precision set to 10e-06
    if r == int(r):
        return str(int(r))
    else:
        return str(r)
    
def transform_verts(verts, m):
    r = []
    for v in verts:
        t = [0,0,0]
        t[0] = m[0][0]*v[0] + m[1][0]*v[1] + m[2][0]*v[2] + m[3][0]
        t[1] = m[0][1]*v[0] + m[1][1]*v[1] + m[2][1]*v[2] + m[3][1]
        t[2] = m[0][2]*v[0] + m[1][2]*v[1] + m[2][2]*v[2] + m[3][2]
        r.append(t)
    return r

def matrix_mul(m, n = acmatrix):
    indices = [0,1,2,3]
    t = [[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]]
    for i in indices:
        for j in indices:
            for k in indices:
                t[i][j] += m[i][k]*n[k][j]
    return t

# ---

class AC3DExport:

    def __init__(self, scene, filename):

        global ARG, SKIP_DATA, ADD_DEFAULT_MAT, DEFAULT_MAT

        print 'Trying AC3DExport...'

        header = 'AC3Db'
        self.buf = ''
        self.mbuf = ''
        world_kids = 0
        self.mlist = []
        kids_dict = {}
        objlist = []
        bl_objlist2 = []
 
        if ARG == 'all': bl_objlist = scene.getChildren()
        elif ARG == 'sel': bl_objlist = Blender.Object.GetSelected()

        for obj in bl_objlist:
            if obj.getType() != 'Mesh' and obj.getType() != 'Empty':
                continue
            else: kids_dict[obj.name] = 0
            if obj.getParent() == None:
                objlist.append(obj.name)
            else:
                bl_objlist2.append(obj)

        bl_objlist = bl_objlist2[:]
        world_kids = len(objlist)

        while bl_objlist2:
            for obj in bl_objlist:
                obj2 = obj
                dad = obj.getParent()
                kids_dict[dad.name] += 1
                while dad.name not in objlist:
                    obj2 = dad
                    dad = dad.getParent()
                    kids_dict[dad.name] += 1
                objlist.insert(objlist.index(dad.name)+1, obj2.name)
                bl_objlist2.remove(obj2)

        for object in objlist:
            obj = Blender.Object.Get(object)
            self.obj = obj

            if obj.getType() == 'Empty':
                self.OBJECT("group")
                self.name(obj.name)
                #self.rot(obj.rot)
                #self.loc(obj.loc)
            else:
                mesh = self.mesh = obj.getData()
                self.MATERIAL(mesh.materials)
                self.OBJECT("poly")
                self.name(obj.name)
                if not SKIP_DATA: self.data(mesh.name)
                self.texture(mesh.faces)
                self.numvert(mesh.verts, obj.getMatrix())
                self.numsurf(mesh.faces, mesh.hasFaceUV())

            self.kids(kids_dict[object])

        if not self.mbuf or ADD_DEFAULT_MAT:
            self.mbuf = DEFAULT_MAT + '\n' + self.mbuf
            print "\nNo materials: a default (white) has been assigned.\n"
        self.mbuf = self.mbuf + "%s\n%s %s\n" \
                    % ('OBJECT world', 'kids', world_kids)
        buf = "%s\n%s%s" % (header, self.mbuf, self.buf)

        if filename.find('.ac', -3) <= 0: filename += '.ac'

        try:
            file = open(filename, 'w')
        except IOError, (errno, strerror):
            errmsg = "IOError #%s" % errno
            errmsg = errmsg + "%t|" + strerror
            Blender.Draw.PupMenu(errmsg)
            return None
        file.write(buf)
        file.close()

        print "Done. Saved to %s\n" % filename

    def MATERIAL(self, mat):
        if mat == [None]:
            print "Notice -- object %s has no material linked to it:" % self.name
            print "\tThe first entry in the .ac file will be used."
            return
        mbuf = ''
        mlist = self.mlist
        for m in xrange(len(mat)):
            name = mat[m].name
            try:
                mlist.index(name)
            except ValueError:
                mlist.append(name)
                M = Blender.Material.Get(name)
                material = 'MATERIAL "%s"' % name
                mirCol = "%s %s %s" % (Round(M.mirCol[0]),
                                       Round(M.mirCol[1]), Round(M.mirCol[2]))
                rgb = "rgb %s %s %s" % (Round(M.R), Round(M.G), Round(M.B))
                amb = "amb %s %s %s" % (Round(M.amb), Round(M.amb), Round(M.amb))
                if MIRCOL_AS_AMB:
                    amb = "amb %s" % mirCol 
                emis = "emis 0 0 0"
                if MIRCOL_AS_EMIS:
                    emis = "emis %s" % mirCol
                spec = "spec %s %s %s" % (Round(M.specCol[0]),
                                          Round(M.specCol[1]), Round(M.specCol[2]))
                shi = "shi 72"
                trans = "trans %s" % (Round(1 - M.alpha))
                mbuf = mbuf + "%s %s %s %s %s %s %s\n" \
                       % (material, rgb, amb, emis, spec, shi, trans)
        self.mlist = mlist
        self.mbuf = self.mbuf + mbuf

    def OBJECT(self, type):
        self.buf = self.buf + "OBJECT %s\n" % type

    def name(self, name):
        self.buf = self.buf + 'name "%s"\n' % name

    def data(self, name):
        self.buf = self.buf + 'data %s\n%s\n' % (len(name), name)

    def texture(self, faces):
        tex = []
        for f in faces:
            if f.image and f.image.name not in tex:
                tex.append(f.image.name)
        if tex:
            if len(tex) > 1:
                print "\nAC3Db format supports only one texture per object."
                print "Object %s -- using only the first one: %s\n" % (self.obj.name, tex[0])
            image = Blender.Image.Get(tex[0])
            buf = 'texture "%s"\n' % image.filename
            xrep = image.xrep
            yrep = image.yrep
            buf += 'texrep %s %s\n' % (xrep, yrep)
            self.buf = self.buf + buf

    def rot(self, matrix):
        rot = ''
        not_I = 0
        for i in [0, 1, 2]:
            r = map(Round, matrix[i])
            not_I += (r[0] != '0.0')+(r[1] != '0.0')+(r[2] != '0.0')
            not_I -= (r[i] == '1.0')
            for j in [0, 1, 2]:
                rot = "%s %s" % (rot, r[j])
        if not_I:
            rot = rot.strip()
            buf = 'rot %s\n' % rot
            self.buf = self.buf + buf
        
    def loc(self, loc):
        loc = map(Round, loc)
        if loc[0] or loc[1] or loc[2]:
            buf = 'loc %s %s %s\n' % (loc[0], loc[1], loc[2])
            self.buf = self.buf + buf

    def numvert(self, verts, matrix):
        buf = "numvert %s\n" % len(verts)
        m = matrix_mul(matrix)
        verts = transform_verts(verts, m)
        for v in verts:
            v = map(Round, v)
            buf = buf + "%s %s %s\n" % (v[0], v[1], v[2])
        self.buf = self.buf + buf

    def numsurf(self, faces, hasFaceUV):

        global ADD_DEFAULT_MAT
        
        buf = "numsurf %s\n" % len(faces)
        
        mlist = self.mlist
        indexerror = 0
        omlist = {}
        objmats = self.mesh.materials
        for i in range(len(objmats)):
            objmats[i] = objmats[i].name
        for f in faces:
            m_idx = f.materialIndex
            try:
                m_idx = mlist.index(objmats[m_idx])
            except IndexError:
                if not indexerror:
                    print "\nNotice: object " + self.obj.name + \
                          " has at least one material *index* assigned"
                    print "\tbut not defined (not linked to an existing material)."
                    print "\tThis can cause some of its faces to be exported with a wrong color."
                    print "\tYou can fix the problem in the Blender Edit Buttons Window (F9).\n"
                    indexerror = 1
                m_idx = 0
            refs = len(f)
            flaglow = (refs == 2) << 1
            two_side = f.mode & Blender.NMesh.FaceModes['TWOSIDE']
            two_side = (two_side > 0) << 1
            flaghigh = f.smooth | two_side
            buf = buf + "SURF 0x%d%d\n" % (flaghigh, flaglow)
            if ADD_DEFAULT_MAT and objmats: m_idx += 1
            buf = buf + "mat %s\n" % m_idx
            buf = buf + "refs %s\n" % refs
            u, v, vi = 0, 0, 0
            for vert in f.v:
                vindex = self.mesh.verts.index(vert)
                if hasFaceUV:
                    u = f.uv[vi][0]
                    v = f.uv[vi][1]
                    vi += 1
                buf = buf + "%s %s %s\n" % (vindex, u, v)
        self.buf = self.buf + buf

    def kids(self, kids = 0):
        self.buf = self.buf + "kids %s\n" % kids

# End of Class AC3DExport

from Blender import Draw, BGL

def gui():
  global SKIP_DATA, MIRCOL_AS_AMB, MIRCOL_AS_EMIS, ADD_DEFAULT_MAT, HELPME
  global HELPME

  if HELPME:
    BGL.glClearColor(0.6,0.6,0.9,1)
    BGL.glClear(BGL.GL_COLOR_BUFFER_BIT)
    BGL.glColor3f(1,1,1)
    BGL.glRasterPos2i(18, 150)
    Draw.Text("AC3D is a simple, affordable commercial 3d modeller that can "
      "be found at www.ac3d.org .")
    BGL.glRasterPos2i(18, 130)
    Draw.Text("It uses a nice text file format (extension .ac) which supports "
      "uv-textured meshes")
    BGL.glRasterPos2i(18, 110)
    Draw.Text("with parenting (grouping) information.")
    BGL.glRasterPos2i(18, 90)
    Draw.Text("Notes: AC3D has a 'data' token that assigns a string to each "
      "mesh, useful for games,")
    BGL.glRasterPos2i(55, 70)
    Draw.Text("for example. You can use Blender's mesh 'ME:' field for that.")
    BGL.glRasterPos2i(55, 50)
    Draw.Text("The .ac format is well supported by the PLib 3d gaming library.")
    Draw.Button("Ok", 21, 285, 10, 45, 20,
      "Click to return to previous screen.")
  else:
    BGL.glClearColor(0,0,1,1)
    BGL.glClear(BGL.GL_COLOR_BUFFER_BIT)
    BGL.glColor3f(1,1,1)
    BGL.glRasterPos2i(20, 150)
    Draw.Text("AC3D Exporter")
    Draw.Toggle("Default mat", 1, 15, 100, 90, 20, ADD_DEFAULT_MAT,
      "Objects without materials assigned get a default (white) one"
      " automatically.")
    Draw.Toggle("Skip data", 2, 15, 80, 90, 20, SKIP_DATA,
      "Don't export mesh names as 'data' info.")
    Draw.Toggle("Mir2Amb", 3, 15, 50, 90, 20, MIRCOL_AS_AMB,
      "Get AC3D's ambient RGB color for each object from its mirror color "
      "in Blender.")
    Draw.Toggle("Mir2Emis", 4, 15, 30, 90, 20, MIRCOL_AS_EMIS,
      "Get AC3D's emissive RGB color for each object from its mirror color "
      "in Blender.")
    Draw.Button("Export All...", 10, 140, 80, 110, 30,
      "Export all meshes to an AC3D file.")
    Draw.Button("Export Selected...", 11, 140, 40, 110, 30,
      "Export selected meshes to an AC3D file.")
    Draw.Button("HELP", 20, 285, 80, 100, 40, "Click for additional info.")
    Draw.Button("EXIT", 22, 285, 30, 100, 40, "Click to leave.")

def event(evt, val):
  global HELPME

  if not val: return

  if HELPME:
    if evt == Draw.ESCKEY:
      HELPME = 0
      Draw.Register(gui, event, b_event)
      return
    else: return

  if evt == Draw.ESCKEY:
    update_RegistryInfo()
    Draw.Exit()
    return
  else: return

  Draw.Register(gui, event, b_event)

def b_event(evt):
  global ARG, SKIP_DATA, MIRCOL_AS_AMB, MIRCOL_AS_EMIS, ADD_DEFAULT_MAT
  global HELPME

  if evt == 1:
    ADD_DEFAULT_MAT = 1 - ADD_DEFAULT_MAT
    Draw.Redraw(1)
  elif evt == 2:
    SKIP_DATA = 1 - SKIP_DATA
    Draw.Redraw(1)
  elif evt == 3:
    MIRCOL_AS_AMB = 1 - MIRCOL_AS_AMB
    Draw.Redraw(1)
  elif evt == 4:
    MIRCOL_AS_EMIS = 1 - MIRCOL_AS_EMIS
    Draw.Redraw(1)
  elif evt == 10:
    ARG = 'all'
    fname = Blender.sys.makename(ext=".ac")
    Blender.Window.FileSelector(fs_callback, "Export AC3D", fname)
  elif evt == 11:
    ARG = 'sel'
    fname = Blender.sys.makename(ext=".ac")
    Blender.Window.FileSelector(fs_callback, "Export AC3D", fname)
  elif evt == 20:
    HELPME = 1 - HELPME
    Draw.Redraw(1)
  elif evt == 21: # leave Help screen
    HELPME = 0
    Draw.Register(gui, event, b_event)
  elif evt == 22:
    update_RegistryInfo()
    Draw.Exit()
  else:
    Draw.Register(gui, event, b_event)

def fs_callback(filename):
  scene = Blender.Scene.GetCurrent()
  test = AC3DExport(scene, filename)

if __script__['arg'] == 'config':
  Draw.Register(gui, event, b_event)
else:
  fname = Blender.sys.makename(ext=".ac")
  Blender.Window.FileSelector(fs_callback, "Export AC3D", fname)
