Welcome to vtkplotlib’s documentation!¶
Introduction¶
A simple library to make 3D graphics using easy. Built on top of VTK. Whilst VTK is a very versatile library, the learning curve is steep and writing in it is slow and painful. This library seeks to overcome that by wrapping the all ugliness into numpy friendly functions to create a 3D equivalent of matplotlib. All the VTK components/functionality are still accessible but by default are already setup for you.
Key features¶
Clean and easy to install.
Takes advantage of VTK’s lesser known numpy support so that data can be efficiently copied by reference between numpy and VTK making it much faster than most of the VTK examples you’ll find online.
Has direct support for STL plotting.
Can be embedded seamlessly into PyQt6 (or PyQt5, PySide2 or PySide6) applications.
Is freezable with PyInstaller.
Warning
Sort-of abondoned project!
I hate maintaining this project. This is the most popular project I’ve ever conceived yet it is also the one I wish never to have to work on again. VTK with it’s infuriating API, memory leaks and tendency to segfault with no discernible patterns (I have tests which if ran in isolation, work fine, but segfault if ran one after another but don’t segfault if ran in a different order) make even trivial updates an exercise in grinding teeth and resisting the urge to throw something.
If anyone discovers a non-VTK derived 3D rendering library which sets up the lighting for you and doesn’t require you to write your own trackball then please let me know. Until then, I will reluctantly continue to address issues with lower cost to benefit ratios since I do still need this library for some of my other projects.
There are other VTK derivative libraries (mayavi
, pyvista
,
vedo
). They are all useless to me since the first thing they all do is
load the entirety of the vtk
package (vtkplotlib
only loads the bits
it uses and only when it needs them) ballooning the end user application
size and startup time spectacularly. If your code is never going to leave
your own computer and you therefore don’t care about either of those factors
then you’re probably better off using one of those libraries instead.
Requirements for installing:¶
Please note that new minor versions of Python can’t be supported until VTK supports them.
Installation:¶
To install run the following into shell/bash/terminal. The mandatory dependencies will installed automatically.
pip install vtkplotlib
Optional requirements:¶
Some features require you to install the following:
numpy-stl or any other STL library if you want to plot STL files. numpy-stl is my STL library of choice.
PyQt6 if you want to include your plots in a larger Qt GUI.
namegenerator for fun.
Quickstart:¶
Scatter plots:¶
import vtkplotlib as vpl
import numpy as np
# In vtkplotlib coordinates are always expressed as numpy arrays with shape
# (3,) or (n, 3) or (..., 3).
# Create 30 random points.
points = np.random.uniform(-10, 10, (30, 3))
# Plot the points as spheres.
vpl.scatter(points)
# Show the plot.
vpl.show()
A window should open with lots of white sphere’s in it.
You can add some color using the color argument.
Colors can be assigned to all the balls using rgb
vpl.scatter(points, color=(.3, .8, .8))
vpl.show()
or rgba
vpl.scatter(points, color=(.3, .8, .8, .2))
vpl.show()
or using any of matplotlib’s named colors using strings.
vpl.scatter(points, color="r")
vpl.show()
See matplotlib or vpl.colors.mpl_colors
for a full list of available colors.
Or colors can be given per point
colors = np.random.random(points.shape)
vpl.scatter(points, color=colors)
vpl.show()
Line plots:¶
import vtkplotlib as vpl
import numpy as np
# Create some kind of wiggly shape
# use ``vpl.zip_axes()`` to combine (x, y, z) axes
t = np.linspace(0, 2 * np.pi, 300)
points = vpl.zip_axes(np.cos(2 * t),
np.sin(3 * t),
np.cos(5 * t) * np.sin(7 *t))
# Plot a line
vpl.plot(points,
color="green",
line_width=3)
vpl.show()
For plotting a polygon you can use join_ends=True to join the last point with the first.
# Create the corners of an octagon
t = np.arange(0, 1, 1 / 8) * 2 * np.pi
points = vpl.zip_axes(np.cos(t), np.sin(t), 0)
# Plot them
vpl.plot(points,
join_ends=True)
vpl.show()
Mesh plots:¶
- vtkplotlib.mesh_plot(mesh_data, tri_scalars=None, scalars=None, color=None, opacity=None, cmap=None, fig='gcf', label=None)
To plot STL files you will need some kind of STL reader library. If you don’t have one then get numpy-stl. Their Mesh class can be passed directly to
mesh_plot()
.- Parameters
mesh_data – A mesh object to plot.
tri_scalars (numpy.ndarray) – Per-triangle scalar, texture-coordinates or RGB values, defaults to None.
scalars (numpy.ndarray) – Per-vertex scalar, texture-coordinates or RGB values, defaults to None.
color (str or tuple or numpy.ndarray) – The color (see
colors.as_rgb_a()
) of the whole plot, ignored if scalars are used, defaults to white.opacity (float) – The translucency of the plot. Ranges from
0.0
(invisible) to1.0
(solid).cmap – A colormap (see
vtkplotlib.colors.as_vtk_cmap()
) to convert scalars to colors, defaults to'rainbow'
.fig (
figure
orQtFigure
) – The figure to plot into, useNone
for no figure, defaults to the output ofvtkplotlib.gcf()
.
- Returns
A mesh object.
- Return type
The following example assumes you have installed numpy-stl.
import vtkplotlib as vpl from stl.mesh import Mesh # path = "if you have an STL file then put it's path here." # Otherwise vtkplotlib comes with a small STL file for demos/testing. path = vpl.data.get_rabbit_stl() # Read the STL using numpy-stl mesh = Mesh.from_file(path) # Plot the mesh vpl.mesh_plot(mesh) # Show the figure vpl.show()
Unfortunately there are far too many mesh/STL libraries/classes out there to support them all. To overcome this as best we can, mesh_plot has a flexible constructor which accepts any of the following.
A filename.
Some kind of mesh class that has form 3 stored in
mesh.vectors
. For example numpy-stl’sstl.mesh.Mesh
or pymesh’spymesh.stl.Stl
.An
numpy.array
with shape(n, 3, 3)
of the form:
np.array([[[x, y, z], # corner 0 \ [x, y, z], # corner 1 | triangle 0 [x, y, z]], # corner 2 / ... [[x, y, z], # corner 0 \ [x, y, z], # corner 1 | triangle n-1 [x, y, z]], # corner 2 / ])
Note it’s not uncommon to have arrays of shape (n, 3, 4) or (n, 4, 3) where the additional entries’ meanings are usually irrelevant (often to represent scalars but as STL has no color this is always uniform). Hence to support mesh classes that have these, these arrays are allowed and the extra entries are ignored.
An
numpy.array
with shape (k, 3) of (usually unique) vertices of the form:np.array([[x, y, z], [x, y, z], ... [x, y, z], [x, y, z]])
And a second argument of an
numpy.array
of integers with shape(n, 3)
of point args in the form:np.array([[i, j, k], # triangle 0 ... [i, j, k], # triangle n-1 ])
where i, j, k are the indices of the points (in the vertices array) representing each corner of a triangle.
Note that this form can be easily converted to form 2) using
vertices = unique_vertices[point_args]
Hopefully this will cover most of the cases. If you are using or have written an STL library (or any other format) that you want supported then let me know. If it’s numpy based then it’s probably only a few extra lines to support. Or you can have a go at writing it yourself, either with
mesh_plot()
or with thevtkplotlib.PolyData
class.Mesh plotting with scalars:
To create a heat map like image use the scalars or tri_scalars options.
Use the scalars option to assign a scalar value to each point/corner:
import vtkplotlib as vpl from stl.mesh import Mesh # Open an STL as before path = vpl.data.get_rabbit_stl() mesh = Mesh.from_file(path) # Plot it with the z values as the scalars. scalars is 'per vertex' or 1 # value for each corner of each triangle and should have shape (n, 3). plot = vpl.mesh_plot(mesh, scalars=mesh.z) # Optionally the plot created by mesh_plot can be passed to color_bar vpl.color_bar(plot, "Heights") vpl.show()
Use the tri_scalars option to assign a scalar value to each triangle:
import vtkplotlib as vpl from stl.mesh import Mesh import numpy as np # Open an STL as before path = vpl.data.get_rabbit_stl() mesh = Mesh.from_file(path) # `tri_scalars` must have one value per triangle and have shape (n,) or (n, 1). # Create some scalars showing "how upwards facing" each triangle is. tri_scalars = np.inner(mesh.units, np.array([0, 0, 1])) vpl.mesh_plot(mesh, tri_scalars=tri_scalars) vpl.show()
Note
scalars and tri_scalars overwrite each other and can’t be used simultaneously.
See also
Having per-triangle-edge scalars doesn’t fit well with VTK. So it got its own separate function
mesh_plot_with_edge_scalars()
.
Figure managing:¶
There are two main basic types in vtkplotlib.
Figures are the window you plot into.
Plots are the physical objects that go in the figures.
In all the previous examples the figure has been handled automatically. For more complex scenarios you may need to handle the figures yourself. The following demonstrates the figure handling functions.
import vtkplotlib as vpl
import numpy as np
# You can create a figure explicitly using figure()
fig = vpl.figure("Your Figure Title Here")
# Creating a figure automatically sets it as the current working figure
# You can get the current figure using gcf()
vpl.gcf() is fig # Should be True
# If a figure hadn't been explicitly created using figure() then gcf()
# would have created one. If gcf() had also not been called here then
# the plotting further down will have internally called gcf().
# A figure's properties can be edited directly
fig.background_color = "dark green"
fig.window_name = "A New Window Title"
points = np.random.uniform(-10, 10, (2, 3))
# To add to a figure you can either:
# 1) Let it automatically add to the whichever figure gcf() returns
vpl.scatter(points[0], color="r")
# 2) Explicitly give it a figure to add to
vpl.scatter(points[1], radius=2, fig=fig)
# 3) Or pass fig=None to prevent it being added then add it later
arrow = vpl.arrow(points[0], points[1], color="g", fig=None)
fig += arrow
# fig.add_plot(arrow) also does the same thing
# Finally when your ready to view the plot call show. Like before you can
# do this one of several ways
# 1) fig.show()
# 2) vpl.show() # equivalent to gcf().show()
# 3) vpl.show(fig=fig)
fig.show() # The program will wait here until the user closes the window.
# Once a figure is shown it is gets placed in `vpl.figure_history` which
# stores recent figures. The default maximum number of figures is two. For
# convenience whilst console bashing, you can retrieve the last figure.
# But it will no longer be the current working figure.
vpl.figure_history[-1] is fig # Should be True
fig is vpl.gcf() # Should be False
# A figure can be reshown indefinitely and should be exactly as you left it
# when it was closed.
fig.show()