Actions to implement custom additional functionality for Plug-ins

I feel the need for custom actions that can be implemented for any plug-in and become visible in the UI is there. This could be under “Right mouse context menu” or as visible icons on the respective Plug-ins display.

Goal

To provide plug-in developers to add additional functionality to their plug-in that enables artists to work with the data it processes.

Example

Imagine a Validator plug-in that invalidates a set of meshes in your scene because of a wrong naming convention (or anything really). One could implement a “Select” action that would select/highlight the wrong meshes. One could also additionally implement a “Repair” functionality to perform some sort of fixing functionality.

Implementation

I’m not sure what the best implementation would be, but if it’s possible to be a visible icon button or text button could already be really great!

Some related discussions

Does this also involve the chaining of plugins with inputs/outputs?

Thanks for the request, @BigRoy. We’ve mentioned it in the past, but I think it’s time we lay it out and get a proper sense of what it really is.

Implementation

I think we’ve already got a rather solid example of this from your comment in the Feedback Plug-ins issue. Something like this.

class RepairAction(pyblish.Action):
  pass

class FeedbackAction(pyblish.Action):
  pass

class ValidateInstances(pyblish.Validator):
    def __init__(self):
        self.add_action(RepairAction)
        self.add_action(FeedbackAction)

Alternatively, to keep it in line with other additions to plug-ins, maybe this is preferable.

class ValidateInstances(pyblish.Validator):
    actions = [RepairAction, FeedbackAction]

It sounds to me like what that separates Action's from other plug-ins is that Action's are interactive. Meaning they are never run on their own accord but must be run by a human or otherwise intentional manner, like a script.

My thoughts

About implementing this request, because it means pivoting Pyblish in a direction it was never meant to go, I feel the need to clarify a few things.

I’ve generally been opposed to the idea of widening Pyblish beyond SVEC. Primarily because I believe in it to be capable of encompassing every need - something that has yet to be disproved.

The future I see is one where SVEC is commonplace. When something is commonplace it means we can discuss it, perfect it. Something we are already doing, both here and in the chat. And even though SVEC may not ever be perfect, it is to our advantage to bend, adapt and work around it’s limitations instead of going solo, for the single purpose of having access to a common vocabulary.

To me, that is far greater than a perfect implementation or individual technology.

Having said that, and having listened to your feedback, I’ve also started coming around to an alternative route for getting there. A way in which SVEC is built on-top of the core API, instead of within it.

         Before                         After
 ________________________      _______________ ________    
|                        |    |               |        |   
|     Customisation      |    | Customisation |  SVEC  |   
|________________________|    |_______________|________|   
 ________________________      ________________________    
|                        |    |                        |   
|       API, SVEC        |    |          API           |   
|________________________|    |________________________|   

It means that SVEC can continue to flourish without obstacles and also that alternative - possibly better - systems can be implemented, such as Action's. It also puts SVEC to the test in the sense that developers can on their own accord discover the reason for it’s existence. Experience why there is features such as Instance's and a Context, why there is a need for the family attribute. And finally, if it turns out that there is a better system than SVEC, it means Pyblish is able to welcome it.

Related

1 Like

No, this is supposed to be something different. This is not about chaining plug-ins or influence order of processing, but to allow Artists convenient ways of working with the data/functionality of the Plug-ins.

To me this addition doesn’t change the SVEC system. It stays in place and should still be the only way to actually process everything with Pyblish to get through validation and extraction. But it’s to allow easier interaction with the Plug-ins that is convenient for the Artist. Like selecting the meshes that are invalid, so he can quickly fix it. Or even a repair button. Of course we could define ways the data will get represented by Pyblish and find our way to ‘selecting a mesh’, but I think it’s much more than that. It’s such a specific Action for a Plug-in that it’s hardly predictable and should not be limited.

Sounds good. I think the less ‘Custom’ stuff we need per SVEC plug-in class the stronger the core of Pyblish is. We should keep Pyblish “breathing SVEC” as its main motto.

Would it make sense to make this distinction then, that Action's are about interactivity?

It’s the only way I can see this not conflicting with other plug-ins at the moment, and it would make for a clear separation of concerns. Visually, as you say, they could become accessible via right-click or similar means. Implementation wise, they could remain identical to plug-ins.

class MyAction(pyblish.Action):
  def process_context(self, context):
    # do
  def process_instance(self, instance):
    # do

It would align itself with dependency injection.

It could possibly utilise families too, in cases where you have multiple actions that apply to different types of assets.

class MyAction(pyblish.Action):
  families = ["mesh"]
  def process_context(self):
    # do mesh things

This way, you can have a single plug-in process multiple families, but restrict an action to a subset of those. For example, ValidateNamingConvention may apply to both meshes and shaders, but selecting shaders might open up the Hypershade first.

Yes, that covers it very well!

To be honest I think the Actions wouldn’t even need access to process_context() or process_instance() but more so to something like process_plugin() and (whenever it requires context it takes it from the plug-in?). This also makes it less likely for people to use it as something that adjusts the context/instance?

They might not need to, but there might be cases in which it would make sense.

