Files
create/src/App/Application.cpp
Zheng, Lei f4205130ae App: Property related API changes
Property:

* Extended property status bitset. Mirror most of PropertyType and
  allow dynamic change property type.

* Cache property name and type to improve performance

* Centralize property status change signalling

* Change aboutToSetValue()/hasSetValue() to virtual

* Add new API getFullName() to obtain full quanlified name of the property

AtomicPropertyChangeInterface:

* Allow calling aboutToSetValue()/hasSetValue() when actually changed

PropertyLists:

* Refactor implementation by an abstract class PropertyListBase and a
  template class PropertyListsT, to allow better code reuse.
  PropertyListT is derived from AtomicPropertyChangeInterface to allow
  more efficient change on individual elements.

* All list type property now accept setting python value as a dictionary
  with index as key to set individual element of a list.

* Add touch list for more efficient handling of value changes. The list
  contains the index of changed value. And empty touch list should be
  treated as the entire list is changed. PropertyContainerPy expose this
  functionality with getPropertyTouchList().

PropertyPersistentObject:

* New property to allow dynamic creation of any FreeCAD object derived
  from Base::Persistence, and use it as a property.

DynamicProperty:

* Use boost multi_index_container for efficient property lookup while
  keeping order.

* Modify to be allowed to use in PropertyContainer directly

PropertyContainer:

* Use boost multi_index_container for efficient property lookup while
  keeping order.

* Allow adding/removing dynamic property on all property container

* Modify Save/Restore() to persist property status, and better handle
  transient property which can now be dynamically enabled/disabled per
  object.

* Add new API getFullName() to obtain full quanlified name of the property.
  Implemented by Document, DocumentObject, and also
  ViewProviderDocumentObject if future patch

DocumentObject and FeaturePython are modified to accommondate the
dynamic property changes.

Removed get/setCustomAttribute() implementation from DocumentObjectPy,
and rely on PropertyContainerPy for the implementation, because of the
additional dynamic property support in property container.

Gui::ViewProviderDocumentObject, which is derived from
PropertyContainer, is also modified accordingly
2019-08-17 14:52:09 +02:00

2773 lines
100 KiB
C++

