Context sensitive plugins

###Goal###
Ability to disable plugins based on missing data in the context

####Usecase####
While working on the ftrack extension I ran into a situation which wasn’t apparent until now.

Normally plugins are dependent on an instance family. So if no instance with given family is found, the plugin doesn’t appear in the UI and doesn’t run.

pyblish-ftrack however is dependant on ftrack data being present in the environment variables and ability to import ftrack.

Currently if the first selector runs into any issues, hence doesn’t manage to collect all the data needed for the rest of the plugins to run, they all still appear in the ui and try to run on each instance. To prevent them from failing we’re simply adding a condition to do nothing if they don’t find data they need, but that results in lots of ‘green’ plugins in the UI which effectively did nothing.

####Solution####
The solution might be allowing plugins to be run based on content of the context as well as instance family.

####Example####

  • All pyblish-ftrack plugins need ftrackData data member in context to be able to do anything.
  • This data get’s created by select-ftrack-data plugin a passed into context.
  • All plugins dependent on this data could not appear in the UI unless ftrackData is present.

Plugins could then look something like this:

# Pseudo Code

import pyblish.api

@pyblish.api.log
class ValidateFtrackVersion(pyblish.api.Validator):
    """ Validates something really important"""
    
    families = ['*']
    hosts = ['*']
    context = ['ftrackData']
    version = (0, 1, 0)
    
    def process_instance(self, instance):
        
        ftrack_data = instance.context.data('ftrackData')

        assert something_really_important

Interesting proposal. Having a think.

As an addition to the proposal.

What this would effectively mean is that extensions would have a ‘master’ plugin. A selector that determines whether all the other plugins from extension should run or not by adding the required data member to the context. In my eyes this would be very powerful and would open many possibilities without introducing too much complexity for the user.

Allright, before having a go implementing this feature, let’s run through some more general use cases, what alternatives there are and why they aren’t a good fit.

First off, as an alternative, why not let a Selector determine, based on some data, whether or not to create an Instance? If the Instance is not created, it won’t be processed.

I get that the feature would solve your immediate problem with pyblish-ftrack, but for a core feature like this one, we’ll need to get a bit more general. Can you think of any other scenario in which this can be used, where the current methods are insufficient?

Because selector in this case is not creating any instances. But merely adding data to context, so all the instances can benefit from this data. The same situation would probably happen with potential future shotgun integration.

The point is that if certain conditions are met (currently launched host is ftrack aware), we want to show related plugins which operate on instances created by other selectors. There’s no point in creating new instance because it already exists. If we wanted for sake of argument create instances specifically for ftrack publishing, we would be essentially duplicating already existing ones. Rather than adding conforming to ftrack as another processor for the existing ones. It’s certainly possible, however I find it very clumsy and confusing for the user to see each instance twice once of it’s original family and once of ftrack.originalFamily.

The cleanliness of current implementation I think is in the fact, that user only need’s to append some simple data to his instances and they become compatible with ftrack-extension.

In a studio environment quite a few. Any extensions that should deal with conforming existing instances but are dependent on certain conditions.

For example we could dynamically change what plugins are available to different shows based on a few simple rules. Currently to give people different plugins on a per show, or per department basis, I’d have to give them customized launchers that each set different PYBLISH_PLUGIN_PATH. This way I’d only need to search for type of task they are working on and enable/disable plugins based on that.

Network dependency: Plugins that require access to certain network folders that might only be available to a few people in the studio because of NDAs. I could check for availability and disable these plugins before they appear in the UI. Whereas another person on the same project might have access to this because he’s conforming work of multiple people together and publishing the combined result.

The point is to keep the initial registering of the plugins simple, but give the ability to exclude/include plugins on the fly.

Bringing in some related topics for completeness.

Each are related to dynamically altering the available plug-ins at run-time. Perhaps we can find a uniform solution for each. At the moment, they each propose unique solutions that, in a way, achieve a similar goal.

Let’s address the scenarios first.

This kind of flexibility is actually why PYBLISHPLUGINPATH and pyblish.api.register_plugin_path exists.

Prior to launching any process, you have access to the full environment of the workstation - like user, current project, task, time, hardware specs etc. This should provide you with enough information to make a decision about what features (i.e. plug-ins) to include.

# Register paths before launching software.
import os
import getpass
import subprocess

os.environ["PYBLISHPLUGINPATH"] = "/my/plugins/%s" % getpass.getuser()
subprocess.Popen("maya")

Run-time is for when you require access to additional information only available from within a process. It’s intended primarily for advanced/esoteric and debugging cases.

# Register paths at run-time
import pyblish.api
import maya.cmds

has_renderlayers = True if maya.cmds.ls(type="renderLayer") else False
pyblish.api.register_plugin_path(
  "/my/plugins" + "/render" if has_renderlayers else "")

At each point, a decision is made using the available information about which plug-ins to include. This proposal would allow this decision to also be made after Selection has finished, is that right?

Yes, that’s right, this doesn’t work for you?

See above.

Alternative 2

Would it be possible to determine whether a host is ftrack-aware during startup or at run-time?

Example 1 in studio X, everyone uses ftrack. So I know that it is safe to make ftrack-assumptions in all of my plug-ins. In this case, plug-ins don’t require a has_data but can instead assert that it exists, resulting in appropriate result in the GUI.

Example 2 during start of a process via the ftrack launcher, would it be possible to register ftrack-related plug-ins then? In this case, plug-ins can still assert the existence of ftrack-related data, as ftrack-plugins are only loaded when they are supported, as guaranteed by the launcher, resulting in appropriate results in the GUI.

Example 3 the launcher decides whether or not ftrack is available, but only a subset of all families are meant for processing by it. In this case, maybe we could utilise the family registration from this thread to inform the extension about which families to process? The end result is what we would expect in the GUI.

To be honest quite a few things you’ve mentioned here I’ve missed when reading through docs. I think I should be able to get what I need with current implementation. So at least for now we can leave this as a dead idea :wink:

Not a problem, @mkolar, it’s good that you found what you were looking for and I also think it’s important to point out that your proposal does have a unique value proposition - to be able to enable/disable plug-ins at run-time.

The reason I wanted to first run through a few alternatives was because it is a core feature and I wanted to make sure it didn’t shadow an existing core feature without good reason. Keeping the feature set as small as possible is a thing to strive for, but sometimes new requirements do appear so I’d still encourage you to continue thinking outside the box and post proposals. They don’t necessarily need to start out strong, but through conversation anything can happen and that’s the important bit I think.