MeVisLab Scripting Reference
|
MeVisLab offers object wrappers that allow access to C++ objects from Python. An object wrapper can be registered for any class, and is retrieved by calling the method object() on a Python object. Typically, object wrappers are used to work directly with Base objects, but of course wrappers can be implemented for ML or Inventor modules to make them scriptable without an intermediate layer of fields. For more information on the underlying Qt binding, see its web page (http://pythonqt.sourceforge.net/).
Object wrappers are implemented in C++ and register a wrapper name and a wrapper class to an arbitrary C++ class. On the MeVisLab side, a definition file for this wrapper name is required.
We will start by creating an object wrapper for a simple example Base object: the BaseMessengerExample class that can be found in the package MeVisLab/Examples. The BaseMessengerExample contains four attributes of different types (Vector3, double and a custom MessengerShapeType). For the full example wrapper code, see the MLBaseCommunicationExampleWrappers project in MeVisLab/Examples.
An object wrapper object looks very similar to an ML module project, but there are some fundamental differences. Because we are writing a class for the PythonQt binding, we need to include the script interface header, but can omit most ML headers normally used in ML modules. Of course we need to include the headers for the classes we want to wrap.
Add any projects you need, e.g., the module or class you are wrapping.
Wrappers are derived from QObject.
Currently, there is no wizard for object wrapper project creation available.
The wrapper class above is useless in its current state, since it doesn't know about any objects, let alone the one we want to wrap. In fact, this code would not compile.
Now let's add some lines that introduce the class to be wrapped and will make the code compile.
When object() is called, a pointer to the underlying object is passed to the wrapper constructor. The wrapped class usually is not aware of the wrapper.
Now we would be able to get the wrapper object in a scripting console, but we can't do much with it, since the wrapper class does only define a constructor. Now our BaseMessengerExample class has several attributes we would very much like to read and modify.
Wrapper methods are declared as slots.
Our wrapper object now has a method getDiameter() that returns the value of the diameter property, and a method setDiameter that sets that value. This is pretty straightforward, but let's have a look at that code again. Because diameter is of an elemental type that's available in Qt, we could simply use that type. But a script wrapper can only use Qt types for it's setters and getters. So how are types like strings, Vector3 or STL vectors converted?
When writing object wrappers, we have to be aware that attributes will change their type two times on the way from the wrapped class to the scripting console. First they have to be converted to something PythonQt can understand, and then PythonQt will convert it again to something Python can understand.
The first part is pretty easy, since C++/STL types and Qt types are quite similar. An std::string becomes a QString, an std::vector<T> a QList<T> (actually a QVector<T> would be more appropriate, but we anticipating the conversion to Python types here), elemental types remain elemental types (except for integers, which become qints), and so on. Now with ML types, it's a little more difficult. Elemental types still map to the corresponding elemental Qt type, that is, MLdouble becomes double and MLint becomes qint64. But how about Vector3? For a custom data type, we need to know how it is structured. A Vector3 is basically a list of three double values, so an obvious equivalent would be a QList<double>. More complex types, especially ones with custom behavior (i.e., more than just collections of values), cannot be converted properly. Note that the class we're wrapping in the first place is such a complex type, and we're doing the wrapping piece-by-piece.
The second part is even easier, because PythonQt knows how to convert types between Qt/C++ and Python. Please have a look at the data type mapping (http://pythonqt.sourceforge.net/Developer.html). Note that you can return and receive almost everything with QVariants and QVariantList. If you need to transfer a Python list with differently typed values in it, for example to serialize a more complex object, you can do so with a QVariantList, but you'll need to take care when unpacking that list. Depending on your knowledge about the transfered structures, you'll need to cast values on their way in or out.
Note that conversion methods are omitted here.
Our example wrapper now offers access to all four attributes of the BaseMessengerExample.
Both Qt and Python can use properties to read and write class members, and of course we can use those in our wrapper. Although Qt supports arbitrary types in its properties, PythonQt only supports a useful subset. In case of QList<double>, we have to use QVariant (or QVariantList) as the property type instead.
Note that we changed the wrapper types of position and color.
Just replicate this for the color property. The other setters and getters are not changed. For ease of read, we use pseudo-codish for-loops.
If you use properties, it's often a good idea to declare the respective setters and getters as private. And here's one more thing: nothing prevents you from adding methods that just trigger some action in the wrapped object, or that even do things the object cannot do.
The wrapper for the BaseMessengerExample is finished now, so we instantiate a BaseOwnerExample module to see how the code we implemented in the previous steps works out on the Python side. Get the wrapper object with
Note the message from the wrapper init method.
You can modify and examine the underlying object with the available properties.
Base objects do not emit field notifications when changed, so your modifications will not be propagated until you force this with
We can access an existing object with our wrapper, but can we also create a new one from Python? For simple ml::Base derived objects, the answer is no. For objects derived from ml::RefCountedBase, it becomes possible to create an instance and the wrapper for it using Python only. Such RefCountedBase derived objects can be created using MLAB.createMLBaseObject("Classname", [arg1, arg2, ...]). Examples for ml::RefCountedBase derived classes are WEM, CSOList and RemoteCallInterface.
In case of a RefCountedBase derived class, you need to derive your wrapper from the MLRefCountedBaseWrapper class instead of QObject, which handles ref-counting of the base object for you. The following code shows how to do this:
An example RefCountedBase derived class:
The wrapper for it:
Registration in the init code of your library:
It is possible to use/return QObject derived types from your wrapper's slots, but you need to register these types using:
When you return such an object, make sure that it either has a QObject as its parent (so the ownership is handled by the parent), or that you mark the object as disposable before you return it:
Marking the object as disposable will allow PythonQt to garbage collect it when it goes out of scope. When you return such objects, make sure that they do NOT contain a reference to the base object which can be destroyed any time. If you need such a reference, make sure that you clear that reference when the base wrapper is destroyed, which effectively means that you need to track the returned QObjects in your wrapper.
Many Base types comprise a type proper and a list type. When getting the wrapper for the list type, PythonQt does not create the wrappers for the objects in the list automatically. You have to implement two wrappers for these types, one for the list and one for the type proper. The list wrapper constructor then creates the remaining object wrappers (the element wrappers) itself. For the object ownership, it is crucial that you set the parent of each object wrapper to the list wrapper.
Example code:
The list wrapper is now the owner of the element wrappers, and the desctructor of the list wrapper has to destroy them. In the same way, you can implement methods adding and removing elements to and from the list. When you create a new element, you create a wrapper for it, and when you remove it, you remove the wrapper, too.
Typically, wrappers are used to work with Base objects, but wrappers for classes derived from Module can be written the same way, and the same limitations on types and ownership apply.
To create Doxygen documentation for the scripting reference, add
\ script
(remove the space between the backslash and script) to Doxygen blocks that describe properties or slots, and add the wrapper name in the list below. To be created by the ToolRunner or MasterBuilder, add a line like
with the correct package and directory to pathList in MeVisLab/Resources/Documentation/Sources/SDK/ScriptingReference/filter.py.
If your wrapper doesn't work as it should, have a look at the common reasons for this:
The following object wrappers (mostly for Base object wrapping) are currently available: