4178b353 |
1 | // Created: 2016-05-01 |
2 | // Author: Andrey Betenev |
3 | // Copyright: Open CASCADE 2016 |
4 | // |
5 | // This file is part of Open CASCADE Technology software library. |
6 | // |
7 | // This library is free software; you can redistribute it and/or modify it under |
8 | // the terms of the GNU Lesser General Public License version 2.1 as published |
9 | // by the Free Software Foundation, with special exception defined in the file |
10 | // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT |
11 | // distribution for complete text of the license and disclaimer of any warranty. |
12 | // |
13 | // Alternatively, this file may be used under the terms of Open CASCADE |
14 | // commercial license or contractual agreement. |
15 | |
16 | #include <RWStl_Reader.hxx> |
17 | |
18 | #include <gp_XY.hxx> |
19 | #include <Message.hxx> |
20 | #include <Message_Messenger.hxx> |
21 | #include <Message_ProgressSentry.hxx> |
22 | #include <NCollection_DataMap.hxx> |
23 | #include <NCollection_IncAllocator.hxx> |
24 | #include <FSD_BinaryFile.hxx> |
25 | #include <OSD_OpenFile.hxx> |
26 | #include <OSD_Timer.hxx> |
27 | #include <Precision.hxx> |
28 | #include <Standard_CLocaleSentry.hxx> |
29 | |
30 | #include <algorithm> |
31 | #include <limits> |
32 | |
33 | IMPLEMENT_STANDARD_RTTIEXT(RWStl_Reader, Standard_Transient) |
34 | |
35 | namespace |
36 | { |
37 | // Binary STL sizes |
38 | static const size_t THE_STL_HEADER_SIZE = 84; |
39 | static const size_t THE_STL_SIZEOF_FACET = 50; |
40 | static const size_t THE_STL_MIN_FILE_SIZE = THE_STL_HEADER_SIZE + THE_STL_SIZEOF_FACET; |
41 | |
42 | //! Auxiliary tool for merging nodes during STL reading. |
43 | class MergeNodeTool |
44 | { |
45 | public: |
46 | |
47 | //! Constructor |
48 | MergeNodeTool (RWStl_Reader* theReader) |
49 | : myReader (theReader), |
50 | myMap (1024, new NCollection_IncAllocator (1024 * 1024)) |
51 | { |
52 | } |
53 | |
54 | //! Add new triangle |
55 | int AddNode (double theX, double theY, double theZ) |
56 | { |
57 | // use existing node if found at the same point |
58 | gp_XYZ aPnt (theX, theY, theZ); |
59 | |
60 | Standard_Integer anIndex = -1; |
61 | if (myMap.Find (aPnt, anIndex)) |
62 | { |
63 | return anIndex; |
64 | } |
65 | |
66 | anIndex = myReader->AddNode (aPnt); |
67 | myMap.Bind (aPnt, anIndex); |
68 | return anIndex; |
69 | } |
70 | |
71 | public: |
72 | |
73 | static Standard_Boolean IsEqual (const gp_XYZ& thePnt1, const gp_XYZ& thePnt2) |
74 | { |
75 | return (thePnt1 - thePnt2).SquareModulus() < Precision::SquareConfusion(); |
76 | } |
77 | |
78 | static Standard_Integer HashCode (const gp_XYZ& thePnt, Standard_Integer theUpper) |
79 | { |
80 | return ::HashCode (thePnt.X() * M_LN10 + thePnt.Y() * M_PI + thePnt.Z() * M_E, theUpper); |
81 | } |
82 | |
83 | private: |
84 | RWStl_Reader* myReader; |
85 | NCollection_DataMap<gp_XYZ, Standard_Integer, MergeNodeTool> myMap; |
86 | }; |
87 | |
88 | //! Read a Little Endian 32 bits float |
89 | inline static float readStlFloat (const char* theData) |
90 | { |
91 | #if OCCT_BINARY_FILE_DO_INVERSE |
92 | // on big-endian platform, map values byte-per-byte |
93 | union |
94 | { |
95 | uint32_t i; |
96 | float f; |
97 | } bidargum; |
98 | bidargum.i = theData[0] & 0xFF; |
99 | bidargum.i |= (theData[1] & 0xFF) << 0x08; |
100 | bidargum.i |= (theData[2] & 0xFF) << 0x10; |
101 | bidargum.i |= (theData[3] & 0xFF) << 0x18; |
102 | return bidargum.f; |
103 | #else |
104 | // on little-endian platform, use plain cast |
105 | return *reinterpret_cast<const float*>(theData); |
106 | #endif |
107 | } |
108 | |
109 | //! Read a Little Endian 32 bits float |
110 | inline static gp_XYZ readStlFloatVec3 (const char* theData) |
111 | { |
112 | return gp_XYZ (readStlFloat (theData), |
113 | readStlFloat (theData + sizeof(float)), |
114 | readStlFloat (theData + sizeof(float) * 2)); |
115 | } |
116 | |
117 | } |
118 | |
119 | //============================================================================== |
120 | //function : Read |
121 | //purpose : |
122 | //============================================================================== |
123 | |
124 | Standard_Boolean RWStl_Reader::Read (const char* theFile, |
125 | const Handle(Message_ProgressIndicator)& theProgress) |
126 | { |
127 | std::filebuf aBuf; |
128 | OSD_OpenStream (aBuf, theFile, std::ios::in | std::ios::binary); |
129 | if (!aBuf.is_open()) |
130 | { |
131 | return Standard_False; |
132 | } |
133 | |
134 | Standard_IStream aStream (&aBuf); |
135 | if (IsAscii (aStream)) |
136 | { |
137 | // get length of file to feed progress indicator |
138 | aStream.seekg (0, aStream.end); |
139 | std::streampos theEnd = aStream.tellg(); |
140 | aStream.seekg (0, aStream.beg); |
141 | return ReadAscii (aStream, theEnd, theProgress); |
142 | } |
143 | else |
144 | { |
145 | return ReadBinary (aStream, theProgress); |
146 | } |
147 | } |
148 | |
149 | //============================================================================== |
150 | //function : IsAscii |
151 | //purpose : |
152 | //============================================================================== |
153 | |
154 | Standard_Boolean RWStl_Reader::IsAscii (Standard_IStream& theStream) |
155 | { |
156 | // read first 134 bytes to detect file format |
157 | char aBuffer[THE_STL_MIN_FILE_SIZE]; |
158 | std::streamsize aNbRead = theStream.read (aBuffer, THE_STL_MIN_FILE_SIZE).gcount(); |
159 | if (!theStream) |
160 | { |
161 | Message::DefaultMessenger()->Send ("Error: Cannot read file", Message_Fail); |
162 | return false; |
163 | } |
164 | |
165 | // put back the read symbols |
166 | for (std::streamsize aByteIter = aNbRead; aByteIter > 0; --aByteIter) |
167 | { |
168 | theStream.unget(); |
169 | } |
170 | |
171 | // if file is shorter than size of binary file with 1 facet, it must be ascii |
172 | if (aNbRead < std::streamsize(THE_STL_MIN_FILE_SIZE)) |
173 | { |
174 | return true; |
175 | } |
176 | |
177 | // otherwise, detect binary format by presence of non-ascii symbols in first 128 bytes |
178 | // (note that binary STL file may start with the same bytes "solid " as Ascii one) |
179 | for (Standard_Integer aByteIter = 0; aByteIter < aNbRead; ++aByteIter) |
180 | { |
181 | if ((unsigned char )aBuffer[aByteIter] > (unsigned char )'~') |
182 | { |
183 | return false; |
184 | } |
185 | } |
186 | return true; |
187 | } |
188 | |
189 | // adapted from Standard_CString.cxx |
190 | #ifdef __APPLE__ |
191 | // There are a lot of *_l functions availalbe on Mac OS X - we use them |
192 | #define SAVE_TL() |
193 | #elif defined(_MSC_VER) |
194 | // MSVCRT has equivalents with slightly different syntax |
195 | #define SAVE_TL() |
196 | #define sscanf_l(theBuffer, theLocale, theFormat, ...) _sscanf_s_l(theBuffer, theFormat, theLocale, __VA_ARGS__) |
197 | #else |
198 | // glibc provides only limited xlocale implementation: |
199 | // strtod_l/strtol_l/strtoll_l functions with explicitly specified locale |
200 | // and newlocale/uselocale/freelocale to switch locale within current thread only. |
201 | // So we switch to C locale temporarily |
202 | #define SAVE_TL() Standard_CLocaleSentry aLocaleSentry; |
203 | #define sscanf_l(theBuffer, theLocale, theFormat, ...) sscanf(theBuffer, theFormat, __VA_ARGS__) |
204 | #endif |
205 | |
206 | // Macro to get 64-bit position of the file from streampos |
207 | #if defined(_MSC_VER) |
208 | #define GETPOS(aPos) aPos.seekpos() |
209 | #else |
210 | #define GETPOS(aPos) ((int64_t)aPos) |
211 | #endif |
212 | |
213 | static inline bool str_starts_with (const char* theStr, const char* theWord, int theN) |
214 | { |
215 | while (isspace (*theStr) && *theStr != '\0') theStr++; |
216 | return !strncmp (theStr, theWord, theN); |
217 | } |
218 | |
219 | //============================================================================== |
220 | //function : ReadAscii |
221 | //purpose : |
222 | //============================================================================== |
223 | Standard_Boolean RWStl_Reader::ReadAscii (Standard_IStream& theStream, |
224 | const std::streampos theUntilPos, |
225 | const Handle(Message_ProgressIndicator)& theProgress) |
226 | { |
227 | // use method seekpos() to get true 64-bit offset to enable |
228 | // handling of large files (VS 2010 64-bit) |
229 | const int64_t aStartPos = GETPOS(theStream.tellg()); |
230 | const int64_t aEndPos = (theUntilPos > 0 ? GETPOS(theUntilPos) : std::numeric_limits<int64_t>::max()); |
231 | |
232 | // skip header "solid ..." |
233 | theStream.ignore (aEndPos - aStartPos, '\n'); |
234 | if (!theStream) |
235 | { |
236 | Message::DefaultMessenger()->Send ("Error: premature end of file", Message_Fail); |
237 | return false; |
238 | } |
239 | |
240 | MergeNodeTool aMergeTool (this); |
241 | Standard_CLocaleSentry::clocale_t aLocale = Standard_CLocaleSentry::GetCLocale(); |
242 | (void)aLocale; // to avoid warning on GCC where it is actually not used |
243 | SAVE_TL() // for GCC only, set C locale globally |
244 | |
245 | // report progress every 1 MiB of read data |
246 | const int aStepB = 1024 * 1024; |
247 | const Standard_Integer aNbSteps = 1 + Standard_Integer((theUntilPos - aStartPos) / aStepB); |
248 | Message_ProgressSentry aPSentry (theProgress, "Reading text STL file", 0, aNbSteps, 1); |
249 | |
250 | int64_t aProgressPos = aStartPos + aStepB; |
251 | const int64_t LINELEN = 1024; |
252 | int aNbLine = 1; |
253 | char aLine1[LINELEN], aLine2[LINELEN], aLine3[LINELEN]; |
254 | while (aPSentry.More()) |
255 | { |
256 | if (GETPOS(theStream.tellg()) > aProgressPos) |
257 | { |
258 | aPSentry.Next(); |
259 | aProgressPos += aStepB; |
260 | } |
261 | |
262 | char facet[LINELEN], outer[LINELEN]; |
263 | theStream.getline (facet, std::min (LINELEN, aEndPos - GETPOS(theStream.tellg()))); // "facet normal nx ny nz" |
264 | if (str_starts_with (facet, "endsolid", 8)) |
265 | { |
266 | // end of STL code |
267 | break; |
268 | } |
269 | theStream.getline (outer, std::min (LINELEN, aEndPos - GETPOS(theStream.tellg()))); // "outer loop" |
270 | if (!str_starts_with (facet, "facet", 5) || !str_starts_with (outer, "outer", 5)) |
271 | { |
272 | TCollection_AsciiString aStr ("Error: unexpected format of facet at line "); |
273 | aStr += aNbLine + 1; |
274 | Message::DefaultMessenger()->Send (aStr, Message_Fail); |
275 | return false; |
276 | } |
277 | |
278 | theStream.getline (aLine1, std::min (LINELEN, aEndPos - GETPOS(theStream.tellg()))); |
279 | theStream.getline (aLine2, std::min (LINELEN, aEndPos - GETPOS(theStream.tellg()))); |
280 | theStream.getline (aLine3, std::min (LINELEN, aEndPos - GETPOS(theStream.tellg()))); |
281 | |
282 | // stop reading if end of file is reached; |
283 | // note that well-formatted file never ends by the vertex line |
284 | if (theStream.eof() || GETPOS(theStream.tellg()) >= aEndPos) |
285 | { |
286 | break; |
287 | } |
288 | |
289 | if (!theStream) |
290 | { |
291 | Message::DefaultMessenger()->Send ("Error: premature end of file", Message_Fail); |
292 | return false; |
293 | } |
294 | aNbLine += 5; |
295 | |
296 | Standard_Real x1, y1, z1, x2, y2, z2, x3, y3, z3; |
297 | Standard_Integer aReadCount = // read 3 lines "vertex x y z" |
298 | sscanf_l (aLine1, aLocale, "%*s %lf %lf %lf", &x1, &y1, &z1) + |
299 | sscanf_l (aLine2, aLocale, "%*s %lf %lf %lf", &x2, &y2, &z2) + |
300 | sscanf_l (aLine3, aLocale, "%*s %lf %lf %lf", &x3, &y3, &z3); |
301 | if (aReadCount != 9) |
302 | { |
303 | TCollection_AsciiString aStr ("Error: cannot read vertex co-ordinates at line "); |
304 | aStr += aNbLine; |
305 | Message::DefaultMessenger()->Send(aStr, Message_Fail); |
306 | return false; |
307 | } |
308 | |
309 | // add triangle |
310 | int n1 = aMergeTool.AddNode (x1, y1, z1); |
311 | int n2 = aMergeTool.AddNode (x2, y2, z2); |
312 | int n3 = aMergeTool.AddNode (x3, y3, z3); |
313 | if (n1 != n2 && n2 != n3 && n3 != n1) |
314 | { |
315 | AddTriangle (n1, n2, n3); |
316 | } |
317 | |
318 | theStream.ignore (aEndPos - GETPOS(theStream.tellg()), '\n'); // skip "endloop" |
319 | theStream.ignore (aEndPos - GETPOS(theStream.tellg()), '\n'); // skip "endfacet" |
320 | |
321 | aNbLine += 2; |
322 | } |
323 | |
324 | return aPSentry.More(); |
325 | } |
326 | |
327 | //============================================================================== |
328 | //function : readStlBinary |
329 | //purpose : |
330 | //============================================================================== |
331 | |
332 | Standard_Boolean RWStl_Reader::ReadBinary (Standard_IStream& theStream, |
333 | const Handle(Message_ProgressIndicator)& theProgress) |
334 | { |
335 | /* |
336 | // the size of the file (minus the header size) |
337 | // must be a multiple of SIZEOF_STL_FACET |
338 | if ((theFileLen - THE_STL_HEADER_SIZE) % THE_STL_SIZEOF_FACET != 0 |
339 | || (theFileLen < THE_STL_MIN_FILE_SIZE)) |
340 | { |
341 | Message::DefaultMessenger()->Send ("Error: Corrupted binary STL file (inconsistent file size)!", Message_Fail); |
342 | return Standard_False; |
343 | } |
344 | const Standard_Integer aNbFacets = Standard_Integer((theFileLen - THE_STL_HEADER_SIZE) / THE_STL_SIZEOF_FACET); |
345 | */ |
346 | |
347 | // read file header at first |
348 | char aHeader[THE_STL_HEADER_SIZE + 1]; |
349 | if (theStream.read (aHeader, THE_STL_HEADER_SIZE).gcount() != std::streamsize(THE_STL_HEADER_SIZE)) |
350 | { |
351 | Message::DefaultMessenger()->Send ("Error: Corrupted binary STL file!", Message_Fail); |
352 | return false; |
353 | } |
354 | |
355 | // number of facets is stored as 32-bit integer at position 80 |
356 | const Standard_Integer aNbFacets = *(int32_t*)(aHeader + 80); |
357 | |
358 | MergeNodeTool aMergeTool (this); |
359 | |
360 | // don't trust the number of triangles which is coded in the file |
361 | // sometimes it is wrong, and with this technique we don't need to swap endians for integer |
362 | Message_ProgressSentry aPSentry (theProgress, "Reading binary STL file", 0, aNbFacets, 1); |
363 | Standard_Integer aNbRead = 0; |
364 | |
365 | // allocate buffer for 80 triangles |
366 | const int THE_CHUNK_NBFACETS = 80; |
367 | char aBuffer[THE_STL_SIZEOF_FACET * THE_CHUNK_NBFACETS]; |
368 | |
369 | // normal + 3 nodes + 2 extra bytes |
370 | const size_t aVec3Size = sizeof(float) * 3; |
371 | const size_t aFaceDataLen = aVec3Size * 4 + 2; |
372 | const char* aBufferPtr = aBuffer; |
373 | Standard_Integer aNbFacesInBuffer = 0; |
374 | for (Standard_Integer aNbFacetRead = 0; aNbFacetRead < aNbFacets && aPSentry.More(); |
375 | ++aNbFacetRead, ++aNbRead, --aNbFacesInBuffer, aBufferPtr += aFaceDataLen, aPSentry.Next()) |
376 | { |
377 | // read more data |
378 | if (aNbFacesInBuffer <= 0) |
379 | { |
380 | aNbFacesInBuffer = Min (THE_CHUNK_NBFACETS, aNbFacets - aNbFacetRead); |
381 | const std::streamsize aDataToRead = aNbFacesInBuffer * aFaceDataLen; |
382 | if (theStream.read (aBuffer, aDataToRead).gcount() != aDataToRead) |
383 | { |
384 | Message::DefaultMessenger()->Send ("Error: read filed", Message_Fail); |
385 | return false; |
386 | } |
387 | aBufferPtr = aBuffer; |
388 | } |
389 | |
390 | // get points from buffer |
391 | // readStlFloatVec3 (aBufferPtr); // skip normal |
392 | gp_XYZ aP1 = readStlFloatVec3 (aBufferPtr + aVec3Size); |
393 | gp_XYZ aP2 = readStlFloatVec3 (aBufferPtr + aVec3Size * 2); |
394 | gp_XYZ aP3 = readStlFloatVec3 (aBufferPtr + aVec3Size * 3); |
395 | |
396 | // add triangle |
397 | int n1 = aMergeTool.AddNode (aP1.X(), aP1.Y(), aP1.Z()); |
398 | int n2 = aMergeTool.AddNode (aP2.X(), aP2.Y(), aP2.Z()); |
399 | int n3 = aMergeTool.AddNode (aP3.X(), aP3.Y(), aP3.Z()); |
400 | if (n1 != n2 && n2 != n3 && n3 != n1) |
401 | { |
402 | AddTriangle (n1, n2, n3); |
403 | } |
404 | } |
405 | |
406 | return true; |
407 | } |