Module sverchok.core.handlers
Expand source code
import bpy
from bpy.app.handlers import persistent
import sverchok
from sverchok import old_nodes
from sverchok import data_structure
import sverchok.core.events as ev
import sverchok.core.tasks as ts
from sverchok.core.event_system import handle_event
from sverchok.core.socket_data import clear_all_socket_cache
from sverchok.ui import bgl_callback_nodeview, bgl_callback_3dview
from sverchok.utils.handle_blender_data import BlTrees
from sverchok.utils.sv_logging import catch_log_error, TextBufferHandler, sv_logger
import sverchok.settings as settings
_state = {'frame': None}
def sverchok_trees():
    for ng in bpy.data.node_groups:
        if ng.bl_idname == 'SverchCustomTreeType':
            yield ng
def has_frame_changed(scene):
    last_frame = _state['frame']
    _state['frame'] = scene.frame_current
    return not last_frame == scene.frame_current
#
#  app.handlers.undo_post and app.handlers.undo_pre are necessary to help remove stale
#  draw callbacks (bgl / gpu / blf). F.ex the rightlick menu item "attach viewer draw"
#  will invoke a number of commands as one event, if you undo that event (ctrl+z) then
#  there is never a point where the node can ask "am i connected to anything, do i need
#  to stop drawing?". When the Undo event causes a node to be removed, its node.free function
#  is not called. (maybe it should be...)
#
#  If at any time the undo system is fixed and does call "node.free()" when a node is removed
#  after ctrl+z. then these two functions and handlers are no longer needed.
@persistent
def sv_handler_undo_pre(scene):
    from sverchok.core import undo_handler_node_count
    for ng in sverchok_trees():
        undo_handler_node_count['sv_groups'] += len(ng.nodes)
@persistent
def sv_handler_undo_post(scene):
    # It also can be called during work of Blender operators - https://developer.blender.org/T89546
    # this function appears to be hoisted into an environment that does not have the same locals()
    # hence this dict must be imported. (jan 2019)
    from sverchok.core import undo_handler_node_count
    num_to_test_against = 0
    for ng in sverchok_trees():
        num_to_test_against += len(ng.nodes)
    if undo_handler_node_count['sv_groups'] != num_to_test_against:
        sv_logger.debug('looks like a node was removed, cleaning')
        sv_clean(scene)
    undo_handler_node_count['sv_groups'] = 0
    handle_event(ev.UndoEvent())
@persistent
def sv_update_handler(scene):
    """
    Update sverchok node groups on frame change events.
    Jump from one frame to another: has_frame_changed=True, is_animation_playing=False
    Scrubbing variant 1: has_frame_changed=True, is_animation_playing=True
    Scrubbing variant 2(stop): has_frame_changed=True, is_animation_playing=False
    Scrubbing variant 3: has_frame_changed=False, is_animation_playing=True
    Scrubbing variant 4(stop): has_frame_changed=False, is_animation_playing=False
    Playing animation: has_frame_changed=True, is_animation_playing=True
    Playing animation(stop): has_frame_changed=False, is_animation_playing=False
    """
    if bpy.context.screen is None:  # rendering
        is_playing = False  # should be False to update UI
        is_frame_changed = True
    else:
        is_playing = bpy.context.screen.is_animation_playing
        is_frame_changed = has_frame_changed(scene)
    # print(f"Frame changed: {is_frame_changed}, Animation is playing: {is_playing}")
    for ng in sverchok_trees():  # Comparatively small overhead with 200 trees in a file
        with catch_log_error():
            ng.process_ani(is_frame_changed, is_playing)
@persistent
def sv_scene_change_handler(scene):
    """
    On depsgraph update (pre)
    """
    # When the Play Animation is on this trigger is executed once. Such event
    # should be suppressed because it repeats animation trigger. When Play
    # animation is on and user changes something in the scene this trigger is
    # only called if frame rate is equal to maximum.
    if bpy.context.screen.is_animation_playing:
        return
    # scene handler can be triggered even when new node or its property
    # is changed what causes redundant updates. Such events are created first,
    # so it's possible to check them in the tasks object.
    # also be aware that updates generate scene events by their selves and
    # they have mechanism to avoid them
    if ts.tasks:
        return
    for ng in BlTrees().sv_main_trees:
        ng.scene_update()
