[Bf-extensions-cvs] SVN commit: /data/svn/bf-extensions [4765] trunk/py/scripts/addons/ io_scene_fbx/export_fbx.py: Enhanced FBX export.

Bastien Montagne montagne29 at wanadoo.fr
Fri Sep 20 11:04:31 CEST 2013


Revision: 4765
          http://projects.blender.org/scm/viewvc.php?view=rev&root=bf-extensions&revision=4765
Author:   mont29
Date:     2013-09-20 09:04:31 +0000 (Fri, 20 Sep 2013)
Log Message:
-----------
Enhanced FBX export.
* Export polygons instead of tessellated tris/quads.
* Export split vertex normals.
* Better performances (about 20% gain at least with big files), and use much less bytes when exporting UVs/VCol!

Modified Paths:
--------------
    trunk/py/scripts/addons/io_scene_fbx/export_fbx.py

Modified: trunk/py/scripts/addons/io_scene_fbx/export_fbx.py
===================================================================
--- trunk/py/scripts/addons/io_scene_fbx/export_fbx.py	2013-09-20 06:21:10 UTC (rev 4764)
+++ trunk/py/scripts/addons/io_scene_fbx/export_fbx.py	2013-09-20 09:04:31 UTC (rev 4765)
@@ -28,6 +28,30 @@
 import bpy
 from mathutils import Vector, Matrix
 
+def grouper_exact(it, chunk_size):
+    """
+    Grouper-like func, but returns exactly all elements from it:
+
+    >>> for chunk in grouper_exact(range(10), 3): print(e)
+    (0,1,2)
+    (3,4,5)
+    (6,7,8)
+    (9,)
+
+    About 2 times slower than simple zip(*[it] * 3), but does not need to convert iterator to sequence to be sure to
+    get exactly all elements in it (i.e. get a last chunk that may be smaller than chunk_size).
+    """
+    import itertools
+    i = itertools.zip_longest(*[iter(it)] * chunk_size, fillvalue=...)
+    curr = next(i)
+    for nxt in i:
+        yield curr
+        curr = nxt
+    if ... in curr:
+        yield curr[:curr.index(...)]
+    else:
+        yield curr
+
 # I guess FBX uses degrees instead of radians (Arystan).
 # Call this function just before writing to FBX.
 # 180 / math.pi == 57.295779513
@@ -409,7 +433,7 @@
     # ----------------------------------------------
 
     print('\nFBX export starting... %r' % filepath)
-    start_time = time.clock()
+    start_time = time.process_time()
     try:
         file = open(filepath, "w", encoding="utf8", newline="\n")
     except:
@@ -1344,24 +1368,21 @@
         fw('\n\t}')
 
     def write_mesh(my_mesh):
-
         me = my_mesh.blenData
 
         # if there are non NULL materials on this mesh
         do_materials = bool(my_mesh.blenMaterials)
         do_textures = bool(my_mesh.blenTextures)
-        do_uvs = bool(me.tessface_uv_textures)
+        do_uvs = bool(me.uv_layers)
         do_shapekeys = (my_mesh.blenObject.type == 'MESH' and
                         my_mesh.blenObject.data.shape_keys and
                         len(my_mesh.blenObject.data.vertices) == len(me.vertices))
+        print(len(my_mesh.blenObject.data.vertices), len(me.vertices))
 
         fw('\n\tModel: "Model::%s", "Mesh" {' % my_mesh.fbxName)
         fw('\n\t\tVersion: 232')  # newline is added in write_object_props
 
         # convert into lists once.
-        me_vertices = me.vertices[:]
-        me_edges = me.edges[:] if use_mesh_edges else ()
-        me_faces = me.tessfaces[:]
 
         poseMatrix = write_object_props(my_mesh.blenObject, None, my_mesh.parRelMatrix())[3]
 
@@ -1384,151 +1405,89 @@
 
         # Write the Real Mesh data here
         fw('\n\t\tVertices: ')
