Skip to content
This repository has been archived by the owner on Sep 20, 2024. It is now read-only.

Publisher: Convertors for legacy instances #4020

Merged
merged 31 commits into from
Oct 26, 2022
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
2605735
Created simple item representing conversion requirement
iLLiCiTiT Oct 21, 2022
8e99d91
implemented basic of convertor
iLLiCiTiT Oct 21, 2022
971e4a2
split reset of plugins to more methods
iLLiCiTiT Oct 21, 2022
cff9990
added logic to discover convertors and find legacy items
iLLiCiTiT Oct 21, 2022
24ebd76
fix convertor creation
iLLiCiTiT Oct 21, 2022
3bdaf89
added id to legacy item
iLLiCiTiT Oct 21, 2022
e484df2
Define constant for context group
iLLiCiTiT Oct 21, 2022
3a6bc00
controller has access to convertor items
iLLiCiTiT Oct 21, 2022
b8e5e5e
create context has function to run convertor
iLLiCiTiT Oct 21, 2022
e19268c
implemented basic implementation of converter
iLLiCiTiT Oct 21, 2022
45c9448
removed unused variable
iLLiCiTiT Oct 21, 2022
245c5e9
changed label of legacy group
iLLiCiTiT Oct 24, 2022
080deda
fix list view update
iLLiCiTiT Oct 24, 2022
2787351
change labels of the message for user
iLLiCiTiT Oct 24, 2022
e94cd00
change separator size
iLLiCiTiT Oct 24, 2022
a980857
added some padding and spacing
iLLiCiTiT Oct 24, 2022
271a005
change the item look
iLLiCiTiT Oct 24, 2022
7afb2b2
change variable to use convertor instead of legacy
iLLiCiTiT Oct 24, 2022
be54ff4
rename 'convert_legacy_items' to 'trigger_convertor_items'
iLLiCiTiT Oct 24, 2022
81f7aa5
get rid of 'legacy' from variables
iLLiCiTiT Oct 24, 2022
4f70a58
renamed 'LegacySubsetConvertor' to 'SubsetConvertorPlugin'
iLLiCiTiT Oct 24, 2022
ac696ae
Merge branch 'develop' into feature/OP-4285_Convertors-for-legacy-ins…
iLLiCiTiT Oct 24, 2022
87671bc
added style for errored card message
iLLiCiTiT Oct 24, 2022
0fd5445
wrap convertor callbacks by custom exceptions
iLLiCiTiT Oct 24, 2022
9774c50
Error message box is less creator's specific
iLLiCiTiT Oct 24, 2022
3ab3582
prepare to handle convertor errors
iLLiCiTiT Oct 24, 2022
f9a75ea
handle ConvertorsOperationFailed in controller
iLLiCiTiT Oct 24, 2022
22a1191
emit card message can accept message types
iLLiCiTiT Oct 24, 2022
12a272a
added different types of card messages
iLLiCiTiT Oct 24, 2022
6f642ab
trigger reset of controller when conversion finishes
iLLiCiTiT Oct 25, 2022
698fe83
added logger to convertor
iLLiCiTiT Oct 25, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 185 additions & 0 deletions openpype/pipeline/create/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
Creator,
AutoCreator,
discover_creator_plugins,
discover_convertor_plugins,
CreatorError,
)

Expand Down Expand Up @@ -70,6 +71,41 @@ def __init__(self, host, missing_methods):
super(HostMissRequiredMethod, self).__init__(msg)


class ConvertorsOperationFailed(Exception):
def __init__(self, msg, failed_info):
super(ConvertorsOperationFailed, self).__init__(msg)
self.failed_info = failed_info


class ConvertorsFindFailed(ConvertorsOperationFailed):
def __init__(self, failed_info):
msg = "Failed to find incompatible subsets"
super(ConvertorsFindFailed, self).__init__(
msg, failed_info
)


class ConvertorsConversionFailed(ConvertorsOperationFailed):
def __init__(self, failed_info):
msg = "Failed to convert incompatible subsets"
super(ConvertorsConversionFailed, self).__init__(
msg, failed_info
)


def prepare_failed_convertor_operation_info(identifier, exc_info):
exc_type, exc_value, exc_traceback = exc_info
formatted_traceback = "".join(traceback.format_exception(
exc_type, exc_value, exc_traceback
))

return {
"convertor_identifier": identifier,
"message": str(exc_value),
"traceback": formatted_traceback
}


class CreatorsOperationFailed(Exception):
"""Raised when a creator process crashes in 'CreateContext'.

Expand Down Expand Up @@ -926,6 +962,37 @@ def apply_changes(self, changes):
self[key] = new_value


class ConvertorItem(object):
"""Item representing convertor plugin.

