BlenDaViz

Set Up

BlenDaViz requires Blender version 4.4 or above (including Blender 5.0). It also requires the Python libraries numpy, scipy, matplotlib and scikit-image.

Blender can be installed via a package manager or downloaded as binary files from https://www.blender.org/download/.

Finding Blender’s Python Executable

You need to use Blender’s bundled Python interpreter to install dependencies and run BlenDaViz. To find the path to Blender’s Python executable, open Blender, switch to a Python console (Shift + F4) and type:

import sys
print(sys.executable)

This will print a path like /usr/share/blender/4.4/python/bin/python3.11 or similar.

Installing BlenDaViz

  1. Clone or download the BlenDaViz repository:

git clone https://github.com/SimonCan/BlenDaViz.git
cd BlenDaViz
  1. Install BlenDaViz and its dependencies using Blender’s Python:

/path/to/blenders/python -m pip install .

Replace /path/to/blenders/python with the actual path you found above.

Alternatively, you can install the dependencies separately:

/path/to/blenders/python -m pip install numpy scipy matplotlib scikit-image

And then add the BlenDaViz directory to your Python path within Blender.

Verifying the Installation

Once you start Blender, open a Python console and type:

import blendaviz as blt

If no errors appear, you are ready to go.

Usage and Examples

Open Blender and within Blender a Python console. Import BlendaViz and numpy:

import blendaviz as blt
import numpy as np

Marker Plots

In this simple line plot we will create some data and plot them. We will also see how we can manipulate existing plots.

# Create the data.
y = np.linspace(0, 6*np.pi, 20)
x = 2*np.cos(y/2)
z = 2*np.sin(y/2)

# Generate the scatter plot.
pl = blt.plot(x, y, z, marker='cube', radius=0.7)

# Change the color.
pl.color = np.ones([x.shape[0], 4])
pl.color[:, 0]  = np.linspace(0, 1, 20)
pl.color[:, 1] = 0
pl.plot()

Now you can render the scene by pressing F12.

_images/marker_plot.png

Line Plots

A line plot is very similar to a marker plot. It draws the data points as a line/tube.

import blendaviz as blt
import numpy as np

# Generate the data.
y = np.linspace(0, 6*np.pi, 400)
x = 2*np.cos(y)
z = 2*np.sin(y)

# Generate the line plot.
pl = blt.plot(x, y, z, radius=0.5)
_images/line_plot.png

Mesh Plots

We can plot 2d data arrays using mesh. We need two 2d arrays containing the x and y coordinates of the data points.

import numpy as np
import blendaviz as blt

# Generate the data.
x0 = np.linspace(-3, 3, 20)
y0 = np.linspace(-3, 3, 20)
x, y = np.meshgrid(x0, y0, indexing='ij')
z = (1 - x**2-y**2)*np.exp(-(x**2+y**2)/5)

# Genereate the mesh plot.
mesh = blt.mesh(x, y, z)
_images/mesh_plot.png

Quiver Plots

For three-dimensional vector arrays we can user quiver to plot the vector field as arrows. We need the x, y and z-coordinates of the data points as 3d arrays.

import numpy as np
import blendaviz as blt

# Generate the data.
x = np.linspace(-3, 3, 3)
y = np.linspace(-7, 7, 7)
z = np.linspace(-3, 3, 3)
xx, yy, zz = np.meshgrid(x, y, z, indexing='ij')
uu = 0.3*(xx + yy)
vv = 0.3*xx
ww = 0.3*zz + 0.8

# Genereate the quiver plot.
quiver = blt.quiver(xx, yy, zz, uu, vv, ww, length='magnitude', color='magnitude')
_images/quiver_plot.png

Contour Plots

Three-dimensional scalar fields can be plotted using contour. We need the x, z and z-coordinates of the data points as 3d arrays.

import blendaviz as blt

# Generate the data.
x = np.linspace(-2, 2, 21)
y = np.linspace(-2, 2, 21)
z = np.linspace(-2, 2, 21)
xx, yy, zz = np.meshgrid(x, y, z, indexing='ij')
phi = np.sin(3*xx) + np.cos(2*yy) + np.sin(zz)

# Genereate the contour plot.
contour = blt.contour(phi, xx, yy, zz)
_images/contour_plot.png

Streamline Plots

A three-dimensional vector field can be plotted as streamlines. For that we need specify the three components of the vector field as 3d arrays, the coordinates of the data points as 3d arrays and the position or number of seeds. If the number of seeds is passed, they will be randomly distributed within the domain.

import numpy as np
import blendaviz as blt

