10
$\begingroup$

How can I get the active region (i. e. the region the mouse pointer is over and keystrokes are sent to) with the Python API?

Background: I want to manipulate the view_matrix of the region the mouse pointer is over. In the "Scripting" screen I can get the matrix with

bpy.data.window_managers[0].windows[0].screen.areas[2].spaces[0].region_3d.view_matrix

or with

bpy.data.window_managers[0].windows[0].screen.areas[2].spaces[0].region_quadviews[x].view_matrix

if the SpaceView3D is in quad view mode. bpy.context does not help: My script runs in a timer started from the text editor, and its context is always "Scripting" therefore.

Getting the active region seemed to be a simple, common task to me, but I am stuck here. May be I am looking in the documentation's wrong area ...

$\endgroup$
  • 2
    $\begingroup$ Usually you call an operator and the context passed to the operator's execute or invoke method provides information about the current area, space, region etc... ->context $\endgroup$ – pink vertex Dec 9 '14 at 16:08
11
$\begingroup$

bpy.context.space_data is the current space, if you need a certain one, iterate over all areas in the current screen and test for the .type:

for area in bpy.context.screen.areas:
    if area.type == 'VIEW_3D':
        area.spaces.active.region_3d.view_matrix
        # you may want to break here

To figure out which region / quadview the mouse is over in a modal operator, compare the mouse coordinates with the region rectangle:

import bpy
import math

def get_view_orientation(space, view):

    if view.view_perspective == 'CAMERA':
        if space.camera.type == 'CAMERA':
            view_orientation = "Camera " + space.camera.data.type.capitalize()
        else:
            view_orientation = "Object as Camera"

    else:
        r = lambda x: round(x, 2)
        view_rot = view.view_matrix.to_euler()

        orientation_dict = {(0.0, 0.0, 0.0) : 'TOP',
                            (r(math.pi), 0.0, 0.0) : 'BOTTOM',
                            (r(-math.pi/2), 0.0, 0.0) : 'FRONT',
                            (r(math.pi/2), 0.0, r(-math.pi)) : 'BACK',
                            (r(-math.pi/2), r(math.pi/2), 0.0) : 'LEFT',
                            (r(-math.pi/2), r(-math.pi/2), 0.0) : 'RIGHT'}

        view_orientation =  orientation_dict.get(tuple(map(r, view_rot)), 'USER').capitalize()
        view_orientation += " " + view.view_perspective.capitalize()

    if space.local_view is not None:
        view_orientation += " (Local)"

    return view_orientation


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 (area.spaces.active, None if is_quadview else i)
    return (None, None)

class ModalTimerOperator(bpy.types.Operator):
    """Operator which runs its self from a timer"""
    bl_idname = "wm.modal_timer_operator"
    bl_label = "Modal Timer Operator"

    _timer = None

    def modal(self, context, event):
        if event.type in {'RIGHTMOUSE', 'ESC'}:
            return self.cancel(context)

        if event.type == 'TIMER':
            space, i = get_quadview_index(context, event.mouse_x, event.mouse_y)
            if space is not None:
                if i is None:
                    view = space.region_3d
                else:
                    view = space.region_quadviews[i]
                print(get_view_orientation(space, view))

        return {'PASS_THROUGH'}

    def execute(self, context):
        wm = context.window_manager
        self._timer = wm.event_timer_add(0.5, context.window)
        wm.modal_handler_add(self)
        return {'RUNNING_MODAL'}

    def cancel(self, context):
        wm = context.window_manager
        wm.event_timer_remove(self._timer)
        return {'FINISHED'}


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


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


if __name__ == "__main__":
    register()

    # test call
    bpy.ops.wm.modal_timer_operator()
$\endgroup$
  • $\begingroup$ Thank you for the quick response, bpy.context gives more data access than I thought. But your code does not tell me what I need to know because I get a view_matrix even if the mouse pointer is not over the region.Example: $\endgroup$ – frisee Dec 10 '14 at 10:29
  • $\begingroup$ (Sorry, the system kicked me out here is the example): I am in "Scripting" layout and have the 3D view split into two. In this situation, as you said, I can access the two view_matrices with bpy.context.screen.areas[x].spaces.active.region_3d.view_matrix, setting x = 2 for the first and x = 6 for the second view. Both seem to be "active", but I need the one (and only the one) the mouse pointer is in. $\endgroup$ – frisee Dec 10 '14 at 10:53
  • $\begingroup$ You are right, the original context is captured in a modal operator and you can't immediately determine which quadview the mouse is over. It's possible if you utilize the mouse coordinates from the event object and the region properties x, y, width and height. See my updated answer for a nice example. $\endgroup$ – CoDEmanX Dec 10 '14 at 21:44
2
$\begingroup$

Thank you very much, CoDEmanX! I hoped there is a more efficient method to get the active region. Let me post my own code here I am using now. It delivers the active region of a 3D view even if it is split or in quad view mode, or None if the mouse pointer is not in such a region:

def getActiveViewportRegion(self):
    for area in bpy.context.screen.areas:
        if area.type == 'VIEW_3D':
            if (area.x <= self._mouseX < (area.x+area.width) and
                area.y <= self._mouseY < (area.y+area.height)):
                # Mouse is in area
                if len(area.spaces.active.region_quadviews) > 0:
                    # Area is quadview
                    header = next(r for r in area.regions if r.type == 'HEADER')

                    quad_w = area.width/2                       
                    quad_h = (area.height-header.height)/2        #  +-----------------+
                    quad_0_x = area.x                             #  | quad 1 | quad 3 |
                    quad_0_y = area.y + header.height             #  |--------+--------|
                    quad_1_x = area.x                             #  | quad 0 | quad 2 |
                    quad_1_y = area.y + quad_h + header.height    #  |-----------------| 
                    quad_2_x = area.x + quad_w                    #  |     Header      |
                    quad_2_y = area.y + header.height             #  +-----------------+

                    if (quad_0_x <= self._mouseX < (quad_0_x+quad_w ) and
                        quad_0_y <= self._mouseY < (quad_0_y+quad_h)):
                        return = area.spaces.active.region_quadviews[0]

                    if (quad_1_x <= self._mouseX < (quad_1_x+quad_w ) and
                        quad_1_y <= self._mouseY < (quad_1_y+quad_h)):
                        return = area.spaces.active.region_quadviews[1]

                    if (quad_2_x <= self._mouseX < (quad_2_x+quad_w ) and
                        quad_2_y <= self._mouseY < (quad_2_y+quad_h)):
                        return = area.spaces.active.region_quadviews[2]

                    return = area.spaces.active.region_quadviews[3]
                else:
                    # Area is not quadview
                    return area.spaces.active.region_3d
    return None

self._mouseX and self._mouseY come from a ModalTimerOperator as in CoDEmanX's code.

$\endgroup$
  • $\begingroup$ It's kind of a design limitation, modal operators are supposed to run inside the invocation context and thus bpy.context.region does not refer to the active region. Not sure why you do all the math there, my code determines the quadview region the mouse is over with way less code and calculations?! (the nth region of type WINDOW in 3D View is the nth region_quadviews space) $\endgroup$ – CoDEmanX Dec 11 '14 at 17:06
  • $\begingroup$ Updated my code to use either region_3d or region_quadviews and honor active camera perspective type. $\endgroup$ – CoDEmanX Dec 11 '14 at 17:32
  • $\begingroup$ Thank you for the enhancements. Interesting your method for getting the header. Being new not only to Blender but Python as well I will check how these iterators are working ... $\endgroup$ – frisee Dec 12 '14 at 10:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.