FreeCAD Mod Dev Guide 20190912
FreeCAD Mod Dev Guide 20190912
1
Module developer’s guide to FreeCAD source code
by Qingfeng Xia 2016~
by Luzpaz 2019~
download the latest version from pdf folder of this repo
Changelog
This book should be updated for the recent release, esp. after migration to
Python3 + Pyside2. I plan another release for FreeCAD 0.19 dev near Xmas
time.
Original/lead developers:
• Jürgen Riegel
• Werner Mayer
• yorik van havre
Add all contributors see [Link]
1
Target audiences: new module developers
Make sure you are familiar with FreeCAD workbench GUI and API as a user:
• Foundamental document on official wiki for FreeCAD
• FreeCAD python API document
• single file PDF user manual for quick start
2
Acknowledgement to my family
3
Contents
1
2 CONTENTS
First of all, thanks to the original developers (Jürgen Riegel, Werner Mayer, Yorik van Havre), for sharing this great
artwork freely. FreeCAD is released under LGPL license, free for commercial usage with dynamic linkage.
The birth of FreeCAD: version 0.0.1 October 29, 2002 Initial Upload
wikipedia of FreeCAD
FreeCAD is basically a collage of different powerful libraries, the most important being openCascade, for managing
and constructing geometry, Coin3D to display that geometry, Qt to put all this in a nice Graphical User Interface,
and Python to give full scripting/macro functions.
7
8 CHAPTER 1. FREECAD OVERVIEW AND ARCHITECTURE
• python scripting in console mode and python-based macro recording in GUI mode
• all FreeCAD class is derived from this BaseClass, connected with BaseClassPy
• c++11 is not extensively used before 0.17
• c++ template is not heavily used, but FeatureT<> make DocumentObject, ViewProvider extensible in Python
• FreeCAD not tied to Qt system until GUI, Boost::signal is used in command line mode: FreeCADCmd
• std::string(UTF8) is used internally, using QString getString(){QString.fromUtf8(s.c_str())}
• C++ for most of time consuming task (threading model), to avoid bottleneck of Global Interpreter Lock
Mixing C++ and Python in module development will be discussed in Chapter 5.
[Link]
Current FreeCAD policy is to include only LGPL software and no GPL by default. Mentioned DXF import-export libraries
were downloaded by default. On DXF import-export operation in the past but Debian didn’t like that and FreeCAD changed
in a way user has to manually enable (Opt-In) the download.
Open Draft workbench and after that select Edit -> Preferences. Under Import-Export -> DXF / DWG tab, enable Automatic
update. After that FreeCAD will download mentioned libraries on first DXF import-export operation and it should work. If it
does not work restart FreeCAD and try again.
The geometry that appears in the 3D views of FreeCAD are rendered by the Coin3D library. Coin3D is an implementation of
the OpenInventor standard, which exempt you from OpenGL coding.
FreeCAD itself features several tools to see or modify openInventor code. For example, the following Python code will show
the openInventor representation of a selected object:
obj = [Link]
viewprovider = [Link]
print [Link]()
pivy is Python wrapper of Coin3D C++ lib, via SWIG A new SoPyScript Node is added to include Python script directly
OpenCASCADE, as a CAD kernel, did not render 3D object to screen (when FreeCAD was born in 2002) until recently
release. Currently, there are several 3D lib based on OpenGL, see a list that works with QT [Link]
engines_with_Qt. 3D gaming engines can also been used to render 3D objects, such as OGRE(Object-Oriented Graphics
Rendering Engine), Unreal, Unity.
Selection of Open Inventor to render FreeCAD is based on software license and performance consideration. Open Inventor,
originally IRIS Inventor, is a C++ object oriented retained mode 3D graphics API designed by SGI to provide a higher layer
of programming for OpenGL. Its main goals are better programmer convenience and efficiency. Open Inventor is free and
open-source software, subject to the requirements of the GNU Lesser General Public License (LGPL), version 2.1, in Aug 2000.
Coin3D implements the same API but not source code with Open Inventor, via clean room implementation comapatible
Stable release Open Inventor v2.1. Kongsberg ended development of Coin3D in 2011 and released the code under the BSD
3-clause license. It is possible to draw object in OpenInventor Scene by Python, via Coin3D’s python wrapper pivy , see
[Link]
1.5. ROADMAP OF FREECAD 9
VTK, is another open source and cross-platform visualisating library, which ParaView is based on. Interoperation is possible,
see Method for converting output from the VTK pipeline into Inventor nodes. From 0.17 and beyond, VTK pipeline is added
to Fem module.
It is important to track the roadmap of FreeCAD as it is still under heavy development. [Link]
Development_roadmap
The Main external components are upgrade gradually, like OpenInventor, pyCXX.
• C++11 is adopted since 0.17. C++17 latest standard library could replace boost::FileSystem in the future
• Migration from Qt4 to Qt5 is straight-forward (Qt4All.h Switch from Qt4->Qt5) in C++, but depending on availability
of LGPL version of Qt5 python wrapping: PySide2
• Python3 support is under implementation
• OpenCASCADE(OCC) and VTK is migrating to 7.0 in late 2016
Transitioning from OpenGL to Vulkan will not happen in the future, while OpenGL should be available for a long time (10
years).
10 CHAPTER 1. FREECAD OVERVIEW AND ARCHITECTURE
1.5.2 C++11
C++ is NOT a easy language but targeting at high performance, you need to manage memory manually and there are a lot of
pitfalls, see [Link] Even experienced C++ programmer will find himself/herself has not
fully master C++. Exception safety will be beyond layman’s brain.
Nevertheless, C++11 is almost a branch new language, or next generation c++. C++11 add some extra keywords like
“explicit, overload/final, noexcept” to avoid some unintended mistakes, also introduce new features and extra STL functions
like lambda and std::function, constexpr, enum class, smart pointer, auto type derivation, std::thread, atomic, regex etc.
According to Qt community news (July, 2016), LGPL python wrapping for Qt 5.x is promising in the near future
The Pyside 2 project aims to provide a complete port of PySide to Qt 5.x. The development started on GitHub in
May 2015. The project managed to port Pyside to Qt 5.3, 5.4 & 5.5. During April 2016 The Qt Company decided
to properly support the port (see details ).
Chapter 2
The FreeCAD official repository has the standard github project layout, with README, License, Continuous Integration
setup files at the repository root. The FreeCAD source tree in the src/ folder reflects its modular design.
11
12 CHAPTER 2. ORGANIZATION OF FREECAD SOURCE CODE
• XDGData [Link] file for linux package compliant Linux freedesktop standard
• WindowsInstaller config files to generate windows installer
• JtReader
CMake is the cross-platform build tool for FreeCAD. Along with generating the make files that are used to build FC, it also
generates the installer for Windows, the DEB/RPM packages for Linux, and an image bundle MacOS X.
See Appendix 2 cmake cheatsheet for a quick start on CMake.
The project top level [Link] at the repository root is very long, detecting third party library detection and
dealing compiler and OS differences. In contrast, src/[Link] in the source tree is the much shorter, with
add_directory(subfolder_name).
CMake folder in the repo root is filled with *.cmake files to detect libraries. If a new workbench with c++ code is added, then
CMake’s third parties detection cmake file is probably needed.]
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cMake")
The following hypothetical code demonstrates how we can specify with CMake to not build OpenFOAM from source but
instead we can just install as a binary. The same can be done for other dependencies like other FEM meshing tools like
netgen, and gmsh.
if(NOT MSVC)
OPTION(BUILD_FEM_FOAM "Build the FreeCAD FEM module with the OpenFOAM CFD solver" ON)
else
OPTION(BUILD_FEM_FOAM "Build the FreeCAD FEM module with the OpenFOAM CFD solver" OFF)
endif(NOT MSVC)
Examining further, we can see the following in the the toplevel CMake file src/[Link]
# -------------------- OpenFOAM --------------------
if (BUILD_FEM_FOAM)
find_package(FOAM)
endif(BUILD_FEM_FOAM)
14 CHAPTER 2. ORGANIZATION OF FREECAD SOURCE CODE
IF (CMAKE_COMPILER_IS_GNUCC)
FIND_PATH(SMESH_INCLUDE_DIR SMESH_Mesh.hxx
# These are default search paths, why specify them?
# /usr/include
# /usr/local/include
PATH_SUFFIXES smesh
)
FIND_LIBRARY(SMESH_LIBRARY SMESH
# /usr/lib
# /usr/local/lib
)
ELSE (CMAKE_COMPILER_IS_GNUCC)
# Not yet implemented
ENDIF (CMAKE_COMPILER_IS_GNUCC)
SET(SMESH_FOUND FALSE)
IF(SMESH_LIBRARY)
SET(SMESH_FOUND TRUE)
GET_FILENAME_COMPONENT(SMESH_LIBRARY_DIR ${SMESH_LIBRARY} PATH)
set(SMESH_LIBRARIES
${SMESH_LIBRARY_DIR}/[Link]
${SMESH_LIBRARY_DIR}/[Link]
${SMESH_LIBRARY_DIR}/[Link]
${SMESH_LIBRARY_DIR}/[Link]
${SMESH_LIBRARY_DIR}/[Link]
${SMESH_LIBRARY_DIR}/[Link]
${SMESH_LIBRARY_DIR}/[Link]
${SMESH_LIBRARY_DIR}/[Link]
)
ENDIF(SMESH_LIBRARY)
Continuous Integration (CI) is basically a paradigm for building and testing automation. CI is crucial to maintain a software
project efficiently. There are several CI tools that are utilized by FreeCAD.
2.4.1 Travis CI
Travis is tightly integrated with github and it is widely used in the FreeCAD github repo. For more information on how to set
Travis CI for a code repository read this useful guide.
Travis uses YAML files. For the Linux and MacOS platform, the main Travis config file for FreeCAD is .[Link].
For Windows, the config file is .travis/[Link].
2.5. PACKAGING AND DEPLOYMENT 15
Along with Travis, there is a whole ecosystem of CI Software, you can learn learn more about them through a dedicated
Wikipedia page
.circleci/[Link] which will pull a docker image to setup testing environment.
"[Link]" : "[Link]
subuser/freecad-dev [.[Link]] adds subuser files for developing freecad within a Docker container.
2.4.6 AppVoyer
Sign up and setup github and bitbucket repo is simple, see official document [Link] FreeCAD
project use AppVoyer for building on windows, although it is a cross-platform solution.
FreeCAD has been in official repo of major linux distro, but the version may be outdated. For Ubuntu, there is PPA to install
the latest or even daily development build. It is recommended for developers to install lattest version, especially Qt5+Python3
combination.
Windows installer is generated by NSIS, and open source windows installer generation tool. Packaging for windows its in
another subproject FreeCADInstProj, see [Link]
The package folder in the repository root contains the [conda]() recipe to build Anaconda package. Conda is a popular
binary python package (python module compiled from other languages) distribution system.
fedora subfolder has the configuration
3. be familiar with key classes in FreeCAD source code: Base, App, Gui, Part
It is really challenging to code in C++, Python GIL, Coin3D, OCC. However, it is not needed to know about OCC
as module developers. FreeCAD has a online API document for import classes like Properties, Document Objects, see
[Link]
To explore the source code, doxygen generated document is available on [Link]. Furthermore, Sourcetrailcan also
be one of the tools to help in this effort is a powerful tool, free for non commercial usage, to explore large softwar projects, see
FreeCAD forum discussion. The software can be downloaded from [Link]
4. develop/extend pure Python module, the challenging Python wrapping task can be avoided
In this chapter, the namespace of Base, App and Main modules are introduced, these 3 modules make a complete program
without GUI.
Their functions can be accessed in python by “import FreeCAD”, see [FreeCAD module][Link]
[Link]
This chapter focused on the property framework and DocumentObject in App namespace, as they are most interesting to
module developer. The classes in Base namespace are not frequently used, but understanding of the type system could be
useful. Finally, the FreeCAD startup process is tracked in Main source code folder.
17
18 CHAPTER 3. BASE, APP AND MAIN MODULE
• Stream.h define adapter classes for Qt class QByteArray; class QIODevice;class QBuffer;
• InputSource.h
class BaseExport StdInputStream : public XERCES_CPP_NAMESPACE_QUALIFIER BinInputStream
• FileInfo.h File name unification class
This class handles everything related to file names the file names which are internal generally UTF-8 encoded on
all platforms.
• FileTemplate.h used for testing purpose
• gzStream.h gzip compressed file Stream
• Console.h output message to terminal which starts FreeCADCmd
ConsoleObserver and ConsoleSingleton with python code [[Link]], This is not Python Console, but dealing
with stdio, logging to terminal which starts FreeCADCmd. class BaseExport ConsoleObserverStd: public
ConsoleObserver to write Console messages and logs the system con.
• Parameter.h ParameterGrp: key-value, XML persistence as app config
class BaseExport ParameterGrp : public Base::Handled,public Base::Subject <const char*>
class BaseExport ParameterManager : public ParameterGrp
• Debugger.h Debugger class
Debugging related classes in source files [Debugger.h, [Link], StackWalker.h, [Link], MemDebug.h]
serialization support, example of class with cpp, py and XML code
• Persistence.h serialization of objects
base class for DocumentObject, Property, etc
• [Link] C++ implementation of Persistence class
• [Link] automatically generated C++ code for exporting Persistence class to python
• [Link] XML to generate [Link] by python script
**Geometry related calculation classes with *[Link]**
• Axis.h Class: Axis
• BoundBox.h bounding boxes of the 3D part, define max{x,y,z} and min{x,y,z}
• Rotation.h define class and method for rotation an objecti n 3D space
• Placement.h class to place/relocate an object in 3D space
see official api doc: [Link]
• [Link] class represents a point, direction in 3D space typedef Vector3<float> Vector3f; typedef
Vector3<double> Vector3d;
• [Link] class: Matrix4D for coordination translation and rotation
#include <Base/Sequencer.h>
void runOperation();
void myTest()
{
try{
runOperation();
} catch(...) {
20 CHAPTER 3. BASE, APP AND MAIN MODULE
The string encoding for FreeCAD is different form Qt’s wide char, using the helper functions in src/Base/Tools.h
fromStdString(const std::string & s) and toStdString(const QString& s)
struct BaseExport Tools
{
static std::string getUniqueName(const std::string&, const std::vector<std::string>&,int d=0);
static std::string addNumber(const std::string&, unsigned int, int d=0);
static std::string getIdentifier(const std::string&);
static std::wstring widen(const std::string& str);
static std::string narrow(const std::wstring& str);
static std::string escapedUnicodeFromUtf8(const char *s);
/**
* @brief toStdString Convert a QString into a UTF-8 encoded std::string.
* @param s String to convert.
* @return A std::string encoded as UTF-8.
*/
static inline std::string toStdString(const QString& s) { QByteArray tmp = s.toUtf8(); return std::string(t
/**
* @brief fromStdString Convert a std::string encoded as UTF-8 into a QString.
* @param s std::string, expected to be UTF-8 encoded.
* @return String represented as a QString.
*/
static inline QString fromStdString(const std::string & s) { return QString::fromUtf8(s.c_str(), [Link]());
see src/Base/Type.h
struct Base::TypeData
{
TypeData(const char *theName,
const Type type = Type::badType(),
const Type theParent = Type::badType(),
Type::instantiationMethod method = 0
):name(theName),parent(theParent),type(type),instMethod(method) { }
std::string name;
Type parent;
Type type;
Type::instantiationMethod instMethod;
};
class Type
{
//...
static void *createInstanceByName(const char* TypeName, bool bLoadModule=false);
static const Type createType(const Type parent, const char *name,instantiationMethod method = 0);
private:
unsigned int index;
static std::map<std::string,unsigned int> typemap;
static std::vector<TypeData*> typedata;
3.2.2 src/Base/BaseClass.h
Macro function is widely employed to generate boilplate code, similar with QObject macro for QT
#ifndef BASE_BASECLASS_H
#define BASE_BASECLASS_H
#include "Type.h"
// Python stuff
typedef struct _object PyObject;
namespace Base
{
/// BaseClass class and root of the type system
class BaseExport BaseClass
{
public:
static Type getClassTypeId(void);
virtual Type getTypeId(void) const;
bool isDerivedFrom(const Type type) const {return getTypeId().isDerivedFrom(type);}
public:
/// Construction
BaseClass();
/// Destruction
virtual ~BaseClass();
};
3.2. TYPE, BASECLASS, PYOBJECTBASE 23
} //namespace Base
#endif // BASE_BASECLASS_H
///////////////////////////////////////////////////////////////////////////////
#include "PreCompiled.h"
#ifndef _PreComp_
# include <assert.h>
#endif
//**************************************************************************
// separator for other implementation aspects
void BaseClass::init(void)
{
assert(BaseClass::classTypeId == Type::badType() && "don't init() twice!") ;
/* Make sure superclass gets initialized before subclass. */
/*assert(strcmp(#_parentclass_), "inherited"));*/
/*Type parentType(Type::fromName(#_parentclass_));*/
/*assert(parentType != Type::badType() && "you forgot init() on parentclass!");*/
Type BaseClass::getClassTypeId(void)
{
return BaseClass::classTypeId;
}
/**
* This method returns the Python wrapper for a C++ object. It's in the responsibility of
* the programmer to do the correct reference counting. Basically there are two ways how
* to implement that: Either always return a new Python object then reference counting is
* not a matter or return always the same Python object then the reference counter must be
* incremented by one. However, it's absolutely forbidden to return always the same Python
* object without incrementing the reference counter.
*
* The default implementation returns 'None'.
*/
PyObject *BaseClass::getPyObject(void)
{
assert(0);
Py_Return;
}
void BaseClass::setPyObject(PyObject *)
{
assert(0);
}
3.2.3 src/Base/PyObjectBase.h
Py_Header is a macro function, PyObject is defined in <python.h>, the header for python C API.
/** The PyObjectBase class, exports the class as a python type
* PyObjectBase is the base class for all C++ classes which
* need to get exported into the python namespace.
3.2.4 src/Base/Persistence.h
3.3.1 src/Base/Unit.h
There are 7 SI base units, but FreeCAD defined Density, which is a derived unit
struct UnitSignature{
int32_t Length:UnitSignatureLengthBits;
int32_t Mass:UnitSignatureMassBits;
int32_t Time:UnitSignatureTimeBits;
int32_t ElectricCurrent:UnitSignatureElectricCurrentBits;
int32_t ThermodynamicTemperature:UnitSignatureThermodynamicTemperatureBits;
int32_t AmountOfSubstance:UnitSignatureAmountOfSubstanceBits;
int32_t LuminoseIntensity:UnitSignatureLuminoseIntensityBits;
int32_t Angle:UnitSignatureAngleBits;
int32_t Density:UnitSignatureDensityBits;
};
Predefined static Unit types: static Unit Length; ... static Unit Stress;
3.3.2 src/Base/Quantity.h
Quantity is value + unit. Common quantities defined as static instances. Quantity string can be parsed into value and unit
by quantitylexer
Property name should begin with uppercase like “ThisPropertyName”, and it will show as “This Property Name” in property
editor There is indeed the logic to split property names on capital letters and insert a space. But that’s only for visual
purposes and doesn’t affect changing a property value.
3.5.2 src/App/PropertyStandard.h
Define property for common C++ data type: PropertyBool, PropertyInteger (long), PropertyString (utf8/std::string),
PropertyFloat (double), PropertyPath (boost::filesystem::path), PropertyFont, PropertyColor, PropertyMaterial,PropertyUuid,
PropertyStringLists, PropertyMap(std::map)
For PropertyList derived class, it is possible to set and get values in std::vector<> reference setValues(std::vector<T>&),
std::vector& getValues()‘
#include <App/PropertyStandard.h>
const char* FinenessEnums[]= {"VeryCoarse","Coarse","Moderate","Fine","VeryFine","UserDefined",NULL};
...
ADD_PROPERTY_TYPE(Fineness,(2), "MeshParams",Prop_None,"Fineness level of the mesh");
[Link](FinenessEnums);
see src/App/[Link]
App::PropertyPath
App::PropertyFile
App::PropertyFileIncluded
App::PropertyPythonObject
3.5. PROPERTY FRAMEWROK 29
src/App/[Link] link to other document object in the this document. For example, FemMeshObject has a link to
Part object.
There are scennarios where link to sub feature are needed, e.g. faces of a part.
3.5.7 PropertyMap
implements a key/value list as property. The key ought to be ASCII the Value should be treated as UTF8 to be save
src/App/[Link]
TYPESYSTEM_SOURCE(App::PropertyDistance, App::PropertyQuantity);
PropertyDistance::PropertyDistance()
{
setUnit(Base::Unit::Length);
}
3.5.9 src/App/Property.h
3.5.10 src/App/PropertyContainer.h
enum PropertyType
{
Prop_None = 0,
Prop_ReadOnly = 1,
Prop_Transient= 2,
Prop_Hidden = 4,
Prop_Output = 8
};
struct PropertySpec
{
const char* Name;
const char * Group;
const char * Docu;
short Offset,Type;
};
// vector of all properties
std::vector<PropertySpec> propertyData;
const PropertyData *parentPropertyData;
void addProperty(const PropertyContainer *container,const char* PropName, Property *Prop, const char* Propert
private:
static PropertyData propertyData;
};
• ADD_PROPERTY_TYPE(prop, defaultval, group,type,Docu), where Docu is docstring tooltip for user, group should be
“Data” , type is enum PropertyType, Prop_None is the most common type
• PROPERTY_SOURCE_ABSTRACT,
• TYPESYSTEM_SOURCE_TEMPLATE(class),
• PROPERTY_SOURCE_TEMPLATE(class, parentclass
3.6. DOCUMENT-VIEW-OBSERVER PATTERN 31
3.6.1 src/App/Document.h
3.6.2 src/App/DocumentObject.h
class AppExport DocumentObject: public App::PropertyContainer , Base class of all Classes handled in the Document.
see [Link] some important methods (excluding methods from
App::PropertyContainer) are extracted here:
• state enumeration.
enum ObjectStatus {
Touch = 0, Error = 1, New = 2, Recompute = 3,
Restore = 4, Expand = 16
}
• __setstate__(value) allows to save custom attributes of this object as strings, so they can be saved when saving the
FreeCAD document
• touch() marks this object to be recomputed
• purgeTouched() removes the to-be-recomputed flag of this object
• execute() this method is executed on object creation and whenever the document is recomputed
Implementation: [src/App/DocumentObject.h] and [src/App/[Link]]
protected:
/* get called by the document to recompute this feature
* Normally this method get called in the processing of Document::recompute().
* In execute() the output properties get recomputed with the data from linked objects and objects own pr
*/
virtual App::DocumentObjectExecReturn *execute(void);
protected: // attributes
Py::Object PythonObject;
/// pointer to the document this object belongs to
App::Document* _pDoc;
// Connections to track relabeling of document and document objects
boost::BOOST_SIGNALS_NAMESPACE::scoped_connection onRelabledDocumentConnection;
boost::BOOST_SIGNALS_NAMESPACE::scoped_connection onRelabledObjectConnection;
Observer class Implementation of the well known Observer Design Pattern. * The observed object, which inherit FCSubject,
will call all its observers in case of changes. A observer class has to attach itself to the observed object.
The DocumentObserver class simplfies the step to write classes that listen to what happens inside a document. This is very
useful for classes that needs to be notified when an observed object has changed.
void attachDocument(Document*);
/* Checks if the given document is about to be opened/closed */
virtual void slotDeletedDocument(const App::Document& Doc) {}
/* Checks if a new object was added, removed, changed. */
virtual void slotCreatedObject(const App::DocumentObject& Obj) {}
3.6.4 App::DocumentObjectExecReturn
std::string Why;
DocumentObject* Which;
};
3.6. DOCUMENT-VIEW-OBSERVER PATTERN 33
3.6.5 FeaturePython
DocumentObjectExecReturn *FeaturePythonImp::execute()
{
// Run the execute method of the proxy object.
Base::PyGILStateLocker lock;
try {
Property* proxy = object->getPropertyByName("Proxy");
if (proxy && proxy->getTypeId() == PropertyPythonObject::getClassTypeId()) {
Py::Object feature = static_cast<PropertyPythonObject*>(proxy)->getValue();
if ([Link]("__object__")) {
Py::Callable method([Link](std::string("execute")));
Py::Tuple args;
[Link](args);
}
else {
Py::Callable method([Link](std::string("execute")));
Py::Tuple args(1);
[Link](0, Py::Object(object->getPyObject(), true));
[Link](args);
}
}
}
catch (Py::Exception&) {
Base::PyException e; // extract the Python error text
[Link]();
std::stringstream str;
str << object->[Link]() << ": " << [Link]();
return new App::DocumentObjectExecReturn([Link]());
}
return DocumentObject::StdReturn;
}
This template helps to expose document object derived class to python as/like DocumentObjectPy see chapters on Fem
module code analysis and python wrapping for details
template <class FeaturePyT>
class FeaturePythonPyT : public FeaturePyT
{
public:
static PyTypeObject Type;
static PyMethodDef Methods[];
public:
FeaturePythonPyT(DocumentObject *pcObject, PyTypeObject *T = &Type);
virtual ~FeaturePythonPyT();
/** @name callbacks and implementers for the python object methods */
//@{
static int __setattr(PyObject *PyObj, char *attr, PyObject *value);
/// callback for the addProperty() method
static PyObject * staticCallback_addProperty (PyObject *self, PyObject *args);
/// implementer for the addProperty() method
PyObject* addProperty(PyObject *args);
/// callback for the removeProperty() method
static PyObject * staticCallback_removeProperty (PyObject *self, PyObject *args);
/// implementer for the removeProperty() method
34 CHAPTER 3. BASE, APP AND MAIN MODULE
protected:
std::map<std::string, PyObject*> dyn_methods;
private:
};
} //namespace App
protected:
virtual void onBeforeChange(const Property* prop) {
FeatureT::onBeforeChange(prop);
imp->onBeforeChange(prop);
}
virtual void onChanged(const Property* prop) {
imp->onChanged(prop);
FeatureT::onChanged(prop);
}
private:
FeaturePythonImp* imp;
DynamicProperty* props;
PropertyPythonObject Proxy;
};
This framework is added in 0.17, see feature announcement in forum discussion: Developer Feature: Extensions
[Link] **************************************
3.7. STARTUP PROCESS OF FREECADCMD 35
This cpp script will be compiled into the executable freecadcmd, which drop you to a python interpreter with FreeCAD
modules path appended to [Link].
main()
{
try {
// Init phase ===============================
// sets the default run mode for FC, starts with command prompt
//if not overridden in InitConfig...
App::Application::Config()["RunMode"] = "Exit";
// cleans up
Application::destruct();
return 0;
}
3.7.2 src/Main/[Link]
This cpp file will be compiled into a python module “FreeCAD” that can be imported into standard python.
This source code deal with different OS platforms, python 2 or 3 version, by conditional C macro. Set IO rediction for error,
output and log.
PyMOD_INIT_FUNC(FreeCAD)
//void MainExport initFreeCAD() // in version 0.16 the function name was called
{
// Init phase ================
App::Application::Config()["ExeName"] = "FreeCAD";
// ...
// load shared dll/so
App::Application::init(argc,argv);
}
At the end of this function, module is return, see
#if PY_MAJOR_VERSION >= 3
//PyObject* module = _PyImport_FindBuiltin("FreeCAD");
PyObject* modules = PyImport_GetModuleDict();
PyObject* module = PyDict_GetItemString(modules, "FreeCAD");
if (!module) {
36 CHAPTER 3. BASE, APP AND MAIN MODULE
Application::Application(ParameterManager * /*pcSysParamMngr*/ ,
ParameterManager * /*pcUserParamMngr*/ ,
3.8. FREECADGUI START UP PROCESS 37
std::map<std::string,std::string> &mConfig)
://_pcSysParamMngr(pcSysParamMngr),
//_pcUserParamMngr(pcUserParamMngr),
_mConfig(mConfig),
_pActiveDoc(0)
{
//_hApp = new ApplicationOCC;
mpcPramManager["System parameter"] = _pcSysParamMngr;
mpcPramManager["User parameter"] = _pcUserParamMngr;
if (mConfig["RunMode"] == "Cmd") {
// Run the comandline 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());
}
}
This source will be compiled into the program “freecad”, which will take you FreeCAD GUI.
38 CHAPTER 3. BASE, APP AND MAIN MODULE
This main function is similar with src/Main/[Link], except it supports both Gui and nonGui mode
App::Application::init(argc, argv); and App::Application::destruct(); are still called!
QCoreApplication is defined for WIN32, see src/Main/[Link], text banner is defined here
main()
{
App::Application::init(argc, argv);
Gui::Application::initApplication(); // extra InitApplication();
// Only if 'RunMode' is set to 'Gui' do the replacement
if (App::Application::Config()["RunMode"] == "Gui")
Base::Interpreter().replaceStdOutput();
try {
if (App::Application::Config()["RunMode"] == "Gui")
Gui::Application::runApplication();
else
App::Application::runApplication();
}
...
App::Application::destruct();
}
void Application::runApplication(void)
{
GUIApplication mainApp(argc, App::Application::GetARGV(), systemExit);
// set application icon and window title
const std::map<std::string,std::string>& cfg = App::Application::Config();
...
QCoreApplication::addLibraryPath(plugin);
...//setup config, style sheet
Application app(true); // it is worth of going through the constructor of Gui::Application
MainWindow mw;
[Link]([Link]());
// close the lock file, in case of a crash we can see the existing lock file
// on the next restart and try to repair the documents, if needed.
[Link]();
[Link]();
[Link]();
}
3.8.3 src/Main/[Link]
This cpp file will be compiled into a python module “FreeCADGui” that can be imported into standard python.
refer to src/Gui/[Link] for details of FreeCAD start up with GUI
It defines the GuiThread class
struct PyMethodDef FreeCADGui_methods[] = {
{"showMainWindow",FreeCADGui_showMainWindow,METH_VARARGS,
"showMainWindow() -- Show the main window\n"
"If no main window does exist one gets created"},
{"exec_loop",FreeCADGui_exec_loop,METH_VARARGS,
"exec_loop() -- Starts the event loop\n"
"Note: this will block the call until the event loop has terminated"},
{"setupWithoutGUI",FreeCADGui_setupWithoutGUI,METH_VARARGS,
"setupWithoutGUI() -- Uses this module without starting\n"
"an event loop or showing up any GUI\n"},
{"embedToWindow",FreeCADGui_embedToWindow,METH_VARARGS,
"embedToWindow() -- Embeds the main window into another window\n"},
{NULL, NULL} /* sentinel */
};
PyMODINIT_FUNC initFreeCADGui()
{
try {
Base::Interpreter().loadModule("FreeCAD");
App::Application::Config()["AppIcon"] = "freecad";
App::Application::Config()["SplashScreen"] = "freecadsplash";
40 CHAPTER 3. BASE, APP AND MAIN MODULE
41
42 CHAPTER 4. OVERVIEW OF GUI MODULE
• DownloadManager.h
• NetworkRetriever.h
subfolders in Gui
• 3Dconnexion 3D mouse 3Dconnexion’s supporting lib
• Inventor Inventor 3D rendering lib
• TaskView TaskView Framework for FreeCAD Gui
• QSint Collection of extra Qt widgets from community
• iisTaskPanel Task panel UI widgets, now part of QSint
• propertyeditor Widget for property edit for DocumentObject
• Language translation for FreeCADGui
• Icons icon for commands
4.2.1 Gui::Application
Gui::Application::Instance->activeDocument()
4.2.2 Gui::Document
[Link]
PropertyView has PropertyEditor,
class PropertyView : public QWidget, public Gui::SelectionObserver
class PropertyDockView : public Gui::DockWindow
The key C++ API are defined in src/Gui/Selection.h. see the doxygen document: [Link]
d4/dca/classGui_1_1SelectionSingleton.html
The SelectionSingleton keeps track of the selection state of the whole application. It gets messages from all entities which
can alter the selection (e.g. tree view and 3D-view) and sends messages to entities which need to keep track on the selection
state. SelectionObserver class implements the observer pattern; SelectionGate class enables the selective pickup for the
specific type (filtering).
The selection consists mainly out of following information per selected object: - document (pointer) - Object (pointer) - list of
subelements (list of strings) - 3D coordinates where the user clicks to select (Vector3d)
Also the preselection is managed. That means you can add a filter to prevent selection of unwanted objects or subelements.
src/Gui/Selection.h
std::vector<App::DocumentObject*> getObjectsOfType(const char* typeName, const char* pDocName=0) const;
struct SelObj {
const char* DocName;
const char* FeatName;
const char* SubName;
const char* TypeName;
App::Document* pDoc;
App::DocumentObject* pObject;
float x,y,z;
};
/// returns the selected DocumentObject or NULL if the object is already deleted
const App::DocumentObject *getObject(void) const;
/// returns the selected DocumentObject or NULL if the object is already deleted
App::DocumentObject *getObject(void);
4.3.1 Gui::ViewProvider
The general interface for all visual stuff in FreeCAD. This class is used to generate and handle all around visualizing and
presenting objects from the FreeCAD App layer to the user. This class and its descendants have to be implemented for any
object type in order to show them in the 3DView and TreeView.
Inventor object will be created and ref in the constructor if defined in this base class; while in destructor, [Link]()
is called, in addition to unref open inventor objects. show() and hide() are virtual functions, but they have implementation,
intensive implementation happens in Gui::DocumentObjectViewProvider. PyObject* ViewProvider::getPyObject() has
its implementation so all the derived classes for the specific python type, however, only one PyObject destruction happens in
this based class ([Link]() is called).
Base class ViewProvider, derived from PropertyContainer , is surprisingly short in coding; the derived classes have
implementation. Some important methods for python module developer are listed: - Object returns the DocumentObject this
ViewProvider is associated to - RootNode returns the Root coin node of this object - toString() returns a string representation
of the coin node of this object - update() this method is executed whenever any of the properties of this ViewProvider changes
see details python API manual at [Link]
grouped in doxygen document
OpenInventor related objects () are declared as protected var:
SoSeparator * pcAnnotation // The root separator for annotations.
SoSwitch * pcModeSwitch // this is the mode switch, all the different viewing modes are collected here
4.3.2 Gui::DocumentObjectViewProvider
This is the counterpart of the DocumentObject in the GUI space. It is only present when FreeCAD runs in GUI mode
(e.g. show(), hide(), update() ). It contains all that is needed to represent the DocumentObject in the 3D view and the
FreeCAD CombiView. It implements show() hide() attach(), also restores view provider from document file loaded:
virtual void finishRestoring () and virtual void startRestoring ().
This class has detailed doxygen code documentation in this header file, Similar with the ViewProvider class, show() hide()
are virtual member functions but with implementation.
src/Gui/[Link] This class defines two new Properties in constructor.
ADD_PROPERTY(DisplayMode,((long)0));
ADD_PROPERTY(Visibility,(true));
Thereby, onChanged(const App::Property* prop) is reimplemented
void ViewProviderDocumentObject::onChanged(const App::Property* prop)
{
if (prop == &DisplayMode) {
setActiveMode();
}
else if (prop == &Visibility) {
// use this bit to check whether show() or hide() must be called
if ([Link](App::Property::User2) == false) {
[Link](App::Property::User2, true);
[Link]() ? show() : hide();
[Link](App::Property::User2, false);
}
}
ViewProvider::onChanged(prop);
}
DisplayMode related code is found inattach()
Gui::MDIView* ViewProviderDocumentObject::getActiveView() const
viewer->getSoRenderManager()->getViewportRegion());
viewer->getSoRenderManager()->getCamera();
Similar with ViewProvider class, show() hide() are virtual member functions but with implementation.
4.3. VIEWPROVIDER FRAMEWORK AND 3D REDERRING 49
void ViewProviderDocumentObject::updateView()
{
std::map<std::string, App::Property*> Map;
pcObject->getPropertyMap(Map);
4.3.3 Gui::ViewProviderGeometryObject
The base class for all view providers that display geometric data, like mesh, point clouds, and shapes. drag, select(pick),
boundingbox, sensorCallback()
src/Gui/[Link]
ADD_PROPERTY(ShapeColor,(r, g, b));
ADD_PROPERTY(Transparency,(0));
[Link](&intPercent);
App::Material mat(App::Material::DEFAULT);
ADD_PROPERTY(ShapeMaterial,(mat));
ADD_PROPERTY(BoundingBox,(false));
ADD_PROPERTY(Selectable,(true));
void ViewProviderGeometryObject::onChanged(const App::Property* prop) just call parent methods, in addition to
properties defined in this class. void ViewProviderGeometryObject::updateData(const App::Property* prop), update
Placement and PropertyComplexGeoData.
Gui::ViewProviderBuilder: Render complex geometry like points.
4.3.4 Fem::ViewProviderFemConstraint
This class draws some visual objects, arrows and cubes in 3D view, see src/Mod/Fem/Gui/[Link]
• Some more inventor objects are created in Constructor: cpp SoPickStyle* ps = new SoPickStyle();
ps->style = SoPickStyle::UNPICKABLE;
• onChange() for updated drawing for changed ViewProvider properties void ViewProviderFemConstraint::onChanged(const
App::Property* prop) { if (prop == &Mirror || prop == &DistFactor) { updateData(prop);
src/Mod/Fem/Gui/[Link]
Draw 3D objects more specifically for different constraint types
• bool ViewProviderFemConstraintFluidBoundary::setEdit(int ModNum) activate the taskpanel dialog
• void ViewProviderFemConstraintFluidBoundary::updateData(const App::Property* prop) for DocumentOb-
ject property update
src/Mod/Part/Gui/ViewProvider.h
The base class for all CAD features like boolean operation, fillet, etc. . . implemented by OpenCASCADE.
50 CHAPTER 4. OVERVIEW OF GUI MODULE
if (L < Precision::Confusion())
return new App::DocumentObjectExecReturn("Length of box too small") ;
if (W < Precision::Confusion())
return new App::DocumentObjectExecReturn("Width of box too small") ;
if (H < Precision::Confusion())
return new App::DocumentObjectExecReturn("Height of box too small") ;
try {
// Build a box using the dimension attributes
BRepPrimAPI_MakeBox mkBox(L, W, H);
TopoDS_Shape ResultShape = [Link]();
this->[Link](ResultShape);
}
catch (Standard_Failure) {
Handle_Standard_Failure e = Standard_Failure::Caught();
return new App::DocumentObjectExecReturn(e->GetMessageString());
4.3. VIEWPROVIDER FRAMEWORK AND 3D REDERRING 51
return App::DocumentObject::StdReturn;
}
}
//
class View3DInventorPy : public Py::PythonExtension<View3DInventorPy>
class View3DInventorViewerPy : public Py::PythonExtension<View3DInventorViewerPy>
Note: Quarter::SoQTQuarterAdaptor is derived from QGraphicsView
class GuiExport View3DInventorViewer : public Quarter::SoQTQuarterAdaptor, public Gui::SelectionSingleton::Obse
Gui::MDIView* ViewProviderDocumentObject::getInventorView() const
{
App::Document* pAppDoc = pcObject->getDocument();
Gui::Document* pGuiDoc = Gui::Application::Instance->getDocument(pAppDoc);
return mdi;
}
if (root) {
pcViewProviderRoot->addChild(root);
_ViewProviderMap[root] = pcProvider;
}
if (fore)
foregroundroot->addChild(fore);
if (back)
backgroundroot->addChild(back);
pcProvider->setOverrideMode(this->getOverrideMode());
_ViewProviderSet.insert(pcProvider);
}
setSceneGraph(pcViewProviderRoot);
4.4.1 src/Gui/Selection.h
This file has defined important classes: SelectionObserver SelectionChanges SelectionObserverPython SelectionGate - Selec-
tionGate: allows or disallows selection of certain types. - SelectionObserver: observer pattern - SelectionChanges: as message
for Observer
This file is well documented, see the header file for all API src/Gui/Selection.h
class GuiExport SelectionSingleton : public Base::Subject<const SelectionChanges&>
bool SelectionSingleton::setPreselect(const char* pDocName, const char* pObjectName, const char* pSubName, floa
4.4. SELECTION FRAMEWORK 53
/// returns the selected DocumentObject or NULL if the object is already deleted
const App::DocumentObject *getObject(void) const;
...
namespace Gui {
namespace DockWnd {
QListWidget* selectionView;
This class builds up a type/count tree out of a string to test very fast a selection or object/subelement type against it.
Example strings are: “SELECT Part::Feature SUBELEMENT Edge”, “SELECT Robot::RobotObject”, “SELECT
Robot::RobotObject COUNT 1..5”
4.4.5 src/Gui/MouseSelection.h
if ([Link]() != 1) {
QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"),
QObject::tr("Select an edge, face or body. Only one body is allowed."));
return;
}
54 CHAPTER 4. OVERVIEW OF GUI MODULE
if (!selection[0].isObjectTypeOf(Part::Feature::getClassTypeId())){
QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong object type"),
QObject::tr("Fillet works only on parts"));
return;
}
In GUI applications many commands can be invoked via a menu item, a toolbar button or an accelerator key. The answer of
Qt to master this challenge is the class QAction. A QAction object can be added to a popup menu or a toolbar and keep the
state of the menu item and the toolbar button synchronized.
For example, if the user clicks the menu item of a toggle action then the toolbar button gets also pressed and vice versa. For
more details refer to your Qt documentation.
Since QAction inherits QObject and emits the triggered() signal or toggled() signal for toggle actions it is very convenient to
connect these signals e.g. with slots of your MainWindow class. But this means that for every action an appropriate slot of
MainWindow is necessary and leads to an inflated MainWindow class. Furthermore, it’s simply impossible to provide plugins
that may also need special slots – without changing the MainWindow class.
To solve these problems we have introduced the command framework to decouple QAction and MainWindow. The base
classes of the framework are Gui::CommandBase and Gui::Action that represent the link between Qt’s QAction world and the
FreeCAD’s command world.
The Action class holds a pointer to QAction and CommandBase and acts as a mediator and – to save memory – that gets
created (Gui::CommandBase::createAction()) not before it is added (Gui::Command::addTo()) to a menu or toolbar.
Now, the implementation of the slots of MainWindow can be done in the method activated() of subclasses of Command
instead.
For example, the implementation of the “Open file” command can be done as follows.
class OpenCommand : public Command
{
public:
OpenCommand() : Command("Std_Open")
{
// set up menu text, status tip, ...
sMenuText = "&Open";
sToolTipText = "Open a file";
sWhatsThis = "Open a file";
sStatusTip = "Open a file";
sPixmap = "Open"; // name of a registered pixmap
sAccel = "Shift+P"; // or "P" or "P, L" or "Ctrl+X, Ctrl+C" for a sequence
}
4.6. TASKVIEW FRAMEWORK: UI FOR INTERACTIVE DESIGN 55
protected:
void activated(int)
{
QString filter ... // make a filter of all supported file formats
QStringList FileList = QFileDialog::getOpenFileNames( filter,QString::null, getMainWindow() );
for ( QStringList::Iterator it = [Link](); it != [Link](); ++it ) {
getGuiApplication()->open((*it).latin1());
}
}
};
An instance of OpenCommand must be created and added to the Gui::CommandManager to make the class known to FreeCAD.
To see how menus and toolbars can be built go to the Workbench Framework.
Both Qt C++ and python (file names start with TaskPanel) are used to design the UI (*.ui file generated by QtDesigner) for
FreeCAD. Related to setEdit(), unsetEdit() in ViewProvider class. Another Qt library ** is used. An image shows the
taskpanel is welcomed here!
}
src/Gui/TaskView/TaskView.h
class GuiExport TaskGroup : public QSint::ActionBox, public TaskContent
class GuiExport TaskView : public QScrollArea, public Gui::SelectionSingleton::ObserverType
{
//boost::signal connection + slot to App::Document
[Link]
// this is an example of QObject event system and boost::signal
}
class GuiExport TaskWatcher : public QObject, public Gui::SelectionFilter
/// List of TaskBoxes of that dialog
std::vector<QWidget*> Content;
56 CHAPTER 4. OVERVIEW OF GUI MODULE
public:
static ControlSingleton& instance(void);
static void destruct (void);
public Q_SLOTS:
void accept();
void reject();
void closeDialog();
/// raises the task view panel
void showTaskView();
private Q_SLOTS:
/// This get called by the TaskView when the Dialog is finished
void closedDialog();
private:
Gui::TaskView::TaskView *getTaskPanel();
private:
struct status {
std::bitset<32> StatusBits;
} CurrentStatus;
std::stack<status> StatusStack;
4.7. EXPRESSION AND QUANTITY 57
Gui::TaskView::TaskDialog *ActiveDialog;
private:
/// Construction
ControlSingleton();
/// Destruction
virtual ~ControlSingleton();
4.6.3 TaskDriver
src/Gui/PropertyPage.h
class GuiExport PreferencePage : public QWidget class GuiExport PreferenceUiForm : public PreferencePage
src/Mod/Fem/Gui/DlgSettingsFemCcxImp.h [src/Mod/Fem/Gui/PrefWidgets.h] wideges with save
class DlgSettingsFemCcxImp : public Gui::Dialog::PreferencePage, public Ui_DlgSettingsFemCcxImp
This section is mainly copied from FreeCAD documentation, see Internationalization with FreeCAD Doxygen document
position: Module->Gui->Internationalization with FreeCAD
The internationalization of FreeCAD makes heavy use of the internationalization support of Qt. For more details refer to
your Qt documentation. As FreeCAD will migrated to Qt5 in the future, QString::fromLatin1() should be used to convert
C-style char array and std::string in GUI code.
To integrate a new language into FreeCAD or one of its application modules you have to perform the following steps:
58 CHAPTER 4. OVERVIEW OF GUI MODULE
First you have to generate a .ts file for the language to be translated. You can do this by running the lupdate tool in the bin
path of your Qt installation. As argument you can specify either all related source files and the .ts output file or a Qt project
file (.pro) which contains all relevant source files.
To translate the english string literals into the language you want to support you can open your .ts file with QtLinguist and
translate all literals by hand. Another way for translation is to use the tool tsauto from Sebastien [Link] tool uses the
engine from Google web page ([Link]).ts auto supports the languages
To get most of the literals translated you should have removed all special characters (like &, !, ?, . . . ). Otherwise the
translation could fail. After having translated all literals you can load the .ts file into QtLinguist and invoke the menu item
Release which generates the binary .qm file.
The .qm file should now be integrated into the GUI library (either of FreeCAD itself or its application module). The .qm file
will be embedded into the resulting binary file. So, at runtime you don’t need any .qm files any more. Indeed you will have a
bigger binary file but you haven’t any troubles concerning missing .qm files.
To integrate the .qm file into the executable you have to create a resource file (.qrc), first. This is an XML file where you can
append the .qm file. For the .qrc file you have to define the following curstom build step inside the Visual Studio project file:
For the gcc build system you just have to add the line .qrc to the BUILT_SOURCES sources section of the [Link], run
automake and configure (or ./[Link]) afterwards.
[Link] Q_INIT_RESOURCE
Finally, you have to add a the line Q_INIT_RESOURCE(resource); where resource is the name of the .qrc file. That’s all!
It is the python interpreter that makes magic of scripting, macro recording, etc. While wrapping cpp code in python is a
tough story.
61
62 CHAPTER 5. INTRODUCTION TO PYTHON WRAPPING
pybind11: latest solution based on C++11 feature, similar but simpler API as boost::python. It is header only and smaller in
binary glue code and faster building, see example
example of C++ class wrapping in pybind11
#include <pybind11/pybind11.h>
namespace py = pybind11;
PYBIND11_PLUGIN(example) {
py::module m("example", "pybind11 example plugin");
py::class_<Pet>(m, "Pet")
.def(py::init<const std::string &>())
.def("setName", &Pet::setName)
.def("getName", &Pet::getName);
return [Link]();
}
For a small project with several header files, pybind11 is recommended to write pyhtonic interface in a manageable way.
For a project with tens of headers, writing configuration file to control binder for code generation is recom-
mended which can automatically generate boilerplate wrapping code, [Link]
[Link]
If the project has no dependency on binary library, but C++ STL. It is recommended to try cppyy to generate the interface
without writing extra interfacing code. Currently, installation and project integration is a bit difficult.
For large project like [Link] project specific fork of binder, see [Link]
is used with the specific pyOCCT project.
see also the source code src/Base/PyObjectBase.h, and src/Base/[Link] class BaseExport PyObjectBase :
public PyObject
It is not like other cpp lib that has python wrapper, like VTK another famous 3D visualization. Programmer will use either
cpp API or Python API, but not both in one project, usually. The mixture of cpp and python is highly challenging, like when
GIL is necessary, reference counting and passing of PyObject. For module developers, pure python developing is a good start
point, and anaylising code from other module can also ease the difficulty of hybrid cpp and python programming.
Reminder: FreeCAD is under migration from python2+Qt4 to python3+Qt5. Module developer should
“In FreeCAD we have our own little framework to create Python bindings for cpp classes but these classes are not prepared to
be sub-classed in Python.”
see example in src/Mod/TemplatePyMod/[Link]#113
def makeBox():
[Link]()
a=[Link]("Part::FeaturePython","Box")
Box(a)
if [Link]:
ViewProviderBox([Link])
There must be one cpp DocumentObject derived type like Part::Feature added to Document. Python class must ref/link to
the underlying cpp object, during __init__() It is the same for ViewProviderBox([Link]), which has a method of
attach(). more example can be found in Fem module
5.2.3 What is the limitation of pure python module except for performance?
Scripted objects pure python feature One particularity must be understood, those objects are saved in FreeCAD FcStd files
with python’s JSON module. cPickle is avoid for security reason.
That module turns a python object as a string, allowing it to be added to the saved file. On load, the JSON module uses that
string to recreate the original object, provided it has access to the source code that created the object.
5.2.5 DocumentObjectPy
DocumentObjectPy is python export class for App::DocumentObject, “DocumentObjectPy.h” is not manually coded but
generated from [Link] file, and its implementation is coded in src/App/[Link].
Can ViewProviderPy, DocumentObjectPy be subclassed in python?
Yes, but it is not what FreeCAD usually do. Due to this the normal way is to do things by aggregation
(FeaturePythonT<>), if you insist on doing it by sub-classing a further Python wrapper class is needed.
If only new properties are needed for the derived class, just declare FeaturePythonT<> and extend DocumentObjectPy in
python. see FemSolver example in Fem module analysis.
From the author’s point of view, it is recommended to use compatible layer such as “[Link]” (single python file) or “qtpy”
(PyPI package) to support various bindings: “PyQt4, PyQt5, PySide and PySide2”. Pyside2 is the preferred binding for
LGPL license and it is under heavy development as an official module for Qt.
The usage of “[Link]” or “qtpy” is similar. QtWidgets is a alias to QtGui for Qt4.
from Qt import QtCore, QtWidgets, QtGui
#from qtpy mport QtCore, QtWidgets, QtGui
For module developers, it is time to make the module python2 and python3 compatible in 2017.
discussion on python 3 compatibility
Yorik is working on this now, see python 3 fork at github [Link]
see discussion on forum: [Link] according to that discussion, string is the only
obstacle but overcomable.
The major changes from python 2 to python 3 excerpt from that discussion
• If using py3, you need minimum 3.3 (otherwise there are some useful unicode<->UTF8 functions not available yet).
• Python3 doesn’t have Int and Long anymore. Everything is Long. This is safe to use in py2 too (in python, it is still
called int, mind you, but it’s a long).
• By far the most important change: All python strings are now unicode. So all strings-related methods are gone.
Fortunately since @shoogen and @wmayer already made a great job at spreading the use of UTF8 everywhere, this
doesn’t give major problems, it even simplifies (no more of that question to use Latin1 or Utf8)
• PyObject->ob_type doesn’t exist anymore, use Py_TYPE(PyObject)
• Class definition (PyTypeObject) has changed a lot:
• different way to initialize it (PyVarObject_HEAD_INIT instead of PyObject_HEAD_INIT)
• tp_getattr and tp_setattr slots are being deprecated. Use tp_getattro and tp_setattro instead (they take a PyObject
instead of a char*)
• several numeric handlers have been removed: divide (everything is remainder now), long (everything is long now, but
the handler to keep is int :? ), coerce, oct and hex.
For python code (97% problems in FreeCAD source are print() and key of dict):
• print(“something”,“something else”) doesn’t work in py2 unless “from future import print_function”
• for key, value in [Link]() becomes for key,value in [Link](), works in py2 too
• except Error,message becomes except Error(message) works in py2 too
• import submodule becomes from . import submodule
Although there are several python binding tool recently, when FreeCAD was born, the choice was very limited. The python
API of FreeCAD is mostly created by hand. See for example all files ending with *[Link] in the source code.
The choice is discussed in forum thread: [Link]
However, the reason not to use SWIG or SIP is that they are a bit overkill and too complex. BTW, from one
version to another in SWIG there are always some slight internal changes which makes it very difficult to keep the
sources in shape that they work OK with different SWIG versions. We have made this experience with pivy – a
Python binding for Coin. This is a constant point of trouble we run into on different platforms Another even more
important reason is that we, in most cases, do not want to wrap the interface 1:1 of the Python and C++ class.
• C and C++ Python API Both <python.h> C API and pyCXX c++API are directly used. Wrapping FreeCAD cpp
code is kind of writing C module for python, emphasizing performance.
66 CHAPTER 5. INTRODUCTION TO PYTHON WRAPPING
• Qt wrapping tool sip is not a choice, since FreeCAD BaseClass is not derived from QObject. However, it is possible
to design all FreeCAD classes derived from QObject with pros and cons. FreeCAD can be run without GUI, so the
FreeCAD objects should not depend or are mixed with QObject.
• swig, It is used only to generate pivy objects from FreeCADGui. swig code can be found at the end of source file
src/Base/[Link] There is no stable ABI for wrapping, each time swig upgrade, even a mino upgrade from 3.0 to
3.1, a compilation is needed.
• boost::python in FreeCAD 0.17, boost::python is a dependent component for FreeCAD.
[Link]
Direct usage of C API is NOT recommended, since C API is not compatible for the migration from python 2.7 to python 3.3+
Recently, Python 3.x defined a set of Stable Application Binary Interface(ABI), see [Link]
If module developer wants to mimic some new feature from existent code, understanding of common API in python.h is
essential
Official document of python C API
• Include Files, Objects, Types and Reference Counts (Introduction)
• The Very High Level Layer (cpp structurs responds to common python objects)
• Reference Counting ()
• Exception Handling (set proper exception handling before return NULL)
general tutorial on [Link], before jumping into FreeCAD source code
Py++ uses GCC C++ compiler to parse C++ source files and allows you to expose C++ code to Python in quick and elegant
way using the [Link] library.
It uses the following steps to do so: - source code is passed to GCC-XML - GCC-XML passes it to GCC C++ compiler -
GCC-XML generates an XML description of a C++ program from GCC’s internal representation. - Py++ uses pygccxml
package to read GCC-XML generated file.
Pybind11 is a relative new project, similar with [Link]. Pybind11 uses C++11 new lang features to simply the wrapping
process, making it easier to write more pythonic interface. Pybind11 also has a project binder to provide automatic wrapping
code for Pybind11.
5.4. EXTENDING CPP CLASS FUNCTION IN PYTHON 67
For module developer who works only at the DocumentObject level, usage of FeaturePythonT could be sufficient without
touching PyObject*
FeaturePythonT Generic Python feature class which allows to behave every DocumentObject derived class as Python feature –
simply by subclassing. FeatureT
There are also a lot of helper structures and templates to ease with that tedious process. For example, in the
different src/Mod folders, when you see a [Link] file together with a [Link], the xml files contains a structure
that will automatically generate Py.h and [Link] files at build time, to which the *.[Link] will be merged.
It is possible to parse C++ header file to generate the interface definition like the XML file
!DocumentObjectPy__inherit__graph
This file is generated by src/Tools/generateTemaplates/[Link] out of the XML file
Automatic python wrapping code can be generated by python script in building tools.
src/Mod/Part/App/[Link] is are built by hand (which could be generate from text definition file or swig scanning
from header file in the future), then there is a cmake macro that converts them in Py.h and [Link] files at build time (an
accompanying *[Link] file must be present).
In the src/Mod/Part/App/[Link], this python type is registered to interpreter Base::Interpreter().addType(&Part::ConePy
::Type,partModule,"Cone"); which is implemented in src/Base/[Link]
void InterpreterSingleton::addType(PyTypeObject* Type,PyObject* Module, const char * Name)
{
// 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.
if (PyType_Ready(Type) < 0) return;
union PyType_Object pyType = {Type};
PyModule_AddObject(Module, Name, pyType.o);
}
Then this cpp type/class is registered into cpp type system in src/Mod/Part/App/[Link] Part::Cone
::init();
void BaseClass::init(void)
{
assert(BaseClass::classTypeId == Type::badType() && "don't init() twice!");
/* Make sure superclass gets initialized before subclass. */
/*assert(strcmp(#_parentclass_), "inherited"));*/
/*Type parentType(Type::fromName(#_parentclass_));*/
/*assert(parentType != Type::badType() && "you forgot init() on parentclass!");*/
It is possible to extend cpp DocumentObject in Python. see discussion on forum What is relation between
Fem/App/FemAnalysis.h and _FemAnalysis.py
Proxy is a property of App::PropertyPythonObject Proxy;. Both methods defined in Python and cpp will be called, see []
Python needs not to specify which class is derived, just provide the methods(API).
Todo: This section is not completed!!! Sequence? derived from *Imp
def attach(self, vobj):
[Link] = vobj
[Link] = [Link]
[Link] = None
The ViewProvider attachment happens here src/Gui/[Link]#L299
protected:
virtual void onChanged(const App::Property* prop) {
if (prop == &Proxy) {
if (ViewProviderT::pcObject && ![Link]().is(Py::_None())) {
if (!_attached) {
_attached = true;
imp->attach(ViewProviderT::pcObject);
ViewProviderT::attach(ViewProviderT::pcObject);
// needed to load the right display mode after they're known now
ViewProviderT::[Link]();
ViewProviderT::setOverrideMode(viewerMode);
}
ViewProviderT::updateView();
}
}
else {
imp->onChanged(prop);
ViewProviderT::onChanged(prop);
}
}
src/App/FeaturePythonPyImp.h FeaturePyT
// Special Feature-Python classes
typedef FeaturePythonT<DocumentObject> FeaturePython;
typedef FeaturePythonT<GeoFeature > GeometryPython;
src/App/FeaturePython.h
// Helper class to hide implementation details
class AppExport FeaturePythonImp
...
template <class FeatureT>
class FeaturePythonT : public FeatureT
{
...
/// recalculate the Feature
virtual DocumentObjectExecReturn *execute(void) {
try {
bool handled = imp->execute();
if (!handled)
return FeatureT::execute();
}
5.4. EXTENDING CPP CLASS FUNCTION IN PYTHON 69
FemSolverObject is derived from DocumentObject without any property added in C++, but it can be extended in Python.
Look at the template class FeaturePythonT, it is of the form: src/App/FeaturePython.h
template <class FeatureT>
class FeaturePythonT : public FeatureT
template FeatureT is the parent class of FeaturePythonT.
src/Mod/Fem/App/FemSolverObject.h class AppFemExport FemSolverObject : public App::DocumentObject typedef
App::FeaturePythonT<FemSolverObject> FemSolverObjectPython; FemSolverObjectPython is a type of sub-class of
Fem::Fem::FemSolverObject.
src/Mod/Fem/App/[Link]
PyObject *FemSolverObject::getPyObject()
{
if ([Link](Py::_None())){
// ref counter is set to 1
70 CHAPTER 5. INTRODUCTION TO PYTHON WRAPPING
namespace App {
/// @cond DOXERR
PROPERTY_SOURCE_TEMPLATE(Fem::FemSolverObjectPython, Fem::FemSolverObject)
template<> const char* Fem::FemSolverObjectPython::getViewProviderName(void) const {
return "FemGui::ViewProviderSolverPython";
}
5.4.4 Gui::ViewProviderPythonFeatureT
PyObject* is passed in as argument and returned by C wrapper function. Python type checking and argument validation
should be done in this function before try-catch block. In addition, proper exception should be set before return 0, which
means *NULL PyObject *.
// Path + radius
if (!PyArg_ParseTuple(args, "O!d|sii", &(TopoShapePy::Type), &pshape, &radius, &scont, &maxdegree, &maxsegm
return 0;
std::string str_cont = scont;
int cont;
if (str_cont == "C0")
cont = (int)GeomAbs_C0;
else if (str_cont == "C1")
cont = (int)GeomAbs_C1;
else if (str_cont == "C2")
cont = (int)GeomAbs_C2;
else if (str_cont == "C3")
cont = (int)GeomAbs_C3;
else if (str_cont == "CN")
cont = (int)GeomAbs_CN;
else if (str_cont == "G1")
cont = (int)GeomAbs_G1;
else if (str_cont == "G2")
cont = (int)GeomAbs_G2;
else
cont = (int)GeomAbs_C0;
try {
const TopoDS_Shape& path_shape = static_cast<TopoShapePy*>(pshape)->getTopoShapePtr()->_Shape;
TopoShape myShape(path_shape);
TopoDS_Shape face = [Link](radius, tolerance, cont, maxdegree, maxsegment);
return new TopoShapeFacePy(new TopoShape(face));
}
catch (Standard_Failure) {
Handle_Standard_Failure e = Standard_Failure::Caught();
PyErr_SetString(PartExceptionOCCError, e->GetMessageString());
return 0;
}
}
/* registration table */
struct PyMethodDef Part_methods[] = {
{"open" ,open ,METH_VARARGS,
"open(string) -- Create a new document and load the file into the document."},
...
72 CHAPTER 5. INTRODUCTION TO PYTHON WRAPPING
/** If the application starts we release immediately the global interpreter lock
* (GIL) once the Python interpreter is initialized, i.e. no thread -- including
* the main thread doesn't hold the GIL. Thus, every thread must instantiate an
* object of PyGILStateLocker if it needs to access protected areas in Python or
* areas where the lock is needed. It's best to create the instance on the stack,
* not on the heap.
*/
class BaseExport PyGILStateLocker
{
public:
PyGILStateLocker()
{
gstate = PyGILState_Ensure();
}
~PyGILStateLocker()
{
PyGILState_Release(gstate);
}
private:
PyGILState_STATE gstate;
};
/**
* If a thread holds the global interpreter lock (GIL) but runs a long operation
* in C where it doesn't need to hold the GIL it can release it temporarily. Or
* if the thread has to run code in the main thread where Python code may be
* executed it must release the GIL to avoid a deadlock. In either case the thread
* must hold the GIL when instantiating an object of PyGILStateRelease.
* As PyGILStateLocker it's best to create an instance of PyGILStateRelease on the
* stack.
*/
class BaseExport PyGILStateRelease
{
public:
PyGILStateRelease()
{
// release the global interpreter lock
state = PyEval_SaveThread();
}
~PyGILStateRelease()
{
// grab the global interpreter lock again
PyEval_RestoreThread(state);
}
private:
PyThreadState* state;
};
Modular design is a key design principle for a successful software architecture. Salome platform is an Open source CAE
platform with geometry building, meshing and FEM and CFD solver modules. Salome has a dummy plugin module, as a start
point of a new module. Samole plugin has a standard folder structure
FreeCAD has the same infrastructure, template, and a python code to generate a new module from the template.
There are workbench template in official source repository, c++ template folder at [src/Tools/_TEMPLAT_] and pure python
template [src/Tools/_TEMPLATPY_]. Those templates give the module developers the recommended/standard module
directory structure and file names.
A new module folder structure with essential code for the new module can be generated by fcbt script
fcbt is the acrynom for FreeCAD build tool, it can replace dummy names with your module name (CFD in my example
below) in source names.
usage of [Link] And example of output:
qingfeng@qingfeng-ubuntu:/opt/FreeCAD/src/Tools$ python [Link]
73
74 CHAPTER 6. MODULAR DESIGN OF FREECAD (PLUGIN SYSTEM)
Insert command: CM
Please enter a name for your application:Cfd
Copying files... from _TEMPLATE_ folder and modify them
...
Modifying files...
../Mod/Cfd/[Link]
../Mod/Cfd/[Link]
../Mod/Cfd/[Link]
../Mod/Cfd/App/PreCompiled.h
../Mod/Cfd/App/[Link]
../Mod/Cfd/App/[Link]
../Mod/Cfd/App/[Link]
../Mod/Cfd/App/[Link]
../Mod/Cfd/[Link]
../Mod/Cfd/Gui/PreCompiled.h
../Mod/Cfd/Gui/[Link]
../Mod/Cfd/Gui/[Link]
../Mod/Cfd/Gui/[Link]
../Mod/Cfd/Gui/[Link]
../Mod/Cfd/Gui/[Link]
../Mod/Cfd/Gui/[Link]
../Mod/Cfd/Gui/Workbench.h
../Mod/Cfd/Gui/Resources/[Link]
Modifying files done.
Cfd module created successfully.
• Gui/[Link]
• Gui/[Link]
within function of initFemGui(): - Fem_Import_methods[] - load [Link], - workbench and ViewProvider
init(), - Base::Interpreter().loadModule('python modules') - register preferences pages - load resource,
mainly translation
• Gui/[Link] wrapping code to export functions to python
/* registration table */ struct PyMethodDef FemGui_Import_methods[]
• Gui/PreCompiled.h include some headers shared by most source code files
• Gui/[Link] contains single line #include "PreCompiled.h"
• Gui/[Link] cmake config file to generate dll or so shared dynamically linkable lib
• Gui/[Link] to add Toolbar and MenuItem to module workbench
• Gui/Resources/[Link] file contains translattion for Qt widgets
•
The template module is organized similar with other official Module in FreeCAD source in the src/Mod folder. Gui related
C++ code is located in “Gui” subfolder, while nonGui code are put into “App” subfolder.
The generate module will be in a minimal runnable/compilable state without any specific functions. Extra source files and
code should be included and trimmed by module developer .
Some good example and best practice should be included.
This section (Create Workbench step by step) is a copy of FreeCAD doxygen documentation on workbench
FreeCAD provides the possibility to have one or more workbenches for a module.
A workbench changes the appearance of the main window in that way that it defines toolbars, items in the toolbox,
menus or the context menu and dockable windows that are shown to the user. The idea behind this concept is
that the user should see only the functions that are required for the task that he is doing at this moment and not
to show dozens of unneeded functions which the user never uses.
Here follows a short description of how your own workbench can be added to a module.
First you have to subclass either Workbench or StdWorkbench and reimplement the methods setupMenuBar(),
setupToolBars(), setupCommandBars() setupDockWindows().
The difference between both classes is that these methods of Workbench are pure virtual while StdWorkbench defines already
the standard menus and toolbars, such as the ‘File’, ‘Edit’, . . . , ‘Help’ menus with their common functions.
If your class derives from Workbench then you have to define your menus, toolbars and toolbox items from scratch while
deriving from StdWorkbench you have the possibility to add your preferred functions or even remove some unneeded functions.
class MyWorkbench : public StdWorkbench
{
...
protected:
MenuItem* setupMenuBar() const
{
MenuItem* root = StdWorkbench::setupMenuBar();
// your changes
return root;
}
ToolBarItem* setupToolBars() const
76 CHAPTER 6. MODULAR DESIGN OF FREECAD (PLUGIN SYSTEM)
{
ToolBarItem* root = StdWorkbench::setupToolBars();
// your changes
return root;
}
ToolBarItem* setupCommandBars() const
{
ToolBarItem* root = StdWorkbench::setupCommandBars();
// your changes
return root;
}
};
//or
If you want to customize your workbench by adding or removing items you can use the ToolBarItem class for customizing
toolbars and the MenuItem class for menus. Both classes behave basically the same. To add a new menu item you can do it
as follows
Toolbars can be customized the same way unless that you shouldn’t create subitems (there are no subtoolbars).
6.2. WORKBENCH FRAMEWORK: KEY TO MODULAR DESIGN 77
Once you have implemented your workbench class you have to register it to make it known to the FreeCAD core system.
You must make sure that the step of registration is performed only once. A good place to do it is e.g. in the global
function initMODULEGui in [Link] where MODULE stands for the name of your module. Just add the line
MODULEGui::MyWorkbench::init(); somewhere there.
Though your workbench has been registered now, at this stage you still cannot invoke it yet. Therefore you must create
an item in the list of all visible workbenches. To perform this step you must open your [Link] (a Python file) and do
some adjustments. The file contains already a Python class MODULEWorkbench that implements the Activate() method (it
imports the needed library). You can also implement the GetIcon() method to set your own icon for your workbench, if not,
the default FreeCAD icon is taken, and finally the most important method GetClassName(). that represents the link between
Python and C++. This method must return the name of the associated C++ including namespace. In this case it must the
string ModuleGui::MyWorkbench. At the end you can change the line from
[Link]("MODULE design",MODULEWorkbench()) to [Link]("My workbench",MODULEWorkbench())
or whatever you want.
[Link] Note
You must make sure to choose a unique name for your workbench (in this example “My workbench”). Since FreeCAD doesn’t
provide a mechanism for this you have to care on your own.
One of the key concepts of the workbench framework is to load a module at runtime when the user needs some function
that it provides. So, if the user doesn’t need a module it never gets loaded into RAM. This speeds up the startup procedure
of FreeCAD and saves memory. At startup FreeCAD scans all module directories and invokes [Link]. So an item for
a workbench gets created. If the user clicks on such an item the matching module gets loaded, the C++ workbench gets
registered and activated.
The user is able to modify a workbench (Edit|Customize). E.g. he can add new toolbars or items for the toolbox and add his
preferred functions to them. But he has only full control over “his” toolbars, the default workbench items cannot be modified
or even removed.
FreeCAD provides also the possibility to define pure Python workbenches. Such workbenches are temporarily only and are
lost after exiting the FreeCAD session. But if you want to keep your Python workbench you can write a macro and attach it
with a user defined button or just perform the macro during the next FreeCAD session. Here follows a short example of how
to create and embed a workbench in Python
w=Workbench() # creates a standard workbench (the same as StdWorkb
[Link] = "My Workbench" # the text that will appear in the combo box
dir(w) # lists all available function of the object
[Link](w) # Creates an item for our workbenmch now
# Note: We must first add the workbench to run some
# Then we are ready to customize the workbench
list = ["Std_Test1", "Std_Test2", "Std_Test3"] # creates a list of new functions
[Link]("Test functions", list) # creates a new menu with these functions
[Link]("Test", list) # ... and also a new toolbar
It is error-prone to mix C++ and Python. Fortunately, it is possible using Python only to develop plugin, Cfd or ‘plot’
workbench is the example.
class CfdWorkbench(Workbench):
"CFD workbench object"
def __init__(self):
self.__class__.Icon = [Link]() + "Mod/Fem/Resources/icons/[Link]"
self.__class__.MenuText = "CFD"
self.__class__.ToolTip = "CFD workbench"
def Initialize(self) :
import Fem
import FemGui
import _CommandCfdAnalysis
import _CommandCfdSolverFoam
import _CommandCfdSolverControl
import _CommandCfdResult
def GetClassName(self):
return "Gui::PythonWorkbench"
[Link](CfdWorkbench())
Icon could be XPM embedded into source code, or just pick up one from other module. Python workbench could has its own
“Resource” folder under module folder, instead of “Mod/ModName/Gui/Resource”.
Python [Link] registered import and export file types, and “[Link]” append command class or other UI elements to
module workbench
C++ side registered type and export to python, a similar but much simpler process as [src/App/[Link]] and
[src/App/[Link]]
For example, src/Mod/Fem/Gui/[Link] registered all viewProvider types, C++ commands classes defined in
[Link], load extra python module.
Tool->Parameter has some windows register style/parameter setting without design QT dialog ui file [images/FreeCAD_parameter_edit
/**
* Constructs a DlgSettingsUnitsImp which is a child of 'parent', with the
* name 'name' and widget flags set to 'f'
*/
DlgSettingsUnitsImp::DlgSettingsUnitsImp(QWidget* parent)
: PreferencePage( parent ), ui(new Ui_DlgSettingsUnits)
{
ui->setupUi(this);
//fillUpListBox();
ui->tableWidget->setVisible(false);
}
void DlgSettingsUnitsImp::saveSettings()
{
// must be done as very first because we create a new instance of NavigatorStyle
// where we set some attributes afterwards
auto hGrp = App::GetApplication().GetParameterGroupByPath
("User parameter:BaseApp/Preferences/Units") ;
hGrp->SetInt("UserSchema", ui->comboBox_ViewSystem->currentIndex());
hGrp->SetInt("Decimals", ui->spinBoxDecimals->value());
Base::UnitsApi::setDecimals(ui->spinBoxDecimals->value());
}
void DlgSettingsUnitsImp::loadSettings()
{
auto hGrp = App::GetApplication().GetParameterGroupByPath
("User parameter:BaseApp/Preferences/Units") ;
ui->comboBox_ViewSystem->setCurrentIndex(hGrp->GetInt("UserSchema", 0));
ui->spinBoxDecimals->setValue(hGrp->GetInt("Decimals", Base::UnitsApi::getDecimals()));
}
p = [Link]("User parameter:BaseApp/Preferences/Mod/Draft")
p = [Link]("TeighaFileConverter")
Some example utility functions have been defined in [src/mod/Draft/[Link]]
#getParamType(param) is also defined in this file
def getParam(param,default=None):
"getParam(parameterName): returns a Draft parameter value from the current config"
p = [Link]("User parameter:BaseApp/Preferences/Mod/Draft")
80 CHAPTER 6. MODULAR DESIGN OF FREECAD (PLUGIN SYSTEM)
t = getParamType(param)
#print("getting param ",param, " of type ",t, " default: ",str(default))
if t == "int":
if default == None:
default = 0
return [Link](param,default)
elif t == "string":
if default == None:
default = ""
return [Link](param,default)
elif t == "float":
if default == None:
default = 0
return [Link](param,default)
elif t == "bool":
if default == None:
default = False
return [Link](param,default)
elif t == "unsigned":
if default == None:
default = 0
return [Link](param,default)
else:
return None
def setParam(param,value):
"setParam(parameterName,value): sets a Draft parameter with the given value"
p = [Link]("User parameter:BaseApp/Preferences/Mod/Draft")
t = getParamType(param)
if t == "int": [Link](param,value)
elif t == "string": [Link](param,value)
elif t == "float": [Link](param,value)
elif t == "bool": [Link](param,value)
elif t == "unsigned": [Link](param,value)
protected:
/** Restores the preferences
* Must be reimplemented in any subclasses.
*/
6.3. MODULE PREFERENCE 81
PrefWidget();
virtual ~PrefWidget();
private:
QByteArray m_sPrefName;
QByteArray m_sPrefGrp;
// friends
friend class Gui::WidgetFactoryInst;
};
example of making preference page UI in python, no extra python code is needed to fill the UI with parameter data or
collect/save data into file
2. Sometimes the static approach is not sufficient because you want to implement a special program logic.
from PySide import QtGui
class MyPrefPage:
def __init__(self, parent=None):
print ("Create pref page")
[Link] = [Link]()
[Link]("My pref page")
def saveSettings(self):
print ("saveSettings") # [Link]('...').setInt(param, value)
def loadSettings(self):
print ("loadSettings") # value = [Link]('...').getInt(param)
[Link](MyPrefPage,"General")
Preference page can be loaded t workbench in Python in [Link] [src/mod/Draft/[Link]]
import Draft_rc
[Link](":/ui/[Link]","Import-Export")
[Link](":/ui/[Link]","Import-Export")
[Link](":/ui/[Link]","Import-Export")
[Link](":/ui/[Link]","Import-Export")
[Link]("Import-Export",2)
path for saving user preference is ~/.FreeCAD/[Link], see the xml content below. There is another file called
~/.FreeCAD/[Link], but it is not recommended to edit by module developers
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<FCParameters>
<FCParamGroup Name="Root">
<FCParamGroup Name="BaseApp">
<FCParamGroup Name="Preferences">
<FCParamGroup Name="Units">
<FCInt Name="UserSchema" Value="0"/>
<FCInt Name="Decimals" Value="2"/>
to retrieve the setting which is a combobox UI, “0” (standard: mm-kg-s) [Link]("User parameter:BaseApp/Preferences/
82 CHAPTER 6. MODULAR DESIGN OF FREECAD (PLUGIN SYSTEM)
Which is very useful to scale and export geometry, mesh to MKS unit scheme (metre-kg-second).
...
TaskFemConstraintForce.h
...
fc_wrap_cpp(FemGui_MOC_SRCS ${FemGui_MOC_HDRS})
• symbols_library:
OpenCASCADE Technoloyg (OCCT) is kind of only full-feature, industrial-strength, open source CAD kernel, on which
FreeCAD and Salome are built on. OCCT has been released under LGPL in 2013, not from OCC license any more, see
[Link]
The official (doxygen generated) document is here [Link]
all OCCT classes organized in hierarchical form corresponding to OCCT structure (module -> toolkit -> package
-> class).
85
86 CHAPTER 7. CAD MODULES IN FREECAD
When FreeCAD was designed in early 2000s, OCAF and VIS is not available, therefore, the FreeCAD team design their own
infrastructure modules (Base, App and Gui).
OCCT ’s VIS component provides shapes via VTK library, in FreeCAD 3D shape renering is done by Coin3D/openInventor.
7.2.3 TopoDS_Shape
Topology defines relationships between simple geometric entities. A shape, which is a basic topological entity, can be divided
into components (sub-shapes):
• Vertex - a zero-dimensional shape corresponding to a point;
• Edge - a shape corresponding to a curve and bounded by a vertex at each extremity;
• Wire - a sequence of edges connected by their vertices;
• Face - a part of a plane (in 2D) or a surface (in 3D) bounded by wires;
• Shell - a collection of faces connected by edges of their wire boundaries;
• Solid - a finite closed part of 3D space bounded by shells;
• Compound solid - a collection of solids connected by faces of their shell boundaries.
Part module is coded in C++ for better performance (another reason: there is no python wrapping to the underlying CAD
kernel: OpenCASCASE when FreeCAD was designed), but there is the official example of pure python implemented Feature
like Plot.
7.3.2 src/Mod/Part/App/PartFeature.h
}
src/Mod/Part/App/[Link]
7.3. PART MODULE 87
Feature::Feature(void)
{
ADD_PROPERTY(Shape, (TopoDS_Shape()));
}
PyObject *Feature::getPyObject(void)
{
if ([Link](Py::_None())){
// ref counter is set to 1
PythonObject = Py::Object(new PartFeaturePy(this),true);
}
return Py::new_reference_to(PythonObject);
}
src/Mod/PartDesign/App/Feature.h
class PartDesignExport Feature : public Part::Feature static TopoDS_Shape getSolid(const TopoDS_Shape&);
src/Mod/PartDesign/App/FeaturePad.h FeaturePad<- FeatureAdditive <- SketchBased <- PartDesign::Feature
App::PropertyLinkSub UpToFace; // refer to face (subfeature) of another Feature
App::PropertyLink Sketch; // 2D sketch for Pad
88 CHAPTER 7. CAD MODULES IN FREECAD
7.3.6 TechDraw
Acknowledge of Fem module developers: Bernd, PrzemoF, etc., of course, three core developers.
This module is usable in v0.16 (install netgen and calculix first) and extended dramatically to multiple CAE solver support in
v0.17.
Basically, the whole process is to mesh the solid part into small element (currently tetragen only), add boundary condition and
material information, write all these information into case file that external solver can accept, launch the external solver (only
Calculix is supported for the time being), finally load result from solver output file and show result in FreeCAD workbench.
For example, error message “CalculiX ccx binary not found! Please set it manually in FEM preferences.” is solved by
For Ubuntu 64bit linux, ccx_2.10 single file executable can be downloaded from [Link] the dependency
[Link] can be made available from symbolic link:
ln -s <source_path> /lib/x86_64-linux-gnu/[Link]
Although most of the class started with the prefix of “Fem”, they works for various CAE application( mechanical, fluidic,
electromagnetic).
89
90 CHAPTER 8. FEM MODULE SOURCE CODE ANALYSIS
Fem module specific file types import and export, this is implemented in [src/Mod/Fem/App/[Link]] Various mesh
format can be imported and exported, see the list in FreeCAD wiki for Fem
class Module : public Py::ExtensionModule<Module>
{
public:
Module() : Py::ExtensionModule<Module>("Fem")
{
add_varargs_method("open",&Module::open,
"open(string) -- Create a new document and a Mesh::Import feature to load the file into the documen
);
add_varargs_method("insert",&Module::insert,
"insert(string|mesh,[string]) -- Load or insert a mesh into the given or active document."
);
add_varargs_method("export",&Module::exporter,
"export(list,string) -- Export a list of objects into a single file."
);
add_varargs_method("read",&Module::read,
"Read a mesh from a file and returns a Mesh object."
);
add_varargs_method("show",&Module::show,
"show(shape) -- Add the shape to the active document or create one if no document exists."
);
initialize("This module is the Fem module."); // register with Python
}
functionality of src/Mod/Fem/Gui/[Link]
• FemGui_Import_methods[] all method in python module FemGui should be registered
• load commands defined in [Link],
• workbench and ViewProvider types init(),
• Base::Interpreter().loadModule('some python modules in Mod/Fem folder')
• Register preferences pages new Gui::PrefPageProducer<FemGui::DlgSettingsFemImp> ("FEM");
• load resource, mainly icons and translation
In short, python scripts for Fem should be loaded/imported in [Link] to avoid cyclic dependency.
see Forum discussion: cyclic dependency FemCommands and FemGui modules
There seems a cyclic dependency. When you try to load [Link] it internally tries to load FemGui. However, at
this stage [Link] is not yet loaded and FemGui also tries to load [Link].
Then there are two flaws inside initFemGui:
1. Base::Interpreter().loadModule() MUST be inside a try/catch block and in case an exception occurs the initFemGui
must be aborted and an import error must be raised. Fixed with git commit abd6e8c438c
8.3. KEY CLASSES ANALYSIS 91
2. The FemGui must not be initialized before dependent modules are fully loaded. Fixed with git commit 60c8180079f20
The latter fix currently causes the FemGui not to load any more but that’s because of the cyclic dependency. IMO, there are
two ways to fix:
1. Do not load any of the Python module inside initFemGui because I don’t see why they should be needed there. It’s
much better to move this to the Initialize() method of the Workbench class ([Link])
2. Alternatively, make sure that the Python modules loaded inside initFemGui does not load FemGui in the global scope
but do it locally where it’s really needed.
This is the container DocumentObject in this module. It is NOT a DocumentGroup object before the commit (id ce68094,
on Oct 2017 by wwmayer, inherit FemAnalysis from DocumentObjectGroup and remove Member property, see https:
//[Link]/FreeCAD/FreeCAD/commit/ce6809415b45d760961eddb58e7c4544b602495a). After refactering, FemAnalysis is
derived from DocumentObjectGroup directly, the previous Member attribute is replaced by Group.
New FemObjects will be inserted by [Link]().addObject(...), instead of python list operation on
Member.
It accepts drag-and-drop of only FEM specific DocumentObjects. Only Fem related DocumentObject can be dragged into,
see ViewProviderFemAnalysis::canDragObject in src/Mod/Fem/Gui/ViewProviderAnalysis.h. And any new Fem specific
DocumentObject should registered here.
bool ViewProviderFemAnalysis::canDragObject(App::DocumentObject* obj) const
{
if (!obj)
return false;
if (obj->getTypeId().isDerivedFrom(Fem::FemMeshObject::getClassTypeId()))
return true;
else if (obj->getTypeId().isDerivedFrom(Fem::FemSolverObject::getClassTypeId()))
return true;
else if (obj->getTypeId().isDerivedFrom(Fem::Constraint::getClassTypeId()))
return true;
else if (obj->getTypeId().isDerivedFrom(Fem::FemSetObject::getClassTypeId()))
return true;
else if (obj->getTypeId().isDerivedFrom(Base::Type::fromName("Fem::FeaturePython")))
return true;
else if (obj->getTypeId().isDerivedFrom(App::MaterialObject::getClassTypeId()))
return true;
else
return false;
}
It has no 3D representation in Inventor/Coin scenegraph, different from FemMeshObject or Fem::Constraint. It has an
Documdent Observer in GUI part. see src/Mod/Fem/Gui/ActiveAnalysisObserver.h It is a singleton instance static
ActiveAnalysisObserver* instance();, from which [Link]() is possible from python.
see void ActiveAnalysisObserver::setActiveObject(Fem::FemAnalysis* fem) in src/Mod/Fem/Gui/[Link]
for activeView and activeDocument are managed
namespace FemGui {
92 CHAPTER 8. FEM MODULE SOURCE CODE ANALYSIS
void setActiveObject(Fem::FemAnalysis*);
Fem::FemAnalysis* getActiveObject() const;
8.3.2 [src/Mod/Fem/[Link]]
[src/Mod/Fem/Gui/FemResultObject.h] defined several properties to hold data like: Time, Temperature, Displacement,etc.
Result mesh can be different from mesh written to solver. It is defined only for solid mechanics, not for fluid dynamics.
This is class has implemented FeatureT<> template, thereby, it can be extended in python into CfdResult for CFD module.
This class defines necessary property to show result, e.g. contour, in 3D scene. This class should be extended in python to
deal with result from different solver (different result file type).
Idealy, this class should be a container without any domain specific properties, just like FemSolverObject, mechanical field
properties should be made in Python
Bottom-up analysis of this Object:
1. FemResultObject is derived from DocumdentObject with some properties, defined in src/Mod/Fem/App/FemResultObject.h
A list of property can be shared by CFD or Mechancial analysis implemented in src/Mod/Fem/App/[Link].
Most of code is standard, with the defined properties instantiated in constructor.
ADD_PROPERTY_TYPE(Mesh,(0), "General",Prop_None,"Link to the corresponding mesh");
ADD_PROPERTY_TYPE(NodeNumbers,(0), "Data",Prop_None,"Numbers of the result nodes");
ADD_PROPERTY_TYPE(Stats,(0), "Fem",Prop_None,"Statistics of the results");
ADD_PROPERTY_TYPE(Time,(0), "Fem",Prop_None,"Time of analysis incement");
Mechancial analysis should be moved into [src/Mod/Fem/[Link]]
App::PropertyIntegerList ElementNumbers;
/// Link to the corresponding mesh
App::PropertyLink Mesh;
/// Stats of analysis
App::PropertyFloatList Stats;
/// Displacement vectors of analysis
App::PropertyVectorList DisplacementVectors;
/// Lengths of displacement vestors of analysis
App::PropertyFloatList DisplacementLengths;
/// Von Mises Stress values of analysis
8.3. KEY CLASSES ANALYSIS 93
App::PropertyFloatList StressValues;
2. ViewProvider: [src/Mod/Fem/Gui/ViewProviderResult.h] and [src/Mod/Fem/Gui/[Link]]
3. add Command class and appended to workbench menu in Python
[src/Mod/Fem/_CommandShowResult.py]
class _CommandMechanicalShowResult:
"the Fem JobControl command definition"
def GetResources(self):
return {'Pixmap': 'fem-result',
'MenuText' : QtCore.QT_TRANSLATE_NOOP("Fem_Result", "Show result"),
'Accel' : "S, R",
'ToolTip' : QtCore.QT_TRANSLATE_NOOP("Fem_Result", "Show result information of an analysis")}
def Activated(self):
self.result_object = get_results_object([Link]())
if not self.result_object:
[Link](None, "Missing prerequisite", "No result found in active Analysis")
return
taskd = _ResultControlTaskPanel()
[Link](taskd)
def IsActive(self):
return [Link] is not None and results_present()
In this class, sPixmap = "fem-result" and helper function get_results_object is worth of explanation src/Mod/Fem/Gui/Resources
SVG file naming: lowercase with module name as suffix, connected with dash
def get_results_object(sel):
if (len(sel) == 1):
if sel[0].isDerivedFrom("Fem::FemResultObject"):
return sel[0]
for i in [Link]().Member:
if([Link]("Fem::FemResultObject")):
return i
return None
4. TaskView: defined in python: [src/Mod/Fem/_TaskPanelShowResult.py]
5. python script to read result data file: [src/Mod/Fem/[Link]] and other solver result writer
src/Mod/Fem/App/[Link] src/Mod/Fem/App/[Link]
loading FemResult into pipe happens in Command Class CmdFemPostPipelineFromResult
//poly data is the only data we can visualize, hence every post processing object needs to expose it
class AppFemExport FemPostObject : public App::GeoFeature
{
Fem::PropertyPostDataObject Data;
}
App::PropertyLink Input;
94 CHAPTER 8. FEM MODULE SOURCE CODE ANALYSIS
8.4 FemConstraint
• DocumentObject: FemConstraint is derived from
• ViewProvider: ViewProviderFemConstraint
• TaskPanel and TaskDlg:
• [Link]: adding those source files into [Link]
• SVG icon file in researce folder and XML file
Actually drawing is defined in [Link]
createSymmetry(sep, HEIGHT, WIDTH);
createPlacement(pShapeSep, b, SbRotation(SbVec3f(0,1,0), ax));
// gear , change colorProperty, it is a new , aspect ratio, it is new constraint
This is an DocumentObject with basic 3D Inventor, and a good start point to learn drawing in 3D scence. Fem::Constraint is
the base class for all the other specific constraints, or boundary conditions, in other domain like CFD.
class FemGuiExport ViewProviderFemConstraint : public Gui::ViewProviderGeometryObject
#define CUBE_CHILDREN 1
void ViewProviderFemConstraint::createCube(SoSeparator* sep, const double width, const double length, const dou
{
SoCube* cube = new SoCube();
cube->[Link](width);
cube->[Link](length);
cube->[Link](height);
sep->addChild(cube);
}
SoSeparator* ViewProviderFemConstraint::createCube(const double width, const double length, const double height
{
SoSeparator* sep = new SoSeparator();
createCube(sep, width, length, height);
return sep;
}
void ViewProviderFemConstraint::updateCube(const SoNode* node, const int idx, const double width, const double
{
8.5. IMPORT AND EXPORT FILE FORMATS 95
8.4.3 TaskFemConstraint
src/Mod/Fem/Gui/TaskFemConstraint.h onChange(): Constraint only record geometry reference, not femCellSet, accept()
add into Analysis src/Mod/Fem/Gui/[Link]
#include "moc_TaskFemConstraintPressure.cpp"
[/src/Mod/Fem/Gui/TaskFemConstraintPressure.h] task panel to select geometry face the pressure constrain is applied to,
also the pressure magnitude and direction.
class TaskFemConstraintPressure : public TaskFemConstraint
class TaskDlgFemConstraintPressure : public TaskDlgFemConstraint accept/reject/open
It is possible to set up boundary condition with imported mesh, given the original part is still in the document file. Unfortunately,
boundary facets are not importable into FreeCAD (new data structure is needed to hold such data within mesh), thereby
boundary face mesh is extracted by distance detection of meshcell with 3D part surfaces in FreeCAD.
Procedure: 1. export geometry to external meshing tool like Ansys and Salome for advanced function like boundary layer
setup, which is not available in FreeCAD yet. 2. mesing and export into a file format supported by FreeCAD import (vtk,
96 CHAPTER 8. FEM MODULE SOURCE CODE ANALYSIS
unv) or supported by CAE solver directly for case setup. 3. imported back to FreeCAD for FEM/CFD case setup within
FreeCAD, then following the FEM module working procedure
8.6.2 src/Mod/Fem/App/FemMesh.h
FreeCAD Fem mesh is based on 3party lib: SMESH, the meshing facility used in Salome. This SMESH is powerful but also
chanllenging. It is a deep water zone, just ignore this class if you are not going to extend Fem meshing facility.
SMDS_Mesh
SMESH_Mesh
SMESHDS_Mesh
SMESH_SMDS.hxx
Important classes:
class AppFemExport FemMesh : public Data::ComplexGeoData
class AppFemExport FemMeshObject : public App::GeoFeature
class AppFemExport FemMeshShapeObject : public FemMeshObject
class AppFemExport FemMeshShapeNetgenObject : public FemMeshShapeObject //with Fineness property
8.6.3 [src/Mod/Fem/[Link]]
With Gmsh, it is possible to specify mesh length scale for subfeatures like faces/edges. [Link] and FemMesh-
[Link]
[src/Mod/Fem/[Link]]
def makeFemMeshGmsh(name="FEMMeshGMSH"):
'''makeFemMeshGmsh(name): makes a GMSH FEM mesh object'''
obj = [Link]("Fem::FemMeshObjectPython", name)
_FemMeshGmsh._FemMeshGmsh(obj)
if [Link]:
import _ViewProviderFemMeshGmsh
_ViewProviderFemMeshGmsh._ViewProviderFemMeshGmsh([Link])
return obj
8.7. FEMRESULT AND VTK BASED POST-PROCESSING PIPELINE 97
Gmsh is supported mainly by [src/Mod/Fem/_FemMeshGmsh.py] which defines various properties for gmsh settigns.
[src/Mod/Fem/[Link]] will find the executable binary gmsh and write input file *.geo, which contains all
info needed to generate a mesh
def create_mesh(self):
print("\nWe gone start GMSH FEM mesh run!")
print(' Part to mesh: Name --> ' + self.part_obj.Name + ', Label --> ' + self.part_obj.Label + ', ShapeType
print(' CharacteristicLengthMax: ' + str([Link]))
print(' CharacteristicLengthMin: ' + str([Link]))
print(' ElementOrder: ' + [Link])
self.get_dimension()
self.get_tmp_file_paths()
self.get_gmsh_command()
self.get_group_data()
self.write_part_file() #self.part_obj.[Link](self.temp_file_geometry)
self.write_geo() # gmsh input file, containing all info needed for gen mesh
error = self.run_gmsh_with_geo() # gmsh *.geo
self.read_and_set_new_mesh() # load unv file into FemMeshObject
return error
src/Mod/Fem/Gui/[Link] nodeset
FemSetObject::FemSetObject()
{
ADD_PROPERTY_TYPE(FemMesh,(0), "MeshSet link",Prop_None,"MeshSet the set belongs to");
}
src/Mod/Fem/Gui/[Link] nodeset
related files:
src/Mod/Fem/App/FemPostObject.h src/Mod/Fem/App/FemPostPipeline.h src/Mod/Fem/App/FemPostFilter.h
src/Mod/Fem/App/FemPostFunction.h
Task panel and view providers in src/Mod/Fem/Gui
It could be thought of miniature paraview pipeline. Implemented in cpp only, perhaps for speed concern.
8.8 PreferencePage
• src/Mod/Fem/Gui/DlgSettingsFemGmshImp.h
• src/Mod/Fem/Gui/[Link]
#include "PreCompiled.h"
#include "Gui/Application.h"
#include "DlgSettingsFemGmshImp.h"
#include <Gui/PrefWidgets.h>
DlgSettingsFemGmshImp::~DlgSettingsFemGmshImp()
{
// no need to delete child widgets, Qt does it all for us
}
void DlgSettingsFemGmshImp::saveSettings()
{
cb_gmsh_binary_std->onSave();
fc_gmsh_binary_path->onSave();
}
void DlgSettingsFemGmshImp::loadSettings()
{
cb_gmsh_binary_std->onRestore();
fc_gmsh_binary_path->onRestore();
}
/**
* Sets the strings of the subwidgets using the current language.
*/
void DlgSettingsFemGmshImp::changeEvent(QEvent *e)
{
if (e->type() == QEvent::LanguageChange) {
}
else {
QWidget::changeEvent(e);
}
}
#include "moc_DlgSettingsFemGmshImp.cpp"
The implementation is surprisingly convenient, just calling onSave() and onRestore() methods of standard PrefWidget defined
in [src/Gui/PrefWidgets.h]. This UI file uses some FreeCAD costumed widgets, e.g. <widget class="Gui::PrefCheckBox"
name="cb_int_editor"> Those PrefWidgets needs to be registered into QtDesigner. In short, You need to compile
[src/Tools/plugins/widget] and register that library with Qt-designer in order to get the FreeCAD-specific widgets in
Qt-designer."
2. register page in [Mod/Fem/Gui/[Link]]
// register preferences pages
src/Gui/PrefWidgets.h preference widgets are not wrapped into python, therefore, can not been used by Python
class CfdPreferencePage:
def __init__(self):
ui_path = [Link]([Link](__file__), "[Link]")
[Link] = [Link](ui_path)
class CfdWorkbench(Workbench):
"CFD workbench object"
def __init__(self):
#...
import CfdPreferencePage
[Link]([Link], "CFD")
“[Link]” or “[Link]” must be in icon search path The icon file name pattern “preferences-”
100 CHAPTER 8. FEM MODULE SOURCE CODE ANALYSIS
Chapter 9
Solidworks provides not only FEM function, but also CFD function. See the SolidWorks flow-simulation. It is desirable that
FreeCAD can have such a feature.
Instead of creating a new CFD or CAE module, I am trying to add CFD function to the the current Fem workbench and
reuse most of the infrastructure.
See Appendix FreeCAD From Fem workbench towards a full-fledged CAE workbench
CFD simulation needs a more complex setup and dedicated mesh, thereby, in FreeCAD engineering an accurate simulation is
not the design aim. Importing a FreeCAD model into other pre-processing tools for meshing and tweaking the experiment
setup many times, is needed for serious study.
OpenFoam is not the only free open source CFD solver, but it is powerful. A free GUI case setup tool is missing (arguably).
It is not designed for windows, but usable via Cygwin: see FreeFoam. It is possible to add Cygwin to the PATH as
C:\cygwin\bin, then run the solver from command line. Furthermore, it can be run in a container, or even the Ubuntu-on-
Windows subsystem as in Windows 10.
Requirement anslysis: see appendix FreeCAD combining the strength of FreeCAD and Salome
Free Solver selection: External solver, it can potentially use a solver of any license.
9.1.3 Roadmap
• Current limitation: FreeCAD FEM is designed only for MechanicalAnalysis and the Solver is tightly coupled with
analysis object, not pluggable design. JobControlTaskView should be reusable by CFD solver after some refactoring
work.
• Case writer is the primary task for function of CFD simulation
• FemMesh export into UNV format, but it does not export boundary condition.
• Only Solid mechanical material is defined in Material module, but no fluid material.
• BoundaryCondition for CFD is not defined, this could be derived from Fem::Constraint
• View result back to FreeCAD is highly challenging task, thus external
101
102 CHAPTER 9. DEVELOPING CFD MODULE BASED ON FEMWORKBENCH
It is possible to extend function of DocumentObject and ViewProvider in Python. The Howto and limitation of developing
modules in python has been discussed in the previous chapters.
Example code for type checking in cpp:
(obj->getTypeId().isDerivedFrom(Fem::FemSolverObject::getClassTypeId()))
analysis_obj.isDerivedFrom('')
TypeId is string representation
[Link] is binary representation
Label is unicode string
Detailed documentation and excellent code are equally important for the survival of Open Source projects. Thereby, users can
enjoy a potentially smoother functioning program and new developers could be facilitated to extend/maintain the source code
with greater ease.
For a workbench developer, it is good to have a page on FreeCAD that introduces ways to easily add features, documentation,
and make progress undertaking this effort.
Ways to accomplish this:
First of all, write a good in-source documentation and detailed [Link] in the git repo.
Secondly, publish the workbench on the FreeCAD wiki
1. Apply for a wiki account via forum private message, see How to get wiki editing permissions
2. Once approved, read this before editing wiki pages
3. Add an item to the external workbenches section, not directly to user hub, which is for official (core) workbench
4. Add one unique page for the workbench itself.
• FemSolverObject: abstract DocumentObject for any CAE solver; concrete solver is developed in C++
• FemConstraintFluidBoundary: a single type to represent all boundary conditions in CFD: pressure, velocity, turbulence,
and thermal
• Note: see the later section on design and implementation of FemConstraintFluidBoundary in c++
• VTK mesh import and export
• CFD results exported as VTK file format for building up VTK pipe
9.2. DESIGN OF CFD WORKBENCH 103
This is a pure python module/workbench. A template of empty workbench could be downloaded from Bernd’ git:
• Load commands into workbench, which will load new python module as in cpp mode: src/Mod/Fem/Gui/[Link]
• Add MenuItem and Toolbar items for this module
• Resource (icon files)
• Translation
• Example and testing
• CMake setting (module [Link] and a global option to activate CFD workbench)
As an addon module made in pure Python, no module [Link] is needed. Just download this module folder ‘Cfd’
into ~/.FreeCAD/Mod/ or the FreeCAD installation Mod/ subfolder.
9.2.4 [Link]
• makeCfdAnalysis() creates a FemAnalysis object within the current document, defined in [Link]
• No need to Extend Fem::FemAnalysisObject into CfdAnalysis class in python
• ViewProviderCfdAnalysis python class is necessary as double-click will activate CFD workbench
UNV to foam, mesh renumbering, thereby, a result mesh is needed to show Result
[Link]
ideasUnvToFoam.C
This class and its derived, equal to [Link] family, hides solver specific implementation. Thereby, TaskPanelCfdSolverControl
can be shared by any CFD solver. The Cfd runnable write solver input file, run the solving process and finally load the result
back to FreeCAD.
This class extracts information from FreeCAD GUI for FoamCaseBuilder, e.g. mesh, material, solver setup and boundary,
while the actual case builder is done by FoamCaseBuilder
This is an independent python module, it will be developed in parallel with FreeCAD CFD workbench
• Export UNV mesh with boundary conditions FaceSet
• Case setup by setting boundary condition in workbench
• Case build up from scratch by generating OpenFOAM case setting files
• Case check or update case setup
• [Link] shows a tutorial about how to build a case via script once mesh file is ready
• [Link]: This class only defines properties representing CFD result, pressure, velocity, temperature, etc.
It is extended from the c++ class: FemResultObject, shared by any CFD solver.
• [Link]: load results from OpenFOAM solver
104 CHAPTER 9. DEVELOPING CFD MODULE BASED ON FEMWORKBENCH
OpenFOAM result is exported in VTK legacy file, then read by python-vtk6 module to show as FemResultObject in FreeCAD.
Only scalers like pressure can be illustrated as different color in FemMesh nodes. Velocity vector will not be supported although
FemPostPipeline is a promising solution.
This module will be re-implemented in c++ to save computation time, since CFD meshes are always huge.
• [Link]: select scalar to be shown as colormap on FemMesh via modifying ViewProviderFemMesh
properties
The Solver class provide information for QProcess to start external solver. It is mainly designed for CFD for the moment,
but any solver like Fem, could use it. Another commandline property could be added, or built from current property,so
JobControlTaskPanel will be reused by renaming Calculix (QProcess Object) -> SolverProcessObject or like name. Although
ccx works perfect now, we are not locked to only one Fem solver.
Solver should be pluggable, swappable. Analysis is a pure container (DocumentObjectGroup) to search for Mesh and Solver
Object, from my perspective. Currently, some properties are added into AnalysisObjects, but in Salome or Ansys workbench,
Solver is an object equal to Mesh. A lot of parameters, switches are needed to tweak solver, they are not belong to Analysis,but
solver specific.
Define a SolverObject can do persistence and replay of solver setup, and work without GUI. SolverObject can be subclass in
python to deal with specific solver.
#include <Mod/Fem/App/FemSolverObject.h>
DEF_STD_CMD_A(CmdFemCreateSolver);
...
• add cmd class into workbench
void CreateFemCommands(void){
{
Gui::CommandManager &rcCmdMgr = Gui::Application::Instance->commandManager();
...
[Link](new CmdFemCreateSolver());
• src/Mod/Fem/Gui/[Link]
Gui::ToolBarItem* Workbench::setupToolBars() const
{
Gui::ToolBarItem* root = StdWorkbench::setupToolBars();
Gui::ToolBarItem* fem = new Gui::ToolBarItem(root);
fem->setCommand("FEM");
...
<< "Fem_CreateSolver"
...
Gui::MenuItem* Workbench::setupMenuBar() const
{
Gui::MenuItem* root = StdWorkbench::setupMenuBar();
Gui::MenuItem* item = root->findItem("&Windows");
Gui::MenuItem* fem = new Gui::MenuItem;
root->insertItem(item, fem);
fem->setCommand("&FEM");
...
<< "Fem_CreateSolver"
• add new SVG icon file “[Link]” in Gui/Resource
• add “[Link]” file into [Link] XML file [Link] resource icon images are built into
bindary file [Link] or [Link]. cmake has one line to rebuilt resources.
• add or update Translation. This is temporarily left behind, until the code is stable.
• git add <the above newly added file> If you forget to work in a branch, you can git stash branch testchanges
see [Link]
Class Fem::ConstraintFluidBoundary should be derived from FemConstraint and adapted from some concrete class like
FemConstraintFixed, to reduce the work. As python has limitation, e.g. Coin3D scene, there must be coded in C++. The
closest class is FemConstraintForce, which is derived from FemConstraint, except no PythonFeature, but adding TaskPanel.
Modelled after CFX, a commercial CFD tool, boundary conditions are grouped into 5 categories, inlet, outlet, symmetry,
wall, opening (freestream/far field in other tools). BoundaryType Combobox is used to select from the categories. For each
categories, there is another combobox for Subtype, e.g. inlet and outlet has different valueType: pressure, flowrate, velocity,
etc. A task panel containing properties like: Value, Direction, Reversed, could be hidden if no value is needed for any
specific boundary subtype.
“Symmetry” should be named more generally as “interface”, which can be any special boundaries: wedge(axisymmetry),
empty(front and back face for 2D domain, single layer 3D mesh), coupled(FSI coupling interface), symmetry, interior (baffle),
processor(interface for domain decomposition), cyclic ( Enables two patches to be treated as if they are physically connected),
etc.
9.4. BOUNDARY CONDITION SETTINGS FOR CFD 107
inlet {totalPressure, velocity, flowrate} outlet {pressure, velocity, inletOutlet} wall {fixed, moving, slip} freestream {freestream}
interface {empty, symmetry, cyclic, wedge}
Only uniform value boundary type is supported in GUI, user should edit the case file for OpenFOAM supported csv or
function object non-uniform boundary.
The turbulent inlet and thermal boundary condition is editable in the tab of boundary condition, which is accessed by tab in
boundary control panel
Other solver control, like gravity, reference pressure, is the internal field initialisation/body force for pressure and velocity.
• DocumentObject Fem::ConstraintFluidBoundary
• add file names into App/[Link]
• type initialisaton App/[Link]
• ViewProvider FemGui::ViewProviderConstraintFluidBoundary
• TaskPanel and ui “
• add file names Gui/[Link]
• type initialisaton Gui/[Link]
• add svg icon file and update XML resource file [Link]
• add menuItem in FemWorkbench <Gui/[Link]> and <Gui/[Link]>
9.4.4 ViewProviderConstraintFluidBoundary.h
(changed combobox type should trigger a redraw) Only outlet, will show arrow as FemConstrainForce, inlet has the arrow
but in reverse direction (flow into the geometry) Other boundary types will shows as FemConstrainFixed. However, simply
merging codes of two viewProviders into [Link] does not work properly.
void ViewProviderFemConstraintFluidBoundary::updateData(const App::Property* prop) only update property
data, while actural drawing is done in base class method: ViewProviderFemConstraint::updateData(prop);
//change color to distinguish diff subtype
App::PropertyColor FaceColor;
// comment out *createCone* will make draw "interface" type and "freestream" type of fluid boundary
void ViewProviderFemConstraint::createFixed(SoSeparator* sep, const double height, const double width, const bo
{
createCone(sep, height-width/4, height-width/4);
createPlacement(sep, SbVec3f(0, -(height-width/4)/2-width/8 - (gap ? 1.0 : 0.1) * width/8, 0), SbRotation()
createCube(sep, width, width, width/4);
}
adding header and init function into src/Mod/Fem/Gui/[Link] This module is not designed to be extended in
python as other FemConstraint class, thereby only cpp type are declared.
#include "ViewProviderFemConstraintFluidBoundary.h"
...
PyMODINIT_FUNC initFemGui()
{
FemGui::ViewProviderFemConstraintFluidBoundary ::init();
9.4.5 TaskFemConstraintFluidBoundary
I use inkscape to make new svg icon for this class and add file name into src/Mod/Fem/Gui/Resources/[Link]
9.5. EXAMPLE OF EXTENDING FEMSOLVEROBJECT IN PYTHON 109
[Link] _FemSolverCalculix.py
• add dialog UI into update property of FemSolverObject
• design [Link] dialog GUI form by QtDesigner
• add _TaskPanelCfdSolverControl python class
• add ViewProviderCfdSolverFoam python class
• Macro replay/ document import should work now.
update [Link] and resource
• add new files into in Gui/[Link]
• deprecated class _FemAnalysis _ViewProviderFemAnalysis (feature dropped)
• rename and refactoring of _JobControlTaskPanel.py (feature dropped)
• create new icons file
makeCfdSolverFoam is the magic connection between cpp class and Python class. It returns a document object derived
type “Fem::FemSolverObjectPython”, which is defined in c++ using FeatureT template. Extra properties can be added by
CfdSolverFoam(obj) constructor. Furthermore, ViewProvider can be extended by _ViewProviderCfdSolverFoam python
class.
def makeCfdSolverFoam(name="OpenFOAM"):
obj = [Link]("Fem::FemSolverObjectPython", name)
CfdSolverFoam(obj)
if [Link]:
from _ViewProviderCfdSolverFoam import _ViewProviderCfdSolverFoam
_ViewProviderCfdSolverFoam([Link])
return obj
CfdSolver is a generic class for any CFD solver, defining shared properties
class CfdSolver(object):
def __init__(self, obj):
[Link] = "CfdSolver"
[Link] = obj # keep a ref to the DocObj for nonGui usage
[Link] = self # link between Fem::FemSolverObjectPython to this python object
# API: addProperty(self,type,name='',group='',doc='',attr=0,readonly=False,hidden=False)
[Link]("App::PropertyEnumeration", "TurbulenceModel", "CFD",
"Laminar,KE,KW,LES,etc")
[Link] = list(supported_turbulence_models)
[Link] = "laminar"
OpenFOAM specific properties go into CfdSolverFoam
110 CHAPTER 9. DEVELOPING CFD MODULE BASED ON FEMWORKBENCH
class CfdSolverFoam([Link]):
def __init__(self, obj):
super(CfdSolverFoam, self).__init__(obj)
[Link] = "CfdSolverFoam"
**_ViewProviderCfdSolverFoam.py**
import FreeCAD
import FreeCADGui
import FemGui
class _ViewProviderCfdSolverFoam:
"""A View Provider for the Solver object, base class for all derived solver
derived solver should implement a specific TaskPanel and set up solver and override setEdit()
"""
def getIcon(self):
"""after load from FCStd file, [Link] does not exist, return constant path instead"""
return ":/icons/[Link]"
taskd = _TaskPanelCfdSolverControl(foamRunnable)
[Link] = [Link]
[Link](taskd)
return True
def __getstate__(self):
return None
=====================================================
[Link] This workbench is designed to fit in more solvers. To solve CFD problem in other
solvers, you may reuse some of the code in CfdWorkbench, which is a split from FemWorkbench. [Link] has several
implementation, currently needs a unification. [Link] can be reused for common CFD boundary
types
• [Link]
derived from CfdSolver which defined most of setting for CFD, include elmer specific settings, example are CfdSolver-
[Link]
• [Link]
expose only a write_case() method. write_mesh (which should be similar as Bernd has done)and write boundary
condition
• [Link]
call the calculation and retrieve the result and display, CfdSolverControl task panel can be reused with tiny modification
• then add _CommandCfdSolverElmer.py _ViewProviderCfdSolverElmer.py, icon svg file can be adapted from *[Link]
add into CfdWorkbench via [Link]
+[Link]
load result . currently this piece of code (CfdResult and taskpanel) is under review, there is no need to
112 CHAPTER 9. DEVELOPING CFD MODULE BASED ON FEMWORKBENCH
Chapter 10
For python coding, a text editor with grammar highlight + QtDesigner is enough to code in the first case. QtDesigner can
be used to generate and edit Qt ui files. Spyder, which comes with Anaconda, is a good and lightweight Python IDE with
debugger support.
Various c++ IDE are available to support Cmake project. Visual Studio 2015 is essential for development on Windows.
Cmake project can be mapped to a VS solution (*.sln) with a bundle of projects corresponding to
Latest QtCreator works with Qt 4.x and Qt 5.x; it also support CMake project.
• InkScape to generate SVG icon Great vector drawing program. Adhers to the SVG standard and is used to draw Icons
and Pictures. Get it at [Link]
• Doxygen to generate documentation A very good and stable tool to generate source documentation from the .h and .cpp
files.
• Gimp to edit XPM icon file Not much to say about the Gnu Image Manipulation Program. Besides it can handle .xpm
files which is a very convenient way to handle Icons in QT Programs. XPM is basically C-Code which can be compiled
into a program. Get the GIMP here: [Link]
113
114 CHAPTER 10. TESTING AND DEBUGGING MODULE
for a cmake project, add the following code into top level [Link]
First of all, make sure you can build the official source once done git clone which confirms you can install all the lib
dependent.
compiler’s warning is the first place to spot error and potential bugs.
10.2. C++ DEBUGGING 115
after changing an ui-file like this one ([Link] . . . [Link]) I have to run
make clean && make to get the changes active.
Re: make clean after changing an *.ui file Postby wmayer » Thu Aug 06, 2015 4:46 pm
In this case cd into the Arch directory first before running “make clean” because then it only rebuilds this module and not
the whole project.
Qt debug [Link]
The console class This class manage all the stdio stuff. here is the generated document for src/Base/Console.h
This includes Messages, Warnings, Log entries and Errors. The incoming Messages are distributed with the
FCConsoleObserver. The FCConsole class itself makes no IO, it’s more like a manager. ConsoleSingleton is a
singleton! That means you can access the only instance of the class from every where in c++ by simply using:
#include <Base/Console.h>
//...
Base::Console().Log("Stage: %d",i);
src/Base/Tools.h
/**
* @brief fromStdString Convert a std::string encoded as UTF-8 into a QString.
* @param s std::string, expected to be UTF-8 encoded.
* @return String represented as a QString.
*/
static inline QString fromStdString(const std::string & s)
{ return QString::fromUtf8(s.c_str(), [Link]()); }
}
[Link]
print "Error Message" does not work in FreeCAD, neither PythonConsole in GUI mode, or terminal starting freecad
program (stdout can be viewed in ReportView, by activating this view). By changing the default preference, it is possible to
show print message from python module.
• Method 1: [Link]() for show up
• Method 2: Print to TextEdit widget in your specific TaskPanel class
src/Gui/GuiConsole.h
/** The console window class This class opens a console window when instantiated and redirect the stdio strea
* After instantiation it automatically register itself at the FCConsole class and gets all the FCConsoleObser
*/
import imp
[Link](module)
#For >=Python3.4
import importlib
[Link](module)
We already know that non-GUI code of FreeCAD can be tested without starting GUI, however, the GUI is still needed for
testing. It is possible to test GUI code by script, see an example of CFD workbench test script. It can save time to utilize the
GUI. Potentially, a git hook can be set to run this script for each commit. This is useful since a third-party UnitTest script
will not be run by FreeCAD Test workbench.
# run a gui test for FreeCAD GUI functions
import sys
[Link]('/usr/lib/freecad-daily/lib')
[Link]()
[Link]("StartWorkbench")
##########################################################
[Link]("Unnamed")
[Link]("Unnamed")
[Link]=[Link]("Unnamed")
[Link]=[Link]("Unnamed")
[Link]("PartWorkbench")
[Link]("Part::Cylinder","Cylinder")
[Link] = "Cylinder"
[Link]()
[Link]("ViewFit")
[Link]("CfdWorkbench")
[Link]('Cylinder',0)
import FemGui
import CfdObjects
[Link]('CfdAnalysis')
[Link]([Link]().ActiveObject)
[Link]().addObject([Link]('OpenFOAM'))
[Link]().addObject([Link]('FluidMaterial'))
mesh_obj = [Link]('Cylinder_Mesh')
mesh_obj.Part = [Link]
[Link]().addObject(mesh_obj)
10.4. PYTHON DEBUGGING 119
# more code copied from interpreter widget, whatever recorded by macro recording
##########################################################
[Link]().close()
#[Link]('exit()') # another way to exit
The AppImage technology allows to construct and distribute a cross Linux distro executable. It also has the ability to be
utilized as a testing tool for python code.
[Link] Background
As mentioned above, a very convenient aspect of FreeCAD is that a majority of it is built in python and doesn’t require to
manually compile the code (like c++). Essentially, a python file can be modified and upon reloading the python script (or
restarting FreeCAD) those changes will be integrated. A developer can quickly work on the latest FreeCAD release using
this technique and an appimage. FYI, the following procedure doesn’t modify the environment in any way, in other words,
nothing gets installed and no environment paths get modified. All one needs to do is the following:
If you’ve done the above and now want to re-package the AppImage with your latest changes (useful for several reasons
though mainly for having others quickly test your modifications) proceed as follows:
• appimagetool will repackage the AppImage and compress it making it ready for distribution
120 CHAPTER 10. TESTING AND DEBUGGING MODULE
Chapter 11
Read this first if you want to write code for FreeCAD Some guide lines for contribute code to FreeCAD Roadmap of FreeCAD:
search FreeCAD and roadmap to get the last
Those guidelines are a bit terse, it may be worth of demonstration for new developers.
FreeCAD development is very community discussion driven. Many of these discussions occur on the FreeCAD forum.
If you want to contribute new features to FreeCAD, you should always first check if someone else has already had that idea
and has completed it or if they are just in the process of implementing it.
Secondly, introduce yourself to the community" by posting your idea onto the forum and hear feedback from the FreeCAD
developers and users.
121
122 CHAPTER 11. CONTRIBUTE CODE TO FREECAD
• github cheatsheat This section will explain in details: How you can contribute to FreeCAD project
• git from the bottom up
• The 11 Rules of GitLab Flow link to Chinese translation of The 11 Rules of GitLab Flow
• github tutorials for common jobs
• google if you run into trouble
The Author recommended gitKraken, a free, portable and powerful GUI tools. It make the advanced operations like merge,
cherry pick easier for new git users.
Note: gitKraken is proprietary but can be used for free if the user is working on Open Source Software/Public repositories.
The best features are:
• one click undo and redo
• visual hints
• intuitive merge for conflict
Start by making changes to the feature branch you’re doing work on. Let’s assume that these changes span a few commits
and I want to consolidate them into one commit. The first step involves making sure the master branch is up to date with the
destination repo’s master branch:
• switch to master branch: git checkout master
• ensure our master is up to date: git pull upstream master
• With the master branch up to date, we’ll use git rebase to consolidate: git checkout your_branch git rebase -i
master That command will show a list of each commit. If there is conflict, trouble is coming. By default, it’s a classic
rebase: cherry-picking in sequence for every commit in the list. Abort anytime if you are not sure, using git rebase
--abort.
Uisng merge GUI tool for 2-way merge git mergetool --tool=meld each time when there is a conflict. After solving the
conflict , git rebase --continue again.
git checkout A
git rebase B # rebase A on top of B
local is B,
remote is A
Instead of interactive mode, git rebase master will give you a list of conflicts. Graphical merge GUI tool can be used and
git rebase --continue
If you start three-pane merging tool (e.g. meld, kdiff3 and most of the others), you usually see LOCAL on the left (official
remote master), merged file in the middle and REMOTE (your dev branch) on the right pane. It is enough
for everyday usage. Edit only the merged file in the middle, otherwise, modification on the left and right will lead to
trouble/repeating manually merge conflict many times.
What you don’t see is the BASE file (the common ancestor of $LOCAL and $REMOTE), how it looked like before it was
changed in any way.
advanced topic
Meld has a hidden 3-way merge feature activated by passing in the 4th parameter:
meld $LOCAL $BASE $REMOTE $MERGED The right and left panes are opened in read-only mode, so you can’t accidentally
merge the wrong way around. The middle pane shows the result of [Link] the conflicts it shows the base version so that
you can see all the important bits: original text in the middle, and conflicting modifications at both sides. Finally, when you
press the “Save” button, the $MERGED file is written - exactly as expected by git. The ~/.gitconfig file I use contains the
following settings:
[merge]
tool = mymeld
conflictstyle = diff3
[mergetool "mymeld"]
cmd = meld --diff $BASE $LOCAL --diff $BASE $REMOTE --diff $LOCAL $BASE $REMOTE --output $MERGED
this opens meld with 3 tabs, 1st and 2nd tab containing the simple diffs I’m trying to merge, and the 3rd tab, open by default,
shows the 3-way merge view.
1) $LOCAL=the file on the branch where you are merging; untouched by the merge process when shown to you
2) $REMOTE=the file on the branch from where you are merging; untouched by the merge process when shown to you
3) $BASE=the common ancestor of $LOCAL and $REMOTE, ie. the point where the two branches started diverting the
considered file; untouched by the merge process when shown to you
4) $MERGED=the partially merged file, with conflicts; this is the only file touched by the merge process and, actually,
never shown to you in meld
The middle pane show (BASE) initially and it turns/saved into (MERGED) as the result of merging. Make sure you move
your feature code (LOCAL) from left to the middle and move upstream updated code from the right pane (REMOTE)
[Link]
[Link]
11.3. WORKFLOW TO ADD NEW FEATURE TO FREECAD 125
After ignore the official master for half year, I found it is not possible to rebase my feature. It is ended up with kind of merge,
instead of smooth playing back my feature commit.
I start to split my feature into C++ section, which is more stable, into a clean branch for pull request. Instead of
[Link]
File with difference will be highlight, double-click will bring up a double-pane comparasion tab, similar merging operaiton is avail-
• line encoding
• indentation by spaces
• backup files
After rebase, lots of *.orig files left in source folder. The git mergetool that produces these files, you can disable them with
this command:
After test, git commit it and even git push to your repo. make a copy of the changed folder of the merged-with-upstream
feature branch.
git checkout master and copy the folder back.
git status will show all the changed files in feature branch.
git checkout -b feature_branch_clean will make a patch/diff of all feature change wihte upstream master. git commit it
after testing
git push origin feature_branch_clean and make a pull request online
Testing by macro or scripting
I taught myself a painful lession by attempting modifying many file before testing. Finally, I start again to refactoring single
file and pass the test.
Unit test would be recommended, feeding predefined data input to automate the testing.
GUI debugging is time-consuming. FreeCAD has the macro-recording function, which can be used to save time on GUI testing
by playing back macro.
FreeCAD is still under heavy development, testers are welcomed in every modules.
11.3. WORKFLOW TO ADD NEW FEATURE TO FREECAD 127
11.3.10 Procedure for user without a online forked repo (not tested ,not recommended)
As you don’t have push (write) access to an upstream master repository, then you can pull commits from that repository into
your own fork.
• Open Terminal (for Mac and Linux users) or the command prompt (for Windows users).
• Change the current working directory to your local project.
• Check out the branch you wish to merge to, usually, you will merge into master
• git checkout master
• Pull the desired branch from the upstream repository. git pull upstream master, This method will retain the commit
history without modification.
• git pull [Link] BRANCH_NAME
• If there are conflicts, resolve them. For more information, see “Resolving a merge conflict from the command line”.
• Commit the merge.
• Review the changes and ensure they are satisfactory.
• Push the merge to your GitHub repository.
• git push origin master
It is recommended to submit small, atomic, manageable pull request to master, definitely after a full test.
After you push your commit to your fork/branch, you can compare your code with master. It is worth of coding style checking.
For python code, using flake, PEP8 etc. , cppcheck for C++ code.
Follow the standard github pull request routine, plus create a new post to describe the pull request, and wait for core
developers/collobrators to merge.
Spot out the bug: naming bug in Fem module: StanardHypotheses should be StandardHypotheses
1. find the bug and plan for bugfix
Assuming, current folder is Fem /opt/FreeCAD/src/Mod/Fem, find a string in all files in a folder, including subfolders: grep
-R 'StanardHypotheses' ./ output:
./App/[Link]:void FemMesh::setStanardHypotheses()
./App/FemMesh.h: void setStanardHypotheses();
./App/[Link]:PyObject* FemMeshPy::setStanardHypotheses(PyObject *args)
./App/[Link]: getFemMeshPtr()->setStanardHypotheses();
./App/[Link]: <Methode Name="setStanardHypotheses">
If not, then use find, try this: find ./ -type f -exec grep -H 'yourstring' {} +
2. make the patch and test locally
pull from the most updated upstream master, then make a new branch and checkout this branch git checkout renamingFem
replace a string in all files in a folder, including subfolders
grep -rl StanardHypotheses ./ | xargs sed -i 's/StanardHypotheses/StandardHypotheses/g'
check the result of replacement: There should be no output: grep -R 'StanardHypotheses' ./ Then again: grep -R
'StandardHypotheses' ./, should match the file and lines number found in step 1
git add ./App/[Link]
git add ./App/FemMesh.h
git add ./App/[Link]
git add ./App/[Link]
128 CHAPTER 11. CONTRIBUTE CODE TO FREECAD
After a pull request on github, it will be compiled and unit test will be conducted automatically at 3 major platforms, win,
macos and ubuntu.
Continuous-integration/appveyor/pr — Waiting for AppVeyor build to complete
Required continuous-integration/travis-ci/pr — The Travis CI build is in progress
The setup of Travis-ci for github is very straight-forward, see official manual [Link]
1. Sign in to Travis CI with your GitHub account, accepting the GitHub access permissions confirmation.
Once you’re signed in, and we’ve synchronized your repositories from GitHub, go to your profile page and enable
Note: You can only enable Travis CI builds for repositories you have admin access to.
3. Add the .[Link] file to git, commit and push, to trigger a Travis CI build:
Travis only runs a build on the commits you push after adding the repository to Travis.
4. Check the build status page to see if your build passes or fails.
However, for module developers, the free compiling service may not be so usful. The compiling of FreeCAD code base may be
too long to finish within free time limit.
7. Setting up dashboard (page that users see as the main page) - it’s gui driven and very easy.
8. There are github integration options - I did not explore them yet.
131
132 CHAPTER 12. FREECAD CODING STYLE
eType = 0;
}
type prefix for function parameter is not as useful as for member Variable,
• i: integer
• s: char const*, std::string
• p for pointer (and pp for pointer to pointer)
• pc: pointer of C++ class
• py: pointer of Python object
• _privateMember
for example: App::DocumentObject *pcFeat
It is more Coin3D style,except “So” namspace suffix is not used. In 2003, C++ compilers are not so powerful and standardised
to support even template and namespace in a cross-platform way. visual c++ was really bad to support C++ standard for
some time.
• Namespace is enforced for each module, using “Export”
• class name (CamelClass) , Acronyms are camel-cased like ‘XmlWriter’
• private members:
• member function name (begins with lowerCase).
• no tab but 4 spaces indentation
• getPropertyName() is used in FreeCAD, while propertyName() is used in Qt, more example on getter names
Gui::Application::Instance
Gui::MainWindow::getInstance();
Gui::getMainWindow();
• function parameter has the pattern “a single char for type”+“meaningful name” > common type char: s->string; i->int;
h->Base::Reference/object handle; e->enum; f->float/double; p->pointer;
• c++ STL and boost lib is used, but higher level Qt style API provided for user
PyCXX (Py::Object) should be used as possible, it may give better python2.x and python 3.x compatibility over the raw C
API in <Python.h>
return PyObject* and Py::Object has different impact
134 CHAPTER 12. FREECAD CODING STYLE
pep8 and pyflake to check coding style: sudo apt-get install python3-flake8 flake8 flake8 --ignore E265,E402,E501,E266
[Link]
[Link]
Python IDE would suggest confliction with flake8 and avoid trailing spaces in c++ IDE
135
136 CHAPTER 13. CMAKE CHEAT SHEET
13.3.1 [Link]
• CMAKE_CXX_COMPILER
• CMAKE_LINK_LIBRARY_CFLAGS
platform like: WIN32, APPLE, UNIX (linux is UNIX but not APPLE), MYSYS, CYGWIN compilers: MSVC, MINGW,
iNTEL; CMAKE_COMPILER_IS_GNUCXX, CLANG
cmake --variable-list
cmake --command-list
cmake --module-list
cmake -h #show all generator
[Link]
cmake is a new language, why should we learn a new language instead of json or python (as in scons)?
2. multiple programming lang support and cross-platform with generators for majourity of IDE tools
7. tool to generate [Link] from scratch or convert from existent build system
Learning OpenInventor/Coin3D
Coin3D is an open source implementation for OpenInventor spec/API, released in a less constrainted license, LGPL.
Usful links to learn OpenInventor programming: [Link]
[Link] Coin3D Online Document
SoPath, SoNode, SoEngine are three main categories of Object in Coin3D. Classes are organised into modules, see http:
//[Link]/APIS/RefManCpp/[Link]
Description from this online documentation is extracted for key classes. See the brief description for classes: [Link]
[Link]/Coin/[Link];
** Basic objects **
• SbXXX: Basic types like SbVec3f, SbMatrix, SbColor, SbString, SbTime; Containers like SbDict, SbList; geometrical
representation of basic shape like SbSphere; SbTypeInfo
• SoBase: ancester for most Coin3D ojbects, similar with QObject, FreeCAD’s Base::BaseClass
Top-level superclass for a number of class-hierarchies. SoBase provides the basic interfaces and methods for doing
reference counting, type identification and import/export. All classes in Coin3D which uses these mechanisms are
descendent from this class
ref() unref() getName()
virtual SoType getTypeId (void) const =0
notify (SoNotList *nl) //observer pattern, notify Auditor
addAuditor (void *const auditor, const SoNotRec::Type type)
Qobject is the base object for all derived Qt objects, offering event, container, property, type support.
Example of inheritance chains:
Coin3D: SoBase->SoFieldContainer->SoNode->SoGroup->SoShape FreeCAD: BaseClass->App::PropertyContainer->App::Docu
• SoType: Inventor provides runtime type-checking through the SoType class. node->getTypeId().getName(); like
Base::TypeClass in FreeCAD
Basis for the run-time type system in Coin3D. Many of the classes in the Coin3D library must have their type
information registered before any instances are created (including, but not limited to: engines, nodes, fields, actions,
nodekits and manipulators). The use of SoType to store this information provides lots of various functionality for
working with class hierarchies, comparing class types, instantiating objects from classnames, etc etc
• SoField: Top-level abstract base class for fields serializable, similar with App::Property in FreeCAD
Fields is the mechanism used throughout Coin for encapsulating basic data types to detect changes made to
them, and to provide conversion, import and export facilities. SoSFXXX : Single Field with Base type wrapped
(App::Property); SoMFXXX : Multiple Field (array of field). E.g. SoSFBool class is a container for an SbBool
value.
139
140 CHAPTER 14. LEARNING OPENINVENTOR/COIN3D
Detail information about shapes is used in relation to picking actions in Coin. They typically contain the relevant
information about what particular part of the shape a pick ray intersected with
** misc objects **
• SoEngine: SoEngine (derived from SoFieldContainer, as a sibling of SoNode) is the base class for Coin/Inventor engines.
Engines enables the application programmers to make complex connections between fields, for example, animation.
• SoVRMLXXX: VRML file import and export
• SoAudioDevice: 3D sound
• SoSensor: for scene manipulation
• SoCamera: belongs only to scene
• SoLight: belongs only to scene
• SoEnvironment: gloable settings
• ScXml: Namespace for static ScXML-related functions
• SoElement: base class for classes used internally for storing information in Open Inventor’s traversal state list.
• SoSelection: Manages a list of selected nodes, Derived from SoSeparator.
Inserting an SoSelection node in your scene graph enables you to let the user “pick” with the left mousebutton to
select/deselect objects below the SoSelection node
• SoSFEnum/SoMFEnum: single or multiple Enumeration fields
** Action, event and callback **
• SoAction: SoCallback(object oriented)
Applying actions is the basic mechanism in Coin for executing various operations on scene graphs or paths within
scene graphs, including search operations, rendering, interaction through picking, etc
• SoEvent: Base class for keyboard/mouse/motion3d event
• SoEventCallback: nodes in the scenegraph for catching user interaction events with the scenegraph’s render canvas
• SoCallback: Node type which provides a means of setting callback hooks in the scene graph.
By inserting SoCallback nodes in a scene graph, the application programmer can set up functions to be executed at
certain points in the traversal - SoCallbackAction: Invokes callbacks at specific [Link] action has mechanisms
for tracking traversal position and traversal state.
In combination with the ability to pass geometry primitives to callback actions set by the user, this does for
instance make it rather straightforward to extract the geometry of a scene graph
• SoCallbackList The SoCallbackList is a container for callback function pointers, providing a method for triggering the
callback functions
see [Link]
For developers targeting multi-platform - ‘Quarter’ provides a seamless integration with the Qt framework. [Link]
[Link]/wiki/Coin3D
[Link]