@persistent
def sv_clean(scene):
    """
    Cleanup callbacks, clean dicts.
    """
    bgl_callback_nodeview.callback_disable_all()
    bgl_callback_3dview.callback_disable_all()
    data_structure.sv_Vars = {}
    data_structure.temp_handle = {}
@persistent
def sv_pre_load(scene):
    """
    This method is called whenever new file is opening
    THe update order is next:
    1. pre_load handler
    2. update methods of trees in a file
    3. post_load handler
    4. evaluate trees from main tree handler
    """
    clear_all_socket_cache()
    sv_clean(scene)
    handle_event(ev.FileEvent())
@persistent
def sv_post_load(scene):
    """
    Upgrade nodes, apply preferences and do an update.
    THe update order is next:
    1. pre_load handler
    2. update methods of trees in a file
    3. post_load handler
    4. evaluate trees from main tree handler
    post_load handler is also called when Blender is first ran
    The method should initialize Sverchok parts which are required by loaded tree
    """
    TextBufferHandler.add_to_main_logger()
    from sverchok import node_tree, settings
    # ensure current nodeview view scale / location parameters reflect users' system settings
    node_tree.SverchCustomTree.update_gl_scale_info(None, "sv_post_load")
    # register and mark old and dependent nodes
    with catch_log_error():
        if any(not n.is_registered_node_type() for ng in BlTrees().sv_trees for n in ng.nodes):
            old_nodes.register_all()
        old_nodes.mark_all()
    with catch_log_error():
        settings.apply_theme_if_necessary()
    # when a file is opened as a startup file update method of its trees is not called (Blender inconsistency??)
    for tree in BlTrees().sv_main_trees:
        tree.update()
def set_frame_change(mode):
    post = bpy.app.handlers.frame_change_post
    pre = bpy.app.handlers.frame_change_pre
    # remove all
    if sv_update_handler in post:
        post.remove(sv_update_handler)
    if sv_update_handler in pre:
        pre.remove(sv_update_handler)
    # apply the right one
    if mode == "POST":
        post.append(sv_update_handler)
    elif mode == "PRE":
        pre.append(sv_update_handler)
def update_frame_change_mode():
    from sverchok import settings
    mode = settings.get_param("frame_change_mode", "POST")
    set_frame_change(mode)
@persistent
def save_pre_handler(scene):
    addon = bpy.context.preferences.addons.get(sverchok.__name__)
    prefs = addon.preferences
    if prefs.log_to_buffer_clean:
        for handler in sv_logger.handlers:
            if hasattr(handler, 'clear'):
                handler.clear()
handler_dict = {
    'undo_pre': sv_handler_undo_pre,
    'undo_post': sv_handler_undo_post,
    'load_pre': sv_pre_load,
    'load_post': sv_post_load,
    'depsgraph_update_pre': sv_scene_change_handler,
    'save_pre': save_pre_handler,
}
@persistent
def call_user_functions_on_post_load_event(scene):
    for function in data_structure.post_load_call.registered_functions:
        function()
def app_handler_ops(append=None, remove=None):
    """ append or remove multiple items to specific bpy.app.handlers """
    (operation, handler_dict) = ('append', append) if append else ('remove', remove)
    for handler_name, handler_function in handler_dict.items():
        handler = getattr(bpy.app.handlers, handler_name)
        getattr(handler, operation)(handler_function)
settings.set_frame_change = set_frame_change
def register():
    app_handler_ops(append=handler_dict)
    update_frame_change_mode()
    bpy.app.handlers.load_post.append(call_user_functions_on_post_load_event)