-        i = -1
-        for v in me_vertices:
-            if i == -1:
-                fw('%.6f,%.6f,%.6f' % v.co[:])
-                i = 0
-            else:
-                if i == 7:
-                    fw('\n\t\t')
-                    i = 0
-                fw(',%.6f,%.6f,%.6f' % v.co[:])
-            i += 1
+        _nchunk = 12  # Number of coordinates per line.
+        t_co = [None] * len(me.vertices) * 3
+        me.vertices.foreach_get("co", t_co)
+        fw(',\n\t\t          '.join(','.join('%.6f' % co for co in chunk) for chunk in grouper_exact(t_co, _nchunk)))
+        del t_co
 
         fw('\n\t\tPolygonVertexIndex: ')
-        i = -1
-        for f in me_faces:
-            fi = f.vertices[:]
+        _nchunk = 32  # Number of indices per line.
+        # A bit more complicated, as we have to ^-1 last index of each loop.
+        # NOTE: Here we assume that loops order matches polygons order!
+        t_vi = [None] * len(me.loops)
+        me.loops.foreach_get("vertex_index", t_vi)
+        t_ls = [None] * len(me.polygons)
+        me.polygons.foreach_get("loop_start", t_ls)
+        if t_ls != sorted(t_ls):
+            print("Error: polygons and loops orders do not match!")
+        for ls in t_ls:
+            t_vi[ls - 1] ^= -1
+        prep = ',\n\t\t                    '
+        fw(prep.join(','.join('%i' % vi for vi in chunk) for chunk in grouper_exact(t_vi, _nchunk)))
+        del t_vi
+        del t_ls
 
-            # last index XORd w. -1 indicates end of face
-            if i == -1:
-                if len(fi) == 3:
-                    fw('%i,%i,%i' % (fi[0], fi[1], fi[2] ^ -1))
-                else:
-                    fw('%i,%i,%i,%i' % (fi[0], fi[1], fi[2], fi[3] ^ -1))
-                i = 0
-            else:
-                if i == 13:
-                    fw('\n\t\t')
-                    i = 0
-                if len(fi) == 3:
-                    fw(',%i,%i,%i' % (fi[0], fi[1], fi[2] ^ -1))
-                else:
-                    fw(',%i,%i,%i,%i' % (fi[0], fi[1], fi[2], fi[3] ^ -1))
-            i += 1
+        if use_mesh_edges:
+            t_vi = [None] * len(me.edges) * 2
+            me.edges.foreach_get("vertices", t_vi)
 
