EGSnrc C++ class library  Report PIRS-898 (2021)
Iwan Kawrakow, Ernesto Mainegra-Hing, Frederic Tessier, Reid Townson and Blake Walters
msh_parser.h
1 /*
2 ###############################################################################
3 #
4 # EGSnrc Gmsh msh file parser
5 # Copyright (C) 2022 Max Orok
6 #
7 # This file is part of EGSnrc.
8 #
9 # EGSnrc is free software: you can redistribute it and/or modify it under
10 # the terms of the GNU Affero General Public License as published by the
11 # Free Software Foundation, either version 3 of the License, or (at your
12 # option) any later version.
13 #
14 # EGSnrc is distributed in the hope that it will be useful, but WITHOUT ANY
15 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16 # FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
17 # more details.
18 #
19 # You should have received a copy of the GNU Affero General Public License
20 # along with EGSnrc. If not, see <http://www.gnu.org/licenses/>.
21 #
22 ###############################################################################
23 #
24 # Author: Max Orok, 2020
25 #
26 # Contributors:
27 #
28 ###############################################################################
29 */
30 
31 // exclude from doxygen
33 
34 #ifndef MSH_PARSER_
35 #define MSH_PARSER_
36 
37 #include "egs_mesh.h" // for EGS_MeshSpec
38 
39 #include <algorithm>
40 #include <fstream>
41 #include <iostream>
42 #include <limits>
43 #include <sstream>
44 #include <stdexcept>
45 #include <vector>
46 #include <unordered_map>
47 #include <unordered_set>
48 
49 namespace msh_parser {
50 
51 // Top-level Gmsh msh file parser.
52 EGS_MeshSpec parse_msh_file(std::istream &input, EGS_InfoFunction info = nullptr);
53 
56 namespace internal {
57 
58 // trim function from https://stackoverflow.com/questions/216823/whats-the-best-way-to-trim-stdstring
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);
62  }).base(), s.end());
63 }
64 
65 enum class MshVersion { v41 };
66 constexpr std::size_t SIZET_MAX = std::numeric_limits<std::size_t>::max();
67 
72 static MshVersion parse_msh_version(std::istream &input) {
73  if (!input) {
74  throw std::runtime_error("bad input to parse_msh_version");
75  }
76  std::string format_line;
77  std::getline(input, format_line);
78  if (input.bad()) {
79  throw std::runtime_error("IO error during reading");
80  }
81  if (input.eof()) {
82  throw std::runtime_error("unexpected end of input");
83  }
84  rtrim(format_line);
85  if (format_line != "$MeshFormat") {
86  throw std::runtime_error("expected $MeshFormat, got " + format_line);
87  }
88 
89  std::string version;
90  int binary_flag = -1;
91  int sizet = -1;
92  input >> version;
93  input >> binary_flag;
94  input >> sizet;
95 
96  if (input.fail()) {
97  throw std::runtime_error("failed to parse msh version");
98  }
99  if (version != "4.1") {
100  throw std::runtime_error("unsupported msh version `" + version + "`, the only supported version is 4.1");
101  }
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");
105  }
106  throw std::runtime_error("failed to parse msh version");
107  }
108  if (sizet != 8) {
109  throw std::runtime_error("msh file size_t must be 8");
110  }
111  // eat newline
112  std::getline(input, format_line);
113 
114  std::getline(input, format_line);
115  rtrim(format_line);
116  if (format_line != "$EndMeshFormat") {
117  throw std::runtime_error("expected $EndMeshFormat, got `" + format_line + "`");
118  }
119 
120  return MshVersion::v41;
121 }
122 
125 namespace msh41 {
126 
127 // A model volume (e.g. cube, cylinder, complex shape constructed by boolean operations).
128 struct MeshVolume {
129  MeshVolume(int tag, int group) : tag(tag), group(group) {}
130  int tag = -1;
131  int group = -1;
132 };
133 
134 // A point in 3D space
135 struct Node {
136  Node(int tag, double x, double y, double z) : tag(tag), x(x), y(y), z(z) {}
137  int tag = -1; // TODO size_t?
138  double x = 0.0;
139  double y = 0.0;
140  double z = 0.0;
141 };
142 
143 // A tetrahedron composed of four nodes
144 struct Tetrahedron {
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) {}
147  int tag = -1;
148  int volume = -1;
149  int a = -1;
150  int b = -1;
151  int c = -1;
152  int d = -1;
153 };
154 
155 // 3D Gmsh physical group
156 struct PhysicalGroup {
157  PhysicalGroup(int tag, std::string name) : tag(tag), name(name) {}
158  int tag = -1;
159  std::string name;
160 };
161 
162 // Checks whether a list of structs with "tag" members have unique tags.
163 // Returns (false, <duplicate_tag>) if a duplicate was found, and returns
164 // (true, 0) otherwise.
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);
173  }
174  }
175  return std::make_pair(true, 0);
176 }
177 
181 static std::vector<MeshVolume> parse_entities(std::istream &input) {
182  std::vector<MeshVolume> volumes;
183  int num_3d = -1;
184  // parse number of entities
185  {
186  std::string line;
187  std::getline(input, line);
188  std::istringstream line_stream(line);
189  int num_0d = -1;
190  int num_1d = -1;
191  int num_2d = -1;
192  line_stream >> num_0d >> num_1d >> num_2d >> num_3d;
193  if (input.fail()
194  || num_0d < 0 || num_1d < 0 || num_2d < 0 || num_3d < 0) {
195  throw std::runtime_error("$Entities parsing failed");
196  }
197  if (num_3d == 0) {
198  throw std::runtime_error("$Entities parsing failed, no volumes found");
199  }
200  // skip to 3d entities
201  for (int i = 0; i < (num_0d + num_1d + num_2d); ++i) {
202  std::getline(input, line);
203  }
204  }
205 
206  // parse 3d entities
207  volumes.reserve(num_3d);
208  std::string line;
209  while (std::getline(input, line)) {
210  rtrim(line);
211  if (line == "$EndEntities") {
212  break;
213  }
214  std::istringstream line_stream(line);
215  int tag = -1;
216  // unused
217  double min_x = 0.0;
218  double min_y = 0.0;
219  double min_z = 0.0;
220  double max_x = 0.0;
221  double max_y = 0.0;
222  double max_z = 0.0;
223  // ...unused
224  std::size_t num_groups = 0;
225  int group = -1;
226  line_stream >> tag >>
227  min_x >> min_y >> min_z >>
228  max_x >> max_y >> max_z >>
229  num_groups >> group;
230  if (line_stream.fail()) {
231  throw std::runtime_error("$Entities parsing failed, 3d volume parsing failed");
232  }
233  if (num_groups == 0) {
234  throw std::runtime_error("$Entities parsing failed, volume " + std::to_string(tag) + " was not assigned a physical group");
235  }
236  if (num_groups != 1) {
237  throw std::runtime_error("$Entities parsing failed, volume " + std::to_string(tag) + " has more than one physical group");
238  }
239  volumes.push_back(MeshVolume(tag, group));
240  }
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()));
243  }
244  // ensure volume tags are unique
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));
249  }
250  return volumes;
251 }
252 
256 static std::vector<Node> parse_node_bloc(std::istream &input) {
257  std::vector<Node> nodes;
258  std::size_t num_nodes = SIZET_MAX;
259  int entity = -1;
260  std::string line;
261  {
262  std::getline(input, line);
263  std::istringstream line_stream(line);
264  int dim = -1;
265  int parametric = -1;
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");
270  }
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");
273  }
274  }
275  nodes.reserve(num_nodes);
276  // initialize node tags
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;
281  line_stream >> tag;
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));
284  }
285  Node n(-1, 0.0, 0.0, 0.0);
286  n.tag = tag;
287  nodes.push_back(n);
288  }
289  // fill in coordinates
290  for (std::size_t i = 0; i < num_nodes; ++i) {
291  std::getline(input, line);
292  std::istringstream line_stream(line);
293  double x = 0.0;
294  double y = 0.0;
295  double z = 0.0;
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));
299  }
300  nodes.at(i).x = x;
301  nodes.at(i).y = y;
302  nodes.at(i).z = z;
303  }
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));
307  }
308  return nodes;
309 }
310 
314 static std::vector<Node> parse_nodes(std::istream &input,
315  EGS_InfoFunction info) {
316  std::vector<Node> nodes;
317  std::size_t num_blocs = SIZET_MAX;
318  std::size_t num_nodes = SIZET_MAX;
319  std::string line;
320  {
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");
329  }
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()));
333  }
334  }
335 
336  if (num_nodes < 50000) {
337  info = nullptr; // don't log for small meshes
338  }
339 
340  egs_mesh::internal::PercentCounter progress(info, "EGS_Mesh: reading " +
341  std::to_string(num_nodes) + " nodes");
342  progress.start(num_nodes);
343 
344  nodes.reserve(num_nodes);
345  for (std::size_t i = 0; i < num_blocs; ++i) {
346  std::vector<Node> bloc_nodes;
347  try {
348  bloc_nodes = parse_node_bloc(input);
349  }
350  catch (const std::runtime_error &err) {
351  throw std::runtime_error("$Nodes section parsing failed\n" + std::string(err.what()));
352  }
353  nodes.insert(nodes.end(), bloc_nodes.begin(), bloc_nodes.end());
354  progress.step(bloc_nodes.size());
355  }
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()));
359  }
360  std::getline(input, line);
361  rtrim(line);
362  if (line != "$EndNodes") {
363  throw std::runtime_error("$Nodes section parsing failed, expected $EndNodes");
364  }
365  // ensure node tags are unique
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));
370  }
371 
372  progress.finish("EGS_Mesh: read " + std::to_string(num_nodes) + " nodes");
373  return nodes;
374 }
375 
379 static std::vector<PhysicalGroup> parse_groups(std::istream &input) {
380  std::vector<PhysicalGroup> groups;
381  // this is the total number of groups, not just 3D groups
382  int num_groups = -1;
383  std::string line;
384  {
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");
390  }
391  }
392  groups.reserve(num_groups);
393 
394  int dim = -1;
395  int tag = -1;
396  while (input) {
397  std::getline(input, line);
398  rtrim(line);
399  if (line == "$EndPhysicalNames") {
400  break;
401  }
402  std::istringstream line_stream(line);
403  line_stream >> dim;
404  line_stream >> tag;
405  if (line_stream.eof()) {
406  throw std::runtime_error("unexpected end of file, expected $EndPhysicalNames");
407  }
408  if (line_stream.fail()) {
409  throw std::runtime_error("physical group parsing failed: " + line);
410  }
411  // only save 3D physical groups
412  if (dim != 3) {
413  continue;
414  }
415  // find quoted group name
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);
419  }
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);
423  }
424  if (name_end - name_start == 1) {
425  throw std::runtime_error("empty physical group name: " + line);
426  }
427  auto name_len = name_end - name_start - 1; // -1 to exclude closing quote
428  groups.push_back(PhysicalGroup(tag, line.substr(name_start + 1, name_len)));
429  }
430  // ensure group tags are unique
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));
435  }
436  return groups;
437 }
438 
442 static std::vector<Tetrahedron> parse_element_bloc(std::istream &input) {
443  std::vector<Tetrahedron> elts;
444  std::size_t num_elts = SIZET_MAX;
445  int entity = -1;
446  std::string line;
447  {
448  std::getline(input, line);
449  std::istringstream line_stream(line);
450  int dim = -1;
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");
456  }
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");
460  }
461  // skip 0, 1, 2d element blocs
462  if (dim != 3) {
463  for (std::size_t i = 0; i < num_elts; ++i) {
464  std::getline(input, line);
465  }
466  return std::vector<Tetrahedron> {};
467  }
468  // If a mesh with 3d non-tetrahedral elements is provided, exit.
469  // The mesh may have some volumes that are supposed to be simulated but
470  // not represented by tetrahedrons, so they will be missing from the
471  // EGSnrc representation of the mesh.
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));
476  }
477  }
478  elts.reserve(num_elts);
479 
480  for (std::size_t i = 0; i < num_elts; ++i) {
481  std::getline(input, line);
482  std::istringstream line_stream(line);
483  int tag = -1;
484  int a = -1;
485  int b = -1;
486  int c = -1;
487  int d = -1;
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));
492  }
493  elts.push_back(Tetrahedron(tag, entity, a, b, c, d));
494  }
495  return elts;
496 }
497 
501 static std::vector<Tetrahedron> parse_elements(std::istream &input,
502  EGS_InfoFunction info) {
503  std::vector<Tetrahedron> elts;
504  std::size_t num_blocs = SIZET_MAX;
505  std::size_t num_elts = SIZET_MAX;
506  std::string line;
507  {
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");
516  }
517  }
518 
519  if (num_elts < 50000) {
520  info = nullptr; // don't log for small meshes
521  }
522 
523  egs_mesh::internal::PercentCounter progress(info, "EGS_Mesh: reading " +
524  std::to_string(num_elts) + " tetrahedrons");
525  progress.start(num_elts);
526 
527  elts.reserve(num_elts);
528  for (std::size_t i = 0; i < num_blocs; ++i) {
529  std::vector<Tetrahedron> bloc_elts;
530  try {
531  bloc_elts = parse_element_bloc(input);
532  }
533  catch (const std::runtime_error &err) {
534  throw std::runtime_error("$Elements section parsing failed\n" + std::string(err.what()));
535  }
536  elts.insert(elts.end(), bloc_elts.begin(), bloc_elts.end());
537  progress.step(bloc_elts.size());
538  }
539  // can't check against num_elts because it counts all elements
540  std::getline(input, line);
541  rtrim(line);
542  if (line != "$EndElements") {
543  throw std::runtime_error("$Elements section parsing failed, expected $EndElements");
544  }
545  if (elts.size() == 0) {
546  throw std::runtime_error("$Elements section parsing failed, no tetrahedral elements were read");
547  }
548  // ensure element tags are unique
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));
553  }
554  progress.finish("EGS_Mesh: read " + std::to_string(num_elts)
555  + " tetrahedrons");
556  // TODO check against min and max tag values
557  return elts;
558 }
559 
563 EGS_MeshSpec parse_msh41_body(std::istream &input, EGS_InfoFunction info) {
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;
568 
569  std::string parse_err;
570  std::string input_line;
571  while (std::getline(input, input_line)) {
572  msh_parser::internal::rtrim(input_line);
573  // stop reading if we hit another mesh file
574  if (input_line == "$MeshFormat") {
575  break;
576  }
577  if (input_line == "$Entities") {
578  volumes = msh_parser::internal::msh41::parse_entities(input);
579  }
580  else if (input_line == "$PhysicalNames") {
581  groups = msh_parser::internal::msh41::parse_groups(input);
582  }
583  else if (input_line == "$Nodes") {
584  nodes = msh_parser::internal::msh41::parse_nodes(input, info);
585  }
586  else if (input_line == "$Elements") {
587  elements = msh_parser::internal::msh41::parse_elements(input, info);
588  }
589  }
590  if (volumes.empty()) {
591  throw std::runtime_error("No volumes were parsed from $Entities section");
592  }
593  if (nodes.empty()) {
594  throw std::runtime_error("No nodes were parsed, missing $Nodes section");
595  }
596  if (groups.empty()) {
597  throw std::runtime_error("No groups were parsed from $PhysicalNames section");
598  }
599  if (elements.empty()) {
600  throw std::runtime_error("No tetrahedrons were parsed from $Elements section");
601  }
602 
603  // ensure each entity has a valid group
604  std::unordered_set<int> group_tags;
605  group_tags.reserve(groups.size());
606  for (auto g: groups) {
607  group_tags.insert(g.tag);
608  }
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));
614  }
615  volume_groups.insert({ v.tag, v.group });
616  }
617 
618  // ensure each element has a valid entity and therefore a valid physical 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));
625  }
626  element_groups.push_back(elt_group->second);
627  }
628 
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];
633  mesh_elts.push_back(EGS_MeshSpec::Tetrahedron(
634  elt.tag, element_groups[i], elt.a, elt.b, elt.c, elt.d
635  ));
636  }
637 
638  std::vector<EGS_MeshSpec::Node> mesh_nodes;
639  mesh_nodes.reserve(nodes.size());
640  for (const auto &n: nodes) {
641  mesh_nodes.push_back(EGS_MeshSpec::Node(
642  n.tag, n.x, n.y, n.z
643  ));
644  }
645 
646  std::vector<EGS_MeshSpec::Medium> media;
647  media.reserve(groups.size());
648  for (const auto &g: groups) {
649  media.push_back(EGS_MeshSpec::Medium(g.tag, g.name));
650  }
651 
652  // TODO: check all 3d physical groups were used by elements
653  // TODO: ensure all element node tags are valid
654  return EGS_MeshSpec(std::move(mesh_elts), std::move(mesh_nodes),
655  std::move(media));
656 }
657 
658 } // namespace msh_parser::internal::msh41
659 } // namespace msh_parser::internal
660 
661 EGS_MeshSpec parse_msh_file(std::istream &input, EGS_InfoFunction info /*default=nullptr*/) {
662  auto version = msh_parser::internal::parse_msh_version(input);
663  switch (version) {
664  case msh_parser::internal::MshVersion::v41:
665  try {
666  return msh_parser::internal::msh41::parse_msh41_body(
667  input, info);
668  }
669  catch (const std::runtime_error &err) {
670  throw std::runtime_error("msh 4.1 parsing failed\n" + std::string(err.what()));
671  }
672  break;
673  }
674  throw std::runtime_error("couldn't parse msh file");
675 }
676 
677 } // namespace msh_parser
678 
679 #endif // MSH_PARSER_
680 
A 3D point. Units are cm.
Definition: egs_mesh.h:111
A tetrahedral mesh element.
Definition: egs_mesh.h:98
A medium. The medium name must match an EGSnrc medium name.
Definition: egs_mesh.h:121
A container for raw unstructured tetrahedral mesh data.
Definition: egs_mesh.h:94
Tetrahedral mesh geometry: header.
void(* EGS_InfoFunction)(const char *,...)
Defines a function printf-like prototype for functions to be used to report info, warnings...