FreeCAD源码分析之文件打开

2022年3月16日 5087点热度 2人点赞 0条评论

FreeCAD文件框架有两个程序入口,一个是通过命令行的方式打开时使用,一个在程序正常运行时使用。

命令行方式

这个流程图列举了三种以命令参数的形式打开文档的方式:

  1. 以控制台模式启动
    这个模式直接调用App::Application::runApplication(),在这个函数中获取命令行中的参数,通过参数提供的路径打开文档。
  2. 以GUI方式启动,单实例模式
    这个模式会调用Gui::Application::runApplication(),在这个函数中判断是否是单实例模式。在这个模式里如果程序已经被启动了一次,那么会通过localSocket将命令行中的参数发送给已经运行的程序,已经运行的程序收到消息后,会处理消息中的路径。反之则与多实例模式一样。
  3. 以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++中去处理文件格式,那么谨记需要修改两处的代码,才能够支持两种打开方式。

 

补充

关于为什么需要支持命令行参数打开文件的方式:

  1. FreeCAD支持紧使用命令方式打开,无GUI模式
  2. 在windows系统上,如果将文件打开方式默认设置为某个软件,然后双击这个文件,会直接用关联软件打开这个文件,这个时候其实是用命令行方式打开的文件。

大脸猫

这个人虽然很勤快,但什么也没有留下!

文章评论