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