mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-01 16:34:53 +01:00
282 lines
8.5 KiB
Python
282 lines
8.5 KiB
Python
import inspect
|
|
from abc import ABCMeta
|
|
|
|
import pyblish.api
|
|
from pyblish.plugin import MetaPlugin, ExplicitMetaPlugin
|
|
|
|
from openpype.lib import BoolDef
|
|
|
|
from .lib import (
|
|
load_help_content_from_plugin,
|
|
get_errored_instances_from_context,
|
|
get_errored_plugins_from_context,
|
|
get_instance_staging_dir,
|
|
)
|
|
|
|
|
|
class AbstractMetaInstancePlugin(ABCMeta, MetaPlugin):
|
|
pass
|
|
|
|
|
|
class AbstractMetaContextPlugin(ABCMeta, ExplicitMetaPlugin):
|
|
pass
|
|
|
|
|
|
class PublishValidationError(Exception):
|
|
"""Validation error happened during publishing.
|
|
|
|
This exception should be used when validation publishing failed.
|
|
|
|
Has additional UI specific attributes that may be handy for artist.
|
|
|
|
Args:
|
|
message(str): Message of error. Short explanation an issue.
|
|
title(str): Title showed in UI. All instances are grouped under
|
|
single title.
|
|
description(str): Detailed description of an error. It is possible
|
|
to use Markdown syntax.
|
|
"""
|
|
|
|
def __init__(self, message, title=None, description=None, detail=None):
|
|
self.message = message
|
|
self.title = title or "< Missing title >"
|
|
self.description = description or message
|
|
self.detail = detail
|
|
super(PublishValidationError, self).__init__(message)
|
|
|
|
|
|
class PublishXmlValidationError(PublishValidationError):
|
|
def __init__(
|
|
self, plugin, message, key=None, formatting_data=None
|
|
):
|
|
if key is None:
|
|
key = "main"
|
|
|
|
if not formatting_data:
|
|
formatting_data = {}
|
|
result = load_help_content_from_plugin(plugin)
|
|
content_obj = result["errors"][key]
|
|
description = content_obj.description.format(**formatting_data)
|
|
detail = content_obj.detail
|
|
if detail:
|
|
detail = detail.format(**formatting_data)
|
|
super(PublishXmlValidationError, self).__init__(
|
|
message, content_obj.title, description, detail
|
|
)
|
|
|
|
|
|
class KnownPublishError(Exception):
|
|
"""Publishing crashed because of known error.
|
|
|
|
Message will be shown in UI for artist.
|
|
"""
|
|
|
|
pass
|
|
|
|
|
|
class OpenPypePyblishPluginMixin:
|
|
# TODO
|
|
# executable_in_thread = False
|
|
#
|
|
# state_message = None
|
|
# state_percent = None
|
|
# _state_change_callbacks = []
|
|
#
|
|
# def set_state(self, percent=None, message=None):
|
|
# """Inner callback of plugin that would help to show in UI state.
|
|
#
|
|
# Plugin have registered callbacks on state change which could trigger
|
|
# update message and percent in UI and repaint the change.
|
|
#
|
|
# This part must be optional and should not be used to display errors
|
|
# or for logging.
|
|
#
|
|
# Message should be short without details.
|
|
#
|
|
# Args:
|
|
# percent(int): Percent of processing in range <1-100>.
|
|
# message(str): Message which will be shown to user (if in UI).
|
|
# """
|
|
# if percent is not None:
|
|
# self.state_percent = percent
|
|
#
|
|
# if message:
|
|
# self.state_message = message
|
|
#
|
|
# for callback in self._state_change_callbacks:
|
|
# callback(self)
|
|
|
|
@classmethod
|
|
def get_attribute_defs(cls):
|
|
"""Publish attribute definitions.
|
|
|
|
Attributes available for all families in plugin's `families` attribute.
|
|
Returns:
|
|
list<AbtractAttrDef>: Attribute definitions for plugin.
|
|
"""
|
|
|
|
return []
|
|
|
|
@classmethod
|
|
def convert_attribute_values(cls, attribute_values):
|
|
if cls.__name__ not in attribute_values:
|
|
return attribute_values
|
|
|
|
plugin_values = attribute_values[cls.__name__]
|
|
|
|
attr_defs = cls.get_attribute_defs()
|
|
for attr_def in attr_defs:
|
|
key = attr_def.key
|
|
if key in plugin_values:
|
|
plugin_values[key] = attr_def.convert_value(
|
|
plugin_values[key]
|
|
)
|
|
return attribute_values
|
|
|
|
@staticmethod
|
|
def get_attr_values_from_data_for_plugin(plugin, data):
|
|
"""Get attribute values for attribute definitions from data.
|
|
|
|
Args:
|
|
plugin (Union[publish.api.Plugin, Type[publish.api.Plugin]]): The
|
|
plugin for which attributes are extracted.
|
|
data(dict): Data from instance or context.
|
|
"""
|
|
|
|
if not inspect.isclass(plugin):
|
|
plugin = plugin.__class__
|
|
|
|
return (
|
|
data
|
|
.get("publish_attributes", {})
|
|
.get(plugin.__name__, {})
|
|
)
|
|
|
|
def get_attr_values_from_data(self, data):
|
|
"""Get attribute values for attribute definitions from data.
|
|
|
|
Args:
|
|
data(dict): Data from instance or context.
|
|
"""
|
|
|
|
return self.get_attr_values_from_data_for_plugin(self.__class__, data)
|
|
|
|
|
|
class OptionalPyblishPluginMixin(OpenPypePyblishPluginMixin):
|
|
"""Prepare mixin for optional plugins.
|
|
|
|
Defined active attribute definition prepared for published and
|
|
prepares method which will check if is active or not.
|
|
|
|
```
|
|
class ValidateScene(
|
|
pyblish.api.InstancePlugin, OptionalPyblishPluginMixin
|
|
):
|
|
def process(self, instance):
|
|
# Skip the instance if is not active by data on the instance
|
|
if not self.is_active(instance.data):
|
|
return
|
|
```
|
|
"""
|
|
|
|
@classmethod
|
|
def get_attribute_defs(cls):
|
|
"""Attribute definitions based on plugin's optional attribute."""
|
|
|
|
# Empty list if plugin is not optional
|
|
if not getattr(cls, "optional", None):
|
|
return []
|
|
|
|
# Get active value from class as default value
|
|
active = getattr(cls, "active", True)
|
|
# Return boolean stored under 'active' key with label of the class name
|
|
label = cls.label or cls.__name__
|
|
return [
|
|
BoolDef("active", default=active, label=label)
|
|
]
|
|
|
|
def is_active(self, data):
|
|
"""Check if plugins is active for instance/context based on their data.
|
|
|
|
Args:
|
|
data(dict): Data from instance or context.
|
|
"""
|
|
# Skip if is not optional and return True
|
|
if not getattr(self, "optional", None):
|
|
return True
|
|
attr_values = self.get_attr_values_from_data(data)
|
|
active = attr_values.get("active")
|
|
if active is None:
|
|
active = getattr(self, "active", True)
|
|
return active
|
|
|
|
|
|
class RepairAction(pyblish.api.Action):
|
|
"""Repairs the action
|
|
|
|
To process the repairing this requires a static `repair(instance)` method
|
|
is available on the plugin.
|
|
"""
|
|
|
|
label = "Repair"
|
|
on = "failed" # This action is only available on a failed plug-in
|
|
icon = "wrench" # Icon from Awesome Icon
|
|
|
|
def process(self, context, plugin):
|
|
if not hasattr(plugin, "repair"):
|
|
raise RuntimeError("Plug-in does not have repair method.")
|
|
|
|
# Get the errored instances
|
|
self.log.info("Finding failed instances..")
|
|
errored_instances = get_errored_instances_from_context(context)
|
|
|
|
# Apply pyblish.logic to get the instances for the plug-in
|
|
instances = pyblish.api.instances_by_plugin(errored_instances, plugin)
|
|
for instance in instances:
|
|
plugin.repair(instance)
|
|
|
|
|
|
class RepairContextAction(pyblish.api.Action):
|
|
"""Repairs the action
|
|
|
|
To process the repairing this requires a static `repair(instance)` method
|
|
is available on the plugin.
|
|
"""
|
|
|
|
label = "Repair"
|
|
on = "failed" # This action is only available on a failed plug-in
|
|
|
|
def process(self, context, plugin):
|
|
if not hasattr(plugin, "repair"):
|
|
raise RuntimeError("Plug-in does not have repair method.")
|
|
|
|
# Get the errored instances
|
|
self.log.info("Finding failed instances..")
|
|
errored_plugins = get_errored_plugins_from_context(context)
|
|
|
|
# Apply pyblish.logic to get the instances for the plug-in
|
|
if plugin in errored_plugins:
|
|
self.log.info("Attempting fix ...")
|
|
plugin.repair(context)
|
|
|
|
|
|
class Extractor(pyblish.api.InstancePlugin):
|
|
"""Extractor base class.
|
|
|
|
The extractor base class implements a "staging_dir" function used to
|
|
generate a temporary directory for an instance to extract to.
|
|
|
|
This temporary directory is generated through `tempfile.mkdtemp()`
|
|
|
|
"""
|
|
|
|
order = 2.0
|
|
|
|
def staging_dir(self, instance):
|
|
"""Provide a temporary directory in which to store extracted files
|
|
|
|
Upon calling this method the staging directory is stored inside
|
|
the instance.data['stagingDir']
|
|
"""
|
|
|
|
return get_instance_staging_dir(instance)
|