46 #include <unordered_map>
47 #include <unordered_set>
49 namespace msh_parser {
59 static inline void rtrim(std::string &s) {
60 s.erase(std::find_if(s.rbegin(), s.rend(), [](
unsigned char ch) {
61 return !std::isspace(ch);
65 enum class MshVersion { v41 };
66 constexpr std::size_t SIZET_MAX = std::numeric_limits<std::size_t>::max();
72 static MshVersion parse_msh_version(std::istream &input) {
74 throw std::runtime_error(
"bad input to parse_msh_version");
76 std::string format_line;
77 std::getline(input, format_line);
79 throw std::runtime_error(
"IO error during reading");
82 throw std::runtime_error(
"unexpected end of input");
85 if (format_line !=
"$MeshFormat") {
86 throw std::runtime_error(
"expected $MeshFormat, got " + format_line);
97 throw std::runtime_error(
"failed to parse msh version");
99 if (version !=
"4.1") {
100 throw std::runtime_error(
"unsupported msh version `" + version +
"`, the only supported version is 4.1");
102 if (binary_flag != 0) {
103 if (binary_flag == 1) {
104 throw std::runtime_error(
"binary msh files are unsupported, please convert this file to ascii and try again");
106 throw std::runtime_error(
"failed to parse msh version");
109 throw std::runtime_error(
"msh file size_t must be 8");
112 std::getline(input, format_line);
114 std::getline(input, format_line);
116 if (format_line !=
"$EndMeshFormat") {
117 throw std::runtime_error(
"expected $EndMeshFormat, got `" + format_line +
"`");
120 return MshVersion::v41;
129 MeshVolume(
int tag,
int group) : tag(tag), group(group) {}
136 Node(
int tag,
double x,
double y,
double z) : tag(tag), x(x), y(y), z(z) {}
145 Tetrahedron(
int tag,
int volume,
int a,
int b,
int c,
int d) :
146 tag(tag), volume(volume), a(a), b(b), c(c), d(d) {}
156 struct PhysicalGroup {
157 PhysicalGroup(
int tag, std::string name) : tag(tag), name(name) {}
165 template <
typename T>
166 std::pair<bool, int> check_unique_tags(
const std::vector<T> &values) {
167 std::unordered_set<int> tags;
168 tags.reserve(values.size());
169 for (
const auto &v: values) {
170 auto insert_res = tags.insert(v.tag);
171 if (insert_res.second ==
false) {
172 return std::make_pair(
false, v.tag);
175 return std::make_pair(
true, 0);
181 static std::vector<MeshVolume> parse_entities(std::istream &input) {
182 std::vector<MeshVolume> volumes;
187 std::getline(input, line);
188 std::istringstream line_stream(line);
192 line_stream >> num_0d >> num_1d >> num_2d >> num_3d;
194 || num_0d < 0 || num_1d < 0 || num_2d < 0 || num_3d < 0) {
195 throw std::runtime_error(
"$Entities parsing failed");
198 throw std::runtime_error(
"$Entities parsing failed, no volumes found");
201 for (
int i = 0; i < (num_0d + num_1d + num_2d); ++i) {
202 std::getline(input, line);
207 volumes.reserve(num_3d);
209 while (std::getline(input, line)) {
211 if (line ==
"$EndEntities") {
214 std::istringstream line_stream(line);
224 std::size_t num_groups = 0;
226 line_stream >> tag >>
227 min_x >> min_y >> min_z >>
228 max_x >> max_y >> max_z >>
230 if (line_stream.fail()) {
231 throw std::runtime_error(
"$Entities parsing failed, 3d volume parsing failed");
233 if (num_groups == 0) {
234 throw std::runtime_error(
"$Entities parsing failed, volume " + std::to_string(tag) +
" was not assigned a physical group");
236 if (num_groups != 1) {
237 throw std::runtime_error(
"$Entities parsing failed, volume " + std::to_string(tag) +
" has more than one physical group");
239 volumes.push_back(MeshVolume(tag, group));
241 if (volumes.size() !=
static_cast<std::size_t
>(num_3d)) {
242 throw std::runtime_error(
"$Entities parsing failed, expected " + std::to_string(num_3d) +
" volumes but got " + std::to_string(volumes.size()));
245 auto unique_res = check_unique_tags(volumes);
246 if (!unique_res.first) {
247 throw std::runtime_error(
"$Entities section parsing failed, found duplicate volume tag "
248 + std::to_string(unique_res.second));
256 static std::vector<Node> parse_node_bloc(std::istream &input) {
257 std::vector<Node> nodes;
258 std::size_t num_nodes = SIZET_MAX;
262 std::getline(input, line);
263 std::istringstream line_stream(line);
266 line_stream >> dim >> entity >> parametric >> num_nodes;
267 if (line_stream.fail() || dim == -1 || entity == -1 || parametric == -1
268 || num_nodes == SIZET_MAX) {
269 throw std::runtime_error(
"Node bloc parsing failed");
271 if (dim < 0 || dim > 3) {
272 throw std::runtime_error(
"Node bloc parsing failed for entity " + std::to_string(entity) +
", got dimension " + std::to_string(dim) +
", expected 0, 1, 2, or 3");
275 nodes.reserve(num_nodes);
277 for (std::size_t i = 0; i < num_nodes; ++i) {
278 std::getline(input, line);
279 std::istringstream line_stream(line);
280 std::size_t tag = SIZET_MAX;
282 if (line_stream.fail() || tag == SIZET_MAX) {
283 throw std::runtime_error(
"Node bloc parsing failed during node tag section of entity " + std::to_string(entity));
285 Node n(-1, 0.0, 0.0, 0.0);
290 for (std::size_t i = 0; i < num_nodes; ++i) {
291 std::getline(input, line);
292 std::istringstream line_stream(line);
296 line_stream >> x >> y >> z;
297 if (line_stream.fail()) {
298 throw std::runtime_error(
"Node bloc parsing failed during node coordinate section of entity " + std::to_string(entity));
304 if (nodes.size() != num_nodes) {
305 throw std::runtime_error(
"Node bloc parsing failed, expected " + std::to_string(num_nodes) +
" nodes but read "
306 + std::to_string(nodes.size()) +
" for entity " + std::to_string(entity));
314 static std::vector<Node> parse_nodes(std::istream &input,
316 std::vector<Node> nodes;
317 std::size_t num_blocs = SIZET_MAX;
318 std::size_t num_nodes = SIZET_MAX;
321 std::getline(input, line);
322 std::istringstream line_stream(line);
323 std::size_t min_tag = SIZET_MAX;
324 std::size_t max_tag = SIZET_MAX;
325 line_stream >> num_blocs >> num_nodes >> min_tag >> max_tag;
326 if (line_stream.fail() || num_blocs == SIZET_MAX || num_nodes == SIZET_MAX ||
327 min_tag == SIZET_MAX || max_tag == SIZET_MAX) {
328 throw std::runtime_error(
"$Nodes section parsing failed, missing metadata");
330 if (max_tag > static_cast<std::size_t>(std::numeric_limits<int>::max())) {
331 throw std::runtime_error(
"Max node tag is too large (" + std::to_string(max_tag) +
"), limit is "
332 + std::to_string(std::numeric_limits<int>::max()));
336 if (num_nodes < 50000) {
340 egs_mesh::internal::PercentCounter progress(info,
"EGS_Mesh: reading " +
341 std::to_string(num_nodes) +
" nodes");
342 progress.start(num_nodes);
344 nodes.reserve(num_nodes);
345 for (std::size_t i = 0; i < num_blocs; ++i) {
346 std::vector<Node> bloc_nodes;
348 bloc_nodes = parse_node_bloc(input);
350 catch (
const std::runtime_error &err) {
351 throw std::runtime_error(
"$Nodes section parsing failed\n" + std::string(err.what()));
353 nodes.insert(nodes.end(), bloc_nodes.begin(), bloc_nodes.end());
354 progress.step(bloc_nodes.size());
356 if (nodes.size() != num_nodes) {
357 throw std::runtime_error(
"$Nodes section parsing failed, expected " + std::to_string(num_nodes) +
" nodes but read "
358 + std::to_string(nodes.size()));
360 std::getline(input, line);
362 if (line !=
"$EndNodes") {
363 throw std::runtime_error(
"$Nodes section parsing failed, expected $EndNodes");
366 auto unique_res = check_unique_tags(nodes);
367 if (!unique_res.first) {
368 throw std::runtime_error(
"$Nodes section parsing failed, found duplicate node tag "
369 + std::to_string(unique_res.second));
372 progress.finish(
"EGS_Mesh: read " + std::to_string(num_nodes) +
" nodes");
379 static std::vector<PhysicalGroup> parse_groups(std::istream &input) {
380 std::vector<PhysicalGroup> groups;
385 std::getline(input, line);
386 std::istringstream line_stream(line);
387 line_stream >> num_groups;
388 if (line_stream.fail() || num_groups == -1) {
389 throw std::runtime_error(
"$PhysicalNames parsing failed");
392 groups.reserve(num_groups);
397 std::getline(input, line);
399 if (line ==
"$EndPhysicalNames") {
402 std::istringstream line_stream(line);
405 if (line_stream.eof()) {
406 throw std::runtime_error(
"unexpected end of file, expected $EndPhysicalNames");
408 if (line_stream.fail()) {
409 throw std::runtime_error(
"physical group parsing failed: " + line);
416 auto name_start = line.find_first_of(
'"');
417 if (name_start == std::string::npos) {
418 throw std::runtime_error(
"physical group names must be quoted: " + line);
420 auto name_end = line.find_last_of(
'"');
421 if (name_end == name_start) {
422 throw std::runtime_error(
"couldn't find closing quote for physical group: " + line);
424 if (name_end - name_start == 1) {
425 throw std::runtime_error(
"empty physical group name: " + line);
427 auto name_len = name_end - name_start - 1;
428 groups.push_back(PhysicalGroup(tag, line.substr(name_start + 1, name_len)));
431 auto unique_res = check_unique_tags(groups);
432 if (!unique_res.first) {
433 throw std::runtime_error(
"$PhysicalNames section parsing failed, found duplicate tag "
434 + std::to_string(unique_res.second));
442 static std::vector<Tetrahedron> parse_element_bloc(std::istream &input) {
443 std::vector<Tetrahedron> elts;
444 std::size_t num_elts = SIZET_MAX;
448 std::getline(input, line);
449 std::istringstream line_stream(line);
451 int element_type = -1;
452 line_stream >> dim >> entity >> element_type >> num_elts;
453 if (line_stream.fail() || dim == -1 || entity == -1 || element_type == -1
454 || num_elts == SIZET_MAX) {
455 throw std::runtime_error(
"Element bloc parsing failed");
457 if (dim < 0 || dim > 3) {
458 throw std::runtime_error(
"Element bloc parsing failed for entity " + std::to_string(entity)
459 +
", got dimension " + std::to_string(dim) +
", expected 0, 1, 2, or 3");
463 for (std::size_t i = 0; i < num_elts; ++i) {
464 std::getline(input, line);
466 return std::vector<Tetrahedron> {};
472 const int TETRAHEDRON_TYPE = 4;
473 if (element_type != TETRAHEDRON_TYPE) {
474 throw std::runtime_error(
"Element bloc parsing failed for entity " + std::to_string(entity) +
475 ", got non-tetrahedral mesh element type " + std::to_string(element_type));
478 elts.reserve(num_elts);
480 for (std::size_t i = 0; i < num_elts; ++i) {
481 std::getline(input, line);
482 std::istringstream line_stream(line);
488 line_stream >> tag >> a >> b >> c >> d;
489 if (line_stream.fail() || tag == -1 || a == -1 || b == -1 ||
490 c == -1 || d == -1) {
491 throw std::runtime_error(
"Element bloc parsing failed for entity " + std::to_string(entity));
493 elts.push_back(Tetrahedron(tag, entity, a, b, c, d));
501 static std::vector<Tetrahedron> parse_elements(std::istream &input,
503 std::vector<Tetrahedron> elts;
504 std::size_t num_blocs = SIZET_MAX;
505 std::size_t num_elts = SIZET_MAX;
508 std::getline(input, line);
509 std::istringstream line_stream(line);
510 std::size_t min_tag = SIZET_MAX;
511 std::size_t max_tag = SIZET_MAX;
512 line_stream >> num_blocs >> num_elts >> min_tag >> max_tag;
513 if (line_stream.fail() || num_blocs == SIZET_MAX || num_elts == SIZET_MAX ||
514 min_tag == SIZET_MAX || max_tag == SIZET_MAX) {
515 throw std::runtime_error(
"$Elements section parsing failed, missing metadata");
519 if (num_elts < 50000) {
523 egs_mesh::internal::PercentCounter progress(info,
"EGS_Mesh: reading " +
524 std::to_string(num_elts) +
" tetrahedrons");
525 progress.start(num_elts);
527 elts.reserve(num_elts);
528 for (std::size_t i = 0; i < num_blocs; ++i) {
529 std::vector<Tetrahedron> bloc_elts;
531 bloc_elts = parse_element_bloc(input);
533 catch (
const std::runtime_error &err) {
534 throw std::runtime_error(
"$Elements section parsing failed\n" + std::string(err.what()));
536 elts.insert(elts.end(), bloc_elts.begin(), bloc_elts.end());
537 progress.step(bloc_elts.size());
540 std::getline(input, line);
542 if (line !=
"$EndElements") {
543 throw std::runtime_error(
"$Elements section parsing failed, expected $EndElements");
545 if (elts.size() == 0) {
546 throw std::runtime_error(
"$Elements section parsing failed, no tetrahedral elements were read");
549 auto unique_res = check_unique_tags(elts);
550 if (!unique_res.first) {
551 throw std::runtime_error(
"$Elements section parsing failed, found duplicate tetrahedron tag "
552 + std::to_string(unique_res.second));
554 progress.finish(
"EGS_Mesh: read " + std::to_string(num_elts)
564 std::vector<msh_parser::internal::msh41::Node> nodes;
565 std::vector<msh_parser::internal::msh41::MeshVolume> volumes;
566 std::vector<msh_parser::internal::msh41::PhysicalGroup> groups;
567 std::vector<msh_parser::internal::msh41::Tetrahedron> elements;
569 std::string parse_err;
570 std::string input_line;
571 while (std::getline(input, input_line)) {
572 msh_parser::internal::rtrim(input_line);
574 if (input_line ==
"$MeshFormat") {
577 if (input_line ==
"$Entities") {
578 volumes = msh_parser::internal::msh41::parse_entities(input);
580 else if (input_line ==
"$PhysicalNames") {
581 groups = msh_parser::internal::msh41::parse_groups(input);
583 else if (input_line ==
"$Nodes") {
584 nodes = msh_parser::internal::msh41::parse_nodes(input, info);
586 else if (input_line ==
"$Elements") {
587 elements = msh_parser::internal::msh41::parse_elements(input, info);
590 if (volumes.empty()) {
591 throw std::runtime_error(
"No volumes were parsed from $Entities section");
594 throw std::runtime_error(
"No nodes were parsed, missing $Nodes section");
596 if (groups.empty()) {
597 throw std::runtime_error(
"No groups were parsed from $PhysicalNames section");
599 if (elements.empty()) {
600 throw std::runtime_error(
"No tetrahedrons were parsed from $Elements section");
604 std::unordered_set<int> group_tags;
605 group_tags.reserve(groups.size());
606 for (
auto g: groups) {
607 group_tags.insert(g.tag);
609 std::unordered_map<int, int> volume_groups;
610 volume_groups.reserve(volumes.size());
611 for (
auto v: volumes) {
612 if (group_tags.find(v.group) == group_tags.end()) {
613 throw std::runtime_error(
"volume " + std::to_string(v.tag) +
" had unknown physical group tag " + std::to_string(v.group));
615 volume_groups.insert({ v.tag, v.group });
619 std::vector<int> element_groups;
620 element_groups.reserve(elements.size());
621 for (
auto e: elements) {
622 auto elt_group = volume_groups.find(e.volume);
623 if (elt_group == volume_groups.end()) {
624 throw std::runtime_error(
"tetrahedron " + std::to_string(e.tag) +
" had unknown volume tag " + std::to_string(e.volume));
626 element_groups.push_back(elt_group->second);
629 std::vector<EGS_MeshSpec::Tetrahedron> mesh_elts;
630 mesh_elts.reserve(elements.size());
631 for (std::size_t i = 0; i < elements.size(); ++i) {
632 const auto &elt = elements[i];
634 elt.tag, element_groups[i], elt.a, elt.b, elt.c, elt.d
638 std::vector<EGS_MeshSpec::Node> mesh_nodes;
639 mesh_nodes.reserve(nodes.size());
640 for (
const auto &n: nodes) {
646 std::vector<EGS_MeshSpec::Medium> media;
647 media.reserve(groups.size());
648 for (
const auto &g: groups) {
654 return EGS_MeshSpec(std::move(mesh_elts), std::move(mesh_nodes),
662 auto version = msh_parser::internal::parse_msh_version(input);
664 case msh_parser::internal::MshVersion::v41:
666 return msh_parser::internal::msh41::parse_msh41_body(
669 catch (
const std::runtime_error &err) {
670 throw std::runtime_error(
"msh 4.1 parsing failed\n" + std::string(err.what()));
674 throw std::runtime_error(
"couldn't parse msh file");
679 #endif // MSH_PARSER_
A 3D point. Units are cm.
A tetrahedral mesh element.
A medium. The medium name must match an EGSnrc medium name.
A container for raw unstructured tetrahedral mesh data.
Tetrahedral mesh geometry: header.
void(* EGS_InfoFunction)(const char *,...)
Defines a function printf-like prototype for functions to be used to report info, warnings...