diff --git a/src/App/Extension.h b/src/App/Extension.h index c576c8936..e8bcddf8b 100644 --- a/src/App/Extension.h +++ b/src/App/Extension.h @@ -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 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 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> 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 { diff --git a/src/App/ExtensionContainer.h b/src/App/ExtensionContainer.h index b7a80b56d..258e13528 100644 --- a/src/App/ExtensionContainer.h +++ b/src/App/ExtensionContainer.h @@ -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(); + * 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 {