I am trying to create an operator that works in the 3d view which constantly listens for mouse/keyboard events in the background and handles them.

I have found example code for an operator which mostly works, but I cannot completely achieve my goal because when it runs it always has the SpaceTextEditor space in the context, presumably because I run it directly from the Text Editor. Since I am also trying to implement ray intersection to determine where on the Z plane the cursor is at a given time, I need access to the 3Dview in the context instead, and I need mouse coordinates that are relative to the 3D view and not the Text Editor.

How can I change the context so that it uses the 3D view instead of the text editor? I know that with a bit of additional code I can register the operator as a menu item and make it searchable in the F3 view, but I just always want the operator to run in the background, I don't want to having to enable it first.

My Operator code currently looks like the following:

import bpy
from bpy.types import Operator

import mathutils
from mathutils import Vector
from bpy_extras import view3d_utils

class TestOperator(bpy.types.Operator):
    bl_idname = "object.test_tool"
    bl_label = "Test Tool"
    bl_options = {'REGISTER'}

    def __init__(self):

    def __del__(self):
    def execute(self, context):
        if context != None and context.object != None:
            context.object.location.x = self.x / 100.0
            context.object.location.y = self.y / 100.0
    def setpoint(self, contextOriginal, event):
        context = contextOriginal.copy()
        viewport_region = context.region
        viewport_region_data = context.space_data.region_3d
        viewport_matrix = viewport_region_data.view_matrix.inverted()
        # Shooting a ray from the camera, through the mouse cursor towards the grid with a length of 100000
        # If the camera is more than 100000 units away from the grid it won't detect a point
        ray_start = viewport_matrix.to_translation()
        ray_depth = viewport_matrix @ Vector((0,0,-100000))
        # Get the 3D vector position of the mouse
        ray_end = view3d_utils.region_2d_to_location_3d(viewport_region,viewport_region_data, (event.mouse_region_x, event.mouse_region_y), ray_depth )
        # A triangle on the grid plane. We use these 3 points to define a plane on the grid
        point_1 = Vector((0,0,0))
        point_2 = Vector((0,1,0))
        point_3 = Vector((1,0,0))
        # Create a 3D position on the grid under the mouse cursor using the triangle as a grid plane
        # and the ray cast from the camera
        position_on_grid = mathutils.geometry.intersect_ray_tri(point_1,point_2,point_3,ray_end,ray_start,False )

        # Place the empty on the grid under the mouse cursor
        context.object.location = position_on_grid

    def modal(self, context, event):

        if event.alt and event.type == 'MOUSEMOVE' and context.object != None:  # Apply
            self.setpoint(context, event)
        elif event.type == 'LEFTMOUSE':  # Confirm
            return {'PASS_THROUGH'}    
        elif event.type in ('RIGHTMOUSE', 'ESC'):  # Cancel
            return {'PASS_THROUGH'}    

        return {'RUNNING_MODAL'}

    def invoke(self, context, event):
        self.x = event.mouse_region_x
        self.y = event.mouse_region_y

        return {'RUNNING_MODAL'}   

# test call

The Error I Get:

The above code results in this error message (when an object is selected, the alt key is held down, and the mouse is moved):

Python: Traceback (most recent call last):
  File "/home/corner/cloud-sync/projects/3d-modeling/TestProject/testproject_005.blend/testproject_005.py", line 236, in modal
  File "/home/corner/cloud-sync/projects/3d-modeling/TestProject/testproject_005.blend/testproject_005.py", line 208, in setpoint
AttributeError: 'SpaceTextEditor' object has no attribute 'region_3d'

The above line numbers refer to line 30 in the code block above, the line which says viewport_region_data = context.space_data.region_3d


The long term goal is to augment the user interface by adding additional elements in the viewport which the user can interact with - for example when a cube is selected it should render a smaller cube on each side of the cube, which when clicked would spawn a copy of the large cube at that location. Or if the smaller cube is dragged, the side of the cube is extruded in that direction.

Operator Sample code comes from the official documentation (https://docs.blender.org/api/blender2.8/bpy.types.Operator.html#modal-execution)

Ray Intersection code comes from https://devtalk.blender.org/t/mouse-position-on-gride-view/6095

  • $\begingroup$ Press F3 in the 3d view and type in "Test Tool" ... then your operator will be run in the context of 3d view, or add a button in a panel or menu item in the 3d view. It's that easy. $\endgroup$
    – batFINGER
    Commented Mar 14, 2021 at 0:10
  • $\begingroup$ I am aware of this option, but as I wrote I do not want this extra step. I want the operator to be always active without further involvement from the user. Both because it would slow down development, but also because when I release this as an add on, I don't want the user to do anything more than to install/enable it. $\endgroup$
    – cornergraf
    Commented Mar 14, 2021 at 3:37
  • 3
    $\begingroup$ The answer to the error has been covered extensively. Can override the context of any operator, to trick it into thinking it has been invoked elsewhere. Recommend also testing as an addon where "test call" above will fail due to restricted context. $\endgroup$
    – batFINGER
    Commented Mar 14, 2021 at 13:31
  • $\begingroup$ Thank you, the linked answer does look like it should help. I did try to find other answers, but the other answers I found did not actually do what I wanted as far as I could tell. This one seems to do it, but with that title I would never have found it. $\endgroup$
    – cornergraf
    Commented Mar 15, 2021 at 13:11


You must log in to answer this question.

Browse other questions tagged .