0

Some operators are global. Some are not. Transform for example is bound to the window where it gets executed in. As an example let's say i want to perform the transform.rotate from the UV Editor in the UV menu of the 3D view?

All I found so far was some advice in the manual regarding execution context. This example maximizes the screen in all open 3D Views, from: https://docs.blender.org/api/current/bpy.ops.html

# maximize 3d view in all windows
import bpy

for window in bpy.context.window_manager.windows: screen = window.screen

for area in screen.areas:
    if area.type == 'VIEW_3D':
        override = {'window': window, 'screen': screen, 'area': area}
        bpy.ops.screen.screen_full_area(override)
        break

But how would transform.translate fit in here? I'd like to get a button in 3D View .

To make it a bit more complicated, I don't only want to transform actually, I'd like to add back the old transform by 90 degrees back into its location. Which was:

layout.operator("transform.rotate", text="Rotate Minus 90").value = math.pi / -2

enter image description here

Do I still would have to write my own operator here? Or is there an easier way meanwhile?

brockmann
  • 12,613
  • 4
  • 50
  • 93
Tiles
  • 2,028
  • 16
  • 27
  • You can also change the area temporarily: https://blender.stackexchange.com/questions/6101/poll-failed-context-incorrect-example-bpy-ops-view3d-background-image-add or just rotate using low-level functions: https://blender.stackexchange.com/a/28931/31447 and https://blender.stackexchange.com/a/111307/31447 – brockmann Jan 07 '21 at 14:12
  • 1
    Thanks brockman. This is what i was looking for. The Change Area.type looks interesting. Will investigate into the possibilities :) – Tiles Jan 07 '21 at 16:47
  • Hm, no. Seems that i have to create a own class for every operator. – Tiles Jan 07 '21 at 16:59
  • No need for a class. Select the default cube and run the script from: https://blender.stackexchange.com/questions/28929/rotate-uv-by-specific-angle-e-g-30deg-in-python-script-in-backgroud-mode/28931#28931 – brockmann Jan 07 '21 at 17:23
  • The rotate was just an example. There is much more. It's around half of the operators that needs to run in correct area when i remember right. But thanks for the hint :) – Tiles Jan 07 '21 at 17:51
  • No problem. But hard to help otherwise, since each operator is different and you'd have to pass specific context members depending on the operator. – brockmann Jan 07 '21 at 17:58
  • I see. Was worth a try. – Tiles Jan 07 '21 at 19:51

2 Answers2

2

The method from brockman. It shows one of the operator buttons. For the other direction simply use bpy.ops.object.simple_operator(val=math.pi/-2)

import bpy
import math
​
class SimpleOperator(bpy.types.Operator):
    """Tooltip"""
    bl_idname = "object.simple_operator"
    bl_label = "Simple Object Operator"
    bl_options = {'REGISTER', 'UNDO'}
val: bpy.props.FloatProperty()

@classmethod
def poll(cls, context):
    return context.active_object is not None

@classmethod
def description(cls, context, properties):
    return properties.arg

​ def execute(self, context): for area in bpy.context.screen.areas: if area.type == 'IMAGE_EDITOR': override = bpy.context.copy() override['area'] = area bpy.ops.transform.rotate(override, value = self.val) return {'FINISHED'} ​ ​ def register(): bpy.utils.register_class(SimpleOperator) ​ ​ def unregister(): bpy.utils.unregister_class(SimpleOperator) ​ ​ if name == "main": register() ​ # test call bpy.ops.object.simple_operator(val=math.pi/2)

Buttons:

    #buttons with separated tooltips. Put it in a menu or a panel
    myvar = layout.operator(SimpleOperator.bl_idname, text="Rotate Counter Clockwise").arg = 'Tooltip for Rotate Counter Clockwise'
    myvar.val=math.pi/2
myvar = layout.operator(SimpleOperator.bl_idname, text="Rotate Clockwise").arg = 'Tooltip for Rotate Clockwise'
myvar.val=math.pi/-2

