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.
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
bpy.ops.transform.translate('INVOKE_DEFAULT') should be executed under invoke method beforemodal_handler_add(self). If you add it after, it will block input events to your custom modal until transform finishes.
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.
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()
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.
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.
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)?