How to start a Modal Timer at launch in an addon ?

Hello

In an addon I would like to start a modal timer operator (to read a device) as soon Blender is started.
Unfortunately I’m stuck, the operator seems extremely picky about the context.

I tried various things but so far getting only errors and segfaults:

  1. putting the operator call straight in the register function -> error :

    scene = context.scene
AttributeError: '_RestrictContext' object has no attribute 'scene'
  1. via a “load_post” handler in the register function -> segfault
    I don’t know if it is helpful but here is the backtrace:
$ cat /tmp/blender.crash.txt 
# Blender 2.70 (sub 0), Commit date: 1970-01-01 00:00, Hash unknown

# backtrace
blender() [0x9730ad]
/lib64/libpthread.so.0(+0x10b10) [0x7f972da37b10]
blender(BLI_addhead+0x5) [0x10b87e5]
blender(WM_event_add_modal_handler+0x80) [0x97c2f0]
blender(WindowManager_modal_handler_add_call+0xc) [0x150f86c]
blender(RNA_function_call+0x12) [0x141ecb2]
blender() [0xd00259]
/usr/lib64/libpython3.3.so.1.0(PyObject_Call+0x7a) [0x7f972e99c45a]
/usr/lib64/libpython3.3.so.1.0(PyEval_EvalFrameEx+0x31dc) [0x7f972ea45dfc]
/usr/lib64/libpython3.3.so.1.0(PyEval_EvalCodeEx+0x803) [0x7f972ea4aad3]
/usr/lib64/libpython3.3.so.1.0(+0x87c8f) [0x7f972e9c2c8f]
/usr/lib64/libpython3.3.so.1.0(PyObject_Call+0x7a) [0x7f972e99c45a]
blender() [0xd00ed4]
blender() [0x150f0a3]
blender() [0x979aaa]
blender(WM_operator_call_py+0x6f) [0x97a38f]
blender() [0xd0a06c]
/usr/lib64/libpython3.3.so.1.0(PyEval_EvalFrameEx+0x649f) [0x7f972ea490bf]
/usr/lib64/libpython3.3.so.1.0(PyEval_EvalCodeEx+0x803) [0x7f972ea4aad3]
/usr/lib64/libpython3.3.so.1.0(+0x87c8f) [0x7f972e9c2c8f]
/usr/lib64/libpython3.3.so.1.0(PyObject_Call+0x7a) [0x7f972e99c45a]
/usr/lib64/libpython3.3.so.1.0(+0x74e0d) [0x7f972e9afe0d]
/usr/lib64/libpython3.3.so.1.0(PyObject_Call+0x7a) [0x7f972e99c45a]
/usr/lib64/libpython3.3.so.1.0(+0xb3f6b) [0x7f972e9eef6b]
/usr/lib64/libpython3.3.so.1.0(PyObject_Call+0x7a) [0x7f972e99c45a]
/usr/lib64/libpython3.3.so.1.0(PyEval_EvalFrameEx+0x31dc) [0x7f972ea45dfc]
/usr/lib64/libpython3.3.so.1.0(PyEval_EvalCodeEx+0x803) [0x7f972ea4aad3]                                                                                                    
/usr/lib64/libpython3.3.so.1.0(+0x87c8f) [0x7f972e9c2c8f]                                                                                                                   
/usr/lib64/libpython3.3.so.1.0(PyObject_Call+0x7a) [0x7f972e99c45a]                                                                                                         
blender(bpy_app_generic_callback+0x8c) [0xd0778c]                                                                                                                           
blender(BLI_callback_exec+0x2d) [0x10b2abd]                                                                                                                                 
blender(main+0xbd7) [0x95ef97]                                                                                                                                              
/lib64/libc.so.6(__libc_start_main+0xf5) [0x7f97296c3c45]                                                                                                                   
blender() [0x971c35] 
  1. via a thread with a timer set to 5 second to defer the call -> segfault
    Weird because the default scene is loaded and the context should be ok (??).
    Here is the backtrace as well, almost the same output:
$ cat /tmp/blender.crash.txt 
# Blender 2.70 (sub 0), Commit date: 1970-01-01 00:00, Hash unknown

