1
$\begingroup$

I have a question about the opengl modal draw :

How to align text from a modal draw to the middle of the viewport and and keep it automatic between Tool Shelf and Property Shelf, when i changing them?

I want to avoid that the property shelf overlapp the modal draw on the right viewport side, when i change the scale of left toolshelf.

Maybe there is a similar question always done?

$\endgroup$
2
  • 1
    $\begingroup$ You draw in the window type region of the space you are drawing text on (eg the 3d view). The x, y, width and height attributes of the region take into account the tool / property bars. $\endgroup$
    – batFINGER
    Commented May 2, 2018 at 12:21
  • 1
    $\begingroup$ If the text is generated dynamically, major issue is calculating the position based on the width to get it centered (IMHO). Probably related: blender.stackexchange.com/q/57709/31447 $\endgroup$
    – brockmann
    Commented May 2, 2018 at 12:26

2 Answers 2

6
$\begingroup$

According to the API, Area.region collection holds all "regions of the screen area" as well as their dimensions. In case of the 3d Viewport: Header, Transform Panel and N-Panel.

The following code is based on this answer, extended with a simple calculation to get the correct position of the text in the 3d Viewport. Notice that you can also iterate through the collection and sum up the dimensions instead of doing that stepwise.

enter image description here

import bpy
import bgl
import blf


def draw_callback_px(self, context):

    bgl.glEnable(bgl.GL_BLEND)

    # ...api_current/bpy.types.Area.html?highlight=bpy.types.area
    header_height = context.area.regions[0].height # 26px
    npanel_width = context.area.regions[1].width
    transorm_panel_width = context.area.regions[3].width

    width = context.area.width - npanel_width - transorm_panel_width
    height = context.area.height + header_height

    # draw text
    text = "Hello World"
    font_id = 0  # XXX, need to find out how best to get this.
    bgl.glColor4f(*(1.0, 1.0, 1.0, 1))
    blf.size(font_id, 70, 72)
    dim_x = blf.dimensions(font_id, text)[0]/2
    dim_y = blf.dimensions(font_id, text)[1]
    blf.position(font_id, width/2-dim_x, height/2-dim_y, 0)
    blf.draw(font_id, text)

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


class ModalDrawOperator(bpy.types.Operator):
    """Draw a line with the mouse"""
    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, 'WINDOW')
            return {'CANCELLED'}

        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 = bpy.types.SpaceView3D.draw_handler_add(draw_callback_px, 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()

To display the text as well as allow the user to interact with the UI, you can change the return statement of the modal function from {'RUNNING_MODAL'} to {'PASS_THROUGH'}:

...

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

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

    return {'PASS_THROUGH'}

...

enter image description here

Also see the API on Modal Execution: https://docs.blender.org/api/blender_python_api_current/bpy.types.Operator.html#modal-execution

$\endgroup$
1
  • $\begingroup$ Thank you! That's really detailed. Again briefly repeated: >> total window minus tpanel minus npanel results is real area. >> divided by 2 for the center point. >> minus lenght of the text divided also by 2 $\endgroup$
    – MKB
    Commented May 3, 2018 at 11:19
0
$\begingroup$

Great practice script.

Rewritten for 2.8.

Added descriptive functions in class for extensibility.

When switching to another tab, the descriptive text is removed.

# for blender 2.8

import bpy
import blf
  

class ModalDrawOperator(bpy.types.Operator):
    """Draw a line with the mouse"""
    bl_idname = "view3d.modal_operator"
    bl_label = "Simple Modal View3D Operator"

    font_info = {
        "font_id": 0,
        "handler": None,
                }
        
    def draw_callback_px(self, context, event):
        
        header_height = bpy.context.area.regions[0].height # 26px
        npanel_width = bpy.context.area.regions[3].width

        width = bpy.context.area.width - npanel_width 
        height = bpy.context.area.height + header_height

        """Draw on the viewports"""
        # BLF drawing routine
        text = "Hello World"

        font_id = self.font_info["font_id"]
        blf.color(font_id, 0, 0.292, 1, 0.7)
        blf.size(font_id, 50, 72)
        dim_x = blf.dimensions(font_id, text)[0]/2
        dim_y = blf.dimensions(font_id, text)[1]
        blf.position(font_id, width/2-dim_x, height/2-dim_y, 0)
        blf.draw(font_id, text)

  
    def modal(self, context, event):
        # If overridden outside of the 3D view
        try:
            context.area.tag_redraw()
        except AttributeError:
            bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
            return {'CANCELLED'}
        # If there is no active 3D view
        if bpy.context.area.type !="VIEW_3D":
            bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
            return {'CANCELLED'}

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

        # All other operations are permitted.
        return {'PASS_THROUGH'}


    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 = bpy.types.SpaceView3D.draw_handler_add(self.draw_callback_px, 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()

enter image description here

$\endgroup$

You must log in to answer this question.

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