1
$\begingroup$

I'm trying to register a msgbus handler for a callback that is called when the View3D area's shading mode changes. I know from testing with this class that context.space_data.shading.typecontains the relevant state.

class TLA_PT_sidebar(Panel):
    """Display test button"""
    bl_label = "Closet"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = "TLA"

    def draw(self, context):
        col = self.layout.column(align=True)
        col.label(text = f"{context.space_data.shading.type}")

so I tried this code fragment in the Console

owner = object()
subscribe_to = (bpy.types.View3DShading, "type")
def notify_test(context):
    print(context.space_data.shading.type)
    
bpy.msgbus.subscribe_rna(key=subscribe_to, owner=owner, args= (bpy.context,), notify=notify_test,)

It executes with no errors, and my notify handler is being called when I switch Shading Modes, but args must be receiving the wrong value, since I get

AttributeError: 'NoneType' object has no attribute 'shading'
File "<blender_console>", line 1, in notify_test

when I change the shading type. Since I want to execute code based on the new Shading type, I need the value that I get from accessing context.space_data.shading.type in the Panel.

So my question is, what argument(s) should I be passing to the handler to make this access possible and what should the syntax of the access in the handler be?

$\endgroup$
0

1 Answer 1

1
$\begingroup$

TL;DNR: Can't be done.

I took the question to Blender Developer Talk where I received several replies including this workaround

import bpy


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 point_inside_rect(self, pos, rect):
        return pos[0] > rect[0] and pos[1] > rect[1] and pos[0] < rect[2] and pos[1] < rect[3]

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

        if event.type == 'TIMER':
            print('modal update')

            mousepos = [event.mouse_x, event.mouse_y]
            print(mousepos)

            for area in [a for a in bpy.context.screen.areas if a.type == 'VIEW_3D']:
                arearect = [area.x, area.y, area.width, area.height]
                print(area, arearect)

                if self.point_inside_rect(mousepos, arearect):
                    print('inside')

            
        return {'PASS_THROUGH'}

    def execute(self, context):
        wm = context.window_manager
        self._timer = wm.event_timer_add(2, window=context.window)
        wm.modal_handler_add(self)

        print('test point in rect', self.point_inside_rect([5, 5], [0, 0, 10, 10]))
        print('running')

        return {'RUNNING_MODAL'}

    def cancel(self, context):
        wm = context.window_manager
        wm.event_timer_remove(self._timer)

def menu_func(self, context):
    self.layout.operator(ModalTimerOperator.bl_idname, text=ModalTimerOperator.bl_label)

def register():
    bpy.utils.register_class(ModalTimerOperator)
    bpy.types.VIEW3D_MT_view.append(menu_func)

# Register and add to the "view" menu (required to also use F3 search "Modal Timer Operator" for quick access)
def unregister():
    bpy.utils.unregister_class(ModalTimerOperator)
    bpy.types.VIEW3D_MT_view.remove(menu_func)


if __name__ == "__main__":
    register()
    # test call
    bpy.ops.wm.modal_timer_operator()

and a recommendation to try another forum where the author of msgbus, Campbel Barton (@ideasman), might answer. But there was no luck there either.

After two months I've come to the conclusion that it's not possible to use msgbus directly to do what I wanted to do and that the various work-arounds are all pretty fragile.

$\endgroup$

You must log in to answer this question.

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