Args:
identifier (str): Identifier of convertor.
label (str): Label which will be shown in UI.
"""

def __init__(self, identifier, label):
self._id = str(uuid4())
self.identifier = identifier
self.label = label

@property
def id(self):
return self._id

def to_data(self):
return {
"id": self.id,
"identifier": self.identifier,
"label": self.label
}

@classmethod
def from_data(cls, data):
obj = cls(data["identifier"], data["label"])
obj._id = data["id"]
return obj


class CreateContext:
"""Context of instance creation.

Expand Down Expand Up @@ -991,6 +1058,9 @@ def __init__(
# Manual creators
self.manual_creators = {}

self.convertors_plugins = {}
self.convertor_items_by_id = {}

self.publish_discover_result = None
self.publish_plugins_mismatch_targets = []
self.publish_plugins = []
Expand Down Expand Up @@ -1071,6 +1141,7 @@ def reset(self, discover_publish_plugins=True):

with self.bulk_instances_collection():
self.reset_instances()
self.find_convertor_items()
self.execute_autocreators()

self.reset_finalization()
Expand Down Expand Up @@ -1125,6 +1196,12 @@ def reset_plugins(self, discover_publish_plugins=True):
Reloads creators from preregistered paths and can load publish plugins
if it's enabled on context.
"""

self._reset_publish_plugins(discover_publish_plugins)
self._reset_creator_plugins()
self._reset_convertor_plugins()

def _reset_publish_plugins(self, discover_publish_plugins):
import pyblish.logic

from openpype.pipeline import OpenPypePyblishPluginMixin
Expand Down Expand Up @@ -1166,6 +1243,7 @@ def reset_plugins(self, discover_publish_plugins=True):
self.publish_plugins = plugins_by_targets
self.plugins_with_defs = plugins_with_defs

def _reset_creator_plugins(self):
# Prepare settings
system_settings = get_system_settings()
project_settings = get_project_settings(self.project_name)
Expand Down Expand Up @@ -1217,6 +1295,27 @@ def reset_plugins(self, discover_publish_plugins=True):

self.creators = creators

def _reset_convertor_plugins(self):
convertors_plugins = {}
for convertor_class in discover_convertor_plugins():
if inspect.isabstract(convertor_class):
self.log.info(
"Skipping abstract Creator {}".format(str(convertor_class))
)
continue

convertor_identifier = convertor_class.identifier
if convertor_identifier in convertors_plugins:
self.log.warning((
"Duplicated Converter identifier. "
"Using first and skipping following"
))
continue

convertors_plugins[convertor_identifier] = convertor_class(self)

self.convertors_plugins = convertors_plugins

def reset_context_data(self):
"""Reload context data using host implementation.

Expand Down Expand Up @@ -1346,6 +1445,14 @@ def creator_removed_instance(self, instance):

self._instances_by_id.pop(instance.id, None)

def add_convertor_item(self, convertor_identifier, label):
self.convertor_items_by_id[convertor_identifier] = ConvertorItem(
convertor_identifier, label
)

def remove_convertor_item(self, convertor_identifier):
self.convertor_items_by_id.pop(convertor_identifier, None)

@contextmanager
def bulk_instances_collection(self):
"""Validate context of instances in bulk.
Expand Down Expand Up @@ -1413,6 +1520,37 @@ def reset_instances(self):
if failed_info:
raise CreatorsCollectionFailed(failed_info)

def find_convertor_items(self):
"""Go through convertor plugins to look for items to convert.

Raises:
ConvertorsFindFailed: When one or more convertors fails during
finding.
"""

self.convertor_items_by_id = {}

failed_info = []
for convertor in self.convertors_plugins.values():
try:
convertor.find_instances()

except:
iLLiCiTiT marked this conversation as resolved.
Show resolved Hide resolved
failed_info.append(
prepare_failed_convertor_operation_info(
convertor.identifier, sys.exc_info()
)
)
self.log.warning(
"Failed to find instances of convertor \"{}\"".format(
convertor.identifier
),
exc_info=True
)

if failed_info:
raise ConvertorsFindFailed(failed_info)

def execute_autocreators(self):
"""Execute discovered AutoCreator plugins.

Expand Down Expand Up @@ -1668,3 +1806,50 @@ def collection_shared_data(self):
"Accessed Collection shared data out of collection phase"
)
return self._collection_shared_data

def run_convertor(self, convertor_identifier):
"""Run convertor plugin by it's idenfitifier.

Conversion is skipped if convertor is not available.

Args:
convertor_identifier (str): Identifier of convertor.
"""

convertor = self.convertors_plugins.get(convertor_identifier)
if convertor is not None:
convertor.convert()

