Fix T71100: Node Wrangler creates nodes on linked node trees
[blender-addons.git] / io_mesh_ply / export_ply.py
blobb3be770cdd46f4d10eb7fc4663df7613fa36795d
1 # SPDX-License-Identifier: GPL-2.0-or-later
3 """
4 This script exports Stanford PLY files from Blender. It supports normals,
5 colors, and texture coordinates per face or per vertex.
6 """
8 import bpy
11 def _write_binary(fw, ply_verts: list, ply_faces: list) -> None:
12 from struct import pack
14 # Vertex data
15 # ---------------------------
17 for v, normal, uv, color in ply_verts:
18 fw(pack("<3f", *v.co))
19 if normal is not None:
20 fw(pack("<3f", *normal))
21 if uv is not None:
22 fw(pack("<2f", *uv))
23 if color is not None:
24 fw(pack("<4B", *color))
26 # Face data
27 # ---------------------------
29 for pf in ply_faces:
30 length = len(pf)
31 fw(pack(f"<B{length}I", length, *pf))
34 def _write_ascii(fw, ply_verts: list, ply_faces: list) -> None:
36 # Vertex data
37 # ---------------------------
39 for v, normal, uv, color in ply_verts:
40 fw(b"%.6f %.6f %.6f" % v.co[:])
41 if normal is not None:
42 fw(b" %.6f %.6f %.6f" % normal[:])
43 if uv is not None:
44 fw(b" %.6f %.6f" % uv)
45 if color is not None:
46 fw(b" %u %u %u %u" % color)
47 fw(b"\n")
49 # Face data
50 # ---------------------------
52 for pf in ply_faces:
53 fw(b"%d" % len(pf))
54 for index in pf:
55 fw(b" %d" % index)
56 fw(b"\n")
59 def save_mesh(filepath, bm, use_ascii, use_normals, use_uv, use_color):
60 uv_lay = bm.loops.layers.uv.active
61 col_lay = bm.loops.layers.color.active
63 use_uv = use_uv and uv_lay is not None
64 use_color = use_color and col_lay is not None
65 normal = uv = color = None
67 ply_faces = []
68 ply_verts = []
69 ply_vert_map = {}
70 ply_vert_id = 0
72 for f in bm.faces:
73 pf = []
74 ply_faces.append(pf)
76 for loop in f.loops:
77 v = map_id = loop.vert
79 if use_uv:
80 uv = loop[uv_lay].uv[:]
81 map_id = uv
83 # Identify vertex by pointer unless exporting UVs,
84 # in which case id by UV coordinate (will split edges by seams).
85 if (_id := ply_vert_map.get(map_id)) is not None:
86 pf.append(_id)
87 continue
89 if use_normals:
90 normal = v.normal
91 if use_color:
92 color = tuple(int(x * 255.0) for x in loop[col_lay])
94 ply_verts.append((v, normal, uv, color))
95 ply_vert_map[map_id] = ply_vert_id
96 pf.append(ply_vert_id)
97 ply_vert_id += 1
99 with open(filepath, "wb") as file:
100 fw = file.write
101 file_format = b"ascii" if use_ascii else b"binary_little_endian"
103 # Header
104 # ---------------------------
106 fw(b"ply\n")
107 fw(b"format %s 1.0\n" % file_format)
108 fw(b"comment Created by Blender %s - www.blender.org\n" % bpy.app.version_string.encode("utf-8"))
110 fw(b"element vertex %d\n" % len(ply_verts))
112 b"property float x\n"
113 b"property float y\n"
114 b"property float z\n"
116 if use_normals:
118 b"property float nx\n"
119 b"property float ny\n"
120 b"property float nz\n"
122 if use_uv:
124 b"property float s\n"
125 b"property float t\n"
127 if use_color:
129 b"property uchar red\n"
130 b"property uchar green\n"
131 b"property uchar blue\n"
132 b"property uchar alpha\n"
135 fw(b"element face %d\n" % len(ply_faces))
136 fw(b"property list uchar uint vertex_indices\n")
137 fw(b"end_header\n")
139 # Geometry
140 # ---------------------------
142 if use_ascii:
143 _write_ascii(fw, ply_verts, ply_faces)
144 else:
145 _write_binary(fw, ply_verts, ply_faces)
148 def save(
149 context,
150 filepath="",
151 use_ascii=False,
152 use_selection=False,
153 use_mesh_modifiers=True,
154 use_normals=True,
155 use_uv_coords=True,
156 use_colors=True,
157 global_matrix=None,
159 import time
160 import bmesh
162 t = time.time()
164 if bpy.ops.object.mode_set.poll():
165 bpy.ops.object.mode_set(mode='OBJECT')
167 if use_selection:
168 obs = context.selected_objects
169 else:
170 obs = context.scene.objects
172 depsgraph = context.evaluated_depsgraph_get()
173 bm = bmesh.new()
175 for ob in obs:
176 if use_mesh_modifiers:
177 ob_eval = ob.evaluated_get(depsgraph)
178 else:
179 ob_eval = ob
181 try:
182 me = ob_eval.to_mesh()
183 except RuntimeError:
184 continue
186 me.transform(ob.matrix_world)
187 bm.from_mesh(me)
188 ob_eval.to_mesh_clear()
190 # Workaround for hardcoded unsigned char limit in other DCCs PLY importers
191 if (ngons := [f for f in bm.faces if len(f.verts) > 255]):
192 bmesh.ops.triangulate(bm, faces=ngons)
194 if global_matrix is not None:
195 bm.transform(global_matrix)
197 if use_normals:
198 bm.normal_update()
200 save_mesh(
201 filepath,
203 use_ascii,
204 use_normals,
205 use_uv_coords,
206 use_colors,
209 bm.free()
211 t_delta = time.time() - t
212 print(f"Export completed {filepath!r} in {t_delta:.3f}")