4

Since there isn't demo in API docs, so I tried simple script below:

import bpy
from bgl import *
from bpy import context


def draw_line():

    glColor3f(1.0, 0.0, 0.0)
    glLineWidth(10)

    glBegin(GL_LINES)
    glVertex3f(0, 0, 0)
    glVertex3f(0.5 ,0.5, 0.5)
    glEnd()

draw_line()

But it doesn't work, I can't find a line in the viewport. I want to know the simplest way to draw a line with bgl, not with modal operator. So I tried script like below:

from bgl import *
import bpy


def draw_line():

    glColor3f(1.0, 0.0, 0.0)
    glLineWidth(2.0)
    glBegin(GL_LINES)
    glVertex3f(0.0, 0.0, 0.0)
    glVertex3f(1.0, 1.0, 1.0)
    glEnd()

def draw():
    if bpy.context.area.type == 'VIEW_3D':
        handle = bpy.types.SpaceView3D.draw_handler_add(draw_line, 'WINDOW', 'POST_VIEW')

    bpy.context.area.tag_redraw()

draw()

But it doesn't work, too. What's wrong with it?

p2or
  • 15,860
  • 10
  • 83
  • 143
NGE
  • 347
  • 2
  • 10
  • 1
    Try to play with the template scripts in blender's text editor. There is one or two for drawing in the viewport. – JakeD Aug 26 '16 at 16:13
  • 2
    The simplest way to draw to the 3dView with bgl is with a modal Operator. – zeffii Sep 27 '16 at 07:01

2 Answers2

13

Using a modal operator is the simplest way to handle drawing custom geometry.

enter image description here

Updated example using only gpu and blf modules for Blender 3.5+ since bgl module is considered deprecated as OpenGL is just being replaced by Metal and Vulkan:

import bpy
import blf
import gpu
from gpu_extras.batch import batch_for_shader

def draw_line_3d(color, start, end, width=1.0): shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR') gpu.state.line_width_set(width) batch = batch_for_shader(shader, 'LINES', {"pos": [start,end]}) shader.bind() shader.uniform_float("color", color) batch.draw(shader)

def draw_type_2d(color, text, size=17): font_id = 0 blf.position(font_id, 20, 70, 0) blf.color(font_id, color) blf.size(font_id, size(bpy.context.preferences.system.dpi/72)) blf.draw(font_id, text)

def draw_callback_3d(self, context): cube_loc = context.scene.objects['Cube'].location lamp_loc = context.scene.objects['Light'].location camera_loc = context.scene.objects['Camera'].location

# set the gpu state
gpu.state.blend_set('ALPHA')

# green, red and blue line   
draw_line_3d((0.0, 1.0, 0.0, 0.7), camera_loc, cube_loc)
draw_line_3d((1.0, 0.0, 0.0, 0.7), cube_loc, lamp_loc)
draw_line_3d((0.0, 0.0, 1.0, 0.7), lamp_loc, camera_loc)

# restore gpu defaults
gpu.state.line_width_set(1.0)
gpu.state.blend_set('NONE')

def draw_callback_2d(self, context): gpu.state.blend_set('ALPHA')

# dynamic text
hud = "Hello Word {} {}".format(len(self.mouse_path), self.mouse_path[-1])
draw_type_2d((1.0, 0.5, 0.0, 0.8), hud)

# restore gpu defaults
gpu.state.line_width_set(1.0)
gpu.state.blend_set('NONE')


class ModalDrawOperator(bpy.types.Operator): """Draw 3d Operator""" bl_idname = "view3d.modal_draw_operator" bl_label = "Simple Modal View3D Operator"

def modal(self, context, event):
    context.area.tag_redraw()

    if event.type == 'MOUSEMOVE':
        self.mouse_path.append((event.mouse_region_x, event.mouse_region_y))

    if event.type in {'RIGHTMOUSE', 'ESC'}:
        bpy.types.SpaceView3D.draw_handler_remove(self._handle_3d, 'WINDOW')
        bpy.types.SpaceView3D.draw_handler_remove(self._handle_2d, 'WINDOW')
        return {'CANCELLED'}

    return {'PASS_THROUGH'}
    #return {'RUNNING_MODAL'}

def invoke(self, context, event):
    if context.area.type == 'VIEW_3D':
        # the arguments we pass the the callback
        args = (self, context)
        # draw in view space with 'POST_VIEW' and 'PRE_VIEW'
        self._handle_3d = bpy.types.SpaceView3D.draw_handler_add(draw_callback_3d, args, 'WINDOW', 'POST_VIEW')
        self._handle_2d = bpy.types.SpaceView3D.draw_handler_add(draw_callback_2d, args, 'WINDOW', 'POST_PIXEL')

        self.mouse_path = []
        context.window_manager.modal_handler_add(self)
        return {'RUNNING_MODAL'}
    else:
        self.report({'WARNING'}, "View3D not found, cannot run operator")
        return {'CANCELLED'}


