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 | |
51ee6a7d |
42 | // The length of buffer to read (in bytes) |
43 | static const size_t THE_BUFFER_SIZE = 1024; |
44 | |
4178b353 |
45 | //! Auxiliary tool for merging nodes during STL reading. |
46 | class MergeNodeTool |
47 | { |
48 | public: |
49 | |
50 | //! Constructor |
51 | MergeNodeTool (RWStl_Reader* theReader) |
52 | : myReader (theReader), |
53 | myMap (1024, new NCollection_IncAllocator (1024 * 1024)) |
54 | { |
55 | } |
56 | |
57 | //! Add new triangle |
58 | int AddNode (double theX, double theY, double theZ) |
59 | { |
60 | // use existing node if found at the same point |
61 | gp_XYZ aPnt (theX, theY, theZ); |
62 | |
63 | Standard_Integer anIndex = -1; |
64 | if (myMap.Find (aPnt, anIndex)) |
65 | { |
66 | return anIndex; |
67 | } |
68 | |
69 | anIndex = myReader->AddNode (aPnt); |
70 | myMap.Bind (aPnt, anIndex); |
71 | return anIndex; |
72 | } |
73 | |
74 | public: |
75 | |
76 | static Standard_Boolean IsEqual (const gp_XYZ& thePnt1, const gp_XYZ& thePnt2) |
77 | { |
78 | return (thePnt1 - thePnt2).SquareModulus() < Precision::SquareConfusion(); |
79 | } |
80 | |
2b2be3fb |
81 | //! Computes a hash code for the point, in the range [1, theUpperBound] |
82 | //! @param thePoint the point which hash code is to be computed |
83 | //! @param theUpperBound the upper bound of the range a computing hash code must be within |
84 | //! @return a computed hash code, in the range [1, theUpperBound] |
85 | static Standard_Integer HashCode (const gp_XYZ& thePoint, const Standard_Integer theUpperBound) |
4178b353 |
86 | { |
2b2be3fb |
87 | return ::HashCode (thePoint.X() * M_LN10 + thePoint.Y() * M_PI + thePoint.Z() * M_E, theUpperBound); |
4178b353 |
88 | } |
89 | |
90 | private: |
91 | RWStl_Reader* myReader; |
92 | NCollection_DataMap<gp_XYZ, Standard_Integer, MergeNodeTool> myMap; |
93 | }; |
94 | |
95 | //! Read a Little Endian 32 bits float |
96 | inline static float readStlFloat (const char* theData) |
97 | { |
98 | #if OCCT_BINARY_FILE_DO_INVERSE |
99 | // on big-endian platform, map values byte-per-byte |
100 | union |
101 | { |
102 | uint32_t i; |
103 | float f; |
104 | } bidargum; |
105 | bidargum.i = theData[0] & 0xFF; |
106 | bidargum.i |= (theData[1] & 0xFF) << 0x08; |
107 | bidargum.i |= (theData[2] & 0xFF) << 0x10; |
108 | bidargum.i |= (theData[3] & 0xFF) << 0x18; |
109 | return bidargum.f; |
110 | #else |
111 | // on little-endian platform, use plain cast |
112 | return *reinterpret_cast<const float*>(theData); |
113 | #endif |
114 | } |
115 | |
116 | //! Read a Little Endian 32 bits float |
117 | inline static gp_XYZ readStlFloatVec3 (const char* theData) |
118 | { |
119 | return gp_XYZ (readStlFloat (theData), |
120 | readStlFloat (theData + sizeof(float)), |
121 | readStlFloat (theData + sizeof(float) * 2)); |
122 | } |
123 | |
124 | } |
125 | |
126 | //============================================================================== |
127 | //function : Read |
128 | //purpose : |
129 | //============================================================================== |
130 | |
131 | Standard_Boolean RWStl_Reader::Read (const char* theFile, |
132 | const Handle(Message_ProgressIndicator)& theProgress) |
133 | { |
134 | std::filebuf aBuf; |
135 | OSD_OpenStream (aBuf, theFile, std::ios::in | std::ios::binary); |
136 | if (!aBuf.is_open()) |
137 | { |
138 | return Standard_False; |
139 | } |
140 | |
141 | Standard_IStream aStream (&aBuf); |
1d949423 |
142 | |
143 | // get length of file to feed progress indicator in Ascii mode |
144 | aStream.seekg (0, aStream.end); |
145 | std::streampos theEnd = aStream.tellg(); |
146 | aStream.seekg (0, aStream.beg); |
147 | |
22e70738 |
148 | // binary STL files cannot be shorter than 134 bytes |
149 | // (80 bytes header + 4 bytes facet count + 50 bytes for one facet); |
150 | // thus assume files shorter than 134 as Ascii without probing |
151 | // (probing may bring stream to fail state if EOF is reached) |
152 | bool isAscii = ((size_t)theEnd < THE_STL_MIN_FILE_SIZE || IsAscii (aStream)); |
153 | |
c37bd936 |
154 | Standard_ReadLineBuffer aBuffer (THE_BUFFER_SIZE); |
155 | |
22e70738 |
156 | while (aStream.good()) |
4178b353 |
157 | { |
22e70738 |
158 | if (isAscii) |
1d949423 |
159 | { |
c37bd936 |
160 | if (!ReadAscii (aStream, aBuffer, theEnd, theProgress)) |
1d949423 |
161 | { |
162 | break; |
163 | } |
164 | } |
165 | else |
166 | { |
167 | if (!ReadBinary (aStream, theProgress)) |
168 | { |
169 | break; |
170 | } |
171 | } |
172 | aStream >> std::ws; // skip any white spaces |
4178b353 |
173 | } |
22e70738 |
174 | return ! aStream.fail(); |
4178b353 |
175 | } |
176 | |
177 | //============================================================================== |
178 | //function : IsAscii |
179 | //purpose : |
180 | //============================================================================== |
181 | |
182 | Standard_Boolean RWStl_Reader::IsAscii (Standard_IStream& theStream) |
183 | { |
184 | // read first 134 bytes to detect file format |
185 | char aBuffer[THE_STL_MIN_FILE_SIZE]; |
186 | std::streamsize aNbRead = theStream.read (aBuffer, THE_STL_MIN_FILE_SIZE).gcount(); |
22e70738 |
187 | if (! theStream) |
4178b353 |
188 | { |
a87b1b37 |
189 | Message::SendFail ("Error: Cannot read file"); |
22e70738 |
190 | return true; |
4178b353 |
191 | } |
192 | |
193 | // put back the read symbols |
194 | for (std::streamsize aByteIter = aNbRead; aByteIter > 0; --aByteIter) |
195 | { |
196 | theStream.unget(); |
197 | } |
198 | |
199 | // if file is shorter than size of binary file with 1 facet, it must be ascii |
200 | if (aNbRead < std::streamsize(THE_STL_MIN_FILE_SIZE)) |
201 | { |
202 | return true; |
203 | } |
204 | |
205 | // otherwise, detect binary format by presence of non-ascii symbols in first 128 bytes |
206 | // (note that binary STL file may start with the same bytes "solid " as Ascii one) |
207 | for (Standard_Integer aByteIter = 0; aByteIter < aNbRead; ++aByteIter) |
208 | { |
209 | if ((unsigned char )aBuffer[aByteIter] > (unsigned char )'~') |
210 | { |
211 | return false; |
212 | } |
213 | } |
214 | return true; |
215 | } |
216 | |
217 | // adapted from Standard_CString.cxx |
218 | #ifdef __APPLE__ |
219 | // There are a lot of *_l functions availalbe on Mac OS X - we use them |
220 | #define SAVE_TL() |
221 | #elif defined(_MSC_VER) |
222 | // MSVCRT has equivalents with slightly different syntax |
223 | #define SAVE_TL() |
224 | #define sscanf_l(theBuffer, theLocale, theFormat, ...) _sscanf_s_l(theBuffer, theFormat, theLocale, __VA_ARGS__) |
225 | #else |
226 | // glibc provides only limited xlocale implementation: |
227 | // strtod_l/strtol_l/strtoll_l functions with explicitly specified locale |
228 | // and newlocale/uselocale/freelocale to switch locale within current thread only. |
229 | // So we switch to C locale temporarily |
230 | #define SAVE_TL() Standard_CLocaleSentry aLocaleSentry; |
231 | #define sscanf_l(theBuffer, theLocale, theFormat, ...) sscanf(theBuffer, theFormat, __VA_ARGS__) |
232 | #endif |
233 | |
04232180 |
234 | // Macro to get 64-bit position of the file from std::streampos |
6b1800cb |
235 | #if defined(_MSC_VER) && _MSC_VER < 1700 |
04232180 |
236 | // In MSVC 2010, cast of std::streampos to 64-bit int is implemented incorrectly; |
6b1800cb |
237 | // work-around (relevant for files larger than 4 GB) is to use internal function seekpos(). |
238 | // Since MSVC 15.8, seekpos() is deprecated and is said to always return 0. |
4178b353 |
239 | #define GETPOS(aPos) aPos.seekpos() |
240 | #else |
241 | #define GETPOS(aPos) ((int64_t)aPos) |
242 | #endif |
243 | |
d45b7860 |
244 | # if defined(_MSC_VER) && ! defined(strncasecmp) |
245 | # define strncasecmp _strnicmp |
246 | # endif |
247 | |
4178b353 |
248 | static inline bool str_starts_with (const char* theStr, const char* theWord, int theN) |
249 | { |
250 | while (isspace (*theStr) && *theStr != '\0') theStr++; |
d45b7860 |
251 | return !strncasecmp (theStr, theWord, theN); |
4178b353 |
252 | } |
253 | |
07bbde45 |
254 | static bool ReadVertex (const char* theStr, double& theX, double& theY, double& theZ) |
255 | { |
256 | const char *aStr = theStr; |
257 | |
258 | // skip 'vertex' |
259 | while (isspace ((unsigned char)*aStr) || isalpha ((unsigned char)*aStr)) |
260 | ++aStr; |
261 | |
262 | // read values |
263 | char *aEnd; |
264 | theX = Strtod (aStr, &aEnd); |
265 | theY = Strtod (aStr = aEnd, &aEnd); |
266 | theZ = Strtod (aStr = aEnd, &aEnd); |
267 | |
268 | return aEnd != aStr; |
269 | } |
270 | |
4178b353 |
271 | //============================================================================== |
272 | //function : ReadAscii |
273 | //purpose : |
274 | //============================================================================== |
275 | Standard_Boolean RWStl_Reader::ReadAscii (Standard_IStream& theStream, |
c37bd936 |
276 | Standard_ReadLineBuffer& theBuffer, |
4178b353 |
277 | const std::streampos theUntilPos, |
278 | const Handle(Message_ProgressIndicator)& theProgress) |
279 | { |
280 | // use method seekpos() to get true 64-bit offset to enable |
281 | // handling of large files (VS 2010 64-bit) |
282 | const int64_t aStartPos = GETPOS(theStream.tellg()); |
51ee6a7d |
283 | size_t aLineLen = 0; |
284 | const char* aLine; |
4178b353 |
285 | |
286 | // skip header "solid ..." |
c37bd936 |
287 | aLine = theBuffer.ReadLine (theStream, aLineLen); |
51ee6a7d |
288 | if (aLine == NULL) |
4178b353 |
289 | { |
a87b1b37 |
290 | Message::SendFail ("Error: premature end of file"); |
4178b353 |
291 | return false; |
292 | } |
293 | |
294 | MergeNodeTool aMergeTool (this); |
295 | Standard_CLocaleSentry::clocale_t aLocale = Standard_CLocaleSentry::GetCLocale(); |
296 | (void)aLocale; // to avoid warning on GCC where it is actually not used |
297 | SAVE_TL() // for GCC only, set C locale globally |
298 | |
299 | // report progress every 1 MiB of read data |
300 | const int aStepB = 1024 * 1024; |
71c810df |
301 | const Standard_Integer aNbSteps = 1 + Standard_Integer((GETPOS(theUntilPos) - aStartPos) / aStepB); |
4178b353 |
302 | Message_ProgressSentry aPSentry (theProgress, "Reading text STL file", 0, aNbSteps, 1); |
4178b353 |
303 | int64_t aProgressPos = aStartPos + aStepB; |
4178b353 |
304 | int aNbLine = 1; |
51ee6a7d |
305 | |
4178b353 |
306 | while (aPSentry.More()) |
307 | { |
308 | if (GETPOS(theStream.tellg()) > aProgressPos) |
309 | { |
310 | aPSentry.Next(); |
311 | aProgressPos += aStepB; |
312 | } |
313 | |
c37bd936 |
314 | aLine = theBuffer.ReadLine (theStream, aLineLen); // "facet normal nx ny nz" |
51ee6a7d |
315 | if (aLine == NULL) |
316 | { |
a87b1b37 |
317 | Message::SendFail ("Error: premature end of file"); |
51ee6a7d |
318 | return false; |
319 | } |
320 | if (str_starts_with (aLine, "endsolid", 8)) |
4178b353 |
321 | { |
322 | // end of STL code |
323 | break; |
324 | } |
51ee6a7d |
325 | if (!str_starts_with (aLine, "facet", 5)) |
4178b353 |
326 | { |
a87b1b37 |
327 | Message::SendFail (TCollection_AsciiString ("Error: unexpected format of facet at line ") + (aNbLine + 1)); |
4178b353 |
328 | return false; |
329 | } |
330 | |
c37bd936 |
331 | aLine = theBuffer.ReadLine (theStream, aLineLen); // "outer loop" |
51ee6a7d |
332 | if (aLine == NULL || !str_starts_with (aLine, "outer", 5)) |
4178b353 |
333 | { |
a87b1b37 |
334 | Message::SendFail (TCollection_AsciiString ("Error: unexpected format of facet at line ") + (aNbLine + 1)); |
51ee6a7d |
335 | return false; |
4178b353 |
336 | } |
337 | |
51ee6a7d |
338 | gp_XYZ aVertex[3]; |
339 | Standard_Boolean isEOF = false; |
340 | for (Standard_Integer i = 0; i < 3; i++) |
4178b353 |
341 | { |
c37bd936 |
342 | aLine = theBuffer.ReadLine (theStream, aLineLen); |
51ee6a7d |
343 | if (aLine == NULL) |
344 | { |
345 | isEOF = true; |
346 | break; |
347 | } |
348 | gp_XYZ aReadVertex; |
349 | if (!ReadVertex (aLine, aReadVertex.ChangeCoord (1), aReadVertex.ChangeCoord (2), aReadVertex.ChangeCoord (3))) |
350 | { |
a87b1b37 |
351 | Message::SendFail (TCollection_AsciiString ("Error: cannot read vertex co-ordinates at line ") + aNbLine); |
51ee6a7d |
352 | return false; |
353 | } |
354 | aVertex[i] = aReadVertex; |
4178b353 |
355 | } |
4178b353 |
356 | |
51ee6a7d |
357 | // stop reading if end of file is reached; |
358 | // note that well-formatted file never ends by the vertex line |
359 | if (isEOF) |
4178b353 |
360 | { |
51ee6a7d |
361 | break; |
4178b353 |
362 | } |
363 | |
51ee6a7d |
364 | aNbLine += 5; |
365 | |
4178b353 |
366 | // add triangle |
51ee6a7d |
367 | int n1 = aMergeTool.AddNode (aVertex[0].X(), aVertex[0].Y(), aVertex[0].Z()); |
368 | int n2 = aMergeTool.AddNode (aVertex[1].X(), aVertex[1].Y(), aVertex[1].Z()); |
369 | int n3 = aMergeTool.AddNode (aVertex[2].X(), aVertex[2].Y(), aVertex[2].Z()); |
4178b353 |
370 | if (n1 != n2 && n2 != n3 && n3 != n1) |
371 | { |
372 | AddTriangle (n1, n2, n3); |
373 | } |
374 | |
c37bd936 |
375 | theBuffer.ReadLine (theStream, aLineLen); // skip "endloop" |
376 | theBuffer.ReadLine (theStream, aLineLen); // skip "endfacet" |
4178b353 |
377 | |
378 | aNbLine += 2; |
379 | } |
380 | |
381 | return aPSentry.More(); |
382 | } |
383 | |
384 | //============================================================================== |
385 | //function : readStlBinary |
386 | //purpose : |
387 | //============================================================================== |
388 | |
389 | Standard_Boolean RWStl_Reader::ReadBinary (Standard_IStream& theStream, |
390 | const Handle(Message_ProgressIndicator)& theProgress) |
391 | { |
392 | /* |
393 | // the size of the file (minus the header size) |
394 | // must be a multiple of SIZEOF_STL_FACET |
395 | if ((theFileLen - THE_STL_HEADER_SIZE) % THE_STL_SIZEOF_FACET != 0 |
396 | || (theFileLen < THE_STL_MIN_FILE_SIZE)) |
397 | { |
a87b1b37 |
398 | Message::SendFail ("Error: Corrupted binary STL file (inconsistent file size)"); |
4178b353 |
399 | return Standard_False; |
400 | } |
401 | const Standard_Integer aNbFacets = Standard_Integer((theFileLen - THE_STL_HEADER_SIZE) / THE_STL_SIZEOF_FACET); |
402 | */ |
403 | |
404 | // read file header at first |
405 | char aHeader[THE_STL_HEADER_SIZE + 1]; |
406 | if (theStream.read (aHeader, THE_STL_HEADER_SIZE).gcount() != std::streamsize(THE_STL_HEADER_SIZE)) |
407 | { |
a87b1b37 |
408 | Message::SendFail ("Error: Corrupted binary STL file"); |
4178b353 |
409 | return false; |
410 | } |
411 | |
412 | // number of facets is stored as 32-bit integer at position 80 |
413 | const Standard_Integer aNbFacets = *(int32_t*)(aHeader + 80); |
414 | |
415 | MergeNodeTool aMergeTool (this); |
416 | |
417 | // don't trust the number of triangles which is coded in the file |
418 | // sometimes it is wrong, and with this technique we don't need to swap endians for integer |
419 | Message_ProgressSentry aPSentry (theProgress, "Reading binary STL file", 0, aNbFacets, 1); |
420 | Standard_Integer aNbRead = 0; |
421 | |
422 | // allocate buffer for 80 triangles |
423 | const int THE_CHUNK_NBFACETS = 80; |
424 | char aBuffer[THE_STL_SIZEOF_FACET * THE_CHUNK_NBFACETS]; |
425 | |
426 | // normal + 3 nodes + 2 extra bytes |
427 | const size_t aVec3Size = sizeof(float) * 3; |
428 | const size_t aFaceDataLen = aVec3Size * 4 + 2; |
429 | const char* aBufferPtr = aBuffer; |
430 | Standard_Integer aNbFacesInBuffer = 0; |
431 | for (Standard_Integer aNbFacetRead = 0; aNbFacetRead < aNbFacets && aPSentry.More(); |
432 | ++aNbFacetRead, ++aNbRead, --aNbFacesInBuffer, aBufferPtr += aFaceDataLen, aPSentry.Next()) |
433 | { |
434 | // read more data |
435 | if (aNbFacesInBuffer <= 0) |
436 | { |
437 | aNbFacesInBuffer = Min (THE_CHUNK_NBFACETS, aNbFacets - aNbFacetRead); |
438 | const std::streamsize aDataToRead = aNbFacesInBuffer * aFaceDataLen; |
439 | if (theStream.read (aBuffer, aDataToRead).gcount() != aDataToRead) |
440 | { |
a87b1b37 |
441 | Message::SendFail ("Error: binary STL read failed"); |
4178b353 |
442 | return false; |
443 | } |
444 | aBufferPtr = aBuffer; |
445 | } |
446 | |
447 | // get points from buffer |
448 | // readStlFloatVec3 (aBufferPtr); // skip normal |
449 | gp_XYZ aP1 = readStlFloatVec3 (aBufferPtr + aVec3Size); |
450 | gp_XYZ aP2 = readStlFloatVec3 (aBufferPtr + aVec3Size * 2); |
451 | gp_XYZ aP3 = readStlFloatVec3 (aBufferPtr + aVec3Size * 3); |
452 | |
453 | // add triangle |
454 | int n1 = aMergeTool.AddNode (aP1.X(), aP1.Y(), aP1.Z()); |
455 | int n2 = aMergeTool.AddNode (aP2.X(), aP2.Y(), aP2.Z()); |
456 | int n3 = aMergeTool.AddNode (aP3.X(), aP3.Y(), aP3.Z()); |
457 | if (n1 != n2 && n2 != n3 && n3 != n1) |
458 | { |
459 | AddTriangle (n1, n2, n3); |
460 | } |
461 | } |
462 | |
463 | return true; |
464 | } |