When I select an object and press R I get the black double-arrow indicator:
rotate command visual indicators

However, when I run bpy.ops.transform.rotate('INVOKE_DEFAULT') from an operator (e.g. the built-in Tools -> Rotate) there are no indicators.

This applies to bpy.ops.transform.resize('INVOKE_DEFAULT') too.

Is there a way to get these indicators to show?

It is strange that even though I'm placing the mouse pointer into the WINDOW region with cursor_warp() before calling transform.rotate() I do not get the visual indicator. The mouse pointer does get placed correctly and the operator performs as expected - but without the indicator showing up.

The problem is that cursor_warp() changes only the mouse pointer position and does not update anything else (i.e. does not update the active region).
The transform operators always do work with the correct region ('WINDOW') but the UI part does not (it works with the region from where the operator was invoked).


2 Answers 2


The visual indicator that follows the mouse doesn't appear because when you use a button in the Tool Shelf to invoke an operator, the mouse is in a region separate from the actual 3D View, which is where the operator takes place. Here are the region types, taken from https://docs.blender.org/api/blender_python_api_2_78_4/bpy.types.Region.html:


Rotate and Resize are operators that will not draw their visual indicators if they are not invoked in the proper region. If you call these same operators from the search menu (Spacebar) and the mouse is in the 3D View's region instead of the Tool Shelf's region, the indicators will appear.

Additionally, if you press R to rotate an object with the mouse inside the Tool Shelf (Region Overlap must be enabled in the User Preferences for this to work, unless you use the search menu instead of R), the indicators will still not appear, for the same reason that it doesn't work with buttons.

  • $\begingroup$ Thanks for explaining this, Michael. Is there a particular reason for this behavior? $\endgroup$
    – spacer
    Apr 25, 2017 at 19:24
  • $\begingroup$ I noticed this when making a modal-based add-on: if a modal for the 3D View gets invoked with a button in the Tool Shelf, the mouse won't retain its coordinates relative to the screen, just the region that it was invoked in. When I used a draw handler to visually indicate the mouse's position with a circle, it was really being drawn about 200 pixels below the mouse due to the separate lower half of the Tool Shelf being visible. When I moved the mouse over to the 3D View, the circle was instantly off-screen. $\endgroup$ Apr 25, 2017 at 19:40
  • $\begingroup$ Yes, I find this behavior odd too. I'm using the window.cursor_warp() to position the pointer before starting the transform and it works, but I'd really like to get these visual indicators too. $\endgroup$
    – spacer
    Apr 26, 2017 at 9:19
  • $\begingroup$ It appears that context.area.type and context.region.type are the same ('VIEW_3D' and 'WINDOW') no matter if the operator is executed from the Toolshelf or the search menu even though visual indicators do appear from search menu ... This smells as though the decision is made based on something else ... And I'm placing the mouse pointer into the WINDOW region with cursor_warp() before calling transform.rotate() and it behaves correctly (minus the visual indicator) ... $\endgroup$
    – spacer
    Apr 26, 2017 at 12:40

The catch is that the transform operators themselves do not decide whether to draw the visual indicators - it is decided upon by actual drawing functions based on the region which was active when the operator was invoked.

Operators always work in the correct region, which is 'WINDOW' here, but the GUI code works on the "active region", which can be the ToolShelf, the main 3D View, etc.

I came to this by analyzing the source code.

Using window.cursor_warp() to move the mouse into the main 3D View does not solve the issue by itself because it changes only the mouse pointer position and does not update anything else (i.e. does not update the active region).

The way to always show the indicators would be to somehow force Blender to change the active region (i.e. active subwindow, as termed in the source code) before invoking the transform operators.
However, as I noted in the comment to Michael's answer, the active region as accessible from operator's execute code is already set to 'WINDOW' so there is nothing to change from there. A source code hack seems to be the only a solution.

In other words, there is no way to show the indicators from a Python script.

Update - there is a solution:

Invoking the transform operators from a modal timer operator.

The delay gives Blender time to process the new mouse cursor position and transform operators work in the correct context.

That also solves a glitch introduced by that hack occasionally visible in transform.rotate() and transform.resize() (rotation/size becomes huge only for an instant after the operation starts).

Example code:

import bpy
import mathutils
from bpy_extras import view3d_utils
from bpy.types import Operator

class DelayedTransform(Operator):
    bl_idname = "my.delayedtransform"
    bl_label = "DelayedTransform Operator"

    timer = None
    operation = bpy.props.StringProperty()

    def invoke(self, context, event):
        print("DelayedTransform invoke", self.operation)
        if self.operation in {"ROTATE", "SCALE"}:
            self.timer = context.window_manager.event_timer_add(0.01, context.window)
            # get the selection
            selobj = context.selected_objects
            if len(selobj) > 0:
                # calculate median point of the selection
                locs = [obj.location for obj in selobj]
                gloc = sum(locs, mathutils.Vector()) / len(locs)
                # offset position to make it easier to scale/rotate
                MouseTo3DLocation(gloc, 50, context)
            return {'RUNNING_MODAL'}
            print("Bad operation parameter:", self.operation)
            return {'CANCELLED'}

    def modal(self, context, event):
        print("DelayedTransform modal", self.operation, event.type, event.value)
        if event.type == 'TIMER':
            print("---DelayedTransform modal finished")
            if self.operation == "ROTATE":
            elif self.operation == "SCALE":
            return {'FINISHED'}
            print("---DelayedTransform modal pass-through")
            return {'PASS_THROUGH'}

def MouseTo3DLocation(location, offset_pixels=0, context = None):
    if type(location) is mathutils.Vector:
        if context == None:
            context = bpy.context
        for area in context.screen.areas:
            if area.type == 'VIEW_3D':
                viewport = area.regions[4]  # yes, this can be improved :)
                # 2D coordinates in the 3D View area (origin lower left)
                c_2d =  view3d_utils.location_3d_to_region_2d(viewport, area.spaces[0].region_3d, location)
                # 2D coordinates in Blender window (origin lower left)
                c_2d_w = (c_2d[0] + viewport.x, c_2d[1] + viewport.y)
                # add the offset
                context.window.cursor_warp(c_2d_w[0]+offset_pixels, c_2d_w[1]+offset_pixels)
        print("MouseTo3DLocation parameter is not a Vector:", location)


class MyOperator(Operator):
    """This is my operator"""
    bl_idname = "my.operator"
    bl_label = "My Operator"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        bpy.ops.my.delayedtransform('INVOKE_DEFAULT', operation = "ROTATE")
        return {'FINISHED'}

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .