0027622: Data Exchange - STL file having less than 4 triangles cannot be read
[occt.git] / src / RWStl / RWStl.cxx
1 // Created on: 1994-10-13
2 // Created by: Marc LEGAY
3 // Copyright (c) 1994-1999 Matra Datavision
4 // Copyright (c) 1999-2014 OPEN CASCADE SAS
5 //
6 // This file is part of Open CASCADE Technology software library.
7 //
8 // This library is free software; you can redistribute it and/or modify it under
9 // the terms of the GNU Lesser General Public License version 2.1 as published
10 // by the Free Software Foundation, with special exception defined in the file
11 // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
12 // distribution for complete text of the license and disclaimer of any warranty.
13 //
14 // Alternatively, this file may be used under the terms of Open CASCADE
15 // commercial license or contractual agreement.
16
17
18 #include <BRepBuilderAPI_CellFilter.hxx>
19 #include <BRepBuilderAPI_VertexInspector.hxx>
20 #include <gp.hxx>
21 #include <gp_Vec.hxx>
22 #include <gp_XYZ.hxx>
23 #include <Message.hxx>
24 #include <Message_Messenger.hxx>
25 #include <Message_ProgressIndicator.hxx>
26 #include <Message_ProgressSentry.hxx>
27 #include <OSD.hxx>
28 #include <OSD_File.hxx>
29 #include <OSD_Host.hxx>
30 #include <OSD_OpenFile.hxx>
31 #include <OSD_Path.hxx>
32 #include <OSD_Protection.hxx>
33 #include <Precision.hxx>
34 #include <RWStl.hxx>
35 #include <Standard_NoMoreObject.hxx>
36 #include <Standard_TypeMismatch.hxx>
37 #include <StlMesh_Mesh.hxx>
38 #include <StlMesh_MeshExplorer.hxx>
39 #include <TCollection_AsciiString.hxx>
40
41 #include <stdio.h>
42 // A static method adding nodes to a mesh and keeping coincident (sharing) nodes.
43 static Standard_Integer AddVertex(Handle(StlMesh_Mesh)& mesh,
44                                   BRepBuilderAPI_CellFilter& filter, 
45                                   BRepBuilderAPI_VertexInspector& inspector,
46                                   const gp_XYZ& p)
47 {
48   Standard_Integer index;
49   inspector.SetCurrent(p);
50   gp_XYZ minp = inspector.Shift(p, -Precision::Confusion());
51   gp_XYZ maxp = inspector.Shift(p, +Precision::Confusion());
52   filter.Inspect(minp, maxp, inspector);
53   const TColStd_ListOfInteger& indices = inspector.ResInd();
54   if (indices.IsEmpty() == Standard_False)
55   {
56     index = indices.First(); // it should be only one
57     inspector.ClearResList();
58   }
59   else
60   {
61     index = mesh->AddVertex(p.X(), p.Y(), p.Z());
62     filter.Add(index, p);
63     inspector.Add(p);
64   }
65   return index;
66 }
67
68 // constants
69 static const size_t HEADER_SIZE           =  84;
70 static const size_t SIZEOF_STL_FACET      =  50;
71 static const size_t ASCII_LINES_PER_FACET =   7;
72
73 static const int IND_THRESHOLD = 1000; // increment the indicator every 1k triangles
74
75 //=======================================================================
76 //function : WriteInteger
77 //purpose  : writing a Little Endian 32 bits integer
78 //=======================================================================
79
80 inline static void WriteInteger(OSD_File& ofile,const Standard_Integer value)
81 {
82   union {
83     Standard_Integer i;// don't be afraid, this is just an unsigned int
84     char c[4];
85   } bidargum;
86
87   bidargum.i = value;
88
89   Standard_Integer entier;
90
91   entier  =  bidargum.c[0] & 0xFF;
92   entier |= (bidargum.c[1] & 0xFF) << 0x08;
93   entier |= (bidargum.c[2] & 0xFF) << 0x10;
94   entier |= (bidargum.c[3] & 0xFF) << 0x18;
95
96   ofile.Write((char *)&entier,sizeof(bidargum.c));
97 }
98
99 //=======================================================================
100 //function : WriteDouble2Float
101 //purpose  : writing a Little Endian 32 bits float
102 //=======================================================================
103
104 inline static void WriteDouble2Float(OSD_File& ofile,Standard_Real value)
105 {
106   union {
107     Standard_ShortReal f;
108     char c[4];
109   } bidargum;
110
111   bidargum.f = (Standard_ShortReal)value;
112
113   Standard_Integer entier;
114
115   entier  =  bidargum.c[0] & 0xFF;
116   entier |= (bidargum.c[1] & 0xFF) << 0x08;
117   entier |= (bidargum.c[2] & 0xFF) << 0x10;
118   entier |= (bidargum.c[3] & 0xFF) << 0x18;
119
120   ofile.Write((char *)&entier,sizeof(bidargum.c));
121 }
122
123
124 //=======================================================================
125 //function : readFloat2Double
126 //purpose  : reading a Little Endian 32 bits float
127 //=======================================================================
128
129 inline static Standard_Real ReadFloat2Double(OSD_File &aFile)
130 {
131   union {
132     Standard_Boolean i; // don't be afraid, this is just an unsigned int
133     Standard_ShortReal f;
134   }bidargum;
135
136   char c[4];
137   Standard_Address adr;
138   adr = (Standard_Address)c;
139   Standard_Integer lread;
140   aFile.Read(adr,4,lread);
141   bidargum.i  =  c[0] & 0xFF;
142   bidargum.i |=  (c[1] & 0xFF) << 0x08;
143   bidargum.i |=  (c[2] & 0xFF) << 0x10;
144   bidargum.i |=  (c[3] & 0xFF) << 0x18;
145
146   return (Standard_Real)(bidargum.f);
147 }
148
149
150
151 //=======================================================================
152 //function : WriteBinary
153 //purpose  : write a binary STL file in Little Endian format
154 //=======================================================================
155
156 Standard_Boolean RWStl::WriteBinary (const Handle(StlMesh_Mesh)& theMesh,
157                                      const OSD_Path& thePath,
158                                      const Handle(Message_ProgressIndicator)& theProgInd)
159 {
160   OSD_File aFile (thePath);
161   aFile.Build (OSD_WriteOnly, OSD_Protection());
162
163   Standard_Real x1, y1, z1;
164   Standard_Real x2, y2, z2;
165   Standard_Real x3, y3, z3;
166
167   // writing 80 bytes of the trash?
168   char sval[80];
169   aFile.Write ((Standard_Address)sval,80);
170   WriteInteger (aFile, theMesh->NbTriangles());
171
172   int dum=0;
173   StlMesh_MeshExplorer aMexp (theMesh);
174
175   // create progress sentry for domains
176   Standard_Integer aNbDomains = theMesh->NbDomains();
177   Message_ProgressSentry aDPS (theProgInd, "Mesh domains", 0, aNbDomains, 1);
178   for (Standard_Integer nbd = 1; nbd <= aNbDomains && aDPS.More(); nbd++, aDPS.Next())
179   {
180     // create progress sentry for triangles in domain
181     Message_ProgressSentry aTPS (theProgInd, "Triangles", 0,
182         theMesh->NbTriangles (nbd), IND_THRESHOLD);
183     Standard_Integer aTriangleInd = 0;
184     for (aMexp.InitTriangle (nbd); aMexp.MoreTriangle(); aMexp.NextTriangle())
185     {
186       aMexp.TriangleVertices (x1,y1,z1,x2,y2,z2,x3,y3,z3);
187       //pgo       aMexp.TriangleOrientation (x,y,z);
188       gp_XYZ Vect12 ((x2-x1), (y2-y1), (z2-z1));
189       gp_XYZ Vect13 ((x3-x1), (y3-y1), (z3-z1));
190       gp_XYZ Vnorm = Vect12 ^ Vect13;
191       Standard_Real Vmodul = Vnorm.Modulus ();
192       if (Vmodul > gp::Resolution())
193       {
194         Vnorm.Divide(Vmodul);
195       }
196       else
197       {
198         // si Vnorm est quasi-nul, on le charge a 0 explicitement
199         Vnorm.SetCoord (0., 0., 0.);
200       }
201
202       WriteDouble2Float (aFile, Vnorm.X());
203       WriteDouble2Float (aFile, Vnorm.Y());
204       WriteDouble2Float (aFile, Vnorm.Z());
205
206       WriteDouble2Float (aFile, x1);
207       WriteDouble2Float (aFile, y1);
208       WriteDouble2Float (aFile, z1);
209
210       WriteDouble2Float (aFile, x2);
211       WriteDouble2Float (aFile, y2);
212       WriteDouble2Float (aFile, z2);
213
214       WriteDouble2Float (aFile, x3);
215       WriteDouble2Float (aFile, y3);
216       WriteDouble2Float (aFile, z3);
217
218       aFile.Write (&dum, 2);
219
220       // update progress only per 1k triangles
221       if (++aTriangleInd % IND_THRESHOLD == 0)
222       {
223         if (!aTPS.More())
224           break;
225         aTPS.Next();
226       }
227     }
228   }
229   aFile.Close();
230   Standard_Boolean isInterrupted = !aDPS.More();
231   return !isInterrupted;
232 }
233 //=======================================================================
234 //function : WriteAscii
235 //purpose  : write an ASCII STL file
236 //=======================================================================
237
238 Standard_Boolean RWStl::WriteAscii (const Handle(StlMesh_Mesh)& theMesh,
239                                     const OSD_Path& thePath,
240                                     const Handle(Message_ProgressIndicator)& theProgInd)
241 {
242   OSD_File theFile (thePath);
243   theFile.Build(OSD_WriteOnly,OSD_Protection());
244   TCollection_AsciiString buf ("solid\n");
245   theFile.Write (buf,buf.Length());buf.Clear();
246
247   Standard_Real x1, y1, z1;
248   Standard_Real x2, y2, z2;
249   Standard_Real x3, y3, z3;
250   char sval[512];
251
252   // create progress sentry for domains
253   Standard_Integer aNbDomains = theMesh->NbDomains();
254   Message_ProgressSentry aDPS (theProgInd, "Mesh domains", 0, aNbDomains, 1);
255   StlMesh_MeshExplorer aMexp (theMesh);
256   for (Standard_Integer nbd = 1; nbd <= aNbDomains && aDPS.More(); nbd++, aDPS.Next())
257   {
258     // create progress sentry for triangles in domain
259     Message_ProgressSentry aTPS (theProgInd, "Triangles", 0,
260         theMesh->NbTriangles (nbd), IND_THRESHOLD);
261     Standard_Integer aTriangleInd = 0;
262     for (aMexp.InitTriangle (nbd); aMexp.MoreTriangle(); aMexp.NextTriangle())
263     {
264       aMexp.TriangleVertices (x1,y1,z1,x2,y2,z2,x3,y3,z3);
265
266 //      Standard_Real x, y, z;
267 //      aMexp.TriangleOrientation (x,y,z);
268
269       gp_XYZ Vect12 ((x2-x1), (y2-y1), (z2-z1));
270       gp_XYZ Vect23 ((x3-x2), (y3-y2), (z3-z2));
271       gp_XYZ Vnorm = Vect12 ^ Vect23;
272       Standard_Real Vmodul = Vnorm.Modulus ();
273       if (Vmodul > gp::Resolution())
274       {
275         Vnorm.Divide (Vmodul);
276       }
277       else
278       {
279         // si Vnorm est quasi-nul, on le charge a 0 explicitement
280         Vnorm.SetCoord (0., 0., 0.);
281       }
282       Sprintf (sval,
283           " facet normal % 12e % 12e % 12e\n"
284           "   outer loop\n"
285           "     vertex % 12e % 12e % 12e\n"
286           "     vertex % 12e % 12e % 12e\n"
287           "     vertex % 12e % 12e % 12e\n"
288           "   endloop\n"
289           " endfacet\n",
290           Vnorm.X(), Vnorm.Y(), Vnorm.Z(),
291           x1, y1, z1,
292           x2, y2, z2,
293           x3, y3, z3);
294       buf += sval;
295       theFile.Write (buf, buf.Length()); buf.Clear();
296
297       // update progress only per 1k triangles
298       if (++aTriangleInd % IND_THRESHOLD == 0)
299       {
300         if (!aTPS.More())
301             break;
302         aTPS.Next();
303       }
304     }
305   }
306
307   buf += "endsolid\n";
308   theFile.Write (buf, buf.Length()); buf.Clear();
309   theFile.Close();
310   Standard_Boolean isInterrupted = !aDPS.More();
311   return !isInterrupted;
312 }
313 //=======================================================================
314 //function : ReadFile
315 //Design   :
316 //Warning  :
317 //=======================================================================
318
319 Handle(StlMesh_Mesh) RWStl::ReadFile (const OSD_Path& thePath,
320                                      const Handle(Message_ProgressIndicator)& theProgInd)
321 {
322   OSD_File file (thePath);
323   file.Open(OSD_ReadOnly,OSD_Protection(OSD_RWD,OSD_RWD,OSD_RWD,OSD_RWD));
324   Standard_Boolean IsAscii;
325   unsigned char str[128];
326   Standard_Integer lread,i;
327   Standard_Address ach;
328   ach = (Standard_Address)str;
329
330   // we skip the header which is in Ascii for both modes
331   file.Read(ach,HEADER_SIZE,lread);
332
333   // we read 128 characters to detect if we have a non-ascii char
334   file.Read(ach,sizeof(str),lread);
335
336   IsAscii = Standard_True;
337   for (i = 0; i< lread && IsAscii; ++i) {
338     if (str[i] > '~') {
339       IsAscii = Standard_False;
340     }
341   }
342 #ifdef OCCT_DEBUG
343   cout << (IsAscii ? "ascii\n" : "binary\n");
344 #endif
345   file.Close();
346
347   return IsAscii ? RWStl::ReadAscii  (thePath, theProgInd)
348                  : RWStl::ReadBinary (thePath, theProgInd);
349 }
350
351 //=======================================================================
352 //function : ReadBinary
353 //Design   :
354 //Warning  :
355 //=======================================================================
356
357 Handle(StlMesh_Mesh) RWStl::ReadBinary (const OSD_Path& thePath,
358                                        const Handle(Message_ProgressIndicator)& /*theProgInd*/)
359 {
360   Standard_Integer ifacet;
361   Standard_Real fx,fy,fz,fx1,fy1,fz1,fx2,fy2,fz2,fx3,fy3,fz3;
362   Standard_Integer i1,i2,i3,lread;
363   char buftest[5];
364   Standard_Address adr;
365   adr = (Standard_Address)buftest;
366
367   // Open the file
368   OSD_File theFile (thePath);
369   theFile.Open(OSD_ReadOnly,OSD_Protection(OSD_RWD,OSD_RWD,OSD_RWD,OSD_RWD));
370
371   // the size of the file (minus the header size)
372   // must be a multiple of SIZEOF_STL_FACET
373
374   // compute file size
375   Standard_Size filesize = theFile.Size();
376
377   // don't trust the number of triangles which is coded in the file sometimes it is wrong
378   Standard_Integer NBFACET = (Standard_Integer)((filesize - HEADER_SIZE) / SIZEOF_STL_FACET);
379   if (NBFACET < 1)
380   {
381     Standard_NoMoreObject::Raise("RWStl::ReadBinary (wrong file size)");
382   }
383
384   theFile.Seek (80, OSD_FromBeginning);
385   theFile.Read (adr, 4, lread);
386   Standard_Integer aNbTrisInHeader = (((char*           )buftest)[3] << 24) | (((Standard_Byte* )buftest)[2] << 16)
387                                     | (((Standard_Byte* )buftest)[1] << 8 ) | (((Standard_Byte* )buftest)[0] << 0 );
388   if (NBFACET < aNbTrisInHeader)
389   {
390     Message::DefaultMessenger()->Send (TCollection_AsciiString ("RWStl - Binary STL file defines more triangles (") + aNbTrisInHeader
391                                      + ") that can be read (" + NBFACET + ") - probably corrupted file",
392                                        Message_Warning);
393   }
394   else if (NBFACET > aNbTrisInHeader)
395   {
396     Message::DefaultMessenger()->Send (TCollection_AsciiString ("RWStl - Binary STL file defines less triangles (") + aNbTrisInHeader
397                                      + ") that can be read (" + NBFACET + ") - probably corrupted file",
398                                        Message_Warning);
399   }
400   else if ((filesize - HEADER_SIZE) % SIZEOF_STL_FACET != 0)
401   {
402     Message::DefaultMessenger()->Send (TCollection_AsciiString ("RWStl - Binary STL file has unidentified tail"),
403                                        Message_Warning);
404   }
405
406   // skip the header
407   theFile.Seek(HEADER_SIZE,OSD_FromBeginning);
408
409   // create the StlMesh_Mesh object
410   Handle(StlMesh_Mesh) ReadMesh = new StlMesh_Mesh ();
411   ReadMesh->AddDomain ();
412
413   // Filter unique vertices to share the nodes of the mesh.
414   BRepBuilderAPI_CellFilter uniqueVertices(Precision::Confusion());
415   BRepBuilderAPI_VertexInspector inspector(Precision::Confusion());
416   
417   for (ifacet=1; ifacet<=NBFACET; ++ifacet) {
418     // read normal coordinates
419     fx = ReadFloat2Double(theFile);
420     fy = ReadFloat2Double(theFile);
421     fz = ReadFloat2Double(theFile);
422
423     // read vertex 1
424     fx1 = ReadFloat2Double(theFile);
425     fy1 = ReadFloat2Double(theFile);
426     fz1 = ReadFloat2Double(theFile);
427
428     // read vertex 2
429     fx2 = ReadFloat2Double(theFile);
430     fy2 = ReadFloat2Double(theFile);
431     fz2 = ReadFloat2Double(theFile);
432
433     // read vertex 3
434     fx3 = ReadFloat2Double(theFile);
435     fy3 = ReadFloat2Double(theFile);
436     fz3 = ReadFloat2Double(theFile);
437
438     // Add vertices.
439     i1 = AddVertex(ReadMesh, uniqueVertices, inspector, gp_XYZ(fx1, fy1, fz1));
440     i2 = AddVertex(ReadMesh, uniqueVertices, inspector, gp_XYZ(fx2, fy2, fz2));
441     i3 = AddVertex(ReadMesh, uniqueVertices, inspector, gp_XYZ(fx3, fy3, fz3));
442
443     // Add triangle.
444     ReadMesh->AddTriangle (i1,i2,i3,fx,fy,fz);
445
446     // skip extra bytes
447     theFile.Read(adr,2,lread);
448   }
449
450   theFile.Close ();
451   return ReadMesh;
452 }
453
454 //=======================================================================
455 //function : ReadAscii
456 //Design   :
457 //Warning  :
458 //=======================================================================
459
460 Handle(StlMesh_Mesh) RWStl::ReadAscii (const OSD_Path& thePath,
461                                       const Handle(Message_ProgressIndicator)& theProgInd)
462 {
463   TCollection_AsciiString filename;
464   long ipos;
465   Standard_Integer nbLines = 0;
466   Standard_Integer nbTris = 0;
467   Standard_Integer iTri;
468   Standard_Integer i1,i2,i3;
469   Handle(StlMesh_Mesh) ReadMesh;
470
471   thePath.SystemName (filename);
472
473   // Open the file
474   FILE* file = OSD_OpenFile(filename.ToCString(),"r");
475
476   fseek(file,0L,SEEK_END);
477
478   long filesize = ftell(file);
479
480   rewind(file);
481
482   // count the number of lines
483   for (ipos = 0; ipos < filesize; ++ipos) {
484           if (getc(file) == '\n')
485         nbLines++;
486   }
487
488   // compute number of triangles
489   nbTris = (nbLines / ASCII_LINES_PER_FACET);
490
491   // go back to the beginning of the file
492   rewind(file);
493
494   // skip header
495   while (getc(file) != '\n');
496 #ifdef OCCT_DEBUG
497   cout << "start mesh\n";
498 #endif
499   ReadMesh = new StlMesh_Mesh();
500   ReadMesh->AddDomain();
501
502   // Filter unique vertices to share the nodes of the mesh.
503   BRepBuilderAPI_CellFilter uniqueVertices(Precision::Confusion());
504   BRepBuilderAPI_VertexInspector inspector(Precision::Confusion());
505   
506   // main reading
507   Message_ProgressSentry aPS (theProgInd, "Triangles", 0, (nbTris - 1) * 1.0 / IND_THRESHOLD, 1);
508   for (iTri = 0; iTri < nbTris && aPS.More();)
509   {
510     char x[256]="", y[256]="", z[256]="";
511
512     // reading the facet normal
513     if (3 != fscanf(file,"%*s %*s %80s %80s %80s\n", x, y, z))
514       break; // error should be properly reported
515     gp_XYZ aN (Atof(x), Atof(y), Atof(z));
516
517     // skip the keywords "outer loop"
518     if (fscanf(file,"%*s %*s") < 0)
519       break;
520
521     // reading vertex
522     if (3 != fscanf(file,"%*s %80s %80s %80s\n", x, y, z))
523       break; // error should be properly reported
524     gp_XYZ aV1 (Atof(x), Atof(y), Atof(z));
525     if (3 != fscanf(file,"%*s %80s %80s %80s\n", x, y, z))
526       break; // error should be properly reported
527     gp_XYZ aV2 (Atof(x), Atof(y), Atof(z));
528     if (3 != fscanf(file,"%*s %80s %80s %80s\n", x, y, z))
529       break; // error should be properly reported
530     gp_XYZ aV3 (Atof(x), Atof(y), Atof(z));
531
532     // here the facet must be built and put in the mesh datastructure
533
534     i1 = AddVertex(ReadMesh, uniqueVertices, inspector, aV1);
535     i2 = AddVertex(ReadMesh, uniqueVertices, inspector, aV2);
536     i3 = AddVertex(ReadMesh, uniqueVertices, inspector, aV3);
537     ReadMesh->AddTriangle (i1, i2, i3, aN.X(), aN.Y(), aN.Z());
538
539     // skip the keywords "endloop"
540     if (fscanf(file,"%*s") < 0)
541       break;
542
543     // skip the keywords "endfacet"
544     if (fscanf(file,"%*s") < 0)
545       break;
546
547     // update progress only per 1k triangles
548     if (++iTri % IND_THRESHOLD == 0)
549       aPS.Next();
550   }
551 #ifdef OCCT_DEBUG
552   cout << "end mesh\n";
553 #endif
554   fclose(file);
555   return ReadMesh;
556 }