1 # SPDX-License-Identifier: GPL-2.0-or-later
6 This script exports Stanford PLY files from Blender. It supports normals,
7 colors, and texture coordinates per face or per vertex.
13 def _write_binary(fw
, ply_verts
: list, ply_faces
: list) -> None:
14 from struct
import pack
17 # ---------------------------
19 for v
, normal
, uv
, color
in ply_verts
:
20 fw(pack("<3f", *v
.co
))
21 if normal
is not None:
22 fw(pack("<3f", *normal
))
26 fw(pack("<4B", *color
))
29 # ---------------------------
33 fw(pack(f
"<B{length}I", length
, *pf
))
36 def _write_ascii(fw
, ply_verts
: list, ply_faces
: list) -> None:
39 # ---------------------------
41 for v
, normal
, uv
, color
in ply_verts
:
42 fw(b
"%.6f %.6f %.6f" % v
.co
[:])
43 if normal
is not None:
44 fw(b
" %.6f %.6f %.6f" % normal
[:])
46 fw(b
" %.6f %.6f" % uv
)
48 fw(b
" %u %u %u %u" % color
)
52 # ---------------------------
61 def save_mesh(filepath
, bm
, use_ascii
, use_normals
, use_uv
, use_color
):
62 uv_lay
= bm
.loops
.layers
.uv
.active
63 col_lay
= bm
.loops
.layers
.color
.active
65 use_uv
= use_uv
and uv_lay
is not None
66 use_color
= use_color
and col_lay
is not None
67 normal
= uv
= color
= None
79 v
= map_id
= loop
.vert
82 uv
= loop
[uv_lay
].uv
[:]
85 # Identify vertex by pointer unless exporting UVs,
86 # in which case id by UV coordinate (will split edges by seams).
87 if (_id
:= ply_vert_map
.get(map_id
)) is not None:
94 color
= tuple(int(x
* 255.0) for x
in loop
[col_lay
])
96 ply_verts
.append((v
, normal
, uv
, color
))
97 ply_vert_map
[map_id
] = ply_vert_id
98 pf
.append(ply_vert_id
)
101 with
open(filepath
, "wb") as file:
103 file_format
= b
"ascii" if use_ascii
else b
"binary_little_endian"
106 # ---------------------------
109 fw(b
"format %s 1.0\n" % file_format
)
110 fw(b
"comment Created by Blender %s - www.blender.org\n" % bpy
.app
.version_string
.encode("utf-8"))
112 fw(b
"element vertex %d\n" % len(ply_verts
))
114 b
"property float x\n"
115 b
"property float y\n"
116 b
"property float z\n"
120 b
"property float nx\n"
121 b
"property float ny\n"
122 b
"property float nz\n"
126 b
"property float s\n"
127 b
"property float t\n"
131 b
"property uchar red\n"
132 b
"property uchar green\n"
133 b
"property uchar blue\n"
134 b
"property uchar alpha\n"
137 fw(b
"element face %d\n" % len(ply_faces
))
138 fw(b
"property list uchar uint vertex_indices\n")
142 # ---------------------------
145 _write_ascii(fw
, ply_verts
, ply_faces
)
147 _write_binary(fw
, ply_verts
, ply_faces
)
155 use_mesh_modifiers
=True,
166 if bpy
.ops
.object.mode_set
.poll():
167 bpy
.ops
.object.mode_set(mode
='OBJECT')
170 obs
= context
.selected_objects
172 obs
= context
.scene
.objects
174 depsgraph
= context
.evaluated_depsgraph_get()
178 if use_mesh_modifiers
:
179 ob_eval
= ob
.evaluated_get(depsgraph
)
184 me
= ob_eval
.to_mesh()
188 me
.transform(ob
.matrix_world
)
190 ob_eval
.to_mesh_clear()
192 # Workaround for hardcoded unsigned char limit in other DCCs PLY importers
193 if (ngons
:= [f
for f
in bm
.faces
if len(f
.verts
) > 255]):
194 bmesh
.ops
.triangulate(bm
, faces
=ngons
)
196 if global_matrix
is not None:
197 bm
.transform(global_matrix
)
213 t_delta
= time
.time() - t
214 print(f
"Export completed {filepath!r} in {t_delta:.3f}")