STLCore.C
Go to the documentation of this file.
1 /*---------------------------------------------------------------------------*\
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) 2011-2016 OpenFOAM Foundation
9  Copyright (C) 2016-2024 OpenCFD Ltd.
10 -------------------------------------------------------------------------------
11 License
12  This file is part of OpenFOAM.
13 
14  OpenFOAM is free software: you can redistribute it and/or modify it
15  under the terms of the GNU General Public License as published by
16  the Free Software Foundation, either version 3 of the License, or
17  (at your option) any later version.
18 
19  OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
20  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
21  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
22  for more details.
23 
24  You should have received a copy of the GNU General Public License
25  along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
26 
27 \*---------------------------------------------------------------------------*/
28 
29 #include "STLCore.H"
30 #include "OSspecific.H"
31 #include "IFstream.H"
32 
33 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
34 
35 // The number of bytes in the STL binary header
36 static constexpr const unsigned STLHeaderSize = 80;
37 
38 
39 // * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
40 
41 // Check if "SOLID" or "solid" appears as the first non-space content.
42 // Assume that any leading space is less than 75 chars or so, otherwise
43 // it is really bad input.
44 static bool startsWithSolid(const char header[STLHeaderSize])
45 {
46  unsigned pos = 0;
47  while (std::isspace(header[pos]) && pos < STLHeaderSize)
48  {
49  ++pos;
50  }
51 
52  return
53  (
54  pos < (STLHeaderSize-5) // At least 5 chars remaining
55  && std::toupper(header[pos+0]) == 'S'
56  && std::toupper(header[pos+1]) == 'O'
57  && std::toupper(header[pos+2]) == 'L'
58  && std::toupper(header[pos+3]) == 'I'
59  && std::toupper(header[pos+4]) == 'D'
60  );
61 }
62 
63 
64 // Check if file size appears to be reasonable for an STL binary file.
65 // Compare file size with that expected from number of tris
66 // If this is not sensible, it may be an ASCII file
67 //
68 // sizeof(STLtriangle) = 50 bytes [int16 + 4 * (3 float)]
69 
70 inline static bool checkBinaryFileSize
71 (
72  const int64_t nTris,
73  const Foam::fileName& file
74 )
75 {
76  // When checking the content size, account for the header size (80),
77  // but ignore the nTris information (int32_t) to give some rounding
78 
79  const int64_t contentSize =
80  (
81  int64_t(Foam::fileSize(file))
82  - int64_t(STLHeaderSize)
83  );
84 
85  return
86  (
87  (contentSize >= 0)
88  && (nTris >= contentSize/50)
89  && (nTris <= contentSize/25)
90  );
91 }
92 
93 
94 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
95 
97 (
98  const fileName& filename,
99  const STLFormat format
100 )
101 {
102  return
103  (
104  format == STLFormat::UNKNOWN
105  ? filename.has_ext("stlb")
106  : format == STLFormat::BINARY
107  );
108 }
109 
110 
111 // Check binary by getting the header and number of facets
112 // this seems to work better than the old token-based method
113 // - using wordToken can cause an abort if non-word (binary) content
114 // is detected ... this is not exactly what we want.
115 // - some programs (eg, PROSTAR) have 'solid' as the first word in
116 // the binary header. This is just wrong and not our fault.
118 (
119  const fileName& filename
120 )
121 {
122  // Handle compressed (.gz) or uncompressed input files
123 
124  ifstreamPointer isPtr(filename);
125  const bool unCompressed =
127 
128  auto& is = *isPtr;
129 
130  if (!is.good())
131  {
133  << "Cannot read file " << filename
134  << " or file " << filename + ".gz"
135  << exit(FatalError);
136  }
137 
138  // Read the STL header
139  char header[STLHeaderSize];
140  is.read(header, STLHeaderSize);
141 
142  // If the stream is bad, it can't be a binary STL
143  if (!is.good() || startsWithSolid(header))
144  {
145  return 0;
146  }
147 
148 
149  // Read the number of triangles in the STL file
150  // (note: read as signed int so we can check whether >2^31).
151  //
152  // With nTris == 2^31, file size is 107.37 GB !
153  //
154  // However, the limit is more likely caused by the number of points
155  // that can be stored (label-size=32) when flattened for merging.
156  // So more like 715.8M triangles (~35.8 GB)
157 
158  int32_t nTris;
159  is.read(reinterpret_cast<char*>(&nTris), sizeof(int32_t));
160 
161  bool ok = (is && nTris >= 0);
162 
163  if (ok && unCompressed)
164  {
165  ok = checkBinaryFileSize(nTris, filename);
166  }
167 
168  //if (ok)
169  //{
170  // InfoErr<< "stlb : " << nTris << " triangles" << nl;
171  //}
172 
173  // Return number of triangles if it appears to be BINARY and good.
174  return (ok ? nTris : 0);
175 }
176 
177 
178 std::unique_ptr<std::istream>
180 (
181  const fileName& filename,
182  label& nTrisEstimated
183 )
184 {
185  nTrisEstimated = 0;
186 
187  std::unique_ptr<std::istream> streamPtr;
188  bool unCompressed(true);
189 
190  // Handle compressed (.gz) or uncompressed input files
191  {
192  ifstreamPointer isPtr(filename);
193  unCompressed =
195 
196  // Take ownership
197  streamPtr.reset(isPtr.release());
198  }
199  auto& is = *streamPtr;
200 
201  if (!is.good())
202  {
204  << "Cannot read file " << filename
205  << " or file " << filename + ".gz"
206  << exit(FatalError);
207  }
208 
209 
210  // Read the STL header
211  char header[STLHeaderSize];
212  is.read(header, STLHeaderSize);
213 
214  // Check that stream is OK, if not this may be an ASCII file
215  if (!is.good()) // could check again: startsWithSolid(header)
216  {
218  << "problem reading header, perhaps file is not binary "
219  << exit(FatalError);
220  }
221 
222 
223  // Read the number of triangles in the STL file
224  // (note: read as signed int so we can check whether >2^31).
225  //
226  // With nTris == 2^31, file size is 107.37 GB !
227  //
228  // However, the limit is more likely caused by the number of points
229  // that can be stored (label-size=32) when flattened for merging.
230  // So more like 715.8M triangles (~35.8 GB)
231 
232  int32_t nTris;
233  is.read(reinterpret_cast<char*>(&nTris), sizeof(int32_t));
234 
235  bool ok = (is && nTris >= 0);
236 
237  if (ok && unCompressed)
238  {
239  ok = checkBinaryFileSize(nTris, filename);
240  }
241 
242  if (!ok)
243  {
245  << "problem reading number of triangles, perhaps file is not binary"
246  << exit(FatalError);
247  }
248 
249  nTrisEstimated = nTris;
250 
251  return streamPtr;
252 }
253 
254 
256 (
257  ostream& os,
258  uint32_t nTris
259 )
260 {
261  // STL header with extra information about nTris
262  char header[STLHeaderSize];
263  ::snprintf(header, STLHeaderSize, "STL binary file %u facets", nTris);
264 
265  // Fill trailing with zeroes (to avoid writing junk)
266  for (size_t i = strlen(header); i < STLHeaderSize; ++i)
267  {
268  header[i] = 0;
269  }
270 
271  os.write(header, STLHeaderSize);
272  os.write(reinterpret_cast<char*>(&nTris), sizeof(uint32_t));
273 }
274 
275 
276 // ************************************************************************* //
A wrapped std::ifstream with possible compression handling (igzstream) that behaves much like a std::...
A class for handling file names.
Definition: fileName.H:72
off_t fileSize(const fileName &name, const bool followLink=true)
Return size of file or -1 on failure (normally follows symbolic links).
Definition: POSIX.C:905
errorManipArg< error, int > exit(error &err, const int errNo=1)
Definition: errorManip.H:125
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:608
IOstreamOption::compressionType whichCompression() const
Which compression type?
static void writeBinaryHeader(ostream &os, uint32_t nTris)
Write STL binary file and number of triangles to stream.
Definition: STLCore.C:249
std::istream * release() noexcept
Return managed pointer and release ownership.
Functions used by OpenFOAM that are specific to POSIX compliant operating systems and need to be repl...
dimensionedScalar pos(const dimensionedScalar &ds)
static bool isBinaryName(const fileName &filename, const STLFormat format)
Detect &#39;stlb&#39; extension as binary when format = UNKNOWN.
Definition: STLCore.C:90
static constexpr const unsigned STLHeaderSize
Definition: STLCore.C:29
static bool startsWithSolid(const char header[STLHeaderSize])
Definition: STLCore.C:37
static bool checkBinaryFileSize(const int64_t nTris, const Foam::fileName &file)
Definition: STLCore.C:64
OBJstream os(runTime.globalPath()/outputName)
word format(conversionProperties.get< word >("format"))
bool isspace(char c) noexcept
Test for whitespace (C-locale)
Definition: char.H:69
bool has_ext() const
Various checks for extensions.
Definition: stringI.H:43
static std::unique_ptr< std::istream > readBinaryHeader(const fileName &filename, label &nTrisEstimated)
Read STL binary file header.
Definition: STLCore.C:173
static int detectBinaryHeader(const fileName &filename)
Check contents to detect if the file is a binary STL.
Definition: STLCore.C:111