Creating an object at mouse position when invoking an operator from a panel

I am trying to run the code from the answer of this question
(1) in the addon im am writing. When I run it from the TextEditor in Blender like the following it works fine, but also only in the TextEditior window. (This is changed to View3D).

When I run it from a Button, than the Region is not ‘WINDOW’ but ‘UI’ so I thought this is the reason why the sphere is not where the mouseclick was. I didn’t succeed in changeing the region so I am not sure.

So it would be good to know what is causing the difference when running the script from TextEditor vs Addon (I use the BlenderDevelopmentTool in VS-Code) , and how to fix it. Thanks already for any answers :smiley:

"import bpy
from bpy_extras.view3d_utils import region_2d_to_vector_3d, region_2d_to_location_3d, region_2d_to_origin_3d


#source: https://blender.stackexchange.com/questions/80565/how-can-i-add-a-new-object-on-mouse-click?noredirect=1&lq=1
#abgerufen 12.10.2021
class ModalTimerOperator(bpy.types.Operator):
    """Operator which runs its self from a timer"""
    bl_idname = "wm.modal_timer_operator"
    bl_label = "Add Sphere on Click\"

    _timer = None
    
    def modal(self, context, event):
        if event.type in {'RIGHTMOUSE', 'ESC'}:
            self.cancel(context)
            return {'CANCELLED'}

        if event.type == 'LEFTMOUSE' and event.value == 'PRESS':
            # left click
            context.area.type = 'VIEW_3D'
            if context.area.type == 'VIEW_3D':
                region = context.region
                r3d = context.space_data.region_3d
                x, y = event.mouse_region_x, event.mouse_region_y
                view = region_2d_to_vector_3d(region, r3d, (x, y))
                origin = region_2d_to_origin_3d(region, r3d, (x, y))
                loc = region_2d_to_location_3d(region, r3d, (x, y), view)
                print("origin", origin, "view", view, "location", loc)
                print("view_matrix", r3d.view_matrix )
                print("perspective_matrix" , r3d.perspective_matrix)
                # TODO: change this ...
                print("world_matrix" , r3d.perspective_matrix)
                bpy.ops.mesh.primitive_uv_sphere_add(location=loc)
        
        if event.type == 'TIMER':
            #print("timer")
            pass
        
        return {'PASS_THROUGH'}

    def execute(self, context):
        context.area.type = 'VIEW_3D'
        if context.area.type != 'VIEW_3D':
            print("Must use 3d region")
            return {'CANCELLED'}
        
        wm = context.window_manager
        self._timer = wm.event_timer_add(0.1, window=context.window)
        wm.modal_handler_add(self)
        return {'RUNNING_MODAL'}

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


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


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


if __name__ == "__main__":
    register()

    # test call
    bpy.ops.wm.modal_timer_operator()

# note code edited from text editor > templates > python > modal timer operator."

what I use to call the operator:

        row = layout.row()
        row.operator_context = 'INVOKE_DEFAULT'
        row.operator('annotate.image', text="Annotate Image")

pushing the button causes the ‘event.mouse_region_x’ to be related to the ‘UI’ instead of ‘WINDOW’ I think?

(1) https://blender.stackexchange.com/questions/80565/how-can-i-add-a-new-object-on-mouse-click?noredirect=1&lq=1 https://blender.stackexchange.com/questions/80565/how-can-i-add-a-new-object-on-mouse-click?noredirect=1&lq=1

Why a modal timer? Use a normal modal operator instead. Or if you don’t need the complexity that a modal operator can offer:

you could write code that you can call with a hotkey instead, you’d add code for a keymap in the register function. No modal needed. Just an operator/class that runs when your key is pressed.

            bpy.ops.mesh.primitive_uv_sphere_add(location=loc)

and the code for the proper location.

Thank you for the answer.
So I also tried it in the Add-on with the following, which doesn’t contain a timer, but also in this case the location doesn’t match the mouseclick. I want to integrate it in a Panel so it can be used after loading an Image in 3D View. The spheres added on Mouseklick demonstrate Landmarks in the Image for later use. Do you have any suggestions why the location is wrong ?

import bpy
from bpy_extras.view3d_utils import region_2d_to_vector_3d, region_2d_to_location_3d, region_2d_to_origin_3d

class Image_Annotator_OT(bpy.types.Operator):
    bl_idname = "wm.image_annotator"
    bl_label = "Annotate Image"
    bl_description = "annotate the image with mouseclicks"

    def __init__(self):
        """called before invoke """
        print("Start")

    def __del__(self):
        """called, when operator is finished"""
        print("End")

    def modal(self, context, event):
        # area_window = self.get_3D_viewport_area(context)
        # region_window = self.get_window_in_area(area_window)

        if event.type == 'LEFTMOUSE' and event.value == 'PRESS':
            x, y = event.mouse_region_x, event.mouse_region_y
            region = context.region
            r3d = context.space_data.region_3d
            # origin = region_2d_to_origin_3d(region, r3d, (x, y))
            view = region_2d_to_vector_3d(region, r3d, (x, y))
            loc = region_2d_to_location_3d(region, r3d, (x, y), view)
            bpy.ops.mesh.primitive_uv_sphere_add(align='WORLD', location=loc)
            return {'RUNNING_MODAL'}

        if event.type == 'ESC':
            return {'FINISHED'}

        return {'RUNNING_MODAL'}

    def invoke(self, context, event):
        print('invoke')
        context.window_manager.modal_handler_add(self)
        # start the modal function
        return {'RUNNING_MODAL'}
    

I see, I thought it wouldn’t ran because of incorrect context/poll sorry. I’ve not yet done this myself so I don’t know what currently works for sure tbh.

What I gather from your response, is it so that only the depth does not match? And that you want to place these spheres at a specific depth/ on a plane that is your image? If so, the code:

loc = region_2d_to_location_3d(region, r3d, (x, y), view)

needs to be changed and most likely it is the view variable as this variable provides input for the depth vector if i’m not mistaken.

The location of a different location, where not only the depth is different. I want to place the spheres on a specific depth x.

I tried to change the view to (0,1,0) when I am viewing the region from Numpad 1 view. But that also didn’t fix the problem. What is strange is that when i click on the left side of the Panel, the sphere appears at the left side of the 3d view. When I iterate (click) than over the panel on a horizontal line, the spheres always appear a bit right to the previous one. On the picture the sphere selected is related to the current mouse position. Thats why I think the problem is caused because the event (and/or the context) is locked to the region where I actived the button (‘UI’) and event.mouse_region_x returns the value related to ‘UI’ instead of ‘WINDOW’. But I am having trouble changing this region in modal. So I don’t know when event and context are updated in modal() and how to change them to ‘WINDOW’ ?

I think you’re right!

what about this:
Operators (bpy.ops) — Blender Python API(just above submodules)
python - Start modal operator from a click in panel - Blender Stack Exchange

what you want to do is called overriding a context.