[Bf-extensions-cvs] [7f4c2d5e] master: PLY: refactor exporter

Mikhail Rachinskiy noreply at git.blender.org
Mon Jan 3 21:48:23 CET 2022


Commit: 7f4c2d5e48657af8bf450da7d6a0587065cfa8b4
Author: Mikhail Rachinskiy
Date:   Tue Jan 4 00:24:29 2022 +0400
Branches: master
https://developer.blender.org/rBA7f4c2d5e48657af8bf450da7d6a0587065cfa8b4

PLY: refactor exporter

New geometry traversal code, use bmesh data instead of mesh,
much simpler logic.

This also fixes two unreported issues with exporter:
* Splitting faces when uv, vertex color or normals export options are
enabled.
* Disrupted vertex indices.

On top of that there is a significant performance improvements,
export Suzanne model with 4 subdivision levels:
* Old 4.527s
* New 1.029s

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

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

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

diff --git a/io_mesh_ply/__init__.py b/io_mesh_ply/__init__.py
index a3f08ebd..b75084a8 100644
--- a/io_mesh_ply/__init__.py
+++ b/io_mesh_ply/__init__.py
@@ -20,7 +20,7 @@
 
 bl_info = {
     "name": "Stanford PLY format",
-    "author": "Bruce Merry, Campbell Barton", "Bastien Montagne"
+    "author": "Bruce Merry, Campbell Barton, Bastien Montagne, Mikhail Rachinsky",
     "version": (2, 1, 0),
     "blender": (2, 90, 0),
     "location": "File > Import/Export",
diff --git a/io_mesh_ply/export_ply.py b/io_mesh_ply/export_ply.py
index d90c0e49..a2acb1e8 100644
--- a/io_mesh_ply/export_ply.py
+++ b/io_mesh_ply/export_ply.py
@@ -23,23 +23,17 @@ This script exports Stanford PLY files from Blender. It supports normals,
 colors, and texture coordinates per face or per vertex.
 """
 
+import bpy
 
-class _PLYface:
-    __slots__ = "verts", "sides"
 
-    def __init__(self, sides: int) -> None:
-        self.verts = []
-        self.sides = sides
-
-
-def _write_binary(fw, ply_verts: list, ply_faces: list[_PLYface], mesh_verts: list) -> None:
+def _write_binary(fw, ply_verts: list, ply_faces: list) -> None:
     from struct import pack
 
     # Vertex data
     # ---------------------------
 
-    for index, normal, uv_coords, color in ply_verts:
-        fw(pack("<3f", *mesh_verts[index].co))
+    for v, normal, uv_coords, color in ply_verts:
+        fw(pack("<3f", *v.co))
         if normal is not None:
             fw(pack("<3f", *normal))
         if uv_coords is not None:
@@ -51,20 +45,21 @@ def _write_binary(fw, ply_verts: list, ply_faces: list[_PLYface], mesh_verts: li
     # ---------------------------
 
     for pf in ply_faces:
-        fw(pack(f"<B{pf.sides}I", pf.sides, *pf.verts))
+        length = len(pf)
+        fw(pack(f"<B{length}I", length, *pf))
 
 
-def _write_ascii(fw, ply_verts: list, ply_faces: list[_PLYface], mesh_verts: list) -> None:
+def _write_ascii(fw, ply_verts: list, ply_faces: list) -> None:
 
     # Vertex data
     # ---------------------------
 
-    for index, normal, uv_coords, color in ply_verts:
-        fw(b"%.6f %.6f %.6f" % mesh_verts[index].co[:])
+    for v, normal, uv_coords, color in ply_verts:
+        fw(b"%.6f %.6f %.6f" % v.co[:])
         if normal is not None:
-            fw(b" %.6f %.6f %.6f" % normal)
+            fw(b" %.6f %.6f %.6f" % normal[:])
         if uv_coords is not None:
-            fw(b" %.6f %.6f" % uv_coords)
+            fw(b" %.6f %.6f" % uv_coords[:])
         if color is not None:
             fw(b" %u %u %u %u" % color)
         fw(b"\n")
@@ -73,93 +68,45 @@ def _write_ascii(fw, ply_verts: list, ply_faces: list[_PLYface], mesh_verts: lis
     # ---------------------------
 
     for pf in ply_faces:
-        fw(b"%d" % pf.sides)
-        for index in pf.verts:
+        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 save_mesh(filepath, bm, use_ascii, use_normals, use_uv, use_color):
+    uv_lay = bm.loops.layers.uv.active
+    col_lay = bm.loops.layers.color.active
 
-    def rvec3d(v):
-        return round(v[0], 6), round(v[1], 6), round(v[2], 6)
+    use_uv = use_uv and uv_lay is not None
+    use_color = use_color and col_lay is not None
+    uv = color = None
 
-    def rvec2d(v):
-        return round(v[0], 6), round(v[1], 6)
-
-    if use_uv_coords and mesh.uv_layers:
-        active_uv_layer = mesh.uv_layers.active.data
-    else:
-        use_uv_coords = False
-
-    if use_colors and mesh.vertex_colors:
-        active_col_layer = mesh.vertex_colors.active.data
-    else:
-        use_colors = False
+    ply_verts = [None for _ in range(len(bm.verts))]
+    ply_faces = []
 
-    # in case
-    color = uvcoord = uvcoord_key = normal = normal_key = None
+    for f in bm.faces:
+        pf = []
+        ply_faces.append(pf)
 
-    mesh_verts = mesh.vertices
-    # vdict = {} # (index, normal, uv) -> new index
-    vdict = [{} for _ in range(len(mesh_verts))]
-    ply_verts = []
-    ply_faces = []
-    vert_count = 0
+        normal = None
+        if use_normals and not f.smooth:
+            normal = f.normal
 
-    for f in mesh.polygons:
+        for loop in f.loops:
+            v = loop.vert
+            pf.append(v.index)
 
-        if use_normals:
-            smooth = f.use_smooth
-            if not smooth:
-                normal = f.normal[:]
-                normal_key = rvec3d(normal)
-
-        if use_uv_coords:
-            uv = [
-                active_uv_layer[l].uv[:]
-                for l in range(f.loop_start, f.loop_start + f.loop_total)
-            ]
-        if use_colors:
-            col = [
-                active_col_layer[l].color[:]
-                for l in range(f.loop_start, f.loop_start + f.loop_total)
-            ]
-
-        pf = _PLYface(f.loop_total)
-        for i, vidx in enumerate(f.vertices):
-            v = mesh_verts[vidx]
-
-            if use_normals and smooth:
-                normal = v.normal[:]
-                normal_key = rvec3d(normal)
-
-            if use_uv_coords:
-                uvcoord = uv[i][0], uv[i][1]
-                uvcoord_key = rvec2d(uvcoord)
-
-            if use_colors:
-                color = col[i]
-                color = (
-                    int(color[0] * 255.0),
-                    int(color[1] * 255.0),
-                    int(color[2] * 255.0),
-                    int(color[3] * 255.0),
-                )
-            key = normal_key, uvcoord_key, color
-
-            vdict_local = vdict[vidx]
-            pf_vidx = vdict_local.get(key)  # Will be None initially
-
-            if pf_vidx is None:  # Same as vdict_local.has_key(key)
-                pf_vidx = vdict_local[key] = vert_count
-                ply_verts.append((vidx, normal, uvcoord, color))
-                vert_count += 1
-
-            pf.verts.append(pf_vidx)
+            if not v.tag:
+                if use_normals and normal is None:
+                    normal = v.normal
+                if use_uv:
+                    uv = loop[uv_lay].uv
+                if use_color:
+                    color = tuple(int(x * 255.0) for x in loop[col_lay])
 
-        ply_faces.append(pf)
+                ply_verts[v.index] = (v, normal, uv, color)
+                v.tag = True
 
     with open(filepath, "wb") as file:
         fw = file.write
@@ -184,12 +131,12 @@ def save_mesh(filepath, mesh, use_ascii, use_normals, use_uv_coords, use_colors)
                 b"property float ny\n"
                 b"property float nz\n"
             )
-        if use_uv_coords:
+        if use_uv:
             fw(
                 b"property float s\n"
                 b"property float t\n"
             )
-        if use_colors:
+        if use_color:
             fw(
                 b"property uchar red\n"
                 b"property uchar green\n"
@@ -197,7 +144,7 @@ def save_mesh(filepath, mesh, use_ascii, use_normals, use_uv_coords, use_colors)
                 b"property uchar alpha\n"
             )
 
-        fw(b"element face %d\n" % len(mesh.polygons))
+        fw(b"element face %d\n" % len(ply_faces))
         fw(b"property list uchar uint vertex_indices\n")
         fw(b"end_header\n")
 
@@ -205,9 +152,9 @@ def save_mesh(filepath, mesh, use_ascii, use_normals, use_uv_coords, use_colors)
         # ---------------------------
 
         if use_ascii:
-            _write_ascii(fw, ply_verts, ply_faces, mesh_verts)
+            _write_ascii(fw, ply_verts, ply_faces)
         else:
-            _write_binary(fw, ply_verts, ply_faces, mesh_verts)
+            _write_binary(fw, ply_verts, ply_faces)
 
 
 def save(
@@ -222,7 +169,6 @@ def save(
     global_matrix=None,
 ):
     import time
-    import bpy
     import bmesh
 
     t = time.time()
@@ -257,26 +203,22 @@ def save(
     if (ngons := [f for f in bm.faces if len(f.verts) > 255]):
         bmesh.ops.triangulate(bm, faces=ngons)
 
-    mesh = bpy.data.meshes.new("TMP PLY EXPORT")
-    bm.to_mesh(mesh)
-    bm.free()
-
     if global_matrix is not None:
-        mesh.transform(global_matrix)
+        bm.transform(global_matrix)
 
     if use_normals:
-        mesh.calc_normals()
+        bm.normal_update()
 
     save_mesh(
         filepath,
-        mesh,
+        bm,
         use_ascii,
         use_normals,
         use_uv_coords,
         use_colors,
     )
 
-    bpy.data.meshes.remove(mesh)
+    bm.free()
 
     t_delta = time.time() - t
     print(f"Export completed {filepath!r} in {t_delta:.3f}")



More information about the Bf-extensions-cvs mailing list