How get drawing area in callback for quadview?

I need help with Quadview drawing. I know how get the quad using the mouse position, but the problem is get the drawing area in the callback.

I have some code like this:

bpy.types.SpaceView3D.draw_handler_add(draw_callback_px, (self, context), 'WINDOW', 'POST_PIXEL')


..


def draw_callback_px(self, context):
    # mi draw code here
      quad = get_quad(x, y)  # x/y is inside quad area
    # need information of area in pixels or location to select Quad

But, I need the area that is drawing or any information about location to detect the Quad.

how can get it?

I posted some code a while ago, I believe that should solve your problem:

I quickly adapted it to draw the region id and dimensions to the active quad view:

import bpy
import bgl
import blf

def get_quadview_index(context, x, y):
    for area in context.screen.areas:
        if area.type != 'VIEW_3D':
            continue
        is_quadview = len(area.spaces.active.region_quadviews) == 0
        i = -1
        for region in area.regions:
            if region.type == 'WINDOW':
                i += 1
                if (x >= region.x and
                    y >= region.y and
                    x < region.width + region.x and
                    y < region.height + region.y):

                    return (region, area.spaces.active, None if is_quadview else i)
    return (None, None, None)

def draw_callback_px(self, context):
    #print("mouse points", len(self.mouse_path))
    region, space, qi = get_quadview_index(context, self.mouse_x, self.mouse_y) 
    if region.id != context.region.id:
        return

    font_id = 0  # XXX, need to find out how best to get this.

    # draw some text
    blf.position(font_id, 15, 30, 0)
    blf.size(font_id, 20, 72)
    blf.draw(font_id, "Region {} ({},{}; {}x{})".format(region.id, region.x, region.y, region.width, region.height))

    # 50% alpha, 2 pixel width line
    bgl.glEnable(bgl.GL_BLEND)
    bgl.glColor4f(0.0, 0.0, 0.0, 0.5)
    bgl.glLineWidth(2)

    bgl.glBegin(bgl.GL_LINE_STRIP)
    for x, y in self.mouse_path:
        bgl.glVertex2i(x, y)

    bgl.glEnd()

    # 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 == 'MOUSEMOVE':
            self.mouse_path.append((event.mouse_region_x, event.mouse_region_y))
            self.mouse_x = event.mouse_x
            self.mouse_y = event.mouse_y

        elif event.type == 'LEFTMOUSE':
            bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
            return {'FINISHED'}

        elif 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')

            self.mouse_path = []
            self.mouse_x = 0
            self.mouse_y = 0

            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()


Note that the line drawing is done in the perspective of the original quadview the operator was started in (maybe some matrix pushing and popping could solve this…)

A comparison of regions is inevitable, because all are of type WINDOW, and draw handlers are bound via name (‘WINDOW’), not ID or some other unique property.

CoDEmanX, I have seen your code in other web before, maybe stackexchange , but there is a problem, you need mouse input.

Really, what I need is get this:

rv3d = bpy.context.space_data.region_3d

to later use it to convert 3D in 2D location with:

point2d = view3d_utils.location_3d_to_region_2d(region, rv3d, point3d)

I have seen that you can get the region with this code:

if quad:
   rv3d = area.spaces.active.region_quadviews[x]
else:
   rv3d = area.spaces.active.region_3d

In other languages, the draw event send what is the area to draw. How can I do that?

I still don’t understand what you are after. Do you want to draw over 3d objects in viewport? Or just know where on screen the viewport is?

I want to draw in viewport. Maybe you have seen my add-on MeasureIt. The problem is when you select QuadView, the measures are not correct. I’m trying to fix this error.

Are your measures correct for one of the quadviews? (the one in which your addon operator was started in?)

This might some kind of bug, or a limitation of the quadview implementation. I suspect the matrix stack to be wrong for 3/4 of the quadviews… You should ask the devs if draw handler + multiple regions of same type (WINDOW) might cause this.

Yes, one of the quads is correct, but as I have no way to detect the other quads when drawing, I cannot change the region to calculate the 2d point, so the lines are wrong in other quads.

I need a way to detect what area is drawing and then set the right value for rv3d before calling

point2d = view3d_utils.location_3d_to_region_2d(region, rv3d, point3d)

The code I posted determines the region, space and quad index in get_quadview_index(). This is basically what you need, but it is based on mouse location - but you want to determine it from inside your draw function as I understood.

You don’t need to check mouse location, just find the right quadview from the context region. This can be achieved using region.id to determine the position of the region in Area.regions - the nth WINDOW-type region equals the nth quadview in SpaceView3D.region_quadviews.

