Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWillian Padovani Germano <wpgermano@gmail.com>2004-01-27 06:34:16 +0300
committerWillian Padovani Germano <wpgermano@gmail.com>2004-01-27 06:34:16 +0300
commit33dd2f5e0d58427d49f9bdbd3a593af5a9cc6302 (patch)
tree59fc8dcdd2617c78f6a69dbd88ee578dbe44a63d /release
parentd482d34ffb7ec92b63a5ffb1fa3237da720cc928 (diff)
BPython:
- 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.
Diffstat (limited to 'release')
-rw-r--r--release/bpydata/readme.txt2
-rw-r--r--release/scripts/DirectXExporter.py434
-rw-r--r--release/scripts/ac3d_export.py425
-rw-r--r--release/scripts/ac3d_import.py421
-rw-r--r--release/scripts/blender2cal3d.py983
5 files changed, 2265 insertions, 0 deletions
diff --git a/release/bpydata/readme.txt b/release/bpydata/readme.txt
new file mode 100644
index 00000000000..d518634c2c4
--- /dev/null
+++ b/release/bpydata/readme.txt
@@ -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
index 00000000000..6e5436df27b
--- /dev/null
+++ b/release/scripts/DirectXExporter.py
@@ -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
index 00000000000..6a464767c4c
--- /dev/null
+++ b/release/scripts/ac3d_export.py
@@ -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
index 00000000000..3e3d08a1155
--- /dev/null
+++ b/release/scripts/ac3d_import.py
@@ -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
index 00000000000..9d0d7295512
--- /dev/null
+++ b/release/scripts/blender2cal3d.py
@@ -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()