0
$\begingroup$

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

$\endgroup$
8
  • $\begingroup$ I'm not failure with the ops you are using, but have you tried def invoke_draw(self, context): $\endgroup$
    – Psyonic
    Sep 19, 2022 at 7:38
  • $\begingroup$ @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? $\endgroup$
    – cak3_lover
    Sep 19, 2022 at 8:27
  • $\begingroup$ 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 $\endgroup$
    – Psyonic
    Sep 19, 2022 at 10:34
  • $\begingroup$ @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 $\endgroup$
    – cak3_lover
    Sep 19, 2022 at 11:35
  • $\begingroup$ Have you tried overriding the context in invoke_draw() blender.stackexchange.com/questions/6101/… $\endgroup$
    – Psyonic
    Sep 19, 2022 at 11:51

1 Answer 1

0
$\begingroup$

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()
```
$\endgroup$
1
  • $\begingroup$ 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 $\endgroup$
    – cak3_lover
    Sep 19, 2022 at 15:32

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.