import bpy
import bgl
import blf
from bpy_extras.view3d_utils import location_3d_to_region_2d

def draw_callback_px(self, context):
    print("mouse points", len(self.mouse_path))
    
    # only quadview mode supported
    if not context.space_data.region_quadviews:
        return 
    
    # should be true as long as operator was started from 3D View
    assert context.area.type == 'VIEW_3D' and context.space_data.type == 'VIEW_3D'
    i = -1
    for region in context.area.regions:
        if region.type == 'WINDOW':
            i += 1
            if context.region.id == region.id:
                break
    else:
        return
    
    rv3d = context.space_data.region_quadviews[i]
    
    point3d = context.scene.cursor_location
    loc = location_3d_to_region_2d(region, rv3d, point3d)

    font_id = 0  # XXX, need to find out how best to get this.

    # draw some text
    blf.position(font_id, 15, 30, 0)
    blf.size(font_id, 20, 72)
    blf.draw(font_id, "Region {}, Quad {}".format(context.region.id, i))

    blf.position(font_id, 15, 60, 0)
    blf.size(font_id, 14, 72)
    blf.draw(font_id, "Cursor 2D {}".format(tuple(map(lambda x: round(x, 2), loc))))

    blf.position(font_id, 15, 80, 0)
    blf.size(font_id, 14, 72)
    x, y = self.mouse_path[-1]
    blf.draw(font_id, "Mouse: {} {}".format(x - context.region.x, y - context.region.y))

    # 50% alpha, 2 pixel width line
    bgl.glEnable(bgl.GL_BLEND)
    bgl.glColor4f(0.0, 0.0, 0.0, 0.3)
    bgl.glLineWidth(2)

    bgl.glBegin(bgl.GL_LINE_STRIP)
    for x, y in self.mouse_path:
        bgl.glVertex2i(x - context.region.x, y - context.region.y)

    bgl.glEnd()
    
    bgl.glColor4f(1.0, 0.6, 0.0, 1.0)
    bgl.glPointSize(7)
    bgl.glEnable(bgl.GL_POINT_SMOOTH)
    bgl.glBegin(bgl.GL_POINTS)
    bgl.glVertex2f(*loc)
    bgl.glEnd()

    # 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 == 'MOUSEMOVE':
            #self.mouse_path.append((event.mouse_region_x, event.mouse_region_y))
            self.mouse_path.append((event.mouse_x, event.mouse_y))

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

        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(draw_callback_px, 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 register():
    bpy.utils.register_class(ModalDrawOperator)


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

if __name__ == "__main__":
    register()


The function “location_3d_to_region_2d” is located in the file “… \2.75\scripts\modules\bpy_extras\view3d_utils.py”, simply copy it and modify it your way to get the correct location of the coordinate 2d.

The function is this:

def location_3d_to_region_2d(region, rv3d, coord, default=None):    """
    Return the *region* relative 2d location of a 3d position.


    :arg region: region of the 3D viewport, typically bpy.context.region.
    :type region: :class:`bpy.types.Region`
    :arg rv3d: 3D region data, typically bpy.context.space_data.region_3d.
    :type rv3d: :class:`bpy.types.RegionView3D`
    :arg coord: 3d worldspace location.
    :type coord: 3d vector
    :arg default: Return this value if ``coord``
       is behind the origin of a perspective view.
    :return: 2d location
    :rtype: :class:`mathutils.Vector` or ``default`` argument.
    """
    from mathutils import Vector


    prj = rv3d.perspective_matrix * Vector((coord[0], coord[1], coord[2], 1.0))
    if prj.w > 0.0:
        width_half = region.width / 2.0
        height_half = region.height / 2.0


        return Vector((width_half + width_half * (prj.x / prj.w),
                       height_half + height_half * (prj.y / prj.w),
                       ))
    else:
        return default

I think if you divide the “region.width” and “region.height” by 4 instead of 2, will solve the problem for the regions in quad_view

@CoDEmanX your solution works perfect. The final code I need is this:

if not context.space_data.region_quadviews:
        rv3d = bpy.context.space_data.region_3d
    else:
        # verify area 
        if context.area.type != 'VIEW_3D' or context.space_data.type != 'VIEW_3D':
            return
        i = -1
        for region in context.area.regions:
            if region.type == 'WINDOW':
                i += 1
                if context.region.id == region.id:
                    break
        else:
            return


        rv3d = context.space_data.region_quadviews[i]

Thanks for your Help!

1 Like