Publish: Enhance automated publish plugin settings (#4986)

* prepared helper functions for custom settings apply method

* publish plugin can have 'settings_category' attribute to define settings category

* Better 'settings_category' comment

Co-authored-by: Roy Nieterau <roy_nieterau@hotmail.com>

* fix trailing spaces

* added more information about pyblish plugins to dev docs

---------

Co-authored-by: Roy Nieterau <roy_nieterau@hotmail.com>
This commit is contained in:
Jakub Trllo 2023-05-22 13:28:09 +02:00 committed by GitHub
parent 136af34a71
commit 30fe6759c3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 136 additions and 23 deletions

View file

@ -36,6 +36,9 @@ from .lib import (
context_plugin_should_run,
get_instance_staging_dir,
get_publish_repre_path,
apply_plugin_settings_automatically,
get_plugin_settings,
)
from .abstract_expected_files import ExpectedFiles
@ -80,6 +83,9 @@ __all__ = (
"get_instance_staging_dir",
"get_publish_repre_path",
"apply_plugin_settings_automatically",
"get_plugin_settings",
"ExpectedFiles",
"RenderInstance",

View file

@ -355,29 +355,55 @@ def publish_plugins_discover(paths=None):
return result
def _get_plugin_settings(host_name, project_settings, plugin, log):
def get_plugin_settings(plugin, project_settings, log, category=None):
"""Get plugin settings based on host name and plugin name.
Note:
Default implementation of automated settings is passing host name
into 'category'.
Args:
host_name (str): Name of host.
plugin (pyblish.Plugin): Plugin where settings are applied.
project_settings (dict[str, Any]): Project settings.
plugin (pyliblish.Plugin): Plugin where settings are applied.
log (logging.Logger): Logger to log messages.
category (Optional[str]): Settings category key where to look
for plugin settings.
Returns:
dict[str, Any]: Plugin settings {'attribute': 'value'}.
"""
# Use project settings from host name category when available
try:
return (
project_settings
[host_name]
["publish"]
[plugin.__name__]
)
except KeyError:
pass
# Plugin can define settings category by class attribute
# - it's impossible to set `settings_category` via settings because
# obviously settings are not applied before it.
# - if `settings_category` is set the fallback category method is ignored
settings_category = getattr(plugin, "settings_category", None)
if settings_category:
try:
return (
project_settings
[settings_category]
["publish"]
[plugin.__name__]
)
except KeyError:
log.warning((
"Couldn't find plugin '{}' settings"
" under settings category '{}'"
).format(plugin.__name__, settings_category))
return {}
# Use project settings based on a category name
if category:
try:
return (
project_settings
[category]
["publish"]
[plugin.__name__]
)
except KeyError:
pass
# Settings category determined from path
# - usually path is './<category>/plugins/publish/<plugin file>'
@ -386,9 +412,10 @@ def _get_plugin_settings(host_name, project_settings, plugin, log):
split_path = filepath.rsplit(os.path.sep, 5)
if len(split_path) < 4:
log.warning(
'plugin path too short to extract host {}'.format(filepath)
)
log.debug((
"Plugin path is too short to automatically"
" extract settings category. {}"
).format(filepath))
return {}
category_from_file = split_path[-4]
@ -410,6 +437,28 @@ def _get_plugin_settings(host_name, project_settings, plugin, log):
return {}
def apply_plugin_settings_automatically(plugin, settings, logger=None):
"""Automatically apply plugin settings to a plugin object.
Note:
This function was created to be able to use it in custom overrides of
'apply_settings' class method.
Args:
plugin (type[pyblish.api.Plugin]): Class of a plugin.
settings (dict[str, Any]): Plugin specific settings.
logger (Optional[logging.Logger]): Logger to log debug messages about
applied settings values.
"""
for option, value in settings.items():
if logger:
logger.debug("Plugin {} - Attr: {} -> {}".format(
option, value, plugin.__name__
))
setattr(plugin, option, value)
def filter_pyblish_plugins(plugins):
"""Pyblish plugin filter which applies OpenPype settings.
@ -453,13 +502,10 @@ def filter_pyblish_plugins(plugins):
)
else:
# Automated
plugin_settins = _get_plugin_settings(
host_name, project_settings, plugin, log
plugin_settins = get_plugin_settings(
plugin, project_settings, log, host_name
)
for option, value in plugin_settins.items():
log.info("setting {}:{} on plugin {}".format(
option, value, plugin.__name__))
setattr(plugin, option, value)
apply_plugin_settings_automatically(plugin, plugin_settins, log)
# Remove disabled plugins
if getattr(plugin, "enabled", True) is False:

View file

@ -506,6 +506,67 @@ or the scene file was copy pasted from different context.
#### *Known errors*
When there is a known error that can't be fixed by the user (e.g. can't connect to deadline service, etc.) `KnownPublishError` should be raised. The only difference is that its message is shown in UI to the artist otherwise a neutral message without context is shown.
### Plugins
Plugin is a single processing unit that can work with publish context and instances.
#### Plugin types
There are 2 types of plugins - `InstancePlugin` and `ContextPlugin`. Be aware that inheritance of plugin from `InstancePlugin` or `ContextPlugin` actually does not affect if plugin is instance or context plugin, that is affected by argument name in `process` method.
```python
import pyblish.api
# Context plugin
class MyContextPlugin(pyblish.api.ContextPlugin):
def process(self, context):
...
# Instance plugin
class MyInstancePlugin(pyblish.api.InstancePlugin):
def process(self, instance):
...
# Still an instance plugin
class MyOtherInstancePlugin(pyblish.api.ContextPlugin):
def process(self, instance):
...
```
#### Plugin filtering
By pyblish logic, plugins have predefined filtering class attributes `hosts`, `targets` and `families`. Filter by `hosts` and `targets` are filters that are applied for current publishing process. Both filters are registered in `pyblish` module, `hosts` filtering may not match OpenPype host name (e.g. farm publishing uses `shell` in pyblish). Filter `families` works only on instance plugins and is dynamic during publish process by changing families of an instance.
All filters are list of a strings `families = ["image"]`. Empty list is invalid filter and plugin will be skipped, to allow plugin for all values use a start `families = ["*"]`. For more detailed filtering options check [pyblish documentation](https://api.pyblish.com/pluginsystem).
Each plugin must have order, there are 4 order milestones - Collect, Validate, Extract, Integration. Any plugin below collection order won't be processed. for more details check [pyblish documentation](https://api.pyblish.com/ordering).
#### Plugin settings
Pyblish plugins may have settings. There are 2 ways how settings are applied, first is automated, and it's logic is based on function `filter_pyblish_plugins` in `./openpype/pipeline/publish/lib.py`, second is explicit by implementing class method `apply_settings` on a plugin.
Automated logic is expecting specific structure of project settings `project_settings[{category}]["plugins"]["publish"][{plugin class name}]`. The category is a key in root of project settings. There are currently 3 ways how the category key is received.
1. Use `settings_category` class attribute value from plugin. If `settings_category` is not `None` there is not any fallback to other way.
2. Use currently registered pyblish host. This will be probably deprecated soon.
3. Use 3rd folder name from a plugin filepath. From path `./maya/plugins/publish/collect_render.py` is used `maya` as the key.
For any other use-case is recommended to use explicit approach by implementing `apply_settings` method. Must use `@classmethod` decorator and expect arguments for project settings and system settings. We're planning to support single argument with only project settings.
```python
import pyblish.api
class MyPlugin(pyblish.api.InstancePlugin):
profiles = []
@classmethod
def apply_settings(cls, project_settings, system_settings):
cls.profiles = (
project_settings
["addon"]
["plugins"]
["publish"]
["vfx_profiles"]
)
```
### Plugin extension
Publish plugins can be extended by additional logic when inheriting from `OpenPypePyblishPluginMixin` which can be used as mixin (additional inheritance of class). Publish plugins that inherit from this mixin can define attributes that will be shown in **CreatedInstance**. One of the most important usages is to be able turn on/off optional plugins.
@ -596,4 +657,4 @@ Publish attributes work the same way as create attributes but the source of attr
### Create dialog
![Publisher UI - Create dialog](assets/publisher_create_dialog.png)
Create dialog is used by artist to create new instances in a context. The context selection can be enabled/disabled by changing `create_allow_context_change` on [creator plugin](#creator). In the middle part the artist selects what will be created and what variant it is. On the right side is information about the selected creator and its pre-create attributes. There is also a question mark button which extends the window and displays more detailed information about the creator.
Create dialog is used by artist to create new instances in a context. The context selection can be enabled/disabled by changing `create_allow_context_change` on [creator plugin](#creator). In the middle part the artist selects what will be created and what variant it is. On the right side is information about the selected creator and its pre-create attributes. There is also a question mark button which extends the window and displays more detailed information about the creator.