OGS
GocadAsciiReader.cpp
Go to the documentation of this file.
1
10#include "GocadAsciiReader.h"
11
12#include <fstream>
13
15#include "BaseLib/FileTools.h"
16#include "BaseLib/Logging.h"
17#include "BaseLib/StringTools.h"
20#include "MeshLib/Mesh.h"
21#include "MeshLib/Node.h"
22#include "MeshLib/Properties.h"
23
24namespace FileIO
25{
26namespace Gocad
27{
28namespace GocadAsciiReader
29{
30enum class NodeType
31{
33 VRTX,
34 PVRTX
35};
36
37const std::string mat_id_name = "MaterialIDs";
38const std::string eof_error = "Error: Unexpected end of file.";
39
43void checkMeshNames(std::vector<std::unique_ptr<MeshLib::Mesh>> const& meshes)
44{
45 std::size_t const n_meshes = meshes.size();
46 for (std::size_t i = 0; i < n_meshes; ++i)
47 {
48 std::string const& name = meshes[i]->getName();
49 for (std::size_t j = i + 1; j < n_meshes; ++j)
50 {
51 if (meshes[j]->getName() == name)
52 {
53 std::string const id_str = std::to_string(meshes[j]->getID());
54 meshes[i]->setName(name + "--importID-" + id_str);
55 break;
56 }
57 }
58 }
59}
60
62bool isCommentLine(std::string const& str)
63{
64 return (str.substr(0, 1) == "#");
65}
66
68bool skipToEND(std::ifstream& in)
69{
70 std::string line;
71 while (std::getline(in, line))
72 {
73 if (line == "END")
74 {
75 return true;
76 }
77 }
78 ERR("{:s}", eof_error);
79 return false;
80}
81
83bool isKeyword(DataType const t, std::string const& line)
84{
85 std::size_t str_length = dataType2String(t).length();
86 return (line.substr(0, str_length) == dataType2String(t));
87}
88
90DataType datasetFound(std::ifstream& in)
91{
92 std::string line;
93 while (std::getline(in, line))
94 {
95 if (line.empty() || isCommentLine(line))
96 {
97 continue;
98 }
99
100 if (isKeyword(DataType::VSET, line))
101 {
102 return DataType::VSET;
103 }
104 if (isKeyword(DataType::PLINE, line))
105 {
106 return DataType::PLINE;
107 }
108 if (isKeyword(DataType::TSURF, line))
109 {
110 return DataType::TSURF;
111 }
112 if (isKeyword(DataType::MODEL3D, line))
113 {
114 return DataType::MODEL3D;
115 }
116 ERR("No known identifier found...");
117 return DataType::UNDEFINED;
118 }
119 return DataType::UNDEFINED;
120}
122void checkLineEndings(std::string const& file_name)
123{
124#ifndef _WIN32
125 std::ifstream in(file_name);
126 if (in.is_open())
127 {
128 std::string line;
129 std::getline(in, line);
130 if (line.back() == '\r')
131 {
132 OGS_FATAL(
133 "Error in input file: {:s}. The line endings are in windows "
134 "format. To read this file under UNIX, transform the input "
135 "file to unix style line endings (e.g. dos2unix).",
136 file_name);
137 }
138 }
139#endif
140}
141
143bool parseHeader(std::ifstream& in, std::string& mesh_name)
144{
145 std::string line;
146 while (std::getline(in, line))
147 {
148 if (line.substr(0, 5) == "name:")
149 {
150 mesh_name = line.substr(5, line.length() - 5);
151 BaseLib::trim(mesh_name, ' ');
152 // replace chars that will prevent writing the file
153 std::replace(mesh_name.begin(), mesh_name.end(), '/', '-');
154 std::replace(mesh_name.begin(), mesh_name.end(), '\\', '-');
155 }
156 else if (line.substr(0, 1) == "}")
157 {
158 return true;
159 }
160 // ignore all other header parameters
161 }
162 ERR("{:s}", eof_error);
163 return false;
164}
165
168bool parsePropertyClass(std::ifstream& in)
169{
170 std::string line;
171 while (std::getline(in, line))
172 {
173 if (line.substr(0, 1) == "}")
174 {
175 return true;
176 }
177 }
178 ERR("{:s}", eof_error);
179 return false;
180}
181
183std::string propertyCheck(std::string const& string)
184{
185 std::array<std::string, 7> const property_keywords = {
186 {"PROPERTY_CLASSES", "PROP_LEGAL_RANGES", "NO_DATA_VALUES",
187 "PROPERTY_KINDS", "PROPERTY_SUBCLASSES", "UNITS", "ESIZES"}};
188
189 std::string const str = BaseLib::splitString(string)[0];
190 auto res =
191 std::find(property_keywords.begin(), property_keywords.end(), str);
192 if (res != property_keywords.end())
193 {
194 return *res;
195 }
196 return std::string("");
197}
198
201bool parseProperties(std::ifstream& in,
202 std::vector<std::string> const& names,
203 MeshLib::Properties& mesh_prop)
204{
205 // Because properties have no end-tag, the position of the last line is
206 // stored, so the stream can be set back if none of the allowed property-
207 // related keywords is found.
208 std::streampos pos = in.tellg();
209 std::string line;
210 while (std::getline(in, line))
211 {
212 std::string const key = propertyCheck(line);
213 // This is the intended way to exit this method:
214 // No property-related keyword has been found, so the stream is set
215 // back one line and the (unrelated) keyword can be read again in the
216 // parent method.
217 if (key.empty())
218 {
219 in.seekg(pos);
220 return true;
221 }
222
223 // Currently all property parameters except array name and size are
224 // ignored.
225 if (key == "ESIZES")
226 {
227 std::vector<std::string> prop_size = BaseLib::splitString(line);
228
229 if (names.size() != prop_size.size())
230 {
231 ERR("Error: Number of PROPERTY-names ({:d}) does not match "
232 "number of ESIZES ({:d})",
233 names.size(), prop_size.size());
234 return false;
235 }
236 std::size_t const n_names(names.size());
237 for (std::size_t i = 1; i < n_names; ++i)
238 {
239 mesh_prop.createNewPropertyVector<double>(
240 names[i],
243 }
244 }
245 // Remember current position in case the properties black ends now.
246 pos = in.tellg();
247 }
248 ERR("{:s}", eof_error);
249 return false;
250}
251
252MeshLib::Node* createNode(std::stringstream& sstr)
253{
254 std::string keyword;
255 std::size_t id;
256 std::array<double, 3> data{};
257 sstr >> keyword >> id >> data[0] >> data[1] >> data[2];
258 return new MeshLib::Node(data, id);
259}
260
263bool parseAtomRegionIndicators(std::ifstream& in)
264{
265 std::string line;
266 while (std::getline(in, line))
267 {
268 if (line.substr(0, 26) == "END_ATOM_REGION_INDICATORS")
269 {
270 return true;
271 }
272 }
273 return false;
274}
275
277bool parseNodes(std::ifstream& in,
278 std::vector<MeshLib::Node*>& nodes,
279 std::map<std::size_t, std::size_t>& node_id_map,
280 MeshLib::Properties const& mesh_prop)
281{
283 std::streampos pos = in.tellg();
284 std::string line;
285 while (std::getline(in, line))
286 {
287 std::vector<std::string> str = BaseLib::splitString(line);
288 if (line.substr(0, 3) == "SEG" || line.substr(0, 4) == "TRGL")
289 {
290 in.seekg(pos);
291 return true;
292 }
293
294 if (line.substr(0, 28) == "BEGIN_ATOM_REGION_INDICATORS")
295 {
297 {
298 ERR("File ended while parsing Atom Region Indicators...");
299 return false;
300 }
301 return true;
302 }
303
304 if (line.empty() || isCommentLine(line))
305 {
306 continue;
307 }
308 if (!(line.substr(0, 4) == "VRTX" || line.substr(0, 5) == "PVRTX" ||
309 line.substr(0, 4) == "ATOM"))
310 {
311 WARN("GocadAsciiReader::parseNodes() - Unknown keyword found: {:s}",
312 line);
313 continue;
314 }
315
316 std::stringstream sstr(line);
317 if (line.substr(0, 4) == "VRTX" && t != NodeType::PVRTX)
318 {
319 t = NodeType::VRTX;
320 nodes.push_back(createNode(sstr));
321 }
322 else if (line.substr(0, 5) == "PVRTX" && t != NodeType::VRTX)
323 {
324 t = NodeType::PVRTX;
325 nodes.push_back(createNode(sstr));
326 for (auto [name, property] : mesh_prop)
327 {
328 if (name == mat_id_name)
329 {
330 continue;
331 }
332 if (auto p = dynamic_cast<MeshLib::PropertyVector<double>*>(
333 property))
334 {
335 double value;
336 sstr >> value;
337 p->push_back(value);
338 }
339 }
340 }
341 else if (line.substr(0, 4) == "ATOM")
342 {
343 std::size_t new_id;
344 std::size_t ref_id;
345 std::string keyword;
346 sstr >> keyword >> new_id >> ref_id;
347 nodes.push_back(new MeshLib::Node(nodes[ref_id]->data(), new_id));
348 }
349 node_id_map[nodes.back()->getID()] = nodes.size() - 1;
350 pos = in.tellg();
351 }
352 ERR("{:s}", eof_error);
353 return false;
354}
355
357bool parseLineSegments(std::ifstream& in,
358 std::vector<MeshLib::Node*> const& nodes,
359 std::vector<MeshLib::Element*>& elems,
360 std::map<std::size_t, std::size_t> const& node_id_map,
361 MeshLib::Properties& mesh_prop)
362{
364 *mesh_prop.getPropertyVector<int>(mat_id_name);
365 int current_mat_id(0);
366 if (!mat_ids.empty())
367 {
368 current_mat_id = (*std::max_element(mat_ids.begin(), mat_ids.end()))++;
369 }
370 std::streampos pos = in.tellg();
371 std::size_t id(0);
372 std::string line;
373 while (std::getline(in, line))
374 {
375 if (line.empty() || isCommentLine(line))
376 {
377 continue;
378 }
379 if (line.substr(0, 3) == "SEG")
380 {
381 std::stringstream sstr(line);
382 std::string keyword;
383 std::array<std::size_t, 2> data{};
384 sstr >> keyword >> data[0] >> data[1];
385 std::array<MeshLib::Node*, 2> elem_nodes{};
386 for (std::size_t i = 0; i < 2; ++i)
387 {
388 auto const it = node_id_map.find(data[i]);
389 if (it == node_id_map.end() || it->second >= nodes.size())
390 {
391 ERR("Error: Node ID ({:d}) out of range (0, {:d}).",
392 data[i], nodes.back()->getID());
393 return false;
394 }
395 elem_nodes[i] = nodes[it->second];
396 }
397 elems.push_back(new MeshLib::Line(elem_nodes, id++));
398 mat_ids.push_back(current_mat_id);
399 }
400 else
401 {
402 in.seekg(pos);
403 return true;
404 }
405 pos = in.tellg();
406 }
407 ERR("{:s}", eof_error);
408 return false;
409}
410
412bool parseLine(std::ifstream& in,
413 std::vector<MeshLib::Node*>& nodes,
414 std::vector<MeshLib::Element*>& elems,
415 std::map<std::size_t, std::size_t>& node_id_map,
416 MeshLib::Properties& mesh_prop)
417{
418 if (!parseNodes(in, nodes, node_id_map, mesh_prop))
419 {
420 return false;
421 }
422 if (!parseLineSegments(in, nodes, elems, node_id_map, mesh_prop))
423 {
424 return false;
425 }
426
427 std::string line;
428 while (std::getline(in, line))
429 {
430 std::vector<std::string> str = BaseLib::splitString(line);
431 if (str[0] == "ILINE")
432 {
433 parseLine(in, nodes, elems, node_id_map, mesh_prop);
434 return true;
435 }
436 if (line == "END")
437 {
438 return true;
439 }
440 WARN("GocadAsciiReader::parseLine() - Unknown keyword found: {:s}",
441 line);
442 }
443 ERR("{:s}", eof_error);
444 return false;
445}
446
448bool parseElements(std::ifstream& in,
449 std::vector<MeshLib::Node*> const& nodes,
450 std::vector<MeshLib::Element*>& elems,
451 std::map<std::size_t, std::size_t> const& node_id_map,
452 MeshLib::Properties& mesh_prop)
453{
455 *mesh_prop.getPropertyVector<int>(mat_id_name);
456 int current_mat_id(0);
457 if (!mat_ids.empty())
458 {
459 current_mat_id = (*std::max_element(mat_ids.begin(), mat_ids.end()))++;
460 }
461 std::streampos pos = in.tellg();
462 std::size_t id(0);
463 std::string line;
464 while (std::getline(in, line))
465 {
466 if (line.empty() || isCommentLine(line))
467 {
468 continue;
469 }
470 if (line.substr(0, 4) == "TRGL")
471 {
472 std::stringstream sstr(line);
473 std::string keyword;
474 std::array<std::size_t, 3> data{};
475 sstr >> keyword >> data[0] >> data[1] >> data[2];
476 std::array<MeshLib::Node*, 3> elem_nodes{};
477 for (std::size_t i = 0; i < 3; ++i)
478 {
479 auto const it = node_id_map.find(data[i]);
480 if (it == node_id_map.end() || it->second >= nodes.size())
481 {
482 ERR("Error: Node ID ({:d}) out of range (0, {:d}).",
483 data[i], nodes.back()->getID());
484 return false;
485 }
486 elem_nodes[i] = nodes[it->second];
487 }
488 elems.push_back(new MeshLib::Tri(elem_nodes, id++));
489 mat_ids.push_back(current_mat_id);
490 }
491 else
492 {
493 in.seekg(pos);
494 return true;
495 }
496 pos = in.tellg();
497 }
498 ERR("{:s}", eof_error);
499 return false;
500}
501
503bool parseSurface(std::ifstream& in,
504 std::vector<MeshLib::Node*>& nodes,
505 std::vector<MeshLib::Element*>& elems,
506 std::map<std::size_t, std::size_t>& node_id_map,
507 MeshLib::Properties& mesh_prop)
508{
509 if (!parseNodes(in, nodes, node_id_map, mesh_prop))
510 {
511 return false;
512 }
513 if (!parseElements(in, nodes, elems, node_id_map, mesh_prop))
514 {
515 return false;
516 }
517
518 std::string line;
519 while (std::getline(in, line))
520 {
521 std::vector<std::string> str = BaseLib::splitString(line);
522 if (str[0] == "TFACE" || str[0] == "3DFace")
523 {
524 parseSurface(in, nodes, elems, node_id_map, mesh_prop);
525 return true;
526 }
527 if (str[0] == "BSTONE")
528 {
529 // borderstone definition - currently ignored
530 }
531 else if (str[0] == "BORDER")
532 {
533 // border tracking direction - currently ignored
534 }
535 else if (line == "END")
536 {
537 return true;
538 }
539 else
540 {
541 WARN(
542 "GocadAsciiReader::parseSurface() - Unknown keyword found: "
543 "{:s}",
544 line);
545 }
546 }
547 ERR("{:s}", eof_error);
548 return false;
549}
550
552template <typename T>
553MeshLib::Mesh* createMesh(std::ifstream& in, DataType type,
554 std::string& mesh_name,
555 MeshLib::Properties& mesh_prop, T parser,
556 bool const flip_elevation)
557{
558 std::vector<MeshLib::Node*> nodes;
559 std::vector<MeshLib::Element*> elems;
560 std::map<std::size_t, std::size_t> node_id_map;
561 INFO("Parsing {:s} {:s}.", dataType2ShortString(type), mesh_name);
562 bool return_val;
563 return_val = parser(in, nodes, elems, node_id_map, mesh_prop);
564
565 if (return_val)
566 {
567 if (flip_elevation)
568 {
569 std::for_each(nodes.begin(), nodes.end(),
570 [](MeshLib::Node* n) { (*n)[2] *= -1; });
571 }
572 return new MeshLib::Mesh(mesh_name, nodes, elems,
573 true /* compute_element_neighbors */,
574 mesh_prop);
575 }
576 ERR("Error parsing {:s} {:s}.", dataType2ShortString(type), mesh_name);
577 BaseLib::cleanupVectorElements(nodes, elems);
578 return nullptr;
579}
580
582MeshLib::Mesh* readData(std::ifstream& in,
583 DataType const& type,
584 std::string& mesh_name)
585{
586 if (!parseHeader(in, mesh_name))
587 {
588 return nullptr;
589 }
590
591 MeshLib::Properties mesh_prop;
594 bool flip_elevation = false;
595 std::string line;
596 while (std::getline(in, line))
597 {
598 std::vector<std::string> str = BaseLib::splitString(line);
599 if (line.empty() || isCommentLine(line))
600 {
601 continue;
602 }
603 if (str[0] == "GOCAD_ORIGINAL_COORDINATE_SYSTEM")
604 {
605 CoordinateSystem coordinate_system;
606 if (!coordinate_system.parse(in))
607 {
608 ERR("Error parsing coordinate system.");
609 return nullptr;
610 }
611 flip_elevation = (coordinate_system.z_positive ==
613 }
614 else if (str[0] == "GEOLOGICAL_FEATURE" ||
615 str[0] == "GEOLOGICAL_TYPE" ||
616 str[0] == "STRATIGRAPHIC_POSITION" || str[0] == "REGION")
617 {
618 // geological and stratigraphic information - currently ignored
619 }
620 else if (str[0] == "PROPERTY_CLASS_HEADER")
621 {
622 if (!parsePropertyClass(in))
623 {
624 ERR("Error parsing PROPERTY_CLASS_HEADER.");
625 return nullptr;
626 }
627 }
628 else if (str[0] == "PROPERTIES")
629 {
630 if (!parseProperties(in, str, mesh_prop))
631 {
632 ERR("Error parsing PROPERTIES");
633 return nullptr;
634 }
635 }
636 else if (type == DataType::PLINE && str[0] == "ILINE")
637 {
638 return createMesh(in, type, mesh_name, mesh_prop, parseLine,
639 flip_elevation);
640 }
641 else if (type == DataType::TSURF &&
642 (str[0] == "TFACE" || str[0] == "3DFace"))
643 {
644 return createMesh(in, type, mesh_name, mesh_prop, parseSurface,
645 flip_elevation);
646 }
647 else
648 {
649 WARN("GocadAsciiReader::readData() - Unknown keyword found: {:s}",
650 line);
651 }
652 }
653 ERR("{:s}", eof_error);
654 return nullptr;
655}
656
657bool readFile(std::string const& file_name,
658 std::vector<std::unique_ptr<MeshLib::Mesh>>& meshes,
659 DataType const export_type)
660{
661 std::ifstream in(file_name);
662 if (!in.is_open())
663 {
664 ERR("GocadAsciiReader::readFile(): Could not open file {:s}.",
665 file_name);
666 return false;
667 }
668
669 checkLineEndings(file_name);
670
671 DataType type;
672 while ((type = datasetFound(in)) != DataType::UNDEFINED)
673 {
674 if (export_type != DataType::ALL && type != export_type)
675 {
676 skipToEND(in);
677 continue;
678 }
679
680 if (type == DataType::VSET || type == DataType::MODEL3D)
681 {
682 if (!skipToEND(in))
683 {
684 ERR("Parsing of type {:s} is not implemented. Skipping "
685 "section.",
686 dataType2String(type));
687 return false;
688 }
689 continue;
690 }
691
692 std::string mesh_name = BaseLib::dropFileExtension(file_name) +
693 std::to_string(meshes.size() + 1);
694 std::unique_ptr<MeshLib::Mesh> mesh(readData(in, type, mesh_name));
695 if (mesh == nullptr)
696 {
697 ERR("File parsing aborted...");
698 return false;
699 }
700 meshes.push_back(std::move(mesh));
701 }
702 checkMeshNames(meshes);
703 return true;
704}
705
706} // namespace GocadAsciiReader
707} // end namespace Gocad
708} // end namespace FileIO
#define OGS_FATAL(...)
Definition Error.h:26
Filename manipulation routines.
Definition of the Line class.
void INFO(fmt::format_string< Args... > fmt, Args &&... args)
Definition Logging.h:35
void ERR(fmt::format_string< Args... > fmt, Args &&... args)
Definition Logging.h:45
void WARN(fmt::format_string< Args... > fmt, Args &&... args)
Definition Logging.h:40
Definition of the class Properties that implements a container of properties.
Definition of the Mesh class.
Definition of the Node class.
Definition of string helper functions.
std::string getName(std::string const &line)
Returns the name/title from the "Zone"-description.
Definition of the Tri class.
Property manager on mesh items. Class Properties manages scalar, vector or matrix properties....
Definition Properties.h:36
PropertyVector< T > * createNewPropertyVector(std::string_view name, MeshItemType mesh_item_type, std::size_t n_components=1)
PropertyVector< T > const * getPropertyVector(std::string_view name) const
void trim(std::string &str, char ch)
void cleanupVectorElements(std::vector< T * > &items)
Definition Algorithm.h:256
std::string dropFileExtension(std::string const &filename)
std::vector< std::string > splitString(std::string const &str)
T str2number(const std::string &str)
Definition StringTools.h:60
void checkLineEndings(std::string const &file_name)
Checks if current line is a designated keyword for a GoCAD data set.
bool parseLineSegments(std::ifstream &in, std::vector< MeshLib::Node * > const &nodes, std::vector< MeshLib::Element * > &elems, std::map< std::size_t, std::size_t > const &node_id_map, MeshLib::Properties &mesh_prop)
Parses the segments of the current line.
bool parseHeader(std::ifstream &in, std::string &mesh_name)
Parses the HEADER section (everything except the name is ignored right now)
bool parseLine(std::ifstream &in, std::vector< MeshLib::Node * > &nodes, std::vector< MeshLib::Element * > &elems, std::map< std::size_t, std::size_t > &node_id_map, MeshLib::Properties &mesh_prop)
Parses line information (nodes, segments, properties)
bool parsePropertyClass(std::ifstream &in)
bool isCommentLine(std::string const &str)
Checks if the current line is a comment.
bool skipToEND(std::ifstream &in)
Parses current section until END-tag is reached.
bool isKeyword(DataType const t, std::string const &line)
Checks if current line is a designated keyword for a GoCAD data set.
void checkMeshNames(std::vector< std::unique_ptr< MeshLib::Mesh > > const &meshes)
bool parseElements(std::ifstream &in, std::vector< MeshLib::Node * > const &nodes, std::vector< MeshLib::Element * > &elems, std::map< std::size_t, std::size_t > const &node_id_map, MeshLib::Properties &mesh_prop)
Parses the element data for the current mesh.
bool parseNodes(std::ifstream &in, std::vector< MeshLib::Node * > &nodes, std::map< std::size_t, std::size_t > &node_id_map, MeshLib::Properties const &mesh_prop)
Parses the node data for the current mesh.
bool parseAtomRegionIndicators(std::ifstream &in)
bool readFile(std::string const &file_name, std::vector< std::unique_ptr< MeshLib::Mesh > > &meshes, DataType const export_type)
Reads the specified file and writes data into internal mesh vector.
std::string propertyCheck(std::string const &string)
Checks if the current line starts with one of the allowed keywords.
bool parseProperties(std::ifstream &in, std::vector< std::string > const &names, MeshLib::Properties &mesh_prop)
MeshLib::Mesh * readData(std::ifstream &in, DataType const &type, std::string &mesh_name)
Reads one mesh contained in the file (there may be more than one!)
MeshLib::Mesh * createMesh(std::ifstream &in, DataType type, std::string &mesh_name, MeshLib::Properties &mesh_prop, T parser, bool const flip_elevation)
Converts parsed data into mesh.
DataType datasetFound(std::ifstream &in)
Checks if a GoCAD data set begins at the current stream position.
bool parseSurface(std::ifstream &in, std::vector< MeshLib::Node * > &nodes, std::vector< MeshLib::Element * > &elems, std::map< std::size_t, std::size_t > &node_id_map, MeshLib::Properties &mesh_prop)
Parses the surface information (nodes, triangles, properties)
MeshLib::Node * createNode(std::stringstream &sstr)
std::string dataType2String(DataType const t)
Given a Gocad DataType this returns the appropriate string.
std::string dataType2ShortString(DataType const t)
Given a Gocad DataType this returns the appropriate short form.