0031015: Visualization, TKOpenGl - OpenGl_WorldViewState does not cache inverted...
[occt.git] / src / RWStl / RWStl_Reader.cxx
CommitLineData
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
33IMPLEMENT_STANDARD_RTTIEXT(RWStl_Reader, Standard_Transient)
34
35namespace
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
2b2be3fb 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)
4178b353 83 {
2b2be3fb 84 return ::HashCode (thePoint.X() * M_LN10 + thePoint.Y() * M_PI + thePoint.Z() * M_E, theUpperBound);
4178b353 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
128Standard_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);
1d949423 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
22e70738 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())
4178b353 152 {
22e70738 153 if (isAscii)
1d949423 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
4178b353 168 }
22e70738 169 return ! aStream.fail();
4178b353 170}
171
172//==============================================================================
173//function : IsAscii
174//purpose :
175//==============================================================================
176
177Standard_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();
22e70738 182 if (! theStream)
4178b353 183 {
184 Message::DefaultMessenger()->Send ("Error: Cannot read file", Message_Fail);
22e70738 185 return true;
4178b353 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
04232180 229// Macro to get 64-bit position of the file from std::streampos
6b1800cb 230#if defined(_MSC_VER) && _MSC_VER < 1700
04232180 231 // In MSVC 2010, cast of std::streampos to 64-bit int is implemented incorrectly;
6b1800cb 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.
4178b353 234 #define GETPOS(aPos) aPos.seekpos()
235#else
236 #define GETPOS(aPos) ((int64_t)aPos)
237#endif
238
d45b7860 239# if defined(_MSC_VER) && ! defined(strncasecmp)
240# define strncasecmp _strnicmp
241# endif
242
4178b353 243static inline bool str_starts_with (const char* theStr, const char* theWord, int theN)
244{
245 while (isspace (*theStr) && *theStr != '\0') theStr++;
d45b7860 246 return !strncasecmp (theStr, theWord, theN);
4178b353 247}
248
07bbde45 249static 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
4178b353 266//==============================================================================
267//function : ReadAscii
268//purpose :
269//==============================================================================
270Standard_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());
22e70738 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());
4178b353 279
280 // skip header "solid ..."
71c810df 281 theStream.ignore ((std::streamsize)(aEndPos - aStartPos), '\n');
4178b353 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;
71c810df 295 const Standard_Integer aNbSteps = 1 + Standard_Integer((GETPOS(theUntilPos) - aStartPos) / aStepB);
4178b353 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];
71c810df 311 theStream.getline (facet, (std::streamsize)std::min (LINELEN, aEndPos - GETPOS(theStream.tellg()))); // "facet normal nx ny nz"
4178b353 312 if (str_starts_with (facet, "endsolid", 8))
313 {
314 // end of STL code
315 break;
316 }
71c810df 317 theStream.getline (outer, (std::streamsize)std::min (LINELEN, aEndPos - GETPOS(theStream.tellg()))); // "outer loop"
4178b353 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
71c810df 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())));
4178b353 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;
07bbde45 345 if (! ReadVertex (aLine1, x1, y1, z1) ||
346 ! ReadVertex (aLine2, x2, y2, z2) ||
347 ! ReadVertex (aLine3, x3, y3, z3))
4178b353 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
71c810df 364 theStream.ignore ((std::streamsize)(aEndPos - GETPOS(theStream.tellg())), '\n'); // skip "endloop"
365 theStream.ignore ((std::streamsize)(aEndPos - GETPOS(theStream.tellg())), '\n'); // skip "endfacet"
4178b353 366
367 aNbLine += 2;
368 }
369
370 return aPSentry.More();
371}
372
373//==============================================================================
374//function : readStlBinary
375//purpose :
376//==============================================================================
377
378Standard_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 {
4c4420df 430 Message::DefaultMessenger()->Send ("Error: binary STL read failed", Message_Fail);
4178b353 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}