0028840: Data Exchange - rewrite the STL Reader/Writer
[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     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 }