FreeCAD文件框架有两个程序入口,一个是通过命令行的方式打开时使用,一个在程序正常运行时使用。
命令行方式
这个流程图列举了三种以命令参数的形式打开文档的方式:
- 以控制台模式启动
这个模式直接调用App::Application::runApplication(),在这个函数中获取命令行中的参数,通过参数提供的路径打开文档。 - 以GUI方式启动,单实例模式
这个模式会调用Gui::Application::runApplication(),在这个函数中判断是否是单实例模式。在这个模式里如果程序已经被启动了一次,那么会通过localSocket将命令行中的参数发送给已经运行的程序,已经运行的程序收到消息后,会处理消息中的路径。反之则与多实例模式一样。 - 以GUi方式启动,多实例模式
略。。
单实例模式指,保证程序只被运行一次,多次从外部命令打开文档时,会将文档路径发送到已与运行的程序,由已运行的程序来负责打开文档。这个做法十分有趣,有空会深入了解一下。
2022.3.26 更新:通过进程通信的方式实现程序单例
程序运行时文件打开
FreeCAD有一个Command框架,详情可参考这篇博文:http://cppdebug.com/archives/110,它可以做到将一些常用的功能,封装到一个个命令中,并且还有一个全局可获取的命令管理器,所以在程序的可以在任何地方调用这些命令,十分方便。
在程序中点击打开按钮时,实际上是触发了绑定到按钮上的StdCmdOpen命令,这个命令中会弹出文件选择Window,再获取文件路径之后调用open()。
如何修改文件格式的打开方式
有两种方法,一种是直接再C++中拦截,然后增加过滤的代码。另一种是直接修改Python代码中的打开方式。
Gui::Application::open
void Application::open(const char* FileName, const char* Module) { WaitCursor wc; wc.setIgnoreEvents(WaitCursor::NoEvents); Base::FileInfo File(FileName); string filepath = File.filePath().c_str(); string te = File.extension(); string unicodepath = Base::Tools::escapedUnicodeFromUtf8(File.filePath().c_str()); string filename = filepath.substr(0, filepath.find_last_of("/\\")); // if the active document is empty and not modified, close it // in case of an automatically created empty document at startup App::Document* act = App::GetApplication().getActiveDocument(); Gui::Document* gui = this->getDocument(act); if (act && act->countObjects() == 0 && gui && gui->isModified() == false){ Command::doCommand(Command::App, "App.closeDocument('%s')", act->getName()); qApp->processEvents(); // an update is needed otherwise the new view isn't shown } if (Module != 0) { // issue module loading Command::doCommand(Command::App, "import %s", Module); try { // load the file with the module Command::doCommand(Command::App, "%s.open(u\"%s\")", Module, unicodepath.c_str()); if (File.hasExtension("FCStd")) { Command::doCommand(Command::App, "import Modeling\nModeling.Common.Tools.DocumentTools.initWhenOpenFCStdFile()\n"); } // ViewFit if (!File.hasExtension("FCStd") && sendHasMsgToActiveView("ViewFit")) { ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath ("User parameter:BaseApp/Preferences/View"); if (hGrp->GetBool("AutoFitToView", true)) Command::doCommand(Command::Gui, "Gui.SendMsgToActiveView(\"ViewFit\")"); } // the original file name is required QString filename = QString::fromUtf8(File.filePath().c_str()); getMainWindow()->appendRecentFile(filename); FileDialog::setWorkingDirectory(filename); } catch (const Base::PyException& e){ // Usually thrown if the file is invalid somehow e.ReportException(); } } else { wc.restoreCursor(); QMessageBox::warning(getMainWindow(), QObject::tr("Unknown filetype"), QObject::tr("Cannot open unknown filetype: %1").arg(QLatin1String(te.c_str()))); wc.setWaitCursor(); return; } if (File.hasExtension("FCStd")){ Base::Interpreter().runString("FreeCADGui.runCommand('Customize_Open')"); } }
App::Application::processFiles()
std::list<std::string> Application::processFiles(const std::list<std::string>& files) { std::list<std::string> processed; Base::Console().Log("Init: Processing command line files\n"); for (std::list<std::string>::const_iterator it = files.begin(); it != files.end(); ++it) { Base::FileInfo file(*it); Base::Console().Log("Init: Processing file: %s\n",file.filePath().c_str()); try { if (file.hasExtension("fcstd") || file.hasExtension("std")) { // try to open Application::_pcSingleton->openDocument(file.filePath().c_str()); processed.push_back(*it); } else if (file.hasExtension("fcscript") || file.hasExtension("fcmacro")) { Base::Interpreter().runFile(file.filePath().c_str(), true); processed.push_back(*it); } else if (file.hasExtension("py")) { try{ Base::Interpreter().loadModule(file.fileNamePure().c_str()); processed.push_back(*it); } catch(const PyException&) { // if loading the module does not work, try just running the script (run in __main__) Base::Interpreter().runFile(file.filePath().c_str(),true); processed.push_back(*it); } } else { std::string ext = file.extension(); std::vector<std::string> mods = App::GetApplication().getImportModules(ext.c_str()); if (!mods.empty()) { std::string escapedstr = Base::Tools::escapedUnicodeFromUtf8(file.filePath().c_str()); Base::Interpreter().loadModule(mods.front().c_str()); Base::Interpreter().runStringArg("import %s",mods.front().c_str()); Base::Interpreter().runStringArg("%s.open(u\"%s\")",mods.front().c_str(), escapedstr.c_str()); processed.push_back(*it); Base::Console().Log("Command line open: %s.open(u\"%s\")\n",mods.front().c_str(),escapedstr.c_str()); } else { Console().Warning("File format not supported: %s \n", file.filePath().c_str()); } } } catch (const Base::SystemExitException&) { throw; // re-throw to main() function } catch (const Base::Exception& e) { Console().Error("Exception while processing file: %s [%s]\n", file.filePath().c_str(), e.what()); } catch (...) { Console().Error("Unknown exception while processing file: %s \n", file.filePath().c_str()); } } return processed; // successfully processed files }
这分别是之前描述的两种文件打开方式的处理函数,从内容中看处理方式是大同小异的。
标准的Fcstd格式的文件,会直接调用OpenDocument()处理,其他的则调用对应的python代码处理,所以增加或者修改非Fcstd格式,可按照FreeCAD的框架,添加新的python代码进行文件处理。
反之如果需要在C++中去处理文件格式,那么谨记需要修改两处的代码,才能够支持两种打开方式。
关于为什么需要支持命令行参数打开文件的方式:
- FreeCAD支持紧使用命令方式打开,无GUI模式
- 在windows系统上,如果将文件打开方式默认设置为某个软件,然后双击这个文件,会直接用关联软件打开这个文件,这个时候其实是用命令行方式打开的文件。
文章评论