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)
context.window_manager.modal_handler_add(self)
# 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'}
else:
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")
context.window_manager.event_timer_remove(self.timer)
if self.operation == "ROTATE":
bpy.ops.transform.rotate('INVOKE_DEFAULT')
elif self.operation == "SCALE":
bpy.ops.transform.resize('INVOKE_DEFAULT')
return {'FINISHED'}
else:
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)
else:
print("MouseTo3DLocation parameter is not a Vector:", location)
Usage:
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'}