Activating the script in parallel with the operation

Hello everyone.

I wrote a small script that should speed up the process of production of fences in my models and the question arose about how it would be more convenient to use it.

I don’t want to click the button every time to activate the script and would like to combine it with the operation “transform .translation”, but there were difficulties. I’m having trouble figuring out how to do it right.
When trying to embed an operation call together with my script via modal - for some reason my operation was performed first, than transformation, that deprived the script of any sense.:exploding_head:
I need to know somehow when the transform operation ends and execute the script or, ideally, run the script in parallel by tweaking the UV-wrapper during the transformation process.

Couldn’t add to the post.
This is what activation looks like in modal.
It seems to be after the completion of transform.translation should execute the script, but I don’t understand why it doesn’t :C

Screenshot_2

A couple of things:

  1. bpy.ops.transform.translate('INVOKE_DEFAULT') should be executed under invoke method before modal_handler_add(self). If you add it after, it will block input events to your custom modal until transform finishes.

  2. Your modal method’s continuous return value is RUNNING_MODAL, but it needs to be PASS_THROUGH to allow sending input events to the transform operator.

  3. When you return CANCELLED or FINISHED after ESC or RIGHTMOUSE, this event is never passed to the transform modal. You should instead use self.stopped = True and self.cancelled = True and return PASS_THROUGH. This will pass the event to the transform operator (so it cancels properly), and the next time your modal method runs and checks these values it automatically stops.

See this example:

# run this script
# set viewport to solid mode
# select an object
# open search and run 'Test Modal'

import bpy
from mathutils import Color
from random import random


class TestModal(bpy.types.Operator):
    bl_idname = "object.testmodal"
    bl_label = "Test Modal"
    bl_options = {'REGISTER', 'UNDO'}
    
    
    @classmethod
    def poll(self, context):
        mode = 'OBJECT' in context.mode
        obj = context.selected_objects
        return mode and obj
    
    
    def modal(self, context, event):
        
        if self.finished:
            
            print("stopped")
            return {'FINISHED'}
        
        elif self.cancelled:
            
            print("cancelled")
            return {'CANCELLED'}
                
                
        if event.type == 'MOUSEMOVE':
            
            rand_color = random(), random(), random()
            context.object.active_material.diffuse_color = rand_color
                
            return {'PASS_THROUGH'}
            
            
        if event.type in {'LEFTMOUSE'}:
            
            # use a "stop" property to tell this modal to end while allowing
            # the bpy.ops.transform operator receive the mouse mouse event
            self.finished = True
            print("got", event.type, "stopping...")
            
            return {'PASS_THROUGH'}
        
        elif event.type in {'RIGHTMOUSE'}:
            
            self.cancelled = True
            
        return {'PASS_THROUGH'}
        
        
    def invoke(self, context, event):
        self.finished = False
        self.cancelled = False
        
        # transform operator must be executed before modal
        # handler is added, otherwise it will block events
        bpy.ops.transform.translate('INVOKE_DEFAULT')
        wm = context.window_manager
        wm.modal_handler_add(self)
        
        return {'RUNNING_MODAL'}
    
    
def register():
    bpy.utils.register_class(TestModal)
    
def unregister():
    bpy.utils.unregister_class(TestModal)
    
if __name__ == '__main__':
    register()
3 Likes

Wow, thanks!
Tell me more - what is this method self.finished? Where can I read more about this in order to solve such problems independently in the future?

You can use self.anything = value to store an attribute in the class instance so that methods (like execute, modal, invoke etc.) can access it just by typing self..

This is useful when you want to store something as a result from one method, and then change the program flow from another method without needing to pass it as an argument. In the example above I store it as an instanced attribute, but could have also defined it in the class.

I don’t have any good sources to give, but if you search for “python class self” you will find that it’s a basic concept for a python class.

Sorry - I didn’t notice that self.finished is an unrelated variable.
For some reason it seemed to me that this variable is set for something.

If I understand correctly, it is only for show {‘PASS_THROUGH’} before {‘FINISHED’}, right?

Yep. It’s a way to tell the modal to stop at next cycle without using {'FINISHED'}. We need to use {'PASS_THROUGH'} after the modal finds 'RIGHTMOUSE' or 'ESC', because we want these to be passed to the transform modal so that it also stops.

1 Like

Hi @kaio… thank you for this tips.

what if I need cancel/stop transform modal, change some things and call it again and keeping watching events from my addon? some way to call again the ‘invoke’ method? or delete and add again the modal_handler?

know you how to force stop the modal transform, from python code ? or simulate the ‘esc’ key press or the right mouse button click (from python code)?