def menu_func(self, context): self.layout.operator(ModalDrawOperator.bl_idname, text="Modal Draw Operator")

Register and add to the "view" menu (required to also use F3 search "Modal Draw Operator" for quick access).

def register(): bpy.utils.register_class(ModalDrawOperator) bpy.types.VIEW3D_MT_view.append(menu_func)

def unregister(): bpy.utils.unregister_class(ModalDrawOperator) bpy.types.VIEW3D_MT_view.remove(menu_func)

if name == "main": register()


Basic example using bgl, gpu and blf modules for Blender 2.8+:

import bpy
import bgl
import blf
import gpu
from gpu_extras.batch import batch_for_shader

def draw_line_3d(color, start, end): shader = gpu.shader.from_builtin('3D_UNIFORM_COLOR') batch = batch_for_shader(shader, 'LINES', {"pos": [start,end]}) shader.bind() shader.uniform_float("color", color) batch.draw(shader)

def draw_typo_2d(color, text): font_id = 0 blf.position(font_id, 20, 70, 0) blf.color(font_id, color[0], color[1], color[2], color[3]) blf.size(font_id, 20, 72) blf.draw(font_id, text)

def draw_callback_3d(operator, context):

# object locations
cube_loc = context.scene.objects['Cube'].location
lamp_loc = context.scene.objects['Light'].location
camera_loc = context.scene.objects['Camera'].location

# 80% alpha, 2 pixel width line
bgl.glEnable(bgl.GL_BLEND)
bgl.glEnable(bgl.GL_LINE_SMOOTH)
bgl.glEnable(bgl.GL_DEPTH_TEST)

# green line   
draw_line_3d((0.0, 1.0, 0.0, 0.7), camera_loc, cube_loc)

# red line
draw_line_3d((1.0, 0.0, 0.0, 0.7), cube_loc, lamp_loc)

# blue line
draw_line_3d((0.0, 0.0, 1.0, 0.7), lamp_loc, camera_loc)

bgl.glEnd()
# restore opengl defaults
bgl.glLineWidth(1)
bgl.glDisable(bgl.GL_BLEND)
bgl.glDisable(bgl.GL_LINE_SMOOTH)
bgl.glEnable(bgl.GL_DEPTH_TEST)


def draw_callback_2d(self, context):

bgl.glEnable(bgl.GL_BLEND)
draw_typo_2d((1.0, 0.0, 0.0, 1.0), "Hello Word")
bgl.glEnd()

# restore opengl defaults
bgl.glLineWidth(1)
bgl.glDisable(bgl.GL_BLEND)
bgl.glEnable(bgl.GL_DEPTH_TEST)


class ModalDrawOperator(bpy.types.Operator): bl_idname = "view3d.modal_operator" bl_label = "Simple Modal View3D Operator"

def modal(self, context, event):
    context.area.tag_redraw()

    if event.type in {'RIGHTMOUSE', 'ESC'}:
        bpy.types.SpaceView3D.draw_handler_remove(self._handle_3d, 'WINDOW')
        bpy.types.SpaceView3D.draw_handler_remove(self._handle_2d, 'WINDOW')
        return {'CANCELLED'}

    return {'PASS_THROUGH'}
    #return {'RUNNING_MODAL'}

def invoke(self, context, event):
    if context.area.type == 'VIEW_3D':
        # the arguments we pass the the callback
        args = (self, context)
        # Add the region OpenGL drawing callback
        # draw in view space with 'POST_VIEW' and 'PRE_VIEW'
        self._handle_3d = bpy.types.SpaceView3D.draw_handler_add(draw_callback_3d, args, 'WINDOW', 'POST_VIEW')
        self._handle_2d = bpy.types.SpaceView3D.draw_handler_add(draw_callback_2d, args, 'WINDOW', 'POST_PIXEL')

        context.window_manager.modal_handler_add(self)
        return {'RUNNING_MODAL'}
    else:
        self.report({'WARNING'}, "View3D not found, cannot run operator")
        return {'CANCELLED'}


def register(): bpy.utils.register_class(ModalDrawOperator)

def unregister(): bpy.utils.unregister_class(ModalDrawOperator)

if name == "main": register()

Documentation: https://www.blender.org/api/current/bgl.html#module-bgl


Blender 2.7x

enter image description here Click to enlarge

import bpy
import bgl
import blf

def draw_line_3d(color, start, end, width=1): bgl.glLineWidth(width) bgl.glColor4f(color) bgl.glBegin(bgl.GL_LINES) bgl.glVertex3f(start) bgl.glVertex3f(*end)

def draw_typo_2d(color, text): font_id = 0 # XXX, need to find out how best to get this. # draw some text bgl.glColor4f(*color) blf.position(font_id, 20, 70, 0) blf.size(font_id, 20, 72) blf.draw(font_id, text)

def draw_callback_3d(self, context): bgl.glEnable(bgl.GL_BLEND)

