Proper way to access SGTK in a RV package

Any pointers on how to do this? The only documentation I can find indicates that I should be able to, in a Python script:

import rv
rv.commands.sendInternalEvent("external-sgtk-initialize")
import sgtk

This produces:

ModuleNotFoundError: No module named 'sgtk'

I’m using RV 2022.3.1. I’m trying to write a package that will apply a CDL to an image sequence. The path to that CDL is a published file that is linked to a Version, so in my package code, I need to first retrieve the Version entity from ShotGrid, and then, I need to retrieve the PublishedFile for the LUT, like so:

sg_lut_pfile = shotgun.find_one("PublishedFile", [["sg_version", "is", sg_version], ["sg_published_file_type", "is", {"type": "PublishedFileType", id: 123}]])

The problem here is - I can’t for the life of me figure out how to get at SGTK.

This is a hack, but the following seems to sort of work:

        os.environ["BOOTSTRAP_TK_ENGINE_SYNCHRO"] = "YES"
        # commands.sendInternalEvent("external-sgtk-initialize")
        import sgtk_bootstrap
        rv_tkm = sgtk_bootstrap.ToolkitBootstrap()
        rv_tkm.initialize_toolkit()
        # wait until toolkit is initialized to proceed
        while not rv_tkm.toolkit_initialized:
            time.sleep(1)
        import sgtk

This will eventually break out of the while loop and import sgtk will work. Unfortunately, when the actual module loads later on it will throw an exception indicating that the mode has already been activated. It seems like the sendInternalEvent returns immediately, and until toolkit is initialized, the path to the sgtk module will not be defined, so you got to wait on it.

I wish there was a cleaner way to do this, some way to make the event call be synchronous?

You can add a callback to the sgtk-authenticated-user-changed event and import sgtk locally from that function. The event gets fired once the asynchronous tk-rv engine bootstrap is completed.

As you can see, this module is a special because it’s dynamic and importing in the scope of your function allows you to be sure to have a reference to the right sgtk.

You can then do something like:

user = sgtk.get_authenticated_user():

if user is None:
  return

sg = user.create_sg_connection()

# Then use sg find_one() and do the rest of your logic. 
1 Like

@geffrak ,

Thanks so much for getting back to me!

This is how I’m binding to the event:

rv.commands.bind("default", "global", "sgtk-authenticated-user-changed", self.handle_toolkit_init_complete, "Asynchronous SG Toolkit Initialization complete.")

Unfortunately, it looks like that sgtk-authenticated-user-changed event never gets fired. I’m looking at the code for sgtk_bootstrap.py, and in the initialize_toolkit() function it first checks to see if the toolkit has already been bootstrapped by testing if sgtk.platform.current_engine(), and then fires the event. Unfortunately, the first time around, this condition is not satisfied, since bootstrap_engine_async() is not called until later in the code.

Also, bootstrap_engine_async() is given a completed callback method that does not fire the event. If I edit the code at /Applications/RV.app/Contents/PlugIns/Python/sgtk_bootstrap.py, and add this at line 295:

rvc.sendInternalEvent("sgtk-authenticated-user-changed")

Everything works as expected.

Am I doing something wrong?

I had these same questions when I was working on my first RV package… and was pointed to that same bootstrap initialize event, I got it to work but it took about 2-5mins depending on how our network was that day… Maybe it is faster now?

However,
If you have a centralized core, OR you store descriptors in your pipeline config, you can easily just import sgtk by adding your core path to sys.path. That is how I intitalize sgtk, then from there you can enter a project by doing sgtk_from_path… It is miles faster than the bootstrap almost no overhead.

So that is always an option… We have a wrapper that we import on all our scripts that imports dynamic environment variables so I query core_paths off that… not sure if that thats an option for you… but it is a faster and less overhead I found…

Sadly, our pipeline config is distributed - it’s designed to run for remote artists who likely will not have access to the central storage repository.

Performance-wise, the bootstrap isn’t terrible - I’m seeing about 5 seconds, give or take. I’ve gotten it to work by editing SG’s sgtk_bootstrap.py inside the RV application folder and having it fire an event when it finishes initializing. I am fully aware that this is a sub-optimal solution.

Thank you for the pointers! I’ll need to come up with something that’s a little more sustainable in the long term. Perhaps I can convince SG devs to add that event firing in the sgtk_bootstrap.py code, and barring that, I’ll definitely make use of your wrapper idea.

ahhhh,

I wonder if you can use RV_SUPPORT_PATH environment variable to supply a new path to sgtk_bootstrap.py and override theirs with yours… that would at least allow you to put the module into a pipeline central location instead of needing to deploy/edit the install directory on XXXX machines… (YIKES)

