0
$\begingroup$

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?

$\endgroup$
7
2
$\begingroup$

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
$\endgroup$
11
  • $\begingroup$ Just wanted to add, this method has then just one tooltip though for two tools. $\endgroup$ – Tiles Jan 13 at 10:17
  • $\begingroup$ "Operators can now have a dynamic tooltip that changes based on the context and operator parameters." -> wiki.blender.org/wiki/Reference/Release_Notes/2.81/… $\endgroup$ – brockmann Jan 13 at 11:26
  • $\begingroup$ Ah, that's cool. But how does this work with two buttons then? $\endgroup$ – Tiles Jan 13 at 12:14
  • 1
    $\begingroup$ As stated in the release notes: op = layout.operator(SimpleOperator.bl_idname).arg = 'FOO' \ op.val=math.pi/2, see: blender.stackexchange.com/a/2516/31447 $\endgroup$ – brockmann Jan 13 at 13:59
  • $\begingroup$ 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 ^^ $\endgroup$ – Tiles Jan 13 at 16:53
0
$\begingroup$

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,
$\endgroup$
4
  • $\begingroup$ 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. $\endgroup$ – brockmann Jan 10 at 18:32
  • $\begingroup$ Could you provide an example please? $\endgroup$ – Tiles Jan 12 at 7:26
  • 1
    $\begingroup$ Sure: pasteall.org/YUho Consider adding a proper poll method. $\endgroup$ – brockmann Jan 12 at 10:15
  • $\begingroup$ Thanks. Pasteall is nothing permanent, so i will add it as a second answer here. Or you do. Then i will remove mine. $\endgroup$ – Tiles Jan 12 at 16:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.