# object locations
cube_loc = context.scene.objects['Cube'].location
lamp_loc = context.scene.objects['Light'].location
camera_loc = context.scene.objects['Camera'].location

# green line   
draw_line_3d((0.0, 1.0, 0.0, 0.7), camera_loc, cube_loc)

# red line
draw_line_3d((1.0, 0.0, 0.0, 0.7), cube_loc, lamp_loc)

# blue line
draw_line_3d((0.0, 0.0, 1.0, 0.7), lamp_loc, camera_loc, 2)

bgl.glEnd()
# restore opengl defaults
bgl.glLineWidth(1)
bgl.glDisable(bgl.GL_BLEND)
bgl.glColor4f(0.0, 0.0, 0.0, 1.0)

def draw_callback_2d(self, context):

bgl.glEnable(bgl.GL_BLEND)

# draw text
draw_typo_2d((1.0, 1.0, 1.0, 1), "Hello Word ")

bgl.glEnd()
# restore opengl defaults
bgl.glLineWidth(1)
bgl.glDisable(bgl.GL_BLEND)
bgl.glEnable(bgl.GL_DEPTH_TEST)
bgl.glColor4f(0.0, 0.0, 0.0, 1.0)


class ModalDrawOperator(bpy.types.Operator): bl_idname = "view3d.modal_operator" bl_label = "Simple Modal View3D Operator"

def modal(self, context, event):
    context.area.tag_redraw()

    if event.type in {'RIGHTMOUSE', 'ESC'}:
        bpy.types.SpaceView3D.draw_handler_remove(self._handle_3d, 'WINDOW')
        bpy.types.SpaceView3D.draw_handler_remove(self._handle_2d, 'WINDOW')
        return {'CANCELLED'}

    return {'PASS_THROUGH'}
    #return {'RUNNING_MODAL'}

def invoke(self, context, event):
    if context.area.type == 'VIEW_3D':
        # the arguments we pass the the callback
        args = (self, context)
        # Add the region OpenGL drawing callback
        # draw in view space with 'POST_VIEW' and 'PRE_VIEW'
        self._handle_3d = bpy.types.SpaceView3D.draw_handler_add(draw_callback_3d, args, 'WINDOW', 'POST_VIEW')
        self._handle_2d = bpy.types.SpaceView3D.draw_handler_add(draw_callback_2d, args, 'WINDOW', 'POST_PIXEL')

        context.window_manager.modal_handler_add(self)
        return {'RUNNING_MODAL'}
    else:
        self.report({'WARNING'}, "View3D not found, cannot run operator")
        return {'CANCELLED'}


def register(): bpy.utils.register_class(ModalDrawOperator)

def unregister(): bpy.utils.unregister_class(ModalDrawOperator)

if name == "main": register()

p2or
  • 15,860
  • 10
  • 83
  • 143
  • Thank you, @poor. But I'm still confused. I've edited my question again.... – NGE Aug 28 '16 at 02:49
  • @NGE So why is this import bgl ??? –  Jul 17 '19 at 14:00
  • Note that the API for making the operator searchable in the Spacebar/F3 menu has changed in Blender 2.9: https://stackoverflow.com/questions/63863764/unable-to-find-custom-blender-operator-in-f3-operator-search-blender-2-9 – Will Chen Mar 19 '21 at 20:14
  • 3
    Your note and the SO link is misleading IMHO @WillChen, I think this one is much better. What do you think? Anyway, thanks for trying to help though. – p2or Mar 21 '21 at 01:24
  • 1
    was thinking "wha???" but then again like I don't have developer extras always enabled. Then again noticed the menu_func names re https://blender.stackexchange.com/a/211980/15543 – batFINGER Mar 22 '21 at 21:48
  • Hehe! Nice find, thanks @batFINGER BTW: I tried to update your code for https://blender.stackexchange.com/a/57786/3710 as well, but hadn't the time to finish it yet: https://gist.github.com/p2or/ba75af5a70c8086929c92f9f013988e5 – p2or Mar 31 '21 at 06:05
4

I think I've found the answer I want:

from bgl import *
import bpy


def draw_line():

    glColor3f(1.0, 0.0, 0.0)
    glLineWidth(2.0)
    glBegin(GL_LINES)
    glVertex3f(0.0, 0.0, 0.0)
    glVertex3f(1.0, 1.0, 1.0)
    glEnd()


handle = bpy.types.SpaceView3D.draw_handler_add(draw_line, (), 'WINDOW', 'POST_VIEW')

draw function needs no argument, so an empty tuple(()) should be added to draw_handler_add as an argument.

NGE
  • 347
  • 2
  • 10
  • 1
    It doesn't need to have parameters, but if at some point you need to pass some data from your operator instance to this drawing callback then you absolutely must use parameters, it's the only way to do that (unless you can access bpy.context, then you can also store data in some custom property). The template "modal draw" example shows how to use parameters anyway. – R. Navega Jun 30 '18 at 08:27