The Blender 2.80 API provides the 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.types.SpaceView3D.draw_handler_remove(draw_handler, 'WINDOW') |
If we draw something on a viewport, it will show in a render?
No.
how to erase the draw in blender view port?
remove draw handler
bpy.types.SpaceView3D.draw_handler_remove(draw_handler, ‘WINDOW’)
Hi, removing the draw_handle wont clear the viewport drawing, in order to do so I need to restart blender. How can it be done? I’m trying to draw some text in the viewport and remove it afterwards.
Do you call area.tag_redraw() after removing the draw_handle as you do when starting drawing?
Well, I tried, but clearly there is something wrong with how I’m doing it. First I run your script which creates the lines as expected, then I run the code below to clear the lines, but nothing happens, I don’t even get any message in console:
I keep trying but with no success, here I’m just using a boolean property to switch on and of the draw function… it just keeps drawing it.
In the first parameter of the draw_handler_remove calling, you need to specify the pointer to the draw_handler
I modified your example a bit – I added a timer which after 2 seconds call the draw_handler_remove
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’)
def remove():
# clear hanglder
bpy.types.SpaceView3D.draw_handler_remove(draw_handler, ‘WINDOW’)
# redraw screen
for area in bpy.context.window.screen.areas:
if area.type == ‘VIEW_3D’:
area.tag_redraw()
# add timver to remove drawinw
bpy.app.timers.register(
function=remove,
first_interval=2
)
for area in bpy.context.window.screen.areas:
if area.type == ‘VIEW_3D’:
area.tag_redraw()