diff options
author | Willian Padovani Germano <wpgermano@gmail.com> | 2004-01-27 06:34:16 +0300 |
---|---|---|
committer | Willian Padovani Germano <wpgermano@gmail.com> | 2004-01-27 06:34:16 +0300 |
commit | 33dd2f5e0d58427d49f9bdbd3a593af5a9cc6302 (patch) | |
tree | 59fc8dcdd2617c78f6a69dbd88ee578dbe44a63d /release | |
parent | d482d34ffb7ec92b63a5ffb1fa3237da720cc928 (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.txt | 2 | ||||
-rw-r--r-- | release/scripts/DirectXExporter.py | 434 | ||||
-rw-r--r-- | release/scripts/ac3d_export.py | 425 | ||||
-rw-r--r-- | release/scripts/ac3d_import.py | 421 | ||||
-rw-r--r-- | release/scripts/blender2cal3d.py | 983 |
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() |