/*===========================================================================*\ * * * OpenFlipper * * Copyright (c) 2001-2015, RWTH-Aachen University * * Department of Computer Graphics and Multimedia * * All rights reserved. * * www.openflipper.org * * * *---------------------------------------------------------------------------* * This file is part of OpenFlipper. * *---------------------------------------------------------------------------* * * * Redistribution and use in source and binary forms, with or without * * modification, are permitted provided that the following conditions * * are met: * * * * 1. Redistributions of source code must retain the above copyright notice, * * this list of conditions and the following disclaimer. * * * * 2. Redistributions in binary form must reproduce the above copyright * * notice, this list of conditions and the following disclaimer in the * * documentation and/or other materials provided with the distribution. * * * * 3. Neither the name of the copyright holder nor the names of its * * contributors may be used to endorse or promote products derived from * * this software without specific prior written permission. * * * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER * * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * * \*===========================================================================*/ /*===========================================================================*\ * * * $Revision$ * * $LastChangedBy$ * * $Date$ * * * \*===========================================================================*/ #include #include "OpenFlipper/BasePlugin/PluginFunctions.hh" #include "OpenFlipper/common/GlobalOptions.hh" #include #include #if QT_VERSION >= 0x050000 #include #else #include #endif #include "FileOBJ.hh" #include #include // Defines for the type handling drop down box #define TYPEAUTODETECT 0 #define TYPEASK 1 #define TYPEPOLY 2 #define TYPETRIANGLE 3 using namespace Utils; //----------------------------------------------------------------------------- // help functions void remove_duplicated_vertices(VHandles& _indices) { VHandles::iterator endIter = _indices.end(); for (VHandles::iterator iter = _indices.begin(); iter != endIter; ++iter) endIter = std::remove(iter+1, endIter, *(iter)); _indices.erase(endIter,_indices.end()); } //----------------------------------------------------------------------------- /// Constructor FileOBJPlugin::FileOBJPlugin() : loadOptions_(0), saveOptions_(0), saveBinary_(0), saveVertexColor_(0), saveFaceColor_(0), saveAlpha_(0), saveNormals_(0), saveTexCoords_(0), saveTextures_(0), saveCopyTextures_(0), saveCreateTexFolder_(0), savePrecisionLabel_(0), savePrecision_(0), saveDefaultButton_(0), triMeshHandling_(0), loadVertexColor_(0), loadFaceColor_(0), loadAlpha_(0), loadNormals_(0), loadTexCoords_(0), loadTextures_(0), loadDefaultButton_(0), forceTriangleMesh_(false), forcePolyMesh_(false), textureIndexPropFetched_(false) { } //----------------------------------------------------------------------------------------------------- void FileOBJPlugin::initializePlugin() { } //----------------------------------------------------------------------------------------------------- QString FileOBJPlugin::getLoadFilters() { return QString( tr("Alias/Wavefront ( *.obj )") ); }; //----------------------------------------------------------------------------------------------------- QString FileOBJPlugin::getSaveFilters() { return QString( tr("Alias/Wavefront ( *.obj )") ); }; //----------------------------------------------------------------------------------------------------- DataType FileOBJPlugin::supportedType() { DataType type = DATA_POLY_MESH | DATA_TRIANGLE_MESH | DATA_GROUP; #ifdef ENABLE_BSPLINECURVE_SUPPORT type |= DATA_BSPLINE_CURVE; #endif #ifdef ENABLE_BSPLINESURFACE_SUPPORT type |= DATA_BSPLINE_SURFACE; #endif return type; } //----------------------------------------------------------------------------- bool FileOBJPlugin::readMaterial(QString _filename, OBJImporter& _importer) { static QString line; static QString keyWrd; static QString textureName; line.clear(); keyWrd.clear(); textureName.clear(); static QString matName; matName.clear(); static Material mat; mat.cleanup(); static float f1,f2,f3; f1 = 0; f2 = 0; f3 = 0; static bool insideDefintion; insideDefintion = false; static int textureId; textureId = 1; //open stream QFile matFile(_filename); if (!matFile.open(QFile::ReadOnly)) { emit log(LOGERR, tr("readMaterial : cannot open file %1").arg(_filename)); return false; } QTextStream matStream(&matFile); if ( matStream.status()!=QTextStream::Ok ){ emit log(LOGERR, tr("readMaterial : cannot open stream %1").arg(_filename) ); return false; } //clear temp material mat.cleanup(); //parse material file while( matStream.status() == QTextStream::Ok && !matStream.atEnd() ) { line = matStream.readLine(); if ( matStream.status() != QTextStream::Ok ){ emit log(LOGERR, tr("readMaterial : Warning! Could not read file properly!")); return false; } if ( line.isEmpty() ) continue; QTextStream stream(&line); stream >> keyWrd; if( ( line[0].isSpace() && line[0] != QLatin1Char('\t') ) || line[0] == QLatin1Char('#') ) { if (insideDefintion && !matName.isEmpty() && mat.is_valid()) { _importer.materials()[matName.toStdString()] = mat; mat.cleanup(); } } else if (keyWrd == QLatin1String("newmtl")) // begin new material definition { stream >> matName; insideDefintion = true; } else if (keyWrd == QLatin1String("Kd")) // diffuse color { f1 = getFloat(stream); f2 = getFloat(stream); f3 = getFloat(stream); if( stream.status()==QTextStream::Ok ) mat.set_Kd(f1,f2,f3); } else if (keyWrd == QLatin1String("Ka")) // ambient color { f1 = getFloat(stream); f2 = getFloat(stream); f3 = getFloat(stream); if( stream.status()!=QTextStream::Ok ) mat.set_Ka(f1,f2,f3); } else if (keyWrd == QLatin1String("Ks")) // specular color { f1 = getFloat(stream); f2 = getFloat(stream); f3 = getFloat(stream); if( stream.status()!=QTextStream::Ok ) mat.set_Ks(f1,f2,f3); } #if 0 else if (keyWrd == QLatin1String("illum") // diffuse/specular shading model { ; // just skip this } else if (keyWrd == QLatin1String("Ns") // Shininess [0..200] { ; // just skip this } else if (keyWrd == QLatin1String("map_") // map images { // map_Ks, specular map // map_Ka, ambient map // map_Bump, bump map // map_d, opacity map ; // just skip this } #endif else if (keyWrd == QLatin1String("map_Kd") ) { // Get the rest of the line, removing leading or trailing spaces // This will define the filename of the texture textureName = stream.readLine(); textureName = textureName.trimmed(); if ( ! textureName.isEmpty() ) mat.set_map_Kd( textureName.toStdString(), textureId++ ); } else if (keyWrd == QLatin1String("Tr")) // transparency value { f1 = getFloat(stream); if( stream.status() == QTextStream::Ok ) mat.set_Tr(f1); } else if (keyWrd == QLatin1String("d")) // transparency value { f1 = getFloat(stream); if( stream.status() == QTextStream::Ok ) mat.set_Tr(f1); } if ( matStream.status() == QTextStream::Ok && insideDefintion && mat.is_valid() && !matName.isEmpty()) _importer.materials()[matName.toStdString()] = mat; } emit log( tr("%1 materials loaded.").arg( _importer.materials().size() ) ); return true; } //----------------------------------------------------------------------------- void FileOBJPlugin::createAllGroupObjects(OBJImporter& _importer) { for(unsigned int i = 0; i < _importer.numGroups(); ++i) { // Get group name QString name = _importer.groupName(i); convertToOBJName(name); if ( _importer.isTriangleMesh( i ) ){ // add a triangle mesh int id = -1; emit addEmptyObject(DATA_TRIANGLE_MESH, id); BaseObjectData* object(0); if (PluginFunctions::getObject(id, object)) { _importer.setObject(object, i); object->setPath(_importer.path()); object->setName(name); } } else if (_importer.isPolyMesh( i )) { int id = -1; emit addEmptyObject(DATA_POLY_MESH, id); BaseObjectData* object(0); if (PluginFunctions::getObject(id, object)) { _importer.setObject(object, i); object->setPath(_importer.path()); object->setName(name); } } #ifdef ENABLE_BSPLINECURVE_SUPPORT else if (_importer.isCurve( i )) { int id = -1; emit addEmptyObject(DATA_BSPLINE_CURVE, id); BaseObjectData* object(0); if (PluginFunctions::getObject(id, object)) { _importer.setObject(object, i); object->setPath(_importer.path()); object->setName(name); } } #endif #ifdef ENABLE_BSPLINESURFACE_SUPPORT else if (_importer.isSurface( i )) { int id = -1; emit addEmptyObject(DATA_BSPLINE_SURFACE, id); BaseObjectData* object(0); if (PluginFunctions::getObject(id, object)) { _importer.setObject(object, i); object->setPath(_importer.path()); object->setName(name); } } #endif //force gui settings if (OpenFlipper::Options::gui() && loadOptions_ != 0) { if (!loadFaceColor_->isChecked()) _importer.objectOptions()[ i ] |= OBJImporter::FORCE_NOCOLOR; if (!loadNormals_->isChecked()) _importer.objectOptions()[ i ] |= OBJImporter::FORCE_NONORMALS; if (!loadTexCoords_->isChecked() || !loadTextures_->isChecked()) _importer.objectOptions()[ i ] |= OBJImporter::FORCE_NOTEXTURES; } } } void FileOBJPlugin::convertToOBJName(QString& _name) { QFileInfo fi(_name); QString n = fi.baseName(); _name = n.trimmed() + ".obj"; } /// creates a backup of the original per vertex/face texture coordinates template void FileOBJPlugin::backupTextureCoordinates(MeshT& _mesh) { // Create a backup of the original per Vertex texture Coordinates if (_mesh.has_vertex_texcoords2D()) { OpenMesh::VPropHandleT< typename MeshT::TexCoord2D > oldVertexCoords; if (!_mesh.get_property_handle(oldVertexCoords, "Original Per Vertex Texture Coords")) _mesh.add_property(oldVertexCoords, "Original Per Vertex Texture Coords"); for (typename MeshT::VertexIter v_it = _mesh.vertices_begin(); v_it != _mesh.vertices_end(); ++v_it) _mesh.property(oldVertexCoords, *v_it) = _mesh.texcoord2D(*v_it); } // Create a backup of the original per Face texture Coordinates if (_mesh.has_halfedge_texcoords2D()) { OpenMesh::HPropHandleT< typename MeshT::TexCoord2D > oldHalfedgeCoords; if (!_mesh.get_property_handle(oldHalfedgeCoords,"Original Per Face Texture Coords")) _mesh.add_property(oldHalfedgeCoords,"Original Per Face Texture Coords"); for (typename MeshT::HalfedgeIter he_it = _mesh.halfedges_begin(); he_it != _mesh.halfedges_end(); ++he_it) _mesh.property(oldHalfedgeCoords, *he_it) = _mesh.texcoord2D(*he_it); } } //add textures to the mesh void FileOBJPlugin::addTextures(OBJImporter& _importer, int _objectID ){ // TODO : If only one Texture, use single Texturing mode if ( true ) { BaseObject* object = _importer.object(_objectID); if (!object) return; std::map< int,int > newMapping; // zero ( no texture ) always maps to to zero newMapping[0]=0; const std::vector matNames = _importer.usedMaterials( _objectID ); for (unsigned int i=0; i < matNames.size(); i++){ Material& material = _importer.materials()[ matNames[i] ]; int textureId = -1; QString textureBlock = QString( material.map_Kd().c_str()); QStringList options = textureBlock.split(" ",QString::SkipEmptyParts); while ( options.size() > 1 ) { if ( options[0] == "-blendu" ) { options.pop_front(); options.pop_front(); } else if ( options[0] == "-blendv" ) { options.pop_front(); options.pop_front(); } else if ( options[0] == "-cc" ) { options.pop_front(); options.pop_front(); } else if ( options[0] == "-clamp" ) { options.pop_front(); options.pop_front(); } else if ( options[0] == "-mm" ) { options.pop_front(); options.pop_front(); options.pop_front(); } else if ( options[0] == "-o" ) { options.pop_front(); options.pop_front(); options.pop_front(); options.pop_front(); } else if ( options[0] == "-s" ) { options.pop_front(); options.pop_front(); options.pop_front(); options.pop_front(); } else if ( options[0] == "-t" ) { options.pop_front(); options.pop_front(); options.pop_front(); options.pop_front(); } else if ( options[0] == "-texres" ) { options.pop_front(); options.pop_front(); } else { break; } } QString fullName = _importer.path() + QDir::separator() + options.join(" "); QFileInfo info(fullName); if ( info.exists() ) emit addMultiTexture("OBJ Data", info.baseName().trimmed(), fullName, object->id(), textureId ); else { emit log(LOGWARN, tr("Unable to load texture image %1").arg( QString(material.map_Kd().c_str()) ) ); addMultiTexture("OBJ Data","Unknown Texture image " + QString::number(textureId), "unknown.png", object->id(), textureId ); } newMapping[ material.map_Kd_index() ] = textureId; } //now map all texture indices to the real texture indices used in OpenFlipper OpenMesh::FPropHandleT< int > indexProperty; //handle PolyMeshes PolyMeshObject* polyMeshObj = dynamic_cast< PolyMeshObject* > (object); if ( polyMeshObj ){ PolyMesh& mesh = *(polyMeshObj->mesh()); PolyMesh::FaceIter f_it; PolyMesh::FaceIter f_end = mesh.faces_end(); if (! mesh.get_property_handle(indexProperty,TEXTUREINDEX) ) return; for (f_it = mesh.faces_begin(); f_it != f_end; ++f_it) mesh.property(indexProperty, *f_it) = newMapping[ mesh.property(indexProperty, *f_it) ]; backupTextureCoordinates(mesh); return; } //handle new TriMeshes TriMeshObject* triMeshObj = dynamic_cast< TriMeshObject* > (object); if ( triMeshObj ){ TriMesh& mesh = *(triMeshObj->mesh()); TriMesh::FaceIter f_it; TriMesh::FaceIter f_end = mesh.faces_end(); if (! mesh.get_property_handle(indexProperty,TEXTUREINDEX) ) return; for (f_it = mesh.faces_begin(); f_it != f_end; ++f_it) mesh.property(indexProperty, *f_it) = newMapping[ mesh.property(indexProperty, *f_it) ]; backupTextureCoordinates(mesh); return; } } } void FileOBJPlugin::readOBJFile(QByteArray& _bufferedFile, QString _filename, OBJImporter& _importer) { QString path = QFileInfo(_filename).absolutePath(); ptr::shared_ptr streamPointer; ptr::shared_ptr sourceFile; ////setup filestream if not in memory if (_bufferedFile.isNull()) { sourceFile.reset(new QFile(_filename) ); if(!sourceFile->open(QFile::ReadOnly)) { emit log(LOGERR, tr("readOBJFile : cannot open file %1").arg(_filename) ); return; } //use the QTextStream and QString objects, since they seem to be more efficient when parsing strings. //especially regarding copy operations. streamPointer.reset( new QTextStream(sourceFile.get())); } else { streamPointer.reset( new QTextStream(&_bufferedFile)); } QTextStream input(streamPointer->device()); input.seek(0); QTextStream stream; QTextStream lineData; QTextStream tmp; if ( input.status() != QTextStream::Ok){ emit log(LOGERR, tr("readOBJFile : cannot read file %1 is the file corrupt?").arg(_filename) ); return; } QString currentFileName = QFileInfo(_filename).fileName() ; ReaderMode mode = NONE; QString line; QString keyWrd; QString nextKeyWrd = QLatin1String(""); #ifdef ENABLE_BSPLINECURVE_SUPPORT unsigned int curveCount = 0; #endif #ifdef ENABLE_BSPLINESURFACE_SUPPORT unsigned int surfaceCount = 0; #endif float x, y, z, u, v; int deg; std::vector vhandles; std::vector face_texcoords; QString matname; #if defined (ENABLE_BSPLINECURVE_SUPPORT) || defined (ENABLE_BSPLINESURFACE_SUPPORT) std::vector< int > cpIndices; std::vector< double > knotsU,knotsV; #endif int faceCount = 0; // We have to keep track of the already read number of vertices to resolve relative (negative indices) int currentVertexCount = 0; // We have to keep track of the already read number of Texture coordinates to resolve relative (negative indices) int currentTextureCoordCount = 0; // We have to keep track of the already read number of normals to resolve relative (negative indices) int currentNormalCount = 0; // keeps track if faces belong to a group or the default group bool inGroup = false; // keeps track if the first face of a mesh has been read yet or not bool firstFace = true; _importer.setPath( path ); // Set filename for default mesh _importer.setGroupName(0, currentFileName); // Now add all meshes for every group (if exists) createAllGroupObjects(_importer); while( !input.atEnd() ) { line=input.readLine(); if ( input.status() == QTextStream::ReadCorruptData ){ emit log(LOGERR, tr("readOBJFile : Warning! Could not read file properly!")); return; } // Trim Both leading and trailing spaces line = line.trimmed(); // comment if ( line.isEmpty() || line[0] == QLatin1Char('#') || line[0].isSpace() ) { continue; } stream.setString(&line,QIODevice::ReadOnly); //unless the keyWrd for the new line is not determined by the previous line //read it from stream if (nextKeyWrd == QLatin1String("")) stream >> keyWrd; else { keyWrd = nextKeyWrd; nextKeyWrd = QLatin1String(""); } // material file if (mode == NONE && keyWrd == QLatin1String("mtllib")) { QString matString; // This will define the filename of the texture matString = stream.readLine(); QString matFile = path + QDir::separator() + matString.trimmed(); emit log( tr("Loading material file: %1").arg( matFile ) ); readMaterial( matFile, _importer ); } // usemtl else if (mode == NONE && keyWrd == QLatin1String("usemtl")) { stream >> matname; if ( _importer.materials().find(matname.toStdString())==_importer.materials().end() ) { emit log( LOGERR, tr("Warning! Material '%1' not defined in material file").arg( matname ) ); matname=QLatin1String(""); }else{ Material& mat = _importer.materials()[matname.toStdString()]; if ( mat.has_Texture() ){ //add object if not already there _importer.useMaterial( matname.toStdString() ); } } } else if (mode == NONE && keyWrd == QLatin1String("v")) { if (!firstFace) firstFace = true; currentVertexCount++; } // texture coord else if (mode == NONE && keyWrd == QLatin1String("vt")) { if (!firstFace) firstFace = true; // New texture coordinate read so increase counter currentTextureCoordCount++; u = getFloat(stream); v = getFloat(stream); if ( stream.status() == QTextStream::Ok ){ _importer.addTexCoord( OpenMesh::Vec2f(u, v) ); }else{ emit log( LOGERR, tr("Could not add TexCoord. Possible NaN or Inf?\nOnly single 2D texture coordinate per vertex allowed")); } } // normal else if (mode == NONE && keyWrd == QLatin1String("vn")) { if (!firstFace) firstFace = true; // New normal read so increase counter currentNormalCount++; x = getFloat(stream); y = getFloat(stream); z = getFloat(stream); if ( stream.status() == QTextStream::Ok ){ _importer.addNormal( OpenMesh::Vec3f(x,y,z) ); }else{ emit log( LOGERR, tr("Could not read normal. Possible NaN or Inf?")); } } // degree (for curves) else if (mode == NONE && keyWrd == QLatin1String("deg")) { stream >> deg; if ( stream.status() == QTextStream::Ok ) _importer.setDegreeU( deg ); stream >> deg; if ( stream.status() == QTextStream::Ok ) _importer.setDegreeV( deg ); } // group else if (mode == NONE && keyWrd == QLatin1String("g")){ if (!firstFace) firstFace = true; QString groupName; groupName = stream.readLine(); if(faceCount == 0) { currentFileName = groupName; } int id = _importer.groupId(groupName); if(id == -1) { std::cerr << "Error: Group has not been added before!" << std::endl; return; } _importer.setCurrentGroup(id); inGroup = true; faceCount = 0; } // face else if (mode == NONE && keyWrd == QLatin1String("f")) { if (firstFace) { // store faces in the default Group if we aren't in a group already if (!inGroup) _importer.setCurrentGroup(0); firstFace = false; } int component(0), nV(0); int value; vhandles.clear(); face_texcoords.clear(); // read full line after detecting a face QString faceLine; faceLine = stream.readLine(); lineData.setString(&faceLine); // work on the line until nothing left to read while ( !lineData.atEnd() ) { // read one block from the line ( vertex/texCoord/normal ) QString vertex; lineData >> vertex; do{ //get the component (vertex/texCoord/normal) int found=vertex.indexOf(QLatin1String("/")); // parts are seperated by '/' So if no '/' found its the last component if( found != -1 ){ // read the index value QString vertexEntry = vertex.left(found); tmp.setString( &vertexEntry ); // If we get an empty string this property is undefined in the file if ( vertexEntry.isEmpty() ) { // Switch to next field vertex = vertex.right(vertex.length()-(found+1)); // Now we are at the next component ++component; // Skip further processing of this component continue; } // Read current value tmp >> value; // remove the read part from the string vertex = vertex.right(vertex.length()-(found+1)); } else { // last component of the vertex, read it. tmp.setString( &vertex ); tmp >> value; // Clear vertex after finished reading the line vertex=QLatin1String(""); // Nothing to read here ( garbage at end of line ) if ( tmp.status() != QTextStream::Ok ) { continue; } } // store the component ( each component is referenced by the index here! ) switch (component) { case 0: // vertex if ( value < 0 ) { // Calculation of index : // -1 is the last vertex in the list // As obj counts from 1 and not zero add +1 value = currentVertexCount + value + 1; } // Obj counts from 1 and not zero .. array counts from zero therefore -1 vhandles.push_back( value-1 ); break; case 1: // texture coord if ( value < 0 ) { // Calculation of index : // -1 is the last vertex in the list // As obj counts from 1 and not zero add +1 value = currentTextureCoordCount + value + 1; } if (vhandles.empty()) { emit log (LOGWARN, tr("Texture coordinates defined, but no vertex coordinates found!")); break; } if ((unsigned int)(value-1) >= _importer.n_texCoords()) { emit log (LOGWARN, tr("Too many texcoords defined, skipping the rest")); break; } if ( _importer.n_texCoords() > 0 ) { // Obj counts from 1 and not zero .. array counts from zero therefore -1 _importer.setVertexTexCoord( vhandles.back(), value-1 ); face_texcoords.push_back( value-1 ); } else { emit log( LOGERR, tr("Error setting Texture coordinates") ); } break; case 2: // normal if ( value < 0 ) { // Calculation of index : // -1 is the last vertex in the list // As obj counts from 1 and not zero add +1 value = currentNormalCount + value + 1; } if (vhandles.empty()) { emit log (LOGWARN, tr("Texture coordinates defined, but no vertex coordinates found!")); break; } if ((unsigned int)(value-1) >= _importer.n_normals()) { emit log (LOGWARN, tr("Too many normals defined, skipping the rest")); break; } // Obj counts from 1 and not zero .. array counts from zero therefore -1 _importer.setNormal(vhandles.back(), value-1); break; } // Prepare for reading next component ++component; // Read until line does not contain any other info } while ( !vertex.isEmpty() ); component = 0; nV++; } // remove vertices which can lead to degenerated faces remove_duplicated_vertices(vhandles); // from spec: A minimum of three vertices are required. if( vhandles.size() > 2 ){ if ( !face_texcoords.empty() ) //if we have texCoords add face+texCoords _importer.addFace(vhandles, face_texcoords ); else //otherwise just add the face _importer.addFace(vhandles); faceCount++; } //add material to the last added face(s) //if polygons get triangulated this can be more than one face _importer.addMaterial( matname.toStdString() ); } #ifdef ENABLE_BSPLINECURVE_SUPPORT // param else if ( (mode == CURVE && keyWrd == QLatin1String("parm")) || (mode == CURVE && keyWrd == QLatin1String("parm_add")) ){ //get curve knots QString paramLine; QString tmp; paramLine = stream.readLine(); // value may contain a / as line separator if ( paramLine.endsWith(QLatin1String("\\"))){ paramLine = paramLine.left( paramLine.length()-1); nextKeyWrd = QLatin1String("parm_add"); } lineData.setString( ¶mLine ); if ( keyWrd != QLatin1String("parm_add")) lineData >> tmp; //push the first u out // work on the line until nothing left to read while ( !lineData.atEnd() && lineData.status()==QTextStream::Ok ) { double knot; knot = getDouble(lineData); if ( lineData.status() == QTextStream::Ok ) knotsU.push_back( knot ); } } // curve else if ( (mode == NONE && keyWrd == QLatin1String("curv")) || (mode == CURVE && keyWrd == QLatin1String("curv_add")) ){ if (!firstFace) firstFace = true; inGroup = false; mode = CURVE; if ( keyWrd == QLatin1String("curv") ) { int id = _importer.getCurveGroupId(curveCount); if(id == -1) { std::cerr << "Error: Group has not been added before!" << std::endl; return; } _importer.setCurrentGroup(id); curveCount++; } //get curve control points QString curveLine; curveLine = stream.readLine(); // value may contain a / as line separator if ( curveLine.endsWith(QLatin1String("\\"))){ curveLine = curveLine.left(curveLine.length()-1); nextKeyWrd = QLatin1String("curv_add"); } lineData.setString( &curveLine ); // Read knots at the beginning before the indices if ( keyWrd == QLatin1String("curv") ) { double trash; trash = getDouble(lineData); trash = getDouble(lineData); } // work on the line until nothing left to read while ( !lineData.atEnd() && lineData.status()==QTextStream::Ok ) { int index = 0; lineData >> index; if ( index < 0 ) { // Calculation of index : // -1 is the last vertex in the list // As obj counts from 1 and not zero add +1 index = currentVertexCount + index + 1; } if ( lineData.status()==QTextStream::Ok ) cpIndices.push_back( index -1 ); } } // end else if (mode == CURVE && keyWrd == QLatin1String("end")){ if ( _importer.isCurve( _importer.currentObject() ) ){ // set up the spline curve _importer.currentCurve()->set_degree( _importer.degreeU() ); _importer.currentCurve()->autocompute_knotvector(false); // add the control points std::vector< ACG::Vec3d > controlPolygon; for (unsigned int i = 0; i < cpIndices.size(); ++i) controlPolygon.push_back( (ACG::Vec3d) _importer.vertex( cpIndices[i] ) ); _importer.currentCurve()->set_control_polygon( controlPolygon ); _importer.currentCurve()->set_knots(knotsU); } cpIndices.clear(); knotsU.clear(); mode = NONE; } #endif #ifdef ENABLE_BSPLINESURFACE_SUPPORT // param else if ( (mode == SURFACE && keyWrd == QLatin1String("parm")) || (mode == SURFACE && keyWrd == QLatin1String("parm_add")) ){ //get surface knots QString paramLine; QString tmp; paramLine = stream.readLine(); // value may contain a / as line separator if ( paramLine.endsWith(QLatin1String("\\"))){ paramLine = paramLine.left(paramLine.length()-1); nextKeyWrd = QLatin1String("parm_add"); } lineData.setString( ¶mLine ); if ( keyWrd == QLatin1String("parm_add_u")) tmp = QLatin1String("u"); else if ( keyWrd == QLatin1String("parm_add_v")) tmp = QLatin1String("v"); else lineData >> tmp; //get the direction (u or v) std::vector< double >* knots; //Decide if these are knots in U or V direction if (tmp == QLatin1String("u")) knots = &knotsU; else knots = &knotsV; if (nextKeyWrd != QLatin1String("")) nextKeyWrd += QLatin1String("_") + tmp; // work on the line until nothing left to read while ( !lineData.atEnd() && lineData.status()==QTextStream::Ok ) { double knot; knot = getDouble(lineData); if ( lineData.status()==QTextStream::Ok ) { knots->push_back( knot ); } } } // surface else if ( (mode == NONE && keyWrd == QLatin1String("surf")) || (mode == SURFACE && keyWrd == QLatin1String("surf_add")) ){ if (!firstFace) firstFace = true; inGroup = false; mode = SURFACE; if ( keyWrd == QLatin1String("surf") ) { int id = _importer.getSurfaceGroupId(surfaceCount); if(id == -1) { std::cerr << "Error: Group has not been added before!" << std::endl; return; } _importer.setCurrentGroup(id); surfaceCount++; } //get surface control points QString surfLine; surfLine = stream.readLine(); // value may contain a / as line separator if ( surfLine.endsWith(QLatin1String("\\"))){ surfLine = surfLine.left(surfLine.length()-1); nextKeyWrd = QLatin1String("surf_add"); } lineData.setString( &surfLine ); // work on the line until nothing left to read while ( !lineData.atEnd() && lineData.status()==QTextStream::Ok ) { int index = 0; lineData >> index; if ( index < 0 ) { // Calculation of index : // -1 is the last vertex in the list // As obj counts from 1 and not zero add +1 index = currentVertexCount + index + 1; } if ( lineData.status()==QTextStream::Ok ) cpIndices.push_back( index -1 ); } } // end else if (mode == SURFACE && keyWrd == QLatin1String("end")){ if ( _importer.isSurface( _importer.currentObject() ) ){ // remove first 4 entries since they are the first and last knot (for both direction) cpIndices.erase(cpIndices.begin()); cpIndices.erase(cpIndices.begin()); cpIndices.erase(cpIndices.begin()); cpIndices.erase(cpIndices.begin()); // set up the spline surface _importer.currentSurface()->set_degree( _importer.degreeU(), _importer.degreeV() ); // compute number of control points in m and in n direction int dimU = knotsU.size() - _importer.degreeU() - 1; int dimV = knotsV.size() - _importer.degreeV() - 1; // add the control points std::vector< ACG::Vec3d > controlPolygon; for (int i = 0; i < dimU; ++i) { controlPolygon.clear(); for (int j = 0; j < dimV; ++j){ controlPolygon.push_back( (ACG::Vec3d) _importer.vertex( cpIndices[dimU * j + i] ) ); } _importer.currentSurface()->add_vector_m(controlPolygon); } _importer.currentSurface()->set_knots_m(knotsU); _importer.currentSurface()->set_knots_n(knotsV); } cpIndices.clear(); knotsU.clear(); knotsV.clear(); mode = NONE; } #endif } //checks, if an object with a specified type was added. if not, point cloud was read bool isType = faceCount != 0; #ifdef ENABLE_BSPLINECURVE_SUPPORT isType = isType || curveCount != 0; #endif #ifdef ENABLE_BSPLINESURFACE_SUPPORT isType = isType || surfaceCount != 0; #endif // we have only read points so far and no faces or modes // treat them as a polymesh if (!isType && currentVertexCount != 0 ) { _importer.forceMeshType( OBJImporter::POLYMESH ); //actually it is a pointcloud if (!inGroup) _importer.setCurrentGroup(0); } } ///check file types and read general info like vertices void FileOBJPlugin::checkTypes(QByteArray& _bufferedFile, QString _filename, OBJImporter& _importer, QStringList& _includes) { ptr::shared_ptr streamPointer; ptr::shared_ptr sourceFile; //setup filestream if not in memory if (_bufferedFile.isNull() || _bufferedFile.isEmpty()) { sourceFile.reset(new QFile(_filename)); if(!sourceFile->open(QFile::ReadOnly)) { emit log(LOGERR, tr("readOBJFile : cannot open file %1 while checking Types").arg(_filename) ); return; } streamPointer.reset(new QTextStream(sourceFile.get())); } else { streamPointer.reset(new QTextStream(&_bufferedFile)); } QTextStream input(streamPointer->device()); QTextStream stream; QTextStream lineData; QTextStream tmp; if ( input.status()!=QTextStream::Ok ){ emit log(LOGERR, tr("readOBJFile : cannot read file %1 while checking Types (is the file corrupt?)").arg(_filename) ); return; } ReaderMode mode = NONE; QString line; QString keyWrd; QString nextKeyWrd = QLatin1String(""); #ifdef ENABLE_BSPLINECURVE_SUPPORT unsigned int curveCount = 0; #endif #ifdef ENABLE_BSPLINESURFACE_SUPPORT unsigned int surfaceCount = 0; #endif float x, y, z; int faceCount = 0; int PolyMeshCount = 0; int TriMeshCount = 0; OBJImporter::ObjectOptions options = OBJImporter::NONE; // keeps track if faces belong to a group or the default group bool inGroup = false; // keeps track if the first face of a mesh has been read yet or not bool firstFace = true; #if defined ENABLE_BSPLINECURVE_SUPPORT || defined ENABLE_BSPLINESURFACE_SUPPORT QString currentGroupName; int parentId = -1; #endif while( !input.atEnd()) { line = input.readLine(); if ( input.status()!=QTextStream::Ok ){ emit log(LOGERR, tr("readOBJFile : Warning! Could not read file properly!")); return; } // Trim Both leading and trailing spaces line = line.trimmed(); // comment if ( line.isEmpty() || line[0] == QLatin1Char('#') || line[0].isSpace() ) { continue; } stream.setString(&line); //unless the keyWrd for the new line is not determined by the previous line //read it from stream if (nextKeyWrd == QLatin1String("")) stream >> keyWrd; else { keyWrd = nextKeyWrd; nextKeyWrd = QLatin1String(""); } //call - included obj files if (mode == NONE && keyWrd == QLatin1String("call")){ firstFace = true; QString include; include =stream.readLine(); //replace relative path QString includeStr = include.trimmed(); if ( !includeStr.isEmpty() ){ if (includeStr[0] == QLatin1Char('.')){ includeStr = includeStr.right( includeStr.length()-1 ); QFileInfo fi(_filename); includeStr = fi.path() + QDir::separator() + includeStr; } _includes.append( includeStr ); } _importer.setObjectOptions(OBJImporter::NONE); } // vertex else if (mode == NONE && keyWrd == QLatin1String("v")) { if (!firstFace) firstFace = true; x = getFloat(stream); y = getFloat(stream); z = getFloat(stream); if ( stream.status()==QTextStream::Ok ) _importer.addVertex( OpenMesh::Vec3f(x,y,z) ); else emit log(LOGERR, tr("Could not add Vertex %1. Possible NaN or Inf?").arg(_importer.n_vertices())); } // group else if (mode == NONE && keyWrd == QLatin1String("g")){ if (!firstFace) firstFace = true; //give options to importer and reinitialize //for next object QString grpName; grpName = stream.readLine(); if ( options & OBJImporter::TRIMESH ) TriMeshCount++; if ( options & OBJImporter::POLYMESH ) PolyMeshCount++; int id = _importer.addGroup(grpName); #if defined ENABLE_BSPLINECURVE_SUPPORT || defined ENABLE_BSPLINESURFACE_SUPPORT parentId = id; currentGroupName = grpName; currentGroupName.remove(QLatin1String(".obj")); #endif _importer.setCurrentGroup(id); // all following elements are in this group until // a new group is created inGroup = true; _importer.setObjectOptions( options ); options = OBJImporter::NONE; faceCount = 0; } // face else if (mode == NONE && keyWrd == QLatin1String("f")){ if (firstFace) { // store faces in the default group if we aren't in a group already if (!inGroup) _importer.setCurrentGroup(0); firstFace = false; } faceCount++; int verticesPerFace = 0; int value; // read full line after detecting a face QString faceLine; faceLine = stream.readLine(); lineData.setString( &faceLine ); // work on the line until nothing left to read while ( !lineData.atEnd() ) { // read one block from the line ( vertex/texCoord/normal ) QString vertex; lineData >> vertex; verticesPerFace++; //get the vertex component (vertex/texCoord/normal) int found=vertex.indexOf(QLatin1String("/")); // parts are seperated by '/' So if no '/' found its the last component if( found != -1 ){ // read the index value QString vertexEntry = vertex.left(found); tmp.setString( &vertexEntry ); // Read current value tmp >> value; if ( tmp.status()!=QTextStream::Ok ) emit log(LOGERR, tr("readOBJFile : Error reading vertex index!")); } else { // last component of the vertex, read it. tmp.setString( &vertex ); tmp >> value; if ( tmp.status()!=QTextStream::Ok ) emit log(LOGERR, tr("readOBJFile : Error reading vertex index!")); } if ( value < 0 ) { // Calculation of index : // -1 is the last vertex in the list // As obj counts from 1 and not zero add +1 value = _importer.n_vertices() + value + 1; } // Obj counts from 1 and not zero .. array counts from zero therefore -1 // the importer has to know which vertices are used by the object for correct vertex order _importer.useVertex( value -1 ); } if( verticesPerFace > 3 ) { options = OBJImporter::POLYMESH; _importer.setObjectOptions(options); } else if ( verticesPerFace == 3 ) { options = OBJImporter::TRIMESH; _importer.setObjectOptions(options); } } #ifdef ENABLE_BSPLINECURVE_SUPPORT // curve if ( (mode == NONE && keyWrd == QLatin1String("curv")) || (mode == CURVE && keyWrd == QLatin1String("curv_add")) ){ if (!firstFace) firstFace = true; inGroup = false; mode = CURVE; if ( keyWrd == QLatin1String("curv") ) { //give options to importer and reinitialize //for next object if ( options & OBJImporter::TRIMESH ) TriMeshCount++; if ( options & OBJImporter::POLYMESH ) PolyMeshCount++; QString name = currentGroupName; if (name.size() == 0) name = QLatin1String("DefaultGroup"); name.append(QString("_curve_%1").arg(curveCount)); int id = _importer.addGroup(name); if (_importer.numCurves() == 0) { if (currentGroupName.size() == 0) _importer.setGroupName(id, QString("DefaultGroup")); else _importer.setGroupName(id, currentGroupName); } else { if (curveCount == 1) { int first = _importer.getCurveGroupId(0); QString tmp = _importer.groupName(first); tmp.append(QString("_curve_0")); _importer.setGroupName(first, tmp); } _importer.setGroupName(id, name); } _importer.setCurveParentId(id, parentId); _importer.setCurrentGroup(id); _importer.setCurveGroupId(curveCount, id); curveCount++; _importer.setObjectOptions( options ); options = OBJImporter::CURVE; } //get curve control points QString curveLine; curveLine=stream.readLine(); // value may contain a / as line separator if ( curveLine.endsWith(QLatin1String("\\"))){ curveLine = curveLine.left( curveLine.length()-1); nextKeyWrd = QLatin1String("curv_add"); } lineData.setString( &curveLine ); // Read knots at the beginning before the indices if ( keyWrd == QLatin1String("curv") ) { double trash; trash = getDouble(lineData); trash = getDouble(lineData); } // work on the line until nothing left to read while ( !lineData.atEnd() && lineData.status()==QTextStream::Ok ) { int index = 0; lineData >> index; if ( lineData.status()==QTextStream::Ok ){ // the importer has to know which vertices are used by the object for correct vertex order _importer.useVertex( index -1 ); } } } // end else if (mode == CURVE && keyWrd == QLatin1String("end")){ mode = NONE; _importer.setObjectOptions( options ); options = OBJImporter::TRIMESH; faceCount = 0; } #endif #ifdef ENABLE_BSPLINESURFACE_SUPPORT // surface if ( (mode == NONE && keyWrd == QLatin1String("surf")) || (mode == SURFACE && keyWrd == QLatin1String("surf_add")) ){ if (!firstFace) firstFace = true; inGroup = false; mode = SURFACE; if ( keyWrd == QLatin1String("surf") ){ //give options to importer and reinitialize //for next object if ( options & OBJImporter::TRIMESH ) TriMeshCount++; if ( options & OBJImporter::POLYMESH ) PolyMeshCount++; QString name = currentGroupName; if (name.size() == 0) name = QLatin1String("DefaultGroup"); name.append(QString("_surface_%1").arg(surfaceCount)); int id = _importer.addGroup(name); if (_importer.numSurfaces() == 0) { if (currentGroupName.size() == 0) _importer.setGroupName(id, "DefaultGroup"); else _importer.setGroupName(id, currentGroupName); } else { if (surfaceCount == 1) { int first = _importer.getSurfaceGroupId(0); QString tmp = _importer.groupName(first); tmp.append(QString("_surface_0")); _importer.setGroupName(first, tmp); } _importer.setGroupName(id, name); } _importer.setSurfaceParentId(id, parentId); _importer.setCurrentGroup(id); _importer.setSurfaceGroupId(surfaceCount, id); surfaceCount++; _importer.setObjectOptions( options ); options = OBJImporter::SURFACE; } //get surface control points QString surfLine; surfLine = stream.readLine(); // value may contain a / as line separator if ( surfLine.endsWith(QLatin1String("\\"))){ surfLine = surfLine.left(surfLine.length()-1); nextKeyWrd = QLatin1String("surf_add"); } lineData.setString( &surfLine ); // work on the line until nothing left to read while ( !lineData.atEnd() && lineData.status()==QTextStream::Ok ) { int index = 0; lineData >> index; if ( lineData.status()==QTextStream::Ok ){ // the importer has to know which vertices are used by the object for correct vertex order _importer.useVertex( index -1 ); } } } // end else if (mode == SURFACE && keyWrd == QLatin1String("end")){ mode = NONE; _importer.setObjectOptions( options ); options = OBJImporter::TRIMESH; faceCount = 0; } #endif } if(faceCount > 0) { if ( options & OBJImporter::TRIMESH ) TriMeshCount++; if ( options & OBJImporter::POLYMESH ) PolyMeshCount++; _importer.setObjectOptions( options ); } else { // Mesh does not contain any faces PolyMeshCount++; if (keyWrd != QLatin1String("call")) { // we only have vertices and no faces if (keyWrd == QLatin1String("v") && !inGroup) { _importer.setCurrentGroup(0); // treat the file as a polymesh forceTriangleMesh_ = false; forcePolyMesh_ = true; _importer.setObjectOptions(OBJImporter::POLYMESH); _importer.forceMeshType( OBJImporter::POLYMESH ); for (unsigned int i = 0; i < _importer.n_vertices(); ++i) _importer.useVertex(i); } else { unsigned int currentOptions = _importer.objectOptions()[_importer.currentObject()]; // this is only a triangle mesh if the object is not a curve and not a surface // also ignore if it is set to NONE if (!(currentOptions & OBJImporter::CURVE) && !(currentOptions & OBJImporter::SURFACE) && (currentOptions != OBJImporter::NONE)) _importer.setObjectOptions(OBJImporter::TRIMESH); } } } if (TriMeshCount == 0 && PolyMeshCount == 0) { return; } if (forceTriangleMesh_){ _importer.forceMeshType( OBJImporter::TRIMESH ); return; } if (forcePolyMesh_){ _importer.forceMeshType( OBJImporter::POLYMESH ); return; } // If we do not have a gui, we will always use the last default // If we need a gui and the triMeshHandling box is not generated (==0) we also use the last default if ( OpenFlipper::Options::gui() && triMeshHandling_ != 0 ){ switch( triMeshHandling_->currentIndex() ){ case TYPEAUTODETECT : //Detect break; case TYPEASK: //ask QMetaObject::invokeMethod(this,"handleTrimeshDialog",Qt::BlockingQueuedConnection); if (trimeshOptions == OBJImporter::TRIMESH ) _importer.forceMeshType( OBJImporter::TRIMESH ); else if (trimeshOptions == OBJImporter::POLYMESH) _importer.forceMeshType( OBJImporter::POLYMESH ); break; case TYPEPOLY : //polyMesh _importer.forceMeshType( OBJImporter::POLYMESH ); break; case TYPETRIANGLE : //trimesh _importer.forceMeshType( OBJImporter::TRIMESH ); break; default: break; } } } void FileOBJPlugin::handleTrimeshDialog() { QMessageBox msgBox; QPushButton *detectButton = msgBox.addButton(tr("Auto-Detect"), QMessageBox::ActionRole); QPushButton *triButton = msgBox.addButton(tr("Open as triangle mesh"), QMessageBox::ActionRole); QPushButton *polyButton = msgBox.addButton(tr("Open as poly mesh"), QMessageBox::ActionRole); msgBox.setWindowTitle( tr("Mesh types in file") ); msgBox.setText( tr("You are about to open a file containing one or more mesh types. \n\n Which mesh type should be used?") ); msgBox.setDefaultButton( detectButton ); msgBox.exec(); if (msgBox.clickedButton() == triButton) trimeshOptions = OBJImporter::TRIMESH ; else if (msgBox.clickedButton() == polyButton) trimeshOptions = OBJImporter::POLYMESH ; } //----------------------------------------------------------------------------------------------------- int FileOBJPlugin::loadObject(QString _filename) { OBJImporter importer; //included filenames QStringList includes; QFile sourceFile(_filename); if (!sourceFile.open(QFile::ReadOnly)) { emit log(LOGERR, tr("readOBJFile : cannot open file %1 while checking Types").arg(_filename)); return -1; } QByteArray bufferedFile = QByteArray(); //load the entire file to ram if we have at least double the size as free memory. //otherwise the bytearray stays null and the file is read when it is processed. unsigned long freeMem = Utils::Memory::queryFreeRAM(); unsigned long fs = sourceFile.size() / 1024 / 1024; if (freeMem >= 2*fs) { bufferedFile = sourceFile.readAll(); } //preprocess file and store types in ObjectOptions checkTypes( bufferedFile, _filename, importer, includes ); IdList objIDs; //load included obj files for (int i=0; i < includes.size(); i++){ //int id = loadObject( includes[i], importer ); int id = loadObject( includes[i] ); if (id != -1) objIDs.push_back( id ); } //add a group if we have includes if ( ! includes.empty() ) importer.addGroup( QFileInfo(_filename).fileName() ); //check if something was found if ( importer.objectOptions().empty() && objIDs.empty() ){ forceTriangleMesh_ = false; forcePolyMesh_ = false; return -1; } //then parse the obj readOBJFile( bufferedFile, _filename, importer ); // finish up importer.finish(); int returnID = -1; //perhaps add group if ( importer.numGroups() > 1){ bool dataControlExists = false; pluginExists( "datacontrol", dataControlExists ); if ( dataControlExists ){ std::vector options = importer.objectOptions(); #if defined ENABLE_BSPLINECURVE_SUPPORT || defined ENABLE_BSPLINESURFACE_SUPPORT std::map groupNames; #endif #ifdef ENABLE_BSPLINECURVE_SUPPORT std::vector< std::vector > curveGroups; std::vector curveIds; int lastCurveParent = -2; #endif #ifdef ENABLE_BSPLINESURFACE_SUPPORT std::vector< std::vector > surfaceGroups; std::vector surfaceIds; int lastSurfaceParent = -2; #endif for(unsigned int i = 0; i < importer.objectCount(); i++) { // skip the object if it has no option // this can happen if the object only included other objects if (options[i] != NONE) { BaseObject* obj = importer.object(i); if(obj) { #ifdef ENABLE_BSPLINECURVE_SUPPORT if (options[i] & OBJImporter::CURVE) { // store the parent group name for later grouping groupNames[obj->id()] = importer.groupName(importer.getCurveParentId(i)); // first curve group if (lastCurveParent == -2) { lastCurveParent = importer.getCurveParentId(i); curveIds.push_back(obj->id()); BaseObject* parent = importer.object(lastCurveParent); if (parent) { curveIds.push_back(parent->id()); // don't group the parent in the objIDs group std::vector::iterator pos = std::find(objIDs.begin(), objIDs.end(), parent->id()); if (pos != objIDs.end()) objIDs.erase(pos); } // new curve group } else if (lastCurveParent != importer.getCurveParentId(i)) { lastCurveParent = importer.getCurveParentId(i); curveGroups.push_back(curveIds); curveIds.clear(); curveIds.push_back(obj->id()); BaseObject* parent = importer.object(lastCurveParent); if (parent) { curveIds.push_back(parent->id()); // don't group the parent in the objIDs group std::vector::iterator pos = std::find(objIDs.begin(), objIDs.end(), parent->id()); if (pos != objIDs.end()) objIDs.erase(pos); } // add curves to group } else curveIds.push_back(obj->id()); } #endif #ifdef ENABLE_BSPLINESURFACE_SUPPORT if (options[i] & OBJImporter::SURFACE) { // store the parent group name for later grouping groupNames[obj->id()] = importer.groupName(importer.getSurfaceParentId(i)); // first surface group if (lastSurfaceParent == -2) { lastSurfaceParent = importer.getSurfaceParentId(i); surfaceIds.push_back(obj->id()); BaseObject* parent = importer.object(lastSurfaceParent); if (parent) { surfaceIds.push_back(parent->id()); std::vector::iterator pos = std::find(objIDs.begin(), objIDs.end(), parent->id()); if (pos != objIDs.end()) objIDs.erase(pos); } // new surface group } else if (lastSurfaceParent != importer.getSurfaceParentId(i)) { lastSurfaceParent = importer.getSurfaceParentId(i); surfaceGroups.push_back(surfaceIds); surfaceIds.clear(); surfaceIds.push_back(obj->id()); BaseObject* parent = importer.object(lastSurfaceParent); if (parent) { surfaceIds.push_back(parent->id()); std::vector::iterator pos = std::find(objIDs.begin(), objIDs.end(), parent->id()); if (pos != objIDs.end()) objIDs.erase(pos); } // add surfaces to group } else surfaceIds.push_back(obj->id()); } #endif if ( (options[i] & OBJImporter::TRIMESH) || (options[i] & OBJImporter::POLYMESH) ) objIDs.push_back( obj->id() ); } else { std::cerr << "Object is NULL!" << std::endl; } } } #ifdef ENABLE_BSPLINECURVE_SUPPORT // add last group curveGroups.push_back(curveIds); std::vector< std::vector >::iterator it = curveGroups.begin(); for (; it != curveGroups.end(); ++it) { // only group if we have more than one curve if (it->size() > 2) { if (groupNames[it->back()].size() == 0) RPC::callFunctionValue("datacontrol","groupObjects", *it, QFileInfo(_filename).fileName()); else RPC::callFunctionValue("datacontrol","groupObjects", *it, groupNames[it->back()]); } } #endif #ifdef ENABLE_BSPLINESURFACE_SUPPORT // add last group surfaceGroups.push_back(surfaceIds); std::vector< std::vector >::iterator it2 = surfaceGroups.begin(); for (; it2 != surfaceGroups.end(); ++it2) { // only group if we have more than one surface if (it2->size() > 2) { if (groupNames[it2->back()].size() == 0) RPC::callFunctionValue("datacontrol","groupObjects", *it2, QFileInfo(_filename).fileName()); else RPC::callFunctionValue("datacontrol","groupObjects", *it2, groupNames[it2->back()]); } } #endif // only group if we have more than one object if (objIDs.size() > 1) returnID = RPC::callFunctionValue("datacontrol","groupObjects", objIDs, importer.groupName(0)); } } //check all new objects for(unsigned int i=0; i < importer.objectCount(); i++){ BaseObject* object = importer.object(i); if(object == NULL) continue; object->setFromFileName(_filename); //remember the id of the first opened object if ( returnID == -1) returnID = object->id(); //handle new PolyMeshes PolyMeshObject* polyMeshObj = dynamic_cast< PolyMeshObject* > (object); if ( polyMeshObj ){ if ( !importer.hasNormals(i) ) polyMeshObj->mesh()->update_normals(); else polyMeshObj->mesh()->update_face_normals(); } //handle new TriMeshes TriMeshObject* triMeshObj = dynamic_cast< TriMeshObject* > (object); if ( triMeshObj ){ if ( !importer.hasNormals(i) || importer.hasOption( i, OBJImporter::FORCE_NONORMALS ) ) triMeshObj->mesh()->update_normals(); else triMeshObj->mesh()->update_face_normals(); } #ifdef ENABLE_BSPLINECURVE_SUPPORT //handle new BSplineCurves BSplineCurveObject* bscObj = dynamic_cast< BSplineCurveObject* > (object); if ( bscObj ){ bscObj->splineCurveNode()->updateGeometry(); } #endif //textures if ( importer.hasTexture(i) && !importer.hasOption( i, OBJImporter::FORCE_NOTEXTURES ) ){ //add the textures to the object addTextures( importer, i ); //set the texture index property to be used emit setTextureMode("OBJ Data","indexProperty=OriginalTexIndexMapping", object->id() ); emit switchTexture("OBJ Data", object->id() ); PluginFunctions::setDrawMode( ACG::SceneGraph::DrawModes::SOLID_2DTEXTURED_FACE_SHADED, PluginFunctions::ALL_VIEWERS ); } //general stuff emit updatedObject( object->id(), UPDATE_ALL ); emit openedFile( object->id() ); } forceTriangleMesh_ = false; forcePolyMesh_ = false; // if ( topLevelObj ) // OpenFlipper::Options::loadingSettings(false); return returnID; } //----------------------------------------------------------------------------------------------------- /// load a obj and force mesh datatype int FileOBJPlugin::loadObject(QString _filename, DataType _type){ if ( _type == DATA_TRIANGLE_MESH ) forceTriangleMesh_ = true; else if ( _type == DATA_POLY_MESH ) forcePolyMesh_ = true; return loadObject(_filename); } //----------------------------------------------------------------------------------------------------- bool FileOBJPlugin::saveObject(int _id, QString _filename) { BaseObjectData* object; if ( !PluginFunctions::getObject(_id,object) ) { emit log(LOGERR, tr("saveObject : cannot get object id %1 for save name %2").arg(_id).arg(_filename) ); return false; } //open output stream std::string filename = std::string( _filename.toUtf8() ); std::fstream objStream( filename.c_str(), std::ios_base::out ); if ( !objStream ){ emit log(LOGERR, tr("saveObject : cannot not open file %1").arg(_filename) ); return false; } //write object if ( object->dataType( DATA_POLY_MESH ) ) { object->setFromFileName(_filename); object->setName(object->filename()); PolyMeshObject* polyObj = dynamic_cast( object ); if ( writeMesh( objStream, _filename, *polyObj->mesh(), polyObj->id() ) ){ emit log(LOGINFO, tr("Saved object to ") + _filename ); objStream.close(); return true; } else { emit log(LOGERR, tr("Unable to save ") + _filename); objStream.close(); return false; } } else if ( object->dataType( DATA_TRIANGLE_MESH ) ) { object->setFromFileName(_filename); object->setName(object->filename()); TriMeshObject* triObj = dynamic_cast( object ); if ( writeMesh( objStream, _filename, *triObj->mesh(), triObj->id() )) { emit log(LOGINFO, tr("Saved object to ") + _filename ); objStream.close(); return true; } else { emit log(LOGERR, tr("Unable to save ") + _filename ); objStream.close(); return false; } #ifdef ENABLE_BSPLINECURVE_SUPPORT } else if ( object->dataType( DATA_BSPLINE_CURVE ) ) { object->setFromFileName(_filename); object->setName(object->filename()); BSplineCurveObject* bscObj = dynamic_cast( object ); if ( writeCurve( objStream, _filename, bscObj->splineCurve()) ) { emit log(LOGINFO, tr("Saved object to ") + _filename ); objStream.close(); return true; } else { emit log(LOGERR, tr("Unable to save ") + _filename ); objStream.close(); return false; } #endif #ifdef ENABLE_BSPLINESURFACE_SUPPORT } else if ( object->dataType( DATA_BSPLINE_SURFACE ) ) { object->setFromFileName(_filename); object->setName(object->filename()); BSplineSurfaceObject* bssObj = dynamic_cast( object ); if ( writeSurface( objStream, _filename, bssObj->splineSurface()) ) { emit log(LOGINFO, tr("Saved object to ") + _filename ); objStream.close(); return true; } else { emit log(LOGERR, tr("Unable to save ") + object->path() + OpenFlipper::Options::dirSeparator() + object->name()); objStream.close(); return false; } #endif } else { emit log(LOGERR, tr("Unable to save (object is not a compatible mesh type)")); objStream.close(); return false; } } //----------------------------------------------------------------------------------------------------- void FileOBJPlugin::slotHandleCheckBoxes(bool _checked) { if(saveCopyTextures_) { saveCreateTexFolder_->setEnabled(_checked); saveCreateTexFolder_->setChecked(_checked); } } //----------------------------------------------------------------------------------------------------- QWidget* FileOBJPlugin::saveOptionsWidget(QString /*_currentFilter*/) { if (saveOptions_ == 0){ //generate widget saveOptions_ = new QWidget(); QVBoxLayout* layout = new QVBoxLayout(); layout->setAlignment(Qt::AlignTop); saveFaceColor_ = new QCheckBox("Save Face Colors"); layout->addWidget(saveFaceColor_); saveAlpha_ = new QCheckBox("Save Color Alpha"); layout->addWidget(saveAlpha_); saveNormals_ = new QCheckBox("Save Normals"); layout->addWidget(saveNormals_); saveTexCoords_ = new QCheckBox("Save Texture Coordinates"); layout->addWidget(saveTexCoords_); saveTextures_ = new QCheckBox("Save Textures"); layout->addWidget(saveTextures_); saveCopyTextures_ = new QCheckBox("Copy Texture Files"); layout->addWidget(saveCopyTextures_); saveCreateTexFolder_ = new QCheckBox("Create Textures Folder"); layout->addWidget(saveCreateTexFolder_); savePrecisionLabel_ = new QLabel("Writer Precision"); layout->addWidget(savePrecisionLabel_); savePrecision_ = new QSpinBox(); savePrecision_->setMinimum(1); savePrecision_->setMaximum(12); savePrecision_->setValue(6); layout->addWidget(savePrecision_); saveDefaultButton_ = new QPushButton("Make Default"); layout->addWidget(saveDefaultButton_); saveOptions_->setLayout(layout); connect(saveDefaultButton_, SIGNAL(clicked()), this, SLOT(slotSaveDefault())); connect(saveCopyTextures_, SIGNAL(toggled(bool)), this, SLOT(slotHandleCheckBoxes(bool))); saveFaceColor_->setChecked( OpenFlipperSettings().value("FileObj/Save/FaceColor",true).toBool() ); saveAlpha_->setChecked( OpenFlipperSettings().value("FileObj/Save/Alpha",true).toBool() ); saveNormals_->setChecked( OpenFlipperSettings().value("FileObj/Save/Normals",true).toBool() ); saveTexCoords_->setChecked( OpenFlipperSettings().value("FileObj/Save/TexCoords",true).toBool() ); saveTextures_->setChecked( OpenFlipperSettings().value("FileObj/Save/Textures",true).toBool() ); saveCopyTextures_->setChecked( OpenFlipperSettings().value("FileObj/Save/CopyTextures",true).toBool() ); saveCreateTexFolder_->setChecked( OpenFlipperSettings().value("FileObj/Save/CreateTexFolder",true).toBool() ); slotHandleCheckBoxes(saveCopyTextures_->isChecked()); } return saveOptions_; } //----------------------------------------------------------------------------------------------------- QWidget* FileOBJPlugin::loadOptionsWidget(QString /*_currentFilter*/) { if (loadOptions_ == 0){ //generate widget loadOptions_ = new QWidget(); QVBoxLayout* layout = new QVBoxLayout(); layout->setAlignment(Qt::AlignTop); QLabel* label = new QLabel(tr("If file contains meshes:")); layout->addWidget(label); triMeshHandling_ = new QComboBox(); triMeshHandling_->addItem( tr("Detect correct type") ); triMeshHandling_->addItem( tr("Ask") ); triMeshHandling_->addItem( tr("Open as PolyMesh") ); triMeshHandling_->addItem( tr("Open as TriangleMesh") ); layout->addWidget(triMeshHandling_); loadFaceColor_ = new QCheckBox("Load Face Colors"); layout->addWidget(loadFaceColor_); loadNormals_ = new QCheckBox("Load Normals"); layout->addWidget(loadNormals_); loadTexCoords_ = new QCheckBox("Load Texture Coordinates"); layout->addWidget(loadTexCoords_); loadTextures_ = new QCheckBox("Load Textures"); layout->addWidget(loadTextures_); loadDefaultButton_ = new QPushButton("Make Default"); layout->addWidget(loadDefaultButton_); loadOptions_->setLayout(layout); connect(loadDefaultButton_, SIGNAL(clicked()), this, SLOT(slotLoadDefault())); triMeshHandling_->setCurrentIndex(OpenFlipperSettings().value("FileObj/Load/TriMeshHandling",TYPEAUTODETECT).toInt() ); loadFaceColor_->setChecked( OpenFlipperSettings().value("FileObj/Load/FaceColor",true).toBool() ); loadNormals_->setChecked( OpenFlipperSettings().value("FileObj/Load/Normals",true).toBool() ); loadTexCoords_->setChecked( OpenFlipperSettings().value("FileObj/Load/TexCoords",true).toBool() ); loadTextures_->setChecked( OpenFlipperSettings().value("FileObj/Load/Textures",true).toBool() ); } return loadOptions_; } void FileOBJPlugin::slotLoadDefault() { OpenFlipperSettings().setValue( "FileObj/Load/FaceColor", loadFaceColor_->isChecked() ); OpenFlipperSettings().setValue( "FileObj/Load/Normals", loadNormals_->isChecked() ); OpenFlipperSettings().setValue( "FileObj/Load/TexCoords", loadTexCoords_->isChecked() ); OpenFlipperSettings().setValue( "FileObj/Load/Textures", loadTextures_->isChecked() ); OpenFlipperSettings().setValue("FileObj/Load/TriMeshHandling", triMeshHandling_->currentIndex() ); OpenFlipperSettings().setValue( "Core/File/UseLoadDefaults", true ); } void FileOBJPlugin::slotSaveDefault() { OpenFlipperSettings().setValue( "FileObj/Save/FaceColor", saveFaceColor_->isChecked() ); OpenFlipperSettings().setValue( "FileObj/Save/Normals", saveNormals_->isChecked() ); OpenFlipperSettings().setValue( "FileObj/Save/TexCoords", saveTexCoords_->isChecked() ); OpenFlipperSettings().setValue( "FileObj/Save/Textures", saveTextures_->isChecked() ); OpenFlipperSettings().setValue( "FileObj/Save/CopyTextures", saveCopyTextures_->isChecked() ); OpenFlipperSettings().setValue( "FileObj/Save/CreateTexFolder", saveCreateTexFolder_->isChecked() ); } #if QT_VERSION < 0x050000 Q_EXPORT_PLUGIN2( fileobjplugin , FileOBJPlugin ); #endif