Sunday, 21 October 2012

Help for creating C++ Modules in FreeCAD

Being slightly hungover from a crazy night out, I figured I might aswell try and doing something useful, so I am going to try and explain the structure for implementing a structure using c++ in FreeCAD.

It's useful to understand this to build a clear logical  structure rather than a mashup, which makes it more easy to document improving accessible for people to understand how the module fits together and get them developing! That's one of the reason's I'm not a big fan of using python solely in FreeCAD modules...

The App & GUI:

FreeCAD is essentially built around two core areas - the application code and the GUI which is essential for users to get something productive out of the program. 

Application:

The application provides the facility to perform operations, calculations, managethe document structure and its features. It's built around numerous libraries, the obvious one being OpenCascade which provides the modelling features. The application should be usable without a GUI so it behaves like a console application. 

FreeCAD comprehensivly uses python throughout to give greater power to users and ease development. It's very easy for the users to run commands in the python interpreter. Even still the application code can still execute python commands which are typically used for creating / editing features but quite essential for enabling the undo-redo system to work.

Any GUI related libraries cannot be used in the application module, however, we can still use some modules from QT as part of the application modules, such as:

  • QTCore
  • QTNetwork
  • QTXML

The Gui

The GUI is infact optional, and this can be ommited whilst compiling. This eventually give us greater flexibility in terms of the user interaction. For example we could in theory build a WebGL version of FreeCAD.

The GUI uses the Coin3D for managing the OpenGL Scenegraph based on the Open Inventor whilst we use Qt4 for providing the overall application user interface. These are linked together using SoQtViewer. 

Building a Structure:

I always recommend to look at the Sketcher Module which despite it's complexity is a classic example of how a c++ module should look.

It's best to seperate the module into two seperate folders atleast, the App and the GUI folder. This helps prevent cross contamination of GUI code into the application code. 

Below is simple diagram explaining how the structure fits together.



Each module will have it's own Features and potentially sub features too. Depending on the complexity of the feature, these features should only provide an interface (python) to another class which is intialised as a protected member and is callable. This keeps the document feature easier to work with and keeps the main functions are processes isolated together. Usually I create a seperate subfolder in the app directory to keep these classes together.

Each document feature declares a string to a specified viewprovider - defined in

const char* getViewProviderName() const 

This allows FreeCAD to load the viewprovider if there is a GUI interface. Each View Provider is assigned a pcObject which references the document feature that it is assigned to. other gui elements have to be implemented such as command.cpp, workbench and if needed the taskdialog and its taskboxes.

Document Objects / Features:

Each Module will have to define its own document feature, this is achieved by inheriting from App::DocumentObject.  There are many virtual class members that can be overriden. Each Feature has then several properties that can be used to store such as
  • Integers (App::PropertyInteger)
  • Floats (App::PropertyFloat)
  • Strings (App::PropertyString)
  • Lists
  • Links (App::PropertyLink)
  • SubLinks (App::PropertySubLink)  - a link to a feature and with a sub reference (e.g. a face, or edge on a part)
Including these makes life much simpler for the programmer; we don't have to worry about undo-redo mechanism and saving and restoring these into the document. We can also define our own properties but this introduces further complexities and should tried to be avoided.

We can link together other features using links and this introduces a dependency. When the sub feature is touched - its properties have changed, the parent feature will update itself accordingly. We can control this behaviour by re-implementing the following methods

  • void onBeforeChanged(const App::Property* /*prop*/)
  • void onAfterChanged(const App::Property* /*prop*/)
  • void onChanged(const App::Property* /*prop*/)
  • short mustExecute() const;
The main calculations or functions are perfomed in

App::DocumentObjectExecReturn *execute(void);

these are called when the recompute method is called either for the document feature or the whole document.

A document feature can also be implemented to take advantage of python. This requires defining an XML file and then implementing the generating functions in a myFeaturePyImp.cpp file. Generally an argument list is parsed and then if valid the correct c++ functions are called in the document feature. 

ViewProviders

ViewProviders provide the majority of the interface for a document feature. It manages the object's presentation and manipulation in the 3D view by allowing the developer to manipulate the Coin3D scenegraph. Also it defines how the feature is handled and presented in the Document Tree.

ViewProviders inherit from Gui/ViewProvider.h but can also inherit from other ViewProvider classes to obtain already defined behaviours. There aren't any members that need to be defined but it makes sense that you do.

GUI::Commands

These are basically commands that are registered in the GUI application that can be called by tool bar icons or the main menus. These can be programmed with a lot of logic during the prototyping phase, but it's not recommended that they are simple commands which will use the python interface.

 The things to remember:

  • Seperate your module into Application and GUI
  • Keep your Document Features as interfaces
  • Use App::Properties to save you time
  • Seperate processes and calculations into seperate classes
  • Keep Things Simple At First - get your module working and build up