4
$\begingroup$

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?

$\endgroup$
2
  • 1
    $\begingroup$ Try to play with the template scripts in blender's text editor. There is one or two for drawing in the viewport. $\endgroup$
    – JakeD
    Aug 26, 2016 at 16:13
  • 2
    $\begingroup$ The simplest way to draw to the 3dView with bgl is with a modal Operator. $\endgroup$
    – zeffii
    Sep 27, 2016 at 7:01

2 Answers 2

13
$\begingroup$

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()
$\endgroup$
6
  • $\begingroup$ Thank you, @poor. But I'm still confused. I've edited my question again.... $\endgroup$
    – NGE
    Aug 28, 2016 at 2:49
  • $\begingroup$ @NGE So why is this import bgl ??? $\endgroup$
    – user58715
    Jul 17, 2019 at 14:00
  • $\begingroup$ Note that the API for making the operator searchable in the Spacebar/F3 menu has changed in Blender 2.9: stackoverflow.com/questions/63863764/… $\endgroup$
    – Will Chen
    Mar 19, 2021 at 20:14
  • 3
    $\begingroup$ 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. $\endgroup$
    – p2or
    Mar 21, 2021 at 1:24
  • 1
    $\begingroup$ was thinking "wha???" but then again like I don't have developer extras always enabled. Then again noticed the menu_func names re blender.stackexchange.com/a/211980/15543 $\endgroup$
    – batFINGER
    Mar 22, 2021 at 21:48
4
$\begingroup$

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.

$\endgroup$
1
  • 1
    $\begingroup$ 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. $\endgroup$
    – R. Navega
    Jun 30, 2018 at 8:27

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .