Python restart Add-In after self updating

Python restart Add-In after self updating

Anonymous
Not applicable
2,599 Views
9 Replies
Message 1 of 10

Python restart Add-In after self updating

Anonymous
Not applicable

Hey folks,

 

I wrote an update method for my Add-In which automatically checks my linked github for a newer release and pulls its contents into the same folder (overwrites itself). After that it installs all used packages into the ./modules folder.

 

Now my question is:

How can I restart my application within Fusion. I already tried with 

os.exec*()
# and with
subprocess.run()
subprocess.Popen() 

 

A second question: if the folder is overwritten will it still be in Fusion's Startup next time after restarting Fusion?

 

Any hints appreciated very much!

 

Greeting Fabi

Accepted solutions (1)
2,600 Views
9 Replies
Replies (9)
Message 2 of 10

JesusFreke
Advocate
Advocate

Fusion only has a single python instance that all add-ons, etc. run in. You can't restart it, per say, but you could try reloading the module. I don't have my scripts handy to pull out an example, but take a look at the importlib.reload function.  One tricky part is that you have to have an instance of the module in order to reload it. And the way fusion loads addins as modules is a bit funky. But if your "reload from github" code is in the same module as the module you're reloading, you might be able to use the __module__ property.

Message 3 of 10

Anonymous
Not applicable

Thank you for your answer so far.

 

I tried reloading it with 

print(str(__name__))
# __main__C%3A%2FUsers%2FFabi%2FAppData%2FRoaming%2FAutodesk%2FAutodesk%20Fusion%20360%2FAPI%2FScripts%2FReloadModule%2FReloadModule_py

print(str(sys.modules[__name__]))
# <module '__main__C%3A%2FUsers%2FFabi%2FAppData%2FRoaming%2FAutodesk%2FAutodesk%20Fusion%20360%2FAPI%2FScripts%2FReloadModule%2FReloadModule_py' from 'C:/Users/Fabi/AppData/Roaming/Autodesk/Autodesk Fusion 360/API/Scripts/ReloadModule/ReloadModule.py'>

importlib.reload(sys.modules[__name__])

Capture123123.PNG

 

Which might be related to the following error https://stackoverflow.com/questions/27272574/imp-reload-nonetype-object-has-no-attribute-name

 

Any ideas?

Message 4 of 10

JesusFreke
Advocate
Advocate

Yeah, nothing comes to mind immediately, sorry 🙂 For me, the next steps would probably be to look at the python source in the importlib module and see if I can figure out what's going on.

Message 5 of 10

BrianEkins
Mentor
Mentor

I wrote an automatic updater for a client and I ended up just displaying a message box notifying the user that an update ocuured and they need to restart Fusion to load the update.

---------------------------------------------------------------
Brian Ekins
Inventor and Fusion 360 API Expert
Website/Blog: https://EkinsSolutions.com
Message 6 of 10

JesusFreke
Advocate
Advocate
Accepted solution

I played around with this a bit, and was getting the same error.

 

I think I came up with an approach that might work for you. Create a bootstrap module that's the main entry point of the addin, and that has the reloading code. But the rest of the addin code is in a module in a subdirectory.

 

Let's say, loader.py, and module/actual_module.py

 

loader.py would look something like this:

 

import adsk.core
import importlib
import inspect
import os
import sys
import traceback

app = adsk.core.Application.get()
ui = app.userInterface

def run(context):
script_path = os.path.abspath(inspect.getfile(inspect.currentframe()))
script_dir = os.path.dirname(script_path)
module_dir = os.path.abspath(os.path.join(script_dir, "module"))
sys.path.append(module_dir)

try:
import actual_module
importlib.reload(actual_module)

actual_module.run(context)
except:
ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))
finally:
del sys.path[module_dir]

def stop(context):
import actual_module
actual_module.stop(context)

def reload():
# You would call this from your github reload functionality, once the updated module is in place
import actual_module
actual_module.stop(None)
importlib.reload(actual_module)
actual_module.run(None)

 

Message 7 of 10

Anonymous
Not applicable

@JesusFreke Thank you so much it is working! That's awesome!!

 

Full code can be found at https://github.com/Bitfroest/contentcenter_fusion360

 

import adsk.core
import importlib
import inspect
import os
import sys
import traceback
import tempfile
import tarfile
import shutil

sys.path.append("./modules")
sys.path.append("./modules/modules")

import pip
import requests

version = "v1.2.1"
_app = None
_ui = None

def versiontuple(v):
    v = v.replace('v', '')
    return tuple(map(int, (v.split("."))))

