VTK
9.1.0
|
The Python wrappers for VTK are produced algorithmically from the VTK header files, and using VTK through Python is very much like using VTK through C++. Nearly all of the same functionality is available, with Python types converted automatically to C++ types and vice-versa. This document assumes that you are already familiar with VTK itself, and want to know more details about how to use VTK through Python.
The most convenient way to install VTK for Python is with pip, which is a package manager that comes with Python:
pip install vtk
This will provide a basic installation of VTK that includes all core functionality, but which will not include some of the specialized VTK modules that rely on external libraries. Binary packages for VTK can also be downloaded directly from https://www.vtk.org/download/.
Instructions for building VTK from source code are given in the file Documentation/dev/build.md within the source repository.
VTK is comprised of over one hundred individual modules. Programs can import just the modules that are needed, in order to reduce load time.
from vtkmodules.vtkCommonCore import vtkObject from vtkmodules.vtkFiltersSources import vtkConeSource, vtkSphereSource from vtkmodules.vtkRenderingCore import ( vtkActor, vtkDataSetMapper, vtkRenderer, vtkRenderWindow ) import vtkmodules.vtkRenderingOpenGL2
When getting started, however, it is hard to know what modules you will need. So if you are experimenting with VTK in a Python console, or writing a quick and dirty Python script, it is easiest to simply import everything. There is a special module called 'all
' that allows this to be done:
from vtkmodules.all import *
After importing the VTK classes, you can check to see which module each of the classes comes from:
for c in vtkObject, vtkConeSource, vtkRenderWindow: print(f"from {c.__module__} import {c.__name__}")
The output is as follows:
from vtkmodules.vtkCommonCore import vtkObject from vtkmodules.vtkFiltersSources import vtkConeSource from vtkmodules.vtkRenderingCore import vtkRenderWindow
In the first 'import' example above, you might be wondering about this line:
import vtkmodules.vtkRenderingOpenGL2
This import is needed because vtkRenderingOpenGL2
provides the OpenGL implementations of the classes in vtkRenderingCore
. To see this in action, open a new Python console and do the following:
>>> from vtkmodules.vtkRenderingCore import vtkRenderWindow >>> renwin = vtkRenderWindow() >>> type(renwin) <class 'vtkmodules.vtkRenderingCore.vtkRenderWindow'> >>> >>> import vtkmodules.vtkRenderingOpenGL2 >>> renwin2 = vtkRenderWindow() >>> type(renwin2) <class 'vtkmodules.vtkRenderingOpenGL2.vtkXOpenGLRenderWindow'>
After vtkRenderingOpenGL2
has been imported, the vtkRenderWindow()
constructor magically starts returning a different type of object. This occurs because vtkRenderWindow
is a factory class, which means that the kind of object it produces can be overridden by an implementation class. In order for the implementation class to do the override, all that is necessary is that its module is imported. To make things even more confusing, vtkRenderingOpenGL2
is not the only module that contains implementations for the factory classes in vtkRenderingCore
. The following modules are often needed, as well:
import vtkmodules.vtkInteractionStyle import vtkmodules.vtkRenderingFreeType
Although you only need implementations for the factory classes that you use, it can be hard to know which classes are factory classes, or what modules contain implementations for them. Also, it can be difficult to even know what classes you are using, since many VTK classes make use of other VTK classes. An example of this is vtkDataSetMapper
, which internally uses vtkPolyDataMapper
to do the rendering. So even though vtkDataSetMapper
is not a factory class, it needs an OpenGL implmentation for vtkPolyDataMapper
.
The simplest approach is to import all the important implementation modules into your program, even if you are not certain that you need them.
vtkRenderingCore
, import vtkRenderingOpenGL2, vtkRenderingFreeType, vtkInteractionStyle
vtkRenderingVolume
, import vtkRenderingVolumeOpenGL2
vtkCharts
, import vtkContextOpenGL2
There are many VTK programs that still use the old 'vtk
' module, instead of using the 'vtkmodules
' package introduced in VTK 8.2:
import vtk
Although this works, it is not recommended. The 'vtk
' module is only for backwards-compatibility and is, in fact, just the 'vtkmodules.all
' with a different name. Because of the details of how the renaming is done, the 'import vtk
' statement can confuse IDEs like PyCharm and make them unable to index the module. Instead, if you desire a module named 'vtk
', either for personal preference or a need for backwards compatibility, then use this:
import vtkmodules.all as vtk
This will make it clear to people who read your code that 'vtk
' is simply a different name for 'vtkmodules.all
'. It will also keep your IDE from becoming confused about what exactly the 'vtk
' module is.
In C++, classes derived from vtkObjectBase
are instantiated by calling ::New()
. In Python, these classes are instantiated by simply calling the constructor:
o = vtkObject()
For factory classes, the returned object's type might be a subtype of the class. This occurs because the Python wrappers are actually calling ::New()
for you, which allows the VTK factory overrides to occur:
>>> a = vtkActor() >>> type(a) <class 'vtkmodules.vtkRenderingOpenGL2.vtkOpenGLActor'>
When you create a VTK object in Python, you are in fact creating two objects: a C++ object, and a Python object that holds a pointer to the C++ object. The repr()
of the object shows the memory address of the C++ object (in parentheses) and of the Python object (after the 'at
'):
>>> a = vtkFloatArray() >>> a <vtkmodules.vtkCommonCore.vtkFloatArray(0x5653a6a6f700) at 0x7f0e7aecf5e0>
If you call str()
or print()
on these objects, the wrappers will call the C++ PrintSelf()
method. The printed information can be useful for debugging:
>>> o = vtkObject() >>> print(o) vtkObject (0x55858308a210) Debug: Off Modified Time: 85 Reference Count: 1 Registered Events: (none)
VTK also uses several classes that aren't derived from vtkObjectBase
. The most important of these is vtkVariant
, which can hold any type of object:
>>> v1 = vtkVariant('hello') >>> v1 vtkmodules.vtkCommonCore.vtkVariant('hello') >>> v2 = vtkVariant(3.14) >>> v2 vtkmodules.vtkCommonCore.vtkVariant(3.14)
The wrapping of these classes is fully automatic, but is done in a slightly different manner than vtkObjectBase
-derived classes. First, these classes have no ::New()
method, and instead the public C++ constructors are wrapped to create an equivalent Python contructor. Second, the Python object contains its own copy of the C++ object, rather than containing just a pointer to the C++ object. The vast majority of these classes are lightweight containers and numerical types. For example, vtkQuaterniond
, vtkRectf
, vtkColor4ub
, etc. Many of them are actually class templates, which are discussed below.
When you apply print()
or str()
to these objects, the operator<<
of the underlying C++ object is used to print them. For repr()
, the name of the type name is printed, followed by the str()
output in prentheses. The result looks similar to a constructor, though it might look strange depending on what operator<<
produces.
>> v = vtkVariant() >> print(repr(v)) vtkmodules.vtkCommonCore.vtkVariant((invalid))
There are several C++ templates in VTK, which can be tricky to use from the wrappers since the Python language has no real concept of templates. The the wrappers wrap templates as dictionary-like objects that map the template parameters to template instantiations:
>>> vtkSOADataArrayTemplate <template vtkCommonCorePython.vtkSOADataArrayTemplate> >>> vtkSOADataArrayTemplate.keys() ['char', 'int8', 'uint8', 'int16', 'uint16', 'int32', 'uint32', 'int', 'uint', 'int64', 'uint64', 'float32', 'float64'] >>> c = vtkSOADataArrayTemplate['float64'] >>> c <class 'vtkmodules.vtkCommonCore.vtkSOADataArrayTemplate_IdE'>
The wrappers instantiate the C++ template for a few useful types, as indicated by the keys()
of the template. The Python type name also has a suffix (the 'IdE
') that indicates the template parameters in a compressed form according to IA64 C++ ABI name mangling rules, even when VTK is built with a compiler that does not use the IA64 ABI natively.
Objects are created by first instantiating the template, and then instantiating the class:
>>> a = vtkSOADataArrayTemplate['float32']() >>> a.SetNumberOfComponents(3)
In the case of multiple template parameters, the syntax can look rather complicated, but really it isn't all that bad. For example, constructing a vtkTuple<double,4>
in Python looks like this, with the template args in square brackets and the constructor args in parentheses:
>>> vtkTuple['float64',4]([1.0, 2.0, 3.0, 4.0]) vtkmodules.vtkCommonMath.vtkTuple_IdLi4EE([1.0, 2.0, 3.0, 4.0])
The type names are the same as numpy's dtypes: bool
, int8
, uint8
, int16
, uint16
, int32
, uint32
, int64
, uint64
, float32
, and float64
. Since int64
is 'long long
', int
is used for long
. Also see Template Keys in Advanced Topics.
When VTK methods are called from Python, conversion of all parameters from Python to C++ occurs automatically. That is, if the C++ method signature expects an integral type, you can pass a Python int
, and if C++ expects a floating-point type, you can pass a Python float
(or any type that allows implicit conversion to float
).
For C++ 'char
' parameters, which are rarely used in VTK, you must pass a string with a length of 1 or 0 bytes. This restricts the value to ASCII, since non-ASCII characters require at least 2 bytes in utf-8. An empty string signifies a null byte, and '\0' can also be used.
A Python tuple
, list
, or any other Python sequence can be passed to a VTK method that requires an array or std::vector
in C++:
>>> a = vtkActor() >>> p = (100.0, 200.0, 100.0) >>> a.SetPosition(p)
If the method is going to modify the array that you pass as a parameter, then you must pass a Python list
that has the correct number of slots to accept the returned values. If you try this with a tuple
, you will get a TypeError
because tuple
is immutable.
>>> z = [0.0, 0.0, 0.0] >>> vtkMath.Cross((1,0,0),(0,1,0),z) >>> print(z) [0.0, 0.0, 1.0]
For multi-dimensional array parameters, you can either use a nested list, or you can use numpy array with the correct shape.
If the C++ method returns a pointer to an array, then in Python the method will return a tuple if the wrappers know the size of the array. In most cases, the size is hinted in the header file.
>>> a = vtkActor() >>> print(a.GetPosition()) (0.0, 0.0, 0.0)
Finally, Python None
is treated the same as C++ nullptr
, which allows you to pass null objects and null strings:
>>> a = vtkActor() >>> a.SetMapper(None) >>> print(a.GetMapper()) None
A method cannot be used from Python if its C++ parameters or return type cannot be converted to or from Python by the wrappers, or if the method is templated. Common non-convertible types include std::ostream
, std::istream
, and all STL container types except for std::vector
(see below), and any non-trivial pointer type or any pointer to an object whose class is not derived from vtkObjectBase
.
The wrappable parameter types are:
char
, wrapped as a single ASCII character in a Python str
signed char
and unsigned char
, wrapped as Python int
short
, int
, long
and long long
, wrapped as Python int
unsigned short
to unsigned long long
, wrapped as Python int
float
and double
, wrapped as Python float
size_t
and ssize_t
, wrapped as Python int
std::string
, wrapped as Python str
via utf-8 encoding/decodingstd::vector<T>
where T
is one of the above, as Python tuple
or list
const T&
where T
is any of the above, wrapped as described aboveT[N]
where T
is a fundamental type, as Python tuple
or list
T[N][M]
where T
is a fundamental type, as nested tuple
or list
T*
where T
is a fundamental type, as tuple
or list
vtkObjectBase*
and derived types, as their respective Python typevtkVariant
), but not pointers to these typeschar*
, as Python str
via utf-8 encoding/decodingvoid*
, as Python buffer (e.g. bytes
or bytearray
)(void (*f)(void*), void*)
as a Python callable typeReferences like int&
and std::string&
are wrapped via a reference proxy type as described in the Pass by Reference section below. Non-const references to std::vector<T>
and other mutable types do not use a proxy, but instead require that a mutable Python object is passed, for example a list
rather than a tuple
.
A void*
parameter can accept a pointer in two different ways: either from any Python object that supports the Python buffer protocol (this includes all numpy arrays along with the Python bytes and bytearray types), or from a string that contains a mangled pointer of the form '_hhhhhhhhhhhh_p_void
' where 'hhhhhhhhhhhh
' is the hexadecimal address. Return-value void*
will always be a string containing the mangled pointer.
Also, a T*
parameter for fundamental type T
can accept a buffer object, if and only if it is annotated with the VTK_ZEROCOPY
hint in the header file. With this hint, a numpy array of T
can be passed to a T*
parameter and the VTK method will directly access the memory buffer of the array. Hence the name 'zerocopy', which indicates no copying is done, and that direct memory access is used.
The vtkObject::AddObserver()
method has a special wrapping, as discussed in the Observer Callbacks section below.
If a wrapped type has constructor that takes one parameter, and if that constructor is not declared 'explicit
', then the wrappers will automatically use that constructor for type conversion to the parameter type. The wrappers ensure that this conversion occurs in Python in the same manner that it is expected to occur in C++.
For example, vtkVariantArray
has a method InsertNextItem(v:vtkVariant)
, and vtkVariant
has a constructor vtkVariant(x:int)
. So, you can do this:
>>> variantArray.InsertNextItem(1)
The wrappers will automatically construct a vtkVariant
from '1
', and will then pass it as a parameter to InsertNextItem()
. This is a feature that most C++ programmers will take for granted, but Python users might find it surprising.
If you call a VTK method that is overloaded, the Python wrappers will choose the overload that best matches the supplied arguments. This matching takes into account all allowed implicit conversions, such as int to float or any conversion constructors that are defined for wrapped objects.
Some overloads will be unavailable (not wrapped) either because they are unwrappable as per the criteria described above, or because they are shadowed by another overload that is always perferable. A simple example of this is any methods that is overloaded on C++ float
and double
. The Python float
type is a perfect match C++ double
, therefore the float
overload is not wrapped.
A static method can be called without an instance. For example,
vtkObject.SetGlobalWarningDisplay(1)
Some VTK classes, like vtkMath, consist solely of static methods. For others, like vtkMatrix4x4
, most of the non-static methods have static overloads. Within Python, the only way to tell if a VTK method is static (other than trying it) is to look at its docstring.
When a non-static method is called on the class, rather than on an instance, it is called an unbound method call. An unbound method call must provide 'self' as the first argument, where 'self' is an instance of either the class or a subclass.
w = vtkRenderWindow() vtkWindow.Render(w)
In other words, the wrappers translate Python unbound method calls into C++ unbound method calls. These are useful when deriving a Python class from a wrapped VTK class, since they allow you to call any base class methods that have been overridden in the subclass.
For special classes (the ones not derived from vtkObjectBase
), some useful C++ operators are wrapped in python. The '[]
' operator is wrapped for indexing and item assignment, but because it relies on hints to guess which indices are out-of-bounds, it is only wrapped for vtkVector
and related classes.
The comparison operators '<
' '<=
' '==
' '>=
' '>
' are wrapped for all classes that have these operators in C++. These operators allow sorting of vtkVariant
objects with Python.
The '<<
' operator for printing is wrapped and is used by the python print()
and str()
commands.
VTK uses both char*
and std::string
for strings. As far as the wrappers are concerned, these are equivalent except that the former can be nullptr
(None
in Python). For both, the expected encoding is ASCII or utf-8.
In Python, either str
or bytes
can be used to store strings, and both of these can be passed to VTK methods that require char*
or std::string
(or the legacy vtkStdString
). A str
object is passed to VTK as utf-8, while a bytes
object is passed as-is.
When a VTK method returns a string, it is received in Python as a str
object if it is valid utf-8, or as a bytes
object if not. The caller should check the type of the returned object (str
, bytes
, or perhaps None
) if there is any reason to suspect that non-utf-8 text might be present.
VTK provides conversion between std::vector
and Python sequences such as tuple
and list
. If the C++ method returns a vector, the Python method will return a tuple:
C++: const std::vector<std::string>& GetPaths() C++: std::vector<std::string> GetPaths() Python: GetPaths() -> Tuple[str]
If the C++ method accepts a vector, then the Python method can be passed any sequence with compatible values:
C++: void SetPaths(const std::vector<std::string>& paths) C++: void SetPaths(std::vector<std::string> paths) Python: SetPaths(paths: Sequence[str]) -> None
Furthermore, if the C++ method accepts a non-const vector reference, then the Python method can be passed a mutable sequence (e.g. list
):
C++: void GetPaths(std::vector<std::string>& paths) Python: GetPaths(paths: MutableSequence[str]) -> None
The value type of the std::vector<T>
must be std::string
or a fundamental numeric type such as double
or int
(including signed char
and unsigned char
but excluding char
).
Many VTK methods use pass-by-reference to return values back to the caller. Calling these methods from Python requires special consideration, since Python's str
, tuple
, int
, and float
types are immutable. The wrappers provide a 'reference
' type, which is a simple container that allows pass-by-reference.
For example, consider the following C++ method that uses pass-by-reference:
void GetCellAtId(vtkIdType cellId, vtkIdType& cellSize, vtkIdType const*& cellPoints)
It requires a reference to vtkIdType
(a Python int
), and to vtkIdType const*
(a tuple of int
s). So we can call this method as follows:
>>> from vtkmodules.vtkCommonCore import reference >>> from vtkmodules.vtkCommonDataModel import vtkCellArray >>> >>> # Build a cell array >>> a = vtkCellArray() >>> a.InsertNextCell(3, (1, 3, 0)) >>> >>> # Create the reference objects >>> n = reference(0) >>> t = reference((0,)) >>> >>> # Call the pass-by-reference method >>> a.GetCellAtId(0, n, t) >>> >>> n.get() 3 >>> t.get() (1, 3, 0)
Some important notes when using pass-by-reference:
get()
method of the reference is usually unnecessary, because the reference already supports the interface protocols of the object that it contains.One very real concern when using VTK from Python is that the parameters that you pass to a method might cause the program to crash. In particular, it is very easy to pass an index that causes an out-of-bounds memory access, since the C++ methods don't do bounds checking. As a safety precaution, the wrappers perform the bounds check before the C++ method is called:
>>> a = vtkFloatArray() >>> a.GetValue(10) Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: expects 0 <= id && id < GetNumberOfValues()
All precondition checks raise a ValueError
if they fail, since they are checks on the values of the parameters. The wrappers don't know if C++ is using the parameter as an index, so IndexError
is not used.
Currently the only way to find out if a method has preconditions is to look at the declaration of the method in the C++ header file to see if it has a VTK_EXPECTS
hint.
Similar to what can be done in C++, a Python function can be called each time a VTK event is invoked on a given object. In general, the callback function should have the signature func(obj:vtkObject, event:str)
, or func(self, obj:vtkObject, event:str)
if it is a method of a class.
>>> def onObjectModified(object, event): >>> print('object: %s - event: %s' % (object.GetClassName(), event)) >>> >>> o = vtkObject() >>> o.AddObserver(vtkCommand.ModifiedEvent, onObjectModified) 1 >>> o.Modified() object: vtkObject - event: ModifiedEvent
In case there is a 'CallData' value associated with an event, in C++, you have to cast it from void*
to the expected type using reinterpret_cast
. The equivalent in python is to add a CallDataType
attribute to the associated python callback method. The supported CallDataType
values are VTK_STRING
, VTK_OBJECT
, VTK_INT
, VTK_LONG
, VTK_DOUBLE
, and VTK_FLOAT
.
The following example uses a function as a callback, but a method or any callable object can be used:
>>> from vtkmodules.vtkCommonCore import vtkCommand, VTK_INT >>> >>> def onError(object, event, calldata): >>> print('object: %s - event: %s - msg: %s' % (object.GetClassName(), event, calldata)) >>> >>> onError.CallDataType = VTK_INT >>> >>> lt = vtkLookupTable() >>> lt.AddObserver(vtkCommand.ErrorEvent, onError) 1 >>> lt.SetTableRange(2,1) object: vtkLookupTable - event: ErrorEvent - msg: ERROR: In /home/user/VTK/Common/Core/vtkLookupTable.cxx, line 122 vtkLookupTable (0x6b40b30): Bad table range: [2, 1]
For convenience, the CallDataType
can also be specified where the function is first declared with the help of the @calldata_type
decorator:
>>> from vtkmodules.util.misc import calldata_type >>> >>> @calldata_type(VTK_INT) >>> def onError(object, event, calldata): >>> print('object: %s - event: %s - msg: %s' % (object.GetClassName(), event, calldata))
Most of the constants defined in the VTK header files are available in Python, and they can be accessed from the module in which they are defined. Many of these are found in the vtkCommonCore
module, where they were defined as preprocessor macros.
>>> from vtkmodules.vtkCommonCore import VTK_DOUBLE_MAX >>> VTK_DOUBLE_MAX 1.0000000000000001e+299
Others are defined as enums, often within a class namespace. If the enum is anonymous, then its values are int
.
>>> vtkCommand.ErrorEvent 39
Constants in the header files are wrapped if they are enums, or if they are const variables of a wrappable scalar type, or if they are preprocessor symbols that evaluate to integer, floating-point, or string literal types.
Each named enum type is wrapped as a new Python type, and members of the enum are instances of that type. This allows type checking for enum types:
>>> from vtkmodules.vtkCommonColor import vtkColorSeries >>> vtkColorSeries.COOL 2 >>> isinstance(vtkColorSeries.ColorSchemes, vtkColorSeries.COOL) >>> cs = vtkColorSeries() >>> cs.SetColorScheme(vtkColorSeries.COOL)
Enum classes are wrapped in a manner similar to named enums, except that the enum values are placed within the enum class namespace. For example, vtkEventDataAction
is an enum class, with 'Press
' as a member:
>>> from vtkmodules.vtkCommonCore import vtkEventDataAction >>> vtkEventDataAction.Press 1 >>> isinstance(vtkEventDataAction.Press, vtkEventDataAction) True
In the first example, the ColorSchemes
enum type and the COOL
enum value were both defined in the vtkColorSeries
namespace. In the second example, the vtkEventDataAction
enum class was defined in the module namespace, and the Press
value was defined in the enum class namespace.
Note that the VTK enum types behave like C++ enums, and not like the Python enums types provided by the Python 'enum
' module. In particular, all VTK enum values can be used anywhere that an int
can be used.
Namespaces are currently wrapped in a very limited manner. The only namespace members that are wrapped are enum constants and enum types. There is no wrapping of namespaced classes or functions, or of nested namespaces. Currently, the wrappers implement namespaces as Python module
objects.
The wrappers automatically generate docstrings from the doxygen comments in the header files. The Python help()
command can be used to print the documentation to the screen, or the __doc__
attributes of the classes and methods can be accessed directly.
The method docstrings are formatted with the method signatures first, followed by doxygen comments. The Python method signatures have type annotations, and are followed by the C++ method signatures for completeness.
Some Python IDEs will automatically show the docstring as soon as you type the name of the method.
The class docstrings include a brief description of the class, followed by the name of the superclass, and then the full doxygen documentation, including doxygen markup:
If the class is not derived from vtkObjectBase
, then it will have one or more public constructors, and these will be included before the comments:
Class templates are documented similar to classes, except that they include a 'Provided Types' section that lists the available template instantiations and the C++ template arguments that they correspond to.
Unlike classes, the template documentation is formatted similarly regardless of whether the the class template derives from vtkObjectBase
or not:
Classes and objects derived from vtkObjectBase
have special attributes, which are only used in very special circumstances.
The __vtkname__
attribute of the class provides the same string that the GetClassName() method returns. With the exception of classes that are template instantiations, it is identical to the __name__
attribute. For template instantiations, however, GetClassName()
and __vtkname__
return the result of calling typeid(cls).name()
from C++, which provides a platform specific result:
>>> vtkSOADataArrayTemplate['float32'].__vtkname__ '23vtkSOADataArrayTemplateIfE'
This can be used to get the VTK ClassName
when you don't have an instantiation to call GetClassName()
on. It is useful for checking the type of a C++ VTK object against a Python VTK class.
The __this__
attribute of the objects is a bit less esoteric, it provides a pointer to the C++ object as a mangled string:
>>> a = vtkFloatArray() >>> a.__this__ '_00005653a6a6f700_p_vtkFloatArray'
The string provides the hexadecimal address of 'this
', followed by 'p
' (shorthand for pointer), and the type of the pointer. You can also contruct a Python object directly from the C++ address, if the address is formatted as described above:
>>> a = vtkFloatArray('_00005653a6a6f700_p_vtkFloatArray') >>> a <vtkmodules.vtkCommonCore.vtkFloatArray(0x5653a6a6f700) at 0x7f0e7aecf5e0>
If you call the constructor on the string provided by __this__
, you will get exactly the same Python object back again, rather than a new object. But this constructor can be useful if you have some VTK code that has been wrapped with a different wrapper tool, for example with SWIG. If you can get the VTK pointer from SWIG, you can use it to construct Python object that can be used with the native VTK wrappers.
A wrapper hint is an attribute that can be added to a class, method, or parameter declaration in a C++ header file to give extra information to the wrappers. These hints are defined in the vtkWrappingHints.h
header file.
The following hints can appear before a method declaration:
VTK_WRAPEXCLUDE
excludes a method from the wrappersVTK_NEWINSTANCE
passes ownership of a method's return value to the callerFor convenience, VTK_WRAPEXCLUDE
can also be used to exclude a whole class. The VTK_NEWINSTANCE
hint is used when the return value is a vtkObjectBase*
and the caller must not increment the reference count upon acceptance of the object (but must still decrement the reference count when finished with the object).
The following hints can appear after a method declaration:
VTK_EXPECTS(cond)
provides preconditions for the method callVTK_SIZEHINT(expr)
marks the array size of a return valueVTK_SIZEHINT(name, expr)
marks the array size of a parameterFor VTK_EXPECTS(cond)
, the precondition must be valid C++ code, and can use any of the parameter names or this
. Even without this
, any public names in the class namespace (including method names) will be resolved. See the Preconditions section for additional information.
For sized array parameters, such as func(int x[10])
, the wrappers will automatically check the size of the Python sequence that is passed to the method. For bare T*
pointers (with T
as a basic integer or float type), The wrappers will only check the size if a VTK_SIZEHINT
is present. Also, return values of type T*
will not return a tuple unless there is a size hint for the return value. They will, instead, return a string that provides a mangled pointer of the form '_hhhhhhhhhhhh_p_void
' where 'hhhhhhhhhhhh
' is the hexadecimal address.
The following hints can appear before a parameter declaration:
VTK_FILEPATH
marks a parameter that accepts a pathlib.Path objectVTK_ZEROCOPY
marks a parameter that accepts a buffer objectMore specifically, VTK_FILEPATH
is used with char*
and std::string
parameters to indicate that the method also accepts any object with a __fspath__()
method that returns a path string. And VTK_ZEROCOPY
is used with T*
parameters, for basic integer or float type T
, to indicate that the Python buffer protocol will be used to access the values, rather than the Python sequence protocol that is used by default.
In addition to the wrapping hints, the Python wrappers are also aware of the deprecation attributes that have been applied to classes and methods. When a deprecated method is called, a DeprecationWarning
is generated and information about the deprecation is printed, including the VTK version for the deprecation.
To ignore these warnings, use the following code:
import warnings warnings.filterwarnings('ignore', category=DeprecationWarning)
To see each deprecation warning just once per session,
warnings.filterwarnings('once', category=DeprecationWarning)
The following is a table of common template key names, which are the same as the numpy dtype names. Note that you can actually use numpy dtypes as keys, as well as the native Python types bool
, int
, and float
. There is some danger in using int
, however, because it maps to C++ long
which has a platform-dependent size (either 32 bits or 64 bits). Finally, the char codes from the Python array
module can be used as keys, but they should be avoided since more programmers are familiar with numpy than with the much older array
module.
C++ Type | Template Key | Type Key | Char Key | IA64 ABI Code |
---|---|---|---|---|
bool | 'bool' | bool | '?' | IbE |
char | 'char' | 'c' | IcE | |
signed char | 'int8' | 'b' | IaE | |
unsigned char | 'uint8' | 'B' | IhE | |
short | 'int16' | 'h' | IsE | |
unsigned short | 'uint16' | 'H' | ItE | |
int | 'int32' | 'i' | IiE | |
unsigned int | 'uint32' | 'I' | IjE | |
long | 'int' | int | 'l' | IlE |
unsigned long | 'uint' | 'L' | ImE | |
long long | 'int64' | 'q' | IxE | |
unsigned long long | 'uint64' | 'Q' | IyE | |
float | 'float32' | 'f' | IfE | |
double | 'float64' | float | 'd' | IdE |
Since the size of 'long
' and 'unsigned long
' is platform-dependent, these types should generally be avoided.
There are times when an observer might generate a Python exception. Since the observers are called from C++, there is no good way to catch these exceptions from within Python. So, instead, the wrappers simply print a traceback to stderr and then clear the error indicator. The Python program will continue running unless the exception was a KeyboardInterrupt
(Ctrl-C), in which case the program will exit with an error code of 1.
There is no direct equivalent of VTK's Delete()
method, since Python does garbage collection automatically. The Python object will be deleted when there are no references to it within Python, and the C++ object will be deleted when there are no references to it from within either Python or C++. Note that references can hide in unexpected places, for example if a method of an object is used as an observer callback, the object will not be deleted until the observer is disconnected.
The DeleteEvent
can be used to detect object deletion, but note that the observer will receive a None for the object, since the observer is called after (not before) the deletion occurs:
>>> o = vtkObject() >>> o.AddObserver('DeleteEvent', lambda o,e: print(e, o)) 1 >>> del o DeleteEvent None
If you need to know what object is deleted, the identifying information must be extracted before the deletion occurs:
>>> o = vtkObject() >>> o.AddObserver('DeleteEvent',lambda x,e,r=repr(o): print(e, r)) 1 >>> del o DeleteEvent <vtkmodules.vtkCommonCore.vtkObject(0x55783870f970) at 0x7f1e61678be0>
In cases where you need to track down tricky memory issues, you might find it useful to call the SetReferenceCount()
and GetReferenceCount()
methods of the object directly. Of course, direct manipulation of the reference count should never be done in production code.
A wrapped VTK object (derived from vtkObjectBase
) is a Python object that holds a pointer to a C++ object (specifically, a vtkObjectBase*
). The Python object can have attributes that the C++ object knows nothing about. So, what happens to these attributes if the Python object is deleted, but the C++ object lives on? Consider this simple example of storing the C++ object in an array and then deleting the Python object:
obj = vtkObject() obj.tag = 'FirstObject' va = vtkVariantArray() va.InsertNextValue(obj) del obj
When we retrieve the object from the array, we want it to have the 'tag
' attributes that it had we stored it. But you might wonder, aren't all Python-specific attributes deleted along with the Python object? The answer is, no they aren't, they're saved until until the C++ object itself is deleted.
The wrappers have a special place, which we will call the graveyard, where 'ghosts' of objects are stored when the objects are deleted. The ghost is not an object, but rather a container for the Python attributes of a deceased object. If the object ever reappears within Python, usually as a return value from a C++ method call, then the ghost is resurrected as a new Python object that has all the attributes of the original Python object.
The graveyard is only used for objects that have unfinished business. If a Python object has an empty dict and no other special attributes, then it will not go to the graveyard. Also, if the C++ object is deleted at the same time as the Python object, then the graveyard will not be used. Each ghost in the graveyard holds a weak pointer to its C++ object and will vanish when the C++ object is deleted (not immediately, but the next time the graveyard garbage collector runs).
It is possible to subclass a VTK class from within Python, but this is of limited use because the C++ virtual methods are not hooked to the Python methods. In other words, if you make a subclass of vtkPolyDataAlgorithm
and override override the Execute()
method, it will not be automatically called by the VTK pipeline. Your Execute()
method will only be called if the call is made from Python.
The addition of virtual method hooks to the wrappers has been proposed, but currently the only way for Python methods to be called from C++ code is via callbacks. The vtkProgrammableSource
and vtkProgrammableFilter
are examples of VTK algorithm classes that use callbacks for execution, while vtkInteractionStyleUser
can use observer callbacks for event handling.
If you have your own C++ classes that are based on VTK, and if they are placed with a VTK module with a vtk.module file, then they can be wrapped as shown in the Module Wrapping Example. You will also find the cmake documentation on VTK modules to be useful.
VTK includes a script called vtk_generate_pyi.py
that will generate pyi stub files for each wrapped VTK module. The purpose of these files, as explained in PEP 484, is to provide type information for all constants, classes, and methods in the modules. Each of these files contain blocks like this:
VTK_DOUBLE:int VTK_DOUBLE_MAX:float VTK_DOUBLE_MIN:float ... class vtkObject(vtkObjectBase): def AddObserver(self, event:int, command:Callback, priority:float=0.0) -> int: ... def GetMTime(self) -> int: ... @staticmethod def GetNumberOfGenerationsFromBaseType(type:str) -> int: ... @overload def HasObserver(self, event:int, __b:'vtkCommand') -> int: ... @overload def HasObserver(self, event:str, __b:'vtkCommand') -> int: ... class vtkAbstractArray(vtkObject): class DeleteMethod(int): ... VTK_DATA_ARRAY_ALIGNED_FREE:'DeleteMethod' VTK_DATA_ARRAY_DELETE:'DeleteMethod' VTK_DATA_ARRAY_FREE:'DeleteMethod' VTK_DATA_ARRAY_USER_DEFINED:'DeleteMethod' def Allocate(self, numValues:int, ext:int=1000) -> int: ...
Python consoles like ipython and IDEs like PyCharm can use the information in these files to provide hints while you edit the code. In upcoming versions of VTK, these files will probably be included in the Python packages for VTK. But for now, they must be built by executing the vtk_generate_pyi.py
script. To do so, execute the script with the vtkpython
executable (or with the regular python executable, if its paths are set for VTK):
vtkpython vtk_generate_pyi.py
This will place build the pyi files and place them inside the vtkmodules
package, where ipython and PyCharm should automatically find them. The help for this script is as follows:
usage: python vtk_generate_pyi.py [-p package] [-o output_dir] [module ...] options: -p NAME Package name [vtkmodules by default]. -o OUTPUT Output directory [package directory by default]. -e EXT Output file suffix [.pyi by default]. module Module or modules to process [all by default].
The pyi files are syntactically correct python files, so it is possible to load them as such in order to test them and inspect them.