Reload pyRevit as library module and in a hook

Hello pyRevit friends,

After doc-created and doc-opened i want to reconnect network drives and then reload pyRevit so my extensions on the network drives will finally load.

But reloading does not work with the hook script.

I tried to reload pyRevit in a code that is in an ini file in the lib folder.

    def reload():
        try:
            from pyrevit import EXEC_PARAMS
            from pyrevit import script
            from pyrevit import forms
            from pyrevit.loader import sessionmgr
            from pyrevit.loader import sessioninfo

            logger = script.get_logger()
            results = script.get_results()

            # re-load pyrevit session.
            logger.info('Reloading....')
            sessionmgr.reload_pyrevit()
            results.newsession = sessioninfo.get_session_uuid()
        except Exception as e:
            print("An error occurred during reload:", e)

But when I import and call this function in my pushbutton i get this error:

RuntimeError: lost sys.stdout

And I have really no idea what to do with that. Maybe i need to pass some of the imports as variables to the function from my script.py?

Happy about any advice!

As I can´t get this to work in the lib file I tried to reload in the hook file directly, and have a guess, also with no success:

IronPython Traceback:
Traceback (most recent call last):
 File "C:\connect.extension\hooks\connect_doc-created.py", line 23, in <module>
 File "C:\Users\nyk060\AppData\Roaming\pyRevit-Master\pyrevitlib\pyrevit\loader\sessionmgr.py", line 327, in reload_pyrevit
 File "C:\Users\nyk060\AppData\Roaming\pyRevit-Master\pyrevitlib\pyrevit\loader\sessionmgr.py", line 288, in load_session
 File "C:\Users\nyk060\AppData\Roaming\pyRevit-Master\pyrevitlib\pyrevit\loader\sessionmgr.py", line 222, in _new_session
 File "C:\Users\nyk060\AppData\Roaming\pyRevit-Master\pyrevitlib\pyrevit\loader\sessionmgr.py", line 604, in execute_extension_startup_script
SystemError: Object reference not set to an instance of an object.
# -*- coding=utf-8 -*-
#pylint: disable=import-error,invalid-name,broad-except

import connect
from pyrevit import EXEC_PARAMS
from pyrevit import script
from pyrevit.loader import sessionmgr
from pyrevit.loader import sessioninfo
from Autodesk.Revit.UI import TaskDialog

doc = EXEC_PARAMS.event_args.Document
app = __revit__

if not doc.IsFamilyDocument:
    connected = connect.connect()

    if connected:
        logger = script.get_logger()
        results = script.get_results()
    
        # re-load pyrevit session.
        logger.info('Reloading....')
        sessionmgr.reload_pyrevit()
    
        results.newsession = sessioninfo.get_session_uuid()
        taskdialog.show("AFRY","Success: The network drive X: has been connected and the AFRY toolbar has been loaded.")

OK I learned that there is something called

startup.py

I can successful reload the network drives on startup now.
But it seems there is no way to reload pyRevit then, because I can not reload on startup and I can not reload within a hook script. So how can I get my desperately desired extension loading?

Built an Eventhandler that starts the reload as soon as revit is idle, i run it on startup.py
While it works as expected, the start of the reloading triggers this error:

IronPython Traceback:
Traceback (most recent call last):
 File "C:\connect.extension\startup.py", line 5, in <module>
 File "C:\connect.extension\lib\reload\__init__.py", line 68, in reload
 File "C:\connect.extension\lib\reload\__init__.py", line 36, in __init__
 File "C:\connect.extension\lib\reload\__init__.py", line 14, in __init__
 File "C:\connect.extension\lib\reload\__init__.py", line 17, in _register_event
StandardError: Exception has been thrown by the target of an invocation.


