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