Files
create/src/App/Application.cpp
Ladislav Michl e7cd72b2e0 Base: refactor unit formatting defaults
Defaults for both precision (number of digits after decimal point)
and denominator (number of fractions) are defined on various places
making difficult to find which default is used for various tasks.
Store these values at one central place: UnitsApi. Unless overriden
by user, default values are defined by unitSchemasDataPack.
2025-09-05 17:31:54 +02:00

3799 lines
139 KiB
C++

/***************************************************************************
* Copyright (c) 2002 Jürgen Riegel <juergen.riegel@web.de> *
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Library General Public License (LGPL) *
* as published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* for detail see the LICENCE text file. *
* *
* FreeCAD is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with FreeCAD; if not, write to the Free Software *
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
* USA *
* *
***************************************************************************/
#include "PreCompiled.h"
#ifndef _PreComp_
# if defined(FC_OS_LINUX) || defined(FC_OS_MACOSX) || defined(FC_OS_BSD)
# include <unistd.h>
# include <pwd.h>
# include <sys/types.h>
# elif defined(__MINGW32__)
# undef WINVER
# define WINVER 0x502 // needed for SetDllDirectory
# include <Windows.h>
# endif
# include <boost/program_options.hpp>
# include <boost/date_time/posix_time/posix_time.hpp>
# include <boost/scope_exit.hpp>
# include <chrono>
# include <random>
# include <memory>
# include <utility>
# include <set>
# include <list>
# include <algorithm>
# include <iostream>
# include <map>
# include <tuple>
# include <vector>
# include <fmt/format.h>
#endif
#ifdef FC_OS_WIN32
# include <Shlobj.h>
# include <codecvt>
#endif
#if defined(FC_OS_BSD)
#include <sys/param.h>
#include <sys/sysctl.h>
#endif
#include <QCoreApplication>
#include <QDir>
#include <QFileInfo>
#include <QProcessEnvironment>
#include <QRegularExpression>
#include <QSettings>
#include <QStandardPaths>
#include <LibraryVersions.h>
#include <App/MaterialPy.h>
#include <App/MetadataPy.h>
// FreeCAD Base header
#include <Base/AxisPy.h>
#include <Base/BaseClass.h>
#include <Base/BoundBoxPy.h>
#include <Base/ConsoleObserver.h>
#include <Base/ServiceProvider.h>
#include <Base/CoordinateSystemPy.h>
#include <Base/Exception.h>
#include <Base/ExceptionFactory.h>
#include <Base/FileInfo.h>
#include <Base/GeometryPyCXX.h>
#include <Base/Interpreter.h>
#include <Base/MatrixPy.h>
#include <Base/QuantityPy.h>
#include <Base/Parameter.h>
#include <Base/Persistence.h>
#include <Base/PlacementPy.h>
#include <Base/PrecisionPy.h>
#include <Base/ProgressIndicatorPy.h>
#include <Base/RotationPy.h>
#include <Base/UniqueNameManager.h>
#include <Base/Tools.h>
#include <Base/Translate.h>
#include <Base/Type.h>
#include <Base/TypePy.h>
#include <Base/UnitPy.h>
#include <Base/UnitsApi.h>
#include <Base/VectorPy.h>
#include "Annotation.h"
#include "Application.h"
#include "ApplicationDirectories.h"
#include "CleanupProcess.h"
#include "ComplexGeoData.h"
#include "Services.h"
#include "DocumentObjectFileIncluded.h"
#include "DocumentObjectGroup.h"
#include "DocumentObjectGroupPy.h"
#include "DocumentObserver.h"
#include "DocumentPy.h"
#include "ExpressionParser.h"
#include "FeatureTest.h"
#include "FeaturePython.h"
#include "GeoFeature.h"
#include "GeoFeatureGroupExtension.h"
#include "ImagePlane.h"
#include "InventorObject.h"
#include "Link.h"
#include "LinkBaseExtensionPy.h"
#include "VarSet.h"
#include "MaterialObject.h"
#include "MeasureManagerPy.h"
#include "Origin.h"
#include "Datums.h"
#include "OriginGroupExtension.h"
#include "OriginGroupExtensionPy.h"
#include "SuppressibleExtension.h"
#include "Part.h"
#include "GeoFeaturePy.h"
#include "Placement.h"
#include "ProgramOptionsUtilities.h"
#include "Property.h"
#include "PropertyContainer.h"
#include "PropertyExpressionEngine.h"
#include "PropertyFile.h"
#include "PropertyLinks.h"
#include "PropertyPythonObject.h"
#include "StringHasherPy.h"
#include "StringIDPy.h"
#include "TextDocument.h"
#include "Transactions.h"
#include "VRMLObject.h"
// If you stumble here, run the target "BuildExtractRevision" on Windows systems
// or the Python script "SubWCRev.py" on Linux based systems which builds
// src/Build/Version.h. Or create your own from src/Build/Version.h.in!
#include <Build/Version.h>
#include "Branding.h"
// scriptings (scripts are built-in but can be overridden by command line option)
#include <App/InitScript.h>
#include <App/TestScript.h>
#include <App/CMakeScript.h>
#include "SafeMode.h"
#ifdef _MSC_VER // New handler for Microsoft Visual C++ compiler
# pragma warning( disable : 4535 )
# if !defined(_DEBUG) && defined(HAVE_SEH)
# define FC_SE_TRANSLATOR
# endif
# include <new.h>
# include <eh.h> // VC exception handling
#else // Ansi C/C++ new handler
# include <new>
#endif
FC_LOG_LEVEL_INIT("App", true, true)
using namespace App;
namespace sp = std::placeholders;
namespace fs = std::filesystem;
//==========================================================================
// Application
//==========================================================================
Base::Reference<ParameterManager> Application::_pcSysParamMngr;
Base::Reference<ParameterManager> Application::_pcUserParamMngr;
Base::ConsoleObserverStd *Application::_pConsoleObserverStd = nullptr;
Base::ConsoleObserverFile *Application::_pConsoleObserverFile = nullptr;
AppExport std::map<std::string, std::string> Application::mConfig;
std::unique_ptr<ApplicationDirectories> Application::_appDirs;
//**************************************************************************
// Construction and destruction
// clang-format off
PyDoc_STRVAR(FreeCAD_doc,
"The functions in the FreeCAD module allow working with documents.\n"
"The FreeCAD instance provides a list of references of documents which\n"
"can be addressed by a string. Hence the document name must be unique.\n"
"\n"
"The document has the read-only attribute FileName which points to the\n"
"file the document should be stored to.\n"
);
PyDoc_STRVAR(Console_doc,
"FreeCAD Console module.\n\n"
"The Console module contains functions to manage log entries, messages,\n"
"warnings and errors.\n"
"There are also functions to get/set the status of the observers used as\n"
"logging interfaces."
);
PyDoc_STRVAR(Base_doc,
"The Base module contains the classes for the geometric basics\n"
"like vector, matrix, bounding box, placement, rotation, axis, ...\n"
);
// This is called via the PyImport_AppendInittab mechanism called
// during initialization, to make the built-in __FreeCADBase__
// module known to Python.
PyMODINIT_FUNC
init_freecad_base_module(void)
{
static struct PyModuleDef BaseModuleDef = {
PyModuleDef_HEAD_INIT,
"__FreeCADBase__", Base_doc, -1,
nullptr, nullptr, nullptr, nullptr, nullptr
};
return PyModule_Create(&BaseModuleDef);
}
// Set in inside Application
static PyMethodDef* ApplicationMethods = nullptr;
PyMODINIT_FUNC
init_freecad_module(void)
{
static struct PyModuleDef FreeCADModuleDef = {
PyModuleDef_HEAD_INIT,
"FreeCAD", FreeCAD_doc, -1,
ApplicationMethods,
nullptr, nullptr, nullptr, nullptr
};
return PyModule_Create(&FreeCADModuleDef);
}
PyMODINIT_FUNC
init_image_module()
{
static struct PyModuleDef ImageModuleDef = {
PyModuleDef_HEAD_INIT,
"Image", "", -1,
nullptr,
nullptr, nullptr, nullptr, nullptr
};
return PyModule_Create(&ImageModuleDef);
}
// clang-format on
Application::Application(std::map<std::string,std::string> &mConfig)
: _mConfig(mConfig)
{
mpcPramManager["System parameter"] = _pcSysParamMngr;
mpcPramManager["User parameter"] = _pcUserParamMngr;
setupPythonTypes();
}
Application::~Application() = default;
void Application::setupPythonTypes()
{
// setting up Python binding
Base::PyGILStateLocker lock;
PyObject* modules = PyImport_GetModuleDict();
ApplicationMethods = Application::Methods;
PyObject* pAppModule = PyImport_ImportModule ("FreeCAD");
if (!pAppModule) {
PyErr_Clear();
pAppModule = init_freecad_module();
PyDict_SetItemString(modules, "FreeCAD", pAppModule);
}
Py::Module(pAppModule).setAttr(std::string("ActiveDocument"),Py::None());
// clang-format off
static struct PyModuleDef ConsoleModuleDef = {
PyModuleDef_HEAD_INIT,
"__FreeCADConsole__", Console_doc, -1,
Base::ConsoleSingleton::Methods,
nullptr, nullptr, nullptr, nullptr
};
PyObject* pConsoleModule = PyModule_Create(&ConsoleModuleDef);
// fake Image module
PyObject* imageModule = init_image_module();
PyDict_SetItemString(modules, "Image", imageModule);
// introducing additional classes
// NOTE: To finish the initialization of our own type objects we must
// call PyType_Ready, otherwise we run into a segmentation fault, later on.
// This function is responsible for adding inherited slots from a type's base class.
Base::InterpreterSingleton::addType(&Base::VectorPy::Type, pAppModule, "Vector");
Base::InterpreterSingleton::addType(&Base::MatrixPy::Type, pAppModule, "Matrix");
Base::InterpreterSingleton::addType(&Base::BoundBoxPy::Type, pAppModule, "BoundBox");
Base::InterpreterSingleton::addType(&Base::PlacementPy::Type, pAppModule, "Placement");
Base::InterpreterSingleton::addType(&Base::RotationPy::Type, pAppModule, "Rotation");
Base::InterpreterSingleton::addType(&Base::AxisPy::Type, pAppModule, "Axis");
// Note: Create an own module 'Base' which should provide the python
// binding classes from the base module. At a later stage we should
// remove these types from the FreeCAD module.
PyObject* pBaseModule = PyImport_ImportModule ("__FreeCADBase__");
if (!pBaseModule) {
PyErr_Clear();
pBaseModule = init_freecad_base_module();
PyDict_SetItemString(modules, "__FreeCADBase__", pBaseModule);
}
setupPythonException(pBaseModule);
// Python types
Base::InterpreterSingleton::addType(&Base::VectorPy ::Type,pBaseModule,"Vector");
Base::InterpreterSingleton::addType(&Base::MatrixPy ::Type,pBaseModule,"Matrix");
Base::InterpreterSingleton::addType(&Base::BoundBoxPy ::Type,pBaseModule,"BoundBox");
Base::InterpreterSingleton::addType(&Base::PlacementPy ::Type,pBaseModule,"Placement");
Base::InterpreterSingleton::addType(&Base::RotationPy ::Type,pBaseModule,"Rotation");
Base::InterpreterSingleton::addType(&Base::AxisPy ::Type,pBaseModule,"Axis");
Base::InterpreterSingleton::addType(&Base::CoordinateSystemPy::Type,pBaseModule,"CoordinateSystem");
Base::InterpreterSingleton::addType(&Base::TypePy ::Type,pBaseModule,"TypeId");
Base::InterpreterSingleton::addType(&Base::PrecisionPy ::Type,pBaseModule,"Precision");
Base::InterpreterSingleton::addType(&MaterialPy::Type, pAppModule, "Material");
Base::InterpreterSingleton::addType(&MetadataPy::Type, pAppModule, "Metadata");
Base::InterpreterSingleton::addType(&MeasureManagerPy::Type, pAppModule, "MeasureManager");
Base::InterpreterSingleton::addType(&StringHasherPy::Type, pAppModule, "StringHasher");
Base::InterpreterSingleton::addType(&StringIDPy::Type, pAppModule, "StringID");
// Add document types
Base::InterpreterSingleton::addType(&PropertyContainerPy::Type, pAppModule, "PropertyContainer");
Base::InterpreterSingleton::addType(&ExtensionContainerPy::Type, pAppModule, "ExtensionContainer");
Base::InterpreterSingleton::addType(&DocumentPy::Type, pAppModule, "Document");
Base::InterpreterSingleton::addType(&DocumentObjectPy::Type, pAppModule, "DocumentObject");
Base::InterpreterSingleton::addType(&DocumentObjectGroupPy::Type, pAppModule, "DocumentObjectGroup");
Base::InterpreterSingleton::addType(&GeoFeaturePy::Type, pAppModule, "GeoFeature");
// Add extension types
Base::InterpreterSingleton::addType(&ExtensionPy::Type, pAppModule, "Extension");
Base::InterpreterSingleton::addType(&DocumentObjectExtensionPy::Type, pAppModule, "DocumentObjectExtension");
Base::InterpreterSingleton::addType(&GroupExtensionPy::Type, pAppModule, "GroupExtension");
Base::InterpreterSingleton::addType(&GeoFeatureGroupExtensionPy::Type, pAppModule, "GeoFeatureGroupExtension");
Base::InterpreterSingleton::addType(&OriginGroupExtensionPy::Type, pAppModule, "OriginGroupExtension");
Base::InterpreterSingleton::addType(&LinkBaseExtensionPy::Type, pAppModule, "LinkBaseExtension");
//insert Base and Console
Py_INCREF(pBaseModule);
PyModule_AddObject(pAppModule, "Base", pBaseModule);
Py_INCREF(pConsoleModule);
PyModule_AddObject(pAppModule, "Console", pConsoleModule);
// Translate module
PyObject* pTranslateModule = Base::Interpreter().addModule(new Base::Translate);
Py_INCREF(pTranslateModule);
PyModule_AddObject(pAppModule, "Qt", pTranslateModule);
//insert Units module
static struct PyModuleDef UnitsModuleDef = {
PyModuleDef_HEAD_INIT,
"Units", "The Unit API", -1,
Base::UnitsApi::Methods,
nullptr, nullptr, nullptr, nullptr
};
PyObject* pUnitsModule = PyModule_Create(&UnitsModuleDef);
Base::InterpreterSingleton::addType(&Base::QuantityPy ::Type,pUnitsModule,"Quantity");
// make sure to set the 'nb_true_divide' slot
Base::InterpreterSingleton::addType(&Base::UnitPy ::Type,pUnitsModule,"Unit");
Py_INCREF(pUnitsModule);
PyModule_AddObject(pAppModule, "Units", pUnitsModule);
Base::ProgressIndicatorPy::init_type();
Base::InterpreterSingleton::addType(Base::ProgressIndicatorPy::type_object(),
pBaseModule,"ProgressIndicator");
Base::Vector2dPy::init_type();
Base::InterpreterSingleton::addType(Base::Vector2dPy::type_object(),
pBaseModule,"Vector2d");
// clang-format on
}
/*
* Define custom Python exception types
*/
void Application::setupPythonException(PyObject* module)
{
auto setup = [&module, str {"Base."}](const std::string& ename, auto pyExcType) {
auto exception = PyErr_NewException((str + ename).c_str(), pyExcType, nullptr);
Py_INCREF(exception);
PyModule_AddObject(module, ename.c_str(), exception);
return exception;
};
Base::PyExc_FC_GeneralError = setup("FreeCADError", PyExc_RuntimeError);
Base::PyExc_FC_FreeCADAbort = setup("FreeCADAbort", PyExc_BaseException);
Base::PyExc_FC_XMLBaseException = setup("XMLBaseException", PyExc_Exception);
Base::PyExc_FC_XMLParseException = setup("XMLParseException", Base::PyExc_FC_XMLBaseException);
Base::PyExc_FC_XMLAttributeError = setup("XMLAttributeError", Base::PyExc_FC_XMLBaseException);
Base::PyExc_FC_UnknownProgramOption = setup("UnknownProgramOption", PyExc_BaseException);
Base::PyExc_FC_BadFormatError = setup("BadFormatError", Base::PyExc_FC_GeneralError);
Base::PyExc_FC_BadGraphError = setup("BadGraphError", Base::PyExc_FC_GeneralError);
Base::PyExc_FC_ExpressionError = setup("ExpressionError", Base::PyExc_FC_GeneralError);
Base::PyExc_FC_ParserError = setup("ParserError", Base::PyExc_FC_GeneralError);
Base::PyExc_FC_CADKernelError = setup("CADKernelError", Base::PyExc_FC_GeneralError);
Base::PyExc_FC_PropertyError = setup("PropertyError", PyExc_AttributeError);
Base::PyExc_FC_AbortIOException = setup("AbortIOException", PyExc_BaseException);
}
//**************************************************************************
// Interface
/// get called by the document when the name is changing
void Application::renameDocument(const char *OldName, const char *NewName)
{
(void)OldName;
(void)NewName;
throw Base::RuntimeError("Renaming document internal name is no longer allowed!");
}
Document* Application::newDocument(const char * proposedName, const char * proposedLabel, DocumentInitFlags CreateFlags)
{
bool isUsingDefaultName = Base::Tools::isNullOrEmpty(proposedName);
// get a valid name anyway!
if (isUsingDefaultName) {
proposedName = "Unnamed";
}
std::string name(getUniqueDocumentName(proposedName, CreateFlags.temporary));
// return the temporary document if it exists
if (CreateFlags.temporary) {
auto it = DocMap.find(name);
if (it != DocMap.end() && it->second->testStatus(Document::TempDoc)) {
return it->second;
}
}
// Determine the document's Label
std::string label;
if (!Base::Tools::isNullOrEmpty(proposedLabel)) {
// If a label is supplied it is used even if not unique
label = proposedLabel;
}
else {
label = isUsingDefaultName ? QObject::tr("Unnamed").toStdString() : proposedName;
if (!DocMap.empty()) {
// The assumption here is that there are not many documents and
// documents are rarely created so the cost
// of building this manager each time is inconsequential
Base::UniqueNameManager names;
for (const auto& pos : DocMap) {
names.addExactName(pos.second->Label.getValue());
}
label = names.makeUniqueName(label);
}
}
// create the FreeCAD document
auto doc = new Document(name.c_str());
doc->setStatus(Document::TempDoc, CreateFlags.temporary);
// add the document to the internal list
DocMap[name] = doc;
//NOLINTBEGIN
// clang-format off
// connect the signals to the application for the new document
doc->signalBeforeChange.connect(std::bind(&Application::slotBeforeChangeDocument, this, sp::_1, sp::_2));
doc->signalChanged.connect(std::bind(&Application::slotChangedDocument, this, sp::_1, sp::_2));
doc->signalNewObject.connect(std::bind(&Application::slotNewObject, this, sp::_1));
doc->signalDeletedObject.connect(std::bind(&Application::slotDeletedObject, this, sp::_1));
doc->signalBeforeChangeObject.connect(std::bind(&Application::slotBeforeChangeObject, this, sp::_1, sp::_2));
doc->signalChangedObject.connect(std::bind(&Application::slotChangedObject, this, sp::_1, sp::_2));
doc->signalRelabelObject.connect(std::bind(&Application::slotRelabelObject, this, sp::_1));
doc->signalActivatedObject.connect(std::bind(&Application::slotActivatedObject, this, sp::_1));
doc->signalUndo.connect(std::bind(&Application::slotUndoDocument, this, sp::_1));
doc->signalRedo.connect(std::bind(&Application::slotRedoDocument, this, sp::_1));
doc->signalRecomputedObject.connect(std::bind(&Application::slotRecomputedObject, this, sp::_1));
doc->signalRecomputed.connect(std::bind(&Application::slotRecomputed, this, sp::_1));
doc->signalBeforeRecompute.connect(std::bind(&Application::slotBeforeRecompute, this, sp::_1));
doc->signalOpenTransaction.connect(std::bind(&Application::slotOpenTransaction, this, sp::_1, sp::_2));
doc->signalCommitTransaction.connect(std::bind(&Application::slotCommitTransaction, this, sp::_1));
doc->signalAbortTransaction.connect(std::bind(&Application::slotAbortTransaction, this, sp::_1));
doc->signalStartSave.connect(std::bind(&Application::slotStartSaveDocument, this, sp::_1, sp::_2));
doc->signalFinishSave.connect(std::bind(&Application::slotFinishSaveDocument, this, sp::_1, sp::_2));
doc->signalChangePropertyEditor.connect(std::bind(&Application::slotChangePropertyEditor, this, sp::_1, sp::_2));
// clang-format on
//NOLINTEND
// (temporarily) make this the active document for the upcoming notifications.
// Signal NewDocument rather than ActiveDocument (which is what setActiveDocument would do)
auto oldActiveDoc = _pActiveDoc;
setActiveDocumentNoSignal(doc);
signalNewDocument(*doc, CreateFlags.createView);
doc->Label.setValue(label);
// set the old document active again if the new is temporary
if (CreateFlags.temporary && oldActiveDoc) {
setActiveDocument(oldActiveDoc);
}
return doc;
}
bool Application::closeDocument(const char* name)
{
const auto pos = DocMap.find( name );
if (pos == DocMap.end()) // no such document
return false;
Base::ConsoleRefreshDisabler disabler;
// Trigger observers before removing the document from the internal map.
// Some observers might rely on this document still being there.
signalDeleteDocument(*pos->second);
// For exception-safety use a smart pointer
if (_pActiveDoc == pos->second) {
setActiveDocument(static_cast<Document*>(nullptr));
}
const std::unique_ptr<Document> delDoc (pos->second);
DocMap.erase( pos );
DocFileMap.erase(Base::FileInfo(delDoc->FileName.getValue()).filePath());
_objCount = -1;
// Trigger observers after removing the document from the internal map.
signalDeletedDocument();
return true;
}
void Application::closeAllDocuments()
{
Base::FlagToggler<bool> flag(_isClosingAll);
std::map<std::string,Document*>::iterator pos;
while((pos = DocMap.begin()) != DocMap.end())
closeDocument(pos->first.c_str());
}
Document* Application::getDocument(const char *Name) const
{
const auto pos = DocMap.find(Name);
if (pos == DocMap.end())
return nullptr;
return pos->second;
}
const char * Application::getDocumentName(const Document* doc) const
{
for (const auto & it : DocMap) {
if (it.second == doc) {
return it.first.c_str();
}
}
return nullptr;
}
std::vector<Document*> Application::getDocuments() const
{
std::vector<Document*> docs;
docs.reserve(DocMap.size());
for (const auto & it : DocMap)
docs.push_back(it.second);
return docs;
}
std::string Application::getUniqueDocumentName(const char* Name, bool tempDoc) const
{
if (!Name || *Name == '\0') {
return {};
}
std::string CleanName = Base::Tools::getIdentifier(Name);
// name in use?
auto pos = DocMap.find(CleanName);
if (pos == DocMap.end() || (tempDoc && pos->second->testStatus(Document::TempDoc))) {
// if not, name is OK
return CleanName;
}
// The assumption here is that there are not many documents and
// documents are rarely created so the cost
// of building this manager each time is inconsequential
Base::UniqueNameManager names;
for (const auto& pos : DocMap) {
if (!tempDoc || !pos.second->testStatus(Document::TempDoc)) {
names.addExactName(pos.first);
}
}
return names.makeUniqueName(CleanName);
}
int Application::addPendingDocument(const char *FileName, const char *objName, bool allowPartial)
{
if(!_isRestoring)
return 0;
if(allowPartial && _allowPartial)
return -1;
assert(!Base::Tools::isNullOrEmpty(FileName));
assert(!Base::Tools::isNullOrEmpty(objName));
if(!_docReloadAttempts[FileName].emplace(objName).second)
return -1;
const auto ret = _pendingDocMap.emplace(FileName,std::vector<std::string>());
ret.first->second.emplace_back(objName);
if(ret.second) {
_pendingDocs.emplace_back(ret.first->first.c_str());
return 1;
}
return -1;
}
bool Application::isRestoring() const {
return _isRestoring || Document::isAnyRestoring();
}
bool Application::isClosingAll() const {
return _isClosingAll;
}
struct DocTiming {
FC_DURATION_DECLARE(d1);
FC_DURATION_DECLARE(d2);
DocTiming() {
FC_DURATION_INIT(d1);
FC_DURATION_INIT(d2);
}
};
class DocOpenGuard {
public:
bool &flag;
boost::signals2::signal<void ()> &signal;
DocOpenGuard(bool &f, boost::signals2::signal<void ()> &s)
:flag(f),signal(s)
{
flag = true;
}
~DocOpenGuard() {
if(flag) {
flag = false;
try {
signal();
}
catch (const boost::exception&) {
// reported by code analyzers
Base::Console().warning("~DocOpenGuard: Unexpected boost exception\n");
}
}
}
};
Document* Application::openDocument(const char * FileName, DocumentInitFlags initFlags) {
std::vector<std::string> filenames(1,FileName);
auto docs = openDocuments(filenames, nullptr, nullptr, nullptr, initFlags);
if(!docs.empty())
return docs.front();
return nullptr;
}
Document *Application::getDocumentByPath(const char *path, PathMatchMode checkCanonical) const {
if(Base::Tools::isNullOrEmpty(path))
return nullptr;
if (DocFileMap.empty()) {
for(const auto &v : DocMap) {
const auto &file = v.second->FileName.getStrValue();
if(!file.empty())
DocFileMap[Base::FileInfo(file.c_str()).filePath()] = v.second;
}
}
const auto it = DocFileMap.find(Base::FileInfo(path).filePath());
if(it != DocFileMap.end())
return it->second;
if (checkCanonical == PathMatchMode::MatchAbsolute) {
return nullptr;
}
const std::string filepath = Base::FileInfo(path).filePath();
const QString canonicalPath = QFileInfo(QString::fromUtf8(path)).canonicalFilePath();
for (const auto &v : DocMap) {
QFileInfo fi(QString::fromUtf8(v.second->FileName.getValue()));
if (canonicalPath == fi.canonicalFilePath()) {
if (checkCanonical == PathMatchMode::MatchCanonical) {
return v.second;
}
const bool samePath = (canonicalPath == QString::fromUtf8(filepath.c_str()));
FC_WARN("Identical physical path '" << canonicalPath.toUtf8().constData() << "'\n"
<< (samePath?"":" for file '") << (samePath?"":filepath.c_str()) << (samePath?"":"'\n")
<< " with existing document '" << v.second->Label.getValue()
<< "' in path: '" << v.second->FileName.getValue() << "'");
break;
}
}
return nullptr;
}
std::vector<Document*> Application::openDocuments(const std::vector<std::string> &filenames,
const std::vector<std::string> *paths,
const std::vector<std::string> *labels,
std::vector<std::string> *errs,
DocumentInitFlags initFlags)
{
std::vector<Document*> res(filenames.size(), nullptr);
if (filenames.empty())
return res;
if (errs)
errs->resize(filenames.size());
DocOpenGuard guard(_isRestoring, signalFinishOpenDocument);
_pendingDocs.clear();
_pendingDocsReopen.clear();
_pendingDocMap.clear();
_docReloadAttempts.clear();
signalStartOpenDocument();
ParameterGrp::handle hGrp = GetParameterGroupByPath("User parameter:BaseApp/Preferences/Document");
_allowPartial = !hGrp->GetBool("NoPartialLoading",false);
for (auto &name : filenames)
_pendingDocs.emplace_back(name.c_str());
std::map<DocumentT, DocTiming> timings;
FC_TIME_INIT(t);
std::vector<DocumentT> openedDocs;
int pass = 0;
do {
std::set<DocumentT> newDocs;
for (std::size_t count=0;; ++count) {
std::string name = std::move(_pendingDocs.front());
_pendingDocs.pop_front();
bool isMainDoc = (pass == 0 && count < filenames.size());
try {
_objCount = -1;
std::vector<std::string> objNames;
if (_allowPartial) {
auto it = _pendingDocMap.find(name);
if (it != _pendingDocMap.end()) {
if(isMainDoc)
it->second.clear();
else
objNames.swap(it->second);
_pendingDocMap.erase(it);
}
}
FC_TIME_INIT(t1);
DocTiming timing;
const char *path = name.c_str();
const char *label = nullptr;
if (isMainDoc) {
if (paths && paths->size()>count)
path = (*paths)[count].c_str();
if (labels && labels->size()>count)
label = (*labels)[count].c_str();
}
auto doc = openDocumentPrivate(path, name.c_str(), label, isMainDoc, initFlags, std::move(objNames));
FC_DURATION_PLUS(timing.d1,t1);
if (doc) {
timings[doc].d1 += timing.d1;
newDocs.emplace(doc);
}
if (isMainDoc)
res[count] = doc;
_objCount = -1;
}
catch (const Base::Exception &e) {
e.reportException();
if (!errs && isMainDoc)
throw;
if (errs && isMainDoc)
(*errs)[count] = e.what();
else
Base::Console().error("Exception opening file: %s [%s]\n", name.c_str(), e.what());
}
catch (const std::exception &e) {
if (!errs && isMainDoc)
throw;
if (errs && isMainDoc)
(*errs)[count] = e.what();
else
Base::Console().error("Exception opening file: %s [%s]\n", name.c_str(), e.what());
}
catch (...) {
if (errs) {
if (isMainDoc)
(*errs)[count] = "unknown error";
}
else {
_pendingDocs.clear();
_pendingDocsReopen.clear();
_pendingDocMap.clear();
throw;
}
}
if (_pendingDocs.empty()) {
if(_pendingDocsReopen.empty())
break;
_pendingDocs = std::move(_pendingDocsReopen);
_pendingDocsReopen.clear();
for(const auto &file : _pendingDocs) {
auto doc = getDocumentByPath(file.c_str());
if(doc)
closeDocument(doc->getName());
}
}
}
++pass;
_pendingDocMap.clear();
std::vector<Document*> docs;
docs.reserve(newDocs.size());
for(const auto &d : newDocs) {
auto doc = d.getDocument();
if(!doc)
continue;
// Notify PropertyXLink to attach newly opened documents and restore
// relevant external links
PropertyXLink::restoreDocument(*doc);
docs.push_back(doc);
}
Base::SequencerLauncher seq("Postprocessing...", docs.size());
// After external links has been restored, we can now sort the document
// according to their dependency order.
try {
docs = Document::getDependentDocuments(docs, true);
} catch (Base::Exception &e) {
e.reportException();
}
for(auto it=docs.begin(); it!=docs.end();) {
auto doc = *it;
// It is possible that the newly opened document depends on an existing
// document, which will be included with the above call to
// Document::getDependentDocuments(). Make sure to exclude that.
if(!newDocs.contains(doc)) {
it = docs.erase(it);
continue;
}
auto &timing = timings[doc];
FC_TIME_INIT(t1);
// Finalize document restoring with the correct order
if(doc->afterRestore(true)) {
openedDocs.emplace_back(doc);
it = docs.erase(it);
} else {
++it;
// Here means this is a partial loaded document, and we need to
// reload it fully because of touched objects. The reason of
// reloading a partial document with touched object is because
// partial document is supposed to be readonly, while a
// 'touched' object requires recomputation. And an object may
// become touched during restoring if externally linked
// document time stamp mismatches with the stamp saved.
_pendingDocs.emplace_back(doc->FileName.getValue());
_pendingDocMap.erase(doc->FileName.getValue());
}
FC_DURATION_PLUS(timing.d2,t1);
seq.next();
}
// Close the document for reloading
for(const auto doc : docs)
closeDocument(doc->getName());
}while(!_pendingDocs.empty());
// Set the active document using the first successfully restored main
// document (i.e. documents explicitly asked for by caller).
for (auto doc : res) {
if (doc) {
setActiveDocument(doc);
break;
}
}
for (auto &doc : openedDocs) {
auto &timing = timings[doc];
FC_DURATION_LOG(timing.d1, doc.getDocumentName() << " restore");
FC_DURATION_LOG(timing.d2, doc.getDocumentName() << " postprocess");
}
FC_TIME_LOG(t,"total");
PropertyLinkBase::updateAllElementReferences();
_isRestoring = false;
signalFinishOpenDocument();
return res;
}
Document* Application::openDocumentPrivate(const char * FileName,
const char *propFileName, const char *label,
bool isMainDoc, DocumentInitFlags initFlags,
std::vector<std::string> &&objNames)
{
Base::FileInfo File(FileName);
if (!File.exists()) {
std::stringstream str;
str << "File '" << FileName << "' does not exist!";
throw Base::FileSystemError(str.str().c_str());
}
// Before creating a new document we check whether the document is already open
auto doc = getDocumentByPath(File.filePath().c_str(), PathMatchMode::MatchCanonicalWarning);
if(doc) {
if(doc->testStatus(Document::PartialDoc)
|| doc->testStatus(Document::PartialRestore)) {
// Here means a document is already partially loaded, but the document
// is requested again, either partial or not. We must check if the
// document contains the required object
if(isMainDoc) {
// Main document must be open fully, so close and reopen
closeDocument(doc->getName());
doc = nullptr;
} else if(_allowPartial) {
bool reopen = false;
for(const auto &name : objNames) {
auto obj = doc->getObject(name.c_str());
if(!obj || obj->testStatus(PartialObject)) {
reopen = true;
// NOTE: We are about to reload this document with
// extra objects. However, it is possible to repeat
// this process several times, if it is linked by
// multiple documents and each with a different set of
// objects. To partially solve this problem, we do not
// close and reopen the document immediately here, but
// add it to _pendingDocsReopen to delay reloading.
for(auto obj2 : doc->getObjects())
objNames.emplace_back(obj2->getNameInDocument());
_pendingDocMap[doc->FileName.getValue()] = std::move(objNames);
break;
}
}
if(!reopen)
return nullptr;
}
if(doc) {
_pendingDocsReopen.emplace_back(FileName);
return nullptr;
}
}
if (!isMainDoc) {
return nullptr;
}
if (doc) {
return doc;
}
}
std::string name;
if(propFileName != FileName) {
Base::FileInfo fi(propFileName);
name = fi.fileNamePure();
}else
name = File.fileNamePure();
// Use the same name for the internal and user name.
// The file name is UTF-8 encoded which means that the internal name will be modified
// to only contain valid ASCII characters but the user name will be kept.
if(!label)
label = name.c_str();
initFlags.createView &= isMainDoc;
Document* newDoc = newDocument(name.c_str(), label, initFlags);
newDoc->FileName.setValue(propFileName==FileName?File.filePath():propFileName);
try {
// read the document
newDoc->restore(File.filePath().c_str(),true,objNames);
if(!DocFileMap.empty())
DocFileMap[Base::FileInfo(newDoc->FileName.getValue()).filePath()] = newDoc;
return newDoc;
}
// if the project file itself is corrupt then
// close the document
catch (const Base::FileException&) {
closeDocument(newDoc->getName());
throw;
}
catch (const std::ios_base::failure&) {
closeDocument(newDoc->getName());
throw;
}
// but for any other exceptions leave it open to give the
// user a chance to fix it
catch (...) {
throw;
}
}
Document* Application::getActiveDocument() const
{
return _pActiveDoc;
}
void Application::setActiveDocument(Document* pDoc)
{
setActiveDocumentNoSignal(pDoc);
if (pDoc) {
signalActiveDocument(*pDoc);
}
}
void Application::setActiveDocumentNoSignal(Document* pDoc)
{
_pActiveDoc = pDoc;
// make sure that the active document is set in case no GUI is up
if (pDoc) {
Base::PyGILStateLocker lock;
const Py::Object active(pDoc->getPyObject(), true);
Py::Module("FreeCAD").setAttr(std::string("ActiveDocument"), active);
}
else {
Base::PyGILStateLocker lock;
Py::Module("FreeCAD").setAttr(std::string("ActiveDocument"), Py::None());
}
}
void Application::setActiveDocument(const char* Name)
{
// If no active document is set, resort to a default.
if (*Name == '\0') {
_pActiveDoc = nullptr;
return;
}
if (const auto pos = DocMap.find(Name); pos != DocMap.end()) {
setActiveDocument(pos->second);
}
else {
std::stringstream s;
s << "Try to activate unknown document '" << Name << "'";
throw Base::RuntimeError(s.str());
}
}
static int _TransSignalCount;
static bool _TransSignalled;
Application::TransactionSignaller::TransactionSignaller(bool abort, bool signal)
:abort(abort)
{
++_TransSignalCount;
if(signal && !_TransSignalled) {
_TransSignalled = true;
GetApplication().signalBeforeCloseTransaction(abort);
}
}
Application::TransactionSignaller::~TransactionSignaller() {
if(--_TransSignalCount == 0 && _TransSignalled) {
_TransSignalled = false;
try {
GetApplication().signalCloseTransaction(abort);
}
catch (const boost::exception&) {
// reported by code analyzers
Base::Console().warning("~TransactionSignaller: Unexpected boost exception\n");
}
}
}
int64_t Application::applicationPid()
{
static int64_t randomNumber = []() {
const auto tp = std::chrono::high_resolution_clock::now();
const auto dur = tp.time_since_epoch();
const auto seed = dur.count();
std::mt19937 generator(static_cast<unsigned>(seed));
constexpr int64_t minValue {1};
constexpr int64_t maxValue {1000000};
std::uniform_int_distribution<int64_t> distribution(minValue, maxValue);
return distribution(generator);
}();
return randomNumber;
}
std::string Application::getHomePath()
{
return Base::FileInfo::pathToString(Application::directories()->getHomePath()) + PATHSEP;
}
std::string Application::getExecutableName()
{
return mConfig["ExeName"];
}
std::string Application::getNameWithVersion()
{
auto appname = QCoreApplication::applicationName().toStdString();
auto config = Application::Config();
auto major = config["BuildVersionMajor"];
auto minor = config["BuildVersionMinor"];
auto point = config["BuildVersionPoint"];
auto suffix = config["BuildVersionSuffix"];
return fmt::format("{} {}.{}.{}{}", appname, major, minor, point, suffix);
}
bool Application::isDevelopmentVersion()
{
static std::string suffix = []() constexpr {
return FCVersionSuffix;
}();
return suffix == "dev";
}
const std::unique_ptr<ApplicationDirectories>& Application::directories() {
return _appDirs;
}
std::string Application::getTempPath()
{
return Base::FileInfo::pathToString(_appDirs->getTempPath()) + PATHSEP;
}
std::string Application::getTempFileName(const char* FileName)
{
return Base::FileInfo::pathToString(_appDirs->getTempFileName(FileName ? FileName : std::string()));
}
std::string Application::getUserCachePath()
{
return Base::FileInfo::pathToString(_appDirs->getUserCachePath()) + PATHSEP;
}
std::string Application::getUserConfigPath()
{
return Base::FileInfo::pathToString(_appDirs->getUserConfigPath()) + PATHSEP;
}
std::string Application::getUserAppDataDir()
{
return Base::FileInfo::pathToString(_appDirs->getUserAppDataDir()) + PATHSEP;
}
std::string Application::getUserMacroDir()
{
return Base::FileInfo::pathToString(_appDirs->getUserMacroDir()) + PATHSEP;
}
std::string Application::getResourceDir()
{
return Base::FileInfo::pathToString(_appDirs->getResourceDir()) + PATHSEP;
}
std::string Application::getLibraryDir()
{
return Base::FileInfo::pathToString(_appDirs->getLibraryDir()) + PATHSEP;
}
std::string Application::getHelpDir()
{
return Base::FileInfo::pathToString(_appDirs->getHelpDir()) + PATHSEP;
}
int Application::checkLinkDepth(int depth, MessageOption option)
{
if (_objCount < 0) {
_objCount = 0;
for (const auto &v : DocMap) {
_objCount += v.second->countObjects();
}
}
if (depth > _objCount + 2) {
const auto msg = "Link recursion limit reached. "
"Please check for cyclic reference.";
switch (option) {
case MessageOption::Quiet:
return 0;
case MessageOption::Error:
FC_ERR(msg);
return 0;
case MessageOption::Throw:
throw Base::RuntimeError(msg);
}
}
return _objCount + 2;
}
std::set<DocumentObject *> Application::getLinksTo(
const DocumentObject *obj, int options, int maxCount) const
{
std::set<DocumentObject *> links;
if(!obj) {
for(auto &v : DocMap) {
v.second->getLinksTo(links,obj,options,maxCount);
if(maxCount && static_cast<int>(links.size())>=maxCount)
break;
}
} else {
std::set<Document*> docs;
for (const auto o : obj->getInList()) {
if(o && o->isAttachedToDocument() && docs.insert(o->getDocument()).second) {
o->getDocument()->getLinksTo(links,obj,options,maxCount);
if(maxCount && static_cast<int>(links.size())>=maxCount)
break;
}
}
}
return links;
}
bool Application::hasLinksTo(const DocumentObject *obj) const {
return !getLinksTo(obj,0,1).empty();
}
ParameterManager & Application::GetSystemParameter()
{
return *_pcSysParamMngr;
}
ParameterManager & Application::GetUserParameter()
{
return *_pcUserParamMngr;
}
ParameterManager * Application::GetParameterSet(const char* sName) const
{
const auto it = mpcPramManager.find(sName);
return it != mpcPramManager.end() ? it->second : nullptr;
}
const std::map<std::string,Base::Reference<ParameterManager>> &
Application::GetParameterSetList() const
{
return mpcPramManager;
}
void Application::AddParameterSet(const char* sName)
{
const auto it = mpcPramManager.find(sName);
if ( it != mpcPramManager.end() )
return;
mpcPramManager[sName] = ParameterManager::Create();
}
void Application::RemoveParameterSet(const char* sName)
{
const auto it = mpcPramManager.find(sName);
// Must not delete user or system parameter
if ( it == mpcPramManager.end() || it->second == _pcUserParamMngr || it->second == _pcSysParamMngr )
return;
mpcPramManager.erase(it);
}
Base::Reference<ParameterGrp> Application::GetParameterGroupByPath(const char* sName)
{
std::string cName = sName, cTemp;
const std::string::size_type pos = cName.find(':');
// is there a path separator ?
if (pos == std::string::npos) {
throw Base::ValueError("Application::GetParameterGroupByPath() no parameter set name specified");
}
// assigning the parameter set name
cTemp.assign(cName,0,pos);
cName.erase(0,pos+1);
// test if name is valid
const auto It = mpcPramManager.find(cTemp);
if (It == mpcPramManager.end())
throw Base::ValueError("Application::GetParameterGroupByPath() unknown parameter set name specified");
return It->second->GetGroup(cName.c_str());
}
void Application::addImportType(const char* Type, const char* ModuleName)
{
FileTypeItem item;
item.filter = Type;
item.module = ModuleName;
// Extract each filetype from 'Type' literal
std::string::size_type pos = item.filter.find("*.");
while ( pos != std::string::npos ) {
const std::string::size_type next = item.filter.find_first_of(" )", pos + 1);
const std::string::size_type len = next-pos-2;
std::string type = item.filter.substr(pos+2,len);
item.types.push_back(std::move(type));
pos = item.filter.find("*.", next);
}
// Due to branding stuff replace "FreeCAD" with the branded application name
if (strncmp(Type, "FreeCAD", 7) == 0) {
std::string AppName = Config()["ExeName"];
AppName += item.filter.substr(7);
item.filter = std::move(AppName);
// put to the front of the array
_mImportTypes.insert(_mImportTypes.begin(),std::move(item));
}
else {
_mImportTypes.push_back(std::move(item));
}
}
void Application::changeImportModule(const char* Type, const char* OldModuleName, const char* NewModuleName)
{
for (auto& it : _mImportTypes) {
if (it.filter == Type && it.module == OldModuleName) {
it.module = NewModuleName;
break;
}
}
}
std::vector<std::string> Application::getImportModules(const char* Type) const
{
std::vector<std::string> modules;
for (const auto & it : _mImportTypes) {
const std::vector<std::string>& types = it.types;
for (const auto & jt : types) {
#ifdef __GNUC__
if (strcasecmp(Type,jt.c_str()) == 0)
#else
if (_stricmp(Type,jt.c_str()) == 0)
#endif
modules.push_back(it.module);
}
}
return modules;
}
std::vector<std::string> Application::getImportModules() const
{
std::vector<std::string> modules;
modules.reserve(_mImportTypes.size());
for (const auto& it : _mImportTypes) {
modules.push_back(it.module);
}
std::sort(modules.begin(), modules.end());
modules.erase(std::unique(modules.begin(), modules.end()), modules.end());
return modules;
}
std::vector<std::string> Application::getImportTypes(const char* Module) const
{
std::vector<std::string> types;
for (const auto & it : _mImportTypes) {
#ifdef __GNUC__
if (strcasecmp(Module,it.module.c_str()) == 0)
#else
if (_stricmp(Module,it.module.c_str()) == 0)
#endif
types.insert(types.end(), it.types.begin(), it.types.end());
}
return types;
}
std::vector<std::string> Application::getImportTypes() const
{
std::vector<std::string> types;
for (const auto & it : _mImportTypes) {
types.insert(types.end(), it.types.begin(), it.types.end());
}
std::sort(types.begin(), types.end());
types.erase(std::unique(types.begin(), types.end()), types.end());
return types;
}
std::map<std::string, std::string> Application::getImportFilters(const char* Type) const
{
std::map<std::string, std::string> moduleFilter;
for (const auto & it : _mImportTypes) {
const std::vector<std::string>& types = it.types;
for (const auto & jt : types) {
#ifdef __GNUC__
if (strcasecmp(Type,jt.c_str()) == 0)
#else
if (_stricmp(Type,jt.c_str()) == 0)
#endif
moduleFilter[it.filter] = it.module;
}
}
return moduleFilter;
}
std::map<std::string, std::string> Application::getImportFilters() const
{
std::map<std::string, std::string> filter;
for (const auto & it : _mImportTypes) {
filter[it.filter] = it.module;
}
return filter;
}
void Application::addExportType(const char* Type, const char* ModuleName)
{
FileTypeItem item;
item.filter = Type;
item.module = ModuleName;
// Extract each filetype from 'Type' literal
std::string::size_type pos = item.filter.find("*.");
while ( pos != std::string::npos ) {
const std::string::size_type next = item.filter.find_first_of(" )", pos + 1);
const std::string::size_type len = next-pos-2;
std::string type = item.filter.substr(pos+2,len);
item.types.push_back(std::move(type));
pos = item.filter.find("*.", next);
}
// Due to branding stuff replace "FreeCAD" with the branded application name
if (strncmp(Type, "FreeCAD", 7) == 0) {
std::string AppName = Config()["ExeName"];
AppName += item.filter.substr(7);
item.filter = std::move(AppName);
// put to the front of the array
_mExportTypes.insert(_mExportTypes.begin(),std::move(item));
}
else {
_mExportTypes.push_back(std::move(item));
}
}
void Application::changeExportModule(const char* Type, const char* OldModuleName, const char* NewModuleName)
{
for (auto& it : _mExportTypes) {
if (it.filter == Type && it.module == OldModuleName) {
it.module = NewModuleName;
break;
}
}
}
std::vector<std::string> Application::getExportModules(const char* Type) const
{
std::vector<std::string> modules;
for (const auto & it : _mExportTypes) {
const std::vector<std::string>& types = it.types;
for (const auto & jt : types) {
#ifdef __GNUC__
if (strcasecmp(Type,jt.c_str()) == 0)
#else
if (_stricmp(Type,jt.c_str()) == 0)
#endif
modules.push_back(it.module);
}
}
return modules;
}
std::vector<std::string> Application::getExportModules() const
{
std::vector<std::string> modules;
modules.reserve(_mExportTypes.size());
for (const auto& it : _mExportTypes) {
modules.push_back(it.module);
}
std::sort(modules.begin(), modules.end());
modules.erase(std::unique(modules.begin(), modules.end()), modules.end());
return modules;
}
std::vector<std::string> Application::getExportTypes(const char* Module) const
{
std::vector<std::string> types;
for (const auto & it : _mExportTypes) {
#ifdef __GNUC__
if (strcasecmp(Module,it.module.c_str()) == 0)
#else
if (_stricmp(Module,it.module.c_str()) == 0)
#endif
types.insert(types.end(), it.types.begin(), it.types.end());
}
return types;
}
std::vector<std::string> Application::getExportTypes() const
{
std::vector<std::string> types;
for (const FileTypeItem& it : _mExportTypes) {
types.insert(types.end(), it.types.begin(), it.types.end());
}
std::sort(types.begin(), types.end());
types.erase(std::unique(types.begin(), types.end()), types.end());
return types;
}
std::map<std::string, std::string> Application::getExportFilters(const char* Type) const
{
std::map<std::string, std::string> moduleFilter;
for (const auto & it : _mExportTypes) {
const std::vector<std::string>& types = it.types;
for (const auto & jt : types) {
#ifdef __GNUC__
if (strcasecmp(Type,jt.c_str()) == 0)
#else
if (_stricmp(Type,jt.c_str()) == 0)
#endif
moduleFilter[it.filter] = it.module;
}
}
return moduleFilter;
}
std::map<std::string, std::string> Application::getExportFilters() const
{
std::map<std::string, std::string> filter;
for (const FileTypeItem& it : _mExportTypes) {
filter[it.filter] = it.module;
}
return filter;
}
//**************************************************************************
// signaling
void Application::slotBeforeChangeDocument(const Document& doc, const Property& prop)
{
this->signalBeforeChangeDocument(doc, prop);
}
void Application::slotChangedDocument(const Document& doc, const Property& prop)
{
this->signalChangedDocument(doc, prop);
}
void Application::slotNewObject(const DocumentObject& obj)
{
this->signalNewObject(obj);
_objCount = -1;
}
void Application::slotDeletedObject(const DocumentObject& obj)
{
this->signalDeletedObject(obj);
_objCount = -1;
}
void Application::slotBeforeChangeObject(const DocumentObject& obj, const Property& prop)
{
this->signalBeforeChangeObject(obj, prop);
}
void Application::slotChangedObject(const DocumentObject& obj, const Property& prop)
{
this->signalChangedObject(obj, prop);
}
void Application::slotRelabelObject(const DocumentObject& obj)
{
this->signalRelabelObject(obj);
}
void Application::slotActivatedObject(const DocumentObject& obj)
{
this->signalActivatedObject(obj);
}
void Application::slotUndoDocument(const Document& doc)
{
this->signalUndoDocument(doc);
}
void Application::slotRedoDocument(const Document& doc)
{
this->signalRedoDocument(doc);
}
void Application::slotRecomputedObject(const DocumentObject& obj)
{
this->signalObjectRecomputed(obj);
}
void Application::slotRecomputed(const Document& doc)
{
this->signalRecomputed(doc);
}
void Application::slotBeforeRecompute(const Document& doc)
{
this->signalBeforeRecomputeDocument(doc);
}
void Application::slotOpenTransaction(const Document &doc, std::string name)
{
this->signalOpenTransaction(doc, std::move(name));
}
void Application::slotCommitTransaction(const Document& doc)
{
this->signalCommitTransaction(doc);
}
void Application::slotAbortTransaction(const Document& doc)
{
this->signalAbortTransaction(doc);
}
void Application::slotStartSaveDocument(const Document& doc, const std::string& filename)
{
this->signalStartSaveDocument(doc, filename);
}
void Application::slotFinishSaveDocument(const Document& doc, const std::string& filename)
{
DocFileMap.clear();
this->signalFinishSaveDocument(doc, filename);
}
void Application::slotChangePropertyEditor(const Document& doc, const Property& prop)
{
this->signalChangePropertyEditor(doc, prop);
}
//**************************************************************************
// Init, Destruct and singleton
Application * Application::_pcSingleton = nullptr;
int Application::_argc;
char ** Application::_argv;
void Application::cleanupUnits()
{
try {
Base::PyGILStateLocker lock;
Py::Module mod (Py::Module("FreeCAD").getAttr("Units").ptr());
Py::List attr(mod.dir());
for (Py::List::iterator it = attr.begin(); it != attr.end(); ++it) {
mod.delAttr(Py::String(*it));
}
}
catch (Py::Exception& e) {
Base::PyGILStateLocker lock;
e.clear();
}
}
void Application::destruct()
{
// saving system parameter
if (_pcSysParamMngr->IgnoreSave()) {
Base::Console().warning("Discard system parameter\n");
}
else {
Base::Console().log("Saving system parameter...\n");
_pcSysParamMngr->SaveDocument();
Base::Console().log("Saving system parameter...done\n");
}
// saving the User parameter
if (_pcUserParamMngr->IgnoreSave()) {
Base::Console().warning("Discard user parameter\n");
}
else {
Base::Console().log("Saving user parameter...\n");
_pcUserParamMngr->SaveDocument();
Base::Console().log("Saving user parameter...done\n");
}
// now save all other parameter files
auto& paramMgr = _pcSingleton->mpcPramManager;
for (const auto &it : paramMgr) {
if ((it.second != _pcSysParamMngr) && (it.second != _pcUserParamMngr)) {
if (it.second->HasSerializer() && !it.second->IgnoreSave()) {
Base::Console().log("Saving %s...\n", it.first.c_str());
it.second->SaveDocument();
Base::Console().log("Saving %s...done\n", it.first.c_str());
}
}
}
paramMgr.clear();
_pcSysParamMngr = nullptr;
_pcUserParamMngr = nullptr;
#ifdef FC_DEBUG
// Do this only in debug mode for memory leak checkers
cleanupUnits();
#endif
CleanupProcess::callCleanup();
// not initialized or double destruct!
assert(_pcSingleton);
delete _pcSingleton;
// We must detach from console and delete the observer to save our file
destructObserver();
Base::Interpreter().finalize();
Base::ScriptFactorySingleton::Destruct();
Base::InterpreterSingleton::Destruct();
Base::Type::destruct();
ParameterManager::Terminate();
SafeMode::Destruct();
}
void Application::destructObserver()
{
if ( _pConsoleObserverFile ) {
Base::Console().detachObserver(_pConsoleObserverFile);
delete _pConsoleObserverFile;
_pConsoleObserverFile = nullptr;
}
if ( _pConsoleObserverStd ) {
Base::Console().detachObserver(_pConsoleObserverStd);
delete _pConsoleObserverStd;
_pConsoleObserverStd = nullptr;
}
}
/** freecadNewHandler()
* prints an error message and throws an exception
*/
#ifdef _MSC_VER // New handler for Microsoft Visual C++ compiler
int __cdecl freecadNewHandler(size_t size )
{
// throw an exception
throw Base::MemoryException();
return 0;
}
#else // Ansi C/C++ new handler
static void freecadNewHandler ()
{
// throw an exception
throw Base::MemoryException();
}
#endif
#if defined(FC_OS_LINUX)
#include <execinfo.h>
#include <dlfcn.h>
#include <cxxabi.h>
#include <cstdio>
#include <cstdlib>
#include <string>
#include <sstream>
#if HAVE_CONFIG_H
#include <config.h>
#endif // HAVE_CONFIG_H
// This function produces a stack backtrace with demangled function & method names.
void printBacktrace(size_t skip=0)
{
#if defined HAVE_BACKTRACE_SYMBOLS
void *callstack[128];
size_t nMaxFrames = sizeof(callstack) / sizeof(callstack[0]);
size_t nFrames = backtrace(callstack, nMaxFrames);
char **symbols = backtrace_symbols(callstack, nFrames);
for (size_t i = skip; i < nFrames; i++) {
char *demangled = nullptr;
int status = -1;
Dl_info info;
if (dladdr(callstack[i], &info) && info.dli_sname && info.dli_fname) {
if (info.dli_sname[0] == '_') {
demangled = abi::__cxa_demangle(info.dli_sname, nullptr, nullptr, &status);
}
}
std::stringstream str;
if (status == 0) {
void* offset = (void*)((char*)callstack[i] - (char*)info.dli_saddr);
str << "#" << (i-skip) << " " << callstack[i] << " in " << demangled << " from " << info.dli_fname << "+" << offset << '\n';
free(demangled);
}
else {
str << "#" << (i-skip) << " " << symbols[i] << '\n';
}
// cannot directly print to cerr when using --write-log
std::cerr << str.str();
}
free(symbols);
#else //HAVE_BACKTRACE_SYMBOLS
(void)skip;
std::cerr << "Cannot print the stacktrace because the C runtime library doesn't provide backtrace or backtrace_symbols\n";
#endif
}
#endif
void segmentation_fault_handler(int sig)
{
#if defined(FC_OS_LINUX)
(void)sig;
std::cerr << "Program received signal SIGSEGV, Segmentation fault.\n";
printBacktrace(2);
#if defined(FC_DEBUG)
abort();
#else
_exit(1);
#endif
#else
switch (sig) {
case SIGSEGV:
std::cerr << "Illegal storage access..." << '\n';
#if !defined(_DEBUG)
throw Base::AccessViolation("Illegal storage access! Please save your work under a new file name and restart the application!");
#endif
break;
case SIGABRT:
std::cerr << "Abnormal program termination..." << '\n';
#if !defined(_DEBUG)
throw Base::AbnormalProgramTermination("Break signal occurred");
#endif
break;
default:
std::cerr << "Unknown error occurred..." << '\n';
break;
}
#endif // FC_OS_LINUX
}
void unhandled_exception_handler()
{
std::cerr << "Terminating..." << '\n';
}
void unexpection_error_handler()
{
std::cerr << "Unexpected error occurred..." << '\n';
// try to throw an exception and give the user chance to save their work
#if !defined(_DEBUG)
throw Base::AbnormalProgramTermination("Unexpected error occurred! Please save your work under a new file name and restart the application!");
#else
terminate();
#endif
}
#if defined(FC_SE_TRANSLATOR) // Microsoft compiler
void my_se_translator_filter(unsigned int code, EXCEPTION_POINTERS* pExp)
{
Q_UNUSED(pExp)
switch (code)
{
case EXCEPTION_ACCESS_VIOLATION:
throw Base::AccessViolation();
case EXCEPTION_FLT_DIVIDE_BY_ZERO:
case EXCEPTION_INT_DIVIDE_BY_ZERO:
Base::Console().error("SEH exception (%u): Division by zero\n", code);
return;
}
std::stringstream str;
str << "SEH exception of type: " << code;
// general C++ SEH exception for things we don't need to handle separately....
throw Base::RuntimeError(str.str());
}
#endif
void Application::init(int argc, char ** argv)
{
try {
// install our own new handler
#ifdef _MSC_VER // Microsoft compiler
_set_new_handler ( freecadNewHandler ); // Setup new handler
_set_new_mode( 1 ); // Re-route malloc failures to new handler !
#else // Ansi compiler
std::set_new_handler (freecadNewHandler); // ANSI new handler
#endif
// if an unexpected crash occurs we can install a handler function to
// write some additional information
#if defined (_MSC_VER) // Microsoft compiler
std::signal(SIGSEGV,segmentation_fault_handler);
std::signal(SIGABRT,segmentation_fault_handler);
std::set_terminate(unhandled_exception_handler);
::set_unexpected(unexpection_error_handler);
#elif defined(FC_OS_LINUX)
std::signal(SIGSEGV,segmentation_fault_handler);
#endif
#if defined(FC_SE_TRANSLATOR)
_set_se_translator(my_se_translator_filter);
#endif
initTypes();
initConfig(argc,argv);
initApplication();
}
catch (...) {
// force the log to flush
destructObserver();
throw;
}
}
// clang-format off
void Application::initTypes()
{
// Base types
Base::Type ::init();
Base::BaseClass ::init();
Base::Exception ::init();
Base::AbortException ::init();
Base::Persistence ::init();
// Complex data classes
Data::ComplexGeoData ::init();
Data::Segment ::init();
// Properties
// Note: the order matters
App::Property ::init();
App::PropertyContainer ::init();
App::PropertyLists ::init();
App::PropertyBool ::init();
App::PropertyBoolList ::init();
App::PropertyFloat ::init();
App::PropertyFloatList ::init();
App::PropertyFloatConstraint ::init();
App::PropertyPrecision ::init();
App::PropertyQuantity ::init();
App::PropertyQuantityConstraint ::init();
App::PropertyInteger ::init();
App::PropertyIntegerConstraint ::init();
App::PropertyPercent ::init();
App::PropertyEnumeration ::init();
App::PropertyIntegerList ::init();
App::PropertyIntegerSet ::init();
App::PropertyMap ::init();
App::PropertyString ::init();
App::PropertyPersistentObject ::init();
App::PropertyUUID ::init();
App::PropertyFont ::init();
App::PropertyStringList ::init();
App::PropertyLinkBase ::init();
App::PropertyLinkListBase ::init();
App::PropertyLink ::init();
App::PropertyLinkChild ::init();
App::PropertyLinkGlobal ::init();
App::PropertyLinkHidden ::init();
App::PropertyLinkSub ::init();
App::PropertyLinkSubChild ::init();
App::PropertyLinkSubGlobal ::init();
App::PropertyLinkSubHidden ::init();
App::PropertyLinkList ::init();
App::PropertyLinkListChild ::init();
App::PropertyLinkListGlobal ::init();
App::PropertyLinkListHidden ::init();
App::PropertyLinkSubList ::init();
App::PropertyLinkSubListChild ::init();
App::PropertyLinkSubListGlobal ::init();
App::PropertyLinkSubListHidden ::init();
App::PropertyXLink ::init();
App::PropertyXLinkSub ::init();
App::PropertyXLinkSubHidden ::init();
App::PropertyXLinkSubList ::init();
App::PropertyXLinkList ::init();
App::PropertyXLinkContainer ::init();
App::PropertyMatrix ::init();
App::PropertyVector ::init();
App::PropertyVectorDistance ::init();
App::PropertyPosition ::init();
App::PropertyDirection ::init();
App::PropertyVectorList ::init();
App::PropertyPlacement ::init();
App::PropertyPlacementList ::init();
App::PropertyPlacementLink ::init();
App::PropertyRotation ::init();
App::PropertyGeometry ::init();
App::PropertyComplexGeoData ::init();
App::PropertyColor ::init();
App::PropertyColorList ::init();
App::PropertyMaterial ::init();
App::PropertyMaterialList ::init();
App::PropertyPath ::init();
App::PropertyFile ::init();
App::PropertyFileIncluded ::init();
App::PropertyPythonObject ::init();
App::PropertyExpressionContainer::init();
App::PropertyExpressionEngine ::init();
// all know unit properties
App::PropertyAcceleration ::init();
App::PropertyAmountOfSubstance ::init();
App::PropertyAngle ::init();
App::PropertyArea ::init();
App::PropertyCompressiveStrength ::init();
App::PropertyCurrentDensity ::init();
App::PropertyDensity ::init();
App::PropertyDissipationRate ::init();
App::PropertyDistance ::init();
App::PropertyDynamicViscosity ::init();
App::PropertyElectricalCapacitance ::init();
App::PropertyElectricalConductance ::init();
App::PropertyElectricalConductivity ::init();
App::PropertyElectricalInductance ::init();
App::PropertyElectricalResistance ::init();
App::PropertyElectricCharge ::init();
App::PropertySurfaceChargeDensity ::init();
App::PropertyVolumeChargeDensity ::init();
App::PropertyElectricCurrent ::init();
App::PropertyElectricPotential ::init();
App::PropertyElectromagneticPotential ::init();
App::PropertyFrequency ::init();
App::PropertyForce ::init();
App::PropertyHeatFlux ::init();
App::PropertyInverseArea ::init();
App::PropertyInverseLength ::init();
App::PropertyInverseVolume ::init();
App::PropertyKinematicViscosity ::init();
App::PropertyLength ::init();
App::PropertyLuminousIntensity ::init();
App::PropertyMagneticFieldStrength ::init();
App::PropertyMagneticFlux ::init();
App::PropertyMagneticFluxDensity ::init();
App::PropertyMagnetization ::init();
App::PropertyMass ::init();
App::PropertyMoment ::init();
App::PropertyPressure ::init();
App::PropertyPower ::init();
App::PropertyShearModulus ::init();
App::PropertySpecificEnergy ::init();
App::PropertySpecificHeat ::init();
App::PropertySpeed ::init();
App::PropertyStiffness ::init();
App::PropertyStiffnessDensity ::init();
App::PropertyStress ::init();
App::PropertyTemperature ::init();
App::PropertyThermalConductivity ::init();
App::PropertyThermalExpansionCoefficient::init();
App::PropertyThermalTransferCoefficient ::init();
App::PropertyTime ::init();
App::PropertyUltimateTensileStrength ::init();
App::PropertyVacuumPermittivity ::init();
App::PropertyVelocity ::init();
App::PropertyVolume ::init();
App::PropertyVolumeFlowRate ::init();
App::PropertyVolumetricThermalExpansionCoefficient::init();
App::PropertyWork ::init();
App::PropertyYieldStrength ::init();
App::PropertyYoungsModulus ::init();
// Extension classes
App::Extension ::init();
App::ExtensionContainer ::init();
App::DocumentObjectExtension ::init();
App::GroupExtension ::init();
App::GroupExtensionPython ::init();
App::GeoFeatureGroupExtension ::init();
App::GeoFeatureGroupExtensionPython::init();
App::OriginGroupExtension ::init();
App::OriginGroupExtensionPython ::init();
App::LinkBaseExtension ::init();
App::LinkBaseExtensionPython ::init();
App::LinkExtension ::init();
App::LinkExtensionPython ::init();
App::SuppressibleExtension ::init();
App::SuppressibleExtensionPython ::init();
// Document classes
App::TransactionalObject ::init();
App::DocumentObject ::init();
App::GeoFeature ::init();
// Test features
App::FeatureTest ::init();
App::FeatureTestException ::init();
App::FeatureTestColumn ::init();
App::FeatureTestRow ::init();
App::FeatureTestAbsAddress ::init();
App::FeatureTestPlacement ::init();
App::FeatureTestAttribute ::init();
// Feature class
App::FeaturePython ::init();
App::GeometryPython ::init();
App::Document ::init();
App::DocumentObjectGroup ::init();
App::DocumentObjectGroupPython ::init();
App::DocumentObjectFileIncluded::init();
Image::ImagePlane ::init();
App::InventorObject ::init();
App::VRMLObject ::init();
App::Annotation ::init();
App::AnnotationLabel ::init();
App::MaterialObject ::init();
App::MaterialObjectPython ::init();
App::TextDocument ::init();
App::Placement ::init();
App::PlacementPython ::init();
App::DatumElement ::init();
App::Plane ::init();
App::Line ::init();
App::Point ::init();
App::LocalCoordinateSystem ::init();
App::Part ::init();
App::Origin ::init();
App::Link ::init();
App::LinkPython ::init();
App::LinkElement ::init();
App::LinkElementPython ::init();
App::LinkGroup ::init();
App::LinkGroupPython ::init();
App::VarSet ::init();
// Expression classes
App::Expression ::init();
App::UnitExpression ::init();
App::NumberExpression ::init();
App::ConstantExpression ::init();
App::OperatorExpression ::init();
App::VariableExpression ::init();
App::ConditionalExpression ::init();
App::StringExpression ::init();
App::FunctionExpression ::init();
App::RangeExpression ::init();
App::PyObjectExpression ::init();
// Topological naming classes
App::StringHasher ::init();
App::StringID ::init();
// register transaction type
new App::TransactionProducer<TransactionDocumentObject>
(DocumentObject::getClassTypeId());
// register exception producer types
new Base::ExceptionProducer<Base::AbortException>;
new Base::ExceptionProducer<Base::XMLBaseException>;
new Base::ExceptionProducer<Base::XMLParseException>;
new Base::ExceptionProducer<Base::XMLAttributeError>;
new Base::ExceptionProducer<Base::FileException>;
new Base::ExceptionProducer<Base::FileSystemError>;
new Base::ExceptionProducer<Base::BadFormatError>;
new Base::ExceptionProducer<Base::MemoryException>;
new Base::ExceptionProducer<Base::AccessViolation>;
new Base::ExceptionProducer<Base::AbnormalProgramTermination>;
new Base::ExceptionProducer<Base::UnknownProgramOption>;
new Base::ExceptionProducer<Base::ProgramInformation>;
new Base::ExceptionProducer<Base::TypeError>;
new Base::ExceptionProducer<Base::ValueError>;
new Base::ExceptionProducer<Base::IndexError>;
new Base::ExceptionProducer<Base::NameError>;
new Base::ExceptionProducer<Base::ImportError>;
new Base::ExceptionProducer<Base::AttributeError>;
new Base::ExceptionProducer<Base::RuntimeError>;
new Base::ExceptionProducer<Base::BadGraphError>;
new Base::ExceptionProducer<Base::NotImplementedError>;
new Base::ExceptionProducer<Base::ZeroDivisionError>;
new Base::ExceptionProducer<Base::ReferenceError>;
new Base::ExceptionProducer<Base::ExpressionError>;
new Base::ExceptionProducer<Base::ParserError>;
new Base::ExceptionProducer<Base::UnicodeError>;
new Base::ExceptionProducer<Base::OverflowError>;
new Base::ExceptionProducer<Base::UnderflowError>;
new Base::ExceptionProducer<Base::UnitsMismatchError>;
new Base::ExceptionProducer<Base::CADKernelError>;
new Base::ExceptionProducer<Base::RestoreError>;
new Base::ExceptionProducer<Base::PropertyError>;
Base::registerServiceImplementation<CenterOfMassProvider>(new NullCenterOfMass);
}
namespace {
void parseProgramOptions(int ac, char ** av, const std::string& exe, boost::program_options::variables_map& vm)
{
// Declare a group of options that will be
// allowed only on the command line
boost::program_options::options_description generic("Generic options");
generic.add_options()
("version,v", "Prints version string")
("verbose", "Prints verbose version string")
("help,h", "Prints help message")
("console,c", "Starts in console mode")
("response-file", boost::program_options::value<std::string>(),"Can be specified with '@name', too")
("dump-config", "Dumps configuration")
("get-config", boost::program_options::value<std::string>(), "Prints the value of the requested configuration key")
("set-config", boost::program_options::value< std::vector<std::string> >()->multitoken(), "Sets the value of a configuration key")
("keep-deprecated-paths", "If set then config files are kept on the old location")
;
// Declare a group of options that will be
// allowed both on the command line and in
// the config file
std::stringstream descr;
descr << "Writes " << exe << ".log to the user directory.";
boost::program_options::options_description config("Configuration");
config.add_options()
("write-log,l", descr.str().c_str())
("log-file", boost::program_options::value<std::string>(), "Unlike --write-log this allows logging to an arbitrary file")
("user-cfg,u", boost::program_options::value<std::string>(),"User config file to load/save user settings")
("system-cfg,s", boost::program_options::value<std::string>(),"System config file to load/save system settings")
("run-test,t", boost::program_options::value<std::string>()->implicit_value(""),"Run a given test case (use 0 (zero) to run all tests). If no argument is provided then return list of all available tests.")
("run-open,r", boost::program_options::value<std::string>()->implicit_value(""),"Run a given test case (use 0 (zero) to run all tests). If no argument is provided then return list of all available tests. Keeps UI open after test(s) complete.")
("module-path,M", boost::program_options::value< std::vector<std::string> >()->composing(),"Additional module paths")
("macro-path,E", boost::program_options::value< std::vector<std::string> >()->composing(),"Additional macro paths")
("python-path,P", boost::program_options::value< std::vector<std::string> >()->composing(),"Additional python paths")
("disable-addon", boost::program_options::value< std::vector<std::string> >()->composing(),"Disable a given addon.")
("single-instance", "Allow to run a single instance of the application")
("safe-mode", "Force enable safe mode")
("pass", boost::program_options::value< std::vector<std::string> >()->multitoken(), "Ignores the following arguments and pass them through to be used by a script")
;
// Hidden options, will be allowed both on the command line and
// in the config file, but will not be shown to the user.
boost::program_options::options_description hidden("Hidden options");
hidden.add_options()
("input-file", boost::program_options::value< std::vector<std::string> >(), "input file")
("output", boost::program_options::value<std::string>(),"output file")
("hidden", "don't show the main window")
// this are to ignore for the window system (QApplication)
("style", boost::program_options::value< std::string >(), "set the application GUI style")
("stylesheet", boost::program_options::value< std::string >(), "set the application stylesheet")
("session", boost::program_options::value< std::string >(), "restore the application from an earlier session")
("reverse", "set the application's layout direction from right to left")
("widgetcount", "print debug messages about widgets")
("graphicssystem", boost::program_options::value< std::string >(), "backend to be used for on-screen widgets and pixmaps")
("display", boost::program_options::value< std::string >(), "set the X-Server")
("geometry ", boost::program_options::value< std::string >(), "set the X-Window geometry")
("font", boost::program_options::value< std::string >(), "set the X-Window font")
("fn", boost::program_options::value< std::string >(), "set the X-Window font")
("background", boost::program_options::value< std::string >(), "set the X-Window background color")
("bg", boost::program_options::value< std::string >(), "set the X-Window background color")
("foreground", boost::program_options::value< std::string >(), "set the X-Window foreground color")
("fg", boost::program_options::value< std::string >(), "set the X-Window foreground color")
("button", boost::program_options::value< std::string >(), "set the X-Window button color")
("btn", boost::program_options::value< std::string >(), "set the X-Window button color")
("name", boost::program_options::value< std::string >(), "set the X-Window name")
("title", boost::program_options::value< std::string >(), "set the X-Window title")
("visual", boost::program_options::value< std::string >(), "set the X-Window to color scheme")
("ncols", boost::program_options::value< int >(), "set the X-Window to color scheme")
("cmap", "set the X-Window to color scheme")
#if defined(FC_OS_MACOSX)
("psn", boost::program_options::value< std::string >(), "process serial number")
#endif
;
//0000723: improper handling of qt specific command line arguments
std::vector<std::string> args;
bool merge=false;
for (int i=1; i<ac; i++) {
if (merge) {
merge = false;
args.back() += "=";
args.back() += av[i];
}
else {
args.emplace_back(av[i]);
}
if (strcmp(av[i],"-style") == 0) {
merge = true;
}
else if (strcmp(av[i],"-stylesheet") == 0) {
merge = true;
}
else if (strcmp(av[i],"-session") == 0) {
merge = true;
}
else if (strcmp(av[i],"-graphicssystem") == 0) {
merge = true;
}
}
// 0000659: SIGABRT on startup in boost::program_options (Boost 1.49)
// Add some text to the constructor
boost::program_options::options_description cmdline_options("Command-line options");
cmdline_options.add(generic).add(config).add(hidden);
boost::program_options::options_description config_file_options("Config");
config_file_options.add(config).add(hidden);
boost::program_options::options_description visible("Allowed options");
visible.add(generic).add(config);
boost::program_options::positional_options_description p;
p.add("input-file", -1);
try {
store( boost::program_options::command_line_parser(args).
options(cmdline_options).positional(p).extra_parser(Util::customSyntax).run(), vm);
std::ifstream ifs("FreeCAD.cfg");
if (ifs)
store(parse_config_file(ifs, config_file_options), vm);
notify(vm);
}
catch (const std::exception& e) {
std::stringstream str;
str << e.what() << '\n' << '\n' << visible << '\n';
throw Base::UnknownProgramOption(str.str());
}
catch (...) {
std::stringstream str;
str << "Wrong or unknown option, bailing out!" << '\n' << '\n' << visible << '\n';
throw Base::UnknownProgramOption(str.str());
}
if (vm.contains("help")) {
std::stringstream str;
str << exe << '\n' << '\n';
str << "For a detailed description see https://www.freecad.org/wiki/Start_up_and_Configuration" << '\n'<<'\n';
str << "Usage: " << exe << " [options] File1 File2 ..." << '\n' << '\n';
str << visible << '\n';
throw Base::ProgramInformation(str.str());
}
if (vm.contains("response-file")) {
// Load the file and tokenize it
std::ifstream ifs(vm["response-file"].as<std::string>().c_str());
if (!ifs) {
Base::Console().error("Could no open the response file\n");
std::stringstream str;
str << "Could no open the response file: '"
<< vm["response-file"].as<std::string>() << "'" << '\n';
throw Base::UnknownProgramOption(str.str());
}
// Read the whole file into a string
std::stringstream ss;
ss << ifs.rdbuf();
// Split the file content
boost::char_separator<char> sep(" \n\r");
boost::tokenizer<boost::char_separator<char> > tok(ss.str(), sep);
std::vector<std::string> args2;
copy(tok.begin(), tok.end(), back_inserter(args2));
// Parse the file and store the options
store( boost::program_options::command_line_parser(args2).
options(cmdline_options).positional(p).extra_parser(Util::customSyntax).run(), vm);
}
}
void processProgramOptions(const boost::program_options::variables_map& vm, std::map<std::string,std::string>& mConfig)
{
if (vm.contains("version") && !vm.contains("verbose")) {
std::stringstream str;
str << mConfig["ExeName"] << " " << mConfig["ExeVersion"]
<< " Revision: " << mConfig["BuildRevision"] << '\n';
throw Base::ProgramInformation(str.str());
}
if (vm.contains("module-path")) {
auto Mods = vm["module-path"].as< std::vector<std::string> >();
std::string temp;
for (const auto & It : Mods)
temp += It + ";";
temp.erase(temp.end()-1);
mConfig["AdditionalModulePaths"] = temp;
}
if (vm.contains("macro-path")) {
std::vector<std::string> Macros = vm["macro-path"].as< std::vector<std::string> >();
std::string temp;
for (const auto & It : Macros)
temp += It + ";";
temp.erase(temp.end()-1);
mConfig["AdditionalMacroPaths"] = std::move(temp);
}
if (vm.contains("python-path")) {
auto Paths = vm["python-path"].as< std::vector<std::string> >();
for (const auto & It : Paths)
Base::Interpreter().addPythonPath(It.c_str());
}
if (vm.contains("disable-addon")) {
auto Addons = vm["disable-addon"].as< std::vector<std::string> >();
std::string temp;
for (const auto & It : Addons) {
temp += It + ";";
}
temp.erase(temp.end()-1);
mConfig["DisabledAddons"] = temp;
}
if (vm.contains("input-file")) {
auto files(vm["input-file"].as< std::vector<std::string> >());
int OpenFileCount=0;
for (const auto & It : files) {
std::ostringstream temp;
temp << "OpenFile" << OpenFileCount;
mConfig[temp.str()] = It;
OpenFileCount++;
}
std::ostringstream buffer;
buffer << OpenFileCount;
mConfig["OpenFileCount"] = buffer.str();
}
if (vm.contains("output")) {
mConfig["SaveFile"] = vm["output"].as<std::string>();
}
if (vm.contains("hidden")) {
mConfig["StartHidden"] = "1";
}
if (vm.contains("write-log")) {
mConfig["LoggingFile"] = "1";
mConfig["LoggingFileName"] = mConfig["UserAppData"] + mConfig["ExeName"] + ".log";
}
if (vm.contains("log-file")) {
mConfig["LoggingFile"] = "1";
mConfig["LoggingFileName"] = vm["log-file"].as<std::string>();
}
if (vm.contains("user-cfg")) {
mConfig["UserParameter"] = vm["user-cfg"].as<std::string>();
}
if (vm.contains("system-cfg")) {
mConfig["SystemParameter"] = vm["system-cfg"].as<std::string>();
}
if (vm.contains("run-test") || vm.contains("run-open")) {
std::string testCase = vm.contains("run-open") ? vm["run-open"].as<std::string>() : vm["run-test"].as<std::string>();
if ( "0" == testCase) {
testCase = "TestApp.All";
}
else if (testCase.empty()) {
testCase = "TestApp.PrintAll";
}
mConfig["TestCase"] = std::move(testCase);
mConfig["RunMode"] = "Internal";
mConfig["ScriptFileName"] = "FreeCADTest";
mConfig["ExitTests"] = vm.contains("run-open") ? "no" : "yes";
}
if (vm.contains("single-instance")) {
mConfig["SingleInstance"] = "1";
}
if (vm.contains("dump-config")) {
std::stringstream str;
for (const auto & it : mConfig) {
str << it.first << "=" << it.second << '\n';
}
throw Base::ProgramInformation(str.str());
}
if (vm.contains("get-config")) {
auto configKey = vm["get-config"].as<std::string>();
std::stringstream str;
std::map<std::string,std::string>::iterator pos;
pos = mConfig.find(configKey);
if (pos != mConfig.end()) {
str << pos->second;
}
str << '\n';
throw Base::ProgramInformation(str.str());
}
if (vm.contains("set-config")) {
auto configKeyValue = vm["set-config"].as< std::vector<std::string> >();
for (const auto& it : configKeyValue) {
auto pos = it.find('=');
if (pos != std::string::npos) {
std::string key = it.substr(0, pos);
std::string val = it.substr(pos + 1);
mConfig[key] = std::move(val);
}
}
}
}
}
// clang-format on
void Application::initConfig(int argc, char ** argv)
{
// find the home path....
mConfig["AppHomePath"] = ApplicationDirectories::findHomePath(argv[0]).string();
// Version of the application extracted from SubWCRef into src/Build/Version.h
// We only set these keys if not yet defined. Therefore it suffices to search
// only for 'BuildVersionMajor'.
if (Application::Config().find("BuildVersionMajor") == Application::Config().end()) {
std::stringstream str;
str << FCVersionMajor
<< "." << FCVersionMinor
<< "." << FCVersionPoint;
Application::Config()["ExeVersion" ] = str.str();
Application::Config()["BuildVersionMajor" ] = FCVersionMajor;
Application::Config()["BuildVersionMinor" ] = FCVersionMinor;
Application::Config()["BuildVersionPoint" ] = FCVersionPoint;
Application::Config()["BuildVersionSuffix" ] = FCVersionSuffix;
Application::Config()["BuildRevision" ] = FCRevision;
Application::Config()["BuildRepositoryURL" ] = FCRepositoryURL;
Application::Config()["BuildRevisionDate" ] = FCRevisionDate;
#if defined(FCRepositoryHash)
Application::Config()["BuildRevisionHash" ] = FCRepositoryHash;
#endif
#if defined(FCRepositoryBranch)
Application::Config()["BuildRevisionBranch"] = FCRepositoryBranch;
#endif
}
_argc = argc;
_argv = argv;
// Now it's time to read-in the file branding.xml if it exists
Branding brand;
QString binDir = QString::fromUtf8((mConfig["AppHomePath"] + "bin").c_str());
QFileInfo fi(binDir, QStringLiteral("branding.xml"));
if (fi.exists() && brand.readFile(fi.absoluteFilePath())) {
Branding::XmlConfig cfg = brand.getUserDefines();
for (Branding::XmlConfig::iterator it = cfg.begin(); it != cfg.end(); ++it) {
Application::Config()[it.key()] = it.value();
}
}
boost::program_options::variables_map vm;
{
BOOST_SCOPE_EXIT_ALL(&) {
// console-mode needs to be set (if possible) also in case parseProgramOptions
// throws, as it's needed when reporting such exceptions
if (vm.contains("console")) {
mConfig["Console"] = "1";
mConfig["RunMode"] = "Cmd";
}
};
parseProgramOptions(argc, argv, mConfig["ExeName"], vm);
}
if (vm.contains("keep-deprecated-paths")) {
mConfig["KeepDeprecatedPaths"] = "1";
}
if (vm.contains("safe-mode")) {
mConfig["SafeMode"] = "1";
}
// extract home paths
_appDirs = std::make_unique<ApplicationDirectories>(mConfig);
# ifdef FC_DEBUG
mConfig["Debug"] = "1";
# else
mConfig["Debug"] = "0";
# endif
if (!Py_IsInitialized()) {
// init python
PyImport_AppendInittab ("FreeCAD", init_freecad_module);
PyImport_AppendInittab ("__FreeCADBase__", init_freecad_base_module);
}
else {
// "import FreeCAD" in a normal Python 3.12 interpreter would raise
// Fatal Python error: PyImport_AppendInittab:
// PyImport_AppendInittab() may not be called after Py_Initialize()
// because the (external) interpreter is already initialized.
// Therefore we use a workaround as described in https://stackoverflow.com/a/57019607
PyObject* sysModules = PyImport_GetModuleDict();
auto moduleName = "FreeCAD";
PyImport_AddModule(moduleName);
ApplicationMethods = Application::Methods;
PyObject *pyModule = init_freecad_module();
PyDict_SetItemString(sysModules, moduleName, pyModule);
Py_DECREF(pyModule);
moduleName = "__FreeCADBase__";
PyImport_AddModule(moduleName);
pyModule = init_freecad_base_module();
PyDict_SetItemString(sysModules, moduleName, pyModule);
Py_DECREF(pyModule);
}
std::string pythonpath = Base::Interpreter().init(argc,argv);
if (!pythonpath.empty())
mConfig["PythonSearchPath"] = pythonpath;
else
Base::Console().warning("Encoding of Python paths failed\n");
// Handle the options that have impact on the init process
processProgramOptions(vm, mConfig);
// Init console ===========================================================
Base::PyGILStateLocker lock;
_pConsoleObserverStd = new Base::ConsoleObserverStd();
Base::Console().attachObserver(_pConsoleObserverStd);
if (mConfig["LoggingConsole"] != "1") {
_pConsoleObserverStd->bMsg = false;
_pConsoleObserverStd->bLog = false;
_pConsoleObserverStd->bWrn = false;
_pConsoleObserverStd->bErr = false;
}
// file logging Init ===========================================================
if (mConfig["LoggingFile"] == "1") {
_pConsoleObserverFile = new Base::ConsoleObserverFile(mConfig["LoggingFileName"].c_str());
Base::Console().attachObserver(_pConsoleObserverFile);
}
else
_pConsoleObserverFile = nullptr;
// Banner ===========================================================
if (mConfig["RunMode"] != "Cmd" && !(vm.contains("verbose") && vm.contains("version"))) {
// Remove banner if FreeCAD is invoked via the -c command as regular
// Python interpreter
if (mConfig["Verbose"] != "Strict")
Base::Console().message("%s %s, Libs: %s.%s.%s%sR%s\n%s",
mConfig["ExeName"].c_str(),
mConfig["ExeVersion"].c_str(),
mConfig["BuildVersionMajor"].c_str(),
mConfig["BuildVersionMinor"].c_str(),
mConfig["BuildVersionPoint"].c_str(),
mConfig["BuildVersionSuffix"].c_str(),
mConfig["BuildRevision"].c_str(),
mConfig["CopyrightInfo"].c_str());
else
Base::Console().message("%s %s, Libs: %s.%s.%s%sR%s\n",
mConfig["ExeName"].c_str(),
mConfig["ExeVersion"].c_str(),
mConfig["BuildVersionMajor"].c_str(),
mConfig["BuildVersionMinor"].c_str(),
mConfig["BuildVersionPoint"].c_str(),
mConfig["BuildVersionSuffix"].c_str(),
mConfig["BuildRevision"].c_str());
if (SafeMode::SafeModeEnabled()) {
Base::Console().message("FreeCAD is running in _SAFE_MODE_.\n"
"Safe mode temporarily disables your configurations and "
"addons. Restart the application to exit safe mode.\n\n");
}
}
LoadParameters();
auto loglevelParam = _pcUserParamMngr->GetGroup("BaseApp/LogLevels");
const auto &loglevels = loglevelParam->GetIntMap();
bool hasDefault = false;
for (const auto &v : loglevels) {
if (v.first == "Default") {
#ifndef FC_DEBUG
if (v.second>=0) {
hasDefault = true;
Base::Console().setDefaultLogLevel(v.second);
}
#endif
}
else if (v.first == "DebugDefault") {
#ifdef FC_DEBUG
if (v.second>=0) {
hasDefault = true;
Base::Console().setDefaultLogLevel(static_cast<int>(v.second));
}
#endif
}
else {
*Base::Console().getLogLevel(v.first.c_str()) = static_cast<int>(v.second);
}
}
if (!hasDefault) {
#ifdef FC_DEBUG
loglevelParam->SetInt("DebugDefault", Base::Console().logLevel(-1));
#else
loglevelParam->SetInt("Default", Base::Console().logLevel(-1));
#endif
}
// Change application tmp. directory
std::string tmpPath = _pcUserParamMngr->GetGroup("BaseApp/Preferences/General")->GetASCII("TempPath");
Base::FileInfo di(tmpPath);
if (di.exists() && di.isDir()) {
mConfig["AppTempPath"] = tmpPath + PATHSEP;
}
// capture python variables
SaveEnv("PYTHONPATH");
SaveEnv("PYTHONHOME");
SaveEnv("TCL_LIBRARY");
SaveEnv("TCLLIBPATH");
// capture CasCade variables
SaveEnv("CSF_MDTVFontDirectory");
SaveEnv("CSF_MDTVTexturesDirectory");
SaveEnv("CSF_UnitsDefinition");
SaveEnv("CSF_UnitsLexicon");
SaveEnv("CSF_StandardDefaults");
SaveEnv("CSF_PluginDefaults");
SaveEnv("CSF_LANGUAGE");
SaveEnv("CSF_SHMessage");
SaveEnv("CSF_XCAFDefaults");
SaveEnv("CSF_GraphicShr");
SaveEnv("CSF_IGESDefaults");
SaveEnv("CSF_STEPDefaults");
// capture path
SaveEnv("PATH");
// Save version numbers of the libraries
#ifdef OCC_VERSION_STRING_EXT
mConfig["OCC_VERSION"] = OCC_VERSION_STRING_EXT;
#endif
mConfig["BOOST_VERSION"] = BOOST_LIB_VERSION;
mConfig["PYTHON_VERSION"] = PY_VERSION;
mConfig["QT_VERSION"] = QT_VERSION_STR;
mConfig["EIGEN_VERSION"] = fcEigen3Version;
mConfig["PYSIDE_VERSION"] = fcPysideVersion;
#ifdef SMESH_VERSION_STR
mConfig["SMESH_VERSION"] = SMESH_VERSION_STR;
#endif
mConfig["XERCESC_VERSION"] = fcXercescVersion;
logStatus();
if (vm.contains("verbose") && vm.contains("version")) {
Application::_pcSingleton = new Application(mConfig);
throw Base::ProgramInformation(Application::verboseVersionEmitMessage);
}
}
void Application::SaveEnv(const char* s)
{
const char *c = getenv(s);
if (c)
mConfig[s] = c;
}
void Application::initApplication()
{
// interpreter and Init script ==========================================================
// register scripts
new Base::ScriptProducer( "CMakeVariables", CMakeVariables );
new Base::ScriptProducer( "FreeCADInit", FreeCADInit );
new Base::ScriptProducer( "FreeCADTest", FreeCADTest );
// creating the application
if (mConfig["Verbose"] != "Strict")
Base::Console().log("Create Application\n");
Application::_pcSingleton = new Application(mConfig);
// set up Unit system default
const ParameterGrp::handle hGrp = GetApplication().GetParameterGroupByPath
("User parameter:BaseApp/Preferences/Units");
Base::UnitsApi::setSchema(hGrp->GetInt("UserSchema", Base::UnitsApi::getDefSchemaNum()));
Base::UnitsApi::setDecimals(hGrp->GetInt("Decimals", Base::UnitsApi::getDecimals()));
Base::UnitsApi::setDenominator(hGrp->GetInt("FracInch", Base::UnitsApi::getDenominator()));
#if defined (_DEBUG)
Base::Console().log("Application is built with debug information\n");
#endif
// starting the init script
Base::Console().log("Run App init script\n");
try {
Base::Interpreter().runString(Base::ScriptFactory().ProduceScript("CMakeVariables"));
Base::Interpreter().runString(Base::ScriptFactory().ProduceScript("FreeCADInit"));
}
catch (const Base::Exception& e) {
e.reportException();
}
// seed randomizer
srand(time(nullptr));
}
std::list<std::string> Application::getCmdLineFiles()
{
std::list<std::string> files;
// cycling through all the open files
unsigned short count = 0;
count = atoi(mConfig["OpenFileCount"].c_str());
std::string File;
for (unsigned short i=0; i<count; i++) {
// getting file name
std::ostringstream temp;
temp << "OpenFile" << i;
files.emplace_back(mConfig[temp.str()]);
}
return files;
}
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 (const auto & it : files) {
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().addPythonPath(file.dirPath().c_str());
Base::Interpreter().loadModule(file.fileNamePure().c_str());
processed.push_back(it);
}
catch (const Base::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 = GetApplication().getImportModules(ext.c_str());
if (!mods.empty()) {
std::string escapedstr = Base::Tools::escapedUnicodeFromUtf8(file.filePath().c_str());
escapedstr = Base::Tools::escapeEncodeFilename(escapedstr);
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 if (file.exists()) {
Base::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) {
Base::Console().error("Exception while processing file: %s [%s]\n", file.filePath().c_str(), e.what());
}
catch (...) {
Base::Console().error("Unknown exception while processing file: %s \n", file.filePath().c_str());
}
}
return processed; // successfully processed files
}
void Application::processCmdLineFiles()
{
// process files passed to command line
const std::list<std::string> files = getCmdLineFiles();
const std::list<std::string> processed = processFiles(files);
if (files.empty()) {
if (mConfig["RunMode"] == "Exit")
mConfig["RunMode"] = "Cmd";
}
else if (processed.empty() && files.size() == 1 && mConfig["RunMode"] == "Cmd") {
// In case we are in console mode and the argument is not a file but Python code
// then execute it. This is to behave like the standard Python executable.
const Base::FileInfo file(files.front());
if (!file.exists()) {
Base::Interpreter().runString(files.front().c_str());
mConfig["RunMode"] = "Exit";
}
}
const std::map<std::string, std::string>& cfg = Application::Config();
const auto it = cfg.find("SaveFile");
if (it != cfg.end()) {
std::string output = it->second;
output = Base::Tools::escapeEncodeFilename(output);
const Base::FileInfo fi(output);
const std::string ext = fi.extension();
try {
const std::vector<std::string> mods = GetApplication().getExportModules(ext.c_str());
if (!mods.empty()) {
Base::Interpreter().loadModule(mods.front().c_str());
Base::Interpreter().runStringArg("import %s",mods.front().c_str());
Base::Interpreter().runStringArg("%s.export(App.ActiveDocument.Objects, '%s')"
,mods.front().c_str(),output.c_str());
}
else {
Base::Console().warning("File format not supported: %s \n", output.c_str());
}
}
catch (const Base::Exception& e) {
Base::Console().error("Exception while saving to file: %s [%s]\n", output.c_str(), e.what());
}
catch (...) {
Base::Console().error("Unknown exception while saving to file: %s \n", output.c_str());
}
}
}
void Application::runApplication()
{
// process all files given through command line interface
processCmdLineFiles();
if (mConfig["RunMode"] == "Cmd") {
// Run the commandline interface
Base::Interpreter().runCommandLine("FreeCAD Console mode");
}
else if (mConfig["RunMode"] == "Internal") {
// run internal script
Base::Console().log("Running internal script:\n");
Base::Interpreter().runString(Base::ScriptFactory().ProduceScript(mConfig["ScriptFileName"].c_str()));
}
else if (mConfig["RunMode"] == "Exit") {
// getting out
Base::Console().log("Exiting on purpose\n");
}
else {
Base::Console().log("Unknown Run mode (%d) in main()?!?\n\n", mConfig["RunMode"].c_str());
}
}
void Application::logStatus()
{
const std::string time_str = boost::posix_time::to_simple_string(
boost::posix_time::second_clock::local_time());
Base::Console().log("Time = %s\n", time_str.c_str());
for (const auto & It : mConfig) {
Base::Console().log("%s = %s\n", It.first.c_str(), It.second.c_str());
}
}
void Application::LoadParameters()
{
// Init parameter sets ===========================================================
//
if (mConfig.find("UserParameter") == mConfig.end())
mConfig["UserParameter"] = mConfig["UserConfigPath"] + "user.cfg";
if (mConfig.find("SystemParameter") == mConfig.end())
mConfig["SystemParameter"] = mConfig["UserConfigPath"] + "system.cfg";
// create standard parameter sets
_pcSysParamMngr = ParameterManager::Create();
_pcSysParamMngr->SetSerializer(new ParameterSerializer(mConfig["SystemParameter"]));
_pcUserParamMngr = ParameterManager::Create();
_pcUserParamMngr->SetSerializer(new ParameterSerializer(mConfig["UserParameter"]));
try {
if (_pcSysParamMngr->LoadOrCreateDocument() && mConfig["Verbose"] != "Strict") {
// Configuration file optional when using as Python module
if (!Py_IsInitialized()) {
Base::Console().warning(" Parameter does not exist, writing initial one\n");
Base::Console().message(" This warning normally means that FreeCAD is running for the first time\n"
" or the configuration was deleted or moved. FreeCAD is generating the standard\n"
" configuration.\n");
}
}
}
catch (const Base::Exception& e) {
// try to proceed with an empty XML document
Base::Console().error("%s in file %s.\n"
"Continue with an empty configuration.\n",
e.what(), mConfig["SystemParameter"].c_str());
_pcSysParamMngr->CreateDocument();
}
try {
if (_pcUserParamMngr->LoadOrCreateDocument() && mConfig["Verbose"] != "Strict") {
// The user parameter file doesn't exist. When an alternative parameter file is offered
// this will be used.
const auto it = mConfig.find("UserParameterTemplate");
if (it != mConfig.end()) {
QString path = QString::fromUtf8(it->second.c_str());
if (QDir(path).isRelative()) {
const QString home = QString::fromUtf8(mConfig["AppHomePath"].c_str());
path = QFileInfo(QDir(home), path).absoluteFilePath();
}
const QFileInfo fi(path);
if (fi.exists()) {
_pcUserParamMngr->LoadDocument(path.toUtf8().constData());
}
}
// Configuration file optional when using as Python module
if (!Py_IsInitialized()) {
Base::Console().warning(" User settings do not exist, writing initial one\n");
Base::Console().message(" This warning normally means that FreeCAD is running for the first time\n"
" or your configuration was deleted or moved. The system defaults\n"
" will be automatically generated for you.\n");
}
}
}
catch (const Base::Exception& e) {
// try to proceed with an empty XML document
Base::Console().error("%s in file %s.\n"
"Continue with an empty configuration.\n",
e.what(), mConfig["UserParameter"].c_str());
_pcUserParamMngr->CreateDocument();
}
}
#if defined(_MSC_VER) && BOOST_VERSION < 108200
// fix weird error while linking boost (all versions of VC)
// VS2010: https://forum.freecad.org/viewtopic.php?f=4&t=1886&p=12553&hilit=boost%3A%3Afilesystem%3A%3Aget#p12553
namespace boost { namespace program_options { std::string arg="arg"; } }
namespace boost { namespace program_options {
const unsigned options_description::m_default_line_length = 80;
} }
#endif
// A helper function to simplify the main part.
template<class T>
std::ostream& operator<<(std::ostream& os, const std::vector<T>& v)
{
copy(v.begin(), v.end(), std::ostream_iterator<T>(std::cout, " "));
return os;
}
namespace {
/*!
* \brief getUserHome
* Returns the user's home directory.
*/
QString getUserHome()
{
QString path;
#if defined(FC_OS_LINUX) || defined(FC_OS_CYGWIN) || defined(FC_OS_BSD) || defined(FC_OS_MACOSX)
// Default paths for the user specific stuff
struct passwd pwd;
struct passwd *result;
const std::size_t buflen = 16384;
std::vector<char> buffer(buflen);
const int error = getpwuid_r(getuid(), &pwd, buffer.data(), buffer.size(), &result);
Q_UNUSED(error)
if (!result)
throw Base::RuntimeError("Getting HOME path from system failed!");
path = QString::fromUtf8(result->pw_dir);
#else
path = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
#endif
return path;
}
/*!
* \brief getOldGenericDataLocation
* Returns a directory location where persistent data shared across applications can be stored.
* This method returns the old non-XDG-compliant root path where to store config files and application data.
*/
#if defined(FC_OS_WIN32)
QString getOldGenericDataLocation(QString home)
{
#if defined(FC_OS_WIN32)
WCHAR szPath[MAX_PATH];
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
if (SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_APPDATA, NULL, 0, szPath))) {
return QString::fromStdString(converter.to_bytes(szPath));
}
#elif defined(FC_OS_MACOSX)
QFileInfo fi(home, QStringLiteral("Library/Preferences"));
home = fi.absoluteFilePath();
#endif
return home;
}
#endif
/*!
* \brief getSubDirectories
* To a given path it adds the sub-directories where to store application specific files.
*/
void getSubDirectories(std::map<std::string,std::string>& mConfig, std::vector<std::string>& appData)
{
// If 'AppDataSkipVendor' is defined, the value of 'ExeVendor' must not be part of
// the path.
if (mConfig.find("AppDataSkipVendor") == mConfig.end()) {
appData.push_back(mConfig["ExeVendor"]);
}
appData.push_back(mConfig["ExeName"]);
}
/*!
* \brief getOldDataLocation
* To a given path it adds the sub-directories where to store application specific files.
* On Linux or BSD a hidden directory (i.e. starting with a dot) is added.
*/
void getOldDataLocation(std::map<std::string,std::string>& mConfig, std::vector<std::string>& appData)
{
// Actually the name of the directory where the parameters are stored should be the name of
// the application due to branding reasons.
#if defined(FC_OS_LINUX) || defined(FC_OS_CYGWIN) || defined(FC_OS_BSD)
// If 'AppDataSkipVendor' is defined, the value of 'ExeVendor' must not be part of
// the path.
if (mConfig.find("AppDataSkipVendor") == mConfig.end()) {
appData.push_back(std::string(".") + mConfig["ExeVendor"]);
appData.push_back(mConfig["ExeName"]);
} else {
appData.push_back(std::string(".") + mConfig["ExeName"]);
}
#elif defined(FC_OS_MACOSX) || defined(FC_OS_WIN32)
getSubDirectories(mConfig, appData);
#endif
}
/*!
* \brief findUserHomePath
* If the passed path name is not empty it will be returned, otherwise
* the user home path of the system will be returned.
*/
QString findUserHomePath(const QString& userHome)
{
return userHome.isEmpty() ? getUserHome() : userHome;
}
/*!
* \brief findPath
* Returns the path where to store application files to.
* If \a customHome is not empty it will be used, otherwise a path starting from \a stdHome will be used.
*/
std::filesystem::path findPath(const QString& stdHome, const QString& customHome,
const std::vector<std::string>& paths, bool create)
{
QString dataPath = customHome;
if (dataPath.isEmpty()) {
dataPath = stdHome;
}
std::filesystem::path appData(Base::FileInfo::stringToPath(dataPath.toStdString()));
// If a custom user home path is given then don't modify it
if (customHome.isEmpty()) {
for (const auto& it : paths)
appData = appData / it;
}
// In order to write to our data path, we must create some directories, first.
if (create && !std::filesystem::exists(appData) && !Py_IsInitialized()) {
try {
std::filesystem::create_directories(appData);
} catch (const std::filesystem::filesystem_error& e) {
throw Base::FileSystemError("Could not create directories. Failed with: " + e.code().message());
}
}
return appData;
}
/*!
* \brief getCustomPaths
* Returns a tuple of path names where to store config, data and temp. files.
* The method therefore reads the environment variables:
* \list
* \li FREECAD_USER_HOME
* \li FREECAD_USER_DATA
* \li FREECAD_USER_TEMP
* \endlist
*/
std::tuple<QString, QString, QString> getCustomPaths()
{
const QProcessEnvironment env(QProcessEnvironment::systemEnvironment());
QString userHome = env.value(QStringLiteral("FREECAD_USER_HOME"));
QString userData = env.value(QStringLiteral("FREECAD_USER_DATA"));
QString userTemp = env.value(QStringLiteral("FREECAD_USER_TEMP"));
auto toNativePath = [](QString& path) {
if (!path.isEmpty()) {
const QDir dir(path);
if (dir.exists())
path = QDir::toNativeSeparators(dir.canonicalPath());
else
path.clear();
}
};
// verify env. variables
toNativePath(userHome);
toNativePath(userData);
toNativePath(userTemp);
// if FREECAD_USER_HOME is set but not FREECAD_USER_DATA
if (!userHome.isEmpty() && userData.isEmpty()) {
userData = userHome;
}
// if FREECAD_USER_HOME is set but not FREECAD_USER_TEMP
if (!userHome.isEmpty() && userTemp.isEmpty()) {
const QDir dir(userHome);
dir.mkdir(QStringLiteral("temp"));
const QFileInfo fi(dir, QStringLiteral("temp"));
userTemp = fi.absoluteFilePath();
}
return {userHome, userData, userTemp};
}
/*!
* \brief getStandardPaths
* Returns a tuple of XDG-compliant standard paths names where to store config, data and cached files.
* The method therefore reads the environment variables:
* \list
* \li XDG_CONFIG_HOME
* \li XDG_DATA_HOME
* \li XDG_CACHE_HOME
* \endlist
*/
std::tuple<QString, QString, QString, QString> getStandardPaths()
{
QString configHome = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
QString dataHome = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation);
QString cacheHome = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation);
QString tempPath = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
// Keep the old behaviour
#if defined(FC_OS_WIN32)
configHome = getOldGenericDataLocation(QString());
dataHome = configHome;
// On systems with non-7-bit-ASCII application data directories
// GetTempPathW will return a path in DOS format. This path will be
// accepted by boost's file_lock class.
// Since boost 1.76 there is now a version that accepts a wide string.
#if (BOOST_VERSION < 107600)
tempPath = QString::fromStdString(Base::FileInfo::getTempPath());
cacheHome = tempPath;
#endif
#endif
return std::make_tuple(configHome, dataHome, cacheHome, tempPath);
}
}
void Application::ExtractUserPath()
{
bool keepDeprecatedPaths = mConfig.contains("KeepDeprecatedPaths");
// std paths
mConfig["BinPath"] = mConfig["AppHomePath"] + "bin" + PATHSEP;
mConfig["DocPath"] = mConfig["AppHomePath"] + "doc" + PATHSEP;
// this is to support a portable version of FreeCAD
auto paths = getCustomPaths();
QString customHome = std::get<0>(paths);
QString customData = std::get<1>(paths);
QString customTemp = std::get<2>(paths);
// get the system standard paths
auto stdPaths = getStandardPaths();
QString configHome = std::get<0>(stdPaths);
QString dataHome = std::get<1>(stdPaths);
QString cacheHome = std::get<2>(stdPaths);
QString tempPath = std::get<3>(stdPaths);
// User home path
//
QString homePath = findUserHomePath(customHome);
mConfig["UserHomePath"] = homePath.toUtf8().data();
// the old path name to save config and data files
std::vector<std::string> subdirs;
if (keepDeprecatedPaths) {
configHome = homePath;
dataHome = homePath;
cacheHome = homePath;
getOldDataLocation(mConfig, subdirs);
}
else {
getSubDirectories(mConfig, subdirs);
}
// User data path
//
std::filesystem::path data = findPath(dataHome, customData, subdirs, true);
mConfig["UserAppData"] = Base::FileInfo::pathToString(data) + PATHSEP;
// User config path
//
std::filesystem::path config = findPath(configHome, customHome, subdirs, true);
mConfig["UserConfigPath"] = Base::FileInfo::pathToString(config) + PATHSEP;
// User cache path
//
std::vector<std::string> cachedirs = subdirs;
cachedirs.emplace_back("Cache");
std::filesystem::path cache = findPath(cacheHome, customTemp, cachedirs, true);
mConfig["UserCachePath"] = Base::FileInfo::pathToString(cache) + PATHSEP;
// Set application tmp. directory
//
std::vector<std::string> empty;
std::filesystem::path tmp = findPath(tempPath, customTemp, empty, true);
mConfig["AppTempPath"] = Base::FileInfo::pathToString(tmp) + PATHSEP;
// Set the default macro directory
//
std::vector<std::string> macrodirs = std::move(subdirs); // Last use in this method, just move
macrodirs.emplace_back("Macro");
std::filesystem::path macro = findPath(dataHome, customData, macrodirs, true);
mConfig["UserMacroPath"] = Base::FileInfo::pathToString(macro) + PATHSEP;
}
// TODO: Consider using this for all UNIX-like OSes
#if defined(__OpenBSD__)
#include <cstdio>
#include <cstdlib>
#include <sys/param.h>
#include <QCoreApplication>
std::string Application::FindHomePath(const char* sCall)
{
// We have three ways to start this application either use one of the two executables or
// import the FreeCAD.so module from a running Python session. In the latter case the
// Python interpreter is already initialized.
std::string absPath;
std::string homePath;
if (Py_IsInitialized()) {
// Note: realpath is known to cause a buffer overflow because it
// expands the given path to an absolute path of unknown length.
// Even setting PATH_MAX does not necessarily solve the problem
// for sure but the risk of overflow is rather small.
char resolved[PATH_MAX];
char* path = realpath(sCall, resolved);
if (path)
absPath = path;
}
else {
int argc = 1;
QCoreApplication app(argc, (char**)(&sCall));
absPath = QCoreApplication::applicationFilePath().toStdString();
}
// should be an absolute path now
std::string::size_type pos = absPath.find_last_of("/");
homePath.assign(absPath,0,pos);
pos = homePath.find_last_of("/");
homePath.assign(homePath,0,pos+1);
return homePath;
}
#elif defined (FC_OS_LINUX) || defined(FC_OS_CYGWIN) || defined(FC_OS_BSD)
#include <cstdio>
#include <cstdlib>
#include <sys/param.h>
std::string Application::FindHomePath(const char* sCall)
{
// We have three ways to start this application either use one of the two executables or
// import the FreeCAD.so module from a running Python session. In the latter case the
// Python interpreter is already initialized.
std::string absPath;
std::string homePath;
if (Py_IsInitialized()) {
// Note: realpath is known to cause a buffer overflow because it
// expands the given path to an absolute path of unknown length.
// Even setting PATH_MAX does not necessarily solve the problem
// for sure but the risk of overflow is rather small.
char resolved[PATH_MAX];
char* path = realpath(sCall, resolved);
if (path)
absPath = path;
}
else {
// Find the path of the executable. Theoretically, there could occur a
// race condition when using readlink, but we only use this method to
// get the absolute path of the executable to compute the actual home
// path. In the worst case we simply get q wrong path and FreeCAD is not
// able to load its modules.
char resolved[PATH_MAX];
#if defined(FC_OS_BSD)
int mib[4];
mib[0] = CTL_KERN;
mib[1] = KERN_PROC;
mib[2] = KERN_PROC_PATHNAME;
mib[3] = -1;
size_t cb = sizeof(resolved);
sysctl(mib, 4, resolved, &cb, NULL, 0);
int nchars = strlen(resolved);
#else
int nchars = readlink("/proc/self/exe", resolved, PATH_MAX);
#endif
if (nchars < 0 || nchars >= PATH_MAX)
throw Base::FileSystemError("Cannot determine the absolute path of the executable");
resolved[nchars] = '\0'; // enforce null termination
absPath = resolved;
}
// should be an absolute path now
std::string::size_type pos = absPath.find_last_of("/");
homePath.assign(absPath,0,pos);
pos = homePath.find_last_of("/");
homePath.assign(homePath,0,pos+1);
return homePath;
}
#elif defined(FC_OS_MACOSX)
#include <mach-o/dyld.h>
#include <string>
#include <cstdlib>
#include <sys/param.h>
std::string Application::FindHomePath(const char* sCall)
{
// If Python is initialized at this point, then we're being run from
// MainPy.cpp, which hopefully rewrote argv[0] to point at the
// FreeCAD shared library.
if (!Py_IsInitialized()) {
uint32_t sz = 0;
// function only returns "sz" if first arg is to small to hold value
_NSGetExecutablePath(nullptr, &sz);
if (const auto buf = new char[++sz]; _NSGetExecutablePath(buf, &sz) == 0) {
char resolved[PATH_MAX];
const char* path = realpath(buf, resolved);
delete [] buf;
if (path) {
const std::string Call(resolved);
std::string TempHomePath;
std::string::size_type pos = Call.find_last_of(PATHSEP);
TempHomePath.assign(Call,0,pos);
pos = TempHomePath.find_last_of(PATHSEP);
TempHomePath.assign(TempHomePath,0,pos+1);
return TempHomePath;
}
} else {
delete [] buf;
}
}
return sCall;
}
#elif defined (FC_OS_WIN32)
std::string Application::FindHomePath(const char* sCall)
{
// We have several ways to start this application:
// * use one of the two executables
// * import the FreeCAD.pyd module from a running Python session. In this case the
// Python interpreter is already initialized.
// * use a custom dll that links FreeCAD core dlls and that is loaded by its host application
// In this case the calling name should be set to FreeCADBase.dll or FreeCADApp.dll in order
// to locate the correct home directory
wchar_t szFileName [MAX_PATH];
QString dll(QString::fromUtf8(sCall));
if (Py_IsInitialized() || dll.endsWith(QLatin1String(".dll"))) {
GetModuleFileNameW(GetModuleHandleA(sCall),szFileName, MAX_PATH-1);
}
else {
GetModuleFileNameW(0, szFileName, MAX_PATH-1);
}
std::wstring Call(szFileName), homePath;
std::wstring::size_type pos = Call.find_last_of(PATHSEP);
homePath.assign(Call,0,pos);
pos = homePath.find_last_of(PATHSEP);
homePath.assign(homePath,0,pos+1);
// fixes #0001638 to avoid to load DLLs from Windows' system directories before FreeCAD's bin folder
std::wstring binPath = homePath;
binPath += L"bin";
SetDllDirectoryW(binPath.c_str());
// https://stackoverflow.com/questions/5625884/conversion-of-stdwstring-to-qstring-throws-linker-error
#ifdef _MSC_VER
QString str = QString::fromUtf16(reinterpret_cast<const ushort *>(homePath.c_str()));
#else
QString str = QString::fromStdWString(homePath);
#endif
// convert to utf-8
return str.toUtf8().data();
}
#else
# error "std::string Application::FindHomePath(const char*) not implemented"
#endif
QString Application::prettyProductInfoWrapper()
{
auto productName = QSysInfo::prettyProductName();
#ifdef FC_OS_MACOSX
auto macosVersionFile =
QStringLiteral("/System/Library/CoreServices/.SystemVersionPlatform.plist");
auto fi = QFileInfo(macosVersionFile);
if (fi.exists() && fi.isReadable()) {
auto plistFile = QFile(macosVersionFile);
plistFile.open(QIODevice::ReadOnly);
while (!plistFile.atEnd()) {
auto line = plistFile.readLine();
if (line.contains("ProductUserVisibleVersion")) {
auto nextLine = plistFile.readLine();
if (nextLine.contains("<string>")) {
QRegularExpression re(QStringLiteral("\\s*<string>(.*)</string>"));
auto matches = re.match(QString::fromUtf8(nextLine));
if (matches.hasMatch()) {
productName = QStringLiteral("macOS ") + matches.captured(1);
break;
}
}
}
}
}
#endif
#ifdef FC_OS_WIN64
QSettings regKey {
QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"),
QSettings::NativeFormat};
if (regKey.contains(QStringLiteral("CurrentBuildNumber"))) {
auto buildNumber = regKey.value(QStringLiteral("CurrentBuildNumber")).toInt();
if (buildNumber > 0) {
if (buildNumber < 9200) {
productName = QStringLiteral("Windows 7 build %1").arg(buildNumber);
}
else if (buildNumber < 10240) {
productName = QStringLiteral("Windows 8 build %1").arg(buildNumber);
}
else if (buildNumber < 22000) {
productName = QStringLiteral("Windows 10 build %1").arg(buildNumber);
}
else {
productName = QStringLiteral("Windows 11 build %1").arg(buildNumber);
}
}
}
#endif
return productName;
}
void Application::addModuleInfo(QTextStream& str, const QString& modPath, bool& firstMod)
{
QFileInfo mod(modPath);
if (mod.isHidden()) { // Ignore hidden directories
return;
}
if (firstMod) {
firstMod = false;
str << "Installed mods: \n";
}
str << " * " << (mod.isDir() ? QDir(modPath).dirName() : mod.fileName());
try {
auto metadataFile =
std::filesystem::path(mod.absoluteFilePath().toStdString()) / "package.xml";
if (std::filesystem::exists(metadataFile)) {
App::Metadata metadata(metadataFile);
if (metadata.version() != App::Meta::Version()) {
str << QLatin1String(" ") + QString::fromStdString(metadata.version().str());
}
}
}
catch (const Base::Exception& e) {
auto what = QString::fromUtf8(e.what()).trimmed().replace(QChar::fromLatin1('\n'),
QChar::fromLatin1(' '));
str << " (Malformed metadata: " << what << ")";
}
QFileInfo disablingFile(mod.absoluteFilePath(), QStringLiteral("ADDON_DISABLED"));
if (disablingFile.exists()) {
str << " (Disabled)";
}
str << "\n";
}
QString Application::getValueOrEmpty(const std::map<std::string, std::string>& map, const std::string& key) {
auto it = map.find(key);
return (it != map.end()) ? QString::fromStdString(it->second) : QString();
}
void Application::getVerboseCommonInfo(QTextStream& str, const std::map<std::string,std::string>& mConfig)
{
std::map<std::string, std::string>::iterator it;
const QString deskEnv =
QProcessEnvironment::systemEnvironment().value(QStringLiteral("XDG_CURRENT_DESKTOP"),
QString());
const QString deskSess =
QProcessEnvironment::systemEnvironment().value(QStringLiteral("DESKTOP_SESSION"),
QString());
const QString major = getValueOrEmpty(mConfig, "BuildVersionMajor");
const QString minor = getValueOrEmpty(mConfig, "BuildVersionMinor");
const QString point = getValueOrEmpty(mConfig, "BuildVersionPoint");
const QString suffix = getValueOrEmpty(mConfig, "BuildVersionSuffix");
const QString build = getValueOrEmpty(mConfig, "BuildRevision");
const QString buildDate = getValueOrEmpty(mConfig, "BuildRevisionDate");
QStringList deskInfoList;
QString deskInfo;
if (!deskEnv.isEmpty()) {
deskInfoList.append(deskEnv);
}
if (!deskSess.isEmpty()) {
deskInfoList.append(deskSess);
}
const QString sysType = QSysInfo::productType();
if (sysType != QLatin1String("windows") && sysType != QLatin1String("macos")) {
QString sessionType = QProcessEnvironment::systemEnvironment().value(QStringLiteral("XDG_SESSION_TYPE"),
QString());
if (sessionType == QLatin1String("x11")) {
sessionType = QStringLiteral("xcb");
}
deskInfoList.append(sessionType);
}
if (!deskInfoList.isEmpty()) {
deskInfo = QLatin1String(" (") + deskInfoList.join(QLatin1String("/")) + QLatin1String(")");
}
str << "OS: " << prettyProductInfoWrapper() << deskInfo << '\n';
if (QSysInfo::buildCpuArchitecture() == QSysInfo::currentCpuArchitecture()) {
str << "Architecture: " << QSysInfo::buildCpuArchitecture() << "\n";
}
else {
str << "Architecture: " << QSysInfo::buildCpuArchitecture()
<< "(running on: " << QSysInfo::currentCpuArchitecture() << ")\n";
}
str << "Version: " << major << "." << minor << "." << point << suffix << "." << build;
#ifdef FC_CONDA
str << " Conda";
#endif
#ifdef FC_FLATPAK
str << " Flatpak";
#endif
const char* appimage = getenv("APPIMAGE");
if (appimage) {
str << " AppImage";
}
const char* snap = getenv("SNAP_REVISION");
if (snap) {
str << " Snap " << snap;
}
str << '\n';
str << "Build date: " << buildDate << "\n";
#if defined(_DEBUG) || defined(DEBUG)
str << "Build type: Debug\n";
#elif defined(NDEBUG)
str << "Build type: Release\n";
#elif defined(CMAKE_BUILD_TYPE)
str << "Build type: " << CMAKE_BUILD_TYPE << '\n';
#else
str << "Build type: Unknown\n";
#endif
const QString buildRevisionBranch = getValueOrEmpty(mConfig, "BuildRevisionBranch");
if (!buildRevisionBranch.isEmpty()) {
str << "Branch: " << buildRevisionBranch << '\n';
}
const QString buildRevisionHash = getValueOrEmpty(mConfig, "BuildRevisionHash");
if (!buildRevisionHash.isEmpty()) {
str << "Hash: " << buildRevisionHash << '\n';
}
// report also the version numbers of the most important libraries in FreeCAD
str << "Python " << PY_VERSION << ", ";
str << "Qt " << QT_VERSION_STR << ", ";
str << "Coin " << fcCoin3dVersion << ", ";
str << "Vtk " << fcVtkVersion << ", ";
str << "boost " << BOOST_LIB_VERSION << ", ";
str << "Eigen3 " << fcEigen3Version << ", ";
str << "PySide " << fcPysideVersion << '\n';
str << "shiboken " << fcShibokenVersion << ", ";
#ifdef SMESH_VERSION_STR
str << "SMESH " << SMESH_VERSION_STR << ", ";
#endif
str << "xerces-c " << fcXercescVersion << ", ";
const char* cmd = "import ifcopenshell\n"
"version = ifcopenshell.version";
PyObject * ifcopenshellVer = nullptr;
try {
ifcopenshellVer = Base::Interpreter().getValue(cmd, "version");
}
catch (const Base::Exception& e) {
Base::Console().log("%s (safe to ignore, unless using the BIM workbench and IFC).\n", e.what());
}
if (ifcopenshellVer) {
const char* ifcopenshellVerAsStr = PyUnicode_AsUTF8(ifcopenshellVer);
if (ifcopenshellVerAsStr) {
str << "IfcOpenShell " << ifcopenshellVerAsStr << ", ";
}
Py_DECREF(ifcopenshellVer);
}
#if defined(HAVE_OCC_VERSION)
str << "OCC " << OCC_VERSION_MAJOR << "." << OCC_VERSION_MINOR << "." << OCC_VERSION_MAINTENANCE
#ifdef OCC_VERSION_DEVELOPMENT
<< "." OCC_VERSION_DEVELOPMENT
#endif
<< '\n';
#endif
QLocale loc;
str << "Locale: " << QLocale::languageToString(loc.language()) << "/"
#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
<< QLocale::countryToString(loc.country())
#else
<< QLocale::territoryToString(loc.territory())
#endif
<< " (" << loc.name() << ")";
if (loc != QLocale::system()) {
loc = QLocale::system();
str << " [ OS: " << QLocale::languageToString(loc.language()) << "/"
#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
<< QLocale::countryToString(loc.country())
#else
<< QLocale::territoryToString(loc.territory())
#endif
<< " (" << loc.name() << ") ]";
}
str << "\n";
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/View");
QString navStyle = QString::fromStdString(hGrp->GetASCII("NavigationStyle", "Gui::CADNavigationStyle"));
// All navigation styles are named on the format "Gui::<Name>NavigationStyle"
// so we remove the "Gui::" prefix and the "NavigationStyle" suffix before printing.
navStyle.replace(QRegularExpression(QLatin1String("^Gui::")), {});
navStyle.replace(QRegularExpression(QLatin1String("NavigationStyle$")), {});
const QString orbitStyle = QStringLiteral("Turntable,Trackball,Free Turntable,Trackball Classic,Rounded Arcball")
.split(QLatin1Char(','))
.at(hGrp->GetInt("OrbitStyle", 4));
const QString rotMode = QStringLiteral("Window center,Drag at cursor,Object center")
.split(QLatin1Char(','))
.at(hGrp->GetInt("RotationMode", 0));
str << QStringLiteral("Navigation Style/Orbit Style/Rotation Mode: %1/%2/%3\n").arg(navStyle, orbitStyle, rotMode);
}
void Application::getVerboseAddOnsInfo(QTextStream& str, const std::map<std::string,std::string>& mConfig) {
// Add installed module information:
const auto modDir = fs::path(Application::getUserAppDataDir()) / "Mod";
bool firstMod = true;
if (fs::exists(modDir) && fs::is_directory(modDir)) {
for (const auto& mod : fs::directory_iterator(modDir)) {
if (!fs::is_directory(mod)) {
continue; // Ignore files, only show directories
}
auto dirName = mod.path().string();
addModuleInfo(str, QString::fromStdString(dirName), firstMod);
}
}
const QString additionalModules = getValueOrEmpty(mConfig, "AdditionalModulePaths");
if (!additionalModules.isEmpty()) {
auto mods = additionalModules.split(QChar::fromLatin1(';'));
for (const auto& mod : mods) {
addModuleInfo(str, mod, firstMod);
}
}
}