3
$\begingroup$

In my add-on I'm adding a GPU drawing of an image in the Shader Editor. However, if multiple Shader Editor areas are open on the screen, blender will add the GPU drawing to all of them.

self._handle = bpy.types.SpaceNodeEditor.draw_handler_add(callback_image, (self, context), 'WINDOW', 'POST_PIXEL')

I also tried the following but it still adds the draw handler to all Shader Editors, even ones that the user creates after running the modal add-on.

def invoke(self, context, event):
    for i in context.area.spaces:
        if i.type == 'NODE_EDITOR':
            self._handle = i.draw_handler_add(callback_image, (self, context), 'WINDOW', 'POST_PIXEL')

Is there any way to avoid blender adding the draw handler to each Shader Editor area? In other words, can I add the handler to only the Shader Editor area which was active when the Operator was run?

Additionally, my gpu draw function also shows up in Compositor, Geometry Node Editor and Texture Node Editor, but I'd like it to only display in Shader Editor.

$\endgroup$

1 Answer 1

4
$\begingroup$

I think it can't, but you can draw in special areas

enter image description here

import bpy
import gpu
from gpu_extras.batch import batch_for_shader

vertices = (
    (100, 100), (300, 100),
    (100, 200), (300, 200))

indices = (
    (0, 1, 2), (2, 1, 3))

shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
batch = batch_for_shader(shader, 'TRIS', {"pos": vertices}, indices=indices)


def draw():
    cls = ModalDrawOperator
    try:
        # if open a new file, it cannot access the INSTANCE
        ins = cls.INSTANCE
    except:
        cls.modal_fin()
        return

    if ins.AREA != bpy.context.area: # draw only modal area
        return

    shader.bind()
    shader.uniform_float("color", (0, 0.5, 0.5, 1.0))
    batch.draw(shader)


class ModalDrawOperator(bpy.types.Operator):
    bl_idname = "view3d.modal_operator"
    bl_label = "XXX Modal View3D Operator"

    INSTANCE = None
    HANDLE = None

    @classmethod
    def modal_fin(cls):
        try:
            cls.INSTANCE = None
            bpy.types.SpaceView3D.draw_handler_remove(cls.HANDLE, 'WINDOW')
            cls.HANDLE = None
            bpy.context.area.tag_redraw()
        except:
            pass

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

        if event.type in {'ESC'}:
            self.__class__.modal_fin()
            return {'CANCELLED'}

        return {'PASS_THROUGH'}

    def invoke(self, context, event):
        cls = self.__class__
        if cls.HANDLE is not None:
            self.report({'WARNING'}, "Abort, modal already running.")
            return {'CANCELLED'}

        if context.area.type == 'VIEW_3D':
            cls.INSTANCE = self
            cls.AREA = context.area

            cls.HANDLE = bpy.types.SpaceView3D.draw_handler_add(draw, (), '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()
  1. Run script
  2. Go to 3D viewport and call the operator from search menu

enter image description here

$\endgroup$
1
  • $\begingroup$ This works perfectly. Thank you very much for the masterclass on classes :) I was able to incorporate this into my add-on easily, although I will hopefully be able to understand it more deeply in the future. $\endgroup$ Commented Jan 30, 2023 at 12:41

You must log in to answer this question.

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