Instance argument for Actions

Goal

To be able to inject the instance argument to Actions and get the relevant instances.

Motivation

Currently you have to repeat a lot of code to get the relevant instances. Example to get the failed instances:

class Texture(pyblish.api.Action):

    label = "Print Failed Instance"
    on = "failed"

    def process(self, context):

        # get all failed instances
        failed = list()
        for result in context.data["results"]:
            if result["error"] is not None:
                if result["instance"] not in failed:
                    failed.append(result["instance"])

        for instance in failed:
            # context seem to return None
            if not instance:
                continue
            # filter to family specific instance
            if instance.data['family'] != 'texture':
                continue

            self.log.info(instance)

Implementation

You already specify what statuses of instances the action should operate on, from the on property ei. all, failed etc.
You also know the families of instances, which is coming from the plugin executing the action.
With the status and family of the instance, we should be able to find the correct instances to operate on, Unless I’m missing something?

1 Like

I have also been looking for a way to access this easier.

By the way, filtering the instances to the family of the plugin can be done through the API. As such it uses the logic that pyblish uses, and possibly other filtering for a plugin in the future aside from just the family.

# pseudocode
def process(self, context, plugin):

    # Get the errored instances
    failed  = []
    for result in context.data["results"]:
        if result["error"] is not None:
            failed.append(result["instance"])

    # Apply pyblish.logic to get the instances for the plug-in
    instances = pyblish.api.instances_by_plugin(failed, plugin)

Yet this also makes me think about the way the Actions currently work in the UI. Since the Artist cannot run an Action only for a specific instance.

1 Like

@tokejepsen Would you then expect an action to run once per instance, like an InstancePlugin?

Dependency injection was deprecated from 1.3 onwards in favour of explicit plug-ins - ContextPlugin and InstancePlugin.

For this to work, we could potentially make it ContextAction and InstanceAction.

1 Like

You are right, thought there was something I was missing:)

+1

Makes sense to me. :thumbsup:

Feel free to make it a feature issue, should be fairly straightforward to implement.

In theory would an InstanceAction be executable from an instance as well? Ei. right-click on an instance, and select the action.

Not sure about that. :slight_smile: How would you associate this action with the Instance?

And if you were doing something like this…

instance.append(MyAction)

What would it mean for it to be a ContextAction or InstanceAction? Mind blown?

Sorry, thought I could hit two bird with one stone. Was thinking towards the Importer

This is where instance actions could come in, but the logic for having an action on an instance would be different than an action on a plugin iterating over instances. Is that right, @marcus?

I don’t know, there are probably more than one way of going at it. Feel free to propose your ideas. Maybe start with a few examples of actions you would expect to find on an instance?

First would probably be a select action:

class SelectInstance(pyblish.api.InstanceAction):

    label = 'Select'
    on = 'all'

    def process(self, instance):

        pymel.core.select(instance[0])

You would assign the action when collecting the instance:

class CollectLocators(pyblish.api.ContextPlugin):

    order = pyblish.api.CollectorOrder

    def process(self, context):

        for node in pymel.core.ls(type='locator'):
            instance = context.add_instance(node.name(), family='locator')
            instance.actions.append(SelectInstance)

That looks great, I think.

It also might make sense that it is an InstanceAction, in which instance in the argument signature is the right-clicked instance in the view. A ContextAction could take context, and pretty much still make sense, right?

The assignment also works for me.

I think let’s mock it up. It’d need the ContextAction and InstanceAction classes in pyblish-base, to be formatted in pyblish-rpc and finally drawn in pyblish-qml. There is already actions in each project to use for reference and in all should be quite straightforward to implement.

Does it make sense as well that if an InstanceAction assigned to an instance is run once, and an InstanceAction assigned to a plugin will potentially be run multiple times?

Spontaneously, yes I think it does. :slight_smile:

Referencing the original issue for completeness.

Slight adjustment to @BigRoy code, since the context returns None.

# pseudocode
def process(self, context, plugin):

    # Get the errored instances
    failed  = []
    for result in context.data["results"]:
        if result["error"] is not None and result["instance"] is not None:
            failed.append(result["instance"])

    # Apply pyblish.logic to get the instances for the plug-in
    instances = pyblish.api.instances_by_plugin(failed, plugin)
1 Like

If an Action is only available on failed would this also be a problem? I’m assuming it wouldn’t be because it’ll only fail with errors.

# pseudocode
class MyAction(pyblish.api.Action):
    on = "failed"

Doesn’t seem to be a problem :smile:

A problem how?