Extensions: Add Documentation
This commit is contained in:
parent
44d1ee0157
commit
594bb4fecd
|
@ -34,6 +34,94 @@ namespace App {
|
|||
/**
|
||||
* @brief Base class for all extension that can be added to a DocumentObject
|
||||
*
|
||||
* For general documentation on why extension system exists and how to use it see the ExtensionContainer
|
||||
* documentation. Following is a description howto create custom extensions.
|
||||
*
|
||||
* Extensions are like every other FreeCAD object and based on properties. All information storage
|
||||
* and persistance should be achieved by use of those. Additional any number of methods can be
|
||||
* added to provide funtionality around the proerties. The only difference to normal objects is that
|
||||
* extensions must derive from the Extension class and that the tye needs to be initialised. This
|
||||
* works as simple as
|
||||
* @code
|
||||
* class MyExtension : public Extension {
|
||||
* virtual bool overridableMethod(DocumentObject* obj) {};
|
||||
* };
|
||||
*
|
||||
* MyExtension::MyExtension() {
|
||||
* initExtension(MyExtension::getClassTypeId());
|
||||
* }
|
||||
* typedef ExtensionPythonT<MyExtension> MyExtensionPython;
|
||||
* @endcode
|
||||
*
|
||||
* The special python extension type created above is important, as only those python extensions
|
||||
* can be added to an object from python. It does not work to add the c++ version directly there.
|
||||
*
|
||||
* Note that every method of the extension becomes part of the extendded object when added from c++.
|
||||
* This means one should carefully design the API and make only neccessary methods public or protected.
|
||||
* Every internal method should be private.
|
||||
*
|
||||
* The automatic availibility of methods in the class does not hold for the python interface, only
|
||||
* for c++ classes. This is like every where else in FreeCAD, there is no automatic creation of python
|
||||
* API from c++ classes. Hence the extension creator must also create a custom python object of its
|
||||
* extension, which works exactly like the normal FreeCAD python object workflow. There is nothing
|
||||
* special at all for extension python objects, the normal xml + imp.cpp approach is used. It must
|
||||
* only be taken care that the objects father is the correct extension base class. Of course also
|
||||
* makse sure your extension returns the correct python ojbect in its "getPyObject" call.
|
||||
* Every method you create in the extensions python will be later added to an extended object. This
|
||||
* happens automatically for both, c++ and python extension, if "getPyObject" returns the correct
|
||||
* python object. No extra work needs to be done.
|
||||
*
|
||||
* A special case that needs to be handled for extensions is the possibility of overriden methods.
|
||||
* Often it is desired to customise extension behaviour by allowing the user to override methods
|
||||
* provided by the extension. On c++ side this is trival, such methods are simply marked as "virtual"
|
||||
* and can than be overriden in any derived class. This is more involved for the python interface and
|
||||
* here special care needs to be taken.
|
||||
*
|
||||
* As already seen above one needs to create a special ExtensionPythonT<> object for extension from
|
||||
* python. This is done exactly for the purpose of allowing to have overridable methods. The
|
||||
* ExtensionPythonT wrapper adds a proxy property which holds a PyObject which itself will contain
|
||||
* the implementations for the overridden methods. This design is equal to the ObjectPythonT<> design
|
||||
* of normal document objects.
|
||||
* As this wrapper inherits the c++ extension class it can also override the virtual functions the
|
||||
* user designed to be overridden. What it should do at a call of the virtual method is to check if
|
||||
* this method is implemented in the proxy object and if so call it, and if not call the normal
|
||||
* c++ version. It is the extensions creators responsibility to implement this check and call behaviour
|
||||
* for every overridable method.
|
||||
* This is done by creating a custom wrapper just like ExtensionPythonT<> and overriding all virtual
|
||||
* methods.
|
||||
* @code
|
||||
* template<typename ExtensionT> class MyExtensionPythonT : public ExtensionT {
|
||||
* public:
|
||||
*
|
||||
* MyExtensionPythonT() {}
|
||||
* virtual ~MyExtensionPythonT() {}
|
||||
*
|
||||
* virtual bool overridableMethod(DocumentObject* obj) override {
|
||||
* Py::Object pyobj = Py::asObject(obj->getPyObject());
|
||||
* EXTENSION_PROXY_ONEARG(allowObject, pyobj);
|
||||
*
|
||||
* if(result.isNone())
|
||||
* ExtensionT::allowObject(obj);
|
||||
*
|
||||
* if(result.isBoolean())
|
||||
* return result.isTrue();
|
||||
*
|
||||
* return false;
|
||||
* };
|
||||
* };
|
||||
* @endcode
|
||||
* @Note As seen in the code there are multiple helper macros to ease the repetitive work of querying
|
||||
* and calling methods of the proxy object. See the maco documentation for howto use them.
|
||||
*
|
||||
* To ensure that your wrapper is used when a extension is created from python the extension type must
|
||||
* be exposed as follows:
|
||||
* @code
|
||||
* typedef ExtensionPythonT<MyExtensionPythonT<MyExtension>> MyExtensionPython;
|
||||
* @endcode
|
||||
*
|
||||
* This boilerplate is absolutely nesseccary to allow overridable methods in python and it is the
|
||||
* exension creators responsibility to ensure full implementation.
|
||||
*
|
||||
*/
|
||||
class AppExport Extension : public virtual App::PropertyContainer
|
||||
{
|
||||
|
|
|
@ -34,6 +34,79 @@
|
|||
|
||||
namespace App {
|
||||
|
||||
/**
|
||||
* @brief Container which can hold extensions
|
||||
*
|
||||
* In FreeCAD normally inheritance is a chain, it is not possible to use multiple inheritance.
|
||||
* The reason for this is that all objects need to be exposed to python, and it is basically
|
||||
* impossible to handle multiple inheritance in the C-API for python extensions. Also using multiple
|
||||
* parent classes in python is currently not possible with the default object aproach.
|
||||
*
|
||||
* The concept of extensions allow to circumvent those problems. Extensions are FreeCAD objects
|
||||
* which work like normal objects in the sense that they use properties and class methods to define
|
||||
* their functionality. However, they are not exposed as individual usable entities but are used to
|
||||
* extend other objects. A extended object gets all the properties and methods of the extension.
|
||||
* Therefore it is like c++ multiple inheritance, which is indeed used to achieve this on c++ side,
|
||||
* but provides a few important additional functionalities:
|
||||
* - Property persistance is handled, save and restore work out of the box
|
||||
* - The objects python API gets extended too with the extension python API
|
||||
* - Extensions can be added from c++ and python, even from both together
|
||||
*
|
||||
* The interoperability with python is highly important, as in FreeCAD all functionality should be
|
||||
* as easily accessible from python as from c++. To ensure this, and as already noted, extensions can
|
||||
* be added to a object from python. However, this means that it is not clear from the c++ object type
|
||||
* if an extension was added or not. If added from c++ it becomes clear in the type due to the use of
|
||||
* multiple inheritance. If added from python it is a runtime extension and not visible from type.
|
||||
* Hence querying existing extensions of an object and accessing its methods works not by type
|
||||
* casting but by the interface provided in ExtensionContainer. The default workflow is to query if
|
||||
* an extension exists and then get the extension obejct. No matter if added from python or c++ this
|
||||
* interface works always the same.
|
||||
* @code
|
||||
* if (object->hasExtension(GroupExtension::getClassTypeId())) {
|
||||
* App::GroupExtension* group = object->getExtensionByType<GroupExtension>();
|
||||
* group->hasObject(...);
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* To add a extension to an object, it must comply to a single restriction: it must be derived
|
||||
* from ExtensionContainer. This is important to allow adding extensions from python and also to
|
||||
* access the universal extension API. As DocumentObject itself derives from ExtensionContainer this
|
||||
* should be the case automatically in most circumstances.
|
||||
*
|
||||
* Note that a small boilerplate change is needed next to the multiple inheritance when adding
|
||||
* extensions from c++. It must be ensured that the type registration is aware of the extensions.
|
||||
* Here a working example:
|
||||
* @code
|
||||
* class AppExport Part : public App::DocumentObject, public App::FirstExtension, public App::SecondExtension {
|
||||
* PROPERTY_HEADER_WITH_EXTENSIONS(App::Part);
|
||||
* };
|
||||
* PROPERTY_SOURCE_WITH_EXTENSIONS(App::Part, App::DocumentObject, (App::FirstExtension)(App::SecondExtension))
|
||||
* Part::Part(void) {
|
||||
* FirstExtension::initExtension(this);
|
||||
* SecondExtension::initExtension(this);
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* From python adding an extension is easier, it must be simply registered to a document object
|
||||
* at object initialisation like done with proeprties. Note that the special python extension objects
|
||||
* need to be added, not the c++ objects. Normally the only difference in name is the additional
|
||||
* "Python" at the end of the extension name.
|
||||
* @code{.py}
|
||||
* class Test():
|
||||
* __init(self)__:
|
||||
* registerExtension("App::FirstExtensionPython", self)
|
||||
* registerExtension("App::SecondExtensionPython", self)
|
||||
* @endcode
|
||||
*
|
||||
* Extensions can provide methods that should be overriden by the extended object for customisation
|
||||
* of the extension behaviour. In c++ this is as simple as overriding the provided virtual functions.
|
||||
* In python a class method must be provided which has the same name as the method to override. This
|
||||
* method must not neccessarily be in the object that is extended, it must be in the object which is
|
||||
* provided to the "registerExtension" call as second argument. This second argument is used as a
|
||||
* proxy and enqueired if the method to override exists in this proxy before calling it.
|
||||
*
|
||||
* For information on howto create extension see the documentation of Extension
|
||||
*/
|
||||
class AppExport ExtensionContainer : public virtual App::PropertyContainer
|
||||
{
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user