This commit constitutes an almost verbatim move from the documentation of Extension and ExtensionContainer to its own topic in core-app.dox.
446 lines
20 KiB
Plaintext
446 lines
20 KiB
Plaintext
/**
|
|
* @defgroup APP App
|
|
* @ingroup CORE
|
|
* @brief The part of FreeCAD that works without GUI (console or server mode)
|
|
*
|
|
* @details It contains the App namespace and defines core concepts such as
|
|
* @ref DocumentGroup "Document", @ref DocumentObjectGroup "Document Object",
|
|
* @ref PropertyFramework "Property Framework", @ref ExpressionFramework
|
|
* "Expression Framework", and the @ref ExtensionFramework "Extension
|
|
* Framework".
|
|
*
|
|
* The largest difference between the functionality in @ref BASE "Base"
|
|
* compared to %App is that %App introduces the notion of properties, both used
|
|
* in @ref App::Document "Document" and @ref App::DocumentObject
|
|
* "DocumentObject". In addition, %App has a representation of the running
|
|
* @ref App::Application "Application".
|
|
*/
|
|
|
|
/**
|
|
* @defgroup DocumentGroup Document
|
|
* @ingroup APP
|
|
* @brief The class that represents a FreeCAD document
|
|
*
|
|
* This (besides the App::Application class) is the most important class in FreeCAD.
|
|
* It contains all the data of the opened, saved, or newly created FreeCAD Document.
|
|
* The App::Document manages the Undo and Redo mechanism and the linking of documents.
|
|
*
|
|
* Note: the documents are not free objects. They are completely handled by the
|
|
* App::Application. Only the Application can Open or destroy a document.
|
|
*
|
|
* \section Exception Exception handling
|
|
* As the document is the main data structure of FreeCAD we have to take a close
|
|
* look at how Exceptions affect the integrity of the App::Document.
|
|
*
|
|
* \section UndoRedo Undo Redo an Transactions
|
|
* Undo Redo handling is one of the major mechanism of a document in terms of
|
|
* user friendliness and speed (no one will wait for Undo too long).
|
|
*
|
|
* \section Dependency Graph and dependency handling
|
|
* The FreeCAD document handles the dependencies of its DocumentObjects with
|
|
* an adjacency list. This gives the opportunity to calculate the shortest
|
|
* recompute path. Also, it enables more complicated dependencies beyond trees.
|
|
*
|
|
* @see App::Application
|
|
* @see App::DocumentObject
|
|
*/
|
|
|
|
/**
|
|
* @defgroup DocumentObjectGroup Document Object
|
|
* @ingroup APP
|
|
* @brief %Base class of all objects handled in the Document.
|
|
*/
|
|
|
|
/**
|
|
* @defgroup PropertyFramework Property framework
|
|
* @ingroup APP
|
|
* @brief System to access object properties.
|
|
*
|
|
* @section propframe_intro Introduction
|
|
*
|
|
* The property framework introduces an intricate system to access properties
|
|
* of objects. It provides the ability to:
|
|
* 1. define properties in a class and access them by name,
|
|
* 2. add properties to a class at runtime and access them by name, and
|
|
* 3. access properties of a class by name without knowing the class type.
|
|
*
|
|
* The first two points are similar to what the dynamic reflection framework of
|
|
* C# or Java offer. The third point allows FreeCAD to have App::Property as a
|
|
* generic interface to access properties. This is similar to the way that
|
|
* Python allows to access properties of a class by name.
|
|
*
|
|
* This ability is introduced by the @ref App::PropertyContainer
|
|
* "PropertyContainer" class and can be used by all derived classes. In
|
|
* particular, there are two classes that inherit from @ref
|
|
* App::PropertyContainer "PropertyContainer" which are @ref App::Document
|
|
* "Document" and @ref App::DocumentObject "DocumentObject". These two classes
|
|
* serve different purposes but are both able to hold properties. @ref
|
|
* App::PropertyContainer "PropertyContainer" contains the shared logic to do
|
|
* so.
|
|
*
|
|
* @section propframe_static_dynamic_props Static/Dynamic Properties
|
|
*
|
|
* In C++, it is possible to define properties as part of the class. These can
|
|
* be considered "static" properties but this term is typically not used within
|
|
* FreeCAD. Properties created in a class cannot be removed from a @ref
|
|
* App::PropertyContainer "PropertyContainer".
|
|
*
|
|
* However, it is also possible to add properties to a @ref
|
|
* App::PropertyContainer "PropertyContainer" at runtime. These properties are
|
|
* called "dynamic properties" and these properties can be freely added and
|
|
* removed by users. In Python, all properties are dynamic properties.
|
|
*
|
|
* @section propframe_mechanisms Mechanisms
|
|
*
|
|
* The macros `PROPERTY_HEADER` and `PROPERTY_SOURCE` (and variants) are used
|
|
* to define a private static PropertyData member in the class. This data
|
|
* structure contains a multi index that maps 1) the property name to the
|
|
* property specifications @ref App::PropertyData::PropertySpec "PropertySpec",
|
|
* allowing access of properties by name, and 2) maps the offset of a property
|
|
* with respect to its container, allowing access to property specifications.
|
|
*
|
|
* The static function @ref App::PropertyContainer::getPropertyDataPtr()
|
|
* "PropertyContainer::getPropertyDataPtr()" is used to access the class-wide
|
|
* static @ref App::PropertyData "PropertyData" structure shared by all
|
|
* instances of the class. The virtual function @ref
|
|
* App::PropertyContainer::getPropertyData()
|
|
* "PropertyContainer::getPropertyData()" allows access to the class-level
|
|
* static PropertyData based on the dynamic type of the object, even when
|
|
* downcasted. This allows for example a @ref App::PropertyInteger
|
|
* "PropertyInteger*" downcasted to @ref App::Property "Property*" to access
|
|
* all its properties.
|
|
*
|
|
* Since the @ref App::PropertyData "PropertyData" structure is static in the
|
|
* class and maintains static information of properties, such as the group and
|
|
* documentation, we need to be able to access a specific instance given a
|
|
* property name. This is achieved by looking up the @ref
|
|
* App::PropertyData::PropertySpec "PropertySpec" in the index based on the
|
|
* property name. The property specification stores an offset to the property
|
|
* address given an @ref App::PropertyData::OffsetBase "OffsetBase" which wraps
|
|
* the address of a @ref App::PropertyContainer "PropertyContainer". See @ref
|
|
* App::PropertyData::findProperty() "PropertyData::findProperty()" and @ref
|
|
* App::PropertyData::OffsetBase::getOffsetTo() "OffsetBase::getOffsetTo()".
|
|
* The offset of the property relative to the offset base gives us the address
|
|
* of the property in the instance. Note that different values for properties
|
|
* across property containers are stored in the @ref App::Property "Property" instances.
|
|
*
|
|
* The reverse is also needed: Given a property in a property container, it is
|
|
* possible to compute the offset relative to the base of the property
|
|
* container. The index in @ref App::PropertyData "PropertyData" allows us to
|
|
* acquire the property spec and provides us with the static data associated
|
|
* with the property.
|
|
*
|
|
* Dynamic properties are stored in their own @ref App::DynamicProperty
|
|
* "DynamicProperty" container. It can be added with the function @ref
|
|
* App::PropertyContainer::addDynamicProperty()
|
|
* "PropertyContainer::addDynamicProperty()" and removed with @ref
|
|
* App::PropertyContainer::removeDynamicProperty()
|
|
* "PropertyContainer::removeDynamicProperty()". The rest of the interface of
|
|
* dynamic properties is virtually the same. In general, in FreeCAD, it is
|
|
* difficult for users to know whether a property is dynamic or static and they
|
|
* typically do not need to be concerned with this distinction.
|
|
*
|
|
* @section Locking Dynamic Properties
|
|
*
|
|
* Since in Python all properties are dynamic properties, it is difficult to
|
|
* understand whether properties are part of a Python class and are necessary
|
|
* for the functioning of the class, or whether a user has added these
|
|
* properties. Therefore, it is possible to indicate that a property is
|
|
* "locked":
|
|
*
|
|
* @code
|
|
* prop->setStatus(Property::LockDynamic, true);
|
|
* @endcode
|
|
*
|
|
* In Python, this can be indicated in the `addProperty()` function:
|
|
*
|
|
* @code
|
|
* addProperty(type: string, name: string, group="", doc="",
|
|
* attr=0, read_only=False, hidden=False,
|
|
* locked=False, enum_vals=[])
|
|
* @endcode
|
|
*
|
|
* The Property Framework makes it possible in the first place to make an
|
|
* automatic mapping to python (e.g. in App::FeaturePy) and abstract editing
|
|
* properties in Gui::PropertyEditor.
|
|
*
|
|
* @section Examples
|
|
*
|
|
* Here some little examples how to use the property framework:
|
|
*
|
|
* @code
|
|
* // Acquire a property by name and return the value as a Python object.
|
|
* Property *prop = feature->getPropertyByName(nameProperty);
|
|
* if (prop) {
|
|
* return prop->getPyObject();
|
|
* }
|
|
* @endcode
|
|
*
|
|
* or:
|
|
*
|
|
* @code
|
|
* // Restore properties from a reader.
|
|
* void PropertyContainer::Restore(Base::Reader &reader)
|
|
* {
|
|
* reader.readElement("Properties");
|
|
* int Cnt = reader.getAttributeAsInteger("Count");
|
|
*
|
|
* for(int i=0 ;i<Cnt ;i++) {
|
|
* reader.readElement("Property");
|
|
* string PropName = reader.getAttribute("name");
|
|
* Property* prop = getPropertyByName(PropName.c_str());
|
|
* if(prop) {
|
|
* prop->Restore(reader);
|
|
* }
|
|
*
|
|
* reader.readEndElement("Property");
|
|
* }
|
|
* reader.readEndElement("Properties");
|
|
* }
|
|
* @endcode
|
|
*/
|
|
|
|
/**
|
|
* @defgroup ExpressionFramework Expressions framework
|
|
* @ingroup APP
|
|
* @brief The expression system allows users to write expressions and formulas that produce values
|
|
*/
|
|
|
|
/**
|
|
* @defgroup ExtensionFramework Extension framework
|
|
* @ingroup APP
|
|
* @brief The extension system provides a way to extend the functionality of Document Objects.
|
|
*
|
|
* The concept of extensions provides a way to extend the functionality of
|
|
* objects in FreeCAD despite Python's limitations with multiple inheritance
|
|
* (see below for an explanation). Extensions are FreeCAD objects that act
|
|
* like regular objects in the sense that they have properties and class
|
|
* methods to define their functionality. However, extensions are not exposed
|
|
* as individual usable entities but are used to extend other objects. An
|
|
* extended object obtains all the properties and methods of the extension.
|
|
*
|
|
* As such, it is like C++ multiple inheritance, which is indeed used to
|
|
* achieve this on C++ side, but it provides a few important additional
|
|
* functionalities as well:
|
|
*
|
|
* - Property persistence 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 since in FreeCAD, all
|
|
* functionality should be easily accessible from both Python and C++. To
|
|
* ensure this -- as already noted -- extensions can be added to an object from
|
|
* Python.
|
|
*
|
|
* However, this means that it is not clear from the C++ object type whether 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 the 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 whether an extension exists and then to get the extension
|
|
* object. This interface always works the same, no matter if added from
|
|
* Python or C++.
|
|
*
|
|
* @code
|
|
* if (object->hasExtension(GroupExtension::getClassTypeId())) {
|
|
* App::GroupExtension* group = object->getExtensionByType<GroupExtension>();
|
|
* group->hasObject(...);
|
|
* }
|
|
* @endcode
|
|
*
|
|
* To add an 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 two small boilerplate changes are needed in addition to the
|
|
* multiple inheritance when adding extensions from C++.
|
|
*
|
|
* 1. It must be ensured that the property and type registration is aware of
|
|
* the extensions by using special macros.
|
|
* 2. The extensions need to be initialised in the constructor.
|
|
*
|
|
* Here is a working example:
|
|
* @code{.cpp}
|
|
* 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)
|
|
*
|
|
* 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 properties. 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 overridden 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 necessarily 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 queried if the method
|
|
* to override exists in this proxy before calling it.
|
|
*
|
|
* @section SecExtensionFrameworkCreateExtension Create an Extension
|
|
*
|
|
* An Extension is like every other FreeCAD object and is based on properties.
|
|
* All information storage and persistence should be achieved by use of these
|
|
* properties. Additionally, any number of methods can be added to provide
|
|
* functionality related to the properties. There are three small differences
|
|
* to normal objects:
|
|
*
|
|
* 1. They must be derived from this Extension class,
|
|
* 2. Properties must be handled with special extension macros.
|
|
* 3. Extensions must be initialised.
|
|
*
|
|
* The following code illustrates the basic setup of an extension:
|
|
*
|
|
* @code
|
|
* class MyExtension : public Extension
|
|
* {
|
|
* EXTENSION_PROPERTY_HEADER(MyExtension);
|
|
* PropertyInt MyProp;
|
|
* virtual bool overridableMethod(DocumentObject* obj) {};
|
|
* };
|
|
*
|
|
* EXTENSION_PROPERTY_SOURCE(App::MyExtension, App::Extension)
|
|
* MyExtension::MyExtension()
|
|
* {
|
|
* EXTENSION_ADD_PROPERTY(MyProp, (0))
|
|
* initExtensionType(MyExtension::getExtensionClassTypeId());
|
|
* }
|
|
*
|
|
* using MyExtensionPython = ExtensionPythonT<MyExtension>;
|
|
* @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 extended object when added from C++.
|
|
* This means one should carefully design the API and make only necessary methods public or protected.
|
|
* Every internal method should be private.
|
|
*
|
|
* The automatic availability of methods in the class does not hold for the
|
|
* Python interface, only for C++ classes. This is common in the rest of
|
|
* FreeCAD as well: 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 is the same for the normal FreeCAD Python object workflow.
|
|
* There is nothing special at all for Python extension objects: the normal
|
|
* `.pyi` and `PyImp.cpp` approach is used but note that it is important that
|
|
* the object's father is the correct extension base class. Additionally, make
|
|
* sure the extension returns the correct Python object in its
|
|
* getExtensionPyObject() call.
|
|
*
|
|
* Every method that is created in the extension's Python counterpart will be
|
|
* later added to the extended object. This happens automatically for both the
|
|
* C++ and Python extension if getExtensionPyObject() returns the correct
|
|
* Python object. This does not require extra work.
|
|
*
|
|
* A special case that needs to be handled for extensions is the possibility of
|
|
* overridden methods. Often, it is desired to customise extension behaviour
|
|
* by allowing the user to override methods provided by the extension. On the
|
|
* C++ side, this is trivial: such methods are simply marked as "virtual" and
|
|
* can then be overridden in any derived class. This is more involved for the
|
|
* Python interface and here special care needs to be taken.
|
|
*
|
|
* As already shown 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 that 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 extension
|
|
* creator's 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
|
|
* macro documentation for how to 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
|
|
* using MyExtensionPython = ExtensionPythonT<MyExtensionPythonT<MyExtension>>;
|
|
* @endcode
|
|
*
|
|
* This boilerplate is absolutely necessary to allow overridable methods in
|
|
* Python and it is the extension creator's responsibility to ensure full
|
|
* implementation.
|
|
*
|
|
* @section SecExtensionLimitiationsPython Limitations of Python
|
|
*
|
|
* Without this extension system, it would be challenging to extend
|
|
* functionality in FreeCAD. Although C++ supports multiple inheritance, it is
|
|
* not possible to use it in FreeCAD because it should be possible to expose
|
|
* all objects to Python.
|
|
*
|
|
* However, using multiple parent classes in Python is currently not possible
|
|
* with the default object approach. Moreover, it is basically impossible to
|
|
* handle multiple inheritance in the C-API for Python extensions.
|
|
*/
|
|
|
|
/**
|
|
* @namespace App
|
|
* @ingroup APP
|
|
* @brief The namespace for the part of FreeCAD that works without GUI.
|
|
* @details This namespace includes %Application services of FreeCAD that such as:
|
|
* - The Application class
|
|
* - The Document class
|
|
* - The DocumentObject classes
|
|
* - The Expression classes
|
|
* - The Property classes
|
|
*
|
|
* For a more high-level discussion see the topic @ref APP "App".
|
|
*/
|
|
|