Script Executor Traceback:
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> Autodesk.Revit.Exceptions.InvalidOperationException: Can not subscribe to an event during execution of that event!
 at Autodesk.Revit.UI.UIApplication.add_Idling(EventHandler`1 handler)
 --- End of inner exception stack trace ---

Spent the whole day with trying to get rid of this error and now I´m really out of ideas, any suggestions what I can try?

import pyevent
from Autodesk.Revit.UI import UIApplication
from Autodesk.Revit.UI.Events import IdlingEventArgs
from Autodesk.Revit.UI import (TaskDialog, TaskDialogCommonButtons, TaskDialogResult, TaskDialogIcon)

def reload():
    class RevitIdlingEventHandler(object):
        """
        This class handles the Idling event of the Revit application.
        """
        def __init__(self, ui_app):
            self.ui_app = ui_app  # Store it as an instance variable
            self.idling_event, self.trigger_idling_event = pyevent.make_event()
            self._register_event()

        def _register_event(self):
            self.ui_app.Idling += self._on_idling

        def _unregister_event(self):
            self.ui_app.Idling -= self._on_idling

        def _on_idling(self, sender, args):
            # First, unregister the event to prevent it from being triggered again
            self._unregister_event()

            # Now, execute other tasks
            self.trigger_idling_event(self, args)

    class RevitIdleMonitor(object):
        """
        This class creates an instance of the RevitIdlingEventHandler and provides a means 
        to register and unregister this handler with the Revit application.
        """
        def __init__(self, ui_app):
            self.idling_handler = RevitIdlingEventHandler(ui_app)
            self.on_idle = self.idling_handler.idling_event

        def register_handler(self):
            self.idling_handler._register_event()

        def unregister_handler(self):
            self.idling_handler._unregister_event()

    def on_revit_idle(sender, args):

        """
        Function to be executed when Revit goes idle.
        """
        #pylint: disable=import-error,invalid-name,broad-except
        from pyrevit import EXEC_PARAMS
        from pyrevit import script
        from pyrevit import forms
        from pyrevit.loader import sessionmgr
        from pyrevit.loader import sessioninfo

        logger = script.get_logger()
        results = script.get_results()

        # re-load pyrevit session.
        logger.info('Reloading....')

        sessionmgr.reload_pyrevit()

        results.newsession = sessioninfo.get_session_uuid()

    # Initialize the idle monitor and attach the on_revit_idle function to the idling event.
    idle_monitor = RevitIdleMonitor(__revit__)
    idle_monitor.on_idle += on_revit_idle

It looks like you are going down a deep hole :blush:
Why not proceed differently?

  • Have your extension code in a GitHub repo
  • push it when necessary
  • deploy the extension to others with a command line ‘pyrevit extend ui …’ in the pyrevit’ cli
  • use the startup.py to update on startup so that your colleagues get the changes ‘pyrevit update …’

I explained it in a bilt session last year and at the BCS, the content and extension sample is on my github

1 Like

Hello @Jean-Marc,

I think this is not possible without admin rights? If it is, then thats maybe a project for the future, thanks for pointing that out, it´s interresting to see what kind of different workflows are possible.

But for now I really really would like to just get rid of my error! :smiley:

New day, new Event (ApplicationInitialized ), new EventHandler, but still the same error :frowning:

The code works 100% fine. And the Event I´m using should make sure everything is initialized, so why will a reload trigger that error?

def reload():
    import Autodesk
    import clr
    clr.AddReference('System')
    clr.AddReference('RevitAPIUI')
    from Autodesk.Revit import DB
    from Autodesk.Revit.UI import (TaskDialog, TaskDialogCommonButtons, TaskDialogResult, TaskDialogCommandLinkId, TaskDialogIcon)

    def user_message(line1,line2):

        taskDialog = TaskDialog("AFRY")
        taskDialog.MainInstruction = line1
        taskDialog.MainContent = line2
        taskDialog.TitleAutoPrefix = False  
        taskDialog.MainIcon = TaskDialogIcon.TaskDialogIconInformation  
        taskDialog.CommonButtons = TaskDialogCommonButtons.Close #| TaskDialogCommonButtons.Yes 
        #taskDialog.FooterText = "Help"
        return taskDialog.Show()

    def reload_pyrevit():
            #pylint: disable=import-error,invalid-name,broad-except
            from pyrevit import EXEC_PARAMS
            from pyrevit import script
            from pyrevit import forms
            from pyrevit.loader import sessionmgr
            from pyrevit.loader import sessioninfo

            logger = script.get_logger()
            results = script.get_results()

            # re-load pyrevit session.
            logger.info('Reloading....')
            try:
                sessionmgr.reload_pyrevit()
                user_message("Success", "pyRevit has been reloaded.")
            except Exception as e:
                user_message("Error", str(e))

    def app_initialized_handler(sender, args):
        reload_pyrevit()

    # Register the event handler during a separate phase of your script
    def register_event_handler():
        controlled_app = __revit__.Application
        controlled_app.ApplicationInitialized += app_initialized_handler

    # Call the function to register the event handler
    register_event_handler()

After 50 attempts of trying to check if an event has occured afterwards, I now start to understand a little what going on.

Setting and checking a variable does not work:

# Flag to check if event was triggered
event_triggered = False

def event_handler_function(sender, args):
    global event_triggered
    print "triggered"
    event_triggered = True

__revit__.Idling += EventHandler[IdlingEventArgs](event_handler_function)

# Later in your code when you want to check if the event has been triggered
if event_triggered:
    print("The event has been triggered!")
else:
    print("The event has not been triggered yet.")

This is because the script just runs to the end and is over long time before the event happens.

So I added a while loop, can`t tell why that also failed:

event_triggered = False

def event_handler_function(sender, args):
    global event_triggered
    print "triggered"
    event_triggered = True
    __revit__.Idling -= EventHandler[IdlingEventArgs](event_handler_function)

__revit__.Idling += EventHandler[IdlingEventArgs](event_handler_function)

timeout = 10  # in seconds
start_time = time.time()

while not event_triggered:
    if time.time() - start_time > timeout:
        print("Timeout waiting for the event to trigger.")
        break
    time.sleep(0.1)  # sleep for 100 milliseconds

if event_triggered:
    print("The event has been triggered!")
else:
    print("The event has not been triggered.")

Threading Event also failed:


import time
import threading

event_triggered = threading.Event()

def event_handler_function(sender, args):
    print "triggered"
    event_triggered.set()
    __revit__.Idling -= EventHandler[IdlingEventArgs](event_handler_function)

__revit__.Idling += EventHandler[IdlingEventArgs](event_handler_function)

timeout = 10  # in seconds

# The wait method will block until the event is set or the timeout expires
event_triggered.wait(timeout)

if event_triggered.is_set():
    print("The event has been triggered!")
else:
    print("The event has not been triggered or timed out.")

Whatever I do the variable always tells me the the event handler did not run.

So even my idling event gets triggered to infinity, there is no way to verify that it has been running:

OK, I give up the event handler, turns out they get triggered after all my code has finished. I didnt even work to write/read to a file to check if an event has ocured.

Turns out in the startup.py this simple approach works:

import connect
import reload

connected = connect.connect()

if connected:
    reload.reload()

But again with errors :confused:

**ERROR** [pyrevit.loader.uimaker] UI error: Can not create tab: The tab with the input name exists already.
Parameter name: tabName

**ERROR** [pyrevit.loader.uimaker] UI error: Can not create tab: The tab with the input name exists already.
Parameter name: tabName

**ERROR** [pyrevit.loader.uimaker] UI error: Can not create tab: The tab with the input name exists already.
Parameter name: tabName

Got it!

  • Check if network drive is connected
  • If not connected register event handler for Application Initialized Event
  • Connect network drive
  • If connected reload pyRevit

Was planning to suppress the error logger window with:

logger = script.get_logger()
logger.set_quiet_mode()

But for some reason the errors are gone anyway, don´t ask me why.

This was a really hard piece, need a few days off now :woozy_face:

#-*- coding: utf-8 -*-

import clr
clr.AddReference('System')
clr.AddReference('RevitAPIUI')
from System.IO import DriveInfo
from System.Diagnostics import Process
clr.AddReference('System.Management')
import System.Management

from Autodesk.Revit.UI import (
    TaskDialog,
    TaskDialogCommonButtons,
    TaskDialogIcon
)

def is_drive_connected(drive_letter):

    # Checks if a given drive letter corresponds to a connected network drive.

    drives = DriveInfo.GetDrives()
    connected_drives = [drive.Name[0].upper() for drive in drives if drive.DriveType.ToString() == "Network"]
    return drive_letter.upper().strip(":") in connected_drives

def get_all_network_paths():

    # Retrieves all network paths.

    searcher = System.Management.ManagementObjectSearcher("SELECT * FROM Win32_NetworkConnection")
    network_paths = {}
    for item in searcher.Get():
        local_name = item["LocalName"]
        if local_name:
            remote_name = item["RemoteName"]
            network_paths[local_name.lower()] = remote_name
    return network_paths

def user_message(line1, line2):

    # Displays a message to the user.

    taskDialog = TaskDialog("")
    taskDialog.MainInstruction = line1
    taskDialog.MainContent = line2
    taskDialog.TitleAutoPrefix = False  
    taskDialog.MainIcon = TaskDialogIcon.TaskDialogIconInformation  
    taskDialog.CommonButtons = TaskDialogCommonButtons.Close
    return taskDialog.Show()

def connection():

    # Checks connection status for a given drive.

    drive_letter = "X"
    all_paths = get_all_network_paths()
    network_path = all_paths.get(drive_letter.lower() + ":")
    return is_drive_connected(drive_letter) if network_path else False

def connect():

    def connect_network_drive(drive_letter, network_path):

        # Connects to a specific network drive.

        TIMEOUT_IN_MS = 10000  # For example, 10 seconds
        process = Process()
        process.StartInfo.FileName = "net.exe"
        process.StartInfo.Arguments = "use {0}: {1}".format(drive_letter, network_path)
        process.StartInfo.CreateNoWindow = True
        process.StartInfo.UseShellExecute = False
        process.Start()
        # Wait for the timeout period
        process_exited = process.WaitForExit(TIMEOUT_IN_MS)
        
        if not process_exited:  # If the process didn't exit within the timeout period
            process.Kill()  # Forcefully terminate the process
            user_message("Timeout", "DNS server not available, can´t connect to drive X.")

    drive_letter = "X"
    all_paths = get_all_network_paths()
    network_path = all_paths.get(drive_letter.lower() + ":")
    
    try:
        connect_network_drive(drive_letter, network_path)
        if is_drive_connected(drive_letter):
            return True
        else:
            user_message("Fail", "Failed to connect to the network drive X. pyRevit could not be loaded, please reload pyRevit manually.")
            return False
    except Exception as e:
        user_message("Error", str(e))
        return False

def reload():

    # Reload pyRevit.

    from pyrevit.loader import sessionmgr
    try:
        sessionmgr.reload_pyrevit()
        user_message("Success", "Network drive X has been reconnected and pyRevit has been loaded.")
    except Exception as e:
        user_message("Failed", str(e))

def app_initialized_handler(sender, args):
    """
    This function is executed when the application is fully initialized.
    It first unsubscribes itself from the ApplicationInitialized event 
    to avoid being executed again. Then, it tries to connect to the 
    network drive and, if successful, reloads the associated toolbar.
    """
    
    # Unsubscribe from the ApplicationInitialized event to avoid 
    # this handler being triggered multiple times.
    controlled_app.ApplicationInitialized -= app_initialized_handler
    
    # Try to connect to the network drive and reload the toolbar if successful.
    if connect():
        reload()

# Check the status of the network drive connection.
is_drive_connected_status = connection()

if not is_drive_connected_status:
    try:
        # Subscribe to the ApplicationInitialized event.
        controlled_app = __revit__.Application
        controlled_app.ApplicationInitialized += app_initialized_handler
    except Exception as e:
        pass



3 Likes