CC中有两个地方使用了文件保存,一个是MainWindow,另一个是ccCommandLineParser。
MainWindow的保存按钮关联的槽是doActionSaveFile()方法,实现了点云和Mesh的保存。
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 void MainWindow::doActionSaveFile() 2 { 3 size_t selNum = m_selectedEntities.size(); 4 if (selNum == 0) 5 return; 6 7 ccHObject clouds("clouds"); 8 ccHObject meshes("meshes"); 9 ccHObject images("images"); 10 ccHObject polylines("polylines"); 11 ccHObject other("other"); 12 ccHObject otherSerializable("serializable"); 13 ccHObject::Container entitiesToDispatch; 14 entitiesToDispatch.insert(entitiesToDispatch.begin(),m_selectedEntities.begin(),m_selectedEntities.end()); 15 ccHObject entitiesToSave; 16 while (!entitiesToDispatch.empty()) 17 { 18 ccHObject* child = entitiesToDispatch.back(); 19 entitiesToDispatch.pop_back(); 20 21 if (child->isA(CC_TYPES::HIERARCHY_OBJECT)) 22 { 23 for (unsigned j=0; j<child->getChildrenNumber(); ++j) 24 entitiesToDispatch.push_back(child->getChild(j)); 25 } 26 else 27 { 28 //we put the entity in the container corresponding to its type 29 ccHObject* dest = 0; 30 if (child->isA(CC_TYPES::POINT_CLOUD)) 31 dest = &clouds; 32 else if (child->isKindOf(CC_TYPES::MESH)) 33 dest = &meshes; 34 else if (child->isKindOf(CC_TYPES::IMAGE)) 35 dest = &images; 36 else if (child->isKindOf(CC_TYPES::POLY_LINE)) 37 dest = &polylines; 38 else if (child->isSerializable()) 39 dest = &otherSerializable; 40 else 41 dest = &other; 42 43 assert(dest); 44 45 //we don't want double insertions if the user has clicked both the father and child 46 if (!dest->find(child->getUniqueID())) 47 { 48 dest->addChild(child,ccHObject::DP_NONE); 49 entitiesToSave.addChild(child,ccHObject::DP_NONE); 50 } 51 } 52 } 53 54 bool hasCloud = (clouds.getChildrenNumber() != 0); 55 bool hasMesh = (meshes.getChildrenNumber() != 0); 56 bool hasImages = (images.getChildrenNumber() != 0); 57 bool hasPolylines = (polylines.getChildrenNumber() != 0); 58 bool hasSerializable = (otherSerializable.getChildrenNumber() != 0); 59 bool hasOther = (other.getChildrenNumber() != 0); 60 61 int stdSaveTypes = static_cast<int>(hasCloud) 62 + static_cast<int>(hasMesh) 63 + static_cast<int>(hasImages) 64 + static_cast<int>(hasPolylines) 65 + static_cast<int>(hasSerializable); 66 if (stdSaveTypes == 0) 67 { 68 ccConsole::Error("Can't save selected entity(ies) this way!"); 69 return; 70 } 71 72 //we set up the right file filters, depending on the selected 73 //entities type (cloud, mesh, etc.). 74 QStringList fileFilters; 75 { 76 const FileIOFilter::FilterContainer& filters = FileIOFilter::GetFilters(); 77 for (size_t i=0; i<filters.size(); ++i) 78 { 79 bool atLeastOneExclusive = false; 80 81 //current I/O filter 82 const FileIOFilter::Shared filter = filters[i]; 83 84 //does this filter can export one or several clouds? 85 bool canExportClouds = true; 86 if (hasCloud) 87 { 88 bool isExclusive = true; 89 bool multiple = false; 90 canExportClouds = ( filter->canSave(CC_TYPES::POINT_CLOUD,multiple,isExclusive) 91 && (multiple || clouds.getChildrenNumber() == 1) ); 92 atLeastOneExclusive |= isExclusive; 93 } 94 95 //does this filter can export one or several meshes? 96 bool canExportMeshes = true; 97 if (hasMesh) 98 { 99 bool isExclusive = true; 100 bool multiple = false; 101 canExportMeshes = ( filter->canSave(CC_TYPES::MESH,multiple,isExclusive) 102 && (multiple || meshes.getChildrenNumber() == 1) ); 103 atLeastOneExclusive |= isExclusive; 104 } 105 106 //does this filter can export one or several polylines? 107 bool canExportPolylines = true; 108 if (hasPolylines) 109 { 110 bool isExclusive = true; 111 bool multiple = false; 112 canExportPolylines = ( filter->canSave(CC_TYPES::POLY_LINE,multiple,isExclusive) 113 && (multiple || polylines.getChildrenNumber() == 1) ); 114 atLeastOneExclusive |= isExclusive; 115 } 116 117 //does this filter can export one or several images? 118 bool canExportImages = true; 119 if (hasImages) 120 { 121 bool isExclusive = true; 122 bool multiple = false; 123 canExportImages = ( filter->canSave(CC_TYPES::IMAGE,multiple,isExclusive) 124 && (multiple || images.getChildrenNumber() == 1) ); 125 atLeastOneExclusive |= isExclusive; 126 } 127 128 //does this filter can export one or several other serializable entities? 129 bool canExportSerializables = true; 130 if (hasSerializable) 131 { 132 //check if all entities have the same type 133 { 134 CC_CLASS_ENUM firstClassID = otherSerializable.getChild(0)->getUniqueID(); 135 for (unsigned j=1; j<otherSerializable.getChildrenNumber(); ++j) 136 { 137 if (otherSerializable.getChild(j)->getUniqueID() != firstClassID) 138 { 139 //we add a virtual second 'stdSaveType' so as to properly handle exlusivity 140 ++stdSaveTypes; 141 break; 142 } 143 } 144 } 145 146 for (unsigned j=0; j<otherSerializable.getChildrenNumber(); ++j) 147 { 148 ccHObject* child = otherSerializable.getChild(j); 149 bool isExclusive = true; 150 bool multiple = false; 151 canExportSerializables &= ( filter->canSave(child->getUniqueID(),multiple,isExclusive) 152 && (multiple || otherSerializable.getChildrenNumber() == 1) ); 153 atLeastOneExclusive |= isExclusive; 154 } 155 } 156 157 bool useThisFilter = canExportClouds 158 && canExportMeshes 159 && canExportImages 160 && canExportPolylines 161 && canExportSerializables 162 && (!atLeastOneExclusive || stdSaveTypes == 1); 163 164 if (useThisFilter) 165 { 166 QStringList ff = filter->getFileFilters(false); 167 for (int j=0; j<ff.size(); ++j) 168 fileFilters.append(ff[j]); 169 } 170 } 171 } 172 173 //persistent settings 174 QSettings settings; 175 settings.beginGroup(ccPS::SaveFile()); 176 177 //default filter 178 QString selectedFilter = fileFilters.first(); 179 if (hasCloud) 180 selectedFilter = settings.value(ccPS::SelectedOutputFilterCloud(),selectedFilter).toString(); 181 else if (hasMesh) 182 selectedFilter = settings.value(ccPS::SelectedOutputFilterMesh(), selectedFilter).toString(); 183 else if (hasImages) 184 selectedFilter = settings.value(ccPS::SelectedOutputFilterImage(), selectedFilter).toString(); 185 else if (hasPolylines) 186 selectedFilter = settings.value(ccPS::SelectedOutputFilterPoly(), selectedFilter).toString(); 187 188 //default output path (+ filename) 189 QString currentPath = settings.value(ccPS::CurrentPath(),QApplication::applicationDirPath()).toString(); 190 QString fullPathName = currentPath; 191 if (selNum == 1) 192 { 193 //hierarchy objects have generally as name: 'filename.ext (fullpath)' 194 //so we must only take the first part! (otherwise this type of name 195 //with a path inside perturbs the QFileDialog a lot ;)) 196 QString defaultFileName(m_selectedEntities.front()->getName()); 197 if (m_selectedEntities.front()->isA(CC_TYPES::HIERARCHY_OBJECT)) 198 { 199 QStringList parts = defaultFileName.split(' ',QString::SkipEmptyParts); 200 if (parts.size() > 0) 201 defaultFileName = parts[0]; 202 } 203 204 //we remove the extension 205 defaultFileName = QFileInfo(defaultFileName).baseName(); 206 207 if (!IsValidFileName(defaultFileName)) 208 { 209 ccLog::Warning("[I/O] First entity's name would make an invalid filename! Can't use it..."); 210 defaultFileName = "project"; 211 } 212 213 fullPathName += QString("/") + defaultFileName; 214 } 215 216 //ask the user for the output filename 217 QString selectedFilename = QFileDialog::getSaveFileName(this, 218 "Save file", 219 fullPathName, 220 fileFilters.join(s_fileFilterSeparator), 221 &selectedFilter); 222 223 if (selectedFilename.isEmpty()) 224 { 225 //process cancelled by the user 226 return; 227 } 228 229 //ignored items 230 if (hasOther) 231 { 232 ccConsole::Warning("[I/O] The following selected entites won't be saved:"); 233 for (unsigned i=0; i<other.getChildrenNumber(); ++i) 234 ccConsole::Warning(QString("\t- %1s").arg(other.getChild(i)->getName())); 235 } 236 237 CC_FILE_ERROR result = CC_FERR_NO_ERROR; 238 FileIOFilter::SaveParameters parameters; 239 { 240 parameters.alwaysDisplaySaveDialog = true; 241 parameters.parentWidget = this; 242 } 243 244 //specific case: BIN format 245 if (selectedFilter == BinFilter::GetFileFilter()) 246 { 247 if (selNum == 1) 248 { 249 result = FileIOFilter::SaveToFile(m_selectedEntities.front(),selectedFilename,parameters,selectedFilter); 250 } 251 else 252 { 253 //we'll regroup all selected entities in a temporary group 254 ccHObject tempContainer; 255 ConvertToGroup(m_selectedEntities,tempContainer,ccHObject::DP_NONE); 256 if (tempContainer.getChildrenNumber()) 257 { 258 result = FileIOFilter::SaveToFile(&tempContainer,selectedFilename,parameters,selectedFilter); 259 } 260 else 261 { 262 ccLog::Warning("[I/O] None of the selected entities can be saved this way..."); 263 result = CC_FERR_NO_SAVE; 264 } 265 } 266 } 267 else if (entitiesToSave.getChildrenNumber() != 0) 268 { 269 //ignored items 270 /*if (hasSerializable) 271 { 272 if (!hasOther) 273 ccConsole::Warning("[I/O] The following selected entites won't be saved:"); //display this warning only if not already done 274 for (unsigned i=0; i<otherSerializable.getChildrenNumber(); ++i) 275 ccConsole::Warning(QString("\t- %1").arg(otherSerializable.getChild(i)->getName())); 276 } 277 //*/ 278 279 result = FileIOFilter::SaveToFile( entitiesToSave.getChildrenNumber() > 1 ? &entitiesToSave : entitiesToSave.getChild(0), 280 selectedFilename, 281 parameters, 282 selectedFilter); 283 } 284 285 //update default filters 286 if (hasCloud) 287 settings.setValue(ccPS::SelectedOutputFilterCloud(),selectedFilter); 288 if (hasMesh) 289 settings.setValue(ccPS::SelectedOutputFilterMesh(), selectedFilter); 290 if (hasImages) 291 settings.setValue(ccPS::SelectedOutputFilterImage(),selectedFilter); 292 if (hasPolylines) 293 settings.setValue(ccPS::SelectedOutputFilterPoly(), selectedFilter); 294 295 //we update current file path 296 currentPath = QFileInfo(selectedFilename).absolutePath(); 297 settings.setValue(ccPS::CurrentPath(),currentPath); 298 settings.endGroup(); 299 }
再看看ccCommandLineParser的Parse方法:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 int ccCommandLineParser::Parse(int nargs, char** args) 2 { 3 if (!args || nargs < 2) 4 { 5 assert(false); 6 return EXIT_SUCCESS; 7 } 8 9 //reset default behavior(s) 10 s_loadParameters.autoComputeNormals = false; 11 s_MeshExportFormat = s_CloudExportFormat = BinFilter::GetFileFilter(); 12 s_MeshExportExt = s_CloudExportExt = BinFilter::GetDefaultExtension(); 13 s_precision = 12; 14 s_addTimestamp = true; 15 s_silentMode = false; 16 s_autoSaveMode = true; 17 18 //load arguments 19 QStringList arguments; 20 { 21 for (int i=1; i<nargs; ++i) //'i=1' because first argument is always program executable file! 22 arguments.push_back(QString(args[i])); 23 } 24 assert(!arguments.empty()); 25 26 //specific command: silent mode (will prevent the console dialog from appearing! 27 if (IsCommand(arguments.front(),COMMAND_SILENT_MODE)) 28 { 29 arguments.pop_front(); 30 s_silentMode = true; 31 } 32 33 QDialog consoleDlg; 34 if (!s_silentMode) 35 { 36 //show console 37 Ui_commandLineDlg commandLineDlg; 38 commandLineDlg.setupUi(&consoleDlg); 39 consoleDlg.show(); 40 ccConsole::Init(commandLineDlg.consoleWidget, &consoleDlg); 41 s_loadParameters.parentWidget = &consoleDlg; 42 } 43 44 //parse input 45 int result = ccCommandLineParser().parse(arguments,&consoleDlg); 46 47 if (!s_silentMode) 48 { 49 if (result == EXIT_SUCCESS) 50 QMessageBox::information(&consoleDlg,"Processed finished","Job done"); 51 else 52 QMessageBox::warning(&consoleDlg,"Processed finished","An error occurred! Check console"); 53 } 54 55 ccConsole::ReleaseInstance(); 56 57 return result; 58 }
该方法被main函数调用。
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 //主程序入口,十分重要 2 int main(int argc, char **argv) 3 { 4 //QT initialiation 5 qccApplication app(argc, argv); 6 7 //Force 'english' local so as to get a consistent behavior everywhere 8 QLocale::setDefault(QLocale::English); 9 10 #ifdef Q_OS_LINUX 11 // we reset the numeric locale. As suggested in documetation 12 // see http://qt-project.org/doc/qt-5/qcoreapplication.html#locale-settings 13 // Basically - from doc: - "On Unix/Linux Qt is configured to use the system locale settings by default. 14 // This can cause a conflict when using POSIX functions, for instance, 15 // when converting between data types such as floats and strings" 16 setlocale(LC_NUMERIC,"C"); 17 #endif 18 19 #ifdef USE_VLD 20 VLDEnable(); 21 #endif 22 23 //splash screen 24 QSplashScreen* splash = 0; 25 QTime splashStartTime; 26 27 //Command line mode? 28 bool commandLine = (argc > 1 && argv[1][0] == '-'); 29 30 //specific case: translation file selection 31 int lastArgumentIndex = 1; 32 QTranslator translator; 33 if (commandLine && QString(argv[1]).toUpper() == "-LANG") 34 { 35 QString langFilename = QString(argv[2]); 36 37 //Load translation file 38 if (translator.load(langFilename, QCoreApplication::applicationDirPath())) 39 { 40 qApp->installTranslator(&translator); 41 } 42 else 43 { 44 QMessageBox::warning(0, QObject::tr("Translation"), QObject::tr("Failed to load language file '%1'").arg(langFilename)); 45 } 46 commandLine = false; 47 lastArgumentIndex = 3; 48 } 49 50 //command line mode 51 if (!commandLine) 52 { 53 //OpenGL? 54 if (!QGLFormat::hasOpenGL()) 55 { 56 QMessageBox::critical(0, "Error", "This application needs OpenGL to run!"); 57 return EXIT_FAILURE; 58 } 59 60 //splash screen 61 splashStartTime.start(); 62 QPixmap pixmap(QString::fromUtf8(":/CC/images/imLogoV2Qt.png")); 63 splash = new QSplashScreen(pixmap,Qt::WindowStaysOnTopHint); 64 splash->show(); 65 QApplication::processEvents(); 66 } 67 68 //global structures initialization 69 ccTimer::Init(); 70 FileIOFilter::InitInternalFilters(); //load all known I/O filters (plugins will come later!) 71 ccNormalVectors::GetUniqueInstance(); //force pre-computed normals array initialization 72 ccColorScalesManager::GetUniqueInstance(); //force pre-computed color tables initialization 73 74 int result = 0; 75 76 if (commandLine) 77 { 78 //command line processing (no GUI) 79 result = ccCommandLineParser::Parse(argc,argv); 80 } 81 else 82 { 83 //main window init. 84 MainWindow* mainWindow = MainWindow::TheInstance(); 85 if (!mainWindow) 86 { 87 QMessageBox::critical(0, "Error", "Failed to initialize the main application window?!"); 88 return EXIT_FAILURE; 89 } 90 mainWindow->show(); 91 QApplication::processEvents(); 92 93 if (argc > lastArgumentIndex) 94 { 95 if (splash) 96 splash->close(); 97 98 //any additional argument is assumed to be a filename --> we try to load it/them 99 QStringList filenames; 100 for (int i = lastArgumentIndex; i<argc; ++i) 101 filenames << QString(argv[i]); 102 103 mainWindow->addToDB(filenames); 104 } 105 106 if (splash) 107 { 108 //we want the splash screen to be visible a minimum amount of time (1000 ms.) 109 while (splashStartTime.elapsed() < 1000) 110 { 111 splash->raise(); 112 QApplication::processEvents(); //to let the system breath! 113 } 114 115 splash->close(); 116 QApplication::processEvents(); 117 118 delete splash; 119 splash = 0; 120 } 121 122 //let's rock! 123 try 124 { 125 result = app.exec(); 126 } 127 catch(...) 128 { 129 QMessageBox::warning(0, "CC crashed!","Hum, it seems that CC has crashed... Sorry about that :)"); 130 } 131 } 132 133 //release global structures 134 MainWindow::DestroyInstance(); 135 FileIOFilter::UnregisterAll(); 136 137 #ifdef CC_TRACK_ALIVE_SHARED_OBJECTS 138 //for debug purposes 139 unsigned alive = CCShareable::GetAliveCount(); 140 if (alive > 1) 141 { 142 printf("Error: some shared objects (%u) have not been released on program end!",alive); 143 system("PAUSE"); 144 } 145 #endif 146 147 return result; 148 }
两处最终都落实到FileIOFilter::SaveToFile()方法的调用。
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 //保存文件 2 CC_FILE_ERROR FileIOFilter::SaveToFile( ccHObject* entities, 3 const QString& filename, 4 SaveParameters& parameters, 5 QString fileFilter) 6 { 7 if (fileFilter.isEmpty()) 8 return CC_FERR_BAD_ARGUMENT; 9 //获取对应的文件格式,存储器 10 Shared filter = GetFilter(fileFilter,false); 11 if (!filter) 12 { 13 ccLog::Error(QString("[Load] Internal error: no filter corresponds to filter '%1'").arg(fileFilter)); 14 return CC_FERR_UNKNOWN_FILE; 15 } 16 17 return SaveToFile(entities, filename, parameters, filter); 18 } 19 20 CC_FILE_ERROR FileIOFilter::SaveToFile( ccHObject* entities, 21 const QString& filename, 22 SaveParameters& parameters, 23 Shared filter) 24 { 25 if (!entities || filename.isEmpty() || !filter) 26 return CC_FERR_BAD_ARGUMENT; 27 28 //if the file name has no extension, we had a default one! 29 QString completeFileName(filename); 30 if (QFileInfo(filename).suffix().isEmpty()) 31 completeFileName += QString(".%1").arg(filter->getDefaultExtension()); 32 33 CC_FILE_ERROR result = CC_FERR_NO_ERROR; 34 try 35 { 36 result = filter->saveToFile(entities, completeFileName, parameters); 37 } 38 catch(...) 39 { 40 ccLog::Warning(QString("[I/O] CC has caught an unhandled exception while saving file '%1'").arg(filename)); 41 result = CC_FERR_CONSOLE_ERROR; 42 } 43 44 if (result == CC_FERR_NO_ERROR) 45 { 46 ccLog::Print(QString("[I/O] File '%1' saved successfully").arg(filename)); 47 } 48 else 49 { 50 DisplayErrorMessage(result,"saving",filename); 51 } 52 53 return result; 54 }
获取需要的文件存储器。
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 //获取存取器 2 FileIOFilter::Shared FileIOFilter::GetFilter(QString fileFilter, bool onImport) 3 { 4 if (!fileFilter.isEmpty()) 5 { 6 for (FilterContainer::const_iterator it=s_ioFilters.begin(); it!=s_ioFilters.end(); ++it) 7 { 8 QStringList otherFilters = (*it)->getFileFilters(onImport); 9 if (otherFilters.contains(fileFilter)) 10 return *it; 11 } 12 } 13 14 return Shared(0); 15 }
这里重点研究一下obj文件的保存。