Tiles
  • 2,028
  • 16
  • 27
  • Just wanted to add, this method has then just one tooltip though for two tools. – Tiles Jan 13 '21 at 10:17
  • "Operators can now have a dynamic tooltip that changes based on the context and operator parameters." -> https://wiki.blender.org/wiki/Reference/Release_Notes/2.81/Python_API#Operators – brockmann Jan 13 '21 at 11:26
  • Ah, that's cool. But how does this work with two buttons then? – Tiles Jan 13 '21 at 12:14
  • 1
    As stated in the release notes: op = layout.operator(SimpleOperator.bl_idname).arg = 'FOO' \ op.val=math.pi/2, see: https://blender.stackexchange.com/a/2516/31447 – brockmann Jan 13 '21 at 13:59
  • Thanks. I don't mean how you pass the argument, but how you distinguish the two operator cases. Sorry if i nag you too much ^^ – Tiles Jan 13 '21 at 16:53
  • I mean you provided a way to pass the tooltip. But how do you know what tooltip to use? Where is the if? – Tiles Jan 13 '21 at 16:57
  • Still don't get the question... you can specify that in your layout: layout.operator(SimpleOperator.bl_idname, text="FOO").arg = 'FOO', layout.operator(SimpleOperator.bl_idname, text="BAR").arg = 'BAR' – brockmann Jan 13 '21 at 17:01
  • Ah, now i get it. Thanks :) – Tiles Jan 13 '21 at 17:05
  • Do you mind updating the script? – brockmann Jan 13 '21 at 19:47
  • Will do. Let's see if i got it right ^^ – Tiles Jan 14 '21 at 07:37
  • Change is untested. I miss the time at the moment. Will come back to it once tested ... – Tiles Jan 14 '21 at 07:54
0

To have an aswer here for peopel that searches for a solution, I ended in writing my own class. When somebody has a more elegant solution, everything is welcome :)

The classes:

class TOOLBAR_MT_image_uv_rotate_clockwise(bpy.types.Operator):
    """Rotate selected UV geometry clockwise by 90 degrees"""
    bl_idname = "image.uv_rotate_clockwise"
    bl_label = "Rotate UV by 90"
    bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
    for area in bpy.context.screen.areas:
        if area.type == 'IMAGE_EDITOR':
            override = bpy.context.copy()
            override['area'] = area
            bpy.ops.transform.rotate(override, value = math.pi/-2 )
    return {'FINISHED'}


class TOOLBAR_MT_image_uv_rotate_counterclockwise(bpy.types.Operator): """Rotate selected UV geometry counter clockwise by 90 degrees""" bl_idname = "image.uv_rotate_counterclockwise" bl_label = "Rotate UV by minus 90" bl_options = {'REGISTER', 'UNDO'}

def execute(self, context):
    for area in bpy.context.screen.areas:
        if area.type == 'IMAGE_EDITOR':
            override = bpy.context.copy()
            override['area'] = area
            bpy.ops.transform.rotate(override, value = math.pi/2 )
    return {'FINISHED'}

The operators:

layout.operator("image.uv_rotate_clockwise", text="Rotate clockwise")
layout.operator("image.uv_rotate_counterclockwise", text="Rotate counter clockwise")

The register part:

TOOLBAR_MT_image_uv_rotate_clockwise,
TOOLBAR_MT_image_uv_rotate_counterclockwise,
Tiles
  • 2,028
  • 16
  • 27
  • The "more elegant" solution would be adding an operator float property for the rotation, which allows to pass your rotation value, that way you can also get rid of the second class. – brockmann Jan 10 '21 at 18:32
  • Could you provide an example please? – Tiles Jan 12 '21 at 07:26
  • 1
    Sure: https://pasteall.org/YUho Consider adding a proper poll method. – brockmann Jan 12 '21 at 10:15
  • Thanks. Pasteall is nothing permanent, so i will add it as a second answer here. Or you do. Then i will remove mine. – Tiles Jan 12 '21 at 16:47