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