def unregister():
    app_handler_ops(remove=handler_dict)
    set_frame_change(None)
    bpy.app.handlers.load_post.remove(call_user_functions_on_post_load_event)Functions
- def app_handler_ops(append=None, remove=None)
- 
append or remove multiple items to specific bpy.app.handlers Expand source codedef app_handler_ops(append=None, remove=None): """ append or remove multiple items to specific bpy.app.handlers """ (operation, handler_dict) = ('append', append) if append else ('remove', remove) for handler_name, handler_function in handler_dict.items(): handler = getattr(bpy.app.handlers, handler_name) getattr(handler, operation)(handler_function)
- def call_user_functions_on_post_load_event(scene)
- 
Expand source code@persistent def call_user_functions_on_post_load_event(scene): for function in data_structure.post_load_call.registered_functions: function()
- def has_frame_changed(scene)
- 
Expand source codedef has_frame_changed(scene): last_frame = _state['frame'] _state['frame'] = scene.frame_current return not last_frame == scene.frame_current
- def register()
- 
Expand source codedef register(): app_handler_ops(append=handler_dict) update_frame_change_mode() bpy.app.handlers.load_post.append(call_user_functions_on_post_load_event)
- def save_pre_handler(scene)
- 
Expand source code@persistent def save_pre_handler(scene): addon = bpy.context.preferences.addons.get(sverchok.__name__) prefs = addon.preferences if prefs.log_to_buffer_clean: for handler in sv_logger.handlers: if hasattr(handler, 'clear'): handler.clear()
- def set_frame_change(mode)
- 
Expand source codedef set_frame_change(mode): post = bpy.app.handlers.frame_change_post pre = bpy.app.handlers.frame_change_pre # remove all if sv_update_handler in post: post.remove(sv_update_handler) if sv_update_handler in pre: pre.remove(sv_update_handler) # apply the right one if mode == "POST": post.append(sv_update_handler) elif mode == "PRE": pre.append(sv_update_handler)
- def sv_clean(scene)
- 
Cleanup callbacks, clean dicts. Expand source code@persistent def sv_clean(scene): """ Cleanup callbacks, clean dicts. """ bgl_callback_nodeview.callback_disable_all() bgl_callback_3dview.callback_disable_all() data_structure.sv_Vars = {} data_structure.temp_handle = {}
- def sv_handler_undo_post(scene)
- 
Expand source code@persistent def sv_handler_undo_post(scene): # It also can be called during work of Blender operators - https://developer.blender.org/T89546 # this function appears to be hoisted into an environment that does not have the same locals() # hence this dict must be imported. (jan 2019) from sverchok.core import undo_handler_node_count num_to_test_against = 0 for ng in sverchok_trees(): num_to_test_against += len(ng.nodes) if undo_handler_node_count['sv_groups'] != num_to_test_against: sv_logger.debug('looks like a node was removed, cleaning') sv_clean(scene) undo_handler_node_count['sv_groups'] = 0 handle_event(ev.UndoEvent())
- def sv_handler_undo_pre(scene)
- 
Expand source code@persistent def sv_handler_undo_pre(scene): from sverchok.core import undo_handler_node_count for ng in sverchok_trees(): undo_handler_node_count['sv_groups'] += len(ng.nodes)
- def sv_post_load(scene)
- 
Upgrade nodes, apply preferences and do an update. THe update order is next: 1. pre_load handler 2. update methods of trees in a file 3. post_load handler 4. evaluate trees from main tree handler post_load handler is also called when Blender is first ran The method should initialize Sverchok parts which are required by loaded tree Expand source code@persistent def sv_post_load(scene): """ Upgrade nodes, apply preferences and do an update. THe update order is next: 1. pre_load handler 2. update methods of trees in a file 3. post_load handler 4. evaluate trees from main tree handler post_load handler is also called when Blender is first ran The method should initialize Sverchok parts which are required by loaded tree """ TextBufferHandler.add_to_main_logger() from sverchok import node_tree, settings # ensure current nodeview view scale / location parameters reflect users' system settings node_tree.SverchCustomTree.update_gl_scale_info(None, "sv_post_load") # register and mark old and dependent nodes with catch_log_error(): if any(not n.is_registered_node_type() for ng in BlTrees().sv_trees for n in ng.nodes): old_nodes.register_all() old_nodes.mark_all() with catch_log_error(): settings.apply_theme_if_necessary() # when a file is opened as a startup file update method of its trees is not called (Blender inconsistency??) for tree in BlTrees().sv_main_trees: tree.update()
- def sv_pre_load(scene)
- 
This method is called whenever new file is opening THe update order is next: 1. pre_load handler 2. update methods of trees in a file 3. post_load handler 4. evaluate trees from main tree handler Expand source code@persistent def sv_pre_load(scene): """ This method is called whenever new file is opening THe update order is next: 1. pre_load handler 2. update methods of trees in a file 3. post_load handler 4. evaluate trees from main tree handler """ clear_all_socket_cache() sv_clean(scene) handle_event(ev.FileEvent())
- def sv_scene_change_handler(scene)
- 
On depsgraph update (pre) Expand source code@persistent def sv_scene_change_handler(scene): """ On depsgraph update (pre) """ # When the Play Animation is on this trigger is executed once. Such event # should be suppressed because it repeats animation trigger. When Play # animation is on and user changes something in the scene this trigger is # only called if frame rate is equal to maximum. if bpy.context.screen.is_animation_playing: return # scene handler can be triggered even when new node or its property # is changed what causes redundant updates. Such events are created first, # so it's possible to check them in the tasks object. # also be aware that updates generate scene events by their selves and # they have mechanism to avoid them if ts.tasks: return for ng in BlTrees().sv_main_trees: ng.scene_update()
- def sv_update_handler(scene)
- 
Update sverchok node groups on frame change events. Jump from one frame to another: has_frame_changed=True, is_animation_playing=False Scrubbing variant 1: has_frame_changed=True, is_animation_playing=True Scrubbing variant 2(stop): has_frame_changed=True, is_animation_playing=False Scrubbing variant 3: has_frame_changed=False, is_animation_playing=True Scrubbing variant 4(stop): has_frame_changed=False, is_animation_playing=False Playing animation: has_frame_changed=True, is_animation_playing=True Playing animation(stop): has_frame_changed=False, is_animation_playing=False Expand source code@persistent def sv_update_handler(scene): """ Update sverchok node groups on frame change events. Jump from one frame to another: has_frame_changed=True, is_animation_playing=False Scrubbing variant 1: has_frame_changed=True, is_animation_playing=True Scrubbing variant 2(stop): has_frame_changed=True, is_animation_playing=False Scrubbing variant 3: has_frame_changed=False, is_animation_playing=True Scrubbing variant 4(stop): has_frame_changed=False, is_animation_playing=False Playing animation: has_frame_changed=True, is_animation_playing=True Playing animation(stop): has_frame_changed=False, is_animation_playing=False """ if bpy.context.screen is None: # rendering is_playing = False # should be False to update UI is_frame_changed = True else: is_playing = bpy.context.screen.is_animation_playing is_frame_changed = has_frame_changed(scene) # print(f"Frame changed: {is_frame_changed}, Animation is playing: {is_playing}") for ng in sverchok_trees(): # Comparatively small overhead with 200 trees in a file with catch_log_error(): ng.process_ani(is_frame_changed, is_playing)
- def sverchok_trees()
- 
Expand source codedef sverchok_trees(): for ng in bpy.data.node_groups: if ng.bl_idname == 'SverchCustomTreeType': yield ng
- def unregister()
- 
Expand source codedef unregister(): app_handler_ops(remove=handler_dict) set_frame_change(None) bpy.app.handlers.load_post.remove(call_user_functions_on_post_load_event)
- def update_frame_change_mode()
- 
Expand source codedef update_frame_change_mode(): from sverchok import settings mode = settings.get_param("frame_change_mode", "POST") set_frame_change(mode)