BPython:
authorWillian Padovani Germano <wpgermano@gmail.com>
Tue, 27 Jan 2004 03:34:16 +0000 (03:34 +0000)
committerWillian Padovani Germano <wpgermano@gmail.com>
Tue, 27 Jan 2004 03:34:16 +0000 (03:34 +0000)
- as proposed by Ton, default dir for menu enabled scripts is:
userhome/.blender/scripts if available or (using bprogname -- argv[0]),
blenderInstallationDir/.blender/scripts/ otherwise.
- moved a piece of code from BPY_interface.c to BPY_menus.c to get rid
of a linkage warning reported by J. Walton
-- added the first scripts to release/scripts:

We need time to get more scripts there, but the situation should improve
consistently from now on.  Adding three export scripts: cal3d, directX, ac3d.
And one import: ac3d.

release/bpydata/readme.txt [new file with mode: 0644]
release/scripts/DirectXExporter.py [new file with mode: 0644]
release/scripts/ac3d_export.py [new file with mode: 0644]
release/scripts/ac3d_import.py [new file with mode: 0644]
release/scripts/blender2cal3d.py [new file with mode: 0644]
source/blender/python/BPY_interface.c
source/blender/python/BPY_menus.c
source/blender/python/BPY_menus.h

diff --git a/release/bpydata/readme.txt b/release/bpydata/readme.txt
new file mode 100644 (file)
index 0000000..d518634
--- /dev/null
@@ -0,0 +1,2 @@
+This directory will be the default place for scripts to put their data,
+like internal files needed by the script and its saved configuration.
diff --git a/release/scripts/DirectXExporter.py b/release/scripts/DirectXExporter.py
new file mode 100644 (file)
index 0000000..6e5436d
--- /dev/null
@@ -0,0 +1,434 @@
+#!BPY
+
+""" Registration info for Blender menus:
+Name: 'DirectX'
+Blender: 232
+Group: 'Export'
+Submenu: 'Only mesh data...' mesh
+Submenu: 'Animation(not armature yet)...' anim
+Tip: 'Export to DirectX text file format format.'
+"""
+# DirectX.py version 1.0
+# Copyright (C) 2003  Arben OMARI -- aromari@tin.it 
+#
+# 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.
+
+# This script export meshes created with Blender in DirectX file format
+# it exports meshes,materials,normals,texturecoords and and animations
+
+# Grab the latest version here :www.omariben.too.it
+
+import Blender
+from Blender import Types, Object, NMesh, Material
+#import string
+from math import *
+
+
+
+       
+#***********************************************
+#***********************************************
+#                EXPORTER
+#***********************************************
+#***********************************************
+
+class xExport:
+       def __init__(self, filename):
+               self.file = open(filename, "w")
+
+       #***********************************************
+       #  Export animations
+       #***********************************************
+       def exportAnim(self):
+               tex = []
+               print "exporting ..."
+               self.writeHeader()
+               for name in Object.Get():
+                       obj = name.getData()
+                       if type(obj) == Types.NMeshType :
+                               self.writeMaterials(name,tex)   
+                               self.writeFrames(name, obj)     
+                               self.writeMeshcoord(name, obj )
+                               self.writeMeshMaterialList(name, obj, tex)
+                               self.writeMeshNormals(name, obj)
+                               self.writeMeshTextureCoords(name, obj)
+                               self.file.write("}\n")
+                               self.file.write("}\n")
+                               self.writeAnimation(name, obj)
+                       
+               self.writeEnd()
+
+       #***********************************************
+       #  Export geometry
+       #***********************************************
+       def exportTex(self):
+               tex = []
+               print "exporting ..."
+               self.writeHeader()
+               for name in Object.Get():
+                       obj = name.getData()
+                       if type(obj) == Types.NMeshType :
+                               self.writeMaterials(name,tex)           
+                               self.writeMeshcoord(name, obj )
+                               self.writeMeshMaterialList(name, obj, tex)
+                               self.writeMeshNormals(name, obj)
+                               self.writeMeshTextureCoords(name, obj)
+                               self.file.write("}\n")
+
+               self.writeEnd()
+    
+       #***********************************************
+       #HEADER
+       #***********************************************  
+       def writeHeader(self):
+               self.file.write("xof 0302txt 0064\n")
+               self.file.write("\n")
+               self.file.write("Header{\n")
+               self.file.write("1;0;1;\n")
+               self.file.write("}\n")
+
+       #***********************************************
+       #CLOSE FILE
+       #***********************************************
+       def writeEnd(self):
+               self.file.close()
+               print "... finished"
+       #***********************************************
+       #EXPORT MATERIALS
+       #***********************************************
+       def writeMaterials(self,name,tex):
+               for mat in Material.Get():
+                       self.file.write("Material")
+                       self.file.write(" %s "% (mat.name))
+                       self.file.write("{\n")
+                       self.file.write("%s; %s; %s;" % (mat.R, mat.G, mat.B))
+                       self.file.write("%s;;\n" % (mat.alpha))
+                       self.file.write("%s;\n" % (mat.spec))
+                       self.file.write("%s; %s; %s;;\n" % (mat.specR, mat.specG, mat.specB))
+                       self.file.write("0.0; 0.0; 0.0;;\n")
+                       self.file.write("TextureFilename {\n")
+                       self.file.write('none ;')
+                       self.file.write("}\n")
+                       self.file.write("}\n") 
+               self.writeTextures(name, tex)
+               
+
+       #***********************************************
+       #EXPORT TEXTURES
+       #***********************************************
+       def writeTextures(self,name, tex):
+               mesh = name.data
+               for face in mesh.faces:
+                       if face.image and face.image.name not in tex:
+                               tex.append(face.image.name)
+                               self.file.write("Material Mat")
+                               self.file.write("%s "% (len(tex)))
+                               self.file.write("{\n")
+                               self.file.write("1.0; 1.0; 1.0; 1.0;;\n")
+                               self.file.write("1.0;\n")
+                               self.file.write("1.0; 1.0; 1.0;;\n")
+                               self.file.write("0.0; 0.0; 0.0;;\n")
+                               self.file.write("TextureFilename {\n")
+                               self.file.write('"%s" ;'% (face.image.name))
+                               self.file.write("}\n")
+                               self.file.write("}\n") 
+               
+
+       #***********************************************
+       #EXPORT MESH DATA
+       #***********************************************
+       def writeMeshcoord(self, name, obj ):
+               
+               self.file.write("Mesh Mesh_%s {\n" % (name.name))    
+               numfaces=len(obj.faces)
+               #POSITION
+               loc = name.getMatrix()
+               x = loc[3][0]
+               y = loc[3][1]
+               z = loc[3][2]
+               #VERTICES NUMBER
+               mesh = name.data
+               numvert = 0
+               for face in mesh.faces:
+                       numvert = numvert + len(face.v)
+               self.file.write("%s;\n" % (numvert))
+               #VERTICES COORDINATES
+               counter = 0
+               for face in mesh.faces:
+                       counter += 1
+                       if counter == numfaces:
+                               if len(face.v) == 4:
+                                       self.file.write("%s; %s; %s;,\n" % ((face.v[0].co[0] + x), face.v[0].co[1] + y, face.v[0].co[2] + z))
+                                       self.file.write("%s; %s; %s;,\n" % ((face.v[1].co[0] + x), face.v[1].co[1] + y, face.v[1].co[2] + z))           
+                                       self.file.write("%s; %s; %s;,\n" % ((face.v[2].co[0] + x), face.v[2].co[1] + y, face.v[2].co[2] + z))
+                                       self.file.write("%s; %s; %s;;\n" % ((face.v[3].co[0] + x), face.v[3].co[1] + y, face.v[3].co[2] + z))
+                               elif len(face.v) == 3 :
+                                       self.file.write("%s; %s; %s;,\n" % ((face.v[0].co[0] + x), face.v[0].co[1] + y, face.v[0].co[2] + z))
+                                       self.file.write("%s; %s; %s;,\n" % ((face.v[1].co[0] + x), face.v[1].co[1] + y, face.v[1].co[2] + z))           
+                                       self.file.write("%s; %s; %s;;\n" % ((face.v[2].co[0] + x), face.v[2].co[1] + y, face.v[2].co[2] + z))
+                                       
+                       else :
+                               if len(face.v) == 4:
+                                       self.file.write("%s; %s; %s;,\n" % ((face.v[0].co[0] + x), face.v[0].co[1] + y, face.v[0].co[2] + z))
+                                       self.file.write("%s; %s; %s;,\n" % ((face.v[1].co[0] + x), face.v[1].co[1] + y, face.v[1].co[2] + z))           
+                                       self.file.write("%s; %s; %s;,\n" % ((face.v[2].co[0] + x), face.v[2].co[1] + y, face.v[2].co[2] + z))
+                                       self.file.write("%s; %s; %s;,\n" % ((face.v[3].co[0] + x), face.v[3].co[1] + y, face.v[3].co[2] + z))
+                               elif len(face.v) == 3:
+                                       self.file.write("%s; %s; %s;,\n" % ((face.v[0].co[0] + x), face.v[0].co[1] + y, face.v[0].co[2] + z))
+                                       self.file.write("%s; %s; %s;,\n" % ((face.v[1].co[0] + x), face.v[1].co[1] + y, face.v[1].co[2] + z))           
+                                       self.file.write("%s; %s; %s;,\n" % ((face.v[2].co[0] + x), face.v[2].co[1] + y, face.v[2].co[2] + z))
+                                       
+
+
+               #FACES NUMBER 
+               
+               self.file.write("%s;\n" % (numfaces))  
+               #FACES INDEX
+               numface=len(obj.faces)
+               coun,counter = 0, 0
+               for face in mesh.faces :
+                       coun += 1
+                       if coun == numface:
+                               if len(face.v) == 3:
+                                       self.file.write("3; %s; %s; %s;;\n" % (counter, counter + 1, counter + 2))
+                                       counter += 3
+                               else :
+                                       self.file.write("4; %s; %s; %s; %s;;\n" % (counter, counter + 1, counter + 2, counter + 3))
+                                       counter += 4
+                       else:
+                               
+                               if len(face.v) == 3:
+                                       self.file.write("3; %s; %s; %s;,\n" % (counter, counter + 1, counter + 2))
+                                       counter += 3
+                               else :
+                                       self.file.write("4; %s; %s; %s; %s;,\n" % (counter, counter + 1, counter + 2, counter + 3))
+                                       counter += 4
+               
+
+               
+               
+               
+               
+       #***********************************************
+       #MESH MATERIAL LIST
+       #***********************************************
+       def writeMeshMaterialList(self, name, obj, tex):
+               self.file.write("//LET'S BEGIN WITH OPTIONAL DATA\n")
+               self.file.write(" MeshMaterialList {\n")
+               #HOW MANY MATERIALS ARE USED
+               count = 0
+               for mat in Material.Get():
+                       count+=1
+               self.file.write("%s;\n" % (len(tex) + count))
+               #HOW MANY FACES IT HAS
+               numfaces=len(obj.faces)
+               self.file.write("%s;\n" % (numfaces))
+               ##MATERIALS INDEX FOR EVERY FACE
+               counter = 0
+               for face in obj.faces :
+                       counter += 1
+                       mater = face.materialIndex
+                       if counter == numfaces:
+                               if face.image and face.image.name in tex :
+                                       self.file.write("%s;;\n" % (tex.index(face.image.name) + count))
+                               else :
+                                       self.file.write("%s;;\n" % (mater))
+                       else :
+                               if face.image and face.image.name in tex :
+                                       self.file.write("%s,\n" % (tex.index(face.image.name) + count))
+                               else :
+                                       self.file.write("%s,\n" % (mater))
+                       
+               ##MATERIAL NAME
+               for mat in Material.Get():
+                       self.file.write("{%s}\n"% (mat.name))
+               
+               for mat in tex:
+                       self.file.write("{Mat")
+                       self.file.write("%s}\n"% (tex.index(mat) + 1))
+               self.file.write("}\n")
+       #***********************************************
+       #MESH NORMALS
+       #***********************************************
+       def writeMeshNormals(self,name,obj):
+               self.file.write(" MeshNormals {\n")
+               #VERTICES NUMBER
+               numvert=len(obj.verts)
+               self.file.write("%s;\n" % (numvert))
+               #VERTICES NORMAL
+               counter = 0
+               for vert in obj.verts:
+                       counter += 1  
+                       if counter == numvert:
+                               self.file.write("%s; %s; %s;;\n" % (vert.no[0], vert.no[1], vert.no[2]))
+                       else :
+                               self.file.write("%s; %s; %s;,\n" % (vert.no[0], vert.no[1], vert.no[2]))
+               #FACES NUMBER 
+               numfaces=len(obj.faces)
+               self.file.write("%s;\n" % (numfaces))  
+               #FACES INDEX
+               counter = 0
+               for face in obj.faces :
+                       counter += 1
+                       if counter == numfaces:
+                               if len(face.v) == 3:
+                                       self.file.write("3; %s; %s; %s;;\n" % (face[0].index, face[1].index, face[2].index))
+                               elif len(face.v) == 4:
+                                       self.file.write("4; %s; %s; %s; %s;;\n" % (face[0].index, face[1].index, face[2].index, face[3].index))
+                       else:
+                               if len(face.v) == 3:
+                                       self.file.write("3; %s; %s; %s;,\n" % (face[0].index, face[1].index, face[2].index))
+                               elif len(face.v) == 4 :
+                                       self.file.write("4; %s; %s; %s; %s;,\n" % (face[0].index, face[1].index, face[2].index, face[3].index))
+               self.file.write("}\n")
+       #***********************************************
+       #MESH TEXTURE COORDS
+       #***********************************************
+       def writeMeshTextureCoords(self, name, obj):
+                       if obj.hasFaceUV():
+                               self.file.write("MeshTextureCoords {\n")
+                               #VERTICES NUMBER
+                               mesh = name.data
+                               numvert = 0
+                               for face in mesh.faces:
+                                       numvert = numvert + len(face.v)
+                               self.file.write("%s;\n" % (numvert))
+                               #UV COORDS
+                               counter = -1
+                               for face in mesh.faces:
+                                       counter += 1
+                                       if len(face.v) == 4:
+                                               self.file.write("%s;%s;,\n" % (mesh.faces[counter].uv[0][0], -mesh.faces[counter].uv[0][1]))
+                                               self.file.write("%s;%s;,\n" % (mesh.faces[counter].uv[1][0], -mesh.faces[counter].uv[1][1]))
+                                               self.file.write("%s;%s;,\n" % (mesh.faces[counter].uv[2][0], -mesh.faces[counter].uv[2][1]))
+                                               self.file.write("%s;%s;,\n" % (mesh.faces[counter].uv[3][0], -mesh.faces[counter].uv[3][1]))
+                                       elif len(face.v) == 3:
+                                               self.file.write("%s;%s;,\n" % (mesh.faces[counter].uv[0][0], -mesh.faces[counter].uv[0][1]))
+                                               self.file.write("%s;%s;,\n" % (mesh.faces[counter].uv[1][0], -mesh.faces[counter].uv[1][1]))
+                                               self.file.write("%s;%s;,\n" % (mesh.faces[counter].uv[2][0], -mesh.faces[counter].uv[2][1]))
+
+                               self.file.write("}\n")
+
+       #***********************************************
+       #FRAMES
+       #***********************************************
+       def writeFrames(self, name, obj):
+               matx = name.getMatrix()
+               self.file.write("Frame Fr_")  
+               self.file.write("%s {\n" % (obj.name))
+               self.file.write(" FrameTransformMatrix {\n")
+               self.file.write(" %s,%s,%s,%s,\n" %
+                                                       (round(matx[0][0],6),round(matx[0][1],6),round(matx[0][2],6),round(matx[0][3],6)))
+               self.file.write(" %s,%s,%s,%s,\n" %
+                                                       (round(matx[1][0],6),round(matx[1][1],6),round(matx[1][2],6),round(matx[1][3],6)))
+               self.file.write(" %s,%s,%s,%s,\n" %
+                                                       (round(matx[2][0],6),round(matx[2][1],6),round(matx[2][2],6),round(matx[2][3],6)))
+               self.file.write(" %s,%s,%s,%s;;\n" %
+                                                       (round(matx[3][0],6),round(matx[3][1],6),round(matx[3][2],6),round(matx[3][3],6)))
+               self.file.write(" }\n")
+       #***********************************************
+       #WRITE ANIMATION KEYS
+       #***********************************************
+       def writeAnimation(self, name, obj):
+               startFr = Blender.Get('staframe')
+               endFr = Blender.Get('endframe')
+               self.file.write("AnimationSet animset_")
+               self.file.write("%s {\n" % (obj.name))
+               self.file.write(" Animation anim_")
+               self.file.write("%s { \n" % (obj.name))
+               self.file.write("  {Fr_")
+               self.file.write("%s }\n" % (obj.name))
+               self.file.write("   AnimationKey { \n")
+               self.file.write("   0;\n")
+               self.file.write("   %s; \n" % (endFr))
+               for fr in range(startFr,endFr + 1) :
+                       self.file.write("   %s; " % (fr))
+                       self.file.write("4; ")
+                       Blender.Set('curframe',fr)
+                       rot = name.rot
+                       rot_x = rot[0]
+                       rot_y = rot[1]
+                       rot_z = rot[2]
+                       quat = self.euler2quat(rot_x,rot_y,rot_z)
+                       self.file.write("%s, %s, %s,%s;;" % 
+                                                       (quat[0],quat[1],quat[2],quat[3]))
+                       if fr == endFr:
+                               self.file.write(";\n")
+                       else:
+                               self.file.write(",\n")
+               self.file.write("   }\n")
+               self.file.write("   AnimationKey { \n")
+               self.file.write("   2;\n")
+               self.file.write("   %s; \n" % (endFr))
+               for fr in range(startFr,endFr + 1) :
+                       self.file.write("   %s; " % (fr))
+                       self.file.write("3; ")
+                       Blender.Set('curframe',fr)
+                       loc = name.loc
+                       self.file.write("%s, %s, %s;;" %
+                                                       (loc[0],loc[1],loc[2]))
+                       if fr == endFr:
+                               self.file.write(";\n")
+                       else:
+                               self.file.write(",\n")
+               self.file.write("   }\n")
+               self.file.write("   AnimationKey { \n")
+               self.file.write("   1;\n")
+               self.file.write("   %s; \n" % (endFr))
+               for fr in range(startFr,endFr + 1) :
+                       self.file.write("   %s; " % (fr))
+                       self.file.write("3; ")
+                       Blender.Set('curframe',fr)
+                       size = name.size
+                       self.file.write("%s, %s, %s;;" %
+                                                       (size[0],size[1],size[2]))
+                       if fr == endFr:
+                               self.file.write(";\n")
+                       else:
+                               self.file.write(",\n")
+               self.file.write("   }\n")
+               self.file.write("  }\n")
+               self.file.write(" }\n")
+
+       def euler2quat(self,rot_x,rot_y,rot_z):
+               c_x = cos(rot_x / 2)
+               c_y = cos(rot_y / 2)
+               c_z = cos(rot_z / 2)
+
+               s_x = sin(rot_x / 2)
+               s_y = sin(rot_y / 2)
+               s_z = sin(rot_z / 2)
+
+               cy_cz = c_y * c_z
+               sy_sz = s_y * s_z
+               quat_w = c_x * cy_cz - s_x * sy_sz
+               quat_x = s_x * cy_cz + c_x * sy_sz
+               quat_y = c_x * s_y * c_z - s_x * c_y * s_z
+               quat_z = c_x * c_y * s_z + s_x * s_y * c_z
+
+               return(quat_w,quat_x,quat_y,quat_z)
+
+#***********************************************
+# MAIN
+#***********************************************
+
+       
+def my_callback(filename):
+       if filename.find('.x', -2) <= 0: filename += '.x' # add '.x' if the user didn't
+       xexport = xExport(filename)
+       arg = __script__['arg']
+       if arg == 'anim':
+               xexport.exportAnim()
+       else:
+               xexport.exportTex()
+
+Blender.Window.FileSelector(my_callback, "Export DirectX")
diff --git a/release/scripts/ac3d_export.py b/release/scripts/ac3d_export.py
new file mode 100644 (file)
index 0000000..6a46476
--- /dev/null
@@ -0,0 +1,425 @@
+#!BPY
+
+""" Registration info for Blender menus:
+Name: 'AC3D'
+Blender: 232
+Group: 'Export'
+Submenu: 'All meshes...' all
+Submenu: 'Only selected...' sel
+Submenu: 'Configure +' config
+Tip: 'Export to AC3D (.ac) format.'
+"""
+
+# --------------------------------------------------------------------------
+# AC3DExport version 2.32-1 Jan 21, 2004
+# Program versions: Blender 2.32+ and AC3Db files (means version 0xb)
+# --------------------------------------------------------------------------
+# ***** BEGIN GPL LICENSE BLOCK *****
+#
+# Copyright (C) 2004: Willian P. Germano, wgermano@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 right above)
+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
+
+# ---
+
+errmsg = ''
+
+class AC3DExport:
+
+    def __init__(self, scene, filename):
+
+        global ARG, SKIP_DATA, ADD_DEFAULT_MAT, DEFAULT_MAT, errmsg
+
+        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: %s" % (errno, strerror)
+            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(20, 270)
+    Draw.Text("AC3D Exporter")
+    BGL.glRasterPos2i(30, 250)
+    Draw.Text("AC3D is a simple, affordable commercial 3d modeller that can be found at www.ac3d.org .")
+    BGL.glRasterPos2i(30, 230)
+    Draw.Text("It uses a nice text file format (extension .ac) which supports uv-textured meshes")
+    BGL.glRasterPos2i(30, 210)
+    Draw.Text("with parenting (grouping) information.")
+    BGL.glRasterPos2i(30, 190)
+    Draw.Text("Notes: AC3D has a 'data' token that assigns a string to each mesh, useful for games,")
+    BGL.glRasterPos2i(67, 170)
+    Draw.Text("for example. You can use Blender's mesh datablock name for that.")
+    BGL.glRasterPos2i(67, 150)
+    Draw.Text("The .ac format is well supported by the PLib 3d gaming library. You can use this")
+    BGL.glRasterPos2i(67, 130)
+    Draw.Text("exporter to have your Blender models in games and other apps written with PLib.")
+    Draw.Button("Ok", 21, 285, 80, 100, 40, "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'
+    Blender.Window.FileSelector(fs_callback, "AC3D Export")
+  elif evt == 11:
+    ARG = 'sel'
+    Blender.Window.FileSelector(fs_callback, "AC3D Export")
+  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:
+  Blender.Window.FileSelector(fs_callback, "AC3D Export")
diff --git a/release/scripts/ac3d_import.py b/release/scripts/ac3d_import.py
new file mode 100644 (file)
index 0000000..3e3d08a
--- /dev/null
@@ -0,0 +1,421 @@
+#!BPY
+
+""" Registration info for Blender menus:
+Name: 'AC3D...'
+Blender: 232
+Group: 'Import'
+Tip: 'Import an AC3D (.ac) file.'
+"""
+
+# --------------------------------------------------------------------------
+# AC3DImport version 2.32-1 Jan 21, 2004
+# Program versions: Blender 2.32+ and AC3Db files (means version 0xb)
+# --------------------------------------------------------------------------
+# ***** BEGIN GPL LICENSE BLOCK *****
+#
+# Copyright (C) 2004: Willian P. Germano, wgermano@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 *****
+# --------------------------------------------------------------------------
+
+# Note:
+# Blender doesn't handle n-gons (polygons with more than 4 vertices):
+#   The script triangulates them, but concave polygons come out wrong and need
+#   fixing. Avoiding or triangulating concave n-gons in AC3D is a simple way to
+#   avoid problems.
+
+# Default folder for AC3D models, change to your liking or leave as "":
+BASEDIR = ""
+
+# Set 'GROUP' to 1 to make Blender group imported objects using Empties,
+# to reproduce the object hierarchy in the .ac file
+GROUP = 0
+
+import Blender
+
+if BASEDIR:
+    BASEDIR = BASEDIR.replace('\\','/')
+    if BASEDIR[-1] != '/': BASEDIR += '/'
+
+errmsg = ""
+
+class Obj:
+    
+    def __init__(self, type):
+        self.type = type
+        self.dad = None
+        self.name = ''
+        self.data = ''
+        self.tex = ''
+        self.texrep = [1,1]
+        self.texoff = None
+        self.loc = [0, 0, 0]
+        self.rot = []
+        self.vlist = []
+        self.flist = []
+        self.matlist = []
+        self.kids = 0
+
+class AC3DImport:
+
+    def __init__(self, filename):
+
+        global errmsg
+
+        print "Trying to import AC3D model(s) from %s ..." % filename
+
+        self.i = 0
+        errmsg = ''
+        self.importdir = Blender.sys.dirname(filename)
+        try:
+            file = open(filename, 'r')
+        except IOError, (errno, strerror):
+            errmsg = "IOError #%s: %s" % (errno, strerror)
+            print errmsg
+            return None
+        header = file.read(5)
+        header, version = header[:4], header[-1]
+        if header != 'AC3D':
+            file.close()
+            errmsg = 'Invalid file -- AC3D header not found.'
+            print errmsg
+            return None
+        elif version != 'b':
+            print 'AC3D file version 0x%s.' % version
+            print 'This importer is for version 0xb, so it may fail.' 
+
+        self.token = {'OBJECT':     self.parse_obj,
+                      'numvert':    self.parse_vert,
+                      'numsurf':    self.parse_surf,
+                      'name':       self.parse_name,
+                      'data':       self.parse_data,
+                      'kids':       self.parse_kids,
+                      'loc':        self.parse_loc,
+                      'rot':        self.parse_rot,
+                      'MATERIAL':   self.parse_mat,
+                      'texture':    self.parse_tex,
+                      'texrep':     self.parse_texrep,
+                      'texoff':     self.parse_texoff}
+
+        self.objlist = []
+        self.mlist = []
+        self.dads = []
+        self.kids = []
+        self.dad = None
+
+        self.lines = file.readlines()
+        self.lines.append('')
+        self.parse_file()
+        file.close()
+        
+        self.testAC3DImport()
+                
+    def parse_obj(self, value):
+        if self.kids:
+            while not self.kids[-1]:
+                self.kids.pop()
+                self.dad = self.dad.dad
+            self.kids[-1] -= 1
+        new = Obj(value)
+        new.dad = self.dad
+        new.name = value
+        self.objlist.append(new)
+
+    def parse_kids(self, value):
+        kids = int(value)
+        if kids:
+            self.kids.append(kids)
+            self.dad = self.objlist[-1]
+        self.objlist[-1].kids = kids
+
+    def parse_name(self, value):
+        name = value.split('"')[1]
+        self.objlist[-1].name = name
+
+    def parse_data(self, value):
+        data = self.lines[self.i].strip()
+        self.objlist[-1].data = data
+
+    def parse_tex(self, value):
+        texture = value.split('"')[1]
+        self.objlist[-1].tex = texture
+
+    def parse_texrep(self, trash):
+        trep = self.lines[self.i - 1]
+        trep = trep.split()
+        trep = [float(trep[1]), float(trep[2])]
+        self.objlist[-1].texrep = trep
+        self.objlist[-1].texoff = [0, 0]
+
+    def parse_texoff(self, trash):
+        toff = self.lines[self.i - 1]
+        toff = toff.split()
+        toff = [float(toff[1]), float(toff[2])]
+        self.objlist[-1].texoff = toff
+        
+    def parse_mat(self, value):
+        i = self.i - 1
+        lines = self.lines
+        line = lines[i].split()
+        mat_name = ''
+        mat_col = mat_spec_col = [0,0,0]
+        mat_alpha = 1
+
+        while line[0] == 'MATERIAL':
+            mat_name = line[1].split('"')[1]
+            mat_col = map(float,[line[3],line[4],line[5]])
+            mat_spec_col = map(float,[line[15],line[16],line[17]])
+            mat_alpha = float(line[-1])
+            mat_alpha = 1 - mat_alpha
+            self.mlist.append([mat_name, mat_col, mat_spec_col, mat_alpha])
+            i += 1
+            line = lines[i].split()
+
+        self.i = i
+
+    def parse_rot(self, trash):
+        i = self.i - 1
+        rot = self.lines[i].split(' ', 1)[1]
+        rot = map(float, rot.split())
+        self.objlist[-1].rot = rot
+
+    def parse_loc(self, trash):
+        i = self.i - 1
+        loc = self.lines[i].split(' ', 1)[1]
+        loc = map(float, loc.split())
+        self.objlist[-1].loc = loc
+
+    def parse_vert(self, value):
+        i = self.i
+        lines = self.lines
+        obj = self.objlist[-1]
+        vlist = obj.vlist
+        n = int(value)
+
+        while n:
+            line = lines[i].split()
+            line = map(float, line)
+            vlist.append(line)
+            n -= 1
+            i += 1
+
+        self.i = i
+
+        rot = obj.rot
+        if rot:
+            nv = len(vlist)
+            for j in range(nv):
+                v = vlist[j]
+                t = [0,0,0]
+                t[0] = rot[0]*v[0] + rot[3]*v[1] + rot[6]*v[2]
+                t[1] = rot[1]*v[0] + rot[4]*v[1] + rot[7]*v[2]
+                t[2] = rot[2]*v[0] + rot[5]*v[1] + rot[8]*v[2]
+                vlist[j] = t
+
+        loc = obj.loc
+        dad = obj.dad
+        while dad:
+            for j in [0, 1, 2]:
+                loc[j] += dad.loc[j]
+            dad = dad.dad
+
+        for v in vlist:
+            for j in [0, 1, 2]:
+                v[j] += loc[j]
+
+    def parse_surf(self, value):
+        i = self.i
+        is_smooth = 0
+        double_sided = 0
+        lines = self.lines
+        obj = self.objlist[-1]
+        matlist = obj.matlist
+        numsurf = int(value)
+
+        while numsurf:
+            flags = lines[i].split()
+            flaglow = 0
+            if len(flags[1]) > 3: flaglow = int(flags[1][3])
+            flaghigh = int(flags[1][2])
+            is_smooth = flaghigh & 1
+            twoside = flaghigh & 2
+            mat = lines[i+1].split()
+            mat = int(mat[1])
+            if not mat in matlist: matlist.append(mat)
+            refs = lines[i+2].split()
+            refs = int(refs[1])
+            i += 3
+            face = []
+            faces = []
+            fuv = []
+            rfs = refs
+
+            while rfs:
+                line = lines[i].split()
+                v = int(line[0])
+                uv = [float(line[1]), float(line[2])]
+                face.append([v, uv])
+                rfs -= 1
+                i += 1
+                
+            if flaglow:
+                while len(face) >= 2:
+                    cut = face[:2]
+                    faces.append(cut)
+                    face = face[1:]
+
+                if flaglow == 1:
+                    face = [faces[-1][-1], faces[0][0]]
+                    faces.append(face)
+
+            else:
+                while len(face) > 4:
+                    cut = face[:4]
+                    face = face[3:]
+                    face.insert(0, cut[0])
+                    faces.append(cut)        
+
+                faces.append(face)
+
+            for f in faces:
+                f.append(mat)
+                f.append(is_smooth)
+                f.append(twoside)
+                self.objlist[-1].flist.append(f)
+
+            numsurf -= 1      
+
+                            
+        self.i = i
+
+    def parse_file(self):
+        i = 1
+        lines = self.lines
+        line = lines[i].split()
+
+        while line:
+            kw = ''
+            for k in self.token.keys():
+                if line[0] == k:
+                    kw = k
+                    break
+            i += 1
+            if kw:
+                self.i = i
+                self.token[kw](line[1])
+                i = self.i
+            line = lines[i].split()
+
+    def testAC3DImport(self):
+        global GROUP
+        scene = Blender.Scene.GetCurrent()
+
+        bmat = []
+        for mat in self.mlist:
+            name = mat[0]
+            m = Blender.Material.New(name)
+            m.rgbCol = (mat[1][0], mat[1][1], mat[1][2])
+            m.specCol = (mat[2][0], mat[2][1], mat[2][2])
+            m.alpha = mat[3]
+            bmat.append(m)
+
+        for obj in self.objlist:
+            if obj.type == 'world':
+                continue
+            elif obj.type == 'group':
+                if not GROUP: continue
+                empty = Blender.Object.New('Empty')
+                empty.name = obj.name
+                scene.link(empty)
+                if self.dads:
+                    dadobj = Blender.Object.get(self.dads.pop())
+                    dadobj.makeParent([empty])
+                while obj.kids:
+                    self.dads.append(empty.name)
+                    obj.kids -= 1
+                continue
+            mesh = Blender.NMesh.New()
+            if (obj.data): mesh.name = obj.data
+            mesh.hasFaceUV(1)
+
+            tex = None
+            if obj.tex != '':
+                try:
+                    tex = Blender.Image.Load(obj.tex)
+                    # Commented because it's unnecessary:
+                    #tex.xrep = int(obj.texrep[0])
+                    #tex.yrep = int(obj.texrep[1])
+                except:
+                    try:
+                        obj.tex = self.importdir + '/' + obj.tex
+                        tex = Blender.Image.Load(obj.tex)
+                    except:
+                        print "Couldn't load texture: %s" % obj.tex
+
+            for v in obj.vlist:
+                bvert = Blender.NMesh.Vert(v[0],v[1],v[2])
+                mesh.verts.append(bvert)
+
+            objmat_indices = []
+            for mat in bmat:
+                if bmat.index(mat) in obj.matlist:
+                    objmat_indices.append(bmat.index(mat))
+                    mesh.materials.append(mat)
+            for f in obj.flist:
+                twoside = f[-1]
+                is_smooth = f[-2]
+                fmat = f[-3]
+                f=f[:-3]
+                bface = Blender.NMesh.Face()
+                bface.smooth = is_smooth
+                if twoside: bface.mode |= Blender.NMesh.FaceModes['TWOSIDE']
+                if tex:
+                    bface.mode |= Blender.NMesh.FaceModes['TEX']
+                    bface.image = tex
+                bface.materialIndex = objmat_indices.index(fmat)
+                if obj.texoff:
+                    uoff = obj.texoff[0]
+                    voff = obj.texoff[1]
+                    urep = obj.texrep[0]
+                    vrep = obj.texrep[1]
+                    for vi in range(len(f)):
+                        f[vi][1][0] *= urep
+                        f[vi][1][1] *= vrep
+                        f[vi][1][0] += uoff
+                        f[vi][1][1] += voff
+
+                for vi in range(len(f)):
+                    bface.v.append(mesh.verts[f[vi][0]])
+                    bface.uv.append((f[vi][1][0], f[vi][1][1]))
+                mesh.faces.append(bface)
+
+            mesh.mode = 0
+            object = Blender.NMesh.PutRaw(mesh)
+            object.setName(obj.name)
+            object.setEuler([1.5707963,0,0]) # align ac3d w/ Blender
+            if self.dads:
+                dadobj = Blender.Object.get(self.dads.pop())
+                dadobj.makeParent([object])
+
+        print '...done!'
+
+# End of class AC3DImport
+
+def filesel_callback(filename):
+  test = AC3DImport(filename)
+
+Blender.Window.FileSelector(filesel_callback, "Import AC3D")
diff --git a/release/scripts/blender2cal3d.py b/release/scripts/blender2cal3d.py
new file mode 100644 (file)
index 0000000..9d0d729
--- /dev/null
@@ -0,0 +1,983 @@
+#!BPY
+
+"""
+Name: 'Cal3D v0.5'
+Blender: 232
+Group: 'Export'
+Tip: 'Export armature/bone data to the Cal3D library.'
+"""
+
+# blender2cal3D.py version 0.5
+# Copyright (C) 2003 Jean-Baptiste LAMY -- jiba@tuxfamily.org
+#
+# 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
+
+
+# This script is a Blender 2.28 => Cal3D 0.7/0.8 converter.
+# (See http://blender.org and http://cal3d.sourceforge.net)
+#
+# Grab the latest version here :
+# http://oomadness.tuxfamily.org/en/blender2cal3d
+
+# HOW TO USE :
+# 1 - load the script in Blender's text editor
+# 2 - modify the parameters below (e.g. the file name)
+# 3 - type M-P (meta/alt + P) and wait until script execution is finished
+
+# ADVICES :
+# - Use only locrot keys in Blender's action
+# - Do not put "." in action or bone names, and do not start these names by a figure
+# - Objects whose names start by "_" are not exported (hidden object)
+# - All your armature's bones must be connected to another bone (except for the root
+#   bone). Contrary to Blender, Cal3D doesn't support "floating" bones.
+# - Only Linux has been tested
+
+# BUGS / TODO :
+# - Animation names ARE LOST when exporting (this is due to Blender Python API and cannot
+#   be fixed until the API change). See parameters for how to rename your animations
+# - Rotation, translation, or stretch (size changing) of Blender object is still quite
+#   bugged, so AVOID MOVING / ROTATING / RESIZE OBJECTS (either mesh or armature) !
+#   Instead, edit the object (with tab), select all points / bones (with "a"),
+#   and move / rotate / resize them.
+# - Material color is not supported yet
+# - Cal3D springs (for clothes and hair) are not supported yet
+# - Optimization tips : almost all the time is spent on scene.makeCurrent(), called for
+#   updating the IPO curve's values. Updating a single IPO and not the whole scene
+#   would speed up a lot.
+
+# Questions and comments are welcome at jiba@tuxfamily.org
+
+
+# Parameters :
+
+# The directory where the data are saved.
+# blender2cal3d.py will create all files in this directory,
+# including a .cfg file.
+# WARNING: As Cal3D stores model in directory and not in a single file,
+# you MUST avoid putting other file in this directory !
+# Please give an empty directory (or an unexistant one).
+# Files may be deleted from this directoty !
+SAVE_TO_DIR = "cal3d"
+
+# Use this dictionary to rename animations, as their name is lost at the exportation.
+RENAME_ANIMATIONS = {
+  # "OldName" : "NewName",
+  
+  }
+
+# True (=1) to export for the Soya 3D engine (http://oomadness.tuxfamily.org/en/soya).
+# (=> rotate meshes and skeletons so as X is right, Y is top and -Z is front)
+EXPORT_FOR_SOYA = 0
+
+# Enables LODs computation. LODs computation is quite slow, and the algo is surely
+# not optimal :-(
+LODS = 0
+
+# See also BASE_MATRIX below, if you want to rotate/scale/translate the model at
+# the exportation.
+
+
+#########################################################################################
+# Code starts here.
+# The script should be quite re-useable for writing another Blender animation exporter.
+# Most of the hell of it is to deal with Blender's head-tail-roll bone's definition.
+
+import sys, os, os.path, struct, math, string
+import Blender
+
+# HACK -- it seems that some Blender versions don't define sys.argv,
+# which may crash Python if a warning occurs.
+if not hasattr(sys, "argv"): sys.argv = ["???"]
+
+
+# Math stuff
+
+def quaternion2matrix(q):
+  xx = q[0] * q[0]
+  yy = q[1] * q[1]
+  zz = q[2] * q[2]
+  xy = q[0] * q[1]
+  xz = q[0] * q[2]
+  yz = q[1] * q[2]
+  wx = q[3] * q[0]
+  wy = q[3] * q[1]
+  wz = q[3] * q[2]
+  return [[1.0 - 2.0 * (yy + zz),       2.0 * (xy + wz),       2.0 * (xz - wy), 0.0],
+          [      2.0 * (xy - wz), 1.0 - 2.0 * (xx + zz),       2.0 * (yz + wx), 0.0],
+          [      2.0 * (xz + wy),       2.0 * (yz - wx), 1.0 - 2.0 * (xx + yy), 0.0],
+          [0.0                  , 0.0                  , 0.0                  , 1.0]]
+
+def matrix2quaternion(m):
+  s = math.sqrt(abs(m[0][0] + m[1][1] + m[2][2] + m[3][3]))
+  if s == 0.0:
+    x = abs(m[2][1] - m[1][2])
+    y = abs(m[0][2] - m[2][0])
+    z = abs(m[1][0] - m[0][1])
+    if   (x >= y) and (x >= z): return 1.0, 0.0, 0.0, 0.0
+    elif (y >= x) and (y >= z): return 0.0, 1.0, 0.0, 0.0
+    else:                       return 0.0, 0.0, 1.0, 0.0
+  return quaternion_normalize([
+    -(m[2][1] - m[1][2]) / (2.0 * s),
+    -(m[0][2] - m[2][0]) / (2.0 * s),
+    -(m[1][0] - m[0][1]) / (2.0 * s),
+    0.5 * s,
+    ])
+
+def quaternion_normalize(q):
+  l = math.sqrt(q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3])
+  return q[0] / l, q[1] / l, q[2] / l, q[3] / l
+
+def quaternion_multiply(q1, q2):
+  r = [
+    q2[3] * q1[0] + q2[0] * q1[3] + q2[1] * q1[2] - q2[2] * q1[1],
+    q2[3] * q1[1] + q2[1] * q1[3] + q2[2] * q1[0] - q2[0] * q1[2],
+    q2[3] * q1[2] + q2[2] * q1[3] + q2[0] * q1[1] - q2[1] * q1[0],
+    q2[3] * q1[3] - q2[0] * q1[0] - q2[1] * q1[1] - q2[2] * q1[2],
+    ]
+  d = math.sqrt(r[0] * r[0] + r[1] * r[1] + r[2] * r[2] + r[3] * r[3])
+  r[0] /= d
+  r[1] /= d
+  r[2] /= d
+  r[3] /= d
+  return r
+
+def matrix_translate(m, v):
+  m[3][0] += v[0]
+  m[3][1] += v[1]
+  m[3][2] += v[2]
+  return m
+
+def matrix_multiply(b, a):
+  return [ [
+    a[0][0] * b[0][0] + a[0][1] * b[1][0] + a[0][2] * b[2][0],
+    a[0][0] * b[0][1] + a[0][1] * b[1][1] + a[0][2] * b[2][1],
+    a[0][0] * b[0][2] + a[0][1] * b[1][2] + a[0][2] * b[2][2],
+    0.0,
+    ], [
+    a[1][0] * b[0][0] + a[1][1] * b[1][0] + a[1][2] * b[2][0],
+    a[1][0] * b[0][1] + a[1][1] * b[1][1] + a[1][2] * b[2][1],
+    a[1][0] * b[0][2] + a[1][1] * b[1][2] + a[1][2] * b[2][2],
+    0.0,
+    ], [
+    a[2][0] * b[0][0] + a[2][1] * b[1][0] + a[2][2] * b[2][0],
+    a[2][0] * b[0][1] + a[2][1] * b[1][1] + a[2][2] * b[2][1],
+    a[2][0] * b[0][2] + a[2][1] * b[1][2] + a[2][2] * b[2][2],
+     0.0,
+    ], [
+    a[3][0] * b[0][0] + a[3][1] * b[1][0] + a[3][2] * b[2][0] + b[3][0],
+    a[3][0] * b[0][1] + a[3][1] * b[1][1] + a[3][2] * b[2][1] + b[3][1],
+    a[3][0] * b[0][2] + a[3][1] * b[1][2] + a[3][2] * b[2][2] + b[3][2],
+    1.0,
+    ] ]
+
+def matrix_invert(m):
+  det = (m[0][0] * (m[1][1] * m[2][2] - m[2][1] * m[1][2])
+       - m[1][0] * (m[0][1] * m[2][2] - m[2][1] * m[0][2])
+       + m[2][0] * (m[0][1] * m[1][2] - m[1][1] * m[0][2]))
+  if det == 0.0: return None
+  det = 1.0 / det
+  r = [ [
+      det * (m[1][1] * m[2][2] - m[2][1] * m[1][2]),
+    - det * (m[0][1] * m[2][2] - m[2][1] * m[0][2]),
+      det * (m[0][1] * m[1][2] - m[1][1] * m[0][2]),
+      0.0,
+    ], [
+    - det * (m[1][0] * m[2][2] - m[2][0] * m[1][2]),
+      det * (m[0][0] * m[2][2] - m[2][0] * m[0][2]),
+    - det * (m[0][0] * m[1][2] - m[1][0] * m[0][2]),
+      0.0
+    ], [
+      det * (m[1][0] * m[2][1] - m[2][0] * m[1][1]),
+    - det * (m[0][0] * m[2][1] - m[2][0] * m[0][1]),
+      det * (m[0][0] * m[1][1] - m[1][0] * m[0][1]),
+      0.0,
+    ] ]
+  r.append([
+    -(m[3][0] * r[0][0] + m[3][1] * r[1][0] + m[3][2] * r[2][0]),
+    -(m[3][0] * r[0][1] + m[3][1] * r[1][1] + m[3][2] * r[2][1]),
+    -(m[3][0] * r[0][2] + m[3][1] * r[1][2] + m[3][2] * r[2][2]),
+    1.0,
+    ])
+  return r
+
+def matrix_rotate_x(angle):
+  cos = math.cos(angle)
+  sin = math.sin(angle)
+  return [
+    [1.0,  0.0, 0.0, 0.0],
+    [0.0,  cos, sin, 0.0],
+    [0.0, -sin, cos, 0.0],
+    [0.0,  0.0, 0.0, 1.0],
+    ]
+
+def matrix_rotate_y(angle):
+  cos = math.cos(angle)
+  sin = math.sin(angle)
+  return [
+    [cos, 0.0, -sin, 0.0],
+    [0.0, 1.0,  0.0, 0.0],
+    [sin, 0.0,  cos, 0.0],
+    [0.0, 0.0,  0.0, 1.0],
+    ]
+
+def matrix_rotate_z(angle):
+  cos = math.cos(angle)
+  sin = math.sin(angle)
+  return [
+    [ cos, sin, 0.0, 0.0],
+    [-sin, cos, 0.0, 0.0],
+    [ 0.0, 0.0, 1.0, 0.0],
+    [ 0.0, 0.0, 0.0, 1.0],
+    ]
+
+def matrix_rotate(axis, angle):
+  vx  = axis[0]
+  vy  = axis[1]
+  vz  = axis[2]
+  vx2 = vx * vx
+  vy2 = vy * vy
+  vz2 = vz * vz
+  cos = math.cos(angle)
+  sin = math.sin(angle)
+  co1 = 1.0 - cos
+  return [
+    [vx2 * co1 + cos,          vx * vy * co1 + vz * sin, vz * vx * co1 - vy * sin, 0.0],
+    [vx * vy * co1 - vz * sin, vy2 * co1 + cos,          vy * vz * co1 + vx * sin, 0.0],
+    [vz * vx * co1 + vy * sin, vy * vz * co1 - vx * sin, vz2 * co1 + cos,          0.0],
+    [0.0, 0.0, 0.0, 1.0],
+    ]
+
+def matrix_scale(fx, fy, fz):
+  return [
+    [ fx, 0.0, 0.0, 0.0],
+    [0.0,  fy, 0.0, 0.0],
+    [0.0, 0.0,  fz, 0.0],
+    [0.0, 0.0, 0.0, 1.0],
+    ]
+  
+def point_by_matrix(p, m):
+  return [p[0] * m[0][0] + p[1] * m[1][0] + p[2] * m[2][0] + m[3][0],
+          p[0] * m[0][1] + p[1] * m[1][1] + p[2] * m[2][1] + m[3][1],
+          p[0] * m[0][2] + p[1] * m[1][2] + p[2] * m[2][2] + m[3][2]]
+
+def point_distance(p1, p2):
+  return math.sqrt((p2[0] - p1[0]) ** 2 + (p2[1] - p1[1]) ** 2 + (p2[2] - p1[2]) ** 2)
+
+def vector_by_matrix(p, m):
+  return [p[0] * m[0][0] + p[1] * m[1][0] + p[2] * m[2][0],
+          p[0] * m[0][1] + p[1] * m[1][1] + p[2] * m[2][1],
+          p[0] * m[0][2] + p[1] * m[1][2] + p[2] * m[2][2]]
+
+def vector_length(v):
+  return math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2])
+
+def vector_normalize(v):
+  l = math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2])
+  return v[0] / l, v[1] / l, v[2] / l
+
+def vector_dotproduct(v1, v2):
+  return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]
+
+def vector_crossproduct(v1, v2):
+  return [
+    v1[1] * v2[2] - v1[2] * v2[1],
+    v1[2] * v2[0] - v1[0] * v2[2],
+    v1[0] * v2[1] - v1[1] * v2[0],
+    ]
+
+def vector_angle(v1, v2):
+  s = vector_length(v1) * vector_length(v2)
+  f = vector_dotproduct(v1, v2) / s
+  if f >=  1.0: return 0.0
+  if f <= -1.0: return math.pi / 2.0
+  return math.atan(-f / math.sqrt(1.0 - f * f)) + math.pi / 2.0
+
+def blender_bone2matrix(head, tail, roll):
+  # Convert bone rest state (defined by bone.head, bone.tail and bone.roll)
+  # to a matrix (the more standard notation).
+  # Taken from blenkernel/intern/armature.c in Blender source.
+  # See also DNA_armature_types.h:47.
+  
+  target = [0.0, 1.0, 0.0]
+  delta  = [tail[0] - head[0], tail[1] - head[1], tail[2] - head[2]]
+  nor    = vector_normalize(delta)
+  axis   = vector_crossproduct(target, nor)
+  
+  if vector_dotproduct(axis, axis) > 0.0000000000001:
+    axis    = vector_normalize(axis)
+    theta   = math.acos(vector_dotproduct(target, nor))
+    bMatrix = matrix_rotate(axis, theta)
+    
+  else:
+    if vector_crossproduct(target, nor) > 0.0: updown =  1.0
+    else:                                      updown = -1.0
+    
+    # Quoted from Blender source : "I think this should work ..."
+    bMatrix = [
+      [updown, 0.0, 0.0, 0.0],
+      [0.0, updown, 0.0, 0.0],
+      [0.0, 0.0, 1.0, 0.0],
+      [0.0, 0.0, 0.0, 1.0],
+      ]
+  
+  rMatrix = matrix_rotate(nor, roll)
+  return matrix_multiply(rMatrix, bMatrix)
+
+
+# Hack for having the model rotated right.
+# Put in BASE_MATRIX your own rotation if you need some.
+
+if EXPORT_FOR_SOYA:
+  BASE_MATRIX = matrix_rotate_x(-math.pi / 2.0)
+  
+else:
+  BASE_MATRIX = None
+
+
+# Cal3D data structures
+
+CAL3D_VERSION = 700
+
+NEXT_MATERIAL_ID = 0
+class Material:
+  def __init__(self, map_filename = None):
+    self.ambient_r  = 255
+    self.ambient_g  = 255
+    self.ambient_b  = 255
+    self.ambient_a  = 255
+    self.diffuse_r  = 255
+    self.diffuse_g  = 255
+    self.diffuse_b  = 255
+    self.diffuse_a  = 255
+    self.specular_r = 255
+    self.specular_g = 255
+    self.specular_b = 255
+    self.specular_a = 255
+    self.shininess = 1.0
+    if map_filename: self.maps_filenames = [map_filename]
+    else:            self.maps_filenames = []
+    
+    MATERIALS[map_filename] = self
+    
+    global NEXT_MATERIAL_ID
+    self.id = NEXT_MATERIAL_ID
+    NEXT_MATERIAL_ID += 1
+    
+  def to_cal3d(self):
+    s = "CRF\0" + struct.pack("iBBBBBBBBBBBBfi", CAL3D_VERSION, self.ambient_r, self.ambient_g, self.ambient_b, self.ambient_a, self.diffuse_r, self.diffuse_g, self.diffuse_b, self.diffuse_a, self.specular_r, self.specular_g, self.specular_b, self.specular_a, self.shininess, len(self.maps_filenames))
+    for map_filename in self.maps_filenames:
+      s += struct.pack("i", len(map_filename) + 1)
+      s += map_filename + "\0"
+    return s
+  
+MATERIALS = {}
+
+class Mesh:
+  def __init__(self, name):
+    self.name      = name
+    self.submeshes = []
+    
+    self.next_submesh_id = 0
+    
+  def to_cal3d(self):
+    s = "CMF\0" + struct.pack("ii", CAL3D_VERSION, len(self.submeshes))
+    s += "".join(map(SubMesh.to_cal3d, self.submeshes))
+    return s
+  
+class SubMesh:
+  def __init__(self, mesh, material):
+    self.material   = material
+    self.vertices   = []
+    self.faces      = []
+    self.nb_lodsteps = 0
+    self.springs    = []
+    
+    self.next_vertex_id = 0
+    
+    self.mesh = mesh
+    self.id = mesh.next_submesh_id
+    mesh.next_submesh_id += 1
+    mesh.submeshes.append(self)
+    
+  def compute_lods(self):
+    """Computes LODs info for Cal3D (there's no Blender related stuff here)."""
+    
+    print "Start LODs computation..."
+    vertex2faces = {}
+    for face in self.faces:
+      for vertex in (face.vertex1, face.vertex2, face.vertex3):
+        l = vertex2faces.get(vertex)
+        if not l: vertex2faces[vertex] = [face]
+        else: l.append(face)
+        
+    couple_treated         = {}
+    couple_collapse_factor = []
+    for face in self.faces:
+      for a, b in ((face.vertex1, face.vertex2), (face.vertex1, face.vertex3), (face.vertex2, face.vertex3)):
+        a = a.cloned_from or a
+        b = b.cloned_from or b
+        if a.id > b.id: a, b = b, a
+        if not couple_treated.has_key((a, b)):
+          # The collapse factor is simply the distance between the 2 points :-(
+          # This should be improved !!
+          if vector_dotproduct(a.normal, b.normal) < 0.9: continue
+          couple_collapse_factor.append((point_distance(a.loc, b.loc), a, b))
+          couple_treated[a, b] = 1
+      
+    couple_collapse_factor.sort()
+    
+    collapsed    = {}
+    new_vertices = []
+    new_faces    = []
+    for factor, v1, v2 in couple_collapse_factor:
+      # Determines if v1 collapses to v2 or v2 to v1.
+      # We choose to keep the vertex which is on the smaller number of faces, since
+      # this one has more chance of being in an extrimity of the body.
+      # Though heuristic, this rule yields very good results in practice.
+      if   len(vertex2faces[v1]) <  len(vertex2faces[v2]): v2, v1 = v1, v2
+      elif len(vertex2faces[v1]) == len(vertex2faces[v2]):
+        if collapsed.get(v1, 0): v2, v1 = v1, v2 # v1 already collapsed, try v2
+        
+      if (not collapsed.get(v1, 0)) and (not collapsed.get(v2, 0)):
+        collapsed[v1] = 1
+        collapsed[v2] = 1
+        
+        # Check if v2 is already colapsed
+        while v2.collapse_to: v2 = v2.collapse_to
+        
+        common_faces = filter(vertex2faces[v1].__contains__, vertex2faces[v2])
+        
+        v1.collapse_to         = v2
+        v1.face_collapse_count = len(common_faces)
+        
+        for clone in v1.clones:
+          # Find the clone of v2 that correspond to this clone of v1
+          possibles = []
+          for face in vertex2faces[clone]:
+            possibles.append(face.vertex1)
+            possibles.append(face.vertex2)
+            possibles.append(face.vertex3)
+          clone.collapse_to = v2
+          for vertex in v2.clones:
+            if vertex in possibles:
+              clone.collapse_to = vertex
+              break
+            
+          clone.face_collapse_count = 0
+          new_vertices.append(clone)
+
+        # HACK -- all faces get collapsed with v1 (and no faces are collapsed with v1's
+        # clones). This is why we add v1 in new_vertices after v1's clones.
+        # This hack has no other incidence that consuming a little few memory for the
+        # extra faces if some v1's clone are collapsed but v1 is not.
+        new_vertices.append(v1)
+        
+        self.nb_lodsteps += 1 + len(v1.clones)
+        
+        new_faces.extend(common_faces)
+        for face in common_faces:
+          face.can_collapse = 1
+          
+          # Updates vertex2faces
+          vertex2faces[face.vertex1].remove(face)
+          vertex2faces[face.vertex2].remove(face)
+          vertex2faces[face.vertex3].remove(face)
+        vertex2faces[v2].extend(vertex2faces[v1])
+        
+    new_vertices.extend(filter(lambda vertex: not vertex.collapse_to, self.vertices))
+    new_vertices.reverse() # Cal3D want LODed vertices at the end
+    for i in range(len(new_vertices)): new_vertices[i].id = i
+    self.vertices = new_vertices
+    
+    new_faces.extend(filter(lambda face: not face.can_collapse, self.faces))
+    new_faces.reverse() # Cal3D want LODed faces at the end
+    self.faces = new_faces
+    
+    print "LODs computed : %s vertices can be removed (from a total of %s)." % (self.nb_lodsteps, len(self.vertices))
+    
+  def rename_vertices(self, new_vertices):
+    """Rename (change ID) of all vertices, such as self.vertices == new_vertices."""
+    for i in range(len(new_vertices)): new_vertices[i].id = i
+    self.vertices = new_vertices
+    
+  def to_cal3d(self):
+    s =  struct.pack("iiiiii", self.material.id, len(self.vertices), len(self.faces), self.nb_lodsteps, len(self.springs), len(self.material.maps_filenames))
+    s += "".join(map(Vertex.to_cal3d, self.vertices))
+    s += "".join(map(Spring.to_cal3d, self.springs))
+    s += "".join(map(Face  .to_cal3d, self.faces))
+    return s
+
+class Vertex:
+  def __init__(self, submesh, loc, normal):
+    self.loc    = loc
+    self.normal = normal
+    self.collapse_to         = None
+    self.face_collapse_count = 0
+    self.maps       = []
+    self.influences = []
+    self.weight = None
+    
+    self.cloned_from = None
+    self.clones      = []
+    
+    self.submesh = submesh
+    self.id = submesh.next_vertex_id
+    submesh.next_vertex_id += 1
+    submesh.vertices.append(self)
+    
+  def to_cal3d(self):
+    if self.collapse_to: collapse_id = self.collapse_to.id
+    else:                collapse_id = -1
+    s =  struct.pack("ffffffii", self.loc[0], self.loc[1], self.loc[2], self.normal[0], self.normal[1], self.normal[2], collapse_id, self.face_collapse_count)
+    s += "".join(map(Map.to_cal3d, self.maps))
+    s += struct.pack("i", len(self.influences))
+    s += "".join(map(Influence.to_cal3d, self.influences))
+    if not self.weight is None: s += struct.pack("f", len(self.weight))
+    return s
+  
+class Map:
+  def __init__(self, u, v):
+    self.u = u
+    self.v = v
+    
+  def to_cal3d(self):
+    return struct.pack("ff", self.u, self.v)
+    
+class Influence:
+  def __init__(self, bone, weight):
+    self.bone   = bone
+    self.weight = weight
+    
+  def to_cal3d(self):
+    return struct.pack("if", self.bone.id, self.weight)
+    
+class Spring:
+  def __init__(self, vertex1, vertex2):
+    self.vertex1 = vertex1
+    self.vertex2 = vertex2
+    self.spring_coefficient = 0.0
+    self.idlelength = 0.0
+    
+  def to_cal3d(self):
+    return struct.pack("iiff", self.vertex1.id, self.vertex2.id, self.spring_coefficient, self.idlelength)
+
+class Face:
+  def __init__(self, submesh, vertex1, vertex2, vertex3):
+    self.vertex1 = vertex1
+    self.vertex2 = vertex2
+    self.vertex3 = vertex3
+    
+    self.can_collapse = 0
+    
+    self.submesh = submesh
+    submesh.faces.append(self)
+    
+  def to_cal3d(self):
+    return struct.pack("iii", self.vertex1.id, self.vertex2.id, self.vertex3.id)
+    
+class Skeleton:
+  def __init__(self):
+    self.bones = []
+    
+    self.next_bone_id = 0
+    
+  def to_cal3d(self):
+    s = "CSF\0" + struct.pack("ii", CAL3D_VERSION, len(self.bones))
+    s += "".join(map(Bone.to_cal3d, self.bones))
+    return s
+
+BONES = {}
+
+class Bone:
+  def __init__(self, skeleton, parent, name, loc, rot):
+    self.parent = parent
+    self.name   = name
+    self.loc = loc
+    self.rot = rot
+    self.children = []
+    
+    self.matrix = matrix_translate(quaternion2matrix(rot), loc)
+    if parent:
+      self.matrix = matrix_multiply(parent.matrix, self.matrix)
+      parent.children.append(self)
+    
+    # lloc and lrot are the bone => model space transformation (translation and rotation).
+    # They are probably specific to Cal3D.
+    m = matrix_invert(self.matrix)
+    self.lloc = m[3][0], m[3][1], m[3][2]
+    self.lrot = matrix2quaternion(m)
+    
+    self.skeleton = skeleton
+    self.id = skeleton.next_bone_id
+    skeleton.next_bone_id += 1
+    skeleton.bones.append(self)
+    
+    BONES[name] = self
+    
+  def to_cal3d(self):
+    s =  struct.pack("i", len(self.name) + 1) + self.name + "\0"
+    
+    # We need to negate quaternion W value, but why ?
+    s += struct.pack("ffffffffffffff", self.loc[0], self.loc[1], self.loc[2], self.rot[0], self.rot[1], self.rot[2], -self.rot[3], self.lloc[0], self.lloc[1], self.lloc[2], self.lrot[0], self.lrot[1], self.lrot[2], -self.lrot[3])
+    if self.parent: s += struct.pack("i", self.parent.id)
+    else:           s += struct.pack("i", -1)
+    s += struct.pack("i", len(self.children))
+    s += "".join(map(lambda bone: struct.pack("i", bone.id), self.children))
+    return s
+  
+class Animation:
+  def __init__(self, name, duration = 0.0):
+    self.name     = name
+    self.duration = duration
+    self.tracks   = {} # Map bone names to tracks
+    
+  def to_cal3d(self):
+    s = "CAF\0" + struct.pack("ifi", CAL3D_VERSION, self.duration, len(self.tracks))
+    s += "".join(map(Track.to_cal3d, self.tracks.values()))
+    return s
+    
+class Track:
+  def __init__(self, animation, bone):
+    self.bone      = bone
+    self.keyframes = []
+    
+    self.animation = animation
+    animation.tracks[bone.name] = self
+    
+  def to_cal3d(self):
+    s = struct.pack("ii", self.bone.id, len(self.keyframes))
+    s += "".join(map(KeyFrame.to_cal3d, self.keyframes))
+    return s
+    
+class KeyFrame:
+  def __init__(self, track, time, loc, rot):
+    self.time = time
+    self.loc  = loc
+    self.rot  = rot
+    
+    self.track = track
+    track.keyframes.append(self)
+    
+  def to_cal3d(self):
+    # We need to negate quaternion W value, but why ?
+    return struct.pack("ffffffff", self.time, self.loc[0], self.loc[1], self.loc[2], self.rot[0], self.rot[1], self.rot[2], -self.rot[3])
+
+
+def export():
+  # Get the scene
+  
+  scene = Blender.Scene.getCurrent()
+  
+  
+  # Export skeleton (=armature)
+  
+  skeleton = Skeleton()
+  
+  for obj in Blender.Object.Get():
+    data = obj.getData()
+    if type(data) is Blender.Types.ArmatureType:
+      matrix = obj.getMatrix()
+      if BASE_MATRIX: matrix = matrix_multiply(BASE_MATRIX, matrix)
+      
+      def treat_bone(b, parent = None):
+        head = b.getHead()
+        tail = b.getTail()
+        
+        # Turns the Blender's head-tail-roll notation into a quaternion
+        quat = matrix2quaternion(blender_bone2matrix(head, tail, b.getRoll()))
+        
+        if parent:
+          # Compute the translation from the parent bone's head to the child
+          # bone's head, in the parent bone coordinate system.
+          # The translation is parent_tail - parent_head + child_head,
+          # but parent_tail and parent_head must be converted from the parent's parent
+          # system coordinate into the parent system coordinate.
+          
+          parent_invert_transform = matrix_invert(quaternion2matrix(parent.rot))
+          parent_head = vector_by_matrix(parent.head, parent_invert_transform)
+          parent_tail = vector_by_matrix(parent.tail, parent_invert_transform)
+          
+          bone = Bone(skeleton, parent, b.getName(), [parent_tail[0] - parent_head[0] + head[0], parent_tail[1] - parent_head[1] + head[1], parent_tail[2] - parent_head[2] + head[2]], quat)
+        else:
+          # Apply the armature's matrix to the root bones
+          head = point_by_matrix(head, matrix)
+          tail = point_by_matrix(tail, matrix)
+          quat = matrix2quaternion(matrix_multiply(matrix, quaternion2matrix(quat))) # Probably not optimal
+          
+          # Here, the translation is simply the head vector
+          bone = Bone(skeleton, parent, b.getName(), head, quat)
+          
+        bone.head = head
+        bone.tail = tail
+        
+        for child in b.getChildren(): treat_bone(child, bone)
+        
+      for b in data.getBones(): treat_bone(b)
+      
+      # Only one armature / skeleton
+      break
+    
+    
+  # Export Mesh data
+  
+  meshes = []
+  
+  for obj in Blender.Object.Get():
+    data = obj.getData()
+    if (type(data) is Blender.Types.NMeshType) and data.faces:
+      mesh = Mesh(obj.name)
+      meshes.append(mesh)
+      
+      matrix = obj.getMatrix()
+      if BASE_MATRIX: matrix = matrix_multiply(BASE_MATRIX, matrix)
+      
+      faces = data.faces
+      while faces:
+        image          = faces[0].image
+        image_filename = image and image.filename
+        material       = MATERIALS.get(image_filename) or Material(image_filename)
+        
+        # TODO add material color support here
+        
+        submesh  = SubMesh(mesh, material)
+        vertices = {}
+        for face in faces[:]:
+          if (face.image and face.image.filename) == image_filename:
+            faces.remove(face)
+            
+            if not face.smooth:
+              p1 = face.v[0].co
+              p2 = face.v[1].co
+              p3 = face.v[2].co
+              normal = vector_normalize(vector_by_matrix(vector_crossproduct(
+                [p3[0] - p2[0], p3[1] - p2[1], p3[2] - p2[2]],
+                [p1[0] - p2[0], p1[1] - p2[1], p1[2] - p2[2]],
+                ), matrix))
+              
+            face_vertices = []
+            for i in range(len(face.v)):
+              vertex = vertices.get(face.v[i].index)
+              if not vertex:
+                coord  = point_by_matrix (face.v[i].co, matrix)
+                if face.smooth: normal = vector_normalize(vector_by_matrix(face.v[i].no, matrix))
+                vertex  = vertices[face.v[i].index] = Vertex(submesh, coord, normal)
+                
+                influences = data.getVertexInfluences(face.v[i].index)
+                if not influences: print "Warning !  A vertex has no influence !"
+                
+                # sum of influences is not always 1.0 in Blender ?!?!
+                sum = 0.0
+                for bone_name, weight in influences: sum += weight
+                
+                for bone_name, weight in influences:
+                  vertex.influences.append(Influence(BONES[bone_name], weight / sum))
+                  
+              elif not face.smooth:
+                # We cannot share vertex for non-smooth faces, since Cal3D does not
+                # support vertex sharing for 2 vertices with different normals.
+                # => we must clone the vertex.
+                
+                old_vertex = vertex
+                vertex = Vertex(submesh, vertex.loc, normal)
+                vertex.cloned_from = old_vertex
+                vertex.influences = old_vertex.influences
+                old_vertex.clones.append(vertex)
+                
+              if data.hasFaceUV():
+                uv = [face.uv[i][0], 1.0 - face.uv[i][1]]
+                if not vertex.maps: vertex.maps.append(Map(*uv))
+                elif (vertex.maps[0].u != uv[0]) or (vertex.maps[0].v != uv[1]):
+                  # This vertex can be shared for Blender, but not for Cal3D !!!
+                  # Cal3D does not support vertex sharing for 2 vertices with
+                  # different UV texture coodinates.
+                  # => we must clone the vertex.
+                  
+                  for clone in vertex.clones:
+                    if (clone.maps[0].u == uv[0]) and (clone.maps[0].v == uv[1]):
+                      vertex = clone
+                      break
+                  else: # Not yet cloned...
+                    old_vertex = vertex
+                    vertex = Vertex(submesh, vertex.loc, vertex.normal)
+                    vertex.cloned_from = old_vertex
+                    vertex.influences = old_vertex.influences
+                    vertex.maps.append(Map(*uv))
+                    old_vertex.clones.append(vertex)
+                    
+              face_vertices.append(vertex)
+              
+            # Split faces with more than 3 vertices
+            for i in range(1, len(face.v) - 1):
+              Face(submesh, face_vertices[0], face_vertices[i], face_vertices[i + 1])
+              
+        # Computes LODs info
+        if LODS: submesh.compute_lods()
+        
+  # Export animations
+              
+  ANIMATIONS = {}
+  
+  for ipo in Blender.Ipo.Get():
+    name = ipo.getName()
+    
+    # Try to extract the animation name and the bone name from the IPO name.
+    # THIS MAY NOT WORK !!!
+    # The animation name extracted here is usually NOT the name of the action in Blender
+    
+    splitted = name.split(".")
+    if len(splitted) == 2:
+      animation_name, bone_name = splitted
+      animation_name += ".000"
+    elif len(splitted) == 3:
+      animation_name, a, b = splitted
+      if   a[0] in string.digits:
+        animation_name += "." + a
+        bone_name = b
+      elif b[0] in string.digits:
+        animation_name += "." + b
+        bone_name = a
+      else:
+        print "Un-analysable IPO name :", name
+        continue
+    else:
+      print "Un-analysable IPO name :", name
+      continue
+    
+    animation = ANIMATIONS.get(animation_name)
+    if not animation:
+      animation = ANIMATIONS[animation_name] = Animation(animation_name)
+
+    bone  = BONES[bone_name]
+    track = animation.tracks.get(bone_name)
+    if not track:
+      track = animation.tracks[bone_name] = Track(animation, bone)
+      track.finished = 0
+
+    nb_curve = ipo.getNcurves()
+    has_loc = nb_curve in (3, 7)
+    has_rot = nb_curve in (4, 7)
+    
+    # TODO support size here
+    # Cal3D does not support it yet.
+    
+    try: nb_bez_pts = ipo.getNBezPoints(0)
+    except TypeError:
+      print "No key frame for animation %s, bone %s, skipping..." % (animation_name, bone_name)
+      nb_bez_pts = 0
+      
+    for bez in range(nb_bez_pts): # WARNING ! May not work if not loc !!!
+      time = ipo.getCurveBeztriple(0, bez)[3]
+      scene.currentFrame(int(time))
+
+      # Needed to update IPO's value, but probably not the best way for that...
+      scene.makeCurrent()
+
+      # Convert time units from Blender's frame (starting at 1) to second
+      # (using default FPS of 25)
+      time = (time - 1.0) / 25.0
+
+      if animation.duration < time: animation.duration = time
+      
+      loc = bone.loc
+      rot = bone.rot
+      
+      curves = ipo.getCurves()
+      print curves
+      curve_id = 0
+      while curve_id < len(curves):
+        curve_name = curves[curve_id].getName()
+        if curve_name == "LocX":
+          # Get the translation
+          # We need to blend the translation from the bone rest state (=bone.loc) with
+          # the translation due to IPO.
+          trans = vector_by_matrix((
+            ipo.getCurveCurval(curve_id),
+            ipo.getCurveCurval(curve_id + 1),
+            ipo.getCurveCurval(curve_id + 2),
+            ), bone.matrix)
+          loc = [
+            bone.loc[0] + trans[0],
+            bone.loc[1] + trans[1],
+            bone.loc[2] + trans[2],
+            ]
+          curve_id += 3
+          
+        elif curve_name == "RotX":
+          # Get the rotation of the IPO
+          ipo_rot = [
+            ipo.getCurveCurval(curve_id),
+            ipo.getCurveCurval(curve_id + 1),
+            ipo.getCurveCurval(curve_id + 2),
+            ipo.getCurveCurval(curve_id + 3),
+            ]
+          curve_id += 3 # XXX Strange !!!
+          # We need to blend the rotation from the bone rest state (=bone.rot) with
+          # ipo_rot.
+          rot = quaternion_multiply(ipo_rot, bone.rot)
+          
+        else:
+          print "Unknown IPO curve : %s" % curve_name
+          break #Unknown curves
+      
+      KeyFrame(track, time, loc, rot)
+        
+      
+  # Save all data
+  
+  if not os.path.exists(SAVE_TO_DIR): os.makedirs(SAVE_TO_DIR)
+  else:
+    for file in os.listdir(SAVE_TO_DIR):
+      if file.endswith(".cfg") or file.endswith(".caf") or file.endswith(".cmf") or file.endswith(".csf") or file.endswith(".crf"):
+        os.unlink(os.path.join(SAVE_TO_DIR, file))
+        
+  cfg = open(os.path.join(SAVE_TO_DIR, os.path.basename(SAVE_TO_DIR) + ".cfg"), "wb")
+  print >> cfg, "# Cal3D model exported from Blender with blender2cal3d.py"
+  print >> cfg
+  
+  open(os.path.join(SAVE_TO_DIR, os.path.basename(SAVE_TO_DIR) + ".csf"), "wb").write(skeleton.to_cal3d())
+  print >> cfg, "skeleton=%s.csf" % os.path.basename(SAVE_TO_DIR)
+  print >> cfg
+  
+  for animation in ANIMATIONS.values():
+    if animation.duration: # Cal3D does not support animation with only one state
+      animation.name = RENAME_ANIMATIONS.get(animation.name) or animation.name
+      open(os.path.join(SAVE_TO_DIR, animation.name + ".caf"), "wb").write(animation.to_cal3d())
+      print >> cfg, "animation=%s.caf" % animation.name
+      
+      # Prints animation names and durations, in order to help identifying animation
+      # (since their name are lost).
+      print animation.name, "duration", animation.duration * 25.0 + 1.0
+      
+  print >> cfg
+
+  for mesh in meshes:
+    if not mesh.name.startswith("_"):
+      open(os.path.join(SAVE_TO_DIR, mesh.name + ".cmf"), "wb").write(mesh.to_cal3d())
+      print >> cfg, "mesh=%s.cmf" % mesh.name
+  print >> cfg
+  
+  materials = MATERIALS.values()
+  materials.sort(lambda a, b: cmp(a.id, b.id))
+  for material in materials:
+    if material.maps_filenames: filename = os.path.splitext(os.path.basename(material.maps_filenames[0]))[0]
+    else:                       filename = "plain"
+    open(os.path.join(SAVE_TO_DIR, filename + ".crf"), "wb").write(material.to_cal3d())
+    print >> cfg, "material=%s.crf" % filename
+  print >> cfg
+  
+  print "Saved to", SAVE_TO_DIR
+  print "Done."
+
+export()
index 225821dd8c5d94a69a6c18311ab88623d63480e2..70805d71ece87737822825a6fec21db7b8fa9f14 100644 (file)
@@ -482,15 +482,7 @@ int BPY_menu_do_python(short menutype, int event)
        Script *script = G.main->script.first;
        int len;
 
