GCC Code Coverage Report


Directory: ./
File: lib/geogram/mesh/mesh_io.cpp
Date: 2026-09-07 02:28:19
Exec Total Coverage
Lines: 821 1964 41.8%
Functions: 49 83 59.0%
Branches: 807 3292 24.5%

Line Branch Exec Source
1 /*
2 * Copyright (c) 2000-2022 Inria
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * * Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above copyright notice,
11 * this list of conditions and the following disclaimer in the documentation
12 * and/or other materials provided with the distribution.
13 * * Neither the name of the ALICE Project-Team nor the names of its
14 * contributors may be used to endorse or promote products derived from this
15 * software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 * POSSIBILITY OF SUCH DAMAGE.
28 *
29 * Contact: Bruno Levy
30 *
31 * https://www.inria.fr/fr/bruno-levy
32 *
33 * Inria,
34 * Domaine de Voluceau,
35 * 78150 Le Chesnay - Rocquencourt
36 * FRANCE
37 *
38 */
39
40 #include <geogram/mesh/mesh_io.h>
41 #include <geogram/mesh/mesh.h>
42 #include <geogram/mesh/mesh_repair.h>
43 #include <geogram/mesh/index.h>
44 #include <geogram/points/colocate.h>
45 #include <geogram/basic/line_stream.h>
46 #include <geogram/basic/b_stream.h>
47 #include <geogram/basic/geofile.h>
48 #include <geogram/basic/file_system.h>
49 #include <geogram/basic/command_line.h>
50 #include <geogram/basic/argused.h>
51 #include <geogram/basic/logger.h>
52 #include <geogram/basic/geometry.h>
53 #include <geogram/bibliography/bibliography.h>
54
55 #include <fstream>
56
57 #ifdef GEOGRAM_USE_BUILTIN_DEPS
58 extern "C" {
59 #include <geogram/third_party/libMeshb/sources/libmeshb7.h>
60 }
61 #include <geogram/third_party/rply/rply.h>
62 #else
63 extern "C" {
64 #include <libmeshb7.h>
65 }
66 #include <rply.h>
67 #endif
68
69 #ifdef GEO_COMPILER_GCC
70 #include <cxxabi.h>
71 namespace {
72 std::string demangle(const std::string& mangled) {
73 int status;
74 char* realname =
75 abi::__cxa_demangle(mangled.c_str(), nullptr, nullptr, &status);
76 std::string result(realname);
77 free(realname);
78 return result;
79 }
80 }
81 #else
82 namespace {
83 std::string demangle(const std::string& mangled) {
84 return mangled;
85 }
86 }
87 #endif
88
89 // TODO: take into account selected mesh elements
90 // in loaders and exporters.
91
92 // We got some IOHandler classes declared locally that
93 // have no out-of-line virtual functions. It is not a
94 // problem since they are only visible from this translation
95 // unit, but clang will complain.
96 #ifdef __clang__
97 #pragma GCC diagnostic ignored "-Wweak-vtables"
98 #endif
99
100 namespace GEO {
101
102
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 482359 times.
482359 inline void set_mesh_point(
103 Mesh& M, index_t v, const double* coords, index_t dim
104 ) {
105 geo_debug_assert(M.vertices.dimension() >= dim);
106 if(M.vertices.single_precision()) {
107 float* p = M.vertices.single_precision_point_ptr(v);
108 for(index_t c=0; c<dim; ++c) {
109 p[c] = float(coords[c]);
110 }
111 } else {
112 double* p = M.vertices.point_ptr(v);
113
2/2
✓ Branch 0 taken 1449577 times.
✓ Branch 1 taken 482359 times.
1931936 for(index_t c=0; c<dim; ++c) {
114 1449577 p[c] = coords[c];
115 }
116 }
117 482359 }
118
119
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 55506 times.
55506 inline void set_mesh_point(
120 Mesh& M, index_t v, const float* coords, index_t dim
121 ) {
122 geo_debug_assert(M.vertices.dimension() >= dim);
123 if(M.vertices.single_precision()) {
124 float* p = M.vertices.single_precision_point_ptr(v);
125 for(index_t c=0; c<dim; ++c) {
126 p[c] = coords[c];
127 }
128 } else {
129 double* p = M.vertices.point_ptr(v);
130
2/2
✓ Branch 0 taken 166518 times.
✓ Branch 1 taken 55506 times.
222024 for(index_t c=0; c<dim; ++c) {
131 166518 p[c] = double(coords[c]);
132 }
133 }
134 55506 }
135
136
137
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 449232 times.
449232 inline void get_mesh_point(
138 const Mesh& M, index_t v, double* coords, index_t dim
139 ) {
140 if(M.vertices.single_precision()) {
141 const float* p = M.vertices.single_precision_point_ptr(v);
142 for(index_t c=0; c<dim; ++c) {
143 coords[c] = (c < M.vertices.dimension()) ? double(p[c]) : 0.0;
144 }
145 } else {
146 const double* p = M.vertices.point_ptr(v);
147
2/2
✓ Branch 0 taken 1347696 times.
✓ Branch 1 taken 449232 times.
1796928 for(index_t c=0; c<dim; ++c) {
148
1/2
✓ Branch 0 taken 1347696 times.
✗ Branch 1 not taken.
1347696 coords[c] = (c < M.vertices.dimension()) ? p[c] : 0.0;
149 }
150 }
151 449232 }
152
153 /************************************************************************/
154
155 /**
156 * \brief IO handler for AliasWavefront OBJ format.
157 * \see http://en.wikipedia.org/wiki/Wavefront_.obj_file
158 */
159 class GEOGRAM_API OBJIOHandler : public MeshIOHandler {
160 public:
161 /**
162 * \brief Creates a OBJ IO handler.
163 * \param[in] dimension dimension of the vertices
164 * (3 for regular 3d mesh)
165 */
166 227 OBJIOHandler(coord_index_t dimension = 3) :
167 227 dimension_(dimension) {
168 227 }
169
170
1/2
✓ Branch 1 taken 168 times.
✗ Branch 2 not taken.
168 bool load(
171 const std::string& filename, Mesh& M,
172 const MeshIOFlags& ioflags
173 ) override {
174 bool ignore_tex_coords = false;
175 //!ioflags.has_attribute(MESH_VERTEX_TEX_COORD);
176
177 vector<vec2> tex_vertices;
178 Attribute<double> tex_coord;
179
1/2
✓ Branch 1 taken 168 times.
✗ Branch 2 not taken.
168 vector<double> P(dimension_);
180
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 168 times.
168 if(M.vertices.dimension() != dimension_) {
181 M.vertices.set_dimension(dimension_);
182 }
183
184
1/2
✓ Branch 1 taken 168 times.
✗ Branch 2 not taken.
168 LineInput in(filename);
185
2/2
✓ Branch 0 taken 167 times.
✓ Branch 1 taken 1 times.
168 if(!in.OK()) {
186 return false;
187 }
188
189
1/2
✓ Branch 1 taken 167 times.
✗ Branch 2 not taken.
167 bind_attributes(M, ioflags, true);
190 vector<index_t> facet_vertices;
191 vector<index_t> facet_tex_vertices;
192
193 bool first_facet_attribute = true;
194 bool read_facet_regions = false;
195
4/6
✓ Branch 0 taken 763934 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 763934 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 164 times.
✓ Branch 6 taken 763770 times.
764101 while(!in.eof() && in.get_line()) {
196
1/2
✓ Branch 1 taken 763770 times.
✗ Branch 2 not taken.
763770 in.get_fields();
197
1/2
✓ Branch 0 taken 763770 times.
✗ Branch 1 not taken.
763770 if(in.nb_fields() >= 1) {
198
2/2
✓ Branch 0 taken 345174 times.
✓ Branch 1 taken 418596 times.
763770 if(in.field_matches(0, "v")) {
199
2/2
✓ Branch 0 taken 1035520 times.
✓ Branch 1 taken 345173 times.
1380693 for(coord_index_t c = 0; c < dimension_; c++) {
200
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1035520 times.
✓ Branch 2 taken 1035520 times.
✗ Branch 3 not taken.
1035520 if(index_t(c + 1) < in.nb_fields()) {
201
2/2
✓ Branch 1 taken 1035519 times.
✓ Branch 2 taken 1 times.
1035520 P[c] = in.field_as_double(index_t(c + 1));
202 } else {
203 P[c] = 0.0;
204 }
205 }
206 index_t v = M.vertices.create_vertex();
207 345173 set_mesh_point(M, v, P.data(), dimension_);
208 } else if(
209
3/4
✓ Branch 0 taken 418596 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 203903 times.
✓ Branch 3 taken 214693 times.
837192 !ignore_tex_coords &&
210 in.field_matches(0, "vt")
211 ) {
212 if(!tex_coord_.is_bound()) {
213
1/2
✓ Branch 1 taken 115 times.
✗ Branch 2 not taken.
115 tex_coord_.bind_if_is_defined(
214
2/4
✓ Branch 1 taken 115 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 115 times.
230 M.facet_corners.attributes(), "tex_coord"
215 );
216 if(tex_coord.is_bound()) {
217 if(tex_coord_.dimension() != 2) {
218 tex_coord_.unbind();
219 ignore_tex_coords = true;
220 }
221 } else {
222
1/2
✓ Branch 1 taken 115 times.
✗ Branch 2 not taken.
115 tex_coord_.create_vector_attribute(
223
1/2
✓ Branch 1 taken 115 times.
✗ Branch 2 not taken.
230 M.facet_corners.attributes(), "tex_coord", 2
224 );
225 }
226 }
227
228
1/2
✓ Branch 0 taken 203903 times.
✗ Branch 1 not taken.
203903 if(!ignore_tex_coords) {
229 if(
230
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 203903 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
203903 in.nb_fields() != 3 &&
231 in.nb_fields() != 4 // TODO: read 3D UVs ?
232 ) {
233 Logger::err("I/O")
234 << "Line " << in.line_number()
235 << " malformed texture vertex"
236 << std::endl;
237 unbind_attributes();
238 return false;
239 }
240 tex_vertices.push_back(
241 203903 vec2(
242
1/2
✓ Branch 1 taken 203903 times.
✗ Branch 2 not taken.
203903 in.field_as_double(1),
243
1/2
✓ Branch 1 taken 203903 times.
✗ Branch 2 not taken.
203903 in.field_as_double(2))
244 );
245 }
246 } else if(
247
3/4
✓ Branch 0 taken 214693 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 213839 times.
✓ Branch 3 taken 854 times.
429386 ioflags.has_element(MESH_EDGES) &&
248 in.field_matches(0, "l")
249 ) {
250
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 854 times.
854 if(in.nb_fields() < 2) {
251 Logger::err("I/O")
252 << "Line " << in.line_number()
253 << ": polyline only has " << in.nb_fields()
254 << " vertices (at least 2 required)"
255 << std::endl;
256 unbind_attributes();
257 return false;
258 }
259 index_t prev = NO_INDEX;
260
2/2
✓ Branch 0 taken 1708 times.
✓ Branch 1 taken 854 times.
4270 for(index_t i=1; i<in.nb_fields(); ++i) {
261 signed_index_t s_vertex_index =
262
1/2
✓ Branch 1 taken 1708 times.
✗ Branch 2 not taken.
1708 in.field_as_int(i);
263 index_t vertex_index = 0;
264
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1708 times.
1708 if(s_vertex_index < 0) {
265 vertex_index = index_t(
266 1+int(M.vertices.nb()) + s_vertex_index
267 );
268 } else {
269 vertex_index = index_t(s_vertex_index);
270 }
271 if(
272
2/4
✓ Branch 0 taken 1708 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1708 times.
✗ Branch 3 not taken.
1708 (vertex_index < 1) ||
273 (vertex_index > M.vertices.nb())
274 ) {
275 Logger::err("I/O")
276 << "Line " << in.line_number()
277 << ": line vertex #" << i
278 << " references an invalid vertex: "
279 << vertex_index
280 << std::endl;
281 unbind_attributes();
282 return false;
283 }
284
2/2
✓ Branch 0 taken 854 times.
✓ Branch 1 taken 854 times.
1708 if(prev != NO_INDEX) {
285
1/2
✓ Branch 1 taken 854 times.
✗ Branch 2 not taken.
854 M.edges.create_edge(prev-1,vertex_index-1);
286 }
287 prev = vertex_index;
288 }
289 } else if(
290
4/4
✓ Branch 0 taken 213815 times.
✓ Branch 1 taken 24 times.
✓ Branch 2 taken 13211 times.
✓ Branch 3 taken 200604 times.
427654 ioflags.has_element(MESH_FACETS) &&
291 in.field_matches(0, "f")
292 ) {
293
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 200604 times.
200604 if(in.nb_fields() < 3) {
294 Logger::err("I/O")
295 << "Line " << in.line_number()
296 << ": facet only has " << in.nb_fields()
297 << " corners (at least 3 required)"
298 << std::endl;
299 unbind_attributes();
300 return false;
301 }
302
303
1/2
✓ Branch 1 taken 200604 times.
✗ Branch 2 not taken.
200604 facet_vertices.resize(0);
304
1/2
✓ Branch 1 taken 200604 times.
✗ Branch 2 not taken.
200604 facet_tex_vertices.resize(0);
305
306
2/2
✓ Branch 0 taken 640346 times.
✓ Branch 1 taken 200602 times.
1481292 for(index_t i = 1; i < in.nb_fields(); i++) {
307 char* tex_vertex_str = nullptr;
308
3/4
✓ Branch 1 taken 640346 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2692589 times.
✓ Branch 4 taken 319560 times.
3012149 for(char* ptr = in.field(i); *ptr != '\0'; ptr++) {
309
2/2
✓ Branch 0 taken 320786 times.
✓ Branch 1 taken 2371803 times.
2692589 if(*ptr == '/') {
310
1/2
✓ Branch 0 taken 320786 times.
✗ Branch 1 not taken.
320786 if(!ignore_tex_coords &&
311 tex_vertex_str == nullptr) {
312 320786 tex_vertex_str = ptr+1;
313 }
314 320786 *ptr = '\0';
315 320786 break;
316 }
317 }
318
319 // In .obj files,
320 // negative vertex index means
321 // nb_vertices - vertex index
322 GEO::signed_index_t
323
2/2
✓ Branch 1 taken 640345 times.
✓ Branch 2 taken 1 times.
640346 s_vertex_index = in.field_as_int(i);
324 index_t vertex_index = 0;
325
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 640345 times.
640345 if(s_vertex_index < 0) {
326 vertex_index = index_t(
327 1+int(M.vertices.nb()) + s_vertex_index
328 );
329 } else {
330 vertex_index = index_t(s_vertex_index);
331 }
332 if(
333
3/4
✓ Branch 0 taken 640345 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 640344 times.
✓ Branch 3 taken 1 times.
640345 (vertex_index < 1) ||
334 (vertex_index > M.vertices.nb())
335 ) {
336
2/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 Logger::err("I/O")
337 << "Line " << in.line_number()
338 << ": facet corner #" << i
339 << " references an invalid vertex: "
340 << vertex_index
341 << std::endl;
342
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 unbind_attributes();
343 return false;
344 }
345
1/2
✓ Branch 1 taken 640344 times.
✗ Branch 2 not taken.
640344 facet_vertices.push_back(vertex_index-1);
346
347
2/2
✓ Branch 0 taken 320784 times.
✓ Branch 1 taken 319560 times.
640344 if(tex_vertex_str != nullptr &&
348
2/4
✓ Branch 0 taken 320784 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 320784 times.
✗ Branch 3 not taken.
320784 tex_vertex_str[0] != '\0' &&
349 tex_vertex_str[0] != '/'
350 ) {
351 int s_tex_vertex_index = atoi(tex_vertex_str);
352 index_t tex_vertex_index = 0;
353
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 320784 times.
320784 if(s_tex_vertex_index < 0) {
354 tex_vertex_index = index_t(
355 1+int(tex_vertices.size()) +
356 s_tex_vertex_index
357 );
358 } else {
359 320784 tex_vertex_index = index_t(
360 s_tex_vertex_index
361 );
362 }
363 if(
364
2/4
✓ Branch 0 taken 320784 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 320784 times.
✗ Branch 3 not taken.
641568 (tex_vertex_index < 1) ||
365 (tex_vertex_index > tex_vertices.size())
366 ) {
367 Logger::err("I/O")
368 << "Line " << in.line_number()
369 << ": facet corner #" << i
370 << " references an invalid tex vertex: "
371 << tex_vertex_index
372 << std::endl;
373 unbind_attributes();
374 return false;
375 }
376
1/2
✓ Branch 0 taken 320784 times.
✗ Branch 1 not taken.
320784 if(!ignore_tex_coords) {
377 facet_tex_vertices.push_back(
378
1/2
✓ Branch 1 taken 320784 times.
✗ Branch 2 not taken.
320784 tex_vertex_index-1
379 );
380 }
381 }
382 }
383
384 if(
385
3/4
✓ Branch 0 taken 94128 times.
✓ Branch 1 taken 106474 times.
✓ Branch 2 taken 94128 times.
✗ Branch 3 not taken.
294730 facet_tex_vertices.size() != 0 &&
386 facet_tex_vertices.size() != facet_vertices.size()
387 ) {
388 Logger::err("I/O")
389 << "Line " << in.line_number() << ": "
390 << "some facet vertices do not have tex vertices"
391 << std::endl;
392 unbind_attributes();
393 return false;
394 }
395
396
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 200602 times.
✓ Branch 3 taken 200602 times.
✗ Branch 4 not taken.
200602 index_t f = M.facets.create_polygon(
397 facet_vertices.size()
398 );
399
2/2
✓ Branch 0 taken 640343 times.
✓ Branch 1 taken 200602 times.
840945 for(index_t lv=0; lv<facet_vertices.size(); ++lv) {
400
2/2
✓ Branch 0 taken 476691 times.
✓ Branch 1 taken 163652 times.
640343 M.facets.set_vertex(f,lv,facet_vertices[lv]);
401 }
402
2/2
✓ Branch 0 taken 94128 times.
✓ Branch 1 taken 106474 times.
200602 if(facet_tex_vertices.size() != 0) {
403
2/2
✓ Branch 0 taken 320783 times.
✓ Branch 1 taken 94128 times.
414911 for(index_t lv=0; lv<facet_vertices.size(); ++lv) {
404 320783 index_t c = M.facets.corners_begin(f) + lv;
405 const vec2 vt =
406 320783 tex_vertices[facet_tex_vertices[lv]];
407 320783 tex_coord_[2*c] = vt.x;
408 320783 tex_coord_[2*c+1] = vt.y;
409 }
410 }
411 } else if(
412 ioflags.has_element(MESH_FACETS) &&
413
4/4
✓ Branch 0 taken 13211 times.
✓ Branch 1 taken 24 times.
✓ Branch 2 taken 1931 times.
✓ Branch 3 taken 13 times.
15179 facet_region_.is_bound() &&
414 in.field_matches(0, "#")
415 ) {
416 if(
417
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
13 in.nb_fields() >= 5 &&
418
2/4
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 1918 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1931 in.field_matches(1, "attribute") &&
419 in.field_matches(3, "facet")
420 ) {
421 if(
422 first_facet_attribute &&
423 in.field_matches(2, "chart") &&
424 in.field_matches(4, "integer")
425 ) {
426 read_facet_regions = true;
427 } else {
428 first_facet_attribute = false;
429 }
430 } else if(
431 read_facet_regions &&
432 in.nb_fields() >= 5 &&
433
1/4
✓ Branch 0 taken 1931 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1931 in.field_matches(1, "attrs") &&
434 in.field_matches(2, "f")
435 ) {
436 index_t facet_index = in.field_as_uint(3);
437 signed_index_t facet_region = in.field_as_int(4);
438 if(
439 (facet_index < 1) ||
440 (facet_index > M.facets.nb())
441 ) {
442
1/6
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 2 times.
2 Logger::err("I/O")
443 << "Line " << in.line_number()
444 << ": facet attributes "
445 << "reference an invalid facet: "
446 << facet_index
447 << std::endl;
448 unbind_attributes();
449 return false;
450 }
451
452 facet_region_[facet_index - 1] =
453 index_t(facet_region);
454 }
455 }
456 }
457 }
458
1/2
✓ Branch 1 taken 164 times.
✗ Branch 2 not taken.
164 unbind_attributes();
459 return true;
460 168 }
461
462
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 59 times.
59 bool save(
463 const Mesh& M, const std::string& filename,
464 const MeshIOFlags& ioflags
465 ) override {
466
467 std::string mtl_filename;
468 std::string mtl_filename_fullpath;
469
470
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 59 times.
59 if(ioflags.get_texture_filename().length() != 0) {
471 mtl_filename =
472 FileSystem::base_name(filename) + ".mtl";
473 mtl_filename_fullpath =
474 FileSystem::dir_name(filename) + "/" +
475 mtl_filename;
476 std::ofstream mtl_out(mtl_filename_fullpath.c_str());
477 if(!mtl_out) {
478 Logger::err("I/O") << "Could not create mtl file "
479 << mtl_filename_fullpath
480 << std::endl;
481 } else {
482 Logger::out("I/O") << "Saving file "
483 << mtl_filename_fullpath
484 << std::endl;
485 mtl_out << "newmtl Material_0" << std::endl;
486 mtl_out << "map_Kd "
487 << FileSystem::base_name(
488 ioflags.get_texture_filename()
489 )
490 << "."
491 << FileSystem::extension(
492 ioflags.get_texture_filename()
493 )
494 << std::endl;
495 }
496 }
497
498
1/10
✗ Branch 0 not taken.
✓ Branch 1 taken 59 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
59 geo_assert(M.vertices.dimension() >= dimension_);
499
1/2
✓ Branch 1 taken 59 times.
✗ Branch 2 not taken.
59 std::ofstream out(filename.c_str());
500
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 59 times.
59 if(!out) {
501 Logger::err("I/O")
502 << "Could not create file \'"
503 << filename << "\'" << std::endl;
504 return false;
505 }
506
507
1/2
✓ Branch 1 taken 59 times.
✗ Branch 2 not taken.
59 bind_attributes(M, ioflags, false);
508
509 std::vector<std::string> args;
510
1/2
✓ Branch 1 taken 59 times.
✗ Branch 2 not taken.
59 CmdLine::get_args(args);
511
3/4
✗ Branch 0 not taken.
✓ Branch 1 taken 2713 times.
✓ Branch 2 taken 2654 times.
✓ Branch 3 taken 59 times.
2713 for(index_t i = 0; i < args.size(); ++i) {
512 out << "# vorpaline " << args[i] << std::endl;
513 }
514
515
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 59 times.
59 if(mtl_filename.length() != 0) {
516 out << "mtllib " << mtl_filename << std::endl;
517 }
518
519
1/2
✓ Branch 1 taken 59 times.
✗ Branch 2 not taken.
59 vector<double> P(dimension_);
520
2/2
✓ Branch 0 taken 304048 times.
✓ Branch 1 taken 59 times.
304107 for(index_t v = 0; v < M.vertices.nb(); ++v) {
521 304048 get_mesh_point(M, v, P.data(), dimension_);
522
1/2
✓ Branch 1 taken 304048 times.
✗ Branch 2 not taken.
304048 out << "v ";
523
2/2
✓ Branch 0 taken 912144 times.
✓ Branch 1 taken 304048 times.
1216192 for(index_t c = 0; c < dimension_; ++c) {
524
2/4
✓ Branch 1 taken 912144 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 912144 times.
✗ Branch 5 not taken.
912144 out << P[c] << ' ';
525 }
526 out << std::endl;
527 }
528
529 // If mesh has facet corner tex coords, then "compress" tex coords
530 // by generating a single "texture vertex" (vt) for each group of
531 // corners with the same texture coordinates (makes the .obj file
532 // smaller).
533 vector<index_t> vt_old2new;
534 vector<index_t> vt_index;
535 if(tex_coord_.is_bound()) {
536
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 index_t nb_vt = Geom::colocate_by_lexico_sort(
537 &tex_coord_[0], 2, M.facet_corners.nb(), vt_old2new, 2
538 );
539
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 vt_index.assign(M.facet_corners.nb(), NO_INDEX);
540 index_t cur_vt=0;
541
2/2
✓ Branch 0 taken 60 times.
✓ Branch 1 taken 1 times.
61 for(index_t c: M.facet_corners) {
542
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 59 times.
60 if(vt_old2new[c] == c) {
543
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 out << "vt " << tex_coord_[2*c] << " "
544
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 << tex_coord_[2*c+1] << std::endl;
545 1 vt_index[c] = cur_vt;
546 1 ++cur_vt;
547 }
548 }
549
1/10
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
1 geo_assert(cur_vt == nb_vt);
550 } else if(vertex_tex_coord_.is_bound()) {
551 for(index_t v: M.vertices) {
552 out << "vt " << vertex_tex_coord_[2*v] << " "
553 << vertex_tex_coord_[2*v+1] << std::endl;
554 }
555 }
556
557 out << "usemtl Material_0" << std::endl;
558
1/2
✓ Branch 0 taken 59 times.
✗ Branch 1 not taken.
59 if(ioflags.has_element(MESH_FACETS)) {
559
2/2
✓ Branch 0 taken 293342 times.
✓ Branch 1 taken 59 times.
293401 for(index_t f: M.facets) {
560
1/2
✓ Branch 1 taken 293342 times.
✗ Branch 2 not taken.
293342 out << "f ";
561 1184083 for(index_t c = M.facets.corners_begin(f);
562
2/2
✓ Branch 0 taken 1184083 times.
✓ Branch 1 taken 293342 times.
1477425 c < M.facets.corners_end(f); ++c
563 ) {
564
1/2
✓ Branch 1 taken 1184083 times.
✗ Branch 2 not taken.
1184083 out << M.facet_corners.vertex(c) + 1;
565 if(tex_coord_.is_bound()) {
566
1/2
✓ Branch 1 taken 60 times.
✗ Branch 2 not taken.
60 out << "/" << vt_index[ vt_old2new[c] ] + 1;
567 } else if(vertex_tex_coord_.is_bound()) {
568 out << "/" << M.facet_corners.vertex(c) + 1;
569 }
570
1/2
✓ Branch 1 taken 1184083 times.
✗ Branch 2 not taken.
1184083 out << " ";
571 }
572 out << std::endl;
573 }
574 if(facet_region_.is_bound()) {
575 out << "# attribute chart facet integer" << std::endl;
576
2/2
✓ Branch 0 taken 210409 times.
✓ Branch 1 taken 36 times.
210445 for(index_t f: M.facets) {
577 out << "# attrs f "
578
1/2
✓ Branch 1 taken 210409 times.
✗ Branch 2 not taken.
210409 << f + 1 << " "
579
1/2
✓ Branch 1 taken 210409 times.
✗ Branch 2 not taken.
210409 << facet_region_[f] << std::endl;
580 }
581 }
582 }
583
584
1/2
✓ Branch 0 taken 59 times.
✗ Branch 1 not taken.
59 if(ioflags.has_element(MESH_EDGES)) {
585
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 59 times.
59 for(index_t e: M.edges) {
586 out << "l "
587 << M.edges.vertex(e,0)+1 << " "
588 << M.edges.vertex(e,1)+1 << std::endl;
589 }
590 }
591
592
1/2
✓ Branch 1 taken 59 times.
✗ Branch 2 not taken.
59 unbind_attributes();
593
594 return true;
595 59 }
596
597 protected:
598
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 227 times.
908 ~OBJIOHandler() override {
599 908 }
600
601 226 void bind_attributes(
602 const Mesh& M, const MeshIOFlags& flags, bool create
603 ) override {
604 226 MeshIOHandler::bind_attributes(M, flags, create);
605
606
1/2
✓ Branch 1 taken 226 times.
✗ Branch 2 not taken.
226 tex_coord_.bind_if_is_defined(
607
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 225 times.
452 M.facet_corners.attributes(), "tex_coord"
608 );
609
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if(tex_coord_.is_bound() && tex_coord_.dimension() != 2) {
610 tex_coord_.unbind();
611 }
612
613
1/2
✓ Branch 1 taken 226 times.
✗ Branch 2 not taken.
226 vertex_tex_coord_.bind_if_is_defined(
614
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 226 times.
452 M.vertices.attributes(), "tex_coord"
615 );
616 if(
617 vertex_tex_coord_.is_bound() &&
618 vertex_tex_coord_.dimension() != 2
619 ) {
620 vertex_tex_coord_.unbind();
621 }
622
623 226 }
624
625
2/2
✓ Branch 0 taken 115 times.
✓ Branch 1 taken 109 times.
224 void unbind_attributes() override {
626 if(tex_coord_.is_bound()) {
627 115 tex_coord_.unbind();
628 }
629 if(vertex_tex_coord_.is_bound()) {
630 vertex_tex_coord_.unbind();
631 }
632 224 MeshIOHandler::unbind_attributes();
633 224 }
634
635 private:
636 coord_index_t dimension_;
637 Attribute<double> tex_coord_;
638 Attribute<double> vertex_tex_coord_;
639 };
640
641
642 /************************************************************************/
643
644 /**
645 * \brief IO handler for the OBJ6 file format
646 * \see OBJIOHandler
647 */
648 class GEOGRAM_API OBJ6IOHandler : public OBJIOHandler {
649 public:
650 OBJ6IOHandler() :
651 OBJIOHandler(6) {
652 }
653 };
654
655 /************************************************************************/
656
657
658 /**
659 * \brief IO handler for LM5/LM6/Gamma mesh file format
660 * \see http://www-roc.inria.fr/gamma/gamma/Membres/CIPD/Loic.Marechal/Research/LM5.html
661 */
662 class GEOGRAM_API LMIOHandler : public MeshIOHandler {
663 public:
664
665
2/4
✓ Branch 0 taken 22748 times.
✓ Branch 1 taken 94 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
22842 LMIOHandler() {
666
667
1/2
✓ Branch 1 taken 94 times.
✗ Branch 2 not taken.
94 geo_cite("WEB:libMeshb");
668
669
1/2
✓ Branch 1 taken 94 times.
✗ Branch 2 not taken.
94 keyword2name_[GmfTriangles] = "triangle";
670
1/2
✓ Branch 1 taken 94 times.
✗ Branch 2 not taken.
94 keyword2name_[GmfQuadrilaterals] = "quad";
671
1/2
✓ Branch 1 taken 94 times.
✗ Branch 2 not taken.
94 keyword2name_[GmfTetrahedra] = "tet";
672
1/2
✓ Branch 1 taken 94 times.
✗ Branch 2 not taken.
94 keyword2name_[GmfHexahedra] = "hex";
673
1/2
✓ Branch 1 taken 94 times.
✗ Branch 2 not taken.
94 keyword2name_[GmfPrisms] = "prism";
674
1/2
✓ Branch 1 taken 94 times.
✗ Branch 2 not taken.
94 keyword2name_[GmfPyramids] = "pyramid";
675
1/2
✓ Branch 1 taken 94 times.
✗ Branch 2 not taken.
94 keyword2name_[GmfEdges] = "edge";
676 94 keyword2nbv_[GmfTriangles] = 3;
677 94 keyword2nbv_[GmfQuadrilaterals] = 4;
678 94 keyword2nbv_[GmfTetrahedra] = 4;
679 94 keyword2nbv_[GmfHexahedra] = 8;
680 94 keyword2nbv_[GmfPrisms] = 6;
681 94 keyword2nbv_[GmfPyramids] = 5;
682 94 keyword2nbv_[GmfEdges] = 2;
683
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
94 }
684
685 39 bool load(
686 const std::string& filename, Mesh& M,
687 const MeshIOFlags& ioflags
688 ) override {
689
690 int ver, dim;
691 39 int64_t mesh_file_handle = GmfOpenMesh(
692 const_cast<char*>(filename.c_str()), GmfRead, &ver, &dim
693 );
694
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 38 times.
39 if(!mesh_file_handle) {
695
1/2
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 Logger::err("I/O") << "Could not open file: "
696 << filename << std::endl;
697 1 return false;
698 }
699
700 // indices coords
701 // ver=1 int32 float32
702 // ver=2 int32 float64
703 // ver=3 int32 float64
704 // ver=4 int64 float64
705 // TODO: handle ver=4 in GARGANTUA mode
706
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 38 times.
38 if(ver != 1 && ver != 2 && ver != 3) {
707 Logger::err("I/O") << "Invalid version: " << ver << std::endl;
708 GmfCloseMesh(mesh_file_handle);
709 return false;
710 }
711
712 bool use_doubles = (ver != 1);
713
714
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 38 times.
38 if(dim != 3 && dim != 2) {
715 Logger::err("I/O") << "Invalid dimension: " << dim << std::endl;
716 GmfCloseMesh(mesh_file_handle);
717 return false;
718 }
719
720 38 bind_attributes(M, ioflags, true);
721
722 index_t nb_vertices =
723 38 index_t(GmfStatKwd(mesh_file_handle, GmfVertices));
724
725 index_t nb_edges =
726 38 index_t(GmfStatKwd(mesh_file_handle, GmfEdges));
727
728 index_t nb_tris =
729 38 index_t(GmfStatKwd(mesh_file_handle, GmfTriangles));
730 index_t nb_quads =
731 38 index_t(GmfStatKwd(mesh_file_handle, GmfQuadrilaterals));
732
733 index_t nb_tets =
734 38 index_t(GmfStatKwd(mesh_file_handle, GmfTetrahedra));
735 index_t nb_hexes =
736 38 index_t(GmfStatKwd(mesh_file_handle, GmfHexahedra));
737 index_t nb_prisms =
738 38 index_t(GmfStatKwd(mesh_file_handle, GmfPrisms));
739 index_t nb_pyramids =
740 38 index_t(GmfStatKwd(mesh_file_handle, GmfPyramids));
741
742 // Read vertices
743
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 38 times.
38 if(!goto_elements(mesh_file_handle, GmfVertices)) {
744 return false;
745 }
746 M.vertices.create_vertices(nb_vertices);
747
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 26 times.
38 if(use_doubles) {
748
2/2
✓ Branch 0 taken 13492 times.
✓ Branch 1 taken 12 times.
13504 for(index_t v = 0; v < index_t(nb_vertices); ++v) {
749 double xyz[3];
750 13492 int ref = 0;
751 13492 xyz[2] = 0.0;
752
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 13492 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
13492 if(dim == 2 && !GmfGetLin(
753 mesh_file_handle, GmfVertices,
754 &xyz[0], &xyz[1], &ref
755 )) {
756 Logger::err("I/O") << "Failed to read vertex #" << v
757 << std::endl;
758 GmfCloseMesh(mesh_file_handle);
759 unbind_attributes();
760 return false;
761 }
762
2/4
✓ Branch 0 taken 13492 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 13492 times.
✗ Branch 4 not taken.
13492 if(dim == 3 && !GmfGetLin(
763 mesh_file_handle, GmfVertices,
764 &xyz[0], &xyz[1], &xyz[2], &ref
765 )) {
766 Logger::err("I/O") << "Failed to read vertex #" << v
767 << std::endl;
768 GmfCloseMesh(mesh_file_handle);
769 unbind_attributes();
770 return false;
771 }
772 13492 set_mesh_point(M,v,xyz,3);
773 if(vertex_region_.is_bound()) {
774 vertex_region_[v] = index_t(ref);
775 }
776 }
777 } else {
778
2/2
✓ Branch 0 taken 56101 times.
✓ Branch 1 taken 26 times.
56127 for(index_t v = 0; v < index_t(nb_vertices); ++v) {
779 56101 float x=0.0f,y=0.0f,z=0.0f;
780 double xyz[3];
781 56101 int ref = 0;
782
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 56101 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
56101 if(dim == 2 && !GmfGetLin(
783 mesh_file_handle, GmfVertices, &x, &y, &ref)
784 ) {
785 Logger::err("I/O") << "Failed to read vertex #" << v
786 << std::endl;
787 GmfCloseMesh(mesh_file_handle);
788 return false;
789 }
790
2/4
✓ Branch 0 taken 56101 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 56101 times.
✗ Branch 4 not taken.
56101 if(dim == 3 && !GmfGetLin(
791 mesh_file_handle, GmfVertices, &x, &y, &z, &ref)
792 ) {
793 Logger::err("I/O") << "Failed to read vertex #" << v
794 << std::endl;
795 GmfCloseMesh(mesh_file_handle);
796 return false;
797 }
798 56101 xyz[0] = double(x);
799 56101 xyz[1] = double(y);
800 56101 xyz[2] = double(z);
801 56101 set_mesh_point(M,v,xyz,3);
802 if(vertex_region_.is_bound()) {
803 vertex_region_[v] = index_t(ref);
804 }
805 }
806 }
807
808
1/2
✓ Branch 0 taken 38 times.
✗ Branch 1 not taken.
38 if(ioflags.has_element(MESH_EDGES)) {
809
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 38 times.
38 if(nb_edges > 0) {
810 if(!goto_elements(mesh_file_handle, GmfEdges)) {
811 return false;
812 }
813 index_t first_edge = M.edges.create_edges(nb_edges);
814 int v[8];
815 int ref;
816 for(index_t e=0; e<nb_edges; ++e) {
817 if(!read_element(
818 mesh_file_handle, GmfEdges, v, ref, M, e
819 )) {
820 return false;
821 }
822 for(index_t lv=0; lv<2; ++lv) {
823 M.edges.set_vertex(
824 first_edge+e, lv, index_t(v[lv]-1)
825 );
826 }
827 if(edge_region_.is_bound()) {
828 edge_region_[first_edge+e] = index_t(ref);
829 }
830 }
831 }
832 }
833
834
1/2
✓ Branch 0 taken 38 times.
✗ Branch 1 not taken.
38 if(ioflags.has_element(MESH_FACETS)) {
835 // Read triangles
836
2/2
✓ Branch 0 taken 34 times.
✓ Branch 1 taken 4 times.
38 if(nb_tris > 0) {
837
1/2
✓ Branch 1 taken 34 times.
✗ Branch 2 not taken.
34 if(!goto_elements(mesh_file_handle, GmfTriangles)) {
838 2 return false;
839 }
840 34 index_t first_tri = M.facets.create_triangles(nb_tris);
841 int v[8];
842 int ref;
843
2/2
✓ Branch 0 taken 126255 times.
✓ Branch 1 taken 32 times.
126287 for(index_t t=0; t<nb_tris; ++t) {
844
2/2
✓ Branch 1 taken 126253 times.
✓ Branch 2 taken 2 times.
126255 if(!read_element(
845 mesh_file_handle, GmfTriangles, v, ref, M, t
846 )) {
847 return false;
848 }
849
2/2
✓ Branch 0 taken 378759 times.
✓ Branch 1 taken 126253 times.
505012 for(index_t lv=0; lv<3; ++lv) {
850 378759 M.facets.set_vertex(
851
1/2
✓ Branch 0 taken 378759 times.
✗ Branch 1 not taken.
378759 first_tri+t, lv, index_t(v[lv]-1)
852 );
853 }
854 if(facet_region_.is_bound()) {
855 facet_region_[first_tri+t] = index_t(ref);
856 }
857 }
858 }
859
860 // Read quads
861
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 32 times.
36 if(nb_quads > 0) {
862
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 if(!goto_elements(mesh_file_handle, GmfQuadrilaterals)) {
863 return false;
864 }
865 4 index_t first_quad = M.facets.create_quads(nb_quads);
866 int v[8];
867 int ref;
868
2/2
✓ Branch 0 taken 40 times.
✓ Branch 1 taken 4 times.
44 for(index_t q=0; q<nb_quads; ++q) {
869
1/2
✓ Branch 1 taken 40 times.
✗ Branch 2 not taken.
40 if(!read_element(
870 mesh_file_handle, GmfQuadrilaterals,
871 v, ref, M, q
872 )) {
873 return false;
874 }
875
2/2
✓ Branch 0 taken 160 times.
✓ Branch 1 taken 40 times.
200 for(index_t lv=0; lv<4; ++lv) {
876 160 M.facets.set_vertex(
877
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 160 times.
160 first_quad+q, lv, index_t(v[lv]-1)
878 );
879 }
880 if(facet_region_.is_bound()) {
881 facet_region_[first_quad+q] = index_t(ref);
882 }
883 }
884 }
885 }
886
887
1/2
✓ Branch 0 taken 36 times.
✗ Branch 1 not taken.
36 if(ioflags.has_element(MESH_CELLS)) {
888
889 // Read tets
890
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 35 times.
36 if(nb_tets > 0) {
891
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 if(!goto_elements(mesh_file_handle, GmfTetrahedra)) {
892 return false;
893 }
894 1 index_t first_tet = M.cells.create_tets(nb_tets);
895 int v[8];
896 int ref;
897
2/2
✓ Branch 0 taken 10535 times.
✓ Branch 1 taken 1 times.
10536 for(index_t t=0; t<nb_tets; ++t) {
898
1/2
✓ Branch 1 taken 10535 times.
✗ Branch 2 not taken.
10535 if(!read_element(
899 mesh_file_handle, GmfTetrahedra, v, ref, M, t
900 )) {
901 return false;
902 }
903
2/2
✓ Branch 0 taken 42140 times.
✓ Branch 1 taken 10535 times.
52675 for(index_t lv=0; lv<4; ++lv) {
904 42140 M.cells.set_vertex(
905
1/2
✓ Branch 0 taken 42140 times.
✗ Branch 1 not taken.
42140 first_tet+t, lv, index_t(v[lv]-1)
906 );
907 }
908 if(cell_region_.is_bound()) {
909 cell_region_[first_tet+t] = index_t(ref);
910 }
911 }
912 }
913
914 // Read hexes
915
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 36 times.
36 if(nb_hexes > 0) {
916 if(!goto_elements(mesh_file_handle, GmfHexahedra)) {
917 return false;
918 }
919 index_t first_hex = M.cells.create_hexes(nb_hexes);
920
921 int v[8];
922 int ref;
923 for(index_t h=0; h<nb_hexes; ++h) {
924 if(!read_element(
925 mesh_file_handle, GmfHexahedra, v, ref, M, h
926 )) {
927 return false;
928 }
929
930 // Swapping vertices 1<->0 and 4<->5 to
931 // account for differences in the indexing
932 // convetions in .mesh/.meshb files w.r.t.
933 // geogram internal conventions.
934 std::swap(v[0], v[1]);
935 std::swap(v[4], v[5]);
936
937 for(index_t lv=0; lv<8; ++lv) {
938 M.cells.set_vertex(
939 first_hex+h, lv, index_t(v[lv]-1)
940 );
941 }
942 if(cell_region_.is_bound()) {
943 cell_region_[first_hex+h] = index_t(ref);
944 }
945 }
946 }
947
948 // Read prisms
949
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 36 times.
36 if(nb_prisms > 0) {
950 if(!goto_elements(mesh_file_handle, GmfPrisms)) {
951 return false;
952 }
953 index_t first_prism = M.cells.create_prisms(nb_prisms);
954 int v[8];
955 int ref;
956 for(index_t p=0; p<nb_prisms; ++p) {
957 if(!read_element(
958 mesh_file_handle, GmfPrisms, v, ref, M, p
959 )) {
960 return false;
961 }
962 for(index_t lv=0; lv<6; ++lv) {
963 M.cells.set_vertex(
964 first_prism+p, lv, index_t(v[lv]-1)
965 );
966 }
967 if(cell_region_.is_bound()) {
968 cell_region_[first_prism+p] = index_t(ref);
969 }
970 }
971 }
972
973 // Read pyramids
974
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 36 times.
36 if(nb_pyramids > 0) {
975 if(!goto_elements(mesh_file_handle, GmfPyramids)) {
976 return false;
977 }
978 index_t first_pyramid =
979 M.cells.create_pyramids(nb_pyramids);
980 int v[8];
981 int ref;
982 for(index_t p=0; p<nb_pyramids; ++p) {
983 if(!read_element(
984 mesh_file_handle, GmfPyramids, v, ref, M, p
985 )) {
986 return false;
987 }
988 for(index_t lv=0; lv<5; ++lv) {
989 M.cells.set_vertex(
990 first_pyramid+p, lv, index_t(v[lv]-1)
991 );
992 }
993 if(cell_region_.is_bound()) {
994 cell_region_[first_pyramid+p] = index_t(ref);
995 }
996 }
997 }
998 }
999
1000 36 GmfCloseMesh(mesh_file_handle);
1001 36 unbind_attributes();
1002 return true;
1003 }
1004
1005 55 bool save(
1006 const Mesh& M, const std::string& filename,
1007 const MeshIOFlags& ioflags
1008 ) override {
1009
1/2
✓ Branch 2 taken 55 times.
✗ Branch 3 not taken.
55 bool use_doubles = CmdLine::get_arg_bool("sys:use_doubles");
1010
1011 // indices coords
1012 // ver=1 int32 float32
1013 // ver=2 int32 float64
1014 // ver=3 int32 float64
1015 // ver=4 int64 float64
1016 // TODO: handle ver=4 in GARGANTUA mode
1017
1/2
✓ Branch 0 taken 55 times.
✗ Branch 1 not taken.
55 int ver = use_doubles ? 2 : 1;
1018
1019 55 int64_t mesh_file_handle = GmfOpenMesh(
1020 const_cast<char*>(filename.c_str()), GmfWrite,
1021 ver, 3
1022 );
1023
1024
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 55 times.
55 if(mesh_file_handle == 0) {
1025 Logger::err("I/O")
1026 << "Could not create file \'" << filename << "\'"
1027 << std::endl;
1028 return false;
1029 }
1030 55 bind_attributes(M, ioflags, false);
1031
1032 // Save vertices
1033 55 GmfSetKwd(mesh_file_handle, GmfVertices, int64_t(M.vertices.nb()));
1034
2/2
✓ Branch 0 taken 145160 times.
✓ Branch 1 taken 55 times.
145215 for(index_t v = 0; v < M.vertices.nb(); ++v) {
1035 double xyz[3];
1036 index_t ref =
1037 22680 vertex_region_.is_bound() ? vertex_region_[v] : 0;
1038 145160 get_mesh_point(M, v, xyz, 3);
1039 145160 GmfSetLin(
1040 mesh_file_handle, GmfVertices, xyz[0], xyz[1], xyz[2], ref
1041 );
1042 }
1043
1044
1/2
✓ Branch 0 taken 55 times.
✗ Branch 1 not taken.
55 if(ioflags.has_element(MESH_FACETS)) {
1045
1046 index_t nb_tris = 0;
1047 index_t nb_quads = 0;
1048 index_t nb_other = 0;
1049
1050
2/2
✓ Branch 0 taken 284905 times.
✓ Branch 1 taken 55 times.
284960 for(index_t f = 0; f < M.facets.nb(); ++f) {
1051 switch(M.facets.nb_vertices(f)) {
1052 284905 case 3:
1053 284905 nb_tris++;
1054 284905 break;
1055 case 4:
1056 nb_quads++;
1057 break;
1058 default:
1059 nb_other++;
1060 break;
1061 }
1062 }
1063
1064
2/2
✓ Branch 0 taken 51 times.
✓ Branch 1 taken 4 times.
55 if(nb_tris > 0) {
1065 51 GmfSetKwd(mesh_file_handle, GmfTriangles, int64_t(nb_tris));
1066
2/2
✓ Branch 0 taken 284905 times.
✓ Branch 1 taken 51 times.
284956 for(index_t f = 0; f < M.facets.nb(); ++f) {
1067 if(M.facets.nb_vertices(f) == 3) {
1068 index_t ref =
1069 facet_region_.is_bound() ?
1070 45308 facet_region_[f] : 0 ;
1071
1/2
✓ Branch 0 taken 284905 times.
✗ Branch 1 not taken.
284905 GmfSetLin(
1072 mesh_file_handle, GmfTriangles,
1073 284905 int(M.facets.vertex(f,0)+1),
1074 284905 int(M.facets.vertex(f,1)+1),
1075 284905 int(M.facets.vertex(f,2)+1),
1076 int(ref)
1077 );
1078 }
1079 }
1080 }
1081
1082
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 55 times.
55 if(nb_quads > 0) {
1083 GmfSetKwd(
1084 mesh_file_handle, GmfQuadrilaterals, int64_t(nb_quads)
1085 );
1086 for(index_t f = 0; f < M.facets.nb(); ++f) {
1087 if(M.facets.nb_vertices(f) == 4) {
1088 index_t ref =
1089 facet_region_.is_bound() ?
1090 facet_region_[f] : 0 ;
1091 GmfSetLin(
1092 mesh_file_handle, GmfQuadrilaterals,
1093 int(M.facets.vertex(f,0)+1),
1094 int(M.facets.vertex(f,1)+1),
1095 int(M.facets.vertex(f,2)+1),
1096 int(M.facets.vertex(f,3)+1),
1097 int(ref)
1098 );
1099 }
1100 }
1101 }
1102
1103
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 55 times.
55 if(nb_other > 0) {
1104 Logger::warn("I/O")
1105 << "Encountered " << nb_other
1106 << " non-tri / non-quad facets"
1107 << " (not saved)"
1108 << std::endl;
1109 Logger::warn("I/O")
1110 << "Use another file format (e.g., .obj or .geogram)"
1111 << std::endl;
1112
1113 }
1114 }
1115
1116
1/2
✓ Branch 0 taken 55 times.
✗ Branch 1 not taken.
55 if(ioflags.has_element(MESH_EDGES)) {
1117 55 GmfSetKwd(mesh_file_handle, GmfEdges, int64_t(M.edges.nb()));
1118
2/2
✓ Branch 0 taken 164 times.
✓ Branch 1 taken 55 times.
219 for(index_t e=0; e<M.edges.nb(); ++e) {
1119 index_t ref = 0;
1120 164 GmfSetLin(
1121 mesh_file_handle, GmfEdges,
1122 164 int(M.edges.vertex(e,0) + 1),
1123 164 int(M.edges.vertex(e,1) + 1),
1124 ref
1125 );
1126 }
1127 }
1128
1129
1/2
✓ Branch 0 taken 55 times.
✗ Branch 1 not taken.
55 if(ioflags.has_element(MESH_CELLS)) {
1130 index_t nb_tets=0;
1131 index_t nb_hexes=0;
1132 index_t nb_prisms=0;
1133 index_t nb_pyramids=0;
1134
2/2
✓ Branch 0 taken 12093 times.
✓ Branch 1 taken 55 times.
12148 for(index_t c=0; c<M.cells.nb(); ++c) {
1135 switch(M.cells.type(c)) {
1136 12093 case MESH_TET:
1137 12093 ++nb_tets;
1138 12093 break;
1139 case MESH_HEX:
1140 ++nb_hexes;
1141 break;
1142 case MESH_PRISM:
1143 ++nb_prisms;
1144 break;
1145 case MESH_PYRAMID:
1146 ++nb_pyramids;
1147 break;
1148 case MESH_CONNECTOR:
1149 case MESH_NB_CELL_TYPES:
1150 break;
1151 }
1152 }
1153
1154
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 50 times.
55 if(nb_tets > 0) {
1155 5 GmfSetKwd(
1156 mesh_file_handle, GmfTetrahedra, int64_t(nb_tets)
1157 );
1158
2/2
✓ Branch 0 taken 12093 times.
✓ Branch 1 taken 5 times.
12098 for(index_t c=0; c<M.cells.nb(); ++c) {
1159 if(M.cells.type(c) == MESH_TET) {
1160 index_t ref =
1161 cell_region_.is_bound() ? cell_region_[c] : 0;
1162 12093 GmfSetLin(
1163 mesh_file_handle, GmfTetrahedra,
1164 12093 int(M.cells.vertex(c,0) + 1),
1165 12093 int(M.cells.vertex(c,1) + 1),
1166 12093 int(M.cells.vertex(c,2) + 1),
1167 12093 int(M.cells.vertex(c,3) + 1),
1168 ref
1169 );
1170 }
1171 }
1172 }
1173
1174
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 55 times.
55 if(nb_hexes > 0) {
1175 GmfSetKwd(
1176 mesh_file_handle, GmfHexahedra, int64_t(nb_hexes)
1177 );
1178 for(index_t c=0; c<M.cells.nb(); ++c) {
1179 if(M.cells.type(c) == MESH_HEX) {
1180 index_t ref =
1181 cell_region_.is_bound() ? cell_region_[c] : 0;
1182
1183 // Swapping vertices 1<->0 and 4<->5 to
1184 // account for differences in the indexing
1185 // convetions in .mesh/.meshb files w.r.t.
1186 // geogram internal conventions.
1187
1188 GmfSetLin(
1189 mesh_file_handle, GmfHexahedra,
1190 int(M.cells.vertex(c,1) + 1),
1191 int(M.cells.vertex(c,0) + 1),
1192 int(M.cells.vertex(c,2) + 1),
1193 int(M.cells.vertex(c,3) + 1),
1194 int(M.cells.vertex(c,5) + 1),
1195 int(M.cells.vertex(c,4) + 1),
1196 int(M.cells.vertex(c,6) + 1),
1197 int(M.cells.vertex(c,7) + 1),
1198 ref
1199 );
1200 }
1201 }
1202 }
1203
1204
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 55 times.
55 if(nb_prisms > 0) {
1205 GmfSetKwd(mesh_file_handle, GmfPrisms, int64_t(nb_prisms));
1206 for(index_t c=0; c<M.cells.nb(); ++c) {
1207 if(M.cells.type(c) == MESH_PRISM) {
1208 index_t ref =
1209 cell_region_.is_bound() ? cell_region_[c] : 0;
1210 GmfSetLin(
1211 mesh_file_handle, GmfPrisms,
1212 int(M.cells.vertex(c,0) + 1),
1213 int(M.cells.vertex(c,1) + 1),
1214 int(M.cells.vertex(c,2) + 1),
1215 int(M.cells.vertex(c,3) + 1),
1216 int(M.cells.vertex(c,4) + 1),
1217 int(M.cells.vertex(c,5) + 1),
1218 ref
1219 );
1220 }
1221 }
1222 }
1223
1224
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 55 times.
55 if(nb_pyramids > 0) {
1225 GmfSetKwd(
1226 mesh_file_handle, GmfPyramids, int64_t(nb_pyramids)
1227 );
1228 for(index_t c=0; c<M.cells.nb(); ++c) {
1229 if(M.cells.type(c) == MESH_PYRAMID) {
1230 index_t ref =
1231 cell_region_.is_bound() ? cell_region_[c] : 0;
1232 GmfSetLin(
1233 mesh_file_handle, GmfPyramids,
1234 int(M.cells.vertex(c,0) + 1),
1235 int(M.cells.vertex(c,1) + 1),
1236 int(M.cells.vertex(c,2) + 1),
1237 int(M.cells.vertex(c,3) + 1),
1238 int(M.cells.vertex(c,4) + 1),
1239 ref
1240 );
1241 }
1242 }
1243 }
1244
1245 }
1246
1247 55 unbind_attributes();
1248 55 GmfCloseMesh(mesh_file_handle);
1249
1250 // If file is in ASCII, append parameters as comments
1251 // at the end of the file.
1252
3/4
✓ Branch 1 taken 55 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 10 times.
✓ Branch 4 taken 45 times.
110 if(FileSystem::extension(filename) == "mesh") {
1253 10 FILE* f = fopen(filename.c_str(), "a");
1254 std::vector<std::string> args;
1255
1/2
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
10 CmdLine::get_args(args);
1256
3/4
✗ Branch 0 not taken.
✓ Branch 1 taken 377 times.
✓ Branch 2 taken 367 times.
✓ Branch 3 taken 10 times.
377 for(index_t i = 0; i < args.size(); i++) {
1257 fprintf(f, "# vorpaline %s\n", args[i].c_str());
1258 }
1259
1/2
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
10 fclose(f);
1260 10 }
1261
1262 return true;
1263 }
1264
1265 protected:
1266 77 bool goto_elements(int64_t mesh_file_handle, int keyword) {
1267
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 77 times.
77 if(!GmfGotoKwd(mesh_file_handle, keyword)) {
1268 Logger::err("I/O") << "Failed to access "
1269 << keyword2name_[keyword]
1270 << " section"
1271 << std::endl;
1272 GmfCloseMesh(mesh_file_handle);
1273 unbind_attributes();
1274 return false;
1275 }
1276 return true;
1277 }
1278
1279 136830 bool read_element(
1280 int64_t mesh_file_handle,
1281 int keyword, int *v, int& ref,
1282 Mesh& M, index_t element_id
1283 ) {
1284 136830 index_t nbv = keyword2nbv_[keyword];
1285 int res = 0;
1286
2/7
✗ Branch 0 not taken.
✓ Branch 1 taken 126255 times.
✓ Branch 2 taken 10575 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
136830 switch(nbv) {
1287 case 2:
1288 res = GmfGetLin(
1289 mesh_file_handle, keyword,
1290 &v[0], &v[1], &ref
1291 );
1292 break;
1293 126255 case 3:
1294 126255 res = GmfGetLin(
1295 mesh_file_handle, keyword,
1296 &v[0], &v[1], &v[2], &ref
1297 );
1298 126255 break;
1299 10575 case 4:
1300 10575 res = GmfGetLin(
1301 mesh_file_handle, keyword,
1302 &v[0], &v[1], &v[2], &v[3], &ref
1303 );
1304 10575 break;
1305 case 5:
1306 res = GmfGetLin(
1307 mesh_file_handle, keyword,
1308 &v[0], &v[1], &v[2], &v[3], &v[4],
1309 &ref
1310 );
1311 break;
1312 case 6:
1313 res = GmfGetLin(
1314 mesh_file_handle, keyword,
1315 &v[0], &v[1], &v[2],
1316 &v[3], &v[4], &v[5],
1317 &ref
1318 );
1319 break;
1320 case 8:
1321 res = GmfGetLin(
1322 mesh_file_handle, keyword,
1323 &v[0], &v[1], &v[2], &v[3],
1324 &v[4], &v[5], &v[6], &v[7],
1325 &ref
1326 );
1327 break;
1328 default:
1329 geo_assert_not_reached;
1330 }
1331
1332
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 136829 times.
136830 if(!res) {
1333
1/2
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 Logger::err("I/O")
1334 << "Failed to read "
1335 << keyword2name_[keyword]
1336 << " #" << element_id
1337 << std::endl;
1338 1 GmfCloseMesh(mesh_file_handle);
1339 1 unbind_attributes();
1340 1 return false;
1341 }
1342
1343
2/2
✓ Branch 0 taken 421061 times.
✓ Branch 1 taken 136828 times.
557889 for(index_t lv=0; lv < nbv; ++lv) {
1344 if(
1345
3/4
✓ Branch 0 taken 421061 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 421060 times.
421061 v[lv] < 1 ||
1346
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 421060 times.
421061 index_t(v[lv]) > M.vertices.nb()
1347 ) {
1348
1/2
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 Logger::err("I/O")
1349 << "Error: " << keyword2name_[keyword]
1350 <<" # " << element_id
1351
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 << " references an invalid vertex: " << v[lv]
1352 << std::endl;
1353 1 GmfCloseMesh(mesh_file_handle);
1354 1 unbind_attributes();
1355 1 return false;
1356 }
1357 }
1358
1359 return true;
1360 }
1361
1362 protected:
1363 std::string keyword2name_[GmfLastKeyword+1];
1364 index_t keyword2nbv_[GmfLastKeyword+1];
1365 };
1366
1367 /************************************************************************/
1368
1369 /**
1370 * \brief IO handler for the PLY file format
1371 * \details ASCII and binary, single and double precision are supported
1372 * \see http://en.wikipedia.org/wiki/PLY_(file_format)
1373 */
1374 class GEOGRAM_API PLYIOHandler : public MeshIOHandler {
1375 public:
1376
1377 /**
1378 * \brief PLYIOHandler constructor.
1379 */
1380
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 PLYIOHandler() {
1381
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 geo_cite("WEB:rply");
1382 5 }
1383
1384 /**
1385 * \brief Helper class to read files in PLY format
1386 */
1387 class PlyLoader {
1388 public:
1389
1390 /**
1391 * \brief Construts a new PlyLoader.
1392 * \param[in] filename name of the file to be loaded
1393 * \param[out] M the loaded mesh
1394 * \param[in] flags flags that determine which elements
1395 * and attributes should be read
1396 */
1397 4 PlyLoader(
1398 const std::string& filename, Mesh& M, const MeshIOFlags& flags
1399 4 ) :
1400 4 mesh_(M),
1401
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 filename_(filename),
1402 flags_(flags),
1403 4 current_vertex_(max_index_t()),
1404 4 has_colors_(false),
1405 4 color_mult_(1.0),
1406 4 current_color_(max_index_t()),
1407 4 tristrip_index_(0),
1408 4 load_colors_(true) {
1409 4 }
1410
1411 /**
1412 * \brief Specifies whether vertices colors should be loaded.
1413 */
1414 void set_load_colors(bool x) {
1415 load_colors_ = x;
1416 }
1417
1418 /**
1419 * \brief Loads the file.
1420 * \return true on success, false otherwise
1421 */
1422 4 bool load() {
1423 4 p_ply ply = ply_open(filename_.c_str(), nullptr, 0, nullptr);
1424
1425
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if(ply == nullptr) {
1426 Logger::err("I/O")
1427 << "Could not open file: " << filename_
1428 << std::endl;
1429 return false;
1430 }
1431
1432
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
4 if(!ply_read_header(ply)) {
1433 Logger::err("I/O")
1434 << "Invalid PLY header"
1435 << std::endl;
1436 ply_close(ply);
1437 return false;
1438 }
1439
1440 4 current_vertex_ = 0;
1441 4 current_color_ = 0;
1442
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if(load_colors_) {
1443 4 check_for_colors(ply);
1444 } else {
1445 has_colors_ = false;
1446 }
1447
1448 4 long nvertices = ply_set_read_cb(
1449 ply, "vertex", "x", PlyLoader::vertex_cb, this, 0
1450 );
1451 4 ply_set_read_cb(
1452 ply, "vertex", "y", PlyLoader::vertex_cb, this, 1
1453 );
1454 4 ply_set_read_cb(
1455 ply, "vertex", "z", PlyLoader::vertex_cb, this, 2
1456 );
1457
1458 long nfaces = 0;
1459 long ntstrips = 0;
1460
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if(flags_.has_element(MESH_FACETS)) {
1461 4 nfaces += ply_set_read_cb(
1462 ply, "face", "vertex_indices",
1463 PlyLoader::face_cb, this, 0
1464 );
1465 4 nfaces += ply_set_read_cb(
1466 ply, "face", "vertex_index",
1467 PlyLoader::face_cb, this, 0
1468 );
1469 4 ntstrips += ply_set_read_cb(
1470 ply, "tristrips", "vertex_indices",
1471 PlyLoader::tristrip_cb, this, 0
1472 );
1473 4 ntstrips += ply_set_read_cb(
1474 ply, "tristrips", "vertex_index",
1475 PlyLoader::tristrip_cb, this, 0
1476 );
1477 }
1478
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if(nvertices == 0) {
1479 Logger::err("I/O")
1480 << "File contains no vertices" << std::endl;
1481 ply_close(ply);
1482 return false;
1483 }
1484
1485 4 mesh_.vertices.create_vertices(index_t(nvertices));
1486
1487 geo_argused(nfaces);
1488 geo_argused(ntstrips);
1489 // TODO: here we could create / reserve facets
1490
1491
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 1 times.
4 if(!ply_read(ply)) {
1492
1/2
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 Logger::err("I/O")
1493 << "Problem occurred while parsing PLY file"
1494 << std::endl;
1495 3 ply_close(ply);
1496 3 return false;
1497 }
1498
1499 1 ply_close(ply);
1500
1501
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if(current_vertex_ != mesh_.vertices.nb()) {
1502 Logger::err("I/O")
1503 << "File does not contain enough vertex data"
1504 << std::endl;
1505 return false;
1506 }
1507
1508 if(
1509
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 has_colors_ &&
1510 current_color_ != mesh_.vertices.nb()
1511 ) {
1512 Logger::err("I/O")
1513 << "File does not contain enough color data"
1514 << std::endl;
1515 return false;
1516 }
1517
1518 return true;
1519 }
1520
1521 protected:
1522 /**
1523 * \brief Detects whether the input file has colors
1524 * \param[in] ply the p_ply handle to the file
1525 */
1526 4 void check_for_colors(p_ply ply) {
1527 p_ply_element element = nullptr;
1528
1529 bool has_r = false;
1530 bool has_g = false;
1531 bool has_b = false;
1532
1533 bool has_red = false;
1534 bool has_green = false;
1535 bool has_blue = false;
1536
1537 for(;;) {
1538 12 element = ply_get_next_element(ply, element);
1539
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 4 times.
12 if(element == nullptr) {
1540 break;
1541 }
1542 8 const char* elt_name = nullptr;
1543 8 ply_get_element_info(element, &elt_name, nullptr);
1544
1545
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 4 times.
8 if(!strcmp(elt_name, "vertex")) {
1546 p_ply_property property = nullptr;
1547 for(;;) {
1548 16 property = ply_get_next_property(element, property);
1549
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 4 times.
16 if(property == nullptr) {
1550 break;
1551 }
1552 12 const char* prop_name = nullptr;
1553 12 ply_get_property_info(
1554 property, &prop_name, nullptr, nullptr, nullptr
1555 );
1556
2/4
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 12 times.
12 has_r = has_r || !strcmp(prop_name, "r");
1557
2/4
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 12 times.
12 has_g = has_g || !strcmp(prop_name, "g");
1558
2/4
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 12 times.
12 has_b = has_b || !strcmp(prop_name, "b");
1559
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 has_red = has_red ||
1560
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 !strcmp(prop_name, "red");
1561
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 has_green = has_green ||
1562
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 !strcmp(prop_name, "green");
1563
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 has_blue = has_blue ||
1564
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 !strcmp(prop_name, "blue");
1565 12 }
1566 }
1567 8 }
1568
1569
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
4 if(has_r && has_g && has_b) {
1570 has_colors_ = true;
1571 color_mult_ = 1.0;
1572 ply_set_read_cb(
1573 ply, "vertex", "r", PlyLoader::color_cb, this, 0
1574 );
1575 ply_set_read_cb(
1576 ply, "vertex", "g", PlyLoader::color_cb, this, 1
1577 );
1578 ply_set_read_cb(
1579 ply, "vertex", "b", PlyLoader::color_cb, this, 2
1580 );
1581
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
4 } else if(has_red && has_green && has_blue) {
1582 has_colors_ = true;
1583 color_mult_ = 1.0 / 255.0;
1584 ply_set_read_cb(
1585 ply, "vertex", "red", PlyLoader::color_cb, this, 0
1586 );
1587 ply_set_read_cb(
1588 ply, "vertex", "green", PlyLoader::color_cb, this, 1
1589 );
1590 ply_set_read_cb(
1591 ply, "vertex", "blue", PlyLoader::color_cb, this, 2
1592 );
1593 } else {
1594 4 has_colors_ = false;
1595 }
1596
1597
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if(!has_colors_) {
1598 return;
1599 }
1600
1601 vertex_color_.bind_if_is_defined(
1602 mesh_.vertices.attributes(), "color"
1603 );
1604 if(vertex_color_.is_bound() &&
1605 vertex_color_.dimension() != 3
1606 ) {
1607 Logger::warn("PLY")
1608 << "Mesh already has a color attribute "
1609 << "that is not of dimension 3"
1610 << std::endl;
1611 has_colors_ = false;
1612 return;
1613 }
1614 if(!vertex_color_.is_bound()) {
1615 vertex_color_.create_vector_attribute(
1616 mesh_.vertices.attributes(),
1617 "color",
1618 3
1619 );
1620 }
1621 }
1622
1623 /**
1624 * \brief Gets the PlyLoader associated with
1625 * an opaque p_ply_argument.
1626 * \details Used to pass a Plyloader through libply callbacks
1627 * \param[in] argument the opaque p_ply_argument
1628 * \return the PlyLoader associated with \p argument
1629 */
1630 static PlyLoader* loader(p_ply_argument argument) {
1631 366 PlyLoader* result = nullptr;
1632 366 ply_get_argument_user_data(
1633 argument, (void**) (&result), nullptr
1634 );
1635 geo_debug_assert(result != nullptr);
1636 366 return result;
1637 }
1638
1639 /**
1640 * \brief The vertex callback, called for each vertex
1641 * of the input file.
1642 * \param[in] argument the generic opaque argument
1643 * (from which this PlyLoader is retrieved).
1644 * \return callback status code, zero for errors,
1645 * non-zero for success.
1646 */
1647 132 static int vertex_cb(p_ply_argument argument) {
1648 132 return loader(argument)->add_vertex_data(argument);
1649 }
1650
1651 /**
1652 * \brief The facet callback, called for each facet
1653 * of the input file.
1654 * \param[in] argument the generic opaque argument
1655 * (from which this PlyLoader is retrieved).
1656 * \return callback status code, zero for errors,
1657 * non-zero for success.
1658 */
1659 234 static int face_cb(p_ply_argument argument) {
1660 234 return loader(argument)->add_face_data(argument);
1661 }
1662
1663 /**
1664 * \brief The triangle strip callback,
1665 * called for each triangle strip of the input file.
1666 * \param[in] argument the generic opaque argument
1667 * (from which this PlyLoader is retrieved).
1668 * \return callback status code, zero for errors,
1669 * non-zero for success.
1670 */
1671 static int tristrip_cb(p_ply_argument argument) {
1672 return loader(argument)->add_tristrip_data(argument);
1673 }
1674
1675 /**
1676 * \brief The color callback, called for
1677 * each color data of the input file.
1678 * \param[in] argument the generic opaque argument
1679 * (from which this PlyLoader is retrieved).
1680 * \return callback status code, zero for errors,
1681 * non-zero for success.
1682 */
1683 static int color_cb(p_ply_argument argument) {
1684 return loader(argument)->add_color_data(argument);
1685 }
1686
1687 /**
1688 * \brief Decodes vertex data from a generic callback argument.
1689 * \param[in] argument the generic callback argument.
1690 * \return callback status code,
1691 * zero for errors, non-zero for success.
1692 */
1693 132 int add_vertex_data(p_ply_argument argument) {
1694 long coord;
1695 132 ply_get_argument_user_data(argument, nullptr, &coord);
1696
2/2
✓ Branch 0 taken 44 times.
✓ Branch 1 taken 88 times.
132 if(coord == 0) {
1697 geo_debug_assert(mesh_.vertices.dimension() >= 3);
1698
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 44 times.
44 if(
1699
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 44 times.
44 current_vertex_ >= mesh_.vertices.nb()
1700 ) {
1701 Logger::err("I/O")
1702 << "File contains extraneous vertex data"
1703 << std::endl;
1704 return 0;
1705 }
1706 44 current_vertex_++;
1707 }
1708
1709
1/2
✓ Branch 0 taken 132 times.
✗ Branch 1 not taken.
132 if(coord == 0 || coord == 1 || coord == 2) {
1710 // Note: current_vertex_ was incremented before,
1711 // so we need to index by current_vertex_ - 1
1712
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 132 times.
132 if(mesh_.vertices.single_precision()) {
1713 mesh_.vertices.single_precision_point_ptr(
1714 current_vertex_-1
1715 )[coord] = float(ply_get_argument_value(argument));
1716 } else {
1717 132 mesh_.vertices.point_ptr(
1718 132 current_vertex_-1
1719 132 )[coord] = ply_get_argument_value(argument);
1720 }
1721 132 return 1;
1722 }
1723
1724 Logger::err("I/O")
1725 << "In vertex #" << current_vertex_
1726 << ": invalid coordinate index: " << coord
1727 << std::endl;
1728 return 0;
1729 }
1730
1731 /**
1732 * \brief Decodes facet data from a generic callback argument.
1733 * \param[in] argument the generic callback argument.
1734 * \return callback status code, zero for errors,
1735 * non-zero for success.
1736 */
1737 234 int add_face_data(p_ply_argument argument) {
1738 long length, value_index;
1739 234 ply_get_argument_property(
1740 argument, nullptr, &length, &value_index
1741 );
1742
2/2
✓ Branch 0 taken 59 times.
✓ Branch 1 taken 175 times.
234 if(value_index < 0) {
1743 // Ignore negative values -
1744 // this is not an error! (facet markers)
1745 return 1;
1746 }
1747
1748 index_t vertex_index = index_t(
1749 175 ply_get_argument_value(argument)
1750 175 );
1751
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 175 times.
175 if(index_t(vertex_index) >= mesh_.vertices.nb()) {
1752 Logger::err("I/O")
1753 << "Facet corner #" << mesh_.facets.nb()
1754 << " references an invalid vertex: "
1755 << vertex_index
1756 << std::endl;
1757 return 0;
1758 }
1759
1760
2/2
✓ Branch 0 taken 58 times.
✓ Branch 1 taken 117 times.
175 if(value_index == 0) {
1761 begin_facet();
1762 }
1763 175 add_vertex_to_facet(vertex_index);
1764
2/2
✓ Branch 0 taken 117 times.
✓ Branch 1 taken 58 times.
175 if(value_index == length - 1) {
1765 58 end_facet();
1766 }
1767 return 1;
1768 }
1769
1770 /**
1771 * \brief Decodes triangle strip data from a
1772 * generic callback argument.
1773 * \param[in] argument the generic callback argument.
1774 * \return callback status code, zero for errors,
1775 * non-zero for success.
1776 */
1777 int add_tristrip_data(p_ply_argument argument) {
1778 long length, value_index;
1779 ply_get_argument_property(
1780 argument, nullptr, &length, &value_index
1781 );
1782 if(value_index < 0) {
1783 // Ignore negative values - this is not an error!
1784 return 1;
1785 }
1786
1787 // NOTE: negative vertex_index values have
1788 // a special meaning here:
1789 // they tell the loader to start a new strip
1790
1791 signed_index_t vertex_index = signed_index_t(
1792 ply_get_argument_value(argument)
1793 );
1794 if(vertex_index >=
1795 signed_index_t(mesh_.vertices.nb())
1796 ) {
1797 Logger::err("I/O")
1798 << "Invalid vertex reference in tristrip: "
1799 << vertex_index
1800 << std::endl;
1801 return 0;
1802 }
1803
1804 if(value_index == 0) {
1805 begin_tristrip();
1806 }
1807 if(vertex_index >= 0) {
1808 add_to_tristrip(index_t(vertex_index));
1809 } else {
1810 end_tristrip();
1811 begin_tristrip();
1812 }
1813 if(value_index == length - 1) {
1814 end_tristrip();
1815 }
1816 return 1;
1817 }
1818
1819 /**
1820 * \brief Starts a new facet.
1821 */
1822 void begin_facet() {
1823 58 facet_vertices_.resize(0);
1824 58 }
1825
1826 /**
1827 * \brief Adds a vertex to the current facet.
1828 * \param[in] v the index of the vertex to be added
1829 */
1830 void add_vertex_to_facet(index_t v) {
1831
2/2
✓ Branch 0 taken 166 times.
✓ Branch 1 taken 9 times.
175 facet_vertices_.push_back(v);
1832 }
1833
1834
1835 /**
1836 * \brief Terminates the currently constructed
1837 * facet.
1838 */
1839 58 void end_facet() {
1840
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 58 times.
58 index_t f = mesh_.facets.create_polygon(facet_vertices_.size());
1841
2/2
✓ Branch 0 taken 175 times.
✓ Branch 1 taken 58 times.
466 for(index_t lv=0; lv<facet_vertices_.size(); ++lv) {
1842
2/2
✓ Branch 0 taken 123 times.
✓ Branch 1 taken 52 times.
175 mesh_.facets.set_vertex(f,lv,facet_vertices_[lv]);
1843 }
1844 58 }
1845
1846 /**
1847 * \brief Starts a new triangle strip.
1848 */
1849 void begin_tristrip() {
1850 tristrip_index_ = 0;
1851 }
1852
1853 /**
1854 * \brief Terminates the current triangle strip.
1855 */
1856 void end_tristrip() {
1857 }
1858
1859 /**
1860 * \brief Adds a vertex to the current triangle strip.
1861 * \param[in] vertex_index the index of the vertex
1862 */
1863 void add_to_tristrip(index_t vertex_index) {
1864 if(tristrip_index_ >= 2) {
1865 mesh_.facets.create_triangle(
1866 tristrip_points_[0],
1867 tristrip_points_[1],
1868 vertex_index
1869 );
1870 }
1871 tristrip_points_[tristrip_index_ & 1] = vertex_index;
1872 tristrip_index_++;
1873 }
1874
1875 /**
1876 * \brief Adds color data to the current vertex
1877 * \param[in] argument the generic callback argument
1878 * \return callback status code, zero for errors,
1879 * non-zero for success.
1880 */
1881 int add_color_data(p_ply_argument argument) {
1882 long coord;
1883 ply_get_argument_user_data(argument, nullptr, &coord);
1884 if(coord == 0) {
1885 if(current_color_ >= mesh_.vertices.nb()) {
1886 Logger::err("I/O")
1887 << "File contains extraneous color data"
1888 << std::endl;
1889 return 0;
1890 }
1891 current_color_++;
1892 }
1893
1894 if(coord == 0 || coord == 1 || coord == 2) {
1895 double value =
1896 double(ply_get_argument_value(argument)) * color_mult_;
1897 // (note: current_color_ - 1 because it was incremented before)
1898 vertex_color_[3*(current_color_ - 1) + index_t(coord)] = value;
1899 return 1;
1900 }
1901
1902 Logger::err("I/O")
1903 << "In vertex #" << current_color_
1904 << ": invalid color index: " << coord
1905 << std::endl;
1906 return 0;
1907 }
1908
1909 protected:
1910 Mesh& mesh_;
1911 std::string filename_;
1912 MeshIOFlags flags_;
1913
1914 index_t current_vertex_;
1915
1916 bool has_colors_;
1917 double color_mult_;
1918 index_t current_color_;
1919
1920 index_t tristrip_points_[2];
1921 index_t tristrip_index_;
1922
1923 bool load_colors_;
1924
1925 vector<index_t> facet_vertices_;
1926
1927 Attribute<double> vertex_color_;
1928 };
1929
1930
1931 4 bool load(
1932 const std::string& filename, Mesh& M,
1933 const MeshIOFlags& ioflags = MeshIOFlags()
1934 ) override {
1935 4 PlyLoader loader(filename, M, ioflags);
1936
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
8 return loader.load();
1937 4 }
1938
1939 1 bool save(
1940 const Mesh& M, const std::string& filename,
1941 const MeshIOFlags& ioflags
1942 ) override {
1943
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
2 p_ply oply = ply_create(
1944 filename.c_str(),
1945
1/2
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 (CmdLine::get_arg_bool("sys:ascii") ? PLY_ASCII : PLY_LITTLE_ENDIAN),
1946 nullptr, 0, nullptr
1947 );
1948
1949
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if(oply == nullptr) {
1950 return false;
1951 }
1952
1953 Attribute<double> vertex_color;
1954
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 vertex_color.bind_if_is_defined(
1955
2/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
2 M.vertices.attributes(), "color"
1956 );
1957 if(vertex_color.is_bound() && vertex_color.dimension() != 3) {
1958 Logger::warn("IO")
1959 << "Mesh has vertex colors but attribut dim is not 3 (ignoring them)"
1960 << std::endl;
1961 vertex_color.unbind();
1962 }
1963
1964 // Create element and properties for vertices
1965 e_ply_type coord_type = PLY_FLOAT;
1966 e_ply_type color_type = PLY_UINT8;
1967
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 ply_add_element(oply, "vertex", long(M.vertices.nb()));
1968
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 ply_add_property(oply, "x", coord_type, coord_type, coord_type);
1969
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 ply_add_property(oply, "y", coord_type, coord_type, coord_type);
1970
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 ply_add_property(oply, "z", coord_type, coord_type, coord_type);
1971 if(vertex_color.is_bound()) {
1972 ply_add_property(oply, "red", color_type, color_type, color_type);
1973 ply_add_property(oply, "green", color_type, color_type, color_type);
1974 ply_add_property(oply, "blue", color_type, color_type, color_type);
1975 }
1976
1977
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if(ioflags.has_element(MESH_FACETS)) {
1978 // Create element and properties for facets
1979 // (determine best index types)
1980 1 index_t max_facet_size = 0;
1981
2/2
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 1 times.
21 for(index_t f = 0; f < M.facets.nb(); ++f) {
1982 20 max_facet_size = std::max(
1983 max_facet_size, M.facets.nb_vertices(f)
1984 );
1985 }
1986 e_ply_type facet_len_type = PLY_UCHAR;
1987
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if(max_facet_size > 65535) {
1988 facet_len_type = PLY_UINT;
1989
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 } else if(max_facet_size > 255) {
1990 facet_len_type = PLY_USHORT;
1991 }
1992 e_ply_type facet_idx_type = PLY_INT;
1993
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 ply_add_element(oply, "face", long(M.facets.nb()));
1994
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 ply_add_property(
1995 oply, "vertex_indices",
1996 PLY_LIST, facet_len_type, facet_idx_type
1997 );
1998 }
1999
2000 std::vector<std::string> args;
2001
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 CmdLine::get_args(args);
2002
3/4
✗ Branch 0 not taken.
✓ Branch 1 taken 89 times.
✓ Branch 2 taken 88 times.
✓ Branch 3 taken 1 times.
89 for(index_t i = 0; i < args.size(); i++) {
2003
2/4
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 88 times.
✗ Branch 5 not taken.
176 ply_add_comment(oply, ("vorpaline " + args[i]).c_str());
2004 }
2005
2006 // Write header
2007
2/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
1 if(!ply_write_header(oply)) {
2008 ply_close(oply);
2009 return false;
2010 }
2011
2012 // Write vertices
2013
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 1 times.
13 for(index_t v = 0; v < M.vertices.nb(); ++v) {
2014 double xyz[3];
2015 12 get_mesh_point(M, v, xyz, 3);
2016
2/2
✓ Branch 0 taken 36 times.
✓ Branch 1 taken 12 times.
48 for(index_t coord = 0; coord < 3; coord++) {
2017
1/2
✓ Branch 1 taken 36 times.
✗ Branch 2 not taken.
36 ply_write(oply, xyz[coord]);
2018 }
2019 if(vertex_color.is_bound()) {
2020 const double* rgb = &vertex_color[v*3];
2021 for(index_t coord = 0; coord < 3; coord++) {
2022 ply_write(oply, Numeric::uint8(255.0*rgb[coord]));
2023 }
2024 }
2025 }
2026
2027
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if(ioflags.has_element(MESH_FACETS)) {
2028 // Write facets
2029
2/2
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 1 times.
21 for(index_t f = 0; f < M.facets.nb(); ++f) {
2030
1/2
✓ Branch 1 taken 20 times.
✗ Branch 2 not taken.
20 ply_write(oply, double(M.facets.nb_vertices(f)));
2031 60 for(index_t c = M.facets.corners_begin(f);
2032
2/2
✓ Branch 0 taken 60 times.
✓ Branch 1 taken 20 times.
80 c < M.facets.corners_end(f); ++c
2033 ) {
2034
1/2
✓ Branch 1 taken 60 times.
✗ Branch 2 not taken.
60 ply_write(oply, double(M.facet_corners.vertex(c)));
2035 }
2036 }
2037 }
2038
2039
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 ply_close(oply);
2040 return true;
2041 1 }
2042 };
2043
2044 /************************************************************************/
2045
2046 /**
2047 * \brief IO handler for the OFF file format
2048 * \see http://www.geomview.org/docs/html/OFF.html
2049 */
2050 25 class GEOGRAM_API OFFIOHandler : public MeshIOHandler {
2051 public:
2052 /**
2053 * \brief Loads a mesh from a file in OFF format.
2054 * \param[in] filename name of the file
2055 * \param[out] M the loaded mesh
2056 * \param[in] ioflags specifies which attributes
2057 * and elements should be read
2058 * \return true on success, false otherwise
2059 */
2060 24 bool load(
2061 const std::string& filename, Mesh& M,
2062 const MeshIOFlags& ioflags
2063 ) override {
2064 geo_argused(ioflags);
2065
2066 // Note: Vertices indexes start by 0 in off format.
2067
2068 24 LineInput in(filename);
2069
1/2
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
24 if(!in.OK()) {
2070 return false;
2071 }
2072
2073
2/4
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 24 times.
24 if(!in.get_line()) {
2074 Logger::err("I/O")
2075 << "Unexpected end of file"
2076 << std::endl;
2077 return false;
2078 }
2079
1/2
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
24 in.get_fields();
2080
2/4
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 24 times.
48 if(in.nb_fields() == 0 || !in.field_matches(0, "OFF")) {
2081 Logger::err("I/O")
2082 << "Line " << in.line_number()
2083 << ": unrecognized header"
2084 << std::endl;
2085 return false;
2086 }
2087
2088
2/4
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 24 times.
24 if(!in.get_line()) {
2089 Logger::err("I/O")
2090 << "Line " << in.line_number()
2091 << ": unexpected end of file"
2092 << std::endl;
2093 return false;
2094 }
2095
1/2
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
24 in.get_fields();
2096
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
24 if(in.nb_fields() != 3) {
2097 Logger::err("I/O")
2098 << "Line " << in.line_number()
2099 << ": unrecognized header"
2100 << std::endl;
2101 return false;
2102 }
2103
2104
1/2
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
24 index_t nb_vertices = in.field_as_uint(0);
2105 // second field is nb edges (unused)
2106 // index_t nb_facets = in.field_as_uint(2);
2107
2108 M.vertices.create_vertices(nb_vertices);
2109 // TODO: reserve facets
2110
2111
2/2
✓ Branch 0 taken 66903 times.
✓ Branch 1 taken 22 times.
66925 for(index_t i = 0; i < nb_vertices; i++) {
2112 do {
2113
2/4
✓ Branch 1 taken 66903 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 66903 times.
66903 if(!in.get_line()) {
2114 Logger::err("I/O")
2115 << "Line " << in.line_number()
2116 << ": unexpected end of file"
2117 << std::endl;
2118 1 return false;
2119 }
2120 }
2121
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 66903 times.
66903 while(in.current_line()[0] == '#');
2122
1/2
✓ Branch 1 taken 66903 times.
✗ Branch 2 not taken.
66903 in.get_fields();
2123
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 66902 times.
66903 if(in.nb_fields() != 3) {
2124
2/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 Logger::err("I/O")
2125 << "Line " << in.line_number()
2126 << ": invalid number of fields:"
2127 << " expected 3 coordinates, got "
2128 << in.nb_fields()
2129 << std::endl;
2130 1 return false;
2131 }
2132 double xyz[3];
2133
1/2
✓ Branch 1 taken 66902 times.
✗ Branch 2 not taken.
66902 xyz[0] = in.field_as_double(0);
2134
2/2
✓ Branch 1 taken 66901 times.
✓ Branch 2 taken 1 times.
66902 xyz[1] = in.field_as_double(1);
2135
1/2
✓ Branch 1 taken 66901 times.
✗ Branch 2 not taken.
66901 xyz[2] = in.field_as_double(2);
2136 66901 set_mesh_point(M, i, xyz, 3);
2137 }
2138
2139 if(
2140
3/4
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
22 ioflags.has_element(MESH_FACETS) ||
2141 ioflags.has_element(MESH_EDGES)) {
2142
4/6
✗ Branch 0 not taken.
✓ Branch 1 taken 131666 times.
✓ Branch 3 taken 131666 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 18 times.
✓ Branch 6 taken 131648 times.
131666 while(!in.eof() && in.get_line()) {
2143
1/2
✓ Branch 1 taken 131648 times.
✗ Branch 2 not taken.
131648 in.get_fields();
2144 /* if(in.nb_fields() < 4) {
2145 Logger::err("I/O")
2146 << "Line " << in.line_number()
2147 << ": facet line only has " << in.nb_fields()
2148 << " fields (expected 1 count +"
2149 << " at least 3 corner fields)"
2150 << std::endl;
2151 return false;
2152 }*/
2153
2/2
✓ Branch 1 taken 131647 times.
✓ Branch 2 taken 1 times.
131648 index_t nb_facet_vertices = in.field_as_uint(0);
2154
2155 // Note: there can be more fields than the number
2156 // of vertices, for instance some OFF files have
2157 // a RGB color for each facet stored right after
2158 // the vertices indices (we ignore it, thus the
2159 // test here is '<' instead of '!=').
2160
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 131646 times.
131647 if(in.nb_fields() < nb_facet_vertices + 1) {
2161
2/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 Logger::err("I/O")
2162 << "Line " << in.line_number()
2163
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 << ": facet has " << in.nb_fields() - 1
2164 << " actual vertices ("
2165 << nb_facet_vertices << " expected)"
2166 << std::endl;
2167 1 return false;
2168 }
2169
2170
1/2
✓ Branch 0 taken 131646 times.
✗ Branch 1 not taken.
131646 if(nb_facet_vertices >= 3) {
2171
1/2
✓ Branch 1 taken 131646 times.
✗ Branch 2 not taken.
131646 index_t f = M.facets.create_polygon(nb_facet_vertices);
2172
2173
2/2
✓ Branch 0 taken 394934 times.
✓ Branch 1 taken 131644 times.
526578 for(index_t j = 0; j < nb_facet_vertices; j++) {
2174
2/2
✓ Branch 1 taken 394933 times.
✓ Branch 2 taken 1 times.
394934 index_t vertex_index = in.field_as_uint(j + 1);
2175
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 394932 times.
394933 if(vertex_index >= M.vertices.nb()) {
2176
2/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 Logger::err("I/O")
2177 << "Line " << in.line_number()
2178 << ": facet corner #" << j
2179 << " references an invalid vertex: "
2180 << vertex_index
2181 << std::endl;
2182 1 return false;
2183 }
2184 M.facets.set_vertex(f, j, vertex_index);
2185 }
2186 } else if(nb_facet_vertices == 2) {
2187 index_t vertex_index0=in.field_as_uint(1);
2188 index_t vertex_index1=in.field_as_uint(2);
2189
2190 if(
2191 vertex_index0 >= M.vertices.nb() ||
2192 vertex_index1 >= M.vertices.nb()
2193 ) {
2194 Logger::err("I/O")
2195 << "Line " << in.line_number()
2196 << ": edge"
2197 << " references an invalid vertex: "
2198 << vertex_index0 <<" or "<<vertex_index1
2199 << std::endl;
2200 return false; }
2201 M.edges.create_edge(vertex_index0, vertex_index1);
2202 }
2203 }
2204 }
2205 return true;
2206 24 }
2207
2208 /**
2209 * \brief Saves a mesh into a file in OFF format.
2210 * \param[in] M The mesh to save
2211 * \param[in] filename name of the file
2212 * \param[in] ioflags specifies which attributes and elements
2213 * should be saved
2214 * \return true on success, false otherwise
2215 */
2216 1 bool save(
2217 const Mesh& M, const std::string& filename,
2218 const MeshIOFlags& ioflags
2219 ) override {
2220 1 std::ofstream output(filename.c_str());
2221
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if(!output) {
2222 return false;
2223 }
2224 output << "OFF" << std::endl;
2225
2226 /*output << M.vertices.nb() << " "
2227 << M.facets.nb() << " "
2228 << M.facet_corners.nb() / 2
2229 << std::endl;*/
2230
2231 output << M.vertices.nb() << " "
2232 << M.facets.nb() << " "
2233 << M.edges.nb()
2234 << std::endl;
2235
2236 // Output Vertices
2237
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 1 times.
13 for(index_t v = 0; v < M.vertices.nb(); ++v) {
2238 double xyz[3];
2239 12 get_mesh_point(M, v, xyz, 3);
2240
3/6
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 12 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 12 times.
✗ Branch 8 not taken.
36 output << xyz[0] << " " << xyz[1] << " " << xyz[2] << std::endl;
2241 }
2242
2243
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if(ioflags.has_element(MESH_FACETS)) {
2244 // Output facets
2245
2/2
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 1 times.
21 for(index_t f = 0; f < M.facets.nb(); ++f) {
2246
1/2
✓ Branch 1 taken 20 times.
✗ Branch 2 not taken.
20 output << M.facets.nb_vertices(f) << " ";
2247 60 for(
2248 index_t c = M.facets.corners_begin(f);
2249
2/2
✓ Branch 0 taken 60 times.
✓ Branch 1 taken 20 times.
80 c < M.facets.corners_end(f); ++c
2250 ) {
2251
1/2
✓ Branch 1 taken 60 times.
✗ Branch 2 not taken.
60 output << M.facet_corners.vertex(c) << " ";
2252 }
2253 output << std::endl;
2254 }
2255 }
2256
2257
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if(ioflags.has_element(MESH_EDGES)) {
2258 // Output edges
2259
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 for(index_t e = 0; e < M.edges.nb(); ++e)
2260 {
2261 output << "2 " << M.edges.vertex(e, 0)
2262 << " " << M.edges.vertex(e, 1)
2263 << std::endl;
2264 }
2265 }
2266 return true;
2267 1 }
2268 };
2269
2270 /************************************************************************/
2271
2272 /**
2273 * \brief IO handler for the STL file format (ascii and binary)
2274 * \see http://en.wikipedia.org/wiki/STL_(file_format)
2275 */
2276 28 class GEOGRAM_API STLIOHandler : public MeshIOHandler {
2277 public:
2278 /**
2279 * \brief Loads a mesh from a file in STL format (ascii version).
2280 * \param[in] filename name of the file
2281 * \param[out] M the loaded mesh
2282 * \param[in] ioflags specifies which attributes and elements
2283 * should be read
2284 * \return true on success, false otherwise
2285 */
2286 3 bool load_ascii(
2287 const std::string& filename, Mesh& M,
2288 const MeshIOFlags& ioflags
2289 ) {
2290
2291 3 LineInput in(filename);
2292
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if(!in.OK()) {
2293 return false;
2294 }
2295
2296
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 bind_attributes(M, ioflags, true);
2297
2298 index_t current_chart = 0;
2299 bool facet_opened = false;
2300 vector<index_t> facet_vertices;
2301
2302
4/6
✓ Branch 0 taken 452 times.
✓ Branch 1 taken 3 times.
✓ Branch 3 taken 452 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 452 times.
455 while(!in.eof() && in.get_line()) {
2303
1/2
✓ Branch 1 taken 452 times.
✗ Branch 2 not taken.
452 in.get_fields();
2304
2/2
✓ Branch 0 taken 64 times.
✓ Branch 1 taken 388 times.
452 if(in.field_matches(0, "outer")) {
2305
1/2
✓ Branch 1 taken 64 times.
✗ Branch 2 not taken.
64 facet_vertices.resize(0);
2306 facet_opened = true;
2307
2/2
✓ Branch 0 taken 64 times.
✓ Branch 1 taken 324 times.
388 } else if(in.field_matches(0, "endloop")) {
2308 facet_opened = false;
2309
1/2
✓ Branch 0 taken 64 times.
✗ Branch 1 not taken.
64 if(ioflags.has_element(MESH_FACETS)) {
2310
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 64 times.
✓ Branch 3 taken 64 times.
✗ Branch 4 not taken.
64 index_t f = M.facets.create_polygon(
2311 facet_vertices.size()
2312 );
2313
2/2
✓ Branch 0 taken 192 times.
✓ Branch 1 taken 64 times.
256 for(index_t lv=0; lv<facet_vertices.size(); ++lv) {
2314
1/2
✓ Branch 0 taken 192 times.
✗ Branch 1 not taken.
192 M.facets.set_vertex(f,lv,facet_vertices[lv]);
2315 }
2316 if(facet_region_.is_bound()) {
2317 facet_region_[f] = index_t(current_chart);
2318 }
2319 }
2320
2/2
✓ Branch 0 taken 192 times.
✓ Branch 1 taken 132 times.
324 } else if(in.field_matches(0, "vertex")) {
2321
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 192 times.
192 if(in.nb_fields() < 4) {
2322 Logger::err("I/O")
2323 << "Line " << in.line_number()
2324 << ": vertex line has " << in.nb_fields() - 1
2325 << " fields (at least 3 required)"
2326 << std::endl;
2327 unbind_attributes();
2328 return false;
2329 }
2330 double xyz[3];
2331
1/2
✓ Branch 1 taken 192 times.
✗ Branch 2 not taken.
192 xyz[0] = in.field_as_double(1);
2332
1/2
✓ Branch 1 taken 192 times.
✗ Branch 2 not taken.
192 xyz[1] = in.field_as_double(2);
2333
2/4
✓ Branch 1 taken 192 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 192 times.
✗ Branch 5 not taken.
192 xyz[2] = in.field_as_double(3);
2334 192 index_t v = M.vertices.create_vertex();
2335 192 set_mesh_point(M, v, xyz, 3);
2336 facet_vertices.push_back(v);
2337
2/2
✓ Branch 0 taken 131 times.
✓ Branch 1 taken 1 times.
132 } else if(in.field_matches(0, "solid")) {
2338 1 current_chart++;
2339 }
2340 }
2341
2342
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if(facet_opened) {
2343 Logger::err("I/O")
2344 << "Line " << in.line_number()
2345 << ": current facet is not closed"
2346 << std::endl;
2347 unbind_attributes();
2348 return false;
2349 }
2350
2351
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 unbind_attributes();
2352
2353
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
3 if(M.facets.nb() == 0) {
2354
2/6
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
2 Logger::err("I/O")
2355 << "STL file does not contain any facet"
2356 << std::endl;
2357 2 return false;
2358 }
2359
2360 return true;
2361 3 }
2362
2363 /**
2364 * \brief Loads a mesh from a file in STL format (binary version).
2365 * \param[in] filename name of the file
2366 * \param[out] M the loaded mesh
2367 * \param[in] ioflags specifies which attributes and elements
2368 * should be read
2369 * \return true on success, false otherwise
2370 */
2371 25 bool load_binary(
2372 const std::string& filename,
2373 Mesh& M, const MeshIOFlags& ioflags
2374 ) {
2375 25 BinaryInputStream in(filename, BinaryStream::GEO_LITTLE_ENDIAN);
2376 char header[80];
2377 in.read_opaque_data(header, 80);
2378
2/4
✓ Branch 1 taken 25 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 25 times.
25 if(!in.OK()) {
2379 throw "failed to read header";
2380 }
2381 Numeric::uint32 nb_triangles;
2382 in >> nb_triangles;
2383
2/4
✓ Branch 1 taken 25 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 25 times.
25 if(!in.OK()) {
2384 throw "failed to read number of triangles";
2385 }
2386
2387
1/2
✓ Branch 1 taken 25 times.
✗ Branch 2 not taken.
25 bind_attributes(M, ioflags, true);
2388
2389
1/2
✓ Branch 1 taken 25 times.
✗ Branch 2 not taken.
25 M.vertices.create_vertices(nb_triangles*3);
2390
1/2
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
25 if(ioflags.has_element(MESH_FACETS)) {
2391
1/2
✓ Branch 1 taken 25 times.
✗ Branch 2 not taken.
25 M.facets.create_triangles(nb_triangles);
2392 }
2393
2394
2/2
✓ Branch 0 taken 18502 times.
✓ Branch 1 taken 25 times.
18527 for(index_t t = 0; t < nb_triangles; t++) {
2395 Numeric::float32 N[3];
2396 Numeric::float32 XYZ[9];
2397 in >> N[0] >> N[1] >> N[2];
2398
2/2
✓ Branch 0 taken 166518 times.
✓ Branch 1 taken 18502 times.
185020 for(index_t i = 0; i < 9; i++) {
2399
1/2
✓ Branch 1 taken 166518 times.
✗ Branch 2 not taken.
166518 in >> XYZ[i];
2400 }
2401
2/4
✓ Branch 1 taken 18502 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 18502 times.
18502 if(!in.OK()) {
2402 throw "failed to read triangle";
2403 }
2404 Numeric::uint16 attrib;
2405 in >> attrib;
2406
2407 18502 set_mesh_point(M, 3*t, XYZ, 3);
2408 18502 set_mesh_point(M, 3*t+1, XYZ+3, 3);
2409 18502 set_mesh_point(M, 3*t+2, XYZ+6, 3);
2410
2411
1/2
✓ Branch 0 taken 18502 times.
✗ Branch 1 not taken.
18502 if(ioflags.has_element(MESH_FACETS)) {
2412 M.facets.set_vertex(t, 0, 3*t);
2413 M.facets.set_vertex(t, 1, 3*t+1);
2414 M.facets.set_vertex(t, 2, 3*t+2);
2415 if(facet_region_.is_bound()) {
2416 facet_region_[t] = index_t(attrib);
2417 }
2418 }
2419 }
2420
1/2
✓ Branch 1 taken 25 times.
✗ Branch 2 not taken.
25 unbind_attributes();
2421
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 25 times.
25 if(M.facets.nb() != nb_triangles) {
2422 Logger::err("I/O")
2423 << "STL file does not have "
2424 << "the required number of triangles"
2425 << std::endl;
2426
2427 return false;
2428 }
2429
2430 return true;
2431 25 }
2432
2433 /**
2434 * \brief Loads a mesh from a file in STL format.
2435 * \details Supports both ascii and binary STL.
2436 * \param[in] filename name of the file
2437 * \param[out] M the loaded mesh
2438 * \param[in] ioflags specifies which attributes and
2439 * elements should be read
2440 * \return true on success, false otherwise
2441 */
2442 28 bool load(
2443 const std::string& filename, Mesh& M,
2444 const MeshIOFlags& ioflags
2445 ) override {
2446 28 FILE* F = fopen(filename.c_str(), "rb");
2447
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 28 times.
28 if(F == nullptr) {
2448 return false;
2449 }
2450
2451 // The safe way of checking whether an STL file is
2452 // binary is to check whether the size of the file
2453 // matches the size deduced from the number of triangles
2454 // (many binary STL files start with SOLID although it
2455 // is supposed to be only for ASCII STL files)
2456 28 fseek(F, 80, SEEK_SET);
2457 Numeric::uint32 nb_triangles;
2458
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 28 times.
28 if(fread(&nb_triangles, sizeof(nb_triangles), 1, F) != 1) {
2459 Logger::err("I/O")
2460 << "Cannot deduce the format of STL file"
2461 << std::endl;
2462 fclose(F);
2463 return false;
2464 }
2465 28 fseek(F, 0, SEEK_END);
2466 28 long file_size = ftell(F);
2467 28 fclose(F);
2468 bool result;
2469
2/2
✓ Branch 0 taken 25 times.
✓ Branch 1 taken 3 times.
28 if(file_size == long(nb_triangles * 50 + 84)) {
2470 25 result = load_binary(filename, M, ioflags);
2471 } else {
2472 3 result = load_ascii(filename, M, ioflags);
2473 }
2474 // STL files have isolated triangles, in general
2475 // the user wants to merge duplicated vertices and connect
2476 // all the triangles, so let's do that.
2477
2/2
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 2 times.
28 if(result) {
2478 bool fp32 = M.vertices.single_precision();
2479 if(fp32) {
2480 M.vertices.set_double_precision();
2481 }
2482 26 mesh_repair(
2483 M,
2484 GEO::MeshRepairMode(
2485 GEO::MESH_REPAIR_COLOCATE | GEO::MESH_REPAIR_DUP_F
2486 ),
2487 0.0
2488 );
2489
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 26 times.
26 if(fp32) {
2490 M.vertices.set_single_precision();
2491 }
2492 }
2493 return result;
2494 }
2495
2496 /**
2497 * \brief Writes a point to a binary stream.
2498 * \param[in] out the binary stream
2499 * \param[in] V the vector to write
2500 */
2501 inline void write_stl_vector(BinaryOutputStream& out, const vec3& V) {
2502 out << Numeric::float32(V.x);
2503 out << Numeric::float32(V.y);
2504 out << Numeric::float32(V.z);
2505 }
2506
2507 /**
2508 * \brief Saves a mesh into a file in STL format.
2509 * \param[in] M The mesh to save
2510 * \param[in] filename name of the file
2511 * \param[in] ioflags specifies which attributes
2512 * and elements should be saved
2513 * \return true on success, false otherwise
2514 */
2515 bool save(
2516 const Mesh& M, const std::string& filename,
2517 const MeshIOFlags& ioflags
2518 ) override {
2519 bool result = true;
2520 if(CmdLine::get_arg_bool("sys:ascii")) {
2521 result = save_ascii(M, filename, ioflags);
2522 } else {
2523 result = save_binary(M, filename, ioflags);
2524 }
2525 return result;
2526 }
2527
2528 /**
2529 * \brief Saves a mesh into a file in STL ASCII format.
2530 * \param[in] M The mesh to save
2531 * \param[in] filename name of the file
2532 * \param[in] ioflags specifies which attributes
2533 * and elements should be saved
2534 * \return true on success, false otherwise
2535 */
2536 bool save_ascii(
2537 const Mesh& M, const std::string& filename,
2538 const MeshIOFlags& ioflags
2539 ) {
2540 geo_argused(ioflags);
2541 std::ofstream out(filename.c_str());
2542 if(!out) {
2543 return false;
2544 }
2545 out << "solid geogram" << std::endl;
2546 for(index_t f = 0; f < M.facets.nb(); ++f) {
2547 index_t c1 = M.facets.corners_begin(f);
2548 vec3 p1;
2549 get_mesh_point(M, M.facet_corners.vertex(c1), p1.data(), 3);
2550 for(index_t c2 = M.facets.corners_begin(f) + 1;
2551 c2 + 1 < M.facets.corners_end(f); ++c2
2552 ) {
2553 vec3 p2;
2554 get_mesh_point(
2555 M, M.facet_corners.vertex(c2), p2.data(), 3
2556 );
2557 vec3 p3;
2558 get_mesh_point(
2559 M, M.facet_corners.vertex(c2+1), p3.data(), 3
2560 );
2561
2562 // Seriously, this ASCII STL format is soooo verbose !!!
2563 // Crazy...
2564 out << "facet normal " << normalize(cross(p2-p1,p3-p1))
2565 << std::endl;
2566 out << "outer loop" << std::endl;
2567 out << "vertex " << p1 << std::endl;
2568 out << "vertex " << p2 << std::endl;
2569 out << "vertex " << p3 << std::endl;
2570 out << "endloop" << std::endl;
2571 out << "endfacet" << std::endl;
2572 }
2573 }
2574 out << "endsolid" << std::endl;
2575 return true;
2576 }
2577
2578 /**
2579 * \brief Saves a mesh into a file in STL binary format.
2580 * \param[in] M The mesh to save
2581 * \param[in] filename name of the file
2582 * \param[in] ioflags specifies which attributes
2583 * and elements should be saved
2584 * \return true on success, false otherwise
2585 */
2586 bool save_binary(
2587 const Mesh& M, const std::string& filename,
2588 const MeshIOFlags& ioflags
2589 ) {
2590
2591 bind_attributes(M, ioflags, false);
2592
2593 BinaryOutputStream out(filename, BinaryStream::GEO_LITTLE_ENDIAN);
2594 char header[80];
2595 Memory::clear(header, 80);
2596 strcpy(header, "generated with GEOGRAM");
2597 out.write_opaque_data(header, 80);
2598 Numeric::uint32 nb_triangles = 0;
2599 for(index_t f = 0; f < M.facets.nb(); ++f) {
2600 nb_triangles += Numeric::uint32((M.facets.nb_vertices(f) - 2));
2601 }
2602 out << nb_triangles;
2603 for(index_t f = 0; f < M.facets.nb(); ++f) {
2604 index_t c1 = M.facets.corners_begin(f);
2605 vec3 p1;
2606 get_mesh_point(M, M.facet_corners.vertex(c1), p1.data(), 3);
2607 for(index_t c2 = M.facets.corners_begin(f) + 1;
2608 c2 + 1 < M.facets.corners_end(f); ++c2
2609 ) {
2610 vec3 p2;
2611 get_mesh_point(
2612 M, M.facet_corners.vertex(c2), p2.data(), 3
2613 );
2614 vec3 p3;
2615 get_mesh_point(
2616 M, M.facet_corners.vertex(c2+1), p3.data(), 3
2617 );
2618
2619 Numeric::uint16 attribute = Numeric::uint16(
2620 facet_region_.is_bound() ?
2621 facet_region_[f] : 0
2622 );
2623
2624 write_stl_vector(out, normalize(cross(p2-p1,p3-p1)));
2625 write_stl_vector(out, p1);
2626 write_stl_vector(out, p2);
2627 write_stl_vector(out, p3);
2628
2629 out << attribute;
2630 }
2631 }
2632 unbind_attributes();
2633 return true;
2634 }
2635 };
2636
2637 /************************************************************************/
2638
2639
2640 /**
2641 * \brief IO handler for the XYZ file format
2642 * \details Currtently only loading is supported
2643 */
2644 class GEOGRAM_API XYZIOHandler : public MeshIOHandler {
2645 public:
2646 /**
2647 * \brief Loads a pointset from a file in XYZ format.
2648 * \param[in] filename name of the file
2649 * \param[out] M the mesh where to store the points
2650 * \param[in] ioflags specifies which attributes and
2651 * elements should be read
2652 * \return true on success, false otherwise
2653 */
2654 bool load(
2655 const std::string& filename, Mesh& M,
2656 const MeshIOFlags& ioflags
2657 ) override {
2658 geo_argused(ioflags);
2659 index_t nb_vertices = get_nb_vertices(filename);
2660 if(nb_vertices == NO_INDEX) {
2661 return false;
2662 }
2663
2664 M.vertices.create_vertices(nb_vertices);
2665
2666 LineInput in(filename);
2667 if(!in.OK()) {
2668 return false;
2669 }
2670 Attribute<double> normal;
2671 index_t cur_v = 0;
2672 while(!in.eof() && in.get_line()) {
2673 in.get_fields();
2674 switch(in.nb_fields()) {
2675 case 1:
2676 break;
2677 case 2:
2678 case 3:
2679 case 4:
2680 case 6:
2681 {
2682 double xyz[3];
2683 xyz[0] = in.field_as_double(0);
2684 xyz[1] = in.field_as_double(1);
2685 xyz[2] =
2686 (in.nb_fields() >= 3) ? in.field_as_double(2) : 0.0;
2687 // Not all xyz files have the number of vertices
2688 // specified on the first line. If it is unknown,
2689 // then vertices are created dynamically.
2690 if(cur_v+1 >= M.vertices.nb()) {
2691 M.vertices.create_vertices(cur_v+1-M.vertices.nb());
2692 }
2693 set_mesh_point(M,cur_v,xyz,3);
2694
2695 if(in.nb_fields() == 6) {
2696 if(!normal.is_bound()) {
2697 normal.create_vector_attribute(
2698 M.vertices.attributes(), "normal", 3
2699 );
2700 }
2701 normal[3*cur_v] = in.field_as_double(3);
2702 normal[3*cur_v+1] = in.field_as_double(4);
2703 normal[3*cur_v+2] = in.field_as_double(5);
2704 }
2705
2706 ++cur_v;
2707 }
2708 break;
2709 default:
2710 Logger::err("I/O")
2711 << "Line " << in.line_number()
2712 << ": wrong number of fields"
2713 << std::endl;
2714 return false;
2715 }
2716 }
2717 return true;
2718 }
2719
2720 bool save(
2721 const Mesh& M, const std::string& filename,
2722 const MeshIOFlags& ioflags
2723 ) override {
2724 geo_argused(ioflags);
2725
2726 if(M.vertices.dimension() < 3) {
2727 Logger::err("I/O")
2728 << "XYZ format unsupported for dim < 3"
2729 << std::endl;
2730 return false;
2731 }
2732
2733 std::ofstream out(filename.c_str());
2734 if(!out) {
2735 Logger::err("I/O")
2736 << "Could not create file : "
2737 << filename
2738 << std::endl;
2739 return false;
2740 }
2741
2742 Attribute<double> normal;
2743 normal.bind_if_is_defined(M.vertices.attributes(), "normal");
2744 if(normal.is_bound() && normal.dimension() != 3) {
2745 normal.unbind();
2746 }
2747
2748 out << M.vertices.nb() << std::endl;
2749
2750 for(index_t v=0; v<M.vertices.nb(); ++v) {
2751 double point[3];
2752 get_mesh_point(M,v,point,3);
2753 if(normal.is_bound()) {
2754 out << point[0] << ' '
2755 << point[1] << ' '
2756 << point[2] << ' '
2757 << normal[3*v] << ' '
2758 << normal[3*v+1] << ' '
2759 << normal[3*v+2] << ' '
2760 << std::endl;
2761 } else if(
2762 M.vertices.dimension() >= 6 &&
2763 M.vertices.double_precision()
2764 ) {
2765 const double* p = M.vertices.point_ptr(v);
2766 out << p[0] << ' '
2767 << p[1] << ' '
2768 << p[2] << ' '
2769 << p[3] << ' '
2770 << p[4] << ' '
2771 << p[5] << std::endl;
2772 } else {
2773 out << point[0] << ' '
2774 << point[1] << ' '
2775 << point[2] << std::endl;
2776 }
2777 }
2778
2779 return true;
2780 }
2781
2782 protected:
2783
2784 /**
2785 * \brief Gets the number of vertices in the file.
2786 * \details Some xyz files do not have the number of
2787 * points specified in them. For these files, this
2788 * function reads the entire file once and counts the
2789 * points. It is better to do so, because it makes it
2790 * possible to allocate the points once we known the
2791 * required size, instead of growing.
2792 * \param[in] filename the name of the file.
2793 * \return the number of vertices in the file, or
2794 * NO_INDEX if the file could not be opened.
2795 */
2796 index_t get_nb_vertices(const std::string& filename) {
2797 index_t result = 0;
2798 LineInput in(filename);
2799 if(!in.OK()) {
2800 return NO_INDEX;
2801 }
2802 while(!in.eof() && in.get_line()) {
2803 in.get_fields();
2804 switch(in.nb_fields()) {
2805 case 1:
2806 return in.field_as_uint(0);
2807 case 2:
2808 case 3:
2809 case 4:
2810 case 6:
2811 ++result;
2812 break;
2813 default:
2814 Logger::err("I/O")
2815 << "Line " << in.line_number()
2816 << ": wrong number of fields"
2817 << std::endl;
2818 return NO_INDEX;
2819 }
2820 }
2821 return result;
2822 }
2823 };
2824
2825
2826 /************************************************************************/
2827
2828 /**
2829 * \brief IO handler for the PTS file format
2830 */
2831 class GEOGRAM_API PTSIOHandler : public MeshIOHandler {
2832 public:
2833 /**
2834 * \brief Loads a pointset from a file in PTS format.
2835 * \param[in] filename name of the file
2836 * \param[out] M the mesh where to store the points
2837 * \param[in] ioflags specifies which attributes and
2838 * elements should be read
2839 * \return true on success, false otherwise
2840 */
2841 bool load(
2842 const std::string& filename, Mesh& M,
2843 const MeshIOFlags& ioflags
2844 ) override {
2845 geo_argused(ioflags);
2846
2847 LineInput in(filename);
2848 if(!in.OK()) {
2849 return false;
2850 }
2851 while(!in.eof() && in.get_line()) {
2852 in.get_fields();
2853 if(in.nb_fields() == 4 && in.field_matches(0,"v")) {
2854 double xyz[3];
2855 xyz[0] = in.field_as_double(1);
2856 xyz[1] = in.field_as_double(2);
2857 xyz[2] = in.field_as_double(3);
2858 index_t v = M.vertices.create_vertex();
2859 set_mesh_point(M,v,xyz,3);
2860 } else {
2861 Logger::err("I/O")
2862 << "Line " << in.line_number()
2863 << ": wrong number of fields"
2864 << std::endl;
2865 return false;
2866 }
2867 }
2868 return true;
2869 }
2870
2871 bool save(
2872 const Mesh& M, const std::string& filename,
2873 const MeshIOFlags& ioflags
2874 ) override {
2875 geo_argused(ioflags);
2876
2877 std::ofstream out(filename.c_str());
2878 if(!out) {
2879 Logger::err("I/O")
2880 << "Could not create file \'"
2881 << filename << "\'" << std::endl;
2882 return false;
2883 }
2884 if(M.vertices.dimension() != 3) {
2885 Logger::err("I/O")
2886 << "invalid dimension for pts file format"
2887 << std::endl;
2888 return false;
2889 }
2890
2891 for(index_t v = 0; v < M.vertices.nb(); ++v) {
2892 double p[3];
2893 get_mesh_point(M,v,p,3);
2894 out << "v ";
2895 for(index_t c = 0; c < 3; ++c) {
2896 out << p[c] << ' ';
2897 }
2898 out << std::endl;
2899 }
2900
2901 return true;
2902 }
2903 };
2904
2905 /************************************************************************/
2906
2907 /**
2908 * \brief IO handler for the TET file format
2909 */
2910 class GEOGRAM_API TETIOHandler : public MeshIOHandler {
2911 public:
2912 /**
2913 * \brief Creates a TET IO handler.
2914 * \param[in] dimension dimension of the vertices
2915 * (3 for regular 3d mesh)
2916 */
2917 4 TETIOHandler(coord_index_t dimension = 3) :
2918 4 dimension_(dimension) {
2919 }
2920
2921 /**
2922 * \brief Loads a mesh from a file in TET format.
2923 * \details Only tetrahedral cells are supported for now.
2924 * \param[in] filename name of the file
2925 * \param[out] M the loaded mesh
2926 * \param[in] ioflags specifies which attributes and elements
2927 * should be read
2928 * \return true on success, false otherwise
2929 */
2930 4 bool load(
2931 const std::string& filename, Mesh& M,
2932 const MeshIOFlags& ioflags
2933 ) override {
2934 4 LineInput in(filename);
2935
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if(!in.OK()) {
2936 return false;
2937 }
2938
2/4
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
4 if(!in.get_line()) {
2939 Logger::err("I/O")
2940 << "Unexpected end of file"
2941 << std::endl;
2942 return false;
2943 }
2944
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 in.get_fields();
2945
2946 index_t nb_vertices = 0;
2947 index_t nb_cells = 0;
2948 bool has_arbitrary_cells = false;
2949
2950 if(
2951 in.nb_fields() == 4 &&
2952
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
4 in.field_matches(1, "vertices") &&
2953 (
2954 in.field_matches(3, "tets") ||
2955 in.field_matches(3, "cells")
2956 )
2957 ) {
2958 nb_vertices = in.field_as_uint(0);
2959 nb_cells = in.field_as_uint(2);
2960 has_arbitrary_cells = in.field_matches(3, "cells");
2961 } else {
2962
2/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
8 if(in.nb_fields() != 2 || !in.field_matches(1, "vertices")) {
2963 Logger::err("I/O")
2964 << "Line " << in.line_number()
2965 << " expected <number_of_vertices> vertices"
2966 << std::endl;
2967 return false;
2968 }
2969
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 nb_vertices = in.field_as_uint(0);
2970
2971
2/4
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
4 if(!in.get_line()) {
2972 Logger::err("I/O")
2973 << "Unexpected end of file"
2974 << std::endl;
2975 return false;
2976 }
2977
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 in.get_fields();
2978 if(
2979
2/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
8 in.nb_fields() != 2 || (
2980 !in.field_matches(1, "tets") &&
2981 !in.field_matches(1, "cells")
2982 )
2983 ) {
2984 Logger::err("I/O")
2985 << "Line " << in.line_number()
2986 << " expected <number_of_tets> tets"
2987 << std::endl;
2988 return false;
2989 }
2990
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 nb_cells = in.field_as_uint(0);
2991 has_arbitrary_cells = in.field_matches(1, "cells");
2992 }
2993
2994
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 M.vertices.set_dimension(dimension_);
2995 M.vertices.create_vertices(nb_vertices);
2996
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 vector<double> P(dimension_);
2997
2/2
✓ Branch 0 taken 500 times.
✓ Branch 1 taken 4 times.
504 for(index_t v = 0; v < nb_vertices; ++v) {
2998
2/4
✓ Branch 1 taken 500 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 500 times.
500 if(!in.get_line()) {
2999 Logger::err("I/O")
3000 << "Unexpected end of file"
3001 << std::endl;
3002 return false;
3003 }
3004
1/2
✓ Branch 1 taken 500 times.
✗ Branch 2 not taken.
500 in.get_fields();
3005
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 500 times.
500 if(in.nb_fields() != index_t(dimension_)) {
3006 Logger::err("I/O")
3007 << "Line " << in.line_number()
3008 << " expected " << dimension_ << " point coordinates"
3009 << std::endl;
3010 }
3011
2/2
✓ Branch 0 taken 4000 times.
✓ Branch 1 taken 500 times.
4500 for(coord_index_t c = 0; c < dimension_; ++c) {
3012
1/2
✓ Branch 1 taken 4000 times.
✗ Branch 2 not taken.
4000 P[c] = in.field_as_double(c);
3013 }
3014 500 set_mesh_point(M,v,P.data(),dimension_);
3015 }
3016
3017
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if(ioflags.has_element(MESH_CELLS)) {
3018
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if(has_arbitrary_cells) {
3019 for(index_t t = 0; t < nb_cells; ++t) {
3020 if(!in.get_line()) {
3021 Logger::err("I/O")
3022 << "Unexpected end of file"
3023 << std::endl;
3024 return false;
3025 }
3026 in.get_fields();
3027 if(
3028 in.nb_fields() >= 2 &&
3029 in.field_matches(0,"#") && in.field_matches(1,"C")
3030 ) {
3031 if(in.nb_fields() != 6) {
3032 Logger::err("I/O")
3033 << "Line " << in.line_number()
3034 << " expected # C v1 v2 v3 v4"
3035 << std::endl;
3036 return false;
3037 }
3038 M.cells.create_connector(
3039 in.field_as_uint(2),
3040 in.field_as_uint(3),
3041 in.field_as_uint(4),
3042 in.field_as_uint(5)
3043 );
3044 } else {
3045 if(in.nb_fields() > 0) {
3046 index_t nb_vertices_in_cell =
3047 in.field_as_uint(0);
3048 switch(nb_vertices_in_cell) {
3049 case 4: {
3050 M.cells.create_tet(
3051 in.field_as_uint(1),
3052 in.field_as_uint(2),
3053 in.field_as_uint(3),
3054 in.field_as_uint(4)
3055 );
3056 } break;
3057 case 8: {
3058 M.cells.create_hex(
3059 in.field_as_uint(1),
3060 in.field_as_uint(2),
3061 in.field_as_uint(3),
3062 in.field_as_uint(4),
3063 in.field_as_uint(5),
3064 in.field_as_uint(6),
3065 in.field_as_uint(7),
3066 in.field_as_uint(8)
3067 );
3068 } break;
3069 case 6: {
3070 M.cells.create_prism(
3071 in.field_as_uint(1),
3072 in.field_as_uint(2),
3073 in.field_as_uint(3),
3074 in.field_as_uint(4),
3075 in.field_as_uint(5),
3076 in.field_as_uint(6)
3077 );
3078 } break;
3079 case 5: {
3080 M.cells.create_pyramid(
3081 in.field_as_uint(1),
3082 in.field_as_uint(2),
3083 in.field_as_uint(3),
3084 in.field_as_uint(4),
3085 in.field_as_uint(5)
3086 );
3087 } break;
3088 default: {
3089 Logger::err("I/O")
3090 << "Line " << in.line_number()
3091 << " unexpected number of vertices in cell:"
3092 << nb_vertices_in_cell
3093 << std::endl;
3094 return false;
3095 }
3096 }
3097 }
3098 }
3099 }
3100 } else {
3101
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 M.cells.create_tets(nb_cells);
3102
2/2
✓ Branch 0 taken 1536 times.
✓ Branch 1 taken 4 times.
1540 for(index_t t = 0; t < nb_cells; ++t) {
3103
2/4
✓ Branch 1 taken 1536 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 1536 times.
1536 if(!in.get_line()) {
3104 Logger::err("I/O")
3105 << "Unexpected end of file"
3106 << std::endl;
3107 return false;
3108 }
3109
1/2
✓ Branch 1 taken 1536 times.
✗ Branch 2 not taken.
1536 in.get_fields();
3110
3/6
✓ Branch 0 taken 1536 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 1536 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 1536 times.
1536 if(in.nb_fields() != 5 || in.field_as_int(0) != 4) {
3111 Logger::err("I/O")
3112 << "Line " << in.line_number()
3113 << " expected 4 v1 v2 v3 v4"
3114 << std::endl;
3115 } else {
3116
2/2
✓ Branch 0 taken 6144 times.
✓ Branch 1 taken 1536 times.
7680 for(index_t i = 0; i < 4; ++i) {
3117
1/2
✓ Branch 1 taken 6144 times.
✗ Branch 2 not taken.
6144 index_t v = in.field_as_uint(i + 1);
3118
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6144 times.
6144 if(i >= nb_vertices) {
3119 Logger::err("I/O")
3120 << "Line " << in.line_number()
3121 << "invalid vertex index"
3122 << std::endl;
3123 return false;
3124 }
3125 M.cells.set_vertex(t, i, v);
3126 }
3127 }
3128 }
3129 }
3130 }
3131 return true;
3132 4 }
3133
3134 /**
3135 * \brief Saves a mesh into a file in TET format.
3136 * \param[in] M The mesh to save
3137 * \param[in] filename name of the file
3138 * \param[in] ioflags specifies which attributes and elements
3139 * should be saved
3140 * \return true on success, false otherwise
3141 */
3142 bool save(
3143 const Mesh& M, const std::string& filename,
3144 const MeshIOFlags& ioflags
3145 ) override {
3146 geo_argused(ioflags);
3147
3148 if(M.vertices.dimension() < dimension_) {
3149 return false;
3150 }
3151 std::ofstream out(filename.c_str());
3152 if(!out) {
3153 return false;
3154 }
3155 vector<double> P(dimension_);
3156 if(M.cells.are_simplices()) {
3157 out << M.vertices.nb() << " vertices" << std::endl;
3158 out << M.cells.nb() << " tets" << std::endl;
3159 for(index_t v = 0; v < M.vertices.nb(); ++v) {
3160 get_mesh_point(M,v,P.data(),dimension_);
3161 for(coord_index_t c = 0; c < dimension_; ++c) {
3162 out << P[c] << " ";
3163 }
3164 out << std::endl;
3165 }
3166 for(index_t t = 0; t < M.cells.nb(); ++t) {
3167 out << "4";
3168 for(index_t i = 0; i < 4; ++i) {
3169 out << " " << M.cells.vertex(t, i);
3170 }
3171 out << std::endl;
3172 }
3173 } else {
3174 out << M.vertices.nb() << " vertices" << std::endl;
3175 out << M.cells.nb() << " cells" << std::endl;
3176 for(index_t v = 0; v < M.vertices.nb(); ++v) {
3177 get_mesh_point(M,v,P.data(),dimension_);
3178 for(coord_index_t c = 0; c < dimension_; ++c) {
3179 out << P[c] << " ";
3180 }
3181 out << std::endl;
3182 }
3183 bool has_connectors = false;
3184 for(index_t c=0; c<M.cells.nb(); ++c) {
3185 switch(M.cells.type(c)) {
3186 case MESH_TET:
3187 case MESH_HEX:
3188 case MESH_PRISM:
3189 case MESH_PYRAMID: {
3190 out << M.cells.nb_vertices(c) << " ";
3191 for(index_t lv=0; lv<M.cells.nb_vertices(c); ++lv) {
3192 out << M.cells.vertex(c,lv) << " ";
3193 }
3194 out << std::endl;
3195 } break;
3196 case MESH_CONNECTOR: {
3197 has_connectors=true;
3198 } break;
3199 case MESH_NB_CELL_TYPES:
3200 geo_assert_not_reached;
3201 }
3202 }
3203 if(has_connectors) {
3204 for(index_t c=0; c<M.cells.nb(); ++c) {
3205 if(M.cells.type(c) == MESH_CONNECTOR) {
3206 out << "# C"
3207 << " " << M.cells.vertex(c,0)
3208 << " " << M.cells.vertex(c,1)
3209 << " " << M.cells.vertex(c,2)
3210 << " " << M.cells.vertex(c,3)
3211 << std::endl;
3212 }
3213 }
3214 }
3215 }
3216 return true;
3217 }
3218
3219 private:
3220 coord_index_t dimension_;
3221 };
3222
3223 /************************************************************************/
3224
3225 /**
3226 * \brief IO handler for the TET6 file format
3227 * \see TETIOHandler
3228 */
3229 class GEOGRAM_API TET6IOHandler : public TETIOHandler {
3230 public:
3231 TET6IOHandler() : TETIOHandler(6) {
3232 }
3233 };
3234
3235 /**
3236 * \brief IO handler for the TET8 file format
3237 * \see TETIOHandler
3238 */
3239 class GEOGRAM_API TET8IOHandler : public TETIOHandler {
3240 public:
3241 4 TET8IOHandler() : TETIOHandler(8) {
3242 }
3243 };
3244
3245
3246 /************************************************************************/
3247
3248 /**
3249 * \brief IO handler for the geogram native file format.
3250 */
3251 class GEOGRAM_API GeogramIOHandler : public MeshIOHandler {
3252 public:
3253
3254
3255 /**
3256 * \brief Loads a mesh from a GeoFile ('.geogram' file format).
3257 * \details
3258 * Loads the contents of the InputGeoFile \p geofile and stores the
3259 * resulting mesh to \p M. This function can be used to load several
3260 * meshes that are stored in the same GeoFile.
3261 * \param[in] in a reference to the InputGeoFile
3262 * \param[out] M the loaded mesh
3263 * \param[in] ioflags specifies which attributes and
3264 * elements should be loaded
3265 * \return true on success, false otherwise.
3266 */
3267 3 virtual bool load(
3268 InputGeoFile& in,
3269 Mesh& M,
3270 const MeshIOFlags& ioflags = MeshIOFlags()
3271 ) {
3272
3273 3 M.clear();
3274 try {
3275
3276 std::string chunk_class;
3277 for(
3278
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 chunk_class = in.next_chunk();
3279
3/4
✓ Branch 0 taken 28 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 28 times.
✗ Branch 3 not taken.
31 chunk_class != "EOFL" && chunk_class != "SPTR";
3280
1/2
✓ Branch 1 taken 28 times.
✗ Branch 2 not taken.
28 chunk_class = in.next_chunk()
3281 ) {
3282
3283
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 21 times.
28 if(chunk_class == "ATTS") {
3284
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 read_attribute_set(in, M, ioflags);
3285
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 12 times.
21 } else if(chunk_class == "ATTR") {
3286 9 if(
3287 9 String::string_starts_with(
3288
5/10
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 9 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 9 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 4 times.
✓ Branch 10 taken 5 times.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
18 in.current_attribute().name, "GEO::Mesh::"
3289 )
3290 ) {
3291
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 read_internal_attribute(in, M, ioflags);
3292 } else {
3293
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 read_user_attribute(in, M, ioflags);
3294 }
3295 }
3296 }
3297
3298 // Create facet "sentry"
3299
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if(!M.facets.are_simplices()) {
3300 M.facets.facet_ptr_[M.facets.nb()] = M.facet_corners.nb();
3301 }
3302
3303 // Create cell "sentry"
3304
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if(!M.cells.are_simplices()) {
3305 M.cells.cell_ptr_[M.cells.nb()] = M.cell_corners.nb();
3306 }
3307
3308 // This warning when loading a single mesh from a file that may
3309 // contain several meshes -> deactivated for now.
3310 // if(chunk_class == "SPTR") {
3311 // Logger::out("GeoFile")
3312 // << "File may contain several objects"
3313 // << std::endl;
3314 // }
3315
3316 } catch(const GeoFileException& exc) {
3317 Logger::err("I/O") << exc.what() << std::endl;
3318 M.clear();
3319 return false;
3320 } catch(...) {
3321 Logger::err("I/O") << "Caught exception" << std::endl;
3322 M.clear();
3323 return false;
3324 }
3325
3326 // Sanity check for the sentry
3327
3/8
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
5 geo_assert(
3328 M.facets.nb() == 0 || (
3329 M.facets.corners_end(M.facets.nb()-1) == M.facet_corners.nb()
3330 )
3331 );
3332
3333 return true;
3334 }
3335
3336 /**
3337 * \brief Saves a mesh to a GeoFile ('.geogram' file format)
3338 * \details
3339 * Saves mesh \p M to the GeoFile \p geofile. This function can be
3340 * used to write several meshes into the same GeoFile.
3341 * \param[in] M the mesh to save
3342 * \param[in] out a reference to the OutputGeoFile
3343 * \param[in] ioflags specifies which attributes and elements
3344 * should be saved
3345 * \return true on success, false otherwise.
3346 */
3347 35 virtual bool save(
3348 const Mesh& M, OutputGeoFile& out,
3349 const MeshIOFlags& ioflags = MeshIOFlags(),
3350 bool save_command_line = false
3351 ) {
3352 try {
3353
3354
1/2
✓ Branch 0 taken 35 times.
✗ Branch 1 not taken.
35 if(save_command_line) {
3355 // Save command line in file
3356 std::vector<std::string> args;
3357
1/2
✓ Branch 1 taken 35 times.
✗ Branch 2 not taken.
35 CmdLine::get_args(args);
3358
1/2
✓ Branch 1 taken 35 times.
✗ Branch 2 not taken.
35 out.write_command_line(args);
3359 35 }
3360
3361
2/4
✓ Branch 0 taken 35 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 35 times.
✗ Branch 3 not taken.
35 if(ioflags.has_element(MESH_VERTICES) && M.vertices.nb() != 0) {
3362
1/2
✓ Branch 1 taken 35 times.
✗ Branch 2 not taken.
35 out.write_attribute_set(
3363
2/4
✓ Branch 1 taken 35 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 35 times.
✗ Branch 5 not taken.
70 "GEO::Mesh::vertices",
3364 M.vertices.nb()
3365 );
3366
1/2
✓ Branch 1 taken 35 times.
✗ Branch 2 not taken.
35 save_attributes(
3367
1/2
✓ Branch 1 taken 35 times.
✗ Branch 2 not taken.
70 out, "GEO::Mesh::vertices", M.vertices.attributes()
3368 );
3369 }
3370
3371
3/4
✓ Branch 0 taken 35 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 32 times.
✓ Branch 3 taken 3 times.
35 if(ioflags.has_element(MESH_EDGES) && M.edges.nb() != 0) {
3372
1/2
✓ Branch 1 taken 32 times.
✗ Branch 2 not taken.
32 out.write_attribute_set(
3373
2/4
✓ Branch 1 taken 32 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 32 times.
✗ Branch 5 not taken.
64 "GEO::Mesh::edges",
3374 M.edges.nb()
3375 );
3376
3377
1/2
✓ Branch 1 taken 32 times.
✗ Branch 2 not taken.
32 out.write_attribute(
3378
2/6
✓ Branch 1 taken 32 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 32 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
64 "GEO::Mesh::edges",
3379
2/6
✓ Branch 1 taken 32 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 32 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
64 "GEO::Mesh::edges::edge_vertex",
3380
2/4
✓ Branch 1 taken 32 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 32 times.
✗ Branch 5 not taken.
64 "index_t",
3381 sizeof(index_t),
3382 2,
3383 M.edges.edge_vertex_.data()
3384 );
3385
3386
1/2
✓ Branch 1 taken 32 times.
✗ Branch 2 not taken.
32 save_attributes(
3387
1/2
✓ Branch 1 taken 32 times.
✗ Branch 2 not taken.
64 out, "GEO::Mesh::edges", M.edges.attributes()
3388 );
3389 }
3390
3391
3/4
✓ Branch 0 taken 35 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 34 times.
✓ Branch 3 taken 1 times.
35 if(ioflags.has_element(MESH_FACETS) && M.facets.nb() != 0) {
3392
1/2
✓ Branch 1 taken 34 times.
✗ Branch 2 not taken.
34 out.write_attribute_set(
3393
2/4
✓ Branch 1 taken 34 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 34 times.
✗ Branch 5 not taken.
68 "GEO::Mesh::facets",
3394 M.facets.nb()
3395 );
3396
3397
1/2
✓ Branch 1 taken 34 times.
✗ Branch 2 not taken.
34 save_attributes(
3398
2/4
✓ Branch 1 taken 34 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 34 times.
68 out, "GEO::Mesh::facets", M.facets.attributes()
3399 );
3400
3401
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 34 times.
34 if(!M.facets.are_simplices()) {
3402 out.write_attribute(
3403 "GEO::Mesh::facets",
3404 "GEO::Mesh::facets::facet_ptr",
3405 "index_t",
3406 sizeof(index_t),
3407 1,
3408 M.facets.facet_ptr_.data()
3409 );
3410 }
3411
3412
1/2
✓ Branch 1 taken 34 times.
✗ Branch 2 not taken.
34 out.write_attribute_set(
3413
2/4
✓ Branch 1 taken 34 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 34 times.
✗ Branch 5 not taken.
68 "GEO::Mesh::facet_corners",
3414 M.facet_corners.nb()
3415 );
3416
3417
1/2
✓ Branch 1 taken 34 times.
✗ Branch 2 not taken.
34 out.write_attribute(
3418
2/6
✓ Branch 1 taken 34 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 34 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
68 "GEO::Mesh::facet_corners",
3419
2/6
✓ Branch 1 taken 34 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 34 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
68 "GEO::Mesh::facet_corners::corner_vertex",
3420
2/4
✓ Branch 1 taken 34 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 34 times.
✗ Branch 5 not taken.
68 "index_t",
3421 sizeof(index_t),
3422 1,
3423 M.facet_corners.corner_vertex_.data()
3424 );
3425
3426
1/2
✓ Branch 1 taken 34 times.
✗ Branch 2 not taken.
34 out.write_attribute(
3427
2/6
✓ Branch 1 taken 34 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 34 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
68 "GEO::Mesh::facet_corners",
3428
2/6
✓ Branch 1 taken 34 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 34 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
68 "GEO::Mesh::facet_corners::corner_adjacent_facet",
3429
2/4
✓ Branch 1 taken 34 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 34 times.
✗ Branch 5 not taken.
68 "index_t",
3430 sizeof(index_t),
3431 1,
3432 M.facet_corners.corner_adjacent_facet_.data()
3433 );
3434
3435
1/2
✓ Branch 1 taken 34 times.
✗ Branch 2 not taken.
34 save_attributes(
3436
1/2
✓ Branch 1 taken 34 times.
✗ Branch 2 not taken.
68 out, "GEO::Mesh::facet_corners",
3437 M.facet_corners.attributes()
3438 );
3439
3440 }
3441
3442
2/4
✓ Branch 0 taken 35 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 35 times.
35 if(ioflags.has_element(MESH_CELLS) && M.cells.nb() != 0) {
3443 out.write_attribute_set(
3444 "GEO::Mesh::cells",
3445 M.cells.nb()
3446 );
3447
3448 save_attributes(
3449 out, "GEO::Mesh::cells",
3450 M.cells.attributes()
3451 );
3452
3453 if(!M.cells.are_simplices()) {
3454
3455 out.write_attribute(
3456 "GEO::Mesh::cells",
3457 "GEO::Mesh::cells::cell_type",
3458 "char",
3459 sizeof(char),
3460 1,
3461 M.cells.cell_type_.data()
3462 );
3463
3464 out.write_attribute(
3465 "GEO::Mesh::cells",
3466 "GEO::Mesh::cells::cell_ptr",
3467 "index_t",
3468 sizeof(index_t),
3469 1,
3470 M.cells.cell_ptr_.data()
3471 );
3472 }
3473
3474 out.write_attribute_set(
3475 "GEO::Mesh::cell_corners",
3476 M.cell_corners.nb()
3477 );
3478
3479 out.write_attribute(
3480 "GEO::Mesh::cell_corners",
3481 "GEO::Mesh::cell_corners::corner_vertex",
3482 "index_t",
3483 sizeof(index_t),
3484 1,
3485 M.cell_corners.corner_vertex_.data()
3486 );
3487
3488 save_attributes(
3489 out,
3490 "GEO::Mesh::cell_corners",
3491 M.cell_corners.attributes()
3492 );
3493
3494 out.write_attribute_set(
3495 "GEO::Mesh::cell_facets",
3496 M.cell_facets.nb()
3497 );
3498
3499 out.write_attribute(
3500 "GEO::Mesh::cell_facets",
3501 "GEO::Mesh::cell_facets::adjacent_cell",
3502 "index_t",
3503 sizeof(index_t),
3504 1,
3505 M.cell_facets.adjacent_cell_.data()
3506 );
3507
3508 save_attributes(
3509 out,
3510 "GEO::Mesh::cell_facets",
3511 M.cell_facets.attributes()
3512 );
3513 }
3514
3515 } catch(const GeoFileException& exc) {
3516 Logger::err("I/O") << exc.what() << std::endl;
3517 return false;
3518 }
3519 return true;
3520 }
3521
3522 /**
3523 * \copydoc MeshIOHandler::load()
3524 */
3525 3 bool load(
3526 const std::string& filename, Mesh& M,
3527 const MeshIOFlags& ioflags = MeshIOFlags()
3528 ) override {
3529 bool result = true;
3530 try {
3531
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 InputGeoFile in(filename);
3532
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 result = load(in, M, ioflags);
3533
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
3 } catch(const GeoFileException& exc) {
3534 Logger::err("I/O") << exc.what() << std::endl;
3535 result = false;
3536 } catch(...) {
3537 Logger::err("I/O") << "Caught exception" << std::endl;
3538 result = false;
3539 }
3540 3 return result;
3541 }
3542
3543 /**
3544 * \copydoc MeshIOHandler::save()
3545 */
3546 35 bool save(
3547 const Mesh& M, const std::string& filename,
3548 const MeshIOFlags& ioflags = MeshIOFlags()
3549 ) override {
3550 bool result = true;
3551 try {
3552 OutputGeoFile out(
3553 filename,
3554
2/4
✓ Branch 1 taken 35 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 35 times.
✗ Branch 5 not taken.
35 index_t(CmdLine::get_arg_int("sys:compression_level"))
3555
1/2
✓ Branch 1 taken 35 times.
✗ Branch 2 not taken.
35 );
3556
1/2
✓ Branch 1 taken 35 times.
✗ Branch 2 not taken.
35 result = save(M, out, ioflags, true);
3557 } catch(const GeoFileException& exc) {
3558 Logger::err("I/O") << exc.what() << std::endl;
3559 result = false;
3560 } catch(...) {
3561 Logger::err("I/O") << "Caught exception" << std::endl;
3562 result = false;
3563 }
3564 35 return result;
3565 }
3566
3567 protected:
3568
3569 /**
3570 * \brief Reads an attribute set from a geogram file and
3571 * creates the relevant elements in a mesh.
3572 * \param[in] in a reference to the InputGeoFile
3573 * \param[in] M a reference to the Mesh
3574 * \param[in] ioflags the MeshIOFlags that specify which
3575 * attributes and mesh elements should be read
3576 */
3577 7 void read_attribute_set(
3578 InputGeoFile& in,
3579 Mesh& M,
3580 const MeshIOFlags& ioflags
3581 ) {
3582 const std::string& name =
3583 7 in.current_attribute_set().name;
3584 7 index_t nb_items = in.current_attribute_set().nb_items;
3585
3586 if(
3587
3/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
7 name == "GEO::Mesh::vertices" &&
3588 ioflags.has_element(MESH_VERTICES)
3589 ) {
3590 3 M.vertices.resize_store(nb_items);
3591 } else if(
3592
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
4 name == "GEO::Mesh::edges" &&
3593 ioflags.has_element(MESH_EDGES)
3594 ) {
3595 M.edges.resize_store(nb_items);
3596 } else if(
3597
3/4
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
4 name == "GEO::Mesh::facets" &&
3598 ioflags.has_element(MESH_FACETS)
3599 ) {
3600 2 M.facets.resize_store(nb_items);
3601 } else if(
3602
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
2 name == "GEO::Mesh::facet_corners" &&
3603 ioflags.has_element(MESH_FACETS)
3604 ) {
3605 2 M.facet_corners.resize_store(nb_items);
3606 } else if(
3607 name == "GEO::Mesh::cells" &&
3608 ioflags.has_element(MESH_CELLS)
3609 ) {
3610 M.cells.resize_store(nb_items);
3611 } else if(
3612 name == "GEO::Mesh::cell_corners" &&
3613 ioflags.has_element(MESH_CELLS)
3614 ) {
3615 M.cell_corners.resize_store(nb_items);
3616 } else if(
3617 name == "GEO::Mesh::cell_facets" &&
3618 ioflags.has_element(MESH_CELLS)
3619 ) {
3620 M.cell_facets.resize_store(nb_items);
3621 }
3622 7 }
3623
3624 /**
3625 * \brief Reads a user attribute from a geogram file and
3626 * stores it in a mesh.
3627 * \param[in] in a reference to the InputGeoFile
3628 * \param[in] M a reference to the Mesh
3629 * \param[in] ioflags the MeshIOFlags that specify which
3630 * attributes and mesh elements should be read
3631 */
3632 5 void read_user_attribute(
3633 InputGeoFile& in,
3634 Mesh& M,
3635 const MeshIOFlags& ioflags
3636 ) {
3637 const std::string& name =
3638 5 in.current_attribute().name;
3639 const std::string& set_name =
3640 5 in.current_attribute_set().name;
3641
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 2 times.
5 if(set_name == "GEO::Mesh::vertices") {
3642
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if(ioflags.has_element(MESH_VERTICES)) {
3643 // Vertex geometry is a special attribute, already
3644 // created by the Mesh class, therefore we cannot use
3645 // the generic read_attribute() function.
3646
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if(name == "point") {
3647 3 M.vertices.set_double_precision();
3648 3 M.vertices.set_dimension(
3649 3 in.current_attribute().dimension
3650 );
3651 3 in.read_attribute(M.vertices.point_ptr(0));
3652 } else if(name == "point_fp32") {
3653 M.vertices.set_single_precision();
3654 M.vertices.set_dimension(
3655 in.current_attribute().dimension
3656 );
3657 in.read_attribute(
3658 M.vertices.single_precision_point_ptr(0)
3659 );
3660 } else {
3661 read_attribute(in, M.vertices.attributes());
3662 }
3663 }
3664
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 } else if(set_name == "GEO::Mesh::edges") {
3665 if(ioflags.has_element(MESH_EDGES)) {
3666 read_attribute(in, M.edges.attributes());
3667 }
3668
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 } else if(set_name == "GEO::Mesh::facets") {
3669 if(ioflags.has_element(MESH_FACETS)) {
3670 read_attribute(in, M.facets.attributes());
3671 }
3672
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 } else if(set_name == "GEO::Mesh::facet_corners") {
3673
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if(ioflags.has_element(MESH_FACETS)) {
3674 2 read_attribute(
3675 in, M.facet_corners.attributes()
3676 );
3677 }
3678 } else if(set_name == "GEO::Mesh::cells") {
3679 if(ioflags.has_element(MESH_CELLS)) {
3680 read_attribute(in, M.cells.attributes());
3681 }
3682 } else if(set_name == "GEO::Mesh::cell_corners") {
3683 if(ioflags.has_element(MESH_CELLS)) {
3684 read_attribute(in, M.cell_corners.attributes());
3685 }
3686 } else if(set_name == "GEO::Mesh::cell_facets") {
3687 if(ioflags.has_element(MESH_CELLS)) {
3688 read_attribute(in, M.cell_facets.attributes());
3689 }
3690 }
3691 5 }
3692
3693 /**
3694 * \brief Reads an internal attribute from a geogram file and
3695 * stores it in a mesh.
3696 * \param[in] in a reference to the InputGeoFile
3697 * \param[in] M a reference to the Mesh
3698 * \param[in] ioflags the MeshIOFlags that specify which
3699 * attributes and mesh elements should be read
3700 */
3701 4 void read_internal_attribute(
3702 InputGeoFile& in,
3703 Mesh& M,
3704 const MeshIOFlags& ioflags
3705 ) {
3706 const std::string& name =
3707 4 in.current_attribute().name;
3708
3709 const std::string& set_name =
3710 4 in.current_attribute_set().name;
3711
3712
2/4
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 4 times.
8 if(!String::string_starts_with(name, set_name + "::")) {
3713 Logger::warn("I/O")
3714 << "Invalid internal attribute (GEO::Mesh:: scoped): "
3715 << name << " does not start with "
3716 << set_name
3717 << std::endl;
3718 return;
3719 }
3720
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if(name == "GEO::Mesh::edges::edge_vertex") {
3721 if(ioflags.has_element(MESH_EDGES)) {
3722 M.edges.edge_vertex_.resize(M.edges.nb()*2);
3723 in.read_attribute(M.edges.edge_vertex_.data());
3724 }
3725
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 } else if(name == "GEO::Mesh::facets::facet_ptr") {
3726 if(ioflags.has_element(MESH_FACETS)) {
3727 M.facets.is_simplicial_ = false;
3728 M.facets.facet_ptr_.resize(M.facets.nb()+1);
3729 in.read_attribute(M.facets.facet_ptr_.data());
3730 }
3731
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 } else if(name == "GEO::Mesh::facet_corners::corner_vertex") {
3732
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if(ioflags.has_element(MESH_FACETS)) {
3733 2 in.read_attribute(M.facet_corners.corner_vertex_.data());
3734 }
3735 2 } else if(
3736
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 name == "GEO::Mesh::facet_corners::corner_adjacent_facet"
3737 ) {
3738
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if(ioflags.has_element(MESH_FACETS)) {
3739 2 in.read_attribute(
3740 M.facet_corners.corner_adjacent_facet_.data()
3741 );
3742 }
3743 } else if(name == "GEO::Mesh::cells::cell_type") {
3744 if(ioflags.has_element(MESH_CELLS)) {
3745 M.cells.is_simplicial_ = false;
3746 M.cells.cell_type_.resize(M.cells.nb());
3747 in.read_attribute(M.cells.cell_type_.data());
3748 }
3749 } else if(name == "GEO::Mesh::cells::cell_ptr") {
3750 if(ioflags.has_element(MESH_CELLS)) {
3751 M.cells.is_simplicial_ = false;
3752 M.cells.cell_ptr_.resize(M.cells.nb()+1);
3753 in.read_attribute(M.cells.cell_ptr_.data());
3754 }
3755 } else if(name == "GEO::Mesh::cell_corners::corner_vertex") {
3756 if(ioflags.has_element(MESH_CELLS)) {
3757 in.read_attribute(M.cell_corners.corner_vertex_.data());
3758 }
3759 } else if(name == "GEO::Mesh::cell_facets::adjacent_cell") {
3760 if(ioflags.has_element(MESH_CELLS)) {
3761 in.read_attribute(M.cell_facets.adjacent_cell_.data());
3762 }
3763 }
3764 }
3765
3766 /**
3767 * \brief Reads a user attribute from a geogram file and
3768 * stores it in an AttributesManager
3769 * \param[in] in a reference to the InputGeoFile
3770 * \param[in] attributes a reference to the AttributesManager
3771 * where the read attribute should be stored
3772 */
3773 2 void read_attribute(
3774 InputGeoFile& in,
3775 AttributesManager& attributes
3776 ) {
3777
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if(
3778 !AttributeStore::element_type_name_is_known(
3779
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
2 in.current_attribute().element_type
3780 )
3781 ) {
3782 Logger::warn("I/O")
3783 << "Skipping attribute "
3784 << in.current_attribute().name
3785 << ":"
3786 << demangle(in.current_attribute().element_type)
3787 << " (unknown type)"
3788 << std::endl;
3789 return;
3790 }
3791 AttributeStore* store =
3792 2 AttributeStore::create_attribute_store_by_element_type_name(
3793 2 in.current_attribute().element_type,
3794 2 in.current_attribute().dimension
3795 );
3796 2 attributes.bind_attribute_store(in.current_attribute().name,store);
3797 2 in.read_attribute(store->data());
3798 }
3799
3800 /**
3801 * \brief Writes all the user attributes of an AttributesManager
3802 * into a geogram file.
3803 * \param[out] out a reference to the OutputGeoFile
3804 * \param[in] attribute_set_name the name to be used for the attribute
3805 * set in the geogram file
3806 * \param[in] attributes a reference to the AttributesManager
3807 */
3808
1/2
✓ Branch 1 taken 135 times.
✗ Branch 2 not taken.
135 void save_attributes(
3809 OutputGeoFile& out,
3810 const std::string& attribute_set_name,
3811 AttributesManager& attributes
3812 ) {
3813 vector<std::string> attribute_names;
3814
1/2
✓ Branch 1 taken 135 times.
✗ Branch 2 not taken.
135 attributes.list_attribute_names(attribute_names);
3815
2/2
✓ Branch 0 taken 37 times.
✓ Branch 1 taken 135 times.
209 for(index_t i=0; i<attribute_names.size(); ++i) {
3816
1/2
✓ Branch 1 taken 37 times.
✗ Branch 2 not taken.
37 AttributeStore* store = attributes.find_attribute_store(
3817 attribute_names[i]
3818 );
3819 37 if(
3820 AttributeStore::element_typeid_name_is_known(
3821
2/4
✓ Branch 1 taken 37 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 37 times.
✗ Branch 4 not taken.
74 store->element_typeid_name()
3822
1/2
✓ Branch 0 taken 37 times.
✗ Branch 1 not taken.
37 ) &&
3823
1/2
✓ Branch 1 taken 37 times.
✗ Branch 2 not taken.
37 AttributeStore::element_by_typeid_name_is_trivially_copyable(
3824
2/8
✓ Branch 1 taken 37 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 37 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
74 store->element_typeid_name()
3825 )
3826 ) {
3827 std::string element_type =
3828 AttributeStore::element_type_name_by_element_typeid_name(
3829
2/4
✓ Branch 1 taken 37 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 37 times.
✗ Branch 5 not taken.
74 store->user_element_typeid_name()
3830
1/2
✓ Branch 1 taken 37 times.
✗ Branch 2 not taken.
37 );
3831
3832
1/2
✓ Branch 1 taken 37 times.
✗ Branch 2 not taken.
37 out.write_attribute(
3833 attribute_set_name,
3834 attribute_names[i],
3835 element_type,
3836 store->element_size(),
3837 store->dimension(),
3838 store->data()
3839 );
3840 } else {
3841 Logger::warn("I/O")
3842 << "Skipping attribute: "
3843 << attribute_names[i]
3844 << " on "
3845 << attribute_set_name
3846 << std::endl;
3847
3848 Logger::warn("I/O")
3849 << "Unsupported type: "
3850 << demangle(store->element_typeid_name())
3851 << std::endl;
3852 }
3853 }
3854 135 }
3855 };
3856
3857 /************************************************************************/
3858
3859 /**
3860 * \brief IO handler for graphite files.
3861 * \details Graphite files with a single object can be directly read
3862 * by geogram.
3863 */
3864 class GraphiteIOHandler : public GeogramIOHandler {
3865 public:
3866 /**
3867 * \copydoc MeshIOHandler::save()
3868 */
3869 bool save(
3870 const Mesh& M, const std::string& filename,
3871 const MeshIOFlags& ioflags = MeshIOFlags()
3872 ) override {
3873 geo_argused(M);
3874 geo_argused(filename);
3875 geo_argused(ioflags);
3876 Logger::err("I/O")
3877 << "graphite file format not supported for writing"
3878 << std::endl;
3879 return false;
3880 }
3881
3882 /**
3883 * \copydoc MeshIOHandler::save()
3884 */
3885 bool save(
3886 const Mesh& M, OutputGeoFile& out,
3887 const MeshIOFlags& ioflags = MeshIOFlags(),
3888 bool save_command_line = false
3889 ) override {
3890 geo_argused(M);
3891 geo_argused(out);
3892 geo_argused(ioflags);
3893 geo_argused(save_command_line);
3894 Logger::err("I/O")
3895 << "graphite file format not supported for writing"
3896 << std::endl;
3897 return false;
3898 }
3899 };
3900
3901 /************************************************************************/
3902
3903 /**
3904 * \brief IO handler for PDB (Protein DataBase) files.
3905 */
3906 class PDBIOHandler : public MeshIOHandler {
3907 public:
3908 bool load(
3909 const std::string& filename, Mesh& M,
3910 const MeshIOFlags& ioflags
3911 ) override {
3912 geo_argused(ioflags);
3913 M.clear();
3914 M.vertices.set_dimension(3);
3915 std::ifstream in(filename.c_str());
3916 if(!in) {
3917 return false;
3918 }
3919 Attribute<char> atom_type(M.vertices.attributes(), "atom_type");
3920 Attribute<char> chain_id(M.vertices.attributes(), "chain_id");
3921 while(in) {
3922 std::string line;
3923 getline(in, line);
3924 while(line.length() < 80) {
3925 line.push_back(' ');
3926 }
3927 std::string record_name = get_columns(line, 1, 6) ;
3928 if(record_name == "ATOM " || record_name == "HETATM") {
3929 std::string serial = get_columns(line, 7, 11);
3930 std::string name = get_columns(line, 11, 16);
3931 std::string altLoc = get_columns(line, 17, 17);
3932 std::string resName = get_columns(line, 18, 20);
3933 std::string chainID = get_columns(line, 22, 22);
3934 std::string resSeq = get_columns(line, 23, 26);
3935 std::string iCode = get_columns(line, 27, 27);
3936 double x = to_double(get_columns(line, 31, 38));
3937 double y = to_double(get_columns(line, 39, 46));
3938 double z = to_double(get_columns(line, 47, 54));
3939 //double occupancy = to_double(get_columns(line, 55, 60));
3940 //double tempFactor = to_double(get_columns(line, 61, 66));
3941 std::string element = get_columns(line, 77, 78);
3942 std::string charge = get_columns(line, 79, 80);
3943
3944 index_t v = M.vertices.create_vertex();
3945 if(M.vertices.single_precision()) {
3946 M.vertices.single_precision_point_ptr(v)[0] = float(x);
3947 M.vertices.single_precision_point_ptr(v)[1] = float(y);
3948 M.vertices.single_precision_point_ptr(v)[2] = float(z);
3949 } else {
3950 M.vertices.point_ptr(v)[0] = x;
3951 M.vertices.point_ptr(v)[1] = y;
3952 M.vertices.point_ptr(v)[2] = z;
3953 }
3954 atom_type[v] = name[3];
3955 chain_id[v] = chainID[0];
3956 }
3957 }
3958 return true;
3959 }
3960
3961 /**
3962 * \copydoc MeshIOHandler::save()
3963 */
3964 bool save(
3965 const Mesh& M, const std::string& filename,
3966 const MeshIOFlags& ioflags = MeshIOFlags()
3967 ) override {
3968 geo_argused(M);
3969 geo_argused(filename);
3970 geo_argused(ioflags);
3971 Logger::err("I/O")
3972 << "PDB file format not supported for writing"
3973 << std::endl;
3974 return false;
3975 }
3976 protected:
3977
3978 inline std::string get_columns(
3979 const std::string& s, unsigned int from_c, unsigned int to_c
3980 ) const {
3981 return s.substr(from_c - 1, to_c - from_c + 1) ;
3982 }
3983
3984 inline double to_double(const std::string& s) {
3985 return String::to_double(s);
3986 }
3987 };
3988
3989 /************************************************************************/
3990
3991 /**
3992 * \brief Mesh IO Handler for OpenVolumeMesh file format.
3993 * \details Saves a polyhedral mesh stored in a surfacic mesh that
3994 * has all cell boundaries and a vertex_id vertex attribute and cell_id
3995 * facet attribute. Note: the implementation is pretty inefficient (uses
3996 * tables).
3997 */
3998 class OVMIOHandler : public MeshIOHandler {
3999 public:
4000 /**
4001 * \copydoc MeshIOHandler::load()
4002 */
4003 bool load(
4004 const std::string& filename, Mesh& M,
4005 const MeshIOFlags& ioflags
4006 ) override {
4007 geo_argused(ioflags);
4008 M.clear();
4009 M.vertices.set_dimension(3);
4010 LineInput in(filename);
4011 if(!in.OK()) {
4012 return false;
4013 }
4014
4015 in.get_line();
4016 in.get_fields();
4017 if(
4018 in.nb_fields() != 2 ||
4019 strcmp(in.field(0),"OVM") ||
4020 strcmp(in.field(1),"ASCII")
4021 ) {
4022 Logger::err("OVM") << "Invalid file header" << std::endl;
4023 return false;
4024 }
4025
4026
4027 Attribute<int> vertex_id(M.vertices.attributes(), "vertex_id");
4028 Attribute<int> cell_id(M.facets.attributes(), "cell_id");
4029
4030 index_t nb_vertices = 0;
4031 vector<double> vertices;
4032 vector<index_t> ovm_to_vertex_id;
4033
4034 index_t nb_edges = 0;
4035 vector<index_t> edges;
4036
4037 index_t nb_facets = 0;
4038 vector<index_t> facet_ptr;
4039 vector<index_t> facet_edge;
4040
4041 index_t nb_cells = 0;
4042
4043 try {
4044 while(!in.eof()) {
4045 std::string kw = get_keyword(in);
4046 if(kw == "Vertices") {
4047 nb_vertices = get_number(in);
4048 vertices.resize(nb_vertices*3);
4049 ovm_to_vertex_id.assign(nb_vertices*3, NO_INDEX);
4050 FOR(v, nb_vertices) {
4051 in.get_line();
4052 in.get_fields();
4053 if(in.nb_fields() != 3) {
4054 throw(
4055 "Line: " +
4056 String::to_string(in.line_number()) +
4057 ":Invalid vertex, expected 3 coordinates"
4058 );
4059 }
4060 vertices[3*v] = in.field_as_double(0);
4061 vertices[3*v+1] = in.field_as_double(1);
4062 vertices[3*v+2] = in.field_as_double(2);
4063 }
4064 } else if(kw == "Edges") {
4065 nb_edges = get_number(in);
4066 edges.resize(nb_edges*2);
4067 FOR(e, nb_edges) {
4068 in.get_line();
4069 in.get_fields();
4070 if(in.nb_fields() != 2) {
4071 throw(
4072 "Line: " +
4073 String::to_string(in.line_number()) +
4074 ":Invalid edge, expected 2 indices"
4075 );
4076 }
4077 edges[2*e] = in.field_as_uint(0);
4078 edges[2*e+1] = in.field_as_uint(1);
4079 if(
4080 edges[2*e] >= nb_vertices ||
4081 edges[2*e+1] >= nb_vertices) {
4082 throw(
4083 "Line: " +
4084 String::to_string(in.line_number()) +
4085 ":Invalid vertex id in edge"
4086 );
4087 }
4088 }
4089 } else if(kw == "Faces") {
4090 nb_facets = get_number(in);
4091 facet_ptr.resize(nb_facets+1);
4092 facet_ptr[0] = 0;
4093 FOR(f, nb_facets) {
4094 in.get_line();
4095 in.get_fields();
4096 if(in.nb_fields() == 0) {
4097 throw(
4098 "Line: " +
4099 String::to_string(in.line_number()) +
4100 ":Invalid facet, empty line"
4101 );
4102 }
4103 index_t facet_size = in.field_as_uint(0);
4104 facet_ptr[f+1] = facet_ptr[f] + facet_size;
4105 if(in.nb_fields() != facet_size+1) {
4106 throw(
4107 "Line: " +
4108 String::to_string(in.line_number()) +
4109 ":Invalid facet, wrong number of elements"
4110 );
4111 }
4112 FOR(lf, facet_size) {
4113 index_t ie = in.field_as_uint(1+lf);
4114 if((ie/2) >= nb_edges) {
4115 throw(
4116 "Line: " +
4117 String::to_string(in.line_number()) +
4118 ":Invalid edge id in facet"
4119 );
4120 }
4121 facet_edge.push_back(ie);
4122 }
4123 }
4124 } else if(kw == "Polyhedra") {
4125 nb_cells = get_number(in);
4126 FOR(c, nb_cells) {
4127 in.get_line();
4128 in.get_fields();
4129 if(in.nb_fields() == 0) {
4130 throw(
4131 "Line: " +
4132 String::to_string(in.line_number()) +
4133 ":Invalid cell, empty line"
4134 );
4135 }
4136 index_t cell_size = in.field_as_uint(0);
4137 if(in.nb_fields() != cell_size + 1) {
4138 throw(
4139 "Line: " +
4140 String::to_string(in.line_number()) +
4141 ":Invalid cell, wrong number of elements"
4142 );
4143 }
4144 vector<index_t> cell_facets;
4145 FOR(lf, cell_size) {
4146 index_t f = in.field_as_uint(lf+1);
4147 if((f/2) >= nb_facets) {
4148 throw(
4149 "Line: " +
4150 String::to_string(in.line_number()) +
4151 ":Invalid facet id in cell"
4152 );
4153 }
4154 cell_facets.push_back(f);
4155 }
4156
4157 // Clear vertex ids
4158 FOR(lf, cell_size) {
4159 index_t f = cell_facets[lf];
4160 bool inverse_f = (f & 1) != 0;
4161 f /= 2;
4162 for(index_t ee = facet_ptr[f]; ee<facet_ptr[f+1]; ++ee) {
4163 index_t e = facet_edge[ee];
4164 if(inverse_f) {
4165 e = e ^ index_t(1);
4166 }
4167 // Vertex index is directly obtained from edge[]
4168 // (edge2vertices) table, and rule for orientation
4169 // (2*e for direct edge, 2*e+1 for inversed edge)
4170 // directly gives the index of the origin vertex.
4171 // If the facet is inversed, then the edge is inversed
4172 // (by inverting its least significant bit, with the
4173 // XOR e ^(index_t(1)) operation).
4174 index_t ovm_v = edges[e];
4175 ovm_to_vertex_id[ ovm_v ] = NO_INDEX;
4176 }
4177 }
4178
4179 // Create vertices
4180 FOR(lf, cell_size) {
4181 index_t f = cell_facets[lf];
4182 bool inverse_f = (f & 1) != 0;
4183 f /= 2;
4184 for(index_t ee = facet_ptr[f]; ee<facet_ptr[f+1]; ++ee) {
4185 index_t e = facet_edge[ee];
4186 if(inverse_f) {
4187 e = e ^ index_t(1);
4188 }
4189 index_t ovm_v = edges[e];
4190 if(ovm_to_vertex_id[ovm_v] == NO_INDEX) {
4191 const double* p = &(vertices[ ovm_v*3 ]);
4192 index_t new_v = M.vertices.create_vertex();
4193 set_mesh_point(M, new_v, p, 3);
4194 ovm_to_vertex_id[ovm_v] = new_v;
4195 vertex_id[new_v] = int(ovm_v);
4196 }
4197 }
4198 }
4199
4200 // Create facets
4201 FOR(lf, cell_size) {
4202 index_t f = cell_facets[lf];
4203 bool inverse_f = (f & 1) != 0;
4204 f /= 2;
4205 index_t facet_size = facet_ptr[f+1] - facet_ptr[f];
4206 index_t new_f = M.facets.create_polygon(facet_size);
4207 cell_id[new_f] = int(c);
4208 FOR(le, facet_size) {
4209 index_t ee = inverse_f ?
4210 (facet_ptr[f+1] - le - 1) :
4211 (facet_ptr[f] + le);
4212 index_t e = facet_edge[ee];
4213 if(inverse_f) {
4214 e = e ^ index_t(1);
4215 }
4216 index_t ovm_v = edges[e];
4217 index_t geo_v = ovm_to_vertex_id[ovm_v];
4218 M.facets.set_vertex(new_f, le, geo_v);
4219 }
4220 }
4221 }
4222 } else if(kw == "Vertex_Property") {
4223 skip_property(in,nb_vertices);
4224 } else if(kw == "Edge_Property") {
4225 skip_property(in,nb_edges);
4226 } else if(kw == "HalfEdge_Property") {
4227 skip_property(in,nb_edges*2);
4228 } else if(kw == "Face_Property") {
4229 skip_property(in,nb_facets);
4230 } else if(kw == "HalfFace_Property") {
4231 skip_property(in,nb_facets*2);
4232 } else if(kw == "Polyhedron_Property") {
4233 skip_property(in,nb_cells);
4234 }
4235 }
4236 } catch(const std::string& what) {
4237 Logger::err("I/O") << what << std::endl;
4238 return false;
4239 } catch(const std::exception& ex) {
4240 Logger::err("I/O") << ex.what() << std::endl;
4241 return false;
4242 } catch(...) {
4243 Logger::err("I/O") << "Caught exception" << std::endl;
4244 return false;
4245 }
4246
4247 M.facets.connect();
4248
4249 return true;
4250 }
4251
4252 /**
4253 * \copydoc MeshIOHandler::save()
4254 */
4255 bool save(
4256 const Mesh& M, const std::string& filename,
4257 const MeshIOFlags& ioflags = MeshIOFlags()
4258 ) override {
4259 geo_argused(ioflags);
4260
4261 Attribute<int> vertex_id;
4262 vertex_id.bind_if_is_defined(M.vertices.attributes(), "vertex_id");
4263 Attribute<int> cell_id;
4264 cell_id.bind_if_is_defined(M.facets.attributes(), "cell_id");
4265
4266 if(!vertex_id.is_bound()) {
4267 Logger::err("OVM") << "Missing vertex ids" << std::endl;
4268 return false;
4269 }
4270
4271 if(!cell_id.is_bound()) {
4272 Logger::err("OVM") << "Missing cell ids" << std::endl;
4273 return false;
4274 }
4275
4276 std::ofstream out(filename.c_str());
4277 if(!out) {
4278 return false;
4279 }
4280
4281 out << "OVM ASCII" << std::endl;
4282
4283 // Output the vertices
4284 {
4285 index_t nb_vertices = 0;
4286 FOR(v, M.vertices.nb()) {
4287 nb_vertices = std::max(nb_vertices, index_t(vertex_id[v]));
4288 }
4289 ++nb_vertices;
4290 vector<index_t> vid_to_v(nb_vertices);
4291 FOR(v, M.vertices.nb()) {
4292 vid_to_v[vertex_id[v]] = v;
4293 }
4294 out << "Vertices" << std::endl;
4295 out << nb_vertices << std::endl;
4296 FOR(vid, nb_vertices) {
4297 const double* p = M.vertices.point_ptr(vid_to_v[vid]);
4298 FOR(d, M.vertices.dimension()) {
4299 out << std::setprecision(17) << p[d] << " ";
4300 }
4301 out << std::endl;
4302 }
4303 }
4304
4305 std::map<bindex, index_t> edge_to_id;
4306 vector<bindex> edges;
4307 index_t nb_edges = 0;
4308
4309 // Construct edge table and output edges
4310 {
4311 FOR(f, M.facets.nb()) {
4312 for(
4313 index_t c1=M.facets.corners_begin(f);
4314 c1<M.facets.corners_end(f); ++c1
4315 ) {
4316 index_t c2 = M.facets.next_corner_around_facet(f,c1);
4317 index_t iv1 =
4318 index_t(vertex_id[M.facet_corners.vertex(c1)]);
4319 index_t iv2 =
4320 index_t(vertex_id[M.facet_corners.vertex(c2)]);
4321 bindex K(iv1, iv2);
4322 if(edge_to_id.find(K) == edge_to_id.end()) {
4323 edge_to_id[K] = nb_edges;
4324 edges.push_back(K);
4325 ++nb_edges;
4326 }
4327 }
4328 }
4329 out << "Edges" << std::endl;
4330 out << nb_edges << std::endl;
4331 FOR(e, nb_edges) {
4332 out << edges[e].indices[0] << " "
4333 << edges[e].indices[1] << std::endl;
4334 }
4335 }
4336
4337 std::map<trindex, index_t> facet_to_id;
4338 vector<index_t> facets;
4339 index_t nb_facets = 0;
4340
4341 // Construct facet table and output facets
4342 {
4343 FOR(f, M.facets.nb()) {
4344 trindex K = facet_key(M, f, vertex_id, false);
4345 trindex Kinv = facet_key(M, f, vertex_id, true);
4346 if(
4347 facet_to_id.find(K) == facet_to_id.end() &&
4348 facet_to_id.find(Kinv) == facet_to_id.end()
4349 ) {
4350 facet_to_id[K] = 2*nb_facets;
4351 facet_to_id[Kinv] = 2*nb_facets + 1;
4352 ++nb_facets;
4353 facets.push_back(f);
4354 }
4355 }
4356 out << "Faces" << std::endl;
4357 out << nb_facets << std::endl;
4358 FOR(fi, nb_facets) {
4359 index_t f = facets[fi];
4360 out << M.facets.nb_vertices(f) << " ";
4361 for(
4362 index_t c1=M.facets.corners_begin(f);
4363 c1<M.facets.corners_end(f); ++c1
4364 ) {
4365 index_t c2 = M.facets.next_corner_around_facet(f,c1);
4366 index_t iv1 =
4367 index_t(vertex_id[M.facet_corners.vertex(c1)]);
4368 index_t iv2 =
4369 index_t(vertex_id[M.facet_corners.vertex(c2)]);
4370 bindex K(iv1, iv2, bindex::KEEP_ORDER);
4371 index_t ie = NO_INDEX;
4372 auto it = edge_to_id.find(K);
4373 if(it == edge_to_id.end()) {
4374 ie = 2*edge_to_id[bindex(iv1,iv2)]+1;
4375 } else {
4376 ie = 2*it->second;
4377 }
4378 out << ie << " ";
4379 }
4380 out << std::endl;
4381 }
4382 }
4383
4384 // Construct cell table and output cells
4385
4386 index_t nb_cells = 0;
4387 FOR(f, M.facets.nb()) {
4388 nb_cells = std::max(nb_cells, index_t(cell_id[f]));
4389 }
4390 ++nb_cells;
4391
4392 // Ugly ! One could use compressed row storage instead.
4393 // ... but anyway we got all these tables indexed by bindexes
4394 // and trindexes that eat much memory, no need to optimize that
4395 // for now since tables storage probably dominate...
4396 vector< vector<index_t> >cell_to_f(nb_cells);
4397 FOR(f, M.facets.nb()) {
4398 cell_to_f[cell_id[f]].push_back(f);
4399 }
4400
4401 out << "Polyhedra" << std::endl;
4402 out << nb_cells << std::endl;
4403
4404 FOR(ci, nb_cells) {
4405 out << cell_to_f[ci].size() << " ";
4406 FOR(lf, cell_to_f[ci].size()) {
4407 index_t f = cell_to_f[ci][lf];
4408 trindex K = facet_key(M, f, vertex_id);
4409 out << facet_to_id[K] << " ";
4410 }
4411 out << std::endl;
4412 }
4413
4414 vertex_id.unbind();
4415 cell_id.unbind();
4416
4417 return true;
4418 }
4419
4420 private:
4421
4422 /**
4423 * \brief Gets one keyword from the next line.
4424 * \param[in] in a reference to the line input stream.
4425 * \details Throws an exception if the line has
4426 * not exactly one keyword.
4427 */
4428 std::string get_keyword(LineInput& in) {
4429 in.get_line();
4430 in.get_fields();
4431 if(in.nb_fields() == 0 && in.eof()) {
4432 return std::string("");
4433 }
4434 if(in.nb_fields() < 1) {
4435 throw("Expected one keyword");
4436 }
4437 return std::string(in.field(0));
4438 }
4439
4440 /**
4441 * \brief Gets one unsigned integer from the next
4442 * line.
4443 * \param[in] in a reference to the line input stream.
4444 * \details Throws an exception if the next line
4445 * has not exactly one unsigned integer.
4446 */
4447 index_t get_number(LineInput& in) {
4448 in.get_line();
4449 in.get_fields();
4450 if(in.nb_fields() != 1) {
4451 throw("Expected one number");
4452 }
4453 index_t result = in.field_as_uint(0);
4454 return result;
4455 }
4456
4457 /*
4458 * \brief Skips property.
4459 * \param[in] in a reference to the line input stream.
4460 * \param[in] nb_elements number of elements to be skipped
4461 */
4462 void skip_property(LineInput& in, index_t nb_elements) {
4463 // +1 because there is the type of the property
4464 FOR(i, nb_elements+1) {
4465 in.get_line();
4466 }
4467 }
4468
4469 /**
4470 * \brief Gets a key to be able to retrieve facet indices.
4471 * \param[in] M a reference to a mesh
4472 * \param[in] f a facet of the mesh
4473 * \param[in] vertex_id an Id attribute attached to the vertices
4474 * \param[in] invert if true, invert the order of the vertices
4475 * of the facet.
4476 * \return a trindex composed of the ids of three corners of the
4477 * facet, formed by the id of the vertex with the lowest id,
4478 * and the ids of its predecessor and successor around the facet.
4479 */
4480
4481 static trindex facet_key(
4482 const Mesh& M, index_t f, const Attribute<int>& vertex_id,
4483 bool invert=false
4484 ) {
4485 index_t min_iv = NO_INDEX;
4486 index_t min_corner = NO_INDEX;
4487 for(
4488 index_t c=M.facets.corners_begin(f);
4489 c<M.facets.corners_end(f); ++c
4490 ) {
4491 index_t iv = index_t(vertex_id[M.facet_corners.vertex(c)]);
4492 if(min_iv == NO_INDEX || iv < min_iv) {
4493 min_corner = c;
4494 min_iv = iv;
4495 }
4496 }
4497 index_t c1 = min_corner;
4498 index_t c2 = invert ?
4499 M.facets.prev_corner_around_facet(f,c1) :
4500 M.facets.next_corner_around_facet(f,c1) ;
4501 index_t c3 = invert ?
4502 M.facets.prev_corner_around_facet(f,c2) :
4503 M.facets.next_corner_around_facet(f,c2) ;
4504 index_t iv1 = index_t(vertex_id[M.facet_corners.vertex(c1)]);
4505 index_t iv2 = index_t(vertex_id[M.facet_corners.vertex(c2)]);
4506 index_t iv3 = index_t(vertex_id[M.facet_corners.vertex(c3)]);
4507 return trindex(iv1,iv2,iv3,trindex::KEEP_ORDER);
4508 }
4509
4510 };
4511
4512 /****************************************************************************/
4513
4514 const index_t id_offset_msh = 1;
4515 const index_t msh2geo_hex[8] = {1, 3, 7, 5, 0, 2, 6, 4 };
4516 const index_t msh2geo_def[8] = {0, 1, 2, 3, 4, 5, 6, 7 };
4517 const index_t celltype_geo2msh[5] = {4, 5, 6, 7};
4518
4519 /**
4520 * \brief Support for GMSH file format.
4521 * \details By Maxence Reberol.
4522 */
4523 class GEOGRAM_API MSHIOHandler : public MeshIOHandler {
4524 public:
4525 MSHIOHandler() {
4526 }
4527
4528 bool verify_file_format(const std::string& filename) {
4529 LineInput in(filename);
4530 if (!in.OK()) return false;
4531 in.get_line();
4532 in.get_fields();
4533 if (in.field_matches(0, "$MeshFormat")) {
4534 in.get_line();
4535 in.get_fields();
4536 if (in.field_as_double(0) == 2.2
4537 && in.field_as_uint(1) == 0
4538 && in.field_as_uint(2) == 8
4539 ) return true;
4540 }
4541 return false;
4542 }
4543
4544 bool read_vertices(const std::string& filename, Mesh& M) {
4545 LineInput in(filename);
4546 while (in.get_line()) {
4547 in.get_fields();
4548 if (in.field_matches(0, "$Nodes")) {
4549 in.get_line();
4550 in.get_fields();
4551 geo_assert(in.nb_fields() == 1);
4552 M.vertices.create_vertices(in.field_as_uint(0));
4553 for (index_t v = 0; v < M.vertices.nb(); ++v){
4554 in.get_line();
4555 in.get_fields();
4556 geo_assert(in.nb_fields() == 4);
4557 double pt[4] = {
4558 in.field_as_double(1),
4559 in.field_as_double(2),
4560 in.field_as_double(3)
4561 };
4562 set_mesh_point(M, v, pt, 3);
4563 }
4564 } else if (in.field_matches(0, "$EndNodes")) {
4565 return true;
4566 }
4567 }
4568 return false;
4569 }
4570 bool read_elements(const std::string& filename, Mesh& M) {
4571 index_t nb_elements = 0;
4572 index_t nb_edges = 0;
4573 index_t nb_tri = 0;
4574 index_t nb_quad = 0;
4575 index_t nb_tet = 0;
4576 index_t nb_hex = 0;
4577 index_t nb_pyr = 0;
4578 index_t nb_pri = 0;
4579 index_t nb_oth = 0;
4580 { /* First pass to get the number of each element type */
4581 LineInput in(filename);
4582 while (in.get_line()) {
4583 in.get_fields();
4584 if (in.field_matches(0, "$Elements")) {
4585 in.get_line();
4586 in.get_fields();
4587 geo_assert(in.nb_fields() == 1);
4588 nb_elements = in.field_as_uint(0);
4589 for (index_t e = 0; e < nb_elements; ++e){
4590 in.get_line();
4591 in.get_fields();
4592 if (in.field_as_uint(1) == 1) nb_edges += 1;
4593 else if (in.field_as_uint(1) == 2) nb_tri += 1;
4594 else if (in.field_as_uint(1) == 3) nb_quad += 1;
4595 else if (in.field_as_uint(1) == 4) nb_tet += 1;
4596 else if (in.field_as_uint(1) == 5) nb_hex += 1;
4597 else if (in.field_as_uint(1) == 6) nb_pri += 1;
4598 else if (in.field_as_uint(1) == 7) nb_pyr += 1;
4599 else { nb_oth += 1; }
4600 }
4601 if (nb_oth > 0) {
4602 Logger::warn("I/O")
4603 << nb_oth
4604 << " elements with type unsupported"
4605 << std::endl;
4606 }
4607 }
4608 }
4609 M.edges.create_edges(nb_edges);
4610 M.facets.create_triangles(nb_tri);
4611 M.facets.create_quads(nb_quad);
4612 M.cells.create_tets(nb_tet);
4613 M.cells.create_hexes(nb_hex);
4614 M.cells.create_pyramids(nb_pyr);
4615 M.cells.create_prisms(nb_pri);
4616 }
4617 { /* Second pass to fill the content of the mesh */
4618 /* Re-use the number of elts as counters */
4619 nb_edges = 0;
4620 index_t nb_facets = 0;
4621 index_t nb_cells = 0;
4622 LineInput in(filename);
4623 while (in.get_line()) {
4624 in.get_fields();
4625 if (in.field_matches(0, "$Elements")) {
4626 in.get_line();
4627 in.get_fields();
4628 geo_assert(in.field_as_uint(0) == nb_elements);
4629 for (index_t e = 0; e < nb_elements; ++e){
4630 in.get_line();
4631 in.get_fields();
4632 index_t nb_tags = in.field_as_uint(2);
4633 index_t offset = 3 + nb_tags;
4634 if (in.field_as_uint(1) == 1) {
4635 index_t nbv = 2;
4636 geo_debug_assert(
4637 in.nb_fields() == offset + nbv
4638 );
4639 for (index_t j = 0; j < nbv; ++j) {
4640 M.edges.set_vertex(
4641 nb_edges, j,
4642 in.field_as_uint(offset + j) - 1
4643 );
4644 }
4645 nb_edges += 1;
4646 } else if (in.field_as_uint(1) == 2) {
4647 index_t nbv = 3;
4648 geo_debug_assert(
4649 in.nb_fields() == offset + nbv
4650 );
4651 for (index_t j = 0; j < nbv; ++j) {
4652 M.facets.set_vertex(
4653 nb_facets, j,
4654 in.field_as_uint(offset + j) - 1
4655 );
4656 }
4657 nb_facets += 1;
4658 } else if (in.field_as_uint(1) == 3) {
4659 index_t nbv = 4;
4660 geo_debug_assert(
4661 in.nb_fields() == offset + nbv
4662 );
4663 for (index_t j = 0; j < nbv; ++j) {
4664 M.facets.set_vertex(
4665 nb_facets, j,
4666 in.field_as_uint(offset + j) - 1
4667 );
4668 }
4669 nb_facets += 1;
4670 } else if (in.field_as_uint(1) == 4) {
4671 index_t nbv = 4;
4672 geo_debug_assert(
4673 in.nb_fields() == offset + nbv
4674 );
4675 for (index_t j = 0; j < nbv; ++j) {
4676 M.cells.set_vertex(
4677 nb_cells, j,
4678 in.field_as_uint(offset + j) - 1
4679 );
4680 }
4681 nb_cells += 1;
4682 } else if (in.field_as_uint(1) == 5) {
4683 index_t nbv = 8;
4684 geo_debug_assert(
4685 in.nb_fields() == offset + nbv
4686 );
4687 for (index_t j = 0; j < nbv; ++j) {
4688 M.cells.set_vertex(
4689 nb_cells, msh2geo_hex[j],
4690 in.field_as_uint(offset + j) - 1
4691 );
4692 }
4693 nb_cells += 1;
4694 } else if (in.field_as_uint(1) == 6) {
4695 index_t nbv = 6;
4696 geo_debug_assert(
4697 in.nb_fields() == offset + nbv
4698 );
4699 for (index_t j = 0; j < nbv; ++j) {
4700 M.cells.set_vertex(
4701 nb_cells, j,
4702 in.field_as_uint(offset + j) - 1
4703 );
4704 }
4705 nb_cells += 1;
4706 } else if (in.field_as_uint(1) == 7) {
4707 index_t nbv = 5;
4708 geo_debug_assert(
4709 in.nb_fields() == offset + nbv
4710 );
4711 for (index_t j = 0; j < nbv; ++j) {
4712 M.cells.set_vertex(
4713 nb_cells, j,
4714 in.field_as_uint(offset + j) - 1
4715 );
4716 }
4717 nb_cells += 1;
4718 }
4719 }
4720 }
4721 }
4722 }
4723 return true;
4724 }
4725
4726 bool load(
4727 const std::string& filename, Mesh& M,
4728 const MeshIOFlags& ioflags
4729 ) override {
4730 geo_argused(ioflags);
4731 M.clear();
4732 M.vertices.set_dimension(3);
4733 try {
4734 if (!verify_file_format(filename)) {
4735 Logger::err("I/O")
4736 << "$MeshFormat not supported" << std::endl;
4737 return false;
4738 }
4739 if (!read_vertices(filename, M)) {
4740 Logger::err("I/O")
4741 << "failed to read vertices" << std::endl;
4742 return false;
4743 }
4744 if (!read_elements(filename, M)) {
4745 Logger::err("I/O")
4746 << "failed to read elements" << std::endl;
4747 return false;
4748 }
4749 } catch(const std::string& what) {
4750 Logger::err("I/O") << what << std::endl;
4751 return false;
4752 } catch(const std::exception& ex) {
4753 Logger::err("I/O") << ex.what() << std::endl;
4754 return false;
4755 } catch(...) {
4756 Logger::err("I/O") << "Caught exception" << std::endl;
4757 return false;
4758 }
4759 return true;
4760 }
4761
4762 bool save(
4763 const Mesh& M_in, const std::string& filename,
4764 const MeshIOFlags& ioflags
4765 ) override {
4766
4767 Mesh M(M_in.vertices.dimension());
4768 M.copy(M_in, true);
4769
4770 M.vertices.remove_isolated();
4771
4772 Attribute<index_t> region;
4773 Attribute<index_t> bdr_region;
4774 if (M.cells.attributes().is_defined("region")) {
4775 region.bind(M.cells.attributes(), "region");
4776 }
4777 if (M.facets.attributes().is_defined("bdr_region")) {
4778 bdr_region.bind(M.facets.attributes(), "bdr_region");
4779 }
4780
4781 std::ofstream out( filename.c_str() ) ;
4782
4783 if( !out ) {
4784 Logger::err("I/O") << "Fail to open \"" << filename << "\" for writing" << std::endl;
4785 return false;
4786 }
4787
4788 out.precision( 16 ) ;
4789
4790 /* Header */
4791 out << "$MeshFormat\n";
4792 out << "2.2 0 " << sizeof(double) << std::endl;
4793 out << "$EndMeshFormat\n";
4794
4795 /* Vertices */
4796 out << "$Nodes" << std::endl ;
4797 out << M.vertices.nb() << std::endl ;
4798 for( index_t v = 0; v < M.vertices.nb(); v++ ) {
4799 out << v + id_offset_msh << " "
4800 << M.vertices.point_ptr(v)[0] << " "
4801 << M.vertices.point_ptr(v)[1] << " "
4802 << M.vertices.point_ptr(v)[2] << '\n' ;
4803 }
4804 out << "$EndNodes" << std::endl ;
4805
4806 /* Elements */
4807 index_t nb_tet = 0;
4808 index_t nb_hex = 0;
4809 index_t nb_pyr = 0;
4810 index_t nb_pri = 0;
4811 for(index_t c = 0; c != M.cells.nb(); ++c){
4812 if(M.cells.type(c) == GEO::MESH_TET){
4813 ++nb_tet;
4814 } else if(M.cells.type(c) == GEO::MESH_HEX){
4815 ++nb_hex;
4816 } else if(M.cells.type(c) == GEO::MESH_PYRAMID){
4817 ++nb_pyr;
4818 } else if(M.cells.type(c) == GEO::MESH_PRISM){
4819 ++nb_pri;
4820 }
4821 }
4822 index_t nb_elt = nb_tet + nb_hex + nb_pyr + nb_pri;
4823 if (ioflags.has_element(MESH_FACETS)) nb_elt += M.facets.nb();
4824
4825 out << "$Elements" << std::endl ;
4826 out << nb_elt << std::endl ;
4827 index_t elt_id = 0; /* starts at 1, common for faces and cells */
4828 if (ioflags.has_element(MESH_FACETS)) {
4829 for (index_t f = 0; f < M.facets.nb(); ++f) {
4830 int attr_value = 0;
4831 if (bdr_region.is_bound()){
4832 attr_value = int(bdr_region[f]);
4833 }
4834 int type = -1;
4835 if (M.facets.nb_vertices(f) == 3) {
4836 type = 2;
4837 } else if (M.facets.nb_vertices(f) == 4) {
4838 type = 3;
4839 } else {
4840 geo_assert_not_reached
4841 }
4842 elt_id += 1;
4843 out << elt_id << " " << type << " " << "2" << " "
4844 << attr_value << " " << attr_value << " ";
4845 for (index_t li = 0; li < M.facets.nb_vertices(f); ++li) {
4846 out << M.facets.vertex(f, li) + id_offset_msh << " ";
4847 }
4848 out << std::endl;
4849 }
4850 }
4851 for (index_t c = 0; c < M.cells.nb(); ++c) {
4852 if (M.cells.type(c) == GEO::MESH_CONNECTOR) {
4853 continue;
4854 }
4855 int attr_value = 0;
4856 if (region.is_bound()) {
4857 attr_value = int(region[c]);
4858 }
4859 const index_t* msh2geo =
4860 (M.cells.type(c) == GEO::MESH_HEX) ?
4861 msh2geo_hex : msh2geo_def;
4862 elt_id += 1;
4863
4864 /* Write to file, format is:
4865 * elm-number elm-type number-of-tags < tag > ...
4866 * node-number-list
4867 */
4868 out << elt_id << " " << celltype_geo2msh[M.cells.type(c)]
4869 << " " << "1" << " " << attr_value << " ";
4870 for (index_t li = 0; li < M.cells.nb_vertices(c); ++li) {
4871 out << M.cells.vertex(c, msh2geo[li]) + id_offset_msh
4872 << " ";
4873 }
4874 out << std::endl;
4875 }
4876 out << "$EndElements" << std::endl;
4877
4878 out.close();
4879 return true;
4880 }
4881 };
4882
4883 }
4884
4885 /****************************************************************************/
4886
4887 namespace GEO {
4888
4889 421 MeshIOFlags::MeshIOFlags() {
4890 421 dimension_ = 3;
4891 421 attributes_ = MESH_NO_ATTRIBUTES;
4892 421 elements_ = MESH_ALL_ELEMENTS;
4893 421 verbose_ = true;
4894 421 }
4895
4896 /************************************************************************/
4897
4898
1/2
✓ Branch 0 taken 271 times.
✗ Branch 1 not taken.
271 bool GEOGRAM_API mesh_load(
4899 const std::string& filename, Mesh& M,
4900 const MeshIOFlags& ioflags
4901 ) {
4902
1/2
✓ Branch 0 taken 271 times.
✗ Branch 1 not taken.
271 if(ioflags.verbose()) {
4903
1/2
✓ Branch 2 taken 271 times.
✗ Branch 3 not taken.
542 Logger::out("I/O")
4904 << "Loading file " << filename << "..."
4905 << std::endl;
4906 }
4907
4908 271 M.clear();
4909
4910 bool result = false;
4911 271 MeshIOHandler_var handler = MeshIOHandler::get_handler(filename);
4912
2/2
✓ Branch 0 taken 270 times.
✓ Branch 1 taken 1 times.
271 if(handler != nullptr) {
4913 try {
4914
3/4
✓ Branch 1 taken 270 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 265 times.
✓ Branch 5 taken 5 times.
270 result = handler->load(filename, M, ioflags);
4915 }
4916
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 catch(const std::exception& ex) {
4917
3/6
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 5 times.
✗ Branch 5 not taken.
✓ Branch 8 taken 5 times.
✗ Branch 9 not taken.
5 Logger::err("I/O") << ex.what() << std::endl;
4918 result = false;
4919 5 }
4920 }
4921
4922
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 252 times.
265 if(!result) {
4923
2/4
✓ Branch 1 taken 19 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 19 times.
✗ Branch 5 not taken.
19 Logger::err("I/O")
4924 << "Could not load file: " << filename
4925 << std::endl;
4926 19 return false;
4927 }
4928
4929
1/2
✓ Branch 0 taken 252 times.
✗ Branch 1 not taken.
252 if(!M.vertices.single_precision() && M.vertices.nb() > 0) {
4930 252 index_t nb = M.vertices.nb() * M.vertices.dimension();
4931 double* p = M.vertices.point_ptr(0);
4932 bool has_nan = false;
4933
2/2
✓ Branch 0 taken 629211037 times.
✓ Branch 1 taken 252 times.
629211289 for(index_t i = 0; i < nb; i++) {
4934
2/4
✓ Branch 1 taken 629211037 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 629211037 times.
629211037 if(Numeric::is_nan(*p)) {
4935 has_nan = true;
4936 *p = 0.0;
4937 }
4938 629211037 p++;
4939 }
4940
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 252 times.
252 if(has_nan) {
4941 Logger::warn("I/O") << "Found NaNs in input file" << std::endl;
4942 }
4943 }
4944
4945 252 if(
4946
7/8
✓ Branch 1 taken 252 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 250 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 249 times.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 249 times.
✓ Branch 8 taken 3 times.
754 FileSystem::extension(filename) != "geogram" &&
4947
2/6
✓ Branch 1 taken 250 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 252 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
502 FileSystem::extension(filename) != "geogram_ascii"
4948 ) {
4949
1/2
✓ Branch 1 taken 249 times.
✗ Branch 2 not taken.
249 M.facets.connect();
4950
1/2
✓ Branch 1 taken 249 times.
✗ Branch 2 not taken.
249 M.cells.connect();
4951
4/4
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 244 times.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 1 times.
249 if(M.cells.nb() != 0 && M.facets.nb() == 0) {
4952
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 M.cells.compute_borders();
4953 }
4954 }
4955
4956
1/2
✓ Branch 0 taken 252 times.
✗ Branch 1 not taken.
252 if(ioflags.verbose()) {
4957
2/6
✓ Branch 1 taken 252 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 252 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
504 M.show_stats("I/O");
4958 }
4959
4960 return true;
4961 }
4962
4963
1/2
✓ Branch 0 taken 151 times.
✗ Branch 1 not taken.
151 bool GEOGRAM_API mesh_save(
4964 const Mesh& M, const std::string& filename,
4965 const MeshIOFlags& ioflags
4966 ) {
4967
1/2
✓ Branch 0 taken 151 times.
✗ Branch 1 not taken.
151 if(ioflags.verbose()) {
4968
1/2
✓ Branch 2 taken 151 times.
✗ Branch 3 not taken.
302 Logger::out("I/O")
4969 << "Saving file " << filename << "..."
4970 << std::endl;
4971 }
4972
4973
1/2
✓ Branch 1 taken 151 times.
✗ Branch 2 not taken.
151 if( !FileSystem::can_write_directory(
4974
2/4
✓ Branch 2 taken 151 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 151 times.
302 FileSystem::dir_name(FileSystem::absolute_path(filename)), true)
4975 ) {
4976 Logger::err("I/O") << "Failed to open \""
4977 << filename << "\" for writing" << std::endl;
4978 return false;
4979 }
4980
4981 151 MeshIOHandler_var handler = MeshIOHandler::get_handler(filename);
4982
4/8
✓ Branch 0 taken 151 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 151 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 151 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 151 times.
151 if(handler != nullptr && handler->save(M, filename, ioflags)) {
4983 return true;
4984 }
4985
4986 Logger::err("I/O")
4987 << "Could not save file: " << filename
4988 << std::endl;
4989 return false;
4990 }
4991
4992 /************************************************************************/
4993
4994
4995 422 MeshIOHandler* MeshIOHandler::create(const std::string& format) {
4996 MeshIOHandler* handler = MeshIOHandlerFactory::create_object(
4997 format
4998 );
4999
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 421 times.
421 if(handler != nullptr) {
5000 return handler;
5001 }
5002
5003
1/2
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 Logger::err("I/O")
5004 << "Unsupported file format: " << format
5005 << std::endl;
5006 1 return nullptr;
5007 }
5008
5009 422 MeshIOHandler* MeshIOHandler::get_handler(
5010 const std::string& filename
5011 ) {
5012 422 std::string ext = FileSystem::extension(filename);
5013
1/2
✓ Branch 1 taken 422 times.
✗ Branch 2 not taken.
844 return create(ext);
5014 }
5015
5016
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 421 times.
842 MeshIOHandler::~MeshIOHandler() {
5017 842 }
5018
5019
5020 347 void MeshIOHandler::bind_attributes(
5021 const Mesh& M_in, const MeshIOFlags& flags, bool create
5022 ) {
5023 Mesh& M = const_cast<Mesh&>(M_in); // UGLY I know !!
5024
2/2
✓ Branch 0 taken 233 times.
✓ Branch 1 taken 114 times.
347 if(create) {
5025
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 220 times.
233 if(flags.has_attribute(MESH_VERTEX_REGION)) {
5026
1/2
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
26 vertex_region_.bind(M.vertices.attributes(),"region");
5027 }
5028
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 220 times.
233 if(flags.has_attribute(MESH_EDGE_REGION)) {
5029
1/2
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
26 edge_region_.bind(M.edges.attributes(),"region");
5030 }
5031
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 220 times.
233 if(flags.has_attribute(MESH_FACET_REGION)) {
5032
1/2
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
26 facet_region_.bind(M.facets.attributes(),"region");
5033 }
5034
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 220 times.
233 if(flags.has_attribute(MESH_CELL_REGION)) {
5035
1/2
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
26 cell_region_.bind(M.cells.attributes(),"region");
5036 }
5037 } else {
5038
2/2
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 97 times.
114 if(flags.has_attribute(MESH_VERTEX_REGION)) {
5039
1/2
✓ Branch 1 taken 17 times.
✗ Branch 2 not taken.
17 vertex_region_.bind_if_is_defined(
5040 34 M.vertices.attributes(),"region"
5041 );
5042 }
5043
2/2
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 97 times.
114 if(flags.has_attribute(MESH_EDGE_REGION)) {
5044
1/2
✓ Branch 1 taken 17 times.
✗ Branch 2 not taken.
17 edge_region_.bind_if_is_defined(
5045 34 M.edges.attributes(),"region"
5046 );
5047 }
5048
2/2
✓ Branch 0 taken 53 times.
✓ Branch 1 taken 61 times.
114 if(flags.has_attribute(MESH_FACET_REGION)) {
5049
1/2
✓ Branch 1 taken 53 times.
✗ Branch 2 not taken.
53 facet_region_.bind_if_is_defined(
5050 106 M.facets.attributes(),"region"
5051 );
5052 }
5053
2/2
✓ Branch 0 taken 53 times.
✓ Branch 1 taken 61 times.
114 if(flags.has_attribute(MESH_CELL_REGION)) {
5054
1/2
✓ Branch 1 taken 53 times.
✗ Branch 2 not taken.
53 cell_region_.bind_if_is_defined(
5055 106 M.cells.attributes(),"region"
5056 );
5057 }
5058 }
5059 347 }
5060
5061
2/2
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 319 times.
345 void MeshIOHandler::unbind_attributes() {
5062 if(vertex_region_.is_bound()) {
5063 26 vertex_region_.unbind();
5064 }
5065 if(edge_region_.is_bound()) {
5066 26 edge_region_.unbind();
5067 }
5068 if(facet_region_.is_bound()) {
5069 62 facet_region_.unbind();
5070 }
5071 if(cell_region_.is_bound()) {
5072 26 cell_region_.unbind();
5073 }
5074 345 }
5075
5076
5077 251 void mesh_io_initialize() {
5078
3/6
✓ Branch 0 taken 251 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 251 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 251 times.
✗ Branch 7 not taken.
753 geo_register_MeshIOHandler_creator(LMIOHandler, "mesh");
5079
3/6
✓ Branch 0 taken 251 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 251 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 251 times.
✗ Branch 7 not taken.
753 geo_register_MeshIOHandler_creator(LMIOHandler, "meshb");
5080
3/6
✓ Branch 0 taken 251 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 251 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 251 times.
✗ Branch 7 not taken.
753 geo_register_MeshIOHandler_creator(OBJIOHandler, "obj");
5081
3/6
✓ Branch 0 taken 251 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 251 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 251 times.
✗ Branch 7 not taken.
753 geo_register_MeshIOHandler_creator(OBJIOHandler, "eobj");
5082
3/6
✓ Branch 0 taken 251 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 251 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 251 times.
✗ Branch 7 not taken.
753 geo_register_MeshIOHandler_creator(OBJ6IOHandler, "obj6");
5083
3/6
✓ Branch 0 taken 251 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 251 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 251 times.
✗ Branch 7 not taken.
753 geo_register_MeshIOHandler_creator(PLYIOHandler, "ply");
5084
3/6
✓ Branch 0 taken 251 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 251 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 251 times.
✗ Branch 7 not taken.
753 geo_register_MeshIOHandler_creator(OFFIOHandler, "off");
5085
3/6
✓ Branch 0 taken 251 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 251 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 251 times.
✗ Branch 7 not taken.
753 geo_register_MeshIOHandler_creator(STLIOHandler, "stl");
5086
3/6
✓ Branch 0 taken 251 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 251 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 251 times.
✗ Branch 7 not taken.
753 geo_register_MeshIOHandler_creator(XYZIOHandler, "xyz");
5087
3/6
✓ Branch 0 taken 251 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 251 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 251 times.
✗ Branch 7 not taken.
753 geo_register_MeshIOHandler_creator(PTSIOHandler, "pts");
5088
3/6
✓ Branch 0 taken 251 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 251 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 251 times.
✗ Branch 7 not taken.
753 geo_register_MeshIOHandler_creator(TETIOHandler, "tet");
5089
3/6
✓ Branch 0 taken 251 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 251 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 251 times.
✗ Branch 7 not taken.
753 geo_register_MeshIOHandler_creator(TET6IOHandler, "tet6");
5090
3/6
✓ Branch 0 taken 251 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 251 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 251 times.
✗ Branch 7 not taken.
753 geo_register_MeshIOHandler_creator(TET8IOHandler, "tet8");
5091
3/6
✓ Branch 0 taken 251 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 251 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 251 times.
✗ Branch 7 not taken.
753 geo_register_MeshIOHandler_creator(GeogramIOHandler, "geogram");
5092
3/6
✓ Branch 0 taken 251 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 251 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 251 times.
✗ Branch 7 not taken.
753 geo_register_MeshIOHandler_creator(GeogramIOHandler, "geogram_ascii");
5093
3/6
✓ Branch 0 taken 251 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 251 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 251 times.
✗ Branch 7 not taken.
753 geo_register_MeshIOHandler_creator(GraphiteIOHandler, "graphite");
5094
3/6
✓ Branch 0 taken 251 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 251 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 251 times.
✗ Branch 7 not taken.
753 geo_register_MeshIOHandler_creator(PDBIOHandler, "pdb");
5095
3/6
✓ Branch 0 taken 251 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 251 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 251 times.
✗ Branch 7 not taken.
753 geo_register_MeshIOHandler_creator(PDBIOHandler, "pdb1");
5096
3/6
✓ Branch 0 taken 251 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 251 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 251 times.
✗ Branch 7 not taken.
753 geo_register_MeshIOHandler_creator(OVMIOHandler, "ovm");
5097
3/6
✓ Branch 0 taken 251 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 251 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 251 times.
✗ Branch 7 not taken.
753 geo_register_MeshIOHandler_creator(MSHIOHandler, "msh");
5098 251 }
5099
5100
5101 bool GEOGRAM_API mesh_load(
5102 InputGeoFile& geofile, Mesh& M,
5103 const MeshIOFlags& ioflags
5104 ) {
5105 GeogramIOHandler geogram;
5106 return geogram.load(geofile, M, ioflags);
5107 }
5108
5109 bool GEOGRAM_API mesh_save(
5110 const Mesh& M, OutputGeoFile& geofile,
5111 const MeshIOFlags& ioflags
5112 ) {
5113 GeogramIOHandler geogram;
5114 return geogram.save(M, geofile, ioflags);
5115 }
5116
5117 bool Mesh::load(const std::string& filename) {
5118 return mesh_load(filename, *this);
5119 }
5120
5121 bool Mesh::save(const std::string& filename) const {
5122 return mesh_save(*this, filename);
5123 }
5124
5125 }
5126