[Bf-extensions-cvs] [85173fa5] master: PLY: binary export

Mikhail Rachinskiy noreply at git.blender.org
Wed Jul 22 05:31:20 CEST 2020


Commit: 85173fa5263a34924154f26c0c457dd5e79621ea
Author: Mikhail Rachinskiy
Date:   Wed Jul 22 07:28:43 2020 +0400
Branches: master
https://developer.blender.org/rBA85173fa5263a34924154f26c0c457dd5e79621ea

PLY: binary export

Thanks to Adrian Vogelsgesang (@vogelsgesang) and his binary ply export
implementation proposal D4252.
I did not reuse any code from his patch, but it gave me a good starting point
as I had no idea how to work with binary data.

In this commit:
* Implement export to binary little-endian file format.
* Remove information about blend filename from exported file, it has no purpose.
* Binary is the default format from now on.

I cannot see any reason to implement big-endian option, if there is, please let me know.
Below you will find performance comparison between ASCII and binary formats.

Test geometry:
* Verts 379 000
* Faces 378 000

Export:
* ASCII (old) 8.0 sec
* ASCII 3.0 sec
* Binary 2.4 sec

Note: difference between old and new ASCII export is due to avoiding
unnecessary normal claculation when export normals is disabled.

Import:
ASCII 5.75 sec
Binary 4.9 sec

File sizes:
* ASCII 20.6 MB
* Binary 10.4 MB

===================================================================

M	io_mesh_ply/__init__.py
M	io_mesh_ply/export_ply.py
M	io_mesh_ply/import_ply.py

===================================================================

