0

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):
    print("Start")

def __del__(self):
    print("End")

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
        print('test')
        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
    self.execute(context)

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

test call

bpy.utils.register_class(TestOperator) bpy.ops.object.modal_operator('INVOKE_DEFAULT')

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

Background:

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

cornergraf
  • 125
  • 1
  • 7
  • 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. – batFINGER Mar 14 '21 at 00:10
  • 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. – cornergraf Mar 14 '21 at 03:37
  • 3
    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. – batFINGER Mar 14 '21 at 13:31
  • 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. – cornergraf Mar 15 '21 at 13:11

0 Answers0