# backtrace
blender() [0x9730ad]
/lib64/libpthread.so.0(+0x10b10) [0x7f38edde0b10]
blender(BLI_addhead+0x5) [0x10b87e5]
blender(WM_event_add_modal_handler+0x80) [0x97c2f0]
blender(WindowManager_modal_handler_add_call+0xc) [0x150f86c]
blender(RNA_function_call+0x12) [0x141ecb2]
blender() [0xd00259]
/usr/lib64/libpython3.3.so.1.0(PyObject_Call+0x7a) [0x7f38eed4545a]
/usr/lib64/libpython3.3.so.1.0(PyEval_EvalFrameEx+0x31dc) [0x7f38eedeedfc]
/usr/lib64/libpython3.3.so.1.0(PyEval_EvalCodeEx+0x803) [0x7f38eedf3ad3]
/usr/lib64/libpython3.3.so.1.0(+0x87c8f) [0x7f38eed6bc8f]
/usr/lib64/libpython3.3.so.1.0(PyObject_Call+0x7a) [0x7f38eed4545a]
blender() [0xd00ed4]
blender() [0x150f0a3]
blender() [0x979aaa]
blender(WM_operator_call_py+0x6f) [0x97a38f]
blender() [0xd0a06c]
/usr/lib64/libpython3.3.so.1.0(PyEval_EvalFrameEx+0x649f) [0x7f38eedf20bf]
/usr/lib64/libpython3.3.so.1.0(PyEval_EvalCodeEx+0x803) [0x7f38eedf3ad3]
/usr/lib64/libpython3.3.so.1.0(+0x87db0) [0x7f38eed6bdb0]
/usr/lib64/libpython3.3.so.1.0(PyObject_Call+0x7a) [0x7f38eed4545a]
/usr/lib64/libpython3.3.so.1.0(+0x74e0d) [0x7f38eed58e0d]
/usr/lib64/libpython3.3.so.1.0(PyObject_Call+0x7a) [0x7f38eed4545a]
/usr/lib64/libpython3.3.so.1.0(+0xb3f6b) [0x7f38eed97f6b]
/usr/lib64/libpython3.3.so.1.0(PyObject_Call+0x7a) [0x7f38eed4545a]
/usr/lib64/libpython3.3.so.1.0(PyEval_EvalFrameEx+0x2ffc) [0x7f38eedeec1c]
/usr/lib64/libpython3.3.so.1.0(PyEval_EvalFrameEx+0x61a2) [0x7f38eedf1dc2]
/usr/lib64/libpython3.3.so.1.0(PyEval_EvalFrameEx+0x61a2) [0x7f38eedf1dc2]
/usr/lib64/libpython3.3.so.1.0(PyEval_EvalCodeEx+0x803) [0x7f38eedf3ad3]
/usr/lib64/libpython3.3.so.1.0(+0x87c8f) [0x7f38eed6bc8f]
/usr/lib64/libpython3.3.so.1.0(PyObject_Call+0x7a) [0x7f38eed4545a]
/usr/lib64/libpython3.3.so.1.0(+0x74e0d) [0x7f38eed58e0d]
/usr/lib64/libpython3.3.so.1.0(PyObject_Call+0x7a) [0x7f38eed4545a]
/usr/lib64/libpython3.3.so.1.0(PyEval_CallObjectWithKeywords+0x47) [0x7f38eedeb957]
/usr/lib64/libpython3.3.so.1.0(+0x142062) [0x7f38eee26062]
/lib64/libpthread.so.0(+0x8f3a) [0x7f38eddd8f3a]
/lib64/libc.so.6(clone+0x6d) [0x7f38e9b35e8d]

I am a little short of new ideas and it is time to ask for some help.

You can find my test addon (which is basically the Modal Timer example found in the templates directory of the text editor) here:
http://www.pasteall.org/52374/python

(you can uncomment one of the 3 lines in the register function to see the 3 ways exposed and their respective pbms)

I get the same problem I think.
I’m very interested if somebody can answer this.

On my side , here is my example code:


#====================== BEGIN GPL LICENSE BLOCK ======================#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
#======================= END GPL LICENSE BLOCK ========================


