Interactive¶
The interactive
submodule provides methods to handle user-interactivity such
as mouse clicking, keyboard pressing, or timer based events. This submodule is
named vtkplotlib.interactive
but also goes by the alias vtkplotlib.i
.
This mini tutorial works from the bottom up. Starting with how raw, low-level VTK interaction works, then progressively moving towards quicker, more integrated vtkplotlib methods. If you’re feeling lazy, you may want to read this backwards.
New in version 1.4.0.
Reference¶
Some of the classes and methods in this submodule have their own reference pages which can be accessed below. You are recommended, however, to read through this page before consulting them.
Events in VTK¶
Interaction in VTK follows a similar model to those of GUI frameworks such as
PyQt6 or wx
, or web frameworks. For the benefit of those who aren’t
familiar with either, you define a callback function which may take some kind
of event information object as an argument:
def run_me_when_x_happens(event_info):
[respond to event]
Then register the callback function along with exactly what kind of user action you want to capture.
In VTK, callback functions are referred to as observers and always take two arguments:
The invoker, which is whichever vtkObject triggered the event.
And the event_name - the type of event, such as “KeyPressEvent” as a string.
vtkplotlib
at vtkplotlib.figure.style
.vtkplotlib.figure.iren
.
(The arguments to the callback function aren’t particularly helpful.)invoker.AddObserver(event_name, callback)
.Minimal Example¶
The following is an entry-level example which captures the user’s left mouse clicks and prints them:
import vtkplotlib as vpl
# Set up a figure.
fig = vpl.figure()
# With some stuff in it.
vpl.quick_test_plot()
# Define a function to be run on left mouse click.
def callback(invoker, event_name):
# These will always be true. Just for demonstration purposes.
assert invoker is fig.style
assert event_name == "LeftButtonPressEvent"
# Respond to the click. fig.iren.GetEventPosition() tells us where (in
# 2D) the click happened. Converting to 3D is explained later...
print("You clicked at", fig.iren.GetEventPosition())
# Call the original behaviour. Otherwise left clicking will cease to do
# what it used to do. i.e. rotate the camera. Again, explained later...
vpl.i.call_super_callback()
# Register the (event-type, callback) pair with `fig.style`.
fig.style.AddObserver("LeftButtonPressEvent", callback)
# Then show. `vpl.show()` would also work.
fig.show()
This can be modified to print right mouse clicks, mouse releases, keyboard
presses, etc, by changing the event_name 'LeftButtonPressEvent'
to
a different event name. See Event Types for all events available.
Note
IPython buffers stdout in a way that doesn’t play well with VTK’s event
loop. This means that there may be a substantial delay between print
being called and the output appearing on the screen. To get around this,
instead run interactive VTK examples from shell.
You can do this very lazily using pip install pyperclip
, then copy the
code-block to clipboard then, run python -m pyperclip --paste | python
.
Event Types¶
Valid event_names are listed in vtkplotlib.interactive.vtkCommands
.
There are quite a lot of them. Rather than try to explain what each and every
one does, let me show you.
# Same setup as before...
import vtkplotlib as vpl
fig = vpl.figure()
vpl.quick_test_plot()
# Define a callback which prints the event_name.
def print_event_callback(invoker, event_name):
print(event_name, "happened at", fig.iren.GetEventPosition())
vpl.i.call_super_callback()
# Attach it to every available event name.
for event_name in vpl.i.vtkCommands:
fig.style.AddObserver(event_name, print_event_callback)
fig.show()
If you’re unsure what the event you want to capture is called, simply run this example. You may notice that you can’t get all the event names under vtkCommands. This can be due to any of:
The event is not applicable to the vtkInteractorStyle. These events are not covered here.
The event requires special hardware you don’t have, such as FifthButtonPressEvent which requires a mouse with five buttons, or PinchEvent which requires a touch screen.
The event is bizarrely specific e.g. WindowSupportsOpenGLEvent.
Getting Information About an Event¶
We know if a user clicked on something. Now you want to know where they clicked.
And what they clicked on. This is all done using pick()
(see
Pick).
Super Callbacks?¶
What is this call_super_callback()
function that’s in all the examples?
The answer is best explained by omitting it. Try the Minimal Example again
but remove the vpl.i.call_super_callback()
, then left-click-and-drag rotate
the screen.
You should see that our custom callback function still works but the original
behaviour of rotating the camera doesn’t. By adding a
callback to LeftButtonPressEvent we have inadvertently knocked out the
pre-existing behaviour, which was fig.style.OnLeftButtonDown()
.
The typical way around this in other event driven frameworks like PyQt6 is overloading methods and inheriting the previous behaviour. e.g.
class CustomInteractionStyle(vtk.vtkInteractorStyle):
def OnLeftClickDown(self):
[custom behaviour here]
# Call original behaviour using `super()`.
super().OnLeftClickDown()
In Python you can almost always do this. But VTK is written in C++ and in C++
you must explicitly declare a method as virtual to allow this (which VTK
didn’t). Instead we have to do it by hand. i.e. Add
fig.style.OnLeftButtonDown()
to our callbacks for LeftButtonPressEvent.
To help map event names to default callbacks, vtkplotlib provides the methods
get_super_callback()
and call_super_callback()
which respectively
find and call the original callbacks. Like Python’s builtin
super
function, these methods can’t be called anywhere. They must be
called inside of a function taking a vtkObject and a string event name as its
arguments.