-       if ((menutype < 0) || (menutype > PYMENU_TOTAL) || (event < 0))
-               return 0;
-
-       pym = BPyMenuTable[menutype];
-
-       while (event--) {
-               if (pym) pym = pym->next;
-               else break;
-       }
+       pym = BPyMenu_GetEntry(menutype, (short)event);
 
        if (!pym) return 0;
 
@@ -527,7 +519,7 @@ int BPY_menu_do_python(short menutype, int event)
        if (pym->dir) /* script is in U.pythondir */
                BLI_make_file_string("/", filestr, U.pythondir, pym->filename);
        else { /* script is in ~/.blender/scripts/ */
-               BLI_make_file_string("/", dirname, BLI_gethome(), ".blender/scripts");
+               BLI_make_file_string("/", dirname, bpymenu_gethome(), "scripts");
                BLI_make_file_string("/", filestr, dirname,     pym->filename);
        }
 
index a9210f73933b97d60a3f4bc7d63d429c0fc2a2d6..cfe10723329915ac97044a32afa53b21fdadf75c 100644 (file)
@@ -74,19 +74,35 @@ static int DEBUG;
 BPyMenu *BPyMenuTable[PYMENU_TOTAL];
 
 /* we can't be sure if BLI_gethome() returned a path
- * with '.blender' appended or not, so: */
-static char *bpymenu_gethome()
+ * with '.blender' appended or not.  Besides, this function now
+ * either returns userhome/.blender (if it exists) or
+ * blenderInstallDir/.blender/ otherwise */
+char *bpymenu_gethome()
 {
        static char homedir[FILE_MAXDIR];
+       char bprogdir[FILE_MAXDIR];
        char *s;
+       int i;
 
-       if (homedir[0] != '\0') return homedir;
+       if (homedir[0] != '\0') return homedir; /* no need to search twice */
 
        s = BLI_gethome();
 
        if (strstr(s, ".blender")) PyOS_snprintf(homedir, FILE_MAXDIR, s);
        else BLI_make_file_string ("/", homedir, s, ".blender/");
 
+       /* if userhome/.blender/ exists, return it */
+       if (BLI_exists(homedir)) return homedir;
+
+       /* otherwise, use argv[0] (bprogname) to get .blender/ in
+        * Blender's installation dir */
+       s = BLI_last_slash(bprogname);
+
+       i = s - bprogname + 1;
+
+       PyOS_snprintf(bprogdir, i, bprogname);
+       BLI_make_file_string ("/", homedir, bprogdir, ".blender/");
+
        return homedir;
 }
 
