3
$\begingroup$

how can i avoid losing the context with this modal operator?

import bpy 


def get_next_area(area, reverse):
    #context = bpy.context

    areas = [
        "VIEW_3D",
        "VIEW",
        "UV",
    ]

    if reverse is False:
        for index, c in enumerate(areas):
            if c == area.ui_type:
                if index == len(areas) - 1:
                    return areas[0]
                else:
                    return areas[index + 1]
    else:
        for index, c in enumerate(areas):
            if c == area.ui_type:
                if index == 0:
                    return areas[len(areas) - 1]
                else:
                    return areas[index - 1]

class SM_change_area_type_modal(bpy.types.Operator):

    bl_idname = 'sop.sm_change_area_type_modal'
    bl_label = "S.Menu Change Area Type Modal"
    bl_description = 'Calls Change Area Type Modal'
    bl_options = {'REGISTER', 'UNDO', 'GRAB_CURSOR', 'BLOCKING'}




    def modal(self, context, event):

        area = bpy.context.area
        print (area)

        if event.type == 'WHEELUPMOUSE':
            area.ui_type = get_next_area(area, False)

        if event.type == 'WHEELDOWNMOUSE':
            area.ui_type = get_next_area(area, True)

        # -------------------------------------------------------------#   
        #+ Finish/Cancel Modal
        elif event.type == 'LEFTMOUSE':
            return {'FINISHED'}

        elif event.type in {'RIGHTMOUSE', 'ESC'}:
            return {'CANCELLED'}

        return {'RUNNING_MODAL'}

    def invoke(self, context, event):
        #self.first_mouse_x = event.mouse_x
        #self.first_mouse_y = event.mouse_y

        # self.initial_backdrop_offset = bpy.context.space_data.backdrop_offset

        #self.mouse_path = []
        context.window_manager.modal_handler_add(self)
        return {'RUNNING_MODAL'}

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



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



if __name__ == "__main__":
    register()
$\endgroup$
4
$\begingroup$

Get the area from its index in the screen.

Without any "proper" explanation I had an inkling that if the area was indexed from the screen it will remove the context issue seen in code above.

>>> for i, a in enumerate(C.screen.areas):
...     i, a.ui_type
...     
(0, 'PROPERTIES')
(1, 'CONSOLE')
(2, 'OUTLINER')
(3, 'TEXT_EDITOR')

The properties area above will always be the zeroth item of the screen areas no matter what its type is changed to. (Unless areas are joined during operation)

When invoking get the index of the current area in the screen, use this to get the area.

self.screen_area_index = context.screen.areas[:].index(context.area)

Edited code:

Added class properties for current area index, the index of the area in the screen, and the ui type when invoked.

When operation is cancelled go back to original ui type.

Changed the behaviour to pass through when no event is captured. Not required, personal taste, found it a very handy little tool to flip between types, while still being able to use other tools. Would need to reconsider what finishes the operator in this case.

import bpy 


class SM_change_area_type_modal(bpy.types.Operator):
    ''' Tooltip '''
    bl_idname = 'sop.sm_change_area_type_modal'
    bl_label = "S.Menu Change Area Type Modal"
    bl_description = 'Calls Change Area Type Modal'
    #bl_options = {'GRAB_CURSOR', 'BLOCKING'}
    index = 0
    screen_area_index = 0
    save_area_ui_type = ''

    def get_next_area(self, area, delta):

        areas = [
            "VIEW_3D",
            "VIEW",
            "UV",
        ]

        self.index = (self.index + len(areas) + delta) % len(areas)
        area.ui_type = areas[self.index]

    def modal(self, context, event):
        area = context.screen.areas[self.screen_area_index]

        if event.type == 'WHEELUPMOUSE':
            self.get_next_area(area, 1)
            return {'RUNNING_MODAL'}
        elif event.type == 'WHEELDOWNMOUSE':
            self.get_next_area(area, -1)
            return {'RUNNING_MODAL'}
        elif event.type == 'LEFTMOUSE':
            return {'FINISHED'}
        elif event.type in {'RIGHTMOUSE', 'ESC'}:
            area.ui_type = self.save_area_ui_type
            return {'CANCELLED'}

        return {'PASS_THROUGH'}

    def invoke(self, context, event):
        self.screen_area_index = context.screen.areas[:].index(context.area)
        self.save_area_ui_type = context.area.ui_type
        context.window_manager.modal_handler_add(self)
        return {'RUNNING_MODAL'}

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


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


if __name__ == "__main__":
    register()

Please try and adhere to the operator class naming convention

class SM_OT_whatever_else(Operator):
    bl_idname = sm.whatever_else
$\endgroup$

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.