1 /*
2     This file is a part of the RepSnapper project.
3     Copyright (C) 2010  Kulitorum
4     Copyright (C) 2011-12  martin.dieringer@gmx.de
5 
6     This program is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10 
11     This program is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15 
16     You should have received a copy of the GNU General Public License along
17     with this program; if not, write to the Free Software Foundation, Inc.,
18     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20 
21 #include "files.h"
22 
23 #include <iostream>
24 #include <stdlib.h>
25 
26 
27 static string numlocale   = "";
28 static string colllocale  = "";
29 static string ctypelocale = "";
save_locales()30 void save_locales() {
31   numlocale   = string(setlocale(LC_NUMERIC, NULL));
32   colllocale  = string(setlocale(LC_COLLATE, NULL));
33   ctypelocale = string(setlocale(LC_CTYPE,   NULL));
34 }
set_locales(const char * loc)35 void set_locales(const char * loc) {
36   setlocale(LC_NUMERIC, loc);
37   setlocale(LC_COLLATE, loc);
38   setlocale(LC_CTYPE,   loc);
39 }
reset_locales()40 void reset_locales() {
41   setlocale(LC_NUMERIC, numlocale.c_str());
42   setlocale(LC_COLLATE, colllocale.c_str());
43   setlocale(LC_CTYPE,   ctypelocale.c_str());
44 }
45 
46 
read_float(ifstream & file)47 static float read_float(ifstream &file) {
48 	// Read platform independent 32 bit ieee 754 little-endian float.
49 	union ieee_union {
50 		char buffer[4];
51 		struct ieee_struct {
52 			unsigned int mantissa:23;
53 			unsigned int exponent:8;
54 			unsigned int negative:1;
55 		} ieee;
56 	} ieee;
57 	file.read (ieee.buffer, 4);
58 
59 	GFloatIEEE754 ret;
60 	ret.mpn.mantissa = ieee.ieee.mantissa;
61 	ret.mpn.biased_exponent = ieee.ieee.exponent;
62 	ret.mpn.sign = ieee.ieee.negative;
63 
64 	return ret.v_float;
65 }
66 
read_double(ifstream & file)67 static double read_double(ifstream &file) {
68   return double(read_float(file));
69 }
70 
71 
File(Glib::RefPtr<Gio::File> file)72 File::File(Glib::RefPtr<Gio::File> file)
73  : _file(file)
74 {
75   _path = _file->get_path();
76   _type = getFileType(_path);
77 }
78 
loadTriangles(vector<vector<Triangle>> & triangles,vector<ustring> & names,uint max_triangles)79 void File::loadTriangles(vector< vector<Triangle> > &triangles,
80 			 vector<ustring> &names,
81 			 uint max_triangles)
82 {
83   Gio::FileType type = _file->query_file_type();
84   if (type != Gio::FILE_TYPE_REGULAR &&
85       type != Gio::FILE_TYPE_SYMBOLIC_LINK)
86     return;
87 
88   ustring name_by_file = _file->get_basename();
89   size_t found = name_by_file.find_last_of(".");
90   name_by_file = (ustring)name_by_file.substr(0,found);
91 
92   set_locales("C");
93   if(_type == ASCII_STL) {
94     // multiple shapes per file
95     load_asciiSTL(triangles, names, max_triangles);
96     if (names.size() == 1) // if single shape name by file
97       names[0] = name_by_file;
98     if (triangles.size() == 0) {// if no success, try binary mode
99       _type = BINARY_STL;
100       loadTriangles(triangles, names, max_triangles);
101       return;
102     }
103   } else if (_type == AMF) {
104     // multiple shapes per file
105     load_AMF(triangles, names, max_triangles);
106     if (names.size() == 1) // if single shape name by file
107       names[0] = name_by_file;
108   } else {
109     // single shape per file
110     triangles.resize(1);
111     names.resize(1);
112     names[0] = name_by_file;
113     if (_type == BINARY_STL) {
114       load_binarySTL(triangles[0], max_triangles);
115     } else if (_type == VRML) {
116       load_VRML(triangles[0], max_triangles);
117     } else {
118       cerr << _("Unrecognized file - ") << _file->get_parse_name() << endl;
119       cerr << _("Known extensions: ") << "STL, WRL, AMF." << endl;
120     }
121   }
122   reset_locales();
123 }
124 
125 
126 
getFileType(ustring filename)127 filetype_t File::getFileType(ustring filename)
128 {
129     // Extract file extension (i.e. "stl")
130   ustring extension = filename.substr(filename.find_last_of(".")+1);
131 
132 
133     if(extension == "wrl" || extension == "WRL") {
134         return VRML;
135     }
136 
137     if(extension == "amf" || extension == "AMF") {
138         return AMF;
139     }
140 
141     if(extension != "stl" && extension != "STL") {
142         return NONE_STL;
143     }
144 
145     ifstream file;
146     file.open(filename.c_str());
147 
148     if(file.fail()) {
149       cerr << _("Error: Unable to open file - ") << filename << endl;
150       return NONE_STL;
151     }
152 
153     // ASCII files start with "solid [Name_of_file]"
154     ustring first_word;
155     try {
156       file >> first_word;
157 
158       // Find bad Solid Works STL header
159       // 'solid binary STL from Solid Edge, Unigraphics Solutions Inc.'
160       ustring second_word;
161       if(first_word == "solid")
162 	file >> second_word;
163 
164       file.close();
165       if(first_word == "solid" && second_word != "binary") { // ASCII
166 	return ASCII_STL;
167       } else {
168 	return BINARY_STL;
169       }
170     } catch (Glib::ConvertError e) {
171       return BINARY_STL; // no keyword -> binary
172     }
173 
174 }
175 
176 
load_binarySTL(vector<Triangle> & triangles,uint max_triangles,bool readnormals)177 bool File::load_binarySTL(vector<Triangle> &triangles,
178 			  uint max_triangles, bool readnormals)
179 {
180     ifstream file;
181     ustring filename = _file->get_path();
182     file.open(filename.c_str(), ifstream::in | ifstream::binary);
183 
184     if(file.fail()) {
185       cerr << _("Error: Unable to open stl file - ") << filename << endl;
186       return false;
187     }
188     // cerr << "loading bin " << filename << endl;
189 
190     /* Binary STL files have a meaningless 80 byte header
191      * followed by the number of triangles */
192     file.seekg(80, ios_base::beg);
193     unsigned int num_triangles;
194     unsigned char buffer[4];
195     file.read(reinterpret_cast <char *> (buffer), 4);
196     // Read platform independent 32-bit little-endian int.
197     num_triangles = buffer[0] | buffer[1] << 8 | buffer[2] << 16 | buffer[3] << 24;
198 
199     uint step = 1;
200     if (max_triangles > 0 && max_triangles < num_triangles) {
201       step = ceil(num_triangles/max_triangles);
202       triangles.reserve(max_triangles);
203     } else
204       triangles.reserve(num_triangles);
205 
206     uint i = 0;
207     for(; i < num_triangles; i+=step)
208     {
209       if (step>1)
210 	file.seekg(84 + 50*i, ios_base::beg);
211 
212       double a,b,c;
213         a = read_double (file);
214         b = read_double (file);
215         c = read_double (file);
216         Vector3d N(a,b,c);
217         a = read_double (file);
218         b = read_double (file);
219         c = read_double (file);
220         Vector3d Ax(a,b,c);
221         a = read_double (file);
222         b = read_double (file);
223         c = read_double (file);
224         Vector3d Bx(a,b,c);
225         a = read_double (file);
226         b = read_double (file);
227         c = read_double (file);
228         Vector3d Cx(a,b,c);
229 
230         if (file.eof()) {
231             cerr << _("Unexpected EOF reading STL file - ") << filename << endl;
232             break;
233         }
234 
235         /* attribute byte count - sometimes contains face color
236             information but is useless for our purposes */
237         unsigned short byte_count;
238         file.read(reinterpret_cast <char *> (buffer), 2);
239 	byte_count = buffer[0] | buffer[1] << 8;
240 	// Repress unused variable warning.
241 	(void)&byte_count;
242 
243 	Triangle T = Triangle(Ax,Bx,Cx);
244 	if (readnormals)
245 	  if (T.Normal.dot(N) < 0) T.invertNormal();
246 
247 	// cout << "bin triangle "<< N << ":\n\t" << Ax << "/\n\t"<<Bx << "/\n\t"<<Cx << endl;
248         triangles.push_back(T);
249     }
250     file.close();
251 
252     return true;
253     // cerr << "Read " << i << " triangles of " << num_triangles << " from file" << endl;
254 }
255 
256 
load_asciiSTL(vector<vector<Triangle>> & triangles,vector<ustring> & names,uint max_triangles,bool readnormals)257 bool File::load_asciiSTL(vector< vector<Triangle> > &triangles,
258 			 vector<ustring> &names,
259 			 uint max_triangles, bool readnormals)
260 {
261   ustring filename = _file->get_path();
262   ifstream file;
263   file.open(filename.c_str(), ifstream::in);
264   if(file.fail()) {
265     cerr << _("Error: Unable to open stl file - ") << filename << endl;
266     return false;
267   }
268   // fileis.imbue(std::locale::classic());
269 
270   // get as many shapes as found in file
271   while (true) {
272     vector<Triangle> tr;
273     ustring name;
274     if (!File::parseSTLtriangles_ascii(file, max_triangles, readnormals,
275 				       tr, name))
276       break;
277 
278     triangles.push_back(tr);
279     names.push_back(name);
280     // go back to get "solid " keyword again
281     streampos where = file.tellg();
282     where-=100;
283     if (where < 0) break;
284     file.seekg(where,ios::beg);
285   }
286 
287   //     if (ret < 0) {// cannot parse, try binary
288   //       cerr << _("Could not read file in ASCII mode, trying Binary: ")<< filename << endl;
289   //       file.close();
290   //       return loadBinarySTL(filename, max_triangles, readnormals);
291   //     }
292 
293   file.close();
294   return true;
295 }
296 
297 
parseSTLtriangles_ascii(istream & text,uint max_triangles,bool readnormals,vector<Triangle> & triangles,ustring & shapename)298 bool File::parseSTLtriangles_ascii (istream &text,
299 				    uint max_triangles, bool readnormals,
300 				    vector<Triangle> &triangles,
301 				    ustring &shapename)
302 {
303   //cerr << "loading ascii " << endl;
304   //cerr << " locale " << std::locale().name() << endl;
305 
306   shapename = _("Unnamed");
307     // uint step = 1;
308     // if (max_triangles > 0 && max_triangles < num_triangles) {
309     //   step = ceil(num_triangles/max_triangles);
310     streampos pos = text.tellg();
311     text.seekg(0, ios::end);
312     streampos fsize = text.tellg();
313     text.seekg(pos, ios::beg);
314 
315     // a rough estimation
316     uint num_triangles = (fsize-pos)/30;
317 
318     uint step = 1;
319     if (max_triangles > 0 && max_triangles < num_triangles)
320       step = ceil(num_triangles/max_triangles);
321 
322     //cerr << "step " << step << endl;
323 
324     /* ASCII files start with "solid [Name_of_file]"
325      * so get rid of them to access the data */
326     string solid;
327     //getline (text, solid);
328 
329     while(!text.eof()) { // Find next solid
330       text >> solid;
331       if (solid == "solid") {
332 	string name;
333 	getline(text,name);
334 	shapename = (ustring)name;
335 	break;
336       }
337     }
338     if (solid != "solid") {
339       return false;
340     }
341 
342     // uint itr = 0;
343     while(!text.eof()) { // Loop around all triangles
344       string facet;
345         text >> facet;
346 	//cerr << text.tellg() << " - " << fsize << " - " <<facet << endl;
347 	if (step > 1) {
348 	  for (uint s=0; s < step; s++) {
349 	    if (text.eof()) break;
350 	    facet = "";
351 	    while (facet != "facet" && facet != "endsolid"){
352 	      if (text.eof()) break;
353 	      text >> facet;
354 	    }
355 	    if (facet == "endsolid") {
356 	      break;
357 	    }
358 	  }
359 	}
360         // Parse possible end of text - "endsolid"
361         if(text.eof() || facet == "endsolid" ) {
362 	  break;
363         }
364 
365         if(facet != "facet") {
366 	  cerr << _("Error: Facet keyword not found in STL text!") << endl;
367 	  return false;
368         }
369 
370         // Parse Face Normal - "normal %f %f %f"
371         string normal;
372         Vector3d normal_vec;
373         text >> normal;
374         if(readnormals && normal != "normal") {
375 	  cerr << _("Error: normal keyword not found in STL text!") << endl;
376 	  return false;
377 	}
378 
379 	if (readnormals){
380 	  text >> normal_vec.x()
381 	       >> normal_vec.y()
382 	       >> normal_vec.z();
383 	}
384 
385         // Parse "outer loop" line
386         string outer, loop;
387 	while (outer!="outer" && !text.eof()) {
388 	  text >> outer;
389 	}
390 	text >> loop;
391 	if(outer != "outer" || loop != "loop") {
392 	  cerr << _("Error: Outer/Loop keywords not found!") << endl;
393 	  return false;
394 	}
395 
396         // Grab the 3 vertices - each one of the form "vertex %f %f %f"
397         Vector3d vertices[3];
398         for(int i=0; i<3; i++) {
399 	    string vertex;
400             text >> vertex
401 		 >> vertices[i].x()
402 		 >> vertices[i].y()
403 		 >> vertices[i].z();
404 
405             if(vertex != "vertex") {
406 	      cerr << _("Error: Vertex keyword not found") << endl;
407 	      return false;
408             }
409         }
410 
411         // Parse end of vertices loop - "endloop endfacet"
412         string endloop, endfacet;
413         text >> endloop >> endfacet;
414 
415         if(endloop != "endloop" || endfacet != "endfacet") {
416 	  cerr << _("Error: Endloop or endfacet keyword not found") << endl;
417 	  return false;
418         }
419 
420         // Create triangle object and push onto the vector
421         Triangle triangle(vertices[0],
422 			  vertices[1],
423 			  vertices[2]);
424 	if (readnormals){
425 	  //cerr << "reading normals from file" << endl;
426 	  if (triangle.Normal.dot(normal_vec) < 0) triangle.invertNormal();
427 	}
428 
429         triangles.push_back(triangle);
430     }
431     //cerr << "loaded " << filename << endl;
432     return true;
433 }
434 
load_VRML(vector<Triangle> & triangles,uint max_triangles)435 bool File::load_VRML(vector<Triangle> &triangles, uint max_triangles)
436 {
437 
438   triangles.clear();
439   ustring filename = _file->get_path();
440     ifstream file;
441 
442     file.open(filename.c_str());
443 
444     if(file.fail()) {
445       cerr << _("Error: Unable to open vrml file - ") << filename << endl;
446       return false;
447     }
448 
449     file.imbue(std::locale("C"));
450     ustring word;
451     std::vector<float> vertices;
452     std::vector<int> indices;
453     bool finished = false;
454     vector<Vector3d> points;
455     while(!file.eof() && !finished) {
456       // while (word!="Shape"  && !file.eof())
457       // 	file >> word;
458       // while (word!="Appearance" && !file.eof())
459       // 	file >> word;
460       // while (word!="Coordinate" && !file.eof())
461       // 	file >> word;
462       points.clear();
463       vertices.clear();
464       while (word!="coord" && !file.eof()) // only use coord points
465 	file >> word;
466       while (word!="point" && !file.eof())
467 	file >> word;
468       file >> word;
469       if (word=="[") {
470 	double f;
471 	while (word!="]" && !file.eof()){
472 	  file >> word;
473           if (word.find(",") == word.length()-1) { // remove comma
474             word = word.substr(0,word.length()-1);
475           }
476 	  if (word!="]") {
477 	    if (word.find("#") != 0) { // comment
478               f = atof(word.c_str());
479 	      vertices.push_back(f);
480 	    } else { // skip rest of line
481               file.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
482             }
483           }
484 	}
485         if (vertices.size() % 3 == 0)
486           for (uint i = 0; i < vertices.size(); i += 3)
487             points.push_back(Vector3d(vertices[i], vertices[i+1], vertices[i+2]));
488 	//cerr << endl;
489       }
490       indices.clear();
491       while (word!="coordIndex"  && !file.eof())
492 	file >> word;
493       file >> word;
494       if (word=="[") {
495 	int c;
496 	while (word!="]" && !file.eof()){
497 	  file >> word;
498 	  if (word!="]")
499 	    if (word.find("#")!=0) {
500 	      std::istringstream iss(word);
501               iss.precision(20);
502 	      iss >> c;
503 	      indices.push_back(c);
504 	      //cerr << word << "=="<< c << ", ";
505 	    }
506 	}
507         if (indices.size()%4 == 0)
508           for (uint i=0; i<indices.size();i+=4) {
509             Triangle T(points[indices[i]],points[indices[i+1]],points[indices[i+2]]);
510             triangles.push_back(T);
511           }
512       }
513       if (triangles.size()%1000 == 0)
514         cerr << "\rread " << triangles.size() << " triangles " ;
515     }
516     file.close();
517     return true;
518 }
519 
520 
521 // does not cross-compile ....
522 #ifndef WIN32
523 #define ENABLE_AMF 1
524 #else
525 #define ENABLE_AMF 0
526 #endif
527 
528 #if ENABLE_AMF
529 #include "amf/amftools-code/include/AMF_File.h"
530  class AMFLoader : public AmfFile
531  {
532    double _scale;
533  public:
AMFLoader()534    AMFLoader() : _scale(1.) {};
~AMFLoader()535    ~AMFLoader(){};
open(ustring path)536    bool open(ustring path) {
537      bool ok = Load(path);
538      if (!ok)
539        cerr << "AMF Error: " << GetLastErrorMsg() << endl;
540      return ok;
541    }
getVertex(const nMesh & mesh,uint i) const542    Vector3d getVertex(const nMesh &mesh, uint i) const
543    {
544      nCoordinates c = mesh.Vertices.VertexList[i].Coordinates;
545      return Vector3d(_scale * c.X, _scale * c.Y, _scale * c.Z);
546    }
getTriangle(const nMesh & mesh,const nTriangle & t)547    Triangle getTriangle(const nMesh &mesh, const nTriangle &t)
548    {
549      return Triangle(getVertex(mesh, t.v1),
550 		     getVertex(mesh, t.v2),
551 		     getVertex(mesh, t.v3));
552    }
553 
getObjectTriangles(uint onum,vector<Triangle> & triangles)554    bool getObjectTriangles(uint onum, vector<Triangle> &triangles)
555    {
556      nObject* object = GetObject(onum);
557      uint nmeshes = object->Meshes.size();
558      for (uint m = 0; m < nmeshes; m++) {
559        const nMesh mesh = object->Meshes[m];
560        //cerr << "Units "<<  GetUnitsString() << endl;
561        switch (aUnit) {
562        case UNIT_M:  _scale = 1000.; break;
563        case UNIT_IN: _scale = 25.4;  break;
564        case UNIT_FT: _scale = 304.8; break;
565        case UNIT_UM: _scale = 0.001; break;
566        case UNIT_MM:
567        default: _scale = 1.; break;
568        }
569        uint nvolumes = mesh.Volumes.size();
570        for (uint v = 0; v < nvolumes; v++) {
571 	 uint ntria = mesh.Volumes[v].Triangles.size();
572 	 for (uint t = 0; t < ntria; t++) {
573 	   triangles.push_back( getTriangle(mesh, mesh.Volumes[v].Triangles[t]) );
574 	 }
575        }
576      }
577      return true;
578    }
579 
580  };
581  class AMFWriter : public AmfFile
582  {
583  public:
AMFWriter()584    AMFWriter() {};
~AMFWriter()585    ~AMFWriter() {};
586 
vertex(const Vector3d & v) const587    nVertex vertex(const Vector3d &v) const {
588      return nVertex(v.x(), v.y(), v.z());
589    }
590 
AddObject(const vector<Triangle> & triangles,const ustring name)591    bool AddObject(const vector<Triangle> &triangles,
592 		  const ustring name)
593    {
594      int num = AmfFile::AddObject(string(name));
595      if (num<0) return false;
596      nObject* object = GetObject(num, true);
597      if (!object) return false;
598      nMesh mesh;
599      for (uint t = 0; t <  triangles.size(); t++) {
600        nVertex A = vertex(triangles[t].A);
601        mesh.AddVertex(A);
602        nVertex B = vertex(triangles[t].B);
603        mesh.AddVertex(B);
604        nVertex C = vertex(triangles[t].C);
605        mesh.AddVertex(C);
606      }
607      nVolume* vol = mesh.NewVolume(name);
608      for (uint t = 0; t <  triangles.size(); t++) {
609        nTriangle tr(3*t, 3*t+1, 3*t+2);
610        vol->AddTriangle(tr);
611      }
612      object->Meshes.push_back(mesh);
613      return true;
614    }
615 
616  };
617 #endif
618 
load_AMF(vector<vector<Triangle>> & triangles,vector<ustring> & names,uint max_triangles)619 bool File::load_AMF(vector< vector<Triangle> > &triangles,
620 		    vector<ustring> &names,
621 		    uint max_triangles)
622 {
623 #if ENABLE_AMF
624   AMFLoader amf;
625   if (!amf.open(_file->get_path()))
626     return false;
627   uint nobjs = amf.GetObjectCount();
628   //cerr << nobjs << " objs" << endl;
629   for (uint o = 0; o < nobjs; o++) {
630     vector<Triangle> otr;
631     amf.getObjectTriangles(o,otr);
632     triangles.push_back(otr);
633     names.push_back(ustring(amf.GetObjectName(o)));
634   }
635   return true;
636 #else
637   return false;
638 #endif
639 }
640 
save_AMF(ustring filename,const vector<vector<Triangle>> & triangles,const vector<ustring> & names,bool compressed)641 bool File::save_AMF (ustring filename,
642 		     const vector< vector<Triangle> > &triangles,
643 		     const vector<ustring> &names,
644 		     bool compressed)
645 {
646 #if ENABLE_AMF
647   AMFWriter amf;
648   amf.SetUnits(UNIT_MM);
649   uint nobjs = triangles.size();
650   for (uint o = 0; o < nobjs; o++) {
651     bool ok = amf.AddObject(triangles[o],names[o]);
652     if (!ok) return false;
653   }
654   amf.Save(filename, compressed);
655   return true;
656 #else
657   return false;
658 #endif
659 }
660 
661 
saveBinarySTL(ustring filename,const vector<Triangle> & triangles,const Matrix4d & T)662 bool File::saveBinarySTL(ustring filename, const vector<Triangle> &triangles,
663 			 const Matrix4d &T)
664 {
665 
666   FILE *file  = fopen(filename.c_str(),"wb");
667 
668   if (file==0) {
669     cerr << _("Error: Unable to open stl file - ") << filename << endl;
670     return false;
671   }
672 
673   int num_tri = (int)triangles.size();
674 
675   // Write Header
676   string tmp = "solid binary by Repsnapper                                                     ";
677 
678   fwrite(tmp.c_str(), 80, 1, file);
679 
680   // write number of triangles
681   fwrite(&num_tri, 1, sizeof(int), file);
682 
683   for(int i=0; i<num_tri; i++){
684     Vector3f
685       TA = T*triangles[i].A,
686       TB = T*triangles[i].B,
687       TC = T*triangles[i].C,
688       TN = T*triangles[i].Normal; TN.normalize();
689     float N[3] = {TN.x(), TN.y(), TN.z()};
690     float P[9] = {TA.x(), TA.y(), TA.z(),
691 		  TB.x(), TB.y(), TB.z(),
692 		  TC.x(), TC.y(), TC.z()};
693 
694     // write the normal, the three coords and a short set to zero
695     fwrite(&N,3,sizeof(float),file);
696     for(int k=0; k<3; k++) { fwrite(&P[3*k], 3, sizeof(float), file); }
697     unsigned short attributes = 0;
698     fwrite(&attributes, 1, sizeof(short), file);
699   }
700 
701   fclose(file);
702   return true;
703 
704 }
705