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