[Bf-extensions-cvs] [94bd4819] master: PLY: refactor exporter

Mikhail Rachinskiy noreply at git.blender.org
Wed Jan 5 15:38:57 CET 2022


Commit: 94bd4819809c15642cff15bf92b09ed589ffddc2
Author: Mikhail Rachinskiy
Date:   Wed Jan 5 18:21:17 2022 +0400
Branches: master
https://developer.blender.org/rBA94bd4819809c15642cff15bf92b09ed589ffddc2

PLY: refactor exporter

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

This also fixes split faces when exporting normals and vertex colors.

Exporting UVs still results in split faces, since UVs are stored in
vertex data (not sure if PLY format limitation or just this exporter)
faces has to be split along seams in order for each vertex to corespond
with each UV coordinate.

No performance benefits this time.

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

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..87cbe859 100644
--- a/io_mesh_ply/__init__.py
+++ b/io_mesh_ply/__init__.py
@@ -20,9 +20,9 @@
 
 bl_info = {
     "name": "Stanford PLY format",
-    "author": "Bruce Merry, Campbell Barton", "Bastien Montagne"
-    "version": (2, 1, 0),
-    "blender": (2, 90, 0),
+    "author": "Bruce Merry, Campbell Barton, Bastien Montagne, Mikhail Rachinsky",
+    "version": (2, 2, 0),
+    "blender": (3, 0, 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",
diff --git a/io_mesh_ply/export_ply.py b/io_mesh_ply/export_ply.py
index d90c0e49..15176bed 100644
--- a/io_mesh_ply/export_ply.py
+++ b/io_mesh_ply/export_ply.py
@@ -23,27 +23,21 @@ 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, color in ply_verts:
+        fw(pack("<3f", *v.co))
         if normal is not None:
             fw(pack("<3f", *normal))
-        if uv_coords is not None:
-            fw(pack("<2f", *uv_coords))
+        if uv is not None:
+            fw(pack("<2f", *uv))
         if color is not None:
             fw(pack("<4B", *color))
 
@@ -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, color in ply_verts:
+        fw(b"%.6f %.6f %.6f" % v.co[:])
         if normal is not None:
-            fw(b" %.6f %.6f %.6f" % normal)
-        if uv_coords is not None:
-            fw(b" %.6f %.6f" % uv_coords)
+            fw(b" %.6f %.6f %.6f" % normal[:])
+        if uv is not None:
+            fw(b" %.6f %.6f" % uv)
         if color is not None:
             fw(b" %u %u %u %u" % color)
         fw(b"\n")
@@ -73,93 +68,53 @@ 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)
+    ply_faces = []
+    ply_verts = []
+    ply_vert_map = {}
+    ply_vert_id = 0
 
-    if use_uv_coords and mesh.uv_layers:
-        active_uv_layer = mesh.uv_layers.active.data
-    else:
-        use_uv_coords = False
+    for f in bm.faces:
+        pf = []
+        ply_faces.append(pf)
 
-    if use_colors and mesh.vertex_colors:
-        active_col_layer = mesh.vertex_colors.active.data
-    else:
-        use_colors = False
+        normal = None
+        if use_normals and not f.smooth:
+            normal = f.normal
 
-    # in case
-    color = uvcoord = uvcoord_key = normal = normal_key = None
+        for loop in f.loops:
+            v = v_key = loop.vert
 
-    mesh_verts = mesh.vertices
-    # vdict = {} # (index, normal, uv) -> new index
-    vdict = [{} for _ in range(len(mesh_verts))]
-    ply_verts = []
-    ply_faces = []
-    vert_count = 0
+            if use_uv:
+                uv = loop[uv_lay].uv[:]
+                v_key = uv
 
-    for f in mesh.polygons:
+            if (_id := ply_vert_map.get(v_key)) is not None:
+                pf.append(_id)
+                continue
 
-        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 use_normals and normal is None:
+                normal = v.normal
+            if use_color:
+                color = tuple(int(x * 255.0) for x in loop[col_lay])
 
-        ply_faces.append(pf)
+            ply_verts.append((v, normal, uv, color))
+            ply_vert_map[v_key] = ply_vert_id
+            pf.append(ply_vert_id)
+            ply_vert_id += 1
 
     with open(filepath, "wb") as file:
         fw = file.write
@@ -184,12 +139,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 +152,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 +160,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 +177,6 @@ def save(
     global_matrix=None,
 ):
     import time
-    import bpy
     import bmesh
 
     t = time.time()
@@ -257,26 +211,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