2 // Author: Andrey Betenev
3 // Copyright: Open CASCADE 2016
5 // This file is part of Open CASCADE Technology software library.
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.
13 // Alternatively, this file may be used under the terms of Open CASCADE
14 // commercial license or contractual agreement.
16 #include <RWStl_Reader.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>
33 IMPLEMENT_STANDARD_RTTIEXT(RWStl_Reader, Standard_Transient)
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;
42 //! Auxiliary tool for merging nodes during STL reading.
48 MergeNodeTool (RWStl_Reader* theReader)
49 : myReader (theReader),
50 myMap (1024, new NCollection_IncAllocator (1024 * 1024))
55 int AddNode (double theX, double theY, double theZ)
57 // use existing node if found at the same point
58 gp_XYZ aPnt (theX, theY, theZ);
60 Standard_Integer anIndex = -1;
61 if (myMap.Find (aPnt, anIndex))
66 anIndex = myReader->AddNode (aPnt);
67 myMap.Bind (aPnt, anIndex);
73 static Standard_Boolean IsEqual (const gp_XYZ& thePnt1, const gp_XYZ& thePnt2)
75 return (thePnt1 - thePnt2).SquareModulus() < Precision::SquareConfusion();
78 static Standard_Integer HashCode (const gp_XYZ& thePnt, Standard_Integer theUpper)
80 return ::HashCode (thePnt.X() * M_LN10 + thePnt.Y() * M_PI + thePnt.Z() * M_E, theUpper);
84 RWStl_Reader* myReader;
85 NCollection_DataMap<gp_XYZ, Standard_Integer, MergeNodeTool> myMap;
88 //! Read a Little Endian 32 bits float
89 inline static float readStlFloat (const char* theData)
91 #if OCCT_BINARY_FILE_DO_INVERSE
92 // on big-endian platform, map values byte-per-byte
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;
104 // on little-endian platform, use plain cast
105 return *reinterpret_cast<const float*>(theData);
109 //! Read a Little Endian 32 bits float
110 inline static gp_XYZ readStlFloatVec3 (const char* theData)
112 return gp_XYZ (readStlFloat (theData),
113 readStlFloat (theData + sizeof(float)),
114 readStlFloat (theData + sizeof(float) * 2));
119 //==============================================================================
122 //==============================================================================
124 Standard_Boolean RWStl_Reader::Read (const char* theFile,
125 const Handle(Message_ProgressIndicator)& theProgress)
128 OSD_OpenStream (aBuf, theFile, std::ios::in | std::ios::binary);
131 return Standard_False;
134 Standard_IStream aStream (&aBuf);
136 // get length of file to feed progress indicator in Ascii mode
137 aStream.seekg (0, aStream.end);
138 std::streampos theEnd = aStream.tellg();
139 aStream.seekg (0, aStream.beg);
141 // binary STL files cannot be shorter than 134 bytes
142 // (80 bytes header + 4 bytes facet count + 50 bytes for one facet);
143 // thus assume files shorter than 134 as Ascii without probing
144 // (probing may bring stream to fail state if EOF is reached)
145 bool isAscii = ((size_t)theEnd < THE_STL_MIN_FILE_SIZE || IsAscii (aStream));
147 while (aStream.good())
151 if (!ReadAscii (aStream, theEnd, theProgress))
158 if (!ReadBinary (aStream, theProgress))
163 aStream >> std::ws; // skip any white spaces
165 return ! aStream.fail();
168 //==============================================================================
171 //==============================================================================
173 Standard_Boolean RWStl_Reader::IsAscii (Standard_IStream& theStream)
175 // read first 134 bytes to detect file format
176 char aBuffer[THE_STL_MIN_FILE_SIZE];
177 std::streamsize aNbRead = theStream.read (aBuffer, THE_STL_MIN_FILE_SIZE).gcount();
180 Message::DefaultMessenger()->Send ("Error: Cannot read file", Message_Fail);
184 // put back the read symbols
185 for (std::streamsize aByteIter = aNbRead; aByteIter > 0; --aByteIter)
190 // if file is shorter than size of binary file with 1 facet, it must be ascii
191 if (aNbRead < std::streamsize(THE_STL_MIN_FILE_SIZE))
196 // otherwise, detect binary format by presence of non-ascii symbols in first 128 bytes
197 // (note that binary STL file may start with the same bytes "solid " as Ascii one)
198 for (Standard_Integer aByteIter = 0; aByteIter < aNbRead; ++aByteIter)
200 if ((unsigned char )aBuffer[aByteIter] > (unsigned char )'~')
208 // adapted from Standard_CString.cxx
210 // There are a lot of *_l functions availalbe on Mac OS X - we use them
212 #elif defined(_MSC_VER)
213 // MSVCRT has equivalents with slightly different syntax
215 #define sscanf_l(theBuffer, theLocale, theFormat, ...) _sscanf_s_l(theBuffer, theFormat, theLocale, __VA_ARGS__)
217 // glibc provides only limited xlocale implementation:
218 // strtod_l/strtol_l/strtoll_l functions with explicitly specified locale
219 // and newlocale/uselocale/freelocale to switch locale within current thread only.
220 // So we switch to C locale temporarily
221 #define SAVE_TL() Standard_CLocaleSentry aLocaleSentry;
222 #define sscanf_l(theBuffer, theLocale, theFormat, ...) sscanf(theBuffer, theFormat, __VA_ARGS__)
225 // Macro to get 64-bit position of the file from streampos
226 #if defined(_MSC_VER) && _MSC_VER < 1700
227 // In MSVC 2010, cast of streampos to 64-bit int is implemented incorrectly;
228 // work-around (relevant for files larger than 4 GB) is to use internal function seekpos().
229 // Since MSVC 15.8, seekpos() is deprecated and is said to always return 0.
230 #define GETPOS(aPos) aPos.seekpos()
232 #define GETPOS(aPos) ((int64_t)aPos)
235 static inline bool str_starts_with (const char* theStr, const char* theWord, int theN)
237 while (isspace (*theStr) && *theStr != '\0') theStr++;
238 return !strncmp (theStr, theWord, theN);
241 static bool ReadVertex (const char* theStr, double& theX, double& theY, double& theZ)
243 const char *aStr = theStr;
246 while (isspace ((unsigned char)*aStr) || isalpha ((unsigned char)*aStr))
251 theX = Strtod (aStr, &aEnd);
252 theY = Strtod (aStr = aEnd, &aEnd);
253 theZ = Strtod (aStr = aEnd, &aEnd);
258 //==============================================================================
259 //function : ReadAscii
261 //==============================================================================
262 Standard_Boolean RWStl_Reader::ReadAscii (Standard_IStream& theStream,
263 const std::streampos theUntilPos,
264 const Handle(Message_ProgressIndicator)& theProgress)
266 // use method seekpos() to get true 64-bit offset to enable
267 // handling of large files (VS 2010 64-bit)
268 const int64_t aStartPos = GETPOS(theStream.tellg());
269 // Note: 1 is added to theUntilPos to be sure to read the last symbol (relevant for files without EOL at the end)
270 const int64_t aEndPos = (theUntilPos > 0 ? 1 + GETPOS(theUntilPos) : std::numeric_limits<int64_t>::max());
272 // skip header "solid ..."
273 theStream.ignore ((std::streamsize)(aEndPos - aStartPos), '\n');
276 Message::DefaultMessenger()->Send ("Error: premature end of file", Message_Fail);
280 MergeNodeTool aMergeTool (this);
281 Standard_CLocaleSentry::clocale_t aLocale = Standard_CLocaleSentry::GetCLocale();
282 (void)aLocale; // to avoid warning on GCC where it is actually not used
283 SAVE_TL() // for GCC only, set C locale globally
285 // report progress every 1 MiB of read data
286 const int aStepB = 1024 * 1024;
287 const Standard_Integer aNbSteps = 1 + Standard_Integer((GETPOS(theUntilPos) - aStartPos) / aStepB);
288 Message_ProgressSentry aPSentry (theProgress, "Reading text STL file", 0, aNbSteps, 1);
290 int64_t aProgressPos = aStartPos + aStepB;
291 const int64_t LINELEN = 1024;
293 char aLine1[LINELEN], aLine2[LINELEN], aLine3[LINELEN];
294 while (aPSentry.More())
296 if (GETPOS(theStream.tellg()) > aProgressPos)
299 aProgressPos += aStepB;
302 char facet[LINELEN], outer[LINELEN];
303 theStream.getline (facet, (std::streamsize)std::min (LINELEN, aEndPos - GETPOS(theStream.tellg()))); // "facet normal nx ny nz"
304 if (str_starts_with (facet, "endsolid", 8))
309 theStream.getline (outer, (std::streamsize)std::min (LINELEN, aEndPos - GETPOS(theStream.tellg()))); // "outer loop"
310 if (!str_starts_with (facet, "facet", 5) || !str_starts_with (outer, "outer", 5))
312 TCollection_AsciiString aStr ("Error: unexpected format of facet at line ");
314 Message::DefaultMessenger()->Send (aStr, Message_Fail);
318 theStream.getline (aLine1, (std::streamsize)std::min (LINELEN, aEndPos - GETPOS(theStream.tellg())));
319 theStream.getline (aLine2, (std::streamsize)std::min (LINELEN, aEndPos - GETPOS(theStream.tellg())));
320 theStream.getline (aLine3, (std::streamsize)std::min (LINELEN, aEndPos - GETPOS(theStream.tellg())));
322 // stop reading if end of file is reached;
323 // note that well-formatted file never ends by the vertex line
324 if (theStream.eof() || GETPOS(theStream.tellg()) >= aEndPos)
331 Message::DefaultMessenger()->Send ("Error: premature end of file", Message_Fail);
336 Standard_Real x1, y1, z1, x2, y2, z2, x3, y3, z3;
337 if (! ReadVertex (aLine1, x1, y1, z1) ||
338 ! ReadVertex (aLine2, x2, y2, z2) ||
339 ! ReadVertex (aLine3, x3, y3, z3))
341 TCollection_AsciiString aStr ("Error: cannot read vertex co-ordinates at line ");
343 Message::DefaultMessenger()->Send(aStr, Message_Fail);
348 int n1 = aMergeTool.AddNode (x1, y1, z1);
349 int n2 = aMergeTool.AddNode (x2, y2, z2);
350 int n3 = aMergeTool.AddNode (x3, y3, z3);
351 if (n1 != n2 && n2 != n3 && n3 != n1)
353 AddTriangle (n1, n2, n3);
356 theStream.ignore ((std::streamsize)(aEndPos - GETPOS(theStream.tellg())), '\n'); // skip "endloop"
357 theStream.ignore ((std::streamsize)(aEndPos - GETPOS(theStream.tellg())), '\n'); // skip "endfacet"
362 return aPSentry.More();
365 //==============================================================================
366 //function : readStlBinary
368 //==============================================================================
370 Standard_Boolean RWStl_Reader::ReadBinary (Standard_IStream& theStream,
371 const Handle(Message_ProgressIndicator)& theProgress)
374 // the size of the file (minus the header size)
375 // must be a multiple of SIZEOF_STL_FACET
376 if ((theFileLen - THE_STL_HEADER_SIZE) % THE_STL_SIZEOF_FACET != 0
377 || (theFileLen < THE_STL_MIN_FILE_SIZE))
379 Message::DefaultMessenger()->Send ("Error: Corrupted binary STL file (inconsistent file size)!", Message_Fail);
380 return Standard_False;
382 const Standard_Integer aNbFacets = Standard_Integer((theFileLen - THE_STL_HEADER_SIZE) / THE_STL_SIZEOF_FACET);
385 // read file header at first
386 char aHeader[THE_STL_HEADER_SIZE + 1];
387 if (theStream.read (aHeader, THE_STL_HEADER_SIZE).gcount() != std::streamsize(THE_STL_HEADER_SIZE))
389 Message::DefaultMessenger()->Send ("Error: Corrupted binary STL file!", Message_Fail);
393 // number of facets is stored as 32-bit integer at position 80
394 const Standard_Integer aNbFacets = *(int32_t*)(aHeader + 80);
396 MergeNodeTool aMergeTool (this);
398 // don't trust the number of triangles which is coded in the file
399 // sometimes it is wrong, and with this technique we don't need to swap endians for integer
400 Message_ProgressSentry aPSentry (theProgress, "Reading binary STL file", 0, aNbFacets, 1);
401 Standard_Integer aNbRead = 0;
403 // allocate buffer for 80 triangles
404 const int THE_CHUNK_NBFACETS = 80;
405 char aBuffer[THE_STL_SIZEOF_FACET * THE_CHUNK_NBFACETS];
407 // normal + 3 nodes + 2 extra bytes
408 const size_t aVec3Size = sizeof(float) * 3;
409 const size_t aFaceDataLen = aVec3Size * 4 + 2;
410 const char* aBufferPtr = aBuffer;
411 Standard_Integer aNbFacesInBuffer = 0;
412 for (Standard_Integer aNbFacetRead = 0; aNbFacetRead < aNbFacets && aPSentry.More();
413 ++aNbFacetRead, ++aNbRead, --aNbFacesInBuffer, aBufferPtr += aFaceDataLen, aPSentry.Next())
416 if (aNbFacesInBuffer <= 0)
418 aNbFacesInBuffer = Min (THE_CHUNK_NBFACETS, aNbFacets - aNbFacetRead);
419 const std::streamsize aDataToRead = aNbFacesInBuffer * aFaceDataLen;
420 if (theStream.read (aBuffer, aDataToRead).gcount() != aDataToRead)
422 Message::DefaultMessenger()->Send ("Error: binary STL read failed", Message_Fail);
425 aBufferPtr = aBuffer;
428 // get points from buffer
429 // readStlFloatVec3 (aBufferPtr); // skip normal
430 gp_XYZ aP1 = readStlFloatVec3 (aBufferPtr + aVec3Size);
431 gp_XYZ aP2 = readStlFloatVec3 (aBufferPtr + aVec3Size * 2);
432 gp_XYZ aP3 = readStlFloatVec3 (aBufferPtr + aVec3Size * 3);
435 int n1 = aMergeTool.AddNode (aP1.X(), aP1.Y(), aP1.Z());
436 int n2 = aMergeTool.AddNode (aP2.X(), aP2.Y(), aP2.Z());
437 int n3 = aMergeTool.AddNode (aP3.X(), aP3.Y(), aP3.Z());
438 if (n1 != n2 && n2 != n3 && n3 != n1)
440 AddTriangle (n1, n2, n3);