/***************************************************************************
* (c) Juergen Riegel (juergen.riegel@web.de) 2002 *
* *
* 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 *
* *
* Juergen Riegel 2002 *
***************************************************************************/
#include "PreCompiled.h"
#ifndef _PreComp_
# include <iostream>
# include <sstream>
# include <exception>
# include <ios>
# 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__)
# define WINVER 0x502 // needed for SetDllDirectory
# include <Windows.h>
# endif
# include <ctime>
# include <csignal>
# include <boost/program_options.hpp>
#endif
#ifdef FC_OS_WIN32
# include <Shlobj.h>
#endif
#if defined(FC_OS_BSD)
#include <sys/param.h>
#include <sys/sysctl.h>
#endif
#include "Application.h"
#include "Document.h"
// FreeCAD Base header
#include <Base/Interpreter.h>
#include <Base/Exception.h>
#include <Base/Parameter.h>
#include <Base/Console.h>
#include <Base/Factory.h>
#include <Base/ExceptionFactory.h>
#include <Base/FileInfo.h>
#include <Base/Type.h>
#include <Base/BaseClass.h>
#include <Base/Persistence.h>
#include <Base/Reader.h>
#include <Base/MatrixPy.h>
#include <Base/VectorPy.h>
#include <Base/AxisPy.h>
#include <Base/CoordinateSystemPy.h>
#include <Base/BoundBoxPy.h>
#include <Base/PlacementPy.h>
#include <Base/RotationPy.h>
#include <Base/Sequencer.h>
#include <Base/Tools.h>
#include <Base/Translate.h>
#include <Base/UnitsApi.h>
#include <Base/QuantityPy.h>
#include <Base/UnitPy.h>
#include "GeoFeature.h"
#include "FeatureTest.h"
#include "FeaturePython.h"
#include "ComplexGeoData.h"
#include "Property.h"
#include "PropertyContainer.h"
#include "PropertyUnits.h"
#include "PropertyFile.h"
#include "PropertyLinks.h"
#include "PropertyPythonObject.h"
#include "PropertyExpressionEngine.h"
#include "Document.h"
#include "DocumentObjectGroup.h"
#include "DocumentObjectFileIncluded.h"
#include "InventorObject.h"
#include "VRMLObject.h"
#include "Annotation.h"
#include "MeasureDistance.h"
#include "Placement.h"
#include "GeoFeatureGroupExtension.h"
#include "OriginGroupExtension.h"
#include "Part.h"
#include "OriginFeature.h"
#include "Origin.h"
#include "MaterialObject.h"
#include "TextDocument.h"
#include "Expression.h"
#include "Transactions.h"
#include <App/MaterialPy.h>
#include <Base/GeometryPyCXX.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"
#include <boost/tokenizer.hpp>
#include <boost/token_functions.hpp>
#include <boost/bind.hpp>
#include <boost/version.hpp>
#include <QDir>
#include <QFileInfo>
using namespace App;
using namespace std;
using namespace boost;
using namespace boost::program_options;
// scriptings (scripts are build in but can be overridden by command line option)
#include <App/InitScript.h>
#include <App/TestScript.h>
#include <App/CMakeScript.h>
#ifdef _MSC_VER // New handler for Microsoft Visual C++ compiler
# 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 Base::GetConsole;
using namespace Base;
using namespace App;
using namespace std;
/** Observer that watches relabeled objects and make sure that the labels inside
* a document are unique.
* @note In the FreeCAD design it is explicitly allowed to have duplicate labels
* (i.e. the user visible text e.g. in the tree view) while the internal names
* are always guaranteed to be unique.
*/
class ObjectLabelObserver
{
public:
/// The one and only instance.
static ObjectLabelObserver* instance();
/// Destructs the sole instance.
static void destruct ();
/** Checks the new label of the object and relabel it if needed
* to make it unique document-wide
*/
void slotRelabelObject(const App::DocumentObject&, const App::Property&);
private:
static ObjectLabelObserver* _singleton;
ObjectLabelObserver();
~ObjectLabelObserver();
const App::DocumentObject* current;
ParameterGrp::handle _hPGrp;
};
//==========================================================================
// Application
//==========================================================================
ParameterManager *App::Application::_pcSysParamMngr;
ParameterManager *App::Application::_pcUserParamMngr;
Base::ConsoleObserverStd *Application::_pConsoleObserverStd =0;
Base::ConsoleObserverFile *Application::_pConsoleObserverFile =0;
AppExport std::map<std::string,std::string> Application::mConfig;
BaseExport extern PyObject* Base::BaseExceptionFreeCADError;
BaseExport extern PyObject* Base::BaseExceptionFreeCADAbort;
//**************************************************************************
// Construction and destruction
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\n"
);
PyDoc_STRVAR(Base_doc,
"The Base module contains the classes for the geometric basics\n"
"like vector, matrix, bounding box, placement, rotation, axis, ...\n"
);
#if PY_MAJOR_VERSION >= 3
// 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,
NULL, NULL, NULL, NULL, NULL
};
return PyModule_Create(&BaseModuleDef);
}
// Set in inside Application
static PyMethodDef* __AppMethods = nullptr;
PyMODINIT_FUNC
init_freecad_module(void)
{
static struct PyModuleDef FreeCADModuleDef = {
PyModuleDef_HEAD_INIT,
"FreeCAD", FreeCAD_doc, -1,
__AppMethods,
NULL, NULL, NULL, NULL
};
return PyModule_Create(&FreeCADModuleDef);
}
#endif
Application::Application(std::map<std::string,std::string> &mConfig)
: _mConfig(mConfig), _pActiveDoc(0), _objCount(-1)
{
//_hApp = new ApplicationOCC;
mpcPramManager["System parameter"] = _pcSysParamMngr;
mpcPramManager["User parameter"] = _pcUserParamMngr;
// setting up Python binding
Base::PyGILStateLocker lock;
#if PY_MAJOR_VERSION >= 3
PyObject* modules = PyImport_GetModuleDict();
__AppMethods = Application::Methods;
PyObject* pAppModule = PyImport_ImportModule ("FreeCAD");
if (!pAppModule) {
PyErr_Clear();
pAppModule = init_freecad_module();
PyDict_SetItemString(modules, "FreeCAD", pAppModule);
}
#else
PyObject* pAppModule = Py_InitModule3("FreeCAD", Application::Methods, FreeCAD_doc);
#endif
Py::Module(pAppModule).setAttr(std::string("ActiveDocument"),Py::None());
#if PY_MAJOR_VERSION >= 3
static struct PyModuleDef ConsoleModuleDef = {
PyModuleDef_HEAD_INIT,
"__FreeCADConsole__", Console_doc, -1,
ConsoleSingleton::Methods,
NULL, NULL, NULL, NULL
};
PyObject* pConsoleModule = PyModule_Create(&ConsoleModuleDef);
#else
PyObject* pConsoleModule = Py_InitModule3("__FreeCADConsole__", ConsoleSingleton::Methods, Console_doc);
#endif
// 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::Interpreter().addType(&Base::VectorPy::Type, pAppModule, "Vector");
Base::Interpreter().addType(&Base::MatrixPy::Type, pAppModule, "Matrix");
Base::Interpreter().addType(&Base::BoundBoxPy::Type, pAppModule, "BoundBox");
Base::Interpreter().addType(&Base::PlacementPy::Type, pAppModule, "Placement");
Base::Interpreter().addType(&Base::RotationPy::Type, pAppModule, "Rotation");
Base::Interpreter().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.
#if PY_MAJOR_VERSION >= 3
PyObject* pBaseModule = PyImport_ImportModule ("__FreeCADBase__");
if (!pBaseModule) {
PyErr_Clear();
pBaseModule = init_freecad_base_module();
PyDict_SetItemString(modules, "__FreeCADBase__", pBaseModule);
}
#else
PyObject* pBaseModule = Py_InitModule3("__FreeCADBase__", NULL, Base_doc);
#endif
Base::BaseExceptionFreeCADError = PyErr_NewException("Base.FreeCADError", PyExc_RuntimeError, NULL);
Py_INCREF(Base::BaseExceptionFreeCADError);
PyModule_AddObject(pBaseModule, "FreeCADError", Base::BaseExceptionFreeCADError);
Base::BaseExceptionFreeCADAbort = PyErr_NewException("Base.FreeCADAbort", PyExc_BaseException, NULL);
Py_INCREF(Base::BaseExceptionFreeCADAbort);
PyModule_AddObject(pBaseModule, "FreeCADAbort", Base::BaseExceptionFreeCADAbort);
// Python types
Base::Interpreter().addType(&Base::VectorPy ::Type,pBaseModule,"Vector");
Base::Interpreter().addType(&Base::MatrixPy ::Type,pBaseModule,"Matrix");
Base::Interpreter().addType(&Base::BoundBoxPy ::Type,pBaseModule,"BoundBox");
Base::Interpreter().addType(&Base::PlacementPy ::Type,pBaseModule,"Placement");
Base::Interpreter().addType(&Base::RotationPy ::Type,pBaseModule,"Rotation");
Base::Interpreter().addType(&Base::AxisPy ::Type,pBaseModule,"Axis");
Base::Interpreter().addType(&Base::CoordinateSystemPy::Type,pBaseModule,"CoordinateSystem");
Base::Interpreter().addType(&App::MaterialPy::Type, pAppModule, "Material");
//insert Base and Console
Py_INCREF(pBaseModule);
PyModule_AddObject(pAppModule, "Base", pBaseModule);
Py_INCREF(pConsoleModule);
PyModule_AddObject(pAppModule, "Console", pConsoleModule);
// Translate module
PyObject* pTranslateModule = (new Base::Translate)->module().ptr();
Py_INCREF(pTranslateModule);
PyModule_AddObject(pAppModule, "Qt", pTranslateModule);
//insert Units module
#if PY_MAJOR_VERSION >= 3
static struct PyModuleDef UnitsModuleDef = {
PyModuleDef_HEAD_INIT,
"Units", "The Unit API", -1,
Base::UnitsApi::Methods,
NULL, NULL, NULL, NULL
};
PyObject* pUnitsModule = PyModule_Create(&UnitsModuleDef);
#else
PyObject* pUnitsModule = Py_InitModule3("Units", Base::UnitsApi::Methods,"The Unit API");
#endif
Base::Interpreter().addType(&Base::QuantityPy ::Type,pUnitsModule,"Quantity");
// make sure to set the 'nb_true_divide' slot
#if PY_MAJOR_VERSION < 3
Base::QuantityPy::Type.tp_as_number->nb_true_divide = Base::QuantityPy::Type.tp_as_number->nb_divide;
#endif
Base::Interpreter().addType(&Base::UnitPy ::Type,pUnitsModule,"Unit");
Py_INCREF(pUnitsModule);
PyModule_AddObject(pAppModule, "Units", pUnitsModule);
Base::ProgressIndicatorPy::init_type();
Base::Interpreter().addType(Base::ProgressIndicatorPy::type_object(),
pBaseModule,"ProgressIndicator");
Base::Vector2dPy::init_type();
Base::Interpreter().addType(Base::Vector2dPy::type_object(),
pBaseModule,"Vector2d");
}
Application::~Application()
{
}
//**************************************************************************
// Interface
/// get called by the document when the name is changing
void Application::renameDocument(const char *OldName, const char *NewName)
{
std::map<std::string,Document*>::iterator pos;
pos = DocMap.find(OldName);
if (pos != DocMap.end()) {
Document* temp;
temp = pos->second;
DocMap.erase(pos);
DocMap[NewName] = temp;
signalRenameDocument(*temp);
}
else {
throw Base::RuntimeError("Application::renameDocument(): no document with this name to rename!");
}
}
Document* Application::newDocument(const char * Name, const char * UserName)
{
// get a valid name anyway!
if (!Name || Name[0] == '\0')
Name = "Unnamed";
string name = getUniqueDocumentName(Name);
std::string userName;
if (UserName && UserName[0] != '\0') {
userName = UserName;
}
else {
userName = Name;
std::vector<std::string> names;
names.reserve(DocMap.size());
std::map<string,Document*>::const_iterator pos;
for (pos = DocMap.begin();pos != DocMap.end();++pos) {
names.push_back(pos->second->Label.getValue());
}
if (!names.empty())
userName = Base::Tools::getUniqueName(userName, names);
}
// create the FreeCAD document
std::unique_ptr<Document> newDoc(new Document(name.c_str()));
// add the document to the internal list
DocMap[name] = newDoc.release(); // now owned by the Application
_pActiveDoc = DocMap[name];
// connect the signals to the application for the new document
_pActiveDoc->signalBeforeChange.connect(boost::bind(&App::Application::slotBeforeChangeDocument, this, _1, _2));
_pActiveDoc->signalChanged.connect(boost::bind(&App::Application::slotChangedDocument, this, _1, _2));
_pActiveDoc->signalNewObject.connect(boost::bind(&App::Application::slotNewObject, this, _1));
_pActiveDoc->signalDeletedObject.connect(boost::bind(&App::Application::slotDeletedObject, this, _1));
_pActiveDoc->signalBeforeChangeObject.connect(boost::bind(&App::Application::slotBeforeChangeObject, this, _1, _2));
_pActiveDoc->signalChangedObject.connect(boost::bind(&App::Application::slotChangedObject, this, _1, _2));
_pActiveDoc->signalRelabelObject.connect(boost::bind(&App::Application::slotRelabelObject, this, _1));
_pActiveDoc->signalActivatedObject.connect(boost::bind(&App::Application::slotActivatedObject, this, _1));
_pActiveDoc->signalUndo.connect(boost::bind(&App::Application::slotUndoDocument, this, _1));
_pActiveDoc->signalRedo.connect(boost::bind(&App::Application::slotRedoDocument, this, _1));
_pActiveDoc->signalRecomputedObject.connect(boost::bind(&App::Application::slotRecomputedObject, this, _1));
_pActiveDoc->signalRecomputed.connect(boost::bind(&App::Application::slotRecomputed, this, _1));
_pActiveDoc->signalOpenTransaction.connect(boost::bind(&App::Application::slotOpenTransaction, this, _1, _2));
_pActiveDoc->signalCommitTransaction.connect(boost::bind(&App::Application::slotCommitTransaction, this, _1));
_pActiveDoc->signalAbortTransaction.connect(boost::bind(&App::Application::slotAbortTransaction, this, _1));
_pActiveDoc->signalStartSave.connect(boost::bind(&App::Application::slotStartSaveDocument, this, _1, _2));
_pActiveDoc->signalFinishSave.connect(boost::bind(&App::Application::slotFinishSaveDocument, this, _1, _2));
_pActiveDoc->signalChangePropertyEditor.connect(
boost::bind(&App::Application::slotChangePropertyEditor, this, _1, _2));
// make sure that the active document is set in case no GUI is up
{
Base::PyGILStateLocker lock;
Py::Object active(_pActiveDoc->getPyObject(), true);
Py::Module("FreeCAD").setAttr(std::string("ActiveDocument"),active);
}
signalNewDocument(*_pActiveDoc);
// set the UserName after notifying all observers
_pActiveDoc->Label.setValue(userName);
return _pActiveDoc;
}
bool Application::closeDocument(const char* name)
{
map<string,Document*>::iterator 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((Document*)0);
std::unique_ptr<Document> delDoc (pos->second);
DocMap.erase( pos );
_objCount = -1;
// Trigger observers after removing the document from the internal map.
signalDeletedDocument();
return true;
}
void Application::closeAllDocuments(void)
{
std::map<std::string,Document*>::iterator pos;
while((pos = DocMap.begin()) != DocMap.end())
closeDocument(pos->first.c_str());
}
App::Document* Application::getDocument(const char *Name) const
{
std::map<std::string,Document*>::const_iterator pos;
pos = DocMap.find(Name);
if (pos == DocMap.end())
return 0;
return pos->second;
}
const char * Application::getDocumentName(const App::Document* doc) const
{
for (std::map<std::string,Document*>::const_iterator it = DocMap.begin(); it != DocMap.end(); ++it)
if (it->second == doc)
return it->first.c_str();
return 0;
}
std::vector<App::Document*> Application::getDocuments() const
{
std::vector<App::Document*> docs;
for (std::map<std::string,Document*>::const_iterator it = DocMap.begin(); it != DocMap.end(); ++it)
docs.push_back(it->second);
return docs;
}
std::string Application::getUniqueDocumentName(const char *Name) const
{
if (!Name || *Name == '\0')
return std::string();
std::string CleanName = Base::Tools::getIdentifier(Name);
// name in use?
std::map<string,Document*>::const_iterator pos;
pos = DocMap.find(CleanName);
if (pos == DocMap.end()) {
// if not, name is OK
return CleanName;
}
else {
std::vector<std::string> names;
names.reserve(DocMap.size());
for (pos = DocMap.begin();pos != DocMap.end();++pos) {
names.push_back(pos->first);
}
return Base::Tools::getUniqueName(CleanName, names);
}
}
Document* Application::openDocument(const char * FileName)
{
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
std::string filepath = File.filePath();
for (std::map<std::string,Document*>::iterator it = DocMap.begin(); it != DocMap.end(); ++it) {
// get unique path separators
std::string fi = FileInfo(it->second->FileName.getValue()).filePath();
if (filepath == fi) {
std::stringstream str;
str << "The project '" << FileName << "' is already open!";
throw Base::FileSystemError(str.str().c_str());
}
}
// 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.
Document* newDoc = newDocument(File.fileNamePure().c_str(), File.fileNamePure().c_str());
newDoc->FileName.setValue(File.filePath());
try {
// read the document
newDoc->restore();
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(void) const
{
return _pActiveDoc;
}
void Application::setActiveDocument(Document* pDoc)
{
_pActiveDoc = pDoc;
// make sure that the active document is set in case no GUI is up
if (pDoc) {
Base::PyGILStateLocker lock;
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());
}
if (pDoc)
signalActiveDocument(*pDoc);
}
void Application::setActiveDocument(const char *Name)
{
// If no active document is set, resort to a default.
if (*Name == '\0') {
_pActiveDoc = 0;
return;
}
std::map<std::string,Document*>::iterator pos;
pos = DocMap.find(Name);
if (pos != DocMap.end()) {
setActiveDocument(pos->second);
}
else {
std::stringstream s;
s << "Try to activate unknown document '" << Name << "'";
throw Base::RuntimeError(s.str());
}
}
const char* Application::getHomePath(void) const
{
return _mConfig["AppHomePath"].c_str();
}
const char* Application::getExecutableName(void) const
{
return _mConfig["ExeName"].c_str();
}
std::string Application::getTempPath()
{
return mConfig["AppTempPath"];
}
std::string Application::getTempFileName(const char* FileName)
{
return Base::FileInfo::getTempFileName(FileName, getTempPath().c_str());
}
std::string Application::getUserAppDataDir()
{
return mConfig["UserAppData"];
}
std::string Application::getUserMacroDir()
{
std::string path("Macro/");
return mConfig["UserAppData"] + path;
}
std::string Application::getResourceDir()
{
#ifdef RESOURCEDIR
std::string path(RESOURCEDIR);
path.append("/");
QDir dir(QString::fromUtf8(RESOURCEDIR));
if (dir.isAbsolute())
return path;
else
return mConfig["AppHomePath"] + path;
#else
return mConfig["AppHomePath"];
#endif
}
std::string Application::getHelpDir()
{
#ifdef DOCDIR
std::string path(DOCDIR);
path.append("/");
QDir dir(QString::fromUtf8(DOCDIR));
if (dir.isAbsolute())
return path;
else
return mConfig["AppHomePath"] + path;
#else
return mConfig["DocPath"];
#endif
}
int Application::checkLinkDepth(int depth, bool no_throw) {
if(_objCount<0) {
_objCount = 0;
for(auto &v : DocMap)
_objCount += v.second->countObjects();
}
if(depth > _objCount+2) {
const char *msg = "Link recursion limit reached. "
"Please check for cyclic reference.";
if(no_throw) {
FC_ERR(msg);
return 0;
}else
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 && (int)links.size()>=maxCount)
break;
}
} else {
std::set<Document*> docs;
for(auto o : obj->getInList()) {
if(o && o->getNameInDocument() && docs.insert(o->getDocument()).second) {
o->getDocument()->getLinksTo(links,obj,options,maxCount);
if(maxCount && (int)links.size()>=maxCount)
break;
}
}
}
return links;
}
bool Application::hasLinksTo(const DocumentObject *obj) const {
return !getLinksTo(obj,0,1).empty();
}
ParameterManager & Application::GetSystemParameter(void)
{
return *_pcSysParamMngr;
}
ParameterManager & Application::GetUserParameter(void)
{
return *_pcUserParamMngr;
}
ParameterManager * Application::GetParameterSet(const char* sName) const
{
std::map<std::string,ParameterManager *>::const_iterator it = mpcPramManager.find(sName);
if ( it != mpcPramManager.end() )
return it->second;
else
return 0;
}
const std::map<std::string,ParameterManager *> & Application::GetParameterSetList(void) const
{
return mpcPramManager;
}
void Application::AddParameterSet(const char* sName)
{
std::map<std::string,ParameterManager *>::const_iterator it = mpcPramManager.find(sName);
if ( it != mpcPramManager.end() )
return;
mpcPramManager[sName] = new ParameterManager();
}
void Application::RemoveParameterSet(const char* sName)
{
std::map<std::string,ParameterManager *>::iterator it = mpcPramManager.find(sName);
// Must not delete user or system parameter
if ( it == mpcPramManager.end() || it->second == _pcUserParamMngr || it->second == _pcSysParamMngr )
return;
delete it->second;
mpcPramManager.erase(it);
}
Base::Reference<ParameterGrp> Application::GetParameterGroupByPath(const char* sName)
{
std::string cName = sName,cTemp;
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
std::map<std::string,ParameterManager *>::iterator It = mpcPramManager.find(cTemp.c_str());
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 ) {
std::string::size_type next = item.filter.find_first_of(" )", pos+1);
std::string::size_type len = next-pos-2;
std::string type = item.filter.substr(pos+2,len);
item.types.push_back(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 = AppName;
// put to the front of the array
_mImportTypes.insert(_mImportTypes.begin(),item);
}
else {
_mImportTypes.push_back(item);
}
}
std::vector<std::string> Application::getImportModules(const char* Type) const
{
std::vector<std::string> modules;
for (std::vector<FileTypeItem>::const_iterator it = _mImportTypes.begin(); it != _mImportTypes.end(); ++it) {
const std::vector<std::string>& types = it->types;
for (std::vector<std::string>::const_iterator jt = types.begin(); jt != types.end(); ++jt) {
#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;
for (std::vector<FileTypeItem>::const_iterator it = _mImportTypes.begin(); it != _mImportTypes.end(); ++it)
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 (std::vector<FileTypeItem>::const_iterator it = _mImportTypes.begin(); it != _mImportTypes.end(); ++it) {
#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(void) const
{
std::vector<std::string> types;
for (std::vector<FileTypeItem>::const_iterator it = _mImportTypes.begin(); it != _mImportTypes.end(); ++it) {
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 (std::vector<FileTypeItem>::const_iterator it = _mImportTypes.begin(); it != _mImportTypes.end(); ++it) {
const std::vector<std::string>& types = it->types;
for (std::vector<std::string>::const_iterator jt = types.begin(); jt != types.end(); ++jt) {
#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(void) const
{
std::map<std::string, std::string> filter;
for (std::vector<FileTypeItem>::const_iterator it = _mImportTypes.begin(); it != _mImportTypes.end(); ++it) {
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 ) {
std::string::size_type next = item.filter.find_first_of(" )", pos+1);
std::string::size_type len = next-pos-2;
std::string type = item.filter.substr(pos+2,len);
item.types.push_back(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 = AppName;
// put to the front of the array
_mExportTypes.insert(_mExportTypes.begin(),item);
}
else {
_mExportTypes.push_back(item);
}
}
std::vector<std::string> Application::getExportModules(const char* Type) const
{
std::vector<std::string> modules;
for (std::vector<FileTypeItem>::const_iterator it = _mExportTypes.begin(); it != _mExportTypes.end(); ++it) {
const std::vector<std::string>& types = it->types;
for (std::vector<std::string>::const_iterator jt = types.begin(); jt != types.end(); ++jt) {
#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;
for (std::vector<FileTypeItem>::const_iterator it = _mExportTypes.begin(); it != _mExportTypes.end(); ++it)
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 (std::vector<FileTypeItem>::const_iterator it = _mExportTypes.begin(); it != _mExportTypes.end(); ++it) {
#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(void) const
{
std::vector<std::string> types;
for (std::vector<FileTypeItem>::const_iterator it = _mExportTypes.begin(); it != _mExportTypes.end(); ++it) {
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 (std::vector<FileTypeItem>::const_iterator it = _mExportTypes.begin(); it != _mExportTypes.end(); ++it) {
const std::vector<std::string>& types = it->types;
for (std::vector<std::string>::const_iterator jt = types.begin(); jt != types.end(); ++jt) {
#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(void) const
{
std::map<std::string, std::string> filter;
for (std::vector<FileTypeItem>::const_iterator it = _mExportTypes.begin(); it != _mExportTypes.end(); ++it) {
filter[it->filter] = it->module;
}
return filter;
}
//**************************************************************************
// signaling
void Application::slotBeforeChangeDocument(const App::Document& doc, const Property& prop)
{
this->signalBeforeChangeDocument(doc, prop);
}
void Application::slotChangedDocument(const App::Document& doc, const Property& prop)
{
this->signalChangedDocument(doc, prop);
}
void Application::slotNewObject(const App::DocumentObject&O)
{
this->signalNewObject(O);
_objCount = -1;
}
void Application::slotDeletedObject(const App::DocumentObject&O)
{
this->signalDeletedObject(O);
_objCount = -1;
}
void Application::slotBeforeChangeObject(const DocumentObject& O, const Property& Prop)
{
this->signalBeforeChangeObject(O, Prop);
}
void Application::slotChangedObject(const App::DocumentObject&O, const App::Property& P)
{
this->signalChangedObject(O,P);
}
void Application::slotRelabelObject(const App::DocumentObject&O)
{
this->signalRelabelObject(O);
}
void Application::slotActivatedObject(const App::DocumentObject&O)
{
this->signalActivatedObject(O);
}
void Application::slotUndoDocument(const App::Document& d)
{
this->signalUndoDocument(d);
}
void Application::slotRedoDocument(const App::Document& d)
{
this->signalRedoDocument(d);
}
void Application::slotRecomputedObject(const DocumentObject& obj)
{
this->signalObjectRecomputed(obj);
}
void Application::slotRecomputed(const Document& doc)
{
this->signalRecomputed(doc);
}
void Application::slotOpenTransaction(const Document& d, string s)
{
this->signalOpenTransaction(d, s);
}
void Application::slotCommitTransaction(const Document& d)
{
this->signalCommitTransaction(d);
}
void Application::slotAbortTransaction(const Document& d)
{
this->signalAbortTransaction(d);
}
void Application::slotStartSaveDocument(const App::Document& doc, const std::string& filename)
{
this->signalStartSaveDocument(doc, filename);
}
void Application::slotFinishSaveDocument(const App::Document& doc, const std::string& filename)
{
this->signalFinishSaveDocument(doc, filename);
}
void Application::slotChangePropertyEditor(const App::Document &doc, const App::Property &prop)
{
this->signalChangePropertyEditor(doc,prop);
}
//**************************************************************************
// Init, Destruct and singleton
Application * Application::_pcSingleton = 0;
int Application::_argc;
char ** Application::_argv;
void Application::destruct(void)
{
// saving system parameter
Console().Log("Saving system parameter...\n");
_pcSysParamMngr->SaveDocument();
// saving the User parameter
Console().Log("Saving system parameter...done\n");
Console().Log("Saving user parameter...\n");
_pcUserParamMngr->SaveDocument();
Console().Log("Saving user parameter...done\n");
// now save all other parameter files
std::map<std::string,ParameterManager *>& paramMgr = _pcSingleton->mpcPramManager;
for (std::map<std::string,ParameterManager *>::iterator it = paramMgr.begin(); it != paramMgr.end(); ++it) {
if ((it->second != _pcSysParamMngr) && (it->second != _pcUserParamMngr)) {
if (it->second->HasSerializer()) {
Console().Log("Saving %s...\n", it->first.c_str());
it->second->SaveDocument();
Console().Log("Saving %s...done\n", it->first.c_str());
}
}
// clean up
delete it->second;
}
paramMgr.clear();
_pcSysParamMngr = 0;
_pcUserParamMngr = 0;
// 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();
ScriptFactorySingleton::Destruct();
InterpreterSingleton::Destruct();
Base::Type::destruct();
ParameterManager::Terminate();
}
void Application::destructObserver(void)
{
if ( _pConsoleObserverFile ) {
Console().DetachObserver(_pConsoleObserverFile);
delete _pConsoleObserverFile;
_pConsoleObserverFile = 0;
}
if ( _pConsoleObserverStd ) {
Console().DetachObserver(_pConsoleObserverStd);
delete _pConsoleObserverStd;
_pConsoleObserverStd = 0;
}
}
/** 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>
// This function produces a stack backtrace with demangled function & method names.
void printBacktrace(size_t skip=0)
{
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 = NULL;
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, NULL, 0, &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 << std::endl;
free(demangled);
}
else {
str << "#" << (i-skip) << " " << symbols[i] << std::endl;
}
// cannot directly print to cerr when using --write-log
std::cerr << str.str();
}
free(symbols);
}
#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);
exit(1);
#else
switch (sig) {
case SIGSEGV:
std::cerr << "Illegal storage access..." << std::endl;
#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..." << std::endl;
#if !defined(_DEBUG)
throw Base::AbnormalProgramTermination("Break signal occurred");
#endif
break;
default:
std::cerr << "Unknown error occurred..." << std::endl;
break;
}
#endif // FC_OS_LINUX
}
void unhandled_exception_handler()
{
std::cerr << "Terminating..." << std::endl;
}
void unexpection_error_handler()
{
std::cerr << "Unexpected error occurred..." << std::endl;
// 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:
//throw Base::DivisionByZeroError("Division 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);
std::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();
#if (BOOST_VERSION < 104600) || (BOOST_FILESYSTEM_VERSION == 2)
boost::filesystem::path::default_name_check(boost::filesystem::no_check);
#endif
initConfig(argc,argv);
initApplication();
}
catch (...) {
// force the log to flush
destructObserver();
throw;
}
}
void Application::initTypes(void)
{
// 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
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 ::PropertyAngle ::init();
App ::PropertyDistance ::init();
App ::PropertyLength ::init();
App ::PropertyArea ::init();
App ::PropertyVolume ::init();
App ::PropertySpeed ::init();
App ::PropertyAcceleration ::init();
App ::PropertyForce ::init();
App ::PropertyPressure ::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 ::PropertyLink ::init();
App ::PropertyLinkChild ::init();
App ::PropertyLinkGlobal ::init();
App ::PropertyLinkSub ::init();
App ::PropertyLinkSubChild ::init();
App ::PropertyLinkSubGlobal ::init();
App ::PropertyLinkList ::init();
App ::PropertyLinkListChild ::init();
App ::PropertyLinkListGlobal ::init();
App ::PropertyLinkSubList ::init();
App ::PropertyLinkSubListChild ::init();
App ::PropertyLinkSubListGlobal ::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 ::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 ::PropertyExpressionEngine ::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();
// Document classes
App ::TransactionalObject ::init();
App ::DocumentObject ::init();
App ::GeoFeature ::init();
App ::FeatureTest ::init();
App ::FeatureTestException ::init();
App ::FeaturePython ::init();
App ::GeometryPython ::init();
App ::Document ::init();
App ::DocumentObjectGroup ::init();
App ::DocumentObjectGroupPython ::init();
App ::DocumentObjectFileIncluded::init();
App ::InventorObject ::init();
App ::VRMLObject ::init();
App ::Annotation ::init();
App ::AnnotationLabel ::init();
App ::MeasureDistance ::init();
App ::MaterialObject ::init();
App ::MaterialObjectPython ::init();
App ::TextDocument ::init();
App ::Placement ::init();
App ::OriginFeature ::init();
App ::Plane ::init();
App ::Line ::init();
App ::Part ::init();
App ::Origin ::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 ::BooleanExpression ::init();
App ::RangeExpression ::init();
// register transaction type
new App::TransactionProducer<TransactionDocumentObject>
(DocumentObject::getClassTypeId());
// register exception producer types
new ExceptionProducer<Base::AbortException>;
new ExceptionProducer<Base::XMLBaseException>;
new ExceptionProducer<Base::XMLParseException>;
new ExceptionProducer<Base::XMLAttributeError>;
new ExceptionProducer<Base::FileException>;
new ExceptionProducer<Base::FileSystemError>;
new ExceptionProducer<Base::BadFormatError>;
new ExceptionProducer<Base::MemoryException>;
new ExceptionProducer<Base::AccessViolation>;
new ExceptionProducer<Base::AbnormalProgramTermination>;
new ExceptionProducer<Base::UnknownProgramOption>;
new ExceptionProducer<Base::ProgramInformation>;
new ExceptionProducer<Base::TypeError>;
new ExceptionProducer<Base::ValueError>;
new ExceptionProducer<Base::IndexError>;
new ExceptionProducer<Base::NameError>;
new ExceptionProducer<Base::ImportError>;
new ExceptionProducer<Base::AttributeError>;
new ExceptionProducer<Base::RuntimeError>;
new ExceptionProducer<Base::BadGraphError>;
new ExceptionProducer<Base::NotImplementedError>;
new ExceptionProducer<Base::DivisionByZeroError>;
new ExceptionProducer<Base::ReferencesError>;
new ExceptionProducer<Base::ExpressionError>;
new ExceptionProducer<Base::ParserError>;
new ExceptionProducer<Base::UnicodeError>;
new ExceptionProducer<Base::OverflowError>;
new ExceptionProducer<Base::UnderflowError>;
new ExceptionProducer<Base::UnitsMismatchError>;
new ExceptionProducer<Base::CADKernelError>;
new ExceptionProducer<Base::RestoreError>;
}
void Application::initConfig(int argc, char ** argv)
{
// find the home path....
mConfig["AppHomePath"] = FindHomePath(argv[0]);
// 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 (App::Application::Config().find("BuildVersionMajor") == App::Application::Config().end()) {
std::stringstream str; str << FCVersionMajor << "." << FCVersionMinor;
App::Application::Config()["ExeVersion" ] = str.str();
App::Application::Config()["BuildVersionMajor" ] = FCVersionMajor;
App::Application::Config()["BuildVersionMinor" ] = FCVersionMinor;
App::Application::Config()["BuildRevision" ] = FCRevision;
App::Application::Config()["BuildRepositoryURL" ] = FCRepositoryURL;
App::Application::Config()["BuildRevisionDate" ] = FCRevisionDate;
#if defined(FCRepositoryHash)
App::Application::Config()["BuildRevisionHash" ] = FCRepositoryHash;
#endif
#if defined(FCRepositoryBranch)
App::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, QString::fromLatin1("branding.xml"));
if (brand.readFile(fi.absoluteFilePath())) {
Branding::XmlConfig cfg = brand.getUserDefines();
for (Branding::XmlConfig::iterator it = cfg.begin(); it != cfg.end(); ++it) {
App::Application::Config()[it.key()] = it.value();
}
}
// extract home paths
ExtractUserPath();
# ifdef FC_DEBUG
mConfig["Debug"] = "1";
# else
mConfig["Debug"] = "0";
# endif
// init python
#if PY_MAJOR_VERSION >= 3
PyImport_AppendInittab ("FreeCAD", init_freecad_module);
PyImport_AppendInittab ("__FreeCADBase__", init_freecad_base_module);
#endif
const char* pythonpath = Interpreter().init(argc,argv);
if (pythonpath)
mConfig["PythonSearchPath"] = pythonpath;
else
Base::Console().Warning("Encoding of Python paths failed\n");
// Parse the options that have impact on the init process
ParseOptions(argc,argv);
// Init console ===========================================================
Base::PyGILStateLocker lock;
if (mConfig["LoggingConsole"] == "1") {
_pConsoleObserverStd = new ConsoleObserverStd();
Console().AttachObserver(_pConsoleObserverStd);
}
if (mConfig["Verbose"] == "Strict")
Console().UnsetConsoleMode(ConsoleSingleton::Verbose);
// file logging Init ===========================================================
if (mConfig["LoggingFile"] == "1") {
_pConsoleObserverFile = new ConsoleObserverFile(mConfig["LoggingFileName"].c_str());
Console().AttachObserver(_pConsoleObserverFile);
}
else
_pConsoleObserverFile = 0;
// Banner ===========================================================
if (!(mConfig["RunMode"] == "Cmd")) {
// Remove banner if FreeCAD is invoked via the -c command as regular
// Python interpreter
if (!(mConfig["Verbose"] == "Strict"))
Console().Message("%s %s, Libs: %s.%sR%s\n%s",mConfig["ExeName"].c_str(),
mConfig["ExeVersion"].c_str(),
mConfig["BuildVersionMajor"].c_str(),
mConfig["BuildVersionMinor"].c_str(),
mConfig["BuildRevision"].c_str(),
mConfig["CopyrightInfo"].c_str());
else
Console().Message("%s %s, Libs: %s.%sB%s\n",mConfig["ExeName"].c_str(),
mConfig["ExeVersion"].c_str(),
mConfig["BuildVersionMajor"].c_str(),
mConfig["BuildVersionMinor"].c_str(),
mConfig["BuildRevision"].c_str());
}
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(v.second);
}
#endif
}
else {
*Base::Console().GetLogLevel(v.first.c_str()) = v.second;
}
}
if (!hasDefault) {
#ifdef FC_DEBUG
loglevelParam->SetInt("DebugDefault", Base::Console().LogLevel(-1));
#else
loglevelParam->SetInt("Default", Base::Console().LogLevel(-1));
#endif
}
// Set application tmp. directory
mConfig["AppTempPath"] = Base::FileInfo::getTempPath();
std::string tmpPath = _pcUserParamMngr->GetGroup("BaseApp/Preferences/General")->GetASCII("TempPath");
Base::FileInfo di(tmpPath);
if (di.exists() && di.isDir()) {
mConfig["AppTempPath"] = tmpPath + "/";
}
// 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");
logStatus();
}
void Application::SaveEnv(const char* s)
{
char *c = getenv(s);
if (c)
mConfig[s] = c;
}
void Application::initApplication(void)
{
// interpreter and Init script ==========================================================
// register scripts
new ScriptProducer( "CMakeVariables", CMakeVariables );
new ScriptProducer( "FreeCADInit", FreeCADInit );
new ScriptProducer( "FreeCADTest", FreeCADTest );
// creating the application
if (!(mConfig["Verbose"] == "Strict")) Console().Log("Create Application\n");
Application::_pcSingleton = new Application(mConfig);
// set up Unit system default
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath
("User parameter:BaseApp/Preferences/Units");
UnitsApi::setSchema((UnitSystem)hGrp->GetInt("UserSchema",0));
UnitsApi::setDecimals(hGrp->GetInt("Decimals", Base::UnitsApi::getDecimals()));
// In case we are using fractional inches, get user setting for min unit
int denom = hGrp->GetInt("FracInch", Base::QuantityFormat::getDefaultDenominator());
Base::QuantityFormat::setDefaultDenominator(denom);
#if defined (_DEBUG)
Console().Log("Application is built with debug information\n");
#endif
// starting the init script
Console().Log("Run App init script\n");
try {
Interpreter().runString(Base::ScriptFactory().ProduceScript("CMakeVariables"));
Interpreter().runString(Base::ScriptFactory().ProduceScript("FreeCADInit"));
ObjectLabelObserver::instance();
}
catch (const Base::Exception& e) {
e.ReportException();
}
}
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;
std::string file(mConfig[temp.str()]);
files.push_back(file);
}
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 (std::list<std::string>::const_iterator it = files.begin(); it != files.end(); ++it) {
Base::FileInfo file(*it);
Base::Console().Log("Init: Processing file: %s\n",file.filePath().c_str());
try {
if (file.hasExtension("fcstd") || file.hasExtension("std")) {
// try to open
Application::_pcSingleton->openDocument(file.filePath().c_str());
processed.push_back(*it);
}
else if (file.hasExtension("fcscript") || file.hasExtension("fcmacro")) {
Base::Interpreter().runFile(file.filePath().c_str(), true);
processed.push_back(*it);
}
else if (file.hasExtension("py")) {
try{
Base::Interpreter().loadModule(file.fileNamePure().c_str());
processed.push_back(*it);
}
catch(const PyException&) {
// if loading the module does not work, try just running the script (run in __main__)
Base::Interpreter().runFile(file.filePath().c_str(),true);
processed.push_back(*it);
}
}
else {
std::string ext = file.extension();
std::vector<std::string> mods = App::GetApplication().getImportModules(ext.c_str());
if (!mods.empty()) {
std::string escapedstr = Base::Tools::escapedUnicodeFromUtf8(file.filePath().c_str());
Base::Interpreter().loadModule(mods.front().c_str());
Base::Interpreter().runStringArg("import %s",mods.front().c_str());
Base::Interpreter().runStringArg("%s.open(u\"%s\")",mods.front().c_str(),
escapedstr.c_str());
processed.push_back(*it);
Base::Console().Log("Command line open: %s.open(u\"%s\")\n",mods.front().c_str(),escapedstr.c_str());
}
else if (file.exists()) {
Console().Warning("File format not supported: %s \n", file.filePath().c_str());
}
}
}
catch (const Base::SystemExitException&) {
throw; // re-throw to main() function
}
catch (const Base::Exception& e) {
Console().Error("Exception while processing file: %s [%s]\n", file.filePath().c_str(), e.what());
}
catch (...) {
Console().Error("Unknown exception while processing file: %s \n", file.filePath().c_str());
}
}
return processed; // successfully processed files
}
void Application::processCmdLineFiles(void)
{
// process files passed to command line
std::list<std::string> files = getCmdLineFiles();
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.
Base::FileInfo file(files.front());
if (!file.exists()) {
Interpreter().runString(files.front().c_str());
mConfig["RunMode"] = "Exit";
}
}
const std::map<std::string,std::string>& cfg = Application::Config();
std::map<std::string,std::string>::const_iterator it = cfg.find("SaveFile");
if (it != cfg.end()) {
std::string output = it->second;
Base::FileInfo fi(output);
std::string ext = fi.extension();
try {
std::vector<std::string> mods = App::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 {
Console().Warning("File format not supported: %s \n", output.c_str());
}
}
catch (const Base::Exception& e) {
Console().Error("Exception while saving to file: %s [%s]\n", output.c_str(), e.what());
}
catch (...) {
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
Interpreter().runCommandLine("FreeCAD Console mode");
}
else if (mConfig["RunMode"] == "Internal") {
// run internal script
Console().Log("Running internal script:\n");
Interpreter().runString(Base::ScriptFactory().ProduceScript(mConfig["ScriptFileName"].c_str()));
}
else if (mConfig["RunMode"] == "Exit") {
// getting out
Console().Log("Exiting on purpose\n");
}
else {
Console().Log("Unknown Run mode (%d) in main()?!?\n\n",mConfig["RunMode"].c_str());
}
}
void Application::logStatus()
{
time_t now;
time(&now);
Console().Log("Time = %s", ctime(&now));
for (std::map<std::string,std::string>::iterator It = mConfig.begin();It!= mConfig.end();++It) {
Console().Log("%s = %s\n",It->first.c_str(),It->second.c_str());
}
}
void Application::LoadParameters(void)
{
// Init parameter sets ===========================================================
//
if (mConfig.find("UserParameter") == mConfig.end())
mConfig["UserParameter"] = mConfig["UserAppData"] + "user.cfg";
if (mConfig.find("SystemParameter") == mConfig.end())
mConfig["SystemParameter"] = mConfig["UserAppData"] + "system.cfg";
// create standard parameter sets
_pcSysParamMngr = new ParameterManager();
_pcSysParamMngr->SetSerializer(new ParameterSerializer(mConfig["SystemParameter"]));
_pcUserParamMngr = new ParameterManager();
_pcUserParamMngr->SetSerializer(new ParameterSerializer(mConfig["UserParameter"]));
try {
if (_pcSysParamMngr->LoadOrCreateDocument() && !(mConfig["Verbose"] == "Strict")) {
// Configuration file optional when using as Python module
if (!Py_IsInitialized()) {
Console().Warning(" Parameter does not exist, writing initial one\n");
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.
std::map<std::string, std::string>::iterator it = mConfig.find("UserParameterTemplate");
if (it != mConfig.end()) {
QString path = QString::fromUtf8(it->second.c_str());
if (QDir(path).isRelative()) {
QString home = QString::fromUtf8(mConfig["AppHomePath"].c_str());
path = QFileInfo(QDir(home), path).absoluteFilePath();
}
QFileInfo fi(path);
if (fi.exists()) {
_pcUserParamMngr->LoadDocument(path.toUtf8().constData());
}
}
// Configuration file optional when using as Python module
if (!Py_IsInitialized()) {
Console().Warning(" User settings do not exist, writing initial one\n");
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)
// fix weird error while linking boost (all versions of VC)
// VS2010: http://forum.freecadweb.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"; } }
#if (defined (BOOST_VERSION) && (BOOST_VERSION >= 104100))
namespace boost { namespace program_options {
const unsigned options_description::m_default_line_length = 80;
} }
#endif
#endif
#if 0 // it seems that SUSE has fixed the broken boost package
// reported for SUSE in issue #0000208
#if defined(__GNUC__)
#if BOOST_VERSION == 104400
namespace boost { namespace filesystem {
bool no_check( const std::string & ) { return true; }
} }
#endif
#endif
#endif
pair<string, string> customSyntax(const string& s)
{
#if defined(FC_OS_MACOSX)
if (s.find("-psn_") == 0)
return make_pair(string("psn"), s.substr(5));
#endif
if (s.find("-display") == 0)
return make_pair(string("display"), string("null"));
else if (s.find("-style") == 0)
return make_pair(string("style"), string("null"));
else if (s.find("-graphicssystem") == 0)
return make_pair(string("graphicssystem"), string("null"));
else if (s.find("-widgetcount") == 0)
return make_pair(string("widgetcount"), string(""));
else if (s.find("-geometry") == 0)
return make_pair(string("geometry"), string("null"));
else if (s.find("-font") == 0)
return make_pair(string("font"), string("null"));
else if (s.find("-fn") == 0)
return make_pair(string("fn"), string("null"));
else if (s.find("-background") == 0)
return make_pair(string("background"), string("null"));
else if (s.find("-bg") == 0)
return make_pair(string("bg"), string("null"));
else if (s.find("-foreground") == 0)
return make_pair(string("foreground"), string("null"));
else if (s.find("-fg") == 0)
return make_pair(string("fg"), string("null"));
else if (s.find("-button") == 0)
return make_pair(string("button"), string("null"));
else if (s.find("-btn") == 0)
return make_pair(string("btn"), string("null"));
else if (s.find("-name") == 0)
return make_pair(string("name"), string("null"));
else if (s.find("-title") == 0)
return make_pair(string("title"), string("null"));
else if (s.find("-visual") == 0)
return make_pair(string("visual"), string("null"));
// else if (s.find("-ncols") == 0)
// return make_pair(string("ncols"), boost::program_options::value<int>(1));
// else if (s.find("-cmap") == 0)
// return make_pair(string("cmap"), string("null"));
else if ('@' == s[0])
return std::make_pair(string("response-file"), s.substr(1));
else
return make_pair(string(), string());
}
// A helper function to simplify the main part.
template<class T>
ostream& operator<<(ostream& os, const vector<T>& v)
{
copy(v.begin(), v.end(), ostream_iterator<T>(cout, " "));
return os;
}
void Application::ParseOptions(int ac, char ** av)
{
// Declare a group of options that will be
// allowed only on the command line
options_description generic("Generic options");
generic.add_options()
("version,v", "Prints version string")
("help,h", "Prints help message")
("console,c", "Starts in console mode")
("response-file", value<string>(),"Can be specified with '@name', too")
("dump-config", "Dumps configuration")
("get-config", value<string>(), "Prints the value of the requested configuration key")
;
// Declare a group of options that will be
// allowed both on the command line and in
// the config file
std::string descr("Writes a log file to:\n");
descr += mConfig["UserAppData"];
descr += mConfig["ExeName"];
descr += ".log";
boost::program_options::options_description config("Configuration");
config.add_options()
//("write-log,l", value<string>(), "write a log file")
("write-log,l", descr.c_str())
("log-file", value<string>(), "Unlike --write-log this allows logging to an arbitrary file")
("user-cfg,u", value<string>(),"User config file to load/save user settings")
("system-cfg,s", value<string>(),"System config file to load/save system settings")
("run-test,t", value<string>() ,"Test case - or 0 for all")
("module-path,M", value< vector<string> >()->composing(),"Additional module paths")
("python-path,P", value< vector<string> >()->composing(),"Additional python paths")
("single-instance", "Allow to run a single instance of the application")
;
// 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< vector<string> >(), "input file")
("output", boost::program_options::value<string>(),"output file")
("hidden", "don't show the main window")
// this are to ignore for the window system (QApplication)
("style", boost::program_options::value< string >(), "set the application GUI style")
("stylesheet", boost::program_options::value< string >(), "set the application stylesheet")
("session", boost::program_options::value< 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< string >(), "backend to be used for on-screen widgets and pixmaps")
("display", boost::program_options::value< string >(), "set the X-Server")
("geometry ", boost::program_options::value< string >(), "set the X-Window geometry")
("font", boost::program_options::value< string >(), "set the X-Window font")
("fn", boost::program_options::value< string >(), "set the X-Window font")
("background", boost::program_options::value< string >(), "set the X-Window background color")
("bg", boost::program_options::value< string >(), "set the X-Window background color")
("foreground", boost::program_options::value< string >(), "set the X-Window foreground color")
("fg", boost::program_options::value< string >(), "set the X-Window foreground color")
("button", boost::program_options::value< string >(), "set the X-Window button color")
("btn", boost::program_options::value< string >(), "set the X-Window button color")
("name", boost::program_options::value< string >(), "set the X-Window name")
("title", boost::program_options::value< string >(), "set the X-Window title")
("visual", boost::program_options::value< 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< string >(), "process serial number")
#endif
;
// Ignored options, will be safely ignored. Mostly used by underlying libs.
//boost::program_options::options_description x11("X11 options");
//x11.add_options()
// ("display", boost::program_options::value< string >(), "set the X-Server")
// ;
//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.push_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
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);
variables_map vm;
try {
store( boost::program_options::command_line_parser(args).
options(cmdline_options).positional(p).extra_parser(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() << endl << endl << visible << endl;
throw UnknownProgramOption(str.str());
}
catch (...) {
std::stringstream str;
str << "Wrong or unknown option, bailing out!" << endl << endl << visible << endl;
throw UnknownProgramOption(str.str());
}
if (vm.count("help")) {
std::stringstream str;
str << mConfig["ExeName"] << endl << endl;
str << "For detailed description see http://www.freecadweb.org" << endl<<endl;
str << "Usage: " << mConfig["ExeName"] << " [options] File1 File2 ..." << endl << endl;
str << visible << endl;
throw Base::ProgramInformation(str.str());
}
if (vm.count("response-file")) {
// Load the file and tokenize it
std::ifstream ifs(vm["response-file"].as<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<string>() << "'" << endl;
throw Base::UnknownProgramOption(str.str());
}
// Read the whole file into a string
stringstream ss;
ss << ifs.rdbuf();
// Split the file content
char_separator<char> sep(" \n\r");
tokenizer<char_separator<char> > tok(ss.str(), sep);
vector<string> args;
copy(tok.begin(), tok.end(), back_inserter(args));
// Parse the file and store the options
store( boost::program_options::command_line_parser(args).
options(cmdline_options).positional(p).extra_parser(customSyntax).run(), vm);
}
if (vm.count("version")) {
std::stringstream str;
str << mConfig["ExeName"] << " " << mConfig["ExeVersion"]
<< " Revision: " << mConfig["BuildRevision"] << std::endl;
throw Base::ProgramInformation(str.str());
}
if (vm.count("console")) {
mConfig["Console"] = "1";
mConfig["RunMode"] = "Cmd";
}
if (vm.count("module-path")) {
vector<string> Mods = vm["module-path"].as< vector<string> >();
string temp;
for (vector<string>::const_iterator It= Mods.begin();It != Mods.end();++It)
temp += *It + ";";
temp.erase(temp.end()-1);
mConfig["AdditionalModulePaths"] = temp;
}
if (vm.count("python-path")) {
vector<string> Paths = vm["python-path"].as< vector<string> >();
for (vector<string>::const_iterator It= Paths.begin();It != Paths.end();++It)
Base::Interpreter().addPythonPath(It->c_str());
}
if (vm.count("input-file")) {
vector<string> files(vm["input-file"].as< vector<string> >());
int OpenFileCount=0;
for (vector<string>::const_iterator It = files.begin();It != files.end();++It) {
//cout << "Input files are: "
// << vm["input-file"].as< vector<string> >() << "\n";
std::ostringstream temp;
temp << "OpenFile" << OpenFileCount;
mConfig[temp.str()] = *It;
OpenFileCount++;
}
std::ostringstream buffer;
buffer << OpenFileCount;
mConfig["OpenFileCount"] = buffer.str();
}
if (vm.count("output")) {
string file = vm["output"].as<string>();
mConfig["SaveFile"] = file;
}
if (vm.count("hidden")) {
mConfig["StartHidden"] = "1";
}
if (vm.count("write-log")) {
mConfig["LoggingFile"] = "1";
//mConfig["LoggingFileName"] = vm["write-log"].as<string>();
mConfig["LoggingFileName"] = mConfig["UserAppData"] + mConfig["ExeName"] + ".log";
}
if (vm.count("log-file")) {
mConfig["LoggingFile"] = "1";
mConfig["LoggingFileName"] = vm["log-file"].as<string>();
}
if (vm.count("user-cfg")) {
mConfig["UserParameter"] = vm["user-cfg"].as<string>();
}
if (vm.count("system-cfg")) {
mConfig["SystemParameter"] = vm["system-cfg"].as<string>();
}
if (vm.count("run-test")) {
string testCase = vm["run-test"].as<string>();
if ( "0" == testCase) {
testCase = "TestApp.All";
}
mConfig["TestCase"] = testCase;
mConfig["RunMode"] = "Internal";
mConfig["ScriptFileName"] = "FreeCADTest";
//sScriptName = FreeCADTest;
}
if (vm.count("single-instance")) {
mConfig["SingleInstance"] = "1";
}
if (vm.count("dump-config")) {
std::stringstream str;
for (std::map<std::string,std::string>::iterator it=mConfig.begin(); it != mConfig.end(); ++it) {
str << it->first << "=" << it->second << std::endl;
}
throw Base::ProgramInformation(str.str());
}
if (vm.count("get-config")) {
std::string configKey = vm["get-config"].as<string>();
std::stringstream str;
std::map<std::string,std::string>::iterator pos;
pos = mConfig.find(configKey);
if (pos != mConfig.end()) {
str << pos->second;
}
str << std::endl;
throw Base::ProgramInformation(str.str());
}
}
void Application::ExtractUserPath()
{
// std paths
mConfig["BinPath"] = mConfig["AppHomePath"] + "bin" + PATHSEP;
mConfig["DocPath"] = mConfig["AppHomePath"] + "doc" + PATHSEP;
#if defined(FC_OS_LINUX) || defined(FC_OS_CYGWIN) || defined(FC_OS_BSD)
// Default paths for the user specific stuff
struct passwd *pwd = getpwuid(getuid());
if (pwd == NULL)
throw Base::RuntimeError("Getting HOME path from system failed!");
mConfig["UserHomePath"] = pwd->pw_dir;
char *path = pwd->pw_dir;
char *fc_user_data;
if ((fc_user_data = getenv("FREECAD_USER_DATA"))) {
QString env = QString::fromUtf8(fc_user_data);
QDir dir(env);
if (!env.isEmpty() && dir.exists())
path = fc_user_data;
}
std::string appData(path);
Base::FileInfo fi(appData.c_str());
if (!fi.exists()) {
// This should never ever happen
std::stringstream str;
str << "Application data directory " << appData << " does not exist!";
throw Base::FileSystemError(str.str());
}
// In order to write into our data path, we must create some directories, first.
// If 'AppDataSkipVendor' is defined, the value of 'ExeVendor' must not be part of
// the path.
appData += PATHSEP;
appData += ".";
if (mConfig.find("AppDataSkipVendor") == mConfig.end()) {
appData += mConfig["ExeVendor"];
fi.setFile(appData.c_str());
if (!fi.exists() && !Py_IsInitialized()) {
if (!fi.createDirectory()) {
std::string error = "Cannot create directory ";
error += appData;
// Want more details on console
std::cerr << error << std::endl;
throw Base::FileSystemError(error);
}
}
appData += PATHSEP;
}
appData += mConfig["ExeName"];
fi.setFile(appData.c_str());
if (!fi.exists() && !Py_IsInitialized()) {
if (!fi.createDirectory()) {
std::string error = "Cannot create directory ";
error += appData;
// Want more details on console
std::cerr << error << std::endl;
throw Base::FileSystemError(error);
}
}
// Actually the name of the directory where the parameters are stored should be the name of
// the application due to branding reasons.
appData += PATHSEP;
mConfig["UserAppData"] = appData;
#elif defined(FC_OS_MACOSX)
// Default paths for the user specific stuff on the platform
struct passwd *pwd = getpwuid(getuid());
if (pwd == NULL)
throw Base::RuntimeError("Getting HOME path from system failed!");
mConfig["UserHomePath"] = pwd->pw_dir;
std::string appData = pwd->pw_dir;
appData += PATHSEP;
appData += "Library";
appData += PATHSEP;
appData += "Preferences";
Base::FileInfo fi(appData.c_str());
if (!fi.exists()) {
// This should never ever happen
std::stringstream str;
str << "Application data directory " << appData << " does not exist!";
throw Base::FileSystemError(str.str());
}
// In order to write to our data path, we must create some directories, first.
// If 'AppDataSkipVendor' is defined the value of 'ExeVendor' must not be part of
// the path.
appData += PATHSEP;
if (mConfig.find("AppDataSkipVendor") == mConfig.end()) {
appData += mConfig["ExeVendor"];
fi.setFile(appData.c_str());
if (!fi.exists() && !Py_IsInitialized()) {
if (!fi.createDirectory()) {
std::string error = "Cannot create directory ";
error += appData;
// Want more details on console
std::cerr << error << std::endl;
throw Base::FileSystemError(error);
}
}
appData += PATHSEP;
}
appData += mConfig["ExeName"];
fi.setFile(appData.c_str());
if (!fi.exists() && !Py_IsInitialized()) {
if (!fi.createDirectory()) {
std::string error = "Cannot create directory ";
error += appData;
// Want more details on console
std::cerr << error << std::endl;
throw Base::FileSystemError(error);
}
}
// Actually the name of the directory where the parameters are stored should be the name of
// the application due to branding reasons.
appData += PATHSEP;
mConfig["UserAppData"] = appData;
#elif defined(FC_OS_WIN32)
WCHAR szPath[MAX_PATH];
char dest[MAX_PATH*3];
// Get the default path where we can save our documents. It seems that
// 'CSIDL_MYDOCUMENTS' doesn't work on all machines, so we use 'CSIDL_PERSONAL'
// which does the same.
if (SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_PERSONAL, NULL, 0, szPath))) {
WideCharToMultiByte(CP_UTF8, 0, szPath, -1,dest, 256, NULL, NULL);
mConfig["UserHomePath"] = dest;
}
else
mConfig["UserHomePath"] = mConfig["AppHomePath"];
// In the second step we want the directory where user settings of the application can be
// kept. There we create a directory with name of the vendor and a sub-directory with name
// of the application.
if (SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_APPDATA, NULL, 0, szPath))) {
// convert to UTF8
WideCharToMultiByte(CP_UTF8, 0, szPath, -1,dest, 256, NULL, NULL);
std::string appData = dest;
Base::FileInfo fi(appData.c_str());
if (!fi.exists()) {
// This should never ever happen
std::stringstream str;
str << "Application data directory " << appData << " does not exist!";
throw Base::FileSystemError(str.str());
}
// In order to write to our data path we must create some directories first.
// If 'AppDataSkipVendor' is defined the value of 'ExeVendor' must not be part of
// the path.
if (mConfig.find("AppDataSkipVendor") == mConfig.end()) {
appData += PATHSEP;
appData += mConfig["ExeVendor"];
fi.setFile(appData.c_str());
if (!fi.exists() && !Py_IsInitialized()) {
if (!fi.createDirectory()) {
std::string error = "Cannot create directory ";
error += appData;
// Want more details on console
std::cerr << error << std::endl;
throw Base::FileSystemError(error);
}
}
}
appData += PATHSEP;
appData += mConfig["ExeName"];
fi.setFile(appData.c_str());
if (!fi.exists() && !Py_IsInitialized()) {
if (!fi.createDirectory()) {
std::string error = "Cannot create directory ";
error += appData;
// Want more details on console
std::cerr << error << std::endl;
throw Base::FileSystemError(error);
}
}
// Actually the name of the directory where the parameters are stored should be the name of
// the application due to branding reasons.
appData += PATHSEP;
mConfig["UserAppData"] = appData;
// Create the default macro directory
fi.setFile(getUserMacroDir());
if (!fi.exists() && !Py_IsInitialized()) {
if (!fi.createDirectory()) {
// If the creation fails only write an error but do not raise an
// exception because it doesn't prevent FreeCAD from working
std::string error = "Cannot create directory ";
error += fi.fileName();
// Want more details on console
std::cerr << error << std::endl;
}
}
}
#else
# error "Implement ExtractUserPath() for your platform."
#endif
}
#if defined (FC_OS_LINUX) || defined(FC_OS_CYGWIN) || defined(FC_OS_BSD)
#include <stdio.h>
#include <stdlib.h>
#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 <stdlib.h>
#include <sys/param.h>
std::string Application::FindHomePath(const char* call)
{
// 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;
char *buf;
_NSGetExecutablePath(NULL, &sz); //function only returns "sz" if first arg is to small to hold value
buf = new char[++sz];
if (_NSGetExecutablePath(buf, &sz) == 0) {
char resolved[PATH_MAX];
char* path = realpath(buf, resolved);
delete [] buf;
if (path) {
std::string Call(resolved), 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 call;
}
#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);
// switch to posix style
for (std::wstring::iterator it = homePath.begin(); it != homePath.end(); ++it) {
if (*it == '\\')
*it = '/';
}
// 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());
// http://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
ObjectLabelObserver* ObjectLabelObserver::_singleton = 0;
ObjectLabelObserver* ObjectLabelObserver::instance()
{
if (!_singleton)
_singleton = new ObjectLabelObserver;
return _singleton;
}
void ObjectLabelObserver::destruct ()
{
delete _singleton;
_singleton = 0;
}
void ObjectLabelObserver::slotRelabelObject(const App::DocumentObject& obj, const App::Property& prop)
{
// observe only the Label property
if (&prop == &obj.Label) {
// have we processed this (or another?) object right now?
if (current) {
return;
}
std::string label = obj.Label.getValue();
App::Document* doc = obj.getDocument();
if (doc && !_hPGrp->GetBool("DuplicateLabels")) {
std::vector<std::string> objectLabels;
std::vector<App::DocumentObject*>::const_iterator it;
std::vector<App::DocumentObject*> objs = doc->getObjects();
bool match = false;
for (it = objs.begin();it != objs.end();++it) {
if (*it == &obj)
continue; // don't compare object with itself
std::string objLabel = (*it)->Label.getValue();
if (!match && objLabel == label)
match = true;
objectLabels.push_back(objLabel);
}
// make sure that there is a name conflict otherwise we don't have to do anything
if (match && !label.empty()) {
// remove number from end to avoid lengthy names
size_t lastpos = label.length()-1;
while (label[lastpos] >= 48 && label[lastpos] <= 57) {
// if 'lastpos' becomes 0 then all characters are digits. In this case we use
// the complete label again
if (lastpos == 0) {
lastpos = label.length()-1;
break;
}
lastpos--;
}
label = label.substr(0, lastpos+1);
label = Base::Tools::getUniqueName(label, objectLabels, 3);
this->current = &obj;
const_cast<App::DocumentObject&>(obj).Label.setValue(label);
this->current = 0;
}
}
}
}
ObjectLabelObserver::ObjectLabelObserver() : current(0)
{
App::GetApplication().signalChangedObject.connect(boost::bind
(&ObjectLabelObserver::slotRelabelObject, this, _1, _2));
_hPGrp = App::GetApplication().GetUserParameter().GetGroup("BaseApp");
_hPGrp = _hPGrp->GetGroup("Preferences")->GetGroup("Document");
}
ObjectLabelObserver::~ObjectLabelObserver()
{
}