]> OCCT Git - occt.git/blame - src/RWStl/RWStl_Reader.cxx
0031939: Coding - correction of spelling errors in comments [part 10]
[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>
7e785937 21#include <Message_ProgressScope.hxx>
4178b353 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
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
131Standard_Boolean RWStl_Reader::Read (const char* theFile,
7e785937 132 const Message_ProgressRange& theProgress)
4178b353 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)
97454ee0 152 bool isAscii = ((size_t)theEnd < THE_STL_MIN_FILE_SIZE || IsAscii (aStream, true));
22e70738 153
c37bd936 154 Standard_ReadLineBuffer aBuffer (THE_BUFFER_SIZE);
155
7e785937 156 // Note: here we are trying to handle rare but realistic case of
157 // STL files which are composed of several STL data blocks
158 // running translation in cycle.
159 // For this reason use infinite (logarithmic) progress scale,
160 // but in special mode so that the first cycle will take ~ 70% of it
161 Message_ProgressScope aPS (theProgress, NULL, 1, true);
22e70738 162 while (aStream.good())
4178b353 163 {
22e70738 164 if (isAscii)
1d949423 165 {
7e785937 166 if (!ReadAscii (aStream, aBuffer, theEnd, aPS.Next(2)))
1d949423 167 {
168 break;
169 }
170 }
171 else
172 {
7e785937 173 if (!ReadBinary (aStream, aPS.Next(2)))
1d949423 174 {
175 break;
176 }
177 }
178 aStream >> std::ws; // skip any white spaces
4178b353 179 }
22e70738 180 return ! aStream.fail();
4178b353 181}
182
183//==============================================================================
184//function : IsAscii
185//purpose :
186//==============================================================================
187
97454ee0 188Standard_Boolean RWStl_Reader::IsAscii (Standard_IStream& theStream,
189 const bool isSeekgAvailable)
4178b353 190{
191 // read first 134 bytes to detect file format
192 char aBuffer[THE_STL_MIN_FILE_SIZE];
193 std::streamsize aNbRead = theStream.read (aBuffer, THE_STL_MIN_FILE_SIZE).gcount();
22e70738 194 if (! theStream)
4178b353 195 {
a87b1b37 196 Message::SendFail ("Error: Cannot read file");
22e70738 197 return true;
4178b353 198 }
199
97454ee0 200 if (isSeekgAvailable)
4178b353 201 {
97454ee0 202 // get back to the beginning
203 theStream.seekg(0, theStream.beg);
204 }
205 else
206 {
207 // put back the read symbols
208 for (std::streamsize aByteIter = aNbRead; aByteIter > 0; --aByteIter)
209 {
210 theStream.unget();
211 }
4178b353 212 }
213
214 // if file is shorter than size of binary file with 1 facet, it must be ascii
215 if (aNbRead < std::streamsize(THE_STL_MIN_FILE_SIZE))
216 {
217 return true;
218 }
219
220 // otherwise, detect binary format by presence of non-ascii symbols in first 128 bytes
221 // (note that binary STL file may start with the same bytes "solid " as Ascii one)
222 for (Standard_Integer aByteIter = 0; aByteIter < aNbRead; ++aByteIter)
223 {
224 if ((unsigned char )aBuffer[aByteIter] > (unsigned char )'~')
225 {
226 return false;
227 }
228 }
229 return true;
230}
231
232// adapted from Standard_CString.cxx
233#ifdef __APPLE__
316ea293 234 // There are a lot of *_l functions available on Mac OS X - we use them
4178b353 235 #define SAVE_TL()
236#elif defined(_MSC_VER)
237 // MSVCRT has equivalents with slightly different syntax
238 #define SAVE_TL()
239 #define sscanf_l(theBuffer, theLocale, theFormat, ...) _sscanf_s_l(theBuffer, theFormat, theLocale, __VA_ARGS__)
240#else
241 // glibc provides only limited xlocale implementation:
242 // strtod_l/strtol_l/strtoll_l functions with explicitly specified locale
243 // and newlocale/uselocale/freelocale to switch locale within current thread only.
244 // So we switch to C locale temporarily
245 #define SAVE_TL() Standard_CLocaleSentry aLocaleSentry;
246 #define sscanf_l(theBuffer, theLocale, theFormat, ...) sscanf(theBuffer, theFormat, __VA_ARGS__)
247#endif
248
04232180 249// Macro to get 64-bit position of the file from std::streampos
6b1800cb 250#if defined(_MSC_VER) && _MSC_VER < 1700
04232180 251 // In MSVC 2010, cast of std::streampos to 64-bit int is implemented incorrectly;
6b1800cb 252 // work-around (relevant for files larger than 4 GB) is to use internal function seekpos().
253 // Since MSVC 15.8, seekpos() is deprecated and is said to always return 0.
4178b353 254 #define GETPOS(aPos) aPos.seekpos()
255#else
256 #define GETPOS(aPos) ((int64_t)aPos)
257#endif
258
259static inline bool str_starts_with (const char* theStr, const char* theWord, int theN)
260{
261 while (isspace (*theStr) && *theStr != '\0') theStr++;
ff6122e0 262 return !strncasecmp (theStr, theWord, theN);
4178b353 263}
264
07bbde45 265static bool ReadVertex (const char* theStr, double& theX, double& theY, double& theZ)
266{
267 const char *aStr = theStr;
268
269 // skip 'vertex'
270 while (isspace ((unsigned char)*aStr) || isalpha ((unsigned char)*aStr))
271 ++aStr;
272
273 // read values
274 char *aEnd;
275 theX = Strtod (aStr, &aEnd);
276 theY = Strtod (aStr = aEnd, &aEnd);
277 theZ = Strtod (aStr = aEnd, &aEnd);
278
279 return aEnd != aStr;
280}
281
4178b353 282//==============================================================================
283//function : ReadAscii
284//purpose :
285//==============================================================================
286Standard_Boolean RWStl_Reader::ReadAscii (Standard_IStream& theStream,
c37bd936 287 Standard_ReadLineBuffer& theBuffer,
4178b353 288 const std::streampos theUntilPos,
7e785937 289 const Message_ProgressRange& theProgress)
4178b353 290{
291 // use method seekpos() to get true 64-bit offset to enable
292 // handling of large files (VS 2010 64-bit)
293 const int64_t aStartPos = GETPOS(theStream.tellg());
51ee6a7d 294 size_t aLineLen = 0;
295 const char* aLine;
4178b353 296
297 // skip header "solid ..."
c37bd936 298 aLine = theBuffer.ReadLine (theStream, aLineLen);
51ee6a7d 299 if (aLine == NULL)
4178b353 300 {
a87b1b37 301 Message::SendFail ("Error: premature end of file");
4178b353 302 return false;
303 }
304
305 MergeNodeTool aMergeTool (this);
306 Standard_CLocaleSentry::clocale_t aLocale = Standard_CLocaleSentry::GetCLocale();
307 (void)aLocale; // to avoid warning on GCC where it is actually not used
308 SAVE_TL() // for GCC only, set C locale globally
309
310 // report progress every 1 MiB of read data
311 const int aStepB = 1024 * 1024;
71c810df 312 const Standard_Integer aNbSteps = 1 + Standard_Integer((GETPOS(theUntilPos) - aStartPos) / aStepB);
7e785937 313 Message_ProgressScope aPS (theProgress, "Reading text STL file", aNbSteps);
4178b353 314 int64_t aProgressPos = aStartPos + aStepB;
4178b353 315 int aNbLine = 1;
51ee6a7d 316
7e785937 317 while (aPS.More())
4178b353 318 {
319 if (GETPOS(theStream.tellg()) > aProgressPos)
320 {
7e785937 321 aPS.Next();
4178b353 322 aProgressPos += aStepB;
323 }
324
c37bd936 325 aLine = theBuffer.ReadLine (theStream, aLineLen); // "facet normal nx ny nz"
51ee6a7d 326 if (aLine == NULL)
327 {
a87b1b37 328 Message::SendFail ("Error: premature end of file");
51ee6a7d 329 return false;
330 }
331 if (str_starts_with (aLine, "endsolid", 8))
4178b353 332 {
333 // end of STL code
334 break;
335 }
51ee6a7d 336 if (!str_starts_with (aLine, "facet", 5))
4178b353 337 {
a87b1b37 338 Message::SendFail (TCollection_AsciiString ("Error: unexpected format of facet at line ") + (aNbLine + 1));
4178b353 339 return false;
340 }
341
c37bd936 342 aLine = theBuffer.ReadLine (theStream, aLineLen); // "outer loop"
51ee6a7d 343 if (aLine == NULL || !str_starts_with (aLine, "outer", 5))
4178b353 344 {
a87b1b37 345 Message::SendFail (TCollection_AsciiString ("Error: unexpected format of facet at line ") + (aNbLine + 1));
51ee6a7d 346 return false;
4178b353 347 }
348
51ee6a7d 349 gp_XYZ aVertex[3];
350 Standard_Boolean isEOF = false;
351 for (Standard_Integer i = 0; i < 3; i++)
4178b353 352 {
c37bd936 353 aLine = theBuffer.ReadLine (theStream, aLineLen);
51ee6a7d 354 if (aLine == NULL)
355 {
356 isEOF = true;
357 break;
358 }
359 gp_XYZ aReadVertex;
360 if (!ReadVertex (aLine, aReadVertex.ChangeCoord (1), aReadVertex.ChangeCoord (2), aReadVertex.ChangeCoord (3)))
361 {
316ea293 362 Message::SendFail (TCollection_AsciiString ("Error: cannot read vertex coordinates at line ") + aNbLine);
51ee6a7d 363 return false;
364 }
365 aVertex[i] = aReadVertex;
4178b353 366 }
4178b353 367
51ee6a7d 368 // stop reading if end of file is reached;
369 // note that well-formatted file never ends by the vertex line
370 if (isEOF)
4178b353 371 {
51ee6a7d 372 break;
4178b353 373 }
374
51ee6a7d 375 aNbLine += 5;
376
4178b353 377 // add triangle
51ee6a7d 378 int n1 = aMergeTool.AddNode (aVertex[0].X(), aVertex[0].Y(), aVertex[0].Z());
379 int n2 = aMergeTool.AddNode (aVertex[1].X(), aVertex[1].Y(), aVertex[1].Z());
380 int n3 = aMergeTool.AddNode (aVertex[2].X(), aVertex[2].Y(), aVertex[2].Z());
4178b353 381 if (n1 != n2 && n2 != n3 && n3 != n1)
382 {
383 AddTriangle (n1, n2, n3);
384 }
385
c37bd936 386 theBuffer.ReadLine (theStream, aLineLen); // skip "endloop"
387 theBuffer.ReadLine (theStream, aLineLen); // skip "endfacet"
4178b353 388
389 aNbLine += 2;
390 }
391
7e785937 392 return aPS.More();
4178b353 393}
394
395//==============================================================================
396//function : readStlBinary
397//purpose :
398//==============================================================================
399
400Standard_Boolean RWStl_Reader::ReadBinary (Standard_IStream& theStream,
7e785937 401 const Message_ProgressRange& theProgress)
4178b353 402{
403/*
404 // the size of the file (minus the header size)
405 // must be a multiple of SIZEOF_STL_FACET
406 if ((theFileLen - THE_STL_HEADER_SIZE) % THE_STL_SIZEOF_FACET != 0
407 || (theFileLen < THE_STL_MIN_FILE_SIZE))
408 {
a87b1b37 409 Message::SendFail ("Error: Corrupted binary STL file (inconsistent file size)");
4178b353 410 return Standard_False;
411 }
412 const Standard_Integer aNbFacets = Standard_Integer((theFileLen - THE_STL_HEADER_SIZE) / THE_STL_SIZEOF_FACET);
413*/
414
415 // read file header at first
416 char aHeader[THE_STL_HEADER_SIZE + 1];
417 if (theStream.read (aHeader, THE_STL_HEADER_SIZE).gcount() != std::streamsize(THE_STL_HEADER_SIZE))
418 {
a87b1b37 419 Message::SendFail ("Error: Corrupted binary STL file");
4178b353 420 return false;
421 }
422
423 // number of facets is stored as 32-bit integer at position 80
424 const Standard_Integer aNbFacets = *(int32_t*)(aHeader + 80);
425
426 MergeNodeTool aMergeTool (this);
427
428 // don't trust the number of triangles which is coded in the file
429 // sometimes it is wrong, and with this technique we don't need to swap endians for integer
7e785937 430 Message_ProgressScope aPS (theProgress, "Reading binary STL file", aNbFacets);
4178b353 431 Standard_Integer aNbRead = 0;
432
433 // allocate buffer for 80 triangles
434 const int THE_CHUNK_NBFACETS = 80;
435 char aBuffer[THE_STL_SIZEOF_FACET * THE_CHUNK_NBFACETS];
436
437 // normal + 3 nodes + 2 extra bytes
438 const size_t aVec3Size = sizeof(float) * 3;
439 const size_t aFaceDataLen = aVec3Size * 4 + 2;
440 const char* aBufferPtr = aBuffer;
441 Standard_Integer aNbFacesInBuffer = 0;
7e785937 442 for (Standard_Integer aNbFacetRead = 0; aNbFacetRead < aNbFacets && aPS.More();
443 ++aNbFacetRead, ++aNbRead, --aNbFacesInBuffer, aBufferPtr += aFaceDataLen, aPS.Next())
4178b353 444 {
445 // read more data
446 if (aNbFacesInBuffer <= 0)
447 {
448 aNbFacesInBuffer = Min (THE_CHUNK_NBFACETS, aNbFacets - aNbFacetRead);
449 const std::streamsize aDataToRead = aNbFacesInBuffer * aFaceDataLen;
450 if (theStream.read (aBuffer, aDataToRead).gcount() != aDataToRead)
451 {
a87b1b37 452 Message::SendFail ("Error: binary STL read failed");
4178b353 453 return false;
454 }
455 aBufferPtr = aBuffer;
456 }
457
458 // get points from buffer
459// readStlFloatVec3 (aBufferPtr); // skip normal
460 gp_XYZ aP1 = readStlFloatVec3 (aBufferPtr + aVec3Size);
461 gp_XYZ aP2 = readStlFloatVec3 (aBufferPtr + aVec3Size * 2);
462 gp_XYZ aP3 = readStlFloatVec3 (aBufferPtr + aVec3Size * 3);
463
464 // add triangle
465 int n1 = aMergeTool.AddNode (aP1.X(), aP1.Y(), aP1.Z());
466 int n2 = aMergeTool.AddNode (aP2.X(), aP2.Y(), aP2.Z());
467 int n3 = aMergeTool.AddNode (aP3.X(), aP3.Y(), aP3.Z());
468 if (n1 != n2 && n2 != n3 && n3 != n1)
469 {
470 AddTriangle (n1, n2, n3);
471 }
472 }
473
7e785937 474 return aPS.More();
4178b353 475}