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
This commit is contained in:
parent
dbb4c80f22
commit
85173fa526
|
@ -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):
|
||||
|
|
|
@ -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(b"element face %d\n" % len(mesh.polygons))
|
||||
fw(b"property list uchar uint vertex_indices\n")
|
||||
fw(b"end_header\n")
|
||||
|
||||
fw("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}")
|
||||
|
|
|
@ -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
|
||||
])
|
||||
|
|
Loading…
Reference in New Issue