# Generate the data.
x = np.linspace(-4, 4, 100)
y = np.linspace(-4, 4, 100)
z = np.linspace(-4, 4, 100)
xx, yy, zz = np.meshgrid(x, y, z, indexing='ij')
u = -yy*np.exp(-np.sqrt(xx**2+yy**2) - zz**2)
v = np.ones_like(u)*0.1
w = xx*np.exp(-np.sqrt(xx**2+yy**2) - zz**2)

# Define the position of the seeds.
seeds = np.array([np.random.random(10)*2-1, np.zeros(10), np.random.random(10)*2-1]).T

# Generate the streamline plot.
streamlines = blt.streamlines(x, y, z, u, v, w, seeds=seeds, integration_time=100, integration_steps=80)
_images/streamlines_plot.png

Matplotlib Bridge

With BlenDaViz you can also generate plots in Matplotlib and embedthem into your Blender scene. The approach is very simple. You first generate the Matplotlib plot as you would normally do in Python, with no restrictions. Then you use BlenDaViz’ command mpl_figure_to_blender on the generated Matplotlib figure for the generation of the textured plane:

import numpy as np
import matplotlib.pyplot as plt
import blendaviz as blt

# Define the data.
x = np.linspace(0, 5, 1000)
y = np.sin(x)

# Plot the test data into a Matplotlib figure.
plt.rc('text', usetex=True)
plt.rc('font', family='arial')
fig = plt.figure()
plt.plot(x, y, color='g')
plt.title("sine function")

# Generate a texture Blender plane with this plot.
mpl = blt.mpl_figure_to_blender(fig)
_images/mpl_bridge.png

The generated mpl object has the Matplotlib figure object as member. It can be change to e.g. reflect different plotting style or data. Using mpl.plot() would the replot the data.

Plotting Without the Blender GUI

There are a few occasions that you do not want to start the Blender GUI, like you don’t have any compatible graphics driver installed on your system, you want to run the plot in the background, or you are on a cluster with SSH access. Computationally intensive rendering should be ideally done on a powerful computer and done over night or even several days. Here we outline how to make a plot without the GUI.

We present two methods for performing a background plot. The first method is the easier one, but it requires the usage of the GUI to set up the scene, i.e. light, camera and additional rendering options. The second methods requires more coding, but is runs purely in the background.

Using the GUI to set up the scene.

  1. Open Blender and remove the default cube.

  2. Adjust any other scene and rendering options.

  3. Save your scence to something like my_plot.blend.

  4. Prepare the plotting routine using BlenDaViz and save it to something like my_plot.py.

  5. Start Blender from the command line using the prepared scene and the plotting script.

blender --background my_plot.blend -P my_plot.py

This will use your blender scene and execute the plotting script.

Preparing the scene without the GUI, using the Blender Python commands.

This requires a few lines of coding, as we perform all of the steps done in the GUI using the Blender Python commands. Not all of the below steps are required, but highly recommended.

The steps in the script are basically: 1. Remove any existing objects from the default scene, like the default cube at the origin. 2. (Optionally, Recommended) Adjust the background and rendering options. 3. Perform the BlenDaViz plot. 4. Render the scene and save the image.

You then need to run the script using

blender -P my_script.py

It should be evident that using a loop you can generate animations through a sequence of images. You can use ffmpeg to put the images into a video file.

# line_plot_background.py
'''
Plotting example for a line plot in the background.
Usage:
blender -P line_plot_background.py
'''

import blendaviz as blt
import numpy as np
import bpy

# Delete all existing objects, like the default cube, light and camera.
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(use_global=False)

# Change the background color.
bpy.data.worlds["World"].node_tree.nodes["Background"].inputs[0].default_value = (1, 1, 1, 1)
bpy.data.worlds["World"].node_tree.nodes["Background"].inputs[1].default_value = 5
bpy.context.scene.world.cycles_visibility.scatter = False
bpy.context.scene.world.cycles_visibility.diffuse = False
bpy.context.scene.world.cycles_visibility.glossy = False
bpy.context.scene.world.cycles_visibility.transmission = False

# Change the rendering options.
bpy.context.scene.render.engine = 'CYCLES'
bpy.context.scene.render.threads_mode = 'FIXED'
bpy.context.scene.render.threads = 4
bpy.context.scene.cycles.samples = 256
bpy.context.scene.render.resolution_x = 1920
bpy.context.scene.render.resolution_y = 1080

# Generate the data.
y = np.linspace(0, 6*np.pi, 400)
x = 2*np.cos(y)
z = 2*np.sin(y)

# Generate the line plot.
pl = blt.plot(x, y, z, radius=0.5)

# Render the image.
bpy.data.scenes['Scene'].render.filepath = 'line_plot.png'
bpy.ops.render.render(write_still=True)