Developer Documentation
FileOBJ.cc
1 /*===========================================================================*\
2  * *
3  * OpenFlipper *
4  * Copyright (c) 2001-2015, RWTH-Aachen University *
5  * Department of Computer Graphics and Multimedia *
6  * All rights reserved. *
7  * www.openflipper.org *
8  * *
9  *---------------------------------------------------------------------------*
10  * This file is part of OpenFlipper. *
11  *---------------------------------------------------------------------------*
12  * *
13  * Redistribution and use in source and binary forms, with or without *
14  * modification, are permitted provided that the following conditions *
15  * are met: *
16  * *
17  * 1. Redistributions of source code must retain the above copyright notice, *
18  * this list of conditions and the following disclaimer. *
19  * *
20  * 2. Redistributions in binary form must reproduce the above copyright *
21  * notice, this list of conditions and the following disclaimer in the *
22  * documentation and/or other materials provided with the distribution. *
23  * *
24  * 3. Neither the name of the copyright holder nor the names of its *
25  * contributors may be used to endorse or promote products derived from *
26  * this software without specific prior written permission. *
27  * *
28  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS *
29  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED *
30  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
31  * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER *
32  * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, *
33  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, *
34  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR *
35  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF *
36  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING *
37  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS *
38  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
39  * *
40 \*===========================================================================*/
41 
42 
43 #include <ACG/GL/GLState.hh>
44 
47 
48 #include <OpenMesh/Core/IO/IOManager.hh>
49 
50 #include <OpenFlipper/Utils/Memory/RAMInfo.hh>
51 
52 #include <QtWidgets>
53 
54 #include "FileOBJ.hh"
55 
56 #include <ACG/Utils/SmartPointer.hh>
58 
59 // Defines for the type handling drop down box
60 #define TYPEAUTODETECT 0
61 #define TYPEASK 1
62 #define TYPEPOLY 2
63 #define TYPETRIANGLE 3
64 
65 using namespace Utils;
66 
67 //-----------------------------------------------------------------------------
68 // help functions
69 
70 void remove_duplicated_vertices(VHandles& _indices)
71 {
72  VHandles::iterator endIter = _indices.end();
73  for (VHandles::iterator iter = _indices.begin(); iter != endIter; ++iter)
74  endIter = std::remove(iter+1, endIter, *(iter));
75 
76  _indices.erase(endIter,_indices.end());
77 }
78 
79 //-----------------------------------------------------------------------------
80 
83 : materialErrors_(0),
84  loadOptions_(0),
85  saveOptions_(0),
86  saveBinary_(0),
87  saveVertexColor_(0),
88  saveFaceColor_(0),
89  saveFaceColorOverride_(0),
90  saveAlpha_(0),
91  saveNormals_(0),
92  saveTexCoords_(0),
93  saveTextures_(0),
94  saveCopyTextures_(0),
95  saveCreateTexFolder_(0),
96  savePrecisionLabel_(0),
97  savePrecision_(0),
98  saveDefaultButton_(0),
99  triMeshHandling_(0),
100  loadVertexColor_(0),
101  loadFaceColor_(0),
102  loadAlpha_(0),
103  loadNormals_(0),
104  loadTexCoords_(0),
105  loadTextures_(0),
106  loadDefaultButton_(0),
107  forceTriangleMesh_(false),
108  forcePolyMesh_(false),
109  textureIndexPropFetched_(false),
110  trimeshOptions_(OBJImporter::NONE)
111 {
112 }
113 
114 //-----------------------------------------------------------------------------------------------------
115 
117 }
118 
119 //-----------------------------------------------------------------------------------------------------
120 
122  return QString( tr("Alias/Wavefront ( *.obj )") );
123 };
124 
125 //-----------------------------------------------------------------------------------------------------
126 
128  return QString( tr("Alias/Wavefront ( *.obj )") );
129 };
130 
131 //-----------------------------------------------------------------------------------------------------
132 
135 
136  #ifdef ENABLE_BSPLINECURVE_SUPPORT
137  type |= DATA_BSPLINE_CURVE;
138  #endif
139 
140  #ifdef ENABLE_BSPLINESURFACE_SUPPORT
141  type |= DATA_BSPLINE_SURFACE;
142  #endif
143 
144  return type;
145 }
146 
147 //-----------------------------------------------------------------------------
148 
149 bool FileOBJPlugin::readMaterial(QString _filename, OBJImporter& _importer)
150 {
151  static QString line;
152  static QString keyWrd;
153  static QString textureName;
154  line.clear();
155  keyWrd.clear();
156  textureName.clear();
157 
158  static QString matName;
159  matName.clear();
160  static Material mat;
161  mat.cleanup();
162  static float f1,f2,f3;
163  f1 = 0;
164  f2 = 0;
165  f3 = 0;
166  static int i;
167  static bool insideDefintion;
168  insideDefintion = false;
169  static int textureId;
170  textureId = 1;
171 
172 
173  //open stream
174  QFile matFile(_filename);
175  if (!matFile.open(QFile::ReadOnly))
176  {
177  emit log(LOGERR, tr("readMaterial : cannot open file %1").arg(_filename));
178  return false;
179  }
180 
181  QTextStream matStream(&matFile);
182  if ( matStream.status()!=QTextStream::Ok ){
183  emit log(LOGERR, tr("readMaterial : cannot open stream %1").arg(_filename) );
184  return false;
185  }
186 
187  //clear temp material
188  mat.cleanup();
189 
190  //parse material file
191  while( matStream.status() == QTextStream::Ok && !matStream.atEnd() )
192  {
193  line = matStream.readLine();
194  if ( matStream.status() != QTextStream::Ok ){
195  emit log(LOGERR, tr("readMaterial : Warning! Could not read file properly!"));
196  return false;
197  }
198 
199  if ( line.isEmpty() )
200  continue;
201 
202  QTextStream stream(&line);
203 
204  stream >> keyWrd;
205 
206  if( ( line[0].isSpace() && line[0] != QLatin1Char('\t') ) || line[0] == QLatin1Char('#') )
207  {
208  if (insideDefintion && !matName.isEmpty() && mat.is_valid())
209  {
210  _importer.materials()[matName.toStdString()] = mat;
211  mat.cleanup();
212  }
213  }
214 
215  else if (keyWrd == QLatin1String("newmtl")) // begin new material definition
216  {
217  stream >> matName;
218  insideDefintion = true;
219  }
220 
221  else if (keyWrd == QLatin1String("Kd")) // diffuse color
222  {
223  f1 = getFloat(stream);
224  f2 = getFloat(stream);
225  f3 = getFloat(stream);
226 
227  if( stream.status()==QTextStream::Ok )
228  mat.set_Kd(f1,f2,f3);
229  }
230 
231  else if (keyWrd == QLatin1String("Ka")) // ambient color
232  {
233  f1 = getFloat(stream);
234  f2 = getFloat(stream);
235  f3 = getFloat(stream);
236 
237  if( stream.status()==QTextStream::Ok )
238  mat.set_Ka(f1,f2,f3);
239  }
240 
241  else if (keyWrd == QLatin1String("Ks")) // specular color
242  {
243  f1 = getFloat(stream);
244  f2 = getFloat(stream);
245  f3 = getFloat(stream);
246 
247  if( stream.status()==QTextStream::Ok )
248  mat.set_Ks(f1,f2,f3);
249  }
250 
251  else if (keyWrd == QLatin1String("Ke")) // emission color
252  {
253  f1 = getFloat(stream);
254  f2 = getFloat(stream);
255  f3 = getFloat(stream);
256 
257  if( stream.status()==QTextStream::Ok )
258  mat.set_Ke(f1,f2,f3);
259  }
260 
261 
262  else if (keyWrd == QLatin1String("illum")) // diffuse/specular shading model
263  {
264  stream >> i;
265 
266  if(stream.status() == QTextStream::Ok)
267  mat.set_illum(i);
268  }
269 
270  else if (keyWrd == QLatin1String("Ns")) // Shininess;
271  {
272  f1 = getFloat(stream);
273 
274  if(stream.status() == QTextStream::Ok)
275  mat.set_Ns(f1);
276  }
277 
278  else if (keyWrd == QLatin1String("Ni")) // Refractive index
279  {
280  f1 = getFloat(stream);
281 
282  if(stream.status() == QTextStream::Ok)
283  mat.set_Ni(f1);
284  }
285 
286  else if (keyWrd == QLatin1String("Tr")) // transparency value
287  {
288  f1 = getFloat(stream);
289 
290  if(stream.status() == QTextStream::Ok)
291  mat.set_Tr(f1);
292  }
293 
294  else if (keyWrd == QLatin1String("d")) // material dissolve. The result does not depend upon the thickness of the object, no real transparency
295  // ignored when Tr exists
296  {
297  f1 = getFloat(stream);
298 
299  if(stream.status() == QTextStream::Ok && !mat.has_Tr())
300  mat.set_Tr(f1);
301  }
302 #if 0
303  else if (keyWrd == QLatin1String("map_")) // map images
304  {
305  // map_Ks, specular map
306  // map_Ka, ambient map
307  // map_Bump, bump map
308  // map_d, opacity map
309  ; // just skip this
310  }
311 #endif
312  else if (keyWrd == QLatin1String("map_Kd") ) {
313  // Get the rest of the line, removing leading or trailing spaces
314  // This will define the filename of the texture
315  textureName = stream.readLine();
316  textureName = textureName.trimmed();
317  if ( ! textureName.isEmpty() )
318  mat.set_map_Kd( textureName.toStdString(), textureId++ );
319  }
320 
321  if ( matStream.status() == QTextStream::Ok && insideDefintion && mat.is_valid() && !matName.isEmpty())
322  _importer.materials()[matName.toStdString()] = mat;
323  }
324 
325  emit log( tr("%1 materials loaded.").arg( _importer.materials().size() ) );
326 
327  return true;
328 }
329 
330 //-----------------------------------------------------------------------------
331 
332 void FileOBJPlugin::createAllGroupObjects(OBJImporter& _importer) {
333 
334  for(unsigned int i = 0; i < _importer.numGroups(); ++i) {
335 
336  // Get group name
337  QString name = _importer.groupName(i);
338  convertToOBJName(name);
339 
340  if ( _importer.isTriangleMesh( i ) ){
341 
342  // add a triangle mesh
343  int id = -1;
344  emit addEmptyObject(DATA_TRIANGLE_MESH, id);
345 
346  BaseObjectData* object(0);
347 
348  if (PluginFunctions::getObject(id, object)) {
349 
350  _importer.setObject(object, i);
351 
352  object->setPath(_importer.path());
353  object->setName(name);
354  }
355 
356  } else if (_importer.isPolyMesh( i )) {
357 
358  int id = -1;
359  emit addEmptyObject(DATA_POLY_MESH, id);
360 
361  BaseObjectData* object(0);
362 
363  if (PluginFunctions::getObject(id, object)) {
364 
365  _importer.setObject(object, i);
366 
367  object->setPath(_importer.path());
368  object->setName(name);
369  }
370  }
371 
372 #ifdef ENABLE_BSPLINECURVE_SUPPORT
373 
374  else if (_importer.isCurve( i )) {
375 
376  int id = -1;
377  emit addEmptyObject(DATA_BSPLINE_CURVE, id);
378 
379  BaseObjectData* object(0);
380 
381  if (PluginFunctions::getObject(id, object)) {
382 
383  _importer.setObject(object, i);
384 
385  object->setPath(_importer.path());
386  object->setName(name);
387  }
388  }
389 
390 #endif
391 
392 #ifdef ENABLE_BSPLINESURFACE_SUPPORT
393 
394  else if (_importer.isSurface( i )) {
395 
396  int id = -1;
397  emit addEmptyObject(DATA_BSPLINE_SURFACE, id);
398 
399  BaseObjectData* object(0);
400 
401  if (PluginFunctions::getObject(id, object)) {
402 
403  _importer.setObject(object, i);
404 
405  object->setPath(_importer.path());
406  object->setName(name);
407  }
408  }
409 
410 #endif
411 
412  //force gui settings
413  if (OpenFlipper::Options::gui() && loadOptions_ != 0) {
414 
415  if (!loadFaceColor_->isChecked())
416  _importer.setOption(OBJImporter::FORCE_NOCOLOR, i);
417 
418  if (!loadNormals_->isChecked())
419  _importer.setOption(OBJImporter::FORCE_NONORMALS, i);
420 
421  if (!loadTexCoords_->isChecked() || !loadTextures_->isChecked())
422  _importer.setOption(OBJImporter::FORCE_NOTEXTURES, i);
423 
424  }
425  }
426 }
427 
428 void FileOBJPlugin::convertToOBJName(QString& _name) {
429 
430  QFileInfo fi(_name);
431 
432  QString n = fi.baseName();
433 
434  _name = n.trimmed() + ".obj";
435 }
436 
438 template <class MeshT>
440 
441  // Create a backup of the original per Vertex texture Coordinates
442  if (_mesh.has_vertex_texcoords2D()) {
443 
445  if (!_mesh.get_property_handle(oldVertexCoords, "Original Per Vertex Texture Coords"))
446  _mesh.add_property(oldVertexCoords, "Original Per Vertex Texture Coords");
447 
448  for (typename MeshT::VertexIter v_it = _mesh.vertices_begin(); v_it != _mesh.vertices_end(); ++v_it)
449  _mesh.property(oldVertexCoords, *v_it) = _mesh.texcoord2D(*v_it);
450 
451  }
452 
453  // Create a backup of the original per Face texture Coordinates
454  if (_mesh.has_halfedge_texcoords2D()) {
455 
457  if (!_mesh.get_property_handle(oldHalfedgeCoords,"Original Per Face Texture Coords"))
458  _mesh.add_property(oldHalfedgeCoords,"Original Per Face Texture Coords");
459 
460  for (typename MeshT::HalfedgeIter he_it = _mesh.halfedges_begin(); he_it != _mesh.halfedges_end(); ++he_it)
461  _mesh.property(oldHalfedgeCoords, *he_it) = _mesh.texcoord2D(*he_it);
462 
463  }
464 }
465 
466 
467 //add textures to the mesh
468 void FileOBJPlugin::addTextures(OBJImporter& _importer, int _objectID ){
469 
470  // TODO : If only one Texture, use single Texturing mode
471  if ( true ) {
472 
473  BaseObject* object = _importer.object(_objectID);
474 
475  if (!object)
476  return;
477 
478  std::map< int,int > newMapping;
479  // zero ( no texture ) always maps to to zero
480  newMapping[0]=0;
481 
482  const std::vector<std::string> matNames = _importer.usedMaterials( _objectID );
483 
484  for (unsigned int i=0; i < matNames.size(); i++){
485 
486  Material& material = _importer.materials()[ matNames[i] ];
487 
488  int textureId = -1;
489 
490  QString textureBlock = QString( material.map_Kd().c_str());
491 
492 
493 
494 #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
495  QStringList options = textureBlock.split(" ",QString::SkipEmptyParts);
496 #else
497  QStringList options = textureBlock.split(" ",Qt::SkipEmptyParts);
498 #endif
499 
500  while ( options.size() > 1 ) {
501  if ( options[0] == "-blendu" ) {
502  options.pop_front();
503  options.pop_front();
504  } else if ( options[0] == "-blendv" ) {
505  options.pop_front();
506  options.pop_front();
507  } else if ( options[0] == "-cc" ) {
508  options.pop_front();
509  options.pop_front();
510  } else if ( options[0] == "-clamp" ) {
511  options.pop_front();
512  options.pop_front();
513  } else if ( options[0] == "-mm" ) {
514  options.pop_front();
515  options.pop_front();
516  options.pop_front();
517  } else if ( options[0] == "-o" ) {
518  options.pop_front();
519  options.pop_front();
520  options.pop_front();
521  options.pop_front();
522  } else if ( options[0] == "-s" ) {
523  options.pop_front();
524  options.pop_front();
525  options.pop_front();
526  options.pop_front();
527  } else if ( options[0] == "-t" ) {
528  options.pop_front();
529  options.pop_front();
530  options.pop_front();
531  options.pop_front();
532  } else if ( options[0] == "-texres" ) {
533  options.pop_front();
534  options.pop_front();
535  } else {
536  break;
537  }
538  }
539 
540  QString fullName = _importer.path() + QDir::separator() + options.join(" ");
541 
542  QFileInfo info(fullName);
543  if ( info.exists() ) {
544  emit addMultiTexture("OBJ Data", info.baseName().trimmed(), fullName, object->id(), textureId );
545  } else {
546  emit log(LOGWARN, tr("Unable to load texture image %1").arg( QString(material.map_Kd().c_str()) ) );
547  addMultiTexture("OBJ Data","Unknown Texture image " + QString::number(textureId), "unknown.png", object->id(), textureId );
548  }
549 
550  newMapping[ material.map_Kd_index() ] = textureId;
551  }
552 
553  //now map all texture indices to the real texture indices used in OpenFlipper
554 
555  OpenMesh::FPropHandleT< int > indexProperty;
556 
557  //handle PolyMeshes
558  PolyMeshObject* polyMeshObj = dynamic_cast< PolyMeshObject* > (object);
559 
560  if ( polyMeshObj ){
561 
562  PolyMesh& mesh = *(polyMeshObj->mesh());
563 
564  PolyMesh::FaceIter f_it;
565  PolyMesh::FaceIter f_end = mesh.faces_end();
566 
567  if (! mesh.get_property_handle(indexProperty,TEXTUREINDEX) )
568  return;
569 
570  for (f_it = mesh.faces_begin(); f_it != f_end; ++f_it)
571  mesh.property(indexProperty, *f_it) = newMapping[ mesh.property(indexProperty, *f_it) ];
572 
574 
575  return;
576  }
577 
578  //handle new TriMeshes
579  TriMeshObject* triMeshObj = dynamic_cast< TriMeshObject* > (object);
580 
581  if ( triMeshObj ){
582 
583  TriMesh& mesh = *(triMeshObj->mesh());
584 
585  TriMesh::FaceIter f_it;
586  TriMesh::FaceIter f_end = mesh.faces_end();
587 
588  if (! mesh.get_property_handle(indexProperty,TEXTUREINDEX) )
589  return;
590 
591  for (f_it = mesh.faces_begin(); f_it != f_end; ++f_it)
592  mesh.property(indexProperty, *f_it) = newMapping[ mesh.property(indexProperty, *f_it) ];
593 
595 
596  return;
597  }
598  }
599 }
600 
601 void FileOBJPlugin::readOBJFile(QByteArray& _bufferedFile, QString _filename, OBJImporter& _importer)
602 {
603  QString path = QFileInfo(_filename).absolutePath();
604  ptr::shared_ptr<QTextStream> streamPointer;
605  ptr::shared_ptr<QFile> sourceFile;
606 
608  if (_bufferedFile.isNull())
609  {
610  sourceFile.reset(new QFile(_filename) );
611  if(!sourceFile->open(QFile::ReadOnly))
612  {
613  emit log(LOGERR, tr("readOBJFile : cannot open file %1").arg(_filename) );
614  return;
615  }
616  //use the QTextStream and QString objects, since they seem to be more efficient when parsing strings.
617  //especially regarding copy operations.
618  streamPointer.reset( new QTextStream(sourceFile.get()));
619  }
620  else
621  {
622  streamPointer.reset( new QTextStream(&_bufferedFile));
623  }
624  QTextStream input(streamPointer->device());
625  input.seek(0);
626  QTextStream stream;
627  QTextStream lineData;
628  QTextStream tmp;
629  if ( input.status() != QTextStream::Ok){
630  emit log(LOGERR, tr("readOBJFile : cannot read file %1 is the file corrupt?").arg(_filename) );
631  return;
632  }
633 
634  QString currentFileName = QFileInfo(_filename).fileName() ;
635 
636  ReaderMode mode = NONE;
637 
638  QString line;
639  QString keyWrd;
640  QString nextKeyWrd = QLatin1String("");
641 
642 #ifdef ENABLE_BSPLINECURVE_SUPPORT
643  unsigned int curveCount = 0;
644 #endif
645 
646 #ifdef ENABLE_BSPLINESURFACE_SUPPORT
647  unsigned int surfaceCount = 0;
648 #endif
649 
650  float x, y, z, u, v;
651  int deg;
652 
653  std::vector<VertexHandle> vhandles;
654  std::vector<int> face_texcoords;
655  QString matname;
656  QString lastMaterial;
657 
658 #if defined (ENABLE_BSPLINECURVE_SUPPORT) || defined (ENABLE_BSPLINESURFACE_SUPPORT)
659  std::vector< int > cpIndices;
660  std::vector< double > knotsU,knotsV;
661 #endif
662 
663  int faceCount = 0;
664 
665  // We have to keep track of the already read number of vertices to resolve relative (negative indices)
666  int currentVertexCount = 0;
667 
668  // We have to keep track of the already read number of Texture coordinates to resolve relative (negative indices)
669  int currentTextureCoordCount = 0;
670 
671  // We have to keep track of the already read number of normals to resolve relative (negative indices)
672  int currentNormalCount = 0;
673 
674  // keeps track if faces belong to a group or the default group
675  bool inGroup = false;
676  // keeps track if the first face of a mesh has been read yet or not
677  bool firstFace = true;
678 
679  _importer.setPath( path );
680 
681  // Set filename for default mesh
682  _importer.setGroupName(0, currentFileName);
683 
684  // Now add all meshes for every group (if exists)
685  createAllGroupObjects(_importer);
686 
687  bool bSuppNoVertCoord = false;
688  bool bSuppTooManyTexCoord = false;
689  bool bSuppTooManyNormal = false;
690  bool bSuppErrorSettingTexCoord = false;
691 
692  while( !input.atEnd() )
693  {
694  line=input.readLine();
695  if ( input.status() == QTextStream::ReadCorruptData ){
696  emit log(LOGERR, tr("readOBJFile : Warning! Could not read file properly!"));
697  return;
698  }
699 
700  // Trim Both leading and trailing spaces
701  line = line.trimmed();
702 
703  // comment
704  if ( line.isEmpty() || line[0] == QLatin1Char('#') || line[0].isSpace() ) {
705  continue;
706  }
707 
708  stream.setString(&line,QIODevice::ReadOnly);
709 
710  //unless the keyWrd for the new line is not determined by the previous line
711  //read it from stream
712  if (nextKeyWrd == QLatin1String(""))
713  stream >> keyWrd;
714  else {
715  keyWrd = nextKeyWrd;
716  nextKeyWrd = QLatin1String("");
717  }
718 
719  // material file
720  if (mode == NONE && keyWrd == QLatin1String("mtllib"))
721  {
722  QString matString;
723 
724  // This will define the filename of the texture
725  matString = stream.readLine();
726 
727  QString matFile = path + QDir::separator() + matString.trimmed();
728 
729  emit log( tr("Loading material file: %1").arg( matFile ) );
730 
731  readMaterial( matFile, _importer );
732  }
733 
734  // usemtl
735  else if (mode == NONE && keyWrd == QLatin1String("usemtl"))
736  {
737  stream >> matname;
738  if ( _importer.materials().find(matname.toStdString())==_importer.materials().end() )
739  {
740  emit log( LOGERR, tr("Warning! Material '%1' not defined in material file").arg( matname ) );
741  matname=QLatin1String("");
742 
743  }else{
744 
745  Material& mat = _importer.materials()[matname.toStdString()];
746 
747  if ( mat.has_Texture() ){
748 
749  //add object if not already there
750  _importer.useMaterial( matname.toStdString() );
751 
752  }
753 
754  lastMaterial = matname;
755  }
756  }
757  else if (mode == NONE && keyWrd == QLatin1String("v"))
758  {
759  if (!firstFace)
760  firstFace = true;
761 
762  currentVertexCount++;
763  }
764  // texture coord
765  else if (mode == NONE && keyWrd == QLatin1String("vt"))
766  {
767  if (!firstFace)
768  firstFace = true;
769 
770  // New texture coordinate read so increase counter
771  currentTextureCoordCount++;
772 
773  u = getFloat(stream);
774  v = getFloat(stream);
775 
776  if ( stream.status() == QTextStream::Ok ){
777 
778  _importer.addTexCoord( OpenMesh::Vec2f(u, v) );
779 
780  }else{
781 
782  emit log( LOGERR, tr("Could not add TexCoord. Possible NaN or Inf?\nOnly single 2D texture coordinate per vertex allowed"));
783  }
784  }
785 
786 
787  // normal
788  else if (mode == NONE && keyWrd == QLatin1String("vn"))
789  {
790  if (!firstFace)
791  firstFace = true;
792 
793  // New normal read so increase counter
794  currentNormalCount++;
795 
796  x = getFloat(stream);
797  y = getFloat(stream);
798  z = getFloat(stream);
799 
800  if ( stream.status() == QTextStream::Ok ){
801  _importer.addNormal( OpenMesh::Vec3f(x,y,z) );
802  }else{
803  emit log( LOGERR, tr("Could not read normal. Possible NaN or Inf?"));
804  }
805  }
806 
807  // degree (for curves)
808  else if (mode == NONE && keyWrd == QLatin1String("deg"))
809  {
810  stream >> deg;
811 
812  if ( stream.status() == QTextStream::Ok )
813  _importer.setDegreeU( deg );
814 
815  stream >> deg;
816 
817  if ( stream.status() == QTextStream::Ok )
818  _importer.setDegreeV( deg );
819  }
820 
821  // group
822  else if (mode == NONE && keyWrd == QLatin1String("g")){
823  if (!firstFace)
824  firstFace = true;
825 
826  QString groupName;
827  groupName = stream.readLine();
828 
829  if(faceCount == 0) {
830  currentFileName = groupName;
831  }
832 
833  int id = _importer.groupId(groupName);
834  if(id == -1) {
835  std::cerr << "Error: Group has not been added before!" << std::endl;
836  return;
837  }
838  _importer.setCurrentGroup(id);
839  inGroup = true;
840 
841  faceCount = 0;
842 
843  if (lastMaterial != "" ) {
844  _importer.useMaterial(lastMaterial.toStdString());
845  }
846  }
847 
848  // face
849  else if (mode == NONE && keyWrd == QLatin1String("f"))
850  {
851  if (firstFace) {
852  // store faces in the default Group if we aren't in a group already
853  if (!inGroup)
854  _importer.setCurrentGroup(0);
855 
856  firstFace = false;
857  }
858 
859  int component(0), nV(0);
860  int value;
861 
862  vhandles.clear();
863  face_texcoords.clear();
864 
865  // read full line after detecting a face
866  QString faceLine;
867  faceLine = stream.readLine();
868  lineData.setString(&faceLine);
869 
870  // work on the line until nothing left to read
871  while ( !lineData.atEnd() )
872  {
873  // read one block from the line ( vertex/texCoord/normal )
874  QString vertex;
875  lineData >> vertex;
876 
877  do{
878 
879  //get the component (vertex/texCoord/normal)
880  int found=vertex.indexOf(QLatin1String("/"));
881 
882  // parts are seperated by '/' So if no '/' found its the last component
883  if( found != -1 ){
884 
885  // read the index value
886  QString vertexEntry = vertex.left(found);
887  tmp.setString( &vertexEntry );
888 
889  // If we get an empty string this property is undefined in the file
890  if ( vertexEntry.isEmpty() ) {
891  // Switch to next field
892  vertex = vertex.right(vertex.length()-(found+1));
893 
894  // Now we are at the next component
895  ++component;
896 
897  // Skip further processing of this component
898  continue;
899  }
900 
901  // Read current value
902  tmp >> value;
903 
904  // remove the read part from the string
905  vertex = vertex.right(vertex.length()-(found+1));
906 
907  } else {
908 
909  // last component of the vertex, read it.
910  tmp.setString( &vertex );
911  tmp >> value;
912 
913  // Clear vertex after finished reading the line
914  vertex=QLatin1String("");
915 
916  // Nothing to read here ( garbage at end of line )
917  if ( tmp.status() != QTextStream::Ok ) {
918  continue;
919  }
920  }
921 
922  // store the component ( each component is referenced by the index here! )
923  switch (component)
924  {
925  case 0: // vertex
926  if ( value < 0 ) {
927  // Calculation of index :
928  // -1 is the last vertex in the list
929  // As obj counts from 1 and not zero add +1
930  value = currentVertexCount + value + 1;
931  }
932 
933  // Obj counts from 1 and not zero .. array counts from zero therefore -1
934  vhandles.push_back( value-1 );
935  break;
936 
937  case 1: // texture coord
938  if ( value < 0 ) {
939  // Calculation of index :
940  // -1 is the last vertex in the list
941  // As obj counts from 1 and not zero add +1
942  value = currentTextureCoordCount + value + 1;
943  }
944  if (vhandles.empty())
945  {
946  if(!bSuppNoVertCoord) {
947  emit log (LOGWARN, tr("Texture coordinates defined, but no vertex coordinates found!"));
948  bSuppNoVertCoord=true;
949  }
950  break;
951  }
952  if ((unsigned int)(value-1) >= _importer.n_texCoords())
953  {
954  if(!bSuppTooManyTexCoord) {
955  emit log(LOGWARN, tr("Too many texcoords defined, skipping the rest"));
956  bSuppTooManyTexCoord=true;
957  }
958  break;
959  }
960 
961  if ( _importer.n_texCoords() > 0 ) {
962  // Obj counts from 1 and not zero .. array counts from zero therefore -1
963  _importer.setVertexTexCoord( vhandles.back(), value-1 );
964  face_texcoords.push_back( value-1 );
965  } else if(bSuppErrorSettingTexCoord){
966  emit log( LOGERR, tr("Error setting Texture coordinates") );
967  bSuppErrorSettingTexCoord=true;
968  }
969 
970  break;
971 
972  case 2: // normal
973  if ( value < 0 ) {
974  // Calculation of index :
975  // -1 is the last vertex in the list
976  // As obj counts from 1 and not zero add +1
977  value = currentNormalCount + value + 1;
978  }
979 
980  if (vhandles.empty())
981  {
982  if(!bSuppNoVertCoord) {
983  emit log (LOGWARN, tr("Texture coordinates defined, but no vertex coordinates found!"));
984  bSuppNoVertCoord=true;
985  }
986  break;
987  }
988 
989  if ((unsigned int)(value-1) >= _importer.n_normals())
990  {
991  if(!bSuppTooManyNormal)
992  {
993  emit log (LOGWARN, tr("Too many normals defined, skipping the rest"));
994  bSuppTooManyNormal=true;
995  }
996  break;
997  }
998 
999  // Obj counts from 1 and not zero .. array counts from zero therefore -1
1000  _importer.setNormal(vhandles.back(), value-1);
1001  break;
1002  }
1003 
1004  // Prepare for reading next component
1005  ++component;
1006 
1007  // Read until line does not contain any other info
1008  } while ( !vertex.isEmpty() );
1009 
1010  component = 0;
1011  nV++;
1012  }
1013 
1014  // remove vertices which can lead to degenerated faces
1015  remove_duplicated_vertices(vhandles);
1016 
1017  // from spec: A minimum of three vertices are required.
1018  if( vhandles.size() > 2 ){
1019 
1020  if ( !face_texcoords.empty() )
1021  //if we have texCoords add face+texCoords
1022  _importer.addFace(vhandles, face_texcoords );
1023  else
1024  //otherwise just add the face
1025  _importer.addFace(vhandles);
1026 
1027  faceCount++;
1028  }
1029 
1030 
1031  //add material to the last added face(s)
1032  //if polygons get triangulated this can be more than one face
1033  _importer.addMaterial( matname.toStdString() );
1034  }
1035 
1036 #ifdef ENABLE_BSPLINECURVE_SUPPORT
1037  // param
1038  else if ( (mode == CURVE && keyWrd == QLatin1String("parm")) || (mode == CURVE && keyWrd == QLatin1String("parm_add")) ){
1039 
1040  //get curve knots
1041  QString paramLine;
1042  QString tmp;
1043 
1044  paramLine = stream.readLine();
1045 
1046  // value may contain a / as line separator
1047  if ( paramLine.endsWith(QLatin1String("\\"))){
1048  paramLine = paramLine.left( paramLine.length()-1);
1049  nextKeyWrd = QLatin1String("parm_add");
1050  }
1051 
1052  lineData.setString( &paramLine );
1053 
1054  if ( keyWrd != QLatin1String("parm_add"))
1055  lineData >> tmp; //push the first u out
1056 
1057  // work on the line until nothing left to read
1058  while ( !lineData.atEnd() && lineData.status()==QTextStream::Ok )
1059  {
1060 
1061  double knot;
1062 
1063  knot = getDouble(lineData);
1064 
1065  if ( lineData.status() == QTextStream::Ok )
1066  knotsU.push_back( knot );
1067  }
1068  }
1069 
1070  // curve
1071  else if ( (mode == NONE && keyWrd == QLatin1String("curv")) || (mode == CURVE && keyWrd == QLatin1String("curv_add")) ){
1072  if (!firstFace)
1073  firstFace = true;
1074 
1075  inGroup = false;
1076 
1077  mode = CURVE;
1078 
1079  if ( keyWrd == QLatin1String("curv") )
1080  {
1081  int id = _importer.getCurveGroupId(curveCount);
1082  if(id == -1) {
1083  std::cerr << "Error: Group has not been added before!" << std::endl;
1084  return;
1085  }
1086  _importer.setCurrentGroup(id);
1087  curveCount++;
1088  }
1089 
1090  //get curve control points
1091  QString curveLine;
1092 
1093  curveLine = stream.readLine();
1094 
1095  // value may contain a / as line separator
1096  if ( curveLine.endsWith(QLatin1String("\\"))){
1097  curveLine = curveLine.left(curveLine.length()-1);
1098  nextKeyWrd = QLatin1String("curv_add");
1099  }
1100 
1101  lineData.setString( &curveLine );
1102 
1103  // Read knots at the beginning before the indices
1104  if ( keyWrd == QLatin1String("curv") ) {
1105  // skip next two doubles in stream
1106  getDouble(lineData);
1107  getDouble(lineData);
1108  }
1109 
1110 
1111 
1112  // work on the line until nothing left to read
1113  while ( !lineData.atEnd() && lineData.status()==QTextStream::Ok )
1114  {
1115  int index = 0;
1116 
1117  lineData >> index;
1118 
1119  if ( index < 0 ) {
1120  // Calculation of index :
1121  // -1 is the last vertex in the list
1122  // As obj counts from 1 and not zero add +1
1123  index = currentVertexCount + index + 1;
1124  }
1125 
1126  if ( lineData.status()==QTextStream::Ok )
1127  cpIndices.push_back( index -1 );
1128  }
1129  }
1130 
1131  // end
1132  else if (mode == CURVE && keyWrd == QLatin1String("end")){
1133 
1134  if ( _importer.isCurve( _importer.currentGroup() ) ){
1135  // set up the spline curve
1136  _importer.currentCurve()->set_degree( _importer.degreeU() );
1137  _importer.currentCurve()->autocompute_knotvector(false);
1138 
1139  // add the control points
1140  std::vector< ACG::Vec3d > controlPolygon;
1141 
1142  for (unsigned int i = 0; i < cpIndices.size(); ++i)
1143  controlPolygon.push_back( (ACG::Vec3d) _importer.vertex( cpIndices[i] ) );
1144 
1145  _importer.currentCurve()->set_control_polygon( controlPolygon );
1146 
1147  _importer.currentCurve()->set_knots(knotsU);
1148  }
1149 
1150  cpIndices.clear();
1151  knotsU.clear();
1152 
1153  mode = NONE;
1154  }
1155 #endif
1156 
1157 #ifdef ENABLE_BSPLINESURFACE_SUPPORT
1158  // param
1159  else if ( (mode == SURFACE && keyWrd == QLatin1String("parm")) || (mode == SURFACE && keyWrd == QLatin1String("parm_add")) ){
1160 
1161  //get surface knots
1162  QString paramLine;
1163  QString tmp;
1164 
1165  paramLine = stream.readLine();
1166 
1167  // value may contain a / as line separator
1168  if ( paramLine.endsWith(QLatin1String("\\"))){
1169  paramLine = paramLine.left(paramLine.length()-1);
1170  nextKeyWrd = QLatin1String("parm_add");
1171  }
1172 
1173  lineData.setString( &paramLine );
1174 
1175  if ( keyWrd == QLatin1String("parm_add_u"))
1176  tmp = QLatin1String("u");
1177  else if ( keyWrd == QLatin1String("parm_add_v"))
1178  tmp = QLatin1String("v");
1179  else
1180  lineData >> tmp; //get the direction (u or v)
1181 
1182  std::vector< double >* knots;
1183 
1184  //Decide if these are knots in U or V direction
1185  if (tmp == QLatin1String("u"))
1186  knots = &knotsU;
1187  else
1188  knots = &knotsV;
1189 
1190  if (nextKeyWrd != QLatin1String(""))
1191  nextKeyWrd += QLatin1String("_") + tmp;
1192 
1193  // work on the line until nothing left to read
1194  while ( !lineData.atEnd() && lineData.status()==QTextStream::Ok )
1195  {
1196 
1197  double knot;
1198 
1199  knot = getDouble(lineData);
1200 
1201  if ( lineData.status()==QTextStream::Ok ) {
1202  knots->push_back( knot );
1203  }
1204  }
1205  }
1206 
1207  // surface
1208  else if ( (mode == NONE && keyWrd == QLatin1String("surf")) || (mode == SURFACE && keyWrd == QLatin1String("surf_add")) ){
1209  if (!firstFace)
1210  firstFace = true;
1211 
1212  inGroup = false;
1213 
1214  mode = SURFACE;
1215 
1216  if ( keyWrd == QLatin1String("surf") )
1217  {
1218  int id = _importer.getSurfaceGroupId(surfaceCount);
1219  if(id == -1) {
1220  std::cerr << "Error: Group has not been added before!" << std::endl;
1221  return;
1222  }
1223  _importer.setCurrentGroup(id);
1224  surfaceCount++;
1225  }
1226 
1227  //get surface control points
1228  QString surfLine;
1229 
1230  surfLine = stream.readLine();
1231 
1232  // value may contain a / as line separator
1233  if ( surfLine.endsWith(QLatin1String("\\"))){
1234  surfLine = surfLine.left(surfLine.length()-1);
1235  nextKeyWrd = QLatin1String("surf_add");
1236  }
1237 
1238  lineData.setString( &surfLine );
1239 
1240  // work on the line until nothing left to read
1241  while ( !lineData.atEnd() && lineData.status()==QTextStream::Ok )
1242  {
1243  int index = 0;
1244 
1245  lineData >> index;
1246 
1247  if ( index < 0 ) {
1248  // Calculation of index :
1249  // -1 is the last vertex in the list
1250  // As obj counts from 1 and not zero add +1
1251  index = currentVertexCount + index + 1;
1252  }
1253 
1254  if ( lineData.status()==QTextStream::Ok )
1255  cpIndices.push_back( index -1 );
1256  }
1257  }
1258 
1259  // end
1260  else if (mode == SURFACE && keyWrd == QLatin1String("end")){
1261 
1262  if ( _importer.isSurface( _importer.currentGroup() ) ){
1263 
1264  // remove first 4 entries since they are the first and last knot (for both direction)
1265  cpIndices.erase(cpIndices.begin());
1266  cpIndices.erase(cpIndices.begin());
1267  cpIndices.erase(cpIndices.begin());
1268  cpIndices.erase(cpIndices.begin());
1269 
1270  // set up the spline surface
1271  _importer.currentSurface()->set_degree( _importer.degreeU(), _importer.degreeV() );
1272 
1273  // compute number of control points in m and in n direction
1274  int dimU = knotsU.size() - _importer.degreeU() - 1;
1275  int dimV = knotsV.size() - _importer.degreeV() - 1;
1276 
1277  // add the control points
1278  std::vector< ACG::Vec3d > controlPolygon;
1279 
1280  for (int i = 0; i < dimU; ++i)
1281  {
1282  controlPolygon.clear();
1283 
1284  for (int j = 0; j < dimV; ++j){
1285  controlPolygon.push_back( (ACG::Vec3d) _importer.vertex( cpIndices[dimU * j + i] ) );
1286  }
1287 
1288  _importer.currentSurface()->add_vector_m(controlPolygon);
1289  }
1290 
1291  _importer.currentSurface()->set_knots_m(knotsU);
1292  _importer.currentSurface()->set_knots_n(knotsV);
1293 
1294 
1295  }
1296 
1297  cpIndices.clear();
1298  knotsU.clear();
1299  knotsV.clear();
1300 
1301  mode = NONE;
1302  }
1303 #endif
1304 
1305  }
1306  //checks, if an object with a specified type was added. if not, point cloud was read
1307  bool isType = faceCount != 0;
1308 
1309 #ifdef ENABLE_BSPLINECURVE_SUPPORT
1310  isType = isType || curveCount != 0;
1311 #endif
1312 
1313 #ifdef ENABLE_BSPLINESURFACE_SUPPORT
1314  isType = isType || surfaceCount != 0;
1315 #endif
1316 
1317  // we have only read points so far and no faces or modes
1318  // treat them as a polymesh
1319  if (!isType && currentVertexCount != 0 ) {
1320  _importer.forceMeshType( OBJImporter::POLYMESH ); //actually it is a pointcloud
1321  if (!inGroup)
1322  _importer.setCurrentGroup(0);
1323  }
1324 
1325 }
1326 
1328 void FileOBJPlugin::checkTypes(QByteArray& _bufferedFile, QString _filename, OBJImporter& _importer, QStringList& _includes)
1329 {
1330  ptr::shared_ptr<QTextStream> streamPointer;
1331  ptr::shared_ptr<QFile> sourceFile;
1332  //setup filestream if not in memory
1333  if (_bufferedFile.isNull() || _bufferedFile.isEmpty())
1334  {
1335 
1336  sourceFile.reset(new QFile(_filename));
1337  if(!sourceFile->open(QFile::ReadOnly))
1338  {
1339  emit log(LOGERR, tr("readOBJFile : cannot open file %1 while checking Types").arg(_filename) );
1340  return;
1341  }
1342  streamPointer.reset(new QTextStream(sourceFile.get()));
1343  }
1344  else
1345  {
1346  streamPointer.reset(new QTextStream(&_bufferedFile));
1347  }
1348  QTextStream input(streamPointer->device());
1349  QTextStream stream;
1350  QTextStream lineData;
1351  QTextStream tmp;
1352 
1353  if ( input.status()!=QTextStream::Ok ){
1354  emit log(LOGERR, tr("readOBJFile : cannot read file %1 while checking Types (is the file corrupt?)").arg(_filename) );
1355  return;
1356  }
1357 
1358  ReaderMode mode = NONE;
1359 
1360  QString line;
1361  QString keyWrd;
1362  QString nextKeyWrd = QLatin1String("");
1363 
1364 #ifdef ENABLE_BSPLINECURVE_SUPPORT
1365  unsigned int curveCount = 0;
1366 #endif
1367 
1368 #ifdef ENABLE_BSPLINESURFACE_SUPPORT
1369  unsigned int surfaceCount = 0;
1370 #endif
1371 
1372  float x, y, z;
1373  int faceCount = 0;
1374 
1375  int PolyMeshCount = 0;
1376  int TriMeshCount = 0;
1377 
1378  OBJImporter::ObjectOptions options = OBJImporter::NONE;
1379 
1380  // keeps track if faces belong to a group or the default group
1381  bool inGroup = false;
1382  // keeps track if the first face of a mesh has been read yet or not
1383  bool firstFace = true;
1384 
1385 #if defined ENABLE_BSPLINECURVE_SUPPORT || defined ENABLE_BSPLINESURFACE_SUPPORT
1386  QString currentGroupName;
1387  int parentId = -1;
1388 #endif
1389 
1390  while( !input.atEnd())
1391  {
1392  line = input.readLine();
1393  if ( input.status()!=QTextStream::Ok ){
1394  emit log(LOGERR, tr("readOBJFile : Warning! Could not read file properly!"));
1395  return;
1396  }
1397 
1398  // Trim Both leading and trailing spaces
1399  line = line.trimmed();
1400 
1401  // comment
1402  if ( line.isEmpty() || line[0] == QLatin1Char('#') || line[0].isSpace() ) {
1403  continue;
1404  }
1405 
1406  stream.setString(&line);
1407 
1408  //unless the keyWrd for the new line is not determined by the previous line
1409  //read it from stream
1410  if (nextKeyWrd == QLatin1String(""))
1411  stream >> keyWrd;
1412  else {
1413  keyWrd = nextKeyWrd;
1414  nextKeyWrd = QLatin1String("");
1415  }
1416 
1417  //call - included obj files
1418  if (mode == NONE && keyWrd == QLatin1String("call")){
1419  firstFace = true;
1420 
1421  QString include;
1422  include =stream.readLine();
1423 
1424  //replace relative path
1425  QString includeStr = include.trimmed();
1426 
1427  if ( !includeStr.isEmpty() ){
1428 
1429  if (includeStr[0] == QLatin1Char('.')){
1430  includeStr = includeStr.right( includeStr.length()-1 );
1431 
1432  QFileInfo fi(_filename);
1433 
1434  includeStr = fi.path() + QDir::separator() + includeStr;
1435  }
1436 
1437  _includes.append( includeStr );
1438  }
1439 
1440  _importer.setObjectOptions(OBJImporter::NONE);
1441  }
1442 
1443  // vertex
1444  else if (mode == NONE && keyWrd == QLatin1String("v"))
1445  {
1446  if (!firstFace)
1447  firstFace = true;
1448 
1449  x = getFloat(stream);
1450  y = getFloat(stream);
1451  z = getFloat(stream);
1452 
1453  if ( stream.status()==QTextStream::Ok )
1454  _importer.addVertex( OpenMesh::Vec3f(x,y,z) );
1455  else
1456  emit log(LOGERR, tr("Could not add Vertex %1. Possible NaN or Inf?").arg(_importer.n_vertices()));
1457  }
1458 
1459  // group
1460  else if (mode == NONE && keyWrd == QLatin1String("g")){
1461  if (!firstFace)
1462  firstFace = true;
1463 
1464  //give options to importer and reinitialize
1465  //for next object
1466 
1467  QString grpName;
1468  grpName = stream.readLine();
1469 
1470  if ( options & OBJImporter::TRIMESH ) TriMeshCount++;
1471  if ( options & OBJImporter::POLYMESH ) PolyMeshCount++;
1472 
1473  int id = _importer.addGroup(grpName);
1474 #if defined ENABLE_BSPLINECURVE_SUPPORT || defined ENABLE_BSPLINESURFACE_SUPPORT
1475  parentId = id;
1476  currentGroupName = grpName;
1477  currentGroupName.remove(QLatin1String(".obj"));
1478 #endif
1479  _importer.setCurrentGroup(id);
1480 
1481  // all following elements are in this group until
1482  // a new group is created
1483  inGroup = true;
1484 
1485  _importer.setObjectOptions( options );
1486  options = OBJImporter::NONE;
1487  faceCount = 0;
1488  }
1489 
1490  // face
1491  else if (mode == NONE && keyWrd == QLatin1String("f")){
1492 
1493  if (firstFace) {
1494  // store faces in the default group if we aren't in a group already
1495  if (!inGroup)
1496  _importer.setCurrentGroup(0);
1497  firstFace = false;
1498  }
1499 
1500  faceCount++;
1501 
1502  int verticesPerFace = 0;
1503  int value;
1504 
1505  // read full line after detecting a face
1506  QString faceLine;
1507  faceLine = stream.readLine();
1508  lineData.setString( &faceLine );
1509 
1510  // work on the line until nothing left to read
1511  while ( !lineData.atEnd() )
1512  {
1513  // read one block from the line ( vertex/texCoord/normal )
1514  QString vertex;
1515  lineData >> vertex;
1516 
1517  verticesPerFace++;
1518 
1519 
1520  //get the vertex component (vertex/texCoord/normal)
1521  int found=vertex.indexOf(QLatin1String("/"));
1522 
1523  // parts are seperated by '/' So if no '/' found its the last component
1524  if( found != -1 ){
1525 
1526  // read the index value
1527  QString vertexEntry = vertex.left(found);
1528  tmp.setString( &vertexEntry );
1529 
1530  // Read current value
1531  tmp >> value;
1532 
1533  if ( tmp.status()!=QTextStream::Ok )
1534  emit log(LOGERR, tr("readOBJFile : Error reading vertex index!"));
1535 
1536  } else {
1537 
1538  // last component of the vertex, read it.
1539  tmp.setString( &vertex );
1540  tmp >> value;
1541 
1542  if ( tmp.status()!=QTextStream::Ok )
1543  emit log(LOGERR, tr("readOBJFile : Error reading vertex index!"));
1544  }
1545 
1546 
1547  if ( value < 0 ) {
1548  // Calculation of index :
1549  // -1 is the last vertex in the list
1550  // As obj counts from 1 and not zero add +1
1551  value = _importer.n_vertices() + value + 1;
1552  }
1553 
1554  // Obj counts from 1 and not zero .. array counts from zero therefore -1
1555  // the importer has to know which vertices are used by the object for correct vertex order
1556  _importer.useVertex( value -1 );
1557  }
1558 
1559 
1560  if( verticesPerFace > 3 ) {
1561  options = OBJImporter::POLYMESH;
1562  _importer.setObjectOptions(options);
1563  } else if ( verticesPerFace == 3 ) {
1564  options = OBJImporter::TRIMESH;
1565  _importer.setObjectOptions(options);
1566  }
1567  }
1568 
1569 #ifdef ENABLE_BSPLINECURVE_SUPPORT
1570 
1571  // curve
1572  if ( (mode == NONE && keyWrd == QLatin1String("curv")) || (mode == CURVE && keyWrd == QLatin1String("curv_add")) ){
1573  if (!firstFace)
1574  firstFace = true;
1575 
1576  inGroup = false;
1577 
1578  mode = CURVE;
1579 
1580  if ( keyWrd == QLatin1String("curv") ) {
1581 
1582  //give options to importer and reinitialize
1583  //for next object
1584  if ( options & OBJImporter::TRIMESH ) TriMeshCount++;
1585  if ( options & OBJImporter::POLYMESH ) PolyMeshCount++;
1586 
1587  QString name = currentGroupName;
1588  if (name.size() == 0)
1589  name = QLatin1String("DefaultGroup");
1590 
1591  name.append(QString("_curve_%1").arg(curveCount));
1592  int id = _importer.addGroup(name);
1593 
1594  if (_importer.numCurves() == 0) {
1595  if (currentGroupName.size() == 0)
1596  _importer.setGroupName(id, QString("DefaultGroup"));
1597  else
1598  _importer.setGroupName(id, currentGroupName);
1599  } else {
1600  if (curveCount == 1) {
1601  int first = _importer.getCurveGroupId(0);
1602  QString tmp = _importer.groupName(first);
1603  tmp.append(QString("_curve_0"));
1604  _importer.setGroupName(first, tmp);
1605  }
1606  _importer.setGroupName(id, name);
1607  }
1608  _importer.setCurveParentId(id, parentId);
1609  _importer.setCurrentGroup(id);
1610  _importer.setCurveGroupId(curveCount, id);
1611  curveCount++;
1612 
1613  _importer.setObjectOptions( options );
1614 
1615  options = OBJImporter::CURVE;
1616  }
1617 
1618  //get curve control points
1619  QString curveLine;
1620 
1621  curveLine=stream.readLine();
1622 
1623  // value may contain a / as line separator
1624  if ( curveLine.endsWith(QLatin1String("\\"))){
1625  curveLine = curveLine.left( curveLine.length()-1);
1626  nextKeyWrd = QLatin1String("curv_add");
1627  }
1628 
1629  lineData.setString( &curveLine );
1630 
1631  // Read knots at the beginning before the indices
1632  if ( keyWrd == QLatin1String("curv") ) {
1633  // skip next two doubles in stream
1634  getDouble(lineData);
1635  getDouble(lineData);
1636  }
1637 
1638  // work on the line until nothing left to read
1639  while ( !lineData.atEnd() && lineData.status()==QTextStream::Ok )
1640  {
1641  int index = 0;
1642 
1643  lineData >> index;
1644 
1645  if ( lineData.status()==QTextStream::Ok ){
1646  // the importer has to know which vertices are used by the object for correct vertex order
1647  _importer.useVertex( index -1 );
1648  }
1649  }
1650 
1651  }
1652 
1653  // end
1654  else if (mode == CURVE && keyWrd == QLatin1String("end")){
1655 
1656  mode = NONE;
1657 
1658  _importer.setObjectOptions( options );
1659  options = OBJImporter::TRIMESH;
1660  faceCount = 0;
1661  }
1662 #endif
1663 
1664 #ifdef ENABLE_BSPLINESURFACE_SUPPORT
1665 
1666  // surface
1667  if ( (mode == NONE && keyWrd == QLatin1String("surf")) || (mode == SURFACE && keyWrd == QLatin1String("surf_add")) ){
1668  if (!firstFace)
1669  firstFace = true;
1670 
1671  inGroup = false;
1672 
1673  mode = SURFACE;
1674 
1675  if ( keyWrd == QLatin1String("surf") ){
1676 
1677  //give options to importer and reinitialize
1678  //for next object
1679  if ( options & OBJImporter::TRIMESH ) TriMeshCount++;
1680  if ( options & OBJImporter::POLYMESH ) PolyMeshCount++;
1681 
1682  QString name = currentGroupName;
1683  if (name.size() == 0)
1684  name = QLatin1String("DefaultGroup");
1685 
1686  name.append(QString("_surface_%1").arg(surfaceCount));
1687  int id = _importer.addGroup(name);
1688 
1689  if (_importer.numSurfaces() == 0) {
1690  if (currentGroupName.size() == 0)
1691  _importer.setGroupName(id, "DefaultGroup");
1692  else
1693  _importer.setGroupName(id, currentGroupName);
1694  } else {
1695  if (surfaceCount == 1) {
1696  int first = _importer.getSurfaceGroupId(0);
1697  QString tmp = _importer.groupName(first);
1698  tmp.append(QString("_surface_0"));
1699  _importer.setGroupName(first, tmp);
1700  }
1701  _importer.setGroupName(id, name);
1702  }
1703  _importer.setSurfaceParentId(id, parentId);
1704  _importer.setCurrentGroup(id);
1705  _importer.setSurfaceGroupId(surfaceCount, id);
1706  surfaceCount++;
1707 
1708  _importer.setObjectOptions( options );
1709 
1710  options = OBJImporter::SURFACE;
1711  }
1712 
1713  //get surface control points
1714  QString surfLine;
1715 
1716  surfLine = stream.readLine();
1717 
1718  // value may contain a / as line separator
1719  if ( surfLine.endsWith(QLatin1String("\\"))){
1720  surfLine = surfLine.left(surfLine.length()-1);
1721  nextKeyWrd = QLatin1String("surf_add");
1722  }
1723 
1724  lineData.setString( &surfLine );
1725 
1726  // work on the line until nothing left to read
1727  while ( !lineData.atEnd() && lineData.status()==QTextStream::Ok )
1728  {
1729  int index = 0;
1730 
1731  lineData >> index;
1732 
1733  if ( lineData.status()==QTextStream::Ok ){
1734  // the importer has to know which vertices are used by the object for correct vertex order
1735  _importer.useVertex( index -1 );
1736  }
1737  }
1738  }
1739 
1740  // end
1741  else if (mode == SURFACE && keyWrd == QLatin1String("end")){
1742 
1743  mode = NONE;
1744 
1745  _importer.setObjectOptions( options );
1746  options = OBJImporter::TRIMESH;
1747  faceCount = 0;
1748  }
1749 #endif
1750 
1751  }
1752 
1753  if(faceCount > 0) {
1754  if ( options & OBJImporter::TRIMESH ) TriMeshCount++;
1755  if ( options & OBJImporter::POLYMESH ) PolyMeshCount++;
1756  _importer.setObjectOptions( options );
1757  } else {
1758  // Mesh does not contain any faces
1759  PolyMeshCount++;
1760  if (keyWrd != QLatin1String("call")) {
1761  // we only have vertices and no faces
1762  if (keyWrd == QLatin1String("v") && !inGroup) {
1763  _importer.setCurrentGroup(0);
1764  // treat the file as a polymesh
1765  forceTriangleMesh_ = false;
1766  forcePolyMesh_ = true;
1767  _importer.setObjectOptions(OBJImporter::POLYMESH);
1768  _importer.forceMeshType( OBJImporter::POLYMESH );
1769  for (unsigned int i = 0; i < _importer.n_vertices(); ++i)
1770  _importer.useVertex(i);
1771  } else {
1772  // this is only a triangle mesh if the object is not a curve and not a surface
1773  // also ignore if it is set to NONE
1774  if (!(_importer.isCurve(_importer.currentGroup())) &&
1775  !(_importer.isSurface(_importer.currentGroup())) &&
1776  (_importer.isNone(_importer.currentGroup())) )
1777  _importer.setObjectOptions(OBJImporter::TRIMESH);
1778  }
1779  }
1780  }
1781 
1782  if (TriMeshCount == 0 && PolyMeshCount == 0)
1783  {
1784  return;
1785  }
1786 
1787  if (forceTriangleMesh_){
1788  _importer.forceMeshType( OBJImporter::TRIMESH );
1789  return;
1790  }
1791 
1792  if (forcePolyMesh_){
1793  _importer.forceMeshType( OBJImporter::POLYMESH );
1794  return;
1795  }
1796 
1797  // If we do not have a gui, we will always use the last default
1798  // If we need a gui and the triMeshHandling box is not generated (==0) we also use the last default
1799  if ( OpenFlipper::Options::gui() && triMeshHandling_ != 0 ){
1800 
1801  switch( triMeshHandling_->currentIndex() ){
1802  case TYPEAUTODETECT : //Detect
1803  break;
1804 
1805  case TYPEASK: //ask
1806  QMetaObject::invokeMethod(this,"handleTrimeshDialog",Qt::BlockingQueuedConnection);
1807  if (trimeshOptions_ == OBJImporter::TRIMESH )
1808  _importer.forceMeshType( OBJImporter::TRIMESH );
1809  else if (trimeshOptions_ == OBJImporter::POLYMESH)
1810  _importer.forceMeshType( OBJImporter::POLYMESH );
1811 
1812  break;
1813 
1814  case TYPEPOLY : //polyMesh
1815  _importer.forceMeshType( OBJImporter::POLYMESH ); break;
1816 
1817  case TYPETRIANGLE : //trimesh
1818  _importer.forceMeshType( OBJImporter::TRIMESH ); break;
1819 
1820  default: break;
1821 
1822  }
1823 
1824  }
1825 
1826 }
1827 
1828 void FileOBJPlugin::handleTrimeshDialog()
1829 {
1830  QMessageBox msgBox;
1831  QPushButton *detectButton = msgBox.addButton(tr("Auto-Detect"), QMessageBox::ActionRole);
1832  QPushButton *triButton = msgBox.addButton(tr("Open as triangle mesh"), QMessageBox::ActionRole);
1833  QPushButton *polyButton = msgBox.addButton(tr("Open as poly mesh"), QMessageBox::ActionRole);
1834  msgBox.setWindowTitle( tr("Mesh types in file") );
1835  msgBox.setText( tr("You are about to open a file containing one or more mesh types. \n\n Which mesh type should be used?") );
1836  msgBox.setDefaultButton( detectButton );
1837  msgBox.exec();
1838 
1839 
1840  if (msgBox.clickedButton() == triButton)
1841  trimeshOptions_ = OBJImporter::TRIMESH ;
1842  else if (msgBox.clickedButton() == polyButton)
1843  trimeshOptions_ = OBJImporter::POLYMESH ;
1844 }
1845 
1846 //-----------------------------------------------------------------------------------------------------
1847 
1848 int FileOBJPlugin::loadObject(QString _filename) {
1849 
1850  OBJImporter importer;
1851 
1852  //included filenames
1853  QStringList includes;
1854 
1855  QFile sourceFile(_filename);
1856  if (!sourceFile.open(QFile::ReadOnly))
1857  {
1858  emit log(LOGERR, tr("readOBJFile : cannot open file %1 while checking Types").arg(_filename));
1859  return -1;
1860  }
1861  QByteArray bufferedFile = QByteArray();
1862  //load the entire file to ram if we have at least double the size as free memory.
1863  //otherwise the bytearray stays null and the file is read when it is processed.
1864  unsigned long freeMem = Utils::Memory::queryFreeRAM();
1865  unsigned long fs = sourceFile.size() / 1024 / 1024;
1866  if (freeMem >= 2*fs)
1867  {
1868  bufferedFile = sourceFile.readAll();
1869  }
1870 
1871  //preprocess file and store types in ObjectOptions
1872  checkTypes( bufferedFile, _filename, importer, includes );
1873 
1874  IdList objIDs;
1875 
1876  //load included obj files
1877  for (int i=0; i < includes.size(); i++){
1878 
1879  //int id = loadObject( includes[i], importer );
1880  int id = loadObject( includes[i] );
1881 
1882  if (id != -1)
1883  objIDs.push_back( id );
1884  }
1885 
1886  //add a group if we have includes
1887  if ( ! includes.empty() )
1888  importer.addGroup( QFileInfo(_filename).fileName() );
1889 
1890  //check if something was found
1891  if ( importer.noOptions() && objIDs.empty() ){
1892 
1893  forceTriangleMesh_ = false;
1894  forcePolyMesh_ = false;
1895 
1896  return -1;
1897  }
1898 
1899  //then parse the obj
1900  readOBJFile( bufferedFile, _filename, importer );
1901 
1902  // finish up
1903  importer.finish();
1904 
1905  int returnID = -1;
1906 
1907  //perhaps add group
1908  if ( importer.numGroups() > 1){
1909 
1910  bool dataControlExists = false;
1911  pluginExists( "datacontrol", dataControlExists );
1912 
1913  if ( dataControlExists ){
1914 #if defined ENABLE_BSPLINECURVE_SUPPORT || defined ENABLE_BSPLINESURFACE_SUPPORT
1915  std::map<int, QString> groupNames;
1916 #endif
1917 
1918 #ifdef ENABLE_BSPLINECURVE_SUPPORT
1919  std::vector< std::vector<int> > curveGroups;
1920  std::vector<int> curveIds;
1921  int lastCurveParent = -2;
1922 #endif
1923 
1924 #ifdef ENABLE_BSPLINESURFACE_SUPPORT
1925  std::vector< std::vector<int> > surfaceGroups;
1926  std::vector<int> surfaceIds;
1927  int lastSurfaceParent = -2;
1928 #endif
1929  for(unsigned int i = 0; i < importer.groupCount(); i++) {
1930  // skip the object if it has no option
1931  // this can happen if the object only included other objects
1932  if ( !importer.isNone(i) ) {
1933  BaseObject* obj = importer.object(i);
1934  if(obj) {
1935 #ifdef ENABLE_BSPLINECURVE_SUPPORT
1936  if ( importer.isCurve(i) ) {
1937  // store the parent group name for later grouping
1938  groupNames[obj->id()] = importer.groupName(importer.getCurveParentId(i));
1939 
1940  // first curve group
1941  if (lastCurveParent == -2) {
1942  lastCurveParent = importer.getCurveParentId(i);
1943  curveIds.push_back(obj->id());
1944  BaseObject* parent = importer.object(lastCurveParent);
1945  if (parent) {
1946  curveIds.push_back(parent->id());
1947  // don't group the parent in the objIDs group
1948  std::vector<int>::iterator pos = std::find(objIDs.begin(), objIDs.end(), parent->id());
1949  if (pos != objIDs.end())
1950  objIDs.erase(pos);
1951  }
1952  // new curve group
1953  } else if (lastCurveParent != importer.getCurveParentId(i)) {
1954  lastCurveParent = importer.getCurveParentId(i);
1955  curveGroups.push_back(curveIds);
1956  curveIds.clear();
1957  curveIds.push_back(obj->id());
1958  BaseObject* parent = importer.object(lastCurveParent);
1959  if (parent) {
1960  curveIds.push_back(parent->id());
1961  // don't group the parent in the objIDs group
1962  std::vector<int>::iterator pos = std::find(objIDs.begin(), objIDs.end(), parent->id());
1963  if (pos != objIDs.end())
1964  objIDs.erase(pos);
1965  }
1966  // add curves to group
1967  } else
1968  curveIds.push_back(obj->id());
1969  }
1970 
1971 #endif
1972 #ifdef ENABLE_BSPLINESURFACE_SUPPORT
1973  if ( importer.isSurface(i)) {
1974  // store the parent group name for later grouping
1975  groupNames[obj->id()] = importer.groupName(importer.getSurfaceParentId(i));
1976 
1977  // first surface group
1978  if (lastSurfaceParent == -2) {
1979  lastSurfaceParent = importer.getSurfaceParentId(i);
1980  surfaceIds.push_back(obj->id());
1981  BaseObject* parent = importer.object(lastSurfaceParent);
1982  if (parent) {
1983  surfaceIds.push_back(parent->id());
1984  std::vector<int>::iterator pos = std::find(objIDs.begin(), objIDs.end(), parent->id());
1985  if (pos != objIDs.end())
1986  objIDs.erase(pos);
1987  }
1988  // new surface group
1989  } else if (lastSurfaceParent != importer.getSurfaceParentId(i)) {
1990  lastSurfaceParent = importer.getSurfaceParentId(i);
1991  surfaceGroups.push_back(surfaceIds);
1992  surfaceIds.clear();
1993  surfaceIds.push_back(obj->id());
1994  BaseObject* parent = importer.object(lastSurfaceParent);
1995  if (parent) {
1996  surfaceIds.push_back(parent->id());
1997  std::vector<int>::iterator pos = std::find(objIDs.begin(), objIDs.end(), parent->id());
1998  if (pos != objIDs.end())
1999  objIDs.erase(pos);
2000  }
2001  // add surfaces to group
2002  } else
2003  surfaceIds.push_back(obj->id());
2004 
2005  }
2006 #endif
2007  if ( (importer.isTriangleMesh(i) || importer.isPolyMesh(i) ) )
2008  objIDs.push_back( obj->id() );
2009  } else {
2010  std::cerr << "Object is NULL!" << std::endl;
2011  }
2012  }
2013  }
2014 
2015 #ifdef ENABLE_BSPLINECURVE_SUPPORT
2016  // add last group
2017  curveGroups.push_back(curveIds);
2018  std::vector< std::vector<int> >::iterator it = curveGroups.begin();
2019  for (; it != curveGroups.end(); ++it) {
2020  // only group if we have more than one curve
2021  if (it->size() > 2) {
2022  if (groupNames[it->back()].size() == 0)
2023  RPC::callFunctionValue<int>("datacontrol","groupObjects", *it, QFileInfo(_filename).fileName());
2024  else
2025  RPC::callFunctionValue<int>("datacontrol","groupObjects", *it, groupNames[it->back()]);
2026  }
2027  }
2028 #endif
2029 
2030 #ifdef ENABLE_BSPLINESURFACE_SUPPORT
2031  // add last group
2032  surfaceGroups.push_back(surfaceIds);
2033  std::vector< std::vector<int> >::iterator it2 = surfaceGroups.begin();
2034  for (; it2 != surfaceGroups.end(); ++it2) {
2035  // only group if we have more than one surface
2036  if (it2->size() > 2) {
2037  if (groupNames[it2->back()].size() == 0)
2038  RPC::callFunctionValue<int>("datacontrol","groupObjects", *it2, QFileInfo(_filename).fileName());
2039  else
2040  RPC::callFunctionValue<int>("datacontrol","groupObjects", *it2, groupNames[it2->back()]);
2041  }
2042  }
2043 #endif
2044 
2045  // only group if we have more than one object
2046  if (objIDs.size() > 1)
2047  returnID = RPC::callFunctionValue<int>("datacontrol","groupObjects", objIDs, importer.groupName(0));
2048  }
2049  }
2050 
2051  //check all new objects which are created for each group
2052  for(unsigned int i=0; i < importer.groupCount(); i++){
2053 
2054  BaseObject* object = importer.object(i);
2055  if(object == NULL) continue;
2056 
2057  object->setFromFileName(_filename);
2058 
2059  //remember the id of the first opened object
2060  if ( returnID == -1)
2061  returnID = object->id();
2062 
2063  //handle new PolyMeshes
2064  PolyMeshObject* polyMeshObj = dynamic_cast< PolyMeshObject* > (object);
2065 
2066  if ( polyMeshObj ){
2067 
2068  if ( !importer.hasNormals(i) )
2069  polyMeshObj->mesh()->update_normals();
2070  else
2071  polyMeshObj->mesh()->update_face_normals();
2072  }
2073 
2074  //handle new TriMeshes
2075  TriMeshObject* triMeshObj = dynamic_cast< TriMeshObject* > (object);
2076 
2077  if ( triMeshObj ){
2078 
2079  if ( !importer.hasNormals(i) || importer.hasOption( i, OBJImporter::FORCE_NONORMALS ) )
2080  triMeshObj->mesh()->update_normals();
2081  else
2082  triMeshObj->mesh()->update_face_normals();
2083  }
2084 
2085 #ifdef ENABLE_BSPLINECURVE_SUPPORT
2086  //handle new BSplineCurves
2087  BSplineCurveObject* bscObj = dynamic_cast< BSplineCurveObject* > (object);
2088 
2089  if ( bscObj ){
2090  bscObj->splineCurveNode()->updateGeometry();
2091  }
2092 #endif
2093 
2094 
2095  //textures
2096  if ( importer.hasTexture(i) && !importer.hasOption( i, OBJImporter::FORCE_NOTEXTURES ) ){
2097 
2098  //add the textures to the object
2099  addTextures( importer, i );
2100 
2101  //set the texture index property to be used
2102  emit setTextureMode("OBJ Data","indexProperty=OriginalTexIndexMapping", object->id() );
2103 
2104  emit switchTexture("OBJ Data", object->id() );
2105 
2107 
2108  }
2109 
2110  //general stuff
2111  emit updatedObject( object->id(), UPDATE_ALL );
2112  emit openedFile( object->id() );
2113  }
2114 
2115  forceTriangleMesh_ = false;
2116  forcePolyMesh_ = false;
2117 
2118 // if ( topLevelObj )
2119 // OpenFlipper::Options::loadingSettings(false);
2120  return returnID;
2121 }
2122 
2123 //-----------------------------------------------------------------------------------------------------
2124 
2126 int FileOBJPlugin::loadObject(QString _filename, DataType _type){
2127 
2128  if ( _type == DATA_TRIANGLE_MESH )
2129  forceTriangleMesh_ = true;
2130  else if ( _type == DATA_POLY_MESH )
2131  forcePolyMesh_ = true;
2132 
2133  return loadObject(_filename);
2134 }
2135 
2136 //-----------------------------------------------------------------------------------------------------
2137 
2138 bool FileOBJPlugin::saveObject(int _id, QString _filename)
2139 {
2140  BaseObjectData* object;
2141  if ( !PluginFunctions::getObject(_id,object) ) {
2142  emit log(LOGERR, tr("saveObject : cannot get object id %1 for save name %2").arg(_id).arg(_filename) );
2143  return false;
2144  }
2145 
2146  //open output stream
2147  std::string filename = std::string( _filename.toUtf8() );
2148 
2149  std::fstream objStream( filename.c_str(), std::ios_base::out );
2150 
2151  if ( !objStream ){
2152 
2153  emit log(LOGERR, tr("saveObject : cannot not open file %1").arg(_filename) );
2154  return false;
2155  }
2156 
2157  //write object
2158  if ( object->dataType( DATA_POLY_MESH ) ) {
2159 
2160  object->setFromFileName(_filename);
2161  object->setName(object->filename());
2162 
2163  PolyMeshObject* polyObj = dynamic_cast<PolyMeshObject* >( object );
2164 
2165 
2166  if ( writeMesh( objStream, _filename, *polyObj->mesh(), polyObj->id() ) ){
2167 
2168  emit log(LOGINFO, tr("Saved object to ") + _filename );
2169  objStream.close();
2170  return true;
2171 
2172  } else {
2173 
2174  emit log(LOGERR, tr("Unable to save ") + _filename);
2175  objStream.close();
2176  return false;
2177  }
2178 
2179  } else if ( object->dataType( DATA_TRIANGLE_MESH ) ) {
2180 
2181  object->setFromFileName(_filename);
2182  object->setName(object->filename());
2183 
2184  TriMeshObject* triObj = dynamic_cast<TriMeshObject* >( object );
2185 
2186 
2187  if ( writeMesh( objStream, _filename, *triObj->mesh(), triObj->id() )) {
2188 
2189  emit log(LOGINFO, tr("Saved object to ") + _filename );
2190  objStream.close();
2191  return true;
2192 
2193  } else {
2194 
2195  emit log(LOGERR, tr("Unable to save ") + _filename );
2196  objStream.close();
2197  return false;
2198  }
2199 
2200 #ifdef ENABLE_BSPLINECURVE_SUPPORT
2201  } else if ( object->dataType( DATA_BSPLINE_CURVE ) ) {
2202 
2203  object->setFromFileName(_filename);
2204  object->setName(object->filename());
2205 
2206  BSplineCurveObject* bscObj = dynamic_cast<BSplineCurveObject* >( object );
2207 
2208 
2209  if ( writeCurve( objStream, _filename, bscObj->splineCurve()) ) {
2210 
2211  emit log(LOGINFO, tr("Saved object to ") + _filename );
2212  objStream.close();
2213  return true;
2214 
2215  } else {
2216 
2217  emit log(LOGERR, tr("Unable to save ") + _filename );
2218  objStream.close();
2219  return false;
2220  }
2221 #endif
2222 
2223 #ifdef ENABLE_BSPLINESURFACE_SUPPORT
2224  } else if ( object->dataType( DATA_BSPLINE_SURFACE ) ) {
2225 
2226  object->setFromFileName(_filename);
2227  object->setName(object->filename());
2228 
2229  BSplineSurfaceObject* bssObj = dynamic_cast<BSplineSurfaceObject* >( object );
2230 
2231 
2232  if ( writeSurface( objStream, _filename, bssObj->splineSurface()) ) {
2233 
2234  emit log(LOGINFO, tr("Saved object to ") + _filename );
2235  objStream.close();
2236  return true;
2237 
2238  } else {
2239 
2240  emit log(LOGERR, tr("Unable to save ") + object->path() + OpenFlipper::Options::dirSeparator() + object->name());
2241  objStream.close();
2242  return false;
2243  }
2244 #endif
2245 
2246  } else {
2247 
2248  emit log(LOGERR, tr("Unable to save (object is not a compatible mesh type)"));
2249  objStream.close();
2250  return false;
2251  }
2252 }
2253 
2254 //-----------------------------------------------------------------------------------------------------
2255 
2256 void FileOBJPlugin::slotHandleCheckBoxes(bool _checked) {
2257 
2258  if(saveCopyTextures_) {
2259  saveCreateTexFolder_->setEnabled(_checked);
2260  saveCreateTexFolder_->setChecked(_checked);
2261  }
2262 }
2263 
2264 //-----------------------------------------------------------------------------------------------------
2265 
2266 QWidget* FileOBJPlugin::saveOptionsWidget(QString /*_currentFilter*/) {
2267 
2268  if (saveOptions_ == 0){
2269  //generate widget
2270  saveOptions_ = new QWidget();
2271  QVBoxLayout* layout = new QVBoxLayout();
2272  layout->setAlignment(Qt::AlignTop);
2273 
2274  saveFaceColor_ = new QCheckBox("Save Face Colors");
2275  layout->addWidget(saveFaceColor_);
2276 
2277  saveFaceColorOverride_ = new QCheckBox("Save Face Colors Error Override (Save, even if colors contain errors!)");
2278  saveFaceColorOverride_->setToolTip("If color values of a mesh are not initialized, OpenFlipper will detect that and stop writing materials. This option will force it to write them anyway.");
2279  layout->addWidget(saveFaceColorOverride_);
2280 
2281  saveAlpha_ = new QCheckBox("Save Color Alpha");
2282  layout->addWidget(saveAlpha_);
2283 
2284  saveNormals_ = new QCheckBox("Save Normals");
2285  layout->addWidget(saveNormals_);
2286 
2287  saveTexCoords_ = new QCheckBox("Save Texture Coordinates");
2288  layout->addWidget(saveTexCoords_);
2289 
2290  saveTextures_ = new QCheckBox("Save Textures");
2291  layout->addWidget(saveTextures_);
2292 
2293  saveCopyTextures_ = new QCheckBox("Copy Texture Files");
2294  layout->addWidget(saveCopyTextures_);
2295 
2296  saveCreateTexFolder_ = new QCheckBox("Create Textures Folder");
2297  layout->addWidget(saveCreateTexFolder_);
2298 
2299  savePrecisionLabel_ = new QLabel("Writer Precision");
2300  layout->addWidget(savePrecisionLabel_);
2301 
2302  savePrecision_ = new QSpinBox();
2303  savePrecision_->setMinimum(1);
2304  savePrecision_->setMaximum(12);
2305  savePrecision_->setValue(6);
2306  layout->addWidget(savePrecision_);
2307 
2308  saveDefaultButton_ = new QPushButton("Make Default");
2309  layout->addWidget(saveDefaultButton_);
2310 
2311  saveOptions_->setLayout(layout);
2312 
2313  connect(saveDefaultButton_, SIGNAL(clicked()), this, SLOT(slotSaveDefault()));
2314  connect(saveCopyTextures_, SIGNAL(toggled(bool)), this, SLOT(slotHandleCheckBoxes(bool)));
2315 
2316  saveFaceColor_->setChecked( OpenFlipperSettings().value("FileObj/Save/FaceColor",true).toBool() );
2317  saveFaceColorOverride_->setChecked( OpenFlipperSettings().value("FileObj/Save/FaceColorOverrise",false).toBool() );
2318  saveAlpha_->setChecked( OpenFlipperSettings().value("FileObj/Save/Alpha",true).toBool() );
2319  saveNormals_->setChecked( OpenFlipperSettings().value("FileObj/Save/Normals",true).toBool() );
2320  saveTexCoords_->setChecked( OpenFlipperSettings().value("FileObj/Save/TexCoords",true).toBool() );
2321  saveTextures_->setChecked( OpenFlipperSettings().value("FileObj/Save/Textures",true).toBool() );
2322  saveCopyTextures_->setChecked( OpenFlipperSettings().value("FileObj/Save/CopyTextures",true).toBool() );
2323  saveCreateTexFolder_->setChecked( OpenFlipperSettings().value("FileObj/Save/CreateTexFolder",true).toBool() );
2324 
2325  slotHandleCheckBoxes(saveCopyTextures_->isChecked());
2326  }
2327 
2328  return saveOptions_;
2329 }
2330 
2331 //-----------------------------------------------------------------------------------------------------
2332 
2333 QWidget* FileOBJPlugin::loadOptionsWidget(QString /*_currentFilter*/) {
2334 
2335  if (loadOptions_ == 0){
2336  //generate widget
2337  loadOptions_ = new QWidget();
2338  QVBoxLayout* layout = new QVBoxLayout();
2339  layout->setAlignment(Qt::AlignTop);
2340 
2341  QLabel* label = new QLabel(tr("If file contains meshes:"));
2342 
2343  layout->addWidget(label);
2344 
2345  triMeshHandling_ = new QComboBox();
2346  triMeshHandling_->addItem( tr("Detect correct type") );
2347  triMeshHandling_->addItem( tr("Ask") );
2348  triMeshHandling_->addItem( tr("Open as PolyMesh") );
2349  triMeshHandling_->addItem( tr("Open as TriangleMesh") );
2350 
2351  layout->addWidget(triMeshHandling_);
2352 
2353  loadFaceColor_ = new QCheckBox("Load Face Colors");
2354  layout->addWidget(loadFaceColor_);
2355 
2356  loadNormals_ = new QCheckBox("Load Normals");
2357  layout->addWidget(loadNormals_);
2358 
2359  loadTexCoords_ = new QCheckBox("Load Texture Coordinates");
2360  layout->addWidget(loadTexCoords_);
2361 
2362  loadTextures_ = new QCheckBox("Load Textures");
2363  layout->addWidget(loadTextures_);
2364 
2365  loadDefaultButton_ = new QPushButton("Make Default");
2366  layout->addWidget(loadDefaultButton_);
2367 
2368  loadOptions_->setLayout(layout);
2369 
2370  connect(loadDefaultButton_, SIGNAL(clicked()), this, SLOT(slotLoadDefault()));
2371 
2372 
2373  triMeshHandling_->setCurrentIndex(OpenFlipperSettings().value("FileObj/Load/TriMeshHandling",TYPEAUTODETECT).toInt() );
2374 
2375  loadFaceColor_->setChecked( OpenFlipperSettings().value("FileObj/Load/FaceColor",true).toBool() );
2376  loadNormals_->setChecked( OpenFlipperSettings().value("FileObj/Load/Normals",true).toBool() );
2377  loadTexCoords_->setChecked( OpenFlipperSettings().value("FileObj/Load/TexCoords",true).toBool() );
2378  loadTextures_->setChecked( OpenFlipperSettings().value("FileObj/Load/Textures",true).toBool() );
2379  }
2380 
2381  return loadOptions_;
2382 }
2383 
2385  OpenFlipperSettings().setValue( "FileObj/Load/FaceColor", loadFaceColor_->isChecked() );
2386  OpenFlipperSettings().setValue( "FileObj/Load/Normals", loadNormals_->isChecked() );
2387  OpenFlipperSettings().setValue( "FileObj/Load/TexCoords", loadTexCoords_->isChecked() );
2388  OpenFlipperSettings().setValue( "FileObj/Load/Textures", loadTextures_->isChecked() );
2389  OpenFlipperSettings().setValue("FileObj/Load/TriMeshHandling", triMeshHandling_->currentIndex() );
2390 
2391  OpenFlipperSettings().setValue( "Core/File/UseLoadDefaults", true );
2392 }
2393 
2394 
2396  OpenFlipperSettings().setValue( "FileObj/Save/FaceColor", saveFaceColor_->isChecked() );
2397  OpenFlipperSettings().setValue( "FileObj/Save/FaceColorOverride", saveFaceColorOverride_->isChecked() );
2398  OpenFlipperSettings().setValue( "FileObj/Save/Normals", saveNormals_->isChecked() );
2399  OpenFlipperSettings().setValue( "FileObj/Save/TexCoords", saveTexCoords_->isChecked() );
2400  OpenFlipperSettings().setValue( "FileObj/Save/Textures", saveTextures_->isChecked() );
2401  OpenFlipperSettings().setValue( "FileObj/Save/CopyTextures", saveCopyTextures_->isChecked() );
2402  OpenFlipperSettings().setValue( "FileObj/Save/CreateTexFolder", saveCreateTexFolder_->isChecked() );
2403 
2404 }
2405 
2406 
2407 
void slotLoadDefault()
Slot called when user wants to save the given Load options as default.
Definition: FileOBJ.cc:2384
const std::vector< std::string > usedMaterials(unsigned int _objectID)
used materials
#define DATA_TRIANGLE_MESH
Definition: TriangleMesh.hh:60
#define DATA_POLY_MESH
Definition: PolyMesh.hh:59
QString path() const
return the path to the object ( defaults to "." if unset )
Definition: BaseObject.cc:734
void setObjectOptions(ObjectOptions _options)
Definition: OBJImporter.cc:906
void setNormal(int _index, int _normalID)
set vertex normal
Definition: OBJImporter.cc:305
const UpdateType UPDATE_ALL(UpdateTypeSet(1))
Identifier for all updates.
void addMaterial(std::string _materialName)
Add a material.
Definition: OBJImporter.cc:566
QString getSaveFilters()
Definition: FileOBJ.cc:127
Type for a Meshobject containing a poly mesh.
Definition: PolyMesh.hh:65
Vec3f vertex(unsigned int _index)
get vertex with given index
Definition: OBJImporter.cc:64
void addFace(const VHandles &_indices)
add a face with indices _indices refering to vertices
Definition: OBJImporter.cc:468
void slotSaveDefault()
Slot called when user wants to save the given Save options as default.
Definition: FileOBJ.cc:2395
void initializePlugin()
Initialize Plugin.
Definition: FileOBJ.cc:116
void checkTypes(QByteArray &_bufferedFile, QString _filename, OBJImporter &_importer, QStringList &_includes)
Reader functions.
Definition: FileOBJ.cc:1328
void setVertexTexCoord(VertexHandle _vh, int _texCoordID)
set vertex texture coordinate
Definition: OBJImporter.cc:260
QWidget * loadOptionsWidget(QString)
Definition: FileOBJ.cc:2333
bool getObject(const int _identifier, BaseObject *&_object)
Get the object which has the given identifier.
BSplineSurface * splineSurface()
return a pointer to the spline curve
QString name()
Return a name for the plugin.
Definition: FileOBJ.hh:158
void useVertex(int _vertex_index)
used vertices
BaseObject * object(int _groupId)
return object for the given group
Definition: OBJImporter.cc:869
DrawMode SOLID_2DTEXTURED_FACE_SHADED
draw per halfedge textured faces
Definition: DrawModes.cc:97
const DataType DATA_GROUP(1)
Items used for Grouping.
Predefined datatypes.
Definition: DataTypes.hh:83
int addTexCoord(const Vec2f &_coord)
add texture coordinates
Definition: OBJImporter.cc:75
QString filename() const
return the filename of the object
Definition: BaseObject.cc:706
QWidget * saveOptionsWidget(QString)
Definition: FileOBJ.cc:2266
int id() const
Definition: BaseObject.cc:190
void setOption(ObjectOptionsE _option)
Set Object Option.
Definition: OBJImporter.cc:834
MeshT * mesh()
return a pointer to the mesh
void setDegreeV(int _degree)
set degree V direction
Definition: OBJImporter.cc:100
bool dataType(DataType _type) const
Definition: BaseObject.cc:221
std::vector< int > IdList
Standard Type for id Lists used for scripting.
Definition: DataTypes.hh:181
#define DATA_BSPLINE_SURFACE
FileOBJPlugin()
Constructor.
Definition: FileOBJ.cc:82
void setObject(BaseObject *_object, int _groupId)
add an object
Definition: OBJImporter.cc:122
#define DATA_BSPLINE_CURVE
Definition: BSplineCurve.hh:67
int currentGroup()
Get the id of the current group.
Definition: OBJImporter.cc:159
BSplineCurve * splineCurve()
return a pointer to the spline curve
DataType supportedType()
Return your supported object type( e.g. DATA_TRIANGLE_MESH )
Definition: FileOBJ.cc:133
Type for a MeshObject containing a triangle mesh.
Definition: TriangleMesh.hh:67
ACG::SceneGraph::BSplineCurveNodeT< BSplineCurve > * splineCurveNode()
Get the scenegraph Node.
void update_normals()
Compute normals for all primitives.
void update_face_normals()
Update normal vectors for all faces.
void setDrawMode(const ACG::SceneGraph::DrawModes::DrawMode &_mode, int _viewer)
Set the draw Mode of a Viewer. .
VertexHandle addVertex(const Vec3f &_point)
add a vertex with coordinate _point
Definition: OBJImporter.cc:57
bool hasOption(unsigned int _id, ObjectOptions _option)
check if object with given id has given option
Definition: OBJImporter.cc:924
int loadObject(QString _filename)
Loads Object and converts it to a triangle mesh if possible.
Definition: FileOBJ.cc:1848
int addNormal(const Vec3f &_normal)
add a normal
Definition: OBJImporter.cc:84
unsigned int n_vertices()
Global Properties.
Definition: OBJImporter.cc:845
bool hasNormals(int _objectID)
Query Object Options.
Definition: OBJImporter.cc:814
QString path()
Path of the OBJ file.
Definition: OBJImporter.cc:894
QString getLoadFilters()
Definition: FileOBJ.cc:121
unsigned int groupCount()
Number of groups currently stored in the importer.
Definition: OBJImporter.cc:863
void forceMeshType(ObjectOptions _meshType)
force all meshes to be opened with specific type
Definition: OBJImporter.cc:764
void backupTextureCoordinates(MeshT &_mesh)
creates a backup of the original per vertex/face texture coordinates
Definition: FileOBJ.cc:439
void setValue(const QString &key, const QVariant &value)
Wrapper function which makes it possible to enable Debugging output with -DOPENFLIPPER_SETTINGS_DEBUG...
int degreeU()
get current degree
Definition: OBJImporter.cc:108
DLLEXPORT OpenFlipperQSettings & OpenFlipperSettings()
QSettings object containing all program settings of OpenFlipper.
int degreeV()
get current degree
Definition: OBJImporter.cc:115
void convertToOBJName(QString &_name)
Convert non-valid filenames (e.g. of groups that end with .jpg) to valid .objs.
Definition: FileOBJ.cc:428
MaterialList & materials()
return all loaded materials
Definition: OBJImporter.cc:888
bool noOptions()
Return true if the importer has no options stored.
Definition: OBJImporter.cc:917
void setFromFileName(const QString &_filename)
Definition: BaseObject.cc:716
void setDegreeU(int _degree)
set degree
Definition: OBJImporter.cc:93