For example, if a validation fails and you were interested in writing an Action to select the failing portions of the scene, the Context and Instance could contain knowledge about what to select.

class SelectPerpetrators(pyblish.Action):
  def process_instance(self, instance):
    cmds.select(instance)

The same goes for repairs.

class SolvePerpetrators(pyblish.Action):
  def process_instance(self, instance):
    cmds.delete(instance.data("badNodes"))

New users can get away with neither.

class SolvePerpetrators(pyblish.Action):
  def process_context(self, context):
    cmds.delete("myNode_GEO")

Though ideally the method they override wouldn’t contain information that is not relevant to them, such as when dependency injection is active.

class SolvePerpetrators(pyblish.Action):
  def process(self):
    cmds.delete("myNode_GEO")

All clean. :slight_smile:

Specificity

It may also make sense to be specific with your action, such as applying it only to a particular Plugin/Instance combination. In this case, the instance chose would be passed to process() via it’s instance argument.

Good! Looks like it could work.

I still have to adopt the mindset that all data ever being used will be going into the Context or Instance instead of getting it from the Plug-in itself. For me it’s still that the data of invalidated nodes is so linked to the Validator that I would assume the data gets stored on the Validator, but I can see how that would make it much harder to use the data outside of the plug-in. Especially with storing all invalidated nodes’ data into a database or log at the end it’s easier if the data is inside the context/instance (as it should be).

Maybe it would make it easier think of plug-ins more like nodes; each node is stateless and just takes data in, does something to it, and then pushes it back out.

Actually that is the wrong example. :wink: That makes me think the data is retrievable as an output on the node, not on the Context. I would say it’s more like a traffic highway going to one destination and each plug-in will end up at the same destination, which is the resulting Context?

But I get your point, you mean more as a Node that has incoming data and adds to that data as opposed to one that has incoming attributes and provides other/new output attributes (the latter is the terminology I’m used to from Maya).

Ah, yes that makes sense.

Had some ideas that correlate to this topic, but haven’t been able to clearly define it yet, so I’m sharing it here for reference.

What’s wrong with Repair?

Besides the conceptual barrier of publishing versus repairing, which we have covered many(1) times(2) before(3), repairing is difficult from an implementation point-of-view primarily because of how it can affect either the Context, an Instance or many Instance's.

It is also difficult from a user-experience point-of-view. Currently, the repair-button in the GUI is located next to the plug-in such that it is clear what method is being run to actually perform the repair; but what isn’t clear is which instance it applies to, or if it involves the context.

References

  1. Repair Faults on Forum
  2. Actions on Forum
  3. Original issue on GitHub

What’s good about Repair?

Besides it’s usefulness, which has also been discussed in the above links, repairing is about solving problems and validations are in their very nature looking for such problems. Having the solution and problem coupled together in this way is highly intuitive. It means the solution to any given problem is provided to the user in context with the problem.

As in, the user is able to solve a problem, only when a problem is present to begin with.

What can we do?

Here’s an optional name for Action it that more clearly suggests that it only relates to solving problems and that it only appears when a problem has been found; Solution.

class SolveAge(pyblish.api.Solution):
  def process(self, instance):
    # solve problem

class ValidateAge(pyblish.api.Validator):
  solutions = [SolveAge]

  def process(self, instance):
    # find problem
1 Like

Good short write-up.

I think Solution could be a subclass for Action meant to be implemented for Validators to provide… well, a solution.

But I can also see benefits in being able to right-click the extractor after process and have custom Actions there, like “show output in explorer”. Possibly useful if an integrator failed. A similar action is just as useful for an Integrator to identify quickly where our files ended up.

Actions in general would be to allow streamlining our workflow with Pyblish by letting developers customize it for their own workflow/toolset without breaking the functionality of the UI (standalone or integrated) or even in batch mode. In short Actions would be about being able to extend Pyblish, whereas Solutions are a subset of that.

1 Like

Implementing this now, here’s a mockup of the final behaviour.

Where each item is capable of providing it’s own custom menu items, the lower part being user-supplied actions.

1 Like

Wow!

That is super exciting:)

Super-super-awesome!

Will there also be a way to promote an Action into its own icon on the plug-in?

Thanks, glad you guys like it.

Yes, there certainly will be icons coming up.

Edit: for completeness, this got implemented in version 1.2.

1 Like

@marcus I was thinking about using Actions to add a Development category in the Context menu that would allow me to “Show Plug-in in Explorer”. From time to time I’ve had a hard time to figure out where a plug-in was exactly loaded from in the UI if multiple paths were involved. Do you think something like that makes sense?

And would it be easy to find the plug-in instance’s filepath from within the Action if the plug-ins were inherited from one top plug-in that had the action attached to it. (The Action is defined elsewhere)

Yeah, that would totally make sense.

Maybe something to have by default even? There’s been talk about making something like this available before, but nothing ever came up to provide the interface for it. This could be it.

It would be awesome if you could share what you come up with, and maybe even pull request to add a default.

1 Like

Will have a look shortly.

Would it be possible to only show up if some development environment variable was set? Or a way to keep regular artists from seeing it?