def install(path, requirementsFileName):
    if hasattr(pip, 'main'):
        with open(requirementsFileName) as f:
            for line in f:
                pip.main(['install', '-U', line, '-t', path, '--ignore-installed', '-q'])
    else:
        with open(requirementsFileName) as f:
            for line in f:
                pip._internal.main(['install', '-U', line, '-t', path, '--ignore-installed', '-q'])


def update(context):
    global version, _ui
    currentFolder = os.path.dirname(os.path.realpath(__file__))
    currentFolder = os.path.join(currentFolder, '')
    # cwd = os.getcwd()

    releasesURI = 'https://api.github.com/repos/Bitfroest/contentcenter_fusion360/releases'

    r = requests.get(releasesURI)

    if r.status_code == 200:
        releases = r.json()
        tag_name = releases[0]['tag_name']
        tarball_url = releases[0]['tarball_url']
        published_at = releases[0]['published_at']
        # _ui.messageBox(str(tag_name))

        if versiontuple(tag_name) > versiontuple(version):
            # _ui.messageBox('Performing Update')
            # Create a local temporary folder
            with tempfile.TemporaryDirectory() as temp:
                tarball = requests.get(tarball_url)
                if tarball.status_code == 200:
                    tempFileName = os.path.join(temp, str(tag_name+'.tar.gz'))
                    tempFile = open(tempFileName, 'wb')
                    tempFile.write(tarball.content)
                    tempFile.close()

                    tar = tarfile.open(tempFileName, "r:gz")

                    folderName = os.path.join(tar.getmembers()[0].name.split('/')[0], '')
                    tar.extractall(path=temp)
                    tar.close()

                    tempDirectory = os.path.join(temp, folderName)

                    # delete all files in directory
                    for file in os.listdir(currentFolder):
                        filePath = os.path.join(currentFolder, file)
                        try:
                            if os.path.isfile(filePath):
                                os.unlink(filePath)
                            elif os.path.isdir(filePath): shutil.rmtree(filePath)
                        except Exception as e:
                            print(e)

                    # delete directory
                    os.rmdir(currentFolder)

                    # copy all extracted contents to add in folder
                    shutil.copytree(tempDirectory, os.path.join(currentFolder, ''))

                    if os.path.isfile(os.path.join(currentFolder, 'requirements.txt')):
                        modulesFolder= os.path.join(os.path.join(currentFolder, 'modules'), 'modules')
                        if not os.path.exists(modulesFolder):
                            os.makedirs(modulesFolder)
                        install(modulesFolder, os.path.join(currentFolder, 'requirements.txt'))

                    if os.path.isfile(os.path.join(os.path.join(currentFolder, 'modules'), 'ContentCenter.py')):
                        # updated and now reload the function
                        try:
                            reload(context)
                        except:
                            _ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))
                            pass
                        _ui.messageBox(str('Updated Add-In Custom Content Center'))

def run(context):
    global _app, _ui
    script_path = os.path.abspath(inspect.getfile(inspect.currentframe()))
    script_dir = os.path.dirname(script_path)
    module_dir = os.path.abspath(os.path.join(script_dir, "modules"))
    sys.path.append(module_dir)

    _app = adsk.core.Application.get()
    _ui = _app.userInterface

    try:
        import ContentCenter
        importlib.reload(ContentCenter)

        ContentCenter.run(context)
        update(context)
    except:
        _ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))
    finally:
        del sys.path[module_dir]

def stop(context):
    import ContentCenter
    ContentCenter.stop(context)

def reload(context):
    # You would call this from your github reload functionality, once the updated module is in place
    import ContentCenter
    ContentCenter.stop(context)
    importlib.reload(ContentCenter)
    ContentCenter.run(context)
Message 8 of 10

JesusFreke
Advocate
Advocate

Awesome! Glad you were able to get it working 🙂

 

I guess the only downside is the loader module can't update itself, so if you make any changes there, it would still require a fusion restart, or the user to manually stop/restart the add-in, which I think reloads it as well.

 

But once the loader code is working, hopefully it wouldn't need to be touched very often.

Message 9 of 10

balunist
Advocate
Advocate

I just noticed this thread and thought I would add what works for me to restart addIn.    You can save or acquire the CommandDefinition object then execute the following when you are ready to restart.

commandDef.execute()

Icarus 

Message 10 of 10

Anonymous
Not applicable

I ended up putting the python packages into the downloadable Zip/ Tar gz aswell.

 

It seems like pip doesnt work on MacOS.

0 Likes