def run_convertors(self, convertor_identifiers):
"""Run convertor plugins by idenfitifiers.

Conversion is skipped if convertor is not available.

Args:
convertor_identifiers (Iterator[str]): Identifiers of convertors
to run.

Raises:
ConvertorsConversionFailed: When one or more convertors fails.
"""

failed_info = []
for convertor_identifier in convertor_identifiers:
try:
self.run_convertor(convertor_identifier)

except:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not use bare except:, it also catches unexpected events like memory errors, interrupts, system exit, and so on. Prefer except Exception:. If you're sure what you're doing, be explicit and write except BaseException:.
do not use bare 'except'

failed_info.append(
prepare_failed_convertor_operation_info(
convertor_identifier, sys.exc_info()
)
)
self.log.warning(
"Failed to convert instances of convertor \"{}\"".format(
convertor_identifier
),
exc_info=True
)

if failed_info:
raise ConvertorsConversionFailed(failed_info)
103 changes: 103 additions & 0 deletions openpype/pipeline/create/creator_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,97 @@ def __init__(self, message):
super(CreatorError, self).__init__(message)


@six.add_metaclass(ABCMeta)
class SubsetConvertorPlugin(object):
"""Helper for conversion of instances created using legacy creators.

Conversion from legacy creators would mean to loose legacy instances,
convert them automatically or write a script which must user run. All of
these solutions are workign but will happen without asking or user must
know about them. This plugin can be used to show legacy instances in
Publisher and give user ability to run conversion script.

Convertor logic should be very simple. Method 'find_instances' is to
look for legacy instances in scene a possibly call
pre-implemented 'add_convertor_item'.

User will have ability to trigger conversion which is executed by calling
'convert' which should call 'remove_convertor_item' when is done.

It does make sense to add only one or none legacy item to create context
for convertor as it's not possible to choose which instace are converted
and which are not.

Convertor can use 'collection_shared_data' property like creators. Also
can store any information to it's object for conversion purposes.

Args:
create_context
"""

def __init__(self, create_context):
self._create_context = create_context

@abstractproperty
def identifier(self):
"""Converted identifier.

Returns:
str: Converted identifier unique for all converters in host.
"""

pass

@abstractmethod
def find_instances(self):
"""Look for legacy instances in the scene.

Should call 'add_convertor_item' if there is at least one instance to
convert.
"""

pass

@abstractmethod
def convert(self):
"""Conversion code."""

pass

@property
def create_context(self):
"""Quick access to create context."""

return self._create_context

@property
def collection_shared_data(self):
"""Access to shared data that can be used during 'find_instances'.

Retruns:
Dict[str, Any]: Shared data.

Raises:
UnavailableSharedData: When called out of collection phase.
"""

return self._create_context.collection_shared_data

def add_convertor_item(self, label):
"""Add item to CreateContext.

Args:
label (str): Label of item which will show in UI.
"""

self._create_context.add_convertor_item(self.identifier, label)

def remove_convertor_item(self):
"""Remove legacy item from create context when conversion finished."""

self._create_context.remove_convertor_item(self.identifier)


@six.add_metaclass(ABCMeta)
class BaseCreator:
"""Plugin that create and modify instance data before publishing process.
Expand Down Expand Up @@ -469,6 +560,10 @@ def discover_creator_plugins():
return discover(BaseCreator)


def discover_convertor_plugins():
return discover(SubsetConvertorPlugin)


def discover_legacy_creator_plugins():
from openpype.lib import Logger

Expand Down Expand Up @@ -526,6 +621,9 @@ def register_creator_plugin(plugin):
elif issubclass(plugin, LegacyCreator):
register_plugin(LegacyCreator, plugin)

elif issubclass(plugin, SubsetConvertorPlugin):
register_plugin(SubsetConvertorPlugin, plugin)


def deregister_creator_plugin(plugin):
if issubclass(plugin, BaseCreator):
Expand All @@ -534,12 +632,17 @@ def deregister_creator_plugin(plugin):
elif issubclass(plugin, LegacyCreator):
deregister_plugin(LegacyCreator, plugin)

elif issubclass(plugin, SubsetConvertorPlugin):
deregister_plugin(SubsetConvertorPlugin, plugin)


def register_creator_plugin_path(path):
register_plugin_path(BaseCreator, path)
register_plugin_path(LegacyCreator, path)
register_plugin_path(SubsetConvertorPlugin, path)


def deregister_creator_plugin_path(path):
deregister_plugin_path(BaseCreator, path)
deregister_plugin_path(LegacyCreator, path)
deregister_plugin_path(SubsetConvertorPlugin, path)
Loading