0029007: Visualization, AIS_InteractiveContext - the method for accessing Detected...
[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
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
124Standard_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);
1d949423 135
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);
140
141 while (!aStream.eof() && !aStream.bad())
4178b353 142 {
1d949423 143 if (IsAscii (aStream))
144 {
145 if (!ReadAscii (aStream, theEnd, theProgress))
146 {
147 break;
148 }
149 }
150 else
151 {
152 if (!ReadBinary (aStream, theProgress))
153 {
154 break;
155 }
156 }
157 aStream >> std::ws; // skip any white spaces
4178b353 158 }
1d949423 159 return !aStream.bad();
4178b353 160}
161
162//==============================================================================
163//function : IsAscii
164//purpose :
165//==============================================================================
166
167Standard_Boolean RWStl_Reader::IsAscii (Standard_IStream& theStream)
168{
169 // read first 134 bytes to detect file format
170 char aBuffer[THE_STL_MIN_FILE_SIZE];
171 std::streamsize aNbRead = theStream.read (aBuffer, THE_STL_MIN_FILE_SIZE).gcount();
172 if (!theStream)
173 {
174 Message::DefaultMessenger()->Send ("Error: Cannot read file", Message_Fail);
175 return false;
176 }
177
178 // put back the read symbols
179 for (std::streamsize aByteIter = aNbRead; aByteIter > 0; --aByteIter)
180 {
181 theStream.unget();
182 }
183
184 // if file is shorter than size of binary file with 1 facet, it must be ascii
185 if (aNbRead < std::streamsize(THE_STL_MIN_FILE_SIZE))
186 {
187 return true;
188 }
189
190 // otherwise, detect binary format by presence of non-ascii symbols in first 128 bytes
191 // (note that binary STL file may start with the same bytes "solid " as Ascii one)
192 for (Standard_Integer aByteIter = 0; aByteIter < aNbRead; ++aByteIter)
193 {
194 if ((unsigned char )aBuffer[aByteIter] > (unsigned char )'~')
195 {
196 return false;
197 }
198 }
199 return true;
200}
201
202// adapted from Standard_CString.cxx
203#ifdef __APPLE__
204 // There are a lot of *_l functions availalbe on Mac OS X - we use them
205 #define SAVE_TL()
206#elif defined(_MSC_VER)
207 // MSVCRT has equivalents with slightly different syntax
208 #define SAVE_TL()
209 #define sscanf_l(theBuffer, theLocale, theFormat, ...) _sscanf_s_l(theBuffer, theFormat, theLocale, __VA_ARGS__)
210#else
211 // glibc provides only limited xlocale implementation:
212 // strtod_l/strtol_l/strtoll_l functions with explicitly specified locale
213 // and newlocale/uselocale/freelocale to switch locale within current thread only.
214 // So we switch to C locale temporarily
215 #define SAVE_TL() Standard_CLocaleSentry aLocaleSentry;
216 #define sscanf_l(theBuffer, theLocale, theFormat, ...) sscanf(theBuffer, theFormat, __VA_ARGS__)
217#endif
218
219// Macro to get 64-bit position of the file from streampos
220#if defined(_MSC_VER)
221 #define GETPOS(aPos) aPos.seekpos()
222#else
223 #define GETPOS(aPos) ((int64_t)aPos)
224#endif
225
226static inline bool str_starts_with (const char* theStr, const char* theWord, int theN)
227{
228 while (isspace (*theStr) && *theStr != '\0') theStr++;
229 return !strncmp (theStr, theWord, theN);
230}
231
232//==============================================================================
233//function : ReadAscii
234//purpose :
235//==============================================================================
236Standard_Boolean RWStl_Reader::ReadAscii (Standard_IStream& theStream,
237 const std::streampos theUntilPos,
238 const Handle(Message_ProgressIndicator)& theProgress)
239{
240 // use method seekpos() to get true 64-bit offset to enable
241 // handling of large files (VS 2010 64-bit)
242 const int64_t aStartPos = GETPOS(theStream.tellg());
243 const int64_t aEndPos = (theUntilPos > 0 ? GETPOS(theUntilPos) : std::numeric_limits<int64_t>::max());
244
245 // skip header "solid ..."
246 theStream.ignore (aEndPos - aStartPos, '\n');
247 if (!theStream)
248 {
249 Message::DefaultMessenger()->Send ("Error: premature end of file", Message_Fail);
250 return false;
251 }
252
253 MergeNodeTool aMergeTool (this);
254 Standard_CLocaleSentry::clocale_t aLocale = Standard_CLocaleSentry::GetCLocale();
255 (void)aLocale; // to avoid warning on GCC where it is actually not used
256 SAVE_TL() // for GCC only, set C locale globally
257
258 // report progress every 1 MiB of read data
259 const int aStepB = 1024 * 1024;
260 const Standard_Integer aNbSteps = 1 + Standard_Integer((theUntilPos - aStartPos) / aStepB);
261 Message_ProgressSentry aPSentry (theProgress, "Reading text STL file", 0, aNbSteps, 1);
262
263 int64_t aProgressPos = aStartPos + aStepB;
264 const int64_t LINELEN = 1024;
265 int aNbLine = 1;
266 char aLine1[LINELEN], aLine2[LINELEN], aLine3[LINELEN];
267 while (aPSentry.More())
268 {
269 if (GETPOS(theStream.tellg()) > aProgressPos)
270 {
271 aPSentry.Next();
272 aProgressPos += aStepB;
273 }
274
275 char facet[LINELEN], outer[LINELEN];
276 theStream.getline (facet, std::min (LINELEN, aEndPos - GETPOS(theStream.tellg()))); // "facet normal nx ny nz"
277 if (str_starts_with (facet, "endsolid", 8))
278 {
279 // end of STL code
280 break;
281 }
282 theStream.getline (outer, std::min (LINELEN, aEndPos - GETPOS(theStream.tellg()))); // "outer loop"
283 if (!str_starts_with (facet, "facet", 5) || !str_starts_with (outer, "outer", 5))
284 {
285 TCollection_AsciiString aStr ("Error: unexpected format of facet at line ");
286 aStr += aNbLine + 1;
287 Message::DefaultMessenger()->Send (aStr, Message_Fail);
288 return false;
289 }
290
291 theStream.getline (aLine1, std::min (LINELEN, aEndPos - GETPOS(theStream.tellg())));
292 theStream.getline (aLine2, std::min (LINELEN, aEndPos - GETPOS(theStream.tellg())));
293 theStream.getline (aLine3, std::min (LINELEN, aEndPos - GETPOS(theStream.tellg())));
294
295 // stop reading if end of file is reached;
296 // note that well-formatted file never ends by the vertex line
297 if (theStream.eof() || GETPOS(theStream.tellg()) >= aEndPos)
298 {
299 break;
300 }
301
302 if (!theStream)
303 {
304 Message::DefaultMessenger()->Send ("Error: premature end of file", Message_Fail);
305 return false;
306 }
307 aNbLine += 5;
308
309 Standard_Real x1, y1, z1, x2, y2, z2, x3, y3, z3;
310 Standard_Integer aReadCount = // read 3 lines "vertex x y z"
311 sscanf_l (aLine1, aLocale, "%*s %lf %lf %lf", &x1, &y1, &z1) +
312 sscanf_l (aLine2, aLocale, "%*s %lf %lf %lf", &x2, &y2, &z2) +
313 sscanf_l (aLine3, aLocale, "%*s %lf %lf %lf", &x3, &y3, &z3);
314 if (aReadCount != 9)
315 {
316 TCollection_AsciiString aStr ("Error: cannot read vertex co-ordinates at line ");
317 aStr += aNbLine;
318 Message::DefaultMessenger()->Send(aStr, Message_Fail);
319 return false;
320 }
321
322 // add triangle
323 int n1 = aMergeTool.AddNode (x1, y1, z1);
324 int n2 = aMergeTool.AddNode (x2, y2, z2);
325 int n3 = aMergeTool.AddNode (x3, y3, z3);
326 if (n1 != n2 && n2 != n3 && n3 != n1)
327 {
328 AddTriangle (n1, n2, n3);
329 }
330
331 theStream.ignore (aEndPos - GETPOS(theStream.tellg()), '\n'); // skip "endloop"
332 theStream.ignore (aEndPos - GETPOS(theStream.tellg()), '\n'); // skip "endfacet"
333
334 aNbLine += 2;
335 }
336
337 return aPSentry.More();
338}
339
340//==============================================================================
341//function : readStlBinary
342//purpose :
343//==============================================================================
344
345Standard_Boolean RWStl_Reader::ReadBinary (Standard_IStream& theStream,
346 const Handle(Message_ProgressIndicator)& theProgress)
347{
348/*
349 // the size of the file (minus the header size)
350 // must be a multiple of SIZEOF_STL_FACET
351 if ((theFileLen - THE_STL_HEADER_SIZE) % THE_STL_SIZEOF_FACET != 0
352 || (theFileLen < THE_STL_MIN_FILE_SIZE))
353 {
354 Message::DefaultMessenger()->Send ("Error: Corrupted binary STL file (inconsistent file size)!", Message_Fail);
355 return Standard_False;
356 }
357 const Standard_Integer aNbFacets = Standard_Integer((theFileLen - THE_STL_HEADER_SIZE) / THE_STL_SIZEOF_FACET);
358*/
359
360 // read file header at first
361 char aHeader[THE_STL_HEADER_SIZE + 1];
362 if (theStream.read (aHeader, THE_STL_HEADER_SIZE).gcount() != std::streamsize(THE_STL_HEADER_SIZE))
363 {
364 Message::DefaultMessenger()->Send ("Error: Corrupted binary STL file!", Message_Fail);
365 return false;
366 }
367
368 // number of facets is stored as 32-bit integer at position 80
369 const Standard_Integer aNbFacets = *(int32_t*)(aHeader + 80);
370
371 MergeNodeTool aMergeTool (this);
372
373 // don't trust the number of triangles which is coded in the file
374 // sometimes it is wrong, and with this technique we don't need to swap endians for integer
375 Message_ProgressSentry aPSentry (theProgress, "Reading binary STL file", 0, aNbFacets, 1);
376 Standard_Integer aNbRead = 0;
377
378 // allocate buffer for 80 triangles
379 const int THE_CHUNK_NBFACETS = 80;
380 char aBuffer[THE_STL_SIZEOF_FACET * THE_CHUNK_NBFACETS];
381
382 // normal + 3 nodes + 2 extra bytes
383 const size_t aVec3Size = sizeof(float) * 3;
384 const size_t aFaceDataLen = aVec3Size * 4 + 2;
385 const char* aBufferPtr = aBuffer;
386 Standard_Integer aNbFacesInBuffer = 0;
387 for (Standard_Integer aNbFacetRead = 0; aNbFacetRead < aNbFacets && aPSentry.More();
388 ++aNbFacetRead, ++aNbRead, --aNbFacesInBuffer, aBufferPtr += aFaceDataLen, aPSentry.Next())
389 {
390 // read more data
391 if (aNbFacesInBuffer <= 0)
392 {
393 aNbFacesInBuffer = Min (THE_CHUNK_NBFACETS, aNbFacets - aNbFacetRead);
394 const std::streamsize aDataToRead = aNbFacesInBuffer * aFaceDataLen;
395 if (theStream.read (aBuffer, aDataToRead).gcount() != aDataToRead)
396 {
397 Message::DefaultMessenger()->Send ("Error: read filed", Message_Fail);
398 return false;
399 }
400 aBufferPtr = aBuffer;
401 }
402
403 // get points from buffer
404// readStlFloatVec3 (aBufferPtr); // skip normal
405 gp_XYZ aP1 = readStlFloatVec3 (aBufferPtr + aVec3Size);
406 gp_XYZ aP2 = readStlFloatVec3 (aBufferPtr + aVec3Size * 2);
407 gp_XYZ aP3 = readStlFloatVec3 (aBufferPtr + aVec3Size * 3);
408
409 // add triangle
410 int n1 = aMergeTool.AddNode (aP1.X(), aP1.Y(), aP1.Z());
411 int n2 = aMergeTool.AddNode (aP2.X(), aP2.Y(), aP2.Z());
412 int n3 = aMergeTool.AddNode (aP3.X(), aP3.Y(), aP3.Z());
413 if (n1 != n2 && n2 != n3 && n3 != n1)
414 {
415 AddTriangle (n1, n2, n3);
416 }
417 }
418
419 return true;
420}