Summary of new features
- Pyblish for Hiero added
- Enhancement: Collectors now visible
- Actions accessible via right-click on plug-ins.
The new version focuses on Actions, a flexible method of adding any functionality to plug-ins whilst reaping the benefits of a context-sensitive environment. Associated actions show up alongside plug-ins that already limit themselves to various constraints, such as which project, shot, artist and asset is currently active.
The actions can be further tailored to only appear at the success, failure or done state of a plug-in, providing you with much flexibility in terms of which actions to provide when, such as only enabling the selection of affected nodes in the viewport on a plug-in that had just found a problem with an asset.
Furthermore, you will also notice that collectors are now visible in the GUI as unmodifiable, unchecked items, making it more easy to spot what was actually run and to inspect the various properties of each collector, such as it’s description.
Whether you are installing anew or updating, it is recommended that you install from scratch.
- For Windows, download and run the installer.
- For Linux, OSX or to install via the command-line, see manual installation instructions here.
If you run into any issues, feel free to post below.
The only change requiring any additional thought is pure-dict data and the deprecation of
add() on Context and Instance objects. Both of which are fully backwards compatible.
The deprecation of
add means that both Context and Instance are more closely resembling the pure Python
- Instead of using
- Instead of using
This functionality is meant to replace “repair”, along with adding an abundance of flexibility in terms of context-sensitive functionality. Attach any functionality to a plug-in and tailor it to a particular state; like an action only available via a failed validator, or a successful extraction, or just all-round functionality associated with a particular plug-in.
Each action have access to both the Context and it’s parent plug-in via dependency injection, along with any other custom dependencies already available to plug-ins in general.
Actions in QML are arranged in a menu with optional customisable groups and separators. Actions with any kind of implementation error show up as well, including a helpful error message for simplified debugging.
These objects are available via dependency injection by default.
context: The global context
plugin: The parent plug-in
Full list of features
- Per-plugin actions
- Action API ~= Plug-in API, it is more or less a 1-1 match between their interfaces, including
- Standard logging and exception reporting, identical to plug-ins
- Standard dependency injection still applies; can still inject custom functionality
- Customisable icon per action, from Awesome Icon
- Customisable availability
processed: After plug-in has been processed
failed: After plug-in has been processed, and failed
succeeded: After plug-in has been processed, and succeeded
class OpenInExplorer(pyblish.api.Action): label = "Open in Explorer" on = "failed" # This action is only available on a failed plug-in icon = "hand-o-up" # Icon from Awesome Icon def process(self, context): import subprocess subprocess.call("start .", shell=True) # Launch explorer at the cwd class Validate(pyblish.api.Validator): actions = [ # Order of items is preserved pyblish.api.Category("My Actions"), MyAction, pyblish.api.Separator, ] def process(self, context, plugin): """The Context and parent Plug-in are available via dependency injection""" self.log.info("Standard log messages apply here.") raise Exception("Exceptions too.")
Every possible combination of an action.
class ContextAction(pyblish.api.Action): label = "Context action" def process(self, context): self.log.info("I have access to the context") self.log.info("Context.instances: %s" % str(list(context))) class FailingAction(pyblish.api.Action): label = "Failing action" def process(self): self.log.info("About to fail..") raise Exception("I failed") class LongRunningAction(pyblish.api.Action): label = "Long-running action" def process(self): self.log.info("Sleeping for 2 seconds..") time.sleep(2) self.log.info("Ah, that's better") class IconAction(pyblish.api.Action): label = "Icon action" icon = "crop" def process(self): self.log.info("I have an icon") class PluginAction(pyblish.api.Action): label = "Plugin action" def process(self, plugin): self.log.info("I have access to my parent plug-in") self.log.info("Which is %s" % plugin.id) class LaunchExplorerAction(pyblish.api.Action): label = "Open in Explorer" icon = "folder-open" def process(self, context): import os import subprocess cwd = context.data["cwd"] self.log.info("Opening %s in Explorer" % cwd) result = subprocess.call("start .", cwd=cwd, shell=True) self.log.debug(result) class ProcessedAction(pyblish.api.Action): label = "Success action" icon = "check" on = "processed" def process(self): self.log.info("I am only available on a successful plug-in") class FailedAction(pyblish.api.Action): label = "Failure action" icon = "close" on = "failed" class SucceededAction(pyblish.api.Action): label = "Success action" icon = "check" on = "succeeded" def process(self): self.log.info("I am only available on a successful plug-in") class BadEventAction(pyblish.api.Action): label = "Bad event action" on = "not exist" class InactiveAction(pyblish.api.Action): active = False class PluginWithActions(pyblish.api.Validator): optional = True actions = [ pyblish.api.Category("General"), ContextAction, FailingAction, LongRunningAction, IconAction, PluginAction, pyblish.api.Category("OS"), LaunchExplorerAction, pyblish.api.Separator, FailedAction, SucceededAction, pyblish.api.Category("Debug"), BadEventAction, InactiveAction, ] def process(self): self.log.info("Ran PluginWithActions")
import time import pyblish.api import pyblish_qml class Collect(pyblish.api.Collector): def process(self, context): i = context.create_instance("MyInstance") i.data["family"] = "default" i.append("pCube1") class SelectInvalidNodes(pyblish.api.Action): label = "Select broken nodes" on = "failed" icon = "hand-o-up" def process(self, context): self.log.info("Finding bad nodes..") nodes =  for result in context.data["results"]: if result["error"]: instance = result["instance"] nodes.extend(instance) self.log.info("Selecting bad nodes: %s" % ", ".join(nodes)) cmds.select(deselect=True) cmds.select(nodes) class Validate(pyblish.api.Validator): actions = [ pyblish.api.Category("Scene"), SelectInvalidNodes ] def process(self, instance): raise Exception("I failed") pyblish.api.register_plugin(Collect) pyblish.api.register_plugin(Validate) import pyblish_maya pyblish_maya.show()
data property of Context and Instance objects is now a standard Python dictionary, with backwards compatibility for being called directly.
import pyblish.api as pyblish context = pyblish.Context() # Old behaviour preserved context.set_data("key", "value") # New behaviour preferred context.data["key"] = "value" if "key" in context.data: context.data.pop("key") assert context.data.get("key") == None # The same applies to Instances instance = context.create_instance("MyInstance") instance.data["key"] = "value"
The recommended way forwards is to start transitioning to using pure-dict data, not worrying too much about going back to refactor the old ways. Down the line, there will be a warning printed for each call to a deprecated member so as to easily find and refactor things.
From now on, guides and discussion will start to assume this new way of working.
Same name instances
Initially, you could store multiple instances of the same name in the Context. Later on, a feature was implemented to allow referencing an Instance within a Context by name, meaning this ability had to be removed.
Turns out, allowing same-name instances is useful for when you have the same logical asset in your scene, being treated differently by one or more plug-ins; examples include collecting a character rig for extraction to an animator, but then also collecting it for playblasting of a turntable. The turntable instance would only interest itself with plain meshes, and optional overrides for playblast settings being passed onto the relevant playblasting plug-ins, whereas the former requires every related node in order to perform accurate validation on it.
The result is a restored ability to maintain multiple instances of the same name within the Context, whilst at the same time being able to refer to them by name. The ability to refer to by name is to be considered convenience functionality.