0028840: Data Exchange - rewrite the STL Reader/Writer
[occt.git] / src / RWStl / RWStl.cxx
1 // Created on: 2017-06-13
2 // Created by: Alexander MALYSHEV
3 // Copyright (c) 2017 OPEN CASCADE SAS
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.hxx>
17
18 #include <Message_ProgressSentry.hxx>
19 #include <NCollection_Vector.hxx>
20 #include <OSD_File.hxx>
21 #include <OSD_OpenFile.hxx>
22 #include <RWStl_Reader.hxx>
23
24 namespace
25 {
26
27   static const Standard_Integer THE_STL_SIZEOF_FACET = 50;
28   static const Standard_Integer IND_THRESHOLD = 1000; // increment the indicator every 1k triangles
29
30   //! Writing a Little Endian 32 bits integer
31   inline static void convertInteger (const Standard_Integer theValue,
32                                      Standard_Character* theResult)
33   {
34     union
35     {
36       Standard_Integer i;
37       Standard_Character c[4];
38     } anUnion;
39     anUnion.i = theValue;
40
41     theResult[0] = anUnion.c[0];
42     theResult[1] = anUnion.c[1];
43     theResult[2] = anUnion.c[2];
44     theResult[3] = anUnion.c[3];
45   }
46
47   //! Writing a Little Endian 32 bits float
48   inline static void convertDouble (const Standard_Real theValue,
49                                     Standard_Character* theResult)
50   {
51     union
52     {
53       Standard_ShortReal i;
54       Standard_Character c[4];
55     } anUnion;
56     anUnion.i = (Standard_ShortReal)theValue;
57
58     theResult[0] = anUnion.c[0];
59     theResult[1] = anUnion.c[1];
60     theResult[2] = anUnion.c[2];
61     theResult[3] = anUnion.c[3];
62   }
63
64   class Reader : public RWStl_Reader
65   {
66   public:
67     //! Add new node
68     virtual Standard_Integer AddNode (const gp_XYZ& thePnt) Standard_OVERRIDE
69     {
70       myNodes.Append (thePnt);
71       return myNodes.Size();
72     }
73
74     //! Add new triangle
75     virtual void AddTriangle (Standard_Integer theNode1, Standard_Integer theNode2, Standard_Integer theNode3) Standard_OVERRIDE
76     {
77       myTriangles.Append (Poly_Triangle (theNode1, theNode2, theNode3));
78     }
79
80     //! Creates Poly_Triangulation from collected data
81     Handle(Poly_Triangulation) GetTriangulation()
82     {
83       Handle(Poly_Triangulation) aPoly = new Poly_Triangulation (myNodes.Length(), myTriangles.Length(), Standard_False);
84       for (Standard_Integer aNodeIter = 0; aNodeIter < myNodes.Size(); ++aNodeIter)
85       {
86         aPoly->ChangeNode (aNodeIter + 1) = myNodes (aNodeIter);
87       }
88
89       for (Standard_Integer aTriIter = 0; aTriIter < myTriangles.Size(); ++aTriIter)
90       {
91         aPoly->ChangeTriangle (aTriIter + 1) = myTriangles (aTriIter);
92       }
93
94       return aPoly;
95     }
96
97   private:
98     NCollection_Vector<gp_XYZ> myNodes;
99     NCollection_Vector<Poly_Triangle> myTriangles;
100   };
101
102 }
103
104 //=============================================================================
105 //function : Read
106 //purpose  :
107 //=============================================================================
108 Handle(Poly_Triangulation) RWStl::ReadFile (const Standard_CString theFile,
109                                             const Handle(Message_ProgressIndicator)& theProgress)
110 {
111   Reader aReader;
112   if (!aReader.Read (theFile, theProgress))
113   {
114     return Handle(Poly_Triangulation)();
115   }
116   return aReader.GetTriangulation();
117 }
118
119 //=============================================================================
120 //function : ReadFile
121 //purpose  :
122 //=============================================================================
123 Handle(Poly_Triangulation) RWStl::ReadFile (const OSD_Path& theFile,
124                                             const Handle(Message_ProgressIndicator)& theProgress)
125 {
126   OSD_File aFile(theFile);
127   if (!aFile.Exists())
128   {
129     return Handle(Poly_Triangulation)();
130   }
131
132   TCollection_AsciiString aPath;
133   theFile.SystemName (aPath);
134   return ReadFile (aPath.ToCString(), theProgress);
135 }
136
137 //=============================================================================
138 //function : ReadBinary
139 //purpose  :
140 //=============================================================================
141 Handle(Poly_Triangulation) RWStl::ReadBinary (const OSD_Path& theFile,
142                                               const Handle(Message_ProgressIndicator)& theProgress)
143 {
144   OSD_File aFile(theFile);
145   if (!aFile.Exists())
146   {
147     return Handle(Poly_Triangulation)();
148   }
149
150   TCollection_AsciiString aPath;
151   theFile.SystemName (aPath);
152
153   std::filebuf aBuf;
154   OSD_OpenStream (aBuf, aPath, std::ios::in | std::ios::binary);
155   if (!aBuf.is_open())
156   {
157     return Handle(Poly_Triangulation)();
158   }
159   Standard_IStream aStream (&aBuf);
160
161   Reader aReader;
162   if (!aReader.ReadBinary (aStream, theProgress))
163   {
164     return Handle(Poly_Triangulation)();
165   }
166
167   return aReader.GetTriangulation();
168 }
169
170 //=============================================================================
171 //function : ReadAscii
172 //purpose  :
173 //=============================================================================
174 Handle(Poly_Triangulation) RWStl::ReadAscii (const OSD_Path& theFile,
175                                              const Handle(Message_ProgressIndicator)& theProgress)
176 {
177   OSD_File aFile (theFile);
178   if (!aFile.Exists())
179   {
180     return Handle(Poly_Triangulation)();
181   }
182
183   TCollection_AsciiString aPath;
184   theFile.SystemName (aPath);
185
186   std::filebuf aBuf;
187   OSD_OpenStream (aBuf, aPath, std::ios::in | std::ios::binary);
188   if (!aBuf.is_open())
189   {
190     return Handle(Poly_Triangulation)();
191   }
192   Standard_IStream aStream (&aBuf);
193
194   // get length of file to feed progress indicator
195   aStream.seekg (0, aStream.end);
196   std::streampos theEnd = aStream.tellg();
197   aStream.seekg (0, aStream.beg);
198
199   Reader aReader;
200   if (!aReader.ReadAscii (aStream, theEnd, theProgress))
201   {
202     return Handle(Poly_Triangulation)();
203   }
204
205   return aReader.GetTriangulation();
206 }
207
208 //=============================================================================
209 //function : Write
210 //purpose  :
211 //=============================================================================
212 Standard_Boolean RWStl::WriteBinary (const Handle(Poly_Triangulation)& aMesh,
213                                      const OSD_Path& thePath,
214                                      const Handle(Message_ProgressIndicator)& theProgInd)
215 {
216   TCollection_AsciiString aPath;
217   thePath.SystemName (aPath);
218
219   FILE* aFile = OSD_OpenFile (aPath, "wb");
220   if (aFile == NULL)
221   {
222     return Standard_False;
223   }
224
225   Standard_Boolean isOK = writeBinary (aMesh, aFile, theProgInd);
226
227   fclose (aFile);
228   return isOK;
229 }
230
231 //=============================================================================
232 //function : Write
233 //purpose  :
234 //=============================================================================
235 Standard_Boolean RWStl::WriteAscii (const Handle(Poly_Triangulation)& theMesh,
236                                     const OSD_Path& thePath,
237                                     const Handle(Message_ProgressIndicator)& theProgInd)
238 {
239   TCollection_AsciiString aPath;
240   thePath.SystemName (aPath);
241
242   FILE* aFile = OSD_OpenFile (aPath, "w");
243   if (aFile == NULL)
244   {
245     return Standard_False;
246   }
247
248   Standard_Boolean isOK = writeASCII (theMesh, aFile, theProgInd);
249   fclose (aFile);
250   return isOK;
251 }
252
253 //=============================================================================
254 //function : writeASCII
255 //purpose  :
256 //=============================================================================
257 Standard_Boolean RWStl::writeASCII (const Handle(Poly_Triangulation)& theMesh,
258                                     FILE* theFile,
259                                     const Handle(Message_ProgressIndicator)& theProgInd)
260 {
261   // note that space after 'solid' is necessary for many systems
262   if (fwrite ("solid \n", 1, 7, theFile) != 7)
263   {
264     return Standard_False;
265   }
266
267   char aBuffer[512];
268   memset (aBuffer, 0, sizeof(aBuffer));
269
270   Message_ProgressSentry aPS (theProgInd, "Triangles", 0,
271                               theMesh->NbTriangles(), IND_THRESHOLD);
272
273   const TColgp_Array1OfPnt& aNodes = theMesh->Nodes();
274   const Poly_Array1OfTriangle& aTriangles = theMesh->Triangles();
275   const Standard_Integer NBTriangles = theMesh->NbTriangles();
276   Standard_Integer anElem[3] = {0, 0, 0};
277   for (Standard_Integer aTriIter = 1; aTriIter <= NBTriangles; ++aTriIter)
278   {
279     const Poly_Triangle& aTriangle = aTriangles (aTriIter);
280     aTriangle.Get (anElem[0], anElem[1], anElem[2]);
281
282     const gp_Pnt aP1 = aNodes (anElem[0]);
283     const gp_Pnt aP2 = aNodes (anElem[1]);
284     const gp_Pnt aP3 = aNodes (anElem[2]);
285
286     const gp_Vec aVec1 (aP1, aP2);
287     const gp_Vec aVec2 (aP1, aP3);
288     gp_Vec aVNorm = aVec1.Crossed (aVec2);
289     if (aVNorm.SquareMagnitude() > gp::Resolution())
290     {
291       aVNorm.Normalize();
292     }
293     else
294     {
295       aVNorm.SetCoord (0.0, 0.0, 0.0);
296     }
297
298     Sprintf (aBuffer,
299           " facet normal % 12e % 12e % 12e\n"
300           "   outer loop\n"
301           "     vertex % 12e % 12e % 12e\n"
302           "     vertex % 12e % 12e % 12e\n"
303           "     vertex % 12e % 12e % 12e\n"
304           "   endloop\n"
305           " endfacet\n",
306           aVNorm.X(), aVNorm.Y(), aVNorm.Z(),
307           aP1.X(), aP1.Y(), aP1.Z(),
308           aP2.X(), aP2.Y(), aP2.Z(),
309           aP3.X(), aP3.Y(), aP3.Z());
310
311     if (fprintf (theFile, "%s", aBuffer) < 0)
312     {
313       return Standard_False;
314     }
315
316     // update progress only per 1k triangles
317     if ((aTriIter % IND_THRESHOLD) == 0)
318     {
319       aPS.Next();
320     }
321   }
322
323   if (fwrite ("endsolid\n", 1, 9, theFile) != 9)
324   {
325     return Standard_False;
326   }
327
328   return Standard_True;
329 }
330
331 //=============================================================================
332 //function : writeBinary
333 //purpose  :
334 //=============================================================================
335 Standard_Boolean RWStl::writeBinary (const Handle(Poly_Triangulation)& theMesh,
336                                      FILE* theFile,
337                                      const Handle(Message_ProgressIndicator)& theProgInd)
338 {
339   char aHeader[80] = "STL Exported by OpenCASCADE [www.opencascade.com]";
340   if (fwrite (aHeader, 1, 80, theFile) != 80)
341   {
342     return Standard_False;
343   }
344
345   Message_ProgressSentry aPS (theProgInd, "Triangles", 0,
346                               theMesh->NbTriangles(), IND_THRESHOLD);
347
348   const Standard_Size aNbChunkTriangles = 4096;
349   const Standard_Size aChunkSize = aNbChunkTriangles * THE_STL_SIZEOF_FACET;
350   NCollection_Array1<Standard_Character> aData (1, aChunkSize);
351   Standard_Character* aDataChunk = &aData.ChangeFirst();
352
353   const TColgp_Array1OfPnt& aNodes = theMesh->Nodes();
354   const Poly_Array1OfTriangle& aTriangles = theMesh->Triangles();
355   const Standard_Integer aNBTriangles = theMesh->NbTriangles();
356
357   Standard_Character aConv[4];
358   convertInteger (aNBTriangles, aConv);
359   if (fwrite (aConv, 1, 4, theFile) != 4)
360   {
361     return Standard_False;
362   }
363
364   Standard_Size aByteCount = 0;
365   for (Standard_Integer aTriIter = 1; aTriIter <= aNBTriangles; ++aTriIter)
366   {
367     Standard_Integer id[3];
368     const Poly_Triangle& aTriangle = aTriangles (aTriIter);
369     aTriangle.Get (id[0], id[1], id[2]);
370
371     const gp_Pnt aP1 = aNodes (id[0]);
372     const gp_Pnt aP2 = aNodes (id[1]);
373     const gp_Pnt aP3 = aNodes (id[2]);
374
375     gp_Vec aVec1 (aP1, aP2);
376     gp_Vec aVec2 (aP1, aP3);
377     gp_Vec aVNorm = aVec1.Crossed(aVec2);
378     if (aVNorm.SquareMagnitude() > gp::Resolution())
379     {
380       aVNorm.Normalize();
381     }
382     else
383     {
384       aVNorm.SetCoord (0.0, 0.0, 0.0);
385     }
386
387     convertDouble (aVNorm.X(), &aDataChunk[aByteCount]); aByteCount += 4;
388     convertDouble (aVNorm.Y(), &aDataChunk[aByteCount]); aByteCount += 4;
389     convertDouble (aVNorm.Z(), &aDataChunk[aByteCount]); aByteCount += 4;
390
391     convertDouble (aP1.X(), &aDataChunk[aByteCount]); aByteCount += 4;
392     convertDouble (aP1.Y(), &aDataChunk[aByteCount]); aByteCount += 4;
393     convertDouble (aP1.Z(), &aDataChunk[aByteCount]); aByteCount += 4;
394
395     convertDouble (aP2.X(), &aDataChunk[aByteCount]); aByteCount += 4;
396     convertDouble (aP2.Y(), &aDataChunk[aByteCount]); aByteCount += 4;
397     convertDouble (aP2.Z(), &aDataChunk[aByteCount]); aByteCount += 4;
398
399     convertDouble (aP3.X(), &aDataChunk[aByteCount]); aByteCount += 4;
400     convertDouble (aP3.Y(), &aDataChunk[aByteCount]); aByteCount += 4;
401     convertDouble (aP3.Z(), &aDataChunk[aByteCount]); aByteCount += 4;
402
403     aDataChunk[aByteCount] = 0; aByteCount += 1;
404     aDataChunk[aByteCount] = 0; aByteCount += 1;
405
406     // Chunk is filled. Dump it to the file.
407     if (aByteCount == aChunkSize)
408     {
409       if (fwrite (aDataChunk, 1, aChunkSize, theFile) != aChunkSize)
410       {
411         return Standard_False;
412       }
413
414       aByteCount = 0;
415     }
416
417     // update progress only per 1k triangles
418     if ((aTriIter % IND_THRESHOLD) == 0)
419     {
420       aPS.Next();
421     }
422   }
423
424   // Write last part if necessary.
425   if (aByteCount != aChunkSize)
426   {
427     if (fwrite (aDataChunk, 1, aByteCount, theFile) != aByteCount)
428     {
429       return Standard_False;
430     }
431   }
432
433   return Standard_True;
434 }