@@ -195,6 +211,26 @@ static BPyMenu *bpymenu_FindEntry (short group, char *name)
        return NULL;
 }
 
+/* BPyMenu_GetEntry:
+ * given a group and a position, return the entry in that position from
+ * that group.
+*/ 
+BPyMenu *BPyMenu_GetEntry (short group, short pos)
+{
+       BPyMenu *pym = NULL;
+
+       if ((group < 0) || (group >= PYMENU_TOTAL)) return NULL;
+
+       pym = BPyMenuTable[group];
+
+       while (pos--) {
+               if (pym) pym = pym->next;
+               else break;
+       }
+
+       return pym; /* found entry or NULL */
+}
+       
 static void bpymenu_set_tooltip (BPyMenu *pymenu, char *tip)
 {
        if (!pymenu) return;
index 682aa929f862c90a040c2c480d2aa0c5d9f55a06..10e32f66b90b2f741dc829fe6243c06e2358eb46 100644 (file)
@@ -96,5 +96,7 @@ void BPyMenu_RemoveAllEntries(void);
 void BPyMenu_PrintAllEntries(void);
 char *BPyMenu_CreatePupmenuStr(BPyMenu *pym, short group);
 char *BPyMenu_group_itoa (short group);
+char *bpymenu_gethome();
+struct BPyMenu *BPyMenu_GetEntry (short group, short pos);
 
 #endif /* BPY_MENUS_H */