Blf.draw -- how to automatically update 3D View and how to erase the text?

I would like to create a text overlay in all of the 3D View windows. The code listed here

# import stand alone modules
import blf
import bpy

font_info = {
    "font_id": 0,
    "handler": None,
}


def init():
    """init function - runs once"""
    import os
    # Create a new font object, use external ttf file.
    font_path = bpy.path.abspath('//Zeyada.ttf')
    # Store the font indice - to use later.
    if os.path.exists(font_path):
        font_info["font_id"] = blf.load(font_path)
    else:
        # Default font.
        font_info["font_id"] = 0

    # set the font drawing routine to run every frame
    font_info["handler"] = bpy.types.SpaceView3D.draw_handler_add(
        draw_callback_px, (None, None), 'WINDOW', 'POST_PIXEL')


def draw_callback_px(self, context):
    """Draw on the viewports"""
    # BLF drawing routine
    font_id = font_info["font_id"]
    blf.position(font_id, 2, 80, 0)
    blf.size(font_id, 50, 72)
    blf.draw(font_id, "Hello World")


if __name__ == '__main__':
    init()

…is almost exactly what I need. However, I can’t figure out two things:

  1. How do I get the 3D View windows to automatically update? After I run the script, the 3D View does not update with the text until I hover over the rotation gizmo.

  2. How do I erase the text that’s already been written to the window? Subsequent runs of this code with different text just overlay new text over the existing text, creating a confusing jumble of characters.

context.region.tag_redraw()

bpy.types.SpaceView3D.draw_handler_remove(font_info[“handler”], ‘WINDOW’)

Thanks for the response @Cirno, but I’m having trouble getting those lines of code to work for some reason.

I tried putting bpy.context.region.tag_redraw() after the call to init() (i.e. the very last line of code in the original example) but the 3D View did not update itself until I hovered over top of the rotation gizmo (just like it did without having added that line).

As far as bpy.types.SpaceView3D.draw_handler_remove(font_info["handler"], 'WINDOW'), I’m not entirely sure where to put this. When I run it by itself, I get this error message: ValueError: callback_remove(handler): NULL handler given, invalid or already removed

If you want to run it from the text editor:

import blf
import bpy

font_info = {
    "font_id": 0,
    "handler": None,
}

def draw_callback_px(self, context):
    """Draw on the viewports"""
    # BLF drawing routine
    font_id = font_info["font_id"]
    blf.position(font_id, 2, 80, 0)
    blf.size(font_id, 50, 72)
    blf.draw(font_id, "Hello World")

def redraw_regions():
    for area in bpy.context.window.screen.areas:
        if area.type == 'VIEW_3D':
            for region in area.regions:
                if region.type == 'WINDOW':
                    region.tag_redraw()

def init():
    """init function - runs once"""
    import os
    # Create a new font object, use external ttf file.
    font_path = bpy.path.abspath('//Zeyada.ttf')
    # Store the font indice - to use later.
    if os.path.exists(font_path):
        font_info["font_id"] = blf.load(font_path)
    else:
        # Default font.
        font_info["font_id"] = 0

    # set the font drawing routine to run every frame
    handler = bpy.app.driver_namespace.get('draw_hello_world')
    if not handler:
        handler = bpy.types.SpaceView3D.draw_handler_add(
            draw_callback_px, (None, None), 'WINDOW', 'POST_PIXEL')
        dns = bpy.app.driver_namespace
        dns['draw_hello_world'] = handler
        redraw_regions()

def remove_hello_world():
    handler = bpy.app.driver_namespace.get('draw_hello_world')
    if handler:
        bpy.types.SpaceView3D.draw_handler_remove(handler, 'WINDOW')
        del bpy.app.driver_namespace['draw_hello_world']
        redraw_regions()

if __name__ == '__main__':
    init()
    
#remove_hello_world()

To erase the text use remove_hello_world()

Perfect! That’s exactly what I was looking for. Thanks, @Cirno!