Community
Fusion API and Scripts
Got a new add-in to share? Need something specialized to be scripted? Ask questions or share what you’ve discovered with the community.
cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Event Handlers are being cached by Fusion... what's wrong?

5 REPLIES 5
Reply
Message 1 of 6
scottmoyse
446 Views, 5 Replies

Event Handlers are being cached by Fusion... what's wrong?

Starting a new thread in response to this from another thread:

 


@BrianEkins wrote:

As far as the documentActivating event being fired twice, I wrote a little test to watch that event and it's only firing one in my test.


@BrianEkins This is happening because the event handlers are being cached every time the debugger reloads the code, it also happens if you stop then run the add-in in Fusion without using the debugger. I've seen it with scripts now as well. To the point where if you run this script... then stop it. You will continue to see all the code running as you execute commands in Fusion. I've looked through the documentation a fair amount of the last few weeks, looking for examples of how to 'cleanup' the event handlers properly, but I can't find anything.... any clean up only refers to cleaning up cmd defs & ui elements.

 

The problem with this is, its a real PITA for debugging... because I'm having to restart Fusion to clear the handler cache to get reliable results since it holds onto code, which either causes conflicts, or shows old results for code which no longer exists in the script/add-in.

 

import adsk.core, traceback

# global set of event handlers to keep them referenced
handlers = []
ui = None
app = None
# Event handler for the documentOpened event.
class MyDocumentOpenedHandler(adsk.core.DocumentEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        eventArgs = adsk.core.DocumentEventArgs.cast(args)

        # Code to react to the event.
        ui.messageBox('In MyDocumentOpenedHandler event handler.')

# Event handler for the documentOpened event.
class MyDocumentOpeningHandler(adsk.core.DocumentEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        eventArgs = adsk.core.DocumentEventArgs.cast(args)

        # Code to react to the event.
        ui.messageBox('In MyDocumentOpeningHandler event handler.')

        
class StartupCompletedHandler(adsk.core.ApplicationEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        try:
           ui.messageBox('Startup completed')
        except:
            if ui:
                ui.messageBox('Startup completed event failed: {}'.format(traceback.format_exc()))

class OnlineStatusChangedHandler(adsk.core.ApplicationEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        try:
           status = 'Online'
           if args.isOffLine:
               status = 'Offline'
           ui.messageBox('Online status changed: ' + status)
        except:
            if ui:
                ui.messageBox('Online status changed event failed: {}'.format(traceback.format_exc()))

# Event handler for the documentCreated event.
class MyDocumentCreatedHandler(adsk.core.DocumentEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        eventArgs = adsk.core.DocumentEventArgs.cast(args)

        # Code to react to the event.
        ui.messageBox('In MyDocumentCreatedHandler event handler.')        

# Event handler for the documentSaved event.
class MyDocumentSavedHandler(adsk.core.DocumentEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        eventArgs = adsk.core.DocumentEventArgs.cast(args)

        app = adsk.core.Application.get()
        doc = app.activeDocument
        cloudFileName = doc.dataFile.name
        # Code to react to the event.
        ui.messageBox('In MyDocumentSavedHandler event handler.' + cloudFileName) 

        
# Event handler for the documentSaving event.
class MyDocumentSavingHandler(adsk.core.DocumentEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        eventArgs = adsk.core.DocumentEventArgs.cast(args)

        # Code to react to the event.
        ui.messageBox('In MyDocumentSavingHandler event handler.')     

# Event handler for the documentSaving event.
class MyDocumentClosingHandler(adsk.core.DocumentEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        eventArgs = adsk.core.DocumentEventArgs.cast(args)

        # Code to react to the event.
        ui.messageBox('In MyDocumentClosingHandler event handler.')     
        
# Event handler for the documentSaving event.
class MyDocumentClosedHandler(adsk.core.DocumentEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        eventArgs = adsk.core.DocumentEventArgs.cast(args)

        # Code to react to the event.
        ui.messageBox('In MyDocumentClosedHandler event handler.')     

# Event handler for the documentDeactivated event.
class MyDocumentDeactivatedHandler(adsk.core.DocumentEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        eventArgs = adsk.core.DocumentEventArgs.cast(args)

        # Code to react to the event.
        ui.messageBox('In MyDocumentDeactivatedHandler event handler.\ndocument: {}'.format(eventArgs.document.name))    

# Event handler for the documentActivated event.
class MyDocumentActivatedHandler(adsk.core.DocumentEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        eventArgs = adsk.core.DocumentEventArgs.cast(args)

        # Code to react to the event.
        ui.messageBox('In MyDocumentActivatedHandler event handler.\ndocument: {}'.format(eventArgs.document.name))    

# Event handler for the documentDeactivating event.
class MyDocumentDeactivatingHandler(adsk.core.DocumentEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        eventArgs = adsk.core.DocumentEventArgs.cast(args)

        # Code to react to the event.
        ui.messageBox('In MyDocumentDeactivatingHandler event handler.\ndocument: {}'.format(eventArgs.document.name))    

# Event handler for the documentActivating event.
class MyDocumentActivatingHandler(adsk.core.DocumentEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        eventArgs = adsk.core.DocumentEventArgs.cast(args)
        
        # Code to react to the event.
        ui.messageBox('In MyDocumentActivatingHandler event handler.\ndocument: {}'.format(eventArgs.document.name))    

# Event handler for the commandStarting event.
class MyCommandStartingHandler(adsk.core.ApplicationCommandEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        eventArgs = adsk.core.ApplicationCommandEventArgs.cast(args)

        # Code to react to the event.
        ui.messageBox('In MyCommandStartingHandler event handler.')

def run(context):
    try:
        # global app
        app = adsk.core.Application.get()
        global ui
        ui = app.userInterface
        handlers.clear()
        adsk.autoTerminate(False)

        onStartupCompleted = StartupCompletedHandler()
        app.startupCompleted.add(onStartupCompleted)
        handlers.append(onStartupCompleted)
        
        onOnlineStatusChanged = OnlineStatusChangedHandler()
        app.onlineStatusChanged.add(onOnlineStatusChanged)
        handlers.append(onOnlineStatusChanged)
        
        onDocumentCreated = MyDocumentCreatedHandler()
        app.documentCreated.add(onDocumentCreated)
        handlers.append(onDocumentCreated)
        
        onDocumentSaved = MyDocumentSavedHandler()
        app.documentSaved.add(onDocumentSaved)
        handlers.append(onDocumentSaved)
        
        onDocumentSaving = MyDocumentSavingHandler()
        app.documentSaving.add(onDocumentSaving)
        handlers.append(onDocumentSaving)
     
        onDocumentOpened = MyDocumentOpenedHandler()
        app.documentOpened.add(onDocumentOpened)
        handlers.append(onDocumentOpened)   
        
        onDocumentOpening = MyDocumentOpeningHandler()
        app.documentOpening.add(onDocumentOpening)
        handlers.append(onDocumentOpening)    
        
        onDocumentClosing = MyDocumentClosingHandler()
        app.documentClosing.add(onDocumentClosing)
        handlers.append(onDocumentClosing)   
        
        onDocumentClosed = MyDocumentClosedHandler()
        app.documentClosed.add(onDocumentClosed)
        handlers.append(onDocumentClosed)           
        
        onDocumentDeactivated = MyDocumentDeactivatedHandler()
        app.documentDeactivated.add(onDocumentDeactivated)
        handlers.append(onDocumentDeactivated)    
        
        onDocumentActivated = MyDocumentActivatedHandler()
        app.documentActivated.add(onDocumentActivated)
        handlers.append(onDocumentActivated)
        
        onDocumentDeactivating = MyDocumentDeactivatingHandler()
        app.documentDeactivating.add(onDocumentDeactivating)
        handlers.append(onDocumentDeactivating)   
        
        onDocumentActivating = MyDocumentActivatingHandler()
        app.documentActivating.add(onDocumentActivating)
        handlers.append(onDocumentActivating)   

        # "userInterface_var" is a variable referencing a UserInterface object.
        onCommandStarting = MyCommandStartingHandler()
        ui.commandStarting.add(onCommandStarting)
        handlers.append(onCommandStarting)
    except:
        if ui:
            ui.messageBox('AddIn Start Failed:\n{}'.format(traceback.format_exc()))

 


Scott Moyse
Did you find this post helpful? Feel free to Like this post.
Did your question get successfully answered? Then click on the ACCEPT SOLUTION button.


EESignature


Design & Manufacturing Technical Services Manager at Cadpro New Zealand

Co-founder of the Grumpy Sloth full aluminium billet mechanical keyboard project

5 REPLIES 5
Message 2 of 6
JesusFreke
in reply to: scottmoyse

What's worked for me is to unregister the event before registering it.

 

try:
  app().unregisterCustomEvent(CUSTOM_EVENT)
except Exception:
  pass

event = app().registerCustomEvent(CUSTOM_EVENT)
event.add(CustomEventHandler())

 

Message 3 of 6
JesusFreke
in reply to: JesusFreke

Just read your post more carefully, and realized you're not using custom events, sorry 🙂

 

A couple of approaches come to mind. First, add-ins do have a stop() method that should get called when the addin is stopped and re-run. You could remove any registered handlers from their associated events there.

 

Another approach might be to have each event handler somehow detect if it's still active, and if not, unregister itself. I'm not 100% sure the best way to detect whether it's an old handler though. It seems like you would need some sort of global state that persists across add-in/script restarts. Maybe a dynamically created global module with a specific name?

Message 4 of 6
scottmoyse
in reply to: JesusFreke

Thanks for your comments. What blows me away about this is it seems so fundamental to having a reliable add-in running... Especially if a user has multiple add-ins running. And yet there is stuff all information about how to do it properly in the documentation, or even on this forum from what I've seen so far.

Kind Regards
Scott Moyse
Manufacturing Technical Specialist Autodesk Expert Elite
P: +64 9 302 4028 M: +64 21 055 7775
scott.m@cadpro.co.nz

CADPRO Systems Ltd
Auckland - 527b Rosebank Road, Avondale Auckland
Christchurch - Unit 3, Kendal Avenue, Burnside
New Zealand

Scott Moyse
Did you find this post helpful? Feel free to Like this post.
Did your question get successfully answered? Then click on the ACCEPT SOLUTION button.


EESignature


Design & Manufacturing Technical Services Manager at Cadpro New Zealand

Co-founder of the Grumpy Sloth full aluminium billet mechanical keyboard project

Message 5 of 6
BrianEkins
in reply to: scottmoyse

I created an add-in using your code above and do see a couple of issues.  The first is that in your run function you're calling:

adsk.autoTerminate(False)

This isn't commonly used but is used when you run a script that you want to behave like an add-in.  That is after it completes the run function, it should continue to run.  Typically it's used in a case where the script creates a command and executes it within the run.  The command then runs and when the command is finished it calls the adsk.Terminate() function to unload the script.  This is probably causing some of the behavior you're seeing.  None of that is needed with an add-in because it automatically doesn't terminate but continues to run for the Fusion session and is automatically terminated when it's unloaded from the Scripts and Add-Ins dialog.

 

The other issue I found is that Fusion is firing the command starting event and it fires twice for some commands and only once for others.  I think this is a bug.  I haven't seen multiple firings of other events.

 

Here's a slightly modified version of your code.  The only difference being I removed the terminate call, I changed all of the messsageBox calls to call a function I added which writes to the Fusion text window, and I added a stop function so this can be pasted into a new add-in.

import adsk.core, traceback

# global set of event handlers to keep them referenced
handlers = []
ui = None
app = None
# Event handler for the documentOpened event.
class MyDocumentOpenedHandler(adsk.core.DocumentEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        eventArgs = adsk.core.DocumentEventArgs.cast(args)

        # Code to react to the event.
        DebugPrint('In MyDocumentOpenedHandler event handler.')

# Event handler for the documentOpened event.
class MyDocumentOpeningHandler(adsk.core.DocumentEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        eventArgs = adsk.core.DocumentEventArgs.cast(args)

        # Code to react to the event.
        DebugPrint('In MyDocumentOpeningHandler event handler.')

        
class StartupCompletedHandler(adsk.core.ApplicationEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        try:
           DebugPrint('Startup completed')
        except:
            if ui:
                DebugPrint('Startup completed event failed: {}'.format(traceback.format_exc()))

class OnlineStatusChangedHandler(adsk.core.ApplicationEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        try:
           status = 'Online'
           if args.isOffLine:
               status = 'Offline'
           DebugPrint('Online status changed: ' + status)
        except:
            if ui:
                DebugPrint('Online status changed event failed: {}'.format(traceback.format_exc()))

# Event handler for the documentCreated event.
class MyDocumentCreatedHandler(adsk.core.DocumentEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        eventArgs = adsk.core.DocumentEventArgs.cast(args)

        # Code to react to the event.
        DebugPrint('In MyDocumentCreatedHandler event handler.')        

# Event handler for the documentSaved event.
class MyDocumentSavedHandler(adsk.core.DocumentEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        eventArgs = adsk.core.DocumentEventArgs.cast(args)

        app = adsk.core.Application.get()
        doc = app.activeDocument
        cloudFileName = doc.dataFile.name
        # Code to react to the event.
        DebugPrint('In MyDocumentSavedHandler event handler.' + cloudFileName) 

        
# Event handler for the documentSaving event.
class MyDocumentSavingHandler(adsk.core.DocumentEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        eventArgs = adsk.core.DocumentEventArgs.cast(args)

        # Code to react to the event.
        DebugPrint('In MyDocumentSavingHandler event handler.')     

# Event handler for the documentSaving event.
class MyDocumentClosingHandler(adsk.core.DocumentEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        eventArgs = adsk.core.DocumentEventArgs.cast(args)

        # Code to react to the event.
        DebugPrint('In MyDocumentClosingHandler event handler.')     
        
# Event handler for the documentSaving event.
class MyDocumentClosedHandler(adsk.core.DocumentEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        eventArgs = adsk.core.DocumentEventArgs.cast(args)

        # Code to react to the event.
        DebugPrint('In MyDocumentClosedHandler event handler.')     

# Event handler for the documentDeactivated event.
class MyDocumentDeactivatedHandler(adsk.core.DocumentEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        eventArgs = adsk.core.DocumentEventArgs.cast(args)

        # Code to react to the event.
        DebugPrint('In MyDocumentDeactivatedHandler event handler. Document: {}'.format(eventArgs.document.name))    

# Event handler for the documentActivated event.
class MyDocumentActivatedHandler(adsk.core.DocumentEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        eventArgs = adsk.core.DocumentEventArgs.cast(args)

        # Code to react to the event.
        DebugPrint('In MyDocumentActivatedHandler event handler. Document: {}'.format(eventArgs.document.name))    

# Event handler for the documentDeactivating event.
class MyDocumentDeactivatingHandler(adsk.core.DocumentEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        eventArgs = adsk.core.DocumentEventArgs.cast(args)

        # Code to react to the event.
        DebugPrint('In MyDocumentDeactivatingHandler event handler. Document: {}'.format(eventArgs.document.name))    

# Event handler for the documentActivating event.
class MyDocumentActivatingHandler(adsk.core.DocumentEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        eventArgs = adsk.core.DocumentEventArgs.cast(args)
        
        # Code to react to the event.
        DebugPrint('In MyDocumentActivatingHandler event handler. Document: {}'.format(eventArgs.document.name))    

# Event handler for the commandStarting event.
class MyCommandStartingHandler(adsk.core.ApplicationCommandEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        eventArgs = adsk.core.ApplicationCommandEventArgs.cast(args)

        # Code to react to the event.
        DebugPrint('In MyCommandStartingHandler event handler. Command: {}'.format(eventArgs.commandId))

def run(context):
    try:
        # global app
        app = adsk.core.Application.get()
        global ui
        ui = app.userInterface
        DebugPrint('In run')

        handlers.clear()

        onStartupCompleted = StartupCompletedHandler()
        app.startupCompleted.add(onStartupCompleted)
        handlers.append(onStartupCompleted)
        
        onOnlineStatusChanged = OnlineStatusChangedHandler()
        app.onlineStatusChanged.add(onOnlineStatusChanged)
        handlers.append(onOnlineStatusChanged)
        
        onDocumentCreated = MyDocumentCreatedHandler()
        app.documentCreated.add(onDocumentCreated)
        handlers.append(onDocumentCreated)
        
        onDocumentSaved = MyDocumentSavedHandler()
        app.documentSaved.add(onDocumentSaved)
        handlers.append(onDocumentSaved)
        
        onDocumentSaving = MyDocumentSavingHandler()
        app.documentSaving.add(onDocumentSaving)
        handlers.append(onDocumentSaving)
     
        onDocumentOpened = MyDocumentOpenedHandler()
        app.documentOpened.add(onDocumentOpened)
        handlers.append(onDocumentOpened)   
        
        onDocumentOpening = MyDocumentOpeningHandler()
        app.documentOpening.add(onDocumentOpening)
        handlers.append(onDocumentOpening)    
        
        onDocumentClosing = MyDocumentClosingHandler()
        app.documentClosing.add(onDocumentClosing)
        handlers.append(onDocumentClosing)   
        
        onDocumentClosed = MyDocumentClosedHandler()
        app.documentClosed.add(onDocumentClosed)
        handlers.append(onDocumentClosed)           
        
        onDocumentDeactivated = MyDocumentDeactivatedHandler()
        app.documentDeactivated.add(onDocumentDeactivated)
        handlers.append(onDocumentDeactivated)    
        
        onDocumentActivated = MyDocumentActivatedHandler()
        app.documentActivated.add(onDocumentActivated)
        handlers.append(onDocumentActivated)
        
        onDocumentDeactivating = MyDocumentDeactivatingHandler()
        app.documentDeactivating.add(onDocumentDeactivating)
        handlers.append(onDocumentDeactivating)   
        
        onDocumentActivating = MyDocumentActivatingHandler()
        app.documentActivating.add(onDocumentActivating)
        handlers.append(onDocumentActivating)   

        # "userInterface_var" is a variable referencing a UserInterface object.
        onCommandStarting = MyCommandStartingHandler()
        ui.commandStarting.add(onCommandStarting)
        handlers.append(onCommandStarting)
    except:
        if ui:
            ui.messageBox('AddIn Start Failed:\n{}'.format(traceback.format_exc()))


def stop(context):
    try:
        DebugPrint('Stopping add-in.')     

    except:
        if ui:
            ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

# Get the palette that represents the TEXT COMMANDS window. 
def DebugPrint(message):
    textPalette = ui.palettes.itemById('TextCommands')
 
    # Make sure the palette is visible. 
    if not textPalette.isVisible: 
        textPalette.isVisible = True
        
    # Write some text. 
    textPalette.writeText(message)

 

---------------------------------------------------------------
Brian Ekins
Inventor and Fusion 360 API Expert
Website/Blog: https://EkinsSolutions.com
Message 6 of 6
scottmoyse
in reply to: BrianEkins

Thanks for your detailed response @BrianEkins I definitely learnt a few things there. 

 

I also noticed the selectCommand was firing twice in some places, and not others. When you notice these things which you think might be bugs, are you submitting them to the API team?

The PLM360SaveCommand event is also an interesting one, it ONLY fires on first save using the Save button, but I couldn't see it firing one first save as a result of closing the design.

I was aware of the consequences of using:

 

 

adsk.autoTerminate(False)

 

 

I really only used it so I could get the script to continue running since its an easy way for me to check event handler firing on the fly. I can understand how this might make the code continue to run after the script has been stopped in the Add-Ins & Scripts dialog. This code was originally published in the help documentation as an Add-In so I did this just so I could run it as a script.

 

However... I don't think the code you've shared will stop the handlers from being cached by Fusion.  If you load the code you shared above and debug it with VS Code, then reload the code a few times (using the green circular arrow button). You will start to see the handlers stacking up and multiple instances of handler code will get called for each event firing its bound to. After some discussions today, it looks to me that in the stop function developers should be doing this for every handler that gets added to an event:

 

 

app.EventTypeObject.remove(myHandlerClassInstance)

 

 

This isn't something I have seen covered in any of the documentation. I haven't got around to testing it yet, but I'm hoping it will resolve the issue... since it makes debugging a real PITA... you can end up with many copies of the same handler, with different code... at times it makes it look like it gets variables mixed up between different handler class instances and it just gets really messy and confusing. As a result I'm currently restarting Fusion every few minutes to get reliable behaviour when debugging. Which gets infuriating when trying to iterate quickly on code changes.


Scott Moyse
Did you find this post helpful? Feel free to Like this post.
Did your question get successfully answered? Then click on the ACCEPT SOLUTION button.


EESignature


Design & Manufacturing Technical Services Manager at Cadpro New Zealand

Co-founder of the Grumpy Sloth full aluminium billet mechanical keyboard project

Can't find what you're looking for? Ask the community or share your knowledge.

Post to forums  

Autodesk DevCon in Munich May 28-29th


Autodesk Design & Make Report