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系统上,如果将文件打开方式默认设置为某个软件,然后双击这个文件,会直接用关联软件打开这个文件,这个时候其实是用命令行方式打开的文件。
文章评论