]>
Commit | Line | Data |
---|---|---|
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 | ||
33 | IMPLEMENT_STANDARD_RTTIEXT(RWStl_Reader, Standard_Transient) | |
34 | ||
35 | namespace | |
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 | ||
131 | Standard_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 | 188 | Standard_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 | ||
259 | static 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 | 265 | static 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 | //============================================================================== | |
286 | Standard_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 | ||
400 | Standard_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 | } |