bl_info = {
    "name": "test script",
    "author": "Jean-Francois Gallant(PyroEvil)",
    "version": (0, 0, 1),
    "blender": (2, 7, 0),
    "location": "Properties > Mesh Tab",
    "description": ("Test script"),
    "warning": "",  # used for warning icon and text in addons panel
    "wiki_url": "http://pyroevil.com/",
    "tracker_url": "http://pyroevil.com/" ,
    "category": "Object"}
    
import bpy
from mathutils import Vector
from time import clock






class TMModal(bpy.types.Operator):
    """Operator which runs its self from a timer"""
    bl_idname = "wm.tm_modal"
    bl_label = "TM modal operator"


    _timer = None


    def modal(self, context, event):
        
        if event.type == 'ESC':
            print('TEST ADDON STOP')
            return self.cancel(context)


        if event.type == 'TIMER':
            print('TEST ADDON RUN')
        return {'PASS_THROUGH'}


    def execute(self, context):
        self._timer = context.window_manager.event_timer_add(0.001, context.window)
        context.window_manager.modal_handler_add(self)
        return {'RUNNING_MODAL'}


    def cancel(self, context):
        context.window_manager.event_timer_remove(self._timer)
        return {'CANCELLED'}






def register():
    bpy.utils.register_class(TMModal)
    pass




def unregister():
    bpy.utils.unregister_class(TMModal)
    pass


print('---before modal call---')
register()
bpy.ops.wm.tm_modal()
print('---after modal call---')

Working fine if executed from a Text_Editor in Blender but has a Addon , I get this error:


found bundled python: F:\stuff\blender-2.71-windows64\2.71\pythonlibpng warning: iCCP: known incorrect sRGB profile
---before modal call---
Traceback (most recent call last):
  File "F:\stuff\blender-2.71-windows64\2.71\scripts\modules\addon_utils.py", line 299, in enable
    mod = __import__(module_name)
  File "F:\stuff\blender-2.71-windows64\2.71\scripts\addons	ension_map\__init__.py", line 76, in <module>
    bpy.ops.wm.tm_modal()
  File "F:\stuff\blender-2.71-windows64\2.71\scripts\modules\bpy\ops.py", line 182, in __call__
    BPyOpsSubModOp._scene_update(context)
  File "F:\stuff\blender-2.71-windows64\2.71\scripts\modules\bpy\ops.py", line 148, in _scene_update
    scene = context.scene
AttributeError: '_RestrictContext' object has no attribute 'scene'


<!> event has invalid window

Somebody know ?

background info:
http://wiki.blender.org/index.php/Extensions:2.6/Py/API_Changes#Restricted_Context

You can use a hack, but it’s not recommended: Use a scene update app handler and let it remove itself (so its callback function is called exactly once), and in that callback, also call your modal operator or do other things.

Thanx CoDEmanX !

So if I understand , it’s impossible to have a addon iniatialized by itself. The good way to do it it’s a button that the user need to push initialize the addon.

right ?

If yes , user push it just one time or everytime ? Each time it’s open a new blend file or everytime he start blender ? On me side the modal operator need to run all time to get good result , during rendering and ideally when you launch it with commandline too.

EDIT: Finally handlers is a good things I learn today ! I use modal operator to detect frame change but I think it’s would be better to use frame_change handlers to do the same things.

Just a quick answer to say that I solved the issue with some trickery.

@persistent
def my_handler2(scene):
    START_YOUR_OPERATOR_HERE
    bpy.app.handlers.frame_change_post.remove(my_handler2)
 
@persistent
def my_handler(scene):
    bpy.app.handlers.frame_change_post.append(my_handler2)
    bpy.context.scene.frame_current=bpy.context.scene.frame_current
    bpy.app.handlers.scene_update_post.remove(my_handler)

#Put this at the end in the register() function
def register():
    bpy.utils.register_module(__name__)         
    bpy.app.handlers.scene_update_post.append(my_handler)

Replace the line START_YOUR_OPERATOR_HERE with the operator you want to run as soon the user starts Blender. It works well with a modal timer for instance.

The trick uses 2 handlers serially connected. A “false” frame change event (staying in fact at the current frame) triggers the second handler where you can place your code “safely”.

As I said: it’s a hack and may break in future versions. It’s the only way at the moment however.