EGSnrc C++ class library  Report PIRS-898 (2021)
Iwan Kawrakow, Ernesto Mainegra-Hing, Frederic Tessier, Reid Townson and Blake Walters
stl_parser.h
1 /*
2 ###############################################################################
3 #
4 # EGS_TriangleMesh STL 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, 2022
25 #
26 # Contributors:
27 #
28 ###############################################################################
29 */
30 
31 // exclude from doxygen
33 
34 #ifndef EGS_TRIANGLE_MESH_STL_PARSER_
35 #define EGS_TRIANGLE_MESH_STL_PARSER_
36 
37 #include "egs_triangle_mesh.h" // EGS_TriangleMeshSpec
38 
39 #include <algorithm>
40 #include <cstdint>
41 #include <fstream>
42 #include <iostream>
43 //#include <limits>
44 #include <sstream>
45 #include <stdexcept>
46 #include <string>
47 
48 namespace stl_parser {
49 
53 inline EGS_TriangleMeshSpec parse_stl_file(const std::string &filename,
54  EGS_InfoFunction info = nullptr);
55 
58 namespace internal {
59 
60 static inline void trim(std::string &s) {
61  // ltrim
62  s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) {
63  return !std::isspace(ch);
64  }));
65  // rtrim
66  s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) {
67  return !std::isspace(ch);
68  }).base(), s.end());
69 }
70 
71 inline EGS_TriangleMeshSpec::Triangle parse_ascii_stl_triangle(std::string facet_line,
72  std::istream &input,
73  const std::string &filename) {
75  // parse triangle normal
76  {
77  std::istringstream line_stream(facet_line);
78  std::string facet;
79  std::string normal;
80  line_stream >> facet >> normal;
81  if (facet != "facet" || normal != "normal") {
82  throw std::runtime_error("failed to parse STL file `" + filename + "`, expected `facet normal`");
83  }
84  float n_x = 0.0;
85  float n_y = 0.0;
86  float n_z = 0.0;
87  line_stream >> n_x >> n_y >> n_z;
88  if (line_stream.fail()) {
89  throw std::runtime_error("failed to parse STL file `" + filename + "`, normal parsing failed");
90  }
91  tri.n = EGS_Vector(n_x, n_y, n_z);
92  }
93  // parse vertices
94  {
95  std::string line;
96  std::getline(input, line);
97  stl_parser::internal::trim(line);
98  if (line != "outer loop") {
99  throw std::runtime_error("failed to parse STL file `" + filename + "`, expected `outer loop`");
100  }
101  auto parse_vertex = [&]() -> EGS_Vector {
102  std::getline(input, line);
103  stl_parser::internal::trim(line);
104  std::istringstream line_stream(line);
105  std::string vertex;
106  float x = 0.0;
107  float y = 0.0;
108  float z = 0.0;
109  line_stream >> vertex >> x >> y >> z;
110  if (line_stream.fail() || vertex != "vertex") {
111  throw std::runtime_error("failed to parse STL file `" + filename + "` vertex data");
112  }
113  return EGS_Vector(x, y, z);
114  };
115  tri.a = parse_vertex();
116  tri.b = parse_vertex();
117  tri.c = parse_vertex();
118 
119  std::getline(input, line);
120  stl_parser::internal::trim(line);
121  if (line != "endloop") {
122  throw std::runtime_error("failed to parse STL file `" + filename + "`, expected `endloop`");
123  }
124  std::getline(input, line);
125  stl_parser::internal::trim(line);
126  if (line != "endfacet") {
127  throw std::runtime_error("failed to parse STL file `" + filename + "`, expected `endfacet`");
128  }
129  }
130  return tri;
131 }
132 
133 // Parse the body of an ascii STL file into an EGS_TriangleMeshSpec. Throws a
134 // std::runtime_error if parsing fails.
135 inline EGS_TriangleMeshSpec parse_ascii_stl_file(std::istream &input,
136  const std::string &filename) {
137  // discard any remaining characters after `solid`
138  std::string line;
139  std::getline(input, line);
140 
141  std::vector<EGS_TriangleMeshSpec::Triangle> raw_triangles;
142 
143  while (std::getline(input, line)) {
144  stl_parser::internal::trim(line);
145  if (line.rfind("endsolid", 0) == 0) {
146  break;
147  }
148  else if (line.rfind("facet", 0) == 0) {
149  const auto tri = parse_ascii_stl_triangle(line, input, filename);
150  raw_triangles.push_back(tri);
151  }
152  else {
153  throw std::runtime_error("failed to parse STL file `" + filename + "`, expected `facet` or `endsolid`");
154  }
155  }
156 
157  return EGS_TriangleMeshSpec(std::move(raw_triangles));
158 }
159 
160 // Parse the body of a binary STL file into an EGS_TriangleMeshSpec. Throws a
161 // std::runtime_error if parsing fails.
162 inline EGS_TriangleMeshSpec parse_binary_stl_file(std::istream &input,
163  const std::string &filename) {
164  // parse the number of triangles
165  std::uint32_t n_tri = 0;
166  input.read(reinterpret_cast<char *>(&n_tri), 4);
167  if (!input.good()) {
168  throw std::runtime_error("failed to parse the number of triangles");
169  }
170  if (n_tri == 0) {
171  throw std::runtime_error(std::string("STL file `") + filename
172  + "` has 0 triangles");
173  }
174 
175  // working variables for coordinate parsing
176  float xf;
177  float yf;
178  float zf;
179 
180  // potential cast from float to EGS_Float (which is usually double)
181  auto fill_egsvec = [&](EGS_Vector& v) {
182  v.x = xf;
183  v.y = yf;
184  v.z = zf;
185  };
186 
187  std::vector<EGS_TriangleMeshSpec::Triangle> raw_triangles;
188  raw_triangles.resize(n_tri);
189  for (std::uint32_t i = 0; i < n_tri; i++) {
190  // assumed to be an outward-facing unit normal as per STL spec
191  input.read(reinterpret_cast<char *>(&xf), sizeof(float));
192  input.read(reinterpret_cast<char *>(&yf), sizeof(float));
193  input.read(reinterpret_cast<char *>(&zf), sizeof(float));
194  fill_egsvec(raw_triangles[i].n);
195 
196  // nodes assumed to be in CCW order as viewed from outside
197  input.read(reinterpret_cast<char *>(&xf), sizeof(float));
198  input.read(reinterpret_cast<char *>(&yf), sizeof(float));
199  input.read(reinterpret_cast<char *>(&zf), sizeof(float));
200  fill_egsvec(raw_triangles[i].a);
201 
202  input.read(reinterpret_cast<char *>(&xf), sizeof(float));
203  input.read(reinterpret_cast<char *>(&yf), sizeof(float));
204  input.read(reinterpret_cast<char *>(&zf), sizeof(float));
205  fill_egsvec(raw_triangles[i].b);
206 
207  input.read(reinterpret_cast<char *>(&xf), sizeof(float));
208  input.read(reinterpret_cast<char *>(&yf), sizeof(float));
209  input.read(reinterpret_cast<char *>(&zf), sizeof(float));
210  fill_egsvec(raw_triangles[i].c);
211 
212  // Skip any attribute bytes. These are usually zero but just in case.
213  std::uint16_t attr_bytes = 0;
214  input.read(reinterpret_cast<char *>(&attr_bytes), 2);
215  input.ignore(attr_bytes);
216 
217  if (!input.good()) {
218  throw std::runtime_error("failed to parse STL file `" + filename + "`");
219  }
220  }
221 
222  return EGS_TriangleMeshSpec(std::move(raw_triangles));
223 }
224 
225 } // namespace stl_parser::internal
226 
227 inline EGS_TriangleMeshSpec parse_stl_file(const std::string &filename,
228  EGS_InfoFunction info /* = nullptr */) {
229 
230  std::ifstream stl_file(filename, std::ios::binary);
231  if (!stl_file) {
232  throw std::runtime_error("STL file `" + filename
233  + "` does not exist or is not readable");
234  }
235 
236  // check if this is an ascii file
237  std::string header;
238  header.resize(5);
239  stl_file.read(&header[0], 5);
240  if (stl_file.fail()) {
241  throw std::runtime_error("failed to parse STL file `" + filename + "`");
242  }
243 
244  if (header == "solid") {
245  return stl_parser::internal::parse_ascii_stl_file(stl_file, filename);
246  }
247 
248  // otherwise, assume this is a binary file
249 
250  // ignore the next 75 bytes of the 80 byte binary STL header
251  stl_file.ignore(75);
252  return stl_parser::internal::parse_binary_stl_file(stl_file, filename);
253 }
254 
255 } // namespace stl_parser
256 
257 #endif // EGS_TRIANGLE_MESH_STL_PARSER_
258 
A container for raw unstructured triangle surface mesh data.
A class representing 3D vectors.
Definition: egs_vector.h:57
void(* EGS_InfoFunction)(const char *,...)
Defines a function printf-like prototype for functions to be used to report info, warnings,...
Triangle surface mesh geometry: header.