-        # write loose edges as faces.
-        for ed in me_edges:
-            if ed.is_loose:
-                ed_val = ed.vertices[:]
-                ed_val = ed_val[0], ed_val[-1] ^ -1
+            # write loose edges as faces.
+            t_el = [None] * len(me.edges)
+            me.edges.foreach_get("is_loose", t_el)
+            num_lose = sum(t_el)
+            if num_lose != 0:
+                it_el = ((vi ^ -1) if (idx % 2) else vi for idx, vi in enumerate(t_vi) if t_el[idx // 2])
+                if (len(me.loops)):
+                    fw(prep)
+                fw(prep.join(','.join('%i' % vi for vi in chunk) for chunk in grouper_exact(it_el, _nchunk)))
 
-                if i == -1:
-                    fw('%i,%i' % ed_val)
-                    i = 0
-                else:
-                    if i == 13:
-                        fw('\n\t\t')
-                        i = 0
-                    fw(',%i,%i' % ed_val)
-            i += 1
+            fw('\n\t\tEdges: ')
+            fw(',\n\t\t       '.join(','.join('%i' % vi for vi in chunk) for chunk in grouper_exact(t_vi, _nchunk)))
 
-        fw('\n\t\tEdges: ')
-        i = -1
-        for ed in me_edges:
-            if i == -1:
-                fw('%i,%i' % (ed.vertices[0], ed.vertices[1]))
-                i = 0
-            else:
-                if i == 13:
-                    fw('\n\t\t')
-                    i = 0
-                fw(',%i,%i' % (ed.vertices[0], ed.vertices[1]))
-            i += 1
-
         fw('\n\t\tGeometryVersion: 124')
 
-        fw('''
-        LayerElementNormal: 0 {
-            Version: 101
-            Name: ""
-            MappingInformationType: "ByPolygonVertex"
-            ReferenceInformationType: "Direct"
-            Normals: ''')
-
-        # this could be compacted further
-        i = -1
-        for f in me_faces:
-            fi = f.vertices[:]
-            if i != -1:
-                fw(',')  # ack!
-            elif i == 2:
-                fw('\n\t\t\t ')
-                i = 0
-
-            if f.use_smooth:
-                fw('%.6f,%.6f,%.6f' % me_vertices[fi[0]].normal[:])
-                fw(',%.6f,%.6f,%.6f' % me_vertices[fi[1]].normal[:])
-                fw(',%.6f,%.6f,%.6f' % me_vertices[fi[2]].normal[:])
-                if len(fi) == 4:
-                    fw(',%.6f,%.6f,%.6f' % me_vertices[fi[3]].normal[:])
-            else:
-                f_no = f.normal[:]
-                fw('%.6f,%.6f,%.6f' % f_no)
-                fw(',%.6f,%.6f,%.6f' % f_no)
-                fw(',%.6f,%.6f,%.6f' % f_no)
-                if len(fi) == 4:
-                    fw(',%.6f,%.6f,%.6f' % f_no)
-            i += 1
+        _nchunk = 12  # Number of coordinates per line.
+        t_vn = [None] * len(me.loops) * 3
+        me.calc_normals_split()
+        # NOTE: Here we assume that loops order matches polygons order!
+        me.loops.foreach_get("normal", t_vn)
+        fw('\n\t\tLayerElementNormal: 0 {'
+           '\n\t\t\tVersion: 101'
+           '\n\t\t\tName: ""'
+           '\n\t\t\tMappingInformationType: "ByPolygonVertex"'
+           '\n\t\t\tReferenceInformationType: "Direct"'  # We could save some space with IndexToDirect here too...
+           '\n\t\t\tNormals: ')
+        fw(',\n\t\t\t         '.join(','.join('%.6f' % n for n in chunk) for chunk in grouper_exact(t_vn, _nchunk)))
         fw('\n\t\t}')
+        del t_vn
+        me.free_normals_split()
 
         # Write Face Smoothing
+        _nchunk = 64  # Number of bool per line.
         if mesh_smooth_type == 'FACE':
-            fw('''
-		LayerElementSmoothing: 0 {
-			Version: 102
-			Name: ""
-			MappingInformationType: "ByPolygon"
-			ReferenceInformationType: "Direct"
-			Smoothing: ''')
-
-            i = -1
-            for f in me_faces:
-                if i == -1:
-                    fw('%i' % f.use_smooth)
-                    i = 0
-                else:
-                    if i == 54:
-                        fw('\n\t\t\t ')
-                        i = 0
-                    fw(',%i' % f.use_smooth)
-                i += 1
-
+            t_ps = [None] * len(me.polygons)
+            me.polygons.foreach_get("use_smooth", t_ps)
+            fw('\n\t\tLayerElementSmoothing: 0 {'
+               '\n\t\t\tVersion: 102'
+               '\n\t\t\tName: ""'
+               '\n\t\t\tMappingInformationType: "ByPolygon"'
+               '\n\t\t\tReferenceInformationType: "Direct"'
+               '\n\t\t\tSmoothing: ')
+            fw(',\n\t\t\t           '.join(','.join('%d' % b for b in chunk) for chunk in grouper_exact(t_ps, _nchunk)))
             fw('\n\t\t}')
-
         elif mesh_smooth_type == 'EDGE':
             # Write Edge Smoothing
-            fw('''
-		LayerElementSmoothing: 0 {
-			Version: 101
-			Name: ""
-			MappingInformationType: "ByEdge"
-			ReferenceInformationType: "Direct"
-			Smoothing: ''')
-
-            i = -1
-            for ed in me_edges:
-                if i == -1:
-                    fw('%i' % (not ed.use_edge_sharp))
-                    i = 0
-                else:
-                    if i == 54:
-                        fw('\n\t\t\t ')
-                        i = 0
-                    fw(',%i' % (not ed.use_edge_sharp))
-                i += 1
-
+            t_es = [None] * len(me.edges)
+            me.edges.foreach_get("use_edge_sharp", t_es)
+            fw('\n\t\tLayerElementSmoothing: 0 {'
+               '\n\t\t\tVersion: 101'
+               '\n\t\t\tName: ""'
+               '\n\t\t\tMappingInformationType: "ByEdge"'
+               '\n\t\t\tReferenceInformationType: "Direct"'

@@ Diff output truncated at 10240 characters. @@


More information about the Bf-extensions-cvs mailing list