Manual merge of soc-2009-kazanbas branch:
[blender.git] / release / io / export_fbx.py
1 #!BPY
2 """
3 Name: 'Autodesk FBX (.fbx)...'
4 Blender: 249
5 Group: 'Export'
6 Tooltip: 'Selection to an ASCII Autodesk FBX '
7 """
8 __author__ = "Campbell Barton"
9 __url__ = ['www.blender.org', 'blenderartists.org']
10 __version__ = "1.2"
11
12 __bpydoc__ = """\
13 This script is an exporter to the FBX file format.
14
15 http://wiki.blender.org/index.php/Scripts/Manual/Export/autodesk_fbx
16 """
17 # --------------------------------------------------------------------------
18 # FBX Export v0.1 by Campbell Barton (AKA Ideasman)
19 # --------------------------------------------------------------------------
20 # ***** BEGIN GPL LICENSE BLOCK *****
21 #
22 # This program is free software; you can redistribute it and/or
23 # modify it under the terms of the GNU General Public License
24 # as published by the Free Software Foundation; either version 2
25 # of the License, or (at your option) any later version.
26 #
27 # This program is distributed in the hope that it will be useful,
28 # but WITHOUT ANY WARRANTY; without even the implied warranty of
29 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
30 # GNU General Public License for more details.
31 #
32 # You should have received a copy of the GNU General Public License
33 # along with this program; if not, write to the Free Software Foundation,
34 # Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
35 #
36 # ***** END GPL LICENCE BLOCK *****
37 # --------------------------------------------------------------------------
38
39 import os
40 import time
41 import math # math.pi
42 import shutil # for file copying
43
44 # try:
45 #       import time
46 #       # import os # only needed for batch export, nbot used yet
47 # except:
48 #       time = None # use this to check if they have python modules installed
49
50 # for python 2.3 support
51 try:
52         set()
53 except:
54         try:
55                 from sets import Set as set
56         except:
57                 set = None # so it complains you dont have a !
58
59 # # os is only needed for batch 'own dir' option
60 # try:
61 #       import os
62 # except:
63 #       os = None
64
65 # import Blender
66 import bpy
67 import Mathutils
68 # from Blender.Mathutils import Matrix, Vector, RotationMatrix
69
70 # import BPyObject
71 # import BPyMesh
72 # import BPySys
73 # import BPyMessages
74
75 ## This was used to make V, but faster not to do all that
76 ##valid = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_,.()[]{}'
77 ##v = range(255)
78 ##for c in valid: v.remove(ord(c))
79 v = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,42,43,47,58,59,60,61,62,63,64,92,94,96,124,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254]
80 invalid = ''.join([chr(i) for i in v])
81 def cleanName(name):
82         for ch in invalid:      name = name.replace(ch, '_')
83         return name
84 # del v, i
85
86
87 def copy_file(source, dest):
88         file = open(source, 'rb')
89         data = file.read()
90         file.close()
91         
92         file = open(dest, 'wb')
93         file.write(data)
94         file.close()
95
96
97 def copy_images(dest_dir, textures):
98         if not dest_dir.endswith(os.sep):
99                 dest_dir += os.sep
100         
101         image_paths = set()
102         for tex in textures:
103                 image_paths.add(Blender.sys.expandpath(tex.filename))
104         
105         # Now copy images
106         copyCount = 0
107         for image_path in image_paths:
108                 if Blender.sys.exists(image_path):
109                         # Make a name for the target path.
110                         dest_image_path = dest_dir + image_path.split('\\')[-1].split('/')[-1]
111                         if not Blender.sys.exists(dest_image_path): # Image isnt alredy there
112                                 print('\tCopying "%s" > "%s"' % (image_path, dest_image_path))
113                                 try:
114                                         copy_file(image_path, dest_image_path)
115                                         copyCount+=1
116                                 except:
117                                         print('\t\tWarning, file failed to copy, skipping.')
118         
119         print('\tCopied %d images' % copyCount)
120
121 # I guess FBX uses degrees instead of radians (Arystan).
122 # Call this function just before writing to FBX.
123 def eulerRadToDeg(eul):
124         ret = Mathutils.Euler()
125
126         ret.x = 180 / math.pi * eul[0]
127         ret.y = 180 / math.pi * eul[1]
128         ret.z = 180 / math.pi * eul[2]
129
130         return ret
131
132 mtx4_identity = Mathutils.Matrix()
133
134 # testing
135 mtx_x90         = Mathutils.RotationMatrix( math.pi/2, 3, 'x') # used
136 #mtx_x90n       = RotationMatrix(-90, 3, 'x')
137 #mtx_y90        = RotationMatrix( 90, 3, 'y')
138 #mtx_y90n       = RotationMatrix(-90, 3, 'y')
139 #mtx_z90        = RotationMatrix( 90, 3, 'z')
140 #mtx_z90n       = RotationMatrix(-90, 3, 'z')
141
142 #mtx4_x90       = RotationMatrix( 90, 4, 'x')
143 mtx4_x90n       = Mathutils.RotationMatrix(-math.pi/2, 4, 'x') # used
144 #mtx4_y90       = RotationMatrix( 90, 4, 'y')
145 mtx4_y90n       = Mathutils.RotationMatrix(-math.pi/2, 4, 'y') # used
146 mtx4_z90        = Mathutils.RotationMatrix( math.pi/2, 4, 'z') # used
147 mtx4_z90n       = Mathutils.RotationMatrix(-math.pi/2, 4, 'z') # used
148
149 # def strip_path(p):
150 #       return p.split('\\')[-1].split('/')[-1]
151
152 # Used to add the scene name into the filename without using odd chars  
153 sane_name_mapping_ob = {}
154 sane_name_mapping_mat = {}
155 sane_name_mapping_tex = {}
156 sane_name_mapping_take = {}
157 sane_name_mapping_group = {}
158
159 # Make sure reserved names are not used
160 sane_name_mapping_ob['Scene'] = 'Scene_'
161 sane_name_mapping_ob['blend_root'] = 'blend_root_'
162
163 def increment_string(t):
164         name = t
165         num = ''
166         while name and name[-1].isdigit():
167                 num = name[-1] + num
168                 name = name[:-1]
169         if num: return '%s%d' % (name, int(num)+1)      
170         else:   return name + '_0'
171
172
173
174 # todo - Disallow the name 'Scene' and 'blend_root' - it will bugger things up.
175 def sane_name(data, dct):
176         #if not data: return None
177         
178         if type(data)==tuple: # materials are paired up with images
179                 data, other = data
180                 use_other = True
181         else:
182                 other = None
183                 use_other = False
184         
185         if data:        name = data.name
186         else:           name = None
187         orig_name = name
188         
189         if other:
190                 orig_name_other = other.name
191                 name = '%s #%s' % (name, orig_name_other)
192         else:
193                 orig_name_other = None
194         
195         # dont cache, only ever call once for each data type now,
196         # so as to avoid namespace collision between types - like with objects <-> bones
197         #try:           return dct[name]
198         #except:                pass
199         
200         if not name:
201                 name = 'unnamed' # blank string, ASKING FOR TROUBLE!
202         else:
203                 #name = BPySys.cleanName(name)
204                 name = cleanName(name) # use our own
205         
206         while name in iter(dct.values()):       name = increment_string(name)
207         
208         if use_other: # even if other is None - orig_name_other will be a string or None
209                 dct[orig_name, orig_name_other] = name
210         else:
211                 dct[orig_name] = name
212                 
213         return name
214
215 def sane_obname(data):          return sane_name(data, sane_name_mapping_ob)
216 def sane_matname(data):         return sane_name(data, sane_name_mapping_mat)
217 def sane_texname(data):         return sane_name(data, sane_name_mapping_tex)
218 def sane_takename(data):        return sane_name(data, sane_name_mapping_take)
219 def sane_groupname(data):       return sane_name(data, sane_name_mapping_group)
220
221 # def derived_paths(fname_orig, basepath, FORCE_CWD=False):
222 #       '''
223 #       fname_orig - blender path, can be relative
224 #       basepath - fname_rel will be relative to this
225 #       FORCE_CWD - dont use the basepath, just add a ./ to the filename.
226 #               use when we know the file will be in the basepath.
227 #       '''
228 #       fname = bpy.sys.expandpath(fname_orig)
229 # #     fname = Blender.sys.expandpath(fname_orig)
230 #       fname_strip = os.path.basename(fname)
231 # #     fname_strip = strip_path(fname)
232 #       if FORCE_CWD:
233 #               fname_rel = '.' + os.sep + fname_strip
234 #       else:
235 #               fname_rel = bpy.sys.relpath(fname, basepath)
236 # #             fname_rel = Blender.sys.relpath(fname, basepath)
237 #       if fname_rel.startswith('//'): fname_rel = '.' + os.sep + fname_rel[2:]
238 #       return fname, fname_strip, fname_rel
239
240
241 def mat4x4str(mat):
242         return '%.15f,%.15f,%.15f,%.15f,%.15f,%.15f,%.15f,%.15f,%.15f,%.15f,%.15f,%.15f,%.15f,%.15f,%.15f,%.15f' % tuple([ f for v in mat for f in v ])
243
244 # XXX not used
245 # duplicated in OBJ exporter
246 def getVertsFromGroup(me, group_index):
247         ret = []
248
249         for i, v in enumerate(me.verts):
250                 for g in v.groups:
251                         if g.group == group_index:
252                                 ret.append((i, g.weight))
253
254                 return ret
255
256 # ob must be OB_MESH
257 def BPyMesh_meshWeight2List(ob):
258         ''' Takes a mesh and return its group names and a list of lists, one list per vertex.
259         aligning the each vert list with the group names, each list contains float value for the weight.
260         These 2 lists can be modified and then used with list2MeshWeight to apply the changes.
261         '''
262
263         me = ob.data
264
265         # Clear the vert group.
266         groupNames= [g.name for g in ob.vertex_groups]
267         len_groupNames= len(groupNames)
268         
269         if not len_groupNames:
270                 # no verts? return a vert aligned empty list
271                 return [[] for i in range(len(me.verts))], []
272         else:
273                 vWeightList= [[0.0]*len_groupNames for i in range(len(me.verts))]
274
275         for i, v in enumerate(me.verts):
276                 for g in v.groups:
277                         vWeightList[i][g.group] = g.weight
278
279         return groupNames, vWeightList
280
281 def meshNormalizedWeights(me):
282         try: # account for old bad BPyMesh
283                 groupNames, vWeightList = BPyMesh_meshWeight2List(me)
284 #               groupNames, vWeightList = BPyMesh.meshWeight2List(me)
285         except:
286                 return [],[]
287         
288         if not groupNames:
289                 return [],[]
290         
291         for i, vWeights in enumerate(vWeightList):
292                 tot = 0.0
293                 for w in vWeights:
294                         tot+=w
295                 
296                 if tot:
297                         for j, w in enumerate(vWeights):
298                                 vWeights[j] = w/tot
299         
300         return groupNames, vWeightList
301
302 header_comment = \
303 '''; FBX 6.1.0 project file
304 ; Created by Blender FBX Exporter
305 ; for support mail: ideasman42@gmail.com
306 ; ----------------------------------------------------
307
308 '''
309
310 # This func can be called with just the filename
311 def write(filename, batch_objects = None, \
312                 context = None,
313                 EXP_OBS_SELECTED =                      True,
314                 EXP_MESH =                                      True,
315                 EXP_MESH_APPLY_MOD =            True,
316 #               EXP_MESH_HQ_NORMALS =           False,
317                 EXP_ARMATURE =                          True,
318                 EXP_LAMP =                                      True,
319                 EXP_CAMERA =                            True,
320                 EXP_EMPTY =                                     True,
321                 EXP_IMAGE_COPY =                        False,
322                 GLOBAL_MATRIX =                         Mathutils.Matrix(),
323                 ANIM_ENABLE =                           True,
324                 ANIM_OPTIMIZE =                         True,
325                 ANIM_OPTIMIZE_PRECISSION =      6,
326                 ANIM_ACTION_ALL =                       False,
327                 BATCH_ENABLE =                          False,
328                 BATCH_GROUP =                           True,
329                 BATCH_FILE_PREFIX =                     '',
330                 BATCH_OWN_DIR =                         False
331         ):
332         
333         # ----------------- Batch support!
334         if BATCH_ENABLE:
335                 if os == None:  BATCH_OWN_DIR = False
336                 
337                 fbxpath = filename
338                 
339                 # get the path component of filename
340                 tmp_exists = bpy.sys.exists(fbxpath)
341 #               tmp_exists = Blender.sys.exists(fbxpath)
342                 
343                 if tmp_exists != 2: # a file, we want a path
344                         fbxpath = os.path.dirname(fbxpath)
345 #                       while fbxpath and fbxpath[-1] not in ('/', '\\'):
346 #                               fbxpath = fbxpath[:-1]
347                         if not fbxpath:
348 #                       if not filename:
349                                 # XXX
350                                 print('Error%t|Directory does not exist!')
351 #                               Draw.PupMenu('Error%t|Directory does not exist!')
352                                 return
353
354                         tmp_exists = bpy.sys.exists(fbxpath)
355 #                       tmp_exists = Blender.sys.exists(fbxpath)
356                 
357                 if tmp_exists != 2:
358                         # XXX
359                         print('Error%t|Directory does not exist!')
360 #                       Draw.PupMenu('Error%t|Directory does not exist!')
361                         return
362                 
363                 if not fbxpath.endswith(os.sep):
364                         fbxpath += os.sep
365                 del tmp_exists
366                 
367                 
368                 if BATCH_GROUP:
369                         data_seq = bpy.data.groups
370                 else:
371                         data_seq = bpy.data.scenes
372                 
373                 # call this function within a loop with BATCH_ENABLE == False
374                 orig_sce = context.scene
375 #               orig_sce = bpy.data.scenes.active
376                 
377                 
378                 new_fbxpath = fbxpath # own dir option modifies, we need to keep an original
379                 for data in data_seq: # scene or group
380                         newname = BATCH_FILE_PREFIX + cleanName(data.name)
381 #                       newname = BATCH_FILE_PREFIX + BPySys.cleanName(data.name)
382                         
383                         
384                         if BATCH_OWN_DIR:
385                                 new_fbxpath = fbxpath + newname + os.sep
386                                 # path may alredy exist
387                                 # TODO - might exist but be a file. unlikely but should probably account for it.
388
389                                 if bpy.sys.exists(new_fbxpath) == 0:
390 #                               if Blender.sys.exists(new_fbxpath) == 0:
391                                         os.mkdir(new_fbxpath)
392                                 
393                         
394                         filename = new_fbxpath + newname + '.fbx'
395                         
396                         print('\nBatch exporting %s as...\n\t"%s"' % (data, filename))
397
398                         # XXX don't know what to do with this, probably do the same? (Arystan)
399                         if BATCH_GROUP: #group
400                                 # group, so objects update properly, add a dummy scene.
401                                 sce = bpy.data.scenes.new()
402                                 sce.Layers = (1<<20) -1
403                                 bpy.data.scenes.active = sce
404                                 for ob_base in data.objects:
405                                         sce.objects.link(ob_base)
406                                 
407                                 sce.update(1)
408                                 
409                                 # TODO - BUMMER! Armatures not in the group wont animate the mesh
410                                 
411                         else:# scene
412                                 
413                                 
414                                 data_seq.active = data
415                         
416                         
417                         # Call self with modified args
418                         # Dont pass batch options since we alredy usedt them
419                         write(filename, data.objects,
420                                 context,
421                                 False,
422                                 EXP_MESH,
423                                 EXP_MESH_APPLY_MOD,
424 #                               EXP_MESH_HQ_NORMALS,
425                                 EXP_ARMATURE,
426                                 EXP_LAMP,
427                                 EXP_CAMERA,
428                                 EXP_EMPTY,
429                                 EXP_IMAGE_COPY,
430                                 GLOBAL_MATRIX,
431                                 ANIM_ENABLE,
432                                 ANIM_OPTIMIZE,
433                                 ANIM_OPTIMIZE_PRECISSION,
434                                 ANIM_ACTION_ALL
435                         )
436                         
437                         if BATCH_GROUP:
438                                 # remove temp group scene
439                                 bpy.data.remove_scene(sce)
440 #                               bpy.data.scenes.unlink(sce)
441                 
442                 bpy.data.scenes.active = orig_sce
443                 
444                 return # so the script wont run after we have batch exported.
445         
446         # end batch support
447         
448         # Use this for working out paths relative to the export location
449         basepath = os.path.dirname(filename) or '.'
450         basepath += os.sep
451 #       basepath = Blender.sys.dirname(filename)
452         
453         # ----------------------------------------------
454         # storage classes
455         class my_bone_class:
456                 __slots__ =(\
457                   'blenName',\
458                   'blenBone',\
459                   'blenMeshes',\
460                   'restMatrix',\
461                   'parent',\
462                   'blenName',\
463                   'fbxName',\
464                   'fbxArm',\
465                   '__pose_bone',\
466                   '__anim_poselist')
467                 
468                 def __init__(self, blenBone, fbxArm):
469                         
470                         # This is so 2 armatures dont have naming conflicts since FBX bones use object namespace
471                         self.fbxName = sane_obname(blenBone)
472                         
473                         self.blenName =                 blenBone.name
474                         self.blenBone =                 blenBone
475                         self.blenMeshes =               {}                                      # fbxMeshObName : mesh
476                         self.fbxArm =                   fbxArm
477                         self.restMatrix =               blenBone.armature_matrix
478 #                       self.restMatrix =               blenBone.matrix['ARMATURESPACE']
479                         
480                         # not used yet
481                         # self.restMatrixInv =  self.restMatrix.copy().invert()
482                         # self.restMatrixLocal =        None # set later, need parent matrix
483                         
484                         self.parent =                   None
485                         
486                         # not public
487                         pose = fbxArm.blenObject.pose
488 #                       pose = fbxArm.blenObject.getPose()
489                         self.__pose_bone =              pose.pose_channels[self.blenName]
490 #                       self.__pose_bone =              pose.bones[self.blenName]
491                         
492                         # store a list if matricies here, (poseMatrix, head, tail)
493                         # {frame:posematrix, frame:posematrix, ...}
494                         self.__anim_poselist = {}
495                 
496                 '''
497                 def calcRestMatrixLocal(self):
498                         if self.parent:
499                                 self.restMatrixLocal = self.restMatrix * self.parent.restMatrix.copy().invert()
500                         else:
501                                 self.restMatrixLocal = self.restMatrix.copy()
502                 '''
503                 def setPoseFrame(self, f):
504                         # cache pose info here, frame must be set beforehand
505                         
506                         # Didnt end up needing head or tail, if we do - here it is.
507                         '''
508                         self.__anim_poselist[f] = (\
509                                 self.__pose_bone.poseMatrix.copy(),\
510                                 self.__pose_bone.head.copy(),\
511                                 self.__pose_bone.tail.copy() )
512                         '''
513
514                         self.__anim_poselist[f] = self.__pose_bone.pose_matrix.copy()
515 #                       self.__anim_poselist[f] = self.__pose_bone.poseMatrix.copy()
516                 
517                 # get pose from frame.
518                 def getPoseMatrix(self, f):# ----------------------------------------------
519                         return self.__anim_poselist[f]
520                 '''
521                 def getPoseHead(self, f):
522                         #return self.__pose_bone.head.copy()
523                         return self.__anim_poselist[f][1].copy()
524                 def getPoseTail(self, f):
525                         #return self.__pose_bone.tail.copy()
526                         return self.__anim_poselist[f][2].copy()
527                 '''
528                 # end
529                 
530                 def getAnimParRelMatrix(self, frame):
531                         #arm_mat = self.fbxArm.matrixWorld
532                         #arm_mat = self.fbxArm.parRelMatrix()
533                         if not self.parent:
534                                 #return mtx4_z90 * (self.getPoseMatrix(frame) * arm_mat) # dont apply arm matrix anymore
535                                 return mtx4_z90 * self.getPoseMatrix(frame)
536                         else:
537                                 #return (mtx4_z90 * ((self.getPoseMatrix(frame) * arm_mat)))  *  (mtx4_z90 * (self.parent.getPoseMatrix(frame) * arm_mat)).invert()
538                                 return (mtx4_z90 * (self.getPoseMatrix(frame)))  *  (mtx4_z90 * self.parent.getPoseMatrix(frame)).invert()
539                 
540                 # we need thes because cameras and lights modified rotations
541                 def getAnimParRelMatrixRot(self, frame):
542                         return self.getAnimParRelMatrix(frame)
543                 
544                 def flushAnimData(self):
545                         self.__anim_poselist.clear()
546
547
548         class my_object_generic:
549                 # Other settings can be applied for each type - mesh, armature etc.
550                 def __init__(self, ob, matrixWorld = None):
551                         self.fbxName = sane_obname(ob)
552                         self.blenObject = ob
553                         self.fbxGroupNames = []
554                         self.fbxParent = None # set later on IF the parent is in the selection.
555                         if matrixWorld:         self.matrixWorld = matrixWorld * GLOBAL_MATRIX
556                         else:                           self.matrixWorld = ob.matrix * GLOBAL_MATRIX
557 #                       else:                           self.matrixWorld = ob.matrixWorld * GLOBAL_MATRIX
558                         self.__anim_poselist = {} # we should only access this
559                 
560                 def parRelMatrix(self):
561                         if self.fbxParent:
562                                 return self.matrixWorld * self.fbxParent.matrixWorld.copy().invert()
563                         else:
564                                 return self.matrixWorld
565                 
566                 def setPoseFrame(self, f):
567                         self.__anim_poselist[f] =  self.blenObject.matrix.copy()
568 #                       self.__anim_poselist[f] =  self.blenObject.matrixWorld.copy()
569                 
570                 def getAnimParRelMatrix(self, frame):
571                         if self.fbxParent:
572                                 #return (self.__anim_poselist[frame] * self.fbxParent.__anim_poselist[frame].copy().invert() ) * GLOBAL_MATRIX
573                                 return (self.__anim_poselist[frame] * GLOBAL_MATRIX) * (self.fbxParent.__anim_poselist[frame] * GLOBAL_MATRIX).invert()
574                         else:
575                                 return self.__anim_poselist[frame] * GLOBAL_MATRIX
576                 
577                 def getAnimParRelMatrixRot(self, frame):
578                         type = self.blenObject.type
579                         if self.fbxParent:
580                                 matrix_rot = (((self.__anim_poselist[frame] * GLOBAL_MATRIX) * (self.fbxParent.__anim_poselist[frame] * GLOBAL_MATRIX).invert())).rotationPart()
581                         else:
582                                 matrix_rot = (self.__anim_poselist[frame] * GLOBAL_MATRIX).rotationPart()
583                         
584                         # Lamps need to be rotated
585                         if type =='LAMP':
586                                 matrix_rot = mtx_x90 * matrix_rot
587                         elif type =='CAMERA':
588 #                       elif ob and type =='Camera':
589                                 y = Mathutils.Vector(0,1,0) * matrix_rot
590                                 matrix_rot = matrix_rot * Mathutils.RotationMatrix(math.pi/2, 3, 'r', y)
591                         
592                         return matrix_rot
593                         
594         # ----------------------------------------------
595         
596         
597         
598         
599         
600         print('\nFBX export starting...', filename)
601         start_time = time.clock()
602 #       start_time = Blender.sys.time()
603         try:
604                 file = open(filename, 'w')
605         except:
606                 return False
607
608         sce = context.scene
609 #       sce = bpy.data.scenes.active
610         world = sce.world
611         
612         
613         # ---------------------------- Write the header first
614         file.write(header_comment)
615         if time:
616                 curtime = time.localtime()[0:6]
617         else:
618                 curtime = (0,0,0,0,0,0)
619         # 
620         file.write(\
621 '''FBXHeaderExtension:  {
622         FBXHeaderVersion: 1003
623         FBXVersion: 6100
624         CreationTimeStamp:  {
625                 Version: 1000
626                 Year: %.4i
627                 Month: %.2i
628                 Day: %.2i
629                 Hour: %.2i
630                 Minute: %.2i
631                 Second: %.2i
632                 Millisecond: 0
633         }
634         Creator: "FBX SDK/FBX Plugins build 20070228"
635         OtherFlags:  {
636                 FlagPLE: 0
637         }
638 }''' % (curtime))
639         
640         file.write('\nCreationTime: "%.4i-%.2i-%.2i %.2i:%.2i:%.2i:000"' % curtime)
641         file.write('\nCreator: "Blender3D version 2.5"')
642 #       file.write('\nCreator: "Blender3D version %.2f"' % Blender.Get('version'))
643         
644         pose_items = [] # list of (fbxName, matrix) to write pose data for, easier to collect allong the way
645         
646         # --------------- funcs for exporting
647         def object_tx(ob, loc, matrix, matrix_mod = None):
648                 '''
649                 Matrix mod is so armature objects can modify their bone matricies
650                 '''
651                 if isinstance(ob, bpy.types.Bone):
652 #               if isinstance(ob, Blender.Types.BoneType):
653                         
654                         # we know we have a matrix
655                         # matrix = mtx4_z90 * (ob.matrix['ARMATURESPACE'] * matrix_mod)
656                         matrix = mtx4_z90 * ob.armature_matrix # dont apply armature matrix anymore
657 #                       matrix = mtx4_z90 * ob.matrix['ARMATURESPACE'] # dont apply armature matrix anymore
658                         
659                         parent = ob.parent
660                         if parent:
661                                 #par_matrix = mtx4_z90 * (parent.matrix['ARMATURESPACE'] * matrix_mod)
662                                 par_matrix = mtx4_z90 * parent.armature_matrix # dont apply armature matrix anymore
663 #                               par_matrix = mtx4_z90 * parent.matrix['ARMATURESPACE'] # dont apply armature matrix anymore
664                                 matrix = matrix * par_matrix.copy().invert()
665                                 
666                         matrix_rot =    matrix.rotationPart()
667                         
668                         loc =                   tuple(matrix.translationPart())
669                         scale =                 tuple(matrix.scalePart())
670                         rot =                   tuple(matrix_rot.toEuler())
671                         
672                 else:
673                         # This is bad because we need the parent relative matrix from the fbx parent (if we have one), dont use anymore
674                         #if ob and not matrix: matrix = ob.matrixWorld * GLOBAL_MATRIX
675                         if ob and not matrix: raise Exception("error: this should never happen!")
676                         
677                         matrix_rot = matrix
678                         #if matrix:
679                         #       matrix = matrix_scale * matrix
680                         
681                         if matrix:
682                                 loc = tuple(matrix.translationPart())
683                                 scale = tuple(matrix.scalePart())
684                                 
685                                 matrix_rot = matrix.rotationPart()
686                                 # Lamps need to be rotated
687                                 if ob and ob.type =='Lamp':
688                                         matrix_rot = mtx_x90 * matrix_rot
689                                         rot = tuple(matrix_rot.toEuler())
690                                 elif ob and ob.type =='Camera':
691                                         y = Mathutils.Vector(0,1,0) * matrix_rot
692                                         matrix_rot = matrix_rot * Mathutils.RotationMatrix(math.pi/2, 3, 'r', y)
693                                         rot = tuple(matrix_rot.toEuler())
694                                 else:
695                                         rot = tuple(matrix_rot.toEuler())
696                         else:
697                                 if not loc:
698                                         loc = 0,0,0
699                                 scale = 1,1,1
700                                 rot = 0,0,0
701                 
702                 return loc, rot, scale, matrix, matrix_rot
703         
704         def write_object_tx(ob, loc, matrix, matrix_mod= None):
705                 '''
706                 We have loc to set the location if non blender objects that have a location
707                 
708                 matrix_mod is only used for bones at the moment
709                 '''
710                 loc, rot, scale, matrix, matrix_rot = object_tx(ob, loc, matrix, matrix_mod)
711                 
712                 file.write('\n\t\t\tProperty: "Lcl Translation", "Lcl Translation", "A+",%.15f,%.15f,%.15f' % loc)
713                 file.write('\n\t\t\tProperty: "Lcl Rotation", "Lcl Rotation", "A+",%.15f,%.15f,%.15f' % tuple(eulerRadToDeg(rot)))
714 #               file.write('\n\t\t\tProperty: "Lcl Rotation", "Lcl Rotation", "A+",%.15f,%.15f,%.15f' % rot)
715                 file.write('\n\t\t\tProperty: "Lcl Scaling", "Lcl Scaling", "A+",%.15f,%.15f,%.15f' % scale)
716                 return loc, rot, scale, matrix, matrix_rot
717         
718         def write_object_props(ob=None, loc=None, matrix=None, matrix_mod=None):
719                 # if the type is 0 its an empty otherwise its a mesh
720                 # only difference at the moment is one has a color
721                 file.write('''
722                 Properties60:  {
723                         Property: "QuaternionInterpolate", "bool", "",0
724                         Property: "Visibility", "Visibility", "A+",1''')
725                 
726                 loc, rot, scale, matrix, matrix_rot = write_object_tx(ob, loc, matrix, matrix_mod)
727                 
728                 # Rotation order, note, for FBX files Iv loaded normal order is 1
729                 # setting to zero.
730                 # eEULER_XYZ = 0
731                 # eEULER_XZY
732                 # eEULER_YZX
733                 # eEULER_YXZ
734                 # eEULER_ZXY
735                 # eEULER_ZYX
736                 
737                 file.write('''
738                         Property: "RotationOffset", "Vector3D", "",0,0,0
739                         Property: "RotationPivot", "Vector3D", "",0,0,0
740                         Property: "ScalingOffset", "Vector3D", "",0,0,0
741                         Property: "ScalingPivot", "Vector3D", "",0,0,0
742                         Property: "TranslationActive", "bool", "",0
743                         Property: "TranslationMin", "Vector3D", "",0,0,0
744                         Property: "TranslationMax", "Vector3D", "",0,0,0
745                         Property: "TranslationMinX", "bool", "",0
746                         Property: "TranslationMinY", "bool", "",0
747                         Property: "TranslationMinZ", "bool", "",0
748                         Property: "TranslationMaxX", "bool", "",0
749                         Property: "TranslationMaxY", "bool", "",0
750                         Property: "TranslationMaxZ", "bool", "",0
751                         Property: "RotationOrder", "enum", "",0
752                         Property: "RotationSpaceForLimitOnly", "bool", "",0
753                         Property: "AxisLen", "double", "",10
754                         Property: "PreRotation", "Vector3D", "",0,0,0
755                         Property: "PostRotation", "Vector3D", "",0,0,0
756                         Property: "RotationActive", "bool", "",0
757                         Property: "RotationMin", "Vector3D", "",0,0,0
758                         Property: "RotationMax", "Vector3D", "",0,0,0
759                         Property: "RotationMinX", "bool", "",0
760                         Property: "RotationMinY", "bool", "",0
761                         Property: "RotationMinZ", "bool", "",0
762                         Property: "RotationMaxX", "bool", "",0
763                         Property: "RotationMaxY", "bool", "",0
764                         Property: "RotationMaxZ", "bool", "",0
765                         Property: "RotationStiffnessX", "double", "",0
766                         Property: "RotationStiffnessY", "double", "",0
767                         Property: "RotationStiffnessZ", "double", "",0
768                         Property: "MinDampRangeX", "double", "",0
769                         Property: "MinDampRangeY", "double", "",0
770                         Property: "MinDampRangeZ", "double", "",0
771                         Property: "MaxDampRangeX", "double", "",0
772                         Property: "MaxDampRangeY", "double", "",0
773                         Property: "MaxDampRangeZ", "double", "",0
774                         Property: "MinDampStrengthX", "double", "",0
775                         Property: "MinDampStrengthY", "double", "",0
776                         Property: "MinDampStrengthZ", "double", "",0
777                         Property: "MaxDampStrengthX", "double", "",0
778                         Property: "MaxDampStrengthY", "double", "",0
779                         Property: "MaxDampStrengthZ", "double", "",0
780                         Property: "PreferedAngleX", "double", "",0
781                         Property: "PreferedAngleY", "double", "",0
782                         Property: "PreferedAngleZ", "double", "",0
783                         Property: "InheritType", "enum", "",0
784                         Property: "ScalingActive", "bool", "",0
785                         Property: "ScalingMin", "Vector3D", "",1,1,1
786                         Property: "ScalingMax", "Vector3D", "",1,1,1
787                         Property: "ScalingMinX", "bool", "",0
788                         Property: "ScalingMinY", "bool", "",0
789                         Property: "ScalingMinZ", "bool", "",0
790                         Property: "ScalingMaxX", "bool", "",0
791                         Property: "ScalingMaxY", "bool", "",0
792                         Property: "ScalingMaxZ", "bool", "",0
793                         Property: "GeometricTranslation", "Vector3D", "",0,0,0
794                         Property: "GeometricRotation", "Vector3D", "",0,0,0
795                         Property: "GeometricScaling", "Vector3D", "",1,1,1
796                         Property: "LookAtProperty", "object", ""
797                         Property: "UpVectorProperty", "object", ""
798                         Property: "Show", "bool", "",1
799                         Property: "NegativePercentShapeSupport", "bool", "",1
800                         Property: "DefaultAttributeIndex", "int", "",0''')
801                 if ob and not isinstance(ob, bpy.types.Bone):
802 #               if ob and type(ob) != Blender.Types.BoneType:
803                         # Only mesh objects have color 
804                         file.write('\n\t\t\tProperty: "Color", "Color", "A",0.8,0.8,0.8')
805                         file.write('\n\t\t\tProperty: "Size", "double", "",100')
806                         file.write('\n\t\t\tProperty: "Look", "enum", "",1')
807                 
808                 return loc, rot, scale, matrix, matrix_rot
809         
810         
811         # -------------------------------------------- Armatures
812         #def write_bone(bone, name, matrix_mod):
813         def write_bone(my_bone):
814                 file.write('\n\tModel: "Model::%s", "Limb" {' % my_bone.fbxName)
815                 file.write('\n\t\tVersion: 232')
816                 
817                 #poseMatrix = write_object_props(my_bone.blenBone, None, None, my_bone.fbxArm.parRelMatrix())[3]
818                 poseMatrix = write_object_props(my_bone.blenBone)[3] # dont apply bone matricies anymore
819                 pose_items.append( (my_bone.fbxName, poseMatrix) )
820                 
821                 
822                 # file.write('\n\t\t\tProperty: "Size", "double", "",%.6f' % ((my_bone.blenData.head['ARMATURESPACE'] - my_bone.blenData.tail['ARMATURESPACE']) * my_bone.fbxArm.parRelMatrix()).length)
823                 file.write('\n\t\t\tProperty: "Size", "double", "",1')
824                 
825                 #((my_bone.blenData.head['ARMATURESPACE'] * my_bone.fbxArm.matrixWorld) - (my_bone.blenData.tail['ARMATURESPACE'] * my_bone.fbxArm.parRelMatrix())).length)
826                 
827                 """
828                 file.write('\n\t\t\tProperty: "LimbLength", "double", "",%.6f' %\
829                         ((my_bone.blenBone.head['ARMATURESPACE'] - my_bone.blenBone.tail['ARMATURESPACE']) * my_bone.fbxArm.parRelMatrix()).length)
830                 """
831                 
832                 file.write('\n\t\t\tProperty: "LimbLength", "double", "",%.6f' %
833                                    (my_bone.blenBone.armature_head - my_bone.blenBone.armature_tail).length)
834 #                       (my_bone.blenBone.head['ARMATURESPACE'] - my_bone.blenBone.tail['ARMATURESPACE']).length)
835                 
836                 #file.write('\n\t\t\tProperty: "LimbLength", "double", "",1')
837                 file.write('\n\t\t\tProperty: "Color", "ColorRGB", "",0.8,0.8,0.8')
838                 file.write('\n\t\t\tProperty: "Color", "Color", "A",0.8,0.8,0.8')
839                 file.write('\n\t\t}')
840                 file.write('\n\t\tMultiLayer: 0')
841                 file.write('\n\t\tMultiTake: 1')
842                 file.write('\n\t\tShading: Y')
843                 file.write('\n\t\tCulling: "CullingOff"')
844                 file.write('\n\t\tTypeFlags: "Skeleton"')
845                 file.write('\n\t}')
846         
847         def write_camera_switch():
848                 file.write('''
849         Model: "Model::Camera Switcher", "CameraSwitcher" {
850                 Version: 232''')
851                 
852                 write_object_props()
853                 file.write('''
854                         Property: "Color", "Color", "A",0.8,0.8,0.8
855                         Property: "Camera Index", "Integer", "A+",100
856                 }
857                 MultiLayer: 0
858                 MultiTake: 1
859                 Hidden: "True"
860                 Shading: W
861                 Culling: "CullingOff"
862                 Version: 101
863                 Name: "Model::Camera Switcher"
864                 CameraId: 0
865                 CameraName: 100
866                 CameraIndexName: 
867         }''')
868         
869         def write_camera_dummy(name, loc, near, far, proj_type, up):
870                 file.write('\n\tModel: "Model::%s", "Camera" {' % name )
871                 file.write('\n\t\tVersion: 232')
872                 write_object_props(None, loc)
873                 
874                 file.write('\n\t\t\tProperty: "Color", "Color", "A",0.8,0.8,0.8')
875                 file.write('\n\t\t\tProperty: "Roll", "Roll", "A+",0')
876                 file.write('\n\t\t\tProperty: "FieldOfView", "FieldOfView", "A+",40')
877                 file.write('\n\t\t\tProperty: "FieldOfViewX", "FieldOfView", "A+",1')
878                 file.write('\n\t\t\tProperty: "FieldOfViewY", "FieldOfView", "A+",1')
879                 file.write('\n\t\t\tProperty: "OpticalCenterX", "Real", "A+",0')
880                 file.write('\n\t\t\tProperty: "OpticalCenterY", "Real", "A+",0')
881                 file.write('\n\t\t\tProperty: "BackgroundColor", "Color", "A+",0.63,0.63,0.63')
882                 file.write('\n\t\t\tProperty: "TurnTable", "Real", "A+",0')
883                 file.write('\n\t\t\tProperty: "DisplayTurnTableIcon", "bool", "",1')
884                 file.write('\n\t\t\tProperty: "Motion Blur Intensity", "Real", "A+",1')
885                 file.write('\n\t\t\tProperty: "UseMotionBlur", "bool", "",0')
886                 file.write('\n\t\t\tProperty: "UseRealTimeMotionBlur", "bool", "",1')
887                 file.write('\n\t\t\tProperty: "ResolutionMode", "enum", "",0')
888                 file.write('\n\t\t\tProperty: "ApertureMode", "enum", "",2')
889                 file.write('\n\t\t\tProperty: "GateFit", "enum", "",0')
890                 file.write('\n\t\t\tProperty: "FocalLength", "Real", "A+",21.3544940948486')
891                 file.write('\n\t\t\tProperty: "CameraFormat", "enum", "",0')
892                 file.write('\n\t\t\tProperty: "AspectW", "double", "",320')
893                 file.write('\n\t\t\tProperty: "AspectH", "double", "",200')
894                 file.write('\n\t\t\tProperty: "PixelAspectRatio", "double", "",1')
895                 file.write('\n\t\t\tProperty: "UseFrameColor", "bool", "",0')
896                 file.write('\n\t\t\tProperty: "FrameColor", "ColorRGB", "",0.3,0.3,0.3')
897                 file.write('\n\t\t\tProperty: "ShowName", "bool", "",1')
898                 file.write('\n\t\t\tProperty: "ShowGrid", "bool", "",1')
899                 file.write('\n\t\t\tProperty: "ShowOpticalCenter", "bool", "",0')
900                 file.write('\n\t\t\tProperty: "ShowAzimut", "bool", "",1')
901                 file.write('\n\t\t\tProperty: "ShowTimeCode", "bool", "",0')
902                 file.write('\n\t\t\tProperty: "NearPlane", "double", "",%.6f' % near)
903                 file.write('\n\t\t\tProperty: "FarPlane", "double", "",%.6f' % far)
904                 file.write('\n\t\t\tProperty: "FilmWidth", "double", "",0.816')
905                 file.write('\n\t\t\tProperty: "FilmHeight", "double", "",0.612')
906                 file.write('\n\t\t\tProperty: "FilmAspectRatio", "double", "",1.33333333333333')
907                 file.write('\n\t\t\tProperty: "FilmSqueezeRatio", "double", "",1')
908                 file.write('\n\t\t\tProperty: "FilmFormatIndex", "enum", "",4')
909                 file.write('\n\t\t\tProperty: "ViewFrustum", "bool", "",1')
910                 file.write('\n\t\t\tProperty: "ViewFrustumNearFarPlane", "bool", "",0')
911                 file.write('\n\t\t\tProperty: "ViewFrustumBackPlaneMode", "enum", "",2')
912                 file.write('\n\t\t\tProperty: "BackPlaneDistance", "double", "",100')
913                 file.write('\n\t\t\tProperty: "BackPlaneDistanceMode", "enum", "",0')
914                 file.write('\n\t\t\tProperty: "ViewCameraToLookAt", "bool", "",1')
915                 file.write('\n\t\t\tProperty: "LockMode", "bool", "",0')
916                 file.write('\n\t\t\tProperty: "LockInterestNavigation", "bool", "",0')
917                 file.write('\n\t\t\tProperty: "FitImage", "bool", "",0')
918                 file.write('\n\t\t\tProperty: "Crop", "bool", "",0')
919                 file.write('\n\t\t\tProperty: "Center", "bool", "",1')
920                 file.write('\n\t\t\tProperty: "KeepRatio", "bool", "",1')
921                 file.write('\n\t\t\tProperty: "BackgroundMode", "enum", "",0')
922                 file.write('\n\t\t\tProperty: "BackgroundAlphaTreshold", "double", "",0.5')
923                 file.write('\n\t\t\tProperty: "ForegroundTransparent", "bool", "",1')
924                 file.write('\n\t\t\tProperty: "DisplaySafeArea", "bool", "",0')
925                 file.write('\n\t\t\tProperty: "SafeAreaDisplayStyle", "enum", "",1')
926                 file.write('\n\t\t\tProperty: "SafeAreaAspectRatio", "double", "",1.33333333333333')
927                 file.write('\n\t\t\tProperty: "Use2DMagnifierZoom", "bool", "",0')
928                 file.write('\n\t\t\tProperty: "2D Magnifier Zoom", "Real", "A+",100')
929                 file.write('\n\t\t\tProperty: "2D Magnifier X", "Real", "A+",50')
930                 file.write('\n\t\t\tProperty: "2D Magnifier Y", "Real", "A+",50')
931                 file.write('\n\t\t\tProperty: "CameraProjectionType", "enum", "",%i' % proj_type)
932                 file.write('\n\t\t\tProperty: "UseRealTimeDOFAndAA", "bool", "",0')
933                 file.write('\n\t\t\tProperty: "UseDepthOfField", "bool", "",0')
934                 file.write('\n\t\t\tProperty: "FocusSource", "enum", "",0')
935                 file.write('\n\t\t\tProperty: "FocusAngle", "double", "",3.5')
936                 file.write('\n\t\t\tProperty: "FocusDistance", "double", "",200')
937                 file.write('\n\t\t\tProperty: "UseAntialiasing", "bool", "",0')
938                 file.write('\n\t\t\tProperty: "AntialiasingIntensity", "double", "",0.77777')
939                 file.write('\n\t\t\tProperty: "UseAccumulationBuffer", "bool", "",0')
940                 file.write('\n\t\t\tProperty: "FrameSamplingCount", "int", "",7')
941                 file.write('\n\t\t}')
942                 file.write('\n\t\tMultiLayer: 0')
943                 file.write('\n\t\tMultiTake: 0')
944                 file.write('\n\t\tHidden: "True"')
945                 file.write('\n\t\tShading: Y')
946                 file.write('\n\t\tCulling: "CullingOff"')
947                 file.write('\n\t\tTypeFlags: "Camera"')
948                 file.write('\n\t\tGeometryVersion: 124')
949                 file.write('\n\t\tPosition: %.6f,%.6f,%.6f' % loc)
950                 file.write('\n\t\tUp: %i,%i,%i' % up)
951                 file.write('\n\t\tLookAt: 0,0,0')
952                 file.write('\n\t\tShowInfoOnMoving: 1')
953                 file.write('\n\t\tShowAudio: 0')
954                 file.write('\n\t\tAudioColor: 0,1,0')
955                 file.write('\n\t\tCameraOrthoZoom: 1')
956                 file.write('\n\t}')
957         
958         def write_camera_default():
959                 # This sucks but to match FBX converter its easier to
960                 # write the cameras though they are not needed.
961                 write_camera_dummy('Producer Perspective',      (0,71.3,287.5), 10, 4000, 0, (0,1,0))
962                 write_camera_dummy('Producer Top',                      (0,4000,0), 1, 30000, 1, (0,0,-1))
963                 write_camera_dummy('Producer Bottom',                   (0,-4000,0), 1, 30000, 1, (0,0,-1))
964                 write_camera_dummy('Producer Front',                    (0,0,4000), 1, 30000, 1, (0,1,0))
965                 write_camera_dummy('Producer Back',                     (0,0,-4000), 1, 30000, 1, (0,1,0))
966                 write_camera_dummy('Producer Right',                    (4000,0,0), 1, 30000, 1, (0,1,0))
967                 write_camera_dummy('Producer Left',                     (-4000,0,0), 1, 30000, 1, (0,1,0))
968         
969         def write_camera(my_cam):
970                 '''
971                 Write a blender camera
972                 '''
973                 render = sce.render_data
974                 width   = render.resolution_x
975                 height  = render.resolution_y
976 #               render = sce.render
977 #               width   = render.sizeX
978 #               height  = render.sizeY
979                 aspect  = float(width)/height
980                 
981                 data = my_cam.blenObject.data
982                 
983                 file.write('\n\tModel: "Model::%s", "Camera" {' % my_cam.fbxName )
984                 file.write('\n\t\tVersion: 232')
985                 loc, rot, scale, matrix, matrix_rot = write_object_props(my_cam.blenObject, None, my_cam.parRelMatrix())
986                 
987                 file.write('\n\t\t\tProperty: "Roll", "Roll", "A+",0')
988                 file.write('\n\t\t\tProperty: "FieldOfView", "FieldOfView", "A+",%.6f' % data.angle)
989                 file.write('\n\t\t\tProperty: "FieldOfViewX", "FieldOfView", "A+",1')
990                 file.write('\n\t\t\tProperty: "FieldOfViewY", "FieldOfView", "A+",1')
991                 file.write('\n\t\t\tProperty: "FocalLength", "Real", "A+",14.0323972702026')
992                 file.write('\n\t\t\tProperty: "OpticalCenterX", "Real", "A+",%.6f' % data.shift_x) # not sure if this is in the correct units?
993 #               file.write('\n\t\t\tProperty: "OpticalCenterX", "Real", "A+",%.6f' % data.shiftX) # not sure if this is in the correct units?
994                 file.write('\n\t\t\tProperty: "OpticalCenterY", "Real", "A+",%.6f' % data.shift_y) # ditto 
995 #               file.write('\n\t\t\tProperty: "OpticalCenterY", "Real", "A+",%.6f' % data.shiftY) # ditto 
996                 file.write('\n\t\t\tProperty: "BackgroundColor", "Color", "A+",0,0,0')
997                 file.write('\n\t\t\tProperty: "TurnTable", "Real", "A+",0')
998                 file.write('\n\t\t\tProperty: "DisplayTurnTableIcon", "bool", "",1')
999                 file.write('\n\t\t\tProperty: "Motion Blur Intensity", "Real", "A+",1')
1000                 file.write('\n\t\t\tProperty: "UseMotionBlur", "bool", "",0')
1001                 file.write('\n\t\t\tProperty: "UseRealTimeMotionBlur", "bool", "",1')
1002                 file.write('\n\t\t\tProperty: "ResolutionMode", "enum", "",0')
1003                 file.write('\n\t\t\tProperty: "ApertureMode", "enum", "",2')
1004                 file.write('\n\t\t\tProperty: "GateFit", "enum", "",0')
1005                 file.write('\n\t\t\tProperty: "CameraFormat", "enum", "",0')
1006                 file.write('\n\t\t\tProperty: "AspectW", "double", "",%i' % width)
1007                 file.write('\n\t\t\tProperty: "AspectH", "double", "",%i' % height)
1008                 
1009                 '''Camera aspect ratio modes.
1010                         0 If the ratio mode is eWINDOW_SIZE, both width and height values aren't relevant.
1011                         1 If the ratio mode is eFIXED_RATIO, the height value is set to 1.0 and the width value is relative to the height value.
1012                         2 If the ratio mode is eFIXED_RESOLUTION, both width and height values are in pixels.
1013                         3 If the ratio mode is eFIXED_WIDTH, the width value is in pixels and the height value is relative to the width value.
1014                         4 If the ratio mode is eFIXED_HEIGHT, the height value is in pixels and the width value is relative to the height value. 
1015                 
1016                 Definition at line 234 of file kfbxcamera.h. '''
1017                 
1018                 file.write('\n\t\t\tProperty: "PixelAspectRatio", "double", "",2')
1019                 
1020                 file.write('\n\t\t\tProperty: "UseFrameColor", "bool", "",0')
1021                 file.write('\n\t\t\tProperty: "FrameColor", "ColorRGB", "",0.3,0.3,0.3')
1022                 file.write('\n\t\t\tProperty: "ShowName", "bool", "",1')
1023                 file.write('\n\t\t\tProperty: "ShowGrid", "bool", "",1')
1024                 file.write('\n\t\t\tProperty: "ShowOpticalCenter", "bool", "",0')
1025                 file.write('\n\t\t\tProperty: "ShowAzimut", "bool", "",1')
1026                 file.write('\n\t\t\tProperty: "ShowTimeCode", "bool", "",0')
1027                 file.write('\n\t\t\tProperty: "NearPlane", "double", "",%.6f' % data.clip_start)
1028 #               file.write('\n\t\t\tProperty: "NearPlane", "double", "",%.6f' % data.clipStart)
1029                 file.write('\n\t\t\tProperty: "FarPlane", "double", "",%.6f' % data.clip_end)
1030 #               file.write('\n\t\t\tProperty: "FarPlane", "double", "",%.6f' % data.clipStart)
1031                 file.write('\n\t\t\tProperty: "FilmWidth", "double", "",1.0')
1032                 file.write('\n\t\t\tProperty: "FilmHeight", "double", "",1.0')
1033                 file.write('\n\t\t\tProperty: "FilmAspectRatio", "double", "",%.6f' % aspect)
1034                 file.write('\n\t\t\tProperty: "FilmSqueezeRatio", "double", "",1')
1035                 file.write('\n\t\t\tProperty: "FilmFormatIndex", "enum", "",0')
1036                 file.write('\n\t\t\tProperty: "ViewFrustum", "bool", "",1')
1037                 file.write('\n\t\t\tProperty: "ViewFrustumNearFarPlane", "bool", "",0')
1038                 file.write('\n\t\t\tProperty: "ViewFrustumBackPlaneMode", "enum", "",2')
1039                 file.write('\n\t\t\tProperty: "BackPlaneDistance", "double", "",100')
1040                 file.write('\n\t\t\tProperty: "BackPlaneDistanceMode", "enum", "",0')
1041                 file.write('\n\t\t\tProperty: "ViewCameraToLookAt", "bool", "",1')
1042                 file.write('\n\t\t\tProperty: "LockMode", "bool", "",0')
1043                 file.write('\n\t\t\tProperty: "LockInterestNavigation", "bool", "",0')
1044                 file.write('\n\t\t\tProperty: "FitImage", "bool", "",0')
1045                 file.write('\n\t\t\tProperty: "Crop", "bool", "",0')
1046                 file.write('\n\t\t\tProperty: "Center", "bool", "",1')
1047                 file.write('\n\t\t\tProperty: "KeepRatio", "bool", "",1')
1048                 file.write('\n\t\t\tProperty: "BackgroundMode", "enum", "",0')
1049                 file.write('\n\t\t\tProperty: "BackgroundAlphaTreshold", "double", "",0.5')
1050                 file.write('\n\t\t\tProperty: "ForegroundTransparent", "bool", "",1')
1051                 file.write('\n\t\t\tProperty: "DisplaySafeArea", "bool", "",0')
1052                 file.write('\n\t\t\tProperty: "SafeAreaDisplayStyle", "enum", "",1')
1053                 file.write('\n\t\t\tProperty: "SafeAreaAspectRatio", "double", "",%.6f' % aspect)
1054                 file.write('\n\t\t\tProperty: "Use2DMagnifierZoom", "bool", "",0')
1055                 file.write('\n\t\t\tProperty: "2D Magnifier Zoom", "Real", "A+",100')
1056                 file.write('\n\t\t\tProperty: "2D Magnifier X", "Real", "A+",50')
1057                 file.write('\n\t\t\tProperty: "2D Magnifier Y", "Real", "A+",50')
1058                 file.write('\n\t\t\tProperty: "CameraProjectionType", "enum", "",0')
1059                 file.write('\n\t\t\tProperty: "UseRealTimeDOFAndAA", "bool", "",0')
1060                 file.write('\n\t\t\tProperty: "UseDepthOfField", "bool", "",0')
1061                 file.write('\n\t\t\tProperty: "FocusSource", "enum", "",0')
1062                 file.write('\n\t\t\tProperty: "FocusAngle", "double", "",3.5')
1063                 file.write('\n\t\t\tProperty: "FocusDistance", "double", "",200')
1064                 file.write('\n\t\t\tProperty: "UseAntialiasing", "bool", "",0')
1065                 file.write('\n\t\t\tProperty: "AntialiasingIntensity", "double", "",0.77777')
1066                 file.write('\n\t\t\tProperty: "UseAccumulationBuffer", "bool", "",0')
1067                 file.write('\n\t\t\tProperty: "FrameSamplingCount", "int", "",7')
1068                 
1069                 file.write('\n\t\t}')
1070                 file.write('\n\t\tMultiLayer: 0')
1071                 file.write('\n\t\tMultiTake: 0')
1072                 file.write('\n\t\tShading: Y')
1073                 file.write('\n\t\tCulling: "CullingOff"')
1074                 file.write('\n\t\tTypeFlags: "Camera"')
1075                 file.write('\n\t\tGeometryVersion: 124')
1076                 file.write('\n\t\tPosition: %.6f,%.6f,%.6f' % loc)
1077                 file.write('\n\t\tUp: %.6f,%.6f,%.6f' % tuple(Mathutils.Vector(0,1,0) * matrix_rot) )
1078                 file.write('\n\t\tLookAt: %.6f,%.6f,%.6f' % tuple(Mathutils.Vector(0,0,-1)*matrix_rot) )
1079                 
1080                 #file.write('\n\t\tUp: 0,0,0' )
1081                 #file.write('\n\t\tLookAt: 0,0,0' )
1082                 
1083                 file.write('\n\t\tShowInfoOnMoving: 1')
1084                 file.write('\n\t\tShowAudio: 0')
1085                 file.write('\n\t\tAudioColor: 0,1,0')
1086                 file.write('\n\t\tCameraOrthoZoom: 1')
1087                 file.write('\n\t}')
1088         
1089         def write_light(my_light):
1090                 light = my_light.blenObject.data
1091                 file.write('\n\tModel: "Model::%s", "Light" {' % my_light.fbxName)
1092                 file.write('\n\t\tVersion: 232')
1093                 
1094                 write_object_props(my_light.blenObject, None, my_light.parRelMatrix())
1095                 
1096                 # Why are these values here twice?????? - oh well, follow the holy sdk's output
1097                 
1098                 # Blender light types match FBX's, funny coincidence, we just need to
1099                 # be sure that all unsupported types are made into a point light
1100                 #ePOINT, 
1101                 #eDIRECTIONAL
1102                 #eSPOT
1103                 light_type_items = {'POINT': 0, 'SUN': 1, 'SPOT': 2, 'HEMI': 3, 'AREA': 4}
1104                 light_type = light_type_items[light.type]
1105 #               light_type = light.type
1106                 if light_type > 2: light_type = 1 # hemi and area lights become directional
1107
1108 #               mode = light.mode
1109                 if light.shadow_method == 'RAY_SHADOW' or light.shadow_method == 'BUFFER_SHADOW':
1110 #               if mode & Blender.Lamp.Modes.RayShadow or mode & Blender.Lamp.Modes.Shadows:
1111                         do_shadow = 1
1112                 else:
1113                         do_shadow = 0
1114
1115                 if light.only_shadow or (not light.diffuse and not light.specular):
1116 #               if mode & Blender.Lamp.Modes.OnlyShadow or (mode & Blender.Lamp.Modes.NoDiffuse and mode & Blender.Lamp.Modes.NoSpecular):
1117                         do_light = 0
1118                 else:
1119                         do_light = 1
1120                 
1121                 scale = abs(GLOBAL_MATRIX.scalePart()[0]) # scale is always uniform in this case
1122                 
1123                 file.write('\n\t\t\tProperty: "LightType", "enum", "",%i' % light_type)
1124                 file.write('\n\t\t\tProperty: "CastLightOnObject", "bool", "",1')
1125                 file.write('\n\t\t\tProperty: "DrawVolumetricLight", "bool", "",1')
1126                 file.write('\n\t\t\tProperty: "DrawGroundProjection", "bool", "",1')
1127                 file.write('\n\t\t\tProperty: "DrawFrontFacingVolumetricLight", "bool", "",0')
1128                 file.write('\n\t\t\tProperty: "GoboProperty", "object", ""')
1129                 file.write('\n\t\t\tProperty: "Color", "Color", "A+",1,1,1')
1130                 file.write('\n\t\t\tProperty: "Intensity", "Intensity", "A+",%.2f' % (min(light.energy*100, 200))) # clamp below 200
1131                 if light.type == 'SPOT':
1132                         file.write('\n\t\t\tProperty: "Cone angle", "Cone angle", "A+",%.2f' % (light.spot_size * scale))
1133 #               file.write('\n\t\t\tProperty: "Cone angle", "Cone angle", "A+",%.2f' % (light.spotSize * scale))
1134                 file.write('\n\t\t\tProperty: "Fog", "Fog", "A+",50')
1135                 file.write('\n\t\t\tProperty: "Color", "Color", "A",%.2f,%.2f,%.2f' % tuple(light.color))
1136 #               file.write('\n\t\t\tProperty: "Color", "Color", "A",%.2f,%.2f,%.2f' % tuple(light.col))
1137                 file.write('\n\t\t\tProperty: "Intensity", "Intensity", "A+",%.2f' % (min(light.energy*100, 200))) # clamp below 200
1138
1139                 # duplication? see ^ (Arystan)
1140 #               file.write('\n\t\t\tProperty: "Cone angle", "Cone angle", "A+",%.2f' % (light.spotSize * scale))
1141                 file.write('\n\t\t\tProperty: "Fog", "Fog", "A+",50')
1142                 file.write('\n\t\t\tProperty: "LightType", "enum", "",%i' % light_type)
1143                 file.write('\n\t\t\tProperty: "CastLightOnObject", "bool", "",%i' % do_light)
1144                 file.write('\n\t\t\tProperty: "DrawGroundProjection", "bool", "",1')
1145                 file.write('\n\t\t\tProperty: "DrawFrontFacingVolumetricLight", "bool", "",0')
1146                 file.write('\n\t\t\tProperty: "DrawVolumetricLight", "bool", "",1')
1147                 file.write('\n\t\t\tProperty: "GoboProperty", "object", ""')
1148                 file.write('\n\t\t\tProperty: "DecayType", "enum", "",0')
1149                 file.write('\n\t\t\tProperty: "DecayStart", "double", "",%.2f' % light.distance)
1150 #               file.write('\n\t\t\tProperty: "DecayStart", "double", "",%.2f' % light.dist)
1151                 file.write('\n\t\t\tProperty: "EnableNearAttenuation", "bool", "",0')
1152                 file.write('\n\t\t\tProperty: "NearAttenuationStart", "double", "",0')
1153                 file.write('\n\t\t\tProperty: "NearAttenuationEnd", "double", "",0')
1154                 file.write('\n\t\t\tProperty: "EnableFarAttenuation", "bool", "",0')
1155                 file.write('\n\t\t\tProperty: "FarAttenuationStart", "double", "",0')
1156                 file.write('\n\t\t\tProperty: "FarAttenuationEnd", "double", "",0')
1157                 file.write('\n\t\t\tProperty: "CastShadows", "bool", "",%i' % do_shadow)
1158                 file.write('\n\t\t\tProperty: "ShadowColor", "ColorRGBA", "",0,0,0,1')
1159                 file.write('\n\t\t}')
1160                 file.write('\n\t\tMultiLayer: 0')
1161                 file.write('\n\t\tMultiTake: 0')
1162                 file.write('\n\t\tShading: Y')
1163                 file.write('\n\t\tCulling: "CullingOff"')
1164                 file.write('\n\t\tTypeFlags: "Light"')
1165                 file.write('\n\t\tGeometryVersion: 124')
1166                 file.write('\n\t}')
1167         
1168         # matrixOnly is not used at the moment
1169         def write_null(my_null = None, fbxName = None, matrixOnly = None):
1170                 # ob can be null
1171                 if not fbxName: fbxName = my_null.fbxName
1172                 
1173                 file.write('\n\tModel: "Model::%s", "Null" {' % fbxName)
1174                 file.write('\n\t\tVersion: 232')
1175                 
1176                 # only use this for the root matrix at the moment
1177                 if matrixOnly:
1178                         poseMatrix = write_object_props(None, None, matrixOnly)[3]
1179                 
1180                 else: # all other Null's
1181                         if my_null:             poseMatrix = write_object_props(my_null.blenObject, None, my_null.parRelMatrix())[3]
1182                         else:                   poseMatrix = write_object_props()[3]
1183                 
1184                 pose_items.append((fbxName, poseMatrix))
1185                 
1186                 file.write('''
1187                 }
1188                 MultiLayer: 0
1189                 MultiTake: 1
1190                 Shading: Y
1191                 Culling: "CullingOff"
1192                 TypeFlags: "Null"
1193         }''')
1194         
1195         # Material Settings
1196         if world:       world_amb = tuple(world.ambient_color)
1197 #       if world:       world_amb = world.getAmb()
1198         else:           world_amb = (0,0,0) # Default value
1199         
1200         def write_material(matname, mat):
1201                 file.write('\n\tMaterial: "Material::%s", "" {' % matname)
1202                 
1203                 # Todo, add more material Properties.
1204                 if mat:
1205                         mat_cold = tuple(mat.diffuse_color)
1206 #                       mat_cold = tuple(mat.rgbCol)
1207                         mat_cols = tuple(mat.specular_color)
1208 #                       mat_cols = tuple(mat.specCol)
1209                         #mat_colm = tuple(mat.mirCol) # we wont use the mirror color
1210                         mat_colamb = world_amb
1211 #                       mat_colamb = tuple([c for c in world_amb])
1212
1213                         mat_dif = mat.diffuse_reflection
1214 #                       mat_dif = mat.ref
1215                         mat_amb = mat.ambient
1216 #                       mat_amb = mat.amb
1217                         mat_hard = (float(mat.specular_hardness)-1)/5.10
1218 #                       mat_hard = (float(mat.hard)-1)/5.10
1219                         mat_spec = mat.specular_reflection/2.0
1220 #                       mat_spec = mat.spec/2.0
1221                         mat_alpha = mat.alpha
1222                         mat_emit = mat.emit
1223                         mat_shadeless = mat.shadeless
1224 #                       mat_shadeless = mat.mode & Blender.Material.Modes.SHADELESS
1225                         if mat_shadeless:
1226                                 mat_shader = 'Lambert'
1227                         else:
1228                                 if mat.diffuse_shader == 'LAMBERT':
1229 #                               if mat.diffuseShader == Blender.Material.Shaders.DIFFUSE_LAMBERT:
1230                                         mat_shader = 'Lambert'
1231                                 else:
1232                                         mat_shader = 'Phong'
1233                 else:
1234                         mat_cols = mat_cold = 0.8, 0.8, 0.8
1235                         mat_colamb = 0.0,0.0,0.0
1236                         # mat_colm 
1237                         mat_dif = 1.0
1238                         mat_amb = 0.5
1239                         mat_hard = 20.0
1240                         mat_spec = 0.2
1241                         mat_alpha = 1.0
1242                         mat_emit = 0.0
1243                         mat_shadeless = False
1244                         mat_shader = 'Phong'
1245                 
1246                 file.write('\n\t\tVersion: 102')
1247                 file.write('\n\t\tShadingModel: "%s"' % mat_shader.lower())
1248                 file.write('\n\t\tMultiLayer: 0')
1249                 
1250                 file.write('\n\t\tProperties60:  {')
1251                 file.write('\n\t\t\tProperty: "ShadingModel", "KString", "", "%s"' % mat_shader)
1252                 file.write('\n\t\t\tProperty: "MultiLayer", "bool", "",0')
1253                 file.write('\n\t\t\tProperty: "EmissiveColor", "ColorRGB", "",%.4f,%.4f,%.4f' % mat_cold) # emit and diffuse color are he same in blender
1254                 file.write('\n\t\t\tProperty: "EmissiveFactor", "double", "",%.4f' % mat_emit)
1255                 
1256                 file.write('\n\t\t\tProperty: "AmbientColor", "ColorRGB", "",%.4f,%.4f,%.4f' % mat_colamb)
1257                 file.write('\n\t\t\tProperty: "AmbientFactor", "double", "",%.4f' % mat_amb)
1258                 file.write('\n\t\t\tProperty: "DiffuseColor", "ColorRGB", "",%.4f,%.4f,%.4f' % mat_cold)
1259                 file.write('\n\t\t\tProperty: "DiffuseFactor", "double", "",%.4f' % mat_dif)
1260                 file.write('\n\t\t\tProperty: "Bump", "Vector3D", "",0,0,0')
1261                 file.write('\n\t\t\tProperty: "TransparentColor", "ColorRGB", "",1,1,1')
1262                 file.write('\n\t\t\tProperty: "TransparencyFactor", "double", "",%.4f' % (1.0 - mat_alpha))
1263                 if not mat_shadeless:
1264                         file.write('\n\t\t\tProperty: "SpecularColor", "ColorRGB", "",%.4f,%.4f,%.4f' % mat_cols)
1265                         file.write('\n\t\t\tProperty: "SpecularFactor", "double", "",%.4f' % mat_spec)
1266                         file.write('\n\t\t\tProperty: "ShininessExponent", "double", "",80.0')
1267                         file.write('\n\t\t\tProperty: "ReflectionColor", "ColorRGB", "",0,0,0')
1268                         file.write('\n\t\t\tProperty: "ReflectionFactor", "double", "",1')
1269                 file.write('\n\t\t\tProperty: "Emissive", "ColorRGB", "",0,0,0')
1270                 file.write('\n\t\t\tProperty: "Ambient", "ColorRGB", "",%.1f,%.1f,%.1f' % mat_colamb)
1271                 file.write('\n\t\t\tProperty: "Diffuse", "ColorRGB", "",%.1f,%.1f,%.1f' % mat_cold)
1272                 if not mat_shadeless:
1273                         file.write('\n\t\t\tProperty: "Specular", "ColorRGB", "",%.1f,%.1f,%.1f' % mat_cols)
1274                         file.write('\n\t\t\tProperty: "Shininess", "double", "",%.1f' % mat_hard)
1275                 file.write('\n\t\t\tProperty: "Opacity", "double", "",%.1f' % mat_alpha)
1276                 if not mat_shadeless:
1277                         file.write('\n\t\t\tProperty: "Reflectivity", "double", "",0')
1278
1279                 file.write('\n\t\t}')
1280                 file.write('\n\t}')
1281
1282         def copy_image(image):
1283
1284                 rel = image.get_export_path(basepath, True)
1285                 base = os.path.basename(rel)
1286
1287                 if EXP_IMAGE_COPY:
1288                         src = bpy.sys.expandpath(image.filename)
1289                         absp = image.get_export_path(basepath, False)
1290                         if not os.path.exists(absp):
1291                                 shutil.copy(src, absp)
1292
1293                 return (rel, base)
1294
1295         # tex is an Image (Arystan)
1296         def write_video(texname, tex):
1297                 # Same as texture really!
1298                 file.write('\n\tVideo: "Video::%s", "Clip" {' % texname)
1299                 
1300                 file.write('''
1301                 Type: "Clip"
1302                 Properties60:  {
1303                         Property: "FrameRate", "double", "",0
1304                         Property: "LastFrame", "int", "",0
1305                         Property: "Width", "int", "",0
1306                         Property: "Height", "int", "",0''')
1307                 if tex:
1308                         fname_rel, fname_strip = copy_image(tex)
1309 #                       fname, fname_strip, fname_rel = derived_paths(tex.filename, basepath, EXP_IMAGE_COPY)
1310                 else:
1311                         fname = fname_strip = fname_rel = ''
1312                 
1313                 file.write('\n\t\t\tProperty: "Path", "charptr", "", "%s"' % fname_strip)
1314                 
1315                 
1316                 file.write('''
1317                         Property: "StartFrame", "int", "",0
1318                         Property: "StopFrame", "int", "",0
1319                         Property: "PlaySpeed", "double", "",1
1320                         Property: "Offset", "KTime", "",0
1321                         Property: "InterlaceMode", "enum", "",0
1322                         Property: "FreeRunning", "bool", "",0
1323                         Property: "Loop", "bool", "",0
1324                         Property: "AccessMode", "enum", "",0
1325                 }
1326                 UseMipMap: 0''')
1327                 
1328                 file.write('\n\t\tFilename: "%s"' % fname_strip)
1329                 if fname_strip: fname_strip = '/' + fname_strip
1330                 file.write('\n\t\tRelativeFilename: "%s"' % fname_rel) # make relative
1331                 file.write('\n\t}')
1332
1333         
1334         def write_texture(texname, tex, num):
1335                 # if tex == None then this is a dummy tex
1336                 file.write('\n\tTexture: "Texture::%s", "TextureVideoClip" {' % texname)
1337                 file.write('\n\t\tType: "TextureVideoClip"')
1338                 file.write('\n\t\tVersion: 202')
1339                 # TODO, rare case _empty_ exists as a name.
1340                 file.write('\n\t\tTextureName: "Texture::%s"' % texname)
1341                 
1342                 file.write('''
1343                 Properties60:  {
1344                         Property: "Translation", "Vector", "A+",0,0,0
1345                         Property: "Rotation", "Vector", "A+",0,0,0
1346                         Property: "Scaling", "Vector", "A+",1,1,1''')
1347                 file.write('\n\t\t\tProperty: "Texture alpha", "Number", "A+",%i' % num)
1348                 
1349                 
1350                 # WrapModeU/V 0==rep, 1==clamp, TODO add support
1351                 file.write('''
1352                         Property: "TextureTypeUse", "enum", "",0
1353                         Property: "CurrentTextureBlendMode", "enum", "",1
1354                         Property: "UseMaterial", "bool", "",0
1355                         Property: "UseMipMap", "bool", "",0
1356                         Property: "CurrentMappingType", "enum", "",0
1357                         Property: "UVSwap", "bool", "",0''')
1358
1359                 file.write('\n\t\t\tProperty: "WrapModeU", "enum", "",%i' % tex.clamp_x)
1360 #               file.write('\n\t\t\tProperty: "WrapModeU", "enum", "",%i' % tex.clampX)
1361                 file.write('\n\t\t\tProperty: "WrapModeV", "enum", "",%i' % tex.clamp_y)
1362 #               file.write('\n\t\t\tProperty: "WrapModeV", "enum", "",%i' % tex.clampY)
1363                 
1364                 file.write('''
1365                         Property: "TextureRotationPivot", "Vector3D", "",0,0,0
1366                         Property: "TextureScalingPivot", "Vector3D", "",0,0,0
1367                         Property: "VideoProperty", "object", ""
1368                 }''')
1369                 
1370                 file.write('\n\t\tMedia: "Video::%s"' % texname)
1371                 
1372                 if tex:
1373                         fname_rel, fname_strip = copy_image(tex)
1374 #                       fname, fname_strip, fname_rel = derived_paths(tex.filename, basepath, EXP_IMAGE_COPY)
1375                 else:
1376                         fname = fname_strip = fname_rel = ''
1377                 
1378                 file.write('\n\t\tFileName: "%s"' % fname_strip)
1379                 file.write('\n\t\tRelativeFilename: "%s"' % fname_rel) # need some make relative command
1380                 
1381                 file.write('''
1382                 ModelUVTranslation: 0,0
1383                 ModelUVScaling: 1,1
1384                 Texture_Alpha_Source: "None"
1385                 Cropping: 0,0,0,0
1386         }''')
1387
1388         def write_deformer_skin(obname):
1389                 '''
1390                 Each mesh has its own deformer
1391                 '''
1392                 file.write('\n\tDeformer: "Deformer::Skin %s", "Skin" {' % obname)
1393                 file.write('''
1394                 Version: 100
1395                 MultiLayer: 0
1396                 Type: "Skin"
1397                 Properties60:  {
1398                 }
1399                 Link_DeformAcuracy: 50
1400         }''')
1401         
1402         # in the example was 'Bip01 L Thigh_2'
1403         def write_sub_deformer_skin(my_mesh, my_bone, weights):
1404         
1405                 '''
1406                 Each subdeformer is spesific to a mesh, but the bone it links to can be used by many sub-deformers
1407                 So the SubDeformer needs the mesh-object name as a prefix to make it unique
1408                 
1409                 Its possible that there is no matching vgroup in this mesh, in that case no verts are in the subdeformer,
1410                 a but silly but dosnt really matter
1411                 '''
1412                 file.write('\n\tDeformer: "SubDeformer::Cluster %s %s", "Cluster" {' % (my_mesh.fbxName, my_bone.fbxName))
1413                 
1414                 file.write('''
1415                 Version: 100
1416                 MultiLayer: 0
1417                 Type: "Cluster"
1418                 Properties60:  {
1419                         Property: "SrcModel", "object", ""
1420                         Property: "SrcModelReference", "object", ""
1421                 }
1422                 UserData: "", ""''')
1423                 
1424                 # Support for bone parents
1425                 if my_mesh.fbxBoneParent:
1426                         if my_mesh.fbxBoneParent == my_bone:
1427                                 # TODO - this is a bit lazy, we could have a simple write loop
1428                                 # for this case because all weights are 1.0 but for now this is ok
1429                                 # Parent Bones arent used all that much anyway.
1430                                 vgroup_data = [(j, 1.0) for j in range(len(my_mesh.blenData.verts))]
1431                         else:
1432                                 # This bone is not a parent of this mesh object, no weights
1433                                 vgroup_data = []
1434                         
1435                 else:
1436                         # Normal weight painted mesh
1437                         if my_bone.blenName in weights[0]:
1438                                 # Before we used normalized wright list
1439                                 #vgroup_data = me.getVertsFromGroup(bone.name, 1)
1440                                 group_index = weights[0].index(my_bone.blenName)
1441                                 vgroup_data = [(j, weight[group_index]) for j, weight in enumerate(weights[1]) if weight[group_index]] 
1442                         else:
1443                                 vgroup_data = []
1444                 
1445                 file.write('\n\t\tIndexes: ')
1446                 
1447                 i = -1
1448                 for vg in vgroup_data:
1449                         if i == -1:
1450                                 file.write('%i'  % vg[0])
1451                                 i=0
1452                         else:
1453                                 if i==23:
1454                                         file.write('\n\t\t')
1455                                         i=0
1456                                 file.write(',%i' % vg[0])
1457                         i+=1
1458                 
1459                 file.write('\n\t\tWeights: ')
1460                 i = -1
1461                 for vg in vgroup_data:
1462                         if i == -1:
1463                                 file.write('%.8f'  % vg[1])
1464                                 i=0
1465                         else:
1466                                 if i==38:
1467                                         file.write('\n\t\t')
1468                                         i=0
1469                                 file.write(',%.8f' % vg[1])
1470                         i+=1
1471                 
1472                 if my_mesh.fbxParent:
1473                         # TODO FIXME, this case is broken in some cases. skinned meshes just shouldnt have parents where possible!
1474                         m = mtx4_z90 * (my_bone.restMatrix * my_bone.fbxArm.matrixWorld.copy() * my_mesh.matrixWorld.copy().invert() )
1475                 else:
1476                         # Yes! this is it...  - but dosnt work when the mesh is a.
1477                         m = mtx4_z90 * (my_bone.restMatrix * my_bone.fbxArm.matrixWorld.copy() * my_mesh.matrixWorld.copy().invert() )
1478                 
1479                 #m = mtx4_z90 * my_bone.restMatrix
1480                 matstr = mat4x4str(m)
1481                 matstr_i = mat4x4str(m.invert())
1482                 
1483                 file.write('\n\t\tTransform: %s' % matstr_i) # THIS IS __NOT__ THE GLOBAL MATRIX AS DOCUMENTED :/
1484                 file.write('\n\t\tTransformLink: %s' % matstr)
1485                 file.write('\n\t}')
1486         
1487         def write_mesh(my_mesh):
1488                 
1489                 me = my_mesh.blenData
1490                 
1491                 # if there are non NULL materials on this mesh
1492                 if my_mesh.blenMaterials:       do_materials = True
1493                 else:                                           do_materials = False
1494                 
1495                 if my_mesh.blenTextures:        do_textures = True
1496                 else:                                           do_textures = False     
1497                 
1498                 do_uvs = len(me.uv_textures) > 0
1499 #               do_uvs = me.faceUV
1500                 
1501                 
1502                 file.write('\n\tModel: "Model::%s", "Mesh" {' % my_mesh.fbxName)
1503                 file.write('\n\t\tVersion: 232') # newline is added in write_object_props
1504                 
1505                 poseMatrix = write_object_props(my_mesh.blenObject, None, my_mesh.parRelMatrix())[3]
1506                 pose_items.append((my_mesh.fbxName, poseMatrix))
1507                 
1508                 file.write('\n\t\t}')
1509                 file.write('\n\t\tMultiLayer: 0')
1510                 file.write('\n\t\tMultiTake: 1')
1511                 file.write('\n\t\tShading: Y')
1512                 file.write('\n\t\tCulling: "CullingOff"')
1513                 
1514                 
1515                 # Write the Real Mesh data here
1516                 file.write('\n\t\tVertices: ')
1517                 i=-1
1518                 
1519                 for v in me.verts:
1520                         if i==-1:
1521                                 file.write('%.6f,%.6f,%.6f' % tuple(v.co));     i=0
1522                         else:
1523                                 if i==7:
1524                                         file.write('\n\t\t');   i=0
1525                                 file.write(',%.6f,%.6f,%.6f'% tuple(v.co))
1526                         i+=1
1527                                 
1528                 file.write('\n\t\tPolygonVertexIndex: ')
1529                 i=-1
1530                 for f in me.faces:
1531                         fi = [v_index for j, v_index in enumerate(f.verts) if v_index != 0 or j != 3]
1532 #                       fi = [v.index for v in f]
1533
1534                         # flip the last index, odd but it looks like
1535                         # this is how fbx tells one face from another
1536                         fi[-1] = -(fi[-1]+1)
1537                         fi = tuple(fi)
1538                         if i==-1:
1539                                 if len(fi) == 3:        file.write('%i,%i,%i' % fi )
1540 #                               if len(f) == 3:         file.write('%i,%i,%i' % fi )
1541                                 else:                           file.write('%i,%i,%i,%i' % fi )
1542                                 i=0
1543                         else:
1544                                 if i==13:
1545                                         file.write('\n\t\t')
1546                                         i=0
1547                                 if len(fi) == 3:        file.write(',%i,%i,%i' % fi )
1548 #                               if len(f) == 3:         file.write(',%i,%i,%i' % fi )
1549                                 else:                           file.write(',%i,%i,%i,%i' % fi )
1550                         i+=1
1551                 
1552                 file.write('\n\t\tEdges: ')
1553                 i=-1
1554                 for ed in me.edges:
1555                                 if i==-1:
1556                                         file.write('%i,%i' % (ed.verts[0], ed.verts[1]))
1557 #                                       file.write('%i,%i' % (ed.v1.index, ed.v2.index))
1558                                         i=0
1559                                 else:
1560                                         if i==13:
1561                                                 file.write('\n\t\t')
1562                                                 i=0
1563                                         file.write(',%i,%i' % (ed.verts[0], ed.verts[1]))
1564 #                                       file.write(',%i,%i' % (ed.v1.index, ed.v2.index))
1565                                 i+=1
1566                 
1567                 file.write('\n\t\tGeometryVersion: 124')
1568                 
1569                 file.write('''
1570                 LayerElementNormal: 0 {
1571                         Version: 101
1572                         Name: ""
1573                         MappingInformationType: "ByVertice"
1574                         ReferenceInformationType: "Direct"
1575                         Normals: ''')
1576                 
1577                 i=-1
1578                 for v in me.verts:
1579                         if i==-1:
1580                                 file.write('%.15f,%.15f,%.15f' % tuple(v.normal));      i=0
1581 #                               file.write('%.15f,%.15f,%.15f' % tuple(v.no));  i=0
1582                         else:
1583                                 if i==2:
1584                                         file.write('\n                   ');    i=0
1585                                 file.write(',%.15f,%.15f,%.15f' % tuple(v.normal))
1586 #                               file.write(',%.15f,%.15f,%.15f' % tuple(v.no))
1587                         i+=1
1588                 file.write('\n\t\t}')
1589                 
1590                 # Write Face Smoothing
1591                 file.write('''
1592                 LayerElementSmoothing: 0 {
1593                         Version: 102
1594                         Name: ""
1595                         MappingInformationType: "ByPolygon"
1596                         ReferenceInformationType: "Direct"
1597                         Smoothing: ''')
1598                 
1599                 i=-1
1600                 for f in me.faces:
1601                         if i==-1:
1602                                 file.write('%i' % f.smooth);    i=0
1603                         else:
1604                                 if i==54:
1605                                         file.write('\n                   ');    i=0
1606                                 file.write(',%i' % f.smooth)
1607                         i+=1
1608                 
1609                 file.write('\n\t\t}')
1610                 
1611                 # Write Edge Smoothing
1612                 file.write('''
1613                 LayerElementSmoothing: 0 {
1614                         Version: 101
1615                         Name: ""
1616                         MappingInformationType: "ByEdge"
1617                         ReferenceInformationType: "Direct"
1618                         Smoothing: ''')
1619                 
1620 #               SHARP = Blender.Mesh.EdgeFlags.SHARP
1621                 i=-1
1622                 for ed in me.edges:
1623                         if i==-1:
1624                                 file.write('%i' % (ed.sharp));  i=0
1625 #                               file.write('%i' % ((ed.flag&SHARP)!=0));        i=0
1626                         else:
1627                                 if i==54:
1628                                         file.write('\n                   ');    i=0
1629                                 file.write(',%i' % (ed.sharp))
1630 #                               file.write(',%i' % ((ed.flag&SHARP)!=0))
1631                         i+=1
1632                 
1633                 file.write('\n\t\t}')
1634 #               del SHARP
1635
1636                 # small utility function
1637                 # returns a slice of data depending on number of face verts
1638                 # data is either a MeshTextureFace or MeshColor
1639                 def face_data(data, face):
1640                         if f.verts[3] == 0:
1641                                 totvert = 3
1642                         else:
1643                                 totvert = 4
1644                                                 
1645                         return data[:totvert]
1646
1647                 
1648                 # Write VertexColor Layers
1649                 # note, no programs seem to use this info :/
1650                 collayers = []
1651                 if len(me.vertex_colors):
1652 #               if me.vertexColors:
1653                         collayers = me.vertex_colors
1654 #                       collayers = me.getColorLayerNames()
1655                         collayer_orig = me.active_vertex_color
1656 #                       collayer_orig = me.activeColorLayer
1657                         for colindex, collayer in enumerate(collayers):
1658 #                               me.activeColorLayer = collayer
1659                                 file.write('\n\t\tLayerElementColor: %i {' % colindex)
1660                                 file.write('\n\t\t\tVersion: 101')
1661                                 file.write('\n\t\t\tName: "%s"' % collayer.name)
1662 #                               file.write('\n\t\t\tName: "%s"' % collayer)
1663                                 
1664                                 file.write('''
1665                         MappingInformationType: "ByPolygonVertex"
1666                         ReferenceInformationType: "IndexToDirect"
1667                         Colors: ''')
1668                         
1669                                 i = -1
1670                                 ii = 0 # Count how many Colors we write
1671
1672                                 for f, cf in zip(me.faces, collayer.data):
1673                                         colors = [cf.color1, cf.color2, cf.color3, cf.color4]
1674
1675                                         # determine number of verts
1676                                         colors = face_data(colors, f)
1677
1678                                         for col in colors:
1679                                                 if i==-1:
1680                                                         file.write('%.4f,%.4f,%.4f,1' % tuple(col))
1681                                                         i=0
1682                                                 else:
1683                                                         if i==7:
1684                                                                 file.write('\n\t\t\t\t')
1685                                                                 i=0
1686                                                         file.write(',%.4f,%.4f,%.4f,1' % tuple(col))
1687                                                 i+=1
1688                                                 ii+=1 # One more Color
1689
1690 #                               for f in me.faces:
1691 #                                       for col in f.col:
1692 #                                               if i==-1:
1693 #                                                       file.write('%.4f,%.4f,%.4f,1' % (col[0]/255.0, col[1]/255.0, col[2]/255.0))
1694 #                                                       i=0
1695 #                                               else:
1696 #                                                       if i==7:
1697 #                                                               file.write('\n\t\t\t\t')
1698 #                                                               i=0
1699 #                                                       file.write(',%.4f,%.4f,%.4f,1' % (col[0]/255.0, col[1]/255.0, col[2]/255.0))
1700 #                                               i+=1
1701 #                                               ii+=1 # One more Color
1702                                 
1703                                 file.write('\n\t\t\tColorIndex: ')
1704                                 i = -1
1705                                 for j in range(ii):
1706                                         if i == -1:
1707                                                 file.write('%i' % j)
1708                                                 i=0
1709                                         else:
1710                                                 if i==55:
1711                                                         file.write('\n\t\t\t\t')
1712                                                         i=0
1713                                                 file.write(',%i' % j)
1714                                         i+=1
1715                                 
1716                                 file.write('\n\t\t}')
1717                 
1718                 
1719                 
1720                 # Write UV and texture layers.
1721                 uvlayers = []
1722                 if do_uvs:
1723                         uvlayers = me.uv_textures
1724 #                       uvlayers = me.getUVLayerNames()
1725                         uvlayer_orig = me.active_uv_texture
1726 #                       uvlayer_orig = me.activeUVLayer
1727                         for uvindex, uvlayer in enumerate(me.uv_textures):
1728 #                       for uvindex, uvlayer in enumerate(uvlayers):
1729 #                               me.activeUVLayer = uvlayer
1730                                 file.write('\n\t\tLayerElementUV: %i {' % uvindex)
1731                                 file.write('\n\t\t\tVersion: 101')
1732                                 file.write('\n\t\t\tName: "%s"' % uvlayer.name)
1733 #                               file.write('\n\t\t\tName: "%s"' % uvlayer)
1734                                 
1735                                 file.write('''
1736                         MappingInformationType: "ByPolygonVertex"
1737                         ReferenceInformationType: "IndexToDirect"
1738                         UV: ''')
1739                         
1740                                 i = -1
1741                                 ii = 0 # Count how many UVs we write
1742                                 
1743                                 for f, uf in zip(me.faces, uvlayer.data):
1744 #                               for f in me.faces:
1745                                         uvs = [uf.uv1, uf.uv2, uf.uv3, uf.uv4]
1746                                         uvs = face_data(uvs, f)
1747                                         
1748                                         for uv in uvs:
1749 #                                       for uv in f.uv:
1750                                                 if i==-1:
1751                                                         file.write('%.6f,%.6f' % tuple(uv))
1752                                                         i=0
1753                                                 else:
1754                                                         if i==7:
1755                                                                 file.write('\n                   ')
1756                                                                 i=0
1757                                                         file.write(',%.6f,%.6f' % tuple(uv))
1758                                                 i+=1
1759                                                 ii+=1 # One more UV
1760                                 
1761                                 file.write('\n\t\t\tUVIndex: ')
1762                                 i = -1
1763                                 for j in range(ii):
1764                                         if i == -1:
1765                                                 file.write('%i'  % j)
1766                                                 i=0
1767                                         else:
1768                                                 if i==55:
1769                                                         file.write('\n\t\t\t\t')
1770                                                         i=0
1771                                                 file.write(',%i' % j)
1772                                         i+=1
1773                                 
1774                                 file.write('\n\t\t}')
1775                                 
1776                                 if do_textures:
1777                                         file.write('\n\t\tLayerElementTexture: %i {' % uvindex)
1778                                         file.write('\n\t\t\tVersion: 101')
1779                                         file.write('\n\t\t\tName: "%s"' % uvlayer.name)
1780 #                                       file.write('\n\t\t\tName: "%s"' % uvlayer)
1781                                         
1782                                         if len(my_mesh.blenTextures) == 1:
1783                                                 file.write('\n\t\t\tMappingInformationType: "AllSame"')
1784                                         else:
1785                                                 file.write('\n\t\t\tMappingInformationType: "ByPolygon"')
1786                                         
1787                                         file.write('\n\t\t\tReferenceInformationType: "IndexToDirect"')
1788                                         file.write('\n\t\t\tBlendMode: "Translucent"')
1789                                         file.write('\n\t\t\tTextureAlpha: 1')
1790                                         file.write('\n\t\t\tTextureId: ')
1791                                         
1792                                         if len(my_mesh.blenTextures) == 1:
1793                                                 file.write('0')
1794                                         else:
1795                                                 texture_mapping_local = {None:-1}
1796                                                 
1797                                                 i = 0 # 1 for dummy
1798                                                 for tex in my_mesh.blenTextures:
1799                                                         if tex: # None is set above
1800                                                                 texture_mapping_local[tex] = i
1801                                                                 i+=1
1802                                                 
1803                                                 i=-1
1804                                                 for f in uvlayer.data:
1805 #                                               for f in me.faces:
1806                                                         img_key = f.image
1807                                                         
1808                                                         if i==-1:
1809                                                                 i=0
1810                                                                 file.write( '%s' % texture_mapping_local[img_key])
1811                                                         else:
1812                                                                 if i==55:
1813                                                                         file.write('\n                   ')
1814                                                                         i=0
1815                                                                 
1816                                                                 file.write(',%s' % texture_mapping_local[img_key])
1817                                                         i+=1
1818                                 
1819                                 else:
1820                                         file.write('''
1821                 LayerElementTexture: 0 {
1822                         Version: 101
1823                         Name: ""
1824                         MappingInformationType: "NoMappingInformation"
1825                         ReferenceInformationType: "IndexToDirect"
1826                         BlendMode: "Translucent"
1827                         TextureAlpha: 1
1828                         TextureId: ''')
1829                                 file.write('\n\t\t}')
1830                         
1831 #                       me.activeUVLayer = uvlayer_orig
1832                         
1833                 # Done with UV/textures.
1834                 
1835                 if do_materials:
1836                         file.write('\n\t\tLayerElementMaterial: 0 {')
1837                         file.write('\n\t\t\tVersion: 101')
1838                         file.write('\n\t\t\tName: ""')
1839                         
1840                         if len(my_mesh.blenMaterials) == 1:
1841                                 file.write('\n\t\t\tMappingInformationType: "AllSame"')
1842                         else:
1843                                 file.write('\n\t\t\tMappingInformationType: "ByPolygon"')
1844                         
1845                         file.write('\n\t\t\tReferenceInformationType: "IndexToDirect"')
1846                         file.write('\n\t\t\tMaterials: ')
1847                         
1848                         if len(my_mesh.blenMaterials) == 1:
1849                                 file.write('0')
1850                         else:
1851                                 # Build a material mapping for this 
1852                                 material_mapping_local = {} # local-mat & tex : global index.
1853                                 
1854                                 for j, mat_tex_pair in enumerate(my_mesh.blenMaterials):
1855                                         material_mapping_local[mat_tex_pair] = j
1856                                 
1857                                 len_material_mapping_local = len(material_mapping_local)
1858                                 
1859                                 mats = my_mesh.blenMaterialList
1860
1861                                 if me.active_uv_texture:
1862                                         uv_faces = me.active_uv_texture.data
1863                                 else:
1864                                         uv_faces = [None] * len(me.faces)
1865                                 
1866                                 i=-1
1867                                 for f, uf in zip(me.faces, uv_faces):
1868 #                               for f in me.faces:
1869                                         try:    mat = mats[f.material_index]
1870 #                                       try:    mat = mats[f.mat]
1871                                         except:mat = None
1872                                         
1873                                         if do_uvs: tex = uf.image # WARNING - MULTI UV LAYER IMAGES NOT SUPPORTED :/
1874 #                                       if do_uvs: tex = f.image # WARNING - MULTI UV LAYER IMAGES NOT SUPPORTED :/
1875                                         else: tex = None
1876                                         
1877                                         if i==-1:
1878                                                 i=0
1879                                                 file.write( '%s' % (material_mapping_local[mat, tex])) # None for mat or tex is ok
1880                                         else:
1881                                                 if i==55:
1882                                                         file.write('\n\t\t\t\t')
1883                                                         i=0
1884                                                 
1885                                                 file.write(',%s' % (material_mapping_local[mat, tex]))
1886                                         i+=1
1887                         
1888                         file.write('\n\t\t}')
1889                 
1890                 file.write('''
1891                 Layer: 0 {
1892                         Version: 100
1893                         LayerElement:  {
1894                                 Type: "LayerElementNormal"
1895                                 TypedIndex: 0
1896                         }''')
1897                 
1898                 if do_materials:
1899                         file.write('''
1900                         LayerElement:  {
1901                                 Type: "LayerElementMaterial"
1902                                 TypedIndex: 0
1903                         }''')
1904                         
1905                 # Always write this
1906                 if do_textures:
1907                         file.write('''
1908                         LayerElement:  {
1909                                 Type: "LayerElementTexture"
1910                                 TypedIndex: 0
1911                         }''')
1912                 
1913                 if me.vertex_colors:
1914 #               if me.vertexColors:
1915                         file.write('''
1916                         LayerElement:  {
1917                                 Type: "LayerElementColor"
1918                                 TypedIndex: 0
1919                         }''')
1920                 
1921                 if do_uvs: # same as me.faceUV
1922                         file.write('''
1923                         LayerElement:  {
1924                                 Type: "LayerElementUV"
1925                                 TypedIndex: 0
1926                         }''')
1927                 
1928                 
1929                 file.write('\n\t\t}')
1930                 
1931                 if len(uvlayers) > 1:
1932                         for i in range(1, len(uvlayers)):
1933                                 
1934                                 file.write('\n\t\tLayer: %i {' % i)
1935                                 file.write('\n\t\t\tVersion: 100')
1936                                 
1937                                 file.write('''
1938                         LayerElement:  {
1939                                 Type: "LayerElementUV"''')
1940                                 
1941                                 file.write('\n\t\t\t\tTypedIndex: %i' % i)
1942                                 file.write('\n\t\t\t}')
1943                                 
1944                                 if do_textures:
1945                                         
1946                                         file.write('''
1947                         LayerElement:  {
1948                                 Type: "LayerElementTexture"''')
1949                                         
1950                                         file.write('\n\t\t\t\tTypedIndex: %i' % i)
1951                                         file.write('\n\t\t\t}')
1952                                 
1953                                 file.write('\n\t\t}')
1954                 
1955                 if len(collayers) > 1:
1956                         # Take into account any UV layers
1957                         layer_offset = 0
1958                         if uvlayers: layer_offset = len(uvlayers)-1
1959                         
1960                         for i in range(layer_offset, len(collayers)+layer_offset):
1961                                 file.write('\n\t\tLayer: %i {' % i)
1962                                 file.write('\n\t\t\tVersion: 100')
1963                                 
1964                                 file.write('''
1965                         LayerElement:  {
1966                                 Type: "LayerElementColor"''')
1967                                 
1968                                 file.write('\n\t\t\t\tTypedIndex: %i' % i)
1969                                 file.write('\n\t\t\t}')
1970                                 file.write('\n\t\t}')
1971                 file.write('\n\t}')
1972         
1973         def write_group(name):
1974                 file.write('\n\tGroupSelection: "GroupSelection::%s", "Default" {' % name)
1975                 
1976                 file.write('''
1977                 Properties60:  {
1978                         Property: "MultiLayer", "bool", "",0
1979                         Property: "Pickable", "bool", "",1
1980                         Property: "Transformable", "bool", "",1
1981                         Property: "Show", "bool", "",1
1982                 }
1983                 MultiLayer: 0
1984         }''')
1985         
1986         
1987         # add meshes here to clear because they are not used anywhere.
1988         meshes_to_clear = []
1989         
1990         ob_meshes = []  
1991         ob_lights = []
1992         ob_cameras = []
1993         # in fbx we export bones as children of the mesh
1994         # armatures not a part of a mesh, will be added to ob_arms
1995         ob_bones = [] 
1996         ob_arms = []
1997         ob_null = [] # emptys
1998         
1999         # List of types that have blender objects (not bones)
2000         ob_all_typegroups = [ob_meshes, ob_lights, ob_cameras, ob_arms, ob_null]
2001         
2002         groups = [] # blender groups, only add ones that have objects in the selections
2003         materials = {} # (mat, image) keys, should be a set()
2004         textures = {} # should be a set()
2005         
2006         tmp_ob_type = ob_type = None # incase no objects are exported, so as not to raise an error
2007         
2008         # if EXP_OBS_SELECTED is false, use sceens objects
2009         if not batch_objects:
2010                 if EXP_OBS_SELECTED:    tmp_objects = context.selected_objects
2011 #               if EXP_OBS_SELECTED:    tmp_objects = sce.objects.context
2012                 else:                                   tmp_objects = sce.objects
2013         else:
2014                 tmp_objects = batch_objects
2015         
2016         if EXP_ARMATURE:
2017                 # This is needed so applying modifiers dosnt apply the armature deformation, its also needed
2018                 # ...so mesh objects return their rest worldspace matrix when bone-parents are exported as weighted meshes.
2019                 # set every armature to its rest, backup the original values so we done mess up the scene
2020                 ob_arms_orig_rest = [arm.rest_position for arm in bpy.data.armatures]
2021 #               ob_arms_orig_rest = [arm.restPosition for arm in bpy.data.armatures]
2022                 
2023                 for arm in bpy.data.armatures:
2024                         arm.rest_position = True
2025 #                       arm.restPosition = True
2026                 
2027                 if ob_arms_orig_rest:
2028                         for ob_base in bpy.data.objects:
2029                                 #if ob_base.type == 'Armature':
2030                                 ob_base.make_display_list()
2031 #                               ob_base.makeDisplayList()
2032                                         
2033                         # This causes the makeDisplayList command to effect the mesh
2034                         sce.set_frame(sce.current_frame)
2035 #                       Blender.Set('curframe', Blender.Get('curframe'))
2036                         
2037         
2038         for ob_base in tmp_objects:
2039
2040                 # ignore dupli children
2041                 if ob_base.parent and ob_base.parent.dupli_type != 'NONE':
2042                         continue
2043
2044                 obs = [(ob_base, ob_base.matrix)]
2045                 if ob_base.dupli_type != 'NONE':
2046                         ob_base.create_dupli_list()
2047                         obs = [(dob.object, dob.matrix) for dob in ob_base.dupli_list]
2048
2049                 for ob, mtx in obs:
2050 #               for ob, mtx in BPyObject.getDerivedObjects(ob_base):
2051                         tmp_ob_type = ob.type
2052                         if tmp_ob_type == 'CAMERA':
2053 #                       if tmp_ob_type == 'Camera':
2054                                 if EXP_CAMERA:
2055                                         ob_cameras.append(my_object_generic(ob, mtx))
2056                         elif tmp_ob_type == 'LAMP':
2057 #                       elif tmp_ob_type == 'Lamp':
2058                                 if EXP_LAMP:
2059                                         ob_lights.append(my_object_generic(ob, mtx))
2060                         elif tmp_ob_type == 'ARMATURE':
2061 #                       elif tmp_ob_type == 'Armature':
2062                                 if EXP_ARMATURE:
2063                                         # TODO - armatures dont work in dupligroups!
2064                                         if ob not in ob_arms: ob_arms.append(ob)
2065                                         # ob_arms.append(ob) # replace later. was "ob_arms.append(sane_obname(ob), ob)"
2066                         elif tmp_ob_type == 'EMPTY':
2067 #                       elif tmp_ob_type == 'Empty':
2068                                 if EXP_EMPTY:
2069                                         ob_null.append(my_object_generic(ob, mtx))
2070                         elif EXP_MESH:
2071                                 origData = True
2072                                 if tmp_ob_type != 'MESH':
2073 #                               if tmp_ob_type != 'Mesh':
2074 #                                       me = bpy.data.meshes.new()
2075                                         try:    me = ob.create_mesh(True, 'PREVIEW')
2076 #                                       try:    me.getFromObject(ob)
2077                                         except: me = None
2078                                         if me:
2079                                                 meshes_to_clear.append( me )
2080                                                 mats = me.materials
2081                                                 origData = False
2082                                 else:
2083                                         # Mesh Type!
2084                                         if EXP_MESH_APPLY_MOD:
2085 #                                               me = bpy.data.meshes.new()
2086                                                 me = ob.create_mesh(True, 'PREVIEW')
2087 #                                               me.getFromObject(ob)
2088                                                 
2089                                                 # so we keep the vert groups
2090 #                                               if EXP_ARMATURE:
2091 #                                                       orig_mesh = ob.getData(mesh=1)
2092 #                                                       if orig_mesh.getVertGroupNames():
2093 #                                                               ob.copy().link(me)
2094 #                                                               # If new mesh has no vgroups we can try add if verts are teh same
2095 #                                                               if not me.getVertGroupNames(): # vgroups were not kept by the modifier
2096 #                                                                       if len(me.verts) == len(orig_mesh.verts):
2097 #                                                                               groupNames, vWeightDict = BPyMesh.meshWeight2Dict(orig_mesh)
2098 #                                                                               BPyMesh.dict2MeshWeight(me, groupNames, vWeightDict)
2099                                                 
2100                                                 # print ob, me, me.getVertGroupNames()
2101                                                 meshes_to_clear.append( me )
2102                                                 origData = False
2103                                                 mats = me.materials
2104                                         else:
2105                                                 me = ob.data
2106 #                                               me = ob.getData(mesh=1)
2107                                                 mats = me.materials
2108                                                 
2109 #                                               # Support object colors
2110 #                                               tmp_colbits = ob.colbits
2111 #                                               if tmp_colbits:
2112 #                                                       tmp_ob_mats = ob.getMaterials(1) # 1 so we get None's too.
2113 #                                                       for i in xrange(16):
2114 #                                                               if tmp_colbits & (1<<i):
2115 #                                                                       mats[i] = tmp_ob_mats[i]
2116 #                                                       del tmp_ob_mats
2117 #                                               del tmp_colbits
2118                                                         
2119                                         
2120                                 if me:
2121 #                                       # This WILL modify meshes in blender if EXP_MESH_APPLY_MOD is disabled.
2122 #                                       # so strictly this is bad. but only in rare cases would it have negative results
2123 #                                       # say with dupliverts the objects would rotate a bit differently
2124 #                                       if EXP_MESH_HQ_NORMALS:
2125 #                                               BPyMesh.meshCalcNormals(me) # high quality normals nice for realtime engines.
2126                                         
2127                                         texture_mapping_local = {}
2128                                         material_mapping_local = {}
2129                                         if len(me.uv_textures) > 0:
2130 #                                       if me.faceUV:
2131                                                 uvlayer_orig = me.active_uv_texture
2132 #                                               uvlayer_orig = me.activeUVLayer
2133                                                 for uvlayer in me.uv_textures:
2134 #                                               for uvlayer in me.getUVLayerNames():
2135 #                                                       me.activeUVLayer = uvlayer
2136                                                         for f, uf in zip(me.faces, uvlayer.data):
2137 #                                                       for f in me.faces:
2138                                                                 tex = uf.image
2139 #                                                               tex = f.image
2140                                                                 textures[tex] = texture_mapping_local[tex] = None
2141                                                                 
2142                                                                 try: mat = mats[f.material_index]
2143 #                                                               try: mat = mats[f.mat]
2144                                                                 except: mat = None
2145                                                                 
2146                                                                 materials[mat, tex] = material_mapping_local[mat, tex] = None # should use sets, wait for blender 2.5
2147                                                                         
2148                                                         
2149 #                                                       me.activeUVLayer = uvlayer_orig
2150                                         else:
2151                                                 for mat in mats:
2152                                                         # 2.44 use mat.lib too for uniqueness
2153                                                         materials[mat, None] = material_mapping_local[mat, None] = None
2154                                                 else:
2155                                                         materials[None, None] = None
2156                                         
2157                                         if EXP_ARMATURE:
2158                                                 armob = ob.find_armature()
2159                                                 blenParentBoneName = None
2160                                                 
2161                                                 # parent bone - special case
2162                                                 if (not armob) and ob.parent and ob.parent.type == 'ARMATURE' and \
2163                                                                 ob.parent_type == 'BONE':
2164 #                                               if (not armob) and ob.parent and ob.parent.type == 'Armature' and ob.parentType == Blender.Object.ParentTypes.BONE:
2165                                                         armob = ob.parent
2166                                                         blenParentBoneName = ob.parent_bone
2167 #                                                       blenParentBoneName = ob.parentbonename
2168                                                 
2169                                                         
2170                                                 if armob and armob not in ob_arms:
2171                                                         ob_arms.append(armob)
2172                                         
2173                                         else:
2174                                                 blenParentBoneName = armob = None
2175                                         
2176                                         my_mesh = my_object_generic(ob, mtx)
2177                                         my_mesh.blenData =              me
2178                                         my_mesh.origData =              origData
2179                                         my_mesh.blenMaterials = list(material_mapping_local.keys())
2180                                         my_mesh.blenMaterialList = mats
2181                                         my_mesh.blenTextures =  list(texture_mapping_local.keys())
2182                                         
2183                                         # if only 1 null texture then empty the list
2184                                         if len(my_mesh.blenTextures) == 1 and my_mesh.blenTextures[0] == None:
2185                                                 my_mesh.blenTextures = []
2186                                         
2187                                         my_mesh.fbxArm =        armob                                   # replace with my_object_generic armature instance later
2188                                         my_mesh.fbxBoneParent = blenParentBoneName      # replace with my_bone instance later
2189                                         
2190                                         ob_meshes.append( my_mesh )
2191
2192                 # not forgetting to free dupli_list
2193                 if ob_base.dupli_list: ob_base.free_dupli_list()
2194
2195
2196         if EXP_ARMATURE:
2197                 # now we have the meshes, restore the rest arm position
2198                 for i, arm in enumerate(bpy.data.armatures):
2199                         arm.rest_position = ob_arms_orig_rest[i]
2200 #                       arm.restPosition = ob_arms_orig_rest[i]
2201                         
2202                 if ob_arms_orig_rest:
2203                         for ob_base in bpy.data.objects:
2204                                 if ob_base.type == 'ARMATURE':
2205 #                               if ob_base.type == 'Armature':
2206                                         ob_base.make_display_list()
2207 #                                       ob_base.makeDisplayList()
2208                         # This causes the makeDisplayList command to effect the mesh
2209                         sce.set_frame(sce.current_frame)
2210 #                       Blender.Set('curframe', Blender.Get('curframe'))
2211         
2212         del tmp_ob_type, tmp_objects
2213         
2214         # now we have collected all armatures, add bones
2215         for i, ob in enumerate(ob_arms):
2216                 
2217                 ob_arms[i] = my_arm = my_object_generic(ob)
2218                 
2219                 my_arm.fbxBones =               []
2220                 my_arm.blenData =               ob.data
2221                 if ob.animation_data:
2222                         my_arm.blenAction =     ob.animation_data.action
2223                 else:
2224                         my_arm.blenAction = None
2225 #               my_arm.blenAction =             ob.action
2226                 my_arm.blenActionList = []
2227                 
2228                 # fbxName, blenderObject, my_bones, blenderActions
2229                 #ob_arms[i] = fbxArmObName, ob, arm_my_bones, (ob.action, [])
2230                 
2231                 for bone in my_arm.blenData.bones:
2232 #               for bone in my_arm.blenData.bones.values():
2233                         my_bone = my_bone_class(bone, my_arm)
2234                         my_arm.fbxBones.append( my_bone )
2235                         ob_bones.append( my_bone )
2236         
2237         # add the meshes to the bones and replace the meshes armature with own armature class
2238         #for obname, ob, mtx, me, mats, arm, armname in ob_meshes:
2239         for my_mesh in ob_meshes:
2240                 # Replace 
2241                 # ...this could be sped up with dictionary mapping but its unlikely for
2242                 # it ever to be a bottleneck - (would need 100+ meshes using armatures)
2243                 if my_mesh.fbxArm:
2244                         for my_arm in ob_arms:
2245                                 if my_arm.blenObject == my_mesh.fbxArm:
2246                                         my_mesh.fbxArm = my_arm
2247                                         break
2248                 
2249                 for my_bone in ob_bones:
2250                         
2251                         # The mesh uses this bones armature!
2252                         if my_bone.fbxArm == my_mesh.fbxArm:
2253                                 my_bone.blenMeshes[my_mesh.fbxName] = me
2254                                 
2255                                 
2256                                 # parent bone: replace bone names with our class instances
2257                                 # my_mesh.fbxBoneParent is None or a blender bone name initialy, replacing if the names match.
2258                                 if my_mesh.fbxBoneParent == my_bone.blenName:
2259                                         my_mesh.fbxBoneParent = my_bone
2260         
2261         bone_deformer_count = 0 # count how many bones deform a mesh
2262         my_bone_blenParent = None
2263         for my_bone in ob_bones:
2264                 my_bone_blenParent = my_bone.blenBone.parent
2265                 if my_bone_blenParent:
2266                         for my_bone_parent in ob_bones:
2267                                 # Note 2.45rc2 you can compare bones normally
2268                                 if my_bone_blenParent.name == my_bone_parent.blenName and my_bone.fbxArm == my_bone_parent.fbxArm:
2269                                         my_bone.parent = my_bone_parent
2270                                         break
2271                 
2272                 # Not used at the moment
2273                 # my_bone.calcRestMatrixLocal()
2274                 bone_deformer_count += len(my_bone.blenMeshes)
2275         
2276         del my_bone_blenParent 
2277         
2278         
2279         # Build blenObject -> fbxObject mapping
2280         # this is needed for groups as well as fbxParenting
2281 #       for ob in bpy.data.objects:     ob.tag = False
2282 #       bpy.data.objects.tag = False
2283
2284         # using a list of object names for tagging (Arystan)
2285         tagged_objects = []
2286
2287         tmp_obmapping = {}
2288         for ob_generic in ob_all_typegroups:
2289                 for ob_base in ob_generic:
2290                         tagged_objects.append(ob_base.blenObject.name)
2291 #                       ob_base.blenObject.tag = True
2292                         tmp_obmapping[ob_base.blenObject] = ob_base
2293         
2294         # Build Groups from objects we export
2295         for blenGroup in bpy.data.groups:
2296                 fbxGroupName = None
2297                 for ob in blenGroup.objects:
2298                         if ob.name in tagged_objects:
2299 #                       if ob.tag:
2300                                 if fbxGroupName == None:
2301                                         fbxGroupName = sane_groupname(blenGroup)
2302                                         groups.append((fbxGroupName, blenGroup))
2303                                 
2304                                 tmp_obmapping[ob].fbxGroupNames.append(fbxGroupName) # also adds to the objects fbxGroupNames
2305         
2306         groups.sort() # not really needed
2307         
2308         # Assign parents using this mapping
2309         for ob_generic in ob_all_typegroups:
2310                 for my_ob in ob_generic:
2311                         parent = my_ob.blenObject.parent
2312                         if parent and parent.name in tagged_objects: # does it exist and is it in the mapping
2313 #                       if parent and parent.tag: # does it exist and is it in the mapping
2314                                 my_ob.fbxParent = tmp_obmapping[parent]
2315         
2316         
2317         del tmp_obmapping
2318         # Finished finding groups we use
2319         
2320         
2321         materials =     [(sane_matname(mat_tex_pair), mat_tex_pair) for mat_tex_pair in materials.keys()]
2322         textures =      [(sane_texname(tex), tex) for tex in textures.keys()  if tex]
2323         materials.sort() # sort by name
2324         textures.sort()
2325         
2326         camera_count = 8
2327         file.write('''
2328
2329 ; Object definitions
2330 ;------------------------------------------------------------------
2331
2332 Definitions:  {
2333         Version: 100
2334         Count: %i''' % (\
2335                 1+1+camera_count+\
2336                 len(ob_meshes)+\
2337                 len(ob_lights)+\
2338                 len(ob_cameras)+\
2339                 len(ob_arms)+\
2340                 len(ob_null)+\
2341                 len(ob_bones)+\
2342                 bone_deformer_count+\
2343                 len(materials)+\
2344                 (len(textures)*2))) # add 1 for the root model 1 for global settings
2345         
2346         del bone_deformer_count
2347         
2348         file.write('''
2349         ObjectType: "Model" {
2350                 Count: %i
2351         }''' % (\
2352                 1+camera_count+\
2353                 len(ob_meshes)+\
2354                 len(ob_lights)+\
2355                 len(ob_cameras)+\
2356                 len(ob_arms)+\
2357                 len(ob_null)+\
2358                 len(ob_bones))) # add 1 for the root model
2359         
2360         file.write('''
2361         ObjectType: "Geometry" {
2362                 Count: %i
2363         }''' % len(ob_meshes))
2364         
2365         if materials:
2366                 file.write('''
2367         ObjectType: "Material" {
2368                 Count: %i
2369         }''' % len(materials))
2370         
2371         if textures:
2372                 file.write('''
2373         ObjectType: "Texture" {
2374                 Count: %i
2375         }''' % len(textures)) # add 1 for an empty tex
2376                 file.write('''
2377         ObjectType: "Video" {
2378                 Count: %i
2379         }''' % len(textures)) # add 1 for an empty tex
2380         
2381         tmp = 0
2382         # Add deformer nodes
2383         for my_mesh in ob_meshes:
2384                 if my_mesh.fbxArm:
2385                         tmp+=1
2386         
2387         # Add subdeformers
2388         for my_bone in ob_bones:
2389                 tmp += len(my_bone.blenMeshes)
2390         
2391         if tmp:
2392                 file.write('''
2393         ObjectType: "Deformer" {
2394                 Count: %i
2395         }''' % tmp)
2396         del tmp
2397         
2398         # we could avoid writing this possibly but for now just write it
2399         
2400         file.write('''
2401         ObjectType: "Pose" {
2402                 Count: 1
2403         }''')
2404         
2405         if groups:
2406                 file.write('''
2407         ObjectType: "GroupSelection" {
2408                 Count: %i
2409         }''' % len(groups))
2410         
2411         file.write('''
2412         ObjectType: "GlobalSettings" {
2413                 Count: 1
2414         }
2415 }''')
2416
2417         file.write('''
2418
2419 ; Object properties
2420 ;------------------------------------------------------------------
2421
2422 Objects:  {''')
2423         
2424         # To comply with other FBX FILES
2425         write_camera_switch()
2426         
2427         # Write the null object
2428         write_null(None, 'blend_root')# , GLOBAL_MATRIX) 
2429         
2430         for my_null in ob_null:
2431                 write_null(my_null)
2432         
2433         for my_arm in ob_arms:
2434                 write_null(my_arm)
2435         
2436         for my_cam in ob_cameras:
2437                 write_camera(my_cam)
2438
2439         for my_light in ob_lights:
2440                 write_light(my_light)
2441         
2442         for my_mesh in ob_meshes:
2443                 write_mesh(my_mesh)
2444
2445         #for bonename, bone, obname, me, armob in ob_bones:
2446         for my_bone in ob_bones:
2447                 write_bone(my_bone)
2448         
2449         write_camera_default()
2450         
2451         for matname, (mat, tex) in materials:
2452                 write_material(matname, mat) # We only need to have a material per image pair, but no need to write any image info into the material (dumb fbx standard)
2453         
2454         # each texture uses a video, odd
2455         for texname, tex in textures:
2456                 write_video(texname, tex)
2457         i = 0
2458         for texname, tex in textures:
2459                 write_texture(texname, tex, i)
2460                 i+=1
2461         
2462         for groupname, group in groups:
2463                 write_group(groupname)
2464         
2465         # NOTE - c4d and motionbuilder dont need normalized weights, but deep-exploration 5 does and (max?) do.
2466         
2467         # Write armature modifiers
2468         # TODO - add another MODEL? - because of this skin definition.
2469         for my_mesh in ob_meshes:
2470                 if my_mesh.fbxArm:
2471                         write_deformer_skin(my_mesh.fbxName)
2472                         
2473                         # Get normalized weights for temorary use
2474                         if my_mesh.fbxBoneParent:
2475                                 weights = None
2476                         else:
2477                                 weights = meshNormalizedWeights(my_mesh.blenObject)
2478 #                               weights = meshNormalizedWeights(my_mesh.blenData)
2479                         
2480                         #for bonename, bone, obname, bone_mesh, armob in ob_bones:
2481                         for my_bone in ob_bones:
2482                                 if me in iter(my_bone.blenMeshes.values()):
2483                                         write_sub_deformer_skin(my_mesh, my_bone, weights)
2484         
2485         # Write pose's really weired, only needed when an armature and mesh are used together
2486         # each by themselves dont need pose data. for now only pose meshes and bones
2487         
2488         file.write('''
2489         Pose: "Pose::BIND_POSES", "BindPose" {
2490                 Type: "BindPose"
2491                 Version: 100
2492                 Properties60:  {
2493                 }
2494                 NbPoseNodes: ''')
2495         file.write(str(len(pose_items)))
2496         
2497
2498         for fbxName, matrix in pose_items:
2499                 file.write('\n\t\tPoseNode:  {')
2500                 file.write('\n\t\t\tNode: "Model::%s"' % fbxName )
2501                 if matrix:              file.write('\n\t\t\tMatrix: %s' % mat4x4str(matrix))
2502                 else:                   file.write('\n\t\t\tMatrix: %s' % mat4x4str(mtx4_identity))
2503                 file.write('\n\t\t}')
2504         
2505         file.write('\n\t}')
2506         
2507         
2508         # Finish Writing Objects
2509         # Write global settings
2510         file.write('''
2511         GlobalSettings:  {
2512                 Version: 1000
2513                 Properties60:  {
2514                         Property: "UpAxis", "int", "",1
2515                         Property: "UpAxisSign", "int", "",1
2516                         Property: "FrontAxis", "int", "",2
2517                         Property: "FrontAxisSign", "int", "",1
2518                         Property: "CoordAxis", "int", "",0
2519                         Property: "CoordAxisSign", "int", "",1
2520                         Property: "UnitScaleFactor", "double", "",100
2521                 }
2522         }
2523 ''')    
2524         file.write('}')
2525         
2526         file.write('''
2527
2528 ; Object relations
2529 ;------------------------------------------------------------------
2530
2531 Relations:  {''')
2532
2533         file.write('\n\tModel: "Model::blend_root", "Null" {\n\t}')
2534
2535         for my_null in ob_null:
2536                 file.write('\n\tModel: "Model::%s", "Null" {\n\t}' % my_null.fbxName)
2537
2538         for my_arm in ob_arms:
2539                 file.write('\n\tModel: "Model::%s", "Null" {\n\t}' % my_arm.fbxName)
2540
2541         for my_mesh in ob_meshes:
2542                 file.write('\n\tModel: "Model::%s", "Mesh" {\n\t}' % my_mesh.fbxName)
2543
2544         # TODO - limbs can have the same name for multiple armatures, should prefix.
2545         #for bonename, bone, obname, me, armob in ob_bones:
2546         for my_bone in ob_bones:
2547                 file.write('\n\tModel: "Model::%s", "Limb" {\n\t}' % my_bone.fbxName)
2548         
2549         for my_cam in ob_cameras:
2550                 file.write('\n\tModel: "Model::%s", "Camera" {\n\t}' % my_cam.fbxName)
2551         
2552         for my_light in ob_lights:
2553                 file.write('\n\tModel: "Model::%s", "Light" {\n\t}' % my_light.fbxName)
2554         
2555         file.write('''
2556         Model: "Model::Producer Perspective", "Camera" {
2557         }
2558         Model: "Model::Producer Top", "Camera" {
2559         }
2560         Model: "Model::Producer Bottom", "Camera" {
2561         }
2562         Model: "Model::Producer Front", "Camera" {
2563         }
2564         Model: "Model::Producer Back", "Camera" {
2565         }
2566         Model: "Model::Producer Right", "Camera" {
2567         }
2568         Model: "Model::Producer Left", "Camera" {
2569         }
2570         Model: "Model::Camera Switcher", "CameraSwitcher" {
2571         }''')
2572         
2573         for matname, (mat, tex) in materials:
2574                 file.write('\n\tMaterial: "Material::%s", "" {\n\t}' % matname)
2575
2576         if textures:
2577                 for texname, tex in textures:
2578                         file.write('\n\tTexture: "Texture::%s", "TextureVideoClip" {\n\t}' % texname)
2579                 for texname, tex in textures:
2580                         file.write('\n\tVideo: "Video::%s", "Clip" {\n\t}' % texname)
2581
2582         # deformers - modifiers
2583         for my_mesh in ob_meshes:
2584                 if my_mesh.fbxArm:
2585                         file.write('\n\tDeformer: "Deformer::Skin %s", "Skin" {\n\t}' % my_mesh.fbxName)
2586         
2587         #for bonename, bone, obname, me, armob in ob_bones:
2588         for my_bone in ob_bones:
2589                 for fbxMeshObName in my_bone.blenMeshes: # .keys() - fbxMeshObName
2590                         # is this bone effecting a mesh?
2591                         file.write('\n\tDeformer: "SubDeformer::Cluster %s %s", "Cluster" {\n\t}' % (fbxMeshObName, my_bone.fbxName))
2592         
2593         # This should be at the end
2594         # file.write('\n\tPose: "Pose::BIND_POSES", "BindPose" {\n\t}')
2595         
2596         for groupname, group in groups:
2597                 file.write('\n\tGroupSelection: "GroupSelection::%s", "Default" {\n\t}' % groupname)
2598         
2599         file.write('\n}')
2600         file.write('''
2601
2602 ; Object connections
2603 ;------------------------------------------------------------------
2604
2605 Connections:  {''')
2606         
2607         # NOTE - The FBX SDK dosnt care about the order but some importers DO!
2608         # for instance, defining the material->mesh connection
2609         # before the mesh->blend_root crashes cinema4d
2610         
2611
2612         # write the fake root node
2613         file.write('\n\tConnect: "OO", "Model::blend_root", "Model::Scene"')
2614         
2615         for ob_generic in ob_all_typegroups: # all blender 'Object's we support
2616                 for my_ob in ob_generic:
2617                         if my_ob.fbxParent:
2618                                 file.write('\n\tConnect: "OO", "Model::%s", "Model::%s"' % (my_ob.fbxName, my_ob.fbxParent.fbxName))
2619                         else:
2620                                 file.write('\n\tConnect: "OO", "Model::%s", "Model::blend_root"' % my_ob.fbxName)
2621         
2622         if materials:
2623                 for my_mesh in ob_meshes:
2624                         # Connect all materials to all objects, not good form but ok for now.
2625                         for mat, tex in my_mesh.blenMaterials:
2626                                 if mat: mat_name = mat.name
2627                                 else:   mat_name = None
2628                                 
2629                                 if tex: tex_name = tex.name
2630                                 else:   tex_name = None
2631                                 
2632                                 file.write('\n\tConnect: "OO", "Material::%s", "Model::%s"' % (sane_name_mapping_mat[mat_name, tex_name], my_mesh.fbxName))
2633         
2634         if textures:
2635                 for my_mesh in ob_meshes:
2636                         if my_mesh.blenTextures:
2637                                 # file.write('\n\tConnect: "OO", "Texture::_empty_", "Model::%s"' % my_mesh.fbxName)
2638                                 for tex in my_mesh.blenTextures:
2639                                         if tex:
2640                                                 file.write('\n\tConnect: "OO", "Texture::%s", "Model::%s"' % (sane_name_mapping_tex[tex.name], my_mesh.fbxName))
2641                 
2642                 for texname, tex in textures:
2643                         file.write('\n\tConnect: "OO", "Video::%s", "Texture::%s"' % (texname, texname))
2644         
2645         for my_mesh in ob_meshes:
2646                 if my_mesh.fbxArm:
2647                         file.write('\n\tConnect: "OO", "Deformer::Skin %s", "Model::%s"' % (my_mesh.fbxName, my_mesh.fbxName))
2648         
2649         #for bonename, bone, obname, me, armob in ob_bones:
2650         for my_bone in ob_bones:
2651                 for fbxMeshObName in my_bone.blenMeshes: # .keys()
2652                         file.write('\n\tConnect: "OO", "SubDeformer::Cluster %s %s", "Deformer::Skin %s"' % (fbxMeshObName, my_bone.fbxName, fbxMeshObName))
2653         
2654         # limbs -> deformers
2655         # for bonename, bone, obname, me, armob in ob_bones:
2656         for my_bone in ob_bones:
2657                 for fbxMeshObName in my_bone.blenMeshes: # .keys()
2658                         file.write('\n\tConnect: "OO", "Model::%s", "SubDeformer::Cluster %s %s"' % (my_bone.fbxName, fbxMeshObName, my_bone.fbxName))
2659         
2660         
2661         #for bonename, bone, obname, me, armob in ob_bones:
2662         for my_bone in ob_bones:
2663                 # Always parent to armature now
2664                 if my_bone.parent:
2665                         file.write('\n\tConnect: "OO", "Model::%s", "Model::%s"' % (my_bone.fbxName, my_bone.parent.fbxName) )
2666                 else:
2667                         # the armature object is written as an empty and all root level bones connect to it
2668                         file.write('\n\tConnect: "OO", "Model::%s", "Model::%s"' % (my_bone.fbxName, my_bone.fbxArm.fbxName) )
2669         
2670         # groups
2671         if groups:
2672                 for ob_generic in ob_all_typegroups:
2673                         for ob_base in ob_generic:
2674                                 for fbxGroupName in ob_base.fbxGroupNames:
2675                                         file.write('\n\tConnect: "OO", "Model::%s", "GroupSelection::%s"' % (ob_base.fbxName, fbxGroupName))
2676         
2677         for my_arm in ob_arms:
2678                 file.write('\n\tConnect: "OO", "Model::%s", "Model::blend_root"' % my_arm.fbxName)
2679         
2680         file.write('\n}')
2681         
2682         
2683         # Needed for scene footer as well as animation
2684         render = sce.render_data
2685 #       render = sce.render
2686         
2687         # from the FBX sdk
2688         #define KTIME_ONE_SECOND        KTime (K_LONGLONG(46186158000))
2689         def fbx_time(t):
2690                 # 0.5 + val is the same as rounding.
2691                 return int(0.5 + ((t/fps) * 46186158000))
2692         
2693         fps = float(render.fps) 
2694         start = sce.start_frame
2695 #       start = render.sFrame
2696         end =   sce.end_frame
2697 #       end =   render.eFrame
2698         if end < start: start, end = end, start
2699         if start==end: ANIM_ENABLE = False
2700         
2701         # animations for these object types
2702         ob_anim_lists = ob_bones, ob_meshes, ob_null, ob_cameras, ob_lights, ob_arms
2703         
2704         if ANIM_ENABLE and [tmp for tmp in ob_anim_lists if tmp]:
2705                 
2706                 frame_orig = sce.current_frame
2707 #               frame_orig = Blender.Get('curframe')
2708                 
2709                 if ANIM_OPTIMIZE:
2710                         ANIM_OPTIMIZE_PRECISSION_FLOAT = 0.1 ** ANIM_OPTIMIZE_PRECISSION
2711                 
2712                 # default action, when no actions are avaioable
2713                 tmp_actions = [None] # None is the default action
2714                 blenActionDefault = None
2715                 action_lastcompat = None
2716
2717                 # instead of tagging
2718                 tagged_actions = []
2719                 
2720                 if ANIM_ACTION_ALL:
2721 #                       bpy.data.actions.tag = False
2722                         tmp_actions = list(bpy.data.actions)
2723                         
2724                         
2725                         # find which actions are compatible with the armatures
2726                         # blenActions is not yet initialized so do it now.
2727                         tmp_act_count = 0
2728                         for my_arm in ob_arms:
2729                                 
2730                                 # get the default name
2731                                 if not blenActionDefault:
2732                                         blenActionDefault = my_arm.blenAction
2733                                 
2734                                 arm_bone_names = set([my_bone.blenName for my_bone in my_arm.fbxBones])
2735                                 
2736                                 for action in tmp_actions:
2737
2738                                         action_chan_names = arm_bone_names.intersection( set([g.name for g in action.groups]) )
2739 #                                       action_chan_names = arm_bone_names.intersection( set(action.getChannelNames()) )
2740                                         
2741                                         if action_chan_names: # at least one channel matches.
2742                                                 my_arm.blenActionList.append(action)
2743                                                 tagged_actions.append(action.name)
2744 #                                               action.tag = True
2745                                                 tmp_act_count += 1
2746                                                 
2747                                                 # incase there is no actions applied to armatures
2748                                                 action_lastcompat = action
2749                         
2750                         if tmp_act_count:
2751                                 # unlikely to ever happen but if no actions applied to armatures, just use the last compatible armature.
2752                                 if not blenActionDefault:
2753                                         blenActionDefault = action_lastcompat
2754                 
2755                 del action_lastcompat
2756                 
2757                 file.write('''
2758 ;Takes and animation section
2759 ;----------------------------------------------------
2760
2761 Takes:  {''')
2762                 
2763                 if blenActionDefault:
2764                         file.write('\n\tCurrent: "%s"' % sane_takename(blenActionDefault))
2765                 else:
2766                         file.write('\n\tCurrent: "Default Take"')
2767                 
2768                 for blenAction in tmp_actions:
2769                         # we have tagged all actious that are used be selected armatures
2770                         if blenAction:
2771                                 if blenAction.name in tagged_actions:
2772 #                               if blenAction.tag:
2773                                         print('\taction: "%s" exporting...' % blenAction.name)
2774                                 else:
2775                                         print('\taction: "%s" has no armature using it, skipping' % blenAction.name)
2776                                         continue
2777                         
2778                         if blenAction == None:
2779                                 # Warning, this only accounts for tmp_actions being [None]
2780                                 file.write('\n\tTake: "Default Take" {')
2781                                 act_start =     start
2782                                 act_end =       end
2783                         else:
2784                                 # use existing name
2785                                 if blenAction == blenActionDefault: # have we alredy got the name
2786                                         file.write('\n\tTake: "%s" {' % sane_name_mapping_take[blenAction.name])
2787                                 else:
2788                                         file.write('\n\tTake: "%s" {' % sane_takename(blenAction))
2789
2790                                 act_start, act_end = blenAction.get_frame_range()
2791 #                               tmp = blenAction.getFrameNumbers()
2792 #                               if tmp:
2793 #                                       act_start =     min(tmp)
2794 #                                       act_end =       max(tmp)
2795 #                                       del tmp
2796 #                               else:
2797 #                                       # Fallback on this, theres not much else we can do? :/
2798 #                                       # when an action has no length
2799 #                                       act_start =     start
2800 #                                       act_end =       end
2801                                 
2802                                 # Set the action active
2803                                 for my_bone in ob_arms:
2804                                         if blenAction in my_bone.blenActionList:
2805