Also by chance have you tried this in RV 2022? i’ve noticed a few bugs/issues with RV2023 since they moved to python39

im fairly sure you could, I have a mirror structure Python/Packages/OIIO ect that we have on a central server that I have added to RV_SUPPORT_PATH and it looks there to load modules/packages ectectect, we even load Mu files to override the configs… the question is would it override that bootstrap.py that exists in Python folder… Easy test… :smiley:

I finally got this figured out. I wish I could get the 20 hours of time I spent back, hah hah! Basically, the main problem here is that I’m using a distributed configuration. Things like sgtk_from_path() and sgtk_from_entity() only work if you use a centralized configuration.

Furthermore - RV uses a very basic config, and it doesn’t have any local cache storage available, so it can’t download a config. It initializes TK with no context, and forces the bootstrap manager to not lookup/download any pipeline configs. If you try to force it to bootstrap with a specific pipeline config you won’t be able to start the tk-rv engine, and if you try to start a different engine you’ll also get an error since the other engines are not in the code that comes pre-packaged with RV.

The big bummer about this configuration, in addition, is RV’s bare-bones config does not give you any templates, so anything that you have defined in a project-specific templates.yml file will be unavailable.

I also abandoned any sort of event handling or event firing. I found that the sgtk_bootstrap plugin does not consistently fire the sgtk-authenticated-user-changed event, so if I listen for that event, I won’t be guaranteed that I’ll receive it. Also sgtk_bootstrap starts the engine asynchronously, which would explain why that event isn’t fired on startup.

The Python package that I built is designed to apply shot-specific CDLs and look LUTs, and to source these paths from SG in a hopefully platform-independent way. In the init method, I set several class variables to None, such as self.sg. I also set a boolean, self.tkini, to False. For the source_setup method, I test to see if self.tkini is False, and if so, I do the following:

        global sgtk
        try:
            module_test = dir(sgtk)
        except NameError:
            try:
                import sgtk
            except ModuleNotFoundError:
                plugin_root = os.path.join(sgtk_bootstrap.sgtk_dist_dir(), "baked", "plugin")
                sys.path.insert(0, os.path.join(plugin_root, "python"))
                from sgtk_plugin_basic_rv import manifest
                core_path = manifest.get_sgtk_pythonpath(plugin_root)
                self._logger.info("Looking for tk-core here: %s" % str(core_path))
                # now we can kick off sgtk
                sys.path.insert(0, core_path)
                self._logger.info("About to import sgtk...")
                import sgtk
        self.engine = sgtk.platform.current_engine()
        if not self.engine:
            self._logger.warning("Giving up on the external-sgtk-initialize event. Skipping engine.")
            self.user = sgtk.get_authenticated_user()
            if not self.user:
                from sgtk_auth import get_toolkit_user
                (user, url) = get_toolkit_user()
                self.user = user
                sgtk.set_authenticated_user(self.user)
            self.shotgun = self.user.create_sg_connection()
        else:
            self.shotgun = self.engine.shotgun
        self.storagekey = sgtk.util.ShotgunPath.get_shotgun_storage_key()
        self.local_storages = self.shotgun.find("LocalStorage", [], [self.storagekey])
        self.tkini = True
        return

I do a check for sgtk.platform.current_engine() to see if the tk-rv bootstrap has occurred. This works sometimes, so I just return if the current engine is not None. However if it isn’t, I make my own connection to SG.

If anyone from the RV team happens to read this - I would make a case for a complete redesign of RV’s scripting abilities. First, ditch Mu - not sure why that was chosen in the first place, but it’s a language that nobody knows, is not used anywhere else, and the documentation is non-existent except for on Autodesk’s site, and a lot of it is missing even there. I’d love to see an entirely Python API that makes sgtk available in the PYTHONPATH by default, and has already handled all of the authentication/bootstrapping.

Deprecating/Removing Mu is hard given all the studios with custom packages written in Mu.

However, I say your complaint about not having SGTK available in the PYTHONPATH by default and the potential bug where the event might not always fire and I will see what we can do.

Fair enough - that’s all I can ask for!

One additional thing that I found that is a bit tricky. It seems to me that a user-authored Python package executes in the main thread. That is to say - if I write any code where I wait on that event to be received - acquire a lock, bind to the sgtk-authentication-user-changed event, and in my callback function, release the lock - and in the function that applies LUTs, try to acquire the lock - it will hang indefinitely. Should I be launching the callback function in a separate thread?

I would recommend you to look at how it’s done in live_review.py.