The Blender 2.80 API provides an ability to draw in a viewport (3D Viewport) using the “gpu” module.
As an example let’s draw a simple coordinate system widget in the center of the scene, consisting of three lines with different colors.
Custom viewport drawing is performed with the same principles as common meshes are displayed in it. To draw some object in the viewport we need to specify the location of its vertices and assign them a shader.
Let’s set the coordinates for six vertices, each pair of which forms an edge from the center of coordinates – the origin (0.0, 0.0, 0.0) to a point spaced 1 by the desired axis (X, Y, and Z):
1 2 3 |
vertices = [(0.0, 0.0, 0.0), (1.0, 0.0, 0.0), (0.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 0.0), (0.0, 0.0, 1.0)] |
For each vertex, let’s set the color in RGBA format. For the first pair of vertices – red (1.0, 0.0, 0.0), for the second – blue (0.0, 1.0, 0.0) and for the third pair – green (0.0, 0.0, 1.0).
1 2 3 |
col = [(1.0, 0.0, 0.0, 1.0), (1.0, 0.0, 0.0, 1.0), (0.0, 1.0, 0.0, 1.0), (0.0, 1.0, 0.0, 1.0), (0.0, 0.0, 1.0, 1.0), (0.0, 0.0, 1.0, 1.0)] |
and make a shader:
1 2 3 |
import gpu shader = gpu.shader.from_builtin('3D_SMOOTH_COLOR') |
Compile all to the batch for drawing it in the viewport:
1 2 3 |
from gpu_extras.batch import batch_for_shader batch = batch_for_shader(shader, 'LINES', {"pos": vertices, "color": col}) |
The second parameter value “LINES” sets the display mode, in our case – drawing lines by the specified points.
Define a function that will render the given batch in the viewport:
1 2 3 |
def draw(): shader.bind() batch.draw(shader) |
and add it to the viewport drawing handler.
1 2 3 |
import bpy draw_handler = bpy.types.SpaceView3D.draw_handler_add(draw, (), 'WINDOW', 'POST_VIEW') |
Now the “draw” function will be called every time the viewport is updated.
For initial drawing we can force the viewport update:
1 2 3 |
for area in bpy.context.window.screen.areas: if area.type == 'VIEW_3D': area.tag_redraw() |
Full code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import bpy import gpu from gpu_extras.batch import batch_for_shader vertices = [(0.0, 0.0, 0.0), (1.0, 0.0, 0.0), (0.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 0.0), (0.0, 0.0, 1.0)] shader = gpu.shader.from_builtin('3D_SMOOTH_COLOR') col = [(1.0, 0.0, 0.0, 1.0), (1.0, 0.0, 0.0, 1.0), (0.0, 1.0, 0.0, 1.0), (0.0, 1.0, 0.0, 1.0), (0.0, 0.0, 1.0, 1.0), (0.0, 0.0, 1.0, 1.0)] batch = batch_for_shader(shader, 'LINES', {"pos": vertices, "color": col}) def draw(): shader.bind() batch.draw(shader) draw_handler = bpy.types.SpaceView3D.draw_handler_add(draw, (), 'WINDOW', 'POST_VIEW') for area in bpy.context.window.screen.areas: if area.type == 'VIEW_3D': area.tag_redraw() |
After executing this code we can see our widget in the center of the scene:
Not all features have been transferred to the “gpu” AIP module yet. For example, to set the line thickness, you need to use the “bgl” module:
1 2 3 4 5 |
import bgl bgl.glLineWidth(5) ... bgl.glLineWidth(1) |
The final code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
import bpy import gpu from gpu_extras.batch import batch_for_shader import bgl vertices = [(0.0, 0.0, 0.0), (1.0, 0.0, 0.0), (0.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 0.0), (0.0, 0.0, 1.0)] shader = gpu.shader.from_builtin('3D_SMOOTH_COLOR') col = [(1.0, 0.0, 0.0, 1.0), (1.0, 0.0, 0.0, 1.0), (0.0, 1.0, 0.0, 1.0), (0.0, 1.0, 0.0, 1.0), (0.0, 0.0, 1.0, 1.0), (0.0, 0.0, 1.0, 1.0)] batch = batch_for_shader(shader, 'LINES', {"pos": vertices, "color": col}) def draw(): bgl.glLineWidth(5) shader.bind() batch.draw(shader) bgl.glLineWidth(1) draw_handler = bpy.types.SpaceView3D.draw_handler_add(draw, (), 'WINDOW', 'POST_VIEW') for area in bpy.context.window.screen.areas: if area.type == 'VIEW_3D': area.tag_redraw() |
To remove the widget rendering, we need to remove our “draw” function from the window update handler.
1 |
bpy.type.SpaceView3D.draw_handler_remove(draw_handler, 'WINDOW') |