STLAsciiParseManual.C
Go to the documentation of this file.
1 /*--------------------------------*- C++ -*----------------------------------*\
2  ========= |
3  \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
4  \\ / O peration |
5  \\ / A nd | www.openfoam.com
6  \\/ M anipulation |
7 -------------------------------------------------------------------------------
8  Copyright (C) 2018-2023 OpenCFD Ltd.
9 -------------------------------------------------------------------------------
10 License
11  This file is part of OpenFOAM.
12 
13  OpenFOAM is free software: you can redistribute it and/or modify it
14  under the terms of the GNU General Public License as published by
15  the Free Software Foundation, either version 3 of the License, or
16  (at your option) any later version.
17 
18  OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
19  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
20  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
21  for more details.
22 
23  You should have received a copy of the GNU General Public License
24  along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
25 
26 Description
27  Hand-written parsing of STL ASCII format
28 
29 \*---------------------------------------------------------------------------*/
30 
31 #include "STLAsciiParse.H"
32 #include "STLReader.H"
33 #include "stringOps.H"
34 
35 // * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
36 
37 static inline std::string perrorEOF(std::string expected)
38 {
39  return "Premature EOF while reading '" + expected + "'";
40 }
41 
42 
43 static inline std::string perrorParse(std::string expected, std::string found)
44 {
45  return "Parse error. Expecting '" + expected + "' found '" + found + "'";
46 }
47 
48 
49 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
50 
51 namespace Foam
52 {
53 namespace Detail
54 {
55 
56 //- A lexer for parsing STL ASCII files.
57 // Returns DynamicList(s) of points and facets (zoneIds).
58 // The facets are within a solid/endsolid grouping
60 :
62 {
63  enum scanState
64  {
65  scanSolid = 0,
66  scanFacet,
67  scanLoop,
68  scanVerts,
69  scanEndLoop,
70  scanEndFacet,
71  scanEndSolid
72  };
73 
74  scanState state_;
75 
76  std::string errMsg_;
77 
78  //- Like std:csub_match
79  typedef std::pair<const char*, const char*> tokenType;
80 
81  // Tokenized line
82  DynamicList<tokenType> tokens_;
83 
84  //- Tokenize
85  inline std::string::size_type tokenize(const char *p, const char *pe)
86  {
87  const char* start = p;
88  tokens_.clear();
89 
90  // Find not space
91  while (p < pe && isspace(*p))
92  {
93  if (*p == '\n' && lineNum_)
94  {
95  ++lineNum_;
96  }
97  ++p;
98  }
99 
100  while (p != pe)
101  {
102  const char* beg = p;
103 
104  // Find space
105  while (p < pe && !isspace(*p))
106  {
107  ++p;
108  }
109  tokens_.emplace_back(beg, p);
110 
111  // Find next
112  while (p < pe && isspace(*p))
113  {
114  if (*p == '\n')
115  {
116  ++lineNum_;
117  return (p - start);
118  }
119  ++p;
120  }
121  }
122 
123  return (p - start);
124  }
125 
126 
127 public:
128 
129  //- Construct with the estimated number of triangles in the STL
130  STLAsciiParseManual(const label nTrisEstimated)
131  :
132  Detail::STLAsciiParse(nTrisEstimated)
133  {}
134 
135  //- Execute parser
136  void execute(std::istream& is);
137 };
138 
139 } // End namespace Detail
140 } // End namespace Foam
141 
142 
143 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
144 
145 // Length of the input read buffer
146 #define INBUFLEN 16384
147 
148 void Foam::Detail::STLAsciiParseManual::execute(std::istream& is)
149 {
150  if (!is)
151  {
152  return;
153  }
154 
155  // Buffering
156  char inbuf[INBUFLEN];
157  std::streamsize pending = 0;
158 
159  lineNum_ = 0;
160 
161  state_ = scanSolid;
162  errMsg_.clear();
163 
164  // Line-oriented processing loop
165  while (is)
166  {
167  if (pending >= INBUFLEN)
168  {
169  // We overfilled the buffer while trying to scan a token...
171  << "buffer full while scanning near line " << lineNum_ << nl;
172  break;
173  }
174 
175  char *data = inbuf + pending; // current data buffer
176  const std::streamsize buflen = INBUFLEN - pending; // space in buffer
177 
178  is.read(data, buflen);
179  const std::streamsize gcount = is.gcount();
180 
181  if (gcount <= 0)
182  {
183  // EOF
184  // If scanning for next "solid" this is a valid way to exit, but
185  // an error if scanning for the initial "solid" or any other token
186 
187  switch (state_)
188  {
189  case scanSolid:
190  {
191  if (!lineNum_) errMsg_ = perrorEOF("solid");
192  break;
193  }
194  case scanFacet: { errMsg_ = perrorEOF("facet"); break; }
195  case scanLoop: { errMsg_ = perrorEOF("outer loop"); break; }
196  case scanVerts: { errMsg_ = perrorEOF("vertex"); break; }
197  case scanEndLoop: { errMsg_ = perrorEOF("endloop"); break; }
198  case scanEndFacet: { errMsg_ = perrorEOF("endfacet"); break; }
199  case scanEndSolid: { errMsg_ = perrorEOF("endsolid"); break; }
200  }
201 
202  // Terminate the parsing loop
203  break;
204  }
205 
206  // p,pe = Ragel parsing point and parsing end (default naming)
207  // eof = Ragel EOF point (default naming)
208 
209  char *p = inbuf;
210  char *pe = data + gcount;
211 
212  // Line-oriented: search backwards to find last newline
213  {
214  --pe;
215  while (*pe != '\n' && pe >= inbuf)
216  {
217  --pe;
218  }
219  ++pe;
220  }
221 
222  std::string cmd;
223  do
224  {
225  // Tokenize
226  const auto parsedLen = tokenize(p, pe);
227  p += parsedLen;
228  if (!parsedLen || tokens_.empty())
229  {
230  break;
231  }
232 
233  // Ensure consistent case on the first token
234  cmd.assign(tokens_[0].first, tokens_[0].second);
235  stringOps::lower(cmd);
236 
237  // Handle all expected parse states
238  switch (state_)
239  {
240  case scanSolid:
241  {
242  if (cmd == "solid")
243  {
244  if (tokens_.empty())
245  {
247  }
248  else
249  {
250  beginSolid
251  (
253  (
254  tokens_[1].first,
255  tokens_[1].second
256  )
257  );
258  }
259 
260  state_ = scanFacet; // Next state
261  }
262  else
263  {
264  errMsg_ = perrorParse("solid", cmd);
265  }
266  break;
267  }
268  case scanFacet:
269  {
270  if (cmd == "color")
271  {
272  // Optional 'color' entry (after solid)
273  // - continue looking for 'facet'
274  continue;
275  }
276  else if (cmd == "facet")
277  {
278  beginFacet();
279  state_ = scanLoop; // Next state
280  }
281  else if (cmd == "endsolid")
282  {
283  // Finished with 'endsolid' - find next solid
284  state_ = scanSolid;
285  }
286  else
287  {
288  errMsg_ = perrorParse("facet", cmd);
289  }
290  break;
291  }
292  case scanLoop:
293  {
294  if (cmd == "outer")
295  {
296  // More pedantic would with (tokens_[1] == "loop") too
297  state_ = scanVerts; // Next state
298  }
299  else
300  {
301  errMsg_ = perrorParse("outer loop", cmd);
302  }
303  break;
304  }
305  case scanVerts:
306  {
307  if (cmd == "vertex")
308  {
309  if (tokens_.size() > 3)
310  {
311  // The tokens are space-delimited and thus okay
312  // for atof()
313  addVertexComponent(tokens_[1].first);
314  addVertexComponent(tokens_[2].first);
315  addVertexComponent(tokens_[3].first);
316  }
317  else
318  {
319  errMsg_ = "Error parsing vertex value";
320  }
321  }
322  else if (cmd == "endloop")
323  {
324  state_ = scanEndFacet; // Next state
325  }
326  else
327  {
328  errMsg_ = perrorParse("vertex", cmd);
329  }
330  break;
331  }
332  case scanEndLoop:
333  {
334  if (cmd == "endloop")
335  {
336  state_ = scanEndFacet; // Next state
337  }
338  else
339  {
340  errMsg_ = perrorParse("endloop", cmd);
341  }
342  break;
343  }
344  case scanEndFacet:
345  {
346  if (cmd == "endfacet")
347  {
348  endFacet();
349  state_ = scanFacet; // Next facet, or endsolid
350  }
351  else
352  {
353  errMsg_ = perrorParse("endfacet", cmd);
354  }
355  break;
356  }
357  case scanEndSolid:
358  {
359  if (cmd == "endsolid")
360  {
361  state_ = scanSolid; // Start over again
362  }
363  else
364  {
365  errMsg_ = perrorParse("endsolid", cmd);
366  }
367  break;
368  }
369  }
370  }
371  while (errMsg_.empty());
372 
373  // How much still in the buffer?
374  pending = data + gcount - pe;
375 
376  if (pending)
377  {
378  memmove(inbuf, pe, pending);
379  }
380 
381  if (gcount < buflen)
382  {
383  break; // done
384  }
385 
386  if (!errMsg_.empty())
387  {
388  break;
389  }
390  }
391 
392  if (!errMsg_.empty())
393  {
395  << errMsg_ << nl;
396  }
397 }
398 
399 
400 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
401 
402 bool Foam::fileFormats::STLReader::readAsciiManual
403 (
404  const fileName& filename
405 )
406 {
407  IFstream is(filename);
408  if (!is)
409  {
411  << "file " << filename << " not found"
412  << exit(FatalError);
413  }
414 
415  // Create with estimated number of triangles in the STL.
416  // 180 bytes / triangle. For simplicity, ignore compression
417 
418  const auto fileLen = is.fileSize();
419 
420  const label nTrisEstimated =
421  (
422  (fileLen > 0)
423  ? max(label(100), label(fileLen/180))
424  : label(100)
425  );
426 
427 
428  Detail::STLAsciiParseManual lexer(nTrisEstimated);
429  lexer.execute(is.stdStream());
430 
431  transfer(lexer);
432 
433  return true;
434 }
435 
436 
437 // ************************************************************************* //
static word validate(const std::string &s, const bool prefix=false)
Construct validated word (no invalid characters).
Definition: word.C:39
void size(const label n)
Older name for setAddressableSize.
Definition: UList.H:116
#define INBUFLEN
Internal class used when parsing STL ASCII format.
Definition: STLAsciiParse.H:50
void beginSolid(word solidName)
Action when entering &#39;solid&#39;.
errorManipArg< error, int > exit(error &err, const int errNo=1)
Definition: errorManip.H:125
static std::string perrorParse(std::string expected, std::string found)
error FatalError
Error stream (stdout output on all processes), with additional &#39;FOAM FATAL ERROR&#39; header text and sta...
#define FatalErrorInFunction
Report an error message using Foam::FatalError.
Definition: error.H:598
void beginFacet()
Action when entering &#39;facet&#39;.
label max(const labelHashSet &set, label maxValue=labelMin)
Find the max value in labelHashSet, optionally limited by second argument.
Definition: hashSets.C:40
constexpr char nl
The newline &#39;\n&#39; character (0x0a)
Definition: Ostream.H:50
bool empty() const noexcept
True if List is empty (ie, size() is zero)
Definition: UList.H:666
A lexer for parsing STL ASCII files.
bool addVertexComponent(float val)
Add next vertex component. On each third call, adds the point.
STLAsciiParseManual(const label nTrisEstimated)
Construct with the estimated number of triangles in the STL.
T & emplace_back(Args &&... args)
Construct an element at the end of the list, return reference to the new list element.
Definition: DynamicListI.H:538
static std::string perrorEOF(std::string expected)
static const word null
An empty word.
Definition: word.H:84
graph_traits< Graph >::vertices_size_type size_type
Definition: SloanRenumber.C:68
void execute(std::istream &is)
Execute parser.
void clear() noexcept
Clear the addressed list, i.e. set the size to zero.
Definition: DynamicListI.H:405
string lower(const std::string &s)
Return string copy transformed with std::tolower on each character.
Definition: stringOps.C:1171
bool isspace(char c) noexcept
Test for whitespace (C-locale)
Definition: char.H:69
void endFacet()
Action on &#39;endfacet&#39;.
volScalarField & p
bool found
Namespace for OpenFOAM.