diff --git a/io_mesh_ply/__init__.py b/io_mesh_ply/__init__.py
index 835ae60e..2cfb09c3 100644
--- a/io_mesh_ply/__init__.py
+++ b/io_mesh_ply/__init__.py
@@ -21,9 +21,9 @@
 bl_info = {
     "name": "Stanford PLY format",
     "author": "Bruce Merry, Campbell Barton",
-    "version": (1, 1, 0),
-    "blender": (2, 82, 0),
-    "location": "File > Import-Export",
+    "version": (2, 0, 0),
+    "blender": (2, 90, 0),
+    "location": "File > Import/Export",
     "description": "Import-Export PLY mesh data with UVs and vertex colors",
     "doc_url": "{BLENDER_MANUAL_URL}/addons/import_export/mesh_ply.html",
     "support": 'OFFICIAL',
@@ -107,6 +107,10 @@ class ExportPLY(bpy.types.Operator, ExportHelper):
     filename_ext = ".ply"
     filter_glob: StringProperty(default="*.ply", options={'HIDDEN'})
 
+    use_ascii: BoolProperty(
+        name="ASCII",
+        description="Export using ASCII file format, otherwise use binary",
+    )
     use_selection: BoolProperty(
         name="Selection Only",
         description="Export selected objects only",
@@ -164,10 +168,20 @@ class ExportPLY(bpy.types.Operator, ExportHelper):
         filepath = self.filepath
         filepath = bpy.path.ensure_ext(filepath, self.filename_ext)
 
-        return export_ply.save(self, context, **keywords)
+        export_ply.save(context, **keywords)
+
+        return {'FINISHED'}
 
     def draw(self, context):
-        pass
+        layout = self.layout
+        layout.use_property_split = True
+        layout.use_property_decorate = False
+
+        sfile = context.space_data
+        operator = sfile.active_operator
+
+        col = layout.column(heading="Format")
+        col.prop(operator, "use_ascii")
 
 
 class PLY_PT_export_include(bpy.types.Panel):
diff --git a/io_mesh_ply/export_ply.py b/io_mesh_ply/export_ply.py
index 812aeb54..060b3d02 100644
--- a/io_mesh_ply/export_ply.py
+++ b/io_mesh_ply/export_ply.py
@@ -24,8 +24,55 @@ colors, and texture coordinates per face or per vertex.
 """
 
 
-def save_mesh(filepath, mesh, use_normals=True, use_uv_coords=True, use_colors=True):
-    import os
+def _write_binary(fw, ply_verts, ply_faces, mesh_verts):
+    from struct import pack
+
+    # Vertex data
+    # ---------------------------
+
+    for index, normal, uv_coords, color in ply_verts:
+        fw(pack("<3f", *mesh_verts[index].co))
+        if normal is not None:
+            fw(pack("<3f", *normal))
+        if uv_coords is not None:
+            fw(pack("<2f", *uv_coords))
+        if color is not None:
+            fw(pack("<4B", *color))
+
+    # Face data
+    # ---------------------------
+
+    for pf in ply_faces:
+        length = len(pf)
+        fw(pack("<B%dI" % length, length, *pf))
+
+
+def _write_ascii(fw, ply_verts, ply_faces, mesh_verts):
+
+    # Vertex data
+    # ---------------------------
+
+    for index, normal, uv_coords, color in ply_verts:
+        fw(b"%.6f %.6f %.6f" % mesh_verts[index].co[:])
+        if normal is not None:
+            fw(b" %.6f %.6f %.6f" % normal)
+        if uv_coords is not None:
+            fw(b" %.6f %.6f" % uv_coords)
+        if color is not None:
+            fw(b" %u %u %u %u" % color)
+        fw(b"\n")
+
+    # Face data
+    # ---------------------------
+
+    for pf in ply_faces:
+        fw(b"%d" % len(pf))
+        for index in pf:
+            fw(b" %d" % index)
+        fw(b"\n")
+
+
+def save_mesh(filepath, mesh, use_ascii, use_normals, use_uv_coords, use_colors):
     import bpy
 
     def rvec3d(v):
@@ -56,10 +103,11 @@ def save_mesh(filepath, mesh, use_normals=True, use_uv_coords=True, use_colors=T
 
     for i, f in enumerate(mesh.polygons):
 
-        smooth = not use_normals or f.use_smooth
-        if not smooth:
-            normal = f.normal[:]
-            normal_key = rvec3d(normal)
+        if use_normals:
+            smooth = f.use_smooth
+            if not smooth:
+                normal = f.normal[:]
+                normal_key = rvec3d(normal)
 
         if use_uv_coords:
             uv = [
@@ -76,7 +124,7 @@ def save_mesh(filepath, mesh, use_normals=True, use_uv_coords=True, use_colors=T
         for j, vidx in enumerate(f.vertices):
             v = mesh_verts[vidx]
 
-            if smooth:
+            if use_normals and smooth:
                 normal = v.normal[:]
                 normal_key = rvec3d(normal)
 
@@ -104,90 +152,72 @@ def save_mesh(filepath, mesh, use_normals=True, use_uv_coords=True, use_colors=T
 
             pf.append(pf_vidx)
 
-    with open(filepath, "w", encoding="utf-8", newline="\n") as file:
+    with open(filepath, "wb") as file:
         fw = file.write
+        file_format = b"ascii" if use_ascii else b"binary_little_endian"
 
         # Header
         # ---------------------------
 
-        fw("ply\n")
-        fw("format ascii 1.0\n")
-        fw(
-            f"comment Created by Blender {bpy.app.version_string} - "
-            f"www.blender.org, source file: {os.path.basename(bpy.data.filepath)!r}\n"
-        )
+        fw(b"ply\n")
+        fw(b"format %s 1.0\n" % file_format)
+        fw(b"comment Created by Blender %s - www.blender.org\n" % bpy.app.version_string.encode("utf-8"))
 
-        fw(f"element vertex {len(ply_verts)}\n")
+        fw(b"element vertex %d\n" % len(ply_verts))
         fw(
-            "property float x\n"
-            "property float y\n"
-            "property float z\n"
+            b"property float x\n"
+            b"property float y\n"
+            b"property float z\n"
         )
         if use_normals:
             fw(
-                "property float nx\n"
-                "property float ny\n"
-                "property float nz\n"
+                b"property float nx\n"
+                b"property float ny\n"
+                b"property float nz\n"
             )
         if use_uv_coords:
             fw(
-                "property float s\n"
-                "property float t\n"
+                b"property float s\n"
+                b"property float t\n"
             )
         if use_colors:
             fw(
-                "property uchar red\n"
-                "property uchar green\n"
-                "property uchar blue\n"
-                "property uchar alpha\n"
+                b"property uchar red\n"
+                b"property uchar green\n"
+                b"property uchar blue\n"
+                b"property uchar alpha\n"
             )
 
-        fw(f"element face {len(mesh.polygons)}\n")
-        fw("property list uchar uint vertex_indices\n")
-
-        fw("end_header\n")
+        fw(b"element face %d\n" % len(mesh.polygons))
+        fw(b"property list uchar uint vertex_indices\n")
+        fw(b"end_header\n")
 
-        # Vertex data
+        # Geometry
         # ---------------------------
 
-        for i, v in enumerate(ply_verts):
-            fw("%.6f %.6f %.6f" % mesh_verts[v[0]].co[:])
-            if use_normals:
-                fw(" %.6f %.6f %.6f" % v[1])
-            if use_uv_coords:
-                fw(" %.6f %.6f" % v[2])
-            if use_colors:
-                fw(" %u %u %u %u" % v[3])
-            fw("\n")
-
-        # Face data
-        # ---------------------------
-
-        for pf in ply_faces:
-            fw(f"{len(pf)}")
-            for v in pf:
-                fw(f" {v}")
-            fw("\n")
-
-        print(f"Writing {filepath!r} done")
-
-    return {'FINISHED'}
+        if use_ascii:
+            _write_ascii(fw, ply_verts, ply_faces, mesh_verts)
+        else:
+            _write_binary(fw, ply_verts, ply_faces, mesh_verts)
 
 
 def save(
-    operator,
     context,
     filepath="",
+    use_ascii=False,
     use_selection=False,
     use_mesh_modifiers=True,
     use_normals=True,
     use_uv_coords=True,
     use_colors=True,
-    global_matrix=None
+    global_matrix=None,
 ):
+    import time
     import bpy
     import bmesh
 
+    t = time.time()
+
     if bpy.ops.object.mode_set.poll():
         bpy.ops.object.mode_set(mode='OBJECT')
 
@@ -224,14 +254,16 @@ def save(
     if use_normals:
         mesh.calc_normals()
 
-    ret = save_mesh(
+    save_mesh(
         filepath,
         mesh,
-        use_normals=use_normals,
-        use_uv_coords=use_uv_coords,
-        use_colors=use_colors,
+        use_ascii,
+        use_normals,
+        use_uv_coords,
+        use_colors,
     )
 
     bpy.data.meshes.remove(mesh)
 
-    return ret
+    t_delta = time.time() - t
+    print(f"Export completed {filepath!r} in {t_delta:.3f}")
diff --git a/io_mesh_ply/import_ply.py b/io_mesh_ply/import_ply.py
index 6df2ec81..2bc67746 100644
--- a/io_mesh_ply/import_ply.py
+++ b/io_mesh_ply/import_ply.py
@@ -272,20 +272,20 @@ def load_ply_mesh(filepath, ply_name):
             if len(colindices) == 3:
                 mesh_colors.extend([
                     (
-                       vertices[index][colindices[0]] * colmultiply[0],
-                       vertices[index][colindices[1]] * colmultiply[1],
-                       vertices[index][colindices[2]] * colmultiply[2],
-                       1.0,
+                        vertices[index][colindices[0]] * colmultiply[0],
+                        vertices[index][colindices[1]] * colmultiply[1],
+                        vertices[index][colindices[2]] * colmultiply[2],
+                        1.0,
                     )
                     for index in indices
                 ])
             elif len(colindices) == 4:
                 mesh_colors.extend([
                     (
-                       vertices[index][colindices[0]] * colmultiply[0],
-                       vertices[index][colindices[1]] * colmultiply[1],
-                       vertices[index][colindices[2]] * colmultiply[2],
-                       vertices[index][colindices[3]] * colmultiply[3],
+                        vertices[index][colindices[0]] * colmultiply[0],
+                        vertices[index][colindices[1]] * colmultiply[1],
+                        vertices[index][colindices[2]] * colmultiply[2],
+                        vertices[index][colindices[3]] * colmultiply[3],
                     )
                     for index in indices
                 ])



More information about the Bf-extensions-cvs mailing list