0

I'm trying to 'INVOKE_DEFAULT' using thread in the following code:

import bpy
import gpu,threading

def draw(self, context):

if self.modal_redraw == True:

    self.framebuffer = gpu.state.active_framebuffer_get()

    self.viewport_info = gpu.state.viewport_get()
    self.width = self.viewport_info[2]
    self.height = self.viewport_info[3]

    self.framebuffer_image.scale(self.width, self.height)

    self.pixelBuffer = self.framebuffer.read_color(0, 0, self.width, self.height, 4, 0, 'FLOAT')

    self.pixelBuffer.dimensions = self.width * self.height * 4
    self.framebuffer_image.pixels.foreach_set(self.pixelBuffer)

    self.modal_redraw = False


class ModalFramebufferCopy(bpy.types.Operator): bl_idname = "view3d.modal_framebuffer_copy" bl_label = "Draw 3D View Framebuffer"

def __init__(self):
    print("Start example code")

    # init variables
    self.width = 32
    self.height = 32
    self.modal_redraw = False
    self.image_name = "color_buffer_copy"
    self.framebuffer = None
    self.viewport_info = None
    self.pixelBuffer = None

    if not self.image_name in bpy.data.images:
        self.framebuffer_image = bpy.data.images.new(self.image_name , 32, 32, float_buffer=True)
    else:
        self.framebuffer_image = bpy.data.images[self.image_name ]


# 
def __del__(self):
    print("End example code")


# modal operator for controlled redraw of the image
def modal(self, context, event):
    # stop the execution of this example code if 'ESC' is pressed
    if event.type in {'ESC'}:
        bpy.types.SpaceView3D.draw_handler_remove(self._handle_3d, 'WINDOW')
        print("Removing draw handler")
        return {'CANCELLED'}

    else:
        self.modal_redraw = True

    return {'PASS_THROUGH'}

def invoke(self, context, event):
    print("Invoking modal operator")

    self._handle_3d = bpy.types.SpaceView3D.draw_handler_add(draw, (self, context), 'WINDOW', 'PRE_VIEW') 

    context.window_manager.modal_handler_add(self)
    return {'RUNNING_MODAL'}


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

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

def invoke_draw(): bpy.ops.view3d.modal_framebuffer_copy('INVOKE_DEFAULT')

if name == "main": register()

server_thread=threading.Thread(target=invoke_draw)
server_thread.start()

but at the following line

bpy.ops.view3d.modal_framebuffer_copy('INVOKE_DEFAULT')

it gives the following error

RuntimeError: Operator bpy.ops.view3d.modal_framebuffer_copy.poll() Missing 'window' in context

I've desperately tried everything but I'm out of ideas, how do I fix this?

Edit
Let me state what I'm trying to achieve.

I have another application which needs the images, so it sends blender a signal to generate the image via servers, which is why I'm using threading in the first place so that listening for signals

i.e. to listen for incoming messages-> move the 3D figure -> generate image

here's the entire code if you'd like to dive deeper

Edit 2
I changed my approach, you can follow it here

cak3_lover
  • 477
  • 3
  • 11
  • I'm not failure with the ops you are using, but have you tried def invoke_draw(self, context): – Psyonic Sep 19 '22 at 07:38
  • @Psyonic I don't follow, are you saying I should add def invoke_draw(self, context) to class ModalFramebufferCopy? if so how do I run it through a thread? – cak3_lover Sep 19 '22 at 08:27
  • The word "failure" should have been "familiar" - couldn't edit
    No, the actual def at the bottom of your script, change to def invoke_draw(self, context)
    But this is just a guess :) I don't know about multi-threading and I could not find any documentation on bpy.ops.view3d.modal_framebuffer_copy
    – Psyonic Sep 19 '22 at 10:34
  • @Psyonic I don't see how def invoke_draw(self, context) would help exactly? it's not a part of the ModalFramebufferCopy class so what would I pass as arguments in threading.Thread(target=invoke_draw)? also it modal_framebuffer_copy isn't inbuilt, I got it from this answer – cak3_lover Sep 19 '22 at 11:35
  • Have you tried overriding the context in invoke_draw()
    https://blender.stackexchange.com/questions/6101/poll-failed-context-incorrect-example-bpy-ops-view3d-background-image-add/6105#6105
    – Psyonic Sep 19 '22 at 11:51
  • @Psyonic yep I tried that as well, same error – cak3_lover Sep 19 '22 at 12:08
  • When I run that code I don't get any errors ‍♂️ – Psyonic Sep 19 '22 at 12:22
  • @Psyonic did it generate the image? it gives the error in the terminal, also I'm using Blender 3.3.0 - temp-viewport-compositor-merge on Ubuntu 22.04.1 LTS – cak3_lover Sep 19 '22 at 12:29

1 Answers1

0

So the main problem is that you're not allowed to use threading as your way to handle the asyncronous nature of what you're hoping to accomplish. You should rely more on the Modal function and allow blender to take care of the lifecycle of your script.

Something more like this probably accomplishes what you're looking for:

import bpy
import gpu

def draw(self, context): self.report({"WARNING"}, "Trying to Draw")

if self.modal_redraw == True:
    self.framebuffer = gpu.state.active_framebuffer_get()

    self.viewport_info = gpu.state.viewport_get()
    self.width = self.viewport_info[2]
    self.height = self.viewport_info[3]

    self.framebuffer_image.scale(self.width, self.height)

    self.pixelBuffer = self.framebuffer.read_color(0, 0, self.width, self.height, 4, 0, 'FLOAT')

    self.pixelBuffer.dimensions = self.width * self.height * 4
    self.framebuffer_image.pixels.foreach_set(self.pixelBuffer)

    self.modal_redraw = False

class ModalFramebufferCopy(bpy.types.Operator): bl_idname = "view3d.modal_framebuffer_copy" bl_label = "Draw 3D View Framebuffer"

def __init__(self):
    self.report({"WARNING"}, "Start example code")

    # init variables
    self.width = 32
    self.height = 32
    self.modal_redraw = False
    self.image_name = "color_buffer_cop213521y"
    self.framebuffer = None
    self.viewport_info = None
    self.pixelBuffer = None

    if not self.image_name in bpy.data.images:
        self.framebuffer_image = bpy.data.images.new(self.image_name , 32, 32, float_buffer=True)
    else:
        self.framebuffer_image = bpy.data.images[self.image_name ]

def modal(self, context, event):
    # stop the execution of this example code if 'ESC' is pressed
    if event.type in {'ESC'}:
        bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
        self.report({"WARNING"}, "Removing draw handler")
        return {'CANCELLED'}

    else:
        self.modal_redraw = True

    return {'RUNNING_MODAL'}

def invoke(self, context, event):
    self.report({"WARNING"}, "Invoking modal operator")
    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, args, 'WINDOW', 'PRE_VIEW')

        context.window_manager.modal_handler_add(self)
        return {'RUNNING_MODAL'}
    else:

        self.report({'WARNING'}, f"View3D not found, cannot run operator, {context.area.type}")
        return {'CANCELLED'}


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

Register and add to the "view" menu (required to also use F3 search "Simple Modal Operator" for quick access).

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

def unregister(): bpy.utils.unregister_class(ModalFramebufferCopy) bpy.types.VIEW3D_MT_view.remove(menu_func)

if name == "main": register() ```

FarmDev
  • 16
  • well I'm not sure this is what I was looking for, I edited the question so you can have a better perception of what I'm trying to achieve – cak3_lover Sep 19 '22 at 15:32