[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